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¶
- Definición de Vulnerabilidad
- Buffer Overflow
- Heap Overflow
- Use-After-Free (UAF)
- Null Pointer Dereference
- Integer Overflow / Underflow
- Race Conditions (TOCTOU)
- Uninitialized Memory Use
- DLL Hijacking / Insecure Library Loading
- Type Confusion
- 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.
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 | Sí | No directamente |
Relevancia en Reversing¶
- Buscás asignaciones
new/mallocseguidas 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/memcpyque 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 anullptr. - 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
nullptrdespués dedelete: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.
CreateFileconFILE_FLAG_OPEN_REPARSE_POINTpara no seguir symlinks.- En Linux:
openat+O_NOFOLLOW, ofstatsobre el fd abierto en vez destatsobre 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)¶
- Directorio desde donde se cargó la aplicación
- Directorio del sistema (
C:\Windows\System32) - Directorio del sistema de 16-bit (
C:\Windows\System) - Directorio de Windows (
C:\Windows) - Directorio de trabajo actual (CWD)
- 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_SYSTEM32flag enLoadLibraryEx. - 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_casten lugar destatic_castpara downcasting (con verificación en runtime). - Verificar tipos explícitamente antes de castear.
- Evitar
reinterpret_casta 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_casten 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