对于CreateProcess
的底层探究实现。
CreateProcess
CreateProcess
在3环最终会调用ntdll!NtCreateUserProcess
通过syscall
进入0环,我们可以通过调用NtCreateUserProcess
来规避AV/EDR
对CreateProcess
的监控
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 );
|
这里一个个参数来看,ProcessHandle
和ThreadHandle
为进程线程句柄,设置为NULL
即可
1
| HANDLE hProcess, hThread = NULL;
|
接下来两个参数ProcessDesiredAccess
、ThreadDesiredAccess
为控制权限,全部设置为THREAD_ALL_ACCESS
即可
ProcessObjectAttributes
和ThreadObjectAttributes
,它们是指向 的指针OBJECT_ATTRIBUTES
,此结构包含可应用于将要创建的对象或对象句柄的属性,这里设置为NULL
ProcessFlags
和ThreadFlags
内部设置的标志ThreadFlags
决定了我们希望如何创建进程和线程,这里也都设置为NULL
即可
再看ProcessParameters
参数,指向一个RTL_USER_PROCESS_PARAMETERS
结构,该结构描述了要创建的进程的启动参数
这里使用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
结构的指针,这个结构也是一个未文档化的结构
这里设置为PsCreateInitialState
即可
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); }
|
这里注意,因为这里需要使用到一些未导出的结构,这里就需要一个完整的头文件去包含这些结构确保不会出错,这里使用到x64dbg
的ntdll.h
文件,链接如下:https://github.com/x64dbg/TitanEngine/blob/x64dbg/TitanEngine/ntdll.h
实现效果如下
父进程欺骗
我们在使用CreateProcess
创建进程的时候能通过设置特定的参数来达到欺骗的效果,在NtCreateUserProcess
里面也同样能够做到
这里我们首先看一下之前我们生成的进程,可以看到父进程为svchost.exe
那么这里我们首先找到explorer
的PID,为5720
通过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
添加代码即可伪造父进程为explorer