mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-13 06:34:57 +03:00
384 lines
14 KiB
Python
384 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Credential Dumping Detection Script
|
|
Analyzes process access logs for LSASS memory access, SAM extraction,
|
|
DCSync activity, and other credential theft indicators.
|
|
"""
|
|
|
|
import json
|
|
import csv
|
|
import argparse
|
|
import datetime
|
|
import re
|
|
import sys
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
# Suspicious LSASS access masks indicating credential dumping
|
|
SUSPICIOUS_ACCESS_MASKS = {
|
|
"0x1FFFFF": {"risk": "CRITICAL", "description": "PROCESS_ALL_ACCESS - full process access"},
|
|
"0x1010": {"risk": "HIGH", "description": "PROCESS_VM_READ + PROCESS_QUERY_INFORMATION (Mimikatz default)"},
|
|
"0x1038": {"risk": "HIGH", "description": "Common credential dumping access mask"},
|
|
"0x0410": {"risk": "MEDIUM", "description": "PROCESS_QUERY_INFORMATION + PROCESS_VM_READ"},
|
|
"0x1400": {"risk": "MEDIUM", "description": "PROCESS_QUERY_INFORMATION + PROCESS_QUERY_LIMITED"},
|
|
"0x0040": {"risk": "HIGH", "description": "PROCESS_DUP_HANDLE - handle duplication"},
|
|
"0x0810": {"risk": "HIGH", "description": "PROCESS_SUSPEND_RESUME + PROCESS_VM_READ"},
|
|
"0x1fffff": {"risk": "CRITICAL", "description": "PROCESS_ALL_ACCESS (lowercase)"},
|
|
}
|
|
|
|
# Legitimate processes that commonly access LSASS
|
|
LSASS_WHITELIST = {
|
|
"csrss.exe", "svchost.exe", "services.exe", "lsass.exe", "wininit.exe",
|
|
"smss.exe", "wmiprvse.exe", "taskmgr.exe", "procexp.exe", "procexp64.exe",
|
|
"msmpsvc.exe", "msmpeng.exe", "nissrv.exe", "mssense.exe", "sensecncproxy.exe",
|
|
"csfalconservice.exe", "csfalconcontainer.exe",
|
|
"sentinelagent.exe", "sentinelone.exe",
|
|
"cb.exe", "carbonblack.exe",
|
|
"logrhythmagent.exe",
|
|
}
|
|
|
|
# Known credential dumping tool command-line patterns
|
|
CRED_DUMP_TOOL_PATTERNS = {
|
|
"mimikatz": {
|
|
"patterns": [
|
|
r"sekurlsa::",
|
|
r"lsadump::",
|
|
r"kerberos::list",
|
|
r"crypto::cng",
|
|
r"privilege::debug",
|
|
r"token::elevate",
|
|
r"dpapi::",
|
|
r"vault::cred",
|
|
],
|
|
"technique": "T1003.001/T1003.006",
|
|
},
|
|
"comsvcs_minidump": {
|
|
"patterns": [
|
|
r"comsvcs\.dll.*MiniDump",
|
|
r"comsvcs\.dll.*#24",
|
|
],
|
|
"technique": "T1003.001",
|
|
},
|
|
"procdump": {
|
|
"patterns": [
|
|
r"procdump.*-ma.*lsass",
|
|
r"procdump.*lsass.*-ma",
|
|
r"procdump.*-accepteula.*lsass",
|
|
],
|
|
"technique": "T1003.001",
|
|
},
|
|
"reg_save": {
|
|
"patterns": [
|
|
r"reg\s+(save|export)\s+HKLM\\SAM",
|
|
r"reg\s+(save|export)\s+HKLM\\SECURITY",
|
|
r"reg\s+(save|export)\s+HKLM\\SYSTEM",
|
|
],
|
|
"technique": "T1003.002",
|
|
},
|
|
"ntdsutil": {
|
|
"patterns": [
|
|
r"ntdsutil.*ifm",
|
|
r"ntdsutil.*\"activate instance ntds\"",
|
|
r"ntdsutil.*create full",
|
|
],
|
|
"technique": "T1003.003",
|
|
},
|
|
"vssadmin_shadow": {
|
|
"patterns": [
|
|
r"vssadmin.*create\s+shadow",
|
|
r"copy.*GLOBALROOT.*Device.*HarddiskVolumeShadowCopy",
|
|
r"wmic.*shadowcopy.*create",
|
|
],
|
|
"technique": "T1003.003",
|
|
},
|
|
"secretsdump": {
|
|
"patterns": [
|
|
r"secretsdump",
|
|
r"impacket.*dump",
|
|
],
|
|
"technique": "T1003.002/T1003.003/T1003.006",
|
|
},
|
|
"lazagne": {
|
|
"patterns": [
|
|
r"lazagne",
|
|
r"LaZagne\.exe",
|
|
],
|
|
"technique": "T1003.001/T1555",
|
|
},
|
|
"sharpdump": {
|
|
"patterns": [
|
|
r"SharpDump",
|
|
r"sharpdump",
|
|
],
|
|
"technique": "T1003.001",
|
|
},
|
|
"nanodump": {
|
|
"patterns": [
|
|
r"nanodump",
|
|
],
|
|
"technique": "T1003.001",
|
|
},
|
|
}
|
|
|
|
# DCSync detection GUIDs
|
|
DCSYNC_GUIDS = {
|
|
"1131f6aa-9c07-11d1-f79f-00c04fc2dcd2": "DS-Replication-Get-Changes",
|
|
"1131f6ad-9c07-11d1-f79f-00c04fc2dcd2": "DS-Replication-Get-Changes-All",
|
|
"89e95b76-444d-4c62-991a-0facbeda640c": "DS-Replication-Get-Changes-In-Filtered-Set",
|
|
}
|
|
|
|
|
|
def parse_logs(input_path: str) -> list[dict]:
|
|
"""Parse log files in JSON or CSV format."""
|
|
events = []
|
|
path = Path(input_path)
|
|
if path.suffix == ".json":
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
events = data if isinstance(data, list) else data.get("events", data.get("hits", {}).get("hits", []))
|
|
if events and isinstance(events[0], dict) and "_source" in events[0]:
|
|
events = [e["_source"] for e in events]
|
|
elif path.suffix == ".csv":
|
|
with open(path, "r", encoding="utf-8-sig") as f:
|
|
reader = csv.DictReader(f)
|
|
events = [dict(row) for row in reader]
|
|
return events
|
|
|
|
|
|
def normalize_event(event: dict) -> dict:
|
|
"""Normalize event field names."""
|
|
field_map = {
|
|
"source_image": ["SourceImage", "source_image", "InitiatingProcessFileName", "process.executable"],
|
|
"target_image": ["TargetImage", "target_image", "FileName", "target.process.executable"],
|
|
"granted_access": ["GrantedAccess", "granted_access", "AccessMask"],
|
|
"command_line": ["CommandLine", "command_line", "ProcessCommandLine", "process.command_line"],
|
|
"user": ["User", "user", "AccountName", "SubjectUserName", "user.name"],
|
|
"hostname": ["Computer", "hostname", "DeviceName", "host.name"],
|
|
"timestamp": ["UtcTime", "timestamp", "Timestamp", "@timestamp"],
|
|
"event_id": ["EventID", "EventCode", "event_id", "event.code"],
|
|
"parent_image": ["ParentImage", "parent_image", "InitiatingProcessParentFileName"],
|
|
"properties": ["Properties", "properties", "ObjectType"],
|
|
}
|
|
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_lsass_access(event: dict) -> dict | None:
|
|
"""Detect suspicious LSASS process access."""
|
|
target = event.get("target_image", "").lower()
|
|
if "lsass.exe" not in target:
|
|
return None
|
|
|
|
source = event.get("source_image", "").lower()
|
|
source_name = source.split("\\")[-1].split("/")[-1]
|
|
access = event.get("granted_access", "").lower()
|
|
|
|
# Skip whitelisted processes
|
|
if source_name in LSASS_WHITELIST:
|
|
return None
|
|
|
|
risk_info = SUSPICIOUS_ACCESS_MASKS.get(access, SUSPICIOUS_ACCESS_MASKS.get(access.upper()))
|
|
if not risk_info:
|
|
risk_info = {"risk": "LOW", "description": f"Unknown access mask: {access}"}
|
|
|
|
return {
|
|
"detection_type": "LSASS_ACCESS",
|
|
"technique": "T1003.001",
|
|
"source_process": event.get("source_image", ""),
|
|
"target_process": event.get("target_image", ""),
|
|
"granted_access": access,
|
|
"access_description": risk_info["description"],
|
|
"risk_level": risk_info["risk"],
|
|
"user": event.get("user", "unknown"),
|
|
"hostname": event.get("hostname", "unknown"),
|
|
"timestamp": event.get("timestamp", "unknown"),
|
|
"indicators": [f"LSASS access from {source_name} with mask {access}"],
|
|
}
|
|
|
|
|
|
def detect_credential_tool(event: dict) -> dict | None:
|
|
"""Detect known credential dumping tool execution."""
|
|
cmd = event.get("command_line", "")
|
|
if not cmd:
|
|
return None
|
|
|
|
for tool_name, tool_info in CRED_DUMP_TOOL_PATTERNS.items():
|
|
for pattern in tool_info["patterns"]:
|
|
if re.search(pattern, cmd, re.IGNORECASE):
|
|
return {
|
|
"detection_type": "CREDENTIAL_TOOL",
|
|
"technique": tool_info["technique"],
|
|
"tool": tool_name,
|
|
"command_line": cmd,
|
|
"source_process": event.get("source_image", ""),
|
|
"parent_process": event.get("parent_image", ""),
|
|
"risk_level": "CRITICAL",
|
|
"user": event.get("user", "unknown"),
|
|
"hostname": event.get("hostname", "unknown"),
|
|
"timestamp": event.get("timestamp", "unknown"),
|
|
"indicators": [f"Credential tool detected: {tool_name}", f"Pattern matched: {pattern}"],
|
|
}
|
|
return None
|
|
|
|
|
|
def detect_dcsync(event: dict) -> dict | None:
|
|
"""Detect DCSync activity from non-DC sources."""
|
|
props = event.get("properties", "")
|
|
for guid, name in DCSYNC_GUIDS.items():
|
|
if guid.lower() in props.lower():
|
|
return {
|
|
"detection_type": "DCSYNC",
|
|
"technique": "T1003.006",
|
|
"replication_right": name,
|
|
"guid": guid,
|
|
"risk_level": "CRITICAL",
|
|
"user": event.get("user", "unknown"),
|
|
"hostname": event.get("hostname", "unknown"),
|
|
"timestamp": event.get("timestamp", "unknown"),
|
|
"indicators": [f"DCSync activity: {name}", f"GUID: {guid}"],
|
|
}
|
|
return None
|
|
|
|
|
|
def run_hunt(input_path: str, output_dir: str, dc_list: list[str] | None = None) -> None:
|
|
"""Execute credential dumping hunt."""
|
|
print(f"[*] Credential Dumping Hunt - {datetime.datetime.now().isoformat()}")
|
|
print(f"[*] Input: {input_path}")
|
|
|
|
events = parse_logs(input_path)
|
|
print(f"[*] Loaded {len(events)} events")
|
|
|
|
findings = []
|
|
stats = defaultdict(int)
|
|
|
|
for raw_event in events:
|
|
event = normalize_event(raw_event)
|
|
|
|
# Check for LSASS access
|
|
result = detect_lsass_access(event)
|
|
if result:
|
|
findings.append(result)
|
|
stats["LSASS_ACCESS"] += 1
|
|
stats[result["risk_level"]] += 1
|
|
|
|
# Check for credential dumping tools
|
|
result = detect_credential_tool(event)
|
|
if result:
|
|
findings.append(result)
|
|
stats["CREDENTIAL_TOOL"] += 1
|
|
stats[result["risk_level"]] += 1
|
|
|
|
# Check for DCSync
|
|
result = detect_dcsync(event)
|
|
if result:
|
|
if dc_list and result["hostname"].lower() in [dc.lower() for dc in dc_list]:
|
|
continue # Skip legitimate DC replication
|
|
findings.append(result)
|
|
stats["DCSYNC"] += 1
|
|
stats[result["risk_level"]] += 1
|
|
|
|
# Write output
|
|
output_path = Path(output_dir)
|
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
findings_file = output_path / "credential_dump_findings.json"
|
|
with open(findings_file, "w", encoding="utf-8") as f:
|
|
json.dump({
|
|
"hunt_id": f"TH-CRED-DUMP-{datetime.date.today().isoformat()}",
|
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
"total_events": len(events),
|
|
"total_findings": len(findings),
|
|
"statistics": dict(stats),
|
|
"findings": findings,
|
|
}, f, indent=2)
|
|
|
|
# Write report
|
|
report_file = output_path / "hunt_report.md"
|
|
with open(report_file, "w", encoding="utf-8") as f:
|
|
f.write(f"# Credential Dumping Hunt Report\n\n")
|
|
f.write(f"**Date**: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
f.write(f"**Events Analyzed**: {len(events)}\n")
|
|
f.write(f"**Findings**: {len(findings)}\n\n")
|
|
f.write("## Detection Breakdown\n\n")
|
|
for key, count in sorted(stats.items()):
|
|
f.write(f"- {key}: {count}\n")
|
|
f.write("\n## Critical Findings\n\n")
|
|
for finding in sorted(findings, key=lambda x: ("CRITICAL", "HIGH", "MEDIUM", "LOW").index(x["risk_level"])):
|
|
if finding["risk_level"] in ("CRITICAL", "HIGH"):
|
|
f.write(f"### [{finding['risk_level']}] {finding['detection_type']} - {finding['technique']}\n")
|
|
f.write(f"- **Host**: {finding['hostname']}\n")
|
|
f.write(f"- **User**: {finding['user']}\n")
|
|
f.write(f"- **Indicators**: {', '.join(finding['indicators'])}\n\n")
|
|
|
|
print(f"[+] Output written to {output_dir}")
|
|
print(f"\n{'='*60}")
|
|
print(f"FINDINGS: {len(findings)} | CRITICAL: {stats.get('CRITICAL',0)} | HIGH: {stats.get('HIGH',0)}")
|
|
print(f"{'='*60}")
|
|
|
|
|
|
def generate_queries(platform: str) -> None:
|
|
"""Generate hunting queries for specified platform."""
|
|
if platform in ("splunk", "all"):
|
|
print("\n=== SPLUNK QUERIES ===\n")
|
|
print("--- LSASS Access Detection ---")
|
|
print("""index=sysmon EventCode=10 TargetImage="*\\\\lsass.exe"
|
|
| where NOT match(SourceImage, "(?i)(csrss|svchost|services|lsass|wininit|MsMpEng)")
|
|
| stats count by SourceImage GrantedAccess Computer User
|
|
| sort -count""")
|
|
print("\n--- Credential Tool Detection ---")
|
|
print("""index=sysmon EventCode=1
|
|
| where match(CommandLine, "(?i)(sekurlsa|lsadump|comsvcs.*MiniDump|procdump.*lsass|reg save.*SAM)")
|
|
| table _time Computer User Image CommandLine ParentImage""")
|
|
print("\n--- DCSync Detection ---")
|
|
print("""index=wineventlog EventCode=4662
|
|
| where match(Properties, "(?i)(1131f6aa|1131f6ad|89e95b76)")
|
|
| table _time SubjectUserName SubjectDomainName Computer Properties""")
|
|
|
|
if platform in ("kql", "all"):
|
|
print("\n=== KQL QUERIES ===\n")
|
|
print("--- LSASS Access ---")
|
|
print("""DeviceEvents
|
|
| where ActionType == "OpenProcessApiCall"
|
|
| where FileName == "lsass.exe"
|
|
| where InitiatingProcessFileName !in~ ("csrss.exe","svchost.exe","MsMpEng.exe")
|
|
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, AdditionalFields""")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Credential Dumping Detection Hunt")
|
|
subparsers = parser.add_subparsers(dest="command")
|
|
|
|
hunt_parser = subparsers.add_parser("hunt", help="Run credential dumping hunt")
|
|
hunt_parser.add_argument("--input", "-i", required=True, help="Log file path")
|
|
hunt_parser.add_argument("--output", "-o", default="./cred_dump_output", help="Output directory")
|
|
hunt_parser.add_argument("--dc-list", nargs="*", help="List of known DCs to exclude from DCSync alerts")
|
|
|
|
query_parser = subparsers.add_parser("queries", help="Generate hunting queries")
|
|
query_parser.add_argument("--platform", "-p", choices=["splunk", "kql", "all"], default="all")
|
|
|
|
subparsers.add_parser("signatures", help="List detection signatures")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "hunt":
|
|
run_hunt(args.input, args.output, args.dc_list)
|
|
elif args.command == "queries":
|
|
generate_queries(args.platform)
|
|
elif args.command == "signatures":
|
|
print("\n=== Credential Dumping Tool Signatures ===\n")
|
|
for tool, info in CRED_DUMP_TOOL_PATTERNS.items():
|
|
print(f"{tool:<25} {info['technique']:<25} Patterns: {len(info['patterns'])}")
|
|
else:
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|