国际服 Blue Archive 剧情文本提取记录
看 52pojie 上有大佬发了关于提取国际服 BA 剧情文本的帖子,正好有项目需要用到剧情文本,就想试试自己提取。
结果,SQLite 数据库居然打不开!一看文件头才知道,原来数据库文件被加密了。
初步分析
这款游戏使用 Unity 引擎开发,于是使用 IL2CPPDumper 提取 C# 结构。很幸运,这一版本的游戏并没有对 IL2CPP 的相关文件做修改,很轻松地拿到了文件。
将 libil2cpp.so 导入 IDA 反汇编,再使用 IL2CPPDumper 的 IDA 脚本恢复函数名和符号。
根据 52 的那篇帖子,决定先从 ScenarioScriptExcel 结构体相关的逻辑入手。
静态分析
在函数列表中搜索 ScenarioScriptExcel,发现位于 0x206222C 的 MX_Data_ScenarioData__GetScenarioScriptExcels 方法返回了一个 ScenarioScriptExcel 数组。我想这个应该就是获取剧情脚本的函数。
接着看函数调用,发现其中调用

根据 IL2CPP 中处理泛型的方式,可以得出类似的 C# 代码
1 | |
跳转到 MX_Data_DataBaseForSQLite__Get_object_ 方法,其主要逻辑是从自身的 repositoryDic 字段中获取继承自 ISQLiteRepository 的泛型的实例。推测其逻辑大概是由 DataBaseForSQLite 加载数据库,ISQLiteRepository 对象对应了数据库中部分表序列化后的数据。
这时候我在撞运气分析 DataBaseForSQLite 中的其他函数,想看看有没有初始化逻辑。突然发现 MX_Data_DataBaseForSQLite__Initialize_31878536 在泛型方法 MX_Data_DataManager__Load_object_ 中被调用了:

v2 的类型是由传入的泛型决定的,当传入的泛型继承自 DataBaseForSQLite 时,函数则初始化 DataBaseForSQLite 并返回对象。
ExcelDB.db 是在游戏初始化阶段就被加载的,于是搜索 MX_Data_DataManager__Load_object_ 在文件中的调用,找到了 MX_Data_GameDataInitializer__InitInBuildDataManager 中的调用:

这正好也是 DataManager 的初始化逻辑。
其中 LocalizeInBuildData 继承自 DataBaseForSQLite,由此确定 DataManager 确实管理了 ExcelDB.db 的加载。
动态调试
我这时并不清楚解密逻辑具体的位置,网上有文章提到 Windows 上的 BA 使用的是 SQLCipher。于是我便找到了 MX_Data_DataManager__SetSQLCipher 方法,查看 MX_Data_DataManager__SetSQLCipher 的调用点。只有一个直接调用,在 QueuingGetCryptoKeysNetworkTask__HandleMessage 方法里。

看了一眼逻辑,函数的四个参数都是从网络包获取的,看来静态调试已经走到头了。
本来想直接 hook QueuingGetCryptoKeysNetworkTask__HandleMessage 拿到 SetSQLCipher 的参数的,结果看了一眼 SetSQLCipher 中还使用 MX.Crypto 中的方法对这四个参数做了额外处理。索性直接 hook SetSQLCipher 这个方法。
SetSQLCipher 中调用的游戏加密函数调用是

这个函数的逻辑并不在 libil2cpp.so 中,从函数逻辑来看,似乎是 IL2CPP 动态加载函数😓
受限于我的水平,我对加密具体的算法的研究就止步于此了。好在获取到密钥并不需要知道具体的加密函数,直接 hook MX_Core_Crypto_O955132dac5d41feabf746f1417de25a752632adde964512864135725661e5e1f__set_Key 的调用:

类似的 C# 代码就是
1 | |
set_Key 函数只是将传入的 Byte[] value 拆分存入了类的 part1、part2、part3 字段,类中并没有其他的加密逻辑。
直接 hook 对 set_Key 的调用,抓取其第二个参数的值:

编写 Frida 脚本,在 onEnter 时 hook,从 X0 寄存器找到 Byte[] value 的地址,抓取数据:
1 | |
成功获取到了 key。
解密数据库
在 dump 出来的 string literal 中找到了:
1 | |
推断游戏使用了 SQLCipher 来加密数据库。
使用 SQLCipher 交互式命令行连接到数据库。在测试了不同版本的加密参数后,确定了 SQLCipher 配置为:
1 | |

成功加载了数据库。
剧情文本取得
之后便是 dump 了数据库到未加密的数据库文件。
ScenarioScriptExcel 结构体和数据库内部没有其他的改动,直接按照 52 上大佬的方式导出剧情文本就好。

感谢 il2cppdumper 的开发者。