Skip to content
BlackSnufkin edited this page May 3, 2026 · 1 revision

EDR Integration

LitterBox can dispatch a payload to a separate EDR-instrumented Windows VM and pull the correlated detection alerts back into the results page. Two profile kinds are supported, both backed by the same Whiskers Rust agent:

Kind Where alerts come from When to pick it
kind: elastic LitterBox queries an operator-deployed Elastic stack for alerts indexed by Elastic Defend on the VM You already run Elastic Defend or want a "real" commercial EDR in the loop
kind: fibratus LitterBox polls Whiskers's /api/alerts/fibratus/since which wevtutil-queries the local Windows Application event log for Provider=Fibratus records You want an open-source ETW detection engine β€” no remote backend needed, runs entirely on the VM

Both kinds share the same orchestrator structure, the same Phase-1/Phase-2 split, and the same UI. The alert-source plumbing differs, the rest is identical.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ LitterBox (Flask)       β”‚         β”‚ EDR VM (Windows)                   β”‚
β”‚                         β”‚         β”‚                                    β”‚
β”‚  upload page            β”‚         β”‚  Whiskers.exe                      β”‚
β”‚  /analyze/edr/<P>/<T>   β”‚ ──────► β”‚   /api/info                        β”‚
β”‚                         β”‚         β”‚   /api/lock/{acquire,release,...}  β”‚
β”‚  ElasticEdrAnalyzer  OR β”‚ ◄────── β”‚   /api/execute/exec  (XOR-encoded) β”‚
β”‚  FibratusEdrAnalyzer    β”‚         β”‚   /api/execute/kill                β”‚
β”‚                         β”‚         β”‚   /api/logs/execution              β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚         β”‚   /api/alerts/fibratus/since       β”‚
β”‚   β”‚ Phase 1 (sync)  β”‚   β”‚         β”‚                                    β”‚
β”‚   β”‚ exec on agent   β”‚   β”‚         β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚         β”‚  β”‚ EDR / detection engine       β”‚  β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚         β”‚  β”‚  β€’ Elastic Defend agent      β”‚  β”‚
β”‚   β”‚ Phase 2 (async) β”‚ ◄─┼─────────┼──│  β€’ OR Fibratus + ETW         β”‚  β”‚
β”‚   β”‚ alert poll loop β”‚   β”‚         β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚         β”‚            ↓                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚     Application event log          β”‚
            ↑                       β”‚       (Provider=Fibratus)          β”‚
            β”‚                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
            β”‚
            β”‚   for kind=elastic only:
            β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ Elastic stack        β”‚
   β”‚ (.alerts-security.   β”‚
   β”‚  alerts-default-*)   β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Two-phase orchestration

Both ElasticEdrAnalyzer and FibratusEdrAnalyzer expose run_exec() (sync) and run_correlation() (unlocked + threaded via registry.dispatch_split):

Phase 1 β€” exec, holds the agent lock.

  1. GET /api/info β†’ discover hostname (no manual config)
  2. lock_acquire (single-occupancy gate, auto-expires after 30 min if stranded)
  3. exec(payload, args) over multipart, XOR-encoded on the wire
  4. wait_for_exit or exec_timeout_seconds
  5. kill (idempotent)
  6. get_execution_logs β€” stdout/stderr/exit_code
  7. lock_release

Returns (phase_1_dict, continuation) to the caller. The phase-1 dict has status: "polling_alerts" plus a populated execution.{pid, stdout, stderr, exit_code, exec_status, killed_by_edr}. AV-block runs use the same polling_alerts status β€” the orthogonal flag summary.blocked_by_av: true carries the AV-block state.

Phase 2 β€” alert correlation, no lock.

  • Poll every 2s, early-return on first hit, then wait SETTLE_SECONDS (8s) to capture burst-related alerts.
  • Bounded by wait_seconds_for_alerts (90s for elastic, 30s for fibratus by default).
  • AV-block path uses av_block_wait_seconds (60s / 30s) instead.
  • Saves the final findings dict via the route's on_phase_2_done callback.

The lock is intentionally NOT held during Phase 2 so back-to-back dispatches don't queue. Time-bounded queries keep concurrent runs from polluting each other's results.


