NtReplyWaitReceivePort
Server-LPC-Primitive: beantwortet atomar die vorherige Anfrage und blockiert auf die nächste.
Prototyp
NTSTATUS NtReplyWaitReceivePort( HANDLE PortHandle, PVOID *PortContext, PPORT_MESSAGE ReplyMessage, PPORT_MESSAGE ReceiveMessage );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| PortHandle | HANDLE | in | Handle auf den Server-Port aus NtCreatePort; der Thread blockiert auf der Queue dieses Ports. |
| PortContext | PVOID* | out | Optional; erhält den client-spezifischen Kontextzeiger, der bei NtAcceptConnectPort gespeichert wurde. |
| ReplyMessage | PPORT_MESSAGE | in | Optionale Antwort für die zuvor empfangene Anfrage (NULL beim ersten Aufruf / keine ausstehende Antwort). |
| ReceiveMessage | PPORT_MESSAGE | out | Vom Caller allokierter Buffer für die nächste eingehende PORT_MESSAGE; muss ≥ MaxMessageLength sein. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0xB | win10-1507 |
| Win10 1607 | 0xB | win10-1607 |
| Win10 1703 | 0xB | win10-1703 |
| Win10 1709 | 0xB | win10-1709 |
| Win10 1803 | 0xB | win10-1803 |
| Win10 1809 | 0xB | win10-1809 |
| Win10 1903 | 0xB | win10-1903 |
| Win10 1909 | 0xB | win10-1909 |
| Win10 2004 | 0xB | win10-2004 |
| Win10 20H2 | 0xB | win10-20h2 |
| Win10 21H1 | 0xB | win10-21h1 |
| Win10 21H2 | 0xB | win10-21h2 |
| Win10 22H2 | 0xB | win10-22h2 |
| Win11 21H2 | 0xB | win11-21h2 |
| Win11 22H2 | 0xB | win11-22h2 |
| Win11 23H2 | 0xB | win11-23h2 |
| Win11 24H2 | 0xB | win11-24h2 |
| Server 2016 | 0xB | winserver-2016 |
| Server 2019 | 0xB | winserver-2019 |
| Server 2022 | 0xB | winserver-2022 |
| Server 2025 | 0xB | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 0B 00 00 00 mov eax, 0x0B 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
NtReplyWaitReceivePort ist die kanonische *Server-Hauptschleife* von Legacy-LPC: Sie liefert atomar die Antwort auf die in der vorherigen Iteration behandelte Anfrage und blockiert auf der Eingangs-Queue, bis die nächste Anfrage eintrifft. Der Out-Parameter `PortContext` liefert den bei NtAcceptConnectPort registrierten clientspezifischen Kontextzeiger, sodass ein Server-Thread beliebig viele Clients multiplexen kann. Die SSN `0x0B` ist seit Windows 10 1507 eingefroren — sogar niedriger als NtRequestWaitReplyPort, weil die originalen LPC-Dispatch-Routinen früh in der Tabelle platziert wurden.
Häufige Malware-Nutzung
Ein Angreifer ruft NtReplyWaitReceivePort nur auf, nachdem er via NtCreatePort + NtAcceptConnectPort + NtCompleteConnectPort einen benutzerdefinierten LPC-Server hochgezogen hat. Dieses Muster ist in moderner Malware *verschwindend selten* — praktische IPC für Implants läuft über Named Pipes, ALPC oder Shared Sections. Nur historisches / akademisches Interesse: Pre-Vista-User-Mode-Rootkits nutzten die LPC-Server-Schleife als geräuscharme Intra-Host-C2-Stufe. Jeder Nicht-OS-Prozess, der auf Windows 10/11 in NtReplyWaitReceivePort blockiert sitzt, ist für sich untersuchungswürdig.
Erkennungsmöglichkeiten
Kein dedizierter ETW-Provider für Legacy-LPC; Detektion erfordert Kernel-Callbacks oder Syscall-Hooks auf Treiber-Ebene. Der robusteste Blue-Team-Ansatz ist die Aufzählung lebender `Port`-Objekte unter `\RPC Control` und `\Sessions\<n>\Windows` (SystemInformer / WinObj) und das Markieren von Ownern, die nicht csrss, lsass, smss oder das RPC-Subsystem sind. Ein User-Mode-Thread, der dauerhaft in `ntdll!NtReplyWaitReceivePort` blockiert, ohne ein passendes Microsoft-Binary im Stack, ist hochaussagekräftig.
Direkte Syscall-Beispiele
asmx64 direct stub
; Direct syscall stub for NtReplyWaitReceivePort (SSN 0x0B, stable Win10 1507+)
NtReplyWaitReceivePort PROC
mov r10, rcx ; PortHandle
mov eax, 0Bh ; SSN
syscall
ret
NtReplyWaitReceivePort ENDPcMinimal LPC server loop
// Skeleton of a custom LPC server. Error handling elided for brevity.
#include <windows.h>
#include <winternl.h>
typedef NTSTATUS (NTAPI *pNtReplyWaitReceivePort)(
HANDLE, PVOID*, PVOID /*PPORT_MESSAGE*/, PVOID /*PPORT_MESSAGE*/);
void ServerLoop(HANDLE hServerPort) {
BYTE reqBuf[0x100], replyBuf[0x100];
PVOID ctx = NULL;
BOOL havePendingReply = FALSE;
pNtReplyWaitReceivePort fn = (pNtReplyWaitReceivePort)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtReplyWaitReceivePort");
for (;;) {
NTSTATUS s = fn(hServerPort, &ctx,
havePendingReply ? replyBuf : NULL,
reqBuf);
if (s < 0) break;
// ... dispatch on reqBuf, populate replyBuf, set havePendingReply = TRUE ...
havePendingReply = TRUE;
}
}rustFFI declaration via ntapi
// Cargo: ntapi = "0.4"
use ntapi::ntlpcapi::NtReplyWaitReceivePort;
use winapi::shared::ntdef::{HANDLE, NTSTATUS, PVOID};
unsafe fn recv(port: HANDLE, ctx_out: *mut PVOID, reply: *mut u8, recv: *mut u8) -> NTSTATUS {
NtReplyWaitReceivePort(port, ctx_out, reply as *mut _, recv as *mut _)
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20