0X00 漏洞描述
CVE-2014-1767是WindowsAFD.sys双重释放漏洞进行内核提权 该漏洞获得2014年“最佳提权漏洞奖” 影响蛮大的 接下来分析下该漏洞
0X01 分析环境
目标系统:Windows 7 32位
调试器:WinDbg
反汇编器:IDA Pro
poc代码如下:
1 | #include<windows.h> |
0X02 漏洞原理
该漏洞是由于Windows的afd.sys驱动在对系统内存的管理操作中 存在着悬垂指针的问题 在特定情况下攻击者可以通过该悬垂指针造成内存的Double free漏洞
0X03 漏洞分析
打开poc.exe(我是cpp1.exe) WinDbg断下来!analyze -v分析崩溃信息
查看栈回溯 我们可以看出是afd.sys导致重复释放一块内存 导致双重释放才引起的崩溃
查看afd模块的详细信息
在程序中两次调用DeviceIoControl 分别向IO控制码0x1207F和0x120C3发送数据 我们可以从这两个控制码的分发函数入手
控制码0x1207F
首先需要对nt!NtDeviceIoControlFile设置条件断点 当其在处理IO控制码0x1207F时断下 查看NtDeviceIoControlFile结构
1 | __kernel_entry NTSYSCALLAPI NTSTATUS NtDeviceIoControlFile( |
位于第6个参数 即esp+18 因此条件断点设置如下
1 | bp nt!NtDeviceIoControlFile ".if (poi(esp+18) = 0x1207F){}.else{gc;}" |
重新运行后加载断下
执行wt命令行追踪后续函数就停不下来了。。。。。。。。。 我们通过观察可以看到当IOCTL为0x1207F时 afd驱动中的AfdTransmitFile函数会被调用 用IDA查看AfdTransmitFile函数的伪代码 将a1 a2改为pIRP与pIoStackLocation 其实后面看到别的博客上有一种更好理解的办法 就是对afd!AfdDispatchDeviceControl函数下断点 该函数会根据控制码调用函数 所以我们运行到根据控制码调用函数的位置 然后跟进该函数即可知道调用的函数
Lookaside List内存分配机制:https://blog.csdn.net/wu330/article/details/25894841
要调用的AfdTliGetTpInfo函数中就使用到了Lookaside List内存分配机制 在AfdTliGetTpInfo函数上下断点 然后继续跟踪下去 用IDA查看AfdTliGetTpInfo函数
跟进函数 执行到AfdAllocateTpInfo函数 查看函数调用的参数 看到分配的TpInfo结构大小为0x108
可以看出 ExAllocateFronmNPagedLookasideList函数是分配TpInfo结构内存的 AfdTliGetTpInfo函数最终返回设置成TpInfo结构指针
继续分析AfdTransmitFile函数接下来的代码
将函数断在执行MnProbeAndLockPages函数 然后查看内存的内容
触发异常后 程序回去调用AfdReturnTpInfo函数后 确定会触发异常直接断下来
在AfdReturnTpinfo函数 由于在释放MDL资源后 未对TpinfoElement+0xc指针清空 导致后面再次调用时将被IoFreeMdl函数用于释放内存 导致双重释放漏洞
控制码0x120C3
和前面一样 这次对afd!AfdTransmitPackets函数下条件断点
1 | bp nt!NtDeviceIoControlFile ".if (poi(esp+18) = 0x120C3){}.else{gc;}" |
函数断下
这次为了便于分析 依然将IDA中的a1和a2参数分别重命名为pIRP与pIoStackLocation
关于AfdTliGetTpInfo函数 前面已经分析过了 Poc第二次设置的buf2为0xAAAAAAAA个TpInfoElement 而每个占0x18个字节 因此需要申请内存0x18*0xAAAAAAAA=0xFFFFFFF0 所以会申请内存失败 在AfdTliGetTpInfo函数和AfdReturnTpInfo上下断点 然后单步跟踪下去
再看看AfdReturnTpInfo函数 可以看出释放函数
刚才我们已经进入过一次这个函数了 这里进了第二次 导致双重释放
单步执行释放将导致蓝屏
0X04 漏洞利用
思路
1.调用DeviceIoControl IoControlCode=0x1207F 造成MDL free
2.创建某个对象 使得这个对象恰好占据刚才被free掉的空间 转变为UAF问题
3.调用DeviceIoControl IoControlCode=0x120C3 重复释放流程 释放刚才新申请的对象
4.覆盖被释放的对象为可控制数据
5.尝试调用能够操作此对象的函数 让函数通过操作我们刚刚覆盖的可控数据 实现一个内核写操作 ”任意地址写任意内容“ 这样我们就可以覆写HalDispatchTable的某个单元为我们的ShellCode的地址 这样可以劫持一个内核函数调用
6.用户层触发刚刚被Hook的HalDispatchTable函数 使得内核执行shellcode 达到提权效果
选择对象
Siberas团队使用WorkerFactory对象 因为它刚好存在NtSetInformationWorkerFactory操作函数能够帮助实现任意内容写任意地址 反汇编下列的exe
我们加载该exe 搜索反汇编NtSetInformationWorkerFactory函数 找到关键位置
所以当满足arg2 == 8 && *arg3 !=0)时 可以达到任意地址写任意数据的目的 我们可以设置
1 | arg3 = ShellCode |
这样就可以将shellcode地址写入HaliQuerySystemInformation 供后续的shellcode执行 我们知道MDL属于NonPagedPool 用户空间无法分配 看看NtQueryEaFile函数是如何将ShellCode复制到内核空间地址的 函数原型如下
1 | NTSYSAPI |
查看NtQueryEaFile函数 调用ExAllocatePoolWithQuotaTag函数 会使长度值再次加4 即esi的值会再次加4 所以ExAllocatePoolWithQuoTag分配的长度是EaLength+4 在对释放对象内存进行占用时 应该将对象大小objectsize-4 才可以占用成功
再回到NtQueryEaFile函数 调用完之后会调用memcpy函数为内核地址复制数据
确定WorkerFactory大小
根据NtCreateWorkerFactory->ObpCreateObject->ObpAllocateObject-> ExAllocatePoolWithTag确定申请内存的大小为0xA0
编写exp
完整exp:
1 | #include <windows.h> |
利用成功
0X05 补丁分析
在补丁文件中 因为只有TpInfoElementCount>0 才会调用到IoFreeMdl函数 因此在第一次调用完IoFreeMdl后 会将TpInfoElementCount清零 使得他不会再次调用IoFreeMdl 从而避免Double Free
0X06 经验总结
这次内核漏洞分析 涉及的知识挺多的 好的一点是这次的复现过程很顺利 大致知道过程 漏洞的分析过程基本知道流程走向 漏洞的利用思路从分析过程得出 对exp编写前的准备工作还是很多的 进行起来还是挺困难的 我还是入门的初学者 希望以后自己可以发现漏洞写出exp
参考资料:
TJ师傅写的:https://blog.csdn.net/CharlesGodX/article/details/87638788
会飞的猫:https://www.cnblogs.com/flycat-2016/p/5450275.html