一、背景
这篇文章的起因,是笔者之前在做样本分析的时候,经常会遇到需要调试傀儡进程的情况,而其中有一种情景是将启动的白进程PE文件整个掏空并用黑进程进行替换。
为了确保被替换后的进程能顺利执行不崩溃,需要获取原进程各种上下文,并修改被替换后的新进程上下文,其中在原进程被挂起还没开始执行的时候,需要将eax指向新oep,而ebx指向新peb,而为什么这样设置的原因却很少有人提及。为此,在经过查阅了一定的资料与简单的分析后,我们可以找到答案。
二、具体分析
先抛出结论,这里的eax与ebx属于线程上下文信息,在一个PE文件开始被运行的过程中,主线程上下文初始化过程是在进程已经创建完成,而主线程还没创建的阶段发生的,下面是具体更详细的分析:
首先我们需要对进程的创建有一个大概的认识,在ring3下创建进程API无非是CreateProcessA/W,但是无论调用哪一个,最终都会将相关参数转化为Unicode字符串,并最终调用CreateProcessInternalW,因此以下将主要分析CreateProcessInternalW,而在xp和win7下,它具体实现又有一些不一样的地方。
2.1 XP下执行流程
在xp下,它大概分为四个部分,分别是ring3下创建进程,ring0下创建进程,ring3下创建线程,ring0下创建线程,以NtCreateProcessEx为分界线,NtCreateProcessEx之前为ring3下创建进程主要流程。
2.1.1Ring3下创建进程
1. 判断处理dwCreationFlag各种标志位,包括是否包含不合法标记组合,判断优先级。
2. 判断lpEnvironment是否为空,
不为空则调用RtlAnsiStringToUnicodeString将其转为UniCode字符串。
3. 判断lpApplicationName、lpCommandLine是否为空。
如果lpApplicationName不为空直接调用RtlDosPathNameToNtPathName_U函数。
将DOS路径(C:\WINDOWS\XXX)转换为NT路径(\Device\HarddiskVolume1\WINDOWS\XXX),
为空则会解析lpCommandLine,主要按照’”’引号,’ ’空格,’t’制表符作为分隔符进行解析并获取相应的PE文件,然后将DOS路径转换为NT路径。
4. 调用NtOpenFile得到文件句柄,调用了NtCreateSectiond函数得到内存区对象句柄。
5. 调用BasepIsProcessAllowed函数, 该函数用来判断应用程序名是否在授权文件列表中。
6. 之后会经过一大段的函数进行各种校验,再得到内存区对象句柄后调用NtQuerySection函数,
返回后得到节的基本信息(节基地址,大小,属性),并判断创建标志中是否包含DEBUG_PROCESS或者DEBUG_ONLY_THIS_PROCESS,
如果不包含该标志,则判断PEB->ReadImageFileExecOptions域是否为0,
如果包含DEBUG_PROCESS或者DEBUG_ONLY_THIS_PROCESS,或者不包含该标志但ReadImageFileExecOptions域不为0,
调用LdrQueryImageFileExecutionOptions函数查询该信息。
7. 检查镜像文件的部分信息的有效性,并调用函数BasepIsImageVersionOk判断镜像文件版本是否合法。
8. 加载advapi32.dll并获得CreateProcessAsUserSecure函数的地址。
9. 调用BaseFormatObjectAttributes将安全属性结构格式为NT对象属性结构(得到了对象属性),接着调用了_DbgUiConnectToDbg在实现通过调用NtCreateDebugObject函数来创建调试对象,
调用DbgUiGetThreadDebugObject来获得调试对象(作为参数传递到0环)。
10. 最后调用NtCreateProcessEx函数。
2.1.2Ring0下创建进程
NtCreateProcessEx内为ring0下创建进程主要流程。
判断父进程是否存在,不存在则退出,否则,调用PspCreateProcess。
在PspCreateProcess中,保存当前线程运行的前一个模式。通过KTHREAD->PreviousMode可以得到前一个模式。判断创建标志是否包含除DEBUG_PROCESSDEBUG_ONLY_THIS_PROCESS,CREATE_SUSPENDED之外其它标志, 如果包含其他的标志,则报错退出。
3. 通过参数ParentProcess调用ObReferenceObjectByHandle函数得到父进程对象的指针。
4. 判断参数 JobMemberLevel是否为0, 如果不为0,接着判断父进程的EPROCESS->Job是否为0,如果JobMemberLevel不为为0且EPROCESS->Job为0,则返回无效参数错误后退出该函数;否则的话,将父进程对象中的属性保存到局部变量中。
5. 调用ObCreateObject函数创建新进程对象并将对象内容初始化为0,然后从父进程继承配额信息(PspInheritQuot)和设备位图信息(ObInheritDeviceMap),将父进程对象中的部分域给新进程。
6. 判断参数SectionHandle是否为0,若不为0,调用ObReferenceObjectByHandle函数得到区对象指针,然后将区对象指针赋值给新进程EPROCESS的相应域。
7. 接着就判断参数DebugPort是否为0,若不为0,调用ObReferenceObjectByHandle函数通过调试对象句柄得到调试对象指针,否则调用DbgkCopyProcessDebugPort函数从父进程拷贝DebugPort给新进程。
8. 判断参数ExceptionPort是否为0,若不为0,调用ObReferenceObjectByHandle函数通过异常端口对象句柄得到异常端口对象指针。
9. 接着调用PspInitializeProcessSecurity函数来设置新进程的安全属性, 主要是设置新进程的安全令牌对象。该函数会调用SeSubProcessToken函数来设置新进程对象的令牌对象。
10. 接着调用MmCreateProcessAddressSpace为新进程创建地址空间,并构建页目录表、页表及物理页的关系。
11. 调用KeInitializeProcess函数初始化新进程对象中内核对象、优先级、亲和性、页目录表物理地址帧号。
12. 调用ObInitProcess函数来初始化新进程对象的表。
13. 调用MmInitializeProcessAddressSpace函数初始化进程地址空间,该函数的实现中调用了KiAttachProcess函数来实现进程的切换(将当前线程挂靠到新进程中),以及初始化EPROCESS中的部分域和PFN、工作集列表等。
14. 调用PspMapSystemDll函数映射新进程对象的系统DLL(即NTDLL,映射第一个DLL),该函数会调用MmMapViewOfSection映射节区,而MmMapViewOfSection会调用MiMapViewOfImageSection函数将DLL作为镜像映射。
15. 接着调用MmGetSessionId函数获得指定进程的会话ID,然后调用SeSetSessionIdToken函数设置令牌的会话ID,
之后再调用ExCreateHandle函数在PspCidTable中添加一项(PID)。
16. 调用MmCreatePeb为新进程创建PEB,该函数首先通过调用KeAttachProcess函数将当前线程切换到新进程对象,然后通过MmMapViewOfSection函数将NLS节区映射到新进程的地址空间中,随后调用MiCreatePebOrTeb创建PEB/TEB。
在MiCreatePebOrTeb函数中首先会通过ExAllocatePoolWithTag来申请0x34大小的空间,接着通过MiFindEmptyAddressRangeDownTree函数在VAD树中查找一块未被使用的地址空间范围,并返回该范围的起始地址,最后通过MiInsertVad函数将申请的地址空间插入到VAD树中。
在创建PEB结构后,初始化PEB中部分域的值(镜像基地址,操作系统编译号等域),最后调用KeDetachProcess函数使线程回到原来的线程中。截止此步骤,PEB创建完成。
同时观察也可以发现,这里也解析了包括Nt头、扩展头、扩展头魔术字效验等关键PE结构信息,联想到之前分析流程也处理了一部分PE结构,可以猜测早期的PE文件结构逆向可能也是通过逆向进程创建过程,即逆向CreateProcess API来实现的。
17. 最后将新进程对象EPROCESS.ActiveProcessLinks更新为全局的活动进程链表(PsActiveProcessHead), 判断父进程是否为系统进程,调用SeCreateAccessStateEx设置访问状态,
调用ObInsertObject函数将进程对象加入到进程对象的句柄表中,并通过KeQuerySystemTime(获取当前系统时间)结束PspCreateProcess的调用,完成ring0下进程的创建。
接下来我们回到CreateProcessInternalW,以NtCreateThread为分界线,NtCreateProcessEx之后到NtCreateThread之前为ring3下创建线程流程,而NtCreateThread内则是ring0下创建线程流程,经过分析发现,我们所需要寻找的线程上下文设置其实就在ring3下创建线程流程内。