☰
恶意驱动逆向分析:从内核权限到对抗与清除的完整流程
2026/10/11 23:23:53 网站建设 项目流程

最近在整理样本库时翻到一个老驱动样本,勾起了我不少回忆。恶意驱动逆向分析这件事,在恶意样本分析里算是相对冷门但又极有挑战性的一块——冷门是因为它要求你同时具备内核知识、PE结构理解、调试器使用能力和一定的耐心,挑战性则在于驱动一旦加载成功,就运行在Ring 0层,拥有了几乎不受限制的系统权限。

这篇文章我想从一次完整的恶意驱动逆向分析经历出发,把整个流程拆开来聊:从样本捕获、静态分析、动态调试到对抗机制识别,再到最终的行为归纳和清除思路。无论你是刚接触恶意代码分析的新手,还是已经在安全行业摸爬滚打了几年的从业者,这篇内容应该都能给你一些实际可用的思路和工具选择参考,尤其是那些在书本上很少写清楚的实操细节。

1. 恶意驱动不是普通样本:它为什么会出现在攻击链里

1.1 驱动在攻击链中的位置与作用

很多刚入门的分析者第一次遇到驱动样本时,容易把它当成普通PE文件来处理:查壳、看导入表、跑沙箱,一套标准流程下来,发现好像什么行为都没有,于是陷入迷茫。这是因为驱动样本和用户态样本的运行逻辑完全不同。用户态恶意软件通常通过双击运行、脚本加载等方式执行,而驱动程序必须通过内核模块加载机制(比如服务创建加上Start参数,或直接调用NtLoadDriver)才能进入系统。

驱动之所以被攻击者选中,核心原因在于它的权限位置。一旦驱动被系统内核接受,它就位于整个系统安全模型的最顶层,可以访问物理内存、注册表内核区、SSDT(系统服务描述表)、内核对象和进程列表。说白了,恶意驱动就像一把能直接打开保险库总闸的钥匙——它不需要一个房间一个房间地去撬锁,而是直接控制了整栋大楼的电路系统。

从实际的攻击案例来看,驱动在攻击链中通常承担三类角色:第一类是持久化驻留,通过内核回调或自启动方式确保系统重启后仍然存活;第二类是权限提升或自我保护,利用内核权限修改访问控制、注入代码、绕过终端安全产品;第三类是辅助隐蔽,通过挂钩内核关键函数、清除自身痕迹、伪造进程信息等手段让恶意活动不被发现。这三类功能常常叠加在一起,所以你看一个恶意驱动时,不能只关心它具体做了什么,还要问一句"它为什么需要在内核层面做这件事"。

1.2 签名与加载:恶意驱动的入场券是怎么搞到的

主流系统对驱动加载都有强制签名要求,这是很多人的直觉。但恶意驱动实际进入目标系统的路径并没有这么简单。

头号路径是"驱动签名绕过"或"已知合法驱动滥用"。攻击者会优先寻找已经有合法签名但存在已知漏洞的驱动,然后利用这个漏洞执行内核代码。这类样本在分析时会看到两个面孔:表面上是某个大厂签名的正常驱动,向下分析却会发现它加载了未签名的恶意代码。分析这种样本时,你不仅要关注驱动PE本身,还要追踪它释放或读取的后续载荷。

第二条路径是自定义签名证书。攻击者会使用窃取或自签的证书对驱动进行签名,如果系统配置不严格(例如开启了测试模式或关闭了签名强制),驱动也能成功加载。还有一种情况是在早期启动阶段绕过签名校验,比如滥用引导工具的驱动加载能力,这部分技术细节就不展开了。但你在分析时看到驱动文件带签名,不要急着认定“它安全”或“它是正规软件”——签名只能说明“谁签的”,不能说明“代码做了什么”。

1.3 分析视角:为什么需要逆向它

在做恶意驱动逆向分析时,我给自己确立了一个基本原则:不要为了分析而分析。每个样本最终都要回答三件事——它是什么、它通过什么机制实现功能、它留下了哪些痕迹可以用来检测和清除。

