做CTF的Misc方向做了几年,我最大的感受是:内存取证题属于那种“看着不难、一上手就头疼”的类型——丢给你一个几百MB甚至1GB的raw镜像,让你在里面翻出flag,既考验工具熟练度,又考验对操作系统痕迹的敏感度。这次想整理一下我最近集中刷的一组内存取证题目,其中最有代表性的是2021年蓝帽杯某分区赛里的《博人的文件》。这篇文章不是单纯对答案,而是把完整分析链路、每个命令为什么这么敲、每一步的排查思路都拆开来讲,适合刚接触内存取证、或者刷了不少题但总在文件提取和密码线索上卡壳的朋友。
内存取证考的东西其实很固定:进程、命令行、文件缓存、剪贴板、网络连接,翻来覆去就是这些操作系统留下的痕迹。但正因为固定,反而特别能练出人的耐心和细节意识。一个标准的内存取证题,往往从识别镜像profile开始,用Volatility打开进程列表,再顺着可疑进程和文件句柄一步步拿到目标文件,最后结合隐写术解出flag。这套流程看着简单,实际每一步都有坑。
1. 内存取证到底在考什么:先摸清出题人的套路
1.1 从电脑内存里挖flag的核心思路
为什么要从内存里挖?内存(RAM)保存着正在运行的程序、打开过的文件缓存、剪贴板内容、输入过的命令、网络连接状态。即使磁盘上的源文件被删除,只要内存里还有残留,就可能被恢复。CTF内存取证题,本质就是模拟“用户在机器上做过某些操作”然后让你还原这些操作痕迹,最终找到隐藏信息。
我常用一个生活化类比来理解这件事:内存就像正在使用的草稿纸,桌面上的最终文件可能被收走了,但草稿纸上写过的过程、涂改过的字迹还在。内存取证就是从这张“草稿纸”里还原出没来得及擦掉的痕迹。
一个标准的内存镜像通常是一个raw文件,也可能是vmem或vmdk格式,但它不能被当作普通文件直接双击打开。需要用取证框架把它当作“一块内存”来解析,比如Volatility,它会根据内存结构解析出进程列表、文件对象、注册表单元等。
1.2 出题人常用的几种命题套路
从我做过的题来看,内存取证出题基本是这几招:
- 在记事本或文本编辑器里打开了一个含flag的文件,需要导出对应进程内存去读;
- 把flag放在一张图片或一个文档里,图片/文档在内存中被缓存,需要从文件缓存或文件列表中找到并dump出来;
- 把flag打包成加密压缩包,密码存放在另一个进程的内存、剪贴板或浏览器记录里;
- 通过隐藏进程或线程的方式藏活动痕迹,需要扫描进程对象而不是只依赖普通进程列表;
- 把flag用LSB或附加数据等方式藏在图片中,导出图片后再做隐写分析。
这次整理的三道题正好由浅入深覆盖了其中大部分套路。第一道是纯记事本内存提取,第二道是文件缓存加隐写,第三道《博人的文件》则是完整的“进程线索发现—文件导出—密码信息关联—哈希隐写提取”综合链路。练熟这几招,以后遇到全新内存取证题,至少能快速判断该往哪个方向使劲。
1.3 需要准备的知识和工具清单
知识方面不需要太高深,主要得有:
- Windows进程模型的基本概念,进程、线程、句柄之间的关系;
- 常见文件格式的识别能力,至少能分清PE、PNG、JPEG、ZIP;
- 字符串搜索和十六进制分析的基础意识,知道什么时候用strings,什么时候用010 Editor。
工具方面则是一套固定搭配:Volatility 2和Volatility 3做内存解析;binwalk、010 Editor、WinHex做文件分析;stegsolve、zsteg做隐写分析。后面我会逐个介绍这些工具在具体题里怎么用、有哪些坑。
2. 工具选型与高频插件:这些命令必须刻进DNA
2.1 Volatility 2和Volatility 3到底怎么选
Volatility 2是老牌工具,基于Python 2,核心逻辑是先通过imageinfo识别系统profile,再套用对应插件。Volatility 3基于Python 3,最大的改进是不需要手动选择profile,它会自动匹配符号表,对Windows 10、Windows 11这种新系统镜像友好得多。
CTF比赛里Windows 7镜像占了绝大多数,Volatility 2完全够用;但遇到较新系统或64位服务器镜像时,Volatility 3更省心。我的建议是两个都装好,题目拿到手先分别跑一次识别命令,哪个能用就用哪个,不要执着于某一个版本。
| 工具版本 | 依赖环境 | profile处理 | 适合场景 |
|---|---|---|---|
| Volatility 2 | Python 2 | 需手动imageinfo识别 | WinXP/Win7等老镜像、比赛常见题 |
| Volatility 3 | Python 3 | 自动匹配符号表 | Win10/11、新版本服务器镜像 |
| memdump等辅助插件 | 随主框架 | 跟随版本 | 各类内存空间导出 |
2.2 必须背下来的高频插件清单
真正高频且永远离不开的插件,我用一张表列出来:
| 插件名 | 作用 | 常见用途 |
|---|---|---|
| pslist | 列出进程列表 | 入门第一步,看有没有记事本、浏览器、可疑进程 |
| pstree | 进程树,展示父子关系 | 排查由可疑路径启动的进程 |
| cmdline | 列出进程启动命令行 | 发现执行过的命令和文件路径 |
| envars | 打印进程环境变量 | 偶见解压路径、临时目录、用户名 |
| filescan | 扫描内存中的文件对象 | 最重要插件之一,相当于“草稿纸里出现过的文件痕迹” |
| dumpfiles | 按物理偏移导出文件 | 把filescan找到的文件落地到磁盘 |
| memdump | 导出指定进程的整个内存空间 | 常用于记事本、编辑器进程内容提取 |
| netscan | 查看网络连接 | 找C2地址或额外服务 |
| hashdump | 导出SAM用户哈希 | 登录凭据类题目 |
这些插件不要求参数倒背如流,但要理解各自在哪个环节用。实际比赛里,80%的时间花在filescan、dumpfiles、memdump和strings这四个操作上。后面的复盘会反复用到,理解了这几个,做题思路就顺了一大半。
2.3 profile识别的关键细节
Volatility 2要用imageinfo识别profile,如果候选列表太长,可以用kdbgscan进一步确认。常见坑是候选profile有多个时随便选一个,导致后续插件不稳定。我一般先选imageinfo结果里Suggested那一行的profile,跑一次pslist验证进程列表是否正常出现。如果进程数量和系统自带进程对得上,说明profile选对了。
另外有一个容易被忽略的点:拿到镜像文件后,先用file命令或十六进制工具确认文件类型,别急着跑imageinfo。有时候镜像不是完整raw文件,而是被截断的,甚至有可能是别的格式的磁盘镜像,先确认类型能省很多冤枉时间。
3. 实战复盘:从记事本到蓝帽杯的“博人的文件”
3.1 热身题:藏在记事本里的flag
这道题给的是一个memory.raw镜像。拿到手第一步自然是确认profile:
volatility -f memory.raw imageinfo识别结果是Win7SP1x86。接着看进程列表:
volatility -f memory.raw --profile=Win7SP1x86 pslist列表里除了常规系统进程,多了一个notepad.exe,PID是2180。这几乎就是把“flag在记事本里”写在了脸上。直接导出该进程的内存空间:
volatility -f memory.raw --profile=Win7SP1x86 memdump -p 2180 -D dump/然后用字符串搜索从内存转储里提取文本:
strings dump/2180.dmp | grep -E 'flag|ctf|KEY|{'在输出里直接看到了flag。这个解法的核心是memdump加strings的组合,几乎算内存取证的“Hello World”。注意memdump导出的不是普通文本文件,不能直接打开阅读,必须先经过strings清洗,否则中文很可能显示成乱码。
提示:如果有大量UTF-16编码的中文文本,记得用strings的-el参数,或者直接在010 Editor里查看Unicode字符串。
3.2 进阶题:从内存中导出一张带隐写的图片
第二道题的镜像明显比第一道大,分析时不能再用“瞎翻全部字符串”的思路。先用filescan扫描文件对象:
volatility -f advanced.raw --profile=Win7SP1x64 filescan | grep -i -E 'jpg|png|zip|secret'结果里有一条指向某图片文件的对象记录,物理偏移量很长,比如0x000000003e1f4c60。用dumpfiles把它导出:
volatility -f advanced.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000003e1f4c60 -D dump/导出的文件名带.dat后缀,这是Volatility的习惯,不代表文件真的损坏。用file命令一看,显示是PNG图像数据,于是重命名成secret.png。再用binwalk扫描:
binwalk secret.png结果发现文件在偏移0x1A55处还藏着一个ZIP压缩包。用binwalk -e提取,解压后得到flag文本。
这道题的重点有三个:一是filescan给的偏移量很长,直接复制就好,不要手动去数位数;二是dumpfiles输出文件名带后缀很正常,要用file命令确认真实类型后再改名;三是binwalk提取出的文件不一定都要,可以先用ls看大小再决定分析对象。
3.3 核心题:蓝帽杯某分区赛“博人的文件”
终于说到这道题。当时给的镜像名字大概是disk.raw,大小接近1GB。我第一次做时还闹了笑话,看到文件名以为要用磁盘工具挂载分区,后来才意识到这就是个纯内存镜像。
第一步,老规矩识别profile:
volatility -f disk.raw imageinfo系统是Win7SP1x64,于是后面所有命令都带着--profile=Win7SP1x64。
先看进程树:
volatility -f disk.raw --profile=Win7SP1x64 pstree除了explorer、svchost这些系统进程,里面有一个cmd.exe,父进程是explorer.exe,说明有人在桌面上手动执行过命令。接着看命令行:
volatility -f disk.raw --profile=Win7SP1x64 cmdlinecmd.exe的命令行没留下太多有效信息,但这条线索已经足够提醒我:操作系统里存在过交互行为,接下来要重点看文件系统痕迹。
于是上filescan扫描:
volatility -f disk.raw --profile=Win7SP1x64 filescan | grep -i -E 'boruto|naruto|博人|火影|zip|rar|7z'输出里出现了好几条与“博人”相关的记录,其中一个路径类似Users/xxx/Desktop/博人的文件.zip。题目名字就写在那,目标基本锁定了。
用dumpfiles把压缩包导出:
volatility -f disk.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000005e2f6c40 -D dump/导出后file确认是Zip archive data,顺手改成boruto.zip。尝试解压时发现压缩包有密码。密码从哪里找?一般有三个方向:用户桌面上的文本文件、浏览器访问记录、剪贴板或记事本残留。
我先用filescan再扫一遍文本文件:
volatility -f disk.raw --profile=Win7SP1x64 filescan | grep -i -E 'txt|log|note'结果发现一个名为“看这里.txt”的文件,导出后内容是“博人的生日是几月几号”。顺着这个提示,结合《博人传》的角色设定,把可能的日期按常见格式拼:2021、0315、20010315这些,逐个尝试后终于成功解压。
解压出来的主文件是Boruto.jpg。用zsteg做LSB隐写检测:
zsteg -a Boruto.jpg最后在LSB通道里拿到了flag。整道题的链路是:进程线索扫描到文件缓存,再导出压缩包,再通过内存中的文本信息找密码,最后做隐写提取。每一步单独看不难,但串联起来就需要基础足够扎实。
我复盘时特别注意到一个细节:题目虽然叫“博人的文件”,但文件路径里存在大量临时文件(以~$开头),这是Office文档在内存中留下的锁文件痕迹。密码线索很可能就来自内存中某个Office文档的缓存。因此做题时不能只盯着目标文件,所有看起来不起眼的小文件都值得扫一眼。
注意:这道题真正拉开差距的不是Volatility命令本身,而是“发现目标文件后怎么联想去找密码”。压缩包密码的线索不一定在压缩包旁边,可能在浏览器缓存、剪贴板、甚至另一个进程的可执行字符串里。做题要有全局眼光。
4. 踩坑实录与排查顺序:坏习惯比不会更致命
4.1 高频报错原因与解决方法
做这一组题我踩到过不少典型坑,列成速查表方便自查:
| 报错或现象 | 常见原因 | 解决办法 |
|---|---|---|
| imageinfo找不到profile | 镜像不是完整内存镜像;或Volatility 2对较新系统识别弱 | 用Volatility 3的windows.info确认;检查镜像文件头 |
| pslist结果为空 | profile选错 | 换候选profile,或改用psscan扫描 |
| filescan没有匹配结果 | 关键词设得太严 | 放宽grep条件,尝试jpg、png、dat、tmp等常见后缀 |
| dumpfiles导出0字节 | 偏移量不对,或对象属于分页文件 | 换相邻偏移;对所属进程使用memdump |
| strings输出乱码 | 中文大概率是UTF-16LE编码 | 用strings -el参数,或到010 Editor里看Unicode |
4.2 一条更稳的排查顺序
如果某一步没有进展,不要反复跑同一个插件。我自己的顺序是:
- 先确认系统版本和profile;
- 用pstree和cmdline判断整体活动;
- 用filescan分别扫图片、压缩包、文本三类关键词;
- 找到目标文件后先导出,再判断是否需要密码;
- 需要密码时,优先查记事本进程内存、剪贴板、浏览器历史,最后才做全镜像字符串搜索。
这个顺序能避免在开头就陷入“全镜像找flag”的大海捞针。顺便提醒:大镜像直接全盘跑哈希或strings会非常慢,比赛时间也是成本,能缩小范围就一定要缩小范围。
4.3 提速与细心的平衡
做题既要快又要细,我的经验是“先粗后细”。粗是指快速确认镜像类型、进程列表、明显可疑文件;细是指对锁定的目标做逐字节分析。不要一开始就在notepad的整个内存空间里翻来翻去,先用strings快速过滤,出现可疑字符串再用十六进制定位。
《博人的文件》这道题,我一开始在压缩包密码上卡了半小时,后来发现密码线索其实就在同一个桌面路径下的一个txt文件里,只是文件名太普通,被我下意识忽略。所以文件命名越普通,越有可能是题目故意埋的点,反而要格外留心。
5. 赛后延伸:从题目到真实取证,保持手感
5.1 从CTF题目到真实应急响应
比赛里那套流程放在真实安全事件排查中一模一样:遭遇可疑活动时先采集内存镜像,再用Volatility分析进程与网络连接,提取进程中的凭证、文件缓存。比如要确认某台主机是否被远控,重点看网络连接和异常进程;要还原攻击者执行过哪些命令,就看cmdline和事件痕迹。很多应急响应工作,本质就是把比赛题“工程化放大”。
区别在于真实环境更脏:内存占用大、进程数量多、符号文件可能不匹配,需要结合威胁情报和系统日志做横向验证。但基本功就是在比赛题里练出来的,做题不亏。
5.2 平时如何训练内存取证
如果想练好内存取证,最有效的办法是自己造镜像:在虚拟机里做一系列操作,拍照或记录,然后制作成内存镜像,再假装不知道答案,用Volatility一步步还原刚才的动作。操作越接近真实越好,比如打开压缩包、编辑文档、访问网页、下载文件。做完一轮再回看操作记录,能极大增强对内存痕迹的敏感度。
另一个建议是养成读插件源码的习惯。像Volatility的filescan,原理是遍历内存中的对象头。理解了扫描原理,遇到插件报错时判断会更准,不会一报错就慌,也能自己推测某些结果为什么异常。
最后再分享一个我个人做内存取证的小技巧:所有从内存中导出的文件,不要直接双击打开,先file命令确认类型,再算一下哈希,最后才做内容分析。很多“文件损坏”的错觉,其实只是类型识别错误,改个后缀就能正常用了。