深入探究Windows过滤器通信端口技术分析

本文详细介绍了Windows过滤器驱动通信端口的工作原理,包括如何查找过滤器创建的通信端口、识别连接的进程,以及分析端口的消息流向和等待队列,使用WinDbg进行内核调试分析。

调查过滤器通信端口

如果您花费时间编写或研究过滤器驱动程序,可能会遇到过滤器通信端口。这是过滤器驱动程序与其用户模式进程之间的标准通信方法,由过滤器管理器(FltMgr.sys)实现和管理。端口允许进程和驱动程序来回发送消息。端口被命名,以便进程可以轻松找到并连接到它们,并且它们允许过滤器驱动程序通过安全描述符、最大连接数字段以及在每次新连接尝试时调用的方法来决定谁可以访问端口,从而允许驱动程序动态允许或拒绝特定的连接请求。

如果您有兴趣学习如何创建和使用通信端口,我建议查看Windows驱动程序示例Github存储库。在本文中,我将专注于取证方面,看看我们如何调查过滤器通信端口以获取一些有趣的信息。具体来说,我将展示如何回答两个问题:

  • 我们如何找出过滤器驱动程序创建了哪些通信端口?
  • 哪些用户模式进程连接到通信端口?

像往常一样,我在WinDbg内核调试会话中进行调查。

查找通信端口

我们可以轻松回答第一个问题。要找出过滤器驱动程序创建的端口,我们可以使用FltKd扩展——SDK中提供的许多有用调试器扩展之一。此扩展DLL并不总是默认加载,因此您可能必须使用.load命令手动将DLL加载到调试器中。如果您使用旧版调试器,DLL应在“C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\winxp\fltkd.dll”中,如果您使用Preview,则在WinDbg Preview安装路径下。

FltKd有几个有用的命令来调试过滤器驱动程序(您可以通过运行!fltkd.help查看所有命令)。第一个命令是!fltkd.filters,它显示系统中所有注册的过滤器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
!fltkd.filters

