首页
社区
课程
招聘
[原创][原创]Linux Kernel Exploit 内核漏洞学习(1)-Double Fetch
发表于: 2019-7-24 10:05 13478

[原创][原创]Linux Kernel Exploit 内核漏洞学习(1)-Double Fetch

2019-7-24 10:05
13478

Double Fetch从漏洞原理上讲是属于条件竞争漏洞,是一种内核态与用户态之间的数据存在着访问竞争;而条件竞争漏洞我们都比较清楚,简单的来说就是多线程数据访问时,并且没有对数据做必要的安全同步措施;当多线程时,对于同一数据有一个线程在读而有另外一个线程在写,这就可能引起数据的访问异常,而此时如果这个异常访问情况发生在内核与用户线程之间时,就触发double fetch漏洞了....
为了简化漏洞,这里我们利用2018 0CTF Finals Baby Kernel来学习这个漏洞的利用方法,其中驱动的运行环境我都已经放在这个github里面了,有需要的可以下载学习....

一个用户态线程准备的数据通过系统调用进入内核,这个数据在内核中有两次被取用,内核第一次取用数据进行了安全检查(比如缓冲区大小、指针可用性等),当检查通过后内核第二次取用数据进行实际处理;而在两次取用数据的间隙,另一个用户态线程可以创造条件竞争,对那个已经将通过了检查的用户态数据进行篡改,使得数据在真实使用时造成访问越界或缓冲区溢出,最终导致内核崩溃或权限提升....
简单的原理示意图就是这个样子:
原理示意图

现在我们直接来分析baby.ko这个驱动文件:

这个驱动文件主要注册一个baby_ioctl的函数:
baby_ioctl
这个函数中主要分为2个部分,一个部分打印flag在内核中的地址:

而另一部分则是直接打印出flag的值:

并且我们发现flag是被硬编码在驱动文件中的:
flag
(注意我们的目的为了不是直接得到这个flag的,而是通过Double Fetch漏洞从内核中获得她....)
但是如果想要驱动直接打印出flag的话,我们必须要绕过两处检查:
第一处是else if里面的条件:

其中_chk_range_not_ok的内容是:
_chk_range_not_ok
其实就是判断a1+a2是否小于a3....
而通过分析这个v5应该是一个结构体,通过*(_QWORD *)v5*(_DWORD *)(v5 + 8) == strlen(flag)我们很容易推出v5这个结构体包含的是flag的地址及其长度,如下:

而我们通过gdb调试发现*(_QWORD *)(current_task + 0x1358LL)的值为0x7ffffffff000:
gdb
所以我们推测和调试我们发现上面这个判断是判断v5以及v5->flag是否为用户态,如果不是用户态就直接返回:
gdb2
所以综上所述,检查为:

第一处是for循环里面的条件:

对用户输入的内容与硬编码的flag进行逐字节比较,如果一致了,就通过printk把flag打印出来了;

这个驱动晃眼一看好像没有什么漏洞,但是其实上面两个检查是分开的:
test
这就表明我们可以在判断flag地址范围和flag内容之间进行竞争,通过第一处的检查之后就把flag的地址偷换成内核中真正flag的地址;然后自身与自身做比较,通过检查得到flag....

所以整体思路就是先利用驱动提供的cmd=0x6666功能,获取内核中flag的加载地址(这个地址可以通过dmesg命令查看);
然后,我们构造一个符合cmd=0x1337功能的数据结构,其中len可以从硬编码中直接数出来为33,user_flag地址指向一个用户空间地址;
最后,创建一个恶意线程,不断的将user_flag所指向的用户态地址修改为flag的内核地址以制造竞争条件,从而使其通过驱动中的逐字节比较检查,输出flag内容....

poc.c:


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-7-25 11:29 被钞sir编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (16)
雪    币: 0
能力值: (RANK:0 )
在线值:
发帖
回帖
粉丝
2
2019-7-26 00:01
0
雪    币: 6313
活跃值: (3212)
能力值: ( LV12,RANK:330 )
在线值:
发帖
回帖
粉丝
3
分析很详细
2019-8-3 07:26
0
雪    币: 1421
活跃值: (162)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
想问下lz为什么我这里下完断点运行后就不动了,卡在这里了,另外一边运行poc也是直接出结果,没有断下这边的

gdb-peda$ target remote :1234
Remote debugging using :1234

[----------------------------------registers-----------------------------------]
RAX: 0xffffffffb9d99a00 --> 0x8948559066666666 
RBX: 0x0 
RCX: 0x0 
RDX: 0x0 
RSI: 0x0 
RDI: 0x0 
RBP: 0xffffffffba803e20 --> 0xffffffffba803e40 --> 0xffffffffba803e50 --> 0xffffffffba803e60 --> 0xffffffffba803ea0 --> 0xffffffffba803ec8 (--> ...)
RSP: 0xffffffffba803e20 --> 0xffffffffba803e40 --> 0xffffffffba803e50 --> 0xffffffffba803e60 --> 0xffffffffba803ea0 --> 0xffffffffba803ec8 (--> ...)
RIP: 0xffffffffb9d99d46 --> 0x841f0fc35d 
R8 : 0xffffffffbad4cad8 (0xffffffffbad4cad8)
R9 : 0x0 
R10: 0x0 
R11: 0xcdab07333 
R12: 0x0 
R13: 0xffffffffba812480 --> 0x80000000 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xffffffffb9d99d41:        mov    rbp,rsp
   0xffffffffb9d99d44:        sti    
   0xffffffffb9d99d45:        hlt    
