PAC bypass on ARM64e
TL;DR: Apple Silicon signs function pointers, return addresses, and select data pointers with a per-process key — bypass is rarely “crack the key”, it’s finding a signing oracle, hijacking an unsigned pointer the code still trusts, or reusing a signed pointer the attacker can replay.
What it is
Pointer Authentication (ARMv8.3 PAC) inserts a 16-bit MAC (“tag”) into the unused high bits of a pointer using one of four keys (IA, IB, DA, DB) plus a 64-bit context modifier. PACIA x0, x1 signs; AUTIA x0, x1 verifies. A mis-auth corrupts the pointer so the next access faults. macOS / iOS use:
IA— function pointers, indirect calls.IB— return addresses.DA— Obj-Cisa, vtables,cf_runtime_class.DB— block invoke pointers, some Swift metadata.
Keys are reseeded per exec() and are kernel-only readable. The MAC is short (16 bits) — brute force is not infeasible for an oracle, but the more common path is to make the code sign something for you.
Preconditions / where it applies
- Apple Silicon (ARM64e) target — Apple’s own binaries, kernel, WebKit content process, frameworks compiled with
-mbranch-protection=pac-ret. - An existing arbitrary read/write or type-confusion primitive.
Technique
1. Signing oracle. Find code that accepts an attacker-influenced pointer and signs it. Common shapes:
- Obj-C
isaswizzling: writing to an object’sisafield invokesobjc_setClass, which signs the class pointer with the object’s context. If you control the value, you control a signedisa. - Block creation:
_Block_copysigns the invoke pointer. If a heap UAF lets you rewrite the block descriptor before_Block_copyruns, the runtime signs your value for free. - C++ vtable construction in attacker-controlled allocations.
2. Context confusion. PAC uses the storage address as the context discriminator by default (__ptrauth(key, address-diversity, discriminator)). If you can place a forged pointer where an authenticator expects a different context, the auth fails. But: if you find two sites that share a key + discriminator (e.g., compiler bug, missing __ptrauth attribute), you can move a legitimately-signed pointer between them.
3. Unsigned-pointer hijack. Most data pointers are not signed. A struct with one signed vtable and many raw void* callbacks lets you overwrite the unsigned ones; if the consuming code calls them via an unsigned indirect call (blr x8 without blraa), no auth runs. Look in disassembly for blr vs blraa/blrab — blr is unauthenticated.
4. Replay across processes. Keys are per-process, but inside one process you can replay any same-context signed pointer (Linus-style UAF: free a signed-pointer-containing object, reallocate, observe).
5. Brute force. 16-bit tag → 65,536 outcomes. If you have an oracle that lets you observe “fault vs no fault” cheaply and reset state, you can spray and confirm in seconds. Real example: Project Zero’s prosper brute force against WebKit JIT.
6. JIT escape. A signed function pointer in an RWX JIT region can be overwritten with attacker code; if the call site uses blraa it still authenticates the pointer’s tag, but if your overwrite preserves the tagged value and changes only the unsigned low bits, the auth passes (the auth covers the high bits, not the destination instructions).
Confirming PAC. otool -arch arm64e -hv binary shows the arm64e slice. pacibsp in prologues, retab in epilogues confirm pac-ret. BTI instructions (bti c, bti j) accompany it.
Detection and defence
- Always sign function pointers stored in attacker-reachable memory with
__ptrauth_function_pointer(or equivalent) — leaving them unsigned is the most common bug. - Use address diversity for every signed pointer; never share a discriminator across unrelated sites.
- Audit indirect-branch instructions: any
blrnot preceded by anaut*is a primitive. - For exploit developers: dump the dyld_shared_cache slice for arm64e and grep for
blraa/braato map authenticated callsites.
References
- Project Zero — PAC on the iPhone XS — first public deep dive
- Apple — Preparing your app to work with pointer authentication —
__ptrauthqualifiers - Brandon Azad — PAC and iBoot — kernel-side PAC research
- LLVM — clang pointer authentication ABI — discriminator selection rules