Exploit Mitigations
Definición
Exploit mitigations son las tecnologías defensivas en capas — compile-time, runtime, OS-level y hardware-level — que interrumpen la cadena desde bug de memory-corruption hasta exploit funcional. Individualmente, cada mitigación derrota una primitive (stack canaries detectan overwrite de return-address; ASLR randomiza layout de direcciones; DEP vuelve no ejecutables las páginas de datos; CFI restringe targets de indirect-branch; MTE captura use-after-free). Colectivamente, fuerzan a atacantes a combinar múltiples primitives — típicamente un info leak más una control-flow primitive más una técnica de cadena como ROP — para aterrizar un exploit, elevando el costo de explotación por órdenes de magnitud. Esta nota es el par defensor de memory-corruption y stack-buffer-overflow; cada técnica de explotación de la rama está moldeada por qué mitigaciones están presentes.
Por qué importa
La historia de la defensa binaria es la historia de exploit mitigations:
- 1996 — Aleph One publica "Smashing the Stack for Fun and Profit". Stack overflows entregan code execution trivial.
- 1998-2001 — StackGuard / ProPolice /
-fstack-protectorintroduce stack canaries. - 2003 — OpenBSD shippea W^X (writable XOR executable). Windows XP SP2 agrega soporte NX bit; Linux sigue.
- 2005 — ASLR entra en distribuciones mainstream.
- 2006 — Mark Dowd / Ben Hawkes publican ROP, demostrando que DEP puede bypass-earse sin code injection.
- 2009 — Vista/7 habilitan ASLR + DEP por default. La explotación moderna empieza a requerir info-leak primitives.
- 2014 — Microsoft introduce Control Flow Guard (CFG). Clang shippea CFI poco después.
- 2020 — Intel CET (shadow stack + ENDBR para indirect branches) shippea en CPUs Tiger Lake.
- 2021 — ARM Memory Tagging Extension (MTE) shippea en Pixel 8 / iPhone 15+.
- 2023+ — Consenso industrial: la única solución estructural son lenguajes memory-safe (Rust, Swift, C++ moderno).
Un exploit moderno contra un target hardened casi nunca es un solo bug. Es una cadena: info leak + memory-corruption primitive + control-flow hijack + payload, cada paso derrotando una o dos mitigaciones. Practitioners senior piensan en términos de qué mitigaciones tiene el target y qué primitives hacen falta para derrotar cada una. Sin este modelo mental, no podés razonar si un CVE es explotable en la práctica.
La clase también importa como nota de enseñanza porque expone tres hechos senior transferibles:
- Las mitigaciones no son "anti-virus". No detectan comportamiento malicioso; detectan primitives específicas de corrupción en puntos específicos (canary mismatch en
ret, indirect branch a target no-ENDBR, MTE tag mismatch en access). La detección es mecánica y arquitectónica: mucho más difícil de bypass-ear que AV basado en firmas. - Cada mitigación tiene una clase definida de bypass. ASLR se derrota con info leaks. DEP se derrota con ROP/JOP. Stack canaries se derrotan con info leaks (leer el canary) o con primitives que no tocan el canary. CFI se derrota con data-only attacks y gadgets que matchean firma. El uso senior nombra la clase de bypass, no solo la mitigación.
- El orden de aparición importa operacionalmente. Canaries (1998) + DEP (2003) + ASLR (2005) + CFI (2014) + Shadow Stack (2020) forman un cost-stack acumulativo contra atacantes. Un atacante enfrentando todas simultáneamente necesita primitives que derroten cada una. Por eso las cadenas modernas de Pwn2Own tienen 4-8 bugs.
Cómo funciona
Exploit mitigations se dividen en 6 familias de mecanismos, cada una derrotando una exploitation primitive específica:
1. Protección de stack frame. Stack canaries / SSP. Un valor random por proceso colocado entre variables locales y saved return address. Se chequea al salir de la función. La corrupción que lo cruza dispara __stack_chk_fail. Shadow stacks (Intel CET, ARM PAC). Una segunda copia write-protected del return address. La CPU compara contra ella en ret; un mismatch trapea. Hardware-enforced; no puede bypass-earse con info leaks estilo canary. Derrota: classic stack buffer overflow -> control de return-address. Clase de bypass: canary-leak primitives, o corrupción que no cruza el canary (locals in-frame, overwrite de frame-pointer).
2. Separación code-data (DEP / NX / W^X). Ninguna página de memoria puede ser writable y executable a la vez. Páginas de datos (stack, heap, BSS) se marcan non-executable; páginas de código se marcan read-only. Derrota: shellcode inyectado en stack o heap. Clase de bypass: ROP y JOP: reutilizar código executable existente en vez de inyectar código nuevo.
3. Address Space Layout Randomization (ASLR / PIE / KASLR). Randomiza la base address del executable, libraries, stack, heap y (en binaries PIE) del binary mismo al cargar. El atacante no sabe dónde viven gadgets, funciones libc o buffers en memoria. Derrota: gadget addresses hardcodeadas, shellcode addresses hardcodeadas. Clase de bypass: info-leak primitives que divulgan una dirección de cada región randomizada; una vez filtrada, todas las direcciones de esa región son computables.
4. Control Flow Integrity (CFI). El compilador etiqueta cada indirect-call target como "válido" (instrucciones Intel ENDBR/ENDBRANCH, tablas Clang CFI, bitmap MSVC CFG). En indirect branch, la CPU o runtime verifica que el target sea un landing pad válido. Targets inválidos trapean. Derrota: la mayoría de ROP/JOP chains, vtable hijacking, overwrites de function-pointer. Clase de bypass: data-only attacks (corromper datos no-control para lograr el mismo efecto), gadgets que matchean firma (CFI permite cualquier function entry legal; el atacante elige uno con side effects útiles), call-oriented programming.
5. Memory tagging (ARM MTE, HWASAN). Cada granule de memoria de 16 bytes tiene un "color" tag de 4 bits. Los punteros llevan el color esperado en bits altos. La CPU trapea ante mismatch de color en load/store. Captura use-after-free, heap overflow, out-of-bounds a velocidad de hardware. Derrota: la mayoría de heap-based memory corruption. Clase de bypass: colisiones de tag-aliasing (probabilidad 1 en 16 por intento, viable para algunas primitives), manipulación explícita de tags si el atacante tiene primitives read/write sobre regiones taggeadas.
6. Sandboxing y process isolation. Código no confiable corre en un proceso restringido: filtros seccomp (Linux), App Sandbox (macOS/iOS), filtros Win32k (Windows), JIT sandbox isolation, browser renderer sandbox. Código explotado en el sandbox puede hacer daño limitado; llegar al lado privilegiado requiere un segundo bug explotable. Derrota: RCE en renderer/parser que produciría compromiso inmediato del sistema. Clase de bypass: bugs de sandbox-escape (ellos mismos suelen ser kernel memory corruption o bugs de validación IPC). La mayoría de cadenas browser-RCE de Pwn2Own incluyen 2+ bugs solo para sandbox escape.
Comparación representativa "con vs sin mitigaciones" para el mismo stack buffer overflow:
WITHOUT mitigations (1996-style):
bug primitive: overwrite saved RIP
exploit: write shellcode in buf, point RIP at &buf
cost: 30 lines of Python, 1 hour
WITH modern stack mitigations (2026 default):
bug primitive: overwrite saved RIP
blocked by: stack canary, ASLR, DEP, CET shadow stack
exploit requires:
- info-leak primitive to read canary (separate bug or format-string)
- info-leak primitive to read libc base (defeat ASLR)
- ROP chain (defeat DEP)
- CET-aware control-flow target (CET-permitted indirect-branch target or stack-pivot to ROP)
cost: 200+ lines of pwntools, weeks of research, often a second bug
El bug no es "las mitigaciones son bypass-eables"; es cada mitigación derrota una primitive específica, y el stack acumulativo impone un multiplicador de costo que determina si un CVE es explotable en la práctica.
Técnicas / patrones
- Habilitá siempre el set completo moderno de mitigaciones en compile time. GCC/Clang:
-fstack-protector-strong -D_FORTIFY_SOURCE=3 -fPIE -pie -fcf-protection=full -Wl,-z,relro,-z,now -fstack-clash-protection. MSVC:/GS /guard:cf /guard:ehcont /CETCOMPAT /DYNAMICBASE /NXCOMPAT /HIGHENTROPYVA. El costo marginal de performance es < 1% en la mayoría de workloads; el aumento de costo de exploit es de órdenes de magnitud. - Verificá estado de mitigaciones con
checksecoreadelf.checksec(parte de pwntools o script standalone) reporta qué mitigaciones están activas en un binary. Binaries de producción deberían reportar Canary=Yes, NX=Yes, PIE=Yes, RELRO=Full, RUNPATH=No. - Auditá dependencias, no solo código first-party. Una library statically-linked compilada sin
-fstack-protectorno tiene canary en funciones que posee. Distribuciones hardened modernas (Ubuntu Pro hardening, Red Hat Enterprise Linux) construyen su package set con mitigaciones completas; usar tu propia toolchain renuncia a eso. - Mitigaciones hardware requieren compilador + kernel + CPU. Intel CET necesita soporte CPU (Tiger Lake+), soporte kernel (Linux 6.6+ con CET habilitado) y binary compilado con
-fcf-protection=full. ARM MTE necesita CPU ARMv8.5+, soporte kernel, malloc que taggee allocations y binary linkeado contra libc tagging-aware. El uso senior verifica todo el stack. - Las mitigaciones interactúan entre sí. Stack canaries protegen saved return address pero no locals adyacentes; CFI protege indirect branches pero no direct calls; ASLR se derrota con un solo info leak que rompe toda la randomización. Razonar sobre qué combinación protege contra qué primitive es la habilidad senior.
- El multiplicador de costo varía por target. Firmware embedded / IoT suele shippear con cero mitigaciones; Linux consumer con defaults fuertes; Chrome / Edge con PartitionAlloc + V8 sandbox + MiraclePtr encima de defaults OS-level. La misma bug class tiene costo de explotación muy distinto según dónde viva el bug.
- OPSEC: exploits mitigation-aware producen telemetría mitigation-aware. Productos EDR detectan canary failures, CET violations, llamadas inusuales a
VirtualProtecty patrones ROP-gadget. Las primitives de mitigation-bypass suelen dejar señales de alta fidelidad incluso cuando tienen éxito.
Variantes y bypasses
El paisaje de mitigaciones se divide en 5 capas de deployment, cada una con su propia superficie de bypass.
1. Mitigaciones compile-time
Stack canaries, FORTIFY_SOURCE, RELRO/BIND_NOW, PIE, flags de compatibilidad CET. Se setean en build time; el costo lo paga el compilador. No pueden agregarse retroactivamente a binaries: requiere recompilación. Gap de deployment más común: binaries closed-source de terceros compilados sin flags modernos.
2. Mitigaciones runtime OS-level
ASLR (randomización gestionada por kernel), DEP (enforcement de page-table por kernel), KPTI (kernel page-table isolation contra Meltdown), SMEP/SMAP (supervisor-mode execute/access prevention). Configuradas por el OS al inicio del proceso. Bypass-eables por info leaks (ASLR) o primitives compuestas (KASLR + KPTI juntas).
3. Mitigaciones hardware
Intel CET (shadow stack + indirect-branch tracking), ARM PAC (pointer authentication), ARM MTE (memory tagging), ARM BTI (branch-target identification). Enforced por la CPU; no pueden bypass-earse solo con software. Requieren hardware nuevo y soporte OS/toolchain actualizado.
4. Allocator hardening
PartitionAlloc de Chrome, Microsoft Segment Heap, mimalloc-secure, MiraclePtr (protección de dangling-pointer con reference counting), GuardedMalloc. Viven entre userspace code y kernel; trapean primitives comunes de heap-corruption. Capa de defensa browser-grade; menos común en software server general.
5. Memory safety a nivel lenguaje
Rust, Swift, Go, Java moderno, C#, TypeScript. El fix estructural: la bug class se vuelve imposible de expresar en el lenguaje. Costo: reescribir código C/C++. Microsoft Azure, Linux kernel (rust-for-linux), Android (más del 50% de código nuevo en 2023+) avanzan agresivamente en esta dirección.
Impacto
Ordenado por severidad real típica (de mala configuración defensiva; la técnica es defensiva):
- Una sola mitigación faltante puede colapsar el cost stack. Un binary de alto valor compilado sin PIE filtra gadget addresses gratis; sin canary, exploits single-overflow se vuelven triviales; sin RELRO, primitives GOT-overwrite funcionan. El costo de exploit lo fija la mitigación más débil del stack.
- Dependencias closed-source de terceros suelen quedarse atrás. Binaries vendor de SDKs era-2018 pueden shippear sin flags modernos. Bundlearlos en tu app importa la superficie de vulnerabilidad que tendrían en ese compile-time.
- Firmware embedded / IoT rutinariamente shippea con cero mitigaciones de memoria. El cost stack es baseline 1996. Por eso comprometer routers/cámaras/impresoras sigue siendo trivial a escala industrial.
- La investigación de mitigation-bypass impulsa exploit-tool development. Cada framework de explotación nombrado (Metasploit, Cobalt Strike, Sliver) shippea generadores de payload mitigation-aware. El mundo "exploit one bug" no existe hace más de una década en targets modernos.
- Mitigaciones hardware-only tienen latencia de rollout. ARM MTE requiere ARMv8.5+; Intel CET requiere Tiger Lake+. Deployment fleet-wide toma años. Hardware viejo nunca obtiene la protección.
Detección y defensa
Esta nota es una defensa: no tiene una sección de defensor target-side en el sentido usual. Los framings relevantes:
Verificación — confirmar que las mitigaciones realmente aplican
1. Auditá binaries de producción con checksec. checksec --file=/usr/bin/myservice reporta estado de cada mitigación. Servidores de producción deberían ser auditables en cualquier momento; findings sorpresa (un binary compilado sin canary en 2024) generan tickets.
2. CI gate sobre output de checksec. Para builds first-party, fallá el pipeline CI si cualquiera de: Canary=No, NX=No, PIE=No, RELRO=Partial. Tratá regresión de mitigación como build break.
3. Verificá que features hardware estén habilitadas. Linux: dmesg | grep -i "smep\|smap\|cet\|mte". macOS: sysctl hw.optional. Android: getprop ro.arm64.memtag.*. Fleets de producción deberían trackear deployment de hardware features.
4. Auditá mitigaciones de dependencias. checksec sobre cada shared library y dependencia statically-linked. Vendor SDKs y prebuilt libraries son el weak link recurrente.
Detección de intentos de explotación
Cuando las mitigaciones disparan, producen telemetría:
1. Canary failures. Aborts de __stack_chk_fail producen eventos de process-crash con firma reconocible. Reglas EDR y SIEM sobre stack-canary aborts son indicadores de exploit de alta fidelidad.
2. CET violations. Mismatches de Intel CET shadow-stack y violaciones de indirect-branch-target producen hardware exceptions entregadas al OS como SIGSEGV con signal codes específicos. Reglas sobre estos eventos capturan muchos intentos ROP/JOP.
3. MTE tag mismatches. ARM MTE genera SIGSEGV con SEGV_MTEAERR (asynchronous) o SEGV_MTESERR (synchronous). En dispositivos Android Pixel, el crash-reporting pipeline de Google agrega estos como señales memory-safety de alta prioridad.
4. Detección runtime EDR de patrones ROP. Firmas behavioral sobre llamadas inusuales VirtualProtect/mprotect, ROP gadget chains en stack memory, patrones JIT-spray. Capa encima de las mitigaciones estructurales.
Qué no funciona como defensa primaria
- "Tenemos antivirus." AV es signature-based y no detecta memory-corruption primitives. Las mitigaciones operan en la capa arquitectónica, donde las firmas no aplican.
- "Solo habilitá ASLR." ASLR sola se derrota con un info leak. La mitigación en capas es la respuesta estructural; ASLR es una capa.
- Confiar en que "las mitigaciones default están activas" sin verificar. Los flags default varían por distro, versión de compilador y build system.
checkseces barato; verificá. - Deshabilitar mitigaciones "por performance". El costo suele ser < 1%; el costo de seguridad de deshabilitar es de órdenes de magnitud. Perfilá primero; la sospecha casi siempre está mal.
- Asumir que mitigaciones binarias cubren scripting languages. Python, JavaScript, Ruby, PHP corren en interpreters/JITs. Las mitigaciones del interpreter protegen memoria del interpreter; bugs en código interpretado (errores lógicos, injection) son otra categoría.
Labs prácticos
# Lab 1 — Install and run checksec on system binaries.
# checksec ships with pwntools, or as the standalone script:
# curl -O https://raw.githubusercontent.com/slimm609/checksec.sh/master/checksec
# chmod +x checksec; ./checksec --file=$(which sudo)
checksec --file=$(which sudo)
checksec --file=$(which ssh)
checksec --file=$(which bash)
# Expected: Canary=Yes, NX=Yes, PIE=Yes, RELRO=Full on modern Linux distros.
# Any No is a finding.
# Lab 2 — Compare the same vulnerable program with progressive mitigations.
cat > vuln.c <<'EOF'
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char buf[64];
if (argc > 1) strcpy(buf, argv[1]);
printf("Got: %s\n", buf);
return 0;
}
EOF
# Build with NO mitigations:
gcc -fno-stack-protector -no-pie -z execstack -o v0 vuln.c
# Build with canary only:
gcc -fstack-protector-strong -no-pie -z execstack -o v1 vuln.c
# Build with canary + PIE:
gcc -fstack-protector-strong -fPIE -pie -z execstack -o v2 vuln.c
# Build with canary + PIE + NX + RELRO:
gcc -fstack-protector-strong -fPIE -pie -Wl,-z,relro,-z,now -o v3 vuln.c
# Build with full modern mitigations:
gcc -fstack-protector-strong -D_FORTIFY_SOURCE=3 -fPIE -pie -Wl,-z,relro,-z,now \
-fcf-protection=full -fstack-clash-protection -O2 -o v4 vuln.c
for bin in v0 v1 v2 v3 v4; do
echo "=== $bin ==="
checksec --file=./$bin
done
# Observe progressive mitigation status.
# Lab 3 — Demonstrate that the same exploit progressively fails.
# Run each binary with a long overflow input:
for bin in v0 v1 v2 v3 v4; do
echo "=== $bin ==="
./$bin $(python3 -c 'print("A"*200)') 2>&1 | head -3
done
# Expected:
# v0: silent corruption or shellcode-territory crash
# v1-v4: '*** stack smashing detected ***: terminated' (canary catches it)
# The bug is the same; the failure mode is controlled.
# Lab 4 — Verify Intel CET is active on the running system.
dmesg 2>&1 | grep -i "x86/cet"
cat /proc/cpuinfo | grep -o 'cet_ss\|cet_ibt' | sort -u
# Expected on Tiger Lake+: cet_ss and/or cet_ibt entries.
# If absent, CET-aware compilation produces ENDBR instructions but no enforcement.
# Lab 5 — Inspect ENDBR instructions in a CET-compiled binary.
gcc -fcf-protection=full -O2 -c -o vuln_cet.o vuln.c
objdump -d vuln_cet.o | grep -E "endbr32|endbr64" | head -10
# Expected: ENDBR64 instructions at the start of every indirect-branch-targetable function.
# These are the landing pads CET enforcement requires.
# Lab 6 — Audit a third-party binary you didn't compile.
# Pick a vendor-provided binary in /opt, /usr/local, or a Docker image:
for f in /opt/*/bin/* /usr/local/bin/* ; do
[ -x "$f" ] && [ ! -L "$f" ] && checksec --file="$f" 2>/dev/null
done | grep -E "(No\b|Partial)" | head -10
# Every result is a hardening gap. The vendor's binary inherits whatever toolchain they used.
Ejemplos prácticos
- Sudo CVE-2021-3156 ("Baron Samedit"). Overflow heap-adjacent estilo off-by-one en stack. Pese a stack canaries, ASLR y PIE — todos habilitados por default en Sudo — el bug entrega root local en esencialmente toda distribución. Takeaway: las mitigaciones suben costo, no garantizan invulnerabilidad. La cadena igual pasó.
- PartitionAlloc + MiraclePtr colapsa un UAF de Chrome. En 2023, MiraclePtr de Chrome (protección de dangling-pointer con reference counting para algunas regiones heap) degradó ~50% de UAFs históricamente explotables a "abort instead of exploit". Los browsers se volvieron mediblemente más difíciles de pwn-ear en la capa renderer. La mitigación no arregló los bugs; rompió la cadena de explotación.
- Print Spooler CVE-2021-1675 / PrintNightmare. Bug Windows kernel-mode explotable pese a SMEP, SMAP, KASLR, KPTI y CFG, porque el atacante controlaba una ruta kernel-mode que bypass-eaba CFG vía DLL loading (una feature documentada de Windows kernel). Operaciones arquitectónicamente permitidas pueden derrotar mitigaciones arquitectónicas.
- ARM MTE en Pixel 8 captura un UAF real. Testing pre-launch de Google de Pixel 8 con malloc MTE-enabled expuso múltiples bugs UAF en apps Android shippeadas que no habían sido detectados por cobertura ASAN/HWASAN. La mitigación hardware capturó lo que software fuzzing había perdido.
- Dispositivo embedded con cero mitigaciones. Auditoría de firmware de una cámara IP barata común revela: no canary, no PIE, no NX, no ASLR. Un buffer-overflow CGI trivial en la interfaz de management web entrega root con un exploit script de 50 líneas. La misma bug class en un servidor Linux moderno con mitigaciones completas requeriría una cadena de 4+ primitives.
Notas relacionadas
- memory-corruption — la bug class objetivo de estas mitigaciones.
- stack-buffer-overflow — el ejemplo trabajado canónico cuyo paisaje de mitigaciones se describe acá.
- rop-and-ret2libc — la técnica que derrota DEP/NX y sigue siendo la base de explotación moderna.
- Dualidad atacante-defensor — exploit mitigations son la expresión más limpia de la dualidad en todo el atlas: cada técnica ofensiva produjo una mitigación correspondiente, y cada mitigación produjo una clase de bypass correspondiente.
- Tríada CIA — las mitigaciones protegen integridad del programa; sus modos de falla preservan disponibilidad (abort controlado) por encima de confidencialidad/integridad (corrupción silenciosa).
- EDR / process correlation — cuando disparan mitigaciones (canary failure, CET violation), EDR captura la señal.
- Behavioral vs Signature Detection — intentos de mitigation-bypass producen señales behavioral (control-flow inusual,
VirtualProtectanómalo) más que patrones de código detectables por firma. - Windows Privilege Escalation — mitigaciones kernel modernas (SMEP, SMAP, CFG, kCET) son las contrapartes kernel-side de las mitigaciones user-space en esta nota.
Futuras notas atómicas sugeridas
- stack-canaries-and-shadow-stacks — deep dive mecánico de SSP / Intel CET shadow stack / ARM PAC.
- aslr-pie-and-info-leak-chains — la clase de bypass para ASLR; la defeat primitive más común en explotación moderna.
- control-flow-integrity-cfi — mecánica Intel IBT / Clang CFI / MSVC CFG.
- arm-mte-and-memory-tagging — deep dive de hardware memory tagging.
- seccomp-and-sandbox-isolation — mecanismo Linux process-sandbox.
- partitionalloc-and-miracleptr — allocator hardening de Chrome como caso de estudio.
- meltdown-spectre-and-side-channel-mitigations — familia de mitigaciones speculative-execution (KPTI, IBRS, RETPOLINE).
Referencias
- Fundamental: Microsoft Security Response Center — A proactive approach to more secure code — https://msrc.microsoft.com/blog/2019/07/a-proactive-approach-to-more-secure-code/
- Investigación / Deep Dive: Dennis Andriesse — Practical Binary Analysis (No Starch Press, 2018) — chapter on modern exploit mitigations
- Hardening: Intel — Control-flow Enforcement Technology (CET) specification — https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf
- Hardening: ARM — Memory Tagging Extension (MTE) — https://developer.arm.com/documentation/108035/latest/
- Hardening: Linux kernel — Self-Protection documentation — https://www.kernel.org/doc/html/latest/security/self-protection.html
- Investigación / Deep Dive: Project Zero — The State of the Art in Bypassing Memory Safety Mitigations — https://googleprojectzero.blogspot.com/