1. 选题背景与整体思路:为什么拿OtterCTF练手
聊到内存取证,圈内绕不开的一个入门靶场就是OtterCTF。这个系列的题目让我印象最深的地方在于,它不靠堆高难度的内核逆向,而是把真实APT事件里最常见的内存痕迹做成了关卡,从进程异常、网络连接、隐藏对象到程序提取,每一道题都在还原一个完整的应急响应场景。最近我把前5关完整走了一遍,把思考过程、工具用法和踩过的坑都整理出来,给正准备入坑内存取证的朋友一个参考。
先说结论:如果你只有imageinfo和pslist的基础用法,那这套题刚好能帮你把“内存镜像”从抽象概念变成具体证据链。前5关覆盖了内存取证最核心的几条主线——识别恶意进程、分析网络行为、发现隐藏或注入痕迹、提取内存中的程序文件、落定用户侧行为。这些正是数字取证中“定位-分析-固定-出报告”的标准流程,拿这套题练手,比啃几百页文档效率高得多。
下面先把前5关的考点和我的整体解题框架列出来,方便心里有个地图再往下看。
| 关卡 | 核心考点 | 涉及工具/插件 | 主线 |
|---|---|---|---|
| 1 | 可疑进程识别 | pslist/psscan/psxview | 定位异常进程 |
| 2 | 进程命令行与父子关系 | cmdline/pstree | 还原启动方式 |
| 3 | 网络行为与外部通信 | netscan/connections | 追踪外联行为 |
| 4 | 隐藏对象与签名/注入检测 | ldrmodules/malfind/vadinfo | 发现隐藏模块 |
| 5 | 程序提取与本地验证 | procdump/memdump/strings | 固定样本并分析 |
每道题的结论本身并不是重点,重点是我怎么从“镜像里有一堆进程”走到“确定这个进程有问题”,这个思考链路才是值得复用的部分。
2. 环境准备与工具链选型:稳定复现的关键前提
在做任何内存镜像分析之前,先把分析和目标系统环境对齐,否则后面会出现插件跑不了或者结果对不上的情况。
2.1 官方框架选择与规避踩坑
Volatility基金会版在Windows 10 1903之后的内核结构上偶尔会出现进程枚举不完整的问题。OtterCTF这个镜像虽然源自较老的系统,但为了确保后续插件输出稳定,我建议直接使用Volatility3或是装了profile的Volatility2。这两个框架我在实测中表现都不错:
- Volatility3:无需额外下载profile,自动识别操作系统,用
windows.pslist、windows.netscan这类带命名空间的插件,输出格式统一,上手最快。 - Volatility2 + profile:适合需要特定老插件的场景,比如
malfind在Volatility2里比Volatility3更直观,且社区示例多,排查报错时参考资料更好找。
我的习惯是先用Volatility3做全量枚举,再用Volatility2做细节验证。两边结果交叉确认,能有效避免单框架插件实现差异导致的漏报或误判。
2.2 镜像基础信息确认
第一步永远是跑imageinfo(Volatility2)或windows.info(Volatility3),确认三件事:操作系统版本、硬件架构、内存分页格式。这一步看起来基础,但如果不做就往下走,后面很多插件会报Invalid protodiagram之类的错误,到时候再回头排查反而浪费时间。
拿OtterCTF前5关示例来说,确认完系统类型后直接锁定了对应的profile。需要提醒的是,Volatility2在部分环境下会将Win10x64识别成Win2019x64之类的相近profile,如果遇到这种模糊识别,建议以windows.info输出的内核版本号为准,手动指定准确的profile再跑一遍对比结果。
2.3 输入输出与目录规划
我习惯把工作目录按关卡分开,保证中间文件互不干扰。比如case1/evidence/放提取出来的二进制,case1/analysis/放strings和扫描日志。镜像文件本身建议放在只读目录,分析过程中绝不直接写回原始文件,防止污染证据。
一个容易被忽视的问题:Volatility在提取大块内存区域时会产生非常大的中间文件,磁盘空间至少预留镜像体积的2~3倍。我有一次在提取某个进程堆的时候差点把磁盘写满,后面调整了--dump-dir路径才继续。
3. 第一关:从进程长列表里锁定可疑对象
3.1 全量进程枚举与过滤逻辑
第1关的核心目标是找到“不该出现的进程”。先用windows.pslist输出全部进程,再结合windows.psscan做交叉校验。
拿到进程列表后我会按以下顺序过滤:
- 已知系统和白名单进程:
System、smss.exe、csrss.exe、wininit.exe、services.exe、lsass.exe、svchost.exe这些出现是常态,先划掉。 - 启动路径异常的进程:普通进程的可执行路径应该在
C:\Windows\System32或程序安装目录,如果发现进程路径在临时目录、用户目录或者奇怪的自定义目录,标记为可疑。 - 名字仿冒的进程:比如
svch0st.exe这类用数字0替换字母o的谐音伪装,在真实木马中很常见,扫描时要用肉眼过一遍而不是完全依赖工具。 - 占用资源异常的进程:如果某个进程的线程数、会话数明显不合理,也值得多看一眼。
OtterCTF第一关里那个问题进程就符合“路径在用户目录下、名字看似正常实则不属于系统组件”的特征。用psscan直接扫物理内存的进程对象,即使进程被rootkit隐藏了也能找出一部分,和pslist的结果一对比就能发现差异。
提示:
psscan扫描的是内核对象池,所以在部分情况下会输出已经退出的进程残留对象。拿到结果后先看ExitTime列,别把僵尸进程当成活跃注入进程。
3.2 进程详细信息的二次确认
锁定候选进程后,我会继续看它的父进程PID、创建时间、会话ID和路径细节。这里有个关键点:正常进程的父进程通常是services.exe或者explorer.exe,如果问题进程的父进程是另一个本身就可疑的进程,那基本可以确认这是一条恶意启动链。
我还会用windows.cmdline查看进程的完整启动命令。很多恶意程序会带一堆参数来指定配置、端口或者运行模式,命令行长字符串本身就是强烈信号。第1关的进程就是用这种方式确认了它是个自解压程序,解压后释放了后续的恶意组件。
要注意的是,cmdline读取的是进程内核对象中的环境信息,少数恶意软件会主动清空这块数据,如果看到命令行是空的但进程确实在运行,这就是一个额外的异常特征。
4. 第二关:解析进程启动链与父子关系
4.1 用pstree还原执行顺序
第二关开始涉及动态分析思维。我需要回答的问题不是“谁是坏的”,而是“它是怎么跑起来的”。这时候windows.pstree会比单纯的pslist好用得多,因为它直接把父子关系用树状缩进展示出来,一眼就能看到谁启动了谁。
OtterCTF第二关的过程可以这样理解:一个看起来正常的文档进程成为父进程,启动了后续的恶意程序。这其实是钓鱼邮件攻击的典型套路:文档兵马来打开恶意载体,然后再去执行一个危险程序,第三步把这个恶意可执行文件正常运行起来。
看到这棵树后,我快速生成了一张进程启动关系表作为记录,类似这样:
| 父进程 | 子进程 | 时间线 | 异常点 |
|---|---|---|---|
| explorer.exe | document_app.exe | 开机后 | 用户手动打开 |
| document_app.exe | unknown_svc.exe | 紧接着 | 非office程序产生子进程 |
| unknown_svc.exe | unknown_svc.exe (子实例) | 持续 | 单进程多实例模式 |
4.2 时间线补全与行为还原
父子关系只能说明“谁创建了谁”,不能说明“谁先到达了战场”。如果镜像里的时间信息完整,我会用windows.mftinfo或时间戳插件把所有进程创建时间排成一个时间线。多个可疑进程之间的时间差小于秒钟级别,往往说明它们在脚本里被批量拉起;如果时间差扩大到几十秒,中间可能夹杂了网络下载或者自解压的过程。
在OtterCTF第2关里,子进程出现的时间相对于文档进程慢了大概一个启动周期,这说明攻击链有个中间文件先被释放,接着才被拉起。顺着这个思路,后续去提取那个中间文件就成了自然的下一个步骤。
5. 第三关:网络痕迹联动分析盘活全局
5.1 netscan输出与可疑外联定位
内存镜像里保留着网络连接对象的相关信息,windows.netscan可以把这些信息从非分页池里翻出来。它区别于传统的流量抓包,是在没有完整PCAP的情况下,依赖内存残留把短连接或结束连接也还原出来。
第3关的做法是先把所有外联IP和端口列出来,再和恶意进程的PID一对上。判断疑问IP主要从两点入手:端口是否常见、连接是否多次尝试。恶意软件外联极少用80/443之外的端口吗?恰恰相反,不少样本为了规避流量检测,喜欢用高端口或非常规端口做加密隧道,因此在netscan结果里看到443之外的高端口连接反而要多留个心眼。
如果某个进程同时满足两个条件:它本身被前两关标记为可疑,又主动向外联某个陌生IP,那么这条连接就非常可能是C2通道。下一步我会把目的IP、目的端口、连接起始时间、协议类型全部导出,再配合windows.socket这类插件交叉验证。
5.2 从连接行为反推攻击意图
网络痕迹的价值不止是定位C2。我习惯在确认外联后反推几个问题:
- 连接是长连接还是短连接序列?长连接更像隧道或远控;高频短连接更像数据回传或者心跳。
- 目的端口是固定还是随机?固定端口通常写死在配置里,随机端口则说明样本可能有端口生成算法。
- 有没有明显的数据流出特征?如果关联的进程同时打开了文件句柄,且文件路径指向敏感目录,那这次外联大概率是数据窃取通道。
OtterCTF第3关通过这些判断,确定了一条规律的C2通信链路,这也直接为下一关的隐藏对象分析提供了方向。
6. 第四关:隐藏模块与内存注入痕迹检测
6.1 三线交叉:pslist / psscan / ldrmodules
隐藏进程是恶意软件的基本功,单靠进程链表无法发现它们。内存取证里的标准做法是拿三种枚举来源做交叉:
- 活动进程列表(
pslist) - 内核对象池扫描(
psscan) - 内存描述符遍历(
windows.vadinfo/windows.vadwalk)
正常进程在这三份结果里应该保持一致,如果某个进程只在其中一两份结果里出现,这往往意味着它的进程对象被摘链或者VAD异常了。OtterCTF第4关的隐藏对象就是这样暴露的——VAD里存在一个没有对应内存映射文件的可执行区域。
6.2 malfind与可疑内存区域标记
隐藏模块的常见形态是在进程中开辟一段可执行的非映像内存区,windows.malfind就负责在VAD层面找这类“可写可执行”的内存区域。它输出中的MZ头部标记是关键,只要看到内存页头部出现可执行文件魔数,基本可以断定这里被注入了一段完整代码。
我的操作方法是:先跑windows.malfind列出所有可疑区域,记录虚地址范围,再对每个区域单独提取出来分析。如果可疑区域里出现了另一个可执行文件的完整PE头,那还要继续做静态分析。做这一步时要注意,Volatility默认提取的是整段VAD内容,体积可能非常大,提取时按偏移范围截断而不是整段导出,效率会高很多。
注意:单独的
MZ头不足以证明是注入代码,有些正常进程的堆区里也会残留一段路径字符串。判断是否为注入,要结合内存页属性(是否PAGE_EXECUTE_READWRITE)、页内容完整性(是否有完整导入表)和上下文行为(该进程是否处于攻击链上)三者同时确认。
7. 第五关:内存程序落地提取与静态验证
7.1 用procdump提取进程实体
当怀疑某个进程本身就是一个恶意程序时,直接用windows.procdump把该进程的可执行映像从内存中提取出来。这一步相当于给恶意样本做“活体固定”,提取出的文件可以直接交给杀毒引擎或静态分析工具做二次验证。
OtterCTF第5关里提取出的进程映像,用文件哈希放到公开威胁情报平台查询时,能够直接匹配到已知家族信息。如果你在离线环境,也可以先用strings快速浏览敏感字符串再决定下一步。
7.2 memdump与堆内存里的关键数据
除了进程主体,堆内存里往往有运行时才出现的解密内容、配置信息、C2域名等。windows.memdump可以导出一个进程的完整内存空间,然后在这个大文件里用strings或grep做内容搜索。搜索时我习惯按优先级过这些关键词:
- URL / 域名 / IP
http/https/ftp前缀- 密码 / token / key / secret
- 文件名后缀(
.dll/.exe/.bin/.dat) - 注册表路径(
HKEY_CURRENT_USER等)
OtterCTF第5关的关键复盘点在于:第4关定位到的隐藏内存区域正是这个进程解密后的主体,直接把它提取出来做分析,比在全部内存里大海捞针高效得多。前几关的线索在这里汇成一条完整的证据链。
7.3 本地沙箱验证与行为补全
提取出来的样本要谨慎运行验证。我通常会在断网沙箱里快速观察它的文件系统行为和注册表行为,但要注意:断网本身会改变恶意软件的部分行为路径,因此还要结合内存里的网络痕迹来补全判断。OtterCTF第5关这种设计,就是逼着你把“样本静态分析”和“内存动态痕迹”两套证据放在一起做交叉验证。
8. 常见问题与排查技巧实录
实操做多了,一些坑基本每次都会碰到。整理在这里,省得你重复踩。
8.1 插件报错与输出为空
跑windows.pslist时报Kernel base相关的错误,绝大多数时候是profile选错或没有指定正确的操作系统版本。Volatility3很少出现这类问题,但如果你用Volatility2,建议按imageinfo给出的建议profile执行,第一次输出不准就多试几个相近profile对比。
8.2 提取文件过大导致程序假死
memdump在处理内存占用大的进程时会输出数GB文件,这时候如果磁盘IO不足,工具会长时间无响应。我的经验是先看进程的WorkingSetSize再决定是否全量导出,对只需要搜索字符串的目标,可以先用strings直接作用于原始镜像配合偏移范围筛选,避免无意义的全量导出。
8.3 时间线不一致的迷惑现象
pslist里进程的创建时间和文件系统MFT记录时间不一致时,别急着下结论。恶意程序完全可以修改自身PE头时间属性,甚至通过触碰操作改变文件时间。内存里的进程创建时间属于内核记录,可信度高于文件系统时间,两者出现矛盾时大概率是样本做了反取证处理。
8.4 netscan看不到连接记录的常见原因
如果你确认进程在运行但没有对应的网络连接记录,有一个可能:连接对象已经释放或存在于未被扫描到的分页池中。此时用windows.handles查看进程句柄,或使用windows.networkscan补扫,能提高连接记录的发现率。另外,某些样本会刻意使用内核回调隐藏连接,这属于反取证范畴,需要结合行为日志综合判断。
8.5 快速自查清单
跑完前5关后,我会按下面的清单做一次复盘,确保没有遗漏关键步骤:
- 是否已从多个枚举源交叉验证进程完整性?
- 是否已还原进程启动链与时间线?
- 是否已关联网络外联与具体进程?
- 是否已检测VAD中的可执行异常区域?
- 是否已提取关键进程映像/内存,并完成静态验证?
- 是否已记录所有文件哈希、IP、域名、命令行等关键证据?
内存取证做到最后,拼的不是某个工具的熟练度,而是把多个层面的痕迹串联成一条可信证据链的能力。OtterCTF这套题恰好把这几个层面全部覆盖了一次,做完一遍再去面对真实的应急响应场景,心里会有底很多。