首页
社区
课程
招聘
15
[原创] Native API系列: 使用NtCreateUserProcess创建正常工作的进程
发表于: 2022-5-14 22:05 27526

[原创] Native API系列: 使用NtCreateUserProcess创建正常工作的进程

2022-5-14 22:05
27526

Direct-NtCreateUserProcess

0x00 Prologue

最近以来, 使用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创建一个正常用户进程。

0x01 最简化实现

以下研究基于Windows10 21H2 (19044.1415)。

 

让我们从原理开始, 先看看Windows下创建用户进程的各个阶段 (Windows Internals 7th):

 

pic1

 

结合kernelbase和ntdll, 可以得出:

 

pic2

 

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

 

pic3

 

默认情况下, 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

ProcessParameters

该参数的构建, 需要依靠另一个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时期相同, 直接使用即可。

CreateInfo

结构体信息来自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);

成功状态的相关信息见下文。

AttributeList

属性列表, 结构体信息:

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
#define PsAttributeValue(Number, Thread, Input, Additive) \
    (((Number) & PS_ATTRIBUTE_NUMBER_MASK) | \
    ((Thread) ? PS_ATTRIBUTE_THREAD : 0) | \
    ((Input) ? PS_ATTRIBUTE_INPUT : 0) | \
    ((Additive) ? PS_ATTRIBUTE_ADDITIVE : 0))

并且, processhacker提供了现成的定义。对比正常进程创建时调用的NtCreateUserProcess, 最简化情况下我们仅需要:

1
2
#define PS_ATTRIBUTE_IMAGE_INFO \
    PsAttributeValue(PsAttributeImageInfo, FALSE, FALSE, FALSE)

其对应的PS_ATTRIBUTE的字段ValuePtr, 为目标进程的image路径, 和RtlCreateProcessParametersEx中使用的一样。

 

构建好参数后, 我们可以调用NtCreateUserProcess了! 注意, 如果flag设置为线程暂停, 还需要调用NtResumeThread进程才可以正常跑起来。

 

成功调用后, 如上文所述, 可以检查CreateInfostate字段是否为6, 并按照其字段, 输出我们感兴趣的信息。

 

pic5

 

如果你旨在寻找一种手动调用NtCreateUserProcess以创建进程的方法, 成功了, 我们的故事就到此为止了。祝你享受一个美好的周末。

 

不过, 仍有一些奇怪的地方。正如本节一开始所述, NtCreateUserProcess调用后可以视为阶段4的结束, 但NtResumeThread视作阶段6的开始, 中间阶段5的缺失, 会不会导致其他的问题呢。

 

我们创建子进程notepad.exe:

 

pic4

 

噢, "不幸"的事情发生了。

0x02 寻找错误原因

版本差异

我们以notepad.exe为切入点, 尝试找到一种解决方案以正常创建进程。

 

调试方法, 在NtCreateUserProcess调用后, 在NtResumeThread之前, 附加到目标进程。断在LdrIntializeThunk, 由于Parallel Loading技术会断下多次。

 

直接f9跑起来, 发现触发异常:

 

pic5

 

pic6

 

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

 

pic7

 

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

 

pic8

 

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

 

pic9

 

二者对比, 发现路径上存在差异:

  • 不正常: 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重定向机制相关。

Fusion/Sxs redirection

简单来讲, CreateProcessInternalsW在阶段5的工作之一, 先要收集SxS信息, 例如清单文件(Manifest), 之后构建一个包含SxS数据的信息, 发送给Csrss以通知Windows子进程实施SxS重定向机制。有关SxS重定向的原理性描述, 参考Windows Internals 7th, P162。

 

pic10

 

以直观的角度, 查看notepad.exe的清单文件, 显示依赖的comctl32.dll版本为6.0.0.0

 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-5-16 08:44 被D0pam1ne编辑 ,原因:
收藏
免费 15
支持
分享
赞赏记录
参与人
雪币
留言
时间
wangzesen
为你点赞~
2023-7-13 09:03
伟叔叔
为你点赞~
2023-3-18 02:30
PLEBFE
为你点赞~
2022-7-27 00:51
心游尘世外
为你点赞~
2022-7-26 22:35
飘零丶
为你点赞~
2022-7-17 02:28
tank小王子
为你点赞~
2022-6-14 14:31
青史无疆
为你点赞~
2022-5-18 10:11
coneco
为你点赞~
2022-5-18 09:44
Ratin
为你点赞~
2022-5-18 00:12
zhczf
为你点赞~
2022-5-17 16:33
Foodie
为你点赞~
2022-5-17 12:53
lovenikola
为你点赞~
2022-5-17 04:27
vvv_347
为你点赞~
2022-5-15 13:07
D0pam1ne
为你点赞~
2022-5-15 13:05
はつゆき
为你点赞~
2022-5-15 07:52
最新回复 (17)
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
大赞。这几天扒拉XP源码没搞定CSR,你这个简洁明了。有点小建议,
既然是NativeAPI程序,应避免调用kernel32函数,可稍微调整一下,比如
m.CreateProcessMSG.ClientId.UniqueProcess = pClientId->UniqueProcess; //::GetProcessId(hProcess);
m.CreateProcessMSG.ClientId.UniqueThread = pClientId->UniqueThread; //::GetThreadId(hThread);

