
Analisador estático para snapshots AOT de Flutter/Dart — recupera nomes de funções, hierarquias de classes, grafos de chamadas e sinais comportamentais de libapp.so sem embutir ou executar a Dart VM. Suporta ARM64 e x86_64, Dart 2.10–3.12.
Um analisador de snapshots AOT do Dart. Transforma o libapp.so — o código Dart compilado dentro de um APK de release do Flutter — em nomes de funções, layouts de classes, grafos de chamadas, sinais comportamentais e pseudocódigo legível. Sem Dart VM, sem compilação do SDK, sem fallback em tempo de execução.
Aviso de fork: O AOTopsy é um fork do unflutter, originalmente criado por Anthony Zboralski. O repositório original
zboralski/unflutternão está mais disponível (removido pelo autor); existe uma continuação da comunidade emKristijanZic/unflutter. Todo o crédito pelo parser de snapshot original, desserializador de clusters, pipeline de desmontagem ARM64 e integração com Ghidra/IDA pertence ao autor original. O AOTopsy o estende com suporte a x86_64, um descompilador nativo, inferência de tipos de programa inteiro, geração de scripts Frida e documentação abrangente.
| Saída | O que é |
|---|---|
| Nomes de funções | O nome Dart original de cada função compilada |
| Estruturas de classes | Nomes de campos, deslocamentos em bytes, cadeias de herança |
| Grafo de chamadas | Arestas de chamada diretas (BL) e indiretas (BLR/dispatch) com proveniência |
| Referências de strings | Quais funções carregam quais literais de string do pool de objetos |
| Sinais comportamentais | Classificação por palavras-chave de criptografia, rede, apostas, SIM, localização, WebView, blockchain |
| Pseudocódigo | Saída descompilada neutra em relação à arquitetura a partir de código de máquina ARM64 ou x86_64 |
| Exportação de Código-Fonte Dart | Arquivos .dart modulares de todo o projeto reconstruídos com classes, campos e métodos |
Suporta ARM64 e x86_64. Cobre Dart 2.10 até 3.13 (3.13.2 é a fronteira estável atual) — toda versão modelada é analisada sem erros em ambas as arquiteturas, veja COVERAGE.md (93 builds de amostra, 0 falhas).
O AOTopsy é medido contra a verdade de referência, não apenas afirmado. Duas propriedades são garantidas pela suíte de testes a cada alteração:
| Métrica | Valor | O que significa |
|---|---|---|
| Concordância de recuperação de nomes | 90,2% no geral (81,3% na pior faixa, piso de aprovação ≥ 0,81) em 44 builds de referência | Nomes de funções recuperados comparados com o próprio .symtab ELF de cada build, a verdade de referência externa — TestSymtabDifferential. Placar completo por build: BENCHMARK.md (make bench). |
| Validade sintática do descompilador | 100% Dart válido | Toda função de pseudocódigo emitida é analisada como Dart — TestDecompileQualityCorpus. |
| Taxa de fabricação | 0% | A regra §2: nunca emitir um nome, tipo ou alvo de chamada adivinhado como fato. Desconhecidos são renderizados honestamente (indirectCall, <unknown>, dynamic). |
Os gêmeos de referência são builds de produção reais que não podemos redistribuir, portanto esses
portões diferenciais rodam localmente; a CI pública valida build, vet, gofmt,
staticcheck, testes unitários (embaralhados) e um piso de cobertura em toda a matriz
de plataformas. samples/ não é redistribuível e está no gitignore, então testes dependentes de amostras
são ignorados ali — mas apenas quando o corpus está totalmente ausente; um corpus que está
presente e sem uma amostra registrada falha, porque esses casos significam coisas opostas.
Como cada número aqui é medido (verdade de referência, definições de métricas, reprodução): METHODOLOGY.md. Veja
SECURITY.md para verificação de binários de release e o escopo honesto abaixo.
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
Consulte DEMO.md para um passo a passo de cinco minutos em um snapshot real, ou WORKFLOW.md para a metodologia passo a passo quando você tem um APK bruto e não sabe por onde começar.
O AOTopsy trata o snapshot AOT do Dart como uma gramática binária determinística. Cada byte tem exatamente uma interpretação correta dadas as restrições adequadas (estrutura ELF, magic do snapshot, hash de versão, tabela CID, codificação de cluster). O parser aplica restrições até que apenas uma interpretação sobreviva — sem heurísticas, sem adivinhação.
O pipeline é executado em estágios, cada um uma função pura de bytes para dados estruturados:
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]
Dois backends independentes compartilham a mesma metade frontal (ELF até o preenchimento do cluster), depois se dividem por arquitetura: internal/disasm para ARM64, internal/disasm/x86.go para x86_64. O descompilador (internal/decompiler) lida com ambas as arquiteturas por meio de uma IR unificada.
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 incorpora a Dart VM para desserializar o snapshot através de seus próprios caminhos de código. Fidelidade perfeita, mas requer a compilação de um Dart SDK correspondente para cada versão de destino — e é apenas para ARM64, sem suporte estático para x86_64. O AOTopsy é o único analisador estático e independente de versão com um descompilador de pseudocódigo nativo e precisão de referência publicada.
O AOTopsy analisa o formato binário diretamente. Sem VM, sem SDK. A contrapartida: cada mudança de formato entre as versões do Dart deve ser modelada explicitamente. Não há runtime para lidar com isso automaticamente.