Android System Securing, Hardening, & Privacy

Bypassing Android PAC? A Reverse Engineering Lab on Pointer Authentication in ARMv9

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Memory Corruption Mitigations in ARMv9

The landscape of exploit development has consistently evolved, driven by the continuous cat-and-mouse game between attackers and defenders. For decades, memory corruption vulnerabilities have been a primary vector for privilege escalation and arbitrary code execution. Operating systems and hardware architects have responded with sophisticated mitigations like Address Space Layout Randomization (ASLR), Data Execution Prevention (DEP/NX bit), and Control Flow Integrity (CFI).

With ARMv9, a new class of hardware-assisted security features has been introduced, significantly raising the bar for exploit developers. Central among these are Pointer Authentication Codes (PAC) and Branch Target Identification (BTI). While BTI aims to restrict indirect branches to specific ‘landing pad’ instructions, PAC introduces cryptographic signatures for pointers, making traditional Return-Oriented Programming (ROP) and Jump-Oriented Programming (JOP) attacks considerably harder. This article delves into the intricacies of PAC and outlines a conceptual reverse engineering lab to explore potential bypass strategies on Android.

Understanding Pointer Authentication Codes (PAC)

PAC is a hardware-backed security feature designed to prevent attackers from corrupting pointers (especially return addresses on the stack or function pointers in memory) and then reusing them to redirect control flow to arbitrary code. It works by cryptographically signing a pointer with a secret hardware key and a context value before it’s stored, and then verifying that signature before the pointer is used.

How PAC Works

  1. Key Generation: ARMv9 processors provide several secret, hardware-generated 128-bit keys (e.g., APIAKey, APIBKey, APDAKey, APDBKey, APGKey). These keys are inaccessible to software.
  2. Pointer Signing (Authentication Code Generation): When a pointer is stored (e.g., a return address pushed onto the stack), a PAC instruction (e.g., PACIA for instruction pointers using APIAKey) takes the pointer, a context value (often the stack pointer, SP, or Link Register, LR), and one of the secret keys. It computes a cryptographic signature (the PAC) and embeds it into the spare bits of the pointer (typically the most significant bits). The resulting value is the ‘authenticated pointer’.
  3. Pointer Verification (Authentication Code Checking): Before an authenticated pointer is used (e.g., a RET or BLR instruction uses a signed return address), an authentication instruction (e.g., AUTIA for instruction pointers) uses the same key and context to regenerate the expected PAC. If the regenerated PAC matches the one embedded in the pointer, the pointer is deemed valid, and the embedded PAC bits are stripped, returning the original pointer. If they don’t match, a fault is triggered, preventing execution from the corrupted address.

Consider a typical function call and return flow with PAC:

// C function example: caller calls callee_with_pac() blr x30 is essentially ret. x30 holds the return address.void callee_with_pac() { // ... function body ...}// In assembly (simplified):// caller side: mov x1, #some_data_address str x1, [sp, #0x10] // Store some data pointer pacia x30, sp // Sign return address in x30 using SP as context blr x2 // Branch to callee. Callee will use x30 to return. // callee side: autia x30, sp // Authenticate x30 before returning. If PAC invalid, fault. ret // Return to signed x30.

PAC Instructions

Key PAC instructions include:

  • PACIA/PACIB: Authenticates instruction addresses using APIAKey/APIBKey.
  • PACDA/PACDB: Authenticates data addresses using APDAKey/APDBKey.
  • PACGA: Authenticates generic pointers using APGKey, SP, and two other registers.
  • AUTIA/AUTIB: Authenticates and strips PAC from instruction addresses using APIAKey/APIBKey.
  • AUTDA/AUTDB: Authenticates and strips PAC from data addresses using APDAKey/APDBKey.
  • BLRAA/BLRAB: Branches to an instruction address, authenticating it with APIAKey/APIBKey.
  • RET: When used with a signed Link Register (X30), it implicitly authenticates X30 using the appropriate key and context (e.g., SP).

The Reverse Engineering Lab: Setting the Stage

To investigate PAC, a practical lab environment is essential. While a physical ARMv9 device offers the most realistic scenario, emulation with QEMU running an Android AOSP build is often more accessible for experimentation.

Lab Setup (Conceptual)

  1. QEMU with ARMv9 Support: Setup a QEMU instance capable of emulating an ARMv9 architecture. This typically involves using a recent QEMU version and a custom-built Android kernel configured for ARMv9.
  2. Android AOSP Build: Compile Android Open Source Project (AOSP) for your QEMU target. Ensure you enable PAC features during kernel configuration (e.g., CONFIG_ARM64_PTR_AUTH).
  3. Debugging Tools:
    • GDB: For low-level debugging of the kernel and user-space binaries.
    • Frida: For dynamic instrumentation, hooking, and memory inspection in user-space.
    • Binary Analysis Tools: Ghidra or IDA Pro for static analysis of system libraries (libc.so, libart.so) and custom binaries to identify PAC-related instructions.

Example Disassembly with PAC

When analyzing a binary with Ghidra or IDA Pro, you’d look for instructions like PACIA or AUTIA. For instance, a function prologue or epilogue might reveal:

// Example of a function prologue pushing LR and signing itsub_400000: mov x0, x1 // ... other instructions ... mov x29, sp // Frame pointer push (fp, lr) stp x29, x30, [sp, #-0x30]! pacia x30, sp // Sign LR (x30) using SP as context and store on stack// ... function body ...// Example of a function epilogue retrieving and authenticating LR// ... some code ... ldp x29, x30, [sp], #0x30 // Restore fp, signed LR autia x30, sp // Authenticate LR (x30) using SP as context ret // Return to validated LR

Potential Bypass Strategies for PAC

Bypassing PAC is significantly harder than traditional ROP. Direct manipulation of a signed pointer will invalidate its PAC, leading to a fault. Attackers must find ways to either:

  • Obtain a valid authenticated pointer from a legitimate source.
  • Influence the PAC generation/validation process.
  • Find code paths that operate on unauthenticated pointers.

1. Information Leaks of Authenticated Pointers

If an attacker can leak an authenticated pointer from a legitimate context, they might reuse it. For example, if a vulnerability allows reading parts of the stack where a valid signed return address resides, an attacker could potentially copy and use this signed pointer without modification. However, the context (e.g., SP value) might differ, making direct reuse difficult without further manipulation.

2. Context Confusion Attacks

PAC is generated using the pointer, a key, and a context value. If an attacker can control the context value (e.g., manipulate SP before a PAC instruction is executed, or find a gadget that uses a different SP for authentication), they might be able to craft a pointer that authenticates successfully for a different target. This is complex as SP is usually managed by the stack frame.

3. Reusing PAC-Aware Gadgets

Instead of trying to forge a PAC, an attacker might look for existing

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner