Files
mukul975 c47eed6a64 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
2026-03-19 13:26:49 +01:00

190 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""Agent for detecting T1003 credential dumping via EDR telemetry analysis."""
import argparse
import json
import re
import subprocess
import sys
from datetime import datetime, timezone
LSASS_ACCESS_MASKS = {
0x1010: "PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ",
0x1FFFFF: "PROCESS_ALL_ACCESS",
0x1410: "PROCESS_QUERY_INFORMATION | PROCESS_VM_READ",
0x0040: "PROCESS_DUP_HANDLE",
}
CREDENTIAL_DUMP_TOOLS = [
"mimikatz", "procdump", "sqldumper", "comsvcs.dll",
"nanodump", "pypykatz", "lazagne", "secretsdump",
"gsecdump", "wce.exe", "fgdump", "pwdump",
"ntdsutil", "reg save hklm\\sam", "reg save hklm\\system",
]
SYSMON_EVENTS = {
1: "Process Creation",
10: "Process Access (LSASS read)",
11: "File Create (credential dump file)",
7: "Image Loaded (suspicious DLL)",
}
def check_lsass_access_sysmon():
"""Query Sysmon Event ID 10 for LSASS process access."""
findings = []
if sys.platform != "win32":
return findings
ps_cmd = (
"Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational';"
"Id=10} -MaxEvents 500 "
"| Where-Object {$_.Properties[8].Value -match 'lsass'} "
"| Select-Object TimeCreated,"
"@{N='SourceProcess';E={$_.Properties[4].Value}},"
"@{N='SourcePID';E={$_.Properties[3].Value}},"
"@{N='TargetProcess';E={$_.Properties[8].Value}},"
"@{N='GrantedAccess';E={$_.Properties[10].Value}} "
"| ConvertTo-Json -Depth 3"
)
try:
result = subprocess.check_output(
["powershell", "-NoProfile", "-Command", ps_cmd],
text=True, errors="replace", timeout=30
)
data = json.loads(result) if result.strip() else []
if not isinstance(data, list):
data = [data]
for evt in data:
source = evt.get("SourceProcess", "")
access = evt.get("GrantedAccess", "")
if not any(safe in source.lower() for safe in ["svchost", "csrss", "lsass", "wmiprvse"]):
findings.append({
"time": evt.get("TimeCreated", ""),
"source": source,
"target": evt.get("TargetProcess", ""),
"access_mask": access,
"suspicious": True,
})
except (subprocess.SubprocessError, json.JSONDecodeError):
pass
return findings
def check_credential_dump_processes():
"""Check for known credential dumping tool processes."""
findings = []
if sys.platform != "win32":
return findings
ps_cmd = (
"Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational';"
"Id=1} -MaxEvents 1000 "
"| Select-Object TimeCreated,"
"@{N='Image';E={$_.Properties[4].Value}},"
"@{N='CommandLine';E={$_.Properties[10].Value}},"
"@{N='ParentImage';E={$_.Properties[20].Value}} "
"| ConvertTo-Json -Depth 3"
)
try:
result = subprocess.check_output(
["powershell", "-NoProfile", "-Command", ps_cmd],
text=True, errors="replace", timeout=30
)
data = json.loads(result) if result.strip() else []
if not isinstance(data, list):
data = [data]
for evt in data:
cmdline = (evt.get("CommandLine", "") or "").lower()
image = (evt.get("Image", "") or "").lower()
for tool in CREDENTIAL_DUMP_TOOLS:
if tool.lower() in cmdline or tool.lower() in image:
findings.append({
"time": evt.get("TimeCreated", ""),
"image": evt.get("Image", ""),
"commandline": evt.get("CommandLine", "")[:200],
"tool_match": tool,
})
break
except (subprocess.SubprocessError, json.JSONDecodeError):
pass
return findings
def check_sam_ntds_access():
"""Check for SAM/NTDS.dit/SYSTEM registry hive access."""
findings = []
patterns = [
r"reg\s+save\s+hklm\\sam",
r"reg\s+save\s+hklm\\system",
r"reg\s+save\s+hklm\\security",
r"ntdsutil.*\"ac\s+i\s+ntds\"",
r"vssadmin.*create\s+shadow",
r"copy.*ntds\.dit",
]
if sys.platform != "win32":
return findings
ps_cmd = (
"Get-WinEvent -FilterHashtable @{LogName='Security';Id=4688} -MaxEvents 500 "
"| Select-Object TimeCreated,"
"@{N='CommandLine';E={$_.Properties[8].Value}} "
"| ConvertTo-Json"
)
try:
result = subprocess.check_output(
["powershell", "-NoProfile", "-Command", ps_cmd],
text=True, errors="replace", timeout=30
)
data = json.loads(result) if result.strip() else []
if not isinstance(data, list):
data = [data]
for evt in data:
cmdline = (evt.get("CommandLine", "") or "").lower()
for pat in patterns:
if re.search(pat, cmdline):
findings.append({
"time": evt.get("TimeCreated", ""),
"commandline": cmdline[:200],
"pattern": pat,
})
except (subprocess.SubprocessError, json.JSONDecodeError):
pass
return findings
def main():
parser = argparse.ArgumentParser(
description="Detect T1003 credential dumping via EDR telemetry"
)
parser.add_argument("--output", "-o", help="Output JSON report")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
print("[*] T1003 Credential Dumping Detection Agent")
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}}
lsass = check_lsass_access_sysmon()
report["findings"]["lsass_access"] = lsass
print(f"[*] Suspicious LSASS access events: {len(lsass)}")
tools = check_credential_dump_processes()
report["findings"]["dump_tools"] = tools
print(f"[*] Credential dump tool detections: {len(tools)}")
sam = check_sam_ntds_access()
report["findings"]["sam_ntds_access"] = sam
print(f"[*] SAM/NTDS access events: {len(sam)}")
total = len(lsass) + len(tools) + len(sam)
report["risk_level"] = "CRITICAL" if total >= 5 else "HIGH" if total >= 2 else "MEDIUM" if total > 0 else "LOW"
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"[*] Report saved to {args.output}")
else:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()