Windows内核安全

8、监控进程与拦截进程

1、课程目标

  1. 掌握进程创建回调的注册和使用
  2. 实现进程创建/退出监控
  3. 学会拦截和阻止特定进程启动
  4. 理解进程监控在安全软件中的应用

2、名词解释

术语 解释
PsSetCreateProcessNotifyRoutine 注册进程通知回调
PsSetCreateProcessNotifyRoutineEx 扩展版进程回调,支持拦截
PEPROCESS 进程执行体对象指针
PS_CREATE_NOTIFY_INFO 进程创建信息结构

3、使用工具

工具 用途
Process Monitor 对比监控结果
WinDbg 调试回调函数
Process Explorer 查看进程树

4、技术原理

4.1、进程回调机制

┌─────────────────────────────────────────────────────────────┐
│                    进程创建回调流程                          │
│                                                             │
│   CreateProcess()                                           │
│        │                                                    │
│        ↓                                                    │
│   NtCreateUserProcess()                                     │
│        │                                                    │
│        ↓                                                    │
│   PspInsertProcess()                                        │
│        │                                                    │
│        ↓                                                    │
│   ┌─────────────────────────────────────────┐              │
│   │     调用所有注册的回调函数               │              │
│   │  PsSetCreateProcessNotifyRoutineEx回调  │              │
│   │                                         │              │
│   │  if (Info->CreationStatus可设置)        │              │
│   │      可以阻止进程创建                    │              │
│   └─────────────────────────────────────────┘              │
│        │                                                    │
│        ↓                                                    │
│   完成进程创建 或 创建失败                                   │
└─────────────────────────────────────────────────────────────┘

5、代码实现

5.1、示例1:基础进程监控

// ProcessMonitor.c - 基础进程监控
#include <ntddk.h>

// 进程回调函数(不可拦截版本)
VOID ProcessNotifyCallback(
    HANDLE ParentId,
    HANDLE ProcessId,
    BOOLEAN Create
) {
    if (Create) {
        // 进程创建
        PEPROCESS process;
        if (NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &process))) {
            PUCHAR imageName = PsGetProcessImageFileName(process);
            DbgPrint("[Process] Created: PID=%d, Parent=%d, Name=%s\n",
                     (ULONG)(ULONG_PTR)ProcessId,
                     (ULONG)(ULONG_PTR)ParentId,
                     imageName ? (char*)imageName : "Unknown");
            ObDereferenceObject(process);
        }
    } else {
        // 进程退出
        DbgPrint("[Process] Exited: PID=%d\n", (ULONG)(ULONG_PTR)ProcessId);
    }
}

// 注册回调
NTSTATUS RegisterProcessCallback() {
    return PsSetCreateProcessNotifyRoutine(ProcessNotifyCallback, FALSE);
}

// 注销回调
VOID UnregisterProcessCallback() {
    PsSetCreateProcessNotifyRoutine(ProcessNotifyCallback, TRUE);
}

5.2、示例2:扩展进程监控(可拦截)

// ProcessMonitorEx.c - 扩展进程监控
#include <ntddk.h>
#include <ntstrsafe.h>

// 黑名单进程列表
WCHAR* g_BlackList[] = {
    L"cmd.exe",
    L"powershell.exe",
    L"regedit.exe",
    L"taskmgr.exe"
};
#define BLACKLIST_COUNT (sizeof(g_BlackList) / sizeof(g_BlackList[0]))

BOOLEAN g_EnableBlock = FALSE;

// 检查是否在黑名单中
BOOLEAN IsInBlackList(PUNICODE_STRING ImageFileName) {
    for (ULONG i = 0; i < BLACKLIST_COUNT; i++) {
        UNICODE_STRING blackItem;
        RtlInitUnicodeString(&blackItem, g_BlackList[i]);
        
        // 检查文件名是否以黑名单项结尾
        if (ImageFileName->Length >= blackItem.Length) {
            UNICODE_STRING suffix;
            suffix.Buffer = ImageFileName->Buffer + 
                           (ImageFileName->Length - blackItem.Length) / sizeof(WCHAR);
            suffix.Length = blackItem.Length;
            suffix.MaximumLength = blackItem.Length;
            
            if (RtlEqualUnicodeString(&suffix, &blackItem, TRUE)) {
                return TRUE;
            }
        }
    }
    return FALSE;
}

