Windows filtering platform wfp

From Wikipedia, the free encyclopedia

Windows Filtering Platform (WFP) is a set of system services in Windows Vista and later that allows Windows software to process and filter network traffic. Microsoft intended WFP for use by firewalls, antimalware software, and parental controls apps. Additionally, WFP is used to implement NAT and to store IPSec policy configuration.

WFP relies on Windows Vista’s Next Generation TCP/IP stack. It provides features such as integrated communication and per-application processing logic. Since Windows 8 and Windows Server 2012, WFP allows filtering at the second layer of TCP/IP suite.

The filtering platform includes the following components:

  • Shims, which expose the internal structure of a packet as properties. Different shims exist for protocols at different layers. WFP comes with a set of shims; users can register shims for other protocols using the API. The in-built set of shims includes:
    • Application Layer Enforcement (ALE) shim
    • Transport Layer Module (TLM) shim
    • Network Layer Module (NLM) shim
    • RPC Runtime shim
    • Internet Control Message Protocol (ICMP) shim
    • Stream shim
  • Filtering engine, which spans both kernel-mode and user-mode, providing basic filtering capabilities. It matches the data within a packet – as exposed by the shims – against filtering rules, and either blocks or permits the packet. A callout (see below) may implement any other action as required. The filters operate on a per-application basis. To mitigate conflicts between filters, they are given weights (priorities) and grouped into sublayers, which also have weights. Filters and callouts may be associated to providers which may be given a name and description and are essentially associated to a particular application or service.
  • Base filtering engine, the module that manages the filtering engine. It accepts filtering rules and enforces the security model of the application. It also maintains statistics for the WFP and logs its state.
  • Callout, a callback function exposed by a filtering driver. The filtering drivers provide filtering capabilities other than the default block/allow. Administrators specify a callout function during registration of a filter rule. When the filter matches, the system invokes the callout, which handles a specified action.

Starting with Windows 7, the netsh command can diagnose of the internal state of WFP.

Microsoft released three out-of-band hotfixes for WFP in Windows Vista and Windows 7 to address issues that could cause a memory leak, loss of connectivity during a Remote Desktop Connection session, or a blue screen of death. Later, these hotfixes were rolled up into one package.[1]

  1. ^ «A Windows Filtering Platform (WFP) driver hotfix rollup package is available for Windows Vista, Windows Server 2008, Windows 7, and Windows Server 2008 R2». Windows support. Microsoft. 12 April 2010.
  • Kresten, Proteus Valre (May 2012). Windows Filtering Platform. VolutPress. ISBN 978-620-1-65842-4. Retrieved 25 March 2025.
  • Windows Filtering Platform Architecture Overview

As part of the second edition of Windows Kernel Programming, I’m working on chapter 13 to describe the basics of the Windows Filtering Platform (WFP). The chapter will focus mostly on kernel-mode WFP Callout drivers (it is a kernel programming book after all), but I am also providing a brief introduction to WFP and its user-mode API.

This introduction (with some simplifications) is what this post is about. Enjoy!

The Windows Filtering Platform (WFP) provides flexible ways to control network filtering. It exposes user-mode and kernel-mode APIs, that interact with several layers of the networking stack. Some configuration and control is available directly from user-mode, without requiring any kernel-mode code (although it does require administrator-level access). WFP replaces older network filtering technologies, such as Transport Driver Interface (TDI) filters some types of NDIS filters.

If examining network packets (and even modification) is required, a kernel-mode Callout driver can be written, which is what we’ll be concerned with in this chapter. We’ll begin with an overview of the main pieces of WFP, look at some user-mode code examples for configuring filters before diving into building simple Callout drivers that allows fine-grained control over network packets.

WFP is comprised of user-mode and kernel-mode components. A very high-level architecture is shown here:

In user-mode, the WFP manager is the Base Filtering Engine (BFE), which is a service implemented by bfe.dll and hosted in a standard svchost.exe instance. It implements the WFP user-mode API, essentially managing the platform, talking to its kernel counterpart when needed. We’ll examine some of these APIs in the next section.

