Simple tool for detecting and alerting suspicious activity on Linux, Windows, and FreeBSD/pfSense.
Built-in detections cover reverse shells (bash/dev/tcp, nc -e, socat, python/perl/ruby/php one-liners, mkfifo), tunnels (chisel, frp, ngrok, ssh -D/-L/-R, DNS tunnelers), credential access (shadow reads, mimikatz, LSASS dumps, SAM saves), LOLBins (certutil, bitsadmin, mshta, rundll32, regsvr32), persistence actions (useradd, chmod u+s, setcap, schtasks, net user /add), anti-forensics (history -c, log shredding), and recon tools. Rules are regex-based and extensible via config.
Install once per box; after that elmer runs as a service (started at boot, restarted whenever it dies) and the only interaction is reading its logs.
# Linux (root): systemd service
sudo ./elmer-linux-amd64 install -c elmer.yaml
tail -f /var/log/elmer/elmer.log
# Windows (elevated): LocalSystem service, also enables 4688 auditing
.\elmer-windows-amd64.exe install -c elmer.yaml
Get-Content -Wait -Tail 50 C:\ProgramData\elmer\logs\elmer.log
# pfSense / FreeBSD (root shell, menu option 8): rc.d service under daemon(8)
./elmer-freebsd-amd64 install -c elmer.yaml
tail -f /var/log/elmer/elmer.logRunning elmer during a competition? See the competition-day runbook: installing on each box, reading alerts, and what to do about them. The full documentation covers configuration, every monitor and rule, the log format, alert channels and the security model.
Without -c, install keeps an existing config or writes the commented
default (elmer init-config writes the same template for editing first;
elmer test-alerts -c elmer.yaml checks delivery). Re-run install to
upgrade the binary or push a new config; elmer uninstall removes the
service but keeps config, state and logs.
Logs live in /var/log/elmer (Linux, also journald) or
C:\ProgramData\elmer\logs (Windows): elmer.log has one line per
event, events.jsonl every field for jq. They rotate by size, carry a
heartbeat every 5 minutes (a gap means elmer was down), and keep recording
when red team cuts internet to the alert channels. elmer start still runs
in the foreground for testing.
Channels: stdout (always, colored or --json NDJSON for jq),
Discord webhook (batched embeds, rate-limit aware), ntfy push
(severity → priority), generic webhook (full event JSON, optional
HMAC-SHA256 X-Elmer-Signature). Each channel filters by min_severity
and has its own queue — a hung webhook never blocks the others. A Wazuh
agent can also ship events.jsonl to a manager, using the rules in
contrib/wazuh/ (see the runbook).
Every other monitor reports changes, so anything planted before elmer
first runs would look normal. elmer therefore sweeps the box at startup
and again every hunt.interval (30m), reporting on re-sweeps only what
was not there last time, so re-planted implants show up again. elmer hunt does the same sweep by hand, printing findings most-severe first
without alerting:
- Linux: running processes (deleted/memfd/tmp/hidden executables,
[kworker]masquerades,LD_PRELOAD, pids hidden from/proc), listeners and raw/packet sockets, uid-0 and passwordless accounts, sudo rules, SSH keys and sshd backdoor settings, shell profiles, cron, systemd units,ld.so.preload, PAM, tainted/hidden kernel modules, SUID shells and root-granting file capabilities, webshells, anddpkg --verify/rpm -Vafor trojaned binaries and PAM modules. - Windows: processes (system names from the wrong path, user-writable locations), listeners and external connections, services and service DLLs, Run keys, startup folders, scheduled tasks, WMI subscriptions, IFEO/SilentProcessExit, sticky-keys swaps, Winlogon, AppInit_DLLs, LSA packages, Defender exclusions/disables, local admins, webshells.
- FreeBSD / pfSense: the Linux account/SSH/cron/shell/PAM/SUID checks
plus rc.d boot scripts, libmap.conf redirects, kernel modules outside
/boot,
pkg check -sfor modified packaged files, code in the pfSense GUI that no package owns, PHP/nginx settings that inject code into the GUI, and pfSense'sconfig.xml: users with SSH keys, boot commands (shellcmd/earlyshellcmd), cron jobs, port forwards, WAN allow rules, packages, logins handed to an external auth server.
Commands found in cron lines, units, Run keys and tasks also go through
the regular detection rules. Disable with monitors: {hunt: false}.
Uptime is a third of the score, and the scoreboard says a check fails, not why. The watchdog alerts HIGH when a service stops working and MEDIUM when it is back, after two failed checks in a row so a deliberate restart does not page. It never restarts anything.
With no configuration it watches every reachable (non-loopback) TCP listener seen in the first two minutes after start, and says why one went away:
HIGH service down: tcp/3306 (mysqld): nothing listens on port 3306 any more;
/usr/sbin/mysqld (pid 812) has exited. The log lines just before this one show what changed.
HIGH service down: tcp/22 (sshd): port 22 now listens on loopback only: unreachable to the scoring engine
Configured services also get a local protocol check (tcp, http,
https, ssh, ftp, smtp, mysql, dns):
watchdog:
services:
- {name: web, port: 80, check: http, path: /}
- {name: mysql, port: 3306, check: mysql}It sees the box, not the network: a pfSense rule, routing, or a changed password the scoring engine logs in with can still fail a check while everything looks fine locally (other monitors report those changes).
There is no tracing tier: elmer polls the process table (every
poll_interval), stats fim.paths and runs sockstat every 2s. Like
Linux's /proc fallback, a process that starts and exits between polls
can be missed. The persistence monitor diffs the live pfctl ruleset
and, on pfSense, the security-relevant parts of /conf/config.xml
within seconds of every save (new users or SSH keys, boot commands,
cron jobs, port forwards, pass rules, auth servers), along with the
GUI's PHP and nginx configs. Logins, including webConfigurator
logins and failures, come from /var/log/auth.log. Keep pfSense's "Use
RAM Disks" off so logs survive reboots.
elmer picks the best telemetry source available and degrades loudly:
- bpftrace (root +
bpftraceon PATH, kernel with tracepoints): successful execve/execveat with full argv, connect/bind with peer address and owning pid. The.btscript is embedded; elmer spawns the host bpftrace binary.elmer-bpftracecheck(built fromtools/bpftracecheck, shipped with the Linux release) verifies it works on a box. If bpftrace dies mid-run, elmer raises a HIGH alert and the tiers below take over. - Netlink proc connector (root): exec events (when bpftrace is not
running) and setuid-to-root escalations from binaries that aren't
setuid-root (
cap_setuidabuse, exploits), enriched from /proc. Runs alongside bpftrace for the setuid events. - /proc polling (any user): exec diffs every
poll_interval— misses processes that exec and die between scans.
| Command | Purpose |
|---|---|
elmer install [-c cfg] |
Install/upgrade as a self-restarting service and start it |
elmer uninstall |
Remove the service (keeps config, state, logs) |
elmer start [-c cfg] [--json] |
Run all monitors in the foreground until interrupted |
elmer hunt [--min-severity low] [--json] |
One-shot sweep for implants already on the box |
elmer audit [--write-baseline] |
One-shot persistence surface report |
elmer test-alerts |
Synthetic alert through every channel |
elmer init-config [path] |
Write a commented default config |
elmer harden |
Platform steps to maximize telemetry (Windows: runs them) |
elmer version |
Print version |
Config discovery: -c → $ELMER_CONFIG → ./elmer.yaml →
/etc/elmer/elmer.yaml (/usr/local/etc/elmer/elmer.yaml on FreeBSD,
%ProgramData%\elmer\elmer.yaml on Windows). With no
config file elmer runs on built-in defaults.
make test # unit tests (host platform)
make build # dist/elmer-{linux,windows,freebsd}-{amd64,arm64}[.exe]Releases: make checksums VERSION=v1.0.0 PUBKEY=<public key> builds
every target with the tag stamped into elmer version and the signing
key baked in, plus SHA256SUMS. elmer isn't published on GitHub: the
team hosts the binaries on its own server (see docs/runbook.md).
Static binaries, CGO_ENABLED=0. Dependencies: cobra, x/sys, yaml.v3.
Linux top-tier telemetry requires the host bpftrace package
(apt-get install bpftrace).