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

时间:2021-01-08 04:52:00

  国内有关于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\n")); 
 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\n"));  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\n")); 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\n")); 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\n")); 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...\n")); 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!\n")); 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\n")); 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\r\n", 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\n")); 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\n")); 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_卸载_蓝屏”这类标题的文章,当时异常高兴,可是进去以后发现内容和我想要的根本不一样,如下:

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

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

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

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

 

 

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