Módulo 7 — Windows Mitigations and bypass techniques¶
← Volver al índice¶
Resumen: este módulo introduce las mitigaciones modernas de explotación kernel en Windows y cómo pensarlas desde el punto de vista ofensivo:
KASLR,CFG/KCFG,SMEP,SMAP,HVCI,KCET, el cambio fuerte de Windows 1124H2sobre leaks de kernel base, la vulnerable driver blocklist, y los patrones de bypass más comunes: primero conseguir un leak, después construirROP/gadgets, deshabilitar o esquivarSMEP, o directamente hacer ataques data-only sobre estructuras kernel como_EPROCESSy_TOKEN. También se estudia cómo ubicar y modificar privilegios dentro de un token (_EX_FAST_REF,_TOKEN,_SEP_TOKEN_PRIVILEGES) con WinDbg.
Tabla de Contenidos¶
- Contexto del módulo
- Mapa rápido de mitigaciones
KASLR— Kernel Address Space Layout RandomizationCFG,KCFGyKCETSMEP— Supervisor Mode Execution PreventionSMAP— Supervisor Mode Access PreventionHVCI— Hypervisor-Protected Code Integrity- Mitigaciones antes y después de Windows
24H2 - Microsoft Vulnerable Driver Blocklist
- El fix que no fue:
CVE-2025-53136 - Patrones de bypass
- Race conditions como amenaza moderna:
CVE-2025-62215 - Por qué
SMEP/SMAPno frenan un ataque data-only - Encontrar
_EPROCESSy el token de un proceso _EX_FAST_REF: puntero + refcount en el token- Inspeccionar
_TOKENy_SEP_TOKEN_PRIVILEGES - Modificar privilegios con
eq - Tabla de privilege bits
- Validar el cambio con
!tokenywhoami /priv - Workflow mental del módulo
- Cheat sheet
- Glosario corto
Clase 1 apuntes¶
1. Contexto del módulo¶
El módulo 7 cambia el foco: ya no alcanza con encontrar un bug kernel y lograr corrupción. En Windows moderno la explotación tiene que contestar varias preguntas:
1. ¿Cómo encuentro direcciones kernel si KASLR oculta bases?
2. ¿Puedo redirigir control flow si CFG/KCFG validan destinos indirectos?
3. ¿Puedo ejecutar shellcode user-mode desde ring 0 si SMEP está activo?
4. ¿Puedo leer/escribir user pages desde kernel si SMAP estuviera activo?
5. ¿Puedo cargar o abusar un driver vulnerable si existe blocklist?
6. ¿Hace falta ejecutar código o alcanza con tocar data kernel?
La idea central: las mitigaciones no eliminan la explotación, cambian el tipo de primitiva que conviene construir.
Dos familias grandes:
| Familia | Idea |
|---|---|
| Control-flow attack | ganar RIP/EIP kernel, armar ROP, saltar a gadgets, deshabilitar SMEP, etc. |
| Data-only attack | no ejecutar código nuevo; modificar estructuras como _TOKEN, _EPROCESS, privilegios, punteros o flags |
2. Mapa rápido de mitigaciones¶

