|
| 1 | +# VirtualBox Slirp NAT Packet Heap Exploitation |
| 2 | + |
| 3 | +{{#include ../../banners/hacktricks-training.md}} |
| 4 | + |
| 5 | +## TL;DR |
| 6 | + |
| 7 | +- VirtualBox ships a heavily modified fork of Slirp whose packet buffers (mbufs) live in a custom zone allocator with inline metadata and function-pointer callbacks (`pfFini`, `pfDtor`). |
| 8 | +- A guest can rewrite the trusted `m->m_len` with an attacker-controlled IP header length, which destroys all later bounds checks and yields both infoleak and overwrite primitives. |
| 9 | +- By abusing UDP packets with checksum `0` and oversized `ip_len`, the guest can exfiltrate mbuf tails and the metadata of neighbouring chunks to learn heap and zone addresses. |
| 10 | +- Providing crafted IP options forces `ip_stripoptions()` to `memcpy()` too much data in-place, so the attacker can overwrite the next mbuf's `struct item` header and point its `zone` field at fully controlled data. |
| 11 | +- Freeing the corrupted mbuf triggers `zone->pfFini()` with attacker-supplied arguments; pointing it to `memcpy@plt` gives an arbitrary copy/write primitive that can be steered toward GOT entries or other control data inside the non-PIE VirtualBox binary. |
| 12 | + |
| 13 | +## Packet allocator anatomy |
| 14 | + |
| 15 | +VirtualBox allocates every ingress Ethernet frame from a per-interface zone named `zone_clust`. Each 0x800-byte data chunk is preceded by an inline header: |
| 16 | + |
| 17 | +```c |
| 18 | +struct item { |
| 19 | + uint32_t magic; // 0xdead0001 |
| 20 | + void *zone; // uma_zone_t pointer with callbacks |
| 21 | + uint32_t ref_count; |
| 22 | + LIST_ENTRY(item) list; // freelist / used list links |
| 23 | +}; |
| 24 | +``` |
| 25 | + |
| 26 | +When an mbuf is freed the call stack `m_freem -> ... -> slirp_uma_free()` trusts the inline header: |
| 27 | + |
| 28 | +1. `uma_zfree_arg()` recomputes `item = (struct item *)mem - 1` and *should* validate `item->zone`, but `Assert()` is compiled out in release builds. |
| 29 | +2. `slirp_uma_free()` loads `zone = item->zone` and unconditionally executes `zone->pfFini(zone->pData, data_ptr, zone->size)` followed by `zone->pfDtor(...)`. |
| 30 | + |
| 31 | +Therefore, any write-what-where into the mbuf header translates into a controlled indirect call during `free()`. |
| 32 | + |
| 33 | +## Infoleak via `m->m_len` override |
| 34 | + |
| 35 | +At the top of `ip_input()` VirtualBox added: |
| 36 | + |
| 37 | +```c |
| 38 | +if (m->m_len != RT_N2H_U16(ip->ip_len)) |
| 39 | + m->m_len = RT_N2H_U16(ip->ip_len); |
| 40 | +``` |
| 41 | + |
| 42 | +Because the assignment happens **before** verifying the IP header, a guest can advertise any length up to 0xffff. The rest of the stack (ICMP, UDP, fragmentation handlers, etc.) assumes `m->m_len` is trustworthy and uses it to decide how many bytes to copy off the mbuf. |
| 43 | + |
| 44 | +Use UDP packets with checksum `0` (meaning "no checksum"). The NAT fast-path forwards `m->m_len` bytes without inspecting payload integrity, so inflating `ip_len` causes Slirp to read past the real buffer and return heap residues to the guest or to a cooperating external helper beyond the NAT. Because the chunk size is 2048 bytes, the leak can include: |
| 45 | + |
| 46 | +- The next mbuf's inline `struct item`, revealing the freelist order and the real `zone` pointer. |
| 47 | +- Heap cookies such as `magic` fields, helping to craft valid-looking headers when performing corruptions later. |
| 48 | + |
| 49 | +## Overwriting neighbouring chunk headers with IP options |
| 50 | + |
| 51 | +The same bogus length can be turned into an overwrite primitive by forcing the packet through `ip_stripoptions()` (triggered when the IP header has options and the payload is UDP/TCP). The helper computes a copy length from `m->m_len` and then calls `memcpy()` to slide the transport header over the stripped options: |
| 52 | + |
| 53 | +1. Supply a long `ip_len` so the computed move length extends past the current mbuf. |
| 54 | +2. Include a small number of IP options so Slirp enters the stripping path. |
| 55 | +3. When `memcpy()` runs, it reads from the following mbuf and writes over the current mbuf's payload and inline header, corrupting `magic`, `zone`, `ref_count`, etc. |
| 56 | + |
| 57 | +Because the allocator keeps packets from the same interface contiguous on the freelist, this overflow deterministically hits the next chunk after modest heap grooming. |
| 58 | + |
| 59 | +## Forging `uma_zone_t` to hijack `pfFini` |
| 60 | + |
| 61 | +Once the adjacent `struct item` is corruptible, the exploit proceeds as follows: |
| 62 | + |
| 63 | +1. Use leaked heap addresses to build a fake `uma_zone` structure inside a mbuf fully controlled by the guest. Populate: |
| 64 | + - `pfFini` with the PLT entry of `memcpy()`. |
| 65 | + - `pData` with the desired destination pointer (e.g. GOT entry, vtable slot, function pointer array). |
| 66 | + - `size` with the number of bytes to copy. |
| 67 | + - Optional: `pfDtor` as a second stage call (e.g. to invoke the newly-written function pointer). |
| 68 | +2. Overwrite the target mbuf's `zone` field with the pointer to the fake structure; adjust `list` pointers so freelist bookkeeping remains consistent enough to avoid crashes. |
| 69 | +3. Free the mbuf. `slirp_uma_free()` now executes `memcpy(dest=pData, src=item_data, n=size)` while the mbuf still contains guest-controlled data, yielding an arbitrary write. |
| 70 | + |
| 71 | +Because the Linux VirtualBox binary is non-PIE, PLT addresses for `memcpy` and `system` are fixed and can be used directly. The guest can also stash strings such as `/bin/sh` inside another mbuf that remains referenced when the hijacked call executes. |
| 72 | + |
| 73 | +## Heap grooming via fragmentation |
| 74 | + |
| 75 | +Slirp's per-interface zone is 3072 chunks deep and initially carved as a contiguous array whose freelist is traversed from high addresses downward. Deterministic adjacency can be achieved by: |
| 76 | + |
| 77 | +- Flooding the NAT with many `IP_MF` fragments of constant size so the reassembly code allocates predictable mbuf sequences. |
| 78 | +- Recycling specific chunks by sending fragments that time out, forcing frees back into the freelist in LIFO order. |
| 79 | +- Using knowledge of the freelist walk to place the future victim mbuf right after the mbuf that will carry the IP options overflow. |
| 80 | + |
| 81 | +This grooming ensures the overflow hits the targeted `struct item` and that the fake `uma_zone` remains in-bounds of the leak primitive. |
| 82 | + |
| 83 | +## From arbitrary write to host code execution |
| 84 | + |
| 85 | +With the memcpy-on-free primitive: |
| 86 | + |
| 87 | +1. Copy an attacker-controlled `/bin/sh` string and command buffer into a stable mbuf. |
| 88 | +2. Use the primitive to overwrite a GOT entry or indirect callsite (e.g. a function pointer inside the NAT device state) with the PLT entry of `system()`. |
| 89 | +3. Trigger the overwritten call. Because VirtualBox runs the NAT device inside the host process, the payload executes with the privileges of the user running VirtualBox, allowing a guest-to-host escape. |
| 90 | + |
| 91 | +Alternative payloads include planting a miniature ROP chain in heap memory and copying its address into a frequently-invoked callback, or repointing `pfFini`/`pfDtor` themselves to chained gadgets for repeated writes. |
| 92 | + |
| 93 | +## References |
| 94 | + |
| 95 | +- [Thinking Outside The Box: Exploiting VirtualBox Slirp NAT Heap Corruption](https://projectzero.google/2025/12/thinking-outside-the-box.html) |
| 96 | + |
| 97 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments