最近以来, 使用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):
结合kernelbase和ntdll, 可以得出:
Stage1,5均在kernelbase!CreateProcessInternalW
中实现。而我们关注的NtCreateUserProcess
也是从CreateProcessInternalW
中调用的:
默认情况下, 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
进程才可以正常跑起来。
成功调用后, 如上文所述, 可以检查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
存在该导出函数。
二者对比, 发现路径上存在差异:
明显是版本不匹配, 一个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
而默认加载的Windows\System32
下的comctl32.dll
文件, 版本为5.82
:
问题的答案呼之欲出, 正是由于阶段5, 缺少通知Windows子系统csrss的信息, 导致SxS重定向机制没有生效。
参考Windows Internals 7th, 进程创建流程中的阶段5, 主要实现Windows子系统相关的初始化操作, 分为3个步骤:
这3个步骤, 主要围绕Native API: CsrClientCallServer
, 以及它的第一个参数, 结构体BASE_API_MSG
。
思路是这样的, 假设CsrClientCallServer
是唯一与Windows子系统通信的函数, 那么我们可以认为其参数涵盖了所有进程创建必备的信息。这样, 只要分析出正常进程时其参数各个字段的信息, 我们就有机会自行构造该参数, 自行告知Windows子系统, 使其相信我们是合法的。
我参考了以下资料:
CsrClientCallServer
的函数原型:
而有关BASE_API_MSG
结构体的相关信息较为过时, 经过亿点点逆向, 最终可以得出以下结构体:
伪造这样的结构体难以第一次就成功, 如果调用错误只有CsrClientCallServer
的NTSTATUS返回值能提供信息。为此还需要了解与Windows子系统的通信的基础概念与机制。
CsrClientCallServer
本质上使用ALPC进行通信,
在alpc通信模型中, 具有客户端(client)和服务端(server)。
利用windbg的!alpc
命令, 结合PortAddress
或结合EPROCESS
得到详细信息。调试下, 我们可以确认父进程确实与csrss.exe进行通信。
但依然不知道扔进去的数据, csrss是如何处理的。参考一些公开资料, 可以了解到csrss由csrsrv.dll
实现本体功能, 并由另外3个dll负责具体功能:
这三个都简单看下, 发现令人感兴趣的函数名
对csrss.exe进行内核态下的调试, 控制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
中为空。
有趣的是, 偏移+0x10上的值看起来是个地址, 但却不能follow。那么如果不是父进程, 只有一种可能性, 就是子进程的。此时子进程已经被NtCreateUserProcess
调用后被创建, 所以可以查看其内存布局:
而这正是notepad.exe的Manifest文件的数据, 偏移+0x18即为Manifest文件的长度。
于是, 将PS_CREATE_INFO.SuccessState
后返回的结果, 复制给CreateProcessMSG.Sxs
:
之后, 如果调用CsrClientCallServer
返回STATUS_ACCESS_DENIED
。欢迎来到最后一步!
正如上一小节所述, 优先分析InternalSxsCreateProcess
何处抛出的STATUS_ACCESS_DENIED
:
与正常情况对比:
异常情况:
发现没有读权限, 自然无法读子进程, 产生访问拒绝也不奇怪了。
往上溯源, 最终发现句柄在nt!NtCreateUserProcess
中被创建:
createProcCtx
作为PspCaptureCreateInfo
的返回值:
而默认情况下, 会or 0x100020
, 代表Sync | Execute/Traverse
。
所以, 我们需要在NtCreateUserProcess
的CreateInfo
参数中, 添加AdditionalFileAccess
:
至此, 所需的皆以满足:
正常创建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编辑
,原因: