# 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) # # TODO: # [-] Functions should return NT error codes # [-] Handling errors in all situations, right now it's just raising exceptions. # [*] Standard authentication support # [ ] Organize the connectionData stuff # [*] Add capability to send a bad user ID if the user is not authenticated, # right now you can ask for any command without actually being authenticated # [ ] PATH TRAVERSALS EVERYWHERE.. BE WARNED! # [ ] Check the credentials.. now we're just letting everybody to log in. # [ ] Check error situation (now many places assume the right data is coming) # [ ] Implement IPC to the main process so the connectionData is on a single place # [ ] Hence.. implement locking # estamos en la B from __future__ import with_statement import calendar import socket import time import datetime import struct import sys if sys.version_info.major >= 3: import configparser import socketserver else: import ConfigParser as configparser import SocketServer as socketserver import threading import logging import logging.config import ntpath import os import fnmatch import errno import random import shutil from binascii import hexlify # For signing from impacket import smb, nmb, ntlm, uuid, LOG from impacket import smb3structs as smb2 from impacket.spnego import SPNEGO_NegTokenInit, TypesMech, MechTypes, SPNEGO_NegTokenResp, ASN1_AID, ASN1_SUPPORTED_MECH from impacket.nt_errors import STATUS_NO_MORE_FILES, STATUS_NETWORK_NAME_DELETED, STATUS_INVALID_PARAMETER, \ STATUS_FILE_CLOSED, STATUS_MORE_PROCESSING_REQUIRED, STATUS_OBJECT_PATH_NOT_FOUND, STATUS_DIRECTORY_NOT_EMPTY, \ STATUS_FILE_IS_A_DIRECTORY, STATUS_NOT_IMPLEMENTED, STATUS_INVALID_HANDLE, STATUS_OBJECT_NAME_COLLISION, \ STATUS_NO_SUCH_FILE, STATUS_CANCELLED, STATUS_OBJECT_NAME_NOT_FOUND, STATUS_SUCCESS, STATUS_ACCESS_DENIED, \ STATUS_NOT_SUPPORTED, STATUS_INVALID_DEVICE_REQUEST, STATUS_FS_DRIVER_REQUIRED, STATUS_INVALID_INFO_CLASS # These ones not defined in nt_errors STATUS_SMB_BAD_UID = 0x005B0002 STATUS_SMB_BAD_TID = 0x00050002 try: unicode # Python 2 except NameError: unicode = str # Python 3 # Utility functions # and general functions. # There are some common functions that can be accessed from more than one SMB # command (or either TRANSACTION). That's why I'm putting them here # TODO: Return NT ERROR Codes def outputToJohnFormat(challenge, username, domain, lmresponse, ntresponse): # We don't want to add a possible failure here, since this is an # extra bonus. We try, if it fails, returns nothing ret_value = '' try: if len(ntresponse) > 24: # Extended Security - NTLMv2 ret_value = {'hash_string':'%s::%s:%s:%s:%s' % (username.decode('utf-16le'), domain.decode('utf-16le'), hexlify(challenge), hexlify(ntresponse)[:32], hexlify(ntresponse)[32:]), 'hash_version':'ntlmv2'} else: # NTLMv1 ret_value = {'hash_string':'%s::%s:%s:%s:%s' % (username.decode('utf-16le'), domain.decode('utf-16le'), hexlify(lmresponse), hexlify(ntresponse), hexlify(challenge)), 'hash_version':'ntlm'} except: # Let's try w/o decoding Unicode try: if len(ntresponse) > 24: # Extended Security - NTLMv2 ret_value = {'hash_string':'%s::%s:%s:%s:%s' % (username, domain, hexlify(challenge), hexlify(ntresponse)[:32], hexlify(ntresponse)[32:]), 'hash_version':'ntlmv2'} else: # NTLMv1 ret_value = {'hash_string':'%s::%s:%s:%s:%s' % (username, domain, hexlify(lmresponse), hexlify(ntresponse), hexlify(challenge)), 'hash_version':'ntlm'} except Exception as e: LOG.error("outputToJohnFormat: %s" % e) pass return ret_value def writeJohnOutputToFile(hash_string, hash_version, file_name): fn_data = os.path.splitext(file_name) if hash_version == "ntlmv2": output_filename = fn_data[0] + "_ntlmv2" + fn_data[1] else: output_filename = fn_data[0] + "_ntlm" + fn_data[1] with open(output_filename,"a") as f: f.write(hash_string) f.write('\n') def decodeSMBString( flags, text ): if flags & smb.SMB.FLAGS2_UNICODE: return text.decode('utf-16le') else: return text def encodeSMBString( flags, text ): if flags & smb.SMB.FLAGS2_UNICODE: return (text).encode('utf-16le') else: return text def getFileTime(t): t *= 10000000 t += 116444736000000000 return t def getUnixTime(t): t -= 116444736000000000 t /= 10000000 return t def getSMBDate(t): # TODO: Fix this :P d = datetime.date.fromtimestamp(t) year = d.year - 1980 ret = (year << 8) + (d.month << 4) + d.day return ret def getSMBTime(t): # TODO: Fix this :P d = datetime.datetime.fromtimestamp(t) return (d.hour << 8) + (d.minute << 4) + d.second def getShares(connId, smbServer): config = smbServer.getServerConfig() sections = config.sections() # Remove the global one del(sections[sections.index('global')]) shares = {} for i in sections: shares[i] = dict(config.items(i)) return shares def searchShare(connId, share, smbServer): config = smbServer.getServerConfig() if config.has_section(share): return dict(config.items(share)) else: return None def openFile(path,fileName, accessMode, fileAttributes, openMode): fileName = os.path.normpath(fileName.replace('\\','/')) errorCode = 0 if len(fileName) > 0 and (fileName[0] == '/' or fileName[0] == '\\'): # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) mode = 0 # Check the Open Mode if openMode & 0x10: # If the file does not exist, create it. mode = os.O_CREAT else: # If file does not exist, return an error if os.path.exists(pathName) is not True: errorCode = STATUS_NO_SUCH_FILE return 0,mode, pathName, errorCode if os.path.isdir(pathName) and (fileAttributes & smb.ATTR_DIRECTORY) == 0: # Request to open a normal file and this is actually a directory errorCode = STATUS_FILE_IS_A_DIRECTORY return 0, mode, pathName, errorCode # Check the Access Mode if accessMode & 0x7 == 1: mode |= os.O_WRONLY elif accessMode & 0x7 == 2: mode |= os.O_RDWR else: mode = os.O_RDONLY try: if sys.platform == 'win32': mode |= os.O_BINARY fid = os.open(pathName, mode) except Exception as e: LOG.error("openFile: %s,%s" % (pathName, mode) ,e) fid = 0 errorCode = STATUS_ACCESS_DENIED return fid, mode, pathName, errorCode def queryFsInformation(path, filename, level=0): if isinstance(filename,unicode): encoding = 'utf-16le' flags = smb.SMB.FLAGS2_UNICODE else: encoding = 'ascii' flags = 0 fileName = os.path.normpath(filename.replace('\\','/')) if len(fileName) > 0 and (fileName[0] == '/' or fileName[0] == '\\'): # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) fileSize = os.path.getsize(pathName) (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(pathName) if level == smb.SMB_QUERY_FS_ATTRIBUTE_INFO or level == smb2.SMB2_FILESYSTEM_ATTRIBUTE_INFO: data = smb.SMBQueryFsAttributeInfo() data['FileSystemAttributes'] = smb.FILE_CASE_SENSITIVE_SEARCH | smb.FILE_CASE_PRESERVED_NAMES data['MaxFilenNameLengthInBytes'] = 255 data['LengthOfFileSystemName'] = len('XTFS')*2 data['FileSystemName'] = 'XTFS'.encode('utf-16le') return data.getData() elif level == smb.SMB_INFO_VOLUME: data = smb.SMBQueryFsInfoVolume( flags = flags ) data['VolumeLabel'] = 'SHARE'.encode(encoding) return data.getData() elif level == smb.SMB_QUERY_FS_VOLUME_INFO or level == smb2.SMB2_FILESYSTEM_VOLUME_INFO: data = smb.SMBQueryFsVolumeInfo() data['VolumeLabel'] = '' data['VolumeCreationTime'] = getFileTime(ctime) return data.getData() elif level == smb.SMB_QUERY_FS_SIZE_INFO: data = smb.SMBQueryFsSizeInfo() return data.getData() elif level == smb.FILE_FS_FULL_SIZE_INFORMATION: data = smb.SMBFileFsFullSizeInformation() return data.getData() elif level == smb.FILE_FS_SIZE_INFORMATION: data = smb.FileFsSizeInformation() return data.getData() else: lastWriteTime = mtime attribs = 0 if os.path.isdir(pathName): attribs |= smb.SMB_FILE_ATTRIBUTE_DIRECTORY if os.path.isfile(pathName): attribs |= smb.SMB_FILE_ATTRIBUTE_NORMAL fileAttributes = attribs return fileSize, lastWriteTime, fileAttributes def findFirst2(path, fileName, level, searchAttributes, isSMB2 = False): # TODO: Depending on the level, this could be done much simpler #print "FindFirs2 path:%s, filename:%s" % (path, fileName) fileName = os.path.normpath(fileName.replace('\\','/')) # Let's choose the right encoding depending on the request if isinstance(fileName,unicode): encoding = 'utf-16le' flags = smb.SMB.FLAGS2_UNICODE else: encoding = 'ascii' flags = 0 if len(fileName) > 0 and (fileName[0] == '/' or fileName[0] == '\\'): # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) files = [] if pathName.find('*') == -1 and pathName.find('?') == -1: # No search patterns pattern = '' else: pattern = os.path.basename(pathName) dirName = os.path.dirname(pathName) # Always add . and .. Not that important for Windows, but Samba whines if # not present (for * search only) if pattern == '*': files.append(os.path.join(dirName,'.')) files.append(os.path.join(dirName,'..')) if pattern != '': for file in os.listdir(dirName): if fnmatch.fnmatch(file.lower(),pattern.lower()): entry = os.path.join(dirName, file) if os.path.isdir(entry): if searchAttributes & smb.ATTR_DIRECTORY: files.append(entry) else: files.append(entry) else: if os.path.exists(pathName): files.append(pathName) searchResult = [] searchCount = len(files) errorCode = STATUS_SUCCESS for i in files: if level == smb.SMB_FIND_FILE_BOTH_DIRECTORY_INFO or level == smb2.SMB2_FILE_BOTH_DIRECTORY_INFO: item = smb.SMBFindFileBothDirectoryInfo( flags = flags ) elif level == smb.SMB_FIND_FILE_DIRECTORY_INFO or level == smb2.SMB2_FILE_DIRECTORY_INFO: item = smb.SMBFindFileDirectoryInfo( flags = flags ) elif level == smb.SMB_FIND_FILE_FULL_DIRECTORY_INFO or level == smb2.SMB2_FULL_DIRECTORY_INFO: item = smb.SMBFindFileFullDirectoryInfo( flags = flags ) elif level == smb.SMB_FIND_INFO_STANDARD: item = smb.SMBFindInfoStandard( flags = flags ) elif level == smb.SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO or level == smb2.SMB2_FILE_ID_FULL_DIRECTORY_INFO: item = smb.SMBFindFileIdFullDirectoryInfo( flags = flags ) elif level == smb.SMB_FIND_FILE_ID_BOTH_DIRECTORY_INFO or level == smb2.SMB2_FILE_ID_BOTH_DIRECTORY_INFO: item = smb.SMBFindFileIdBothDirectoryInfo( flags = flags ) elif level == smb.SMB_FIND_FILE_NAMES_INFO or level == smb2.SMB2_FILE_NAMES_INFO: item = smb.SMBFindFileNamesInfo( flags = flags ) else: LOG.error("Wrong level %d!" % level) return searchResult, searchCount, STATUS_NOT_SUPPORTED (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(i) if os.path.isdir(i): item['ExtFileAttributes'] = smb.ATTR_DIRECTORY else: item['ExtFileAttributes'] = smb.ATTR_NORMAL | smb.ATTR_ARCHIVE item['FileName'] = os.path.basename(i).encode(encoding) if level == smb.SMB_FIND_FILE_BOTH_DIRECTORY_INFO or level == smb.SMB_FIND_FILE_ID_BOTH_DIRECTORY_INFO or level == smb2.SMB2_FILE_ID_BOTH_DIRECTORY_INFO or level == smb2.SMB2_FILE_BOTH_DIRECTORY_INFO: item['EaSize'] = 0 item['EndOfFile'] = size item['AllocationSize'] = size item['CreationTime'] = getFileTime(ctime) item['LastAccessTime'] = getFileTime(atime) item['LastWriteTime'] = getFileTime(mtime) item['LastChangeTime'] = getFileTime(mtime) item['ShortName'] = '\x00'*24 item['FileName'] = os.path.basename(i).encode(encoding) padLen = (8-(len(item) % 8)) % 8 item['NextEntryOffset'] = len(item) + padLen elif level == smb.SMB_FIND_FILE_DIRECTORY_INFO: item['EndOfFile'] = size item['AllocationSize'] = size item['CreationTime'] = getFileTime(ctime) item['LastAccessTime'] = getFileTime(atime) item['LastWriteTime'] = getFileTime(mtime) item['LastChangeTime'] = getFileTime(mtime) item['FileName'] = os.path.basename(i).encode(encoding) padLen = (8-(len(item) % 8)) % 8 item['NextEntryOffset'] = len(item) + padLen elif level == smb.SMB_FIND_FILE_FULL_DIRECTORY_INFO or level == smb.SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO or level == smb2.SMB2_FULL_DIRECTORY_INFO: item['EaSize'] = 0 item['EndOfFile'] = size item['AllocationSize'] = size item['CreationTime'] = getFileTime(ctime) item['LastAccessTime'] = getFileTime(atime) item['LastWriteTime'] = getFileTime(mtime) item['LastChangeTime'] = getFileTime(mtime) padLen = (8-(len(item) % 8)) % 8 item['NextEntryOffset'] = len(item) + padLen elif level == smb.SMB_FIND_INFO_STANDARD: item['EaSize'] = size item['CreationDate'] = getSMBDate(ctime) item['CreationTime'] = getSMBTime(ctime) item['LastAccessDate'] = getSMBDate(atime) item['LastAccessTime'] = getSMBTime(atime) item['LastWriteDate'] = getSMBDate(mtime) item['LastWriteTime'] = getSMBTime(mtime) searchResult.append(item) # No more files if (level >= smb.SMB_FIND_FILE_DIRECTORY_INFO or isSMB2 == True) and searchCount > 0: searchResult[-1]['NextEntryOffset'] = 0 return searchResult, searchCount, errorCode def queryFileInformation(path, filename, level): #print "queryFileInfo path: %s, filename: %s, level:0x%x" % (path,filename,level) return queryPathInformation(path,filename, level) def queryPathInformation(path, filename, level): # TODO: Depending on the level, this could be done much simpler #print "queryPathInfo path: %s, filename: %s, level:0x%x" % (path,filename,level) try: errorCode = 0 fileName = os.path.normpath(filename.replace('\\','/')) if len(fileName) > 0 and (fileName[0] == '/' or fileName[0] == '\\') and path != '': # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) if os.path.exists(pathName): (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(pathName) if level == smb.SMB_QUERY_FILE_BASIC_INFO: infoRecord = smb.SMBQueryFileBasicInfo() infoRecord['CreationTime'] = getFileTime(ctime) infoRecord['LastAccessTime'] = getFileTime(atime) infoRecord['LastWriteTime'] = getFileTime(mtime) infoRecord['LastChangeTime'] = getFileTime(mtime) if os.path.isdir(pathName): infoRecord['ExtFileAttributes'] = smb.ATTR_DIRECTORY else: infoRecord['ExtFileAttributes'] = smb.ATTR_NORMAL | smb.ATTR_ARCHIVE elif level == smb.SMB_QUERY_FILE_STANDARD_INFO: infoRecord = smb.SMBQueryFileStandardInfo() infoRecord['AllocationSize'] = size infoRecord['EndOfFile'] = size if os.path.isdir(pathName): infoRecord['Directory'] = 1 else: infoRecord['Directory'] = 0 elif level == smb.SMB_QUERY_FILE_ALL_INFO or level == smb2.SMB2_FILE_ALL_INFO: infoRecord = smb.SMBQueryFileAllInfo() infoRecord['CreationTime'] = getFileTime(ctime) infoRecord['LastAccessTime'] = getFileTime(atime) infoRecord['LastWriteTime'] = getFileTime(mtime) infoRecord['LastChangeTime'] = getFileTime(mtime) if os.path.isdir(pathName): infoRecord['ExtFileAttributes'] = smb.ATTR_DIRECTORY else: infoRecord['ExtFileAttributes'] = smb.ATTR_NORMAL | smb.ATTR_ARCHIVE infoRecord['AllocationSize'] = size infoRecord['EndOfFile'] = size if os.path.isdir(pathName): infoRecord['Directory'] = 1 else: infoRecord['Directory'] = 0 infoRecord['FileName'] = filename.encode('utf-16le') elif level == smb2.SMB2_FILE_NETWORK_OPEN_INFO: infoRecord = smb.SMBFileNetworkOpenInfo() infoRecord['CreationTime'] = getFileTime(ctime) infoRecord['LastAccessTime'] = getFileTime(atime) infoRecord['LastWriteTime'] = getFileTime(mtime) infoRecord['ChangeTime'] = getFileTime(mtime) infoRecord['AllocationSize'] = size infoRecord['EndOfFile'] = size if os.path.isdir(pathName): infoRecord['FileAttributes'] = smb.ATTR_DIRECTORY else: infoRecord['FileAttributes'] = smb.ATTR_NORMAL | smb.ATTR_ARCHIVE elif level == smb.SMB_QUERY_FILE_EA_INFO or level == smb2.SMB2_FILE_EA_INFO: infoRecord = smb.SMBQueryFileEaInfo() elif level == smb2.SMB2_FILE_STREAM_INFO: infoRecord = smb.SMBFileStreamInformation() else: LOG.error('Unknown level for query path info! 0x%x' % level) # UNSUPPORTED return None, STATUS_NOT_SUPPORTED return infoRecord, errorCode else: # NOT FOUND return None, STATUS_OBJECT_NAME_NOT_FOUND except Exception as e: LOG.error('queryPathInfo: %s' % e) raise def queryDiskInformation(path): # TODO: Do something useful here :) # For now we just return fake values totalUnits = 65535 freeUnits = 65535 return totalUnits, freeUnits # Here we implement the NT transaction handlers class NTTRANSCommands: def default(self, connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): pass # Here we implement the NT transaction handlers class TRANSCommands: @staticmethod def lanMan(connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): # Minimal [MS-RAP] implementation, just to return the shares connData = smbServer.getConnectionData(connId) respSetup = '' respParameters = '' respData = '' errorCode = STATUS_SUCCESS if struct.unpack(' 0 and (fileName[0] == '/' or fileName[0] == '\\') and path != '': # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) if os.path.exists(pathName): informationLevel = setPathInfoParameters['InformationLevel'] if informationLevel == smb.SMB_SET_FILE_BASIC_INFO: infoRecord = smb.SMBSetFileBasicInfo(data) # Creation time won't be set, the other ones we play with. atime = infoRecord['LastAccessTime'] if atime == 0: atime = -1 else: atime = getUnixTime(atime) mtime = infoRecord['LastWriteTime'] if mtime == 0: mtime = -1 else: mtime = getUnixTime(mtime) if mtime != -1 or atime != -1: os.utime(pathName,(atime,mtime)) else: smbServer.log('Unknown level for set path info! 0x%x' % setPathInfoParameters['InformationLevel'], logging.ERROR) # UNSUPPORTED errorCode = STATUS_NOT_SUPPORTED else: errorCode = STATUS_OBJECT_NAME_NOT_FOUND if errorCode == STATUS_SUCCESS: respParameters = smb.SMBSetPathInformationResponse_Parameters() else: errorCode = STATUS_SMB_BAD_TID smbServer.setConnectionData(connId, connData) return respSetup, respParameters, respData, errorCode @staticmethod def setFileInformation(connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): connData = smbServer.getConnectionData(connId) respSetup = '' respParameters = '' respData = '' errorCode = STATUS_SUCCESS setFileInfoParameters = smb.SMBSetFileInformation_Parameters(parameters) if recvPacket['Tid'] in connData['ConnectedShares']: if setFileInfoParameters['FID'] in connData['OpenedFiles']: fileName = connData['OpenedFiles'][setFileInfoParameters['FID']]['FileName'] informationLevel = setFileInfoParameters['InformationLevel'] if informationLevel == smb.SMB_SET_FILE_DISPOSITION_INFO: infoRecord = smb.SMBSetFileDispositionInfo(parameters) if infoRecord['DeletePending'] > 0: # Mark this file for removal after closed connData['OpenedFiles'][setFileInfoParameters['FID']]['DeleteOnClose'] = True respParameters = smb.SMBSetFileInformationResponse_Parameters() elif informationLevel == smb.SMB_SET_FILE_BASIC_INFO: infoRecord = smb.SMBSetFileBasicInfo(data) # Creation time won't be set, the other ones we play with. atime = infoRecord['LastAccessTime'] if atime == 0: atime = -1 else: atime = getUnixTime(atime) mtime = infoRecord['LastWriteTime'] if mtime == 0: mtime = -1 else: mtime = getUnixTime(mtime) os.utime(fileName,(atime,mtime)) elif informationLevel == smb.SMB_SET_FILE_END_OF_FILE_INFO: fileHandle = connData['OpenedFiles'][setFileInfoParameters['FID']]['FileHandle'] infoRecord = smb.SMBSetFileEndOfFileInfo(data) if infoRecord['EndOfFile'] > 0: os.lseek(fileHandle, infoRecord['EndOfFile']-1, 0) os.write(fileHandle, '\x00') else: smbServer.log('Unknown level for set file info! 0x%x' % setFileInfoParameters['InformationLevel'], logging.ERROR) # UNSUPPORTED errorCode = STATUS_NOT_SUPPORTED else: errorCode = STATUS_NO_SUCH_FILE if errorCode == STATUS_SUCCESS: respParameters = smb.SMBSetFileInformationResponse_Parameters() else: errorCode = STATUS_SMB_BAD_TID smbServer.setConnectionData(connId, connData) return respSetup, respParameters, respData, errorCode @staticmethod def queryFileInformation(connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): connData = smbServer.getConnectionData(connId) respSetup = '' respParameters = '' respData = '' queryFileInfoParameters = smb.SMBQueryFileInformation_Parameters(parameters) if recvPacket['Tid'] in connData['ConnectedShares']: if queryFileInfoParameters['FID'] in connData['OpenedFiles']: fileName = connData['OpenedFiles'][queryFileInfoParameters['FID']]['FileName'] infoRecord, errorCode = queryFileInformation('', fileName, queryFileInfoParameters['InformationLevel']) if infoRecord is not None: respParameters = smb.SMBQueryFileInformationResponse_Parameters() respData = infoRecord else: errorCode = STATUS_INVALID_HANDLE else: errorCode = STATUS_SMB_BAD_TID smbServer.setConnectionData(connId, connData) return respSetup, respParameters, respData, errorCode @staticmethod def queryPathInformation(connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): connData = smbServer.getConnectionData(connId) respSetup = '' respParameters = '' respData = '' errorCode = 0 queryPathInfoParameters = smb.SMBQueryPathInformation_Parameters(flags = recvPacket['Flags2'], data = parameters) if recvPacket['Tid'] in connData['ConnectedShares']: path = connData['ConnectedShares'][recvPacket['Tid']]['path'] try: infoRecord, errorCode = queryPathInformation(path, decodeSMBString(recvPacket['Flags2'], queryPathInfoParameters['FileName']), queryPathInfoParameters['InformationLevel']) except Exception as e: smbServer.log("queryPathInformation: %s" % e,logging.ERROR) if infoRecord is not None: respParameters = smb.SMBQueryPathInformationResponse_Parameters() respData = infoRecord else: errorCode = STATUS_SMB_BAD_TID smbServer.setConnectionData(connId, connData) return respSetup, respParameters, respData, errorCode @staticmethod def queryFsInformation(connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): connData = smbServer.getConnectionData(connId) errorCode = 0 # Get the Tid associated if recvPacket['Tid'] in connData['ConnectedShares']: data = queryFsInformation(connData['ConnectedShares'][recvPacket['Tid']]['path'], '', struct.unpack('= maxDataCount or (i[0]+1) >= findNext2Parameters['SearchCount']: # We gotta stop here and continue on a find_next2 endOfSearch = 0 connData['SIDs'][sid] = searchResult[i[0]:] respParameters['LastNameOffset'] = totalData break else: searchCount +=1 respData += data totalData += lenData # Have we reached the end of the search or still stuff to send? if endOfSearch > 0: # Let's remove the SID from our ConnData del(connData['SIDs'][sid]) respParameters['EndOfSearch'] = endOfSearch respParameters['SearchCount'] = searchCount else: errorCode = STATUS_INVALID_HANDLE else: errorCode = STATUS_SMB_BAD_TID smbServer.setConnectionData(connId, connData) return respSetup, respParameters, respData, errorCode @staticmethod def findFirst2(connId, smbServer, recvPacket, parameters, data, maxDataCount): connData = smbServer.getConnectionData(connId) respSetup = '' respParameters = '' respData = '' findFirst2Parameters = smb.SMBFindFirst2_Parameters( recvPacket['Flags2'], data = parameters) if recvPacket['Tid'] in connData['ConnectedShares']: path = connData['ConnectedShares'][recvPacket['Tid']]['path'] searchResult, searchCount, errorCode = findFirst2(path, decodeSMBString( recvPacket['Flags2'], findFirst2Parameters['FileName'] ), findFirst2Parameters['InformationLevel'], findFirst2Parameters['SearchAttributes'] ) respParameters = smb.SMBFindFirst2Response_Parameters() endOfSearch = 1 sid = 0x80 # default SID searchCount = 0 totalData = 0 for i in enumerate(searchResult): #i[1].dump() data = i[1].getData() lenData = len(data) if (totalData+lenData) >= maxDataCount or (i[0]+1) > findFirst2Parameters['SearchCount']: # We gotta stop here and continue on a find_next2 endOfSearch = 0 # Simple way to generate a fid if len(connData['SIDs']) == 0: sid = 1 else: sid = connData['SIDs'].keys()[-1] + 1 # Store the remaining search results in the ConnData SID connData['SIDs'][sid] = searchResult[i[0]:] respParameters['LastNameOffset'] = totalData break else: searchCount +=1 respData += data padLen = (8-(lenData % 8)) %8 respData += '\xaa'*padLen totalData += lenData + padLen respParameters['SID'] = sid respParameters['EndOfSearch'] = endOfSearch respParameters['SearchCount'] = searchCount else: errorCode = STATUS_SMB_BAD_TID smbServer.setConnectionData(connId, connData) return respSetup, respParameters, respData, errorCode # Here we implement the commands handlers class SMBCommands: @staticmethod def smbTransaction(connId, smbServer, SMBCommand, recvPacket, transCommands): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(recvPacket['Command']) transParameters= smb.SMBTransaction_Parameters(SMBCommand['Parameters']) # Do the stuff if transParameters['ParameterCount'] != transParameters['TotalParameterCount']: # TODO: Handle partial parameters raise Exception("Unsupported partial parameters in TRANSACT2!") else: transData = smb.SMBTransaction_SData(flags = recvPacket['Flags2']) # Standard says servers shouldn't trust Parameters and Data comes # in order, so we have to parse the offsets, ugly paramCount = transParameters['ParameterCount'] transData['Trans_ParametersLength'] = paramCount dataCount = transParameters['DataCount'] transData['Trans_DataLength'] = dataCount transData.fromString(SMBCommand['Data']) if transParameters['ParameterOffset'] > 0: paramOffset = transParameters['ParameterOffset'] - 63 - transParameters['SetupLength'] transData['Trans_Parameters'] = SMBCommand['Data'][paramOffset:paramOffset+paramCount] else: transData['Trans_Parameters'] = '' if transParameters['DataOffset'] > 0: dataOffset = transParameters['DataOffset'] - 63 - transParameters['SetupLength'] transData['Trans_Data'] = SMBCommand['Data'][dataOffset:dataOffset + dataCount] else: transData['Trans_Data'] = '' # Call the handler for this TRANSACTION if transParameters['SetupCount'] == 0: # No subcommand, let's play with the Name command = decodeSMBString(recvPacket['Flags2'],transData['Name']) else: command = struct.unpack(' 0 or remainingParameters > 0: respSMBCommand = smb.SMBCommand(recvPacket['Command']) respParameters = smb.SMBTransactionResponse_Parameters() respData = smb.SMBTransaction2Response_Data() respParameters['TotalParameterCount'] = len(parameters) respParameters['ParameterCount'] = len(parameters) respData['Trans_ParametersLength'] = len(parameters) respParameters['TotalDataCount'] = len(data) respParameters['DataDisplacement'] = dataDisplacement # TODO: Do the same for parameters if len(data) > transParameters['MaxDataCount']: # Answer doesn't fit in this packet LOG.debug("Lowering answer from %d to %d" % (len(data),transParameters['MaxDataCount']) ) respParameters['DataCount'] = transParameters['MaxDataCount'] else: respParameters['DataCount'] = len(data) respData['Trans_DataLength'] = respParameters['DataCount'] respParameters['SetupCount'] = len(setup) respParameters['Setup'] = setup # TODO: Make sure we're calculating the pad right if len(parameters) > 0: #padLen = 4 - (55 + len(setup)) % 4 padLen = (4 - (55 + len(setup)) % 4 ) % 4 padBytes = '\xFF' * padLen respData['Pad1'] = padBytes respParameters['ParameterOffset'] = 55 + len(setup) + padLen else: padLen = 0 respParameters['ParameterOffset'] = 0 respData['Pad1'] = '' if len(data) > 0: #pad2Len = 4 - (55 + len(setup) + padLen + len(parameters)) % 4 pad2Len = (4 - (55 + len(setup) + padLen + len(parameters)) % 4) % 4 respData['Pad2'] = '\xFF' * pad2Len respParameters['DataOffset'] = 55 + len(setup) + padLen + len(parameters) + pad2Len else: respParameters['DataOffset'] = 0 respData['Pad2'] = '' respData['Trans_Parameters'] = parameters[:respParameters['ParameterCount']] respData['Trans_Data'] = data[:respParameters['DataCount']] respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData data = data[respParameters['DataCount']:] remainingData -= respParameters['DataCount'] dataDisplacement += respParameters['DataCount'] + 1 parameters = parameters[respParameters['ParameterCount']:] remainingParameters -= respParameters['ParameterCount'] commands.append(respSMBCommand) smbServer.setConnectionData(connId, connData) return commands, None, errorCode else: smbServer.log("Unsupported Transact command %r" % command, logging.ERROR) respParameters = '' respData = '' errorCode = STATUS_NOT_IMPLEMENTED respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbNTTransact(connId, smbServer, SMBCommand, recvPacket, transCommands): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(recvPacket['Command']) NTTransParameters= smb.SMBNTTransaction_Parameters(SMBCommand['Parameters']) # Do the stuff if NTTransParameters['ParameterCount'] != NTTransParameters['TotalParameterCount']: # TODO: Handle partial parameters raise Exception("Unsupported partial parameters in NTTrans!") else: NTTransData = smb.SMBNTTransaction_Data() # Standard says servers shouldn't trust Parameters and Data comes # in order, so we have to parse the offsets, ugly paramCount = NTTransParameters['ParameterCount'] NTTransData['NT_Trans_ParametersLength'] = paramCount dataCount = NTTransParameters['DataCount'] NTTransData['NT_Trans_DataLength'] = dataCount if NTTransParameters['ParameterOffset'] > 0: paramOffset = NTTransParameters['ParameterOffset'] - 73 - NTTransParameters['SetupLength'] NTTransData['NT_Trans_Parameters'] = SMBCommand['Data'][paramOffset:paramOffset+paramCount] else: NTTransData['NT_Trans_Parameters'] = '' if NTTransParameters['DataOffset'] > 0: dataOffset = NTTransParameters['DataOffset'] - 73 - NTTransParameters['SetupLength'] NTTransData['NT_Trans_Data'] = SMBCommand['Data'][dataOffset:dataOffset + dataCount] else: NTTransData['NT_Trans_Data'] = '' # Call the handler for this TRANSACTION command = NTTransParameters['Function'] if command in transCommands: # Call the NT TRANS subcommand setup = '' parameters = '' data = '' try: setup, parameters, data, errorCode = transCommands[command](connId, smbServer, recvPacket, NTTransData['NT_Trans_Parameters'], NTTransData['NT_Trans_Data'], NTTransParameters['MaxDataCount']) except Exception as e: smbServer.log('NTTransaction: (0x%x,%s)' % (command, e), logging.ERROR) errorCode = STATUS_ACCESS_DENIED #raise if setup == '' and parameters == '' and data == '': # Something wen't wrong respParameters = '' respData = '' if errorCode == STATUS_SUCCESS: errorCode = STATUS_ACCESS_DENIED else: # Build the answer data = str(data) remainingData = len(data) parameters = str(parameters) remainingParameters = len(parameters) commands = [] dataDisplacement = 0 while remainingData > 0 or remainingParameters > 0: respSMBCommand = smb.SMBCommand(recvPacket['Command']) respParameters = smb.SMBNTTransactionResponse_Parameters() respData = smb.SMBNTTransactionResponse_Data() respParameters['TotalParameterCount'] = len(parameters) respParameters['ParameterCount'] = len(parameters) respData['Trans_ParametersLength'] = len(parameters) respParameters['TotalDataCount'] = len(data) respParameters['DataDisplacement'] = dataDisplacement # TODO: Do the same for parameters if len(data) > NTTransParameters['MaxDataCount']: # Answer doesn't fit in this packet LOG.debug("Lowering answer from %d to %d" % (len(data),NTTransParameters['MaxDataCount']) ) respParameters['DataCount'] = NTTransParameters['MaxDataCount'] else: respParameters['DataCount'] = len(data) respData['NT_Trans_DataLength'] = respParameters['DataCount'] respParameters['SetupCount'] = len(setup) respParameters['Setup'] = setup # TODO: Make sure we're calculating the pad right if len(parameters) > 0: #padLen = 4 - (71 + len(setup)) % 4 padLen = (4 - (73 + len(setup)) % 4 ) % 4 padBytes = '\xFF' * padLen respData['Pad1'] = padBytes respParameters['ParameterOffset'] = 73 + len(setup) + padLen else: padLen = 0 respParameters['ParameterOffset'] = 0 respData['Pad1'] = '' if len(data) > 0: #pad2Len = 4 - (71 + len(setup) + padLen + len(parameters)) % 4 pad2Len = (4 - (73 + len(setup) + padLen + len(parameters)) % 4) % 4 respData['Pad2'] = '\xFF' * pad2Len respParameters['DataOffset'] = 73 + len(setup) + padLen + len(parameters) + pad2Len else: respParameters['DataOffset'] = 0 respData['Pad2'] = '' respData['NT_Trans_Parameters'] = parameters[:respParameters['ParameterCount']] respData['NT_Trans_Data'] = data[:respParameters['DataCount']] respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData data = data[respParameters['DataCount']:] remainingData -= respParameters['DataCount'] dataDisplacement += respParameters['DataCount'] + 1 parameters = parameters[respParameters['ParameterCount']:] remainingParameters -= respParameters['ParameterCount'] commands.append(respSMBCommand) smbServer.setConnectionData(connId, connData) return commands, None, errorCode else: #smbServer.log("Unsupported NTTransact command 0x%x" % command, logging.ERROR) respParameters = '' respData = '' errorCode = STATUS_NOT_IMPLEMENTED respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbTransaction2(connId, smbServer, SMBCommand, recvPacket, transCommands): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(recvPacket['Command']) trans2Parameters= smb.SMBTransaction2_Parameters(SMBCommand['Parameters']) # Do the stuff if trans2Parameters['ParameterCount'] != trans2Parameters['TotalParameterCount']: # TODO: Handle partial parameters #print "Unsupported partial parameters in TRANSACT2!" raise Exception("Unsupported partial parameters in TRANSACT2!") else: trans2Data = smb.SMBTransaction2_Data() # Standard says servers shouldn't trust Parameters and Data comes # in order, so we have to parse the offsets, ugly paramCount = trans2Parameters['ParameterCount'] trans2Data['Trans_ParametersLength'] = paramCount dataCount = trans2Parameters['DataCount'] trans2Data['Trans_DataLength'] = dataCount if trans2Parameters['ParameterOffset'] > 0: paramOffset = trans2Parameters['ParameterOffset'] - 63 - trans2Parameters['SetupLength'] trans2Data['Trans_Parameters'] = SMBCommand['Data'][paramOffset:paramOffset+paramCount] else: trans2Data['Trans_Parameters'] = '' if trans2Parameters['DataOffset'] > 0: dataOffset = trans2Parameters['DataOffset'] - 63 - trans2Parameters['SetupLength'] trans2Data['Trans_Data'] = SMBCommand['Data'][dataOffset:dataOffset + dataCount] else: trans2Data['Trans_Data'] = '' # Call the handler for this TRANSACTION command = struct.unpack(' 0 or remainingParameters > 0: respSMBCommand = smb.SMBCommand(recvPacket['Command']) respParameters = smb.SMBTransaction2Response_Parameters() respData = smb.SMBTransaction2Response_Data() respParameters['TotalParameterCount'] = len(parameters) respParameters['ParameterCount'] = len(parameters) respData['Trans_ParametersLength'] = len(parameters) respParameters['TotalDataCount'] = len(data) respParameters['DataDisplacement'] = dataDisplacement # TODO: Do the same for parameters if len(data) > trans2Parameters['MaxDataCount']: # Answer doesn't fit in this packet LOG.debug("Lowering answer from %d to %d" % (len(data),trans2Parameters['MaxDataCount']) ) respParameters['DataCount'] = trans2Parameters['MaxDataCount'] else: respParameters['DataCount'] = len(data) respData['Trans_DataLength'] = respParameters['DataCount'] respParameters['SetupCount'] = len(setup) respParameters['Setup'] = setup # TODO: Make sure we're calculating the pad right if len(parameters) > 0: #padLen = 4 - (55 + len(setup)) % 4 padLen = (4 - (55 + len(setup)) % 4 ) % 4 padBytes = '\xFF' * padLen respData['Pad1'] = padBytes respParameters['ParameterOffset'] = 55 + len(setup) + padLen else: padLen = 0 respParameters['ParameterOffset'] = 0 respData['Pad1'] = '' if len(data) > 0: #pad2Len = 4 - (55 + len(setup) + padLen + len(parameters)) % 4 pad2Len = (4 - (55 + len(setup) + padLen + len(parameters)) % 4) % 4 respData['Pad2'] = '\xFF' * pad2Len respParameters['DataOffset'] = 55 + len(setup) + padLen + len(parameters) + pad2Len else: respParameters['DataOffset'] = 0 respData['Pad2'] = '' respData['Trans_Parameters'] = parameters[:respParameters['ParameterCount']] respData['Trans_Data'] = data[:respParameters['DataCount']] respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData data = data[respParameters['DataCount']:] remainingData -= respParameters['DataCount'] dataDisplacement += respParameters['DataCount'] + 1 parameters = parameters[respParameters['ParameterCount']:] remainingParameters -= respParameters['ParameterCount'] commands.append(respSMBCommand) smbServer.setConnectionData(connId, connData) return commands, None, errorCode else: smbServer.log("Unsupported Transact/2 command 0x%x" % command, logging.ERROR) respParameters = '' respData = '' errorCode = STATUS_NOT_IMPLEMENTED respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComLockingAndX(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_LOCKING_ANDX) respParameters = '' respData = '' # I'm actually doing nothing.. just make MacOS happy ;) errorCode = STATUS_SUCCESS respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComClose(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_CLOSE) respParameters = '' respData = '' comClose = smb.SMBClose_Parameters(SMBCommand['Parameters']) if comClose['FID'] in connData['OpenedFiles']: errorCode = STATUS_SUCCESS fileHandle = connData['OpenedFiles'][comClose['FID']]['FileHandle'] try: if fileHandle == PIPE_FILE_DESCRIPTOR: connData['OpenedFiles'][comClose['FID']]['Socket'].close() elif fileHandle != VOID_FILE_DESCRIPTOR: os.close(fileHandle) except Exception as e: smbServer.log("comClose %s" % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: # Check if the file was marked for removal if connData['OpenedFiles'][comClose['FID']]['DeleteOnClose'] is True: try: os.remove(connData['OpenedFiles'][comClose['FID']]['FileName']) except Exception as e: smbServer.log("comClose %s" % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED del(connData['OpenedFiles'][comClose['FID']]) else: errorCode = STATUS_INVALID_HANDLE if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComWrite(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_WRITE) respParameters = smb.SMBWriteResponse_Parameters() respData = '' comWriteParameters = smb.SMBWrite_Parameters(SMBCommand['Parameters']) comWriteData = smb.SMBWrite_Data(SMBCommand['Data']) if comWriteParameters['Fid'] in connData['OpenedFiles']: fileHandle = connData['OpenedFiles'][comWriteParameters['Fid']]['FileHandle'] errorCode = STATUS_SUCCESS try: if fileHandle != PIPE_FILE_DESCRIPTOR: # TODO: Handle big size files # If we're trying to write past the file end we just skip the write call (Vista does this) if os.lseek(fileHandle, 0, 2) >= comWriteParameters['Offset']: os.lseek(fileHandle,comWriteParameters['Offset'],0) os.write(fileHandle,comWriteData['Data']) else: sock = connData['OpenedFiles'][comWriteParameters['Fid']]['Socket'] sock.send(comWriteData['Data']) respParameters['Count'] = comWriteParameters['Count'] except Exception as e: smbServer.log('smbComWrite: %s' % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_INVALID_HANDLE if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComFlush(connId, smbServer, SMBCommand,recvPacket ): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_FLUSH) respParameters = '' respData = '' comFlush = smb.SMBFlush_Parameters(SMBCommand['Parameters']) if comFlush['FID'] in connData['OpenedFiles']: errorCode = STATUS_SUCCESS fileHandle = connData['OpenedFiles'][comFlush['FID']]['FileHandle'] try: os.fsync(fileHandle) except Exception as e: smbServer.log("comFlush %s" % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_INVALID_HANDLE if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComCreateDirectory(connId, smbServer, SMBCommand,recvPacket ): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_CREATE_DIRECTORY) respParameters = '' respData = '' comCreateDirectoryData= smb.SMBCreateDirectory_Data(flags = recvPacket['Flags2'], data = SMBCommand['Data']) # Get the Tid associated if recvPacket['Tid'] in connData['ConnectedShares']: errorCode = STATUS_SUCCESS path = connData['ConnectedShares'][recvPacket['Tid']]['path'] fileName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],comCreateDirectoryData['DirectoryName']).replace('\\','/')) if len(fileName) > 0: if fileName[0] == '/' or fileName[0] == '\\': # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) if os.path.exists(pathName): errorCode = STATUS_OBJECT_NAME_COLLISION # TODO: More checks here in the future.. Specially when we support # user access else: try: os.mkdir(pathName) except Exception as e: smbServer.log("smbComCreateDirectory: %s" % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_SMB_BAD_TID if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComRename(connId, smbServer, SMBCommand, recvPacket ): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_RENAME) respParameters = '' respData = '' comRenameData = smb.SMBRename_Data(flags = recvPacket['Flags2'], data = SMBCommand['Data']) # Get the Tid associated if recvPacket['Tid'] in connData['ConnectedShares']: errorCode = STATUS_SUCCESS path = connData['ConnectedShares'][recvPacket['Tid']]['path'] oldFileName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],comRenameData['OldFileName']).replace('\\','/')) newFileName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],comRenameData['NewFileName']).replace('\\','/')) if len(oldFileName) > 0 and (oldFileName[0] == '/' or oldFileName[0] == '\\'): # strip leading '/' oldFileName = oldFileName[1:] oldPathName = os.path.join(path,oldFileName) if len(newFileName) > 0 and (newFileName[0] == '/' or newFileName[0] == '\\'): # strip leading '/' newFileName = newFileName[1:] newPathName = os.path.join(path,newFileName) if os.path.exists(oldPathName) is not True: errorCode = STATUS_NO_SUCH_FILE # TODO: More checks here in the future.. Specially when we support # user access else: try: os.rename(oldPathName,newPathName) except OSError as e: smbServer.log("smbComRename: %s" % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_SMB_BAD_TID if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComDelete(connId, smbServer, SMBCommand, recvPacket ): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_DELETE) respParameters = '' respData = '' comDeleteData = smb.SMBDelete_Data(flags = recvPacket['Flags2'], data = SMBCommand['Data']) # Get the Tid associated if recvPacket['Tid'] in connData['ConnectedShares']: errorCode = STATUS_SUCCESS path = connData['ConnectedShares'][recvPacket['Tid']]['path'] fileName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],comDeleteData['FileName']).replace('\\','/')) if len(fileName) > 0 and (fileName[0] == '/' or fileName[0] == '\\'): # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) if os.path.exists(pathName) is not True: errorCode = STATUS_NO_SUCH_FILE # TODO: More checks here in the future.. Specially when we support # user access else: try: os.remove(pathName) except OSError as e: smbServer.log("smbComDelete: %s" % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_SMB_BAD_TID if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComDeleteDirectory(connId, smbServer, SMBCommand, recvPacket ): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_DELETE_DIRECTORY) respParameters = '' respData = '' comDeleteDirectoryData= smb.SMBDeleteDirectory_Data(flags = recvPacket['Flags2'], data = SMBCommand['Data']) # Get the Tid associated if recvPacket['Tid'] in connData['ConnectedShares']: errorCode = STATUS_SUCCESS path = connData['ConnectedShares'][recvPacket['Tid']]['path'] fileName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],comDeleteDirectoryData['DirectoryName']).replace('\\','/')) if len(fileName) > 0 and (fileName[0] == '/' or fileName[0] == '\\'): # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) if os.path.exists(pathName) is not True: errorCode = STATUS_NO_SUCH_FILE # TODO: More checks here in the future.. Specially when we support # user access else: try: os.rmdir(pathName) except OSError as e: smbServer.log("smbComDeleteDirectory: %s" % e,logging.ERROR) if e.errno == errno.ENOTEMPTY: errorCode = STATUS_DIRECTORY_NOT_EMPTY else: errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_SMB_BAD_TID if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComWriteAndX(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_WRITE_ANDX) respParameters = smb.SMBWriteAndXResponse_Parameters() respData = '' if SMBCommand['WordCount'] == 0x0C: writeAndX = smb.SMBWriteAndX_Parameters_Short(SMBCommand['Parameters']) writeAndXData = smb.SMBWriteAndX_Data_Short() else: writeAndX = smb.SMBWriteAndX_Parameters(SMBCommand['Parameters']) writeAndXData = smb.SMBWriteAndX_Data() writeAndXData['DataLength'] = writeAndX['DataLength'] writeAndXData['DataOffset'] = writeAndX['DataOffset'] writeAndXData.fromString(SMBCommand['Data']) if writeAndX['Fid'] in connData['OpenedFiles']: fileHandle = connData['OpenedFiles'][writeAndX['Fid']]['FileHandle'] errorCode = STATUS_SUCCESS try: if fileHandle != PIPE_FILE_DESCRIPTOR: offset = writeAndX['Offset'] if 'HighOffset' in writeAndX.fields: offset += (writeAndX['HighOffset'] << 32) # If we're trying to write past the file end we just skip the write call (Vista does this) if os.lseek(fileHandle, 0, 2) >= offset: os.lseek(fileHandle,offset,0) os.write(fileHandle,writeAndXData['Data']) else: sock = connData['OpenedFiles'][writeAndX['Fid']]['Socket'] sock.send(writeAndXData['Data']) respParameters['Count'] = writeAndX['DataLength'] respParameters['Available']= 0xff except Exception as e: smbServer.log('smbComWriteAndx: %s' % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_INVALID_HANDLE if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComRead(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_READ) respParameters = smb.SMBReadResponse_Parameters() respData = smb.SMBReadResponse_Data() comReadParameters = smb.SMBRead_Parameters(SMBCommand['Parameters']) if comReadParameters['Fid'] in connData['OpenedFiles']: fileHandle = connData['OpenedFiles'][comReadParameters['Fid']]['FileHandle'] errorCode = STATUS_SUCCESS try: if fileHandle != PIPE_FILE_DESCRIPTOR: # TODO: Handle big size files os.lseek(fileHandle,comReadParameters['Offset'],0) content = os.read(fileHandle,comReadParameters['Count']) else: sock = connData['OpenedFiles'][comReadParameters['Fid']]['Socket'] content = sock.recv(comReadParameters['Count']) respParameters['Count'] = len(content) respData['DataLength'] = len(content) respData['Data'] = content except Exception as e: smbServer.log('smbComRead: %s ' % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_INVALID_HANDLE if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComReadAndX(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_READ_ANDX) respParameters = smb.SMBReadAndXResponse_Parameters() respData = '' if SMBCommand['WordCount'] == 0x0A: readAndX = smb.SMBReadAndX_Parameters2(SMBCommand['Parameters']) else: readAndX = smb.SMBReadAndX_Parameters(SMBCommand['Parameters']) if readAndX['Fid'] in connData['OpenedFiles']: fileHandle = connData['OpenedFiles'][readAndX['Fid']]['FileHandle'] errorCode = 0 try: if fileHandle != PIPE_FILE_DESCRIPTOR: offset = readAndX['Offset'] if 'HighOffset' in readAndX.fields: offset += (readAndX['HighOffset'] << 32) os.lseek(fileHandle,offset,0) content = os.read(fileHandle,readAndX['MaxCount']) else: sock = connData['OpenedFiles'][readAndX['Fid']]['Socket'] content = sock.recv(readAndX['MaxCount']) respParameters['Remaining'] = 0xffff respParameters['DataCount'] = len(content) respParameters['DataOffset'] = 59 respParameters['DataCount_Hi'] = 0 respData = content except Exception as e: smbServer.log('smbComReadAndX: %s ' % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_INVALID_HANDLE if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbQueryInformation(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_QUERY_INFORMATION) respParameters = smb.SMBQueryInformationResponse_Parameters() respData = '' queryInformation= smb.SMBQueryInformation_Data(flags = recvPacket['Flags2'], data = SMBCommand['Data']) # Get the Tid associated if recvPacket['Tid'] in connData['ConnectedShares']: fileSize, lastWriteTime, fileAttributes = queryFsInformation( connData['ConnectedShares'][recvPacket['Tid']]['path'], decodeSMBString(recvPacket['Flags2'],queryInformation['FileName'])) respParameters['FileSize'] = fileSize respParameters['LastWriteTime'] = lastWriteTime respParameters['FileAttributes'] = fileAttributes errorCode = STATUS_SUCCESS else: # STATUS_SMB_BAD_TID errorCode = STATUS_SMB_BAD_TID respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbQueryInformationDisk(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_QUERY_INFORMATION_DISK) respParameters = smb.SMBQueryInformationDiskResponse_Parameters() respData = '' # Get the Tid associated if recvPacket['Tid'] in connData['ConnectedShares']: totalUnits, freeUnits = queryDiskInformation( connData['ConnectedShares'][recvPacket['Tid']]['path']) respParameters['TotalUnits'] = totalUnits respParameters['BlocksPerUnit'] = 1 respParameters['BlockSize'] = 1 respParameters['FreeUnits'] = freeUnits errorCode = STATUS_SUCCESS else: # STATUS_SMB_BAD_TID respData = '' respParameters = '' errorCode = STATUS_SMB_BAD_TID respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComEcho(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_ECHO) respParameters = smb.SMBEchoResponse_Parameters() respData = smb.SMBEchoResponse_Data() echoData = smb.SMBEcho_Data(SMBCommand['Data']) respParameters['SequenceNumber'] = 1 respData['Data'] = echoData['Data'] respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData errorCode = STATUS_SUCCESS smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComTreeDisconnect(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_TREE_DISCONNECT) # Check if the Tid matches the Tid trying to disconnect respParameters = '' respData = '' if recvPacket['Tid'] in connData['ConnectedShares']: smbServer.log("Disconnecting Share(%d:%s)" % (recvPacket['Tid'],connData['ConnectedShares'][recvPacket['Tid']]['shareName'])) del(connData['ConnectedShares'][recvPacket['Tid']]) errorCode = STATUS_SUCCESS else: # STATUS_SMB_BAD_TID errorCode = STATUS_SMB_BAD_TID respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComLogOffAndX(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_LOGOFF_ANDX) # Check if the Uid matches the user trying to logoff respParameters = '' respData = '' if recvPacket['Uid'] != connData['Uid']: # STATUS_SMB_BAD_UID errorCode = STATUS_SMB_BAD_UID else: errorCode = STATUS_SUCCESS respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData connData['Uid'] = 0 smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComQueryInformation2(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_QUERY_INFORMATION2) respParameters = smb.SMBQueryInformation2Response_Parameters() respData = '' queryInformation2 = smb.SMBQueryInformation2_Parameters(SMBCommand['Parameters']) errorCode = 0xFF if queryInformation2['Fid'] in connData['OpenedFiles']: errorCode = STATUS_SUCCESS pathName = connData['OpenedFiles'][queryInformation2['Fid']]['FileName'] try: (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(pathName) respParameters['CreateDate'] = getSMBDate(ctime) respParameters['CreationTime'] = getSMBTime(ctime) respParameters['LastAccessDate'] = getSMBDate(atime) respParameters['LastAccessTime'] = getSMBTime(atime) respParameters['LastWriteDate'] = getSMBDate(mtime) respParameters['LastWriteTime'] = getSMBTime(mtime) respParameters['FileDataSize'] = size respParameters['FileAllocationSize'] = size attribs = 0 if os.path.isdir(pathName): attribs = smb.SMB_FILE_ATTRIBUTE_DIRECTORY if os.path.isfile(pathName): attribs = smb.SMB_FILE_ATTRIBUTE_NORMAL respParameters['FileAttributes'] = attribs except Exception as e: smbServer.log('smbComQueryInformation2 %s' % e,logging.ERROR) errorCode = STATUS_ACCESS_DENIED if errorCode > 0: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComNtCreateAndX(connId, smbServer, SMBCommand, recvPacket): # TODO: Fully implement this connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX) respParameters = smb.SMBNtCreateAndXResponse_Parameters() respData = '' ntCreateAndXParameters = smb.SMBNtCreateAndX_Parameters(SMBCommand['Parameters']) ntCreateAndXData = smb.SMBNtCreateAndX_Data( flags = recvPacket['Flags2'], data = SMBCommand['Data']) #if ntCreateAndXParameters['CreateFlags'] & 0x10: # NT_CREATE_REQUEST_EXTENDED_RESPONSE # respParameters = smb.SMBNtCreateAndXExtendedResponse_Parameters() # respParameters['VolumeGUID'] = '\x00' # Get the Tid associated if recvPacket['Tid'] in connData['ConnectedShares']: # If we have a rootFid, the path is relative to that fid errorCode = STATUS_SUCCESS if ntCreateAndXParameters['RootFid'] > 0: path = connData['OpenedFiles'][ntCreateAndXParameters['RootFid']]['FileName'] LOG.debug("RootFid present %s!" % path) else: if 'path' in connData['ConnectedShares'][recvPacket['Tid']]: path = connData['ConnectedShares'][recvPacket['Tid']]['path'] else: path = 'NONE' errorCode = STATUS_ACCESS_DENIED deleteOnClose = False fileName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],ntCreateAndXData['FileName']).replace('\\','/')) if len(fileName) > 0 and (fileName[0] == '/' or fileName[0] == '\\'): # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) createDisposition = ntCreateAndXParameters['Disposition'] mode = 0 if createDisposition == smb.FILE_SUPERSEDE: mode |= os.O_TRUNC | os.O_CREAT elif createDisposition & smb.FILE_OVERWRITE_IF == smb.FILE_OVERWRITE_IF: mode |= os.O_TRUNC | os.O_CREAT elif createDisposition & smb.FILE_OVERWRITE == smb.FILE_OVERWRITE: if os.path.exists(pathName) is True: mode |= os.O_TRUNC else: errorCode = STATUS_NO_SUCH_FILE elif createDisposition & smb.FILE_OPEN_IF == smb.FILE_OPEN_IF: if os.path.exists(pathName) is True: mode |= os.O_TRUNC else: mode |= os.O_TRUNC | os.O_CREAT elif createDisposition & smb.FILE_CREATE == smb.FILE_CREATE: if os.path.exists(pathName) is True: errorCode = STATUS_OBJECT_NAME_COLLISION else: mode |= os.O_CREAT elif createDisposition & smb.FILE_OPEN == smb.FILE_OPEN: if os.path.exists(pathName) is not True and (unicode(pathName) in smbServer.getRegisteredNamedPipes()) is not True: errorCode = STATUS_NO_SUCH_FILE if errorCode == STATUS_SUCCESS: desiredAccess = ntCreateAndXParameters['AccessMask'] if (desiredAccess & smb.FILE_READ_DATA) or (desiredAccess & smb.GENERIC_READ): mode |= os.O_RDONLY if (desiredAccess & smb.FILE_WRITE_DATA) or (desiredAccess & smb.GENERIC_WRITE): if (desiredAccess & smb.FILE_READ_DATA) or (desiredAccess & smb.GENERIC_READ): mode |= os.O_RDWR #| os.O_APPEND else: mode |= os.O_WRONLY #| os.O_APPEND if desiredAccess & smb.GENERIC_ALL: mode |= os.O_RDWR #| os.O_APPEND createOptions = ntCreateAndXParameters['CreateOptions'] if mode & os.O_CREAT == os.O_CREAT: if createOptions & smb.FILE_DIRECTORY_FILE == smb.FILE_DIRECTORY_FILE: try: # Let's create the directory os.mkdir(pathName) mode = os.O_RDONLY except Exception as e: smbServer.log("NTCreateAndX: %s,%s,%s" % (pathName,mode,e),logging.ERROR) errorCode = STATUS_ACCESS_DENIED if createOptions & smb.FILE_NON_DIRECTORY_FILE == smb.FILE_NON_DIRECTORY_FILE: # If the file being opened is a directory, the server MUST fail the request with # STATUS_FILE_IS_A_DIRECTORY in the Status field of the SMB Header in the server # response. if os.path.isdir(pathName) is True: errorCode = STATUS_FILE_IS_A_DIRECTORY if createOptions & smb.FILE_DELETE_ON_CLOSE == smb.FILE_DELETE_ON_CLOSE: deleteOnClose = True if errorCode == STATUS_SUCCESS: try: if os.path.isdir(pathName) and sys.platform == 'win32': fid = VOID_FILE_DESCRIPTOR else: if sys.platform == 'win32': mode |= os.O_BINARY if unicode(pathName) in smbServer.getRegisteredNamedPipes(): fid = PIPE_FILE_DESCRIPTOR sock = socket.socket() sock.connect(smbServer.getRegisteredNamedPipes()[unicode(pathName)]) else: fid = os.open(pathName, mode) except Exception as e: smbServer.log("NTCreateAndX: %s,%s,%s" % (pathName,mode,e),logging.ERROR) #print e fid = 0 errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_SMB_BAD_TID if errorCode == STATUS_SUCCESS: # Simple way to generate a fid if len(connData['OpenedFiles']) == 0: fakefid = 1 else: fakefid = connData['OpenedFiles'].keys()[-1] + 1 respParameters['Fid'] = fakefid respParameters['CreateAction'] = createDisposition if fid == PIPE_FILE_DESCRIPTOR: respParameters['FileAttributes'] = 0x80 respParameters['IsDirectory'] = 0 respParameters['CreateTime'] = 0 respParameters['LastAccessTime'] = 0 respParameters['LastWriteTime'] = 0 respParameters['LastChangeTime'] = 0 respParameters['AllocationSize'] = 4096 respParameters['EndOfFile'] = 0 respParameters['FileType'] = 2 respParameters['IPCState'] = 0x5ff else: if os.path.isdir(pathName): respParameters['FileAttributes'] = smb.SMB_FILE_ATTRIBUTE_DIRECTORY respParameters['IsDirectory'] = 1 else: respParameters['IsDirectory'] = 0 respParameters['FileAttributes'] = ntCreateAndXParameters['FileAttributes'] # Let's get this file's information respInfo, errorCode = queryPathInformation('',pathName,level= smb.SMB_QUERY_FILE_ALL_INFO) if errorCode == STATUS_SUCCESS: respParameters['CreateTime'] = respInfo['CreationTime'] respParameters['LastAccessTime'] = respInfo['LastAccessTime'] respParameters['LastWriteTime'] = respInfo['LastWriteTime'] respParameters['LastChangeTime'] = respInfo['LastChangeTime'] respParameters['FileAttributes'] = respInfo['ExtFileAttributes'] respParameters['AllocationSize'] = respInfo['AllocationSize'] respParameters['EndOfFile'] = respInfo['EndOfFile'] else: respParameters = '' respData = '' if errorCode == STATUS_SUCCESS: # Let's store the fid for the connection # smbServer.log('Create file %s, mode:0x%x' % (pathName, mode)) connData['OpenedFiles'][fakefid] = {} connData['OpenedFiles'][fakefid]['FileHandle'] = fid connData['OpenedFiles'][fakefid]['FileName'] = pathName connData['OpenedFiles'][fakefid]['DeleteOnClose'] = deleteOnClose if fid == PIPE_FILE_DESCRIPTOR: connData['OpenedFiles'][fakefid]['Socket'] = sock else: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComOpenAndX(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_OPEN_ANDX) respParameters = smb.SMBOpenAndXResponse_Parameters() respData = '' openAndXParameters = smb.SMBOpenAndX_Parameters(SMBCommand['Parameters']) openAndXData = smb.SMBOpenAndX_Data( flags = recvPacket['Flags2'], data = SMBCommand['Data']) # Get the Tid associated if recvPacket['Tid'] in connData['ConnectedShares']: path = connData['ConnectedShares'][recvPacket['Tid']]['path'] openedFile, mode, pathName, errorCode = openFile(path, decodeSMBString(recvPacket['Flags2'],openAndXData['FileName']), openAndXParameters['DesiredAccess'], openAndXParameters['FileAttributes'], openAndXParameters['OpenMode']) else: errorCode = STATUS_SMB_BAD_TID if errorCode == STATUS_SUCCESS: # Simple way to generate a fid fid = len(connData['OpenedFiles']) + 1 if len(connData['OpenedFiles']) == 0: fid = 1 else: fid = connData['OpenedFiles'].keys()[-1] + 1 respParameters['Fid'] = fid if mode & os.O_CREAT: # File did not exist and was created respParameters['Action'] = 0x2 elif mode & os.O_RDONLY: # File existed and was opened respParameters['Action'] = 0x1 elif mode & os.O_APPEND: # File existed and was opened respParameters['Action'] = 0x1 else: # File existed and was truncated respParameters['Action'] = 0x3 # Let's store the fid for the connection #smbServer.log('Opening file %s' % pathName) connData['OpenedFiles'][fid] = {} connData['OpenedFiles'][fid]['FileHandle'] = openedFile connData['OpenedFiles'][fid]['FileName'] = pathName connData['OpenedFiles'][fid]['DeleteOnClose'] = False else: respParameters = '' respData = '' respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smbComTreeConnectAndX(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId) resp = smb.NewSMBPacket() resp['Flags1'] = smb.SMB.FLAGS1_REPLY resp['Flags2'] = smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | recvPacket['Flags2'] & smb.SMB.FLAGS2_UNICODE resp['Tid'] = recvPacket['Tid'] resp['Mid'] = recvPacket['Mid'] resp['Pid'] = connData['Pid'] respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_TREE_CONNECT_ANDX) respParameters = smb.SMBTreeConnectAndXResponse_Parameters() respData = smb.SMBTreeConnectAndXResponse_Data() treeConnectAndXParameters = smb.SMBTreeConnectAndX_Parameters(SMBCommand['Parameters']) if treeConnectAndXParameters['Flags'] & 0x8: respParameters = smb.SMBTreeConnectAndXExtendedResponse_Parameters() treeConnectAndXData = smb.SMBTreeConnectAndX_Data( flags = recvPacket['Flags2'] ) treeConnectAndXData['_PasswordLength'] = treeConnectAndXParameters['PasswordLength'] treeConnectAndXData.fromString(SMBCommand['Data']) errorCode = STATUS_SUCCESS ## Process here the request, does the share exist? UNCOrShare = decodeSMBString(recvPacket['Flags2'], treeConnectAndXData['Path']) # Is this a UNC? if ntpath.ismount(UNCOrShare): path = UNCOrShare.split('\\')[3] else: path = ntpath.basename(UNCOrShare) share = searchShare(connId, path, smbServer) if share is not None: # Simple way to generate a Tid if len(connData['ConnectedShares']) == 0: tid = 1 else: tid = connData['ConnectedShares'].keys()[-1] + 1 connData['ConnectedShares'][tid] = share connData['ConnectedShares'][tid]['shareName'] = path resp['Tid'] = tid #smbServer.log("Connecting Share(%d:%s)" % (tid,path)) else: smbServer.log("TreeConnectAndX not found %s" % path, logging.ERROR) errorCode = STATUS_OBJECT_PATH_NOT_FOUND resp['ErrorCode'] = errorCode >> 16 resp['ErrorClass'] = errorCode & 0xff ## respParameters['OptionalSupport'] = smb.SMB.SMB_SUPPORT_SEARCH_BITS if path == 'IPC$': respData['Service'] = 'IPC' else: respData['Service'] = path respData['PadLen'] = 0 respData['NativeFileSystem'] = encodeSMBString(recvPacket['Flags2'], 'NTFS' ) respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData resp['Uid'] = connData['Uid'] resp.addCommand(respSMBCommand) smbServer.setConnectionData(connId, connData) return None, [resp], errorCode @staticmethod def smbComSessionSetupAndX(connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId, checkStatus = False) respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX) # From [MS-SMB] # When extended security is being used (see section 3.2.4.2.4), the # request MUST take the following form # [..] # WordCount (1 byte): The value of this field MUST be 0x0C. if SMBCommand['WordCount'] == 12: # Extended security. Here we deal with all SPNEGO stuff respParameters = smb.SMBSessionSetupAndX_Extended_Response_Parameters() respData = smb.SMBSessionSetupAndX_Extended_Response_Data(flags = recvPacket['Flags2']) sessionSetupParameters = smb.SMBSessionSetupAndX_Extended_Parameters(SMBCommand['Parameters']) sessionSetupData = smb.SMBSessionSetupAndX_Extended_Data() sessionSetupData['SecurityBlobLength'] = sessionSetupParameters['SecurityBlobLength'] sessionSetupData.fromString(SMBCommand['Data']) connData['Capabilities'] = sessionSetupParameters['Capabilities'] rawNTLM = False if struct.unpack('B',sessionSetupData['SecurityBlob'][0])[0] == ASN1_AID: # NEGOTIATE packet blob = SPNEGO_NegTokenInit(sessionSetupData['SecurityBlob']) token = blob['MechToken'] if len(blob['MechTypes'][0]) > 0: # Is this GSSAPI NTLM or something else we don't support? mechType = blob['MechTypes'][0] if mechType != TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']: # Nope, do we know it? if mechType in MechTypes: mechStr = MechTypes[mechType] else: mechStr = hexlify(mechType) smbServer.log("Unsupported MechType '%s'" % mechStr, logging.CRITICAL) # We don't know the token, we answer back again saying # we just support NTLM. # ToDo: Build this into a SPNEGO_NegTokenResp() respToken = '\xa1\x15\x30\x13\xa0\x03\x0a\x01\x03\xa1\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a' respParameters['SecurityBlobLength'] = len(respToken) respData['SecurityBlobLength'] = respParameters['SecurityBlobLength'] respData['SecurityBlob'] = respToken respData['NativeOS'] = encodeSMBString(recvPacket['Flags2'], smbServer.getServerOS()) respData['NativeLanMan'] = encodeSMBString(recvPacket['Flags2'], smbServer.getServerOS()) respSMBCommand['Parameters'] = respParameters respSMBCommand['Data'] = respData return [respSMBCommand], None, STATUS_MORE_PROCESSING_REQUIRED elif struct.unpack('B',sessionSetupData['SecurityBlob'][0])[0] == ASN1_SUPPORTED_MECH: # AUTH packet blob = SPNEGO_NegTokenResp(sessionSetupData['SecurityBlob']) token = blob['ResponseToken'] else: # No GSSAPI stuff, raw NTLMSSP rawNTLM = True token = sessionSetupData['SecurityBlob'] # Here we only handle NTLMSSP, depending on what stage of the # authentication we are, we act on it messageType = struct.unpack('> 16 packet['ErrorClass'] = errorCode & 0xff return None, [packet], errorCode class SMB2Commands: @staticmethod def smb2Negotiate(connId, smbServer, recvPacket, isSMB1 = False): connData = smbServer.getConnectionData(connId, checkStatus = False) respPacket = smb2.SMB2Packet() respPacket['Flags'] = smb2.SMB2_FLAGS_SERVER_TO_REDIR respPacket['Status'] = STATUS_SUCCESS respPacket['CreditRequestResponse'] = 1 respPacket['Command'] = smb2.SMB2_NEGOTIATE respPacket['SessionID'] = 0 if isSMB1 is False: respPacket['MessageID'] = recvPacket['MessageID'] else: respPacket['MessageID'] = 0 respPacket['TreeID'] = 0 respSMBCommand = smb2.SMB2Negotiate_Response() respSMBCommand['SecurityMode'] = 1 if isSMB1 is True: # Let's first parse the packet to see if the client supports SMB2 SMBCommand = smb.SMBCommand(recvPacket['Data'][0]) dialects = SMBCommand['Data'].split('\x02') if 'SMB 2.002\x00' in dialects or 'SMB 2.???\x00' in dialects: respSMBCommand['DialectRevision'] = smb2.SMB2_DIALECT_002 else: # Client does not support SMB2 fallbacking raise Exception('SMB2 not supported, fallbacking') else: respSMBCommand['DialectRevision'] = smb2.SMB2_DIALECT_002 respSMBCommand['ServerGuid'] = 'A'*16 respSMBCommand['Capabilities'] = 0 respSMBCommand['MaxTransactSize'] = 65536 respSMBCommand['MaxReadSize'] = 65536 respSMBCommand['MaxWriteSize'] = 65536 respSMBCommand['SystemTime'] = getFileTime(calendar.timegm(time.gmtime())) respSMBCommand['ServerStartTime'] = getFileTime(calendar.timegm(time.gmtime())) respSMBCommand['SecurityBufferOffset'] = 0x80 blob = SPNEGO_NegTokenInit() blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']] respSMBCommand['Buffer'] = blob.getData() respSMBCommand['SecurityBufferLength'] = len(respSMBCommand['Buffer']) respPacket['Data'] = respSMBCommand smbServer.setConnectionData(connId, connData) return None, [respPacket], STATUS_SUCCESS @staticmethod def smb2SessionSetup(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId, checkStatus = False) respSMBCommand = smb2.SMB2SessionSetup_Response() sessionSetupData = smb2.SMB2SessionSetup(recvPacket['Data']) connData['Capabilities'] = sessionSetupData['Capabilities'] securityBlob = sessionSetupData['Buffer'] rawNTLM = False if struct.unpack('B',securityBlob[0])[0] == ASN1_AID: # NEGOTIATE packet blob = SPNEGO_NegTokenInit(securityBlob) token = blob['MechToken'] if len(blob['MechTypes'][0]) > 0: # Is this GSSAPI NTLM or something else we don't support? mechType = blob['MechTypes'][0] if mechType != TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']: # Nope, do we know it? if mechType in MechTypes: mechStr = MechTypes[mechType] else: mechStr = hexlify(mechType) smbServer.log("Unsupported MechType '%s'" % mechStr, logging.CRITICAL) # We don't know the token, we answer back again saying # we just support NTLM. # ToDo: Build this into a SPNEGO_NegTokenResp() respToken = '\xa1\x15\x30\x13\xa0\x03\x0a\x01\x03\xa1\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a' respSMBCommand['SecurityBufferOffset'] = 0x48 respSMBCommand['SecurityBufferLength'] = len(respToken) respSMBCommand['Buffer'] = respToken return [respSMBCommand], None, STATUS_MORE_PROCESSING_REQUIRED elif struct.unpack('B',securityBlob[0])[0] == ASN1_SUPPORTED_MECH: # AUTH packet blob = SPNEGO_NegTokenResp(securityBlob) token = blob['ResponseToken'] else: # No GSSAPI stuff, raw NTLMSSP rawNTLM = True token = securityBlob # Here we only handle NTLMSSP, depending on what stage of the # authentication we are, we act on it messageType = struct.unpack(' 0 and (fileName[0] == '/' or fileName[0] == '\\'): # strip leading '/' fileName = fileName[1:] pathName = os.path.join(path,fileName) createDisposition = ntCreateRequest['CreateDisposition'] mode = 0 if createDisposition == smb2.FILE_SUPERSEDE: mode |= os.O_TRUNC | os.O_CREAT elif createDisposition & smb2.FILE_OVERWRITE_IF == smb2.FILE_OVERWRITE_IF: mode |= os.O_TRUNC | os.O_CREAT elif createDisposition & smb2.FILE_OVERWRITE == smb2.FILE_OVERWRITE: if os.path.exists(pathName) is True: mode |= os.O_TRUNC else: errorCode = STATUS_NO_SUCH_FILE elif createDisposition & smb2.FILE_OPEN_IF == smb2.FILE_OPEN_IF: if os.path.exists(pathName) is True: mode |= os.O_TRUNC else: mode |= os.O_TRUNC | os.O_CREAT elif createDisposition & smb2.FILE_CREATE == smb2.FILE_CREATE: if os.path.exists(pathName) is True: errorCode = STATUS_OBJECT_NAME_COLLISION else: mode |= os.O_CREAT elif createDisposition & smb2.FILE_OPEN == smb2.FILE_OPEN: if os.path.exists(pathName) is not True and (unicode(pathName) in smbServer.getRegisteredNamedPipes()) is not True: errorCode = STATUS_NO_SUCH_FILE if errorCode == STATUS_SUCCESS: desiredAccess = ntCreateRequest['DesiredAccess'] if (desiredAccess & smb2.FILE_READ_DATA) or (desiredAccess & smb2.GENERIC_READ): mode |= os.O_RDONLY if (desiredAccess & smb2.FILE_WRITE_DATA) or (desiredAccess & smb2.GENERIC_WRITE): if (desiredAccess & smb2.FILE_READ_DATA) or (desiredAccess & smb2.GENERIC_READ): mode |= os.O_RDWR #| os.O_APPEND else: mode |= os.O_WRONLY #| os.O_APPEND if desiredAccess & smb2.GENERIC_ALL: mode |= os.O_RDWR #| os.O_APPEND createOptions = ntCreateRequest['CreateOptions'] if mode & os.O_CREAT == os.O_CREAT: if createOptions & smb2.FILE_DIRECTORY_FILE == smb2.FILE_DIRECTORY_FILE: try: # Let's create the directory os.mkdir(pathName) mode = os.O_RDONLY except Exception as e: smbServer.log("SMB2_CREATE: %s,%s,%s" % (pathName,mode,e),logging.ERROR) errorCode = STATUS_ACCESS_DENIED if createOptions & smb2.FILE_NON_DIRECTORY_FILE == smb2.FILE_NON_DIRECTORY_FILE: # If the file being opened is a directory, the server MUST fail the request with # STATUS_FILE_IS_A_DIRECTORY in the Status field of the SMB Header in the server # response. if os.path.isdir(pathName) is True: errorCode = STATUS_FILE_IS_A_DIRECTORY if createOptions & smb2.FILE_DELETE_ON_CLOSE == smb2.FILE_DELETE_ON_CLOSE: deleteOnClose = True if errorCode == STATUS_SUCCESS: try: if os.path.isdir(pathName) and sys.platform == 'win32': fid = VOID_FILE_DESCRIPTOR else: if sys.platform == 'win32': mode |= os.O_BINARY if unicode(pathName) in smbServer.getRegisteredNamedPipes(): fid = PIPE_FILE_DESCRIPTOR sock = socket.socket() sock.connect(smbServer.getRegisteredNamedPipes()[unicode(pathName)]) else: fid = os.open(pathName, mode) except Exception as e: smbServer.log("SMB2_CREATE: %s,%s,%s" % (pathName,mode,e),logging.ERROR) #print e fid = 0 errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_SMB_BAD_TID if errorCode == STATUS_SUCCESS: # Simple way to generate a fid fakefid = uuid.generate() respSMBCommand['FileID'] = fakefid respSMBCommand['CreateAction'] = createDisposition if fid == PIPE_FILE_DESCRIPTOR: respSMBCommand['CreationTime'] = 0 respSMBCommand['LastAccessTime'] = 0 respSMBCommand['LastWriteTime'] = 0 respSMBCommand['ChangeTime'] = 0 respSMBCommand['AllocationSize'] = 4096 respSMBCommand['EndOfFile'] = 0 respSMBCommand['FileAttributes'] = 0x80 else: if os.path.isdir(pathName): respSMBCommand['FileAttributes'] = smb.SMB_FILE_ATTRIBUTE_DIRECTORY else: respSMBCommand['FileAttributes'] = ntCreateRequest['FileAttributes'] # Let's get this file's information respInfo, errorCode = queryPathInformation('',pathName,level= smb.SMB_QUERY_FILE_ALL_INFO) if errorCode == STATUS_SUCCESS: respSMBCommand['CreationTime'] = respInfo['CreationTime'] respSMBCommand['LastAccessTime'] = respInfo['LastAccessTime'] respSMBCommand['LastWriteTime'] = respInfo['LastWriteTime'] respSMBCommand['LastChangeTime'] = respInfo['LastChangeTime'] respSMBCommand['FileAttributes'] = respInfo['ExtFileAttributes'] respSMBCommand['AllocationSize'] = respInfo['AllocationSize'] respSMBCommand['EndOfFile'] = respInfo['EndOfFile'] if errorCode == STATUS_SUCCESS: # Let's store the fid for the connection # smbServer.log('Create file %s, mode:0x%x' % (pathName, mode)) connData['OpenedFiles'][fakefid] = {} connData['OpenedFiles'][fakefid]['FileHandle'] = fid connData['OpenedFiles'][fakefid]['FileName'] = pathName connData['OpenedFiles'][fakefid]['DeleteOnClose'] = deleteOnClose connData['OpenedFiles'][fakefid]['Open'] = {} connData['OpenedFiles'][fakefid]['Open']['EnumerationLocation'] = 0 connData['OpenedFiles'][fakefid]['Open']['EnumerationSearchPattern'] = '' if fid == PIPE_FILE_DESCRIPTOR: connData['OpenedFiles'][fakefid]['Socket'] = sock else: respSMBCommand = smb2.SMB2Error() if errorCode == STATUS_SUCCESS: connData['LastRequest']['SMB2_CREATE'] = respSMBCommand smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2Close(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2Close_Response() closeRequest = smb2.SMB2Close(recvPacket['Data']) if str(closeRequest['FileID']) == '\xff'*16: # Let's take the data from the lastRequest if 'SMB2_CREATE' in connData['LastRequest']: fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] else: fileID = str(closeRequest['FileID']) else: fileID = str(closeRequest['FileID']) if fileID in connData['OpenedFiles']: errorCode = STATUS_SUCCESS fileHandle = connData['OpenedFiles'][fileID]['FileHandle'] pathName = connData['OpenedFiles'][fileID]['FileName'] infoRecord = None try: if fileHandle == PIPE_FILE_DESCRIPTOR: connData['OpenedFiles'][fileID]['Socket'].close() elif fileHandle != VOID_FILE_DESCRIPTOR: os.close(fileHandle) infoRecord, errorCode = queryFileInformation(os.path.dirname(pathName), os.path.basename(pathName), smb2.SMB2_FILE_NETWORK_OPEN_INFO) except Exception as e: smbServer.log("SMB2_CLOSE %s" % e, logging.ERROR) errorCode = STATUS_INVALID_HANDLE else: # Check if the file was marked for removal if connData['OpenedFiles'][fileID]['DeleteOnClose'] is True: try: if os.path.isdir(pathName): shutil.rmtree(connData['OpenedFiles'][fileID]['FileName']) else: os.remove(connData['OpenedFiles'][fileID]['FileName']) except Exception as e: smbServer.log("SMB2_CLOSE %s" % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED # Now fill out the response if infoRecord is not None: respSMBCommand['CreationTime'] = infoRecord['CreationTime'] respSMBCommand['LastAccessTime'] = infoRecord['LastAccessTime'] respSMBCommand['LastWriteTime'] = infoRecord['LastWriteTime'] respSMBCommand['ChangeTime'] = infoRecord['ChangeTime'] respSMBCommand['AllocationSize'] = infoRecord['AllocationSize'] respSMBCommand['EndofFile'] = infoRecord['EndOfFile'] respSMBCommand['FileAttributes'] = infoRecord['FileAttributes'] if errorCode == STATUS_SUCCESS: del(connData['OpenedFiles'][fileID]) else: errorCode = STATUS_INVALID_HANDLE smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2QueryInfo(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2QueryInfo_Response() queryInfo = smb2.SMB2QueryInfo(recvPacket['Data']) errorCode = STATUS_SUCCESS respSMBCommand['OutputBufferOffset'] = 0x48 respSMBCommand['Buffer'] = '\x00' if str(queryInfo['FileID']) == '\xff'*16: # Let's take the data from the lastRequest if 'SMB2_CREATE' in connData['LastRequest']: fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] else: fileID = str(queryInfo['FileID']) else: fileID = str(queryInfo['FileID']) if recvPacket['TreeID'] in connData['ConnectedShares']: if fileID in connData['OpenedFiles']: fileName = connData['OpenedFiles'][fileID]['FileName'] if queryInfo['InfoType'] == smb2.SMB2_0_INFO_FILE: if queryInfo['FileInfoClass'] == smb2.SMB2_FILE_INTERNAL_INFO: # No need to call queryFileInformation, we have the data here infoRecord = smb2.FileInternalInformation() infoRecord['IndexNumber'] = fileID else: infoRecord, errorCode = queryFileInformation(os.path.dirname(fileName), os.path.basename(fileName), queryInfo['FileInfoClass']) elif queryInfo['InfoType'] == smb2.SMB2_0_INFO_FILESYSTEM: infoRecord = queryFsInformation(os.path.dirname(fileName), os.path.basename(fileName), queryInfo['FileInfoClass']) elif queryInfo['InfoType'] == smb2.SMB2_0_INFO_SECURITY: # Failing for now, until we support it infoRecord = None errorCode = STATUS_ACCESS_DENIED else: smbServer.log("queryInfo not supported (%x)" % queryInfo['InfoType'], logging.ERROR) if infoRecord is not None: respSMBCommand['OutputBufferLength'] = len(infoRecord) respSMBCommand['Buffer'] = infoRecord else: errorCode = STATUS_INVALID_HANDLE else: errorCode = STATUS_SMB_BAD_TID smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2SetInfo(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2SetInfo_Response() setInfo = smb2.SMB2SetInfo(recvPacket['Data']) errorCode = STATUS_SUCCESS if str(setInfo['FileID']) == '\xff'*16: # Let's take the data from the lastRequest if 'SMB2_CREATE' in connData['LastRequest']: fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] else: fileID = str(setInfo['FileID']) else: fileID = str(setInfo['FileID']) if recvPacket['TreeID'] in connData['ConnectedShares']: path = connData['ConnectedShares'][recvPacket['TreeID']]['path'] if fileID in connData['OpenedFiles']: pathName = connData['OpenedFiles'][fileID]['FileName'] if setInfo['InfoType'] == smb2.SMB2_0_INFO_FILE: # The file information is being set informationLevel = setInfo['FileInfoClass'] if informationLevel == smb2.SMB2_FILE_DISPOSITION_INFO: infoRecord = smb.SMBSetFileDispositionInfo(setInfo['Buffer']) if infoRecord['DeletePending'] > 0: # Mark this file for removal after closed connData['OpenedFiles'][fileID]['DeleteOnClose'] = True elif informationLevel == smb2.SMB2_FILE_BASIC_INFO: infoRecord = smb.SMBSetFileBasicInfo(setInfo['Buffer']) # Creation time won't be set, the other ones we play with. atime = infoRecord['LastWriteTime'] if atime == 0: atime = -1 else: atime = getUnixTime(atime) mtime = infoRecord['ChangeTime'] if mtime == 0: mtime = -1 else: mtime = getUnixTime(mtime) if atime > 0 and mtime > 0: os.utime(pathName,(atime,mtime)) elif informationLevel == smb2.SMB2_FILE_END_OF_FILE_INFO: fileHandle = connData['OpenedFiles'][fileID]['FileHandle'] infoRecord = smb.SMBSetFileEndOfFileInfo(setInfo['Buffer']) if infoRecord['EndOfFile'] > 0: os.lseek(fileHandle, infoRecord['EndOfFile']-1, 0) os.write(fileHandle, '\x00') elif informationLevel == smb2.SMB2_FILE_RENAME_INFO: renameInfo = smb2.FILE_RENAME_INFORMATION_TYPE_2(setInfo['Buffer']) newPathName = os.path.join(path,renameInfo['FileName'].decode('utf-16le').replace('\\', '/')) if renameInfo['ReplaceIfExists'] == 0 and os.path.exists(newPathName): return [smb2.SMB2Error()], None, STATUS_OBJECT_NAME_COLLISION try: os.rename(pathName,newPathName) connData['OpenedFiles'][fileID]['FileName'] = newPathName except Exception as e: smbServer.log("smb2SetInfo: %s" % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: smbServer.log('Unknown level for set file info! 0x%x' % informationLevel, logging.ERROR) # UNSUPPORTED errorCode = STATUS_NOT_SUPPORTED #elif setInfo['InfoType'] == smb2.SMB2_0_INFO_FILESYSTEM: # # The underlying object store information is being set. # setInfo = queryFsInformation('/', fileName, queryInfo['FileInfoClass']) #elif setInfo['InfoType'] == smb2.SMB2_0_INFO_SECURITY: # # The security information is being set. # # Failing for now, until we support it # infoRecord = None # errorCode = STATUS_ACCESS_DENIED #elif setInfo['InfoType'] == smb2.SMB2_0_INFO_QUOTA: # # The underlying object store quota information is being set. # setInfo = queryFsInformation('/', fileName, queryInfo['FileInfoClass']) else: smbServer.log("setInfo not supported (%x)" % setInfo['InfoType'], logging.ERROR) else: errorCode = STATUS_INVALID_HANDLE else: errorCode = STATUS_SMB_BAD_TID smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2Write(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2Write_Response() writeRequest = smb2.SMB2Write(recvPacket['Data']) respSMBCommand['Buffer'] = '\x00' if str(writeRequest['FileID']) == '\xff'*16: # Let's take the data from the lastRequest if 'SMB2_CREATE' in connData['LastRequest']: fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] else: fileID = str(writeRequest['FileID']) else: fileID = str(writeRequest['FileID']) if fileID in connData['OpenedFiles']: fileHandle = connData['OpenedFiles'][fileID]['FileHandle'] errorCode = STATUS_SUCCESS try: if fileHandle != PIPE_FILE_DESCRIPTOR: offset = writeRequest['Offset'] # If we're trying to write past the file end we just skip the write call (Vista does this) if os.lseek(fileHandle, 0, 2) >= offset: os.lseek(fileHandle,offset,0) os.write(fileHandle,writeRequest['Buffer']) else: sock = connData['OpenedFiles'][fileID]['Socket'] sock.send(writeRequest['Buffer']) respSMBCommand['Count'] = writeRequest['Length'] respSMBCommand['Remaining']= 0xff except Exception as e: smbServer.log('SMB2_WRITE: %s' % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_INVALID_HANDLE smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2Read(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2Read_Response() readRequest = smb2.SMB2Read(recvPacket['Data']) respSMBCommand['Buffer'] = '\x00' if str(readRequest['FileID']) == '\xff'*16: # Let's take the data from the lastRequest if 'SMB2_CREATE' in connData['LastRequest']: fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] else: fileID = str(readRequest['FileID']) else: fileID = str(readRequest['FileID']) if fileID in connData['OpenedFiles']: fileHandle = connData['OpenedFiles'][fileID]['FileHandle'] errorCode = 0 try: if fileHandle != PIPE_FILE_DESCRIPTOR: offset = readRequest['Offset'] os.lseek(fileHandle,offset,0) content = os.read(fileHandle,readRequest['Length']) else: sock = connData['OpenedFiles'][fileID]['Socket'] content = sock.recv(readRequest['Length']) respSMBCommand['DataOffset'] = 0x50 respSMBCommand['DataLength'] = len(content) respSMBCommand['DataRemaining']= 0 respSMBCommand['Buffer'] = content except Exception as e: smbServer.log('SMB2_READ: %s ' % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_INVALID_HANDLE smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2Flush(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2Flush_Response() flushRequest = smb2.SMB2Flush(recvPacket['Data']) if str(flushRequest['FileID']) in connData['OpenedFiles']: fileHandle = connData['OpenedFiles'][str(flushRequest['FileID'])]['FileHandle'] errorCode = STATUS_SUCCESS try: os.fsync(fileHandle) except Exception as e: smbServer.log("SMB2_FLUSH %s" % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_INVALID_HANDLE smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2QueryDirectory(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2QueryDirectory_Response() queryDirectoryRequest = smb2.SMB2QueryDirectory(recvPacket['Data']) respSMBCommand['Buffer'] = '\x00' # The server MUST locate the tree connection, as specified in section 3.3.5.2.11. if (recvPacket['TreeID'] in connData['ConnectedShares']) is False: return [smb2.SMB2Error()], None, STATUS_NETWORK_NAME_DELETED # Next, the server MUST locate the open for the directory to be queried # If no open is found, the server MUST fail the request with STATUS_FILE_CLOSED if str(queryDirectoryRequest['FileID']) == '\xff'*16: # Let's take the data from the lastRequest if 'SMB2_CREATE' in connData['LastRequest']: fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] else: fileID = str(queryDirectoryRequest['FileID']) else: fileID = str(queryDirectoryRequest['FileID']) if (fileID in connData['OpenedFiles']) is False: return [smb2.SMB2Error()], None, STATUS_FILE_CLOSED # If the open is not an open to a directory, the request MUST be failed # with STATUS_INVALID_PARAMETER. if os.path.isdir(connData['OpenedFiles'][fileID]['FileName']) is False: return [smb2.SMB2Error()], None, STATUS_INVALID_PARAMETER # If any other information class is specified in the FileInformationClass # field of the SMB2 QUERY_DIRECTORY Request, the server MUST fail the # operation with STATUS_INVALID_INFO_CLASS. if queryDirectoryRequest['FileInformationClass'] not in ( smb2.FILE_DIRECTORY_INFORMATION, smb2.FILE_FULL_DIRECTORY_INFORMATION, smb2.FILEID_FULL_DIRECTORY_INFORMATION, smb2.FILE_BOTH_DIRECTORY_INFORMATION, smb2.FILEID_BOTH_DIRECTORY_INFORMATION, smb2.FILENAMES_INFORMATION): return [smb2.SMB2Error()], None, STATUS_INVALID_INFO_CLASS # If SMB2_REOPEN is set in the Flags field of the SMB2 QUERY_DIRECTORY # Request, the server SHOULD<326> set Open.EnumerationLocation to 0 # and Open.EnumerationSearchPattern to an empty string. if queryDirectoryRequest['Flags'] & smb2.SMB2_REOPEN: connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'] = 0 connData['OpenedFiles'][fileID]['Open']['EnumerationSearchPattern'] = '' # If SMB2_RESTART_SCANS is set in the Flags field of the SMB2 # QUERY_DIRECTORY Request, the server MUST set # Open.EnumerationLocation to 0. if queryDirectoryRequest['Flags'] & smb2.SMB2_RESTART_SCANS: connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'] = 0 # If Open.EnumerationLocation is 0 and Open.EnumerationSearchPattern # is an empty string, then Open.EnumerationSearchPattern MUST be set # to the search pattern specified in the SMB2 QUERY_DIRECTORY by # FileNameOffset and FileNameLength. If FileNameLength is 0, the server # SHOULD<327> set Open.EnumerationSearchPattern as "*" to search all entries. pattern = queryDirectoryRequest['Buffer'].decode('utf-16le') if connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'] == 0 and \ connData['OpenedFiles'][fileID]['Open']['EnumerationSearchPattern'] == '': if pattern == '': pattern = '*' connData['OpenedFiles'][fileID]['Open']['EnumerationSearchPattern'] = pattern # If SMB2_INDEX_SPECIFIED is set and FileNameLength is not zero, # the server MUST set Open.EnumerationSearchPattern to the search pattern # specified in the request by FileNameOffset and FileNameLength. if queryDirectoryRequest['Flags'] & smb2.SMB2_INDEX_SPECIFIED and \ queryDirectoryRequest['FileNameLength'] > 0: connData['OpenedFiles'][fileID]['Open']['EnumerationSearchPattern'] = pattern pathName = os.path.join(os.path.normpath(connData['OpenedFiles'][fileID]['FileName']),pattern) searchResult, searchCount, errorCode = findFirst2(os.path.dirname(pathName), os.path.basename(pathName), queryDirectoryRequest['FileInformationClass'], smb.ATTR_DIRECTORY, isSMB2 = True ) if errorCode != STATUS_SUCCESS: return [smb2.SMB2Error()], None, errorCode if searchCount > 2 and pattern == '*': # strip . and .. searchCount -= 2 searchResult = searchResult[2:] if searchCount == 0 and connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'] == 0: return [smb2.SMB2Error()], None, STATUS_NO_SUCH_FILE if connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'] < 0: return [smb2.SMB2Error()], None, STATUS_NO_MORE_FILES totalData = 0 respData = '' for nItem in range(connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'], searchCount): connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'] += 1 if queryDirectoryRequest['Flags'] & smb2.SL_RETURN_SINGLE_ENTRY: # If single entry is requested we must clear the NextEntryOffset searchResult[nItem]['NextEntryOffset'] = 0 data = searchResult[nItem].getData() lenData = len(data) padLen = (8-(lenData % 8)) %8 if (totalData+lenData) >= queryDirectoryRequest['OutputBufferLength']: connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'] -= 1 break else: respData += data + '\x00'*padLen totalData += lenData + padLen if queryDirectoryRequest['Flags'] & smb2.SL_RETURN_SINGLE_ENTRY: break if connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'] >= searchCount: connData['OpenedFiles'][fileID]['Open']['EnumerationLocation'] = -1 respSMBCommand['OutputBufferOffset'] = 0x48 respSMBCommand['OutputBufferLength'] = totalData respSMBCommand['Buffer'] = respData smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2ChangeNotify(connId, smbServer, recvPacket): return [smb2.SMB2Error()], None, STATUS_NOT_SUPPORTED @staticmethod def smb2Echo(connId, smbServer, recvPacket): respSMBCommand = smb2.SMB2Echo_Response() return [respSMBCommand], None, STATUS_SUCCESS @staticmethod def smb2TreeDisconnect(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2TreeDisconnect_Response() if recvPacket['TreeID'] in connData['ConnectedShares']: smbServer.log("Disconnecting Share(%d:%s)" % (recvPacket['TreeID'],connData['ConnectedShares'][recvPacket['TreeID']]['shareName'])) del(connData['ConnectedShares'][recvPacket['TreeID']]) errorCode = STATUS_SUCCESS else: # STATUS_SMB_BAD_TID errorCode = STATUS_SMB_BAD_TID smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2Logoff(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2Logoff_Response() if recvPacket['SessionID'] != connData['Uid']: # STATUS_SMB_BAD_UID errorCode = STATUS_SMB_BAD_UID else: errorCode = STATUS_SUCCESS connData['Uid'] = 0 smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2Ioctl(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2Ioctl_Response() ioctlRequest = smb2.SMB2Ioctl(recvPacket['Data']) ioctls = smbServer.getIoctls() if ioctlRequest['CtlCode'] in ioctls: outputData, errorCode = ioctls[ioctlRequest['CtlCode']](connId, smbServer, ioctlRequest) if errorCode == STATUS_SUCCESS: respSMBCommand['CtlCode'] = ioctlRequest['CtlCode'] respSMBCommand['FileID'] = ioctlRequest['FileID'] respSMBCommand['InputOffset'] = 0 respSMBCommand['InputCount'] = 0 respSMBCommand['OutputOffset'] = 0x70 respSMBCommand['OutputCount'] = len(outputData) respSMBCommand['Flags'] = 0 respSMBCommand['Buffer'] = outputData else: respSMBCommand = outputData else: smbServer.log("Ioctl not implemented command: 0x%x" % ioctlRequest['CtlCode'],logging.DEBUG) errorCode = STATUS_INVALID_DEVICE_REQUEST respSMBCommand = smb2.SMB2Error() smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2Lock(connId, smbServer, recvPacket): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2Lock_Response() # I'm actually doing nothing.. just make MacOS happy ;) errorCode = STATUS_SUCCESS smbServer.setConnectionData(connId, connData) return [respSMBCommand], None, errorCode @staticmethod def smb2Cancel(connId, smbServer, recvPacket): # I'm actually doing nothing return [smb2.SMB2Error()], None, STATUS_CANCELLED @staticmethod def default(connId, smbServer, recvPacket): # By default we return an SMB Packet with error not implemented smbServer.log("Not implemented command: 0x%x" % recvPacket['Command'],logging.DEBUG) return [smb2.SMB2Error()], None, STATUS_NOT_SUPPORTED class Ioctls: @staticmethod def fsctlDfsGetReferrals(connId, smbServer, ioctlRequest): return smb2.SMB2Error(), STATUS_FS_DRIVER_REQUIRED @staticmethod def fsctlPipeTransceive(connId, smbServer, ioctlRequest): connData = smbServer.getConnectionData(connId) ioctlResponse = '' if str(ioctlRequest['FileID']) in connData['OpenedFiles']: fileHandle = connData['OpenedFiles'][str(ioctlRequest['FileID'])]['FileHandle'] errorCode = STATUS_SUCCESS try: if fileHandle != PIPE_FILE_DESCRIPTOR: errorCode = STATUS_INVALID_DEVICE_REQUEST else: sock = connData['OpenedFiles'][str(ioctlRequest['FileID'])]['Socket'] sock.sendall(ioctlRequest['Buffer']) ioctlResponse = sock.recv(ioctlRequest['MaxOutputResponse']) except Exception as e: smbServer.log('fsctlPipeTransceive: %s ' % e, logging.ERROR) errorCode = STATUS_ACCESS_DENIED else: errorCode = STATUS_INVALID_DEVICE_REQUEST smbServer.setConnectionData(connId, connData) return ioctlResponse, errorCode @staticmethod def fsctlValidateNegotiateInfo(connId, smbServer, ioctlRequest): connData = smbServer.getConnectionData(connId) errorCode = STATUS_SUCCESS validateNegotiateInfo = smb2.VALIDATE_NEGOTIATE_INFO(ioctlRequest['Buffer']) validateNegotiateInfo['Capabilities'] = 0 validateNegotiateInfo['Guid'] = 'A'*16 validateNegotiateInfo['SecurityMode'] = 1 validateNegotiateInfo['Dialects'] = (smb2.SMB2_DIALECT_002,) smbServer.setConnectionData(connId, connData) return validateNegotiateInfo.getData(), errorCode class SMBSERVERHandler(socketserver.BaseRequestHandler): def __init__(self, request, client_address, server, select_poll = False): self.__SMB = server self.__ip, self.__port = client_address self.__request = request self.__connId = threading.currentThread().getName() self.__timeOut = 60*5 self.__select_poll = select_poll #self.__connId = os.getpid() socketserver.BaseRequestHandler.__init__(self, request, client_address, server) def handle(self): self.__SMB.log("Incoming connection (%s,%d)" % (self.__ip, self.__port)) self.__SMB.addConnection(self.__connId, self.__ip, self.__port) while True: try: # Firt of all let's get the NETBIOS packet session = nmb.NetBIOSTCPSession(self.__SMB.getServerName(),'HOST', self.__ip, sess_port = self.__port, sock = self.__request, select_poll = self.__select_poll) try: p = session.recv_packet(self.__timeOut) except nmb.NetBIOSTimeout: raise except nmb.NetBIOSError: break if p.get_type() == nmb.NETBIOS_SESSION_REQUEST: # Someone is requesting a session, we're gonna accept them all :) _, rn, my = p.get_trailer().split(' ') remote_name = nmb.decode_name('\x20'+rn) myname = nmb.decode_name('\x20'+my) self.__SMB.log("NetBIOS Session request (%s,%s,%s)" % (self.__ip, remote_name[1].strip(), myname[1])) r = nmb.NetBIOSSessionPacket() r.set_type(nmb.NETBIOS_SESSION_POSITIVE_RESPONSE) r.set_trailer(p.get_trailer()) self.__request.send(r.rawData()) else: resp = self.__SMB.processRequest(self.__connId, p.get_trailer()) # Send all the packets recevied. Except for big transactions this should be # a single packet for i in resp: session.send_packet(str(i)) except Exception as e: self.__SMB.log("Handle: %s" % e) #import traceback #traceback.print_exc() break def finish(self): # Thread/process is dying, we should tell the main SMB thread to remove all this thread data self.__SMB.log("Closing down connection (%s,%d)" % (self.__ip, self.__port)) self.__SMB.removeConnection(self.__connId) return socketserver.BaseRequestHandler.finish(self) class SMBSERVER(socketserver.ThreadingMixIn, socketserver.TCPServer): #class SMBSERVER(socketserver.ForkingMixIn, socketserver.TCPServer): def __init__(self, server_address, handler_class=SMBSERVERHandler, config_parser = None): socketserver.TCPServer.allow_reuse_address = True socketserver.TCPServer.__init__(self, server_address, handler_class) # Server name and OS to be presented whenever is necessary self.__serverName = '' self.__serverOS = '' self.__serverDomain = '' self.__challenge = '' self.__log = None # Our ConfigParser data self.__serverConfig = config_parser # Our credentials to be used during the server's lifetime self.__credentials = {} # Our log file self.__logFile = '' # Registered Named Pipes, format is PipeName,Socket self.__registeredNamedPipes = {} # JTR dump path self.__jtr_dump_path = '' # SMB2 Support flag = default not active self.__SMB2Support = False # Our list of commands we will answer, by default the NOT IMPLEMENTED one self.__smbCommandsHandler = SMBCommands() self.__smbTrans2Handler = TRANS2Commands() self.__smbTransHandler = TRANSCommands() self.__smbNTTransHandler = NTTRANSCommands() self.__smb2CommandsHandler = SMB2Commands() self.__IoctlHandler = Ioctls() self.__smbNTTransCommands = { # NT IOCTL, can't find doc for this 0xff :self.__smbNTTransHandler.default } self.__smbTransCommands = { '\\PIPE\\LANMAN' :self.__smbTransHandler.lanMan, smb.SMB.TRANS_TRANSACT_NMPIPE :self.__smbTransHandler.transactNamedPipe, } self.__smbTrans2Commands = { smb.SMB.TRANS2_FIND_FIRST2 :self.__smbTrans2Handler.findFirst2, smb.SMB.TRANS2_FIND_NEXT2 :self.__smbTrans2Handler.findNext2, smb.SMB.TRANS2_QUERY_FS_INFORMATION :self.__smbTrans2Handler.queryFsInformation, smb.SMB.TRANS2_QUERY_PATH_INFORMATION :self.__smbTrans2Handler.queryPathInformation, smb.SMB.TRANS2_QUERY_FILE_INFORMATION :self.__smbTrans2Handler.queryFileInformation, smb.SMB.TRANS2_SET_FILE_INFORMATION :self.__smbTrans2Handler.setFileInformation, smb.SMB.TRANS2_SET_PATH_INFORMATION :self.__smbTrans2Handler.setPathInformation } self.__smbCommands = { #smb.SMB.SMB_COM_FLUSH: self.__smbCommandsHandler.smbComFlush, smb.SMB.SMB_COM_CREATE_DIRECTORY: self.__smbCommandsHandler.smbComCreateDirectory, smb.SMB.SMB_COM_DELETE_DIRECTORY: self.__smbCommandsHandler.smbComDeleteDirectory, smb.SMB.SMB_COM_RENAME: self.__smbCommandsHandler.smbComRename, smb.SMB.SMB_COM_DELETE: self.__smbCommandsHandler.smbComDelete, smb.SMB.SMB_COM_NEGOTIATE: self.__smbCommandsHandler.smbComNegotiate, smb.SMB.SMB_COM_SESSION_SETUP_ANDX: self.__smbCommandsHandler.smbComSessionSetupAndX, smb.SMB.SMB_COM_LOGOFF_ANDX: self.__smbCommandsHandler.smbComLogOffAndX, smb.SMB.SMB_COM_TREE_CONNECT_ANDX: self.__smbCommandsHandler.smbComTreeConnectAndX, smb.SMB.SMB_COM_TREE_DISCONNECT: self.__smbCommandsHandler.smbComTreeDisconnect, smb.SMB.SMB_COM_ECHO: self.__smbCommandsHandler.smbComEcho, smb.SMB.SMB_COM_QUERY_INFORMATION: self.__smbCommandsHandler.smbQueryInformation, smb.SMB.SMB_COM_TRANSACTION2: self.__smbCommandsHandler.smbTransaction2, smb.SMB.SMB_COM_TRANSACTION: self.__smbCommandsHandler.smbTransaction, # Not needed for now smb.SMB.SMB_COM_NT_TRANSACT: self.__smbCommandsHandler.smbNTTransact, smb.SMB.SMB_COM_QUERY_INFORMATION_DISK: self.__smbCommandsHandler.smbQueryInformationDisk, smb.SMB.SMB_COM_OPEN_ANDX: self.__smbCommandsHandler.smbComOpenAndX, smb.SMB.SMB_COM_QUERY_INFORMATION2: self.__smbCommandsHandler.smbComQueryInformation2, smb.SMB.SMB_COM_READ_ANDX: self.__smbCommandsHandler.smbComReadAndX, smb.SMB.SMB_COM_READ: self.__smbCommandsHandler.smbComRead, smb.SMB.SMB_COM_WRITE_ANDX: self.__smbCommandsHandler.smbComWriteAndX, smb.SMB.SMB_COM_WRITE: self.__smbCommandsHandler.smbComWrite, smb.SMB.SMB_COM_CLOSE: self.__smbCommandsHandler.smbComClose, smb.SMB.SMB_COM_LOCKING_ANDX: self.__smbCommandsHandler.smbComLockingAndX, smb.SMB.SMB_COM_NT_CREATE_ANDX: self.__smbCommandsHandler.smbComNtCreateAndX, 0xFF: self.__smbCommandsHandler.default } self.__smb2Ioctls = { smb2.FSCTL_DFS_GET_REFERRALS: self.__IoctlHandler.fsctlDfsGetReferrals, # smb2.FSCTL_PIPE_PEEK: self.__IoctlHandler.fsctlPipePeek, # smb2.FSCTL_PIPE_WAIT: self.__IoctlHandler.fsctlPipeWait, smb2.FSCTL_PIPE_TRANSCEIVE: self.__IoctlHandler.fsctlPipeTransceive, # smb2.FSCTL_SRV_COPYCHUNK: self.__IoctlHandler.fsctlSrvCopyChunk, # smb2.FSCTL_SRV_ENUMERATE_SNAPSHOTS: self.__IoctlHandler.fsctlSrvEnumerateSnapshots, # smb2.FSCTL_SRV_REQUEST_RESUME_KEY: self.__IoctlHandler.fsctlSrvRequestResumeKey, # smb2.FSCTL_SRV_READ_HASH: self.__IoctlHandler.fsctlSrvReadHash, # smb2.FSCTL_SRV_COPYCHUNK_WRITE: self.__IoctlHandler.fsctlSrvCopyChunkWrite, # smb2.FSCTL_LMR_REQUEST_RESILIENCY: self.__IoctlHandler.fsctlLmrRequestResiliency, # smb2.FSCTL_QUERY_NETWORK_INTERFACE_INFO: self.__IoctlHandler.fsctlQueryNetworkInterfaceInfo, # smb2.FSCTL_SET_REPARSE_POINT: self.__IoctlHandler.fsctlSetReparsePoint, # smb2.FSCTL_DFS_GET_REFERRALS_EX: self.__IoctlHandler.fsctlDfsGetReferralsEx, # smb2.FSCTL_FILE_LEVEL_TRIM: self.__IoctlHandler.fsctlFileLevelTrim, smb2.FSCTL_VALIDATE_NEGOTIATE_INFO: self.__IoctlHandler.fsctlValidateNegotiateInfo, } self.__smb2Commands = { smb2.SMB2_NEGOTIATE: self.__smb2CommandsHandler.smb2Negotiate, smb2.SMB2_SESSION_SETUP: self.__smb2CommandsHandler.smb2SessionSetup, smb2.SMB2_LOGOFF: self.__smb2CommandsHandler.smb2Logoff, smb2.SMB2_TREE_CONNECT: self.__smb2CommandsHandler.smb2TreeConnect, smb2.SMB2_TREE_DISCONNECT: self.__smb2CommandsHandler.smb2TreeDisconnect, smb2.SMB2_CREATE: self.__smb2CommandsHandler.smb2Create, smb2.SMB2_CLOSE: self.__smb2CommandsHandler.smb2Close, smb2.SMB2_FLUSH: self.__smb2CommandsHandler.smb2Flush, smb2.SMB2_READ: self.__smb2CommandsHandler.smb2Read, smb2.SMB2_WRITE: self.__smb2CommandsHandler.smb2Write, smb2.SMB2_LOCK: self.__smb2CommandsHandler.smb2Lock, smb2.SMB2_IOCTL: self.__smb2CommandsHandler.smb2Ioctl, smb2.SMB2_CANCEL: self.__smb2CommandsHandler.smb2Cancel, smb2.SMB2_ECHO: self.__smb2CommandsHandler.smb2Echo, smb2.SMB2_QUERY_DIRECTORY: self.__smb2CommandsHandler.smb2QueryDirectory, smb2.SMB2_CHANGE_NOTIFY: self.__smb2CommandsHandler.smb2ChangeNotify, smb2.SMB2_QUERY_INFO: self.__smb2CommandsHandler.smb2QueryInfo, smb2.SMB2_SET_INFO: self.__smb2CommandsHandler.smb2SetInfo, # smb2.SMB2_OPLOCK_BREAK: self.__smb2CommandsHandler.smb2SessionSetup, 0xFF: self.__smb2CommandsHandler.default } # List of active connections self.__activeConnections = {} def getIoctls(self): return self.__smb2Ioctls def getCredentials(self): return self.__credentials def removeConnection(self, name): try: del(self.__activeConnections[name]) except: pass self.log("Remaining connections %s" % self.__activeConnections.keys()) def addConnection(self, name, ip, port): self.__activeConnections[name] = {} # Let's init with some know stuff we will need to have # TODO: Document what's in there #print "Current Connections", self.__activeConnections.keys() self.__activeConnections[name]['PacketNum'] = 0 self.__activeConnections[name]['ClientIP'] = ip self.__activeConnections[name]['ClientPort'] = port self.__activeConnections[name]['Uid'] = 0 self.__activeConnections[name]['ConnectedShares'] = {} self.__activeConnections[name]['OpenedFiles'] = {} # SID results for findfirst2 self.__activeConnections[name]['SIDs'] = {} self.__activeConnections[name]['LastRequest'] = {} def getActiveConnections(self): return self.__activeConnections def setConnectionData(self, connId, data): self.__activeConnections[connId] = data #print "setConnectionData" #print self.__activeConnections def getConnectionData(self, connId, checkStatus = True): conn = self.__activeConnections[connId] if checkStatus is True: if ('Authenticated' in conn) is not True: # Can't keep going further raise Exception("User not Authenticated!") return conn def getRegisteredNamedPipes(self): return self.__registeredNamedPipes def registerNamedPipe(self, pipeName, address): self.__registeredNamedPipes[unicode(pipeName)] = address return True def unregisterNamedPipe(self, pipeName): if pipeName in self.__registeredNamedPipes: del(self.__registeredNamedPipes[unicode(pipeName)]) return True return False def unregisterTransaction(self, transCommand): if transCommand in self.__smbTransCommands: del(self.__smbTransCommands[transCommand]) def hookTransaction(self, transCommand, callback): # If you call this function, callback will replace # the current Transaction sub command. # (don't get confused with the Transaction smbCommand) # If the transaction sub command doesn't not exist, it is added # If the transaction sub command exists, it returns the original function # replaced # # callback MUST be declared as: # callback(connId, smbServer, recvPacket, parameters, data, maxDataCount=0) # # WHERE: # # connId : the connection Id, used to grab/update information about # the current connection # smbServer : the SMBServer instance available for you to ask # configuration data # recvPacket : the full SMBPacket that triggered this command # parameters : the transaction parameters # data : the transaction data # maxDataCount: the max amount of data that can be transfered agreed # with the client # # and MUST return: # respSetup, respParameters, respData, errorCode # # WHERE: # # respSetup: the setup response of the transaction # respParameters: the parameters response of the transaction # respData: the data reponse of the transaction # errorCode: the NT error code if transCommand in self.__smbTransCommands: originalCommand = self.__smbTransCommands[transCommand] else: originalCommand = None self.__smbTransCommands[transCommand] = callback return originalCommand def unregisterTransaction2(self, transCommand): if transCommand in self.__smbTrans2Commands: del(self.__smbTrans2Commands[transCommand]) def hookTransaction2(self, transCommand, callback): # Here we should add to __smbTrans2Commands # Same description as Transaction if transCommand in self.__smbTrans2Commands: originalCommand = self.__smbTrans2Commands[transCommand] else: originalCommand = None self.__smbTrans2Commands[transCommand] = callback return originalCommand def unregisterNTTransaction(self, transCommand): if transCommand in self.__smbNTTransCommands: del(self.__smbNTTransCommands[transCommand]) def hookNTTransaction(self, transCommand, callback): # Here we should add to __smbNTTransCommands # Same description as Transaction if transCommand in self.__smbNTTransCommands: originalCommand = self.__smbNTTransCommands[transCommand] else: originalCommand = None self.__smbNTTransCommands[transCommand] = callback return originalCommand def unregisterSmbCommand(self, smbCommand): if smbCommand in self.__smbCommands: del(self.__smbCommands[smbCommand]) def hookSmbCommand(self, smbCommand, callback): # Here we should add to self.__smbCommands # If you call this function, callback will replace # the current smbCommand. # If smbCommand doesn't not exist, it is added # If SMB command exists, it returns the original function replaced # # callback MUST be declared as: # callback(connId, smbServer, SMBCommand, recvPacket) # # WHERE: # # connId : the connection Id, used to grab/update information about # the current connection # smbServer : the SMBServer instance available for you to ask # configuration data # SMBCommand: the SMBCommand itself, with its data and parameters. # Check smb.py:SMBCommand() for a reference # recvPacket: the full SMBPacket that triggered this command # # and MUST return: # , , errorCode # has higher preference over commands, in case you # want to change the whole packet # errorCode: the NT error code # # For SMB_COM_TRANSACTION2, SMB_COM_TRANSACTION and SMB_COM_NT_TRANSACT # the callback function is slightly different: # # callback(connId, smbServer, SMBCommand, recvPacket, transCommands) # # WHERE: # # transCommands: a list of transaction subcommands already registered # if smbCommand in self.__smbCommands: originalCommand = self.__smbCommands[smbCommand] else: originalCommand = None self.__smbCommands[smbCommand] = callback return originalCommand def unregisterSmb2Command(self, smb2Command): if smb2Command in self.__smb2Commands: del(self.__smb2Commands[smb2Command]) def hookSmb2Command(self, smb2Command, callback): if smb2Command in self.__smb2Commands: originalCommand = self.__smb2Commands[smb2Command] else: originalCommand = None self.__smb2Commands[smb2Command] = callback return originalCommand def log(self, msg, level=logging.INFO): self.__log.log(level,msg) def getServerName(self): return self.__serverName def getServerOS(self): return self.__serverOS def getServerDomain(self): return self.__serverDomain def getSMBChallenge(self): return self.__challenge def getServerConfig(self): return self.__serverConfig def setServerConfig(self, config): self.__serverConfig = config def getJTRdumpPath(self): return self.__jtr_dump_path def verify_request(self, request, client_address): # TODO: Control here the max amount of processes we want to launch # returning False, closes the connection return True def processRequest(self, connId, data): # TODO: Process batched commands. isSMB2 = False SMBCommand = None try: packet = smb.NewSMBPacket(data = data) SMBCommand = smb.SMBCommand(packet['Data'][0]) except: # Maybe a SMB2 packet? packet = smb2.SMB2Packet(data = data) isSMB2 = True # We might have compound requests compoundedPacketsResponse = [] compoundedPackets = [] try: # Search out list of implemented commands # We provide them with: # connId : representing the data for this specific connection # self : the SMBSERVER if they want to ask data to it # SMBCommand : the SMBCommand they are expecting to process # packet : the received packet itself, in case they need more data than the actual command # Only for Transactions # transCommand: a list of transaction subcommands # We expect to get: # respCommands: a list of answers for the commands processed # respPacket : if the commands chose to directly craft packet/s, we use this and not the previous # this MUST be a list # errorCode : self explanatory if isSMB2 is False: if packet['Command'] == smb.SMB.SMB_COM_TRANSACTION2: respCommands, respPackets, errorCode = self.__smbCommands[packet['Command']]( connId, self, SMBCommand, packet, self.__smbTrans2Commands) elif packet['Command'] == smb.SMB.SMB_COM_NT_TRANSACT: respCommands, respPackets, errorCode = self.__smbCommands[packet['Command']]( connId, self, SMBCommand, packet, self.__smbNTTransCommands) elif packet['Command'] == smb.SMB.SMB_COM_TRANSACTION: respCommands, respPackets, errorCode = self.__smbCommands[packet['Command']]( connId, self, SMBCommand, packet, self.__smbTransCommands) else: if packet['Command'] in self.__smbCommands: if self.__SMB2Support is True: if packet['Command'] == smb.SMB.SMB_COM_NEGOTIATE: try: respCommands, respPackets, errorCode = self.__smb2Commands[smb2.SMB2_NEGOTIATE](connId, self, packet, True) isSMB2 = True except Exception as e: self.log('SMB2_NEGOTIATE: %s' % e, logging.ERROR) # If something went wrong, let's fallback to SMB1 respCommands, respPackets, errorCode = self.__smbCommands[packet['Command']]( connId, self, SMBCommand, packet) #self.__SMB2Support = False pass else: respCommands, respPackets, errorCode = self.__smbCommands[packet['Command']]( connId, self, SMBCommand, packet) else: respCommands, respPackets, errorCode = self.__smbCommands[packet['Command']]( connId, self, SMBCommand, packet) else: respCommands, respPackets, errorCode = self.__smbCommands[255](connId, self, SMBCommand, packet) compoundedPacketsResponse.append((respCommands, respPackets, errorCode)) compoundedPackets.append(packet) else: done = False while not done: if packet['Command'] in self.__smb2Commands: if self.__SMB2Support is True: respCommands, respPackets, errorCode = self.__smb2Commands[packet['Command']]( connId, self, packet) else: respCommands, respPackets, errorCode = self.__smb2Commands[255](connId, self, packet) else: respCommands, respPackets, errorCode = self.__smb2Commands[255](connId, self, packet) # Let's store the result for this compounded packet compoundedPacketsResponse.append((respCommands, respPackets, errorCode)) compoundedPackets.append(packet) if packet['NextCommand'] != 0: data = data[packet['NextCommand']:] packet = smb2.SMB2Packet(data = data) else: done = True except Exception as e: #import traceback #traceback.print_exc() # Something wen't wrong, defaulting to Bad user ID self.log('processRequest (0x%x,%s)' % (packet['Command'],e), logging.ERROR) raise # We prepare the response packet to commands don't need to bother about that. connData = self.getConnectionData(connId, False) # Force reconnection loop.. This is just a test.. client will send me back credentials :) #connData['PacketNum'] += 1 #if connData['PacketNum'] == 15: # connData['PacketNum'] = 0 # # Something wen't wrong, defaulting to Bad user ID # self.log('Sending BAD USER ID!', logging.ERROR) # #raise # packet['Flags1'] |= smb.SMB.FLAGS1_REPLY # packet['Flags2'] = 0 # errorCode = STATUS_SMB_BAD_UID # packet['ErrorCode'] = errorCode >> 16 # packet['ErrorClass'] = errorCode & 0xff # return [packet] self.setConnectionData(connId, connData) packetsToSend = [] for packetNum in range(len(compoundedPacketsResponse)): respCommands, respPackets, errorCode = compoundedPacketsResponse[packetNum] packet = compoundedPackets[packetNum] if respPackets is None: for respCommand in respCommands: if isSMB2 is False: respPacket = smb.NewSMBPacket() respPacket['Flags1'] = smb.SMB.FLAGS1_REPLY # TODO this should come from a per session configuration respPacket['Flags2'] = smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | packet['Flags2'] & smb.SMB.FLAGS2_UNICODE #respPacket['Flags2'] = smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES #respPacket['Flags1'] = 0x98 #respPacket['Flags2'] = 0xc807 respPacket['Tid'] = packet['Tid'] respPacket['Mid'] = packet['Mid'] respPacket['Pid'] = packet['Pid'] respPacket['Uid'] = connData['Uid'] respPacket['ErrorCode'] = errorCode >> 16 respPacket['_reserved'] = errorCode >> 8 & 0xff respPacket['ErrorClass'] = errorCode & 0xff respPacket.addCommand(respCommand) packetsToSend.append(respPacket) else: respPacket = smb2.SMB2Packet() respPacket['Flags'] = smb2.SMB2_FLAGS_SERVER_TO_REDIR if packetNum > 0: respPacket['Flags'] |= smb2.SMB2_FLAGS_RELATED_OPERATIONS respPacket['Status'] = errorCode respPacket['CreditRequestResponse'] = packet['CreditRequestResponse'] respPacket['Command'] = packet['Command'] respPacket['CreditCharge'] = packet['CreditCharge'] #respPacket['CreditCharge'] = 0 respPacket['Reserved'] = packet['Reserved'] respPacket['SessionID'] = connData['Uid'] respPacket['MessageID'] = packet['MessageID'] respPacket['TreeID'] = packet['TreeID'] respPacket['Data'] = str(respCommand) packetsToSend.append(respPacket) else: # The SMBCommand took care of building the packet packetsToSend = respPackets if isSMB2 is True: # Let's build a compound answer finalData = '' i = 0 for i in range(len(packetsToSend)-1): packet = packetsToSend[i] # Align to 8-bytes padLen = (8 - (len(packet) % 8) ) % 8 packet['NextCommand'] = len(packet) + padLen finalData += str(packet) + padLen*'\x00' # Last one finalData += str(packetsToSend[len(packetsToSend)-1]) packetsToSend = [finalData] # We clear the compound requests connData['LastRequest'] = {} return packetsToSend def processConfigFile(self, configFile = None): # TODO: Do a real config parser if self.__serverConfig is None: if configFile is None: configFile = 'smb.conf' self.__serverConfig = configparser.ConfigParser() self.__serverConfig.read(configFile) self.__serverName = self.__serverConfig.get('global','server_name') self.__serverOS = self.__serverConfig.get('global','server_os') self.__serverDomain = self.__serverConfig.get('global','server_domain') self.__logFile = self.__serverConfig.get('global','log_file') if self.__serverConfig.has_option('global', 'challenge'): self.__challenge = self.__serverConfig.get('global', 'challenge') else: self.__challenge = 'A'*8 if self.__serverConfig.has_option("global", "jtr_dump_path"): self.__jtr_dump_path = self.__serverConfig.get("global", "jtr_dump_path") if self.__serverConfig.has_option("global", "SMB2Support"): self.__SMB2Support = self.__serverConfig.getboolean("global","SMB2Support") else: self.__SMB2Support = False if self.__logFile != 'None': logging.basicConfig(filename = self.__logFile, level = logging.DEBUG, format="%(asctime)s: %(levelname)s: %(message)s", datefmt = '%m/%d/%Y %I:%M:%S %p') self.__log = LOG # Process the credentials credentials_fname = self.__serverConfig.get('global','credentials_file') if credentials_fname is not "": cred = open(credentials_fname) line = cred.readline() while line: name, domain, lmhash, nthash = line.split(':') self.__credentials[name] = (domain, lmhash, nthash.strip('\r\n')) line = cred.readline() cred.close() self.log('Config file parsed') # For windows platforms, opening a directory is not an option, so we set a void FD VOID_FILE_DESCRIPTOR = -1 PIPE_FILE_DESCRIPTOR = -2