Panorama#

Los tres capítulos previos del track binario —3.6, 3.7 y 3.15— giran alrededor de la pila (stack): una región con una disciplina rígida —crece hacia abajo, guarda el return pointer en un lugar predecible, y lo saca al retornar—. Esa disciplina es justamente lo que la vuelve explotable de forma tan directa: hay un objetivo obvio (el puntero de retorno) y un momento obvio para golpearlo (el epílogo de la función). El heap —el montículo, la región de la que se sirven malloc()/free() en C y los new/delete de C++— no tiene nada de eso. No hay puntero de retorno que sobrescribir, no hay orden temporal garantizado entre las asignaciones, y el atacante no elige directamente dónde cae cada objeto. La idea central de este capítulo es que, a falta de un objetivo tan cómodo como el return pointer, la explotación del heap ataca los metadatos del asignador: las estructuras internas que la biblioteca de C usa para llevar la cuenta de qué bloques están libres y dónde. Corromper esos metadatos convierte un error de gestión de memoria —un use-after-free, un double-free, un desbordamiento de un bloque sobre el siguiente— en una primitiva de escritura arbitraria (write-what-where), y desde ahí, en ejecución de código.

Este capítulo completa el track binario con el hueco que 3.15 dejó explícito: la profundidad SANS 660.4 no llega al heap (esa materia vive en cursos posteriores). Todo lo que sigue se apoya en el asignador ptmalloc2 de glibc —el que usa Linux en la práctica— porque su diseño es público, está muy documentado y sus técnicas son las que mejor ilustran el principio general. Los asignadores de Windows (el NT heap y el Segment Heap), de los navegadores (PartitionAlloc en Chrome, el IsolatedHeap de Internet Explorer que apareció en 3.7) y de los kernels difieren en detalles —el NT heap y el Segment Heap de Windows se abren en la sección «Bajo el capó» de este capítulo, y su cruce con la explotación de esa plataforma es 3.8—, pero comparten la lógica de fondo: el asignador es una máquina de estados que confía en sus propios metadatos, y esa confianza es la superficie de ataque. Como en el resto del track, cada técnica ofensiva se describe para poder reconocerla y cerrarla; el cierre púrpura recoge por qué la explotación de memoria se detecta mal en tiempo de ejecución y qué la ataca de raíz.

flowchart TD
  A["Bug de gestión de memoria\n(UAF / double-free / heap overflow)"] --> B["Grooming del heap\ndejar los chunks en el orden deseado"]
  B --> C{"¿Qué metadato se corrompe?"}
  C -->|"puntero fd de un chunk libre"| D["tcache / fastbin poisoning\nel asignador devuelve una dirección elegida"]
  C -->|"tamaño / punteros del chunk vecino"| E["unlink / overlap\nchunks que se solapan"]
  D --> F["Escritura arbitraria\n(write-what-where)"]
  E --> F
  F --> G["Sobrescribir un puntero de función\n(__malloc_hook, GOT, vtable)"]
  G --> H["Control del flujo\n→ pivote a ROP (3.7) → shell"]

El modelo del asignador: chunks, bins y arenas#

Para entender qué se corrompe hay que entender qué mantiene el asignador. malloc() no pide una página al kernel por cada llamada: reserva grandes regiones con brk() o mmap() y las reparte internamente en bloques llamados chunks. Cada chunk lleva, justo antes de la memoria que el programa usa, una pequeña cabecera de metadatos: el tamaño del bloque (size) y unos bits de estado, entre ellos el PREV_INUSE que indica si el chunk físicamente anterior está en uso. Ese detalle —los metadatos viven en línea, pegados a los datos del usuario— es la raíz de casi todo lo que sigue: un desbordamiento de un bloque no pisa un puntero de retorno, pero sí pisa la cabecera del bloque vecino.

Cuando el programa llama a free(), el chunk no se devuelve al sistema: el asignador lo guarda en una lista de libres para reciclarlo en el próximo malloc() del mismo tamaño. Esas listas se llaman bins y se organizan por tamaño y por política:

  • Los fast bins guardan chunks pequeños en una lista simplemente enlazada (singly-linked), con política LIFO —el último liberado es el primero en reusarse—. Son rápidos porque no se consolidan con sus vecinos.
  • Los tcache bins (thread cache, introducidos en glibc 2.26, 2017) son una caché por hilo, también LIFO y simplemente enlazada, que intercepta la mayoría de las asignaciones pequeñas antes de que lleguen a los fast/small bins. Son hoy el objetivo predilecto de la explotación de heap justamente por lo poco protegidos que nacieron.
  • Los small, large y unsorted bins manejan el resto con listas doblemente enlazadas (doubly-linked) y consolidación de vecinos libres.

El puntero al siguiente elemento de la lista de libres (fd, forward pointer) se guarda dentro del propio chunk liberado, en los primeros 8 bytes de lo que era la zona de datos del usuario. Esto es económico —no gasta memoria extra— pero significa que la lista de libres vive en memoria escribible por el programa. Si el atacante consigue escribir en un chunk que el asignador cree libre, reescribe el fd y, por tanto, controla qué dirección devolverá malloc() la próxima vez. Toda la familia de técnicas de poisoning descansa en esta única observación.

Por encima de los bins está la arena: la estructura malloc_state que agrupa los bins de un conjunto de hilos y apunta al top chunk (el bloque grande sin repartir del que se cortan asignaciones nuevas). Un programa multithread usa varias arenas para reducir la contención de bloqueos.

Por qué el heap es determinista pese a ASLR: el grooming#

ASLR aleatoriza la base del heap, igual que aleatoriza la pila y las bibliotecas —de eso se ocupó 3.15—. Pero dentro del heap, el asignador es determinista: dada una secuencia conocida de malloc()/free(), los chunks caen en posiciones relativas predecibles. El atacante no necesita saber la dirección absoluta de un objeto para saber que el objeto B quedará justo después del objeto A, o que un malloc() de cierto tamaño reusará el hueco que dejó un free() reciente.

Manipular la disposición del heap para dejar los chunks en el orden conveniente —la víctima pegada al desbordamiento, el hueco correcto listo para ser reasignado— se llama heap grooming o heap feng shui. Se logra guiando al programa a hacer asignaciones y liberaciones a través de su funcionalidad legítima: abrir objetos, cerrarlos, cargar campos de cierto tamaño. En un navegador esto se orquesta desde JavaScript (reservar miles de arrays del tamaño justo); en un servicio de red, encadenando peticiones. El grooming es la parte artesanal de la explotación de heap y la que la vuelve fiable: sin él, el bug existe pero el resultado es un crash aleatorio en vez de un control preciso.

Las primitivas-bug: de dónde sale el control#

Un bug de heap por sí solo no da control; da una primitiva, una capacidad elemental que hay que combinar con el modelo del asignador para llegar a algo útil. Cuatro clases cubren la enorme mayoría de los casos.

Heap overflow. Un desbordamiento de un chunk sobre la memoria contigua. Como los metadatos viven en línea, el desbordamiento pisa la cabecera (size, bits de estado) del chunk siguiente, o su contenido si ese chunk está en una lista de libres —y entonces pisa el puntero fd—. Es el equivalente de heap al desborde de pila de 3.6, pero el objetivo no es un return pointer sino los metadatos del vecino. Falsificar el campo size para que el asignador crea que dos chunks son uno solo produce chunks solapados (overlapping chunks): dos punteros del programa que apuntan a la misma memoria con tipos distintos.

Use-after-free (UAF). El bug más productivo de la clase, mapeado a T1203 en ATT&CK y responsable de una fracción enorme de los 0-day de navegador. Ocurre cuando el programa libera un objeto con free() pero conserva un puntero a él (un dangling pointer, puntero colgante) y lo vuelve a usar más tarde. Entre la liberación y el reuso, el atacante consigue que el asignador entregue ese mismo bloque para otro objeto —de un tipo que él controla—. A partir de ahí, el puntero viejo y el objeto nuevo comparten memoria: leer por el puntero viejo filtra datos del objeto nuevo (un info-leak que derrota ASLR, como los que 3.15 necesitaba), y escribir por él corrompe el objeto nuevo. Si ese objeto contiene un puntero de función —una vtable de C++, por ejemplo—, el UAF se convierte directamente en control del flujo. El caso de IE11 que apareció en 3.7 (CVE-2017-0059) era exactamente esto.

Double-free. Liberar dos veces el mismo chunk. El efecto es que el bloque aparece dos veces en la lista de libres. Dos malloc() posteriores devuelven el mismo puntero para dos objetos distintos —otra vez, dos tipos sobre una misma memoria—, y, con la maniobra adecuada, permiten reescribir el fd de un chunk que sigue “en la lista” para envenenarla. Es la primitiva que habilita el tcache poisoning moderno, descrito abajo.

Type confusion. El programa trata una región de memoria como un tipo cuando en realidad contiene otro, normalmente por un UAF o por un error de casting. Si el tipo confundido interpreta como puntero de función lo que en realidad son datos controlados, el atacante gana ejecución sin haber tocado un solo metadato del asignador. Es endémico en motores de JavaScript, donde los objetos cambian de forma dinámicamente.

Explotación del tcache: la técnica dominante hoy#

Desde glibc 2.26, casi toda asignación pequeña pasa primero por el tcache, así que es ahí donde se libra la explotación moderna de heap en Linux. El tcache nació sin las validaciones que glibc había ido acumulando en los fast/small bins a lo largo de años de exploits, lo que lo volvió, durante varias versiones, el camino más simple.

Tcache poisoning. Es la técnica arquetípica y, cuando funciona, casi trivial. El tcache es una lista simplemente enlazada por el puntero fd guardado dentro de cada chunk libre. Si el atacante tiene una primitiva de escritura sobre un chunk que está en el tcache —vía UAF o heap overflow—, reescribe ese fd con una dirección elegida (target). El asignador, que confía ciegamente en el fd, sirve el primer malloc() desde el chunk real y el segundo malloc() desde target: devuelve al programa un puntero a una dirección que el atacante eligió. Si target es una estructura interesante, el siguiente uso de ese bloque escribe donde el atacante quiso. En dos asignaciones se pasa de “controlo un fd” a “malloc() me devuelve la dirección que yo diga”.

El tcache poisoning ilustra por qué esta familia se cierra en el diseño del asignador, no en el código de la aplicación: la aplicación puede no tener ningún strcpy inseguro y aun así ser explotable, porque el error está en que el asignador guarda su lista de libres en memoria que el bug de la aplicación deja escribir. La mitigación efectiva (safe-linking, más abajo) también vive en el asignador, no en la app.

Tcache double-free. Antes de glibc 2.29, liberar dos veces un chunk lo metía dos veces en la lista del tcache sin ninguna comprobación. Combinado con una reasignación intermedia, permitía reescribir el fd y lograr el mismo efecto que el poisoning, sin necesitar siquiera un overflow: bastaba el double-free. A partir de 2.29 glibc añadió una comprobación —un campo key en el chunk del tcache que delata el segundo free()—, que se puede sortear corrompiendo ese key, pero ya con más trabajo.

Fastbin dup (pre-tcache). El precursor histórico, para binarios con glibc anterior a 2.26 o cuando el tcache está lleno y el flujo cae a los fast bins. La única validación clásica del fast bin era que el campo size del chunk devuelto coincidiera con el del bin —de ahí la maniobra del fake chunk con un size falsificado que apunte a una zona con el valor correcto en el lugar justo—. Es más frágil que el tcache poisoning precisamente porque tiene esa comprobación de tamaño.

Unlink clásico. El ataque fundacional de la explotación de heap (Solar Designer, principios de los 2000), hoy sobre todo de valor didáctico. Los bins doblemente enlazados guardan fd y bk (forward y backward). Cuando un chunk se saca de la lista (unlink), el asignador ejecuta en esencia fd->bk = bk; bk->fd = fd —una escritura de un puntero controlado en una dirección controlada, si fd y bk están falsificados—. Es la primitiva write-what-where en su forma más pura. Glibc lo mató hace más de una década con las comprobaciones de integridad de la lista (corrupted double-linked list), que verifican fd->bk == chunk && bk->fd == chunk antes de desenlazar; entenderlo sigue siendo la mejor puerta de entrada conceptual a por qué los metadatos son el objetivo.

De la primitiva a la ejecución#

