Remote Desktop UAF漏洞蓝屏,SQL Server 报偏移量为???的位置执行 读取 期间,操作系统已经向 SQL Server 返回了错误 21

这是一台云服务器,非物理主机,蓝屏转储 memory.dmp, 不及格的程序员-八神, 每次SqlServer数据库不可用时,在相近时间点上能看到系统蓝屏的系统日志,猜测与蓝屏重启有关。

C:\ProgramData\Microsoft\Windows\WER\ReportArchive\

https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2019-0708

3: kd> !analyze -v termdd
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: 0000000000000000, memory referenced
Arg2: 0000000000000002, IRQL
Arg3: 0000000000000000, value 0 = read operation, 1 = write operation
Arg4: fffff88002e67006, address which referenced memory
STACK_TEXT:  
fffff880`088c9a88 fffff800`016bcf69     : 00000000`0000000a 00000000`00000000 00000000`00000002 00000000`00000000 : nt!KeBugCheckEx
fffff880`088c9a90 fffff800`016bad88     : 00000000`00000000 00000000`00000000 fffffa80`06cdc900 fffffa80`08abfd60 : nt!KiBugCheckDispatch+0x69
fffff880`088c9bd0 fffff880`02e67006     : fffffa80`08abfd60 fffffa80`08abfd60 fffffa80`08ada010 fffff880`088c9df0 : nt!KiPageFault+0x448
fffff880`088c9d60 fffff880`02e66e01     : fffffa80`08a74940 fffffa80`08046100 fffff8a0`003ab000 fffff880`088c9fa0 : termdd!IcaChannelInputInternal+0x1f2
fffff880`088c9e40 fffff880`0466f198     : fffff8a0`035de010 fffff880`02e6bd6c fffffa80`076f1740 fffffa80`0938b730 : termdd!IcaChannelInput+0xdd
fffff880`088c9e80 fffff880`0464c708     : 00000000`00000000 fffff800`00000001 00000000`00000000 00000000`00000000 : RDPWD!SignalBrokenConnection+0x54
fffff880`088c9ee0 fffff880`02e66d8f     : fffffa80`08a74940 fffffa80`076f1a20 fffffa80`076f1740 00000000`c000013c : RDPWD!WDLIB_MCSIcaChannelInput+0x90
fffff880`088c9f30 fffff880`046306a4     : fffffa80`076f1a20 fffffa80`076f1a20 fffffa80`076f1740 fffffa80`076f1a20 : termdd!IcaChannelInput+0x6b
fffff880`088c9f70 fffff880`02e6af3e     : fffffa80`07430720 fffffa80`0933bf20 fffffa80`08c8f010 fffffa80`08368750 : tdtcp!TdInputThread+0x64c
fffff880`088ca7f0 fffff880`02e69ae3     : fffffa80`08552cb0 fffffa80`08368750 fffffa80`07189d80 fffffa80`08c8f010 : termdd!IcaDriverThread+0x5a
fffff880`088ca820 fffff880`02e689e9     : fffffa80`088dd420 fffff880`088ca958 fffff880`088ca960 00000000`00000000 : termdd!IcaDeviceControlStack+0x827
fffff880`088ca900 fffff880`02e68689     : 00000000`00000000 fffffa80`08368750 00000000`00000000 fffffa80`088dd538 : termdd!IcaDeviceControl+0x75
fffff880`088ca950 fffff800`0190dd9a     : 00000000`00000002 00000000`00000002 00000000`00000000 fffffa80`08c80660 : termdd!IcaDispatch+0x215
fffff880`088ca990 fffff800`01ad3831     : fffffa80`08c80660 fffffa80`08c80660 fffffa80`08c80660 fffff880`01efa180 : nt!IopSynchronousServiceTail+0xfa
fffff880`088caa00 fffff800`019655d6     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0xc51
fffff880`088cab40 fffff800`016bcbd3     : 00000000`80000001 fffff800`01ab3b46 00000000`00000000 00000000`00000000 : nt!NtDeviceIoControlFile+0x56
fffff880`088cabb0 00000000`77a898fa     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
00000000`01eff818 00000000`00000000     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x77a898fa

SYMBOL_NAME:  termdd!IcaChannelInputInternal+1f2

MODULE_NAME: termdd

IMAGE_NAME:  termdd.sys

STACK_COMMAND:  .cxr; .ecxr ; kb

FAILURE_BUCKET_ID:  0xD1_termdd!IcaChannelInputInternal+1f2

OS_VERSION:  7.1.7601.24384

BUILDLAB_STR:  win7sp1_ldr_escrow

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 7

 系统日志与应用日志报告:不及格的程序员-八神

 

 

 

 

 

 

 

 

 

 


 之前也出现的,3-15日

 

 

 

 

 

 

 

 


 

参考:

POC已公开!RDP远程代码执行漏洞被利用引发蓝屏-ip功击蓝屏

作者:上犹日期:2020-04-07 12:58:36

返回目录:电脑蓝屏

2019年5月31日,深信服安全团队发现公开的CVE-2019-0708的POC已在github上流传,并第一时间进行复现以及分析。经测试,该POC可导致目标主机蓝屏崩溃,特此再次发出预警。

漏洞名称:Remote Desktop Protocol漏洞

威胁等级:严重

影响范围:Windows XP、Windows 7 、Windows Server 2003、Windows Server 2008、Windows Server 2008 R2

漏洞类型:任意代码执行漏洞

利用难度:容易

目前漏洞攻击方式已经公开,后续极有可能发生利用漏洞的攻击事件。深信服提醒广大用户,务必及时更新安全补丁!

漏洞复现

根据该POC的发布者称,该POC针对Windows Server 2008 R2 x64。深信服安全团队第一时间进行了复现,证实该POC确实可导致目标主机的蓝屏崩溃并重启。

测试结果表明,除此版本系统外,该POC还会影响Windows 7 sp1 x64,Windows 7 sp1 x86,Windows Server 2008 x86。(其余尚未进行验证)

攻击者只需以受害主机IP地址为参数执行一个python文件,即可成功实施攻击,最终目标系统处于蓝屏崩溃状态。

漏洞分析

成功利用漏洞的前提是将名称为“MS_T120”的静态信道成功绑定到正常信道以外。由于微软官方在内部会使用一个名为MS_T120的信道,因此此信道理论上不应该接收任意消息。

然而,RDP的内部组件有很多,包括svchost.exe中的几个用户模式的dll,以及几个内核模式的驱动程序。攻击者在MS_T120信道上发送消息,这将导致termdd.sys驱动程序中触发一个双重释放漏洞(free-after-free)。具体分析如下:

客户端在完成RDP协议的握手过程后,将开始向已经绑定的各个信道中发送消息。MS_T120信道的消息是由用户模式组件——rdpwsx.dll进行管理,该dll创建一个线程,该线程则将在函数rdpwsx!IoThreadFunc中进行循环,通过I/O端口读取消息。

数据通过I/O数据包传入后,将进入rdpwsx!MCSPortData 函数进行处理:

从函数中可以看到,在rdpwsx!MCSPortData 函数中,有两个opcode:0x0和0x2。如果opcode为0x2,则调用rdpwsx!HandleDisconnectProviderIndication函数执行清理动作,然后使用rdpwsx!MCSChannelClose关闭通道。

从理论上来说,发送的数据包大小都在合法范围的MS_T120消息,将会正常关闭MS_T120信道,并断开连接程序(此时opcode为0x2),并且进行清理操作,此时将不会产生远程代码执行和蓝屏崩溃的风险。但如果发送的数据包大小无效,就会导致远程代码执行和拒绝服务。

影响范围

目前受影响的Windows版本:

Microsoft Windows XP

Microsoft Windows Server 2008 R2 for x64-based Systems SP1

Microsoft Windows Server 2008 R2 for Itanium-based Systems SP1

Microsoft Windows Server 2008 for x64-based Systems SP2

Microsoft Windows Server 2008 for Itanium-based Systems SP2

Microsoft Windows Server 2008 for 32-bit Systems SP2

Microsoft Windows Server 2003

Microsoft Windows 7 for x64-based Systems SP1

Microsoft Windows 7 for 32-bit Systems SP1

解决方案

1.漏洞检测

深信服云眼在漏洞爆发之初,已完成检测更新,对所有用户网站探测,保障用户安全。不清楚自身业务是否存在漏洞的用户,可注册信服云眼账号,获取30天免费体验。注册地址为:

https://saas.sangfor.com.cn

2.修复建议

(1)及时安装微软发布的安全更新补丁:

Microsoft官方已经在 2019年5月14日修复了该漏洞,用户可以通过安装微软的安全更新来给系统打上安全补丁,下载地址为:

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0708

同时针对已不受微软更新支持的系统Windows Server 2003和Windows XP提供的安全更新,下载地址:

https://support.microsoft.com/zh-cn/help/4500705/customer-guidance-for-cve-2019-0708

(2)缓解措施(在无法及时安装微软安全更新的情况下作为临时性解决方案):

若用户不需要用到远程桌面服务,建议禁用该服务。

开启网络级别身份验证(NLA),此方案适用于Windows 7、 Windows Server 2008、Windows Server 2008 R2。

