Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mini eBPF EDR

This project is a small eBPF-based EDR prototype for tracing suspicious process activity on Linux and storing the results as JSON Lines logs.

The current implementation collects execve, openat, and connect syscall events through tracepoints. openat correlates syscall entry and exit to record success or failure, and connect is enriched in user space with command line and allowlist context before being written to ./log/events.jsonl.

Detection Scope

  • EXEC_FROM_TMP: Binary execution from /tmp/, /var/tmp/, or /dev/shm/
  • SHADOW_OPEN_ATTEMPT: Failed openat attempt for /etc/shadow
  • SHADOW_OPEN_SUCCESS: Successful openat for /etc/shadow
  • SUSPICIOUS_CONNECT: Suspicious public IPv4 connections from shells or script runtimes that are not covered by the allowlist

Architecture

syscall
  -> tracepoint/syscalls/sys_enter_*, sys_exit_openat
  -> eBPF program
  -> BPF ring buffer
  -> user-space loader
  -> /proc/<pid>/cmdline enrichment for connect
  -> rule engine
  -> JSONL writer
  -> ./log/events.jsonl

Key files:

  • src/edr.bpf.c: eBPF tracepoint program
  • src/main.c: libbpf skeleton loader and ring buffer polling loop
  • src/config.c: config/rules.json loader
  • src/rules.c: Rule engine
  • src/json_writer.c: JSON Lines writer
  • include/events.h: Shared event structure for kernel and user space
  • config/rules.json: Detection rule configuration
  • scripts/: Test trigger scripts

Environment

The target environment is Arch Linux with a recent LTS kernel.

Required packages:

sudo pacman -S --needed base-devel clang llvm libbpf bpftool pkgconf make

Verification commands:

bpftool version
clang --version
llvm-strip --version
llc --version | grep bpf

Build

make

Build outputs:

  • build/edr.bpf.o
  • include/edr.skel.h
  • build/mini-edr

Clean up:

make clean

Run

Loading the eBPF program requires root privileges.

sudo -n ./build/mini-edr

If non-interactive sudo is not configured, run it like this.

sudo ./build/mini-edr

At startup, the loader reads config/rules.json and appends JSONL logs to the configured path.

Default log path:

./log/events.jsonl

Configuration

The default configuration file is config/rules.json.

{
  "output": {
    "type": "file",
    "path": "./log/events.jsonl"
  },
  "rules": {
    "exec_from_tmp": {
      "enabled": true,
      "severity": "medium",
      "paths": ["/tmp/", "/var/tmp/", "/dev/shm/"]
    },
    "shadow_open_attempt": {
      "enabled": true,
      "severity": "medium",
      "paths": ["/etc/shadow"]
    },
    "shadow_open_success": {
      "enabled": true,
      "severity": "high",
      "paths": ["/etc/shadow"]
    },
    "suspicious_connect": {
      "enabled": true,
      "severity": "high",
      "process_names": ["sh", "bash", "dash", "zsh", "python", "python3", "perl", "ruby", "nc", "ncat", "socat"],
      "cmdline_tokens": ["/dev/tcp/", "bash -i", "sh -i", "python -c", "python3 -c", "perl -e", "socat ", "ncat "],
      "exclude_private_ip": true,
      "allow_dst_ips": ["1.1.1.1"],
      "allow_cmdline_prefixes": ["python3 /opt/backup/"],
      "suppression_mode": "mark"
    }
  }
}

The current configuration loader is a minimal JSON parser for the MVP schema. It is intended to read this structure and is not a general-purpose JSON parser.

suppression_mode supports mark and drop. mark stores the event with suppressed=true and suppress_reason; drop omits suppressed events from the JSONL output.

Tests

Run the loader in terminal 1.

sudo -n ./build/mini-edr

Run the test triggers in terminal 2.

./scripts/run_tests.sh

Individual tests:

./scripts/test_exec_tmp.sh
./scripts/test_shadow_read.sh
./scripts/test_suspicious_connect.sh

Sample-log regression check:

./tests/test_connect_samples.sh

Check the results:

rg '"rule":"EXEC_FROM_TMP"|"rule":"SHADOW_OPEN_ATTEMPT"|"rule":"SHADOW_OPEN_SUCCESS"|"rule":"SUSPICIOUS_CONNECT"' log/events.jsonl

Log Examples

{"timestamp":6405702218137,"event_type":"execve","pid":82943,"ppid":82936,"uid":1000,"gid":1000,"comm":"zsh","parent_comm":"zsh","cmdline":"/tmp/mini-edr-test-echo","path":"/tmp/mini-edr-test-echo","flags":0,"dst_ip":null,"dst_port":null,"address_family":0,"mnt_ns":4026531832,"pid_ns":4026531836,"rule":"EXEC_FROM_TMP","severity":"medium"}
{"timestamp":6405707911504,"event_type":"openat","pid":82945,"ppid":82936,"uid":1000,"gid":1000,"comm":"cat","parent_comm":"zsh","cmdline":"","path":"/etc/shadow","flags":0,"retval":-13,"error_code":13,"success":false,"dst_ip":null,"dst_port":null,"address_family":0,"mnt_ns":4026531832,"pid_ns":4026531836,"rule":"SHADOW_OPEN_ATTEMPT","severity":"medium"}
{"timestamp":6418125706741,"event_type":"connect","pid":83018,"ppid":83012,"uid":1000,"gid":1000,"comm":"bash","parent_comm":"zsh","cmdline":"bash -c exec 3<>/dev/tcp/203.0.113.10/4444","path":"","flags":0,"dst_ip":"203.0.113.10","dst_port":4444,"address_family":2,"mnt_ns":4026531832,"pid_ns":4026531836,"dst_ip_class":"public","suppressed":false,"suppress_reason":null,"rule":"SUSPICIOUS_CONNECT","severity":"high"}

An allowlisted or private-IP connect event is stored like this.

{"event_type":"connect","comm":"bash","cmdline":"bash -c exec 3<>/dev/tcp/1.1.1.1/80","dst_ip":"1.1.1.1","dst_ip_class":"public","rule":null,"severity":null,"suppressed":true,"suppress_reason":"allow_dst_ip"}

Known Limitations

  • openat correlates entry and exit, but other file-open syscalls such as open, openat2, and creat are not covered yet.
  • openat paths are recorded as the raw filename syscall argument. Relative paths, dirfd, and mount namespace based absolute path reconstruction are not handled.
  • connect parses destination IP and port only for IPv4. IPv6 is left as future work.
  • SUSPICIOUS_CONNECT is heuristic. Command line enrichment and allowlists do not make it an attack-confirmation rule.
  • connect command line enrichment is best-effort through /proc/<pid>/cmdline; it can fail because of process exit, permissions, or namespace differences.
  • timestamp is currently the kernel monotonic nanosecond value. Wall-clock timestamp conversion is future work.
  • The configuration loader is a minimal implementation for the MVP schema.

Next Steps

  • Parse IPv6 connect events
  • Expand process ancestry context
  • Add network context such as DNS, SNI, or HTTP Host
  • Separate output filtering from stdout debug options
  • Capture a README-driven demo

About

This project is a small eBPF-based EDR prototype for tracing suspicious process activity on Linux and storing the results as JSON Lines logs.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages