Torna agli aggiornamenti
New releaseAug 31, 2026

aotopsy v1.1.0

Analizzatore statico per snapshot AOT Flutter/Dart — recupera i nomi delle funzioni, le gerarchie delle classi, i grafi di chiamata e i segnali comportamentali da libapp.so senza incorporare o eseguire la Dart VM. Supporta ARM64 e x86_64, Dart 2.10–3.12.

Condividi

AOTopsy

CI Release License

Un analizzatore di snapshot AOT per Dart. Trasforma libapp.so — il codice Dart compilato all'interno di un APK Flutter in versione release — in nomi di funzioni, layout di classi, grafi di chiamata, segnali comportamentali e pseudocodice leggibile. Nessuna Dart VM, nessuna compilazione SDK, nessun fallback a runtime.

Nota sul fork: AOTopsy è un fork di unflutter, originariamente creato da Anthony Zboralski. Il repository originale zboralski/unflutter non è più disponibile (rimosso dall'autore); esiste una continuazione della community su KristijanZic/unflutter. Tutto il merito per il parser di snapshot originale, il deserializzatore di cluster, la pipeline di disassemblaggio ARM64 e l'integrazione con Ghidra/IDA appartiene all'autore originale. AOTopsy lo estende con supporto x86_64, un decompilatore nativo, inferenza di tipi a livello di intero programma, generazione di script Frida e documentazione completa.

Cosa Recupera

OutputCosa è
Nomi di funzioniIl nome Dart originale per ogni funzione compilata
Strutture di classiNomi dei campi, offset in byte, catene di ereditarietà
Grafo di chiamataArchi di chiamata diretti (BL) e indiretti (BLR/dispatch) con provenienza
Riferimenti a stringheQuali funzioni caricano quali letterali stringa dal pool di oggetti
Segnali comportamentaliClassificazione per parole chiave: crittografia, rete, gioco d'azzardo, SIM, posizione, WebView, blockchain
PseudocodiceOutput decompilato neutro rispetto all'architettura da codice macchina ARM64 o x86_64
Esportazione sorgente DartFile .dart modulari a livello di intero progetto ricostruiti con classi, campi e metodi

Supporta ARM64 e x86_64. Copre Dart 2.10 fino a 3.13 (3.13.2 è l'attuale frontiera stabile).

Accuratezza e Onestà

AOTopsy è misurato rispetto alla verità di base, non semplicemente dichiarato. Due proprietà sono imposte dalla suite di test a ogni modifica:

MetricaValoreCosa significa
Accordo sul recupero dei nomi89,8% complessivo (81,3% nella banda peggiore, soglia minima ≥ 0,81) su 44 build di verità di baseNomi di funzioni recuperati confrontati con il .symtab ELF di ogni build, la verità di base esterna — TestSymtabDifferential. Classifica completa per build: BENCHMARK.md (make bench).
Validità sintattica del decompilatore100% Dart validoOgni funzione in pseudocodice emessa viene parsata come Dart — TestDecompileQualityCorpus.
Tasso di fabbricazione0%La regola §2: non emettere mai un nome, tipo o target di chiamata indovinato come fatto. Gli sconosciuti vengono resi onestamente (indirectCall, <unknown>, dynamic).

I gemelli di verità di base sono build di produzione reali che non possiamo ridistribuire, quindi quelle porte differenziali vengono eseguite localmente; la CI pubblica valida build + test unitari sulla matrice di piattaforme (i test dipendenti da campioni vengono saltati correttamente quando il binario è assente). Vedi SECURITY.md per la verifica dei binari di release e l'ambito onesto di seguito.

Avvio Rapido

make build
./aotopsy libapp.so                    # pipeline completa
./aotopsy doctor libapp.so             # diagnostica rapida
./aotopsy export-dart --lib libapp.so --out ./lib  # ricostruisce l'intero progetto sorgente Dart
./aotopsy _debug decompile-native --lib libapp.so --find MyClass  # trova e decompila una funzione

Vedi WORKFLOW.md per la metodologia passo-passo quando hai un APK grezzo e non sai da dove iniziare.

Come Funziona

AOTopsy tratta lo snapshot AOT di Dart come una grammatica binaria deterministica. Ogni byte ha esattamente un'interpretazione corretta dati i vincoli giusti (struttura ELF, magic dello snapshot, hash di versione, tabella CID, codifica dei cluster). Il parser applica i vincoli finché sopravvive una sola interpretazione — nessuna euristica, nessuna supposizione.

La pipeline procede per fasi, ciascuna una funzione pura da byte a dati strutturati:

flowchart TD
    A[libapp.so] --> B[Parsing ELF]
    B --> C[Estrazione regione snapshot]
    C --> D[Rilevamento versione]
    D --> E[Allocazione cluster<br/>censimento oggetti]
    E --> F[Riempimento cluster<br/>valori campi, nomi, stringhe]
    F --> G[Tabella istruzioni<br/>intervalli codice, confini stub]
    G --> H{Architettura?}
    H -->|ARM64| I[Disassemblaggio ARM64]
    H -->|x86_64| J[Disassemblaggio x86_64]
    I --> K[CFG + archi di chiamata<br/>provenienza registri]
    J --> K
    K --> L[Inferenza tipi<br/>risoluzione tipo ricevente BLR]
    L --> M[Classificazione segnali<br/>corrispondenza parole chiave comportamentali]
    M --> N[Output JSONL + HTML + DOT<br/>pseudocodice]

Due backend indipendenti condividono la stessa metà anteriore (ELF fino al riempimento dei cluster), poi si dividono per architettura: internal/disasm per ARM64, internal/disasm/x86.go per x86_64. Il decompilatore (internal/decompiler) gestisce entrambe le architetture tramite un IR unificato.

flowchart LR
    subgraph "Metà anteriore condivisa"
        A[elfx] --> B[snapshot]
        B --> C[cluster]
    end
    subgraph "Backend ARM64"
        C --> D1[disasm ARM64]
        D1 --> E1[callgraph]
        E1 --> F1[signal]
    end
    subgraph "Backend x86_64"
        C --> D2[disasm x86_64]
        D2 --> E2[callgraph]
        E2 --> F2[signal]
    end
    subgraph "Decompilatore (entrambe le architetture)"
        C --> G[IR decompilatore]
        G --> H[pseudocodice]
    end

Confronto con Blutter

flowchart LR
    subgraph Blutter
        direction TB
        B1[libapp.so] --> B2[Compila SDK Dart<br/>corrispondente]
        B2 --> B3[Integra Dart VM]
        B3 --> B4[Deserializza tramite<br/>API interne VM]
        B4 --> B5[Fedeltà perfetta]
    end
    subgraph AOTopsy
        direction TB
        A1[libapp.so] --> A2[Parsing formato binario<br/>direttamente]
        A2 --> A3[Nessuna VM, nessun SDK]
        A3 --> A4[Modellazione formato<br/>specifico per versione]
        A4 --> A5[Portabilità + velocità]
    end

Blutter integra la Dart VM per deserializzare lo snapshot attraverso i propri percorsi di codice. Fedeltà perfetta, ma richiede la compilazione di un SDK Dart corrispondente per ogni versione target — ed è solo ARM64, senza supporto statico x86_64. AOTopsy è l'unico analizzatore statico e indipendente dalla versione con un decompilatore nativo in pseudocodice e accuratezza pubblicata rispetto alla verità di base.

AOTopsy analizza direttamente il formato binario. Nessuna VM, nessun SDK. Il compromesso: ogni modifica al formato tra le versioni di Dart deve essere modellata esplicitamente. Non c'è un runtime che la gestisca automaticamente.

Comandi

Pipeline completa

aotopsy libapp.so              # disasm + archi di chiamata + segnali + metadati (ARM64: + Ghidra/IDA)
aotopsy signal libapp.so       # come sopra, salta i metadati
aotopsy libapp.so --graph      # crea anche file DOT del grafo di chiamata

Flag: --out <dir> (default: <basename>.aotopsy/), --quiet, --strict, --max-steps <n>, --k <n> (profondità contesto segnali, default 2).

Diagnostica

aotopsy doctor libapp.so       # versione Dart, dimensione puntatore, stato supporto, funzionalità build
aotopsy find-libapp --apk app.apk  # individua libapp.so all'interno di un APK

Esportazione Sorgente Dart a Livello di Intero Progetto

Ricostruisce tutte le classi, i campi, i metodi, i getter, i setter e i costruttori in file .dart modulari mappati dagli URI delle librerie originali:

aotopsy export-dart --lib libapp.so --out ./reconstructed_lib/   # esportazione progetto completo
aotopsy export-dart --lib libapp.so --out ./lib/ --app-only      # filtra il framework core/flutter
aotopsy export-dart --lib libapp.so --out ./lib/ --filter Auth   # esportazione mirata per nome

Decompilatore Nativo ad Alto Livello (ARM64 + x86_64)

Produce codice Dart pulito e idiomatico direttamente dalle istruzioni macchina binarie:

  • Flusso di controllo strutturale: iteratori for-in, while, for, try-catch-finally con delimitazione PC esatta.
  • Linearizzazione Async/Await: Disfa le macchine a stati _SuspendState in await future e await for lineari.
  • Inlining di Lambda: Sintetizza callback a freccia (item) => process(item) direttamente nei siti di chiamata.
  • Reticolo di tipi: Propaga tipi Dart concreti (String, int, UserModel) attraverso i valori SSA senza eseguire una VM live.
  • Idiomi Dart: Null-aware (?., ??, ??=), cascade (..), letterali Set/List/Map, interpolazione di stringhe ("${a}${b}").
aotopsy _debug decompile-native --lib libapp.so --find MyClass         # individua per nome
aotopsy _debug decompile-native --lib libapp.so --func 0x1a92728       # una funzione a un VA
aotopsy _debug decompile-native --lib libapp.so --from-main --out out/ # raggiungibilità dall'entry dell'app
aotopsy _debug decompile-native --lib libapp.so --all --filter MyClass # bulk, filtrato

Attenzione: --all senza un --max piccolo può richiedere ~64GB di RAM su un'app reale. Preferisci --find/--func/--from-main.

Ghidra / IDA (solo ARM64)

aotopsy ghidra libapp.so        # Ghidra headless con iniezione di metadati
aotopsy ida libapp.so           # IDA headless tramite idalib

Entrambi rifiutano input x86_64. Usa decompile-native per lo pseudocodice x86_64.

Generazione script Frida

aotopsy _debug decompile-native --lib libapp.so --func 0x1a92728 --gen-frida --gen-frida-out hooks.js
frida -U -f com.example.app -l hooks.js --no-pause

Vedi FRIDA.md per la guida completa.

Riferimenti incrociati e tracciamento

aotopsy _debug strings --lib libapp.so --find "X-Signature" --xref   # quale funzione carica questa stringa?
aotopsy _debug ffi-trace --lib libapp.so --filter MyClass            # siti di chiamata dart:ffi
aotopsy _debug dispatch-table --lib libapp.so --filter MyClass       # voci della tabella di dispatch
aotopsy _debug fingerprint --lib libapp.so                           # build-id e marcatori di versione
aotopsy _debug funcdiff --old old.so --new new.so                    # diff del set di funzioni
aotopsy _debug symbolmap --stripped lib.so --unstripped debug.so     # risolve i target stripped

Strumenti per corpus

aotopsy inventory --dir samples/                    # catalogo APK
aotopsy parity --samples samples/ --out out/        # report di parsing cross-versione
aotopsy _debug thr-audit -lib libapp.so -out thr.jsonl  # scansione accessi THR

Artefatti di Output

FileContenuto
functions.jsonlNome, indirizzo, dimensione, proprietario, numero di parametri per funzione
call_edges.jsonlArchi BL/BLR con target risolti e provenienza
classes.jsonlNomi dei campi, offset, dimensioni delle istanze per classe
string_refs.jsonlRiferimenti a stringhe dai caricamenti del pool di oggetti
signal.htmlReport dei segnali comportamentali con grafo di contesto
flutter_meta.jsonMetadati unificati per Ghidra/IDA (solo ARM64)
asm/*.txtDisassemblaggio annotato per funzione
cfg/*.dotCFG per funzione (con --graph)

Struttura dei Pacchetti

cmd/aotopsy/          Punto di ingresso CLI e gestori dei comandi
internal/
  elfx/               Validazione ELF ed estrazione simboli
  snapshot/           Estrazione regione snapshot, profili di versione
  dartfmt/            Codifica interi a lunghezza variabile della Dart VM
  cluster/            Deserializzazione snapshot in due fasi (alloc + fill)
  disasm/             Decodifica ARM64 + x86_64, CFG, provenienza archi di chiamata
  callgraph/          Costruttori di grafi a reticolo per rendering DOT
  signal/             Classificazione comportamentale delle stringhe
  render/             Visualizzazione HTML/DOT/SVG
  output/             Serializzazione JSONL
  decompiler/         Decompilatore pseudocodice Dart-AOT (entrambe le architetture)
  typetrack/          Inferenza di tipi a livello di intero programma per risoluzione BLR
  fingerprint/        Identificazione build-id e marcatori di versione
  funcdiff/           Diff del set di funzioni tra build
  symbolmap/          Risoluzione simboli stripped-vs-unstripped
  ffitrace/           Tracciamento statico dei siti di chiamata dart:ffi
  strxref/            Riferimenti incrociati stringa-funzione
  strutil/            Utility condivise per stringhe
  pipeline/           Orchestrazione della pipeline e risoluzione dei nomi
tools/                Utility standalone (estrattore tabella THR)
ghidra_scripts/       Integrazione Ghidra (Python)
ida_scripts/          Integrazione IDA (Python)

Vedi ARCHITECTURE.md per l'analisi approfondita di ogni pacchetto.

Build

Richiede Go 1.25+.

make build      # compila ./aotopsy
make install    # installa in ~/.aotopsy/bin
make test       # esegue i test

I test di integrazione usano variabili d'ambiente (AOTOPSY_TEST_SAMPLE_*) per individuare i binari di esempio — vengono saltati automaticamente se non impostate.

Release e Branch

AOTopsy usa un modello a due branch:

BranchRuolo
mainStabile. Ogni commit è un candidato alla release verificato dai gate e unito con squash. Le release taggate (con binari precompilati cross-platform) vengono tagliate da qui.
developRolling / nightly. Dove arrivano prima i piccoli commit quotidiani, le funzionalità e la ricerca. Può essere instabile tra i merge. Viene raggruppato in main tramite una PR con squash-merge una volta che i gate sono verdi.

Contribuisci su develop; apri una PR verso main solo quando un lotto di lavoro è verificato dai gate.

Binari precompilati per Linux, macOS e Windows (amd64/arm64) sono allegati a ogni release GitHub. AOTopsy è Go puro, quindi make build compila in modo incrociato senza problemi per qualsiasi target.

Limitazioni e Ambito

AOTopsy dichiara chiaramente cosa recupera e cosa non recupera. Alcuni limiti sono questione di ambito ingegneristico; altri sono limiti strutturali del formato AOT — informazioni che il compilatore Dart rimuove nelle build di release (PRODUCT), verificate contro il sorgente SDK. Documentiamo i limiti strutturali piuttosto che fabbricare sopra di essi.

Limiti strutturali AOT (verificati — non aspettarti che migliorino):

  • I nomi dei campi di istanza sono assenti per ~97–99%. Precompiler::DropFields mantiene i nomi dei campi solo sotto #if !defined(PRODUCT); un'app reale ha ~233 oggetti Field nominati contro ~16k sintetici. Il recupero basato sugli accessor (get:/set: mantengono ancora il nome) penetra parzialmente questo limite (−11–22%, deterministico, mai indovinato); il resto è genuinamente perso.
  • I nomi delle variabili locali e catturate sono persi. Resi come local_* / tN.
  • I target di dispatch realmente polimorfici non sono risolvibili staticamente. BLR attraverso una tabella di dispatch / oggetto Closure dinamico è un limite AOT, non una lacuna di analisi; questi vengono resi onestamente come indirectCall / dynamicCall. Vedi docs/roadmap/20-invariants-and-non-defects.md.

Ambito ingegneristico:

  • Solo AOT. Nessun supporto JIT.
  • La decompilazione Ghidra/IDA è solo ARM64. x86_64 viene rifiutato con un errore chiaro — usa decompile-native invece (l'unico decompilatore statico x86_64 per Flutter).
  • La decompilazione --all può mandare in crash l'host. Un'app reale a grandezza naturale richiede ~64GB di RAM per --all senza limiti. Usa le modalità mirate (--find, --func, --from-main) o limita con --max.
  • Il dispatch virtuale è invisibile alla raggiungibilità di --from-main. I callback del ciclo di vita dei widget passano attraverso il dispatch del framework Flutter, non istruzioni di chiamata dirette. Un'euristica di contatto con le classi ne recupera alcuni, ma è una sovra-approssimazione. Usa Frida per il resto.
  • Ogni modifica alla versione di Dart deve essere modellata. Non c'è una VM per gestire automaticamente i cambi di formato. Lo snapshot porta un hash derivato da git, non un numero di versione, quindi il supporto è basato sulla struttura. Attualmente modellato: Dart 2.10 → 3.13.
  • I gemelli di verità di base non sono ridistribuibili (build di produzione reali), quindi il differenziale sul recupero dei nomi viene eseguito localmente, non nella CI pubblica.

Licenza

BSD-3-Clause. Le tabelle CID, gli offset dei campi THR e i nomi degli stub derivano dal Dart SDK (anch'esso BSD-3-Clause). Vedi LICENSE e NOTICE.

Categorie