1. Background
Object Type Hook is an in-depth Hook based on Object Type, which is more in-depth than the commonly used SSDT Hook.
For the analysis of Object Type, please see the article “Windows Driver Development Learning Record-ObjectType Hook’s ObjectType Structure Related Analysis”.
The Hook used here is one of OkayToCloseProcedure. The article implements filtering of file objects.
2. OkayToCloseProcedure function declaration
See the article “Windows Driver Development Learning Record-Related Analysis of ObjectType Structure of ObjectType Hook”.
Here is the structure in x64 environment:
typedef BOOLEAN (*OB_OKAYTOCLOSE_METHOD)( IN PEPROCESS PROCESS OPTIONAL, IN PVOID Object, IN HANDLE Handle, IN KPROCESSOR_MODE PreviousMode );
3. OkayToCloseProcedure uses logical analysis
Use IDA to analyze the Win11 22621 version of ntoskrnl.exe and find the usage logic of OkayToCloseProcedure, as follows:
NTSTATUS __stdcall NtClose(HANDLE Handle) { char v2; // di ULONG_PTR v4; // rcx v2 = KeGetCurrentThread()->PreviousMode; if ((MmVerifierData & amp; 0x100) != 0 & amp; & amp; !v2 & amp; & amp; !(unsigned __int8)ObpIsKernelHandle(Handle, 0i64)) VfCheckUserHandle(v4); return ObpCloseHandle((ULONG_PTR)Handle); }
__int64 __fastcall ObpCloseHandle(ULONG_PTR BugCheckParameter1, unsigned __int8 a2) { ... v9 = ExGetHandlePointer(v7); v51 = BYTE1(v9); v10 = (_OBJECT_TYPE*)ObTypeIndexTable[(unsigned __int8)ObHeaderCookie ^ *(unsigned __int8*)(v9 + 24) ^ (unsigned __int64)BYTE1(v9)]; if (v10->TypeInfo.OkayToCloseProcedure) { if (KeGetCurrentThread()->ApcState.Process != (struct _KPROCESS*)BugCheckParameter1a) { KiStackAttachProcess(BugCheckParameter1a); v42 = 1; } v20 = (struct _EX_RUNDOWN_REF*)BugCheckParameter1a; if (!v10->TypeInfo.OkayToCloseProcedure((_EPROCESS*)BugCheckParameter1a, (void*)(v9 + 48), (void*)v4, a2)) { _InterlockedExchangeAdd64(v7, 1ui64); _InterlockedOr(v41, 0); if (*(_QWORD*)(v6 + 48)) ExfUnblockPushLock(v6 + 48, 0i64); KeLeaveCriticalRegion(); if (v42) KiUnstackDetachProcess(v52, 0i64); v12 = 0xC0000235; goto LABEL_36; } } ... }
You can see that NtClose is called, and then NtClose calls ObpCloseHandle. In ObpCloseHandle, it is judged whether OkayToCloseProcedure is empty. If it is not empty, OkayToCloseProcedure is called. If OkayToCloseProcedure returns failure, the entire ObpCloseHandle returns failure, that is, NtClose returns failure.
So our experimental logic is to open a file handle in the driver, then filter the OkayToCloseProcedure of the file object, and then use IObit Unlocker to unoccupy and delete it.
4. File object filtering
4.1 Opening a file using the driver
The main code is as follows:
EXTERN_C NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { KDPRINT("[Hello]", "Enter...\r\\ "); KDPRINT("[Hello]", "Hello Kernel World! CurrentProcessId:0x%p CurrentIRQL:0x%u\r\\ ", PsGetCurrentProcessId(), KeGetCurrentIrql()); if (RegistryPath != NULL) { KDPRINT("[Hello]", "RegistryPath:%wZ\r\\ ", RegistryPath); } DriverObject->DriverUnload = DriverUnload; OBJECT_ATTRIBUTES oba = { 0 }; IO_STATUS_BLOCK iosb = { 0 }; UNICODE_STRING usFilePath = RTL_CONSTANT_STRING(L"\\C:\Users\Administrator\Desktop\HelloDriver.exe"); InitializeObjectAttributes( &oba, &usFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE | OBJ_OPENIF, NULL, NULL); NTSTATUS ntStatus = ZwCreateFile( &hSysFile, GENERIC_READ, &oba, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(ntStatus)) { KDPRINT("[Hello]", "ZwCreateFile Failed, Code:0x x\r\\ ", ntStatus); } else { KDPRINT("[Hello]", "ZwCreateFile File OK\r\\ "); } return STATUS_SUCCESS; }
The driver opens a file HelloDriver.exe on the desktop and occupies it. If you delete the file directly at this time, you will be prompted to fail.
After installing this driver, the DebugView information is as follows:
Delete directly as follows:
Then use IObit Unlocker to unoccupy and delete it, as follows:
The file is deleted directly.
4.2 Hook file object OkayToCloseProcedure
.h file
#pragma once #include <ntifs.h> #ifDBG #define KDPRINT(projectName, format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,\ projectName "::【" __FUNCTION__ "】" ##format, \ ##__VA_ARGS__ ) #else #define KDPRINT(format, ...) #endif typedef struct _OBJECT_TYPE_FLAGS { UCHAR CaseInsensitive : 1; UCHAR UnnamedObjectsOnly : 1; UCHARUseDefaultObject: 1; UCHARSecurityRequired: 1; UCHAR MaintainHandleCount : 1; UCHAR MaintainTypeList : 1; UCHAR SupportsObjectCallbacks : 1; UCHAR CacheAligned : 1; }OBJECT_TYPE_FLAGS, * P_OBJECT_TYPE_FLAGS; #ifdef _AMD64_ typedef struct _OBJECT_TYPE_INITIALIZER { USHORT wLength; OBJECT_TYPE_FLAGS ObjectTypeFlags; ULONG ObjcetTypeCode; ULONGInvalidAttributes; GENERIC_MAPPING GenericMapping; ULONG ValidAccessMask; ULONG RetainAccess; ULONGPoolType; ULONG DefaultPagedPoolCharge; ULONG DefaultNonPagedPoolCharge; PVOID DumpProcedure; PVOID OpenProcedure; PVOID CloseProcedure; PVOID DeleteProcedure; PVOID ParseProcedure; PVOID SecurityProcedure; PVOID QueryNameProcedure; PVOID OkayToCloseProcedure; }OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER; #else // _AMD64_ typedef struct _OBJECT_TYPE_INITIALIZER { USHORT Length; BOOLEAN UseDefaultObject; BOOLEAN CaseInsensitive; ULONGInvalidAttributes; _GENERIC_MAPPING GenericMapping; ULONG ValidAccessMask; UCHAR SecurityRequired; UCHAR MaintainHandleCount; UCHAR MaintainTypeList; _POOL_TYPE PoolType; ULONG DefaultPagedPoolCharge; ULONG DefaultNonPagedPoolCharge; PVOID DumpProcedure; PVOID OpenProcedure; PVOID CloseProcedure; PVOID DeleteProcedure; PVOID ParseProcedure; PVOID SecurityProcedure; PVOID QueryNameProcedure; PVOID OkayToCloseProcedure; }OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER; #endif #ifdef _AMD64_ typedef struct _OBJECT_TYPE_EX { LIST_ENTRY TypeList; UNICODE_STRING Name; PVOID DefaultObject; ULONG Index; ULONG TotalNumberOfObjects; ULONG TotalNumberOfHandles; ULONG HighWaterNumberOfObjects; ULONG HighWaterNumberOfHandles; OBJECT_TYPE_INITIALIZER TypeInfo; ULONGLONG TypeLock; ULONG Key; LIST_ENTRY CallbackList; }OBJECT_TYPE_EX, *POBJECT_TYPE_EX; #else typedef struct _OBJECT_TYPE_EX { UCHAR Unamed[0x38]; LIST_ENTRY TypeList; UNICODE_STRING Name; PVOID DefaultObject; ULONG Index; ULONG TotalNumberOfObjects; ULONG TotalNumberOfHandles; ULONG HighWaterNumberOfObjects; ULONG HighWaterNumberOfHandles; OBJECT_TYPE_INITIALIZER TypeInfo; ULONG Key; LIST_ENTRY CallbackList; }OBJECT_TYPE_EX, *POBJECT_TYPE_EX; #endif typedef enum _OB_OPEN_REASON { ObCreateHandle, ObOpenHandle, ObDuplicateHandle, ObInheritHandle, ObMaxOpenReason } OB_OPEN_REASON; typedef BOOLEAN (NTAPI* POKAYTOCLOSE_PROCEDURE)( IN PEPROCESS PROCESS OPTIONAL, IN PVOID Object, IN HANDLE Handle, IN KPROCESSOR_MODE PreviousMode); typedef struct _OBJECT_TYPE_HOOK_INFORMATION { POBJECT_TYPE_EX pHookedObject; POKAYTOCLOSE_PROCEDURE pOringinalOkToCloseProcedureAddress; }OBJECT_TYPE_HOOK_INFORMATION, * POBJECT_TYPE_HOOK_INFORMATION; EXTERN_C NTKERNELAPI POBJECT_TYPE NTAPI ObGetObjectType( PVOID Object ); #ifdef _AMD64_ EXTERN_C NTKERNELAPI NTSTATUS PsReferenceProcessFilePointer( IN PEPROCESS PROCESS, OUT PVOID* pFilePointer ); #endif EXTERN_C NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS pEProcess); void UnHookObjectType();
.cpp file
#include "ObjectTypeHook.h" OBJECT_TYPE_HOOK_INFORMATION g_HookInfomation = { 0 }; UNICODE_STRING g_usProtectedFileName = RTL_CONSTANT_STRING(L"*HELLODRIVER.EXE*"); UNICODE_STRING g_usSeperator = RTL_CONSTANT_STRING(L"\"); BOOLEAN NTAPI CustomOkayToCloseProcedure( IN PEPROCESS PROCESS OPTIONAL, IN PVOID Object, IN HANDLE Handle, IN KPROCESSOR_MODE PreviousMode) { BOOLEAN bReturn = true; if (Object) { POBJECT_TYPE pObjectType = ObGetObjectType(Object); if (pObjectType == *IoFileObjectType) { if (FsRtlIsNameInExpression( & amp;g_usProtectedFileName, & amp;((PFILE_OBJECT)Object)->FileName, true, NULL)) { if (PsGetProcessId(Process) == (HANDLE)4) { KDPRINT("[ObjectTypeHook]", "Need Filter File Path Is %wZ\r\\ ", ((PFILE_OBJECT)Object)->FileName); KDPRINT("[ObjectTypeHook]", "Denied Process Id is 0x d\r\\ ", PsGetCurrentProcessId()); bReturn = false; } } } } if (!bReturn) { return false; } else { if (g_HookInfomation.pOringinalOkToCloseProcedureAddress) { bReturn = g_HookInfomation.pOringinalOkToCloseProcedureAddress( Process, Object, Handle, PreviousMode); } return bReturn; } } void UnHookObjectType() { KDPRINT("[ObjectTypeHook]", "UnHook...\r\\ "); if (g_HookInfomation.pHookedObject) { InterlockedExchangePointer( (PVOID*)( & amp;g_HookInfomation.pHookedObject->TypeInfo.OkayToCloseProcedure), g_HookInfomation.pOringinalOkToCloseProcedureAddress); } } VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { UNREFERENCED_PARAMETER(pDriverObject); KDPRINT("[ObjectTypeHook]", "CurrentProcessId : 0x%p CurrentIRQL : 0x%u \r\\ ", PsGetCurrentProcessId(), KeGetCurrentIrql()); UnHookObjectType(); } EXTERN_C NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) { UNREFERENCED_PARAMETER(pDriverObject); UNREFERENCED_PARAMETER(pRegistryPath); NTSTATUS ntStatus = STATUS_SUCCESS; KDPRINT("[ObjectTypeHook]", " Hello Kernel World! CurrentProcessId:0x%p CurrentIRQL:0x%u\r\\ ", PsGetCurrentProcessId(), KeGetCurrentIrql()); pDriverObject->DriverUnload = DriverUnload; g_HookInfomation.pHookedObject = (POBJECT_TYPE_EX)(*IoFileObjectType); g_HookInfomation.pOringinalOkToCloseProcedureAddress = (POKAYTOCLOSE_PROCEDURE)(((POBJECT_TYPE_EX)(*IoFileObjectType))->TypeInfo.OkayToCloseProcedure); InterlockedExchangePointer( (PVOID*)( & amp;g_HookInfomation.pHookedObject->TypeInfo.OkayToCloseProcedure), CustomOkayToCloseProcedure); KDPRINT("[ObjectTypeHook]", "Hook OkayToCloseProcedure!\r\\ "); return ntStatus; }
4.3 Experimental results
Load the first driver to realize file occupation, and then load the second driver for hook installation, as follows:
Use IObit Unlocker afterwards to unblock and delete. The effect is as follows:
Although it prompts that the unlocking and deletion are successful, the file is still there, which means that the actual IObit Unlocker failed to release the closed handle and the purpose was achieved.