1、课程目标
- 理解Windows对象管理器机制
- 掌握ObRegisterCallbacks的使用
- 实现进程和线程句柄监控
- 学会防止进程被终止或注入
2、名词解释
| 术语 |
解释 |
| ObRegisterCallbacks |
注册对象操作回调 |
| OB_OPERATION_HANDLE_CREATE |
创建句柄操作 |
| OB_OPERATION_HANDLE_DUPLICATE |
复制句柄操作 |
| DesiredAccess |
请求的访问权限 |
3、使用工具
| 工具 |
用途 |
| WinDbg |
调试对象回调 |
| Process Explorer |
查看进程句柄 |
| Handle |
命令行句柄查看工具 |
4、技术原理
4.1、对象回调机制
┌─────────────────────────────────────────────────────────────┐
│ 对象操作回调流程 │
│ │
│ OpenProcess() / NtOpenProcess() │
│ │ │
│ ↓ │
│ ObpIncrementHandleCountEx() │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ 调用Pre-Operation回调 │ │
│ │ OB_PRE_OPERATION_INFORMATION │ │
│ │ │ │
│ │ 可以修改: │ │
│ │ - DesiredAccess(移除危险权限) │ │
│ │ 返回: │ │
│ │ - OB_PREOP_SUCCESS (允许) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ 执行实际的句柄操作 │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ 调用Post-Operation回调 │ │
│ │ OB_POST_OPERATION_INFORMATION │ │
│ │ (操作已完成,只能记录) │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
5、代码实现
5.1、示例1:基础对象回调
// ObjectCallback.c - 基础对象回调
#include <ntddk.h>
// 全局回调句柄
PVOID g_RegistrationHandle = NULL;
// 保护的进程列表
ULONG g_ProtectedPids[64];
ULONG g_ProtectedCount = 0;
KSPIN_LOCK g_Lock;
BOOLEAN IsProtectedProcess(HANDLE ProcessId) {
KIRQL oldIrql;
BOOLEAN result = FALSE;
KeAcquireSpinLock(&g_Lock, &oldIrql);
for (ULONG i = 0; i < g_ProtectedCount; i++) {
if (g_ProtectedPids[i] == (ULONG)(ULONG_PTR)ProcessId) {
result = TRUE;
break;
}
}
KeReleaseSpinLock(&g_Lock, oldIrql);
return result;
}
// Pre-Operation回调 - 进程对象
OB_PREOP_CALLBACK_STATUS ProcessPreCallback(
PVOID RegistrationContext,
POB_PRE_OPERATION_INFORMATION OperationInfo
) {
UNREFERENCED_PARAMETER(RegistrationContext);
// 只处理进程对象
if (OperationInfo->ObjectType != *PsProcessType) {
return OB_PREOP_SUCCESS;
}
PEPROCESS targetProcess = (PEPROCESS)OperationInfo->Object;
HANDLE targetPid = PsGetProcessId(targetProcess);
HANDLE currentPid = PsGetCurrentProcessId();
// 如果目标进程受保护
if (IsProtectedProcess(targetPid)) {
// 不限制受保护进程自己
if (targetPid == currentPid) {
return OB_PREOP_SUCCESS;
}
// 获取访问进程信息
PEPROCESS currentProcess = PsGetCurrentProcess();
PUCHAR currentName = PsGetProcessImageFileName(currentProcess);
PUCHAR targetName = PsGetProcessImageFileName(targetProcess);
DbgPrint("[Object] %s (PID=%d) accessing %s (PID=%d)\n",
currentName, (ULONG)(ULONG_PTR)currentPid,
targetName, (ULONG)(ULONG_PTR)targetPid);
// 修改访问权限
if (OperationInfo->Operation == OB_OPERATION_HANDLE_CREATE) {
ACCESS_MASK denyMask =
PROCESS_TERMINATE | // 终止进程
PROCESS_VM_WRITE | // 写内存
PROCESS_VM_OPERATION | // 虚拟内存操作
PROCESS_CREATE_THREAD | // 创建线程
PROCESS_SUSPEND_RESUME; // 挂起/恢复
ACCESS_MASK original =
OperationInfo->Parameters->CreateHandleInformation.DesiredAccess;
OperationInfo->Parameters->CreateHandleInformation.DesiredAccess &= ~denyMask;
if (original != OperationInfo->Parameters->CreateHandleInformation.DesiredAccess) {
DbgPrint("[Object] Modified access: 0x%X -> 0x%X\n",
original,
OperationInfo->Parameters->CreateHandleInformation.DesiredAccess);
}
}
else if (OperationInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
ACCESS_MASK denyMask =
PROCESS_TERMINATE | PROCESS_VM_WRITE | PROCESS_VM_OPERATION;
OperationInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= ~denyMask;
}
}
return OB_PREOP_SUCCESS;
}
// Post-Operation回调
VOID ProcessPostCallback(
PVOID RegistrationContext,
POB_POST_OPERATION_INFORMATION OperationInfo
) {
UNREFERENCED_PARAMETER(RegistrationContext);
UNREFERENCED_PARAMETER(OperationInfo);
// 操作已完成,可以记录日志
}
// 注册对象回调
NTSTATUS RegisterObjectCallbacks() {
NTSTATUS status;
OB_CALLBACK_REGISTRATION callbackReg;
OB_OPERATION_REGISTRATION opReg[1];
// 配置进程对象回调
opReg[0].ObjectType = PsProcessType;
opReg[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
opReg[0].PreOperation = ProcessPreCallback;
opReg[0].PostOperation = ProcessPostCallback;
// 配置回调注册
UNICODE_STRING altitude;
RtlInitUnicodeString(&altitude, L"321000"); // 高度值
callbackReg.Version = OB_FLT_REGISTRATION_VERSION;
callbackReg.OperationRegistrationCount = 1;
callbackReg.Altitude = altitude;
callbackReg.RegistrationContext = NULL;
callbackReg.OperationRegistration = opReg;
status = ObRegisterCallbacks(&callbackReg, &g_RegistrationHandle);
if (NT_SUCCESS(status)) {
DbgPrint("[Object] Callbacks registered\n");
} else {
DbgPrint("[Object] Registration failed: 0x%X\n", status);
}
return status;
}
// 注销对象回调
VOID UnregisterObjectCallbacks() {
if (g_RegistrationHandle) {
ObUnRegisterCallbacks(g_RegistrationHandle);
g_RegistrationHandle = NULL;
DbgPrint("[Object] Callbacks unregistered\n");
}
}
5.2、示例2:进程和线程同时保护
// ObjectProtect.c - 进程和线程保护
#include <ntddk.h>
PVOID g_CallbackHandle = NULL;
// 线程Pre-Operation回调
OB_PREOP_CALLBACK_STATUS ThreadPreCallback(
PVOID RegistrationContext,
POB_PRE_OPERATION_INFORMATION OperationInfo
) {
UNREFERENCED_PARAMETER(RegistrationContext);
if (OperationInfo->ObjectType != *PsThreadType) {
return OB_PREOP_SUCCESS;
}
PETHREAD thread = (PETHREAD)OperationInfo->Object;
HANDLE threadProcessId = PsGetThreadProcessId(thread);
HANDLE currentPid = PsGetCurrentProcessId();
// 检查线程所属进程是否受保护
if (IsProtectedProcess(threadProcessId)) {
if (threadProcessId != currentPid) {
// 移除危险权限
if (OperationInfo->Operation == OB_OPERATION_HANDLE_CREATE) {
ACCESS_MASK denyMask =
THREAD_TERMINATE |
THREAD_SUSPEND_RESUME |
THREAD_SET_CONTEXT |
THREAD_SET_INFORMATION |
THREAD_SET_THREAD_TOKEN;
OperationInfo->Parameters->CreateHandleInformation.DesiredAccess &= ~denyMask;
}
}
}
return OB_PREOP_SUCCESS;
}
// 注册进程和线程回调
NTSTATUS RegisterProcessThreadCallbacks() {
NTSTATUS status;
OB_CALLBACK_REGISTRATION callbackReg;
OB_OPERATION_REGISTRATION opReg[2];
// 进程对象回调
opReg[0].ObjectType = PsProcessType;
opReg[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
opReg[0].PreOperation = ProcessPreCallback;
opReg[0].PostOperation = NULL;
// 线程对象回调
opReg[1].ObjectType = PsThreadType;
opReg[1].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
opReg[1].PreOperation = ThreadPreCallback;
opReg[1].PostOperation = NULL;
UNICODE_STRING altitude;
RtlInitUnicodeString(&altitude, L"321001");
callbackReg.Version = OB_FLT_REGISTRATION_VERSION;
callbackReg.OperationRegistrationCount = 2;
callbackReg.Altitude = altitude;
callbackReg.RegistrationContext = NULL;
callbackReg.OperationRegistration = opReg;
status = ObRegisterCallbacks(&callbackReg, &g_CallbackHandle);
return status;
}
5.3、示例3:句柄审计日志
// HandleAudit.c - 句柄审计
#include <ntddk.h>
// 审计日志结构
typedef struct _HANDLE_AUDIT_LOG {
LARGE_INTEGER Timestamp;
HANDLE SourcePid;
HANDLE TargetPid;
ACCESS_MASK RequestedAccess;
ACCESS_MASK GrantedAccess;
ULONG Operation;
CHAR SourceName[16];
CHAR TargetName[16];
LIST_ENTRY ListEntry;
} HANDLE_AUDIT_LOG, *PHANDLE_AUDIT_LOG;
LIST_ENTRY g_AuditList;
KSPIN_LOCK g_AuditLock;
ULONG g_AuditCount = 0;
#define MAX_AUDIT_LOGS 1000
VOID InitAuditLog() {
InitializeListHead(&g_AuditList);
KeInitializeSpinLock(&g_AuditLock);
}
VOID AddAuditLog(
HANDLE SourcePid,
HANDLE TargetPid,
ACCESS_MASK RequestedAccess,
ACCESS_MASK GrantedAccess,
ULONG Operation
) {
KIRQL oldIrql;
PHANDLE_AUDIT_LOG log = (PHANDLE_AUDIT_LOG)ExAllocatePoolWithTag(
NonPagedPool, sizeof(HANDLE_AUDIT_LOG), 'tidA');
if (!log) return;
KeQuerySystemTime(&log->Timestamp);
log->SourcePid = SourcePid;
log->TargetPid = TargetPid;
log->RequestedAccess = RequestedAccess;
log->GrantedAccess = GrantedAccess;
log->Operation = Operation;
// 获取进程名
PEPROCESS sourceProcess, targetProcess;
if (NT_SUCCESS(PsLookupProcessByProcessId(SourcePid, &sourceProcess))) {
PUCHAR name = PsGetProcessImageFileName(sourceProcess);
if (name) strncpy(log->SourceName, (char*)name, 15);
ObDereferenceObject(sourceProcess);
}
if (NT_SUCCESS(PsLookupProcessByProcessId(TargetPid, &targetProcess))) {
PUCHAR name = PsGetProcessImageFileName(targetProcess);
if (name) strncpy(log->TargetName, (char*)name, 15);
ObDereferenceObject(targetProcess);
}
KeAcquireSpinLock(&g_AuditLock, &oldIrql);
// 限制日志数量
if (g_AuditCount >= MAX_AUDIT_LOGS) {
PLIST_ENTRY oldest = RemoveHeadList(&g_AuditList);
PHANDLE_AUDIT_LOG oldLog = CONTAINING_RECORD(oldest, HANDLE_AUDIT_LOG, ListEntry);
ExFreePoolWithTag(oldLog, 'tidA');
g_AuditCount--;
}
InsertTailList(&g_AuditList, &log->ListEntry);
g_AuditCount++;
KeReleaseSpinLock(&g_AuditLock, oldIrql);
}
// 带审计的Pre回调
OB_PREOP_CALLBACK_STATUS AuditPreCallback(
PVOID RegistrationContext,
POB_PRE_OPERATION_INFORMATION OperationInfo
) {
UNREFERENCED_PARAMETER(RegistrationContext);
if (OperationInfo->ObjectType != *PsProcessType) {
return OB_PREOP_SUCCESS;
}
PEPROCESS targetProcess = (PEPROCESS)OperationInfo->Object;
HANDLE targetPid = PsGetProcessId(targetProcess);
HANDLE currentPid = PsGetCurrentProcessId();
if (currentPid != targetPid) {
ACCESS_MASK requestedAccess = 0;
ACCESS_MASK grantedAccess = 0;
if (OperationInfo->Operation == OB_OPERATION_HANDLE_CREATE) {
requestedAccess = OperationInfo->Parameters->CreateHandleInformation.DesiredAccess;
// 如果受保护,修改权限
if (IsProtectedProcess(targetPid)) {
ACCESS_MASK denyMask = PROCESS_TERMINATE | PROCESS_VM_WRITE;
OperationInfo->Parameters->CreateHandleInformation.DesiredAccess &= ~denyMask;
}
grantedAccess = OperationInfo->Parameters->CreateHandleInformation.DesiredAccess;
}
// 记录审计日志
if (requestedAccess != grantedAccess) {
AddAuditLog(currentPid, targetPid, requestedAccess, grantedAccess,
OperationInfo->Operation);
}
}
return OB_PREOP_SUCCESS;
}
// 导出审计日志
NTSTATUS ExportAuditLogs(PVOID Buffer, ULONG BufferSize, PULONG BytesWritten) {
KIRQL oldIrql;
PLIST_ENTRY entry;
ULONG offset = 0;
KeAcquireSpinLock(&g_AuditLock, &oldIrql);
for (entry = g_AuditList.Flink;
entry != &g_AuditList;
entry = entry->Flink) {
if (offset + sizeof(HANDLE_AUDIT_LOG) > BufferSize) break;
PHANDLE_AUDIT_LOG log = CONTAINING_RECORD(entry, HANDLE_AUDIT_LOG, ListEntry);
RtlCopyMemory((PUCHAR)Buffer + offset, log, sizeof(HANDLE_AUDIT_LOG) - sizeof(LIST_ENTRY));
offset += sizeof(HANDLE_AUDIT_LOG) - sizeof(LIST_ENTRY);
}
KeReleaseSpinLock(&g_AuditLock, oldIrql);
*BytesWritten = offset;
return STATUS_SUCCESS;
}
5.4、示例4:完整的对象保护驱动
// ObjectProtectDriver.c - 完整对象保护驱动
#include <ntddk.h>
#define DEVICE_NAME L"\\Device\\ObjectProtect"
#define SYMBOLIC_NAME L"\\DosDevices\\ObjectProtect"
#define IOCTL_ADD_PROTECTED CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_REMOVE_PROTECTED CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_GET_AUDIT_LOGS CTL_CODE(0x8000, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
PDEVICE_OBJECT g_DeviceObject = NULL;
PVOID g_CallbackHandle = NULL;
ULONG g_ProtectedPids[64];
ULONG g_ProtectedCount = 0;
FAST_MUTEX g_ProtectMutex;
BOOLEAN IsProtectedProcess(HANDLE ProcessId) {
BOOLEAN result = FALSE;
ExAcquireFastMutex(&g_ProtectMutex);
for (ULONG i = 0; i < g_ProtectedCount; i++) {
if (g_ProtectedPids[i] == (ULONG)(ULONG_PTR)ProcessId) {
result = TRUE;
break;
}
}
ExReleaseFastMutex(&g_ProtectMutex);
return result;
}
OB_PREOP_CALLBACK_STATUS ObjectPreCallback(
PVOID RegistrationContext,
POB_PRE_OPERATION_INFORMATION OperationInfo
) {
UNREFERENCED_PARAMETER(RegistrationContext);
HANDLE targetPid = NULL;
if (OperationInfo->ObjectType == *PsProcessType) {
targetPid = PsGetProcessId((PEPROCESS)OperationInfo->Object);
} else if (OperationInfo->ObjectType == *PsThreadType) {
targetPid = PsGetThreadProcessId((PETHREAD)OperationInfo->Object);
} else {
return OB_PREOP_SUCCESS;
}
HANDLE currentPid = PsGetCurrentProcessId();
if (targetPid != currentPid && IsProtectedProcess(targetPid)) {
if (OperationInfo->Operation == OB_OPERATION_HANDLE_CREATE) {
ACCESS_MASK denyMask;
if (OperationInfo->ObjectType == *PsProcessType) {
denyMask = PROCESS_TERMINATE | PROCESS_VM_WRITE |
PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD;
} else {
denyMask = THREAD_TERMINATE | THREAD_SUSPEND_RESUME |
THREAD_SET_CONTEXT;
}
OperationInfo->Parameters->CreateHandleInformation.DesiredAccess &= ~denyMask;
}
}
return OB_PREOP_SUCCESS;
}
NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
NTSTATUS status = STATUS_SUCCESS;
ULONG bytesReturned = 0;
switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_ADD_PROTECTED: {
ULONG pid = *(PULONG)buffer;
ExAcquireFastMutex(&g_ProtectMutex);
if (g_ProtectedCount < 64) {
g_ProtectedPids[g_ProtectedCount++] = pid;
DbgPrint("[ObjectProtect] Added PID: %d\n", pid);
}
ExReleaseFastMutex(&g_ProtectMutex);
break;
}
case IOCTL_REMOVE_PROTECTED: {
ULONG pid = *(PULONG)buffer;
ExAcquireFastMutex(&g_ProtectMutex);
for (ULONG i = 0; i < g_ProtectedCount; i++) {
if (g_ProtectedPids[i] == pid) {
g_ProtectedPids[i] = g_ProtectedPids[--g_ProtectedCount];
DbgPrint("[ObjectProtect] Removed PID: %d\n", pid);
break;
}
}
ExReleaseFastMutex(&g_ProtectMutex);
break;
}
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = bytesReturned;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
VOID DriverUnload(PDRIVER_OBJECT DriverObject) {
if (g_CallbackHandle) ObUnRegisterCallbacks(g_CallbackHandle);
UNICODE_STRING symLink;
RtlInitUnicodeString(&symLink, SYMBOLIC_NAME);
IoDeleteSymbolicLink(&symLink);
if (DriverObject->DeviceObject) IoDeleteDevice(DriverObject->DeviceObject);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS status;
UNICODE_STRING deviceName, symbolicName;
ExInitializeFastMutex(&g_ProtectMutex);
RtlInitUnicodeString(&deviceName, DEVICE_NAME);
RtlInitUnicodeString(&symbolicName, SYMBOLIC_NAME);
status = IoCreateDevice(DriverObject, 0, &deviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_DeviceObject);
if (!NT_SUCCESS(status)) return status;
IoCreateSymbolicLink(&symbolicName, &deviceName);
// 注册对象回调
OB_OPERATION_REGISTRATION opReg[2] = {
{ PsProcessType, OB_OPERATION_HANDLE_CREATE, ObjectPreCallback, NULL },
{ PsThreadType, OB_OPERATION_HANDLE_CREATE, ObjectPreCallback, NULL }
};
UNICODE_STRING altitude;
RtlInitUnicodeString(&altitude, L"321000");
OB_CALLBACK_REGISTRATION callbackReg = {
OB_FLT_REGISTRATION_VERSION,
2,
altitude,
NULL,
opReg
};
status = ObRegisterCallbacks(&callbackReg, &g_CallbackHandle);
DriverObject->DriverUnload = DriverUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] =
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchDeviceControl;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDeviceControl;
g_DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
6、课后作业
- 实现完整的进程自我保护功能
- 添加白名单机制允许特定进程访问
- 实现句柄访问统计功能
- 结合进程和线程回调构建完整防护
7、扩展阅读
- Windows对象管理器内部机制
- ObRegisterCallbacks签名要求
- 对象回调的性能影响分析