// 扩展进程回调(支持拦截)
VOID ProcessNotifyCallbackEx(
    PEPROCESS Process,
    HANDLE ProcessId,
    PPS_CREATE_NOTIFY_INFO CreateInfo
) {
    UNREFERENCED_PARAMETER(Process);
    
    if (CreateInfo) {
        // 进程创建
        DbgPrint("[ProcessEx] Creating: PID=%d\n", (ULONG)(ULONG_PTR)ProcessId);
        
        if (CreateInfo->ImageFileName) {
            DbgPrint("[ProcessEx] Image: %wZ\n", CreateInfo->ImageFileName);
            
            // 检查是否需要阻止
            if (g_EnableBlock && IsInBlackList(CreateInfo->ImageFileName)) {
                DbgPrint("[ProcessEx] BLOCKED: %wZ\n", CreateInfo->ImageFileName);
                
                // 阻止进程创建
                CreateInfo->CreationStatus = STATUS_ACCESS_DENIED;
            }
        }
        
        if (CreateInfo->CommandLine) {
            DbgPrint("[ProcessEx] CommandLine: %wZ\n", CreateInfo->CommandLine);
        }
        
        if (CreateInfo->FileOpenNameAvailable && CreateInfo->ImageFileName) {
            DbgPrint("[ProcessEx] Full Path: %wZ\n", CreateInfo->ImageFileName);
        }
        
        DbgPrint("[ProcessEx] Parent: PID=%d\n", 
                 (ULONG)(ULONG_PTR)CreateInfo->ParentProcessId);
    } else {
        // 进程退出
        DbgPrint("[ProcessEx] Exited: PID=%d\n", (ULONG)(ULONG_PTR)ProcessId);
    }
}

// 注册扩展回调
NTSTATUS RegisterProcessCallbackEx() {
    return PsSetCreateProcessNotifyRoutineEx(ProcessNotifyCallbackEx, FALSE);
}

// 注销扩展回调
VOID UnregisterProcessCallbackEx() {
    PsSetCreateProcessNotifyRoutineEx(ProcessNotifyCallbackEx, TRUE);
}

5.3、示例3:完整的进程保护驱动

// ProcessProtect.c - 完整进程保护驱动
#include <ntddk.h>

#define DEVICE_NAME     L"\\Device\\ProcessProtect"
#define SYMBOLIC_NAME   L"\\DosDevices\\ProcessProtect"

#define IOCTL_SET_PROTECT CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_ADD_BLOCK   CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_CLEAR_BLOCK CTL_CODE(0x8000, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)

// 保护的进程ID列表
#define MAX_PROTECTED_PIDS 64
ULONG g_ProtectedPids[MAX_PROTECTED_PIDS];
ULONG g_ProtectedCount = 0;
KSPIN_LOCK g_ProtectLock;

// 阻止的进程名列表
#define MAX_BLOCKED_NAMES 32
UNICODE_STRING g_BlockedNames[MAX_BLOCKED_NAMES];
ULONG g_BlockedCount = 0;
KSPIN_LOCK g_BlockLock;

PDEVICE_OBJECT g_DeviceObject = NULL;

// 检查进程是否受保护
BOOLEAN IsProcessProtected(HANDLE ProcessId) {
    KIRQL oldIrql;
    BOOLEAN result = FALSE;
    
    KeAcquireSpinLock(&g_ProtectLock, &oldIrql);
    
    for (ULONG i = 0; i < g_ProtectedCount; i++) {
        if (g_ProtectedPids[i] == (ULONG)(ULONG_PTR)ProcessId) {
            result = TRUE;
            break;
        }
    }
    
    KeReleaseSpinLock(&g_ProtectLock, oldIrql);
    return result;
}

// 添加受保护进程
BOOLEAN AddProtectedPid(ULONG Pid) {
    KIRQL oldIrql;
    BOOLEAN result = FALSE;
    
    KeAcquireSpinLock(&g_ProtectLock, &oldIrql);
    
    if (g_ProtectedCount < MAX_PROTECTED_PIDS) {
        g_ProtectedPids[g_ProtectedCount++] = Pid;
        result = TRUE;
    }
    
    KeReleaseSpinLock(&g_ProtectLock, oldIrql);
    return result;
}

