最近以来, 使用Native API绕过AV/EDR的讨论似乎总保持着一定的热度, HellsGate后的各种衍生Gate, Syswhispers也从2进化到了3。这类技术的本质上比较相似, 动态获取目标功能号后, 再调用syscall
指令从而native call。但发现一个现象, 研究者们似乎更倾向于使用NtCreateThread
而非NtCreateProcess
来验证他们的代码有效性。于是, 今年年初开始, 我开始针对进程创建的native call进行一系列研究。
进程创建的native call也并非崭新的话题, 需要4个native API即可满足需求:
NtCreateFile
NtCreateSection
NtCreateProcess(Ex)
NtCreateThread(Ex)
BlackHat 2017中的Process Doppelganging技术就是基于此诞生的。
而时至今日, NtCreateProcess
在Win10时代只在系统进程创建时被调用, 用户进程的创建使用NtCreateUserProcess
。这就是本文的主题, 围绕NtCreateUserProcess
创建一个正常用户进程。
以下研究基于Windows10 21H2 (19044.1415)。
让我们从原理开始, 先看看Windows下创建用户进程的各个阶段 (Windows Internals 7th):

结合kernelbase和ntdll, 可以得出:

Stage1,5均在kernelbase!CreateProcessInternalW
中实现。而我们关注的NtCreateUserProcess
也是从CreateProcessInternalW
中调用的:

默认情况下, ThreadFlags
被赋值为1, 代表创建后线程暂停。
借助processhacker提供的头文件, NtCreateUserProcess
的原型为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
NTSYSCALLAPI
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, / / PROCESS_CREATE_FLAGS_ *
_In_ ULONG ThreadFlags, / / THREAD_CREATE_FLAGS_ *
_In_opt_ PVOID ProcessParameters, / / PRTL_USER_PROCESS_PARAMETERS
_Inout_ PPS_CREATE_INFO CreateInfo,
_In_opt_ PPS_ATTRIBUTE_LIST AttributeList
);
|
这里我们关注最后3个参数:
- ProcessParameters
- CreateInfo
- AttributeList
该参数的构建, 需要依靠另一个Native API: RtlCreateProcessParametersEx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
NTSYSAPI
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 / / pass RTL_USER_PROC_PARAMS_NORMALIZED to keep parameters normalized
);
|
其中参数2ImagePathName
为目标进程的Imag路径, 传入时该字符串必须以\\??\\
为前缀。
函数返回值保存在参数1pProcessParameters
。子进程的PEB的字段ProcessParameters
保存此信息。
函数用法与在NtCreateProcess
时期相同, 直接使用即可。
结构体信息来自processhacker:
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
|
typedef struct _PS_CREATE_INFO
{
SIZE_T Size;
PS_CREATE_STATE State;
union
{
/ / PsCreateInitialState
struct
{
union
{
ULONG InitFlags;
struct
{
UCHAR WriteOutputOnExit : 1 ;
UCHAR DetectManifest : 1 ;
UCHAR IFEOSkipDebugger : 1 ;
UCHAR IFEODoNotPropagateKeyState : 1 ;
UCHAR SpareBits1 : 4 ;
UCHAR SpareBits2 : 8 ;
USHORT ProhibitedImageCharacteristics : 16 ;
};
};
ACCESS_MASK AdditionalFileAccess;
} InitState;
/ / PsCreateFailOnSectionCreate
struct
{
HANDLE FileHandle;
} FailSection;
/ / PsCreateFailExeFormat
struct
{
USHORT DllCharacteristics;
} ExeFormat;
/ / PsCreateFailExeName
struct
{
HANDLE IFEOKey;
} ExeName;
/ / PsCreateSuccess
struct
{
union
{
ULONG OutputFlags;
struct
{
UCHAR ProtectedProcess : 1 ;
UCHAR AddressSpaceOverride : 1 ;
UCHAR DevOverrideEnabled : 1 ; / / from Image File Execution Options
UCHAR ManifestDetected : 1 ;
UCHAR ProtectedProcessLight : 1 ;
UCHAR SpareBits1 : 3 ;
UCHAR SpareBits2 : 8 ;
USHORT SpareBits3 : 16 ;
};
};
HANDLE FileHandle;
HANDLE SectionHandle;
ULONGLONG UserProcessParametersNative;
ULONG UserProcessParametersWow64;
ULONG CurrentParameterFlags;
ULONGLONG PebAddressNative;
ULONG PebAddressWow64;
ULONGLONG ManifestAddress;
ULONG ManifestSize;
} SuccessState;
};
} PS_CREATE_INFO, * PPS_CREATE_INFO;
|
这是个大有用处的结构体, 由状态, 结构体长度, 以及与状态对应union组成, 有2个状态是我们需要的:
状态 |
值 |
说明 |
InitState |
0 |
调用`NtCreateUserProcess 时作为参数, 应设置为该状态 |
SuccessState |
6 |
调用`NtCreateUserProcess 后, 成功时为该状态 |
初始化, 最简化的情况下, 只需要填入状态和大小即可:
1
2
|
createInfo.State = PsCreateInitialState;
createInfo.Size = sizeof(PS_CREATE_INFO);
|
成功状态的相关信息见下文。
属性列表, 结构体信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
typedef struct _PS_ATTRIBUTE
{
ULONG_PTR Attribute;
SIZE_T Size;
union
{
ULONG_PTR Value;
PVOID ValuePtr;
};
PSIZE_T ReturnLength;
} PS_ATTRIBUTE, * PPS_ATTRIBUTE;
typedef struct _PS_ATTRIBUTE_LIST
{
SIZE_T TotalLength;
PS_ATTRIBUTE Attributes[ 1 ];
} PS_ATTRIBUTE_LIST, * PPS_ATTRIBUTE_LIST;
|
使用时, 根据TotalLength
手动调整Attributes
数组的大小。每个PS_ATTRIBUTE
在x64下大小为0x20, 其中字段Attribute
由宏PsAttributeValue
提供:
1
2
3
4
5
|
(((Number) & PS_ATTRIBUTE_NUMBER_MASK) | \
((Thread) ? PS_ATTRIBUTE_THREAD : 0 ) | \
(( Input ) ? PS_ATTRIBUTE_INPUT : 0 ) | \
((Additive) ? PS_ATTRIBUTE_ADDITIVE : 0 ))
|
并且, processhacker提供了现成的定义。对比正常进程创建时调用的NtCreateUserProcess
, 最简化情况下我们仅需要:
1
2
|
PsAttributeValue(PsAttributeImageInfo, FALSE, FALSE, FALSE)
|
其对应的PS_ATTRIBUTE
的字段ValuePtr
, 为目标进程的image路径, 和RtlCreateProcessParametersEx
中使用的一样。
构建好参数后, 我们可以调用NtCreateUserProcess
了! 注意, 如果flag设置为线程暂停, 还需要调用NtResumeThread
进程才可以正常跑起来。
成功调用后, 如上文所述, 可以检查CreateInfo
的state
字段是否为6, 并按照其字段, 输出我们感兴趣的信息。

