Rekd hooks the Linux VFS write path (vfs_write and vfs_writev) with eBPF and scores write payloads by Shannon entropy to catch ransomware in real time. Detection is signature-independent — it looks at what gets written to disk, not at the malware's code, so obfuscation and packing don't matter to it.
Distributed as a single CO-RE static binary — no kernel headers, no libbpf, no Go install needed on the target.
Ransomware has one property it can't avoid: it writes high-entropy ciphertext to disk. Rekd measures that instead of trying to fingerprint the malware.
Kernel side — fentry hooks on vfs_write and vfs_writev (the latter closes the vectorized-I/O bypass used by ransomware like Akira), filtered before anything leaves kernel space:
- writes under 512 bytes are dropped (kills most filesystem noise — WAL checkpoints, journal flushes)
- only regular files are considered (
inode.i_modebitmask); sockets and pipes are ignored - payload is sampled in three 512-byte chunks (header/mid/footer) rather than copied whole, to stay inside the eBPF verifier's memory-safety limits
Userspace side — entropy math runs async so the kernel path is never blocked:
- Shannon entropy, threshold 7.5 (plaintext sits around 4.5; AES ciphertext scores well above the threshold)
- an alert needs both a 70% high-entropy-write ratio and 1MB of cumulative high-entropy volume for that process — either gate alone false-positives on things like gzip or a single large backup write
| Gap | Impact |
|---|---|
| iovec walk bounded to 8 segments | a pathological writev with >8 iovecs undercounts total size; sampling only inspects the first segment |
| One alert per PID | s.Alerted never resets; a pause-resume pattern fires no second alert |
| Scattered sampling | a front-loaded plaintext header could score under threshold |
| No persistent state | a restart resets all per-PID accumulators |
fname is not a full path |
only the dentry name is captured |
| Silent ring buffer drops | a full 16MB ring buffer discards events with no back-pressure |
io_uring writes |
can take the vfs_iter_write path and bypass both hooks |
Rekd detects and logs. It does not kill the process.
sudo ./rekd # interactive TUI
sudo ./rekd --daemon # headless, logs via journaldOr as a systemd service:
sudo ./scripts/install.sh
sudo systemctl status rekd
sudo journalctl -u rekd -fRequires Linux ≥ 5.8 with CONFIG_DEBUG_INFO_BTF=y — default on Ubuntu 20.10+ (20.04 LTS needs the HWE kernel; base 5.4 is too old), Fedora 31+, Debian 12+, RHEL/CentOS Stream 9+, Arch — and root. Rekd checks both at startup and prints an actionable error if either is missing.
The BPF .o objects are committed, so no BPF toolchain is needed to build the Go binary:
make build # CGO_ENABLED=0, static ./rekd
sudo make install # -> /usr/local/binOnly needed if you're changing internal/bpf/main.bpf.c:
sudo apt install clang llvm libbpf-dev # or: dnf install clang llvm libbpf-devel
make generate
git add internal/bpf/bpf_bpf*.o # commit the regenerated objectsGo implementation: under 1.4% idle CPU. The original Python POC (archived in poc/) ran over 6%.
rekd/
├── cmd/rekd/main.go — workers, aggregator, TUI, startup checks
├── internal/bpf/
│ ├── main.bpf.c — eBPF program
│ ├── bpf_bpfel.o / .go — compiled object + generated bindings (committed)
│ └── vmlinux.h — kernel BTF header (CO-RE)
├── tests/dummy_ransomware/ — AES-CTR encryptor used for testing
├── poc/python/ — original Python POC (archived)
├── scripts/ — install.sh / uninstall.sh
└── docs/ARCHITECTURE.md — component breakdown
AES-CTR simulator and test runner in tests/, see tests/README.md. The suite generates a gitignored thekey.key — safe to ignore.
Spider R&D Cybersecurity