PAC — Pointer Authentication Code
PAC thêm cryptographic signature vào pointers. Nếu attacker corrupt pointer, signature check fails → crash thay vì code execution. Áp dụng từ A12 (iPhone XS).
Cơ Chế Hoạt Động
Signing & Verification
Original pointer: 0x0000FFFFF1234000
│ │
PAC bits │ Address │
(upper bits, normally 0)
Signed pointer: 0xAB12FFFFF1234000
│ │
PAC value │ Address │
(cryptographic hash)
Sign: PACIA X0, X1 → X0 = sign(X0, key_A, X1_context)
Verify: AUTIA X0, X1 → if valid: strip PAC, return original pointer
if invalid: corrupt pointer → crash on dereference
Strip: XPACI X0 → strip PAC bits without verification
Keys
| Key | Instruction prefix | Dùng cho |
|---|---|---|
| IA (Instruction A) | PACI/AUTI | Return addresses, function pointers |
| IB (Instruction B) | PACIB/AUTIB | Alternate instruction key |
| DA (Data A) | PACDA/AUTDA | Data pointers |
| DB (Data B) | PACDB/AUTDB | Alternate data key |
| GA (Generic) | PACGA | Generic authentication |
Mỗi process có riêng bộ keys (set bởi kernel tại process creation).
Context (Modifier)
PAC value = f(pointer, key, context):
PACIA X0, X1 ← X1 là context (modifier)
Thường là: SP (stack pointer), hoặc 0, hoặc address of storage
Cùng pointer + cùng key + KHÁC context = KHÁC PAC value
→ Pointer signed cho context A không valid cho context B
→ Ngăn reuse signed pointers across different call sites
PAC Trong XNU Kernel
What’s Protected
1. Return addresses (LR/X30):
Function prologue: PACIBSP ; Sign LR with key B, context SP
Function epilogue: RETAB ; Auth LR with key B, return
2. Function pointers trong kernel structures:
Signed with key A + context (usually address of pointer storage)
3. vtable pointers (C++ virtual dispatch):
IOKit object vtables signed with PAC
4. Thread state:
Exception frames contain PAC'd return addresses
arm64e ABI
arm64e = ARM64 with PAC extensions. Mach-O binaries compiled cho arm64e:
CPU_SUBTYPE_ARM64E = 0x02
arm64e binaries:
- Compiler generates PAC instructions automatically
- vtable entries signed at compile time
- JIT code requires PAC signing
PAC Bypass Techniques
1. Signing Gadgets
Tìm code sequences trong system frameworks sign arbitrary pointers:
; Gadget example (conceptual):
LDR X0, [X1] ; Load attacker-controlled value
PACIA X0, X2 ; Sign it with key A, context X2
STR X0, [X3] ; Store signed pointer
RET
Nếu attacker controls X1 (source) và X3 (destination): → Forge signed pointer cho arbitrary address
Real-world: Predator spyware hunted signing gadgets trong JavaScriptCore (20-byte ARM64 sequence).
2. PAC Forgery via Context Mismatch
Nếu pointer được sign với context C1 nhưng verify với context C2:
- Bug trong kernel code dùng sai context
- Attacker control context value
→ Sign pointer với expected verify context
3. PACMAN Attack (Speculative Execution)
Brute-force PAC value qua speculative execution:
1. Guess PAC value
2. Speculatively execute AUTIA
3. If correct: speculative load succeeds (measurable side effect)
4. If wrong: speculative load fails (different timing)
→ Timing oracle reveals correct PAC value
→ 2^16 attempts max (PAC is typically 16 bits)
4. Kernel Memory R/W → PAC Bypass
Nếu đã có kread/kwrite:
1. Đọc PAC key values từ kernel memory
2. Compute valid PAC offline
3. Write signed pointer directly
Hoặc:
1. Tìm signing gadget address
2. Setup registers + call gadget để sign arbitrary pointer
5. Non-PAC Code Paths
Không phải mọi pointer đều PAC'd:
- Legacy code chưa migrate sang arm64e
- Data pointers (not always signed)
- Certain kernel objects miss PAC coverage
→ Target unprotected pointers