Clase 2 — Debugging del IOCTL vulnerable, integer overflow y grooming con pipes NpFr¶
← Volver al Módulo 6 · ← Clase 1¶
Resumen: en esta clase se continúa desde el dispatch de IOCTL encontrado en la clase anterior. Se reconstruyen los argumentos reales que llegan a la función vulnerable, se valida en runtime el integer overflow del
OutputBufferLength, se observa cómo elmemmovedesborda una allocation de pool de0x1000, y se empieza a estudiarnpfs.syspara entender por qué las named pipes con tagNpFrsirven como objeto adyacente/reclaimer.
Tabla de Contenidos¶
- Contexto: desde el dispatch hasta la función vulnerable
- Reconstruir argumentos antes de entrar a la vulnerable
- Condiciones de entrada del IOCTL
IO_STACK_LOCATIONy campos deDeviceIoControl- Valores del PoC: input y output sizes
- El integer overflow del
OutputBufferLength ProbeForRead,try/excepty por qué no hay BSoD inmediato- Allocation vulnerable en pool
- El
memmoveque provoca el overflow - Confirmación en debugger
- Grooming: por qué usar named pipes
- Pool tags y cómo frenar en allocations
NpFr - Breakpoint genérico vs breakpoint dentro de
npfs.sys - Qué pasa cuando se escriben pipes
- Estructuras de
npfs.syscon ayuda de ReactOS - Objetivo del overflow: pisar
DataSize - Detección del pipe overflowdeado
- Plan para doble debugging user/kernel
- Workflow mental completo
- Cheat sheet
- Glosario corto
1. Contexto: desde el dispatch hasta la función vulnerable¶
La clase arranca retomando el camino visto en la clase anterior:
DispatchIrpBridge
-> dispatch por MajorFunction
-> IRP_MJ_DEVICE_CONTROL
-> DispatchIoctl / DispatchDeviceControl
-> case IOCTL 0x2F007
-> función vulnerable
Ya se había determinado que el IOCTL interesante era:
El foco ahora es dejar de ver la función vulnerable como pseudocódigo suelto y responder:
- con qué argumentos reales se llama;
- qué buffers vienen desde user-mode;
- qué tamaños se controlan;
- qué operación hace overflow;
- qué objeto queda después en el pool;
- qué campo se pisa en el objeto adyacente.
Hallazgos verificados con IDA/MCP¶
El binario cargado en IDA confirma esta ruta con nombres y direcciones concretas:
| Dirección | Función | Rol en la ruta |
|---|---|---|
0x1c0001320 |
CKernelFilterDevice::DispatchIrp |
dispatch genérico por IRP_MJ_*; para IRP_MJ_DEVICE_CONTROL llama virtualmente a DispatchIoctl |
0x1c0001eb0 |
CKSThunkDevice::DispatchIoctl |
router WOW64 del device; enruta 0x2F0003, 0x2F0007, 0x2F000B |
0x1c00020f0 |
CKSThunkPin::DispatchIoctl |
router equivalente cuando ya hay CKernelFilterFile/pin asociado; también enruta IOCTLs de streaming |
0x1c0002ab8 |
CKSAutomationThunk::ThunkEnableEventIrp |
handler vulnerable alcanzado por 0x2F0007 |
La relación importante es:
0x2F0007
-> CKSThunkDevice::DispatchIoctl / CKSThunkPin::DispatchIoctl
-> CKSAutomationThunk::ThunkEnableEventIrp
-> cálculo vulnerable de OutputBufferLength
-> ExAllocatePool2 + memmove overflow
También queda confirmado que 0x2F0007 en decimal es 3080199, que es el valor que a veces muestra el decompiler cuando no aplica enums de IOCTL.
2. Reconstruir argumentos antes de entrar a la vulnerable¶
La clase dedica bastante tiempo a corregir los argumentos que IDA muestra como a1, a2, a3, a4, etc.
Desde el bridge se llega a una llamada conceptual parecida a:
La reconstrucción vista en clase:
| Argumento | Valor real observado | Motivo |
|---|---|---|
arg1 / RCX |
0 |
el path solo llega si esa condición previa deja RCX = 0 |
arg2 / RDX |
PIRP Irp |
viene directo del dispatch WDM |
arg3 / R8 |
0 |
viene como constante desde el bridge |
arg4 / R9 |
puntero a Status = 0 |
local inicializada en cero y pasada por referencia |
La idea práctica es renombrar esos argumentos en IDA para no arrastrar ruido:
Tip de IDA¶
Si una call indirecta no salta bien al destino, se puede usar el plugin/opción de edición de call address para fijar manualmente la dirección y poder navegar directo a la función llamada.
3. Condiciones de entrada del IOCTL¶
Antes del switch/case del IOCTL se validan dos condiciones relevantes:
En Windows kernel:
| Valor | Modo |
|---|---|
0 |
KernelMode |
1 |
UserMode |
Entonces el bug está en una ruta pensada para requests desde user-mode, específicamente para un proceso WOW/32-bit que llega al driver de thunking.
Camino relevante¶
if (Is32BitProcess && Irp->RequestorMode == UserMode) {
switch (IoControlCode) {
case 0x2F007:
vulnerable_path(...);
}
}
4. IO_STACK_LOCATION y campos de DeviceIoControl¶
Dentro de la vulnerable, lo primero importante es recuperar el current stack location del IRP.
En el decompiler puede aparecer como acceso manual a offset:
Ese campo corresponde a:
En IDA conviene forzar el field correcto:
Después hay que corregir la union de IO_STACK_LOCATION, porque para esta ruta estamos en IRP_MJ_DEVICE_CONTROL.
Los campos importantes son:
stack->Parameters.DeviceIoControl.InputBufferLength;
stack->Parameters.DeviceIoControl.OutputBufferLength;
stack->Parameters.DeviceIoControl.Type3InputBuffer;
Irp->UserBuffer;
Offsets confirmados en ThunkEnableEventIrp¶
En la función 0x1c0002ab8, el assembly muestra estos accesos:
| Dirección | Instrucción / acceso | Interpretación |
|---|---|---|
0x1c0002b08 |
mov rcx, [rdx+0B8h] |
Irp->Tail.Overlay.CurrentStackLocation |
0x1c0002b14 |
mov r13d, [rcx+10h] |
InputBufferLength |
0x1c0002b18 |
mov r12d, [rcx+8] |
OutputBufferLength |
0x1c0002b5a |
mov rcx, [rcx+20h] |
Type3InputBuffer como input user-mode |
0x1c0002dd5 |
mov rcx, [rsi+70h] |
Irp->UserBuffer como output user-mode |
IDA puede mostrar nombres de otra union, como Parameters.Read.Length o Parameters.Create.Options, porque IO_STACK_LOCATION.Parameters es una union. Para esta ruta hay que leerlos mentalmente como campos de DeviceIoControl.
METHOD_NEITHER¶
La clase remarca que en esta ruta el driver trata buffers estilo METHOD_NEITHER:
Type3InputBuffercontiene el input user-mode;UserBufferse usa como output user-mode;- no hay
SystemBufferútil como enMETHOD_BUFFERED; - el driver debe validar manualmente punteros y tamaños.
5. Valores del PoC: input y output sizes¶
El PoC llama a DeviceIoControl con valores elegidos para disparar el bug.
Valores vistos en clase:
| Campo | Valor aproximado | Uso |
|---|---|---|
InputBufferLength |
0x1000 |
tamaño normal del input |
OutputBufferLength |
0xFFFFFFF0 |
valor grande/negativo en 32-bit |
Type3InputBuffer |
buffer user-mode controlado | input |
UserBuffer |
output buffer user-mode controlado | salida y fuente posterior del copy |
El input de 0x1000 es válido y no busca fallar. El valor interesante es el output size:
Ese valor está elegido para que algunas sumas/wrapping lo conviertan en un tamaño chico al momento de validar, pero siga representando un tamaño enorme cuando se usa como longitud de copia.
El binario también permite entender por qué otras variantes cercanas funcionan. Por ejemplo, si OutputBufferLength = 0xFFFFFFF1, el cálculo de alineación queda en 0x8; si OutputBufferLength = 0xFFFFFFF0, queda en 0x0. En ambos casos el problema es el mismo: el check usa arithmetic de 32 bits después del wrap.
6. El integer overflow del OutputBufferLength¶
El patrón vulnerable aparece cuando el driver intenta alinear o validar el output size:
En assembly:
Con el valor del PoC:
OutputBufferLength = 0xFFFFFFF0
OutputBufferLength + 0x10 -> 0x00000000 // wrap en 32-bit
OutputBufferLength + 0x17 -> valor chico/alineado
La clase lo valida en debugger: después de sumar y aplicar el AND, el valor queda chico o incluso 0, entonces pasa checks que estaban pensados para tamaños normales.
Check defectuoso¶
Un patrón observado:
AlignedOutputLength = (OutputBufferLength + 0x17) & ~7
tmp = OutputBufferLength + 0x10
if (AlignedOutputLength < tmp) {
ExRaiseStatus(STATUS_INVALID_BUFFER_SIZE)
}
El problema es que el check mira el resultado después del wrap, no el tamaño original.
En el binario vulnerable:
0x1c0002d86 mov r15d, [rsp+Size] ; AlignedOutputLength
0x1c0002d8e lea eax, [r12+10h] ; OutputBufferLength + 0x10, truncado a 32 bits
0x1c0002d93 cmp r15d, eax
0x1c0002d96 jb invalid_buffer_size
Con OutputBufferLength = 0xFFFFFFF0:
Entonces el check no detecta el tamaño malicioso.
Lectura de reversing¶
El bug no es simplemente “copiar mucho”. La causa primaria es:
integer overflow / wraparound en cálculo de size
↓
validación pasa con tamaño chico
↓
copia usa tamaño grande controlado
↓
pool overflow
7. ProbeForRead, try/except y por qué no hay BSoD inmediato¶
La función usa ProbeForRead para validar punteros user-mode.
Puntos importantes de la clase:
ProbeForReadno garantiza que toda la lógica posterior sea segura.- Valida que el rango user-mode sea accesible según dirección y tamaño.
- Si falla, levanta excepción.
- Como el código está dentro de
try/except, la excepción se captura. - El driver sale con error en vez de provocar BSoD directo.
Forma conceptual:
__try {
ProbeForRead(user_buffer, size, alignment);
...
memmove(dst, src, huge_size);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
Esto explica por qué el exploit puede escribir lo suficiente para corromper el pool y después caer en exception handling sin tumbar inmediatamente la máquina.
Idea clave¶
La excepción ocurre después de que parte de la copia ya pisó memoria adyacente. El except evita que el sistema muera ahí, pero no deshace la corrupción.
8. Allocation vulnerable en pool¶
Después de pasar los checks, el driver aloca un buffer de pool.
En la clase se ve una allocation de tamaño:
con tag asociado al componente Kernel Streaming.
Ejemplo de !pool visto conceptualmente:
En IDA/MCP el tag aparece como inmediato:
El puntero a esa allocation se guarda reusando un campo del IRP que para esta ruta no se usa realmente.
Reuso de MasterIrp¶
En el decompiler puede aparecer como:
Pero en una ruta METHOD_NEITHER, ese campo no tiene el mismo significado que en otras rutas. La clase lo interpreta como una variable temporal/reuso interno del driver.
Condición previa¶
El código comprueba que ese campo esté en 0 antes de seguir:
Como en esta ruta viene en 0, el driver lo reutiliza para guardar el puntero de pool.
Allocation exacta confirmada¶
La rama vulnerable llega a:
0x1c0002d9c lea eax, [r15+r13] ; AlignedOutputLength + InputBufferLength
0x1c0002dab mov ecx, 61h ; flags de ExAllocatePool2
0x1c0002db0 mov r8d, 7070534Bh ; tag "KSpp"
0x1c0002db6 call ExAllocatePool2
0x1c0002dc2 mov [rsi+18h], rax ; Irp->AssociatedIrp.MasterIrp = pool
Para el caso de clase:
9. El memmove que provoca el overflow¶
El overflow ocurre en una copia hacia el buffer de pool de 0x1000.
La forma mental:
Direcciones confirmadas:
0x1c0002e39 lea r8, [r12-10h] ; size = OutputBufferLength - 0x10
0x1c0002e3e mov rdx, [rsi+70h]
0x1c0002e42 add rdx, 10h ; src = Irp->UserBuffer + 0x10
0x1c0002e46 lea rcx, [r9+20h] ; dst = pool + 0x20
0x1c0002e4a call memmove
El destino pertenece a una allocation de 0x1000, pero el size usado viene del output length enorme.
El resultado:
[KSpp allocation 0x1000][siguiente chunk NpFr][...]
overflow ---------------> pisa header del siguiente chunk
Por qué está calculado “justo”¶
El output buffer user-mode se prepara para que:
- exista y sea accesible durante la parte inicial;
- contenga bytes controlados;
- al copiar más de la cuenta, se pise el chunk siguiente;
- luego ocurra una excepción controlada cuando se intenta seguir leyendo/escribiendo fuera de rango.
La corrupción útil ya ocurrió antes del exception handler.
10. Confirmación en debugger¶
La clase usa IDA debugger conectado a kernel para validar lo visto estáticamente.
Problema práctico de IDA¶
Para ver estructuras apuntadas por registros, a veces IDA no deja dereferenciar si no tiene región de memoria cargada. El workaround mostrado:
Después se puede ir a registros como RDX y aplicar structs.
Registros importantes al entrar¶
| Registro | Significado |
|---|---|
RCX |
0 |
RDX |
PIRP |
R8 |
0 |
R9 |
puntero a status |
Validaciones hechas en runtime¶
RDXefectivamente apunta a unIRP.CurrentStackLocationse levanta desde el IRP.InputBufferLength = 0x1000.OutputBufferLength = 0xFFFFFFF0.- El cálculo de alineación/wrap produce el valor chico esperado.
MasterIrpestá en0al entrar.ProbeForReaddel input pasa.- La allocation de
0x1000aparece en pool. - El chunk siguiente puede ser un
NpFrde pipe. - El overflow real está en
0x1c0002e4a, no en la copia posterior de input.
Comandos útiles¶
Sirve para confirmar tag, tamaño y chunk adyacente.
11. Grooming: por qué usar named pipes¶
Para convertir el overflow en una primitiva útil, el exploit necesita controlar qué objeto queda después de la allocation vulnerable.
El PoC usa named pipes porque permiten crear muchas allocations de tamaño parecido y bastante predecibles.
Objetivo del grooming¶
spray de pipes
↓
liberar un hueco
↓
trigger del driver vulnerable
↓
allocation KSpp cae en el hueco
↓
pipe NpFr queda justo después
↓
overflow pisa header del pipe
Tag observado¶
El tag de los buffers de named pipe es:
En memoria como entero little-endian suele verse invertido, por lo que siempre conviene pensar tanto en ASCII como en valor entero.
Por qué tamaño 0x1000¶
La clase remarca que allocations alrededor de 0x1000 tienden a ir por el pool estándar, no por Low Fragmentation Heap/pool para tamaños chicos. Eso hace más predecible el layout.
Regla práctica mencionada:
| Tamaño | Comportamiento esperado |
|---|---|
| chico, por debajo de cierto umbral | más probable LFH / comportamiento menos lineal |
0x1000 aprox. |
pool estándar, más secuencial/predecible |
12. Pool tags y cómo frenar en allocations NpFr¶
La clase explica dos formas de frenar cuando se aloca un tag específico.
Variable global nt!PoolHitTag¶
WinDbg permite configurar un tag global para que el kernel dispare una excepción controlada cuando una allocation usa ese tag.
Comando conceptual:
Para NpFr, hay que escribir el valor entero correspondiente al tag en little-endian.
Ventaja:
- no es un breakpoint sobre una función ultra usada;
- la comprobación está integrada en la ruta de allocation.
Desventajas:
- no se puede filtrar por proceso;
- puede parar muchas veces si el tag es común;
- conviene activarlo cerca del momento del spray.
Técnica práctica mencionada¶
Si el tag aparece demasiado:
- Preparar el comando
ed nt!PoolHitTag .... - Poner un
MessageBoxo pausa en user-mode antes del spray. - Al continuar, activar el tag justo antes del momento interesante.
- Dejar que pare en las primeras allocations cercanas al spray.
13. Breakpoint genérico vs breakpoint dentro de npfs.sys¶
Otra opción es poner un breakpoint en la función genérica de pool:
El problema: es demasiado usada por todo el sistema.
Incluso con condición por tag, el debugger puede quedar lento o inusable porque el breakpoint se evalúa constantemente.
Breakpoint filtrado por proceso¶
WinDbg permite usar /p para limitar por proceso:
Pero en la práctica sigue siendo incómodo si hay muchas allocations o si el proceso hace otras operaciones del sistema.
Mejor estrategia¶
Una vez ubicada la call específica dentro de npfs.sys, conviene poner el breakpoint ahí, no en nt!ExAllocatePool2 global.
Ahí el breakpoint condicional sí es manejable, porque se evalúa en una ruta mucho más específica.
14. Qué pasa cuando se escriben pipes¶
La clase aclara un punto importante: la allocation NpFr no ocurre cuando se crea el pipe, sino cuando se escribe data.
Flujo user-mode¶
CreateNamedPipe / CreateFile
↓
obtengo handles
↓
WriteFile
↓
npfs.sys aloca buffer NpFr para guardar data
Crear el pipe solo prepara objetos/handles. Para que exista la allocation de data con el tamaño elegido, hay que escribir.
Handles de pipe¶
Un mismo pipe tiene típicamente:
| Handle | Uso |
|---|---|
| read handle | leer desde el pipe |
| write handle | escribir al pipe |
El PoC guarda handles en arrays, por ejemplo:
Luego usa WriteFile para crear las allocations y ReadFile/PeekNamedPipe para inspeccionar o consumir data.
15. Estructuras de npfs.sys con ayuda de ReactOS¶
npfs.sys no trae símbolos tan ricos como ksthunk.sys. Muchas funciones solo tienen nombre, sin prototipos ni argumentos útiles.
Para reconstruir estructuras, la clase usa ReactOS como referencia.
La función relevante se identifica como algo parecido a:
ReactOS ayuda a entender nombres y layouts aproximados:
No hay que asumir que Windows y ReactOS son idénticos, pero sirve muchísimo para orientar el reversing.
Problema de alineación¶
Al copiar estructuras desde ReactOS o reconstruirlas manualmente en IDA, puede fallar la alineación.
Síntomas:
- campos no coinciden con accesos reales;
- quedan bytes vacíos inesperados;
- offsets del decompiler no matchean;
- un campo que debería ser
IRPno apunta a un IRP válido.
La clase recomienda verificar cada campo contra runtime:
Si un campo supuestamente es PIRP, hay que comprobar que realmente apunta a una estructura IRP válida.
16. Objetivo del overflow: pisar DataSize¶
El objeto de pipe tiene un header antes de la data. La estructura conceptual:
typedef struct _NP_DATA_QUEUE_ENTRY_LIKE {
LIST_ENTRY QueueEntry;
PIRP Irp;
ULONG DataEntryType;
ULONG QuotaInEntry;
ULONG DataSize;
UCHAR Data[];
} NP_DATA_QUEUE_ENTRY_LIKE;
La clase remarca que el target del overflow es el campo:
Antes del overflow, el pipe fue escrito con tamaño cercano a 0x1000. Después del overflow, el exploit busca cambiar ese size a algo mayor, por ejemplo:
Layout mental¶
chunk vulnerable KSpp, size 0x1000
[datos controlados ...]
overflow sale del chunk
↓
chunk pipe NpFr
[NP_DATA_QUEUE_ENTRY header]
... DataSize <- campo pisado
[Data[]]
Por qué sirve pisar DataSize¶
Si el pipe cree que tiene más data de la real, operaciones de lectura/peek pueden leer más allá de los límites originales del buffer.
Eso abre la puerta a:
- leak de memoria adyacente;
- identificación del pipe corrompido;
- construcción posterior de primitivas de read/write;
- recuperación de punteros kernel necesarios para la explotación.
17. Detección del pipe overflowdeado¶
El PoC mantiene un array de muchos pipes. Después de disparar el overflow, necesita encontrar cuál quedó corrompido.
La idea:
for each pipe in pipes:
size = PeekNamedPipe(pipe)
if size > expected_threshold:
overflowed_index = i
break
En la clase se menciona una comparación contra un umbral derivado de:
El único pipe que reporta un tamaño anormalmente grande es el que quedó después del chunk vulnerable y cuyo DataSize fue pisado.
Intuición¶
Esto permite transformar una corrupción probabilística en un objeto identificado dentro del array de handles.
18. Plan para doble debugging user/kernel¶
La clase termina preparando el siguiente paso: debuggear a la vez el PoC user-mode y el kernel.
Objetivo¶
Ver simultáneamente:
- qué hace el PoC en user-mode;
- cuándo crea pipes;
- cuándo escribe pipes;
- cuándo llama al IOCTL vulnerable;
- cómo cambia el pool en kernel;
- qué campo se pisa en
NpFr; - cómo se detecta el pipe corrompido;
- cómo se avanza hacia leak y token stealing.
Setup conceptual¶
IDA kernel debugger -> conectado a la VM para ksthunk.sys / npfs.sys
IDA user debugger -> conectado al PoC de 32 bits
Como el PoC es de 32 bits, se usa el servidor/debugger remoto Win32 de IDA para user-mode.
Por qué hacerlo así¶
El bug cruza capas:
PoC user-mode
-> DeviceIoControl
-> ksthunk.sys overflow
-> pool layout
-> npfs.sys pipe object
-> leak / write primitive
Si se mira solo kernel, cuesta entender qué fase del PoC está corriendo. Si se mira solo user-mode, no se ve qué objeto kernel se corrompe.
19. Workflow mental completo¶
1. Partir del IOCTL 0x2F007 identificado en clase 1
2. Reconstruir argumentos: 0, IRP, 0, &status
3. Confirmar UserMode + proceso 32-bit
4. Recuperar CurrentStackLocation desde Irp->Tail.Overlay
5. Cambiar la union a DeviceIoControl
6. Leer InputBufferLength = 0x1000
7. Leer OutputBufferLength = 0xFFFFFFF0
8. Ver wraparound al sumar/alinear OutputBufferLength
9. Confirmar que los checks pasan por el tamaño wrapped
10. Confirmar allocation KSpp de 0x1000
11. Preparar spray/grooming con pipes NpFr de tamaño parecido
12. Ubicar chunk NpFr adyacente con !pool
13. Llegar al memmove que copia tamaño enorme
14. Ver que pisa el header del siguiente NpFr
15. Reconstruir NP_DATA_QUEUE_ENTRY con ayuda de ReactOS
16. Identificar DataSize como campo objetivo
17. Cambiar DataSize de ~0x1000 a ~0x4000
18. Usar PeekNamedPipe/lecturas para encontrar el pipe corrompido
19. Preparar doble debugging para estudiar leak y escritura posteriores
20. Cheat sheet¶
Valores clave¶
| Valor | Significado |
|---|---|
0x2F007 |
IOCTL vulnerable |
0x1000 |
input size y tamaño de allocation vulnerable |
0xFFFFFFF0 |
output size malicioso que provoca wrap |
0x4000 |
tamaño grande usado para marcar pipe corrompido |
KSpp / 0x7070534B |
tag de allocation del componente vulnerable |
KSqb / 0x6271534B |
tag del query buffer usado en la rama sincrónica 0x400 |
NpFr |
tag de buffer/data entry de named pipes |
Funciones verificadas en IDA¶
| Dirección | Función |
|---|---|
0x1c0001320 |
CKernelFilterDevice::DispatchIrp |
0x1c0001eb0 |
CKSThunkDevice::DispatchIoctl |
0x1c00020f0 |
CKSThunkPin::DispatchIoctl |
0x1c0002ab8 |
CKSAutomationThunk::ThunkEnableEventIrp |
0x1c0002e4a |
memmove vulnerable dentro de ThunkEnableEventIrp |
Campos a corregir en IDA¶
| Acceso | Interpretación |
|---|---|
Irp + 0xB8 |
Tail.Overlay.CurrentStackLocation |
IO_STACK_LOCATION.Parameters |
elegir union DeviceIoControl |
Type3InputBuffer |
input user-mode en METHOD_NEITHER |
Irp->UserBuffer |
output user-mode |
AssociatedIrp.MasterIrp |
campo reusado como temporal en esta ruta |
Debugger¶
Pipe exploitation mental model¶
spray NpFr
free hole
allocate KSpp vulnerable chunk
overflow KSpp -> NpFr header
overwrite NpFr.DataSize
find corrupted pipe with PeekNamedPipe
use corrupted pipe for leak/read/write stages
Errores comunes al reversear esta ruta¶
- Confundir
METHOD_BUFFEREDconMETHOD_NEITHER. - Tomar
SystemBuffercomo válido cuando la ruta usaType3InputBuffer/UserBuffer. - Creer que
ProbeForReadevita el overflow. - Mirar el size después del wrap y olvidar el valor original.
- Poner breakpoints globales en
ExAllocatePool2y congelar el sistema. - Copiar structs de ReactOS sin verificar offsets/alineación.
- Cerrar/liberar objetos corruptos antes de restaurar o controlar su estado.
21. Glosario corto¶
| Término | Descripción |
|---|---|
METHOD_NEITHER |
Método de IOCTL donde el driver recibe punteros user-mode directos y debe validarlos manualmente. |
Type3InputBuffer |
Puntero al input user-mode para rutas METHOD_NEITHER. |
UserBuffer |
Puntero user-mode usado como output buffer. |
ProbeForRead |
API kernel para validar lectura desde un rango user-mode; puede levantar excepción. |
| Integer overflow / wraparound | Cálculo de tamaño que se desborda y vuelve a un valor chico. |
| Pool grooming | Técnica para influir qué objetos quedan adyacentes en pool. |
NpFr |
Pool tag asociado a buffers/data entries de named pipes. |
KSpp |
Tag observado para la allocation vulnerable en la ruta Kernel Streaming. |
PoolHitTag |
Variable global de kernel para frenar cuando se aloca un tag específico. |
NP_DATA_QUEUE_ENTRY |
Estructura conceptual de npfs.sys que representa una entrada de data de pipe. |
DataSize |
Campo del data entry de pipe que indica cuántos bytes cree tener disponibles. |
PeekNamedPipe |
API user-mode para consultar datos disponibles en un pipe sin consumirlos completamente. |
Apunte basado en la transcripción clase-modulo6-2 del Módulo 6. La clase conecta el IOCTL vulnerable con el integer overflow real, el pool overflow y el uso de named pipes NpFr como objeto de grooming para convertir la corrupción en primitivas explotables.