点击上方蓝字“Ots安全”一起玩耍
概述
Windows 事件跟踪 (ETW) 机制允许记录内核或应用程序定义的事件以进行调试。开发人员能够启动和停止事件跟踪会话,检测应用程序以提供跟踪事件,并通过调用 ETW 用户模式 Windows API 集来使用跟踪事件。最终,这些将导致对内核 (ntoskrnl.exe) 的相应系统调用请求以执行功能。
在ETW请求更新周期性捕获状态中,在特定条件下,存在一个use-after-free漏洞,攻击者可以可控地分配一个0x30字节的缓冲区,释放它,然后重用这个缓冲区来执行任意代码。
受影响的版本
POC 测试在
-
安装了 KB5004945 累积更新(2021 年 7 月 7 日)的 Windows 10 (x64) 21H1(操作系统内部版本 19043.1083)
-
安装了 KB5003637 累积更新(2021 年 6 月)的 Windows 10 (x64) 21H1(操作系统内部版本 19043.1052)
-
安装了 KB5003214 累积更新(2021 年 5 月)的 Windows 10 (x64) 21H1(操作系统内部版本 19043.1023)
-
安装了 KB5003173 累积更新(2021 年 5 月)的 Windows 10 (x64) 21H1(操作系统内部版本 19043.985)
以下分析是在 ntoskrnl.exe 10.0.19041.867(即:Windows 10 20H2 与 2021 年 3 月更新)上完成的。
技术细节
更新周期性捕获状态的请求与NtTraceControl()函数一起发送,函数代码为 0x25 并遵循输入缓冲区格式。
typedef struct _ETW_UPDATE_PERIODIC_CAPTURE_STATE
{
ULONG LoggerId;
ULONG DueTime; //system time units (100-nanosecond intervals)
ULONG NumOfGuids;
GUID Guids[ANYSIZE_ARRAY];
} ETW_UPDATE_PERIODIC_CAPTURE_STATE, * PETW_UPDATE_PERIODIC_CAPTURE_STATE;
总共需要 3 次更新定期捕获状态的请求才能触发释放后使用漏洞。
在第一个请求中:
ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff1 = {
2,
0,
1,
{GUID({ 0xF526AD2F, 0x57F9, 0x5336, {0x96, 0x37, 0x5C, 0x2E, 0x54, 0xF8, 0x7E, 0x9C} })} };
ntoskrnl 首先检索PVOID LoggerContext对应的LoggerId 2. 然后它确保所有Guids[]在 this 中都有通知访问权限LoggerContext。接下来,它为PeriodicCaptureStateTimerCallback()计时器回调例程及其回调上下文参数分配一个计时器对象,即CONTEXTINFO TimerContextInfo带有EtwU池标记的 0x30 字节对象,保存引用并激活它。
typedef struct _CONTEXTINFO
{
WORK_QUEUE_ITEM WorkItem;
ULONG64 Unknown;
USHORT LoggerId;
UCHAR Padding[6];
} CONTEXTINFO, *PCONTEXTINFO;
__int64 __fastcall EtwpUpdatePeriodicCaptureState(unsigned int LoggerId, unsigned int DueTime, unsigned __int16 NumOfGuids, GUID *Guids)
{
...
//if there is no saved timer object reference, then allocate a new instance...
if ( !LoggerContext_->ExTimerObject )
{
TimerContextInfo = (CONTEXTINFO *)ExAllocatePoolWithTag(NonPagedPoolNx, 0x30ui64, 'UwtE');//allocate CONTEXTINFO object
TimerContextInfo_ = TimerContextInfo;
if ( !TimerContextInfo )
goto RETURN_ERROR_C0000017;
TimerContextInfo->LoggerId = LoggerId;//InBuff1.LoggerId
TimerContextInfo->Unknown = v22;
TimerContextInfo->WorkItem.WorkerRoutine = SendCaptureStateNotificationsWorker;//pointer of worker function
TimerContextInfo->WorkItem.Parameter = TimerContextInfo;//input parameter for worker function
TimerContextInfo->WorkItem.List.Flink = 0i64;
LoggerContext_->ExTimerObject = ExAllocateTimer((PEXT_CALLBACK)PeriodicCaptureStateTimerCallback, TimerContextInfo, 8u);//save timer ref
}
ExTimerObject = (PEX_TIMER)LoggerContext_->ExTimerObject;
LoggerContext_->DueTime = 0xFFFFFFFFFF676980ui64 * DueTime;
ExSetTimer((ULONG_PTR)ExTimerObject);//DueTime = InBuff1.DueTime * FFFFFFFFFF676980h
LODWORD(LoggerContext_->ExTimerState) = 1;
goto RETURN_1;
...
RETURN_1:
if ( (_InterlockedExchangeAdd64(pLock, 0xFFFFFFFFFFFFFFFFui64) & 6) == 2 )
ExfTryToWakePushLock(pLock);
KeAbPostRelease((ULONG_PTR)pLock);
RETURN_2:
EtwpReleaseLoggerContext(LoggerContext_, 0i64);
return (unsigned int)res_EtwpCheckNotificationAccess;
}
该PeriodicCaptureStateTimerCallback()例程只是将SendCaptureStateNotificationsWorker(PCONTEXTINFO TimerContentInfo) 回调例程作为系统工作队列的工作项排入队列。最后,SendCaptureStateNotificationsWorker()例程将构建通知数据包并将其发送给所有Guids[].
在第二个请求中:
ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff2 = {
2,
0,
1,
{GUID({ 0x60D201F4, 0x741E, 0x4792, {0xB5, 0xB3, 0x67, 0x3F, 0xC6, 0xC2, 0x5B, 0x3B} })} };
但是,这一次,如果其中任何一个Guids[]在检索LoggerContext到的 中没有通知访问权限,则继续重置LoggerContext->NumOfGuids为 0。
__int64 __fastcall EtwpUpdatePeriodicCaptureState(unsigned int LoggerId, unsigned int DueTime, unsigned __int16 NumOfGuids, GUID *Guids)
{
...
{
...
FREE_POOLS_AND_RESET:
GuidsPool = (void *)LoggerContext_->GuidsPool;
if ( GuidsPool )
{
ExFreePoolWithTag(GuidsPool, 0);
LoggerContext->GuidsPool = 0i64;
LOWORD(LoggerContext->NumOfGuids) = 0;
}
...
}
if ( (_DWORD)NumOfGuids_ )
{
while ( 1 )//loop to ensure all Guids[] have notification-access rights
{
res_EtwpCheckNotificationAccess = EtwpCheckNotificationAccess(
&Guids[v4].Data1,
(__int64)&LoggerContext_->field_0[0x124]);
if ( res_EtwpCheckNotificationAccess < 0 )
break;
if ( ++v4 >= (int)NumOfGuids_ )
goto ALL_GUIDS_HAVE_NOTIFICATION_ACCESS_OK;
}
res_EtwpCheckNotificationAccess = 0xC0000022;
v8 = 0;
goto FREE_POOLS_AND_RESET;
}
ALL_GUIDS_HAVE_NOTIFICATION_ACCESS_OK:
...
}
到这个时候,DueTime应该已经过期并且SendCaptureStateNotificationsWorker()工作例程被调用。该例程类似地首先检索相应的LoggerContext. 并且由于LoggerContext->NumOfGuids已被第二个请求重置,该例程将不会执行其任何预期任务。相反,它会立即释放其输入参数;该PCONTEXTINFO TimerContextInfo池。
void __fastcall SendCaptureStateNotificationsWorker(CONTEXTINFO *TimerContextInfo)
{
...
if ( TimerContextInfo )
{
LoggerContext = (LOGGERCONTEXT *)EtwpAcquireLoggerContextByLoggerId(
TimerContextInfo->Unknown,
LOWORD(TimerContextInfo->LoggerId),
0);
if ( LoggerContext )
{
pLock = &LoggerContext->Lock;
ExAcquirePushLockExclusiveEx((ULONG_PTR)&LoggerContext->Lock, 0i64);
LODWORD(LoggerContext__->ExTimerState) = 0;
if ( *(_DWORD *)&LoggerContext__->field_0[336] )
{
//this branch is the main functionality of SendCaptureStateNotificationsWorker() worker routine...
NumOfGuids = LOWORD(LoggerContext__->NumOfGuids);
if ( (_WORD)NumOfGuids )//...which will be executed when LoggerContext->NumOfGuids > 0
{
...
if ( (int)EtwpBuildNotificationPacket(v10, v23, v15, &v19) >= 0 )
{
EtwpSendDataBlock(v12, v19);
EtwpUnreferenceDataBlock(v19);
}
...
if ( LOWORD(LoggerContext__->NumOfGuids) && !LODWORD(LoggerContext__->ExTimerState) )
{
ExSetTimer(LoggerContext__->ExTimer);
LODWORD(LoggerContext__->ExTimerState) = 1;
v2 = 1;
}
...
...
} // end of "NumOfGuids" condition
} // end of "LoggerContext__->field_0[336]" condition
if ( (_InterlockedExchangeAdd64(pLock, 0xFFFFFFFFFFFFFFFFui64) & 6) == 2 )
ExfTryToWakePushLock(pLock);
KeAbPostRelease((ULONG_PTR)pLock);
EtwpReleaseLoggerContext(LoggerContext__, 0i64);
if ( v2 )//v2 is set when main functionality is executed, and hence TimerContextInfo pool is still in-used...
return;
goto LABEL_31;//...otherwise, TimerContextInfo pool is freed
}
}
LABEL_31:
ExFreePoolWithTag(TimerContextInfo, 0);
}
在第三个也是最后一个请求中:
ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff3 = {
2,
0,
1,
{GUID({ 0xF526AD2F, 0x57F9, 0x5336, {0x96, 0x37, 0x5C, 0x2E, 0x54, 0xF8, 0x7E, 0x9C} })} };
ntoskrnl 的执行与第一个数据包类似。除了这一次,因为在 中保存了计时器对象的引用LoggerContext_->ExTimer,之前释放的CONTEXTINFO TimerContextInfo池将被重用以启动新的计时器操作ExSetTimer()。随后,释放的池被引用,导致蓝屏死机。再次引用已释放池有以下三种常见情况:
-
在 中的工作项验证期间ExQueueWorkItem(),导致 BSOD 错误检查代码 E4 (WORKER_INVALID)
-
在SendCaptureStateNotificationsWorker() 工作程序中再次释放,导致 BSOD 错误检查代码 13A (KERNEL_MODE_HEAP_CORRUPTION)
-
已为释放池重新分配的其他无关代码,导致 BSOD 错误检查代码 50 (PAGE_FAULT_IN_NONPAGED_AREA)
值得注意的是,有时,该LoggerId值必须为 3 而不是 2 才能触发漏洞。
开发策略
主要的利用计划是回收释放的 0x30 字节池并用受控字节覆盖它。这将允许我们控制回调函数指针及其对应的参数指针。尽管大多数堆喷射技术分配的池大小大于 0x30 字节,但没有必要像这些技术那样保持池分配。换句话说,如果可以分配一个 0x30 字节的池,写入它的内容就足够了,只要缓冲区没有被其他模块重新分配,随后是否释放缓冲区就没有关系。幸运的是,有一些 Nt 函数允许我们这样做。
接下来,我们必须找到一个设置SE_DEBUG_PRIVILEGE令牌位的小工具函数,条件是它只接受 1 个指针参数。幸运的是,经过一些逆向工程,发现(给魏磊的帽子提示)RtlSetAllBits()to 函数是完美的候选者;它需要一个RTL_BITMAP BitMapHeader结构指针并相应地设置位。但是,由于系统线程正在执行回调函数,因此RTL_BITMAP BitMapHeader必须在内核中分配该结构。否则,超级用户模式访问保护 (SMAP) 将进行错误检查。要解决此问题,可以将结构(加上填充)设置为线程名称,并且可以使用NtQuerySystemInformation(SystemBigPoolInformation)[1]泄漏其池地址。
最后,小工具功能和过程标记指针可与广泛使用的泄露NtQuerySystemInformation(SystemModuleInformation)和NtQuerySystemInformation(SystemHandleInformation)技术。
总而言之,利用步骤是:
-
泄漏 ntoskrnl.exe 的内核基地址 NtQuerySystemInformation(SystemModuleInformation)
-
获取RtlSetAllbits()ntoskrnl.exe 中的函数偏移量
-
泄漏当前进程令牌地址 NtQuerySystemInformation(SystemHandleInformation)
-
设置一个虚假的RTL_BITMAP FakeBitMapHeader线程名称并泄漏其内核池地址NtQuerySystemInformation(SystemBigPoolInformation)
-
分配CONTEXTINFO FakeContextInfo具有以下条件的假结构:
-
FakeContextInfo->WorkItem.List.Flink= 0(这样当前WorkItem将被验证和入队)
-
FakeContextInfo->WorkItem.WorkerRoutine=ntoskrnl基地址 +RtlSetAllBits()偏移量
-
FakeContextInfo->WorkItem.Parameter = RTL_BITMAP FakeBitMapHeader
6.发送第一个NtTraceControl(0x25)请求以触发真实CONTEXTINFO ContextInfo池的分配
7.发送第二个NtTraceControl(0x25)请求以触发免费的真实CONTEXTINFO ContextInfo池
8.回收并覆盖释放的池 FakeContextInfo
9.发送第三个NtTraceControl(0x25)请求以触发(重新)使用 FakeContextInfo,以启用当前进程令牌的 SE_DEBUG_PRIVILEGE
10.注入shellcodewinlogon.exe创建一个SYSTEMcmd.exe
本文始发于微信公众号(Ots安全):【漏洞预警】CVE-2021-34486:Windows 事件跟踪 (ETW)TimerCallbackContex
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论