Leer Ensamblador en Ghidra: Del Disassembly al Decompiler
Guia practica para leer y analizar ensamblador en Ghidra. Navegacion por las vistas de desensamblado, uso del decompilador, cross-references, renombrado de funciones y variables, y flujo de trabajo para analisis de malware.
Ghidra como laboratorio de ensamblador
Ghidra es la herramienta gratuita de la NSA para ingenieria inversa. Su combinacion de desensamblador y decompilador la convierte en el punto de entrada ideal para analizar malware sin coste de licencia.
Este articulo no es un tutorial de instalacion. Asume que tienes Ghidra funcionando y que has importado un binario. El objetivo es ensenar a leer ensamblador dentro de Ghidra de forma eficiente, usando las vistas, atajos y funcionalidades que aceleran el analisis.
La interfaz: vistas que importan
Al abrir un binario analizado, Ghidra presenta varias vistas. Las cuatro esenciales para analisis de malware:
Listing (desensamblado)
La vista central. Muestra las instrucciones en ensamblador con direcciones, bytes crudos y etiquetas. Aqui es donde lees el codigo maquina real.
Cada linea sigue este formato:
DIRECCION BYTES INSTRUCCION OPERANDOS
00401000 55 PUSH EBP
00401001 89 e5 MOV EBP,ESP
00401003 83 ec 20 SUB ESP,0x20
Los colores en Ghidra tienen significado: las referencias a funciones conocidas se colorean diferente de las direcciones de datos, los strings son de otro color, y las instrucciones de salto muestran flechas de flujo en el margen izquierdo.
Decompiler
La vista que reconstruye pseudo-C a partir del ensamblador. Sincronizada con el Listing: al hacer click en una linea del decompilador, el Listing salta a la instruccion correspondiente y viceversa.
// Ejemplo de salida del decompilador
void FUN_00401000(void)
{
HANDLE hProcess;
void *pvVar1;
hProcess = OpenProcess(0x1f0fff, 0, DAT_00403000);
if (hProcess != (HANDLE)0x0) {
pvVar1 = VirtualAllocEx(hProcess, (LPVOID)0x0, 0x1000, 0x3000, 0x40);
WriteProcessMemory(hProcess, pvVar1, &DAT_00403010, 0x200, (SIZE_T *)0x0);
CreateRemoteThread(hProcess, (LPSECURITY_ATTRIBUTES)0x0, 0, pvVar1, 0, 0, 0);
}
return;
}
Este fragmento muestra inyeccion de proceso clasica. El decompilador revela la logica en segundos. Leer lo mismo en ensamblador puro llevaria minutos.
Symbol Tree
Panel izquierdo que organiza el binario en categorias: Functions, Labels, Classes, Namespaces. Desde aqui navegas a cualquier funcion con doble click.
Los imports del PE aparecen bajo "Imports" y estan organizados por DLL. Esto te da una vision rapida de las capacidades del malware: si ves ws2_32.dll sabes que hay funcionalidad de red; si ves advapi32.dll con RegSetValueEx, hay persistencia en registro.
Data Type Manager
Gestiona los tipos de datos que Ghidra conoce. Cuando el decompilador muestra un argumento como DWORD, puedes cambiar el tipo a HANDLE o LPVOID para mejorar la legibilidad. Ghidra incluye archivos de tipos para Windows API (windows_vs12_32.gdt, windows_vs12_64.gdt) que resuelven automaticamente miles de estructuras.
Navegacion eficiente
Atajos criticos
| Atajo | Accion |
|---|---|
| G | Go To Address (saltar a direccion) |
| L | Rename (renombrar funcion o variable) |
| T | Change Type (cambiar tipo de dato) |
| ; | Set Comment (anadir comentario) |
| Ctrl+Shift+F | Find References To (xrefs) |
| Ctrl+E | Export selection |
| D | Disassemble (forzar desensamblado) |
| C | Clear code (limpiar desensamblado erroneo) |
| P | Make function (crear funcion en la direccion actual) |
| Space | Toggle Listing/Graph view |
| Alt+Left | Back (navegacion historica) |
| Alt+Right | Forward |
Grafo de flujo
Pulsa Space en el Listing para cambiar a la vista de grafo. Cada bloque basico se convierte en un nodo con flechas que muestran el flujo de control. Los saltos condicionales generan dos flechas (verde = tomado, rojo = no tomado).
Esta vista es invaluable para entender bucles y condicionales complejos. El malware ofuscado genera grafos caoticos con muchos nodos pequenos (indicador de opaque predicates o control flow flattening).
Cross-references: la herramienta central
Las cross-references (xrefs) responden a la pregunta fundamental: "quien usa esto?".
Tipos de xrefs
Xrefs TO (quien me llama): selecciona una funcion y pulsa Ctrl+Shift+F. Veras todas las ubicaciones que llaman a esa funcion.
Xrefs FROM (a quien llamo): en el grafo de llamadas (Window, Function Call Graph), ves que funciones invoca la funcion seleccionada.
Data xrefs: cuando seleccionas un string o dato global, las xrefs muestran que funciones leen o escriben ese dato.
Ejemplo practico: trazar inyeccion de proceso
Supongamos que detectas una llamada a CreateRemoteThread en los imports.
- En Symbol Tree, navega a Imports, kernel32.dll, CreateRemoteThread.
- Ctrl+Shift+F: ves que FUN_00401000 la llama.
- Navegas a FUN_00401000 y ves el decompilador.
- Identificas que tambien llama a VirtualAllocEx y WriteProcessMemory.
- Ctrl+Shift+F sobre FUN_00401000: ves que es llamada desde FUN_004010A0.
- Navegas a FUN_004010A0 y descubres que recibe un PID como parametro.
- Sigues trazando hacia atras hasta encontrar como obtiene el PID objetivo.
Este flujo de "trazado hacia atras" es el patron de analisis mas comun. Las xrefs son el hilo que sigues.
Renombrado: hacer el codigo legible
El decompilador genera nombres genericos: FUN_00401000, local_20, param_1, DAT_00403000. Tu trabajo como analista es renombrar todo lo que identifiques.
Funciones
Cuando determines que FUN_00401000 realiza inyeccion de proceso, selecciona el nombre y pulsa L:
FUN_00401000 → inject_shellcode_into_process
Ghidra propaga el nuevo nombre automaticamente a todas las xrefs, al decompilador y al grafo de llamadas.
Variables
En el decompilador, click derecho sobre una variable, Rename Variable:
local_20 → shellcode_buffer
param_1 → target_pid
DAT_00403000 → target_process_id
Tipos
Cambiar tipos mejora la legibilidad del decompilador dramaticamente. Si param_1 es un HANDLE, cambialo:
// Antes
void FUN_00401000(undefined4 param_1)
// Despues
void inject_shellcode(HANDLE hTargetProcess)
Constantes con nombre
Los valores magicos como 0x40 (PAGE_EXECUTE_READWRITE) o 0x1f0fff (PROCESS_ALL_ACCESS) se pueden convertir en constantes con nombre usando Equates (click derecho, Set Equate).
// Antes
VirtualAllocEx(hProcess, 0, 0x1000, 0x3000, 0x40);
// Despues (con equates aplicados)
VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
El decompilador en profundidad
Limitaciones a conocer
El decompilador no es perfecto. Situaciones donde falla o produce resultados confusos:
Calling conventions incorrectas: si Ghidra no detecta la convencion de llamada correcta (cdecl, stdcall, fastcall), los parametros se asignan mal. Corrige con click derecho en la funcion, Edit Function Signature.
Overlapping variables: cuando el malware reutiliza posiciones del stack para diferentes propositos en diferentes bloques basicos, el decompilador puede mostrar una variable donde deberian ser dos.
Codigo ofuscado: control flow flattening, opaque predicates y saltos calculados confunden al decompilador. En estos casos, el Listing (ensamblador crudo) es mas fiable.
Inline assembly en C++: bloques __asm dentro de codigo compilado producen decompilacion parcial.
Mejorar la salida del decompilador
-
Aplicar tipos de datos Windows: File, Load Data Type Archive, seleccionar windows_vs12_32.gdt o windows_vs12_64.gdt.
-
Corregir firmas de funciones: si conoces los parametros de una funcion (porque la has analizado), edita su firma. El decompilador propaga la informacion a todos los callers.
-
Marcar funciones no-return: funciones como ExitProcess o abort que nunca retornan. Click derecho, Edit Function, marcar "No Return". Esto limpia el flujo de control del decompilador.
-
Crear estructuras: si el malware usa una estructura personalizada, definiela en Data Type Manager. El decompilador mostrara accesos con nombre en lugar de offsets numericos.
// Antes
*(int *)(param_1 + 0x10) = 0x1234;
*(char **)(param_1 + 0x14) = "C:\\Windows\\cmd.exe";
// Despues (con estructura definida)
config->flags = 0x1234;
config->command = "C:\\Windows\\cmd.exe";
Strings: informacion en texto plano
Ventana de strings definidos
Window, Defined Strings muestra todos los strings que Ghidra ha identificado. En malware, los strings revelan:
- URLs de C2 (si no estan cifrados)
- Comandos de shell
- Rutas de persistencia (registry keys, scheduled tasks)
- Mensajes de error que delatan funcionalidad
- User-agents HTTP
- Mutex names
Strings cifrados
Si la ventana de strings esta inusualmente vacia para un binario de su tamano, el malware probablemente cifra sus strings. Busca funciones que:
- Reciben un puntero a datos y una longitud
- Iteran byte a byte con XOR u otra operacion
- Devuelven un puntero a texto legible
Una vez identificas la funcion de descifrado, puedes usar un script de Ghidra para descifrar todos los strings automaticamente.
Scripting con Ghidra (GhidraScript)
Ghidra soporta scripts en Java y Python (Jython). Para tareas repetitivas de analisis de malware, los scripts ahorran horas.
Ejemplo: listar todas las llamadas a APIs sospechosas
# Script GhidraScript (Python/Jython)
suspicious_apis = [
"VirtualAlloc", "VirtualProtect", "VirtualAllocEx",
"CreateRemoteThread", "WriteProcessMemory",
"NtUnmapViewOfSection", "GetProcAddress",
"LoadLibraryA", "LoadLibraryW"
]
fm = currentProgram.getFunctionManager()
for func in fm.getFunctions(True):
name = func.getName()
if any(api in name for api in suspicious_apis):
refs = getReferencesTo(func.getEntryPoint())
print("API: %s" % name)
for ref in refs:
caller = fm.getFunctionContaining(ref.getFromAddress())
if caller:
print(" Called from: %s at %s" % (caller.getName(), ref.getFromAddress()))
Ejemplo: extraer strings XOR con clave de un byte
# Buscar loops XOR de un byte y extraer resultados
listing = currentProgram.getListing()
mem = currentProgram.getMemory()
for func in currentProgram.getFunctionManager().getFunctions(True):
instructions = listing.getInstructions(func.getBody(), True)
for inst in instructions:
if inst.getMnemonicString() == "XOR":
op0 = str(inst.getOpObjects(0)[0]) if inst.getNumOperands() > 0 else ""
op1 = str(inst.getOpObjects(1)[0]) if inst.getNumOperands() > 1 else ""
if op0 != op1: # XOR con diferentes operandos (no es zero-out)
print("XOR found in %s at %s: %s" % (func.getName(), inst.getAddress(), inst))
Flujo de trabajo completo para analisis de malware
Un flujo tipico de analisis de malware en Ghidra:
Fase 1: reconocimiento (5 minutos)
- Revisar Imports (Symbol Tree): que DLLs y funciones importa.
- Revisar Strings (Window, Defined Strings): URLs, rutas, comandos.
- Revisar Entry Point: navegar al entry y ver el flujo inicial.
- Anotar las APIs sospechosas encontradas en imports.
Fase 2: trazado de funcionalidad (20-60 minutos)
- Para cada API sospechosa: Ctrl+Shift+F para ver desde donde se llama.
- Navegar a las funciones que llaman APIs criticas.
- Leer el decompilador para entender la logica.
- Renombrar funciones y variables conforme las identificas.
- Anotar comentarios con ; en puntos clave.
Fase 3: analisis profundo (variable)
- Descifrar strings si estan cifrados (manual o con script).
- Analizar resolucion dinamica de APIs (GetProcAddress patterns).
- Reconstruir estructuras de configuracion.
- Documentar la cadena de infeccion completa.
Fase 4: documentacion
- Exportar el analisis: File, Export Program (Ghidra XML o formato seleccionado).
- Guardar el proyecto de Ghidra con todas las anotaciones.
- Generar IOCs: hashes, strings descifrados, URLs de C2, mutex names.
Consejos para acelerar el analisis
Usa el decompilador primero: lee el pseudo-C para entender la logica general. Solo baja al ensamblador cuando el decompilador produzca algo confuso.
Renombra agresivamente: cada funcion y variable que renombras hace el resto del analisis mas rapido. Invierte tiempo al principio para ganar velocidad despues.
Bookmarks: marca funciones importantes con Ctrl+D. Cuando el binario tiene cientos de funciones, los bookmarks te permiten saltar entre las relevantes.
Function Call Graph: Window, Function Call Graph muestra el arbol de llamadas desde cualquier funcion. Util para tener una vision general antes de sumergirte en el detalle.
Comparar con muestras conocidas: si sospechas que el malware es una variante de una familia conocida, Ghidra puede comparar dos binarios con Version Tracking (Window, Version Tracking).
En el siguiente articulo exploramos IDA Pro, la alternativa comercial, con sus capacidades avanzadas de analisis como IDAPython y FLIRT signatures.
Preguntas frecuentes
Artículos relacionados
IDA Pro: Analisis Avanzado de Ensamblador
Patrones de Malware en Ensamblador: Lo que Debes Reconocer
Instrucciones Basicas: MOV, ADD, SUB, CMP, JMP y Variantes
Cifrado en Ensamblador: XOR, RC4, AES y Deteccion
Este contenido tiene fines exclusivamente educativos y de investigación en ciberseguridad defensiva. No se proporcionan binarios maliciosos ni payloads ejecutables. El uso indebido de esta información es responsabilidad exclusiva del usuario. Leer disclaimer completo.