61 min read

Every UAC Prompt Is an ALPC Handshake: A Field Guide to Windows' Most-Attacked Local IPC Fabric

ALPC and LRPC are the asynchronous local-IPC fabric under every Windows service. This is the story of the kernel object Microsoft does not document and the attack surface almost every Patch Tuesday still fixes.

Permalink

1. Every UAC Prompt Is an ALPC Handshake

Double-click an installer. The screen dims, a familiar dialog asks whether you want to allow this app to make changes, and a moment later either nothing happens or the installer keeps running. That moment of dim-and-prompt -- the User Account Control consent dialog -- is the most-seen artefact of one of the most-attacked primitives in the Windows kernel: a four-phase handshake on an asynchronous local-IPC port whose name does not appear in any Win32 or WDK reference Microsoft publishes.

Trace the call from the user side. The Explorer shell invokes ShellExecuteEx with the verb set to runas. That call does not magically elevate the process; it sends a request to another process, the Application Information service (appinfo) running as svchost.exe -k netsvcs with SYSTEM authority [1] [2]. The hand-off is an RPC call. The RPC runtime, asked for a local endpoint, selects the ncalrpc protocol sequence -- "Local procedure call" in Microsoft's own protocol-sequence reference [3]. Underneath that string is the LRPC transport in rpcrt4.dll, and underneath the LRPC transport is a kernel ALPC port that lives at the Object Manager name \RPC Control\appinfo. The kernel resolves the name, the handshake completes, and a single syscall named NtAlpcSendWaitReceivePort [4] carries the request message into the SYSTEM-context server and the reply back.

That syscall is the load-bearing entry point for the entire local-IPC fabric. Microsoft Learn does not publish a reference page for it. The de facto reference is a community-maintained header dump at ntdoc.m417z.com [4] that lists all eight parameters of the function. The kernel object behind the call is the _ALPC_PORT, and the per-connection structure layouts are documented only on Geoff Chappell's site [5] [6] and inside the chapter named Advanced local procedure call (ALPC) of Windows Internals 7e Part 2 [7].

Advanced Local Procedure Call (ALPC)

The kernel object and syscall family that replaced classic LPC in Windows Vista (November 2006). ALPC is an asynchronous, message-and-attribute IPC primitive built around the _ALPC_PORT object. The user-mode entry points are the undocumented Nt*Alpc* and Alpc* functions exported from ntdll.dll. Every local RPC call in modern Windows transits an ALPC port [8].

Local RPC (LRPC)

The Microsoft RPC runtime's transport selected when an application binds to the ncalrpc protocol sequence [3]. LRPC layers the RPC interface-registration model -- IDL, NDR marshalling, security callbacks -- on top of ALPC ports. LRPC is implemented inside rpcrt4.dll; the kernel does not know it exists. The kernel sees only ALPC messages.

The abbreviation collision is real and bites every newcomer. LPC is the original Windows NT 3.1 kernel primitive. LRPC is the RPC runtime's local transport, named in Windows NT 3.5 (1994), a full decade before ALPC existed [9]. LRPC was a transport name when the underlying kernel object was still LPC. Vista renamed the kernel object to ALPC; nobody renamed the transport. The two abbreviations differ by one letter and refer to different layers.

Two layers sit on top of one kernel object. The kernel layer is what Nt*Alpc* syscalls touch. The user-mode layer is the RPC runtime's interface dispatch -- the IDL stubs, the NDR encoders, the per-interface security callback the application registers with RpcServerRegisterIf2 [10]. The rest of this article pulls these two layers apart, walks the history that produced them, and explains why almost every Patch Tuesday since 2018 has shipped fixes inside the second one.

Ctrl + scroll to zoom
The four-phase ALPC handshake that runs every time UAC asks for consent. Only the connection port has an Object Manager name; the per-connection communication ports created by NtAlpcAcceptConnectPort are unnamed and exist only as handles.

The diagram is the article in miniature. Three of the four labelled actors are kernel objects: a named connection port, an unnamed pair of communication ports, and the message queue between them. The fourth is application code running in two different processes. The bugs of the next thirteen years live in the application code. The diagram's correctness rests on a structural fact almost every secondary writeup gets wrong, and Section 4 spells it out in full.

If this primitive is everywhere, why does nobody talk about it? Because nobody had to, for thirteen years.

2. Origins -- Cutler's NT and the Birth of LPC (1989-1993)

Dave Cutler talked about it, in October 1988, to a room of people he was trying to recruit out of Digital Equipment Corporation [11]. The pitch was a from-scratch portable operating system at Microsoft. The architectural commitment that mattered for our story was a microkernel-style design: the Windows personality, the OS/2 personality, the POSIX personality would all run as user-mode subsystems, each in its own process, talking to clients through a fast in-machine remote procedure call. The kernel would not implement the Win32 API directly. The kernel would implement an IPC primitive shaped like a procedure call and cheap enough to use for every Win32 API a process made.

That decision created a design problem the team had to solve before any of the subsystems could be written. Microkernel-style separation of subsystems means that the Win32 client of CreateWindow is in one process and the Win32 server that draws the window is in another. Every API call crosses a process boundary. The IPC primitive that carries the crossing has to look like a function call, return like a function call, and cost no more than tens of microseconds. The Cutler team -- Lou Perazzoli, Mark Lucovsky, Steven Wood, Darryl Havens, and the larger NT design group [11] -- shipped that primitive as Local Procedure Call, or LPC, with the first release of Windows NT in July 1993. Helen Custer documented the design that same year in Inside Windows NT [12], the canonical first-edition print primary.

Local Procedure Call (LPC)

The original Windows NT kernel IPC primitive, introduced with NT 3.1 in July 1993 as a synchronous inter-process communication facility [8]. LPC was synchronous-call-shaped, used three port objects per connection (one named connection port plus two unnamed communication ports), and was the transport for every Win32 API call into the Client/Server Runtime Subsystem (CSRSS) until Windows Vista. The kernel removed classic LPC entirely by Windows 7; legacy NtCreatePort callers were silently redirected onto the ALPC implementation [8].

The classic LPC mechanism worked like this. A server process calls NtCreatePort to create a connection port under an Object Manager name (for example, \Windows\ApiPort for CSRSS). The server then waits on the connection port. A client process opens the connection port by name and calls NtConnectPort to request a session. The kernel creates two new, unnamed communication ports -- one the client holds, one the server holds -- and ties them to the connection through the kernel's port-routing tables. From that point on, the client and server send messages through their respective communication-port handles; neither party has to look up the other in the Object Manager namespace. The three-port model is the architectural ancestor of every ALPC handshake the rest of this article will walk.

Ctrl + scroll to zoom
The classic LPC three-port model. The connection port is named in the Object Manager namespace; the per-connection communication ports are unnamed and exist only as handles. Vista's ALPC inherits this topology and adds asynchronous semantics and per-message attributes.

The two design pinch-points that Vista would later have to fix are visible already in the 1993 mechanism. First, the call surface was synchronous: NtRequestWaitReplyPort sent a message and blocked the caller until the reply came back, which forced the higher-level RPC runtime to wrap its own asynchronous machinery around the syscall and doubled the syscall cost for every async RPC. Second, the message payload had a small fixed inline budget -- on the order of 256 bytes [8] -- with anything larger requiring an explicit NtMapViewOfSection dance to set up a shared section the server would then peek into. The split between "short message in the syscall" and "long payload in a shared section" was awkward, racy, and a perennial source of off-by-one bugs in the server stubs.

The third pinch-point was security, and it is the one Cesar Cerrudo will name in 2006. LPC's access check happened once, at NtConnectPort, against the connection port's discretionary access control list (DACL). After the handshake, the kernel had no further opinion about who could send what to whom over the established channel. The server trusted every message it received because the kernel had already vouched that the client cleared the DACL at connect time. In 1993 that trust model was fine. The only callers of CSRSS were Win32 client processes the team controlled. POSIX clients talked to the POSIX subsystem; OS/2 clients talked to the OS/2 subsystem; the trust boundaries were the subsystem boundaries and nobody crossed them on purpose.

The 1993 design assumed the only callers of CSRSS were Win32 client processes the team controlled. That assumption held for thirteen years.

3. The First Reckoning -- LPC's Failure Modes and Cerrudo's WLSI 2006

In March 2006, at Black Hat Europe in Amsterdam, Cesar Cerrudo gave a talk titled WLSI -- Windows Local Shellcode Injection. Twelve weeks later, Microsoft shipped the Vista ALPC redesign. The temporal compression is intentional, but it is not the whole story: the Vista redesign had been underway inside the kernel team for years before Cerrudo's talk. What the talk did was give the public security community a name and a shape for the structural class of bug the redesign was about to address.

Cerrudo's paper, archived at Exploit-DB under the title WLSI Windows Local Shellcode Injection and dated March 14, 2006 [13], with the speaker deck mirrored on Black Hat's own server [14], walked an end-to-end attack on an LPC server inside CSRSS. The exact server is less important than the attack's three-clause shape, which Cerrudo articulated and which would recur, over the next two decades, in every later ALPC and LRPC privilege-escalation primitive.

Ctrl + scroll to zoom
Cerrudo 2006: the three-clause class that every later ALPC and LRPC EoP inherits. Each clause is necessary; the conjunction of all three produces a local elevation primitive whose root cause is the kernel's once-and-done trust model, not any single bug in any single server.

