> Windows Syscalls
ntoskrnl.exeT1068T1134T1559

NtAlpcOpenSenderProcess

Server-side helper that opens a HANDLE to the process that sent a given ALPC message.

Prototype

NTSTATUS NtAlpcOpenSenderProcess(
  PHANDLE             ProcessHandle,
  HANDLE              PortHandle,
  PPORT_MESSAGE       PortMessage,
  ULONG               Flags,
  ACCESS_MASK         DesiredAccess,
  POBJECT_ATTRIBUTES  ObjectAttributes
);

Arguments

NameTypeDirDescription
ProcessHandlePHANDLEoutReceives a HANDLE to the sender's EPROCESS object, opened with DesiredAccess.
PortHandleHANDLEinHandle to the server's ALPC communication port that received the message.
PortMessagePPORT_MESSAGEinThe PORT_MESSAGE just returned from NtAlpcSendWaitReceivePort; its ClientId.UniqueProcess identifies the sender.
FlagsULONGinReserved / context flags. Typically 0; kernel-mode callers may pass ALPC_OPENSENDER_KERNEL_MODE.
DesiredAccessACCESS_MASKinAccess mask for the returned handle (e.g. PROCESS_QUERY_LIMITED_INFORMATION for an identity check, PROCESS_DUP_HANDLE for handle smuggling).
ObjectAttributesPOBJECT_ATTRIBUTESinObject-attributes block (usually inheritance flags only). May be NULL.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
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

Kernel module

ntoskrnl.exeNtAlpcOpenSenderProcess

Related APIs

NtAlpcOpenSenderThreadNtAlpcImpersonateClientOfPortNtAlpcSendWaitReceivePortNtAlpcAcceptConnectPortNtOpenProcessTokenNtQueryInformationProcess

Syscall stub

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

Undocumented notes

`NtAlpcOpenSenderProcess` is the server's **authoritative client-identity hook**. After `NtAlpcSendWaitReceivePort` returns a `PORT_MESSAGE`, the server holds only the message header — which is fully attacker-controlled in every field *except* `ClientId`, which the kernel stamps from the sender's EPROCESS at send time. This syscall takes that `ClientId.UniqueProcess` and returns a real `HANDLE` to the EPROCESS object, opened with the requested access. Used to: (a) check the sender's image path (`QueryFullProcessImageName`), (b) check its token (`NtOpenProcessToken`), (c) check its protection level (PsProtectedSignerSystem etc. via `NtQueryInformationProcess(ProcessProtectionInformation)`). The kernel implementation `AlpcpOpenSender` validates that the message actually arrived on the supplied port and that the sender still exists — pids are not reused while the message holds a reference, so PID-recycle races are not possible *through this primitive*.

Common malware usage

The primitive itself is benign; **bugs in its callers** are not. Three notorious classes: 1. **Forgotten identity check** — a privileged ALPC server (TaskHost, Spooler, DiagTrack) trusts `PORT_MESSAGE.ClientId` for a privileged decision *without* calling `NtAlpcOpenSenderProcess` to canonicalise. An attacker spoofs `ClientId` in a crafted message and triggers a privileged action. **CVE-2018-8440** (SandboxEscaper's ALPC LPE, Task Scheduler) and several PrintNightmare-adjacent bugs were variants of this. 2. **Wrong-DesiredAccess** — server opens with `PROCESS_DUP_HANDLE` 'just in case' and then leaks the handle back to the client; client uses it to inject into the server. Real bug in WinHTTP service, ca. 2020. 3. **Sender-identity TOCTOU** — server opens the sender, reads its image path, then performs work; meanwhile the sender execs a privileged binary via `NtCreateUserProcess` re-using the same pid (rare; needs PROCESS_CREATE_PROCESS). Offensive tooling: the **rpcview** / **alpc-fuzzing** lineage (Clément Rouault, James Forshaw) exercises this exact path to map server-side trust boundaries.

Detection opportunities

Outside `lsass`, `services.exe`, `spoolsv.exe`, Task Scheduler, and a handful of well-known RPC servers, calls to `NtAlpcOpenSenderProcess` are uncommon. ETW provider `Microsoft-Windows-Threat-Intelligence` (Defender-only) traces handle opens with origin; `Microsoft-Windows-RPC` shows the corresponding RPC call if the server uses LRPC. Hunt for **unexpected privileged servers** opening senders — a user-context implant pretending to be an RPC server will issue this syscall against incoming connections from other implants. Pair with `NtOpenProcessToken` on the same returned handle to catch the full identity-check pattern, and with token impersonation downstream (`NtAlpcImpersonateClientOfPort` or `ImpersonateLoggedOnUser`) — that triplet is the classic ALPC trust boundary, and any caller not on the Microsoft allowlist warrants triage.

Direct syscall examples

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) }
}

MITRE ATT&CK mappings

Last verified: 2026-05-20