应用层调用驱动程序的方法

转:http://hi.baidu.com/cealy/blog/item/3d300ed7941182 d9a144df5a.html Windows 中,应用程序使用驱动,应用程序与驱动通信的一些问题。

  2.1 应用程序如何使用驱动

  应用程序中使用 CreateFile,ReadFile,WriteFile,DeviceIoControl,CloseHandle 来指示驱动程序完成某种任务。比如我们在应用程序中使用 ReadFile 来让驱动读取硬件设备,我们在应用程序中使用 WriteFile 来让驱动写硬件设备,我们在应用程序中使用 DeviceIoContorl 来让驱动完成某些驱动支持的功能。而 ReadFile, WriteFile, DeviceIoControl 这三个 api 都需要一个句柄作为参数,以确定他们是要哪个驱动来完成他们的请求。这个句柄是通过 CreateFile 获得的。使用 CloseHandle 关闭这个句柄。简单的说就是,应用程序中,首先要通过 CreateFile 获得一个句柄,之后应用程序可以以这个句柄为参数,使用 ReadFile,WriteFile,DeviceIoControl 让驱动程序执行某种操作。当不再使用时,通过 CloseHandle 关闭这个句柄。

  这几个 api 都位于 KERNEL32.DLL 中,他们最终会通过系统服务(int 2e)调用内核中的相应的函数,如 NtCreateFile,NtReadFile 等。而 NtCreateFile,NtReadFile 等函数中,会创建一个 IRP,并用传入的参数初始化这个 IRP,然后将这个 IRP 发给驱动,让驱动做处理。相应的 NtCreateFile 产生 IRP_MJ_CREATE 的 IRP ,NtReadFile 产生 IRP_MJ_READ 的 IRP。驱动得到这些 IRP ,根据情况做处理,对于 IRP_MJ_READ ,会调用驱动中处理 IRP_MJ_READ 的部分,可能最后引起读硬件的操作。

  2.2 获得指定驱动的句柄

  对于希望被应用程序使用的驱动,会在初始化的过程中,把能找到它设备对象的一个 SymbolicLink 放在对象管理器命名空间(Object Manager Namespace)的 /??/ 下。这样用 ".//那个SymbolicLink的名字" 作为 CreateFile 的 lpFileName 参数,调用 CreateFile ,得到的句柄就可以找到相应的驱动的那个设备对象(//./ 会被转换成 /??/)。之后以这个句柄为参数使用 ReadFile,WriteFile,DeviceIoControl,产生的 IRP 就被发到相应的设备对象。也就是说只要驱动把 设备对象的 SymbolicLink 放在 /??/ 下,并且应用程序知道这个 SymbolicLink 的名字,就可以使用 CreateFile 得到相应的句柄。

  HANDLE CreateFile(

  LPCTSTR lpFileName, // file name

  DWORD dwDesiredAccess, // access mode

  DWORD dwShareMode, // share mode

  LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD

  DWORD dwCreationDisposition, // how to create

  DWORD dwFlagsAndAttributes, // file attributes

  HANDLE hTemplateFile // handle to template file

  );

  可以使用工具 WinObj 来查看 对象管理器命名空间(Object Manager Namespace) 。WinObj 可以从 http://www.sysinternals.com 获得。关于内核对象和命名地址空间的详细介绍,可以参考《 JIURL玩玩Win2k 对象 》,这篇文章可以在我的主页上找到。

  在驱动的初始化过程中,会通过调用 IoCreateDevice 创建设备对象,可以指定一个设备名作为IoCreateDevice 的参数(也可以不指定,那样这个设备对象就没有名字)。这样这个设备对象会被放在 对象管理器命名空间(Object Manager Namespace)的 /Device/ 下。不过应用程序只能访问命名空间的 /??/ ,所以如果驱动希望把设备对象暴露给应用程序的话,会为设备对象创建一个 SymbolicLink 放在 /??/ 下。对于放在 /Device/ 下的有名字的设备,其他驱动程序如果知道它的名字,就可以使用 IoGetDeviceObjectPointer 得到这个设备对象的指针。

  驱动可以通过 IoCreateSymbolicLink ,在 /??/ 下建立设备对象的 SymbolicLink 。这样应用程序必须要也知道该 SymbolicLink 的名字,然后就可以以这个符号链接名做参数使用 CreateFile ,得到句柄。从 IoCreateSymbolicLink 的参数,我们可以知道,只能使用 IoCreateSymbolicLink 为有名字的设备对象建立 SymbolicLink。

  另一种方法是,使用一个 GUID 来标识一个设备接口。驱动使用标识设备的 GUID 做参数调用IoRegisterDeviceInterface ,然后使用 IoSetDeviceInterfaceState ,就会为设备对象在 /??/ 下产生一个符号链接(SymbolicLink)。应用程序使用同一个 GUID 做参数,使用API: SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces, SetupDiGetDeviceInterfaceDetail 就可以得到创建的 /??/ 下的符号链接名,就可以以这个符号链接名做参数使用 CreateFile ,得到句柄。

  句柄简介。每个进程都有一个自己的句柄表,句柄表中放着内核对象的指针,句柄就是内核对象在句柄表中的索引。通过句柄就可以在进程的句柄表中找到对应的内核对象的指针。关于句柄的详细介绍,可以参考《 JIURL玩玩Win2k 进程线程篇 HANDLE_TABLE 》,这篇文章可以在我的主页上找到。

  2.3 一些结论

  SymbolicLink 对象可以找到相应的 设备对象。SymbolicLink 对象中保存着相应的 设备对象的地址。设备对象不保存它的 SymbolicLink 对象的任何信息。SymbolicLink 对象的地址保存在对象管理器命名空间(Object Manager Namespace)中。也就是说只要知道 SymbolicLink 的名字,就可以在对象管理器命名空间中找到它。应用程序 CreateFile 得到的句柄,通过这个句柄在进程的句柄表中找到的是一个文件对象(File Object)。文件对象 对应的 设备对象 中不保存这个文件对象的任何信息。对应的 SymbolicLink 对象中也不保存这个文件对象的任何信息。这个文件对象的地址,保存在应用程序的句柄表中,应用程序通过句柄可以找到这个文件对象。这个 文件对象 中保存着对应的 设备对象 的地址。可以猜到,应用程序在用 CreateFile 创建的时候,会根据参数中的 SymolicLink 名字,找到 SymolicLink 对象,进而找到该对象中保存的 设备对象 的地址,然后直接把找到的 设备对象 的地址保存在文件对象中。文件对象的 +04 struct _DEVICE_OBJECT *DeviceObject 处,保存着对应的设备对象的地址。

  对于需要暴露接口给应用程序的驱动。首先,驱动中需要在 对象管理器命名空间的 /??/ 下,为设备对象建立一个 SymbolicLink ,不管采取何种方式。之后,应用程序要知道这个 SymbolicLink 的名字,不管采取何种方式。然后应用程序以 ".//那个SymbolicLink的名字" 为参数使用 CreateFile 得到一个句柄。这样,之后的 DeviceIoControl(),WriteFile(),ReadFile() 使用前面用 CreateFile 得到的句柄作为参数,他们可以通过这个句柄,找到对应的文件对象,而这个文件对象中保存有对应的 设备对象 的指针,这样就可以将 IRP 发到这些设备。

  上面的结论是通过对一个小例子进行观察得到的。

  2.4 IRP 将被发往设备栈的栈顶

  IRP 将无论如何被发往设备栈的顶。CreateFile,ReadFile,WriteFile,DeviceIoControl,Clos eHandle。他们最终都会产生一个 IRP,发给一个设备对象。对于 CreateFile 来说通过 SymbolicLink的名字 来找到一个设备对象。对于其他的几个函数,通过句柄,找到一个文件对象,文件对象中保存有设备对象的指针。不过产生的 IRP 并不一定发给找到的这个设备对象,而是发给找到的这个设备对象所在设备栈的最顶上的一个设备对象。

  而且通常我们传给 CreateFile,ReadFile,WriteFile,DeviceIoControl,Clos eHandle 的参数所找到的那个设备对象,会是它所在设备栈的 PDO(也就是它所在设备栈最底下的一个设备对象)。CreateFile, ReadFile, WriteFile, DeviceIoControl, CloseHandle 会首先通过找到的这个设备对象,获得它所在设备栈中最顶端的那个设备对象,然后将 IRP 发向设备栈的最顶端的那个设备对象。所以不管我们通过参数找到的设备对象在它所在的设备栈中处于什么位置,顶端,中间,底下,不管处在什么位置,IRP 都会发往这个设备栈的栈顶。

  上面的内容是通过跟踪 nt!NtCreateFile 和 nt!NtReadFile 发现的。

  2.5 nt!NtCreateFile简介

  我们简单介绍一下 nt!NtCreateFile。

  通过一系列的调用 nt!NtCreateFile 最终会调用 nt!IopParseDevice。下面的 call stack 显示了这个调用过程。

  ...

  nt!IopParseDevice+0xa04

  nt!ObpLookupObjectName+0x4c4

  nt!ObOpenObjectByName+0xc5

  nt!IoCreateFile+0x3ec

  nt!NtCreateFile+0x2e

  nt!KiSystemService+0xc4

  ...

  在 nt!IopParseDevice 中

  调用 nt!IoGetAttachedDevice ,获得设备栈最顶端的设备对象。调用 IoAllocateIrp 创建 IRP。调用 nt!ObCreateObject 创建文件对象。初始化这个文件对象。该文件对象的 +04 struct _DEVICE_OBJECT *DeviceObject 赋值为通过传入参数找到的那个设备对象。调用 nt!IopfCallDriver,也就是 IoCallDriver,将 IRP 发给设备栈的栈顶。

  驱动处理完这个 IRP 之后,返回 nt!IopParseDevice 继续执行。nt!IopParseDevice 一路返回到 nt!ObOpenObjectByName。在 nt!ObOpenObjectByName 中继续执行,调用 nt!ObpCreateHandle 在进程的句柄表中创建一个新的句柄,这个句柄对应的对象是刚才创建初始化的那个文件对象。

  2.6 nt!NtReadFile简介

  我们简单介绍一下 nt!NtReadFile。传入参数中有前面 CreateFile 打开的句柄,通过句柄可以在进程句柄表中找到一个文件对象,在这个文件对象中保存有一个设备对象的指针。调用 IoGetRelatedDeviceObject 获得这个设备对象所在设备栈栈顶的设备对象。调用 IoAllocateIrp 创建 IRP。初始化这个 IRP ,并根据传入的参数,设置好这个 IRP。然后调用 IoCallDriver 将这个 IRP 发给设备对象,让驱动进行处理。发往的这个设备对象就是前面使用 IoGetRelatedDeviceObject 所得到的设备栈栈顶的设备对象。下面的 call stack 显示了这个调用过程。

  ...

  nt!IopfCallDriver+0x35

  nt!IopSynchronousServiceTail+0x60

  nt!NtReadFile+0x5f4

  nt!KiSystemService+0xc4

  ...

  3 键盘驱动的应用层

  哪一个应用程序在使用键盘驱动?它是如何使用键盘驱动的?这是讨论键盘驱动肯定要遇到的问题,我们现在就来简单的讨论它。

  3.1 键盘驱动的使用者

  键盘驱动的使用者是线程 win32k!RawInputThread 。线程 win32k!RawInputThread 的进程是 csrss.exe。

  我最早是通过 WinDbg 的 !irpfind 命令看到了这一点。后来看键盘驱动时,观察kbdclass!KeyboardClassRead,kbdclass!KeyboardClassCreate 的 call stack 也看到了这一点。

  kbdclass!KeyboardClassCreate 是,键盘设备栈最顶端的设备对象的驱动中处理 IRP_MJ_CREATE 的函数。所以当有人使用 CreateFile 来打开键盘设备栈上的某个设备对象的句柄的时候,CreateFile 最终会发一个 IRP_MJ_CREATE 的 IRP 给键盘设备栈最顶端的设备对象,这将导致 kbdclass!KeyboardClassCreate 被调用。于是我们在这个函数上下断点,看看是谁引起了这个函数的调用。看看是谁要得到键盘的句柄。

  在系统初始化的末期,在 kbdclass!KeyboardClassCreate 上发生了打断,进入调试器。首先我们看看这时的当前线程是谁。

  kd> !thread

  THREAD fe42e5e0 Cid a0.bc Teb: 00000000 Win32Thread: e194a9e8 RUNNING

  IRP List:

  fe43e9a8: (0006,0148) Flags: 00000884 Mdl: 00000000

  Not impersonating

  Owning Process fe43b760

  Wait Start TickCount 5168 Elapsed Ticks: 0

  Context Switch Count 9

  UserTime 0:00:00.0000

  KernelTime 0:00:00.0250

  Start Address win32k!RawInputThread (0xa000e7cd)

  Stack Init f90f0000 Current f90ef864 Base f90f0000 Limit f90ed000 Call 0

  Priority 19 BasePriority 13 PriorityDecrement 0 DecrementCount 0

  ChildEBP RetAddr Args to Child

  f90ef608 8041f54b fe4f5df0 fe43e9a8 fe43e9b8 kbdclass!KeyboardClassCreate

  f90ef61c 804a3e54 804a392a fe4dd718 f90ef90c nt!IopfCallDriver+0x35

  f90ef7a4 8044e27e fe4dd730 00000000 f90ef850 nt!IopParseDevice+0xa04

  f90ef810 804957ae 00000000 f90ef900 00000000 nt!ObpLookupObjectName+0x4c4

  f90ef920 804a78b8 00000000 00000000 e18f5900 nt!ObOpenObjectByName+0xc5

  f90ef9f4 804a0c5b e197101c 00100001 f90efb14 nt!IoCreateFile+0x3ec

  f90efa34 80461691 e197101c 00100001 f90efb14 nt!NtCreateFile+0x2e

  f90efa34 804009d1 e197101c 00100001 f90efb14 nt!KiSystemService+0xc4

  f90efad8 a000e304 e197101c 00100001 f90efb14 nt!ZwCreateFile+0xb

  f90efb2c a000e192 e1971008 80400b46 00000001 win32k!OpenDevice+0x8e

  f90efb58 a000eb74 00000001 00000000 00000000 win32k!ProcessDeviceChanges+0x92

  f90efda8 804524f6 00000003 00000000 00000000 win32k!RawInputThread+0x463

  f90efddc 80465b62 a000e7cd f8d5f7d0 00000000 nt!PspSystemThreadStartup+0x69

  00000000 f000ff53 f000e2c3 f000ff53 f000ff53 nt!KiThreadStartup+0x16

  f000ff53 00000000 00000000 00000000 00000000 +0xf000ff53

  看到 Start Address 为 win32k!RawInputThread。说明线程 win32k!RawInputThread 在通过 CreateFile 来获得键盘的句柄。

  看到 Cid 为 a0.bc 。说明线程的进程为 a0。

  我们看看 a0 进程是谁。

  kd> !process a0 0

  Searching for Process with Cid == a0

  PROCESS fe43b760 SessionId: 0 Cid: 00a0 Peb: 7ffdf000 ParentCid: 0090

  DirBase: 03642000 ObjectTable: fe43b6c8 TableSize: 53.

  Image: csrss.exe

  看到 a0 进程的 Image 为 csrss.exe。

  kbdclass!KeyboardClassRead 是,键盘设备栈最顶端的设备对象的驱动中处理 IRP_MJ_READ 的函数。所以当有人使用 ReadFile 来要求读入数据的时候,ReadFile 最终会发一个 IRP_MJ_Read 的 IRP 给键盘设备栈最顶端的设备对象,这将导致 kbdclass!KeyboardClassRead 被调用。于是我们在这个函数上下断点,看看是谁引起了这个函数的调用。看看是谁要求从键盘读入数据。

  在 kbdclass!KeyboardClassCreate 上发生打断后,进入调试器。我们看看这时的当前线程是谁。

  kd> !thread

  THREAD fe42e5e0 Cid a0.bc Teb: 00000000 Win32Thread: e194a9e8 RUNNING

  ...

  Start Address win32k!RawInputThread (0xa000e7cd)

  ...

  看到 Start Address 为 win32k!RawInputThread。说明线程 win32k!RawInputThread 在通过 ReadFile 来要求从键盘读取数据。

  看到 Cid 为 a0.bc 。说明线程的进程还是 a0。

  这些足以说明键盘驱动的使用者是线程 win32k!RawInputThread 。线程 win32k!RawInputThread 的进程是 csrss.exe。

  3.2 win32k!RawInputThread 获得句柄简介

  win32k!RawInputThread 会调用 nt!ZwCreateFile ,获得一个可以找到键盘设备栈的 PDO 的句柄,供以后的 ZwReadFile,ZwDeviceIoControlFile 等使用。

  首先我们看看断在 kbdclass!KeyboardClassCreate 时的 call stack ,看看引起 kbdclass!KeyboardClassCreate 的整个调用过程。

  # ChildEBP RetAddr Args to Child

  00 f90ef608 8041f54b fe4f5df0 fe43e9a8 fe43e9b8 kbdclass!KeyboardClassCreate(struct _DEVICE_OBJECT * DeviceObject = 0xfe4f5df0, struct _IRP * Irp = 0xfe43e9a8) (CONV: stdcall)

  01 f90ef61c 804a3e54 804a392a fe4dd718 f90ef90c nt!IopfCallDriver+0x35 (FPO: [0,0,2])

  02 f90ef7a4 8044e27e fe4dd730 00000000 f90ef850 nt!IopParseDevice+0xa04 (FPO: [Non-Fpo])

  03 f90ef810 804957ae 00000000 f90ef900 00000000 nt!ObpLookupObjectName+0x4c4 (FPO: [Non-Fpo])

  04 f90ef920 804a78b8 00000000 00000000 e18f5900 nt!ObOpenObjectByName+0xc5 (FPO: [Non-Fpo])

  05 f90ef9f4 804a0c5b e197101c 00100001 f90efb14 nt!IoCreateFile+0x3ec (FPO: [Non-Fpo])

  06 f90efa34 80461691 e197101c 00100001 f90efb14 nt!NtCreateFile+0x2e (FPO: [Non-Fpo])

  07 f90efa34 804009d1 e197101c 00100001 f90efb14 nt!KiSystemService+0xc4 (FPO: [0,0] TrapFrame @ f90efa68)

  08 f90efad8 a000e304 e197101c 00100001 f90efb14 nt!ZwCreateFile+0xb (FPO: [11,0,0])

  09 f90efb2c a000e192 e1971008 80400b46 00000001 win32k!OpenDevice+0x8e (FPO: [Non-Fpo])

  0a f90efb58 a000eb74 00000001 00000000 00000000 win32k!ProcessDeviceChanges+0x92 (FPO: [EBP 0xf90efda8] [1,5,4])

  0b f90efda8 804524f6 00000003 00000000 00000000 win32k!RawInputThread+0x463 (FPO: [Non-Fpo])

  0c f90efddc 80465b62 a000e7cd f8d5f7d0 00000000 nt!PspSystemThreadStartup+0x69 (FPO: [Non-Fpo])

  0d 00000000 f000ff53 f000e2c3 f000ff53 f000ff53 nt!KiThreadStartup+0x16

  WARNING: Frame IP not in any known module. Following frames may be wrong.

  0e f000ff53 00000000 00000000 00000000 00000000 0xf000ff53

  我简单的跟了一下 win32k!RawInputThread 获得句柄的过程,下面我对这个过程做一个简单的介绍。

  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等使用。

  ZwCreateFile 通过系统服务,调用内核中的 NtCreateFile。NtCreateFile 执行到 nt!IopParseDevice 中,

  调用 nt!IoGetAttachedDevice ,通过 PDO 的设备对象获得键盘设备栈最顶端的设备对象。用得到的这个设备对象的 +30 char StackSize 作为参数来 IoAllocateIrp,创建 IRP。调用 nt!ObCreateObject 创建文件对象。初始化这个文件对象,+04 struct _DEVICE_OBJECT *DeviceObject 赋值为键盘设备栈的 PDO。调用 nt!IopfCallDriver,将 IRP 发往驱动,让驱动进行相应的处理。之后一系列返回,回到 nt!ObOpenObjectByName。在 nt!ObOpenObjectByName 中继续执行,调用 nt!ObpCreateHandle 在进程(csrss.exe)的句柄表中创建一个新的句柄,这个句柄对应的对象是刚才创建初始化的那个文件对象,文件对象中的 DeviceObject 指向键盘设备栈的 PDO。在 nt!ObpCreateHandle 前后,我们使用命令 !handle 0 3 a0 (a0 为此时 csrss.exe进程的进程id),观察 csrss.exe进程 句柄表的前后变化,看到了多出来的那一个文件对象。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值