系统安全漏洞风险解决方案 (系统漏洞挖掘与利用技术)

漏洞描述

Windows 事件跟踪 (ETW) 机制允许记录内核或应用程序定义的事件以进行调试。 开发人员能够启动和停止事件跟踪会话,检测应用程序以提供跟踪事件,并通过调用 ETW 用户模式 Windows API 集来使用跟踪事件。

最终,这些将导致对内核 (ntoskrnl*ex.e**) 的相应系统调用请求以执行功能。在ETW请求更新周期性捕获状态中,在特定条件下,存在一个use-after-free漏洞,攻击者可以可控地分配一个0x30字节的缓冲区,释放它,然后重用这个缓冲区来执行任意代码。该漏洞于2021年8月末纰漏详情

漏洞详情

使用 NtTraceControl() 函数发送更新定期捕获状态的请求,函数代码为 0x25(EtwFunctionUpdatePeriodicCaptureState) ,则会调用内核态函数 EtwpUpdatePeriodicCaptureState

第一次调用会申请一块 TimerContextInfo 内核池。

第二次调用该函数在在内部可以通过传入的参数(控制GUID),导致(调用回调函数)释放到掉内核对象 TimerContextInfo

第三次调用该函数将再次访问 TimerContextInfo ,导致UAF

该漏洞在windows 10低版本中并不存在 EtwpUpdatePeriodicCaptureState ,本文以 windows 10 20H2 x64 系统进行Exploit的开发。

ed2k://|file|cn_windows_10_business_editions_version_20h2_updated_march_2021_x64_dvd_ca61aaaa.iso|6074574848|B6741C4E4B09337471B02DC95E953992|/


为了感谢广大读者伙伴的支持,准备了以下福利给到大家:

[一>关注我,私信回复“资料”获取<一]

1、200多本网络安全系列电子书(该有的都有了)

2、全套工具包(最全中文版,想用哪个用哪个)

3、100份src源码技术文档(项目学习不停,实践得真知)

4、网络安全基础入门、Linux、web安全、攻防方面的视频(2021最新版)

6、 网络安全学习路线(告别不入流的学习)

7、ctf夺旗赛解析(题目解析实战操作)[一>关注我,私信回复“资料”获取<一]

漏洞成因

第一次调用 NtTraceControl 函数并发送 EtwFunctionUpdatePeriodicCaptureState 代码,则会调用内核函数 EtwpUpdatePeriodicCaptureState

♥ 该函数内部通过传入的LogId获取其对应的 LoggerContext ,通过控制Logid和GUID,满足 EtwpCheckNotificationAccess 条件,则会将 LoggerContext->PeriodicCaptureStateGuids.ProviderCount 置为传入的NumOfGuids。♥ 并在后续申请大小为 0x30的TimerContextInfo 对象,并进行初始化,设置 TimerContextInfo->WorkItem.WorkerRoutine的回调函数为SendCaptureStateNotificationsWorker ,参数为 TimerContextInfo 对象♥ 调用ExSetTimer,并将 LoggerContext->PeriodicCaptureStateTimerState 设置为 EtwpPeriodicTimerSet(TRUE) ,计时器例程为 TimerContextInfo

安全漏洞更新日志,内核漏洞怎么检测

第二次调用时,通过控制传入的GUID,可以让其走Free分支,在这个分支LoggerContext->PeriodicCaptureStateGuids.ProviderCount将会被设置为0

安全漏洞更新日志,内核漏洞怎么检测

如果在第二次调用之后,sendCaptureStateNotificationsWorker回调函数被执行,那么此时因为LoggerContext->PeriodicCaptureStateGuids.ProviderCount被重置为0,该函数只是简单的将LoggerContext->PeriodicCaptureStateTimerState = EtwpPeriodicTimerUnset(False)

安全漏洞更新日志,内核漏洞怎么检测

并在函数末尾释放掉TimerContextInfo对象

安全漏洞更新日志,内核漏洞怎么检测

然而再第三次调用,同样选择走 Alloc_TimerContextInfo 分支,在将 LoggerContext->PeriodicCaptureStateGuids.ProviderCount 置为传入的 NumOfGuids 后,因为之前申请的 LoggerContext->PeriodicCaptureStateTimer ,已经被设置了,所以不会申请新的 LoggerContext ,只是将 LoggerContext->PeriodicCaptureStateTimerState 设置为 EtwpPeriodicTimerSet 。当系统调用 SendCaptureStateNotificationsWorker、ExAllocateTimer 函数时,传入的参数TimerContextInfo已经被释放,导致UAF

