"Heap corruption vulnerabilities continue to offer rich attack surfaces. This writeup examines the mechanics of a Use-After-Free (UAF) vulnerability, specifically targeting the tcache (Thread Local Cache) heap management structure introduced in modern glibc allocators."

Exploiting Heap Overflows: Anatomy of a Modern Use-After-Free

Memory safety vulnerabilities remain one of the most prominent attack vectors in native application exploitation. While stack-based exploits have become increasingly difficult due to compilers introducing compiler-enforced protections (like Stack Canaries and SafeStack), Heap corruption vulnerabilities continue to offer rich attack surfaces.

This writeup examines the mechanics of a Use-After-Free (UAF) vulnerability, specifically targeting the tcache (Thread Local Cache) heap management structure introduced in modern glibc allocators.


1. The Vulnerability Pattern

A Use-After-Free (UAF) occurs when a pointer is not cleared (set to NULL) after the memory it points to has been deallocated with free(). If the application subsequently references this "dangling pointer," it can lead to undefined behavior, system crashes, or arbitrary code execution.

Here is a simplified C vulnerability snippet demonstrating the flaw:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
struct User {
    char name[16];
    void (*print_privileges)();
};
 
void guest_privileges() {
    printf("[*] Role: Guest User\n");
}
 
void admin_privileges() {
    printf("[!] WARNING: Root Administrative Shell Spawned!\n");
    system("/bin/sh");
}
 
int main() {
    struct User *user = malloc(sizeof(struct User));
    strcpy(user->name, "Operator");
    user->print_privileges = guest_privileges;
 
    printf("[*] Allocating operator... Address: %p\n", user);
    user->print_privileges();
 
    // Deallocate the memory block
    free(user);
    printf("[*] Freed operator block!\n");
 
    // Danger: Pointer is NOT set to NULL (Dangling Pointer)
    // Allocating a new string block which will re-use the same chunk
    char *malicious_payload = malloc(sizeof(struct User));
    printf("[*] New allocation re-using same chunk space: %p\n", malicious_payload);
 
    // Overwriting print_privileges function pointer with admin_privileges address
    long *overwrite_ptr = (long *)(malicious_payload + 16);
    *overwrite_ptr = (long)admin_privileges;
 
    // Use-After-Free: Accessing dangling pointer
    printf("[!] Calling user->print_privileges() after reallocation...\n");
    user->print_privileges();
 
    return 0;
}

2. Heap Bin Poisoning (tcache Exploitation)

In glibc (version 2.26 and later), the Thread Local Cache (tcache) manages singly-linked bins for small memory chunks. Because speed is favored, tcache chunks are stored in a singly-linked list (LIFO stack) without sanity validation in earlier versions.

When a chunk is freed, it is placed in the tcache list:

tcache_entry -> [Chunk A] -> [Chunk B] -> [NULL]

The Attack Flow

  1. Freeing Chunks: Freeing two chunks of the same size.
  2. Double Free / Poisoning: If we corrupt the forward pointer (next) of a freed chunk in memory to point to an arbitrary target address (e.g., the global offset table, or a stack address), the next allocator call to malloc() returns our poisoned target address.

3. Debugging Heap State under GDB

Using GDB with the pwndbg or peda extension, we can observe the allocation bins:

gdb-peda$ heap bin inspect
heapbinstscachetcachebins0x20[1]:0x55555555a2a0>0x55555555a0b0(overlap)0x30[2]:0x55555555a300>0x7ffff7fb2560(poisonedtarget)heap bins tscache tcachebins 0x20 [ 1]: 0x55555555a2a0 -> 0x55555555a0b0 (overlap) 0x30 [ 2]: 0x55555555a300 -> 0x7ffff7fb2560 (poisoned target) vmmap
Start Address End Address Perm Path
0x55555555a000 0x55555555b000 rwb [heap]
0x7ffff7fb2000 0x7ffff7fb3000 rw- /lib/x86_64-linux-gnu/libc.so.6
$ c
Continuing...

4. Modern Mitigations

Modern heap implementations introduce protections to make direct exploitation significantly more difficult:

  • Safe Linking: Introduced in glibc 2.32, pointers in the tcache and fastbins are obfuscated (XORed with a randomized base page address), preventing direct overwrite poisoning without a heap memory leak.
  • tcache Double Free Protection: A validation check uses a key field inside the chunk structure to verify whether a chunk is already present in the tcache array before pushing it.