Tema 11

11. Ingeniería inversa con desensambladores y decompiladores

La ingeniería inversa permite estudiar la lógica interna de un binario sin disponer del código fuente. Con desensambladores, decompiladores y método podemos reconstruir funciones, decisiones, datos y comportamientos relevantes para el análisis de malware.

Objetivo Comprender cómo leer y documentar lógica interna de binarios
Enfoque Desensamblado, decompilación, grafos, referencias y funciones
Resultado Extraer conocimiento técnico más allá del comportamiento observable

11.1 Introducción

El análisis estático inicial y el análisis dinámico muestran pistas y comportamiento. La ingeniería inversa profundiza un nivel más: intenta entender cómo está construida internamente la muestra y por qué actúa de cierta manera.

Cuando una muestra cifra strings, resuelve APIs dinámicamente, oculta configuración o ejecuta rutas de código condicionadas, observar desde afuera puede no alcanzar. En esos casos, desensambladores y decompiladores ayudan a leer funciones, ramas, estructuras y datos internos.

El objetivo de este tema es aprender un método de lectura. No se trata de entender cada instrucción de un binario completo, sino de encontrar las partes relevantes y documentarlas con precisión.

11.2 Qué es ingeniería inversa

Ingeniería inversa es el proceso de analizar un artefacto para comprender su diseño, funcionamiento y comportamiento. En malware, suele aplicarse a binarios, scripts, documentos maliciosos, configuraciones, protocolos o payloads extraídos.

Puede servir para:

  • Identificar capacidades reales de una muestra.
  • Extraer configuración, claves, dominios o rutas internas.
  • Entender algoritmos de cifrado, compresión u ofuscación.
  • Reconstruir condiciones de activación.
  • Crear reglas de detección más robustas.
  • Comparar variantes o familias relacionadas.
En análisis de malware, la ingeniería inversa debe responder preguntas concretas. Revertir todo el binario sin objetivo suele consumir tiempo sin mejorar la defensa.

11.3 Desensamblador y decompilador

Un desensamblador traduce bytes de máquina a instrucciones ensamblador. Un decompilador intenta elevar esas instrucciones a una representación parecida a código de alto nivel, como pseudocódigo C.

Herramienta Qué muestra Límite principal
Desensamblador Instrucciones, direcciones, referencias y grafos Requiere leer bajo nivel y convenciones de llamada
Decompilador Pseudocódigo, estructuras y flujo más legible Puede inferir mal tipos, variables o lógica compleja
Debugger Estado real durante ejecución Requiere ejecutar la muestra y controlar riesgo

La mejor lectura combina herramientas. El pseudocódigo orienta, el ensamblador confirma y el debugging valida comportamiento real.

11.4 Flujo básico de reversing

Un flujo ordenado reduce la posibilidad de perderse en detalles irrelevantes.

  1. Identificar formato, arquitectura y posible packing.
  2. Importar la muestra en la herramienta de reversing.
  3. Esperar o revisar el análisis automático inicial.
  4. Localizar punto de entrada, imports, strings y funciones destacadas.
  5. Buscar referencias cruzadas hacia strings, APIs y datos importantes.
  6. Renombrar funciones y variables según comportamiento observado.
  7. Documentar hallazgos con direcciones, evidencia y nivel de confianza.

11.5 Preparación de la muestra

Antes de abrir una muestra en una herramienta de reversing, conviene confirmar que se trabaja con una copia y que el archivo está identificado.

  • Registrar hash y tipo de archivo.
  • Confirmar arquitectura x86, x64 u otra.
  • Revisar si está empaquetado u ofuscado.
  • Separar muestra original de archivos generados por la herramienta.
  • Evitar rutas de trabajo sincronizadas o compartidas sin control.
  • Registrar versión de la herramienta usada si el análisis será compartido.

11.6 Análisis automático inicial

Los desensambladores modernos identifican funciones, referencias, strings, tablas, imports y posibles tipos. Ese análisis automático ahorra tiempo, pero no es perfecto.

Puede fallar cuando hay:

  • Packing o código desempaquetado solo en memoria.
  • Ofuscación de control de flujo.
  • Código mezclado con datos.
  • Saltos indirectos o tablas complejas.
  • Funciones resueltas dinámicamente.
  • Arquitectura o modo de ejecución detectado incorrectamente.

El resultado automático es una propuesta de lectura, no una verdad absoluta.

11.7 Punto de entrada y función principal

El punto de entrada es donde el loader transfiere control al programa, pero no siempre equivale a la lógica principal. En programas compilados, puede pasar por runtime, inicializadores y envoltorios antes de llegar al código relevante. En malware empaquetado, puede iniciar en el stub del packer.

Para orientarse:

  • Revisar llamadas cercanas al entry point.
  • Buscar transiciones hacia código con imports relevantes.
  • Identificar inicialización de configuración o strings.
  • Relacionar funciones con comportamiento observado dinámicamente.
  • Marcar posibles funciones principales con nombres temporales.

11.8 Strings y referencias cruzadas

Las strings son puntos de entrada útiles para navegar un binario. Una URL, ruta, mensaje, nombre de mutex o clave de registro puede llevar directamente a la función que la usa.

