CreateProcess

Windows 进程创建完整过程(除去细节)

当前流程是分析WinXP x86得到的,在最新版本Windows上不一定正确,但是可以做一个参考,

由于我这里符号并不全,所以导致我这里有些东西看到的可能是错误的,误导了我,然后我就做了个错误的记录,

有缘人如果看到的话,可以帮我指正一下,我会很高兴。

工作挺忙的,三天的业余时间,哎,个人水平问题吧,还是没有办法详细地分析出完整套路,算是个简要分析吧。

 

差点忘记了,我这里分析的文件是 kernel32.dll 和 ntoskrnl.exe

 

 

1:入口  CreateProcessW

当我们在应用层调用CreateProcessW的时候,参数稍作整理,会直接调用到 CreateProcessInternalW

(CreateProcessA,会调用 CreateProcessInternalA,然后整理了参数也会调用到CreateProcessInternalW)

 

 

2:CreateProcessInternalW

首先,开场是大概是2000来行的汇编指令,判断进程创建参数,以及文件安全属性等等乱七八糟没用的,

 

然后是 NtOpenFile NtCreateSection 一系列的函数,获取文件句柄和section句柄

 

中间经过一系列的文件类型判断,有效性判断,及属性判断,

 

调用 NtQuerySection 获取段属性,

 

判断是否需要Debug方式启动,并且做对应的设置,

 

加载 advapi32.dll ,然后获取 CreateProcessAsUserSecure 函数地址,但是并没有使用它,

 

很可能是根据这个函数是否存在,来判断当前操作系统版本,后面直接调用 NtQuerySystemInformation 来获取操作系统信息,参数很奇怪,0x47,可能就是特殊的情况,

(有空再看)

 

中间判断了一个位,然后调用了DBG相关的函数,(可能是判断DBG相关吧)

 

最后就是一个Nt函数,NtCreateProcessEx,进入内核了,(也可能是个Zw函数)

 

 

3:NtCreateProcessEx

上一部分,调用ntdll 的ZwCreateProcessEx/NtCreateProcessEx 之后,

进入内核,走SSDT表,第48项,进入NtCreateProcessEx,

 

判断当前执行模式,内核模式的话,就往下走,否则,改了模式往下走

调用PspCreateProcessEx,

 

1)获取父进程信息,

并且继承父进程的执行位置

 

2)给子进程创建一个EPROCESS,然后初步设置它

 

3)初始化子进程的线程列表

 

4)增加引用计数,并且继承父进程的QuotaBlock,如果父进程存在的话,否则用系统默认的

 

5)继承父进程的DeviceMap,如果有父进程,否则用系统默认的

 

6)如果父进程存在,那么再继承父进程的属性

 

7)如果参数Section句柄存在,那么就获取对象,并且保存,后面需要用,保存到进程中

这个Section,实际上是上面应用层CreateProcessInternalW函数创建的那个Section

 

8)如果Debug端口存在,那么就获取它的对象,如果不存在,则从父进程继承

上面的第一个jnz跳转到下面,下面的最后一个jmp回到上面的cmp处

 

9)初始化PrimaryToken

 

10)中间继续初始化各种乱七八糟东西,包括

初始化进程地址空间

如果当前进程EProcess不存在,就用另外一种方法,初始化进程空间

PCB

优先级组,因为后面直接就用EPROCESS了,所以我推断这里可能同时也初始化了EPROCESS剩下的部分

 

11)如果 section存在,那么attach到子进程,

在很深的位置先循环调用了N次 MiMapViewOfPhysicalSection 函数来映射物理内存地址,然后

初始化section,根据section展开文件到子进程

这里有else,但是一般来说,不会走到else里面,因为如果是有效PE,那么section肯定存在,这是应用层已经找到的,然后转化成的内核对象,

然后向自己内部再影射一个子进程模块的内存,映射了之后,就释放掉,只是判断是否成功,如果成功,什么都不干,不成功就返回到label_48

(走到else里面也是同样的操作,只不过目标section不存在,那么就只能映射自己的)

 

///中间有一块这段代码,个人能力问题,无法看出这里是做什么的,因为结构体识别可能是有一定的问题

/// 

 

12)初始化PEB

PEB

实际上这里有点问题,v74在前面的时候,是必须 hSection 存在的时候,才会 ==1,也就是说必须hSection存在,才会进入外层if

但是外层if进入了之后,反过来说,就是hSection肯定存在,肯定不会走else,但是为什么它这么写

 

13)初始化APC?不知道是否真的是这样,但是清理APC的功能,这里是第一次使用

然后判断APC对列里面有没有APC,如果有的话,触发一个软中断,去运行它

 

14)做一个 AccessState ,然后使用它把子进程EPROCESS放到进程句柄表中,返回一个句柄

然后AccessState就没用了,释放掉

最后设置一下进程优先级组

 

15)接近尾声,这里获取当前进程允许的访问权限位

 

16)收尾

设置进程创建时间,然后返回进程句柄,减掉自己的引用计数,防止泄露

 

 

4:CreateProcessInternalW,回到应用层了

沿着第二部分的NtCreateProcessEx继续来看,这里已经回到应用层了,

继续往下,并且这里拿到了一个 新进程的句柄,也就是第一个参数

 

一大片代码,设置进程优先级和处理模式相关

NtSetInformationProcess

NtAllocateVirtualMemory

申请了一块内存,然后好像也没用过,

创建命令行参数,

初始化stdin stdout stderr

BasePushProcessParameters

就是直接dup出来,然后给对面进程用

给目标进程创建一个栈

给线程构建上下文

然后,主线程就可以跑了

(上下文构建中,其实有个小细节)

其实在 BaseInitializeContext 函数中,根据第五个参数,会判断走哪个启动函数

如上,进程启动,最后一个参数,写死为0,则会走最下面的 BaseProcessStartThunk 函数

而 BaseProcessStartThunk 内部还调用了。。。

再往里看,就是这样

通过对比另外两个函数,可以推断,这里应该就是走主线程 _tmainCRTStartup / wWinMainCRTStartup 位置的了

 

 

5:NtCreateThread 开始主线程部分

应用层陷入内核,走SSDT,到了驱动里面的NtCreateThread,

进来还是模式校验,

然后走

 

1)获取进程对象

 

2)创建ETHREAD

 

3)在进程句柄表中创建线程的handle

 

4)创建TEB

初始化TEB

 

5)中间使用了超级大篇幅来初始化这个ETHREAD

初始化了之后,这一块,就是准备开跑了,

进程的活动线程数++,然后插入列表,然后启动线程,

 

6)直到这里,这里是进程创建回调,注意哦,这里是在父进程里面调用的

 

7)判断作业是否在工作,所在进程是否在作业中,如果在的话,特殊处理,让他去完成,并且清理APC

 

8)压轴戏,线程创建回调

 

9)两个回调结束之后,似乎就没什么好做得了,把线程对象插入句柄表

这里的插入和前面的创建不是一个东西,那个ExCreateHandle 是创建全局句柄表,

这里是插入进程句柄表

 

10)后面就是枯燥乏味的收尾工作

写时间戳,写访问权限,解对象引用,

再清理一次APC,

然后把线程句柄返回,

 

11)补充一下吧

正常结束时,这里实际上是有个收尾的小工作的,

这里面KeReadyThread 是关键操作,它把 ETHREAD 放到了进程 KPROCESS 的 ReadyListHead 里面,

这样应该就可以swapcontext了,

其实它内部还有众多关键函数,如 KiSetSwapEvent ,看名字和内部实现,似乎就是抢抢占时间片去了。

 

我所关心的整个部分,实际上就是,

进程、线程创建回调的触发时机,实际上触发的进程上下文都在父进程中,触发时机都在PspCreateThread中,

因为实际工作中,我们能用到的部分,也就这里了

 

 

6:回到应用层

这里直接判断返回值,有问题的话,直接错误返回,

没有问题的情况下,那么继续往下做判断

 

1)通知Csrss

由于我这里符号不全,我想下面的ExitStatus 应该是 Csrss返回的吧。

一旦有问题,立刻退出

 

2)指派进程到一个作业中

 

3)后面直到最后,通篇都是整理内存,释放空间了,

基本上就没有干别的活,

最后才是函数返回。

 

这样,整体流程结束。

 

 

补充一下,镜像加载回调的位置吧,

这个,实际上,镜像加载回调被调用的时候,已经和镜像没什么关系了,

而且镜像早早已经被铺到内存中了

在InitThread之前,是初始化TEB部分,这里初始化了TEB之后,设置了一个回调函数

创建应用层线程PspUserThreadStartup

进入这个函数里面,可以看到

有这样一个判断,由于我实在没有找到那个6代表什么,可能是2|4,但是我没找到,所以无法知道它是什么,

进入函数之后,里面会进行一次镜像加载回调状态的判断,如果非隐藏状态,那么再经过一个函数,

就会到这里,联系外面的函数,可以清晰地看出,这里就是镜像加载回调的调用位置,

总地来说,由于镜像加载回调有可能是异步调用的,所以无法确切地知道它的位置,

但是它的位置实际上是在前两个回调之后的(其实已知,这些都是废话)

 

 

 

重点:

在CreateProcess 函数中,会开辟子进程的进程空间,

然后同时会map子进程的主模块到进程空间中,这时不会调用镜像加载回调和进程创建回调,

到了CreateThread 里面,整理了线程各种信息之后,

会先调用进程创建回调,

然后调用线程创建回调,

这时,当前进程上下文还是在父进程中,

最后,当线程跑起来之后,

第一个回调会被触发,就是主模块的镜像加载回调,

这时剩下的就是其他模块的镜像加载回调了,

后面就不是很重要了,前面这个流程应该是最重要的。

posted @ 2016-09-08 17:59  穷到底  阅读(3434)  评论(0编辑  收藏  举报