CreateProcess函数源码分析

CreateProcess函数源码分析

​ 源码版本:Windows 2003 源码

​ 源码阅读工具:Source Insight

函数功能分析

函数原型

BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

函数详细文档:CreateProcessA function (processthreadsapi.h) - Win32 apps |微软文档 (microsoft.com)

创建进程流程

总结:创建进程共六个阶段
一、第一阶段:打开目标映像文件,创建文件映射区(NtOpenFile,NtCreateSection)
二、第二阶段:创建进程内核对象和进程地址空间,此时创建的进程只是一个容器
1.ObCreateObject创建内核对象
2.MmCreateProcessAddressSpace创建地址空间
3.KeInitializeProcess初始化新进程内核对象
4.ObInitProcess初始化进程句柄表
5.MmInitializeProcessAddressSpace->MmMapViewOfSection将进程模块映射到地址空间
6.PspMapSystemDll->MmMapViewOfSection映射系统模块ntdll.dll
7.创建进程ID,调用ExCreateHandle把该内核对象加入到进程线程对象表(PspCidTable)中
8.完成EPROCESS创建,将其挂入进程队列并插入创建者的句柄表
9.调用MmCreatePeb创建一个PEB
10.把新进程加入全局的进程链表中:PsActiveProcessHead
11.调用ObInsertObject把新进程对象插入到当前进程的句柄表中,获得进程句柄

过渡阶段:
设置线程优先级,设置PEB的参数块,调用BaseCreateStack创建线程栈,初始化线程上下文
Context->Eip = (ULONG)BaseProcessStartThunk; //主线程的用户空间总入口,内部调用BaseProcessStart

三、第三阶段:创建初始线程
1.调用ObCreateObject创建一个线程对象ETHREAD
2.创建线程ID,调用ExCreateHandle把该内核对象加入到进程线程对象表(PspCidTable)中
3.调用MmCreateTeb创建TEB
4.初始化线程启动地址为BaseProcessStartThunk,Windows子系统启动地址为ImageInformation.TransferAddress
5.调用KeInitThread初始化线程的一些属性;设置线程的启动函数为KiThreadStartup
6.进程的活动线程计数加一,新线程加入进程的线程链表
7.调用KeStartThread初始化线程对象,把线程插入到进程对象(如果线程有挂靠进程,则插入挂靠进程)的线程列表中,此时线程可以被调度执行
8.调用ObReferenceObjectEx线程对象的引用计数加2,一个针对当前的创建操作,一个针对要返回的线程句柄
9.调用ObInsertObject把新线程对象插入到当前进程的句柄表
10.调用KeReadyThread,使线程进入“就绪”状态,此时引用计数-1

四、第四阶段:通知windows子系统有新进程创建
填充向csrss进程发出的消息,调用CsrClientCallServer向csrss通知有新进程创建
五、第五阶段:启动初始线程
根据是否传入CREATE_SUSPENDED决定是否调用NtResumeThread启动初始线程

////下述操作在上述操作完成后自动启动//////////
调用内核中线程的启动函数KiThreadStartup
1.调用PspUserThreadStartup,该函数初始化用户APC,将LdrInitializeThunk函数作为APC函数挂入APC队列中
2.发出中断,隐式调用KiUserApcDispatcher回到用户模式,此时执行PspUserThreadStartup插入的APC函数LdrInitializeThunk

六、用户空间的初始化和Dll连接(加载任何必要的DLL,并且调用这些DLL的入口函数)
LdrInitializeThunk内部实现
1.遍历主模块输入表并加载相关模块(LdrpWalkImportDescriptor)
2.修正主模块重定位(LdrRelocateImageWithBias)
3.初始化TLS(LdrpInitializeTls) 计算TLS占用的空间大小
4.调用模块入口点LdrpRunInitializeRoutines(包括TLS CALLBACK)(DLL_PROCESS_ATTACH)
5.调用NtTestAlert检查当前线程的APC队列,如果APC队列不为空的话,其将会直接调用函数KiUserApcDispatcher处理用户APC,回到内核状态

内核做完部分操作后,再次回到用户态调用BaseProcessStart
1.将主线程的入口函数设置为mainCRTStartup
2.异常处理
调用OEP(_mainCRTStartup)
调用main函数

CreateProcess函数源码分析

函数源码分析

函数调用栈

​ CreateProcess->CreateProcessInternalW

CreateProcessInternalW函数分析

函数原型

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
    )

