Skip to content

PAC and BTI: ARM's Answer to Code Reuse

Pointer authentication signs return addresses so a forged one faults; BTI forces indirect branches onto landing pads. How both work, how to enable and verify them, and their limits.

Published on 4 min read

This is the third walkthrough in the ARM64 Exploitation area, and it is written from the defender's side. The ret2win and ROP guides both ended at the same two mitigations. Here we explain what they actually do, how to turn them on and confirm they took, and where their limits are. PAC and BTI are the most important thing to understand about modern ARM security, because they attack the code-reuse class in hardware.

PAC: signing the return address

Pointer authentication (ARMv8.3) uses a secret, per-process key to compute a short signature — a PAC — over a pointer plus a context value, and stores that signature in the pointer's otherwise-unused high bits. For return addresses, -mbranch-protection=pac-ret makes the compiler:

  • in the prologue, paciasp — sign x30 (lr) using the key and sp as context, then save it;
  • in the epilogue, autiasp — authenticate x30 before ret (or the combined retaa).
pac-ret authenticates the link register before every return
paciasp (prologue)sign x30 with key + sp
body runs; saved lr on the stack carries a valid PAC
autiasp (epilogue)authenticate x30 before ret
valid? branch to caller / tampered? fault
ret / faulta forged lr fails auth and faults
codewritable memory

If an attacker overwrites the saved x30 via a stack overflow, the high-bit signature no longer matches the pointer. autiasp corrupts the value further (it flips the top bits on failure), so the subsequent ret jumps to a non-canonical address and the process faults. The attacker cannot supply a correct PAC because they do not have the key.

BTI: landing pads for indirect branches

Branch-target identification (ARMv8.5) protects the forward edge. With -mbranch-protection=bti, indirect branches (br, blr) may only transfer control to an instruction that is a valid landing pad — a bti (with c/j/jc modes) that the compiler emits at legitimate entry points.

A ROP or JOP gadget sitting in the middle of a function does not begin with a bti, so using it as an indirect-branch target raises a Branch Target exception. This does not restrict which valid function you jump to (it is coarse-grained), but it removes the vast supply of mid-function gadgets that code reuse relies on.

FeatureEdge protectedInstructionWhat it blocks
PAC (pac-ret)Backward (returns)paciasp/autiasp/retaaForged saved link register
BTIForward (indirect branches)bti c/jJumps into mid-function gadgets

-mbranch-protection=standard enables both.

Enabling and verifying

# Build with both PAC (return addresses) and BTI:
aarch64-linux-gnu-gcc -mbranch-protection=standard -O2 -o app app.c

Then confirm the binary really carries them — a flag that is silently dropped protects nothing:

$ readelf -n ./app | grep -A2 'GNU'
  Properties: AArch64 feature: BTI, PAC
$ objdump -d ./app | grep -m1 -E 'paciasp|bti c'
   4007c0:  d503233f  paciasp        ; return-address signing in the prologue

If the .note.gnu.property feature line and the paciasp/bti instructions are present, the protection is live. On real systems, every library in the process must also be built with these features for the protection to be complete.

The limits — an honest accounting

PAC and BTI raise the cost of code reuse; they are not an end to it:

  • Signing gadgets. If the program contains code that signs an attacker-influenced pointer with the right key and context, an attacker can obtain a validly signed pointer without the key.
  • Context reuse. PAC ties a signature to a context (e.g. sp). Signed pointers can sometimes be replayed where the same context recurs.
  • Leaks. A disclosure bug that leaks a validly signed pointer can hand the attacker a usable authenticated value — the same leak-then-act pattern from x86.
  • Speculation (PACMAN). Speculative-execution techniques have been shown to test PAC guesses without triggering faults, chipping at the brute-force cost.
  • BTI is coarse. It permits any valid landing pad, so whole-function reuse (ret2libc) into a bti-marked entry can still be possible.

None of this makes PAC/BTI optional — it makes them one strong layer among several. Combine them with a stack canary, PIE/ASLR, and memory-safety work.

What this teaches a defender

  • Turn on -mbranch-protection=standard and verify the note. It is the highest-leverage single measure against ARM code reuse, and it is easy to enable but also easy to drop silently (a prebuilt dependency, a wrong flag). Check readelf -n.
  • All-or-nothing across the process. PAC/BTI protect only the objects built with them. One legacy library without the feature is a gap. Audit the whole loaded set.
  • It is a cost multiplier, not a cure. Treat PAC/BTI like ASLR: they make exploitation expensive and unreliable, which is valuable, but the durable fix remains not having the memory-corruption bug — see memory-safe languages.

Key takeaways

  • PAC signs the return address with a secret key and a context; the epilogue authenticates it, so a forged saved lr faults instead of returning.
  • BTI forces indirect branches onto bti landing pads, invalidating mid-function gadgets on the forward edge.
  • -mbranch-protection=standard enables both; verify with readelf -n (BTI, PAC) and by finding paciasp/bti c.
  • They are a strong layer with real limits (signing gadgets, leaks, PACMAN, coarse BTI) — combine with the classic mitigations and memory safety.

This completes the ARM64 Exploitation arc: the link register and ret2win, ROP through the link register, and the PAC/BTI defences that break it. For the cross-architecture picture, compare with the x86-64 Exploitation Techniques area.

Related guides

0x8000 · ARM64 Exploitation

ARM64 Exploitation: the Link Register and ret2win

On AArch64 the return address lives in a register, not on the stack — until a non-leaf function saves it. Build the ARM ret2win in a lab and see where the saved link register sits.

0x8000 · ARM64 Exploitation

ROP on ARM64: Gadgets and the Link Register

AArch64 gadgets end in ret, which branches to x30 — so the chain is threaded through the link register with ldp gadgets. Build a system("/bin/sh") chain, then watch PAC and BTI break it.