首页
论坛
课程
招聘
[原创]PE映像切换技术(Process Hollowing)不需要填充IAT表和进行重定位的原因
2021-6-8 11:03 4670

[原创]PE映像切换技术(Process Hollowing)不需要填充IAT表和进行重定位的原因

2021-6-8 11:03
4670

记下笔记以及一点小思考,让诸位见笑啦

问题阐述

在学习了PE加载器的实现原理后,我发现这个加载PE的过程与PE映像切换技术(Process Hollowing)有点类似,区别在于后者并没有去填充内存中PE文件的IAT表与进行重定位。那同样是把PE文件手动从硬盘里加载到内存中运行,为什么PE映像切换技术就没有去对内存中PE文件的IAT表进行操作呢?

 

为了叙述的完整,先介绍一下PE映像切换技术与PE加载器的具体原理。

PE映像切换技术(Process Hollowing)

image-20210607201713573

 

效果就是套一个傀儡进程的壳来执行我们希望执行的其他PE文件,《逆向工程核心原理》中给出的代码实现如下:

 

1、创建挂起的傀儡进程

1
CreateProcess(NULL, FakeProcesssPath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)

2、卸载掉原来的模块

1
2
3
4
5
6
7
8
9
10
11
12
// 通过PEB获取傀儡进程的映像基址
GetThreadContext(pi->hThread, &ctx)
ReadProcessMemory(
            pi->hProcess,
            (LPCVOID)(ctx.Ebx + 8),     // ctx.Ebx = PEB, ctx.Ebx + 8 = PEB.ImageBase
            &dwFakeProcImageBase,
            sizeof(DWORD),
            NULL) )
...
// 卸载原傀儡进程映像
pFunc = GetProcAddress(GetModuleHandle(L"ntdll.dll"), "ZwUnmapViewOfSection");
(PFZWUNMAPVIEWOFSECTION)pFunc)(pi->hProcess, (PVOID)dwFakeProcImageBase);

3、写入新的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 从硬盘上读取目标PE文件
hFile = CreateFile(RealProcessPath, GENERIC_READ, FILE_SHARE_READ, NULL,  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
dwFileSize = GetFileSize(hFile, NULL);
ReadFile(hFile, pRealFileBuf, dwFileSize, &dwBytesRead, NULL);
CloseHandle(hFile);
...
// 根据PE文件的偏移量得到各个PE头
// DOS头在PE文件的最开始
PIMAGE_DOS_HEADER       pIDH = (PIMAGE_DOS_HEADER)pRealFileBuf;
// NT头的偏移量 = pIDH->e_lfanew, 可选头相对于NT头的偏移量 = 0x18 
PIMAGE_OPTIONAL_HEADER  pIOH = (PIMAGE_OPTIONAL_HEADER)(pRealFileBuf + pIDH->e_lfanew + 0x18);
// 节区头的偏移量 = NT头的偏移量 + NT头的大小, 因为节区头位于NT头的后面
PIMAGE_SECTION_HEADER   pISH = (PIMAGE_SECTION_HEADER)(pRealFileBuf + pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS));
// 在傀儡进程中,目标PE文件基址的地址处,分配目标PE文件大小的内存
pRealProcImage = (LPBYTE)VirtualAllocEx(
    pi->hProcess,
    (LPVOID)pIOH->ImageBase,
    pIOH->SizeOfImage,
    MEM_RESERVE | MEM_COMMIT,
    PAGE_EXECUTE_READWRITE)
// 写入PE头
WriteProcessMemory(
        pi->hProcess,
        pRealProcImage,
        pRealFileBuf,
        pIOH->SizeOfHeaders,
        NULL);
// 写入各节区
for( int i = 0; i < pIFH->NumberOfSections; i++, pISH++ ){
    if( pISH->SizeOfRawData != 0 ){
        // 这里注意要将各节区写到对应的 映像基址+RVA 处的内存中
        if( !WriteProcessMemory(
                ppi->hProcess,
                pRealProcImage + pISH->VirtualAddress,
                pRealFileBuf + pISH->PointerToRawData,
                pISH->SizeOfRawData,
                NULL) ){
            printf("WriteProcessMemory(%.8X) failed!!! [%d]\n",
            pRealProcImage + pISH->VirtualAddress, GetLastError());
            return FALSE;
        }
    }
}

