调试CreateProcess
本文记录我调试CreateProcess函数的过程,从用户态到内核态。
示例代码
cpp
#include <Windows.h>
//创建进程
int main()
{
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
PROCESS_INFORMATION pi = { 0 };
CreateProcess(TEXT("C:\\Windows\\System32\\notepad.exe"), (PTCH)TEXT(" \"test.txt\""),
NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
return 0;
}
程序在64位win11上运行。
CreateProcessWThunk
在现代版本的Windows中,先前在kernel32模块中实现的函数,大多被移植到了kernelbase模块中实现,kernel32中只保存了对应的桩函数。比如对于这次我们调试的CreateProcessW函数,它在kernel32中:

由它调用kernelbase中的CreateProcessW函数:
txt
0:000> uf kernel32!CreateProcessWStub
KERNEL32!CreateProcessWStub:
00007ff8`1f3cc680 4c8bdc mov r11,rsp
00007ff8`1f3cc683 4883ec58 sub rsp,58h
00007ff8`1f3cc687 488b8424a8000000 mov rax,qword ptr [rsp+0A8h]
00007ff8`1f3cc68f 498943f0 mov qword ptr [r11-10h],rax
00007ff8`1f3cc693 488b8424a0000000 mov rax,qword ptr [rsp+0A0h]
00007ff8`1f3cc69b 498943e8 mov qword ptr [r11-18h],rax
00007ff8`1f3cc69f 488b842498000000 mov rax,qword ptr [rsp+98h]
00007ff8`1f3cc6a7 498943e0 mov qword ptr [r11-20h],rax
00007ff8`1f3cc6ab 488b842490000000 mov rax,qword ptr [rsp+90h]
00007ff8`1f3cc6b3 498943d8 mov qword ptr [r11-28h],rax
00007ff8`1f3cc6b7 8b842488000000 mov eax,dword ptr [rsp+88h]
00007ff8`1f3cc6be 89442428 mov dword ptr [rsp+28h],eax
00007ff8`1f3cc6c2 8b842480000000 mov eax,dword ptr [rsp+80h]
00007ff8`1f3cc6c9 89442420 mov dword ptr [rsp+20h],eax
00007ff8`1f3cc6cd 48ff158cde0400 call qword ptr [KERNEL32!_imp_CreateProcessW (00007ff8`1f41a560)]
00007ff8`1f3cc6d4 0f1f440000 nop dword ptr [rax+rax]
00007ff8`1f3cc6d9 4883c458 add rsp,58h
00007ff8`1f3cc6dd c3 ret
可以看到,CreateProcessWThunk只是简单调用了kernelbase!CreateProcessW函数,并没有多做任何处理。
CreateProcessW
txt
0:000> uf kernelbase!CreateProcessW
KERNELBASE!CreateProcessW:
00007ff8`1ddff320 4c8bdc mov r11,rsp
00007ff8`1ddff323 4883ec68 sub rsp,68h
00007ff8`1ddff327 498363f000 and qword ptr [r11-10h],0
00007ff8`1ddff32c 488b8424b8000000 mov rax,qword ptr [rsp+0B8h]
00007ff8`1ddff334 498943e8 mov qword ptr [r11-18h],rax
00007ff8`1ddff338 488b8424b0000000 mov rax,qword ptr [rsp+0B0h]
00007ff8`1ddff340 498943e0 mov qword ptr [r11-20h],rax
00007ff8`1ddff344 488b8424a8000000 mov rax,qword ptr [rsp+0A8h]
00007ff8`1ddff34c 498943d8 mov qword ptr [r11-28h],rax
00007ff8`1ddff350 488b8424a0000000 mov rax,qword ptr [rsp+0A0h]
00007ff8`1ddff358 498943d0 mov qword ptr [r11-30h],rax
00007ff8`1ddff35c 8b842498000000 mov eax,dword ptr [rsp+98h]
00007ff8`1ddff363 89442430 mov dword ptr [rsp+30h],eax
00007ff8`1ddff367 8b842490000000 mov eax,dword ptr [rsp+90h]
00007ff8`1ddff36e 89442428 mov dword ptr [rsp+28h],eax
00007ff8`1ddff372 4d894bb8 mov qword ptr [r11-48h],r9
00007ff8`1ddff376 4d8bc8 mov r9,r8
00007ff8`1ddff379 4c8bc2 mov r8,rdx
00007ff8`1ddff37c 488bd1 mov rdx,rcx
00007ff8`1ddff37f 33c9 xor ecx,ecx
00007ff8`1ddff381 e88a000000 call KERNELBASE!CreateProcessInternalW (00007ff8`1ddff410)
00007ff8`1ddff386 4883c468 add rsp,68h
00007ff8`1ddff38a c3 ret
它什么也没做,只是调用了CreateProcessInternalW。
CreateProcessInternalW
这个函数很庞大,它的主要工作是将CreateProcess参数转换为NtCreateUserProcess调用。我们只关注和逆向有关的部分。
有必要使用ida分析,先给出函数声明:
cpp
__int64 __fastcall CreateProcessInternalW(
void *a1,
const WCHAR *lpApplicationName,
const wchar_t *lpCommandLine,
__int64 lpProcessAttributes,
__int64 lpThreadAttributes,
int bInheritHandles,
unsigned int dwCreationFlags,
WCHAR *lpEnvironment,
WCHAR *lpCurrentDirectory,
__int64 lpStartupInfo,
__int64 lpProcessInformation)
首先它做了一大堆初始化工作,先不看,直接跳到下面这个地方:

这里主要是检查一些参数是否为空。
CreationFlags 验证

检查 Flag 的合法性,防止调试标志冲突。
调试器连接处理

在调试子进程时,Windows通过DbgUiConnectToDbg() 建立调试会话,这是用户态调试的基础。
随后CreateProcessInternalW又进行了一系列参数处理和信息搜集,它们会被放在一个类型为RTL_USER_PROCESS_PARAMETERS中的结构体中。
接着调用了NtCreateUserProcess:

NtCreateUserProcess
txt
0:000> uf .
ntdll!NtCreateUserProcess:
00007ff8`20943490 4c8bd1 mov r10,rcx
00007ff8`20943493 b8d1000000 mov eax,0D1h
00007ff8`20943498 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ff8`209434a0 7503 jne ntdll!NtCreateUserProcess+0x15 (00007ff8`209434a5) Branch
ntdll!NtCreateUserProcess+0x12:
00007ff8`209434a2 0f05 syscall
00007ff8`209434a4 c3 ret
ntdll!NtCreateUserProcess+0x15:
00007ff8`209434a5 cd2e int 2Eh
00007ff8`209434a7 c3 ret
这里执行了系统调用,调用号为0xD1,执行syscall陷入内核。
内核中的NtCreateUserProcess
通过系统服务调用,我们来到了ntoskrnl.exe中的NtCreateUserProcess中:
asm
0: kd> k
# Child-SP RetAddr Call Site
00 fffff585`ccb65a68 fffff801`956bc555 nt!NtCreateUserProcess
01 fffff585`ccb65a70 00007ff9`ced836b4 nt!KiSystemServiceCopyEnd+0x25
02 000000b9`644fdc38 00007ff9`cb6db82d 0x00007ff9`ced836b4
03 000000b9`644fdc40 00000000`00000020 0x00007ff9`cb6db82d
04 000000b9`644fdc48 000000b9`644fe5d0 0x20
05 000000b9`644fdc50 000000b9`00000001 0x000000b9`644fe5d0
06 000000b9`644fdc58 000000b9`644fe014 0x000000b9`00000001
07 000000b9`644fdc60 00000000`00000000 0x000000b9`644fe014
cpp
__int64 __fastcall NtCreateUserProcess(
HANDLE *a1,
__int64 a2,
int a3,
int a4,
__int64 a5,
__int64 a6,
unsigned int a7,
unsigned int a8,
__int64 a9,
__int64 a10,
__int64 a11);
| 参数 | 实际含义 | 说明 |
|---|---|---|
a1 |
ProcessHandle |
输出:进程句柄 |
a2 |
ThreadHandle |
输出:线程句柄 |
a3 |
ProcessDesiredAccess |
进程访问权限掩码 |
a4 |
ThreadDesiredAccess |
线程访问权限掩码 |
a5 |
ObjectAttributes |
进程属性 |
a6 |
InheritObjectAttributes |
继承属性 |
a7 |
ProcessCreationFlags |
创建标志 |
a8 |
ThreadCreationFlags |
线程创建标志 |
a9 |
ProcessParameters |
RTL_USER_PROCESS_PARAMETERS |
a10 |
CreateInfo |
PS_CREATE_INFO |
a11 |
AttributeList |
属性列表 |
这个函数执行了下面的步骤:
1. 参数验证

内核必须验证所有用户态传入的指针:
cpp
// 第197-198行:检查无效标志
v14 = a7;
if ((a7 & 0xFFB17838) != 0 || (a8 & 0xFFFFFFFE) != 0)
return 0xC000000D; // STATUS_INVALID_PARAMETER
// 第200行:禁止同时设置 DEBUG_PROCESS(0x8000) + DEBUG_ONLY_THIS_PROCESS(0x400)
if ((a7 & 0x8400) == 0x8400)
return 0xC0000070; // STATUS_INVALID_PARAMETER_MIX
cpp
// 第203-213行:PreviousMode 检查 + 用户态指针 Probe
if (PreviousMode) {
// 用户态:通过 "probing read" 检查 a1 和 a2 指针的可访问性
v16 = 0x7FFFFFFF0000; // 用户态地址上限
if ((ULONG_PTR)v97 < 0x7FFFFFFF0000)
v16 = v97;
*(_QWORD *)v16 = *(_QWORD *)v16; // ProbeForWrite效果:通过尝试读写来检查用户态地址的合法性。
v17 = 0x7FFFFFFF0000;
if ((ULONG_PTR)v96 < 0x7FFFFFFF0000)
v17 = v96;
*(_QWORD *)v17 = *(_QWORD *)v17;
}
2. 创建上下文
cpp
// 第225-232行:属性列表解析
if (a11) {
result = PspBuildCreateProcessContext(a11, PreviousMode, 0, &v108);
// v108 = PspCreateProcessContext 结构体 → 包含所有安全策略
if (result < 0) return result;
v76 = v131 != 0; // 是否有用户上下文(CONTEXT 结构)
}
这里解析用户态传入的属性列表(PROC_THREAD_ATTRIBUTE_LIST),其中包含:
- 缓解策略(Mitigation Policies)
- 父进程
- 安全令牌等
PROC_THREAD_ATTRIBUTE_LIST结构
3. 打开映像文件
cpp
// 第349-364行:打开 PE 文件!
ProcessProtection = IoCreateFileEx(
&FileHandle,
v123 | 0x100020, // FILE_EXECUTE | SYNCHRONIZE | FILE_READ_DATA
&ObjectAttributes, // 指向文件路径
&IoStatusBlock,
nullptr,
0x80u, // FILE_ATTRIBUTE_NORMAL
5u, // FILE_SHARE_READ | FILE_SHARE_DELETE (没有共享写!)
1u, // FILE_OPEN
0x60u, // FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE
nullptr, 0,
CreateFileTypeNone,
nullptr, 0,
&DriverContext); // ECP (Extra Create Parameters) 上下文
// 第365-381行:如果第一次失败,尝试降级访问
if (ProcessProtection < 0 && v123) {
ProcessProtection = IoCreateFileEx(
&FileHandle, 0x100020u, // 只用 FILE_EXECUTE | SYNCHRONIZE
...);
}
4. 创建Section对象
这是核心步骤------将 PE 文件映射为内存Section:
cpp
// 第461-467行:创建映像 Section
ProcessProtection = MmCreateSpecialImageSection(
(unsigned int)Handle, // 输出 Section 句柄
(unsigned int)&ObjectAttributes,
(_DWORD)v84, // 令牌
v35, // 签名级别
(__int64)FileHandle, // 文件句柄
v38); // 标志
5. 进程对象分配
cpp
// 第550-565行:创建 EPROCESS 对象
ProcessProtection = PspAllocateProcess(
v85, // 父进程 EPROCESS
PreviousMode,
v94, // 线程访问掩码
v43, // 签名级别(保护级别)
v30, // 签名级别
SHIBYTE(v74), // 签名类型
v127, // Section 对象指针
v122, // 令牌
a7, // 创建标志
0,
&v108, // 创建上下文
v121 != 0, // 是否指定了父进程句柄
v91, // 内存分区上下文
&v82, // 输出:是否需要调试通知
&SystemArgument1[1]); // 输出:新 EPROCESS 地址
PspAllocateProcess 内部完成:
- ObCreateObject → 分配 EPROCESS 对象
- PspInitializeProcessSecurity → 设置安全描述符
- MmInitializeProcessAddressSpace → 初始化页表、地址空间
- KeInitializeProcess → 内核调度初始化
- 复制父进程的句柄表(如果继承)
6. 创建线程
cpp
// 第630-639行:创建初始线程
ProcessProtection = PspAllocateThread(
*(ULONG_PTR *)&SystemArgument1[1], // EPROCESS
v59, // CONTEXT (线程上下文)
v105, // 线程起始地址
0, 0,
SystemArgument1, // 创建标志
&v93, // 输出:线程对象
v56, // 栈信息
v142); // 访问状态
7. 插入进程与线程
cpp
// 第673行:将 EPROCESS 插入全局进程链表
inserted = PspInsertProcess(
*(PVOID *)&SystemArgument1[1], // EPROCESS
v120, // 父进程 ID
v83[0], // 标志
v68, // Silo
v138); // 访问状态
// 第674行:插入线程
ProcessProtection = PspInsertThread(
v93, // 线程对象
v67, // 线程访问掩码
v56, // 栈
&v108, // 创建上下文
0, v142,
v96, // 线程句柄
v112);
8. 返回进程句柄
cpp
// 第690行:创建进程句柄返回用户态
ProcessProtection = PspCreateObjectHandle(
*(_QWORD *)&SystemArgument1[1], // EPROCESS
v138, // 访问状态
PsProcessType); // 类型
// 第705行:写入用户态句柄
*v97 = v141; // 写回进程句柄!
// 第707行:更新创建状态
PspUpdateCreateInfo(6, &v108, *(_QWORD *)&SystemArgument1[1]);
// 第720行:如果出错,终止进程
if (ProcessProtection < 0)
PsTerminateProcess(*(_QWORD *)&SystemArgument1[1], ProcessProtection);