Расшифровать и повторно зашифровать файлы cookie Chrome

Я пытаюсь расшифровать базу данных SQLite файлов cookie Chrome, переместить расшифрованные файлы cookie на другой компьютер (браузер), повторно зашифровать БД и реплицировать сеансы.

Вот что я планирую:

  1. Расшифруйте ключ AES из Local State в C:\Users\[username]\AppData\Local\Google\Chrome\User Data\Local State с помощью DPAPI.
  2. Используйте расшифрованный ключ для расшифровки базы данных файлов cookie в C:\Users\[username]\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies
  3. Скопируйте расшифрованную базу данных файлов cookie на другой компьютер.
  4. Сгенерируйте случайный ключ/одноразовый номер AES и зашифруйте базу данных файлов cookie в виде открытого текста, передаваемую на другой компьютер. Замените исходную базу данных файлов cookie на другом компьютере.
  5. Зашифруйте ключ AES с помощью DPAPI и замените соответствующую запись в Local State на другом компьютере.

И у меня есть следующие два файла Python для выполнения описанных выше действий:

encrypt.py:

from win32.win32crypt import CryptProtectData
import base64
import sqlite3
import os
from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
import decrypt
import json

def encrypt_dpapi_blob(decrypted_blob):
    encrypted_blob = CryptProtectData(decrypted_blob, DataDescr = "Google Chrome", OptionalEntropy=None, Reserved=None, PromptStruct=None, Flags=0)
    encrypted_blob = b'DPAPI' + encrypted_blob
    encrypted_blob_base64 = base64.b64encode(encrypted_blob)
    return encrypted_blob_base64

def encrypt_cookies(cookies_db, key):
    sqlite3.enable_callback_tracebacks(True)
    conn = sqlite3.connect(cookies_db)

    query = "SELECT name, encrypted_value FROM cookies"
    cursor = conn.execute(query)
    query_res = cursor.fetchall()

    for row in query_res:
        cookie_name, decrypted_value = row
        # print(f"Encrypting cookie: {cookie_name}")
        if decrypted_value is None or len(decrypted_value) == 0:
            # print("No decrypted value found.")
            continue

        aes_cipher = new(key=key, mode=MODE_GCM, nonce=decrypted_value[3:15])
        encrypted_value = aes_cipher.encrypt(decrypted_value[15: -16])
        # print(f"Encrypted cookie:\n  {decrypt.bytes_to_hex(encrypted_value)}\n  {encrypted_value}")

        verification_tag = decrypted_value[-16:]
        # print(f"Verification tag:\n  {decrypt.bytes_to_hex(verification_tag)}\n  {verification_tag}")

        nonce = decrypted_value[3:15]
        # print(f"Nonce:\n  {decrypt.bytes_to_hex(nonce)}\n  {nonce}")

        encrypted_cookie = b'\x76\x31\x30' +\
            nonce +\
            encrypted_value +\
            verification_tag

        query = f"UPDATE cookies SET encrypted_value = ? WHERE name = \"{cookie_name}\""
        params = [encrypted_cookie]
        cursor.execute(query, params)
        # print("")

    conn.commit()
    conn.close()

if __name__ == "__main__":
    cookies_db = os.path.join(os.getcwd(), "Cookies")
    # print(f"Decrypted key:\n  {decrypt.bytes_to_hex(key)}\n  {key}")

    key = os.urandom(32)
    encrypt_cookies(cookies_db, key)
    encrypted_key = encrypt_dpapi_blob(key)
    print(f"Encrypted key:\n  {str(encrypted_key, 'utf-8')}")

    local_state = json.load(open('Local State'))
    local_state['os_crypt']['encrypted_key'] = encrypted_key.decode()
    json.dump(local_state, open('Local State', 'w'))

decrypt.py:

from win32.win32crypt import CryptUnprotectData
import base64
import sqlite3
import os
from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
import sys
import json


