#!/usr/bin/env python3 """DLL sideloading detection agent using Sysmon Event ID 7 (Image Loaded) analysis. Detects unsigned DLLs loaded by signed executables from non-standard paths, a common APT persistence and defense evasion technique (T1574.002). """ import argparse import json import re from datetime import datetime from pathlib import Path try: import Evtx.Evtx as evtx except ImportError: evtx = None KNOWN_SIDELOAD_TARGETS = { "vmwaretray.exe": ["vmtools.dll"], "colorcpl.exe": ["colorui.dll"], "searchprotocolhost.exe": ["msfte.dll"], "consent.exe": ["comctl32.dll"], "dxcap.exe": ["d3d9.dll"], "eventvwr.exe": ["mmc.dll"], "msdeploy.exe": ["microsoft.web.deployment.dll"], "bginfo.exe": ["version.dll"], "winword.exe": ["wwlib.dll"], "teams.exe": ["version.dll"], } STANDARD_DLL_DIRS = { r"c:\windows\system32", r"c:\windows\syswow64", r"c:\windows\winsxs", r"c:\program files", r"c:\program files (x86)", } def parse_sysmon_dll_loads(filepath): if evtx is None: return {"error": "python-evtx not installed: pip install python-evtx"} findings = [] with evtx.Evtx(filepath) as log: for record in log.records(): xml = record.xml() if "7" not in xml: continue image = re.search(r'([^<]+)', xml) loaded = re.search(r'([^<]+)', xml) signed = re.search(r'([^<]+)', xml) sig_status = re.search(r'([^<]+)', xml) sha256 = re.search(r'([^<]+)', xml) time_created = re.search(r'SystemTime="([^"]+)"', xml) if not image or not loaded: continue image_path = image.group(1).lower() dll_path = loaded.group(1).lower() is_signed = signed.group(1) if signed else "unknown" image_name = image_path.rsplit("\\", 1)[-1] dll_name = dll_path.rsplit("\\", 1)[-1] dll_dir = dll_path.rsplit("\\", 1)[0] if "\\" in dll_path else "" is_standard_dir = any(dll_dir.startswith(d) for d in STANDARD_DLL_DIRS) is_known_target = (image_name in KNOWN_SIDELOAD_TARGETS and dll_name in KNOWN_SIDELOAD_TARGETS[image_name]) if is_signed == "false" and not is_standard_dir: severity = "CRITICAL" if is_known_target else "HIGH" findings.append({ "timestamp": time_created.group(1) if time_created else "", "host_process": image_path, "loaded_dll": dll_path, "signed": is_signed, "signature_status": sig_status.group(1) if sig_status else "", "hash": sha256.group(1) if sha256 else "", "known_sideload_target": is_known_target, "non_standard_path": True, "severity": severity, "mitre": "T1574.002", }) return findings def scan_directory_for_sideloading(directory): findings = [] dir_path = Path(directory) if not dir_path.is_dir(): return [{"error": f"Directory not found: {directory}"}] exe_files = list(dir_path.glob("*.exe")) dll_files = list(dir_path.glob("*.dll")) for exe in exe_files: exe_name = exe.name.lower() if exe_name in KNOWN_SIDELOAD_TARGETS: for dll in dll_files: if dll.name.lower() in KNOWN_SIDELOAD_TARGETS[exe_name]: findings.append({ "exe_path": str(exe), "dll_path": str(dll), "dll_size_bytes": dll.stat().st_size, "known_sideload_pair": True, "severity": "CRITICAL", "mitre": "T1574.002", "description": f"Known sideloading pair: {exe.name} + {dll.name}", }) return findings def generate_sigma_rule(): return { "title": "DLL Sideloading - Unsigned DLL in Non-Standard Path", "status": "experimental", "logsource": {"product": "windows", "category": "image_load"}, "detection": { "selection": {"EventID": 7, "Signed": "false"}, "filter_standard": {"ImageLoaded|startswith": [ "C:\\Windows\\System32\\", "C:\\Windows\\SysWOW64\\", "C:\\Program Files\\", "C:\\Program Files (x86)\\" ]}, "condition": "selection and not filter_standard" }, "level": "high", "tags": ["attack.defense_evasion", "attack.t1574.002"], } def main(): parser = argparse.ArgumentParser(description="DLL Sideloading Detector") parser.add_argument("--sysmon-log", help="Sysmon EVTX file with Event ID 7") parser.add_argument("--scan-dir", help="Directory to scan for sideloading pairs") parser.add_argument("--generate-sigma", action="store_true") args = parser.parse_args() results = {"timestamp": datetime.utcnow().isoformat() + "Z", "findings": []} if args.sysmon_log: evtx_findings = parse_sysmon_dll_loads(args.sysmon_log) if isinstance(evtx_findings, dict) and "error" in evtx_findings: results["error"] = evtx_findings["error"] else: results["findings"].extend(evtx_findings) if args.scan_dir: dir_findings = scan_directory_for_sideloading(args.scan_dir) results["findings"].extend(dir_findings) if args.generate_sigma: results["sigma_rule"] = generate_sigma_rule() results["total_findings"] = len(results["findings"]) print(json.dumps(results, indent=2)) if __name__ == "__main__": main()