Add streaming filesystem triage and German usage guide
This commit is contained in:
1 parent
ae5a1a1e54
commit
83f53adb29
10 files changed
+1622
-157
No files matched your search
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the installed filesystem scanner concurrently on explicitly named exports."""
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("archives", nargs="+", type=Path)
|
||||
parser.add_argument("--output", required=True, type=Path)
|
||||
parser.add_argument("--ioc-file", type=Path)
|
||||
parser.add_argument("--workers", type=int, default=4)
|
||||
args = parser.parse_args()
|
||||
if args.workers < 1 or args.workers > 4:
|
||||
parser.error("workers must be 1-4")
|
||||
names = [p.name.removesuffix(".tar.gz").removesuffix(".tgz") for p in args.archives]
|
||||
if len(set(names)) != len(names):
|
||||
parser.error("Archive names must be unique")
|
||||
if args.output.exists():
|
||||
parser.error("Output must be new")
|
||||
for archive in args.archives:
|
||||
if not archive.is_file():
|
||||
parser.error(f"Not a readable regular archive: {archive.name}")
|
||||
args.output.mkdir(parents=True, mode=0o700)
|
||||
if args.ioc_file:
|
||||
# Preserve the exact small case rule input used for this run.
|
||||
data = args.ioc_file.read_bytes()
|
||||
(args.output / "case-iocs.csv").write_bytes(data)
|
||||
args.ioc_file = args.output / "case-iocs.csv"
|
||||
code = Path(__file__).resolve().parents[1] / "src/netscaler_triage"
|
||||
manifest = {
|
||||
"archives": [p.name for p in args.archives],
|
||||
"workers": args.workers,
|
||||
"tool_sha256": {
|
||||
str(p.relative_to(code)): hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
for p in [
|
||||
code / "filesystem.py",
|
||||
code / "cli.py",
|
||||
code / "data/filesystem_rules.json",
|
||||
code / "data/threat_intel.json",
|
||||
]
|
||||
},
|
||||
}
|
||||
(args.output / "run_manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
|
||||
|
||||
def run(item: tuple[Path, str]) -> dict:
|
||||
archive, name = item
|
||||
command = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"netscaler_triage",
|
||||
"filesystem",
|
||||
str(archive),
|
||||
"--output",
|
||||
str(args.output / name),
|
||||
"--max-output-bytes",
|
||||
str(256 * 1024**2),
|
||||
"--json",
|
||||
]
|
||||
if args.ioc_file:
|
||||
command.extend(["--ioc-file", str(args.ioc_file)])
|
||||
print(json.dumps({"started": archive.name}), flush=True)
|
||||
with (
|
||||
(args.output / (name + ".stdout.json")).open("w") as stdout,
|
||||
(args.output / (name + ".progress.log")).open("w") as stderr,
|
||||
):
|
||||
result = subprocess.run(command, stdout=stdout, stderr=stderr, check=False)
|
||||
row = {"archive": archive.name, "output": name, "exit_code": result.returncode}
|
||||
print(json.dumps(row), flush=True)
|
||||
return row
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as pool:
|
||||
results = list(pool.map(run, zip(args.archives, names, strict=True)))
|
||||
(args.output / "batch_summary.json").write_text(json.dumps(results, indent=2) + "\n")
|
||||
return int(any(r["exit_code"] != 0 for r in results))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user