Tema 12

12. Debugging de malware: breakpoints, memoria, llamadas API y flujo de ejecución

El debugging permite observar una muestra mientras se ejecuta, detenerla en puntos específicos, inspeccionar memoria, revisar argumentos de funciones y validar hipótesis sobre su comportamiento interno.

Objetivo Usar debugging para validar comportamiento y extraer evidencias
Enfoque Breakpoints, memoria, APIs, stepping y trazas
Resultado Comprender ejecución real con mayor precisión

12.1 Introducción

El debugging es una técnica de análisis dinámico avanzado. En lugar de observar la muestra solo desde afuera, el analista la ejecuta bajo control para detenerla, avanzar instrucción por instrucción, inspeccionar registros, examinar memoria y observar llamadas a funciones críticas.

En malware, el debugger ayuda a responder preguntas que el análisis estático y la sandbox no siempre resuelven: cuándo se descifra una configuración, qué argumentos recibe una API, dónde se desempaqueta un payload o qué condición evita que una rama se ejecute.

Como implica ejecución de código malicioso, debe realizarse en laboratorio aislado, con snapshots limpios y un plan claro de observación.

12.2 Cuándo usar debugging

No toda muestra requiere debugging. Es una técnica poderosa, pero consume tiempo y puede activar riesgos si se usa sin objetivo.

  • Cuando el análisis estático muestra strings cifradas o imports ocultos.
  • Cuando una sandbox no activa comportamiento esperado.
  • Cuando se necesita capturar configuración descifrada en memoria.
  • Cuando hay indicios de packing o desempaquetado en tiempo de ejecución.
  • Cuando se quiere validar argumentos de APIs sensibles.
  • Cuando un crash o excepción requiere interpretación técnica.
Debugging sin pregunta concreta suele convertirse en exploración lenta. Primero define qué quieres confirmar.

12.3 Preparación del entorno

Antes de abrir una muestra en el debugger, el entorno debe estar listo. El debugger no reemplaza el aislamiento del laboratorio.

  1. Restaurar snapshot limpio.
  2. Verificar red aislada o simulada.
  3. Deshabilitar carpetas compartidas peligrosas.
  4. Registrar hash de la muestra y objetivo de análisis.
  5. Activar monitoreo externo de procesos, archivos, registro y red.
  6. Preparar ruta para guardar dumps, notas y capturas.
  7. Definir puntos de parada iniciales o APIs de interés.

12.4 Debugger de usuario y kernel

La mayoría de los ejercicios de este curso se concentran en debugging de espacio de usuario. El debugging de kernel se usa para drivers, rootkits o fallas profundas del sistema, y requiere preparación adicional.

Tipo Uso común Riesgo o complejidad
Usuario Ejecutables, DLLs, procesos, APIs Más accesible y suficiente para muchas muestras
Kernel Drivers, rootkits, fallas privilegiadas Mayor complejidad y posibilidad de bloquear el sistema
Remoto Análisis desde otra máquina o VM Requiere red y configuración controlada

12.5 Breakpoints

Un breakpoint detiene la ejecución en una dirección, función o condición. Permite inspeccionar estado justo antes o después de una acción importante.

Tipos comunes:

  • Software: modifica temporalmente el código para detener ejecución.
  • Hardware: usa registros del procesador para detener sin modificar bytes de código.
  • De memoria: detiene cuando una región se lee, escribe o ejecuta.
  • Condicional: detiene solo si se cumple una condición.
  • En API: detiene cuando se llama una función del sistema relevante.

12.6 Breakpoints en APIs

Colocar breakpoints en APIs permite observar acciones de alto nivel: archivos, registro, red, memoria, procesos o criptografía. La clave es revisar argumentos y contexto cuando la ejecución se detiene.

Familia de API Qué puede revelar Dato importante
Archivos Creación, lectura, escritura o borrado Ruta, modo de acceso y proceso responsable
Registro Persistencia o configuración Clave, valor y datos escritos
Red Conexiones y transferencia Destino, puerto, buffer y frecuencia
Memoria Asignación, permisos y código generado Tamaño, protección y dirección resultante
Procesos Ejecución, apertura o inyección PID, permisos solicitados y ruta objetivo

12.7 Stepping

Stepping es avanzar controladamente por el código. Las acciones principales son entrar en una llamada, pasar por encima de una llamada o continuar hasta retornar.

  • Step into: entra en la función llamada.
  • Step over: ejecuta la llamada sin entrar en ella.
  • Step out: continúa hasta salir de la función actual.
  • Run until: ejecuta hasta una dirección o condición.

Entrar en todas las llamadas produce ruido. Conviene entrar solo en funciones propias de la muestra o en rutinas críticas para la pregunta de análisis.

12.8 Registros y flags

Los registros muestran el estado inmediato de ejecución. En x64, muchos argumentos de funciones se pasan por registros; por eso, revisar RCX, RDX, R8 y R9 en Windows x64 o RDI, RSI, RDX, RCX, R8 y R9 en System V puede revelar datos clave.

Los flags indican resultados de comparaciones y operaciones. Saltos condicionales dependen de ellos, así que son importantes para entender por qué se toma una rama.

  • RIP/EIP indica la instrucción actual.
  • RSP/ESP ubica el stack.
  • RAX/EAX suele contener valores de retorno.
  • Flags como ZF o CF condicionan saltos.

12.9 Stack durante debugging

El stack ayuda a reconstruir llamadas, argumentos, direcciones de retorno y variables locales. Durante debugging, revisar el stack puede explicar cómo se llegó a una función y qué datos temporales maneja.

Usos habituales:

  • Ver direcciones de retorno.
  • Identificar argumentos en x86 o argumentos adicionales en x64.
  • Reconocer cadenas o punteros cercanos.
  • Interpretar crashes por corrupción de retorno o punteros.
  • Relacionar funciones llamadoras y llamadas.

12.10 Memoria y permisos

La vista de memoria permite revisar regiones asignadas, permisos, módulos, strings y datos generados durante ejecución. En malware, muchas piezas importantes aparecen solo en memoria.

Observación Posible significado Acción de análisis
Región RWX Código generado o desempaquetado Inspeccionar y considerar dump
Strings nuevas en memoria Configuración descifrada Buscar referencias y guardar evidencia
Buffer enviado por red Beaconing o exfiltración Correlacionar con PCAP
Módulo no esperado DLL cargada o inyección Revisar ruta, firma y exportaciones

12.11 Dumps de memoria

Un dump conserva una región de memoria, un módulo o un proceso completo para analizarlo luego. Es útil cuando el código real aparece desempaquetado durante ejecución o cuando la configuración queda descifrada temporalmente.

Al guardar dumps, registrar:

  • Proceso y PID.
  • Dirección base y tamaño de la región.
  • Permisos de memoria.
  • Motivo de captura.
  • Hash del dump resultante.
  • Momento de ejecución en que fue capturado.

12.12 Flujo de ejecución

El flujo de ejecución describe qué instrucciones y ramas toma el programa. El debugger permite seguir ese flujo, pero el analista debe evitar perderse en rutas irrelevantes.

Recomendaciones:

  • Usar breakpoints para saltar a puntos de interés.
  • Combinar grafo del desensamblador con ejecución real.
  • Registrar decisiones importantes y valores evaluados.
  • Observar cuándo cambia el flujo hacia regiones nuevas de memoria.
  • Comparar ramas tomadas y no tomadas si hay comportamiento condicionado.

12.13 Condiciones y ramas

Muchas muestras verifican entorno, privilegios, idioma, conectividad o presencia de herramientas. El debugging permite observar qué condición se evalúa y qué rama se toma.

Ejemplos de condiciones relevantes:

  • Si el sistema parece una máquina virtual.
  • Si el usuario tiene privilegios suficientes.
  • Si existe un archivo, proceso o clave esperada.
  • Si una conexión de red responde.
  • Si la fecha, idioma o zona horaria coincide con una campaña.

Modificar condiciones dentro del debugger puede servir para explorar rutas, pero debe documentarse claramente porque cambia la ejecución natural.

12.14 Excepciones y crashes

Las excepciones detienen ejecución por eventos como acceso inválido a memoria, instrucción ilegal, división por cero o breakpoint. En malware, una excepción puede ser error real, mecanismo anti-debugging o parte de un flujo controlado.

Al analizar un crash, registrar:

  • Tipo de excepción.
  • Dirección donde ocurre.
  • Módulo afectado.
  • Registros y stack.
  • Instrucción que falló.
  • Datos de entrada o condición previa.

12.15 Debugging de desempaquetado

Cuando una muestra está empaquetada, el debugger puede ayudar a llegar al momento en que el código real aparece en memoria. El objetivo defensivo es observar y capturar el contenido desempaquetado para analizarlo con más claridad.

Señales durante ejecución:

  • Asignación de memoria con permisos de escritura y ejecución.
  • Bucles que copian o transforman grandes buffers.
  • Cambios de protección de memoria.
  • Resolución dinámica de APIs.
  • Salto a una región recién preparada.

12.16 Debugging de comunicación

Para entender comunicación, conviene combinar breakpoints en APIs de red con captura de tráfico. El debugger muestra buffers y argumentos; el PCAP muestra lo que efectivamente salió o intentó salir.

  • Revisar dominios antes de resolución.
  • Observar direcciones y puertos usados.
  • Inspeccionar buffers antes de envío.
  • Comparar datos en memoria con tráfico capturado.
  • Identificar intervalos de beaconing y condiciones de reintento.

12.17 Debugging de persistencia

La persistencia se puede estudiar deteniendo llamadas a registro, tareas, servicios o escritura de archivos en ubicaciones de inicio. Esto permite ver exactamente qué valor se crea y de dónde sale.

Mecanismo Qué observar Evidencia útil
Registro Clave, valor y datos Ruta de persistencia y proceso creador
Tarea programada Comando, disparador y usuario XML, logs o comandos asociados
Servicio Nombre, binario y tipo de inicio Configuración y privilegios
Archivo en startup Ruta y contenido Hash del archivo y enlace con muestra

12.18 Anti-debugging

Algunas muestras intentan detectar o interferir con debuggers. Pueden consultar flags del proceso, medir tiempos, buscar ventanas o procesos de herramientas, provocar excepciones o comprobar diferencias de ejecución.

Señales comunes:

  • Comportamiento distinto bajo debugger.
  • Finalización inmediata al iniciar análisis.
  • Demoras o bucles de espera inusuales.
  • Excepciones repetidas o intencionales.
  • Consultas a información del proceso o entorno.

La respuesta debe ser metódica: primero confirmar que existe anti-debugging, luego documentar qué condición lo activa.

12.19 Modificar ejecución durante análisis

Un debugger permite cambiar registros, memoria o flujo. Esto puede ayudar a explorar ramas no tomadas, pero también altera el comportamiento natural de la muestra.

Si se modifica ejecución:

  • Registrar exactamente qué se cambió.
  • Guardar estado anterior si es posible.
  • Distinguir resultados naturales de resultados forzados.
  • No mezclar evidencias sin aclarar condiciones.
  • Repetir ejecución limpia para confirmar hallazgos importantes.

12.20 Documentación de una sesión

Una sesión de debugging puede generar mucha información. La documentación debe capturar lo esencial sin convertirse en una transcripción completa de cada instrucción.

  • Objetivo de la sesión.
  • Hash de la muestra y snapshot usado.
  • Breakpoints configurados.
  • Eventos importantes con hora o secuencia.
  • Registros, argumentos o buffers relevantes.
  • Dumps guardados y sus hashes.
  • Conclusiones y preguntas pendientes.

12.21 Checklist de debugging seguro

  1. Definir pregunta de análisis.
  2. Restaurar snapshot limpio y confirmar aislamiento.
  3. Registrar hash, arquitectura y contexto de la muestra.
  4. Activar monitoreo externo complementario.
  5. Configurar breakpoints en puntos de interés.
  6. Ejecutar y observar registros, stack, memoria y APIs.
  7. Guardar dumps o buffers relevantes.
  8. Documentar cambios manuales si se modifica ejecución.
  9. Exportar evidencias y restaurar entorno al finalizar.

12.22 Errores frecuentes

  • Depurar sin objetivo y perderse en instrucciones irrelevantes.
  • Olvidar que el debugger ejecuta código malicioso real.
  • No iniciar monitoreo externo antes de la sesión.
  • Entrar en todas las funciones del sistema en vez de seguir puntos de interés.
  • No guardar dumps antes de que la memoria cambie.
  • Modificar registros o saltos sin documentarlo.
  • Confiar en una sola ejecución cuando hay comportamiento condicionado.

12.23 Qué debes recordar de este tema

  • El debugging permite validar hipótesis observando ejecución interna.
  • Breakpoints bien elegidos ahorran tiempo y reducen ruido.
  • Registros, stack, memoria y argumentos de API son fuentes centrales de evidencia.
  • Los dumps permiten conservar código o configuración que solo aparece en memoria.
  • Modificar ejecución puede ser útil, pero debe separarse de comportamiento natural.

12.24 Conclusión

El debugging agrega precisión al análisis de malware. Permite mirar dentro de la ejecución, capturar datos temporales, seguir decisiones y explicar comportamientos que desde afuera solo se ven como efectos.

En el próximo tema estudiaremos ofuscación, packing, anti-debugging, anti-VM y técnicas de evasión, para entender por qué algunas muestras intentan resistirse al análisis.