进程线程创建过程

本文详细解析了Windows系统中进程和线程的创建过程。从PspCreateProcess函数开始,阐述了如何创建进程对象、初始化内存、调试端口、异常端口等,并介绍了线程创建的细节,包括PspCreateThread函数中的用户线程和系统线程创建步骤。此外,还概述了从用户模式的CreateProcessW函数到内核模式的NtCreateProcessEx和NtCreateThread的转换,以及进程线程在Windows子系统中的初始化工作。
摘要由CSDN通过智能技术生成

一、进程创建过程

所有进程都通过 PspCreateProcess 函数创建,包括 System 进程。它被三个函数调用,分别是NtCreateProcessEx、PsCreateSystemProcess 和 PspInitPhase0 。

NtCreateProcessEx 是 CreateProcess 的内核服务;

PspInitPhase0 函数是系统初始化早期调用的,它创建了 System 进程,System 进程的句柄保存在全局变量 PspInitialSystemProcessHandle 中,EPROCESS 保存在 PsInitialSystemProcess 中;

PsCreateSystemProcess 是用来创建系统进程的,它创建的进程都是 PsInitialSystemProcess 的子进程。

PspCreateProcess 的大致流程在136-140页分析了。这里简单介绍一下,完整代码放在文末。

  • 创建进程对象
  • 初始化进程对象
  • 初始化内存区对象 SectionObject
  • 初始化调试端口
  • 初始化异常端口
  • 创建进程地址空间
  • 初始化进程基本优先级,CPU亲和性,页目录基址,超空间页帧号
  • 初始化进程安全属性(从父进程复制令牌)
  • 设置优先级类别
  • 初始化句柄表
  • 初始化进程地址空间
  • 创建进程ID
  • 审计此次进程创建行为
  • 作业相关操作
  • 创建或复制PEB
  • 新进程对象插入 PsActiveProcessHead 链表
  • 新进程对象插入当前进程句柄表
  • 计算新进程的基本优先级和时限重置值
  • 设置进程访问权限,当前进程句柄可访问,允许进程终止
  • 设置进程创建时间

PspCreateProcess 创建了进程,此时进程中的代码还没运行起来,进程还是死的,因为此时还没有线程,接下来介绍线程的创建过程。

二、线程创建过程

NtCreateThread 和 PsCreateSystemThread 函数会调用 PspCreateThread ,分别用于创建用户线程和系统线程。

下面简单概括 PspCreateThread 创建线程的工作,可能有遗漏或错误。

  • 获取当前CPU模式
  • 获取进程对象
  • 创建线程对象并初始化为0
  • 设置父进程
  • 创建CID
  • 初始化线程结构的部分属性
  • 初始化定时器链表
  • 如果是用户线程,创建并初始化TEB,用 ThreadConatext 初始化R0 R3的入口点
  • 如果是系统线程,用 StartRoutine 初始化R0入口点
  • 不管是用户线程还是内核线程,都调用 KeInitThread 初始化 Header, WaitBlock, ServiceTable, APC,定时器,内核栈等属性
  • 进程的活动线程计数加1
  • 新线程加入进程的线程链表
  • 调用 KeStartThread 初始化剩余的域,主要是和调度相关的优先级、时限、CPU亲和性等
  • 如果是该进程的第一个线程,则触发该进程的创建通知
  • 工作集相关的操作
  • 线程对象的引用计数加2,一个针对当前的创建操作,一个针对要返回的线程句柄
  • 如果 CreateSuspended 为真,指示新线程立即被挂起
  • 根据指定的期望访问权限,调用 SeCreateAccessStateEx 创建一个访问状态结构
  • 把新线程对象插入到当前进程的句柄表
  • 设置输出参数 ThreadHandle
  • 设置输出参数 ClientId
  • 设置线程创建时间
  • 设置线程访问权限
  • 新线程加入就绪链表,等待调度;或者此时进程不在内存中,设置新线程状态为转移
  • 引用计数减1

