Android Hacking, Sandboxing, & Security Exploits

From Userland to Kernel: Privilege Escalation Techniques on Android ARM64 Devices

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction

Privilege escalation on Android ARM64 devices is a critical area in mobile security, enabling an attacker to gain elevated permissions from a sandboxed application context to the highly privileged kernel space. This transition, often referred to as a “userland to kernel” exploit, bypasses Android’s robust security mechanisms, including SELinux and user ID separation, to achieve full control over the device. Understanding these techniques is paramount for both security researchers developing defensive measures and ethical hackers identifying vulnerabilities. This article delves into the methodologies, common vulnerability classes, and exploitation primitives involved in crafting a kernel exploit on Android’s ARM64 architecture.

Android’s Kernel Interface and Security Posture

The Userland-Kernel Boundary

Android applications operate in userland, isolated by various mechanisms like sandboxing and permission models. Interaction with hardware and core system functionalities is mediated by the Linux kernel, primarily through system calls and device drivers (e.g., in /dev). Vulnerabilities often reside in these kernel interfaces, particularly in custom drivers provided by SoC vendors or OEMs, which might not receive the same level of scrutiny as the mainline Linux kernel.

SELinux and Namespaces

Android extensively uses SELinux (Security-Enhanced Linux) to enforce Mandatory Access Control (MAC) policies, strictly limiting what even a root process can do unless its SELinux context permits. Kernel exploits typically aim to achieve an arbitrary kernel read/write primitive, which can then be used to modify kernel data structures (like cred) to elevate privileges. However, a full bypass often requires also manipulating SELinux policies or switching the process’s SELinux context.

Identifying Kernel Vulnerabilities on ARM64

Finding exploitable bugs in the kernel involves a combination of static and dynamic analysis. Focus areas typically include:

  • Custom Device Drivers: Often found in /dev, these are prime targets due to their proprietary nature and potential for less rigorous auditing.
  • IPC Mechanisms: Binder, ASHMEM, and other inter-process communication interfaces can expose kernel components to userland input.
  • System Calls: While mainline syscalls are heavily vetted, vendor-specific additions or modifications can introduce vulnerabilities.

Common Vulnerability Classes

Kernel vulnerabilities often fall into categories similar to userland memory corruption issues:

  • Kernel Information Leaks: These expose kernel addresses, critical for bypassing KASLR (Kernel Address Space Layout Randomization). Examples include uninitialized stack variables or out-of-bounds reads on kernel heaps that leak pointers.
  • Memory Corruption:
    • Use-After-Free (UAF): A kernel object is freed, but the pointer to it remains in use, leading to potential re-use with attacker-controlled data.
    • Out-of-Bounds (OOB) Access: Reading or writing beyond the intended boundaries of a kernel buffer.
    • Type Confusion: An object is treated as a different type, leading to incorrect member access.
  • Logic Bugs: Flaws in the kernel’s design or implementation that can lead to privilege escalation without direct memory corruption, e.g., incorrect permission checks.

Tools like Ghidra, IDA Pro, and various fuzzers (syzkaller, customized userspace fuzzers for specific drivers) are essential for this phase. When analyzing a custom driver, one might look for specific ioctl commands that take large or unchecked userland buffers.

// Example of a conceptual vulnerable ioctl handler in a kernel driver: // Vulnerable driver `my_device` in `drivers/char/`// Userland interacts with `/dev/my_device` long my_driver_ioctl(struct file *file, unsigned int cmd, unsigned long arg){    struct my_object *obj = (struct my_object *)file->private_data;    char *kernel_buffer;    size_t user_size;    switch (cmd) {        case VULN_IOCTL_ALLOC_BUF:            user_size = (size_t)arg;            obj->buffer = kmalloc(user_size, GFP_KERNEL); // Allocates a buffer of user-specified size            obj->size = user_size;            if (!obj->buffer) return -ENOMEM;            break;        case VULN_IOCTL_COPY_TO_BUF:            if (!obj->buffer) return -EINVAL;            // VULNERABILITY: No size check against obj->size, potential OOB write            // `copy_from_user` might copy more than `obj->size` if `arg` points to a larger user buffer            copy_from_user(obj->buffer, (void __user *)arg, obj->size_from_user_input); // `size_from_user_input` might be larger than `obj->size`            break;        case VULN_IOCTL_FREE_BUF:            if (obj->buffer) {                kfree(obj->buffer);                obj->buffer = NULL; // VULNERABILITY: Potential UAF if not cleared globally            }            break;    }    return 0;}

Exploitation Primitives: From Bug to Control

Achieving Arbitrary Read/Write

Once a memory corruption vulnerability is identified, the goal is to transform it into a reliable arbitrary kernel read/write primitive. This often involves:

  • Heap Feng Shui (Heap Spray): For UAF vulnerabilities, this technique involves carefully allocating and freeing kernel objects to control the memory layout. The goal is to ensure that a newly allocated object, controlled by the attacker, occupies the memory region of a previously freed vulnerable object. This allows the attacker to manipulate the contents of the reallocated object through the dangling pointer.
  • Modifying Pointers: An OOB write can directly corrupt kernel pointers, redirecting them to attacker-controlled memory.

