OkayToCloseProcedure of Windows ObjectType Hook

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.