3

CVE-2021-34486 ETW权限提升漏洞分析

 2 years ago
source link: https://www.freebuf.com/articles/network/320483.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

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_QUERYreadread0x0002WMIGUID_SETwritewrite0x0004WMIGUID_NOTIFICATIONread0x0008WMIGUID_READ_DESCRIPTIONread0x0010WMIGUID_EXECUTEexecuteexecute0x0020TRACELOG_CREATE_REALTIMEwrite0x0040TRACELOG_CREATE_ONDISKwrite0x0080TRACELOG_GUID_ENABLEexecute0x0100TRACELOG_ACCESS_KERNEL_LOGGER0x0200TRACELOG_LOG_EVENTexecute0x0400TRACELOG_ACCESS_REALTIMEexecute0x0800TRACELOG_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/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK