MemoryModule的学习

MemoryModule

对于dll劫持和转发攻击时,一般会通过LoadLibrary这个API来加载DLL文件,虽然其存在一定的免杀效果,但效果仍不是十分显著,于是便引出了MemoryModule这个用法
而网上对于MemoryModule的文章大多是英文,而且晦涩难懂,这篇文章记录一下自己的学习过程和个人对于MemoryModule的理解,可能很多地方翻译的不准确,见谅

MemoryModule是什么

MemoryModule项目文章
MemoryModule地址

本文的大量示例代码来源于项目文章,在这里直接放上链接,就不一块一块代码照搬了

默认的 Windows API 函数将外部库加载到程序中(加载库、加载库Ex)仅适用于文件系统上的文件。因此,不可能从内存中加载 DLL。但有时,您确实需要此功能(例如,您不想分发大量文件或希望使反汇编更加困难)。此问题的常见解决方法是首先将 DLL 写入临时文件,然后从那里导入。当程序终止时,临时文件将被删除

MemoryModule为什么能免杀

Windows操作系统在执行一个Windows PE格式的文件时,Windows自身是有一个Windows PE格式的解析器,通过PE格式把文件的各个节放入不同的内存区域

爱折腾的程序员自己也想实现这个过程,那就是反射,这个反射机制就是将Windows PE格式通过自己写的代码进行解析,并把不同的节数据加载到内存中,通常这个反射加载技术被很多APT组织、大型渗透框架、病毒作者使用比较广泛

当一个Windows PE格式的文件变成了一个内存中的字符串,意味着这个文件可以被任意方式去转换、加密、混淆,因此反病毒软件也难以查杀

理解MemoryModule

windows 可执行文件PE

PE header

大多数可以包含可执行代码(.exe .dll.sys)的 Windows 二进制文件共享一种通用文件格式,而PE文件格式也是如此
在windows二进制文件中,PE文件有独特的标识,称之为PE header
PE标头包含有关可执行文件中用于存储代码和数据或定义从其他库导入或此库提供的导出的不同部分的信息
在该信息中

  • 描述了文件的物理格式,即内容,有关符号的信息等
  • 包含有关库的逻辑格式的信息,包括所需的操作系统版本、内存要求和入口点
  • 包含 16 () 个条目,用于定义库的逻辑组件
    对于导入 DLL,我们只需要描述导入和基重定位表的条目。为了提供对导出函数的访问,需要导出条目。

个人理解:PE header更类似于一种网络协议中的标识符,来告诉windows文件的格式信息

Section header

Section header存储在 PE header中的OptionalHeader_结构之后。微软提供了宏来获取基于 PE 标头的起始地址。
个人理解:Section header的大致用处是存储了有关文件中的每个Seciton的信息

Loading the library

在发出 API 调用时,窗口基本上执行以下任务:LoadLibrary

  • 打开给定文件并检查 DOS 和 PE 标头。

  • 尝试在位置PEHeader.OptionalHeader.ImageBase分配PEHeader.OptionalHeader.SizeOfImage字节的内存块。

  • 解析节标题并将节复制到其地址。相对于分配的内存块的基数,每个部分的目标地址都存储在IMAGE_SECTION_HEADER结构的VirtualAddress属性中。

  • 如果分配的内存块与ImageBase不同,则必须调整代码和/或数据部分中的各种引用。这称为基本重定位。

  • 必须通过加载相应的库来解决库所需的导入。

  • 必须根据节的特征保护不同节的内存区域。某些部分被标记为可丢弃,因此此时可以安全地释放。这些部分通常包含仅在导入期间需要的临时数据

  • 现在库已完全加载。 须通过使用标志DLL_PROCESS_ATTACH调用入口点 来通知它。

分配内存

库所需的所有内存必须使用 保留/分配,因为 Windows 提供了保护这些内存块的功能。这是限制对内存的访问所必需的,例如阻止对代码或常量数据的写入访问。
个人理解:这里主要是利用VirtualAlloc函数来分配内存,对于该函数是windows的一个API函数,用来申请内存空间

复制内存

经过前面的分配内存之后,可以将文件内容复制到系统中。必须计算节头,以确定文件中的位置和内存中的目标区域
个人理解:通过复制内容,将shellcode移动到申请的内存空间中来实现免杀

迁移内存

库的代码/数据部分中的所有内存地址都相对于OptionalHeader_中定义的地址进行存储
如果无法将库导入到此内存地址,则必须调整引用 =>重新定位。文件格式通过在基重定位表中存储有关所有这些引用的信息来帮助实现这一点
个人理解:这里需要重新计算内存地址,否则解析PE文件时,导入库可能会导致内存指针指向错误

解析导入

当我们解析导入时,我们并行遍历两个列表,导入由第一个列表中的名称定义的函数,并将指向第二个列表中的符号的指针存储
个人理解:我对这里的理解并没有十分清晰,但其目的可能是为了使外部库导入的函数名能找到其对应的内存地址

保护内存与通知库

VirtualProtect该函数可用于限制对内存的访问。如果程序尝试以未经授权的方式访问它,则 Windows 会引发异常。
最后要做的是调用 DLL 入口点(由 定义),从而通知库连接到进程。AddressOfEntryPoint
个人理解:内存保护类似于const,防止其他程序的使用导致该内存出现问题

Exported functions

如果要访问库导出的函数,则需要找到符号的入口点,即要调用的函数的名称。
首先要做的是将函数的名称映射到导出符号的序号。因此,只需遍历由 定义的数组并并行,直到找到所需的名称
个人理解:这里与前面的解析导入相对应,为了使函数名能找到其对应的内存地址

Freeing the library & MemoryModule

这里没什么意义,只是释放自定义的库与C中的一个库,可以从内存中加载DLL


MemoryModule的学习
https://glacierrrr.online/2022/10/18/MemoryModule的学习/
作者
Glacier
发布于
2022年10月18日
许可协议