LoadPE 被加载PE文件代码分析(ASM汇编版本)>>03
2026/6/20 10:27:57 网站建设 项目流程

目录

一、整体加载流程概览

二、详细实现步骤(汇编思路)

阶段0: OEP环境设置引用等

确定OEP加载程序

需要加载的程序大小

编译器链接器解决基地址(到时候在看看)

申请LoadPE所需要的空间

加载的过程

包引用环境变量等

查看基地址是否在400000地址加载

定义全局变量

三、阶段分析

阶段 1:打开目标文件

阶段 2 & 3:文件映射到虚拟内存

阶段 4:解析 PE 结构(核心阶段)

阶段 5:跳到目标程序运行

四、汇编实现的核心特点

五、总结

一、整体加载流程概览

LoadPE 的加载过程主要分为5 个大阶段,共 10 余个具体步骤:

  1. 打开文件

  2. 映射虚拟内存

  3. 获取虚拟内存

  4. 解析 PE 结构(最核心、最复杂的一步)

  5. 跳到目标程序运行

二、详细实现步骤(汇编思路)

阶段0: OEP环境设置引用等
确定OEP加载程序
  1. 首先要确定加载的程序的入口点也就是OEP

  1. 但是加载到目标主机 可能0x40000被占用

  2. 所以我们loadPE需要解决这个问题

需要加载的程序大小
  1. 可选头里面有个sizeofImage 394000也就是这个大小

编译器链接器解决基地址(到时候在看看)
  1. 工程选项 设置链接器

申请LoadPE所需要的空间
  • 也就是在程序入口点code的位置后面

  • 申请394000H 字节的空间给我的 LoadPE函数加载的程序

IMAGE_SIZE EQU 394000H .code ORG IMAGE_SIZE ; 也就是LoadPE函数的其实位置是394000H + 1H LoadPE PROC ; Function ret LoadPE endp
加载的过程

包引用环境变量等
  • 使用的模式

.386 .model flat,c option casemap:none
  • 引用的宏静态库

.const ; constant IMAGE_SIZE EQU 394000H .data ; data g_szfile db "PlantsVsZombies.exe" ; file Name .code ORG IMAGE_SIZE LoadPE PROC ; Function ret LoadPE endp start: ; main INVOKE LoadPE ; call function end start
查看基地址是否在400000地址加载

  • 这也就说明成功加载到了0x00400000的位置

  • 400000以后的位置都预留好了 ---> 394000

定义全局变量
LOCAL @hFILE:HANDLE LOCAL @hFileMap:HANDLE LOCAL @szPeBuffer:LPVOID LOCAL @pDosHeader:PTR IMAGE_DOS_HEADER LOCAL @pNtHeader:PTR IMAGE NT HEADERS LOCAL @pSecetionHeader:PTR IMAGE_SECTION_HEADER LOCAL @dwNumberSec:DWORD LOCAL @dwSizeOfHeader:DWORD LOCAL @dwEP:DWORD LOCAL @plmagelmpHeader:PTR IMAGE_IMPORT_DESCRIPTOR LOCAL @ZeroHeadImp: IMAGE IMPORT DESCRIPTOR LOCAL @dwImageBase:DWORD LOCAL @dwMod:HANDLE

三、阶段分析

阶段 1:打开目标文件
  • 使用CreateFileAPI 打开目标程序文件(例如PlantsVsZombies.exe)。

  • 以只读方式打开,获取文件句柄(@hFILE)。

阶段 2 & 3:文件映射到虚拟内存
  • 调用CreateFileMapping,创建一个文件映射对象。

  • 调用MapViewOfFile,将文件内容映射到进程的虚拟内存空间,得到内存指针(@szPeBuffer)。

  • 此时目标 PE 文件的内容以只读的形式存在于内存中,供后续解析使用。

阶段 4:解析 PE 结构(核心阶段)

这是整个 LoadPE 中最重要、最体现汇编功底的部分,需要一步步手动解析 PE 文件格式:

4.1 获取 DOS 头

  • 将映射的内存首地址赋值给@pDosHeader

  • 通过e_lfanew字段定位到 PE 头的位置。

4.2 获取 NT 头

  • 根据 DOS 头的e_lfanew偏移,找到IMAGE_NT_HEADERS

4.3 获取文件头大小(SizeOfHeaders)

  • 从 NT 头的 OptionalHeader 中读取SizeOfHeaders,用于后续拷贝头部。

4.4 获取节数量(NumberOfSections)

  • 从 FileHeader 中读取节的数量,后续用于遍历所有节。

4.5 获取程序入口点(AddressOfEntryPoint)

  • 读取 OptionalHeader 中的AddressOfEntryPoint(RVA),并加上 ImageBase,得到最终入口点地址。

4.6 获取导入表(Import Directory)

  • 从 DataDirectory 中找到导入表的位置(IMAGE_DIRECTORY_ENTRY_IMPORT)。

  • 由于植物大战僵尸是需要api函数,由于我们是脱了操作系统,那就没人帮我获取请api,需要自己手动解析导入表

4.7 获取名称表和 IAT 表

  • 遍历IMAGE_IMPORT_DESCRIPTOR结构,分别获取 DLL 名称 RVA 和 FirstThunk(IAT)、OriginalFirstThunk(INT)。

4.8 加载所需的 DLL

  • 对于每个需要导入的 DLL,调用 LoadLibrary 将其加载到当前进程。

  • 填充IAT表

4.9 修复导入地址表(IAT)

  • 遍历 IAT 中的每个函数:

    • 如果是序号导入,则直接使用序号。

    • 如果是名称导入,则通过 GetProcAddress 获取函数地址。

  • 将获取到的真实函数地址填充回目标程序的 IAT 表中,使其能正常调用系统 API。


阶段 5:跳到目标程序运行
  • 对目标加载地址(通常为00400000)进行VirtualProtect,修改内存保护属性为PAGE_EXECUTE_READWRITE

  • 将解析好的 PE 头部和所有节数据拷贝到目标地址空间。

  • 完成所有准备工作后,使用JMP指令直接跳转到目标程序的入口点(@dwEP)。

  • 此时控制权完全交给目标程序,LoadPE 的使命完成。

四、汇编实现的核心特点

  • 全程手动操作:几乎所有 PE 结构字段都需要通过寄存器偏移手动读取和计算。

  • 寄存器密集:由于汇编无法直接进行内存到内存的操作,大量使用MOVADDLEA等指令,以及ASSUME伪指令来模拟结构体。

  • 内存精确控制:通过VirtualProtect+crt_memcpy实现对内存的精准拷贝和权限管理。

  • 地址固定技巧:使用ORG IMAGE_SIZE将自身代码推后,为目标程序的 ImageBase(00400000)预留足够空间。

五、总结

用汇编实现 LoadPE 的本质是:

自己当一次 Windows Loader—— 手动打开文件、映射内存、解析 PE 头、拷贝数据、修复导入表,最后把执行权交给目标程序。

  • 整个过程虽然代码量较大、逻辑繁琐,但赋予了开发者对加载流程的完全控制权,为后续的免杀优化(例如手动导出表解析、减少敏感 API 调用、直接 syscall 等)提供了坚实的基础。

  • 这就是为什么许多高隐蔽 Loader 都倾向于使用汇编来实现核心加载逻辑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询