这个视角决定了分析的优先级。比如同样一个驱动样本,静态分析阶段你可能花很多时间在反汇编上,但真正高效的路径是先看它的资源段、版本信息和字符串,快速判断它的来源和用途;动态分析阶段也不是上来就开调试器单步走,而是先捕获它的文件操作、注册表操作和内核对象创建行为。先有全局画像,再做微观逆向,这样才能避免在无关函数里浪费大量时间。

2. 搭建一个“安全”的恶意驱动分析环境

2.1 虚拟机与快照管理的坑

分析恶意驱动必须在虚拟机中进行,这是底线,但虚拟机本身也有不少坑需要提前处理。驱动运行在内核层,一旦分析环境不够隔离,就可能造成宿主机被波及,所以建议使用支持嵌套虚拟化的方案,并在分析开始前制作一个干净的还原快照。

我个人比较习惯的做法是准备两台虚拟机:一台被测机,一台监控机。被测机上运行样本,安装内核调试组件;监控机负责抓取网络流量和内核日志,通过串口或网络与测试机相连。这样做的依据是:恶意驱动经常会探测常见调试器和虚拟机特征,如果你在本机既跑样本又跑调试工具,很容易让样本感知到异常而导致行为不触发或反调试。

另外要注意的是快照策略。我通常会在样本执行前打一个快照,执行后再打一个快照,然后用工具对比两个快照之间的差异。文件系统变化、注册表变化、服务创建情况,这些差异往往比动态调试器单步跟踪更快更全面地给出答案,因为你不用去猜测驱动调了哪些API,而是直接看到它改变的真实痕迹。

2.2 内核调试器的连接要点

内核调试一般用双机调试,调试器(WinDbg或兼容的调试客户端)在监控机上运行,被测机上开启调试模式。这里最容易出问题的点是核心数和调试端口配置。如果被测机是多核CPU,驱动行为可能在不同核心上并发,调试时容易出现断点不命中、寄存器状态跳变的情况。建议在BIOS/引导配置里固定CPU核心数,或者结合处理器掩码(processor mask)把被调试的驱动线程限定到单个核心上。

还有一个经验是:不要只依赖WinDbg的图形界面。买一本WinDbg命令速查挂在手边常翻一翻,因为内核调试中最高效的操作往往不是点鼠标设断点,而是直接输命令。比如!drvobj看驱动对象、!object查内核对象、!process 0 0列进程、lm查看内核模块,这些命令的组合使用能让你在几分钟内建立起对内核态全局状态的认知。新入门的同事经常问我为什么我在调试器里操作那么快,其实就是因为我不依赖鼠标点击,而是用命令驱动一切。

2.3 行为监控工具的组合配置

静态分析之外,动态行为捕获的准确性决定了一个分析的深度。市面上有很多行为监控工具,性价比最高的组合是:Process Monitor跑文件、注册表、网络行为,加上一个轻量级API监控工具,再加Wireshark或NFlog抓网络包。

但这里有一个内核样本特有的问题:Process Monitor这类工具在用户态工作,对于驱动直接操作内核对象、内核内存的行为是看不见的。所以还要搭配内核层面的监测手段,例如开启驱动的I/O请求跟踪,或用tracelog启动内核日志来捕获驱动注册回调、创建设备对象的记录。这些日志在平时可能是无用的噪音,但在分析恶意驱动时,它们几乎是唯一能还原内核行为的来源。

工具配置需要注意的一点是:监控工具本身也要安装在被测机上,但它们的用户态界面最好不要显示在桌面上。很多恶意驱动会调用用户态组件来定位杀软、监控软件窗口,一旦发现就会改变行为路径。更稳妥的办法是运行监控、采集日志后,再在分析机上检查日志文件,让样本在一个看似“干净”的环境中运行。

3. 静态分析的完整拆解:从文件头到DriverEntry

3.1 第一步不是反汇编,而是看文件身体特征

大多数教程一上来就让你进入反汇编视图,这其实是个不太高效的起点。拿到一个驱动样本,我建议按下面的顺序做静态勘查:

