首页
社区
课程
招聘
[原创]windows 虚拟网卡PNP安装流程调试
发表于: 2014-3-14 21:28 10183

[原创]windows 虚拟网卡PNP安装流程调试

2014-3-14 21:28
10183

XMIND文件是原始调试记录,请用XMIND打开,太大了,不好生成图片
具体看文档吧,没精力倒腾了 ,so 没完成,留给大家自行去发掘吧

doc.rar

Windows INF 文件安装过程调试
By [email]boywhp@126.com[/email]

最近对 NDIS inf 虚拟网卡驱动安装过程比较好奇,花了大概一周的时间研究了下 Windows 处理 INF安装的一些细节,整理成文档,以便以后参考。

一、虚拟网卡安装后操作系统重启后的启动流程

我直接使用了 WDK netvmini 虚拟网卡示例代码,首先看系统启动时是如何处理虚拟网卡驱动启动的。

用 devcon 安装上虚拟网卡驱动,代码入口点加上断点,重启系统。

Windbg 断点后堆栈如下:


直接 IDA 查看 PipDeviceActionWorker 相关代码,这是一个工作项执行函数



Ctrl+x 查看该函数的调用情况,发现 PipRequestDeviceAction 函数明显是该工作项函数的发起者,含义也很明显,就是请求执行具体设备动作。



如果请求 type = 9 或者 6 会直接执行 PipDeviceActionWorker 工作项函数,否则使用工作线程执行工作项函数,该函数调用情况如下(只关心启动过程调用的情况)。


IopInitializePlugPlayServices 调用该函数时都是使用了参数 type=9


所以可以最后一次调用明显是在 IopInitializeSystemDrivers 完成的,而且这个含义也比较符合上下文,初始化系统驱动嘛!IDA IopInitializeSystemDrivers 如下:


首先调用 PipRequestDeviceAction 请求设备 Action (type = 0x11),然后完成后再从注册表获取查询的系统驱动列表,下 CmGetSystemDriverList 断点,命中如下:


可见我们的驱动的确在 CmGetSystemDriverList 执行前就启动了:-)

看来我们的驱动并不是通过系统驱动注册表服务信息加载的,(type = 0x11)对应的处理函数是:PiProcessStartSystemDevices,该函数取出 DeviceNode 后,直接调用 PipProcessDevNodeTree


PipProcessDevNodeTree 函数是一个相对比较复杂的函数,字面含义就是处理设备节点树,Windows系统内核使用了设备节点(nt!_DEVICE_NODE)来处理 PNP 设备的层次关系,内核设备(device_object)和设备节点通过指针相互绑定,设备节点数据结构里面包含了很多设备的具体信息,部分字段如下。


PipProcessDevNodeTree 函数最终根据节点的状态,调用不同的处理函数完成设备树的处理,如下:



断点 PipCallDriverAddDevice,命中如下:



这正是我们期望看到的,有了这些信息内核就可以找到具体的驱动并加载。

现 在 我 们 关 心 的 是 内 核 在 什 么 时 候 将 设 备 节 点 的 状 态 从 DeviceNodeUninitialized 变 成DeviceNodeInitialized 的,Windows 内核使用 PipSetDevNodeState 实现对设备节点状态设置。



直接条件断点,


命中堆栈如下:



我们发现在 IoInitSystem->IopInitializePlugPlayServices 时即完成了设备节点的创建已经状态设置了。

具体是在 PipEnumerateDevice 函数,枚举设备的时候完成了设备节点的初始化,具体如下。


如果当前设备已经启动了,继续枚举当前设备的子设备。


IopQueryDeviceRelations 函数直接填了IRP_MJ_PNP/IRP_MN_QUERY_DEVICE_RELATIONS 发送给具体的设备。



我们的虚拟网卡设备被没有真实的总线枚举器,直接指向了 nt!IopPnPDispatch 函数,具体的初始化是在 PipPnPDriverEntry 函数。


对应的驱动对象初始化位于 IopInitializePlugPlayServices 函数,具体如下:


显然 Windows 系统直接内置了 Pnp 管理的功能,IoPnpDriverObject 对象代表了系统的 Pnp 管理驱动,PipAllocateDeviceNode 函数完成设备节点创建,虚拟网卡驱动必然也需要创建一个 Pnp 设备和一个设备节点。IDA 查找 IoPnpDriverObject 以及 PipAllocateDeviceNode 调用情况。


结合先前的命中堆栈(PiInitializeDevice 暂时先略过),我们在IopInitializeDeviceInstanceKey 函数找到对应的关键代码:


然后创建设备节点,并初始化


读取注册表值 ConfigFlags,


如果 ConfigFlags 标志不是失败的话(正常情况下是 0)


继续读取 NoResourceAtInitTime 键值以及设备 Capabilities


通知设备来到并注册设备服务名


PpDeviceRegistration 函数根据 InstancePath(ROOT\NET\0000)调用 PiDeviceRegistration 函数从注册表 CurrentControlSet\Enum\InstacePath 获取服务名,最终写入 DeveiceNode->ServiceName,如下:


