
aotopsy v1.1.0
Analizador estático para instantáneas AOT de Flutter/Dart — recupera nombres de funciones, jerarquías de clases, grafos de llamadas y señales de comportamiento de libapp.so sin incrustar ni ejecutar la VM de Dart. Compatible con ARM64 y x86_64, Dart 2.10–3.12.
AOTopsy
Un analizador de snapshots AOT de Dart. Convierte libapp.so — el código Dart compilado dentro de un APK de Flutter en versión release — en nombres de funciones, diseños de clases, grafos de llamadas, señales de comportamiento y pseudocódigo legible. Sin VM de Dart, sin compilación del SDK, sin respaldo en tiempo de ejecución.
Aviso de fork: AOTopsy es un fork de unflutter, originalmente creado por Anthony Zboralski. El repositorio original
zboralski/unflutterya no está disponible (eliminado por el autor); existe una continuación comunitaria enKristijanZic/unflutter. Todo el crédito por el parser de snapshots original, el deserializador de clústeres, el pipeline de desensamblado ARM64 y la integración con Ghidra/IDA pertenece al autor original. AOTopsy lo extiende con soporte para x86_64, un descompilador nativo, inferencia de tipos a nivel de programa completo, generación de scripts de Frida y documentación exhaustiva.
Qué Recupera
| Salida | Qué es |
|---|---|
| Nombres de funciones | El nombre Dart original de cada función compilada |
| Estructuras de clases | Nombres de campos, desplazamientos de bytes, cadenas de herencia |
| Grafo de llamadas | Aristas de llamadas directas (BL) e indirectas (BLR/dispatch) con procedencia |
| Referencias a cadenas | Qué funciones cargan qué literales de cadena desde el pool de objetos |
| Señales de comportamiento | Clasificación de palabras clave: criptografía, red, juegos de azar, SIM, ubicación, WebView, blockchain |
| Pseudocódigo | Salida descompilada neutral respecto a la arquitectura desde código máquina ARM64 o x86_64 |
| Exportación de código fuente Dart | Archivos .dart modulares de todo el proyecto reconstruidos con clases, campos y métodos |
Soporta ARM64 y x86_64. Cubre Dart 2.10 hasta 3.13 (3.13.2 es la frontera estable actual).
Precisión y Honestidad
AOTopsy se mide contra la verdad de referencia, no se asume. Dos propiedades se verifican mediante el conjunto de pruebas en cada cambio:
| Métrica | Valor | Qué significa |
|---|---|---|
| Concordancia en la recuperación de nombres | 89.8% en general (81.3% en la peor banda, umbral mínimo de 0.81) en 44 compilaciones de verdad de referencia | Nombres de funciones recuperados comparados con el .symtab ELF de cada compilación, la verdad de referencia externa — TestSymtabDifferential. Marcador completo por compilación: BENCHMARK.md (make bench). |
| Validez sintáctica del descompilador | 100% Dart válido | Cada función de pseudocódigo emitida se analiza como Dart — TestDecompileQualityCorpus. |
| Tasa de fabricación | 0% | La regla §2: nunca emitir un nombre, tipo o destino de llamada adivinado como un hecho. Los desconocidos se muestran honestamente (indirectCall, <unknown>, dynamic). |
Las gemelas de verdad de referencia son compilaciones de producción reales que no podemos redistribuir, por lo que esas compuertas diferenciales se ejecutan localmente; el CI público valida la compilación y las pruebas unitarias en la matriz de plataformas (las pruebas dependientes de muestras se omiten limpiamente cuando el binario está ausente). Consulta SECURITY.md para la verificación de binarios de release y el alcance honesto a continuación.
Inicio Rápido
make build
./aotopsy libapp.so # pipeline completo
./aotopsy doctor libapp.so # diagnóstico rápido
./aotopsy export-dart --lib libapp.so --out ./lib # reconstruir todo el proyecto de código fuente Dart
./aotopsy _debug decompile-native --lib libapp.so --find MyClass # encontrar y descompilar una función
Consulta WORKFLOW.md para la metodología paso a paso cuando tienes un APK crudo y no sabes por dónde empezar.
Cómo Funciona
AOTopsy trata el snapshot AOT de Dart como una gramática binaria determinista. Cada byte tiene exactamente una interpretación correcta dadas las restricciones adecuadas (estructura ELF, magic del snapshot, hash de versión, tabla CID, codificación de clústeres). El parser aplica restricciones hasta que solo sobrevive una interpretación — sin heurísticas, sin adivinanzas.
El pipeline se ejecuta en etapas, cada una una función pura de bytes a datos estructurados:
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]
Dos backends independientes comparten la misma mitad frontal (ELF hasta el llenado de clústeres), y luego se dividen por arquitectura: internal/disasm para ARM64, internal/disasm/x86.go para x86_64. El descompilador (internal/decompiler) maneja ambas arquitecturas a través de un IR unificado.
flowchart LR
subgraph "Mitad frontal compartida"
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 "Descompilador (ambas arquitecturas)"
C --> G[decompiler IR]
G --> H[pseudocode]
end
Comparación con Blutter
flowchart LR
subgraph Blutter
direction TB
B1[libapp.so] --> B2[Compilar SDK de Dart<br/>coincidente]
B2 --> B3[Incrustar VM de Dart]
B3 --> B4[Deserializar mediante<br/>APIs internas de la VM]
B4 --> B5[Fidelidad perfecta]
end
subgraph AOTopsy
direction TB
A1[libapp.so] --> A2[Analizar formato binario<br/>directamente]
A2 --> A3[Sin VM, sin SDK]
A3 --> A4[Modelado de formato<br/>específico de versión]
A4 --> A5[Portabilidad + velocidad]
end
Blutter incrusta la VM de Dart para deserializar el snapshot a través de sus propias rutas de código. Fidelidad perfecta, pero requiere compilar un SDK de Dart coincidente para cada versión objetivo — y es solo ARM64, sin soporte estático para x86_64. AOTopsy es el único analizador estático e independiente de versión con un descompilador de pseudocódigo nativo y precisión de verdad de referencia publicada.
AOTopsy analiza el formato binario directamente. Sin VM, sin SDK. La compensación: cada cambio de formato entre versiones de Dart debe modelarse explícitamente. No hay tiempo de ejecución que lo maneje automáticamente.
Comandos
Pipeline completo
aotopsy libapp.so # desensamblado + aristas de llamadas + señales + metadatos (ARM64: + Ghidra/IDA)
aotopsy signal libapp.so # igual, omitir metadatos
aotopsy libapp.so --graph # también construir archivos DOT del grafo de llamadas
Banderas: --out <dir> (predeterminado: <basename>.aotopsy/), --quiet, --strict, --max-steps <n>, --k <n> (profundidad de contexto de señales, predeterminado 2).