Windows Defender网络检查驱动逆向分析研究
2021-08-30 12:45:00 Author: www.4hou.com(查看原文) 阅读量:83 收藏

本篇主要是介绍了 Windows Defender 如何通过使用 WFP(Windows 过滤平台)在内核中实现其网络检查功能,设备对象的安全描述符如何保护WFP免受潜在漏洞的影响,并详细介绍了我发现的一些漏洞。

Windows过滤平台(Windows Filtering Platform,缩写WFP):是微软操作系统中的一套系统服务和应用程序接口,于2006年至2007年在Windows Vista中首次引入。它允许应用程序绑定到包处理环节,过滤TCP/IP协议栈的流水线数据包。WFP提供了集成通信等功能,管理员可以将其配置为在每个应用程序的基础上调用处理逻辑,WFP将被防火墙及其他数据包处理或连接监控组件调用,如杀毒软件和家长控制软件。

就像所有逆向工程师一样,了解事物的运作方式本身就很有趣,而闭源软件的逆向会更加令人兴奋。Windows 已经发展更新了很多版本,防病毒软件需要有质的上升。随着 Patchguard 的出现以及对操作系统的安全性关注上升,内核钩子几乎已经消失了。但是,Windows 提供了多种方法来收集有关对象(进程、线程、文件、注册表……)的信息,例如notifications回调、过滤器、事件……人们可能想知道 AV 驱动程序如何收集信息以及它们收集的信息类型。逆向 Windows Defender 是有价值的,因为它可能依赖于 Windows 必须提供的所有最新功能。

内核补丁保护(Kernel Patch Protection),PatchGuard:是Microsoft Windows 64位版本中预防对内核进行修补的一种特性。该特性2005年在Windows XP与Windows Server 2003 Service Pack 1的64位版本中首次推出。 PatchGuard:是指对Windows操作系统的核心组件或内核本不支持的修改。这种修改没有得到微软的支持,并据微软称可能大幅降低系统的安全性、可靠性以及性能。尽管微软并不推荐,但在Windows的x86版本上修补内核没有受到限制;而在Windows的x64版本中,微软选择为此行为实施额外的保护和技术障碍。 由于Windows内核的设计,内核补丁保护并不能完全阻拦内核修补。因而这引发了对内核补丁保护的批评,指责它是一种不完美的防护措施,对反病毒厂商造成的障碍超过了其带来的好处,因为恶意软件开发者可能找到方法来绕过该措施。尽管如此,内核补丁保护仍可防止由合法软件以不受支持方式进行的内核修补,这可能对系统的稳定性、可靠性和性能带来负面结果。

0x01 基于 WFP 的驱动程序

Windows 过滤平台允许在网络堆栈的不同层设置过滤器,并提供一组丰富的功能来与流量交互:数据篡改、注入、应用策略、重定向……

MSDN 页面 About Windows Filtering Platform 广泛描述了它的所有功能以及运行方式。

1.Filter

此处分析的驱动程序的文件版本是4.18.2102.3-0。

如前所述,网络检查驱动程序WdNisDrv严重依赖 WFP 模型。该架构非常复杂,但基本上驱动程序需要在特定层或子层上注册过滤器,指定过滤条件,然后为该过滤器提供一组称为callout的回调函数。

WFP提供了过滤器(Filter)的概率,其实现对数据的过滤。例如我们注册了一个对网络数据操作的Callout,但是我们只想对目标IP地址为A的数据包进行操作,这时就可以通过定义和注册相应的Filter,过滤出目标地址为A的数据包,Callout则只会对经过过滤后的数据包进行处理。

Callout是WFP中非常重要的数据结构,其中包括对数据的具体操作函数,以及一个唯一标识符GUID,我们主要是通过向过滤引擎注册自己定义的Callout来实现对数据的操作。

img

可以通过执行以下命令dump系统上当前配置的不同过滤器:

netsh wfp show filters

将会输出一个非常冗长的 XML 文件,其中包含系统上的filter、callout、跳转、层次……。

有关数据包在遍历网络堆栈时所经过的层次的完整列表, 可以查阅文档 TCP 数据包流。它在建立连接时将 TCP 标志与层映射。

在 XML 文件中搜索“windefend”时,可以检索不同层的配置。例如,当查看FWPM_LAYER_STREAM_V4层时,可以了解到windefend_stream_v4的作用是关联和注册。

