DEV Community

Bartosz Osiej
Bartosz Osiej

Posted on

Your Antivirus Only Watches. Mine Kills: Building a Detect-and-Respond Agent in Rust + eBPF

Most ransomware "solutions" stop at detection: an alert, a dashboard row, an email you'll read tomorrow. By then the attacker has already encrypted the shares.

I wanted the other thing: the process dies the moment the verdict fires. This post is about how Talus does that in Rust, with eBPF doing the watching and a response layer doing the killing — and what it took to keep the whole pipeline at ~280,000 events/second on ~7.6% CPU on a live desktop.

Talus is MIT-licensed and open source: github.com/BartoszOsiej/talus-process-monitor — demo GIF included, it really does SIGKILL the ransomware simulator in the terminal.

The shape of the problem

Ransomware is boring to detect in theory: it opens a lot of files, very fast. The hard parts are:

  1. Seeing the opens without slowing them down. Every file open goes through the kernel — if your monitor adds latency there, you're taxing every process on the machine.
  2. Not killing rsync or your build system. A naive "too many opens = ransomware" rule murders backup jobs and cargo build.
  3. Acting faster than the encryptor. Detection that arrives after the first directory is wiped is just forensics.

Architecture: kernel watches, userspace thinks, response acts

Talus is a pipeline, and every stage exists to keep stage 1 cheap:

KERNEL SPACE (eBPF tracepoints, Linux 5.8+)
   │  hooks syscalls at the source — no LD_PRELOAD, no FUSE
   ▼
per-CPU perf buffers  ──►  zero-copy ring  ──►  userspace detection engine
                                                     │  per-PID sliding window
                                                     ▼
                                          TUI / JSON / WebSocket / Prometheus
                                                     │  verdict
                                                     ▼
                                               response layer (SIGKILL)
Enter fullscreen mode Exit fullscreen mode

Why this split works:

  • eBPF tracepoints mean zero drivers and zero kernel patches. The probe is verified by the kernel before it runs — it cannot crash the box.
  • Per-CPU perf buffers mean no cross-CPU lock contention. Each core streams its own events; nothing serializes.
  • Zero-copy handoff to userspace means the detection engine reads events without copying them again.

Detection: a 1-second sliding window per PID

The heuristic is deliberately simple: per-PID file-open rate inside a 1-second sliding window. Cross the configurable threshold and the verdict fires.

Sliding (not fixed) windows matter here. With fixed buckets, a bursty process that opens 200 files in 50ms every second can hide between samples. A sliding window sees the burst inside the second.

To cut false positives, the file ranking layer scores most-opened files with Shannon entropy — encrypted or randomized filenames stand out from normal workloads immediately.

And the threshold is yours to set: watch-mode for auditing, low-threshold + auto-kill for EDR mode.

Response: yes, it kills

The response layer sends SIGKILL to the offending PID the moment the verdict fires. Not "quarantine eventually", not "notify admin". Kill, then log, then alert.

Is SIGKILL aggressive? Yes. That's the point — ransomware doesn't pause for your opinion. If you want to watch first, run monitor-only mode. The demo in the repo shows exactly this: terminal 1 runs Talus with a low threshold and auto-kill, terminal 2 runs a mass-file-encryption simulator, and Talus ends it mid-run.

The performance work that actually mattered

Getting to 280k events/s at ~7.6% CPU was mostly subtraction:

  • Don't copy what you can reference. The zero-copy ring between kernel and userspace removed an entire memcpy stage.
  • Don't aggregate what you can count. The eBPF side does minimal work per event — increment a map entry, emit when needed. The sliding-window math lives in userspace, where a bug costs a panic, not a kernel oops.
  • Let each CPU mind its own business. Per-CPU buffers beat one big shared buffer by not fighting over locks at the busiest moment (an attack is precisely when events spike).

The full data-flow diagram and pipeline stages are documented in ARCHITECTURE.md.

Try it in 30 seconds

# monitor-only
talus monitor

# full EDR mode: detect + auto-kill
talus monitor --kill --threshold 50
Enter fullscreen mode Exit fullscreen mode

Second terminal, if you want to see a verdict fire:

# ransomware-like mass file-open burst
for i in $(seq 1 10000); do echo x > /tmp/victim_$i; done
Enter fullscreen mode Exit fullscreen mode

(On a throwaway VM or container, like any security demo.)

What's next

Roadmap items I'm working through: per-process allowlists tuned for build/backup workloads, and richer response actions beyond SIGKILL. The enterprise maturity report (level 4/20 path, priority patches, license) is on the landing page for teams that need a paper trail:

If you build something on top of it — or it saves you from a real incident — I want to hear that story in the comments.

Top comments (0)