Tema 19

19. Buffer overflow, control de flujo, stack, heap y condiciones de explotación

Los desbordamientos de buffer son una familia clásica de errores de memoria. Comprenderlos ayuda a interpretar crashes, evaluar impacto, reconocer control de flujo indebido y aplicar mitigaciones que reducen el riesgo de explotación.

Objetivo Entender cómo una corrupción de memoria puede afectar seguridad
Enfoque Stack, heap, control de flujo, crashes y mitigaciones
Resultado Evaluar fallos de memoria con criterio defensivo

19.1 Introducción

Un buffer overflow ocurre cuando un programa escribe más datos de los que una región de memoria puede contener. Esa escritura excedente puede alterar datos cercanos, corromper estructuras internas, provocar un crash o, bajo condiciones específicas, modificar el flujo de ejecución.

Históricamente, los buffer overflows fueron una de las vías más conocidas para explotación de memoria. Hoy existen mitigaciones importantes, pero siguen siendo relevantes para comprender fallos, analizar crashes y evaluar riesgos en software nativo, parsers, servicios, drivers y componentes embebidos.

Este tema explica los conceptos desde una mirada educativa y defensiva. El objetivo es entender qué hace peligroso un fallo de memoria, no construir código ofensivo contra sistemas reales.

19.2 Qué es un buffer

Un buffer es una zona de memoria reservada para almacenar datos temporalmente: una cadena, un paquete, una ruta, un bloque de archivo, una estructura o cualquier secuencia de bytes.

El riesgo aparece cuando el programa asume tamaños incorrectos o no valida límites. Si un buffer tiene espacio para cierta cantidad de datos y se escriben más, lo sobrante termina en memoria adyacente.

El problema central no es que exista un buffer, sino que el programa pierda control sobre cuánto se escribe, dónde se escribe y qué queda cerca.

19.3 Buffer overflow y buffer overread

Un overflow escribe fuera de límites. Un overread lee fuera de límites. Ambos son errores de memoria, pero su impacto suele ser distinto.

Error Qué ocurre Impacto posible
Buffer overflow Se escriben datos más allá del buffer Crash, corrupción, control de flujo o alteración de estado
Buffer overread Se leen datos más allá del buffer Fuga de información, crash o exposición de memoria
Out-of-bounds access Acceso fuera del rango esperado Lectura o escritura indebida según el caso

19.4 Stack overflow

Un stack overflow ocurre cuando la escritura fuera de límites afecta memoria ubicada en el stack. El stack contiene datos temporales de funciones, variables locales, direcciones de retorno y valores guardados.

Desde el análisis defensivo, interesa observar:

  • Qué función contiene el buffer vulnerable.
  • Qué entrada controla el tamaño de escritura.
  • Qué datos quedan cerca del buffer.
  • Si el crash afecta dirección de retorno, punteros o variables de control.
  • Qué mitigaciones estaban habilitadas.

19.5 Heap overflow

Un heap overflow ocurre cuando se escriben datos fuera de un bloque reservado dinámicamente. El heap contiene objetos y buffers cuya vida puede extenderse más allá de una función.

Los errores de heap pueden ser más difíciles de analizar porque la corrupción puede no provocar crash inmediato. El fallo visible puede aparecer mucho después, cuando el programa usa una estructura dañada.

  • Corrupción de objetos cercanos.
  • Alteración de punteros o tamaños internos.
  • Crash diferido en una función distinta.
  • Comportamiento no determinista por layout de memoria.
  • Diferencias entre ejecuciones por ASLR o asignaciones variables.

19.6 Control de flujo

Control de flujo es el orden en que el programa ejecuta instrucciones. Normalmente lo determinan llamadas, retornos, saltos y condiciones. Una corrupción de memoria se vuelve especialmente grave si altera un dato que influye en ese flujo.

Elementos relacionados:

  • Direcciones de retorno.
  • Punteros a función.
  • Tablas virtuales u objetos con métodos.
  • Manejadores de excepciones.
  • Variables que deciden ramas de seguridad.

No todo overflow controla flujo. Algunos solo provocan crash o alteran datos. La evaluación debe demostrar qué se controla y con qué estabilidad.

19.7 Crash no equivale a exploit

Un crash indica que el programa falló, pero no prueba por sí solo que exista explotación práctica. Para evaluar impacto se necesita entender causa, control, repetibilidad y mitigaciones.

Observación Pregunta defensiva Conclusión posible
Acceso inválido a memoria La dirección depende de entrada controlada Puede ser crash sin control o indicio mayor
RIP/EIP alterado El valor proviene de datos controlados Potencial control de flujo
Crash no determinista Se reproduce de forma estable Puede requerir más análisis
Mitigación detiene ejecución Qué defensa actuó Impacto reducido o explotación más compleja

19.8 Condiciones de explotación

Para que una vulnerabilidad de memoria sea explotable suelen coincidir varias condiciones. La ausencia de una condición puede reducir impacto, aunque no elimina la necesidad de corregir el fallo.

  • Entrada controlable por un actor no confiable.
  • Escritura o lectura fuera de límites reproducible.
  • Corrupción de un dato relevante.
  • Conocimiento suficiente del layout de memoria.
  • Mitigaciones ausentes, débiles o sorteables.
  • Contexto de ejecución con permisos útiles para el atacante.

19.9 Entradas que activan fallos

Los overflows pueden activarse por datos provenientes de muchas fuentes. El analista debe identificar la ruta completa desde entrada hasta escritura.

  • Campos de archivos procesados por parsers.
  • Paquetes o mensajes de red.
  • Parámetros de API o formularios.
  • Argumentos de línea de comandos.
  • Variables de entorno.
  • Datos serializados o comprimidos.
  • Mensajes entre procesos.

19.10 Longitud, terminadores y codificación

Muchos errores aparecen por diferencias entre longitud real, longitud esperada y representación. Un campo puede medirse en caracteres, bytes, palabras o unidades codificadas.

Riesgos típicos:

  • Contar caracteres pero reservar bytes insuficientes.
  • Depender de terminadores nulos ausentes.
  • Truncar longitudes al convertir tipos.
  • Validar antes de decodificar o descomprimir.
  • Copiar datos sin asegurar espacio para terminador.

19.11 Análisis de un crash

El crash analysis busca entender qué falló y por qué. Debe realizarse en entorno controlado, con símbolos cuando estén disponibles y registro cuidadoso de entradas.

  1. Reproducir el fallo con entrada mínima.
  2. Registrar versión, plataforma y mitigaciones activas.
  3. Identificar excepción, dirección y módulo.
  4. Revisar registros, stack y memoria cercana.
  5. Relacionar datos de entrada con estado del proceso.
  6. Determinar si hay control parcial o total de valores relevantes.
  7. Documentar impacto probable y mitigaciones.

19.12 Registros importantes

Durante un crash, los registros muestran el estado inmediato del procesador. En análisis de explotación, interesa especialmente si valores controlados por entrada llegan a registros críticos.

Registro Qué indica Valor para análisis
RIP/EIP Instrucción actual o destino de ejecución Evalúa control de flujo
RSP/ESP Puntero al stack Permite revisar retornos y datos cercanos
RBP/EBP Base de frame, si se usa Ayuda a reconstruir función
RAX/EAX Resultados o valores temporales Puede explicar operaciones fallidas
Flags Resultado de comparaciones Explica ramas previas al fallo

19.13 Patrones controlados

En laboratorio, los patrones controlados permiten saber qué parte de una entrada llegó a una región de memoria o registro. Esto ayuda a medir offset y alcance de corrupción sin usar datos reales ni payloads dañinos.

Desde la defensa, la idea es responder:

  • Qué bytes de la entrada terminan cerca del crash.
  • Si la corrupción es reproducible.
  • Si hay control sobre punteros o direcciones.
  • Si el tamaño necesario es razonable para una entrada real.
  • Si la aplicación filtra, transforma o corta datos.

19.14 Stack canaries

Los stack canaries son valores colocados para detectar corrupción del stack antes de que una función retorne. Si el canary cambia, el programa puede abortar ejecución antes de usar una dirección de retorno dañada.

Valor defensivo:

  • Detectan ciertos overflows de stack.
  • Reducen probabilidad de explotación directa.
  • No corrigen la vulnerabilidad original.
  • No protegen todos los tipos de corrupción.
  • Deben combinarse con validación y otras mitigaciones.

19.15 DEP y NX

DEP o NX impide ejecutar código en páginas marcadas como datos. Esto dificulta técnicas clásicas donde se escribía código en stack o heap y se intentaba ejecutarlo directamente.

En análisis, una excepción por ejecución en memoria no ejecutable puede indicar que una mitigación actuó. La corrección sigue siendo necesaria: DEP reduce impacto, pero no elimina la escritura fuera de límites.

19.16 ASLR

ASLR aleatoriza direcciones de memoria para dificultar que una explotación dependa de ubicaciones fijas. Afecta módulos, stack, heap y otras regiones según plataforma y configuración.

