内核模式进程保护驱动程序,使用 ObRegisterCallbacks 从句柄中剥离内存访问权限。基础实现拦截句柄创建,并从未经授权的访问尝试中移除 PROCESS_VM_READ/WRITE/OPERATION。


注意: 如果 GIF 没有动画,请在 Imgur 上直接查看
驱动在高度 321123 注册 OB 回调,拦截 OB_OPERATION_HANDLE_CREATE 和 OB_OPERATION_HANDLE_DUPLICATE 操作。当进程尝试打开受保护目标的句柄时,我们根据白名单(explorer.exe, csrss.exe, svchost.exe, MsMpEng.exe, RuntimeBroker.exe)检查源进程名称,如果不在白名单中,则剥离访问权限。
当前实现基于名称匹配,使用 PsGetProcessImageFileName(未公开 API)。保护列表通过带自旋锁同步的链表管理。工作线程存在,但刷新逻辑尚未实现。
用户态组件提供 ImGui GUI,用于向保护列表添加进程。通信通过 IOCTL 码在 \\Device\\Aegis 符号链接上使用 METHOD_BUFFERED 传输进行。驱动创建一个从 \\DosDevices\\Aegis 到 \\Device\\Aegis 的符号链接,以便用户态可以通过 \\\\.\\Aegis 访问它。
内核 (km/):
driver.c - DriverEntry, OB 注册, IOCTL 调度器, 设备/符号链接创建protect_manager/ - 进程列表管理, 工作线程(刷新逻辑待实现)用户态 (um/):
client.cpp - 主循环, ImGui 渲染, 窗口管理gui/ - DirectX 11 + ImGui 设置, 自定义字体 (Gilroy)service_manager/ - 用于驱动加载/卸载的 SCM 集成ioctl_manager/ - 用于驱动通信的 DeviceIoControl 封装321123 注册(第三方标准为 321000+,设置更高以避免冲突)PsGetProcessImageFileName - 未公开 API,仅返回 .exe 文件名(最多 15 个字符)KSPIN_LOCK 操作,防止竞态条件NonPagedPool 用于 protect_manager(始终在 RAM 中,访问更快,池有限)PagedPool 用于单个进程结构(可分页)KEVENT 的系统线程,用于异步处理(目前为存根)METHOD_BUFFERED 用于安全的缓冲区处理,设备类型 0x8000(第三方范围)SERVICE_DEMAND_STARTDeviceIoControl 配合在共享 ioctl.h 中定义的 IOCTL 码OB 回调注册:
// 注册 OB 回调用于句柄创建/复制
OB_CALLBACK_REGISTRATION ob_registration = { 0 };
OB_OPERATION_REGISTRATION op_registration = { 0 };
RtlSecureZeroMemory(&ob_registration, sizeof(ob_registration));
RtlSecureZeroMemory(&op_registration, sizeof(op_registration));
op_registration.ObjectType = PsProcessType;
op_registration.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
// 我们关心复制操作,因为聪明的黑客或恶意软件通常不创建句柄,而是复制现有句柄以避免引起怀疑
op_registration.PreOperation = pre_open_process;
RtlInitUnicodeString(&ob_registration.Altitude, L"321123");
// 321000 是第三方驱动的标准高度,我们设置略高以避免冲突
status = ObRegisterCallbacks(&ob_registration, ®_handle);
句柄访问权限剥离:
OB_PREOP_CALLBACK_STATUS pre_open_process(PVOID reg_context, POB_PRE_OPERATION_INFORMATION info)
{
if (info->ObjectType == *PsProcessType) {
PEPROCESS proc = (PEPROCESS)info->Object;
char* process_name = (char*)PsGetProcessImageFileName(proc);
// 在 DISPATCH_LEVEL 锁定列表以避免蓝屏
KIRQL irql;
KeAcquireSpinLock(&g_protect_manager->list_lock, &irql);
// 检查进程是否在保护列表中
for (entry = g_protect_manager->process_list.Flink;
entry != &g_protect_manager->process_list;
entry = entry->Flink) {
pprocess proces = CONTAINING_RECORD(entry, process, entry);
if (strcmp(proces->path, process_name) == 0) {
// 检查源进程 - 允许系统进程
PEPROCESS source_process = IoGetCurrentProcess();
char* source_name = (char*)PsGetProcessImageFileName(source_process);
if (strcmp(source_name, "explorer.exe") == 0 ||
strcmp(source_name, "csrss.exe") == 0 || ...) {
KeReleaseSpinLock(&g_protect_manager->list_lock, irql);
return OB_PREOP_SUCCESS;
}
// 剥离 VM_READ, VM_WRITE, VM_OPERATION (0x1, 0x10, 0x20)
// 我们不将其设为 0,因为这可能导致错误
info->Parameters->CreateHandleInformation.DesiredAccess &= ~(0x1 | 0x10 | 0x20);
KeReleaseSpinLock(&g_protect_manager->list_lock, irql);
return OB_PREOP_SUCCESS;
}
}
KeReleaseSpinLock(&g_protect_manager->list_lock, irql);
}
return OB_PREOP_SUCCESS;
}
进程列表管理:
NTSTATUS protect_manager_protect_process(pprotect_manager self, pprocess process)
{
// KIRQL (内核中断请求级别) 0-31 告诉 CPU 不要打扰
// 0 = PASSIVE_LEVEL, 2 = DISPATCH_LEVEL, 31 = HIGH_LEVEL
KIRQL irql;
// 获取自旋锁,将 IRQL 提升到 DISPATCH_LEVEL (2)
// 锁定列表,使其在我们编辑时不会改变
KeAcquireSpinLock(&self->list_lock, &irql);
InsertTailList(&self->process_list, &process->entry);
KeReleaseSpinLock(&self->list_lock, irql);
// 通知工作线程进行刷新
self->refresh_needed = TRUE;
KeSetEvent(&self->worker_event, 0, FALSE); // FALSE = 自动恢复 IRQL
return STATUS_SUCCESS;
}
用户态 IOCTL 通信:
bool ioctl_manager::check(std::string name)
{
// 通过符号链接 \\\\.\\Aegis 打开驱动设备 -> \\Device\\Aegis
this->driver_object = CreateFile(this->path, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
// 通过 IOCTL 向驱动发送进程名
result = DeviceIoControl(this->driver_object, IOCTL_PROTECT_PID,
(LPVOID)data.c_str(), data.size() + 1,
NULL, 0, &bytes_returned, NULL);
CloseHandle(this->driver_object); // 不要忘记关闭句柄
return result;
}
所有代码都附有关于内核概念、IRQL 级别、内存管理和潜在陷阱的详尽注释。欢迎查看源码——它兼具教育性且文档完善。
bcdedit /set testsigning on)构建输出:km.sys(驱动)和 um.exe(用户态应用)。
OB_OPERATION_HANDLE_CREATE,复制攻击可能绕过protect_manager.c 中的刷新逻辑为空ExAllocatePool 而非 ExAllocatePool2PsActiveProcessHead 解链隐藏进程,从 PsLoadedModuleList 隐藏驱动仅用于教育目的。使用可能随 Windows 更新而中断的未公开 API。需要测试签名或正确的代码签名才能运行。