Intermedioensambladorghidrareverse-engineeringmalwareherramientas

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.

MalwareIntel Research··10 min lectura
Serie: Lenguaje Ensamblador — Parte 10

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.

Atajos criticos

AtajoAccion
GGo To Address (saltar a direccion)
LRename (renombrar funcion o variable)
TChange Type (cambiar tipo de dato)
;Set Comment (anadir comentario)
Ctrl+Shift+FFind References To (xrefs)
Ctrl+EExport selection
DDisassemble (forzar desensamblado)
CClear code (limpiar desensamblado erroneo)
PMake function (crear funcion en la direccion actual)
SpaceToggle Listing/Graph view
Alt+LeftBack (navegacion historica)
Alt+RightForward

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.

  1. En Symbol Tree, navega a Imports, kernel32.dll, CreateRemoteThread.
  2. Ctrl+Shift+F: ves que FUN_00401000 la llama.
  3. Navegas a FUN_00401000 y ves el decompilador.
  4. Identificas que tambien llama a VirtualAllocEx y WriteProcessMemory.
  5. Ctrl+Shift+F sobre FUN_00401000: ves que es llamada desde FUN_004010A0.
  6. Navegas a FUN_004010A0 y descubres que recibe un PID como parametro.
  7. 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

  1. Aplicar tipos de datos Windows: File, Load Data Type Archive, seleccionar windows_vs12_32.gdt o windows_vs12_64.gdt.

  2. 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.

  3. 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.

  4. 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:

  1. Reciben un puntero a datos y una longitud
  2. Iteran byte a byte con XOR u otra operacion
  3. 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)

  1. Revisar Imports (Symbol Tree): que DLLs y funciones importa.
  2. Revisar Strings (Window, Defined Strings): URLs, rutas, comandos.
  3. Revisar Entry Point: navegar al entry y ver el flujo inicial.
  4. Anotar las APIs sospechosas encontradas en imports.

Fase 2: trazado de funcionalidad (20-60 minutos)

  1. Para cada API sospechosa: Ctrl+Shift+F para ver desde donde se llama.
  2. Navegar a las funciones que llaman APIs criticas.
  3. Leer el decompilador para entender la logica.
  4. Renombrar funciones y variables conforme las identificas.
  5. Anotar comentarios con ; en puntos clave.

Fase 3: analisis profundo (variable)

  1. Descifrar strings si estan cifrados (manual o con script).
  2. Analizar resolucion dinamica de APIs (GetProcAddress patterns).
  3. Reconstruir estructuras de configuracion.
  4. Documentar la cadena de infeccion completa.

Fase 4: documentacion

  1. Exportar el analisis: File, Export Program (Ghidra XML o formato seleccionado).
  2. Guardar el proyecto de Ghidra con todas las anotaciones.
  3. 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

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.