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

Tài Nguyên