
Analyseur statique pour les instantanés AOT Flutter/Dart — récupère les noms de fonctions, les hiérarchies de classes, les graphes d'appel et les signaux comportementaux de libapp.so sans embarquer ni exécuter la machine virtuelle Dart. Prend en charge ARM64 et x86_64, Dart 2.10–3.12.
Un analyseur de snapshots AOT Dart. Transforme libapp.so — le code Dart compilé à l'intérieur d'un APK Flutter release — en noms de fonctions, dispositions de classes, graphes d'appels, signaux comportementaux et pseudocode lisible. Pas de Dart VM, pas de compilation SDK, pas de repli à l'exécution.
Avis de fork : AOTopsy est un fork de unflutter, créé à l'origine par Anthony Zboralski. Le dépôt original
zboralski/unfluttern'est plus disponible (supprimé par l'auteur) ; une continuation communautaire existe àKristijanZic/unflutter. Tout le crédit pour le parseur de snapshot original, le désérialiseur de clusters, le pipeline de désassemblage ARM64 et l'intégration Ghidra/IDA revient à l'auteur original. AOTopsy l'étend avec le support x86_64, un décompilateur natif, l'inférence de types à l'échelle du programme, la génération de scripts Frida et une documentation complète.
| Sortie | Ce que c'est |
|---|---|
| Noms de fonctions | Le nom Dart original de chaque fonction compilée |
| Structures de classes | Noms de champs, décalages d'octets, chaînes d'héritage |
| Graphe d'appels | Arêtes d'appel directes (BL) et indirectes (BLR/dispatch) avec provenance |
| Références de chaînes | Quelles fonctions chargent quels littéraux de chaîne depuis le pool d'objets |
| Signaux comportementaux | Classification par mots-clés : crypto, réseau, jeux d'argent, SIM, localisation, WebView, blockchain |
| Pseudocode | Sortie décompilée indépendante de l'architecture à partir de code machine ARM64 ou x86_64 |
| Export de source Dart | Fichiers .dart modulaires de tout le projet reconstruits avec classes, champs et méthodes |
Supporte ARM64 et x86_64. Couvre Dart 2.10 à 3.13 (3.13.2 est la frontière stable actuelle) — chaque version modélisée s'analyse proprement sur les deux architectures, voir COVERAGE.md (93 builds d'échantillons, 0 échec).
AOTopsy est mesuré par rapport à une vérité terrain, non affirmé. Deux propriétés sont imposées par la suite de tests à chaque changement :
| Métrique | Valeur | Ce que cela signifie |
|---|---|---|
| Concordance de récupération des noms | 90,2 % global (81,3 % pire bande, plancher de seuil ≥ 0,81) sur 44 builds de vérité terrain | Noms de fonctions récupérés comparés au .symtab ELF propre à chaque build, la vérité terrain externe — TestSymtabDifferential. Tableau de bord complet par build : BENCHMARK.md (make bench). |
| Validité syntaxique du décompilateur | 100 % Dart valide | Chaque fonction de pseudocode émise s'analyse comme du Dart — TestDecompileQualityCorpus. |
| Taux de fabrication | 0 % | La règle §2 : ne jamais émettre un nom, type ou cible d'appel deviné comme un fait. Les inconnues sont rendues honnêtement (indirectCall, <unknown>, dynamic). |
Les jumeaux de vérité terrain sont de vrais builds de production que nous ne pouvons pas redistribuer, donc ces
seuils différentiels s'exécutent localement ; la CI publique valide le build, vet, gofmt,
staticcheck, les tests unitaires (mélangés) et un plancher de couverture sur la matrice
de plateformes. samples/ n'est pas redistribuable et est ignoré par git, donc les tests dépendant des échantillons
sont ignorés là — mais uniquement lorsque le corpus est entièrement absent ; un corpus
présent mais auquel il manque un échantillon enregistré échoue, car ces situations signifient des choses opposées.
Comment chaque chiffre ici est mesuré (vérité terrain, définitions des métriques, reproduction) : METHODOLOGY.md. Voir
SECURITY.md pour la vérification des binaires de release et la portée honnête ci-dessous.
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
Consultez DEMO.md pour une démonstration de cinq minutes sur un instantané réel, ou WORKFLOW.md pour la méthodologie étape par étape lorsque vous disposez d'un APK brut et ne savez pas par où commencer.
AOTopsy traite l'instantané AOT Dart comme une grammaire binaire déterministe. Chaque octet a exactement une interprétation correcte étant donné les bonnes contraintes (structure ELF, magie de l'instantané, hachage de version, table CID, encodage de cluster). Le parseur applique des contraintes jusqu'à ce qu'une seule interprétation subsiste — aucune heuristique, aucune supposition.
Le pipeline s'exécute par étapes, chacune étant une fonction pure des octets vers des données structurées :
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]
Deux backends indépendants partagent la même moitié avant (ELF jusqu'au remplissage des clusters), puis se séparent selon l'architecture : internal/disasm pour ARM64, internal/disasm/x86.go pour x86_64. Le décompilateur (internal/decompiler) gère les deux architectures via une IR unifiée.
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 intègre la VM Dart pour désérialiser le snapshot via ses propres chemins de code. Une fidélité parfaite, mais nécessite de compiler un SDK Dart correspondant pour chaque version cible — et il est limité à ARM64, sans prise en charge statique de x86_64. AOTopsy est le seul analyseur statique et indépendant de la version doté d'un décompilateur de pseudocode natif et d'une précision de référence publiée.