
Статический анализатор AOT-снимков Flutter/Dart — восстанавливает имена функций, иерархии классов, графы вызовов и поведенческие сигналы из libapp.so без встраивания или выполнения Dart VM. Поддерживает ARM64 и x86_64, Dart 2.10–3.12.
Анализатор AOT-снимков Dart. Превращает libapp.so — скомпилированный код Dart внутри release-APK Flutter — в имена функций, структуры классов, графы вызовов, поведенческие сигналы и читаемый псевдокод. Без Dart VM, без компиляции SDK, без резервного выполнения во время работы.
Уведомление о форке: AOTopsy — это форк unflutter, изначально созданного Anthony Zboralski. Исходный репозиторий
zboralski/unflutterбольше недоступен (удалён автором); продолжение сообщества существует по адресуKristijanZic/unflutter. Все заслуги за исходный парсер снимков, десериализатор кластеров, конвейер дизассемблирования ARM64 и интеграцию с Ghidra/IDA принадлежат оригинальному автору. AOTopsy расширяет его поддержкой x86_64, собственным декомпилятором, выводом типов для всей программы, генерацией скриптов Frida и подробной документацией.
| Выходные данные | Что это |
|---|---|
| Имена функций | Исходное имя Dart для каждой скомпилированной функции |
| Структуры классов | Имена полей, байтовые смещения, цепочки наследования |
| Граф вызовов | Прямые (BL) и косвенные (BLR/dispatch) рёбра вызовов с указанием происхождения |
| Ссылки на строки | Какие функции загружают какие строковые литералы из пула объектов |
| Поведенческие сигналы | Классификация по ключевым словам: криптография, сеть, азартные игры, SIM, местоположение, WebView, блокчейн |
| Псевдокод | Декомпилированный вывод, независимый от архитектуры, из машинного кода ARM64 или x86_64 |
| Экспорт исходного кода Dart | Модульные файлы .dart для всего проекта, восстановленные с классами, полями и методами |
Поддерживаются ARM64 и x86_64. Охватывает Dart 2.10–3.13 (3.13.2 — текущий стабильный рубеж) — каждая смоделированная версия разбирается без ошибок на обеих архитектурах, см. COVERAGE.md (93 образца сборок, 0 сбоев).
AOTopsy измеряется относительно эталонных данных, а не декларируется. Два свойства проверяются набором тестов при каждом изменении:
| Метрика | Значение | Что это означает |
|---|---|---|
| Согласованность восстановления имён | 90,2% в целом (81,3% худший диапазон, порог ≥ 0,81) по 44 эталонным сборкам | Восстановленные имена функций сравниваются с собственным ELF .symtab каждой сборки — внешним эталоном — TestSymtabDifferential. Полная таблица по сборкам: BENCHMARK.md (make bench). |
| Синтаксическая корректность декомпилятора | 100% корректный Dart | Каждая выданная функция псевдокода разбирается как Dart — TestDecompileQualityCorpus. |
| Доля выдуманных данных | 0% | Правило §2: никогда не выдавать угаданное имя, тип или цель вызова за факт. Неизвестные значения отображаются честно (indirectCall, <unknown>, dynamic). |
Эталонные двойники — это реальные production-сборки, которые мы не можем распространять, поэтому эти
дифференциальные проверки выполняются локально; публичный CI проверяет сборку, vet, gofmt,
staticcheck, модульные тесты (в перемешанном порядке) и минимальный порог покрытия по матрице
платформ. samples/ не подлежит распространению и добавлен в gitignore, поэтому зависящие от образцов
тесты там пропускаются — но только когда корпус полностью отсутствует; корпус, который
присутствует, но не содержит зарегистрированного образца, приводит к сбою, потому что это означает противоположные
вещи. Как измеряется каждое число здесь (эталонные данные, определения метрик, воспроизведение): METHODOLOGY.md. См.
SECURITY.md для проверки release-бинарников и честного описания области применения ниже.
make build
./aotopsy libapp.so # full pipeline
./aotopsy doctor libapp.so # quick diagnostic
./aotopsy export-dart --lib libapp.so --out ./lib # reconstruct entire Dart source project
./aotopsy _debug decompile-native --lib libapp.so --find MyClass # find and decompile a function
См. DEMO.md — пятиминутное пошаговое руководство на реальном снимке, или WORKFLOW.md — пошаговую методологию на случай, когда у вас есть необработанный APK и вы не знаете, с чего начать.
AOTopsy рассматривает снимок Dart AOT как детерминированную бинарную грамматику. Каждый байт имеет ровно одну правильную интерпретацию при наличии правильных ограничений (структура ELF, магия снимка, хеш версии, таблица CID, кодировка кластера). Парсер применяет ограничения до тех пор, пока не останется только одна интерпретация — никаких эвристик, никаких догадок.
Конвейер выполняется поэтапно, каждый этап — чистая функция от байтов к структурированным данным:
flowchart TD
A[libapp.so] --> B[ELF parse]
B --> C[snapshot region extraction]
C --> D[version detection]
D --> E[cluster alloc<br/>object census]
E --> F[cluster fill<br/>field values, names, strings]
F --> G[instructions table<br/>code ranges, stub boundaries]
G --> H{architecture?}
H -->|ARM64| I[ARM64 disassembly]
H -->|x86_64| J[x86_64 disassembly]
I --> K[CFG + call edges<br/>register provenance]
J --> K
K --> L[type inference<br/>BLR receiver type resolution]
L --> M[signal classification<br/>behavioral keyword matching]
M --> N[JSONL + HTML + DOT<br/>pseudocode output]
Два независимых бэкенда используют одну и ту же первую половину (ELF до заполнения кластера), затем разделяются по архитектуре: internal/disasm для ARM64, internal/disasm/x86.go для x86_64. Декомпилятор (internal/decompiler) обрабатывает обе архитектуры через единый IR.
flowchart LR
subgraph "Shared front half"
A[elfx] --> B[snapshot]
B --> C[cluster]
end
subgraph "ARM64 backend"
C --> D1[disasm ARM64]
D1 --> E1[callgraph]
E1 --> F1[signal]
end
subgraph "x86_64 backend"
C --> D2[disasm x86_64]
D2 --> E2[callgraph]
E2 --> F2[signal]
end
subgraph "Decompiler (both archs)"
C --> G[decompiler IR]
G --> H[pseudocode]
end
flowchart LR
subgraph Blutter
direction TB
B1[libapp.so] --> B2[Compile matching<br/>Dart SDK]
B2 --> B3[Embed Dart VM]
B3 --> B4[Deserialize via<br/>VM internal APIs]
B4 --> B5[Perfect fidelity]
end
subgraph AOTopsy
direction TB
A1[libapp.so] --> A2[Parse binary format<br/>directly]
A2 --> A3[No VM, no SDK]
A3 --> A4[Version-specific<br/>format modeling]
A4 --> A5[Portability + speed]
end
Blutter встраивает Dart VM для десериализации снимка через собственные пути кода. Идеальная точность, но требует компиляции соответствующего Dart SDK для каждой целевой версии — и поддерживает только ARM64, без статической поддержки x86_64. AOTopsy — единственный статический, независимый от версии анализатор с нативным декомпилятором псевдокода и опубликованной точностью относительно эталонных данных.
AOTopsy разбирает бинарный формат напрямую. Без VM, без SDK. Компромисс: каждое изменение формата между версиями Dart должно моделироваться явно. Нет среды выполнения, которая обработала бы это автоматически.