内存取证入门:用Volatility还原系统状态与恶意进程分析
OtterCTF 2018的电子取证前这道题我前后刷了三遍才把整套流程理清楚。题面不复杂给了一个名为OtterCTF.vmem的内存镜像目标是通过电子数据取证还原系统的关键状态操作系统版本、主机名、用户信息、网络连接以及最核心的——那一串藏在进程和文件背后的恶意行为。这篇笔记就是把这道题的前半段完整复盘一遍重点不是背答案而是把Volatility这套工具链怎么用顺、哪些坑必须躲开讲清楚。适合刚开始接触内存取证、或者准备电子数据取证相关比赛和实战的人参考。1. 环境准备与镜像Profile识别为什么第一步决定成败1.1 拿到镜像之后先别急着跑命令不管是在CTF赛场上还是真实取证现场我拿到镜像后的第一个动作永远是算哈希值。SHA256或者MD5都行目的是留一个证据底稿。OtterCTF这道题的镜像接近4GB不同平台二次打包后哈希可能不同但你自己手上的这份必须记录清楚。这是电子数据取证的职业习惯也是后面和队友沟通时的重要依据。sha256sum OtterCTF.vmem记录完之后再想一想这类题目的目标到底是什么从题目名字电子取证前能猜到前面的考点大概率是基础信息还原比如系统版本、主机名、用户名、IP地址、可疑进程最后才轮到恶意样本和Flag。所以分析节奏也应该按这个顺序走而不是一上来就strings一把梭。1.2 为什么我坚持用Volatility 2OtterCTF 2018的镜像是非常典型的Windows 7内存快照网上的Writeup几乎全部基于Volatility 2。原因有两点一是Volatility 2的插件体系在那个年代最成熟pslist、netscan、malfind、hashdump这些命令都有大量实战验证二是遇到问题的时候照着旧资料排查最方便很多坑已经被前人踩平了。Volatility 3不是不能用但它的插件名和输出字段和2差别很大。比如Volatility 2的imageinfo在Volatility 3里变成了windows.infohivelist的实现也不完全一样。我的建议是主用Volatility 2辅用Volatility 3做交叉验证两边都装好。git clone https://github.com/volatilityfoundation/volatility.git cd volatility python2 vol.py --info这里有个环境坑新版Kali、Ubuntu默认都没有Python 2了。我自己是直接拉了一个REMnux虚拟机里面预装了Volatility 2省去很多折腾时间。如果你不想装虚拟机也可以用Docker跑一个带Python 2的容器总之别在环境上浪费太多时间。1.3 imageinfo让工具自己猜ProfileProfile这个词在Volatility里是关键。它决定了工具按什么版本的Windows内核结构去解析内存选错了后面全错。第一步永远是自动识别python2 vol.py -f OtterCTF.vmem imageinfo输出会有一段类似这样的内容Volatility Foundation Volatility Framework 2.6 INFO : volatility.debug : Determining profile automatically... Suggested Profile(s) : Win7SP1x64, Win7SP1x64_24000, Win7SP1x64_23418 AS Layer1 : Win7SP1x64 (Kernel AS) ...这里直接选第一个Win7SP1x64。如果后面跑pslist发现进程列表异常或者大量报错再回头试其他变体或者手动指定KDBG偏移。不要小看这一步很多人折腾半天发现结果对不上最后回头一看是Profile选错了。2. 系统身份信息提取主机名、系统版本、用户口令和基础网络状态2.1 先用hivelist定位注册表HiveWindows内存镜像里藏着注册表Volatility通过解析注册表Hive来获取系统信息。先运行hivelist看看哪些Hive在内存里可用python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 hivelist输出大概长这样0xfffff8a000024010 \SystemRoot\System32\Config\SYSTEM 0xfffff8a00006b010 \SystemRoot\System32\Config\SAM 0xfffff8a00009b010 \??\C:\Windows\ServiceProfiles\LocalService\...\NTUSER.DAT重点记下SYSTEM和SAM的偏移地址后面很多操作会用到。在这个阶段你还能顺便判断镜像里加载了哪些用户态配置对还原系统全貌很有帮助。2.2 操作系统版本与主机名系统版本信息通常藏在Microsoft\Windows NT\CurrentVersion这个键下面。用printkey读取python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 printkey -K Microsoft\Windows NT\CurrentVersion注意路径里的反斜杠在Shell里最好用单引号包起来否则转义问题会搞得你很崩溃。输出里的ProductName、CurrentBuildNumber、CSDVersion就是你要的答案。主机名则在另一个键下面python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 printkey -K ControlSet001\Control\ComputerName\ComputerName如果printkey报错找不到键别急着怀疑命令先用hivelist拿到对应Hive的偏移然后用-o参数手动指定。这种自动不行就手动的思路在内存取证里会反复用到。2.3 用户信息与密码哈希题目前部分经常会让找用户列表或者某个用户的密码。hashdump可以直接从SAM里导出用户哈希python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 hashdump输出格式类似Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::如果是弱口令直接把NTLM哈希存到文本里交给John the Ripper跑字典基本秒破。这里有个很常见的坑hashdump要求SYSTEM和SAM两个Hive都能被完整读取如果镜像有损坏或者Profile不对输出就会缺行。遇到这种情况优先怀疑Profile而不是工具。2.4 基础网络状态先看本机IP和外联netscan是Volatility 2里最实用的网络插件它基于内核网络结构扫描能列出TCP和UDP连接python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 netscan输出里能看到本地IP地址、端口、远程地址以及对应的进程PID。答题时如果问这台机器的IP是多少从Local Address列就能直接看到。如果问有没有外联重点关注Foreign Address那一列特别是连接状态为ESTABLISHED的。恶意进程通常会主动向外部IP发起连接这里的信息要和后面的进程分析对上。2.5 关于时间的坑内存镜像里的时间默认是UTC很多题目问的是攻击者什么时候做了什么操作如果不做时区转换答案会差好几个小时。Windows系统日志和文件时间也类似先弄清楚题目要的是UTC还是本地时间。我自己就吃过这个亏最终答案差了一个时区页面怎么提交都不对。3. 进程与网络行为分析从几百个进程里揪出恶意软件3.1 pslist和pstree先把进程全景铺开系统基础信息拿到之后重心转向进程分析。pslist会把内存镜像里的进程列表拉出来python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 pslist --outputcsv pslist.csv python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 pstree pstree.txt为什么推荐CSV因为进程数量多黑色终端里翻看容易漏导入Excel或者WPS里筛选就直观多了。先看几个要素进程名、PID、父进程PID、进程路径。正常系统里svchost.exe这种系统进程的父进程通常是services.exe但如果你看到某个进程的路径在C:\Users\xxx\AppData\Local\Temp下面那基本就是重点怀疑对象。pstree能帮你快速判断父子关系是否异常。比如某个看起来像文档阅读器的进程子进程却是PowerShell这种组合在正常使用中几乎不会出现。3.2 cmdline和dlllist让恶意行为浮现出来进程名可以伪装但启动参数往往藏不住。用cmdline查看所有进程的命令行python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 cmdline恶意程序常见的手法是在命令行里塞PowerShell加密参数、下载器URL、或者一段base64字符串。我在本地复现的时候最可疑的进程就是躲在用户Temp目录下命令行参数里带了一串看起来像编码后的数据配合父进程和网络行为一对照基本就能锁定。dlllist可以顺手跑一下看进程加载了哪些DLL。如果某个DLL的路径在临时目录或者名称和系统DLL很像但路径不对那大概率是DLL劫持或者注入。这个细节在后续判断恶意样本时很有用。3.3 netscan结果要和进程联动单独看netscan输出还不够一定要把IP和PID对应起来。比如看到本地IP的某个随机高端口正在和远程IP通信而这个PID正是你在pslist里标记的可疑进程那恶意行为就闭环了。如果题目问的是C2服务器地址把Foreign Address一列整理出来结合威胁情报平台查一下IP历史通常能确认。CTF题目里的IP不一定是真实恶意IP但按照这个思路做就没问题。3.4 隐藏进程和psxview有的恶意软件会尝试隐藏进程让普通枚举看不到。psxview可以对比多种进程枚举方式的结果找出那些只在部分列表里出现的进程python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 psxview如果发现某个进程在PsActiveProcessHead里存在但在CSRSS等其他列表里缺失就要小心了。虽然这种技术多见于RootkitCTF老题里也偶尔会埋这种考点。前半段题目不一定考得这么深但跑一下不亏多条信息多条路。4. 恶意进程内存转储把可疑样本从镜像里抠出来4.1 malfind找出内存里的异常代码当你锁定了一个可疑PID下一步就是看它内存里有没有注入的可执行代码。malfind专门检查进程虚拟地址空间里那些可读可写可执行的区域python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 malfind -p PID输出里经常能看到某段内存以MZ开头这就是一个完整的PE文件被映射到了进程空间里。正常程序不会在堆内存里凭空冒出一个PE头遇到这种情况基本实锤了恶意代码被注入进来了。而且很多无文件攻击的样本在磁盘上根本不存在只有在这里能发现。4.2 procdump和memdump的区别拿到可疑PID后需要把内存内容转储出来。Volatility 2提供两种常用方式# 转储进程的可执行文件 python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 procdump -p PID -D dump/ # 转储进程的完整虚拟地址空间 python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 memdump -p PID -D dump/procdump适合快速还原PE文件做静态分析输出的是一个个可执行文件。memdump则会生成一个很大的内存镜像文件里面包含进程堆、栈、加载的DLL、明文数据等。分析恶意软件行为时memdump更全面但文件巨大磁盘空间要提前留好。4.3 转储文件到手后的第一轮鉴定对转储出来的文件先用基础命令过一遍file executable.exe sha256sum executable.exe strings executable.exe | grep -i http\|flag\|key\|加密file能告诉你它到底是什么格式sha256sum用于查VirusTotal或本地记录strings则快速扫一遍敏感字符串。CTF定制的恶意样本在在线平台上不一定查得到但内核逻辑其实大差不差勒索软件会有加密扩展名和勒索信内容挖矿程序会连矿池地址远控木马会有C2域名或IP。把字符串里的关键信息捞出来通常就能推断功能。4.4 用简单Yara规则做交叉验证如果你写过Yara规则这里可以派上用场。自己写一条简单的规则扫一下镜像里所有进程内存python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 yarascan -Y MZyarascan会把所有命中MZ头的位置列出来再结合之前的可疑PID做交叉验证。这个方法在真实应急响应里也很常用核心思路是不只看有没有恶意进程还要看恶意代码出现在哪些进程里。5. 文件对象、剪贴板与字符串搜索最后一公里的线索挖掘5.1 filescan和dumpfiles从内存里抠文件前面还在分析进程现在该看文件了。filescan会扫描内存中的文件对象包括已经打开过、缓存过甚至被删除但仍留在内存里的文件python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 filescan filescan.txt输出第一列是物理偏移后面是文件路径。我一般会先grep一下常见扩展名grep -iE \.(jpg|png|zip|docx|pdf|txt|rar|7z) filescan.txt如果试题说桌面上有个加密压缩包或者攻击者查看过某张图片大概率就在这里。确认目标后用偏移量提取文件python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 dumpfiles -Q 0x0000000001234567 -D dump_files/注意dumpfiles输出的文件名经常带.dat后缀这是Volatility的命名规则不代表文件损坏。提取完记得重新用file看一眼真实格式。5.2 剪贴板、IE历史和记事本残留有些线索藏在用户行为里。clipboard可以读取剪贴板内容万一当时攻击者或用户复制过Flag这里直接就有python2 vol.py -f OtterCTF.vmem --profileWin7SP1x64 clipboardiehistory能还原IE浏览记录包括访问过的URL和文件名。notepad插件可以查看记事本未保存内容Win7镜像上效果相对可靠。这些插件不一定每道题都有产出但跑一遍成本极低属于明显的高性价比操作。5.3 strings搜索小心 UTF-16 编码内存里大量字符串是UTF-16LE编码尤其是Windows系统相关的文本。如果只搜ASCII很容易漏掉关键Flag。strings -el OtterCTF.vmem | grep -i OtterCTF\|flag-el表示按UTF-16LE小端编码搜索这个参数一定要记住。CTF比赛里Flag格式通常是OtterCTF{...}所以搜到OtterCTF{就可以开始兴奋了。不过直接对4GB的镜像跑strings会比较慢建议优先对之前memdump出来的可疑进程内存做搜索缩小范围、提高精度。5.4 图片和压缩包里的隐写是后半段的事如果提取出的文件是一个加密压缩包或者看起来坏掉的图片那往往是后半段的伏笔。前半段你只需要把这些文件完整保留下来记录好来源进程和偏移地址。后面做隐写分析或者解密的时候这些记录就是你的线索地图。6. 复盘与避坑那些内存取证必踩的雷6.1 Volatility 2和3别再纠结了很多新手一直在纠结用哪个版本。我的建议是两个都装以2为主、3为辅。OtterCTF这类老镜像Volatility 2的资料最全报错最好查。Volatility 3在新系统和新镜像上更强但在这里反而容易因为插件差异给你添乱。遇到Volatility 3解析报错的时候切回2往往直接通畅反过来也一样。6.2 Profile识别错误后的排查顺序如果imageinfo给出的Profile在运行pslist时报出一堆Invalid address或者进程列表明显不全按这个顺序排查换一个Suggested Profile比如Win7SP1x64_24000用--kdbg手动指定KDBG结构地址用Volatility 3的windows.info交叉验证检查镜像文件是否被截断或二次打包损坏第一步先怀疑Profile不要怀疑人生。这个问题占了内存取证踩坑的一半以上。6.3 养成记录取证过程的好习惯CTF做题虽然不用写正式报告但我强烈建议按真实取证流程做记录。命令输出全部重定向到文件关键发现整理成表命令输出文件关键发现imageinfoprofile.txtWin7SP1x64hivelisthives.txtSYSTEM/SAM偏移pslistpslist.csv可疑进程PID/路径netscannetscan.txt外联IPmalfindmalfind.txt注入代码区域proc/memdumpdump/样本文件这样做的好处是复查时不用重新跑全量命令也方便和队友交流。真实案件里这叫底稿CTF赛场上这叫解题过程本质是一回事。6.4 做题顺序的自检清单在结束前篇之前你应该能回答这几个问题镜像的操作系统版本、主机名、用户列表、IP地址是什么最可疑的进程是哪个它的启动参数和父子关系是什么这个进程有没有外联连到了哪里它的内存里有没有注入代码能不能成功转储如果以上全部完成前篇的核心考点基本就覆盖完了。我自己在复现这道题的时候最大的感悟就是别急着冲向Flag先把地基打牢。很多人卡在中间往往不是因为没有工具而是基础信息没取全后面的恶意样本分析自然就对不上号。最后分享一个我自己的习惯做题时把每条跑过的命令都写进history或者一个临时脚本遇到问题能快速重跑也不用担心漏掉某个环节。OtterCTF 2018虽然是几年前的题但前篇这套流程放到今天的应急响应里依然通用。后面有机会我再把后篇整理出来重点聊恶意样本的行为还原和加密数据解密那部分才是真正烧脑的开始。