| Mitigación | Qué intenta frenar | Idea ofensiva típica |
|---|---|---|
KASLR |
no saber direcciones kernel | primero conseguir leak de base/punteros |
CFG |
llamadas indirectas a destinos no válidos | usar destinos válidos o data-only |
KCFG |
versión kernel-mode de CFG |
evitar control-flow arbitrario o usar gadgets válidos |
KCET |
shadow stack kernel | complica ROP/ret hijacking clásico |
SMEP |
kernel ejecutando páginas user | ROP para deshabilitar SMEP o data-only |
SMAP |
kernel accediendo páginas user | en Windows está soportado por hardware pero no enabled default |
HVCI |
integridad de código protegida por hypervisor | evitar cargar/ejecutar código no íntegro; data-only/BYOVD más complejo |
| Driver blocklist | cargar drivers vulnerables conocidos | buscar drivers no bloqueados, variantes, configuración vulnerable |
3. KASLR — Kernel Address Space Layout Randomization¶
KASLR randomiza direcciones base de código y data kernel al boot.
sin KASLR:
ntoskrnl.exe siempre en base conocida
gadgets / variables globales / funciones con direcciones fijas
con KASLR:
ntoskrnl.exe, drivers y regiones kernel cambian por boot
necesitás un leak antes de usar direcciones absolutas
Qué rompe:
- hardcodear
nt!PsInitialSystemProcess; - hardcodear gadgets de
ntoskrnl.exe; - usar offsets absolutos de drivers;
- cadenas
ROPsin leak previo.
Lo que no rompe:
- corrupción data-only si ya tenés punteros válidos;
- offsets relativos dentro de estructuras (
_EPROCESS.Token,_TOKEN.Privileges, etc.); - explotación que primero obtiene una dirección kernel y calcula todo relativo a esa base.
Modelo mental:
leak kernel pointer
-> identificar módulo / base
-> base + offset gadget/función/global
-> usar primitive de read/write o ROP
4. CFG, KCFG y KCET¶
CFG — Control Flow Guard¶
CFG valida targets de llamadas indirectas en runtime.
Objetivo: impedir que una corrupción de puntero de función salte a cualquier dirección arbitraria.
KCFG¶
KCFG aplica la idea de CFG al kernel-mode. Es relevante cuando el exploit quiere abusar de:
- function pointers;
- callbacks;
- vtables;
- dispatch tables;
- indirect calls dentro de drivers.
Bypass conceptual:
no saltar a cualquier address,
sino a un target válido que igual sirva para la cadena,
o evitar control-flow hijack y hacer data-only.
KCET¶
KCET es CET para kernel-mode: shadow stack / protección contra manipulación clásica de returns.
Qué complica:
ROPbasado enret;- stack pivot + chain clásica;
- sobrescribir return address y volver feliz.
Qué empuja a buscar:
- data-only;
- corrupción de objetos/callbacks compatibles con validaciones;
- primitivas de read/write sobre estructuras sensibles;
- bugs de lógica o race donde no hace falta ejecutar shellcode.
5. SMEP — Supervisor Mode Execution Prevention¶
SMEP impide que el kernel ejecute páginas user-mode.
Antes de SMEP, una técnica clásica era ret2user:
1. poner shellcode en user-mode
2. corromper RIP kernel
3. hacer que kernel salte al shellcode user-mode
4. shellcode corre en ring 0
Con SMEP activo:
Bypass típico de control-flow:
kernel bug
-> leak KASLR
-> ROP chain en kernel gadgets
-> modificar CR4 y apagar SMEP
-> saltar a shellcode / seguir ROP
-> SYSTEM
Pero el módulo remarca algo importante: si el exploit es data-only, SMEP no ayuda, porque nunca se intenta ejecutar código user-mode.
6. SMAP — Supervisor Mode Access Prevention¶
SMAP impide que el kernel lea/escriba páginas user-mode arbitrariamente.
Diferencia con SMEP:
| Mitigación | Bloquea |
|---|---|
SMEP |
ejecución de user pages desde kernel |
SMAP |
lectura/escritura de user pages desde kernel |
Punto crítico de las slides:
Por eso muchas rutas METHOD_NEITHER / user pointers siguen siendo relevantes: Windows depende de validaciones explícitas (ProbeForRead, ProbeForWrite, try/except, checks de modo) en vez de tener SMAP cortando todo por hardware.
7. HVCI — Hypervisor-Protected Code Integrity¶
HVCI usa virtualización para reforzar code integrity.
Idea:
VBS / hypervisor
-> protege decisiones de integridad de código
-> hace más difícil ejecutar o cargar código kernel no autorizado
Impacto ofensivo:
- complica drivers unsigned o manipulación de código;
- reduce utilidad de payloads que dependen de patching directo de código kernel;
- empuja a BYOVD con drivers firmados y no bloqueados;
- empuja a data-only sobre estructuras ya existentes.
No significa que no haya explotación: significa que la cadena tiene que evitar violar code integrity o usar un camino donde el código vulnerable ya está cargado y firmado.
8. Mitigaciones antes y después de Windows 24H2¶