Al analizar fallos, ASLR explica por qué direcciones cambian entre ejecuciones. También obliga a distinguir entre offset relativo, base de módulo y dirección virtual real.

19.17 Control Flow Integrity

Las tecnologías de integridad de flujo intentan validar que llamadas y saltos indirectos vayan a destinos esperados. Su objetivo es reducir abuso de punteros de función, retornos o transiciones anómalas.

Estas mitigaciones no reemplazan correcciones de código, pero agregan barreras. En un reporte, conviene indicar si el fallo fue detenido por una mitigación y qué impacto residual queda.

19.18 Heap hardening

Los allocators modernos incluyen verificaciones para detectar corrupción de heap, metadatos inconsistentes o uso indebido de bloques. Esto puede convertir una corrupción explotable en un crash temprano.

Señales:

  • Abortos por corrupción detectada.
  • Crashes al liberar o reasignar memoria.
  • Errores que aparecen después de la escritura original.
  • Diferencias entre builds debug y release.

19.19 Impacto según contexto

El mismo tipo de fallo puede tener impactos muy distintos según dónde ocurra.

Contexto Riesgo Prioridad defensiva
Parser local de archivo Requiere que usuario abra archivo malicioso Alta si el formato es común o correo lo distribuye
Servicio expuesto Entrada remota sin interacción Muy alta si está accesible a internet
Proceso con privilegios Puede elevar impacto del fallo Alta por permisos y alcance
Componente aislado Mitigado por sandbox o contenedor Depende de escape o datos afectados

19.20 Prevención en desarrollo

La prevención comienza en diseño y código. Las mitigaciones de plataforma ayudan, pero el objetivo principal es evitar la corrupción.

  • Usar APIs que reciben tamaño explícito y validan límites.
  • Preferir lenguajes o abstracciones con seguridad de memoria cuando sea viable.
  • Validar longitudes antes de copiar o transformar.
  • Revisar conversiones numéricas y cálculos de tamaño.
  • Aplicar fuzzing y sanitizers durante pruebas.
  • Activar mitigaciones de compilador y sistema operativo.

19.21 Evidencia para un reporte

Un reporte de fallo de memoria debe demostrar causa e impacto sin incluir material peligroso innecesario.

  • Versión afectada y configuración.
  • Entrada mínima o descripción controlada del caso.
  • Tipo de excepción y módulo.
  • Registros relevantes al momento del crash.
  • Relación entre entrada y corrupción.
  • Mitigaciones presentes y efecto observado.
  • Recomendación de corrección y pruebas de regresión.

19.22 Checklist de análisis

  1. Confirmar entorno autorizado y reproducible.
  2. Registrar versión, arquitectura y mitigaciones.
  3. Reproducir crash con entrada controlada.
  4. Identificar tipo de excepción y dirección.
  5. Revisar registros, stack, heap y memoria cercana.
  6. Determinar relación entre entrada y corrupción.
  7. Evaluar si hay control de flujo o solo caída.
  8. Valorar impacto según contexto y exposición.
  9. Proponer corrección y mitigaciones complementarias.

19.23 Errores frecuentes

  • Asumir que todo crash permite ejecución de código.
  • No registrar mitigaciones activas durante la prueba.
  • Confundir dirección aleatorizada con offset estable.
  • Analizar solo el punto de crash e ignorar la escritura original.
  • Usar entradas destructivas o datos reales cuando basta un caso mínimo.
  • No distinguir impacto local, remoto, autenticado o no autenticado.
  • Confiar en mitigaciones como sustituto de corregir el bug.

19.24 Qué debes recordar de este tema

  • Un buffer overflow escribe fuera de límites y puede corromper memoria cercana.
  • Stack y heap tienen comportamientos y riesgos distintos.
  • Control de flujo indebido es una condición crítica, pero debe demostrarse.
  • Un crash no equivale automáticamente a explotación práctica.
  • Mitigaciones como canaries, DEP/NX, ASLR y CFI reducen impacto, pero no corrigen la causa raíz.

19.25 Conclusión

Los buffer overflows siguen siendo una base importante para comprender explotación de memoria, análisis de crashes y mitigaciones modernas. El valor defensivo está en identificar causa, condiciones, impacto y corrección con evidencia clara.

En el próximo tema estudiaremos desarrollo de exploits en laboratorio desde una mirada controlada: fuzzing, crash analysis y prueba de concepto segura para validar impacto sin afectar sistemas reales.