PspCreateThread 函数返回后,新线程随时可以被调度执行。

三、创建进程的全貌

进程创建后才创建线程,但是 PspCreateProcess 函数中根本没有创建线程的动作,也没有打开进程可执行映像文件的代码。在WRK中,我们看不到完整的进程创建过程,这是不行的!下面从 CreateProcess 函数调用开始,分析进程创建的全过程。

实际上,0环的东西已经分析过了,我们只需要分析3环 CreateProcessW 进0环之前干了啥就行。

CreateProcessW 本身没干啥,而是调用了 CreateProcessInternalW 函数,源码见文末。

CreateProcessInternalW 函数在 base\win32\client\process.c,总共 2582 行C代码,我看不懂,主要是不知道怎么把文件名转化成 SectionHandle 的,代码实在太多了,而且有很多看不懂的操作。

BOOL
WINAPI
CreateProcessInternalW(
    HANDLE hUserToken,
    LPCWSTR lpApplicationName,
    LPWSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCWSTR lpCurrentDirectory,
    LPSTARTUPINFOW lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation,
    PHANDLE hRestrictedUserToken
    )

CreateProcessInternalW 打开指定的可执行文件,并创建一个内存区对象,注意,内存区对象并没有被映射到内存中(由于目标进程尚未创建,不可能完成内存映射),但它确实是打开了。

接下来就是调用 ntdll 的 NtCreateProcessEx,通过系统调用,CPU模式变成内核模式,进入0环 KiSystemService / KiFastCallEntry 分发函数,然后调用执行体的 NtCreateProcessEx 函数。

接下来的工作我们先前已经分析过了,这里再次过一遍。NtCreateProcessEx 函数执行前面介绍的进程创建逻辑,包括创建并初始化 EPROCESS 对象,创建初始的进程地址空间,创建和初始化句柄表,设置 EPROCESS KPROCESS 的各种属性,如进程优先级,安全属性,创建时间等。到这里,执行体层的进程对象已经建立,进程地址空间已经初始化,PEB也已初始化。

接下来是创建线程,首先需要构造一个栈,和一个上下文环境。栈的大小通过映像文件获得,创建线程通过 ntdll 的 NtCreateThread 调用执行体的 NtCreateThread 完成,具体工作就是先前介绍过的,包括创建并初始化 ETHREAD,生成线程ID,建立TEB和设置线程安全性。

进程的第一个线程启动函数是 kernel32 的 BaseProcessStart ,这里创建的线程并不会立即执行,而是要等进程完全初始化后才执行。

到此为止,从内核角度来看,进程对象和第一个线程对象已经建立起来了,但是对子系统而言,进程创建才刚刚开始。kernel32 给windows子系统发送一个消息,消息中包括进程和线程的句柄、进程创建者的ID等必要信息。windows子系统 csrss.exe 接收到此消息,执行以下操作:

  • 保留一份句柄
  • 设定新进程的优先级类别
  • 在子系统中分配一个内部进程块
  • 设置新进程的异常端口,从而子系统可以接收到该进程中发生的异常
  • 对于被调试进程,设置它的调试端口,从而子系统可以接收到该进程的调试事件
  • 分配并初始化一个内部线程块,并插入到进程的线程列表中
  • 窗口会话中的进程计数加1
  • 设置进程的停机级别位默认级别
  • 将新进程插入到子系统的进程列表中
  • 分配并初始化一块内存供子系统的内核模式部分使用(W32PROCESS结构)
  • 显示应用程序启动光标

到此为止,进程环境已经建好,其线程将要使用的资源也分配好了,windows子系统已经知道并登记了此进程和线程。所以,初始线程被恢复执行,余下部分的初始化工作是在初始线程在新进程环境中完成的。在内核中,新线程启动的例程是 KiThreadStartup 函数,这是在 PspCreateThread 函数中调用 KeInitThread 时,KeInitThread 函数又调用 KiInitializeContextThread 函数来设置的。