Filter List: ffff9c8f51af0320 "Frame 0"
    FLT_FILTER: ffff9c8f5bce7010 "bindflt" "409800"
       FLT_INSTANCE: ffff9c8f6aa51010 "bindflt Instance" "409800"
   FLT_FILTER: ffff9c8f55b86ba0 "FsDepends" "407000"
      FLT_INSTANCE: ffff9c8f554c1b40 "FsDepends" "407000"
      FLT_INSTANCE: ffff9c8f554ca6a0 "FsDepends" "407000"
      FLT_INSTANCE: ffff9c8f68fd2010 "FsDepends" "407000"
      FLT_INSTANCE: ffff9c8f68fea930 "FsDepends" "407000"
      FLT_INSTANCE: ffff9c8f68fea4a0 "FsDepends" "407000"
      FLT_INSTANCE: ffff9c8f68fea010 "FsDepends" "407000"
   FLT_FILTER: ffff9c8f53d3dab0 "WdFilter" "328010"
      FLT_INSTANCE: ffff9c8f53eb48a0 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffff9c8f551398e0 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffff9c8f553858e0 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffff9c8f55643010 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffff9c8f5573d8e0 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffff9c8f5577c8a0 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffff9c8f5a3d38a0 "WdFilter Instance" "328010"
   FLT_FILTER: ffff9c8f627d1ba0 "storqosflt" "244000"
   FLT_FILTER: ffff9c8f5a6d7030 "wcifs" "189900"
      FLT_INSTANCE: ffff9c8f6add9010 "wcifs Outer Instance" "189899"
   FLT_FILTER: ffff9c8f62eee8a0 "CldFlt" "180451"
      FLT_INSTANCE: ffff9c8f557b8010 "CldFlt" "180451"
   FLT_FILTER: ffff9c8f628cbba0 "bfs" "150000"
      FLT_INSTANCE: ffff9c8f55734b00 "bfs" "150000"
      FLT_INSTANCE: ffff9c8f5a7e0ba0 "bfs" "150000"
      FLT_INSTANCE: ffff9c8f5a7e1ba0 "bfs" "150000"
      FLT_INSTANCE: ffff9c8f627ee8a0 "bfs" "150000"
      FLT_INSTANCE: ffff9c8f627ed8a0 "bfs" "150000"
      FLT_INSTANCE: ffff9c8f627ec8a0 "bfs" "150000"
      FLT_INSTANCE: ffff9c8f627eb8a0 "bfs" "150000"
      FLT_INSTANCE: ffff9c8f627ea8a0 "bfs" "150000"
      FLT_INSTANCE: ffff9c8f627e98a0 "bfs" "150000"
   FLT_FILTER: ffff9c8f550d4c60 "FileCrypt" "141100"
   FLT_FILTER: ffff9c8f5a85e010 "luafv" "135000"
      FLT_INSTANCE: ffff9c8f629cf010 "luafv" "135000"
   FLT_FILTER: ffff9c8f552e8c40 "npsvctrig" "46000"
      FLT_INSTANCE: ffff9c8f5516d8a0 "npsvctrig" "46000"
   FLT_FILTER: ffff9c8f53d38a00 "Wof" "40700"
      FLT_INSTANCE: ffff9c8f5510b8a0 "Wof Instance" "40700"
      FLT_INSTANCE: ffff9c8f5569f8a0 "Wof Instance" "40700"
      FLT_INSTANCE: ffff9c8f5572c8e0 "Wof Instance" "40700"
      FLT_INSTANCE: ffff9c8f5574a8a0 "Wof Instance" "40700"
   FLT_FILTER: ffff9c8f53d3b8a0 "FileInfo" "40500"
      FLT_INSTANCE: ffff9c8f53ea28a0 "FileInfo" "40500"
      FLT_INSTANCE: ffff9c8f550d58a0 "FileInfo" "40500"
      FLT_INSTANCE: ffff9c8f55364010 "FileInfo" "40500"
      FLT_INSTANCE: ffff9c8f556486e0 "FileInfo" "40500"
      FLT_INSTANCE: ffff9c8f556cd8a0 "FileInfo" "40500"
      FLT_INSTANCE: ffff9c8f557458a0 "FileInfo" "40500"
      FLT_INSTANCE: ffff9c8f5a3c9730 "FileInfo" "40500"

此命令枚举FLTMGR!FltGlobals中的帧,然后枚举为每个帧注册的过滤器。如果我们愿意,我们可以用DX重新创建这个,但目前FltKd输出已经足够好了。

