ALPC (Advanced Local Procedure Call) is a kernel-implemented IPC mechanism exposed through Native API syscalls.
It is not:
- A user-mode library
- A high-level WinAPI wrapper
- A named pipe replacement
It is a kernel subsystem.
When you call NtAlpc*, the kernel:
- Creates port objects in the Object Manager namespace
- Validates security descriptors
- Enforces access checks
- Queues messages in kernel-managed structures
- Fills system metadata fields (ClientId, MessageId, etc.)
Because ALPC lives in kernel code, bugs in its message parsing or attribute handling have historically resulted in Elevation of Privilege (EoP) vulnerabilities.
Where ALPC Is Used in Windows 11 and Windows Server 2025
Now let’s get practical.
ALPC is not a niche mechanism — it is deeply embedded in the OS.
1. Local RPC (LRPC)
The most important use case.
When RPC clients and servers communicate locally (same machine), Windows frequently uses ALPC as the transport layer.
This means that many RPC endpoints under:
\RPC Control\
are backed by ALPC ports.
This directly affects:
- Service-to-service communication
- COM/DCOM local activation
- Security-sensitive RPC interfaces
If you analyze RPC endpoints or inspect Object Manager, you will observe ALPC-backed ports associated with:
- RPCSS (Remote Procedure Call Service)
- DCOMLAUNCH
- EventLog
- WinDefend
- LSASS-related RPC endpoints
- Task Scheduler (Schedule service)
ALPC is effectively the transport backbone of Local RPC.
2. RPCSS (Remote Procedure Call Subsystem)
The RPCSS service (RpcSs) is central to RPC and COM activation.
It uses ALPC for:
- Local activation requests
- Interface negotiation
- Service registration
- Endpoint management
When COM launches an out-of-process server, the activation traffic frequently goes through ALPC-backed RPC channels.
3. DCOMLAUNCH Service
The DCOM Server Process Launcher service handles:
- Out-of-process COM activation
- Security context propagation
- Server process startup
Local activation requests commonly traverse RPC over ALPC.
If you are researching COM exploitation or token manipulation paths — ALPC is underneath.
4. LSASS (Local Security Authority Subsystem Service)
LSASS exposes multiple RPC interfaces for:
- Authentication
- SAM operations
- LSA policy queries
- Kerberos ticket operations
Local callers frequently reach LSASS via RPC → ALPC transport.
This is especially relevant in:
- Privilege escalation research
- Credential security analysis
- Token manipulation paths
5. Task Scheduler Service
The Schedule service exposes RPC interfaces for:
- Task registration
- Task execution
- Trigger management
Local administrative tools use RPC which typically resolves to ALPC transport.
6. Windows Defender / Security Services
Modern security services such as:
- WinDefend
- Security Center components
- Some EDR integrations
Use RPC-based communication internally — often backed by ALPC locally.
7. Service Control Manager (SCM)
The Service Control Manager exposes RPC interfaces for:
- Service creation
- Configuration
- Start/Stop operations
Local administrative interactions often traverse RPC endpoints backed by ALPC.
8. COM and Out-of-Process Servers
Out-of-process COM servers rely heavily on RPC infrastructure.
When activation is local, ALPC is typically the underlying IPC channel.
If you are studying:
- COM hijacking
- DCOM abuse
- IRundown::DoCallback chains
- RPC-based injection techniques
Hardened production-style ALPC server (C, Native API)
Below is a single-file hardened ALPC server that:
- creates an ALPC port under
\RPC Control\YourPort - applies a Security Descriptor (SDDL) to restrict who can connect
- uses connected port for messaging (fixing the common mistake)
- adds a small binary protocol header with
magic/version/type/len - supports:
PING(echo)SECTION_REQUEST(server creates a section and passes the section handle to client via ALPC message attribute; client maps & reads/writes)
- resolves all
Nt*+Alpc*helpers dynamically fromntdll.dll
File: alpc_hardened_server.c
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#include <stdint.h>
#include <sddl.h>
#pragma comment(lib, "advapi32.lib")
// -------------------- Minimal NT/ALPC typedefs --------------------
typedef NTSTATUS (NTAPI* NtAlpcCreatePort_T)(
PHANDLE PortHandle,
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID /*PALPC_PORT_ATTRIBUTES*/ PortAttributes
);
typedef NTSTATUS (NTAPI* NtAlpcAcceptConnectPort_T)(
PHANDLE PortHandle,
HANDLE ConnectionPortHandle,
ULONG Flags,
PVOID /*PALPC_PORT_ATTRIBUTES*/ PortAttributes,
PVOID /*PALPC_MESSAGE_ATTRIBUTES*/ MessageAttributes,
PPORT_MESSAGE ConnectionRequest,
PVOID /*PVOID ConnectionMessage*/ ConnectionMessage,
PBOOLEAN AcceptConnection
);
typedef NTSTATUS (NTAPI* NtAlpcConnectPort_T)(
PHANDLE PortHandle,
PUNICODE_STRING PortName,
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID /*PALPC_PORT_ATTRIBUTES*/ PortAttributes,
ULONG Flags,
PSID RequiredServerSid,
PPORT_MESSAGE ConnectionMessage,
PULONG BufferLength,
PVOID /*PALPC_MESSAGE_ATTRIBUTES*/ OutMessageAttributes,
PVOID /*PALPC_MESSAGE_ATTRIBUTES*/ InMessageAttributes,
PLARGE_INTEGER Timeout
);
typedef NTSTATUS (NTAPI* NtAlpcSendWaitReceivePort_T)(
HANDLE PortHandle,
ULONG Flags,
PPORT_MESSAGE SendMessage,
PVOID /*PALPC_MESSAGE_ATTRIBUTES*/ SendMessageAttributes,
PPORT_MESSAGE ReceiveMessage,
PSIZE_T BufferLength,
PVOID /*PALPC_MESSAGE_ATTRIBUTES*/ ReceiveMessageAttributes,
PLARGE_INTEGER Timeout
);
typedef NTSTATUS (NTAPI* NtAlpcDisconnectPort_T)(HANDLE PortHandle, ULONG Flags);
typedef NTSTATUS (NTAPI* NtAlpcCreatePortSection_T)(
HANDLE PortHandle,
ULONG Flags,
HANDLE SectionHandle,
SIZE_T SectionSize,
PHANDLE AlpcSectionHandle,
PSIZE_T ActualSectionSize
);
typedef NTSTATUS (NTAPI* NtAlpcDeletePortSection_T)(
HANDLE PortHandle,
ULONG Flags,
HANDLE SectionHandle
);
typedef NTSTATUS (NTAPI* NtCreateSection_T)(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PLARGE_INTEGER MaximumSize,
ULONG SectionPageProtection,
ULONG AllocationAttributes,
HANDLE FileHandle
);
typedef NTSTATUS (NTAPI* NtClose_T)(HANDLE Handle);
typedef NTSTATUS (NTAPI* AlpcInitializeMessageAttribute_T)(
ULONG AttributeFlags,
PVOID Buffer,
SIZE_T BufferSize,
PSIZE_T RequiredBufferSize
);
typedef PVOID (NTAPI* AlpcGetMessageAttribute_T)(
PVOID /*PALPC_MESSAGE_ATTRIBUTES*/ Buffer,
ULONG AttributeFlag
);
// -------------------- ALPC attribute bits & structs (minimal) --------------------
// NOTE: These definitions match common PHNT/ReactOS layouts used by researchers.
// They are sufficient for HANDLE passing + basic SECURITY attribute usage.
#define ALPC_MESSAGE_ATTRIBUTE_SECURITY 0x00000001
#define ALPC_MESSAGE_ATTRIBUTE_HANDLE 0x00000004
typedef struct _ALPC_MESSAGE_ATTRIBUTES {
ULONG AllocatedAttributes;
ULONG ValidAttributes;
} ALPC_MESSAGE_ATTRIBUTES, *PALPC_MESSAGE_ATTRIBUTES;
typedef struct _ALPC_SECURITY_ATTR {
ULONG Flags;
PSECURITY_QUALITY_OF_SERVICE QoS;
HANDLE ContextHandle; // reserved/optional
} ALPC_SECURITY_ATTR, *PALPC_SECURITY_ATTR;
typedef struct _ALPC_HANDLE_ATTR {
ULONG Flags;
HANDLE Handle;
ULONG ObjectType; // optional
ULONG DesiredAccess;
} ALPC_HANDLE_ATTR, *PALPC_HANDLE_ATTR;
// flags for handle attribute (common)
#define ALPC_HANDLEFLG_DUPLICATE_SAME_ACCESS 0x00000002
// -------------------- ALPC port attributes (minimal) --------------------
typedef struct _ALPC_PORT_ATTRIBUTES {
ULONG Flags;
SECURITY_QUALITY_OF_SERVICE SecurityQos;
SIZE_T MaxMessageLength;
SIZE_T MemoryBandwidth;
SIZE_T MaxPoolUsage;
SIZE_T MaxSectionSize;
SIZE_T MaxViewSize;
SIZE_T MaxTotalSectionSize;
ULONG DupObjectTypes;
ULONG Reserved;
} ALPC_PORT_ATTRIBUTES, *PALPC_PORT_ATTRIBUTES;
// -------------------- Utility --------------------
static void die_nt(const char* what, NTSTATUS st) {
printf("[-] %s: NTSTATUS=0x%08X\n", what, (unsigned)st);
ExitProcess(1);
}
static FARPROC gp(HMODULE m, const char* n) {
FARPROC p = GetProcAddress(m, n);
if (!p) {
printf("[-] GetProcAddress(%s) failed (%lu)\n", n, GetLastError());
ExitProcess(1);
}
return p;
}
static void init_unicode(UNICODE_STRING* us, const wchar_t* ws) {
us->Buffer = (PWSTR)ws;
us->Length = (USHORT)(wcslen(ws) * sizeof(wchar_t));
us->MaximumLength = us->Length + sizeof(wchar_t);
}
// -------------------- Simple protocol --------------------
#define MSG_MAGIC 0x434C5041u /* 'ALPC' */
typedef enum _MSG_TYPE : uint32_t {
MSG_PING = 1,
MSG_SECTION_REQUEST = 2,
MSG_SECTION_INFO = 3,
} MSG_TYPE;
#pragma pack(push, 1)
typedef struct _APP_HDR {
uint32_t magic;
uint16_t version;
uint16_t type;
uint32_t payload_len;
} APP_HDR;
#pragma pack(pop)
typedef struct _APP_MSG {
PORT_MESSAGE pm;
APP_HDR hdr;
uint8_t payload[1];
} APP_MSG;
// -------------------- Hardened port security --------------------
// SDDL example: allow SYSTEM + Administrators + LocalService; deny everyone else.
// Tune to your needs.
// SY: LocalSystem, BA: Builtin Administrators, LS: LocalService
static PSECURITY_DESCRIPTOR build_sd_from_sddl(const wchar_t* sddl) {
PSECURITY_DESCRIPTOR sd = NULL;
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
sddl, SDDL_REVISION_1, &sd, NULL)) {
printf("[-] ConvertStringSecurityDescriptorToSecurityDescriptorW failed (%lu)\n", GetLastError());
ExitProcess(1);
}
return sd; // must be LocalFree()
}
int main(void) {
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) die_nt("GetModuleHandleW(ntdll)", STATUS_DLL_NOT_FOUND);
// Resolve functions
NtAlpcCreatePort_T NtAlpcCreatePort = (NtAlpcCreatePort_T)gp(ntdll, "NtAlpcCreatePort");
NtAlpcAcceptConnectPort_T NtAlpcAcceptConnectPort = (NtAlpcAcceptConnectPort_T)gp(ntdll, "NtAlpcAcceptConnectPort");
NtAlpcSendWaitReceivePort_T NtAlpcSendWaitReceivePort = (NtAlpcSendWaitReceivePort_T)gp(ntdll, "NtAlpcSendWaitReceivePort");
NtAlpcDisconnectPort_T NtAlpcDisconnectPort = (NtAlpcDisconnectPort_T)gp(ntdll, "NtAlpcDisconnectPort");
NtCreateSection_T NtCreateSection = (NtCreateSection_T)gp(ntdll, "NtCreateSection");
NtClose_T NtClose = (NtClose_T)gp(ntdll, "NtClose");
AlpcInitializeMessageAttribute_T AlpcInitializeMessageAttribute =
(AlpcInitializeMessageAttribute_T)gp(ntdll, "AlpcInitializeMessageAttribute");
AlpcGetMessageAttribute_T AlpcGetMessageAttribute =
(AlpcGetMessageAttribute_T)gp(ntdll, "AlpcGetMessageAttribute");
// Port name
wchar_t portPath[256] = L"\\RPC Control\\AlpcHardenedDemo";
UNICODE_STRING usPort;
init_unicode(&usPort, portPath);
// Build Security Descriptor for the port object
// Only SYSTEM + Admins + LocalService allowed.
// You can add AU (Authenticated Users) if you want: (A;;GA;;;AU)
const wchar_t* sddl = L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;LS)";
PSECURITY_DESCRIPTOR sd = build_sd_from_sddl(sddl);
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &usPort, OBJ_CASE_INSENSITIVE, NULL, sd);
// Port attributes (production-ish defaults)
ALPC_PORT_ATTRIBUTES pa;
ZeroMemory(&pa, sizeof(pa));
pa.MaxMessageLength = 0x2000; // 8KB messages
pa.MaxPoolUsage = 0; // let kernel decide
pa.MaxSectionSize = 1 << 20; // 1MB sections if you later use them
pa.MaxViewSize = 1 << 20;
pa.MaxTotalSectionSize = 4 << 20;
pa.SecurityQos.Length = sizeof(pa.SecurityQos);
pa.SecurityQos.ImpersonationLevel = SecurityImpersonation;
pa.SecurityQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
pa.SecurityQos.EffectiveOnly = TRUE;
HANDLE hConnPort = NULL;
NTSTATUS st = NtAlpcCreatePort(&hConnPort, &oa, &pa);
if (st) die_nt("NtAlpcCreatePort", st);
printf("[+] Connection port created: %ws\n", portPath);
// Accept loop: accept one client, then serve it (extend to multi-client with worker threads).
for (;;) {
uint8_t rxBuf[0x4000];
SIZE_T rxLen = sizeof(rxBuf);
PPORT_MESSAGE rxPm = (PPORT_MESSAGE)rxBuf;
// Wait for incoming connection request on the connection port
st = NtAlpcSendWaitReceivePort(
hConnPort,
0,
NULL, NULL,
rxPm,
&rxLen,
NULL,
NULL
);
if (st) die_nt("NtAlpcSendWaitReceivePort(wait connect)", st);
// Prepare attributes buffer (handle+security as needed)
uint8_t attrBuf[512];
SIZE_T required = 0;
ZeroMemory(attrBuf, sizeof(attrBuf));
st = AlpcInitializeMessageAttribute(ALPC_MESSAGE_ATTRIBUTE_SECURITY | ALPC_MESSAGE_ATTRIBUTE_HANDLE,
attrBuf, sizeof(attrBuf), &required);
if (st == STATUS_BUFFER_TOO_SMALL) {
printf("[-] Attributes buffer too small (need %zu)\n", required);
ExitProcess(1);
}
if (st) die_nt("AlpcInitializeMessageAttribute", st);
HANDLE hClientPort = NULL;
BOOLEAN accept = TRUE;
st = NtAlpcAcceptConnectPort(
&hClientPort,
hConnPort,
0,
&pa,
(PALPC_MESSAGE_ATTRIBUTES)attrBuf,
rxPm,
NULL,
&accept
);
if (st) die_nt("NtAlpcAcceptConnectPort", st);
if (!accept || !hClientPort) {
printf("[!] Connection rejected\n");
continue;
}
printf("[+] Client connected. Serving on connected port.\n");
// Serve this client until disconnect/error
for (;;) {
uint8_t msgBuf[0x4000];
SIZE_T msgLen = sizeof(msgBuf);
APP_MSG* m = (APP_MSG*)msgBuf;
// Receive on connected port (IMPORTANT)
st = NtAlpcSendWaitReceivePort(
hClientPort,
0,
NULL, NULL,
(PPORT_MESSAGE)m,
&msgLen,
NULL,
NULL
);
if (st) {
printf("[!] Client receive ended: 0x%08X\n", (unsigned)st);
break;
}
// Basic validation
if (msgLen < sizeof(PORT_MESSAGE) + sizeof(APP_HDR)) {
printf("[!] Short message\n");
continue;
}
if (m->hdr.magic != MSG_MAGIC || m->hdr.version != 1) {
printf("[!] Bad magic/version\n");
continue;
}
uint32_t type = m->hdr.type;
uint32_t payLen = m->hdr.payload_len;
if (sizeof(PORT_MESSAGE) + sizeof(APP_HDR) + payLen > msgLen) {
printf("[!] Length mismatch\n");
continue;
}
if (type == MSG_PING) {
// Echo back same payload
// Build reply in-place
m->pm.u2.s2.Type = 0; // reply-ish; keep it simple
// In ALPC, MessageId is managed; we can send as is for demo.
SIZE_T outLen = sizeof(PORT_MESSAGE) + sizeof(APP_HDR) + payLen;
st = NtAlpcSendWaitReceivePort(
hClientPort,
0,
(PPORT_MESSAGE)m,
NULL,
NULL,
NULL,
NULL,
NULL
);
if (st) {
printf("[!] Send reply failed: 0x%08X\n", (unsigned)st);
} else {
printf("[+] PING handled (%u bytes)\n", payLen);
}
}
else if (type == MSG_SECTION_REQUEST) {
// Create a section, write something in it, and pass section HANDLE back via attributes.
HANDLE hSection = NULL;
LARGE_INTEGER maxSize;
maxSize.QuadPart = 0x10000; // 64KB
st = NtCreateSection(
&hSection,
SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_QUERY,
NULL,
&maxSize,
PAGE_READWRITE,
SEC_COMMIT,
NULL
);
if (st) {
printf("[!] NtCreateSection failed: 0x%08X\n", (unsigned)st);
continue;
}
// Map it in server to initialize content
void* base = NULL;
SIZE_T view = 0;
NTSTATUS (NTAPI* NtMapViewOfSection_T)(HANDLE, HANDLE, PVOID*, ULONG_PTR, SIZE_T, PLARGE_INTEGER, PSIZE_T, DWORD, ULONG, ULONG) =
(NtMapViewOfSection_T)gp(ntdll, "NtMapViewOfSection");
st = NtMapViewOfSection_T(hSection, GetCurrentProcess(), &base, 0, 0, NULL, &view, 2 /*ViewUnmap*/, 0, PAGE_READWRITE);
if (!st && base) {
const char* banner = "Hello from server via shared section.\n";
memcpy(base, banner, strlen(banner) + 1);
}
// Prepare reply message with HANDLE attribute
uint8_t outBuf[0x4000];
ZeroMemory(outBuf, sizeof(outBuf));
APP_MSG* out = (APP_MSG*)outBuf;
out->hdr.magic = MSG_MAGIC;
out->hdr.version = 1;
out->hdr.type = MSG_SECTION_INFO;
out->hdr.payload_len = 0;
out->pm.u1.s1.DataLength = (USHORT)(sizeof(APP_HDR) + out->hdr.payload_len);
out->pm.u1.s1.TotalLength = (USHORT)(sizeof(PORT_MESSAGE) + sizeof(APP_HDR) + out->hdr.payload_len);
// Build attributes with handle passing
uint8_t outAttrBuf[512];
SIZE_T req2 = 0;
ZeroMemory(outAttrBuf, sizeof(outAttrBuf));
st = AlpcInitializeMessageAttribute(ALPC_MESSAGE_ATTRIBUTE_HANDLE, outAttrBuf, sizeof(outAttrBuf), &req2);
if (st == STATUS_BUFFER_TOO_SMALL) {
printf("[-] Out attributes too small (need %zu)\n", req2);
ExitProcess(1);
}
if (st) die_nt("AlpcInitializeMessageAttribute(out)", st);
PALPC_HANDLE_ATTR ha = (PALPC_HANDLE_ATTR)AlpcGetMessageAttribute(outAttrBuf, ALPC_MESSAGE_ATTRIBUTE_HANDLE);
if (!ha) {
printf("[!] AlpcGetMessageAttribute(handle) returned NULL\n");
NtClose(hSection);
continue;
}
ha->Flags = ALPC_HANDLEFLG_DUPLICATE_SAME_ACCESS;
ha->Handle = hSection;
ha->DesiredAccess = 0;
st = NtAlpcSendWaitReceivePort(
hClientPort,
0,
(PPORT_MESSAGE)out,
(PALPC_MESSAGE_ATTRIBUTES)outAttrBuf,
NULL, NULL,
NULL,
NULL
);
if (st) {
printf("[!] Send section handle failed: 0x%08X\n", (unsigned)st);
} else {
printf("[+] Section handle sent to client\n");
}
// Cleanup server-side mapping + handle
if (base) {
NTSTATUS (NTAPI* NtUnmapViewOfSection_T)(HANDLE, PVOID) =
(NtUnmapViewOfSection_T)gp(ntdll, "NtUnmapViewOfSection");
NtUnmapViewOfSection_T(GetCurrentProcess(), base);
}
NtClose(hSection);
}
else {
printf("[!] Unknown msg type: %u\n", type);
}
}
NtAlpcDisconnectPort(hClientPort, 0);
NtClose(hClientPort);
printf("[*] Client disconnected\n");
}
// never reached
// LocalFree(sd);
// NtClose(hConnPort);
// return 0;
}
Client extension: message attributes + section-based transfer
This client:
- connects
- sends
MSG_SECTION_REQUEST - receives
MSG_SECTION_INFOwith a section handle passed via ALPC HANDLE attribute - maps the section and reads the server’s banner
File: alpc_section_client.c
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#include <stdint.h>
typedef NTSTATUS (NTAPI* NtAlpcConnectPort_T)(
PHANDLE PortHandle,
PUNICODE_STRING PortName,
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID PortAttributes,
ULONG Flags,
PSID RequiredServerSid,
PPORT_MESSAGE ConnectionMessage,
PULONG BufferLength,
PVOID OutMessageAttributes,
PVOID InMessageAttributes,
PLARGE_INTEGER Timeout
);
typedef NTSTATUS (NTAPI* NtAlpcSendWaitReceivePort_T)(
HANDLE PortHandle,
ULONG Flags,
PPORT_MESSAGE SendMessage,
PVOID SendMessageAttributes,
PPORT_MESSAGE ReceiveMessage,
PSIZE_T BufferLength,
PVOID ReceiveMessageAttributes,
PLARGE_INTEGER Timeout
);
typedef NTSTATUS (NTAPI* NtClose_T)(HANDLE Handle);
typedef NTSTATUS (NTAPI* AlpcInitializeMessageAttribute_T)(
ULONG AttributeFlags,
PVOID Buffer,
SIZE_T BufferSize,
PSIZE_T RequiredBufferSize
);
typedef PVOID (NTAPI* AlpcGetMessageAttribute_T)(
PVOID Buffer,
ULONG AttributeFlag
);
#define ALPC_MESSAGE_ATTRIBUTE_HANDLE 0x00000004
typedef struct _ALPC_MESSAGE_ATTRIBUTES {
ULONG AllocatedAttributes;
ULONG ValidAttributes;
} ALPC_MESSAGE_ATTRIBUTES, *PALPC_MESSAGE_ATTRIBUTES;
typedef struct _ALPC_HANDLE_ATTR {
ULONG Flags;
HANDLE Handle;
ULONG ObjectType;
ULONG DesiredAccess;
} ALPC_HANDLE_ATTR, *PALPC_HANDLE_ATTR;
static FARPROC gp(HMODULE m, const char* n) {
FARPROC p = GetProcAddress(m, n);
if (!p) {
printf("[-] GetProcAddress(%s) failed (%lu)\n", n, GetLastError());
ExitProcess(1);
}
return p;
}
static void init_unicode(UNICODE_STRING* us, const wchar_t* ws) {
us->Buffer = (PWSTR)ws;
us->Length = (USHORT)(wcslen(ws) * sizeof(wchar_t));
us->MaximumLength = us->Length + sizeof(wchar_t);
}
#define MSG_MAGIC 0x434C5041u
typedef enum _MSG_TYPE : uint32_t { MSG_PING=1, MSG_SECTION_REQUEST=2, MSG_SECTION_INFO=3 } MSG_TYPE;
#pragma pack(push, 1)
typedef struct _APP_HDR {
uint32_t magic;
uint16_t version;
uint16_t type;
uint32_t payload_len;
} APP_HDR;
#pragma pack(pop)
typedef struct _APP_MSG {
PORT_MESSAGE pm;
APP_HDR hdr;
uint8_t payload[1];
} APP_MSG;
int main(void) {
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
NtAlpcConnectPort_T NtAlpcConnectPort = (NtAlpcConnectPort_T)gp(ntdll, "NtAlpcConnectPort");
NtAlpcSendWaitReceivePort_T NtAlpcSendWaitReceivePort = (NtAlpcSendWaitReceivePort_T)gp(ntdll, "NtAlpcSendWaitReceivePort");
NtClose_T NtClose = (NtClose_T)gp(ntdll, "NtClose");
AlpcInitializeMessageAttribute_T AlpcInitializeMessageAttribute =
(AlpcInitializeMessageAttribute_T)gp(ntdll, "AlpcInitializeMessageAttribute");
AlpcGetMessageAttribute_T AlpcGetMessageAttribute =
(AlpcGetMessageAttribute_T)gp(ntdll, "AlpcGetMessageAttribute");
UNICODE_STRING us;
init_unicode(&us, L"\\RPC Control\\AlpcHardenedDemo");
HANDLE hPort = NULL;
NTSTATUS st = NtAlpcConnectPort(&hPort, &us, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
if (st) { printf("[-] NtAlpcConnectPort 0x%08X\n", (unsigned)st); return 1; }
printf("[+] Connected\n");
// Send SECTION_REQUEST
uint8_t txBuf[512];
ZeroMemory(txBuf, sizeof(txBuf));
APP_MSG* tx = (APP_MSG*)txBuf;
tx->hdr.magic = MSG_MAGIC;
tx->hdr.version = 1;
tx->hdr.type = MSG_SECTION_REQUEST;
tx->hdr.payload_len = 0;
tx->pm.u1.s1.DataLength = (USHORT)(sizeof(APP_HDR) + tx->hdr.payload_len);
tx->pm.u1.s1.TotalLength = (USHORT)(sizeof(PORT_MESSAGE) + sizeof(APP_HDR) + tx->hdr.payload_len);
uint8_t rxBuf[4096];
SIZE_T rxLen = sizeof(rxBuf);
APP_MSG* rx = (APP_MSG*)rxBuf;
uint8_t rxAttr[512];
SIZE_T need = 0;
ZeroMemory(rxAttr, sizeof(rxAttr));
st = AlpcInitializeMessageAttribute(ALPC_MESSAGE_ATTRIBUTE_HANDLE, rxAttr, sizeof(rxAttr), &need);
if (st == STATUS_BUFFER_TOO_SMALL) { printf("[-] attr too small need=%zu\n", need); return 1; }
if (st) { printf("[-] AlpcInitializeMessageAttribute 0x%08X\n", (unsigned)st); return 1; }
st = NtAlpcSendWaitReceivePort(hPort, 0, (PPORT_MESSAGE)tx, NULL, (PPORT_MESSAGE)rx, &rxLen, (PALPC_MESSAGE_ATTRIBUTES)rxAttr, NULL);
if (st) { printf("[-] SendWaitReceive 0x%08X\n", (unsigned)st); return 1; }
if (rxLen < sizeof(PORT_MESSAGE) + sizeof(APP_HDR)) {
printf("[-] short reply\n"); return 1;
}
PALPC_HANDLE_ATTR ha = (PALPC_HANDLE_ATTR)AlpcGetMessageAttribute(rxAttr, ALPC_MESSAGE_ATTRIBUTE_HANDLE);
if (!ha || !ha->Handle) {
printf("[-] no handle attribute\n"); return 1;
}
HANDLE hSection = ha->Handle;
printf("[+] Got section handle: 0x%p\n", hSection);
// Map and read
NTSTATUS (NTAPI* NtMapViewOfSection_T)(HANDLE, HANDLE, PVOID*, ULONG_PTR, SIZE_T, PLARGE_INTEGER, PSIZE_T, DWORD, ULONG, ULONG) =
(NtMapViewOfSection_T)gp(ntdll, "NtMapViewOfSection");
NTSTATUS (NTAPI* NtUnmapViewOfSection_T)(HANDLE, PVOID) =
(NtUnmapViewOfSection_T)gp(ntdll, "NtUnmapViewOfSection");
void* base = NULL;
SIZE_T view = 0;
st = NtMapViewOfSection_T(hSection, GetCurrentProcess(), &base, 0, 0, NULL, &view, 2, 0, PAGE_READONLY);
if (st || !base) { printf("[-] map section 0x%08X\n", (unsigned)st); NtClose(hSection); return 1; }
printf("[SECTION] %s\n", (const char*)base);
NtUnmapViewOfSection_T(GetCurrentProcess(), base);
NtClose(hSection);
NtClose(hPort);
return 0;
}
Why I chose “handle passing” for section transfer
ALPC also supports fancier “views” (data view / port section APIs), but handle passing + NtCreateSection/NtMapViewOfSection is:
- stable,
- easy to validate,
- production-friendly,
- and clearly demonstrates message attributes in a way you can extend.
Offensive analysis perspective (ALPC as an attack surface)
I’ll keep this at a research/defense level (no step-by-step exploitation).
What attackers look for
A) Misconfigured ALPC port ACLs
If a privileged service exposes an ALPC endpoint under \RPC Control\X and allows broad access (e.g., AU or WD), then any local user might:
- invoke privileged operations (confused deputy),
- trigger unsafe code paths,
- or reach sensitive RPC interfaces indirectly.
Defense: restrict by SID/group, validate client identity on each request.
B) Impersonation and “trusted payload” bugs
A classic failure mode:
- server impersonates client (or assumes client identity),
- then uses that to access resources, but mixes privilege contexts incorrectly.
Defense: explicit authorization rules; avoid “do privileged thing based only on client-provided path/handle”.
C) Message attribute parsing bugs (service-side)
Once you support:
- handles,
- security attributes,
- views/sections,
the parser surface grows. Bugs become likely.
Defense: strict attribute allowlist; reject unknown attributes; cap sizes; fuzz internally.
D) Kernel surface (historical reality)
Because ALPC is kernel-managed, bugs in kernel handling of certain attributes have led to EoP classes historically.
Defense: keep OS patched; reduce exposure by minimizing who can connect; use least-privileged service design.
Detection ideas (blue team)
- inventory
\RPC Control\*objects and map them to owning processes - monitor unusual clients connecting to sensitive endpoints (where feasible)
- look for services that expose endpoints with weak DACLs
Diagram deep dive: RPC → ALPC → Kernel
Here’s the mental model you should keep:
┌────────────────────┐
│ Client Process │
│ (app / service / │
│ COM client) │
└─────────┬──────────┘
│ (MSRPC runtime, for RPC calls)
│ NdrClientCall2 / RpcBinding*
v
┌────────────────────┐
│ Local RPC (LRPC) │
│ chooses local │
│ transport │
└─────────┬──────────┘
│ (often ALPC when local)
v
┌──────────────────────────────────────────┐
│ ntdll.dll │
│ NtAlpcConnectPort / SendWaitReceivePort │
│ AlpcInitializeMessageAttribute, etc. │
└─────────┬────────────────────────────────┘
│ syscall
v
┌──────────────────────────────────────────┐
│ Kernel (ALPC subsystem) │
│ - Port objects in Object Manager │
│ - DACL enforcement / access checks │
│ - Message queues / delivery │
│ - Attributes handling (handles, views…) │
└─────────┬────────────────────────────────┘
│
v
┌────────────────────┐
│ Server Process │
│ (Windows service, │
│ COM server, LSASS, │
│ RPC endpoint) │
└────────────────────┘
What Changed in Windows 11 and Server 2025?
ALPC itself remains architecturally stable.
However:
1. RPC Hardening
Windows Server 2025 increases emphasis on:
- RPC authentication enforcement
- RestrictRemoteClients policies
- Stronger default security configurations
While this does not replace ALPC, it affects how RPC endpoints using ALPC must be secured.
2. Security-by-default in Windows 11
Windows 11 continues tightening:
- Service isolation
- Protected processes
- Reduced attack surface
Misconfigured ALPC ports (incorrect Security Descriptor, missing validation, over-permissive access) become more dangerous under stricter models.
Who Should Care About ALPC?
System Software Developers
If you build:
- Security agents
- System services
- High-performance IPC channels
- Privileged components
ALPC gives you:
- Performance
- Security enforcement
- Structured message control
- Kernel-backed reliability
Security Researchers
If you analyze:
- Local RPC endpoints
- Privilege escalation paths
- Kernel attack surfaces
- Service-to-service communication
Understanding ALPC is mandatory.
Blue Teams / Administrators
If you want to:
- Understand local attack surface
- Harden RPC interfaces
- Audit system service communication
You must understand that many “local RPC” channels are actually ALPC underneath.
Final Thoughts
ALPC is not just another IPC mechanism.
It is:
- The transport backbone of Local RPC
- A core primitive of Windows inter-process architecture
- A frequent participant in EoP vulnerabilities
- A powerful tool for system developers
In Windows 11 and Windows Server 2025, ALPC remains a foundational internal communication layer.
