Windows通知功能(WNF)妙用
字数 1355 2025-08-27 12:33:31

Windows通知功能(WNF)深入解析与实战应用

1. WNF概述

Windows通知功能(WNF)是一个内核组件,用于在系统中分发通知。它既可以在内核模式中使用,也可以在用户空间中使用,包含一组导出但未公开文档的API函数和相关数据结构。

1.1 WNF核心概念

  • 状态名(StateName): 标识特定类型的事件
  • 状态数据(StateData): 与通知关联的数据
  • 订阅(Subscription): 组件对特定事件的注册
  • 作用域(Scope): 决定通知的可见范围

1.2 WNF特点

  • 支持内核和用户空间使用
  • 通知可以关联数据
  • 支持多种作用域级别
  • 部分状态数据可以持久化

2. WNF核心数据结构

2.1 主要结构体

typedef struct _WNF_NAME_INSTANCE {
    WNF_CONTEXT_HEADER Header;  // 结构头(类型码0x903)
    // 其他成员...
} WNF_NAME_INSTANCE, *PWNF_NAME_INSTANCE;

typedef struct _WNF_SCOPE_INSTANCE {
    WNF_CONTEXT_HEADER Header;  // 结构头(类型码0x902)
    // 其他成员...
} WNF_SCOPE_INSTANCE, *PWNF_SCOPE_INSTANCE;

typedef struct _WNF_SUBSCRIPTION {
    WNF_CONTEXT_HEADER Header;  // 结构头(类型码0x905)
    // 其他成员...
} WNF_SUBSCRIPTION, *PWNF_SUBSCRIPTION;

typedef struct _WNF_STATE_DATA {
    WNF_CONTEXT_HEADER Header;  // 结构头(类型码0x904)
    // 其他成员...
} WNF_STATE_DATA, *PWNF_STATE_DATA;

2.2 作用域类型

typedef enum _WNF_DATA_SCOPE {
    WnfDataScopeSystem = 0x0,   // 系统范围
    WnfDataScopeSession = 0x1, // 会话范围
    WnfDataScopeUser = 0x2,    // 用户范围
    WnfDataScopeProcess = 0x3, // 进程范围
    WnfDataScopeMachine = 0x4, // 机器范围
} WNF_DATA_SCOPE;

2.3 WNF状态名解码

WNF状态名以不透明格式存储,可通过以下方式解码:

#define WNF_XOR_KEY 0x41C64E6DA3BC0074

ClearStateName = StateName ^ WNF_XOR_KEY;
Version = ClearStateName & 0xf;
LifeTime = (ClearStateName >> 4) & 0x3;
DataScope = (ClearStateName >> 6) & 0xf;
IsPermanent = (ClearStateName >> 0xa) & 0x1;
Unique = ClearStateName >> 0xb;

对应的结构体定义:

typedef struct _WNF_STATE_NAME_INTERNAL {
    ULONG64 Version : 4;      // 版本号(4位)
    ULONG64 Lifetime : 2;    // 生命周期(2位)
    ULONG64 DataScope : 4;   // 数据作用域(4位)
    ULONG64 IsPermanent : 1; // 是否持久化(1位)
    ULONG64 Unique : 53;     // 唯一标识(53位)
} WNF_STATE_NAME_INTERNAL, *PWNF_STATE_NAME_INTERNAL;

3. WNF核心API解析

3.1 ExSubscribeWnfStateChange

NTSTATUS ExSubscribeWnfStateChange(
    _Out_ptr_ PWNF_SUBSCRIPTION* Subscription,
    _In_ PWNF_STATE_NAME StateName,
    _In_ ULONG DeliveryOption,
    _In_ WNF_CHANGE_STAMP CurrentChangeStamp,
    _In_ PWNF_CALLBACK Callback,
    _In_opt_ PVOID CallbackContext
);

功能:注册一个新的WNF订阅,当指定状态名对应的事件发生时调用回调函数。

内部流程

  1. 解码StateName
  2. 解析或创建作用域实例(WNF_SCOPE_INSTANCE)
  3. 查找或创建名称实例(WNF_NAME_INSTANCE)
  4. 创建订阅对象(WNF_SUBSCRIPTION)
  5. 将订阅添加到挂起队列并触发通知

3.2 ExQueryWnfStateData