先看文件哈希和外壳信息,判断有没有被保护工具处理过。恶意驱动同样会套壳或混淆,常见的壳有VMProtect、Themida以及专门针对内核驱动的混淆框架,这些特征会在入口点、节名和导入表上留下痕迹。再用PE工具(如CFF Explorer或Detect It Easy)查节表、时间戳、编译器和链接器信息。如果驱动是用较新版本的WDK(Windows Driver Kit)编译的,那么导入表和例行结构会有比较规范的痕迹;如果字符表出现大量异常或节名被重命名,大概率是经过了混淆处理。

接下来再检查证书和数字签名。驱动文件的证书藏在证书表中,你可以用工具提取出签名信息的摘要算法、证书签发者名称和有效时间。很多恶意驱动使用的证书是伪造的,签发者名称往往是对某个正规公司的模仿或篡改,比如大小写差异、字库替换等,仔细核对能发现端倪。反过来,合法的驱动证书也能帮你快速定位样本的来源公司,有助于后续踪研判。

3.2 解析驱动对象和设备创建逻辑

驱动分析和普通程序逆向有一个明显的区别:驱动程序的入口不是main而是DriverEntry,这个函数的核心任务不是执行业务逻辑,而是设置驱动对象的各种回调函数、创建设备对象、创建符号链接,然后返回状态。理解了这一点,你在静态分析时就不会紧盯某个函数体的计算逻辑,而是跟着结构体指针跳转关系走。

在汇编代码层面,比较实用的切入点是寻找IoCreateDevice、IoCreateSymbolicLink、RtlInitUnicodeString这几个API的调用,它们标志着驱动对外的I/O接口已经建立。恶意驱动通常会创建可被用户态程序访问的设备对象,用户态恶意组件向设备对象发送控制码(IOCTL),驱动在内核态响应控制码并执行危险动作。这个通信模式是整个恶意驱动分析中的主线。

比如你在一段反汇编代码里看到先压入一个Unicode字符串"\\Device\\MyMalDrv"再调用RtlInitUnicodeString,后面紧跟着IoCreateDevice,那么基本可以断定:这个驱动会暴露一个设备接口。顺着这个字符串往下追,你很快就能找到分派函数(IRP Handler)表的位置,然后逐个分析不同IOCTL对应的分支。这是写清逻辑链路的黄金路径。

3.3 字符串和导入表里的隐藏线索

静态分析中,字符串信息经常被低估。驱动里的中文字符串、路径名、命名管道、互斥体名称是判断恶意行为和家族归属的关键线索。我会先用二进制工具提取可打印字符串,加上UTF-16编码的字符串,然后人工浏览过滤。一百来个字符串通常只需要十几分钟就能筛出有价值的信息:某个硬编码的注册表路径、某个管道名、某个僵尸网络通信域名或者硬编码的密钥数据。

导入表也不可忽视。虽然驱动导入表里的API都是内核导出函数,但不同的API组合能快速指出驱动的功能类型:如果看到ZwOpenProcess、KeSetProcessAffinity这类API,很可能与进程注入和线程操作有关;如果看到CmRegisterCallback或ObRegisterCallbacks,那它应该想监控/篡改注册表或句柄操作;如果导入IoRegisterFsRegistrationChange和FsRtlRegisterFilter,则有文件系统过滤驱动的嫌疑。每类功能都有对应的一簇API集合,记住几个典型簇会大幅提升你的判断速度。

4. 动态分析:进入内核视角的行为捕获

4.1 观察驱动的加载路径

动态分析的第一步不是直接运行样本,而是准备好对加载过程的捕获。恶意驱动的加载方式有很多种,最常见的是通过服务管理接口创建内核服务,或者直接调用NtLoadDriver。在虚拟机里,你可以在运行前开启内核日志和文件监控,然后执行样本文件,再立刻查看日志,确认驱动文件被拷贝到了哪个目录、以什么服务名注册、加载顺序是怎样的。

