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->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 == %04x\n", 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\0"
    L"CSRSS Client\0"
    L"LDR Database\0"
    L"Current Directory\0"
    L"TLS Storage\0"
    L"DBGSS Client\0"
    L"SE Temporary\0"
    L"Temporary\0"
    L"LocalAtom\0");

    7)设置系统Dll路径为\system32\mscoree.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目录所在的路径,打开如\KnownDlls\KnownDllPath形式的符号链接,获得KnownDllPath

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

    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博客

CreateProcess函数源码分析的更多相关文章

  1. Vue中之nextTick函数源码分析

    Vue中之nextTick函数源码分析 1. 什么是Vue.nextTick()?官方文档解释如下:在下次DOM更新循环结束之后执行的延迟回调.在修改数据之后立即使用这个方法,获取更新后的DOM. 2 ...

  2. PHP 源码 — intval 函数源码分析

    PHP 源码 - intval 函数源码分析 文章来源: https://github.com/suhanyujie/learn-computer/ 作者:suhanyujie 基于PHP 7.3.3 ...

  3. PHP 源码 —— is_array 函数源码分析

    is_array 函数源码分析 本文首发于 https://github.com/suhanyujie/learn-computer/blob/master/src/function/array/is ...

  4. 序列化器中钩子函数源码分析、many关键字源码分析

    局部钩子和全局钩子源码分析(2星) # 入口是 ser.is_valid(),是BaseSerializer的方法 # 最核心的代码 self._validated_data = self.run_v ...

  5. Spark GraphX的函数源码分析及应用实例

    1. outerJoinVertices函数 首先给出源代码 override def outerJoinVertices[U: ClassTag, VD2: ClassTag] (other: RD ...

  6. 【C++】【源码解读】std::is_same函数源码解读

    std::is_same使用很简单 重点在于对源码的解读 参考下面一句静态断言: static_assert(!std::is_same<bool, T>::value, "ve ...

  7. lodash框架中的chunk与drop函数源码逐行分析

    lodash是一个工具库,跟underscore差不多 chunk函数的作用: 把一维数组,按照固定的长度分段成二维数组 如: chunk( [ 10, 20, 30, 40 ], 2 )     结 ...

  8. Go语言fmt库的print函数源码解析

    // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a B ...

  9. 巡风视图函数源码学习--view.py

    记录一下巡风扫描器view.py这个脚本里的视图函数的学习,直接在代码里面做的注释,里面有一些print 代码是为了把数据打印出来小白我自己加的,勿怪勿怪.可能存在一些理解错误和不到位的地方,希望大佬 ...

  10. mongodb操作:利用javaScript封装db.collection.find()后可调用函数源码解读

    { "_mongo" : connection to YOURIP:27017{ SSL: { sslSupport: false, sslPEMKeyFile: "&q ...

随机推荐

  1. 看火山引擎DataLeap如何做好电商治理(一):挑战与痛点

    现在人们的日常生活中,网购已经成为人们生活中不可或缺的购物形式.根据中国电子商会发布的<中国社交电商行业发展白皮书(2022)>的数据显示,2022年社交电商市场交易规模达到28542.8 ...

  2. 【flask】flask-sqlalchemy使用 flask-migrate使用 flask项目演示

    目录 昨日回顾 今日内容 1 flask-sqlalchemy使用 2 flask-migrate使用 3 flask项目演示 昨日回顾 类装饰器可能有两种含义: 使用类作为装饰器 装饰类的装饰器 基 ...

  3. 记录一次Java内存泄露分析过程

    版权说明: 本文章版权归本人及博客园共同所有,转载请在文章前标明原文出处( https://www.cnblogs.com/mikevictor07/p/13032635.html ),以下内容为个人 ...

  4. awk 文本编辑器

    1.简介 文本编辑器 非交互式的编辑器 编程语言 功能:对文本数据进行汇总和处理 是一个报告生成器 能够对数据进行排版 工作模式:行工作模式 读入一行 将整行内容存在$0里,一行等于一个记录 记录分隔 ...

  5. 在Windows上D盘上安装Docker

    Reference https://www.willh.cn/articles/2022/07/13/1657676401964.html Docker默认安装在C盘: "C:\Progra ...

  6. OS | 银行家算法C语言实现

    算法简介 银行家算法(Banker's Algorithm)是一个避免死锁( Deadlock)的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.H.E系统设计的一种避免死锁产生的算法.它以银行借贷 ...

  7. 基于 HTML5 WebGL + WebVR 的 3D 虚拟现实可视化培训系统

    前言 2019 年 VR, AR, XR, 5G, 工业互联网等名词频繁出现在我们的视野中,信息的分享与虚实的结合已经成为大势所趋,5G 是新一代信息通信技术升级的重要方向,工业互联网是制造业转型升级 ...

  8. 通义千问预体验,如何让 AI 模型应用“奔跑”在函数计算上?

    立即体验基于函数计算部署通义千问预体验: https://developer.aliyun.com/topic/aigc_fc AIGC 浪潮已来,从文字生成到图片生成,AIGC 的创造力让人惊叹,更 ...

  9. hdu 5547

    ***题意:4*4数独,要求在同一行同一列不能有相同的数字,另外在2*2的小单元里也不能有相同的数字 思路:DFS暴力搜索, 每个位置填1-4,递归回溯,判断是否符合条件,递归到最后一个位置+1则输出 ...

  10. loadrunner12的安装教程

    一.LR12安装包: 链接:https://pan.baidu.com/s/1UU304e-nP7qAL-fV8T39YQ 密码:jpln 二.LR12安装: 1.下载完成后点击解压