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

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

2022-5-14 22:05
26793

最近以来, 使用Native API绕过AV/EDR的讨论似乎总保持着一定的热度, HellsGate后的各种衍生Gate, Syswhispers也从2进化到了3。这类技术的本质上比较相似, 动态获取目标功能号后, 再调用syscall指令从而native call。但发现一个现象, 研究者们似乎更倾向于使用NtCreateThread而非NtCreateProcess来验证他们的代码有效性。于是, 今年年初开始, 我开始针对进程创建的native call进行一系列研究。

进程创建的native call也并非崭新的话题, 需要4个native API即可满足需求:

BlackHat 2017中的Process Doppelganging技术就是基于此诞生的。

而时至今日, NtCreateProcess在Win10时代只在系统进程创建时被调用, 用户进程的创建使用NtCreateUserProcess。这就是本文的主题, 围绕NtCreateUserProcess创建一个正常用户进程。

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

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

pic1

结合kernelbase和ntdll, 可以得出:

pic2

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

pic3

默认情况下, ThreadFlags被赋值为1, 代表创建后线程暂停。

借助processhacker提供的头文件, NtCreateUserProcess的原型为:

这里我们关注最后3个参数:

该参数的构建, 需要依靠另一个Native API: RtlCreateProcessParametersEx

其中参数2ImagePathName为目标进程的Imag路径, 传入时该字符串必须以\\??\\为前缀。

函数返回值保存在参数1pProcessParameters。子进程的PEB的字段ProcessParameters保存此信息。

函数用法与在NtCreateProcess时期相同, 直接使用即可。

结构体信息来自processhacker:

这是个大有用处的结构体, 由状态, 结构体长度, 以及与状态对应union组成, 有2个状态是我们需要的:

初始化, 最简化的情况下, 只需要填入状态和大小即可:

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

属性列表, 结构体信息:

使用时, 根据TotalLength手动调整Attributes数组的大小。每个PS_ATTRIBUTE在x64下大小为0x20, 其中字段Attribute由宏PsAttributeValue提供:

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

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

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

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

pic5

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

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

我们创建子进程notepad.exe:

pic4

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

我们以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

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

明显是版本不匹配, 一个5.82一个6.0。推测存在一种机制, 被创建的进程有能力指定加载DLL的版本。而路径中的WinSxS已经提示与SxS重定向机制相关。

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

pic10

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

pic11

而默认加载的Windows\System32下的comctl32.dll文件, 版本为5.82:

pic12

问题的答案呼之欲出, 正是由于阶段5, 缺少通知Windows子系统csrss的信息, 导致SxS重定向机制没有生效。

参考Windows Internals 7th, 进程创建流程中的阶段5, 主要实现Windows子系统相关的初始化操作, 分为3个步骤:

这3个步骤, 主要围绕Native API: CsrClientCallServer, 以及它的第一个参数, 结构体BASE_API_MSG

pic13

思路是这样的, 假设CsrClientCallServer是唯一与Windows子系统通信的函数, 那么我们可以认为其参数涵盖了所有进程创建必备的信息。这样, 只要分析出正常进程时其参数各个字段的信息, 我们就有机会自行构造该参数, 自行告知Windows子系统, 使其相信我们是合法的。

我参考了以下资料:

CsrClientCallServer的函数原型:

而有关BASE_API_MSG结构体的相关信息较为过时, 经过亿点点逆向, 最终可以得出以下结构体:

伪造这样的结构体难以第一次就成功, 如果调用错误只有CsrClientCallServer的NTSTATUS返回值能提供信息。为此还需要了解与Windows子系统的通信的基础概念与机制。

CsrClientCallServer本质上使用ALPC进行通信,

pic14

在alpc通信模型中, 具有客户端(client)和服务端(server)。

pic15

利用windbg的!alpc命令, 结合PortAddress或结合EPROCESS得到详细信息。调试下, 我们可以确认父进程确实与csrss.exe进行通信。

但依然不知道扔进去的数据, csrss是如何处理的。参考一些公开资料, 可以了解到csrss由csrsrv.dll实现本体功能, 并由另外3个dll负责具体功能:

这三个都简单看下, 发现令人感兴趣的函数名

对csrss.exe进行内核态下的调试, 控制alpc的调用, 我们可以得到以下执行逻辑:

pic_alpc

