> Windows Syscalls
ntoskrnl.exeT1068T1134T1559

NtAlpcOpenSenderProcess

Server-seitiger Helper, der ein HANDLE auf den Prozess öffnet, der eine bestimmte ALPC-Nachricht gesendet hat.

Prototyp

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

Argumente

NameTypeDirDescription
ProcessHandlePHANDLEoutEmpfängt ein HANDLE auf das EPROCESS-Objekt des Senders, geöffnet mit DesiredAccess.
PortHandleHANDLEinHandle auf den ALPC-Kommunikationsport des Servers, der die Nachricht empfangen hat.
PortMessagePPORT_MESSAGEinDie gerade von NtAlpcSendWaitReceivePort gelieferte PORT_MESSAGE; ihre ClientId.UniqueProcess identifiziert den Sender.
FlagsULONGinReservierte / Kontext-Flags. Üblicherweise 0; Kernel-Mode-Aufrufer können ALPC_OPENSENDER_KERNEL_MODE übergeben.
DesiredAccessACCESS_MASKinAccess-Mask für das zurückgegebene Handle (z. B. PROCESS_QUERY_LIMITED_INFORMATION zur Identitätsprüfung, PROCESS_DUP_HANDLE für Handle-Smuggling).
ObjectAttributesPOBJECT_ATTRIBUTESinObject-Attributes-Block (üblicherweise nur Vererbungs-Flags). Kann NULL sein.

Syscall-IDs pro 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-Modul

ntoskrnl.exeNtAlpcOpenSenderProcess

Verwandte 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

Undokumentierte Hinweise

`NtAlpcOpenSenderProcess` ist der **autoritative Client-Identity-Hook** des Servers. Nachdem `NtAlpcSendWaitReceivePort` ein `PORT_MESSAGE` zurückgibt, hält der Server nur den Nachrichtenkopf — der in jedem Feld vollständig angreifer-kontrolliert ist *außer* `ClientId`, das der Kernel beim Send aus dem EPROCESS des Senders stempelt. Dieser Syscall nimmt das `ClientId.UniqueProcess` und liefert ein echtes `HANDLE` auf das EPROCESS-Objekt, geöffnet mit dem gewünschten Access. Verwendung: (a) Image-Pfad des Senders prüfen (`QueryFullProcessImageName`), (b) dessen Token prüfen (`NtOpenProcessToken`), (c) Protection-Level prüfen (PsProtectedSignerSystem etc. via `NtQueryInformationProcess(ProcessProtectionInformation)`). Die Kernel-Implementierung `AlpcpOpenSender` validiert, dass die Nachricht tatsächlich auf dem übergebenen Port ankam und der Sender noch existiert — PIDs werden nicht recycled, solange die Nachricht eine Referenz hält, also sind PID-Recycle-Races *über diese Primitive* nicht möglich.

Häufige Malware-Nutzung

Die Primitive selbst ist benign; **Bugs in ihren Aufrufern** sind es nicht. Drei berüchtigte Klassen: 1. **Vergessene Identitätsprüfung** — ein privilegierter ALPC-Server (TaskHost, Spooler, DiagTrack) vertraut `PORT_MESSAGE.ClientId` für eine privilegierte Entscheidung *ohne* `NtAlpcOpenSenderProcess` zur Kanonisierung aufzurufen. Ein Angreifer spooft `ClientId` in einer präparierten Nachricht und triggert eine privilegierte Aktion. **CVE-2018-8440** (SandboxEscapers ALPC-LPE, Task Scheduler) und mehrere PrintNightmare-nahe Bugs waren Varianten. 2. **Falsches DesiredAccess** — der Server öffnet mit `PROCESS_DUP_HANDLE` 'für alle Fälle' und leakt das Handle dann an den Client zurück; der Client nutzt es zur Injection in den Server. Echter Bug im WinHTTP-Dienst, ca. 2020. 3. **Sender-Identity-TOCTOU** — der Server öffnet den Sender, liest dessen Image-Pfad, arbeitet; währenddessen exec der Sender ein privilegiertes Binary via `NtCreateUserProcess` und nutzt die gleiche PID weiter (selten; benötigt PROCESS_CREATE_PROCESS). Offensive Tools: die **rpcview** / **alpc-fuzzing**-Linie (Clément Rouault, James Forshaw) übt genau diesen Pfad, um server-seitige Vertrauensgrenzen zu kartieren.

Erkennungs­möglichkeiten

Außerhalb von `lsass`, `services.exe`, `spoolsv.exe`, Task Scheduler und einer Handvoll bekannter RPC-Server sind Aufrufe von `NtAlpcOpenSenderProcess` unüblich. Der ETW-Provider `Microsoft-Windows-Threat-Intelligence` (nur Defender) traced Handle-Opens mit Origin; `Microsoft-Windows-RPC` zeigt den zugehörigen RPC-Call, wenn der Server LRPC nutzt. Auf **unerwartete privilegierte Server** jagen, die Sender öffnen — ein User-Context-Implant, das vorgibt RPC-Server zu sein, setzt diesen Syscall gegen eingehende Verbindungen anderer Implants ab. Mit `NtOpenProcessToken` auf demselben Handle koppeln, um das vollständige Identity-Check-Muster zu fangen, und mit nachgelagerter Token-Impersonation (`NtAlpcImpersonateClientOfPort` oder `ImpersonateLoggedOnUser`) — dieses Triplett ist die klassische ALPC-Vertrauensgrenze, und jeder Aufrufer außerhalb der Microsoft-Allowlist verdient Triage.

Direkte Syscall-Beispiele

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