Si vous êtes intéressés pour bosser sur des sujets sympas tout en restant loin de Paris, consultez nos offres d'emploi et envoyez nous votre CV à

Windows filter communication ports

Brief technical analysis of the Microsoft Windows "filter communication port" kernel communication mechanism with filtering drivers.

Filter communication ports are a communication mechanism between usermode processes and filesystem filter drivers. When accessible by unprivileged users, it may offer privilege escalation vulnerabilities. We did not understand why we could not find opened handles in client process, and as we did not find documentation on how it is implemented in Windows, we took a quick look and wrote this blog post :). Keep in mind that it is a quickly written blog post, several asumptions may be wrong. If you find such mistakes, just let us know :).

Filter communication ports are handled by the fltlib.dll module. The FilterConnectCommunicationPort function will create the port handle. It opens a new handle on the \\Global??\\FltMgrMsg symbolic link, which points over the \FileSystem\Filters\FltMgrMsg device object. Actually, the NtCreateFile syscall will also be provided with one or two extended attributes.

The following struct represents NTFS Extended Attributes (the attribute's value follows directly EaName, after EaValueLength bytes):

typedef struct _FILE_FULL_EA_INFORMATION {
  ULONG  NextEntryOffset;
  UCHAR  Flags;
  UCHAR  EaNameLength;
  USHORT EaValueLength;
  CHAR   EaName[1];

In this case, the first attribute will be:

  • Flag: 0x700 (undocumented) ;
  • EaName: FLTPORT ;
  • EaValue: filter communication port name (PortName parameter), as a UNICODE_STRING pointer.

A third extended attribute may be present, and will contain the Context data, which is provided to the FilterConnectCommunicationPort, and which will be sent to the driver.

When a driver creates the filter communication port using the FltCreateCommunicationPort function, a new object will be created with:

  • The provided object attributes, and thus the provided port name. The OBJ_KERNEL_HANDLE attribute must be set.
  • The "KernelMode" AccessMode.
  • The "KernelMode" ProbeMode.

This newly created object is a FLT_MSG_CONNECTION (undocumented, we chose a new name :) ), which follows this scheme:

typedef struct _FLT_MSG_OBJECT_CALLBACKS {
    PFLT_FILTER             Filter;
    ULONGLONG               ServerPortCookie;
    PFLT_CONNECT_NOTIFY     ConnectNotifyCallback;
    PFLT_DISCONNECT_NOTIFY  DisconnectNotifyCallback;
    PFLT_MESSAGE_NOTIFY     MessageNotifyCallback;

typedef struct _FLT_MSG_CONNECTION {
    LIST_ENTRY              List;
    PFLT_CONNECT_NOTIFY     ConnectNotifyCallback;
    PFLT_DISCONNECT_NOTIFY  DisconnectNotifyCallback;
    PFLT_MESSAGE_NOTIFY     MessageNotifyCallback;
    PFLT_FILTER             Filter;
    ULONG                   undefined;
    ULONG                   ConnectionCount;
    ULONG                   MaximumConnections;

The FLT_MSG_CONNECTION is also added to the Filter object (FLT_FILTER), in its ConnectionList item.

Now, when the IRP_MJ_CREATE reaches the FltMgr.sys driver for \\.\FltMgrMsg, a custom IRP handler is called for this particular device object, and a specific function handles this IRP. Before that, ntoskrnl will parse the EaBuffer NtCreateFile parameter, and will copy its contents into an newly allocated pool buffer (IoEa tag) and will check this buffer using the IoCheckEaBufferValidity(PVOID EaBuffer, ULONG EaLength, PULONG DwEaAttributesCount). This functions only checks if there is no overflow with the offsets and sizes.

We did not search where the extended attributes checks are performed by the kernel, we only know it is correctly checked (we tried providing bad structs and pointers, without any problem).

This routine will verify that a "FLTPORT" extended attribute is present, and will retrieve its EaValue (the communication port name). This name will be passed to ObReferenceObject (which seems to perform the access check), with an AccessMode set to ExGetPreviousMode(). The MaximumConnections is checked, and a new unnamed kernel object will be created. Then, the ConnectNotifyCallback routine of the port object will be called. If this call is successful, the FsContext2 item of the referenced FileObject (FILE_OBJECT struct) will also be created, and will have several of his members initiliazed, and will point over these objects (filter object, etc.). This struct has not been fully analyzed, and will not be detailed here.

When sending a filter message using the FilterSendMessage function from the userland, an IOCTL is sent to the opened device object. In this case, the 0x8801B CTL code will be used (DISK_FILE_SYSTEM , METHOD_NEITHER, FILE_WRITE_ACCESS, Function 6). For FltGetMessage, the 0x8401F CTL code will be used. They are sent with the NtDeviceIoControlFile function, but several other ones may be sent through NtFsControlFile (the control codes begin with 0x90000).

The FltMsg.sys driver will handle these IOCTLs by first checking the provided userland buffer pointers with ProbeForRead and ProbeForWrite calls, but will NOT copy these buffers into newly allocated areas, and that's why states "Note that OutputBuffer is a pointer to a raw, unlocked user-mode buffer. This pointer is valid only in the context of the user-mode process and must only be accessed from within a try/except block". On FltSendMessage IOCTLs, the callback function pointer is retrieved using the referenced file object (FILE_OBJECT) and its FsContext2 member. The original FLT_MSG_CONNECTION structure pointer may be found at *(*(FsContext2 + 0x8) + 0x10) :

Send message IOCTL handler

To summarize, filter communication ports relay on METHOD_NEITHER IOCTLs. It seems that it is actually not possible to retrieve, from userland, which process opens which filter communication port, as the only handles which may be found are the FltMgrMsg device object, and an unamed one. If you have any insight on how it could be possible to list them from userland, let us know!

Like METHOD_NEITHER IOCTLs, supplied message buffers must be probed and locked (or copied in kernel buffers) by the message notify routine: unlike these IOCTLs, attackers cannot provide kernel addresses (a ProbeForRead/Write is made by default by FltMgr) but TOCTOU attacks may also be present. And like any other IOCTL handling, the notify routine should never store userland pointers in these buffers (or, in this case, handle them with the same security considerations).