如何用skitter-creek-bath-salts的dram_dump批量读取受保护内存:含别名映射与往返校准的完整教程
【免费下载链接】skitter-creek-bath-saltsUnlocking _everything_ on the CPU with DRAM scrambling项目地址: https://gitcode.com/gh_mirrors/sk/skitter-creek-bath-salts
skitter-creek-bath-salts是一个基于DRAM 地址扰序(DRAM scrambling)的研究型开源工具集:它改写 AMD 内存控制器(MCT/DCT)里的一两个配置位,把物理地址"搅成意大利面",让受保护内存区域(PSP、SMRAM、C6 上下文等)通过别名地址(alias)变得可读。本文聚焦其中的批量读取工具 userspace/dram_dump.c:从别名映射的原理、.map文件的生成,到内置的固件指纹校验 + 往返校准双重安全机制,一步步带你完成对受保护 DRAM 区间的批量转储。
一、动手前的三个前提
⚠️ 先说清楚:这是一个研究向工具,会临时改写活体内存控制器状态,可能造成系统不稳定。请务必在自有的备用机器上实验,并准备好断电重启手段。
| 前提 | 说明 |
|---|---|
| 目标平台 | AMD Family 16h(数据手册公开了内存控制器转换寄存器,且不可锁定)。可用 userspace/platform_check.c 提前自检,不支持的平台会直接退出 |
| root 权限 | 所有用户态工具均需 root 运行,内核模块 kernel/spaghettify.c 提供/dev/spaghettify设备节点 |
| 匹配内存的别名映射 | 仓库 data/maps/ 内置了多种 DIMM 配置的现成.map文件(如2x4gb_fw-s1b1_at-s0b0.map),文件名中的fw-sWbS / at-sWbS表示固件默认状态与扰序访问状态;也可用自己硬件生成(见第四节) |
用sudo dmidecode -t memory查看你的内存条型号,对照 data/maps/ 前缀即可选到正确的映射。
二、别名映射:为什么"别的地址"能读到受保护内存 🔑
理解dram_dump的关键,是理解别名(alias):
- 正常视图下,物理地址
A经内存控制器换算成 DRAM 坐标,落在受保护单元里——普通读访问会被围栏(fence)挡下,返回全0xff; - 控制器被临时翻转某个 swizzle 位后,另一个普通地址
B经换算落在同一个 DRAM 单元; - 于是"在扰序视图中读
B"就等于"在正常视图中读A",而B是一条完全不受围栏保护的普通通路。读完后立刻还原控制器位,平台恢复原状,不留痕迹。
这个"地址搅动"过程的直观演示(&x != &x的瞬间):
数学上,地址转换是 GF(2) 线性映射,所以只要采集一批(target, alias)数据对,就能用 Z3 求解出完整的扰序矩阵——这就是.map文件。
三、dram_dump 的内置双保险:固件指纹 + 往返校准
批量读受保护内存最怕的不是读不到,而是读错位置。userspace/dram_dump.c 在每次真正读取之前强制执行两道检查:
1️⃣ 固件指纹(fw-fingerprint)校验工具先查询控制器实时寄存器(swizzle / bankswap / bank-addr-map),与.map文件头部的fw_*字段比对。BIOS 更新、换内存条、重训导致任何一位不一致,运行立即中止(--ignore-fw-mismatch可强制跳过,仅建议研究使用)。
2️⃣ 往返校准(round-trip calibration)
- 内核模块在加载时保留一块 16 KB 的安全区(
DRAM_SCRATCH_INFO),其中没有别的内核代码会碰; - 工具从安全区自动挑一个满足约束的普通地址,短暂写入一个随机魔数(随后恢复),再按映射预测的别名在扰序视图下读回;
- 读回值不等于魔数 → 映射对当前硬件失效 → 中止。
巧妙的地方:校准用的翻转访问是读而不是写——映射算错只会让你看到垃圾值并安全中止,绝不会向未知 DRAM 单元误写。同一开机会话内确认无误后,可用--dangerously-skip-calibration跳过以加快速度。
四、完整工作流:五步转储受保护内存
# 1. 编译(生成 kernel/spaghettify.ko 与全部用户态工具) make # 2. 平台自检,不在支持范围直接退出 ./userspace/platform_check || exit 1 # 3. 查看当前固件默认状态(fw_* 指纹来源) sudo ./userspace/dram_state # 4. 定位受保护区域,自动导出 PSP_BASE / PSP_SIZE eval "$(sudo ./userspace/dram_carveouts --region psp)" # 5. 批量读取:原始字节输出到 stdout,重定向保存 sudo ./userspace/dram_dump --protected-pa $PSP_BASE --length $PSP_SIZE \ $(printf -- '--map %s ' data/maps/2x4gb_*.map) > psp.bin💡 注意第 5 步一次性传入了三张映射(at-s0b0/at-s0b1/at-s1b0)。每张映射由不同的扰序状态求解而来,各有一些秩亏导致的"死区"(某些字无别名可达);dram_dump按顺序对每个 4 字节字采用第一张能到达它的映射,三者取并集即可覆盖单张映射读不到的地址。所有映射必须描述同一块硬件(位宽、MMIO 空洞、fw_*一致),否则拒绝启动。
没有现成映射?三步自己生成
仓库提供了现成的分析流水线(analysis/):
# 采集别名数据对(JSONL 存入 data/aliases/,可用 --at-swizzle/--at-bankswap 选扰序态) sudo python3 analysis/gather_aliases.py --at-swizzle 0 --at-bankswap 0 \ --log data/aliases/2x4gb_fw-s1b1_at-s0b0.jsonl # 求解并保存映射(--progress 可实时观察矩阵收敛) python3 analysis/unspaghettify.py \ --save-map data/maps/2x4gb_fw-s1b1_at-s0b0.map \ < data/aliases/2x4gb_fw-s1b1_at-s0b0.jsonl- analysis/gather_aliases.py:自动探测 DIMM 容量与搜索掩码,随机采样收集别名;不稳定的机器可改用 analysis/gather_aliases_remote.py 经 SSH 远程驱动;
- analysis/unspaghettify.py:Z3 逐位求解扰序矩阵;加
--target-pa 0x...还能"只查不读",打印任意受保护地址的别名——dram_dump读取路径上不需要 Z3,昂贵的求解只发生在离线这一步。
采集到的数据与求解出的映射在仓库中各有一份样例,格式可对照:
- 样例数据:data/aliases/2x4gb_fw-s1b1_at-s0b0.jsonl
- 样例映射:data/maps/2x4gb_fw-s1b1_at-s0b0.map
五、dram_dump 常用选项速查
| 选项 | 作用 |
|---|---|
-s, --protected-pa <hex> | 受保护区间起始物理地址(正常视图) |
-l, --length <n> | 读取字节数,须为 4 的倍数 |
--map <file> | 别名映射,可重复传入以并集覆盖 |
--dry-run | 只执行双保险检查后退出,不读任何 DRAM——验证映射是否可用的首选 |
--calibrate-pa <hex> | 指定校准用地址(缺省时自动从模块安全区挑选) |
--dangerously-skip-calibration | 跳过往返校准(名字故意起得刺眼,慎点) |
--ignore-fw-mismatch | 指纹不一致时仍强制加载映射 |
--fenced-range <lo>,<hi> | 把更大的受保护预留区标为围栏窗口,防止"别名落回窗口内"的假读 |
--allow-fenced-alias | 对落回围栏的别名也照样读(通常只会拿到围栏填充值) |
--plain | 不加载映射、不改控制器,直接按地址读——用于对比"未扰序"的基线视图 |
一个实用技巧:先用--plain读同一区间,看到整片0xff(围栏填充值),再切换到--map模式读同一区间——前后对比能直观感受扰序前后的差别。
六、读不到怎么办?处理 0xff 空洞
dram_dump对每个读不到的 4 字节字会填充0xff、计数并在 stderr 告警,保证输出字节流与请求区间严格对齐。出现空洞时的排查顺序:
- 多传几张
--map:不同(at_swizzle, at_bankswap)组合的死区互不相同,并集能救回大量地址; - 换扰序态重新采集:
--at-swizzle/--at-bankswap与固件态至少差一位才能产生有效扰序,换一组重新跑 analysis/gather_aliases.py 再求解; - 注意围栏别名:某些映射的数学别名会落回受保护窗口内部,翻转读取只会撞回围栏拿到填充值。
dram_dump默认把这类目标按空洞处理而不是静默报告假数据,可用--fenced-range显式标出整个预留区来捕捉这种情况; - 接受现实:若求解出的矩阵在前向方向秩亏、目标地址的位模式恰好在常量位上冲突,则任何开关与 DIMM 组合都不可达——这是控制器状态与内存布局的数学属性,不是工具能"再努力一下"的。
七、安全提示 ⚠️
- 工具会临时改写活体内存控制器,README 原话:"Expect system instability; have a power-cycle path ready"(预期系统不稳定,请准备好断电重启路径);
- 只读方向(
dram_dump)误操作的代价是读出垃圾;而它的写侧兄弟 userspace/dram_poke.c 误写会损坏未知 DRAM 单元,所以后者强制校准、逐字回读验证、跳过无法到达的字——批量实验请始终从读开始; - 本教程仅面向合法的安全研究场景,请在自己的设备上、为自己的安全评估目的使用。
八、相关源码索引 📁
| 文件 | 说明 |
|---|---|
| userspace/dram_dump.c | 本文主角:批量读取受保护 DRAM 区间 |
| userspace/alias_map.h | 映射解析、GF(2) 伪逆、校准与多映射调度的共享核心 |
| userspace/dram_state.c | 只读快照:当前 fw_* 指纹 |
| userspace/dram_carveouts.c | 定位 PSP / SMRAM / C6 等受保护预留区 |
| analysis/unspaghettify.py | Z3 求解别名映射并保存.map |
| kernel/spaghettify.h | 内核 ioctl 接口定义(DRAM_RW/DRAM_STATE等) |
| USAGE.md | 完整工具参考与端到端示例 |
按这套流程走完,你已经掌握了dram_dump的全部精髓:离线求解一次映射,在线读取零 Z3;指纹 + 校准双保险兜底;多映射并集补齐死区。接下来,试着把psp.bin丢进反汇编器看看 PSP 里的 ARM 代码吧——那是另一个故事了 🕵️
【免费下载链接】skitter-creek-bath-saltsUnlocking _everything_ on the CPU with DRAM scrambling项目地址: https://gitcode.com/gh_mirrors/sk/skitter-creek-bath-salts
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考