-
-
[原创] 学习笔记:CVE-2014-1767漏洞利用思路与我的理解
-
发表于: 9小时前 122
-
前言
CVE-2014-1767:Windows AFD.sys 双重释放漏洞,本地提权
写这篇博客之前我一直在想:因为我自己也是看别人的复现文章入手的,如果单纯再复述一遍漏洞,绝对是没有Hacksign大神的CVE-2014-1767 分析报告这篇文章好的。虽然我从零手写了 PoC 和 Exp,但还是感觉缺少一个独特的点。
于是,这篇文章我会着重分享自己对这个漏洞的思考,并尝试提炼出可举一反三的漏洞利用方法。
让我们开始吧。
漏洞成因(PoC分析)
漏洞调用链如下:
DeviceIoControl(0x1207F) -> AfdTransmitFile -> MmProbeAndLockPages(error) -> AfdReturnTpinfo -> IoFreeMdl(First Time)
-------------------------------------------------------------------
DeviceIoControl(0x120C3) -> AfdTransmitPackets -> AfdTliGetTpInfo -> ExAllocatePoolwithQutaTag(error) -> AfdReturnTpinfo -> IoFreeMdl(Second Time)
AfdReturnTpinfo函数主要错误:释放Mdl后,没有将指针清零。 
网上的绝大部分漏洞成因讲的都是Double Free漏洞,意思是同一块内核池内存被释放了两次,指针没有置零,触发蓝屏了
我的思考
不知道读者看完简单的漏洞介绍后,会不会冒出这样一个想法:既然是双重释放,那我们直接调用两次 DeviceIoControl(0x1207F),或者把两次释放的顺序调换一下,不就行了吗?这样“应该”也能触发吧?
出于这个疑问,我写 PoC 的时候特地尝试了好几次:调换顺序、使用相同的 DeviceIoControl 参数。很遗憾,上面这些想法都行不通。为什么呢?我们先来看 AfdTransmitFile 函数。

这里的v34是每一次使用afd维护的变量,IoFreeMdl也是通过这个变量找Mdl指针的。
可以看到,AfdTransmitFile 函数先申请了一个Mdl,初始化了全局变量,然后再发生错误
再来看AfdTliGetTpInfo 函数:

