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 的 CubismMoc MonoBehaviour 抽出 (亦可 Frida hook 原生 csmReviveMocInPlace 即時攔截)
  • 貼圖 atlast_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 卡片動畫 bugmov_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

在 SiaoHub 檢視原始檔