Anyone working in security or networking will stumble upon the Windows Filtering Platform (WFP) at some point in their career. The WFP is utilized by a whole host of security apparatuses (the Windows firewall, Windows services, applications, and more), which each create their own customized network rules. This is all well and good, until something breaks. Then, debugging the WFP is no walk in the park.
This post will help you make sense of the WFP by exploring it hands on. To accomplish this, we will use a new open-source tool we released called WTF-WFP. WTF-WFP enables users to understand complex WFP issues in production environments via a simple command line interface.
After running WTF-WFP, the reader will finally understand WTF (’What The Filter’) is going on with WFP.
Windows Filtering Platform Basics
Before we provide a quick primer on the WFP, it is important to note that this is not a comprehensive analysis of the WFP. For more details on WFP, we suggest starting with James Forshaw’s excellent WFP architecture primer in this post about AppContainers, or dive into the official Microsoft documentation.
To put it simply, the Windows Filtering Platform is the underlying mechanism that enables various components to block, permit, audit and perform more complex operations on network traffic (such as deep packet inspection). The Windows Defender Firewall with Advanced Security—which is the built in firewall for modern Windows OS—is probably the most obvious example of an application that utilizes the WFP to enforce network restrictions. Additional applications, 3rd party firewalls, and various services can also interact with the WFP. Changes to the WFP are mediated between the user mode and kernel via the Base Filtering Engine service.
The most important elements of WFP to understand are the following:
- Filter: A filter is made up of conditions (IP address, port, application, protocol, user, etc.) and a decision: permit, block or callout (which also breaks down to terminating / unknown / inspection).
- Sublayer: A sublayer is a way to group filters together so their arbitration in a single sublayer can be predicted. For example, firewall rules all belong to the same sublayer, and Windows Service Hardening rules are in a separate sublayer.
- Layer: Most layers correlate to an event in the networking stack, such as listening on a socket, or various stages in the TCP handshake. There are usually two matching layers of the same network: one for IPv4 and one for IPv6.
Now that we have the basics covered, we can start exploring the WFP. But before we do, it will be helpful to visualize just how the WFP layers relate to actual TCP and UDP connections. Here’s a diagram that shows how different network operations translate to WFP layers.
Understanding these layers is helpful when debugging various WFP issues. The ALE layers (any layer with ALE in its name) are stateful, while the others are stateless. So an ALE layer already indicates at which state of the connection the filters apply.
Exploring with WTF-WFP
Now that we got those pesky definitions out of the way, we can really start exploring the WFP ourselves! We will start by installing the WTF-WFP. WTF-WFP depends on NtObjectManager to interact with the WFP API (thanks to James Farshow for the hard work he put there). Both are PowerShell modules that can be installed from the PowerShell Gallery. Note: MS Defender may alert on NtObjectManager, so you may need to turn off real time protection / set the PS Gallery as trusted / create exclusion for the module.
From an elevated PS:
Once done, you should have the Get-WFPInfo command available. To get more info about this command, please refer to the readme and consult the help:
Now that installation is done, we can start by looking at the WFP status. Let’s start with some sheer numbers. We will run the Get-FwFilter command (from NtObjectManager) to see just how many filters we have on our host. In the next example, we ran the command twice: first when the firewall was on, and then when it was turned off:
As we can see, there are A LOT of filters. Even when the firewall is off, we still have 204(!) filters – showing us that the firewall is indeed not the only thing using the WFP.
Now, let’s start by trying to understand some of the filters (at this stage, the firewall is still turned off). We are interested in filters that are relevant for inbound traffic for our local IP address. Get-WFPInfo uses the connection direction and IP address to show only filters in layers that are relevant to the traffic direction, and with conditions that match the IP protocol we’re interested in.
Right off the bat, we understand that most filters are either in the FWPM_LAYER_ALE_AUTH_CONNECT or FWPM_LAYER_ALE_AUTH_ACCEPT layers. This shouldn’t be surprising, as it makes most sense to have stateful filters, right before an outbound connection is made, or right when an inbound connection should be accepted (or not).
Another thing to note are the sublayers. The majority of filters are in the MICROSOFT_DEFENDER_SUBLAYER_WSH sublayer , but there are a couple more for the firewall sublayer (which seems odd, as we turned the firewall off). These are actually two filters that do the exact same thing, which is to allow AppContainers with the same SID to communicate with one another.
Now, let’s turn on the firewall again, and rerun our Get-WFPInfo command. The filters that match our condition will be in under the WFP Relevant Filters section. This time, the output is much more verbose – so we will only add snippets here:
First thing that is important to understand, is that WTF-WFP shows the filters ordered by weight. This is the actual filter processing order (or arbitration if we use MS terminology) that takes place. Arbitration is a bit confusing at first, but looking at the output of Get-WFPInfo orders makes the arbitration easier to understand:
- Each sublayer is considered from top to bottom for the relevant layer matching the network traffic.
- If there is a match with the filter conditions, the processing for this sublayer is done, and WFP moves on to the next sublayer.
- Finally, the WFP takes the results from all the sublayers and decides whether to permit or block the connection. Usually a block overrides a permit (apart from more complex conditions of override policy, which we will not go into).
TIP: The filter conditions are shown in the strConditions part. Due to the abundance of information, it is recommended to output the command result into a csv, which then can be inspected more thoroughly. To do this use the -csvPath parameter.
Knowing how arbitration works, we can notice the following:
- At the top, the initial filters are the quarantine filters, which take precedence over all other filters. These are the filters that block network traffic while the network interface is undergoing changes which could affect the network profile (such as switching WiFi networks).
- In the middle, there are the Windows service hardening rules, which are there whether the firewall is on or off.
- Lastly, the firewall filters themselves are considered.
One of the big takeaways from this should be that there is a lot of filtering happening “outside” the firewall filters. Which could cause network traffic to be blocked or permitted, even if there is no firewall rule that matches this traffic, or even in-spite of a firewall rule that does the opposite. For example, if network traffic resulted in a block because of quarantine or WSH filters, it will override permitted traffic in the firewall.
Even though we now understand layers, sublayers, filters and how the WFP processes them against a given network traffic, it could still be challenging to understand why specific traffic is getting blocked or allowed. Using the “old” ways, one would have to enable windows security events or other WFP traces, and export complex filter configurations while trying to understand why a certain connection is getting blocked. Luckily, Get-WFPInfo also has a tracing capability, that will show us the WFP decisions that match specific network traffic, along with the matching filters.
Let’s assume we want to understand why port 5375 is being blocked. Again, we will use the Get-WFPInfo command with the netTrace param:
Now we see the network events that were captured, and which layers and sublayers were responsible for dropping these packets. Finally, the details of each filter shown in the trace are printed for easier analysis. In this case, the “Query User” filter hit because there is no other more specific filter that allows this connection in the firewall sublayer. So, any network traffic that originated from a domain network is blocked.
WTF-WFP is obviously not the only method to explore WFP. There are additional tools that can help you explore the WFP configuration:
- The NtObjectManager exposes many WFP APIs, so you can do more than just explor WFP via PowerShell, you can even create filters or build your own analysis modules (like WTF-WFP).
- WFPExplorer is another excellent tool, that displays filters in a GUI.
However, even with such tools, debugging the WFP is no easy task. WTF-WFP is the first attempt at giving users the ability to quickly understand WFP issues, without familiarizing themselves with all of the WFP details, and without the need to familiarize themselves with the WFP API.
As with all our open-source tools, we are eager to hear feedback from the community. Please don’t hesitate to reach out via email@example.com for any suggestions or issues.