LoadPE &&& 原理以及作用 (ASM汇编版本)>>01

目录

系统LoadPE

什么是系统级别的LodePE

[么是系统级别的 LoadPE 过程?](#么是系统级别的 LoadPE 过程?)

过程:

LoadPE的应用(手动映射过程)

为什么要实现手动PE

核心原理

[PE 手动加载绕过监控的思路](#PE 手动加载绕过监控的思路)

手动加载绕过方式:

为什么这种方式可有效绕过监控

手动映射的局限性

总结

我会实现什么版本

编写的版本架构设计

[一、C 语言](#一、C 语言)

[二、汇编语言 ------ 提供底层控制与规避能力](#二、汇编语言 —— 提供底层控制与规避能力)

三、推荐的实现架构

LoadePE静态还是动态免杀的过程

[一、LoadPE 的核心定位与免杀本质](#一、LoadPE 的核心定位与免杀本质)

[1. 静态免杀(Static Evasion)](#1. 静态免杀(Static Evasion))

[2. 动态免杀(Dynamic Evasion)------ LoadPE 的主战场](#2. 动态免杀(Dynamic Evasion)—— LoadPE 的主战场)

[二、高级免杀方案设计(含 LoadPE 组合)](#二、高级免杀方案设计(含 LoadPE 组合))

[方案一:基础静动结合 LoadPE](#方案一:基础静动结合 LoadPE)

[方案二:LoadPE + 进程伪装 + 模块镂空](#方案二:LoadPE + 进程伪装 + 模块镂空)

[方案三:LoadPE + APC Early Bird + Shellcode(高阶)](#方案三:LoadPE + APC Early Bird + Shellcode(高阶))

[方案四:LoadPE + 反射 DLL 注入 + 反调试(生产级)](#方案四:LoadPE + 反射 DLL 注入 + 反调试(生产级))


  • 这个技术点必须要有较强的windows编程能力以及深入理解PE结构以及汇编堆栈知识点切记哦! 懂则还行 不懂就非常难 熟悉就一般 精通这些就简单

系统LoadPE

什么是系统级别的LodePE

在 Windows 系统中,.exe.dll.sys 文件本质上是同一种东西------PE (Portable Executable) 文件。它们是操作系统能够理解和执行的标准二进制格式。

要理解它们的本质,首先要明白操作系统加载它们的过程,也就是 LoadPE


么是系统级别的 LoadPE 过程?

  • 当你双击一个 .exe 文件,或者一个程序调用一个 .dll 时,Windows 的加载器 (Loader) 会自动执行一系列复杂的操作,将磁盘上的 PE 文件变成一个可以在内存中运行的程序。
  • 这个过程就是 LoadPE

过程:

  1. 读取文件头

    加载器首先读取 PE 文件的头部信息(如 DOS 头和 NT 头)。这些头部就像是文件的"说明书",告诉加载器这个文件是什么架构(x86 或 x64)、代码和数据在哪里、入口点(程序开始执行的地方)是哪个。

  2. 分配内存

    根据"说明书"中的指示(SizeOfImage),加载器会在进程的虚拟内存空间中开辟一块足够大的区域。

  3. 映射节区 (Sections)

    PE 文件的内容被划分为不同的"节"(Sections),例如 .text(代码)、.data(数据)、.rsrc(资源)等。加载器会按照头部信息,将这些节从磁盘文件复制到刚刚分配的内存中对应的位置。

  4. 修复重定位 (Relocations)

    由于现代操作系统启用了地址空间布局随机化 (ASLR) 安全机制,程序每次加载到内存的地址都可能不同。重定位表的作用就是修正程序中所有硬编码的地址,确保它们指向正确的内存位置。

  5. 解析导入表 (Import Address Table, IAT)

    程序通常会调用系统或其他 DLL 中的函数(如 MessageBoxA)。导入表记录了这些外部依赖。加载器会负责找到这些 DLL,并获取所需函数的真实地址,填入程序的导入地址表中,这样程序才能正确调用它们。

  6. 跳转到入口点

    完成以上所有准备工作后,加载器最终会将 CPU 的执行权交给程序的入口点,程序正式开始运行。

简单来说,.exe.dll.sys 都是 PE 格式,它们的区别主要在于文件头中的某个标志位,这个标志位告诉加载器如何对待它:是创建一个新进程(.exe),还是将其映射到现有进程空间(.dll),或是加载到内核空间(.sys),这个过程称之为系统级别的LoadPE, 也成为之Windows 系统自带的加载器


LoadPE的应用(手动映射过程)

为什么要实现手动PE

在 Windows 系统中,绝大多数恶意软件,免杀,游戏修改器(Loader、注入器、内存补丁等)都需要调用一系列系统 API,例如:

  • LoadLibrary / GetProcAddress(动态加载 DLL 和函数)

  • VirtualAlloc / VirtualProtect(内存分配与权限修改)

  • CreateRemoteThreadWriteProcessMemory 等注入相关函数

现代杀毒软件(尤其是行为监控类杀软)对这些高敏感 API 进行了严格的监控。

一旦检测到进程调用这些函数,就会触发告警、拦截甚至直接查杀。

  • 传统方式是直接通过 GetProcAddresskernel32.dllntdll.dll 等模块中获取函数地址,这种"明目张胆"的调用很容易被行为监控捕获。

那么,有没有办法不直接调用这些被监控的 API,却依然能完成同样的功能呢?

答案就是:手动加载 PE(Manual PE Loading),也称为 PE 隐藏加载 或 PE 映射加载。

核心原理

Windows 加载一个 PE 文件(.exe 或 .dll)到内存中的过程,本质上是由操作系统内核(ntdll + kernel32)完成的,主要步骤包括:

  1. 读取 PE 文件

  2. 解析 DOS 头 → NT 头 → 节表

  3. 分配内存并拷贝文件头和各个节

  4. 处理重定位(Relocation)

  5. 解析导入表(Import Table),加载依赖的 DLL 并填充 IAT(Import Address Table)

  6. 处理 TLS、异常表等

  7. 调用入口点(Entry Point)

其中,第 5 步"解析导入表并调用 LoadLibrary + GetProcAddress" 是关键。

  • 手动加载 PE 的核心思路是:我们自己模拟操作系统加载 PE 的整个过程,把这个加载行为完全放在用户态自己的代码中完成。

  • 这样做的最大好处是:

  1. 在加载目标 PE 的过程中,我们可以完全控制导入表的解析方式

  2. 我们可以不通过系统的 LoadLibraryGetProcAddress 来解析和填充 IAT,而是自己实现一套加载逻辑。

  3. 甚至可以直接从已加载的 kernel32.dll、ntdll.dll 等模块的导出表中手动解析函数地址,完全避开被杀软重点监控的 API 调用路径。

PE 手动加载绕过监控的思路

典型的 PE 加载器(Loader)会执行以下步骤:

1. 打开并映射目标 PE 文件 (如 PlantsVsZombies.exe 或某个 DLL)到内存。

2. 解析 PE 结构:找到 DOS 头、NT 头、节表、导入目录等。

3. 在当前进程内存中分配空间 (通常使用 VirtualAlloc + PAGE_EXECUTE_READWRITE)。

4. 拷贝文件头和所有节到分配的内存中。

5. 手动解析导入表(Import Directory) ------ 这就是绕过监控的关键点:传统方式(会被监控):

复制代码
HMODULE hMod = LoadLibrary("kernel32.dll");
FARPROC pFunc = GetProcAddress(hMod, "VirtualProtect");

手动加载绕过方式:

  • 遍历目标 PE 的导入表(IMAGE_IMPORT_DESCRIPTOR)

  • 对于每一个需要导入的 DLL:

    • 不调用 LoadLibrary,而是检查当前进程中是否已经加载了该 DLL(通过已知的模块列表或手动遍历 PEB 的 InMemoryOrderModuleList)。

    • 如果已加载,直接获取模块句柄(HMODULE)。

    • 不调用 GetProcAddress,而是自己解析该 DLL 的导出表(Export Directory),通过名称或序号找到目标函数的地址。

  • 将解析到的函数地址直接填充到目标 PE 的 IAT(Import Address Table)中

  1. 处理重定位表(如果需要支持 ASLR)。

  2. 修改内存保护属性,跳转到目标 PE 的入口点运行。

为什么这种方式可有效绕过监控

  • 杀软的行为监控主要 hook 的是 公开的、高频使用的 API(LoadLibrary、GetProcAddress、VirtualProtect 等)。

  • 当我们手动解析导出表时,我们直接操作内存中的 PE 结构(IMAGE_EXPORT_DIRECTORY),通过遍历 Export Name Pointer Table、Ordinal Table 等结构来获取函数地址。

  • 这个过程没有走标准的 API 调用路径,很多杀软的 hook 点无法覆盖到这种底层手动解析行为。

  • 整个加载过程看起来更像是"自己映射一段数据到内存并执行",而不是"加载 DLL 并动态获取函数"。

这也就是常说的 PE 隐藏加载 或 无 Import 加载(Import-less Loading)的核心思想。


手动映射的局限性

仍然需要少量敏感 API

  • 手动加载器本身通常仍需要 VirtualAllocVirtualProtectCreateFileMapping 等基础 API。这些调用依然可能被监控,但数量远少于完整加载过程,且可以进一步通过 API Hashing直接 syscall 等方式继续隐藏。

兼容性问题

  • 需要正确处理重定位、延迟导入(Delay Import)、TLS 回调、异常处理等复杂情况,否则目标程序容易崩溃。

反检测与对抗升级

  • 高级杀软已开始监控内存中的异常 PE 结构、异常的内存执行流、IAT 填充行为等。单纯的 PE 手动加载已不是万能方案,需要结合更多技术(如 syscall、间接调用、代码混淆等)使用。

代码复杂度较高

  • 手动实现 PE 加载器需要深入理解 PE 格式,对编程能力要求较高。

总结

  • PE 手动加载技术的核心原理是:
    • 自己完整模拟 Windows 操作系统的 PE 加载流程 ,尤其是手动解析导入表和导出表
    • 从而避免直接调用 LoadLibraryGetProcAddress 等被杀软重点监控的敏感 API。
  • 通过这种方式
    • 我们把"加载 DLL 并获取函数地址"这个容易被检测的行为
    • 变成了"自己解析内存中的 PE 结构并填充地址"的低调操作
    • 显著降低了被行为监控触发的概率。

这也是许多高级 Loader、内存执行框架(如 Reflective DLL Injection 的变种)采用的核心技术之一。


我会实现什么版本

编写的版本架构设计

  • 在手动实现 PE Loader 时,选择 C 还是汇编并非非此即彼的问题。
  • 最佳策略是 "C 为主、汇编为辅",充分发挥两者优势:C 语言负责构建清晰、可维护的复杂逻辑框架,汇编语言则专注于底层控制、性能关键路径和反检测能力。
  • 其实这里就涉及到内联汇编或者是混合编程的一个设计的过程

一、C 语言

C 语言适合作为 Loader 的主体语言

  • 复杂数据结构处理 PE 文件格式包含 DOS 头、NT 头、可选头、节表、导入表、导出表、重定位表等复杂结构。使用 C 的 struct 和 typedef 可以清晰、直观地映射这些格式,提升代码可读性和维护性。
  • 核心加载流程实现 Loader 的主要步骤(如文件读取、内存分配、节区映射、权限设置、IAT 修复、重定位处理、TLS 执行等)逻辑复杂。C 语言的结构化控制流(if/else、for、while)和函数封装使流程清晰,便于调试和扩展。
  • Windows API 调用 方便调用 VirtualAlloc、LoadLibrary、GetProcAddress 等 API,代码简洁。

二、汇编语言 ------ 提供底层控制与规避能力

汇编语言用于解决 C 语言难以实现或容易被检测的关键环节:

  • 直接系统调用 (Direct Syscall) 核心应用之一。绕过 EDR/XDR 对 NtAllocateVirtualMemory、NtProtectVirtualMemory 等 API 的 Inline Hook 监控。
    • C 的局限:难以直接发起系统调用。
    • 汇编优势:手动设置寄存器(RAX/R10 存放 syscall 号,RDX、R8 等存放参数),执行 syscall 指令,直接与内核交互,实现"隐身"操作。
  • Shellcode 执行 最直接、最可控的执行方式。使用 call 或 jmp 跳转到 Shellcode 内存地址。 C 语言虽可通过函数指针调用,但在 x64 下稳定性与控制力度不如汇编。
  • 反调试与反分析 插入花指令(junk code)、实现反调试技巧(如检测硬件断点、时间检查)、手动栈帧操作等,增加逆向分析难度。

优势:对寄存器和指令的精确控制、极致性能、绕过安全软件监控。


三、推荐的实现架构

一个成熟的 PE Loader 通常采用以下分层设计:

  1. C 语言主框架
    • PE 文件解析
    • 内存管理与节区映射
    • 加载流程编排
    • 错误处理与日志
  2. 汇编封装函数(内联或单独 .asm 文件)
    • DirectSyscall 系列函数(NtAllocateVirtualMemory、NtWriteVirtualMemory 等)
    • ExecuteShellcode / CallPayload 函数
    • 反调试与花指令模块
  3. 接口设计 使用 __declspec(naked) 或单独汇编模块,将汇编功能封装成 C 可调用的函数,保持主代码的简洁性。

总结

C 语言提供结构与效率,汇编语言提供深度与隐蔽性

两者结合才能打造出既稳定可靠、又具备强对抗能力的 Loader PE。

  • C 为主 → 保证开发效率和代码质量
  • 汇编为辅 → 突破限制,实现关键"隐身"能力

LoadePE静态还是动态免杀的过程

一、LoadPE 的核心定位与免杀本质

LoadPE(手动实现 PE Loader)属于典型的「动态加载 / 运行时注入」技术主要用于动态免杀 ,但可与静态免杀技术深度结合,形成**"静动结合"**的高级免杀链。

1. 静态免杀(Static Evasion)
  • 发生在文件落地前(磁盘上的 PE 文件被杀软扫描时)。
  • LoadPE 本身作为加载器(Loader),其可执行文件(.exe)需要先通过静态特征免杀。
  • 关键技术
    • 代码混淆、花指令、虚拟机保护(VMProtect)、函数调用序列欺骗
    • Shellcode 加密(对称/非对称/异或+凯撒等)
    • 移除/混淆 PE 特征(Import Table 打散、字符串加密等)
2. 动态免杀(Dynamic Evasion)------ LoadPE 的主战场
  • 发生在运行时(进程内存中执行时)。
  • LoadPE 的核心价值在于不依赖系统 LoadLibrary ,而是手动映射 PE 到内存,实现"无文件落地"或"内存加载"。

LoadPE 动态加载完整流程(清晰版)

  1. 读取 PE 文件(磁盘或资源节中嵌入)
  2. 解析 PE 结构(DOS头 → NT头 → Optional头 → Section Table)
  3. 申请内存(VirtualAlloc 或 Direct Syscall NtAllocateVirtualMemory,权限 RWX)
  4. 映射节区(拷贝各 Section 到对应内存地址)
  5. 修复重定位表(Relocation Table)------ 处理 ASLR
  6. 修复导入表(Import Address Table)------ 解析 DLL 和函数地址
  7. 处理 TLS 回调(线程本地存储)
  8. 执行入口点(调用 PE 的 AddressOfEntryPoint)

二、高级免杀方案设计(含 LoadPE 组合)

方案一:基础静动结合 LoadPE
  • Loader 本身做强静态混淆(花指令 + 函数调用欺骗 + 代码虚拟化)
  • Payload(Shellcode 或 PE)做多层加密(异或 → AES → 动态解密)
  • 使用 Direct Syscall 完成所有内存操作
  • 效果:绕过大部分静态引擎 + 部分行为监控
方案二:LoadPE + 进程伪装 + 模块镂空
  • Loader 注入到高信任进程(explorer.exe、svchost.exe 等)
  • 使用模块镂空(Hollowing):把合法进程的内存空间清空,再 LoadPE 映射恶意 PE
  • 结合 PPID Spoofing + Parent Process Masquerading
  • 额外加入 ETW 绕过 + AMS I 绕过
方案三:LoadPE + APC Early Bird + Shellcode(高阶)
  • Loader 创建挂起进程(CreateProcess + CREATE_SUSPENDED)
  • 使用 Early Bird APC 将 LoadPE 的执行逻辑投递到主线程
  • LoadPE 在 APC 中完成内存映射和 PE 执行
  • 优点:极早执行,绕过很多用户态监控
方案四:LoadPE + 反射 DLL 注入 + 反调试(生产级)
  • 将目标 PE 编译成 Reflective DLL
  • LoadPE 实现反射式加载(无需文件,直接在内存中解析并执行)
  • 加入:
    • 反调试(检测硬件断点、单步、时间差)
    • PPL 绕过 / Kernel Callback 绕过
    • 动态 Patch EDR 驱动保护函数

总结建议

  • LoadPE 本身是动态免杀的核心技术 ,但必须配合强静态免杀才能落地。
  • 真正厉害的 Loader 一定是 C + 汇编混合,Direct Syscall 是标配。
相关推荐
ThornArmor10 小时前
【控制篇】斩断无休止空转:4-bit 指令集里的跳转律令与时序状态机
c语言·汇编·c++·单片机·嵌入式硬件
大阳12315 小时前
ARM4.(通过汇编,c语言,固件库点亮LED)
c语言·开发语言·汇编
iCxhust15 小时前
8086 汇编 TINY 和 SMALL 编程MODEL区别
汇编·单片机·嵌入式硬件·操作系统·微机原理·8088单板机
say_fall2 天前
从零开始学x86汇编_16位指令系统完全指南
开发语言·汇编·计算机组成·微机原理
txg6663 天前
编译无关的漏洞检测:基于 Transformer 的 LLVM-IR 与汇编鲁棒建模
汇编·深度学习·安全·transformer
浩浩测试一下4 天前
汇编 16位32位64位通用寄存器(逆向分析)
汇编·windows·stm32·单片机·嵌入式硬件·逆向·二进制
浩浩测试一下4 天前
汇编常用的(JCC 串 判断)指令 通用寄存器 标志寄存器 段寄存器(逆向分析)
汇编·通用寄存器·逆向二进制·标志寄存器·段寄存器·串 jcc 常用指令
浩浩测试一下5 天前
汇编 标志位寄存器 (逆向分析 )
c语言·汇编·逆向·windows编程·标志寄存器
浩浩测试一下5 天前
汇编 数组与串指令(逆向分析)
汇编·逆向·二进制·免杀·串指令·汇编数组