一、概述
内存是唯一一个能拿到运行时明文密钥的地方。
磁盘上的加密数据,密钥要么以密文形式存在,要么需要用户提供口令。而只要系统处于解锁或已登录状态,密钥与凭据就在内存里以可直接读取的形态存在。
这决定了取证侧的一个基本分工:落盘加密的破局点在内存,不在密码学。
一台加密的机器,磁盘镜像里只有包裹态的密钥;但它一旦开机登录,解密后的会话密钥、域凭据、应用令牌就驻留在内存里。
要解决的是"如何把凭据相关的结构体和内存区域固定为证据"——定位、转储、离线分析、记录。这里明确不提供明文口令的具体提取实现,原因在二、核心原理里说得很清楚:合规要求、最小化原则,以及证据价值的严重不对称。
这一步落在分析环节,上游是已获取并完成合规确认的内存镜像,下游是"这条凭据能证明什么"的定性。两个位置约束必须先说清楚。
一是它与加密卷取证是互补而非替代关系——内存里拿到的是运行态密钥,磁盘侧拿到的是可复现的密文与包裹密钥,两条路径各自独立可采。
二是提取动作本身必须可复现、可交代,三、操作步骤的第 0 步"合规确认"不可跳过。
Windows 凭据分三层存储(2.1 详述):hashdump 从内存中已解密的 SAM hive 取本地账户哈希,lsadump 与 cachedump 从 SECURITY hive 取 LSA Secret。六个目标及其载体与判据:
| 目标 |
内存中的载体 |
关键判据 |
| 本地账户哈希 |
已解密的 SAM / SYSTEM hive |
hashdump 输出,BOOT_KEY 关系 |
| 域凭据 |
LSA Secret 结构 |
lsadump 输出,条目时间与类型 |
| 缓存域凭据 |
SECURITY hive 的 Cache 键 |
cachedump 输出的用户名 / 域名 / 16 字节缓存哈希 |
| LSASS 完整内存 |
进程转储 lsass.dmp |
独立哈希,体积与进程匹配 |
| 卷密钥 |
驱动上下文中的明文 FVEK |
与该卷的保护器组合对应 |
| 应用层密钥 |
内存中的 API Key、Token、JWT 结构 |
上下文(哪个进程、哪个应用) |
第四行与第六行是反复强调的边界:LSASS 转储必须逐个哈希后单独保管,而内存里扫出的字符串不等于可用凭据——一个 Bearer 开头的串可能是过期令牌,也可能属于另一个用户。
据此能回答的是这些:某账户的口令哈希是什么、某账户是否曾在本机登录、内存中是否存在某把会话密钥、某个应用是否持有令牌、加密卷在采集时是否处于解锁状态。
回答不了的是这些。它不能回答"某条凭据当前是否仍然有效"——哈希存在不等于凭据可用,撤销与轮转在内存外发生。也不能据此认定"某人实施了某行为",凭据归属与行为归属之间隔着账户使用记录这一层。
动手之前,先确认你已经具备:明确的取证授权与合规意识、内存镜像与 Volatility 基础操作能力、以及对 Windows 凭据存储体系(SAM/SECURITY/DPAPI 层次)的基本理解。合规意识缺失时先看合规授权与法律边界;
镜像还没有则看内存采集方法。
凭据落到磁盘侧后怎么固定,去远程工具凭据解密;加密卷密钥的落盘路径看BitLocker 全卷加密取证。
二、核心原理
2.1 Windows 凭据的三层存储
| 层 |
位置 |
内容 |
保护机制 |
内存版插件 |
| SAM |
System32\config\SAM |
本地账户的 NTLM 哈希(RID + hash) |
SYSTEM hive 中的 BOOT_KEY XOR 加密 |
windows.registry.hashdump |
| SECURITY hive |
System32\config\SECURITY |
域凭据、缓存的域登录、服务账号口令、EFS 私钥 |
DPAPI(系统密钥 + 用户密钥双重加密) |
windows.registry.lsadump、windows.registry.cachedump |
| LSASS 进程 |
lsass.exe 地址空间 |
正在使用的凭据、会话票据、已解密的用户数据 |
Windows 内部机制 |
无专用转储插件,用 windows.memmap --pid --dump |
层与插件不是一一对应:SECURITY hive 这一层上同时有 lsadump(遍历 Policy 下的 Secrets)和 cachedump(遍历 Cache 键下的缓存条目)两个插件,「三层存储」不等于「三个插件」。
重要区别:SAM 里是哈希(不可逆),LSA Secret 里是 DPAPI 加密的密文(lsadump 利用内存中已解密的 DPAPI 主密钥解开),LSASS 内存里是使用中的凭据状态。
三者信息量不同,不可互相替代。
插件名核实:Volatility 3 没有 windows.lsass_dump 这个插件。
LSASS 内存转储的规范做法是用 windows.memmap --pid <PID> --dump 导出该进程内存映射。
任何声称提供 lsass_dump 的教程都在误导。
2.2 hashdump:从内存中的 SAM 提取本地账户哈希
流程:遍历内存中的注册表 hive(依赖 windows.registry.hivelist)→ 定位 SAM 与 SYSTEM → 取出 BOOT_KEY 与 HBootKey → 逐账户用 HBootKey 解密 NTLM 哈希。
Volatility 3 输出的是四列表格:
User rid lmhash nthash
Administrator 500 aad3b435b51404eeaad3b435b51404ee 31d6cfe0d16ae931b73c59d7e0c089c0
Guest 501 aad3b435b51404eeaad3b435b51404ee 31d6cfe0d16ae931b73c59d7e0c089c0
格式澄清(vol2 → vol3 迁移中最常见的错误):Volatility 2 的 hashdump 输出是冒号分隔的 user:rid:lm:nt:::,而 vol3 是 User/rid/lmhash/nthash 四列。网上大量教程仍在用 vol2 的冒号格式,脚本按冒号切分在 vol3 上会直接失败。
⚠️ hashdump 没有 --sam 参数——它通过 automagic 自行定位 hive,失败时检查 hivelist 是否同时列出 SAM 与 SYSTEM。
空口令判读:nthash 为 31d6cfe0d16ae931b73c59d7e0c089c0 即为空口令(MD4(""),源码中以 empty_nt 常量定义)。lmhash 的 aad3b435b51404eeaad3b435b51404ee 同样是空值标记。
2.3 lsadump:解密 LSA Secret
原理:从内存中的 SECURITY hive 取出 BOOT_KEY(依赖 hashdump 的解密链路)→ 由它导出 LSA 密钥(lsakey) → 遍历 Policy\Secrets 下的每个子键 → 读取其 CurrVal 并跳过前 4 字节长度头 → 用 AES 解密(Vista 之前走 RC4 派生)。
输出 Key / Secret / Hex 三列。
⚠️ 列语义(源码核实,与多数教程的说法不同):框架 CLI 渲染器对这两列的呈现方式都不是网上常见的"可读明文"或"连续 hex 串":
Secret 列类型是 format_hints.HexBytes,渲染成 hex + ASCII 双栏 dump(每 16 字节一行,非打印字符显示为 .),并且每行前后都带换行符——它会跨行输出。
Hex 列类型是 bytes,渲染成 空格分隔的逐字节 hex(4b 4c 53 41),不是 0x4b4c5341 那种连续串。
实践影响有两条:① lsadump 的 quick/pretty 输出不能按行 awk '{print $1}' 取 Key——多行字段会打乱列对齐;② 要稳定提取 Key 名,必须用 CSV 渲染器(换行会被 csv 模块转义为字段内的 \ ),见 3.4。
这也是本文只讲凭据转储与离线分析、不讲明文提取的技术原因之一:lsadump 给的是密钥材料(DPAPI/Session Key),不是账号口令;把它变成明文需要额外的、口令驱动的解密步骤,属于本文范围之外。
依赖关系(源码核实):框架层面 lsadump 声明了对 hashdump(版本 1.1.0)与 hivelist(版本 2.0.0)的版本依赖——hashdump 失败时 lsadump 也会失败,两者共享 hive 定位与 BOOT_KEY 解密逻辑。
2.4 cachedump:缓存的域凭据条目
原理:域环境下 SECURITY hive 的 Cache 键下缓存着上次登录的域凭据,用当前用户口令派生的 NL$KM 密钥加密。cachedump 的处理链是:从 Policy 取出 LSA 密钥 → 用它解开 Policy\Secrets\NL$KM 拿到 NL$KM 本身 → 遍历 Cache 键下的每个条目(跳过 NL$Control)→ 解析条目头拿到用户名长度、域名长度、校验 ch → 用 NL$KM 解密条目尾部 → 从明文里按偏移切出用户名、域名和哈希:
Username Domain Domain name Hash
zhangsan CONTOSO contoso.com <bytes 类型,MS-CACHE v2 哈希>
⚠️ 三处必须核实过的澄清:
- 列名是
Domain name(含空格),不是 DomainName。CSV 表头是 TreeDepth,Username,Domain,Domain name,Hash。
Hash 列不是「加密材料」,而是已用 NL$KM 解密后取出的哈希(明文缓冲区的头 16 字节,MS-CACHE v2 形式)。它的类型是 bytes——不是口令,也不是可直接登录的凭据,而是需要候选口令才能比对的缓存哈希。
- 输出里没有时间戳。 插件只产出
Username / Domain / Domain name / Hash 四列,「条目更新时间」这个判据在 cachedump 的输出里根本不存在。要拿缓存条目本身的时间,得回到 Cache 键下各值项的注册表时间戳,或改用 windows.registry.printkey 直接读键。
框架中它只声明了 kernel / hivelist / lsadump / hashdump 四项依赖,cachedump 不接受 --password 参数——它做的是在已解密的内存镜像上直接给出缓存哈希。把缓存哈希还原为口令需要外部用已获授权的候选口令做验证,这是另一个工具的工作,不在本文范围内。
2.5 LSASS 进程内存转储(无专用插件)
LSASS 是 Windows 凭据处理的中心进程,其地址空间里同时存在已解密的凭据、会话票据、缓存的用户数据。
Volatility 3 没有 lsass_dump 插件,导出方式是 windows.memmap --pid <PID> --dump(命令见 3.5)。
其正当用途是离线反汇编分析、检测注入(正常 LSASS 内不应有非微软模块)、时间点取证。
明文口令提取本篇不涉及实现。
合规提示:LSASS 进程内存包含当前所有登录用户的凭据材料与会话票据。
导出到文件系统意味着扩大了敏感数据的暴露面,必须有明确授权,且导出文件应按与内存镜像同等级别保护。
2.6 内存中的加密密钥与应用层令牌
BitLocker FVEK:内存中真正有价值的是 fvevol.sys 驱动维护的 FVE context 结构。
其中实际加密扇区的密钥在卷解锁期间以可直接读取的形态驻留。
相关内容见加密取证板块。
现实约束:提取 FVEK 需要解析 FVE context 的内部结构,目前没有成熟的现成 Volatility 插件,通常需要自定义插件或 WinDbg + IDA 手工分析。技术门槛高,不建议作为首选路径。
EFS 私钥:EFS 的 RSA 私钥在 DPAPI 保护下存储于用户配置单元,内存中可见其明文 PEM 形态(BEGIN RSA PRIVATE KEY)。
VeraCrypt 挂载卷后,派生密钥材料在驱动上下文中以缓存形式存在(搜 VeraCrypt / TrueCrypt / \\.\PhysicalDrive)。
完整路径见 EFS、VeraCrypt——本篇只说明"内存是它们的第三条路径"。
应用层密钥通常以明文或简单编码形式驻留内存。
这是低成本高回报的搜索方向:
| 类型 |
典型特征 |
| API Key |
api_key、apiKey、x-api-key、Authorization: Bearer |
| Token / JWT |
access_token、refresh_token、Bearer eyJ...(三段 Base64 用 . 分隔) |
| 数据库连接串 |
Server=...;Database=...;User Id=...;Password=... |
| 云厂商密钥 |
AWS AKIA...、Google API key(AIza...)、Azure 连接串 |
| SSH 私钥 |
BEGIN OPENSSH PRIVATE KEY / BEGIN RSA PRIVATE KEY |
| Webhook |
hooks.slack.com/services/...、Discord webhook |
从内存明文反推浏览器的 DPAPI 与 App-Bound 保护范围、以及落到磁盘后的解密路径,见浏览器密码库与 Token 解密。
三、操作步骤
3.1 第 0 步:合规确认(不可跳过)
在执行任何凭据提取前,确认:
☐ 是否有针对该设备/该账户的明确取证授权?
☐ 授权范围是否覆盖"提取账户凭据"?
☐ 提取出的凭据将如何使用(验证身份 / 破解加密 / 关联分析)?
☐ 谁有权接触提取结果?是否有保密协议与销毁要求?
☐ 是否已通知相关方(依法律要求)?
⚠️ 未取得明确授权而提取他人账户凭据,在多数司法辖区可能构成犯罪。 本节不是形式主义——它是本篇的前置条件。
3.2 第 1 步:确认 hive 可用
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.registry.hivelist \
| grep -iE 'SAM|SYSTEM|SECURITY'
没有 SAM / SYSTEM / SECURITY 就说明凭据提取这条路走不通(可能原因:内存采集不完整、系统使用特定配置、hive 已被换出到页面文件)。
3.3 第 2 步:本地账户哈希
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.registry.hashdump | tee /work/hashdump.txt
python3 vol.py -f DigiForensics-mem.raw -r csv windows.registry.hashdump > /work/hashdump.csv
head -1 /work/hashdump.csv
TreeDepth,User,rid,lmhash,nthash
↑ 首列是渲染器自动加的,实体列从第 2 列开始
| 观察 |
含义 |
| 无输出 |
hive 未定位,或符号/镜像问题(先查 hivelist) |
| 仅有少量账户 |
可能是域环境(本地账户少),需转向 lsadump |
nthash 为 31d6cfe0... |
空口令(该值是空哈希的标准形式) |
| 账户数与磁盘 SAM 不一致 |
记录为异常——可能存在被删账户或内存与磁盘不同步 |
3.4 第 3 步:域凭据与 LSA Secret
必须用 csv 渲染:quick/pretty 下 Secret 列是多行 hex+ASCII dump,列会错位
python3 vol.py -f DigiForensics-mem.raw -r csv windows.registry.lsadump \
/work/lsadump.csv 2>/work/lsadump.err
chmod 600 /work/lsadump.csv
取 Key 名(多行字段已被 csv 转义为字段内的 \
)
python3 - <<'PY'
import csv
with open("/work/lsadump.csv", newline="") as fh:
for row in csv.DictReader(fh):
print(row["Key"])
PY
cachedump 遍历 SECURITY 的 Cache 键(跳过 NL$Control),不接受口令参数
python3 vol.py -f DigiForensics-mem.raw -r csv windows.registry.cachedump > /work/cachedump.csv
表头:TreeDepth,Username,Domain,Domain name,Hash
Hash 是 NL$KM 解密后取出的 MS-CACHE v2 缓存哈希;输出无时间戳
⚠️ 此命令的输出即为已解密的凭据材料。 处理规则:立即重定向到受控目录,不要在共享终端滚动显示;该文件按与内存镜像同等级别保护;分析完毕后按授权要求销毁或归档。
3.5 第 4 步:LSASS 进程转储(离线分析)
1) 确认 LSASS 的 PID
python3 vol.py -f DigiForensics-mem.raw -r pretty windows.pslist | grep -i lsass
2) 转储其内存映射(无 lsass_dump