Clause one: the port is reachable. The LPC connection port has a DACL; the attacker happens to be inside it. For CSRSS's \Windows\ApiPort, that means "any Win32 process on the desktop", which is exactly what NT was supposed to permit. Clause two: the DACL is permissive. Every authenticated user is in scope of the LPC servers that brokered the user-mode Win32 API surface, by design. Clause three: the server trusts the message. The LPC kernel object exposes a PORT_MESSAGE header with two fields the receiver can use for bookkeeping -- a process ID and a thread ID. The fields are not authenticated. The receiving server, in the WLSI demonstration, read attacker-controlled offsets and lengths out of the message body and walked into the server's own address space.

The three clauses together produce a local elevation primitive. None of the clauses, taken individually, is a kernel bug. None, taken individually, is even an application bug. The bug -- in the WLSI exemplar -- is that the CSRSS server trusted a length field that came from a process the server itself had no reason to trust. The OS did exactly what its security model promised. The application did exactly what the IPC primitive made easy.

Discretionary Access Control List (DACL)

A Windows access control list attached to a securable object (a file, a registry key, a kernel object such as an LPC or ALPC port) that names the security principals allowed or denied each access right. For an LPC connection port, the DACL governs whether a calling process is allowed to open the port at all. Once the port is opened, the DACL is no longer consulted for messages flowing across the established connection -- which is exactly the once-and-done check at the centre of Cerrudo's structural class.

The 1993 trust model held until 2006 because the team controlled both ends of every conversation. Cerrudo named the class of bug that emerged when that assumption stopped holding.

That structural class is the load-bearing reason the Vista redesign was about to be a redesign and not a patch. The three LPC failure modes the kernel team had identified -- the ones that motivated re-architecting the primitive rather than fixing the WLSI server -- compose a near-perfect mirror of Cerrudo's three clauses. They are: (1) the synchronous-only design forced the RPC runtime to layer its own asynchronous wrapper around NtRequestWaitReplyPort, doubling the per-call syscall cost for async RPC; (2) the 256-byte inline plus shared-section dance was awkward and prone to race conditions in the server stub; (3) the port-DACL-only security model checked access once at connect and then trusted the channel, with no kernel primitive for per-message caller identity. A redesign was the only way to attack all three at once without breaking every NT 4-era server in the field.

One LPC failure mode that did not make Cerrudo's slide and that Microsoft has never publicly discussed in detail was the reply-port confusion class. In classic LPC, a server's reply traveled back over the client's communication port handle, and a misbehaving server could be tricked into replying to the wrong client when multiple connections were interleaved. Microsoft addressed this quietly in the Vista era; the only public references are footnotes in Windows Internals editions and the occasional aside in csandker [8]. The public security community did not catch the bug class at the time.

In November 2006 -- eight months after WLSI -- Windows Vista shipped. The new kernel called the replacement primitive Advanced LPC. The redesign closed half of Cerrudo's structural class -- the permissive port DACL half, by giving servers fine-grained tools to control who reaches their connection ports and by introducing a per-message security attribute the server could query for caller identity. It left the other half completely intact, because the other half is not a kernel property. The other half lives in the user-mode RPC runtime and in the application code that registers RPC interfaces on top of ALPC ports. That intact half is what the next thirteen years of public security research is about.

The naive read of Cerrudo's paper is "Microsoft will fix the bug." The structural read is harder: Cerrudo did not find a bug. He named a class of bug whose root cause is a property of the trust model. The Vista redesign closed the half of the class the kernel could close. It could not close the rest, because the rest is application code, and the kernel cannot inspect application code.

4. The Breakthrough -- ALPC, the Vista Redesign, and the Message-Attribute System

The Vista kernel team's answer to Cerrudo was not a patch. It was a complete replacement of the kernel object.

ALPC re-cast the LPC port as an asynchronous, message-and-attribute-based primitive. The classic LPC quartet -- NtRequestPort, NtReplyPort, NtRequestWaitReplyPort, NtReplyWaitReplyPort -- collapsed into a single syscall, NtAlpcSendWaitReceivePort [4], with eight parameters whose combinations express every variant the older quartet supported. The kernel object behind the syscall is the _ALPC_PORT. The structure layout is documented only in the chapter named Advanced local procedure call (ALPC) of Windows Internals 7e Part 2 [7], in the reverse-engineered header dumps on Geoff Chappell's site [5] [6], and in the community-maintained phnt headers that the Process Hacker project ships. None of those is a Microsoft Learn page.

ALPC port object (_ALPC_PORT)

The kernel object at the centre of Vista-and-later local IPC. Named connection ports are referenced by Object Manager name (typically under \RPC Control, \BaseNamedObjects, or per-session AppContainer subtrees). The per-connection communication ports created by NtAlpcAcceptConnectPort are unnamed and exist only as handles in the connecting and accepting processes. The structure layout is undocumented by Microsoft; the canonical reverse-engineered reference is Geoff Chappell's site [5].

The user-mode syscall surface, enumerated as exhaustively as anyone outside Microsoft can: NtAlpcCreatePort, NtAlpcConnectPort, NtAlpcAcceptConnectPort, NtAlpcSendWaitReceivePort, NtAlpcDisconnectPort, NtAlpcCancelMessage, NtAlpcCreatePortSection, NtAlpcCreateResourceReserve, plus the PORT_ATTRIBUTES and message-attribute structures that decorate each call. Microsoft Learn does not list any of them under a Win32 or WDK developer-facing reference. NtDoc [4] is the de facto syscall reference, and the Windows Internals 7e Part 2 chapter is the de facto architectural reference.

The structural break with classic LPC is the message-attribute system. Every ALPC message can carry four optional attributes, each of which targets one of the awkward LPC patterns the old kernel forced server authors to roll by hand.

Message attribute

An optional decoration on an ALPC message that lets the sender or receiver request a kernel service in band with the message itself. The four attribute types are Context, Handle, Security, and View. Each one targets a workflow that classic LPC required application code to perform out of band; in ALPC the kernel does the work atomically with the message exchange.

The Context attribute carries a per-message per-client cookie the server uses to associate the message with a logical operation. In classic LPC, a server tracking a multi-step protocol had to maintain its own client-to-state map indexed by client process ID, with all the race conditions that map invited; the Context attribute moves that bookkeeping into the kernel and makes it correct by construction.

The Handle attribute is first-class handle passing inside the message itself. In classic LPC, transferring a kernel handle from sender to receiver required the sender to call DuplicateHandle with the receiver's process handle, hope the receiver hadn't exited, and then send the resulting handle value in the message body. The Handle attribute lets the kernel do the duplication atomically with delivery; the receiver finds the duplicated handle already in its own handle table when the message lands.

The Security attribute is the per-message identity primitive whose absence Cerrudo had named in 2006. The sender can opt to attach its caller token to a message; the receiver can opt to query the token (process ID, thread ID, integrity level, AppContainer SID) when it dispatches the message. The classic LPC pattern -- "trust the channel because the kernel checked the DACL at connect" -- gets replaced by "ask the kernel who is actually sending this message right now."

The View attribute is the shared-section dance, rewritten. In classic LPC, payloads larger than the inline budget required the sender to call NtCreateSection, both parties to call NtMapViewOfSection, and the receiver to peek into the shared mapping. The View attribute hands the receiver a section view automatically as a side effect of message delivery; no out-of-band coordination is required.

Ctrl + scroll to zoom
The four ALPC message attributes and the LPC awkwardness each one retires. Each attribute moves a workflow that LPC servers had to coordinate out of band into a single in-message kernel transaction.

The handshake topology survives from classic LPC and tightens. The server creates a named connection port with NtAlpcCreatePort. The client opens the connection port by name with NtAlpcConnectPort and sends an initial connect message; the kernel queues the connect on the server's port. The server calls NtAlpcAcceptConnectPort, and the kernel returns a pair of communication-port handles -- one to the client, one to the server -- that are bound to that single connection. From that point on, the kernel routes messages through the paired handles, and every send or receive is a single call to NtAlpcSendWaitReceivePort. Asynchronous is the default; synchronous semantics are a flag combination. The per-port message queue, the blocked-receiver wake, and the cross-port routing all run inside the kernel dispatcher.

Ctrl + scroll to zoom
From NtAlpcConnectPort to a paired set of unnamed communication ports. The named connection port is what shows up in Object Manager listings; the per-connection communication ports never do.

Here is the structural correction the input premise to this article got wrong, and that almost every secondary writeup gets wrong. Only the named connection port has an Object Manager name. The per-connection communication ports created by NtAlpcAcceptConnectPort are unnamed. They have no path under \RPC Control or \BaseNamedObjects or anywhere else. They exist only as handles in the address spaces of the two processes that completed the handshake. No third party can open them, because no third party has a name with which to ask the Object Manager for them.

ALPC's structural correctness rests on a single move: the per-connection communication ports are unnamed. Only the parties that completed the handshake can address the channel. The kernel does not let anyone else find it. This is the half of Cerrudo's structural class the Vista redesign actually closed.

Microsoft's documentation choice has consequences for tooling. The Wireshark dissector for MSRPC handles the on-the-wire NDR encoding well, but it has no view into the kernel ALPC layer because the kernel does not emit a packet capture. To see ALPC at the kernel level the tooling has to subscribe to the Microsoft-Windows-Kernel-ALPC ETW provider [15], and even that provider is gated behind EVENT_TRACE_SYSTEM_LOGGER_MODE, which a non-SYSTEM caller cannot enable. The structural opacity of the kernel layer is partly an artefact of the deliberate "no public WDK developer-facing reference" position.