Antes de Windows 24H2¶
Había leaks cómodos:
NtQuerySystemInformation(SystemModuleInformation)
-> leakeaba bases de módulos kernel
-> cualquier usuario podía consultarlo
Esto hacía que el bypass de KASLR fuera casi un trámite:
También se menciona un PTE_BASE hardcodeado:
Esa clase de constantes históricas facilitaba introspección y ataques sobre page tables.
Después de Windows 24H2¶
Cambio importante:
Y SeDebugPrivilege queda restringido a procesos administrativos.
Consecuencia:
Entonces un exploit moderno low-priv necesita otro leak.
9. Microsoft Vulnerable Driver Blocklist¶
La blocklist intenta impedir que drivers vulnerables conocidos carguen.
BYOVD clásico:
cargar driver firmado pero vulnerable
usar IOCTL para read/write kernel
elevar privilegios / desactivar defensas
blocklist:
si el hash/cert/driver está listado -> Windows bloquea la carga
Límites conceptuales:
- bloquea conocidos, no todos los vulnerables;
- depende de configuración y versión;
- puede haber variantes no listadas;
- no arregla drivers vulnerables ya presentes/cargados;
- no frena bugs en drivers de Microsoft o del propio sistema.
Regla práctica:
blocklist reduce la superficie BYOVD obvia,
pero no reemplaza hardening de drivers ni elimina EoP locales.
10. El fix que no fue: CVE-2025-53136¶

Las slides presentan un caso donde la mitigación de 24H2 fue bypassed a los pocos meses.
Qué pasó¶
Un patch de 2024 para CVE-2024-43511 (bug TOCTOU) modificó:
El nuevo código, por un momento breve, escribía un puntero kernel a TOKEN dentro de un buffer controlado por user-mode.
Con ganar una carrera de lectura alcanzaba para capturar ese puntero.
Impacto¶
Encadenado con un write-what-where:
leak TOKEN pointer
-> calcular/ubicar estructuras
-> primitive write-what-where
-> full LPE en build 24H2 actual
Lección¶
mitigations close known doors, not all doors
cada patch nuevo es superficie de ataque nueva para los patches anteriores
La idea importante: Microsoft cerró el leak obvio (SystemModuleInformation), pero otro cambio introdujo un leak distinto.
11. Patrones de bypass¶

1. ROP chain en kernel¶
Kernel vulnerability
-> control RIP / stack pivot
-> ROP chain con gadgets kernel
-> disable SMEP
-> shellcode / token stealing
-> SYSTEM
Requisitos:
- bug que permita control-flow hijack;
- leak para
KASLR; - gadgets válidos;
- lidiar con
KCFG/KCETsi aplican.
2. KASLR bypass first¶
El leak es el primer ladrillo. Sin leak, no sabés dónde están gadgets/funciones/globales.
3. Data-only attacks¶
Ejemplos:
- cambiar
_EPROCESS.Token; - modificar
_TOKEN.Privileges; - cambiar flags de protección;
- manipular listas o referencias;
- corromper objetos para conseguir read/write.
Ventaja: SMEP no ayuda porque no hay ejecución de páginas user.
12. Race conditions como amenaza moderna: CVE-2025-62215¶

Las slides usan CVE-2025-62215 como ejemplo real de race condition kernel parcheada en noviembre de 2025.
Características:
| Campo | Valor |
|---|---|
| Tipo | privilege escalation vía race condition |
| Acceso | local only |
| Privilegios | low privileges / cualquier usuario |
| Complejidad | alta |
| Resultado | kernel memory corruption -> SYSTEM |
Flujo conceptual:
La conclusión es que en builds modernos siguen apareciendo bugs explotables. Las mitigaciones suben la complejidad, pero las race conditions y UAFs siguen siendo amenazas principales.
13. Por qué SMEP/SMAP no frenan un ataque data-only¶

El caso moderno del módulo se apoya en data-only:
Por qué SMEP no ayuda¶
SMEP sólo bloquea ejecución de user pages desde kernel.
Por qué SMAP podría ayudar, pero no está default¶
SMAP bloquearía accesos kernel a páginas user, pero la slide remarca:
Entonces muchos bugs siguen dependiendo de checks de software.
Observaciones clave¶
- siguen existiendo vulnerabilidades en
24H2/25H2; - mitigaciones hacen la explotación más difícil, no imposible;
- race conditions y UAF siguen siendo de las clases más relevantes;
- si el exploit logra una primitiva de write kernel, puede atacar data y evitar varias defensas de control-flow.
14. Encontrar _EPROCESS y el token de un proceso¶