cPublicProc _KiThreadStartup ,1
xor ebx,ebx ; clear registers
xor esi,esi ;
xor edi,edi ;
xor ebp,ebp ;
LowerIrql APC_LEVEL ; KeLowerIrql(APC_LEVEL)
pop eax ; (eax)->SystemRoutine
call eax ; SystemRoutine(StartRoutine, StartContext)
pop ecx ; (ecx) = UserContextFlag
or ecx, ecx
jz short kits10 ; No user context, go bugcheck
mov ebp,esp ; (bp) -> TrapFrame holding UserContext
jmp _KiServiceExit2
kits10: stdCall _KeBugCheck, <NO_USER_MODE_CONTEXT>
stdENDP _KiThreadStartup

KiThreadStartup 函数首先将 IRQL 降低到 APC_LEVEL,然后调用系统初始的线程函数 PspUserThreadStartup (PspCreateThread 函数在调用KeInitThread 时指定的,如果是创建系统线程,这里就是 PspSystemThreadStartup 函数)。线程启动函数被作为一个参数传递给 PspUserThreadStartup ,此处应是 kernel32 的 BaseProcessStart。

PspUserThreadStartup 函数设置异步函数调用APC机制,基本流程如下:

  • 获得当前线程和进程对象。

  • 是否由于创建过程中出错而需要终止本线程。

  • 如果需要,通知调试器。

  • 如果这是进程中的第一个线程,则判断系统是否支持应用程序预取的特性,如果
    是,则通知缓存管理器预取可执行映像文件中的页面(见2 106 行的CcPfBeginAppLaunch
    调用)。所谓应用程序预取,是指将该进程上一次启动的前10 s 内引用到的页面直接读
    入到内存中。

  • 然后,PspUserThreadStartup 把一个用户模式APC 插入到线程的用户APC 队列中,
    此APC 例程是在全局变量PspSystemDll 中指定的,指向ntdll.dll 的LdrInitializeThunk 函数。

  • 接下来填充系统范围的一个Cookie 值。

PspUserThreadStartup 返回后,KiThreadStartup 函数返回到用户模式,此时,PspUserThreadStartup 插入的APC 被交付,于是 LdrInitializeThunk 函数被调用,这是映像加载器(image loader)的初始化函数,完成加载器,堆管理器等初始化工作,然后加载必要的dll,并调用它们的入口函数。最后,当 LdrInitializeThunk 返回到用户模式 APC 分发器时,该线程开始在用户模式下执行,调用应用程序指定的线程启动函数,此启动函数的地址已经在APC交付时被压到用户栈中。

至此,进程创建完毕,开始执行用户空间中的代码。

本文涉及的函数源码

PspCreateProcess

NTSTATUS
PspCreateProcess(
    OUT PHANDLE ProcessHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ParentProcess OPTIONAL,
    IN ULONG Flags,
    IN HANDLE SectionHandle OPTIONAL,
    IN HANDLE DebugPort OPTIONAL,
    IN HANDLE ExceptionPort OPTIONAL,
    IN ULONG JobMemberLevel
    )

/*++

Routine Description:

    This routine creates and initializes a process object.  It implements the
    foundation for NtCreateProcess and for system initialization process
    creation.
    这个函数创建并初始化一个进程对象。NtCreateProcess 会调用它;系统进程初始化也会调用它。

Arguments:

    ProcessHandle - Returns the handle for the new process.
    输出参数,返回新进程句柄

    DesiredAccess - Supplies the desired access modes to the new process.
    期望对新进程的访问权限

    ObjectAttributes - Supplies the object attributes of the new process.
    新进程的对象属性

    ParentProcess - Supplies a handle to the process' parent process.  If this
                    parameter is not specified, then the process has no parent
                    and is created using the system address space.
    指定父进程句柄。如果未指定,说明没有父进程,那就是用系统地址空间创建进程。

    Flags         - Process creation flags
    进程创建标志

    SectionHandle - Supplies a handle to a section object to be used to create
                    the process' address space.  If this parameter is not
                    specified, then the address space is simply a clone of the
                    parent process' address space.
    提供一个内存区对象句柄用来创建进程地址空间。如果此参数未指定,则简单地复制父进程
    的地址空间。

    DebugPort - Supplies a handle to a port object that will be used as the
                process' debug port.
    调试端口句柄

    ExceptionPort - Supplies a handle to a port object that will be used as the
                    process' exception port.
    异常端口句柄

    JobMemberLevel - Level for a create process in a jobset
    工作集等级
--*/