Backward compatibility was preserved by silent rewiring rather than by parallel kernel objects. The classic LPC syscall names continue to link in any pre-Vista binary, but from Windows 7 onward the kernel routes those calls into the ALPC implementation underneath [8]. Classic LPC, as an independent kernel object, no longer exists. The 1993 syscall surface is alive only as a thin compatibility shim. The 2006 kernel object is what every modern Windows service actually uses.

The Vista redesign closed the permissive port DACL half of the structural problem. It left the interface callback returns RPC_S_OK when it should return RPC_S_ACCESS_DENIED half completely intact. The Vista kernel team's collective attribution stops short of naming individual ALPC architects. Windows Internals 7e Part 2 [7] credits the work institutionally rather than to a single engineer, and no public Microsoft artefact identifies a single ALPC architect by name; secondary attributions in conference talks and blog posts trace back to footnotes rather than to primary record. That intact half is the rest of this article.

5. The Universalisation -- ALPC as the Local IPC Fabric (2009-2013)

By 2013, ALPC ran the local-IPC traffic of every Windows service that mattered. The kernel team had removed classic LPC. The Vista replacement had not been replaced; it had been adopted.

The transition was technically backwards-compatible. Pre-Vista binaries that called NtCreatePort and NtRequestWaitReplyPort continued to link and run; the kernel preserved the syscall names and silently rerouted the calls into the ALPC implementation underneath [8]. The compatibility was not lossless -- the old single-message-per-call semantics map onto the ALPC asynchronous primitive only at the cost of an extra wait -- but it was good enough that no Microsoft-shipped service ever needed a port from classic LPC. Every service author upgrading to Vista or later was implicitly upgraded to ALPC.

By Windows 8.1 the roll-call of services riding LRPC on ALPC was effectively the roll-call of services that ship with Windows. The Client/Server Runtime Subsystem (CSRSS) had been ALPC-only since Vista. The Local Security Authority Subsystem Service (LSASS) -- which brokers logon, token issuance, and Kerberos ticket caching -- exposes its API surface over LRPC. The Service Control Manager (SCM, services.exe) accepts service-control commands over an LRPC interface. The DCOM activation service (rpcss) marshals every local COM activation request through an LRPC pipeline. Windows Error Reporting, the audio service (audiosrv), Task Scheduler (schedsvc/schrpc), the Application Information service (appinfo) that brokers UAC, the Encrypting File System extension (efslsaext, the EFSRPC server documented in the [MS-EFSR] specification [16]), the print spooler (spoolsv), and the Background Intelligent Transfer Service (BITS) all expose at least one LRPC interface for client communication [17].

Ctrl + scroll to zoom
By Windows 8.1, every major Windows service exposes at least one LRPC interface backed by a kernel ALPC port. The kernel ALPC layer is the single rectangle every service sits on top of.

That fan-out is the article's load-bearing diagram for understanding why ALPC is the most-attacked local IPC fabric in modern Windows. Every named service in that diagram is reachable over an LRPC interface. Every LRPC interface registers a per-interface security callback through RpcServerRegisterIf2 [10] or RpcServerRegisterIf3 [18]. Every callback is application code that the kernel cannot inspect. A single permissive interface in a single one of those services is a structural primitive that works against the transport every service uses. Trail of Bits, announcing their RPC Investigator tool in January 2023, captured the surface area in one line: MSRPC is "involved on some level in nearly every activity that you can take on a Windows system, from logging in to your laptop to opening a file" [19].

MSRPC is involved on some level in nearly every activity that you can take on a Windows system, from logging in to your laptop to opening a file. -- Trail of Bits, RPC Investigator announcement, January 2023 [19]

To see the fabric in operation, walk one call. An unprivileged user invokes StartServiceW from the SCM client library inside sechost.dll. The library binds to the SCM's local RPC endpoint -- the \RPC Control\ntsvcs ALPC port that the Service Control Manager registers at boot. The MIDL-generated client stub packs the service name and arguments into NDR and hands them to NdrClientCall3. rpcrt4.dll crosses into the kernel through NtAlpcSendWaitReceivePort. The kernel routes the ALPC message to the SCM's blocked worker thread inside services.exe. The worker, running as SYSTEM, unpacks the NDR body with NdrStubCall3 and prepares to dispatch the server-side procedure. Before the procedure runs, the RPC runtime invokes the interface security callback, which checks whether the caller's token holds SC_MANAGER_CONNECT and the target service's DACL grants SERVICE_START. If the callback returns RPC_S_OK, the SCM starts the service. The reply -- an NDR-encoded error code -- rides another NtAlpcSendWaitReceivePort back to the client. One user call, five layers crossed, and the kernel never knew it was running an RPC.

One consequence of the silent kernel rewiring is that pre-Vista NT 4-era code samples appear to work on Windows 11. A textbook example from a 1996 driver-development book that calls NtCreatePort will link, load, and exchange messages just fine; the messages are travelling over the 2006 ALPC kernel object behind a 1993 syscall name. This is unusual generosity from a kernel team that breaks driver ABIs every few releases, and it is one of the reasons Microsoft has preserved the option not to publish a Nt*Alpc* developer-facing reference: as long as everyone is supposed to use the RPC runtime, the kernel object can keep evolving.

Once the transport was universal, enumeration became valuable. If only LSASS used ALPC, listing LSASS's interfaces by hand was fine. Once every service did, automation was the only tractable methodology. The answer to who built that automation is the next section.

6. The Eureka Year -- Public Tooling and the Interface-Callback Class (2017-2019)

In an eighteen-month span between October 2017 and December 2019, four researchers turned ALPC from internal NT plumbing into the most-attacked local-IPC surface in modern Windows. The exemplars were structurally identical: an LRPC server registered an RPC interface with a callback that either was NULL or returned RPC_S_OK for a caller that should have received RPC_S_ACCESS_DENIED. The kernel ALPC layer behaved correctly in every one of them. The application code did not.

Ctrl + scroll to zoom
Eighteen months that turned ALPC from plumbing into attack surface. The four publications below taught the public security community how to enumerate the LRPC interface inventory and named the structural bug class that the interface-callback layer produces.

The first publication is Clement Rouault and Thomas Imbert's "A view into ALPC-RPC", presented at PacSec in November 2017 [20] [21] and at Hack.lu the same season [22]. The talk is the first end-to-end mechanical walk of the LRPC-over-ALPC stack to appear at a public security conference, and the talk's deliverable was a working NDR-aware fuzzer named RPCForge [23]. RPCForge surfaced CVE-2017-11783 [24], the first publicly-acknowledged ALPC elevation-of-privilege issue surfaced by an outside-Microsoft fuzzer. The NVD entry phrases the bug class as "the way it handles calls to Advanced Local Procedure Call (ALPC)" -- the canonical "ALPC EoP" classification that NVD reuses for every later instance.

The second is James Forshaw's NtObjectManager tooling, distributed through the sandbox-attacksurface-analysis-tools repository at Google Project Zero [25]. The tooling is a PowerShell module backed by a .NET library originally called NtApiDotNet and renamed to NtCoreLib in 2024. Forshaw introduced the design intent in a December 17, 2019 Project Zero post titled Calling Local Windows RPC Servers from .NET [2], opening with what amounts to a personal manifesto: "As much as I enjoy finding security vulnerabilities in Windows, in many ways I prefer the challenge of writing the tools to make it easier for me and others to do the hunting." The post named a gap in his own methodology -- "one of my big blind spots was anything which directly interacted with a Local RPC server" -- and introduced Get-RpcServer, Get-NtAlpcServer, and New-RpcClient as the cmdlets that closed it.

As much as I enjoy finding security vulnerabilities in Windows, in many ways I prefer the challenge of writing the tools to make it easier for me and others to do the hunting. -- James Forshaw, Calling Local Windows RPC Servers from .NET, Project Zero, December 17, 2019 [2]

The conceptual workflow Forshaw's tooling enables is short enough to fit on one screen. Enumerate every DLL on the system that contains RPC interface metadata. Parse the metadata to recover the IDL-equivalent description of each interface -- the UUID, the version, the procedures, the parameter types. Filter to the ones bound to a local-only protocol sequence. The result is an inventory of "every local RPC procedure callable on this Windows install." Diff the inventory across a Patch Tuesday and the changes -- new procedures, retired procedures, changed security descriptors -- become a research backlog.

JavaScript Get-RpcServer pseudocode: enumerate local RPC interfaces (PowerShell equivalent)
// PowerShell equivalent (run inside an elevated session with NtObjectManager installed):
//   Install-Module NtObjectManager
//   Get-RpcServer -DbgHelpPath 'C:\\Program Files\\Debugging Tools for Windows\\dbghelp.dll' |
//     Where-Object { $_.Endpoints.ProtocolSequence -eq 'ncalrpc' } |
//     Select-Object Name, InterfaceId, @{N='ProcCount';E={$_.Procedures.Count}}

// The runnable below mirrors the same logic in plain JS so the in-browser engine can execute it.
const interfaces = [
{ name: 'AppInfo',        interfaceId: '201ef99a-7fa0-444c-9399-19ba84f12a1a', protocolSequence: 'ncalrpc', procedures: 12 },
{ name: 'schrpc',         interfaceId: '86d35949-83c9-4044-b424-db363231fd0c', protocolSequence: 'ncalrpc', procedures: 27 },
{ name: 'spoolss',        interfaceId: '12345678-1234-abcd-ef00-0123456789ab', protocolSequence: 'ncacn_np', procedures: 96 },
{ name: 'lsarpc-local',   interfaceId: '12345778-1234-abcd-ef00-0123456789ab', protocolSequence: 'ncalrpc', procedures: 81 },
{ name: 'epmapper',       interfaceId: 'e1af8308-5d1f-11c9-91a4-08002b14a0fa', protocolSequence: 'ncalrpc', procedures: 5  },
];