Status flow

polling_alerts     ← Phase 1 succeeded; Phase 2 in flight.
                     AV-block runs use this too β€” summary.blocked_by_av
                     carries the flag, status doesn't fork.
completed          ← Phase 2 done, alerts populated
blocked_by_av      ← Phase 2 done from AV-block path
partial            ← Phase 1 ok but backend query failed
agent_unreachable  ← couldn't reach Whiskers (transport-level)
busy               ← lock held by another run (409 from agent)
error              ← anything else

The frontend polls GET /api/results/edr/<profile>/<target> adaptively β€” 2s base, Γ—1.5 backoff up to 15s on consecutive ticks where total_alerts didn't change, snap back to 2s on movement.

agent_unreachable, busy, and pre-execution error are not persisted to disk β€” they're transient transport failures, not real findings. The file-info hero only shows EDR-profile buttons that have actual saved data on disk per-sample.


Per-payload alert correlation

Elastic. ElasticClient.fetch_alerts() filters by host.name (case-insensitive) + @timestamp window, plus a bool/should clause that requires the alert to mention the payload filename in any of:

  • file.name (term match)
  • process.name (term match)
  • file.path (wildcard *<filename>)
  • process.executable (wildcard)
  • process.command_line (wildcard β€” picks up rundll32-launched DLLs where the DLL name appears in the parent command line)
  • process.args (wildcard)

The MD5-prefixed dropper filename is unique enough across uploads that concurrent dispatches don't taint each other's results.

Fibratus. FibratusEdrAnalyzer._poll_alerts calls AgentClient.get_fibratus_alerts(since, until) which hits Whiskers's /api/alerts/fibratus/since. Whiskers shells wevtutil qe Application "/q:..." /f:xml with the same XPath shape DetonatorAgent uses, extracts <TimeCreated> + <EventID> + <Data> per record, returns the raw JSON <Data> strings. LitterBox parses + filters by filename match against events[].proc.{name,exe,cmdline,parent_name,parent_cmdline}.


Detection-Score contribution

EDR alerts feed the run's own results page but do NOT contribute to the file-info detection score. EDR is its own analysis type with its own page; the file's score stays scoped to static + dynamic + PE info.

Within the EDR page, scoring is described in Detection Score Explained.


Setting up your first profile

Pick the kind that matches your environment:

Both setups depend on Whiskers being installed on the VM first β€” see Whiskers Agent.


Profile YAML schema

Profiles live under Config/edr_profiles/*.yml. Real files are gitignored; example files (*.yml.example) are tracked. Drop one file per profile; restart LitterBox to pick up changes.

Common fields (both kinds):

name: "elastic"               # used in URLs (/analyze/edr/<name>/<target>)
display_name: "Elastic Defend" # shown on the upload page button
kind: "elastic"               # or "fibratus" β€” defaults to "elastic" for back-compat
agent_url: "http://192.168.1.100:8080"   # Whiskers
wait_seconds_for_alerts: 90   # Phase 2 max budget for the success path
av_block_wait_seconds: 60     # Phase 2 max budget for the AV-block path
exec_timeout_seconds: 60      # hard cap on payload runtime
drop_path: "C:\\Users\\Public\\sample.exe"   # optional, defaults to <Whiskers-exe-dir>/samples/

kind: elastic-only fields:

elastic_url: "https://192.168.1.50:9200"
elastic_apikey: "<base64-encoded-API-key>"
elastic_verify_tls: false     # self-signed cert support

kind: fibratus adds nothing β€” the Whiskers agent does the alert-source work via wevtutil.

The agent self-reports its hostname via /api/info, so there is no hostname field β€” moving the agent to a different VM is transparent.


See also

πŸ“Œ LitterBox Β· self-hosted payload analysis sandbox

Release


πŸš€ Getting Started

πŸ“Š Pipelines & Pages

πŸ”¬ Scanners Β· 4 modules

πŸ›°οΈ EDR Integration
πŸ”Œ API & Clients
βš™οΈ Configuration & Dev

Releases Β· CHANGELOG Β· Issues Β· README

Clone this wiki locally