前言:
傀儡进程是将目标进程的映射文件替换为指定的映射文件,替换后的进程称之为傀儡进程;常常有恶意程序将隐藏在自己文件内的恶意代码加载进目标进程,而在加载进目标进程之前,会利用ZwUnmpViewOfSection或者NtUnmapViewOfSection进行相关设置
相关技术要点
1.创建挂起进程
系统函数CreateProcessW中参数dwCreation传递CREATE_SUSPEND(0X4)便可以创建一个挂起的进程,进程被创建之后系统会为它分配足够的资源和初始化必要的操作,(常见的操作有:为进程分配空间,加载映像文件,创建主进程,将EIP指向代码入口点,并将主线程挂起等)
查看MADN,下面是CreateProcessW的原型:
创建挂起进程实例代码:
2.保存现场,收集信息
傀儡进程在替换目标进程之前,必须要保存当前线程的上下文环境,在替换完成后要及时恢复。这样系统才能将傀儡进程视为“正常”进程,而不会被发现。另外为了后边清空内存空间的操作,也必须要通过上下文获得进程的加载基地址。利用系统函数GetThreadContest()便可得到当前线程上下文。相关的API和结构信息如下:
获取线程信息实例代码:
3.清空目标进程
目标进程被初始化后,进程的映像文件也随之被加载进对应的内存空间。傀儡进程在替换之前必须将目标进程的内容清除掉。此时要用到另一个系统未文档化的函数NtUnmapViewOfSection,需要自行从ntdll.dll中获取。该函数需要指定的进程加载的基地址,基地址即是从第2步的上下文取的。相关的函数说明及基地址计算方法如下:
context.Ebx + 8 = 基地址,因此从context.Ebx + 8的地址读取4字节的内容并转化为DWORD类型,即是进程加载的基地址。
示例代码如下:
4.重新分配空间
在第3步中,NtUnmapViewOfSection将原始空间清除并释放了,因此在写入傀儡进程之前需要重新在目标进程中分配大小足够的空间。需要用到跨进程内存分配函数VirtualAllocEx:
一般情况下,在写入傀儡进程对应的文件按照申请空间的首地址作为基地址进行“重定位”,这样才能保证傀儡进程的正常运行。为了避免这一步操作,可以以傀儡进程PE文件头部的建议加载基地址作为VirtualAllocEx的lpAddress参数,申请与之对应的内存空间,然后以此地址作为基地址将傀儡进程写入目标程序,就不会存在重定位问题。关于“重定位”的原理可以自行网络查找相关资料。示例代码如下:
5.写入傀儡进程
准备工作完成后,现在开始将傀儡进程的代码写入到对应的空间中,注意写入的时候要按照傀儡进程PE文件头标明的信息进行。一般是先写入PE头,再写入PE节,如果存在附加数据还需要写入附加数据。示例代码如下:
6.恢复现场并运行傀儡进程
在第2步中,保存的线程上下文信息需要在此时就需要及时恢复了。由于目标进程和傀儡进程的入口点一般不相同,因此在恢复之前,需要更改一下其中的线程入口点,需要用到系统函数SetThreadContext。将挂起的进程开始运行需要用到函数ResumeThread: