Skip to content

Commit 4543f7b

Browse files
committed
Merge branch 'master' of github.com:HackTricks-wiki/hacktricks
2 parents 144dff1 + d13692c commit 4543f7b

14 files changed

Lines changed: 902 additions & 58 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}}

src/binary-exploitation/linux-kernel-exploitation/ksmbd-streams_xattr-oob-write-cve-2025-37947.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ Example smb.conf
1919

2020
Root cause (allocation clamped, memcpy at unclamped offset)
2121
- The function computes size = *pos + count, clamps size to XATTR_SIZE_MAX (0x10000) when exceeded, and recomputes count = (*pos + count) - 0x10000, but still performs memcpy(&stream_buf[*pos], buf, count) into a 0x10000-byte buffer. If *pos ≥ 0x10000 the destination pointer is already outside the allocation, producing an OOB write of count bytes.
22+
- `streams_xattr` stores SMB alternate data streams inside POSIX extended attributes, so the 0x10000 ceiling comes from the Linux single-xattr size limit rather than from an SMB protocol field. That makes the bug practical only when the share explicitly enables `vfs objects = streams_xattr` and the filesystem supports xattrs.
23+
24+
Why the write offset matters
25+
- The vulnerable path is not just "write more than 64KiB". The missing check was that `*pos` was not validated against the current stream length (`v_len`) before the append/copy logic ran.
26+
- Upstream fixed this by rejecting writes where `*pos >= v_len` with `-EINVAL`. Pre-fix, an attacker could reuse a valid authenticated handle to a named stream and send a raw SMB2 WRITE whose `file_offset` already points at or past the end of the existing stream, which turns the post-clamp `memcpy()` into a deterministic page overflow.
27+
- The public PoC demonstrates this by authenticating with `libsmb2`, opening a stream path such as `1337:`, extracting `SessionId`/`TreeId`/`FileId`, and then sending a handcrafted SMB2 WRITE with `file_offset = 0x10018` and a small `Length`.
2228

2329
<details>
2430
<summary>Vulnerable function snippet (ksmbd_vfs_stream_write)</summary>
@@ -52,6 +58,7 @@ Offset steering and OOB length
5258
Triggering the bug via SMB streams write
5359
- Use the same authenticated SMB connection to open a file on the share and issue a write to a named stream (streams_xattr). Set file_offset ≥ 0x10000 with a small length to generate a deterministic OOB write of controllable size.
5460
- libsmb2 can be used to authenticate and craft such writes over SMB2/3.
61+
- In practice, reusing the negotiated SMB session is convenient because the exploit only needs to patch a few dynamic fields in the WRITE request (`TreeId`, `SessionId`, `FileId`) and can then transmit the malformed packet directly on the same socket.
5562
5663
Minimal reachability (concept)
5764
```c
@@ -79,6 +86,11 @@ sudo cat /proc/pagetypeinfo | sed -n '/Node 0, zone Normal/,/Node/p'
7986
sudo ./bpf-tracer.sh
8087
```
8188

89+
What to trace while tuning
90+
- `kvmalloc_node(0x10000)` confirms when the vulnerable stream write actually consumes an order-4 allocation.
91+
- `load_msg`/`kretprobe:load_msg` lets you estimate how many `msg_msgseg` allocations are attached to each sprayed message, which is useful when tuning primary/secondary message sizes for a specific kernel build.
92+
- If the exploit is ported to a different distro/kernel, re-check cache names, inline `msg_msg` payload sizes, `anon_pipe_buf_ops` offsets, and gadget addresses rather than assuming the Ubuntu 22.04 LTS `5.15.0-153-generic` constants still match.
93+
8294
Exploitation plan (msg_msg + pipe_buffer), adapted from CVE-2021-22555
8395
1) Spray many System V msg_msg primary/secondary messages (4KiB-sized to fit kmalloc-cg-4k).
8496
2) Trigger ksmbd OOB to corrupt a primary message’s next pointer so that two primaries share one secondary.
@@ -106,16 +118,20 @@ Mitigations and reachability
106118
- Fix: clamp both allocation and destination/length or bound memcpy against the allocated size; upstream patches track as CVE-2025-37947.
107119
- Remote exploitation would additionally require a reliable infoleak and remote heap grooming; this write-up focuses on local LPE.
108120

121+
See also
122+
123+
{{#ref}}
124+
../../network-services-pentesting/pentesting-smb/ksmbd-attack-surface-and-fuzzing-syzkaller.md
125+
{{#endref}}
126+
109127
References PoC and tooling
110128
- libsmb2 for SMB auth and streams writes
111129
- eBPF tracer script to log kvmalloc addresses and histogram allocations (e.g., grep 4048 out-4096.txt)
112130
- Minimal reachability PoC and full local exploit are publicly available (see References)
113131

114132
## References
115133
- [ksmbd - Exploiting CVE-2025-37947 (3/3) — Doyensec](https://blog.doyensec.com/2025/10/08/ksmbd-3.html)
116-
- [libsmb2](https://github.com/sahlberg/libsmb2)
117-
- [KSMBD-CVE-2025-37947: proof-of-concept.c](https://github.com/doyensec/KSMBD-CVE-2025-37947/blob/main/proof-of-concept.c)
118-
- [KSMBD-CVE-2025-37947: CVE-2025-37947.c (full exploit)](https://github.com/doyensec/KSMBD-CVE-2025-37947/blob/main/CVE-2025-37947.c)
119-
- [bpf-tracer.sh](https://github.com/doyensec/KSMBD-CVE-2025-37947/blob/main/bpf-tracer.sh)
134+
- [Linux upstream fix: `ksmbd: prevent out-of-bounds stream writes by validating *pos`](https://github.com/torvalds/linux/commit/0ca6df4f40cf4c32487944aaf48319cb6c25accc)
135+
- [KSMBD-CVE-2025-37947 PoC repository](https://github.com/doyensec/KSMBD-CVE-2025-37947)
120136

121137
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)