Dark background with blue code overlay
Blog

A Definitive Guide to the Remote Procedure Call (RPC) Filter

Akamai blue wave

Written by

Ophir Harpaz and Stiv Kupchik

February 23, 2022

Stiv Kupchik is a Security Researcher at Akamai, and Ophir Harpaz is a Security Research Team Lead at Akamai.

rpcfilters.png

Remote Procedure Calls (RPC) are one of the most widespread protocols in use today. It allows for inter-process communication, both on a single host and across the network, and serves as a critical building block for countless applications and services. For the sake of this guide, we’ll be referring to MSRPC as we discuss RPC, given the focus of our work.

The RPC filter is a mechanism in Windows that enables controlling and limiting RPC traffic, as well as limiting the creation of RPC endpoints. It is implemented by the Windows Filtering Platform (WFP) and is exposed through the netsh command-line utility. The RPC filter has been available since Windows Vista and Windows Server 2008.

This guide covers multiple aspects of the RPC filter to help those looking to leverage it in their practice to properly defend against RPC-based attacks. Such aspects include what RPC filters can do, the filter’s internal implementation, the interfaces that expose them (netsh for users and Windows API for developers), as well as step-by-step guidance with explanations on creating, testing, deleting, and viewing RPC filters.  

Akamai’s goal is to offer this guide as a tool that can benefit many different types of practitioners — from network administrators wishing to protect their environments, and developers curious to learn RPC filter’s API, to security researchers interested in the bits and bytes of the mechanism, or red-teamers attempting to bypass it.

 

Why use Remote Procedure Call filters?

RPC exploitation is not a hypothetical scenario. For example, Ryuk ransomware leverages remote scheduled task creation using the RPC protocol for lateral movement and malware propagation. RPC filter came into light recently as a tool to mitigate PetitPotam — a privilege escalation attack targeting the RPC server of the encrypting file system (EFS) feature on Windows. 

Blocking RPC completely to mitigate these attacks would be devastating for a data center’s functionality, as many Windows services depend on this protocol. RPC filters, however, provide a more granular control of RPC traffic that is essential to protect the network from novel threats.

The RPC filter can mitigate different attack scenarios. The table below shows some examples of what one can do with RPC filters:

Prevention method

Related MITRE technique

Allow only certain users (e.g., domain administrators) to create services remotely

Create or Modify System Process: Windows Service

Allow only specific machines (e.g., IP addresses) to modify registry data remotely

Modify Registry, Query Registry

Completely prevent scheduled tasks from being created over the network

Scheduled Task/Job: Scheduled Task, Scheduled Task/Job: At (Windows)

Allow only Kerberos authentication to a certain RPC endpoint

Use Alternate Authentication Material: Pass the Hash

Block connections to an RPC endpoint made over a certain named pipe

Remote Services: SMB

Controlling and limiting RPC traffic is made possible by Windows Filtering Platform (WFP), which exposes five separate filtering layers. The layers differ in the phase at which packets are inspected, and the data fields available at that point in time. All five layers run in user mode, and are listed below:

Layer identifier

Purpose

FWPM_LAYER_RPC_UM 

Filter traffic destined to RPC endpoints

FWPM_LAYER_RPC_EPMAP

Filter traffic destined to the RPC endpoint mapper for endpoint resolution

FWPM_LAYER_RPC_EP_ADD

Filter the creation of new RPC endpoints

FWPM_LAYER_RPC_PROXY_CONN 

Filter connections made to the RPC proxy (RPC over HTTP)

FWPM_LAYER_RPC_PROXY_IF 

Filter connections made to certain RPC interfaces over the RPC proxy

Each of the layers has a set of conditions that are available at that layer. MSDN lists the layers and conditions, to which we added our own explanations and insights in this table.

Auditing Remote Procedure Call traffic

Simply filtering traffic, however, isn’t always enough. Often, monitoring is also needed — both for security reasons (to see what unwanted operations are attempted in the network and blocked), and to see if filter rules are operating correctly. Luckily, WFP contains built-in capabilities to create audits of both successful and failed connections, and this extends to RPC traffic and filters as well. Once auditing is configured, RPC events will be written to the Security Event Log.

 

Audited RPC events are written to the Security event log with event id 5712

Under the hood, RPC filter auditing is achieved with a special sublayer named FWPM_SUBLAYER_RPC_AUDIT, which filters the need to specify for their events to be logged. See the sections below on adding filter auditing when using netsh or the Windows API.

RPC auditing isn’t enabled by default. To enable it, you can use the following command:

auditpol /set /subcategory:"RPC Events" /success:enable /failure:enable