const local = interfaces
.filter(i => i.protocolSequence === 'ncalrpc')
.map(i => ({ name: i.name, interfaceId: i.interfaceId, procCount: i.procedures }));

console.log('Local RPC interfaces (ncalrpc only):');
local.forEach(i => console.log(`  ${i.name.padEnd(16)} ${i.interfaceId}  procs=${i.procCount}`));
console.log(`Total: ${local.length}`);

Press Run to execute.

The third publication is SandboxEscaper's CVE-2018-8440 [26], dropped as a 0-day on GitHub on August 27, 2018, and triaged by CERT/CC as VU#906424 on August 28 with the note that the vulnerability was "being exploited in the wild" [27]. The 0patch team published a micropatch within days and walked the bug specifics [28]. The structural shape of the bug is canonical and is worth tracing carefully.

Ctrl + scroll to zoom
CVE-2018-8440: NULL callback, SYSTEM-context ACL change, SYSTEM execution. The kernel ALPC layer behaved correctly; the Task Scheduler interface registered SchRpcSetSecurity without a security callback, and the SYSTEM-context service performed the requested operation on the attacker-chosen file.

The Task Scheduler service exposes an LRPC interface containing a procedure named SchRpcSetSecurity, registered through RpcServerRegisterIf2 with IfCallbackFn set to NULL. NULL has a specific meaning, documented verbatim on Microsoft Learn: "IfCallbackFn: Security-callback function, or NULL for no callback" [10]. No callback means the RPC runtime dispatches the call without asking the application whether the caller should be allowed.

Once dispatched, SchRpcSetSecurity running in the SYSTEM-context Task Scheduler worker thread set a permissive DACL on a file the attacker specified. The attacker chose a file the attacker did not have write access to. The SYSTEM-context service made it writable. The attacker then wrote attacker-controlled bytes into the file, triggered execution, and inherited SYSTEM.

The 0patch micropatch writeup named the structural pattern as "the Task Scheduler fails to impersonate the requesting client" [28] -- which is to say, the service did the operation in its own privileged identity instead of the caller's. CERT/CC framed the same bug in transport terms: a vulnerability "in the handling of ALPC" that lets an authenticated user overwrite an arbitrary file [27].

The fourth is Tavis Ormandy's CVE-2019-1162 [29], disclosed in the August 13, 2019 Project Zero post Down the Rabbit-Hole... [30]. The bug class Ormandy named is the structural exemplar of "shared system ALPC ports that ignore caller integrity." The Microsoft Text Services Framework (MSCTF) shipped a global ALPC port -- present since Windows XP in 2001 -- that any process on the desktop could open regardless of integrity level. The CTF subsystem trusted clients to identify themselves correctly in the messages they sent; the protocol had no integrity-level check or AppContainer enforcement. A low-integrity browser process could send messages that impersonated a high-integrity privileged process, and the CTF service would honour them. The fix narrowed the specific instance and left the general class of "shared ALPC ports without caller-integrity enforcement" open.

A partially-overlapping fifth example -- the same interface-callback class expressed through DCOM activation rather than direct LRPC -- is Forshaw's October 18, 2018 Project Zero post Injecting Code into Windows Protected Processes using COM [31]. The post documented a class of Protected Process Light (PPL) bypass in which a DCOM activator marshalled an impersonated client token into a privileged COM server, and the server's interface callback trusted the marshalled identity too early in the dispatch flow. The kernel ALPC layer is doing exactly what the spec says; the bug is in the user-mode interface code that interprets the message.

Interface security callback

The application-supplied function whose pointer is passed as the IfCallbackFn argument to RpcServerRegisterIf2 [10] or RpcServerRegisterIf3 [18]. The RPC runtime invokes the callback after the port-level access check passes and before the call is dispatched to the IDL-named procedure. The callback inspects the binding handle, the calling user's token, the integrity level, and any other attribute the application chooses to consult. The callback returns RPC_S_OK to permit the call or any other status code to reject it. A NULL callback pointer is documented as a legal value and means "permit every call that reaches the runtime."

NDR / NDR64

The wire format that LRPC payloads marshal through. NDR is the original 32-bit Network Data Representation transfer syntax used by DCE/RPC; NDR64 is the 64-bit extension Microsoft introduced for 64-bit Windows [32]. Local LRPC and remote MSRPC use the same transfer syntax; the only difference is that local calls travel inside an ALPC PORT_MESSAGE body rather than over a TCP or named-pipe transport.

By the end of 2019, the inventory was visible, the bug class had been named, and four worked exemplars had been published. The mechanism underneath -- what an interface-registration callback actually is, why the OS cannot enforce its correctness -- is what the next section unpacks.

The deeper realisation is that none of these are kernel bugs. The kernel ALPC layer behaved correctly in every one; the bugs live in the user-mode interface-callback layer that Section 7 walks next.

7. The LRPC Overlay -- Interface Registration and the Asymmetry the OS Cannot Fix

Look at the signature of RpcServerRegisterIf2. The seventh parameter is named IfCallbackFn. Microsoft's own reference page documents that NULL is a legal value, and that NULL means "no callback" [10]. That parameter is the asymmetry the rest of this section is about.

A canonical server-side LRPC startup sequence looks like this. The service compiles an IDL file with MIDL; MIDL emits an RPC_SERVER_INTERFACE structure that pins down the interface's UUID, version, and procedure table. The service calls RpcServerUseProtseqEp with the protocol sequence "ncalrpc", an endpoint name, and a security descriptor; that call asks the kernel, by way of the RPC runtime, to create an ALPC connection port at the requested name under \RPC Control. The service calls RpcServerRegisterIf2 or, since Windows 8, RpcServerRegisterIf3 [18]. The newer call additionally accepts a per-interface security descriptor that the runtime enforces before consulting the callback. Both calls store the IDL spec, the interface-registration flags, and the per-interface security callback. Finally the service calls RpcServerListen, and worker threads in the RPC runtime block inside NtAlpcSendWaitReceivePort.

Per call, the dispatch sequence is: accept the inbound ALPC connection, read the NDR-encoded request from the message body, invoke the registered security callback (if any), dispatch to the MIDL-generated server stub, and marshal the reply back.

Ctrl + scroll to zoom
One full LRPC call lifecycle. The kernel ALPC dispatcher routes the message; the user-mode RPC runtime invokes the application's security callback; the application's MIDL stub executes the procedure. The kernel cannot inspect the body of the callback; the runtime trusts whatever the callback returns.

The kernel's job ends at "deliver the message to a worker thread." Everything after that is application code. The RPC runtime is a DLL that the service loads into its own address space, and the runtime's notion of authorization is whatever the callback returns. If the callback returns RPC_S_OK, the call proceeds. If the callback is NULL, the call proceeds without ever asking the application. The kernel has no notion of "this call requires SeImpersonatePrivilege" or "this call requires the caller to be in the local Administrators group", because those notions are policy choices the application makes, not properties of the IPC primitive.

Endpoint Mapper (epmapper)

The RPC service-discovery primitive at the well-known ALPC port \RPC Control\epmapper. An LRPC client that knows the interface UUID it wants to call -- but not which endpoint name a particular service is listening on -- calls into the endpoint mapper, hands over the UUID, and gets back the endpoint name. The mapper is itself an LRPC service; it bootstraps the rest. rpcss (the DCOM activator service) hosts the endpoint mapper on every Windows install.

MIDL (Microsoft Interface Definition Language)

The Microsoft dialect of OSF DCE IDL used to declare RPC interfaces. An .idl file pins down the interface UUID, version, methods, and parameter types; the MIDL compiler produces three artifacts: a header for both client and server, a client-side stub that marshals call arguments into NDR, and a server-side stub that unmarshals NDR back into call arguments and dispatches to the application's implementation.

The interface-registration flag inventory tells the same story from a different angle. Microsoft Learn enumerates the flags on a single reference page [33]; the four that matter for this section are quoted verbatim from that page.

FlagWhat Microsoft says it doesWhat it closesWhat it leaves open
RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH"the RPC runtime invokes the registered security callback for all calls, regardless of identity, protocol sequence, or authentication level of the client"Forces the callback to run even for unauthenticated callsThe correctness of the callback's return value
RPC_IF_ALLOW_SECURE_ONLYrejects callers that did not authenticate at the runtime's minimum authentication levelUnauthenticated callersAuthenticated-but-unauthorized callers; Microsoft notes verbatim that "Using the RPC_IF_ALLOW_SECURE_ONLY flag does not imply or guarantee a high level of privilege on the part of the calling user" [33]
RPC_IF_SEC_NO_CACHE"Disables security callback caching, forcing a security callback for each RPC call on a given interface"Stale cached approval after a token-state changeThe correctness of the callback's body
RPC_IF_ALLOW_LOCAL_ONLYrejects remote callers at the runtime layerCross-machine reachabilityLocal elevation primitives

The table is the argument. Every flag closes a specific known-bad pattern. No flag changes the fact that the per-interface authorization decision is application code. The runtime can be configured to force the callback to run. It cannot be configured to make the callback return the right answer.

