From 0a903dcac051ded28a97ed73cb392b74d38da77a Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Mon, 14 Sep 2026 03:58:18 +0000 Subject: [PATCH] Add content from: How Browser Exploits Work: DarkSword, CVE-2025-43529, and th... --- .../webkit-dfg-store-barrier-uaf-angle-oob.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/binary-exploitation/ios-exploiting/webkit-dfg-store-barrier-uaf-angle-oob.md b/src/binary-exploitation/ios-exploiting/webkit-dfg-store-barrier-uaf-angle-oob.md index ffb2b758525..84f2dc48473 100644 --- a/src/binary-exploitation/ios-exploiting/webkit-dfg-store-barrier-uaf-angle-oob.md +++ b/src/binary-exploitation/ios-exploiting/webkit-dfg-store-barrier-uaf-angle-oob.md @@ -8,6 +8,27 @@ - **ANGLE Metal PBO bug (CVE-2025-14174)**: The Metal backend allocates the PBO staging buffer using `UNPACK_IMAGE_HEIGHT` instead of the real texture height. Supplying a tiny unpack height then issuing a large `texImage2D` causes a **staging-buffer OOB write** (~240KB in the PoC below).[[1]](#references) - **PAC blockers on arm64e (iOS 26.1)**: TypedArray `m_vector` and JSArray `butterfly` are PAC-signed; forging fake objects with attacker-chosen pointers crashes with `EXC_BAD_ACCESS`/`EXC_ARM_PAC`. Only reusing **already-signed** butterflies (boxed/unboxed reinterpretation) works.[[1]](#references)[[2]](#references) +## Renderer-to-kernel process chain + +A browser memory primitive is only the first boundary. Safari executes page JavaScript in the tightly sandboxed `com.apple.WebKit.WebContent` process, while WebGL work is decoded in the more privileged `com.apple.WebKit.GPU` process. DarkSword composed the following reusable sequence; the final two bugs are included only to show why each intermediate primitive was needed.[[3]](#references)[[4]](#references) + +```text +landing page -> hidden iframe -> rce_loader.js + -> JSC DFG confusion/UAF -> addrof + fakeobj -> WebContent arbitrary R/W + -> dyld PAC bypass -> authenticated native calls + -> ANGLE/WebGL OOB -> code execution in the GPU process + -> XNU copy-on-write bug -> native calls in mediaplaybackd + -> XNU VFS race -> physical and virtual kernel R/W +``` + +The important selection rule for multi-process chains is **reachability plus privilege gain**: compromise an IPC endpoint the current sandbox may contact, then move into a process with additional filesystem, Mach-service, entitlement, or kernel-facing attack surface. In this chain WebContent can submit graphics commands to the GPU process; the GPU process can reach surfaces used to pivot into `mediaplaybackd`, where the final kernel stage is run.[[3]](#references)[[4]](#references) + +### Version-gated staged delivery + +The loader fingerprinted the iOS version, fetched the matching JSC worker and downloaded later stages only after the preceding primitive succeeded. The 18.4 exploit split work between a main-context module and a Web Worker communicating with `postMessage`, whereas the 18.6/18.7 implementation placed the exploit in the worker and used a placeholder main module. Per-build workers and offset tables are necessary because JSC structure IDs, object layouts, signed-pointer fields and library offsets change even when the underlying bug remains reachable.[[3]](#references)[[4]](#references) + +This modular design also leaves a useful delivery signature: an iframe loads `frame.html`, which injects `rce_loader.js`; the loader then retrieves names such as `rce_worker_18.6.js`, `rce_worker_18.7.js` and `sbx0_main.js`. Some observed loaders used `sessionStorage.uid` to suppress reinfection and one variant encrypted retrieved stages with an ephemeral ECDH-derived AES key.[[3]](#references) + ## Triggering the DFG missing barrier → UAF ```js function triggerUAF(flag, allocCount) { @@ -49,7 +70,22 @@ Status on **iOS 26.1 (arm64e)**: - The confusion primitive works because it **reuses legitimate signed butterflies**; introducing unsigned attacker pointers fails authentication. - Potential bypass ideas noted: JIT paths that skip auth, gadgets that sign attacker pointers, or pivoting through the ANGLE OOB. +### Legitimate pointer signer as a PAC oracle + +Arbitrary write cannot directly forge an arm64e call target because its PAC binds the pointer to a secret key and a modifier. A general bypass pattern is therefore to corrupt the inputs or state of code that must legitimately sign pointers—such as dyld while resolving bindings—and turn it into a **signing oracle**. The reported CVE-2026-20700 stage applied this pattern to dyld, converting WebContent arbitrary R/W into authenticated native function calls; the complete oracle primitive has not been publicly described.[[3]](#references)[[4]](#references) + +Conceptually, exploitation asks the trusted signer for an authenticated attacker-selected target, places the result in a callback or other authenticated indirect-call slot, and reaches the consuming callsite. The CPU then accepts the pointer without disclosure of the PAC key and without mapping attacker-supplied executable pages.[[4]](#references) + +### JavaScript-only post-exploitation runtime + +DarkSword kept the orchestration, later exploit stages and final payloads in JavaScript. Recovered build paths show separate abstractions for raw memory/native calls (`Chain/Native.js`), per-version offsets (`Chain/OffsetsStruct.js`), PAC and remote calls (`TaskRop/PAC.js`, `TaskRop/RemoteCall.js`), Mach VM operations (`TaskRop/VM.js`), filesystem access (`JSUtils/FileUtils.js`) and cross-process loading (`InjectJS.js`). This replaces a monolithic shellcode payload with modules layered on the memory and authenticated-call primitives.[[3]](#references)[[4]](#references) + +This design uses JSC's legitimate execution environment instead of the classic “write shellcode, change page permissions, branch” path. It therefore avoids requiring a separate PPL/SPTM bypass merely to execute unsigned native payload code, at the cost of fragile per-version object layouts and offsets. The daemon pivot demonstrates that the model can survive a process boundary: the chain loaded a JavaScriptCore runtime into `mediaplaybackd` and executed the next JavaScript exploit there.[[3]](#references)[[4]](#references) + ## ANGLE Metal PBO under-allocation → OOB write + +A WebGL request crosses several trust boundaries: the WebContent binding serializes renderer-controlled arguments, the GPU-process IPC decoder reconstructs them, ANGLE validates/translates them, and the Metal backend submits the operation. Audit sizes, offsets, indices, formats, dimensions and overflow-safe arithmetic at **every** layer; disagreement between frontend validation and backend allocation can turn a JavaScript call into GPU-process corruption and therefore a sandbox escape.[[3]](#references)[[4]](#references) + Use a tiny unpack height to shrink the staging buffer, then upload a large texture so the copy overruns:[[1]](#references) ```js gl.pixelStorei(gl.UNPACK_IMAGE_HEIGHT, 16); // alloc height @@ -67,6 +103,8 @@ Notes: - [1] [WebKit-UAF-ANGLE-OOB-Analysis - DFG Store Barrier UAF (CVE-2025-43529) & ANGLE Metal PBO OOB (CVE-2025-14174) on iOS 26.1](https://github.com/zeroxjf/WebKit-UAF-ANGLE-OOB-Analysis) - [2] [CVE-2025-43529 - WebKit JSC DFG StoreBarrierInsertionPhase UAF PoC](https://github.com/jir4vv1t/CVE-2025-43529) +- [3] [Google Threat Intelligence Group - The Proliferation of DarkSword: iOS Exploit Chain Adopted by Multiple Threat Actors](https://cloud.google.com/blog/topics/threat-intelligence/darksword-ios-exploit-chain) +- [4] [8kSec - How Browser Exploits Work: DarkSword, CVE-2025-43529, and the iOS Browser Exploit Chain](https://8ksec.io/how-browser-exploits-work-darksword-ios-cve-2025-43529) {{#include ../../banners/hacktricks-training.md}}