GetLastError可以用ntdll的RtlGetLastWin32Error,或者NtCurrentTeb()->LastErrorValue
2022-5-17 10:55
0
雪    币: 1060
活跃值: (325)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
zz77 大赞。这几天扒拉XP源码没搞定CSR,你这个简洁明了。有点小建议, 既然是NativeAPI程序,应避免调用kernel32函数,可稍微调整一下,比如 m.CreateProcessMSG.Cli ...
非常感谢! 这部分旨在输出信息而考虑不周了
2022-5-17 11:50
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
另外Nt函数出现错误时不会设置LastStatusValue和LastErrorValue,这时GetLastError得不到错误信息,还是需要根据返回的NTSTATUS来解释。
2022-5-17 12:49
0
雪    币: 89
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
难受啊,我四月份就弄好了,在做兼容适配,被大佬先发布了
2022-5-17 13:04
0
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
mark一下 
2022-5-17 15:19
0
雪    币: 1060
活跃值: (325)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
7
zz77 另外Nt函数出现错误时不会设置LastStatusValue和LastErrorValue,这时GetLastError得不到错误信息,还是需要根据返回的NTSTATUS来解释。
确实. 代码已更新.
2022-5-17 21:19
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
[+] Process Handle                 = 0x00000000000000BC
[+] Thread Handle                  = 0x00000000000000B4
[+] target process pid             = 0x27d0
[+] target thread pid              = 0x149c
[+] createInfo.State               = 0x6
[+] createInfo.FileHandle          = 0x00000000000000C0
[+] createInfo.SectionHandle       = 0x00000000000000CC
[+] createInfo.PebAddressNative    = 0xf49000
[+] createInfo.ManifestAddress     = 0x7ff7160c78b0
[+] createInfo.ManifestSize        = 0x270
[>] peb                            = 0x0000000000F49000
[+] capture success!               = 0x0000000000010750
[-] CsrClientCallServer failed     = 0xC000002F              ??????????????????????????
[>] done!
2022-5-18 08:24
0
雪    币: 2564
活跃值: (13149)
能力值: ( LV12,RANK:312 )
在线值:
发帖
回帖
粉丝
9
幸苦
2022-5-18 08:54
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
通知那里不稳妥,是否可以考虑csrsrv!CsrCreateProcess
2022-5-18 09:09
0
雪    币: 1060
活跃值: (325)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
11
ntd11 通知那里不稳妥,是否可以考虑csrsrv!CsrCreateProcess
问题多半出在不同os版本导致的BASE_API_MSG不同。使用CsrClientCallServer的原因为它是native api。
2022-5-18 09:14
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
那这个就只是理论了,应用不了实践,后续工作很多,或者说,刚进入角色,还是考虑下稳妥方案
2022-5-18 09:37
0
雪    币: 5138
活跃值: (4925)
能力值: ( LV10,RANK:171 )
在线值:
发帖
回帖
粉丝
13
感谢分享
2022-5-18 09:46
0
雪    币: 1060
活跃值: (325)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
14
ntd11 那这个就只是理论了,应用不了实践,后续工作很多,或者说,刚进入角色,还是考虑下稳妥方案
本项目提供PoC, 而非解决方案(将来没有计划)。如果你所创建的进程不依赖SxS机制, 可以将宏IS_NOT_MINIMAL注释, 忽略与csrss通信。
2022-5-18 10:56
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
感谢分享
2022-5-18 11:11
0
雪    币: 19
活跃值: (312)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
感谢大佬的分享,有一个问题,在执行像是有回显命令的时候,这时需要自己创建一个管道来读取数据,但是在读取的时候会一直处于阻塞状态
2022-6-1 11:16
0
雪    币: 1060
活跃值: (325)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
17
小朋友呢 感谢大佬的分享,有一个问题,在执行像是有回显命令的时候,这时需要自己创建一个管道来读取数据,但是在读取的时候会一直处于阻塞状态
这是我没有遇到的情况, 可以私信我吗?
2022-6-6 00:09
0
雪    币: 1806
活跃值: (2120)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
用这个启动steam进程信息都成功了,但是进程自动退出了。。
2022-6-17 16:35
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册