// 检查是否被阻止
BOOLEAN IsProcessBlocked(PUNICODE_STRING ImageName) {
    KIRQL oldIrql;
    BOOLEAN result = FALSE;
    
    if (!ImageName || !ImageName->Buffer) return FALSE;
    
    KeAcquireSpinLock(&g_BlockLock, &oldIrql);
    
    for (ULONG i = 0; i < g_BlockedCount; i++) {
        if (g_BlockedNames[i].Buffer) {
            // 检查是否包含被阻止的名称
            // 简化:使用后缀匹配
            if (ImageName->Length >= g_BlockedNames[i].Length) {
                UNICODE_STRING suffix;
                suffix.Buffer = ImageName->Buffer + 
                    (ImageName->Length - g_BlockedNames[i].Length) / sizeof(WCHAR);
                suffix.Length = g_BlockedNames[i].Length;
                suffix.MaximumLength = g_BlockedNames[i].Length;
                
                if (RtlEqualUnicodeString(&suffix, &g_BlockedNames[i], TRUE)) {
                    result = TRUE;
                    break;
                }
            }
        }
    }
    
    KeReleaseSpinLock(&g_BlockLock, oldIrql);
    return result;
}

// 进程创建回调
VOID ProcessCreateCallback(
    PEPROCESS Process,
    HANDLE ProcessId,
    PPS_CREATE_NOTIFY_INFO CreateInfo
) {
    UNREFERENCED_PARAMETER(Process);
    
    if (CreateInfo) {
        // 进程创建
        if (CreateInfo->ImageFileName && IsProcessBlocked(CreateInfo->ImageFileName)) {
            DbgPrint("[Protect] Blocking process: %wZ\n", CreateInfo->ImageFileName);
            CreateInfo->CreationStatus = STATUS_ACCESS_DENIED;
        }
    } else {
        // 进程退出 - 从保护列表移除
        KIRQL oldIrql;
        KeAcquireSpinLock(&g_ProtectLock, &oldIrql);
        
        for (ULONG i = 0; i < g_ProtectedCount; i++) {
            if (g_ProtectedPids[i] == (ULONG)(ULONG_PTR)ProcessId) {
                // 移除:用最后一个替换当前
                g_ProtectedPids[i] = g_ProtectedPids[--g_ProtectedCount];
                break;
            }
        }
        
        KeReleaseSpinLock(&g_ProtectLock, oldIrql);
    }
}

// 对象操作回调 - 保护进程句柄
OB_PREOP_CALLBACK_STATUS ObjectPreCallback(
    PVOID RegistrationContext,
    POB_PRE_OPERATION_INFORMATION OperationInfo
) {
    UNREFERENCED_PARAMETER(RegistrationContext);
    
    if (OperationInfo->ObjectType != *PsProcessType) {
        return OB_PREOP_SUCCESS;
    }
    
    PEPROCESS process = (PEPROCESS)OperationInfo->Object;
    HANDLE processId = PsGetProcessId(process);
    
    // 检查是否受保护
    if (IsProcessProtected(processId)) {
        // 当前进程是否也受保护
        if (!IsProcessProtected(PsGetCurrentProcessId())) {
            // 移除危险权限
            if (OperationInfo->Operation == OB_OPERATION_HANDLE_CREATE) {
                OperationInfo->Parameters->CreateHandleInformation.DesiredAccess &= 
                    ~(PROCESS_TERMINATE | PROCESS_VM_WRITE | 
                      PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD);
            }
        }
    }
    
    return OB_PREOP_SUCCESS;
}

