Windows内核安全

10、对象监控

1、课程目标

  1. 理解Windows对象管理器机制
  2. 掌握ObRegisterCallbacks的使用
  3. 实现进程和线程句柄监控
  4. 学会防止进程被终止或注入

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、课后作业

  1. 实现完整的进程自我保护功能
  2. 添加白名单机制允许特定进程访问
  3. 实现句柄访问统计功能
  4. 结合进程和线程回调构建完整防护

7、扩展阅读

  • Windows对象管理器内部机制
  • ObRegisterCallbacks签名要求
  • 对象回调的性能影响分析