mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-16 07:53:18 +03:00
296 lines
12 KiB
Python
296 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
EZ Tools Forensic Artifact Processor
|
|
|
|
Automates the execution of Eric Zimmerman's tools against collected
|
|
forensic artifacts and generates consolidated analysis reports.
|
|
"""
|
|
|
|
import subprocess
|
|
import csv
|
|
import os
|
|
import sys
|
|
import json
|
|
import hashlib
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
from collections import defaultdict
|
|
|
|
|
|
class EZToolsProcessor:
|
|
"""Orchestrates EZ Tools processing against forensic artifact collections."""
|
|
|
|
def __init__(self, ez_tools_path: str, evidence_path: str, output_path: str):
|
|
self.ez_tools_path = Path(ez_tools_path)
|
|
self.evidence_path = Path(evidence_path)
|
|
self.output_path = Path(output_path)
|
|
self.output_path.mkdir(parents=True, exist_ok=True)
|
|
self.results = {}
|
|
self.tools = {
|
|
"MFTECmd": self.ez_tools_path / "MFTECmd.exe",
|
|
"PECmd": self.ez_tools_path / "PECmd.exe",
|
|
"LECmd": self.ez_tools_path / "LECmd.exe",
|
|
"JLECmd": self.ez_tools_path / "JLECmd.exe",
|
|
"SBECmd": self.ez_tools_path / "SBECmd.exe",
|
|
"EvtxECmd": self.ez_tools_path / "EvtxECmd.exe",
|
|
"RECmd": self.ez_tools_path / "RECmd.exe",
|
|
"RBCmd": self.ez_tools_path / "RBCmd.exe",
|
|
"AmcacheParser": self.ez_tools_path / "AmcacheParser.exe",
|
|
"AppCompatCacheParser": self.ez_tools_path / "AppCompatCacheParser.exe",
|
|
}
|
|
|
|
def verify_tools(self) -> dict:
|
|
"""Verify which EZ Tools are available on the system."""
|
|
availability = {}
|
|
for name, path in self.tools.items():
|
|
availability[name] = path.exists()
|
|
return availability
|
|
|
|
def run_tool(self, tool_name: str, args: list) -> dict:
|
|
"""Execute an EZ Tool with given arguments."""
|
|
tool_path = self.tools.get(tool_name)
|
|
if not tool_path or not tool_path.exists():
|
|
return {"status": "error", "message": f"{tool_name} not found at {tool_path}"}
|
|
|
|
cmd = [str(tool_path)] + args
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=300
|
|
)
|
|
return {
|
|
"status": "success" if result.returncode == 0 else "error",
|
|
"stdout": result.stdout,
|
|
"stderr": result.stderr,
|
|
"returncode": result.returncode
|
|
}
|
|
except subprocess.TimeoutExpired:
|
|
return {"status": "error", "message": f"{tool_name} timed out after 300 seconds"}
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
|
|
def process_mft(self, mft_path: str = None) -> dict:
|
|
"""Parse the $MFT file with MFTECmd."""
|
|
if mft_path is None:
|
|
mft_candidates = list(self.evidence_path.rglob("$MFT"))
|
|
if not mft_candidates:
|
|
return {"status": "skipped", "message": "$MFT not found"}
|
|
mft_path = str(mft_candidates[0])
|
|
|
|
output_file = "MFT_output.csv"
|
|
args = ["-f", mft_path, "--csv", str(self.output_path), "--csvf", output_file]
|
|
result = self.run_tool("MFTECmd", args)
|
|
result["output_file"] = str(self.output_path / output_file)
|
|
self.results["MFT"] = result
|
|
return result
|
|
|
|
def process_usn_journal(self, journal_path: str = None) -> dict:
|
|
"""Parse the USN Journal ($J) with MFTECmd."""
|
|
if journal_path is None:
|
|
j_candidates = list(self.evidence_path.rglob("$J"))
|
|
if not j_candidates:
|
|
return {"status": "skipped", "message": "$J not found"}
|
|
journal_path = str(j_candidates[0])
|
|
|
|
output_file = "USNJournal_output.csv"
|
|
args = ["-f", journal_path, "--csv", str(self.output_path), "--csvf", output_file]
|
|
result = self.run_tool("MFTECmd", args)
|
|
result["output_file"] = str(self.output_path / output_file)
|
|
self.results["USNJournal"] = result
|
|
return result
|
|
|
|
def process_prefetch(self, prefetch_dir: str = None) -> dict:
|
|
"""Parse Prefetch files with PECmd."""
|
|
if prefetch_dir is None:
|
|
pf_candidates = list(self.evidence_path.rglob("Prefetch"))
|
|
if not pf_candidates:
|
|
return {"status": "skipped", "message": "Prefetch directory not found"}
|
|
prefetch_dir = str(pf_candidates[0])
|
|
|
|
output_file = "Prefetch_output.csv"
|
|
args = ["-d", prefetch_dir, "--csv", str(self.output_path), "--csvf", output_file]
|
|
result = self.run_tool("PECmd", args)
|
|
result["output_file"] = str(self.output_path / output_file)
|
|
self.results["Prefetch"] = result
|
|
return result
|
|
|
|
def process_lnk_files(self, lnk_dir: str = None) -> dict:
|
|
"""Parse LNK shortcut files with LECmd."""
|
|
if lnk_dir is None:
|
|
recent_candidates = list(self.evidence_path.rglob("Recent"))
|
|
if not recent_candidates:
|
|
return {"status": "skipped", "message": "Recent directory not found"}
|
|
lnk_dir = str(recent_candidates[0])
|
|
|
|
output_file = "LNK_output.csv"
|
|
args = ["-d", lnk_dir, "--csv", str(self.output_path), "--csvf", output_file]
|
|
result = self.run_tool("LECmd", args)
|
|
result["output_file"] = str(self.output_path / output_file)
|
|
self.results["LNK"] = result
|
|
return result
|
|
|
|
def process_jump_lists(self, jl_dir: str = None) -> dict:
|
|
"""Parse Jump List files with JLECmd."""
|
|
if jl_dir is None:
|
|
auto_dest = list(self.evidence_path.rglob("AutomaticDestinations"))
|
|
if not auto_dest:
|
|
return {"status": "skipped", "message": "AutomaticDestinations not found"}
|
|
jl_dir = str(auto_dest[0])
|
|
|
|
output_file = "JumpLists_output.csv"
|
|
args = ["-d", jl_dir, "--csv", str(self.output_path), "--csvf", output_file]
|
|
result = self.run_tool("JLECmd", args)
|
|
result["output_file"] = str(self.output_path / output_file)
|
|
self.results["JumpLists"] = result
|
|
return result
|
|
|
|
def process_event_logs(self, evtx_dir: str = None) -> dict:
|
|
"""Parse Windows Event Logs with EvtxECmd."""
|
|
if evtx_dir is None:
|
|
logs_candidates = list(self.evidence_path.rglob("winevt"))
|
|
if logs_candidates:
|
|
evtx_dir = str(logs_candidates[0] / "Logs")
|
|
else:
|
|
evtx_files = list(self.evidence_path.rglob("*.evtx"))
|
|
if not evtx_files:
|
|
return {"status": "skipped", "message": "Event logs not found"}
|
|
evtx_dir = str(evtx_files[0].parent)
|
|
|
|
output_file = "EventLogs_output.csv"
|
|
args = ["-d", evtx_dir, "--csv", str(self.output_path), "--csvf", output_file]
|
|
result = self.run_tool("EvtxECmd", args)
|
|
result["output_file"] = str(self.output_path / output_file)
|
|
self.results["EventLogs"] = result
|
|
return result
|
|
|
|
def process_shellbags(self, registry_dir: str = None) -> dict:
|
|
"""Parse Shellbag artifacts with SBECmd."""
|
|
if registry_dir is None:
|
|
registry_dir = str(self.evidence_path)
|
|
|
|
output_file = "Shellbags_output.csv"
|
|
args = ["-d", registry_dir, "--csv", str(self.output_path), "--csvf", output_file]
|
|
result = self.run_tool("SBECmd", args)
|
|
result["output_file"] = str(self.output_path / output_file)
|
|
self.results["Shellbags"] = result
|
|
return result
|
|
|
|
def process_recycle_bin(self, recycle_dir: str = None) -> dict:
|
|
"""Parse Recycle Bin artifacts with RBCmd."""
|
|
if recycle_dir is None:
|
|
rb_candidates = list(self.evidence_path.rglob("$Recycle.Bin"))
|
|
if not rb_candidates:
|
|
return {"status": "skipped", "message": "$Recycle.Bin not found"}
|
|
recycle_dir = str(rb_candidates[0])
|
|
|
|
output_file = "RecycleBin_output.csv"
|
|
args = ["-d", recycle_dir, "--csv", str(self.output_path), "--csvf", output_file]
|
|
result = self.run_tool("RBCmd", args)
|
|
result["output_file"] = str(self.output_path / output_file)
|
|
self.results["RecycleBin"] = result
|
|
return result
|
|
|
|
def detect_timestomping(self, mft_csv_path: str) -> list:
|
|
"""Analyze MFT CSV output to detect timestomping indicators."""
|
|
timestomped = []
|
|
try:
|
|
with open(mft_csv_path, "r", encoding="utf-8-sig") as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
si_created = row.get("Created0x10", "")
|
|
fn_created = row.get("Created0x30", "")
|
|
if si_created and fn_created:
|
|
try:
|
|
si_dt = datetime.fromisoformat(si_created.replace("Z", "+00:00"))
|
|
fn_dt = datetime.fromisoformat(fn_created.replace("Z", "+00:00"))
|
|
if si_dt < fn_dt:
|
|
timestomped.append({
|
|
"file": row.get("FileName", "Unknown"),
|
|
"entry_number": row.get("EntryNumber", ""),
|
|
"si_created": si_created,
|
|
"fn_created": fn_created,
|
|
"indicator": "$SI Created before $FN Created"
|
|
})
|
|
except (ValueError, TypeError):
|
|
continue
|
|
except FileNotFoundError:
|
|
return [{"error": f"MFT CSV not found: {mft_csv_path}"}]
|
|
return timestomped
|
|
|
|
def process_all(self) -> dict:
|
|
"""Run all available EZ Tools processors against evidence."""
|
|
print("[*] Starting comprehensive EZ Tools processing...")
|
|
print(f"[*] Evidence path: {self.evidence_path}")
|
|
print(f"[*] Output path: {self.output_path}")
|
|
|
|
available = self.verify_tools()
|
|
print(f"[*] Available tools: {sum(v for v in available.values())}/{len(available)}")
|
|
|
|
processors = [
|
|
("MFT", self.process_mft),
|
|
("USN Journal", self.process_usn_journal),
|
|
("Prefetch", self.process_prefetch),
|
|
("LNK Files", self.process_lnk_files),
|
|
("Jump Lists", self.process_jump_lists),
|
|
("Event Logs", self.process_event_logs),
|
|
("Shellbags", self.process_shellbags),
|
|
("Recycle Bin", self.process_recycle_bin),
|
|
]
|
|
|
|
for name, processor in processors:
|
|
print(f"[*] Processing {name}...")
|
|
result = processor()
|
|
status = result.get("status", "unknown")
|
|
print(f" [{status.upper()}] {name}")
|
|
|
|
return self.results
|
|
|
|
def generate_report(self) -> str:
|
|
"""Generate a summary report of all processing results."""
|
|
report = {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"evidence_path": str(self.evidence_path),
|
|
"output_path": str(self.output_path),
|
|
"results": {}
|
|
}
|
|
|
|
for artifact, result in self.results.items():
|
|
report["results"][artifact] = {
|
|
"status": result.get("status", "unknown"),
|
|
"output_file": result.get("output_file", ""),
|
|
}
|
|
output_file = result.get("output_file", "")
|
|
if output_file and os.path.exists(output_file):
|
|
file_size = os.path.getsize(output_file)
|
|
report["results"][artifact]["output_size_bytes"] = file_size
|
|
with open(output_file, "rb") as f:
|
|
report["results"][artifact]["sha256"] = hashlib.sha256(f.read()).hexdigest()
|
|
|
|
report_path = self.output_path / "processing_report.json"
|
|
with open(report_path, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
|
|
print(f"\n[*] Report saved to: {report_path}")
|
|
return str(report_path)
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 4:
|
|
print("Usage: python process.py <ez_tools_path> <evidence_path> <output_path>")
|
|
print("Example: python process.py C:\\Tools\\EZTools C:\\Cases\\Evidence C:\\Cases\\Output")
|
|
sys.exit(1)
|
|
|
|
ez_tools_path = sys.argv[1]
|
|
evidence_path = sys.argv[2]
|
|
output_path = sys.argv[3]
|
|
|
|
processor = EZToolsProcessor(ez_tools_path, evidence_path, output_path)
|
|
processor.process_all()
|
|
processor.generate_report()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|