From f4f19e2605ca144b5c8ac18445867392a5623fb0 Mon Sep 17 00:00:00 2001 From: azoxlpf <213314124+azoxlpf@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:38:18 +0100 Subject: [PATCH] reuse delegated ST across Kerberos RPC connections --- nxc/protocols/smb.py | 16 ++++++++++++---- nxc/protocols/smb/atexec.py | 4 +++- nxc/protocols/smb/mmcexec.py | 3 ++- nxc/protocols/smb/passpol.py | 2 ++ nxc/protocols/smb/samrfunc.py | 13 +++++++++---- nxc/protocols/smb/samruser.py | 3 ++- nxc/protocols/smb/smbexec.py | 3 ++- nxc/protocols/smb/wmiexec.py | 3 ++- 8 files changed, 34 insertions(+), 13 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 491bf2f1a7..14503bcff8 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -129,6 +129,7 @@ def __init__(self, args, db, host): self.protocol = "SMB" self.is_guest = None self.isdc = None + self.delegated_st = None connection.__init__(self, args, db, host) @@ -361,6 +362,7 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", self.logger.debug(f"Attempting to do Kerberos Login with useCache: {useCache}") tgs = None + self.delegated_st = None if self.args.delegate: kerb_pass = "" self.username = self.args.delegate @@ -376,6 +378,8 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", if self.args.generate_st: self.save_st(tgs, sk, spn if self.args.delegate_spn else None) + self.delegated_st = tgs + self.conn.kerberosLogin(self.username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache, TGS=tgs) if "Unix" not in self.server_os: self.check_if_admin() @@ -843,7 +847,8 @@ def execute(self, payload=None, get_output=False, methods=None) -> str: self.args.share, logger=self.logger, timeout=self.args.dcom_timeout, - tries=self.args.get_output_tries + tries=self.args.get_output_tries, + st=self.delegated_st, ) self.logger.info("Executed command via wmiexec") break @@ -871,7 +876,8 @@ def execute(self, payload=None, get_output=False, methods=None) -> str: self.args.share, logger=self.logger, timeout=self.args.dcom_timeout, - tries=self.args.get_output_tries + tries=self.args.get_output_tries, + st=self.delegated_st, ) self.logger.info("Executed command via mmcexec") break @@ -894,7 +900,8 @@ def execute(self, payload=None, get_output=False, methods=None) -> str: self.hash, self.logger, self.args.get_output_tries, - self.args.share + self.args.share, + st=self.delegated_st, ) self.logger.info("Executed command via atexec") break @@ -919,7 +926,8 @@ def execute(self, payload=None, get_output=False, methods=None) -> str: self.args.share, self.port, self.logger, - self.args.get_output_tries + self.args.get_output_tries, + st=self.delegated_st, ) self.logger.info("Executed command via smbexec") break diff --git a/nxc/protocols/smb/atexec.py b/nxc/protocols/smb/atexec.py index 2300c03678..b017fef4bd 100755 --- a/nxc/protocols/smb/atexec.py +++ b/nxc/protocols/smb/atexec.py @@ -13,7 +13,7 @@ class TSCH_EXEC: def __init__(self, target, share_name, username, password, domain, doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None, # These options are used by the schtask_as module, except the run_task_as # that defaults to NT AUTHORITY\System user (SID S-1-5-18) if not specified - run_task_as="S-1-5-18", run_cmd=None, output_filename=None, task_name=None, output_file_location=None): + run_task_as="S-1-5-18", run_cmd=None, output_filename=None, task_name=None, output_file_location=None, st=None): self.__target = target self.__username = username self.__password = password @@ -30,6 +30,7 @@ def __init__(self, target, share_name, username, password, domain, doKerberos=Fa self.__tries = tries self.__output_filename = None self.__share = share + self.__st = st self.logger = logger # Optional args for finetuning the task execution, e.g. used in nxc/modules/schtask_as.py @@ -62,6 +63,7 @@ def __init__(self, target, share_name, username, password, domain, doKerberos=Fa self.__lmhash, self.__nthash, self.__aesKey, + TGS=self.__st, ) self.__rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost) diff --git a/nxc/protocols/smb/mmcexec.py b/nxc/protocols/smb/mmcexec.py index b964c8ff14..2299cf1294 100644 --- a/nxc/protocols/smb/mmcexec.py +++ b/nxc/protocols/smb/mmcexec.py @@ -59,7 +59,7 @@ class MMCEXEC: - def __init__(self, target, share_name, username, password, domain, smbconnection, doKerberos=False, aesKey=None, kdcHost=None, remoteHost=None, hashes=None, share=None, logger=None, timeout=None, tries=None): + def __init__(self, target, share_name, username, password, domain, smbconnection, doKerberos=False, aesKey=None, kdcHost=None, remoteHost=None, hashes=None, share=None, logger=None, timeout=None, tries=None, st=None): self.__target = target self.__username = username self.__password = password @@ -101,6 +101,7 @@ def __init__(self, target, share_name, username, password, domain, smbconnection doKerberos=self.__doKerberos, kdcHost=self.__kdcHost, remoteHost=self.__remoteHost, + TGS=st, ) try: iInterface = self.__dcom.CoCreateInstanceEx(string_to_bin("49B2791A-B1AE-4C90-9B8E-E860BA07F889"), IID_IDispatch) diff --git a/nxc/protocols/smb/passpol.py b/nxc/protocols/smb/passpol.py index 875e344945..736103b2d8 100644 --- a/nxc/protocols/smb/passpol.py +++ b/nxc/protocols/smb/passpol.py @@ -26,6 +26,7 @@ def __init__(self, connection): self.doKerberos = connection.kerberos self.host = connection.host self.kdcHost = connection.kdcHost + self.delegated_st = connection.delegated_st self.protocols = PassPolDump.KNOWN_PROTOCOLS.keys() self.pass_pol = {} @@ -57,6 +58,7 @@ def dump(self): self.lmhash, self.nthash, self.aesKey, + TGS=self.delegated_st, doKerberos=self.doKerberos, kdcHost=self.kdcHost, remote_host=self.host, diff --git a/nxc/protocols/smb/samrfunc.py b/nxc/protocols/smb/samrfunc.py index 5a4cc0f1e0..9e272bc3c1 100644 --- a/nxc/protocols/smb/samrfunc.py +++ b/nxc/protocols/smb/samrfunc.py @@ -35,8 +35,9 @@ def __init__(self, connection): if self.password is None: self.password = "" - self.samr_query = SAMRQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.remote_name, remote_host=self.host, lmhash=self.lmhash, nthash=self.nthash, kerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey, logger=self.logger) - self.lsa_query = LSAQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.remote_name, remote_host=self.host, lmhash=self.lmhash, nthash=self.nthash, kdcHost=self.kdcHost, kerberos=self.doKerberos, aesKey=self.aesKey, logger=self.logger) + self.delegated_st = connection.delegated_st + self.samr_query = SAMRQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.remote_name, remote_host=self.host, lmhash=self.lmhash, nthash=self.nthash, kerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey, logger=self.logger, st=self.delegated_st) + self.lsa_query = LSAQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.remote_name, remote_host=self.host, lmhash=self.lmhash, nthash=self.nthash, kdcHost=self.kdcHost, kerberos=self.doKerberos, aesKey=self.aesKey, logger=self.logger, st=self.delegated_st) def get_builtin_groups(self, group): domains = self.samr_query.get_domains() @@ -85,7 +86,7 @@ def get_local_users(self, group, domain_handle): class SAMRQuery: - def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", lmhash="", nthash="", kerberos=None, kdcHost="", aesKey="", logger=None): + def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", lmhash="", nthash="", kerberos=None, kdcHost="", aesKey="", logger=None, st=None): self.__username = username self.__password = password self.__domain = domain @@ -97,6 +98,7 @@ def __init__(self, username="", password="", domain="", port=445, remote_name="" self.__remote_host = remote_host self.__kerberos = kerberos self.__kdcHost = kdcHost + self.__st = st self.logger = logger self.dce = self.get_dce() self.server_handle = self.get_server_handle() @@ -115,6 +117,7 @@ def get_transport(self): self.__lmhash, self.__nthash, self.__aesKey, + TGS=self.__st, doKerberos=self.__kerberos, kdcHost=self.__kdcHost, ) @@ -178,7 +181,7 @@ def get_alias_members(self, domain_handle, alias_id): class LSAQuery: - def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", lmhash="", nthash="", kdcHost="", aesKey="", kerberos=None, logger=None): + def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", lmhash="", nthash="", kdcHost="", aesKey="", kerberos=None, logger=None, st=None): self.__username = username self.__password = password self.__domain = domain @@ -190,6 +193,7 @@ def __init__(self, username="", password="", domain="", port=445, remote_name="" self.__remote_host = remote_host self.__kdcHost = kdcHost self.__kerberos = kerberos + self.__st = st self.dce = self.get_dce() self.policy_handle = self.get_policy_handle() self.logger = logger @@ -210,6 +214,7 @@ def get_transport(self): self.__lmhash, self.__nthash, self.__aesKey, + TGS=self.__st, ) return rpc_transport diff --git a/nxc/protocols/smb/samruser.py b/nxc/protocols/smb/samruser.py index 43a6c8685a..538fb40fab 100644 --- a/nxc/protocols/smb/samruser.py +++ b/nxc/protocols/smb/samruser.py @@ -27,6 +27,7 @@ def __init__(self, connection): self.doKerberos = connection.kerberos self.host = connection.host self.kdcHost = connection.kdcHost + self.delegated_st = connection.delegated_st self.protocols = UserSamrDump.KNOWN_PROTOCOLS.keys() self.users = [] self.rpc_transport = None @@ -51,7 +52,7 @@ def dump(self, requested_users=None, dump_path=None): self.logger.debug(f"Invalid Protocol: {protocol}") self.logger.debug(f"Trying protocol {protocol}") - self.rpc_transport = transport.SMBTransport(self.addr, port, r"\samr", self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, doKerberos=self.doKerberos, kdcHost=self.kdcHost, remote_host=self.host) + self.rpc_transport = transport.SMBTransport(self.addr, port, r"\samr", self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, TGS=self.delegated_st, doKerberos=self.doKerberos, kdcHost=self.kdcHost, remote_host=self.host) try: self.fetch_users(requested_users, dump_path) break diff --git a/nxc/protocols/smb/smbexec.py b/nxc/protocols/smb/smbexec.py index 73dd59f2ab..7c21e9f585 100755 --- a/nxc/protocols/smb/smbexec.py +++ b/nxc/protocols/smb/smbexec.py @@ -8,7 +8,7 @@ class SMBEXEC: - def __init__(self, host, share_name, smbconnection, username="", password="", domain="", doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, share=None, port=445, logger=None, tries=None): + def __init__(self, host, share_name, smbconnection, username="", password="", domain="", doKerberos=False, aesKey=None, remoteHost=None, kdcHost=None, hashes=None, share=None, port=445, logger=None, tries=None, st=None): self.__host = host self.__share_name = share_name self.__port = port @@ -61,6 +61,7 @@ def __init__(self, host, share_name, smbconnection, username="", password="", do self.__lmhash, self.__nthash, self.__aesKey, + TGS=st, ) self.__rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost) diff --git a/nxc/protocols/smb/wmiexec.py b/nxc/protocols/smb/wmiexec.py index 6608e08391..8eef46ff21 100755 --- a/nxc/protocols/smb/wmiexec.py +++ b/nxc/protocols/smb/wmiexec.py @@ -10,7 +10,7 @@ class WMIEXEC: - def __init__(self, target, share_name, username, password, domain, smbconnection, doKerberos=False, aesKey=None, kdcHost=None, remoteHost=None, hashes=None, share=None, logger=None, timeout=None, tries=None): + def __init__(self, target, share_name, username, password, domain, smbconnection, doKerberos=False, aesKey=None, kdcHost=None, remoteHost=None, hashes=None, share=None, logger=None, timeout=None, tries=None, st=None): self.__target = target self.__username = username self.__password = password @@ -55,6 +55,7 @@ def __init__(self, target, share_name, username, password, domain, smbconnection doKerberos=self.__doKerberos, kdcHost=self.__kdcHost, remoteHost=self.__remoteHost, + TGS=st, ) iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login) flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__remoteHost, self.__timeout)