Accessing the Remote Procedure Call filter

There are two ways to access the RPC filter. Network administrators will most likely use netsh to manage their filters, as it is a relatively simple command-line utility. The RPC filter is also exposed through the Windows Filtering Platform (WFP) API, to allow developers to integrate RPC filtering functionality to their applications.

Using netsh to create RPC filters

Network administrators can add, list, and remove RPC filters using netsh. The RPC filter is a netsh context that is implemented in a helper DLL named rpcnsh.dll. To enter the RPC filter context, simply run netsh from an elevated prompt and then execute rpc filter.

The process of adding filters through netsh is as follows:

  1. Add a rule

  2. Add at least one condition to the rule

  3. Add the rule (along with its conditions) to a filter

Note: In WFP, filters are created and assigned to a certain layer, and conditions are then added to those filters; netsh adds the concept of rules to this hierarchy, which is somewhat unnecessary as only one rule can be attached to a filter. 

Let’s continue with the command syntax for every step.

When adding a rule, the following parameters can be specified:

  • layer — the WFP layer to which the rule (=filter) should be added

  • actiontype — block, permit, or continue

  • persistence — whether or not the rule is persistent across reboots

  • filterkey — set a custom UUID for the RPC filter (if not specified, one is created randomly)

  • audit — set the rule to be an audit rule (see the “Auditing with netsh” section)

When adding a condition, the following parameters can be specified:

  • field — the data field on which comparison is performed; fields are specified in our table as well as in the WFP documentation

  • matchtype — the matching operation (equal, greater, less, etc.)

  • data — the value to associate with the field

When adding a rule to a filter, one simply needs to execute add filter and the current rule (along with its conditions) will be added to the filter. This command takes no parameters.

The general netsh chain of commands would look like this:

rpc filter

add rule layer=um actiontype=block

add condition field=if_uuid matchtype=equal data=f6beaff7-1e19-4fbb-9f8f-b89e2018337c

add filter

These commands result in the creation of an RPC filter that blocks remote connections to the RPC endpoint with the specified UUID, which happens to be the RPC interface endpoint of the Windows Event Log.

netsh does not support adding rules with IPv4 conditions!

Whereas WFP allows for limiting traffic based on the remote and local IP addresses, netsh (for unknown reasons) does not expose this functionality.

We provide a tool to complement this missing feature in netsh in our repo.

Auditing with netsh

RPC filter auditing is supported on netsh, though only with permit rules. To add auditing, simply add the flag audit=enable to the rule creation command. According to MSDN, auditing can be applied to all layers except for FWPM_LAYER_RPC_EP_ADD.

Using Windows API to create RPC filters

As the RPC filter is implemented in Windows Filtering Platform (WFP), it can be managed through the WFP API that is exported by FWPUCLNT.DLL and declared in fwpmu.h. This can be useful for developers who wish to build their own defense tools and mechanisms. The general process of adding filters through the WinAPI is as follows:

  1. Create a session and get a handle to FwpmEngine with FwpmEngineOpen0

  2. Populate the filter struct FWPM_FILTER0 with the data needed for the filter (layer, action and conditions)

  3. Call FwpmFilterAdd0 to register the filter

  4. Close the handle to the FwpmEngine with FwpmEngineClose0

All the structures and functions are documented in MSDN, and are pretty straightforward to find. Nonetheless, we provide a detailed walk-through of the API in the section below.

Auditing with the API

To make the rules audit to the Event Log, two things need to happen:

  1. The filter should populate the sublayer struct member with FWPM_SUBLAYER_RPC_AUDIT, thus registering to the auditing sublayer.

  2. The rawContext member of the filter struct should be set to 1. We haven’t found the documentation supporting this, but we have found it necessary to do so to set the auditing. Netsh is doing the same thing behind the scenes when setting auditing for filters.

netsh code logic for adding filter auditing

Step by step: Working with RPC filters

We’ve gone through all the theory of creating RPC filters both using netsh and the Windows API, but nothing beats hands-on experience. For this purpose, we’re including a short tutorial session using Windows Event Log’s RPC endpoint, whose UUID is F6BEAFF7-1E19-4FBB-9F8F-B89E2018337C. For your convenience, we have also opened a GitHub repository with all the code and scripts from this tutorial.

How to locate an RPC interface’s UUID

