Add 30 new production-grade cybersecurity skills: AI security, supply chain, firmware, cloud-native, compliance, deception, crypto, threat hunting, purple team, OT, privacy

This commit is contained in:
mukul975
2026-03-19 19:14:23 +01:00
parent d43cc7a766
commit d833f0eab9
125 changed files with 47874 additions and 334 deletions
@@ -0,0 +1,348 @@
#!/usr/bin/env python3
"""DCOM Lateral Movement Detection Agent - Hunts for DCOM object abuse via Sysmon event correlation."""
import json
import logging
import argparse
import os
import sys
import subprocess
from collections import defaultdict
from datetime import datetime, timedelta
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
# DCOM COM object CLSIDs used for lateral movement
DCOM_CLSIDS = {
"{49B2791A-B1AE-4C90-9B8E-E860BA07F889}": "MMC20.Application",
"{9BA05972-F6A8-11CF-A442-00A0C90A8F39}": "ShellWindows",
"{C08AFD90-F2A1-11D1-8455-00A0C91F3880}": "ShellBrowserWindow",
"{00024500-0000-0000-C000-000000000046}": "Excel.Application",
"{0006F03A-0000-0000-C000-000000000046}": "Outlook.Application",
}
DCOM_PARENT_PROCESSES = ["mmc.exe", "dllhost.exe", "explorer.exe"]
SUSPICIOUS_CHILDREN = [
"cmd.exe", "powershell.exe", "pwsh.exe", "wscript.exe",
"cscript.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe",
"certutil.exe", "bitsadmin.exe",
]
SYSMON_NS = "http://schemas.microsoft.com/win/2004/08/events/event"
EVTX_PARSE_TIMEOUT = 300 # seconds
def parse_evtx_records(evtx_path):
"""Parse Sysmon EVTX file into structured events using python-evtx."""
try:
from Evtx.Evtx import FileHeader
from lxml import etree
except ImportError:
logger.error("Required packages missing. Install: pip install python-evtx lxml")
sys.exit(1)
events = []
ns = {"evt": SYSMON_NS}
with open(evtx_path, "rb") as f:
fh = FileHeader(f)
for record in fh.records():
try:
xml = record.xml()
root = etree.fromstring(xml.encode("utf-8"))
event_id_elem = root.find(".//evt:System/evt:EventID", ns)
if event_id_elem is None:
continue
eid = int(event_id_elem.text)
if eid not in (1, 3, 7):
continue
data = {}
for elem in root.findall(".//evt:EventData/evt:Data", ns):
data[elem.get("Name", "")] = elem.text or ""
time_elem = root.find(".//evt:System/evt:TimeCreated", ns)
timestamp = time_elem.get("SystemTime", "") if time_elem is not None else ""
comp_elem = root.find(".//evt:System/evt:Computer", ns)
computer = comp_elem.text if comp_elem is not None else ""
data["EventID"] = eid
data["TimeCreated"] = timestamp
data["Computer"] = computer
events.append(data)
except Exception:
continue
logger.info("Parsed %d Sysmon events (EID 1,3,7) from %s", len(events), evtx_path)
return events
def detect_mmc20_lateral(events):
"""Detect MMC20.Application DCOM lateral movement: mmc.exe spawning suspicious children."""
findings = []
for ev in events:
if ev.get("EventID") != 1:
continue
parent = ev.get("ParentImage", "").lower()
image = ev.get("Image", "").lower()
if "mmc.exe" not in parent:
continue
if not any(child in image for child in SUSPICIOUS_CHILDREN):
continue
findings.append({
"detection": "MMC20.Application DCOM Lateral Movement",
"severity": "HIGH",
"mitre": "T1021.003",
"timestamp": ev.get("TimeCreated"),
"computer": ev.get("Computer"),
"parent_image": ev.get("ParentImage"),
"parent_cmdline": ev.get("ParentCommandLine"),
"child_image": ev.get("Image"),
"child_cmdline": ev.get("CommandLine"),
"user": ev.get("User"),
"clsid": "{49B2791A-B1AE-4C90-9B8E-E860BA07F889}",
})
logger.info("MMC20 detections: %d", len(findings))
return findings
def detect_shell_dcom_lateral(events):
"""Detect ShellWindows/ShellBrowserWindow: explorer.exe spawning cmd/powershell."""
findings = []
for ev in events:
if ev.get("EventID") != 1:
continue
parent = ev.get("ParentImage", "").lower()
image = ev.get("Image", "").lower()
if "explorer.exe" not in parent:
continue
if not any(child in image for child in ["cmd.exe", "powershell.exe", "pwsh.exe",
"mshta.exe", "wscript.exe", "cscript.exe"]):
continue
findings.append({
"detection": "ShellWindows/ShellBrowserWindow DCOM Lateral Movement",
"severity": "MEDIUM",
"mitre": "T1021.003",
"timestamp": ev.get("TimeCreated"),
"computer": ev.get("Computer"),
"parent_image": ev.get("ParentImage"),
"child_image": ev.get("Image"),
"child_cmdline": ev.get("CommandLine"),
"user": ev.get("User"),
"clsid": "{9BA05972} or {C08AFD90}",
})
logger.info("ShellWindows/ShellBrowserWindow detections: %d", len(findings))
return findings
def detect_dllhost_lateral(events):
"""Detect DCOM via dllhost.exe spawning suspicious children."""
findings = []
for ev in events:
if ev.get("EventID") != 1:
continue
parent = ev.get("ParentImage", "").lower()
image = ev.get("Image", "").lower()
if "dllhost.exe" not in parent:
continue
if not any(child in image for child in SUSPICIOUS_CHILDREN):
continue
parent_cmdline = ev.get("ParentCommandLine", "")
clsid = "Unknown"
if "/Processid:" in parent_cmdline:
start = parent_cmdline.find("/Processid:") + len("/Processid:")
clsid_raw = parent_cmdline[start:].strip().strip("{}")
clsid = "{" + clsid_raw + "}"
dcom_name = DCOM_CLSIDS.get(clsid.upper(), "Unknown DCOM Object")
findings.append({
"detection": f"DCOM via dllhost.exe ({dcom_name})",
"severity": "HIGH",
"mitre": "T1021.003",
"timestamp": ev.get("TimeCreated"),
"computer": ev.get("Computer"),
"parent_image": ev.get("ParentImage"),
"parent_cmdline": parent_cmdline,
"child_image": ev.get("Image"),
"child_cmdline": ev.get("CommandLine"),
"user": ev.get("User"),
"clsid": clsid,
"dcom_object": dcom_name,
})
logger.info("dllhost.exe DCOM detections: %d", len(findings))
return findings
def detect_rpc_connections(events):
"""Detect inbound RPC endpoint mapper connections (port 135) from Sysmon Event ID 3."""
rpc_connections = []
for ev in events:
if ev.get("EventID") != 3:
continue
dest_port = ev.get("DestinationPort", "")
initiated = ev.get("Initiated", "").lower()
if dest_port == "135" and initiated == "false":
rpc_connections.append({
"detection": "Inbound RPC Connection (DCOM Prerequisite)",
"severity": "LOW",
"timestamp": ev.get("TimeCreated"),
"computer": ev.get("Computer"),
"source_ip": ev.get("SourceIp"),
"dest_ip": ev.get("DestinationIp"),
"dest_port": dest_port,
"image": ev.get("Image"),
})
logger.info("Inbound RPC (port 135) connections: %d", len(rpc_connections))
return rpc_connections
def correlate_rpc_with_process(rpc_events, process_findings, window_seconds=60):
"""Correlate RPC connections with DCOM process creation for high-confidence detections."""
correlated = []
for proc in process_findings:
proc_time_str = proc.get("timestamp", "")
proc_computer = proc.get("computer", "")
if not proc_time_str:
continue
try:
proc_dt = datetime.fromisoformat(proc_time_str.replace("Z", "+00:00"))
except (ValueError, TypeError):
continue
for rpc in rpc_events:
rpc_time_str = rpc.get("timestamp", "")
rpc_computer = rpc.get("computer", "")
if not rpc_time_str or rpc_computer != proc_computer:
continue
try:
rpc_dt = datetime.fromisoformat(rpc_time_str.replace("Z", "+00:00"))
except (ValueError, TypeError):
continue
delta = (proc_dt - rpc_dt).total_seconds()
if 0 <= delta <= window_seconds:
correlated.append({
"detection": "CORRELATED: RPC Connection -> DCOM Process Creation",
"severity": "CRITICAL",
"mitre": "T1021.003",
"computer": proc_computer,
"source_ip": rpc.get("source_ip"),
"rpc_time": rpc_time_str,
"process_time": proc_time_str,
"time_delta_seconds": round(delta, 2),
"dcom_detection": proc.get("detection"),
"child_image": proc.get("child_image"),
"child_cmdline": proc.get("child_cmdline"),
"user": proc.get("user"),
})
break
logger.info("Correlated RPC->Process chains: %d", len(correlated))
return correlated
def audit_dcom_config():
"""Audit local DCOM configuration for high-risk COM objects (Windows only)."""
if sys.platform != "win32":
logger.info("DCOM config audit only available on Windows")
return []
audit_results = []
for clsid, name in DCOM_CLSIDS.items():
try:
result = subprocess.run(
["reg", "query", f"HKLM\\SOFTWARE\\Classes\\CLSID\\{clsid}"],
capture_output=True, text=True, timeout=10
)
exists = result.returncode == 0
audit_results.append({
"clsid": clsid,
"name": name,
"registered": exists,
"risk": "HIGH" if exists else "N/A",
})
except subprocess.TimeoutExpired:
audit_results.append({"clsid": clsid, "name": name, "registered": "TIMEOUT", "risk": "UNKNOWN"})
except Exception as e:
audit_results.append({"clsid": clsid, "name": name, "registered": f"ERROR: {e}", "risk": "UNKNOWN"})
# Check if DCOM is enabled
try:
result = subprocess.run(
["reg", "query", "HKLM\\SOFTWARE\\Microsoft\\Ole", "/v", "EnableDCOM"],
capture_output=True, text=True, timeout=10
)
dcom_enabled = "Y" in result.stdout if result.returncode == 0 else "UNKNOWN"
audit_results.append({"check": "DCOM Enabled", "value": dcom_enabled,
"risk": "HIGH" if dcom_enabled == "Y" else "LOW"})
except (subprocess.TimeoutExpired, Exception):
pass
return audit_results
def generate_report(all_findings, dcom_audit, output_path):
"""Generate JSON detection report."""
report = {
"scan_timestamp": datetime.utcnow().isoformat() + "Z",
"mitre_technique": "T1021.003",
"summary": {
"total_findings": len(all_findings),
"critical": len([f for f in all_findings if f.get("severity") == "CRITICAL"]),
"high": len([f for f in all_findings if f.get("severity") == "HIGH"]),
"medium": len([f for f in all_findings if f.get("severity") == "MEDIUM"]),
"low": len([f for f in all_findings if f.get("severity") == "LOW"]),
},
"findings": all_findings,
"dcom_config_audit": dcom_audit,
}
with open(output_path, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("Report saved to %s", output_path)
s = report["summary"]
print(f"\nDCOM LATERAL MOVEMENT DETECTION REPORT")
print(f" Total findings: {s['total_findings']}")
print(f" Critical: {s['critical']}, High: {s['high']}, Medium: {s['medium']}, Low: {s['low']}")
if s["critical"] > 0:
print(" [!!!] CRITICAL: Correlated RPC + process creation chains detected")
return report
def main():
parser = argparse.ArgumentParser(
description="DCOM Lateral Movement Detection Agent (T1021.003)"
)
parser.add_argument("--evtx", required=True, help="Path to Sysmon .evtx log file")
parser.add_argument("--output", "-o", default="dcom_detection_report.json",
help="Output JSON report path (default: dcom_detection_report.json)")
parser.add_argument("--correlation-window", type=int, default=60,
help="Seconds window for RPC-to-process correlation (default: 60)")
parser.add_argument("--audit-dcom", action="store_true",
help="Audit local DCOM object registration (Windows only)")
parser.add_argument("--verbose", "-v", action="store_true", help="Enable debug logging")
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
if not os.path.isfile(args.evtx):
logger.error("EVTX file not found: %s", args.evtx)
sys.exit(1)
logger.info("Parsing Sysmon events from: %s", args.evtx)
events = parse_evtx_records(args.evtx)
mmc_findings = detect_mmc20_lateral(events)
shell_findings = detect_shell_dcom_lateral(events)
dllhost_findings = detect_dllhost_lateral(events)
rpc_connections = detect_rpc_connections(events)
all_process_findings = mmc_findings + shell_findings + dllhost_findings
correlated = correlate_rpc_with_process(
rpc_connections, all_process_findings, args.correlation_window
)
all_findings = correlated + all_process_findings
all_findings.sort(key=lambda x: x.get("severity", ""), reverse=True)
dcom_audit = audit_dcom_config() if args.audit_dcom else []
generate_report(all_findings, dcom_audit, args.output)
if __name__ == "__main__":
main()
@@ -0,0 +1,495 @@
#!/usr/bin/env python3
"""
DCOM Lateral Movement Detection Script
Parses Windows Security and Sysmon event logs to detect DCOM-based lateral movement
via MMC20.Application, ShellWindows, and ShellBrowserWindow COM object abuse.
MITRE ATT&CK: T1021.003 (Remote Services: Distributed Component Object Model)
Usage:
python detect_dcom_lateral_movement.py --evtx <path_to_sysmon_evtx>
python detect_dcom_lateral_movement.py --evtx <sysmon.evtx> --security <security.evtx>
python detect_dcom_lateral_movement.py --evtx <sysmon.evtx> --json --output results.json
Requirements:
pip install python-evtx lxml
"""
import argparse
import json
import sys
import os
from datetime import datetime, timedelta
from collections import defaultdict
try:
import Evtx.Evtx as evtx
import Evtx.Views as evtx_views
from lxml import etree
except ImportError:
print("[!] Required packages not found. Install with: pip install python-evtx lxml")
sys.exit(1)
# DCOM-related COM object CLSIDs
DCOM_CLSIDS = {
"{49B2791A-B1AE-4C90-9B8E-E860BA07F889}": "MMC20.Application",
"{9BA05972-F6A8-11CF-A442-00A0C90A8F39}": "ShellWindows",
"{C08AFD90-F2A1-11D1-8455-00A0C91F3880}": "ShellBrowserWindow",
"{00024500-0000-0000-C000-000000000046}": "Excel.Application",
"{0006F03A-0000-0000-C000-000000000046}": "Outlook.Application",
}
# Suspicious child processes when spawned by DCOM parent processes
SUSPICIOUS_CHILDREN = [
"cmd.exe", "powershell.exe", "pwsh.exe", "wscript.exe",
"cscript.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe",
"certutil.exe", "bitsadmin.exe", "msbuild.exe",
]
# DCOM parent processes that spawn child processes during lateral movement
DCOM_PARENTS = ["mmc.exe", "dllhost.exe", "explorer.exe", "svchost.exe"]
SYSMON_NS = "http://schemas.microsoft.com/win/2004/08/events/event"
def parse_sysmon_event(record_xml):
"""Parse a Sysmon event record XML into a dictionary."""
try:
root = etree.fromstring(record_xml)
except etree.XMLSyntaxError:
return None
ns = {"e": SYSMON_NS}
event = {}
system = root.find(".//e:System", ns)
if system is not None:
event_id_elem = system.find("e:EventID", ns)
event["EventID"] = int(event_id_elem.text) if event_id_elem is not None else 0
time_elem = system.find("e:TimeCreated", ns)
if time_elem is not None:
event["TimeCreated"] = time_elem.get("SystemTime", "")
computer_elem = system.find("e:Computer", ns)
event["Computer"] = computer_elem.text if computer_elem is not None else ""
event_data = root.find(".//e:EventData", ns)
if event_data is not None:
for data in event_data.findall("e:Data", ns):
name = data.get("Name", "")
value = data.text or ""
event[name] = value
return event
def is_dcom_parent(image_path):
"""Check if the process image is a known DCOM parent."""
if not image_path:
return False
image_lower = image_path.lower()
return any(parent in image_lower for parent in DCOM_PARENTS)
def is_suspicious_child(image_path):
"""Check if the process image is a suspicious child for DCOM context."""
if not image_path:
return False
image_lower = image_path.lower()
return any(child in image_lower for child in SUSPICIOUS_CHILDREN)
def check_dcomllaunch_parent(command_line):
"""Check if the parent command line indicates DcomLaunch service."""
if not command_line:
return False
return "dcomlaunch" in command_line.lower()
def detect_dcom_process_creation(events):
"""
Detect DCOM lateral movement via Sysmon Event ID 1 (Process Create).
Looks for DCOM parent processes spawning suspicious children.
"""
findings = []
for event in events:
if event.get("EventID") != 1:
continue
parent_image = event.get("ParentImage", "")
image = event.get("Image", "")
parent_cmdline = event.get("ParentCommandLine", "")
cmdline = event.get("CommandLine", "")
user = event.get("User", "")
time_created = event.get("TimeCreated", "")
computer = event.get("Computer", "")
# Pattern 1: mmc.exe spawning suspicious child (MMC20.Application)
if "mmc.exe" in parent_image.lower() and is_suspicious_child(image):
findings.append({
"timestamp": time_created,
"computer": computer,
"detection_type": "MMC20.Application DCOM Lateral Movement",
"dcom_object": "MMC20.Application",
"clsid": "{49B2791A-B1AE-4C90-9B8E-E860BA07F889}",
"parent_image": parent_image,
"parent_commandline": parent_cmdline,
"child_image": image,
"child_commandline": cmdline,
"user": user,
"severity": "HIGH",
"mitre": "T1021.003",
})
# Pattern 2: DcomLaunch svchost spawning dllhost or mmc
if check_dcomllaunch_parent(parent_cmdline) and is_suspicious_child(image):
findings.append({
"timestamp": time_created,
"computer": computer,
"detection_type": "DcomLaunch Service Spawning Suspicious Process",
"dcom_object": "Unknown (DcomLaunch)",
"clsid": "N/A",
"parent_image": parent_image,
"parent_commandline": parent_cmdline,
"child_image": image,
"child_commandline": cmdline,
"user": user,
"severity": "HIGH",
"mitre": "T1021.003",
})
# Pattern 3: explorer.exe spawning cmd/powershell on servers
# (ShellWindows/ShellBrowserWindow)
if "explorer.exe" in parent_image.lower() and is_suspicious_child(image):
# Check if this might be interactive (less suspicious) or DCOM (more suspicious)
findings.append({
"timestamp": time_created,
"computer": computer,
"detection_type": "ShellWindows/ShellBrowserWindow DCOM Lateral Movement (Requires Correlation)",
"dcom_object": "ShellWindows or ShellBrowserWindow",
"clsid": "{9BA05972-F6A8-11CF-A442-00A0C90A8F39} or {C08AFD90-F2A1-11D1-8455-00A0C91F3880}",
"parent_image": parent_image,
"parent_commandline": parent_cmdline,
"child_image": image,
"child_commandline": cmdline,
"user": user,
"severity": "MEDIUM",
"mitre": "T1021.003",
})
# Pattern 4: dllhost.exe spawning suspicious children
if "dllhost.exe" in parent_image.lower() and is_suspicious_child(image):
# Extract CLSID from dllhost command line if present
detected_clsid = "Unknown"
if "/Processid:" in parent_cmdline:
clsid_start = parent_cmdline.find("/Processid:") + len("/Processid:")
detected_clsid = parent_cmdline[clsid_start:].strip().strip("{}")
detected_clsid = "{" + detected_clsid + "}"
dcom_name = DCOM_CLSIDS.get(detected_clsid.upper(), "Unknown DCOM Object")
findings.append({
"timestamp": time_created,
"computer": computer,
"detection_type": "DCOM Object Execution via dllhost.exe",
"dcom_object": dcom_name,
"clsid": detected_clsid,
"parent_image": parent_image,
"parent_commandline": parent_cmdline,
"child_image": image,
"child_commandline": cmdline,
"user": user,
"severity": "HIGH",
"mitre": "T1021.003",
})
return findings
def detect_dcom_network_connections(events):
"""
Detect DCOM-related network connections via Sysmon Event ID 3.
Looks for inbound RPC connections (port 135) to DCOM processes.
"""
findings = []
for event in events:
if event.get("EventID") != 3:
continue
image = event.get("Image", "")
dest_port = event.get("DestinationPort", "")
source_ip = event.get("SourceIp", "")
dest_ip = event.get("DestinationIp", "")
initiated = event.get("Initiated", "")
time_created = event.get("TimeCreated", "")
computer = event.get("Computer", "")
# Inbound RPC connection (port 135) -- DCOM always starts here
if dest_port == "135" and initiated.lower() == "false":
findings.append({
"timestamp": time_created,
"computer": computer,
"detection_type": "Inbound RPC Endpoint Mapper Connection",
"source_ip": source_ip,
"destination_ip": dest_ip,
"destination_port": dest_port,
"process_image": image,
"severity": "MEDIUM",
"mitre": "T1021.003",
"note": "DCOM communication begins with RPC endpoint mapper query on port 135",
})
# DCOM process making outbound connection on high port (dynamic RPC)
if is_dcom_parent(image) and dest_port and int(dest_port) > 49151:
findings.append({
"timestamp": time_created,
"computer": computer,
"detection_type": "DCOM Process Dynamic RPC Connection",
"source_ip": source_ip,
"destination_ip": dest_ip,
"destination_port": dest_port,
"process_image": image,
"severity": "LOW",
"mitre": "T1021.003",
"note": "DCOM process communicating on dynamic RPC port range",
})
return findings
def correlate_network_and_process(process_findings, network_findings, window_seconds=60):
"""
Correlate network connections with process creation events.
A network connection to port 135 followed by DCOM process creation
within the time window is a strong indicator of lateral movement.
"""
correlated = []
for proc in process_findings:
proc_time = proc.get("timestamp", "")
proc_computer = proc.get("computer", "")
if not proc_time:
continue
try:
proc_dt = datetime.fromisoformat(proc_time.replace("Z", "+00:00"))
except (ValueError, TypeError):
continue
for net in network_findings:
net_time = net.get("timestamp", "")
net_computer = net.get("computer", "")
if not net_time or net_computer != proc_computer:
continue
try:
net_dt = datetime.fromisoformat(net_time.replace("Z", "+00:00"))
except (ValueError, TypeError):
continue
time_diff = abs((proc_dt - net_dt).total_seconds())
if time_diff <= window_seconds and net_dt <= proc_dt:
correlated.append({
"correlation_type": "DCOM Lateral Movement Chain",
"severity": "CRITICAL",
"mitre": "T1021.003",
"computer": proc_computer,
"network_event": {
"timestamp": net_time,
"source_ip": net.get("source_ip"),
"destination_port": net.get("destination_port"),
},
"process_event": {
"timestamp": proc_time,
"dcom_object": proc.get("dcom_object"),
"parent_image": proc.get("parent_image"),
"child_image": proc.get("child_image"),
"child_commandline": proc.get("child_commandline"),
"user": proc.get("user"),
},
"time_delta_seconds": round(time_diff, 2),
})
return correlated
def parse_evtx_file(filepath):
"""Parse a .evtx file and return list of parsed events."""
events = []
try:
with evtx.Evtx(filepath) as log:
for record in log.records():
try:
event = parse_sysmon_event(record.xml())
if event:
events.append(event)
except Exception:
continue
except Exception as e:
print(f"[!] Error parsing {filepath}: {e}")
return events
def print_findings(findings, title):
"""Print findings in a formatted table."""
if not findings:
print(f"\n[+] {title}: No findings")
return
print(f"\n{'=' * 80}")
print(f" {title} ({len(findings)} findings)")
print(f"{'=' * 80}")
for i, finding in enumerate(findings, 1):
print(f"\n [{i}] {finding.get('detection_type', 'Unknown')}")
print(f" Severity: {finding.get('severity', 'N/A')}")
print(f" MITRE: {finding.get('mitre', 'N/A')}")
print(f" Time: {finding.get('timestamp', 'N/A')}")
print(f" Computer: {finding.get('computer', 'N/A')}")
if "dcom_object" in finding:
print(f" DCOM Object: {finding['dcom_object']}")
print(f" CLSID: {finding.get('clsid', 'N/A')}")
if "parent_image" in finding:
print(f" Parent: {finding['parent_image']}")
print(f" Child: {finding.get('child_image', 'N/A')}")
print(f" Command: {finding.get('child_commandline', 'N/A')[:120]}")
if "source_ip" in finding:
print(f" Source IP: {finding['source_ip']}")
print(f" Dest Port: {finding.get('destination_port', 'N/A')}")
if "note" in finding:
print(f" Note: {finding['note']}")
def print_correlated(correlated):
"""Print correlated findings."""
if not correlated:
print("\n[+] Correlated DCOM Chains: No findings")
return
print(f"\n{'=' * 80}")
print(f" CORRELATED DCOM LATERAL MOVEMENT CHAINS ({len(correlated)} findings)")
print(f"{'=' * 80}")
for i, c in enumerate(correlated, 1):
net = c["network_event"]
proc = c["process_event"]
print(f"\n [{i}] {c['correlation_type']}")
print(f" Severity: {c['severity']}")
print(f" Target: {c['computer']}")
print(f" Source IP: {net['source_ip']} -> port {net['destination_port']}")
print(f" Time Delta: {c['time_delta_seconds']}s")
print(f" DCOM Object: {proc['dcom_object']}")
print(f" Process Chain: {proc['parent_image']} -> {proc['child_image']}")
print(f" Command: {proc.get('child_commandline', 'N/A')[:120]}")
print(f" User: {proc.get('user', 'N/A')}")
def main():
parser = argparse.ArgumentParser(
description="Detect DCOM lateral movement from Sysmon and Security event logs"
)
parser.add_argument(
"--evtx", required=True,
help="Path to Sysmon .evtx log file"
)
parser.add_argument(
"--security",
help="Path to Windows Security .evtx log file (optional, for 4624 correlation)"
)
parser.add_argument(
"--json", action="store_true",
help="Output results in JSON format"
)
parser.add_argument(
"--output", "-o",
help="Output file path (default: stdout)"
)
parser.add_argument(
"--correlation-window", type=int, default=60,
help="Time window in seconds for correlating network and process events (default: 60)"
)
args = parser.parse_args()
if not os.path.exists(args.evtx):
print(f"[!] File not found: {args.evtx}")
sys.exit(1)
print(f"[*] Parsing Sysmon events from: {args.evtx}")
events = parse_evtx_file(args.evtx)
print(f"[*] Parsed {len(events)} Sysmon events")
security_events = []
if args.security:
if os.path.exists(args.security):
print(f"[*] Parsing Security events from: {args.security}")
security_events = parse_evtx_file(args.security)
print(f"[*] Parsed {len(security_events)} Security events")
else:
print(f"[!] Security log not found: {args.security}")
print("[*] Analyzing for DCOM lateral movement indicators...")
process_findings = detect_dcom_process_creation(events)
network_findings = detect_dcom_network_connections(events)
correlated = correlate_network_and_process(
process_findings, network_findings, args.correlation_window
)
all_results = {
"scan_time": datetime.utcnow().isoformat() + "Z",
"sysmon_log": args.evtx,
"security_log": args.security or "Not provided",
"total_events_parsed": len(events) + len(security_events),
"process_creation_findings": process_findings,
"network_connection_findings": network_findings,
"correlated_chains": correlated,
"summary": {
"process_detections": len(process_findings),
"network_detections": len(network_findings),
"correlated_chains": len(correlated),
"critical_findings": len([c for c in correlated]),
"high_findings": len([f for f in process_findings if f.get("severity") == "HIGH"]),
},
}
if args.json:
output = json.dumps(all_results, indent=2, default=str)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"[*] JSON results written to: {args.output}")
else:
print(output)
else:
print(f"\n[*] DCOM Lateral Movement Detection Report")
print(f"[*] Scan Time: {all_results['scan_time']}")
print(f"[*] Events Analyzed: {all_results['total_events_parsed']}")
print_findings(process_findings, "DCOM Process Creation Detections")
print_findings(network_findings, "DCOM Network Connection Detections")
print_correlated(correlated)
print(f"\n{'=' * 80}")
print(f" SUMMARY")
print(f"{'=' * 80}")
s = all_results["summary"]
print(f" Process Creation Detections: {s['process_detections']}")
print(f" Network Connection Detections: {s['network_detections']}")
print(f" Correlated Lateral Movement Chains: {s['correlated_chains']}")
print(f" Critical Findings: {s['critical_findings']}")
print(f" High Findings: {s['high_findings']}")
if args.output:
with open(args.output, "w") as f:
json.dump(all_results, f, indent=2, default=str)
print(f"\n[*] Full results written to: {args.output}")
if __name__ == "__main__":
main()