NtAlpcOpenSenderProcess
Ayudante del lado servidor que abre un HANDLE al proceso que envió un mensaje ALPC dado.
Prototipo
NTSTATUS NtAlpcOpenSenderProcess( PHANDLE ProcessHandle, HANDLE PortHandle, PPORT_MESSAGE PortMessage, ULONG Flags, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | PHANDLE | out | Recibe un HANDLE al objeto EPROCESS del remitente, abierto con DesiredAccess. |
| PortHandle | HANDLE | in | Handle al puerto de comunicación ALPC del servidor que recibió el mensaje. |
| PortMessage | PPORT_MESSAGE | in | El PORT_MESSAGE recién devuelto por NtAlpcSendWaitReceivePort; su ClientId.UniqueProcess identifica al remitente. |
| Flags | ULONG | in | Banderas reservadas / contextuales. Típicamente 0; quienes llaman en kernel-mode pueden pasar ALPC_OPENSENDER_KERNEL_MODE. |
| DesiredAccess | ACCESS_MASK | in | Máscara de acceso para el handle devuelto (p. ej. PROCESS_QUERY_LIMITED_INFORMATION para chequear identidad, PROCESS_DUP_HANDLE para handle smuggling). |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Bloque de atributos de objeto (normalmente sólo flags de herencia). Puede ser NULL. |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x83 | win10-1507 |
| Win10 1607 | 0x83 | win10-1607 |
| Win10 1703 | 0x84 | win10-1703 |
| Win10 1709 | 0x84 | win10-1709 |
| Win10 1803 | 0x85 | win10-1803 |
| Win10 1809 | 0x85 | win10-1809 |
| Win10 1903 | 0x85 | win10-1903 |
| Win10 1909 | 0x85 | win10-1909 |
| Win10 2004 | 0x87 | win10-2004 |
| Win10 20H2 | 0x87 | win10-20h2 |
| Win10 21H1 | 0x87 | win10-21h1 |
| Win10 21H2 | 0x87 | win10-21h2 |
| Win10 22H2 | 0x87 | win10-22h2 |
| Win11 21H2 | 0x87 | win11-21h2 |
| Win11 22H2 | 0x87 | win11-22h2 |
| Win11 23H2 | 0x87 | win11-23h2 |
| Win11 24H2 | 0x89 | win11-24h2 |
| Server 2016 | 0x83 | winserver-2016 |
| Server 2019 | 0x85 | winserver-2019 |
| Server 2022 | 0x87 | winserver-2022 |
| Server 2025 | 0x89 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 89 00 00 00 mov eax, 0x89 F6 04 25 08 03 FE 7F 01 test byte ptr [0x7FFE0308], 1 75 03 jne short +3 0F 05 syscall C3 ret CD 2E int 2Eh C3 ret
Notas no documentadas
`NtAlpcOpenSenderProcess` es el **gancho autoritativo de identidad de cliente** del servidor. Tras que `NtAlpcSendWaitReceivePort` devuelva un `PORT_MESSAGE`, el servidor sólo tiene la cabecera de mensaje — totalmente controlable por el atacante en cada campo *excepto* `ClientId`, que el kernel estampa desde el EPROCESS del remitente en el envío. Este syscall toma ese `ClientId.UniqueProcess` y devuelve un `HANDLE` real al objeto EPROCESS, abierto con el acceso solicitado. Sirve para: (a) verificar la ruta de imagen (`QueryFullProcessImageName`), (b) verificar el token (`NtOpenProcessToken`), (c) verificar el nivel de protección (PsProtectedSignerSystem, etc., vía `NtQueryInformationProcess(ProcessProtectionInformation)`). La implementación de kernel `AlpcpOpenSender` valida que el mensaje llegó al puerto suministrado y que el remitente sigue existiendo — los pids no se reutilizan mientras el mensaje mantiene una referencia, así que las carreras de PID-recycle no son posibles *vía esta primitiva*.
Uso común por malware
La primitiva en sí es benigna; **los bugs en quienes la llaman** no. Tres clases notorias: 1. **Verificación de identidad olvidada** — un servidor ALPC privilegiado (TaskHost, Spooler, DiagTrack) confía en `PORT_MESSAGE.ClientId` para una decisión privilegiada *sin* llamar `NtAlpcOpenSenderProcess` para canonicalizar. Un atacante spoofea `ClientId` en un mensaje forjado y dispara una acción privilegiada. **CVE-2018-8440** (LPE ALPC de SandboxEscaper, Task Scheduler) y varios bugs cercanos a PrintNightmare fueron variantes. 2. **DesiredAccess equivocado** — el servidor abre con `PROCESS_DUP_HANDLE` 'por si acaso' y luego filtra el handle al cliente; el cliente lo usa para inyectar en el servidor. Bug real en el servicio WinHTTP hacia 2020. 3. **TOCTOU de identidad del remitente** — el servidor abre al remitente, lee su ruta de imagen, luego trabaja; mientras tanto el remitente ejecuta un binario privilegiado vía `NtCreateUserProcess` reutilizando el mismo pid (raro; requiere PROCESS_CREATE_PROCESS). Herramientas ofensivas: el linaje **rpcview** / **alpc-fuzzing** (Clément Rouault, James Forshaw) ejercita exactamente este camino para cartografiar las fronteras de confianza del lado servidor.
Oportunidades de detección
Fuera de `lsass`, `services.exe`, `spoolsv.exe`, Task Scheduler y un puñado de servidores RPC bien conocidos, las llamadas a `NtAlpcOpenSenderProcess` son poco comunes. El proveedor ETW `Microsoft-Windows-Threat-Intelligence` (sólo Defender) traza aperturas de handle con origen; `Microsoft-Windows-RPC` muestra la llamada RPC correspondiente si el servidor usa LRPC. Cazar **servidores privilegiados inesperados** abriendo sus remitentes — un implante en contexto de usuario que aparenta ser servidor RPC emitirá este syscall contra conexiones entrantes de otros implantes. Combinar con `NtOpenProcessToken` sobre el mismo handle para atrapar el patrón completo de chequeo de identidad, y con la impersonación de token aguas abajo (`NtAlpcImpersonateClientOfPort` o `ImpersonateLoggedOnUser`) — ese triplete es la frontera de confianza ALPC clásica, y cualquier llamante fuera de la allowlist Microsoft amerita triage.
Ejemplos de syscalls directos
cALPC server identity-check pattern
// Canonical server-side trust gate: never trust PORT_MESSAGE.ClientId for an
// authorisation decision — always re-derive the sender via NtAlpcOpenSenderProcess.
#include <windows.h>
#include <winternl.h>
NTSTATUS NTAPI NtAlpcOpenSenderProcess(
PHANDLE, HANDLE, PPORT_MESSAGE, ULONG, ACCESS_MASK, POBJECT_ATTRIBUTES);
BOOL CallerIsSystem(HANDLE serverPort, PPORT_MESSAGE msg) {
HANDLE hSender = NULL;
if (NtAlpcOpenSenderProcess(
&hSender, serverPort, msg,
0,
PROCESS_QUERY_LIMITED_INFORMATION,
NULL) < 0) {
return FALSE;
}
HANDLE hTok = NULL;
BOOL ok = OpenProcessToken(hSender, TOKEN_QUERY, &hTok);
if (hSender) CloseHandle(hSender);
if (!ok) return FALSE;
BYTE buf[512]; DWORD ret = 0;
BOOL isSystem = FALSE;
if (GetTokenInformation(hTok, TokenUser, buf, sizeof(buf), &ret)) {
PTOKEN_USER tu = (PTOKEN_USER)buf;
// Compare tu->User.Sid against S-1-5-18 (LocalSystem)
SID localSystem = {SID_REVISION, 1, {0,0,0,0,0,5}, {18}};
isSystem = EqualSid(tu->User.Sid, &localSystem);
}
CloseHandle(hTok);
return isSystem;
}asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtAlpcOpenSenderProcess (SSN 0x89 on Win11 24H2 / Server 2025)
NtAlpcOpenSenderProcess PROC
mov r10, rcx ; ProcessHandle
mov eax, 89h ; SSN
syscall
ret
NtAlpcOpenSenderProcess ENDPrustDynamic resolution + null-OA call
// Cargo: ntapi = "0.4", windows-sys = "0.59"
use std::ptr::null_mut;
use windows_sys::Win32::Foundation::HANDLE;
type FnOpenSender = unsafe extern "system" fn(
*mut HANDLE, HANDLE, *const u8, u32, u32, *const u8,
) -> i32;
pub unsafe fn open_sender_process(
server_port: HANDLE, msg: *const u8, desired: u32,
) -> Option<HANDLE> {
let ntdll = libloading::Library::new("ntdll.dll").ok()?;
let f: libloading::Symbol<FnOpenSender> =
ntdll.get(b"NtAlpcOpenSenderProcess\0").ok()?;
let mut h: HANDLE = 0;
let st = f(&mut h, server_port, msg, 0, desired, std::ptr::null());
if st < 0 { None } else { Some(h) }
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20