Port-level security is kernel infrastructure. Interface-level security is application code. The kernel can enforce the first; it cannot enforce the second. Everything in the rest of this article follows from that asymmetry.

RpcServerRegisterIf3, introduced in Windows 8 [18], partially mitigates the structural concern by adding a per-interface security descriptor argument the runtime checks before the callback runs. Microsoft Learn documents the order: "If both SecurityDescriptor and IfCallbackFn are specified, the security descriptor in SecurityDescriptor will be checked first and the callback in IfCallbackFn will be called after the access check against the security descriptor passes." The If3 API also bakes in an AppContainer default-deny: in the absence of an explicit security descriptor, the runtime refuses calls from AppContainer processes. These are real defences. They do not change the underlying property that the per-call authorization decision -- the one that says "this caller is allowed to invoke this procedure with these arguments" -- is delegated to an application function the kernel cannot inspect.

The kernel-vs-application boundary inside rpcrt4.dll is unusual and easy to miss. The same DLL contains both the user-mode side of the kernel ALPC syscall surface (the thin wrappers around NtAlpcSendWaitReceivePort that the runtime threads call) and the interface dispatch loop that ends in the application callback. Both halves run inside the service process; both halves are user-mode code from the kernel's point of view. The kernel does not know which RPC interface a given ALPC message is going to dispatch to. It just hands the message to a worker thread and forgets.

The endpoint-mapper bootstrap path is the other piece of the LRPC overlay worth naming. A client that knows the interface UUID it wants to talk to -- say, the AppInfo interface UUID for UAC -- but does not know which endpoint name appinfo happens to be listening on, opens the well-known ALPC port \RPC Control\epmapper, sends a query containing the UUID, and gets back the endpoint name. The endpoint mapper is itself an LRPC service running inside rpcss. It bootstraps the rest of the local-IPC fabric.

NDR and NDR64 are the wire format. NdrClientCall3 on the client side packs the call arguments into the NDR representation Microsoft documents on Learn [32]; the bytes ride inside an ALPC PORT_MESSAGE body to the server; NdrStubCall3 on the server side unpacks them. The same NDR format that travels over a TCP socket for cross-machine MSRPC travels through an ALPC port for local LRPC. The transport is the only thing that differs.

The structural punchline is that the RPC runtime is application code -- the callback runs in user mode in the server's address space, the runtime trusts whatever the callback returns, and the OS cannot validate the callback's body. The CVE-2019-1162 MSCTF disclosure [30] and the local-COM-over-LRPC PPL-bypass class [31] are both structural instances of this asymmetry; no kernel change could have prevented them.

That asymmetry is the engine. Almost every CVE on the Patch-Tuesday treadmill since 2018 -- the Task Scheduler ACL bug, the CTF subsystem disclosure, the PPL-COM bypasses, the Potato-family activations -- is structurally the same shape. Some are LRPC bugs. Some are not. The next section explains which is which.

8. Competing Approaches -- Named Pipes, COM, Filter Ports, and the Potato Disambiguation

Roughly half the time a defender reads "Potato" in a CVE writeup, the underlying primitive is not ALPC. The other half of the time, it is. Knowing which is which is the single most-cited reason defenders mis-classify privilege-escalation attacks. The disambiguation matters because the mitigations differ: an LRPC-on-ALPC Potato is closed (or worsened) by RPC interface-flag changes; a named-pipe Potato is closed (or worsened) by SeImpersonatePrivilege policy.

Before the Potato classification, four local-IPC primitives sit alongside LRPC-on-ALPC and deserve a brief tour.

Named pipes [3] [34] [35] are the first-class alternative that works both locally and across machines over SMB. The Windows RPC runtime supports a ncacn_np (Network Computing Architecture, Connection-oriented, Named Pipe) protocol sequence that lets an RPC interface be reached either through \\.\pipe\name locally or through an SMB tree-connect remotely. The load-bearing security primitive for the named-pipe-Potato class is ImpersonateNamedPipeClient [34], a Win32 API that lets the server end of a named pipe impersonate the client process; the API requires the caller to hold SeImpersonatePrivilege. The privilege is granted by default to LocalSystem, LocalService, NetworkService, and to processes that hold the privilege in their token through policy. The named-pipe-Potato attack pattern is "a service running with SeImpersonatePrivilege is tricked into connecting to a named pipe the attacker controls, and the attacker calls ImpersonateNamedPipeClient to inherit the service's token."

SeImpersonatePrivilege

The Windows user-right that permits a thread to impersonate another security principal -- specifically by calling APIs such as ImpersonateNamedPipeClient [34] or ImpersonateLoggedOnUser. The privilege is granted by default to LocalSystem, NetworkService, LocalService, and processes started by the Service Control Manager. As Clement Labro summarised the practical implication: "if you have SeAssignPrimaryToken or SeImpersonate privilege, you are SYSTEM" [36], because every interactive way to use either privilege ends in a SYSTEM token under the right circumstances. The named-pipe-Potato family exploits exactly this fact.

OXID resolver

The DCOM lookup primitive that translates an object exporter identifier (OXID) to a string binding (a protocol sequence plus an endpoint) where the corresponding COM server is listening. By default the OXID resolver runs in rpcss on TCP port 135. RoguePotato [37] [38] -- the post-Windows-10-1809 evolution of the Potato family -- redirects an outbound OXID-resolver query to an attacker-controlled host, which lets the attacker substitute an arbitrary endpoint and, through that, an arbitrary impersonation token.

Shared sections plus events is the lowest-level local-IPC pattern. Two processes call NtCreateSection to back the same shared memory, then synchronise with kernel events or semaphores. There is no framing, no caller-identity primitive, and no message boundary. The pattern is used in performance-sensitive contexts such as browser sandboxes and DirectX swapchain handoff; it is not a competitor with LRPC-on-ALPC for general request-reply use cases.

COM local activation [31] [37] is not a competitor. It is a higher-level overlay. The DCOM activation service (rpcss) takes a CoCreateInstance-style activation request and, for local activations, marshals into LRPC under the hood. This is why DCOM-activation attacks are also LRPC attacks: the trigger transport is DCOM, but the impersonation primitive ends up being the LRPC RpcImpersonateClient machinery that runs inside the activated server.

Filter Communication Ports [39] [40] are the minifilter-specific IPC channel for talking between a kernel-mode file-system filter driver and a user-mode service. A minifilter calls FltCreateCommunicationPort to set up the server side; a user-mode application calls FilterConnectCommunicationPort to attach to it; the kernel-side FltSendMessage and the user-side FilterReplyMessage carry payloads in either direction. Filter Communication Ports are a separate primitive from ALPC and live in their own namespace; the only reason to mention them in this section is that defenders sometimes conflate "any named local IPC endpoint" with ALPC, and they should not.

Now the Potato disambiguation. The Potato family is the loudest local-EoP cluster of the last decade, and the family contains two structurally different sub-families that share the surname for historical reasons.

AxisDCOM-activation PotatoNamed-pipe Potato
Triggering protocolDCOM CoGetInstanceFromIStorage activation against 127.0.0.1 plus the local OXID resolverService connects out to a named pipe controlled by the attacker (often via UNC or by tricking a print or EFS hook)
Impersonation primitiveRpcImpersonateClient invoked by the activated COM server during the LRPC dispatchImpersonateNamedPipeClient invoked by the attacker on the receiving end of the pipe
Required attacker privilegeSeImpersonatePrivilege or SeAssignPrimaryTokenPrivilegeSeImpersonatePrivilege plus the ability to direct the service to connect to the attacker's pipe
Canonical exemplarsRoguePotato (May 2020) [37] [38], JuicyPotato, RottenPotatoPrintSpoofer (2020) [36], EfsPotato, PetitPotam
Post-KB5004442 statusOXID redirection to remote hosts blocked by RPC_C_AUTHN_LEVEL_PKT_INTEGRITY enforcement, March 2023 [41]Unchanged at the OS level; mitigation is SeImpersonatePrivilege hygiene
Underlying IPC fabricLRPC on ALPCNamed pipes

The HITB Amsterdam 2021 talk The Rise of Potatoes: Privilege Escalation in Windows Services by Andrea Pierini and Antonio Cocomazzi [42] is the canonical end-to-end family classification. Pierini and Cocomazzi are also the disclosers of RoguePotato [37] -- the variant that broke the post-Windows-10-1809 mitigation by redirecting the OXID resolver to an attacker-controlled host on a port other than 135. The disclosure was May 11, 2020, building on their December 6, 2019 "RogueWinRM" precursor work [43] in which they obtained a SYSTEM identification token but not yet a usable impersonation token.

The KB5004442 DCOM hardening rollout [41], which addresses CVE-2021-26414, completed phase 3 on March 14, 2023. Phase 3 enabled the hardening with no override path: DCOM activations are subject to RPC_C_AUTHN_LEVEL_PKT_INTEGRITY as a mandatory minimum, and the previously available registry overrides were removed. The OS-default configuration since March 2023 closes the JuicyPotato variant that depended on outbound DCOM to TCP/135 with downgraded authentication. RoguePotato and its descendants survived the rollout because they did not depend on the downgrade -- they depend on the OXID redirect itself, which the hardening did not block at the OS-default configuration.

The comparison matrix gives us the surface area of competing primitives. The next section asks: given this surface area, what can the OS structurally not guarantee?

9. The Limits -- Three Things ALPC and LRPC Structurally Cannot Enforce

The Vista redesign closed half the structural problem of LPC. It left three other things permanently open, and no future ALPC version can close them without a new ABI. Each of the three is a property of the trust model, not a bug in any specific server. Each has a CVE-history footprint that confirms the structural framing.

