1 # Copyright (c) 2003-2016 CORE Security Technologies
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.
7 # Author: Alberto Solino (@agsolino)
10 # [MS-SMB2] Protocol Implementation (SMB2 and SMB3)
11 # As you might see in the code, it's implemented strictly following
12 # the structures defined in the protocol specification. This may
13 # not be the most efficient way (e.g. self._Connection is the
14 # same to self._Session in the context of this library ) but
15 # it certainly helps following the document way easier.
18 # [X] Implement SMB2_CHANGE_NOTIFY
19 # [X] Implement SMB2_QUERY_INFO
20 # [X] Implement SMB2_SET_INFO
21 # [ ] Implement SMB2_OPLOCK_BREAK
22 # [X] Implement SMB3 signing
23 # [ ] Implement SMB3 encryption
24 # [ ] Add more backward compatible commands from the smb.py code
25 # [ ] Fix up all the 'ToDo' comments inside the code
33 from binascii import a2b_hex
34 from contextlib import contextmanager
36 from impacket import nmb, ntlm, uuid, crypto, LOG
37 from impacket.smb3structs import *
38 from impacket.nt_errors import STATUS_SUCCESS, STATUS_MORE_PROCESSING_REQUIRED, STATUS_INVALID_PARAMETER, \
39 STATUS_NO_MORE_FILES, STATUS_PENDING, STATUS_NOT_IMPLEMENTED, ERROR_MESSAGES
40 from impacket.spnego import SPNEGO_NegTokenInit, TypesMech, SPNEGO_NegTokenResp
44 import hashlib, hmac, copy
52 # If the client implements the SMB 3.0 dialect,
53 # the client MUST also implement the following
55 'EncryptData' : False,
56 'IsScaleoutShare' : False,
57 # Outside the protocol
71 'Connection' : 0, # Not Used
75 'ResilientHandle' : False,
76 'LastDisconnectTime' : 0,
77 'ResilientTimeout' : 0,
78 'OperationBuckets' : [],
79 # If the client implements the SMB 3.0 dialect,
80 # the client MUST implement the following
82 'IsPersistent' : False,
86 'FileAttributes' : '',
87 'CreateDisposition' : '',
102 class SessionError(Exception):
103 def __init__( self, error = 0, packet=0):
104 Exception.__init__(self)
108 def get_error_code( self ):
111 def get_error_packet( self ):
115 return 'SMB SessionError: %s(%s)' % (ERROR_MESSAGES[self.error])
119 def __init__(self, remote_name, remote_host, my_name = None, host_type = nmb.TYPE_SERVER, sess_port = 445, timeout=60, UDP = 0, preferredDialect = None, session = None):
121 # [MS-SMB2] Section 3
122 self.RequireMessageSigning = False #
123 self.ConnectionTable = {}
124 self.GlobalFileTable = {}
125 self.ClientGuid = ''.join([random.choice(string.letters) for i in range(16)])
127 self.EncryptionAlgorithmList = ['AES-CCM']
129 self.RequireSecureNegotiate = False
131 # Per Transport Connection Data
133 # Indexed by SessionID
134 #'SessionTable' : {},
135 # Indexed by MessageID
136 'OutstandingRequests' : {},
137 'OutstandingResponses' : {}, #
138 'SequenceWindow' : 0, #
139 'GSSNegotiateToken' : '', #
140 'MaxTransactSize' : 0, #
142 'MaxWriteSize' : 0, #
144 'RequireSigning' : False, #
146 # If the client implements the SMB 2.1 or SMB 3.0 dialects, it MUST
147 # also implement the following
149 'SupportsFileLeasing' : False, #
150 'SupportsMultiCredit' : False, #
151 # If the client implements the SMB 3.0 dialect,
152 # it MUST also implement the following
153 'SupportsDirectoryLeasing' : False, #
154 'SupportsMultiChannel' : False, #
155 'SupportsPersistentHandles': False, #
156 'SupportsEncryption' : False, #
157 'ClientCapabilities' : 0,
158 'ServerCapabilities' : 0, #
159 'ClientSecurityMode' : 0, #
160 'ServerSecurityMode' : 0, #
161 # Outside the protocol
167 'TreeConnectTable' : {}, #
169 'SigningRequired' : False, #
171 'UserCredentials' : '', #
173 # If the client implements the SMB 3.0 dialect,
174 # it MUST also implement the following
176 'ChannelSequence' : 0,
177 #'EncryptData' : False,
178 'EncryptData' : True,
179 'EncryptionKey' : '',
180 'DecryptionKey' : '',
182 'ApplicationKey' : '',
183 # Outside the protocol
184 'SessionFlags' : 0, #
186 'ServerDomain' : '', #
187 'ServerDNSDomainName' : '', #
189 'SigningActivated' : False, #
192 self.SMB_PACKET = SMB2Packet
194 self._timeout = timeout
195 self._Connection['ServerIP'] = remote_host
196 self._NetBIOSSession = None
208 if sess_port == 445 and remote_name == '*SMBSERVER':
209 self._Connection['ServerName'] = remote_host
211 self._Connection['ServerName'] = remote_name
215 my_name = socket.gethostname()
216 i = string.find(my_name, '.')
218 my_name = my_name[:i]
221 self._NetBIOSSession = nmb.NetBIOSUDPSession(my_name, self._Connection['ServerName'], remote_host, host_type, sess_port, self._timeout)
223 self._NetBIOSSession = nmb.NetBIOSTCPSession(my_name, self._Connection['ServerName'], remote_host, host_type, sess_port, self._timeout)
225 self.negotiateSession(preferredDialect)
227 self._NetBIOSSession = session
228 # We should increase the SequenceWindow since a packet was already received.
229 self._Connection['SequenceWindow'] += 1
230 # Let's negotiate again using the same connection
231 self.negotiateSession(preferredDialect)
233 def printStatus(self):
235 for i in self._Connection.items():
236 print "%-40s : %s" % i
239 for i in self._Session.items():
240 print "%-40s : %s" % i
242 def getServerName(self):
243 return self._Session['ServerName']
245 def getServerIP(self):
246 return self._Connection['ServerIP']
248 def getServerDomain(self):
249 return self._Session['ServerDomain']
251 def getServerDNSDomainName(self):
252 return self._Session['ServerDNSDomainName']
254 def getServerOS(self):
255 return self._Session['ServerOS']
257 def getServerOSMajor(self):
258 return self._Session['ServerOSMajor']
260 def getServerOSMinor(self):
261 return self._Session['ServerOSMinor']
263 def getServerOSBuild(self):
264 return self._Session['ServerOSBuild']
266 def isGuestSession(self):
267 return self._Session['SessionFlags'] & SMB2_SESSION_FLAG_IS_GUEST
269 def setTimeout(self, timeout):
270 self._timeout = timeout
273 def useTimeout(self, timeout):
274 prev_timeout = self.getTimeout(timeout)
278 self.setTimeout(prev_timeout)
280 def getDialect(self):
281 return self._Connection['Dialect']
284 def signSMB(self, packet):
285 packet['Signature'] = '\x00'*16
286 if self._Connection['Dialect'] == SMB2_DIALECT_21 or self._Connection['Dialect'] == SMB2_DIALECT_002:
287 if len(self._Session['SessionKey']) > 0:
288 signature = hmac.new(self._Session['SessionKey'], str(packet), hashlib.sha256).digest()
289 packet['Signature'] = signature[:16]
291 if len(self._Session['SessionKey']) > 0:
293 signature = crypto.AES_CMAC(self._Session['SigningKey'], p, len(p))
294 packet['Signature'] = signature
296 def sendSMB(self, packet):
297 # The idea here is to receive multiple/single commands and create a compound request, and send it
298 # Should return the MessageID for later retrieval. Implement compounded related requests.
300 # If Connection.Dialect is equal to "3.000" and if Connection.SupportsMultiChannel or
301 # Connection.SupportsPersistentHandles is TRUE, the client MUST set ChannelSequence in the
302 # SMB2 header to Session.ChannelSequence
304 # Check this is not a CANCEL request. If so, don't consume sequece numbers
305 if packet['Command'] is not SMB2_CANCEL:
306 packet['MessageID'] = self._Connection['SequenceWindow']
307 self._Connection['SequenceWindow'] += 1
308 packet['SessionID'] = self._Session['SessionID']
310 # Default the credit charge to 1 unless set by the caller
311 if packet.fields.has_key('CreditCharge') is False:
312 packet['CreditCharge'] = 1
314 # Standard credit request after negotiating protocol
315 if self._Connection['SequenceWindow'] > 3:
316 packet['CreditRequestResponse'] = 127
318 messageId = packet['MessageID']
320 if self._Session['SigningActivated'] is True and self._Connection['SequenceWindow'] > 2:
321 if packet['TreeID'] > 0 and self._Session['TreeConnectTable'].has_key(packet['TreeID']) is True:
322 if self._Session['TreeConnectTable'][packet['TreeID']]['EncryptData'] is False:
323 packet['Flags'] = SMB2_FLAGS_SIGNED
325 elif packet['TreeID'] == 0:
326 packet['Flags'] = SMB2_FLAGS_SIGNED
329 if (self._Session['SessionFlags'] & SMB2_SESSION_FLAG_ENCRYPT_DATA) or ( packet['TreeID'] != 0 and self._Session['TreeConnectTable'][packet['TreeID']]['EncryptData'] is True):
330 plainText = str(packet)
331 transformHeader = SMB2_TRANSFORM_HEADER()
332 transformHeader['Nonce'] = ''.join([random.choice(string.letters) for i in range(11)])
333 transformHeader['OriginalMessageSize'] = len(plainText)
334 transformHeader['EncryptionAlgorithm'] = SMB2_ENCRYPTION_AES128_CCM
335 transformHeader['SessionID'] = self._Session['SessionID']
336 from Crypto.Cipher import AES
340 LOG.critical("Your pycrypto doesn't support AES.MODE_CCM. Currently only pycrypto experimental supports this mode.\nDownload it from https://www.dlitz.net/software/pycrypto ")
342 cipher = AES.new(self._Session['EncryptionKey'], AES.MODE_CCM, transformHeader['Nonce'])
343 cipher.update(str(transformHeader)[20:])
344 cipherText = cipher.encrypt(plainText)
345 transformHeader['Signature'] = cipher.digest()
346 packet = str(transformHeader) + cipherText
348 self._NetBIOSSession.send_packet(str(packet))
351 def recvSMB(self, packetID = None):
352 # First, verify we don't have the packet already
353 if self._Connection['OutstandingResponses'].has_key(packetID):
354 return self._Connection['OutstandingResponses'].pop(packetID)
356 data = self._NetBIOSSession.recv_packet(self._timeout)
358 if data.get_trailer().startswith('\xfdSMB'):
359 # Packet is encrypted
360 transformHeader = SMB2_TRANSFORM_HEADER(data.get_trailer())
361 from Crypto.Cipher import AES
365 LOG.critical("Your pycrypto doesn't support AES.MODE_CCM. Currently only pycrypto experimental supports this mode.\nDownload it from https://www.dlitz.net/software/pycrypto ")
367 cipher = AES.new(self._Session['DecryptionKey'], AES.MODE_CCM, transformHeader['Nonce'][:11])
368 cipher.update(str(transformHeader)[20:])
369 plainText = cipher.decrypt(data.get_trailer()[len(SMB2_TRANSFORM_HEADER()):])
370 #cipher.verify(transformHeader['Signature'])
371 packet = SMB2Packet(plainText)
373 # In all SMB dialects for a response this field is interpreted as the Status field.
374 # This field can be set to any value. For a list of valid status codes,
375 # see [MS-ERREF] section 2.3.
376 packet = SMB2Packet(data.get_trailer())
378 # Loop while we receive pending requests
379 if packet['Status'] == STATUS_PENDING:
380 status = STATUS_PENDING
381 while status == STATUS_PENDING:
382 data = self._NetBIOSSession.recv_packet(self._timeout)
383 if data.get_trailer().startswith('\xfeSMB'):
384 packet = SMB2Packet(data.get_trailer())
386 # Packet is encrypted
387 transformHeader = SMB2_TRANSFORM_HEADER(data.get_trailer())
388 from Crypto.Cipher import AES
392 LOG.critical("Your pycrypto doesn't support AES.MODE_CCM. Currently only pycrypto experimental supports this mode.\nDownload it from https://www.dlitz.net/software/pycrypto ")
394 cipher = AES.new(self._Session['DecryptionKey'], AES.MODE_CCM, transformHeader['Nonce'][:11])
395 cipher.update(str(transformHeader)[20:])
396 plainText = cipher.decrypt(data.get_trailer()[len(SMB2_TRANSFORM_HEADER()):])
397 #cipher.verify(transformHeader['Signature'])
398 packet = SMB2Packet(plainText)
399 status = packet['Status']
401 if packet['MessageID'] == packetID or packetID is None:
402 # if self._Session['SigningRequired'] is True:
403 # self.signSMB(packet)
404 # Let's update the sequenceWindow based on the CreditsCharged
405 self._Connection['SequenceWindow'] += (packet['CreditCharge'] - 1)
408 self._Connection['OutstandingResponses'][packet['MessageID']] = packet
409 return self.recvSMB(packetID)
411 def negotiateSession(self, preferredDialect = None):
412 packet = self.SMB_PACKET()
413 packet['Command'] = SMB2_NEGOTIATE
414 negSession = SMB2Negotiate()
416 negSession['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_ENABLED
417 if self.RequireMessageSigning is True:
418 negSession['SecurityMode'] |= SMB2_NEGOTIATE_SIGNING_REQUIRED
419 negSession['Capabilities'] = SMB2_GLOBAL_CAP_ENCRYPTION
420 negSession['ClientGuid'] = self.ClientGuid
421 if preferredDialect is not None:
422 negSession['Dialects'] = [preferredDialect]
424 negSession['Dialects'] = [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]
425 negSession['DialectCount'] = len(negSession['Dialects'])
426 packet['Data'] = negSession
428 # Storing this data for later use
429 self._Connection['ClientSecurityMode'] = negSession['SecurityMode']
430 self._Connection['Capabilities'] = negSession['Capabilities']
432 packetID = self.sendSMB(packet)
433 ans = self.recvSMB(packetID)
434 if ans.isValidAnswer(STATUS_SUCCESS):
436 # If the DialectRevision in the SMB2 NEGOTIATE Response is 0x02FF, the client MUST issue a new
437 # SMB2 NEGOTIATE request as described in section 3.2.4.2.2.2 with the only exception
438 # that the client MUST allocate sequence number 1 from Connection.SequenceWindow, and MUST set
439 # MessageId field of the SMB2 header to 1. Otherwise, the client MUST proceed as follows.
440 negResp = SMB2Negotiate_Response(ans['Data'])
441 self._Connection['MaxTransactSize'] = min(0x100000,negResp['MaxTransactSize'])
442 self._Connection['MaxReadSize'] = min(0x100000,negResp['MaxReadSize'])
443 self._Connection['MaxWriteSize'] = min(0x100000,negResp['MaxWriteSize'])
444 self._Connection['ServerGuid'] = negResp['ServerGuid']
445 self._Connection['GSSNegotiateToken'] = negResp['Buffer']
446 self._Connection['Dialect'] = negResp['DialectRevision']
447 if (negResp['SecurityMode'] & SMB2_NEGOTIATE_SIGNING_REQUIRED) == SMB2_NEGOTIATE_SIGNING_REQUIRED:
448 self._Connection['RequireSigning'] = True
449 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LEASING) == SMB2_GLOBAL_CAP_LEASING:
450 self._Connection['SupportsFileLeasing'] = True
451 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LARGE_MTU) == SMB2_GLOBAL_CAP_LARGE_MTU:
452 self._Connection['SupportsMultiCredit'] = True
454 if self._Connection['Dialect'] == SMB2_DIALECT_30:
455 # Switching to the right packet format
456 self.SMB_PACKET = SMB3Packet
457 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) == SMB2_GLOBAL_CAP_DIRECTORY_LEASING:
458 self._Connection['SupportsDirectoryLeasing'] = True
459 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_MULTI_CHANNEL) == SMB2_GLOBAL_CAP_MULTI_CHANNEL:
460 self._Connection['SupportsMultiChannel'] = True
461 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) == SMB2_GLOBAL_CAP_PERSISTENT_HANDLES:
462 self._Connection['SupportsPersistentHandles'] = True
463 if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_ENCRYPTION) == SMB2_GLOBAL_CAP_ENCRYPTION:
464 self._Connection['SupportsEncryption'] = True
466 self._Connection['ServerCapabilities'] = negResp['Capabilities']
467 self._Connection['ServerSecurityMode'] = negResp['SecurityMode']
469 def getCredentials(self):
480 def kerberosLogin(self, user, password, domain = '', lmhash = '', nthash = '', aesKey='', kdcHost = '', TGT=None, TGS=None):
481 # If TGT or TGS are specified, they are in the form of:
482 # TGS['KDC_REP'] = the response from the server
483 # TGS['cipher'] = the cipher used
484 # TGS['sessionKey'] = the sessionKey
485 # If we have hashes, normalize them
486 if lmhash != '' or nthash != '':
487 if len(lmhash) % 2: lmhash = '0%s' % lmhash
488 if len(nthash) % 2: nthash = '0%s' % nthash
489 try: # just in case they were converted already
490 lmhash = a2b_hex(lmhash)
491 nthash = a2b_hex(nthash)
495 self.__userName = user
496 self.__password = password
497 self.__domain = domain
498 self.__lmhash = lmhash
499 self.__nthash = nthash
501 self.__aesKey = aesKey
505 sessionSetup = SMB2SessionSetup()
506 if self.RequireMessageSigning is True:
507 sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_REQUIRED
509 sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_ENABLED
511 sessionSetup['Flags'] = 0
512 #sessionSetup['Capabilities'] = SMB2_GLOBAL_CAP_LARGE_MTU | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_DFS
514 # Importing down here so pyasn1 is not required if kerberos is not used.
515 from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set
516 from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
517 from impacket.krb5 import constants
518 from impacket.krb5.types import Principal, KerberosTime, Ticket
519 from pyasn1.codec.der import decoder, encoder
522 # First of all, we need to get a TGT for the user
523 userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
526 tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost)
529 cipher = TGT['cipher']
530 sessionKey = TGT['sessionKey']
533 # If you want, for debugging purposes
534 # from impacket.krb5.ccache import CCache
538 # ccache.fromTGT(tgt, oldSessionKey, sessionKey)
540 # ccache.fromTGS(TGS['KDC_REP'], TGS['oldSessionKey'], TGS['sessionKey'] )
541 # ccache.saveFile('/tmp/ticket.bin')
542 # except Exception, e:
546 # Now that we have the TGT, we should ask for a TGS for cifs
549 serverName = Principal('cifs/%s' % (self._Connection['ServerName']), type=constants.PrincipalNameType.NT_SRV_INST.value)
550 tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey)
553 cipher = TGS['cipher']
554 sessionKey = TGS['sessionKey']
556 # Let's build a NegTokenInit with a Kerberos REQ_AP
558 blob = SPNEGO_NegTokenInit()
561 blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]
563 # Let's extract the ticket from the TGS
564 tgs = decoder.decode(tgs, asn1Spec = TGS_REP())[0]
566 ticket.from_asn1(tgs['ticket'])
568 # Now let's build the AP_REQ
571 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
574 apReq['ap-options'] = constants.encodeFlags(opts)
575 seq_set(apReq,'ticket', ticket.to_asn1)
577 authenticator = Authenticator()
578 authenticator['authenticator-vno'] = 5
579 authenticator['crealm'] = domain
580 seq_set(authenticator, 'cname', userName.components_to_asn1)
581 now = datetime.datetime.utcnow()
583 authenticator['cusec'] = now.microsecond
584 authenticator['ctime'] = KerberosTime.to_asn1(now)
586 encodedAuthenticator = encoder.encode(authenticator)
589 # AP-REQ Authenticator (includes application authenticator
590 # subkey), encrypted with the application session key
592 encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None)
594 apReq['authenticator'] = None
595 apReq['authenticator']['etype'] = cipher.enctype
596 apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator
598 blob['MechToken'] = encoder.encode(apReq)
600 sessionSetup['SecurityBufferLength'] = len(blob)
601 sessionSetup['Buffer'] = blob.getData()
603 packet = self.SMB_PACKET()
604 packet['Command'] = SMB2_SESSION_SETUP
605 packet['Data'] = sessionSetup
607 packetID = self.sendSMB(packet)
608 ans = self.recvSMB(packetID)
609 if ans.isValidAnswer(STATUS_SUCCESS):
610 self._Session['SessionID'] = ans['SessionID']
611 self._Session['SigningRequired'] = self._Connection['RequireSigning']
612 self._Session['UserCredentials'] = (user, password, domain, lmhash, nthash)
613 self._Session['Connection'] = self._NetBIOSSession.get_socket()
615 self._Session['SessionKey'] = sessionKey.contents[:16]
616 if self._Session['SigningRequired'] is True and self._Connection['Dialect'] == SMB2_DIALECT_30:
617 self._Session['SigningKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], "SMB2AESCMAC\x00", "SmbSign\x00", 128)
619 # Calculate the key derivations for dialect 3.0
620 if self._Session['SigningRequired'] is True:
621 self._Session['SigningActivated'] = True
622 if self._Connection['Dialect'] == SMB2_DIALECT_30:
623 self._Session['ApplicationKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], "SMB2APP\x00", "SmbRpc\x00", 128)
624 self._Session['EncryptionKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], "SMB2AESCCM\x00", "ServerIn \x00", 128)
625 self._Session['DecryptionKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], "SMB2AESCCM\x00", "ServerOut\x00", 128)
629 # We clean the stuff we used in case we want to authenticate again
630 # within the same connection
631 self._Session['UserCredentials'] = ''
632 self._Session['Connection'] = 0
633 self._Session['SessionID'] = 0
634 self._Session['SigningRequired'] = False
635 self._Session['SigningKey'] = ''
636 self._Session['SessionKey'] = ''
637 self._Session['SigningActivated'] = False
641 def login(self, user, password, domain = '', lmhash = '', nthash = ''):
642 # If we have hashes, normalize them
643 if lmhash != '' or nthash != '':
644 if len(lmhash) % 2: lmhash = '0%s' % lmhash
645 if len(nthash) % 2: nthash = '0%s' % nthash
646 try: # just in case they were converted already
647 lmhash = a2b_hex(lmhash)
648 nthash = a2b_hex(nthash)
652 self.__userName = user
653 self.__password = password
654 self.__domain = domain
655 self.__lmhash = lmhash
656 self.__nthash = nthash
661 sessionSetup = SMB2SessionSetup()
662 if self.RequireMessageSigning is True:
663 sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_REQUIRED
665 sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_ENABLED
667 sessionSetup['Flags'] = 0
668 #sessionSetup['Capabilities'] = SMB2_GLOBAL_CAP_LARGE_MTU | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_DFS
670 # Let's build a NegTokenInit with the NTLMSSP
671 # TODO: In the future we should be able to choose different providers
673 blob = SPNEGO_NegTokenInit()
676 blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']]
677 auth = ntlm.getNTLMSSPType1('','', self._Connection['RequireSigning'])
678 blob['MechToken'] = str(auth)
680 sessionSetup['SecurityBufferLength'] = len(blob)
681 sessionSetup['Buffer'] = blob.getData()
684 # If this authentication is for establishing an alternative channel for an existing Session, as specified
685 # in section 3.2.4.1.7, the client MUST also set the following values:
686 # The SessionId field in the SMB2 header MUST be set to the Session.SessionId for the new
687 # channel being established.
688 # The SMB2_SESSION_FLAG_BINDING bit MUST be set in the Flags field.
689 # The PreviousSessionId field MUST be set to zero.
691 packet = self.SMB_PACKET()
692 packet['Command'] = SMB2_SESSION_SETUP
693 packet['Data'] = sessionSetup
695 packetID = self.sendSMB(packet)
696 ans = self.recvSMB(packetID)
697 if ans.isValidAnswer(STATUS_MORE_PROCESSING_REQUIRED):
698 self._Session['SessionID'] = ans['SessionID']
699 self._Session['SigningRequired'] = self._Connection['RequireSigning']
700 self._Session['UserCredentials'] = (user, password, domain, lmhash, nthash)
701 self._Session['Connection'] = self._NetBIOSSession.get_socket()
702 sessionSetupResponse = SMB2SessionSetup_Response(ans['Data'])
703 respToken = SPNEGO_NegTokenResp(sessionSetupResponse['Buffer'])
705 # Let's parse some data and keep it to ourselves in case it is asked
706 ntlmChallenge = ntlm.NTLMAuthChallenge(respToken['ResponseToken'])
707 if ntlmChallenge['TargetInfoFields_len'] > 0:
708 av_pairs = ntlm.AV_PAIRS(ntlmChallenge['TargetInfoFields'][:ntlmChallenge['TargetInfoFields_len']])
709 if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None:
711 self._Session['ServerName'] = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode('utf-16le')
713 # For some reason, we couldn't decode Unicode here.. silently discard the operation
715 if av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME] is not None:
717 if self._Session['ServerName'] != av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le'):
718 self._Session['ServerDomain'] = av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le')
720 # For some reason, we couldn't decode Unicode here.. silently discard the operation
722 if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] is not None:
724 self._Session['ServerDNSDomainName'] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode('utf-16le')
726 # For some reason, we couldn't decode Unicode here.. silently discard the operation
729 # Parse Version to know the target Operating system name. Not provided elsewhere anymore
730 if ntlmChallenge.fields.has_key('Version'):
731 version = ntlmChallenge['Version']
733 if len(version) >= 4:
734 self._Session['ServerOS'] = "Windows %d.%d Build %d" % (ord(version[0]), ord(version[1]), struct.unpack('<H',version[2:4])[0])
735 self._Session["ServerOSMajor"] = ord(version[0])
736 self._Session["ServerOSMinor"] = ord(version[1])
737 self._Session["ServerOSBuild"] = struct.unpack('<H',version[2:4])[0]
739 type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, respToken['ResponseToken'], user, password, domain, lmhash, nthash)
741 if exportedSessionKey is not None:
742 self._Session['SessionKey'] = exportedSessionKey
743 if self._Session['SigningRequired'] is True and self._Connection['Dialect'] == SMB2_DIALECT_30:
744 self._Session['SigningKey'] = crypto.KDF_CounterMode(exportedSessionKey, "SMB2AESCMAC\x00", "SmbSign\x00", 128)
746 respToken2 = SPNEGO_NegTokenResp()
747 respToken2['ResponseToken'] = str(type3)
749 # Reusing the previous structure
750 sessionSetup['SecurityBufferLength'] = len(respToken2)
751 sessionSetup['Buffer'] = respToken2.getData()
753 packetID = self.sendSMB(packet)
754 packet = self.recvSMB(packetID)
756 if packet.isValidAnswer(STATUS_SUCCESS):
757 sessionSetupResponse = SMB2SessionSetup_Response(packet['Data'])
758 self._Session['SessionFlags'] = sessionSetupResponse['SessionFlags']
760 # Calculate the key derivations for dialect 3.0
761 if self._Session['SigningRequired'] is True:
762 self._Session['SigningActivated'] = True
763 if self._Connection['Dialect'] == SMB2_DIALECT_30:
764 self._Session['ApplicationKey'] = crypto.KDF_CounterMode(exportedSessionKey, "SMB2APP\x00", "SmbRpc\x00", 128)
765 self._Session['EncryptionKey'] = crypto.KDF_CounterMode(exportedSessionKey, "SMB2AESCCM\x00", "ServerIn \x00", 128)
766 self._Session['DecryptionKey'] = crypto.KDF_CounterMode(exportedSessionKey, "SMB2AESCCM\x00", "ServerOut\x00", 128)
770 # We clean the stuff we used in case we want to authenticate again
771 # within the same connection
772 self._Session['UserCredentials'] = ''
773 self._Session['Connection'] = 0
774 self._Session['SessionID'] = 0
775 self._Session['SigningRequired'] = False
776 self._Session['SigningKey'] = ''
777 self._Session['SessionKey'] = ''
778 self._Session['SigningActivated'] = False
781 def connectTree(self, share):
783 # Just in case this came with the full path (maybe an SMB1 client), let's just leave
784 # the sharename, we'll take care of the rest
786 #print self._Session['TreeConnectTable']
787 share = share.split('\\')[-1]
788 if self._Session['TreeConnectTable'].has_key(share):
789 # Already connected, no need to reconnect
790 treeEntry = self._Session['TreeConnectTable'][share]
791 treeEntry['NumberOfUses'] += 1
792 self._Session['TreeConnectTable'][treeEntry['TreeConnectId']]['NumberOfUses'] += 1
793 return treeEntry['TreeConnectId']
797 _, _, _, _, sockaddr = socket.getaddrinfo(self._Connection['ServerIP'], 80, 0, 0, socket.IPPROTO_TCP)[0]
798 remoteHost = sockaddr[0]
800 remoteHost = self._Connection['ServerIP']
801 path = '\\\\' + remoteHost + '\\' +share
803 treeConnect = SMB2TreeConnect()
804 treeConnect['Buffer'] = path.encode('utf-16le')
805 treeConnect['PathLength'] = len(path)*2
807 packet = self.SMB_PACKET()
808 packet['Command'] = SMB2_TREE_CONNECT
809 packet['Data'] = treeConnect
810 packetID = self.sendSMB(packet)
811 packet = self.recvSMB(packetID)
812 if packet.isValidAnswer(STATUS_SUCCESS):
813 treeConnectResponse = SMB2TreeConnect_Response(packet['Data'])
814 treeEntry = copy.deepcopy(TREE_CONNECT)
815 treeEntry['ShareName'] = share
816 treeEntry['TreeConnectId'] = packet['TreeID']
817 treeEntry['Session'] = packet['SessionID']
818 treeEntry['NumberOfUses'] += 1
819 if (treeConnectResponse['Capabilities'] & SMB2_SHARE_CAP_DFS) == SMB2_SHARE_CAP_DFS:
820 treeEntry['IsDfsShare'] = True
821 if (treeConnectResponse['Capabilities'] & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY) == SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY:
822 treeEntry['IsCAShare'] = True
824 if self._Connection['Dialect'] == SMB2_DIALECT_30:
825 if (self._Connection['SupportsEncryption'] is True) and ((treeConnectResponse['ShareFlags'] & SMB2_SHAREFLAG_ENCRYPT_DATA) == SMB2_SHAREFLAG_ENCRYPT_DATA):
826 treeEntry['EncryptData'] = True
827 # ToDo: This and what follows
828 # If Session.EncryptData is FALSE, the client MUST then generate an encryption key, a
829 # decryption key as specified in section 3.1.4.2, by providing the following inputs and store
830 # them in Session.EncryptionKey and Session.DecryptionKey:
831 if (treeConnectResponse['Capabilities'] & SMB2_SHARE_CAP_SCALEOUT) == SMB2_SHARE_CAP_SCALEOUT:
832 treeEntry['IsScaleoutShare'] = True
834 self._Session['TreeConnectTable'][packet['TreeID']] = treeEntry
835 self._Session['TreeConnectTable'][share] = treeEntry
837 return packet['TreeID']
839 def disconnectTree(self, treeId):
840 if self._Session['TreeConnectTable'].has_key(treeId) is False:
841 raise SessionError(STATUS_INVALID_PARAMETER)
843 if self._Session['TreeConnectTable'].has_key(treeId):
844 # More than 1 use? descrease it and return, if not, send the packet
845 if self._Session['TreeConnectTable'][treeId]['NumberOfUses'] > 1:
846 treeEntry = self._Session['TreeConnectTable'][treeId]
847 treeEntry['NumberOfUses'] -= 1
848 self._Session['TreeConnectTable'][treeEntry['ShareName']]['NumberOfUses'] -= 1
851 packet = self.SMB_PACKET()
852 packet['Command'] = SMB2_TREE_DISCONNECT
853 packet['TreeID'] = treeId
854 treeDisconnect = SMB2TreeDisconnect()
855 packet['Data'] = treeDisconnect
856 packetID = self.sendSMB(packet)
857 packet = self.recvSMB(packetID)
858 if packet.isValidAnswer(STATUS_SUCCESS):
859 shareName = self._Session['TreeConnectTable'][treeId]['ShareName']
860 del(self._Session['TreeConnectTable'][shareName])
861 del(self._Session['TreeConnectTable'][treeId])
864 def create(self, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition, fileAttributes, impersonationLevel = SMB2_IL_IMPERSONATION, securityFlags = 0, oplockLevel = SMB2_OPLOCK_LEVEL_NONE, createContexts = None):
865 if self._Session['TreeConnectTable'].has_key(treeId) is False:
866 raise SessionError(STATUS_INVALID_PARAMETER)
868 fileName = string.replace(fileName, '/', '\\')
869 if len(fileName) > 0:
870 fileName = ntpath.normpath(fileName)
871 if fileName[0] == '\\':
872 fileName = fileName[1:]
874 if self._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
877 pathName = '\\\\' + self._Connection['ServerName'] + '\\' + fileName
879 fileEntry = copy.deepcopy(FILE)
880 fileEntry['LeaseKey'] = uuid.generate()
881 fileEntry['LeaseState'] = SMB2_LEASE_NONE
882 self.GlobalFileTable[pathName] = fileEntry
884 if self._Connection['Dialect'] == SMB2_DIALECT_30 and self._Connection['SupportsDirectoryLeasing'] is True:
885 # Is this file NOT on the root directory?
886 if len(fileName.split('\\')) > 2:
887 parentDir = ntpath.dirname(pathName)
888 if self.GlobalFileTable.has_key(parentDir):
889 LOG.critical("Don't know what to do now! :-o")
892 parentEntry = copy.deepcopy(FILE)
893 parentEntry['LeaseKey'] = uuid.generate()
894 parentEntry['LeaseState'] = SMB2_LEASE_NONE
895 self.GlobalFileTable[parentDir] = parentEntry
897 packet = self.SMB_PACKET()
898 packet['Command'] = SMB2_CREATE
899 packet['TreeID'] = treeId
900 if self._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
901 packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS
903 smb2Create = SMB2Create()
904 smb2Create['SecurityFlags'] = 0
905 smb2Create['RequestedOplockLevel'] = oplockLevel
906 smb2Create['ImpersonationLevel'] = impersonationLevel
907 smb2Create['DesiredAccess'] = desiredAccess
908 smb2Create['FileAttributes'] = fileAttributes
909 smb2Create['ShareAccess'] = shareMode
910 smb2Create['CreateDisposition'] = creationDisposition
911 smb2Create['CreateOptions'] = creationOptions
913 smb2Create['NameLength'] = len(fileName)*2
915 smb2Create['Buffer'] = fileName.encode('utf-16le')
917 smb2Create['Buffer'] = '\x00'
919 if createContexts is not None:
920 smb2Create['Buffer'] += createContexts
921 smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength']
922 smb2Create['CreateContextsLength'] = len(createContexts)
924 smb2Create['CreateContextsOffset'] = 0
925 smb2Create['CreateContextsLength'] = 0
927 packet['Data'] = smb2Create
929 packetID = self.sendSMB(packet)
930 ans = self.recvSMB(packetID)
931 if ans.isValidAnswer(STATUS_SUCCESS):
932 createResponse = SMB2Create_Response(ans['Data'])
934 openFile = copy.deepcopy(OPEN)
935 openFile['FileID'] = createResponse['FileID']
936 openFile['TreeConnect'] = treeId
937 openFile['Oplocklevel'] = oplockLevel
938 openFile['Durable'] = False
939 openFile['ResilientHandle'] = False
940 openFile['LastDisconnectTime'] = 0
941 openFile['FileName'] = pathName
943 # ToDo: Complete the OperationBuckets
944 if self._Connection['Dialect'] == SMB2_DIALECT_30:
945 openFile['DesiredAccess'] = oplockLevel
946 openFile['ShareMode'] = oplockLevel
947 openFile['CreateOptions'] = oplockLevel
948 openFile['FileAttributes'] = oplockLevel
949 openFile['CreateDisposition'] = oplockLevel
951 # ToDo: Process the contexts
952 self._Session['OpenTable'][str(createResponse['FileID'])] = openFile
954 # The client MUST generate a handle for the Open, and it MUST
955 # return success and the generated handle to the calling application.
956 # In our case, str(FileID)
957 return str(createResponse['FileID'])
959 def close(self, treeId, fileId):
960 if self._Session['TreeConnectTable'].has_key(treeId) is False:
961 raise SessionError(STATUS_INVALID_PARAMETER)
962 if self._Session['OpenTable'].has_key(fileId) is False:
963 raise SessionError(STATUS_INVALID_PARAMETER)
965 packet = self.SMB_PACKET()
966 packet['Command'] = SMB2_CLOSE
967 packet['TreeID'] = treeId
969 smbClose = SMB2Close()
970 smbClose['Flags'] = 0
971 smbClose['FileID'] = fileId
973 packet['Data'] = smbClose
975 packetID = self.sendSMB(packet)
976 ans = self.recvSMB(packetID)
978 if ans.isValidAnswer(STATUS_SUCCESS):
979 del(self.GlobalFileTable[self._Session['OpenTable'][fileId]['FileName']])
980 del(self._Session['OpenTable'][fileId])
982 # ToDo Remove stuff from GlobalFileTable
985 def read(self, treeId, fileId, offset = 0, bytesToRead = 0, waitAnswer = True):
986 # IMPORTANT NOTE: As you can see, this was coded as a recursive function
987 # Hence, you can exhaust the memory pretty easy ( large bytesToRead )
988 # This function should NOT be used for reading files directly, but another higher
989 # level function should be used that will break the read into smaller pieces
991 if self._Session['TreeConnectTable'].has_key(treeId) is False:
992 raise SessionError(STATUS_INVALID_PARAMETER)
993 if self._Session['OpenTable'].has_key(fileId) is False:
994 raise SessionError(STATUS_INVALID_PARAMETER)
996 packet = self.SMB_PACKET()
997 packet['Command'] = SMB2_READ
998 packet['TreeID'] = treeId
1000 if self._Connection['MaxReadSize'] < bytesToRead:
1001 maxBytesToRead = self._Connection['MaxReadSize']
1003 maxBytesToRead = bytesToRead
1005 if self._Connection['Dialect'] != SMB2_DIALECT_002 and self._Connection['SupportsMultiCredit'] is True:
1006 packet['CreditCharge'] = ( 1 + (maxBytesToRead - 1) / 65536)
1008 maxBytesToRead = min(65536,bytesToRead)
1010 smbRead = SMB2Read()
1011 smbRead['Padding'] = 0x50
1012 smbRead['FileID'] = fileId
1013 smbRead['Length'] = maxBytesToRead
1014 smbRead['Offset'] = offset
1015 packet['Data'] = smbRead
1017 packetID = self.sendSMB(packet)
1018 ans = self.recvSMB(packetID)
1020 if ans.isValidAnswer(STATUS_SUCCESS):
1021 readResponse = SMB2Read_Response(ans['Data'])
1022 retData = readResponse['Buffer']
1023 if readResponse['DataRemaining'] > 0:
1024 retData += self.read(treeId, fileId, offset+len(retData), readResponse['DataRemaining'], waitAnswer)
1027 def write(self, treeId, fileId, data, offset = 0, bytesToWrite = 0, waitAnswer = True):
1028 # IMPORTANT NOTE: As you can see, this was coded as a recursive function
1029 # Hence, you can exhaust the memory pretty easy ( large bytesToWrite )
1030 # This function should NOT be used for writing directly to files, but another higher
1031 # level function should be used that will break the writes into smaller pieces
1033 if self._Session['TreeConnectTable'].has_key(treeId) is False:
1034 raise SessionError(STATUS_INVALID_PARAMETER)
1035 if self._Session['OpenTable'].has_key(fileId) is False:
1036 raise SessionError(STATUS_INVALID_PARAMETER)
1038 packet = self.SMB_PACKET()
1039 packet['Command'] = SMB2_WRITE
1040 packet['TreeID'] = treeId
1042 if self._Connection['MaxWriteSize'] < bytesToWrite:
1043 maxBytesToWrite = self._Connection['MaxWriteSize']
1045 maxBytesToWrite = bytesToWrite
1047 if self._Connection['Dialect'] != SMB2_DIALECT_002 and self._Connection['SupportsMultiCredit'] is True:
1048 packet['CreditCharge'] = ( 1 + (maxBytesToWrite - 1) / 65536)
1050 maxBytesToWrite = min(65536,bytesToWrite)
1052 smbWrite = SMB2Write()
1053 smbWrite['FileID'] = fileId
1054 smbWrite['Length'] = maxBytesToWrite
1055 smbWrite['Offset'] = offset
1056 smbWrite['WriteChannelInfoOffset'] = 0
1057 smbWrite['Buffer'] = data[:maxBytesToWrite]
1058 packet['Data'] = smbWrite
1060 packetID = self.sendSMB(packet)
1061 if waitAnswer is True:
1062 ans = self.recvSMB(packetID)
1064 return maxBytesToWrite
1066 if ans.isValidAnswer(STATUS_SUCCESS):
1067 writeResponse = SMB2Write_Response(ans['Data'])
1068 bytesWritten = writeResponse['Count']
1069 if bytesWritten < bytesToWrite:
1070 bytesWritten += self.write(treeId, fileId, data[bytesWritten:], offset+bytesWritten, bytesToWrite-bytesWritten, waitAnswer)
1073 def queryDirectory(self, treeId, fileId, searchString = '*', resumeIndex = 0, informationClass = FILENAMES_INFORMATION, maxBufferSize = None, enumRestart = False, singleEntry = False):
1074 if self._Session['TreeConnectTable'].has_key(treeId) is False:
1075 raise SessionError(STATUS_INVALID_PARAMETER)
1076 if self._Session['OpenTable'].has_key(fileId) is False:
1077 raise SessionError(STATUS_INVALID_PARAMETER)
1079 packet = self.SMB_PACKET()
1080 packet['Command'] = SMB2_QUERY_DIRECTORY
1081 packet['TreeID'] = treeId
1083 queryDirectory = SMB2QueryDirectory()
1084 queryDirectory['FileInformationClass'] = informationClass
1085 if resumeIndex != 0 :
1086 queryDirectory['Flags'] = SMB2_INDEX_SPECIFIED
1087 queryDirectory['FileIndex'] = resumeIndex
1088 queryDirectory['FileID'] = fileId
1089 if maxBufferSize is None:
1090 maxBufferSize = self._Connection['MaxReadSize']
1091 queryDirectory['OutputBufferLength'] = maxBufferSize
1092 queryDirectory['FileNameLength'] = len(searchString)*2
1093 queryDirectory['Buffer'] = searchString.encode('utf-16le')
1095 packet['Data'] = queryDirectory
1097 if self._Connection['Dialect'] != SMB2_DIALECT_002 and self._Connection['SupportsMultiCredit'] is True:
1098 packet['CreditCharge'] = ( 1 + (maxBufferSize - 1) / 65536)
1100 packetID = self.sendSMB(packet)
1101 ans = self.recvSMB(packetID)
1102 if ans.isValidAnswer(STATUS_SUCCESS):
1103 queryDirectoryResponse = SMB2QueryDirectory_Response(ans['Data'])
1104 return queryDirectoryResponse['Buffer']
1107 packet = self.SMB_PACKET()
1108 packet['Command'] = SMB2_ECHO
1109 smbEcho = SMB2Echo()
1110 packet['Data'] = smbEcho
1111 packetID = self.sendSMB(packet)
1112 ans = self.recvSMB(packetID)
1113 if ans.isValidAnswer(STATUS_SUCCESS):
1116 def cancel(self, packetID):
1117 packet = self.SMB_PACKET()
1118 packet['Command'] = SMB2_CANCEL
1119 packet['MessageID'] = packetID
1121 smbCancel = SMB2Cancel()
1123 packet['Data'] = smbCancel
1124 self.sendSMB(packet)
1126 def ioctl(self, treeId, fileId = None, ctlCode = -1, flags = 0, inputBlob = '', maxInputResponse = None, maxOutputResponse = None, waitAnswer = 1):
1127 if self._Session['TreeConnectTable'].has_key(treeId) is False:
1128 raise SessionError(STATUS_INVALID_PARAMETER)
1132 if self._Session['OpenTable'].has_key(fileId) is False:
1133 raise SessionError(STATUS_INVALID_PARAMETER)
1135 packet = self.SMB_PACKET()
1136 packet['Command'] = SMB2_IOCTL
1137 packet['TreeID'] = treeId
1139 smbIoctl = SMB2Ioctl()
1140 smbIoctl['FileID'] = fileId
1141 smbIoctl['CtlCode'] = ctlCode
1142 smbIoctl['MaxInputResponse'] = maxInputResponse
1143 smbIoctl['MaxOutputResponse'] = maxOutputResponse
1144 smbIoctl['InputCount'] = len(inputBlob)
1145 if len(inputBlob) == 0:
1146 smbIoctl['InputOffset'] = 0
1147 smbIoctl['Buffer'] = '\x00'
1149 smbIoctl['Buffer'] = inputBlob
1150 smbIoctl['OutputOffset'] = 0
1151 smbIoctl['MaxOutputResponse'] = maxOutputResponse
1152 smbIoctl['Flags'] = flags
1154 packet['Data'] = smbIoctl
1156 packetID = self.sendSMB(packet)
1161 ans = self.recvSMB(packetID)
1163 if ans.isValidAnswer(STATUS_SUCCESS):
1164 smbIoctlResponse = SMB2Ioctl_Response(ans['Data'])
1165 return smbIoctlResponse['Buffer']
1167 def flush(self,treeId, fileId):
1168 if self._Session['TreeConnectTable'].has_key(treeId) is False:
1169 raise SessionError(STATUS_INVALID_PARAMETER)
1170 if self._Session['OpenTable'].has_key(fileId) is False:
1171 raise SessionError(STATUS_INVALID_PARAMETER)
1173 packet = self.SMB_PACKET()
1174 packet['Command'] = SMB2_FLUSH
1175 packet['TreeID'] = treeId
1177 smbFlush = SMB2Flush()
1178 smbFlush['FileID'] = fileId
1180 packet['Data'] = smbFlush
1182 packetID = self.sendSMB(packet)
1183 ans = self.recvSMB(packetID)
1185 if ans.isValidAnswer(STATUS_SUCCESS):
1188 def lock(self, treeId, fileId, locks, lockSequence = 0):
1189 if self._Session['TreeConnectTable'].has_key(treeId) is False:
1190 raise SessionError(STATUS_INVALID_PARAMETER)
1191 if self._Session['OpenTable'].has_key(fileId) is False:
1192 raise SessionError(STATUS_INVALID_PARAMETER)
1194 packet = self.SMB_PACKET()
1195 packet['Command'] = SMB2_LOCK
1196 packet['TreeID'] = treeId
1198 smbLock = SMB2Lock()
1199 smbLock['FileID'] = fileId
1200 smbLock['LockCount'] = len(locks)
1201 smbLock['LockSequence'] = lockSequence
1202 smbLock['Locks'] = ''.join(str(x) for x in locks)
1204 packet['Data'] = smbLock
1206 packetID = self.sendSMB(packet)
1207 ans = self.recvSMB(packetID)
1209 if ans.isValidAnswer(STATUS_SUCCESS):
1210 smbFlushResponse = SMB2Lock_Response(ans['Data'])
1214 # If Open.ResilientHandle is TRUE or Connection.SupportsMultiChannel is TRUE, the client MUST
1216 # The client MUST scan through Open.OperationBuckets and find an element with its Free field
1217 # set to TRUE. If no such element could be found, an implementation-specific error MUST be
1218 # returned to the application.
1219 # Let the zero-based array index of the element chosen above be referred to as BucketIndex, and
1220 # let BucketNumber = BucketIndex +1.
1221 # Set Open.OperationBuckets[BucketIndex].Free = FALSE
1222 # Let the SequenceNumber of the element chosen above be referred to as BucketSequence.
1223 # The LockSequence field of the SMB2 lock request MUST be set to (BucketNumber<< 4) +
1225 # Increment the SequenceNumber of the element chosen above using MOD 16 arithmetic.
1228 packet = self.SMB_PACKET()
1229 packet['Command'] = SMB2_LOGOFF
1231 smbLogoff = SMB2Logoff()
1233 packet['Data'] = smbLogoff
1235 packetID = self.sendSMB(packet)
1236 ans = self.recvSMB(packetID)
1238 if ans.isValidAnswer(STATUS_SUCCESS):
1239 # We clean the stuff we used in case we want to authenticate again
1240 # within the same connection
1241 self._Session['UserCredentials'] = ''
1242 self._Session['Connection'] = 0
1243 self._Session['SessionID'] = 0
1244 self._Session['SigningRequired'] = False
1245 self._Session['SigningKey'] = ''
1246 self._Session['SessionKey'] = ''
1247 self._Session['SigningActivated'] = False
1250 def queryInfo(self, treeId, fileId, inputBlob = '', infoType = SMB2_0_INFO_FILE, fileInfoClass = SMB2_FILE_STANDARD_INFO, additionalInformation = 0, flags = 0 ):
1251 if self._Session['TreeConnectTable'].has_key(treeId) is False:
1252 raise SessionError(STATUS_INVALID_PARAMETER)
1253 if self._Session['OpenTable'].has_key(fileId) is False:
1254 raise SessionError(STATUS_INVALID_PARAMETER)
1256 packet = self.SMB_PACKET()
1257 packet['Command'] = SMB2_QUERY_INFO
1258 packet['TreeID'] = treeId
1260 queryInfo = SMB2QueryInfo()
1261 queryInfo['FileID'] = fileId
1262 queryInfo['InfoType'] = SMB2_0_INFO_FILE
1263 queryInfo['FileInfoClass'] = fileInfoClass
1264 queryInfo['OutputBufferLength'] = 65535
1265 queryInfo['AdditionalInformation'] = additionalInformation
1266 if len(inputBlob) == 0:
1267 queryInfo['InputBufferOffset'] = 0
1268 queryInfo['Buffer'] = '\x00'
1270 queryInfo['InputBufferLength'] = len(inputBlob)
1271 queryInfo['Buffer'] = inputBlob
1272 queryInfo['Flags'] = flags
1274 packet['Data'] = queryInfo
1275 packetID = self.sendSMB(packet)
1276 ans = self.recvSMB(packetID)
1278 if ans.isValidAnswer(STATUS_SUCCESS):
1279 queryResponse = SMB2QueryInfo_Response(ans['Data'])
1280 return queryResponse['Buffer']
1282 def setInfo(self, treeId, fileId, inputBlob = '', infoType = SMB2_0_INFO_FILE, fileInfoClass = SMB2_FILE_STANDARD_INFO, additionalInformation = 0 ):
1283 if self._Session['TreeConnectTable'].has_key(treeId) is False:
1284 raise SessionError(STATUS_INVALID_PARAMETER)
1285 if self._Session['OpenTable'].has_key(fileId) is False:
1286 raise SessionError(STATUS_INVALID_PARAMETER)
1288 packet = self.SMB_PACKET()
1289 packet['Command'] = SMB2_SET_INFO
1290 packet['TreeID'] = treeId
1292 setInfo = SMB2SetInfo()
1293 setInfo['InfoType'] = SMB2_0_INFO_FILE
1294 setInfo['FileInfoClass'] = fileInfoClass
1295 setInfo['BufferLength'] = len(inputBlob)
1296 setInfo['AdditionalInformation'] = additionalInformation
1297 setInfo['FileID'] = fileId
1298 setInfo['Buffer'] = inputBlob
1300 packet['Data'] = setInfo
1301 packetID = self.sendSMB(packet)
1302 ans = self.recvSMB(packetID)
1304 if ans.isValidAnswer(STATUS_SUCCESS):
1307 def getSessionKey(self):
1308 if self.getDialect() == SMB2_DIALECT_30:
1309 return self._Session['ApplicationKey']
1311 return self._Session['SessionKey']
1313 def setSessionKey(self, key):
1314 if self.getDialect() == SMB2_DIALECT_30:
1315 self._Session['ApplicationKey'] = key
1317 self._Session['SessionKey'] = key
1319 ######################################################################
1320 # Higher level functions
1322 def rename(self, shareName, oldPath, newPath):
1323 oldPath = string.replace(oldPath,'/', '\\')
1324 oldPath = ntpath.normpath(oldPath)
1325 if len(oldPath) > 0 and oldPath[0] == '\\':
1326 oldPath = oldPath[1:]
1328 newPath = string.replace(newPath,'/', '\\')
1329 newPath = ntpath.normpath(newPath)
1330 if len(newPath) > 0 and newPath[0] == '\\':
1331 newPath = newPath[1:]
1333 treeId = self.connectTree(shareName)
1336 fileId = self.create(treeId, oldPath, MAXIMUM_ALLOWED ,FILE_SHARE_READ | FILE_SHARE_WRITE |FILE_SHARE_DELETE, 0x200020, FILE_OPEN, 0)
1337 renameReq = FILE_RENAME_INFORMATION_TYPE_2()
1338 renameReq['ReplaceIfExists'] = 1
1339 renameReq['RootDirectory'] = '\x00'*8
1340 renameReq['FileNameLength'] = len(newPath)*2
1341 renameReq['FileName'] = newPath.encode('utf-16le')
1342 self.setInfo(treeId, fileId, renameReq, infoType = SMB2_0_INFO_FILE, fileInfoClass = SMB2_FILE_RENAME_INFO)
1344 if fileId is not None:
1345 self.close(treeId, fileId)
1346 self.disconnectTree(treeId)
1350 def writeFile(self, treeId, fileId, data, offset = 0):
1352 writeOffset = offset
1356 writeData = data[:self._Connection['MaxWriteSize']]
1357 data = data[self._Connection['MaxWriteSize']:]
1358 written = self.write(treeId, fileId, writeData, writeOffset, len(writeData))
1359 writeOffset += written
1360 return writeOffset - offset
1362 def listPath(self, shareName, path, password = None):
1363 # ToDo: Handle situations where share is password protected
1364 path = string.replace(path,'/', '\\')
1365 path = ntpath.normpath(path)
1366 if len(path) > 0 and path[0] == '\\':
1369 treeId = self.connectTree(shareName)
1373 # ToDo, we're assuming it's a directory, we should check what the file type is
1374 fileId = self.create(treeId, ntpath.dirname(path), FILE_READ_ATTRIBUTES | FILE_READ_DATA ,FILE_SHARE_READ | FILE_SHARE_WRITE |FILE_SHARE_DELETE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN, 0)
1377 from impacket import smb
1380 res = self.queryDirectory( treeId, fileId, ntpath.basename(path), maxBufferSize = 65535, informationClass = FILE_FULL_DIRECTORY_INFORMATION )
1382 while nextOffset != 0:
1383 fileInfo = smb.SMBFindFileFullDirectoryInfo(smb.SMB.FLAGS2_UNICODE)
1384 fileInfo.fromString(res)
1385 files.append(smb.SharedFile(fileInfo['CreationTime'],fileInfo['LastAccessTime'],fileInfo['LastChangeTime'],fileInfo['EndOfFile'],fileInfo['AllocationSize'],fileInfo['ExtFileAttributes'],fileInfo['FileName'].decode('utf-16le'), fileInfo['FileName'].decode('utf-16le')))
1386 nextOffset = fileInfo['NextEntryOffset']
1387 res = res[nextOffset:]
1388 except SessionError, e:
1389 if (e.get_error_code()) != STATUS_NO_MORE_FILES:
1393 if fileId is not None:
1394 self.close(treeId, fileId)
1395 self.disconnectTree(treeId)
1399 def mkdir(self, shareName, pathName, password = None):
1400 # ToDo: Handle situations where share is password protected
1401 pathName = string.replace(pathName,'/', '\\')
1402 pathName = ntpath.normpath(pathName)
1403 if len(pathName) > 0 and pathName[0] == '\\':
1404 pathName = pathName[1:]
1406 treeId = self.connectTree(shareName)
1410 fileId = self.create(treeId, pathName,GENERIC_ALL ,FILE_SHARE_READ | FILE_SHARE_WRITE |FILE_SHARE_DELETE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATE, 0)
1412 if fileId is not None:
1413 self.close(treeId, fileId)
1414 self.disconnectTree(treeId)
1418 def rmdir(self, shareName, pathName, password = None):
1419 # ToDo: Handle situations where share is password protected
1420 pathName = string.replace(pathName,'/', '\\')
1421 pathName = ntpath.normpath(pathName)
1422 if len(pathName) > 0 and pathName[0] == '\\':
1423 pathName = pathName[1:]
1425 treeId = self.connectTree(shareName)
1429 fileId = self.create(treeId, pathName, DELETE, FILE_SHARE_DELETE, FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_OPEN, 0)
1431 if fileId is not None:
1432 self.close(treeId, fileId)
1433 self.disconnectTree(treeId)
1437 def remove(self, shareName, pathName, password = None):
1438 # ToDo: Handle situations where share is password protected
1439 pathName = string.replace(pathName,'/', '\\')
1440 pathName = ntpath.normpath(pathName)
1441 if len(pathName) > 0 and pathName[0] == '\\':
1442 pathName = pathName[1:]
1444 treeId = self.connectTree(shareName)
1448 fileId = self.create(treeId, pathName,DELETE | FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_OPEN, 0)
1450 if fileId is not None:
1451 self.close(treeId, fileId)
1452 self.disconnectTree(treeId)
1456 def retrieveFile(self, shareName, path, callback, mode = FILE_OPEN, offset = 0, password = None, shareAccessMode = FILE_SHARE_READ):
1457 # ToDo: Handle situations where share is password protected
1458 path = string.replace(path,'/', '\\')
1459 path = ntpath.normpath(path)
1460 if len(path) > 0 and path[0] == '\\':
1463 treeId = self.connectTree(shareName)
1465 from impacket import smb
1467 fileId = self.create(treeId, path, FILE_READ_DATA, shareAccessMode, FILE_NON_DIRECTORY_FILE, mode, 0)
1468 res = self.queryInfo(treeId, fileId)
1469 fileInfo = smb.SMBQueryFileStandardInfo(res)
1470 fileSize = fileInfo['EndOfFile']
1471 if (fileSize-offset) < self._Connection['MaxReadSize']:
1472 # Skip reading 0 bytes files.
1473 if (fileSize-offset) > 0:
1474 data = self.read(treeId, fileId, offset, fileSize-offset)
1478 toBeRead = fileSize-offset
1479 while written < toBeRead:
1480 data = self.read(treeId, fileId, offset, self._Connection['MaxReadSize'])
1481 written += len(data)
1485 if fileId is not None:
1486 self.close(treeId, fileId)
1487 self.disconnectTree(treeId)
1489 def storeFile(self, shareName, path, callback, mode = FILE_OVERWRITE_IF, offset = 0, password = None, shareAccessMode = FILE_SHARE_WRITE):
1490 # ToDo: Handle situations where share is password protected
1491 path = string.replace(path,'/', '\\')
1492 path = ntpath.normpath(path)
1493 if len(path) > 0 and path[0] == '\\':
1496 treeId = self.connectTree(shareName)
1499 fileId = self.create(treeId, path, FILE_WRITE_DATA, shareAccessMode, FILE_NON_DIRECTORY_FILE, mode, 0)
1501 writeOffset = offset
1503 data = callback(self._Connection['MaxWriteSize'])
1506 written = self.write(treeId, fileId, data, writeOffset, len(data))
1507 writeOffset += written
1509 if fileId is not None:
1510 self.close(treeId, fileId)
1511 self.disconnectTree(treeId)
1513 def waitNamedPipe(self, treeId, pipename, timeout = 5):
1514 pipename = ntpath.basename(pipename)
1515 if self._Session['TreeConnectTable'].has_key(treeId) is False:
1516 raise SessionError(STATUS_INVALID_PARAMETER)
1517 if len(pipename) > 0xffff:
1518 raise SessionError(STATUS_INVALID_PARAMETER)
1520 pipeWait = FSCTL_PIPE_WAIT_STRUCTURE()
1521 pipeWait['Timeout'] = timeout*100000
1522 pipeWait['NameLength'] = len(pipename)*2
1523 pipeWait['TimeoutSpecified'] = 1
1524 pipeWait['Name'] = pipename.encode('utf-16le')
1526 return self.ioctl(treeId, None, FSCTL_PIPE_WAIT,flags=SMB2_0_IOCTL_IS_FSCTL, inputBlob=pipeWait, maxInputResponse = 0, maxOutputResponse=0)
1528 def getIOCapabilities(self):
1531 res['MaxReadSize'] = self._Connection['MaxReadSize']
1532 res['MaxWriteSize'] = self._Connection['MaxWriteSize']
1536 ######################################################################
1537 # Backward compatibility functions and alias for SMB1 and DCE Transports
1538 # NOTE: It is strongly recommended not to use these commands
1539 # when implementing new client calls.
1540 get_server_name = getServerName
1541 get_server_domain = getServerDomain
1542 get_server_dns_domain_name = getServerDNSDomainName
1543 get_remote_name = getServerName
1544 get_remote_host = getServerIP
1545 get_server_os = getServerOS
1546 get_server_os_major = getServerOSMajor
1547 get_server_os_minor = getServerOSMinor
1548 get_server_os_build = getServerOSBuild
1549 tree_connect_andx = connectTree
1550 tree_connect = connectTree
1551 connect_tree = connectTree
1552 disconnect_tree = disconnectTree
1553 set_timeout = setTimeout
1554 use_timeout = useTimeout
1555 stor_file = storeFile
1556 retr_file = retrieveFile
1557 list_path = listPath
1560 if self._NetBIOSSession:
1561 self._NetBIOSSession.close()
1564 def doesSupportNTLMv2(self):
1568 def is_login_required(self):
1572 def is_signing_required(self):
1573 return self._Session["SigningRequired"]
1575 def nt_create_andx(self, treeId, fileName, smb_packet=None, cmd = None):
1576 if len(fileName) > 0 and fileName[0] == '\\':
1577 fileName = fileName[1:]
1580 from impacket import smb
1581 ntCreate = smb.SMBCommand(data = str(cmd))
1582 params = smb.SMBNtCreateAndX_Parameters(ntCreate['Parameters'])
1583 return self.create(treeId, fileName, params['AccessMask'], params['ShareAccess'],
1584 params['CreateOptions'], params['Disposition'], params['FileAttributes'],
1585 params['Impersonation'], params['SecurityFlags'])
1588 return self.create(treeId, fileName,
1589 FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_READ_EA |
1590 FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | READ_CONTROL,
1591 FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_NON_DIRECTORY_FILE, FILE_OPEN, 0 )
1593 def get_socket(self):
1594 return self._NetBIOSSession.get_socket()
1597 def write_andx(self,tid,fid,data, offset = 0, wait_answer=1, write_pipe_mode = False, smb_packet=None):
1598 # ToDo: Handle the custom smb_packet situation
1599 return self.write(tid, fid, data, offset, len(data))
1601 def TransactNamedPipe(self, tid, fid, data, noAnswer = 0, waitAnswer = 1, offset = 0):
1602 return self.ioctl(tid, fid, FSCTL_PIPE_TRANSCEIVE, SMB2_0_IOCTL_IS_FSCTL, data, maxOutputResponse = 65535, waitAnswer = noAnswer | waitAnswer)
1604 def TransactNamedPipeRecv(self):
1605 ans = self.recvSMB()
1607 if ans.isValidAnswer(STATUS_SUCCESS):
1608 smbIoctlResponse = SMB2Ioctl_Response(ans['Data'])
1609 return smbIoctlResponse['Buffer']
1612 def read_andx(self, tid, fid, offset=0, max_size = None, wait_answer=1, smb_packet=None):
1613 # ToDo: Handle the custom smb_packet situation
1614 if max_size is None:
1615 max_size = self._Connection['MaxReadSize']
1616 return self.read(tid, fid, offset, max_size, wait_answer)
1618 def list_shared(self):
1619 # In the context of SMB2/3, forget about the old LANMAN, throw NOT IMPLEMENTED
1620 raise SessionError(STATUS_NOT_IMPLEMENTED)
1622 def open_andx(self, tid, fileName, open_mode, desired_access):
1623 # ToDo Return all the attributes of the file
1624 if len(fileName) > 0 and fileName[0] == '\\':
1625 fileName = fileName[1:]
1627 fileId = self.create(tid,fileName,desired_access, open_mode, FILE_NON_DIRECTORY_FILE, open_mode, 0)
1628 return fileId, 0, 0, 0, 0, 0, 0, 0, 0