Unlink Exploit
Understanding how unlinking happens in libc (old and modern) and how to exploit it
Unlink Exploit
In this blog I will try to explain the unlink heap exploit technique and also show examples of how it works. I have had a hard time understanding how this technique works, so i thought let me try to explain it to someone else (via this blog post at least) to see if i really understood the technique.
Overview
These technique is triggered by the unlink macro hence the name. The unlink macro is used to remove a free chunks from the middle of a bin (double-linked list), the bins include unsorted bin, small bin, and large bin. The tcache/fastbin are singly-linked-lists they won’t apply the unlink mechanism . This exploit has a write-what-where primitive .
When a chunk is remove from a bin, the unlink() macro is called on the chunk. The unlink macro looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
#define unlink(AV, P, BK, FD) { \
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV); \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (chunksize_nomask (P))
} \
}
At first this macro was difficult for me to understand so here is another way to view it.
1
2
3
4
5
6
7
8
9
10
11
/*
Curr = current chunk (P)
Next = next chunk in doubly linked list (FD)
Prev = previous chunk in doubly linked list (BK)
*/
Next = Curr->fd;
Prev = Curr->bk;
Next->bk = Prev;
Prev->fd = Next;
The macro is changing the pointers of the chunks before (BK/Prev) and after (FD/Next) the victim chunk (P/Curr). The below images show how the macro works in the unlink process.
The middle chunk is being
unlinked.
Before Unlink
After Unlink
To clearly understand this technique we first have to understand heap consolidation.
Heap Consolidation.
When a chunk is freed, it is put into the unsorted bin. The chunk will first be merged with neighbouring free chunks, then added to the unsorted bin as a larger free chunk for future allocations. How it works:
- Consolidate backward If previous chunk in memory is not in use (
prev_inuse bit == 0),unlinkis called on the previous chunk to take it off the free list. The previous chunk’s size is then added to the current chunk’s size, and the current chunk pointer points to the previous chunk. - Consolidate forward If next chunk in memory is not the top chunk and not in use (
confirmed by next-to-next chunk's prev_inuse bit),unlinkis called on the next chunk to take it off the free list. The next chunk’s size is added to the current chunk’s size. - Add consolidated chunk to unsorted bin. The
unlinkmacro is called.
Unlink Exploit
In older glibc versions we can use this vulnerability to modify the fd and bk pointers of the P/Curr chunk to perform arbitrary read/write primitive after the macro executes.
Lets break down the unlink macro from a C language’s point of view.
- 32-bit architecture
1 2 3 4
BK = *(P + (0x4 * 3)); // Prev = *(Curr + (0x4 * 3)); FD = *(P + (0x4 * 2)); // Next = *(Curr + (0x4 *2)); *(FD + (0x4 * 3)) = BK; // *(Next + (0x4 * 3)) = Prev *(BK + (0x4 * 2)) = FD; // *(Prev + (0x4 * 2)) = Next
- 64-bit architecture
1 2 3 4
BK = *(P + (0x8 * 3)); // Prev = *(Curr + (0x8 * 3)); FD = *(P + (0x8 * 2)); // Next = *(Curr + (0x8 *2)); *(FD + (0x8 * 3)) = BK; // *(Next + (0x8 * 3)) = Prev *(BK + (0x8 * 2)) = FD; // *(Prev + (0x8 * 2)) = Next
If we had fully control of the chunk P/Curr (first chunk) , we can put any values to P->bk/Curr->bk and P->bk/Curr->bk (usually has to be global variable), and we can achieve arbitrary write.
If we can overflow the first chunk into the second chunk, then we are free to put any value to bk and fd of the second chunk.
We can achieve arbitrary write by:
- change
fdof fake chunk to point nearglobal ptrso thatP->fd->bk = P - change
fdof fake chunk to point near toglobal pointerso thatP->bk->fd = P - trigger ``unlink`
- use first chunk to overwrite itself to point to an arbitrary location.
We need to overflow to the next chunk and unset the prev_inuse bit so that when we free the second chunk backward consolidation can happen and the unlink macro will be called.
Steps
- Allocate two adjacent chunk.
- Setup
fdandbkof first chunk. (near global ptr) - Overflow on first chunk to control the metadata of the second chunk.
- Set the
prev_sizeto 0 - Unset the
prev_inusebit - Set the size. (larger the fastbins size)
- Set the
- Trigger the
unlink. (Free second chunk) - Overwrite
fdandbkof the fake chunk.fdpoint to target address that we want to overwrite. (where)bkpoints to address that we want to write. (what)
- Control flow will be redirected.
- Pwned.

