[原创]64位CreateProcess逆向:(四)创建进程的过程
点击下面进入总目录:
http://bbs.pediy.com/showthread.php?t=207430
这个系列的这篇及下一篇文章,将涉及到操作系统创建进程中的进程、线程管理。本文是与进程管理有关的内容。主要涉及到内核函数PspAllocateProcess。
PspAllocateProcess的函数原型如下:
NTSTATS PspAllocateProcess(
//父进程内核对象指针
IN PEPROCESS pParentEProcess,
//访问模式,表明调用来自用户态,还是内核态
IN KPROCESSOR_MODE AccessMode,
//新进程对象安全属性
IN POBJECT_ATTRIBUTES pProcessObjectAttributes,
//新进程的内存区对象句柄, 代表进程的映像文件
IN HANDLE SectionObject,
//令牌对象句柄
IN HANDLE hSeTokenObject,
//进程标识符
OUT PVOID pProcessFlags,
//传入传出结构体指针, 存放进程创建过程中一些句柄 内核对象, 大小0xC4
IN OUT CREATEPROCESSCONTEXT *pCreateProcessContext,
IN PVOID pUnKnow,
//如果成功创建新进程的EProcess , 则从这里传出PEprocess
OUT PPEPROCESS ppNewEprocess
);
各个参数的含义已经给出注释,大家可以自己对照理解。倒数第三个参数pCreateProcessContext的定义请参照此系列的
http://bbs.pediy.com/showthread.php?t=207683这篇文章。
这个函数的主要功能,是创建新进程的EPROCESS,为进程分配进程空间,创建进程的句柄表、PEB,以及设置进程的安全属性。
概念介绍
不是所有的人都对Windows的进程、线程管理熟悉,所以我们先在这简要介绍下相关概念,深入的了解,大家还是需要翻阅相关资料或者使用WinDbg查看。
Windows的进程与线程管理
我们知道,Windows中进程与线程的管理,是一对多的关系,即一个进程下可以拥有多个线程,这些线程共用所属进程的内存空间。
在Windows内核中,用_EPROCESS、_ETHREAD结构体来代表进程、线程。实际分析中,你又会发现这些“E”前缀的结构体中,还包含了“K”前缀的同类型结构体,如_KPROCESS、_KTHREAD。这其实是Windows系统分层设计带来的后果,K前缀代表着内核层次,E代表着执行层次。
这些内核结构体是可能随操作系统版本变化而变化的,比如以下是WinDbg中查看到的Windows 7的_KPROCESS的结构:
nt!_KPROCESS +0x000 Header : _DISPATCHER_HEADER +0x018 ProfileListHead : _LIST_ENTRY +0x028 DirectoryTableBase : Uint8B +0x030 ThreadListHead : _LIST_ENTRY +0x040 ProcessLock : Uint8B +0x048 Affinity : _KAFFINITY_EX ... +0x15a LdtTableLength : Uint2B
它与http://www.nirsoft.net/kernel_struct/vista/KPROCESS.html上记录的vista版本的KPROCESS还是有区别的:
typedef struct _KPROCESS
{
DISPATCHER_HEADER Header;
LIST_ENTRY ProfileListHead;
ULONG DirectoryTableBase;
ULONG Unused0;
KGDTENTRY LdtDescriptor;
KIDTENTRY Int21Descriptor;
WORD IopmOffset;
UCHAR Iopl;
UCHAR Unused;
ULONG ActiveProcessors;
ULONG KernelTime;
ULONG UserTime;
LIST_ENTRY ReadyListHead;
SINGLE_LIST_ENTRY SwapListEntry;
PVOID VdmTrapcHandler;
LIST_ENTRY ThreadListHead;
ULONG ProcessLock;
...
UINT64 CycleTime;
} KPROCESS, *PKPROCESS;不过,还是可以发现,不管哪个版本,KPROCESS中都包含有一个链表ThreadListHead,即线程链表,Windows用这种在进程结构体中挂线程链表的方式,体现进程与线程的一对多关系。
句柄表
在EPROCESS结构体中,有一个句柄表成员:

我们知道,句柄是为了操作内核对象的“序号”,它的好处在于统一了接口,使得进程(准确来说是线程)能够用统一的方式操作不同的对象资源。一个进程能够使用的句柄,都放在EPROCESS中的句柄表ObjectTable中。PspAllocateProcess这个函数中也涉及到新进程的句柄表的初始化。
函数流程详细解析