以上缓解措施只能暂时性针对该漏洞对系统进行部分缓解,强烈建议在条件允许的情况下及时安装微软安全更新。

参考链接

[1].https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0708

[2].https://securingtomorrow.mcafee.com/other-blogs/mcafee-labs/rdp-stands-for-really-do-patch-understanding-the-wormable-rdp-vulnerability-cve-2019-0708/

[3].https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/5073f4ed-1e93-45e1-b039-6e30c385867c

[4].https://zerosum0x0.blogspot.com/2019/05/avoiding-dos-how-bluekeep-scanners-work.html


 

CVE-2019-0708(BlueKeep): 利用RDP PDU将数据写入内核的3种方式

阅读量433997

|评论3

|

发布时间 : 2019-09-02 11:00:23

x
译文声明

本文是翻译文章,文章原作者 paloaltonetworks,文章来源:unit42.paloaltonetworks.com

原文地址:https://unit42.paloaltonetworks.com/exploitation-of-windows-cve-2019-0708-bluekeep-three-ways-to-write-data-into-the-kernel-with-rdp-pdu/

译文仅供参考,具体内容表达以及含义原文为准。

 

0x00 前言

2019年5月,微软针对远程代码执行(RCE)漏洞CVE-2019-0708专门发布了带外补丁更新包,这个漏洞也就是知名的“BlueKeep”漏洞,存在于远程桌面服务(RDS)种。这是一个预身份认证漏洞,无需用户交互,因此很有可能被攻击者利用,带来破坏性风险。如果成功利用漏洞,攻击者就能以SYSTEM权限执行任意代码。根据微软安全响应中心(MSRC)发布的公告,这是一种蠕虫级漏洞,可能被攻击者用来发起类似Wannacry及EsteemAudit级别的攻击。了解到这个漏洞的严重性及可能对公众造成的潜在风险后,微软也采取了罕见的处理流程,为不再受支持的Windows XP系统推出补丁,以全面保护Windows用户。

由于该漏洞可能会带来全球范围内的灾难性后果,Palo Alto Networks Unit 42研究人员认为该漏洞非常值得研究,以便澄清RDS的内部工作原理及漏洞利用方式。我们深入研究了RDP内部实现以及如何利用这些内部工作流程在未打补丁的主机上实现代码执行。本文讨论了如何利用Bitmap Cache PDU(协议数据单元)、Refresh Rect PDU以及RDPDR Client Name Request PDU将数据写入内核内存中。

自微软在5月份发布补丁以来,该漏洞得到了计算机安全行业的广泛关注。事实上漏洞利用工具的公开并在实际攻击中使用只是一个时间问题,根据我们的研究成果,大家可以了解到存在漏洞的系统实际上会面临极大的风险。

 

0x01 Bitmap Cache PDU

根据MS-RDPBCGR(Remote Desktop Protocol: Basic Connectivity and Graphics Remoting)文档描述,Bitmap Cache PDU的全称为TS_BITMAPCACHE_PERSISTENT_LIST_PDU,这是一种Persistent Key List PDU Data,内嵌在Persistent Key List PDU中。Persistent Key List PDU是一种RDP Connection Sequence(连接时序)PDU,在RDP Connection Sequence的Connection Finalization(连接完成)阶段由客户端发送至服务端,如图1所示。

图1. RDP连接时序

Persistent Key List PDU头部为通用的RDP PDU头,具体格式如图2所示:tpktHeader(4字节)+x224Data(3字节)+mcsSDrq(可变字节)+securityHeader(可变字节)。

图2. Client Persistent Key List PDU

根据MS-RDPBCGR文档,TS_BITMAPCACHE_PERSISTENT_LIST_PDU结构中包含由之前会话中发送的一系列已缓存的bitmap key(位图键值),这些key与Cache Bitmap(Revision 2)Order(缓存位图序)相对应,如图3所示:

图3. Persistent Key List PDU Data(BITMAPCACHE PERSISTENT LIST PDU)

在官方设计方案中,RDP客户端可以使用Bitmap Cache PDU向服务端发送信息,表明客户端本地包含与这些key对应的位图拷贝,这意味着服务端不需要重新将位图发送给客户端。根据MS-RDPBCGR文档描述,Bitmap PDU有如下4个特点:

  • RDP服务端会分配一个内核池,用来存储已缓存的bitmap key;
  • RDP服务端分配的内核池大小由RDP客户端发送过来的BITMAPCACHE PERSISTENT LIST结构中的numEntriesCacheXX取0到4之间的值)以及totalEntriesCacheXX取0到4之间的值)字段所控制,这两个字段大小均为2字节(WORD);
  • Bitmap Cache PDU可以被多次发送,因为bitmap key可以通过多个Persistent Key List PDU发送,每个PDU通过bBitMask字段中flag的来标记;
  • bitmap key的数量最多为169个。

根据BITMAPCACHE PERSISTENT LIST PDU的这4个特点,如果我们能绕过bitmap key数量限制(169个),或者微软在实现RDP时没有遵循这个限制值,那么就有可能将任意数据写入内核中。

 

0x02 如何通过Bitmap Cache PDU将数据写入内核

根据MS-RDPBCGR文档,正常加密的BITMAPCACHE PERSISTENT LIST PDU如下所示:

f2 00 -> TS_SHARECONTROLHEADER::totalLength = 0x00f2 = 242 bytes
17 00 -> TS_SHARECONTROLHEADER::pduType = 0x0017

0x0017

= 0x0010 | 0x0007

= TS_PROTOCOL_VERSION | PDUTYPE_DATAPDU

ef 03 -> TS_SHARECONTROLHEADER::pduSource = 0x03ef = 1007

ea 03 01 00 -> TS_SHAREDATAHEADER::shareID = 0x000103ea

00 -> TS_SHAREDATAHEADER::pad1

01 -> TS_SHAREDATAHEADER::streamId = STREAM_LOW (1)

00 00 -> TS_SHAREDATAHEADER::uncompressedLength = 0

2b -> TS_SHAREDATAHEADER::pduType2 =

PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST (43)

00 -> TS_SHAREDATAHEADER::generalCompressedType = 0

00 00 -> TS_SHAREDATAHEADER::generalCompressedLength = 0

00 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::numEntries[0] = 0

00 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::numEntries[1] = 0

19 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::numEntries[2] = 0x19 = 25

00 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::numEntries[3] = 0

00 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::numEntries[4] = 0

00 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::totalEntries[0] = 0

00 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::totalEntries[1] = 0

19 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::totalEntries[2] = 0x19 = 25

00 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::totalEntries[3] = 0

00 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::totalEntries[4] = 0

03 -> TS_BITMAPCACHE_PERSISTENT_LIST::bBitMask = 0x03

0x03

= 0x01 | 0x02

= PERSIST_FIRST_PDU | PERSIST_LAST_PDU

00 -> TS_BITMAPCACHE_PERSISTENT_LIST::Pad2

00 00 -> TS_BITMAPCACHE_PERSISTENT_LIST::Pad3

TS_BITMAPCACHE_PERSISTENT_LIST::entries:

a3 1e 51 16 -> Cache 2, Key 0, Low 32-bits (TS_BITMAPCACHE_PERSISTENT_LIST_ENTRY::Key1)

48 29 22 78 -> Cache 2, Key 0, High 32-bits (TS_BITMAPCACHE_PERSISTENT_LIST_ENTRY::Key2)

61 f7 89 9c -> Cache 2, Key 1, Low 32-bits (TS_BITMAPCACHE_PERSISTENT_LIST_ENTRY::Key1)

cd a9 66 a8 -> Cache 2, Key 1, High 32-bits (TS_BITMAPCACHE_PERSISTENT_LIST_ENTRY::Key2)

…

RDPWD.sys内核模块中,ShareClass::SBC_HandlePersistentCacheList函数负责解析BITMAPCACHE PERSISTENT LIST PDU。当结构体中的bBitMask字段值设置为0x01时,表示当前PDU为PERSIST FIRST PDU。随后SBC_HandlePersistentCacheList会调用WDLIBRT_MemAlloc来分配一个内核池(分配内核空间)来存储持久性位图缓存键值,如图4所示。其他情况下,0x00值表示当前PDU为PERSIST MIDDLE PDU,0x02值表示当前PDU为PERSIST LAST PDU。当解析PERSIST MIDDLE PDU以及PERSIST LAST PDU时,SBC_HandlePersistentCacheList会将bitmap cache key缓存到前面分配的内存空间中,如图5所示。

图4. SBC_HandlePersistentCacheList分配池空间以及检查totalEntriesCacheLimi

图5. SBC_HandlePersistentCacheList拷贝bitmap cache key

Windows 7 x86系统中调用栈情况如图6所示,以参数形式传递给SBC_HandlePersistentCacheList函数的TS_BITMAPCACHE_PERSISTENT_LIST结构体如图7所示。

图6. SBC_HandlePersistentCacheList栈轨迹

图7. 以SBC_HandlePersistentCacheList第二个参数形式传入的TS_BITMAPCACHE_PERSISTENT_LIST结构

