windows 驱动实例分析系列: wintun驱动分析-driver篇(二)

📅 2026/8/18 13:05:45
windows 驱动实例分析系列: wintun驱动分析-driver篇(二)
一、驱动入口与 NDIS 注册在DriverEntry中除了填充NDIS_MINIPORT_DRIVER_CHARACTERISTICS外还特别注入了IRP_MJ_DEVICE_CONTROL、IRP_MJ_CLOSE、IRP_MJ_PNP的自定义处理函数。这些处理函数会在用户态调用DeviceIoControl或进程关闭文件句柄时被调用用于注册/注销环形缓冲区及清理。二、适配器初始化TunInitializeEx当 NDIS 需要启动一个 Wintun 适配器实例时调用此函数。流程详解分配上下文使用ExAllocatePoolZero在非分页池分配TUN_CTX并清零。保存 NDIS 句柄Ctx-MiniportAdapterHandle MiniportAdapterHandle。获取设备对象调用NdisMGetDeviceProperty取得FunctionalDeviceObject并将Ctx存入DeviceObject-Reserved以便后续 IRP 分发使用。初始化同步对象事件Device.Disconnected初始为 signaled表示未连接。自旋锁Device.Send.Lock和Device.Receive.Lock。事件Device.Receive.ActiveNbls.Empty初始 signaled。资源锁Device.RegistrationLock。分配 NBL 池调用NdisAllocateNetBufferListPool指定fAllocateNetBuffer TRUE池标签为wtun。设置 NDIS 属性填充NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES和NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES介质类型NdisMediumIP直接处理 IP 包无以太网头。MTU0xFFFF最大 IP 包大小。链路速度100 Gbps虚拟实际受用户态处理限制。支持 OID 列表包括统计、电源管理、链路参数等。电源管理能力不支持唤醒。标志允许意外移除。通过NdisMSetMiniportAttributes两次调用分别注册这两组属性。返回NDIS_STATUS_SUCCESS。此时适配器处于“已停止”状态Running 0初始值。三、暂停与重启3.1TunPause当 NDIS 要求暂停例如系统进入低功耗状态或正在卸载驱动时调用将Running置为 0使用WriteRelease。获取TransitionLock的独占锁并立即释放使所有正在进行的读路径持有共享锁能感知到Running的变化。等待Device.Receive.ActiveNbls.Empty事件确保所有已指示的接收 NBL 都已返回避免使用已释放的 MDL。返回NDIS_STATUS_SUCCESS。3.2TunRestart恢复运行时调用将Running置为 1WriteRelease。返回成功。四、适配器停止TunHaltEx当适配器被永久移除如用户手动删除设备时调用调用TunUnregisterBuffers强制注销TUN_FORCE_UNREGISTRATION断开连接、释放资源。再次获取TransitionLock独占锁确保所有读者已退出。释放 NBL 池。清空MiniportAdapterHandle和FunctionalDeviceObject-Reserved。删除资源锁Device.RegistrationLock。释放TUN_CTX内存。五、缓冲区的注册与注销5.1 注册TunRegisterBuffers由用户态DeviceIoControlTUN_IOCTL_REGISTER_RINGS触发该 IOCTL 最终会调用到此函数。核心步骤获取Device.RegistrationLock独占锁检查是否已有OwningFileObject若有则返回STATUS_ALREADY_INITIALIZED。从 IRP 的输入缓冲区解析TUN_REGISTER_RINGS结构支持 32/64 位跨进程。校验参数计算Capacity TUN_RING_CAPACITY(RingSize)必须介于 128 KiB 和 64 MiB 之间且为 2 的幂。检查事件句柄TailMoved是否有效并通过ObReferenceObjectByHandle获取内核事件对象指针用于驱动唤醒。锁定用户内存分配 MDL调用MmProbeAndLockPages将用户空间环形缓冲区锁定在物理内存中。使用MmGetSystemAddressForMdlSafe获取内核地址空间映射NonPagedPool且带MdlMappingNoExecute。读取初始 Tail 指针从用户环中读取当前 Tail 并保存到Ctx-Device.Send.RingTail。创建接收线程调用PsCreateSystemThread创建内核线程TunProcessReceiveData负责从接收环读取数据并转发给 NDIS。保存进程 IDCtx-Device.OwningProcessId PsGetCurrentProcessId()。加入全局设备列表获取TunDispatchDeviceListLock将Ctx-Device.Entry插入链表用于后续孤儿检测。指示链接状态为已连接调用TunIndicateStatus(MediaConnectStateConnected)。释放RegistrationLock返回成功。5.2 注销TunUnregisterBuffers由WintunEndSession或进程退出、设备移除等触发获取RegistrationLock独占锁确认OwningFileObject与调用者匹配若为强制注销忽略匹配。从全局设备列表中移除自身。指示链接状态为断开。置位Device.Disconnected事件并获取TransitionLock独占锁使所有读路径感知到断开。等待接收线程退出通过ObReferenceObjectByHandle获取线程对象KeWaitForSingleObject等待。关闭接收线程句柄。将发送环的Tail设为MAXULONG并触发Send.TailMoved事件让可能的等待者退出。解锁用户内存MmUnlockPages、释放 MDL、取消引用事件对象。清空OwningFileObject释放锁。六、进程退出通知TunProcessNotification注册了PsSetCreateProcessNotifyRoutine在进程退出时被回调如果Create FALSE进程退出则遍历全局设备列表查找OwningProcessId ProcessId的设备。若找到则调用TunUnregisterBuffers(TUN_FORCE_UNREGISTRATION)强制断开该进程持有的适配器避免资源泄漏。七、设备对象的 IRP 处理7.1TunDispatchDeviceControl拦截IRP_MJ_DEVICE_CONTROL仅处理TUN_IOCTL_REGISTER_RINGS其他交给 NDIS 默认处理。首先进行安全访问检查SeAccessCheck确保调用者有FILE_WRITE_DATA权限。获取TunDispatchCtxGuard共享锁保护DeviceObject-Reserved调用TunRegisterBuffers。7.2TunDispatchClose拦截IRP_MJ_CLOSE当用户态关闭设备句柄时触发获取TunDispatchCtxGuard共享锁调用TunUnregisterBuffers并传入当前FileObject。将原关闭处理函数NdisDispatchClose的结果返回。7.3TunDispatchPnp拦截IRP_MJ_PNP主要处理IRP_MN_QUERY_REMOVE_DEVICE和IRP_MN_SURPRISE_REMOVAL。在此函数中驱动会尝试强制关闭仍持有设备句柄的其他进程。具体做法获取RegistrationLock。如果当前存在OwningFileObject且不是本文件对象则枚举系统句柄表ZwQuerySystemInformation查找与OwningFileObject相同的对象句柄并关闭它们通过ObCloseHandle。这样做的目的是防止其他进程持有句柄导致设备无法移除。最终交给NdisDispatchPnp完成剩余 PnP 处理。八、总结本文档详细描述了驱动的初始化、暂停、重启、关闭、缓冲区注册与注销以及进程退出通知和 IRP 拦截。这些机制共同保证了适配器的生命周期管理、资源安全和跨进程共享的正确性特别是通过进程通知和句柄枚举有效避免了“僵尸适配器”问题。下一篇文章将聚焦最关键的数据路径——发送与接收。