// IOCTL处理
NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
    UNREFERENCED_PARAMETER(DeviceObject);
    
    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
    PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
    ULONG inputLen = irpStack->Parameters.DeviceIoControl.InputBufferLength;
    NTSTATUS status = STATUS_SUCCESS;
    
    switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {
        case IOCTL_SET_PROTECT: {
            if (inputLen >= sizeof(ULONG)) {
                ULONG pid = *(PULONG)buffer;
                if (AddProtectedPid(pid)) {
                    DbgPrint("[Protect] Added PID: %d\n", pid);
                } else {
                    status = STATUS_INSUFFICIENT_RESOURCES;
                }
            }
            break;
        }
        
        case IOCTL_ADD_BLOCK: {
            // 添加阻止的进程名
            if (buffer && inputLen > 0) {
                KIRQL oldIrql;
                KeAcquireSpinLock(&g_BlockLock, &oldIrql);
                
                if (g_BlockedCount < MAX_BLOCKED_NAMES) {
                    PUNICODE_STRING pName = &g_BlockedNames[g_BlockedCount];
                    pName->MaximumLength = (USHORT)inputLen + sizeof(WCHAR);
                    pName->Buffer = ExAllocatePoolWithTag(NonPagedPool, 
                                                          pName->MaximumLength, 'kolB');
                    if (pName->Buffer) {
                        RtlCopyMemory(pName->Buffer, buffer, inputLen);
                        pName->Length = (USHORT)inputLen;
                        g_BlockedCount++;
                        DbgPrint("[Protect] Added block: %wZ\n", pName);
                    }
                }
                
                KeReleaseSpinLock(&g_BlockLock, oldIrql);
            }
            break;
        }
        
        case IOCTL_CLEAR_BLOCK: {
            KIRQL oldIrql;
            KeAcquireSpinLock(&g_BlockLock, &oldIrql);
            
            for (ULONG i = 0; i < g_BlockedCount; i++) {
                if (g_BlockedNames[i].Buffer) {
                    ExFreePoolWithTag(g_BlockedNames[i].Buffer, 'kolB');
                    g_BlockedNames[i].Buffer = NULL;
                }
            }
            g_BlockedCount = 0;
            
            KeReleaseSpinLock(&g_BlockLock, oldIrql);
            break;
        }
    }
    
    Irp->IoStatus.Status = status;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    
    return status;
}

5.4、示例4:用户模式控制程序

// ProcessProtectClient.c - 用户模式控制
#include <windows.h>
#include <stdio.h>

#define DEVICE_NAME "\\\\.\\ProcessProtect"

#define IOCTL_SET_PROTECT CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_ADD_BLOCK   CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_CLEAR_BLOCK CTL_CODE(0x8000, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)

int main(int argc, char* argv[]) {
    HANDLE hDevice;
    DWORD bytesReturned;
    
    hDevice = CreateFileA(DEVICE_NAME, GENERIC_READ | GENERIC_WRITE,
                          0, NULL, OPEN_EXISTING, 0, NULL);
    
    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("Failed to open device: %d\n", GetLastError());
        return 1;
    }
    
    if (argc < 2) {
        printf("Usage:\n");
        printf("  %s protect <pid>     - Protect a process\n", argv[0]);
        printf("  %s block <name.exe>  - Block a process name\n", argv[0]);
        printf("  %s clear             - Clear block list\n", argv[0]);
        CloseHandle(hDevice);
        return 0;
    }
    
    if (strcmp(argv[1], "protect") == 0 && argc >= 3) {
        ULONG pid = atoi(argv[2]);
        if (DeviceIoControl(hDevice, IOCTL_SET_PROTECT, 
                           &pid, sizeof(pid), NULL, 0, &bytesReturned, NULL)) {
            printf("Protected PID: %d\n", pid);
        }
    }
    else if (strcmp(argv[1], "block") == 0 && argc >= 3) {
        WCHAR name[256];
        MultiByteToWideChar(CP_ACP, 0, argv[2], -1, name, 256);
        ULONG len = (ULONG)wcslen(name) * sizeof(WCHAR);
        
        if (DeviceIoControl(hDevice, IOCTL_ADD_BLOCK,
                           name, len, NULL, 0, &bytesReturned, NULL)) {
            printf("Blocked: %s\n", argv[2]);
        }
    }
    else if (strcmp(argv[1], "clear") == 0) {
        if (DeviceIoControl(hDevice, IOCTL_CLEAR_BLOCK,
                           NULL, 0, NULL, 0, &bytesReturned, NULL)) {
            printf("Block list cleared\n");
        }
    }
    
    CloseHandle(hDevice);
    return 0;
}

6、课后作业

  1. 实现进程父子关系验证
  2. 添加进程命令行参数过滤
  3. 实现进程创建日志记录到文件
  4. 编写规则引擎支持复杂的进程策略

7、扩展阅读

  • PsSetCreateProcessNotifyRoutineEx2
  • 进程回调的性能影响
  • 杀毒软件的进程监控实现