User-mode applications, services and other components can utilize this user-mode management API to examine WFP objects state, and make changes, such as adding or deleting filters. A classic example of such “user” is the Windows Firewall, which is normally controllable by leveraging the Microsoft Management Console (MMC) that is provided for this purpose, but using these APIs from other applications is just as effective.

The kernel-mode filter engine exposes various logical layers, where filters (and callouts) can be attached. Layers represent locations in the network processing of one or more packets. The TCP/IP driver makes calls to the WFP kernel engine so that it can decide which filters (if any) should be “invoked”.

For filters, this means checking the conditions set by the filter against the current request. If the conditions are satisfied, the filter’s action is applied. Common actions include blocking a request from being further processed, allowing the request to continue without further processing in this layer, continuing to the next filter in this layer (if any), and invoking a callout driver. Callouts can perform any kind of processing, such as examining and even modifying packet data.
The relationship between layers, filters, and callouts is shown here:

As you can see the diagram, each layer can have zero or more filters, and zero or more callouts. The number and meaning of the layers is fixed and provided out of the box by Windows. On most system, there are about 100 layers. Many of the layers are sets of pairs, where one is for IPv4 and the other (identical in purpose) is for IPv6.

The WFP Explorer tool I created provides some insight into what makes up WFP. Running the tool and selecting View/Layers from the menu (or clicking the Layers tool bar button) shows a view of all existing layers.

You can download the WFP Explorer tool from its Github repository
(https://github.com/zodiacon/WFPExplorer) or the AllTools repository
(https://github.com/zodiacon/AllTools).

Each layer is uniquely identified by a GUID. Its Layer ID is used internally by the kernel engine as an identifier rather than the GUID, as it’s smaller and so is faster (layer IDs are 16-bit only). Most layers have fields that can be used by filters to set conditions for invoking their actions. Double-clicking a layer shows its properties. The next figure shows the general properties of an example layer. Notice it has 382 filters and 2 callouts attached to it.

Clicking the Fields tab shows the fields available in this layer, that can be used by filters to set conditions.

The meaning of the various layers, and the meaning of the fields for the layers are all documented in the official WFP documentation.

The currently existing filters can be viewed in WFP Explorer by selecting Filters from the View menu. Layers cannot be added or removed, but filters can. Management code (user or kernel) can add and/or remove filters dynamically while the system is running. You can see that on the system the tool is running on there are currently 2978 filters.

Each filter is uniquely identified by a GUID, and just like layers has a “shorter” id (64-bit) that is used by the kernel engine to more quickly compare filter IDs when needed. Since multiple filters can be assigned to the same layer, some kind of ordering must be used when assessing filters. This is where the filter’s weight comes into play. A weight is a 64-bit value that is used to sort filters by priority. As you can see in figure 13-7, there are two weight properties – weight and effective weight. Weight is what is specified when adding the filter, but effective weight is the actual one used. There are three possible values to set for weight:

  • A value between 0 and 15 is interpreted by WFP as a weight index, which simply means that the effective weight is going to start with 4 bits having the specified weight value and generate the other 60 bit. For example, if the weight is set to 5, then the effective weight is going to be between 0x5000000000000000 and 0x5FFFFFFFFFFFFFFF.
  • An empty value tells WFP to generate an effective weight somewhere in the 64-bit range.
  • A value above 15 is taken as is to become the effective weight.

What is an “empty” value? The weight is not really a number, but a FWP_VALUE type can hold all sorts of values, including holding no value at all (empty).

Double-clicking a filter in WFP Explorer shows its general properties:

The Conditions tab shows the conditions this filter is configured with. When all the conditions are met, the action of the filter is going to fire.

The list of fields used by a filter must be a subset of the fields exposed by the layer this filter is attached to. There are six conditions shown in figure 13-9 out of the possible 39 fields supported by this layer (“ALE Receive/Accept v4 Layer”). As you can see, there is a lot of flexibility in specifying conditions for fields – this is evident in the matching enumeration, FWPM_MATCH_TYPE:

typedef enum FWP_MATCH_TYPE_ {
    FWP_MATCH_EQUAL    = 0,
    FWP_MATCH_GREATER,
    FWP_MATCH_LESS,
    FWP_MATCH_GREATER_OR_EQUAL,
    FWP_MATCH_LESS_OR_EQUAL,
    FWP_MATCH_RANGE,
    FWP_MATCH_FLAGS_ALL_SET,
    FWP_MATCH_FLAGS_ANY_SET,
    FWP_MATCH_FLAGS_NONE_SET,
    FWP_MATCH_EQUAL_CASE_INSENSITIVE,
    FWP_MATCH_NOT_EQUAL,
    FWP_MATCH_PREFIX,
    FWP_MATCH_NOT_PREFIX,
    FWP_MATCH_TYPE_MAX
} FWP_MATCH_TYPE;

The WFP API exposes its functionality for user-mode and kernel-mode callers. The header files used are different, to cater for differences in API expectations between user-mode and kernel-mode, but APIs in general are identical. For example, kernel APIs return NTSTATUS, whereas user-mode APIs return a simple LONG, that is the error value that is returned normally from GetLastError. Some APIs are provided for kernel-mode only, as they don’t make sense for user mode.

W> The user-mode WFP APIs never set the last error, and always return the error value directly. Zero (ERROR_SUCCESS) means success, while other (positive) values mean failure. Do not call GetLastError when using WFP – just look at the returned value.

WFP functions and structures use a versioning scheme, where function and structure names end with a digit, indicating version. For example, FWPM_LAYER0 is the first version of a structure describing a layer. At the time of writing, this was the only structure for describing a layer. As a counter example, there are several versions of the function beginning with FwpmNetEventEnum: FwpmNetEventEnum0 (for Vista+), FwpmNetEventEnum1 (Windows 7+), FwpmNetEventEnum2 (Windows 8+), FwpmNetEventEnum3 (Windows 10+), FwpmNetEventEnum4 (Windows 10 RS4+), and FwpmNetEventEnum5 (Windows 10 RS5+). This is an extreme example, but there are others with less “versions”. You can use any version that matches the target platform. To make it easier to work with these APIs and structures, a macro is defined with the base name that is expanded to the maximum supported version based on the target compilation platform. Here is part of the declarations for the macro FwpmNetEventEnum:

DWORD FwpmNetEventEnum0(
   _In_ HANDLE engineHandle,
   _In_ HANDLE enumHandle,
   _In_ UINT32 numEntriesRequested,
   _Outptr_result_buffer_(*numEntriesReturned) FWPM_NET_EVENT0*** entries,
   _Out_ UINT32* numEntriesReturned);
#if (NTDDI_VERSION >= NTDDI_WIN7)
DWORD FwpmNetEventEnum1(
   _In_ HANDLE engineHandle,
   _In_ HANDLE enumHandle,
   _In_ UINT32 numEntriesRequested,
   _Outptr_result_buffer_(*numEntriesReturned) FWPM_NET_EVENT1*** entries,
   _Out_ UINT32* numEntriesReturned);
#endif // (NTDDI_VERSION >= NTDDI_WIN7)
#if (NTDDI_VERSION >= NTDDI_WIN8)
DWORD FwpmNetEventEnum2(
   _In_ HANDLE engineHandle,
   _In_ HANDLE enumHandle,
   _In_ UINT32 numEntriesRequested,
   _Outptr_result_buffer_(*numEntriesReturned) FWPM_NET_EVENT2*** entries,
   _Out_ UINT32* numEntriesReturned);
#endif // (NTDDI_VERSION >= NTDDI_WIN8)

You can see that the differences in the functions relate to the structures returned as part of these APIs (FWPM_NET_EVENTx). It’s recommended you use the macros, and only turn to specific versions if there is a compelling reason to do so.

The WFP APIs adhere to strict naming conventions that make it easier to use. All management functions start with Fwpm (Filtering Windows Platform Management), and all management structures start with FWPM. The function names themselves use the pattern <prefix><object type><operation>, such as FwpmFilterAdd and FwpmLayerGetByKey.

It’s curious that the prefixes used for functions, structures, and enums start with FWP rather than the (perhaps) expected WFP. I couldn’t find a compelling reason for this.

WFP header files start with fwp and end with u for user-mode or k for kernel-mode. For example, fwpmu.h holds the management functions for user-mode callers, whereas fwpmk.h is the header for kernel callers. Two common files, fwptypes.h and fwpmtypes.h are used by both user-mode and kernel-mode headers. They are included by the “main” header files.

User-Mode Examples

Before making any calls to specific APIs, a handle to the WFP engine must be opened with FwpmEngineOpen:

DWORD FwpmEngineOpen0(
   _In_opt_ const wchar_t* serverName,  // must be NULL
   _In_ UINT32 authnService,            // RPC_C_AUTHN_DEFAULT
   _In_opt_ SEC_WINNT_AUTH_IDENTITY_W* authIdentity,
   _In_opt_ const FWPM_SESSION0* session,
   _Out_ HANDLE* engineHandle);

Most of the arguments have good defaults when NULL is specified. The returned handle must be used with subsequent APIs. Once it’s no longer needed, it must be closed:

DWORD FwpmEngineClose0(_Inout_ HANDLE engineHandle);

Enumerating Objects

What can we do with an engine handle? One thing provided with the management API is enumeration. These are the APIs used by WFP Explorer to enumerate layers, filters, sessions, and other object types in WFP. The following example displays some details for all the filters in the system (error handling omitted for brevity, the project wfpfilters has the full source code):

#include <Windows.h>
#include <fwpmu.h>
#include <stdio.h>
#include <string>

#pragma comment(lib, "Fwpuclnt")

std::wstring GuidToString(GUID const& guid) {
    WCHAR sguid[64];
    return ::StringFromGUID2(guid, sguid, _countof(sguid)) ? sguid : L"";
}

const char* ActionToString(FWPM_ACTION const& action) {
    switch (action.type) {
        case FWP_ACTION_BLOCK:               return "Block";
        case FWP_ACTION_PERMIT:              return "Permit";
        case FWP_ACTION_CALLOUT_TERMINATING: return "Callout Terminating";
        case FWP_ACTION_CALLOUT_INSPECTION:  return "Callout Inspection";
        case FWP_ACTION_CALLOUT_UNKNOWN:     return "Callout Unknown";
        case FWP_ACTION_CONTINUE:            return "Continue";
        case FWP_ACTION_NONE:                return "None";
        case FWP_ACTION_NONE_NO_MATCH:       return "None (No Match)";
    }
    return "";
}

int main() {
    //
    // open a handle to the WFP engine
    //
    HANDLE hEngine;
    FwpmEngineOpen(nullptr, RPC_C_AUTHN_DEFAULT, nullptr, nullptr, &hEngine);

    //
    // create an enumeration handle
    //
    HANDLE hEnum;
    FwpmFilterCreateEnumHandle(hEngine, nullptr, &hEnum);

    UINT32 count;
    FWPM_FILTER** filters;
    //
    // enumerate filters
    //
    FwpmFilterEnum(hEngine, hEnum, 
        8192,       // maximum entries, 
        &filters,   // returned result
        &count);    // how many actually returned

    for (UINT32 i = 0; i < count; i++) {
        auto f = filters[i];
        printf("%ws Name: %-40ws Id: 0x%016llX Conditions: %2u Action: %s\n",
            GuidToString(f->filterKey).c_str(),
            f->displayData.name,
            f->filterId,
            f->numFilterConditions,
            ActionToString(f->action));
    }
    //
    // free memory allocated by FwpmFilterEnum
    //
    FwpmFreeMemory((void**)&filters);

    //
    // close enumeration handle
    //
    FwpmFilterDestroyEnumHandle(hEngine, hEnum);

    //
    // close engine handle
    //
    FwpmEngineClose(hEngine);

    return 0;
}

The enumeration pattern repeat itself with all other WFP object types (layers, callouts, sessions, etc.).

Adding Filters

Let’s see if we can add a filter to perform some useful function. Suppose we want to prevent network access from some process. We can add a filter at an appropriate layer to make it happen. Adding a filter is a matter of calling FwpmFilterAdd:

DWORD FwpmFilterAdd0(
   _In_ HANDLE engineHandle,
   _In_ const FWPM_FILTER0* filter,
   _In_opt_ PSECURITY_DESCRIPTOR sd,
   _Out_opt_ UINT64* id);

The main work is to fill a FWPM_FILTER structure defined like so:

typedef struct FWPM_FILTER0_ {
    GUID filterKey;
    FWPM_DISPLAY_DATA0 displayData;
    UINT32 flags;
    /* [unique] */ GUID *providerKey;
    FWP_BYTE_BLOB providerData;
    GUID layerKey;
    GUID subLayerKey;
    FWP_VALUE0 weight;
    UINT32 numFilterConditions;
    /* [unique][size_is] */ FWPM_FILTER_CONDITION0 *filterCondition;
    FWPM_ACTION0 action;
    /* [switch_is] */ /* [switch_type] */ union 
        {
        /* [case()] */ UINT64 rawContext;
        /* [case()] */ GUID providerContextKey;
        }     ;
    /* [unique] */ GUID *reserved;
    UINT64 filterId;
    FWP_VALUE0 effectiveWeight;
} FWPM_FILTER0;

The weird-looking comments are generated by the Microsoft Interface Definition Language (MIDL) compiler when generating the header file from an IDL file. Although IDL is most commonly used by Component Object Model (COM) to define interfaces and types, WFP uses IDL to define its APIs, even though no COM interfaces are used; just plain C functions. The original IDL files are provided with the SDK, and they are worth checking out, since they may contain developer comments that are not “transferred” to the resulting header files.

Some members in FWPM_FILTER are necessary – layerKey to indicate the layer to attach this filter, any conditions needed to trigger the filter (numFilterConditions and the filterCondition array), and the action to take if the filter is triggered (action field).

Let’s create some code that prevents the Windows Calculator from accessing the network. You may be wondering why would calculator require network access? No, it’s not contacting Google to ask for the result of 2+2. It’s using the Internet for accessing current exchange rates.

Clicking the Update Rates button causes Calculator to consult the Internet for the updated exchange rate. We’ll add a filter that prevents this.

We’ll start as usual by opening handle to the WFP engine as was done in the previous example. Next, we need to fill the FWPM_FILTER structure. First, a nice display name:

FWPM_FILTER filter{};   // zero out the structure
WCHAR filterName[] = L"Prevent Calculator from accessing the web";
filter.displayData.name = filterName;

The name has no functional part – it just allows easy identification when enumerating filters. Now we need to select the layer. We’ll also specify the action:

filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
filter.action.type = FWP_ACTION_BLOCK;

There are several layers that could be used for blocking access, with the above layer being good enough to get the job done. Full description of the provided layers, their purpose and when they are used is provided as part of the WFP documentation.

The last part to initialize is the conditions to use. Without conditions, the filter is always going to be invoked, which will block all network access (or just for some processes, based on its effective weight). In our case, we only care about the application – we don’t care about ports or protocols. The layer we selected has several fields, one of with is called ALE App ID (ALE stands for Application Layer Enforcement).

This field can be used to identify an executable. To get that ID, we can use FwpmGetAppIdFromFileName. Here is the code for Calculator’s executable:

WCHAR filename[] = LR"(C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_11.2210.0.0_x64__8wekyb3d8bbwe\CalculatorApp.exe)";
FWP_BYTE_BLOB* appId;
FwpmGetAppIdFromFileName(filename, &appId);

The code uses the path to the Calculator executable on my system – you should change that as needed because Calculator’s version might be different. A quick way to get the executable path is to run Calculator, open Process Explorer, open the resulting process properties, and copy the path from the Image tab.

The R"( and closing parenthesis in the above snippet disable the “escaping” property of backslashes, making it easier to write file paths (C++ 14 feature).

The return value from FwpmGetAppIdFromFileName is a BLOB that needs to be freed eventually with FwpmFreeMemory.

Now we’re ready to specify the one and only condition:

FWPM_FILTER_CONDITION cond;
cond.fieldKey = FWPM_CONDITION_ALE_APP_ID;      // field
cond.matchType = FWP_MATCH_EQUAL;
cond.conditionValue.type = FWP_BYTE_BLOB_TYPE;
cond.conditionValue.byteBlob = appId;

filter.filterCondition = &cond;
filter.numFilterConditions = 1;

The conditionValue member of FWPM_FILTER_CONDITION is a FWP_VALUE, which is a generic way to specify many types of values. It has a type member that indicates the member in a big union that should be used. In our case, the type is a BLOB (FWP_BYTE_BLOB_TYPE) and the actual value should be passed in the byteBlob union member.

The last step is to add the filter, and repeat the exercise for IPv6, as we don’t know how Calculator connects to the currency exchange server (we can find out, but it would be simpler and more robust to just block IPv6 as well):

FwpmFilterAdd(hEngine, &filter, nullptr, nullptr);

filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;   // IPv6
FwpmFilterAdd(hEngine, &filter, nullptr, nullptr);

We didn’t specify any GUID for the filter. This causes WFP to generate a GUID. We didn’t specify weight, either. WFP will generate them.

All that’s left now is some cleanup:

FwpmFreeMemory((void**)&appId);
FwpmEngineClose(hEngine);

Running this code (elevated) should and trying to refresh the currency exchange rate with Calculator should fail. Note that there is no need to restart Calculator – the effect is immediate.

We can locate the filters added with WFP Explorer:

Double-clicking one of the filters and selecting the Conditions tab shows the only condition where the App ID is revealed to be the full path of the executable in device form. Of course, you should not take any dependency on this format, as it may change in the future.

You can right-click the filters and delete them using WFP Explorer. The FwpmFilterDeleteByKey API is used behind the scenes. This will restore Calculator’s exchange rate update functionality.

Часть 2, Архитектура.

Введение.

Рассмотрим типичный жизненный цикл обмена информацией по протоколу TCP.

1. Приложение (клиент) вызывает функцию connect, указывая адрес и порт пункта
назначения, а также семейство и тип протокола. Или же (сервер) оно вызывает accept в
ожидании подключений.

2. Через некоторое время соединение установлено. Клиенту возвращается управление из
функции connect (ну или приходит соответствующий сигнал, как в случае с моделью
select или асинхронным I/O — это уже за рамками темы), серверу возвращается
новый сокет, связанный с клиентом.

3. Приложения обмениваются данными посредством функций send и recv.

4. Приложения закрывают свои концы соединений функцией shutdown.
Когда оба конца закрыты, связь завершается, соединение считается разомкнутым.

Представим, что мы пишем фильтр TCP-трафика, и нас на каждом шаге этой
последовательности интересуют определенные данные:

1. Адрес и порт пункта назначения, ID процесса, который выполнил connect/accept.
Имя и вообще контекст безопасности пользователя, которому принадлежит процесс.

2. Статус операции connect (успех или ошибка).

3. Буферы, передаваемые через send и recv.

Предположим, мы хотим блокировать нежелательные соединения (по набору запрещенных
IP-адресов и портов, а также по процессам или пользователям). Мы также хотим получать
уведомление о закрытии соединения (п.4), чтобы освободить все связанные с ним данные.

WFP для решения этой задачи предлагает несколько абстракций:

Layers — уровни фильтрации. Каждым уровнем обслуживается определенный этап обработки
соединения или передачи данных. Например, установка соединения обрабатывается на
уровне ALE (будет рассмотрен ниже), а работа с буферами send/recv — на уровне Stream.
У каждого уровня своя специфика и свой набор свойств, с которыми можно работать.
Часто ту информацию, что можно достать на одном уровне, уже нельзя увидеть на других.
Аналогично, не все функции, которые работают на одних уровнях, работают на других.

Sublayers — подуровни. Нужны для организации более гибкой стратегии фильтрации.
Например, можно зарегистрировать проверки на разных Sublayers и они будут выполняться
по очереди.

Filters — фильтры. Задают разные условия, при которых должна (или не должна) выполняться
фильтрация. Например, можно фильтровать только трафик, идущий на порт 80, а остальной
игнорировать. Фильтры определяют не только условия, но и действия, т.е. что делать с трафиком,
когда условие срабатывает. Основные операции: разрешить, заблокировать, задержать, либо
вызвать соответствующий Callout. Про Callout-ы будет написано ниже.

Conditions — условия, заданные в фильтре. В фильтре может быть несколько условий.

Shims — исполнительные объекты, расположенные во всех ключевых точках сетевого стека.
Если Filters и Conditions только задают правила, что делать с трафиком, то Shims отвечают за
само выполнение этих правил, т.е. блокировка, задержка трафика, вызов Callout-а и так далее.

Callout — блок функций, которые обрабатывают трафик и решают, что конкретно с ним делать.

Callout Driver — драйвер, реализующий один или несколько Callout-ов.

Provider — поставщик. Объединяет логически связанные Filters, Sublayers и Callouts,
принадлежащие одной программе, одной задаче/политике, или объединенные какими-то
другими общими признаками.

Filter Engine (Engine) — функциональное ядро WFP, которое управляет фильтрами, Shim-ами,
Callout-ами и остальными объектами технологии.

Base Filter Engine (BFE) — служба, отвечающая за координацию компонентов WFP, за
регистрацию новых фильтров, за хранение конфигурации, настройки безопасности и т.д.

Ссылки по теме:

WFP Architecture
http://msdn.microsoft.com/en-u… 85%29.aspx

Object Model
http://msdn.microsoft.com/en-u… 85%29.aspx

Как это работает.

Когда в сетевом стеке происходит какое-то событие, например установка или закрытие
соединения, приход дейтаграммы, получение буфера с данными и т.п., Filter Engine с
помощью Filters определяет, нужно ли как-то обрабатывать это событие. В терминах WFP
данный процесс называется классификацией (classify). Если хотя бы один Condition
срабатывает, вызывается Shim, который, в зависимости от того, что задано в фильтре,
выполняет нужное действие. В частности, Shim может вызвать ваш зарегистрированный
Callout, точнее, одну из его функций (их всего три).

Все остальное выполняет Callout, в соответствии с логикой фильтрации трафика.
Если, к примеру, Callout работает на уровне ALE, то в функцию ему будут переданы
адрес и порт пункта назначения и источника, тип протокола, ID приложения, полный
путь к exe и другая полезная информация. Если на уровне Stream, там будут другие
данные — направление трафика (входящий/исходящий), флаги и т.д. Далее Callout
может поступать с данными на свое усмотрение: блокировка, игнор, редирект, анализ,
задержка трафика и многое другое. Все ограничено лишь фантазией разработчика.

Выше была описана типичная последовательность операций при работе с TCP.
Давайте посмотрим, как она будет фильтроваться WFP.

1. Установка соединения (connect).
Здесь будут срабатывать фильтры на уровне FWPS_LAYER_ALE_AUTH_CONNECT_V4.
Здесь же можно заблокировать соединение.

2. Соединение установлено.
Сработают фильтры на уровне FWPS_LAYER_ALE_FLOW_ESTABLISHED_V4.
Добавлю, что на этом уровне блокировать коннект не рекомендуется, он создан
только для того, чтобы Callout получил уведомление о том, успешно или нет
создано соединение. Здесь же можно установить связь между коннектом и
потоком данных.

3. Передача данных.
Будут срабатывать фильтры на уровне FWPS_LAYER_STREAM_V4, причем как
для send (outgoing data), так и для recv (incoming data).

4. Закрытие соединения.
Если с потоком данных был ассоциирован контекст, будет вызвана одна из
функций Callout-а. Также на Windows 7 есть специальные уровни для
очистки ресурсов — FWPS_LAYER_ALE_RESOURCE_RELEASE_V4.

Еще определения.

Flow (поток данных). Каждое соединение — это отдельный двунаправленный поток данных.
Если, к примеру, программа создает два коннекта на один и тот же адрес, вы будете в
драйвере видеть два разных потока данных, каждый со своим состоянием.

Flow Context (контекст потока). 64-битное число, ассоциированное с определенным
потоком данных. Нужно, чтобы отличать потоки друг от друга.

Fixed Values. Фиксированный набор аргументов, приходящий в Callout.
Описан здесь:

Data Field Identifiers
http://msdn.microsoft.com/en-u… 85%29.aspx

Для каждого Layer-а свой набор аргументов.
Например, на уровне FWPS_LAYER_ALE_CONNECT_REDIRECT_V4 (установка соединения) через
Fixed Values можно получить имя пользователя, а на уровне FWPS_LAYER_STREAM_V4
его уже нет.

Meta Values. Аналогично Fixed Values.
Информация здесь:

Metadata Fields at Each Filtering Layer
http://msdn.microsoft.com/en-u… 85%29.aspx

Итак, Callout, заглядывая в Fixed Values и Meta Values, решает, что делать с
данным событием/трафиком и предпринимает определенные действия, после чего весь
цикл повторяется заново. Доступ к буферам с данными осуществляется через
структуру NET_BUFFER (будет описана в следующих частях).



13



Introduction

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.

From WFP Architecture Overview

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.

Various Layers involved in a TCP connection

Various Layers involved in a UDP connection

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:

Install-Module NtObjectManager
Install-Module wtf-wfp

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:
Get-Help Get-WFPInfo

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.

Tracing Traffic

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.

Summary

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 Zero Labs Slack for any suggestions or issues.

What is it?

The Tripwire Enterprise Critical Change Audit rules provide customers with the ability to monitor for critical events that could have a significant impact on a system. Monitoring for critical events can help administrators identify malicious and/or unexpected changes within their environment.

Changes To CCA

Additional rules were added to the Critical Change Audit rule set. These rules provide customers the ability to monitor for changes to the firewall, installed certificates, expiring/expired certificates, USB devices (i.e. keyboards), WFP Filtering, and HTTP Proxy.

Firewall Status

Firewalls monitor network traffic and use rules to block or allow traffic. Allowing services that are not normally accessible to the network could cause unnecessary risk. Monitoring the Firewall state ensures that the firewall is currently active and not tampered with. These new rules monitor for changes made locally or via group policy on Windows as well as UFW and nftables on Linux.

Local Firewall (Windows)

Group Policy (Windows)

Linux (Ubuntu)

Certificates

TLS certificates allow for secure communication. An expired certificate can interrupt TLS communication and potentially expose sensitive data. Monitoring for changes in the state of certificates allows administrators to ensure that TLS-based communication continues to be secure. These new rules monitor for newly added, expiring, or expired certificates on both Windows and Linux. 

Installed Certificates (Windows)

Expired Certificates (Windows/Linux)

Expiring Certificates (Windows)

USB Devices/Keyboards

Rubber Duckies are devices that look like an innocent USB drive but deliver a malicious payload. Rubber Duckies that interact with the system by typing characters try to install a new keyboard. The Tripwire Enterprise CCA rule set now monitors for newly added keyboards.

WFP EDR Silencer Rule

Endpoint Detection and Response (EDR) Silencers are tools that utilize the Windows Filtering Platform (WFP) to block EDR agents from communicating with servers. WFP has an API that provides a way to filter network traffic. Tripwire Enterprise now monitors for additional entries that block traffic for applications.

Netsh Interface Portproxy [coming soon]

Netsh interface portproxy functions as proxies between IPv4 and IPv6 networks and applications. This allows malicious users/applications to pivot and access networks/services that are not usually accessible. These new rules report when a new port proxy is added to the system.

Summary

In order to have access to this new content, Tripwire Enterprise users must install the latest version of the Critical Change Audit rule set. Once installed, these changes will allow a Tripwire Enterprise admin to determine if a critical event has occurred.

Interested in the difference between a critical change audit and a change audit? Click here! 

If you’d like to learn more about our services, you can contact us by following this link.


Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Как скинуть все фото с айфона на компьютер через шнур windows 10
  • Почему windows не видит сканер
  • Журнал восстановления системы windows 10
  • Broadcom bcm94313hmgb driver windows 10
  • Программа запущена в режиме совместимости windows 10 как отключить