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订阅,当指定状态名对应的事件发生时调用回调函数。
内部流程:
- 解码StateName
- 解析或创建作用域实例(WNF_SCOPE_INSTANCE)
- 查找或创建名称实例(WNF_NAME_INSTANCE)
- 创建订阅对象(WNF_SUBSCRIPTION)
- 将订阅添加到挂起队列并触发通知
3.2 ExQueryWnfStateData
NTSTATUS ExQueryWnfStateData(
_In_ PWNF_SUBSCRIPTION Subscription,
_Out_ PWNF_CHANGE_STAMP ChangeStamp,
_Out_ PVOID OutputBuffer,
_Out_ PULONG OutputBufferSize
);
功能:查询与订阅关联的状态数据。
内部流程:
- 从订阅中获取名称实例
- 读取关联的状态数据
- 将数据复制到输出缓冲区
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事件
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,
¬ifCallback,
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. 总结与注意事项
-
WNF的强大功能:
- 可以监控系统各种事件
- 既可用于监控也可用于通信
- 支持多种作用域级别
-
开发注意事项:
- WNF API未公开文档,使用时需自行声明
- 确保正确处理缓冲区大小
- 驱动程序卸载时必须取消订阅
-
调试技巧:
- 使用结构头中的NodeTypeCode识别WNF结构
- 可以通过nt!ExpWnfProcessesListHead遍历进程上下文
-
应用场景:
- 监控系统事件(如应用启动、麦克风使用)
- 实现进程间通信
- 系统状态监控工具开发
通过深入理解WNF的内部机制和数据结构,开发者可以构建强大的系统监控和通知功能,但需要注意WNF API的未公开特性可能在不同Windows版本中有所变化。