Drunkmars's Blog

调用NtCreateUserProcess创建进程绕过杀软hook

字数统计: 1.3k阅读时长: 6 min
2022/05/14

对于CreateProcess的底层探究实现。

CreateProcess

CreateProcess在3环最终会调用ntdll!NtCreateUserProcess通过syscall进入0环,我们可以通过调用NtCreateUserProcess来规避AV/EDRCreateProcess的监控

NtCreateUserProcess

NtCreateUserProcess并未文档化,通过逆向可以得到以下结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NTSTATUS
NTAPI
NtCreateUserProcess(
_Out_ PHANDLE ProcessHandle,
_Out_ PHANDLE ThreadHandle,
_In_ ACCESS_MASK ProcessDesiredAccess,
_In_ ACCESS_MASK ThreadDesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ProcessObjectAttributes,
_In_opt_ POBJECT_ATTRIBUTES ThreadObjectAttributes,
_In_ ULONG ProcessFlags,
_In_ ULONG ThreadFlags,
_In_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters,
_Inout_ PPS_CREATE_INFO CreateInfo,
_In_ PPS_ATTRIBUTE_LIST AttributeList
);

这里一个个参数来看,ProcessHandleThreadHandle为进程线程句柄,设置为NULL即可

1
HANDLE hProcess, hThread = NULL;

接下来两个参数ProcessDesiredAccessThreadDesiredAccess为控制权限,全部设置为THREAD_ALL_ACCESS即可

ProcessObjectAttributesThreadObjectAttributes,它们是指向 的指针OBJECT_ATTRIBUTES,此结构包含可应用于将要创建的对象或对象句柄的属性,这里设置为NULL

image-20220514163249970

ProcessFlagsThreadFlags内部设置的标志ThreadFlags决定了我们希望如何创建进程和线程,这里也都设置为NULL即可

再看ProcessParameters参数,指向一个RTL_USER_PROCESS_PARAMETERS结构,该结构描述了要创建的进程的启动参数

image-20220514163543860

这里使用RtlCreateProcessParametersEx来初始化,结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS NTAPI RtlCreateProcessParametersEx(
_Out_ PRTL_USER_PROCESS_PARAMETERS* pProcessParameters,
_In_ PUNICODE_STRING ImagePathName,
_In_opt_ PUNICODE_STRING DllPath,
_In_opt_ PUNICODE_STRING CurrentDirectory,
_In_opt_ PUNICODE_STRING CommandLine,
_In_opt_ PVOID Environment,
_In_opt_ PUNICODE_STRING WindowTitle,
_In_opt_ PUNICODE_STRING DesktopInfo,
_In_opt_ PUNICODE_STRING ShellInfo,
_In_opt_ PUNICODE_STRING RuntimeData,
_In_ ULONG Flags
);

第一个参数指向的就是RTL_USER_PROCESS_PARAMETERS结构,第二个参数即要启动的exe路径,这里以calc.exe为例

1
2
UNICODE_STRING NtImagePath;
RtlInitUnicodeString(&NtImagePath, (PWSTR)L"\\??\\C:\\Windows\\System32\\calc.exe");

然后剩下的参数(除了最后一个)可以全部设置为NULL,最后一个参数Flags用来规范RTL_USER_PROCESS_PARAMETERS_NORMALIZED,创建进程时,一些输入甚至还没有完全初始化。如果发生这种情况,则有可能正在访问的内存只是描述进程的结构的相对偏移量,而不是实际的内存地址,初始化ProcessParameters的代码如下

1
RtlCreateProcessParametersEx(&ProcessParameters, &NtImagePath, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, RTL_USER_PROCESS_PARAMETERS_NORMALIZED);

下一个参数是CreateInfo,它是一个指向PS_CREATE_INFO结构的指针,这个结构也是一个未文档化的结构

image-20220514163711417

这里设置为PsCreateInitialState即可

image-20220514163725804

1
2
3
PS_CREATE_INFO CreateInfo = { 0 };
CreateInfo.Size = sizeof(CreateInfo);
CreateInfo.State = PsCreateInitialState;

最后一个参数AttributeList用于设置进程和线程创建的属性,这里使用RtlAllocateHeap初始化

1
2
3
4
5
6
PPS_ATTRIBUTE_LIST AttributeList = (PS_ATTRIBUTE_LIST*)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PS_ATTRIBUTE));
AttributeList->TotalLength = sizeof(PS_ATTRIBUTE_LIST) - sizeof(PS_ATTRIBUTE);

AttributeList->Attributes[0].Attribute = PS_ATTRIBUTE_IMAGE_NAME;
AttributeList->Attributes[0].Size = NtImagePath.Length;
AttributeList->Attributes[0].Value = (ULONG_PTR)NtImagePath.Buffer;

然后使用NtCreateUserProcess如下

1
NtCreateUserProcess(&hProcess, &hThread, PROCESS_ALL_ACCESS, THREAD_ALL_ACCESS, NULL, NULL, NULL, NULL, ProcessParameters, &CreateInfo, AttributeList);

因为我们是在堆里面分配的空间,需要用RtlFreeHeap释放堆空间,使用RtlDestroyProcessParameters()释放存储在RTL_USER_PROCESS_PARAMETERS结构中的进程参数

1
2
RtlFreeHeap(RtlProcessHeap(), 0, AttributeList);
RtlDestroyProcessParameters(ProcessParameters);

