## https://sploitus.com/exploit?id=80F898C2-508D-52A9-8897-D059BD5AA4BD
# xnutesting
> **Research & Education Only** β Proof-of-concept exploit harness for the XNU AIO Kevent Use-After-Free vulnerability.
> Do not use illegally. For personal devices or controlled research environments only.
---
## Overview
**xnutesting** is an iOS SwiftUI app for researching and testing a security vulnerability in the XNU kernel. The project focuses on a **Use-After-Free (UAF)** bug in the kernel's AIO (Asynchronous I/O) subsystem, chained with a kernel read/write primitive via ICMPv6.
| Field | Details |
|-------|---------|
| **Vulnerability** | XNU AIO Kevent Use-After-Free (`bsd/kern/kern_aio.c`) |
| **CVE** | CVE-2026-XXXX *(placeholder)* |
| **Affected** | iOS β€ 26.2.x (xnu-12377.62.x) β includes **26.2.1** |
| **Patched** | iOS 26.3 (xnu-12377.81.4) |
| **Confirmed device** | iPhone 11 Pro β A13 (arm64e) |
| **Compatible devices** | All arm64e (A12+): iPhone XS, 11, 12, 13β¦ |
| **Sandbox** | Exploitable from app sandbox, no special entitlements required |
---
## Bug Mechanics
### Root Cause
`lio_listio()` registers a kevent **after** the AIO work item has already been enqueued into the kernel. If a racing thread calls `aio_return()` in that window, the `aio_workq_entry` is **freed** while its knote still holds a dangling pointer to it. When `kevent64()` is subsequently called, `filt_aioprocess` reads from freed memory β kernel panic (`FAR = 0x58`).
### Exploitation Chain
```
Stage 1 β AIO UAF (Double-Free)
lio_listio() β aio_workq_entry queued, knote registered
aio_return() [racer] β entry freed (aio_workq_entry ~224B, KT_DEFAULT zone)
aio_read() [reclaim] β slot immediately reused (LIFO, same CPU)
kevent64() β filt_aioprocess reads reclaimed slot
β kev.ext[1] = leaked kernel value (+0x20)
β TAILQ_REMOVE write primitive
Stage 2 β ICMPv6 Kernel R/W
Spray 64 ICMPv6 sockets (post-UAF to avoid zone noise)
Corrupt inpcbβicmp6filter β points to second socket's PCB
getsockopt() β early_kread64() (arbitrary kernel read)
setsockopt() β early_kwrite64() (arbitrary kernel write)
Stage 3 β Kernel Base Scan
Read pr_input pointer from protosw struct
XPACI (arm64e PAC strip) + backward page scan
Match Mach-O magic (0xFEEDFACF) β kernel base & slide
```
---
## Project Structure
```
xnutesting/
βββ xnutesting/
β βββ ContentView.swift # SwiftUI UI harness, real-time log capture
β βββ xnutestingApp.swift # App entry point
β βββ xnutesting-Bridging-Header.h # Swift β ObjC/C bridge
β βββ ex/
β βββ ex_bridge.h # C/ObjC entry point declarations
β βββ ex_PoC.c # Minimal PoC β triggers kernel panic only
β βββ ex.m # ObjC UAF double-free variant (no KRW)
β βββ ex_combined.m # Full chain: AIO UAF β ICMPv6 KRW
β βββ dsw.m # Darksword: physical KRW via IOSurface
βββ xnutestingTests/
βββ xnutestingUITests/
```
### Modules
| File | Description |
|------|-------------|
| `ex_PoC.c` | Minimal PoC β triggers kernel panic, no further exploitation |
| `ex.m` | ObjC variant of UAF double-free, no KRW primitive |
| `ex_combined.m` | **Full exploit chain**: AIO UAF + ICMPv6 KRW + kernel base scan |
| `dsw.m` | Darksword KRW using physical OOB read/write via IOSurface (`PurpleGfxMem`). Requires entitlement β skipped in combined chain |
| `ContentView.swift` | SwiftUI harness with real-time log output, 4 trigger buttons (one per module) |
---
## Technical Details
### LIFO Zone Trick (CPU Affinity)
To ensure the freed slot is reclaimed **on the same CPU** (LIFO zone magazine reuse), both the main thread and racer thread are pinned to the same CPU tag (`THREAD_AFFINITY_POLICY`, tag `42`). If different CPUs are used, the freed slot enters CPU-A's magazine while the reclaim alloc draws from CPU-B's magazine β LIFO miss β kevent64 reads a zeroed slot β panic.
### ICMPv6 KRW Primitive
```
controlSocket β setsockopt(ICMP6_FILTER) writes target address into icmp6filter
rwSocket β getsockopt(ICMP6_FILTER) reads 0x20 bytes from that address
```
By corrupting the `icmp6filter` pointer inside `inpcb`, two sockets form a control/read-write pair enabling arbitrary kernel read/write (up to 32 bytes per call).
### arm64e PAC
On arm64e (A12+), function pointers are signed with PAC. The `__xpaci()` macro (instruction `XPACI X0`) strips the PAC signature before the kernel base scan.
---
## Usage
1. Open `xnutesting.xcodeproj` in Xcode.
2. Build and deploy to a device running iOS β€ 26.2.x (vulnerable β includes 26.2.1).
3. The app presents 4 buttons:
- **Run Double-Free UAF** β `ex.m`, UAF stage only (no KRW)
- **Run Kernel Panic PoC** β `ex_PoC.c`, deliberate kernel panic
- **Run Combined KRW Chain** β `ex_combined.m`, full chain (recommended)
- **Run Darksword KRW** β `dsw.m`, physical KRW (requires entitlement)
4. Real-time log output is displayed on screen.
> β οΈ **Run Kernel Panic PoC** will immediately reboot the device on vulnerable systems.
---
## Device Compatibility
| Device | Chip | iOS 26.2 | iOS 26.2.1 | iOS 26.3+ |
|--------|------|----------|------------|-----------|
| iPhone 11 Pro | A13 | β Confirmed | β Expected | β Patched |
| iPhone 12 | A14 | β Expected | β Expected | β Patched |
| iPhone 13+ | A15+ | β Expected | β Expected | β Patched |
| iPhone XS/X | A12 | β Expected | β Expected | β Patched |
> **Note on iPhone 12 (iOS 26.2.1):** The vulnerability is in the kernel (`bsd/kern/kern_aio.c`) and is chip-independent. iOS 26.2.1 is a minor patch on top of 26.2 β the kernel ABI is unchanged (`xnu-12377.62.x`) and the hardcoded `inpcb`/`socket` offsets remain valid. `ex_combined.m` (full chain) and `ex_PoC.c` (panic) are expected to work normally.
---
## Known Limitations
- `dsw.m` (`pe_v1`/`pe_v2`, physical OOB) requires the `IOSurface PurpleGfxMem` entitlement β not available from a standard sandbox.
- The full KRW chain (`ex_combined.m`) needs `kev.ext[1]` to be a valid kernel pointer. Without an `ipc_port` spray into the freed slot before `kevent64`, `ext[1]` only contains the `aio_nbytes` placeholder value.
- Race success rate is ~1β10 attempts depending on CPU load.
- Struct offsets (`OFFSET_PCB_SOCKET`, `OFFSET_ICMP6FILT`, β¦) were verified against `xnu-12377.62.10`. If Apple changes internal layout in a minor patch, re-verification is required.
---
## References
- XNU source: `bsd/kern/kern_aio.c` β `lio_listio`, `aio_return`, `filt_aioprocess`
- Patch commit (iOS 26.3): xnu-12377.81.4
- IOSurface physical mapping: `IOSurfaceGetBaseAddress` + `mach_make_memory_entry_64`