对象管理:对象管理器、句柄操作、对象回调、保护进程对象

说到Windows内核里的对象管理,我估计不少朋友第一反应就是“玄学”。其实没那么复杂。说白了,Windows内核里的一切资源——进程、线程、文件、注册表键值——都被抽象成了“对象”。而对象管理器,就是管这些对象的“大管家”。

我刚开始写驱动时,总觉得对象管理器离我挺远。直到有一次,我写了个文件过滤驱动,死活拿不到正确的句柄,蓝屏了一下午。后来才发现,是我对对象头部的理解出了问题。嗯,从那以后,我再也不敢小看这个模块了。

对象管理器的核心架构

对象管理器位于ntoskrnl.exe中,它维护着三个核心数据结构:对象目录、对象类型和对象头部。你可以把对象目录想象成文件系统的目录树,对象类型就是“进程”、“线程”、“事件”这些分类,而对象头部则是每个对象实例的“身份证”。

核心要点:每个内核对象在内存中由两部分组成——对象头部(OBJECT_HEADER)和对象体。对象头部由对象管理器管理,对象体由具体对象类型的管理者(如进程管理器)管理。

Windows 对象管理器架构 对象目录 (\Object\...) 对象类型 (OBJECT_TYPE) 进程句柄表 OBJECT_HEADER 结构 句柄计数 | 引用计数 | 安全描述符指针 | 对象类型指针 | 名称信息 | 创建信息 对象体 (由具体类型定义) 例如:EPROCESS(进程对象)、ETHREAD(线程对象)、FILE_OBJECT(文件对象) 句柄操作流程 用户态 → ObOpenObjectByName() → 对象管理器查找 → 句柄表插入 → 返回句柄值 → 用户态使用 对象回调机制 ObRegisterCallbacks() → 进程/线程创建/复制/删除时触发 → 可用于保护进程对象

句柄操作:用户态与内核态的桥梁

句柄是什么?我习惯把它理解成“对象的门票”。用户态程序不能直接访问内核对象,只能通过句柄来间接操作。每个进程都有一张句柄表,里面存着它打开的所有对象的“门票”。

在内核驱动里,我们经常需要自己打开句柄。比如你想遍历进程,就得先拿到进程对象的句柄。常用的API有这些:

API 函数 用途 注意事项
ObOpenObjectByName 通过对象名称打开句柄 需要对象有名字,如 \Device\HarddiskVolume1
ObReferenceObjectByHandle 通过句柄获取对象指针 会增加引用计数,记得 Dereference
ZwOpenProcess 打开进程对象句柄 需要 PROCESS_ACCESS 权限
ZwDuplicateObject 复制句柄到其他进程 可用于跨进程操作

个人经验:我在写进程保护驱动时,经常用 ObReferenceObjectByHandle 来验证句柄是否有效。但要注意,这个函数返回的是对象指针,用完后必须调用 ObDereferenceObject,否则会造成对象泄漏。我曾经因为这个疏忽,导致系统运行几天后内存暴涨——教训深刻。

对象回调:监控和保护的关键

对象回调是Windows Vista之后引入的机制。它允许驱动注册回调函数,在进程或线程对象被创建、复制、删除时得到通知。说白了,就是对象管理器在干活时,会喊你一声:“嘿,有人要动这个对象了,你要不要管管?”

注册对象回调的步骤很简单:

// 1. 定义回调函数
OB_PREOP_CALLBACK_STATUS PreProcessCallback(
    PVOID RegistrationContext,
    POB_PRE_OPERATION_INFORMATION OperationInfo
)
{
    // 检查操作类型
    if (OperationInfo->Operation == OB_OPERATION_HANDLE_CREATE)
    {
        // 获取目标进程对象
        PEPROCESS TargetProcess = (PEPROCESS)OperationInfo->Object;
        
        // 检查是否需要保护
        if (IsProtectedProcess(TargetProcess))
        {
            // 阻止操作
            OperationInfo->Parameters->CreateHandleInformation.DesiredAccess = 0;
            return OB_PREOP_SUCCESS;
        }
    }
    return OB_PREOP_SUCCESS;
}

// 2. 注册回调
OB_CALLBACK_REGISTRATION CallbackReg;
OB_OPERATION_REGISTRATION OpReg;

OpReg.ObjectType = PsProcessType;
OpReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
OpReg.PreOperation = PreProcessCallback;
OpReg.PostOperation = NULL;

CallbackReg.Version = OB_FLT_REGISTRATION_VERSION;
CallbackReg.OperationRegistrationCount = 1;
CallbackReg.RegistrationContext = NULL;
CallbackReg.OperationRegistration = &OpReg;

NTSTATUS status = ObRegisterCallbacks(&CallbackReg, &RegistrationHandle);
if (!NT_SUCCESS(status))
{
    // 处理错误
}

注意:对象回调是在DISPATCH_LEVEL级别执行的。这意味着你不能在里面做太多事情,比如不能等待、不能访问分页内存。我见过有人试图在回调里调用 ZwOpenProcess——直接蓝屏,因为 Zw 系列函数需要 PASSIVE_LEVEL。

保护进程对象的实战方案

保护进程对象,说白了就是不让别人杀掉你的关键进程。常见的需求有:保护反病毒软件、保护游戏反作弊模块、保护系统关键进程。

实现思路其实不复杂:

  1. 注册对象回调:监控进程句柄的创建和复制操作
  2. 判断目标进程:通过进程名、PID、或者进程特征来识别
  3. 拦截恶意操作:修改 DesiredAccess,或者直接返回拒绝
  4. 处理绕过手段:有些恶意软件会尝试直接调用 NtOpenProcess,这时候回调就派上用场了

这里有个坑,我踩过好几次:

避坑指南:我曾经只拦截了 OB_OPERATION_HANDLE_CREATE,结果发现恶意软件通过 DuplicateHandle 来获取进程句柄。后来我加上了 OB_OPERATION_HANDLE_DUPLICATE 的监控,才彻底堵住漏洞。所以,保护进程时一定要同时监控创建和复制两种操作。

另外,还有一个细节容易被忽略:回调函数里判断进程时,不要用 PsGetCurrentProcess()。为什么?因为回调是在目标进程的上下文中执行的,但也不一定——有时候是在发起操作的进程上下文中。我建议用 OperationInfo->Object 来获取目标对象,用 OperationInfo->CallerProcess 来获取发起者。

对象管理的高级技巧

除了基本的句柄操作和回调,对象管理器还有一些“隐藏技能”:

  • 对象名称空间:你可以用 ObCreateObject 创建自定义对象,然后通过 ObInsertObject 把它加入对象目录。这样其他驱动就能通过名字找到它。
  • 对象类型扩展:每个对象类型可以关联一个 TypeInfo,里面定义了对象的操作方法。你可以通过 ObGetObjectType 获取对象类型,然后检查它的特性。
  • 句柄表遍历:虽然微软没有公开 API,但通过解析 EPROCESS 结构,可以遍历进程的句柄表。这在做句柄泄漏检测时非常有用。

我的建议:如果你要做进程保护,别只依赖对象回调。配合进程保护回调(PsSetCreateProcessNotifyRoutine)和线程保护回调,效果会更好。多层防护总比单层靠谱,你想想看,恶意软件也在不断进化,咱们得比它们多想一步。

嗯,对象管理这块内容其实挺深的。今天讲的这些,足够你应付大部分驱动开发场景了。记住一句话:对象管理器是内核的基石,理解它,你就能理解Windows内核的一半。


公众号:蓝海资料掘金营,微信deep3321