This walks you from source to a running beta, using Visual Studio 2022. CLI equivalents are given where useful. Read section 0 first.
BruceEDR suspends and can kill processes, adds Windows Firewall rules, and moves files to quarantine. Do not run it on a machine you care about.
- Use a disposable Windows 10/11 x64 VM (Hyper-V, VMware, or VirtualBox).
- Take a VM snapshot before you start so you can roll back.
- Keep
autoKill=false(the default) during early testing so contained processes are only suspended, not terminated.
| Need | For |
|---|---|
| Windows 10/11 x64 (or Server 2019/2022), in a VM | running the agent (ETW + admin) |
| Visual Studio 2022 17.8+ with the ".NET desktop development" workload | building the solution |
| .NET 8 SDK | included with that VS workload (or install standalone for CLI) |
| Local Administrator rights | ETW kernel session, process access, service control |
| (optional) Windows Driver Kit (WDK) matching your VS | building the kernel minifilter (section 9) |
Nothing extra is needed for the default build. YARA and the kernel driver are opt-in (sections 7 and 9).
- Copy
BruceEDR.zipinto the VM and unzip it. You'll get aBruceEDR\folder containingBruceEDR.sln. - Folder map:
BruceEDR.csproj- the agent (app)tests\BruceEDR.Tests\- the xUnit test projectkernel\BruceFilter\- the C minifilter (built separately with the WDK)rules\- sample YARA rules, andrules\detection\- the JSON detection packsReplay\scenarios\- detection scenarios replayed by--selftestintel\feeds\- drop your indicator feeds heretools\simulate-benign-stealer.ps1- the safe detection demotools\verify.ps1- build + tests + rules + replay in one commandbruce.config.json- configuration
- Double-click
BruceEDR.slnto open it in Visual Studio 2022. - Wait for NuGet restore (watch the status bar). If it doesn't start automatically: right-click the solution in Solution Explorer -> Restore NuGet Packages. (First restore needs internet: it pulls TraceEvent, System.Management, and Microsoft.Extensions.Hosting[.WindowsServices].)
- In the top toolbar set the two dropdowns to
Releaseandx64. (Both projects are x64-only;x64is the only platform offered.) - Build -> Build Solution (
Ctrl+Shift+B). You should getBuild: 2 succeeded, 0 failed.
Output: bin\x64\Release\net8.0-windows\BruceEDR.exe (plus its DLLs).
CLI equivalent:
dotnet build BruceEDR.csproj -c Release
Besides the console, the solution includes a WPF desktop app — a dark
instrument-panel dashboard. It shows current posture, a live table of contained and
flagged processes with one-click Release / Suspend / End process, a live event
feed, and a Settings editor that writes bruce.config.json (threshold and
allowlist changes apply live).
The Watchlist tab is where you name things you have already decided do not belong on
your estate. Each entry matches on one of four things — process name, image path,
image SHA-256, or a command-line fragment (names and paths accept */? globs) —
and picks one of three actions:
| Action | What happens |
|---|---|
quarantine |
Contained on sight: suspended and network-blocked the moment it appears |
warn |
Alert only, no containment |
score |
Adds points and lets the normal thresholds decide |
Two things make this more than a blocklist:
- A
quarantineentry overrides a trusted signature. If you name a binary by hand, a valid Authenticode chain does not excuse it — that is the whole point of naming it. - It cannot be aimed at Windows itself. Entries resolving to a protected system process
(
lsass.exe,csrss.exe,services.exe,svchost.exe, …) are still reported, but containment is refused, so a typo in a rule cannot bugcheck the host it is defending.
Entries live in bruce.config.json under watchlist and are hot-reloaded — saving in
the GUI arms them within a second or two, no restart. Invalid entries are rejected
individually with the reason, and a pattern that would match everything (*, *.exe) is
refused outright.
The Network map plots every observed outbound destination on a world map, grouped by country and sized by connection volume. Hovering a marker highlights its endpoints in the side list and vice versa; markers are coloured by severity, and a dashed ring marks a destination reached over plaintext HTTP. Traffic that cannot be placed (local network, IPv6, unallocated space) is counted in a tray rather than silently dropped.
Geolocation is fully offline — BruceEDR makes no network call to build this view,
because asking a third party where an address is would tell them exactly which
infrastructure you are investigating. It resolves the country an address block is
registered to, at /16 resolution, so CDNs and anycast land in the wrong place. Read
intel/geo/README.md before drawing any conclusion from a marker.
- Incident detail — the selected process shows its ATT&CK technique chips, command line, ancestry chain, peak score and reason timeline; Copy report puts a plain-text hand-off on the clipboard, Open location reveals the image in Explorer (both also on the row's right-click menu).
- Live events — search box, severity filter chips with live counts, Pause/Resume (buffered while paused), Export to CSV/JSON, per-row copy.
- API surface — search, an only beaconing switch, CSV export.
- Coverage — techniques grouped by tactic with per-tactic coverage bars and links to each MITRE page.
- Status bar — monitor health, signals/sec, processed count, p95 detection latency, rules/IOCs loaded, uptime, and a dropped-signal warning when the pipeline sheds telemetry.
- Tray icon — minimizing keeps monitoring from the notification area and containments raise a balloon alert (toggle in Settings → Interface).
- Quality of life — window size/position and last tab persist,
F5refreshes,Ctrl+1…5switch views.
Build the solution (section 3), then run it either way:
- Visual Studio: right-click BruceEDR.Gui in Solution Explorer -> Set as Startup Project, then Debug -> Start (F5). It requests administrator rights through its manifest, so a UAC prompt appears automatically.
- Or run
BruceEDR.Gui.exefromgui\BruceEDR.Gui\bin\x64\Release\net8.0-windows\.
It drives the same engine as the console — use whichever you prefer. The detection walkthrough below works with either front-end.
- In VS: Test -> Run All Tests (opens Test Explorer). All 2,016 tests should pass. They cover the exfil-chain scoring, the JSON rule engine, ATT&CK mapping, beacon and DGA analytics, the process tree (including PID reuse and hostile parent cycles), PE parsing against malformed files, indicator feeds, the encrypted quarantine vault, ECS/OCSF/CEF serialisation, the whole API Studio stack, the audit hash-chain (incl. tamper detection), config clamping, the firewall-name sanitizer, the routable-IP check, and the pattern matcher.
CLI equivalent — or just run everything at once:
dotnet test tests\BruceEDR.Tests\BruceEDR.Tests.csproj -c Release .\tools\verify.ps1 # build + tests + rule validation + scenario replay
This is the fastest way to know the detection content is healthy, and it is the loop to use when writing rules:
BruceEDR.exe --selftest
It validates every JSON rule pack (reporting rule count and ATT&CK coverage) and replays
every scenario in Replay\scenarios\ through a real detection engine on a simulated
clock. It starts no monitors and needs no elevation, so it is safe to run anywhere.
The agent requires elevation. Double-clicking the .exe now pops a UAC
prompt and relaunches itself elevated (accept it, and an elevated console opens
at the bruce> prompt). If anything fails at startup the window stays open with
the error and a "Press Enter to close" pause, so it won't just vanish anymore.
For the cleanest experience, run it from an elevated terminal. Pick one:
Option A - elevated terminal (recommended, simplest):
- Open Windows Terminal or cmd via right-click -> Run as administrator.
cdinto the build output folder, e.g.:cd C:\path\to\BruceEDR\bin\x64\Release\net8.0-windows- Run:
BruceEDR.exe
Option B - from Visual Studio: right-click Visual Studio -> Run as
administrator, reopen the solution, make BruceEDR the startup project,
then Debug -> Start Without Debugging (Ctrl+F5). If VS is not elevated the
app prints Run as Administrator and exits by design - use Option A.
You should see it start the ETW monitor and print the prompt:
[*] BruceEDR active (console mode). Monitors: ETW(process,image,file,network), FileStaging.
bruce>
Console commands: list / list all, info N, tree N, resume N, suspend N,
kill N, stats, metrics, reload (re-read config, rules and intel), audit (verify
the tamper-evident log), attack, rules [id], intel, surface [pid], api ...,
vault ..., isolate on|off, triage N, replay <file>, quit. Type help for the
full list.
With the agent running (section 5), open a second elevated PowerShell and run the included harmless simulator:
powershell -ExecutionPolicy Bypass -File .\tools\simulate-benign-stealer.ps1
It reproduces the collect -> archive -> exfil shape without stealing anything:
it writes a dummy file on a path containing \Google\Chrome\User Data\...\Login Data under %TEMP%, zips it into %TEMP%, and opens/closes a TCP connection to
1.1.1.1:443. That crosses the quarantine threshold, so BruceEDR will
suspend that PowerShell process.
Now switch to the BruceEDR console:
bruce> list
# PID SCORE STATE NAME
1 7364 90 contained powershell.exe
bruce> info 1 # see the full [+points] reason breakdown
bruce> resume 1 # release it (or: kill 1)
The simulator sleeps ~90s so you can observe and resume it, then cleans up its dummy files.
If ETW couldn't start (you'll see it fall back to WMI), file/network events are not captured and only process-start heuristics fire - make sure you launched elevated.
YARA is off by default so the standard build has no third-party native/API dependency. To enable it:
dotnet build BruceEDR.csproj -c Release -p:EnableYara=true
(In Visual Studio, open Tools -> Command Line -> Developer PowerShell and run
the same command, or add <EnableYara>true</EnableYara> to a
Directory.Build.props at the repo root.)
Then set "memoryScanEngine": "yara" in bruce.config.json. If the installed
dnYara build doesn't match, the agent logs a warning and falls back to the builtin
scanner automatically - it won't crash.
The service host needs a self-contained executable so its binPath is the app
itself (not dotnet.exe).
- Publish self-contained:
(VS: right-click the project -> Publish -> Folder -> target
dotnet publish BruceEDR.csproj -c Release -r win-x64 --self-contained true -o publishwin-x64, deployment mode Self-contained.) - From an elevated prompt in the
publishfolder:This registers and starts theBruceEDR.exe --installBruceEDRservice (with auto-restart recovery) plus a SYSTEM watchdog scheduled task. - Verify:
Events stream to the configured
sc query BruceEDRincidents.jsonlandaudit.log. - Remove it when done:
BruceEDR.exe --uninstall
Tamper note: the watchdog + service recovery restart the agent if it crashes or is stopped, but an administrator can still kill both. True kill-resistance needs PPL/ELAM, which requires Microsoft's anti-malware vendor program (out of scope).
Real inline prevention. Built separately with the WDK.
- Install the WDK matching your VS 2022.
- In VS: New Project -> "Filter Driver: Filesystem Mini-Filter". Add
kernel\BruceFilter\BruceFilter.c,.h, and.infto it. Build x64 / Release to produceBruceFilter.sys. - In the VM, enable test signing and reboot:
Then install + load (see full steps in
bcdedit /set testsigning onkernel\BruceFilter\README.md):RUNDLL32.EXE SETUPAPI.DLL,InstallHinfSection DefaultInstall 128 .\BruceFilter.inf fltmc load BruceFilter - The agent's
MinifilterClientauto-connects to\BruceFilterPortand pushes policy on startup.
Keep
"kernelBlocking": falseunless you've extended the driver with a trusted-PID allowlist (per the driver README) - with ittrue, the skeleton denies every process access to sensitive paths, including legitimate apps. Production loading (no test signing) requires attestation/EV signing + a Microsoft-assigned altitude.
Key settings:
detection.warnThreshold/quarantineThreshold- scoring cutoffs.detection.autoKill-false= suspend only (recommended for beta).detection.memoryScanEngine-"builtin"or"yara".detection.rulesPath- where the JSON detection packs live (defaultrules/detection).detection.enableScoreDecay- lets a quiet process cool off so it cannot trip on weeks of accumulated low-value hits. Contained processes never decay.detection.enableExtendedMonitors- the DNS / AMSI / registry / process-access ETW sessions. Turn off if one of them misbehaves on your build; the agent keeps running.allowlist.publishers/thumbprints- signed apps to de-prioritise (e.g. your legitimate RMM tools); pin exact SHA-1 thumbprints for strongest trust.intel.feedPath- drop hash/domain/IP/CIDR indicator files in here (seeintel/feeds/README.md).response.useEncryptedVault- quarantine files into an AES-256-GCM vault instead of moving them somewhere they are still runnable.response.isolationAllowlist- set this before ever usingisolate, or you will cut off your own remote session.telemetry.format-native,ecs,ocsforcef. Pick the one your SIEM parses.telemetry.syslog/webhook- setenabled+ endpoint to forward to a SIEM.api.control.enabled- the localhost REST control plane. Off by default, and read-only unless you also setallowActions.api.studio.allowedHosts- API Studio sends nothing until a host is listed here.
Editing the file hot-reloads the detection posture, allowlist, detection rules and
indicator feeds live (the console prints config applied: ... / rules reloaded: N).
Scan-engine, telemetry-format and control-API changes take effect on restart.
Rules are JSON and need no rebuild. Copy one out of rules\detection\, edit it, and type
reload at the bruce> prompt. The schema, every field and operator, and the
contribution guide are in rules\detection\README.md. Test with a replay scenario rather
than with live malware — see Replay\scenarios\README.md.
bruce> surface # what this machine has actually been connecting to
bruce> api surface # turn that into an inspectable collection
bruce> api send 1 # send it and grade the response
bruce> api help # everything else
It refuses to send anywhere until you allowlist a host in api.studio.allowedHosts, and
refuses POST/PUT/PATCH/DELETE until you set allowMutatingMethods. That is deliberate.
incidents.jsonl- one JSON line per WARN/QUARANTINE/ACTION event.audit.log- the same events in a keyed (HMAC-SHA256) hash chain with a sibling head-anchor (audit.log.anchor) and key (audit.log.key). Runauditin the console (orAuditLogSink.Verify) to detect alteration, reordering, interior deletion, tail truncation, and emptying.- Honest scope: the key lives on disk next to the log. This defeats an attacker who
has only a copy of the log, or who cannot read the key — so protect the audit
directory with an admin-only ACL. It does not defeat a same-privilege attacker who
can read the key (BruceEDR runs elevated, so a same-integrity RAT can re-forge the
chain). For proof against an equal-privilege adversary, forward every event off-box to
an append-only SIEM (enable the
syslog/webhooksinks) and reconcile against that remote head — the local chain is evidence, not a guarantee, once the host is owned.
- Honest scope: the key lives on disk next to the log. This defeats an attacker who
has only a copy of the log, or who cannot read the key — so protect the audit
directory with an admin-only ACL. It does not defeat a same-privilege attacker who
can read the key (BruceEDR runs elevated, so a same-integrity RAT can re-forge the
chain). For proof against an equal-privilege adversary, forward every event off-box to
an append-only SIEM (enable the
| Symptom | Fix |
|---|---|
App prints Run as Administrator and exits |
Launch from an elevated terminal (section 5, Option A). |
| Window flashed open and closed on double-click (older build) | The app now self-elevates via UAC; accept the prompt, or run from an elevated terminal. |
Falls back to WMI(process) only |
You're not elevated, or ETW is blocked; file/network correlation is limited until ETW works. |
Build error about platform / AnyCPU |
Set the VS platform dropdown to x64. |
| NuGet restore fails | Check the VM's internet/proxy; retry Restore NuGet Packages. |
--install says it needs a self-contained exe |
You ran it from a framework-dependent build; publish self-contained first (section 8). |
| YARA build errors | Only happens with -p:EnableYara=true; the default build doesn't reference dnYara. |
| Nothing detects during the sim | Confirm the agent started ETW (not WMI) and that you ran the sim after the agent. |
| GUI shows a red banner, a warning dialog, or won't start | It logs to %LOCALAPPDATA%\BruceEDR\gui.log — open that for the exact error. The banner usually means "not running as Administrator." |
rules: '...' not found; running with builtin detections only |
The rules/detection folder didn't reach the output directory. Rebuild, or point detection.rulesPath at an absolute path. |
| The API surface tab is empty | It needs the DNS/network ETW monitors, which need elevation, plus some actual traffic. Check stats shows DNS among the active monitors. |
api send says blocked by API safety policy |
By design. Add the host to api.studio.allowedHosts, and set allowMutatingMethods if you need a non-GET. |
A monitor is missing from stats |
Each extended ETW monitor degrades independently — the startup log says which one failed and why. The agent is still detecting on everything else. |
--selftest fails after editing a rule |
It prints the exact validation error and which scenario expectation went unmet. That is the intended feedback loop. |
This is a hardened prototype plus real integration layers, audited by review
but not yet run through a full security review or tested against live malware.
Two capabilities are intentionally not shippable here because they're gated behind
Microsoft programs: PPL/ELAM tamper protection and production driver
signing. See README.md -> "Security model & honest limitations".
What is verified on every build: the solution compiles with zero warnings, 2,016 unit tests pass, all 72 detection rules validate, and all three replay scenarios meet their expectations — including the benign one that must produce no verdicts at all.
What is not verified: the four extended ETW monitors (registry, DNS, AMSI, process-access) have not been soak-tested against a live fleet; host isolation and triage collection have been unit-tested at the command-construction level but not exercised end-to-end on a production box; and nothing here has been run against real malware. Treat the detection content as a starting point to tune, not as a finished ruleset.