def decrypt_dpapi_blob(encrypted_blob):
    encrypted_blob = base64.b64decode(encrypted_blob)[5:]  # Leading bytes "DPAPI" need to be removed
    decrypt_res = CryptUnprotectData(encrypted_blob, None, None, None, 0)
    return decrypt_res

def decrypt_cookies(cookies_db, key):
    sqlite3.enable_callback_tracebacks(True)
    conn = sqlite3.connect(cookies_db)

    query = "SELECT name, encrypted_value FROM cookies"
    cursor = conn.execute(query)
    query_res = cursor.fetchall()

    for row in query_res:
        cookie_name, encrypted_value = row
        # print(f"Decrypting cookie: {cookie_name}")
        if encrypted_value is None or len(encrypted_value) == 0:
            # print("No encrypted value found.")
            continue

        aes_cipher = new(key=key, mode=MODE_GCM, nonce=encrypted_value[3:15])
        decrypted_value = aes_cipher.decrypt(encrypted_value[15: -16])
        # print(f"Decrypted cookie:\n  {bytes_to_hex(decrypted_value)}\n  {decrypted_value}")
        if cookie_name == "BITBUCKETSESSIONID":
            print(f"Decrypted cookie (bitbucket): {decrypted_value.decode()}")

        verification_tag = encrypted_value[-16:]
        # print(f"Verification tag:\n  {bytes_to_hex(verification_tag)}\n  {verification_tag}")

        nonce = encrypted_value[3:15]
        # print(f"Nonce:\n  {bytes_to_hex(nonce)}\n  {nonce}")

        decrypted_cookie = b'\x76\x31\x30' +\
            nonce +\
            decrypted_value +\
            verification_tag

        query = f"UPDATE cookies SET encrypted_value = ? WHERE name = \"{cookie_name}\""
        params = [decrypted_cookie]
        cursor.execute(query, params)
        # print("")

    conn.commit()
    conn.close()

def bytes_to_hex(byte_data):
    return f"b'{''.join(f'\\x{byte:02x}' for byte in byte_data)}'"

if __name__ == "__main__":
    encrypted_key_base64 = json.load(open('Local State'))['os_crypt']['encrypted_key']

    # print(f"Encrypted key:\n  {encrypted_key_base64}")
    try:
        decrypted_key = decrypt_dpapi_blob(encrypted_key_base64)[1]
        print(f"Decrypted key:\n  {bytes_to_hex(decrypted_key)}")
    except Exception as e:
        print("Decryption failed:", str(e))
        sys.exit(1)
    
    # get current working directory path
    cookies_db = os.path.join(os.getcwd(), "Cookies")
    decrypt_cookies(cookies_db, decrypted_key)
    # print(f"Decrypted key:\n  {bytes_to_hex(decrypted_key)}")

С помощью этих функций я могу получить файл cookie в виде открытого текста и убедиться, что, если я вручную скопирую текст файла cookie в Chrome, я смогу получить целевой сеанс.

if cookie_name == "BITBUCKETSESSIONID":
            print(f"Decrypted cookie (bitbucket): {decrypted_value.decode()}")

Расшифровка и шифрование туда и обратно также работают без проблем.

Однако если я заменю измененный файл Cookies и файл Local State, Chrome не будет читать перенесенный файл cookie.

Могу я узнать, что здесь не так?


Как предложил Топако в комментариях, я изменил свои функции следующим образом:

  1. Использование существующего локального ключа AES на другом компьютере.
  2. Создать новый случайный одноразовый номер (nonce = os.urandom(12))
  3. Замените encrypt на encrypt_and_digest и decrypt на decrypt_and_verify.
  4. Сохраните новый тег подтверждения, возвращенный encrypt_and_digest и nonce в encrypted_cookie

... и вот новые функции: encrypt.py:

from win32.win32crypt import CryptProtectData
import base64
import sqlite3
import os
from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
import decrypt
import json
from os.path import expandvars

def encrypt_dpapi_blob(decrypted_blob):
    encrypted_blob = CryptProtectData(decrypted_blob, DataDescr = "Google Chrome", OptionalEntropy=None, Reserved=None, PromptStruct=None, Flags=0)
    encrypted_blob = b'DPAPI' + encrypted_blob
    encrypted_blob_base64 = base64.b64encode(encrypted_blob)
    return encrypted_blob_base64

def encrypt_cookies(cookies_db, key):
    sqlite3.enable_callback_tracebacks(True)
    conn = sqlite3.connect(cookies_db)

    query = "SELECT name, encrypted_value FROM cookies"
    cursor = conn.execute(query)
    query_res = cursor.fetchall()

    for row in query_res:
        cookie_name, decrypted_value = row
        # print(f"Encrypting cookie: {cookie_name}")
        if decrypted_value is None or len(decrypted_value) == 0:
            # print("No decrypted value found.")
            continue
        
        nonce = os.urandom(12)

        aes_cipher = new(key=key, mode=MODE_GCM, nonce=nonce)
        # encrypted_value = aes_cipher.encrypt(decrypted_value[15: -16]) # wrong
        encrypted_value, verification_tag = aes_cipher.encrypt_and_digest(decrypted_value[15: -16])
        # print(f"Encrypted cookie:\n  {decrypt.bytes_to_hex(encrypted_value)}\n  {encrypted_value}")

        # verification_tag = decrypted_value[-16:] # wrong
        # print(f"Verification tag:\n  {decrypt.bytes_to_hex(verification_tag)}\n  {verification_tag}")

        # nonce = decrypted_value[3:15] # wrong
        # print(f"Nonce:\n  {decrypt.bytes_to_hex(nonce)}\n  {nonce}")

        encrypted_cookie = b'\x76\x31\x30' +\
            nonce +\
            encrypted_value +\
            verification_tag

        query = f"UPDATE cookies SET encrypted_value = ? WHERE name = \"{cookie_name}\""
        params = [encrypted_cookie]
        cursor.execute(query, params)
        # print("")

    conn.commit()
    conn.close()

def get_local_state_key():
    local_state = json.load(open(expandvars('%LOCALAPPDATA%/Google/Chrome/User Data/Local State')))
    encrypted_key = local_state['os_crypt']['encrypted_key']
    decrypted_key = decrypt.decrypt_dpapi_blob(encrypted_key)[1]
    return decrypted_key

# Example usage
if __name__ == "__main__":
    cookies_db = os.path.join(os.getcwd(), "Cookies")
    # print(f"Decrypted key:\n  {decrypt.bytes_to_hex(key)}\n  {key}")

    # key = os.urandom(32)
    # Using existing key
    key = get_local_state_key()
    encrypt_cookies(cookies_db, key)
    # encrypted_key = encrypt_dpapi_blob(key)
    # print(f"Encrypted key:\n  {str(encrypted_key, 'utf-8')}")

    # wrong
    # local_state = json.load(open('Local State'))
    # local_state['os_crypt']['encrypted_key'] = encrypted_key.decode()
    # json.dump(local_state, open('Local State', 'w'))

decrypt.py:

from win32.win32crypt import CryptUnprotectData
import base64
import sqlite3
import os
from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
import sys
import json
import encrypt

def decrypt_dpapi_blob(encrypted_blob):
    encrypted_blob = base64.b64decode(encrypted_blob)[5:]  # Leading bytes "DPAPI" need to be removed
    decrypt_res = CryptUnprotectData(encrypted_blob, None, None, None, 0)
    return decrypt_res

