调试CreateProcess

调试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);
相关推荐
AI行业学习2 小时前
.NET Framework 3.5 官方离线包下载+完整安装教程【2026.5.29】
windows·.net·notepad++
思麟呀2 小时前
C++工业级日志项目(七)日志器核心
linux·开发语言·c++·windows
影寂ldy2 小时前
C#List泛型集合
windows·c#·list
Li-Yongjun3 小时前
Linux 内核等待队列(Wait Queue)
linux·运维·windows
taiguisheng3 小时前
Docker中编译esp32
windows·docker·esp32
m0_617493944 小时前
【PySide6实战】QListView与QListWidget深度解析:从入门到进阶的完整指南
windows·pyside6
light blue bird4 小时前
Razor Pages工序管理Web端界面化实现方案
jvm·windows·web端
特立独行的猫a4 小时前
Fast DDS & Fast DDS Spy Windows x64 编译安装完全指南
windows·编译·安装·fastdds·fastddsspy
爱喝热水的呀哈喽4 小时前
多轮对话 gpt‘
运维·windows·python