如图4所示,bitmapCacheListPoolLen = 0xC * (total length + 4),而total length = totalEntriesCache0 + totalEntriesCache1 + totalEntriesCache2 + totalEntriesCache3 + totalEntriesCache4。根据这个公式,我们可以设置totalEntriesCacheX=0xffff,这样bitmapCacheListPoolLen就能取得最大值。然而如图8所示,系统会对每个totalEntriesCacheX检查totalEntriesCacheLimittotalEntriesCacheLimitX来自于TS_BITMAPCACHE_CAPABILITYSET_REV2结构体,当RDPWD调用DCS_Init时,CAPAPI_LOAD_TS_BITMAPCACHE_CAPABILITYSET_REV2函数就会初始化这个结构体。当解析Confirm Active PDU时,CAPAPI_COMBINE_TS_BITMAPCACHE_CAPABILITYSET_REV2函数就会将这些值组合起来,如图9所示。

图8. RDPWD!CAPAPI_LOAD_TS_BITMAPCACHE_CAPABILITYSET_REV2

图9. RDPWD!CAPAPI_COMBINE_TS_BITMAPCACHE_CAPABILITYSET_REV2

CAPAPI_COMBINE_TS_BITMAPCACHE_CAPABILITYSET_REV2会将服务端初始化的NumCellCaches0x03)及totalEntriesCacheLimit[0-4]0x2580x2580x100000x00x0,如图9中edx寄存器所示)与客户端请求中的NumCellCaches0x03)及totalEntriesCache[0-4]0x800002580x800002580x8000fffc0x00x0,如图9中esi寄存器所示)组合起来。客户端可以控制NumCellCachestotalEntriesCache[0-4],如图10所示,但不能超过服务端初始化的NumCellCaches0x03)及totalEntriesCacheLimit[0-4]0x2580x2580x100000x00x0),如图11随时。

图10. TS_BITMAPCACHE_CAPABILITYSET_REV2

图11. CAPAPI_COMBINE_TS_BITMAPCACHE_CAPABILITYSET_REV2函数

了解这些知识后,我们可以计算出bitmapCacheListPoolLen的最大值,最大值bitmapCacheListPoolLen = 0xC * (0x10000 + 0x258 + 0x258 + 4) = 0xc3870,理论上我们可以在内核池中控制大小为0x8 * (0x10000 + 0x258 + 0x258 + 4) = 0x825a0字节数据,如图12所示。

图12. 转储出的Persistent Key List PDU内存

然而,我们发现RDP客户端并没有按我们设想的方式来控制位图缓存列表池中的所有数据。每8字节可控数据之间都有一个4字节不可控数据(索引值),这对shellcode利用来说非常不友好。此外,0xc3870字节大小的内核池不能被多次分配,因为正常情况下Persistent Key List PDU只能发送1次。然而经过多次测试后,我们发现内核池会在同一个内存地址处进行分配。此外,在分配的位图缓存列表池之前始终会有系统分配的大小为0x2b522c(x86系统)或0x2b5240(x64系统)字节的内核池,这一点对堆布局而言非常重要(尤其是在x64系统中),如图13所示。

图13. 统计Persistent Key List PDU的布局特征

 

0x03 Refresh Rect PDU

根据MS-RDPBCGR文档,RDP客户端可以通过Refresh Rect PDU请求服务端重绘会话屏幕中的1个或多个矩形区域。这个结构体中包含通用PDU头以及refreshRectPduData(可变)字段,如图14所示。

图14. Refresh Rect PDU Data

numberOfAreas字段是一个8比特无符号整数,用来定义areasToRefresh字段中Inclusive Rectangle结构的数量。areaToRefresh是包含TS_RECTANGLE16结构的一个数组,如图15所示。