def decrypt_cookies(cookies_db, key):
    sqlite3.enable_callback_tracebacks(True)
    conn = sqlite3.connect(cookies_db)

    query = "SELECT name, encrypted_value FROM cookies"
    cursor = conn.execute(query)
    query_res = cursor.fetchall()

    for row in query_res:
        cookie_name, encrypted_value = row
        # print(f"Decrypting cookie: {cookie_name}")
        if encrypted_value is None or len(encrypted_value) == 0:
            # print("No encrypted value found.")
            continue

        aes_cipher = new(key=key, mode=MODE_GCM, nonce=encrypted_value[3:15])
        # decrypted_value = aes_cipher.decrypt(encrypted_value[15: -16]) # wrong
        decrypted_value = aes_cipher.decrypt_and_verify(encrypted_value[15: -16], encrypted_value[-16:])
        # print(f"Decrypted cookie:\n  {bytes_to_hex(decrypted_value)}\n  {decrypted_value}")
        if cookie_name == "BITBUCKETSESSIONID":
            print(f"Decrypted cookie (bitbucket): {decrypted_value.decode()}")

        verification_tag = encrypted_value[-16:]
        # print(f"Verification tag:\n  {bytes_to_hex(verification_tag)}\n  {verification_tag}")

        nonce = encrypted_value[3:15]
        # print(f"Nonce:\n  {bytes_to_hex(nonce)}\n  {nonce}")

        decrypted_cookie = b'\x76\x31\x30' +\
            nonce +\
            decrypted_value +\
            verification_tag

        query = f"UPDATE cookies SET encrypted_value = ? WHERE name = \"{cookie_name}\""
        params = [decrypted_cookie]
        cursor.execute(query, params)
        # print("")

    conn.commit()
    conn.close()

# Custom function to display all bytes in the \x[something] format
def bytes_to_hex(byte_data):
    return f"b'{''.join(f'\\x{byte:02x}' for byte in byte_data)}'"

# Example usage
if __name__ == "__main__":
    # encrypted_key_base64 = json.load(open('Local State'))['os_crypt']['encrypted_key']

    # print(f"Encrypted key:\n  {encrypted_key_base64}")
    try:
        decrypted_key = encrypt.get_local_state_key()
        print(f"Decrypted key:\n  {bytes_to_hex(decrypted_key)}")
    except Exception as e:
        print("Decryption failed:", str(e))
        sys.exit(1)
    
    # get current working directory path
    cookies_db = os.path.join(os.getcwd(), "Cookies")
    decrypt_cookies(cookies_db, decrypted_key)
    # print(f"Decrypted key:\n  {bytes_to_hex(decrypted_key)}")

🤔 А знаете ли вы, что...
С Python можно создавать кросс-платформенные приложения для Windows, macOS и Linux.


417
1

Ответ:

Решено

Как было предложено Топако в комментариях и в соответствии с моими собственными попытками, я изменил свои функции следующим образом:

  1. Использование существующего локального ключа AES на другом компьютере.
  2. Создать новый случайный одноразовый номер (nonce = os.urandom(12))
  3. Измените шифрование на encrypt_and_digest и расшифруйте на decrypt_and_verify.
  4. Сохраните новый тег проверки, возвращаемый encrypt_and_digest и nonce, в Encrypt_cookie.
  5. Заменить двойные кавычки одинарными в SQL-запросе ... и вот новые функции:

encrypt.py:

import argparse
import json
import os
import base64
import sqlite3
from os.path import expandvars

from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
from win32.win32crypt import CryptProtectData # pip install pywin32

import decrypt


def encrypt_dpapi_blob(decrypted_blob):
    encrypted_blob = CryptProtectData(decrypted_blob, DataDescr = "Google Chrome", OptionalEntropy=None, Reserved=None, PromptStruct=None, Flags=0)
    encrypted_blob = b'DPAPI' + encrypted_blob
    encrypted_blob_base64 = base64.b64encode(encrypted_blob)
    return encrypted_blob_base64