函数最开始,先调用ObCreateObject函数,根据全局的参数cs:PsProcessType创建EPROCESS对象,并进行部分初始化。

接着,通过调用MmGetSectionInformation,根据传入参数Section Handle,拿到对应的Section。这个Section在此系列的之前文章中已经谈过,就是PE文件在内存中的映射。


接着,会根据参数CreateProcessContext中的标志,来决定是否读取注册表中的信息来进行相应的设置。注册表中的相关设置,大都与系统设置有关,如用WinDbg动态跟踪,可以找到与新进程默认调试器有关的注册表地址(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug)。还有其它与分页大小、内存映射方式一类的键值设置,大家可以查看IDA文件中,对应注视自己看。



此外,ntdll.dll这个所有进程都会加载的dll文件,也是在PspAllocateProcess中查询该文件,并最终映射到新进程的内存空间的。

接着,PspAllocateProcess根据传入参数,获取token,并根据token设置进程相关的安全属性。token来自于CreateProcessContext.hSeTokenObject。
父进程不存在则获取系统默认Quota,并初始化为handleBuild进程。


然后,PspAllocateProcess所做的工作就与进程空间有关了。我们知道,在Windows的内存管理方式下,每一个进程使用的都是虚拟内存,也就是说,不同进程间的内存空间是相互独立的。为了达到这个目的,操作系统起码要做两个事:
1. 将新进程的虚拟内存地址,映射到物理内存地址上
2. 保证不同进程所使用的物理内存地址不相互冲突

Windows把这些工作封装在了MmCreateProcessAddressSpace函数中,想要了解内存映射、内存管理相关细节的人,应该深入看下这个函数。不过这是个很大并且有些偏离我们此系列的话题,我们就不多剖析了,只贴出调用MmCreateProcessAddressSpace的相关代码位置:

至此,新进程已经拥有了它自己的虚拟内存空间(但还没初始化),操作系统可以初始化代表着新进程的内核对象EPROCESS了。

调用KeInitializeProcess初始化EPROCESS:
1、 初始化_KPROCESS结构体的_DISPATCHER_HEADER(分发器对象)结构体成员;一般同步大家会用到自旋锁,自旋锁虽然效率高,不过有些浪费系统资源,_DISPATCHER_HEADER就可以用于完成同步的任务,援引《寒江独钓》中的一句话“在windows内核中,凡是头部以DISPATCH_HEADER开始的对象都是"分发器对象”。
2、 填充页目录表地址(PDE),内存对齐检查标志,进程可以在哪些处理器上运行,所有线程的时限;
3、 初始化性能分析、线程等链表,为一个进程选择优先的处理器节点。

调用ObInitProcess,创建新进程的句柄表:若父进程(当前进程)的某些句柄有“可继承”属性,那么新进程可以直接继承这些句柄并操作相关对象。这个继承的过程,其实就是从父进程的句柄表中复制相关句柄到自己的句柄表中(EPROCESS里的ObjectTable)。这些工作在内核函数ObInitProcess中完成。

接着,会对新进程的内存空间进行一些操作。包括:
初始化进程地址空间:

创建PEB,主要调用函数MmCreatePeb,在这里会创建一个关键的结构VAD,所谓VAD(Virtual Address Descriptor),就是虚拟地址描述符。VAD记录了一个进程内各个内存段的分段信息,要深入了解用户空间(3环)如何管理的,可以深入研究这个函数。网上也有一些关于VAD的资料,比如这篇:http://blog.csdn.net/saict/article/details/3572691

创建用户地址空间

此外,PspAllocateProcess还会做设置虚拟内存保护等工作。
以上工作都完成后,操作系统会调用ExCreateHandle创建进程句柄,并写入EPORCESS结构体中,这是为了操作系统之后好管理新进程而必须做的:

最后,向EPROCESS中写入进程创建的时间,这使得我们可以在程序运行时,调用一些与运行时长相关的API:

总流程图:

创建进程的过程.doc
创建进程的过程.pdf
IDB:
ntoskrnl.7z.001.rar
ntoskrnl.7z.002.rar
ntoskrnl.7z.003.rar
请将IDB文件的后缀名“.rar”删除后用7z解压