安全漏洞更新日志,内核漏洞怎么检测

POC开发

在调用NtTraceControl,FunctionCode为EtwFunctionUpdatePeriodicCaptureState,InBuffer的结构体为

安全漏洞更新日志,内核漏洞怎么检测

EtwpCheckNotificationAccess绕过

POC开发的难点在于如何绕过EtwpCheckNotificationAccess的检测,该函数会检测GUID对应的SecurityDescriptor是否包含特定的权限

安全漏洞更新日志,内核漏洞怎么检测

本次需要绕过的权限检测为 0x80(**TRACELOG_GUID_ENABLE**)

安全漏洞更新日志,内核漏洞怎么检测

系统中的GUID,包含 TRACELOG_GUID_ENABLE 的有很多,但是有一点我们需要明确,我们现在的权限相对较低,不能使用组为SYSTEM、LOCAL SERVICE、Administrators等权限较高的组,可以选择使用Everyone、Users等权限较低的用户

安全漏洞更新日志,内核漏洞怎么检测

经过筛选,有3个GUID可以满足要求,如下,既是Authenticated Users 低权限组,又包含TRACELOG_GUID_ENABLE的ACCESS MASK权限

14f8138e-3b61-580b-544b-2609378ae460             Allow NT AUTHORITY\Authenticated Users        001204e1    WMIGUID_QUERY, TRACELOG_CREATE_REALTIME, TRACELOG_CREATE_ONDISK, TRACELOG_GUID_ENABLE, TRACELOG_ACCESS_REALTIME                                                    
541dae91-cc3c-5807-b064-c2561c16d7e8             Allow NT AUTHORITY\Authenticated Users        001204e1    WMIGUID_QUERY, TRACELOG_CREATE_REALTIME, TRACELOG_CREATE_ONDISK, TRACELOG_GUID_ENABLE, TRACELOG_ACCESS_REALTIME                                                    
cb2ff72d-d4e4-585d-33f9-f3a395c40be7             Allow NT AUTHORITY\Authenticated Users        001204e1    WMIGUID_QUERY, TRACELOG_CREATE_REALTIME, TRACELOG_CREATE_ONDISK, TRACELOG_GUID_ENABLE, TRACELOG_ACCESS_REALTIME


EtwpCheckNotificationAccess有两个参数

第一个参数为 上述结构体中的Guids,我们可以通过上面3个GUID中的任意一个进行绕过

第二个参数为LoggerId对应的 LoggerContext 结构体的 InstanceGuid (可以通过对 EtwpAcquireLoggerContextByLoggerId 函数的逆向得到)

安全漏洞更新日志,内核漏洞怎么检测

EtwpLoggerContext中默认的GUID

windbg里面可以直观的看到EtwpLoggerContext里面保存的是个类型为_WMI_LOGGER_CONTEXT的数组

安全漏洞更新日志,内核漏洞怎么检测

该结构体_WMI_LOGGER_CONTEXT,的0x124位置保存着InstanceGuid

安全漏洞更新日志,内核漏洞怎么检测

通过以下命令,我们可以获取该数组内的所有InstanceGuid

.for(r $t1=0;$t1<0x40;r $t1=$t1+1;){r $t1 ;dt nt!_GUID poi(poi(poi( nt!PspHostSiloGlobals+360h)+1c8)+($t1*8))+124h)}


安全漏洞更新日志,内核漏洞怎么检测

这些LoggerId对应的权限虽然有包含0x80权限的,但其所处的组权限较高,因此我们通过控制LoggerId获取系统自身的InstanceGuid进行绕过可能不太理想

安全漏洞更新日志,内核漏洞怎么检测

向EtwpLoggerContext注册GUID

既然EtwpLoggerContext中不存在低权限,且又包含TRACELOG_GUID_ENABLE权限的GUID,那么我们是否可以注册一个GUID到EtwpLoggerContext中呢?

发现确实可以注册GUID到EtwpLoggerContext中,该代码中,我们只需要将SessionGuid和ProviderGuid改为我们自己的GUID即可

