#!/usr/bin/env python3 # For authorized penetration testing and educational environments only. # Usage against targets without prior mutual consent is illegal. # It is the end user's responsibility to obey all applicable local, state and federal laws. """DCSync attack detection and analysis agent using impacket and ldap3.""" import json import sys import argparse from datetime import datetime try: import ldap3 from ldap3 import Server, Connection, ALL, NTLM except ImportError: print("Install: pip install ldap3") sys.exit(1) def check_dcsync_permissions(server_ip, domain, username, password): """Check which accounts have DCSync-capable permissions (Replicating Directory Changes).""" server = Server(server_ip, get_info=ALL) conn = Connection(server, user=f"{domain}\\{username}", password=password, authentication=NTLM, auto_bind=True) base_dn = ",".join([f"DC={p}" for p in domain.split(".")]) conn.search( search_base=base_dn, search_filter="(objectClass=domain)", attributes=["nTSecurityDescriptor"], ) dcsync_accounts = [] REPL_CHANGES_GUID = "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2" REPL_ALL_GUID = "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2" conn.search( search_base=base_dn, search_filter="(&(objectCategory=person)(objectClass=user)(adminCount=1))", attributes=["sAMAccountName", "distinguishedName", "memberOf"], ) for entry in conn.entries: dcsync_accounts.append({ "account": str(entry.sAMAccountName), "dn": str(entry.distinguishedName), "admin_count": True, "risk": "HIGH", "note": "adminCount=1 — potential DCSync privilege holder", }) conn.unbind() return dcsync_accounts def detect_dcsync_events(log_file=None): """Parse Windows Security event logs for DCSync indicators (Event IDs 4662, 4624).""" dcsync_indicators = { "4662": "Directory service access — replication operation", "4624": "Logon event — check for NTLM from unexpected source", } REPL_GUIDS = [ "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c", ] detections = [] if log_file: try: with open(log_file, "r") as f: events = json.load(f) for event in events: eid = str(event.get("EventID", "")) if eid == "4662": props = event.get("Properties", "") for guid in REPL_GUIDS: if guid in str(props).lower(): detections.append({ "event_id": eid, "timestamp": event.get("TimeCreated", ""), "account": event.get("SubjectUserName", ""), "operation": dcsync_indicators[eid], "guid_matched": guid, "severity": "CRITICAL", }) except (FileNotFoundError, json.JSONDecodeError) as e: detections.append({"error": str(e)}) return detections def generate_sigma_rule(): """Generate Sigma detection rule for DCSync activity.""" return { "title": "DCSync Attack Detected via Directory Replication", "status": "production", "logsource": {"product": "windows", "service": "security"}, "detection": { "selection": { "EventID": 4662, "Properties|contains": [ "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", ], }, "filter": {"SubjectUserName|endswith": "$"}, "condition": "selection and not filter", }, "level": "critical", "tags": ["attack.credential_access", "attack.t1003.006"], } def run_audit(server, domain, username, password, log_file=None): """Run DCSync persistence audit.""" print(f"\n{'='*60}") print(f" DCSYNC PERSISTENCE AUDIT") print(f" Domain: {domain} | Server: {server}") print(f" Generated: {datetime.utcnow().isoformat()} UTC") print(f"{'='*60}\n") accounts = check_dcsync_permissions(server, domain, username, password) print(f"--- PRIVILEGED ACCOUNTS ({len(accounts)}) ---") for a in accounts[:15]: print(f" [{a['risk']}] {a['account']}: {a['note']}") events = detect_dcsync_events(log_file) print(f"\n--- DCSYNC DETECTIONS ({len(events)}) ---") for e in events[:10]: if "error" not in e: print(f" [{e['severity']}] {e['account']} at {e['timestamp']}") sigma = generate_sigma_rule() print(f"\n--- SIGMA RULE ---") print(f" {sigma['title']}") print(f" Level: {sigma['level']}") return {"accounts": accounts, "detections": events, "sigma_rule": sigma} def main(): parser = argparse.ArgumentParser(description="DCSync Detection Agent") parser.add_argument("--server", required=True, help="Domain controller IP") parser.add_argument("--domain", required=True, help="AD domain (e.g., corp.local)") parser.add_argument("--username", required=True, help="LDAP bind username") parser.add_argument("--password", required=True, help="LDAP bind password") parser.add_argument("--log-file", help="Windows event log JSON export to analyze") parser.add_argument("--output", help="Save report to JSON file") args = parser.parse_args() report = run_audit(args.server, args.domain, args.username, args.password, args.log_file) if args.output: with open(args.output, "w") as f: json.dump(report, f, indent=2, default=str) print(f"\n[+] Report saved to {args.output}") if __name__ == "__main__": main()