我们的下一步是找到过滤器驱动程序注册的所有端口。我们也可以使用FltKd,使用fltkd.portlist命令。对于这个练习,我们将选择Windows Defender过滤器驱动程序wdfilter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
!fltkd.portlist 0xffff9c8f53d3dab0
 FLT_FILTER: ffff9c8f53d3dab0    Client Port List         : Mutex (ffff9c8f53d3dd08) List [ffff9c8f6b6312f0-ffff9c8f6b633270] mCount=5
       FLT_PORT_OBJECT: ffff9c8f6b6312f0
          FilterLink               : [ffff9c8f6b630870-ffff9c8f53d3dd40]
          ServerPort               : ffff9c8f524f3420
          Cookie                   : ffff9c8f53d3e108
          Lock                     : (ffff9c8f6b631318)
         MsgQ                     : (ffff9c8f6b631350)  NumEntries=0 Enabled
         MessageId                : 0x0000000000000000
          DisconnectEvent          : (ffff9c8f6b631428)
         Disconnected             : FALSE
       FLT_PORT_OBJECT: ffff9c8f6b630870
          FilterLink               : [ffff9c8f6b634770-ffff9c8f6b6312f0]
          ServerPort               : ffff9c8f524f4550
          Cookie                   : ffff9c8f53d3e148
          Lock                     : (ffff9c8f6b630898)
         MsgQ                     : (ffff9c8f6b6308d0)  NumEntries=8 Enabled
          MessageId                : 0x0000000000000000
          DisconnectEvent          : (ffff9c8f6b6309a8)
          Disconnected             : FALSE
       FLT_PORT_OBJECT: ffff9c8f6b634770
          FilterLink               : [ffff9c8f6b634cb0-ffff9c8f6b630870]
          ServerPort               : ffff9c8f524f44a0
          Cookie                   : ffff9c8f53d3e138
          Lock                     : (ffff9c8f6b634798)
          MsgQ                     : (ffff9c8f6b6347d0)  NumEntries=16 Enabled
          MessageId                : 0x0000000000000000
          DisconnectEvent          : (ffff9c8f6b6348a8)
          Disconnected             : FALSE
       FLT_PORT_OBJECT: ffff9c8f6b634cb0
          FilterLink               : [ffff9c8f6b633270-ffff9c8f6b634770]
          ServerPort               : ffff9c8f524f3840
          Cookie                   : ffff9c8f53d3e118
          Lock                     : (ffff9c8f6b634cd8)
          MsgQ                     : (ffff9c8f6b634d10)  NumEntries=16 Enabled
          MessageId                : 0x000000000000a3c1
          DisconnectEvent          : (ffff9c8f6b634de8)
          Disconnected             : FALSE
       FLT_PORT_OBJECT: ffff9c8f6b633270
          FilterLink               : [ffff9c8f53d3dd40-ffff9c8f6b634cb0]
          ServerPort               : ffff9c8f524f3e70
          Cookie                   : ffff9c8f53d3e128
          Lock                     : (ffff9c8f6b633298)
          MsgQ                     : (ffff9c8f6b6332d0)  NumEntries=2 Enabled
          MessageId                : 0x0000000000001e98
          DisconnectEvent          : (ffff9c8f6b6333a8)
          Disconnected             : FALSE

很好,我们找到了wdfilter创建的五个端口!然而,在这种情况下,我们可能确实想尝试用DX命令获取此信息,而不是满足于旧版扩展输出。这是因为旧版扩展命令的输出无法枚举或操作,并且没有旧版命令可以回答我们的第二个问题。这意味着要找到连接的进程,我们必须对每个端口单独操作,导致大量手动步骤。如果我们想自动化这个过程,我们应该使用调试器数据模型获取此信息,并将端口保存在一个变量中,以便用于其他命令。

每个过滤器驱动程序通过FLT_FILTER结构进行管理。此结构包含过滤器的所有管理信息,包括其所有通信端口的列表,链接在其PortList字段中。每个端口的数据保存在FLT_PORT_OBJECT结构中。方便的是,我们从之前的命令!fltkd.filters中获得了所有注册过滤器的FLT_FILTER结构的地址。因此,让我们获取wdfilter FLT_FILTER结构的地址,并使用DX解析端口列表。为了以后更容易使用,我将创建一个辅助函数来执行此操作,并将wdfilter地址保存在一个变量中:

1
2
3
dx @$enumPortsForFilter = (filter => Debugger.Utility.Collections.FromListEntry(((fltmgr!_FLT_FILTER*)filter)->PortList.mList, "fltmgr!_FLT_PORT_OBJECT", "FilterLink"))

dx @$wdfilter = 0xffff9c8f53d3dab0

现在我们可以调用该函数并获取驱动程序注册的所有端口,并将它们保存在一个变量中,我们将在本文的其余部分使用:

1
2
3
4
5
6
7
dx @$wdfilterports = @$enumPortsForFilter(@$wdfilter)
@$wdfilterports = @$enumPortsForFilter(@$wdfilter)
    [0x0]            [Type: _FLT_PORT_OBJECT]
    [0x1]            [Type: _FLT_PORT_OBJECT]
    [0x2]            [Type: _FLT_PORT_OBJECT]
    [0x3]            [Type: _FLT_PORT_OBJECT]
    [0x4]            [Type: _FLT_PORT_OBJECT]

在我们进入问题的第二部分并尝试找到使用每个端口的进程之前,我们可能还想找到关于每个端口的另一个信息:它的名称。为此,我们需要查看端口结构本身,因为我们检索到的通信端口没有名称,正如我们可以用!object命令看到的:

1
2
3
4
5
6
7
dx -r0 &@$wdfilterports.First()
&@$wdfilterports.First()                 : 0xffff9c8f6b6312f0 [Type: _FLT_PORT_OBJECT *]

!object 0xffff9c8f6b6312f0
Object: ffff9c8f6b6312f0  Type: (ffff9c8f4f0f5f00) FilterCommunicationPort
    ObjectHeader: ffff9c8f6b6312c0 (new version)
    HandleCount: 1  PointerCount: 3

相反,我们需要查看FLT_PORT_OBJECT的ServerPort字段,它指向一个连接端口对象,表示驱动程序到端口的连接:

1
2
3
4
5
6
7
8
dx -r0 @$wdfilterports.First().ServerPort
@$wdfilterports.First().ServerPort                 : 0xffff9c8f524f3420 [Type: _FLT_SERVER_PORT_OBJECT *]

!object 0xffff9c8f524f3420
Object: ffff9c8f524f3420  Type: (ffff9c8f4f0f5400) FilterConnectionPort
    ObjectHeader: ffff9c8f524f33f0 (new version)
    HandleCount: 1  PointerCount: 3
    Directory Object: ffffd584ae22c930  Name: MicrosoftMalwareProtectionControlPortWD

现在我们找到了端口的名称——MicrosoftMalwareProtectionControlPortWD。我们可以对每个通信端口的服务器端口运行!object,并找到所有端口的名称。这可以用dx和ExecuteCommand例程自动化,但如果您运行的是现代版本的WinDbg,您可以找到连接端口的对象头并访问ObjectName字段以检索名称。此字段实际上不是OBJECT_HEADER结构的一部分,但在现代版本中,调试器数据模型解析名称并将其添加为合成字段。不幸的是,调试器数据模型没有提供简单的方法来获取给定对象的头地址,硬编码偏移量也不理想,因此我们将使用C++ #FIELD_OFFSET宏将偏移量保存在寄存器中,并在我们的DX命令中使用它。然后我们可以快速获取wdfilter创建的每个端口的名称:

1
2
3
4
5
6
7
8
r? @$t1 = #FIELD_OFFSET(nt!_OBJECT_HEADER, Body)
dx @$wdfilterports.Select(p => ((nt!_OBJECT_HEADER*)((__int64)p.ServerPort - @$t1))->ObjectName)
@$wdfilterports.Select(p => ((nt!_OBJECT_HEADER*)((__int64)p.ServerPort - @$t1))->ObjectName)
    [0x0]            : "MicrosoftMalwareProtectionControlPortWD"
    [0x1]            : "MicrosoftMalwareProtectionAsyncPortWD"
    [0x2]            : "MicrosoftMalwareProtectionRemoteIoPortWD"
    [0x3]            : "MicrosoftMalwareProtectionPortWD"
    [0x4]            : "MicrosoftMalwareProtectionVeryLowIoPortWD"

如果您使用的是旧版本的WinDbg,可能没有自动添加ObjectName字段,需要自己解析。这样做有点丑陋,也不是本文的主题,因此我将跳过这一步,只建议您使用最新版本的调试器。

或者,我们可以跳过本文的整个部分,使用WinObjEx的搜索功能搜索所有FilterConnectionPort对象,并查看每个对象以找出哪个驱动程序创建了它:

但是像WinObjEx这样的工具并不总是可用(例如,当您分析崩溃转储且无法访问实时机器时),此外,我们只能使用内核调试器回答问题的

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计