Initial commit: chainsaw EVTX hunter
WithSecure Labs' chainsaw — fast Sigma-based EVTX hunter, complementary
to hayabusa/zircolite (different rule engine + format).
- ubuntu:24.04 base, multi-stage (fetcher + runtime).
- Pulls latest chainsaw release tarball from GitHub at build time
(greps the API JSON because release notes contain control chars
that break jq).
- Clones SigmaHQ rules at build (chainsaw v2 dropped bundled rules).
- start.sh: chainsaw hunt /data --csv --output (CSV is mutually
exclusive with --json/--jsonl in v2.x; pick CSV for grep-ability).
- Output: /output/chainsaw_<ts>/{csv/, hunt.txt}.
- test_smoke.sh: fetch Yamato sample-evtx, scan, count detections.
- fetch-test-data.sh + .gitignore.
Validated end-to-end on amd64 Linux: 6/6 PASS, 3970 detections on
DeepBlueCLI subset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
# Smoke test for chainsaw.
|
||||
#
|
||||
# Available SUBSET values: DeepBlueCLI | YamatoSecurity | EVTX-ATTACK-SAMPLES |
|
||||
# EVTX-to-MITRE-Attack | "" (full bundle).
|
||||
#
|
||||
# Env vars: TAG=ls-chainsaw:test SUBSET=DeepBlueCLI KEEP_DATA=1
|
||||
set -u
|
||||
TAG="${TAG:-ls-chainsaw:test}"
|
||||
SUBSET="${SUBSET:-DeepBlueCLI}"
|
||||
KEEP_DATA="${KEEP_DATA:-0}"
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
ROOT="$(pwd)"
|
||||
DATA="$ROOT/test-data/sample-evtx"
|
||||
OUT="$(mktemp -d)"
|
||||
trap 'rm -rf "$OUT"; [ "$KEEP_DATA" = 0 ] && rm -rf "$ROOT/test-data"' EXIT
|
||||
|
||||
pass=0; fail=0
|
||||
ok() { echo "PASS $1"; pass=$((pass+1)); }
|
||||
bad() { echo "FAIL $1"; fail=$((fail+1)); }
|
||||
|
||||
if docker image inspect "$TAG" >/dev/null 2>&1; then
|
||||
ok "image $TAG present"
|
||||
else
|
||||
bad "image $TAG not present"; exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$DATA" ]; then
|
||||
echo "Fetching sample EVTX..."
|
||||
./fetch-test-data.sh >/dev/null
|
||||
fi
|
||||
SCAN="$DATA/$SUBSET"; [ -z "$SUBSET" ] && SCAN="$DATA"
|
||||
n=$(find "$SCAN" -name "*.evtx" | wc -l | tr -d ' ')
|
||||
[ "$n" -gt 0 ] && ok "found $n EVTX in ${SUBSET:-<all>}" || { bad "no EVTX"; exit 1; }
|
||||
|
||||
echo "Running chainsaw hunt..."
|
||||
if docker run --rm --network=none -v "$SCAN:/data:ro" -v "$OUT:/output" "$TAG" >"$OUT/.run.log" 2>&1; then
|
||||
ok "container exited cleanly"
|
||||
else
|
||||
bad "container non-zero"; tail -25 "$OUT/.run.log"
|
||||
fi
|
||||
|
||||
run_dir=$(ls -d "$OUT"/chainsaw_* 2>/dev/null | head -1)
|
||||
[ -d "$run_dir" ] && ok "output dir created" || { bad "no chainsaw_<ts>/ dir"; }
|
||||
[ -d "$run_dir/csv" ] && ok "csv subdir present" || bad "csv/ missing"
|
||||
[ -s "$run_dir/hunt.txt" ] && ok "hunt summary saved" || bad "hunt.txt missing"
|
||||
|
||||
# chainsaw prints "[+] X Detections found" — pull that.
|
||||
if [ -s "$run_dir/hunt.txt" ]; then
|
||||
hits=$(grep -oE '[0-9]+ Detections found' "$run_dir/hunt.txt" | grep -oE '[0-9]+' | head -1)
|
||||
echo
|
||||
echo "Detections: ${hits:-0}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Summary: $pass pass, $fail fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
Reference in New Issue
Block a user