threat hunting

Detecting DLL Sideloading Attacks

Detect DLL side-loading attacks where adversaries place malicious DLLs alongside legitimate applications to hijack execution flow for defense evasion.

defense-evasiondll-sideloadingedrmitre-attackproactive-detectiont1574threat-hunting
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When investigating potential DLL hijacking in enterprise environments
  • After EDR alerts on unsigned DLLs loaded by signed applications
  • When hunting for APT persistence using legitimate application wrappers
  • During incident response to identify trojanized applications
  • When threat intel indicates DLL sideloading campaigns targeting specific software

Prerequisites

  • EDR with DLL load monitoring (CrowdStrike, MDE, SentinelOne)
  • Sysmon Event ID 7 (Image Loaded) with hash verification
  • Application whitelisting or DLL integrity monitoring
  • Software inventory of legitimate applications and expected DLL paths
  • Code signing verification capabilities

Workflow

  1. Identify Sideloading Targets: Research known vulnerable applications that load DLLs without full path qualification (LOLBAS, DLL-sideload databases).
  2. Monitor DLL Load Events: Query Sysmon Event ID 7 for DLL loads where the DLL path differs from the application's expected directory.
  3. Check DLL Signatures: Flag unsigned or untrusted DLLs loaded by signed executables.
  4. Detect Path Anomalies: Identify legitimate executables running from unusual locations (Temp, AppData, Public) that may be decoy wrappers.
  5. Hash Verification: Compare loaded DLL hashes against known-good versions and threat intel feeds.
  6. Correlate with Process Behavior: Check if the host process exhibits unusual behavior (network connections, child processes) after loading the suspicious DLL.
  7. Document and Remediate: Report sideloading instances, quarantine malicious DLLs, and update detection rules.

Key Concepts

Concept Description
T1574.002 DLL Side-Loading
T1574.001 DLL Search Order Hijacking
T1574.006 Dynamic Linker Hijacking
T1574.008 Path Interception by Search Order Hijacking
DLL Search Order Windows DLL loading priority path
Side-Loading Placing malicious DLL where legitimate app loads it
Phantom DLL DLL that legitimate apps try to load but does not exist
DLL Proxying Malicious DLL forwarding calls to legitimate DLL

Tools & Systems

Tool Purpose
Sysmon Event ID 7 DLL load monitoring
CrowdStrike Falcon DLL load detection with process context
Microsoft Defender for Endpoint DLL load anomaly detection
Process Monitor Real-time DLL load tracing
DLL Export Viewer Verify DLL export functions
Sigcheck Digital signature verification
pe-sieve PE analysis for proxied DLLs

Common Scenarios

  1. Legitimate App Wrapper: Adversary copies signed application (e.g., OneDrive updater) to temp folder alongside malicious DLL with same name as expected dependency.
  2. Phantom DLL Exploitation: Malicious DLL placed in PATH location where legitimate app searches for non-existent DLL.
  3. DLL Proxy Loading: Malicious version.dll proxies all exports to real version.dll while executing malicious code on DllMain.
  4. Software Update Hijack: Attacker replaces DLL in update staging directory before legitimate updater loads it.

Output Format

Hunt ID: TH-SIDELOAD-[DATE]-[SEQ]
Technique: T1574.002
Host Application: [Legitimate signed executable]
Sideloaded DLL: [Malicious DLL name and path]
Expected DLL Path: [Where DLL should legitimately be]
DLL Signed: [Yes/No]
App Location: [Expected/Anomalous]
Host: [Hostname]
Risk Level: [Critical/High/Medium/Low]
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.5 KB

API Reference: Detecting DLL Sideloading Attacks

Sysmon Event ID 7 (Image Loaded)

<EventID>7</EventID>
<Data Name="Image">C:\Users\victim\app\signed.exe</Data>
<Data Name="ImageLoaded">C:\Users\victim\app\malicious.dll</Data>
<Data Name="Signed">false</Data>
<Data Name="SignatureStatus">Unavailable</Data>
<Data Name="Hashes">SHA256=abc123...</Data>

python-evtx Usage

import Evtx.Evtx as evtx
with evtx.Evtx("Sysmon.evtx") as log:
    for record in log.records():
        xml = record.xml()
        # Filter EventID 7, check Signed=false, non-standard path

Known Sideloading Targets

Legitimate Executable Vulnerable DLL
vmwaretray.exe vmtools.dll
colorcpl.exe colorui.dll
consent.exe comctl32.dll
bginfo.exe version.dll
teams.exe version.dll
winword.exe wwlib.dll

Splunk SPL Detection

index=sysmon EventCode=7 Signed=false
| where NOT match(ImageLoaded, "(?i)(System32|SysWOW64|Program Files)")
| stats count by Image, ImageLoaded, SignatureStatus, Computer
| where count > 0

Sigma Rule Fields

logsource:
  product: windows
  category: image_load
detection:
  selection:
    EventID: 7
    Signed: "false"
  filter:
    ImageLoaded|startswith:
      - "C:\\Windows\\System32\\"
      - "C:\\Program Files\\"

CLI Usage

