v5 release prep: shrink top-level READMEs, dead-code cleanup, release notes
- README, GrumpyCats/README, Whiskers/README: trim feature dumps; point at the wiki for deep docs - Whiskers/BUILD.md: folded into Whiskers/README "Building from source" - HolyGrail analyzer: drop 178 lines of dead code - Patriot, blender, holygrail, manager: pyflakes-clean unused imports - Add release-notes.md
This commit is contained in:
+49
-396
@@ -7,415 +7,68 @@
|
||||
[]()
|
||||
[]()
|
||||
|
||||
## What This Is
|
||||
The client side of LitterBox. Three pieces sharing one codebase:
|
||||
|
||||
GrumpyCats is the client side of LitterBox. Three pieces:
|
||||
| File | What it is | Wiki |
|
||||
|---|---|---|
|
||||
| **`grumpycat.py`** | Command-line client | [GrumpyCats CLI](../../../wiki/GrumpyCats-CLI) |
|
||||
| **`litterbox_client/`** | Python library (composed mixins) | [GrumpyCats Library](../../../wiki/GrumpyCats-Library) |
|
||||
| **`LitterBoxMCP.py`** + **`install_mcp.py`** | MCP server + installer (29 tools, 4 OPSEC prompts) | [LitterBoxMCP](../../../wiki/LitterBoxMCP) |
|
||||
|
||||
1. **`grumpycat.py`** — thin CLI orchestrator (~14 lines). Imports the parser
|
||||
+ handlers from the `cli/` package and the API surface from the
|
||||
`litterbox_client/` package.
|
||||
2. **`LitterBoxMCP.py`** — MCP server that exposes the same surface to AI
|
||||
agents (Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, …) plus
|
||||
four OPSEC-review prompts.
|
||||
3. **`install_mcp.py`** — one-shot installer that wires `LitterBoxMCP.py`
|
||||
into the right config file for your MCP client of choice.
|
||||
## Install
|
||||
|
||||
### Layout
|
||||
```bash
|
||||
pip install requests # CLI + library
|
||||
pip install mcp # additionally for the MCP server
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
**CLI:**
|
||||
```bash
|
||||
python grumpycat.py upload payload.exe --analysis static dynamic
|
||||
python grumpycat.py edr-run <md5> --profile elastic --wait
|
||||
python grumpycat.py results <md5> --comprehensive
|
||||
```
|
||||
|
||||
**Library:**
|
||||
```python
|
||||
from litterbox_client import LitterBoxClient
|
||||
|
||||
with LitterBoxClient("http://127.0.0.1:1337") as c:
|
||||
info = c.upload_file("payload.exe")
|
||||
c.analyze_file(info["md5"], "static", wait_for_completion=True)
|
||||
print(c.get_risk_assessment(info["md5"]))
|
||||
```
|
||||
|
||||
**MCP (Claude Desktop / Claude Code / Cursor / Windsurf / VS Code):**
|
||||
```bash
|
||||
py install_mcp.py --list # see supported clients
|
||||
py install_mcp.py --install claude-code-project
|
||||
```
|
||||
|
||||
Reload the MCP client after install so the new config is picked up.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
GrumpyCats/
|
||||
├── grumpycat.py # entry point — calls cli.run()
|
||||
├── cli/ # argparse parser + per-command handlers
|
||||
│ ├── parser.py # build_parser() — split into _add_<group> helpers
|
||||
│ ├── handlers.py # _cmd_* + COMMAND_HANDLERS dispatch
|
||||
│ └── runner.py # exception-aware dispatch + exit codes
|
||||
├── litterbox_client/ # API client, split into per-domain mixins
|
||||
│ ├── exceptions.py # LitterBoxError, LitterBoxAPIError
|
||||
│ ├── base.py # _BaseClient (session, _make_request, validators)
|
||||
│ ├── files.py # FilesMixin
|
||||
│ ├── analysis.py # AnalysisMixin
|
||||
│ ├── doppelganger.py # DoppelgangerMixin
|
||||
│ ├── results.py # ResultsMixin
|
||||
│ ├── edr.py # EdrMixin (Whiskers + Elastic + Fibratus)
|
||||
│ ├── reports.py # ReportsMixin
|
||||
│ ├── system.py # SystemMixin (cleanup, health, comprehensive)
|
||||
│ └── client.py # LitterBoxClient = composition of all mixins
|
||||
├── grumpycat.py # CLI entry point
|
||||
├── cli/ # argparse parser + command handlers
|
||||
├── litterbox_client/ # API client, per-domain mixins composed onto _BaseClient
|
||||
├── LitterBoxMCP.py # FastMCP wrapper
|
||||
└── install_mcp.py # MCP-client config installer
|
||||
```
|
||||
|
||||
Adding a new command = one method in the right mixin under
|
||||
`litterbox_client/`, one subparser in `cli/parser.py`, one `_cmd_*` handler
|
||||
in `cli/handlers.py`, one row in `COMMAND_HANDLERS`. Done. The MCP tool is
|
||||
one decorator in `LitterBoxMCP.py` since it shares the client.
|
||||
Adding a new command = one method in the right mixin under `litterbox_client/`, one subparser in `cli/parser.py`, one handler in `cli/handlers.py`. The MCP tool is one decorator in `LitterBoxMCP.py` since it shares the client.
|
||||
|
||||
---
|
||||
## Documentation
|
||||
|
||||
## Table of Contents
|
||||
- [Command Line Tool](#command-line-tool)
|
||||
- [Using It](#using-it)
|
||||
- [Using It as a Library](#using-it-as-a-library)
|
||||
- [AI Integration (MCP)](#ai-integration-mcp)
|
||||
- [Installer Reference](#installer-reference)
|
||||
- [MCP Tools Reference](#mcp-tools-reference)
|
||||
- [OPSEC Review Prompts](#opsec-review-prompts)
|
||||
Full reference for every CLI command, library method, MCP tool, and OPSEC prompt lives in the wiki:
|
||||
|
||||
---
|
||||
|
||||
## Command Line Tool
|
||||
|
||||
The CLI talks to your LitterBox server, handles connection pooling, retries, and structured errors for you.
|
||||
|
||||
### Requirements
|
||||
|
||||
```bash
|
||||
pip install requests
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
python grumpycat.py [global-options] <command> [command-options]
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | What It Does |
|
||||
|--------------------------|---------------------------------------------|
|
||||
| `upload` | Upload a payload for analysis |
|
||||
| `upload-driver` | Upload a kernel driver for BYOVD analysis |
|
||||
| `analyze-pid` | Analyze a running process |
|
||||
| `results` | Fetch analysis results |
|
||||
| `report` | Generate / view / download an HTML report |
|
||||
| `files` | List every analyzed payload + summary |
|
||||
| `doppelganger-scan` | Snapshot the host for Blender comparison |
|
||||
| `doppelganger-analyze` | Compare a payload against Blender or fuzzy |
|
||||
| `doppelganger-db` | Build the FuzzyHash baseline DB |
|
||||
| `edr-run` | Dispatch a payload to an EDR profile (Elastic / Fibratus) |
|
||||
| `edr-results` | Read saved EDR findings (per-profile or cross-profile index) |
|
||||
| `edr-profiles` | List registered EDR profiles |
|
||||
| `edr-status` | Live probe of every EDR profile (agent + backend reachability) |
|
||||
| `fibratus-alerts` | Test/debug — pull Fibratus alerts via Whiskers without dispatching a payload |
|
||||
| `scanners` | Inventory of configured local analyzers + whether their binaries exist |
|
||||
| `status` | Server health + fleet summary |
|
||||
| `health` | Just the health check |
|
||||
| `cleanup` | Wipe sandbox artifacts |
|
||||
| `delete` | Delete one payload + its results |
|
||||
|
||||
### Global Options
|
||||
|
||||
| Option | What It Does |
|
||||
|-----------------------|----------------------------------------------------|
|
||||
| `--debug` | Verbose logging |
|
||||
| `--url URL` | LitterBox server URL (default `http://127.0.0.1:1337`) |
|
||||
| `--timeout TIMEOUT` | Request timeout, seconds |
|
||||
| `--no-verify-ssl` | Skip SSL verification |
|
||||
| `--proxy PROXY` | Route requests through a proxy |
|
||||
|
||||
## Using It
|
||||
|
||||
### Basics
|
||||
|
||||
```bash
|
||||
# Upload and run static + dynamic
|
||||
grumpycat.py upload payload.exe --analysis static dynamic
|
||||
|
||||
# Upload a kernel driver and immediately run BYOVD
|
||||
grumpycat.py upload-driver rootkit.sys --holygrail
|
||||
|
||||
# Analyze a running process by PID
|
||||
grumpycat.py analyze-pid 1234 --wait
|
||||
|
||||
# Pull every result for a target in one call
|
||||
grumpycat.py results abc123def --comprehensive
|
||||
|
||||
# Or scope to one analysis type
|
||||
grumpycat.py results abc123def --type static
|
||||
grumpycat.py results abc123def --type holygrail
|
||||
```
|
||||
|
||||
### EDR (Whiskers + Elastic Defend or Fibratus)
|
||||
|
||||
```bash
|
||||
# List registered EDR profiles
|
||||
grumpycat.py edr-profiles
|
||||
|
||||
# Live probe of every profile — agent + backend reachability (cached server-side)
|
||||
grumpycat.py edr-status
|
||||
|
||||
# Dispatch a payload to a profile and wait for Phase-2 settle
|
||||
grumpycat.py edr-run abc123def --profile elastic --wait
|
||||
grumpycat.py edr-run abc123def --profile fibratus --wait
|
||||
|
||||
# DLL payload — first arg becomes the rundll32 entry point
|
||||
grumpycat.py edr-run def456abc --profile elastic --args MyExportedFunc --wait
|
||||
|
||||
# Read saved EDR findings
|
||||
grumpycat.py edr-results abc123def --profile fibratus # one profile
|
||||
grumpycat.py edr-results abc123def # cross-profile index
|
||||
|
||||
# Verify the Fibratus alert wire without running a payload
|
||||
grumpycat.py fibratus-alerts --profile fibratus --from 2026-04-30T00:00:00Z
|
||||
|
||||
# Inventory of local analyzers (drives the dashboard's Scanners panel)
|
||||
grumpycat.py scanners
|
||||
```
|
||||
|
||||
### Doppelganger / similarity
|
||||
|
||||
```bash
|
||||
# Snapshot the live host for baseline comparison
|
||||
grumpycat.py doppelganger-scan --type blender
|
||||
|
||||
# Score a payload against the FuzzyHash baseline
|
||||
grumpycat.py doppelganger-analyze abc123def --type fuzzy --threshold 85
|
||||
|
||||
# Build the FuzzyHash baseline DB
|
||||
grumpycat.py doppelganger-db --folder /path/to/refs --extensions .exe .dll
|
||||
```
|
||||
|
||||
### Reports
|
||||
|
||||
```bash
|
||||
# Print to stdout
|
||||
grumpycat.py report abc123def
|
||||
|
||||
# Download to current dir
|
||||
grumpycat.py report abc123def --download
|
||||
|
||||
# Download to a specific dir
|
||||
grumpycat.py report abc123def --download --output ./reports/
|
||||
|
||||
# Open in your browser
|
||||
grumpycat.py report abc123def --browser
|
||||
```
|
||||
|
||||
### Maintenance
|
||||
|
||||
```bash
|
||||
# Health + fleet summary
|
||||
grumpycat.py status --full
|
||||
|
||||
# Wipe everything
|
||||
grumpycat.py cleanup --all
|
||||
|
||||
# Delete one payload
|
||||
grumpycat.py delete abc123def
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Using It as a Library
|
||||
|
||||
```python
|
||||
from litterbox_client import LitterBoxClient
|
||||
|
||||
with LitterBoxClient(base_url="http://127.0.0.1:1337") as client:
|
||||
# Upload and run analysis
|
||||
result = client.upload_file("payload.exe")
|
||||
file_hash = result["file_info"]["md5"]
|
||||
static_result = client.analyze_file(file_hash, "static")
|
||||
dynamic_result = client.analyze_file(file_hash, "dynamic")
|
||||
|
||||
# Pull every result for a target with one fan-out call
|
||||
# (file_info + static + dynamic + holygrail + edr_index in parallel)
|
||||
all_results = client.get_comprehensive_results(file_hash)
|
||||
|
||||
# Driver workflow
|
||||
driver_result = client.upload_and_analyze_driver("driver.sys", run_holygrail=True)
|
||||
|
||||
# Detection assessment endpoint
|
||||
risk = client.get_risk_assessment(file_hash)
|
||||
# → {"risk_score": 32.5, "risk_level": "Medium", "risk_factors": [...]}
|
||||
|
||||
# Doppelganger
|
||||
blender_snapshot = client.run_blender_scan()
|
||||
comparison = client.compare_with_blender(file_hash)
|
||||
fuzzy_score = client.analyze_with_fuzzy(file_hash, threshold=85)
|
||||
|
||||
# EDR — works for kind=elastic and kind=fibratus alike
|
||||
profiles = client.list_edr_profiles()
|
||||
health = client.get_edr_agents_status() # cached server-side
|
||||
phase1 = client.analyze_edr(file_hash, "elastic")
|
||||
final = client.wait_for_edr_completion(file_hash, "elastic")
|
||||
saved = client.get_edr_results(file_hash, "elastic")
|
||||
index = client.get_edr_index(file_hash)
|
||||
|
||||
# Fibratus testing helper — wire-check without running a payload
|
||||
alerts = client.fibratus_alerts_since(
|
||||
"fibratus", since_iso="2026-04-30T00:00:00Z",
|
||||
)
|
||||
|
||||
# Server + scanner health
|
||||
status = client.get_system_status()
|
||||
scanners = client.get_scanners_status()
|
||||
```
|
||||
|
||||
The client uses a `requests.Session` with retry-on-5xx, fans out the
|
||||
five `get_comprehensive_results` calls (file_info / static / dynamic /
|
||||
holygrail / edr_index) in parallel, and exposes a context-manager
|
||||
interface so the session closes cleanly. The class itself is composed
|
||||
from per-domain mixins under `litterbox_client/`; public callers see
|
||||
one class and don't need to care about the split.
|
||||
|
||||
---
|
||||
|
||||
## AI Integration (MCP)
|
||||
|
||||
`LitterBoxMCP.py` is a stdio MCP server that exposes 29 tools and 4 OPSEC-review prompts to any MCP-compatible client. Tools run async and offload the sync `LitterBoxClient` calls to a worker thread, so multiple tool calls don't serialize.
|
||||
|
||||
### Requirements
|
||||
|
||||
```bash
|
||||
pip install mcp requests
|
||||
```
|
||||
|
||||
The included installer auto-detects the project's `venv/` Python first, then `$VIRTUAL_ENV`, then the `python` running the script — and warns if `mcp` or `requests` are missing so the server actually has a chance of starting after install.
|
||||
|
||||
### Quick Install
|
||||
|
||||
```bash
|
||||
# See what clients the installer recognises and which already have litterbox configured
|
||||
py install_mcp.py --list
|
||||
|
||||
# Install for a single client (project-scoped Claude Code config in <repo>/.mcp.json)
|
||||
py install_mcp.py --install claude-code-project
|
||||
|
||||
# Install everywhere
|
||||
py install_mcp.py --install all
|
||||
```
|
||||
|
||||
After install, **reload your MCP client** (close + reopen Claude Desktop, "Developer: Reload Window" in VS Code, etc.) so the new config is picked up.
|
||||
|
||||
### Supported clients
|
||||
|
||||
| Key | Scope | Config file |
|
||||
|------------------------|----------|----------------------------------------------------------------|
|
||||
| `claude-code-project` | project | `<repo>/.mcp.json` |
|
||||
| `claude-code-global` | global | `~/.claude.json` |
|
||||
| `claude-desktop` | global | `%APPDATA%\Claude\claude_desktop_config.json` (Win) / equivalents on macOS / Linux |
|
||||
| `cursor` | global | `~/.cursor/mcp.json` |
|
||||
| `windsurf` | global | `~/.codeium/windsurf/mcp_config.json` |
|
||||
| `vscode-project` | project | `<repo>/.vscode/mcp.json` |
|
||||
|
||||
Aliases: `claude-code` → `claude-code-project`, `claude` → `claude-desktop`, `vscode` / `vs-code` → `vscode-project`.
|
||||
|
||||
The installer:
|
||||
- Reads any existing config and merges the LitterBox entry without clobbering other MCP servers you already have.
|
||||
- Knows that VS Code uses `{"servers": {...}}` while everyone else uses `{"mcpServers": {...}}` and writes the right structure per client.
|
||||
- Writes atomically (`.tmp` + rename) so a partial write can't corrupt your config.
|
||||
- Same flags work for `--uninstall`.
|
||||
|
||||
### Running the server directly
|
||||
|
||||
The installer's job ends after writing the config. The MCP client launches the server as a subprocess. If you want to run it by hand for debugging:
|
||||
|
||||
```bash
|
||||
# stdio transport (default — what MCP clients use)
|
||||
py LitterBoxMCP.py
|
||||
|
||||
# Streamable HTTP transport (for remote clients)
|
||||
py LitterBoxMCP.py --transport streamable-http --host 127.0.0.1 --port 8765
|
||||
```
|
||||
|
||||
Logs go to stderr — required for stdio transport, since stdout is the JSON-RPC channel.
|
||||
|
||||
---
|
||||
|
||||
## Installer Reference
|
||||
|
||||
`install_mcp.py` modes are mutually exclusive — pick exactly one:
|
||||
|
||||
| Flag | What It Does |
|
||||
|-----------------------------------|------------------------------------------------------------------------------|
|
||||
| `--list` | Show every supported client + whether LitterBox is currently installed |
|
||||
| `--install CLIENT [CLIENT ...]` | Write the LitterBox entry into one or more clients (or `all`) |
|
||||
| `--uninstall CLIENT [CLIENT ...]` | Remove the LitterBox entry from one or more clients (or `all`) |
|
||||
| `--print` | Dump the config JSON to stdout — for copy-paste into clients we don't ship |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
py install_mcp.py --list
|
||||
py install_mcp.py --install claude-code-project
|
||||
py install_mcp.py --install claude-code cursor # alias + multi-target
|
||||
py install_mcp.py --install all
|
||||
py install_mcp.py --uninstall cursor
|
||||
py install_mcp.py --print # JSON only, no file writes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Tools Reference
|
||||
|
||||
All 29 tools are async. Tool exceptions become MCP error responses automatically — no manual envelopes.
|
||||
|
||||
### Intake — upload + kick off analysis
|
||||
|
||||
| Tool | What It Does |
|
||||
|-------------------------|-----------------------------------------------------------------------|
|
||||
| `upload_payload` | Upload an `.exe / .dll / .bin / .lnk / .docx / .xlsx` |
|
||||
| `upload_driver` | Upload a `.sys` and (by default) immediately run BYOVD |
|
||||
| `analyze_static` | YARA / CheckPlz / Stringnalyzer on an uploaded file |
|
||||
| `analyze_dynamic` | In-memory YARA, PE-Sieve, Moneta, Patriot, HSB, RedEdr (executes!) |
|
||||
| `analyze_holygrail` | BYOVD analysis on a kernel driver |
|
||||
| `validate_pid` | Confirm a PID is accessible before targeting it for dynamic analysis |
|
||||
|
||||
### Retrieval — read the results
|
||||
|
||||
| Tool | What It Does |
|
||||
|-------------------------------|-----------------------------------------------------------------------|
|
||||
| `get_file_info` | Metadata: type, size, hashes, entropy, PE structure, sensitive imports |
|
||||
| `get_static_results` | YARA + CheckPlz + Stringnalyzer findings |
|
||||
| `get_dynamic_results` | Memory scanners + behavioral telemetry + process output |
|
||||
| `get_holygrail_results` | LOLDrivers + block status + critical imports |
|
||||
| `get_risk_assessment` | Detection score + level + triggering indicators for the target |
|
||||
| `get_comprehensive_results` | All four results in one parallel call |
|
||||
| `get_report` | Full HTML report inline |
|
||||
| `download_report` | Save the HTML report to disk and return the path |
|
||||
|
||||
### Doppelganger — comparison
|
||||
|
||||
| Tool | What It Does |
|
||||
|----------------------------|------------------------------------------------------------------------|
|
||||
| `run_blender_scan` | Snapshot the live host |
|
||||
| `compare_with_blender` | Compare a payload's runtime indicators against the host snapshot |
|
||||
| `analyze_fuzzy_similarity` | ssdeep similarity score (0-100) against the FuzzyHash baseline |
|
||||
| `create_fuzzy_database` | (Re)build the FuzzyHash baseline DB from a folder of reference binaries|
|
||||
|
||||
### EDR — Whiskers + Elastic / Fibratus
|
||||
|
||||
| Tool | What It Does |
|
||||
|----------------------------|------------------------------------------------------------------------|
|
||||
| `list_edr_profiles` | List EDR profiles registered under `Config/edr_profiles/` |
|
||||
| `get_edr_agents_status` | Live agent + backend reachability snapshot (server-cached) |
|
||||
| `analyze_edr` | Dispatch a payload to a profile (executes! confirm with the user first) |
|
||||
| `get_edr_results` | Saved findings for a specific EDR profile run |
|
||||
| `get_edr_index` | Cross-profile index of every EDR run for a target |
|
||||
| `fibratus_alerts_since` | Test/debug — pull Fibratus alerts via Whiskers without dispatching a payload |
|
||||
|
||||
### Fleet management
|
||||
|
||||
| Tool | What It Does |
|
||||
|---------------------|-----------------------------------------------------------------------|
|
||||
| `list_payloads` | List every analyzed payload + driver + process with detection summary |
|
||||
| `sandbox_status` | Health + tool readiness + fleet summary |
|
||||
| `get_scanners_status` | Inventory of configured local analyzers + binary availability |
|
||||
| `cleanup_sandbox` | Wipe artifacts (destructive — confirm before calling) |
|
||||
| `delete_payload` | Delete one payload + its results (destructive) |
|
||||
|
||||
---
|
||||
|
||||
## OPSEC Review Prompts
|
||||
|
||||
Short, data-first prompt templates. Each one tells the LLM which tools to call and asks targeted questions instead of dumping a wall of categories. All take a `file_hash` parameter.
|
||||
|
||||
| Prompt | Use For |
|
||||
|-----------------------------|------------------------------------------------------------------------|
|
||||
| `detection_summary` | "What triggered detection?" — YARA matches, memory anomalies, behavioral telemetry, static red flags |
|
||||
| `evasion_recommendations` | "How do I make this stealthier?" — concrete changes per detection, ranked by impact |
|
||||
| `attribution_check` | "What gives me away?" — tool similarity, framework fingerprints, compilation artifacts |
|
||||
| `deployment_readiness` | "Should I ship this?" — GO / CONDITIONAL / NO-GO verdict against pass-fail criteria |
|
||||
- **[GrumpyCats CLI](../../../wiki/GrumpyCats-CLI)** — every command + flags + examples
|
||||
- **[GrumpyCats Library](../../../wiki/GrumpyCats-Library)** — mixin structure + every method + batch-fanout example
|
||||
- **[LitterBoxMCP](../../../wiki/LitterBoxMCP)** — install matrix, all 29 tools, all 4 OPSEC prompts
|
||||
|
||||
### Claude Integration
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ Lifted out of `grumpycat.py` so the orchestrator stays a few lines.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from litterbox_client import LitterBoxAPIError, LitterBoxError
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
from typing import Dict, List
|
||||
|
||||
# Server identity used as the dict key inside each client's "mcpServers" map.
|
||||
MCP_SERVER_NAME = "litterbox"
|
||||
|
||||
@@ -10,579 +10,95 @@
|
||||
[](https://deepwiki.com/BlackSnufkin/LitterBox)
|
||||
[](https://github.com/BlackSnufkin/LitterBox/stargazers)
|
||||
|
||||
A self-hosted payload-analysis sandbox for red teams. Upload a sample, run static / dynamic / EDR analysis against it, get a Detection Score and a triggering-indicators breakdown — decide whether the payload is field-ready before it leaves the lab.
|
||||
|
||||
## Table of Contents
|
||||
- [Overview](#overview)
|
||||
- [Documentation](#documentation)
|
||||
- [Analysis Capabilities](#analysis-capabilities)
|
||||
- [Analysis Engines](#analysis-engines)
|
||||
- [Integrated Tools](#integrated-tools)
|
||||
- [API Reference](#api-reference)
|
||||
- [Installation](#installation)
|
||||
- [Windows Installation](#windows-installation)
|
||||
- [Linux Installation (Docker)](#linux-installation)
|
||||
- [Configuration](#configuration)
|
||||
- [Client Libraries](#client-libraries)
|
||||
- [Contributing](#contributing)
|
||||
- [Security Advisory](#security-advisory)
|
||||
- [Acknowledgments](#acknowledgments)
|
||||
- [Interface](#interface)
|
||||
LitterBox can also dispatch payloads to a separate EDR-instrumented Windows VM (Elastic Defend or Fibratus) and pull the correlated detection alerts back into the results page.
|
||||
|
||||
## Overview
|
||||
|
||||
LitterBox provides a controlled sandbox environment designed for security professionals to develop and test payloads. This platform allows red teams to:
|
||||
|
||||
* Test evasion techniques against modern detection techniques
|
||||
* Validate detection signatures before field deployment
|
||||
* Analyze malware behavior in an isolated environment
|
||||
* Keep payloads in-house without exposing them to external security vendors
|
||||
* Ensure payload functionality without triggering production security controls
|
||||
|
||||
The platform includes LLM-assisted analysis capabilities through the LitterBoxMCP server, offering advanced analytical insights using natural language processing technology.
|
||||
|
||||
**Note**: While designed primarily for red teams, LitterBox can be equally valuable for blue teams by shifting perspective – using the same tools in their malware analysis workflows.
|
||||
> While designed primarily for red teams, LitterBox is equally useful for blue teams running the same tools in their malware-analysis workflows.
|
||||
|
||||
## Documentation
|
||||
|
||||
**[LitterBox Wiki](../../wiki)** - Advanced configuration and technical guides
|
||||
Operator and developer documentation lives in the **[LitterBox Wiki](../../wiki)**.
|
||||
|
||||
Key sections:
|
||||
- **Scanner Configuration** - HolyGrail, Blender, and FuzzyHash setup
|
||||
- **YARA Rules Management** - Custom rules and organization
|
||||
- **Configuration Reference** - Complete config.yml options
|
||||
- **Architecture & Development** - System design and custom scanners
|
||||
|
||||
## Analysis Capabilities
|
||||
|
||||
### Initial Processing
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| File Identification | Multiple hashing algorithms (MD5, SHA256) |
|
||||
| Entropy Analysis | Detection of encryption and obfuscation |
|
||||
| Type Classification | Advanced MIME and file type analysis |
|
||||
| Metadata Preservation | Original filename and timestamp tracking |
|
||||
| Runtime detection | Compiled binary identification
|
||||
|
||||
### Executable Analysis
|
||||
|
||||
For Windows PE files (.exe, .dll, .sys):
|
||||
|
||||
- Architecture identification (PE32/PE32+)
|
||||
- Compilation timestamp verification
|
||||
- Subsystem classification
|
||||
- Entry point analysis
|
||||
- Section enumeration and characterization
|
||||
- Import/export table mapping
|
||||
- Runtime detection for Go and Rust binaries with specialized import analysis
|
||||
|
||||
### Document Analysis
|
||||
|
||||
For Microsoft Office files:
|
||||
|
||||
- Macro detection and extraction
|
||||
- VBA code security analysis
|
||||
- Hidden content identification
|
||||
- Obfuscation technique detection
|
||||
|
||||
### LNK Analysis
|
||||
|
||||
For Windows shortcut Files (.lnk)
|
||||
|
||||
- Target execution paths and arguments
|
||||
- Machine tracking identifiers
|
||||
- Timestamps and file attributes
|
||||
- Network share information
|
||||
- Volume and drive details
|
||||
- Environment variables and metadata
|
||||
|
||||
## Analysis Engines
|
||||
|
||||
### Static Analysis
|
||||
|
||||
- Industry-standard signature detection
|
||||
- Binary entropy profiling
|
||||
- String extraction and classification
|
||||
- Pattern matching for known indicators
|
||||
|
||||
### Dynamic Analysis
|
||||
|
||||
Available in dual operation modes:
|
||||
- **File Analysis**: Focused on submitted samples
|
||||
- **Process Analysis**: Targeting running processes by PID
|
||||
|
||||
Capabilities include:
|
||||
|
||||
- Runtime behavioral monitoring
|
||||
- Memory region inspection and classification
|
||||
- Process hollowing detection
|
||||
- Code injection technique identification
|
||||
- Sleep pattern analysis
|
||||
- Windows telemetry collection via ETW
|
||||
|
||||
### HolyGrail BYOVD Analysis
|
||||
|
||||
Find undetected legitimate drivers for BYOVD attacks:
|
||||
|
||||
- **LOLDrivers Database**: Cross-reference against known vulnerable drivers
|
||||
- **Windows Block Policy**: Validation against Microsoft's recommended driver block rules for Windows 10/11
|
||||
- **Dangerous Import Analysis**: Detection of privileged functions commonly exploited in BYOVD attacks
|
||||
- **BYOVD Score Calculation**: Risk assessment based on exploitation potential and defensive controls
|
||||
|
||||
### EDR Integration
|
||||
|
||||
Dispatch a payload to a separate, EDR-instrumented Windows VM and pull the
|
||||
correlated detection alerts back into the LitterBox results page. Two profile
|
||||
kinds are supported, both backed by the same Whiskers agent:
|
||||
|
||||
- **`kind: elastic`** — LitterBox queries an operator-deployed Elastic stack
|
||||
for alerts raised against the run. Best for Elastic Defend or any other EDR
|
||||
whose alerts ship to Elasticsearch.
|
||||
- **`kind: fibratus`** — LitterBox polls Whiskers's `/api/alerts/fibratus/since`,
|
||||
which `wevtutil`-queries the EDR VM's Windows Application event log for
|
||||
`Provider=Fibratus` records. Best for the Fibratus open-source ETW detection
|
||||
engine — no remote backend required.
|
||||
|
||||
Built around three components:
|
||||
|
||||
- **Whiskers** — single-binary Rust agent (`Whiskers/`) that runs on the EDR VM.
|
||||
Exposes lock acquire/release, multipart payload upload (XOR-on-the-wire),
|
||||
process spawn + kill, stdout/stderr/exit-code capture, agent self-
|
||||
identification (auto-discovered hostname + Fibratus presence), and the
|
||||
`/api/alerts/fibratus/since` event-log query for Fibratus profiles.
|
||||
Single-occupancy by design; one run at a time per VM.
|
||||
- **ElasticEdrAnalyzer** — orchestrator for `kind: elastic` profiles.
|
||||
- **FibratusEdrAnalyzer** — orchestrator for `kind: fibratus` profiles. Same
|
||||
two-phase shape as Elastic but Phase 2 polls Whiskers's event-log endpoint
|
||||
instead of Elasticsearch, and normalizes Fibratus's native alert shape
|
||||
(`events[].proc.{name,exe,cmdline,parent_name,…}` + bare `tactic.id`/
|
||||
`technique.id` MITRE labels) into the saved-view renderer's dict.
|
||||
|
||||
Capabilities include:
|
||||
|
||||
- **Two-phase orchestration** — Phase 1 (lock + exec + log fetch on the agent)
|
||||
returns in ~1-7s; Phase 2 (alert correlation) polls in the background with
|
||||
early-return on first hit and an 8-second settle window for related-alert
|
||||
bursts. Lock is released after Phase 1 so back-to-back dispatches don't queue.
|
||||
- **Per-payload alert correlation** — query is scoped by `host.name` (case-
|
||||
insensitive) AND filename match across `file.name` / `process.name` /
|
||||
`file.path` / `process.executable` / `process.command_line` / `process.args`
|
||||
for Elastic; the Fibratus path filters on `events[].proc.*` substring match.
|
||||
- **AV-block detection** — Whiskers tags the run as `virus` when Defend
|
||||
intercepts on file write or spawn; orchestrator surfaces the prevention
|
||||
alert as a distinct `summary.blocked_by_av: true` flag.
|
||||
- **EDR-kill detection** — when the agent didn't issue the kill but the process
|
||||
exited non-zero, the run is labeled "killed by EDR behavior protection". For
|
||||
DLL payloads (rundll32-spawned), the heuristic additionally requires alert
|
||||
evidence to avoid false positives from benign rundll32 exit codes.
|
||||
- **DLL execution** — `.dll` payloads spawn via `rundll32.exe <path>,<entry> [args…]`;
|
||||
the entry point is the first token of the executable-args field (rundll32
|
||||
syntax `<ExportedFunction> [args…]`).
|
||||
- **XOR-on-the-wire** — every dispatch picks a random byte 0-255, XORs the
|
||||
payload before multipart upload, and tells Whiskers to reverse the XOR
|
||||
while writing to disk. Avoids leaving cleartext in HTTP buffers / OS network
|
||||
stacks where Defender's network inspection would flag it pre-write.
|
||||
- **Rich alert detail** — per-alert expandable panel with rule reason, rule
|
||||
description, MITRE ATT&CK tactic/technique chips, triggering API + behaviors,
|
||||
memory region + protection flags, full call stack with module provenance,
|
||||
final user module callout, process tree (spawned + parent), Defend's response
|
||||
actions (kill targets, tree kills), user identity, raw `_source`.
|
||||
- **Live agent dashboard** — `/whiskers` lists every registered profile with
|
||||
live agent + backend reachability; `/` shows the system dashboard with
|
||||
scanner availability + EDR fleet health. Both backed by an in-process TTL
|
||||
cache + background poller, so the dashboard loads instantly even when one
|
||||
VM is unreachable.
|
||||
- **Saved-view route** — `/results/edr/<profile>/<target>` renders the run's
|
||||
saved findings using the same renderer as the live scan view (no fork).
|
||||
|
||||
The integration is profile-driven — drop one or more `Config/edr_profiles/*.yml`
|
||||
files (gitignored; ship as `*.example.yml`) and each registered profile gets
|
||||
its own button on the upload page. Deployment is operator-managed; LitterBox
|
||||
does not deploy Elastic, Fibratus, or the EDR VM.
|
||||
|
||||
### Doppelganger Analysis
|
||||
|
||||
#### Blender Module
|
||||
Provides system-wide process comparison by:
|
||||
- Collecting IOCs from active processes
|
||||
- Comparing process characteristics with submitted payloads
|
||||
- Identifying behavioral similarities
|
||||
|
||||
#### FuzzyHash Module
|
||||
Delivers code similarity analysis through:
|
||||
- Maintained database of known tools and malware
|
||||
- ssdeep fuzzy hash comparison methodology
|
||||
- Detailed similarity scoring and reporting
|
||||
|
||||
## Integrated Tools
|
||||
|
||||
### Static Analysis Suite
|
||||
- [YARA](https://github.com/elastic/protections-artifacts/tree/main/yara) - Signature detection engine
|
||||
- [CheckPlz](https://github.com/BlackSnufkin/CheckPlz) - AV detection testing framework
|
||||
- [Stringnalyzer](https://github.com/BlackSnufkin/Rusty-Playground/tree/main/Stringnalyzer) - Advanced string analysis utility
|
||||
- [HolyGrail](https://github.com/BlackSnufkin/HolyGrail) - BYOVD Hunter
|
||||
|
||||
### Dynamic Analysis Suite
|
||||
- [YARA Memory](https://github.com/elastic/protections-artifacts/tree/main/yara) - Runtime pattern detection
|
||||
- [PE-Sieve](https://github.com/hasherezade/pe-sieve) - In-memory malware detection
|
||||
- [Moneta](https://github.com/forrest-orr/moneta) - Memory region IOC analyzer
|
||||
- [Patriot](https://github.com/BlackSnufkin/patriot) - In-memory stealth technique detection
|
||||
- [RedEdr](https://github.com/dobin/RedEdr) - ETW telemetry collection
|
||||
- [Hunt-Sleeping-Beacons](https://github.com/thefLink/Hunt-Sleeping-Beacons) - C2 beacon analyzer
|
||||
- [Hollows-Hunter](https://github.com/hasherezade/hollows_hunter) - Process hollowing detection
|
||||
|
||||
### EDR Integration Suite
|
||||
- **Whiskers** (this repo, `Whiskers/`) - Single-binary Rust HTTP agent for EDR-VM dispatch + Fibratus event-log query
|
||||
- [Elastic Defend](https://www.elastic.co/security/endpoint-security) - EDR backend for `kind: elastic` profiles (operator-deployed via [elastic-container-project](https://www.elastic.co/security-labs/the-elastic-container-project))
|
||||
- [Fibratus](https://github.com/rabbitstack/fibratus) - Open-source ETW detection engine for `kind: fibratus` profiles (no remote backend)
|
||||
|
||||
|
||||
## API Reference
|
||||
|
||||
The URL convention puts the analysis type / qualifier BEFORE the target hash
|
||||
everywhere — `/results/<type>/<target>`, `/api/results/<type>/<target>`,
|
||||
`/api/results/edr/<profile>/<target>` — to match the existing
|
||||
`/analyze/edr/<profile>/<target>` shape.
|
||||
|
||||
### File Operations
|
||||
```http
|
||||
POST /upload # Upload samples for analysis
|
||||
GET /upload # Drop-zone page (analysis-mode picker)
|
||||
GET /files # Retrieve processed file list
|
||||
```
|
||||
|
||||
### Analysis Endpoints
|
||||
```http
|
||||
GET /analyze/static/<hash> # Execute static analysis
|
||||
POST /analyze/dynamic/<hash> # Perform dynamic file analysis
|
||||
POST /analyze/dynamic/<pid> # Conduct process analysis
|
||||
GET /analyze/edr/<profile>/<target> # Render EDR results page (uses analysis_type=edr)
|
||||
POST /analyze/edr/<profile>/<target> # Dispatch payload to EDR profile (Phase 1 sync, Phase 2 background)
|
||||
GET /analyze/all/<target> # "All" pipeline coordinator page (Static + every EDR profile in parallel; Dynamic waits only for Static)
|
||||
```
|
||||
|
||||
### HolyGrail BYOVD Analysis
|
||||
```http
|
||||
POST /holygrail # Upload driver for BYOVD analysis
|
||||
GET /holygrail?hash=<hash> # Execute BYOVD analysis on uploaded driver
|
||||
```
|
||||
|
||||
### Doppelganger API
|
||||
```http
|
||||
# Blender Module
|
||||
GET /doppelganger?type=blender # Retrieve latest scan results
|
||||
GET /doppelganger?type=blender&hash=<hash> # Compare process IOCs with payload
|
||||
POST /doppelganger # Execute system scan with {"type": "blender", "operation": "scan"}
|
||||
|
||||
# FuzzyHash Module
|
||||
GET /doppelganger?type=fuzzy # Retrieve fuzzy analysis statistics
|
||||
GET /doppelganger?type=fuzzy&hash=<hash> # Execute fuzzy hash analysis
|
||||
POST /doppelganger # Generate database with {"type": "fuzzy", "operation": "create_db", "folder_path": "C:\path\to\folder"}
|
||||
```
|
||||
|
||||
### Results Retrieval (JSON)
|
||||
```http
|
||||
GET /api/results/info/<target> # File metadata
|
||||
GET /api/results/static/<target> # Static analysis results
|
||||
GET /api/results/dynamic/<target> # Dynamic analysis (file or PID)
|
||||
GET /api/results/holygrail/<target> # BYOVD analysis results
|
||||
GET /api/results/risk/<target> # Computed Detection Score + triggering indicators
|
||||
GET /api/results/edr/<target> # Index of every EDR profile run for this target
|
||||
GET /api/results/edr/<profile>/<target> # Saved findings for a specific EDR profile (used by Phase-2 polling)
|
||||
```
|
||||
|
||||
### EDR + System Health
|
||||
```http
|
||||
GET /api/edr/profiles # List registered EDR profiles (public — no secrets)
|
||||
GET /api/edr/agents/status # Per-profile agent + backend reachability snapshot (TTL-cached)
|
||||
GET /api/edr/fibratus/<profile>/alerts/since?from=&until= # Test/debug passthrough — query the Whiskers agent's Fibratus event-log endpoint without dispatching a payload
|
||||
GET /api/system/scanners # Inventory of configured local analyzers + whether their binaries exist
|
||||
```
|
||||
|
||||
### HTML Report Generation
|
||||
```http
|
||||
GET /api/report/<target> # Generate comprehensive HTML report (target = hash or pid)
|
||||
GET /api/report/<target>?download=true # Download report as file attachment
|
||||
GET /report/<target> # Download report directly (redirects to api with download=true)
|
||||
```
|
||||
|
||||
### Web Interface (Pages)
|
||||
```http
|
||||
GET / # System dashboard — scanner availability + EDR agent reachability
|
||||
GET /upload # Upload drop-zone (analysis-mode picker)
|
||||
GET /whiskers # EDR agents inventory (live status per registered profile)
|
||||
GET /summary # Cross-file results summary
|
||||
GET /results/info/<target> # File information page
|
||||
GET /results/static/<target> # Static analysis report
|
||||
GET /results/dynamic/<target> # Dynamic analysis report
|
||||
GET /results/holygrail/<target> # BYOVD analysis results
|
||||
GET /results/edr/<profile>/<target> # Saved EDR findings (rich detail — same renderer as the live scan)
|
||||
```
|
||||
|
||||
### System Management
|
||||
```http
|
||||
GET /health # System health verification
|
||||
POST /cleanup # Remove analysis artifacts
|
||||
POST /validate/<pid> # Verify process accessibility
|
||||
DELETE /file/<target> # Remove specific analysis
|
||||
```
|
||||
| Topic | Wiki page |
|
||||
|---|---|
|
||||
| How everything fits together | [Application Architecture](../../wiki/Application-Architecture) |
|
||||
| Run static + every reachable EDR in parallel | [All in One Pipeline](../../wiki/All-in-One-Pipeline) |
|
||||
| Dispatch payloads to a real EDR VM | [EDR Integration](../../wiki/EDR-Integration) → [Elastic Defend](../../wiki/Elastic-Defend-Setup) / [Fibratus](../../wiki/Fibratus-Setup) |
|
||||
| Whiskers agent (install, endpoints, build) | [Whiskers Agent](../../wiki/Whiskers-Agent) |
|
||||
| Every HTTP endpoint | [HTTP API Reference](../../wiki/HTTP-API-Reference) |
|
||||
| CLI / Python lib / MCP for LLMs | [GrumpyCats CLI](../../wiki/GrumpyCats-CLI) · [GrumpyCats Library](../../wiki/GrumpyCats-Library) · [LitterBoxMCP](../../wiki/LitterBoxMCP) |
|
||||
| What feeds the Detection Score | [Detection Score Explained](../../wiki/Detection-Score-Explained) |
|
||||
| Configure scanners / paths / timeouts | [Configuration Reference](../../wiki/Configuration-Reference) |
|
||||
| Add custom YARA rules / scanners | [YARA Rules Management](../../wiki/YARA-Rules-Management) · [New Scanner](../../wiki/New-Scanner) |
|
||||
|
||||
## Installation
|
||||
|
||||
### Windows Installation
|
||||
### Windows
|
||||
|
||||
**System Requirements:**
|
||||
- Windows operating system
|
||||
- Python 3.11 or higher
|
||||
- Administrator privileges
|
||||
|
||||
**Deployment Process:**
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/BlackSnufkin/LitterBox.git
|
||||
cd LitterBox
|
||||
```
|
||||
|
||||
2. Configure environment:
|
||||
```bash
|
||||
python -m venv venv
|
||||
.\venv\Scripts\Activate.ps1
|
||||
pip install -r requirements.txt
|
||||
python litterbox.py # add --debug for verbose logging
|
||||
```
|
||||
|
||||
**Operation:**
|
||||
```bash
|
||||
# Standard operation
|
||||
python litterbox.py
|
||||
Open `http://127.0.0.1:1337`. Requires Python 3.11+ and an admin shell.
|
||||
|
||||
# Diagnostic mode
|
||||
python litterbox.py --debug
|
||||
```
|
||||
### Linux (Docker)
|
||||
|
||||
**Access:**
|
||||
- **Web UI**: `http://127.0.0.1:1337`
|
||||
- **API Access**: Python client integration
|
||||
- **LLM Integration**: MCP server
|
||||
|
||||
---
|
||||
|
||||
### Linux Installation
|
||||
|
||||
**System Requirements:**
|
||||
- Linux operating system
|
||||
- Docker and Docker Compose
|
||||
- Hardware virtualization support
|
||||
|
||||
**Deployment Process:**
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/BlackSnufkin/LitterBox.git
|
||||
cd LitterBox/Docker
|
||||
```
|
||||
|
||||
2. Run automated setup:
|
||||
```bash
|
||||
chmod +x setup.sh
|
||||
./setup.sh
|
||||
```
|
||||
> **Note**: Initial setup takes approximately `1 hour` depending on internet speed and system resources.
|
||||
|
||||
The setup script automatically:
|
||||
- Installs Docker, Docker Compose, and CPU checker
|
||||
- Verifies KVM hardware virtualization support
|
||||
- Creates Windows 10 container environment with automated LitterBox installation
|
||||
- Starts containerized Windows instance
|
||||
The setup script provisions a Windows 10 container with KVM and runs LitterBox inside. Initial build takes ~1 hour.
|
||||
|
||||
**Access:**
|
||||
- **Installation monitor**: `http://localhost:8006` (track Windows setup progress)
|
||||
- **RDP access**: `localhost:3389` (available after installation completes, creds in docker file)
|
||||
- **Install monitor**: `http://localhost:8006`
|
||||
- **RDP**: `localhost:3389` (creds in the docker compose file)
|
||||
- **LitterBox UI**: `http://127.0.0.1:1337` once setup completes
|
||||
|
||||
Once installation completes, LitterBox provides:
|
||||
- **Web UI**: `http://127.0.0.1:1337`
|
||||
- **API Access**: Python client integration
|
||||
- **LLM Integration**: MCP server
|
||||
### EDR setup (optional)
|
||||
|
||||
---
|
||||
|
||||
>For API access, see the [Client Libraries](#client-libraries) section.
|
||||
|
||||
## Configuration
|
||||
|
||||
All settings are stored in `config/config.yml`. Edit this file to:
|
||||
|
||||
- Change server settings (host/port)
|
||||
- Set allowed file types
|
||||
- Configure analysis tools
|
||||
- Adjust timeouts
|
||||
|
||||
### EDR Setup (optional)
|
||||
|
||||
EDR integration is opt-in — drop one or more profile YAMLs under
|
||||
`Config/edr_profiles/` and the upload page picks them up at boot. Two profile
|
||||
kinds are supported with distinct setup paths.
|
||||
|
||||
#### Common: deploy Whiskers on the EDR VM
|
||||
|
||||
`Whiskers.exe` is a single-binary Rust agent (~1.6 MB, no runtime deps). See
|
||||
[Whiskers/README.md](Whiskers/README.md) for the full guide. Quick version:
|
||||
|
||||
```powershell
|
||||
# on the EDR VM
|
||||
mkdir C:\Tools -Force
|
||||
# copy Whiskers.exe into C:\Tools\
|
||||
|
||||
New-NetFirewallRule -DisplayName "Whiskers Agent" -Direction Inbound `
|
||||
-Protocol TCP -LocalPort 8080 -Action Allow
|
||||
|
||||
# Register as an at-logon scheduled task so it auto-starts.
|
||||
# Runs as the invoking user (no UAC); payloads inherit that privilege.
|
||||
C:\Tools\Whiskers.exe --install
|
||||
# Log out / back in to trigger the scheduled task, OR run it manually:
|
||||
C:\Tools\Whiskers.exe
|
||||
```
|
||||
|
||||
Payloads land in `<exe_dir>\samples\` by default. Override per-VM with
|
||||
`--samples-dir <path>` or per-dispatch via the profile YAML's `drop_path`.
|
||||
|
||||
If you're using Elastic Defend, add `Whiskers.exe` as a Trusted Application
|
||||
in Defend's policy (Kibana → Security → Manage → Trusted Applications), since
|
||||
the agent spawns arbitrary payloads.
|
||||
|
||||
#### Option A: `kind: elastic` (Elastic Defend)
|
||||
|
||||
**1. Stand up an Elastic stack** — LitterBox does not deploy or manage one.
|
||||
The fastest path is [elastic-container-project](https://www.elastic.co/security-labs/the-elastic-container-project)
|
||||
which gives you Elasticsearch + Kibana + Fleet locally. Once it's up:
|
||||
|
||||
- Enable the **Elastic Defend** integration on a Fleet policy
|
||||
- Enroll your EDR Windows VM into that policy
|
||||
- Optionally enable Detection-Engine rules in Kibana → Security → Manage → Rules
|
||||
- Create an API key under **Stack Management → API keys** with read access to
|
||||
`.alerts-security.alerts-*`, `.internal.alerts-security.alerts-*`, and
|
||||
`.ds-logs-endpoint.alerts-*`. Copy the **encoded** value.
|
||||
|
||||
**2. Configure the profile** — copy
|
||||
`Config/edr_profiles/elastic.yml.example` to `elastic.yml` (the real file is
|
||||
gitignored) and fill in:
|
||||
|
||||
```yaml
|
||||
name: "elastic"
|
||||
display_name: "Elastic Defend"
|
||||
kind: "elastic" # default; can be omitted for back-compat
|
||||
agent_url: "http://<edr-vm-ip>:8080"
|
||||
elastic_url: "https://<elastic-stack-ip>:9200"
|
||||
elastic_apikey: "<base64-encoded-key>"
|
||||
elastic_verify_tls: false # self-signed cert from elastic-container-project
|
||||
wait_seconds_for_alerts: 90 # max poll window for successful execs
|
||||
av_block_wait_seconds: 60 # max poll window for AV-block events
|
||||
exec_timeout_seconds: 60 # how long to let the payload run
|
||||
```
|
||||
|
||||
#### Option B: `kind: fibratus` (Fibratus open-source ETW)
|
||||
|
||||
No Elastic stack needed — Fibratus does ETW collection + rule matching locally
|
||||
on the EDR VM and writes alerts to the Windows Application event log. Whiskers
|
||||
queries the log on demand.
|
||||
|
||||
**1. Install Fibratus** on the EDR VM from
|
||||
[github.com/rabbitstack/fibratus](https://github.com/rabbitstack/fibratus/releases).
|
||||
Default install path is `C:\Program Files\Fibratus\`; Whiskers detects this
|
||||
and reports `telemetry_sources: ["fibratus"]` via `/api/info`.
|
||||
|
||||
**2. Configure Fibratus** to write JSON alerts to the event log. Edit
|
||||
`%PROGRAMFILES%\Fibratus\Config\fibratus.yml`:
|
||||
|
||||
```yaml
|
||||
alertsenders:
|
||||
eventlog:
|
||||
enabled: true
|
||||
format: json # CRITICAL — analyzer parses the <Data> field as JSON
|
||||
```
|
||||
|
||||
Make sure `filters.rules.enabled: true` and `filters.rules.from-paths` points
|
||||
at the rule pack you want active. Restart the service:
|
||||
`net stop fibratus; net start fibratus`.
|
||||
|
||||
**3. Configure the profile** — copy
|
||||
`Config/edr_profiles/fibratus.yml.example` to `fibratus.yml` and fill in:
|
||||
|
||||
```yaml
|
||||
name: "fibratus"
|
||||
display_name: "Fibratus"
|
||||
kind: "fibratus"
|
||||
agent_url: "http://<edr-vm-ip>:8080"
|
||||
wait_seconds_for_alerts: 30 # Fibratus pushes in real-time → shorter
|
||||
av_block_wait_seconds: 30
|
||||
exec_timeout_seconds: 60
|
||||
```
|
||||
|
||||
#### Verifying the wire
|
||||
|
||||
`hostname` is **not** configured — the agent self-reports it via
|
||||
`GET /api/info`, so moving the agent to a different VM is transparent. Restart
|
||||
LitterBox after editing any profile YAML; the upload page gains a button per
|
||||
registered profile.
|
||||
|
||||
Quick smoke-tests with the GrumpyCats CLI:
|
||||
```bash
|
||||
python GrumpyCats/grumpycat.py edr-profiles # list registered profiles
|
||||
python GrumpyCats/grumpycat.py edr-status # live agent + backend probe
|
||||
python GrumpyCats/grumpycat.py fibratus-alerts --profile fibratus \
|
||||
--from 2026-04-30T00:00:00Z # pull alerts via Whiskers without running a payload
|
||||
```
|
||||
|
||||
## Client Libraries
|
||||
|
||||
For programmatic access to LitterBox, use the **GrumpyCats** package:
|
||||
|
||||
**[GrumpyCats Documentation](GrumpyCats/README.md)**
|
||||
|
||||
The package includes:
|
||||
|
||||
* **grumpycat.py**: Dual-purpose tool that functions as:
|
||||
* Standalone CLI utility for direct server interaction
|
||||
* Python library for integrating LitterBox capabilities into custom tools
|
||||
|
||||
* **LitterBoxMCP.py**: Specialized server component that:
|
||||
* Wraps the GrumpyCat library functionality
|
||||
* Enables LLM agents to interact with the LitterBox analysis platform
|
||||
* Provides natural language interfaces to malware analysis workflows
|
||||
Drop one or more profile YAMLs under `Config/edr_profiles/` and the upload page picks them up at boot. Full walkthroughs in the wiki: [Whiskers Agent](../../wiki/Whiskers-Agent) → [Elastic Defend Setup](../../wiki/Elastic-Defend-Setup) or [Fibratus Setup](../../wiki/Fibratus-Setup).
|
||||
|
||||
## Contributing
|
||||
|
||||
Development contributions should be conducted in feature branches on personal forks.
|
||||
For detailed contribution guidelines, refer to: [CONTRIBUTING.md](./CONTRIBUTING.md)
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md). Work in feature branches on personal forks.
|
||||
|
||||
## Support 🍺
|
||||
|
||||
If LitterBox has been useful for your security research:
|
||||
|
||||
<a href="https://www.buymeacoffee.com/blacksnufkin"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" width="200" height="60"></a>
|
||||
|
||||
## Stargazers 🌟
|
||||
[](https://starchart.cc/blacksnufkin/litterbox)
|
||||
|
||||
## Security Advisory
|
||||
|
||||
- **DEVELOPMENT USE ONLY**: This platform is designed exclusively for testing environments. Production deployment presents significant security risks.
|
||||
- **ISOLATION REQUIRED**: Execute only in isolated virtual machines or dedicated testing environments.
|
||||
- **WARRANTY DISCLAIMER**: Provided without guarantees; use at your own risk.
|
||||
- **LEGAL COMPLIANCE**: Users are responsible for ensuring all usage complies with applicable laws and regulations.
|
||||
- **Development use only.** This platform is designed for testing environments. Production deployment presents significant security risks.
|
||||
- **Isolation required.** Run only in isolated VMs or dedicated testing environments.
|
||||
- **No warranty.** Provided without guarantees; use at your own risk.
|
||||
- **Legal compliance.** Users are responsible for ensuring usage complies with applicable laws.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
This project incorporates technologies from the following contributors:
|
||||
LitterBox stands on the work of these projects and their authors:
|
||||
|
||||
- [Elastic Security](https://github.com/elastic/protections-artifacts/tree/main/yara)
|
||||
- [hasherezade](https://github.com/hasherezade/pe-sieve)
|
||||
- [Forrest Orr](https://github.com/forrest-orr/moneta)
|
||||
- [rasta-mouse](https://github.com/rasta-mouse/ThreatCheck)
|
||||
- [thefLink](https://github.com/thefLink/Hunt-Sleeping-Beacons)
|
||||
- [joe-desimone](https://github.com/joe-desimone/patriot)
|
||||
- [dobin](https://github.com/dobin/RedEdr)
|
||||
- [mr.d0x](https://malapi.io/)
|
||||
| Tool | Author |
|
||||
|---|---|
|
||||
| [YARA rules](https://github.com/elastic/protections-artifacts/tree/main/yara) · [Elastic Defend](https://www.elastic.co/security/endpoint-security) | [Elastic Security](https://github.com/elastic) |
|
||||
| [PE-Sieve](https://github.com/hasherezade/pe-sieve) · [Hollows-Hunter](https://github.com/hasherezade/hollows_hunter) | [hasherezade](https://github.com/hasherezade) |
|
||||
| [Moneta](https://github.com/forrest-orr/moneta) | [Forrest Orr](https://github.com/forrest-orr) |
|
||||
| [Patriot](https://github.com/joe-desimone/patriot) | [joe-desimone](https://github.com/joe-desimone) |
|
||||
| [Hunt-Sleeping-Beacons](https://github.com/thefLink/Hunt-Sleeping-Beacons) | [thefLink](https://github.com/thefLink) |
|
||||
| [RedEdr](https://github.com/dobin/RedEdr) | [dobin](https://github.com/dobin) |
|
||||
| [Fibratus](https://github.com/rabbitstack/fibratus) | [rabbitstack](https://github.com/rabbitstack) |
|
||||
| [ThreatCheck](https://github.com/rasta-mouse/ThreatCheck) (basis for CheckPlz) | [rasta-mouse](https://github.com/rasta-mouse) |
|
||||
| [MalAPI](https://malapi.io/) reference DB | [mr.d0x](https://github.com/mrd0x) |
|
||||
|
||||
## Interface
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
# Build pipeline — Whiskers
|
||||
|
||||
Whiskers is a single Rust binary. Two supported build paths.
|
||||
|
||||
## On Windows (native)
|
||||
|
||||
```powershell
|
||||
# Toolchain — install once via rustup-init.exe from https://rustup.rs
|
||||
rustup target add x86_64-pc-windows-msvc
|
||||
|
||||
# Build
|
||||
cargo build --release
|
||||
|
||||
# Result
|
||||
target\release\Whiskers.exe # ~1 MB, no runtime deps
|
||||
```
|
||||
|
||||
The binary is fully static (no MSVCRT runtime DLL chase) when built with
|
||||
`x86_64-pc-windows-msvc` — the MSVC linker bundles vcruntime statically by
|
||||
default on `cargo build --release`.
|
||||
|
||||
## On Linux (cross-compile to Windows x64)
|
||||
|
||||
For CI / Linux dev hosts. Uses the mingw-w64 GCC cross-toolchain.
|
||||
|
||||
```bash
|
||||
# One-time toolchain setup (Debian / Ubuntu / Kali)
|
||||
sudo apt install gcc-mingw-w64-x86-64
|
||||
rustup target add x86_64-pc-windows-gnu
|
||||
|
||||
# Build
|
||||
cargo build --release --target x86_64-pc-windows-gnu
|
||||
|
||||
# Result
|
||||
target/x86_64-pc-windows-gnu/release/Whiskers.exe
|
||||
```
|
||||
|
||||
If linker complains about missing CRT, add to `~/.cargo/config.toml` once:
|
||||
|
||||
```toml
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
linker = "x86_64-w64-mingw32-gcc"
|
||||
```
|
||||
|
||||
The MSVC and GNU outputs are functionally equivalent; MSVC's binary is
|
||||
slightly smaller (better LTO with our `panic = "abort"` config), GNU's
|
||||
ships from any Linux box without a Windows machine in the loop.
|
||||
|
||||
## Release profile
|
||||
|
||||
`Cargo.toml` already configures the release profile for size:
|
||||
|
||||
```toml
|
||||
[profile.release]
|
||||
opt-level = "z" # optimize for size
|
||||
lto = true # link-time optimization
|
||||
codegen-units = 1 # better optimization at the cost of compile time
|
||||
strip = true # strip symbols
|
||||
panic = "abort" # smaller binary, no unwinding tables
|
||||
```
|
||||
|
||||
Result is ~1 MB. Strip + panic=abort + LTO together cut roughly 60% off
|
||||
the unoptimized `cargo build` size.
|
||||
|
||||
## Verifying a build
|
||||
|
||||
```bash
|
||||
# Quick smoke test
|
||||
./target/release/Whiskers.exe --port 8087 --bind 127.0.0.1 &
|
||||
AGENT_PID=$!
|
||||
sleep 1
|
||||
curl -s http://127.0.0.1:8087/api/info
|
||||
# Expected: {"hostname":"...","os_version":"...","agent_version":"0.1.0",
|
||||
# "telemetry_sources": [...]}
|
||||
kill $AGENT_PID
|
||||
```
|
||||
|
||||
The unit tests cover the parser-critical pieces — wevtutil XML parsing
|
||||
(both single- and double-quoted attribute styles) and ISO timestamp
|
||||
formatting. Run with:
|
||||
|
||||
```bash
|
||||
cargo test --release
|
||||
```
|
||||
|
||||
For the full integration test scenario (lock / exec / kill / logs +
|
||||
fibratus alerts round-trip), drop the binary on a real EDR VM and exercise
|
||||
it from LitterBox via `grumpycat.py edr-status` and
|
||||
`grumpycat.py fibratus-alerts --profile <name>`.
|
||||
+65
-128
@@ -1,66 +1,25 @@
|
||||
# Whiskers
|
||||
|
||||
LitterBox's sensor agent — a single-binary HTTP execution runner. Runs on
|
||||
the Windows VM where you've installed an EDR (Elastic Defend, Defender,
|
||||
etc.) and accepts payloads from LitterBox over HTTP. Executes them, reports
|
||||
stdout/stderr/PID/exit code back.
|
||||
LitterBox's sensor agent — a single-binary Rust HTTP execution runner. Runs on the Windows VM where you've installed an EDR (Elastic Defend, Fibratus, Defender, etc.) and accepts payloads from LitterBox over HTTP. Executes them, reports stdout/stderr/PID/exit code back, and (for `kind: fibratus` profiles) `wevtutil`-queries the local Application event log on demand.
|
||||
|
||||
```
|
||||
LitterBox ── HTTP ──► Whiskers.exe ── Process.Spawn ──► payload
|
||||
│
|
||||
└─ same VM has your EDR agent watching everything
|
||||
(Elastic Defend / Defender / Fibratus / etc.)
|
||||
(Elastic Defend / Fibratus / Defender / etc.)
|
||||
```
|
||||
|
||||
**Alert sourcing.** Whiskers itself does not author or interpret alerts —
|
||||
they come from whatever EDR you installed alongside it. LitterBox resolves
|
||||
alerts in one of two ways depending on the configured EDR profile kind:
|
||||
In the LitterBox family: `LitterBox` is the orchestrator, `GrumpyCats` is the client library, and **`Whiskers`** is the agent — sensors out in the field, picking up what happens to a payload during execution.
|
||||
|
||||
- **`kind: elastic`** — LitterBox queries your Elastic stack directly.
|
||||
Whiskers stays a pure exec runner.
|
||||
- **`kind: fibratus`** — LitterBox calls Whiskers's
|
||||
`GET /api/alerts/fibratus/since` (added v5.x), which `wevtutil`-queries
|
||||
the local Application event log for `Provider=Fibratus` rule matches.
|
||||
No remote backend needed.
|
||||
## Documentation
|
||||
|
||||
The naming: in the LitterBox family, `Whiskers` is the agent — sensors
|
||||
out in the field, deployed on the EDR VM, picking up what happens to a
|
||||
payload during execution. The orchestrator is `LitterBox` itself; the
|
||||
client library is `GrumpyCats`.
|
||||
Full agent docs (install, CLI flags, endpoints, security model, troubleshooting) live in the wiki: **[Whiskers Agent](../../../wiki/Whiskers-Agent)**.
|
||||
|
||||
## Install
|
||||
For end-to-end setup walkthroughs combining Whiskers with each EDR backend:
|
||||
- **[Elastic Defend Setup](../../../wiki/Elastic-Defend-Setup)** — `kind: elastic` profiles
|
||||
- **[Fibratus Setup](../../../wiki/Fibratus-Setup)** — `kind: fibratus` profiles
|
||||
|
||||
1. Get the binary
|
||||
- Download `Whiskers.exe` from the LitterBox release page, OR
|
||||
- Build from source — see [`BUILD.md`](BUILD.md)
|
||||
2. Drop it anywhere on the VM (e.g. `C:\Tools\Whiskers.exe`). The folder you
|
||||
put it in becomes the agent home — payloads land in
|
||||
`<that folder>\samples\` by default and the directory is created on first
|
||||
write.
|
||||
3. Allow inbound TCP 8080 in Windows Firewall:
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "Whiskers" `
|
||||
-Direction Inbound -Protocol TCP -LocalPort 8080 `
|
||||
-Action Allow
|
||||
```
|
||||
4. Run it once to verify:
|
||||
```powershell
|
||||
.\Whiskers.exe --port 8080
|
||||
```
|
||||
You should see something like:
|
||||
```
|
||||
2026-04-29T13:30:12Z INFO whiskers ready version=0.1.0 listen=0.0.0.0:8080
|
||||
```
|
||||
5. (Optional, recommended) Register it to auto-start on user logon so you
|
||||
don't have to launch it manually every session:
|
||||
```powershell
|
||||
.\Whiskers.exe --install
|
||||
```
|
||||
This creates an `ONLOGON` Windows scheduled task named `Whiskers` running
|
||||
as the current user (no UAC prompt). Log out and back in to confirm.
|
||||
To remove the task: `.\Whiskers.exe --uninstall`.
|
||||
|
||||
## Verify
|
||||
## Quick verify
|
||||
|
||||
From any machine that can reach the VM:
|
||||
|
||||
@@ -70,99 +29,77 @@ curl http://<edr-vm-ip>:8080/api/info
|
||||
# "telemetry_sources":["fibratus"]} # ← only when Fibratus is installed
|
||||
```
|
||||
|
||||
`telemetry_sources` is auto-populated based on what's present on the VM
|
||||
(currently just Fibratus presence at
|
||||
`C:\Program Files\Fibratus\Bin\fibratus.exe`). The orchestrator uses this
|
||||
to preflight before dispatching to a `kind: fibratus` profile.
|
||||
## Building from source
|
||||
|
||||
## CLI flags
|
||||
Whiskers is a single Rust binary. Two supported build paths.
|
||||
|
||||
| Flag | Default | Notes |
|
||||
|---|---|---|
|
||||
| `--port <PORT>` | `8080` | TCP port to listen on |
|
||||
| `--bind <ADDR>` | `0.0.0.0` | Bind address. Set `127.0.0.1` for loopback-only testing |
|
||||
| `--max-payload-mb <MB>` | `200` | Multipart upload cap on `/api/execute/exec`. LitterBox's own upload cap is 100 MB; this leaves headroom for the multipart envelope |
|
||||
| `--samples-dir <PATH>` | `<exe_dir>\samples` | Where payloads land when the orchestrator doesn't supply a per-request `drop_path`. Auto-created on first write |
|
||||
| `--install` | — | Register Whiskers as an `ONLOGON` Windows scheduled task (no UAC, runs as the invoking user). Forwards any non-default flags from the current invocation into the task. Exits without starting the server |
|
||||
| `--uninstall` | — | Remove the previously installed scheduled task. Exits |
|
||||
### On Windows (native)
|
||||
|
||||
The binary also accepts `--help` and `--version`.
|
||||
```powershell
|
||||
# Toolchain — install once via rustup-init.exe from https://rustup.rs
|
||||
rustup target add x86_64-pc-windows-msvc
|
||||
|
||||
## Endpoints
|
||||
# Build
|
||||
cargo build --release
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/info` | Self-reported `{hostname, os_version, agent_version, telemetry_sources}` |
|
||||
| `POST` | `/api/lock/acquire` | Single-occupancy gate. 200 if free, 409 if held |
|
||||
| `POST` | `/api/lock/release` | 200, idempotent |
|
||||
| `GET` | `/api/lock/status` | `{in_use: bool}` |
|
||||
| `POST` | `/api/execute/exec` | Multipart: `file` + `drop_path` + `executable_args` + `xor_key`. Returns `{status, pid}` immediately; the spawn runs detached. `.dll` payloads spawn via `rundll32.exe <path>,<entry> [args...]` — entry point is the first token of `executable_args`. |
|
||||
| `POST` | `/api/execute/kill` | Terminate the most recent run if alive |
|
||||
| `GET` | `/api/logs/execution` | `{pid, status, stdout, stderr, exit_code, started_at, finished_at, is_running}` for the most recent run |
|
||||
| `GET` | `/api/logs/agent` | Plain-text agent-debug log (last 1000 lines) |
|
||||
| `DELETE` | `/api/logs/agent` | Clear the agent log buffer |
|
||||
| `GET` | `/api/alerts/fibratus/since` | Query params `from=<ISO8601>&until=<ISO8601>`. Returns `{supported: bool, events: [{time_created, event_id, data}]}` — `data` is the raw JSON `<Data>` from each `Provider=Fibratus` event-log record in the window. `supported: false` when Fibratus isn't installed on the VM. |
|
||||
# Result
|
||||
target\release\Whiskers.exe # ~1.6 MB, no runtime deps
|
||||
```
|
||||
|
||||
Single-occupancy by design — a new `/api/execute/exec` while a previous
|
||||
run is still live will kill the previous one before starting the new
|
||||
one. The lock is what the orchestrator (LitterBox) holds across the
|
||||
whole exec → poll → release window to make sure two operators don't
|
||||
double-fire.
|
||||
The binary is fully static (no MSVCRT runtime DLL chase) when built with `x86_64-pc-windows-msvc` — the MSVC linker bundles vcruntime statically by default on `cargo build --release`.
|
||||
|
||||
The lock auto-expires after 30 minutes of being held — protects against
|
||||
a crashed orchestrator stranding Whiskers.
|
||||
### On Linux (cross-compile to Windows x64)
|
||||
|
||||
## Security model
|
||||
For CI / Linux dev hosts. Uses the mingw-w64 GCC cross-toolchain.
|
||||
|
||||
- Whiskers has **no authentication**. It's designed to run on a VM that
|
||||
only LitterBox should be able to reach (private network, VPN, or
|
||||
loopback). Don't expose port 8080 to the internet.
|
||||
- Payloads land in `<exe_dir>\samples\` by default (override with
|
||||
`--samples-dir` or per-request via the multipart `drop_path` field).
|
||||
The drop is auto-cleaned after each run, but Whiskers never reaches
|
||||
outside the supplied path.
|
||||
- Execution runs as the same user the Whiskers process is running as.
|
||||
`--install` registers the task as `ONLOGON` running as the invoking
|
||||
user — payloads run unelevated unless you launched the install from an
|
||||
elevated shell.
|
||||
- The XOR option on `/api/execute/exec` keeps the payload encrypted in
|
||||
transit and during the in-memory copy on the agent — useful when your
|
||||
EDR's behavioral monitor would match a plaintext known-bad sample
|
||||
before the spawn happens.
|
||||
```bash
|
||||
# One-time toolchain setup (Debian / Ubuntu / Kali)
|
||||
sudo apt install gcc-mingw-w64-x86-64
|
||||
rustup target add x86_64-pc-windows-gnu
|
||||
|
||||
## Troubleshooting
|
||||
# Build
|
||||
cargo build --release --target x86_64-pc-windows-gnu
|
||||
|
||||
**`Bind error: address already in use`** — another process holds port
|
||||
8080. Pick a different `--port` or stop the conflicting service.
|
||||
# Result
|
||||
target/x86_64-pc-windows-gnu/release/Whiskers.exe
|
||||
```
|
||||
|
||||
**Curl times out from another machine** — Windows Firewall is blocking.
|
||||
Verify the rule: `Get-NetFirewallRule -DisplayName "Whiskers"`.
|
||||
If the linker complains about missing CRT, add to `~/.cargo/config.toml` once:
|
||||
|
||||
**Lock stuck "in_use"** — wait 30 minutes for auto-expiry, or `POST
|
||||
/api/lock/release` from any client (release is unauthenticated by
|
||||
design — single-VM trust model).
|
||||
```toml
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
linker = "x86_64-w64-mingw32-gcc"
|
||||
```
|
||||
|
||||
**`/api/execute/exec` returns 500 with `Failed to spawn`** — the EDR on
|
||||
the VM probably blocked the dropper or the spawn. Check your EDR's
|
||||
quarantine log; this is exactly the signal we want to capture and
|
||||
return to LitterBox via the alert query path.
|
||||
The MSVC and GNU outputs are functionally equivalent; MSVC's binary is slightly smaller (better LTO with `panic = "abort"`), GNU's ships from any Linux box without a Windows machine in the loop.
|
||||
|
||||
**`/api/execute/exec` returns 200 with `{"status":"virus", ...}`** —
|
||||
Whiskers detected an AV intercept on file write or spawn (Windows errno
|
||||
225 / 995 / 1234). That's a successful detection from our point of view,
|
||||
not a transport-level failure; the orchestrator surfaces it as
|
||||
`summary.blocked_by_av: true` in the saved findings.
|
||||
### Release profile
|
||||
|
||||
**`/api/logs/execution` shows empty stdout** — the spawned process may
|
||||
not have flushed before exit, or it wrote to a separate console (GUI
|
||||
app). For GUI / detached payloads, capture is best-effort.
|
||||
`Cargo.toml` already configures the release profile for size:
|
||||
|
||||
**`/api/alerts/fibratus/since` returns `supported: false`** — the agent
|
||||
didn't find `C:\Program Files\Fibratus\Bin\fibratus.exe` on disk. Confirm
|
||||
Fibratus is installed at the default path; non-standard install paths
|
||||
are not currently auto-detected.
|
||||
```toml
|
||||
[profile.release]
|
||||
opt-level = "z" # optimize for size
|
||||
lto = true # link-time optimization
|
||||
codegen-units = 1 # better optimization at the cost of compile time
|
||||
strip = true # strip symbols
|
||||
panic = "abort" # smaller binary, no unwinding tables
|
||||
```
|
||||
|
||||
**`/api/alerts/fibratus/since` returns prose `data` strings instead of
|
||||
JSON** — Fibratus is in the default `format: pretty` mode. Edit
|
||||
`%PROGRAMFILES%\Fibratus\Config\fibratus.yml` to set
|
||||
`alertsenders.eventlog.format: json` and restart the Fibratus service.
|
||||
Strip + panic=abort + LTO together cut roughly 60% off the unoptimized `cargo build` size.
|
||||
|
||||
### Verifying a local build
|
||||
|
||||
```bash
|
||||
# Smoke test
|
||||
./target/release/Whiskers.exe --port 8087 --bind 127.0.0.1 &
|
||||
AGENT_PID=$!
|
||||
sleep 1
|
||||
curl -s http://127.0.0.1:8087/api/info
|
||||
kill $AGENT_PID
|
||||
|
||||
# Unit tests (parser-critical pieces — wevtutil XML + ISO timestamps)
|
||||
cargo test --release
|
||||
```
|
||||
|
||||
For the full integration test scenario (lock / exec / kill / logs + fibratus alerts round-trip), drop the binary on a real EDR VM and exercise it from LitterBox via `grumpycat.py edr-status` and `grumpycat.py fibratus-alerts --profile <name>`.
|
||||
|
||||
@@ -6,7 +6,6 @@ import logging
|
||||
import glob
|
||||
import json
|
||||
import re
|
||||
import difflib
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from .dynamic.moneta_analyzer import MonetaAnalyzer
|
||||
@@ -331,10 +330,6 @@ class BlenderAnalyzer:
|
||||
p_instances = self._extract_instance_count(p_ioc['description'])
|
||||
s_instances = self._extract_instance_count(s_ioc['description'])
|
||||
|
||||
# Compare descriptions without memory addresses
|
||||
p_desc = self._normalize_description(p_ioc['description'])
|
||||
s_desc = self._normalize_description(s_ioc['description'])
|
||||
|
||||
# Calculate match score based on instances
|
||||
instance_score = 1.0
|
||||
if p_instances and s_instances:
|
||||
|
||||
@@ -32,26 +32,27 @@ class PatriotAnalyzer(BaseSubprocessAnalyzer):
|
||||
'findings': [],
|
||||
}
|
||||
|
||||
current_section = None
|
||||
current_finding = None
|
||||
collecting_module_info = False
|
||||
|
||||
# Section-header markers that the absorbers don't need to see.
|
||||
# The absorbers detect their own field labels; these banner lines
|
||||
# are just visual separators in Patriot's stdout.
|
||||
SECTION_BANNERS = {
|
||||
'=== Process Information ===',
|
||||
'=== Memory Statistics ===',
|
||||
'=== Scan Summary ===',
|
||||
'=== Detailed Findings ===',
|
||||
}
|
||||
|
||||
for line in output.splitlines():
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if line.startswith('== Patriot Memory Scanner =='):
|
||||
current_section = 'header'
|
||||
elif line == '=== Process Information ===':
|
||||
current_section = 'process_info'
|
||||
elif line == '=== Memory Statistics ===':
|
||||
current_section = 'memory_stats'
|
||||
elif line == '=== Scan Summary ===':
|
||||
current_section = 'scan_summary'
|
||||
elif line == '=== Detailed Findings ===':
|
||||
current_section = 'detailed_findings'
|
||||
elif line.startswith('--- Finding #'):
|
||||
if line.startswith('== Patriot Memory Scanner ==') or line in SECTION_BANNERS:
|
||||
continue
|
||||
if line.startswith('--- Finding #'):
|
||||
if current_finding:
|
||||
sections['findings'].append(current_finding)
|
||||
current_finding = {'finding_number': int(re.search(r'#(\d+)', line).group(1))}
|
||||
|
||||
@@ -17,7 +17,7 @@ the rest of the analyzer config).
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from typing import Callable, Dict, List, Optional, Tuple
|
||||
from typing import Callable, Dict, List, Optional
|
||||
|
||||
from .elastic_edr_analyzer import ElasticEdrAnalyzer
|
||||
from .fibratus_edr_analyzer import FibratusEdrAnalyzer
|
||||
|
||||
+6
-168
@@ -16,7 +16,6 @@ class HolyGrailAnalyzer:
|
||||
self.tool_path = holygrail_config.get('tool_path', '')
|
||||
self.policies_path = holygrail_config.get('policies_path', '')
|
||||
self.command_template = holygrail_config.get('command', '')
|
||||
# rename to results_path and use consistently
|
||||
self.results_path = holygrail_config.get('results_path', '')
|
||||
self.timeout = holygrail_config.get('timeout', 120)
|
||||
|
||||
@@ -36,23 +35,21 @@ class HolyGrailAnalyzer:
|
||||
return {'status': 'error', 'error': f'File not found: {file_path}'}
|
||||
|
||||
try:
|
||||
# Build command
|
||||
command = self.command_template.format(
|
||||
tool_path=self.tool_path,
|
||||
file_path=file_path,
|
||||
policies_path=self.policies_path,
|
||||
results_path=self.results_path, # <— ensure provided
|
||||
results_path=self.results_path,
|
||||
)
|
||||
|
||||
self.logger.debug(f"Executing command: {command}")
|
||||
|
||||
# Run command
|
||||
result = subprocess.run(
|
||||
command,
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=self.timeout
|
||||
timeout=self.timeout,
|
||||
)
|
||||
|
||||
self.logger.debug(f"Command completed with return code: {result.returncode}")
|
||||
@@ -63,10 +60,9 @@ class HolyGrailAnalyzer:
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': f'Tool failed with code {result.returncode}',
|
||||
'stderr': result.stderr
|
||||
'stderr': result.stderr,
|
||||
}
|
||||
|
||||
# Find JSON in output
|
||||
json_data = self._extract_json(result.stdout)
|
||||
|
||||
if json_data:
|
||||
@@ -74,15 +70,15 @@ class HolyGrailAnalyzer:
|
||||
return {
|
||||
'status': 'completed',
|
||||
'findings': json_data,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
}
|
||||
else:
|
||||
|
||||
self.logger.error("No JSON found in holygrail output")
|
||||
self.logger.debug(f"Raw output: {result.stdout}")
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': 'No JSON found in output',
|
||||
'raw_output': result.stdout
|
||||
'raw_output': result.stdout,
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
@@ -92,170 +88,12 @@ class HolyGrailAnalyzer:
|
||||
self.logger.error(f"holygrail analysis failed: {str(e)}")
|
||||
return {'status': 'error', 'error': str(e)}
|
||||
|
||||
# --- below remains as-is; add results_path to its .format too ---
|
||||
add_debug(f"Starting analysis - file_path: {file_path}")
|
||||
add_debug(f"Config - enabled: {self.enabled}")
|
||||
add_debug(f"Config - tool_path: {self.tool_path}")
|
||||
add_debug(f"Config - policies_path: {self.policies_path}")
|
||||
add_debug(f"Config - timeout: {self.timeout}")
|
||||
|
||||
if not self.enabled:
|
||||
add_debug("FAILED: holygrail is disabled in config")
|
||||
result = {'status': 'error', 'error': 'holygrail disabled'}
|
||||
if debug_mode:
|
||||
result['debug_output'] = debug_info
|
||||
return result
|
||||
|
||||
add_debug(f"Checking tool exists: {self.tool_path}")
|
||||
if not os.path.exists(self.tool_path):
|
||||
add_debug(f"FAILED: Tool not found at {self.tool_path}")
|
||||
result = {'status': 'error', 'error': f'Tool not found: {self.tool_path}'}
|
||||
if debug_mode:
|
||||
result['debug_output'] = debug_info
|
||||
return result
|
||||
add_debug("Tool exists - OK")
|
||||
|
||||
add_debug(f"Checking policies exist: {self.policies_path}")
|
||||
if not os.path.exists(self.policies_path):
|
||||
add_debug(f"WARNING: Policies path not found: {self.policies_path}")
|
||||
else:
|
||||
add_debug("Policies path exists - OK")
|
||||
|
||||
add_debug(f"Checking file exists: {file_path}")
|
||||
if not os.path.exists(file_path):
|
||||
add_debug(f"FAILED: File not found at {file_path}")
|
||||
result = {'status': 'error', 'error': f'File not found: {file_path}'}
|
||||
if debug_mode:
|
||||
result['debug_output'] = debug_info
|
||||
return result
|
||||
add_debug("Target file exists - OK")
|
||||
|
||||
try:
|
||||
# Build command (debug path)
|
||||
command = self.command_template.format(
|
||||
tool_path=self.tool_path,
|
||||
file_path=file_path,
|
||||
policies_path=self.policies_path,
|
||||
results_path=self.results_path, # <— ensure provided here too
|
||||
)
|
||||
|
||||
add_debug(f"Built command: {command}")
|
||||
add_debug(f"Running with timeout: {self.timeout} seconds")
|
||||
|
||||
# Run command
|
||||
result = subprocess.run(
|
||||
command,
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
add_debug(f"Command completed - return code: {result.returncode}")
|
||||
add_debug(f"STDOUT length: {len(result.stdout)} chars")
|
||||
add_debug(f"STDERR length: {len(result.stderr)} chars")
|
||||
|
||||
if debug_mode:
|
||||
add_debug(f"STDOUT content: {result.stdout}")
|
||||
add_debug(f"STDERR content: {result.stderr}")
|
||||
|
||||
if result.returncode != 0:
|
||||
add_debug(f"FAILED: Tool returned error code {result.returncode}")
|
||||
error_result = {
|
||||
'status': 'error',
|
||||
'error': f'Tool failed with code {result.returncode}',
|
||||
'stderr': result.stderr
|
||||
}
|
||||
if debug_mode:
|
||||
error_result['debug_output'] = debug_info
|
||||
return error_result
|
||||
|
||||
add_debug("Tool executed successfully")
|
||||
|
||||
# Find JSON in output
|
||||
add_debug("Searching for JSON in output...")
|
||||
json_data = self._extract_json(result.stdout, debug_info if debug_mode else None)
|
||||
|
||||
if json_data:
|
||||
add_debug("JSON extracted successfully")
|
||||
add_debug(f"JSON keys found: {list(json_data.keys())}")
|
||||
success_result = {
|
||||
'status': 'completed',
|
||||
'findings': json_data,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
}
|
||||
if debug_mode:
|
||||
success_result['debug_output'] = debug_info
|
||||
return success_result
|
||||
else:
|
||||
add_debug("FAILED: No JSON found in output")
|
||||
error_result = {
|
||||
'status': 'error',
|
||||
'error': 'No JSON found in output',
|
||||
'raw_output': result.stdout
|
||||
}
|
||||
if debug_mode:
|
||||
error_result['debug_output'] = debug_info
|
||||
return error_result
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
add_debug(f"FAILED: Command timed out after {self.timeout} seconds")
|
||||
error_result = {'status': 'error', 'error': f'Timeout after {self.timeout} seconds'}
|
||||
if debug_mode:
|
||||
error_result['debug_output'] = debug_info
|
||||
return error_result
|
||||
except Exception as e:
|
||||
add_debug(f"FAILED: Exception occurred: {str(e)}")
|
||||
error_result = {'status': 'error', 'error': str(e)}
|
||||
if debug_mode:
|
||||
error_result['debug_output'] = debug_info
|
||||
return error_result.logger.info(f"Running: {command}")
|
||||
|
||||
# (unchanged code below)
|
||||
|
||||
|
||||
# Run command
|
||||
result = subprocess.run(
|
||||
command,
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': f'Tool failed: {result.stderr}',
|
||||
'stdout': result.stdout
|
||||
}
|
||||
|
||||
# Find JSON in output
|
||||
json_data = self._extract_json(result.stdout)
|
||||
|
||||
if json_data:
|
||||
return {
|
||||
'status': 'completed',
|
||||
'findings': json_data,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': 'No JSON found in output',
|
||||
'stdout': result.stdout
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {'status': 'error', 'error': str(e)}
|
||||
|
||||
def _extract_json(self, output: str) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
lines = output.strip().split('\n')
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith('{'):
|
||||
# Get JSON block
|
||||
json_lines = []
|
||||
brace_count = 0
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import logging
|
||||
import subprocess
|
||||
import time
|
||||
import psutil
|
||||
import json
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from typing import Dict, Type, Optional, Tuple
|
||||
from abc import ABC, abstractmethod
|
||||
@@ -541,7 +540,7 @@ class AnalysisManager:
|
||||
raise Exception(f"Process terminated after {elapsed:.1f} seconds (Command: {cmd_str})")
|
||||
|
||||
if not ps_process.is_running():
|
||||
raise Exception(f"Process terminated during initialization")
|
||||
raise Exception("Process terminated during initialization")
|
||||
|
||||
except psutil.NoSuchProcess:
|
||||
cmd_str = ' '.join(command)
|
||||
|
||||
@@ -33,7 +33,7 @@ import os
|
||||
import struct
|
||||
import datetime
|
||||
import hashlib
|
||||
from typing import Dict, List, Optional, Union, Any
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
class LnkForensicsError(Exception):
|
||||
"""Custom exception for LNK parsing errors."""
|
||||
|
||||
@@ -21,7 +21,6 @@ def get_edr_saved_results(profile, target):
|
||||
"""Saved-data view of an EDR run — does NOT re-dispatch to Whiskers.
|
||||
Mirrors the static_info / dynamic_info pattern for the EDR analyzer.
|
||||
The live-runner page is /analyze/edr/<profile>/<target>."""
|
||||
app = current_app
|
||||
deps = _deps()
|
||||
|
||||
data, error_msg, is_error = deps.helpers.load_analysis_data(target)
|
||||
|
||||
@@ -381,7 +381,6 @@ def save_uploaded_file(file, config):
|
||||
sha256_hash = hashlib.sha256(file_content).hexdigest()
|
||||
|
||||
original_filename = secure_filename(file.filename)
|
||||
extension = os.path.splitext(original_filename)[1].lower()
|
||||
filename = f"{md5_hash}_{original_filename}"
|
||||
|
||||
upload_folder = config['utils']['upload_folder']
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
LitterBox v5.0.0 is a major release. New EDR-integration pipeline (Elastic + Fibratus) backed by the **Whiskers** Rust agent, a system dashboard at `/`, a full backend / frontend refactor onto Flask blueprints + ES6 modules, and a perf cluster (parallel static analyzers, mtime-validated dashboard cache, lazy saved-views, adaptive polling). Drop `Whiskers.exe` on your EDR VM, copy a profile YAML from `Config/edr_profiles/*.yml.example`, restart LitterBox — the new profile button appears on the upload page.
|
||||
|
||||
## Downloads
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `Whiskers.exe` | Single-binary Rust agent — drop on the EDR-instrumented Windows VM. |
|
||||
| `Whiskers.exe.sha256` | SHA256 of the binary — verify the download before deploying. |
|
||||
| `Source code (zip / tar.gz)` | Auto-attached by GitHub. Full LitterBox source at this tag. |
|
||||
|
||||
## Verifying the binary
|
||||
|
||||
```powershell
|
||||
Get-FileHash Whiskers.exe -Algorithm SHA256
|
||||
# compare against the value in Whiskers.exe.sha256
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
See [README.md](README.md) for the full setup. Two EDR paths after the LitterBox install:
|
||||
|
||||
- **Elastic Defend** — copy `Config/edr_profiles/elastic.yml.example` → `elastic.yml`, fill in `agent_url` (Whiskers VM), `elastic_url`, and `elastic_apikey`. Optional Whiskers auto-start: `Whiskers.exe --install`.
|
||||
- **Fibratus (open-source ETW)** — copy `fibratus.yml.example` → `fibratus.yml` with just `agent_url`. On the VM, edit `%PROGRAMFILES%\Fibratus\Config\fibratus.yml` to set `alertsenders.eventlog: {enabled: true, format: json}` and restart the Fibratus service.
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### Added
|
||||
- Tailored downloadable report for driver samples (BYOVD section + BYOVD Potential hero)
|
||||
- `/api/results/<target>/risk` endpoint and matching `grumpycat.get_risk_assessment()` client method
|
||||
- `GrumpyCats/install_mcp.py` — installer for six MCP clients with auto-detected venv Python
|
||||
- Command-line arguments input on the dynamic-analysis warning modal (pre-populated from last run)
|
||||
- RedEdr now captures Microsoft-Windows-Kernel-File / -Network / -Audit-API-Calls / Antimalware-Engine ETW events; new tabs surface File Ops / Network / Audit API / Defender with Process Tree panel and ETW Provider Diagnostics
|
||||
- Defender threat verdicts at runtime contribute +50 to the Detection Score (only verdicts; scan activity stays descriptive)
|
||||
- Whiskers — single-binary Rust HTTP agent (`Whiskers/`) for dispatching payloads to a separate EDR-instrumented Windows VM
|
||||
- Fibratus profile kind (`kind: fibratus` in `Config/edr_profiles/<name>.yml`) — pull-from-event-log alternative to Elastic Defend, matching DetonatorAgent's `FibratusEdrPlugin.cs` integration shape. The operator configures Fibratus on the EDR VM with `alertsenders.eventlog: {enabled: true, format: json}`; rule matches land in the Windows Application event log under `Provider=Fibratus`. Whiskers gains a `GET /api/alerts/fibratus/since?from=…&until=…` endpoint that wevtutil-queries the log for records inside the run window and returns the raw JSON `<Data>` blobs (the agent does no parsing). The new `FibratusEdrAnalyzer` mirrors the Elastic two-phase shape — Phase 1 exec, Phase 2 polls Whiskers — and normalizes Fibratus's actual schema (`events[].proc.{name,exe,cmdline,parent_name,parent_cmdline,ancestors}` + bare `tactic.id`/`technique.id`/`subtechnique.id` labels) into the saved-view renderer's dict shape. No new alert-source coupling on Whiskers beyond one event-log query endpoint.
|
||||
- Whiskers `--install` / `--uninstall` flags register an `ONLOGON` Windows scheduled task so the agent auto-starts at user logon (no UAC, runs as the invoking user)
|
||||
- Whiskers `--samples-dir` flag; default drop path is now `<agent-exe-dir>/samples/` (auto-created on first write) instead of `C:\Users\Public\Downloads\`
|
||||
- Whiskers chunked-XOR write (64 KiB working buffer) — multi-MB payloads finish in milliseconds instead of the 10+ seconds the byte-by-byte loop took, which had been timing out the orchestrator
|
||||
- Elastic EDR integration via per-profile YAMLs under `Config/edr_profiles/`; each profile gets a "Run with X" tab on the upload page
|
||||
- Two-phase EDR orchestration: Phase 1 (exec) returns sync, Phase 2 (Elastic alert poll) runs in a background thread and updates the saved JSON when done
|
||||
- Per-payload alert correlation — query scoped by `host.name` + filename across `file.name`/`process.name`/`file.path`/`process.executable`/`process.command_line`/`process.args` (the `command_line` / `args` clauses pull in alerts on rundll32-launched DLLs, which only carry the DLL name in the parent's command line)
|
||||
- AV-block detection from Whiskers (`status:"virus"` on Windows errno 225/995/1234) surfaces as `blocked_by_av`
|
||||
- EDR-kill detection — non-zero exit without an agent-issued kill is labeled "killed by EDR behavior protection". For DLLs, kill classification additionally requires alert evidence (DLL hosts can exit non-zero for benign reasons)
|
||||
- Per-alert detail panel: Reason, Rule Description, MITRE chips, Triggering API, Memory Region, Call Stack with module provenance, Final User Module, Process / Parent / EDR Response cards
|
||||
- High/critical EDR alerts contribute up to +50 to the Detection Score (AV blocks +35; multi-profile takes the max)
|
||||
- New endpoints: `GET /api/edr/profiles`, `GET /api/edr/agents/status`, `GET /api/system/scanners`, `GET /api/results/edr/<target>[/<profile>]`, `GET/POST /analyze/edr/<profile>/<target>`
|
||||
- New pages: system dashboard at `/` (live scanner availability + EDR agent reachability, polls every minute), `/whiskers` agent inventory, `/analyze/all/<target>` "All" pipeline coordinator, `/results/edr/<profile>/<target>` saved-view (rich detail — MITRE chips, call stack, expandable per-alert detail, raw `_source` — backed by the same renderer as the live scan view)
|
||||
- "All" analysis mode: client-side coordinator runs Static + every EDR profile in parallel; Dynamic waits only for Static (EDR is on a remote VM, no local resource contention)
|
||||
- DLL execution support — payloads ending in `.dll` spawn via `rundll32.exe <path>,<entry> [args...]` in both the Whiskers agent and the local Dynamic analyzer; entry point comes from the executable-args field
|
||||
- GrumpyCats client gained EDR + scanner-health methods (`analyze_edr`, `get_edr_results`, `get_edr_index`, `list_edr_profiles`, `get_edr_agents_status`, `get_scanners_status`, `wait_for_edr_completion`, `fibratus_alerts_since`); CLI subcommands `edr-run` / `edr-results` / `edr-profiles` / `edr-status` / `scanners` / `fibratus-alerts`; matching MCP tools surface the same to LLM clients
|
||||
|
||||
### Changed
|
||||
- Drop-zone moved from `/` to `/upload` (GET); `/` is now the system dashboard. Existing POST `/upload` for file uploads is unchanged.
|
||||
- URL convention unified — analysis type / qualifier always comes BEFORE the target hash: `/results/<type>/<target>`, `/api/results/<type>/<target>`, `/api/results/edr/<profile>/<target>`. Matches `/analyze/edr/<profile>/<target>`.
|
||||
- AgentClient gains a separate `exec_timeout` (180s) for the multipart upload + agent-side write path; the cheap endpoints stay at the 10s default.
|
||||
- Status flow simplified — `blocked_polling_alerts` collapsed into `polling_alerts` plus an orthogonal `summary.blocked_by_av` flag (the polling itself is purely LitterBox→Elastic, so the EDR-VM blocked/clean state shouldn't affect the polling status).
|
||||
- Sidebar nav: added Dashboard entry; Upload nav points to `/upload`; "Agents" renamed to "Whiskers" (route also at `/whiskers`).
|
||||
- File-info detection score no longer folds in EDR results (EDR is its own analysis type with its own page; it shouldn't bleed into the static + dynamic + PE score).
|
||||
- Dashboard `/api/edr/agents/status` is now backed by a 30s TTL cache pre-warmed by a background poller (`services.edr_health`); per-probe timeouts dropped from 4s/5s to 2s. Cold path under 2s, every subsequent dashboard load <5ms (warm cache hit), and the auto-refresh tick stays within cache TTL.
|
||||
- Whiskers `/api/info` now reports `telemetry_sources: ["fibratus"]` when Fibratus is installed at `C:\Program Files\Fibratus\Bin\fibratus.exe` so the orchestrator can preflight before dispatching to a Fibratus profile.
|
||||
- Static analyzers (yara + checkplz + stringnalyzer) now run concurrently via a `ThreadPoolExecutor`. Wall time drops from `sum(per-tool)` to `max(per-tool)` — typically ~50% off static analysis (CheckPlz alone is multi-second; yara + stringnalyzer used to add several more after it). Dynamic stays parallel for yara/pe_sieve/moneta/patriot, with hsb running solo afterwards so its sleep-timing measurements aren't perturbed by concurrent process inspection. Per-tool start + finish + wall-time logged.
|
||||
- `/files` dashboard backed by a per-sample `_summary_cache.json` (`app/services/summary_cache.py`). Each cached entry stamps the source-JSON mtimes; a read recomputes the source mtime set and compares — any drift forces a recompute, so no manual invalidation is needed at any save site. Cache hit short-circuits the 4-6 disk reads + risk recompute the dashboard previously did per-sample. Single-sample render goes from ~16ms cold to ~2ms warm; expected to scale to ~3s cold → ~50ms warm at 200 samples.
|
||||
- `find_file_by_hash` (`app/utils/path_manager.py`) now keeps a per-folder hash→dirname index validated against the folder's mtime. The 15+ endpoints calling it 2-3× per page load (analysis dispatch, results pages, API readers) share the cache. Cold ≈ 470µs (one listdir), warm ≈ 50µs.
|
||||
- BYOVD route reads `compile_time` from `file_info.json` instead of re-parsing the PE — saves a redundant `pefile.PE(...)` + `generate_checksum()` round trip on every BYOVD run (multi-second on signed/large drivers).
|
||||
- Saved EDR view (`edr_info.html` + `edr-saved.js`) drops the inline `{{ edr_results | tojson }}` blob and lazy-fetches the JSON via `/api/results/edr/<profile>/<target>` on `DOMContentLoaded`. HTML shrinks from ~329 KB → ~13 KB on alert-heavy runs; the browser also caches the JSON between reloads.
|
||||
- EDR alerts table is now diff-aware — fingerprints the alert IDs into `target.dataset.alertsKey` and bails on re-render when unchanged. Detail bodies build lazily on first expand (cached via `data-built`) instead of running `JSON.stringify(a.raw, ...)` for every alert on every poll. Phase-2 polling no longer wipes user-expanded rows.
|
||||
- EDR Phase-2 client poll picks up adaptive cadence: 2s base, ×1.5 back-off up to 15s on consecutive ticks where `total_alerts` didn't change, reset on movement. Stops on terminal status.
|
||||
- Dashboard / Whiskers / EDR / All-pipeline poll loops pause on `document.visibilityState === 'hidden'` and resume (with one immediate refresh) on visible. No background-tab traffic.
|
||||
- Logging unified — single root-level handler with a compact formatter (`HH:MM:SS LEVEL module message`); 5-char fixed-width colored level, dim module name with `app.` / `services.` / `blueprints.` / `analyzers.` prefixes and `_analyzer` suffixes stripped. Werkzeug renamed to `http` and access lines reformatted from `127.0.0.1 - - [date] "GET /path HTTP/1.1" 200 -` to `GET /path → 200`. urllib3 / requests muted to WARNING.
|
||||
- `_classify_kill` (both elastic + fibratus EDR analyzers) requires alert evidence for ALL payloads — non-zero exit alone is no longer sufficient (false positives on payloads that crash on their own; Fibratus is detect-only and can never legitimately trigger this). Frontend DETECTED badge gated on `isTerminal && totalAlerts > 0`; killed_by_edr / blocked_by_av / failure / polling states only influence the detail string, never the badge.
|
||||
- AgentClient.get_execution_logs caps stdout/stderr at 256 KB. Prevents the saved-view template from inlining a 263 MB stdout (mimikatz spamming the prompt 18M times) and hanging the browser. Saved-view route also truncates defensively at load time so older saved findings render without a re-save.
|
||||
- /analyze/edr no longer writes a JSON for pre-execution failures (agent_unreachable / busy / error). Pages for samples whose EDR dispatch failed at the transport layer no longer pretend to have results — file-info hero hides per-profile buttons unless the saved JSON actually exists.
|
||||
- /analyze/all redesign: stat tiles (stages / alerts / elapsed), phase-banded rows, color-coded state pills (QUEUED / RUNNING / COMPLETED / FAILED / SKIPPED), agent-down preflight marks unreachable EDR profiles `SKIPPED` instead of burning the timeout, done banner only links to stages that actually produced data.
|
||||
- File-info hero buttons fully data-driven — Static / Dynamic / HolyGrail / per-EDR-profile only render if the corresponding saved JSON exists for the sample. A freshly-uploaded sample with no analyses run shows only the Back button.
|
||||
- Backend split into Flask blueprints, services, and a `utils/` package; subprocess analyzers consolidated under `BaseSubprocessAnalyzer`
|
||||
- Frontend split into per-tool ES6 modules with shared utils; reusable Jinja macros for scanner tables
|
||||
- Full UI redesign on a terminal/IDE shell with new `.lb-*` design tokens and JetBrains Mono throughout
|
||||
- Tailwind upgraded to v4 via the standalone CLI binary (committed `tailwind.min.css` ~10× smaller)
|
||||
- Self-contained downloadable report — Tailwind CDN dropped, CSS inlined, logo embedded
|
||||
- Stringnalyzer block in the report now renders every non-empty IOC bucket as a full code block (16 categories, 100-item cap)
|
||||
- `LitterBoxMCP.py` full rewrite onto modern FastMCP (async tools, stderr logging, `Annotated[..., Field(...)]` params, four focused OPSEC prompts)
|
||||
- `grumpycat.py` dispatch-table CLI and parallel `get_comprehensive_results`
|
||||
- UI terminology reframed for operator-first reading: Detection Score, Triggering Indicators, Sensitive Imports, Signature triggered, Critical Imports, Payload Analysis
|
||||
- Color palette softened across the app — severity tokens shifted -500 → -400, summary risk badges converted from solid bg to outlined chips, heavy rgba alphas tightened
|
||||
- Analysis-type cards now show explicit `Run X Scan →` CTAs with stronger hover state
|
||||
- RedEdr launch line is now `--etw --show --with-antimalwareengine --with-defendertrace --trace ...` (replaces broken `-e --trace` which RedEdr's cxxopts schema didn't recognize)
|
||||
- Payload now fires as soon as RedEdr signals ETW-providers-attached (1-3s typical) instead of a fixed 15s sleep
|
||||
- Module-load timeline deduplicates PEB-snapshot DLLs against ETW image_loads; kernel device paths stripped to basenames
|
||||
- ETW timestamps shown as `HH:MM:SS.mmm` (FILETIME → local time) instead of raw 64-bit values
|
||||
- Defender events split into threat / scan / internal categories; the noise table is collapsed by default with a verdict line summarizing what Defender did
|
||||
- Upload page analysis selector — stack of cards replaced with a segmented tab strip + per-mode body
|
||||
- Global font-size bump (10/11/12/13px → 11/12/13/14px)
|
||||
- Upload pipeline ~4× faster on multi-MB PEs: `Counter`-based entropy, `pefile` fast_load + lazy import parse, single checksum pass
|
||||
|
||||
### Fixed
|
||||
- XSS hardening at user-data interpolation sites in results-page renderers
|
||||
- Detection counts on `/results/<hash>/static` no longer leak dynamic-scope counts
|
||||
- Per-tool render failures no longer suppress the rest of the rendering
|
||||
- Office macro upload no longer throws on missing `macroDetectionNotes` element (upstream issue)
|
||||
- `LitterBoxMCP.py` startup crash — broken import, removed `mcp.serve(...)` API, and stdout-corrupting logging all fixed
|
||||
- RedEdr parser was reading PascalCase ETW field names (ProcessID, ImageName, ThreadID, etc.) but RedEdr lowercases all field names; Threads / Images / Child Processes / CPU Priority tabs now populate with real data instead of nulls
|
||||
- Audit-API events now show `OpenProcess` / `OpenThread` (mapped from `etw_event_id`) instead of the placeholder task name `Info`
|
||||
- RedEdr is now always cleaned up on dynamic-analysis failure paths (early termination, payload crash, analyzer exception); previously left orphaned subprocesses
|
||||
|
||||
### Removed
|
||||
- Pre-redesign Tailwind utility chains and inline cyber-themed `<style>` blocks
|
||||
- Tailwind CDN runtime dependency from `report.html`
|
||||
- Dead code in `grumpycat.py` and `LitterBoxMCP.py` (cache, unused imports, exception envelope, lazy client wrapper)
|
||||
- `etw_wait_time` config key (replaced by event-driven readiness signal)
|
||||
|
||||
### Notes
|
||||
- New runtime dependency: `requests==2.32.3`
|
||||
- Whiskers binary not committed — build via `cargo build --release` (see `Whiskers/README.md` → "Building from source")
|
||||
- No public API / endpoint changes; existing JS DOM-ID contracts preserved
|
||||
Reference in New Issue
Block a user