Todas las técnicas anteriores convergen en lo mismo: una escritura arbitraria, la capacidad de poner un valor elegido en una dirección elegida. Falta el último paso —convertir esa escritura en ejecución de código—, y depende de la versión de glibc, porque los objetivos históricos se fueron cerrando.

  • __malloc_hook / __free_hook (histórico, hasta glibc 2.33). glibc exponía punteros de función globales que malloc()/free() invocaban si no eran nulos, pensados para depuración. Sobrescribir __free_hook con la dirección de system y liberar un chunk que contuviera la cadena "/bin/sh" ejecutaba system("/bin/sh"). Era el objetivo clásico por su simplicidad; glibc 2.34 eliminó los hooks justamente porque eran un blanco tan cómodo.
  • La GOT (Global Offset Table). Igual que en la explotación de pila de 3.15, si el binario no tiene Full RELRO, su GOT es escribible: sobrescribir la entrada de una función de biblioteca con otra dirección redirige la próxima llamada. La escritura arbitraria del heap alcanza la GOT tan bien como una de la pila.
  • Punteros de función de la aplicación: vtables de C++, callbacks registrados, tablas de despacho. Es el objetivo natural cuando el UAF o la type confusion ya dieron control sobre un objeto con métodos virtuales.
  • Estructuras de la propia glibc: con los hooks muertos, la explotación moderna pivota a objetivos como la estructura _IO_FILE de los streams de stdio (la técnica FSOP, File Stream Oriented Programming), que contienen vtables invocadas durante operaciones de E/S o al salir el programa.

Sobrescrito un puntero de función, el flujo pasa al terreno del capítulo 3.7: rara vez se salta a shellcode —NX/DEP lo impide igual que en la pila—, sino que se pivota a una cadena ROP. La escritura controla RIP; un stack pivot (un gadget que mueve RSP a una región controlada, típicamente el propio heap donde se preparó la cadena) entrega el control a los gadgets de 3.7, que llaman a mprotect para hacer ejecutable una región o directamente a execve. El heap aporta la primitiva de escritura; el ROP de 3.7 aporta la ejecución. Son dos mitades del mismo exploit.

Mitigaciones modernas: por qué el heap sigue vivo#

La explotación de heap es una carrera larga entre técnicas y contramedidas, y a diferencia de la pila —donde canary + NX + ASLR + CET forman una defensa madura— el asignador se endureció más tarde y de forma más desigual.

  • Comprobaciones de integridad: las de lista doblemente enlazada que mataron el unlink clásico (corrupted double-linked list), la validación de size de los fast bins, y el campo key del tcache (glibc 2.29) contra el double-free. Cada una cerró una técnica concreta.
  • Safe-linking (glibc 2.32, 2020). La respuesta directa al tcache/fastbin poisoning. En vez de guardar el puntero fd en claro, glibc lo almacena ofuscado: fd XOR (dirección_del_chunk >> 12). Como el atacante que quiere envenenar la lista debe escribir un fd que apunte a target, ahora necesita conocer la dirección del chunk (que ASLR oculta) para calcular el valor ofuscado correcto. No es infalible —un info-leak de la dirección del chunk lo derrota, y hay comprobaciones de alineación que sortear—, pero eleva sustancialmente el costo del poisoning ciego.
  • Pointer guard y pointer mangling: glibc cifra ciertos punteros sensibles (los de las estructuras de gestión de hilos, los atexit) con un valor secreto por proceso, para que sobrescribirlos con una dirección útil requiera conocer el secreto.

Y sin embargo el heap sigue siendo la superficie de explotación dominante en los objetivos que importan. La razón es doble. Primero, el volumen: navegadores, kernels y motores de scripting son millones de líneas de C/C++ que gestionan objetos con ciclos de vida complejos, y el UAF y la type confusion nacen de errores de lógica de ciclo de vida que ninguna comprobación del asignador puede prevenir —el asignador no sabe que la aplicación conservó un puntero colgante—. Segundo, la diversidad de asignadores: cada uno (el tcache de glibc, PartitionAlloc, el Segment Heap de Windows, el kmalloc/SLUB del kernel de Linux) tiene sus propios metadatos y sus propias grietas, así que el conocimiento se fragmenta y las mitigaciones no se transfieren. Por eso los programas de bug bounty de navegador siguen pagando sumas de seis cifras por cadenas que empiezan en un UAF de heap.

Bajo el capó: el heap de Windows (NT Heap, LFH, Segment Heap)#

Todo el cuerpo de este capítulo se apoyó en ptmalloc2 porque su diseño es público y sus técnicas ilustran el principio con la máxima claridad; pero el Panorama prometió los asignadores de Windows, y merecen abrirse —no solo porque Windows es el objetivo dominante, sino porque su historia cuenta la misma tesis (el asignador confía en sus metadatos) con un mecanismo distinto y endurecido antes, y porque explica por qué «el heap es determinista», una verdad de glibc, es más débil en el Windows moderno.

