Next-Gen Reverse Engineering Training¶
Módulo 5: Explotación II — Fundamentos del Kernel de Windows¶
Objetivo del módulo: Comprender la arquitectura del kernel de Windows, cómo se establece y se depura desde WinDbg, los componentes principales del kernel-mode, el modelo de drivers (WDM), las estructuras internas más relevantes (KPCR, KPRCB, KTHREAD, KAPC_STATE, KPROCESS/EPROCESS), la comunicación user-kernel mediante IRPs e IOCTLs, y la técnica clásica de token stealing para obtener privilegios SYSTEM.
Tabla de Contenidos¶
- BinDiff — Instalación y uso
- Memoria virtual
- Layout del espacio de direcciones x64
- User mode vs Kernel mode
- Setup y depuración del kernel de Windows
- Lab Setup — VM target + WinDbg host
- WinDbg Basics
- User-Mode Processes
- NTDLL — la cara user-mode del kernel
- WIN32K.sys — syscalls gráficas
- Componentes del Kernel-Mode
- Windows Driver Model (WDM)
- DRIVER_OBJECT y DriverEntry
- DEVICE_OBJECT
- I/O Request Packet (IRP) e I/O Stack Location
- Acceso a buffers de user-mode (Buffered / Direct / Neither I/O)
- Comunicación User-Kernel
- Estructuras del kernel
- Token Stealing — obtener privilegios SYSTEM
- Demo de explotación — HEVD
- Glosario
Extras¶
- Apuntes Clase 1
- Apuntes Clase 2
- Apuntes Clase 3
- Apuntes Clase 4
- Apuntes Clase 5
- Pipes en Windows y explotación de drivers
1. BinDiff — Instalación y uso¶
BinDiff es un plugin de Google para IDA Pro que permite comparar dos binarios (típicamente la versión vulnerable y la parcheada del mismo módulo) para identificar exactamente qué cambió en un patch. Es la herramienta más usada para hacer patch diffing y descubrir vulnerabilidades 1-day.
Instalación para IDA Pro¶
- Descarga: https://github.com/google/bindiff/releases/download/v8/bindiff8.msi
- Instalar
bindiff8.msi - Descargar los archivos parcheados desde el moodle del curso (https://moodle.geckoacademy.io/mod/resource/view.php?id=35)
- Reemplazar en
C:\Program Files\BinDiff\bin\: bindiff.exebindiff_config_setup.exebinexport2dump.exe- Reemplazar
binexport12_ida64.dllybindiff8_ida64.dllen: C:\Program Files\BinDiff\Plugins\IDA ProC:\Users\<usuario>\AppData\Roaming\Hex-Rays\IDA Pro\plugins
Cómo usar BinDiff¶
- Obtener la versión parcheada del archivo a analizar.
- Obtener la versión vulnerable inmediatamente anterior al parche (o la más cercana posible).
- Abrir la versión parcheada en IDA, dejar que termine el análisis, guardar la base de datos y cerrar IDA.
- Abrir la versión vulnerable en IDA. Cuando termine el análisis: EDIT → Plugin → BinDiff → Diff Database
- Seleccionar el
.i64de la versión parcheada y esperar resultados.
Resultado típico: BinDiff muestra cada función emparejada con un similarity score. Las funciones con score < 1.0 son las que cambiaron — ahí está el patch, y por reflexión, la vulnerabilidad.
2. Memoria virtual¶
Windows implementa un sistema de memoria virtual basado en un espacio de direcciones plano (lineal) que da a cada proceso la ilusión de tener su propio espacio de direcciones grande y privado.
| Concepto | Descripción |
|---|---|
| Memoria virtual | Vista lógica de la memoria que puede no corresponder al layout físico |
| Memory manager | Traduce (mapea) direcciones virtuales a direcciones físicas en runtime |
| Paging | Cuando hay menos memoria física que virtual en uso, el memory manager mueve páginas a disco (pagefile.sys) |
Process A (virtual) Physical Memory Process B (virtual)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ page 0 │─────────────→│ frame X │←──────────│ page 0 │
│ page 1 │ │ frame Y │ │ page 1 │
│ page 2 │──────┐ │ frame Z │ │ page 2 │
│ page 3 │ └──────→│ frame W │ │ page 3 │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌──────────┐
│ Disk │ pagefile.sys (páginas swappeadas)
└──────────┘
3. Layout del espacio de direcciones x64¶
- El espacio de direcciones teórico de 64 bits es de 16 exabytes (EB).
- Las CPUs actuales solo implementan 48 líneas de direcciones, limitando el espacio efectivo a 256 TB.
- Ese espacio se divide a la mitad:
- Lower 128 TB: procesos privados de usuario
- Upper 128 TB: espacio del sistema (kernel)
Direcciones canónicas¶
En x86-64 solo se usan 48 bits de dirección. El bit 47 determina si la dirección es:
| Bit 47 | Tipo |
|---|---|
0 |
User space |
1 |
Kernel space |
Los bits 63 a 48 deben ser una copia del bit 47 (sign extension). Si no lo son, la dirección es NON-CANONICAL y la CPU genera un fault inmediato al usarla.
0000 0000 0000 0000 ← inicio del user space
...
0000 7FFF FFFF FFFF ← fin del user space (128 TB)
Non canonical addresses (zona prohibida)
FFFF 8000 0000 0000 ← inicio del kernel space
...
FFFF FFFF FFFF FFFF ← fin del kernel space
Mapa del kernel-mode (system space)¶
Algunas regiones notables del kernel-mode en x64:
| Región | Tamaño | Propósito |
|---|---|---|
| Per Process | 128 TB − 64 KB | Espacio del proceso (user) |
| Hyperspace | 1 TB | Vistas de proceso, working sets |
| Win32k.sys / Session Data | 512 GB | Datos de sesión, Win32k, drivers |
| Four Level Page Table Map | 512 GB | Tablas de páginas |
| Shared System Page | 4 KB | Página compartida entre procesos |
| System PTEs WS info | 512 GB − 4 KB | Working set info de PTEs |
| Kernel CFG Bitmap | 1 TB | Bitmap de Control Flow Guard |
| Ultra Zero | 16 TB | Páginas zero-filled |
| System Cache | 16 TB | Caché del sistema |
| Paged Pool | 512 GB − 256 TB | Pool paginable |
| Special Pool | 512 GB | Pool especial (paged & non-paged) |
| System Cache WS | 1 TB | Working sets del system cache |
| System PTEs | 16 TB | PTEs del sistema |
| Non Paged Pool | 512 GB − 16 TB | Pool no paginable |
| PFN Database | (variable) | Base de datos de Page Frame Numbers |
| HAL Reserved | 4 MB | Reservado por la HAL |
4. User mode vs Kernel mode¶
| Aspecto | User mode | Kernel mode |
|---|---|---|
| Quién corre ahí | Aplicaciones de usuario | OS, drivers |
| Acceso a memoria | Solo su propio espacio privado | Toda la memoria del sistema |
| Instrucciones de CPU | Subset (anillo 3) | Todas (anillo 0) |
| Aislamiento | Procesos no pueden interferir entre sí | Sin restricciones |
¿Por qué la separación?¶
Protege a las aplicaciones de usuario de acceder o modificar datos críticos del OS. Una aplicación que se comporta mal no puede afectar la estabilidad del sistema entero.
Transición user → kernel¶
Una aplicación de user mode pasa a kernel mode cuando hace una system service call. Por ejemplo, CreateFile eventualmente necesita llamar a la rutina interna de Windows que maneja la lectura de archivos. Esa rutina, al acceder a estructuras internas del sistema, debe correr en kernel mode.
Mecánica:
- La CPU ejecuta una instrucción especial (
syscallen x64,int 2Een sistemas viejos). - Esto causa que la CPU entre al system service dispatching code del kernel.
- El kernel ejecuta la operación solicitada.
- Antes de retornar al thread de usuario, la CPU vuelve a user mode.
Así, el OS se protege a sí mismo y a sus datos contra inspección y modificación por procesos de usuario.
5. Setup y depuración del kernel de Windows¶
Descargar WinDbg¶
https://aka.ms/windbg/download
Configurar el Symbol Path¶
- Crear el directorio
C:\symbolspara almacenar los archivos de símbolos localmente. - Configurar la variable de entorno
_NT_SYMBOL_PATH:
Esto le dice a WinDbg que cachee los símbolos en C:\symbols y los descargue del Microsoft Symbol Server cuando no los tenga localmente.
6. Lab Setup — VM target + WinDbg host¶
El kernel debugging requiere dos máquinas: el target (la VM cuyo kernel se va a debuggear) y el host (donde corre WinDbg). La conexión típica es por red.
Paso 1 — Obtener IP de la VM target¶
En la VM target, ver con ipconfig la IP y el adaptador VMnet asociado. Ejemplo:
Paso 2 — Identificar la IP del host en el VMnet correspondiente¶
En el host (donde correrá WinDbg):
Paso 3 — Configurar el target para kernel debugging¶
Abrir CMD como administrador en la VM target y ejecutar:
El primer comando configura los parámetros de debugging por red y genera una clave única. El segundo activa el modo debug en el boot.
Paso 4 — Anotar la clave¶
La clave generada por newkey debe copiarse — se la necesitará en WinDbg. Ejemplo:
Paso 5 — Verificar configuración¶
Debe mostrar:
key 2cc411ap6pnl7.1r9ctkigja3xl.1lso9pd3mxid3.3poen5cou7st1
debugtype NET
hostip 192.168.119.1
port 50000
dhcp Yes
Paso 6 — Conectar WinDbg¶
En el host:
- Abrir WinDbg → Start debugging → Attach to kernel → Net
- Completar:
- Port number: 50000
- Key: la clave del paso 4
- Click OK.
Paso 7 — Reiniciar la VM target¶
Al reiniciar, WinDbg comenzará a debuggear el kernel. La consola mostrará algo como:
Microsoft (R) Windows Debugger
Connected to Windows 10 x64 target at (date)
Kernel Debugger connection established.
nt!DbgBreakPointWithStatus:
fffff807`3fa28610 cc int 3
7. WinDbg Basics¶
Módulos y símbolos¶
| Comando | Descripción |
|---|---|
.reload /f |
Recarga símbolos de los módulos cargados (forzado) |
.reload /u |
Descarga símbolos |
lm |
Lista módulos cargados |
x |
Muestra símbolos cargados |
!process 0 0 |
Lista todos los procesos |
!process -1 0 |
Muestra el contexto de proceso actual |
.process /i <eprocess> |
Cambia al contexto de otro proceso |
Breakpoints¶
| Comando | Descripción |
|---|---|
bp <addr> |
Breakpoint normal (software) |
bl |
Lista breakpoints activos |
ba e1 <addr> |
Breakpoint hardware en ejecución, tamaño 1 byte |
bc <id> |
Borrar breakpoint por ID |
Tracing y stepping¶
| Comando | Descripción | Atajo |
|---|---|---|
g |
Continue | F5 |
p |
Step over | F10 |
t |
Step into | F11 |
Cheat sheet completa: https://github.com/f1zm0/WinDBG-Cheatsheet
8. User-Mode Processes¶
Tipos de procesos en user-mode:
| Tipo | Descripción | Ejemplos |
|---|---|---|
| User processes | Procesos de aplicaciones (32 o 64 bits) | notepad.exe, chrome.exe |
| Service processes | Hostean servicios de Windows. Corren independientes del logon | Task Scheduler, Print Spooler |
| System processes | Procesos fijos no iniciados por el SCM | system, smss.exe, services.exe, lsass.exe |
| Environment subsystem | Implementan parte del soporte del entorno OS | csrss.exe, wininit.exe |
9. NTDLL — la cara user-mode del kernel¶
ntdll.dll es el módulo de user-mode que actúa como puente hacia el kernel: contiene los stubs (envoltorios) que disparan las syscalls.
Estructura de un stub de syscall en x64¶
ntdll!NtOpenFile:
00007FFB`06B5145D 4C:8BD1 mov r10, rcx ; primer argumento → r10
00007FFB`06B51460 B8 33000000 mov eax, 33h ; SSN (System Service Number)
00007FFB`06B51465 F60425 ... test byte ptr ds:[7FFE0308h], 1
00007FFB`06B5146D 75 03 jne short next
00007FFB`06B5146F 0F05 syscall ; transición a kernel
00007FFB`06B51471 C3 ret
El System Service Number (SSN) se carga en EAX (0x33 en este caso para NtOpenFile) y luego syscall transfiere el control al kernel.
Resolver la dirección kernel correspondiente¶
Estando en user-mode dentro de una función de NTDLL, para calcular la dirección de la función equivalente en kernel-mode hay que buscar en la System Service Descriptor Table (SSDT) la entrada indicada por el valor en EAX antes del syscall.
En x64, la SSDT contiene offsets relativos de 32 bits en lugar de punteros directos. Para obtener la dirección absoluta:
- Leer el DWORD de la entrada de la SSDT
- Hacer right-shift de 4 bits (
>> 4) - Sumar al base address de
KiServiceTable
Ejemplo práctico en WinDbg¶
0: kd> dd /c1 nt!KiServiceTable + (0x33 * 4) L1
fffff807`226cef5c 067e9b02
0: kd> u (nt!KiServiceTable + (0x067e9b02 >>4 ))
nt!NtOpenFile:
fffff807`22d4d840 4c8bdc mov r11,rsp
fffff807`22d4d843 4881ec88000000 sub rsp,88h
fffff807`22d4d84a 8b8424b8000000 mov eax,dword ptr [rsp+0B8h]
fffff807`22d4d851 4533d2 xor r10d,r10d
...
Comandos explicados:
| Token | Significado |
|---|---|
dd |
Display dword |
/c1 |
Mostrar 1 columna |
nt!KiServiceTable + (0x33 * 4) |
KiServiceTable apunta al inicio de la tabla en ntoskrnl.exe. 0x33 es el SSN. * 4 porque cada entrada es DWORD |
L1 |
Length: leer 1 elemento |
u |
Unassemble |
>>4 |
Right shift 4 bits para obtener el offset real en x64 |
Recurso útil: https://j00ru.vexillium.org/syscalls/nt/64/ — tablas de syscalls por versión de Windows, busca por SSN.
10. WIN32K.sys — syscalls gráficas¶
win32k.sys implementa la GUI (USER y GDI). Sus syscalls tienen SSN > 0x1000 y se resuelven contra W32pServiceTable en lugar de KiServiceTable.
Resolver una syscall de win32k.sys — ejemplo con SSN 0x1022¶
Paso 1 — Extraer el offset¶
Bitwise AND con 0xFFF para obtener el offset dentro de la tabla.
Paso 2 — Leer la entrada¶
Devuelve un valor codificado: 0xff84e740.
Paso 3 — Convertir a entero con signo¶
Interpretado como int32 signed: -8067264.
Paso 4 — Calcular la dirección real¶
Nota:
0nes prefijo decimal en WinDbg,>>>es arithmetic right shift (preserva el signo).
11. Componentes del Kernel-Mode¶
Componentes principales¶
| Componente | Archivo | Descripción |
|---|---|---|
| Windows Executive | Ntoskrnl.exe (prefijo Ex) |
Servicios base del OS: memory mgmt, process/thread mgmt, security I/O, networking, IPC |
| Windows Kernel | Ntoskrnl.exe (prefijo Ke) |
Funciones de bajo nivel: thread scheduling, interrupt/exception dispatching, multiprocessor sync. Provee primitivas que el resto del executive usa |
| Device drivers | *.sys |
Drivers de hardware (traducen I/O calls a operaciones de hardware) y no-hardware (file system, network, filter drivers) |
| HAL (Hardware Abstraction Layer) | Hal.dll |
Aísla el kernel y los drivers de las diferencias de hardware específicas de plataforma |
| Win32k subsystem | Win32k.sys |
Implementa funciones GUI (USER y GDI): ventanas, controles, drawing |
| Hypervisor | Hvix64.exe |
El hypervisor en sí. Compone múltiples capas: memory manager propio, virtual processor scheduler, interrupt/time mgmt, sync routines, partitions (VMs), inter-partition communication (IPC) |
Diagrama de capas¶
┌─────────────────────────────────────────────────────────────────────┐
│ User Mode │
│ Environment Subsystems (CSRSS) | System processes (Wininit, SCM) │
│ Services (Spooler, SvcHost) | Applications (User Process) │
│ Subsystem DLLs │
│ │ │
│ ▼ │
│ NTDLL.DLL │
└─────────────────────────────────────────────────────────────────────┘
─────────────────────────────────────────────────────────────────────────
┌─────────────────────────────────────────────────────────────────────┐
│ Kernel Mode │
│ System Service Dispatcher (KiSystemCall64) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ I/O Mgr | Sec Ref Mon | Plug&Play | Power | Memory Mgr │ │
│ │ Process Mgr | Config Mgr | Object Mgr | ALPC │ │
│ │ Device & File System Drivers │ │
│ │ Kernel │ │
│ │ Win32k + │ │
│ │ Graphics │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ Hardware Abstraction Layer (HAL) + HAL Extensions │
└─────────────────────────────────────────────────────────────────────┘
Hyper-V Hypervisor
12. Windows Driver Model (WDM)¶
- WDM es el framework de drivers introducido con Windows 2000.
- Los drivers WDM se organizan en stacks por capas y se comunican mediante I/O Request Packets (IRPs).
Aplicación user-mode
│
▼
I/O Manager
│
▼
┌───────────────────┐
│ Filter Driver │ ← capa superior (filter)
├───────────────────┤
│ Function Driver │ ← driver principal del dispositivo
├───────────────────┤
│ Bus Driver │ ← driver del bus (PCI, USB)
└───────────────────┘
│
▼
Hardware
Cada IRP recorre el stack de arriba hacia abajo (request) y de abajo hacia arriba (completion).
13. DRIVER_OBJECT y DriverEntry¶
DRIVER_OBJECT¶
El I/O Manager crea un DRIVER_OBJECT para cada driver instalado y cargado. Cada DRIVER_OBJECT representa la imagen de un driver kernel-mode cargado.
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;
MajorFunction — la dispatch table¶
Es un array de punteros a funciones indexado por los códigos IRP_MJ_XXX. Cada driver setea entry points para los IRP majors que maneja.
| Constante | Valor | Operación |
|---|---|---|
IRP_MJ_CREATE |
0x00 |
Apertura del dispositivo (CreateFile) |
IRP_MJ_CLOSE |
0x02 |
Cierre (CloseHandle) |
IRP_MJ_READ |
0x03 |
Lectura (ReadFile) |
IRP_MJ_WRITE |
0x04 |
Escritura (WriteFile) |
IRP_MJ_DEVICE_CONTROL |
0x0E |
IOCTL (DeviceIoControl) |
IRP_MJ_CLEANUP |
0x12 |
Cleanup |
DriverEntry¶
DriverEntry es la primera rutina del driver que se invoca al cargar. Es responsable de inicializar el driver y su DRIVER_OBJECT.
NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
// ...
// IRP_MJ_DEVICE_CONTROL = 0xE — handler de IOCTLs
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDeviceControl;
// Rutina de descarga del driver
DriverObject->DriverUnload = DriverUnload;
// ...
return STATUS_SUCCESS;
}
14. DEVICE_OBJECT¶
El sistema operativo representa cada dispositivo mediante un DEVICE_OBJECT. Uno o más device objects pueden estar asociados a cada dispositivo y sirven como target de todas las operaciones sobre él.
- Definidos por la estructura
DEVICE_OBJECT, gestionados por el Object Manager. - Pueden tener nombre (named device object) y soportar handles abiertos sobre ellos.
- Encadenados en una linked list (
NextDevice).
Relación entre DRIVER_OBJECT y DEVICE_OBJECT¶
DRIVER_OBJECT DEVICE_OBJECT (1) DEVICE_OBJECT (2)
┌───────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ ... │ │ Size | Type │ │ Size | Type │
│ DeviceObject ─┼──────────────────→│ DriverObject ├──┐ │ DriverObject ├── (apunta de vuelta al driver)
│ ... │ │ NextDevice ├──┘ ──────→│ NextDevice ├──→ NULL
└───────────────┘ │ CurrentIrp │ │ ... │
│ DeviceExtension │ └──────────────────┘
│ Device Ext Spec │
└──────────────────┘
Idea clave: el
DRIVER_OBJECTrepresenta el comportamiento del driver (su código), mientras que losDEVICE_OBJECTrepresentan endpoints de comunicación concretos.
15. I/O Request Packet (IRP) e I/O Stack Location¶
IRP — concepto¶
El IRP es la estructura básica que encapsula operaciones de I/O en Windows.
- Las operaciones de I/O viajan por el stack de drivers dentro de un IRP.
- Cada driver del stack obtiene un I/O stack location propio con los parámetros de su request.
- El IRP tiene major codes (operación principal:
CREATE,READ,WRITE,DEVICE_CONTROL,CLEANUP,CLOSE) y minor codes (subdivisiones). - Los IRPs están asociados al thread que originó la I/O request.
Estructura del IRP¶
Consta de dos partes:
1. Header¶
Usado por el I/O Manager para almacenar info sobre el request original (parámetros independientes del dispositivo, dirección del device object donde el archivo está abierto, etc.). También usado por los drivers para almacenar el status final.
2. I/O stack locations¶
Después del header viene un set de I/O stack locations, una por cada driver del chain de drivers layered al que el request está bound. Cada stack location contiene los parámetros, function codes y context que el driver correspondiente necesita.
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _IRP {
// ...
union {
struct {
union { ... };
PETHREAD Thread;
PCHAR AuxiliaryBuffer;
struct {
LIST_ENTRY ListEntry;
union {
struct _IO_STACK_LOCATION* CurrentStackLocation; // ← location del driver actual
ULONG PacketType;
};
};
PFILE_OBJECT OriginalFileObject;
} Overlay;
KAPC Apc;
PVOID CompletionKey;
} Tail;
} IRP;
IO_STACK_LOCATION¶
Cada IRP va seguido de uno o más I/O stack locations (uno por driver layered).
typedef struct _IO_STACK_LOCATION {
UCHAR MajorFunction; // IRP_MJ_XXX
UCHAR MinorFunction;
UCHAR Flags;
UCHAR Control;
union {
// ...
struct {
ULONG OutputBufferLength;
ULONG POINTER_ALIGNMENT InputBufferLength;
ULONG POINTER_ALIGNMENT IoControlCode; // ← IOCTL code
PVOID Type3InputBuffer;
} DeviceIoControl;
// ...
} Parameters;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
PIO_COMPLETION_ROUTINE CompletionRoutine;
PVOID Context;
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;
Para reversing de drivers: la unión
DeviceIoControles donde aparecen losIoControlCodeque identifican qué operación específica se está pidiendo. Estos IOCTLs son la superficie de ataque más común contra drivers vulnerables.
16. Acceso a buffers de user-mode (Buffered / Direct / Neither I/O)¶
Cuando un driver recibe un IRP que viene de user-mode, los buffers del usuario tienen que ser accedidos de forma segura. El I/O Manager soporta tres métodos:
Buffered I/O¶
El I/O Manager aloca un mirror buffer del mismo tamaño que el del usuario en non-paged pool, copia los datos y guarda el puntero al nuevo buffer en Irp->AssociatedIrp.SystemBuffer.
- ✅ Seguro: el driver opera sobre memoria kernel.
- ⚠️ Costoso: copia completa del buffer.
Direct I/O¶
El I/O Manager lockea las páginas del buffer del usuario en memoria física (mediante MDLs - Memory Descriptor Lists) y le da al driver una manera de acceder directamente sin copiar.
- ✅ Eficiente para buffers grandes.
- ⚠️ El driver debe usar
MmGetSystemAddressForMdlSafepara mappear el MDL.
Neither I/O¶
El I/O Manager no realiza ningún manejo de buffer. El driver recibe el puntero original del usuario directamente.
- ❌ Peligroso: el driver es responsable de validar el puntero, su rango, y las condiciones de race (TOCTOU).
- 🎯 Vector de ataque común: muchos bugs de drivers vienen de Neither I/O sin validación adecuada (no se usa
ProbeForRead/ProbeForWrite).
17. Comunicación User-Kernel¶
Para comunicarse desde user-mode a un driver kernel-mode, una aplicación debe:
- Abrir un handle al device con
CreateFile(como si fuera un archivo). - Enviar/recibir mensajes con
DeviceIoControl,ReadFileoWriteFile. - Cerrar el handle con
CloseHandle.
HANDLE device = CreateFile(
L"\\\\.\\DeviceName", // path al device (formato \\.\)
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (device != INVALID_HANDLE_VALUE) {
DWORD bytes_returned;
BOOL result = DeviceIoControl(
device,
IOCTL_CODE, // código IOCTL específico del driver
input_buffer, sizeof(input_buffer),
output_buffer, sizeof(output_buffer),
&bytes_returned,
NULL
);
CloseHandle(device);
}
Para reversing/exploitation: identificar los IOCTL codes que el driver acepta (vía sus dispatch routines) es el primer paso. Cada IOCTL es un punto de entrada.
18. Estructuras del kernel¶
Las estructuras más relevantes para entender y explotar el kernel de Windows:
| Estructura | Descripción breve |
|---|---|
| KPCR | Kernel Processor Control Region — info del procesador actual |
| KPRCB | Kernel Processor Control Block — embedded en KPCR, info de scheduling |
| KTHREAD | Kernel Thread — núcleo de la estructura ETHREAD |
| KAPC_STATE | Kernel Asynchronous Procedure Call State |
| KPROCESS / EPROCESS | Process Object — estructura de proceso |
18.1 KPCR — Kernel Processor Control Region¶
Estructura de datos kernel-mode que contiene info sobre el procesador actual:
- Interrupt Dispatch Table (IDT)
- Task State Segment (TSS)
- Global Descriptor Table (GDT)
- Estado del interrupt controller
El kernel mantiene una KPCR por cada procesador lógico. En x64 Windows, el kernel guarda un puntero a la KPCR en el registro gs.
typedef struct _KPCR {
// ...
UCHAR SecondLevelCacheAssociativity;
UCHAR ObsoleteNumber;
UCHAR Fill0;
ULONG Unused0[3];
USHORT MajorVersion;
USHORT MinorVersion;
ULONG StallScaleFactor;
PVOID Unused1[3];
ULONG KernelReserved[15];
ULONG SecondLevelCacheSize;
ULONG HalReserved[16];
ULONG Unused2;
PVOID KdVersionBlock;
PVOID Unused3;
ULONG PcrAlign1[24];
KPRCB Prcb; // ← KPRCB embedded
} KPCR, *PKPCR;
Acceso en x64: gs:[0] apunta a la KPCR del procesador actual.
18.2 KPRCB — Kernel Processor Control Block¶
Estructura embebida en la KPCR. Contiene:
- Info de scheduling, dispatcher database del procesador
- DPC queue
- Info de vendor de CPU e identificación
- Tamaños de cache, time accounting, I/O stats
- Cache manager stats, DPC stats, memory manager stats
- Look-aside lists de non-paged y paged pool
typedef struct _KPRCB {
ULONG MxCsr;
UCHAR Number;
UCHAR NestingLevel;
BOOLEAN InterruptRequest;
BOOLEAN IdleHalt;
PKTHREAD CurrentThread; // ← thread ejecutándose actualmente
PKTHREAD NextThread;
// ...
} KPRCB, *PKPRCB;
Tip de exploitation: desde un thread kernel se puede obtener el thread actual con
KeGetCurrentThread()o leyendogs:[0x188](ofset hardcodeado aCurrentThreaden el KPRCB).
18.3 KTHREAD — Kernel Thread¶
Es la porción del Kernel Core de la estructura ETHREAD. La ETHREAD es el thread object expuesto por el Object Manager; la KTHREAD es su núcleo. Contiene info para scheduling, sincronización y time-keeping.
struct _KTHREAD {
// ...
union {
KAPC_STATE ApcState; // ← estado APC actual (incluye proceso!)
struct {
UCHAR ApcStateFill[43];
CHAR Priority;
ULONG UserIdealProcessor;
};
};
volatile LONGLONG WaitStatus;
PKWAIT_BLOCK WaitBlockList;
// ...
} KTHREAD, *PKTHREAD;
18.4 KAPC_STATE — APC State¶
Representa el estado de Asynchronous Procedure Call (APC) del thread. La parte clave:
typedef struct _KAPC_STATE {
LIST_ENTRY ApcListHead[2];
struct _KPROCESS* Process; // ← apunta al proceso del thread!
union {
UCHAR InProgressFlags;
struct {
UCHAR KernelApcInProgress : 1;
UCHAR SpecialApcInProgress : 1;
};
};
UCHAR KernelApcPending;
union { ... };
} KAPC_STATE, *PKAPC_STATE;
Idea clave: desde el thread actual (
KTHREAD) se puede llegar alEPROCESSdel proceso actual víaThread->ApcState.Process. Este es el punto de partida del token stealing.
18.5 KPROCESS / EPROCESS — Process Object¶
KPROCESS es la porción del Kernel del struct EPROCESS del Executive. La EPROCESS es el process object expuesto por el Object Manager; la KPROCESS es su inicio.
typedef struct _EPROCESS {
KPROCESS Pcb; // ← KPROCESS embedded
EX_PUSH_LOCK ProcessLock;
VOID* UniqueProcessId; // PID
LIST_ENTRY ActiveProcessLinks; // ← linked list de procesos
// ...
EX_FAST_REF Token; // ← TOKEN del proceso (privilegios)
// ...
} EPROCESS;
Campos clave para exploitation:
| Campo | Uso en explotación |
|---|---|
UniqueProcessId |
El PID. PID 4 = SYSTEM |
ActiveProcessLinks |
Lista doblemente enlazada — permite iterar todos los procesos |
Token |
El access token que define los privilegios del proceso |
19. Token Stealing — obtener privilegios SYSTEM¶
La técnica clásica de privilege escalation desde una vulnerabilidad kernel: robarle el token al proceso SYSTEM.
Algoritmo¶
- Obtener el
EPROCESSactual (víaKTHREAD.ApcState.ProcessoIoGetCurrentProcess()). - Iterar
EPROCESS.ActiveProcessLinks(lista doblemente enlazada) para recorrer todos los procesos del sistema. - Comparar cada
EPROCESS.UniqueProcessIdcon4. Cuando coincida → es el proceso SYSTEM. - Reemplazar
EPROCESS.Tokendel proceso actual con elTokendel proceso SYSTEM.
A partir de ese momento, el proceso atacante (originalmente con permisos limitados) tendrá los privilegios SYSTEM.
Pseudo-código del shellcode kernel¶
// 1. Obtener el EPROCESS actual
PEPROCESS current = (PEPROCESS)__readgsqword(0x188); // KPRCB.CurrentThread
current = (PEPROCESS)((PUCHAR)current + 0x220); // KTHREAD.ApcState.Process (offset varía por versión)
// 2. Guardar el token del proceso actual (para restaurar luego si quisiéramos)
ULONG_PTR currentToken = current->Token;
// 3. Iterar ActiveProcessLinks buscando PID == 4
PEPROCESS p = current;
do {
p = (PEPROCESS)((PUCHAR)p->ActiveProcessLinks.Flink - OFFSET_ActiveProcessLinks);
if ((ULONG_PTR)p->UniqueProcessId == 4) {
// 4. Robar el token (preservando los low bits del EX_FAST_REF)
current->Token = (p->Token & ~0xF) | (currentToken & 0xF);
break;
}
} while (p != current);
Notas importantes¶
EX_FAST_REF: el campoTokenes unEX_FAST_REF, que usa los 3 bits bajos como contador de referencias. Al copiar el token hay que preservar esos bits del token actual.- Offsets variables por versión:
EPROCESS.Token,ActiveProcessLinks,UniqueProcessIdestán en distintos offsets según la versión de Windows. En exploits reales se calculan dinámicamente o se hardcodean por build. - Thread vs Process token impersonation: alternativa más sutil → no robar el Process Token sino impersonar usando un Thread Token.
Cómo prevenir token stealing (mitigaciones)¶
- KASLR: randomización del kernel base address — dificulta encontrar
nt!PsInitialSystemProcessy offsets. - SMEP (Supervisor Mode Execution Prevention): impide ejecutar shellcode en páginas user-mode desde kernel.
- SMAP (Supervisor Mode Access Prevention): impide leer/escribir páginas user-mode desde kernel sin habilitarlo explícitamente.
- CFG / kCFG: Control Flow Guard kernel-mode valida llamadas indirectas.
- HVCI / VBS: Hypervisor-protected Code Integrity — el hypervisor protege la integridad del código kernel.
20. Demo de explotación — HEVD¶
HEVD (HackSys Extreme Vulnerable Driver) es un driver de Windows intencionalmente vulnerable, mantenido por HackSys Team, diseñado para enseñar exploitation kernel-mode. Implementa una variedad de vulnerabilidades clásicas:
- Stack-based buffer overflow
- Heap-based buffer overflow (Pool Overflow)
- Use-After-Free
- Double Fetch
- Type Confusion
- Arbitrary write (Write-What-Where)
- Null Pointer Dereference
- Integer Overflow
- Race Conditions
Repositorio: https://github.com/hacksysteam/HackSysExtremeVulnerableDriver
Workflow típico para explotar HEVD¶
- Cargar HEVD en una VM target (con kernel debugging configurado contra el host con WinDbg).
- Identificar el device name que crea el driver (con
WinObjo reversing delDriverEntry). - Reversing del dispatch handler para enumerar los IOCTLs soportados y sus funciones vulnerables.
- Construir el exploit en user-mode que abre el device con
CreateFile, dispara la vulnerabilidad conDeviceIoControl. - Lograr ejecución de código kernel o primitiva de read/write.
- Token stealing → escalada a SYSTEM.
- Spawnear
cmd.execon privilegios SYSTEM para confirmar el éxito.
Aproximación recomendada: empezar por stack overflow (la vulnerabilidad más simple). Con SMEP activado se requiere ROP en kernel para bypass, lo que añade complejidad pero es el escenario realista.
21. Glosario¶
| Término | Significado | Descripción |
|---|---|---|
| VA | Virtual Address | Dirección lógica usada por CPU/procesos. El MMU la traduce a una dirección física mediante tablas de páginas. |
| PA | Physical Address | Dirección real en memoria física/RAM. En WinDbg se puede leer con comandos de display memory físicos como !db o !dq. |
| PTE | Page Table Entry | Entrada de tabla de páginas. Contiene el PFN y flags de permisos/estado de una página. |
| PFN | Page Frame Number | Número del frame físico donde vive una página. La base física se obtiene con PFN << 12; la dirección exacta suma el offset VA & 0xFFF. |
| Page offset | Offset de página | Los 12 bits bajos de una dirección en páginas de 0x1000 bytes. Se conservan al pasar de VA a PA. |
| NPFS | Named Pipe File System | Driver/componente (npfs.sys) que implementa named pipes y pipes anónimos por debajo. |
| NpFr | NPFS pool tag | Pool tag de 4 bytes que identifica ciertas allocations internas de NPFS relacionadas con pipes. |
| Pool tag | Etiqueta de pool | Identificador de 4 bytes usado por el kernel para rastrear allocations del pool (Hack, NpFr, etc.). Se ve con !pool / !poolfind. |
| NonPagedPool | Pool no paginable | Memoria kernel que no puede irse a disco. Se usa para estructuras que deben estar disponibles a IRQL alto o durante I/O crítico. |
| NonPagedPoolNx | NonPagedPool no ejecutable | Variante no ejecutable del NonPagedPool. Reduce la posibilidad de ejecutar shellcode directamente desde pool. |
| HEVD | HackSys Extreme Vulnerable Driver | Driver vulnerable usado para practicar kernel exploitation. |
| UAF | Use-After-Free | Bug donde se usa un puntero después de liberar el objeto al que apuntaba. |
| Reclaimer | Objeto reclamador | Allocation controlada que intenta ocupar un chunk liberado por una vulnerabilidad. Ejemplo: pipes NpFr reclamando un chunk Hack. |
| Pool spray | Spray de pool | Crear muchas allocations parecidas para moldear el estado del allocator del kernel. |
| IRP | I/O Request Packet | Estructura que representa una operación de I/O en Windows y viaja por el stack de drivers. |
| IOCTL | I/O Control Code | Código enviado con DeviceIoControl para pedir una operación específica a un driver. |
DRIVER_OBJECT |
Objeto de driver | Estructura kernel que representa un driver cargado y contiene su tabla MajorFunction[]. |
DEVICE_OBJECT |
Objeto de dispositivo | Endpoint kernel que una app abre con CreateFile("\\\\.\\DeviceName", ...). |
MajorFunction[] |
Tabla de dispatch | Array de handlers del driver indexado por códigos IRP_MJ_*. |
IRP_MJ_DEVICE_CONTROL |
Major code 0x0E |
Handler de DeviceIoControl; suele ser la superficie principal de explotación en drivers. |
| SSN | System Service Number | Número de syscall cargado en EAX por los stubs de ntdll antes de ejecutar syscall. |
| SSDT | System Service Descriptor Table | Tabla que resuelve SSNs a funciones kernel (KiServiceTable para NT syscalls). |
KiServiceTable |
Tabla de syscalls NT | Tabla usada por el kernel para despachar syscalls normales de ntoskrnl. |
W32pServiceTable |
Tabla de syscalls Win32k | Tabla usada para syscalls gráficas/USER/GDI de win32k.sys. |
| KPCR | Kernel Processor Control Region | Estructura por CPU con datos del procesador actual; en x64 se accede vía gs. |
| KPRCB | Kernel Processor Control Block | Bloque dentro de KPCR con estado de scheduling, thread actual y datos por procesador. |
| KTHREAD | Kernel Thread | Núcleo kernel de un thread; desde ahí se llega al proceso actual vía ApcState.Process. |
| KAPC_STATE | Kernel APC State | Estado APC del thread; contiene el puntero al proceso asociado. |
| EPROCESS | Executive Process | Estructura kernel que representa un proceso. Contiene PID, lista de procesos y token. |
| Token | Access token | Objeto que define identidad, grupos y privilegios de un proceso/thread. |
EX_FAST_REF |
Fast reference | Tipo usado para referencias compactas; en campos como EPROCESS.Token reserva bits bajos para contador/flags. |
| KASLR | Kernel Address Space Layout Randomization | Randomiza bases de módulos kernel para dificultar direcciones hardcodeadas. |
| SMEP | Supervisor Mode Execution Prevention | Impide que kernel ejecute código ubicado en páginas user-mode. |
| SMAP | Supervisor Mode Access Prevention | Impide que kernel acceda a páginas user-mode sin habilitación explícita. |
| kCFG | Kernel Control Flow Guard | Mitigación que valida ciertos saltos/llamadas indirectas en kernel. |
| HVCI/VBS | Hypervisor-protected Code Integrity / Virtualization-Based Security | Mitigaciones basadas en virtualización para proteger integridad de código y aislar componentes. |
Apunte basado en el material de Binary Gecko — Next-Gen Reverse Engineering Training, Módulo 5: Exploitation II — Kernel Basics.