> Windows Syscalls
ntoskrnl.exeT1068T1134T1559

NtAlpcOpenSenderProcess

Helper côté serveur qui ouvre un HANDLE vers le processus ayant envoyé un message ALPC donné.

Prototype

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

Arguments

NameTypeDirDescription
ProcessHandlePHANDLEoutReçoit un HANDLE vers l'objet EPROCESS de l'expéditeur, ouvert avec DesiredAccess.
PortHandleHANDLEinHandle vers le port de communication ALPC du serveur ayant reçu le message.
PortMessagePPORT_MESSAGEinLe PORT_MESSAGE renvoyé par NtAlpcSendWaitReceivePort ; son ClientId.UniqueProcess identifie l'expéditeur.
FlagsULONGinDrapeaux réservés / contextuels. Typiquement 0 ; les appelants kernel-mode peuvent passer ALPC_OPENSENDER_KERNEL_MODE.
DesiredAccessACCESS_MASKinMasque d'accès pour le handle retourné (ex. PROCESS_QUERY_LIMITED_INFORMATION pour une vérification d'identité, PROCESS_DUP_HANDLE pour smuggling de handle).
ObjectAttributesPOBJECT_ATTRIBUTESinBloc d'attributs objet (généralement uniquement des flags d'héritage). Peut être NULL.

IDs de syscalls par version de Windows

Version 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

Module noyau

ntoskrnl.exeNtAlpcOpenSenderProcess

APIs liées

NtAlpcOpenSenderThreadNtAlpcImpersonateClientOfPortNtAlpcSendWaitReceivePortNtAlpcAcceptConnectPortNtOpenProcessTokenNtQueryInformationProcess

Stub du 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

Notes non documentées

`NtAlpcOpenSenderProcess` est le **hook d'identité client autoritatif** du serveur. Après que `NtAlpcSendWaitReceivePort` retourne un `PORT_MESSAGE`, le serveur ne tient que l'en-tête de message — entièrement contrôlé par l'attaquant dans tous les champs *sauf* `ClientId`, que le noyau estampille depuis l'EPROCESS de l'expéditeur à l'envoi. Ce syscall prend ce `ClientId.UniqueProcess` et retourne un vrai `HANDLE` vers l'objet EPROCESS, ouvert avec l'accès demandé. Sert à : (a) vérifier le chemin d'image (`QueryFullProcessImageName`), (b) vérifier le token (`NtOpenProcessToken`), (c) vérifier le niveau de protection (PsProtectedSignerSystem, etc., via `NtQueryInformationProcess(ProcessProtectionInformation)`). L'implémentation noyau `AlpcpOpenSender` valide que le message est bien arrivé sur le port fourni et que l'expéditeur existe encore — les pids ne sont pas réutilisés tant que le message en détient une référence, donc les courses de PID-recycle ne sont pas possibles *via cette primitive*.

Usage courant par les malwares

La primitive en elle-même est bénigne ; les **bugs chez ses appelants** ne le sont pas. Trois classes notoires : 1. **Vérification d'identité oubliée** — un serveur ALPC privilégié (TaskHost, Spooler, DiagTrack) fait confiance à `PORT_MESSAGE.ClientId` pour une décision privilégiée *sans* appeler `NtAlpcOpenSenderProcess` pour canonicaliser. Un attaquant spoofe `ClientId` dans un message forgé et déclenche une action privilégiée. **CVE-2018-8440** (LPE ALPC de SandboxEscaper, Task Scheduler) et plusieurs bugs proches de PrintNightmare étaient des variantes. 2. **Mauvais DesiredAccess** — le serveur ouvre avec `PROCESS_DUP_HANDLE` 'au cas où' puis fuite le handle au client ; le client l'utilise pour injecter dans le serveur. Bug réel dans le service WinHTTP, vers 2020. 3. **TOCTOU d'identité de l'expéditeur** — le serveur ouvre l'expéditeur, lit son chemin d'image puis agit ; entretemps l'expéditeur exec un binaire privilégié via `NtCreateUserProcess` en réutilisant le même pid (rare ; nécessite PROCESS_CREATE_PROCESS). Outillage offensif : la lignée **rpcview** / **alpc-fuzzing** (Clément Rouault, James Forshaw) exerce exactement ce chemin pour cartographier les frontières de confiance côté serveur.

Opportunités de détection

En dehors de `lsass`, `services.exe`, `spoolsv.exe`, du Task Scheduler et d'une poignée de serveurs RPC bien connus, les appels à `NtAlpcOpenSenderProcess` sont rares. Le provider ETW `Microsoft-Windows-Threat-Intelligence` (Defender uniquement) trace les ouvertures de handle avec origine ; `Microsoft-Windows-RPC` montre l'appel RPC correspondant si le serveur utilise LRPC. Chasser les **serveurs privilégiés inattendus** qui ouvrent leurs expéditeurs — un implant user-context prétendant être un serveur RPC émettra ce syscall sur les connexions entrantes d'autres implants. Coupler avec `NtOpenProcessToken` sur le même handle pour attraper le pattern complet de vérification d'identité, puis l'impersonation token aval (`NtAlpcImpersonateClientOfPort` ou `ImpersonateLoggedOnUser`) — ce triplet est la frontière de confiance ALPC classique, et tout appelant hors allowlist Microsoft mérite triage.

Exemples de syscalls directs

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

Mappings MITRE ATT&CK

Last verified: 2026-05-20