安全漏洞更新日志,内核漏洞怎么检测

注册GUID的部分代码如下,其中SessionHandle即为LoggerID

安全漏洞更新日志,内核漏洞怎么检测

通过上述描述,我们可以通过注册一个GUID到 EtwpLoggerContext 数组中,然后通过该GUID的索引,构造 ETW_UPDATE_PERIODIC_CAPTURE_STATE 结构体,并前后发送三次请求即可造成BSOD

安全漏洞更新日志,内核漏洞怎么检测

Exploit开发

占位TimerContextInfo对象

在POC开发中已经得知是第二次调用 NtTraceControl 之后, TimerContextInfo 对象被释放。因此我们要在第二次调用之后进行占位,其大小为0x30字节(不包含Pool Header),类型为NonPagedPoolNx。通过对 TimerContextInfo 的逆向,该结构体大概如下

安全漏洞更新日志,内核漏洞怎么检测

通过逆向在 IopVerifierExAllocatePoolWithQuota 的调用中会申请类型为 NonPagedPoolNx 的Pool,其上层调用如 NtSetInformationFile、NtSetEaFile 等函数都可以实现控制申请pool的大小,并写入内容。但是却存在一个问题,就是这类poolTag为IO的池,都会在IO结束时被释放,虽然被释放了,但是当前内核池的内容并没有立即被占用,内容还在,因此利用的成功率并不是百分之百。

安全漏洞更新日志,内核漏洞怎么检测

本次占位笔者用的是NtSetEaFile函数,该函数内部检测DEVICE_OBJECT的Flags是否包含4(DO_BUFFERED_IO),因此第一个参数的句柄我给的是PEAuth的文件句柄,该devobj的Flags为0x44。

安全漏洞更新日志,内核漏洞怎么检测

伪造Fake_WorkItem

NtSetEaFile函数内部,会通过其第三个参数为pbuffer(Fake_WorkItem)、其第四个函数申请指定的大小的pool。该函数将把会pbuffer的内容放到新申请的内核的地址空间。

只要占位成功,通过控制 WorkItem.WorkerRoutine 为我们的处理函数, WorkItem.Parameter 为参数,即有可能实现对任意地址的破坏或者是读写。通常我们将这类处理函数称之为Gadget,之前有用过 SeSetAccessStateGenericMapping ,该函数有2个参数,本次需要有一个参数的函数 RtlSetAllBits ,其参数为 PRTL_BITMAP 类型,该函数内部我们可以看到,可以通过控制 BitMapHeader->SizeOfBitMap 大小,实现对 BitMapHeader->Buffer进行0xff 的填充的大小

安全漏洞更新日志,内核漏洞怎么检测

因此 Fake_WorkItem 需要满足以下3个条件,(通过控制pbuffer即可)

FakeContextInfo->WorkItem.List.Flink = 0 (这样当前的 WorkItem 将被验证,并放到队列)♥ FakeContextInfo->WorkItem.WorkerRoutine = RtlSetAllBits() FakeContextInfo->WorkItem.Parameter = RTL_BITMAP FakeBitMapHeader

安全漏洞更新日志,内核漏洞怎么检测

伪造Fake_RtlBitMapAddr

参考附录4,我们可以通过TheadName,来泄露其内核地址空间,通过调用 RtlSetAllBits 函数可以实现对 Fake_RtlBitMapAddr BitMapHeader->Buffer 实现0xff的设置,设置的大小为0x16个字节。我们可以通过设置token的0x40位置的Privileges,给自身进程添加 SE_DEBUG_PRIVILEGE 权限。

因此构造的 Fake_RtlBitMapAddr 需要满足以下两个条件

BitMapHeader->SizeOfBitMap==0x80 (可以设置0x16个字节的0xff)♥ BitMapHeader->Buffer==token.Privileges

安全漏洞更新日志,内核漏洞怎么检测

提权

通过Fake_WorkItem,可以成功的调用到RtlSetAllBits函数,又通过Fake_RtlBitMapAddr成功的将token.Privileges.Present以及Enabled都设置为了0xffffffffffffffff

安全漏洞更新日志,内核漏洞怎么检测

因为有了SE_DEBUG_PRIVILEGE 权限,剩下就是是注入winlogo*ex.e**进程即可。

安全漏洞更新日志,内核漏洞怎么检测