内存执行
内存执行描述的是载荷在进程内存中运行,不是一种单独的加载手法。它可能与白加黑、动态解码、反射加载、进程注入或安全产品致盲组合出现。最终载荷若不以普通 EXE/DLL 文件落盘,会减少某些文件与模块遥测;但这不等于 EDR 无法采集内存行为。
分析银狐相关样本时,建议分别回答三个问题:载荷从哪里来、在哪个进程中执行、哪些采集链路仍然正常。
攻击链中的位置
一种多阶段链路可以抽象为:
初始程序或文档启动 Loader
↓
取得下一阶段载荷:磁盘数据、程序资源或网络响应
↓
在内存中解码,得到 shellcode 或 PE/DLL 内容
↓
在本进程执行,或先送入另一个进程
↓
载荷运行,产生持久化、通信等后续行为
图中的步骤并非每个样本都具备。尤其要区分“最终载荷不落盘”和“攻击链完全没有文件”:初始 EXE、侧载 DLL、加密容器或驱动仍可能出现在磁盘上。MITRE ATT&CK T1620所说的反射代码加载,可以让载荷直接在进程内存中分配并执行,但不保证整个攻击过程没有文件痕迹。
白加黑:让 Loader 先运行
白加黑 / DLL 侧加载常利用合法程序原本的 DLL 搜索或插件加载关系,让恶意 DLL 在合法进程内启动。这个阶段通常有一个落盘的 DLL,也可能产生正常的模块加载事件。该 DLL 随后再解码并运行内存载荷,才形成另一段内存执行链。不能把“白程序加载黑 DLL”本身称作无文件反射加载;对应的侧加载分类可参见 MITRE ATT&CK T1574.001。
动态解码与反射加载:减少最终模块落盘
Loader 可以从数据文件、资源或网络响应中取得编码后的内容,在内存中还原出 shellcode 或 PE/DLL。若后续由自带加载逻辑在内存中装载 DLL,而没有按普通文件路径交给系统加载器,这属于反射加载一类。shellcode 直接运行不一定需要 PE 解析;反射 DLL 则可能需要处理 PE 映像结构。
内存页面可能经历“写入时可写、执行时可执行”的权限变化,也可能采用其他映射方式;RW → RX 是有用的观察线索,并非所有样本都必然出现的固定序列。
进程注入:改变执行所在的进程
如果 Loader 操作的是自己的地址空间,不一定会出现跨进程写内存或远程线程。若它把载荷送入 svchost.exe 等另一个进程,则还涉及进程注入;执行可能由远程线程、APC、现有线程或其他机制触发。进程空洞化则需要另外检查进程映像是否被替换。MITRE ATT&CK T1055将跨进程注入单独归类。进程注入和反射加载可以连续发生,但观察到其中一个动作,不能自动推断另一个已完成。
自注入:载荷留在当前进程
如果 Loader 还原载荷后,直接在自己的地址空间中装载并执行,分析报告常称其为自注入。它是同进程内存执行的一种描述,具体可能是本地 shellcode、反射 DLL 或托管程序集加载。机制与使用动机见注入:自注入。
一个常见目的就是运行自己携带的加密、混淆载荷,并减少静态特征暴露:磁盘上保留 Loader 与经过变换的数据,运行时才在内存中解密、解码或解包,随后在同一进程执行还原后的代码。加密、混淆针对载荷的静态可见性,自注入承接运行时执行;如果还原后的载荷不再落盘,就减少了一次对明文文件进行扫描的机会。MITRE T1027.013解释了这种静态签名漏检风险,但它不保证绕过所有静态分析或运行时检测。
例如,合法程序先因 DLL 侧加载而运行恶意 Loader,Loader 再在同一个 PID 内还原并执行下一阶段。前者解释 Loader 如何进入宿主,后者解释最终载荷如何在宿主内运行,两者需要分别取证。只有模块加载事件,不能直接证明后续自注入。
自注入可能减少最终载荷落盘和跨进程操作,但不改变当前进程的权限与身份,也不意味着“整个攻击无文件”。进程名、主 EXE 的签名和磁盘哈希无法单独说明动态加载的代码可信。
BYOVD:削弱防护的另一段链路
部分攻击会滥用带有合法签名但存在漏洞的驱动(BYOVD)干预安全产品。这不是内存执行的必要条件,也不应把每次内存执行都解释成 EDR 已被致盲。Check Point Research 对一批银狐相关攻击的分析记录了利用脆弱驱动终止安全进程,以及将 ValleyRAT 下载器、最终后门以 DLL 转 shellcode 的形式交付并进行内存反射加载的情况。报告中的具体行为应按样本归纳,不能推广为所有银狐或所有 ValleyRAT 样本的固定步骤。安全产品受损的判断见安全产品致盲。
为什么 EDR 可能少看到一些事件
EDR 是多种传感器、扫描器和分析逻辑的组合。内存执行通常改变的是可观察的事件种类与质量,而不是让进程内存突然不可观察。
| 采集面 | 可能减少的信号 | 仍可寻找的证据 |
|---|---|---|
| 文件与静态扫描 | 最终恶意 DLL 没有作为普通文件创建,缺少该 DLL 的磁盘哈希 | 初始程序、容器文件、侧载 DLL、下载和文件读取 |
| 正常模块加载 | 手工映射的最终映像可能没有相应的正常 ImageLoad / 模块记录 | 宿主与先前 DLL 的加载事件、内存映像与模块记录差异 |
| 用户态 API Hook | 若样本专门绕过某个 Hook,依赖该 Hook 的调用记录可能缺失 | 内核或系统侧事件、内存状态、线程与后续行为 |
| 内存扫描 | 短暂存在的解码内容可能错过某次抽样扫描 | 权限变化、执行地址、按事件触发的扫描和事后内存检查 |
| 合法进程外观 | 进程名和主 EXE 签名可能看起来正常 | 载荷所在内存区域、异常模块、调用栈、网络与持久化行为 |
“绕过用户态 Hook”需要单独证据,不能因为没有 API 日志就断定样本使用了直接系统调用或解除 Hook。相同的缺口也可能来自传感器配置、过滤、版本差异或事件丢失。微软对反射 DLL 加载检测的研究展示了通过异常内存、线程和行为上下文发现这类载荷的思路,因此“内存执行=EDR 看不到”并不成立。
术语上,Windows 的 AMSI主要提供内容扫描集成接口,不能代指所有文件或内存监控;AMFI 则不是 Windows 的文件监控组件。
EDR 可以如何观察
文件、模块与驱动
有落盘启动器或侧载 DLL 时,文件创建和模块加载仍有价值。以 Microsoft Sysmon 事件说明为例,事件 6、7、8、10、11 分别涉及驱动加载、模块加载、远程线程、进程访问和文件创建;其中模块加载事件需要配置,远程线程事件也不覆盖同进程执行或所有注入方式。具体产品是否采集这些事件,以及采集粒度,取决于配置与实现。
内存区域与执行地址
可以关注可执行内存的类型、权限、来源和实际执行情况。例如 MEM_PRIVATE 的可执行区域、线程起始地址或当前执行地址不属于已知模块,都是值得继续追踪的线索。Windows 的 VirtualQueryEx 文档说明了 MEM_PRIVATE、MEM_IMAGE 和页面权限等属性。
但私有可执行内存不等于恶意:JIT、浏览器、游戏引擎和某些插件框架也可能产生它。还应核对进程角色、区域内容、调用栈、页面变化与后续行为;内存中没有 MZ 也不能排除 shellcode 执行或被处理过的 PE 映像。
ETW-TI 能提供什么
ETW-TI是 Windows 内核侧的 ETW 事件源。在安全产品具备消费条件并启用相应事件时,它可以提供本进程或跨进程的内存分配、权限变化、映射、读写,以及部分 APC 和线程上下文操作的线索。
对同进程内存执行,重点关联同一 PID 内的内存操作与实际执行地址;对注入,重点关联发起 PID、目标 PID 与目标进程的后续执行。这些事件提供地址、大小、权限和进程关系等元数据,不会自动给出完整载荷内容,也不能仅凭分配或写入证明代码已执行。由于事件来自内核侧,绕过用户态 API Hook 不等于这类事件必然消失;具体覆盖仍由 Windows 版本和产品采集配置决定。
采集链路的健康状态
具备相应权限和能力的产品还可能结合其他内核事件、内存检查及云端关联分析,但不能假设某一种遥测会完整记录所有内存分配和权限变化。若怀疑 BYOVD 或其他致盲行为,应比对驱动加载、安全服务状态、本地事件、中心端心跳与上报量,确认是攻击行为未被检测,还是采集或上报已经受损。
PPL(Protected Process Light)能限制普通进程对受保护安全服务的访问和终止;它不是对内核权限滥用的绝对保证。可参见微软的反恶意软件服务保护说明。
自注入的检测与处置
如何理解 EDR 中“自己操作自己”的记录
假设某产品把调用方记为 actor.process,目标记为 process,出现以下概念性记录:
actor.process.name: malware.exe
actor.process.pid: 4200
process.name: malware.exe
process.pid: 4200
action_name(同一时间窗内的多条记录):
VirtualAllocEx
WriteProcessMemory
NtProtectVirtualMemory
NtCreateThreadEx
如果字段含义确实代表发起方和目标方,而且关联到了同一个进程实例,这可能是自注入或自加载行为。不同产品的字段与事件覆盖不同,上述不是通用日志格式,也不表示任一产品必然完整记录这些 API。不能只比较进程名,还要核对 PID、启动时间或进程唯一标识,排除同名进程与 PID 复用。
同进程内存操作单独提供的恶意证据有限。 JIT、运行时、合法加壳软件也会在自身生成或执行代码;不能仅因其“像注入”就定性,也不宜笼统认定所有跨进程操作都比它更恶意。可按以下证据逐步提高置信度:
- 写入区域属于
MEM_PRIVATE,随后获得执行权限,线程入口或实际执行地址落入同一区域,支持“动态代码已装载并执行”的判断。 - 进一步发现该区域内容来自可疑加密、压缩容器,且运行时发生了还原,支持“Loader 交接到第二阶段”的解释。
- 再关联异常外联、凭据访问、持久化等行为,并排除合理的 JIT 或软件保护场景,才能更有把握判断恶意性。
为什么已经运行,却还分配可执行内存并启动线程
分析银狐相关样本时,可以围绕这个问题追踪:初始 EXE 是否只是 Loader,真正的功能是否封装在第二阶段 Payload 中? 重点核对载荷来源、还原前后内容、执行地址和阶段交接后的行为。新分配内存和新线程与这种解释相容,但不是单独证明银狐家族、加密载荷或恶意行为的依据。
为什么远程注入规则可能没有告警
只查“进程 A 打开进程 B → 远程写入 → 创建远程线程”的规则,会遗漏不涉及 B 的同进程执行。Sysmon 官方说明中,事件 8 记录在另一个进程中创建线程,事件 10 记录访问另一个进程;它们不是本地内存执行的通用记录。事件 7 也不能完整覆盖手工装载的载荷。
所以“没有事件 8 / 10”只能说明缺少相应跨进程证据,不能据此排除自注入。Sysmon 也不是完整的内存操作审计器,需要结合具备相应能力的 EDR、内存检查及应用运行时遥测。
将证据关联到同一进程和同一内存区域
| 阶段 | 要寻找的证据 | 研判边界 |
|---|---|---|
| 输入 | 可疑模块启动后读取资源、容器文件或取得网络数据 | 普通程序也有这些行为,要查来源与加载关系 |
| 内存准备 | 输入后出现新代码内容、私有可执行区域或页面权限变化 | RW → RX 不是必备步骤,JIT 也会动态生成代码 |
| 执行 | 线程当前执行地址、调用栈或回调落点进入相关区域 | 线程起点可以正常,之后才进入载荷,只看起点会遗漏 |
| 行为 | 执行后紧随异常外联、凭据访问或持久化动作 | 应证明时间、进程和执行上下文的联系,避免拼接无关事件 |
一个概念性调查线索是:“不常生成动态代码的程序,在读取可疑容器后出现无法用正常模块解释的执行区域,随后从该区域发起异常外联”。这比单独看到 RWX、MZ 或一个内存分配事件更有解释力,但仍需进一步验证。
遇到加密或封装载荷时,应在受控环境中检查运行时还原后的内容,而不只是扫描原始文件。将“输入数据 → 还原后的代码 → 实际执行区域”关联起来,可以弥补仅依赖原始静态特征的不足;单独的高熵、解密操作或内存写入都不足以证明恶意自注入。
对浏览器、.NET、Java、游戏引擎等程序,应结合运行时、调用栈和业务行为建立基线,避免把所有私有可执行页判为恶意,也不要因主程序有签名就排除调查。原生载荷可能没有 PE 头,托管载荷也不能只按原生 shellcode 的内存模式寻找。微软的反射 DLL 加载检测研究可作为内存与行为关联分析的参考。
防护和响应落点
- 限制入口。 对启动器、脚本、侧载 DLL 与插件实施应用控制,修补被利用的软件,并保护程序目录的写权限。允许主 EXE 运行不应等于信任它能加载的所有内容。
- 限制动态执行。 对兼容程序评估 DEP、ACG、CFG 等机制,它们的作用和限制见自注入防护说明。不要把 PPL 或限制跨进程句柄当作阻止普通进程自注入的完整方案。
- 保留调查能力。 确认内存、线程、模块与网络遥测的实际覆盖及传感器健康状态;围绕同一进程实例关联事件,避免 PID 复用造成误判。
- 处置整条链。 确认入侵后隔离主机,在控制风险的前提下尽可能于结束进程前保全内存、线程栈和相关文件,再清理启动器、侧载组件及持久化入口。只结束内存载荷所在进程,可能留下再次启动它的条件。
研判顺序
- 确认输入。 查清最终载荷来自文件、资源还是网络;磁盘上是否只有编码容器,是否另有侧载 DLL 或驱动。
- 确认进程关系。 区分同进程加载与跨进程注入;记录发起进程、目标进程和实际执行地址。
- 确认内存执行。 关联可执行区域的类型、权限、内容和线程 / 回调落点,避免只凭一次分配或一次文件读取下结论。
- 确认采集完整性。 对照文件、模块、内存、线程和安全产品健康事件,解释缺失的遥测,而不是将“没看到”直接写成“没有发生”。
- 再做家族归因。 将加载方式与配置、持久化、通信和样本证据结合;仅有内存执行或 ValleyRAT 名称不足以归因银狐。Check Point 后续研究也提醒,ValleyRAT 的公开构建器会削弱仅凭家族名称进行归因的可靠性。
小结
内存执行会减少最终载荷的落盘和正常模块加载痕迹,但初始文件、私有可执行内存、执行流与后续行为仍可提供证据。分析时把加载方式、执行进程、EDR 采集状态分开核实,才能解释某条告警为什么缺失,以及剩余证据能支持多强的结论。