函数详细分析

  1. 打开目标映像文件,为其创建一个Section即文件映射区,将文件内容映射进来
    1)打开目标文件

    Status = NtOpenFile(&FileHandle,
                        SYNCHRONIZE | FILE_EXECUTE,
                        &LocalObjectAttributes,
                        &IoStatusBlock,
                        FILE_SHARE_READ | FILE_SHARE_DELETE,
                        FILE_SYNCHRONOUS_IO_NONALERT |
                        FILE_NON_DIRECTORY_FILE);
    

    2)为其创建一个文件映射区

    Status = NtCreateSection(&SectionHandle,
                              SECTION_ALL_ACCESS,
                              NULL,
                              NULL,
                              PAGE_EXECUTE,
                              SEC_IMAGE,
                              FileHandle);
    
  2. 调用NtCreateProcessEx创建进程内核对象

    Status = NtCreateProcessEx(
                &ProcessHandle,
                PROCESS_ALL_ACCESS,
                pObja,
                NtCurrentProcess(),
                Flags,
                SectionHandle,
                DebugPortHandle,
                NULL,
                dwJobMemberLevel         // Job member level
                );
    
  3. 设置进程优先级

    Status = NtSetInformationProcess(ProcessHandle,
                                         ProcessPriorityClass,
                                         &PriorityClass,
                                         sizeof(PROCESS_PRIORITY_CLASS));
    if (RealTimePrivilegeState) RtlReleasePrivilege(RealTimePrivilegeState);
    
  4. 设置进程参数(BasePushProcessParameters):设置进程参数、当前目录、环境变量等信息

    //内部调用了RtlCreateProcessParameters来创建进程参数, 该函数对RTL_USER_PROCESS_PARAMETERS结构中的字符串域的地址改为相对的偏移量
    Result = BasePushProcessParameters(ParameterFlags,
                                           ProcessHandle,
                                           RemotePeb,
                                           lpApplicationName,
                                           CurrentDirectory,
                                           lpCommandLine,
                                           lpEnvironment,
                                           &StartupInfo,
                                           dwCreationFlags | NoWindow,
                                           bInheritHandles,
                                           IsWowApp ? IMAGE_SUBSYSTEM_WINDOWS_GUI: 0,
                                           AppCompatData,
                                           AppCompatDataSize);
    
  5. 调用BaseCreateStack创建线程栈

    Status = BaseCreateStack(ProcessHandle,
                                 ImageInformation.CommittedStackSize,
                                 StackSize,
                                 &InitialTeb);
    
  6. 初始化线程上下文

    BaseInitializeContext(&Context,
                              Peb,
                              ImageInformation.TransferAddress,
                              InitialTeb.StackBase,
                              0);
    

    BaseInitializeContext内部实现:

    1)设置寄存器值

    Context->Eax = (ULONG)StartAddress; //入口点 ImageInformation.TransferAddress
    Context->Ebx = (ULONG)Parameter;//参数 Peb
    Context->Esp = (ULONG)StackAddress;//栈底指针 InitialTeb.StackBase
    

    2)设置段寄存器的值
    3)判断上下文类型是否为1 (创建进程中传入0) 注意:eip = BaseProcessStartThunk

    if (ContextType == 1)
    {
        /* For Threads */
        Context->Eip = (ULONG)BaseThreadStartupThunk; //普通线程的用户空间总入口
    }
    else if (ContextType == 2)
    {
        Context->Esp -= sizeof(PVOID);
        *((PVOID*)Context->Esp) = BaseFiberStartup;//纤程
    }
    else
    {
        Context->Eip = (ULONG)BaseProcessStartThunk; //主线程的用户空间总入口
        //内部调用BaseProcessStart,这个函数就是调用OEP所在函数的上层函数
    }
    
  7. 调用NtCreateThread创建初始线程

    Status = NtCreateThread(
                &ThreadHandle,
                THREAD_ALL_ACCESS,
                pObja,
                ProcessHandle,
                &ClientId,
                &ThreadContext,
                &InitialTeb,
                TRUE
                );
    
  8. 通知windows子系统有新进程创建

    每个进程在创建/退出的时候都要向windows子系统进程csrss.exe进程发出通知,因为它担负着对windows所有进程的管理的责任
    注意,这里发出通知的是CreateProcess的调用者,不是新建出来的进程,因为它还没有开始运行
    1)填充向csrss进程发出的信息

    CreateProcessMsg->ProcessHandle = ProcessHandle;
    CreateProcessMsg->ThreadHandle = ThreadHandle;
    CreateProcessMsg->ClientId = ClientId;
    CreateProcessMsg->PebAddressNative = RemotePeb;
    CreateProcessMsg->PebAddressWow64 = (ULONG)RemotePeb;
    RemotePeb = NULL;
    switch (ImageInformation.Machine)
    {
        /* IA32, IA64 and AMD64 are supported in Server 2003 */
        case IMAGE_FILE_MACHINE_I386:
            CreateProcessMsg->ProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL;
            break;
        case IMAGE_FILE_MACHINE_IA64:
            CreateProcessMsg->ProcessorArchitecture = PROCESSOR_ARCHITECTURE_IA64;
            break;
        case IMAGE_FILE_MACHINE_AMD64:
            CreateProcessMsg->ProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64;
            break;
            /* Anything else results in image unknown -- but no failure */
        default:
            DbgPrint("kernel32: No mapping for ImageInformation.Machine == %04xn", ImageInformation.Machine);
            CreateProcessMsg->ProcessorArchitecture = PROCESSOR_ARCHITECTURE_UNKNOWN;
            break;
    }
    

    2)通知csrss进程

    CsrClientCallServer((PCSR_API_MESSAGE)&CsrMsg[0],
                            CaptureBuffer,
                            CSR_CREATE_API_NUMBER(BASESRV_SERVERDLL_INDEX, BasepCreateProcess),
                            sizeof(*CreateProcessMsg));
    

  1. 启动初始线程

    根据是否传入CREATE_SUSPENDED决定新创建的线程是否被立即调度运行

    if (!(dwCreationFlags & CREATE_SUSPENDED))
    {
        NtResumeThread(ThreadHandle, &ResumeCount);
    }
    

    上述工作做完以后自动调用以下函数

  2. 调用内核中线程的启动函数KiThreadStartup

    1)调用PspUserThreadStartup,该函数初始化用户APC,将LdrInitializeThunk函数作为APC函数挂入APC队列中
    ①初始化用户APC

    KiInitializeUserApc(KeGetExceptionFrame(&Thread->Tcb),
                                KeGetTrapFrame(&Thread->Tcb),
                                PspSystemDllEntryPoint,
                                NULL,
                                PspSystemDllBase,
                                NULL);
    

    2)中断,隐式调用KiUserApcDispatcher回到用户模式,此时执行PspUserThreadStartup插入的APC函数LdrInitializeThunk

    KiServiceExit2(TrapFrame);
    
  3. 调用LdrInitializeThunk完成用户空间的初始化和Dll连接(加载任何必要的DLL,并且调用这些DLL的入口函数)
    LdrInitializeThunk()是 ntdll.dll 中不经连接就可进入的函数,实质上就是 ntdll.dll 的入口

  4. 内核做完部分操作后,再次回到用户态调用BaseProcessStart

    内部实现
    1)将主线程的入口函数设置为mainCRTStartup
    NtSetInformationThread(GetCurrentThread(),SET_THREAD_ENTRY_ROUTINE,&lpfnStartRoutine,sizeof(lpfnStartRoutine));
    2)异常处理

  5. 调用OEP(_mainCRTStartup)

    1)初始化缓冲区溢出全局变量->__security_init_cookie
    2)初始化C语法中的全局数据->_initterm_e
    3)初始化C++语法中的全局数据->_initterm
    4)线程局部存储变量->__scrt_get_dyn_tls_init_callback
    5)注册线程局部存储析构函数->__scrt_get_dyn_tls_dtor_callback
    6)初始化完成,调用main()函数->invoke_main
    7)main()函数返回执行析构函数或atexit注册的函数指针,并结束程序->exit(main_result)