=> 0xffffffffb9d99d46:        pop    rbp
   0xffffffffb9d99d47:        ret    
   0xffffffffb9d99d48:        nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffffb9d99d50:        push   rbp
   0xffffffffb9d99d51:        mov    rbp,rsp
[------------------------------------stack-------------------------------------]
0000| 0xffffffffba803e20 --> 0xffffffffba803e40 --> 0xffffffffba803e50 --> 0xffffffffba803e60 --> 0xffffffffba803ea0 --> 0xffffffffba803ec8 (--> ...)
0008| 0xffffffffba803e28 --> 0xffffffffb9d99a1e --> 0x5708258b44659066 
0016| 0xffffffffba803e30 --> 0x0 
0024| 0xffffffffba803e38 --> 0xffffffffba812480 --> 0x80000000 
0032| 0xffffffffba803e40 --> 0xffffffffba803e50 --> 0xffffffffba803e60 --> 0xffffffffba803ea0 --> 0xffffffffba803ec8 --> 0xffffffffba803ed8 (--> ...)
0040| 0xffffffffba803e48 --> 0xffffffffb94385f5 --> 0x841f0f66c35d 
0048| 0xffffffffba803e50 --> 0xffffffffba803e60 --> 0xffffffffba803ea0 --> 0xffffffffba803ec8 --> 0xffffffffba803ed8 --> 0xffffffffba803f20 (--> ...)
0056| 0xffffffffba803e58 --> 0xffffffffb9d99ed3 --> 0x6666906666fbc35d 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGTRAP
0xffffffffb9d99d46 in ?? ()
gdb-peda$ b baby_ioctl
Breakpoint 1 at 0x50
gdb-peda$ start
gdb-peda$ c
Continuing.
2019-8-3 20:04
0
雪    币: 1515
活跃值: (4416)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
5
tearorca 想问下lz为什么我这里下完断点运行后就不动了,卡在这里了,另外一边运行poc也是直接出结果,没有断下这边的 gdb-peda$ target remote :1234 Remote debug ...
加ko文件符号表的时候是不是没有给出基地址....
2019-8-4 22:21
0
雪    币: 1421
活跃值: (162)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
钞sir 加ko文件符号表的时候是不是没有给出基地址....
加了,用这个查的基地址是0

[    0.057898] Spectre V2 : Spectre mitigation: LFENCE not serializing, switchie

Boot took 1.28 seconds

/ $ lsmod
baby 16384 0 - Live 0x0000000000000000 (OE)
/ $ 
2019-8-6 13:56
0
雪    币: 1515
活跃值: (4416)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
7
因为系统有保护机制 你权限不够看不到真实地址 在调试的时候最好用root权限....
最后于 2019-8-9 20:18 被钞sir编辑 ,原因:
2019-8-6 18:11
0
雪    币: 1515
活跃值: (4416)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
8
在调试的时候最好用root权限....
最后于 2019-8-9 20:18 被钞sir编辑 ,原因:
2019-8-6 18:12
0
雪    币: 14539
活跃值: (17553)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
9
钞sir 不可以用这个 因为系统有保护机制 你权限不够看不到真实地址 在调试的时候最好用root权限....
mark,不过有一点不太明白,如果需要root权限才能看到地址,那我们黑什么呢??反正都已经有root权限了。。。还是我没太搞明白这个漏洞真正的作用??
2019-8-9 16:15
0
雪    币: 1515
活跃值: (4416)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
10
pureGavin mark,不过有一点不太明白,如果需要root权限才能看到地址,那我们黑什么呢??反正都已经有root权限了。。。还是我没太搞明白这个漏洞真正的作用??
是调试的时候用root权限.....
2019-8-9 20:16
0
雪    币: 3499
活跃值: (800)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
11
lz 请教个问题  我能否像win下 修改目标函数首字节为cc的方式 使它把异常扔给gdb 从而在加载驱动的那一刻对它进行调试呢?或者说有什么方法可以在它加载进驱动的那一刻断下 让我对目标函数下断呢 ? 小白求指导 感谢
最后于 2019-10-18 17:18 被F4our444编辑 ,原因: 语术不清
2019-10-18 16:53
0
雪    币: 1515
活跃值: (4416)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
12
噗咚Four lz 请教个问题  我能否像win下 修改目标函数首字节为cc的方式 使它把异常扔给gdb 从而在加载驱动的那一刻对它进行调试呢?或者说有什么方法可以在它加载进驱动 ...
可以在start.sh中的-gdb tcp::1234后面加上-S选项,让qemu暂停cpu等待gdb的链接试试
2019-10-19 14:26
0
雪    币: 3499
活跃值: (800)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
13
请教个问题 单核单线程的话 程序执行时 有没有可能在第一次验证和第二次验证之间 切换线程 将flag地址修改为内核地址  从而利用成功呢?
2019-10-25 18:39
0
雪    币: 1515
活跃值: (4416)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
14
噗咚Four 请教个问题 单核单线程的话 程序执行时 有没有可能在第一次验证和第二次验证之间 切换线程 将flag地址修改为内核地址 从而利用成功呢?
单核单线程恐怕是不可以了....
2019-10-26 17:05
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
厉害
2019-11-11 20:27
0
雪    币: 6313
活跃值: (3212)
能力值: ( LV12,RANK:330 )
在线值:
发帖
回帖
粉丝
16
请教一下setvbuf起到什么作用
2020-3-12 23:46
0
雪    币: 83
活跃值: (1087)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
17
祝你好运
2020-3-13 00:45
0
游客
登录 | 注册 方可回帖
返回
//