(IoGetDeviceObjectPointer後沒有對傳回的檔案對象調用ObDerefernceObject)傳感器裝置對象;直到App通知驅動停止時,驅動才對檔案對象進行解引用。看到如此流程,又因為這份驅動年代久遠,期間又經由多人之手(簡單的說就是沒有spec),我初步斷定就是因為檔案對象一直沒有解引用引起的HLK測試項失敗。後來,經另一位同僚提醒,當時的設計背景可能是考慮到傳感器在使用過程中可能會被停用,為了保證傳感器對象的有效性,原作者在驅動工作期間保持該對象的引用計數,這樣,傳感器對象至少在我們的驅動主動停用前不會被解除安裝。
IoGetDeviceObjectPointer傳回裝置對象和檔案對象,以為這個函數會使目标裝置的引用計數+2,調用ObDereferenceObject(FileObject);後,目标裝置上的引用計數為1,是以總覺得還需要對裝置對象進行一次解引用才能徹底釋放。因為有這樣的疑惑,我沒有馬上動手,而是寫了一份測試代碼調試了IoGetDeviceObjectPointer對指定裝置的影響。我用的測試裝置是DDK源碼src\general\ioctl編譯生成的sioctl裝置;測試IoGetDeviceObjectPointer對sioctl的引用的源碼如下:
#define NT_DEVICE_NAME L"\\Device\\SIOCTL"
#define DOS_DEVICE_NAME L"\\DosDevices\\IoctlTest"
NTSTATUS
DriverEntry(
__in PDRIVER_OBJECT DriverObject,
__in PUNICODE_STRING RegistryPath
)
{
UNICODE_STRING devStr;
NTSTATUS ntStatus;
FILE_OBJECT* fileObj;
DEVICE_OBJECT* devObj;
RtlInitUnicodeString(&devStr, DOS_DEVICE_NAME);
ntStatus = IoGetDeviceObjectPointer(&devStr, FILE_ALL_ACCESS, &fileObj, &devObj);
if(ntStatus == STATUS_SUCCESS)
ObDereferenceObject(fileObj);
return ntStatus;
}
下面是我的調試過程以我對調試輸出的個人了解:
kd> g
KDTARGET: Refreshing KD connection
Breakpoint 0 hit
getdev!DriverEntry:
92ee1010 8bff mov edi,edi
驅動編譯後子產品名為getdev,我對DriverEntry下了延遲斷點,這就可以觀察調用IoGetDeviceObjectPointer前sioctl的引用計數:
kd> t
nt!IoGetDeviceObjectPointer:
82ca2e98 8bff mov edi,edi
kd> !drvobj sioctl
Driver object (850ff468) is for:
\Driver\sioctl
Driver Extension List: (id , addr)
Device Object list:
850ff340
kd> !devobj 850ff340
Device object (850ff340) is for:
SIOCTL \Driver\sioctl DriverObject 850ff468
Current Irp 00000000 RefCount 0 <------引用計數為0 Type 00000022 Flags 00000040
...
kd> !object 850ff340
Object: 850ff340 Type: (84ff7bc8) Device
ObjectHeader: 850ff328 (new version)
HandleCount: 0 PointerCount: 2 ;<-------對象指針計數==2
Directory Object: 88a0f710 Name: SIOCTL
剛進入IoGetDeviceObjectPointer時,sioctl引用計數等于0,另外它的對象指針計數為2,這是初始值。
IoGetDeviceObjectPointer的源碼,第一印象有4個函數可能會影響sioctl的引用計數,他們分别是:ZwOpenFile,ObReferenceObjectByHandle,IoGetRelatedDeviceObject,ZwClose.
NTSTATUS
NTAPI
IopGetDeviceObjectPointer(IN PUNICODE_STRING ObjectName,
IN ACCESS_MASK DesiredAccess,
OUT PFILE_OBJECT *FileObject,
OUT PDEVICE_OBJECT *DeviceObject,
IN ULONG AttachFlag)
{
InitializeObjectAttributes(&ObjectAttributes,
ObjectName,
OBJ_KERNEL_HANDLE,
NULL,
NULL);
Status = ZwOpenFile(&FileHandle,
DesiredAccess,
&ObjectAttributes,
&StatusBlock,
0,
FILE_NON_DIRECTORY_FILE | AttachFlag);
if (!NT_SUCCESS(Status)) return Status;
Status = ObReferenceObjectByHandle(FileHandle,
0,
IoFileObjectType,
KernelMode,
(PVOID*)&LocalFileObject,
NULL);
if (NT_SUCCESS(Status))
{
*DeviceObject = IoGetRelatedDeviceObject(LocalFileObject);
*FileObject = LocalFileObject;
ZwClose(FileHandle);
}
return Status;
}
我要做的就是在進出這些函數前後檢視sioctl的對象指針及引用計數的變化,是以要在這些入口下斷點。另外,在x86的機器上,裝置對象的RefCount字段距對象頭的偏移是4B,可以在這個位置下通路斷點:
kd> u 82ca2eec
82ca2eec e89fa8dbff call nt!ZwOpenFile (82a5d790)
kd> u 82ca2f09
82ca2f09 e8f7d1f9ff call nt!ObReferenceObjectByHandle (82c40105)
kd> u 82ca2f1e
82ca2f1e e837c1e2ff call nt!IoGetRelatedDeviceObject (82acf05a)
kd> u 82ca2f2c
82ca2f2c e84b9edbff call nt!ZwClose (82a5cd7c)
;普通斷點
kd> bp 82ca2eec
kd> bp 82ca2f09
kd> bp 82ca2f1e
kd> bp 82ca2f2c
;通路斷點
kd> ba w 4 850ff340 +4
kd> bl
0 e Disable Clear 92ee1010 [c:\users\eugene\desktop\studio\ioget\getdev.c @ 12] 0001 (0001) getdev!DriverEntry
1 e Disable Clear 92ee1041 [c:\users\eugene\desktop\studio\ioget\getdev.c @ 21] 0001 (0001) getdev!DriverEntry+0x31
2 e Disable Clear 82ca2eec 0001 (0001) nt!IoGetDeviceObjectPointer+0x54
3 e Disable Clear 82ca2f09 0001 (0001) nt!IoGetDeviceObjectPointer+0x71
4 e Disable Clear 82ca2f1e 0001 (0001) nt!IoGetDeviceObjectPointer+0x86
5 e Disable Clear 82ca2f2c 0001 (0001) nt!IoGetDeviceObjectPointer+0x94
6 e Disable Clear 850ff344 w 4 0001 (0001)
好了,準備工作做好了,開始我的探索之旅:
kd> g
Breakpoint 6 hit
nt!IopCheckDeviceAndDriver+0x45:
82ab8393 33f6 xor esi,esi
kd> kb
# ChildEBP RetAddr Args to Child
00 807e5750 82c5b889 19870092 807e58f8 00000000 nt!IopCheckDeviceAndDriver+0x45
01 807e5828 82c3d1d7 850ff340 84ff7970 87248008 nt!IopParseDevice+0x133
02 807e58a4 82c6324d 00000000 807e58f8 00000240 nt!ObpLookupObjectName+0x4fa
03 807e5904 82c5b5ab 807e5a90 84ff7970 00022000 nt!ObOpenObjectByName+0x159
04 807e5980 82c965ee 807e5a80 001f01ff 807e5a90 nt!IopCreateFile+0x673
05 807e59c8 82a5f42a 807e5a80 001f01ff 807e5a90 nt!NtOpenFile+0x2a
06 807e59c8 82a5d7a1 807e5a80 001f01ff 807e5a90 nt!KiFastCallEntry+0x12a
07 807e5a58 82ca2ef1 807e5a80 001f01ff 807e5a90 nt!ZwOpenFile+0x11
08 807e5aac 92ee103e 807e5acc 001f01ff 807e5ad4 nt!IoGetDeviceObjectPointer+0x59
09 807e5ad8 82bbf728 8514fea8 85141000 00000000 getdev!DriverEntry+0x2e [c:\users\eugene\desktop\studio\ioget\getdev.c @ 19]
...
首次觸發的斷點是通路斷點,
ZwOpenFile打開sioctl裝置的過程中,OS對sioctl的引用計數做了加1操作,我們可以在ZwOpenFile傳回後驗證:
kd> g
Breakpoint 3 hit
nt!IoGetDeviceObjectPointer+0x71:
82ca2f09 e8f7d1f9ff call nt!ObReferenceObjectByHandle (82c40105)
kd> kb
# ChildEBP RetAddr Args to Child
00 807e5aac 92ee103e 807e5acc 001f01ff 807e5ad4 nt!IoGetDeviceObjectPointer+0x71
01 807e5ad8 82bbf728 8514fea8 85141000 00000000 getdev!DriverEntry+0x2e [c:\users\eugene\desktop\studio\ioget\getdev.c @ 19]
...
kd> r esp
esp=807e5a60
kd> !handle poi(@esp) 7
PROCESS 84fe5a20 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 88a01b40 HandleCount: 480.
Image: System
Kernel handle table at 88a01b40 with 480 entries in use
800007f8: Object: 85153d58 GrantedAccess: 001f01ff Entry: 88a03ff0
Object: 85153d58 Type: (84ff7970) File
ObjectHeader: 85153d40 (new version)
HandleCount: 1 PointerCount: 1
kd> !fileobj 85153d58
Device Object: 0x850ff340 \Driver\sioctl
Vpb is NULL
Event signalled
Flags: 0x40000
Handle Created
CurrentByteOffset: 0
ZwOpenFile打開sioctl的檔案對象,并傳回檔案對象的句柄。我們可以簡單的認為句柄直到ZwOpenFile函數傳回才有效。當獲得有效句柄後,就能用!handle調試指令獲得句柄對應的對象及對象狀态。現在的問題是從哪裡能獲得ZwOpenFile傳回的句柄?檢視IoGetDeviceObjectPointer的源碼發現,ObReferenceObjectByHandle會用到這個句柄。問題變得很簡單了,在執行
82ca2f09 e8f7d1f9ff call nt!ObReferenceObjectByHandle (82c40105)
前,OS已經準備好了必要的參數,是以,隻要從堆棧上獲得參數就行了。此時堆棧頂由esp指向,是以我從esp處取出了sioctl句柄,正是這句:
kd> !handle poi(@esp) 7
windbg分析認為傳入的是指向檔案對象的句柄,檔案對象的句柄計數和對象計數分别是1。
結合<深入解析Windows作業系統>知,句柄數增加和對象數增加是一一對應的,每次OS打開傳回一個句柄,必然在核心中準備了一個對象。
前文曽懷疑調用ObReferenceObjectByHandle後,會影響sioctl的引用計數。現在來驗證猜想是否正确。
kd> p
nt!IoGetDeviceObjectPointer+0x76:
82ca2f0e 8bf8 mov edi,eax
kd> ub . L2
nt!IoGetDeviceObjectPointer+0x6d:
82ca2f05 ff74241c push dword ptr [esp+1Ch]
82ca2f09 e8f7d1f9ff call nt!ObReferenceObjectByHandle (82c40105)
kd> !object 85153d58
Object: 85153d58 Type: (84ff7970) File
ObjectHeader: 85153d40 (new version)
HandleCount: 1 PointerCount: 2
上面的調試記錄,我用p單步越過ObReferenceObjectByHandle,然後再檢視對象狀态(!object 85153d58檢視的就是ZwOpenFile傳回的句柄所指向的檔案對象的狀态)。windbg分析出,自調用ObReferenceObjectByHandle後,對象的句柄計數保持為1,而指針對象增加到2。
由于,核心除了可以使用句柄,還能單獨打開并使用句柄背後的對象。是以在核心中,同一個對象的記錄的PointerCount>=HandlerCount。
kd> g
Breakpoint 4 hit
nt!IoGetDeviceObjectPointer+0x86:
82ca2f1e e837c1e2ff call nt!IoGetRelatedDeviceObject (82acf05a)
kd> ub . L2
nt!IoGetDeviceObjectPointer+0x83: ;IoGetRelatedDeivceObject的參數是FileObject,結合反彙編可知,eax中儲存了FileObject
82ca2f1b 50 push eax
82ca2f1c 8901 mov dword ptr [ecx],eax
kd> r eax
eax=85153d58
kd> !object 850ff340
Object: 850ff340 Type: (84ff7bc8) Device
ObjectHeader: 850ff328 (new version)
HandleCount: 0 PointerCount: 2
Directory Object: 88a0f710 Name: SIOCTL
!object 850ff340
上面指令用于檢視裝置對象(0x850ff340是裝置對象的位址)狀态的指令。
1).剛進入IoGetDeviceObjectPointer時執行過這條指令;
2).調用了IoGetRelatedDeviceObject,并從FileObject成功獲得DeviceObject後,再次執行這條指令;
對比1),2)處兩次指令執行,除了裝置對象的RefCount不同以外,其他輸出一緻。側面反映了在IoGetDeviceObjectPointer中執行ZwOpenFile和ObReferenceObjectByHandle以及IoGetRelatedDeviceObject函數并不會影響裝置對象的HandleCount和PointerCount的值。
有點奇怪,IoGetRelatedDeviceObject明明用于獲得裝置對象,可是卻沒有修改PointerCount的值,為什麼會這樣?答案在源碼中:
PDEVICE_OBJECT
NTAPI
IoGetRelatedDeviceObject(IN PFILE_OBJECT FileObject)
{
PDEVICE_OBJECT DeviceObject = FileObject->DeviceObject;
...
else
{
/* Otherwise, this was a direct open */
DeviceObject = FileObject->DeviceObject;
}
...
/* Check if we were attached */
if (DeviceObject->AttachedDevice)
{
/* Check if the file object has an extension present */
if (FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION)
{
/* Sanity check, direct open files can't have this */
ASSERT(!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN));
/* Check if the extension is really present */
if (FileObject->FileObjectExtension)
{
/* FIXME: Unhandled yet */
DPRINT1("FOEs not supported\n");
KEBUGCHECK(0);
}
}
/* Return the highest attached device */
DeviceObject = IoGetAttachedDevice(DeviceObject);
}
/* Return the DO we found */
return DeviceObject;
}
因為FileObject中存有DeviceObject的指針,并沒有經過對象管理器配置設定DeviceObject對象,是以不會增加sioctl的PointerCount。
HandleCount和PoniterCount是一一對應的關系,HandleCount減少,必然是PointerCount減少引起的。是以,可以确定FileObject的PointerCount也減少了。有調試輸出為證:
kd> g
Breakpoint 5 hit
nt!IoGetDeviceObjectPointer+0x94:
82ca2f2c e84b9edbff call nt!ZwClose (82a5cd7c)
kd> r esp
esp=807e5a74 ;調用ZwClose前,棧頂儲存了ZwClose的參數,也就是句柄
kd> !handle poi(@esp)
...
800007f8: Object: 85153d58 GrantedAccess: 001f01ff Entry: 88a03ff0
Object: 85153d58 Type: (84ff7970) File
ObjectHeader: 85153d40 (new version)
HandleCount: 1 PointerCount: 2
;調用ZwClose前HandleCount=1,PointerCount=2
kd> p ;Step Over ZwClose
nt!IoGetDeviceObjectPointer+0x99:
82ca2f31 8bc7 mov eax,edi
;調用ZwClose後,句柄無效,是以不再使用!handle指令;但是FileObject仍然存在,是以還能用!object來檢視FileObject狀态
kd> !object 85153d58
Object: 85153d58 Type: (84ff7970) File
ObjectHeader: 85153d40 (new version)
HandleCount: 0 PointerCount: 1
當IoGetDeviceObjectPointer傳回到示例驅動中,sioctl對應的FileObject!pointerCount保持為1,sioctl的RefCount同樣為1:
kd> !devobj 850ff340 ;850ff340是sioctl的裝置對象位址
Device object (850ff340) is for:
SIOCTL \Driver\sioctl DriverObject 850ff468
Current Irp 00000000 RefCount 1 ;<----------------------引用計數仍為1
SecurityDescriptor 88a51678 DevExt 00000000 DevObjExt 850ff3f8
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00000100) FILE_DEVICE_SECURE_OPEN
Device queue is not busy.
kd> !object 850ff340
Object: 850ff340 Type: (84ff7bc8) Device
ObjectHeader: 850ff328 (new version)
HandleCount: 0 PointerCount: 2 ; <-----------HandleCount/PointerCount穩定保持不變,呵呵,真是波瀾不驚
Directory Object: 88a0f710 Name: SIOCTL
kd> g
Breakpoint 1 hit ;斷點1是測試代碼中的ObDereferenceObject語句
getdev!DriverEntry+0x31:
92ee1041 837dec00 cmp dword ptr [ebp-14h],0
kd> p
getdev!DriverEntry+0x37:
92ee1047 8b4dfc mov ecx,dword ptr [ebp-4]
kd> p ;單步越過ObDereferenceObject後,還會觸發通路斷點,即sioctl裝置對象的引用計數被清零了
Breakpoint 6 hit
nt!IopDecrementDeviceObjectRef+0x12:
82ac39d2 8ad0 mov dl,al
kd> !object 0x85153d58 ;再次檢視FileObject,發現pointerCount終于清零了
Object: 85153d58 Type: (84ff7970) File
ObjectHeader: 85153d40 (new version)
HandleCount: 0 PointerCount: 0
kd> g
Breakpoint 2 hit