Tcache Bin Attack
{{#include ../../banners/hacktricks-training.md}}
Basic Information
For more information about what a Tcache bin is, check this page:
{{#ref}}
bins-and-memory-allocations.md
{{#endref}}
The Tcache attack (also known as Tcache poisoning) is the tcache equivalent of a fast-bin attack: the attacker corrupts the next pointer stored in a freed tcache entry so that a later malloc() returns an attacker-chosen address.
This primitive is only useful if the attacker can first write into a freed chunk. Common ways of getting that primitive are explained here:
{{#ref}}
overwriting-a-freed-chunk.md
{{#endref}}
Historically, this was introduced together with tcache in glibc 2.26 and was initially very easy to exploit. Modern glibc added several checks, so the attack is still relevant, but it now depends a lot more on the target version and the primitive you start from.
Modern constraints
- glibc 2.29+ added stronger tcache double-free detection using the
keyfield stored inside a freedtcache_entry. A plainfree(A); free(B); free(A);usually aborts now unless thekeycheck is bypassed or the chunk is reintroduced through another path. - glibc 2.32+ added safe-linking to singly-linked allocator lists (
tcacheandfastbins). The storednextpointer is no longer raw, but mangled as:
stored_next = target ^ (chunk_addr >> 12)
Therefore, a modern tcache poisoning usually needs either:
- A heap leak to compute the protected pointer correctly, or
- A different primitive that bypasses/abuses safe-linking instead of forging the pointer directly.
- Alignment checks also became stricter, which is why a bad poisoned pointer now tends to crash with malloc(): unaligned tcache chunk detected.
- glibc 2.34+ removed the old malloc hooks from the active API, so classic end goals such as overwriting __malloc_hook / __free_hook should be treated as version-specific legacy targets, not as the default modern outcome.
In practice, the modern end goal is usually one of these:
- Return a chunk on top of another heap object to get arbitrary read/write.
- Return a chunk on a writable global / application structure and corrupt a code pointer later used by the program.
- Return a chunk on top of a structure later abused for FSOP, ROP, or another post-write primitive.
What a poisoned tcache entry looks like
A freed tcache chunk is reused as a tcache_entry:
struct tcache_entry {
struct tcache_entry *next;
uintptr_t key;
};
So, after freeing a chunk, the first qword of its user data becomes the singly-linked-list pointer and the next qword is used for the double-free check. If the vulnerability lets you overwrite either of them, you can often turn it into:
- Arbitrary allocation by corrupting
next - Double-free bypass by corrupting
key
Basic modern poisoning flow
- Obtain a primitive that lets you edit a freed chunk.
- Free a chunk of the target tcache size.
- Leak or infer the freed chunk address if safe-linking is enabled.
- Overwrite its
nextpointer with the mangled version of the target address. - Allocate once to consume the corrupted entry.
- Allocate again to get a chunk returned at the chosen target.
For glibc 2.32+ the forged value is typically:
fake_next = target ^ (victim_chunk_addr >> 12)
If the returned pointer is not aligned to the allocator expectations, malloc() will usually abort before you get control.
Tcache indexes attack
Usually it's possible to find at the beginning of the heap a chunk containing the amount of chunks per index inside the tcache and the address to the head chunk of each tcache index. If for some reason it's possible to modify this information, it would be possible to make the head chunk of some index point to a desired address so then allocating a chunk of the corresponding size returns that controlled address.
This is powerful because it doesn't just poison one freed entry: it corrupts the per-thread tcache metadata itself, which can let you pivot several size classes at once.
Modern variants worth knowing
House of Botcake
This is the standard modern answer when the old tcache double-free no longer works. The idea is to use overlap/consolidation with the unsorted bin so the victim chunk ends up both reachable from tcache and part of a larger freed region. After regaining access to overlapping memory, you poison the tcache entry as usual.
This is especially useful on modern glibc when you have:
- A way to trigger consolidation
- A double-free-like primitive that is blocked by tcache checks
- Enough heap control to recover the victim chunk and rewrite its mangled
next
Tcache stashing unlink attack
This is not a direct "overwrite next inside a freed tcache chunk" attack, but it is a very relevant tcache-focused arbitrary-allocation primitive. It abuses the path where glibc moves chunks from a small bin into tcache. If you can corrupt the small-bin metadata (typically bk) before that transfer, glibc can end up stashing a fake chunk into tcache, after which a normal malloc() returns it.
This is useful when a challenge gives you stronger control over small-bin metadata than over a freed tcache entry itself.
Safe-linking bypasses
On glibc 2.32+ the core problem is not "how do I overwrite next?" but "how do I produce a valid protected pointer?" Common answers are:
- Leak the heap by printing or reading a freed tcache chunk.
- Use an overlap/UAF to recover the heap base and then encode the poisoned pointer correctly.
- Abuse a primitive that effectively applies the protection logic twice or that corrupts the tcache metadata rather than a single entry.
Examples
- CTF https://guyinatuxedo.github.io/29-tcache/dcquals19_babyheap/index.html
- Libc info leak: It's possible to fill the tcaches, add a chunk into the unsorted list, empty the tcache and re-allocate the chunk from the unsorted bin only overwriting the first 8B, leaving the second address to libc from the chunk intact so we can read it.
- Tcache attack: The binary is vulnerable a 1B heap overflow. This will be abuse to change the size header of an allocated chunk making it bigger. Then, this chunk will be freed, adding it to the tcache of chunks of the fake size. Then, we will allocate a chunk with the faked size, and the previous chunk will be returned knowing that this chunk was actually smaller and this grants up the opportunity to overwrite the next chunk in memory.\
We will abuse this to overwrite the next chunk's FD pointer to point to a sensitive target, so then later allocations return a controlled pointer and give an arbitrary write primitive. - CTF https://guyinatuxedo.github.io/29-tcache/plaid19_cpp/index.html
- Libc info leak: There is a use after free and a double free. In this writeup the author leaked an address of libc by readnig the address of a chunk placed in a small bin (like leaking it from the unsorted bin but from the small one)
- Tcache attack: A Tcache is performed via a double free. The same chunk is freed twice, so inside the Tcache the chunk will point to itself. Then, it's allocated, its FD pointer is modified to point to the free hook and then it's allocated again so the next chunk in the list is going to be in the free hook. Then, this is also allocated and it's possible to write a the address of
systemhere so when a malloc containing"/bin/sh"is freed we get a shell. - This is still a good historical example, but remember that the easy version of this attack does not generalise to glibc
2.32+/2.34+without accounting for safe-linking and hook removal. - CTF https://guyinatuxedo.github.io/44-more_tcache/csaw19_popping_caps0/index.html
- The main vuln here is the capacity to
freeany address in the heap by indicating its offset - Tcache indexes attack: It's possible to allocate and free a chunk of a size that when stored inside the tcache chunk (the chunk with the info of the tcache bins) will generate an address with the value
0x100. This is because the tcache stores the amount of chunks on each bin in different bytes, therefore one chunk in one specific index generates the value0x100. - Then, this value looks like there is a chunk of size
0x100, allowing the attacker tofreethis address. - Then, allocating a chunk of size
0x100, the previous address will be returned as a chunk, allowing to overwrite other tcache indexes. - CTF https://guyinatuxedo.github.io/44-more_tcache/csaw19_popping_caps1/index.html
- Same vulnerability as before with one extra restriction
- Tcache indexes attack: Similar attack to the previous one but using less steps by freeing the chunk that contains the tcache info so its address is added to the tcache index of its size. Then, allocating that size returns the tcache metadata chunk itself, which allows poisoning other indexes.
- Math Door. HTB Cyber Apocalypse CTF 2023
- Write After Free to add a number to the
fdpointer. - A lot of heap feng-shui is needed in this challenge. The writeup shows how controlling the head of the Tcache free-list is pretty handy.
- Glibc leak through
stdout(FSOP). - Tcache poisoning to get an arbitrary write primitive.
- mailman. ImaginaryCTF 2023
- Modern glibc 2.35 challenge.
- The exploit chain uses a heap leak to defeat safe-linking, then House of Botcake to create the overlap needed for a modern tcache poisoning.
- Good example of using tcache poisoning as a step towards FSOP/ROP, not just a hook overwrite.
- catastrophe. DiceCTF 2022
- Modern glibc 2.35 challenge.
- Leak a heap pointer by reading a freed tcache entry, encode the poisoned pointer correctly, then use House of Botcake to obtain the arbitrary write needed for the rest of the chain.
References
- https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/
- https://github.com/shellphish/how2heap
{{#include ../../banners/hacktricks-training.md}}