Hololive Dreams 資源逆向工程完整報告
目標:game.qualiarts.hololive.dreams(國際版)— 逆向 octo (Sonet OctoCache) 資源系統的加密,
建立通用解密工具,完整取出音效、影片、Live2D 模型與角色美術。
平台:越獄 iPhone X (iOS, Sileo/OpenSSH) + macOS (Apple Silicon) 引擎:Unity 6000.3.0b1,IL2CPP,metadata v39
本文為統整報告。逐步踩坑紀錄另見
RE_NOTES.md(音效/影片)、RE_NOTES_LIVE2D.md(Live2D)、 演算法數學式見ALGORITHM_MATH.md,工具見hddunpack/。
0. 成果總覽
| 類型 | 數量 | 格式 | 目錄 |
|---|---|---|---|
| Live2D 角色貼圖 atlas | 80 | 4096×4096 PNG | all_live2d_atlas/ |
| Live2D moc3 模型 | 4 | .moc3 v5 |
live2d_models/ |
| 角色全身立繪 | 19 | 4096×4096 PNG | character_art/ |
| 音效 | 1240 | WAV | converted/audio_wav/ |
| 影片 | 120 | MP4 | converted/video_mp4/ |
(angka受限於「已在遊戲內觸發下載」的資源;未下載的 catalog 項目觸發後可用同工具解出。)
1. 取得資源
- USB (
iproxy+ libimobiledevice) 建立 SSH/Frida 隧道,比 WiFi 快且穩(WiFi 曾慢到 18KB/s) - 打包
Library/octo(3.4GB)、解密後的 IPA、global-metadata.dat - 教訓:SSH 預設 cipher 慢,
-c aes128-gcm@openssh.com或走 USB 快很多
2. IL2CPP Method Map(逆向的地基)
global-metadata.dat被加密(magic 對不上)→ Perfare/Il2CppDumper 失敗- Frida attach 執行中 App,記憶體掃 metadata magic (
AF 1B B1 FA),dump 出解密後 metadata(v39) - Il2CppDumper 不支援 v39 → 改用 Il2CppInspectorRedux(osx-arm64 預編譯)→ 產出 561MB
il2cpp.json - Java 被系統 (AppleSystemPolicy) 封鎖,改編譯 rz-ghidra(純 C++ Ghidra 反編譯引擎,無需 JVM)
3. 加密演算法(核心)
公開版說明:本節原本載有完整、可直接實作的虛擬碼(填充順序、雜湊種子、 白化步驟),已於此公開版移除。 保留下面的形狀描述與 §3.1 的過程紀錄。 移除的理由與判斷依據見根目錄
README.md。需要說明的是,這裡不存在「拿掉金鑰、留下演算法」這種切法:金鑰是從 資產名推導出來的,推導程序本身就是金鑰。原始文件裡唯一的魔術常數是一個 單一位元組的雜湊種子——在已知明文就寫在檔頭的情況下,256 種可能是 微秒級的窮舉。只遮那個常數等於什麼都沒遮,所以整段一起移除。
反編譯目標:Vision.Octo.ResourceDecrypter.DecryptHeader。
可以說的形狀:
- 這是遊戲自製的一層,不是 CRIWARE 官方加密——hook
CriWareDecrypter.Initialize可以看到enableAtom=false, enableMana=false,官方那層根本沒啟用 - 金鑰逐檔不同,由該資源的 address(原始檔名字串)衍生;演算法本身對所有 檔案一致,不隨版本或時間改變
- 是對稱的 XOR,只作用在檔頭前 256 bytes,後面的內容原封不動
- 音效/影片與 AssetBundle 共用同一套,只差在前綴與 XOR 起點
| 資源 | 前綴 | XOR 起點 | address 來源 |
|---|---|---|---|
| CRI 音效/影片 | QUAVMAGIC(9B 明文) |
byte 9 | 原始檔名 |
| AssetBundle | 無 | byte 0 | catalog 資產名 |
3.1 一段關於「以為是兩套加密」的誠實紀錄
最初只看表面(bundle 無 QUAVMAGIC、開頭高熵)就過早斷定是不同機制,繞了很大一圈 (試過 LZ4、AES、GPU hook、Metal capture…)。真正的突破是回到基本功做已知明文攻擊:
- 所有 UnityFS bundle 前 30 bytes 固定(
UnityFS\0\0\0\0\x08+5.x.x\0+0.0.0\0) 密文 XOR 已知明文→ keystream,一看 char/~char 交錯結構就認出是音效同一套- 缺的只是「正確的 key 輸入」= catalog 資產名
教訓:表面特徵不同 ≠ 演算法不同。該先做已知明文分析再下結論。
4. octo 資源目錄 (catalog)
- 目錄清單存於加密 pdb (
octocacheevai),遊戲啟動後解密為 protobuf 存在記憶體 - Frida dump 含
vo_sys等字串的 rw 區塊 → 用\x12<len><name>...\x2a\x20<32hex hash>解析 - 得 36184 筆 hash↔address(涵蓋 img/mdl/fbx/live2d/vo/bgm… 所有類型)
- hash 即 octo 磁碟檔名,address 即解密金鑰 → 一份 catalog 解鎖一切
5. 各資源類型的抽取
音效/影片
decrypt_resource()→ 標準 CRI (@UTF/CRID)vgmstream-cli轉 WAV(1240個),wannacri+ffmpeg轉 MP4(120個;56個卡片動畫因 wannacri bug 未轉)
Live2D
- moc3:模型骨架/形變資料。走 octo bundle,UnityPy 從解密後 bundle 的
CubismMocMonoBehaviour 抽出 (亦可 Frida hook 原生csmReviveMocInPlace即時攔截) - 貼圖 atlas:
t_live2d_*_texture_00,4096×4096。曾誤判「只在 GPU」——實為 加密躺在 octo 磁碟、存取時只在記憶體解密不落地。用 catalog address +decrypt_bundle離線解出 - 貼圖為 ASTC 壓縮,UnityPy 於 CPU 解碼成 PNG(不需 GPU/實機)
角色美術
img_chr_full_2d_*(4096全身立繪)、img_chr_room_*、img_music_jacket_*等,同樣 bundle 解密後 UnityPy 抽出
6. 通用工具 hddunpack/
sssekai 風格的 CLI + library:
python -m hddunpack catalog-parse catalog_region.bin -o catalog.json
python -m hddunpack unpack-octo octo/v1 --catalog catalog.json -o out/ --filter live2d_mdl
python -m hddunpack decrypt-bundle <file> --address live2d_mdl_00019-nrml-0016-00 -o out.bundle模組:crypto(金鑰/解密)、catalog(目錄解析)、extract(UnityPy 抽取)。
7. 尚未完成 / 後續
- catalog 完全離線化:目前需從記憶體 dump。若逆向 pdb (
octocacheevai) 本身的加密, 即可純離線建 catalog,工具完全不需實機 - 未下載資源:catalog 有 170 個 live2d_mdl,僅 80 個已下載到快取。其餘需遊戲內觸發下載,
或直接對 CDN (
https://asset.game-hololive-dreams.com/...) 拉取加密檔後用同工具解 - wannacri 卡片動畫 bug:
mov_card_sign_*系列 USM 未能轉出(工具限制,非加密問題) - Live2D 完整打包:目前有 moc3 + atlas,可再補
.model3.json/.physics3.json(在 bundle 的 TextAsset/MonoBehaviour 內)以組成官方 Cubism 可直接開啟的完整模型
附錄:關鍵位址(UnityFramework, Unity 6000.3.0b1)
# 加密
Vision.Octo.ResourceDecrypter.DecryptHeader 0x0B449AAC
KDF 包裝 0x0B449F9C
填充+雜湊+白化 0x0B44B28C
# Live2D 原生 (Cubism Core, 匯出符號)
csmReviveMocInPlace (Module export)
# IL2CPP embedding API (本地符號表, 未匯出但存在)
il2cpp_domain_get / _get_assemblies 0x002d4e18 / 0x002d4e24
il2cpp_class_from_name 0x002d48b4
il2cpp_runtime_invoke 0x002d533c
# Unity
AssetBundle.LoadFromFile(Async) 0x09E0153C / 0x09E01228
ImageConversion.EncodeToPNG 0x09F44568
Object.FindObjectsOfType(Type) 0x09ED7AC8