Double Fetch从漏洞原理上讲是属于条件竞争漏洞,是一种内核态与用户态之间的数据存在着访问竞争;而条件竞争漏洞我们都比较清楚,简单的来说就是多线程数据访问时,并且没有对数据做必要的安全同步措施;当多线程时,对于同一数据有一个线程在读而有另外一个线程在写,这就可能引起数据的访问异常,而此时如果这个异常访问情况发生在内核与用户线程之间时,就触发double fetch漏洞了....
为了简化漏洞,这里我们利用2018 0CTF Finals Baby Kernel来学习这个漏洞的利用方法,其中驱动的运行环境我都已经放在这个github里面了,有需要的可以下载学习....
一个用户态线程准备的数据通过系统调用进入内核,这个数据在内核中有两次被取用,内核第一次取用数据进行了安全检查(比如缓冲区大小、指针可用性等),当检查通过后内核第二次取用数据进行实际处理;而在两次取用数据的间隙,另一个用户态线程可以创造条件竞争,对那个已经将通过了检查的用户态数据进行篡改,使得数据在真实使用时造成访问越界或缓冲区溢出,最终导致内核崩溃或权限提升....
简单的原理示意图就是这个样子:
现在我们直接来分析baby.ko这个驱动文件:
这个驱动文件主要注册一个baby_ioctl的函数:
这个函数中主要分为2个部分,一个部分打印flag在内核中的地址:
而另一部分则是直接打印出flag的值:
并且我们发现flag是被硬编码在驱动文件中的:
(注意我们的目的为了不是直接得到这个flag的,而是通过Double Fetch漏洞从内核中获得她....)
但是如果想要驱动直接打印出flag的话,我们必须要绕过两处检查:
第一处是else if里面的条件:
其中_chk_range_not_ok的内容是:
其实就是判断a1+a2是否小于a3....
而通过分析这个v5应该是一个结构体,通过*(_QWORD *)v5
和*(_DWORD *)(v5 + 8) == strlen(flag)
我们很容易推出v5这个结构体包含的是flag的地址及其长度,如下:
而我们通过gdb调试发现*(_QWORD *)(current_task + 0x1358LL)
的值为0x7ffffffff000:
所以我们推测和调试我们发现上面这个判断是判断v5以及v5->flag是否为用户态,如果不是用户态就直接返回:
所以综上所述,检查为:
第一处是for循环里面的条件:
对用户输入的内容与硬编码的flag进行逐字节比较,如果一致了,就通过printk把flag打印出来了;
这个驱动晃眼一看好像没有什么漏洞,但是其实上面两个检查是分开的:
这就表明我们可以在判断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编辑
,原因: