《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\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_卸载_蓝屏”这类标题的文章,当时异常高兴,可是进去以后发现内容和我想要的根本不一样,如下:
我大概花了2个小时从百度搜到了404搜索引擎,结果都是说蓝屏的原因出现在unload函数中。这样一来我上面那个问题还是没有解决,然而我并没有放弃,果然在搜了大概三个小时之后终于搜到了下面这篇文章
小结:经过我三个小时的努力搜索,终于皇天不负有心人,得到了我想要的结果,解决了我心中的疑惑。因为现在已经星期四晚上了,星期天下午还要考概率论,必须留点时间来预习(上了大学的同学应该都能体会“预习”这个词吧。。。),不然就真的gg了。所以没有时间来验证这个问题,总之就目前的结果来说unload那边是不是真的有问题还不知道(写这篇文章的时候还没看到unload部分),但是ObReferenceObject这部分应该是有问题无疑了。