img

标志FWP_CALLOUT_FLAG_CONDITIONAL_ON_FLOW说明上下文需要与要调用的数据流相关联。其关联的过滤器表明,如果调用被取消注册,它将允许流量通过,并且调用可以阻止/允许处理数据流。它与 FWPM _ layer _ ale_flow _ established _ v4层的工作状态是一致的。

img

MSDN 表示该层用于在 TCP 连接建立时进行notifications。callout将负责创建一个“用户定义的” FLOW_CONTEXT结构,这是调用流层callout所必需的。关联过滤器需要满足以下条件才能触发callout:

◼数据流的方向需要向外(FWP_DIRECTION_OUTBOUND);

◼IP 协议必须是 TCP 或 UDP;

◼callout只能检查数据包,不能阻止流量(FWP_ACTION_CALLOUT_INSPECTION)。

总结一下网络检测驱动内部一个IPv4数据包的流程,当一个连接建立时,它会经过 FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4层。如果数据流上下文已由先行层创建,则数据包将通过流/数据报层过滤器。当关闭连接时,数据包通过相同的层并且流上下文被删除。在每一层,将执行一组注册到callout的回调。

2.Callout

callout 的注册是通过调用FwpsCalloutRegister2实现的,该调用 将FWPS_CALLOUT2_结构作为参数。它由一个与过滤器相关联的 GUID 和三个不同的回调函数组成:notify, classify 和 delete。继续传输 IPv4 数据包流,驱动程序仅为FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4注册了一个分类函数。函数原型如下:

void        FWPS_CALLOUT_CLASSIFY_FN2(
  const FWPS_INCOMING_VALUES0 *inFixedValues,
  const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues,
  void *layerData,
  const void *classifyContext,
  const FWPS_FILTER2 *filter,
  UINT64 flowContext,
  FWPS_CLASSIFY_OUT0 *classifyOut
)

如前所述,该函数负责创建上下文数据流,在两个端点之间的通信期间,该数据流上下文将与交换的数据包相关联,使用实参来填充以下结构。

img

FWPM_LAYER_STREAM_V4层的callout注册了 classify和delete函数。该classify函数的主要目标是确定数据包是否应该被丢弃,通过查看FLOW_CONTEXT结构的FilterFlag成员来实现。Delete 函数的主要目的是释放,释放和删除与连接相关的所有内容,比如 FLOW _ context。

这些函数都会为userland服务WdNisSvc创建一个要处理的notifications。这个过程由NisSrv.exe可执行文件负责,根据收到的notifications分析网络数据流。

3.消息notifications

基本上,用户态服务向 WdNisDrv 驱动程序发送特定的 IOCTL 以请求连接notifications。驱动程序使用取消安全 IRP 队列来跟踪请求并在调用调出时完成。连接notifications以header开头,然后是union,具体取决于notifications的类型。

typedef struct {
    unsigned long long CreationTime;
    unsigned long long NotificationType;
} _CONNECTION_NOTIFICATION_HEADER;
typedef struct {
    _CONNECTION_NOTIFICATION_HEADER Header;
    union {
        _FLOW_NOTIFICATION FlowNotification;
        _STREAM_DATA_NOTIFICATION StreamDataNotification;
        _ERROR_NOTIFICATION ErrorNotification;
        _FLOW_DELETE_NOTIFICATION FlowDeleteNotification;
    };
} _CONNECTION_NOTIFICATION;

例如,当建立连接时,该层的 classify 函数会创建以下notifications:

typedef struct {
    unsigned long long FlowHandle;
    unsigned short Layer;
    unsigned int CalloutId;
    unsigned int IpProtocol;
    unsigned char FilterFlag;
    union {
        SOCKADDR_IN IPv4;
        SOCKADDR_IN6 IPv6;
        SOCKADDR_STORAGE_LH IPvX;
    } LocalAddress;
    union {
        SOCKADDR_IN IPv4;
        SOCKADDR_IN6 IPv6;
        SOCKADDR_STORAGE_LH IPvX;
    } RemoteAddress;
    unsigned int ProcessId;
    unsigned long long ProcessCreationTime;
    unsigned char IsProcessExcluded;
    unsigned int ProcessPathLength;
} _FLOW_NOTIFICATION;

流数据notifications非常简单,它最重要的字段是数据本身,附加到notifications和 StreamFlags成员,表示报文是出方向还是入方向,以及其他标志(PSH、URG)。