NtCreateProcessEx函数分析

函数调用栈

​ NtCreateProcessEx->PspCreateProcess

PspCreateProcess函数详细分析

  1. 如果父进程句柄ParentProcess不为空,则通过ObReferenceObjectByHandle获得父进程对象的EPROCESS指针,放在局部变量Parent

    Status = ObReferenceObjectByHandle(ParentProcess,
                                               PROCESS_CREATE_PROCESS,
                                               PsProcessType,
                                               PreviousMode,
                                               (PVOID*)&Parent,
                                               NULL);
    
  2. 调用ObCreateObject创建一个类型为PsProcessType的内核对象,置于局部变量Process中,对象体为EPROCESS,即创建一个EPROCESS

    Status = ObCreateObject(PreviousMode,
                                PsProcessType,
                                ObjectAttributes,
                                PreviousMode,
                                NULL,
                                sizeof(EPROCESS),
                                0,
                                0,
                                (PVOID*)&Process);
    
  3. 把Process置0,并初始化部分成员

    //初始化进程的引用计数
    ExInitializeRundownProtection(&Process->RundownProtect);
    //初始化进程的线程链表
    InitializeListHead(&Process->ThreadListHead);
    //继承资源配额
    PspInheritQuota(Process, Parent);
    //继承父进程的设备位图
    ObInheritDeviceMap(Parent, Process);
    //继承父进程
    Process->DefaultHardErrorProcessing = Parent->DefaultHardErrorProcessing;
    //保存父进程PID
    Process->InheritedFromUniqueProcessId = Parent->UniqueProcessId;
    
  4. 获取文件映射内存区对象的指针

    Status = ObReferenceObjectByHandle(SectionHandle,
                                               SECTION_MAP_EXECUTE,
                                               MmSectionObjectType,
                                               PreviousMode,
                                               (PVOID*)&SectionObject,
                                               NULL);
    
  5. 调用MmCreateProcessAddressSpace创建新的进程地址空间

    MmCreateProcessAddressSpace(MinWs,Process,DirectoryTableBase)
    
  6. 初始化新进程内核对象的基本优先级、Affinity、进程页表目录和超空间的页帧号

    KeInitializeProcess(&Process->Pcb,
                            PROCESS_PRIORITY_NORMAL,
                            Affinity,
                            DirectoryTableBase,
                            (BOOLEAN)(Process->DefaultHardErrorProcessing & 4));
    
  7. 初始化新进程的安全属性(赋值父进程的Token)

    Status = PspInitializeProcessSecurity(Process, Parent);
    
  8. 设置新进程优先级

    Process->PriorityClass = PROCESS_PRIORITY_CLASS_NORMAL;
    
  9. 初始化进程的句柄表 一种就是通过复制父进程的句柄表,一种就是创建一个新的句柄表

    Status = ObInitProcess ((Flags&PROCESS_CREATE_FLAGS_INHERIT_HANDLES) ? Parent : NULL, Process);
    
  10. 把进程模块映射进内存,初始化新进程的地址空间,内部调用MmMapViewOfSection将进程模块映射到地址空间

    //1.User Process(New Image Address Space):根据指定文件区域对象映射地址空间
        Status = MmInitializeProcessAddressSpace(Process,
                                                     NULL,
                                                     SectionObject,
                                                     &Flags,
                                                     &Process->
                                                     SeAuditProcessCreationInfo.
                                                     ImageFileName);
    //2.User Process(Cloned Address Space):根据父进程克隆地址空间,并把父进程的映像名称拷贝到新进程对象的数据结构中
        NeedsPeb = TRUE;
    //3.System Process:系统进程的初始化,无父进程,无文件区域对象,如果是系统进程,同样拷贝父进程的映像名称到新进程对象的数据结构中
        Status = MmInitializeProcessAddressSpace(Process,
                                                    NULL,
                                                    NULL,
                                                    &Flags,
                                                    NULL);
    //4.Boot Process:系统引导时调用,无父进程。地址空间在MmInitSystem执行过程中初始化,由MiInitMachineDependent调用MmInitializeProcessAddressSpace来完成
    

    MmInitializeProcessAddressSpace调用MmMapViewOfSection(即将进程映射到默认地址处)

    BaseAddress = NULL;
    ViewSize = 0;
    ZERO_LARGE (SectionOffset);
    
    Status = MmMapViewOfSection ((PSECTION)SectionToMap,
                                 ProcessToInitialize,
                                 &BaseAddress,
                                 0,
                                 0,
                                 &SectionOffset,
                                 &ViewSize,
                                 ViewShare,
                                 0,
                                 PAGE_READWRITE);
    
    ProcessToInitialize->SectionBaseAddress = BaseAddress;
    
  11. 映射系统模块ntdll.dll(PspMapSystemDll-->MmMapViewOfSection)

    if (SectionObject) PspMapSystemDll(Process, NULL, FALSE);
    
  12. 把该内核对象加入到进程线程对象表(PspCidTable)中,得到进程ID,创建进程ID。调用ExCreateHandle在System进程句柄表中存一个句柄,句柄值就是PID

    CidEntry.Object = Process;
    CidEntry.GrantedAccess = 0;
    Process->UniqueProcessId = ExCreateHandle(PspCidTable, &CidEntry);
    
  13. 设置对象表的进程ID

    Process->ObjectTable->UniqueProcessId = Process->UniqueProcessId;
    
  14. 如果父进程属于一个作业对象,则也加入到父进程所在的作业中

  15. 对于通过映像内存区来创建进程的情形,创建一个PEB,对于进程拷贝的情况,则使用继承的PEB

    Status = MmCreatePeb(Process, &InitialPeb, &Process->Peb);
    
  16. 把新进程加入全局的进程链表中。PsActiveProcessHead

    InsertTailList(&PsActiveProcessHead, &Process->ActiveProcessLinks);
    
  17. 创建一个access state

    Status = SeCreateAccessStateEx(CurrentThread,
                                       ((Parent) &&
                                       (Parent == PsInitialSystemProcess)) ?
                                        Parent : CurrentProcess,
                                       &LocalAccessState,
                                       &AuxData,
                                       DesiredAccess,
                                       &PsProcessType->TypeInfo.GenericMapping);
    
  18. 把新进程对象插入到当前进程的句柄表中,获得进程句柄

    Status = ObInsertObject(Process,
                                AccessState,
                                DesiredAccess,
                                1,
                                NULL,
                                &hProcess);
    
  19. 计算新进程的优先级和时限重置值,设置内存优先级

    Process->Pcb.BasePriority =(SCHAR)PspComputeQuantumAndPriority(Process,PsProcessPriorityBackground,&Quantum);
    Process->Pcb.QuantumReset = Quantum;
    
  20. 设置进程访问权限,当前进程句柄可访问,允许进程终止

    //有父进程,但不是PsInitialialSystemProcess,首先执行访问检查,然后计算进程的访问权限
    如果是PsInitialialSystemProcess的子进程,则授予所有的访问权限
    Process->GrantedAccess = PROCESS_TERMINATE;
    
  21. 设置进程的创建时间,把新进程的句柄赋到ProcessHandle中

    KeQuerySystemTime(&Process->CreateTime);
    
  22. 执行(监控进程创建)回调函数

    PspRunCreateProcessNotifyRoutines(Process, TRUE);
    

