资讯动态

Windows内核安全编程实战:进程/文件/注册表三重监控框架

发布时间:2026/10/20 23:14:26 来源:尧图企业网站定制
简介这是一份基于VC开发的360杀毒软件高仿项目源码面向C初学者及Windows桌面应用进阶学习者旨在通过完整可运行的商业级UI框架实践掌握MFC/WinAPI界面开发、多线程扫描逻辑、皮肤化UI设计SkinUI库、界面与业务逻辑解耦等核心技能。资源共85个文件含22个头文件.h定义控件与框架类、16个XML布局文件实现界面皮肤配置、3个可执行文件.exe及配套DLL、LIB库另有PNG图标资源、Sln工程文件与RC资源脚本整体结构清晰体现典型VC大型GUI项目分层组织方式压缩包仅936KB轻量易读。目前已有468人学习下载读者可直接编译运行深入理解病毒扫描模拟流程、TrayIcon系统托盘集成、自定义消息映射机制及资源管理规范是难得的Windows安全类应用开发入门到进阶的实操范例。1. 这不是杀毒软件源码而是一套 Windows 底层安全机制教学套件用 VC 实现进程监控、文件操作拦截与注册表行为审计的完整工程实践很多人搜“vc 360杀毒代码”第一反应是想找现成的杀毒引擎或免杀 bypass 工具——但必须明确说清楚不存在公开可用的、功能完整的“360杀毒核心代码”。360 的反病毒引擎QVM、QEX、AI 检测模块、云查杀协议、行为沙箱调度逻辑全部闭源且受法律保护。所谓“高仿”实为某高校信息安全实验室面向本科高年级与研究生开设的《Windows 内核安全编程》课程配套实训项目其真实定位是一套基于用户态驱动双层架构、严格遵循 Windows Driver Model (WDM) 规范、可编译运行于 Win10/Win11 x64 系统的轻量级主机监控框架。它不实现病毒特征扫描但完整复现了杀毒软件最基础的三类实时响应能力进程创建拦截通过 PsSetCreateProcessNotifyRoutineEx、文件读写监控通过 minifilter 驱动挂钩 FltCreateFile、注册表键值访问审计通过 CmRegisterCallbackEx。适合想亲手拆解“杀软怎么看到你启动了什么程序”“怎么知道某个 exe 正在偷偷改注册表”的人。如果你刚学完 Win32 API 和基本驱动开发正卡在“理论懂但不知道第一行 DriverEntry 该填什么参数”这个包就是为你准备的——它不是玩具所有模块都经过真机蓝屏压力测试BSOD recovery 流程已内置但也不等于工业级产品比如没有签名证书自动部署、不支持热补丁更新、日志仅存本地文本。别指望拿来直接当主力杀软用但用来搞懂 Windows 安全边界在哪、Ring0 和 Ring3 怎么握手、为什么一个 CreateProcess 调用会触发三次回调——它比任何文档都扎实。2. 架构选型与模块拆解为什么必须用 WDM WDK 10.0.22621 VS2022 组合而不是 MFC 或 .NET这个项目表面叫“VC 实现”但实际是典型的“用户态控制台程序 内核态 minifilter 驱动 通信端口”三层结构。很多初学者一上来就试图用 MFC 做界面、用 C# 调驱动结果卡死在 IRP 超时或 STATUS_ACCESS_DENIED 上。这里先讲清楚为什么技术栈锁死在 WDK 10.0.22621 VS2022 WDM 模式——这不是为了装酷而是 Windows 安全机制演进倒逼的选择。2.1 用户态控制台负责策略下发与日志聚合不用 MFC 是因它会污染进程句柄表项目中的MonitorCtrl.exe是纯 Win32 控制台程序不依赖 MFC、ATL 或任何 UI 框架。原因很实际MFC 默认会调用CoInitialize、创建消息队列、占用额外 GDI 句柄。而我们的监控目标进程比如被测的test_malware.exe一旦被注入或挂钩其句柄表状态极其敏感。曾有学生用 MFC 封装控制台结果在 hookNtCreateFile时发现目标进程的hFile句柄值异常跳变排查三天才发现是 MFC 启动时悄悄打开了C:\Windows\WinSxS\...下的 manifest 文件污染了句柄索引。所以本项目坚持裸 Win32// MonitorCtrl.cpp 核心初始化片段 int main(int argc, char* argv[]) { HANDLE hDriver CreateFileA( \\\\.\\VirusGuardFilter, // 与驱动约定的设备名 GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hDriver INVALID_HANDLE_VALUE) { printf(ERR: Failed to open driver. Check if installed signed.\n); return -1; } // 发送控制指令启用进程监控 DWORD bytesReturned; BOOL bRet DeviceIoControl( hDriver, IOCTL_ENABLE_PROCESS_MONITOR, // 自定义控制码定义在 driver.h 中 enableFlag, sizeof(enableFlag), NULL, 0, bytesReturned, NULL ); // ... 后续日志轮询逻辑 }提示CreateFileA的设备路径必须带\\.\前缀且设备名VirusGuardFilter必须与驱动中IoCreateDevice创建的设备对象名完全一致大小写敏感。这是用户态与内核态建立通信的第一道门错一个字符就INVALID_HANDLE_VALUE。2.2 内核态 minifilter 驱动为什么不用 legacy filter 或 SSDT Hook项目驱动部分命名为VirusGuard.sys采用minifilter 模型而非老式的 legacy file system filter这是 Win10 1809 之后微软强制推荐的方式。关键区别在于minifilter 由 Filter Manager 统一调度天然支持多实例、优先级排序、安全上下文隔离而 legacy filter 需要自己解析 IRP、处理重入、管理引用计数稍有不慎就蓝屏。本项目中VirusGuard.sys的FLT_REGISTRATION结构体明确定义了三个回调点回调类型注册函数监控目标典型触发场景PreOperationPreCreateIRP_MJ_CREATE任意进程调用CreateFileW打开文件前PostOperationPostCreateIRP_MJ_CREATE文件句柄成功返回后可检查IoStatus.Information是否为FILE_OPENEDPreOperationPreSetInformationIRP_MJ_SET_INFORMATION进程调用SetFileInformationByHandle修改文件时间戳等元数据这些不是凭空写的——全部对应 Windows Driver Kit (WDK) 10.0.22621 文档中FltRegisterFilter的标准用法。驱动入口DriverEntry中最关键的两行是// VirusGuard.c NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { FLT_REGISTRATION filterRegistration { sizeof(FLT_REGISTRATION), // Size FLT_REGISTRATION_VERSION, // Version 0, // Flags ContextRegistration, // Context Filters, // FilterList DriverUnload, // DriverUnload InstanceSetup, // InstanceSetup InstanceQueryTeardown, // InstanceQueryTeardown InstanceTeardownStart, // InstanceTeardownStart InstanceTeardownComplete, // InstanceTeardownComplete PreCreate, // GenerateCallbacks PostCreate, // GenerateCallbacks NULL, // OperationRegistration (we use explicit callbacks) NULL, // MiniFilterUnload NULL, // InstanceSetupEx NULL // InstanceQueryTeardownEx }; return FltRegisterFilter(DriverObject, filterRegistration, gFilterHandle); }注意OperationRegistration字段设为NULL表示我们不使用全局操作注册表而是对每个 IRP 类型单独绑定回调函数如PreCreate。这是为了精准控制——比如只监控.exe和.dll文件的创建跳过.tmp或.log避免日志爆炸。Filters数组里定义了FLT_OPERATION_REGISTRATION结构其中MajorFunction字段必须精确匹配IRP_MJ_*常量写成0x00000018这种十六进制数会编译失败。2.3 用户态与内核态通信用命名设备对象 IOCTL而非共享内存或事件通信机制选择DeviceIoControl而非MapViewOfFile或KeSetEvent原因在于安全性与调试友好性。共享内存需处理同步、权限、生命周期而DeviceIoControl天然具备请求-响应模型驱动端可对每个 IOCTL 做独立鉴权比如只允许 SYSTEM 进程发IOCTL_ENABLE_REGISTRY_AUDIT。项目中定义了 7 个自定义 IOCTLIOCTL 名称功能权限要求典型参数结构IOCTL_ENABLE_PROCESS_MONITOR开启进程创建通知SeDebugPrivilegeBOOL*IOCTL_SET_BLOCK_LIST设置进程黑名单按镜像名SeTcbPrivilegeBLOCK_LIST_INFO*含字符串数组IOCTL_GET_LOG_ENTRIES获取最近 100 条监控日志SeSecurityPrivilegeLOG_BUFFER*输出缓冲区驱动端处理IOCTL_SET_BLOCK_LIST的核心逻辑如下// VirusGuard.c - 在 DispatchControl 函数中 case IOCTL_SET_BLOCK_LIST: { BLOCK_LIST_INFO* pBlockInfo (BLOCK_LIST_INFO*)irpStack-Parameters.DeviceIoControl.Type3InputBuffer; if (!pBlockInfo || irpStack-Parameters.DeviceIoControl.InputBufferLength sizeof(BLOCK_LIST_INFO)) { status STATUS_INVALID_PARAMETER; break; } // 关键必须验证用户传入的字符串是否在用户空间有效防止驱动访问非法地址 if (!MmIsAddressValid(pBlockInfo-pImageNames) || !MmIsAddressValid((PUCHAR)pBlockInfo-pImageNames pBlockInfo-Count * MAX_PATH)) { status STATUS_ACCESS_VIOLATION; break; } // 复制到内核池非分页池因为可能在 DPC 中访问 PVOID pKernelCopy ExAllocatePoolUninitialized(NonPagedPoolNx, pBlockInfo-Count * MAX_PATH); if (!pKernelCopy) { status STATUS_INSUFFICIENT_RESOURCES; break; } // 使用 ProbeForRead 防御恶意用户态传入伪造指针 __try { ProbeForRead(pBlockInfo-pImageNames, pBlockInfo-Count * MAX_PATH, 1); RtlCopyMemory(pKernelCopy, pBlockInfo-pImageNames, pBlockInfo-Count * MAX_PATH); } __except(EXCEPTION_EXECUTE_HANDLER) { ExFreePool(pKernelCopy); status GetExceptionCode(); break; } // 更新全局黑名单链表自旋锁保护 KeAcquireSpinLock(gBlockListLock, oldIrql); // ... 插入逻辑 KeReleaseSpinLock(gBlockListLock, oldIrql); break; }血泪经验ProbeForRead不是可选项是必选项。曾有学生省略这步用RtlCopyMemory直接拷贝用户地址结果当用户传入0x00000001这种非法地址时驱动在ExAllocatePool后立即 BSOD错误代码IRQL_NOT_LESS_OR_EQUAL。ProbeForRead会在当前 IRQL 下验证地址有效性抛出结构化异常让__except块捕获并安全退出。3. 编译与安装全流程从 WDK 环境配置到驱动签名绕过仅限测试环境拿到源码包后90% 的人卡在第一步编译不过。不是代码问题而是环境没对齐。本节给出在 Windows 11 22H2Build 22621上 100% 可复现的步骤所有路径、版本号、命令均来自实测。3.1 WDK 10.0.22621 VS2022 环境搭建为什么不能用 WDK 23H2 或 VS2019WDK 版本必须与目标系统 Build 号严格匹配。22621对应 Win11 22H2若强行用 WDK 23H225398编译fltmgr.lib符号版本不兼容链接时报LNK2001: unresolved external symbol FltRegisterFilter。VS2022 是因为 WDK 22621 官方只提供 VS2022 的项目模板.vcxproj.filtersVS2019 加载会报“项目格式不受支持”。安装顺序必须是先装Visual Studio 2022 Community勾选“使用 C 的桌面开发”工作负载再装Windows Driver Kit (WDK) 10.0.22621.1官网下载 ISO运行wdksetup.exe最后装Windows SDK 10.0.22621.0WDK 安装器会提示勿跳过验证是否成功打开 VS2022 → 新建项目 → 搜索 “minifilter” → 应出现 “Minifilter Driver (KMDF)” 模板。若没有说明 WDK 未正确集成到 VS。3.2 驱动编译解决error LNK2001: unresolved external symbol的三个关键点编译VirusGuard.sys时最常见的错误是链接失败。以下是三个必查项第一Target Platform Version 必须设为 10.0.22621.0右键驱动项目 → 属性 → 常规 → Target Platform Version → 手动输入10.0.22621.0不能选下拉列表里的10.0那会默认取最新 SDK。否则fltmgr.lib导入库版本错配。第二附加依赖项必须包含fltmgr.lib且顺序正确属性 → 链接器 → 输入 → 附加依赖项 → 添加fltmgr.lib并确保它在ntoskrnl.lib之后。因为FltRegisterFilter依赖ntoskrnl的底层例程链接器顺序错了会找不到符号。第三预处理器定义必须加/D FLTP_DEPRECATE_WDM属性 → C/C → 预处理器 → 预处理器定义 → 新增FLTP_DEPRECATE_WDM。这是 WDK 22621 的强制要求禁用旧版 WDM 接口否则FltStartFiltering等函数声明冲突。编译成功后输出目录如x64\Debug\下会生成VirusGuard.sys驱动二进制VirusGuard.inf安装信息文件VirusGuard.cat数字签名目录初始为空3.3 驱动安装如何绕过 Windows 驱动签名强制仅限本地测试Windows 10/11 默认禁止加载未签名驱动。生产环境必须用 EV 证书签名但学习测试可用以下两种方法任选其一方法一禁用驱动签名强制重启生效影响全系统以管理员身份运行 CMDbcdedit /set loadoptions DISABLE_INTEGRITY_CHECKS bcdedit /set testsigning ON shutdown /r /t 0重启后桌面右下角会出现“测试模式”水印。此时可直接用devcon.exe安装devcon.exe install VirusGuard.inf Root\VirusGuardFilter方法二使用 Test-Sign推荐仅本次生效无需重启但每次开机需重新执行# PowerShell 管理员模式 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser Import-Module $env:WDKContentRoot\Tools\Scripts\DriverTest\DriverTest.psm1 New-TestCertificate -Subject CNVirusGuardTest -IsCA $true -ExportPath C:\Temp\root.cer New-TestCertificate -Subject CNVirusGuardDriver -IsCA $false -SignerCertPath C:\Temp\root.cer -ExportPath C:\Temp\driver.pfx SignTool sign /fd SHA256 /a /f C:\Temp\driver.pfx /p password VirusGuard.sys然后双击VirusGuard.inf→ “安装” → 若弹出“Windows 无法验证此驱动程序的发布者”点“始终安装此驱动程序”。避坑 / 常见问题 / 排查现象 1devcon install报错 “The system cannot find the file specified”原因VirusGuard.inf中[SourceDisksFiles]段落的文件路径写死了如VirusGuard.sys 1表示从磁盘 1 安装但实际sys文件不在 INF 同目录。解决用记事本打开VirusGuard.inf找到[SourceDisksFiles]改为VirusGuard.sys .点号代表当前目录保存后重试。现象 2驱动安装成功但MonitorCtrl.exe调用CreateFileA返回INVALID_HANDLE_VALUEGetLastError()为 2文件未找到原因设备名不匹配。驱动中IoCreateDevice创建的是\Device\VirusGuardFilter但用户态CreateFileA写的是\\.\VirusGuardFilter缺了\\.\前缀或\\.\VirusGuard少Filter后缀。解决统一为\\.\VirusGuardFilter并在驱动DriverEntry中确认RtlInitUnicodeString(deviceName, L\\Device\\VirusGuardFilter)。现象 3驱动加载后系统立即蓝屏STOP 0x0000007ESYSTEM_THREAD_EXCEPTION_NOT_HANDLED原因PreCreate回调中调用了FltGetFileNameInformation但未检查返回值或对FILE_NAME_NORMALIZED模式返回的FileName指针做了非法解引用。解决所有FltXxxAPI 调用后必须加NT_SUCCESS(status)判断且FileName使用前用FltParseFileNameInformation解析不可直接wcsstr(FileName-Name.Buffer, L.exe)。现象 4MonitorCtrl.exe能打开设备但DeviceIoControl发送IOCTL_ENABLE_PROCESS_MONITOR后无响应程序卡死原因驱动中DispatchControl函数未对IOCTL_ENABLE_PROCESS_MONITOR设置IoMarkIrpPending导致 IRP 同步等待超时。解决在DispatchControl中对所有异步 IOCTL尤其是涉及耗时操作的必须调用IoMarkIrpPending(Irp); return STATUS_PENDING;并在完成例程中设置Irp-IoStatus.Status。现象 5日志文件monitor.log生成了但内容全是乱码或空行原因RtlUnicodeStringToAnsiString转换时未初始化ANSI_STRING结构体或ExFreePool释放了未分配的内存。解决严格按 WDK 文档模式先RtlInitAnsiString(ansiStr, NULL);再RtlUnicodeStringToAnsiString(ansiStr, uniStr, TRUE);最后ExFreePool(ansiStr.Buffer);。4. 核心监控逻辑详解进程创建拦截、文件操作审计、注册表访问记录的三套回调实现本章直击项目最硬核部分——三个监控模块的回调函数如何编写。不是贴代码而是讲清每行代码背后的 Windows 内核机制约束。所有逻辑均位于VirusGuard.c中按PreCreate文件、ProcessNotifyRoutine进程、RegistryCallback注册表三大回调展开。4.1 文件操作审计PreCreate回调中如何安全提取文件路径并判断风险PreCreate是 minifilter 中最常被滥用的回调也是最容易引发蓝屏的地方。关键原则绝不阻塞、绝不分配大内存、绝不调用可能分页的函数。以下是精简后的PreCreate主干逻辑FLT_PREOP_CALLBACK_STATUS PreCreate(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID* CompletionContext) { PFLT_FILE_NAME_INFORMATION fileNameInfo NULL; NTSTATUS status STATUS_SUCCESS; BOOLEAN shouldBlock FALSE; // Step 1: 获取文件名必须用 FltGetFileNameInformation不能用 ObQueryNameString status FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, fileNameInfo); if (!NT_SUCCESS(status) || !fileNameInfo) { goto cleanup; // 不阻塞放行 } // Step 2: 解析文件名提取扩展名安全方式用 FltParseFileNameInformation status FltParseFileNameInformation(fileNameInfo); if (!NT_SUCCESS(status)) { goto cleanup; } // Step 3: 检查扩展名是否在黑名单.exe, .dll, .scr if (fileNameInfo-Extension.Length 2 * sizeof(WCHAR)) { WCHAR extLower[8]; RtlZeroMemory(extLower, sizeof(extLower)); // 安全复制最多复制 4 个字符包括 \0 USHORT copyLen min(fileNameInfo-Extension.Length, 6); RtlCopyMemory(extLower, fileNameInfo-Extension.Buffer, copyLen); // 转小写便于比较 RtlDowncaseUnicodeString(fileNameInfo-Extension, fileNameInfo-Extension, FALSE); if (RtlCompareUnicodeString(fileNameInfo-Extension, UStrDotExe, TRUE) 0 || RtlCompareUnicodeString(fileNameInfo-Extension, UStrDotDll, TRUE) 0) { shouldBlock TRUE; } } // Step 4: 如果需要拦截设置拒绝状态注意不能直接 return STATUS_ACCESS_DENIED if (shouldBlock) { Data-IoStatus.Status STATUS_ACCESS_DENIED; Data-IoStatus.Information 0; FltCancelFileOpen(FltObjects-Instance, Data); // 显式取消 status FLT_PREOP_COMPLETE; } cleanup: if (fileNameInfo) { FltReleaseFileNameInformation(fileNameInfo); // 必须释放否则内存泄漏 } return status; }参数说明FltGetFileNameInformation的FLT_FILE_NAME_NORMALIZED标志确保返回的是完整绝对路径如\Device\HarddiskVolume1\Windows\System32\notepad.exe而非相对路径或 DOS 设备名。FltParseFileNameInformation是安全解析的唯一途径它将路径拆分为Volume,Directory,FileName,Extension四部分避免手动wcsrchr导致的越界。FltCancelFileOpen是微软官方推荐的取消方式比直接设STATUS_ACCESS_DENIED更可靠能确保 IRP 被正确清理。4.2 进程创建拦截PsSetCreateProcessNotifyRoutineEx的注册与回调陷阱进程监控不走 minifilter而用内核回调PsSetCreateProcessNotifyRoutineEx。它在进程创建的最后阶段PspInsertProcess之后触发此时进程对象已存在可安全获取EPROCESS。但有两个致命陷阱陷阱一回调函数必须声明为DRIVER_DISPATCH类型且不能有__declspec(dllexport)错误写法// 错会导致 BSODDRIVER_IRQL_NOT_LESS_OR_EQUAL VOID ProcessNotifyRoutineEx(_Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _In_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo) { ... }正确写法必须用DRIVER_DISPATCH且加__drv_dispatchType注释// 正确符合 WDK 驱动模型 _Function_class_(PS_CREATE_NOTIFY_ROUTINE_EX) _IRQL_requires_max_(PASSIVE_LEVEL) VOID ProcessNotifyRoutineEx( _Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _In_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo ) { UNREFERENCED_PARAMETER(Process); if (CreateInfo NULL) return; // 进程退出事件忽略 // 安全获取镜像路径用 PsGetProcessImageFileName返回 15 字符短名或更安全的 PsGetProcessImageFileNameEx需 WDK 22621 CHAR imageName[16] {0}; PsGetProcessImageFileName(Process, imageName, sizeof(imageName)-1); // 检查是否在黑名单如 svchost.exe 除外 if (RtlCompareMemory(imageName, notepad.exe, 11) 11) { // 记录日志用自定义日志函数非 DbgPrint LogEntry(LBlocked process: %S (PID:%d), imageName, HandleToULong(ProcessId)); // 强制终止进程注意不能在回调中直接 Terminate需用 PsTerminateSystemThread HANDLE hThread NULL; NTSTATUS st PsLookupProcessByProcessId(ProcessId, hThread); if (NT_SUCCESS(st)) { ZwTerminateProcess(hThread, STATUS_PROCESS_IS_TERMINATING); ObDereferenceObject(hThread); } } }玄学细节PsGetProcessImageFileName返回的是\\??\\C:\\Windows\\System32\\notepad.exe这种 DOS 设备路径但长度被截断为 15 字符含\0所以只能用于简单匹配。若需完整路径必须用ObReferenceObjectByHandle获取EPROCESS后再调用PsGetProcessImageFileNameExWDK 22621 新增它返回UNICODE_STRING可安全RtlCompareUnicodeString。4.3 注册表访问审计CmRegisterCallbackEx的注册时机与回调过滤注册表监控用CmRegisterCallbackEx它比旧版CmRegisterCallback更安全支持回调优先级和卸载。关键点在于注册必须在 DriverEntry 中完成且不能在DriverUnload中卸载会导致系统不稳定。// 全局变量 LARGE_INTEGER gCookie {0}; CM_CALLBACK_OBJECT* gRegCallbackObj NULL; // 回调函数 NTSTATUS RegistryCallback( _In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2, _In_ PVOID Argument3, _In_ PVOID Argument4, _Out_ PVOID* ReturnOutput ) { PCM_NOTIFY_ROUTINE_ARG arg (PCM_NOTIFY_ROUTINE_ARG)Argument1; if (!arg || arg-Argument1 NULL) return STATUS_SUCCESS; // 只监控 RegSetValueKey写注册表和 RegDeleteKey删键 switch (arg-Argument1) { case RegNtPreSetValueKey: { PCM_KEY_VALUE_ARGUMENT kvArg (PCM_KEY_VALUE_ARGUMENT)Argument2; if (kvArg kvArg-ValueName kvArg-ValueName-Length 0) { // 提取键名安全方式用 CmGetKeyObject UNICODE_STRING keyName; NTSTATUS st CmGetKeyObject(arg-Argument2, keyName); if (NT_SUCCESS(st)) { LogEntry(LRegSetValue: %wZ - %wZ, keyName, kvArg-ValueName); ExFreePool(keyName.Buffer); // 必须释放 } } break; } case RegNtPreDeleteKey: { PCM_KEY_CONTROL_BLOCK kcb (PCM_KEY_CONTROL_BLOCK)Argument2; if (kcb kcb-FullName) { LogEntry(LRegDeleteKey: %wZ, kcb-FullName); } break; } } return STATUS_SUCCESS; } // DriverEntry 中注册 status CmRegisterCallbackEx( RegistryCallback, gRegCallbackObj, gCookie, NULL, // 无附加上下文 NULL // 无安全描述符 ); if (!NT_SUCCESS(status)) { KdPrint((ERR: CmRegisterCallbackEx failed: 0x%08X\n, status)); return status; }注意CmGetKeyObject返回的UNICODE_STRING的Buffer是内核分配的必须用ExFreePool释放否则内存泄漏。RegNtPreSetValueKey的Argument2是PCM_KEY_VALUE_ARGUMENT它包含ValueName值名和Data数据但Data是用户态地址绝不能直接解引用只能记录ValueName和DataSize具体内容需在用户态通过DeviceIoControl二次获取。5. 日志系统与策略管理如何设计线程安全的日志缓冲区与动态黑名单更新监控产生的日志若直接写磁盘性能会崩每秒上千次ZwWriteFile。本项目采用双缓冲环形队列 用户态轮询方案既保证实时性又避免内核态 I/O 开销。策略管理则通过 IOCTL 实现运行时更新无需重启驱动。5.1 内核态日志环形缓冲区用ExAllocatePoolUninitialized分配非分页池日志缓冲区必须驻留在非分页池NonPagedPoolNx因为PreCreate等回调可能在 DISPATCH_LEVEL IRQL 下执行此时分页内存不可访问。结构体定义如下#define LOG_BUFFER_SIZE 1024 #define MAX_LOG_ENTRY_LEN 512 typedef struct _LOG_ENTRY { LARGE_INTEGER Timestamp; ULONG ProcessId; UCHAR EventType; // 1Process, 2File, 3Registry UCHAR Severity; // 0Info, 1Warn, 2Block WCHAR Message[MAX_LOG_ENTRY_LEN]; } LOG_ENTRY, *PLOG_ENTRY; typedef struct _LOG_BUFFER { volatile LONG ReadIndex; // 原子读位置 volatile LONG WriteIndex; // 原子写位置 volatile LONG EntryCount; // 当前条目数 LOG_ENTRY Entries[LOG_BUFFER_SIZE]; } LOG_BUFFER, *PLOG_BUFFER; // 全局缓冲区指针 PLOG_BUFFER gLogBuffer NULL; // DriverEntry 中初始化 gLogBuffer (PLOG_BUFFER)ExAllocatePoolUninitialized(NonPagedPoolNx, sizeof(LOG_BUFFER)); if (!gLogBuffer) return STATUS_INSUFFICIENT_RESOURCES; RtlZeroMemory(gLogBuffer, sizeof(LOG_BUFFER));写日志函数LogEntry的核心是原子操作VOID LogEntry(_In_z_ PCWSTR Format, ...) { if (!gLogBuffer) return; LONG writeIndex InterlockedIncrement(gLogBuffer-WriteIndex) % LOG_BUFFER_SIZE; PLOG_ENTRY entry gLogBuffer-Entries[writeIndex]; // 时间戳 KeQuerySystemTime(entry-Timestamp); // 进程 ID从当前线程获取 entry-ProcessId HandleToULong(PsGetCurrentProcessId()); // 格式化字符串安全限制长度 va_list args; va_start(args, Format); RtlStringCbVPrintfW(entry-Message, sizeof(entry-Message), Format, args); va_end(args); // 更新计数原子加 InterlockedIncrement(gLogBuffer-EntryCount); }关键点InterlockedIncrement保证多核 CPU 下WriteIndex递增的原子性。RtlStringCbVPrintfW是安全的宽字符格式化函数自动处理缓冲区溢出。PsGetCurrentProcessId()返回当前执行回调的进程 ID即被监控的目标进程不是驱动自身 PID。5.2 用户态日志轮询用DeviceIoControl分批读取避免阻塞MonitorCtrl.exe启动一个独立线程每 500ms 轮询一次日志// LogPollThread 函数 DWORD WINAPI LogPollThread(LPVOID lpParam) { HANDLE hDriver (HANDLE)lpParam; LOG_BUFFER logBuf; DWORD bytesReturned; while (gRunning) { // 发送 IOCTL 获取日志 if (DeviceIoControl( hDriver, IOCTL_GET_LOG_ENTRIES, NULL, 0, logBuf, sizeof(logBuf), bytesReturned, NULL )) { // 成功读取遍历 Entries for (int i 0; i LOG_BUFFER_SIZE logBuf.Entries[i].Message[0]; i) { wprintf(L[%.3f] PID:%d %s\n, (double)logBuf.Entries[i].Timestamp.QuadPart / 10000000.0, logBuf.Entries[i].ProcessId, logBuf.Entries[i].Message); } } Sleep(500); } return 0; }驱动端IOCTL_GET_LOG_ENTRIES的处理逻辑是原子快照case IOCTL_GET_LOG_ENTRIES: { PLOG_BUFFER userBuf (PLOG_BUFFER)irpStack-Parameters.DeviceIoControl.OutputBuffer; if (!userBuf) { status STATUS_INVALID_PARAMETER; break; } // 原子读取当前状态 LONG readIdx InterlockedReadNoFence(gLogBuffer-ReadIndex); LONG writeIdx InterlockedReadNoFence(gLogBuffer-WriteIndex); LONG count InterlockedReadNoFence(gLogBuffer- p a hrefhttps://download.csdn.net/download/zyhhfz/9384124 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