An arbitrary read/write primitive is the holy grail, as it allows attackers to inspect and modify any kernel memory location.

// Conceptual steps for gaining arbitrary write via UAF: // 1. Trigger the UAF to free `obj_A`. The kernel pointer `dangling_ptr` still points to its memory. // 2. Perform heap spray: Allocate many kernel objects `obj_B` of the same size as `obj_A`, containing controlled data (e.g., a faked object with a function pointer pointing to `commit_creds`). // 3. One `obj_B` will likely occupy the memory previously held by `obj_A`. // 4. Trigger an operation on `dangling_ptr` that writes data. This write now modifies `obj_B`, allowing an attacker to overwrite critical fields like function pointers or security-related flags.

Bypassing KASLR (Kernel Address Space Layout Randomization)

KASLR randomizes the base address of the kernel and modules at boot time, making it harder to predict the location of essential kernel functions (like commit_creds or prepare_kernel_cred). An information leak vulnerability is crucial for bypassing KASLR. This leak can come from:

  • An uninitialized stack/heap variable leaking a kernel pointer.
  • An OOB read on a kernel object that contains function pointers.
  • Reading symbolic information from /proc/kallsyms (if permissions allow, which is rare on production Android).
// Userland pseudocode for leaking a kernel function pointer: // Assume a UAF vulnerability allows reading from a reallocated object // that now contains a pointer to a known kernel function. unsigned long leaked_addr = ioctl(fd, VULN_IOCTL_LEAK_ADDR, NULL); printf("Leaked kernel address: 0x%lx
", leaked_addr); // Calculate kernel base based on the known offset of the leaked function unsigned long kernel_base = leaked_addr - KNOWN_OFFSET_OF_LEAKED_FUNCTION; printf("Estimated kernel base: 0x%lx
", kernel_base);

With the kernel base address, an attacker can then calculate the addresses of any desired kernel functions or gadgets by consulting kernel symbol tables (e.g., System.map from a matching kernel image).

Gaining Root: Kernel Payload and Privilege Escalation

Understanding cred and task_struct

The Linux kernel manages process privileges using the cred structure, which contains fields like uid, gid, euid, etc. Each process also has a task_struct, which holds a pointer to its cred structure. To escalate privileges to root (UID 0, GID 0), an attacker typically needs to modify these values in their process’s cred structure.

The commit_creds(prepare_kernel_cred(0)) Gadget

The standard way to escalate privileges in the kernel is to call commit_creds(prepare_kernel_cred(0)). The prepare_kernel_cred(0) function creates a new cred structure with root privileges. commit_creds() then applies these new credentials to the current task. To execute this, an attacker needs to gain arbitrary code execution in kernel mode.

Kernel Code Execution (ROP/JOP)

On ARM64, arbitrary kernel code execution is often achieved using Return-Oriented Programming (ROP) or Jump-Oriented Programming (JOP). With an arbitrary write primitive, an attacker can overwrite a function pointer (e.g., in a kernel object or a hook point) to point to a chain of small code snippets (gadgets) within the existing kernel text. These gadgets are carefully selected to:

  • Set up arguments for prepare_kernel_cred(0).
  • Call prepare_kernel_cred.
  • Set up arguments for commit_creds.
  • Call commit_creds.
  • Return to a safe kernel address or userspace.
// Simplified Kernel Payload to call `commit_creds(prepare_kernel_cred(0))` // This would be the 'code' executed by the ROP/JOP chain. void kernel_payload(void) {    void *(*prepare_kernel_cred_ptr)(void *) = (void *(*)(void *))kernel_prepare_kernel_cred_address;    void (*commit_creds_ptr)(void *) = (void (*)(void *))kernel_commit_creds_address;    // The '0' argument signifies requesting root credentials    commit_creds_ptr(prepare_kernel_cred_ptr(0));    // Optionally, return to a userspace function or a safe kernel return address}

The addresses of kernel_prepare_kernel_cred_address and kernel_commit_creds_address would be determined after bypassing KASLR.

Post-Exploitation and SELinux Bypass

Even after successfully calling commit_creds(prepare_kernel_cred(0)), the attacker’s process might still be constrained by SELinux. If the process is in a highly restricted SELinux domain, it may still be unable to perform common root actions (like writing to sensitive files or escalating other processes). Advanced exploits might therefore also target:

  • Modifying the process’s SELinux context to an unconfined domain.
  • Disabling SELinux entirely (though this is increasingly difficult and detected).
  • Bypassing SELinux checks by directly modifying kernel security hooks.

Conclusion

Privilege escalation from userland to kernel on Android ARM64 devices is a complex but achievable goal for determined attackers. It hinges on identifying subtle vulnerabilities in vendor-specific kernel code, transforming these into powerful arbitrary read/write primitives, bypassing KASLR, and then executing a kernel payload to achieve root privileges. The ongoing evolution of Android’s security features, including stricter SELinux policies, PAC (Pointer Authentication Codes), and MTE (Memory Tagging Extension) on newer ARM architectures, continuously raises the bar for exploit developers. Nevertheless, understanding these foundational techniques remains crucial for hardening Android devices against sophisticated threats.

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