python agent.py --sysmon-log Sysmon.evtx
python agent.py --scan-dir C:\Users\victim\Downloads\app\
python agent.py --generate-sigma
standards.md1.9 KB

Standards and References - DLL Sideloading Detection

MITRE ATT&CK Mappings

T1574.002 - Hijack Execution Flow: DLL Side-Loading

  • Tactic: Persistence (TA0003), Privilege Escalation (TA0004), Defense Evasion (TA0005)
  • Platforms: Windows
  • Data Sources: File monitoring, DLL monitoring, Process monitoring

Related Techniques

Technique Name
T1574.001 DLL Search Order Hijacking
T1574.006 Dynamic Linker Hijacking
T1574.008 Path Interception by Search Order
T1574.009 Path Interception by Unquoted Service Path
T1574.011 Services Registry Permissions Weakness
T1574.012 COR_PROFILER

Windows DLL Search Order

  1. Directory of the executable (or directory specified by SetDllDirectory)
  2. System32 directory
  3. 16-bit system directory
  4. Windows directory
  5. Current working directory
  6. PATH environment variable directories

Known Vulnerable Applications

Application Vulnerable DLL Vendor Notes
OneDriveUpdater.exe version.dll Microsoft Frequently abused by APTs
Teams.exe CRYPTSP.dll Microsoft Side-loading target
DismHost.exe dismcore.dll Microsoft Signed binary side-loading
MpCmdRun.exe mpclient.dll Microsoft AV binary abuse
WerFault.exe dbgcore.dll Microsoft Error handler abuse
Grammarly Various Grammarly User-space application
Zoom Various Zoom Meeting application

Detection Data Sources

Source Event Purpose
Sysmon Event 7 Image Loaded DLL load with hash and signature
Sysmon Event 1 Process Create Application launch location
Windows Security 4688 Process Create Command line monitoring
ETW DLL Load Events Kernel-level DLL tracking
MDE DeviceImageLoadEvents DLL load telemetry
workflows.md2.2 KB

Detailed Hunting Workflow - DLL Sideloading

Phase 1: Sysmon DLL Load Analysis

Step 1.1 - Unsigned DLLs Loaded by Signed Applications

index=sysmon EventCode=7 Signed=false
| where match(Image, "(?i)\\\\(Program Files|Windows)\\\\")
| where NOT match(ImageLoaded, "(?i)\\\\(Windows|Program Files)\\\\")
| stats count by Image ImageLoaded Signature Computer
| sort -count

Step 1.2 - DLL Loads from Unusual Directories

index=sysmon EventCode=7
| where match(ImageLoaded, "(?i)(\\\\temp\\\\|\\\\appdata\\\\|\\\\public\\\\|\\\\downloads\\\\)")
| where Signed=false OR Signature="?"
| stats count by Image ImageLoaded Computer User
| sort -count

Step 1.3 - KQL for MDE DLL Sideloading

DeviceImageLoadEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("OneDriveUpdater.exe","DismHost.exe","WerFault.exe")
| where not(FolderPath startswith "C:\\Windows" or FolderPath startswith "C:\\Program Files")
| project Timestamp, DeviceName, InitiatingProcessFileName, FolderPath, FileName, SHA256

Phase 2: Legitimate App in Wrong Location

Step 2.1 - Signed Binaries Running Outside Standard Paths

index=sysmon EventCode=1
| where NOT match(Image, "(?i)^(C:\\\\Windows|C:\\\\Program Files)")
| where match(Image, "(?i)(svchost|explorer|rundll32|dllhost|OneDrive|Teams)\.exe$")
| table _time Computer User Image CommandLine ParentImage Hashes

Phase 3: Hash-Based Detection

Step 3.1 - Known-Bad DLL Hashes

Compare loaded DLL hashes against threat intelligence:

index=sysmon EventCode=7
| rex field=Hashes "SHA256=(?<sha256>[A-Fa-f0-9]{64})"
| lookup threat_intel_hashes sha256 OUTPUT malware_family confidence
| where isnotnull(malware_family)
| table _time Computer Image ImageLoaded sha256 malware_family

Phase 4: Behavioral Correlation

Step 4.1 - Network Activity After DLL Load

Correlate DLL loads with subsequent network connections:

index=sysmon EventCode=7 Signed=false
| rename Image as proc_image
| join proc_image Computer [
    search index=sysmon EventCode=3
    | rename Image as proc_image
    | where NOT match(DestinationIp, "^(10\.|172\.|192\.168\.)")
]
| table _time Computer proc_image ImageLoaded DestinationIp DestinationPort

Scripts 2

agent.py5.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/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 "<EventID>7</EventID>" not in xml:
                continue
            image = re.search(r'<Data Name="Image">([^<]+)', xml)
            loaded = re.search(r'<Data Name="ImageLoaded">([^<]+)', xml)
            signed = re.search(r'<Data Name="Signed">([^<]+)', xml)
            sig_status = re.search(r'<Data Name="SignatureStatus">([^<]+)', xml)
            sha256 = re.search(r'<Data Name="Hashes">([^<]+)', 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()
process.py6.0 KB
Display-only source. This catalog never executes bundled scripts.
#!/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()

Assets 1

template.mdtext/markdown · 0.7 KB
Keep exploring