In this post, I will play with some classic heap exploitation tricks in Linux Glibc, in order to better understand the linux kernel heap management.
Background
Heap is a logically continuous area that consists of many chunks. For heap allocator such as ptmalloc, the minimum operation unit is the chunk. Chunks are classified into 4 different categories. 1. Allocated chunk 2, Free chunk; 3, Top chunk; 4. Last Remainder chunk. malloc maintains a finite state machine whitin the four types of chunks. A detailed analysis of ptmalloc anylasis can be find here (in Chinese) ptmalloc code review in glibc 2.12.1
Fastbin
Fastbin is one of the four freelist data structures (fast bin, unsorted bin, small bin, large bin) that are used to hold free chunks. Many attacks relates to fastbins.
In malloc_state (heap header), there is an array called fastbinsY that manages the fast bins. In Linux, chunks that are smaller than MAX_FAST_SIZE (80 on 32-bit machine, 160 on 64-bit machine) will be treated as fastbins. The figure below demonstrates the runtime structure of fastbin.
1 | 1570 /* The maximum fastbin request size we support */ |
Techniques
fastbin_dup_stack
fastbin_dup_into_stack introduce a technique to forge a fake chunk by modifiying(heap overflow or double free) a specific fast bin’s FD
to the target address.
The trick has three steps. Step 1, we put the a victim chunk into bins by freeing it. Step 2, we need a double free or heap overflow(Double free puts the victim chunk into fastbin, and modified its fd using exsting reference to the chunk. Heap overflow can directly overflow the next chunk’s fd when it is still in use) to modify the victim chunk’s fd to the target address to claim that there is another chunk there. It is worth noting that in order to pass the malloc check we also need the allocation point at the target address has a legal size
. Step 3, we malloc two times. The first time will put the fake chunk at the head of free list. The second time will malloc a chunk at the target address.
The 0ctfbabyheap challenge can be solved with this trick. The trick was used for twice. In the first time, we are trying to leak the libc base. To achieve this goal, we forge a fast chunk at the smallbins (Our target address is at smallbin is because smallbin has bk
pointer which points to the main_arena
when the smallbin is at the head of smallbin free list). The following code demonstrate the processs.
1 | #STEP 1 Free a victim |
unlink
unsafe unlink technique can be used when you have a pointer at a known location (e.g., .bss) that points to a region you can call unlink on. The most common scenario is a vulnerable buffer that can be overflown and has a global pointer. The attack effect is let this pointer points to &pointer - 0x18 (ptr = &ptr - 0x18
).
To demonstrate how the attack effect is achieved, we assume we have p0
and p1
, and the two chunk are the neighbours. First, we craft a fake chunk
in p0
. We also need to modified the prev_size in p1
to pass the check. In this way, we cheat glibc thatp1
‘s neighbour is the freed fake chunk
. Second, we free p1
to let fake chunk
also be freed at the same time. The process can be demonstrated as the following picture.
The stkof challenge can be solved with this trick.
1 | from pwn import * |
fastbin_dup_consolidate
fastbin_dup_consolidate abuses the malloc_consolidate
in malloc.c:L3672 to allocate a freed fast bin into unsorted bins. The underlying malloc code is as followed.
1 | fb = &fastbin (av, 0); |
In the Sleepyholder challenge, we leverage fastbin_dup_consolidate to set the previous_in_use as false in the neighbour chunk of the fake chunk. By this way, the unlink will unlink the fake chunk when freeing its neighbour. Here, it is worth nothing that we need double free small chunk because we want the big chunk’s pre_inuse to be false. If we don’t free it twice. malloc will use the record from small bins (the destination of malloc_consolidate for fast chunk) and mark the big chunk’s pre_inuse as true.
1 | from pwn import * |
House of Spirit
House of Spirit Essentially it involves us writing two integers (chunk size) to an area we control (it will be in the bss global variables section in the following challenge) to make fake fast chunks.
The prerequisite of forging fake chunks can be figured out from the malloc.c1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//Constraint 1 ISMMAP should not be 1
//__libc_free (void *mem)
if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
......
munmap_chunk (p);
return;
}
//Constraint 2 nextchunk size of fake chunk1 should between 2* SIZE_SZ and av->system_mem
// _int_free
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
//Constraint 3 fake chunk address must aligned
// _int_free
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
{
errstr = "free(): invalid pointer";
Hence, we can forge two chunks (chunk1 is fast chunk) by injecting two size in a appropriate offset. It is worth noting that constraint 3 is likely to be confused with fastbin attack (e.g., babyheap 0ctf 2017dow). When malloc()
something, unlike free()
, we in fact don’t need address alignment.
The Hack.Lu Oreo challenge can be addressed by hos. In this challenge, we have a heap overflow to let us have the ptr in free(ptr) points to any address we want. We also find that we can inject two integers into .bss by absuing two global variables (rifle_cnt and messages).
The exploitation process is quite trivial. The only thing that took me some time is how to reconstruct structs in IDA[3].
1 | #!/usr/bin/env python |
Reference
[1] https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/
[2] ptmalloc code review http://fxiao.me/files/glibc-2.12.1-ptmalloc.pdf
[3] https://www.hex-rays.com/products/ida/support/tutorials/structs.shtml