这里有一个容易踩的坑:恶意驱动为了实现可靠加载,往往会先释放一个合法的“着陆器”(loader)程序,由用户态着陆器来加载真正的驱动。所以当你看到一个驱动样本自释放时,别急着只分析驱动本体,还要分析释放出来的那个加载器,因为真正的业务逻辑可能全部藏在加载器的交互协议里。之前分析过一个样本,驱动文件本身只做了IOCTL接口管理,真正进行文件加密逻辑的反而是一个独立的SYS文件,前者的唯一任务就是把这个SYS解密到内存并映射执行,这种分阶段加载的设计越来越常见。

4.2 内核断点与行为追踪

当驱动真正运行起来后,就要从静态分析转到动态分析。你需要把静态分析中定位的关键函数和关键地址转换为断点,然后在驱动模块加载后下断。常用的策略是模块加载事件触发时自动下断,例如在WinDbg中用sxe ld命令设置一个模块加载断点,当恶意驱动模块名出现在系统里时调试器自动中断,这时候你可以在驱动的入口(通常就是DriverEntry地址)下断,然后逐步跟踪。

动态分析中常见的问题是“跑飞”现象——即驱动在加载过程中触发了很多其他系统组件,调试器陷入大量无关代码中。这时要善用ba(break on access)断点:对某个特定内存地址或I/O端口设置访问断点,当硬件访问触发时中断,这样可以精准定位驱动操作的具体对象。比如你想知道驱动何时改写了某个内核函数的前几个字节,只要对那个函数入口下访问断点,就能在篡改发生瞬间断下来。

4.3 避开恶意驱动常见的反调试特征

恶意驱动同样有反调试意识,而且手段往往比用户态恶意软件更底层。最常见的一类是检测调试寄存器状态,比如读取系统中的KdDebuggerEnabled、KdDebuggerNotPresent、NtGlobalFlag等内核标志,如果发现有内核调试器附随,驱动会选择延迟执行或进行伪装行为。处理这一类反调试,比较有效的方法是先打开内核调试器,再在驱动运行前先把相关内核标志位的值改动一下,或者使用WinDbg的“调试器隐身边界”功能直接隐藏自身。

更麻烦的是检测虚拟机特征的反调试。很多恶意驱动会枚举系统固件表(如ACPI表)、读取特定的I/O端口或查找虚拟设备名称,用来判断自己是否运行在虚拟机中。遇到这类检测,常规做法是修改虚拟机配置,隐藏设备名称、调整ACPI表特征,或者在分析时直接对相关指令做补丁绕过。但每次修改虚拟机配置都会引入新的变量,后续分析行为时需要更加谨慎地核对。经验之谈:先确认样本是否存在虚拟机检测,再决定是否投入精力去绕;如果一个样本在虚拟机里完全无行为,很可能就是被环境特征“劝退”了。

5. 对抗机制拆解:恶意驱动的隐藏技术与持久化

5.1 隐藏进程和文件:DKOM与其他思路

恶意驱动一个标志性的功能是隐藏自己在系统中的痕迹,最常见的手段便是直接操作内核对象(DKOM,Direct Kernel Object Manipulation)或回调机制。DKOM的思路很朴素:每个进程在内核中对应一个EPROCESS结构,进程之间的链接通过双向链表串在一起。驱动如果想隐藏某个进程,只需要修改链表指针,将目标进程的节点从链上摘除,系统遍历进程时自然就看不到它了。这种方式非常经典,也相对容易被检测到——因为分派进程信息的API最终会遍历这个链表,链表的异常会暴露踪迹。

分析这类驱动时,我会关注两个点:一是看它是否修改了PsLookupProcessByProcessId的返回逻辑,二是看它是否通过ObRegisterCallbacks拦截了进程句柄的访问与控制。前者是主动式隐藏,后者是响应式保护——一旦有安全软件尝试打开被保护进程的句柄,驱动会回调中抹去关键权限位,让操作失败或返回错误数据。这两种行为在反汇编里都有相对明显的模式,你可以搜索PspCidTable相关操作或OB_OPERATION_REGISTRATION结构体特征。

