近期有新游上线,久违的写篇流水账。
在 Android 目录下可以找到热更新包的配置文件,后缀名是 .bytes
,直接打开发现是加密过的,基本没有规律可言。
首先找 libil2cpp.so
和 global-metadata.dat
,两个文件都没有加密,可以直接用 Il2CppDumper 解出。打开导出的 stringliteral.json
文件,搜索 .bytes
,发现确实存在该字符串,位于地址 0x5249578
。
在 IDA 中打开 libil2cpp.so
,定位到这个地址,搜索其交叉引用,可以找到一个函数 SQLiteLoader$$OnLoadFile
,疑似用于加载配置文件。
在反汇编结果中发现该函数只是读取了配置文件内容,没有对文件解密的过程,在相关函数中寻找也一无所获。但是在另一个函数 ConfigDBMap$$IsDBInUse
中发现调用了 MUGame.LuaUtil$$IsFileLocked
,怀疑配置文件读取发生在 lua 脚本中。
热更新包中可以找到分散的 lua 文件,其中有一个文件名为 DBTools.bytes
,文件头是 1B 4C 4A
,标准的 LuaJIT 文件头,尝试使用 ljd 反编译,可惜失败。重新打开文件,发现存在字符串字面量 dbPassword
,最后可以找到一串显然格格不入的字符串 apj20240312
(已脱敏),疑似数据库密码。
Google 搜索「SQLite3 加密」,基本上都指向同一个软件 SQLCipher。软件付费分发但是免费开源。在 WSL2 上克隆储存库之后按照 README.md
中的方法 2 编译成功,之后尝试解密。
1 | milkory@mky0:~/test$ ./sqlcipher |
可见解密成功。接下来要写脚本导出数据,我选择使用 Python 库 pysqlcipher3 来导出。这个库也需要自己构建。
构建后,编写解包脚本,其中核心代码如下。
1 | from pysqlcipher3 import dbapi2 as sqlite |
以上是对游戏数据的解包流程。该游戏的游戏资源也无法直接解出,但不在本文讨论范围之内。这里简单提一下,只要把文件头中多余的 UnityFS
片段删去,就能用 Studio 处理了。