📄 jiurl键盘驱动 2.htm
字号:
而且通常我们传给 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 处的 PUNICODE_STRING ObjectName 。然后调用
ZwCreateFile。ZwCreateFile 完成得到句柄的工作,最后通过传入的参数返回得到的句柄。win32k!RawInputThread
把得到的句柄保存起来,供后面的 ReadFile, DeviceIoControl等使用。<BR><BR>ZwCreateFile
通过系统服务,调用内核中的 NtCreateFile。NtCreateFile 执行到 nt!IopParseDevice 中 ,<BR>调用
nt!IoGetAttachedDevice ,通过 PDO 的设备对象获得键盘设备栈最顶端的设备对象。用得到的这个设备对象的 +30 char
StackSize 作为参数来 IoAllocateIrp,创建 IRP。调用 nt!ObCreateObject
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -