PE映像
PE映像就是PE文件在内存中的映射形态,这在之前PE文件学习中也讲过,32位下OS为进程开辟4GB虚拟空间,低2G为用户空间,高2G是内核空间。
PE文件映射到内存空间中是一个拉伸的过程
PE映像切换
PE映像切换就是先以挂起的方式运行某个进程(A.exe),然后将完全不同的一个PE文件(B.exe)的PE映像映射到A.exe的进程内存空间中,并在A.exe的内存空间中运行。
此时虽然运行的进程名称是A.exe,但实际映射在内存中的PE映射却是B.exe,所以最终的行为动作与A.exe原来的行为动作完全不同。
示例程序:Fake.exe、Real.exe、DebugMe3.exe
Fake.exe
Real.exe
DebugMe3.exe
DebugMe3.exe是以fake.exe为外壳,real.exe为内核程序
要注意在控制台窗口输入运行命令时,命令的第一个参数为充当外壳进程的可执行文件路径,第二个参数为充当内核进程的可执行文件路径
调试
Open-输入运行参数
以参数的方式打开DebugMe3.exe
这里就是EP代码
main()函数分析
代码流程图
CreateProcess()和ResumeThread()的行为动作明确,所以我们主要分析main调用的三个子函数
FUNC_1_401150
该函数是将real.exe文件读取到内存,将内存地址记为MEM_FILE_REAL_EXE
CreateProcess(“fake.exe”,CREATE_SUSPENDED)
调用CreateProcess创建fake.exe进程,此时的进程处于挂起状态,同时也是暂停状态,此时可以自由操作其对应的内存。
FUNC_2_4011D0
这个函数里面实现了PE映像切换技术,我们逐步分析
获取fake.exe进程的实际映射地址
这是获取fake.exe的主线程上下文,然后我们可以通过它来获取进程的PEB,由PEB.ImageBase得到进程的实际映射地址
[EBP-2D0]是ConText结构体,[EBP-22C]是ConText偏移A4的成员,也就是Ebx
因为当前的fake.exe是以挂起模式创建的,处于暂停状态,进程被创建出来时,PE装载器就会把PEB结构体的地址设置给Ebx寄存器,所以当我们获取到Ebx寄存器的值就必须先获取进程主线程的上下文
此时的ECX就是PEB首地址,偏移为8的地方就是ImageBase,所以先执行add ecx+8
- PS:对于Win XP的EXE文件来说,PE文件头中的ImageBase就是进程的实际映射地址
- PS:Win Vista(内核6以后),引入了ASLR技术,ImageBase与实际映射地址不在相同
获取real.exe文件的ImageBase
EDI寄存器的值为MEM_FILE_REAL_EXE地址,该地址是在FUNC1中分配得到的内存起始地址,而real.exe被原封不动的保存在其中。也就是说EDI指向real.exe文件的PE头,所以EDI+3C就是IMAGE_DOS_HEADER结构体中的e_lfanew
[EDI+EAX]=IMAGE_NT_HEADER的起始地址
[EDI+EAX+34]指向IMAGE_OPTION_HEADER的ImageBase成员
执行上面指令后,real.exe的ImageBase就被存入了ECX寄存器中
比较ImageBase of fake.exe和ImageBase of real.exe
两值相同时
如果相同就继续往下执行,如果不相同就跳到004012EA
当fake.exe的PE镜像已经映射到400000地址处,地址400000也是real.exe的PE映像要映射的地方,若强行映射到该地址处,就会发生冲突,必须先卸载(Unmapping)fake.exe的PE映像的映射。由于fake.exe进程处于挂起状态,所以卸载PE映像时进程中也不会发生错误。
NTSYSAPI NTSTATUS ZwUnmapViewOfSection(
[in] HANDLE ProcessHandle,
[in, optional] PVOID BaseAddress
);
两值不同时
这时候不一定要卸载fake.exe的PE映像,可以先在fake.exe进程的虚拟内存空间中为real.exe的PE映像分配所需要的空间,然后将real.exe映射进去就行了。接下来还得告诉PE装载器,fake.exe进程的PE映像是400000地址处的real.exe,而不是 1000000地址处的fake.exe,简单修改PEB的ImageBase成员就行了
通过调用WriteProcessMemory()API成功将fake.exe进程的PEB.ImageBase改为real.exe文件的ImageBase值,此时fake.exe进程的PE映就被卸载了。
FUNC_3_401320
现在需要把real.exe的文件映射到fake.exe进程中,main函数的4010D1地址调用了FUNC_3_401320,进入FUNC3分析
为real.exe的PE映像分配内存
这个就是为real.exe的PE映像分配内存
第二个参数是想要分配的内存起始地址,其值为real.exe的ImageBase(0x4000000);函数的第三个参数是要分配内存大小,其值为real.exe的SizeofImage(0xA000);
映射PE文件头
为real.exe的PE映像分配好内存空间后,接下来就是要将real.exe映射到fake.exe进程中。
调用WriteProcessMemory()API将real.exePE文件头写入到刚刚分配的内存区域,0x262CF8是由FUNC1函数读入到debug3.exe内存中的
BytesToWrite参数为real.exe的sizeofHeader(0x4000)
映射节区
启动循环,根据节区的数量反复调用WriteProcessMemory,映射PE节区。该过程需要读取的信息是读取PE文件头的IMAGE_SECTION_HEADER。循环结束后,real.exe文件被完全映射到fake.exe进程的40000地址处,但是此时real.exe代码还无法正常运行,需要修改进程的EP.
修改EP
现在fake.exe处于挂起状态,将其恢复运行后,进程的主线程会运行何处的代码呢?这时候我们需要查看主线程CONTEXT结构体中的Eip成员
CONTEXT中有两个成员需要注意,一个就是EAX与EIP,分别位于结构体偏移B0和B8处,EAX=0x00401041,这个值很可能位于fake.exe进程中,我们用PE工具查看一下
如图所示,EAX值是fake.exe进程的EP地址,接下来看CONTEXT.EIP,其值为0x77C67098,在调试器中查看一下该地址
可以发现这个地址是ntdll.RtlUserThreadStart()API开始的地址,我们已知EIP寄存器存储的是要执行的指令代码的地址,而EAX存放返回地址,查看CONTEXT的EAX和EIP后,我们可以整理一下上面的代码执行过程:处于挂起状态的fake.exe进程恢复运行,首先会调用ntdll.RtlUserThreadStart(),跳转到EP地址。前面已经将fake.exe的进程PE映像替换为了real.exe的PE映像,所以我们只需要将CONTEXT.EAX修改为real.exe的EP地址(0x401060)
0x4014A2-0x4014B3就是将real.exe的EP地址设置到CONTEXT.EAX中
ResumeThread()
最后调用ResumeThread(),恢复运行fake.exe进程。
通过上面的调试练习,我们学会了PE映像切换技术的工作原理。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 767778848@qq.com