用户
搜索

[思路/技术] double free分析

回帖奖励 50 魔法币 回复本帖可获得 1 魔法币奖励! 每人限 1 次(中奖概率 10%)
  • TA的每日心情

    2018-6-6 19:03
  • 签到天数: 38 天

    连续签到: 1 天

    [LV.5]常住居民I

    i春秋作家

    Rank: 7Rank: 7Rank: 7

    99

    主题

    584

    帖子

    601

    魔法币
    收听
    0
    粉丝
    80
    注册时间
    2016-2-2

    楚燕春秋巡逻春秋游侠核心白帽i春秋签约作者突出贡献白帽高手秦齐赵春秋文阁

    发表于 2018-7-17 02:00:03 04407
    简介

    Double Free其实就是同一个指针free两次。虽然一般把它叫做double free。其实只要是free一个指向堆内存的指针都有可能产生可以利用的漏洞 double free的原理其实和堆溢出的原理差不多,都是通过unlink这个双向链表删除的宏来利用的。只是double free需要由自己来伪造整个chunk并且欺骗操作系统。

    实现对漏洞的有效利用,攻击者利用成功可导致权限提升。afd.sys是内核用来管理socket的模块


    漏洞分析
    POC
    1.png
    POC主要做了这么两件事:
    初始化了一个本地socket连接。
    给这个socket发送了两个控制码:0x1207F和0x120C3。
    将编译好的程序放在虚拟机中运行,触发漏洞之后的BSOD:
    2.png

    看到这里是不是觉得这个漏洞很眼熟??
    没错他就是ms14-040蓝屏漏洞
    3.png
    显示的信息为BAD_POOL_CALLER,BugCheck 0xc2。
    设置双机调试后,触发BSOD,部分windbg输出的信息:

    4.png
    调用堆栈:
    5.png

    现在,我们可以知道:
    出问题的是afd.sys模块,漏洞的类型为double free,free 的对象是Mdl,并且发生崩溃时存在这样的调用关系:
    afd!AfdTransmitPacketsà afd!AfdTliGetTpInfoà afd!AfdReturnTpInfoànt!IoFreeMdl

    对afd.sys开启Special Pool后,再次运行POC,但是却没有发生崩溃,这有点奇怪。可是我们必须找到切入点,对问题Mdl对象进行回溯,然后搞清楚整个流程,锁定问题。

    未开启Special Pool时,我们知道最后崩溃时的函数调用关系,或许可以对afd!AfdReturnTpInfo下断点,若此时也调用了该函数,可能会获得一些信息。但首先我们要搞清楚POC向afd.sys发送的两个控制码所对应的内核函数。

    要找到这个对应关系,有这样的调试技巧:用户层的IoControl消息到都会被内核包装成IRP包,发送给对应驱动的IRP_MJ_DEVICE_CONTROL例程来处理,IRP_MJ_DEVICE_CONTROL例程会根据控制码来选择对应的函数。

    使用Windbg为我们提供了这样的功能
    6.png
    这样就可以得到afd.sys对应的IRP_MJ_DEVICE_CONTROL例程为afd!AfdDispatchDeviceControl,利用IDA对该函数简单分析后,其大致流程如下: 7.png
    设置如下两个断点:
    8.png
    再次运行POC,便可以得到两个控制码所对应的内核函数:
    0x1207F:afd!AfdTransmitFile
    0x120C3:afd!AfdTransmitPackets
    接下来对上面提到的afd!AfdReturnTpInfo下断点,此时对afd.sys仍然开启了Special pool。看看有啥惊喜。
    运行POC,待其断下来之后,对释放的Mdl进行跟踪:
    9.png


    此时调用afd!AfdReturnTpInfo的并不是afd!AfdTransmitPackets,而是afd!AfdTransmitFile。好像还不能明白发生了什么,先记录下此时Mdl分配和释放的相关函数调用关系:
    分配:afd!AfdTransmitFile+0x170à nt!VerifierIoAllocateMdl
    释放:afd!AfdTransmitFile+0x5a3à afd!AfdReturnTpInfo+0xadxà nt!IoFreeMdl
    nt!VerifierIoAllocateMdl这个函数有点奇怪,正常情况都是调用nt!IoAllocateMdl来分配Mdl的空间,IDA中此时也是调用的IoAllocateMdl,这是否是导致开启special pool后不崩溃的原因?这个问题还有待考证。

    接着再看,在函数afd!AfdReturnTpInfo内,Mdl=[edi+0ch],所以利用同样的方法,在windbg中查看edi的分配释放记录:
    10.png
    记录下关于edi的函数的调用关系:
    AfdTransmitFileàAfdTliGetTpInfoàExAllocateFromNPagedLookasideListàAfdAllocateTpInfo
    而在函数afd!AfdReturnTpInfo中,edi=[esi+20h],同样的方法:
    11.png
    怎么会和edi的结果一样?根据此时的函数调用,来看看afd!AfdTliGetTpInfo这个函数:
    12.png
    从这段IDA截图,根据微软的匈牙利命名法,可以知道函数afd! AfdReturnTpInfo中的edi为TpInfoElement,esi=TpInfo。并且TpInfoElementArray=*(DWORD*)(TpInfo+20h),sizeof(TpInfoElement)=0x18。

    稍作总结一下我们所知道的:IoControl=0x1207F时,会调用afd!AfdTransmitFile,afd!AfdTransmitFile会调用afd!AfdTliGetTpInfo分配一个TpInfo,接着会调用nt!IoAllocateMdl分配一个Mdl,然后会从TpInfo结构中得到这个Mdl,并释放掉。

    接着用windbg使用系统继续运行,windbg再也没有断下来,有一点忧伤。再次查看崩溃时的函数调用关系,将断点的位置提前到afd!AfdReturnTpInfo开始的时候。然后IoControl=0x120C3时,断点断下来后,利用windbg跟踪此时的esi:
    13.png

    这个调用关系看起来十分的眼熟,和IoControl=0x1207F时除了对应的内核函数不同,其他调用简直一模一样!那么我们来看看此时是如果避开了释放Mdl的流程。

    利用windbg单步跟踪一下,发现其在afd!AfdReturnTpInfo+0x69处,由于[esi+28h]=0,跳转到另一条不执行释放Mdl的流程了。
    14.png

    而此时esi=TpInfo,我们可以猜测TpInfo结构另一个成员:TpInfo+28h=TpInfoElementCount。
    好了,现在对整个流程有一个粗略的了解了。再回看一下漏洞的描述,"Mdl double free",那么可以大胆的猜测一下:double free 的Mdl就是afd!AfdTransmitFile所创建的Mdl!

    思考一下
    关闭special pool,设置如下两个断点:
    15.png
    记录下afd! AfdTransmitFile所创建的Mdl的地址,和最后调用afd!AfdTransmitPackets时释放Mdl地址做比较。

    我们惊奇的发现,这两个地址是一样的!也就是最后afd!AfdTransmitPackets流程中释放的Mdl正是afd!AfdTransmitFile所创建的Mdl!
    这样我们对整个流程又有了进一步的了解:
    IoControl=0x1207F,对应的内核函数afd!AfdTransmitFile会创建TpInfo结构,分配一个Mdl并将地址存入到TpInfo结构的TpInfoElementArray中,接着其会调用afd!AfdReturnTpInfo释放掉Mdl。

    IoControl=0x120C3,对应的内核函数afd!AfdTransmitPackets会从TpInfo取出Mdl,而这个Mdl正好是afd!AfdTransmitFile已经释放掉了的,此时afd!AfdTransmitPackets会尝试对其进行第二次释放。然后就BSOD。

    目前有一个大大的疑问:为什么afd!AfdTransmitPackets流程中释放的就那么巧的就是afd!AfdTransmitFile所创建的那个Mdl?内核中pool的分配和释放时刻都在发生,为何会恰好得到那一块pool?

    看来,得要去分析一下TpInfo分配和释放相关的一些东西了。

    通过上面的分析,我们知道了一些和TpInfo分配和释放相关的函数:
    ExAllocateFromNPagedLookasideList,AfdTliGetTpInfo,AfdAllocateTpInfo,AfdReturnTpInfo,ExFreeToNPagedLookasideList,AfdTransmitFile和AfdTransmitPackets。

    结合静态和动态分析,将POC流程走的这个几个函数分析出来,特别留意和POC的关系。这个没啥好说的了,就直接把逆出来的伪C代码贴出来,然后加以解释了。

    再说第一个函数之前,介绍一下IRP和IO_Stack_Location,比如IOControl=0x120C3时:
    函数AfdTransmitPackets开始时候的ecx就是IRP:
    16.png

    红框内的值是否看起来有一点眼熟?回头看看POC,这正是第二次DeviceIoControl的部分参数。IRP的结构微软一直藏着掖着的,公开的部分结构也比较模糊。

    IO_Stack_Location位置IRP+60h位置,其结构很简单但是因为有一个union结构的存在,显得很多,此时Parameters对应的是DeviceIoControl,结构如下: 17.png

    此时IO_Stack_Location的内存:
    18.png
    其中的Type3InputBuffer成员是用来存储DeviceIoControl中InputBuffer的内容,位于IO_Stack_Location+0x10的位置。
    19.png
    AfdTransmitFile
    20.png
    1. 检查用户层DeviceIoControl中InputBufferSize是否大于30h。
    2. 对IoStackLocation->Type3InputBuffer做一些有效性检查。
    3. 调用AfdTliGetTpInfo分配一个TpInfo结构。
    4. 根据VirtualAddress和Length创建一个Mdl(此时VirtualAddress和Length的值是从Type3InputBuffer得到的,分别对应的是inbuf1[6]和inbuf1[7]),并将其地址写入到TpElementArray的合适位置。
    5. 调用函数MmProbeAndLockPages准备操作这块Mdl,但是此时因为要映射的地址无效(VirtualAddress=0x13371337),触发异常,调用AfdReturnTpInfo释放TpInfo。
    21.png
    1. 设置异常处理模块,发生异常会调用AfdReturnInfo函数。
    2. 调用ExAllocateFromNPagedLookasideList函数从Lookaside List为TpInfo分配一块pool。
    3. 判断tpElementCount是否大于3,大于则会为TpElementArray更多的空间,否则就直接用TpInfo的空间了。
    这里的nCount是通过用户层DeviceIoControl的InputBuffer获得,位于InputBuffer[2]。

    来解释一下此时的Lookaside,可以看到其是一个定值为0x874ff428,这表明其是一个Dedicated Lookaside Lists,相当于专用的Lookaside 。用windbg查看这个Lookaside:
    22.png 23.png
    可以看到,刚开始的时候这个Lookaside为空。
    Lookaside的分配和释放算法是理解为何会得到同一个Mdl的关键所在,来看一下:
    24.png


    首先是分配算法,流程如下:
    1. 记录分配的次数。
    2. 尝试从Lookaside卸载下一个ListEntry。第一次的IOControl,Lookaside为空,所以会进入到下一步的流程。
    3. 尝试失败,调用Lookaside的分配函数重新分配一块pool。

    接着是释放算法:

    25.png
    流程:
    1. 记录释放的次数。
    2. 如果此时Lookaside的Depth小于Lookaside的MaxDepth,则会将参数中的ListEntry加入到Lookaside中,否则进入下一步。
    3. 记录释放失败的次数,调用Lookaside对应的释放函数。
    在来看看TpInfo的分配和释放函数:
    26.png
    分配函数没有什么好说的,下面是释放函数:
    27.png
    流程:
    1. 遍历TpInfo的TpElementArray数组,释放Mdl。
    2. flags(第二个参数)为0,调用AfdFreeTpInfo释放TpInfo,否则调用ExFreeToNPagedLookasideList。
    好了,现在还剩最后一个函数,IOControl=0x000120c3对应的AfdTransmitPackets函数:
    28.png
    流程:
    1. 对IRP和IoStackLocation做有效性检查。
    2. (IoStackLocation->InputBufferLength)>0x10。
    3. InputBuffer[0]!=0,InputBuffer[1]<=0x0AAAAAAA。这点解释了POC中inbuf2的值。
    4. 调用AfdTliGetTpInfo()。

    此漏洞被2014年黑客奥斯卡评为最佳提权漏洞,因为其从Windows上的内核级漏洞绕过Windows 8.1上的IE11沙箱。关于漏洞成因流程有两个比较有意思的地方:
    1. 两次使内核函数进入到异常处理流程。
    2. 两次从Lookaside得到的pool地址相同。

    参考
    http://www.siberas.de/papers/Pwn2Own_2014_AFD.sys_privilege_escalation.pdf


    我欲将心向明月,奈何明月照沟渠。
                      天人照我本和兴,只是难易风化岩。
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册