📄 csdn技术中心 jiurl键盘驱动 2.htm
字号:
NtCreateFile,NtReadFile 等函数中,会创建一个 IRP,并用传入的参数初始化这个 IRP,然后将这个
IRP 发给驱动,让驱动做处理。相应的 NtCreateFile 产生 IRP_MJ_CREATE 的 IRP
,NtReadFile 产生 IRP_MJ_READ 的 IRP。驱动得到这些 IRP ,根据情况做处理,对于
IRP_MJ_READ ,会调用驱动中处理 IRP_MJ_READ
的部分,可能最后引起读硬件的操作。<BR><BR><BR>2.2
获得指定驱动的句柄<BR><BR>
对于希望被应用程序使用的驱动,会在初始化的过程中,把能找到它设备对象的一个 SymbolicLink
放在对象管理器命名空间(Object Manager Namespace)的 \??\ 下。这样用
"\\\\.\\那个SymbolicLink的名字" 作为 CreateFile 的 lpFileName 参数,调用
CreateFile ,得到的句柄就可以找到相应的驱动的那个设备对象(\\.\ 会被转换成
\??\)。之后以这个句柄为参数使用 ReadFile,WriteFile,DeviceIoControl,产生的 IRP
就被发到相应的设备对象。也就是说只要驱动把 设备对象的 SymbolicLink 放在 \??\ 下,并且应用程序知道这个
SymbolicLink 的名字,就可以使用 CreateFile 得到相应的句柄。<BR><BR>HANDLE
CreateFile(<BR>LPCTSTR lpFileName, // file name<BR>DWORD
dwDesiredAccess, // access mode<BR>DWORD dwShareMode, // share
mode<BR>LPSECURITY_ATTRIBUTES lpSecurityAttributes, //
SD<BR>DWORD dwCreationDisposition, // how to create<BR>DWORD
dwFlagsAndAttributes, // file attributes<BR>HANDLE
hTemplateFile // handle to template
file<BR>);<BR><BR><BR>可以使用工具 WinObj 来查看 对象管理器命名空间(Object
Manager Namespace) 。WinObj 可以从 http://www.sysinternals.com
获得。关于内核对象和命名地址空间的详细介绍,可以参考《 JIURL玩玩Win2k 对象
》,这篇文章可以在我的主页上找到。<BR><BR>在驱动的初始化过程中,会通过调用 IoCreateDevice
创建设备对象,可以指定一个设备名作为IoCreateDevice
的参数(也可以不指定,那样这个设备对象就没有名字)。这样这个设备对象会被放在 对象管理器命名空间(Object
Manager Namespace)的 \Device\ 下。不过应用程序只能访问命名空间的 \??\
,所以如果驱动希望把设备对象暴露给应用程序的话,会为设备对象创建一个 SymbolicLink 放在 \??\ 下。对于放在
\Device\ 下的有名字的设备,其他驱动程序如果知道它的名字,就可以使用
IoGetDeviceObjectPointer 得到这个设备对象的指针。<BR><BR>驱动可以通过
IoCreateSymbolicLink ,在 \??\ 下建立设备对象的 SymbolicLink
。这样应用程序必须要也知道该 SymbolicLink 的名字,然后就可以以这个符号链接名做参数使用 CreateFile
,得到句柄。从 IoCreateSymbolicLink 的参数,我们可以知道,只能使用
IoCreateSymbolicLink 为有名字的设备对象建立
SymbolicLink。<BR><BR>另一种方法是,使用一个 GUID 来标识一个设备接口。驱动使用标识设备的 GUID
做参数调用IoRegisterDeviceInterface ,然后使用 IoSetDeviceInterfaceState
,就会为设备对象在 \??\ 下产生一个符号链接(SymbolicLink)。应用程序使用同一个 GUID
做参数,使用API: SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces,
SetupDiGetDeviceInterfaceDetail 就可以得到创建的 \??\
下的符号链接名,就可以以这个符号链接名做参数使用 CreateFile
,得到句柄。<BR><BR>
句柄简介。每个进程都有一个自己的句柄表,句柄表中放着内核对象的指针,句柄就是内核对象在句柄表中的索引。通过句柄就可以在进程的句柄表中找到对应的内核对象的指针。关于句柄的详细介绍,可以参考
《 JIURL玩玩Win2k 进程线程篇 HANDLE_TABLE 》,这篇文章可以在我的主页上找到。<BR><BR>2.3
一些结论<BR><BR> SymbolicLink 对象可以找到相应的
设备对象。SymbolicLink 对象中保存着相应的 设备对象的地址。设备对象不保存它的 SymbolicLink
对象的任何信息。SymbolicLink 对象的地址保存在对象管理器命名空间(Object Manager
Namespace)中。也就是说只要知道 SymbolicLink 的名字,就可以在对象管理器命名空间中找到它。应用程序
CreateFile 得到的句柄,通过这个句柄在进程的句柄表中找到的是一个文件对象(File Object)。文件对象
对应的 设备对象 中不保存这个文件对象的任何信息。对应的 SymbolicLink
对象中也不保存这个文件对象的任何信息。这个文件对象的地址,保存在应用程序的句柄表中,应用程序通过句柄可以找到这个文件对象。这个
文件对象 中保存着对应的 设备对象 的地址。可以猜到,应用程序在用 CreateFile 创建的时候,会根据参数中的
SymolicLink 名字,找到 SymolicLink 对象,进而找到该对象中保存的 设备对象 的地址,然后直接把找到的
设备对象 的地址保存在文件对象中。文件对象的 +04 struct _DEVICE_OBJECT *DeviceObject
处,保存着对应的设备对象的地址。<BR><BR>
对于需要暴露接口给应用程序的驱动。首先,驱动中需要在 对象管理器命名空间的 \??\ 下,为设备对象建立一个
SymbolicLink ,不管采取何种方式。之后,应用程序要知道这个 SymbolicLink
的名字,不管采取何种方式。然后应用程序以 "\\\\.\\那个SymbolicLink的名字" 为参数使用
CreateFile 得到一个句柄。这样,之后的
DeviceIoControl(),WriteFile(),ReadFile() 使用前面用 CreateFile
得到的句柄作为参数,他们可以通过这个句柄,找到对应的文件对象,而这个文件对象中保存有对应的 设备对象 的指针,这样就可以将
IRP 发到这些设备。 <BR><BR>
上面的结论是通过对一个小例子进行观察得到的。<BR><BR>2.4 IRP
将被发往设备栈的栈顶<BR><BR> IRP
将无论如何被发往设备栈的顶。CreateFile,ReadFile,WriteFile,DeviceIoControl,CloseHandle。他们最终都会产生一个
IRP,发给一个设备对象。对于 CreateFile 来说通过 SymbolicLink的名字
来找到一个设备对象。对于其他的几个函数,通过句柄,找到一个文件对象,文件对象中保存有设备对象的指针。不过产生的 IRP
并不一定发给找到的这个设备对象,而是发给找到的这个设备对象所在设备栈的最顶上的一个设备对象。<BR><BR>
而且通常我们传给
CreateFile,ReadFile,WriteFile,DeviceIoControl,CloseHandle
的参数所找到的那个设备对象,会是它所在设备栈的 PDO(也就是它所在设备栈最底下的一个设备对象)。CreateFile,
ReadFile, WriteFile, DeviceIoControl, CloseHandle
会首先通过找到的这个设备对象,获得它所在设备栈中最顶端的那个设备对象,然后将 IRP
发向设备栈的最顶端的那个设备对象。所以不管我们通过参数找到的设备对象在它所在的设备栈中处于什么位置,顶端,中间,底下,不管处在什么位置,IRP
都会发往这个设备栈的栈顶。<BR><BR> 上面的内容是通过跟踪
nt!NtCreateFile 和 nt!NtReadFile 发现的。<BR><BR>2.5
nt!NtCreateFile简介<BR><BR> 我们简单介绍一下
nt!NtCreateFile。<BR><BR>通过一系列的调用 nt!NtCreateFile 最终会调用
nt!IopParseDevice。下面的 call stack
显示了这个调用过程。<BR><BR>...<BR>nt!IopParseDevice+0xa04<BR>nt!ObpLookupObjectName+0x4c4<BR>nt!ObOpenObjectByName+0xc5<BR>nt!IoCreateFile+0x3ec<BR>nt!NtCreateFile+0x2e<BR>nt!KiSystemService+0xc4<BR>...<BR><BR>在
nt!IopParseDevice 中 <BR><BR>调用 nt!IoGetAttachedDevice
,获得设备栈最顶端的设备对象。调用 IoAllocateIrp 创建 IRP。调用 nt!ObCreateObject
创建文件对象。初始化这个文件对象。该文件对象的 +04 struct _DEVICE_OBJECT
*DeviceObject 赋值为通过传入参数找到的那个设备对象。调用 nt!IopfCallDriver,也就是
IoCallDriver,将 IRP 发给设备栈的栈顶。<BR><BR>驱动处理完这个 IRP 之后,返回
nt!IopParseDevice 继续执行。nt!IopParseDevice 一路返回到
nt!ObOpenObjectByName。在 nt!ObOpenObjectByName 中继续执行,调用
nt!ObpCreateHandle
在进程的句柄表中创建一个新的句柄,这个句柄对应的对象是刚才创建初始化的那个文件对象。<BR><BR>2.6
nt!NtReadFile简介<BR><BR> 我们简单介绍一下
nt!NtReadFile。传入参数中有前面 CreateFile
打开的句柄,通过句柄可以在进程句柄表中找到一个文件对象,在这个文件对象中保存有一个设备对象的指针。调用
IoGetRelatedDeviceObject 获得这个设备对象所在设备栈栈顶的设备对象。调用 IoAllocateIrp
创建 IRP。初始化这个 IRP ,并根据传入的参数,设置好这个 IRP。然后调用 IoCallDriver 将这个 IRP
发给设备对象,让驱动进行处理。发往的这个设备对象就是前面使用 IoGetRelatedDeviceObject
所得到的设备栈栈顶的设备对象。下面的 call stack
显示了这个调用过程。<BR><BR>...<BR>nt!IopfCallDriver+0x35<BR>nt!IopSynchronousServiceTail+0x60<BR>nt!NtReadFile+0x5f4<BR>nt!KiSystemService+0xc4<BR>...<BR><BR><B>3
键盘驱动的应用层</B><BR><BR>
哪一个应用程序在使用键盘驱动?它是如何使用键盘驱动的?这是讨论键盘驱动肯定要遇到的问题,我们现在就来简单的讨论它。<BR><BR>3.1
键盘驱动的使用者<BR><BR> 键盘驱动的使用者是线程
win32k!RawInputThread 。线程 win32k!RawInputThread 的进程是
csrss.exe。<BR><BR> 我最早是通过 WinDbg 的 !irpfind
命令看到了这一点。后来看键盘驱动时,观察kbdclass!KeyboardClassRead,kbdclass!KeyboardClassCreate
的 call stack 也看到了这一点。<BR><BR>
kbdclass!KeyboardClassCreate 是,键盘设备栈最顶端的设备对象的驱动中处理
IRP_MJ_CREATE 的函数。所以当有人使用 CreateFile
来打开键盘设备栈上的某个设备对象的句柄的时候,CreateFile 最终会发一个 IRP_MJ_CREATE 的 IRP
给键盘设备栈最顶端的设备对象,这将导致 kbdclass!KeyboardClassCreate
被调用。于是我们在这个函数上下断点,看看是谁引起了这个函数的调用。看看是谁要得到键盘的句柄。<BR><BR>
在系统初始化的末期,在 kbdclass!KeyboardClassCreate
上发生了打断,进入调试器。首先我们看看这时的当前线程是谁。<BR><BR>kd> !thread<BR>THREAD
fe42e5e0 Cid a0.bc Teb: 00000000 Win32Thread: e194a9e8
RUNNING<BR>IRP List:<BR>fe43e9a8: (0006,0148) Flags: 00000884
Mdl: 00000000<BR>Not impersonating<BR>Owning Process
fe43b760<BR>Wait Start TickCount 5168 Elapsed Ticks:
0<BR>Context Switch Count 9 <BR>UserTime
0:00:00.0000<BR>KernelTime 0:00:00.0250<BR>Start Address
win32k!RawInputThread (0xa000e7cd)<BR>Stack Init f90f0000
Current f90ef864 Base f90f0000 Limit f90ed000 Call
0<BR>Priority 19 BasePriority 13 PriorityDecrement 0
DecrementCount 0<BR><BR>ChildEBP RetAddr Args to
Child<BR>f90ef608 8041f54b fe4f5df0 fe43e9a8 fe43e9b8
kbdclass!KeyboardClassCreate<BR>f90ef61c 804a3e54 804a392a
fe4dd718 f90ef90c nt!IopfCallDriver+0x35<BR>f90ef7a4 8044e27e
fe4dd730 00000000 f90ef850 nt!IopParseDevice+0xa04<BR>f90ef810
804957ae 00000000 f90ef900 00000000
nt!ObpLookupObjectName+0x4c4<BR>f90ef920 804a78b8 00000000
00000000 e18f5900 nt!ObOpenObjectByName+0xc5<BR>f90ef9f4
804a0c5b e197101c 00100001 f90efb14
nt!IoCreateFile+0x3ec<BR>f90efa34 80461691 e197101c 00100001
f90efb14 nt!NtCreateFile+0x2e<BR>f90efa34 804009d1 e197101c
00100001 f90efb14 nt!KiSystemService+0xc4<BR>f90efad8 a000e304
e197101c 00100001 f90efb14 nt!ZwCreateFile+0xb<BR>f90efb2c
a000e192 e1971008 80400b46 00000001
win32k!OpenDevice+0x8e<BR>f90efb58 a000eb74 00000001 00000000
00000000 win32k!ProcessDeviceChanges+0x92<BR>f90efda8 804524f6
00000003 00000000 00000000
win32k!RawInputThread+0x463<BR>f90efddc 80465b62 a000e7cd
f8d5f7d0 00000000 nt!PspSystemThreadStartup+0x69<BR>00000000
f000ff53 f000e2c3 f000ff53 f000ff53
nt!KiThreadStartup+0x16<BR>f000ff53 00000000 00000000 00000000
00000000 +0xf000ff53<BR><BR>看到 Start Address 为
win32k!RawInputThread。说明线程 win32k!RawInputThread 在通过
CreateFile 来获得键盘的句柄。<BR><BR>看到 Cid 为 a0.bc 。说明线程的进程为
a0。<BR><BR>我们看看 a0 进程是谁。<BR><BR>kd> !process a0
0<BR>Searching for Process with Cid == a0<BR>PROCESS fe43b760
SessionId: 0 Cid: 00a0 Peb: 7ffdf000 ParentCid:
0090<BR>DirBase: 03642000 ObjectTable: fe43b6c8 TableSize:
53.<BR>Image: csrss.exe<BR><BR>看到 a0 进程的 Image 为
csrss.exe。<BR><BR>
kbdclass!KeyboardClassRead 是,键盘设备栈最顶端的设备对象的驱动中处理 IRP_MJ_READ
的函数。所以当有人使用 ReadFile 来要求读入数据的时候,ReadFile 最终会发一个 IRP_MJ_Read 的
IRP 给键盘设备栈最顶端的设备对象,这将导致 kbdclass!KeyboardClassRead
被调用。于是我们在这个函数上下断点,看看是谁引起了这个函数的调用。看看是谁要求从键盘读入数据。<BR><BR>
在 kbdclass!KeyboardClassCreate
上发生打断后,进入调试器。我们看看这时的当前线程是谁。<BR><BR>kd> !thread<BR>THREAD
fe42e5e0 Cid a0.bc Teb: 00000000 Win32Thread: e194a9e8
RUNNING<BR>...<BR>Start Address win32k!RawInputThread
(0xa000e7cd)<BR>...<BR><BR>看到 Start Address 为
win32k!RawInputThread。说明线程 win32k!RawInputThread 在通过 ReadFile
来要求从键盘读取数据。<BR><BR>看到 Cid 为 a0.bc 。说明线程的进程还是
a0。<BR><BR>这些足以说明键盘驱动的使用者是线程 win32k!RawInputThread 。线程
win32k!RawInputThread 的进程是 csrss.exe。<BR><BR>3.2
win32k!RawInputThread 获得句柄简介<BR><BR>
win32k!RawInputThread 会调用 nt!ZwCreateFile ,获得一个可以找到键盘设备栈的 PDO
的句柄,供以后的 ZwReadFile,ZwDeviceIoControlFile
等使用。<BR><BR> 首先我们看看断在
kbdclass!KeyboardClassCreate 时的 call stack ,看看引起
kbdclass!KeyboardClassCreate 的整个调用过程。<BR><BR># ChildEBP
RetAddr Args to Child <BR>00 f90ef608 8041f54b fe4f5df0
fe43e9a8 fe43e9b8 kbdclass!KeyboardClassCreate(struct
_DEVICE_OBJECT * DeviceObject = 0xfe4f5df0, struct _IRP * Irp
= 0xfe43e9a8) (CONV: stdcall)<BR>01 f90ef61c 804a3e54 804a392a
fe4dd718 f90ef90c nt!IopfCallDriver+0x35 (FPO: [0,0,2])<BR>02
f90ef7a4 8044e27e fe4dd730 00000000 f90ef850
nt!IopParseDevice+0xa04 (FPO: [Non-Fpo])<BR>03 f90ef810
804957ae 00000000 f90ef900 00000000
nt!ObpLookupObjectName+0x4c4 (FPO: [Non-Fpo])<BR>04 f90ef920
804a78b8 00000000 00000000 e18f5900 nt!ObOpenObjectByName+0xc5
(FPO: [Non-Fpo])<BR>05 f90ef9f4 804a0c5b e197101c 00100001
f90efb14 nt!IoCreateFile+0x3ec (FPO: [Non-Fpo])<BR>06 f90efa34
80461691 e197101c 00100001 f90efb14 nt!NtCreateFile+0x2e (FPO:
[Non-Fpo])<BR>07 f90efa34 804009d1 e197101c 00100001 f90efb14
nt!KiSystemService+0xc4 (FPO: [0,0] TrapFrame @
f90efa68)<BR>08 f90efad8 a000e304 e197101c 00100001 f90efb14
nt!ZwCreateFile+0xb (FPO: [11,0,0])<BR>09 f90efb2c a000e192
e1971008 80400b46 00000001 win32k!OpenDevice+0x8e (FPO:
[Non-Fpo])<BR>0a f90efb58 a000eb74 00000001 00000000 00000000
win32k!ProcessDeviceChanges+0x92 (FPO: [EBP 0xf90efda8]
[1,5,4])<BR>0b f90efda8 804524f6 00000003 00000000 00000000
win32k!RawInputThread+0x463 (FPO: [Non-Fpo])<BR>0c f90efddc
80465b62 a000e7cd f8d5f7d0 00000000
nt!PspSystemThreadStartup+0x69 (FPO: [Non-Fpo])<BR>0d 00000000
f000ff53 f000e2c3 f000ff53 f000ff53
nt!KiThreadStartup+0x16<BR>WARNING: Frame IP not in any known
module. Following frames may be wrong.<BR>0e f000ff53 00000000
00000000 00000000 00000000
0xf000ff53<BR><BR><BR> 我简单的跟了一下
win32k!RawInputThread
获得句柄的过程,下面我对这个过程做一个简单的介绍。<BR><BR>win32k!RawInputThread 通过
GUID_CLASS_KEYBOARD 获得键盘设备栈中的 PDO (简单的说 PDO 是设备栈最下面的那个设备对象)的
SymbolicLink(符号链接)名。执行到 win32k!OpenDevice,它的一个参数可以找到 键盘设备栈的
PDO 的符号链接(SymbolicLink)名。win32k!OpenDevice 有一个
OBJECT_ATTRIBUTES 结构的局部变量,它自己初始化这个局部变量,用传入参数中的键盘设备栈的 PDO
的符号链接(SymbolicLink)名 赋值这个 OBJECT_ATTRIBUTES +0x8 处的
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -