Files
NetScaler_forensik/scripts/analyze_filesystem_exports.py
T

85 lines
3.2 KiB
Python
Raw Normal View History

#!/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())