mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-19 22:19:39 +03:00
- 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
194 lines
7.6 KiB
Python
194 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for hunting suspicious Windows service installations (T1543.003) via Event ID 7045."""
|
|
|
|
import json
|
|
import re
|
|
import argparse
|
|
from datetime import datetime
|
|
from collections import Counter
|
|
|
|
try:
|
|
from lxml import etree
|
|
except ImportError:
|
|
etree = None
|
|
|
|
try:
|
|
import Evtx.Evtx as evtx
|
|
except ImportError:
|
|
evtx = None
|
|
|
|
SUSPICIOUS_PATH_PATTERNS = [
|
|
(r"\\temp\\", "temp_directory"),
|
|
(r"\\tmp\\", "temp_directory"),
|
|
(r"\\appdata\\", "appdata_directory"),
|
|
(r"\\users\\public\\", "public_user_directory"),
|
|
(r"\\programdata\\[^\\]+\.[^\\]+$", "loose_file_in_programdata"),
|
|
(r"powershell\.exe", "powershell_execution"),
|
|
(r"cmd\.exe\s*/c", "cmd_execution"),
|
|
(r"-enc\s+", "encoded_command"),
|
|
(r"-encodedcommand", "encoded_command"),
|
|
(r"frombase64string", "base64_decode"),
|
|
(r"downloadstring|downloadfile|webclient", "download_pattern"),
|
|
(r"invoke-expression|iex\s", "invoke_expression"),
|
|
(r"\\windows\\temp\\", "windows_temp"),
|
|
(r"mshta\.exe|regsvr32\.exe|rundll32\.exe|msiexec\.exe", "lolbin_execution"),
|
|
(r"sc\.exe\s+create", "sc_create_chain"),
|
|
(r"\\\$recycle\.bin\\", "recycle_bin"),
|
|
(r"\\perflog", "perflog_abuse"),
|
|
]
|
|
|
|
LEGITIMATE_SERVICE_PATHS = [
|
|
r"^\"?C:\\Windows\\System32\\",
|
|
r"^\"?C:\\Windows\\SysWOW64\\",
|
|
r"^\"?C:\\Program Files\\",
|
|
r"^\"?C:\\Program Files \(x86\)\\",
|
|
r"^\"?C:\\Windows\\Microsoft\.NET\\",
|
|
]
|
|
|
|
|
|
def parse_evtx_events(evtx_path):
|
|
"""Parse System.evtx and extract Event ID 7045 records."""
|
|
if not evtx:
|
|
raise RuntimeError("python-evtx not installed: pip install python-evtx")
|
|
events = []
|
|
ns = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"}
|
|
with evtx.Evtx(evtx_path) as log:
|
|
for record in log.records():
|
|
try:
|
|
xml = record.xml()
|
|
root = etree.fromstring(xml.encode("utf-8") if isinstance(xml, str) else xml)
|
|
event_id_el = root.find(".//e:System/e:EventID", ns)
|
|
if event_id_el is None or event_id_el.text != "7045":
|
|
continue
|
|
system = root.find(".//e:System", ns)
|
|
event_data = root.find(".//e:EventData", ns)
|
|
data_fields = {}
|
|
if event_data is not None:
|
|
for data in event_data.findall("e:Data", ns):
|
|
name = data.get("Name", "")
|
|
data_fields[name] = data.text or ""
|
|
time_el = system.find("e:TimeCreated", ns)
|
|
timestamp = time_el.get("SystemTime", "") if time_el is not None else ""
|
|
computer_el = system.find("e:Computer", ns)
|
|
computer = computer_el.text if computer_el is not None else ""
|
|
events.append({
|
|
"timestamp": timestamp,
|
|
"event_id": 7045,
|
|
"computer": computer,
|
|
"service_name": data_fields.get("ServiceName", ""),
|
|
"image_path": data_fields.get("ImagePath", ""),
|
|
"service_type": data_fields.get("ServiceType", ""),
|
|
"start_type": data_fields.get("StartType", ""),
|
|
"account_name": data_fields.get("AccountName", ""),
|
|
})
|
|
except Exception:
|
|
continue
|
|
return events
|
|
|
|
|
|
def analyze_service_path(image_path):
|
|
"""Analyze a service binary path for suspicious indicators."""
|
|
findings = []
|
|
path_lower = image_path.lower()
|
|
for pattern, indicator in SUSPICIOUS_PATH_PATTERNS:
|
|
if re.search(pattern, path_lower, re.IGNORECASE):
|
|
findings.append(indicator)
|
|
is_legitimate = any(re.match(p, image_path, re.IGNORECASE) for p in LEGITIMATE_SERVICE_PATHS)
|
|
risk_score = 0
|
|
if not is_legitimate:
|
|
risk_score += 20
|
|
risk_score += len(findings) * 15
|
|
risk_score = min(risk_score, 100)
|
|
return {
|
|
"suspicious_indicators": findings,
|
|
"legitimate_path": is_legitimate,
|
|
"risk_score": risk_score,
|
|
"risk_level": "CRITICAL" if risk_score >= 70 else "HIGH" if risk_score >= 50 else "MEDIUM" if risk_score >= 20 else "LOW",
|
|
}
|
|
|
|
|
|
def hunt_suspicious_services(evtx_path):
|
|
"""Main hunting function: parse events and analyze each service."""
|
|
events = parse_evtx_events(evtx_path)
|
|
results = []
|
|
for event in events:
|
|
analysis = analyze_service_path(event.get("image_path", ""))
|
|
entry = {**event, **analysis}
|
|
if entry.get("account_name", "").lower() == "localsystem" and not analysis["legitimate_path"]:
|
|
entry["risk_score"] = min(entry["risk_score"] + 20, 100)
|
|
entry["risk_level"] = "CRITICAL" if entry["risk_score"] >= 70 else entry["risk_level"]
|
|
entry["suspicious_indicators"].append("localsystem_nonstandard_path")
|
|
results.append(entry)
|
|
results.sort(key=lambda x: x.get("risk_score", 0), reverse=True)
|
|
return results
|
|
|
|
|
|
def generate_statistics(results):
|
|
"""Generate summary statistics from hunting results."""
|
|
total = len(results)
|
|
risk_counts = Counter(r.get("risk_level", "LOW") for r in results)
|
|
indicator_counts = Counter()
|
|
account_counts = Counter()
|
|
for r in results:
|
|
for ind in r.get("suspicious_indicators", []):
|
|
indicator_counts[ind] += 1
|
|
account_counts[r.get("account_name", "Unknown")] += 1
|
|
return {
|
|
"total_service_installations": total,
|
|
"risk_distribution": dict(risk_counts),
|
|
"top_indicators": dict(indicator_counts.most_common(10)),
|
|
"service_accounts": dict(account_counts.most_common(10)),
|
|
"critical_count": risk_counts.get("CRITICAL", 0),
|
|
"high_count": risk_counts.get("HIGH", 0),
|
|
}
|
|
|
|
|
|
def full_hunt(evtx_path):
|
|
"""Run comprehensive service installation threat hunt."""
|
|
results = hunt_suspicious_services(evtx_path)
|
|
stats = generate_statistics(results)
|
|
suspicious = [r for r in results if r.get("risk_score", 0) >= 20]
|
|
return {
|
|
"hunt_type": "Unusual Service Installation (T1543.003)",
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"evtx_file": evtx_path,
|
|
"statistics": stats,
|
|
"suspicious_services": suspicious[:30],
|
|
"mitre_technique": {
|
|
"id": "T1543.003",
|
|
"name": "Create or Modify System Process: Windows Service",
|
|
"tactic": "Persistence, Privilege Escalation",
|
|
},
|
|
"recommendation": "Investigate CRITICAL and HIGH services. Verify binary hashes against known-good baselines." if stats["critical_count"] + stats["high_count"] > 0
|
|
else "No high-risk service installations detected.",
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Service Installation Threat Hunting Agent (T1543.003)")
|
|
parser.add_argument("evtx", help="Path to System.evtx file")
|
|
sub = parser.add_subparsers(dest="command")
|
|
sub.add_parser("parse", help="Parse and list all Event ID 7045 records")
|
|
sub.add_parser("hunt", help="Hunt for suspicious service installations")
|
|
sub.add_parser("stats", help="Generate hunting statistics")
|
|
sub.add_parser("full", help="Full threat hunt report")
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "parse":
|
|
result = parse_evtx_events(args.evtx)
|
|
elif args.command == "hunt":
|
|
result = hunt_suspicious_services(args.evtx)
|
|
elif args.command == "stats":
|
|
results = hunt_suspicious_services(args.evtx)
|
|
result = generate_statistics(results)
|
|
elif args.command == "full" or args.command is None:
|
|
result = full_hunt(args.evtx)
|
|
else:
|
|
parser.print_help()
|
|
return
|
|
print(json.dumps(result, indent=2, default=str))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|