ELF + MDF PSB 样本分析笔记

逆向工程 实用技术ELF + MDF PSB
浏览数 - 63发布于 - 2026-04-13 - 13:27

感谢CODEX5.4的正确结果hhh


这份文档不是只给结论,而是按一次可复现的逆向过程来写,

目标是带着样本结构一路走到 freemote 所需的 -k / -l 参数。

这次样本的最终验证结果是:

  • -k 38757621acf82
  • -l 131

验证命令:

powershell
.\tool\freemote\PsbDecompile.exe info-psb -k 38757621acf82 -l 131 .\压缩包\image_info.psb.m -o .\out_try

实际结果:

  • 成功提取 837 个文件

但重点不是记住这两个值,而是知道为什么它们就是答案。

1. 先做题目画像

样本目录里最重要的对象有三个:

  • main.elf
  • 压缩包\image_info.psb.m
  • 压缩包\image_body.bin

第一眼要先判断三者的分工。

1.1 我的初始假设

结合你给的线索:

  • ELF
  • krkr / e-mote / PSB
  • freemote
  • -k
  • -l

我一开始的高优先级假设是:

  1. main.elf 是游戏主程序或者资源加载器,不是单独的小工具。
  2. .psb.mbody.bin 是一对资源容器,可能是 info/body 配对。
  3. -k-l 更可能来自程序初始化时设置的资源解密参数,而不是文件头里直接明文摆着。

这个判断背后的原因很简单:

  • 如果 freemote 需要参数,说明资源不是裸 PSB。
  • 如果资源不是裸 PSB,最可靠的参数来源往往是“程序自己怎么初始化解密器”。

2. 第一轮:不进反编译,先看文件头

这一步的目标只有一个:

  • 判断 .m.bin 是不是已经加了壳

这次我直接看了前 128 字节。

2.1 image_info.psb.m 文件头

text
00000000  6D 64 66 00 ...

2.2 image_body.bin 文件头

text
00000000  6D 64 66 00 ...

这里最关键的 4 个字节是:

  • 6D 64 66 00
  • ASCII 就是 mdf\0

2.3 这一步为什么关键

很多人看到 .psb.m 会下意识以为是“已经是 PSB,只是后缀特殊”。

但这个样本不是。

它的外层是 MDF,不是裸 PSB

这意味着:

  1. freemote 的参数不是“可选增强项”,而是解开外层封装的必要条件。
  2. 如果你一开始去硬看 PSB 结构、字段表、对象树,很可能浪费时间。

3. 第二轮:先把工具参数语义搞清楚

这一步非常重要,因为如果你连参数含义都理解错,后面整条线都会偏。

我直接看了本地 freemote 的帮助。

命令:

powershell
.\tool\freemote\PsbDecompile.exe --help
.\tool\freemote\PsbDecompile.exe info-psb --help

得到的关键信息是:

  • -k|--key <KEY>: Set key
  • -l|--length <LEN>: Set key length. Default=131
  • -s|--seed <SEED>: Set MT19937 MDF seed

4. 这一步帮我们排除了什么

一开始最容易误判的是:

  • -l 当成 label
  • -l 当成资源名
  • -l 当成 level

freemote 已经把语义写死了:

  • -l 是 key length

所以后面在 ELF 里要找的,不再是“某个 label 字符串”,而是:

  1. 一个 key
  2. 一个 key length
  3. 可能还有 seed

5. 第三轮:判断 ELF 是什么程序

这一步不是为了炫架构知识,而是为了决定后面是走静态优先,还是动态优先。

我先读了 ELF 头。

结论:

  • ELF64
  • little-endian
  • e_machine = 0xB7

0xB7 对应:

  • AArch64

再结合字符串区里大量出现:

  • NintendoSDK
  • nn::gfx
  • nn::vi

可以进一步判断:

  • 这基本是 Nintendo Switch 游戏程序

6. 这个判断有什么实际意义

它直接影响策略:

  1. 这不是一份适合本机直接跑的普通 Linux x64 ELF。
  2. 动态分析不是不能做,但成本比静态高。
  3. 优先级应当是:先利用已保留的符号、字符串、IDA 数据库把资源链扣出来。

换句话说,这题最短路不是先上 gdb,而是先找“谁设置了资源加密参数”。

7. 第四轮:从字符串和符号切资源加载链

这一步开始进入真正的样本相关分析。

我重点搜了这些词:

  • psb
  • PSBObject
  • FetchResource
  • LoadResource
  • MountArchive
  • body.bin
  • info.psb.m
  • key
  • seed

命中的高价值结果有:

  • script_info.psb.m
  • script_body.bin
  • MFile::MountArchive
  • ResourceManager::FetchResource
  • SQPSBFile
  • PSBObject

7.1 为什么 script_info.psb.m / script_body.bin 非常关键

因为这不是我们根据文件名在外部猜出来的。

它们出现在程序自己的字符串区里,说明:

  • 程序内部确实采用了 info.psb.m + body.bin 配对归档的加载范式

