Files
T
mukul975 8cae0648ec Add 55 new skills across 3 new domains + 6 undercovered areas (762 -> 817)
Demand-driven expansion targeting the fastest-growing 2025-2026 threat and
skills categories (ISC2/WEF/CrowdStrike/Mandiant signals):

- AI Security (NEW domain, 12 skills): LLM red-teaming with garak/PyRIT,
  prompt injection (direct/indirect/RAG), MCP tool-poisoning, agentic tool
  invocation, guardrails, model/data poisoning, system-prompt leakage,
  embedding/vector weaknesses, model extraction, continuous red-teaming
- Supply Chain Security (NEW domain, 5 skills): SBOMs, dependency confusion,
  malicious-npm triage, typosquatting, SLSA/Sigstore provenance
- Hardware & Firmware Security (NEW domain, 4 skills): CHIPSEC/UEFI audit,
  Secure Boot bypass, TPM measured-boot attestation, ESP bootkit hunting
- Identity (10): Entra ID/ROADtools, GraphRunner, AADInternals, ADCS/Certipy,
  shadow credentials, coercion, BloodHound CE, device-code phishing, SSO abuse
- Cloud-native (8): Stratus, Pacu, CloudFox, container escape, K8s RBAC,
  Falco, Trivy, kube-bench
- Offensive C2 (6): Sliver, Havoc, NetExec, DPAPI, NTLM relay ESC8, redirectors
- DFIR (6): Hayabusa, Chainsaw, KAPE, Velociraptor, EZ Tools, Plaso
- Backfill (4): OpenCTI, MISP, honeytokens, post-quantum crypto migration

Each skill follows the repo taxonomy (SKILL.md + references/{standards,api-reference}.md
+ scripts/agent.py + LICENSE), with researched real tool commands (no placeholders),
complete frontmatter, and ATT&CK/ATLAS + NIST CSF mappings. Updates README domain
table, skill count, and index.json.
2026-06-22 19:08:16 +02:00

