---
name: Ad Discovery
slug: ad-discovery
category: Automation
description: Ad Discovery enumerates Active Directory domains to map hosts, signing settings, and attack paths during authorized testing. It supports unauthenticated and credentialed reconnaissance and records findings for orchestration.
github: "https://github.com/blacklanternsecurity/red-run/tree/main/skills/ad/ad-discovery"
language: Python
stars: 266
forks: 37
install: "npx degit https://github.com/blacklanternsecurity/red-run/tree/main/skills/ad/ad-discovery ~/.claude/skills/ad-discovery"
installs_to: ~/.claude/skills/ad-discovery
source_path: skills/ad/ad-discovery/SKILL.md
collection_size: 25
category_size: 1648
collection_url: "https://dirskills.com/collections/blacklanternsecurity/red-run"
added: 2026-09-02T05:19:24.372Z
last_synced: 2026-09-02T05:19:24.372Z
canonical_url: "https://dirskills.com/skills/ad-discovery"
---

# Ad Discovery

Ad Discovery enumerates Active Directory domains to map hosts, signing settings, and attack paths during authorized testing. It supports unauthenticated and credentialed reconnaissance and records findings for orchestration.

**Install:**

```bash
npx degit https://github.com/blacklanternsecurity/red-run/tree/main/skills/ad/ad-discovery ~/.claude/skills/ad-discovery
```

## README

# AD Attack Discovery

You are helping a penetration tester enumerate an Active Directory domain and
identify attack paths. All testing is under explicit written authorization.

This skill works at three access levels:
1. **No credentials** — network-level recon, poisoning, RID cycling
2. **Username only** — AS-REP roasting, Kerberos user validation
3. **Valid credentials** — full enumeration, BloodHound, ADCS, ACLs

## Engagement Logging

Check for `./engagement/` directory. If absent, proceed without logging.

When an engagement directory exists:
- Print `[ad-discovery] Activated → <target>` to the screen on activation.
- **Evidence** → save significant output to `engagement/evidence/` with
  descriptive filenames (e.g., `sqli-users-dump.txt`, `ssrf-aws-creds.json`).

## Scope Boundary

This skill covers Active Directory discovery — enumerating domain objects,
identifying misconfigurations, and reporting findings to the orchestrator.
When you confirm an exploitable finding — **STOP**.

Do not load or execute another skill. Do not continue past your scope boundary.
Instead, return to the orchestrator with:
  - What was found (vulns, credentials, access gained)
  - Detection details (finding type, affected objects, evidence)
  - Context for technique execution (credentials, DC hostname, domain name, etc.)

The orchestrator decides what runs next. Your job is to execute this skill
thoroughly and return clean findings.

**Stay in methodology.** Only use techniques documented in this skill. If you
encounter a scenario not covered here, note it and return — do not improvise
attacks, write custom exploit code, or apply techniques from other domains.
The orchestrator will provide specific guidance or route to a different skill.

You MUST NOT:
- Perform Kerberoasting or AS-REP roasting beyond identifying targets
- Exploit delegation misconfigurations
- Exploit ACL misconfigurations
- Perform credential dumping
- Forge tickets
- Perform coercion or relay attacks
- Exploit ADCS beyond enumeration

When you find exploitable attack paths, present routing recommendations in
your return summary. Do not continue past enumeration.

## State Management

Call `get_state_summary()` from the state MCP server to read current
engagement state. Use it to:
- Skip re-testing targets, parameters, or vulns already confirmed
- Leverage existing credentials or access for this technique
- Understand what's been tried and failed (check Blocked section)

### State Writes

Write actionable findings **immediately** via state so the orchestrator
can react in real time (via event watcher) instead of waiting for your full
return summary. Use these tools as you discover findings:

