Skip to content

Commit 65c239d

Browse files
author
HackTricks News Bot
committed
Add content from: Research Update Enhanced src/binary-exploitation/libc-heap/t...
1 parent 21f4ebf commit 65c239d

1 file changed

Lines changed: 108 additions & 13 deletions

File tree

src/binary-exploitation/libc-heap/tcache-bin-attack.md

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,142 @@
44

55
## Basic Information
66

7-
For more information about what is a Tcache bin check this page:
8-
7+
For more information about what a Tcache bin is, check this page:
98

109
{{#ref}}
1110
bins-and-memory-allocations.md
1211
{{#endref}}
1312

14-
First of all, note that the Tcache was introduced in Glibc version 2.26.
13+
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**.
14+
15+
This primitive is only useful if the attacker can first **write into a freed chunk**. Common ways of getting that primitive are explained here:
16+
17+
{{#ref}}
18+
overwriting-a-freed-chunk.md
19+
{{#endref}}
20+
21+
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.
22+
23+
### Modern constraints
24+
25+
- **glibc 2.29+** added stronger tcache **double-free detection** using the `key` field stored inside a freed `tcache_entry`. A plain `free(A); free(B); free(A);` usually aborts now unless the `key` check is bypassed or the chunk is reintroduced through another path.
26+
- **glibc 2.32+** added **safe-linking** to singly-linked allocator lists (`tcache` and `fastbins`). The stored `next` pointer is no longer raw, but mangled as:
27+
28+
```c
29+
stored_next = target ^ (chunk_addr >> 12)
30+
```
31+
32+
Therefore, a modern tcache poisoning usually needs either:
33+
- A **heap leak** to compute the protected pointer correctly, or
34+
- A different primitive that bypasses/abuses safe-linking instead of forging the pointer directly.
35+
- **Alignment checks** also became stricter, which is why a bad poisoned pointer now tends to crash with **`malloc(): unaligned tcache chunk detected`**.
36+
- **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.
37+
38+
In practice, the modern end goal is usually one of these:
39+
40+
- Return a chunk on top of another **heap object** to get arbitrary read/write.
41+
- Return a chunk on a **writable global / application structure** and corrupt a code pointer later used by the program.
42+
- Return a chunk on top of a structure later abused for **FSOP**, **ROP**, or another post-write primitive.
43+
44+
### What a poisoned tcache entry looks like
45+
46+
A freed tcache chunk is reused as a `tcache_entry`:
47+
48+
```c
49+
struct tcache_entry {
50+
struct tcache_entry *next;
51+
uintptr_t key;
52+
};
53+
```
54+
55+
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:
56+
57+
- **Arbitrary allocation** by corrupting `next`
58+
- **Double-free bypass** by corrupting `key`
1559

16-
The **Tcache attack** (also known as **Tcache poisoning**) proposed in the [**guyinatuxido page**](https://guyinatuxedo.github.io/29-tcache/tcache_explanation/index.html) is very similar to the fast bin attack where the goal is to overwrite the pointer to the next chunk in the bin inside a freed chunk to an arbitrary address so later it's possible to **allocate that specific address and potentially overwrite pointes**.
60+
### Basic modern poisoning flow
1761

18-
However, nowadays, if you run the mentioned code you will get the error: **`malloc(): unaligned tcache chunk detected`**. So, it's needed to write as address in the new pointer an aligned address (or execute enough times the binary so the written address is actually aligned).
62+
1. Obtain a primitive that lets you **edit a freed chunk**.
63+
2. Free a chunk of the target tcache size.
64+
3. Leak or infer the freed chunk address if safe-linking is enabled.
65+
4. Overwrite its `next` pointer with the **mangled** version of the target address.
66+
5. Allocate once to consume the corrupted entry.
67+
6. Allocate again to get a chunk returned at the chosen target.
68+
69+
For glibc 2.32+ the forged value is typically:
70+
71+
```c
72+
fake_next = target ^ (victim_chunk_addr >> 12)
73+
```
74+
75+
If the returned pointer is not aligned to the allocator expectations, `malloc()` will usually abort before you get control.
1976

2077
### Tcache indexes attack
2178

22-
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** (like `__malloc_hook`) to then allocated a chunk of the size of the index and overwrite the contents of `__malloc_hook` in this case.
79+
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.
80+
81+
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.
82+
83+
### Modern variants worth knowing
84+
85+
#### House of Botcake
86+
87+
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.
88+
89+
This is especially useful on modern glibc when you have:
90+
91+
- A way to trigger consolidation
92+
- A double-free-like primitive that is blocked by tcache checks
93+
- Enough heap control to recover the victim chunk and rewrite its mangled `next`
94+
95+
#### Tcache stashing unlink attack
96+
97+
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.
98+
99+
This is useful when a challenge gives you stronger control over **small-bin metadata** than over a freed tcache entry itself.
100+
101+
#### Safe-linking bypasses
102+
103+
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:
104+
105+
- Leak the heap by printing or reading a **freed tcache chunk**.
106+
- Use an overlap/UAF to recover the heap base and then encode the poisoned pointer correctly.
107+
- Abuse a primitive that effectively applies the protection logic **twice** or that corrupts the **tcache metadata** rather than a single entry.
23108

24109
## Examples
25110

26111
- CTF [https://guyinatuxedo.github.io/29-tcache/dcquals19_babyheap/index.html](https://guyinatuxedo.github.io/29-tcache/dcquals19_babyheap/index.html)
27112
- **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**.
28113
- **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**.\
29-
We will abuse this to **overwrite the next chunk's FD pointer** to point to **`malloc_hook`**, so then its possible to alloc 2 pointers: first the legit pointer we just modified, and then the second allocation will return a chunk in **`malloc_hook`** that it's possible to abuse to write a **one gadget**.
114+
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.
30115
- CTF [https://guyinatuxedo.github.io/29-tcache/plaid19_cpp/index.html](https://guyinatuxedo.github.io/29-tcache/plaid19_cpp/index.html)
31116
- **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)
32117
- **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 `system` here so when a malloc containing `"/bin/sh"` is freed we get a shell.
118+
- 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.
33119
- CTF [https://guyinatuxedo.github.io/44-more_tcache/csaw19_popping_caps0/index.html](https://guyinatuxedo.github.io/44-more_tcache/csaw19_popping_caps0/index.html)
34120
- The main vuln here is the capacity to `free` any address in the heap by indicating its offset
35-
- **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 value 0x100.
36-
- Then, this value looks like there is a chunk of size 0x100. Allowing to abuse it by `free` this address. This will **add that address to the index of chunks of size 0x100 in the tcache**.
37-
- Then, **allocating** a chunk of size **0x100**, the previous address will be returned as a chunk, allowing to overwrite other tcache indexes.\
38-
For example putting the address of malloc hook in one of them and allocating a chunk of the size of that index will grant a chunk in calloc hook, which allows for writing a one gadget to get a s shell.
121+
- **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 value `0x100`.
122+
- Then, this value looks like there is a chunk of size `0x100`, allowing the attacker to `free` this address.
123+
- Then, **allocating** a chunk of size **`0x100`**, the previous address will be returned as a chunk, allowing to overwrite other tcache indexes.
39124
- CTF [https://guyinatuxedo.github.io/44-more_tcache/csaw19_popping_caps1/index.html](https://guyinatuxedo.github.io/44-more_tcache/csaw19_popping_caps1/index.html)
40125
- Same vulnerability as before with one extra restriction
41-
- **Tcache indexes attack**: Similar attack to the previous one but using less steps by **freeing the chunk that contains the tcache info** so it's address is added to the tcache index of its size so it's possible to allocate that size and get the tcache chunk info as a chunk, which allows to add free hook as the address of one index, alloc it, and write a one gadget on it.
126+
- **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.
42127
- [**Math Door. HTB Cyber Apocalypse CTF 2023**](https://7rocky.github.io/en/ctf/other/htb-cyber-apocalypse/math-door/)
43128
- **Write After Free** to add a number to the `fd` pointer.
44129
- 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.
45130
- **Glibc leak** through `stdout` (FSOP).
46131
- **Tcache poisoning** to get an arbitrary write primitive.
132+
- [**mailman. ImaginaryCTF 2023**](https://sekai.team/blog/imaginary-ctf-2023/mailman)
133+
- Modern **glibc 2.35** challenge.
134+
- 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.
135+
- Good example of using tcache poisoning as a step towards **FSOP/ROP**, not just a hook overwrite.
136+
- [**catastrophe. DiceCTF 2022**](https://ret2school.github.io/post/catastrophe/)
137+
- Modern **glibc 2.35** challenge.
138+
- 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.
47139

48-
{{#include ../../banners/hacktricks-training.md}}
140+
## References
49141

142+
- [https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/](https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/)
143+
- [https://github.com/shellphish/how2heap](https://github.com/shellphish/how2heap)
50144

145+
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)