NtCreateThread函数分析

函数调用栈

​ NtCreateThread->PspCreateThread

函数详细分析

  1. 获取进程对象,存储到局部变量 Process

    Status = ObReferenceObjectByHandle(ProcessHandle,
                                               PROCESS_CREATE_THREAD,
                                               PsProcessType,
                                               PreviousMode,
                                               (PVOID*)&Process,
                                               NULL);
    PSREFTRACE(Process);
    
  2. 调用ObCreateObject创建一个线程对象ETHREAD,并初始化为零

    Status = ObCreateObject(PreviousMode,
                                PsThreadType,
                                ObjectAttributes,
                                PreviousMode,
                                NULL,
                                sizeof(ETHREAD),
                                0,
                                0,
                                (PVOID*)&Thread);
    RtlZeroMemory(Thread, sizeof(ETHREAD));
    
  3. 初始化RundownProtect

    ExInitializeRundownProtection(&Thread->RundownProtect);
    
  4. 设置新线程的父进程

    Thread->ThreadsProcess = Process;
    Thread->Cid.UniqueProcess = Process->UniqueProcessId;
    
  5. 创建线程ID,调用ExCreateHandle在PspCidTable内核句柄表中存一个句柄,句柄值就是TID

    PspCidTable:存放进程和线程的内核对象(EPROCESS 和 ETHREAD),并通过 PID 和 TID 进行索引,ID 号以 4 递增
    CidEntry.Object = Thread;
    CidEntry.GrantedAccess = 0;
    Thread->Cid.UniqueThread = ExCreateHandle(PspCidTable, &CidEntry);
    
  6. 调用MmCreateTeb函数创建TEB,用ThreadContext的Eip初始化StartAddress,用ThreadContext的Eax初始化Win32StartAddress

    Status = MmCreateTeb(Process, &Thread->Cid, InitialTeb, &TebBase);
    
  7. 初始化线程启动地址和Windows子系统的启动地址

    Thread->StartAddress = (PVOID)KeGetContextPc(ThreadContext);//ThreadContext->Eip  初始化线程的启动地址
    Thread->Win32StartAddress = (PVOID)KeGetContextReturnRegister(ThreadContext);//ThreadContext->Eax  初始化WINDOWS子系统的启动地址
    
  8. 初始化线程的一些属性,包括同步头(Header域), WaitBlock, ServiceTable, APC,定时器,线程的内核栈;设置线程的启动函数为KiThreadStartup
    ·当创建用户线程赋值PspUserThreadStartup 当线程创建完毕后,在3环下启动线程时,即通过此函数入口点执行。
    ·当创建系统线程赋值PspSystemThreadStartup

    Status = KeInitThread(&Thread->Tcb,
                                 NULL,
                                 PspUserThreadStartup,
                                 NULL,
                                 Thread->StartAddress,
                                 ThreadContext,
                                 TebBase,
                                 &Process->Pcb);
    

    KeInitThread内部实现:
    1)根据进程对象中的信息来初始化新线程的一些属性
    2)根据所提供的参数信息调用KiInitializeContextThread

    KiInitializeContextThread(Thread,
                               SystemRoutine,//PspUserThreadStartup
                               StartRoutine,
                               StartContext,//Thread->StartAddress
                               Context);//ThreadContext
    

    KiInitializeContextThread内部实现:

    1)在堆栈上构建KTRAP_FRAME(陷井框架, 用来系统调用时保存用户空间堆栈的信息,或者在为了返回到用户空间时构建的上下文环境)

    KeContextToTrapFrame(Context,
                          NULL,
                          TrapFrame,
                          CONTEXT_AMD64 | ContextFlags,
                          UserMode);
    

    2)指定了线程的启动函数KiThreadStartup
    CtxSwitchFrame->Return = (ULONG64)KiThreadStartup;
    启动函数内部调用PspUserThreadStartup:
    StartFrame->SystemRoutine(StartFrame->StartRoutine, StartFrame->StartContext);

  9. 锁住进程,确保不是在退出或终止过程中

    KeEnterCriticalRegion();
    ExAcquirePushLockExclusive(&Process->ProcessLock);
    
  10. 进程的活动线程计数加一,新线程加入进程的线程链表

    InsertTailList(&Process->ThreadListHead, &Thread->ThreadListEntry);
    Process->ActiveThreads++;
    
  11. 初始化剩余的域,尤其是和调度相关的,比如优先级、时限设置、CPU亲和性等,把线程插入到进程对象(如果线程有挂靠进程,则插入挂靠进程)的线程列表中,此时线程可以被调度执行了

    KeStartThread(&Thread->Tcb);
    
  12. 释放进程锁

    ExReleasePushLockExclusive(&Process->ProcessLock);
    KeLeaveCriticalRegion();
    
  13. 如果被创建的线程是该进程的第一个线程,触发该进程的创建通知

        if (OldActiveThreads == 0) {
            PERFINFO_PROCESS_CREATE (Process);
            if (PspCreateProcessNotifyRoutineCount != 0) {
                ULONG i;
                PEX_CALLBACK_ROUTINE_BLOCK CallBack;
                PCREATE_PROCESS_NOTIFY_ROUTINE Rtn;
                for (i=0; i<PSP_MAX_CREATE_PROCESS_NOTIFY; i++) {
                    CallBack = ExReferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i]);
                    if (CallBack != NULL) {
                        Rtn = (PCREATE_PROCESS_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack);
                        Rtn (Process->InheritedFromUniqueProcessId,
                             Process->UniqueProcessId,
                             TRUE);
                        ExDereferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i],
                                                    CallBack);
                    }
                }
            }
        }
    
  14. 作业处理,暂时略过

  15. 线程对象的引用计数加2,一个针对当前的创建操作,一个针对要返回的线程句柄

    ObReferenceObjectEx(Thread, 2);

  16. 如果传入挂起标识,挂起该线程

    if (CreateSuspended) KeSuspendThread(&Thread->Tcb);

  17. 根据指定的期望访问权限,调用 SeCreateAccessStateEx 创建一个访问状态结构

    Status = SeCreateAccessStateEx(NULL,
                                       ThreadContext ?
                                       PsGetCurrentProcess() : Process,
                                       &LocalAccessState,
                                       &AuxData,
                                       DesiredAccess,
                                       &PsThreadType->TypeInfo.GenericMapping);
    
  18. 把新线程对象插入到当前进程的句柄表

    Status = ObInsertObject(Thread,
                                AccessState,
                                DesiredAccess,
                                0,
                                NULL,
                                &hThread);
    
  19. 设置输出参数ThreadHandle,设置输出参数 ClientId

    if (ClientId) *ClientId = Thread->Cid;
          *ThreadHandle = hThread;
    
  20. 设置线程的创建时间

    KeQuerySystemTime(&Thread->CreateTime);
    
  21. 设置线程访问权限

  22. 调用KeReadyThread,使线程进入“就绪”状态,准备马上执行。或者此时进程未在内存中,则新线程的状态为“转移”,以等待换入内存后再执行。引用计数减1

    KeReadyThread(&Thread->Tcb);
    ObDereferenceObject(Thread);
    

