【漏洞预警】CVE-2021-34486:Windows 事件跟踪 (ETW)TimerCallbackContex

admin 2022年3月22日10:18:38评论141 views字数 7524阅读25分4秒阅读模式

点击上方蓝字“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]" conditionif ( (_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)技术。


总而言之,利用步骤是:

  1. 泄漏 ntoskrnl.exe 的内核基地址 NtQuerySystemInformation(SystemModuleInformation)

  2. 获取RtlSetAllbits()ntoskrnl.exe 中的函数偏移量

  3. 泄漏当前进程令牌地址 NtQuerySystemInformation(SystemHandleInformation)

  4. 设置一个虚假的RTL_BITMAP FakeBitMapHeader线程名称并泄漏其内核池地址NtQuerySystemInformation(SystemBigPoolInformation)

  5. 分配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

【漏洞预警】CVE-2021-34486:Windows 事件跟踪 (ETW)TimerCallbackContex



本文始发于微信公众号(Ots安全):【漏洞预警】CVE-2021-34486:Windows 事件跟踪 (ETW)TimerCallbackContex

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月22日10:18:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【漏洞预警】CVE-2021-34486:Windows 事件跟踪 (ETW)TimerCallbackContexhttps://cn-sec.com/archives/507539.html

发表评论

匿名网友 填写信息