El NT Heap y el write-4 clásico: el eco del unlink. El heap heredado de Windows lleva la contabilidad de los chunks libres en FreeLists (listas doblemente enlazadas, indexadas por tamaño, con FreeList[0] para los grandes), y cada chunk arrastra su cabecera en línea, igual que glibc. El ataque fundacional es el análogo exacto del unlink de la sección anterior: desbordar un chunk ocupado para corromper los punteros Flink/Blink de un chunk libre vecino, de modo que cuando el asignador lo desenlaza —al consolidar en un free/malloc— ejecuta en esencia Flink->Blink = Blink; Blink->Flink = Flink, una escritura de una DWORD arbitraria en una dirección arbitraria (el clásico write-4), dirigida históricamente a un puntero de función como el Unhandled Exception Filter o un puntero de bloqueo en el PEB. Misma primitiva, misma raíz: la lista de libres vive en memoria escribible y el asignador confía en ella.

Windows cerró los metadatos antes que glibc. La diferencia es cronológica: Windows endureció su asignador una década antes. XP SP2 añadió una cookie de heap y el safe unlinking —validar Flink->Blink == chunk y Blink->Flink == chunk antes de desenlazar, exactamente la comprobación que glibc sumaría mucho después contra la corrupted double-linked list—; y Vista pasó a codificar la cabecera entera del chunk (XOR con una clave aleatoria por heap), de modo que un atacante que desborda una cabecera sin conocer la clave produce basura que la validación rechaza. La consecuencia es un espejo del arco glibc-tras-los-hooks: los ataques a los metadatos del NT Heap murieron en gran parte, y la explotación de heap en Windows se corrió hacia los datos de la aplicación —sobrescribir un puntero de función o una vtable de C++ que vive dentro de un objeto del heap, el mismo giro data-only que la sección glibc describió—.

La LFH: la aleatorización que ataca el determinismo. El front-end de rendimiento, el Low Fragmentation Heap (LFH), se activa por bucket de tamaño cuando se acumulan muchas asignaciones del mismo tamaño, y las sirve desde subsegmentos. Su relevancia de seguridad es el contrapunto directo del apartado «por qué el heap es determinista pese a ASLR» de este capítulo: desde Windows 8, las asignaciones de la LFH dentro de un bucket están aleatorizadas, así que la disposición relativa determinista de la que depende el grooming ya no está garantizada. El atacante debe rociar (spray) de forma más agresiva y aceptar una ubicación probabilística —el grooming en el Windows moderno pelea contra la aleatorización del asignador, no solo contra ASLR—. Es el mecanismo detrás de que los heap sprays de Windows sean más grandes y más ruidosos.

El Segment Heap (Windows 10+) volvió a cambiar el tablero. Windows 10 introdujo el Segment Heap, el asignador por defecto de muchos procesos del sistema y de las apps empaquetadas (Store/UWP), con una arquitectura distinta —un back-end por segmentos servido desde VirtualAlloc, un asignador de tamaño variable (VS) y un front-end LFH—, con más páginas de guarda, metadatos codificados y sus propios invariantes de integridad. La consecuencia práctica para la ofensiva es que el heap de Windows no es un objetivo sino varios, dependientes de la versión y del proceso (¿es el NT Heap heredado o el Segment Heap? ¿está la LFH activa para este bucket?), lo que fragmenta el conocimiento igual que el capítulo señaló para el ecosistema de asignadores. La lección unificadora se sostiene: cada asignador es una máquina de estados que confía en sus propios metadatos; Windows simplemente endureció esa confianza antes y por un camino distinto al de glibc, así que el juego moderno en Windows —como el de glibc tras la muerte de los hooks— es la corrupción data-only de los objetos que el asignador entrega. Y cuanto más abajo se baja —hacia el pool del kernel, que se empareja con el SLUB de 3.21— más se repite la misma historia. El cruce con la explotación de user-land de Windows en su conjunto es 3.8.

Defensa y detección#