LdrInitializeThunk函数分析

函数调用栈

​ LdrInitializeThunk->LdrpInitialize

LdrpInitialize函数详细分析

  1. 如果是进程中第一个线程,调用LdrpInitializeProcess

    InitStatus = LdrpInitializeProcess (Context, SystemArgument1);
    

    LdrpInitializeProcess函数实现:

    1)获得TEB和PEB的相关信息

    Teb = NtCurrentTeb();
    Peb = Teb->ProcessEnvironmentBlock;
    ProcessParameters = Peb->ProcessParameters;
    pw = ProcessParameters->ImagePathName.Buffer;
    if (!(ProcessParameters->Flags & RTL_USER_PROC_PARAMS_NORMALIZED)) {
        pw = (PWSTR)((PCHAR)pw + (ULONG_PTR)(ProcessParameters));
    }
    UnicodeImageName.Buffer = pw;
    UnicodeImageName.Length = ProcessParameters->ImagePathName.Length;
    UnicodeImageName.MaximumLength = UnicodeImageName.Length + sizeof(WCHAR);
    StaticCurDir = TRUE;
    UseCOR = FALSE;
    ImagePathNameBuffer = NULL;
    DebugProcessHeapOnly = 0;
    

    2)获得NtHeader

    NtHeader = RtlImageNtHeader (Peb->ImageBaseAddress);
    

    3)初始化NLS

    RtlInitNlsTables (Peb->AnsiCodePageData,
                      Peb->OemCodePageData,
                      Peb->UnicodeCaseTableData,
                      &xInitTableInfo);
    

    4)设置PEB的标志及相关内容

    5)创建进程堆(RtlCreateHeap)

    ProcessHeap = RtlCreateHeap (ProcessHeapFlags,
                                      NULL,
                                      NtHeader->OptionalHeader.SizeOfHeapReserve,
                                      NtHeader->OptionalHeader.SizeOfHeapCommit,
                                      NULL, // Lock to use for serialization
                                      &HeapParameters);
    

    6)为Loader创建堆

    LdrpHeap = RtlCreateHeap (
                        HEAP_GROWABLE | HEAP_CLASS_1,
                        NULL,
                        64 * 1024, // 0 is ok here, 64k is a chosen tuned number
                        24 * 1024, // 0 is ok here, 24k is a chosen tuned number
                        NULL,
                        &LdrpHeapParameters);
    NtdllBaseTag = RtlCreateTagHeap (ProcessHeap,
                                     0,
                                     L"NTDLL!",
                                     L"!Process"         
                                     L"CSRSS Client"
                                     L"LDR Database"
                                     L"Current Directory"
                                     L"TLS Storage"
                                     L"DBGSS Client"
                                     L"SE Temporary"
                                     L"Temporary"
                                     L"LocalAtom");
    

    7)设置系统Dll路径为system32mscoree.dll

    SystemDllPath.Buffer = SystemDllPathBuffer;
    SystemDllPath.Length = 0;
    SystemDllPath.MaximumLength = sizeof (SystemDllPathBuffer);
    RtlInitUnicodeString (&SystemRoot, USER_SHARED_DATA->NtSystemRoot);
    RtlAppendUnicodeStringToString (&SystemDllPath, &SystemRoot);
    RtlAppendUnicodeStringToString (&SystemDllPath, &SlashSystem32SlashString);
    

    8)获得Knowndll目录所在的路径,打开如KnownDllsKnownDllPath形式的符号链接,获得KnownDllPath

    InitializeObjectAttributes (&Obja,
                                (PUNICODE_STRING)&SlashKnownDllsString,
                                OBJ_CASE_INSENSITIVE,
                                NULL,
                                NULL);
    st = NtOpenDirectoryObject (&LdrpKnownDllObjectDirectory,
                                DIRECTORY_QUERY | DIRECTORY_TRAVERSE,
                                &Obja);
    

    CreateProcess函数源码分析

    9)为第一个LdrDataTableEntry分配内存并初始化(第一个模块即为当前进程exe),将其插入模块链表中

    10)为系统DLL(ntdll)分配内存并初始化,将其插入模块链表中

    11)遍历主模块输入表并加载相关模块(LdrpWalkImportDescriptor)

    st = LdrpWalkImportDescriptor (LdrpDefaultPath.Buffer, LdrpImageEntry);
    

    12)修正主模块重定位(LdrRelocateImageWithBias)

    if ((PVOID)NtHeader->OptionalHeader.ImageBase != Peb->ImageBaseAddress) {
        PVOID ViewBase;
        ViewBase = Peb->ImageBaseAddress;
        st = LdrpSetProtection (ViewBase, FALSE);
        st = LdrRelocateImage (ViewBase,
                               "LDR",
                               STATUS_SUCCESS,
                               STATUS_CONFLICTING_ADDRESSES,
                               STATUS_INVALID_IMAGE_FORMAT);
    }
    

    Peb->ImageBaseAddress的赋值如下:

    1.在MmCreateProcessAddressSpace中调用MmCreateSection->MiCreateImageFileMap:
      NewSegment->BasedAddress = (PVOID) NextVa;//NextVa即为0x400000
    
    2.在MmInitializeProcessAddressSpace调用MmMapViewOfSection(即将进程映射到默认地址处):
      BaseAddress = NULL;
      ViewSize = 0;
      ZERO_LARGE (SectionOffset);
      Status = MmMapViewOfSection ((PSECTION)SectionToMap,
                                 ProcessToInitialize,
                                 &BaseAddress,
                                 0,
                                 0,
                                 &SectionOffset,
                                 &ViewSize,
                                 ViewShare,
                                 0,
                                 PAGE_READWRITE);
    3.在MmMapViewOfSection中调用MiMapViewOfImageSection:
    1)若传入BaseAddress不为空
      StartingAddress = MI_64K_ALIGN(*CapturedBase);
      EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1) | (PAGE_SIZE - 1));
    2)若传入BaseAddress为空
      StartingAddress = (PVOID)((ULONG_PTR)BasedAddress + (ULONG_PTR)MI_64K_ALIGN(SectionOffset->LowPart));
      EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress + *CapturedViewSize - 1) | (PAGE_SIZE - 1));
    
    4.回到MmInitializeProcessAddressSpace函数中,对EPROCESS.SectionBaseAddress进行赋值:
      ProcessToInitialize->SectionBaseAddress = BaseAddress;
    
    5.在MmCreatePeb函数中:  
      PebBase->ImageBaseAddress = TargetProcess->SectionBaseAddress;
    

    13)初始化TLS(LdrpInitializeTls) 计算TLS占用的空间大小

    14)如果有调试器,通知调试器

    15)调用模块入口点LdrpRunInitializeRoutines(包括TLS CALLBACK)(DLL_PROCESS_ATTACH)

    st = LdrpRunInitializeRoutines (Context);
    
  2. 非第一个线程,调用LdrpInitializeThread函数

    LdrpInitializeThread函数实现:循环调用各个模块的TLS callback,入口点函数 (DLL_THREAD_ATTACH)

    while (Next != &PebLdr.InMemoryOrderModuleList) {
        LdrDataTableEntry = (PLDR_DATA_TABLE_ENTRY)(CONTAINING_RECORD(Next, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks));
        if ((Peb->ImageBaseAddress != LdrDataTableEntry->DllBase) &&
            (!(LdrDataTableEntry->Flags & LDRP_DONT_CALL_FOR_THREADS))) {
    
            InitRoutine = (PDLL_INIT_ROUTINE)(ULONG_PTR)LdrDataTableEntry->EntryPoint;
            if ((InitRoutine) &&
                (LdrDataTableEntry->Flags & LDRP_PROCESS_ATTACH_CALLED) &&
                (LdrDataTableEntry->Flags & LDRP_IMAGE_DLL)) {
    
                LDRP_ACTIVATE_ACTIVATION_CONTEXT (LdrDataTableEntry);
    
                if (LdrDataTableEntry->TlsIndex) {
                    if (!LdrpShutdownInProgress) {
                        LdrpCallTlsInitializers (LdrDataTableEntry->DllBase, DLL_THREAD_ATTACH);
                    }
                }
    
                if (!LdrpShutdownInProgress) {
    
                    LdrpCallInitRoutine (InitRoutine,
                                         LdrDataTableEntry->DllBase,
                                         DLL_THREAD_ATTACH,
                                         NULL);
                }
                LDRP_DEACTIVATE_ACTIVATION_CONTEXT ();
            }
        }
        Next = Next->Flink;
    }
    
  3. 测试Alart(NtTestAlert):不用alertable状态运行APC作业的函数

    检查当前线程的APC队列,如果APC队列不为空的话,其将会直接调用函数KiUserApcDispatcher处理用户APC,回到内核状态

参考

博客

内核原理与实现 011进程线程创建过程 | 码农家园 (codenong.com)

(74条消息) CreateProcess 内部实现_zhou191954的博客-CSDN博客

原文链接: https://www.cnblogs.com/XiuzhuKirakira/p/17077412.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    CreateProcess函数源码分析

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/383667

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年3月8日 下午6:26
下一篇 2023年3月8日 下午6:28

相关推荐