Files

182 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""
DLL Sideloading Detection Script
Analyzes DLL load events for sideloading indicators including unsigned DLLs,
path anomalies, and known vulnerable application abuse.
"""
import json
import csv
import argparse
import datetime
import re
from collections import defaultdict
from pathlib import Path
KNOWN_SIDELOAD_TARGETS = {
"version.dll": ["OneDriveUpdater.exe", "Grammarly.exe"],
"cryptsp.dll": ["Teams.exe"],
"dismcore.dll": ["DismHost.exe"],
"mpclient.dll": ["MpCmdRun.exe"],
"dbgcore.dll": ["WerFault.exe"],
"wbemcomn.dll": ["mmc.exe"],
"comsvcs.dll": ["rundll32.exe"],
"uxtheme.dll": ["Multiple"],
"dwmapi.dll": ["Multiple"],
"winmm.dll": ["Multiple"],
"dxgi.dll": ["Multiple"],
}
LEGITIMATE_DLL_PATHS = [
r"C:\\Windows\\System32\\",
r"C:\\Windows\\SysWOW64\\",
r"C:\\Windows\\WinSxS\\",
r"C:\\Program Files\\",
r"C:\\Program Files \(x86\)\\",
]
SUSPICIOUS_DLL_PATHS = [
r"\\Temp\\", r"\\tmp\\", r"\\AppData\\Local\\Temp\\",
r"\\Users\\Public\\", r"\\Downloads\\", r"\\Desktop\\",
r"\\ProgramData\\(?!Microsoft)",
]
def parse_logs(input_path: str) -> list[dict]:
path = Path(input_path)
if path.suffix == ".json":
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
return data if isinstance(data, list) else data.get("events", [])
elif path.suffix == ".csv":
with open(path, "r", encoding="utf-8-sig") as f:
return [dict(row) for row in csv.DictReader(f)]
return []
def normalize_event(event: dict) -> dict:
field_map = {
"event_id": ["EventCode", "EventID"],
"image": ["Image", "InitiatingProcessFileName"],
"dll_loaded": ["ImageLoaded", "FileName", "FolderPath"],
"signed": ["Signed", "IsSigned"],
"signature": ["Signature", "SignatureType"],
"hashes": ["Hashes", "SHA256"],
"hostname": ["Computer", "DeviceName"],
"user": ["User", "AccountName"],
"timestamp": ["UtcTime", "Timestamp"],
}
normalized = {}
for target, sources in field_map.items():
for src in sources:
if src in event and event[src]:
normalized[target] = str(event[src])
break
if target not in normalized:
normalized[target] = ""
return normalized
def detect_sideloading(event: dict) -> dict | None:
if event.get("event_id") != "7":
return None
dll_path = event.get("dll_loaded", "").lower()
image_path = event.get("image", "").lower()
signed = event.get("signed", "").lower()
dll_name = dll_path.split("\\")[-1] if dll_path else ""
app_name = image_path.split("\\")[-1] if image_path else ""
risk = 0
indicators = []
# Check if DLL is a known sideloading target
if dll_name in KNOWN_SIDELOAD_TARGETS:
# Check if loaded from non-standard path
is_legit_path = any(re.search(p, dll_path, re.IGNORECASE) for p in LEGITIMATE_DLL_PATHS)
if not is_legit_path:
risk += 40
indicators.append(f"Known sideload target DLL: {dll_name}")
# Check for unsigned DLL
if signed in ("false", "0", ""):
risk += 20
indicators.append("Unsigned DLL loaded")
# Check for suspicious DLL path
for pattern in SUSPICIOUS_DLL_PATHS:
if re.search(pattern, dll_path, re.IGNORECASE):
risk += 25
indicators.append(f"DLL in suspicious path: {pattern}")
break
# Check for app running from unusual location
app_in_standard = any(re.search(p, image_path, re.IGNORECASE) for p in LEGITIMATE_DLL_PATHS)
if not app_in_standard and app_name:
for pattern in SUSPICIOUS_DLL_PATHS:
if re.search(pattern, image_path, re.IGNORECASE):
risk += 20
indicators.append(f"Host application in suspicious path")
break
if not indicators:
return None
risk_level = "CRITICAL" if risk >= 70 else "HIGH" if risk >= 50 else "MEDIUM" if risk >= 30 else "LOW"
return {
"detection_type": "DLL_SIDELOADING",
"technique": "T1574.002",
"host_application": image_path,
"sideloaded_dll": dll_path,
"dll_name": dll_name,
"signed": signed,
"hostname": event.get("hostname", "unknown"),
"user": event.get("user", "unknown"),
"timestamp": event.get("timestamp", "unknown"),
"risk_score": risk,
"risk_level": risk_level,
"indicators": indicators,
}
def run_hunt(input_path: str, output_dir: str) -> None:
print(f"[*] DLL Sideloading Hunt - {datetime.datetime.now().isoformat()}")
events = [normalize_event(e) for e in parse_logs(input_path)]
print(f"[*] Loaded {len(events)} events")
findings = [f for f in (detect_sideloading(e) for e in events) if f]
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
with open(output_path / "sideload_findings.json", "w", encoding="utf-8") as f:
json.dump({
"hunt_id": f"TH-SIDELOAD-{datetime.date.today().isoformat()}",
"total_events": len(events),
"findings": sorted(findings, key=lambda x: x["risk_score"], reverse=True),
}, f, indent=2)
print(f"[+] {len(findings)} findings written to {output_dir}")
def main():
parser = argparse.ArgumentParser(description="DLL Sideloading Detection")
subparsers = parser.add_subparsers(dest="command")
hunt_p = subparsers.add_parser("hunt")
hunt_p.add_argument("--input", "-i", required=True)
hunt_p.add_argument("--output", "-o", default="./sideload_output")
subparsers.add_parser("queries")
args = parser.parse_args()
if args.command == "hunt":
run_hunt(args.input, args.output)
elif args.command == "queries":
print("=== Sysmon DLL Load Queries ===")
print('index=sysmon EventCode=7 Signed=false\n| stats count by Image ImageLoaded Computer\n| sort -count')
else:
parser.print_help()
if __name__ == "__main__":
main()