4、恢复现场

1
2
3
4
5
6
// 把傀儡进程的控制流修改到目标PE的入口处
GetThreadContext(pi->hThread, &ctx);
// Eax寄存器保存的值为程序的入口点地址
ctx.Eax = pIOH->AddressOfEntryPoint + pIOH->ImageBase;
SetThreadContext(pi->hThread, &ctx);
ResumeThread(pi.hThread);

PE加载器

在看雪知识库的Windows安全-系统篇-PE格式-PE文件的加载章节收录了许多PE加载器的实现文章。实现上只要模拟操作系统加载PE文件的方式来做就可以,简单来说分为以下几个步骤:

 

1.申请一块内存,将PE文件由硬盘加载到内存中,这部分与上文基本相同

 

2.修复重定位

 

根据重定位表的内容,把PE文件中的对应位置的硬编码地址进行重定位

 

例如,假设该PE文件的默认基址为0x00400000,加载到内存后的基址为0x00100000

 

image-20210608095023797

 

就是把RVA为10F5处的硬编码地址0x00467C28重定位为0x00167C28,即减去原基址再加上实际的基址

 

image-20210608094556603

 

3.加载导入表

 

先根据IDT里的dll名称用LoadLibrary()加载对应的dll

 

image-20210608100629664

 

然后到对应dll的导入表项(理论上应该去INT中,但实际上这俩表内容是一样的)中用GetProcAddress()获取导入的函数地址,并写到导入表的相应位置

 

image-20210608100842238

 

4.跳转到PE的入口点处执行

问题解答

问题的关键就是因为PE映像切换技术使用了CreateProcess()来挂起创建一个傀儡进程,当恢复线程执行后还会进行一些进程初始化的工作,所以不用我们手工的去填IAT表和进行重定位。

 

image-20210608103328608

 

进程初始化有一部分实在新的进程中进行的,就比如IAT表的填充和重定位。当初始线程启动时,首先会执行KiThreadStartup,把目标线程的IRQL从DPC级降低到APC级。然后调用PspUserThreadStartup,将用户空间ntdll.dll中的函数LdrInitializeThunk作为APC函数挂入APC队列,再企图返回到用户空间,执行LdrInitializeThunk,正是在这个函数中,进行了IAT表的填充以及重定位。

参考

《逆向工程核心原理》

 

Process Hollowing and Portable Executable Relocations

 

常见进程注入的实现及内存dump分析——Process Hollowing(冷注入)

 

PE加载器的简单实现

 

一种保护应用程序的方法 模拟Windows PE加载器,从内存资源中加载DLL

 

《深入解析Windows操作系统》

 

《漫谈兼容内核》


[培训] 优秀毕业生寄语:恭喜id:一颗金柚子获得阿里offer《安卓高级研修班》火热招生!!!

收藏
点赞2
打赏
分享
最新回复 (6)
雪    币: 2393
活跃值: 活跃值 (688)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
htpidk 活跃值 2021-6-9 10:10
2
0
顶一个。。
雪    币: 29
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
いろはね 活跃值 2021-6-22 16:53
3
0
顶一个
雪    币: 28
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
肱桡鸡 活跃值 2021-7-7 02:33
4
0
楼主有一点说的不准确,如果原imagebase无法virtualalloc到的话,是需要进行手动修复重定位表的
雪    币: 2033
活跃值: 活跃值 (1050)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
危楼高百尺 活跃值 1 2021-7-7 15:06
5
0
mb_aydzspbs 楼主有一点说的不准确,如果原imagebase无法virtualalloc到的话,是需要进行手动修复重定位表的
确实,感谢补充
雪    币: 525
活跃值: 活跃值 (240)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_qmomcleg 活跃值 2021-7-8 07:56
6
0
雪    币: 5
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
麦瑞鸭 活跃值 2021-7-8 11:57
7
0
收藏吃灰
游客
登录 | 注册 方可回帖
返回