5.2 内核回调:合法机制,恶意用法

Windows内核提供了一批回调接口供驱动注册,典型的有注册表回调CmRegisterCallback、进线程回调PsSetCreateProcessNotifyRoutine、镜像加载回调PsSetLoadImageNotifyRoutine和对象句柄回调ObRegisterCallbacks。恶意驱动常常利用这些回调来实现“观察-审核-篡改”三步曲:观察目标行为,审核是否符合恶意条件,进行数据篡改或拦截。

分析回调注册型驱动的一个实用方法是在动态调试中断下驱动加载,然后在回调注册函数上设置断点,观察回调函数的地址。获得回调地址后,直接在WinDbg中用u命令反汇编这段代码。如果你看到回调函数里有操作目标对象字段的代码,那基本可以确认它的功能:比如注册表回调里写入某个特定路径,进线程回调里检查映像路径是否匹配某个字符串。

5.3 持久化:开机自启与服务组策略

恶意驱动很难做到“一次加载、永远不死”。即使加载完成,系统重启后驱动映像文件也必须能被重新定位和重新加载。所以恶意驱动一定会留下持久化手段,最常见的是创建内核服务并把启动类型设为0(系统启动时加载),同时把驱动文件放到受保护目录,甚至用文件系统过滤技术把自己的驱动文件“遮罩”起来,管理员删除时莫名失败。

分析持久化时,我一般会去检查三类位置:服务注册表路径HKLM\SYSTEM\CurrentControlSet\Services下新出现的服务键,尤其注意ImagePath和Start值;启动文件夹或Run注册表项目;还有Winlogon相关的Shell替换。恶意驱动的服务名通常模仿系统组件或随机生成,遇到可疑项时可以使用sc query和sc qc命令查看服务的详细配置和对应路径。另外别忘了检查驱动文件本身是否有伴生文件,比如名为.dat或.ini的配置数据文件,很多驱动会在首次加载时释放一个配置文件,里面记录了加密密钥或C&C地址。

6. 一个典型恶意驱动样本的分析复盘

6.1 样本概况与静态勘查证据

下面用以前分析的一个样本作为案例,把整个分析复盘串起来。这个样本是一个SYS文件,大小约128KB,没有数字签名,文件时间戳显示编译时间大约在凌晨三点——这种非常规编译时间往往是样本自动构建程序打包产物,可信度有限。用DIE查看后发现没有加壳,但导入表里有若干个非常态组合的API入口:IoCreateDevice、ZwOpenProcess、ObRegisterCallbacks和RtlWriteRegistryValue,单从这四个API组合就能大致猜出它的意图——创建一个交互设备,同时保护某个进程,并写注册表做自启动。

进一步分析字符串时,发现了一个有意思的命名:设备名使用了一个“\\Device\\WinDriverComm”的样式,仿冒Windows组件;字符串中还出现了两个具体路径,一个是某个第三方安全软件的驱动名,另一个是注册表自启动路径。到这里基本可以确认:这是一只模仿系统载荷形态的驱动,目标极有可能与规避终端安全软件有关。

6.2 动态验证与关键行为时间线

静态分析之后,我在虚拟机里开始了动态观察。先是干净快照,然后落地驱动文件并执行加载,同时用Procmon、内核日志和网络抓包工具并行采集。

加载进行到第3秒时,内核日志显示驱动成功创建了设备对象,并注册了对象回调。第7秒左右,Procmon捕捉到一条对第三方安全软件驱动文件的尝试删除操作,但没有成功。随后网络抓包发现,驱动在加载后与一个海外IP地址建立了连接,传输数据第一个字节是固定的0x16——这很像某种协商握手。在这个节点,我回到调试器中用!object \Device\WinDriverComm查看设备对象的IoControlHandler分派表,确认设备控制码0xAA1A和0xAA1B两个分支并没有伪造行为,而是分别对应“开启保护”和“连接C2”操作。