NTSTATUS ExQueryWnfStateData(
    _In_ PWNF_SUBSCRIPTION Subscription,
    _Out_ PWNF_CHANGE_STAMP ChangeStamp,
    _Out_ PVOID OutputBuffer,
    _Out_ PULONG OutputBufferSize
);

功能:查询与订阅关联的状态数据。

内部流程

  1. 从订阅中获取名称实例
  2. 读取关联的状态数据
  3. 将数据复制到输出缓冲区

4. WNF实战应用

4.1 查找WNF状态名

WNF状态名可以从以下位置获取:

  1. contentDeliveryManager_Utilities.dll中的WNF状态名表
  2. perf_nt_c.dll中的类似表
  3. 博客和文档中公开的已知状态名

例如,麦克风使用事件的状态名:

  • 名称: WNF_AUDC_CAPTURE
  • 值: 0x2821B2CA3BC4075
  • 描述: "Reports the number of, and process ids of all applications currently capturing audio"

4.2 编写WNF驱动程序

4.2.1 订阅WNF事件

NTSTATUS CallExSubscribeWnfStateChange(VOID) {
    PWNF_SUBSCRIPTION wnfSubscription = NULL;
    WNF_STATE_NAME stateName;
    NTSTATUS status;
    
    stateName.Data = 0x2821B2CA3BC4075; // WNF_AUDC_CAPTURE
    
    status = ExSubscribeWnfStateChange(
        &wnfSubscription, 
        &stateName, 
        0x1, 
        NULL, 
        &notifCallback, 
        NULL);
    
    if (NT_SUCCESS(status))
        DbgPrint("Subscription address: %p\n", wnfSubscription);
    
    return status;
}

4.2.2 定义回调函数

NTSTATUS notifCallback(
    _In_ PWNF_SUBSCRIPTION Subscription,
    _In_ PWNF_STATE_NAME StateName,
    _In_ ULONG SubscribedEventSet,
    _In_ WNF_CHANGE_STAMP ChangeStamp,
    _In_opt_ PWNF_TYPE_ID TypeId,
    _In_opt_ PVOID CallbackContext
) {
    NTSTATUS status = STATUS_SUCCESS;
    ULONG bufferSize = 0x0;
    PVOID pStateData = NULL;
    WNF_CHANGE_STAMP changeStamp = 0;
    
    // 第一次调用获取所需缓冲区大小
    status = ExQueryWnfStateData(Subscription, &changeStamp, NULL, &bufferSize);
    if (status != STATUS_BUFFER_TOO_SMALL)
        goto Exit;
    
    // 分配缓冲区
    pStateData = ExAllocatePoolWithTag(PagedPool, bufferSize, 'LULZ');
    if (pStateData == NULL) {
        status = STATUS_UNSUCCESSFUL;
        goto Exit;
    }
    
    // 第二次调用获取实际数据
    status = ExQueryWnfStateData(Subscription, &changeStamp, pStateData, &bufferSize);
    if (NT_SUCCESS(status)) {
        // 处理数据...
        DbgPrint("## Data processed: %S\n", pStateData);
    }
    
Exit:
    if (pStateData != NULL)
        ExFreePoolWithTag(pStateData, 'LULZ');
    return status;
}

4.2.3 卸载时清理

PVOID ExUnsubscribeWnfStateChange(
    _In_ PWNF_SUBSCRIPTION Subscription
);

VOID DriverUnload(
    _In_ PDRIVER_OBJECT DriverObject
) {
    ExUnsubscribeWnfStateChange(g_WnfSubscription);
}

5. WNF状态名收集工具

可以使用以下Python脚本收集和比较WNF状态名:

import argparse

def main():
    parser = argparse.ArgumentParser(description='Stupid little script to dump or diff wnf name table from dll')
    parser.add_argument('file1', help='First file')
    parser.add_argument('file2', nargs='?', help='Second file (only used when diffing)')
    parser.add_argument('-dump', action='store_true', help='Dump the table into a file')
    parser.add_argument('-diff', action='store_true', help='Diff two tables and dump the difference into a file')
    parser.add_argument('-v', '--verbose', action='store_true', help='Print the description of the keys')
    parser.add_argument('-o', '--output', default='output.txt', help='Output file (Default: output.txt)')
    parser.add_argument('-py', '--python', action='store_true', help="Change the output language to python (by default it's c)")
    args = parser.parse_args()
    
    # 实现代码...

if __name__ == '__main__':
    main()

输出示例(C格式):

typedef struct _WNF_NAME {
    PCHAR Name;
    ULONG64 Value;
} WNF_NAME, *PWNF_NAME;