La incomodidad que define el cierre púrpura de este capítulo es que la explotación de memoria se detecta mal en tiempo de ejecución. Un tcache poisoning no genera un log, no lanza un proceso hijo anómalo, no toca el disco: es una escritura a una dirección de memoria dentro de un proceso que ya se estaba ejecutando. Las señales que el resto del manual usa —Event ID 4688 / Sysmon 1 por creación de proceso, auditd/execve en Linux, la relación padre-hijo anómala— solo aparecen después, cuando el exploit ya pivotó a ROP y llama a execve o levanta un reverse shell, momento en que se cruza con la detección de ejecución anómala del lado blue (correlación en 3.15 y con el SIEM). La corrupción en sí es casi invisible al SOC. De ahí que la defensa contra el heap sea, más que en ningún otro capítulo del track, preventiva y de plataforma, no detectiva:

  • Eliminar la clase, no el bug: reescribir los componentes expuestos (parsers, motores de red) en lenguajes con seguridad de memoria (Rust, Go) elimina de raíz UAF, double-free y overflow. Es la recomendación estructural de agencias como CISA y la NSA para código nuevo de alto riesgo, y la razón por la que los navegadores migran componentes críticos a Rust.
  • Asignadores endurecidos: hardened_malloc (de GrapheneOS) y las variantes guard aíslan metadatos fuera de línea, insertan páginas de guarda y aleatorizan la ubicación de los chunks, encareciendo el grooming. GWP-ASan (Chrome, Android) es un detector probabilístico y de bajo costo que en producción atrapa una fracción de los UAF/overflow con páginas de guarda muestreadas, convirtiendo un exploit silencioso en un crash reportado.
  • Mitigaciones de flujo por hardware: Intel CET (shadow stack + indirect branch tracking) y CFG (Control Flow Guard) en Windows rompen el último eslabón —el pivote a ROP y el salto a un puntero de función sobrescrito— al validar los destinos de retorno y de llamada indirecta, igual que hacen contra el ROP de pila de 3.7. No previenen la corrupción, pero cortan su explotación.
  • Mantener glibc/el asignador al día: safe-linking, la muerte de los hooks (2.34) y el campo key del tcache solo protegen si el sistema los tiene. Un binario enlazado contra una glibc vieja conserva los objetivos fáciles.
  • Cazarlo antes de producción con fuzzing: la defensa más rentable no es en runtime sino en el ciclo de desarrollo. AddressSanitizer (ASan) instrumenta el binario para detectar UAF, double-free y overflow en el instante en que ocurren, con stack traces precisos; combinado con fuzzing (AFL++, libFuzzer) que genera entradas hasta disparar la corrupción, encuentra estos bugs antes de que salgan al mundo. Es la misma lógica que cierra 3.15: la explotación de memoria se combate mejor eliminando el bug en el código que detectando el exploit en la red.

En síntesis: contra el heap, el orden de prioridad se invierte respecto de la pila. Ahí la telemetría de crash delataba los intentos; aquí el intento es mudo, y la inversión —seguridad de memoria en el lenguaje, asignador endurecido, hardware de flujo, y fuzzing en el pipeline— es lo que efectivamente cierra la clase. La detección en el SIEM entra tarde, en la fase de ejecución, y es la red de seguridad, no la primera línea.

Referencias#

  • red-infra/grayhat/cap-03 — Catálogo de mitigaciones de memoria (ASLR/NX/canary y su alcance stack-vs-heap); el ancla que conecta este capítulo con el resto del track binario.
  • Phrack #57, Vudo malloc tricks (Michel «MaXX» Kaempf, 2001) y Once upon a free() — los textos fundacionales del unlink clásico.
  • glibc malloc/malloc.c — la fuente autoritativa del comportamiento de ptmalloc2 (tcache, bins, safe-linking).
  • how2heap (Shellphish) — colección de PoC didácticos por técnica y por versión de glibc; la referencia práctica de facto para entender cada primitiva.
  • Chris Anley et al., The Shellcoder’s Handbook, 2ª ed. — capítulos de heap overflow como base histórica.
  • Windows Internals (Yosifovich, Russinovich et al.) y Chris Valasek, Windows 8 Heap Internals — el NT Heap, la codificación de cabecera (XP SP2/Vista), la LFH y su aleatorización, y el Segment Heap de Windows 10. Nativo para la sección «Bajo el capó» (el cuerpo del capítulo, ptmalloc2, no cubre la plataforma Windows).
  • MITRE ATT&CK — T1203 Exploitation for Client Execution; CWE-416 (Use After Free), CWE-415 (Double Free), CWE-122 (Heap-based Buffer Overflow), CWE-843 (Type Confusion).
  • CISA/NSA, The Case for Memory Safe Roadmaps (2023) — el argumento de plataforma para eliminar la clase.