freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

CVE-2021-34486 ETW权限提升漏洞分析
2022-01-21 10:38:34
所属地 北京

前言

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才行。权限表如下:

ConstantSymbolic NameGeneric Mapping (ETW)Generic Mapping (WMI)
0x0001WMIGUID_QUERYreadread
0x0002WMIGUID_SETwritewrite
0x0004WMIGUID_NOTIFICATIONread
0x0008WMIGUID_READ_DESCRIPTIONread
0x0010WMIGUID_EXECUTEexecuteexecute
0x0020TRACELOG_CREATE_REALTIMEwrite
0x0040TRACELOG_CREATE_ONDISKwrite
0x0080TRACELOG_GUID_ENABLEexecute
0x0100TRACELOG_ACCESS_KERNEL_LOGGER

0x0200TRACELOG_LOG_EVENTexecute
0x0400TRACELOG_ACCESS_REALTIMEexecute
0x0800TRACELOG_REGISTER_GUIDSexecute

而在该漏洞中需要寻找权限为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/

# 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录