Repository: fortra/impacket
Pull request #1919: Support channel binding and ldap signing for ntlm and kerberos auth
Author: login: zblurx
Labels: name: enhancement
description: Implemented features can be improved or revised
Created: 2025-03-13T15:13:47Z | Merged: 2025-04-15T22:56:18Z

Add support for LDAP signing on SASL NTLM and Kerberos, and support for LDAPS channel binding on Kerberos

Before:
![image](https://github.com/user-attachments/assets/8b607275-28f5-4322-9ab0-d8ac3c8a9820)

After:
![image](https://github.com/user-attachments/assets/f3b728cf-6686-4e53-8e77-0b2bf40a5c72)



Diff:
diff --git a/impacket/krb5/gssapi.py b/impacket/krb5/gssapi.py
index 44cbefb9c4..4a7b2ef739 100644
--- a/impacket/krb5/gssapi.py
+++ b/impacket/krb5/gssapi.py
@@ -57,6 +57,48 @@
 
 KRB5_AP_REQ = struct.pack('<H', 0x1)
 
+# Kerberos OID: 1.2.840.113554.1.2.2
+KRB_OID = b'\x06\t*\x86H\x86\xf7\x12\x01\x02\x02'
+
+class MechIndepToken():
+    
+    def __init__(self, data=None, oid=KRB_OID):
+        self.data = data
+        self.token_oid = oid
+    
+    @staticmethod
+    def from_bytes(data=None):
+        if data[0:1] != b'\x60':
+            raise Exception('Incorrect token data!')
+        data = data[1:]
+        length, data = MechIndepToken.get_length(data)
+        token_data = data[0:length]
+        oid_length, data = MechIndepToken.get_length(token_data[1:])
+        token_oid = token_data[0:oid_length+2]
+        data = token_data[oid_length+2:]
+        return MechIndepToken(data, token_oid)
+
+    @staticmethod
+    def get_length(data):
+        if data[0] < 128:
+            return data[0], data
+        else:
+            bytes_count = data[0] - 128
+            return int.from_bytes(data[1:1+bytes_count], byteorder='big', signed=False), data[1+bytes_count:]
+
+    @staticmethod
+    def encode_length(length):
+        if length < 128:
+            return length.to_bytes(1, byteorder = 'big', signed = False)
+        else:
+            lb = length.to_bytes((length.bit_length() + 7) // 8, 'big')
+            return (128+len(lb)).to_bytes(1, byteorder = 'big', signed = False) + lb
+
+    def to_bytes(self):
+        temp = self.token_oid + self.data
+        temp = b'\x60' + self.encode_length(len(temp)) + temp
+        return temp[:-len(self.data)], self.data
+
 # 1.1.1. Initial Token - Checksum field
 class CheckSumField(Structure):
     structure = (
@@ -196,6 +238,70 @@ def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt
     def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None):
         return self.GSS_Wrap(sessionKey, data, sequenceNumber, direction, encrypt, authData)
 
+    def GSS_Wrap_LDAP(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True):
+       
+        if encrypt:
+            data += b'\x01'
+
+        token = self.WRAP()
+        token['SGN_ALG'] = GSS_HMAC
+        token['SEAL_ALG'] = GSS_RC4
+
+        if direction == 'init':
+            token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\x00'*4
+        else:
+            token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\xff'*4
+
+        # Random confounder :)
+        token['Confounder'] = b(''.join([rand.choice(string.ascii_letters) for _ in range(8)]))
+
+        Ksign = HMAC.new(sessionKey.contents, b'signaturekey\0', MD5).digest()
+        Sgn_Cksum = MD5.new(struct.pack('<L',13) + token.getData()[:8] + token['Confounder'] + data).digest()
+
+        Klocal = bytearray()
+        from builtins import bytes
+        for n in bytes(sessionKey.contents):
+            Klocal.append( n ^ 0xF0)
+
+        Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest()
+        Kcrypt = HMAC.new(Kcrypt,struct.pack('>L', sequenceNumber), MD5).digest()
+        
+        Sgn_Cksum = HMAC.new(Ksign, Sgn_Cksum, MD5).digest()
+
+        token['SGN_CKSUM'] = Sgn_Cksum[:8]
+
+        Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest()
+        Kseq = HMAC.new(Kseq, token['SGN_CKSUM'], MD5).digest()
+
+        token['SND_SEQ'] = ARC4.new(Kseq).encrypt(token['SND_SEQ'])
+
+        if encrypt is False:
+            sspi_wrap = MechIndepToken.from_bytes(data)
+
+            wrap = self.WRAP(sspi_wrap.data[:32])
+            data = sspi_wrap.data[32:]
+            
+            Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest()
+            Kseq = HMAC.new(Kseq, wrap['SGN_CKSUM'], MD5).digest()
+
+            snd_seq = ARC4.new(Kseq).encrypt(wrap['SND_SEQ'])
+
+            Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest()
+            Kcrypt = HMAC.new(Kcrypt,snd_seq[:4], MD5).digest()
+            rc4 = ARC4.new(Kcrypt)
+            cipherText = rc4.decrypt(token['Confounder'] + data)[8:]
+            return cipherText[:-1], None
+        
+        elif encrypt is True:
+            rc4 = ARC4.new(Kcrypt)
+            token['Confounder'] = rc4.encrypt(token['Confounder'])
+            cipherText = rc4.encrypt(data)
+            finalData, cipherText = MechIndepToken(token.getData() + cipherText, KRB_OID).to_bytes()
+            return cipherText, finalData
+
+    def GSS_Unwrap_LDAP(self, sessionKey, data, sequenceNumber, direction = 'init'):
+        return self.GSS_Wrap_LDAP(sessionKey, data, sequenceNumber, direction, encrypt=False)
+
 class GSSAPI_AES():
     checkSumProfile = None
     cipherType = None
@@ -291,6 +397,52 @@ def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction = 'init', encry
 
         return plainText[:-(token['EC']+len(self.WRAP()))], None
 
+    def GSS_Wrap_LDAP(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, use_padding=False):
+        token = self.WRAP()
+
+        cipher = self.cipherType()
+
+        pad = 0
+        if use_padding:
+        # Let's pad the data
+            pad = (cipher.blocksize - (len(data) % cipher.blocksize)) & 15
+            padStr = b'\xFF' * pad
+            data += padStr
+
+        # The RRC field ([RFC4121] section 4.2.5) is 12 if no encryption is requested or 28 if encryption 
+        # is requested. The RRC field is chosen such that all the data can be encrypted in place.
+        rrc = 28
+
+        token['Flags'] = 6
+        token['EC'] = pad
+        token['RRC'] = 0
+        token['SND_SEQ'] = struct.pack('>Q',sequenceNumber)
+
+        cipherText = cipher.encrypt(sessionKey, KG_USAGE_INITIATOR_SEAL,  data + token.getData(), None)
+        token['RRC'] = rrc
+
+        cipherText = self.rotate(cipherText, token['RRC'] + token['EC'])
+
+        #nn = self.unrotate(cipherText, token['RRC'] + token['EC'])
+        # ret1 = cipherText[len(self.WRAP()) + token['RRC'] + token['EC']:]
+        # ret2 = token.getData() + cipherText[:len(self.WRAP()) + token['RRC'] + token['EC']]
+        ret1 = cipherText
+        ret2 = token.getData()
+
+        return ret1, ret2
+
+    def GSS_Unwrap_LDAP(self, sessionKey, data, sequenceNumber, direction = 'init'):
+
+        cipher = self.cipherType()
+        token = self.WRAP(data[:16])
+
+        rotated = data[16:]
+ 
+        cipherText = self.unrotate(rotated, token['RRC'] + token['EC'])
+        plainText = cipher.decrypt(sessionKey, KG_USAGE_ACCEPTOR_SEAL,  cipherText)
+
+        return plainText[:-(token['EC']+ 16)], None
+
 class GSSAPI_AES256(GSSAPI_AES):
     checkSumProfile = crypto._SHA1AES256
     cipherType = crypto._AES256CTS
diff --git a/impacket/ldap/ldap.py b/impacket/ldap/ldap.py
index 9a6f0dae74..3313c40f30 100644
--- a/impacket/ldap/ldap.py
+++ b/impacket/ldap/ldap.py
@@ -36,8 +36,8 @@
 from impacket.ldap.ldapasn1 import Filter, Control, SimplePagedResultsControl, ResultCode, Scope, DerefAliases, Operation, \
     KNOWN_CONTROLS, CONTROL_PAGEDRESULTS, NOTIFICATION_DISCONNECT, KNOWN_NOTIFICATIONS, BindRequest, SearchRequest, \
     SearchResultDone, LDAPMessage
-from impacket.ntlm import getNTLMSSPType1, getNTLMSSPType3
-from impacket.spnego import SPNEGO_NegTokenInit, SPNEGO_NegTokenResp, TypesMech
+from impacket.ntlm import getNTLMSSPType1, getNTLMSSPType3, VERSION, MAC
+from impacket.spnego import SPNEGO_NegTokenInit, SPNEGO_NegTokenResp, SPNEGOCipher, TypesMech
 
 try:
     import OpenSSL
@@ -68,7 +68,7 @@
 
 
 class LDAPConnection:
-    def __init__(self, url, baseDN='', dstIp=None):
+    def __init__(self, url, baseDN='', dstIp=None, signing=True):
         """
         LDAPConnection class
 
@@ -84,6 +84,7 @@ def __init__(self, url, baseDN='', dstIp=None):
         self._socket = None
         self._baseDN = baseDN
         self._dstIp = dstIp
+        self.__signing = signing
 
         if url.startswith('ldap://'):
             self._dstPort = 389
@@ -92,21 +93,36 @@ def __init__(self, url, baseDN='', dstIp=None):
         elif url.startswith('ldaps://'):
             self._dstPort = 636
             self._SSL = True
+            self.__signing = False
             self._dstHost = url[8:]
         elif url.startswith('gc://'):
             self._dstPort = 3268
             self._SSL = False
+            self.__signing = False
             self._dstHost = url[5:]
         else:
             raise LDAPSessionError(errorString="Unknown URL prefix: '%s'" % url)
 
+        self.__binded = False
+
+        ### SASL Auth LDAP Signing arguments
+        self.sequenceNumber = 0
+        
+        # Kerberos
+        self.__auth_type = None
+        self.__gss = None
+        self.__sessionKey = None
+
+        # NTLM
+        self.__spnego_cipher_blob = None
+
         # Try to connect
         if self._dstIp is not None:
             targetHost = self._dstIp
         else:
             targetHost = self._dstHost
 
-        LOG.debug('Connecting to %s, port %d, SSL %s' % (targetHost, self._dstPort, self._SSL))
+        LOG.debug('Connecting to %s, port %d, SSL %s, signing %s' % (targetHost, self._dstPort, self._SSL, self.__signing))
         try:
             af, socktype, proto, _, sa = socket.getaddrinfo(targetHost, self._dstPort, 0, socket.SOCK_STREAM)[0]
             self._socket = socket.socket(af, socktype, proto)
@@ -125,6 +141,27 @@ def __init__(self, url, baseDN='', dstIp=None):
             self._socket.connect(sa)
             self._socket.do_handshake()
 
+    def generateChannelBindingValue(self):
+        # From: https://github.com/ly4k/ldap3/commit/87f5760e5a68c2f91eac8ba375f4ea3928e2b9e0#diff-c782b790cfa0a948362bf47d72df8ddd6daac12e5757afd9d371d89385b27ef6R1383
+        from hashlib import md5
+        # Ugly but effective, to get the digest of the X509 DER in bytes
+        peer_cert_digest_str = self._socket.get_peer_certificate().digest('sha256').decode()
+        peer_cert_digest_bytes = bytes.fromhex(peer_cert_digest_str.replace(':', ''))
+    
+        channel_binding_struct = b''
+        initiator_address = b'\x00'*8
+        acceptor_address = b'\x00'*8
+
+        # https://datatracker.ietf.org/doc/html/rfc5929#section-4
+        application_data_raw = b'tls-server-end-point:' + peer_cert_digest_bytes
+        len_application_data = len(application_data_raw).to_bytes(4, byteorder='little', signed = False)
+        application_data = len_application_data
+        application_data += application_data_raw
+        channel_binding_struct += initiator_address
+        channel_binding_struct += acceptor_address
+        channel_binding_struct += application_data
+        return md5(channel_binding_struct).digest()
+
     def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None,
                       TGS=None, useCache=True):
         """
@@ -157,8 +194,9 @@ def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey=
 
         # Importing down here so pyasn1 is not required if kerberos is not used.
         from impacket.krb5.ccache import CCache
+        from impacket.krb5.gssapi import GSSAPI, GSS_C_CONF_FLAG, GSS_C_INTEG_FLAG, GSS_C_SEQUENCE_FLAG, GSS_C_REPLAY_FLAG
         from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set
-        from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
+        from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, CheckSumField
         from impacket.krb5 import constants
         from impacket.krb5.types import Principal, KerberosTime, Ticket
         import datetime
@@ -219,7 +257,24 @@ def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey=
 
         authenticator['cusec'] = now.microsecond
         authenticator['ctime'] = KerberosTime.to_asn1(now)
-
+        
+        authenticator['cksum'] = noValue
+        authenticator['cksum']['cksumtype'] = 0x8003
+
+        chkField = CheckSumField()
+        chkField['Lgth'] = 16
+
+        chkField['Flags'] = GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG
+
+        # If TLS is used, setup channel binding
+        
+        if self._SSL:
+            chkField['Bnd'] = self.generateChannelBindingValue()
+        if self.__signing:
+            chkField['Flags'] |= GSS_C_CONF_FLAG
+            chkField['Flags'] |= GSS_C_INTEG_FLAG
+        authenticator['cksum']['checksum'] = chkField.getData()
+        authenticator['seq-number'] = 0
         encodedAuthenticator = encoder.encode(authenticator)
 
         # Key Usage 11
@@ -231,28 +286,33 @@ def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey=
         apReq['authenticator'] = noValue
         apReq['authenticator']['etype'] = cipher.enctype
         apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator
-
         blob['MechToken'] = encoder.encode(apReq)
-
+        
         # Done with the Kerberos saga, now let's get into LDAP
 
         bindRequest = BindRequest()
         bindRequest['version'] = 3
-        bindRequest['name'] = user
+        bindRequest['name'] = 'user'
         bindRequest['authentication']['sasl']['mechanism'] = 'GSS-SPNEGO'
         bindRequest['authentication']['sasl']['credentials'] = blob.getData()
-
+        
         response = self.sendReceive(bindRequest)[0]['protocolOp']
-
         if response['bindResponse']['resultCode'] != ResultCode('success'):
             raise LDAPSessionError(
                 errorString='Error in bindRequest -> %s: %s' % (response['bindResponse']['resultCode'].prettyPrint(),
                                                                 response['bindResponse']['diagnosticMessage'])
             )
+        
+        self.__auth_type = "KRB5"
+        self.__binded = True
+
+        if self.__signing:
+            self.__sessionKey = sessionKey
+            self.__gss = GSSAPI(cipher)
 
         return True
 
-    def login(self, user='', password='', domain='', lmhash='', nthash='', authenticationChoice='sicilyNegotiate'):
+    def login(self, user='', password='', domain='', lmhash='', nthash='', authenticationChoice='sasl'):
         """
         logins into the target system
 
@@ -313,25 +373,7 @@ def login(self, user='', password='', domain='', lmhash='', nthash='', authentic
             # If TLS is used, setup channel binding
             channel_binding_value = b''
             if self._SSL:
-                # From: https://github.com/ly4k/ldap3/commit/87f5760e5a68c2f91eac8ba375f4ea3928e2b9e0#diff-c782b790cfa0a948362bf47d72df8ddd6daac12e5757afd9d371d89385b27ef6R1383
-                from hashlib import md5
-                # Ugly but effective, to get the digest of the X509 DER in bytes
-                peer_cert_digest_str = self._socket.get_peer_certificate().digest('sha256').decode()
-                peer_cert_digest_bytes = bytes.fromhex(peer_cert_digest_str.replace(':', ''))
-            
-                channel_binding_struct = b''
-                initiator_address = b'\x00'*8
-                acceptor_address = b'\x00'*8
-
-                # https://datatracker.ietf.org/doc/html/rfc5929#section-4
-                application_data_raw = b'tls-server-end-point:' + peer_cert_digest_bytes
-                len_application_data = len(application_data_raw).to_bytes(4, byteorder='little', signed = False)
-                application_data = len_application_data
-                application_data += application_data_raw
-                channel_binding_struct += initiator_address
-                channel_binding_struct += acceptor_address
-                channel_binding_struct += application_data
-                channel_binding_value = md5(channel_binding_struct).digest()
+                channel_binding_value = self.generateChannelBindingValue()
 
             # NTLM Auth
             type3, exportedSessionKey = getNTLMSSPType3(negotiate, bytes(type2), user, password, domain, lmhash, nthash, channel_binding_value=channel_binding_value)
@@ -349,10 +391,11 @@ def login(self, user='', password='', domain='', lmhash='', nthash='', authentic
                 except TypeError:
                     pass
 
-            bindRequest['name'] = user
-
+            bindRequest['name'] = ''
+            self.version = VERSION()
+            self.version['ProductMajorVersion'], self.version['ProductMinorVersion'], self.version['ProductBuild'] = 10, 0, 19041
             # NTLM Negotiate
-            negotiate = getNTLMSSPType1('', domain)
+            negotiate = getNTLMSSPType1('', domain, signingRequired=self.__signing, use_ntlmv2=True, version=self.version)
 
             blob = SPNEGO_NegTokenInit()
             blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']]
@@ -372,12 +415,19 @@ def login(self, user='', password='', domain='', lmhash='', nthash='', authentic
             serverSaslCreds = response['bindResponse']['serverSaslCreds']
             spnegoTokenResp = SPNEGO_NegTokenResp(serverSaslCreds.asOctets())
             type2 = spnegoTokenResp['ResponseToken']
-
+            
+            # channel binding
+            channel_binding_value = b''
+            if self._SSL:
+                channel_binding_value = self.generateChannelBindingValue()
+            
             # NTLM Auth
-            type3, exportedSessionKey = getNTLMSSPType3(negotiate, type2, user, password, domain, lmhash, nthash)
+            type3, exportedSessionKey = getNTLMSSPType3(negotiate, type2, user, password, domain, lmhash, nthash, service='ldap', version=self.version, use_ntlmv2=True, channel_binding_value=channel_binding_value)
             blob = SPNEGO_NegTokenResp()
             blob['ResponseToken'] = type3.getData()
-
+            if self.__signing:
+                self.__spnego_cipher_blob = SPNEGOCipher(flags=negotiate['flags'], randomSessionKey=exportedSessionKey)
+                blob['mechListMIC'] = self.__spnego_cipher_blob.sign(b'0\x0c\x06\n+\x06\x01\x04\x01\x827\x02\x02\n', 0, reset_cipher=True).getData()
             bindRequest['authentication']['sasl']['mechanism'] = 'GSS-SPNEGO'
             bindRequest['authentication']['sasl']['credentials'] = blob.getData()
             response = self.sendReceive(bindRequest)[0]['protocolOp']
@@ -389,9 +439,35 @@ def login(self, user='', password='', domain='', lmhash='', nthash='', authentic
                 errorString='Error in bindRequest -> %s: %s' % (response['bindResponse']['resultCode'].prettyPrint(),
                                                                 response['bindResponse']['diagnosticMessage'])
             )
-
+        
+        self.__auth_type = f"NTLM-{authenticationChoice}"
+        self.__binded = True
         return True
 
+    def encrypt(self, data):
+        if self.__auth_type == "KRB5":
+            data, signature = self.__gss.GSS_Wrap_LDAP(self.__sessionKey, data, self.sequenceNumber)
+            data = signature + data
+            data = len(data).to_bytes(4, byteorder = 'big', signed = False) + data
+        elif self.__auth_type == "NTLM-sasl":
+            signature, data = self.__spnego_cipher_blob.encrypt(data)
+            data = signature.getData() + data
+            data = len(data).to_bytes(4, byteorder = 'big', signed = False) + data
+        else:
+            raise(f"Encryption not implemented for {self.__auth_type} protocol")
+        return data
+
+    def decrypt(self, data):
+        if self.__auth_type == "KRB5":
+            data = data[4:]
+            data, _ = self.__gss.GSS_Unwrap_LDAP(self.__sessionKey, data, 0, direction='init')
+        elif self.__auth_type == "NTLM-sasl":
+            data= data[4:]
+            signature, data = self.__spnego_cipher_blob.decrypt(data)
+        else:
+            raise(f"Decryption not implemented for {self.__auth_type} protocol")
+        return data
+
     def search(self, searchBase=None, scope=None, derefAliases=None, sizeLimit=0, timeLimit=0, typesOnly=False,
                searchFilter='(objectClass=*)', attributes=None, searchControls=None, perRecordCallback=None):
         if searchBase is None:
@@ -471,6 +547,9 @@ def send(self, request, controls=None):
 
         data = encoder.encode(message)
 
+        if self.__binded and self.__signing:
+            data = self.encrypt(data)
+            self.sequenceNumber += 1
         return self._socket.sendall(data)
 
     def recv(self):
@@ -484,8 +563,11 @@ def recv(self):
             data += recvData
 
         response = []
+        if self.__binded and self.__signing:
+                data = self.decrypt(data)
         while len(data) > 0:
             try:
+                # need to decrypt before
                 message, remaining = decoder.decode(data, asn1Spec=LDAPMessage())
             except SubstrateUnderrunError:
                 # We need more data
diff --git a/impacket/ntlm.py b/impacket/ntlm.py
index 5c95711f74..499b741f3a 100644
--- a/impacket/ntlm.py
+++ b/impacket/ntlm.py
@@ -40,10 +40,10 @@
 DEFAULT_LM_HASH = binascii.unhexlify('AAD3B435B51404EEAAD3B435B51404EE')
 
 def computeResponse(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash='', nthash='',
-                    use_ntlmv2=USE_NTLMv2, channel_binding_value=b''):
+                    use_ntlmv2=USE_NTLMv2, channel_binding_value=b'', service='cifs'):
     if use_ntlmv2:
         return computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password,
-                                     lmhash, nthash, use_ntlmv2=use_ntlmv2, channel_binding_value=channel_binding_value)
+                                     lmhash, nthash, use_ntlmv2=use_ntlmv2, channel_binding_value=channel_binding_value, service=service)
     else:
         return computeResponseNTLMv1(flags, serverChallenge, clientChallenge, serverName, domain, user, password,
                                      lmhash, nthash, use_ntlmv2=use_ntlmv2)
@@ -410,7 +410,7 @@ class NTLMAuthChallengeResponse(Structure):
         ('flags','<L'),
         ('VersionLen','_-Version','self.checkVersion(self["flags"])'), 
         ('Version',':=""'),
-        ('MICLen','_-MIC','self.checkMIC(self["flags"])'),
+        ('MICLen','_-MIC','self.checkMIC(self)'),
         ('MIC',':=""'),
         ('domain_name',':'),
         ('user_name',':'),
@@ -461,16 +461,15 @@ def checkVersion(flags):
         return 8
 
     @staticmethod
-    def checkMIC(flags):
+    def checkMIC(struct):
         # TODO: Find a proper way to check the MIC is in there
-        if flags is not None:
-           if flags & NTLMSSP_NEGOTIATE_VERSION == 0:
-              return 0
-        return 16
+        if 'MIC' in struct.fields:
+            return len(struct['MIC'])
+        return 0
                                                                                 
     def getData(self):
-        self['domain_offset']=64+self.checkMIC(self["flags"])+self.checkVersion(self["flags"])
-        self['user_offset']=64+self.checkMIC(self["flags"])+self.checkVersion(self["flags"])+len(self['domain_name'])
+        self['domain_offset']=64+self.checkMIC(self)+self.checkVersion(self["flags"])
+        self['user_offset']=64+self.checkMIC(self)+self.checkVersion(self["flags"])+len(self['domain_name'])
         self['host_offset']=self['user_offset']+len(self['user_name'])
         self['lanman_offset']=self['host_offset']+len(self['host_name'])
         self['ntlm_offset']=self['lanman_offset']+len(self['lanman'])
@@ -579,7 +578,7 @@ def ntlmssp_DES_encrypt(key, challenge):
 
 # High level functions to use NTLMSSP
 
-def getNTLMSSPType1(workstation='', domain='', signingRequired = False, use_ntlmv2 = USE_NTLMv2):
+def getNTLMSSPType1(workstation='', domain='', signingRequired = False, use_ntlmv2 = USE_NTLMv2, version = None):
     # Let's do some encoding checks before moving on. Kind of dirty, but found effective when dealing with
     # international characters.
     import sys
@@ -605,13 +604,17 @@ def getNTLMSSPType1(workstation='', domain='', signingRequired = False, use_ntlm
     auth['flags'] |= NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY | NTLMSSP_NEGOTIATE_UNICODE | \
                      NTLMSSP_REQUEST_TARGET |  NTLMSSP_NEGOTIATE_128 | NTLMSSP_NEGOTIATE_56
 
+    if version is not None:
+        auth['flags'] |= NTLMSSP_NEGOTIATE_VERSION
+        auth['os_version'] = version
+
     # We're not adding workstation / domain fields this time. Normally Windows clients don't add such information but,
     # we will save the workstation name to be used later.
     auth.setWorkstation(workstation)
 
     return auth
 
-def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash = '', use_ntlmv2 = USE_NTLMv2, channel_binding_value = b''):
+def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash = '', use_ntlmv2 = USE_NTLMv2, channel_binding_value = b'', service='cifs', version=None):
 
     # Safety check in case somebody sent password = None.. That's not allowed. Setting it to '' and hope for the best.
     if password is None:
@@ -650,7 +653,7 @@ def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash =
 
     ntResponse, lmResponse, sessionBaseKey = computeResponse(ntlmChallenge['flags'], ntlmChallenge['challenge'],
                                                              clientChallenge, serverName, domain, user, password,
-                                                             lmhash, nthash, use_ntlmv2, channel_binding_value = channel_binding_value)
+                                                             lmhash, nthash, use_ntlmv2, channel_binding_value = channel_binding_value, service=service)
 
     # Let's check the return flags
     if (ntlmChallenge['flags'] & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) == 0:
@@ -716,6 +719,9 @@ def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash =
         ntlmChallengeResponse['lanman'] = b'\x00'
     else:
         ntlmChallengeResponse['lanman'] = lmResponse
+
+    if version is not None:
+        ntlmChallengeResponse['Version'] = version
     ntlmChallengeResponse['ntlm'] = ntResponse
     if encryptedRandomSessionKey is not None: 
         ntlmChallengeResponse['session_key'] = encryptedRandomSessionKey
@@ -923,19 +929,17 @@ def LMOWFv2( user, password, domain, lmhash = ''):
 
 
 def computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash='',
-                          nthash='', use_ntlmv2=USE_NTLMv2, channel_binding_value=b''):
-
+                          nthash='', use_ntlmv2=USE_NTLMv2, channel_binding_value=b'', service='cifs'):
     responseServerVersion = b'\x01'
     hiResponseServerVersion = b'\x01'
     responseKeyNT = NTOWFv2(user, password, domain, nthash)
-
     av_pairs = AV_PAIRS(serverName)
     # In order to support SPN target name validation, we have to add this to the serverName av_pairs. Otherwise we will
     # get access denied
     # This is set at Local Security Policy -> Local Policies -> Security Options -> Server SPN target name validation
     # level
     if TEST_CASE is False:
-        av_pairs[NTLMSSP_AV_TARGET_NAME] = 'cifs/'.encode('utf-16le') + av_pairs[NTLMSSP_AV_HOSTNAME][1]
+        av_pairs[NTLMSSP_AV_TARGET_NAME] = f"{service}/".encode('utf-16le') + av_pairs[NTLMSSP_AV_DNS_HOSTNAME][1]
         if av_pairs[NTLMSSP_AV_TIME] is not None:
            aTime = av_pairs[NTLMSSP_AV_TIME][1]
         else:
diff --git a/impacket/spnego.py b/impacket/spnego.py
index e1f77cb479..b752f1239b 100644
--- a/impacket/spnego.py
+++ b/impacket/spnego.py
@@ -19,6 +19,9 @@
 from __future__ import print_function
 from struct import pack, unpack, calcsize
 
+from impacket import ntlm
+from Cryptodome.Cipher import ARC4
+
 ############### GSS Stuff ################
 GSS_API_SPNEGO_UUID              = b'\x2b\x06\x01\x05\x05\x02'
 ASN1_SEQUENCE                    = 0x30
@@ -29,6 +32,7 @@
 ASN1_MECH_TOKEN                  = 0xa2
 ASN1_SUPPORTED_MECH              = 0xa1
 ASN1_RESPONSE_TOKEN              = 0xa2
+ASN1_MECH_LIST_MIC               = 0xa3
 ASN1_ENUMERATED                  = 0x0a
 MechTypes = {
 b'+\x06\x01\x04\x01\x827\x02\x02\n': 'NTLMSSP - Microsoft NTLM Security Support Provider',
@@ -296,12 +300,29 @@ def getData(self):
                asn1encode( self['NegState'] ))))
         else:
             # Client resp
-            ans += asn1encode(
-               pack('B', ASN1_SEQUENCE) +
-               asn1encode(
-               pack('B', ASN1_RESPONSE_TOKEN) +
-               asn1encode(
-               pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken']))))
+            if 'mechListMIC' in self.fields:
+                ans += asn1encode(
+                    pack('B', ASN1_SEQUENCE) +
+                    asn1encode(
+                        pack('B', ASN1_RESPONSE_TOKEN) +
+                        asn1encode(
+                            pack('B', ASN1_OCTET_STRING) + 
+                            asn1encode(self['ResponseToken'])
+                        ) +
+                        pack('B', ASN1_MECH_LIST_MIC) +
+                        asn1encode(
+                            pack('B', ASN1_OCTET_STRING) + 
+                            asn1encode(self['mechListMIC'])
+                        )
+                    )
+                )
+            else:
+                ans += asn1encode(
+                    pack('B', ASN1_SEQUENCE) +
+                    asn1encode(
+                    pack('B', ASN1_RESPONSE_TOKEN) +
+                    asn1encode(
+                    pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken']))))
         return ans
 
 class SPNEGO_NegTokenInit(GSSAPI):
@@ -389,3 +410,70 @@ def getData(self):
 
         self['Payload'] = ans
         return GSSAPI.getData(self)
+
+class SPNEGOCipher:
+    def __init__(self, flags, randomSessionKey):
+        self.__flags = flags
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey)
+            self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server")
+            self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey)
+            self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server")
+            
+            # Preparing the keys handle states
+            cipher3 = ARC4.new(self.__clientSealingKey)
+            self.__clientSealingHandle = cipher3.encrypt
+            cipher4 = ARC4.new(self.__serverSealingKey)
+            self.__serverSealingHandle = cipher4.encrypt
+        else:
+            # Same key for everything
+            self.__clientSigningKey = randomSessionKey
+            self.__serverSigningKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            cipher = ARC4.new(self.__clientSigningKey)
+            self.__clientSealingHandle = cipher.encrypt
+            self.__serverSealingHandle = cipher.encrypt
+        self.__sequence = 0
+
+    def encrypt(self, plain_data):
+        sealedMessage, signature =  ntlm.SEAL(self.__flags, 
+                self.__clientSigningKey, 
+                self.__clientSealingKey,  
+                plain_data, 
+                plain_data, 
+                self.__sequence, 
+                self.__clientSealingHandle)
+
+        self.__sequence += 1
+
+        return signature, sealedMessage
+
+    def decrypt(self, answer):
+        answer, signature =  ntlm.SEAL(self.__flags, 
+                self.__serverSigningKey, 
+                self.__serverSealingKey,  
+                answer[:16], 
+                answer[16:], 
+                self.__sequence, 
+                self.__serverSealingHandle)
+    
+        self.__sequence += 1
+
+        return signature, answer
+    
+    def sign(self,data, seqNum=0, reset_cipher=False):
+        signature = ntlm.MAC(self.__flags, self.__clientSealingHandle, self.__clientSigningKey, seqNum, data)
+        if reset_cipher:
+            if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+                cipher3 = ARC4.new(self.__clientSealingKey)
+                self.__clientSealingHandle = cipher3.encrypt
+                cipher4 = ARC4.new(self.__serverSealingKey)
+                self.__serverSealingHandle = cipher4.encrypt
+            else:
+                cipher = ARC4.new(self.__clientSigningKey)
+                self.__clientSealingHandle = cipher.encrypt
+                self.__serverSealingHandle = cipher.encrypt
+        self.__sequence += 1
+        return signature
+
diff --git a/tests/SMB_RPC/test_ntlm.py b/tests/SMB_RPC/test_ntlm.py
index e1ee6400af..531c2c4e8d 100644
--- a/tests/SMB_RPC/test_ntlm.py
+++ b/tests/SMB_RPC/test_ntlm.py
@@ -114,7 +114,7 @@ def test_ntlmv1(self):
         ntlmChallengeResponse['ntlm'] = ntResponse
         ntlmChallengeResponse['session_key'] = encryptedSessionKey
         hexdump(ntlmChallengeResponse.getData())
-        self.assertEqual(ntlmChallengeResponse.getData(), bytearray(b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00|\x00\x00\x00\x18\x00\x18\x00\x94\x00\x00\x00\x0c\x00\x0c\x00X\x00\x00\x00\x08\x00\x08\x00d\x00\x00\x00\x10\x00\x10\x00l\x00\x00\x00\x10\x00\x10\x00\xac\x00\x00\x00\xb3\x82\x02\xe2D\x00o\x00m\x00a\x00i\x00n\x00U\x00s\x00e\x00r\x00C\x00O\x00M\x00P\x00U\x00T\x00E\x00R\x00\x98\xde\xf7\xb8\x7f\x88\xaa]\xaf\xe2\xdfw\x96\x88\xa1r\xde\xf1\x1c}\\\xcd\xef\x13g\xc40\x11\xf3\x02\x98\xa2\xad5\xec\xe6O\x163\x1cD\xbd\xbe\xd9\'\x84\x1f\x94Q\x88"\xb1\xb3\xf3P\xc8\x95\x86\x82\xec\xbb><\xb7'))
+        self.assertEqual(ntlmChallengeResponse.getData(), bytearray(b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00l\x00\x00\x00\x18\x00\x18\x00\x84\x00\x00\x00\x0c\x00\x0c\x00H\x00\x00\x00\x08\x00\x08\x00T\x00\x00\x00\x10\x00\x10\x00\\\x00\x00\x00\x10\x00\x10\x00\x9c\x00\x00\x00\xb3\x82\x02\xe2D\x00o\x00m\x00a\x00i\x00n\x00U\x00s\x00e\x00r\x00C\x00O\x00M\x00P\x00U\x00T\x00E\x00R\x00\x98\xde\xf7\xb8\x7f\x88\xaa]\xaf\xe2\xdfw\x96\x88\xa1r\xde\xf1\x1c}\\\xcd\xef\x13g\xc40\x11\xf3\x02\x98\xa2\xad5\xec\xe6O\x163\x1cD\xbd\xbe\xd9\'\x84\x1f\x94Q\x88"\xb1\xb3\xf3P\xc8\x95\x86\x82\xec\xbb><\xb7'))
         print("\n")
 
         print("4.2.2.4 GSS_WrapEx")
@@ -176,7 +176,7 @@ def test_ntlmv1(self):
         ntlmChallengeResponse['lanman'] = lmResponse
         ntlmChallengeResponse['ntlm'] = ntResponse
         hexdump(ntlmChallengeResponse.getData())
-        self.assertEqual(ntlmChallengeResponse.getData(), bytearray(b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00|\x00\x00\x00\x18\x00\x18\x00\x94\x00\x00\x00\x0c\x00\x0c\x00X\x00\x00\x00\x08\x00\x08\x00d\x00\x00\x00\x10\x00\x10\x00l\x00\x00\x00\x00\x00\x00\x00\xac\x00\x00\x00\xb3\x82\x02\xe2D\x00o\x00m\x00a\x00i\x00n\x00U\x00s\x00e\x00r\x00C\x00O\x00M\x00P\x00U\x00T\x00E\x00R\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00u7\xf8\x03\xae6q(\xcaE\x82\x04\xbd\xe7\xca\xf8\x1e\x97\xed&\x83&r2'))
+        self.assertEqual(ntlmChallengeResponse.getData(), bytearray(b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00l\x00\x00\x00\x18\x00\x18\x00\x84\x00\x00\x00\x0c\x00\x0c\x00H\x00\x00\x00\x08\x00\x08\x00T\x00\x00\x00\x10\x00\x10\x00\\\x00\x00\x00\x00\x00\x00\x00\x9c\x00\x00\x00\xb3\x82\x02\xe2D\x00o\x00m\x00a\x00i\x00n\x00U\x00s\x00e\x00r\x00C\x00O\x00M\x00P\x00U\x00T\x00E\x00R\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00u7\xf8\x03\xae6q(\xcaE\x82\x04\xbd\xe7\xca\xf8\x1e\x97\xed&\x83&r2'))
 
         print("\n")
         print("4.2.3.4 GSS_WrapEx")
@@ -266,7 +266,7 @@ def test_ntlmv2(self):
         ntlmChallengeResponse['ntlm'] = ntResponse
         ntlmChallengeResponse['session_key'] = encryptedSessionKey
         hexdump(ntlmChallengeResponse.getData())
-        self.assertEqual(ntlmChallengeResponse.getData(), bytearray(b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00|\x00\x00\x00P\x00P\x00\x94\x00\x00\x00\x0c\x00\x0c\x00X\x00\x00\x00\x08\x00\x08\x00d\x00\x00\x00\x10\x00\x10\x00l\x00\x00\x00\x10\x00\x10\x00\xe4\x00\x00\x003\x82\x8a\xe2D\x00o\x00m\x00a\x00i\x00n\x00U\x00s\x00e\x00r\x00C\x00O\x00M\x00P\x00U\x00T\x00E\x00R\x00\x86\xc3P\x97\xac\x9c\xec\x10%TvJW\xcc\xcc\x19\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xb22\x05\x0b\x98\xe5\xf4\xe36\xbd\x18y!\xa2{\xb2\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x02\x00\x0c\x00D\x00o\x00m\x00a\x00i\x00n\x00\x01\x00\x0c\x00S\x00e\x00r\x00v\x00e\x00r\x00\x00\x00\x00\x00\x18l\xaf\xeef \x16\x9d\xd9\x8cM\x1a"VqL'))
+        self.assertEqual(ntlmChallengeResponse.getData(), bytearray(b'NTLMSSP\x00\x03\x00\x00\x00\x18\x00\x18\x00l\x00\x00\x00P\x00P\x00\x84\x00\x00\x00\x0c\x00\x0c\x00H\x00\x00\x00\x08\x00\x08\x00T\x00\x00\x00\x10\x00\x10\x00\\\x00\x00\x00\x10\x00\x10\x00\xd4\x00\x00\x003\x82\x8a\xe2D\x00o\x00m\x00a\x00i\x00n\x00U\x00s\x00e\x00r\x00C\x00O\x00M\x00P\x00U\x00T\x00E\x00R\x00\x86\xc3P\x97\xac\x9c\xec\x10%TvJW\xcc\xcc\x19\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xb22\x05\x0b\x98\xe5\xf4\xe36\xbd\x18y!\xa2{\xb2\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x02\x00\x0c\x00D\x00o\x00m\x00a\x00i\x00n\x00\x01\x00\x0c\x00S\x00e\x00r\x00v\x00e\x00r\x00\x00\x00\x00\x00\x18l\xaf\xeef \x16\x9d\xd9\x8cM\x1a"VqL'))
         print("\n")
         print("4.2.4.4 GSS_WrapEx")
         print("Plaintext")


Reviews:
user: login: gabrielg5
body: Hi @zblurx,

Been also testing latest changes in 2019 / 2022 and worked fine!
Checked the source code and added a couple comments to some functions as suggestions to validate and move forward with this - besides checking @fsoc106 issue.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T20:49:40Z
user: login: zblurx
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T08:25:11Z
user: login: zblurx
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T08:25:21Z
user: login: zblurx
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T08:25:28Z
user: login: zblurx
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T08:25:36Z
user: login: zblurx
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T08:25:42Z
user: login: gabrielg5
body: Hi @zblurx,
Sorry not replying last week...
Was about to merge this one, but found that latest changeset may be breaking **DCERPC_v5** class

https://github.com/fortra/impacket/blob/00ced47f3ed61b64441146e24042f865dcc017b9/impacket/dcerpc/v5/rpcrt.py#L1209

https://github.com/fortra/impacket/blob/00ced47f3ed61b64441146e24042f865dcc017b9/impacket/dcerpc/v5/rpcrt.py#L1385

I guess setting a different name for the new _GSS_Wrap_ and _GSS_Unwrap_ (and leave old ones as before) is a safer move
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:14:42Z
user: login: zblurx
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T07:46:37Z

Review comments:
diff_hunk: @@ -222,6 +259,7 @@ class WRAP(Structure):
         )
 
     def GSS_GetMIC(self, sessionKey, data, sequenceNumber, direction = 'init'):
+        raise Exception('Not tested')
path: impacket/krb5/gssapi.py
user: login: gabrielg5
body: This exception was mis-pushed right?
created_at: 2025-04-07T18:44:44Z
author_association: COLLABORATOR
diff_hunk: @@ -389,3 +410,91 @@ def getData(self):
 
         self['Payload'] = ans
         return GSSAPI.getData(self)
+
+class SPNEGOCipher:
+    def __init__(self, flags, randomSessionKey):
+        self.__flags = flags
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey)
+            self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server")
+            self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey)
+            self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server")
+            
+            # Preparing the keys handle states
+            cipher3 = ARC4.new(self.__clientSealingKey)
+            self.__clientSealingHandle = cipher3.encrypt
+            cipher4 = ARC4.new(self.__serverSealingKey)
+            self.__serverSealingHandle = cipher4.encrypt
+        else:
+            # Same key for everything
+            self.__clientSigningKey = randomSessionKey
+            self.__serverSigningKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            cipher = ARC4.new(self.__clientSigningKey)
+            self.__clientSealingHandle = cipher.encrypt
+            self.__serverSealingHandle = cipher.encrypt
+        self.__sequence = 0
+
+    def encrypt(self, plain_data):
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            # When NTLM2 is on, we sign the whole pdu, but encrypt just
+            # the data, not the dcerpc header. Weird..
+            sealedMessage, signature =  ntlm.SEAL(self.__flags, 
+                    self.__clientSigningKey, 
+                    self.__clientSealingKey,  
+                    plain_data, 
+                    plain_data, 
+                    self.__sequence, 
+                    self.__clientSealingHandle)
+        else:
+            sealedMessage, signature =  ntlm.SEAL(self.__flags, 
+                    self.__clientSigningKey, 
+                    self.__clientSealingKey,  
+                    plain_data, 
+                    plain_data, 
+                    self.__sequence, 
+                    self.__clientSealingHandle)
path: impacket/spnego.py
user: login: gabrielg5
body: This if-else clause is the same in both conditions just to make explicit that something has yet to be done??
created_at: 2025-04-07T20:37:03Z
author_association: COLLABORATOR
diff_hunk: @@ -389,3 +410,91 @@ def getData(self):
 
         self['Payload'] = ans
         return GSSAPI.getData(self)
+
+class SPNEGOCipher:
+    def __init__(self, flags, randomSessionKey):
+        self.__flags = flags
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey)
+            self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server")
+            self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey)
+            self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server")
+            
+            # Preparing the keys handle states
+            cipher3 = ARC4.new(self.__clientSealingKey)
+            self.__clientSealingHandle = cipher3.encrypt
+            cipher4 = ARC4.new(self.__serverSealingKey)
+            self.__serverSealingHandle = cipher4.encrypt
+        else:
+            # Same key for everything
+            self.__clientSigningKey = randomSessionKey
+            self.__serverSigningKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            cipher = ARC4.new(self.__clientSigningKey)
+            self.__clientSealingHandle = cipher.encrypt
+            self.__serverSealingHandle = cipher.encrypt
+        self.__sequence = 0
+
+    def encrypt(self, plain_data):
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            # When NTLM2 is on, we sign the whole pdu, but encrypt just
+            # the data, not the dcerpc header. Weird..
+            sealedMessage, signature =  ntlm.SEAL(self.__flags, 
+                    self.__clientSigningKey, 
+                    self.__clientSealingKey,  
+                    plain_data, 
+                    plain_data, 
+                    self.__sequence, 
+                    self.__clientSealingHandle)
+        else:
+            sealedMessage, signature =  ntlm.SEAL(self.__flags, 
+                    self.__clientSigningKey, 
+                    self.__clientSealingKey,  
+                    plain_data, 
+                    plain_data, 
+                    self.__sequence, 
+                    self.__clientSealingHandle)
+
+        self.__sequence += 1
+
+        return signature, sealedMessage
+
+    def decrypt(self, answer):
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            # TODO: FIX THIS, it's not calculating the signature well
+            # Since I'm not testing it we don't care... yet
+            answer, signature =  ntlm.SEAL(self.__flags, 
+                    self.__serverSigningKey, 
+                    self.__serverSealingKey,  
+                    answer[:16], 
+                    answer[16:], 
+                    self.__sequence, 
+                    self.__serverSealingHandle)
+        else:
+            answer, signature = ntlm.SEAL(self.__flags, 
+                    self.__serverSigningKey, 
+                    self.__serverSealingKey, 
+                    answer[:16], 
+                    answer[16:],
+                    self.__sequence, 
+                    self.__serverSealingHandle)
path: impacket/spnego.py
user: login: gabrielg5
body: This if-else clause is the same in both conditions just to make explicit that something has yet to be done??
created_at: 2025-04-07T20:37:11Z
author_association: COLLABORATOR
diff_hunk: @@ -219,7 +236,45 @@ def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey=
 
         authenticator['cusec'] = now.microsecond
         authenticator['ctime'] = KerberosTime.to_asn1(now)
-
+        
+        authenticator['cksum'] = noValue
+        authenticator['cksum']['cksumtype'] = 0x8003
+
+        chkField = CheckSumField()
+        chkField['Lgth'] = 16
+
+        chkField['Flags'] = GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG
+
+        # If TLS is used, setup channel binding
+        channel_binding_value = b''
+        if self._SSL:
+            # From: https://github.com/ly4k/ldap3/commit/87f5760e5a68c2f91eac8ba375f4ea3928e2b9e0#diff-c782b790cfa0a948362bf47d72df8ddd6daac12e5757afd9d371d89385b27ef6R1383
+            from hashlib import md5
+            # Ugly but effective, to get the digest of the X509 DER in bytes
+            peer_cert_digest_str = self._socket.get_peer_certificate().digest('sha256').decode()
+            peer_cert_digest_bytes = bytes.fromhex(peer_cert_digest_str.replace(':', ''))
+        
+            channel_binding_struct = b''
+            initiator_address = b'\x00'*8
+            acceptor_address = b'\x00'*8
+
+            # https://datatracker.ietf.org/doc/html/rfc5929#section-4
+            application_data_raw = b'tls-server-end-point:' + peer_cert_digest_bytes
+            len_application_data = len(application_data_raw).to_bytes(4, byteorder='little', signed = False)
+            application_data = len_application_data
+            application_data += application_data_raw
+            channel_binding_struct += initiator_address
+            channel_binding_struct += acceptor_address
+            channel_binding_struct += application_data
+            channel_binding_value = md5(channel_binding_struct).digest()
path: impacket/ldap/ldap.py
user: login: gabrielg5
body: This process to get the channel_binding_value is repeated a couple times in the file (two times in `login` and one in `kerberosLogin`). Perhaps it's better to extract it as an external function to avoid duplicates...
created_at: 2025-04-07T20:40:32Z
author_association: COLLABORATOR
diff_hunk: @@ -100,6 +143,7 @@ class WRAP(Structure):
         )
 
     def GSS_GetMIC(self, sessionKey, data, sequenceNumber, direction = 'init'):
+        raise Exception('Not tested')
path: impacket/krb5/gssapi.py
user: login: gabrielg5
body: This exception was mis-pushed right?
created_at: 2025-04-07T20:41:55Z
author_association: COLLABORATOR
diff_hunk: @@ -222,6 +259,7 @@ class WRAP(Structure):
         )
 
     def GSS_GetMIC(self, sessionKey, data, sequenceNumber, direction = 'init'):
+        raise Exception('Not tested')
path: impacket/krb5/gssapi.py
user: login: zblurx
body: Yup, fixed now
created_at: 2025-04-08T08:25:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -389,3 +410,91 @@ def getData(self):
 
         self['Payload'] = ans
         return GSSAPI.getData(self)
+
+class SPNEGOCipher:
+    def __init__(self, flags, randomSessionKey):
+        self.__flags = flags
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey)
+            self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server")
+            self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey)
+            self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server")
+            
+            # Preparing the keys handle states
+            cipher3 = ARC4.new(self.__clientSealingKey)
+            self.__clientSealingHandle = cipher3.encrypt
+            cipher4 = ARC4.new(self.__serverSealingKey)
+            self.__serverSealingHandle = cipher4.encrypt
+        else:
+            # Same key for everything
+            self.__clientSigningKey = randomSessionKey
+            self.__serverSigningKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            cipher = ARC4.new(self.__clientSigningKey)
+            self.__clientSealingHandle = cipher.encrypt
+            self.__serverSealingHandle = cipher.encrypt
+        self.__sequence = 0
+
+    def encrypt(self, plain_data):
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            # When NTLM2 is on, we sign the whole pdu, but encrypt just
+            # the data, not the dcerpc header. Weird..
+            sealedMessage, signature =  ntlm.SEAL(self.__flags, 
+                    self.__clientSigningKey, 
+                    self.__clientSealingKey,  
+                    plain_data, 
+                    plain_data, 
+                    self.__sequence, 
+                    self.__clientSealingHandle)
+        else:
+            sealedMessage, signature =  ntlm.SEAL(self.__flags, 
+                    self.__clientSigningKey, 
+                    self.__clientSealingKey,  
+                    plain_data, 
+                    plain_data, 
+                    self.__sequence, 
+                    self.__clientSealingHandle)
path: impacket/spnego.py
user: login: zblurx
body: Yup, fixed now
created_at: 2025-04-08T08:25:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -219,7 +236,45 @@ def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey=
 
         authenticator['cusec'] = now.microsecond
         authenticator['ctime'] = KerberosTime.to_asn1(now)
-
+        
+        authenticator['cksum'] = noValue
+        authenticator['cksum']['cksumtype'] = 0x8003
+
+        chkField = CheckSumField()
+        chkField['Lgth'] = 16
+
+        chkField['Flags'] = GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG
+
+        # If TLS is used, setup channel binding
+        channel_binding_value = b''
+        if self._SSL:
+            # From: https://github.com/ly4k/ldap3/commit/87f5760e5a68c2f91eac8ba375f4ea3928e2b9e0#diff-c782b790cfa0a948362bf47d72df8ddd6daac12e5757afd9d371d89385b27ef6R1383
+            from hashlib import md5
+            # Ugly but effective, to get the digest of the X509 DER in bytes
+            peer_cert_digest_str = self._socket.get_peer_certificate().digest('sha256').decode()
+            peer_cert_digest_bytes = bytes.fromhex(peer_cert_digest_str.replace(':', ''))
+        
+            channel_binding_struct = b''
+            initiator_address = b'\x00'*8
+            acceptor_address = b'\x00'*8
+
+            # https://datatracker.ietf.org/doc/html/rfc5929#section-4
+            application_data_raw = b'tls-server-end-point:' + peer_cert_digest_bytes
+            len_application_data = len(application_data_raw).to_bytes(4, byteorder='little', signed = False)
+            application_data = len_application_data
+            application_data += application_data_raw
+            channel_binding_struct += initiator_address
+            channel_binding_struct += acceptor_address
+            channel_binding_struct += application_data
+            channel_binding_value = md5(channel_binding_struct).digest()
path: impacket/ldap/ldap.py
user: login: zblurx
body: Yup, fixed now
created_at: 2025-04-08T08:25:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -389,3 +410,91 @@ def getData(self):
 
         self['Payload'] = ans
         return GSSAPI.getData(self)
+
+class SPNEGOCipher:
+    def __init__(self, flags, randomSessionKey):
+        self.__flags = flags
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey)
+            self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server")
+            self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey)
+            self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server")
+            
+            # Preparing the keys handle states
+            cipher3 = ARC4.new(self.__clientSealingKey)
+            self.__clientSealingHandle = cipher3.encrypt
+            cipher4 = ARC4.new(self.__serverSealingKey)
+            self.__serverSealingHandle = cipher4.encrypt
+        else:
+            # Same key for everything
+            self.__clientSigningKey = randomSessionKey
+            self.__serverSigningKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            self.__clientSealingKey = randomSessionKey
+            cipher = ARC4.new(self.__clientSigningKey)
+            self.__clientSealingHandle = cipher.encrypt
+            self.__serverSealingHandle = cipher.encrypt
+        self.__sequence = 0
+
+    def encrypt(self, plain_data):
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            # When NTLM2 is on, we sign the whole pdu, but encrypt just
+            # the data, not the dcerpc header. Weird..
+            sealedMessage, signature =  ntlm.SEAL(self.__flags, 
+                    self.__clientSigningKey, 
+                    self.__clientSealingKey,  
+                    plain_data, 
+                    plain_data, 
+                    self.__sequence, 
+                    self.__clientSealingHandle)
+        else:
+            sealedMessage, signature =  ntlm.SEAL(self.__flags, 
+                    self.__clientSigningKey, 
+                    self.__clientSealingKey,  
+                    plain_data, 
+                    plain_data, 
+                    self.__sequence, 
+                    self.__clientSealingHandle)
+
+        self.__sequence += 1
+
+        return signature, sealedMessage
+
+    def decrypt(self, answer):
+        if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
+            # TODO: FIX THIS, it's not calculating the signature well
+            # Since I'm not testing it we don't care... yet
+            answer, signature =  ntlm.SEAL(self.__flags, 
+                    self.__serverSigningKey, 
+                    self.__serverSealingKey,  
+                    answer[:16], 
+                    answer[16:], 
+                    self.__sequence, 
+                    self.__serverSealingHandle)
+        else:
+            answer, signature = ntlm.SEAL(self.__flags, 
+                    self.__serverSigningKey, 
+                    self.__serverSealingKey, 
+                    answer[:16], 
+                    answer[16:],
+                    self.__sequence, 
+                    self.__serverSealingHandle)
path: impacket/spnego.py
user: login: zblurx
body: Yup, fixed now
created_at: 2025-04-08T08:25:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -100,6 +143,7 @@ class WRAP(Structure):
         )
 
     def GSS_GetMIC(self, sessionKey, data, sequenceNumber, direction = 'init'):
+        raise Exception('Not tested')
path: impacket/krb5/gssapi.py
user: login: zblurx
body: Yup, fixed now
created_at: 2025-04-08T08:25:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -196,6 +238,70 @@ def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt
     def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None):
         return self.GSS_Wrap(sessionKey, data, sequenceNumber, direction, encrypt, authData)
 
+    def GSS_Wrap_LDAP(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True):
+       
+        if encrypt:
+            data += b'\x01'
+
+        token = self.WRAP()
+        token['SGN_ALG'] = GSS_HMAC
+        token['SEAL_ALG'] = GSS_RC4
+
+        if direction == 'init':
+            token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\x00'*4
+        else:
+            token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\xff'*4
+
+        # Random confounder :)
+        token['Confounder'] = b(''.join([rand.choice(string.ascii_letters) for _ in range(8)]))
+
+        Ksign = HMAC.new(sessionKey.contents, b'signaturekey\0', MD5).digest()
+        Sgn_Cksum = MD5.new(struct.pack('<L',13) + token.getData()[:8] + token['Confounder'] + data).digest()
+
+        Klocal = bytearray()
+        from builtins import bytes
+        for n in bytes(sessionKey.contents):
+            Klocal.append( n ^ 0xF0)
+
+        Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest()
+        Kcrypt = HMAC.new(Kcrypt,struct.pack('>L', sequenceNumber), MD5).digest()
+        
+        Sgn_Cksum = HMAC.new(Ksign, Sgn_Cksum, MD5).digest()
+
+        token['SGN_CKSUM'] = Sgn_Cksum[:8]
+
+        Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest()
+        Kseq = HMAC.new(Kseq, token['SGN_CKSUM'], MD5).digest()
+
+        token['SND_SEQ'] = ARC4.new(Kseq).encrypt(token['SND_SEQ'])
+
+        if encrypt is False:
+            sspi_wrap = MechIndepToken.from_bytes(data)
+
+            wrap = self.WRAP(sspi_wrap.data[:32])
+            data = sspi_wrap.data[32:]
+            
+            Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest()
+            Kseq = HMAC.new(Kseq, wrap['SGN_CKSUM'], MD5).digest()
+
+            snd_seq = ARC4.new(Kseq).encrypt(wrap['SND_SEQ'])
+
+            Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest()
+            Kcrypt = HMAC.new(Kcrypt,snd_seq[:4], MD5).digest()
+            rc4 = ARC4.new(Kcrypt)
+            cipherText = rc4.decrypt(token['Confounder'] + data)[8:]
+            return cipherText[:-1], None
+        
+        elif encrypt is True:
+            rc4 = ARC4.new(Kcrypt)
+            token['Confounder'] = rc4.encrypt(token['Confounder'])
+            cipherText = rc4.encrypt(data)
+            finalData, cipherText = MechIndepToken(token.getData() + cipherText, KRB_OID).to_bytes()
+            return cipherText, finalData
+
+    def GSS_Unwrap_LDAP(self, sessionKey, data, sequenceNumber, direction = 'init'):
+        return self.GSS_Wrap(sessionKey, data, sequenceNumber, direction, encrypt=False)
path: impacket/krb5/gssapi.py
user: login: gabrielg5
body: ```suggestion
        return self.GSS_Wrap_LDAP(sessionKey, data, sequenceNumber, direction, encrypt=False)
```

Should call this one right? (beware if implementing the more global change of the other comment :) )
created_at: 2025-04-14T18:12:49Z
author_association: COLLABORATOR
diff_hunk: @@ -196,6 +238,70 @@ def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt
     def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None):
         return self.GSS_Wrap(sessionKey, data, sequenceNumber, direction, encrypt, authData)
 
+    def GSS_Wrap_LDAP(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True):
+       
+        if encrypt:
+            data += b'\x01'
+
+        token = self.WRAP()
+        token['SGN_ALG'] = GSS_HMAC
+        token['SEAL_ALG'] = GSS_RC4
+
+        if direction == 'init':
+            token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\x00'*4
+        else:
+            token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\xff'*4
+
+        # Random confounder :)
+        token['Confounder'] = b(''.join([rand.choice(string.ascii_letters) for _ in range(8)]))
+
+        Ksign = HMAC.new(sessionKey.contents, b'signaturekey\0', MD5).digest()
+        Sgn_Cksum = MD5.new(struct.pack('<L',13) + token.getData()[:8] + token['Confounder'] + data).digest()
+
+        Klocal = bytearray()
+        from builtins import bytes
+        for n in bytes(sessionKey.contents):
+            Klocal.append( n ^ 0xF0)
+
+        Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest()
+        Kcrypt = HMAC.new(Kcrypt,struct.pack('>L', sequenceNumber), MD5).digest()
+        
+        Sgn_Cksum = HMAC.new(Ksign, Sgn_Cksum, MD5).digest()
+
+        token['SGN_CKSUM'] = Sgn_Cksum[:8]
+
+        Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest()
+        Kseq = HMAC.new(Kseq, token['SGN_CKSUM'], MD5).digest()
+
+        token['SND_SEQ'] = ARC4.new(Kseq).encrypt(token['SND_SEQ'])
+
+        if encrypt is False:
+            sspi_wrap = MechIndepToken.from_bytes(data)
+
+            wrap = self.WRAP(sspi_wrap.data[:32])
+            data = sspi_wrap.data[32:]
+            
+            Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest()
+            Kseq = HMAC.new(Kseq, wrap['SGN_CKSUM'], MD5).digest()
+
+            snd_seq = ARC4.new(Kseq).encrypt(wrap['SND_SEQ'])
+
+            Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest()
+            Kcrypt = HMAC.new(Kcrypt,snd_seq[:4], MD5).digest()
+            rc4 = ARC4.new(Kcrypt)
+            cipherText = rc4.decrypt(token['Confounder'] + data)[8:]
+            return cipherText[:-1], None
+        
+        elif encrypt is True:
+            rc4 = ARC4.new(Kcrypt)
+            token['Confounder'] = rc4.encrypt(token['Confounder'])
+            cipherText = rc4.encrypt(data)
+            finalData, cipherText = MechIndepToken(token.getData() + cipherText, KRB_OID).to_bytes()
+            return cipherText, finalData
+
+    def GSS_Unwrap_LDAP(self, sessionKey, data, sequenceNumber, direction = 'init'):
+        return self.GSS_Wrap(sessionKey, data, sequenceNumber, direction, encrypt=False)
path: impacket/krb5/gssapi.py
user: login: zblurx
body: Good catch !
created_at: 2025-04-15T07:46:37Z
author_association: CONTRIBUTOR

PR comments:
user: login: mpgn
created_at: 2025-03-16T18:05:38Z
body: Hello @anadrianmanrique hope you are doing well 😊

Can you check this one as it solves a big issue on netexec and avoid people to switch from one to another tool depending of the ldap configuration during a pentest 😩

Best regards !
author_association: CONTRIBUTOR
user: login: anadrianmanrique
created_at: 2025-03-17T18:28:28Z
body: Pushing this one to my queue. Will reach out during the code review process
Thanks!
author_association: COLLABORATOR
user: login: fsoc106
created_at: 2025-04-02T14:06:59Z
body: hello, I just poc in internal assesment and it doesnt work
author_association: NONE
user: login: Mercury0
created_at: 2025-04-03T03:07:58Z
body: > hello, I just poc in internal assesment and it doesnt work

Could you provide some additional detail so we can troubleshoot what caused it to fail?
author_association: NONE
user: login: anadrianmanrique
created_at: 2025-04-03T05:22:04Z
body: hello, this is the output I get, targeting a windows 2025 with ldaps/signing enabled

without PR changes:
```
└─$ python3 GetUserSPNs.py -dc-ip 192.0.2.69 vb25.local/Administrator -k -no-pass -debug
Cannot determine Impacket version. If running from source you should at least run "python setup.py egg_info"
Impacket v? - Copyright Fortra, LLC and its affiliated companies

[+] Impacket Library Installation Path: /home/kali/impacket/master/impacket/impacket
[*] Getting machine hostname
[+] Connecting to 192.0.2.69, port 389, SSL False
[+] Using Kerberos Cache: Administrator.ccache
[+] SPN LDAP/DC25.VB25.LOCAL@VB25.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for KRBTGT/VB25.LOCAL@VB25.LOCAL
[+] Using TGT from cache
[+] Trying to connect to KDC at 192.0.2.69:88
[+] Connecting to 192.0.2.69, port 636, SSL True
[+] Using Kerberos Cache: Administrator.ccache
[+] SPN LDAP/DC25.VB25.LOCAL@VB25.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for KRBTGT/VB25.LOCAL@VB25.LOCAL
[+] Using TGT from cache
[+] Trying to connect to KDC at 192.0.2.69:88
Traceback (most recent call last):
  File "/home/kali/impacket/master/impacket/examples/GetUserSPNs.py", line 276, in run
    ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
  File "/home/kali/impacket/master/impacket/impacket/ldap/ldap.py", line 248, in kerberosLogin
    raise LDAPSessionError(
impacket.ldap.ldap.LDAPSessionError: Error in bindRequest -> strongerAuthRequired: 00002028: LdapErr: DSID-0C090343, comment: The server requires binds to turn on integrity checking if SSL\TLS are not already active on the connection, data 0, v65f4

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/kali/impacket/master/impacket/examples/GetUserSPNs.py", line 577, in <module>
    executer.run()
  File "/home/kali/impacket/master/impacket/examples/GetUserSPNs.py", line 286, in run
    ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
  File "/home/kali/impacket/master/impacket/impacket/ldap/ldap.py", line 248, in kerberosLogin
    raise LDAPSessionError(
impacket.ldap.ldap.LDAPSessionError: Error in bindRequest -> invalidCredentials: 80090346: LdapErr: DSID-0C0906BF, comment: AcceptSecurityContext error, data 80090346, v65f4
[-] Error in bindRequest -> invalidCredentials: 80090346: LdapErr: DSID-0C0906BF, comment: AcceptSecurityContext error, data 80090346, v65f4

```

with PR changes

```
└─$ python3 GetUserSPNs.py -k -no-pass vb25.local/Administrator -dc-ip 192.0.2.69 -debug
Cannot determine Impacket version. If running from source you should at least run "python setup.py egg_info"
Impacket v? - Copyright Fortra, LLC and its affiliated companies

[+] Impacket Library Installation Path: /home/kali/impacket/PRs/1919/impacket/impacket
[*] Getting machine hostname
[+] Connecting to 192.0.2.69, port 389, SSL False, signing True
[+] Using Kerberos Cache: Administrator.ccache
[+] SPN LDAP/DC25.VB25.LOCAL@VB25.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for KRBTGT/VB25.LOCAL@VB25.LOCAL
[+] Using TGT from cache
[+] Trying to connect to KDC at 192.0.2.69:88
Traceback (most recent call last):
  File "/home/kali/impacket/PRs/1919/impacket/examples/GetUserSPNs.py", line 577, in <module>
    executer.run()
  File "/home/kali/impacket/PRs/1919/impacket/examples/GetUserSPNs.py", line 322, in run
    resp = ldapConnection.search(searchFilter=searchFilter,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/impacket/PRs/1919/impacket/impacket/ldap/ldap.py", line 532, in search
    response = self.sendReceive(searchRequest, searchControls)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/impacket/PRs/1919/impacket/impacket/ldap/ldap.py", line 630, in sendReceive
    return self.recv()
           ^^^^^^^^^^^
  File "/home/kali/impacket/PRs/1919/impacket/impacket/ldap/ldap.py", line 603, in recv
    data = self.decrypt(data)
           ^^^^^^^^^^^^^^^^^^
  File "/home/kali/impacket/PRs/1919/impacket/impacket/ldap/ldap.py", line 499, in decrypt
    data, _ = self.__gss.GSS_Unwrap(self.__sessionKey, data, 0, direction='init')
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/impacket/PRs/1919/impacket/impacket/krb5/gssapi.py", line 322, in GSS_Unwrap
    token = self.WRAP(authData[len(SEC_TRAILER()):])
                      ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
[-] 'NoneType' object is not subscriptable


```
In both cases I used a TGT ticket that worked ok with smbclient.py in previous test
author_association: COLLABORATOR
user: login: fsoc106
created_at: 2025-04-03T07:09:28Z
body: Hello, there is my error : 

```
``` Impacket v0.13.0.dev0+20250313.181730.6a0d2953 - Copyright Fortra, LLC and its affiliated companies 

[+] Impacket Library Installation Path: /workspace/impacket/impacket-env/lib/python3.11/site-packages/impacket
[+] Connecting to [REDACTED], port 389, SSL False, signing True
Traceback (most recent call last):
  File "/workspace/impacket/examples/GetUserSPNs.py", line 577, in <module>
    executer.run()
  File "/workspace/impacket/examples/GetUserSPNs.py", line 274, in run
    ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
  File "/workspace/impacket/impacket-env/lib/python3.11/site-packages/impacket/ldap/ldap.py", line 424, in login
    response = self.sendReceive(bindRequest)[0]['protocolOp']
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/impacket/impacket-env/lib/python3.11/site-packages/impacket/ldap/ldap.py", line 630, in sendReceive
    return self.recv()
           ^^^^^^^^^^^
  File "/workspace/impacket/impacket-env/lib/python3.11/site-packages/impacket/ldap/ldap.py", line 596, in recv
    recvData = self._socket.recv(REQUEST_SIZE)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ConnectionResetError: [Errno 104] Connection reset by peer
[-] [Errno 104] Connection reset by peer ```
```
author_association: NONE
user: login: anadrianmanrique
created_at: 2025-04-04T03:00:21Z
body: Ok , trying windows 2019 and 2022 everything seems to be working "almost" as expected.
here's the output targeting 2022 **ldaps/channel binding=always**
without changes
```
└─$ python3 GetUserSPNs.py -dc-ip 192.0.2.57 vb22.local/Administrator -k -no-pass -debug
Cannot determine Impacket version. If running from source you should at least run "python setup.py egg_info"
Impacket v? - Copyright Fortra, LLC and its affiliated companies

[+] Impacket Library Installation Path: /home/kali/impacket/master/impacket/impacket
[*] Getting machine hostname
[+] Connecting to 192.0.2.57, port 636, SSL True
[+] Using Kerberos Cache: Administrator.ccache
[+] SPN LDAP/DC22.VB22.LOCAL@VB22.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for KRBTGT/VB22.LOCAL@VB22.LOCAL
[+] Using TGT from cache
[+] Trying to connect to KDC at 192.0.2.57:88
Traceback (most recent call last):
  File "/home/kali/impacket/master/impacket/examples/GetUserSPNs.py", line 577, in <module>
    executer.run()
  File "/home/kali/impacket/master/impacket/examples/GetUserSPNs.py", line 276, in run
    ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
  File "/home/kali/impacket/master/impacket/impacket/ldap/ldap.py", line 248, in kerberosLogin
    raise LDAPSessionError(
impacket.ldap.ldap.LDAPSessionError: Error in bindRequest -> invalidCredentials: 80090346: LdapErr: DSID-0C090585, comment: AcceptSecurityContext error, data 80090346, v4f7c
[-] Error in bindRequest -> invalidCredentials: 80090346: LdapErr: DSID-0C090585, comment: AcceptSecurityContext error, data 80090346, v4f7c
```

with changes
```
└─$ python3 GetUserSPNs.py -dc-ip 192.0.2.57 vb22.local/Administrator -k -no-pass -debug
Cannot determine Impacket version. If running from source you should at least run "python setup.py egg_info"
Impacket v? - Copyright Fortra, LLC and its affiliated companies

[+] Impacket Library Installation Path: /home/kali/impacket/PRs/1919/impacket/impacket
[*] Getting machine hostname
[+] Connecting to 192.0.2.57, port 636, SSL True, signing False
[+] Using Kerberos Cache: Administrator.ccache
[+] SPN LDAP/DC22.VB22.LOCAL@VB22.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for KRBTGT/VB22.LOCAL@VB22.LOCAL
[+] Using TGT from cache
[+] Trying to connect to KDC at 192.0.2.57:88
[+] Total of records returned 3
No entries found!
```
same target SSL=off , signing=always

without changes:
```
└─$ python3 GetUserSPNs.py -dc-ip 192.0.2.57 vb22.local/Administrator -k -no-pass -debug
Cannot determine Impacket version. If running from source you should at least run "python setup.py egg_info"
Impacket v? - Copyright Fortra, LLC and its affiliated companies

[+] Impacket Library Installation Path: /home/kali/impacket/master/impacket/impacket
[*] Getting machine hostname
[+] Connecting to 192.0.2.57, port 389, SSL False
[+] Using Kerberos Cache: Administrator.ccache
[+] SPN LDAP/DC22.VB22.LOCAL@VB22.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for KRBTGT/VB22.LOCAL@VB22.LOCAL
[+] Using TGT from cache
[+] Trying to connect to KDC at 192.0.2.57:88
[+] Connecting to 192.0.2.57, port 636, SSL True
[+] Using Kerberos Cache: Administrator.ccache
[+] SPN LDAP/DC22.VB22.LOCAL@VB22.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for KRBTGT/VB22.LOCAL@VB22.LOCAL
[+] Using TGT from cache
[+] Trying to connect to KDC at 192.0.2.57:88
Traceback (most recent call last):
  File "/home/kali/impacket/master/impacket/examples/GetUserSPNs.py", line 276, in run
    ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
  File "/home/kali/impacket/master/impacket/impacket/ldap/ldap.py", line 248, in kerberosLogin
    raise LDAPSessionError(
impacket.ldap.ldap.LDAPSessionError: Error in bindRequest -> strongerAuthRequired: 00002028: LdapErr: DSID-0C090254, comment: The server requires binds to turn on integrity checking if SSL\TLS are not already active on the connection, data 0, v4f7c

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/kali/impacket/master/impacket/examples/GetUserSPNs.py", line 577, in <module>
    executer.run()
  File "/home/kali/impacket/master/impacket/examples/GetUserSPNs.py", line 286, in run
    ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
  File "/home/kali/impacket/master/impacket/impacket/ldap/ldap.py", line 248, in kerberosLogin
    raise LDAPSessionError(
impacket.ldap.ldap.LDAPSessionError: Error in bindRequest -> invalidCredentials: 80090346: LdapErr: DSID-0C090585, comment: AcceptSecurityContext error, data 80090346, v4f7c
[-] Error in bindRequest -> invalidCredentials: 80090346: LdapErr: DSID-0C090585, comment: AcceptSecurityContext error, data 80090346, v4f7c
```
with changes:
```
└─$ python3 GetUserSPNs.py -dc-ip 192.0.2.57 vb22.local/Administrator -k -no-pass -debug
Cannot determine Impacket version. If running from source you should at least run "python setup.py egg_info"
Impacket v? - Copyright Fortra, LLC and its affiliated companies

[+] Impacket Library Installation Path: /home/kali/impacket/PRs/1919/impacket/impacket
[*] Getting machine hostname
[+] Connecting to 192.0.2.57, port 389, SSL False, signing True
[+] Using Kerberos Cache: Administrator.ccache
[+] SPN LDAP/DC22.VB22.LOCAL@VB22.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for KRBTGT/VB22.LOCAL@VB22.LOCAL
[+] Using TGT from cache
[+] Trying to connect to KDC at 192.0.2.57:88
[+] Total of records returned 3
No entries found!
```
question: for client/server signing=None configuration in the target, code still reports signing as True, which is confusing
author_association: COLLABORATOR
user: login: anadrianmanrique
created_at: 2025-04-04T03:01:12Z
body: @fsoc106 can you provide specs/configuration of your target?
author_association: COLLABORATOR
user: login: fsoc106
created_at: 2025-04-04T07:50:18Z
body: > @fsoc106 can you provide specs/configuration of your target?

Yes,

Thereis the configuration of the server DC : 

Windows 10 / Server 2019 Build 17763 x64 (name:DC) (domain:REDACTED) (signing:True) (SMBv1:False)
LDAP : 
port 389, SSL False, signing True



author_association: NONE
user: login: zblurx
created_at: 2025-04-04T08:34:46Z
body: @anadrianmanrique I'm going to start to work on Windows Server 2025 today. 

For the signing=True, it is because even if the LDAP server does not require signing, you can still use and it will work. So I put it at True by default in order to not add another parameter in every example scripts that may require LDAP signing support. 

@fsoc106 I'm going to investigate this one, but the `Connection reset by peer` error seems odd.
author_association: CONTRIBUTOR
user: login: zblurx
created_at: 2025-04-04T10:20:03Z
body: @anadrianmanrique last commit fix the problem on windows server 2025. By default Windows Server 2025 disable Kerberos auth on RC4_HMAC, and therefore enforce auth on AES128 or AES256, and I completely forgot about implementing GSSAPI support for AES.

With the recent fix:
```bash
(venv) tse@laptop:~/Documents/Dev/impacket$ python examples/GetUserSPNs.py -k -no-pass test.lab/Administrator -dc-ip 192.168.56.120 -debug
Impacket v0.13.0.dev0+20250404.120906.af399255 - Copyright Fortra, LLC and its affiliated companies

[+] Impacket Library Installation Path: /home/tse/Documents/Dev/impacket/venv/lib/python3.12/site-packages/impacket
[*] Getting machine hostname
[+] Connecting to 192.168.56.120, port 389, SSL False, signing True
[+] Using Kerberos Cache: Administrator.ccache
[+] SPN LDAP/DC.TEST.LAB@TEST.LAB not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for KRBTGT/TEST.LAB@TEST.LAB
[+] Using TGT from cache
[+] Trying to connect to KDC at 192.168.56.120:88
[+] Total of records returned 3
No entries found!
```
author_association: CONTRIBUTOR
user: login: zblurx
created_at: 2025-04-04T10:26:19Z
body: @fsoc106 Do you have the same error without the PR ?
author_association: CONTRIBUTOR
user: login: fsoc106
created_at: 2025-04-04T10:42:59Z
body: > @fsoc106 Do you have the same error without the PR ?

Yes same error without the PR.

Same error with nxc : 

Error connecting to the domain, are you sure LDAP service is running on the tar
get? \r\nError: [Errno 104] Connection reset by peer\r\n"]

and the same error with ntlmrelayx : 

Connection from REDACTED con
trolled, attacking target ldap://REDACTED\u001b[39m\u001b[K\u001b[33m\r\n[-] HTTPD(80): Exception while Negotiating NTLM with ldap://REDACTED: \"error receiving data: [Errno 104] Connection reset by peer
author_association: NONE
user: login: zblurx
created_at: 2025-04-04T11:52:17Z
body: @fsoc106 can you try with another tool that does not rely on Impacket please ? For example ldeep or BloodyAd ?
author_association: CONTRIBUTOR
user: login: fsoc106
created_at: 2025-04-08T07:01:50Z
body: Hello, I can't reproduce the issue in the lab, and my internal assessment is complete.
author_association: NONE
user: login: zblurx
created_at: 2025-04-08T08:28:49Z
body: Hey @gabrielg5, thanks for reviewing. I fixed the mistakes you spotted in the code, it should be all good now ! 

As for @fsoc106 issue, I don't think it's related to Impacket and this PR, seems like a broken domain controller to me, but I guess we'll never know 
author_association: CONTRIBUTOR
user: login: gabrielg5
created_at: 2025-04-08T11:18:38Z
body: Hi @zblurx, awesome, thank you!

One last thing I realized last night, related to the `gssapi` and `gssapismb` files. Would it be better to keep the old as gssapi and change the name of the new one? I'm thinking in scenarios where people are using the lib in their projects, to not break these configurations / keep backwards compatibility...

Thanks!
author_association: COLLABORATOR
user: login: zblurx
created_at: 2025-04-08T15:32:55Z
body: Hi @gabrielg5,

You are totally right. I made this duplication on `gssapi.py` file during development of the feature, but now it seems useless and will definitely break multiple projects when implemented. Anyway I renamed `gssapismb.py` to `gssapi.py`, and I merged the changes needed for ldap signing to work (the  `GSS_Wrap_LDAP` and `GSS_Unwrap_LDAP` functions)
author_association: CONTRIBUTOR
user: login: zblurx
created_at: 2025-04-15T08:16:28Z
body: Hi @gabrielg5,
I just pushed a fix for `GSS_Unwrap_LDAP()`.

> Hi @zblurx, Sorry not replying last week... Was about to merge this one, but found that latest changeset may be breaking **DCERPC_v5** class
> 
> https://github.com/fortra/impacket/blob/00ced47f3ed61b64441146e24042f865dcc017b9/impacket/dcerpc/v5/rpcrt.py#L1209
> 
> https://github.com/fortra/impacket/blob/00ced47f3ed61b64441146e24042f865dcc017b9/impacket/dcerpc/v5/rpcrt.py#L1385
> 
> I guess setting a different name for the new _GSS_Wrap_ and _GSS_Unwrap_ (and leave old ones as before) is a safer move

Since I have not change `GSS_Wrap` and `GSS_Unwrap` functions, I don't think anything would have an impact on **DCERPCv5** class. Also I removed the changes I made to kerberosv5 since they were not needed in the PR.

author_association: CONTRIBUTOR
user: login: gabrielg5
created_at: 2025-04-15T18:10:18Z
body: > Since I have not change GSS_Wrap and GSS_Unwrap functions, I don't think anything would have an impact on DCERPCv5 class.

true! don't know what I saw yesterday...

will merge in a moment.
Thank you!
author_association: COLLABORATOR
user: login: gabrielg5
created_at: 2025-04-15T22:56:24Z
body: Thank you guys!!
author_association: COLLABORATOR
user: login: mpgn
created_at: 2025-04-16T07:07:19Z
body: Thanks for the quick review @anadrianmanrique @gabrielg5 🥰
author_association: CONTRIBUTOR
user: login: anadrianmanrique
created_at: 2025-04-21T15:22:37Z
body: awesome feature, thank you guys!
author_association: COLLABORATOR
user: login: Neustradamus
created_at: 2025-07-19T09:10:56Z
body: @zblurx and all, thanks for your work! :)

Linked to Channel Binding:
- https://github.com/scram-sasl/info/issues/1

If one guy can look for Roundcube and kolab/Net_LDAP3, it will be nice :)
- https://github.com/roundcube/roundcubemail/issues/6917#issuecomment-1783829351

Thanks in advance.
author_association: NONE