如果你旨在寻找一种手动调用NtCreateUserProcess
以创建进程的方法, 成功了, 我们的故事就到此为止了。祝你享受一个美好的周末。
不过, 仍有一些奇怪的地方。正如本节一开始所述, NtCreateUserProcess
调用后可以视为阶段4的结束, 但NtResumeThread
视作阶段6的开始, 中间阶段5的缺失, 会不会导致其他的问题呢。
我们创建子进程notepad.exe
:

噢, "不幸"的事情发生了。
我们以notepad.exe为切入点, 尝试找到一种解决方案以正常创建进程。
调试方法, 在NtCreateUserProcess
调用后, 在NtResumeThread
之前, 附加到目标进程。断在LdrIntializeThunk
, 由于Parallel Loading技术会断下多次。
直接f9跑起来, 发现触发异常:


根据日志, 异常发生在comctl32.dll
的加载中。查看notepad.exe的导出表,

而加载路径中的comctl32.dll
, 导出表确实没有序号345 (0x159):

与可以正常创建的notepad.exe对比, 正常加载的comctl32.dll
存在该导出函数。

二者对比, 发现路径上存在差异:
- 不正常:
WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_5.82.19041.1110_none_792d1c772443f647
- 正常:
WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.19041.1110_none_60b5254171f9507e
明显是版本不匹配, 一个5.82
一个6.0
。推测存在一种机制, 被创建的进程有能力指定加载DLL的版本。而路径中的WinSxS
已经提示与SxS重定向机制相关。
简单来讲, CreateProcessInternalsW
在阶段5的工作之一, 先要收集SxS信息, 例如清单文件(Manifest), 之后构建一个包含SxS数据的信息, 发送给Csrss以通知Windows子进程实施SxS重定向机制。有关SxS重定向的原理性描述, 参考Windows Internals 7th, P162。

以直观的角度, 查看notepad.exe的清单文件, 显示依赖的comctl32.dll
版本为6.0.0.0
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-5-16 08:44
被D0pam1ne编辑
,原因: