Saltar a contenido

Next-Gen Reverse Engineering Training

Módulo 4: Introducción a la Explotación de Vulnerabilidades

Objetivo del módulo: Comprender las vulnerabilidades más comunes en software Windows nativo (C/C++), cómo se manifiestan en el código fuente, cómo las explota un atacante, y qué patrones dejan en el binario al analizarlas con un desensamblador.

Apunte de ROP

Apunte de UAF


Tabla de Contenidos

  1. Definición de Vulnerabilidad
  2. Buffer Overflow
  3. Heap Overflow
  4. Use-After-Free (UAF)
  5. Null Pointer Dereference
  6. Integer Overflow / Underflow
  7. Race Conditions (TOCTOU)
  8. Uninitialized Memory Use
  9. DLL Hijacking / Insecure Library Loading
  10. Type Confusion
  11. Resumen Comparativo

1. Definición de Vulnerabilidad

Una vulnerabilidad es una debilidad o falla en software, hardware, o el diseño de un sistema que puede ser explotada —intencional o accidentalmente— para comprometer la confidencialidad, integridad, o disponibilidad del sistema o sus datos.

El triángulo CIA

Propiedad Descripción Ejemplo de compromiso
Confidencialidad Los datos solo son accesibles para quienes están autorizados Leak de contraseñas o claves privadas
Integridad Los datos no son modificados sin autorización Alteración de logs o binarios
Disponibilidad El sistema está operativo cuando se lo necesita Crash forzado por un attacker (DoS)

Vulnerabilidad vs. Exploit

Una vulnerabilidad puede ser explotable si las condiciones permiten a un atacante aprovecharla de forma confiable para obtener acceso no autorizado o causar daño. Sin embargo, no toda vulnerabilidad es explotable en la práctica: algunas existen solo en teoría, requieren condiciones poco realistas, o representan un riesgo mínimo en el mundo real.

Vulnerabilidad  →  ¿Es alcanzable?  →  ¿Es controlable?  →  Exploit
                        No                   No
                    (teórica)           (muy limitada)

Relevancia en Reversing

Al analizar un binario, el objetivo no es solo encontrar bugs sino determinar si son explotables y bajo qué condiciones. Esto implica entender: - Superficie de ataque: qué entrada controla el atacante. - Primitivas de explotación: qué operaciones de memoria puede controlar (lectura, escritura, ejecución). - Mitigaciones activas: ASLR, DEP/NX, Stack Canaries, CFG, etc.


2. Buffer Overflow

Concepto

Un buffer overflow (desbordamiento de búfer) ocurre cuando un programa escribe más datos en un búfer de los que este puede contener, pisando memoria adyacente. Es una de las vulnerabilidades más antiguas y estudiadas, y sigue siendo muy común en código C/C++ nativo de Windows.

¿Por qué es peligroso?

En la pila (stack), la memoria adyacente a un buffer local incluye: - Otras variables locales - El saved frame pointer (SFP) del llamador - La dirección de retorno (return address)

Si un atacante controla qué se escribe, puede sobrescribir la dirección de retorno con una dirección de su elección, redirigiendo la ejecución.

Stack frame de foo():
┌──────────────────────┐  ← RSP (tope de la pila)
│  buffer[10]          │  ← buffer local (10 bytes)
├──────────────────────┤
│  saved RBP           │  ← frame pointer del llamador
├──────────────────────┤
│  return address      │  ← ¡OBJETIVO del atacante!
├──────────────────────┤
│  argumentos          │
└──────────────────────┘

Código vulnerable

// Patrón vulnerable (solo ilustrativo)
#include <cstring>

void foo(const char* input) {
    char buffer[10];
    strcpy(buffer, input); // Inseguro: sin verificación de longitud
}

El problema: strcpy copia hasta el \0 del origen sin verificar el tamaño del destino. Si input tiene más de 9 caracteres (+ el \0), se desbordan los límites del buffer.

Funciones inseguras más comunes

Función insegura Función segura alternativa Riesgo
strcpy(dst, src) strncpy(dst, src, n) o strcpy_s Overflow sin límite
strcat(dst, src) strncat(dst, src, n) o strcat_s Overflow sin límite
sprintf(buf, fmt, ...) snprintf(buf, n, fmt, ...) Overflow de formato
gets(buf) fgets(buf, n, stdin) Overflow sin límite
scanf("%s", buf) scanf("%9s", buf) Overflow sin límite

Ejemplo con explotación conceptual

#include <cstring>
#include <cstdio>

void secretFunction() {
    printf("Acceso no autorizado!\n");
}

void foo(const char* input) {
    char buffer[10];
    // Con un input de 18 bytes en x86 (10 buffer + 4 SFP + 4 ret):
    // Los últimos 4 bytes sobreescriben la dirección de retorno.
    // Un atacante puede apuntar esos 4 bytes a &secretFunction.
    strcpy(buffer, input); // VULNERABLE
}

int main() {
    foo("AAAAAAAAAAAAAAAAAAAAAA"); // 22 'A' → overflow
    return 0;
}

Mitigaciones modernas

  • Stack Canary (GS en MSVC): El compilador coloca un valor aleatorio entre el buffer y la dirección de retorno. Si el canary fue modificado, el programa aborta antes de retornar.
  • ASLR (Address Space Layout Randomization): Aleatoriza las direcciones base de módulos, heap y stack, dificultando saber a dónde saltar.
  • DEP / NX (Data Execution Prevention): Marca la pila y el heap como no ejecutables, impidiendo ejecutar shellcode inyectado ahí.
  • CFG (Control Flow Guard): Valida que los saltos indirectos apunten a destinos legítimos.

Relevancia en Reversing

Al analizar un binario buscás: - Llamadas a strcpy, strcat, gets, sprintf sin límite de tamaño. - Buffers de tamaño fijo en el stack usados con entrada externa. - Ausencia del canary en el prólogo (sin __security_cookie en MSVC).


3. Heap Overflow

Concepto

Similar al buffer overflow pero el buffer víctima está en el heap (memoria dinámica). En lugar de sobrescribir la dirección de retorno en el stack, se corrompen metadatos del heap o objetos adyacentes, lo que puede alterar el flujo del programa.

Estructura del heap en Windows

El heap de Windows (NT Heap / Segment Heap) organiza la memoria en chunks. Cada chunk tiene un header con metadatos (tamaño, flags, etc.). Si un overflow sobrescribe el header del chunk siguiente, el allocator puede ser manipulado para escribir datos arbitrarios en ubicaciones arbitrarias (técnica conocida como heap feng shui o heap grooming).

Heap en memoria:
┌──────────────┐
│ Header chunk │  ← tamaño, flags, punteros de lista enlazada
├──────────────┤
│ buffer[8]    │  ← dato del usuario (8 bytes)
├──────────────┤
│ Header chunk │  ← ¡OBJETIVO! Si se sobreescribe, el free() es controlable
│ siguiente    │
├──────────────┤
│ datos chunk  │
│ siguiente    │
└──────────────┘

Código vulnerable

#include <cstring>

void heapExample(const char* input) {
    char* buffer = new char[8];
    strcpy(buffer, input); // Inseguro: puede escribir más allá del bloque del heap
    delete[] buffer;
}

El problema: Se asignan 8 bytes en el heap, pero strcpy puede escribir más si input es largo, corrompiendo el chunk adyacente o sus metadatos.

Consecuencias típicas

  • Corrupción de metadatos del heap → crash al llamar a free()/delete.
  • Sobrescritura de punteros de función en objetos C++ adyacentes (muy relevante si hay vtables en el heap).
  • Arbitrary write si se controla la corrupción de la freelist.

Diferencias con Stack Overflow

Característica Stack Overflow Heap Overflow
Memoria afectada Stack (automática) Heap (dinámica)
Target típico Return address / SFP Metadatos de chunk / objetos adyacentes
Dificultad de explotación Media Alta (requiere heap shaping)
Detección por canary No directamente

Relevancia en Reversing

  • Buscás asignaciones new/malloc seguidas de operaciones de copia sin validación de tamaño.
  • Heap spraying: patrones de muchas asignaciones del mismo tamaño → típico de explotación de heap.
  • En IDA/Ghidra, rastreás el flujo de datos desde la entrada del usuario hasta el strcpy/memcpy que llena el buffer heap-allocated.

4. Use-After-Free (UAF)

Concepto

Un Use-After-Free ocurre cuando el código continúa usando un puntero después de que la memoria apuntada fue liberada. El puntero se convierte en un dangling pointer (puntero colgante) que apunta a memoria que puede haber sido reasignada a otro objeto.

¿Por qué es explotable?

Si un atacante puede controlar qué datos se asignan en el espacio de memoria recién liberado (técnica llamada heap grooming o heap spraying), puede colocar datos maliciosos exactamente donde estaba el objeto original. Cuando el código usa el dangling pointer, estará usando los datos del atacante como si fueran el objeto original.

En objetos C++ con vtable, esto es especialmente poderoso: si el atacante puede colocar un puntero a una vtable falsa en la memoria liberada, la siguiente llamada a un método virtual ejecutará código arbitrario.

Timeline de un UAF:

t=1: obj = new Foo();     → alloc en 0x1234
t=2: delete obj;          → 0x1234 liberado, obj sigue apuntando a 0x1234
t=3: bar = new Bar();     → el allocator reutiliza 0x1234 para Bar
t=4: obj->method();       → usa 0x1234 como si fuera Foo, pero es Bar
                             → si Bar tiene vftable falsa → RCE

Código vulnerable

#include <iostream>

void uafExample() {
    int* p = new int(42);
    delete p;                          // p liberado, pero sigue siendo válido como dirección
    std::cout << *p;                   // Dangling pointer — comportamiento indefinido
    // En explotación real: otro alloc reutilizó esa memoria,
    // y *p ahora lee datos controlados por el atacante.
}

Ejemplo más realista con objetos C++

#include <iostream>

class Animal {
public:
    virtual void speak() { std::cout << "Animal\n"; }
    virtual ~Animal() {}
};

class Dog : public Animal {
public:
    void speak() override { std::cout << "Woof!\n"; }
};

void uafObjectExample() {
    Animal* a = new Dog();
    delete a;           // a liberado → dangling pointer
    // Atacante aquí podría hacer un alloc del mismo tamaño
    // y colocar una vftable falsa en esa dirección.
    a->speak();         // UAF: llama a través de una vftable potencialmente controlada
}

Patrones de detección en Reversing

  • Puntero liberado (delete/free) seguido de uso posterior sin ser puesto a nullptr.
  • Objetos cuyo ciclo de vida no es claro: múltiples módulos compartiendo punteros.
  • Llamadas a métodos virtuales sobre objetos que podrían haber sido liberados antes.

Mitigaciones

  • Poner el puntero a nullptr después de delete: delete p; p = nullptr;
  • Smart pointers (std::unique_ptr, std::shared_ptr): eliminan la gestión manual.
  • Heap isolation / delayed reuse: algunas implementaciones modernas del heap evitan reutilizar inmediatamente la memoria liberada.

5. Null Pointer Dereference

Concepto

Ocurre cuando se desreferencia un puntero que contiene nullptr (0x0) sin haberlo verificado previamente. Históricamente en sistemas de 32 bits esto podía ser explotable mapeando la página 0 con datos controlados por el atacante. En sistemas modernos de 64 bits generalmente causa un crash (Denial of Service).

Código vulnerable

void nullDeref(int* p) {
    // Riesgoso: sin chequeo de null
    int x = *p;   // Si p == nullptr → acceso a 0x0 → EXCEPTION_ACCESS_VIOLATION
}

La versión segura:

void nullDerefSafe(int* p) {
    if (p == nullptr) {
        return; // o lanzar excepción, o manejar el error
    }
    int x = *p; // Seguro
}

¿Cuándo se vuelve explotable?

En sistemas Linux de 32 bits con mmap_min_addr = 0 (configuración antigua), un atacante podía mapear la página 0 con shellcode. Hoy esto está mitigado por el kernel.

En Windows, el acceso a la página 0 genera una excepción estructurada (SEH). En kernelmode, una excepción no manejada puede causar un BSOD, lo que la hace útil como primitiva de DoS en drivers vulnerables.

Patrones frecuentes

// Patrón 1: retorno no verificado
FILE* f = fopen("datos.txt", "r");
fread(buffer, 1, 100, f);  // Si fopen falló, f == NULL → crash

// Patrón 2: resultado de new sin excepción (nothrow)
int* p = new (std::nothrow) int[1000000];
*p = 42;  // Si la asignación falló, p == nullptr → crash

// Patrón 3: puntero de estructura no inicializado
struct Node { int val; Node* next; };
Node* n = nullptr;
n->val = 10;  // Crash inmediato

Relevancia en Reversing

Buscás en el grafo de flujo de control (CFG) de IDA/Ghidra: - Llamadas a funciones que pueden retornar NULL, sin verificación posterior. - Accesos a miembros de estructuras sin validar que el puntero a la estructura no sea NULL. - En drivers, ExAllocatePool retorna NULL si falla; drivers mal escritos no lo verifican.


6. Integer Overflow / Underflow

Concepto

Ocurre cuando el resultado de una operación aritmética excede el rango del tipo entero usado. En enteros sin signo, el valor "da la vuelta" (wrap-around). En enteros con signo, es comportamiento indefinido en C/C++.

El peligro real no está en el overflow en sí, sino en que el valor incorrecto resultante se usa luego como tamaño de un buffer, índice de un array, o condición de seguridad.

Tipos

Tipo Descripción Ejemplo
Overflow Supera el máximo del tipo uint8_t x = 255; x += 1; // x == 0
Underflow Cae por debajo del mínimo uint8_t x = 0; x -= 1; // x == 255
Signedness Confusión entre signed/unsigned int len = -1; size_t s = (size_t)len; // s == 0xFFFFFFFF
Truncation Recorte al asignar a tipo más chico int n = 256; uint8_t b = n; // b == 0

Código vulnerable

#include <cstdint>

void intOverflow(uint32_t len) {
    uint32_t total = len * 1024; // Podría overflow si len > 4194303
    char* buf = new char[total]; // Se asigna un tamaño incorrecto (muy chico)
    // Luego se copia 'len * 1024' bytes reales al buffer chico → overflow
    delete[] buf;
}

Escenario de ataque: - len = 0x00400001 (4194305) - len * 1024 = 0x100000400 → en uint32_t se trunca a 0x400 (1024) - Se asigna un buffer de 1024 bytes - Luego el código copia 0x100000400 bytes reales → heap overflow masivo

Ejemplo de signedness confusion

#include <cstring>

void copyData(const char* src, int len) {
    // len podría ser negativo si viene de entrada del usuario
    if (len > 1024) return;             // Chequeo insuficiente: no verifica < 0
    char buf[1024];
    memcpy(buf, src, (size_t)len);      // Si len == -1 → (size_t)(-1) == 0xFFFFFFFF
                                        // → memcpy copia 4GB → stack/heap overflow
}

Mitigaciones

  • Validar siempre que el resultado de multiplicaciones usadas como tamaños no supere SIZE_MAX.
  • Usar funciones de multiplicación segura: ULongLongMult, SIZETMult (Windows), o __builtin_mul_overflow (GCC/Clang).
  • Usar tipos con signo explícito y verificar rangos antes de asignar.

Relevancia en Reversing

En el binario, buscás: - Instrucciones imul/mul cuyos resultados se pasan directamente a malloc/new sin verificación. - Comparaciones que solo verifican el límite superior (> MAX) pero no el inferior (< 0). - Conversiones implícitas de int a size_t cerca de operaciones de memoria.


7. Race Conditions (TOCTOU)

Concepto

Una race condition (condición de carrera) ocurre cuando el comportamiento del programa depende de la secuencia o el timing relativo de múltiples hilos o procesos. La variante más común en explotación es TOCTOU (Time-Of-Check vs Time-Of-Use): hay una ventana de tiempo entre que se verifica una condición y se usa el recurso, y en ese intervalo el estado puede cambiar.

El problema fundamental

Hilo A (programa víctima):          Hilo B (atacante):
1. stat("file.txt", &s)  ← CHEQUEO
                                     2. symlink("evil.txt", "file.txt")
3. open("file.txt")      ← USO (abre evil.txt, no file.txt!)

Entre los pasos 1 y 3, el atacante reemplazó el archivo por un symlink a un archivo malicioso. El programa creyó que abría file.txt pero abre evil.txt.

Código vulnerable

#include <fstream>
#include <sys/stat.h>

void raceExample(const char* file) {
    struct stat s;
    stat(file, &s);              // CHECK: verifica que el archivo existe
    // ← ventana de vulnerabilidad: otro proceso puede cambiar el archivo aquí
    std::ifstream f(file);       // USE: asume que sigue siendo el mismo archivo
}

Ejemplo más elaborado: escalada de privilegios

// Escenario: servicio corriendo como SYSTEM
// crea un archivo temporal y luego lo abre para escritura

#include <windows.h>
#include <stdio.h>

void vulnerableService() {
    char tmpPath[MAX_PATH];
    GetTempFileName("C:\\Temp", "svc", 0, tmpPath);

    // CHECK: crea el archivo vacío
    // VENTANA: atacante puede borrar tmpPath y crear un symlink
    //          apuntando a C:\Windows\System32\evil.dll

    HANDLE h = CreateFile(tmpPath,               // USE: abre lo que sea que esté en tmpPath
                          GENERIC_WRITE, 0, NULL,
                          OPEN_EXISTING, 0, NULL);
    // Si el atacante ganó la carrera, el servicio SYSTEM escribe en evil.dll
}

Mitigaciones

  • Usar handles en lugar de nombres: abrir el archivo una sola vez y usar el handle para todas las operaciones.
  • CreateFile con FILE_FLAG_OPEN_REPARSE_POINT para no seguir symlinks.
  • En Linux: openat + O_NOFOLLOW, o fstat sobre el fd abierto en vez de stat sobre el path.
  • Minimizar el tiempo entre check y use.

Relevancia en Reversing

Las race conditions son difíciles de ver en el código binario porque requieren entender el timing. Lo que buscás: - Múltiples referencias al mismo path de archivo con operaciones separadas (stat + open, access + fopen). - En drivers: recursos compartidos entre IRPs sin locks adecuados. - Uso de nombres de archivo temporales predecibles en directorios con permisos amplios (C:\Temp).


8. Uninitialized Memory Use

Concepto

Ocurre cuando se lee memoria que nunca fue inicializada. La memoria de la pila o el heap contiene los valores que quedaron de usos anteriores (basura). Esto puede: - Filtrar información sensible (contraseñas, claves, punteros de kernel). - Causar comportamiento impredecible según el contenido de la memoria. - En combinación con otras vulnerabilidades, servir como leak de punteros para romper ASLR.

Código vulnerable

#include <iostream>

void uninitExample() {
    int x;               // No inicializado → contiene lo que había en el stack
    std::cout << x;      // Comportamiento indefinido: puede imprimir cualquier valor
}

Más peligroso — estructura no inicializada:

struct Credentials {
    char username[32];
    char password[32];
};

void sendCredentials() {
    Credentials creds;
    strncpy(creds.username, "admin", 5);
    // creds.password NUNCA se inicializa
    // Si se envía 'creds' por red, se filtra lo que estaba en el stack
    // (potencialmente: contraseña de otra llamada, punteros, etc.)
    send(socket, &creds, sizeof(creds), 0);
}

Relación con information leaks

En explotación, una information leak (filtración de información) es a menudo el primer paso: 1. Usar una vuln de uninitialized memory para leer punteros de stack/heap. 2. Calcular las direcciones reales de módulos (rompiendo ASLR). 3. Usar esas direcciones en el exploit final (ROP gadgets, shellcode, etc.).

Herramientas de detección

  • Valgrind (Linux): detecta lecturas de memoria no inicializada en runtime.
  • AddressSanitizer / MemorySanitizer (Clang/GCC): instrumentación en compilación.
  • Application Verifier (Windows): detecta heap corruption y uso de memoria liberada.
  • Static analysis: PVS-Studio, Coverity, CodeQL.