{
   

    NTSTATUS Status;
    PEPROCESS Process;
    PEPROCESS CurrentProcess;
    PEPROCESS Parent;
    PETHREAD CurrentThread;
    KAFFINITY Affinity;
    KPRIORITY BasePriority;
    PVOID SectionObject;
    PVOID ExceptionPortObject;
    PVOID DebugPortObject;
    ULONG WorkingSetMinimum, WorkingSetMaximum;
    HANDLE LocalProcessHandle;
    KPROCESSOR_MODE PreviousMode;
    INITIAL_PEB InitialPeb;
    BOOLEAN CreatePeb;
    ULONG_PTR DirectoryTableBase[2];
    BOOLEAN AccessCheck;
    BOOLEAN MemoryAllocated;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    SECURITY_SUBJECT_CONTEXT SubjectContext;
    NTSTATUS accesst;
    NTSTATUS SavedStatus;
    ULONG ImageFileNameSize;
    HANDLE_TABLE_ENTRY CidEntry;
    PEJOB Job;
    PPEB Peb;
    AUX_ACCESS_DATA AuxData;
    PACCESS_STATE AccessState;
    ACCESS_STATE LocalAccessState;
    BOOLEAN UseLargePages;
    SCHAR QuantumReset;
#if defined(_WIN64)
    INITIAL_PEB32 InitialPeb32;
#endif

    PAGED_CODE();

    // 获取当前线程、CPU模式、当前进程
    CurrentThread = PsGetCurrentThread ();
    PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);
    CurrentProcess = PsGetCurrentProcessByThread (CurrentThread);

    CreatePeb = FALSE;
    UseLargePages = FALSE;
    DirectoryTableBase[0] = 0;
    DirectoryTableBase[1] = 0;
    Peb = NULL;
    
    //
    // Reject bogus create parameters for future expansion
    // 如果 Flags 里的保留位被置1,就是非法参数
    if (Flags&~PROCESS_CREATE_FLAGS_LEGAL_MASK) {
   
        return STATUS_INVALID_PARAMETER;
    }

    //
    // Parent
    // 检查有无指定父进程
    // 

    if (ARGUMENT_PRESENT (ParentProcess)) {
   
        // 如果指定了父进程句柄,就获取它的EPROCESS
        Status = ObReferenceObjectByHandle (ParentProcess,
                                            PROCESS_CREATE_PROCESS,
                                            PsProcessType,
                                            PreviousMode,
                                            &Parent,
                                            NULL);
        if (!NT_SUCCESS (Status)) {
   
            return Status;
        }

        // 工作集相关
        if (JobMemberLevel != 0 && Parent->Job == NULL) {
   
            ObDereferenceObject (Parent);
            return STATUS_INVALID_PARAMETER;
        }

        // 继承父进程的CPU亲和性
        Affinity = Parent->Pcb.Affinity;
        // 用全局变量初始化工作集最大最小值
        WorkingSetMinimum = PsMinimumWorkingSet;
        WorkingSetMaximum = PsMaximumWorkingSet;


    } else {
   
        // 没有父进程
        Parent = NULL;
        Affinity = KeActiveProcessors;
        WorkingSetMinimum = PsMinimumWorkingSet;
        WorkingSetMaximum = PsMaximumWorkingSet;
    }

    //
    // Create the process object
    // 创建进程对象 EPROCESS
    //
    Status = ObCreateObject (PreviousMode,
                             PsProcessType,
                             ObjectAttributes,
                             PreviousMode,
                             NULL,
                             sizeof (EPROCESS),
                             0,
                             0,
                             &Process);

    if (!NT_SUCCESS (Status)) {
   
        goto exit_and_deref_parent;
    }

    //
    // The process object is created set to NULL. Errors
    // That occur after this step cause the process delete
    // routine to be entered.
    // EPROCESS 数据清零。此后发生的错误会导致调用进程删除函数
    //
    // Teardown actions that occur in the process delete routine
    // do not need to be performed inline.
    //

    RtlZeroMemory (Process, sizeof(EPROCESS));
    ExInitializeRundownProtection (&Process->RundownProtect);
    PspInitializeProcessLock (Process);
    InitializeListHead (&Process->ThreadListHead);

