mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-30 07:56:52 +03:00
Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Credential dumping detection agent using Sysmon and Windows Event Log analysis.
|
||||
|
||||
Parses EVTX logs for LSASS access (Event ID 10), SAM registry access,
|
||||
DCSync indicators (Event ID 4662), and suspicious process patterns.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
import Evtx.Evtx as evtx
|
||||
except ImportError:
|
||||
evtx = None
|
||||
|
||||
LSASS_SUSPICIOUS_ACCESS = {
|
||||
"0x1010": "PROCESS_VM_READ | PROCESS_QUERY_INFORMATION (Mimikatz)",
|
||||
"0x1038": "PROCESS_VM_READ | PROCESS_QUERY_INFO | PROCESS_VM_WRITE",
|
||||
"0x1fffff": "PROCESS_ALL_ACCESS",
|
||||
"0x1410": "PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION",
|
||||
"0x0810": "PROCESS_VM_READ | PROCESS_QUERY_INFORMATION",
|
||||
}
|
||||
|
||||
LSASS_LEGITIMATE_SOURCES = {
|
||||
"csrss.exe", "services.exe", "lsm.exe", "svchost.exe",
|
||||
"mrt.exe", "taskmgr.exe", "wmiprvse.exe",
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
SAM_COMMANDS = [
|
||||
r"reg\s+save\s+hklm\\sam",
|
||||
r"reg\s+save\s+hklm\\security",
|
||||
r"reg\s+save\s+hklm\\system",
|
||||
r"vssadmin\s+create\s+shadow",
|
||||
r"ntdsutil.*ifm",
|
||||
r"copy.*ntds\.dit",
|
||||
r"esentutl.*ntds",
|
||||
]
|
||||
|
||||
DUMP_TOOLS = {
|
||||
"mimikatz.exe": "CRITICAL", "procdump.exe": "HIGH", "procdump64.exe": "HIGH",
|
||||
"nanodump.exe": "CRITICAL", "pypykatz": "CRITICAL",
|
||||
"secretsdump.py": "CRITICAL", "lazagne.exe": "HIGH",
|
||||
}
|
||||
|
||||
|
||||
def parse_sysmon_event10(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>10</EventID>" not in xml:
|
||||
continue
|
||||
target = re.search(r'<Data Name="TargetImage">([^<]+)', xml)
|
||||
if not target or "lsass.exe" not in target.group(1).lower():
|
||||
continue
|
||||
source = re.search(r'<Data Name="SourceImage">([^<]+)', xml)
|
||||
access = re.search(r'<Data Name="GrantedAccess">([^<]+)', xml)
|
||||
source_user = re.search(r'<Data Name="SourceUser">([^<]+)', xml)
|
||||
time_created = re.search(r'SystemTime="([^"]+)"', xml)
|
||||
|
||||
source_name = source.group(1) if source else ""
|
||||
source_basename = source_name.rsplit("\\", 1)[-1].lower()
|
||||
access_mask = access.group(1) if access else ""
|
||||
|
||||
if source_basename in LSASS_LEGITIMATE_SOURCES:
|
||||
continue
|
||||
|
||||
severity = "HIGH"
|
||||
technique = "T1003.001"
|
||||
if access_mask.lower() in LSASS_SUSPICIOUS_ACCESS:
|
||||
severity = "CRITICAL"
|
||||
|
||||
findings.append({
|
||||
"event_id": 10,
|
||||
"timestamp": time_created.group(1) if time_created else "",
|
||||
"source_image": source_name,
|
||||
"target_image": target.group(1),
|
||||
"granted_access": access_mask,
|
||||
"access_meaning": LSASS_SUSPICIOUS_ACCESS.get(access_mask.lower(), ""),
|
||||
"source_user": source_user.group(1) if source_user else "",
|
||||
"severity": severity,
|
||||
"mitre": technique,
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def parse_security_4662(filepath):
|
||||
if evtx is None:
|
||||
return {"error": "python-evtx not installed"}
|
||||
findings = []
|
||||
with evtx.Evtx(filepath) as log:
|
||||
for record in log.records():
|
||||
xml = record.xml()
|
||||
if "<EventID>4662</EventID>" not in xml:
|
||||
continue
|
||||
props = re.search(r'<Data Name="Properties">([^<]+)', xml)
|
||||
if not props:
|
||||
continue
|
||||
prop_text = props.group(1).lower()
|
||||
matched_guids = []
|
||||
for guid, name in DCSYNC_GUIDS.items():
|
||||
if guid in prop_text:
|
||||
matched_guids.append(name)
|
||||
if not matched_guids:
|
||||
continue
|
||||
subject = re.search(r'<Data Name="SubjectUserName">([^<]+)', xml)
|
||||
subject_name = subject.group(1) if subject else ""
|
||||
if subject_name.endswith("$"):
|
||||
continue
|
||||
time_created = re.search(r'SystemTime="([^"]+)"', xml)
|
||||
findings.append({
|
||||
"event_id": 4662,
|
||||
"timestamp": time_created.group(1) if time_created else "",
|
||||
"subject_user": subject_name,
|
||||
"replication_rights": matched_guids,
|
||||
"severity": "CRITICAL",
|
||||
"mitre": "T1003.006",
|
||||
"description": "DCSync - non-DC account requesting replication",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def detect_sam_dump_commands(filepath):
|
||||
findings = []
|
||||
with open(filepath, "r", encoding="utf-8", errors="replace") as f:
|
||||
for line_num, line in enumerate(f, 1):
|
||||
for pattern in SAM_COMMANDS:
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
findings.append({
|
||||
"line": line_num,
|
||||
"command": line.strip()[:200],
|
||||
"pattern": pattern,
|
||||
"severity": "CRITICAL",
|
||||
"mitre": "T1003.002",
|
||||
})
|
||||
for tool, sev in DUMP_TOOLS.items():
|
||||
if tool.lower() in line.lower():
|
||||
findings.append({
|
||||
"line": line_num,
|
||||
"tool": tool,
|
||||
"severity": sev,
|
||||
"mitre": "T1003",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Credential Dumping Detector")
|
||||
parser.add_argument("--sysmon-log", help="Sysmon EVTX file for LSASS access (Event 10)")
|
||||
parser.add_argument("--security-log", help="Security EVTX file for DCSync (Event 4662)")
|
||||
parser.add_argument("--command-log", help="Text log to scan for SAM dump commands")
|
||||
args = parser.parse_args()
|
||||
|
||||
results = {"timestamp": datetime.utcnow().isoformat() + "Z", "findings": []}
|
||||
|
||||
if args.sysmon_log:
|
||||
lsass = parse_sysmon_event10(args.sysmon_log)
|
||||
if isinstance(lsass, dict) and "error" in lsass:
|
||||
results["lsass_error"] = lsass["error"]
|
||||
else:
|
||||
results["lsass_access"] = lsass
|
||||
results["findings"].extend(lsass)
|
||||
|
||||
if args.security_log:
|
||||
dcsync = parse_security_4662(args.security_log)
|
||||
if isinstance(dcsync, dict) and "error" in dcsync:
|
||||
results["dcsync_error"] = dcsync["error"]
|
||||
else:
|
||||
results["dcsync_events"] = dcsync
|
||||
results["findings"].extend(dcsync)
|
||||
|
||||
if args.command_log:
|
||||
sam = detect_sam_dump_commands(args.command_log)
|
||||
results["sam_dump_commands"] = sam
|
||||
results["findings"].extend(sam)
|
||||
|
||||
results["total_findings"] = len(results["findings"])
|
||||
print(json.dumps(results, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,383 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user