1.调用CreateProcessW打开指定可执行文件,并创建一个内存区对象。这里仅仅是打开exe文件,并没有将文件映射到内存中。打开exe是为了在后续创建进程的过程中从exe头部中读取一些环境配置。
2.调用ntdll.dll中的NtCreateProcessEx系统服务。该函数仅仅是一个内核层对外的门户,其实ntdll.dll中几乎所有的API都是一个空壳,ntdll.dll的本质就是用户层和内核层之间的一道屏障。而这道屏障就是负责保护内核层的,防止用户层直接控制内核层。ntdll.dll中的API会对用户输入的参数进行严格检测,防止对内核层造成破坏。
3.调用ntdll.dll中的NtCreateProcessEx之后,他会利用处理器的陷阱机制切换到内核模式下。之后在内核模式中的系统服务分发函数KiSystemService获得控制权,它利用当前线程指定的系统服务表,调用到执行体层的NtCreateProcessEx函数。这里需要注意的是ntoskrnl.dll中的执行体层,该层中包含了与ntdll.dll中API完全对应的一套Nt函数。而恰恰就是执行体中的这一套API才是真正的函数本体。
4.执行体层的NtCreateProcessEx被调用之后,系统开始正式在内核层创建进程。
5.创建EPROCESS对象,并初始化其中的域(这里的域表示的就是结构体中的成员或属性)。
6.创建初始的进程地址空间。
7.创建进程的句柄表。
8.调用KeInitializeProcess函数来初始化新进程对象的基本优先级、Affinity、进程页表目录和超空间的页帧号。
9.通过PspInitializeProcessSecurity函数初始化新进程的安全属性,主要是从父进程复制一个令牌。
10.设置新进程的优先级类别。
11.初始化新进程的句柄表。
12.初始化新进程的进程地址空间,该过程是通过调用MmInitializeProcessAddressSpace函数进行的。主要是读取exe文件中的部分属性对进程地址空间进行初始化。注意:这里还没有将exe加载到进程地址空间中。仅仅是读了些属性进行初始化用。
13.创建进程ID,利用ExCreateHandle函数在CID句柄表中创建一个进程ID项。
14.对这次进程的创建行为进行审计。审计就是类似于记录。
15.创建一个PEB,这里首次出现了进程环境快PEB。PEB是EPROCESS结构成员之一。
16.将新进程加入到全局的进程链表PsActiveProcessHead中。
17.调用ObInsertObject函数,把新进程对象插入到当前进程的句柄表中。这里的新进程对象就是EPROCESS结构对象。
18.设置进程的创建时间。并把新进程的句柄赋到ProcessHandle中,从而创建者可以获得新进程的句柄。
19.到目前为止,内核中的进程已经创建完毕,但进程本身是不具备执行能力的,进程仅仅只是一个执行环境,必须要创建一根线程来负责执行代码。
20.在创建初始线程之前,首先要构造一个栈以及一个可供运行的环境。初始线程的栈大小可以通过PE映像文件获取。
21.调用ntdll.dll中的NtCreateThread函数来创建线程。
22.和进程创建同理,线程创建最后也是由执行体层的NtCreateThread函数来完成。
23.创建ETHREAD对象,并初始化其中的域。
24.生成线程ID。
25.建立TEB线程环境块。
26.设置线程安全属性工作。
27.进程的初始线程的启动函数默认是kernel32.dll中的BaseProcessStart函数(非线程函数)。然而这里的线程不会立即执行,它会先处于挂起状态,等待进程完全初始化后才开始真正执行。
28.到目前为止内核部分的进程创建基本完成,但是从用户层的子系统角度来说,用户进程的创建才刚刚开始。
29.Kernel32.dll会给Windows子系统发送一个消息,消息的内容包括:进程和线程的句柄、进程创建者的ID等必要信息。
30.Windows子系统进程csrss.exe负责接收此消息。
31.保留一份句柄。
32.设定新进程的优先级类别。
33.在子系统中分配一个内部进程块。
34.设置新进程的异常端口,从而子系统可以接收到该进程中发生的异常。
35.对于正在被调试的进程,设置它的调试端口,从而子系统可以接收到该进程的调试事件。
36.分配并初始化一个内部线程块,并插入到进程的线程列表中。
37.窗口会话中的进程计数增加1。
38.设置进程的停机级别为默认级别。
39.将新进程插入到子系统的进程列表中。
40.分配并初始化一块内存供子系统的内核模式部分使用(W32PROCESS结构)。
41.显示应用程序启动光标。
42.到这时候,进程环境已经建立好了,其线程将要使用的资源也分配好了,Windows子系统已经知道并登记了此进程和线程。所以,初始线程被恢复执行,余下的初始化工作是初始线程在新进程的环境中完成的。
43.KiThreadStartup是新线程的启动例程,它调用PspUserThreadStartup函数,该函数中会将PE文件读取到内存中。
44.然后调用LdrInitializeThunk函数对PE映像加载器进行初始化。
45.初始化堆管理器
46.根据导入表加载DLL,并调用DLL的入口函数。
47.当LdrInitializeThunk返回到用户模式后,该线程开始执行应用程序指定的线程启动函数(BaseProcessStart)。
这里需要注意:Vista以后是BaseThreadInitThunk函数
// Before Vista
VOID
BaseThreadStart(
IN LPTHREAD_START_ROUTINE lpStartAddress,
IN LPVOID lpParameter
)
// Vista+
VOID
BaseThreadInitThunk(
IN DWORD LdrReserved,
IN LPTHREAD_START_ROUTINE lpStartAddress,
IN LPVOID lpParameter
);
而其中的lpStartAddress传入的就是EP入口地址。
48.至此,进程已经完全建立起来了,并正式开始执行用户空间的代码。