免责声明:本篇文章仅作个人逆向分析经验记录与学习之用,所获得的一切资产恕不作任何分享。实践本文章记录的任何操作所产生的一切后果均与本人无关。侵删
本文的撰写离不开 @DNLINYJ 前辈开路,也是前辈的帖子带我认识了Process Monitor这一神器,前辈帖子指路:https://bbs.kanxue.com/thread-279118.htm 和 https://www.52pojie.cn/thread-1840843-1-1.html
1. global-metadata.dat 及 AssetBundle
跟着前辈的文章一步步走就行了,具体怎么操作就请诸位大佬各显神通了,在此略过
2. 植入UnityExplorer
简单介绍一下:UnityExplorer (https://github.com/sinai-dev/UnityExplorer) 是一个十分方便且开源的Unity游戏内调试辅助工具,提供了Inspector、Object Explorer、Console和Hook等诸多强大功能。(可惜的是该项目不知出于何种原因已经archived不再更新)
- 在上一步中得到解密的global-metadata.dat后,先将BepinEx或者MelonLoader框架安装到游戏上,这里根据我的经验il2cpp最好选择MelonLoader 0.5.7版。
MelonLoader官方安装教程: https://github.com/LavaGang/MelonLoader?tab=readme-ov-file#how-to-use-the-installer
(这里我已经安装过了所以显示为RE-INSTALL,正常安装应该只有一个INSTALL按钮)
- 安装MelonLoader后,依照UnityExplorer的Readme安装UnityExplorer
https://github.com/sinai-dev/UnityExplorer?tab=readme-ov-file#melonloader
(其实就是将解压出来的 Mods
文件夹和 UserLibs
文件夹复制到游戏对应目录就行了)
3. 替换global-metadata.dat
安装MelonLoader启动后会在终端发现Cpp2IL报错导致游戏无法正常启动,这是因为原版加密的global-metadata.dat依然在 il2cpp_data\Metadata
文件夹中,需要凭借单身多年的手速将其替换。
替换之前记得将dump出来的global-metadata.dat的第4-8字节的版本号patch正确,经过一遍遍在il2cppDumper中试错后确认为 0x18
。
偷梁换柱具体步骤如下:
在 Game
文件夹中直接启动游戏,不通过启动器启动
立即切换到终端窗口,当 Loading Plugins from ...
字样出现时立即选中终端内任意范围冻结进程,此时游戏窗口应刚好出现并冻结于如图所示白屏状态
复制并备份好原版global-metadata.dat (这一步和接下来的几步理应不会出现“文件被占用”、“无法启动”、游戏崩溃之类的任何错误,如果遇到了说明上一步冻结时机过晚、过早)
将dump并patch好版本(再强调一次,十分甚至九分重要)的global-metadata.dat替换过去
切换至终端窗口,按 Esc
取消选中,解冻进程
退出游戏,将第3步备份好的原版global-metadata.dat替换回来,之后在遇到游戏更新时重新走一遍以上步骤即可
4. 解密其它未封装进AssetBundle的资源
在成功安装UnityExplorer之后,我们应当已经拥有了il2CppDumper提供的dump.cs文件,以及UnityExplorer提供的强大hook功能。至此我们终于可以进入正题,解密其它资源。
4.1 解密.acb、.awb音频
根据以往经验,特殊格式必有特殊工具。通过万能的搜索引擎,我最终找到了 vgmstream
这个强大工具。
通过阅读 USAGE.md
的 Decryption keys 这节可以发现解码.acb和.awb需要 .hcakey
。经过测试发现确实如此,没有.hcakey解码出来的.wav文件就是一坨答辩。
再次借助万能搜索引擎の抛瓦,我找到了@头蟹床大佬的这篇分析.hca的顶级文章。
https://blog.mottomo.moe/categories/Tech/RE/zh/2018-10-12-New-HCA-Encryption/
这里我就直奔主题跳到初步分析密钥设置流程这一节,发现了 criWareUnity_SetDecryptionKey()
这一关键函数。然而在IDA或者il2CppDumper的dump.cs中都搜索不到 SetDecryptionKey
这一函数。
因此我退而求其次,搜索 Decrypt
关键词,这次在IDA中发现了一些跟CriWare有关的目标函数。
其中包括两个跟acb有关的目标函数:
CriWare.CriAtomExAcb::Decrypt()
CriWare.CriAtomPlugin::DecryptAcb()
重启游戏,在点击开始屏幕进入游戏之前的界面尝试用UnityExplorer hook住这两个函数。
点击开始屏幕,切换到终端查看打印输出,发现成功打印出key,调用的函数是 CriWare.CriAtomExAcb::Decrypt()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | [ 13 : 15 : 16.183 ] [UnityExplorer] - - - - - - - - - - - - - - - - - - - -
void CriWare.CriAtomExAcb::Decrypt(ulong key, ulong nonce)
- __instance: CriWare.CriAtomExAcb
- Parameter 0 'key' : 密钥在这里
- Parameter 1 'nonce' : 0
[ 13 : 15 : 22.939 ] [UnityExplorer] - - - - - - - - - - - - - - - - - - - -
void CriWare.CriAtomExAcb::Decrypt(ulong key, ulong nonce)
- __instance: CriWare.CriAtomExAcb
- Parameter 0 'key' : 密钥在这里
- Parameter 1 'nonce' : 0
[ 13 : 15 : 22.966 ] [UnityExplorer] - - - - - - - - - - - - - - - - - - - -
void CriWare.CriAtomExAcb::Decrypt(ulong key, ulong nonce)
- __instance: CriWare.CriAtomExAcb
- Parameter 0 'key' : 密钥在这里
- Parameter 1 'nonce' : 0
[ 13 : 15 : 23.574 ] [UnityExplorer] - - - - - - - - - - - - - - - - - - - -
void CriWare.CriAtomExAcb::Decrypt(ulong key, ulong nonce)
- __instance: CriWare.CriAtomExAcb
- Parameter 0 'key' : 密钥在这里
- Parameter 1 'nonce' : 0
|
根据vgmstream的Readme,将得到的 ulong key
转换为hex bytes。注意.net的BigEndian编码问题。go代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 | package main
import (
"encoding/binary"
"fmt"
)
func main() {
bytes : = make([]byte, 8 )
binary.BigEndian.PutUint64(bytes, <替换为密钥>)
fmt.Printf( "%X\n" , bytes)
}
|
将转换后的hex bytes保存为.hcakey并复制到与.acb相同的目录,就可以愉快地用vgmstream解密.acb和.awb音频了
4.2 转换.usm视频
相较而言,.usm视频的解码对我而言就简单多了
- 使用vgmtoolbox解码.usm为.m2v和.hca
vgmtoolbox官方地址:https://sourceforge.net/projects/vgmtoolbox/
- 静静等待vgmtoolbox完成它的工作
- 使用vgmstream转换.hca为.wav
- 直接用potplayer播放(注意音轨数量,有的两条有的一条)
- 如需合并音视频为一个文件,先用everything处理一下文件名
- 再使用ffmpeg合并
1 2 | @REM 单音轨
FOR % % a IN ( * .m2v) DO ffmpeg - i "%%a" - i "%%a_0.wav" - map 0 :v - map 1 :a - c copy "%%a.mov"
|
1 2 | @REM 双音轨
FOR % % a IN ( * .m2v) DO ffmpeg - i "%%a" - i "%%a_0.wav" - i "%%a_1.wav" - map 0 :v - map 1 :a - map 2 :a - c copy "%%a.mov"
|
4.3 解码Table
这一部分其实说简单也不行说难也不行。
不能说它难是因为这就是一个加了长度前缀的protobuf,通过 08 0A
这种极具protobuf特点的字节可以看出。因此解码前只需删掉前4字节即可正常解码。
关于长度前缀详见protobuf官方文档:https://protobuf.dev/programming-guides/encoding/#length-types
不能说它简单是由于游戏使用了HybridCLR,global-metadata.dat和DummyDLL中的类型信息都不全,暂时无法自动化地导出.proto用来解析,因此在这部分如果想要获得到.json需要人工去对照CheatEngine得到的mono信息猜出字段名,十分麻烦,故在此略过,直接使用python的 protod
包解码raw wire
1 | protod - - file XXXXXXXX.bytes
|
(找错别字环节)
结语
在这次实战中,遇到的最大阻力其实并不是易盾反作弊的保护,反而是HybridCLR的特殊指令集,导致解码Table时由于缺少.proto又不能通过DLL直接生成,需要手动一个个字段地猜,不是很优雅。希望之后有大佬能写出将HybridCLR指令集转换回IL指令集的工程,这样就可以开摆了嘻嘻(bushi
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2023-12-27 22:59
被iuwerury编辑
,原因: 修改细节