图15. Inclusive Rectangle(TS_RECTANGLE16

Refresh Rect PDU用来通知服务端一系列“Inclusive Rectangle”屏幕区域,以便服务端重绘会话屏幕区域中的一个或多个矩形区域。这个PDU基于默认打开的一个信道进行传输,信道ID为0x03ea(Server Channel ID)。当连接时序完成后,如图1所示,RDP服务端就可以接收/解析Refresh Rect PDU,并且这里最重要的一点在于Refresh Rect PDU可以多次发送。虽然TS_RECTANGLE16大小只有8字节,这意味着RDP客户端只能控制8字节数据,但这依然可以作为将数据写入内核的一个切入点。

 

0x04 如何通过Refresh Rect PDU将数据写入内核

正常解密后的Refresh Rect PDU如图16所示。

图16. 解密后的Refresh Rect PDU

RDPWD.sys内核模块中的WDW_InvalidateRect函数负责解析Refresh Rect PDU,如图17所示:

图17. RDPWD!WDW_InvalidateRect栈轨迹

如图18所示,WDW_InvalidateRect函数会解析Refresh Rect PDU数据流,从数据流中提取numberOfAreas字段值作为循环计数值。由于numberOfAreas属于1字节字段,最大值为0xFF,因此最大循环计数值也为0xFF。在循环中,WDW_InvalidateRect函数会分别提取TS_RECTANGLE16结构中的lefttoprightbottom字段值,将其放入栈上的一个结构中,然后作为WDICART_IcaChannelInput函数的第5个参数传入。这里需要提一句,WDICART_IcaChannelInput的第6个参数为常数值0x808,这个值对堆喷射来说非常有用。

图18. RDPWD!WDW_InvalidateRect函数

WDICART_IcaChannelInput最终会调用termdd.sys内核模块中的IcaChannelInputInternal函数。如图19所示,如果满足一系列判断条件,那么IcaChannelInputInternal函数就会调用ExAllocatePoolWithTag来分配大小为inputSize_6th_para + 0x20的内核池。因此,当RDPWD!WDW_InvalidateRect调用IcaChannelInputInternal函数时,inputSize_6th_para=0x808,并且内核池的大小为0x828

图19. termdd!IcaChannelInputInternal中的ExAllocatePoolWithTagmemcpy操作

如何内核池分配成功完成,系统就会调用memcpy,将input_buffer_2拷贝到新分配的内核池内存中。当调用方为RDPWD!WDW_InvalidateRect时,memcpy所使用的参数如图20所示:

图20. 在windbg中观察termdd!IcaChannelInputInternal对应的memcpy操作

有趣的是,memcpy函数的源地址来自于RDPWD!WDW_InvalidateRect栈上的stRect结构,并且只有前3个DWORD会在RDPWD!WDW_InvalidateRect中设置,如图21所示。栈上的其他内存处于未初始化状态,因此容易导致信息泄露。此外,使用大小为0x808的内存空间来存储12字节的数据也是非常适用于堆喷射场景。

图21. RDPWD!WDW_InvalidateRect stRect结构集

根据这些信息,当RDP客户端向服务端发送一个Refresh Rect PDU,且numberOfAreas字段值为0xFF时,RDP服务端就会调用termdd!IcaChannelInputInternal 0xFF次。每次termdd!IcaChannelInputInternal调用都会分配0x828大小的内核池内存空间,将客户端可控的8字节TS_RECTANGLE16拷贝到该内核池中。因此,numberOfAreas字段值为0xFF的1个Refresh Rect PDU就可以分配0xFF个大小为0x828字节的内核池。从理论上讲,如果RDP客户端发送0x200次Refresh Rect PDU,那么RDP服务端就会分配大约0x20000个大小为0x828的非分页内核池。考虑到0x828大小的内核池会按照0x1000进行对齐,因此会同时占据非常大范围的内核池,客户端可控的8字节数据会被复制到每个0x1000内核池中0x02c固定偏移处。如图22所示,我们可以通过Refresh Rect PDU在内核中构建非常稳定的池喷射。

图22. RDPWD!WDW_InvalidateRect喷射

在某些情况下,当termdd!_IcaQueueReadChannelRequest修改某个指针时(图23中的v14变量),判断条件为False,此时ExAllocatePoolWithTag以及memcpy并不会被调用,代码也不会进入_IcaCopyDataToUserBuffer分支,这样将导致池无法成功分配。然而,当多次发送Refresh Rect PDU时,尽管某些池无法成功分配,我们还是可以成功形成内核池喷射。

此外在某些情况下,当RDP服务端使用完有些内核池后,可能会释放掉这些内核池,但内核池的数据并不会被清除,这样我们喷射到内核中的数据依然可以在漏洞利用过程中使用。

图23. termdd!IcaChannelInputInternal中的IcaCopyDataToUserBuffer

 

0x05 RDPDR Client Name Request PDU

根据MS-RDPEFS文档描述,RDPDR Client Name Request PDU在“Remote Desktop Protocol: File System Virtual Channel Extension”(文件系统虚拟信道扩展)中指定,该扩展运行在名为RDPDR的静态虚拟信道(static virtual channel)中。MS-RDPEFS协议的目的是将访问流从服务端重定向到客户端文件系统。Client Name Request是客户端发往服务端的第二个PDU,如图24所示。

图24. File System Virtual Channel Extension协议初始化

客户端使用Client Name Request PDU将主机名发送给服务端,如图25所示。

图25. Client Name Request(DR_CORE_CLIENT_NAME_REQ

这里头部为4字节的RDPDR_HEADERComponent字段值为RDPDR_CTYP_COREPacketId字段值为PAKID_CORE_CLIENT_NAMEComputerNameLen(4字节)是一个32位无符号整数,用来指定ComputerName字段的字节数。ComputerName字段(可变)是一个长度可变的ASCII或Unicode字符数组,具体格式由UnicodeFlag字段所决定,这个字符串用来标识客户端的计算机名。

 

0x06 如何通过RDPDR Client Name Request PDU将数据写入内核

典型的RDPDR Client Name Request PDU数据如下图所示。正常情况Client Name Request PDU可以被多次发送,对于每个请求,RDP服务端会分配一个内核池来存储这些信息,最重要的是:RDP客户端可以完全控制PDU的内容和长度。这也是将数据写入内核内存的一个绝佳切入点。典型的RDPDR Client Name Request PDU如图26所示。

图26. Client Name Request内存布局

当RDP服务端收到一个RDPDR Client Name Request PDU时,就会调用termdd.sys内核模块中的IcaChannelInputInternal函数来调度(dispatch)信道数据,然后调用RDPDR模块来解析Client Name Request PDU的数据部分。这里的IcaChannelInputInternal函数在处理Client Name Request PDU的代码逻辑上与对Refresh Rect PDU的处理逻辑相同。该函数会调用ExAllocatePoolWithTag(使用TSic标签)来分配内核内存,然后使用memcpy将Client Name Request数据拷贝到新分配的内核内存中,如图27所示。

图27. Client Name Request

到目前为止,我们已经知道服务端拷贝的数据内容及长度可以被RDP客户端可以控制,并且Client Name Request PDU也可以多次发送。正是因为这种灵活性及友好性,我们可以使用Client Name Request PDU来构造针对已释放内核池的UAF(释放后重用)漏洞利用场景,也可以用来将shellcode写入内核池,甚至可以用来将客户端可控的数据连续喷射到内核内存中。

如图28所示,我们成功实现了稳定的池分配,通过RDPDR Client Name Request PDU将客户端可控的数据写入内核池中。

图28. 利用Client Name Request PDU实现稳定池分配

0x07 检测及缓解

CVE-2019-0708是针对RDP的一个严重漏洞,可以被未经身份认证的攻击者所使用。根据MSRC安全公告,Windows XP、Windows 2003、Windows 7以及Windows 2008都受该漏洞影响。大家应该尽快给自己的Windows系统打上补丁,以缓解该威胁。如果条件允许,用户应当禁用或者限制通过外部接口访问RDP资源。

 

0x08 总结

在本文中,我们介绍了通过RDP PDU将数据写入内核的3种方法:

  • Bitmap Cache PDU可以让RDP服务端在分配0x2b5200大小的内核池后,分配0xc3870大小的内核池,并写入客户端可控的数据,然而无法多次执行分配0xc3870大小的内核池操作。
  • Refresh Rect PDU可以用来喷射0x828大小的多个内核池,这些内核池以0x1000方式对齐,并将客户端可控的8字节写入0x828大小的每个内核池中。
  • RDPDR Client Name Request PDU可以用来喷射大小可控的内核池,并填充客户端可控的数据。

我们认为还有其他未公开的方式,可以让CVE-2019-0708的利用方式更加简单及稳定。用户应当采取措施,通过前面提到的缓解方法保护可能存在风险的系统。


 

在2019年5月的补丁周期中,Microsoft在其远程桌面服务(RDS)中发布了一个远程代码执行错误补丁。远程未经身份验证的攻击者可以通过将精心设计的RDP消息发送到目标服务器来利用此漏洞。成功利用可能会导致执行具有管理权限的任意代码。虽然我们对此漏洞的初步研究主要集中在缓解和保护上,但趋势科技安全研究团队的Pengsu Cheng,Kamlapati Choubey和Saran Neti致力于彻底分析漏洞。以下是趋势科技漏洞研究服务报告的摘录,内容涵盖CVE-2019-0708,并进行了一些最小的修改。

漏洞

Microsoft远程桌面服务(以前称为终端服务)允许用户远程打开交互式Windows会话。远程桌面服务提供与基于终端的环境类似的功能,其中多个终端(客户端)可以连接到单个主机(服务器)。远程用户可以登录到远程主机并访问主机上的数据,运行应用程序等。远程桌面连接默认使用远程桌面协议(RDP)通过端口3389 / TCP与远程服务器通信。

RDP指定多个会议参与者如何查看和协作共享程序。该协议是ITU-T T.128应用程序共享协议的Microsoft扩展。该协议利用T.120标准中较低层协议提供的其他服务,例如T.124通用会议控制(GCC),T.122多点通信服务(MCS)等。

RDP连接以连接序列消息开始,由远程桌面协议:基本连接和图形远程处理(MS-RDPBCGR)协议定义,如下所示:

每条消息的格式可以在[ 1 ]中找到。该漏洞与“MCS Connect Initial and GCC Create”请求有关。

收到“X.224连接确认”响应后,从客户端发送到服务器的“MCS Connect Initial and GCC Create”请求。“MCS Connect Initial and GCC Create”请求包含与安全相关的信息,虚拟通道创建信息以及其他受支持的RDP客户端功能。“MCS Connect Initial and GCC Create”请求的结构如下:

除tpktHeader字段外,所有多字节整数都是小端字节顺序。

•X.224层通常可以具有多种PDU类型并由任意长度组成,但“MCS Connect Initial and GCC Create”数据包具有3字节x224结构。
•mcsCi结构是T.125 MULTIPOINT-COMMUNICATION-SERVICE连接初始PDU,使用ASN.1 DER进行编码。
•gccCrq结构是T.124 GCC(Generic Conference Control) ConnectData结构。

“Settings Data Block”是一个或多个“Settings Data Block”的串联,其中每个具有以下格式:

存在各种类型的“Settings Data Block”,包括CS_CORE(0xC001),CS_SECURITY(0xC002),CS_NET(0xC003)等。
该tpktHeader字段具有下列结构:

tpktHeader中的所有多字节整数都是big-endian字节顺序。version必须为0x03,tpktLength指定整个数据包的长度。该漏洞与 “CS_NET”块也称为clientNetworkData)有关。
该clientNetworkData字段包含请求的虚拟频道列表。clientNetworkData字段的结构如下:

clientNetworkData的CS_NETHeader字段是0xC003,小端表示是\ x03 \ xc0。 channelCount字段指示请求的静态虚拟通道。 channelNamen(其中n是1,2,...,N)字段定义了通道的8字节空终止名称,channelOption_n字段指定了通道的属性。
RDP协议支持静态虚拟通道,旨在用作各种RDP组件和用户扩展的通信链路。 这些通道以其8字节通道名称而闻名,幷包括标准的Microsoft假设通道,如“rdpdr”(重定向),“rdpsnd”(声音),“cliprdr”(剪贴板共享)等。用户可以使用 RDP API支持其他渠道。 除上述通道外,Microsoft默认创建两个通道:MS_T120(用于RDP本身)和CTXTW(用于Citrix ICA)。 客户不应通过网络创建这些渠道; 相反,当建立连接时,这些通道由Windows RDP系统在内部初始化。

使用termdd!IcaCreateChannel()创建通道,它首先检查指定的命名通道是否存在,如果不是,则分配通道结构来创建通道。 指向通道结构的指针,这个指针我们称为ChannelControlStructure,它的结构存储在一个表中,这个表我们称为ChannelPointerTable。 所有RDP连接都以ChannelPointerTable开头,如下所示(前五个插槽不是用户控制的,因此不显示。而是将插槽号0作为第一个客户端可写通道):

在上表中,每个槽都可以存储一个ChannelControlStructure指针,其中标记为Empty的存储空指针。 当RDP客户端通过在clientNetworkData中指定它们来连接和打开通道时,将创建相应的ChannelControlStructures,并将其指针存储在从Slot 0开始的ChannelPointerTable中。请注意,CTXTW始终存在于插槽7中,而MS_T120存在于插槽0x1F中。

Microsoft Windows RDP内核驱动程序termdd.sys中存在“UAF”漏洞。在接收到包含clientNetworkData的“MCS Connect Initial and GCC Create”分组时,创建其中指定的信道的ChannelControlStructures。如果指定了名为“MS_T120 \ x00”的通道(例如,在插槽10中),则termdd!IcaCreateChannel()调用termdd!IcaFindChannelByName()并返回由插槽0x1F中的MS_T120结构指向的ChannelControlStructure。该指针(与插槽0x1F中的指针相同)存储在函数termdd!IcaBindVirtualChannels()中的用户指定插槽(在此示例中为插槽10)中。接下来,当使用MCS通道。加入请求打开通道时,MS_T120通道也会成功打开。如果攻击者随后将制作的数据发送到MS_T120频道,则termdd.sys会尝试通过发送错误消息并使用termdd!IcaCloseChannel()关闭该频道来响应该消息,后者又调用termdd!IcaFreeChannel(),从而释放 MS_T120 ChannelControlStructure并清除ChannelPointerTable中用户控制的插槽(运行示例中的插槽10)中的指针。但是,插槽0x1F中的相同指针不会被清除。随后,当连接终止时,调用RDPWD!SignalBrokenConnection(),然后调用termdd!IcaChannelInputInternal()并尝试使用Slot 0x1F处的指针写入释放的ChannelControlStructure。这导致了“UAF”状态。
远程未经身份验证的攻击者可以通过在打开MS_T120通道时与目标服务器建立RDP连接并向其发送精心设计的数据来利用此漏洞。 成功利用将导致攻击者能够使用管理(内核级)权限执行任意代码。

源代码演示

以下代码段取自termdd.sys版本6.1.7601.24056。 趋势科技添加的评论已经突出显示。

要检测利用此漏洞的攻击,检测设备必须监视和解析分配端口上的流量,默认情况下为3389 / TCP。
RDP连接以连接序列消息开始,由远程桌面协议:基本连接和图形远程处理(MS-RDPBCGR)协议定义,如下所示:

注意:
•此检测指南涉及消息“MCS Connect Initial和GCC Create”。
•RDP有两种类型的加密:自定义RDP加密,一种使用TLS。在前一种情况下,“MCS Connect Initial和GCC Create”是纯文本,而在后一种情况下,“MCS Connect Initial和GCC Create”是RDP客户端在TLS建立后发送的第一个数据包。
•在交换第一个请求和响应之后,可以使用TLS加密流量。确定这一点的最简单方法是检查到服务器的第二个传入数据包是否以“\ x16 \ x03”(TLS记录类型和TLS客户端Hello的高版本号)开头。

检测设备必须能够检查和分析RDP服务器与RDP客户端之间的RDP通信。如果RDP通信使用TLS,则检测设备必须在继续执行后续步骤之前解密流量。
检测设备必须查找传入的“MCS Connect Initial and GCC Create”请求。“MCS Connect Initial and GCC Create”请求的结构如下:


除tpktHeader字段外,所有多字节整数都是小端字节顺序。
•X.224层通常可以具有多种PDU类型并由任意长度组成,但“MCS Connect Initial和GCC Create”数据包具有3字节x224结构。
• mcsCi结构是T.125 MULTIPOINT-COMMUNICATION-SERVICE连接初始PDU,使用ASN.1 DER进行编码。
•gccCCrq结构是T.124通用会议控制ConnectData结构.
“Settings Data Block”是一个或多个“Settings Data Block”的串联,每个该块具有以下格式:


存在各种类型的“Settings Data Block”,包括CS_CORE(0xC001),CS_SECURITY(0xC002),CS_NET(0xC003)等。

如果找到“MCS Connect Initial and GCC Create”请求,则检测设备必须检查每个“Settings Data Block”并查找类型为CS_NET(0xC003)的设置。这样的“Settings Data Block”称为clientNetworkData,具有以下结构:


如果找到clientNetworkData,则检测设备必须遍历每个channelName_n(其中n是1,2 ..,N)并检查任何channelName n字段的值是否包含不区分大小写的字符串“MS_T120”。如果找到这样的频道,则应将流量视为恶意; 利用此漏洞的攻击正在进行中

触发漏洞

在将调试程序附加到目标系统时触发漏洞时,会发生以下错误检查:

结论

当Microsoft为其支持的操作系统修补此漏洞时,他们决定还为现在不支持的Windows XP和Windows Server 2003系统发布补丁。这表明他们认为这个漏洞有多严重。还有一些关于检测到主动攻击的讨论,但毫无疑问这个漏洞的可利用性。此错误明显获得其关键评级,受影响的系统应尽快修补。对于那些仍然在Windows XP或Server 2003上的人来说,这是另一个提醒,要求制定升级计划。微软可能已经发布了针对此漏洞的补丁,但是每次发布时,他们为这些现在古老的系统发布未来补丁的可能性会降低。
请注意,Microsoft补丁IcaBindVirtualChannels()和IcaReBindVirtualChannels()修复了termdd.sys中的两个易受攻击的函数。这两个函数暴露了两个不同但相似的攻击向量。我们的分析侧重于IcaBindVirtualChannels()公开的攻击媒介。

特别感谢趋势科技安全研究团队的Richard Chen,Pengsu Cheng,Kamlapati Choubey和Saran Neti对此漏洞提供了如此全面的分析。我们当然希望将来可以看到更多的漏洞分析。

参考文献:

[1] [MS-RDPBCGR]:Remote Desktop Protocol: Basic Connectivity and Graphics Remoting, https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-RDPBCGR/[MS-RDPBCGR].pdf
[2] Network-specific data protocol stacks for multimedia conferencing, ITU-T Recommendation T.123, https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-T.123-200701-I!!PDF-E&type=items
[3] Client Network Data (TS_UD_CS_NET), Microsoft, https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/49f99e00-caf1-4786-b43c-d425de29a03f
[4] T.125, Multipoint communication service protocol specification, ITU, https://www.itu.int/rec/T-REC-T.125
[5] T.124, Generic Conference Control, ITU, https://www.itu.int/rec/T-REC-T.124


 

0x00 前言

漏洞分析第一篇,在讲UAF之前, 先简单过一下HEVD代码的逻辑,方便后续的分析。

0x01 HEVD代码分析

1.1 驱动程序逻辑分析

驱动程序的主函数文件是HackSysExtremeVulnerableDriver.c
主要包含5个函数
DriverEntry: 驱动程序入口函数,类似于exe的main、dll的DllMain
IrpDeviceIoCtlHandler: 设备操作处理函数,DeviceIoControl请求处理,重要函数,根据请求会调用不同漏洞代码模块。
DriverUnloadHandler: 驱动卸载处理函数,可忽略
IrpCreateCloseHandler:驱动设备打开关闭处理函数,通常来说就是CreateFile、CloseHandle的请求处理,可忽略
IrpNotImplementedHandler:可忽略

除了DriverEntry是固定函数名,其他都是自定义的,只有传参和返回类型是有要求的,那么是怎么将相关请求绑定相关函数的呢。
ring3在访问驱动(通过驱动符号链接)进行操作时,会产生相应的IRP(I/O Request Package)事件,在驱动内对IRP进行操作,实现用户层对驱动的操作。
实现对IRP事件的处理需要使用到派遣函数,这时就是通过驱动对象的MajorFunction属性进行IRP请求类型和派遣函数绑定,所以派遣函数其实也是回调函数,也是为啥传参和返回类型是有要求的。
如下所示

IRP_MJ_CREATE对应CreateFile请求。
IRP_MJ_CLOSE对应CloseHandle请求。
IRP_MJ_DEVICE_CONTROL则对应DeviceIoControl请求,绑定的派遣函数是IrpDeviceIoCtlHandler。
IrpDeviceIoCtlHandler里获取IRP请求里设置的控制码,通过switch-case来调用不同的漏洞代码模块。当然控制码是自定义的。

再来看一下exp里如何访问漏洞模块的,像UAF,通过IOCTL_ALLOCATE_UAF_OBJECT和IOCTL_FREE_UAF_OBJECT等控制码来访问驱动里的漏洞模块。

对应到驱动代码里就是这块分支函数,也是后续的分析重点。
我们需要理解的逻辑大概就这些了,关于更多驱动开发的知识可参考下面这个,可以快速掌握一些驱动知识。
https://bbs.pediy.com/thread-266038.htm

1.2 UAF漏洞代码分析

1.2.1 UAF漏洞介绍

申请出一个堆块保存在一个指针中,在释放后,没有将该指针清空,形成了一个悬挂指针(danglingpointer),而后再申请出堆块时会将刚刚释放出的堆块申请出来,并复写其内容,而悬挂指针此时仍然可以使用,使得出现了不可控的情况。攻击者一般利用该漏洞进行函数指针的控制,从而劫持程序执行流。
当应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放回内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放回内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。(这块可能不太准确,大概看看就行)
但是其实这里有以下几种情况

  • 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
  • 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
  • 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。

漏洞利用的过程可以分为以下4步:

  1. 申请堆块,保存指针。
  2. 释放堆块,形成悬挂指针。
  3. 再次申请堆块,填充恶意数据。
  4. 使用悬挂指针,实现恶意目的。

下面我们去HEVD项目中具体看如何体现。

1.2.2 UAF漏洞代码

漏洞代码位于UseAfterFreeNonPagedPool.c
里面包括4个重要函数以及这4个IRP处理函数,IRP处理函数会分别调用这4个重要函数

AllocateUaFObjectNonPagedPool

用于创建内核对象PUSE_AFTER_FREE_NON_PAGED_POOL
如下调用ExAllocatePoolWithTag在内核非分页池申请内存,并填充数据

_USE_AFTER_FREE_NON_PAGED_POOL是一个0x58大小的结构体。

这边将该结构体对象存放在全局变量g_UseAfterFreeObjectNonPagedPool中了

FreeUaFObjectNonPagedPool

用于释放内核对象,这个函数里有两段代码,上面会修复代码,可以看到比g_UseAfterFreeObjectNonPagedPool被释放后,多了一个置NULL的动作,这样就可以防止悬挂指针的重利用。

AllocateFakeObjectNonPagedPool

将用户模式传入UserFakeObject指向内容拷贝给核心对象。
先根据FAKE_OBJECT_NON_PAGED_POOL结构体分配一个非分页池的内存,然后将UserFakeObject的内容拷贝给KernelFakeObject。
这里重点在于ExAllocatePoolWithTag分配内核池内存,如果存在一个已释放的相同大小或者大一点的内存,那么重新申请就有概率申请到该段内存,然后再为该段内存写入恶意代码,这样就会导致之前的悬挂指针再被调用时,访问的是被覆盖的内存内容,从而执行恶意代码。
为了增大申请到该段内存的概率,会使用一种池喷射的技巧,可参考扩展知识部分。

查看这个FAKE_OBJECT_NON_PAGED_POOL可以看到大小与之前的机构体一致。这个结构体没有如上分成callback和buffer,这个其实不影响的,只要大小一样,把结构体前4字节设置成恶意代码地址即可。

UseUaFObjectNonPagedPool

该函数作用是调用全局变量g_UseAfterFreeObjectNonPagedPool,执行他的回调函数。

1.2.3 小结

分析了漏洞代码,其实会对漏洞成因更加了解,我们只要按照AllocateUaFObjectNonPagedPool->FreeUaFObjectNonPagedPool->AllocateFakeObjectNonPagedPool->UseUaFObjectNonPagedPool的顺序调用,就可以触发漏洞,在第三步传入包含恶意代码地址的结构体,覆盖原来的内存,再二次调用原来的结构体指针即可访问恶意代码,也就是UAF(use after free)的含义。
而修复方案在于Free之后将引用指针置位NULL,来避免二次访问已释放内存块。
后续对于漏洞的利用除了上述流程还需要考虑如何提高申请到相同内存块的机率,这个涉及到内核池管理,也用到内核池漏洞常用的池喷射技术。

0x02 漏洞利用

先在用户空间的堆中分配FakeObject,将前4字节指向漏洞利用后运行的payload EopPayload地址。

2.1 池喷射代码

再强调下为啥需要池喷射,UAF需要重新申请到相同的内存块并覆盖成恶意代码,而内核池中可能会有许多空间的内存块,如果释放的内存块刚好和其他空闲的内存块相邻,系统就会将这两个内存块合并,那么再申请内存时, 无法保证刚好用到我们释放的那个内存块。
NtAllocateReserveObject可用于在内核池分配两个可选的内核对象,这里是调用NtAllocateReserveObject在内核空间分配IoCompletionReserve内核对象,IoCompletionReserve的内核对象大小为0x60,刚好比我们需要重利用的结构体0x58大一点。
池喷射第一步,先申请10000个IoCompletionReserve对象,用于将内核池中空闲、零散的池块都申请完。
第二步,然后再申请5000个该对象,这时申请出来的池块很大概率是连续的。

第三步,每隔一个内核对象释放一个对象,这样就会留下很多间隔0x60的空闲池块,那么在申请_USE_AFTER_FREE_NON_PAGED_POOL结构体时用到的池块的前一个池块就不会是空闲的,释放的时候就不会被合并,这样出意外的可能性就很低了。

这里可能会有一个疑问,上面释放了那么多池块,为啥不会申请到其他,一个原因是申请是优先使用池块大小相同或更相近的,我们在漏洞代码里看到的两个结构体都是0x58是最相近的,另一个原因是越晚释放的池块会更优先被使用,也就是后入先出的概念。

2.2 UAF利用代码

接着就是UAF利用的常规几步,
第一步:访问驱动,发送申请UAF_OBJECT结构体的请求。
第二步:访问驱动,发送释放UAF_OBJECT结构体的请求。

第三步:访问驱动,发送申请FAKE_OBJECT结构体的请求,这里循环了1000次,也是池喷射的概念,一次可能不一定申请到上面释放的内存块,所以增大概率,申请1000次。
第四步:也就是漏洞触发恶意代码执行的一步,调用原来已释放结构体的悬挂指针,访问被覆盖的内存块,触发恶意代码执行。
中间有一个FreeReserveObjects,用于释放之前池喷射申请的所有内存块,不然太占用内存空间了,因为运行在内核,不释放的话即使你当前漏洞利用程序退出也不会释放。

2.3 payload代码

这段payload的作用是将SYSTEM进程的token复制到当前进程,这样当前进程则为system权限。

fs寄存器在Ring0中指向一个称为KPCR的数据结构,即FS段的起点与 KPCR 结构对齐,而在Ring0中fs寄存器一般为0x30,这样fs:[124h]就指向KPRCB数据结构的第四个字节。由于 KPRCB 结构比较大,在此就不列出来了。查看其数据结构可以看到第四个字节指向CurrentThead(KTHREAD类型)。这样fs:[124h]其实是指向当前线程的_KTHREAD

kd> dt nt!_KPCR
   +0x000 NtTib            : _NT_TIB
    ......
   +0x0dc KernelReserved2  : [17] Uint4B
   +0x120 PrcbData         : _KPRCB


kd> dt _KPRCB
nt!_KPRCB
   +0x000 MinorVersion     : Uint2B
   +0x002 MajorVersion     : Uint2B
   +0x004 CurrentThread    : Ptr32 _KTHREAD
   +0x008 NextThread       : Ptr32 _KTHREAD
   +0x00c IdleThread       : Ptr32 _KTHREAD

_KTHREAD:[0x50] 指向 _KPROCESS, 即 nt!_KTHREAD.ApcState.Process_EPROCESS的第一个成员就是_KPROCESS,表示两个数据结构地址一样,则可以通过_KPROCESS访问_EPROCESS数据
再来看看_EPROCESS的结构,+0xb8处是进程活动链表,用于储存当前进程的信息,我们通过对它的遍历,可以找到system的token(+0xf8),我们知道system的PID一直是4,通过这一点我们就可以遍历了,遍历到系统token之后替换就行了

kd> dt nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x098 ProcessLock      : _EX_PUSH_LOCK
   +0x0a0 CreateTime       : _LARGE_INTEGER
   +0x0a8 ExitTime         : _LARGE_INTEGER
   +0x0b0 RundownProtect   : _EX_RUNDOWN_REF
   +0x0b4 UniqueProcessId  : Ptr32 Void
   +0x0b8 ActiveProcessLinks : _LIST_ENTRY
   +0x0c0 ProcessQuotaUsage : [2] Uint4B
   +0x0c8 ProcessQuotaPeak : [2] Uint4B
   +0x0d0 CommitCharge     : Uint4B
   +0x0d4 QuotaBlock       : Ptr32 _EPROCESS_QUOTA_BLOCK
   +0x0d8 CpuQuotaBlock    : Ptr32 _PS_CPU_QUOTA_BLOCK
   +0x0dc PeakVirtualSize  : Uint4B
   +0x0e0 VirtualSize      : Uint4B
   +0x0e4 SessionProcessLinks : _LIST_ENTRY
   +0x0ec DebugPort        : Ptr32 Void
   +0x0f0 ExceptionPortData : Ptr32 Void
   +0x0f0 ExceptionPortValue : Uint4B
   +0x0f0 ExceptionPortState : Pos 0, 3 Bits
   +0x0f4 ObjectTable      : Ptr32 _HANDLE_TABLE
   +0x0f8 Token            : _EX_FAST_REF

2.4 执行

执行效果如下,效果是通过UAF在内核进行system token复制,让当前进程的token已切换为system,接着创建一个新进程如cmd.exe则也是system权限。

然后看下内核的变化

# 搜索HEVD
lm m H*
# 查看符号表
kd> x /D HEVD!u*
 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
9a3e77f2          HEVD!UaFObjectCallbackNonPagedPoolNx (void)
9a3e7806          HEVD!UseUaFObjectNonPagedPoolNx (void)
9a3e74e8          HEVD!UseUaFObjectNonPagedPoolIoctlHandler (struct _IRP *, struct _IO_STACK_LOCATION *)
9a3e742c          HEVD!UseUaFObjectNonPagedPool (void)
9a3e78c2          HEVD!UseUaFObjectNonPagedPoolNxIoctlHandler (struct _IRP *, struct _IO_STACK_LOCATION *)
9a3e7108          HEVD!UninitializedMemoryStackObjectCallback (void)
9a3e6fe6          HEVD!UninitializedMemoryPagedPoolObjectCallback (void)
9a3e70e8          HEVD!UninitializedMemoryStackIoctlHandler (struct _IRP *, struct _IO_STACK_LOCATION *)
9a3e6fc6          HEVD!UninitializedMemoryPagedPoolIoctlHandler (struct _IRP *, struct _IO_STACK_LOCATION *)
9a3e7418          HEVD!UaFObjectCallbackNonPagedPool (void)

通过上述操作可找到UseUaFObjectNonPagedPool函数的地址,然后分析该函数调用g_UseAfterFreeObjectNonPagedPool结构体的回调函数位置,定位到9a3e749b

在9a3e749b下断点, 然后再运行exp

bp 9a3e749b

此处跳转的内存地址是00ab39d0

再步入之前,我们先看下nonPagedPool,看池喷射的效果
g_UseAfterFreeObjectNonPagedPool保存着内核对象_USE_AFTER_FREE_NON_PAGED_POOL的地址

9a3e4014->8757b948

通过dd 8757b948可以看到,当前释放的内核对象_USE_AFTER_FREE_NON_PAGED_POOL->CallBack已经指向ac39d0,后面连续的41(即A),其实这段就是FakeObject。
然后查看内核对象所在的nonPagedPool,这里很明显可以看到每个pool chunk大小都是60,并且每隔一个就是释放的状态,也正好符合我们刚才池喷射的理论。

!pool 8757b948

最后一列TAG中,Hack即表示AllocateFakeObjectNonPagedPool调用分配给fakeObject的内存

我们继续跟踪步入该段代码,可以看到和之前分析的payload一致,说明复盖悬挂指针的内存块成功。

kd> !dml_proc
Address  PID  Image file name
86cf38a8 4    System         
.... 
88a8e460 368  HackSysEVDExpl

断到最后token复制的位置,可以看到将system的token=0x8da01277拷贝给当前进程了。

kd> r ecx
ecx=88a8e460 #当前进程句柄
kd> r edx
edx=8da01277 #sytem进程的token
kd> dt nt!_EX_FAST_REF 86cf38a8+f8 # 通过句柄查看system进程的token
   +0x000 Object           : 0x8da01277 Void
   +0x000 RefCnt           : 0y111
   +0x000 Value            : 0x8da01277

0x03 扩展知识

3.1 windows API

3.1.1 DeviceIoControl

DeviceIoControl 将控制代码直接发送到指定的设备驱动程序,使相应的设备执行相应的操作。
语法

BOOL WINAPI DeviceIoControl(
  _In_        HANDLE       hDevice,
  _In_        DWORD        dwIoControlCode,
  _In_opt_    LPVOID       lpInBuffer,
  _In_        DWORD        nInBufferSize,
  _Out_opt_   LPVOID       lpOutBuffer,
  _In_        DWORD        nOutBufferSize,
  _Out_opt_   LPDWORD      lpBytesReturned,
  _Inout_opt_ LPOVERLAPPED lpOverlapped
);
  • hDevice [in]
    需要执行操作的设备句柄。该设备通常是卷,目录,文件或流,使用 CreateFile 函数打开获取设备句柄。
  • dwIoControlCode [in]
    操作的控制代码,该值标识要执行的特定操作以及执行该操作的设备的类型,每个控制代码的文档都提供了lpInBuffernInBufferSizelpOutBuffernOutBufferSize参数的使用细节。
  • lpInBuffer [in, optional]
    (可选)指向输入缓冲区的指针。这些数据的格式取决于dwIoControlCode参数的值。如果dwIoControlCode指定不需要输入数据的操作,则此参数可以为NULL。
  • nInBufferSize [in]
    输入缓冲区以字节为单位的大小。单位为字节。
  • lpOutBuffer [out, optional]
    (可选)指向输出缓冲区的指针。这些数据的格式取决于dwIoControlCode参数的值。如果dwIoControlCode指定不返回数据的操作,则此参数可以为NULL。
  • nOutBufferSize [in]
    输出缓冲区以字节为单位的大小。单位为字节。
  • lpBytesReturned [out, optional]
    (可选)指向一个变量的指针,该变量接收存储在输出缓冲区中的数据的大小。如果输出缓冲区太小,无法接收任何数据,则GetLastError返回ERROR_INSUFFICIENT_BUFFER,错误代码122(0x7a),此时lpBytesReturned是零。
  • lpOverlapped [in, out, optional]
    (可选)指向OVERLAPPED结构的指针。

返回值:
如果操作成功完成,DeviceIoControl将返回一个非零值。
如果操作失败或正在等待,则DeviceIoControl返回零。 要获得扩展的错误信息,请调用GetLastError。
https://docs.microsoft.com/zh-cn/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol?redirectedfrom=MSDN

3.1.2 ExAllocatePoolWithTag

ExAllocatePoolWithTag用于内核模式,在中分配指定类型的池内存,并返回指向已分配内存空间的首地址的指针。

PVOID ExAllocatePoolWithTag(
  __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType,
  SIZE_T                                         NumberOfBytes,
  ULONG                                          Tag
);
  • PoolType
    该参数用来指定想要申请的内存的类型(内核空间中的内存主要分成两类;分页内存区,和未分页内存区)。查询可选的内存区类型可以到MSDN查询POOL_TYPE结构。
    如果此值为NonPagedPool,则分配非分页内存。
    如果为PagedPool, 则分配内存为分页内存。
  • NumberOfBytes
    通过该参数指定想要分配的内存的字节数,最好是4的倍数。
  • Tag
    为将要被分配的空间指定标志(就是给你得到的空间取个独一无二的名字)。
    进一步解释:赋给该参数的内容是一个字符串常量,最多可以包含四个字母,该字符串应该放到单引号当中(比如:‘tag1’‘tag2’)。另外,这个字符串常常是逆序的,如,‘1gaT’(所以大家会发现输入这个参数的串确实都是倒过来的。。。)。输入到这个参数中的每一个字符的ASCII值都必须在0-127之间。每次的申请空间的时候都最好应该使用一个独一无二的标识,这样可以帮助调试器和检查器辨认和分析。
  • 返回值
    如果该函数发现目前系统的自由空间不足,就会返回NULL。否则,将返回指向被分配出来的空间的首地址。
    ### 3.1.3 ProbeForRead
    检查用户模式缓冲区是否确实驻留在地址空间的用户部分中,并且是否正确对齐。简而言之,就是看看这块内存是否是Ring3的内存,并不检查内存是否可读。如果不存在ring3内存地址空间范围内,则抛出异常。

    void ProbeForRead(
    const volatile VOID *Address,
    SIZE_T              Length,
    ULONG               Alignment
    );
  • Address
    [in] 指定用户模式缓冲区的开始

  • Length
    [in] 指定用户模式缓冲区的长度(以字节为单位)
  • Alignment
    [in] 指定用户模式缓冲区开头所需的对齐方式(以字节为单位)。
  • 返回值
    None

3.1.4 UNREFERENCED_PARAMETER

作用:告诉编译器,已经使用了该变量,不必检测警告!
在VC编译器下,如果您用最高级别进行编译,编译器就会很苛刻地指出您的非常细小的警告。当你声明了一个变量,而没有使用时,编译器就会报警告。

3.1.4 NtAllocateReserveObject

系统调用,负责在内核端创建保留对象–在内核池上执行内存分配,返回适当的Handle等

#define APC_OBJECT              0
#define IO_COMPLETION_OBJECT    1
#define MAX_OBJECT_ID           1
NTSTATUS STDCALL NtAllocateReserveObject(
    OUT PHANDLE hObject,
    IN POBJECT_ATTRIBUTES ObjectAttributes,
    IN DWORD ObjectType )
{
    PVOID       ObjectBuffer;
    HANDLE      hOutputHandle;
    NTSTATUS    NtStatus;
    if ( PreviousMode == UserMode )
    {
        /* Validate hObject */
    }
    if ( ObjectType > MAX_OBJECT_ID )
    {
        /* Bail out: STATUS_INVALID_PARAMETER
         */
    }else  {
        NtStatus = ObCreateObject( PreviousMode,
                       PspMemoryReserveObjectTypes[ObjectType],
                       ObjectAttributes,
                       PreviousMode,
                       0,
                       PspMemoryReserveObjectSizes[ObjectType],
                       0,
                       0,
                       &ObjectBuffer );
        if ( !NT_SUCCESS( NtStatus ) )
        /* Bail out: NtStatus
         */
            memset( ObjectBuffer, 0, PspMemoryReserveObjectSizes[ObjectType] );
        if ( ObjectType == IO_COMPLETION )
        {
            /*
             *
             * Perform some ObjectBuffer initialization
             *
             */
            ObjectBuffer[0x0C]  = 3;
            ObjectBuffer[0x20]  = PspIoMiniPacketCallbackRoutine;
            ObjectBuffer[0x24]  = ObjectBuffer;
            ObjectBuffer[0x28]  = 0;
        }
        NtStatus = ObInsertObjectEx( ObjectBuffer,
                         &hOutputHandle,
                         0,
                         0xF0003,
                         0,
                         0,
                         0 );
        if ( !NT_SUCCESS( NtStatus ) )
        /* Bail out: NtStatus
         */
            *hObject = hOutputHandle;
    }
    return(NtStatus);
}

hObject: 分配的内核对象句柄
ObjectType: 目前该参数只有两个值0/1,用于标识两个内核对象UserApcReserve和IoCompletionReserve,IoCompletionReserve对象大小为0x60。

3.2 Preparing Pool Memory

翻译自 Kernel Pool Exploitation on Windows 7 (BlackHat_DC_2011_Mandt_kernelpool-wp)
内核池利用的一个重要方面是能够一致地覆盖所需的内存。 由于内核池的碎片状态使分配的位置无法预测,因此攻击者必须首先使用内核对象或其他可控制的内存分配对内核池进行碎片整理。 在这方面的目标是分配所有空闲块,以使池分配器返回一个新页面。 用相同大小的分配填充新分配的页面,并释放第二个分配,这使攻击者可以为易受攻击的缓冲区创建漏洞。 反过来,这将使攻击者能够溢出用于填充内核池的对象或内存分配。

3.3 池喷射

尝试利用内核池漏洞时,必须处理块(chunks)和池(pool)的元数据。如果你想避免蓝屏,你需要控制一切,因为在块头上会有一些额外的检查。
内核池喷射是一项使池中分配位置可预测的艺术。这意味着你可以知道一个块将被分配到哪里,哪些块在其附近。
如果您想要泄露某些精确的信息或覆盖特定的数据,利用内核池喷射是必须的。
池喷射的基础是分配足够的对象,以确保您控制分配的位置。 Windows为我们提供了许多在不同类型的池中分配对象的工具。例如,我们可以在NonPagedPool(非分页池)中分配ReservedObjects或Semaphore 。关键是要找到与您要控制的池类型相匹配的对象。您选择的对象大小也很重要,因为它与创建后所留的空隙大小直接相关。一旦您选择了对象,您将首先通过大量分配该对象使得池非随机化。
上面申请的对象是属于内核对象,针对内核漏洞的堆喷射,微软有一个内核对象列表,我们可以通过调用用户模式功能来创建内核对象,尽管它不是很完整。
https://msdn.microsoft.com/library/windows/desktop/ms724485(v=vs.85).aspx
有些细节仍然需要注意,否则可能会遇到麻烦:

  1. 如果您选择的对象的大小不超过0x200字节,这很可能会在lookaside列表中存储相应的释放块,这样这些块的不会被合并。为避免这种情况,您必须释放足够多的对象填充满lookaside列表。
  2. 您的释放的块可能会落在DeferredFree列表中,并且不会立即合并。所以你必须释放足够多的对象来填充满这个列表,这样才能释放出块制造空隙。
  3. 最后,你在池中分配对象,这对于整个内核是很常见的。这意味着您刚创建的空隙可能随时被您无法控制的东西分配填充。所以你必须要快!

上述步骤的要点是:

  1. 通过使用对象的句柄,选择需要释放的块
  2. 释放足够的块填满lookaside列表
  3. 释放选定的块
  4. 免释放足够的块填充DeferredFree列表
  5. 尽可能快地使用你制造的空隙!

该技术实际应用中会有些改动。
先了解下UAF中的步骤
1.首先申请0x10000个该对象并将指针保存下来;
2.然后再申请0x5000个对象,将指针保存下来;
3.第二步中的0x5000个对象,每隔一个对象释放一个对象;
第一步的操作是将现有的空余堆块都申请出来,第二步中申请出来的堆块应该都是连续的,通过第三步的操作,使得我们申请UAE_AFTER_FREE结构体其前面的堆块应该不是空闲的,因此在释放的时候不会合并,从而再分配的时候出现意外的可能性基本为0。

参考

Windows内核池喷射1-偏内核池介绍 https://www.anquanke.com/post/id/86188

3.4 内核对象

3.4.1 内核对象类型查询

池喷射需要找到适合大小的内核对象
这里使用windbg分析
首先,获取更全面的对象列表

!object \ObjectTypes


这是一个可以在内核空间中分配的对象的列表。我们可以通过查看更多的细节来探索几个关于它们的重要属性。使用命令 dt nt!_OBJECT_TYPE _OBJECT_TYPE_INITIALIZER 结构的偏移量,它将给我们带来极大的方便。让我们看看它为我们提供了 Mutant 对象的哪些我想要的信息:

dt nt!_OBJECT_TYPE 8521a838


然后阅读下 _OBJECT_TYPE_INITIALIZER

dt nt!_OBJECT_TYPE_INITIALIZER 8521a838+28


_OBJECT_TYPE_INITIALIZER中有两个关键信息
此对象被分配给的池类型 – 在这里是非分页池(NonPagedPool)
功能偏移(这在实际的漏洞利用部分十分重要)
上面就是可获取到内核对象的一些信息,在实际过程中,分配到内核池的时候大小可能会有一些偏差。
像 用户模式 CreateMutexA调用可在内核中创建Mutant对象,而未命名和命名的mutex大小是不一样的,提供的名称会占用Mutant对象大小。

3.4.2 内核对象实际分析

接下来我们再测试下实际分配到内核池中时的大小怎么查看。
如下运行一段代码,会创建IoCompletionReserve内核对象,并保持不退出。
PS:这边获取到的句柄为0x70

from ctypes import *
from ctypes.wintypes import *
kernel32 = windll.kernel32
ntdll = windll.ntdll
def alloc_iocreserve():
    IO_COMPLETION_OBJECT = 1
    hHandle = HANDLE(0)
    ntdll.NtAllocateReserveObject(byref(hHandle), 0x0, IO_COMPLETION_OBJECT)
    hHandle = hHandle.value
    if hHandle == None:
        print "[-] Error while creating IoCompletionReserve"
        return 0
    print "Handle: " + hex(hHandle)
    return hex(hHandle)
alloc_iocreserve()
variable = raw_input('Press any key to exit...')

搜索名称为python.exe的进程信息

kd> !process 0 0 python.exe
PROCESS 88bd0030  SessionId: 1  Cid: 0b00    Peb: 7ffd3000  ParentCid: 019c
    DirBase: bf2d0580  ObjectTable: 969c1de8  HandleCount:  40.
    Image: python.exe

切换到该进程上下文

kd> .process 88bd0030
ReadVirtual: 88bd0048 not properly sign extended
Implicit process is now 88bd0030
WARNING: .cache forcedecodeuser is not enabled

在当前上下文查询句柄,其中可看到IoCompletionReserve内核对象的地址

kd> !handle 70
PROCESS 88bd0030  SessionId: 1  Cid: 0b00    Peb: 7ffd3000  ParentCid: 019c
    DirBase: bf2d0580  ObjectTable: 969c1de8  HandleCount:  40.
    Image: python.exe
Handle table at 969c1de8 with 40 entries in use
0070: Object: 8843a4b8  GrantedAccess: 000f0003 Entry: 8a2780e0
Object: 8843a4b8  Type: (86cf3be0) IoCompletionReserve
    ObjectHeader: 8843a4a0 (new version)
        HandleCount: 1  PointerCount: 1

这样就可以找到池的位置,如下,每行是一个pool chunk,注意到带*号的 pool chunk就是IoCompletionReserve内核对象在内核池块中实际地址,并且可以看到大小为0x60,根据这个大小我们就可以选取相应的内核对象进行池喷射了。

kd> !pool 8843a4b8
Pool page 8843a4b8 region is Unknown
 8843a000 size:   30 previous size:    0  (Allocated)  Mmdi
 8843a030 size:   18 previous size:   30  (Allocated)  MmSi
 8843a048 size:   30 previous size:   18  (Allocated)  Icp 
 8843a078 size:   18 previous size:   30  (Allocated)  MmSi
 8843a090 size:   68 previous size:   18  (Allocated)  EtwR (Protected)
 8843a0f8 size:   48 previous size:   68  (Allocated)  Vad 
 8843a140 size:   68 previous size:   48  (Allocated)  FMsl
 8843a1a8 size:   40 previous size:   68  (Allocated)  Even (Protected)
 8843a1e8 size:   20 previous size:   40  (Allocated)  ReTa
 8843a208 size:   50 previous size:   20  (Allocated)  Vadm
 8843a258 size:   c8 previous size:   50  (Allocated)  Ntfx
 8843a320 size:   48 previous size:   c8  (Allocated)  Vad 
 8843a368 size:   40 previous size:   48  (Allocated)  VM3D
 8843a3a8 size:   38 previous size:   40  (Allocated)  AlIn
 8843a3e0 size:   a8 previous size:   38  (Allocated)  File (Protected)
*8843a488 size:   60 previous size:   a8  (Allocated) *IoCo (Protected)
        Owning component : Unknown (update pooltag.txt)
 8843a4e8 size:   40 previous size:   60  (Allocated)  Even (Protected)

如果想看pool chunk具体信息,可如下,PreviousSize BlockSize在32位系统中是实际大小>>3,64位是>>4,所以这里BlockSize=0xc*8=0x60,和上面获取到的一致

kd> dt _POOL_HEADER 8843a488
nt!_POOL_HEADER
   +0x000 PreviousSize     : 0y000010101 (0x15)
   +0x000 PoolIndex        : 0y0000000 (0)
   +0x002 BlockSize        : 0y000001100 (0xc)
   +0x002 PoolType         : 0y0000010 (0x2)
   +0x000 Ulong1           : 0x40c0015
   +0x004 PoolTag          : 0xef436f49
   +0x004 AllocatorBackTraceIndex : 0x6f49
   +0x006 PoolTagHash      : 0xef43

3.4.3 参考

Windows内核池喷射1-偏内核池介绍 https://www.anquanke.com/post/id/86188
Windows内核池喷射2-合适的内核对象获取 https://www.anquanke.com/post/id/86896
堆喷射 http://huntercht.51vip.biz:8888/zblog/?id=35

0x04 参考

https://gloomyer.com/2019/01/17/uaf-2019-1-17/
Windows Kernel Exploit 内核漏洞学习(1)-UAF https://bbs.pediy.com/thread-252310.htm
https://www.freesion.com/article/9516423134/
Windows堆喷射 https://www.anquanke.com/post/id/85586
Windows堆喷射 https://www.anquanke.com/post/id/85592
内核池块分析: https://www.cnblogs.com/flycat-2016/p/5449738.html
windows 内核池原理: https://blog.csdn.net/qq_38025365/article/details/106259634
内核池利用文档 BlackHat_DC_2011_Mandt_kernelpool-wp.pdf


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章