Las referencias cruzadas, o xrefs, muestran dónde se utiliza un dato o función. Son esenciales para responder preguntas como "¿qué código usa este dominio?" o "¿dónde se crea esta clave de registro?".

Pista Cómo navegar Qué puede revelar
URL Buscar xrefs hacia la string Función de comunicación o descarga
Ruta de archivo Seguir uso en llamadas de archivos Instalación, persistencia o recolección
Mensaje de error Ubicar rama que lo muestra o registra Condición fallida o flujo alternativo
Nombre de API Buscar llamadas o resolución dinámica Capacidad técnica concreta

11.9 Imports y llamadas a APIs

Los imports y llamadas a APIs ayudan a ubicar capacidades. En reversing, interesa no solo qué API existe, sino qué datos recibe y desde qué función se llama.

Ejemplos de preguntas útiles:

  • Qué ruta se pasa a una función de creación de archivo.
  • Qué clave se abre o modifica en el registro.
  • Qué dominio o dirección se usa en una conexión.
  • Qué permisos se piden al abrir otro proceso.
  • Qué buffer se cifra, descifra o envía por red.

Una llamada API cobra valor cuando conectamos argumentos, contexto y resultado.

11.10 Resolución dinámica de APIs

Muchas muestras evitan mostrar imports claros. En lugar de importar funciones directamente, cargan bibliotecas y resuelven direcciones en tiempo de ejecución, a veces usando nombres cifrados o hashes.

Señales comunes:

  • Imports muy pocos o genéricos.
  • Llamadas a funciones de carga de bibliotecas y resolución de símbolos.
  • Bucles que calculan hashes sobre nombres de funciones.
  • Tablas internas de punteros a funciones.
  • Strings de APIs descifradas durante ejecución.
Si los imports parecen demasiado pobres para el comportamiento observado, puede haber resolución dinámica u ofuscación.

11.11 Grafos de control de flujo

El grafo de control de flujo muestra bloques básicos y saltos entre ellos. Ayuda a entender decisiones, bucles y caminos alternativos sin leer el código como una lista lineal.

En un grafo conviene observar:

  • Bloques de validación inicial.
  • Ramas de éxito y error.
  • Bucles de procesamiento de datos.
  • Llamadas a funciones importantes.
  • Bloques poco alcanzables o condicionados.
  • Patrones anómalos de ofuscación de flujo.

11.12 Decompilación y pseudocódigo

El pseudocódigo facilita la lectura al mostrar estructuras parecidas a if, while, funciones y variables. Es muy útil para comprender lógica general, pero puede ocultar detalles importantes.

Ventaja Riesgo Práctica recomendada
Lectura más rápida Tipos inferidos incorrectos Verificar con ensamblador en partes críticas
Mejor vista de estructuras Variables artificiales o confusas Renombrar según comportamiento confirmado
Facilita seguir lógica Puede simplificar efectos de flags o casts Revisar saltos y llamadas sensibles
Ayuda a documentar Puede parecer código fuente real Recordar que es reconstrucción aproximada

11.13 Renombrar funciones y variables

Renombrar es una parte central del reversing. Convierte un binario desconocido en un mapa comprensible. Al principio se usan nombres tentativos; luego se refinan con evidencia.

Ejemplos de nombres útiles:

  • check_environment para una función que valida VM, idioma o usuario.
  • decode_config para una rutina que descifra datos internos.
  • create_persistence para una función que crea tarea o clave de inicio.
  • send_beacon para una función que contacta C2 periódicamente.
  • enumerate_files para una rutina que recorre directorios.

Conviene evitar nombres demasiado seguros si la función aún no está confirmada. Un prefijo como possible_ puede ser honesto durante la exploración.

11.14 Comentarios y notas

Los comentarios deben explicar lo que no es obvio. No hace falta comentar cada instrucción; sí conviene registrar hipótesis, datos importantes, condiciones y relaciones con evidencias externas.

  • Indicar qué string o API motivó revisar una función.
  • Anotar argumentos relevantes antes de llamadas críticas.
  • Marcar puntos donde se descifra configuración.
  • Registrar direcciones de funciones importantes.
  • Separar observaciones confirmadas de hipótesis.

11.15 Identificación de estructuras de datos

El malware suele usar estructuras internas para configuración, comandos, claves, listas de extensiones, dominios o estados. Reconocerlas permite pasar de bytes sueltos a información organizada.

Señales de estructuras:

  • Accesos repetidos a offsets desde un mismo puntero.
  • Campos de tamaño, puntero y contador usados juntos.
  • Tablas de comandos o funciones.
  • Listas de cadenas procesadas en bucle.
  • Datos descifrados y luego consultados por varias funciones.

11.16 Análisis de algoritmos simples

Muchas muestras usan rutinas simples de codificación, hashing o cifrado casero para ocultar strings y configuración. No siempre es necesario entender toda la matemática; a veces basta con ubicar entrada, salida y punto donde los datos quedan en claro.

Preguntas útiles:

  • Qué buffer entra a la rutina.
  • Qué clave, semilla o constante se usa.
  • Dónde queda el resultado.
  • Qué función consume el resultado descifrado.
  • Si el algoritmo puede replicarse de forma segura para extraer configuración.

11.17 Comparar estático y dinámico

La ingeniería inversa gana fuerza cuando se conecta con análisis dinámico. Lo observado en ejecución indica dónde mirar; lo leído en el binario explica por qué ocurrió.

Hallazgo dinámico Pregunta de reversing Resultado esperado
Conexión a dominio Dónde se construye o descifra el dominio Configuración C2 o algoritmo de generación
Clave de persistencia creada Qué función decide la ruta y el valor Mecanismo de persistencia documentado
Archivo creado Si fue extraído de recursos o descargado Relación entre dropper y payload
Proceso inyectado Qué APIs y permisos usa Técnica de inyección y artefactos

11.18 Reversing de malware empaquetado

Si una muestra está empaquetada, el archivo en disco puede mostrar poco código real. En ese caso, el reversing estático inicial sirve para identificar el stub, pero la lógica principal puede aparecer solo después de desempaquetar en memoria.

Señales de que conviene cambiar de estrategia:

  • Pocos imports y alta entropía.
  • Entry point en sección inusual.
  • Bucles de copia o descifrado al inicio.
  • Cambios de permisos de memoria.
  • Salto final hacia una región recién preparada.

En estos casos, el análisis dinámico y el debugging ayudan a capturar el código ya desempaquetado.

11.19 Manejo de ofuscación

La ofuscación intenta dificultar lectura. Puede cambiar nombres, ocultar strings, complicar control de flujo, insertar código basura o resolver funciones indirectamente.

Estrategias útiles:

  • Buscar efectos observables en lugar de seguir cada salto.
  • Identificar funciones pequeñas reutilizadas.
  • Concentrarse en puntos de interacción con el sistema.
  • Renombrar bloques a medida que se entiende su propósito.
  • Usar debugging para validar caminos complejos.
  • Documentar límites cuando una parte no pueda confirmarse.

11.20 Extracción de configuración

Una de las tareas más valiosas del reversing es extraer configuración: dominios C2, claves, identificadores de campaña, intervalos de beaconing, rutas, extensiones objetivo o flags de comportamiento.

La configuración puede estar:

  • En strings directas.
  • En recursos o secciones cifradas.
  • Generada por algoritmo.
  • Descargada desde red.
  • Construida a partir de datos del entorno.
  • Guardada en memoria después de una rutina de decodificación.

11.21 Documentar hallazgos de reversing

Los hallazgos deben ser verificables por otra persona. Una buena nota de reversing incluye dirección, función, evidencia y conclusión.

Elemento Contenido recomendado Motivo
Dirección RVA, VA o nombre de función renombrada Permite ubicar el hallazgo
Evidencia String, API, bloque de pseudocódigo o traza Respalda la interpretación
Interpretación Qué hace la función y con qué confianza Comunica resultado técnico
Impacto Persistencia, C2, robo, evasión, cifrado Conecta con defensa y respuesta
IOC derivado Dominio, ruta, clave, mutex, patrón Facilita detección o búsqueda

11.22 Checklist de ingeniería inversa inicial

  1. Confirmar hash, formato y arquitectura.
  2. Revisar si la muestra parece empaquetada.
  3. Cargar en desensamblador y revisar análisis automático.
  4. Ubicar imports, strings, recursos y punto de entrada.
  5. Seguir referencias cruzadas hacia datos relevantes.
  6. Renombrar funciones y variables con base en evidencia.
  7. Usar pseudocódigo como guía y ensamblador como confirmación.
  8. Correlacionar con comportamiento dinámico.
  9. Extraer configuración o IOCs cuando sea posible.
  10. Documentar hallazgos con direcciones y nivel de confianza.

11.23 Errores frecuentes

  • Intentar entender todo el binario sin una pregunta concreta.
  • Confiar ciegamente en el pseudocódigo decompilado.
  • No diferenciar dirección de archivo, RVA y dirección en memoria.
  • Renombrar funciones con conclusiones no confirmadas.
  • Ignorar imports y strings como puntos de navegación.
  • Perder tiempo en runtime o librerías no relevantes.
  • No conectar el reversing con evidencias dinámicas.

11.24 Qué debes recordar de este tema

  • La ingeniería inversa busca responder preguntas concretas sobre lógica interna.
  • Desensambladores muestran instrucciones; decompiladores ofrecen una reconstrucción aproximada.
  • Strings, imports y referencias cruzadas son puntos de entrada muy útiles.
  • Renombrar y comentar transforma un binario desconocido en un mapa de análisis.
  • El mejor resultado surge de combinar reversing con análisis estático inicial y dinámico.

11.25 Conclusión

La ingeniería inversa permite pasar de observar efectos a comprender causas. Con método, un binario opaco empieza a revelar funciones, decisiones, configuraciones y relaciones internas que pueden convertirse en detección, mitigación y conocimiento defensivo.

En el próximo tema trabajaremos debugging de malware: breakpoints, memoria, llamadas API y flujo de ejecución, para validar hipótesis observando la muestra mientras corre.