Skip to main content

反射加载

反射加载是让一段载荷在进程内存中完成装载并执行,而不依赖 Windows 按常规路径从磁盘加载这段载荷。读取一个“非 PE 文件”只是可能的前置环节;真正需要证明的是后续内容如何进入内存、如何变成可执行代码,以及执行流是否到达它。

这篇笔记主要讨论 Windows 木马分析中常见的反射 DLL 加载和手工映射。MITRE ATT&CK 的 T1620 Reflective Code Loading 范围更广,也包括直接在内存中运行位置无关代码等情况。

机制​

正常加载磁盘上的 DLL 时,系统加载器会依据 PE 信息把映像放入进程地址空间,处理导入、重定位等装载工作,并维护相应的模块信息。

反射加载则由载荷自带的加载逻辑或另一个 Loader 在进程内处理这些工作。对于以 PE/DLL 字节为输入的情况,可以概括为:

取得载荷字节
↓
在当前进程的内存中准备映像
↓
按照 PE 信息整理节区、重定位和导入
↓
准备执行所需的页面权限
↓
执行载荷代码

这是一张分析流程图,不是每个样本都会完整呈现上述步骤。如果输入已经是位置无关的 shellcode,就不一定存在 PE 解析或手工映射;有些样本还会清除内存中的 PE 头。PE 的节区、导入表和重定位表可参见微软的 PE Format 文档。

反射加载描述的是代码如何装载。它可以发生在 Loader 自己的进程中,也可以发生在载荷先被送入另一个进程之后。后者同时涉及进程注入,可能借助 APC 等机制获得执行机会;“反射加载”和“注入”并非同一个动作。

与相近术语的关系​

术语关注的问题分析时的区别
常规 DLL 加载系统加载器从文件路径加载模块通常能关联到磁盘文件及正常模块记录
反射 DLL 加载DLL 字节如何在内存中完成装载载荷本身或配套 Stub 负责装载逻辑
手工映射谁在处理 PE 映像结构强调自行完成映像装载,可作为反射加载的一种实现方式
进程注入代码如何进入并在其他进程执行关注跨进程操作;注入后的载荷可以再反射加载

因此,报告里出现“DLL 转成 shellcode,再通过 Stub 反射加载”,不能直接简化成“系统用 LoadLibrary 加载了一个 DLL”;看到远程写内存,也不能仅凭这一步认定已经完成反射加载。

读取非 PE 文件的场景​

“读取非 PE 文件执行反射装载”容易让人误以为普通数据文件自己会运行。更准确的描述是:磁盘文件可能只是载荷容器,Loader 读取并解码后,才在内存中得到可执行内容。

磁盘上的 .dat / .bin / 无后缀文件
↓ 读取
编码、压缩或自定义封装的数据
↓ 解码
DLL / PE 字节,或可直接执行的 shellcode
↓ 内存装载与执行
进程中的载荷行为

这里有三个容易混淆的判断:

  1. 扩展名不是文件格式。 名为 .dat 的文件可能直接包含 PE;没有 MZ 文件头的文件也可能在解码后才显现 PE 结构。
  2. 解码结果不一定是 PE。 如果结果是 shellcode,就不应仅因为出现了内存执行而推断存在 PE 手工映射。
  3. “不落盘”要说明对象。 容器文件可以落盘,最终的 DLL 映像却可能只在内存中出现;也可能连容器都来自网络或资源段。

所以,“读取非 PE”只能说明输入的外观。它本身不足以证明反射加载,更不足以证明某个木马家族。

银狐相关案例​

Check Point Research 对银狐相关 ValleyRAT 攻击链的分析指出,所分析的下载器和最终后门都采用“64 位 DLL 转为 shellcode,并在前面加入负责内存反射加载的 Stub”的形式;其中下载器载荷被注入到已运行的进程中。

这个案例展示了两个连续但不同的环节:

载荷送入目标进程       ← 注入 / 载荷传递
↓
Stub 在内存中装载 DLL ← 反射加载
↓
后门逻辑运行 ← 后续行为

公开报告描述的是其所分析样本。不能由此推断所有银狐样本都读取同一种非 PE 文件、使用同一种解码方式,或者都选择同一个宿主进程。

检测与研判​

建议把文件、内存和执行三类证据关联到同一进程及相近时间窗,再判断是否构成一条完整链。

观察点能说明什么单独使用的限制
进程读取数据文件、无后缀文件或网络响应找到可能的载荷来源正常软件也大量读取这类内容
读取后出现解码、解压或内存中新的 PE 结构输入内容可能被还原为映像内存里出现 MZ 仍需验证结构与来源
新的私有内存区域被写入并变为可执行有动态生成或装载代码的迹象JIT、浏览器和游戏引擎也可能如此
线程起始地址或执行地址落在私有可执行内存执行流已到达无普通文件映射的区域仍需结合进程角色、调用栈和内存内容
内存中的 PE 映像缺少对应的常规模块记录支持手工映射判断载荷可能清除头部;模块记录也不是唯一证据
同时出现跨进程写入、APC 或线程上下文变化支持“先注入、后加载”的链路不能把跨进程动作当作反射加载本身

从弱到强,可以这样组织研判结论:

仅读取“非 PE”文件
↓ 增加来源与内容变化证据
文件内容被还原为疑似代码或 PE
↓ 增加内存权限与执行证据
私有可执行内存中的载荷实际运行
↓ 增加装载结构与行为关联
更有把握地判断为反射加载 / 手工映射

ReadFile → VirtualAlloc 这样的 API 顺序只能作为线索。若能进一步确认读入内容与内存区域有关联、该区域实际被执行、且缺少正常文件映射,判断才更扎实。MITRE ATT&CK 的 T1620 检测建议也强调进程内“内存分配、写入、权限变化、执行”的组合,而不是把某个单独 API 当成结论。

排查误报时,重点看进程是否本来就有 JIT、插件或解压执行需求,以及可执行内存和磁盘模块、已知软件组件之间能否建立合理对应关系。即使内存中没有 MZ,也不能直接排除反射加载:载荷可能是 shellcode,或者 PE 头已经被处理。

小结​

反射加载的核心是在内存中完成载荷装载和执行。“非 PE 文件”可能只是外层容器;注入是载荷进入其他进程的另一段链路。研判时应追踪输入内容 → 内存形态 → 实际执行,再结合宿主进程和后续行为判断风险。