UserInfo.exe
An unknown binary has been found in one of the SMB shares that is suspected to be used as a storage space to store common provisioning tools. While it is indeed [[Support_SMB#binary|written in C# and built on the .NET framework]], it’s compiled to the x86 architecture. It allows me to be able to execute the binary within Kali using mono to inspect the flow of execution.
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe
usage: UserInfo.exe [options] [commands]
options:
-v|--verbose Verbose output
commands:
find Find a user
user Get information about a user
Executing the binary alone shows the help menu
While it has the -v
flag for verbose output, it supports 2 commands; find
and user
find
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe find --help
Usage: UserInfo.exe find [options]
Options:
-first First name
-last Last name
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe user --help
Usage: UserInfo.exe user [options]
Options:
-username Username
Additional help menu is shown for each command with the --help
flag
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe find
[-] At least one of -first or -last is required.
As the name suggests, the find
command appears to be used to find users, requiring at least one of the follow flags; -first
or -last
user
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe user
unable to parse command 'user' reason: Required option '-username' not found!
The user
command, on the other hand, requires the -username
flag and seems to return information about given user
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe user -username ford.victoria
[-] exception: Connect Error
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe -v user -username ford.victoria
[*] Getting data for ford.victoria
[-] exception: Connect Error
Testing out the execution with one of the domain users found during RID Cycling attack returns a connection error
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe find -last ford
[-] exception: Connect Error
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe -v find -last ford
[*] ldap query to use: (sn=ford)
[-] exception: Connect Error
While it shows the same connection error with the find
command, the -v
flag for verbose output reveals a critical information about the execution.
The find
command appears to be fetching user information via LDAP query
This would mean that the binary must be authenticating to the LDAP server as it requires a successful bind to fetch user information That being said, it is highly probable that the binary may contain a valid credential for authentication
I will open up Wireshark to inspect the outbound packets
Packet Capture
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono UserInfo.exe -v find -last ford
[*] LDAP query to use: (sn=ford)
[-] Exception: Connect Error
Attempting another find
command while Wireshark is running
2 DNS queries first got sent out by the binary
Interestingly, the binary appears to be misconfigured as those DNS queries are made to the domain itself,
support.htb
, instead of the supposed name server, which is dc.support.htb
”Misconfiguring” /etc/hosts
Since DNS queries are sent to the misconfigured host at
support.htb
, I would need to “misconfigure” the /etc/hosts
file on Kali as well
Executing the binary again shows a complete different result this time
after the initial 3-way tcp handshake, the binary sends out a LDAP simple authentication to the target LDAP server on the port
389
LDAP server on the port 389
is NOT encrypted, and data is sent across in CLEARTEXT
CLEARTEXT Credential
Inspecting the packet containing the LDAP simple authentication reveals the CLEARTEXT domain credential;
ldap
:nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz
While it is likely a valid domain as the ldap
account indeed exists, additional validation is never a bad idea
I will first undo the “misconfiguration” on my end
Validation
┌──(kali㉿kali)-[~/archive/htb/labs/support]
└─$ impacket-getTGT support.htb/ldap@dc.support.htb -dc-ip $IP
Impacket v0.11.0 - Copyright 2023 Fortra
password: nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz
[*] Saving ticket in ldap@dc.support.htb.ccache
Additional validation complete for the ldap
account
TGT generated for OPSEC
Decompile
Although the vulnerable custom binary has been already assessed and a CREARTEXT credential has been retrieved, decompilation can still be done to better understand the binary
Porting out the binary archive to a Windows host
I will then open it up in dnSpy
The
LdapQuery
class is defined in the UserInfo.Servies
namespace
There is also the Protected
class at the bottom
This is the
LdapQuery()
function that sends out the LDAP simple authentication.
The credential is stored to the password
variable, fetched from the Protected.getPassword()
function
The
Protected
class can be seen above, which includes the public getPassword()
function as well as the private variables, enc_password
and key
The getPassword()
function basically does the following;
- `byte[] array = Convert.FromBase64String(Protected.enc_password);
- decodes the base64-encoded password stored in the
enc_password
variable and stores the result in thearray
byte array.
- decodes the base64-encoded password stored in the
for (int i = 0; i < array.Length; i++) {array2[i] = (array[i] ^ Protected.key[i % Protected.key.Length] ^ 223);}
- iterates through each byte in the
array
- performs a bitwise XOR operation on each byte of the
array
with the corresponding byte from thekey
array (cycling through the key if necessary) and 223
- performs a bitwise XOR operation on each byte of the
- iterates through each byte in the
return Encoding.Default.GetString(array2);
- converts the modified byte array
array2
back to a string using the default encoding and returns it as the result of thegetPassword
method
- converts the modified byte array
Now that I understand how the getPassword()
function works, I could reverse engineer the process to decrypt the encrypted string (0Nv32PTwgYjzg9/8j5TbmvPd3e7WhtWWyuPsyO76/Y+U193E
) with the key array from the ASCII, armando
Reverse Engineering
using System;
internal class Program
{
public static void Main(string[] args)
{
string decryptedPassword = Protected.decryptPassword();
console.writeline("decrypted password: " + decryptedPassword);
}
}
internal class Protected
{
// This is the base64-encoded encrypted password
private static string enc_password = "0Nv32PTwgYjzg9/8j5TbmvPd3e7WhtWWyuPsyO76/Y+U193E";
// This is a byte array representation of the decryption key "armando"
private static byte[] key = System.Text.Encoding.ASCII.GetBytes("armando");
// This method performs a one-way decryption and returns the decrypted string.
public static string decryptPassword()
{
byte[] array = Convert.FromBase64String(Protected.enc_password);
byte[] array2 = array;
for (int i = 0; i < array.Length; i++)
{
array2[i] = (byte)(array[i] ^ Protected.key[i % Protected.key.Length] ^ 223);
}
return System.Text.Encoding.Default.GetString(array2);
}
}
This is the reverse-engineered program that I wrote
It does exactly the opposite of the getPassword()
function
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mcs decryption.cs -out:decryption.exe -debug
Cross-compiling with mcs
┌──(kali㉿kali)-[~/…/htb/labs/support/smb]
└─$ mono decryption.exe
decrypted password: nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz
There is the decrypted password; nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz
It matches the one I saw earlier in CLEARTEXT