Relevancia en Reversing

  • Estructuras enviadas por red o escritas a archivos con padding no inicializado.
  • Variables de stack usadas antes de su primera asignación (visible en el grafo de flujo de datos de IDA).
  • Buffers asignados con malloc (no inicializa) vs. calloc (sí inicializa a cero).

9. DLL Hijacking / Insecure Library Loading

Concepto

Si un programa carga una DLL sin especificar la ruta completa, Windows busca la DLL en un orden predefinido de directorios. Un atacante que puede colocar una DLL maliciosa con el nombre correcto en alguno de esos directorios antes de que el proceso cargue la legítima, logra que se ejecute su código con los privilegios del proceso víctima.

Orden de búsqueda de DLLs en Windows (DLL Search Order)

  1. Directorio desde donde se cargó la aplicación
  2. Directorio del sistema (C:\Windows\System32)
  3. Directorio del sistema de 16-bit (C:\Windows\System)
  4. Directorio de Windows (C:\Windows)
  5. Directorio de trabajo actual (CWD)
  6. Directorios en la variable %PATH%

El CWD es especialmente peligroso: si el programa se ejecuta desde un directorio donde el usuario tiene permisos de escritura (Downloads, Desktop, carpeta de un ZIP extraído), un atacante puede colocar ahí una DLL maliciosa.

Código vulnerable

#include <windows.h>

void loadExample() {
    LoadLibraryA("mydll.dll"); // Peligroso: ruta relativa
                               // Windows buscará mydll.dll en el orden de búsqueda
                               // incluyendo el CWD donde podría haber una DLL maliciosa
}

La versión segura:

#include <windows.h>

void loadSafe() {
    LoadLibraryA("C:\\Program Files\\MyApp\\mydll.dll"); // Ruta absoluta: segura
}

Escenario de ataque típico

1. Víctima descarga "installer.exe" a C:\Downloads\
2. Atacante previamente colocó C:\Downloads\version.dll (DLL maliciosa)
3. installer.exe llama LoadLibraryA("version.dll")
4. Windows encuentra primero C:\Downloads\version.dll (CWD)
5. Se ejecuta el DllMain del atacante con los privilegios del installer

DLLs frecuentemente hijackeadas

Algunas DLLs que muchas aplicaciones cargan sin ruta completa y que no están en System32: - version.dll - dwmapi.dll - uxtheme.dll - cryptbase.dll - wldap32.dll

Mitigaciones

  • Siempre usar rutas absolutas en LoadLibrary.
  • Usar SetDllDirectory("") para remover el CWD del orden de búsqueda.
  • Usar LOAD_LIBRARY_SEARCH_SYSTEM32 flag en LoadLibraryEx.
  • Firmar digitalmente las DLLs y verificar la firma antes de cargarlas.

Relevancia en Reversing

En el binario, buscás: - Llamadas a LoadLibraryA/LoadLibraryW con strings que no empiezan con C:\ o \\. - Imports de DLLs que no están en System32 y que podrían no estar instaladas. - En el IAT (Import Address Table), DLLs cuyo path no está verificado.


10. Type Confusion

Concepto

Type confusion es una vulnerabilidad que ocurre cuando un programa trata un bloque de memoria como si fuera de un tipo diferente al que realmente es. Este mismatch hace que el programa realice operaciones inválidas sobre esa memoria, potencialmente causando comportamiento indefinido, corrupción de memoria, o ejecución de código arbitrario.

Es especialmente crítica en motores de JavaScript, navegadores, y cualquier sistema que maneje tipos dinámicos o polimorfismo C++.

Mecanismo

Memoria real:                    Interpretación incorrecta:
┌────────────────┐               ┌────────────────┐
│ struct A       │               │ struct B       │
│  int x = 42   │  ←───────── │  int y         │  ← lee x como y (OK, mismo offset)
└────────────────┘               │  ptr* fn       │  ← ¡lee más allá de A!
                                  └────────────────┘

Si struct B es más grande que struct A, acceder a b->fn lee memoria que no pertenece al objeto, que podría ser controlada por el atacante.

Código vulnerable

#include <iostream>

