Pipes en Windows y explotacion de drivers¶
Resumen: Un pipe en Windows es un canal de IPC expuesto como objeto del kernel y accesible mediante handles. Entender pipes ayuda a conectar tres ideas del modulo: el namespace de objetos (
\\.\pipe\X->\Device\NamedPipe\X), el patronCreateFile/ReadFile/WriteFile/DeviceIoControl, y el uso de pipes como herramienta de kernel pool spraying para explotar un UAF en drivers como HEVD.
<- Volver al Modulo 5¶
Tabla de Contenidos¶
- Idea base: pipe como IPC
- Pipes como objetos del kernel
- Anonymous pipes vs named pipes
- APIs principales
CreateFileno significa solo "archivo"- Named Pipe Impersonation
- Conexion con HEVD: UAF en kernel pool
- Que significa "spammear pipes"
- Por que pipes sirven como reclaimer
- Secuencia mental del exploit
- Fake object placement
- Debugging y verificacion
- Cheat sheet
- Recursos
1. Idea base: pipe como IPC¶
IPC significa Inter-Process Communication. Windows tiene varias primitivas para que procesos se comuniquen: memoria compartida, mailslots, sockets, RPC, ALPC, COM y pipes.
Un pipe es la version conceptual mas simple:
Un extremo escribe bytes o mensajes; el otro extremo los lee. Por eso muchas APIs de pipes usan nombres de I/O comun:
Aunque la API parezca de archivos, el objeto subyacente no tiene por que ser un archivo de disco. En Windows, muchas cosas se manipulan igual: un path se resuelve a un objeto del kernel, se obtiene un HANDLE, y despues se hacen operaciones sobre ese handle.
2. Pipes como objetos del kernel¶
Los named pipes viven dentro del Windows Object Manager namespace. El path Win32 tipico:
se traduce internamente a un objeto bajo el device de NPFS, el Named Pipe File System:
La idea importante para reversing/exploitation:
Esto se parece mucho a como una aplicacion habla con un driver:
Por eso los pipes son buenos para entender drivers: ambos pasan por el modelo de objetos, handles, ACLs e I/O del kernel.
3. Anonymous pipes vs named pipes¶
Anonymous pipe¶
Un anonymous pipe no tiene un nombre global elegido por el programador. Normalmente se usa entre procesos relacionados, por ejemplo padre e hijo:
Usos comunes:
- Redirigir
stdin,stdoutostderrde un proceso hijo. - Conectar dos procesos cuando ya se pueden pasar handles.
- Armar sprays simples con
CreatePipe+WriteFile.
Punto clave: Microsoft documenta que CreatePipe crea un pipe anonimo, pero internamente estos pipes se implementan usando un named pipe con nombre unico. En la practica, tambien terminan pasando por NPFS.
Named pipe¶
Un named pipe si tiene un nombre visible para otros procesos:
Modelo tipico:
Servidor:
CreateNamedPipe("\\\\.\\pipe\\demo", ...)
ConnectNamedPipe(...)
ReadFile(...)
WriteFile(...)
Cliente:
CreateFile("\\\\.\\pipe\\demo", ...)
WriteFile(...)
ReadFile(...)
Los named pipes pueden ser locales o remotos, unidireccionales o duplex, de bytes o de mensajes. Cada instancia comparte el mismo nombre, pero tiene sus propios buffers y handles.
4. APIs principales¶
| API | Rol |
|---|---|
CreatePipe |
Crea un pipe anonimo y devuelve handles de lectura/escritura |
CreateNamedPipeW |
Crea el extremo servidor de un named pipe |
ConnectNamedPipe |
Espera o completa la conexion de un cliente |
CreateFileW |
El cliente abre \\.\pipe\Nombre o un device como \\.\HEVD |
ReadFile |
Lee datos desde el handle |
WriteFile |
Escribe datos al handle |
ImpersonateNamedPipeClient |
El servidor adopta temporalmente el token del cliente |
DisconnectNamedPipe |
Desconecta una instancia de named pipe |
CloseHandle |
Libera el handle; el objeto se destruye cuando no quedan referencias |
Ejemplo minimo de named pipe:
HANDLE hPipe = CreateNamedPipeW(
L"\\\\.\\pipe\\demo",
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1,
0x1000,
0x1000,
0,
NULL
);
ConnectNamedPipe(hPipe, NULL);
Cliente:
HANDLE hClient = CreateFileW(
L"\\\\.\\pipe\\demo",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL
);
5. CreateFile no significa solo "archivo"¶
En Win32, CreateFile es una API generica para abrir objetos que exponen semantica de I/O.
Esto abre un archivo:
Esto abre un pipe:
Esto abre un device/driver:
Despues, con un driver, la comunicacion no suele ser ReadFile/WriteFile, sino DeviceIoControl:
DeviceIoControl(
hDevice,
IOCTL_CODE,
inputBuffer,
inputSize,
outputBuffer,
outputSize,
&bytesReturned,
NULL
);
La similitud conceptual:
Pipe:
CreateFile("\\\\.\\pipe\\demo")
ReadFile / WriteFile
Driver:
CreateFile("\\\\.\\DeviceName")
DeviceIoControl
El aprendizaje es el mismo: user-mode obtiene un handle a un objeto kernel y le manda requests.
6. Named Pipe Impersonation¶
Otra relacion entre pipes y explotacion es la impersonation. Windows permite que el servidor de un named pipe impersone el contexto de seguridad del cliente que envio el ultimo mensaje leido.
Flujo legitimo:
1. Cliente se conecta al pipe de un servicio.
2. Cliente manda una request.
3. Servicio llama ImpersonateNamedPipeClient(hPipe).
4. El thread del servicio actua temporalmente con el token del cliente.
5. Servicio llama RevertToSelf().
Uso defensivo normal:
Servicio corre como SYSTEM.
Usuario normal pide acceder a un archivo.
Servicio impersona al usuario antes de tocar el archivo.
Asi evita leer/escribir algo que el usuario no deberia poder tocar.
Uso ofensivo clasico:
Atacante crea un pipe servidor.
Fuerza a un servicio privilegiado a conectarse como cliente.
El atacante llama ImpersonateNamedPipeClient.
Si el token y privilegios lo permiten, duplica ese token y spawnea proceso privilegiado.
Esto es una familia distinta a HEVD UAF. En HEVD, los pipes no se explotan por impersonation; se usan como herramienta para moldear memoria del kernel.
7. Conexion con HEVD: UAF en kernel pool¶
En un Use-After-Free, el driver libera un objeto pero conserva un puntero viejo. Luego vuelve a usar ese puntero.
Modelo simplificado:
typedef struct _UAF_OBJECT {
void (*Callback)(void);
char Buffer[0x50];
} UAF_OBJECT;
UAF_OBJECT *g_Object;
g_Object = ExAllocatePoolWithTag(...);
g_Object->Callback = LegitFunction;
ExFreePoolWithTag(g_Object, ...);
// BUG: g_Object sigue apuntando al chunk liberado
g_Object->Callback();
Despues del free, la direccion sigue siendo una direccion kernel valida, pero esa memoria ya no pertenece logicamente al objeto original:
Antes:
[ UAF_OBJECT ]
[ Callback = LegitFunction ]
[ data... ]
Despues del free:
[ chunk libre en kernel pool ]
^
g_Object todavia apunta aca
La explotacion intenta que otra allocation controlada por el atacante ocupe ese mismo chunk antes del use.
Despues del reclaim:
[ fake object controlado por atacante ]
^
g_Object ahora interpreta esta memoria como UAF_OBJECT
8. Que significa "spammear pipes"¶
"Spammear pipes" significa crear muchos pipes y escribir datos en ellos para provocar muchas allocations en kernel pool. No se busca atacar al pipe; se usa el pipe como spray primitive.
Conceptualmente:
for (int i = 0; i < MANY; i++) {
CreatePipe(&readPipe[i], &writePipe[i], NULL, 0x1000);
}
// despues del free del objeto vulnerable
for (int i = 0; i < MANY; i++) {
WriteFile(writePipe[i], fakeObject, fakeObjectSize, &written, NULL);
}
Eso genera muchas allocations parecidas en el kernel:
Pipe 0 -> buffer kernel con datos controlados
Pipe 1 -> buffer kernel con datos controlados
Pipe 2 -> buffer kernel con datos controlados
...
El objetivo estadistico:
En heap/pool exploitation, esto se llama pool spraying o kernel pool feng shui: ordenar el estado del allocator para aumentar la probabilidad de que una allocation caiga en el hueco exacto.
9. Por que pipes sirven como reclaimer¶
Los pipes son buenos reclaimers por varias razones:
| Propiedad | Por que importa |
|---|---|
| Alocan en kernel | Sirven para competir por chunks del kernel pool |
Usan NPFS (npfs.sys) |
El subsystem de pipes crea objetos y buffers internos |
| Tamanos influenciables | CreateNamedPipe recibe tamanos de buffers y WriteFile fuerza espacio para datos |
| Contenido controlado | Los bytes escritos vienen desde user-mode |
| Faciles de crear en masa | Cientos o miles de handles son triviales en laboratorio |
| Liberables | Cerrando handles se puede desmontar parte del layout |
Microsoft documenta que al crear named pipes el sistema crea buffers inbound/outbound usando nonpaged pool, y que los tamanos son advisory: el sistema puede redondearlos a boundaries internos. Esa frase explica por que los pipes aparecen tanto en kernel pool exploitation: son una API user-mode que empuja allocations en memoria kernel no paginable.
La parte delicada es el tamano. El allocator no ve "un objeto HEVD" y "un pipe buffer"; ve requests de memoria redondeados a clases/buckets.
Objeto UAF liberado:
request: 0x58
bucket real: 0x60 / 0x70 segun build, header y alineacion
Pipe buffer:
write size: N
allocation real: redondeada por NPFS + pool allocator
Objetivo:
que ambos compitan en una clase compatible
Si el tamano no calza, el spray puede ser enorme y aun asi no reclamar el slot correcto.
10. Secuencia mental del exploit¶
El flujo conceptual para un UAF de HEVD con pipe spray:
1. Groom inicial
Crear objetos para estabilizar el pool.
2. Crear huecos
Liberar algunos objetos para dejar espacios repetibles.
3. Allocate vulnerable object
Pedirle a HEVD que cree el UAF_OBJECT.
4. Free vulnerable object
Pedirle a HEVD que lo libere, dejando el dangling pointer.
5. Spray / reclaim con pipes
Escribir payloads en muchos pipes para ocupar el hueco liberado.
6. Trigger
Pedirle a HEVD que use g_Object.
7. Resultado
El driver interpreta el pipe buffer como si fuera UAF_OBJECT.
Visual:
Pool estable:
[A][A][A][A][A][A][A]
Huecos:
[A][ ][A][ ][A][ ][A]
HEVD alloca:
[A][UAF_OBJECT][A][ ][A][ ][A]
HEVD free:
[A][ FREE ][A][ ][A][ ][A]
^
g_Object sigue apuntando aca
Pipe spray:
[A][PIPE_BUFFER_CONTROLADO][A][PIPE][A][PIPE][A]
^
g_Object ahora cae sobre datos controlados
Trigger:
g_Object->Callback()
11. Fake object placement¶
Supongamos que el objeto vulnerable empieza con un callback:
El fake object que se escribe dentro del pipe buffer tiene que respetar ese layout:
offset 0x00: puntero que el driver va a usar como Callback
offset 0x08: padding / datos esperados
offset 0x10: mas datos
...
En laboratorio se puede usar un marker para confirmar control:
Si al triggerear el UAF el sistema intenta saltar a 0x4141414141414141, no se "exploto" todavia, pero se confirmo algo importante:
1. El free ocurrio.
2. El dangling pointer sobrevivio.
3. El spray reclamo el chunk.
4. El driver interpreto datos controlados como objeto.
5. Hay control del indirect call / function pointer.
En sistemas modernos, controlar un function pointer no alcanza por si solo:
| Mitigacion | Efecto |
|---|---|
| SMEP | Kernel no puede ejecutar shellcode en paginas user-mode |
| SMAP | Kernel no puede leer/escribir user-mode sin habilitarlo explicitamente |
| KASLR | Hay que conocer direcciones kernel validas |
| kCFG / CFG | Llamadas indirectas pueden ser validadas |
| Pool hardening | Reuse menos determinista y metadatos mas robustos |
Por eso en targets reales se suele pasar a ROP kernel, data-only attacks o primitivas de read/write mas estables. Para el modulo, la idea central es entender el reclaim del chunk y el control de layout.
12. Debugging y verificacion¶
WinDbg ayuda a separar "el bug existe" de "el spray reclamo el slot".
Comandos utiles:
!pool <addr> ; muestra el chunk al que pertenece una direccion
!pool <addr> 2 ; mas detalle
!poolfind <tag> ; busca chunks por pool tag
dt nt!_POOL_HEADER <addr>
!handle <handle> ; inspecciona handles del proceso
!object \Device\NamedPipe
Workflow de verificacion:
1. Breakpoint en Allocate UAF.
2. Guardar la direccion del objeto.
3. `!pool <addr>` para ver tag, tamano y estado.
4. Breakpoint despues de Free UAF.
5. Verificar que el chunk queda free pero la global no se nulifica.
6. Ejecutar pipe spray.
7. Volver a mirar `!pool <addr>` y memoria alrededor.
8. Breakpoint en el indirect call del Use.
9. Confirmar que el primer qword del fake object controla el destino.
Si el destino todavia apunta a la funcion legitima, no se reclamo el slot. Si apunta a un marker (0x4141...) o a datos del fake object, el spray esta funcionando.
13. Cheat sheet¶
| Concepto | Frase corta |
|---|---|
| Pipe | Canal IPC entre procesos |
| Named pipe | Pipe con nombre en \\.\pipe\Nombre |
| Anonymous pipe | Pipe sin nombre visible; CreatePipe devuelve dos handles |
| NPFS | Named Pipe File System (npfs.sys) |
| Handle | Referencia user-mode a un objeto kernel |
CreateFile |
Abre archivos, pipes, devices y otros objetos con semantica de I/O |
DeviceIoControl |
Envia IOCTLs a un driver |
| Pipe impersonation | Servidor adopta temporalmente el token del cliente |
| UAF | Puntero viejo usado despues de liberar el objeto |
| Pool spray | Muchas allocations para moldear el allocator |
| Reclaimer | Objeto usado para ocupar un chunk liberado |
| Fake object | Layout controlado que reemplaza logicamente al objeto UAF |
Regla practica:
Pipes para entender drivers:
ambos son objetos kernel abiertos por handles.
Pipes para explotar UAF:
sirven para meter muchas allocations controladas en kernel pool.
Pipes para impersonation:
sirven para robar/adoptar tokens cuando un cliente privilegiado se conecta.
14. Recursos¶
| Recurso | URL |
|---|---|
| Chat base de estos apuntes | https://chatgpt.com/share/69fd0acb-9218-83e9-8d78-a8e30ddbf9e3 |
| Microsoft Learn - Named Pipes | https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes |
| Microsoft Learn - Pipe Names | https://learn.microsoft.com/en-us/windows/win32/ipc/pipe-names |
| Microsoft Learn - CreatePipe | https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe |
| Microsoft Learn - CreateNamedPipeA | https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea |
| Microsoft Learn - ImpersonateNamedPipeClient | https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-impersonatenamedpipeclient |
| HEVD repo | https://github.com/hacksysteam/HackSysExtremeVulnerableDriver |
| HEVD x64 UAF - Generic Non-Paged Pool Feng-Shui | https://securityinsecurity.github.io/exploiting-hevd-use-after-free/ |
Extra basado en la conversacion sobre pipes en Windows, CreateFile, named pipe impersonation y el uso de pipes como primitive de kernel pool spraying para el UAF de HEVD.