WNF_NAME g_WellKnownWnfNames[] = {
    {"WNF_A2A_APPURIHANDLER_INSTALLED", 0x41877c2ca3bc0875},
    {"WNF_AAD_DEVICE_REGISTRATION_STATUS_CHANGE", 0x41820f2ca3bc0875},
    {"WNF_AA_CURATED_TILE_COLLECTION_STATUS", 0x41c60f2ca3bc1075},
    {"WNF_AA_LOCKDOWN_CHANGED", 0x41c60f2ca3bc0875},
    // 更多条目...
};

6. 总结与注意事项

  1. WNF的强大功能

    • 可以监控系统各种事件
    • 既可用于监控也可用于通信
    • 支持多种作用域级别
  2. 开发注意事项

    • WNF API未公开文档,使用时需自行声明
    • 确保正确处理缓冲区大小
    • 驱动程序卸载时必须取消订阅
  3. 调试技巧

    • 使用结构头中的NodeTypeCode识别WNF结构
    • 可以通过nt!ExpWnfProcessesListHead遍历进程上下文
  4. 应用场景

    • 监控系统事件(如应用启动、麦克风使用)
    • 实现进程间通信
    • 系统状态监控工具开发

通过深入理解WNF的内部机制和数据结构,开发者可以构建强大的系统监控和通知功能,但需要注意WNF API的未公开特性可能在不同Windows版本中有所变化。

Windows通知功能(WNF)深入解析与实战应用 1. WNF概述 Windows通知功能(WNF)是一个内核组件,用于在系统中分发通知。它既可以在内核模式中使用,也可以在用户空间中使用,包含一组导出但未公开文档的API函数和相关数据结构。 1.1 WNF核心概念 状态名(StateName) : 标识特定类型的事件 状态数据(StateData) : 与通知关联的数据 订阅(Subscription) : 组件对特定事件的注册 作用域(Scope) : 决定通知的可见范围 1.2 WNF特点 支持内核和用户空间使用 通知可以关联数据 支持多种作用域级别 部分状态数据可以持久化 2. WNF核心数据结构 2.1 主要结构体 2.2 作用域类型 2.3 WNF状态名解码 WNF状态名以不透明格式存储,可通过以下方式解码: 对应的结构体定义: 3. WNF核心API解析 3.1 ExSubscribeWnfStateChange 功能 :注册一个新的WNF订阅,当指定状态名对应的事件发生时调用回调函数。 内部流程 : 解码StateName 解析或创建作用域实例(WNF_ SCOPE_ INSTANCE) 查找或创建名称实例(WNF_ NAME_ INSTANCE) 创建订阅对象(WNF_ SUBSCRIPTION) 将订阅添加到挂起队列并触发通知 3.2 ExQueryWnfStateData 功能 :查询与订阅关联的状态数据。 内部流程 : 从订阅中获取名称实例 读取关联的状态数据 将数据复制到输出缓冲区 4. WNF实战应用 4.1 查找WNF状态名 WNF状态名可以从以下位置获取: contentDeliveryManager_Utilities.dll 中的WNF状态名表 perf_nt_c.dll 中的类似表 博客和文档中公开的已知状态名 例如,麦克风使用事件的状态名: 名称: WNF_AUDC_CAPTURE 值: 0x2821B2CA3BC4075 描述: "Reports the number of, and process ids of all applications currently capturing audio" 4.2 编写WNF驱动程序 4.2.1 订阅WNF事件 4.2.2 定义回调函数 4.2.3 卸载时清理 5. WNF状态名收集工具 可以使用以下Python脚本收集和比较WNF状态名: 输出示例(C格式): 6. 总结与注意事项 WNF的强大功能 : 可以监控系统各种事件 既可用于监控也可用于通信 支持多种作用域级别 开发注意事项 : WNF API未公开文档,使用时需自行声明 确保正确处理缓冲区大小 驱动程序卸载时必须取消订阅 调试技巧 : 使用结构头中的NodeTypeCode识别WNF结构 可以通过nt !ExpWnfProcessesListHead遍历进程上下文 应用场景 : 监控系统事件(如应用启动、麦克风使用) 实现进程间通信 系统状态监控工具开发 通过深入理解WNF的内部机制和数据结构,开发者可以构建强大的系统监控和通知功能,但需要注意WNF API的未公开特性可能在不同Windows版本中有所变化。