这会让你对 image_info.psb.m + image_body.bin 的判断更有把握。

8. 第五轮:追交叉引用,不要泛追,要追主流程

我追了 script_info.psb.mscript_body.bin 的交叉引用,最后命中了:

  • M2Main
  • 地址 0x17960

这是全题最关键的一步。

因为一旦你在主流程里看到:

  • 先设置 crypt
  • 再挂载 archive

那基本就离答案只差一次验证了。

9. 核心证据:M2Main 里的初始化逻辑

反编译结果里最重要的伪代码片段是:

c
strcpy((char *)v136 + 1, "38757621acf82");
MFile::SetCryptSetting(v136, 131);

strcpy(..., "script_info.psb.m");
strcpy(..., "script_body.bin");
MFile::MountArchive(v136, v134, &v132, 1);

这段逻辑应该怎么读:

  1. 程序启动时先准备一个字符串 38757621acf82
  2. 调用 MFile::SetCryptSetting(..., 131)
  3. 再去挂载 script_info.psb.mscript_body.bin

这就说明:

  • 这组 crypt setting 是 archive 挂载前必须设置的
  • 它不是某个后期资源临时派生值
  • 它就是资源系统自己的初始化参数

10. 为什么我认为这就是 freemote 的答案

理由不是一个,而是三个同时成立:

10.1 语义对得上

freemote 需要:

  • key
  • key length

程序里正好有:

  • 一个 key 样式的字符串
  • 一个长度值 131

10.2 时机对得上

这个设置发生在:

  • MountArchive 之前

也就是说它是资源系统基础配置,而不是某个无关字符串。

10.3 对象对得上

后面紧跟的就是:

  • script_info.psb.m
  • script_body.bin

这和你手头的:

  • image_info.psb.m
  • image_body.bin

属于同一类对象。

11. 最后一轮:必须回喂工具验证

逆向里最怕“看起来像答案”,所以必须实测。

我实际执行了:

powershell
.\tool\freemote\PsbDecompile.exe info-psb -k 38757621acf82 -l 131 .\压缩包\image_info.psb.m -o .\out_try

结果:

  • 自动识别 body 文件:image_body.bin
  • 成功提取 837 个文件

这一步把整条推理链闭环了。

所以这题不是“推测大概如此”,而是:

  • 已从 ELF 中定位参数来源
  • 已用 freemote 成功验证

12. 这题里我最看重的观察点

如果你以后自己做类似题,最值得优先盯住的是这些点。

12.1 文件头是不是已经说明“还有一层”

像这题:

  • mdf\0

这四个字节本身就告诉你:

  • 先不要把它当裸 PSB

12.2 初始化函数里有没有“设 crypt 再 mount”

这类题最值钱的不是深处的解密循环,而是早期初始化代码。

因为真正的参数往往在这里直接落地。

12.3 info/body 成对文件名

一旦同时看到:

  • xxx_info.psb.m
  • xxx_body.bin

就该马上联想到:

  • archive 索引 + 资源正文

12.4 参数设置函数的形态

最像答案入口的函数通常长这样:

  • SetCryptSetting(key, len)
  • SetKey(key, len)
  • InitMdf(seed, len)
  • MountArchive(info, body, ...)

12.5 数值 131

如果工具帮助里告诉你默认 -l = 131,而程序里又正好显式传 131,这个重合就非常值得重视。

13. 如果这次没这么顺,下一步应该怎么切

虽然这次已经成功,但你要知道卡住时怎么切线。

13.1 静态卡住

优先顺序:

  1. 先追 MountArchive
  2. 再追 SetCryptSetting
  3. 再追 FetchResource
  4. 最后才去看具体解密循环

13.2 动态卡住

不要先断“解密循环”。

优先断:

  1. SetCryptSetting
  2. MountArchive

因为这两个点更接近“参数生成”和“参数使用”的交界处。

13.3 样本结构反推

如果程序分析暂时做不下去,就先靠结构缩小范围:

  1. .psb.mbody.bin 是否配对
  2. 两者是否同头
  3. 是否都为 mdf\0
  4. freemote 默认 -l 是否先尝试 131

14. 这次题目的最终结论

main.elf 的主流程 M2Main 中,可以直接恢复出资源解密初始化参数:

  • key = 38757621acf82
  • length = 131

对应到 freemote

powershell
.\tool\freemote\PsbDecompile.exe info-psb -k 38757621acf82 -l 131 .\压缩包\image_info.psb.m -o .\out_try

并且已经成功提取。

15. 你下次自己做时的最短路线

如果你只记 5 步,记这 5 步:

  1. 先看 .m / .bin 文件头
  2. 确认 freemote 参数语义
  3. 在 ELF 里搜 MountArchive / SetCryptSetting
  4. info.psb.m / body.bin 的交叉引用
  5. 拿到值后立刻回喂工具验证

这次样本就是按这条线打通的。

本文版权遵循 CC BY-NC 协议 本站版权政策

(。>︿<。) 已经一滴回复都不剩了哦~