可见 IopInitializeDeviceInstanceKey 函数完成了实际的设备节点的创建,以及注册表信息读取,同时将信息保存到创建的设备节点里面去,最终 PiProcessStartSystemDevices-> PipProcessDevNodeTreeb调用 PipCallDriverAddDevice(State = DeviceNodeInitialized),PipCallDriverAddDevice 函数查询Service 注册表值,执行 PipCallDriverAddDeviceQueryRoutine 函数



至此我们大致对虚拟网卡驱动开机驱动加载的一个流程基本搞清楚了,总结一下就是:

1、IoInitSystem 时 IopInitializePlugPlayServices 初始化 Pnp 管理器

2、创建\\Driver\\PnpManager 驱动对象,IRP_MJ_PNP -> IopPnPDispatch

3、创建 Root 设备节点 IopRootDeviceNode (HTREE\\ROOT\\0,state = DeviceNodeStarted

4、PipRequestDeviceAction TYPE = 9

5、执行 TYPE 9 设备枚举函数 PiProcessReenumeration

6、最终调用 PipEnumerateDevice 执行具体的设备枚举,IRP

7、PipEnumerateDevice 调用 IopPnPDispatch (minorFunction = IRP_MN_QUERY_DEVICE_RELATIONS)

8、调用 IopGetRootDevices 获取 Pnp 根设备,打开 CurrentControlSet\Enum\Root 键值开始枚举

9、将枚举到的注册表键值依次调用 IopInitializeDeviceKey 依次完成具体的设备节点初始化。

10、IoInitSystem 此后执行 IopInitializeSystemDrivers,PipRequestDeviceAction TYPE=11

11、在工作项线程中,执行 PiProcessStartSystemDevices 函数,遍历先前创建的 DevNode 树

12、节点状态 DeviceNodeInitialized -> PipCallDriverAddDevice 然后你懂的。

二、虚拟网卡 PNP 管理器即时启动驱动流程

前面分析我们发现 PiInitializeDevice 函数也会类似创建设备节点以及 PNP 设备,直接从这个函数入手,
直接断点,命中如下:


umpnpmgr.dll 是 windows 系统 Ring3 下 PNP 管理器组件,以服务模式运行于 services 进程。通过NtPlugPlayControl 系统调用同内核 PNP 管理器进行协同。



NtPlugPlayControl 函数首先会检查 Tcb 权限,然后通过参数 1 获取调用的函数索引号,根据内核
PlugPlayHandlerTable 分发表执行具体的功能函数,具体如下:


umpnpmgr.dll 提供了 RPC 服务调用接口,命中堆栈也印证了这一点,对应的 RPC 服务接口如下:


GUID 对应 8d9f4e40-a03d-11ce-8f69-08003e30051b 正是 PNP 调用的接口,对于特定 GUID 的RPC 服务调用者可以通过如下条件断点:


命中堆栈如下:


正是 devcon.exe 安装 ndis 驱动时调用 SETUPAPI!PNP_CreateDevInst 的 RPC 客户端接口,函数名称都一模一样,SETUPAPI 对这个 PNP RPC 接口进行了二次封装,整了一套 API,这也符合微软的一贯作风,基本流程是:

SETUPAPI->RPC 客户函数->RPC 调用->RPC 服务(services!umpnpmgr.dll)->NtPlugPlayControl->内核PNP 管理器。

我们继续看 PiInitializeDevice 函数,该函数首先打开 CurrentControlSet\Enum 注册表键(该键下面记录了 Windows 系统下所有 PNP 设备信息):


创建或者打开应用层传递的 InstancePath 路径(设备路径\ROOT\NET\0000)


检查 ConfigFlags 以及 Service 键内容


创建 PNP 设备以及对应的设备节点



插入根设备节点树并发出 GUID_DEVICE_ENUMERATED PNP 消息


Windows 处理 PNP 消息的大致流程如下:

1、PpSetPlugPlayEvent 申请一个 PNP_EVENT_BLOCK 数据结构,并填写好对应的 Event 信息。

2、PiInsertEventInQueue 将该 Pnp 消息插入 PpDeviceEventList 链表。

3、PiWalkDeviceList 工作项里,依次从队列取出 PNP 消息,根据消息类型直接对应处理。

4、PiNotifyUserMode 将事件通知用户模式 PNP 管理器,等待用户模式。

5、用户模式 PNP 管理器调用 NtGetPlugPlayEvent 准备内核 PNP 管理器消息。

6、PiNotifyUserMode 将 PNP 消息信息记录到 PpUserBlock。

7、NtGetPlugPlayEvent 根据 PpUserBlock 的指针将 PNP 消息取走。

Windbg 断点 nt!NtPlugPlayControl,依次命中序列如下:

注:本帖由看雪论坛志愿者PEstone 重新将PDF整理排版,若和原文有出入,以原作者附件为准


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 5
支持
分享
最新回复 (2)
雪    币: 8835
活跃值: (2404)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
2
你真疯狂~
2014-3-15 03:25
0
雪    币: 1233
活跃值: (907)
能力值: ( LV12,RANK:750 )
在线值:
发帖
回帖
粉丝
3
人不疯狂枉少年啊,可惜老了
2014-3-15 17:46
0
游客
登录 | 注册 方可回帖
返回
//