天天看點

IoGetDeviceObjectPointer引起的引用計數改變

(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