mcpField
R
re-flutter

re-flutter

github.com/dslsdzcセキュリティ20650 回インストール2 週間前に更新
リポジトリ

re-flutter について

re-flutterは、FlutterおよびDartアプリのリバースエンジニアリング向けスキルです。AOTスナップショット解析、シンボル復元、Dart VMの動的解析を組み合わせ、アプリの処理とFlutterエンジンを区別します。

主な機能

  1. 01Flutterのdebug・release成果物の識別
  2. 02Dart AOTスナップショットと文字列の解析
  3. 03シンボルマップによる名前の復元
  4. 04Ghidraによるアプリ関数の特定
  5. 05FridaとVM Serviceによる動的解析

活用シーン

  1. 01AndroidまたはiOSのFlutterアプリ解析
  2. 02スナップショットからの通信先やプロトコルの調査
  3. 03署名、暗号処理、MethodChannelの追跡
  4. 04管理下のデバイスでのDart実行動作の観察

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dslsdzc/rev-skills --skill re-flutter

インストール後、Claude Code で/re-flutterを入力して読み込まれたか確認します。

詳細

Flutter / Dart AOT 逆向(快照解析 / 符号还原 / VM 动态分析)

何时使用 / 何时不用

  • 用:Android 包内发现 lib/<abi>/libapp.so(Dart AOT 快照)与 libflutter.so(引擎)——业务逻辑在 libapp.so
  • 用:iOS 的 App.framework/App(AOT 快照嵌入 Mach-O,Flutter.framework 为引擎)
  • 用:debug 构建的 assets/flutter_assets/kernel_blob.bin(Dart kernel 二进制,含类/函数名与 AST)
  • 用:需要还原 Flutter App 的 Dart 业务逻辑(网络接口、签名/加密算法、协议、MethodChannel 通道)
  • 不用:纯原生 Android App(无 libapp.so / kernel_blob,走 [[re-apk]])
  • 不用:纯原生 iOS App(无 App.framework,走 [[re-ios]])
  • 不用:Flutter Web(产物是 JS/WASM 而非快照,走 [[re-wasm]])
  • 不用:引擎层本身(libflutter.so / Flutter.framework 内部逻辑,见步骤 4)
  • 注意:动态步骤在受控设备 / 模拟器快照内执行([[re-analyze/platform-tips]] 最高原则);静态优先(大型样本原则)

工具准备

参考 [[re-analyze/platform-tips]]——Flutter 产物大(libapp.so 数十 MB 起),遵循「静态优先(大型样本)」:先静态定位、动态按需补充;动态(运行 / 重打包 App)默认在受控设备 / 模拟器快照内。

python3(快照头部 / 分区解析主力)
  • Linux: apt install python3 / dnf install python3 / pacman -S python
  • macOS: 自带;Windows: 官方安装器或 choco install python
  • 验证: python3 --version
binutils(readelf / strings / objcopy,快照定位与段提取)
  • Debian/Ubuntu: apt install binutils;Fedora: dnf install binutils;Arch: pacman -S binutils
  • macOS: Xcode 命令行工具自带(xcode-select --install)
  • Windows: 无自带 binutils——用 WSL,或 Ghidra 内置解析替代
  • 验证: readelf --version && strings --version
Ghidra(指令段反汇编 / 反编译与交叉引用)
  • 装法与验证见 [[re-ghidra]];无 Ghidra 时 IDA / radare2 等价([[re-ida]] / [[re-radare2]])
  • 验证: 导入 libapp.so 后能定位 _kDart* 符号
frida(动态侧,Dart VM 入口 hook)
  • 安装、frida-server 部署与版本匹配见 [[re-frida]] 工具准备(主机与设备版本必须一致)
  • 验证: frida --version;frida-ps -U 列出设备进程
blutter(AOT 快照自动还原,可选)
  • release libapp.so 的类/函数/对象池自动还原,安装与用法见 [[re-hybrid-app]] 工具准备;本技能步骤 2 以手动解析为核心方法,blutter 作自动化替代与核对
  • 验证: 输出目录出现 pp.txt 且含目标 App 类名

操作步骤

按顺序执行;每步产物(段提取 / 解析脚本 / hook 脚本 + sha256)存档。

  1. 识别构建模式(debug / release):

    unzip -l app.apk | grep -E 'kernel_blob|libapp|libflutter'    # Android 容器内定位
    file lib/arm64-v8a/libapp.so
    readelf -sW lib/arm64-v8a/libapp.so | grep _kDart             # release: AOT 快照符号
    xxd -l 32 assets/flutter_assets/kernel_blob.bin               # debug: 前 4 字节应为 ef cd ab 90(kernel magic 0x90abcdef 的小端)
    readelf -sW libapp.so | grep -c _kDartIsolateSnapshot         # AOT 段符号(data/instructions),比按 ASCII 串搜可靠
    
    • debug: assets/flutter_assets/kernel_blob.bin 存在(header 共 8 字节 = magic 0x90abcdef + formatVersion,Dart kernel 二进制,类/函数名与 AST 保留,还原成本低)→ 直接步骤 3
    • release: 无 kernel_blob,业务代码在 libapp.so 的 _kDartIsolateSnapshotData(数据)/ _kDartIsolateSnapshotInstructions(指令)→ 步骤 2
    • iOS: Payload/<App>.app/Frameworks/App.framework/App(业务)与 Flutter.framework/Flutter(引擎),Mach-O 内同段名,otool -l / strings 定位
  2. Dart AOT 快照分区解析(release):

    objcopy --dump-section .rodata=snap.rodata libapp.so    # 数据段(含 VM + isolate 两份快照)
    objcopy --dump-section .text=snap.text libapp.so        # 指令段
    python3 - <<'PY'
    import struct
    data = open('snap.rodata', 'rb').read()
    MAGIC = 0xDCDCF5F5                          # Dart VM snapshot magic(int32;文件里是小端 f5 f5 dc dc)
    KINDS = {0: 'full', 1: 'full-jit', 2: 'full-aot'}
    off = 0
    while True:
        off = data.find(struct.pack('<I', MAGIC), off)
        if off < 0: break
        length, kind = struct.unpack('<qq', data[off+4:off+20])   # 基础头共 20 字节
        print('snapshot @', hex(off), 'kind:', KINDS.get(kind, kind), 'length:', length)
        off += 4
    PY
    
    • 头部格式:magic(4, 0xdcdcf5f5) + length(8) + kind(8) = 20 字节基础头(其后是哈希等字段)。没有 ASCII "SNAPSHOT",也没有固定 12 字节版本串——按字符串搜 SNAPSHOT 在真实 AOT 样本上找不到任何东西
    • 头之后是版本相关的内部序列化布局(子 blob 的顺序与结构随 Dart 版本变化),不要当稳定格式承诺:要精确解析就对着目标 SDK 版本的 runtime/vm/snapshot.h、序列化器与 image snapshot 实现来读,而不是套一份写死的分区表
    • 机器码本体在独立的指令段(_kDartIsolateSnapshotInstructions),与数据段分开;kind 为 full-aot 时即 AOT 快照
    • _kDartVmSnapshot* 是 VM 快照(dart:core/io 等标准库,所有 Flutter App 共用),业务只在 _kDartIsolateSnapshot*
    • strings blob 提取业务字符串:
      strings -n 6 -t x snap.rodata | grep -E 'https?://|token|secret|api/|MethodChannel'
      
    • 指令段导入 Ghidra:自动分析常认不出 AOT 代码对象,需按目标版本的代码对象元数据(子 blob 布局随版本变化,见上条)划分函数边界;反编译后按字符串交叉引用定位业务函数
    • 自动替代:blutter(工具准备)还原 asm/对象池/类名;手动解析用于核对与版本兜底(坑 5)
  3. 符号还原:

    • 构建侧留了映射(--obfuscate --split-debug-info):
      # 构建命令: flutter build apk --obfuscate --split-debug-info=symbols/
      ls symbols/                               # app.android-arm64.symbols 等(每 ABI 一个)
      python3 -c "import json; d=json.load(open('symbols/app.android-arm64.symbols')); print(len(d['data']))"
      # JSON 结构: {"data": {"混淆名": "原名"}} —— 反向映射表,查表即得原名
      
    • 无映射时字符串交叉引用反推:strings blob 里的 UI 文案 / 错误提示 / URL → Ghidra 交叉引用找引用指令 → 沿调用链上下游展开(方法对应 [[re-binary-core]] 交叉引用)
    • debug kernel_blob.bin:kernel 格式含类/函数名与 AST,strings + 结构解析直接还原调用关系,无需符号表
  4. 引擎与业务区分:

    • libflutter.so / Flutter.framework:Flutter 引擎(C++:Dart VM、Skia、通道实现),各 App 基本一致——不分析,仅用于取 VM 导出符号(步骤 5)
    • libapp.so / App.framework/App:Dart 业务代码 AOT 快照——分析目标
    • _kDartVmSnapshot*(VM 快照 / 标准库)≠ _kDartIsolateSnapshot*(业务):业务字符串与类名只在 isolate 快照
    • 原生插件与 MethodChannel 原生端在业务 so 之外,走 [[re-binary-core]]
  5. 动态侧(受控设备 / 模拟器快照内,[[re-analyze/platform-tips]] 默认沙箱):

    • hook Dart VM 类库注册点——libflutter.so 导出的 Dart_CreateRootLibrary(root library 注册入口,onEnter 读库 URI):
      var f = Process.getModuleByName("libflutter.so").findExportByName("Dart_CreateRootLibrary");
      if (f) Interceptor.attach(f, {
        onEnter: function (args) {
          var uri = args[1].readCString();     // 库 URI(C 字符串)
          console.log("root library: " + uri);
        }
      });
      
      • release AOT 下该 C API 可能不触发(库直接由快照反序列化)——改 hook VM 内部 dart::Library::New(libflutter.so 内符号,随版本裁剪)或直接走下面的 VM Service 枚举
    • Dart VM Service 枚举 isolate / 库(release 需先开启 VM service:hook Dart_SetVMFlags / Dart_InitIsolate 注入 --enable-vm-service,或改用 debug 构建验证动态行为):
      adb forward tcp:8181 tcp:8181
      curl http://127.0.0.1:8181/getVM
      curl "http://127.0.0.1:8181/getIsolate?isolateId=isolates/1"
      curl "http://127.0.0.1:8181/getInstalledLibraries?isolateId=isolates/1"
      
      • getInstalledLibraries 列出全部业务库(库名 = 包名 / 路径);getObject 按 objectId 枚举类/字段;VM service 元数据里类名与字符串仍可读
    • 定位到目标函数后按地址 hook(_kDartIsolateSnapshotInstructions 基址 + 代码对象偏移),插桩底座见 [[re-frida]]

跨域联合

  • [[re-mobile]]:移动 App 分析网关——Flutter 分支由 [[re-hybrid-app]] 与本技能承接(容器侧静态与动态跑通走网关)
  • [[re-hybrid-app]]:Flutter 快照自动还原(blutter / reFlutter)与 MethodChannel 拦截——与本技能(手动快照解析 + VM 层动态)互补
  • [[re-apk]] / [[re-ios]]:Android / iOS 容器侧静态(解包、manifest、签名、DEX/ObjC 原生桥接)
  • [[re-frida]]:动态插桩底座(版本匹配、spawn/attach、反检测、脚本模板)
  • [[re-binary-core]]:libapp.so / libflutter.so 的原生层([[re-format-elf]] + [[re-ghidra]] 反编译引擎与 JNI 桥)

常见坑与陷阱

  • Release に kernel_blob.bin がない:現象——デバッグチュートリアルに従って assets/flutter_assets/kernel_blob.bin を探しても見つかりません。原因——ビルドモードによって生成物が決まります。Debug → JIT(kernel バイナリ。アプリのコードは kernel_blob 内)、Profile と Release はどちらも AOT(アプリのコードは libapp.so のスナップショットセグメント内)です。——Profile は kernel_blob によってアプリのコードを実行するわけではありません。Release との違いは、実行モデルではなく profiling/service/最適化の設定です。対策——まず手順 1 に従ってモードを確認します。kernel_blob がない場合は、手順 2 のスナップショットパーティション解析に進み、assets 内を探し続けないでください
  • obfuscate/tree-shaking 後に名前が短くなる:現象——スナップショット内の関数名/クラス名がすべて a/b などの短い名前になっているか、完全に消えています。原因——--obfuscate による名前の難読化と、tree-shaking による未参照コードの削除です。対策——ビルド側に --split-debug-info のマッピングファイルがあれば、直接照合します(手順 3 の JSON)。マッピングがなければ、strings blob の文言/URL を手がかりに相互参照して呼び出しチェーンを推定します。動的解析では、主要な関数を hook して動作を観察し、補完します
  • エンジンとアプリのコードの混同:現象——libflutter.so の dart:: 内部関数や _kDartVmSnapshot* セグメント(dart:core/io 標準ライブラリ)の分析に深入りしてしまい、時間を浪費します。原因——エンジン層と VM スナップショットはすべての Flutter アプリに共通するコードであり、アプリのコードではありません。対策——アプリのコードは libapp.so の _kDartIsolateSnapshot* のみを分析します。libflutter.so から取得するのは VM のエクスポート情報のみです(手順 4/5)
  • frida のバージョン不一致:現象——frida-ps -U でプロトコルエラー/unable to communicate が発生します。原因——ホスト側の frida-tools とデバイス側の frida-server のバージョンが一致していないか、アーキテクチャが異なります。対策——[[re-frida]] のツール準備に厳密に従います。frida --version を確認して同じバージョンの frida-server をダウンロードし、正しいアーキテクチャ(arm64/arm/x86_64)を選びます
  • スナップショットのバージョンと解析スクリプトの不一致:現象——パーティションのオフセットがずれたり、strings blob の内容が文字化けしたり、解析が中断したりします。原因——Dart のバージョンによってスナップショットのレイアウトが異なります(バージョン文字列はヘッダーで確認できます)。手作業やサードパーティ製スクリプトは特定のバージョンのみを対象としている場合があります。対策——まずヘッダーのバージョン文字列を読み取ります。バージョンごとに解析を分岐します。新しすぎるバージョンでは、汎用的な方法(magic のスキャン+パーティションの意味を推定)で補います
  • hook で読み取った Dart 文字列が文字化けする:現象——Dart オブジェクトのポインターを hook できても、出力が文字化けします。原因——Dart 文字列は C 文字列ではありません(オブジェクトポインターの +7 オフセットに 4 バイトの Smi エンコードされた長さがあり、右に 1 ビットシフトすると実際の長さになります。+15 から UTF-8 データが始まります。このオフセットはコミュニティでの実測値です)。対策——このレイアウトに従って読み取り、長さの上限を設ける readDartString ユーティリティ関数を書きます(レイアウトの詳細は [[re-hybrid-app]] の落とし穴の項目を参照してください)。オフセットは Dart のバージョンによって変動します——gen_snapshot --print-object-layout-to で出力した信頼できるオフセット、または実測値を基準にし、異なるバージョンで固定オフセットをそのまま使わないでください
  • オブジェクトプールのデシリアライズで VLE を固定幅として読み取る:現象——blutter が custom engine/構造的に並べ替えられたスナップショットで失敗(No cluster defined for cid N または空の出力)した後、自作のパーサーで読み取ると ref がずれ、文字列がすべて乱れます。原因——Dart AOT のオブジェクトプールはタグ付きエントリーの配列です(X27 がプールのベースアドレスを保持し、LDR Xn,[X27,#off] で off/8 番目のスロットを読み込みます)。埋め込み領域では、各エントリーが 1 バイトの type を持ちます(旧バージョンでは TypeBits = eb & 0x7f、PatchableBit = 0x80)。type 0(kTaggedObject)は VLE ref(可変長)を持ち、**type 1(kImmediate)のペイロードは固定幅の整数(intptr_t であり、VLE ではありません)**です(よくある落とし穴)。符号なし VLE は各バイトのデータが 7 ビットで、最終バイトの bit7 が 1 になります(5→0x85、27673→19 58 81)。なお、上記の type 番号/ビット幅は Dart 2.9 時代のレイアウトです。現行バージョンでは TypeBits のビット数と列挙順が変わっているため、解析前にスナップショットのバージョンに応じたレイアウト表を確認し、異なるバージョン間で使い回さないでください。対策——先頭の length を VLE で読み取った後、各 type に従ってデコードします。slot_offset = idx × 8 です。全体を信頼する前に、既知の ref→offset を 2~4 個使って検証します。アンカーが一致しない場合は、スキャン範囲を絞って再試行します。オブジェクトプールは、符号のないバイナリのシンボルテーブルです——pool_offset → {それを読み込むすべての PC} → {所属する関数} と辿れば、「どの関数が文字列 ref N に触れるか」を静的に特定できます(出典:reverse-skills(inliver233)、MIT)
  • strings で中国語の文言が見つからない:現象——libapp.so に strings/grep を実行しても、中国語/CJK/絵文字のアプリ文言が見つからず、手がかりが途切れます。原因——Dart スナップショットには 2 種類の文字列ストレージがあります。OneByteString(Latin-1/UTF-8。通常の grep で検索可能)と TwoByteString(UTF-16LE)です。中国語/CJK/絵文字のほとんどは TwoByteString に格納されているため、UTF-8 のバイト列で検索しても、UTF-16LE で格納された文字列には当然一致しません。CID 番号は Dart のバージョンによって変動します(旧バージョンでは 85/86、現行 3.x では 93/94/95 など)。CID を固定せず、スナップショットのクラス表から動的に読み取ります。対策——対象の文字列を UTF-8 と UTF-16LE の両方のエンコードで検索し、オフセット+エンコード+出現回数を記録します。--dump-utf16 で CJK 文字列をすべて出力してから grep します。オブジェクトプールでは関数 Code とそれが読み込む文字列が隣接しているため、シンボルがなくてもプール内の近接性から文字列と関数の対応を推定できます(出典:reverse-skills(inliver233)、MIT)
  • プールスロットの文字列のクロスリファレンスが見つからない:現象——プール内の文字列のオフセットがわかっているのに、逆アセンブラのクロスリファレンス一覧が空で、参照関数を特定できません。原因——プールの読み出しには、標準的な idiom が 2 種類しかありません。ADD xR,x27,#page,LSL#12 の後に LDR xD,[xR,#off] が続く形式(offset=(page<<12)+off)と、page==0 の場合の直接 LDR xD,[x27,#off] です。汎用 xref 解析では、こうした間接アドレス指定を見落としがちです。対策——逆アセンブラを必要としない、元のバイト列に対して 2 種類の idiom を照合するパターンスキャナーを書きます。関数の特定には prologue の特徴(fd 79 bf a9 fd 03 0f aa)を使い、逆方向にスキャンして関数の開始位置を特定します。全体を信頼する前に、既知の読み込み位置を 1 つ使って調整します。「スキャナーで見つからない」≠「読み込み元がない」——標準的でないエンコードの読み込みは見落とされるため、陰性の結果は別の方法でも検証が必要です。ちょうど 1 つの関数だけが読み込む文字列は feature-dedicated であり、機能 UI builder の強い手がかりになります(出典:reverse-skills(inliver233)、MIT)
  • build/仮想メソッドの callers=0 をデッドコードと誤判定する:現象——build または仮想メソッドに対して BL xref スキャンを実行すると、呼び出し元が 0 件となり、デッドコードと誤判定して分析を打ち切ってしまいます。原因——Dart の build/仮想メソッドは BL 呼び出しではなく、ディスパッチ stub を介して呼び出されます。オブジェクトの cid を読み取り、ディスパッチテーブルを検索して、blr x30 で仮想ディスパッチするため、静的な BL スキャンでは当然見つかりません。対策——callers=0 は通常の状態であり、デッドコードの証拠にはなりません。呼び出し元を見つけるには動的解析が必要です。ディスパッチ stub または対象関数の onEnter で lr と Thread.backtrace(Backtracer.ACCURATE) を取得し、ディスパッチポイントを特定します(「while rebuilding dirty elements」フレームはフレームワークのレンダリング経路であり、アプリの builder はすでに戻っています)。stub は cid を x0(下位 16 ビット)に置き、オブジェクトポインターは x1 に置きます。代替手段として、オブジェクトのフィールド @1 にも cid が格納されています。cid を取得した後、静的に movk xR,#<cid>,lsl#16 を検索してコンストラクターを見つけ、その BL 呼び出し元が親 build です。テーブル全体を対象にする場合は、X21 がディスパッチテーブルのベースアドレスであるため、cid→コンストラクターの静的マッピングと組み合わせます(出典:reverse-skills(inliver233)、MIT)
  • Dart の bool を 0/1 として扱う:現象——AOT コードを逆コンパイルする際に bool を通常の 0/1 整数として読み取り、ロジックが何度分析しても一致しません。原因——Dart AOT には固定レジスター規約があります。X15=shadow-stack、X26=Thread、X27=Pool、X22=null のセンチネル、X28=ヒープベース(圧縮ポインター)、X21=ディスパッチテーブルベースです。Dart の bool は canonical オブジェクトの tagged pointer であり、0/1 ではありません——True は null からの小さいオフセットにあり、False はさらに大きいオフセットにあります(圧縮されていないポインターでは True = null+0x20(下位 16 ビットは通常 …8061 のような値)、False = null+0x30(…8071)。圧縮ポインター(Flutter の release でデフォルト)では True = null+0x10(…8051)、False = null+0x18(…8059)。消費側の tbz w0,#4(圧縮ポインターでは #3)は、bit=0 の場合に true 分岐に入ります——判定ビットが 0 なのが True です。対策——固定レジスターの意味に従って逆アセンブリを読み取ります(mov x0,x22 は Dart の null を返します。標準的な prologue は stp x29,x30,[x15,#-16]!; mov x29,x15、バイト列は fd 79 bf a9 fd 03 0f aa です)。bool のエンコードは命令の出現頻度から特定できます。add xR,x22,#0x20(圧縮ポインターでは #0x10)は数千回出現し(True)、add xR,x22,#0x30(圧縮時は #0x18)も同様です(False)。オフセット/判定ビットはポインター圧縮とバージョンによって変動するため、実測値または --print-object-layout-to を基準にします。Thread のグローバル G = Thread[x26+0x80] は、フィールドをオフセットに従って読み取ります。割り当て thunk のパターンは mov x2,#imm; movk x2,#cid,lsl16; ldr x4,[x26,#0x228]; br x4 です(出典:reverse-skills(inliver233)、MIT)
  • patch で false を物化する際に movz w0,#0 を使うとクラッシュする(SIGILL):現象——静的 patch または Frida の実行時メモリー書き込みで movz w0,#0(バイト列 52800000)を使って「偽」を物化すると、実行時にシグナル 4(SIGILL)でクラッシュします。原因——Dart bool は tagged pointer であり、0 は不正な immediate tag です。対策——True の物化には add x0,x22,#0x20(バイト列 c0820091)、False には +0x30 を使います(圧縮ポインターでは True が +0x10、False が +0x18)。このルールは静的 patch と Frida の実行時メモリー書き込みの両方に適用されます。Dart bool を書き込む場合は、必ず canonical アドレスを書き込みます。エンコードはポインター圧縮モードとバージョンによって変動するため、実測値を基準にします(出典:reverse-skills(inliver233)、MIT)
  • ダイアログの patch が効かない(三つの発生源を切り分ける):現象——showDialog の呼び出しをすべて NOP してもダイアログが表示されたり、約 60 秒後に定期的に表示されたりして、何度もやり直すうちに時間を浪費します。原因——Flutter のダイアログの発生源は 3 つだけです。誤った切り分けが最大の浪費につながります。(a) 明示的な showDialog BL、(b) 未捕捉例外——MissingPluginException → FlutterError.onError/zone onError → showDialog BL を経由しない非同期ダイアログ、(c) 整合性チェックの集約ゲート——約 7 個のチェックを 1 つの集約関数で OR し、いずれかが true ならダイアログを表示します。対策——logcat と Dart スタックフレームに MissingPluginException/Unhandled Exception があり、定期的に表示される場合は、発生源 (b) です。正しい対処は fault-body コンストラクターを切断することです(body を構築する条件分岐を、無条件でスキップするように変更します)。グローバル handler に表示内容がなくなるため、ダイアログの NOP はやめてください。ダイアログの文言 → プール ref → body builder → 呼び出し元の順に追跡し、callers=0(cid ディスパッチ)で showDialog BL がない場合も、発生源は (b) です。発生源 (c) では、集約関数を探します(多数の check*/get* チャネルメソッド文字列と条件 → ダイアログ構築を参照する関数)。その集約関数の失敗分岐(CMP W0,W22; B.EQ fail)または entry-null ダイアログ構築 helper を NOP します。あるデバイスでは patch が有効でも別のデバイスでは効かない場合は、文言の内容から新たに起動したチェックの種類を特定します(たとえば、検証メッセージの文言は環境/エミュレーターのチェックに対応します)(出典:reverse-skills(inliver233)、MIT)
  • kernel 形式の magic を ASCII の KERNEL と誤認する:現象——「ファイルヘッダーは KERNEL」と考えて grep/xxd で kernel_blob.bin を判定したり、コンテナー内をこの文字列で検索して kernel ファイルを探したりしても、見つからないか、別のものがヒットします。原因——Dart kernel バイナリの magic は数値 0x90abcdef(ソースコード runtime/vm/kernel_binary.h の kMagicProgramFile)です。ファイル内ではリトルエンディアンのバイト列 ef cd ab 90 として表示され、ASCII 文字列ではありません。ヘッダーは 8 バイト(magic + formatVersion)で、その後に形式バージョン番号が続き、バージョン判定に使えます。対策——4 バイトのマジックナンバーで判定し、文字列検索は使わないでください
  • ASCII の SNAPSHOT で AOT スナップショットを探す:現象——実際の libapp.so で grep SNAPSHOT/data.find(b'SNAPSHOT') を実行しても一件も見つからず、「スナップショットがない」と判断したり、文字列からオフセットを推測したりします。原因——Dart VM スナップショットのヘッダーは、数値 magic 0xdcdcf5f5(4B)+ length(8B)+ kind(8B)からなる 20 バイトの基本ヘッダーです。ASCII の SNAPSHOT は存在せず、固定の 12 バイトのバージョン文字列もありません。ヘッダーの後に続くサブ blob のレイアウトは、バージョン依存の内部シリアライズ実装であり、安定した形式として扱うことはできません。対策——0xdcdcf5f5(リトルエンディアンでは f5 f5 dc dc)で位置を特定し、length/kind を読み取ります(kind が full-aot なら AOT スナップショットです)。正確に解析する場合は、対象 SDK バージョンの runtime/vm/snapshot.h とシリアライザーの実装を参照します。機械語の本体は独立した命令セグメントにあります
  • シンボルのない数万個の関数をどこから調べるかわからない:現象——libapp.so にシンボルがなく、関数が約 2 万個あるため、逆アセンブリを直接調べても手がかりが見つかりません。原因——シンボルがない場合、関数境界、文字列の帰属、呼び出し関係をすべて静的に再構築する必要があり、優先順位を決める手がかりがありません。対策——prologue の特徴バイト(fd 79 bf a9 fd 03 0f aa)を使い、.text 内を逆方向にスキャンします(ウィンドウは約 0x6000–0x8000 バイト)。まず関数境界を復元します。関数ごとに特徴ベクトルを作成します。アクセスする文字列の集合(最も重要な手がかり)、BL のターゲット/呼び出し元の fan-in((word>>26)==0b100101 で imm26 をデコード)、gate 後の分岐点(TBZ/TBNZ/B.EQ)、null 比較(CMP W0,W22/MOV X0,X22)、bool 戻り値のフラグです。4 種類のランキングで 2 万件を短い候補リストに絞ります。キーワード文字列の候補リスト → アクセス数(キーワードに該当する文字列を 2 個以上参照 → orchestrator)→ 2 つのアンカー集合の積集合(タイトル文字列と本文文字列の両方を参照する関数集合の積集合 → 正確な builder、0 個または 1 個)→ BL 呼び出し fan-in を上向きに反復します。caller の数はリスク指標です——数が多い(状態を共有する関数)場合は patch の対象にせず、数が 1 の専用 handler は安全な対象です。cmp と条件分岐の数が多い関数を集計してスコア化すると、整合性ゲート/集約述語を特定できます(出典:reverse-skills(inliver233)、MIT)

コメント