RPC interfaces, like any other interface, specify which functions (procedures) that an RPC server exposes. They are defined using .IDL files and are uniquely identified using UUIDs. But given a certain service, or RPC server, how can one locate the UUID to block?

  • RpcView is an open source tool that lets you observe RPC endpoints on your machine. It also provides the UUIDs of interfaces that are implemented on each endpoint.

  • Microsoft provides a technical overview for each of their protocols. For some of these protocols, the introduction section contains a Standard Assignments document where the UUIDs of RPC endpoints are specified. See Encrypted File System (EFS) as an example.

Creating an RPC filter with netsh

To block all remote RPC connections to the Event Log, you can use the netsh commands listed in Using netsh to create RPC filters. To pass this as a script to netsh, simply call it with the -f flag:

 

Using netsh with the -f flag

More RPC filter scripts

Jonathan Johnson from Red Canary published a GitHub repository with multiple RPC filter scripts, each tailored to a specific potentially exploitable service.

Creating an RPC filter with WinAPI

For brevity, we will not include the full source code in this guide, but only parts of it. The full code can be found in our repo.

To get started, simply include the header fwpmu.h and point the linker to the DLL’s lib file (we did it simply with #pragma comment(lib, "Fwpuclnt.lib")). 

Opening a session

To get a handle to the firewall engine, create the session struct FWPM_SESSION0, zero it out and fill some of its fields. You really only need to set the kernelMode boolean to FALSE, but we also set the dynamic session flag to not have to deal with cleanup. The filter will disappear automatically once the session closes (which happens automatically upon process termination). Pass the session pointer to FwpmEngineOpen0.

FWPM_SESSION0 session;
ZeroMemory(&session, sizeof(session));
session.kernelMode = FALSE;
session.flags = FWPM_SESSION_FLAG_DYNAMIC;
result = FwpmEngineOpen0(
NULL,
RPC_C_AUTHN_WINNT,
NULL,
&session,
&engineHandle);

Specifying conditions

Conditions are specified in the FWPM_FILTER0 struct under the member filterCondition. They come as an array of the struct FWPM_FILTER_CONDITION0, where each array member is a different condition. Conditions in the array are matched using logical AND.

MSDN lists filtering conditions per each layer and provides details for each filter. We created our own table aggregating this information with our own insights and observations.

The condition struct FWPM_FILTER_CONDITION0 is fairly straightforward; simply specify the field identifier (e.g., FWPM_CONDITION_RPC_IF_UUID), the value which you want to match (e.g., 338CD001-2244-31F1-AAAA-900038001003), and how to match it (equals, contains, greater, etc.). The API doesn’t accept a string UUID but a struct instead. You can use UuidFromString to achieve this translation. 

FWPM_FILTER_CONDITION0 fwpCondition;
ZeroMemory(&fwpCondition, sizeof(fwpCondition));
fwpCondition.matchType = FWP_MATCH_EQUAL;
fwpCondition.fieldKey = FWPM_CONDITION_RPC_IF_UUID;
fwpCondition.conditionValue.type = FWP_BYTE_ARRAY16_TYPE;
fwpCondition.conditionValue.byteArray16 = &interfaceUUID;

Pass the filtering condition to the filter struct with action BLOCK:

ZeroMemory(&fwpFilter, sizeof(fwpFilter));
fwpFilter.layerKey = FWPM_LAYER_RPC_UM;
fwpFilter.action.type = FWP_ACTION_BLOCK;
fwpFilter.weight.type = FWP_EMPTY; // auto-weight.
fwpFilter.numFilterConditions = 1;
fwpFilter.filterCondition = &fwpCondition;

Adding a filter

Once all the necessary struct members have been set, a simple call to FwpmFilterAdd0 with the engine handle, and a pointer to the filter struct will register it in the WFP and will get it to work.

FwpmFilterAdd0(engineHandle, &fwpFilter, NULL, NULL);

Viewing your filters

To view your existing RPC filter, simply use netsh’s show filter command, inside the RPC filter context.

 

Output of netsh's show filter command

Testing your filters

To trigger a RPC call to the Event Log, which can be caught by RPC filters, you can use the following command:

wevtutil /r:127.0.0.1 qe System /c:1

This would try to access the Event Log over RPC, over the network, even though it is confined to the localhost. With the RPC filter we described above in place, which blocks RPC connections from remote computers on the interface UUID, the call gets denied:

 

remote operations are met with an Access Denied error with an RPC filter in place

Deleting filters

To delete filters, if you haven’t used the dynamic flag in their creation (if created with WinAPI), you can use netsh’s delete filter command. You can put a specific filter key instead of all.

 

deleting filters using netsh's delete filter command

The OS internals of rule creation and packet blocking

This section is a deep dive into RPC filter internals. It describes the flow that takes place internally, from the moment a rule is created until a packet is blocked. This knowledge can be of use for both security researchers and red-teamers seeking to find vulnerabilities in the implementation or bypasses of the filter.

The moving parts

The following are the processes and libraries that are involved in RPC filtering.

Image

Details

netsh.exe

The command-line interface that exposes RPC filter functionality

rpcnsh.dll

The helper DLL netsh uses to expose RPC filters

fwpuclnt.dll

WFP’s library, which exports WFP API for user-mode clients

rpcrt4.dll

The RPC runtime, loaded and used by any process implementing an RPC server

RpcRtRemote.dll

An extension DLL implementing the filtering logic for the user-mode and endpoint mapper layers

rpchttp.dll

An extension DLL implementing filtering logic for RPC proxy layers

Rule creation

When a new rule is created, the helper DLL for the RPC filter (rpcnsh.dll) invokes WFP functionality from fwpuclnt.dll. This results in RPC requests to an interface that is exported by the Base Filtering Engine, a service that manages firewall policies and implements user-mode filtering.

 

rpcfilter.png

Consequently, a WNF state named WNF_RPCF_FWMAN_RUNNING is changed. The RPC runtime subscribes to this state, such that whenever it changes, the RPC runtime loads the extension DLL that filters packets.

A packet’s journey

An RPC packet’s journey includes parts in the kernel space as well as the transition from kernel to userland. We will skip these parts, and focus only on the parts of the journey that are relevant to the RPC filtering. There are two parts for this: what happens in the context of the receiving process, and what happens in the Windows Firewall itself, namely the Base Filtering Engine. Standing between the two parts is the extension DLL RpcRtRemote.dll, with its FwFilter function.

 

call stack for a process RPC filter

First, the receiving process. Once the RPC packet has passed the OS’s processing, and is associated with the relevant process and identified as an RPC packet, it arrives at the RPC runtime — RpcRT4.dll (RPCRT4!CO_ConnectionThreadPoolCallback).

The RPC runtime begins parsing the received packet, and eventually checks if the connection is allowed, with an AccessCheck (RPCRT4!OSF_SCALL::DoSecurityCallbackAndAccessCheck); this matches other Windows API naming conventions — for example, object access rights are also verified with a function called AccessCheck.

Did you know?

Microsoft’s RPC is based on OSF’s DCE/RPC, thus the OSF_SCALL prefix means the function is for handling RPC connections on the server side (versus CCALL for RPC client side).

The AccessCheck doesn’t do much. It checks if filtering is needed by determining whether the client is local or remote (RPC filters only apply to remote clients):

 

disassembly of DoSecurityCallbackAndAccessCheck showing the check if filtering is required

If filtering is required, the RPC runtime attempts to load the extension DLL if it isn’t already loaded, and then calls RPCRT4!OSF_SCALL::FwFilter. The RPC runtime FWFilter function collects some information on the remote client (e.g., its IP address) and passes the ball to the extension DLL’s FwFilter function (RpcRtRemote!FwFilter). 

The actual data that is passed to the function depends on the RPC transport layer. For example, for named pipe transport (ncacn_np) the pipe name and username are passed, but for regular TCP transmission (ncacn_ip_tcp) the packet contains network data, like source and destination IP and ports.

What the extensions DLL’s FwFilter does is fairly similar to what the runtime did. It collects more information on the connection (e.g., the security descriptor of the connecting user), and then uses the actual Windows Firewall client DLL fwpuclnt.dll, with a call to the internal function FwpsClassifyUser0. This is the last part in the function call chain that occurs in the context of the process hosting the RPC server. From there, a local RPC request is made to the Base Filtering Engine, which is what is actually responsible for deciding whether the connection should be allowed or blocked.

Conclusion

RPC filters are a powerful utility that comes with Windows Firewall. They open the door for various defense and segmentation capabilities that aren’t easily accessible using traditional security tools, like segmenting randomly assigned ephemeral ports or specific users. 

In this guide, we explained what RPC filters are in the Windows Firewall, how they can be utilized, and their possible use cases. We included guides on their usage, both for power users using netsh, and for developers using Windows API. We also added a short tutorial for creating a filter, shared a GitHub repository with code examples, and researched the inner workings of packet filtering.

We wrote this guide in the hope that the security community can build atop it various tools and capabilities that better defend against emerging threats. We also hope that our work will pave the way for further OS internal research to ensure the security and reliability of this and other protection mechanisms.



Akamai blue wave

Written by

Ophir Harpaz and Stiv Kupchik

February 23, 2022

Stiv Kupchik is a Security Researcher at Akamai, and Ophir Harpaz is a Security Research Team Lead at Akamai.