> Windows Syscalls
ntoskrnl.exeT1068T1134T1559

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

NameTypeDirDescription
ProcessHandlePHANDLEoutRecibe un HANDLE al objeto EPROCESS del remitente, abierto con DesiredAccess.
PortHandleHANDLEinHandle al puerto de comunicación ALPC del servidor que recibió el mensaje.
PortMessagePPORT_MESSAGEinEl PORT_MESSAGE recién devuelto por NtAlpcSendWaitReceivePort; su ClientId.UniqueProcess identifica al remitente.
FlagsULONGinBanderas reservadas / contextuales. Típicamente 0; quienes llaman en kernel-mode pueden pasar ALPC_OPENSENDER_KERNEL_MODE.
DesiredAccessACCESS_MASKinMáscara de acceso para el handle devuelto (p. ej. PROCESS_QUERY_LIMITED_INFORMATION para chequear identidad, PROCESS_DUP_HANDLE para handle smuggling).
ObjectAttributesPOBJECT_ATTRIBUTESinBloque de atributos de objeto (normalmente sólo flags de herencia). Puede ser NULL.

IDs de syscalls por versión de Windows

Versión de WindowsID de syscallBuild
Win10 15070x83win10-1507
Win10 16070x83win10-1607
Win10 17030x84win10-1703
Win10 17090x84win10-1709
Win10 18030x85win10-1803
Win10 18090x85win10-1809
Win10 19030x85win10-1903
Win10 19090x85win10-1909
Win10 20040x87win10-2004
Win10 20H20x87win10-20h2
Win10 21H10x87win10-21h1
Win10 21H20x87win10-21h2
Win10 22H20x87win10-22h2
Win11 21H20x87win11-21h2
Win11 22H20x87win11-22h2
Win11 23H20x87win11-23h2
Win11 24H20x89win11-24h2
Server 20160x83winserver-2016
Server 20190x85winserver-2019
Server 20220x87winserver-2022
Server 20250x89winserver-2025

Módulo del kernel

ntoskrnl.exeNtAlpcOpenSenderProcess

APIs relacionadas

NtAlpcOpenSenderThreadNtAlpcImpersonateClientOfPortNtAlpcSendWaitReceivePortNtAlpcAcceptConnectPortNtOpenProcessTokenNtQueryInformationProcess

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 ENDP

rustDynamic 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