The interface-callback gate cannot be enforced by the OS. The RpcServerRegisterIf2 contract [10] accepts a function pointer into the application's address space; the runtime trusts whatever the callback returns. The OS-side enforcement available without an ABI change is at most "invoke the callback" (which RPC_IF_SEC_NO_CACHE [33] already enforces on every call). The OS cannot read the callback's source, cannot infer its policy, and cannot decide whether the callback's verdict matches what the application's specification says it should be. Every interface-callback EoP -- CVE-2019-1162 MSCTF [30], the PPL-COM class [31], CVE-2018-8440 [26] -- is a structural instance of this bound. Closing it requires either inventing a declarative authorization ABI the OS can validate, or sandboxing callback execution. Neither has been proposed as a stable Microsoft direction in any public artefact through 2026.

There is no transitive caller identity. ALPC's Security message attribute captures the caller's token at handshake or on demand; it does not carry a chain of trust across multiple hops. A proxy server in the middle of a call chain has to impersonate explicitly or marshal identity in band, and the receiving party at the far end has no kernel primitive that tells it "the message came from caller A, was forwarded by proxy B, and the original token is still attached." Confused-deputy attacks in the LRPC fabric are not bugs; they are an inherent property of the trust model. The DCOM-activation Potato class [37] [38] exploits exactly this property: the DCOM activator passes a token into a privileged COM server, and the server cannot reliably tell whether the token chain on the way in matches what the activator's specification said it should be.

The kernel routing path is in the trusted computing base. The ALPC dispatcher runs in Ring 0. Any bug in _ALPC_PORT object lifecycle, in _ALPC_HANDLE_DATA reference counting, in message-attribute marshalling, or in any of the dozens of structures Geoff Chappell's site [5] [6] documents but Microsoft does not, is a direct kernel-elevation primitive. The CVE history demonstrates the assumption is wishful: CVE-2018-8440 [26] has a kernel reference-counting flavour in addition to the well-known interface-callback flavour, and several of the Patch-Tuesday ALPC EoP advisories of 2020-2024 carry NVD descriptions that say "improperly handles calls to Advanced Local Procedure Call (ALPC)" with no further detail because the underlying bug is a kernel bookkeeping issue Microsoft does not enumerate. The kernel routing path is settled engineering by any reasonable standard, but settled engineering is not zero-bug engineering. A new ALPC CVE in any given Patch Tuesday is consistent with the structural model.

Ctrl + scroll to zoom
The three impossibility results and the Patch-Tuesday treadmill they each feed. Almost every Patch Tuesday since 2018 has shipped fixes inside one of these three rectangles. None of the three is closable without a new ABI.

There is a fourth observation that is not an impossibility result but is worth stating in the same breath: the practical upper bound on local authentication strength. RPC_C_AUTHN_LEVEL_PKT_INTEGRITY is the practical ceiling for local LRPC; the ncalrpc transport supports only RPC_C_AUTHN_WINNT authentication [3], and the strongest integrity check the runtime offers under that authentication service is packet integrity. The KB5004442 DCOM rollout [41] raised the minimum for DCOM activations to PKT_INTEGRITY in March 2023; it did not change the ceiling. The gap between upper and lower bounds is substantial and structural: raising mandatory authentication closes the unauthenticated vector and leaves the authenticated-but-unauthorized vector -- the interface-callback class -- wide open.

The OS can require that the callback runs. It cannot require that the callback returns the right answer. The Patch-Tuesday treadmill is the consequence.