- `add_credential()` — valid credentials (pre-created computer accounts, gMSA readable, cleartext in descriptions/GPP/shares)
- `add_vuln()` — ADCS misconfigs (ESC1-ESC8), Kerberoastable accounts, coercion vectors, SMB signing disabled, LDAP signing not required
- `add_pivot()` — delegation paths, ACL abuse chains, trust relationships, new subnets from AD Sites
- `add_blocked()` — techniques attempted and failed (so orchestrator doesn't re-route)
Your return summary must include:
- New targets/hosts discovered (with ports and services)
- New credentials or tokens found
- Access gained or changed (user, privilege level, method)
- Vulnerabilities confirmed (with status and severity)
- Pivot paths identified (what leads where)
- Blocked items (what failed and why, whether retryable)

## Prerequisites

- Network access to the target domain (ports 88, 135, 389, 445, 636)
- For unauthenticated enumeration: just network access
- For authenticated enumeration: valid domain credentials (any privilege level)
- Tools: `netexec` (nxc), `bloodhound-python` or `rusthound-ce`, `certipy`,
  `bloodyAD`, `kerbrute`, Impacket suite (`GetUserSPNs.py`, `GetNPUsers.py`,
  `lookupsid.py`)

**Kerberos-first authentication** (when credentials are available):

This skill may start unauthenticated. Once credentials are obtained, switch
to Kerberos authentication for all subsequent enumeration:

```bash
# Get a TGT (password, hash, or AES key)
# Use getTGT.py or impacket-getTGT — both are the same tool (see Troubleshooting)
getTGT.py DOMAIN/user:'Password123'@dc.domain.local
# or with NTLM hash
getTGT.py DOMAIN/user@dc.domain.local -hashes :NTHASH

export KRB5CCNAME=user.ccache

# Then use -k -no-pass on all Impacket tools
# Use --use-kcache on NetExec
# Use -k on Certipy and bloodyAD
```

## Step 1: Initial Reconnaissance

Identify domain controllers and assess the network posture.

### Find Domain Controllers

```bash
# DNS SRV records
nslookup -type=srv _ldap._tcp.dc._msdcs.DOMAIN.LOCAL
nslookup -type=srv _kerberos._tcp.DOMAIN.LOCAL

# NetExec SMB scan — shows OS, signing, SMBv1
nxc smb 10.10.10.0/24

# NetExec generate /etc/hosts entries
nxc smb 10.10.10.0/24 --generate-hosts-file hosts
```

### Check Signing and Relay Posture

```bash
# SMB signing — signing:False = relay target
nxc smb 10.10.10.0/24 | grep -i "signing:False"

# LDAP signing — signing:None = relay to LDAP viable
nxc ldap DC01.DOMAIN.LOCAL

# Determine if LDAPS is available
nxc ldap DC01.DOMAIN.LOCAL --port 636
```

**Findings:**
- SMB signing disabled on non-DCs -> note for coercion/relay
- LDAP signing not required -> note for relay to LDAP
- Domain name, DC hostnames, OS versions -> record in the engagement state

**State writes:**
- SMB signing disabled → `add_vuln(title="SMB signing disabled on <host>", host="<host>", vuln_type="smb-signing", severity="medium")`
- LDAP signing not required → `add_vuln(title="LDAP signing not required on <host>", host="<host>", vuln_type="ldap-signing", severity="medium")`
- Domain name/hostnames discovered → `add_pivot(source="AD discovery", destination="<domain>/<hostname>", method="DNS/SMB enumeration")`

## Step 2: Unauthenticated Enumeration

Use when no valid credentials are available yet.

### Null Session / Guest Access

```bash
# SMB null session
nxc smb DC01.DOMAIN.LOCAL -u '' -p ''
nxc smb DC01.DOMAIN.LOCAL -u 'guest' -p ''

# enum4linux
enum4linux -a -u "" -p "" DC01.DOMAIN.LOCAL

# rpcclient
rpcclient -U "" -N DC01.DOMAIN.LOCAL -c "enumdomusers;enumdomgroups;querydominfo"
```

### RID Cycling (Unauthenticated User Enumeration)

```bash
# NetExec — enumerate users via RID brute force
nxc smb DC01.DOMAIN.LOCAL -u 'guest' -p '' --rid-brute 10000

# Impacket
lookupsid.py -no-pass 'guest@DC01.DOMAIN.LOCAL' 20000

# Extract just usernames
nxc smb DC01.DOMAIN.LOCAL -u '' -p '' --rid-brute \
  | awk -F'\\\\| ' '/SidTypeUser/ {print $3}' > users.txt
```

### Kerberos Username Enumeration

```bash
# kerbrute — validates usernames via Kerberos pre-auth responses
# Generates Event 4771, NOT 4625 (often less monitored)
kerbrute userenum -d DOMAIN.LOCAL --dc DC01.DOMAIN.LOCAL usernames.txt
```

Use output as username list for password spraying and AS-REP roasting checks.

### Unauthenticated ADCS CA Enumeration

```bash
# Enumerate Certificate Authorities via RPC — no credentials needed
nxc smb DC01.DOMAIN.LOCAL -M enum_ca
```

If CAs found, note for authenticated ADCS enumeration in Step 3 (certipy).

### LLMNR/NBT-NS/mDNS Poisoning Check

If network position allows, note LLMNR/NBT-NS traffic for Responder-based
hash capture. → STOP. Return to orchestrator with: DC IP, domain name, network
position, LLMNR/NBT-NS traffic details. Do not execute poisoning or relay
commands inline.

## Step 3: BloodHound Collection

The single highest-value enumeration step. Requires valid domain credentials.

### Linux (Remote Collection)

```bash
# bloodhound-python — LDAP-based, remote
bloodhound-python -d DOMAIN.LOCAL -u 'user' -p 'Password123' \
  -gc DC01.DOMAIN.LOCAL -c all -ns DC_IP

# rusthound-ce — faster, includes ADCS data
rusthound-ce -d DOMAIN.LOCAL -u 'user@DOMAIN.LOCAL' -p 'Password123' \
  -o /tmp/bloodhound -z --adcs

# With Kerberos auth
export KRB5CCNAME=user.ccache
bloodhound-python -d DOMAIN.LOCAL -u 'user' -k -no-pass \
  -gc DC01.DOMAIN.LOCAL -c all
```

### Windows (On-Host Collection)

```powershell
# SharpHound — full collection
.\SharpHound.exe -c all -d DOMAIN.LOCAL --searchforest

# Stealthier — DC-only mode (no host enumeration)
.\SharpHound.exe --CollectionMethod DCOnly

# OPSEC — throttle and randomize
.\SharpHound.exe -c all,GPOLocalGroup --throttle 10000 --jitter 23

# SOAPHound — uses ADWS instead of LDAP (avoids LDAP monitoring)
SOAPHound.exe --buildcache -c c:\temp\cache.txt
SOAPHound.exe -c c:\temp\cache.txt --bhdump -o c:\temp\bh-output
SOAPHound.exe -c c:\temp\cache.txt --certdump -o c:\temp\bh-output
```

### ADCS Certificate Data

```bash
# Certipy — full certificate template enumeration
certipy find 'DOMAIN/user:Password123@DC01.DOMAIN.LOCAL' \
  -output engagement/evidence/certipy-full-DOMAIN

# Find vulnerable templates only
certipy find 'DOMAIN/user:Password123@DC01.DOMAIN.LOCAL' -vulnerable -hide-admins \
  -output engagement/evidence/certipy-vulnerable-DOMAIN

# With Kerberos
certipy find 'DOMAIN/user@DC01.DOMAIN.LOCAL' -k \
  -output engagement/evidence/certipy-full-DOMAIN
```

**State writes:** Vulnerable ADCS templates found →
`add_vuln(title="ADCS <ESC_type> on <template>", host="<CA_host>", vuln_type="adcs", severity="high")`.

**Certipy output**: Always use `-output engagement/evidence/certipy-<label>`
to write results to the evidence directory. Without `-output`, certipy writes
`{timestamp}_Certipy.{json,txt}` to CWD, polluting the working directory.

**Certipy version notes:**
- Certipy v5.0+ removed the `-bloodhound` flag. Run `certipy find` without it
  — v5 outputs JSON by default. Import the JSON into BloodHound CE manually.
- If `-vulnerable` returns 0 results, re-run without it to get the full
  template list. Some ESC variants (ESC9-15) require manual analysis of the
  full output rather than certipy's built-in vulnerability checks.

### BloodHound Analysis Priorities

After importing data, run these queries first:
1. **Shortest Paths to Domain Admins** — identify the quickest win
2. **Kerberoastable Users** — prioritize by blast radius and pwdLastSet age
3. **AS-REP Roastable Users** — free hashes, no special access needed
4. **Unconstrained Delegation** — TGT harvesting opportunities
5. **Dangerous ACLs** — GenericAll/WriteDACL/WriteOwner on high-value targets
6. **ADCS Attack Paths** — ESC1-ESC15 (requires certipy data import)
7. **Owned -> Domain Admins** — mark owned principals and find chains

## Step 4: Targeted Enumeration

Deeper enumeration beyond BloodHound. Run these based on what BloodHound reveals.

### Password Policy

```bash
# NetExec
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' --pass-pol

# enum4linux
enum4linux -u 'user' -p 'Password123' -P DC01.DOMAIN.LOCAL

# PowerView
(Get-DomainPolicy)."SystemAccess"
```

Record lockout threshold, observation window, complexity requirements. Report
for password spraying decisions.

### Fine-Grained Password Policies (PSOs)

```bash
# Fine-grained password policies — different groups may have different lockout rules
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M pso
```

PSOs override default domain policy for specific groups. A service account
group may have no lockout while user accounts lock at 5 attempts. Report any
PSOs found — they affect password spraying decisions.

### Pre-Windows 2000 Computer Accounts

**High-value quick win.** Pre-created computer accounts (created via "Pre-Windows
2000 Compatible" checkbox in ADUC or `New-ADComputer`) often have their password
set to the sAMAccountName in lowercase, minus the trailing `$`. For example,
`MS01$` has password `ms01`. This is a common administrative oversight that
yields machine account credentials with zero noise.

```bash
# Identify pre-created computer accounts and test default passwords
# The module checks for accounts and attempts TGT with default password
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M pre2k
```

**What pre2k checks:**
1. Finds computer accounts in the "Pre-Windows 2000 Compatible Access" group
   or with `userAccountControl` flags indicating pre-creation
2. Tests each account with `sAMAccountName.rstrip('$').lower()` as the password
3. Attempts to obtain a TGT — if successful, the password is confirmed

**Why this matters:**
- Machine accounts often have broad read rights (BloodHound "Owned" marking)
- Machine accounts in groups like "Domain Secure Servers" can read gMSA passwords
- Machine accounts may have constrained delegation, RBCD, or other privileges
- A pre2k computer account is functionally equivalent to a domain user credential
  but with a machine account's group memberships and trust level

If pre2k finds valid machine credentials → write immediately:
`add_credential(username="MACHINE$", secret="machine", source="pre2k module on <DC>")`.
Also record in your return summary: account name, confirmed password, and any
group memberships or privileges visible from LDAP.

### NetExec Module Sweep

Run these modules as a batch during authenticated enumeration. They are all
non-destructive and low-privilege — any domain user can run them.

#### Quick-Win Credential Checks

```bash
# User descriptions — frequently contain cleartext passwords
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M get-desc-users

# GPP cpassword in SYSVOL (MS14-025)
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M gpp_password

# Autologon credentials from registry.xml in SYSVOL
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M gpp_autologin
```

**User descriptions** are a common source of cleartext passwords — admins often
document initial passwords or service account passwords in the AD description
field. Any result containing password-like strings is a credential finding.
Write immediately: `add_credential(username=..., secret=..., source="AD description field")`.

#### Coercion & Relay Surface

```bash
# WebClient service (WebDAV) — enables HTTP-based NTLM coercion
# Critical: if WebDAV is running, coercion can use HTTP instead of SMB
# which bypasses SMB signing and enables relay to LDAP/ADCS
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M webdav

# Print Spooler — enables PrinterBug/SpoolSample coercion
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M spooler

# Broad coercion vulnerability check (check-only mode — no LISTENER set)
# Tests for PetitPotam, PrinterBug, DFSCoerce, ShadowCoerce, MSEven, etc.
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M coerce_plus
```

Note all coercion-eligible hosts. WebDAV is especially valuable — it enables
HTTP-based coercion that works even when SMB signing is enforced.

**State writes:** Coercion-eligible hosts →
`add_vuln(title="Coercion: <type> on <host>", host="<host>", vuln_type="coercion", severity="medium")`.

#### Attack Surface Mapping

```bash
# AV/EDR detection — identifies endpoint security products
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M enum_av

# MachineAccountQuota — can current user add computer accounts?
# MAQ > 0 enables resource-based constrained delegation (RBCD) attacks
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M maq

# Obsolete/EOL operating systems — unpatched, vulnerable to known CVEs
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M obsolete

# BadSuccessor (dMSA) — CVE-2025-21293, privilege escalation via
# delegated Managed Service Accounts
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M badsuccessor
```

#### Network Topology

```bash
# DNS records from AD — all A/AAAA/CNAME/SRV records in the domain
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M get-network

# AD Sites and Subnets — reveals internal network segmentation
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M subnets

# Additional network interfaces on hosts (multi-homed pivot targets)
nxc smb DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M ioxidresolver
```

#### SCCM and Infrastructure

```bash
# SCCM discovery via LDAP
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M sccm

# Entra ID (Azure AD Connect) sync server
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M entra-id

# DNS zones allowing nonsecure dynamic updates (ADIDNS poisoning)
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' -M dns-nonsecure
```

If SCCM found → note for SCCM exploitation. If Entra ID Connect server
found → high-value target (stores cleartext AD sync credentials). If
nonsecure DNS updates allowed → note for ADIDNS poisoning (enables
MITM/coercion without LLMNR).

#### ADIDNS Zone ACL Check

**Standard in most AD environments.** Authenticated Users have CreateChild on
AD-Integrated DNS zones by default, allowing any domain user to create new A
records. This enables ADIDNS poisoning: redirect hostnames that don't have DNS
records (or that you can overwrite) to the attackbox for credential capture.

```bash
# Check zone ACL — look for Authenticated Users with CreateChild
# Use dacledit to read the ACL on the DNS zone object
dacledit.py -action read -target-dn \
  'DC=DOMAIN.LOCAL,CN=MicrosoftDNS,DC=DomainDnsZones,DC=DOMAIN,DC=LOCAL' \
  'DOMAIN/user:Password123@DC01.DOMAIN.LOCAL'

# List existing DNS records (look for gaps — hostnames referenced but missing)
# dnstool.py is from krbrelayx toolkit
python3 /opt/krbrelayx/dnstool.py -u 'DOMAIN\user' -p 'Password123' \
  -r '*' --action query DC01.DOMAIN.LOCAL
```

**Cross-reference with engagement state:** Check for unreachable hostnames in
the pivot map or blocked items — linked server data sources, SPN hostnames,
or service references that resolved to nothing. If an expected hostname has no
DNS A record and Authenticated Users can create records, this is a high-value
pivot: create an A record pointing to the attackbox, then capture credentials
when the service authenticates.

**State writes:** ADIDNS CreateChild confirmed →
`add_vuln(title="ADIDNS: Authenticated Users can create DNS records", host="<DC>", vuln_type="adidns-poisoning", severity="critical")`.
Cross-reference with unreachable hostnames →
`add_pivot(source="ADIDNS poisoning", destination="<hostname> → attackbox for credential capture", method="Create A record for <hostname> pointing to attackbox, trigger service authentication, capture with Responder")`.

### SPN Enumeration (Kerberoasting Targets)

```bash
# Impacket — list accounts with SPNs
GetUserSPNs.py DOMAIN/user:'Password123' -dc-ip DC_IP

# NetExec
nxc ldap DC01.DOMAIN.LOCAL -u 'user' -p 'Password123' --kerberoasting output.txt

# Rubeus — stats only (no ticket requests)
.\Rubeus.exe kerberoast /stats
```

If SPNs found on user accounts → STOP. Report: DC IP, domain name, SPN list,
current credentials. Do not request or crack service tickets inline.

### AS-REP Roastable Accounts

```bash
# Impacket — enumerate users without pre-auth
GetNPUsers.py DOMAIN/user:'Password123' -dc-ip DC_IP

# bloodyAD — LDAP filter for DONT_REQ_PREAUTH
bloodyAD -u user -p 'Password123' -d DOMAIN.LOCAL --host DC_IP \
  get search --filter '(&(userAccountControl:1.2.840.113556.1.4.803:=4194304)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))' \
  --attr sAMAccountName
```

If found → STOP. Report: DC IP, domain name, AS-REP roastable user list,
current credentials. Do not request or crack AS-REP hashes inline.

### Delegation Enumeration

```bash
# Unconstrained delegation
bloodyAD -u user -p 'Password123' -d DOMAIN.LOCAL --host DC_IP \
  get search --filter '(userAccountControl:1.2.840.113556.1.4.803:=524288)' \
  --attr sAMAccountName,dNSHostName

# Constrained delegation
bloodyAD -u user -p 'Password123' -d DOMAIN.LOCAL --host DC_IP \
  get search --filter '(msDS-AllowedToDelegateTo=*)' \
  --attr sAMAccountName,msDS-AllowedToDelegateTo

# RBCD
bloodyAD -u user -p 'Password123' -d DOMAIN.LOCAL --host DC_IP \
  get search --filter '(msDS-AllowedToActOnBehalfOfOtherIdentity=*)' \
  --attr sAMAccountName

# PowerView
Get-DomainComputer -Unconstrained
Get-DomainUser -TrustedToAuth
Get-DomainComputer -TrustedToAuth
```

**S