175 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""
esp_bootkit_hunter.py — Baseline and hunt malicious EFI binaries on the EFI System Partition.
Mounts (or reads an already-mounted) ESP, inventories EFI/PE boot binaries, computes
SHA-256 hashes, verifies Secure Boot signatures with sbverify, flags files outside the
canonical EFI/ directory, diffs against a golden baseline, and optionally YARA-scans.
Real tooling used: sbverify (sbsigntool), yara, sha256 hashing. No placeholders.
Usage:
sudo python3 esp_bootkit_hunter.py --esp /mnt/esp --baseline baseline.sha256
sudo python3 esp_bootkit_hunter.py --device /dev/sda1 --mount --yara bootkit.yar
"""
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
EFI_EXTENSIONS = (".efi", ".sys", ".dll")
def run(cmd):
"""Run a command, return (returncode, stdout, stderr)."""
try:
p = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return p.returncode, p.stdout, p.stderr
except FileNotFoundError:
return 127, "", f"binary not found: {cmd[0]}"
except subprocess.TimeoutExpired:
return 124, "", "timeout"
def mount_esp(device, mountpoint):
os.makedirs(mountpoint, exist_ok=True)
rc, _, err = run(["mount", "-o", "ro,umask=077", device, mountpoint])
if rc != 0:
sys.exit(f"[!] Failed to mount {device}: {err.strip()}")
print(f"[+] Mounted {device} read-only at {mountpoint}")
def sha256_file(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return h.hexdigest()
def find_efi_binaries(esp_root):
found = []
for dirpath, _, files in os.walk(esp_root):
for name in files:
if name.lower().endswith(EFI_EXTENSIONS):
found.append(os.path.join(dirpath, name))
return found
def detect_anomalous_root(esp_root):
"""Per Velociraptor/Rapid7: EFI/ should be the only top-level dir on the ESP."""
allowed = {"efi", "system volume information"}
anomalies = []
for entry in os.listdir(esp_root):
if entry.lower() not in allowed:
anomalies.append(os.path.join(esp_root, entry))
return anomalies
def verify_signature(path):
"""Use sbverify --list to confirm the binary carries a Secure Boot signature."""
if not shutil.which("sbverify"):
return "sbverify-missing"
rc, out, _ = run(["sbverify", "--list", path])
if rc != 0:
return "UNSIGNED-or-invalid"
# sbverify --list prints 'signature N' lines for each embedded signature
return "signed" if "signature" in out.lower() else "UNSIGNED"
def load_baseline(path):
baseline = set()
if not path or not os.path.exists(path):
return baseline
with open(path) as f:
for line in f:
tok = line.strip().split()
if tok:
baseline.add(tok[0].lower())
return baseline
def yara_scan(rules, esp_root):
if not shutil.which("yara"):
return "yara-missing"
rc, out, err = run(["yara", "-r", "-w", rules, esp_root])
return out.strip() or ("(no matches)" if rc == 0 else err.strip())
def main():
ap = argparse.ArgumentParser(description="Hunt bootkits on the EFI System Partition.")
ap.add_argument("--esp", default="/mnt/esp", help="ESP mountpoint")
ap.add_argument("--device", help="ESP block device to mount (e.g. /dev/sda1)")
ap.add_argument("--mount", action="store_true", help="Mount --device read-only first")
ap.add_argument("--baseline", help="Golden baseline sha256 file to diff against")
ap.add_argument("--yara", help="YARA ruleset to scan the ESP with")
ap.add_argument("--json", help="Write findings to this JSON path")
args = ap.parse_args()
if os.geteuid() != 0:
print("[!] Warning: ESP access typically requires root.", file=sys.stderr)
if args.mount:
if not args.device:
sys.exit("--mount requires --device")
mount_esp(args.device, args.esp)
if not os.path.isdir(args.esp):
sys.exit(f"[!] ESP path not found: {args.esp}")
report = {"esp": args.esp, "binaries": [], "root_anomalies": [],
"baseline_new": [], "yara": None}
# Step 2: anomalous root entries
report["root_anomalies"] = detect_anomalous_root(args.esp)
for a in report["root_anomalies"]:
print(f"[!] ANOMALY: non-EFI entry in ESP root -> {a}")
# Steps 3-4: inventory, hash, verify signatures
baseline = load_baseline(args.baseline)
for path in find_efi_binaries(args.esp):
try:
digest = sha256_file(path)
except OSError as e:
print(f"[!] Cannot read {path}: {e}", file=sys.stderr)
continue
sig = verify_signature(path)
new = bool(baseline) and digest.lower() not in baseline
entry = {"path": path, "sha256": digest, "signature": sig, "new_vs_baseline": new}
report["binaries"].append(entry)
flag = ""
if sig.startswith("UNSIGNED"):
flag += " [UNSIGNED]"
if new:
flag += " [NOT-IN-BASELINE]"
report["baseline_new"].append(digest)
print(f"[+] {digest} {sig:18s} {path}{flag}")
# Step 6: YARA
if args.yara:
report["yara"] = yara_scan(args.yara, args.esp)
print(f"[+] YARA results:\n{report['yara']}")
# Summary
n_unsigned = sum(1 for b in report["binaries"] if b["signature"].startswith("UNSIGNED"))
print(f"\n[=] {len(report['binaries'])} EFI binaries | "
f"{n_unsigned} unsigned | {len(report['baseline_new'])} new vs baseline | "
f"{len(report['root_anomalies'])} root anomalies")
if args.json:
with open(args.json, "w") as f:
json.dump(report, f, indent=2)
print(f"[+] Wrote findings to {args.json}")
# Non-zero exit if anything suspicious was found (CI/hunt automation friendly)
if n_unsigned or report["baseline_new"] or report["root_anomalies"]:
sys.exit(2)
if __name__ == "__main__":
main()