sxssrv!InternalSxsCreateProcess实现了与Windows子系统相关的进程创建的具体逻辑。

这样, 当调用CsrClientCallServer失败时, 我们有了可以优先考虑并分析的函数。

回到notepad.exe的345错误。先调试正常创建的notepad.exe, 在LdrInitializeThunk断下, 此时PEB.ActivationContextData字段已经有值了, 说明该字段由父进程创建有关。将该字段置0, F9, 会出现STATUS_ORDINAL_NOT_FOUND一样的报错。

我们可以推测该字段的缺失正是SxS机制没有起作用的表现。所以, 只要我们找到正常创建进程下, 何时子进程的PEB中诞生了该字段的值, 就能找到根本原因。

仍然需要通过对正常进程创建时的BASE_API_MSG分析。

注意到在BASE_SXS_STREAM的偏移+0x10和偏移+0x18处有值, 而在伪造的BASE_API_MSG中为空。

pic16

有趣的是, 偏移+0x10上的值看起来是个地址, 但却不能follow。那么如果不是父进程, 只有一种可能性, 就是子进程的。此时子进程已经被NtCreateUserProcess调用后被创建, 所以可以查看其内存布局:

pic17

而这正是notepad.exe的Manifest文件的数据, 偏移+0x18即为Manifest文件的长度。

于是, 将PS_CREATE_INFO.SuccessState后返回的结果, 复制给CreateProcessMSG.Sxs:

之后, 如果调用CsrClientCallServer返回STATUS_ACCESS_DENIED。欢迎来到最后一步!

正如上一小节所述, 优先分析InternalSxsCreateProcess何处抛出的STATUS_ACCESS_DENIED:

pic18

与正常情况对比:

pic19

异常情况:

pic20

发现没有读权限, 自然无法读子进程, 产生访问拒绝也不奇怪了。

往上溯源, 最终发现句柄在nt!NtCreateUserProcess中被创建:

pic21

createProcCtx作为PspCaptureCreateInfo的返回值:

pic22

而默认情况下, 会or 0x100020, 代表Sync | Execute/Traverse

所以, 我们需要在NtCreateUserProcessCreateInfo参数中, 添加AdditionalFileAccess:

至此, 所需的皆以满足:

pic23

正常创建notepad.exe成功!

完整的项目地址: Direct-NtCreateUserProcess

本文至此就要结束了。NtCreateUserProcess之前存在公开代码(Microwave89), 可惜无法正常工作, 但是作者提供了一种可行的蓝图。本项目中进程创建过程中的API皆为Native API, 理论上下一步可以全部使用Syswhisper3等Direct Syscall技术以实现Bypass AV/EDR。
另外, BASE_API_MSG对于红队来讲, 似乎还有可挖掘的空间, 期待后续对此的研究。

参考文章以及代码:

 
 
 
 
 
 
 
 
 
 
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
);
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
);
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
);
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
);
 
 
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;
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;
状态 说明
InitState 0 调用`NtCreateUserProcess时作为参数, 应设置为该状态
SuccessState 6 调用`NtCreateUserProcess后, 成功时为该状态
 
createInfo.State = PsCreateInitialState;
createInfo.Size = sizeof(PS_CREATE_INFO);
createInfo.State = PsCreateInitialState;

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

最后于 2022-5-16 08:44 被D0pam1ne编辑 ,原因:
收藏
免费 15
支持
分享
最新回复 (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
雪    币: 2197
活跃值: (12709)
能力值: ( 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
雪    币: 4962
活跃值: (4723)
能力值: ( 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
活跃值: (272)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
感谢大佬的分享,有一个问题,在执行像是有回显命令的时候,这时需要自己创建一个管道来读取数据,但是在读取的时候会一直处于阻塞状态
2022-6-1 11:16
0
雪    币: 1060
活跃值: (325)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
17
小朋友呢 感谢大佬的分享,有一个问题,在执行像是有回显命令的时候,这时需要自己创建一个管道来读取数据,但是在读取的时候会一直处于阻塞状态
这是我没有遇到的情况, 可以私信我吗?
2022-6-6 00:09
0
雪    币: 1607
活跃值: (1915)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
用这个启动steam进程信息都成功了,但是进程自动退出了。。
2022-6-17 16:35
0
游客
登录 | 注册 方可回帖
返回
//