Skip to content

Latest commit

 

History

29 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MiniEDRSensor

A mini, cross-platform EDR (Endpoint Detection and Response) telemetry sensor — a lightweight background agent that observes a single host's own signals (its running-process table), applies light local detection, and reports events over REST to a receiver.

Defensive telemetry only. MIT licensed.

Overview

The sensor snapshots the local process table on an interval, detects processes that appeared since the last snapshot and matches them against a small IOC watchlist (process name / file hash), and posts each event as JSON to a receiver that renders them in real time.

Architecture

flowchart LR
    subgraph sensor ["Sensor process · periodic loop"]
        collector["Collector<br/>(per-platform strategy)"] --> detect["Detection<br/>new-process diff · IOC watchlist"]
        detect --> serialize["JSON serializer"]
        serialize --> transport["Transport<br/>(injectable)"]
    end
    factory["Factory"] -. selects .-> collector
    transport -- "HTTP POST" --> receiver["Receiver<br/>(real-time console)"]
Loading
  • ITelemetryCollector — one concrete collector per platform, selected by a factory.
  • ITransport — the HTTP seam; injectable, so tests substitute a fake and never touch the network.

How it works

The sensor watches for newly appeared processes, because the moment a new process starts is the moment new code begins running on the machine — and that is where most attacks become visible. Malware has to execute; a dropper has to launch its payload; lateral movement spawns a new process. "A process that wasn't here a moment ago" is the single most worthwhile thing to look at.

Each cycle the sensor:

  1. Snapshots the running-process table (Windows: ToolHelp32).
  2. Diffs against the previous snapshot to find the processes that are new this cycle.
  3. Matches only the new ones against a small IOC watchlist (process name / file hash) and raises an alert on a hit.
  4. Serializes the event to JSON and POSTs it to the receiver.

Reporting only what is new — rather than the whole table every cycle — keeps the signal on the interesting moment (something started) and keeps the volume low.

Design

  • RAII ownership of every native resource (OS handles, HTTP client) via a single move-only wrapper.
  • Injectable transport — the sensor depends on an abstraction, not a concrete HTTP stack.
  • Caller-held timeouts — a request deadline is enforced by the caller, not delegated to the HTTP library as the only guarantee.
  • Honest errors — a failed request reports "unknown" rather than fabricating a success result, and a malformed payload is reported with a reason rather than parsed into a plausible-but-wrong event.
  • Bounded parsing — untrusted input is size- and nesting-depth-limited before parsing, so an oversized or deeply nested payload cannot exhaust memory or overflow the recursive parser.

Build & test

Requirements: a C++17 compiler and CMake ≥ 3.16. nlohmann/json, libcurl, and GoogleTest are fetched and built automatically on the first configure — no system packages or vcpkg needed. libcurl is built minimally (HTTP(S) only; on Windows the native Schannel TLS backend, so there is no OpenSSL to install), which makes the first configure/build slower while it compiles.

git clone https://github.com/terrytwchen/MiniEDRSensor.git
cd MiniEDRSensor

cmake -S . -B build                 # Windows: add -G "Visual Studio 17 2022" -A x64
cmake --build build --config RelWithDebInfo

ctest --test-dir build -C RelWithDebInfo --output-on-failure

Prefer not to build? A prebuilt Windows x64 archive is attached to each tagged release: download, unzip, and run it as described under Receiver and Run below. The prebuilt sensor needs the Microsoft Visual C++ runtime installed.

Receiver

Start the local receiver in a separate terminal; the sensor posts events to it.

python -u receiver/receiver.py      # listens on http://127.0.0.1:8787

It prints each event as it arrives and highlights alerts. See receiver/README.md.

Run

With the receiver running, start the sensor in a separate terminal and point it at the receiver:

build/RelWithDebInfo/minisensor.exe http://127.0.0.1:8787/   # from a source build
# or, using the prebuilt binary from a release:
minisensor.exe http://127.0.0.1:8787/

The endpoint URL is optional — it defaults to http://127.0.0.1:8787/ and can also be set with the MINIEDR_ENDPOINT environment variable. The sensor prints each detection as it posts it; press Ctrl+C to stop.

Limitations

This is a compact, defensive sensor demo. It deliberately approximates, in user space, what a production EDR does with a kernel driver, and the trade-offs are explicit:

  • Polling, not kernel events. The sensor snapshots every ~2 seconds and diffs. A real EDR gets an exact process-creation event from the kernel (on Windows, PsSetCreateProcessNotifyRoutine) the instant a process is created — no polling, nothing missed. The cost of not shipping a driver is that a short-lived process born and gone within one poll interval is missed — and because the interval is a predictable observation gap, a payload can be deliberately kept short-lived to run entirely between two snapshots and never appear in one.
  • Processes only. A full EDR also observes file, registry, network, and module-load events. This demo covers the process-execution slice — the sensor half of "Endpoint Detection and Response", which is why it is named MiniEDRSensor rather than MiniEDR.
  • Detection, not response. This is the Detection half of "Endpoint Detection and Response": it observes and reports, but never acts — it cannot block, kill, or quarantine a process. Prevention — stopping a process at creation — is what a kernel driver enables (the Response half) and is deliberately out of scope here. This is a sensor.
  • PID reuse. The OS can recycle a PID between snapshots, so a diff keyed on PID alone can misattribute; correlating on start time / image would be needed to be robust.
  • Pre-existing processes. Only processes that appear after the first snapshot are treated as new; anything already running at start-up is baseline.
  • No process lineage. Parent/child relationships aren't tracked, so a benign process spawning a malicious child isn't shown as such — real EDRs record the parent PID and build a process tree.
  • Static IOC matching. The watchlist matches known names / hashes, so a renamed, packed, or never-before-seen binary is missed; name matching in particular is trivially evaded by renaming. This is not behavioural detection.
  • User-mode visibility. Process enumeration is user-mode (ToolHelp32), so a rootkit that unlinks itself from the kernel process list (DKOM) can hide from enumeration; a kernel-mode sensor would see more.
  • Windows is the primary implemented and tested platform. Linux has a real implementation path (process enumeration via /proc) that is not exercised in CI; macOS is a compile-time stub. The platform is chosen in one place by a Factory, so the periodic loop is platform-agnostic.
  • Unencrypted, unauthenticated transport. Events are POSTed over plain HTTP, with no TLS and no authentication of either end. Against the default loopback receiver this is not a network exposure — the traffic never leaves the host — but pointing the sensor at a remote collector over plain HTTP would put the telemetry (process names, host id) on the wire in cleartext and let it be observed or spoofed. A real deployment would use HTTPS (the HTTP client is built with a TLS backend, so an https:// endpoint works unchanged) together with authentication such as a bearer token or mTLS.
  • Defensive telemetry only — it observes and reports. It does not hook, inject, or load drivers, and is not a hardened production security product.

License

MIT — see LICENSE.

About

Mini cross-platform EDR endpoint telemetry sensor — defensive telemetry only (modern C++17)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages