首页
社区
课程
招聘
[原创]CVE-2014-1767提权漏洞学习笔记
发表于: 2022-7-30 17:19 15227

[原创]CVE-2014-1767提权漏洞学习笔记

2022-7-30 17:19
15227

afd.sys驱动用于支持Win Socket应用程序。由于afd!AfdReturnTpInfo函数调用IoFreeMdl函数释放MDL内存的时候,没有及时将指针清空,导致再次调用的时候会再次释放相同的内存地址,导致双重释放的错误。通过WorkerFatory对象占有释放的内存,利用相关的函数可以实现一次任意地址写入,实现修改关键函数来实现提权。

操作系统:Win7 x86 sp1 专业版

编译器:Visual Studio 2017

调试器:IDA Pro,WinDbg

以下是该漏洞的POC代码:

触发漏洞的关键就是两次调用DeviceIoControl函数来实现用户层和内核层的I/O通信,函数定义如下,其中第二个参数为控制码,第三个参数为传入内核从的输入数据,这两个参数也是触发此次漏洞的关键。

对于每一个驱动,在内核中都会有一个对应的DRIVER_OBJECT对象,该结构体定义如下:

其中最后一个成员MajorFunction数组保存了不同的派遣函数用于处理用户层的通信要求,其中与调用DeviceIoControl进行通信对应的派遣函数保存于MajorFunction数组的0xE下标中。

从afd.sys的DriverEntry函数中可以看到驱动创建了"\\Device\\Afd"设备对象:

将MajorFunction数组下标0xE的派遣函数赋值为AfdDispatchDeviceControl函数,所以POC调用DeviceIoControl进行和内核进行通信的时候,就会调用该函数。

用户层和内核层之间的信息传递通过IRP结构体实现,该结构体定义如下:

偏移0x60指向_IO_STACK_LOCATION结构体,该结构体定义如下:

其中偏移0x8的InputBufferLength为调用DeviceIoControl函数时指定的输入数据的长度,偏移0xC为指定的dwIoControlCode,偏移0x10保存的是输入数据的指针。AfdDispatchDeviceIoControl函数的实现如下,可以很容易看出,该函数通过IoControlCode来计算一个下标,从AfdIoctlTable中取出该下标对应的数值来和IoControlCode进行验证,通过验证后,从AfdIrpCallDispatch数值中取出要执行的函数,在调用取出的函数。

AfdIoctlTable保存了不同的IoControlCode,其中可以发现POC中使用的两个控制码:

AfdIrpCallDispatch中保存了不同的执行函数,根据POC使用的控制码对应在AfdIoctlTable中的下标可以知道两次通信会分别调用AfdTransmitFile和AfdTransmitPackets函数:

这两个函数的定义如下:

根据定义可以知道,这两个函数的调用约定是__fastcall,且都有两个参数,所以上面IDA反编译的结果在调用函数部分是有错误的,这里可以直接通过看汇编代码来看相应的参数传递,这里需要记一下,IoControlCode保存在edi中,要调用的函数保存在esi中,这个后面动态调试要用。


要触发漏洞,AfdTransmitFile需要绕过三处验证,第一处和调用DeviceIoControl时第一个参数,即要通信的设备有关,当它为Socket套接字就可以绕过,第二处和第三处就是和输入数据和输入长度有关:

通过验证以后就会调用AfdTliGetIpInfo来申请tpInfo:

tpInfo通过ExAllocateFromNPagedLookasideList来申请内存,其中偏移0x20处保存了通过ExAllocatePoolWithQuotaTag申请的tpInfoElement类型的数组,这里可以看出每个成员大小为0x18,参数指定了数组元素个数,这里为3:

ExAllocateFromNPagedLookasideList的实现如下,可以看出是通过链表的方式实现,所以申请的tpInfo很有可能是上一次释放时候所指的内存:

AfdTransmitFile申请完tpInfo内存之后,会从输入数据中取出地址和长度,然后调用IoAllocateMdl申请MDL内存,保存在tpInfoElement偏移0xC处,之后会调用函数MmProbeAndLockPages:

在POC中,由输入数据指定的地址和长度是不合法的,这就会导致MnProbeAndLockPages调用出现错误,从而导致AfdReturnTpInfo函数的调用:


AfdReturnTpInfo函数会调用IoFreeMdl来释放保存在tpInfoElement偏移0xC处的MDL内存。然而这里释放之后没有及时清空指针,而tpInfo在释放之后会挂回原来的链表中,此时如果可以再次申请tpInfo就会申请到同一块内存,其中保存的数据是原来的数据,如果有办法再次申请tpInfo,且对其进行初始化之前就再次进入AfdReturnTpInfo就可以释放相同的MDL内存造成双重释放:

POC是通过AfdTransmitPackets来造成第二次释放,函数前面的部分和之前差不多,也是进行几个验证:

验证通过就会使用输入数据偏移0x4处保存的数值作为参数调用AfdTliGetTpInfp:

在POC中第二次调用时候这个参数被指定为0x0AAAAAAA,因此,当AfdTliGetTpInfo申请数组的时候,会因为需要申请的内存过大(0x18 * 0x0AAAAAAA)导致错误,调用AfdReturnToInfo。而此时的tpInfo已经从链表中成功取出上一次挂入链表的内存,且相关成员没有被初始化,这个时候在AfdReturnTpInfo中释放MDL内存的时候就会释放同一块内存。

以下是在WinDbg中,afd.sys驱动的信息,其中相关的信息和前面静态分析的结果一样:

接下来在AfdDispatchDeviceControl中的call esi处下条件断点来查看第一次通信的执行情况,编译运行POC就可以看到,此时esi保存是AfdTransmitFile函数地址,在AfdTransmit中调用IoAllocateMdl来申请MDL内存处下断点,可以看到此时要申请的内存为输入数据偏移0x18指定的地址:

继续向下运行会因为对不合法地址MnProbeAndLockPages而产生异常,进而执行IoFreeMdl,此时释放的MDL内存地址为0x87C55150:

继续上面步骤,可以看到第二次通信要执行的是AfdTransmitPackets函数,当执行ExAllocatePoolWithQuotaTag的时候,因为要申请的内存过大导致异常产生,进而执行IoFreeMdl来释放内存,而此时要释放的内存地址还是0x87C55150这个在上面刚被释放的内存,这就会导致双重释放产生BSOD错误:

继续运行系统就会出现蓝屏,以下是部分错误信息:

以下是要释放的内存情况,可以看到要释放的这个内存处于Free状态:

要利用该漏洞,需要用到WokerFactory对象,该对象可以通过以下函数创建,WorkerFactory占0x78大小的内存,在加上0x8大小的_POOL_HEADER和对象头,共会占有0xA0字节:

对于WorkerFactory对象,可以调用NtSetInformationWorkerFactory,该函数定义如下:

该函数从第三个参数中获取要写入的值,调用ObReferenceObjectByHandle获取传入的WorkerFactoryHandle句柄对应的WorkerFactory对象,并执行最后的写入操作:

成功利用该漏洞还需要NtQueryEaFile函数,该函数定义如下:


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-8-6 10:39 被1900编辑 ,原因:
收藏
免费 3
支持
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/09/27 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//