1、课程目标
- 掌握进程创建回调的注册和使用
- 实现进程创建/退出监控
- 学会拦截和阻止特定进程启动
- 理解进程监控在安全软件中的应用
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、课后作业
- 实现进程父子关系验证
- 添加进程命令行参数过滤
- 实现进程创建日志记录到文件
- 编写规则引擎支持复杂的进程策略
7、扩展阅读
- PsSetCreateProcessNotifyRoutineEx2
- 进程回调的性能影响
- 杀毒软件的进程监控实现