from __future__ import print_function # Copyright (c) 2003-2016 CORE Security Technologies # # This software is provided under under a slightly modified version # of the Apache Software License. See the accompanying LICENSE file # for more information. # # Author: Alberto Solino (@agsolino) # # Description: # [MS-SMB2] Protocol Implementation (SMB2 and SMB3) # As you might see in the code, it's implemented strictly following # the structures defined in the protocol specification. This may # not be the most efficient way (e.g. self._Connection is the # same to self._Session in the context of this library ) but # it certainly helps following the document way easier. # # ToDo: # [X] Implement SMB2_CHANGE_NOTIFY # [X] Implement SMB2_QUERY_INFO # [X] Implement SMB2_SET_INFO # [ ] Implement SMB2_OPLOCK_BREAK # [X] Implement SMB3 signing # [ ] Implement SMB3 encryption # [ ] Add more backward compatible commands from the smb.py code # [ ] Fix up all the 'ToDo' comments inside the code # import socket import ntpath import random import string import struct from binascii import a2b_hex from contextlib import contextmanager from impacket import nmb, ntlm, uuid, crypto, LOG from impacket.smb3structs import * from impacket.nt_errors import STATUS_SUCCESS, STATUS_MORE_PROCESSING_REQUIRED, STATUS_INVALID_PARAMETER, \ STATUS_NO_MORE_FILES, STATUS_PENDING, STATUS_NOT_IMPLEMENTED, ERROR_MESSAGES from impacket.spnego import SPNEGO_NegTokenInit, TypesMech, SPNEGO_NegTokenResp # For signing import hashlib, hmac, copy # Structs to be used TREE_CONNECT = { 'ShareName' : '', 'TreeConnectId' : 0, 'Session' : 0, 'IsDfsShare' : False, # If the client implements the SMB 3.0 dialect, # the client MUST also implement the following 'IsCAShare' : False, 'EncryptData' : False, 'IsScaleoutShare' : False, # Outside the protocol 'NumberOfUses' : 0, } FILE = { 'OpenTable' : [], 'LeaseKey' : '', 'LeaseState' : 0, 'LeaseEpoch' : 0, } OPEN = { 'FileID' : '', 'TreeConnect' : 0, 'Connection' : 0, # Not Used 'Oplocklevel' : 0, 'Durable' : False, 'FileName' : '', 'ResilientHandle' : False, 'LastDisconnectTime' : 0, 'ResilientTimeout' : 0, 'OperationBuckets' : [], # If the client implements the SMB 3.0 dialect, # the client MUST implement the following 'CreateGuid' : '', 'IsPersistent' : False, 'DesiredAccess' : '', 'ShareMode' : 0, 'CreateOption' : '', 'FileAttributes' : '', 'CreateDisposition' : '', } REQUEST = { 'CancelID' : '', 'Message' : '', 'Timestamp' : 0, } CHANNEL = { 'SigningKey' : '', 'Connection' : 0, } class SessionError(Exception): def __init__( self, error = 0, packet=0): Exception.__init__(self) self.error = error self.packet = packet def get_error_code( self ): return self.error def get_error_packet( self ): return self.packet def __str__( self ): return 'SMB SessionError: %s(%s)' % (ERROR_MESSAGES[self.error]) class SMB3: 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): # [MS-SMB2] Section 3 self.RequireMessageSigning = False # self.ConnectionTable = {} self.GlobalFileTable = {} self.ClientGuid = ''.join([random.choice(string.letters) for i in range(16)]) # Only for SMB 3.0 self.EncryptionAlgorithmList = ['AES-CCM'] self.MaxDialect = [] self.RequireSecureNegotiate = False # Per Transport Connection Data self._Connection = { # Indexed by SessionID #'SessionTable' : {}, # Indexed by MessageID 'OutstandingRequests' : {}, 'OutstandingResponses' : {}, # 'SequenceWindow' : 0, # 'GSSNegotiateToken' : '', # 'MaxTransactSize' : 0, # 'MaxReadSize' : 0, # 'MaxWriteSize' : 0, # 'ServerGuid' : '', # 'RequireSigning' : False, # 'ServerName' : '', # # If the client implements the SMB 2.1 or SMB 3.0 dialects, it MUST # also implement the following 'Dialect' : '', # 'SupportsFileLeasing' : False, # 'SupportsMultiCredit' : False, # # If the client implements the SMB 3.0 dialect, # it MUST also implement the following 'SupportsDirectoryLeasing' : False, # 'SupportsMultiChannel' : False, # 'SupportsPersistentHandles': False, # 'SupportsEncryption' : False, # 'ClientCapabilities' : 0, 'ServerCapabilities' : 0, # 'ClientSecurityMode' : 0, # 'ServerSecurityMode' : 0, # # Outside the protocol 'ServerIP' : '', # } self._Session = { 'SessionID' : 0, # 'TreeConnectTable' : {}, # 'SessionKey' : '', # 'SigningRequired' : False, # 'Connection' : 0, # 'UserCredentials' : '', # 'OpenTable' : {}, # # If the client implements the SMB 3.0 dialect, # it MUST also implement the following 'ChannelList' : [], 'ChannelSequence' : 0, #'EncryptData' : False, 'EncryptData' : True, 'EncryptionKey' : '', 'DecryptionKey' : '', 'SigningKey' : '', 'ApplicationKey' : '', # Outside the protocol 'SessionFlags' : 0, # 'ServerName' : '', # 'ServerDomain' : '', # 'ServerDNSDomainName' : '', # 'ServerOS' : '', # 'SigningActivated' : False, # } self.SMB_PACKET = SMB2Packet self._timeout = timeout self._Connection['ServerIP'] = remote_host self._NetBIOSSession = None self.__userName = '' self.__password = '' self.__domain = '' self.__lmhash = '' self.__nthash = '' self.__kdc = '' self.__aesKey = '' self.__TGT = None self.__TGS = None if sess_port == 445 and remote_name == '*SMBSERVER': self._Connection['ServerName'] = remote_host else: self._Connection['ServerName'] = remote_name if session is None: if not my_name: my_name = socket.gethostname() i = string.find(my_name, '.') if i > -1: my_name = my_name[:i] if UDP: self._NetBIOSSession = nmb.NetBIOSUDPSession(my_name, self._Connection['ServerName'], remote_host, host_type, sess_port, self._timeout) else: self._NetBIOSSession = nmb.NetBIOSTCPSession(my_name, self._Connection['ServerName'], remote_host, host_type, sess_port, self._timeout) self.negotiateSession(preferredDialect) else: self._NetBIOSSession = session # We should increase the SequenceWindow since a packet was already received. self._Connection['SequenceWindow'] += 1 # Let's negotiate again using the same connection self.negotiateSession(preferredDialect) def printStatus(self): print("CONNECTION") for i in self._Connection.items(): print("%-40s : %s" % i) print() print("SESSION") for i in self._Session.items(): print("%-40s : %s" % i) def getServerName(self): return self._Session['ServerName'] def getServerIP(self): return self._Connection['ServerIP'] def getServerDomain(self): return self._Session['ServerDomain'] def getServerDNSDomainName(self): return self._Session['ServerDNSDomainName'] def getServerOS(self): return self._Session['ServerOS'] def getServerOSMajor(self): return self._Session['ServerOSMajor'] def getServerOSMinor(self): return self._Session['ServerOSMinor'] def getServerOSBuild(self): return self._Session['ServerOSBuild'] def isGuestSession(self): return self._Session['SessionFlags'] & SMB2_SESSION_FLAG_IS_GUEST def setTimeout(self, timeout): self._timeout = timeout @contextmanager def useTimeout(self, timeout): prev_timeout = self.getTimeout(timeout) try: yield finally: self.setTimeout(prev_timeout) def getDialect(self): return self._Connection['Dialect'] def signSMB(self, packet): packet['Signature'] = '\x00'*16 if self._Connection['Dialect'] == SMB2_DIALECT_21 or self._Connection['Dialect'] == SMB2_DIALECT_002: if len(self._Session['SessionKey']) > 0: signature = hmac.new(self._Session['SessionKey'], str(packet), hashlib.sha256).digest() packet['Signature'] = signature[:16] else: if len(self._Session['SessionKey']) > 0: p = str(packet) signature = crypto.AES_CMAC(self._Session['SigningKey'], p, len(p)) packet['Signature'] = signature def sendSMB(self, packet): # The idea here is to receive multiple/single commands and create a compound request, and send it # Should return the MessageID for later retrieval. Implement compounded related requests. # If Connection.Dialect is equal to "3.000" and if Connection.SupportsMultiChannel or # Connection.SupportsPersistentHandles is TRUE, the client MUST set ChannelSequence in the # SMB2 header to Session.ChannelSequence # Check this is not a CANCEL request. If so, don't consume sequece numbers if packet['Command'] is not SMB2_CANCEL: packet['MessageID'] = self._Connection['SequenceWindow'] self._Connection['SequenceWindow'] += 1 packet['SessionID'] = self._Session['SessionID'] # Default the credit charge to 1 unless set by the caller if ('CreditCharge' in packet.fields) is False: packet['CreditCharge'] = 1 # Standard credit request after negotiating protocol if self._Connection['SequenceWindow'] > 3: packet['CreditRequestResponse'] = 127 messageId = packet['MessageID'] if self._Session['SigningActivated'] is True and self._Connection['SequenceWindow'] > 2: if packet['TreeID'] > 0 and (packet['TreeID'] in self._Session['TreeConnectTable']) is True: if self._Session['TreeConnectTable'][packet['TreeID']]['EncryptData'] is False: packet['Flags'] = SMB2_FLAGS_SIGNED self.signSMB(packet) elif packet['TreeID'] == 0: packet['Flags'] = SMB2_FLAGS_SIGNED self.signSMB(packet) if (self._Session['SessionFlags'] & SMB2_SESSION_FLAG_ENCRYPT_DATA) or ( packet['TreeID'] != 0 and self._Session['TreeConnectTable'][packet['TreeID']]['EncryptData'] is True): plainText = str(packet) transformHeader = SMB2_TRANSFORM_HEADER() transformHeader['Nonce'] = ''.join([random.choice(string.letters) for i in range(11)]) transformHeader['OriginalMessageSize'] = len(plainText) transformHeader['EncryptionAlgorithm'] = SMB2_ENCRYPTION_AES128_CCM transformHeader['SessionID'] = self._Session['SessionID'] from Crypto.Cipher import AES try: AES.MODE_CCM except: 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 ") raise cipher = AES.new(self._Session['EncryptionKey'], AES.MODE_CCM, transformHeader['Nonce']) cipher.update(str(transformHeader)[20:]) cipherText = cipher.encrypt(plainText) transformHeader['Signature'] = cipher.digest() packet = str(transformHeader) + cipherText self._NetBIOSSession.send_packet(str(packet)) return messageId def recvSMB(self, packetID = None): # First, verify we don't have the packet already if packetID in self._Connection['OutstandingResponses']: return self._Connection['OutstandingResponses'].pop(packetID) data = self._NetBIOSSession.recv_packet(self._timeout) if data.get_trailer().startswith('\xfdSMB'): # Packet is encrypted transformHeader = SMB2_TRANSFORM_HEADER(data.get_trailer()) from Crypto.Cipher import AES try: AES.MODE_CCM except: 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 ") raise cipher = AES.new(self._Session['DecryptionKey'], AES.MODE_CCM, transformHeader['Nonce'][:11]) cipher.update(str(transformHeader)[20:]) plainText = cipher.decrypt(data.get_trailer()[len(SMB2_TRANSFORM_HEADER()):]) #cipher.verify(transformHeader['Signature']) packet = SMB2Packet(plainText) else: # In all SMB dialects for a response this field is interpreted as the Status field. # This field can be set to any value. For a list of valid status codes, # see [MS-ERREF] section 2.3. packet = SMB2Packet(data.get_trailer()) # Loop while we receive pending requests if packet['Status'] == STATUS_PENDING: status = STATUS_PENDING while status == STATUS_PENDING: data = self._NetBIOSSession.recv_packet(self._timeout) if data.get_trailer().startswith('\xfeSMB'): packet = SMB2Packet(data.get_trailer()) else: # Packet is encrypted transformHeader = SMB2_TRANSFORM_HEADER(data.get_trailer()) from Crypto.Cipher import AES try: AES.MODE_CCM except: 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 ") raise cipher = AES.new(self._Session['DecryptionKey'], AES.MODE_CCM, transformHeader['Nonce'][:11]) cipher.update(str(transformHeader)[20:]) plainText = cipher.decrypt(data.get_trailer()[len(SMB2_TRANSFORM_HEADER()):]) #cipher.verify(transformHeader['Signature']) packet = SMB2Packet(plainText) status = packet['Status'] if packet['MessageID'] == packetID or packetID is None: # if self._Session['SigningRequired'] is True: # self.signSMB(packet) # Let's update the sequenceWindow based on the CreditsCharged self._Connection['SequenceWindow'] += (packet['CreditCharge'] - 1) return packet else: self._Connection['OutstandingResponses'][packet['MessageID']] = packet return self.recvSMB(packetID) def negotiateSession(self, preferredDialect = None): packet = self.SMB_PACKET() packet['Command'] = SMB2_NEGOTIATE negSession = SMB2Negotiate() negSession['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_ENABLED if self.RequireMessageSigning is True: negSession['SecurityMode'] |= SMB2_NEGOTIATE_SIGNING_REQUIRED negSession['Capabilities'] = SMB2_GLOBAL_CAP_ENCRYPTION negSession['ClientGuid'] = self.ClientGuid if preferredDialect is not None: negSession['Dialects'] = [preferredDialect] else: negSession['Dialects'] = [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30] negSession['DialectCount'] = len(negSession['Dialects']) packet['Data'] = negSession # Storing this data for later use self._Connection['ClientSecurityMode'] = negSession['SecurityMode'] self._Connection['Capabilities'] = negSession['Capabilities'] packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): # ToDo this: # If the DialectRevision in the SMB2 NEGOTIATE Response is 0x02FF, the client MUST issue a new # SMB2 NEGOTIATE request as described in section 3.2.4.2.2.2 with the only exception # that the client MUST allocate sequence number 1 from Connection.SequenceWindow, and MUST set # MessageId field of the SMB2 header to 1. Otherwise, the client MUST proceed as follows. negResp = SMB2Negotiate_Response(ans['Data']) self._Connection['MaxTransactSize'] = min(0x100000,negResp['MaxTransactSize']) self._Connection['MaxReadSize'] = min(0x100000,negResp['MaxReadSize']) self._Connection['MaxWriteSize'] = min(0x100000,negResp['MaxWriteSize']) self._Connection['ServerGuid'] = negResp['ServerGuid'] self._Connection['GSSNegotiateToken'] = negResp['Buffer'] self._Connection['Dialect'] = negResp['DialectRevision'] if (negResp['SecurityMode'] & SMB2_NEGOTIATE_SIGNING_REQUIRED) == SMB2_NEGOTIATE_SIGNING_REQUIRED: self._Connection['RequireSigning'] = True if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LEASING) == SMB2_GLOBAL_CAP_LEASING: self._Connection['SupportsFileLeasing'] = True if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LARGE_MTU) == SMB2_GLOBAL_CAP_LARGE_MTU: self._Connection['SupportsMultiCredit'] = True if self._Connection['Dialect'] == SMB2_DIALECT_30: # Switching to the right packet format self.SMB_PACKET = SMB3Packet if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) == SMB2_GLOBAL_CAP_DIRECTORY_LEASING: self._Connection['SupportsDirectoryLeasing'] = True if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_MULTI_CHANNEL) == SMB2_GLOBAL_CAP_MULTI_CHANNEL: self._Connection['SupportsMultiChannel'] = True if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) == SMB2_GLOBAL_CAP_PERSISTENT_HANDLES: self._Connection['SupportsPersistentHandles'] = True if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_ENCRYPTION) == SMB2_GLOBAL_CAP_ENCRYPTION: self._Connection['SupportsEncryption'] = True self._Connection['ServerCapabilities'] = negResp['Capabilities'] self._Connection['ServerSecurityMode'] = negResp['SecurityMode'] def getCredentials(self): return ( self.__userName, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, self.__TGT, self.__TGS) def kerberosLogin(self, user, password, domain = '', lmhash = '', nthash = '', aesKey='', kdcHost = '', TGT=None, TGS=None): # If TGT or TGS are specified, they are in the form of: # TGS['KDC_REP'] = the response from the server # TGS['cipher'] = the cipher used # TGS['sessionKey'] = the sessionKey # If we have hashes, normalize them if lmhash != '' or nthash != '': if len(lmhash) % 2: lmhash = '0%s' % lmhash if len(nthash) % 2: nthash = '0%s' % nthash try: # just in case they were converted already lmhash = a2b_hex(lmhash) nthash = a2b_hex(nthash) except: pass self.__userName = user self.__password = password self.__domain = domain self.__lmhash = lmhash self.__nthash = nthash self.__kdc = kdcHost self.__aesKey = aesKey self.__TGT = TGT self.__TGS = TGS sessionSetup = SMB2SessionSetup() if self.RequireMessageSigning is True: sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_REQUIRED else: sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_ENABLED sessionSetup['Flags'] = 0 #sessionSetup['Capabilities'] = SMB2_GLOBAL_CAP_LARGE_MTU | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_DFS # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket from pyasn1.codec.der import decoder, encoder import datetime # First of all, we need to get a TGT for the user userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] # Save the ticket # If you want, for debugging purposes # from impacket.krb5.ccache import CCache # ccache = CCache() # try: # if TGS is None: # ccache.fromTGT(tgt, oldSessionKey, sessionKey) # else: # ccache.fromTGS(TGS['KDC_REP'], TGS['oldSessionKey'], TGS['sessionKey'] ) # ccache.saveFile('/tmp/ticket.bin') # except Exception, e: # print e # pass # Now that we have the TGT, we should ask for a TGS for cifs if TGS is None: serverName = Principal('cifs/%s' % (self._Connection['ServerName']), type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] # Let's build a NegTokenInit with a Kerberos REQ_AP blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec = TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = list() apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq,'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', userName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator'] = None apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = encoder.encode(apReq) sessionSetup['SecurityBufferLength'] = len(blob) sessionSetup['Buffer'] = blob.getData() packet = self.SMB_PACKET() packet['Command'] = SMB2_SESSION_SETUP packet['Data'] = sessionSetup packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): self._Session['SessionID'] = ans['SessionID'] self._Session['SigningRequired'] = self._Connection['RequireSigning'] self._Session['UserCredentials'] = (user, password, domain, lmhash, nthash) self._Session['Connection'] = self._NetBIOSSession.get_socket() self._Session['SessionKey'] = sessionKey.contents[:16] if self._Session['SigningRequired'] is True and self._Connection['Dialect'] == SMB2_DIALECT_30: self._Session['SigningKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], "SMB2AESCMAC\x00", "SmbSign\x00", 128) # Calculate the key derivations for dialect 3.0 if self._Session['SigningRequired'] is True: self._Session['SigningActivated'] = True if self._Connection['Dialect'] == SMB2_DIALECT_30: self._Session['ApplicationKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], "SMB2APP\x00", "SmbRpc\x00", 128) self._Session['EncryptionKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], "SMB2AESCCM\x00", "ServerIn \x00", 128) self._Session['DecryptionKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], "SMB2AESCCM\x00", "ServerOut\x00", 128) return True else: # We clean the stuff we used in case we want to authenticate again # within the same connection self._Session['UserCredentials'] = '' self._Session['Connection'] = 0 self._Session['SessionID'] = 0 self._Session['SigningRequired'] = False self._Session['SigningKey'] = '' self._Session['SessionKey'] = '' self._Session['SigningActivated'] = False raise def login(self, user, password, domain = '', lmhash = '', nthash = ''): # If we have hashes, normalize them if lmhash != '' or nthash != '': if len(lmhash) % 2: lmhash = '0%s' % lmhash if len(nthash) % 2: nthash = '0%s' % nthash try: # just in case they were converted already lmhash = a2b_hex(lmhash) nthash = a2b_hex(nthash) except: pass self.__userName = user self.__password = password self.__domain = domain self.__lmhash = lmhash self.__nthash = nthash self.__aesKey = '' self.__TGT = None self.__TGS = None sessionSetup = SMB2SessionSetup() if self.RequireMessageSigning is True: sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_REQUIRED else: sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_ENABLED sessionSetup['Flags'] = 0 #sessionSetup['Capabilities'] = SMB2_GLOBAL_CAP_LARGE_MTU | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_DFS # Let's build a NegTokenInit with the NTLMSSP # TODO: In the future we should be able to choose different providers blob = SPNEGO_NegTokenInit() # NTLMSSP blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']] auth = ntlm.getNTLMSSPType1('','', self._Connection['RequireSigning']) blob['MechToken'] = str(auth) sessionSetup['SecurityBufferLength'] = len(blob) sessionSetup['Buffer'] = blob.getData() # ToDo: # If this authentication is for establishing an alternative channel for an existing Session, as specified # in section 3.2.4.1.7, the client MUST also set the following values: # The SessionId field in the SMB2 header MUST be set to the Session.SessionId for the new # channel being established. # The SMB2_SESSION_FLAG_BINDING bit MUST be set in the Flags field. # The PreviousSessionId field MUST be set to zero. packet = self.SMB_PACKET() packet['Command'] = SMB2_SESSION_SETUP packet['Data'] = sessionSetup packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_MORE_PROCESSING_REQUIRED): self._Session['SessionID'] = ans['SessionID'] self._Session['SigningRequired'] = self._Connection['RequireSigning'] self._Session['UserCredentials'] = (user, password, domain, lmhash, nthash) self._Session['Connection'] = self._NetBIOSSession.get_socket() sessionSetupResponse = SMB2SessionSetup_Response(ans['Data']) respToken = SPNEGO_NegTokenResp(sessionSetupResponse['Buffer']) # Let's parse some data and keep it to ourselves in case it is asked ntlmChallenge = ntlm.NTLMAuthChallenge(respToken['ResponseToken']) if ntlmChallenge['TargetInfoFields_len'] > 0: av_pairs = ntlm.AV_PAIRS(ntlmChallenge['TargetInfoFields'][:ntlmChallenge['TargetInfoFields_len']]) if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None: try: self._Session['ServerName'] = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode('utf-16le') except: # For some reason, we couldn't decode Unicode here.. silently discard the operation pass if av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME] is not None: try: if self._Session['ServerName'] != av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le'): self._Session['ServerDomain'] = av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le') except: # For some reason, we couldn't decode Unicode here.. silently discard the operation pass if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] is not None: try: self._Session['ServerDNSDomainName'] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode('utf-16le') except: # For some reason, we couldn't decode Unicode here.. silently discard the operation pass # Parse Version to know the target Operating system name. Not provided elsewhere anymore if 'Version' in ntlmChallenge.fields: version = ntlmChallenge['Version'] if len(version) >= 4: self._Session['ServerOS'] = "Windows %d.%d Build %d" % (ord(version[0]), ord(version[1]), struct.unpack('<H',version[2:4])[0]) self._Session["ServerOSMajor"] = ord(version[0]) self._Session["ServerOSMinor"] = ord(version[1]) self._Session["ServerOSBuild"] = struct.unpack('<H',version[2:4])[0] type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, respToken['ResponseToken'], user, password, domain, lmhash, nthash) if exportedSessionKey is not None: self._Session['SessionKey'] = exportedSessionKey if self._Session['SigningRequired'] is True and self._Connection['Dialect'] == SMB2_DIALECT_30: self._Session['SigningKey'] = crypto.KDF_CounterMode(exportedSessionKey, "SMB2AESCMAC\x00", "SmbSign\x00", 128) respToken2 = SPNEGO_NegTokenResp() respToken2['ResponseToken'] = str(type3) # Reusing the previous structure sessionSetup['SecurityBufferLength'] = len(respToken2) sessionSetup['Buffer'] = respToken2.getData() packetID = self.sendSMB(packet) packet = self.recvSMB(packetID) try: if packet.isValidAnswer(STATUS_SUCCESS): sessionSetupResponse = SMB2SessionSetup_Response(packet['Data']) self._Session['SessionFlags'] = sessionSetupResponse['SessionFlags'] # Calculate the key derivations for dialect 3.0 if self._Session['SigningRequired'] is True: self._Session['SigningActivated'] = True if self._Connection['Dialect'] == SMB2_DIALECT_30: self._Session['ApplicationKey'] = crypto.KDF_CounterMode(exportedSessionKey, "SMB2APP\x00", "SmbRpc\x00", 128) self._Session['EncryptionKey'] = crypto.KDF_CounterMode(exportedSessionKey, "SMB2AESCCM\x00", "ServerIn \x00", 128) self._Session['DecryptionKey'] = crypto.KDF_CounterMode(exportedSessionKey, "SMB2AESCCM\x00", "ServerOut\x00", 128) return True except: # We clean the stuff we used in case we want to authenticate again # within the same connection self._Session['UserCredentials'] = '' self._Session['Connection'] = 0 self._Session['SessionID'] = 0 self._Session['SigningRequired'] = False self._Session['SigningKey'] = '' self._Session['SessionKey'] = '' self._Session['SigningActivated'] = False raise def connectTree(self, share): # Just in case this came with the full path (maybe an SMB1 client), let's just leave # the sharename, we'll take care of the rest #print self._Session['TreeConnectTable'] share = share.split('\\')[-1] if share in self._Session['TreeConnectTable']: # Already connected, no need to reconnect treeEntry = self._Session['TreeConnectTable'][share] treeEntry['NumberOfUses'] += 1 self._Session['TreeConnectTable'][treeEntry['TreeConnectId']]['NumberOfUses'] += 1 return treeEntry['TreeConnectId'] #path = share try: _, _, _, _, sockaddr = socket.getaddrinfo(self._Connection['ServerIP'], 80, 0, 0, socket.IPPROTO_TCP)[0] remoteHost = sockaddr[0] except: remoteHost = self._Connection['ServerIP'] path = '\\\\' + remoteHost + '\\' +share treeConnect = SMB2TreeConnect() treeConnect['Buffer'] = path.encode('utf-16le') treeConnect['PathLength'] = len(path)*2 packet = self.SMB_PACKET() packet['Command'] = SMB2_TREE_CONNECT packet['Data'] = treeConnect packetID = self.sendSMB(packet) packet = self.recvSMB(packetID) if packet.isValidAnswer(STATUS_SUCCESS): treeConnectResponse = SMB2TreeConnect_Response(packet['Data']) treeEntry = copy.deepcopy(TREE_CONNECT) treeEntry['ShareName'] = share treeEntry['TreeConnectId'] = packet['TreeID'] treeEntry['Session'] = packet['SessionID'] treeEntry['NumberOfUses'] += 1 if (treeConnectResponse['Capabilities'] & SMB2_SHARE_CAP_DFS) == SMB2_SHARE_CAP_DFS: treeEntry['IsDfsShare'] = True if (treeConnectResponse['Capabilities'] & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY) == SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY: treeEntry['IsCAShare'] = True if self._Connection['Dialect'] == SMB2_DIALECT_30: if (self._Connection['SupportsEncryption'] is True) and ((treeConnectResponse['ShareFlags'] & SMB2_SHAREFLAG_ENCRYPT_DATA) == SMB2_SHAREFLAG_ENCRYPT_DATA): treeEntry['EncryptData'] = True # ToDo: This and what follows # If Session.EncryptData is FALSE, the client MUST then generate an encryption key, a # decryption key as specified in section 3.1.4.2, by providing the following inputs and store # them in Session.EncryptionKey and Session.DecryptionKey: if (treeConnectResponse['Capabilities'] & SMB2_SHARE_CAP_SCALEOUT) == SMB2_SHARE_CAP_SCALEOUT: treeEntry['IsScaleoutShare'] = True self._Session['TreeConnectTable'][packet['TreeID']] = treeEntry self._Session['TreeConnectTable'][share] = treeEntry return packet['TreeID'] def disconnectTree(self, treeId): if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if treeId in self._Session['TreeConnectTable']: # More than 1 use? descrease it and return, if not, send the packet if self._Session['TreeConnectTable'][treeId]['NumberOfUses'] > 1: treeEntry = self._Session['TreeConnectTable'][treeId] treeEntry['NumberOfUses'] -= 1 self._Session['TreeConnectTable'][treeEntry['ShareName']]['NumberOfUses'] -= 1 return True packet = self.SMB_PACKET() packet['Command'] = SMB2_TREE_DISCONNECT packet['TreeID'] = treeId treeDisconnect = SMB2TreeDisconnect() packet['Data'] = treeDisconnect packetID = self.sendSMB(packet) packet = self.recvSMB(packetID) if packet.isValidAnswer(STATUS_SUCCESS): shareName = self._Session['TreeConnectTable'][treeId]['ShareName'] del(self._Session['TreeConnectTable'][shareName]) del(self._Session['TreeConnectTable'][treeId]) return True def create(self, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition, fileAttributes, impersonationLevel = SMB2_IL_IMPERSONATION, securityFlags = 0, oplockLevel = SMB2_OPLOCK_LEVEL_NONE, createContexts = None): if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) fileName = string.replace(fileName, '/', '\\') if len(fileName) > 0: fileName = ntpath.normpath(fileName) if fileName[0] == '\\': fileName = fileName[1:] if self._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True: pathName = fileName else: pathName = '\\\\' + self._Connection['ServerName'] + '\\' + fileName fileEntry = copy.deepcopy(FILE) fileEntry['LeaseKey'] = uuid.generate() fileEntry['LeaseState'] = SMB2_LEASE_NONE self.GlobalFileTable[pathName] = fileEntry if self._Connection['Dialect'] == SMB2_DIALECT_30 and self._Connection['SupportsDirectoryLeasing'] is True: # Is this file NOT on the root directory? if len(fileName.split('\\')) > 2: parentDir = ntpath.dirname(pathName) if parentDir in self.GlobalFileTable: LOG.critical("Don't know what to do now! :-o") raise else: parentEntry = copy.deepcopy(FILE) parentEntry['LeaseKey'] = uuid.generate() parentEntry['LeaseState'] = SMB2_LEASE_NONE self.GlobalFileTable[parentDir] = parentEntry packet = self.SMB_PACKET() packet['Command'] = SMB2_CREATE packet['TreeID'] = treeId if self._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True: packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS smb2Create = SMB2Create() smb2Create['SecurityFlags'] = 0 smb2Create['RequestedOplockLevel'] = oplockLevel smb2Create['ImpersonationLevel'] = impersonationLevel smb2Create['DesiredAccess'] = desiredAccess smb2Create['FileAttributes'] = fileAttributes smb2Create['ShareAccess'] = shareMode smb2Create['CreateDisposition'] = creationDisposition smb2Create['CreateOptions'] = creationOptions smb2Create['NameLength'] = len(fileName)*2 if fileName != '': smb2Create['Buffer'] = fileName.encode('utf-16le') else: smb2Create['Buffer'] = '\x00' if createContexts is not None: smb2Create['Buffer'] += createContexts smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength'] smb2Create['CreateContextsLength'] = len(createContexts) else: smb2Create['CreateContextsOffset'] = 0 smb2Create['CreateContextsLength'] = 0 packet['Data'] = smb2Create packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): createResponse = SMB2Create_Response(ans['Data']) openFile = copy.deepcopy(OPEN) openFile['FileID'] = createResponse['FileID'] openFile['TreeConnect'] = treeId openFile['Oplocklevel'] = oplockLevel openFile['Durable'] = False openFile['ResilientHandle'] = False openFile['LastDisconnectTime'] = 0 openFile['FileName'] = pathName # ToDo: Complete the OperationBuckets if self._Connection['Dialect'] == SMB2_DIALECT_30: openFile['DesiredAccess'] = oplockLevel openFile['ShareMode'] = oplockLevel openFile['CreateOptions'] = oplockLevel openFile['FileAttributes'] = oplockLevel openFile['CreateDisposition'] = oplockLevel # ToDo: Process the contexts self._Session['OpenTable'][str(createResponse['FileID'])] = openFile # The client MUST generate a handle for the Open, and it MUST # return success and the generated handle to the calling application. # In our case, str(FileID) return str(createResponse['FileID']) def close(self, treeId, fileId): if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if (fileId in self._Session['OpenTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) packet = self.SMB_PACKET() packet['Command'] = SMB2_CLOSE packet['TreeID'] = treeId smbClose = SMB2Close() smbClose['Flags'] = 0 smbClose['FileID'] = fileId packet['Data'] = smbClose packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): del(self.GlobalFileTable[self._Session['OpenTable'][fileId]['FileName']]) del(self._Session['OpenTable'][fileId]) # ToDo Remove stuff from GlobalFileTable return True def read(self, treeId, fileId, offset = 0, bytesToRead = 0, waitAnswer = True): # IMPORTANT NOTE: As you can see, this was coded as a recursive function # Hence, you can exhaust the memory pretty easy ( large bytesToRead ) # This function should NOT be used for reading files directly, but another higher # level function should be used that will break the read into smaller pieces if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if (fileId in self._Session['OpenTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) packet = self.SMB_PACKET() packet['Command'] = SMB2_READ packet['TreeID'] = treeId if self._Connection['MaxReadSize'] < bytesToRead: maxBytesToRead = self._Connection['MaxReadSize'] else: maxBytesToRead = bytesToRead if self._Connection['Dialect'] != SMB2_DIALECT_002 and self._Connection['SupportsMultiCredit'] is True: packet['CreditCharge'] = ( 1 + (maxBytesToRead - 1) / 65536) else: maxBytesToRead = min(65536,bytesToRead) smbRead = SMB2Read() smbRead['Padding'] = 0x50 smbRead['FileID'] = fileId smbRead['Length'] = maxBytesToRead smbRead['Offset'] = offset packet['Data'] = smbRead packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): readResponse = SMB2Read_Response(ans['Data']) retData = readResponse['Buffer'] if readResponse['DataRemaining'] > 0: retData += self.read(treeId, fileId, offset+len(retData), readResponse['DataRemaining'], waitAnswer) return retData def write(self, treeId, fileId, data, offset = 0, bytesToWrite = 0, waitAnswer = True): # IMPORTANT NOTE: As you can see, this was coded as a recursive function # Hence, you can exhaust the memory pretty easy ( large bytesToWrite ) # This function should NOT be used for writing directly to files, but another higher # level function should be used that will break the writes into smaller pieces if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if (fileId in self._Session['OpenTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) packet = self.SMB_PACKET() packet['Command'] = SMB2_WRITE packet['TreeID'] = treeId if self._Connection['MaxWriteSize'] < bytesToWrite: maxBytesToWrite = self._Connection['MaxWriteSize'] else: maxBytesToWrite = bytesToWrite if self._Connection['Dialect'] != SMB2_DIALECT_002 and self._Connection['SupportsMultiCredit'] is True: packet['CreditCharge'] = ( 1 + (maxBytesToWrite - 1) / 65536) else: maxBytesToWrite = min(65536,bytesToWrite) smbWrite = SMB2Write() smbWrite['FileID'] = fileId smbWrite['Length'] = maxBytesToWrite smbWrite['Offset'] = offset smbWrite['WriteChannelInfoOffset'] = 0 smbWrite['Buffer'] = data[:maxBytesToWrite] packet['Data'] = smbWrite packetID = self.sendSMB(packet) if waitAnswer is True: ans = self.recvSMB(packetID) else: return maxBytesToWrite if ans.isValidAnswer(STATUS_SUCCESS): writeResponse = SMB2Write_Response(ans['Data']) bytesWritten = writeResponse['Count'] if bytesWritten < bytesToWrite: bytesWritten += self.write(treeId, fileId, data[bytesWritten:], offset+bytesWritten, bytesToWrite-bytesWritten, waitAnswer) return bytesWritten def queryDirectory(self, treeId, fileId, searchString = '*', resumeIndex = 0, informationClass = FILENAMES_INFORMATION, maxBufferSize = None, enumRestart = False, singleEntry = False): if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if (fileId in self._Session['OpenTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) packet = self.SMB_PACKET() packet['Command'] = SMB2_QUERY_DIRECTORY packet['TreeID'] = treeId queryDirectory = SMB2QueryDirectory() queryDirectory['FileInformationClass'] = informationClass if resumeIndex != 0 : queryDirectory['Flags'] = SMB2_INDEX_SPECIFIED queryDirectory['FileIndex'] = resumeIndex queryDirectory['FileID'] = fileId if maxBufferSize is None: maxBufferSize = self._Connection['MaxReadSize'] queryDirectory['OutputBufferLength'] = maxBufferSize queryDirectory['FileNameLength'] = len(searchString)*2 queryDirectory['Buffer'] = searchString.encode('utf-16le') packet['Data'] = queryDirectory if self._Connection['Dialect'] != SMB2_DIALECT_002 and self._Connection['SupportsMultiCredit'] is True: packet['CreditCharge'] = ( 1 + (maxBufferSize - 1) / 65536) packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): queryDirectoryResponse = SMB2QueryDirectory_Response(ans['Data']) return queryDirectoryResponse['Buffer'] def echo(self): packet = self.SMB_PACKET() packet['Command'] = SMB2_ECHO smbEcho = SMB2Echo() packet['Data'] = smbEcho packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): return True def cancel(self, packetID): packet = self.SMB_PACKET() packet['Command'] = SMB2_CANCEL packet['MessageID'] = packetID smbCancel = SMB2Cancel() packet['Data'] = smbCancel self.sendSMB(packet) def ioctl(self, treeId, fileId = None, ctlCode = -1, flags = 0, inputBlob = '', maxInputResponse = None, maxOutputResponse = None, waitAnswer = 1): if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if fileId is None: fileId = '\xff'*16 else: if (fileId in self._Session['OpenTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) packet = self.SMB_PACKET() packet['Command'] = SMB2_IOCTL packet['TreeID'] = treeId smbIoctl = SMB2Ioctl() smbIoctl['FileID'] = fileId smbIoctl['CtlCode'] = ctlCode smbIoctl['MaxInputResponse'] = maxInputResponse smbIoctl['MaxOutputResponse'] = maxOutputResponse smbIoctl['InputCount'] = len(inputBlob) if len(inputBlob) == 0: smbIoctl['InputOffset'] = 0 smbIoctl['Buffer'] = '\x00' else: smbIoctl['Buffer'] = inputBlob smbIoctl['OutputOffset'] = 0 smbIoctl['MaxOutputResponse'] = maxOutputResponse smbIoctl['Flags'] = flags packet['Data'] = smbIoctl packetID = self.sendSMB(packet) if waitAnswer == 0: return True ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): smbIoctlResponse = SMB2Ioctl_Response(ans['Data']) return smbIoctlResponse['Buffer'] def flush(self,treeId, fileId): if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if (fileId in self._Session['OpenTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) packet = self.SMB_PACKET() packet['Command'] = SMB2_FLUSH packet['TreeID'] = treeId smbFlush = SMB2Flush() smbFlush['FileID'] = fileId packet['Data'] = smbFlush packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): return True def lock(self, treeId, fileId, locks, lockSequence = 0): if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if (fileId in self._Session['OpenTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) packet = self.SMB_PACKET() packet['Command'] = SMB2_LOCK packet['TreeID'] = treeId smbLock = SMB2Lock() smbLock['FileID'] = fileId smbLock['LockCount'] = len(locks) smbLock['LockSequence'] = lockSequence smbLock['Locks'] = ''.join(str(x) for x in locks) packet['Data'] = smbLock packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): smbFlushResponse = SMB2Lock_Response(ans['Data']) return True # ToDo: # If Open.ResilientHandle is TRUE or Connection.SupportsMultiChannel is TRUE, the client MUST # do the following: # The client MUST scan through Open.OperationBuckets and find an element with its Free field # set to TRUE. If no such element could be found, an implementation-specific error MUST be # returned to the application. # Let the zero-based array index of the element chosen above be referred to as BucketIndex, and # let BucketNumber = BucketIndex +1. # Set Open.OperationBuckets[BucketIndex].Free = FALSE # Let the SequenceNumber of the element chosen above be referred to as BucketSequence. # The LockSequence field of the SMB2 lock request MUST be set to (BucketNumber<< 4) + # BucketSequence. # Increment the SequenceNumber of the element chosen above using MOD 16 arithmetic. def logoff(self): packet = self.SMB_PACKET() packet['Command'] = SMB2_LOGOFF smbLogoff = SMB2Logoff() packet['Data'] = smbLogoff packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): # We clean the stuff we used in case we want to authenticate again # within the same connection self._Session['UserCredentials'] = '' self._Session['Connection'] = 0 self._Session['SessionID'] = 0 self._Session['SigningRequired'] = False self._Session['SigningKey'] = '' self._Session['SessionKey'] = '' self._Session['SigningActivated'] = False return True def queryInfo(self, treeId, fileId, inputBlob = '', infoType = SMB2_0_INFO_FILE, fileInfoClass = SMB2_FILE_STANDARD_INFO, additionalInformation = 0, flags = 0 ): if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if (fileId in self._Session['OpenTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) packet = self.SMB_PACKET() packet['Command'] = SMB2_QUERY_INFO packet['TreeID'] = treeId queryInfo = SMB2QueryInfo() queryInfo['FileID'] = fileId queryInfo['InfoType'] = SMB2_0_INFO_FILE queryInfo['FileInfoClass'] = fileInfoClass queryInfo['OutputBufferLength'] = 65535 queryInfo['AdditionalInformation'] = additionalInformation if len(inputBlob) == 0: queryInfo['InputBufferOffset'] = 0 queryInfo['Buffer'] = '\x00' else: queryInfo['InputBufferLength'] = len(inputBlob) queryInfo['Buffer'] = inputBlob queryInfo['Flags'] = flags packet['Data'] = queryInfo packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): queryResponse = SMB2QueryInfo_Response(ans['Data']) return queryResponse['Buffer'] def setInfo(self, treeId, fileId, inputBlob = '', infoType = SMB2_0_INFO_FILE, fileInfoClass = SMB2_FILE_STANDARD_INFO, additionalInformation = 0 ): if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if (fileId in self._Session['OpenTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) packet = self.SMB_PACKET() packet['Command'] = SMB2_SET_INFO packet['TreeID'] = treeId setInfo = SMB2SetInfo() setInfo['InfoType'] = SMB2_0_INFO_FILE setInfo['FileInfoClass'] = fileInfoClass setInfo['BufferLength'] = len(inputBlob) setInfo['AdditionalInformation'] = additionalInformation setInfo['FileID'] = fileId setInfo['Buffer'] = inputBlob packet['Data'] = setInfo packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): return True def getSessionKey(self): if self.getDialect() == SMB2_DIALECT_30: return self._Session['ApplicationKey'] else: return self._Session['SessionKey'] def setSessionKey(self, key): if self.getDialect() == SMB2_DIALECT_30: self._Session['ApplicationKey'] = key else: self._Session['SessionKey'] = key ###################################################################### # Higher level functions def rename(self, shareName, oldPath, newPath): oldPath = string.replace(oldPath,'/', '\\') oldPath = ntpath.normpath(oldPath) if len(oldPath) > 0 and oldPath[0] == '\\': oldPath = oldPath[1:] newPath = string.replace(newPath,'/', '\\') newPath = ntpath.normpath(newPath) if len(newPath) > 0 and newPath[0] == '\\': newPath = newPath[1:] treeId = self.connectTree(shareName) fileId = None try: fileId = self.create(treeId, oldPath, MAXIMUM_ALLOWED ,FILE_SHARE_READ | FILE_SHARE_WRITE |FILE_SHARE_DELETE, 0x200020, FILE_OPEN, 0) renameReq = FILE_RENAME_INFORMATION_TYPE_2() renameReq['ReplaceIfExists'] = 1 renameReq['RootDirectory'] = '\x00'*8 renameReq['FileNameLength'] = len(newPath)*2 renameReq['FileName'] = newPath.encode('utf-16le') self.setInfo(treeId, fileId, renameReq, infoType = SMB2_0_INFO_FILE, fileInfoClass = SMB2_FILE_RENAME_INFO) finally: if fileId is not None: self.close(treeId, fileId) self.disconnectTree(treeId) return True def writeFile(self, treeId, fileId, data, offset = 0): finished = False writeOffset = offset while not finished: if len(data) == 0: break writeData = data[:self._Connection['MaxWriteSize']] data = data[self._Connection['MaxWriteSize']:] written = self.write(treeId, fileId, writeData, writeOffset, len(writeData)) writeOffset += written return writeOffset - offset def listPath(self, shareName, path, password = None): # ToDo: Handle situations where share is password protected path = string.replace(path,'/', '\\') path = ntpath.normpath(path) if len(path) > 0 and path[0] == '\\': path = path[1:] treeId = self.connectTree(shareName) fileId = None try: # ToDo, we're assuming it's a directory, we should check what the file type is 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) res = '' files = [] from impacket import smb while True: try: res = self.queryDirectory( treeId, fileId, ntpath.basename(path), maxBufferSize = 65535, informationClass = FILE_FULL_DIRECTORY_INFORMATION ) nextOffset = 1 while nextOffset != 0: fileInfo = smb.SMBFindFileFullDirectoryInfo(smb.SMB.FLAGS2_UNICODE) fileInfo.fromString(res) 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'))) nextOffset = fileInfo['NextEntryOffset'] res = res[nextOffset:] except SessionError as e: if (e.get_error_code()) != STATUS_NO_MORE_FILES: raise break finally: if fileId is not None: self.close(treeId, fileId) self.disconnectTree(treeId) return files def mkdir(self, shareName, pathName, password = None): # ToDo: Handle situations where share is password protected pathName = string.replace(pathName,'/', '\\') pathName = ntpath.normpath(pathName) if len(pathName) > 0 and pathName[0] == '\\': pathName = pathName[1:] treeId = self.connectTree(shareName) fileId = None try: 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) finally: if fileId is not None: self.close(treeId, fileId) self.disconnectTree(treeId) return True def rmdir(self, shareName, pathName, password = None): # ToDo: Handle situations where share is password protected pathName = string.replace(pathName,'/', '\\') pathName = ntpath.normpath(pathName) if len(pathName) > 0 and pathName[0] == '\\': pathName = pathName[1:] treeId = self.connectTree(shareName) fileId = None try: fileId = self.create(treeId, pathName, DELETE, FILE_SHARE_DELETE, FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_OPEN, 0) finally: if fileId is not None: self.close(treeId, fileId) self.disconnectTree(treeId) return True def remove(self, shareName, pathName, password = None): # ToDo: Handle situations where share is password protected pathName = string.replace(pathName,'/', '\\') pathName = ntpath.normpath(pathName) if len(pathName) > 0 and pathName[0] == '\\': pathName = pathName[1:] treeId = self.connectTree(shareName) fileId = None try: fileId = self.create(treeId, pathName,DELETE | FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_OPEN, 0) finally: if fileId is not None: self.close(treeId, fileId) self.disconnectTree(treeId) return True def retrieveFile(self, shareName, path, callback, mode = FILE_OPEN, offset = 0, password = None, shareAccessMode = FILE_SHARE_READ): # ToDo: Handle situations where share is password protected path = string.replace(path,'/', '\\') path = ntpath.normpath(path) if len(path) > 0 and path[0] == '\\': path = path[1:] treeId = self.connectTree(shareName) fileId = None from impacket import smb try: fileId = self.create(treeId, path, FILE_READ_DATA, shareAccessMode, FILE_NON_DIRECTORY_FILE, mode, 0) res = self.queryInfo(treeId, fileId) fileInfo = smb.SMBQueryFileStandardInfo(res) fileSize = fileInfo['EndOfFile'] if (fileSize-offset) < self._Connection['MaxReadSize']: # Skip reading 0 bytes files. if (fileSize-offset) > 0: data = self.read(treeId, fileId, offset, fileSize-offset) callback(data) else: written = 0 toBeRead = fileSize-offset while written < toBeRead: data = self.read(treeId, fileId, offset, self._Connection['MaxReadSize']) written += len(data) offset += len(data) callback(data) finally: if fileId is not None: self.close(treeId, fileId) self.disconnectTree(treeId) def storeFile(self, shareName, path, callback, mode = FILE_OVERWRITE_IF, offset = 0, password = None, shareAccessMode = FILE_SHARE_WRITE): # ToDo: Handle situations where share is password protected path = string.replace(path,'/', '\\') path = ntpath.normpath(path) if len(path) > 0 and path[0] == '\\': path = path[1:] treeId = self.connectTree(shareName) fileId = None try: fileId = self.create(treeId, path, FILE_WRITE_DATA, shareAccessMode, FILE_NON_DIRECTORY_FILE, mode, 0) finished = False writeOffset = offset while not finished: data = callback(self._Connection['MaxWriteSize']) if len(data) == 0: break written = self.write(treeId, fileId, data, writeOffset, len(data)) writeOffset += written finally: if fileId is not None: self.close(treeId, fileId) self.disconnectTree(treeId) def waitNamedPipe(self, treeId, pipename, timeout = 5): pipename = ntpath.basename(pipename) if (treeId in self._Session['TreeConnectTable']) is False: raise SessionError(STATUS_INVALID_PARAMETER) if len(pipename) > 0xffff: raise SessionError(STATUS_INVALID_PARAMETER) pipeWait = FSCTL_PIPE_WAIT_STRUCTURE() pipeWait['Timeout'] = timeout*100000 pipeWait['NameLength'] = len(pipename)*2 pipeWait['TimeoutSpecified'] = 1 pipeWait['Name'] = pipename.encode('utf-16le') return self.ioctl(treeId, None, FSCTL_PIPE_WAIT,flags=SMB2_0_IOCTL_IS_FSCTL, inputBlob=pipeWait, maxInputResponse = 0, maxOutputResponse=0) def getIOCapabilities(self): res = dict() res['MaxReadSize'] = self._Connection['MaxReadSize'] res['MaxWriteSize'] = self._Connection['MaxWriteSize'] return res ###################################################################### # Backward compatibility functions and alias for SMB1 and DCE Transports # NOTE: It is strongly recommended not to use these commands # when implementing new client calls. get_server_name = getServerName get_server_domain = getServerDomain get_server_dns_domain_name = getServerDNSDomainName get_remote_name = getServerName get_remote_host = getServerIP get_server_os = getServerOS get_server_os_major = getServerOSMajor get_server_os_minor = getServerOSMinor get_server_os_build = getServerOSBuild tree_connect_andx = connectTree tree_connect = connectTree connect_tree = connectTree disconnect_tree = disconnectTree set_timeout = setTimeout use_timeout = useTimeout stor_file = storeFile retr_file = retrieveFile list_path = listPath def __del__(self): if self._NetBIOSSession: self._NetBIOSSession.close() def doesSupportNTLMv2(self): # Always true :P return True def is_login_required(self): # Always true :P return True def is_signing_required(self): return self._Session["SigningRequired"] def nt_create_andx(self, treeId, fileName, smb_packet=None, cmd = None): if len(fileName) > 0 and fileName[0] == '\\': fileName = fileName[1:] if cmd is not None: from impacket import smb ntCreate = smb.SMBCommand(data = str(cmd)) params = smb.SMBNtCreateAndX_Parameters(ntCreate['Parameters']) return self.create(treeId, fileName, params['AccessMask'], params['ShareAccess'], params['CreateOptions'], params['Disposition'], params['FileAttributes'], params['Impersonation'], params['SecurityFlags']) else: return self.create(treeId, fileName, FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_READ_EA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | READ_CONTROL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_NON_DIRECTORY_FILE, FILE_OPEN, 0 ) def get_socket(self): return self._NetBIOSSession.get_socket() def write_andx(self,tid,fid,data, offset = 0, wait_answer=1, write_pipe_mode = False, smb_packet=None): # ToDo: Handle the custom smb_packet situation return self.write(tid, fid, data, offset, len(data)) def TransactNamedPipe(self, tid, fid, data, noAnswer = 0, waitAnswer = 1, offset = 0): return self.ioctl(tid, fid, FSCTL_PIPE_TRANSCEIVE, SMB2_0_IOCTL_IS_FSCTL, data, maxOutputResponse = 65535, waitAnswer = noAnswer | waitAnswer) def TransactNamedPipeRecv(self): ans = self.recvSMB() if ans.isValidAnswer(STATUS_SUCCESS): smbIoctlResponse = SMB2Ioctl_Response(ans['Data']) return smbIoctlResponse['Buffer'] def read_andx(self, tid, fid, offset=0, max_size = None, wait_answer=1, smb_packet=None): # ToDo: Handle the custom smb_packet situation if max_size is None: max_size = self._Connection['MaxReadSize'] return self.read(tid, fid, offset, max_size, wait_answer) def list_shared(self): # In the context of SMB2/3, forget about the old LANMAN, throw NOT IMPLEMENTED raise SessionError(STATUS_NOT_IMPLEMENTED) def open_andx(self, tid, fileName, open_mode, desired_access): # ToDo Return all the attributes of the file if len(fileName) > 0 and fileName[0] == '\\': fileName = fileName[1:] fileId = self.create(tid,fileName,desired_access, open_mode, FILE_NON_DIRECTORY_FILE, open_mode, 0) return fileId, 0, 0, 0, 0, 0, 0, 0, 0