mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-25 05:50:57 +03:00
207 lines
8.0 KiB
Python
207 lines
8.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Timestomping detection agent for NTFS MFT analysis.
|
|
|
|
Detects MITRE T1070.006 (Timestomping) by comparing $STANDARD_INFORMATION
|
|
and $FILE_NAME timestamps in NTFS Master File Table entries. Identifies
|
|
anomalous nanosecond patterns and temporal inconsistencies.
|
|
"""
|
|
|
|
import argparse
|
|
import csv
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
import datetime
|
|
|
|
|
|
TIMESTOMP_INDICATORS = {
|
|
"zero_nanoseconds": "Nanosecond field is exactly 0000000 (common in timestomping tools)",
|
|
"si_before_fn": "$STANDARD_INFORMATION created before $FILE_NAME created",
|
|
"future_timestamp": "Timestamp is in the future",
|
|
"pre_os_timestamp": "Timestamp predates the operating system install",
|
|
"round_seconds": "Timestamp has perfectly round seconds (no fractional component)",
|
|
}
|
|
|
|
|
|
def parse_mft_csv(csv_path):
|
|
"""Parse analyzeMFT CSV output for timestamp analysis."""
|
|
entries = []
|
|
try:
|
|
with open(csv_path, "r", encoding="utf-8", errors="replace") as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
entry = {
|
|
"record_number": row.get("Record Number", ""),
|
|
"filename": row.get("Filename", row.get("Good", "")),
|
|
"si_created": row.get("SI Created", row.get("STD_INFO Creation date", "")),
|
|
"si_modified": row.get("SI Modified", row.get("STD_INFO Modification date", "")),
|
|
"si_accessed": row.get("SI Accessed", row.get("STD_INFO Access date", "")),
|
|
"si_entry_modified": row.get("SI Entry Modified", row.get("STD_INFO Entry date", "")),
|
|
"fn_created": row.get("FN Created", row.get("FN Creation date", "")),
|
|
"fn_modified": row.get("FN Modified", row.get("FN Modification date", "")),
|
|
"fn_accessed": row.get("FN Accessed", row.get("FN Access date", "")),
|
|
"fn_entry_modified": row.get("FN Entry Modified", row.get("FN Entry date", "")),
|
|
"in_use": row.get("Active", row.get("In Use", "")).lower() in ("true", "1", "yes"),
|
|
}
|
|
if entry["filename"]:
|
|
entries.append(entry)
|
|
except FileNotFoundError:
|
|
return {"error": f"File not found: {csv_path}"}
|
|
return entries
|
|
|
|
|
|
def parse_timestamp(ts_str):
|
|
"""Parse various timestamp formats to datetime."""
|
|
if not ts_str or ts_str in ("", "NoFNDate", "N/A"):
|
|
return None
|
|
formats = [
|
|
"%Y-%m-%d %H:%M:%S.%f",
|
|
"%Y-%m-%d %H:%M:%S",
|
|
"%m/%d/%Y %H:%M:%S",
|
|
"%Y-%m-%dT%H:%M:%S.%f",
|
|
"%Y-%m-%dT%H:%M:%S",
|
|
]
|
|
for fmt in formats:
|
|
try:
|
|
return datetime.datetime.strptime(ts_str.strip(), fmt)
|
|
except ValueError:
|
|
continue
|
|
return None
|
|
|
|
|
|
def detect_timestomping(entries, os_install_date=None):
|
|
"""Analyze MFT entries for timestomping indicators."""
|
|
if os_install_date is None:
|
|
os_install_date = datetime.datetime(2020, 1, 1)
|
|
now = datetime.datetime.now()
|
|
findings = []
|
|
|
|
for entry in entries:
|
|
if isinstance(entry, dict) and "error" in entry:
|
|
continue
|
|
reasons = []
|
|
si_created = parse_timestamp(entry.get("si_created", ""))
|
|
fn_created = parse_timestamp(entry.get("fn_created", ""))
|
|
|
|
# Check zero nanoseconds
|
|
si_str = entry.get("si_created", "")
|
|
if ".0000000" in si_str or (si_str and re.search(r"\.\d{6}0$", si_str)):
|
|
reasons.append("zero_nanoseconds")
|
|
|
|
# Check SI before FN (most reliable indicator)
|
|
if si_created and fn_created:
|
|
if si_created < fn_created - datetime.timedelta(seconds=2):
|
|
reasons.append("si_before_fn")
|
|
|
|
# Check future timestamps
|
|
if si_created and si_created > now + datetime.timedelta(days=1):
|
|
reasons.append("future_timestamp")
|
|
|
|
# Check pre-OS timestamps
|
|
if si_created and si_created < os_install_date:
|
|
if fn_created and fn_created >= os_install_date:
|
|
reasons.append("pre_os_timestamp")
|
|
|
|
# Check perfectly round timestamps
|
|
if si_created and si_created.microsecond == 0:
|
|
si_mod = parse_timestamp(entry.get("si_modified", ""))
|
|
if si_mod and si_mod.microsecond == 0:
|
|
reasons.append("round_seconds")
|
|
|
|
if reasons:
|
|
findings.append({
|
|
"filename": entry.get("filename", ""),
|
|
"record_number": entry.get("record_number", ""),
|
|
"si_created": entry.get("si_created", ""),
|
|
"fn_created": entry.get("fn_created", ""),
|
|
"indicators": reasons,
|
|
"descriptions": [TIMESTOMP_INDICATORS.get(r, r) for r in reasons],
|
|
"confidence": "HIGH" if "si_before_fn" in reasons else "MEDIUM",
|
|
"mitre": "T1070.006",
|
|
})
|
|
|
|
return findings
|
|
|
|
|
|
def generate_report(entries, findings):
|
|
"""Generate timestomping analysis report."""
|
|
return {
|
|
"timestamp": datetime.datetime.utcnow().isoformat() + "Z",
|
|
"total_mft_entries": len(entries) if isinstance(entries, list) else 0,
|
|
"total_findings": len(findings),
|
|
"high_confidence": sum(1 for f in findings if f.get("confidence") == "HIGH"),
|
|
"medium_confidence": sum(1 for f in findings if f.get("confidence") == "MEDIUM"),
|
|
"indicator_counts": dict(collections.Counter(
|
|
ind for f in findings for ind in f.get("indicators", [])
|
|
)) if findings else {},
|
|
"mitre_technique": "T1070.006 - Indicator Removal: Timestomp",
|
|
}
|
|
|
|
|
|
# Need collections for generate_report
|
|
import collections
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="NTFS timestomping detection via MFT analysis (MITRE T1070.006)"
|
|
)
|
|
parser.add_argument("mft_csv", nargs="?", help="Path to analyzeMFT CSV output")
|
|
parser.add_argument("--os-install", help="OS install date (YYYY-MM-DD) for baseline")
|
|
parser.add_argument("--high-only", action="store_true", help="Show only HIGH confidence findings")
|
|
parser.add_argument("--output", "-o", help="Output JSON report path")
|
|
args = parser.parse_args()
|
|
|
|
print("[*] Timestomping Detection Agent (MITRE T1070.006)")
|
|
print("[*] Compares $STANDARD_INFORMATION vs $FILE_NAME timestamps")
|
|
|
|
if not args.mft_csv:
|
|
print("\n[DEMO] Usage:")
|
|
print(" 1. Extract MFT: ftkimager /path/to/image mft_output")
|
|
print(" 2. Parse MFT: analyzeMFT.py -f $MFT -o mft.csv")
|
|
print(" 3. Detect: python agent.py mft.csv [--os-install 2022-01-15]")
|
|
print("\n Indicators detected:")
|
|
for name, desc in TIMESTOMP_INDICATORS.items():
|
|
print(f" - {name}: {desc}")
|
|
print(json.dumps({"demo": True, "indicators": len(TIMESTOMP_INDICATORS)}, indent=2))
|
|
sys.exit(0)
|
|
|
|
os_date = None
|
|
if args.os_install:
|
|
try:
|
|
os_date = datetime.datetime.strptime(args.os_install, "%Y-%m-%d")
|
|
except ValueError:
|
|
print(f"[!] Invalid date format: {args.os_install}")
|
|
|
|
entries = parse_mft_csv(args.mft_csv)
|
|
if isinstance(entries, dict) and "error" in entries:
|
|
print(f"[!] {entries['error']}")
|
|
sys.exit(1)
|
|
|
|
findings = detect_timestomping(entries, os_date)
|
|
if args.high_only:
|
|
findings = [f for f in findings if f.get("confidence") == "HIGH"]
|
|
|
|
report = generate_report(entries, findings)
|
|
print(f"[*] MFT entries analyzed: {report['total_mft_entries']}")
|
|
print(f"[*] Timestomping findings: {report['total_findings']}")
|
|
print(f" HIGH confidence: {report['high_confidence']}")
|
|
print(f" MEDIUM confidence: {report['medium_confidence']}")
|
|
|
|
for f in findings[:20]:
|
|
print(f" [{f['confidence']}] {f['filename']}")
|
|
for desc in f["descriptions"]:
|
|
print(f" - {desc}")
|
|
|
|
if args.output:
|
|
full_report = {"summary": report, "findings": findings}
|
|
with open(args.output, "w") as f:
|
|
json.dump(full_report, f, indent=2)
|
|
|
|
print(json.dumps(report, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|