mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-14 03:15:16 +03:00
356 lines
13 KiB
Python
356 lines
13 KiB
Python
"""
|
|
Timesketch Timeline Builder Script
|
|
Automates creation, import, and analysis of forensic timelines in Timesketch.
|
|
"""
|
|
|
|
import json
|
|
import csv
|
|
import os
|
|
import subprocess
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
|
|
class TimesketchTimelineBuilder:
|
|
"""Builds and manages forensic timelines using Timesketch."""
|
|
|
|
PLASO_PARSER_SETS = {
|
|
"quick_triage": "winevtx,prefetch,chrome_history,firefox_history",
|
|
"windows_full": "winevtx,prefetch,amcache,shimcache,userassist,mft,winreg,recycler,lnk",
|
|
"linux_full": "syslog,utmp,bash_history,cron,dpkg,selinux",
|
|
"network_focused": "winevtx,syslog",
|
|
"cloud_focused": "jsonl",
|
|
}
|
|
|
|
SIGMA_CATEGORIES = {
|
|
"lateral_movement": [
|
|
"event_identifier:4624 AND LogonType:3",
|
|
"event_identifier:4624 AND LogonType:10",
|
|
"event_identifier:5140",
|
|
],
|
|
"privilege_escalation": [
|
|
"event_identifier:4672",
|
|
"event_identifier:4728",
|
|
"event_identifier:4732",
|
|
],
|
|
"execution": [
|
|
"event_identifier:4688",
|
|
"event_identifier:4104",
|
|
"data_type:windows:prefetch:execution",
|
|
],
|
|
"persistence": [
|
|
"event_identifier:7045",
|
|
"event_identifier:4698",
|
|
"data_type:windows:registry:key_value",
|
|
],
|
|
"credential_access": [
|
|
"event_identifier:4768",
|
|
"event_identifier:4769",
|
|
"event_identifier:4776",
|
|
],
|
|
}
|
|
|
|
def __init__(self, timesketch_url=None, output_dir="timeline_output"):
|
|
self.timesketch_url = timesketch_url or "https://localhost"
|
|
self.output_dir = Path(output_dir)
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
self.timelines = []
|
|
self.events = []
|
|
|
|
def process_evidence_with_plaso(self, evidence_path, output_plaso, parser_set="quick_triage"):
|
|
"""Run log2timeline (Plaso) to process evidence into timeline format."""
|
|
parsers = self.PLASO_PARSER_SETS.get(parser_set, parser_set)
|
|
|
|
cmd = [
|
|
"log2timeline.py",
|
|
"--parsers", parsers,
|
|
"--storage-file", str(output_plaso),
|
|
str(evidence_path),
|
|
]
|
|
|
|
print(f"[*] Running Plaso with parsers: {parsers}")
|
|
print(f"[*] Evidence: {evidence_path}")
|
|
print(f"[*] Output: {output_plaso}")
|
|
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)
|
|
if result.returncode == 0:
|
|
print("[+] Plaso processing completed successfully")
|
|
return True
|
|
else:
|
|
print(f"[!] Plaso error: {result.stderr[:500]}")
|
|
return False
|
|
except FileNotFoundError:
|
|
print("[!] log2timeline.py not found. Install Plaso: pip install plaso")
|
|
return False
|
|
except subprocess.TimeoutExpired:
|
|
print("[!] Plaso processing timed out after 1 hour")
|
|
return False
|
|
|
|
def convert_evtx_to_csv(self, evtx_dir, output_csv):
|
|
"""Convert Windows event logs to CSV format for direct Timesketch import."""
|
|
events = []
|
|
|
|
for evtx_file in Path(evtx_dir).glob("*.evtx"):
|
|
print(f"[*] Processing: {evtx_file.name}")
|
|
# This would use python-evtx library in practice
|
|
# Placeholder for EVTX parsing logic
|
|
|
|
# Write CSV in Timesketch format
|
|
fieldnames = ["message", "datetime", "timestamp_desc", "source_short", "hostname", "tag"]
|
|
|
|
with open(output_csv, "w", newline="", encoding="utf-8") as f:
|
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
writer.writeheader()
|
|
for event in events:
|
|
writer.writerow(event)
|
|
|
|
print(f"[+] Wrote {len(events)} events to {output_csv}")
|
|
return output_csv
|
|
|
|
def create_csv_timeline_from_logs(self, log_entries, timeline_name):
|
|
"""Create a Timesketch-compatible CSV from structured log entries."""
|
|
output_file = self.output_dir / f"{timeline_name}.csv"
|
|
|
|
fieldnames = [
|
|
"message", "datetime", "timestamp_desc",
|
|
"source_short", "hostname", "tag",
|
|
]
|
|
|
|
with open(output_file, "w", newline="", encoding="utf-8") as f:
|
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
writer.writeheader()
|
|
|
|
for entry in log_entries:
|
|
row = {
|
|
"message": entry.get("message", ""),
|
|
"datetime": entry.get("datetime", ""),
|
|
"timestamp_desc": entry.get("timestamp_desc", "Event Recorded"),
|
|
"source_short": entry.get("source", ""),
|
|
"hostname": entry.get("hostname", ""),
|
|
"tag": entry.get("tag", ""),
|
|
}
|
|
writer.writerow(row)
|
|
|
|
print(f"[+] Created timeline CSV: {output_file} ({len(log_entries)} events)")
|
|
self.timelines.append({"name": timeline_name, "file": str(output_file)})
|
|
return str(output_file)
|
|
|
|
def create_jsonl_timeline(self, events, timeline_name):
|
|
"""Create a Timesketch-compatible JSONL timeline."""
|
|
output_file = self.output_dir / f"{timeline_name}.jsonl"
|
|
|
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
for event in events:
|
|
jsonl_event = {
|
|
"message": event.get("message", ""),
|
|
"datetime": event.get("datetime", ""),
|
|
"timestamp_desc": event.get("timestamp_desc", "Event Recorded"),
|
|
"source_short": event.get("source", ""),
|
|
"hostname": event.get("hostname", ""),
|
|
}
|
|
# Include any additional fields
|
|
for key, value in event.items():
|
|
if key not in jsonl_event:
|
|
jsonl_event[key] = value
|
|
|
|
f.write(json.dumps(jsonl_event) + "\n")
|
|
|
|
print(f"[+] Created JSONL timeline: {output_file} ({len(events)} events)")
|
|
self.timelines.append({"name": timeline_name, "file": str(output_file)})
|
|
return str(output_file)
|
|
|
|
def import_to_timesketch(self, sketch_name, timeline_file, timeline_name):
|
|
"""Import timeline file into Timesketch using the CLI importer."""
|
|
cmd = [
|
|
"timesketch_importer",
|
|
"-s", sketch_name,
|
|
"-t", timeline_name,
|
|
str(timeline_file),
|
|
]
|
|
|
|
print(f"[*] Importing {timeline_name} into sketch '{sketch_name}'")
|
|
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
|
|
if result.returncode == 0:
|
|
print(f"[+] Successfully imported {timeline_name}")
|
|
return True
|
|
else:
|
|
print(f"[!] Import error: {result.stderr[:500]}")
|
|
return False
|
|
except FileNotFoundError:
|
|
print("[!] timesketch_importer not found. Install: pip install timesketch-import-client")
|
|
return False
|
|
|
|
def generate_search_queries(self, iocs=None):
|
|
"""Generate Timesketch search queries for common investigation patterns."""
|
|
queries = {}
|
|
|
|
# Standard investigation queries
|
|
queries["lateral_movement"] = {
|
|
"query": 'event_identifier:4624 AND xml_string:"LogonType\\">3"',
|
|
"description": "Network logons indicating lateral movement",
|
|
}
|
|
queries["rdp_sessions"] = {
|
|
"query": 'event_identifier:4624 AND xml_string:"LogonType\\">10"',
|
|
"description": "RDP logon sessions",
|
|
}
|
|
queries["powershell_execution"] = {
|
|
"query": "event_identifier:4104 OR event_identifier:4103",
|
|
"description": "PowerShell script block logging events",
|
|
}
|
|
queries["process_creation"] = {
|
|
"query": "event_identifier:4688",
|
|
"description": "New process creation events",
|
|
}
|
|
queries["service_installation"] = {
|
|
"query": "event_identifier:7045",
|
|
"description": "New service installations",
|
|
}
|
|
queries["scheduled_tasks"] = {
|
|
"query": "event_identifier:4698 OR event_identifier:4702",
|
|
"description": "Scheduled task creation and modification",
|
|
}
|
|
queries["privilege_escalation"] = {
|
|
"query": "event_identifier:4672",
|
|
"description": "Special privileges assigned to new logon",
|
|
}
|
|
queries["account_changes"] = {
|
|
"query": "event_identifier:4720 OR event_identifier:4722 OR event_identifier:4738",
|
|
"description": "User account creation and modification",
|
|
}
|
|
queries["group_membership"] = {
|
|
"query": "event_identifier:4728 OR event_identifier:4732 OR event_identifier:4756",
|
|
"description": "Security group membership changes",
|
|
}
|
|
queries["file_access"] = {
|
|
"query": 'data_type:"fs:stat"',
|
|
"description": "File system activity",
|
|
}
|
|
|
|
# IOC-specific queries
|
|
if iocs:
|
|
for ioc_type, values in iocs.items():
|
|
for value in values:
|
|
key = f"ioc_{ioc_type}_{value[:20]}"
|
|
queries[key] = {
|
|
"query": f'"{value}"',
|
|
"description": f"IOC search: {ioc_type} = {value}",
|
|
}
|
|
|
|
# Save queries
|
|
query_file = self.output_dir / "investigation_queries.json"
|
|
with open(query_file, "w") as f:
|
|
json.dump(queries, f, indent=2)
|
|
|
|
print(f"[+] Generated {len(queries)} search queries -> {query_file}")
|
|
return queries
|
|
|
|
def build_attack_narrative(self, findings):
|
|
"""Build structured attack narrative from investigation findings."""
|
|
narrative = {
|
|
"title": "Attack Timeline Narrative",
|
|
"generated": datetime.utcnow().isoformat(),
|
|
"phases": [],
|
|
}
|
|
|
|
attack_phases = [
|
|
"Initial Access",
|
|
"Execution",
|
|
"Persistence",
|
|
"Privilege Escalation",
|
|
"Defense Evasion",
|
|
"Credential Access",
|
|
"Discovery",
|
|
"Lateral Movement",
|
|
"Collection",
|
|
"Exfiltration",
|
|
"Impact",
|
|
]
|
|
|
|
for phase in attack_phases:
|
|
phase_findings = [f for f in findings if f.get("mitre_tactic") == phase]
|
|
if phase_findings:
|
|
narrative["phases"].append({
|
|
"tactic": phase,
|
|
"events": sorted(phase_findings, key=lambda x: x.get("datetime", "")),
|
|
})
|
|
|
|
narrative_file = self.output_dir / "attack_narrative.json"
|
|
with open(narrative_file, "w") as f:
|
|
json.dump(narrative, f, indent=2, default=str)
|
|
|
|
print(f"[+] Attack narrative saved to {narrative_file}")
|
|
return narrative
|
|
|
|
def generate_timeline_report(self):
|
|
"""Generate summary report of all timelines and analysis."""
|
|
report = {
|
|
"report_title": "Forensic Timeline Analysis Report",
|
|
"generated": datetime.utcnow().isoformat(),
|
|
"timelines_processed": len(self.timelines),
|
|
"timelines": self.timelines,
|
|
"total_events": len(self.events),
|
|
"analysis_queries": str(self.output_dir / "investigation_queries.json"),
|
|
}
|
|
|
|
report_file = self.output_dir / "timeline_report.json"
|
|
with open(report_file, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
|
|
print(f"\n[+] Timeline report saved to {report_file}")
|
|
return report
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Timesketch Timeline Builder - Forensic Timeline Creation Tool"
|
|
)
|
|
parser.add_argument(
|
|
"--evidence", "-e",
|
|
help="Path to evidence directory or disk image",
|
|
)
|
|
parser.add_argument(
|
|
"--parsers", "-p",
|
|
default="quick_triage",
|
|
choices=["quick_triage", "windows_full", "linux_full", "network_focused"],
|
|
help="Plaso parser set to use",
|
|
)
|
|
parser.add_argument(
|
|
"--sketch", "-s",
|
|
default="Investigation",
|
|
help="Timesketch sketch name",
|
|
)
|
|
parser.add_argument(
|
|
"--output", "-o",
|
|
default="timeline_output",
|
|
help="Output directory",
|
|
)
|
|
parser.add_argument(
|
|
"--iocs",
|
|
help="JSON file with IOCs to search for",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
builder = TimesketchTimelineBuilder(output_dir=args.output)
|
|
|
|
if args.evidence:
|
|
plaso_output = Path(args.output) / "evidence.plaso"
|
|
builder.process_evidence_with_plaso(args.evidence, plaso_output, args.parsers)
|
|
|
|
iocs = None
|
|
if args.iocs:
|
|
with open(args.iocs) as f:
|
|
iocs = json.load(f)
|
|
|
|
builder.generate_search_queries(iocs=iocs)
|
|
builder.generate_timeline_report()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|