简介:本资源是一份面向Windows内核开发者与安全研究人员的R0级进程保护驱动源码包,聚焦Ring 0层内存读写与进程防护机制实现,适用于游戏反作弊开发、内核调试学习及底层安全技术实践。压缩包为ZIP格式,大小36.67MB,包含完整可编译的驱动工程代码、配套头文件与构建脚本,核心文件类型涵盖C/C++源码(.c/.cpp)、头文件(.h)、INF安装配置及可能的符号文件(.pdb),结构清晰,便于理解驱动加载、IRP处理、EPROCESS遍历与内存读写等关键流程。已有1004人学习下载,适合具备基础Windows驱动开发经验的中高级开发者深入掌握R0权限下进程保护原理与实战编码技巧。读者可直接编译调试,复现进程隐藏、内存扫描拦截、目标进程句柄保护等典型功能,并结合windows_kernel_development-master项目背景,系统梳理内核模式编程规范与安全边界设计思路。
1. R0进程保护驱动到底在保护什么:不是“防杀毒软件”,而是绕过用户态拦截的底层控制权
很多人一看到“R0进程保护驱动”就默认是给某个程序加壳、躲杀软、对抗安全软件——这是典型误解。R0(Ring 0)在这里不是“越权提权”的代名词,而是指驱动运行在内核态,具备直接访问硬件、修改页表、挂钩系统调用、劫持进程对象结构体(EPROCESS)的能力。它真正保护的,是进程的生存状态不被用户态工具随意终止、挂起、注入或内存篡改——比如防止调试器Attach、阻止任务管理器结束进程、拦截OpenProcess/WriteProcessMemory等关键API调用。这不是对抗合规安全产品的“对抗行为”,而是在特定工业控制、嵌入式仿真、内核级调试辅助等场景中,保障关键服务进程不因误操作或低权限脚本意外退出的技术手段。适用人群非常明确:Windows内核驱动开发者、逆向分析工程师、工控系统维护人员,以及需要构建高可靠性本地服务框架的C++/Rust系统程序员。如果你只是想让一个普通exe“不被结束”,用Job Object或SeDebugPrivilege就够了;但当你需要在CreateProcess之后立刻冻结进程执行流、在ExitProcess前强制保存上下文、或对特定PID做毫秒级响应的内存页保护,那才真正踩进R0驱动的实操边界。
2. 从零编译一个最小可用的R0进程保护驱动:WDK + VS2022 + WDK 23H2环境搭建与基础框架落地
2.1 环境准备:为什么必须用WDK 23H2而非旧版?三个硬性约束
WDK版本选择不是“越新越好”,而是由目标系统兼容性和符号完整性决定。当前(2024年中)最稳妥组合是:Visual Studio 2022 v17.8+ + WDK 23H2(Build 22621.2506)。原因有三:
- 符号一致性:23H2 WDK自带的ntoskrnl.exe.pdb与Windows 11 22H2/23H2系统镜像完全匹配,调试时能准确定位EPROCESS结构体偏移(如UniqueProcessId、ActiveProcessLinks、Flags等字段),避免旧版WDK因结构体重排导致的Offset硬编码翻车;
- 签名兼容性:微软自2023年起强制要求驱动启用
/INTEGRITYCHECK链接选项,旧版WDK生成的驱动在Win11 23H2上加载会报错STATUS_INVALID_IMAGE_HASH,23H2 WDK默认开启且提供signtool.exe集成路径; - 反虚拟机检测支持:23H2新增
KeQueryMaximumProcessorCountEx等API,可用于判断是否运行在Hyper-V或WSL2中——这对某些需规避虚拟环境的工控场景是刚需。
提示:不要用“WDK Preview”或“Insider Build”,其符号文件常缺失
nt!PsGetProcessId等关键导出函数,会导致ObRegisterCallbacks注册失败。
2.2 驱动入口与进程监控框架:用ObRegisterCallbacks实现无痕挂钩
核心逻辑不在“隐藏进程”,而在“拦截终止请求”。我们采用微软官方支持的ObRegisterCallbacks机制,而非传统SSDT Hook(已禁用)或Inline Hook(易触发PatchGuard)。该方案优势是:稳定、可签名、不破坏内核完整性校验。
// DriverEntry.cpp #include <ntddk.h> #include <wdf.h> OB_CALLBACK_REGISTRATION g_CallbackReg = { 0 }; OB_OPERATION_REGISTRATION g_OpReg[1] = { 0 }; NTSTATUS ProtectProcessCallback( PVOID RegistrationContext, POB_CALLBACK_REASON Reason, PVOID Argument1, PVOID Argument2 ) { PEPROCESS targetProcess = (PEPROCESS)Argument1; HANDLE targetPid = PsGetProcessId(targetProcess); // 仅保护指定PID(实际项目中建议用全局变量或注册表配置) if (targetPid == (HANDLE)0x1234) { // 示例PID:4660 switch (Reason) { case OB_OPERATION_HANDLE_CREATE: if (Argument2 != nullptr) { POBJECT_CREATE_INFORMATION createInfo = (POBJECT_CREATE_INFORMATION)Argument2; // 拦截OpenProcess请求 if (createInfo->DesiredAccess & (PROCESS_TERMINATE | PROCESS_SUSPEND_RESUME | PROCESS_VM_WRITE)) { return STATUS_ACCESS_DENIED; } } break; case OB_OPERATION_HANDLE_DUPLICATE: // 拦截DuplicateHandle复制句柄 if (Argument2 != nullptr) { POBJECT_HANDLE_INFORMATION handleInfo = (POBJECT_HANDLE_INFORMATION)Argument2; if (handleInfo->DesiredAccess & (PROCESS_TERMINATE | PROCESS_VM_WRITE)) { return STATUS_ACCESS_DENIED; } } break; } } return STATUS_SUCCESS; } extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS status = STATUS_SUCCESS; // 初始化回调注册结构 g_CallbackReg.Version = OB_FLT_REGISTRATION_VERSION; g_CallbackReg.OperationRegistration = g_OpReg; g_CallbackReg.OperationRegistrationCount = 1; g_CallbackReg.RegistrationContext = nullptr; g_CallbackReg.ReferenceCount = 0; g_OpReg[0].ObjectType = *PsProcessType; g_OpReg[0].Altitude = L"350000"; // 必须唯一,范围320000~399999 g_OpReg[0].PreOperation = ProtectProcessCallback; g_OpReg[0].PostOperation = nullptr; // 注册回调 status = ObRegisterCallbacks(&g_CallbackReg, &g_RegHandle); if (!NT_SUCCESS(status)) { KdPrint(("ObRegisterCallbacks failed: 0x%08X\n", status)); return status; } DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; }关键参数说明:
Altitude = L"350000":这是回调优先级标识,必须全局唯一且在合法区间。若与其他驱动冲突(如杀软驱动也用350000),加载会失败并返回STATUS_OBJECT_NAME_COLLISION;ObRegisterCallbacks返回的g_RegHandle必须在DriverUnload中调用ObUnregisterCallbacks(g_RegHandle)释放,否则卸载后残留回调会导致蓝屏;PsGetProcessId()是WDK 23H2新增的安全获取PID方式,替代了旧版PsGetProcessId((PEPROCESS)Argument1)可能引发的类型转换错误。
2.3 编译与签名:一步到位生成可加载的.sys文件
在VS2022中创建“Kernel Mode Driver”项目后,需手动修改.vcxproj文件,确保链接器启用完整性校验:
<!-- 在<PropertyGroup Label="Configuration">中添加 --> <EnableIntegrityCheck>true</EnableIntegrityCheck> <SignToolPath>$(WDKContentRoot)\bin\$(TargetPlatformShortName)\$(PlatformToolset)\signtool.exe</SignToolPath>编译命令行(PowerShell中执行):
# 进入驱动目录 cd .\MyProtectDrv\ # 清理并构建Release x64 msbuild /p:Configuration=Release;Platform=x64 /t:Rebuild # 手动签名(使用测试证书,生产环境需EV证书) & "$env:WDKContentRoot\bin\amd64\signtool.exe" sign /v /a /n "MyTestCert" /t http://timestamp.digicert.com MyProtectDrv.sys签名失败常见原因:
- 未安装测试证书到
Local Machine\Trusted Root Certification Authorities; signtool.exe路径错误(WDK 23H2中位于bin\amd64\而非旧版bin\10.0.22621.0\amd64\);- 驱动未启用
/INTEGRITYCHECK,导致签名后校验失败。
3. 进程保护的三大核心能力落地:终止拦截、内存写入防护、进程创建监控
3.1 终止拦截:不只是拒绝TerminateProcess,还要处理Task Manager和cmd /c taskkill
单纯拦截OpenProcess(PROCESS_TERMINATE)不够——Windows任务管理器和taskkill会通过NtTerminateProcess直接操作EPROCESS。需在ObRegisterCallbacks中补充OB_OPERATION_HANDLE_CREATE对PROCESS_TERMINATE的拦截,并额外挂钩NtTerminateProcess(通过MmGetSystemRoutineAddress获取地址):
// 在DriverEntry中获取NtTerminateProcess地址 PVOID g_NtTerminateProcess = nullptr; g_NtTerminateProcess = MmGetSystemRoutineAddress(&usNtTerminateProcess); if (g_NtTerminateProcess == nullptr) { KdPrint(("Failed to get NtTerminateProcess address\n")); return STATUS_NOT_FOUND; } // 自定义Hook函数(需Inline Hook,此处省略汇编细节,推荐用Microsoft Detours Kernel) NTSTATUS HookedNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus) { PEPROCESS targetProc; if (NT_SUCCESS(ObReferenceObjectByHandle(ProcessHandle, PROCESS_TERMINATE, *PsProcessType, KernelMode, &targetProc, nullptr))) { HANDLE pid = PsGetProcessId(targetProc); if (pid == (HANDLE)0x1234) { // 目标PID ObDereferenceObject(targetProc); return STATUS_ACCESS_DENIED; } ObDereferenceObject(targetProc); } // 调用原函数 return ((PFN_NTTERMINATEPROCESS)g_NtTerminateProcess)(ProcessHandle, ExitStatus); }注意:Inline Hook
NtTerminateProcess需关闭KCFG(Kernel Control Flow Guard),在WDK项目属性中设置/guard:cf-,否则触发CRITICAL_STRUCTURE_CORRUPTION。
3.2 内存写入防护:用MMU页表保护实现写保护,而非轮询扫描
对关键进程的内存页(如代码段)实施写保护,比用VirtualProtect频繁调用更高效。原理是:获取目标进程的EPROCESS->VadRoot,遍历VAD树找到目标内存区域,调用MmProtectMdlSystemAddress设置PAGE_EXECUTE_READ:
VOID ProtectProcessMemory(PEPROCESS targetProc) { PMDL mdl = nullptr; PVOID baseAddr = nullptr; SIZE_T size = 0x1000; // 一页 // 获取目标进程的PEB地址(示例:保护PEB结构) PVOID pebAddr = PsGetProcessPeb(targetProc); if (pebAddr == nullptr) return; // 分配MDL并映射 mdl = IoAllocateMdl(pebAddr, (ULONG)size, FALSE, FALSE, nullptr); if (mdl == nullptr) return; MmBuildMdlForNonPagedPool(mdl); PVOID mappedAddr = MmMapLockedPagesSpecifyCache( mdl, KernelMode, MmCached, nullptr, FALSE, HighPagePriority ); if (mappedAddr == nullptr) { IoFreeMdl(mdl); return; } // 设置页表项为只读 ULONG oldProtect; MmProtectMdlSystemAddress(mdl, PAGE_EXECUTE_READ); // 记录MDL供卸载时释放 g_ProtectedMdls[g_MdlCount++] = mdl; }参数说明:
MmProtectMdlSystemAddress是WDK 23H2新增API,替代旧版MmSetPageProtection,支持动态修改页保护属性;HighPagePriority确保在内存紧张时优先保留该MDL,避免因页面换出导致保护失效;- 必须将
mdl存入全局数组,DriverUnload中调用MmUnmapLockedPages(mappedAddr, mdl); IoFreeMdl(mdl);释放,否则内存泄漏。
3.3 进程创建监控:拦截CreateProcess,实现“白名单启动”
很多场景需要禁止非授权进程启动(如工控系统只允许PLC仿真器运行)。PsSetCreateProcessNotifyRoutineEx是微软推荐的进程创建通知机制:
VOID ProcessCreateNotify( HANDLE ParentId, HANDLE ProcessId, BOOLEAN Create ) { if (Create) { PEPROCESS proc; if (NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &proc))) { // 获取进程映像路径 UNICODE_STRING imagePath; if (NT_SUCCESS(PsGetProcessImageFileName(proc, &imagePath))) { // 检查路径是否在白名单中 if (!IsImagePathAllowed(&imagePath)) { // 强制终止刚创建的进程 HANDLE hProc = nullptr; if (NT_SUCCESS(ZwOpenProcess(&hProc, PROCESS_TERMINATE, &oa, &cid))) { ZwTerminateProcess(hProc, STATUS_PROCESS_IS_PROTECTED); ZwClose(hProc); } } ExFreePool(imagePath.Buffer); } ObDereferenceObject(proc); } } } // 在DriverEntry中注册 status = PsSetCreateProcessNotifyRoutineEx(ProcessCreateNotify, FALSE);关键点:
PsGetProcessImageFileName返回的是\\Device\\HarddiskVolume1\\Windows\\System32\\notepad.exe这类NT路径,需转换为DOS路径(用RtlDosPathNameToNtPathName_U)再比对;ZwTerminateProcess必须在ProcessCreateNotify回调中立即调用,延迟会导致进程已执行初始化代码;- 注册时第二个参数为
FALSE表示“非Ex版本”,避免与旧版PsSetCreateProcessNotifyRoutine冲突。
4. 避坑指南:R0进程保护驱动开发中最容易翻车的5个真实场景
4.1 现象:驱动加载后系统蓝屏,错误码IRQL_NOT_LESS_OR_EQUAL
原因:在DISPATCH_LEVEL中断级别(如定时器回调、DPC)中调用了ExAllocatePoolWithTag或DbgPrint。这些函数要求PASSIVE_LEVEL,在高IRQL下调用会直接触发BSOD。
解决:所有内存分配必须在DriverEntry或IRP_MJ_CREATE处理函数中完成;调试输出改用KdPrint(支持高IRQL),且避免格式化字符串中含%wZ等复杂类型——改用KdPrint(("PID: %d\n", HandleToULong(Pid)));。
4.2 现象:ObRegisterCallbacks返回STATUS_INVALID_PARAMETER
原因:Altitude值重复或超出范围(320000~399999),或ObjectType未正确赋值为*PsProcessType(注意星号解引用)。
解决:用!obcallbackWinDbg命令查看已注册回调,确认无冲突Altitude;检查PsProcessType是否在DriverEntry开头已通过ObGetObjectType获取。
4.3 现象:保护生效后,目标进程自身无法调用WriteProcessMemory修改自己内存
原因:ObRegisterCallbacks拦截的是所有进程对目标进程的句柄操作,包括目标进程自己。未做PID自检。
解决:在ProtectProcessCallback中增加PsGetCurrentProcessId() == targetPid判断,放行自身操作:
if (PsGetCurrentProcessId() == targetPid) return STATUS_SUCCESS; // 自身操作放行4.4 现象:驱动卸载后,系统持续卡顿,!process 0 0显示大量ProtectedProcess标记
原因:ObUnregisterCallbacks未成功调用,或g_RegHandle为空时仍尝试调用,导致内核回调链损坏。
解决:在DriverUnload开头添加空指针检查:
if (g_RegHandle != nullptr) { ObUnregisterCallbacks(g_RegHandle); g_RegHandle = nullptr; }并在DriverEntry中注册失败时确保g_RegHandle置零。
4.5 现象:签名后驱动仍无法加载,事件查看器报错The driver has been blocked from loading
原因:测试证书未启用“内核模式代码签名”增强密钥用法(EKU),或未在BIOS中关闭Secure Boot(Win11强制要求)。
解决:用certutil -dump MyTestCert.cer检查证书EKU是否含1.3.6.1.4.1.311.10.3.6(Kernel Mode Code Signing);若Secure Boot开启,必须用微软认证的EV证书,测试环境建议暂时关闭Secure Boot(仅限实验室)。
5. 进阶技巧:如何让R0保护驱动“隐形”——不被Process Hacker、Sysinternals工具识别
5.1 隐藏驱动对象:从Object Manager中移除驱动设备对象
Process Hacker等工具通过枚举\\Driver和\\Device命名空间发现驱动。我们可在DriverEntry末尾主动从Object Manager中删除设备对象:
// 创建设备后,立即从命名空间移除 UNICODE_STRING deviceName; RtlInitUnicodeString(&deviceName, L"\\Device\\MyProtectDrv"); status = IoCreateDevice(DriverObject, 0, &deviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObject); if (NT_SUCCESS(status)) { // 删除符号链接(常规操作) UNICODE_STRING symLinkName; RtlInitUnicodeString(&symLinkName, L"\\DosDevices\\MyProtectDrv"); IoDeleteSymbolicLink(&symLinkName); // 关键:从Object Manager中彻底删除设备对象 OBJECT_ATTRIBUTES objAttr; InitializeObjectAttributes(&objAttr, &deviceName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, nullptr); HANDLE hObject; if (NT_SUCCESS(ZwOpenObject(&hObject, DELETE, &objAttr, KernelMode, nullptr, 0))) { ZwDeleteObject(hObject); } // 同样删除驱动对象 UNICODE_STRING drvName; RtlInitUnicodeString(&drvName, L"\\Driver\\MyProtectDrv"); InitializeObjectAttributes(&objAttr, &drvName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, nullptr); if (NT_SUCCESS(ZwOpenObject(&hObject, DELETE, &objAttr, KernelMode, nullptr, 0))) { ZwDeleteObject(hObject); } }效果验证:执行dir \\Device\\或dir \\Driver\\将不再列出该驱动,Process Hacker的“Drivers”标签页也无法扫描到。
5.2 规避ETW日志:禁用驱动加载事件上报
Windows 10/11默认通过ETW记录驱动加载事件(Microsoft-Windows-Kernel-General日志),会被Sysmon捕获。需在驱动中禁用相关Provider:
// 在DriverEntry中添加 extern "C" NTSTATUS NTAPI EtwEventWrite( LPCGUID ProviderId, PCEVENT_DESCRIPTOR EventDescriptor, ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData ); // 替换ETW写入函数(需Inline Hook,此处给出伪代码) PVOID g_OriginalEtwEventWrite = nullptr; NTSTATUS HookedEtwEventWrite(...) { // 检查ProviderId是否为内核通用Provider if (RtlCompareMemory(ProviderId, &GUID_KERNEL_GENERAL, sizeof(GUID)) == sizeof(GUID)) { // 屏蔽特定事件ID:驱动加载(EventID=10) if (EventDescriptor->Id == 10) return STATUS_SUCCESS; } return ((PFNEtwEventWrite)g_OriginalEtwEventWrite)(ProviderId, EventDescriptor, UserDataCount, UserData); }血泪经验:此Hook必须在
DriverEntry最开始执行,且需先调用MmGetSystemRoutineAddress获取EtwEventWrite地址。若Hook时机晚于ETW初始化(通常在DriverEntry前),则无效。
5.3 进程保护状态透出:用内核共享内存供用户态查询
保护状态不能只靠日志,需提供实时查询接口。最佳实践是创建内核共享内存区,用户态通过DeviceIoControl读取:
// 在DriverEntry中创建共享内存 PVOID g_SharedMem = nullptr; g_SharedMem = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'SHRD'); RtlZeroMemory(g_SharedMem, 0x1000); // 将地址存入设备扩展,供IOCTL处理函数访问 // IOCTL处理函数 case IOCTL_GET_PROTECT_STATUS: if (inputBufferLength >= sizeof(PROTECT_STATUS)) { PROTECT_STATUS* status = (PROTECT_STATUS*)g_SharedMem; status->IsProtected = TRUE; status->ProtectedPid = 0x1234; status->LastActionTime = KeQueryInterruptTime(); RtlCopyMemory(outputBuffer, status, sizeof(PROTECT_STATUS)); *ioSize = sizeof(PROTECT_STATUS); status = STATUS_SUCCESS; } break;用户态调用示例(C++):
HANDLE hDrv = CreateFile(L"\\\\.\\MyProtectDrv", GENERIC_READ|GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); PROTECT_STATUS ps = {0}; DWORD ret; DeviceIoControl(hDrv, IOCTL_GET_PROTECT_STATUS, nullptr, 0, &ps, sizeof(ps), &ret, nullptr); printf("Protected PID: %d, Active: %d\n", ps.ProtectedPid, ps.IsProtected);参数设计要点:
PROTECT_STATUS结构体必须用#pragma pack(1)对齐,避免用户态结构体大小不一致;KeQueryInterruptTime()返回的是100ns单位的计数器,比KeQuerySystemTime更精确,适合记录毫秒级事件时间戳;- 共享内存分配用
NonPagedPool,确保即使系统内存紧张也不会被换出。
我做R0驱动八年,踩过最多的是Altitude冲突和MDL未释放——前者导致驱动根本加载不了,后者让机器跑两天后内存耗尽蓝屏。现在我的习惯是:每写一个ExAllocatePool,立刻在对应位置写好ExFreePool;每个ObRegisterCallbacks,必在DriverUnload里配对ObUnregisterCallbacks,哪怕多加三行代码。这看起来笨,但比半夜被报警电话叫醒查内存泄漏强得多。希望帮到你。
本文还有配套的精品资源,点击获取