struct A { int x; };
struct B { int y; };

int main() {
    A a{42};
    B* b = reinterpret_cast<B*>(&a); // Type confusion: trata A como B
    std::cout << b->y << std::endl;  // Comportamiento indefinido
                                     // En la práctica: lee a.x (mismo offset)
    return 0;
}

Ejemplo más peligroso: type confusion con herencia y vtables

#include <iostream>

class Shape {
public:
    virtual void draw() { std::cout << "Drawing shape\n"; }
    int color;
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing circle\n"; }
    float radius;   // Campo adicional que Shape no tiene
};

class Triangle : public Shape {
public:
    void draw() override { std::cout << "Drawing triangle\n"; }
    int sides;
    float* vertices; // Puntero → si se confunde con Circle::radius, es crítico
};

void typeConfusionExample(Shape* s, bool isCircle) {
    if (isCircle) {
        // El código asume que es Circle, pero podría ser Triangle
        Circle* c = static_cast<Circle*>(s);  // Sin dynamic_cast → sin verificación
        // Si s apunta a un Triangle, c->radius lee Triangle::sides como float
        // Un atacante que controla Triangle::sides controla el valor de radius
        std::cout << c->radius << "\n";
    }
}

Type confusion en navegadores (escenario real)

Los motores JavaScript (V8, SpiderMonkey) representan valores con tipos como HeapNumber, JSArray, JSObject. Una type confusion puede ocurrir si el GC o el JIT compiler asume incorrectamente el tipo de un objeto:

Objeto real en heap:  JSArray { length: 100, elements: [...] }
Interpretado como:    HeapNumber { value: ??? }
→ El atacante puede hacer que el "número" apunte a memoria arbitraria
→ Con read/write de ese "número" puede leer/escribir memoria fuera de los bounds

Mitigaciones

  • Usar dynamic_cast en lugar de static_cast para downcasting (con verificación en runtime).
  • Verificar tipos explícitamente antes de castear.
  • Evitar reinterpret_cast a menos que sea absolutamente necesario.
  • Diseño con tipos bien separados y sin uniones (union) de tipos con semánticas distintas.

Relevancia en Reversing

  • reinterpret_cast en el binario (visible a veces en nombres mangleados o en patrones de acceso a campos).
  • Objetos polimórficos que se castean sin pasar por dynamic_cast (sin llamada a __RTDynamicCast).
  • En exploits de navegador: patrones de JIT corruption o map transitions anómalas.

11. Resumen Comparativo

Vulnerabilidad Memoria afectada Primitiva típica CWE Mitigación principal
Buffer Overflow Stack Control de RIP/RBP CWE-121 Stack Canary, ASLR, DEP
Heap Overflow Heap Heap metadata corruption CWE-122 Heap integrity checks
Use-After-Free Heap Arbitrary read/write CWE-416 Smart pointers, ptr = nullptr
Null Pointer Deref Página 0 DoS / kernel crash CWE-476 Validación de punteros
Integer Overflow Stack o Heap Wrong size alloc CWE-190 Safe integer arithmetic
TOCTOU Filesystem Privilege escalation CWE-367 Atomic operations, handles
Uninit Memory Stack o Heap Info leak CWE-457 Inicialización, sanitizers
DLL Hijacking Código ejecutable Code execution CWE-427 Rutas absolutas, SetDllDirectory
Type Confusion Heap (objetos) Arbitrary read/write CWE-843 dynamic_cast, type checks

Flujo típico de explotación encadenada

En exploits reales, las vulnerabilidades se encadenan:

1. Info Leak (uninit memory / out-of-bounds read)
   Rompe ASLR → obtiene direcciones de módulos en memoria
2. Primitiva de escritura (buffer overflow / UAF / type confusion)
   Sobrescribe un puntero de función / dirección de retorno / vftable
3. Control de ejecución
   ROP chain (Return Oriented Programming) para bypass de DEP/NX
4. Payload final (shellcode, reverse shell, escalada de privilegios)

Apunte basado en el material de Binary Gecko — Next-Gen Reverse Engineering Training, Módulo 4: Introduction to Vulnerability Exploitation