完整代码如下

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
void NtCreateUserProcess()
{
UNICODE_STRING NtImagePath;
RtlInitUnicodeString(&NtImagePath, (PWSTR)L"\\??\\C:\\Windows\\System32\\calc.exe");

PRTL_USER_PROCESS_PARAMETERS ProcessParameters = NULL;
RtlCreateProcessParametersEx(&ProcessParameters, &NtImagePath, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, RTL_USER_PROCESS_PARAMETERS_NORMALIZED);

PS_CREATE_INFO CreateInfo = { 0 };
CreateInfo.Size = sizeof(CreateInfo);
CreateInfo.State = PsCreateInitialState;

PPS_ATTRIBUTE_LIST AttributeList = (PS_ATTRIBUTE_LIST*)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PS_ATTRIBUTE));
AttributeList->TotalLength = sizeof(PS_ATTRIBUTE_LIST) - sizeof(PS_ATTRIBUTE);
AttributeList->Attributes[0].Attribute = PS_ATTRIBUTE_IMAGE_NAME;
AttributeList->Attributes[0].Size = NtImagePath.Length;
AttributeList->Attributes[0].Value = (ULONG_PTR)NtImagePath.Buffer;

HANDLE hProcess, hThread = NULL;
if (FALSE == NtCreateUserProcess(&hProcess, &hThread, PROCESS_ALL_ACCESS, THREAD_ALL_ACCESS, NULL, NULL, NULL, NULL, ProcessParameters, &CreateInfo, AttributeList))
{
printf("NtCreateUserProcess successfully\n");
}

RtlFreeHeap(RtlProcessHeap(), 0, AttributeList);
RtlDestroyProcessParameters(ProcessParameters);
}

这里注意,因为这里需要使用到一些未导出的结构,这里就需要一个完整的头文件去包含这些结构确保不会出错,这里使用到x64dbgntdll.h文件,链接如下:https://github.com/x64dbg/TitanEngine/blob/x64dbg/TitanEngine/ntdll.h

实现效果如下

image-20220514165718575

父进程欺骗

我们在使用CreateProcess创建进程的时候能通过设置特定的参数来达到欺骗的效果,在NtCreateUserProcess里面也同样能够做到

这里我们首先看一下之前我们生成的进程,可以看到父进程为svchost.exe

image-20220514180514740

那么这里我们首先找到explorer的PID,为5720

image-20220514180834860

通过NtOpenProcess获得其句柄

1
2
3
4
5
6
7
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, 0, 0, 0, 0);

CLIENT_ID PID = { (HANDLE)5720, NULL };

HANDLE hParent = NULL;
NtOpenProcess(&hParent, PROCESS_ALL_ACCESS, &oa, &PID);

添加属性即可

1
2
3
AttributeList->Attributes[1].Attribute = PS_ATTRIBUTE_PARENT_PROCESS;
AttributeList->Attributes[1].Size = sizeof(HANDLE);
AttributeList->Attributes[1].ValuePtr = hParent;

完整代码如下

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
#include <Windows.h>
#include "ntdll.h"
#pragma comment(lib, "ntdll")

int main()
{

UNICODE_STRING NtImagePath, CurrentDirectory, CommandLine;
RtlInitUnicodeString(&NtImagePath, (PWSTR)L"\\??\\C:\\Windows\\System32\\mmc.exe");
RtlInitUnicodeString(&CurrentDirectory, (PWSTR)L"C:\\Windows\\System32");
RtlInitUnicodeString(&CommandLine, (PWSTR)L"\"C:\\Windows\\System32\\mmc.exe\"");

PRTL_USER_PROCESS_PARAMETERS ProcessParameters = NULL;
RtlCreateProcessParametersEx(&ProcessParameters, &NtImagePath, NULL, &CurrentDirectory, &CommandLine, NULL, NULL, NULL, NULL, NULL, RTL_USER_PROCESS_PARAMETERS_NORMALIZED);

PS_CREATE_INFO CreateInfo = { 0 };
CreateInfo.Size = sizeof(CreateInfo);
CreateInfo.State = PsCreateInitialState;

PPS_ATTRIBUTE_LIST AttributeList = (PS_ATTRIBUTE_LIST*)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PS_ATTRIBUTE)*3);
AttributeList->TotalLength = sizeof(PS_ATTRIBUTE_LIST);

AttributeList->Attributes[0].Attribute = PS_ATTRIBUTE_IMAGE_NAME;
AttributeList->Attributes[0].Size = NtImagePath.Length;
AttributeList->Attributes[0].Value = (ULONG_PTR)NtImagePath.Buffer;

OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, 0, 0, 0, 0);

CLIENT_ID cid = { (HANDLE)10104, NULL };

HANDLE hParent = NULL;
NtOpenProcess(&hParent, PROCESS_ALL_ACCESS, &oa, &cid);

AttributeList->Attributes[1].Attribute = PS_ATTRIBUTE_PARENT_PROCESS;
AttributeList->Attributes[1].Size = sizeof(HANDLE);
AttributeList->Attributes[1].ValuePtr = hParent;

DWORD64 policy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;

AttributeList->Attributes[2].Attribute = PS_ATTRIBUTE_MITIGATION_OPTIONS_2;
AttributeList->Attributes[2].Size = sizeof(DWORD64);
AttributeList->Attributes[2].ValuePtr = &policy;

HANDLE hProcess, hThread = NULL;
NtCreateUserProcess(&hProcess, &hThread, PROCESS_ALL_ACCESS, THREAD_ALL_ACCESS, NULL, NULL, NULL, NULL, ProcessParameters, &CreateInfo, AttributeList);

CloseHandle(hParent);

RtlFreeHeap(RtlProcessHeap(), 0, AttributeList);
RtlDestroyProcessParameters(ProcessParameters);
}

首先使用之前的代码打开mmc,可以看到跟父进程不是explorer

image-20220514182417599

添加代码即可伪造父进程为explorer

image-20220514183009421

CATALOG
  1. 1. CreateProcess
  2. 2. NtCreateUserProcess
  3. 3. 父进程欺骗