一个驱动无法加载的分析
客户反馈一个问题,原工作很好的usb key设备,安装
NCT_2000_XP
后,运行测试程序找硬件,提示没找到。检查系统
%systemroot%/system32/dirvers
目录,驱动文件安详的躺在那里,
%systemroot%/inf
下也有安装的
inf
文件,设备管理器里看到设备工作正常,这真邪门了。
为何usb key
驱动加载不了呢?此软件有管理
usb
设备的功能,但
U
盘能正常使用,先用
Total Uninstall
对比了此软件安装前后对注册表的影响
,
未果,(后面还用了
Regsnap
)。
毫不犹豫,想到了先跟踪测试程序,看看哪里出错了。先说说该硬件驱动的架构,分二层,
usb
层之上还有一层,负责接收用户具体的请求,把请求解密后再下发给
usb
驱动,此层包含在
upper.sys
文件里,
usb
层由
usb.sys
负责再按照
usb1.1
协议发给硬件。
那就是说,要正常工作必需要加载这二个驱动,缺一不可,为了验证想法的正确性,卸掉
NCT_2000_XP
重装驱动,拔掉
Usb Key
,重启,打开
icesword
看内核模块,找到了
upper.sys
模块,这时插入
Usb Key
,运行测试程序,正常。
且慢,
upper.sys
为何开机就加载了,看注册表,该驱动如下:
名称
|
值
|
说明
|
Type
|
1
|
驱动程序的种类
|
Start
|
3
|
驱动程序的起始启动时间
|
ErrorControl
|
1
|
驱动装入失败的错误处理
|
Group
|
Extended Base
|
驱动程序的组名
|
DependOnGroup
|
|
所依赖的其他驱动程序
|
Tag
|
21
|
同组内驱动程序装入顺序
|
|
|
|
Start
各值含义如下:
|
|
|
0x0 (SERVICE_BOOT_START)
:这个值指定本驱动程序应该由操作系统装入程序启动。一般的驱动程序不会采用本值,因为系统在这个时候几乎还没有启动,大部分系统尚不可用。
0x1 (SERVICE_SYSTEM_START) :该值表示在操作系统装入后但同时初始化它自己时启动驱动程序。
0x2 (SERVICE_AUTO_START) :该值表示在整个系统启动并运行后由服务控制管理器( SCM )装入。
0x3 (SERVICE_DEMAND_START) :该值表示该驱动程序必须手工启动。可以通过控制面板的设备 applet 或者使用 WIN32 API 编程来启动。
0x1 (SERVICE_SYSTEM_START) :该值表示在操作系统装入后但同时初始化它自己时启动驱动程序。
0x2 (SERVICE_AUTO_START) :该值表示在整个系统启动并运行后由服务控制管理器( SCM )装入。
0x3 (SERVICE_DEMAND_START) :该值表示该驱动程序必须手工启动。可以通过控制面板的设备 applet 或者使用 WIN32 API 编程来启动。
0x4 (SERVICE_DISABLED)
:表示本驱动程序被禁用。
0
,
1
,
2
都由系统加载,时机稍有差别,另一个文档如此说的,个人觉得很对:
0
由
ntldr
加载,
1
由
IO
管理器加载,
2
系统启动后由
SCM
自动加载
那我这里
upper.sys
是
3
,谁把它加载的呀?想不清,看官是不是和在下一样迷茫呢?什么你知道,你厉害,水平比俺高呢,反正我当时是不知道哩。不过后面弄明白了(正确与否,还请指正),小疑问一冒出水面。欲知为何,请看下文。
带着疑问,继续往下走吧。回到前面的安装了
NCT_2000_XP
的测试状态,用
icesword
观看内核模块,没找到
upper.Sys
,你小子不让我加载是把,那我通过
SCM
主动加载,看你如何为难我,大体代码如下
scm
=
OpenSCManager
(
NULL
,
NULL
,
SC_MANAGER_ALL_ACCESS
);
if (
scm
==
NULL
)
return(
ERR_UNKNOWN
);
sc
=
OpenService
(
scm
,
sysname
,
SERVICE_ALL_ACCESS
);
if (
sc
==
NULL
)
{
printf
(
"/nOpenService error:%d /n"
,
GetLastError
());
retcode
=
ERR_UNKNOWN
;
}
if (
StartService
(
sc
, 0,
NULL
) == 0)
{
retcode
=
ERR_UNKNOWN
;
printf
(
"error:%d /n"
,
GetLastError
());
}
执行后
StartService
失败了,错误为驱动无法加载。看看驱动代码为何把,
debugview
输出信息,发现驱动运行到了
Driverentry
,但
Adddevice
没执行就运行到
DriverUnload
,退出了,百思不得其解呀。谁发出了
Unload
的
IRP
,那个进程发的?莫非是安装的那个软件?又是一个小疑问,姑且称为疑问二吧。看官你说啥原因呢?
我找进程,如何找?搜了一把,网上来段代码,很多,原理就是调用
PsGetCurrentProcess
得到
EProcess,
再定位得到进程映射路径,或者用
PsGetCurrentProcessId
。代码先放在这里,以后可能有用。
PCWSTR
GetCurrentProcessFileName()
{
DWORD
dwAddress
= (
DWORD
)
PsGetCurrentProcess
();
if(
dwAddress
== 0 ||
dwAddress
== 0xFFFFFFFF)
{
return
NULL
;
}
dwAddress
+= 0x1B0;
if((
dwAddress
= *(
DWORD
*)
dwAddress
) == 0) return 0;
dwAddress
+= 0x10;
if((
dwAddress
= *(
DWORD
*)
dwAddress
) == 0) return 0;
dwAddress
+= 0x3C;
if((
dwAddress
= *(
DWORD
*)
dwAddress
) == 0) return 0;
KdPrint
((
"Current Process Full Path Name: %ws/n"
, (
PCWSTR
)
dwAddress
));
return (
PCWSTR
)
dwAddress
;
}
里面有些硬编码,不同系统不一样。我这里针对
XPsp2
。其余系统用
windbg
跟踪便可知。
运行后,得到了
EProcess
,但得不到进程执行文件路径,反过来一想,好像就算得到了也没用。此路不通,手动加载不了,那开机系统加载行否呢?
把该驱动注册表中
start
改
1
后,重启依然不行。看来只能深入系统加载驱动过程了,这样就明白了。
动用
windbg
吧,还好有二台机器
,
一台作为被调试机,运行被调试程序
upper.Sys
,一台主控机运行
windbg
,不需要虚拟机来搭建调试环境。
1.
用串口线连接
2
台机器,用附件里的超级终端可相互发消息,确认连接正确。
2.
改变被调试机
boot.ini
文件启动设置。
添加一条目
multi(0)disk(0)rdisk(0)partition(5)/WINDOWS="Microsoft Windows XP Professional " /FASTDETECT /debug /debugport=com1 /baudrate=115200
3.
保存修改后,重启被调试机,同时主控机打开
windbg
,选菜单
File
,
kernel debug,
在弹出的对话框中选第一个标签页面。
Baud rate
写上
115200
,
port
为
com1
,确定。这时候
windbg
进入等待连接状态。为了在操作系统启动时尽早中断
,
按热键
ctrl+alt+K
,在下次启动时,在
ntoskrnl
加载之后的一小段时间,这时所有驱动还没有被加载,操作系统将会挂起,而
WinDbg
将会取得控制权。在系统引导时间,这是下断点时机。
4.
被调试机进入启动系统菜单后,选择调试系统项,回车,这时主控机
windbg
应该有信息了。
5.
过一会,
WinDbg
取得了控制权,下断点:
bu upper!driverentry
和断点
bp upper!driverunload
然后按
F5
继续运行。
6.
再次中断,
windbg
已经得到为我们把驱动代码给显示出来了,
K
命令看堆栈,如下:
bad03838 805810ec Rockey4!DriverEntry+0x21 [e:/work/upper/src/32/upper/upper.c @ 62]
bad03908 8058f28f nt!IopLoadDriver+0x66c
bad0394c 805e6ba3 nt!PipCallDriverAddDeviceQueryRoutine+0x235
bad03998 805e6ed8 nt!RtlpCallQueryRegistryRoutine+0x3b1
bad039fc 80590b17 nt!RtlQueryRegistryValues+0x2a6
bad03ad0 80591fd4 nt!PipCallDriverAddDevice+0x261
bad03d2c 805924de nt!PipProcessDevNodeTree+0x1a4
bad03d54 804f7878 nt!PiProcessStartSystemDevices+0x3a
bad03d7c 805389bd nt!PipDeviceActionWorker+0x170
bad03dac 805cf84c nt!ExpWorkerThread+0xef
bad03ddc 8054632e nt!PspSystemThreadStartup+0x34
00000000 00000000 nt!KiThreadStartup+0x16
原来是被
IopLoadDriver
所加载,那
DriverEntry
后为何被卸载。看汇编代码:
nt!IopLoadDriver+0x6b1:
80581131 e87eb10000 call nt!IopIsLegacyDriver (8058c2b4)
80581136 84c0 test al,al
80581138 752c jne nt!IopLoadDriver+0x6e6 (80581166)
8058113a 8d4598 lea eax,[ebp-68h]
8058113d 50 push eax
8058113e ff758c push dword ptr [ebp-74h]
80581141 57 push edi
80581142 e80f5cf7ff call nt!IopPnpDriverStarted (804f6d56)
0: kd> u
nt!IopLoadDriver+0x6c7:
80581147 3bc3 cmp eax,ebx
80581149 8945ac mov dword ptr [ebp-54h],eax
8058114c 7d2c jge nt!IopLoadDriver+0x6fa (8058117a)
8058114e 8b4734 mov eax,dword ptr [edi+34h]
80581151 3bc3 cmp eax,ebx
80581153 7411 je nt!IopLoadDriver+0x6e6 (80581166)
80581155 834f0801 or dword ptr [edi+8],1
80581159 57 push edi
0: kd> u
nt!IopLoadDriver+0x6da:
8058115a ffd0 call eax here call Driverunload
8058115c 53 push ebx
8058115d 8d45a4 lea eax,[ebp-5Ch]
80581160 50 push eax
80581161 e856f4ffff call nt!IopBootLog (805805bc)
80581166 395dac cmp dword ptr [ebp-54h],ebx
80581169 7d0f jge nt!IopLoadDriver+0x6fa (8058117a)
8058116b 57 push edi
IopPnpDriverStarted
这个函数比较关键。失败了就调用
Driverunload
了,那
IopPnpDriverStarted
干了些啥工作。深入看看。
nt!IopPnpDriverStarted:
804f6d56 8bff mov edi,edi
804f6d58 55 push ebp
804f6d59 8bec mov ebp,esp
804f6d5b 56 push esi
804f6d5c 57 push edi
804f6d5d 8b7d08 mov edi,dword ptr [ebp+8]
804f6d60 33f6 xor esi,esi
804f6d62 397704 cmp dword ptr [edi+4],esi
804f6d65 752a jne nt!IopPnpDriverStarted+0x3b (804f6d91)
804f6d67 8b4510 mov eax,dword ptr [ebp+10h]
804f6d6a 397004 cmp dword ptr [eax+4],esi
804f6d6d 7422 je nt!IopPnpDriverStarted+0x3b (804f6d91)
804f6d6f 56 push esi
804f6d70 56 push esi
804f6d71 50 push eax
804f6d72 e8b7780900 call nt!IopIsAnyDeviceInstanceEnabled (8058e62e)
804f6d77 84c0 test al,al
804f6d79 7516 jne nt!IopPnpDriverStarted+0x3b (804f6d91)
804f6d7b f6470808 test byte ptr [edi+8],8
804f6d7f 7510 jne nt!IopPnpDriverStarted+0x3b (804f6d91)
804f6d81 56 push esi
804f6d82 ff750c push dword ptr [ebp+0Ch]
804f6d85 e84c620900 call nt!IopDriverLoadingFailed (8058cfd6)
804f6d8a be5e0200c0 mov esi,0C000025Eh
804f6d8f eb06 jmp nt!IopPnpDriverStarted+0x41 (804f6d97)
804f6d91 57 push edi
804f6d92 e81d6d0900 call nt!IopDeleteLegacyKey (8058dab4)
804f6d97 5f pop edi
804f6d98 8bc6 mov eax,esi
804f6d9a 5e pop esi
804f6d9b 5d pop ebp
804f6d9c c20c00 ret 0Ch
不复杂,调用
IopIsAnyDeviceInstanceEnabled
来看是否有此实例,没有就调用
IopDriverLoadingFailed
,表示失败了。继续跟踪
IopIsAnyDeviceInstanceEnabled
,发现访问了
HKLM/system/currentcontrolset/enum/
下的东东,导致的失败,没仔细跟了,读者有兴趣可以继续深入。看来还是注册表得问题。
用
Regsnap
看看,前后对比发现是在
HKLM/system/currentcontrolset/enum/root
下少了东东,添加后,重启,加载正常。
原来
HKLM/system/currentcontrolset/enum/root
为非
pnp
设备所用,系统启动后
IO
管理器会加载该下的驱动,而与
start
值无关同时,也可断定
Unload
的
IRP
是
IO
管理器主动发出的。
谨以此文纪念此次现在看来迂回的、低效率的跟踪过程。如果仔细对比注册表,就不会有此次费神的跟踪发生,但还是有些意外的小收获,;)。