整个行为链已经相对清晰:驱动加载 → 创建设备接口 → 注册对象回调来保护某个服务进程 → 连接远程地址 → 等待接收指令。从行为上看,这更像一个高级持久化工具的前置模块,而不是单一的破坏型驱动。

6.3 清除思路与检测规则

分析完成后的最后一步,是整理清除思路和检测规则。清除路径分为两部分:首先删除注册表中的服务项,然后重启进入安全模式/恢复环境删除驱动文件。由于驱动在运行时可能对自身文件使用句柄锁,直接删除多半会失败,所以干净的重启步骤在处置上更可靠。

检测规则方面,我优先推荐基于API调用模式的检测,而不是单一的哈希或静态特征。例如,在EDR或内核监控中,如果检测到某个非微软驱动的模块注册了ObRegisterCallbacks,且该驱动文件的签名信息异常(无签名、伪造签名),则应当触发告警。再看注册表如果同时出现Services下新增内核驱动键,且ImagePath指向一个可疑SYS文件,这就凑齐了高置信度检测三件套。如果实际环境允许,还可以在装有内核调试驱动的网关上抓取指向异常IP的流量,用网络侧的关联线索补充主机侧判断。

7. 分析过程中最容易栽的几个跟头

干这行这些年,见过不少人在恶意驱动分析里栽跟头,原因不是技术能力不足,而是几个细节没处理好。第一个高频问题是想在64位系统上直接运行未签名驱动,结果加载直接失败,很多人误以为是样本无行为。解决方法是提前准备一个允许强制签名禁用的测试环境,或者通过内核调试器以测试模式加载,只有驱动真正进入内存后才能分析它的运行逻辑。

第二个问题是模块运行后进程表看不到任何明显恶意进程就判定“无害”。前面反复提到,恶意驱动的功能可以完全在内核态实现,不创建任何用户态进程。判断驱动是否恶意,不能只看进程列表,要结合文件行为、注册表行为、内核回调注册情况和网络请求综合评估。

还有一个问题是用调试器跟踪时被反调试干扰,却不自知。如果你发现样本在调试器附随下出现异常延迟、函数重复执行或返回状态码明显不符合上下文,应该立刻检查是不是反调试机制干扰,而不是一头扎进去死磕单步跟踪。这种时候最经济的方法是直接跑一遍无调试器的行为捕获,对比两次结果,差异会告诉你反调试保护的范围和强度。

8. 一些实操时很有用的命令与判断技巧

在日常分析里,有几条命令组合我认为效率极高,整理出来供参考。WinDbg环境里,加载恶意驱动模块后,先用lm确认模块基址,再结合!dh或!lmi查看PE头部信息;想知道当前系统有没有系统回调被注册,输入!ci或查询nt!CallbackListHead链;碰到设备对象问题,优先用!drvobj和!devobj。字符数据分析这块,strings -el提取UTF-16字符串之后,优先搜索关键词“Device”“SymbolicLink”“Register”“Protect”和“SelfDelete”,这五个词能帮你迅速建立功能基线。

还有一条重要经验:所有命令、断点和脚本在使用前先在干净系统上试一遍。很多新手以为自己写出了完美的自动化分析脚本,结果到了靶机上一跑就报错,原因是脚本里的地址偏移或符号查找方式不通用。建议用系统符号表或本地符号缓存来解析内核函数,尽量不要写死硬编码地址。每一次成功的分析都是“静态读码 + 动态确认 + 交叉验证”的组合拳,少一个环节都可能让你对样本行为产生误判。

回看整个分析过程,恶意驱动逆向分析的魅力在于,它迫使你从“应用层用户”切换成“操作系统的管理视角”,重新审视内核与用户态之间的每一个接口和信任边界。每一次样本分析不只是解开一个恶意代码的秘密,也是加深对操作系统本身运行机制的理解。如果你也想在安全分析这条路上走得更深,我建议多花时间去读内核文档、多写调试脚本、多复盘那些“看似无害”的驱动样本——它们通常才是更深层攻防逻辑的起点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询