Closing the interface-callback gap would look like one of two architectural shifts. Either Microsoft would introduce a declarative authorization language for RPC interfaces -- a manifest the application ships alongside the IDL that the runtime can parse and the OS can validate -- and then forbid the imperative callback. Or the runtime would execute the callback inside a sandbox that constrains what the callback can do (no arbitrary memory reads of the service's address space, no ability to issue privileged syscalls, no ability to side-channel through global state). Neither is on a publicly-named Microsoft roadmap; the closest public artefact is Forshaw's ongoing tooling work on parsing the interface inventory [25] [2] [46], which equips defenders to audit the callbacks they have rather than to replace the model.

The limits are honest. They are also not the whole story. Research has not stopped trying to close the gap, and the next section names what is still active.

The Patch-Tuesday treadmill is the expected steady state, not a transitional embarrassment. Closing the class requires reworking the contract -- a different ABI, or a sandboxed execution model -- and no public Microsoft roadmap commits to either.

10. Open Problems and a Practical Field Guide (2024-2026)

The 2024-2026 conference cycle is still arguing about how to make the interface-callback class scalable to defend. This section enumerates the open problems and then closes with the practical workflow a defender or an in-house RPC author can run today. The practical recipe is in part an answer to the open problems.

Open problem 1: public RPC fuzzing at Microsoft-internal scale. The public ceiling is RPCForge [23] for NDR-aware fuzzing, Forshaw's NtObjectManager for interface inventory and client generation [25] [2], and the November 2023 PoC talk Building More Windows RPC Tooling for Security Research [46] for the latest research-tooling continuation. Microsoft's internal pipeline is not public; whether a coverage-guided NDR64 fuzzer can become a small-team repeatable Patch-Tuesday tool is open.

Open problem 2: auditing the interface-registration model for structural permissiveness. A defender using Get-RpcServer can enumerate every LRPC interface on a Windows install and dump each interface's procedures and security descriptor. The defender cannot tell, without per-interface manual review, whether a registered callback is correct. Heuristic detection of NULL IfCallbackFn is mechanical; detection of semantically permissive callbacks -- callbacks whose body trusts a field the caller controls -- is open and probably AI-shaped.

Open problem 3: RPC_IF_SEC_NO_CACHE adoption and cost. No public catalogue of which Microsoft services use the flag exists. No per-call cost benchmark is published. Defender heuristics that recommend the flag for high-risk interfaces cannot quantify the performance trade-off they are recommending.

Open problem 4: the local-COM-over-LRPC bypass class. Forshaw's 2018 PPL-COM post [31] articulated a class of attack against Protected Process Light that continues to surface in CVE reports. The structural class is unaddressed at the OS level.

Open problem 5: ALPC as covert channel. The CVE-2019-1162 MSCTF fix [30] narrowed the MSCTF subsystem's exposure. The general class of "shared system ALPC ports that ignore caller integrity" is structural; identifying others requires the kind of systematic audit Open Problem 2 names.

Open problem 6: defender SOC integration of the Microsoft-Windows-Kernel-ALPC ETW provider [15]. The provider is high-volume; production SOC pipelines rarely subscribe to it because the event rate overwhelms commodity collection. Per-call ALPC visibility today is concentrated inside EDR vendors that gate it behind antimalware-PPL processes.

Open problem 7: AppContainer-aware RPC capability checking. RpcServerRegisterIf3 [18] introduces an AppContainer default-deny, but there is no standard pattern for in-house service authors who want to express "this procedure requires capability X." Service authors roll their own; some get it right.

ToolPurposeAuthor / OrgReference
NtObjectManager / NtCoreLib (formerly NtApiDotNet)LRPC interface enumeration, decompilation, and client generation from PowerShell or .NETJames Forshaw, Project Zero[25] [2]
RpcViewQt5/C++ GUI for browsing RPC servers and decompiled interface metadata across Windows versionssilverf0x[47]
RPC Investigator.NET Forms UI built on NtApiDotNet for enumeration, client workbench, and an "RPC Sniffer" ETW-backed live viewTrail of Bits, January 2023[19] [48]
RPCMonETW-based GUI for scanning RPC communication, built like Sysinternals Procmon, depending on Forshaw's libraryCyberArk Labs[49]
RPCForgeNDR-aware local Python fuzzer for ALPC-exposed RPC interfacesClement Rouault and Thomas Imbert, Sogeti ESEC[23]
Forshaw NDR64 / RPC research pipeline (2023)Continued research tooling and conference materialsJames Forshaw[46]

The practical field guide. Eight numbered actions for the defender or in-house RPC service author. Each cites a verified source the reader can re-read in full.

JavaScript Patch-Tuesday RPC interface diff: pseudocode
// Real shell pipeline that produces the inputs:
//   Get-RpcServer | Export-Clixml -Path C:\\Snaps\\rpc-pre-patch.xml
//   <Install Patch Tuesday updates and reboot>
//   Get-RpcServer | Export-Clixml -Path C:\\Snaps\\rpc-post-patch.xml
//   Compare-Object (Import-Clixml C:\\Snaps\\rpc-pre-patch.xml) ...
// The diff logic below is what Compare-Object is doing under the hood, in plain JS.

const pre = new Map([
['201ef99a-7fa0-444c-9399-19ba84f12a1a', ['Activate','Cancel','Continue','GetElevationType']],
['86d35949-83c9-4044-b424-db363231fd0c', ['SchRpcRegisterTask','SchRpcRetrieveTask','SchRpcSetSecurity']],
['e1af8308-5d1f-11c9-91a4-08002b14a0fa', ['ept_lookup','ept_map','ept_insert']],
]);

const post = new Map([
['201ef99a-7fa0-444c-9399-19ba84f12a1a', ['Activate','Cancel','Continue','GetElevationType','RequestElevation2']],
['86d35949-83c9-4044-b424-db363231fd0c', ['SchRpcRegisterTask','SchRpcRetrieveTask','SchRpcSetSecurityV2']],
['e1af8308-5d1f-11c9-91a4-08002b14a0fa', ['ept_lookup','ept_map','ept_insert']],
]);

const interfaces = new Set([...pre.keys(), ...post.keys()]);
for (const uuid of interfaces) {
const a = new Set(pre.get(uuid) || []);
const b = new Set(post.get(uuid) || []);
const added   = [...b].filter(p => !a.has(p));
const removed = [...a].filter(p => !b.has(p));
if (added.length || removed.length) {
  console.log(`Interface ${uuid}`);
  if (added.length)   console.log('  + added:   ' + added.join(', '));
  if (removed.length) console.log('  - removed: ' + removed.join(', '));
}
}

Press Run to execute.

RPCMon ships a hard-coded RPC interface dictionary named RPC_UUID_Map_Windows10_1909_18363.1977.rpcdb.json [49] -- a snapshot of Windows 10 1909 build 18363.1977 -- as the baseline against which it labels traced interfaces. The choice to bake in a build-specific baseline is evidence of how often the inventory needs refreshing: a defender running RPCMon on Windows 11 23H2 in 2026 is looking up call sites against a six-year-old dictionary. The accompanying tooling Forshaw built makes the regeneration mechanical in principle; the burden of running the regeneration is what stays on the defender.
A one-liner you can paste into PowerShell right now

Install Forshaw's module and dump every local-only RPC interface on the current Windows install, one row per interface, sorted by procedure count:

Install-Module NtObjectManager -Scope CurrentUser
Get-RpcServer -DbgHelpPath "$env:ProgramFiles\Debugging Tools for Windows\dbghelp.dll" |
  Where-Object { $_.Endpoints.ProtocolSequence -eq 'ncalrpc' } |
  Sort-Object { $_.Procedures.Count } -Descending |
  Select-Object Name, InterfaceId, @{N='Procs';E={$_.Procedures.Count}} |
  Format-Table -AutoSize

Expect dozens of named interfaces on a clean Windows 11 install. Save the output, install Patch Tuesday, run it again, and Compare-Object the two snapshots. That diff is the canonical research workflow that the December 2019 Project Zero post [2] introduced.

The practical recipe answers the everyday question: what do I do tomorrow morning? The misconceptions section answers a harder question: what should I stop believing?

11. FAQ -- Six Misconceptions, Removed

Half the operational confusion about ALPC and LRPC comes from premises that sound plausible and are wrong. This section names six of them. Each answer starts with the wrong answer, explicitly, before correcting it.

Frequently asked misconceptions about ALPC and LRPC

Is every Windows service reachable over ALPC?

Wrong answer: yes. Right answer: every service that exposes an LRPC interface is. Services that expose only ncacn_np (named-pipe RPC) or ncacn_ip_tcp (TCP RPC) are not reachable over ALPC, even when the caller is on the same machine [3]. The print spooler, for example, exposes its primary interface over named pipes and is the trigger for several of the named-pipe-Potato attacks; AppInfo, Task Scheduler, and the endpoint mapper expose theirs over LRPC and are reachable through the kernel ALPC fabric. The right mental model is "every Windows service that wants to be reachable locally with first-class kernel-mediated transport uses LRPC on ALPC", not "every service uses ALPC."

Are all Potato-family attacks ALPC attacks?

Wrong answer: yes. Right answer: the DCOM-activation Potatoes (RoguePotato [37] [38], JuicyPotato, RottenPotato) exercise LRPC-on-ALPC because local DCOM activation rides that fabric; the impersonation primitive is RpcImpersonateClient inside the activated COM server. The named-pipe Potatoes (EfsPotato, PrintSpoofer [36], PetitPotam) use ImpersonateNamedPipeClient [34] as the impersonation primitive and exercise the named-pipe fabric. The trigger transport can be shared (DCOM, RPRN, EFSR), but the impersonation primitive is what tells you which IPC surface the attack actually exercises. See Section 8 for the 30-second classifier and the HITB 2021 Pierini and Cocomazzi talk [42] for the canonical end-to-end family classification.

Does "every ALPC port has an Object Manager name" hold?

Wrong answer: yes. Several secondary writeups (and the original input premise for this article) say so. Right answer: named connection ports have Object Manager names, typically under \RPC Control or per-session AppContainer subtrees. The per-connection communication ports created by NtAlpcAcceptConnectPort are unnamed and exist only as handles. This is the structural correction Section 4 walks in full and the load-bearing invariant the Vista redesign rests on: only the parties that completed the handshake can address the per-connection channel. The kernel does not let anyone else find it because there is no name to find.

Is ALPC documented by Microsoft?

Wrong answer: yes, it is in the SDK. Right answer: partially. Microsoft does not publish a Win32 or WDK API reference for the Nt*Alpc* and Alpc* surface; the de facto syscall reference is NtDoc [4], and the de facto structure reference is Geoff Chappell's site [5] [6]. Microsoft does document ALPC architecturally in Windows Internals 7th Edition Part 2 [7], Chapter 8 section "Advanced local procedure call (ALPC)"; through the Microsoft-Windows-Kernel-ALPC ETW provider [15]; and indirectly through the user-mode RPC runtime documentation. The documentation gap is a deliberate choice -- Microsoft's position is that application authors should use the RPC runtime, not the kernel ALPC API -- and the gap is the reason the public knowledge of ALPC comes from a handful of named researchers reverse-engineering it.

Is "Local RPC" the same as "Local Procedure Call"?

Wrong answer: yes, the abbreviations collide so they must be related. Right answer: LPC was the original Windows NT 3.1-through-Server-2003 kernel IPC primitive, replaced by ALPC in Vista (November 2006) and removed from the kernel by Windows 7 [8]. LRPC is the Microsoft RPC runtime's transport selected when ncalrpc is the protocol sequence [3]; it has always lived inside rpcrt4.dll, and it rides on top of kernel ALPC ports. The two entities are at different layers (kernel object vs user-mode transport) and were named a decade apart -- LRPC in 1994, ALPC in 2006. The abbreviation collision is real; the entities are not the same thing.

Where is Yarden Shafir's 'ALPC Internals' series at Trail of Bits?

Wrong answer: on the Trail of Bits blog. Right answer: it does not exist under that title. The input premise for this article (and several AI-generated summaries circulating in 2024-2025) referenced a Trail of Bits "ALPC Internals" series by Shafir. The Trail of Bits author page for Yarden Shafir [50] lists her actual posts; the kernel-IPC posts are Introducing Windows Notification Facility's WNF Code Integrity (May 2023) [44] and ETW Internals for Security Research and Forensics (November 2023) [45]. Her dedicated ALPC material lives in her conference training surface, indexed via the Winsider Seminars author page [51]. The cousin posts (WNF and ETW) are the right Trail of Bits citations for the architectural-cousin framing.

The kernel did its job at the port-DACL layer. The application disclaimed responsibility at the interface-callback layer. Almost every Patch-Tuesday LRPC fix since 2018 is some recombination of those two halves, and the half the kernel cannot fix is the half that keeps shipping.

The named-researcher canon for ALPC -- Forshaw, Shafir, csandker, Cerrudo, Cocomazzi, Pierini, Rouault, Imbert, Ormandy, Chappell -- is what this article is an attempt to read in one place.

Study guide

Key terms

ALPC
Advanced Local Procedure Call. The Vista-and-later kernel asynchronous message-and-attribute IPC primitive; replaces classic LPC. Microsoft does not publish a developer-facing reference for the kernel surface.
LRPC
The Microsoft RPC runtime's local-only transport, selected when the protocol sequence is `ncalrpc`. Implemented in `rpcrt4.dll`; rides on top of ALPC ports.
LPC
Local Procedure Call. The original NT 3.1 kernel IPC primitive, synchronous, three-port; replaced by ALPC in Vista and removed from the kernel by Windows 7.
Connection port (ALPC)
The named ALPC port a server creates so clients can find it. Lives in the Object Manager namespace, typically under `\RPC Control`.
Communication port (ALPC)
The unnamed per-connection ALPC port created by `NtAlpcAcceptConnectPort`. Exists only as handles in the connecting and accepting processes; not reachable by name.
Message attribute
An optional in-message kernel service: Context, Handle, Security, or View. Each retires an awkward LPC pattern by moving the work into a single ALPC transaction.
Interface security callback
The application-supplied `IfCallbackFn` passed to `RpcServerRegisterIf2`/`RpcServerRegisterIf3`. The kernel cannot inspect or constrain it. NULL is a legal value and means 'no callback'.
Endpoint mapper
The well-known LRPC service at `\RPC Control\epmapper` that translates an interface UUID into the endpoint name a service is listening on. Hosted by `rpcss`.
NDR / NDR64
The (Network) Data Representation transfer syntax that MIDL-generated stubs use to marshal RPC arguments. Local LRPC and remote MSRPC use the same wire format.
SeImpersonatePrivilege
Windows user-right that permits a thread to impersonate another security principal via APIs such as `ImpersonateNamedPipeClient`. The privilege the named-pipe-Potato family abuses.

Comprehension questions

  1. Why does the per-connection ALPC communication port have no Object Manager name?

    So that no third party can address the channel. Only the parties that completed the handshake hold the paired handles; the kernel does not expose the unnamed port through any namespace operation. This is the half of Cerrudo's 2006 structural class the Vista redesign closed.

  2. Why can the OS not enforce the correctness of an interface security callback?

    The callback is a function pointer into application code. The kernel cannot symbolically execute the function to determine whether its return value is correct, and even if it could, the kernel does not know what 'correct' means for an arbitrary application's authorization policy. Closing the gap requires either a declarative authorization ABI or a sandbox; Microsoft has not publicly committed to either.

  3. What distinguishes a DCOM-activation Potato from a named-pipe Potato?

    The impersonation primitive. DCOM-activation Potatoes (RoguePotato, JuicyPotato, RottenPotato) use `RpcImpersonateClient` inside an LRPC-on-ALPC dispatch path. Named-pipe Potatoes (PrintSpoofer, EfsPotato, PetitPotam) use `ImpersonateNamedPipeClient` on a named pipe. The trigger transport (DCOM, RPRN, EFSR) can be shared; the impersonation primitive is what determines which IPC surface the attack exercises.

  4. What changed in March 2023 for DCOM-activated services?

    KB5004442 phase 3 enabled the DCOM hardening with no override path. `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY` is now a mandatory minimum for DCOM activations, and the previously available registry override is removed. The change closed the JuicyPotato variant at the OS-default configuration.

  5. Where can a defender see ALPC traffic at the per-message level?

    From the `Microsoft-Windows-Kernel-ALPC` system ETW provider, enabled in an `EVENT_TRACE_SYSTEM_LOGGER_MODE` session. The provider is high-volume; production SOC pipelines rarely subscribe directly and instead rely on EDR vendors that gate the provider behind antimalware-PPL processes.

References

  1. Service host (svchost.exe) grouping in Windows 10. https://learn.microsoft.com/en-us/windows/application-management/svchost-service-refactoring
  2. James Forshaw (2019). Calling Local Windows RPC Servers from .NET. https://googleprojectzero.blogspot.com/2019/12/calling-local-windows-rpc-servers-from.html
  3. Protocol Sequence Constants (ncalrpc, ncacn_np, ncacn_ip_tcp). https://learn.microsoft.com/en-us/windows/win32/rpc/protocol-sequence-constants
  4. Michael Maltsev (m417z) and contributors NtDoc: NtAlpcSendWaitReceivePort reference. https://ntdoc.m417z.com/ntalpcsendwaitreceiveport
  5. Geoff Chappell ALPC -- alpc.h reverse-engineered structure documentation. https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/alpc.htm
  6. Geoff Chappell ALPC private API -- alpcp.h reverse-engineered structure documentation. https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/alpcp.htm
  7. Andrea Allievi, Alex Ionescu, Mark E. Russinovich, & David A. Solomon (2021). Windows Internals, Seventh Edition, Part 2. Microsoft Press. ISBN 978-0-13-546238-6.
  8. Christian Sandker (2022). Offensive Windows IPC Internals 3: ALPC. https://csandker.io/2022/05/24/Offensive-Windows-IPC-3-ALPC.html
  9. David A. Solomon (1998). Inside Windows NT, Second Edition. Microsoft Press. ISBN 1-57231-677-2.
  10. RpcServerRegisterIf2 function (rpcdce.h). https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterif2
  11. G. Pascal Zachary (1994). Showstopper!: The Breakneck Race to Create Windows NT and the Next Generation at Microsoft. Free Press. ISBN 0-02-935671-7.
  12. Helen Custer (1993). Inside Windows NT. Microsoft Press. ISBN 1-55615-481-X.
  13. Cesar Cerrudo (2006). WLSI Windows Local Shellcode Injection. https://www.exploit-db.com/papers/13230
  14. Cesar Cerrudo (2006). WLSI -- Windows Local Shellcode Injection. https://www.blackhat.com/presentations/bh-europe-06/bh-eu-06-Cerrudo/bh-eu-06-Cerrudo-up.pdf
  15. ETW System Providers (including the Kernel-ALPC provider). https://learn.microsoft.com/en-us/windows/win32/etw/system-providers
  16. [MS-EFSR]: Encrypting File System Remote (EFSRPC) Protocol. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-efsr/
  17. Christian Sandker (2021). Offensive Windows IPC Internals 2: RPC. https://csandker.io/2021/02/21/Offensive-Windows-IPC-2-RPC.html
  18. RpcServerRegisterIf3 function (rpcdce.h). https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-rpcserverregisterif3
  19. Trail of Bits (2023). RPC Investigator: Microsoft Windows Remote Procedure Call. https://blog.trailofbits.com/2023/01/17/rpc-investigator-microsoft-windows-remote-procedure-call/
  20. Clement Rouault & Thomas Imbert (2017). A view into ALPC-RPC. https://hakril.net/slides/A_view_into_ALPC_RPC_pacsec_2017.pdf
  21. Clement Rouault & Thomas Imbert (2017). A view into ALPC-RPC (PacSec 2017 slide mirror). https://www.slideshare.net/slideshow/rouault-imbert-alpcrpcpacsec/81414417
  22. Clement Rouault & Thomas Imbert (2017). A view into ALPC-RPC (Hack.lu 2017 recording). https://www.youtube.com/watch?v=D-F5RxZ_yXc
  23. Clement Rouault & Thomas Imbert RPCForge: a Python fuzzer for Windows RPC interfaces over ALPC. https://github.com/sogeti-esec-lab/RPCForge
  24. (2017). CVE-2017-11783: Windows Elevation of Privilege Vulnerability (ALPC). https://nvd.nist.gov/vuln/detail/CVE-2017-11783
  25. James Forshaw sandbox-attacksurface-analysis-tools (NtCoreLib / NtObjectManager). https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools
  26. (2018). CVE-2018-8440: Windows ALPC Elevation of Privilege Vulnerability. https://nvd.nist.gov/vuln/detail/CVE-2018-8440
  27. (2018). CERT/CC VU#906424: Microsoft Windows task scheduler SchRpcSetSecurity API contains a vulnerability. https://www.kb.cert.org/vuls/id/906424
  28. Mitja Kolsek (2018). How we micropatched a publicly dropped 0day in Task Scheduler. https://blog.0patch.com/2018/08/how-we-micropatched-publicly-dropped.html
  29. (2019). CVE-2019-1162: Windows ALPC Elevation of Privilege Vulnerability (MSCTF). https://nvd.nist.gov/vuln/detail/CVE-2019-1162
  30. Tavis Ormandy (2019). Down the Rabbit-Hole.... https://googleprojectzero.blogspot.com/2019/08/down-rabbit-hole.html
  31. James Forshaw (2018). Injecting Code into Windows Protected Processes using COM. https://googleprojectzero.blogspot.com/2018/10/injecting-code-into-windows-protected.html
  32. Transfer Syntax and NDR64. https://learn.microsoft.com/en-us/windows/win32/rpc/transfer-syntax-and-ndr64
  33. RPC interface registration flags. https://learn.microsoft.com/en-us/windows/win32/rpc/interface-registration-flags
  34. ImpersonateNamedPipeClient function (namedpipeapi.h). https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-impersonatenamedpipeclient
  35. Christian Sandker (2021). Offensive Windows IPC Internals 1: Named Pipes. https://csandker.io/2021/01/10/Offensive-Windows-IPC-1-NamedPipes.html
  36. Clement Labro (itm4n) (2020). PrintSpoofer: Abusing Impersonation Privileges on Windows 10 and Server 2019. https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/
  37. Antonio Cocomazzi & Andrea Pierini (2020). No more JuicyPotato? Old story, welcome RoguePotato. https://decoder.cloud/2020/05/11/no-more-juicypotato-old-story-welcome-roguepotato/
  38. Antonio Cocomazzi & Andrea Pierini RoguePotato (source). https://github.com/antonioCoco/RoguePotato
  39. Communication between user mode and kernel mode (Filter Manager). https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/communication-between-user-mode-and-kernel-mode
  40. FltSendMessage function (fltkernel.h). https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltsendmessage
  41. KB5004442 -- Manage changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414). https://support.microsoft.com/en-us/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-37ed901c769c
  42. Andrea Pierini & Antonio Cocomazzi (2021). The Rise of Potatoes: Privilege Escalation in Windows Services. https://conference.hitb.org/hitbsecconf2021ams/materials/D2T1%20-%20The%20Rise%20of%20Potatoes%20-%20Privilege%20Escalation%20in%20Windows%20Services%20-%20Andrea%20Pierini%20&%20Antonio%20Cocomazzi.pdf
  43. Antonio Cocomazzi & Andrea Pierini (2019). We thought they were Potatoes but they were beans. https://decoder.cloud/2019/12/06/we-thought-they-were-potatoes-but-they-were-beans/
  44. Yarden Shafir (2023). Introducing Windows Notification Facility's WNF Code Integrity. https://blog.trailofbits.com/2023/05/15/introducing-windows-notification-facilitys-wnf-code-integrity/
  45. Yarden Shafir (2023). ETW Internals for Security Research and Forensics. https://blog.trailofbits.com/2023/11/22/etw-internals-for-security-research-and-forensics/
  46. James Forshaw (2023). Building More Windows RPC Tooling for Security Research. https://powerofcommunity.net/assets/v0/poc2023/JamesForshaw.pdf
  47. silverf0x RpcView. https://github.com/silverf0x/RpcView
  48. Trail of Bits RPC Investigator (source). https://github.com/trailofbits/RpcInvestigator
  49. CyberArk Labs RPCMon: ETW-based GUI tool for scanning RPC communication. https://github.com/cyberark/RPCMon
  50. Trail of Bits author page: Yarden Shafir. https://blog.trailofbits.com/authors/yarden-shafir/
  51. Winsider Seminars author page: Yarden Shafir. https://windows-internals.com/author/yarden/