La segunda mitad del módulo baja a WinDbg y token internals.
Buscar el proceso por PID¶
Ejemplo del slide:
0: kd> !process 1a34 0 ; 1a34 es el PID
Searching for Process with Cid == 1a34
PROCESS ffff87869c4490c0 ; este es el EPROCESS
SessionId: 1 Cid: 1a34 Peb: f08083000 ParentCid: 03e8
DirBase: b3e0c002 ObjectTable: ffff9d07a63d0600 HandleCount: 98
Image: cmd.exe
Ubicar el campo Token¶
En esa build:
Con recursión:
kd> dt -r ntkrnlmp!_EPROCESS ffff87869c4490c0
+0x4b8 Token : _EX_FAST_REF
+0x000 Object : 0xffff9d07`a6b6d776 Void
+0x000 RefCnt : 0y0110
+0x000 Value : 0xffff9d07`a6b6d776
15. _EX_FAST_REF: puntero + refcount en el token¶

_EPROCESS.Token no es directamente un puntero plano: es _EX_FAST_REF.
La idea:
Como los objetos kernel están alineados, los bits bajos pueden almacenar refcount. Para obtener el puntero real al _TOKEN, se limpian los bits bajos:
Ejemplo del slide:
Después:
16. Inspeccionar _TOKEN y _SEP_TOKEN_PRIVILEGES¶

