对象管理:对象管理器、句柄操作、对象回调、保护进程对象
说到Windows内核里的对象管理,我估计不少朋友第一反应就是“玄学”。其实没那么复杂。说白了,Windows内核里的一切资源——进程、线程、文件、注册表键值——都被抽象成了“对象”。而对象管理器,就是管这些对象的“大管家”。
我刚开始写驱动时,总觉得对象管理器离我挺远。直到有一次,我写了个文件过滤驱动,死活拿不到正确的句柄,蓝屏了一下午。后来才发现,是我对对象头部的理解出了问题。嗯,从那以后,我再也不敢小看这个模块了。
对象管理器的核心架构
对象管理器位于ntoskrnl.exe中,它维护着三个核心数据结构:对象目录、对象类型和对象头部。你可以把对象目录想象成文件系统的目录树,对象类型就是“进程”、“线程”、“事件”这些分类,而对象头部则是每个对象实例的“身份证”。
核心要点:每个内核对象在内存中由两部分组成——对象头部(OBJECT_HEADER)和对象体。对象头部由对象管理器管理,对象体由具体对象类型的管理者(如进程管理器)管理。
句柄操作:用户态与内核态的桥梁
句柄是什么?我习惯把它理解成“对象的门票”。用户态程序不能直接访问内核对象,只能通过句柄来间接操作。每个进程都有一张句柄表,里面存着它打开的所有对象的“门票”。
在内核驱动里,我们经常需要自己打开句柄。比如你想遍历进程,就得先拿到进程对象的句柄。常用的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。
保护进程对象的实战方案
保护进程对象,说白了就是不让别人杀掉你的关键进程。常见的需求有:保护反病毒软件、保护游戏反作弊模块、保护系统关键进程。
实现思路其实不复杂:
- 注册对象回调:监控进程句柄的创建和复制操作
- 判断目标进程:通过进程名、PID、或者进程特征来识别
- 拦截恶意操作:修改 DesiredAccess,或者直接返回拒绝
- 处理绕过手段:有些恶意软件会尝试直接调用 NtOpenProcess,这时候回调就派上用场了
这里有个坑,我踩过好几次:
避坑指南:我曾经只拦截了 OB_OPERATION_HANDLE_CREATE,结果发现恶意软件通过 DuplicateHandle 来获取进程句柄。后来我加上了 OB_OPERATION_HANDLE_DUPLICATE 的监控,才彻底堵住漏洞。所以,保护进程时一定要同时监控创建和复制两种操作。
另外,还有一个细节容易被忽略:回调函数里判断进程时,不要用 PsGetCurrentProcess()。为什么?因为回调是在目标进程的上下文中执行的,但也不一定——有时候是在发起操作的进程上下文中。我建议用 OperationInfo->Object 来获取目标对象,用 OperationInfo->CallerProcess 来获取发起者。
对象管理的高级技巧
除了基本的句柄操作和回调,对象管理器还有一些“隐藏技能”:
- 对象名称空间:你可以用 ObCreateObject 创建自定义对象,然后通过 ObInsertObject 把它加入对象目录。这样其他驱动就能通过名字找到它。
- 对象类型扩展:每个对象类型可以关联一个 TypeInfo,里面定义了对象的操作方法。你可以通过 ObGetObjectType 获取对象类型,然后检查它的特性。
- 句柄表遍历:虽然微软没有公开 API,但通过解析 EPROCESS 结构,可以遍历进程的句柄表。这在做句柄泄漏检测时非常有用。
我的建议:如果你要做进程保护,别只依赖对象回调。配合进程保护回调(PsSetCreateProcessNotifyRoutine)和线程保护回调,效果会更好。多层防护总比单层靠谱,你想想看,恶意软件也在不断进化,咱们得比它们多想一步。
嗯,对象管理这块内容其实挺深的。今天讲的这些,足够你应付大部分驱动开发场景了。记住一句话:对象管理器是内核的基石,理解它,你就能理解Windows内核的一半。
公众号:蓝海资料掘金营,微信deep3321