Windows Filtering Platform: Persistent state under the hood

Since Windows XP SP2, the Windows firewall is deployed and enabled by default in every Microsoft Windows operating system. Starting with Windows Vista the firewall relies on a set of API and services called the Windows Filtering Platform (WFP). Although used by almost every Windows OS, WFP is still one of the relatively unknown beast that lies in the kernel. In this post we will see how the firewall manages its persistent state.

Disclaimer: this post was written a year ago with Alexandre Gazet, a former colleague. After gathering dust for too long we decided to publish it anyway. All experiments were conducted on a Microsoft Windows 8.1 operating system.

Introduction

The registry is full of unknown binary blobs. Not so long ago, we stumbled upon the registry sub-key of the BFE service.

On this picture we see a bunch of entries with a name that looks like a GUID and some binary data.

So what is this BFE thingy? A quick search on Google points us on the right direction: BFE stands for Base Filtering Engine which is a core part of the Windows Filtering Platform (WFP).

Since Windows XP SP2, the Windows firewall is deployed and enabled by default in every Microsoft Windows operating system. Starting with Windows Vista the firewall relies on a set of API and services called the Windows Filtering Platform (WFP). Although used by almost every Windows OS, WFP is still one of the relatively unknown beast that lies in the kernel.

The WFP architecture is well explained on the MSDN (http://msdn.microsoft.com/en-us/library/windows/desktop/aa366509(v=vs.85).aspx)

Amongst the points of high interest we can mention two components: the user-mode Base Filtering Engine (BFE locating in bfe.dll) and its kernel-mode counterpart KM Filter Engine (KMFE located in netio.sys).

WFP can be used by third parties to develop advanced filtering or routing solution (implementing a VPN solution comes to mind). However, this is also the core of the well known Windows firewall which comes by default with a set of pre-configured rules:

For now let's just say that a filter is a rule that governs classification. It defines a set of conditions, when met, triggers an action (ie: a callout). A filter operates at a certain level: e.g.: FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD. Rules are grouped into providers that define "logical features" (for example: Microsoft Windows WFP Built-in TCP Templates provider or Windows Firewall IPsec Provider).

Our objective is to discover how the OS interacts with the WFP and how the configuration is persistently stored in the binary format. With this premise in mind, we'll start to examine the WFP objects' lifetime.

A quick look on the documentation tells us that WFP objects can have one of four possible lifetimes:

  • Dynamic: An object is dynamic only if it is added using a dynamic session handle. Dynamic objects live until they are deleted or the owning session terminates.
  • Static: Objects are static by default. Static objects live until they are deleted, BFE stops, or the system is shutdown.
  • Persistent: Persistent objects are created by passing the appropriate FWPM_*_FLAG_PERSISTENT flag to an Fwpm*Add0 function. Persistent objects live until they are deleted.
  • Built-in: Built-in objects are predefined by BFE and cannot be added or deleted. They live forever.

Kernel-mode Filters can be marked as boot-time filters by passing the appropriate flag to FwpmFilterAdd0 function. Boot-time filters are added to the system when the TCP/IP driver starts, and removed when BFE finishes initialization.

So how are these persistent objects managed?

It's time to do a bit of reversing.

Deep inside netio.sys and bfe.dll persistence

This little experiment was done on the following patients:

  • BFE.DLL: 6.3.9600.16427 (winblue_gdr.131012-0944)
  • NETIO.SYS: 6.3.9600.16384 (winblue_rtm.130821-1623)

We have seen before that the persistent state of the BFE is stored in 2 different places in the registry. The boot-time filters are located in the HKLM\SYSTEM\ControlSet001\Services\BFE\Parameters\Policy\BootTime registry key. The persistent objects are stored in the HKLM\SYSTEM\ControlSet001\Services\BFE\Parameters\Policy\Persistent registry key.

Boot-time filters

We'll start with the boot-time filters. By searching for the registry key inside the netio.sys binary we quickly find how this data is processed. The function KfdApplyBoottimePolicyCallback is called for each registry key stored in the Filter subkey.

PAGE:00056B2B                 mov     [ebp+var_38], offset KfdApplyBoottimePolicyCallback(x,x,x,x,x,x)
PAGE:00056B32                 lea     eax, [ebp+var_38]
PAGE:00056B35                 mov     [ebp+var_34], 10h
PAGE:00056B3C                 push    esi
PAGE:00056B3D                 push    esi
PAGE:00056B3E                 push    eax
PAGE:00056B3F                 push    offset aBfeParametersP ; "BFE\\Parameters\\Policy\\BootTime\\Filt"...
PAGE:00056B44                 push    80000001h
PAGE:00056B49                 call    ds:RtlQueryRegistryValuesEx(x,x,x,x,x)

When the system boots (netio.sys is a boot driver), several filters are loaded and are in effect until the BFE service is ready.

By using a kernel debugger we can see that these filters are enforced quite early in the boot process.

Breakpoint 1 hit
NETIO!KfdApplyBoottimePolicyCallback:
82731b6a 8bff            mov     edi,edi
kd> k
ChildEBP RetAddr
829fc330 81d2d6fb NETIO!KfdApplyBoottimePolicyCallback
829fc374 81d2d516 nt!RtlpCallQueryRegistryRoutine+0x101
829fc3ec 81d77b11 nt!RtlpQueryRegistryValues+0x32c
829fc404 82731b4f nt!RtlQueryRegistryValuesEx+0x19
829fc45c 82731ad2 NETIO!KfdReadAndApplyBoottimePolicy+0x3e
829fc474 82731a74 NETIO!KfdProcessBoottimePolicy+0x4a
829fc478 82731a22 NETIO!KfdStartModuleEx+0x2e
829fc480 82731878 NETIO!KfdStartModule+0x16
829fc494 827390f4 NETIO!RtlInvokeStartRoutines+0x1e
829fc4ac 81de1ba2 NETIO!DllInitialize+0x6b
829fc4e0 81f916a2 nt!MmCallDllInitialize+0x134
829fc6c0 81f82882 nt!IopInitializeBootDrivers+0x628
829fc740 81f7b450 nt!IoInitSystem+0x61e
829fcc28 81dd1c5b nt!Phase1InitializationDiscard+0x9f4
829fcc30 81b221d4 nt!Phase1Initialization+0xd
829fcc70 81b87fb1 nt!PspSystemThreadStartup+0x58
829fcc7c 00000000 nt!KiThreadStartup+0x15

So now one question remains: how is the data loaded from the registry structured?

If we take a filter from the registry the data takes this form:

"{dc95b53e-01cf-4058-821d-350b3d0d4676}"=hex:01,10,08,00,cc,cc,cc,cc,98,00,00,\
  00,00,00,00,00,00,00,02,00,00,00,00,00,2e,00,00,00,00,00,00,00,00,00,00,00,\
  00,00,00,00,00,00,00,00,00,00,00,00,04,00,02,00,00,00,00,00,01,00,00,00,00,\
  00,00,00,04,00,00,00,04,00,00,00,08,00,02,00,02,00,00,00,02,00,00,00,0c,00,\
  02,00,02,10,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
  00,00,00,00,00,00,e0,00,10,02,00,00,00,05,00,00,00,00,00,00,00,01,00,00,00,\
  01,00,00,00,3a,00,00,00,04,00,00,00,00,00,00,00,02,00,00,00,02,00,00,00,87,\
  00,00,00,00,00,00,00

How can we decode these bytes?

The heavy lifting is done by the WfpMidlObjectDecode function.

PAGE:00056B81                 mov     ecx, offset PWFP_BOOTTIME_FILTER_Decode(x,x)
PAGE:00056B86                 push    eax             ; int
PAGE:00056B87                 push    [ebp+BufferSize] ; BufferSize
PAGE:00056B8A                 call    WfpMidlObjectDecode(x,x,x,x)

This function calls the PWFP_BOOTTIME_FILTER_Decode function. From the name of the function we infer that the registry data is a serialized PWFP_BOOTIME_FILTER structure.

.text:000300C4 __stdcall PWFP_BOOTTIME_FILTER_Decode(x, x) proc near
.text:000300C4                                         ; DATA XREF: KfdApplyBoottimePolicyCallback(x,x,x,x,x,x)+17
.text:000300C4
.text:000300C4 arg_0           = dword ptr  8
.text:000300C4 arg_4           = dword ptr  0Ch
.text:000300C4
.text:000300C4                 mov     edi, edi
.text:000300C6                 push    ebp
.text:000300C7                 mov     ebp, esp
.text:000300C9                 push    [ebp+arg_4]
.text:000300CC                 xor     eax, eax
.text:000300CE                 inc     eax
.text:000300CF                 imul    eax, 92Ch
.text:000300D5                 add     eax, offset unk_535AA
.text:000300DA                 push    eax
.text:000300DB                 push    offset off_53558
.text:000300E0                 push    offset unk_540AC
.text:000300E5                 push    [ebp+arg_0]
.text:000300E8                 call    ds:NdrMesTypeDecode2(x,x,x,x,x) ; format string @ 0x53ed6
.text:000300EE                 pop     ebp
.text:000300EF                 retn    8
.text:000300EF __stdcall PWFP_BOOTTIME_FILTER_Decode(x, x) endp

Finally NdrMesTypeDecode2 function is called, it has the following prototype:

void NdrMesTypeDecode2(handle_t     Handle,
    const MIDL_TYPE_PICKLING_INFO * pPicklingInfo,
    const MIDL_STUB_DESC * pStubDesc,
    PFORMAT_STRING pFormatString,
    void * pObject
    )

NDR is well-known to anyone who have been working with Windows RPC. NDR is used to serialize and deserialize complex structures over the wire. It's the core of the RPC mechanisms (e.g. DCOM relies upon it).

If you are new to the subject you may find useful information in the following resources:

This quote from the documentation explains the purpose of the NDR:

"The role of NDR is to provide a mapping of IDL data types onto octet streams. NDR defines primitive data types, constructed data types and representations for these types in an octet stream"

As a quick refresher and to warm things up, we will explain how to to decode the previous filter.

For the readers wanting to decode everything, the following link is a must-read and contains all the necessary information to do it.

http://msdn.microsoft.com/en-us/library/windows/desktop/aa378635%28v=vs.85%29.aspx

To start things up, we check out the 0x53ed6 offset (fourth parameter of the NdrMesTypeDecode2 function) and begin to decode the stream of bytes using the previous documentation.

.rdata:00053ED6                 db 12h                  ; FC_UP
.rdata:00053ED7                 db 0                    ; attributes
.rdata:00053ED8                 dw 1Ch                  ; offset 0x53ef4

The FC_UP tag is the declaration of an Unique Pointer. The target of the pointer is contained at the 0x53ef4 offset.

It's time to head to the next interesting offset: 0x53ef4.

.rdata:00053EF4                 db 1Ah                  ; FC_BOGUS_STRUCT
.rdata:00053EF5                 db 3                    ; alignment
.rdata:00053EF6                 dw 1Ch                  ; memory_size
.rdata:00053EF8                 dw 0                    ; array: offset 0x53ef8
.rdata:00053EFA                 dw 0                    ; pointer layout: offset 0x53efa
.rdata:00053EFC                 db 8                    ; FC_LONG
.rdata:00053EFD                 db 8                    ; FC_LONG
.rdata:00053EFE                 db 4Ch                  ; FC_EMBEDDED_COMPLEX
.rdata:00053EFF                 db 0                    ; padding
.rdata:00053F00                 dw 0F930h               ; offset 0x53830
.rdata:00053F02                 db 4Ch                  ; FC_EMBEDDED_COMPLEX
.rdata:00053F03                 db 0                    ; padding
.rdata:00053F04                 dw 0FFD6h               ; offset 0x53eda
.rdata:00053F06                 db 5Ch                  ; FC_PAD
.rdata:00053F07                 db 5Bh                  ; FC_END

The target of the previous pointer is a structure whose members can be tricky to serialize (hence the bogus part).

Quoting again the documentation:

"A complex structure is any structure containing one or more fields that either prevent the structure from being block-copied, or for which additional checking must be performed during marshaling or unmarshaling (for example, bound checks on an enumeration)."

The remainder of the PWFP_BOOTTIME_FILTER structure is a straightforward use of these concepts.

To sum up our progression: we have the definition of the structure. However, we need to figure out how to decode the data stored in the registry.

All marshalled objects have the same layout:

  • 0x10 bytes header containing metadata (marker) and size of data
  • The remaining data are NDR-encoded

With that knowledge we can extract the data of our encoded boot-time filter. This gives us the following result:

"{dc95b53e-01cf-4058-821d-350b3d0d4676}"=hex:01,10,08,00,cc,cc,cc,cc,98,00,00,\
    00,00,00,00,00,00,00,02,00,00,00,00,00,2e,00,00,00,00,00,00,00,00,00,00,00,\
    00,00,00,00,00,00,00,00,00,00,00,00,04,00,02,00,00,00,00,00,01,00,00,00,00,\
    00,00,00,04,00,00,00,04,00,00,00,08,00,02,00,02,00,00,00,02,00,00,00,0c,00,\
    02,00,02,10,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
    00,00,00,00,00,00,e0,00,10,02,00,00,00,05,00,00,00,00,00,00,00,01,00,00,00,\
    01,00,00,00,3a,00,00,00,04,00,00,00,00,00,00,00,02,00,00,00,02,00,00,00,87,\
    00,00,00,00,00,00,00
  • offset 0000: 01 10 08 00 cc cc cc cc 98 00 00 00 00 00 00 00 (header)
  • offset 0010: 00 00 02 00 (pointer id=0x20000)
  • offset 0014: 00 00 00 00 (NdrLong=0x0)
  • offset 0018: 1c 00 00 00 (NdrLong=0x1c)
  • offset 001c: 00 00 00 00 (NdrLong=0x0)
  • offset 0020: 00 00 (NdrUshort=0x0)
  • offset 0022: 00 00 (NdrUshort=0x0)
  • offset 0024: 00 (NdrByte=0x0)
  • offset 0025: 00 (NdrByte=0x0)
  • offset 0026: 00 (NdrByte=0x0)
  • offset 0027: 00 (NdrByte=0x0)
  • offset 0028: 00 (NdrByte=0x0)
  • offset 0029: 00 (NdrByte=0x0)
  • offset 002a: 00 (NdrByte=0x0)
  • offset 002b: 00 (NdrByte=0x0)
  • offset 002c: 00 00 00 00 (union discriminant=0x0)
  • offset 0030: 04 00 02 00 (pointer id=0x20004)
  • offset 0034: 00 00 00 00 (padding for alignment)
  • offset 0038: 0f 00 00 00 00 00 00 00 (NdrHyper=0xf)
  • offset 0040: 04 00 00 00 (NdrEnum32=0x4)
  • offset 0044: 04 00 00 00 (union discriminant=0x4)
  • offset 0048: 08 00 02 00 (pointer id=0x20008)
  • offset 004c: 02 00 (NdrUshort=0x2)
  • offset 004e: 00 00 (NdrUshort=0x0)
  • offset 0050: 00 00 00 00 (NdrLong=0x0)
  • offset 0054: 00 00 00 00 (pointer id=0x0)
  • offset 0058: 01 10 00 00 (NdrLong=0x1001)
  • offset 005c: 00 00 00 00 (NdrLong=0x0)
  • offset 0060: 00 00 00 00 00 00 00 00 (NdrHyper=0x0)
  • offset 0068: 00 00 00 00 (pointer id=0x0)
  • offset 006c: 00 00 00 00 (padding for alignment)
  • offset 0070: 00 00 00 00 00 00 00 00 (NdrHyper=0x0)

Once decoded and formatted we obtain the following structure:

[15] GUID: {dc95b53e-01cf-4058-821d-350b3d0d4676}
Struct FC_BOGUS_STRUCT_10005bac @ 0x4:
    +000: 0x000000 (NdrLong)
    +004: 0x00002e (FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6)
    +008: Struct FC_STRUCT_10004bdc @ 0xc:
        +000: 0x000000 (NdrLong)
        +004: 0x00 (NdrUshort)
        +006: 0x00 (NdrUshort)
        +008: Array FC_SMFARRAY_10004bd6, 8 element(s) @ 0x14:
            [0]: 0x0 (NdrByte)
            [1]: 0x0 (NdrByte)
            [2]: 0x0 (NdrByte)
            [3]: 0x0 (NdrByte)
            [4]: 0x0 (NdrByte)
            [5]: 0x0 (NdrByte)
            [6]: 0x0 (NdrByte)
            [7]: 0x0 (NdrByte)
        => GUID: 00000000-00-00-0000-000000000000
+018: Union FC_NON_ENCAPSULATED_UNION_10005b96 @ 0x1c:
    union type 0x0: Struct FC_BOGUS_STRUCT_100058d2 @ 0x28:
        +000: 0x00000000000001 (NdrHyper)
        +008: Struct FC_BOGUS_STRUCT_10004d86 @ 0x30:
            +000: 0x04 (NdrEnum32)
            +004: Union FC_NON_ENCAPSULATED_UNION_10004cd8 @ 0x34:
                union type 0x4: 0x1000e00000000000 (NdrHyper)
        +010: 0x02 (NdrUshort)
        +012: 0x00 (NdrUshort)
        +014: 0x000002 (NdrLong)
        +018: Array FC_BOGUS_ARRAY_100058bc, 2 element(s) @ 0x68:
            [0]: Struct FC_BOGUS_STRUCT_1000587e @ 0x6c:
                +000: 0x05 (FWPM_CONDITION_IP_PROTOCOL)
                +002: 0x00 (NdrUshort)
                +004: 0x00 (FWP_MATCH_EQUAL)
                +008: Struct FC_BOGUS_STRUCT_10004e9e @ 0x74:
                    +000: 0x01 (FWP_UINT8)
                    +004: Union FC_NON_ENCAPSULATED_UNION_10004dda @ 0x78:
                        union type 0x1: 0x3a ':' (NdrChar)
            [1]: Struct FC_BOGUS_STRUCT_1000587e @ 0x80:
                +000: 0x04 (FWPM_CONDITION_IP_LOCAL_PORT)
                +002: 0x00 (NdrUshort)
                +004: 0x00 (FWP_MATCH_EQUAL)
                +008: Struct FC_BOGUS_STRUCT_10004e9e @ 0x88:
                    +000: 0x02 (FWP_UINT16)
                    +004: Union FC_NON_ENCAPSULATED_UNION_10004dda @ 0x8c:
                        union type 0x2: 0x87 (NdrUshort)
        +01c: Struct FC_STRUCT_10004e72 @ 0x48:
            +000: 0x001002 (FWP_ACTION_PERMIT)
            +004: 0x000000 (NdrLong)
        +028: 0x00000000000000 (NdrHyper)
        +030: 0x000000 (NdrLong)

The decoded structure was obtained by using a special script developed for this occasion. Its main objective is to automate this decoding process.

An astute reader will notice that the offsets used for naming NDR types are different (FC_BOGUS_STRUCT_10005bac versus FC_BOGUS_STRUCT_53ef4). In our script we use the types contained inside bfe.dll. The format string is the same, only the offset is different.

Now that we know how the boot-time filters are processed, it's time to look how BFE manages the registry persistent objects.

BFE persistent objects

BFE service (bfe.dll) acts as netio.sys counterpart. When it starts persistents objects are loaded from the registry (by calling the BfePersistentPolicyHandler function).

Persistent objects like the ones we have found in the registry are dynamically loaded into the filtering engine when BFE starts.

Let's focus on BFE's' startup, BfePersistentPolicyInit proceeds to the following calls sequence:

Built-in (they're stored in the bfe.dll binary) objects are loaded first, then BfeObjectMgrLoad is called:

BfeObjectMgrLoad first calls BfeDbOpen; the said database actually refers to the registry key we have seen at the beginning of this article:

All objects are marshalled using the same algorithm:

  • 0x10 bytes header containing metadata (marker) and size of data

  • Each object is marshalled in the registry as a BFE_PERSISTENT_OBJECT encoded using NDR:

    typedef struct _BFE_PERSISTENT_OBJECT {
        DWORD Type;
        DWORD SizeObject;
        CHAR  Object[];
        DWORD SizeSecurityDescriptor;
        CHAR SecurityDescriptor[];
    } BFE_PERSISTENT_OBJECT, * PBFE_PERSISTENT_OBJECT;
    
  • Type field is used to select the correct handler to unmarshall the object (data stored in Object field). There are 7 types.

    • 0 : Provider
    • 1 : Provider Context
    • 2 : SubLayer
    • 3 : Layer
    • 4 : Callout
    • 5 : Filter
    • 6 : Container
  • SecurityDescriptor contains a self-relative security descriptor.

The marshalling/unmarshalling operation is performed by the NdrMesTypeDecode2 function

__stdcall BFE_PERSISTENT_OBJECT_MARSHAL_Decode(x, x) proc near
        mov     edi, edi
        push    ebp
        mov     ebp, esp
        push    [ebp+pObject]   ; pObject
        xor     eax, eax
        inc     eax
        imul    eax, 180Ah
        add     eax, offset pFormatTypes
        push    eax             ; pFormatString
        push    offset pStubDesc ; pStubDesc
        push    offset pPicklingInfo ; pPicklingInfo
        push    [ebp+Handle]    ; Handle
        call    ds:NdrMesTypeDecode2
        pop     ebp
        retn    8
__stdcall BFE_PERSISTENT_OBJECT_MARSHAL_Decode(x, x) endp

Conclusion

It's time to end this post which is already long enough. We want to thank all the readers who read this far :) We hope you enjoyed our little journey inside WFP persistent state. However, keep in mind that we have barely scratched the surface! If you like these small analysis don't hesitate to tell us in the comments, we'll try to do more of them.

As a little bonus and to really conclude: when we finished our script we discovered that the definition of a vast majority of the encoded structure were already defined in the headers of WDK... Nevertheless it was fun!

Addendum: Decoded objects samples

Just for the record here is some decoded persistent objects.

Callout (sample)

[00] GUID: {22001ee0-8e87-4f75-ba58-248f5918a63a}
Struct FC_PSTRUCT_10004c12 @ 0x4:
    +000: Struct FC_STRUCT_10004bdc @ 0x4:
        => GUID: 22001ee0-8e87-4f75-ba58-248f5918a63a
    +010: Struct FC_PSTRUCT_10004be8 @ 0x14:
        +000: NIS Stream V4 Callout (FC_C_WSTRING_10004bf6)
        +004: NIS Stream V4 Callout (FC_C_WSTRING_10004c00)
    +018: 0x010000 (NdrLong)
    +01c: Struct FC_STRUCT_10004bdc @ 0xb0:
        => GUID: 839cd73f-1907-49ea-9aa5-0e6be9048087
    +020: 0x000000 (NdrLong)
    +024: 0x000000 (NdrLong)
    +028: Struct FC_STRUCT_10004bdc @ 0x2c:
        => GUID: 3b89653c-c170-49e4-b1cd-e0eeeee19a3e (FWPM_LAYER_STREAM_V4)
    +038: 0x00011e (NdrLong)

Provider (sample)

[00] GUID: {1bebc969-61a5-4732-a177-847a0817862a}
Struct FC_PSTRUCT_1000505e @ 0x4:
    +000: Struct FC_STRUCT_10004bdc @ 0x4:
        +000: 0x1bebc969 (NdrLong)
        +004: 0x61a5 (NdrUshort)
        +006: 0x4732 (NdrUshort)
        +008: Array FC_SMFARRAY_10004bd6, 8 element(s) @ 0xc:
            [0]: 0xa1 (NdrByte)
            [1]: 0x77 (NdrByte)
            [2]: 0x84 (NdrByte)
            [3]: 0x7a (NdrByte)
            [4]: 0x8 (NdrByte)
            [5]: 0x17 (NdrByte)
            [6]: 0x86 (NdrByte)
            [7]: 0x2a (NdrByte)
    +010: Struct FC_PSTRUCT_10004be8 @ 0x14:
        +000: @FirewallAPI.dll,-23521 (FC_C_WSTRING_10004bf6)
        +004: @FirewallAPI.dll,-23522 (FC_C_WSTRING_10004c00)
    +018: 0x000001 (NdrLong)
    +01c: 0x000000 (NdrLong)
    +020: 0x000000 (NdrLong)
    +024: MPSSVC (FC_C_WSTRING_1000508a)

Sublayer (sample)

[00] GUID: {8c36b346-4e0c-4049-8b55-5295ac35567c}
Struct FC_BOGUS_STRUCT_100056d2 @ 0x4:
    +000: Struct FC_STRUCT_10004bdc @ 0x4:
        +000: 0x8c36b346 (NdrLong)
        +004: 0x4e0c (NdrUshort)
        +006: 0x4049 (NdrUshort)
        +008: Array FC_SMFARRAY_10004bd6, 8 element(s) @ 0xc:
            [0]: 0x8b (NdrByte)
            [1]: 0x55 (NdrByte)
            [2]: 0x52 (NdrByte)
            [3]: 0x95 (NdrByte)
            [4]: 0xac (NdrByte)
            [5]: 0x35 (NdrByte)
            [6]: 0x56 (NdrByte)
            [7]: 0x7c (NdrByte)
    +010: Struct FC_PSTRUCT_10004be8 @ 0x14:
        +000: NIS High Priority Sublayer (FC_C_WSTRING_10004bf6)
        +004: NIS High Priority Sublayer (FC_C_WSTRING_10004c00)
    +018: 0x000001 (NdrLong)
    +01c: Struct FC_STRUCT_10004bdc @ 0xb8:
        +000: 0x839cd73f (NdrLong)
        +004: 0x1907 (NdrUshort)
        +006: 0x49ea (NdrUshort)
        +008: Array FC_SMFARRAY_10004bd6, 8 element(s) @ 0xc0:
            [0]: 0x9a (NdrByte)
            [1]: 0xa5 (NdrByte)
            [2]: 0xe (NdrByte)
            [3]: 0x6b (NdrByte)
            [4]: 0xe9 (NdrByte)
            [5]: 0x4 (NdrByte)
            [6]: 0x80 (NdrByte)
            [7]: 0x87 (NdrByte)
    +020: Struct FC_PSTRUCT_10004cc4 @ 0x24:
        +000: 0x000000 (NdrLong)
        +004: 0x000000 (NdrLong)
    +028: 0xffff (NdrUshort)

Filter (sample)

[20] GUID: {4e718c57-c397-4221-9fbb-14fd51701d6a}
Struct FC_BOGUS_STRUCT_10004ed4 @ 0x8:
    +000: 4e718c57-c397-4221-9fbb-14fd51701d6a # guid
    +010: Struct FC_PSTRUCT_10004be8 @ 0x18: # name
        +000: Interface Un-quarantine filter (FC_C_WSTRING_10004bf6)
        +004:  (FC_C_WSTRING_10004c00)
    +018: 0x000041 (NdrLong)
    +01c: decc16ca-3f33-4346-be1e-8fb4ae0f3d62
    +020: Struct FC_PSTRUCT_10004cc4 @ 0x28:
        +000: 0x000008 (NdrLong)
        +004: Array FC_CARRAY_10004cb8, 8 element(s) @ 0x110:
            [0]: 0xff (NdrChar)
            [1]: 0xff (NdrChar)
            [2]: 0xff (NdrChar)
            [3]: 0xff (NdrChar)
            [4]: 0xff (NdrChar)
            [5]: 0xff (NdrChar)
            [6]: 0xff (NdrChar)
            [7]: 0xff (NdrChar)
    +028: e1cd9fe7-f4b5-4273-96c0-592e487b8650 (FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4)
    +038: b3cdd441-af90-41ba-a745-7c6008ff2302
    +048: Struct FC_BOGUS_STRUCT_10004d86 @ 0x50:
        +000: 0x01 (NdrEnum32)
        +004: Union FC_NON_ENCAPSULATED_UNION_10004cd8 @ 0x54:
            union type 0x1: 0x1 (NdrChar)
    +050: 0x000004 (NdrLong)
    +054: Array FC_BOGUS_ARRAY_10004ebe, 4 element(s) @ 0x11c:
        [0]: Struct FC_BOGUS_STRUCT_10004eac @ 0x120:
            +000: 3971ef2b-623e-4f9a-8cb1-6e79b806b9a7 (FWPM_CONDITION_IP_PROTOCOL)
            +010: 0x00 (FWP_MATCH_EQUAL)
            +014: Struct FC_BOGUS_STRUCT_10004e9e @ 0x134:
                +000: 0x01 (FWP_UINT8)
                +004: Union FC_NON_ENCAPSULATED_UNION_10004dda @ 0x138:
                    union type 0x1: 0x11 (NdrChar)
        [1]: Struct FC_BOGUS_STRUCT_10004eac @ 0x140:
            +000: 0c1ba1af-5765-453f-af22-a8f791ac775b (FWPM_CONDITION_IP_LOCAL_PORT)
            +010: 0x00 (FWP_MATCH_EQUAL)
            +014: Struct FC_BOGUS_STRUCT_10004e9e @ 0x154:
                +000: 0x02 (FWP_UINT16)
                +004: Union FC_NON_ENCAPSULATED_UNION_10004dda @ 0x158:
                    union type 0x2: 0x44 (NdrUshort)
        [2]: Struct FC_BOGUS_STRUCT_10004eac @ 0x160:
            +000: c35a604d-d22b-4e1a-91b4-68f674ee674b (FWPM_CONDITION_IP_REMOTE_PORT)
            +010: 0x00 (FWP_MATCH_EQUAL)
            +014: Struct FC_BOGUS_STRUCT_10004e9e @ 0x174:
                +000: 0x02 (FWP_UINT16)
                +004: Union FC_NON_ENCAPSULATED_UNION_10004dda @ 0x178:
                    union type 0x2: 0x43 (NdrUshort)
        [3]: Struct FC_BOGUS_STRUCT_10004eac @ 0x180:
            +000: 632ce23b-5167-435c-86d7-e903684aa80c (FWPM_CONDITION_FLAGS)
            +010: 0x08 (FWP_MATCH_FLAGS_NONE_SET)
            +014: Struct FC_BOGUS_STRUCT_10004e9e @ 0x194:
                +000: 0x03 (FWP_UINT32)
                +004: Union FC_NON_ENCAPSULATED_UNION_10004dda @ 0x198:
                    union type 0x3: 0x000001 (NdrLong)
    +058: Struct FC_BOGUS_STRUCT_10004db0 @ 0x64:
        +000: 0x001002 (FWP_ACTION_PERMIT)
        +004: Union FC_NON_ENCAPSULATED_UNION_10004d94 @ 0x68:
            union type 0x0: 00000000-00-00-0000-000000000000
    +070: Union FC_NON_ENCAPSULATED_UNION_10004dbe @ 0x7c:
        union type 0x0: 0x00000000000000 (NdrHyper)
    +080: 0x000000 (NdrLong)
    +088: 0x0000000001010a (NdrHyper)
    +090: Struct FC_BOGUS_STRUCT_10004d86 @ 0x98:
        +000: 0x04 (NdrEnum32)
        +004: Union FC_NON_ENCAPSULATED_UNION_10004cd8 @ 0x9c:
            union type 0x4: 0x1007830800000000 (NdrHyper)

Comments