Primero se ve el token con !token:
Después se dumpea la estructura:
0: kd> dt ntkrnlmp!_TOKEN ffff9d07`a6b6d770
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER 0x7fffffff`ffffffff
+0x030 TokenLock : 0xffff8786`9c35ce90 _ERESOURCE
+0x038 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
Campo clave:
Estructura:
_SEP_TOKEN_PRIVILEGES
+0x000 Present : uint64
+0x008 Enabled : uint64
+0x010 EnabledByDefault : uint64
Significado:
| Campo | Qué indica |
|---|---|
Present |
privilegios que existen en el token |
Enabled |
privilegios activos ahora |
EnabledByDefault |
privilegios que arrancan enabled por default |
17. Modificar privilegios con eq¶

En el ejemplo, la dirección de _SEP_TOKEN_PRIVILEGES es:
Antes:
Comandos usados:
Después:
Qué cambió:
Esa diferencia agrega el bit de SeDebugPrivilege (20). En hexadecimal:
Por eso después SeDebugPrivilege aparece como enabled.
Nota práctica: esto es una demo con WinDbg escribiendo memoria kernel. En un exploit real, el equivalente sería usar una primitiva de kernel write para cambiar esos QWORDs.
18. Tabla de privilege bits¶

La slide 13 es una tabla visual de nombres de privilegios y número de bit. Los más relevantes para explotación/debugging:
| Constante | Privilegio | Bit |
|---|---|---|
SE_CREATE_TOKEN_NAME |
SeCreateTokenPrivilege |
2 |
SE_ASSIGNPRIMARYTOKEN_NAME |
SeAssignPrimaryTokenPrivilege |
3 |
SE_LOCK_MEMORY_NAME |
SeLockMemoryPrivilege |
4 |
SE_INCREASE_QUOTA_NAME |
SeIncreaseQuotaPrivilege |
5 |
SE_MACHINE_ACCOUNT_NAME |
SeMachineAccountPrivilege |
6 |
SE_TCB_NAME |
SeTcbPrivilege |
7 |
SE_SECURITY_NAME |
SeSecurityPrivilege |
8 |
SE_TAKE_OWNERSHIP_NAME |
SeTakeOwnershipPrivilege |
9 |
SE_LOAD_DRIVER_NAME |
SeLoadDriverPrivilege |
10 |
SE_SYSTEM_PROFILE_NAME |
SeSystemProfilePrivilege |
11 |
SE_SYSTEMTIME_NAME |
SeSystemtimePrivilege |
12 |
SE_PROF_SINGLE_PROCESS_NAME |
SeProfileSingleProcessPrivilege |
13 |
SE_INC_BASE_PRIORITY_NAME |
SeIncreaseBasePriorityPrivilege |
14 |
SE_CREATE_PAGEFILE_NAME |
SeCreatePagefilePrivilege |
15 |
SE_CREATE_PERMANENT_NAME |
SeCreatePermanentPrivilege |
16 |
SE_BACKUP_NAME |
SeBackupPrivilege |
17 |
SE_RESTORE_NAME |
SeRestorePrivilege |
18 |
SE_SHUTDOWN_NAME |
SeShutdownPrivilege |
19 |
SE_DEBUG_NAME |
SeDebugPrivilege |
20 |
SE_AUDIT_NAME |
SeAuditPrivilege |
21 |
SE_SYSTEM_ENVIRONMENT_NAME |
SeSystemEnvironmentPrivilege |
22 |
SE_CHANGE_NOTIFY_NAME |
SeChangeNotifyPrivilege |
23 |
SE_REMOTE_SHUTDOWN_NAME |
SeRemoteShutdownPrivilege |
24 |
SE_UNDOCK_NAME |
SeUndockPrivilege |
25 |
SE_SYNC_AGENT_NAME |
SeSyncAgentPrivilege |
26 |
SE_ENABLE_DELEGATION_NAME |
SeEnableDelegationPrivilege |
27 |
SE_MANAGE_VOLUME_NAME |
SeManageVolumePrivilege |
28 |
SE_IMPERSONATE_NAME |
SeImpersonatePrivilege |
29 |
SE_CREATE_GLOBAL_NAME |
SeCreateGlobalPrivilege |
30 |
SE_TRUSTED_CREDMAN_ACCESS_NAME |
SeTrustedCredManAccessPrivilege |
31 |
SE_RELABEL_NAME |
SeRelabelPrivilege |
32 |
SE_INC_WORKING_SET_NAME |
SeIncreaseWorkingSetPrivilege |
33 |
SE_TIME_ZONE_NAME |
SeTimeZonePrivilege |
34 |
SE_CREATE_SYMBOLIC_LINK_NAME |
SeCreateSymbolicLinkPrivilege |
35 |
SE_DELEGATE_SESSION_USER_IMPERSONATE_NAME |
SeDelegateSessionUserImpersonatePrivilege |
36 |
Regla para prender un privilegio:
Ejemplo para SeDebugPrivilege:
19. Validar el cambio con !token y whoami /priv¶

Después de modificar el token, se valida de dos formas.
En kernel debugger¶
El output muestra:
En user-mode¶
Salida relevante:
Privilege Name Description State
==================== =============== =======
SeDebugPrivilege Debug programs Enabled
Esto confirma que la escritura sobre _SEP_TOKEN_PRIVILEGES tuvo efecto visible en el proceso.
20. Workflow mental del módulo¶
1. Identificar mitigaciones presentes: KASLR, CFG/KCFG, SMEP, HVCI, KCET, blocklist
2. Preguntar si el exploit necesita control-flow o puede ser data-only
3. Si necesita gadgets -> primero conseguir leak de KASLR
4. En 24H2, SystemModuleInformation ya no da kernel base sin SeDebugPrivilege
5. Buscar leaks alternativos: bugs nuevos, races, TOCTOU, UAF, info leaks
6. Si hay control RIP -> ROP en kernel, no ret2user directo por SMEP
7. Si hay write kernel -> preferir data-only sobre _TOKEN/_EPROCESS
8. Encontrar proceso target con !process <pid> 0
9. Leer _EPROCESS.Token en offset de la build (ej. 0x4b8)
10. Limpiar bits bajos de _EX_FAST_REF: token = Value & ~0xF
11. Dump _TOKEN y ubicar _SEP_TOKEN_PRIVILEGES en +0x40
12. Calcular bit del privilegio deseado (ej. SeDebugPrivilege = 20)
13. Present |= 1 << bit; Enabled |= 1 << bit
14. Escribir QWORDs con primitive kernel write o eq en WinDbg
15. Validar con !token y whoami /priv
21. Cheat sheet¶
Mitigaciones¶
| Mitigación | Bit / dato | Nota |
|---|---|---|
SMEP |
CR4 bit 20 |
bloquea ejecución de user pages desde kernel |
SMAP |
CR4 bit 21 |
bloquea access a user pages desde kernel; no enabled default en Windows |
KASLR |
base random por boot | requiere info leak |
CFG |
user/control-flow | valida indirect calls |
KCFG |
kernel/control-flow | versión kernel de CFG |
KCET |
shadow stack | complica ROP basado en returns |
HVCI |
VBS/code integrity | complica código kernel no íntegro |
Windows 24H2¶
Antes:
NtQuerySystemInformation(SystemModuleInformation) -> kernel base para cualquier user
Después:
kernel bases suprimidas si no hay SeDebugPrivilege
SeDebugPrivilege restringido a procesos administrativos
WinDbg token commands¶
!process <pid> 0
dt ntkrnlmp!_EPROCESS Token <EPROCESS>
dt -r ntkrnlmp!_EPROCESS <EPROCESS>
; limpiar low nibble del _EX_FAST_REF
; token = value & ~0xF
!token <TOKEN>
dt ntkrnlmp!_TOKEN <TOKEN>
Offsets de la demo¶
| Estructura | Campo | Offset |
|---|---|---|
_EPROCESS |
Token |
0x4b8 |
_TOKEN |
Privileges |
0x40 |
_SEP_TOKEN_PRIVILEGES |
Present |
0x0 |
_SEP_TOKEN_PRIVILEGES |
Enabled |
0x8 |
_SEP_TOKEN_PRIVILEGES |
EnabledByDefault |
0x10 |
Direcciones de la demo¶
EPROCESS cmd.exe: ffff87869c4490c0
_EX_FAST_REF Value: ffff9d07`a6b6d776
TOKEN real alineado: ffff9d07`a6b6d770
Privileges struct: ffff9d07`a6b6d7b0
Activar SeDebugPrivilege en la demo¶
bit(SeDebugPrivilege) = 20
mask = 1 << 20 = 0x100000
antes:
Present = 0x602880000
Enabled = 0x800000
después:
Present = 0x602980000
Enabled = 0x900000
WinDbg:
eq ffff9d07`a6b6d7b0 602980000
eq ffff9d07`a6b6d7b8 900000
Patrones de bypass¶
KASLR bypass:
info leak -> base -> offsets/gadgets
SMEP bypass clásico:
ROP kernel -> clear CR4 bit 20 -> ret2user/shellcode
Mitigation-agnostic:
data-only -> modify TOKEN/EPROCESS -> no shellcode
22. Glosario corto¶
| Término | Descripción |
|---|---|
KASLR |
Randomización de bases kernel; obliga a conseguir leaks. |
CFG |
Control Flow Guard; valida targets indirectos. |
KCFG |
CFG aplicado al kernel. |
KCET |
CET/shadow stack en kernel, complica ROP clásico. |
SMEP |
Impide ejecutar user pages desde kernel; CR4 bit 20. |
SMAP |
Impide leer/escribir user pages desde kernel; CR4 bit 21, no default en Windows. |
HVCI |
Code integrity protegida por hypervisor. |
| BYOVD | Bring Your Own Vulnerable Driver; cargar/abusar driver firmado vulnerable. |
| Vulnerable Driver Blocklist | Lista de drivers vulnerables conocidos que Windows bloquea. |
| Data-only attack | Exploit que sólo modifica datos kernel, sin ejecutar shellcode. |
_EPROCESS |
Estructura kernel de proceso; contiene el campo Token. |
_EX_FAST_REF |
Empaqueta puntero + refcount en bits bajos. |
_TOKEN |
Objeto token de seguridad del proceso/thread. |
_SEP_TOKEN_PRIVILEGES |
Bitmasks de privilegios presentes/enabled/default del token. |
SeDebugPrivilege |
Privilegio de debugging; bit 20, requerido para ciertos leaks post-24H2. |
eq |
Comando WinDbg para escribir un QWORD en memoria. |
Apunte basado en el PDF Module_07_Windows_Mitigations_and_bypass_techniques.pdf y en la inspección visual de sus slides renderizados. El módulo resume mitigaciones kernel modernas, sus límites, patrones de bypass y una demo práctica de manipulación de privilegios en _TOKEN para habilitar SeDebugPrivilege.