#if defined(_WIN64)

    if (Flags & PROCESS_CREATE_FLAGS_OVERRIDE_ADDRESS_SPACE) {
   
        PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_OVERRIDE_ADDRESS_SPACE);
    }

#endif

    PspInheritQuota (Process, Parent);
    ObInheritDeviceMap (Process, Parent);
    if (Parent != NULL) {
   
        Process->DefaultHardErrorProcessing = Parent->DefaultHardErrorProcessing;
        Process->InheritedFromUniqueProcessId = Parent->UniqueProcessId;

    } else {
   
        Process->DefaultHardErrorProcessing = PROCESS_HARDERROR_DEFAULT;
        Process->InheritedFromUniqueProcessId = NULL;
    }

    //
    // Section
    //

    if (ARGUMENT_PRESENT (SectionHandle)) {
   
        // 如果指定了内存区对象句柄参数 SectionHandle,就获取内存区对象
        Status = ObReferenceObjectByHandle (SectionHandle,
                                            SECTION_MAP_EXECUTE,
                                            MmSectionObjectType,
                                            PreviousMode,
                                            &SectionObject,
                                            NULL);
        if (!NT_SUCCESS (Status)) {
   
            goto exit_and_deref;
        }

    } else {
   
        // SectionHandle 参数为NULL,要看父进程是不是 System 进程

        SectionObject = NULL;

        
        if (Parent != PsInitialSystemProcess) {
   
            // 如果父进程不是 System 进程 ,那么内存区对象继承自父进程
            //
            // Fetch the section pointer from the parent process
            // as we will be cloning. Since the section pointer
            // is removed at last thread exit we need to protect against
            // process exit here to be safe.
            //

            if (ExAcquireRundownProtection (&Parent->RundownProtect)) {
   
                SectionObject = Parent->SectionObject;
                if (SectionObject != NULL) {
   
                    ObReferenceObject (SectionObject);
                }

                ExReleaseRundownProtection (&Parent->RundownProtect);
            }

            if (SectionObject == NULL) {
   
                Status = STATUS_PROCESS_IS_TERMINATING;
                goto exit_and_deref;
            }
        }

        // 如果父进程是 System 进程,那么 SectionObject 就是 NULL
    }

    // 内存区对象 SectionObject 初始化完成(如果是NULL则表示父进程是System进程)
    Process->SectionObject = SectionObject;

    //
    // DebugPort
    // 调试端口初始化

    if (ARGUMENT_PRESENT (DebugPort)) {
   
        Status = ObReferenceObjectByHandle (DebugPort,
                                            DEBUG_PROCESS_ASSIGN,
                                            DbgkDebugObjectType,
                                            PreviousMode,
                                            &DebugPortObject,
                                            NULL);

        if (!NT_SUCCESS (Status)) {
   
            goto exit_and_deref;
        }

        Process->DebugPort = DebugPortObject;
        if (Flags&PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT) {
   
            PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_NO_DEBUG_INHERIT);
        }

    } else {
   
        if (Parent != NULL) {
   
            DbgkCopyProcessDebugPort (Process, Parent);
        }
    }

    //
    // ExceptionPort
    // 异常端口初始化

    if (ARGUMENT_PRESENT (Exception
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值