def encrypt_cookies(cookies_db, key):
    sqlite3.enable_callback_tracebacks(True)
    conn = sqlite3.connect(cookies_db)

    query = "SELECT name, encrypted_value FROM cookies"
    cursor = conn.execute(query)
    query_res = cursor.fetchall()

    for row in query_res:
        cookie_name, decrypted_value = row
        if decrypted_value is None or len(decrypted_value) == 0:
            continue
        
        nonce = os.urandom(12)
        aes_cipher = new(key=key, mode=MODE_GCM, nonce=nonce)
        encrypted_value, verification_tag = aes_cipher.encrypt_and_digest(decrypted_value)

        encrypted_cookie = b'\x76\x31\x30' +\
            nonce +\
            encrypted_value +\
            verification_tag

        query = f"UPDATE cookies SET encrypted_value = ? WHERE name = '{cookie_name}'"
        params = [encrypted_cookie]
        cursor.execute(query, params)

    conn.commit()
    conn.close()


def get_local_state_key():
    local_state = json.load(open(expandvars('%LOCALAPPDATA%/Google/Chrome/User Data/Local State')))
    encrypted_key = local_state['os_crypt']['encrypted_key']
    decrypted_key = decrypt.decrypt_dpapi_blob(encrypted_key)[1]
    return decrypted_key


if __name__ == "__main__":
    # Arg: cookies_db
    parser = argparse.ArgumentParser()
    parser.add_argument("--cookies", help = "Name of the cookies database file", default = "Cookies")
    args = parser.parse_args()

    cookies_db = os.path.join(os.getcwd(), args.cookies)
    key = get_local_state_key()
    encrypt_cookies(cookies_db, key)

decrypt.py:

import argparse
import base64
import os
import sqlite3
import sys

from Cryptodome.Cipher.AES import new, MODE_GCM # pip install pycryptodomex
from win32.win32crypt import CryptUnprotectData # pip install pywin32

import encrypt


def decrypt_dpapi_blob(encrypted_blob):
    encrypted_blob = base64.b64decode(encrypted_blob)[5:]  # Leading bytes "DPAPI" need to be removed
    decrypt_res = CryptUnprotectData(encrypted_blob, None, None, None, 0)
    return decrypt_res


def decrypt_cookies(cookies_db, key):
    sqlite3.enable_callback_tracebacks(True)
    conn = sqlite3.connect(cookies_db)

    query = "SELECT name, encrypted_value FROM cookies"
    cursor = conn.execute(query)
    query_res = cursor.fetchall()

    for row in query_res:
        cookie_name, encrypted_value = row
        if encrypted_value is None or len(encrypted_value) == 0:
            continue

        verification_tag = encrypted_value[-16:]
        aes_cipher = new(key=key, mode=MODE_GCM, nonce=encrypted_value[3:15])
        decrypted_value = aes_cipher.decrypt_and_verify(ciphertext=encrypted_value[15: -16], received_mac_tag=verification_tag)
        # if cookie_name == "BITBUCKETSESSIONID":
        #     print(f"Decrypted cookie (bitbucket):\n  {decrypted_value.decode()}")
        #     print(f"Verification tag (bitbucket):\n  {bytes_to_hex(verification_tag)}")
        #     print(f"Nonce (bitbucket):\n  {bytes_to_hex(nonce)}")
        query = f"UPDATE cookies SET encrypted_value = ? WHERE name = '{cookie_name}'"
        params = [decrypted_value]
        cursor.execute(query, params)

    conn.commit()
    conn.close()


if __name__ == "__main__":
    try:
        decrypted_key = encrypt.get_local_state_key()
    except Exception as e:
        print("Decryption failed:", str(e))
        sys.exit(1)
    
    # Arg: cookies_db
    parser = argparse.ArgumentParser()
    parser.add_argument("--cookies", help = "Name of the cookies database file", default = "Cookies")
    args = parser.parse_args()
    cookies_db = os.path.join(os.getcwd(), args.cookies)

    decrypt_cookies(cookies_db, decrypted_key)

И теперь эти функции можно использовать для безошибочного переноса файлов cookie между компьютерами.