windows 驱动实例分析系列: HidHide 驱动分析 - HidHide 篇(四)
2026/9/15 9:56:19 网站建设 项目流程

HidHide 驱动分析 - HidHide 篇(四):日志系统与数据结构自检

一、ETW 日志与追踪系统(Logging.c/h)

HidHide 驱动利用Windows 事件跟踪(ETW)提供全面的诊断能力。日志系统分为两个独立的提供者:

  • 日志提供者(Logging Provider)Nefarius_Hid_Hide,记录关键事件(如驱动启用/禁用、白名单命中、异常)。
  • 追踪提供者(Tracing Provider)Nefarius_Drivers_HidHide,记录详细执行路径,支持性能分析和深度调试。

1.1 提供者注册与注销

NTSTATUSLogRegisterProviders(){ntstatus=EventRegisterNefarius_Hid_Hide();// 日志提供者ntstatus=EventRegisterNefarius_Drivers_HidHide();// 追踪提供者LogEvent(ETW(Started),L"%s",_L(BldProductVersion));// 记录启动事件returnSTATUS_SUCCESS;}NTSTATUSLogUnregisterProviders(){LogEvent(ETW(Stopped),L"");EventUnregisterNefarius_Drivers_HidHide();EventUnregisterNefarius_Hid_Hide();returnSTATUS_SUCCESS;}

_L(BldProductVersion)是编译期注入的版本号,每次驱动加载时记录,便于版本追溯。

1.2 日志消息的写入

LogEvent是核心日志函数,支持格式化输出:

NTSTATUSLogEvent(...,PCEVENT_DESCRIPTOR event,PCWSTR format,...){WCHAR buffer[LOGGING_MESSAGE_MAXIMUM_SIZE];va_start(args,format);RtlStringCchVPrintfW(&buffer[0],_countof(buffer),format,args);// 写入日志提供者LogWriteTransfer(&EtwProviderLogging_Context,event,fileName,lineNumber,functionName,&buffer[0],"");// 同时写入追踪提供者(使用相同消息)LogWriteTransfer(&EtwProviderTracing_Context,&eventDescriptor,fileName,lineNumber,functionName,&buffer[0],"");returnSTATUS_SUCCESS;}

这种"一次日志,双重输出"的策略确保了日志事件同时出现在系统事件日志和跟踪日志中,方便管理员从不同视角查看。

1.3 追踪宏

#defineTRACE_PERFORMANCE(message){TraceEvent(...,&EtwEventTracePerformance,message,"");}#defineTRACE_ALWAYS(message){TraceEvent(...,&EtwEventTraceAlways,message,"");}#defineTRACE_DETAILED(message){TraceEvent(...,&EtwEventTraceDetailed,message,"");}

三种追踪级别对应不同的 ETW 事件关键字,管理员可通过wevtutil工具动态调整启用级别,无需重启驱动。

1.4 调试输出与日志的联动

TRACE_XXX宏在写入 ETW 的同时,若EtwEventTraceDebugging关键字被启用,还会调用DbgPrintEx输出到调试器(WinDbg / DebugView):

if(MCGEN_EVENT_ENABLED(EtwEventTraceDetailed)&&MCGEN_EVENT_ENABLED(EtwEventTraceDebugging))DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"%s(%d) %s\n",fileName,lineNumber,functionName);

这在驱动开发阶段极其有用,开发者无需借助 ETW 查看器即可实时看到执行路径。

二、数据结构自检机制

2.1 二叉搜索树自检

HidHideVerifyInternalConsistency在驱动加载时执行,测试 BST 的完整功能:

ULONG s_testPattern[]={5,11,15,10,8,9,3,4,1,2};NTSTATUSHidHideVerifyInternalConsistency(){// 1. 插入测试序列for(index=0;index<_countof(s_testPattern);index++){BstNewNode(s_testPattern[index],&dummy,&node);BstInsert(&tree,node);// 验证查找成功ntstatus=((NULL!=BstLookup(tree,s_testPattern[index]))?STATUS_SUCCESS:STATUS_UNSUCCESSFUL);}// 2. 验证左右子树大小BstAddCount(tree->left,&count);// 应为 4BstAddCount(tree->right,&count);// 应为 5// 3. 删除根节点(5)BstDelete(&tree,5);// 4. 验证删除后左右子树大小BstAddCount(tree->left,&count);// 应为 7(合并了原右子树的一部分)BstAddCount(tree->right,&count);// 应为 1// 5. 清理树BstCleanup(&tree);returnSTATUS_SUCCESS;}

若任何检查失败,驱动加载将中止,防止运行时出现不可预期的数据损坏。

2.2 内存池标签

驱动使用统一的内存池标签CONFIG_TAG'1gaT')和LOGIC_TAG'2gaT'),便于在 Driver Verifier 和 PoolMon 工具中追踪内存泄漏。

