Siguza — iOS/macOS Kernel Research
Siguza là independent security researcher chuyên về iOS/macOS kernel. Write-ups của Siguza nổi tiếng vì sâu, chi tiết, và viết dưới dạng narrative dễ follow. Đặc biệt mạnh về IOKit exploitation và kernel internals documentation.
Tại Sao Siguza Quan Trọng
- v0rtex (2017): IOSurface exploit → tfp0 cho iOS 10.x — nền tảng cho nhiều jailbreaks
- IOHIDeous (2017): macOS kernel 0-day — IOHIDFamily exploit, brilliant write-up
- psychicpaper (2020): sandbox escape + entitlement bypass 0-day — cực kỳ elegant
- cl0ver (2016): iOS 9 exploit — early work nhưng write-up rất instructive
- Kernel research articles: PAC internals, APRR, PPL deep dives
- Collaboration: Góp phần vào nhiều jailbreak projects
Exploits Theo Thời Gian
1. cl0ver (October 2016) — iOS 9.0-9.3.4
| Field | Detail |
|---|---|
| Target | iOS 9.0-9.3.4 |
| Goal | tfp0 (kernel task port) |
| Technique | OSUnserializeXML heap manipulation + UAF |
| Blog | blog.siguza.net/cl0ver |
| Source | github.com/Siguza/cl0ver |
Exploit flow:
1. OSUnserializeXML: kernel function parse XML plist data
→ Can allocate controlled data on kernel heap
→ Primary heap spraying primitive cho iOS exploitation
2. Trigger UAF in target subsystem
3. Spray OSUnserializeXML objects vào freed memory
→ Controlled data overlaps freed object
4. Escalate to kernel task port
cl0ver's contribution:
- Added tfp0 patch cho jailbreaks mà trước đó thiếu nó
- Write-up giải thích OSUnserializeXML technique in detail
- Foundation cho nhiều exploits sau này
Bài học: OSUnserializeXML = controlled kernel heap allocation primitive. Được dùng rộng rãi trước khi bị hardened (iOS 12+).
2. v0rtex (December 2017) — iOS 10.x
| Field | Detail |
|---|---|
| CVE | CVE-2017-13861 (same bug class as async_wake) |
| Target | iOS 10.0-10.3.3, mọi devices (32-bit + 64-bit) |
| Component | IOSurfaceRootUserClient |
| Technique | Mach port reference count bug → UAF → fake port → tfp0 |
| Blog | siguza.github.io/v0rtex |
| Source | github.com/Siguza/v0rtex |
Vulnerability:
"The bug decreases the ref count on a user-supplied mach port
by one too many"
→ Extra release → port refcount reaches 0 → freed
→ But userland still has valid handle → dangling reference
→ Classic port UAF
Exploitation (detailed):
1. Allocate target port
2. Trigger bug → extra port_release → port freed
3. Userland still has mach_port_t handle to freed port
4. Heap spray: allocate objects same size as ipc_port
→ OSUnserializeXML cho controlled data
→ Freed port memory replaced with fake port
5. Fake port setup:
ip_bits = IO_BITS_ACTIVE | IKOT_TASK
ip_kobject → fake task struct
fake task → map = kernel_map
6. Use dangling handle → mach_vm_read/write → kernel r/w
7. Post-exploitation:
→ Get root (modify ucred)
→ Unsandbox (null sandbox slot)
→ Patch amfid (bypass code signing)
→ tfp0 (patch host_special_port)
Tại sao quan trọng:
- Support cả 32-bit và 64-bit → rất rộng device range
- Write-up “Turning the IOSurface inside out” — one of the best exploitation tutorials
- Được dùng bởi Meridian jailbreak (iOS 10.x)
- So sánh với async_wake (Ian Beer): cùng bug class nhưng different exploitation approach
3. IOHIDeous (December 2017) — macOS ≤ 10.13.1
| Field | Detail |
|---|---|
| Target | macOS ≤ 10.13.1 (High Sierra) |
| Component | IOHIDFamily (HID — Human Interface Device drivers) |
| Type | Kernel read/write from any unprivileged user |
| Blog | blog.siguza.net/IOHIDeous |
| Source | github.com/Siguza/IOHIDeous |
IOHIDFamily:
- Handles keyboard, mouse, touchscreen, etc.
- IOHIDSystem, IOHIDResource, IOHIDDevice
- Runs in kernel space
- Complex codebase, historically buggy
Vulnerability:
Race condition trong IOHIDResource/IOHIDSystem
→ Concurrent operations on shared state
→ Use-after-free
Exploitation:
1. Trigger race condition → kernel UAF
2. Reallocate freed memory with controlled data
→ IOSurface property spray cho heap control
3. Type confusion → controlled kernel read/write
Write-up highlights:
- Explains IOHIDFamily architecture in detail
- Demonstrates how to find IOKit race conditions
- Shows timing manipulation techniques
- Published on New Year's Eve 2017 ("Happy New Year")
Bài học:
- IOKit race conditions: IOKit drivers are inherently multi-threaded → rich source of bugs
- IOHIDFamily specifically: complex, many handlers, shared state between UserClients
- macOS exploitation → directly applicable concepts to iOS (same kernel)
4. psychicpaper (May 2020) — iOS < 13.5
| Field | Detail |
|---|---|
| CVE | CVE-2020-9839 |
| Target | iOS < 13.5 beta 3 |
| Type | Sandbox escape + entitlement bypass (0-day at disclosure) |
| Name meaning | “Psychic paper” (Doctor Who) — shows whatever credentials you need |
| Blog | siguza.net/psychicpaper |
| Source | github.com/Siguza/psychicpaper |
Vulnerability:
AMFI/amfid had FOUR different entitlement parsers that disagreed:
1. amfid (userspace daemon) — parses entitlements as plist
2. AMFI (kernel extension) — parses entitlements differently
3. Sandbox — has its own entitlement check
4. Provisioning profile validation — another parser
Parser disagreement = confusion about what entitlements a binary has
Exploitation (brilliantly simple):
Method 1: Invalid plist structure
→ One parser sees entitlement, another doesn't
→ Binary appears to have entitlements it shouldn't
Method 2: Specific XML encoding tricks
→ CDATA sections, entity references, comments
→ Parsers interpret differently
Method 3: Entitlement key encoding
→ Different parsers normalize keys differently
→ Same key appears different to different checkers
Result:
→ Binary gets ANY entitlements (private, restricted, platform)
→ Escape sandbox: claim com.apple.private.security.no-sandbox
→ Get task-for-pid: claim task_for_pid-allow
→ Become platform binary: claim platform-application
→ ALL without actual Apple certificate!
Tại sao đây là exploit elegant nhất:
- Không cần memory corruption — pure logic bug
- Không cần kernel exploit — works entirely in userspace
- Deterministic — works 100% of the time, no races
- Any entitlement — unrestricted capabilities
- Simple — once understood, exploit code is trivial
Impact:
- Dùng bởi unc0ver jailbreak (bypass code signing step)
- TrollStore concept inspiration (entitlement/codesign bypass path)
- Patched in iOS 13.5 beta 3 after Siguza disclosed
Research Articles Quan Trọng
APRR Deep Dive
Siguza documented APRR (Apple Page Region Protection) — hardware feature underpinning PPL:
- How APRR remaps page permissions per-EL
- How PPL uses APRR to protect page tables
- Foundation knowledge cho PPL exploitation research
PAC Internals
Analysis of Apple’s PAC implementation:
- How keys are managed
- Which pointers are signed and with what context
- Where PAC coverage has gaps
Kext Documentation
Siguza documented nhiều undocumented kernel extensions:
- IOKit driver hierarchy
- Hidden IOKit properties
- Undocumented system calls
Siguza’s Approach — What To Learn
1. Deep Source Code Reading
Siguza đọc XNU source code thoroughly:
- Không chỉ tìm bugs — hiểu entire subsystem
- Document behavior mà Apple không document
- Tìm inconsistencies giữa documentation và code
→ Bugs thường nằm ở gaps giữa intended và actual behavior
2. Finding Logic Bugs
psychicpaper = pure logic bug, no memory corruption
- Tìm parser disagreements
- Tìm inconsistent state handling
- Tìm assumption mismatches giữa components
→ Often more reliable và powerful than memory corruption
3. IOKit Expertise
v0rtex, IOHIDeous: cả hai target IOKit drivers
- Understand IOKit object lifecycle
- Understand UserClient patterns
- Understand race conditions in driver callbacks
→ IOKit = largest kernel attack surface
4. Write-Up Quality
Siguza's blog posts:
- Narrative style: tell a story
- Explain WHY decisions were made
- Show failed approaches before successful one
- Include enough detail to reproduce
→ Read these write-ups as tutorials, not just reports
Tài Nguyên — Đọc Theo Thứ Tự
- cl0ver (2016) — First exploit, introduces OSUnserializeXML technique
- v0rtex (2017) — IOSurface exploitation masterclass
- IOHIDeous (2017) — IOKit race condition hunting
- psychicpaper (2020) — Logic bug artistry
- Siguza’s GitHub — Source code cho tất cả exploits
- Siguza’s Blog — Tất cả research articles
PoC Deep Dive: v0rtex — Multi-Offset Fake Port
Vấn đề 3 offsets
// Zone allocation size 0xa8 (port size) trong chunks 0x3000:
// Page boundary alignment tạo 3 possible starting offsets
// Port có thể bắt đầu tại offset 0x0, 0x68, hoặc 0x28
// Fake port structure PHẢI valid ở CẢ 3 offsets
// → Mỗi field quan trọng phải nằm ở position
// mà KHÔNG conflict với field khác ở offset khác
// v0rtex solution: encode identification info vào fields
// mà tồn tại ở mỗi offset:
dptr[j].ip_context = (dptr[j].ip_context & 0xffffffff) |
((uint64_t)(0x10000000 | i) << 32);
dptr[j].ip_messages.port.pad = 0x20000000 | i;
dptr[j].ip_lock.pad = 0x30000000 | i;
// Upper nibble (0x1/0x2/0x3) reveals which offset is active
// Lower bits reveal page index
Shared Memory Trick
// After gaining kread: remap zone-allocated OSString into userland
// → Direct r/w access to kernel memory without message passing
mach_vm_address_t shmem_addr = 0;
vm_prot_t cur = 0, max = 0;
mach_vm_remap(mach_task_self(), &shmem_addr, DATA_SIZE, 0,
VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR,
fakeport, // Fake task port → kernel_map
kernel_obj_addr, // Address of zone allocation
false, // Don't copy — share!
&cur, &max,
VM_INHERIT_NONE);
// shmem_addr giờ trỏ đến CÙNG physical memory với kernel object
// Write to shmem_addr = write to kernel memory
// No syscall needed — just pointer dereference!
Kernel Code Execution Without ROP
// v0rtex: fake IOUserClient với single gadget
// Thay vì ROP chain phức tạp, chỉ cần 1 gadget:
// ARM64 gadget: add x0, x0, #0x10; ret
// Located in kernelcache via pattern search
// Fake IOUserClient vtable:
fake_client->vtable = controlled_vtable;
// Override getExternalTrapForIndex:
controlled_vtable[TRAP_INDEX] = gadget_addr;
// gadget returns: x0 + 0x10 = pointer to IOExternalTrap
// IOExternalTrap contains function pointer + arguments
// When iokit_user_client_trap() called:
// → getExternalTrapForIndex() returns our IOExternalTrap
// → Kernel calls: trap->func(trap->p1, ..., trap->p6)
// → ALL arguments controlled by attacker!
// → Arbitrary kernel function calls with controlled args
PoC Deep Dive: IOHIDeous — Shared Memory Race
Obtaining IOHIDUserClient
// IOHIDUserClient normally held exclusively by WindowServer
// evOpenCalled flag prevents second open
// Method 1: Force logout
system("launchctl reboot logout");
// WindowServer exits → releases IOHIDUserClient
// Brief window to grab it before new WindowServer starts
// Method 2: Wait for shutdown
signal(SIGTERM, handler); // macOS sends SIGTERM before shutdown
// In handler: grab IOHIDUserClient
// After obtaining client: have access to shared memory
// Shared memory contains EvOffsets structure
// This is the race target
Timing the Race
// Race window is very tight — need precise timing
// Thread 1: continuously flip evGlobalsOffset in shared memory
// Thread 2: trigger IOHIDSystem::initShmem()
// Detection: after initShmem(), check evg->version
// If evg->version == 0 → race won! evg points to our offset
// If evg->version != 0 → race lost, retry
// Success rate: ~1 in 1000 attempts
// But deterministic detection → no false positives
// Just retry until success
From evg to Kernel Read
// After race: evg points into kalloc_map (attacker-controlled offset)
// evg->eventFlags field is writable via HIDKeyboardGlobalModifiers
// Trick: corrupt mach message descriptor count
// 1. Send mach message with 0 descriptors
// 2. evg write sets msgh_descriptor_count = 1
// 3. Kernel reinterprets message body as port descriptor
// 4. Craft fake port/task in shared memory (known address)
// 5. Receive message → fake port inserted into task namespace
// 6. pid_for_task(fake_port) → reads arbitrary 32-bit value
PoC Deep Dive: psychicpaper — Parser Exploitation
Full Bypass Chain (iOS 12+)
// Step 1: Create binary with crafted entitlements
// Entitlements plist contains <!---><!--> trick
// amfid (CoreFoundation parser): sees empty dict → PASSES validation
// Kernel (IOKit parser): sees private entitlements → GRANTS capabilities
// Step 2: Binary now has:
// com.apple.private.security.no-container → no sandbox
// task_for_pid-allow → can get task ports
// platform-application → treated as Apple binary
// Step 3 (iOS 11+): Platform check prevents direct mach port use
// Solution: exception port proxy technique
// Spawn Apple binary (e.g., cfprefsd) with:
posix_spawnattr_t attr;
posix_spawnattr_init(&attr);
// Set exception port = our_port (catch crashes)
// Set bootstrap port = our_port (proxy launchd)
// Spawn suspended, then resume
// Binary starts → we intercept bootstrap lookups
// Binary asks for com.apple.xxx service → we return our port
// Binary sends privileged message to "service" → we receive it
// → We can impersonate any mach service to the platform binary!
// Step 4: Use platform binary as "signing oracle"
// Send messages THROUGH platform binary to privileged services
// Platform binary's identity used for authorization checks
// → Full platform-level access from unprivileged app