object_Hook相关知识

本文深入探讨了Windows内核对象的内部结构与管理机制,详细分析了OBJECT_HEADER、OBJECT_TYPE等关键结构,以及如何通过HOOK技术监控内核对象操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前置知识

首先,分析OBJECT(内核对象)

先找到一个进程内核对象的地址,使用!process 0 0查看

PROCESS 885de030  SessionId: 1  Cid: 0700    Peb: 7ffd9000  ParentCid: 0418
    DirBase: 7f3602e0  ObjectTable: 98a8c548  HandleCount:  60.
    Image: Commnuication_ring3.exe

然后查看最后一个这个进程


kd> dt _OBJECT_HEADER 885de030-18
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n41
   +0x004 HandleCount      : 0n2
   +0x004 NextToFree       : 0x00000002 Void
   +0x008 Lock             : _EX_PUSH_LOCK
   +0x00c TypeIndex        : 0x7 ''
   +0x00d TraceFlags       : 0 ''
   +0x00e InfoMask         : 0x8 ''
   +0x00f Flags            : 0 ''
   +0x010 ObjectCreateInfo : 0x83f78cc0 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x83f78cc0 Void
   +0x014 SecurityDescriptor : 0x8c6f64fe Void
   +0x018 Body             : _QUAD

PointerCount保存的是对象的指针计数,HandeCount保存的是对象句柄计数(也就是我们说的引用计数)
当我们通过API得到对象的句柄:
- 打开一个对象时,对象指针计数(差了下windows内核情景分析一书,这个翻译成引用计数,感觉更好一些,因为如果是引用计数,后面获得对象就不会增加句柄计数,指挥增加引用计数)与句柄计数都会被加1。
- 关闭句柄的时候,对象的指针计数和句柄计数都会被减一。
- 我们在内核层获得对象时,会增加其指针计数,一般我们需要通过ObDeferenceObject降低计数。
- 当指针计数与句柄计数都为0,内核对象回本销毁。
- TypeIndex表示对象的类型,他是一个数组的下标,数组名叫做ObTypeIndexTable.由nt模块导出

下面查看一下ObTypeIndexTable

kd> dd ObTypeIndexTable
83f86900  00000000 bad0b0b0 86544768 865446a0
83f86910  865445d8 865de040 865def00 865dee38
83f86920  865ded70 865deca8 865debe0 865de528
83f86930  866059c8 86601418 86601350 866094d0
83f86940  86609408 86609340 865f5de8 865f5d20
83f86950  865f5c58 865fac90 865fabc8 865fab00
83f86960  865faa38 865fa970 865fa8a8 865fa7e0
83f86970  865fa718 86603f78 86603eb0 86603de8

上面的对象是这个里面第七个位置的类型,查看一下,7号位置就是865dee38,他是_OBJECT_TYPE的地址。我们查看_OBJECT_TYPE的内容

kd> dt _OBJECT_TYPE 865dee38
ntdll!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY [ 0x865dee38 - 0x865dee38 ]
   +0x008 Name             : _UNICODE_STRING "Process"
   +0x010 DefaultObject    : (null) 
   +0x014 Index            : 0x7 ''
   +0x018 TotalNumberOfObjects : 0x23
   +0x01c TotalNumberOfHandles : 0xcc
   +0x020 HighWaterNumberOfObjects : 0x2a
   +0x024 HighWaterNumberOfHandles : 0x104
   +0x028 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x078 TypeLock         : _EX_PUSH_LOCK
   +0x07c Key              : 0x636f7250
   +0x080 CallbackList     : _LIST_ENTRY [ 0x865deeb8 - 0x865deeb8 ]

接下来,我们再分析一下_OBJECT_TYPE,其中有一个TypeInfo类型是OBJECT_TYPE_INITIALIZER

kd> dt _OBJECT_TYPE_INITIALIZER
ntdll!_OBJECT_TYPE_INITIALIZER
   +0x000 Length           : Uint2B
   +0x002 ObjectTypeFlags  : UChar
   +0x002 CaseInsensitive  : Pos 0, 1 Bit
   +0x002 UnnamedObjectsOnly : Pos 1, 1 Bit
   +0x002 UseDefaultObject : Pos 2, 1 Bit
   +0x002 SecurityRequired : Pos 3, 1 Bit
   +0x002 MaintainHandleCount : Pos 4, 1 Bit
   +0x002 MaintainTypeList : Pos 5, 1 Bit
   +0x002 SupportsObjectCallbacks : Pos 6, 1 Bit
   +0x004 ObjectTypeCode   : Uint4B
   +0x008 InvalidAttributes : Uint4B
   +0x00c GenericMapping   : _GENERIC_MAPPING
   +0x01c ValidAccessMask  : Uint4B
   +0x020 RetainAccess     : Uint4B
   +0x024 PoolType         : _POOL_TYPE
   +0x028 DefaultPagedPoolCharge : Uint4B
   +0x02c DefaultNonPagedPoolCharge : Uint4B
   +0x030 DumpProcedure    : Ptr32     void 
   +0x034 OpenProcedure    : Ptr32     long 
   +0x038 CloseProcedure   : Ptr32     void 
   +0x03c DeleteProcedure  : Ptr32     void 
   +0x040 ParseProcedure   : Ptr32     long 
   +0x044 SecurityProcedure : Ptr32     long 
   +0x048 QueryNameProcedure : Ptr32     long 
   +0x04c OkayToCloseProcedure : Ptr32     unsigned char 

我们在内核层经常需要使用一个内核对象句柄获得内核对象,比如用户层传入一个时间对象句柄,用于分析内核层与用户层进行同步。
我们可以使用ObReferenceObjectByHandle函数根据句柄获得内核对象
注意:
- 当你调用这个函数时候,需要确保处于当前进程环境。
- 用户模式的句柄,将AccessMode指定为userMode
- 使用完毕,请调用ObDererfanceObject降低指针引用计数。

下面就是相关的代码

通过内核对下头部,能够获得此类型内核对象的创建信息,在这些信息中有一组函数指针非常重要。
我们通过Hook这些函数指针,能够达到监控内核对象操作的目的。
这种Hook被称为Object-Hook,下面的代码平台是Win32
总得来说就是找到地址然后把在OBJECT_TYPE里OBJECT_TYPE_INITIALIZER的参数ParseProcedure替换成自己的

#include <ntifs.h>
VOID DriverUnload(PDRIVER_OBJECT objDriver);
typedef ULONG(*OBGETOBJECTTYPE)(PVOID Object);
typedef NTSTATUS(*PARSEPRODECEDURE)(
    IN PVOID ParseObject,
    IN PVOID ObjectType,
    IN OUT PACCESS_STATE AccessState,
    IN KPROCESSOR_MODE AccessMode,
    IN ULONG Attributes,
    IN OUT PUNICODE_STRING CompleteName,
    IN OUT PUNICODE_STRING RemainingName,
    IN OUT PVOID Context OPTIONAL,
    IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
    OUT PVOID *Object);
PARSEPRODECEDURE g_OldFun;
NTSTATUS NewParseProcedure(
    IN PVOID ParseObject,
    IN PVOID ObjectType,
    IN OUT PACCESS_STATE AccessState,
    IN KPROCESSOR_MODE AccessMode,
    IN ULONG Attributes,
    IN OUT PUNICODE_STRING CompleteName,
    IN OUT PUNICODE_STRING RemainingName,
    IN OUT PVOID Context OPTIONAL,
    IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
    OUT PVOID *Object)
{
    KdPrint(("Hook Success\n"));

    return g_OldFun(ParseObject, ObjectType, AccessState, AccessMode, Attributes,
        CompleteName,
        RemainingName, Context, SecurityQos, Object);
}
OBGETOBJECTTYPE g_OBGetObjectType;
//获得一个函数地址,这个函数能够通过内核对象得到内核对象的类型
VOID GetObjectTypeAddress()
{
    PUCHAR addr;
    UNICODE_STRING pslookup;
    RtlInitUnicodeString(&pslookup, L"ObGetObjectType");

    //               类似于应用层的GetProcAddress
    addr = (PUCHAR)MmGetSystemRoutineAddress(&pslookup);


    g_OBGetObjectType = (OBGETOBJECTTYPE)addr;
}
typedef struct _OBJECT_TYPE_INITIALIZER {
    USHORT Length;
    UCHAR ObjectTypeFlags;
    UCHAR CaseInsensitive;
    UCHAR UnnamedObjectsOnly;
    UCHAR  UseDefaultObject;
    UCHAR  SecurityRequired;
    UCHAR MaintainHandleCount;
    UCHAR MaintainTypeList;
    UCHAR SupportsObjectCallbacks;
    UCHAR CacheAligned;
    ULONG ObjectTypeCode;
    BOOLEAN InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    BOOLEAN   ValidAccessMask;
    BOOLEAN   RetainAccess;
    POOL_TYPE PoolType;
    BOOLEAN DefaultPagedPoolCharge;
    BOOLEAN DefaultNonPagedPoolCharge;
    PVOID DumpProcedure;
    ULONG OpenProcedure;
    PVOID CloseProcedure;
    PVOID DeleteProcedure;
    ULONG ParseProcedure;
    ULONG SecurityProcedure;
    ULONG QueryNameProcedure;
    UCHAR OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
typedef struct _OBJECT_TYPE {
    LIST_ENTRY TypeList;
    UNICODE_STRING Name;
    PVOID DefaultObject;
    ULONG Index;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    OBJECT_TYPE_INITIALIZER TypeInfo;
    ULONG  TypeLock;
    ULONG   Key;
    LIST_ENTRY   CallbackList;
} OBJECT_TYPE, *POBJECT_TYPE;
HANDLE KernelCreateFile(
    IN PUNICODE_STRING pstrFile, // 文件路径符号链接
    IN BOOLEAN         bIsDir)   // 是否为文件夹
{
    HANDLE          hFile = NULL;
    NTSTATUS        Status = STATUS_UNSUCCESSFUL;
    IO_STATUS_BLOCK StatusBlock = { 0 };
    ULONG           ulShareAccess =
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
    ULONG           ulCreateOpt =
        FILE_SYNCHRONOUS_IO_NONALERT;
    // 1. 初始化OBJECT_ATTRIBUTES的内容
    OBJECT_ATTRIBUTES objAttrib = { 0 };
    ULONG             ulAttributes =
        OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
    InitializeObjectAttributes(
        &objAttrib,    // 返回初始化完毕的结构体
        pstrFile,      // 文件对象名称
        ulAttributes,  // 对象属性
        NULL, NULL);   // 一般为NULL
    // 2. 创建文件对象
    ulCreateOpt |= bIsDir;
    FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE;
    Status = ZwCreateFile(
        &hFile,                // 返回文件句柄
        GENERIC_ALL,           // 文件操作描述
        &objAttrib,            // OBJECT_ATTRIBUTES
        &StatusBlock,          // 接受函数的操作结果
        0,                     // 初始文件大小
        FILE_ATTRIBUTE_NORMAL, // 新建文件的属性
        ulShareAccess,         // 文件共享方式
        FILE_OPEN_IF,          // 文件存在则打开不存在则创建
        ulCreateOpt,           // 打开操作的附加标志位
        NULL,                  // 扩展属性区
        0);                   // 扩展属性区长度
    if (!NT_SUCCESS(Status))
        return (HANDLE)-1;
    return hFile;
}
void OnHook()
{
    //1 随便打开一个文件,得到一个文件对象
    UNICODE_STRING ustrFilePath;
    RtlInitUnicodeString(&ustrFilePath,
        L"\\??\\D:\\123.txt");
    HANDLE hFile = KernelCreateFile(&ustrFilePath, FALSE);
    PVOID pObject;
    ObReferenceObjectByHandle(hFile, GENERIC_ALL, NULL, KernelMode, &pObject, NULL);

    //2 通过这个文件对象得到OBJECT_TYPE这个结构体
    OBJECT_TYPE * FileType = NULL;
    FileType = (OBJECT_TYPE *)g_OBGetObjectType(pObject);
    //3 把这个函数地址保存起来
    g_OldFun = (PARSEPRODECEDURE)FileType->TypeInfo.ParseProcedure;
    //4 把这个函数地址替换为自己的函数。
    FileType->TypeInfo.ParseProcedure = (ULONG)NewParseProcedure;
}

void OffHook()
{

}

NTSTATUS DriverEntry(
    PDRIVER_OBJECT  pDriver,
    PUNICODE_STRING strRegPath)
{
    // 避免编译器报未引用参数的警告
    UNREFERENCED_PARAMETER(strRegPath);
    // 打印一行字符串,并注册驱动卸载函数,以便于驱动卸载
    DbgBreakPoint();//_asm int 3
    __try
    {
        GetObjectTypeAddress();
        OnHook();
    }
    except(1)
    {
        KdPrint(("掠过一个异常\n"));
    }
    KdPrint(("My First Dirver!"));
    pDriver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}
VOID DriverUnload(PDRIVER_OBJECT objDriver) {
    // 避免编译器报未引用参数的警告
    UNREFERENCED_PARAMETER(objDriver);
    // 什么也不做,只打印一行字符串
    KdPrint(("My Dirver is unloading..."));
}

参考资料:

[1]:毛德操,《Windows内核情景分析》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值