typedef struct {
    unsigned long long PktExchanged;
    unsigned long long FlowHandle;
    unsigned short Layer;
    unsigned int CalloutId;
    unsigned short StreamFlags;
    unsigned short IsStreamOutbound;
    unsigned int StreamSize;
} _STREAM_DATA_NOTIFICATION;

因此,在发送数据之前,服务知道哪个程序建立了连接以及它与哪个 IP 地址和端口通信。然后它可以跟踪该层上发送和接收的数据包。

我写了一个小程序,可以实时查看这些notifications。

img

收集了有关过滤器、callout和notifications的不同信息后,就可以了解检查驱动程序事怎么样检索网络信息以及它抓取什么样的信息来跟踪数据流。下表总结了所有不同的callouts, filters, conditions 和 flags。

img

0x02 配置和附加功能

1.Set/Get/Add-MpPreference

通过“Windows Security”应用程序配置 Windows Defender 时,是无法获取MpPreference的。但是,可以使用Defender PowerShell 模块访问更多配置选项。该模块由ProtectionManagement.mof描述并在同名的 dll 中实现。Get-MpPreference用于检索当前配置,并使用Add-MpPreference和Set-MpPreference对其进行设置。

img

本质上,当修改配置时,会导致向驱动程序发送一个 IOCTL。例如,当排除 IP 地址时,将SOCKADDR_STORAGE结构列表传递给驱动程序并添加到AVL 树中;然后,在建立连接时,将根据AVL树检查目标地址。流程也是如此,但是为了提供更大的灵活性,我们的方法略有不同。在初始化期间,在初始化期间,WdNisDrv驱动程序会注册一个回调函数,该回调将由WdFilter驱动程序通过\CallbackWdProcessNotificationCallbacknotifications。其他驱动程序通过PsSetCreateProcessNotifyRoutine实现进程notifications回调 。还会创建一个 AVL 树,并且每个进程notifications都会更新树,无论是创建、终止新进程还是更改状态。建立连接时,将根据该树检查进程 ID。

img

该模块提供了 3 个不同级别的EnableNetworkProtection参数配置网络保护级别。有以下枚举:Disabled、Enabled、 AuditMode。Enabled会阻止恶意的 IP 地址和域名,AuditMode不会阻止,而只是创建与本应被阻止的连接相关的 Windows 事件。日志名称是“Microsoft-Windows-Windows Defender/Operational”。有趣的事件是 1125 和 1126,它们会显示目的ip和访问它的程序,两种模式都会为用户空间服务生成连接notifications来进行解析。

2.相关的 IOCTL

如前所述,可以通过使用 I/O 控制代码来配置驱动程序。这是 IOCTL 及其功能的列表:

◼0x226005 : 设置过滤状态

◼0x226009:设置进程异常(可执行路径列表)

◼0x226011:在堆栈中注入数据流或数据报文

◼0x226015 : 设置 IP 地址

◼0x22A00E : 请求连接notifications

IOCTL 允许将数据包或数据报直接注入网络堆栈。例如,如果某些数据需要注入到数据流层内部,则应打开连接并检索其 FlowHandle。该值与数据和数据流层的 calloutid一起通过 IRP 的输入缓冲区传递,并用于通过调用FwpsStreamInjectAsync0来注入数据包 。下图说明了我开发的工具在开放连接中的数据包注入,没有数据流notifications传递给用户空间服务。

img

数据报层也可以通过调用FwpsInjectTransportSendAsync0来实现相同的 效果。

0x03 安全性分析

1.混乱的 ACL 机制

在初始化过程的早期,驱动程序调用 WdmlibIoCreateDeviceSecure例程来创建设备对象。它在_DEVICE_OBJECT标志成员上设置DO_EXCLUSIVE位,之后继续在设备对象上应用安全描述符。

img

unpack('< IIIII', SHA1.new("WDNISSVC".encode('utf-16le')).digest())
(3668810961, 2468724468, 4084584310, 3029221373, 430494444)

安全描述符声明只有WdNisSvc 服务可以打开设备对象的句柄。

为了使用不同的 IOCTL,我写了一个脚本,该脚本删除了设备独占,并且只将SE_SELF_RELATIVE标志保留在安全描述符的控制成员中。这将有效地消除对设备的任何保护。但是,由于设备的排他性,每当收到IRP_MJ_CLEANUP 时(很可能通过调用 CloseHandle),驱动程序将停止过滤。

