バッファオーバーフロー入門のためにしたメモ
書き出し
低い分野の脆弱性が理解できていないので実際にやった実験とそれを実況形式で書き示します。
環境
$ cat /etc/os-release NAME="Ubuntu" VERSION="14.04, Trusty Tahr" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 14.04 LTS" VERSION_ID="14.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" $ uname -a Linux nori 3.13.0-24-generic #46-Ubuntu SMP Thu Apr 10 19:11:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
実験
実験用コード
buf_over.c
#include<stdio.h> int main(int argc,char *argv[]) { char buf[64];//※1 printf("start fgets.\n"); fgets(buf,128,stdin);//※2 printf("\nend fgets.\n"); printf(buf); return 0; }
3番目のprintf(3)で書式文字列攻撃ができそうだけどここでは確認用なので放置。
※1 : 関数内変数を文字型で64バイト
※2 : fgets(3)で標準入力を受けるも128バイトと大幅なサイズオーバー
観察
Hello Worldなどを読んで若干ではあるがStackの動きやメモリへの書き込みを学んでいるので、ここで少し考察をしながら確認をして行こうと考えます。
[1] buf[64]の確保
main関数内で定義されたbuf[64]は、スタックフレーム上にメモリ空間を確保すると思われる。
おおよそasmでのこの部分で確保しているのであろう。
0x0804849d <+0>: push ebp 0x0804849e <+1>: mov ebp,esp 0x080484a0 <+3>: and esp,0xfffffff0 => 0x080484a3 <+6>: sub esp,0x50 //ここで変数の確保 0x080484a6 <+9>: mov DWORD PTR [esp],0x8048580
実際にフレームを感が思い浮かべてみるとこのような形になるのだろうか。
0x00000000方向 低位アドレス +------------------+ |buffer[64] | +------------------+ |Saved EBP | +------------------+ |Return Address | +------------------+ 0xffffffff方向 高位アドレス
それでは実施のリターンアドレスはどのように格納されているのだろうか?それを確認しに行こう。
gdb-peda$ where #0 0x080484ca in main () #1 0xf7e2caf3 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6 #2 0x080483c1 in _start ()
バックとレースではこのように表示されている。 それでは実際のスタック領域ではどのようになっているのだろうか。
gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x00000000 0x00c30000 0x00000001 0x08048321 0xffffd6a0: 0xffffd894 0x0000002f 0x0804a000 0x08048542 0xffffd6b0: 0x00000001 0xffffd774 0xffffd77c 0xf7e464ad 0xffffd6c0: 0xf7fc03c4 0xf7ffd000 0x080484fb 0xf7fc0000 0xffffd6d0: 0x080484f0 0x00000000 0x00000000 0xf7e2caf3 0xffffd6e0: 0x00000001 0xffffd774 0xffffd77c 0xf7feae6a 0xffffd6f0: 0x00000001 0xffffd774 0xffffd714 0x0804a01c 0xffffd700: 0x08048250 0xf7fc0000 0x00000000 0x00000000 0xffffd710: 0x00000000 0x50781f72 0x6a40bb62 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9
0xffffd6d0の行で6列目にwhere
によって確認された0xf7e2caf3を発見。
つまるところこの位置が目印となるReturn Addressになる。
※ 自分用メモ : sub esp,0x50 でポインタを移動させているだけなのでメモリ内部は初期化されていない。
[2] buf[64]への書き込み
実際に書き込みをしてみる。
このままgdbを進めてみると、fgets(3)が作動する。
0x080484c7 <+42>: mov DWORD PTR [esp],eax 0x080484ca <+45>: call 0x8048360 <fgets@plt> => 0x080484cf <+50>: mov DWORD PTR [esp],0x804858d
実際にA*64を入力してみて、64も自分のメモリ空間を埋めてみる。
すると、メモリ領域に変化が見えた。
ESP: 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...)
ESPでは0xffffd680のアドレスをさしており、そのアドレスが参照しているアドレスが0xffffd690となる。
実際にそのアドレスを見てみると、
gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6d0: 0x0804000a 0x00000000 0x00000000 0xf7e2caf3 0xffffd6e0: 0x00000001 0xffffd774 0xffffd77c 0xf7feae6a 0xffffd6f0: 0x00000001 0xffffd774 0xffffd714 0x0804a01c 0xffffd700: 0x08048250 0xf7fc0000 0x00000000 0x00000000 0xffffd710: 0x00000000 0x9ce8c9fd 0xa6d06ded 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9
そう0x414141....とメモリ空間を埋めている。64文字分しっかりと埋めていた。
この入力を実際に実行ファイルに入力してみる。
$ python -c 'print("A"*64)'|./buf start fgets. end fgets. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
問題なく終了した。それでは、64文字以上の文字列を入れたらどうなるのだろうか?ここで初歩的な疑問にやっとのことたどり着いた。
[3] 過剰な入力
それではどれほど入力してみようか、と私は考える。はじめはできる限り多めの入力をしておこう。
ならば上限である128文字を入力しよう。
$ python -c 'print("A"*128)'| ./buf start fgets. end fgets. Segmentation fault (コアダンプ)
まぁ、そうなりますはな....
gdbで実際に見てみよう。
gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x00000000 0x00c30000 0x00000001 0x08048321 0xffffd6a0: 0xffffd894 0x0000002f 0x0804a000 0x08048542 0xffffd6b0: 0x00000001 0xffffd774 0xffffd77c 0xf7e464ad 0xffffd6c0: 0xf7fc03c4 0xf7ffd000 0x080484fb 0xf7fc0000 0xffffd6d0: 0x080484f0 0x00000000 0x00000000 0xf7e2caf3 0xffffd6e0: 0x00000001 0xffffd774 0xffffd77c 0xf7feae6a 0xffffd6f0: 0x00000001 0xffffd774 0xffffd714 0x0804a01c 0xffffd700: 0x08048250 0xf7fc0000 0x00000000 0x00000000 0xffffd710: 0x00000000 0x818fa802 0xbbb70c12 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9 gdb-peda$ n AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA [-----------------------------------registers-----------------------------------] EAX: 0xffffd690 ('A' <repeats 15 times>...) EBX: 0xf7fc0000 --> 0x1acda8 ECX: 0xffffd690 ('A' <repeats 15 times>...) EDX: 0xf7fc18a4 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0xffffd6d8 ('A' <repeats 15 times>...) ESP: 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...) EIP: 0x80484cf (<main+50>: mov DWORD PTR [esp],0x804858d) [-------------------------------------code--------------------------------------] 0x80484c3 <main+38>: lea eax,[esp+0x10] 0x80484c7 <main+42>: mov DWORD PTR [esp],eax 0x80484ca <main+45>: call 0x8048360 <fgets@plt> => 0x80484cf <main+50>: mov DWORD PTR [esp],0x804858d 0x80484d6 <main+57>: call 0x8048370 <puts@plt> 0x80484db <main+62>: lea eax,[esp+0x10] 0x80484df <main+66>: mov DWORD PTR [esp],eax 0x80484e2 <main+69>: call 0x8048350 <printf@plt> [-------------------------------------stack-------------------------------------] 00:0000| esp 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...) 01:0004| 0xffffd684 --> 0x80 02:0008| 0xffffd688 --> 0xf7fc0c20 --> 0xfbad2288 03:0012| 0xffffd68c --> 0xf7e462f3 (add ebx,0x179d0d) 04:0016| ecx eax 0xffffd690 ('A' <repeats 15 times>...) 05:0020| 0xffffd694 ('A' <repeats 15 times>...) 06:0024| 0xffffd698 ('A' <repeats 15 times>...) 07:0028| 0xffffd69c ('A' <repeats 15 times>...) [-------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value 0x080484cf in main () gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6d0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6e0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6f0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd700: 0x41414141 0x41414141 0x41414141 0x00414141 0xffffd710: 0x00000000 0x818fa802 0xbbb70c12 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9
圧倒的塗りつぶし!
0x41(A)で塗りつぶされてReturn Address まで塗りつぶされました。
[4] どこでSegmentation fault?
では実際にどこでSegmentation faultが発生したのか?僕としてはそこが気になりました。
gdbを先に進めましょう。
fgets(3)の後にバックとレースをしてみますと、汚染が常に進んでおり、retに0x414141...がたむろする状況。
gdb-peda$ where #0 0x080484cf in main () #1 0x41414141 in ?? () #2 0x41414141 in ?? () #3 0x41414141 in ?? () #4 0x41414141 in ?? () #5 0x41414141 in ?? () #6 0x41414141 in ?? () #7 0x41414141 in ?? () #8 0x41414141 in ?? () #9 0x41414141 in ?? () #10 0x41414141 in ?? () #11 0x41414141 in ?? () #12 0x41414141 in ?? () #13 0x00414141 in ?? () #14 0x00000000 in ?? ()
この時点では、#1のリターンアドレス、つまりmain()終了時のret時にSegmentation faultが発生するのではないかと今の所は考えている。
では、進めよう。
実際にその通りなのかもしれない。
[-----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xf7fc0000 --> 0x1acda8 ECX: 0x0 EDX: 0xf7fc1898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x41414141 (b'AAAA') ESP: 0xffffd6dc ('A' <repeats 15 times>...) EIP: 0x80484ed (<main+80>: ret) [-------------------------------------code--------------------------------------] 0x80484e2 <main+69>: call 0x8048350 <printf@plt> 0x80484e7 <main+74>: mov eax,0x0 0x80484ec <main+79>: leave => 0x80484ed <main+80>: ret 0x80484ee: xchg ax,ax 0x80484f0 <__libc_csu_init>: push ebp 0x80484f1 <__libc_csu_init+1>: push edi 0x80484f2 <__libc_csu_init+2>: xor edi,edi [-------------------------------------stack-------------------------------------] 00:0000| esp 0xffffd6dc ('A' <repeats 15 times>...) 01:0004| 0xffffd6e0 ('A' <repeats 15 times>...) 02:0008| 0xffffd6e4 ('A' <repeats 15 times>...) 03:0012| 0xffffd6e8 ('A' <repeats 15 times>...) 04:0016| 0xffffd6ec ('A' <repeats 15 times>...) 05:0020| 0xffffd6f0 ('A' <repeats 15 times>...) 06:0024| 0xffffd6f4 ('A' <repeats 15 times>...) 07:0028| 0xffffd6f8 ('A' <repeats 15 times>...) [-------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value 0x080484ed in main () gdb-peda$ where #0 0x080484ed in main () #1 0x41414141 in ?? () #2 0x41414141 in ?? () #3 0x41414141 in ?? () #4 0x41414141 in ?? () #5 0x41414141 in ?? () #6 0x41414141 in ?? () #7 0x41414141 in ?? () #8 0x41414141 in ?? () #9 0x41414141 in ?? () #10 0x41414141 in ?? () #11 0x41414141 in ?? () #12 0x41414141 in ?? () #13 0x00414141 in ?? () #14 0x00000000 in ?? () gdb-peda$ disas Dump of assembler code for function main: 0x0804849d <+0>: push ebp 0x0804849e <+1>: mov ebp,esp 0x080484a0 <+3>: and esp,0xfffffff0 0x080484a3 <+6>: sub esp,0x50 0x080484a6 <+9>: mov DWORD PTR [esp],0x8048580 0x080484ad <+16>: call 0x8048370 <puts@plt> 0x080484b2 <+21>: mov eax,ds:0x804a028 0x080484b7 <+26>: mov DWORD PTR [esp+0x8],eax 0x080484bb <+30>: mov DWORD PTR [esp+0x4],0x80 0x080484c3 <+38>: lea eax,[esp+0x10] 0x080484c7 <+42>: mov DWORD PTR [esp],eax 0x080484ca <+45>: call 0x8048360 <fgets@plt> 0x080484cf <+50>: mov DWORD PTR [esp],0x804858d 0x080484d6 <+57>: call 0x8048370 <puts@plt> 0x080484db <+62>: lea eax,[esp+0x10] 0x080484df <+66>: mov DWORD PTR [esp],eax 0x080484e2 <+69>: call 0x8048350 <printf@plt> 0x080484e7 <+74>: mov eax,0x0 0x080484ec <+79>: leave => 0x080484ed <+80>: ret End of assembler dump.
retに至った時点で常にebpが0x414141...となっておりこのまま進めてもSegmentation faultとなるであろう。
その後実際にstep inで実行してみると、
gdb-peda$ si [-----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xf7fc0000 --> 0x1acda8 ECX: 0x0 EDX: 0xf7fc1898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x41414141 (b'AAAA') ESP: 0xffffd6e0 ('A' <repeats 15 times>...) EIP: 0x41414141 (b'AAAA') [-------------------------------------code--------------------------------------] Invalid $PC address: 0x41414141 [-------------------------------------stack-------------------------------------] 00:0000| esp 0xffffd6e0 ('A' <repeats 15 times>...) 01:0004| 0xffffd6e4 ('A' <repeats 15 times>...) 02:0008| 0xffffd6e8 ('A' <repeats 15 times>...) 03:0012| 0xffffd6ec ('A' <repeats 15 times>...) 04:0016| 0xffffd6f0 ('A' <repeats 15 times>...) 05:0020| 0xffffd6f4 ('A' <repeats 15 times>...) 06:0024| 0xffffd6f8 ('A' <repeats 15 times>...) 07:0028| 0xffffd6fc ('A' <repeats 15 times>...) [-------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value 0x41414141 in ?? ()
となっておりEIPの汚染を確実に可能とした。
結論
スタック状に格納されるローカル変数を汚染するための方法を理解できたと思う。
次はもう少し攻撃的なものを覚えたい。
参考書籍/サイト
ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ Debugging with GDB - データの検査 セキュリティコンテストチャレンジブック -CTFで学ぼう! 情報を守るための戦い方