发生错误的地方主要是我们提前消耗了内存,导致ExAllocatePoolWithQuotaTag函数失败,整个过程没有涉及Mdl
也就是说,我们真正的(或者说更底层一点的)漏洞是,我们调用了两次AfdReturnTpinfo,加上没有把Mdl指针清零。就会重复释放两次同一块池内存,导致蓝屏。
所以,完整的漏洞触发流程应该是这样的
- 调用
DeviceIoControl(0x1207F)初始化本次调用的Mdl MmProbeAndLockPages发生错误,导致释放了刚刚申请的Mdl漏洞,并留下了一个悬挂指针- 调用
DeviceIoControl(0x120C3)使用AfdTliGetTpInfo函数。 ExAllocatePoolwithQutaTag发生错误,导致又一次释放了悬挂的Mdl地址。内核检测到后发生错误
这就解释了为什么只能按照这个顺序才能蓝屏。
- 调用两次
DeviceIoControl(0x1207F):因为AfdTransmitFile函数释放时会申请一个新的Mdl,所以不会蓝屏。 - 调用两次
DeviceIoControl(0x120C3):因为AfdTransmitPackets函数没有使用Mdl,Mdl指针是NULL,所以不会蓝屏。 - 先调用
DeviceIoControl(0x120C3),后调用DeviceIoControl(0x1207F)。第一个函数没有申请Mdl,第二个函数正常释放了,只释放了一次。
漏洞利用(Exp分析)
因为这个漏洞利用已经被大神们讲的很清楚了,我会简单讲讲利用链,在利用中分析我的思考
我的思考
首先,往广了想,我们应该怎么利用Double Free漏洞?
Double Free漏洞的利用
Double Free的含义
有Double Free漏洞,说明ExFreePool的时候,没有一个合理的检查。我们可以利用Double Free漏洞实现一次超出常理的释放。
利用思路
一次超出常理的释放能干什么?
我们可以将一块池内存(这里可能是对象、可能是内核结构体,可能是驱动私有数据)在恰到好处的时机释放,制造悬挂指针,转化为UAF漏洞。
什么是恰到好处的时机,很简单,别人内核对象正在用呢,你给人家释放了,那你这不是闹嘛
具体到这个漏洞
我们要控制内核,就需要在用户态申请一些对象,间接控制内核对象。
这个漏洞,我们选择了WorkFactory对象,我们通过NtSetInformationWorkerFactory和NtQueryInformationWorkerFactory就可以很方便地实现控制内核数据。
刚才上面提到,我们有一次超出常理的释放。所以,我们要把申请WorkFactory对象放在Double Free的第一次Free之后,把刚刚释放的内存申请出来,这样,我们就对这个池内存有一次在用户态违规释放的机会。
Exp代码片段
// Alloc Pool To Make Dangling Pointer
DeviceIoControl(s, 0x1207F, (LPVOID)inbuf1, 0x40, NULL, 0, NULL, NULL); //AfdTransmitFile
hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 4);
st = NtCreateWorkerFactory(
&hWorkerFactory,
GENERIC_ALL,
NULL,
hCompletionPort,
(HANDLE)-1,
NULL,
NULL,
0,
0,
0
);
if (st < 0) {
printf("[-] NtCreateWorkerFactory Failed\n");
return;
}
总结:从Double Free Mdl转化到拥有一次违规释放行为的WorkFactory对象.
如果我们在用户态释放WorkFactory对象。那么,我们就制造了一个悬挂指针,转化为了UAF漏洞!
我的思考
UAF漏洞的利用
利用思路
Free池内存后,这块内存可以被别人使用。可以让两个对象同时使用这块内存。出现未定义行为,使我们可以彻底控制这块内存、这个对象
具体到这个漏洞
我们申请出来的这个WorkFactory对象,最好是直接控制对象的所有数据。
正好,Windows有个函数就可以做到这件事,NtQueryEaFile函数,也会申请内存,会把申请出来的内存拷贝用户态传递过来的数据。
// Occur WorkerFactory
NtQueryEaFile(INVALID_HANDLE_VALUE, &IoStatus, NULL, 0, FALSE, FakeWorkerFactory, FakeObjSize, NULL, FALSE);
这里FakeWorkerFactory是我们构造的WorkerFactory对象的池块数据。
注:Windows 7 内核对象放在非分页内存池块上的,第一部分是可选对象头,第二部分是对象头,第三部分是对象数据。可以通过!handle UserHandleVal 3 EPROCESS命令直接在Windbg用用户态句柄查看内核对象数据。
控制内核对象数据后,接下来的利用就很简单了。使用NtSetInformationWorkerFactory任意写,使用NtQueryInformationWorkerFactory任意读。
先读HalDispatchTable+8的值,再将HalDispatchTable+8写为shellcode地址,执行NtQueryIntervalProfile实则执行Shellcode,最后在Shellcode恢复现场。这个常见的利用方法我这里就不讲了~
总结
以前学 CTF 的 User Pwn 时,也碰到过不少 UAF。但那时候没怎么总结,加上 CTF 里的 UAF 通常比较“直接”——你free掉一块内存,马上就能自己申请回来,然后直接往里写 payload,简单粗暴。
到了内核态就不一样了:
- 你不能直接分配一个“恶意对象”往里写 shellcode
- 你得通过用户态 API 创建合法对象(比如 Mdl、WorkerFactory、EaFile)
- 利用这些对象自身的内部结构、函数指针,来间接控制执行流
绕了一个弯子。
参考资料:
[原创]【EXP 编写与分析系列四】Windows本地提权漏洞CVE-2014-1767分析及EXP编写指导
我自己从零编写了一个PoC和Exp,上传到Github了d7dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6m8e0K6l9K6x3g2)9J5c8V1E0W2M7X3&6W2L8q4)9J5k6q4k6#2L8r3&6W2M7X3q4T1K9h3I4A6N6s2W2Q4x3X3c8d9k6i4m8J5L8$3c8#2j5%4c8A6L8$3^5`.,同时我也放在附件了~
内核浩瀚,漏洞如星。愿我们都能在这片星空下,找到属于自己的那颗。