《windows内核安全与驱动开发》ctrl2cap中的ObReferenceObjectByName疑问

  国内有关于windows内核驱动这块的书籍实在是甚少,不过好在《windows内核安全与驱动开发》这本书还算不错(内容方面),但是不得不说这本书在许多地方存在着一些细节上的问题。比如我今天要谈的这个话题。

  在这本书的键盘过滤这个章节,作者对ObReferenceObjectByName个函数的用法做了介绍,并指明这是一个非文档化函数(可以用,但是在MSDN的文档中以及SDK的头文件中没有公开出来)。这个函数的具体结构如下:

 1 NTSTATUS
 2 ObReferenceObjectByName(  
 3     IN PUNICODE_STRING ObjectName,  
 4     IN ULONG Attributes,  
 5     IN PACCESS_STATE PassedAccessState OPTIONAL,  
 6     IN ACCESS_MASK DesiredAccess OPTIONAL,  
 7     IN POBJECT_TYPE ObjectType,  
 8     IN KPROCESSOR_MODE AccessMode,  
 9     IN OUT PVOID ParseContext OPTIONAL,  
10     OUT PVOID *Object  
11     );

这个函数的作用是利用对象的名字获得对象的指针,在这本书的这个章节,作者用它来以“键盘驱动名”获取一个键盘驱动对象(这个对象通过函数的最后一个参数返回),再从这个键盘驱动对象中获得这个键盘设备。

  由于每次调用这个函数成功的话,系统会给这个对象多增加一个引用,除了属性为OBJ_PERMANENT的对象,一般对象的生命是由引用数控制的,当某个对象的引用数为零了,windows就会删除这个对象,这有点类似于java的垃圾回收机制。所以为了成功调用这个函数并且保持这个对象的引用数不变,就有必要使用ObDereferenceObject这个函数来解除这个引用。

  这本书给出的源码如下:

  1 ///
  2 /// @file            ctrl2cap.c
  3 /// @author    wowocock
  4 /// @date        2009-1-27
  5 /// 
  6 
  7 #include <wdm.h>
  8 
  9 // Kbdclass驱动的名字
 10 #define KBD_DRIVER_NAME  L"\Driver\Kbdclass"
 11 
 12 typedef struct _C2P_DEV_EXT 
 13 { 
 14     // 这个结构的大小
 15     ULONG NodeSize; 
 16     // 过滤设备对象
 17     PDEVICE_OBJECT pFilterDeviceObject;
 18     // 同时调用时的保护锁
 19     KSPIN_LOCK IoRequestsSpinLock;
 20     // 进程间同步处理  
 21     KEVENT IoInProgressEvent; 
 22     // 绑定的设备对象
 23     PDEVICE_OBJECT TargetDeviceObject; 
 24     // 绑定前底层设备对象
 25     PDEVICE_OBJECT LowerDeviceObject; 
 26 } C2P_DEV_EXT, *PC2P_DEV_EXT;
 27 
 28 NTSTATUS 
 29 c2pDevExtInit( 
 30     IN PC2P_DEV_EXT devExt, 
 31     IN PDEVICE_OBJECT pFilterDeviceObject, 
 32     IN PDEVICE_OBJECT pTargetDeviceObject, 
 33     IN PDEVICE_OBJECT pLowerDeviceObject ) 
 34 { 
 35     memset(devExt, 0, sizeof(C2P_DEV_EXT)); 
 36     devExt->NodeSize = sizeof(C2P_DEV_EXT); 
 37     devExt->pFilterDeviceObject = pFilterDeviceObject; 
 38     KeInitializeSpinLock(&(devExt->IoRequestsSpinLock)); 
 39     KeInitializeEvent(&(devExt->IoInProgressEvent), NotificationEvent, FALSE); 
 40     devExt->TargetDeviceObject = pTargetDeviceObject; 
 41     devExt->LowerDeviceObject = pLowerDeviceObject; 
 42     return( STATUS_SUCCESS ); 
 43 }
 44 
 45 // 这个函数是事实存在的,只是文档中没有公开。声明一下
 46 // 就可以直接使用了。
 47 NTSTATUS
 48 ObReferenceObjectByName(
 49                         PUNICODE_STRING ObjectName,
 50                         ULONG Attributes,
 51                         PACCESS_STATE AccessState,
 52                         ACCESS_MASK DesiredAccess,
 53                         POBJECT_TYPE ObjectType,
 54                         KPROCESSOR_MODE AccessMode,
 55                         PVOID ParseContext,
 56                         PVOID *Object
 57                         );
 58 
 59 extern POBJECT_TYPE IoDriverObjectType;
 60 ULONG gC2pKeyCount = 0;
 61 PDRIVER_OBJECT gDriverObject = NULL;
 62 
 63 // 这个函数经过改造。能打开驱动对象Kbdclass,然后绑定
 64 // 它下面的所有的设备:
 65 NTSTATUS 
 66 c2pAttachDevices( 
 67                   IN PDRIVER_OBJECT DriverObject, 
 68                   IN PUNICODE_STRING RegistryPath 
 69                   ) 
 70 { 
 71     NTSTATUS status = 0; 
 72     UNICODE_STRING uniNtNameString; 
 73     PC2P_DEV_EXT devExt; 
 74     PDEVICE_OBJECT pFilterDeviceObject = NULL; 
 75     PDEVICE_OBJECT pTargetDeviceObject = NULL; 
 76     PDEVICE_OBJECT pLowerDeviceObject = NULL; 
 77 
 78     PDRIVER_OBJECT KbdDriverObject = NULL; 
 79 
 80     KdPrint(("MyAttach
")); 
 81 
 82     // 初始化一个字符串,就是Kdbclass驱动的名字。
 83     RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME); 
 84     // 请参照前面打开设备对象的例子。只是这里打开的是驱动对象。
 85     status = ObReferenceObjectByName ( 
 86         &uniNtNameString, 
 87         OBJ_CASE_INSENSITIVE, 
 88         NULL, 
 89         0, 
 90         IoDriverObjectType, 
 91         KernelMode, 
 92         NULL, 
 93         &KbdDriverObject 
 94         ); 
 95     // 如果失败了就直接返回
 96     if(!NT_SUCCESS(status)) 
 97     { 
 98         KdPrint(("MyAttach: Couldn't get the MyTest Device Object
")); 
 99         return( status ); 
100     }
101     else
102     {
103         // 这个打开需要解应用。早点解除了免得之后忘记。
104         ObDereferenceObject(DriverObject);
105     }
106 
107     // 这是设备链中的第一个设备    
108     pTargetDeviceObject = KbdDriverObject->DeviceObject;
109     // 现在开始遍历这个设备链
110     while (pTargetDeviceObject) 
111     {
112         // 生成一个过滤设备,这是前面读者学习过的。这里的IN宏和OUT宏都是
113         // 空宏,只有标志性意义,表明这个参数是一个输入或者输出参数。
114         status = IoCreateDevice( 
115             IN DriverObject, 
116             IN sizeof(C2P_DEV_EXT), 
117             IN NULL, 
118             IN pTargetDeviceObject->DeviceType, 
119             IN pTargetDeviceObject->Characteristics, 
120             IN FALSE, 
121             OUT &pFilterDeviceObject 
122             ); 
123 
124         // 如果失败了就直接退出。
125         if (!NT_SUCCESS(status)) 
126         { 
127             KdPrint(("MyAttach: Couldn't create the MyFilter Filter Device Object
")); 
128             return (status); 
129         } 
130 
131         // 绑定。pLowerDeviceObject是绑定之后得到的下一个设备。也就是
132         // 前面常常说的所谓真实设备。
133         pLowerDeviceObject = 
134             IoAttachDeviceToDeviceStack(pFilterDeviceObject, pTargetDeviceObject); 
135         // 如果绑定失败了,放弃之前的操作,退出。
136         if(!pLowerDeviceObject) 
137         { 
138             KdPrint(("MyAttach: Couldn't attach to MyTest Device Object
")); 
139             IoDeleteDevice(pFilterDeviceObject); 
140             pFilterDeviceObject = NULL; 
141             return( status ); 
142         } 
143 
144         // 设备扩展!下面要详细讲述设备扩展的应用。
145         devExt = (PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension); 
146         c2pDevExtInit( 
147             devExt, 
148             pFilterDeviceObject, 
149             pTargetDeviceObject, 
150             pLowerDeviceObject ); 
151 
152         // 下面的操作和前面过滤串口的操作基本一致。这里不再解释了。
153         pFilterDeviceObject->DeviceType=pLowerDeviceObject->DeviceType; 
154         pFilterDeviceObject->Characteristics=pLowerDeviceObject->Characteristics; 
155         pFilterDeviceObject->StackSize=pLowerDeviceObject->StackSize+1; 
156         pFilterDeviceObject->Flags |= pLowerDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE) ; 
157         //next device 
158         pTargetDeviceObject = pTargetDeviceObject->NextDevice;
159     }
160     return status; 
161 } 
162 
163 VOID 
164 c2pDetach(IN PDEVICE_OBJECT pDeviceObject) 
165 { 
166     PC2P_DEV_EXT devExt; 
167     BOOLEAN NoRequestsOutstanding = FALSE; 
168     devExt = (PC2P_DEV_EXT)pDeviceObject->DeviceExtension; 
169     __try 
170     { 
171         __try 
172         { 
173             IoDetachDevice(devExt->TargetDeviceObject);
174             devExt->TargetDeviceObject = NULL; 
175             IoDeleteDevice(pDeviceObject); 
176             devExt->pFilterDeviceObject = NULL; 
177             DbgPrint(("Detach Finished
")); 
178         } 
179         __except (EXCEPTION_EXECUTE_HANDLER){} 
180     } 
181     __finally{} 
182     return; 
183 }
184 
185 
186 #define  DELAY_ONE_MICROSECOND  (-10)
187 #define  DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
188 #define  DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
189 
190 VOID 
191 c2pUnload(IN PDRIVER_OBJECT DriverObject) 
192 { 
193     PDEVICE_OBJECT DeviceObject; 
194     PDEVICE_OBJECT OldDeviceObject; 
195     PC2P_DEV_EXT devExt; 
196 
197     LARGE_INTEGER    lDelay;
198     PRKTHREAD CurrentThread;
199     //delay some time 
200     lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);
201     CurrentThread = KeGetCurrentThread();
202     // 把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。
203     KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);
204 
205     UNREFERENCED_PARAMETER(DriverObject); 
206     KdPrint(("DriverEntry unLoading...
")); 
207 
208     // 遍历所有设备并一律解除绑定
209     DeviceObject = DriverObject->DeviceObject;
210     while (DeviceObject)
211     {
212         // 解除绑定并删除所有的设备
213         c2pDetach(DeviceObject);
214         DeviceObject = DeviceObject->NextDevice;
215     } 
216     ASSERT(NULL == DriverObject->DeviceObject);
217 
218     while (gC2pKeyCount)
219     {
220         KeDelayExecutionThread(KernelMode, FALSE, &lDelay);
221     }
222     KdPrint(("DriverEntry unLoad OK!
")); 
223     return; 
224 } 
225 
226 NTSTATUS c2pDispatchGeneral( 
227                                  IN PDEVICE_OBJECT DeviceObject, 
228                                  IN PIRP Irp 
229                                  ) 
230 { 
231     // 其他的分发函数,直接skip然后用IoCallDriver把IRP发送到真实设备
232     // 的设备对象。 
233     KdPrint(("Other Diapatch!")); 
234     IoSkipCurrentIrpStackLocation(Irp); 
235     return IoCallDriver(((PC2P_DEV_EXT)
236         DeviceObject->DeviceExtension)->LowerDeviceObject, Irp); 
237 } 
238 
239 NTSTATUS c2pPower( 
240                        IN PDEVICE_OBJECT DeviceObject, 
241                        IN PIRP Irp 
242                        ) 
243 { 
244     PC2P_DEV_EXT devExt;
245     devExt =
246         (PC2P_DEV_EXT)DeviceObject->DeviceExtension; 
247 
248     PoStartNextPowerIrp( Irp ); 
249     IoSkipCurrentIrpStackLocation( Irp ); 
250     return PoCallDriver(devExt->LowerDeviceObject, Irp ); 
251 } 
252 
253 NTSTATUS c2pPnP( 
254                      IN PDEVICE_OBJECT DeviceObject, 
255                      IN PIRP Irp 
256                      ) 
257 { 
258     PC2P_DEV_EXT devExt; 
259     PIO_STACK_LOCATION irpStack; 
260     NTSTATUS status = STATUS_SUCCESS; 
261     KIRQL oldIrql; 
262     KEVENT event; 
263 
264     // 获得真实设备。
265     devExt = (PC2P_DEV_EXT)(DeviceObject->DeviceExtension); 
266     irpStack = IoGetCurrentIrpStackLocation(Irp); 
267 
268     switch (irpStack->MinorFunction) 
269     { 
270     case IRP_MN_REMOVE_DEVICE: 
271         KdPrint(("IRP_MN_REMOVE_DEVICE
")); 
272 
273         // 首先把请求发下去
274         IoSkipCurrentIrpStackLocation(Irp); 
275         IoCallDriver(devExt->LowerDeviceObject, Irp); 
276         // 然后解除绑定。
277         IoDetachDevice(devExt->LowerDeviceObject); 
278         // 删除我们自己生成的虚拟设备。
279         IoDeleteDevice(DeviceObject); 
280         status = STATUS_SUCCESS; 
281         break; 
282 
283     default: 
284         // 对于其他类型的IRP,全部都直接下发即可。 
285         IoSkipCurrentIrpStackLocation(Irp); 
286         status = IoCallDriver(devExt->LowerDeviceObject, Irp); 
287     } 
288     return status; 
289 }
290 
291 // 这是一个IRP完成回调函数的原型
292 NTSTATUS c2pReadComplete( 
293                               IN PDEVICE_OBJECT DeviceObject, 
294                               IN PIRP Irp, 
295                               IN PVOID Context 
296                               ) 
297 {
298      PIO_STACK_LOCATION IrpSp;
299      ULONG buf_len = 0;
300      PUCHAR buf = NULL;
301      size_t i;
302 
303      IrpSp = IoGetCurrentIrpStackLocation( Irp );
304 
305      //  如果这个请求是成功的。很显然,如果请求失败了,这么获取
306      //   进一步的信息是没意义的。
307      if( NT_SUCCESS( Irp->IoStatus.Status ) ) 
308      {
309         // 获得读请求完成后输出的缓冲区
310         buf = Irp->AssociatedIrp.SystemBuffer;
311         // 获得这个缓冲区的长度。一般的说返回值有多长都保存在
312         // Information中。
313         buf_len = Irp->IoStatus.Information;
314 
315         //… 这里可以做进一步的处理。我这里很简单的打印出所有的扫
316         // 描码。
317         for(i=0;i<buf_len;++i)
318         {
319             DbgPrint("ctrl2cap: %2x
", buf[i]);
320         }
321     }
322     gC2pKeyCount--;
323 
324     if( Irp->PendingReturned )
325     { 
326         IoMarkIrpPending( Irp ); 
327     } 
328     return Irp->IoStatus.Status;
329 }
330 
331 
332 NTSTATUS c2pDispatchRead( 
333                               IN PDEVICE_OBJECT DeviceObject, 
334                               IN PIRP Irp ) 
335 { 
336     NTSTATUS status = STATUS_SUCCESS; 
337     PC2P_DEV_EXT devExt; 
338     PIO_STACK_LOCATION currentIrpStack; 
339     KEVENT waitEvent;
340     KeInitializeEvent( &waitEvent, NotificationEvent, FALSE );
341 
342     if (Irp->CurrentLocation == 1) 
343     { 
344         ULONG ReturnedInformation = 0; 
345         KdPrint(("Dispatch encountered bogus current location
")); 
346         status = STATUS_INVALID_DEVICE_REQUEST; 
347         Irp->IoStatus.Status = status; 
348         Irp->IoStatus.Information = ReturnedInformation; 
349         IoCompleteRequest(Irp, IO_NO_INCREMENT); 
350         return(status); 
351     } 
352 
353     // 全局变量键计数器加1
354     gC2pKeyCount++;
355 
356     // 得到设备扩展。目的是之后为了获得下一个设备的指针。
357     devExt =
358         (PC2P_DEV_EXT)DeviceObject->DeviceExtension;
359 
360     // 设置回调函数并把IRP传递下去。 之后读的处理也就结束了。
361     // 剩下的任务是要等待读请求完成。
362     currentIrpStack = IoGetCurrentIrpStackLocation(Irp); 
363     IoCopyCurrentIrpStackLocationToNext(Irp);
364     IoSetCompletionRoutine( Irp, c2pReadComplete, 
365         DeviceObject, TRUE, TRUE, TRUE ); 
366     return  IoCallDriver( devExt->LowerDeviceObject, Irp );     
367 }
368 
369 NTSTATUS DriverEntry( 
370                      IN PDRIVER_OBJECT DriverObject, 
371                      IN PUNICODE_STRING RegistryPath 
372                      ) 
373 { 
374     ULONG i; 
375     NTSTATUS status; 
376     KdPrint (("c2p.SYS: entering DriverEntry
")); 
377 
378     // 填写所有的分发函数的指针
379     for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) 
380     { 
381         DriverObject->MajorFunction[i] = c2pDispatchGeneral; 
382     } 
383 
384     // 单独的填写一个Read分发函数。因为要的过滤就是读取来的按键信息
385     // 其他的都不重要。这个分发函数单独写。
386     DriverObject->MajorFunction[IRP_MJ_READ] = c2pDispatchRead; 
387 
388     // 单独的填写一个IRP_MJ_POWER函数。这是因为这类请求中间要调用
389     // 一个PoCallDriver和一个PoStartNextPowerIrp,比较特殊。
390     DriverObject->MajorFunction [IRP_MJ_POWER] = c2pPower; 
391 
392     // 我们想知道什么时候一个我们绑定过的设备被卸载了(比如从机器上
393     // 被拔掉了?)所以专门写一个PNP(即插即用)分发函数
394     DriverObject->MajorFunction [IRP_MJ_PNP] = c2pPnP; 
395 
396     // 卸载函数。
397     DriverObject->DriverUnload = c2pUnload; 
398     gDriverObject = DriverObject;
399     // 绑定所有键盘设备
400     status =c2pAttachDevices(DriverObject, RegistryPath);
401 
402     return status; 
403 }

   问题的关键在于ObDereferenceObject函数的参数,应该要和ObReferenceObjectByName这个函数的最后一个参数相同,这样才能减少引用,而这里并没有。我当时看到这里的时候百思不得其解,一直觉得有问题,然后我就去百度搜这个东西,结果真的搜到了“《寒江独钓》_键盘过滤_ctrl2cap_卸载_蓝屏”这类标题的文章,当时异常高兴,可是进去以后发现内容和我想要的根本不一样,如下:

我大概花了2个小时从百度搜到了404搜索引擎,结果都是说蓝屏的原因出现在unload函数中。这样一来我上面那个问题还是没有解决,然而我并没有放弃,果然在搜了大概三个小时之后终于搜到了下面这篇文章

小结:经过我三个小时的努力搜索,终于皇天不负有心人,得到了我想要的结果,解决了我心中的疑惑。因为现在已经星期四晚上了,星期天下午还要考概率论,必须留点时间来预习(上了大学的同学应该都能体会“预习”这个词吧。。。),不然就真的gg了。所以没有时间来验证这个问题,总之就目前的结果来说unload那边是不是真的有问题还不知道(写这篇文章的时候还没看到unload部分),但是ObReferenceObject这部分应该是有问题无疑了。

原文地址:https://www.cnblogs.com/flyor/p/8887738.html