ObRegisterCallbacks 是唯一受支持的驱动级进程打开拦截机制,通过注册对象回调在内核中拦截所有 OpenProcess 调用路径,包括 syscall、ZwOpenProcess 等绕过方式,关键在于正确配置 Altitude、安全引用 EPROCESS、快速判断 PID 并设置 GenerateAccessBits=FALSE。

OpenProcess 被拦截的本质是绕过用户态 API,直接调用内核对象管理例程
Windows 用户态的 OpenProcess 最终会调用内核中的 ObOpenObjectByPointer 或 ObOpenObjectByName,而真正决定是否放行的关键点,在于 ObRegisterCallbacks 注册的对象回调(Object Callbacks)——不是挂钩 NtOpenProcess,而是让系统在打开进程对象前主动咨询你的回调函数。
这意味着:你不能靠 patch NtOpenProcess 或 hook kernel32!OpenProcess 实现可靠拦截;那些方法容易被绕过(比如直接 syscall、用 ZwOpenProcess、或通过其他对象句柄间接访问)。真正的驱动级阻止必须落在对象创建/打开路径上。
-
ObRegisterCallbacks是唯一受支持的、可审计的、能拦截所有进程对象打开行为的机制(从 Windows Vista SP1 开始稳定可用) - 回调函数运行在
KernelMode,且处于PassiveLevel(对进程对象),但必须注意:不能执行可能导致分页错误的操作(如访问用户地址、使用分页池) - 注册后,每次有进程调用
OpenProcess、CreateToolhelp32Snapshot、甚至某些调试器枚举行为,都会触发你的PreOperation回调
ObRegisterCallbacks 的关键参数和权限检查逻辑怎么写
回调注册结构体 OB_CALLBACK_REGISTRATION 中,Altitude 字段必须唯一且高于常见安全软件(推荐用 L"380000" 以上,如 L"385000"),否则注册失败并返回 STATUS_FLT_INSTANCE_ALTITUDE_COLLISION;RegistrationContext 可传入自定义结构指针,用于在回调中识别策略上下文。
在 PreOperation 回调里,你要检查:OperationInformation->ObjectType == *PsProcessType、OperationInformation->ObjectInformationClass == ObQueryNameString(非必需)以及最关键的 OperationInformation->Operation == OB_OPERATION_HANDLE_CREATE —— 这才是“试图获得句柄”的信号。不要只看 AccessMode == UserMode,因为内核驱动也可能合法调用。
- 阻止逻辑应基于
OperationInformation->Object(即目标EPROCESS)提取 PID:PsGetProcessId(reinterpret_cast(OperationInformation->Object)) - 若需按白名单放行,建议预存 PID 到
RTL_AVL_TABLE或EX_PUSH_LOCK保护的哈希表,避免每次遍历链表 - 调用
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess可获取请求权限,例如PROCESS_ALL_ACCESS或PROCESS_VM_READ,可用于细粒度控制 - 阻止动作只需设置:
OperationInformation->Parameters->CreateHandleInformation.GenerateAccessBits = FALSE;并返回STATUS_ACCESS_DENIED
为什么 ObRegisterCallbacks 容易注册失败或蓝屏
最常见的崩溃原因是:在 PreOperation 回调中调用了不可重入或不可分页的函数,比如 DbgPrint(尤其带字符串格式化时)、ExAllocatePoolWithTag(未指定 NonPagedPoolNx)、或访问了已释放的 EPROCESS 地址(目标进程可能正在退出)。
- 必须用
ObReferenceObjectSafe包装OperationInformation->Object,再确认非 NULL,否则进程退出瞬间回调仍可能触发,导致访问无效内存 -
ObRegisterCallbacks必须在 DriverEntry 中调用,且在DriverUnload中严格配对调用ObUnregisterCallbacks;漏掉后者会导致系统无法卸载驱动、后续重启失败 - 测试阶段务必关闭 PatchGuard 相关保护(如禁用 HVCI / Memory Integrity),否则签名不合规的驱动会触发
CRITICAL_STRUCTURE_CORRUPTION - 不要在回调里做耗时操作(如文件 I/O、网络请求),这会拖慢整个系统句柄创建路径,引发超时或死锁
用户态配合验证是否生效:用 NtOpenProcess + syscall 绕过检测
仅靠 OpenProcess 测试不够。真实攻击者会用 NtOpenProcess(ntdll 导出)、或直接构造 syscall 指令调用 NtOpenProcess(绕过 ntdll hook),甚至用 ZwDuplicateObject 复制已有句柄。这些路径全都被 ObRegisterCallbacks 覆盖,因为它们最终都走到 ObOpenObjectByPointer。
验证方式:写一个最小用户态程序,用 __syscalls 内联汇编调用 NtOpenProcess,目标 PID 设为自身或系统进程(如 csrss.exe),观察是否返回 STATUS_ACCESS_DENIED;同时用 WinDbg 的 !obtrace 命令确认回调是否被触发。
- 确保测试进程以普通权限运行(非 SYSTEM),否则可能因 SeDebugPrivilege 特权绕过你的策略(除非你在回调中显式检查
SeSinglePrivilegeCheck) - 若发现某些工具(如 Process Hacker)仍能打开,检查它是否用了
ReadProcessMemory+VirtualAllocEx等间接方式 —— 那不属于对象打开,你的回调管不到,需另加PsSetCreateProcessNotifyRoutineEx或 EPT 钩子 - 驱动签名必须是 EV 级别(Windows 10 1607+ 强制要求),否则无法加载到启用了 Driver Signature Enforcement 的系统
真正难的是在回调里做快速、安全、可扩展的决策:PID 判断要快,策略更新要原子,错误处理要静默。很多人卡在 ObReferenceObjectSafe 返回 NULL 后没兜底,或者 Altitude 冲突时没查注册表残留项,结果反复蓝屏。这些细节不写进代码注释,上线后就是半夜告警。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