temp=ExAllocatePoolZero(NonPagedPool,sizeof(*temp),CONFIG_TAG);

使用NonPagedPool确保 BST 节点始终驻留在非分页内存中,可在任意 IRQL 安全访问。

三、同步机制(WDFWAITLOCK)

驱动使用WdfWaitLock(基于FAST_MUTEX的内核锁)保护共享数据结构:

WDFWAITLOCK s_criticalSectionLock=NULL;// 获取锁WdfWaitLockAcquire(s_criticalSectionLock,NULL);// 临界区操作BstLookup(...);// 释放锁WdfWaitLockRelease(s_criticalSectionLock);

WdfWaitLockAcquire的第二个参数为NULL,表示无限等待,这简化了错误处理,但要求锁持有时间极短,避免死锁。

所有对 BST 的访问(插入、查找、删除、缓存刷新)都在锁保护下进行。HidHideProcessIdCheckFullImageNameAgainstWhitelist中虽然持有锁,但在遍历集合(WdfCollectionGetCount)时不持有锁,因为集合本身在遍历时不会被修改(修改操作会先创建新集合再交换指针)。

四、设备实例路径获取

HidHideDeviceInstancePath通过 WDF 属性 API 获取设备的唯一标识符:

NTSTATUSHidHideDeviceInstancePath(WDFDEVICE wdfDevice,WDFSTRING*deviceInstancePath){WDF_DEVICE_PROPERTY_DATA_INIT(&wdfDevicePropertyData,&DEVPKEY_Device_InstanceId);WdfDeviceAllocAndQueryPropertyEx(wdfDevice,&wdfDevicePropertyData,NonPagedPool,&wdfObjectAttributes,&wdfMemory,&devPropType);// 复制字符串ntstatus=WdfStringCreate(&path,&wdfObjectAttributes,deviceInstancePath);returnSTATUS_SUCCESS;}

设备实例路径的格式为HID\VID_xxxx&PID_yyyy\...,是 PnP 管理器分配的唯一标识,驱动用其在黑名单中精确匹配设备。

五、异常处理与错误传播

驱动代码广泛使用LOG_AND_RETURN_NTSTATUS宏,将错误信息记录到 ETW 后返回 NTSTATUS 码:

#defineLOG_AND_RETURN_NTSTATUS(message,result){\LogEvent(ETW(Exception),L"%s reports NT status 0x%08X",message,result);\return(result);\}

这种模式确保了任何 API 失败都会被记录,便于事后分析故障根因。

六、驱动生命周期完整性总结

DriverEntryEvtDriverUnload,驱动的生命周期管理非常严谨:

  1. 加载阶段:注册 ETW → 创建控制设备 → 注册进程/镜像回调 → 自检通过。
  2. 运行阶段OnDeviceFileCreate拦截请求,OnControlDeviceIoSetXxx处理配置变更,进程回调维护 BST 缓存。
  3. 关机阶段OnSystemShutdown注销系统回调,刷新缓存,减少引用计数。
  4. 卸载阶段EvtDriverUnload注销 ETW 提供者。

这种设计确保了驱动在任何情况下(包括异常关机)都能尽可能干净地释放资源,配合用户态 Watchdog 的注册表清理机制,构成了完整的故障自愈闭环。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询