如果不停用上述机制,就无法修复我发现的以下漏洞。必须使用 Windbg Preview 创建内核调试会话。然后,当加载和初始化驱动程序时,可以执行脚本来取消保护。

dx Debugger.State.Scripts.mod_sec.Contents.open_dev()*

2.IP 地址 exclusion 中的整数溢出

处理 IP 地址 exclusion  IOCTL 的函数需要一个 QWORD 作为计数整数,后跟一个描述 IP 地址的SOCKADDR_STORAGE数组。

img

所需字节数的计算和检查如下: (_IO_STACK_LOCATION.Parameters.DeviceIoControl.InputBufferLength < count * sizeof(sockaddr_storage) + sizeof(count))。

由于没有考虑溢出,如果我们设置 count 变量的 7 个最高有效位,就可以实现溢出。但是,该计数仍然在循环中用作要插入的SOCKADDR_STORAGE的数量。当代码尝试访问非映射内存页面时,这将导致蓝屏死机。

3.数据报注入中的越界读取

处理 IOCTL 的函数对输入缓冲区进行一些初步检查,以检查长度是否正确,缓冲区包含一个“注入头”。

img

img

首先,它检查输入缓冲区是否足够大以容纳30字节长的报头结构。然后,它将头的大小添加到头内部指定的大小,并检查输入缓冲区的大小是否等于或高于结果。但是,可以将此大小字段设置为零并通过检查。在这种情况下,当调用流注入函数时,将进行两次访问,这将导致一次越界读取:一次在注入头后面的偏移量0x1E处,另一次在偏移量0xB6处。

img

在调试时,可以通过查看输入缓冲区长度和进行的访问来观察漏洞点。

img

第一次越界读取不太可能导致 BSOD。第一个值用于确定数据包是否包含一些附加数据,而第二个值用于判断数据包的大小。

4.通过连接notifications实现堆栈泄漏

分析连接notifications的创建表明它们是由指定其类型的标头和基于它的联合组成的。这给出了一个 134 字节长的结构。创建notifications时,驱动程序使用堆栈上的临时notifications结构。然后它动态分配一块内存并用零来初始化。但是,它使用 XMM 寄存器从分配的内存中的堆栈复制临时结构。例如, _FLOW_DELETE_NOTIFICATION仅指示正在删除的流句柄,因此连接notifications长度为 24 字节。由于联合结构,134 个字节被分配和复制。换句话说,将 110 个额外字节从堆栈复制并泄漏到发送到用户空间的缓冲区。最重要的是,当完成 IRP 时,驱动程序指定大小为 134。以下两张图片显示了显示删除连接notifications的程序 ,浅蓝色框中是正在泄漏的堆栈上的地址,对应于实际的数据流删除回调。

imgimg

5.漏洞验证

为了评估漏洞是否真的可以被触发,我开发了一个 WinDbg Preview 脚本,此工具允许打开设备的句柄。

如前所述执行脚本后,可以使用一些选项启动该工具:

◼inject:将文件中的数据包注入到打开的连接中

◼notify : 显示实时连接notifications

◼bsod : 通过 IP 地址exclusion漏洞触发整数溢出

◼ipexclu:随机生成要排除的 IPv4 地址

inject和 notifications命令需要一些额外的步骤,在这两种情况下, EnableNetworkProtection选项都必须设置为 1 或 2,这可以通过使用管理员权限执行以下 PowerShell 命令来实现。

Set-MpPreference -EnableNetworkProtection 1

然后可以使用notifications命令运行该程序。例如,打开 Edge 浏览器时,应显示一长串notifications。

在连接内部测试数据包注入时,需要检索其流句柄和过滤器引擎callout ID。第二个可以通过 XML 文件检索,也可以像第一个一样通过实用程序的notifications命令检索 。

例如,假设被调试机器 A 想要向 HTTP 服务器 B 发出请求。在设置了notifications命令后,A 可以开始查询 B。可以在流建立连接notifications中检索流句柄。应该保持notifications命令运行,并打开另一个终端并使用带有检索到的参数的inject命令。

img

本文翻译自:https://blog.quarkslab.com/guided-tour-inside-windefenders-network-inspection-driver.html如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/OLWQ
如有侵权请联系:admin#unsafe.sh