diff options
Diffstat (limited to 'tests/python_dependencies/impacket/smbserver.py')
-rw-r--r-- | tests/python_dependencies/impacket/smbserver.py | 4416 |
1 files changed, 4416 insertions, 0 deletions
diff --git a/tests/python_dependencies/impacket/smbserver.py b/tests/python_dependencies/impacket/smbserver.py new file mode 100644 index 000000000..4baebbb20 --- /dev/null +++ b/tests/python_dependencies/impacket/smbserver.py @@ -0,0 +1,4416 @@ +# 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 ConfigParser +import SocketServer +import threading +import logging +import logging.config +import ntpath +import os +import fnmatch +import errno +import sys +import random +import shutil +import string +from binascii import unhexlify, 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 + +# 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, 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, 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, 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('<H',parameters[:2])[0] == 0: + # NetShareEnum Request + netShareEnum = smb.SMBNetShareEnum(parameters) + if netShareEnum['InfoLevel'] == 1: + shares = getShares(connId, smbServer) + respParameters = smb.SMBNetShareEnumResponse() + respParameters['EntriesReturned'] = len(shares) + respParameters['EntriesAvailable'] = len(shares) + tailData = '' + for i in shares: + # NetShareInfo1 len == 20 + entry = smb.NetShareInfo1() + entry['NetworkName'] = i + '\x00'*(13-len(i)) + entry['Type'] = int(shares[i]['share type']) + # (beto) If offset == 0 it crashes explorer.exe on windows 7 + entry['RemarkOffsetLow'] = 20 * len(shares) + len(tailData) + respData += entry.getData() + if shares[i].has_key('comment'): + tailData += shares[i]['comment'] + '\x00' + else: + tailData += '\x00' + respData += tailData + else: + # We don't support other info levels + errorCode = STATUS_NOT_SUPPORTED + elif struct.unpack('<H',parameters[:2])[0] == 13: + # NetrServerGetInfo Request + respParameters = smb.SMBNetServerGetInfoResponse() + netServerInfo = smb.SMBNetServerInfo1() + netServerInfo['ServerName'] = smbServer.getServerName() + respData = str(netServerInfo) + respParameters['TotalBytesAvailable'] = len(respData) + elif struct.unpack('<H',parameters[:2])[0] == 1: + # NetrShareGetInfo Request + request = smb.SMBNetShareGetInfo(parameters) + respParameters = smb.SMBNetShareGetInfoResponse() + shares = getShares(connId, smbServer) + share = shares[request['ShareName'].upper()] + shareInfo = smb.NetShareInfo1() + shareInfo['NetworkName'] = request['ShareName'].upper() + '\x00' + shareInfo['Type'] = int(share['share type']) + respData = shareInfo.getData() + if share.has_key('comment'): + shareInfo['RemarkOffsetLow'] = len(respData) + respData += share['comment'] + '\x00' + respParameters['TotalBytesAvailable'] = len(respData) + + else: + # We don't know how to handle anything else + errorCode = STATUS_NOT_SUPPORTED + + smbServer.setConnectionData(connId, connData) + + return respSetup, respParameters, respData, errorCode + + @staticmethod + def transactNamedPipe(connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): + connData = smbServer.getConnectionData(connId) + + respSetup = '' + respParameters = '' + respData = '' + errorCode = STATUS_SUCCESS + SMBCommand = smb.SMBCommand(recvPacket['Data'][0]) + transParameters= smb.SMBTransaction_Parameters(SMBCommand['Parameters']) + + # Extract the FID + fid = struct.unpack('<H', transParameters['Setup'][2:])[0] + + if connData['OpenedFiles'].has_key(fid): + fileHandle = connData['OpenedFiles'][fid]['FileHandle'] + if fileHandle != PIPE_FILE_DESCRIPTOR: + os.write(fileHandle,data) + respData = os.read(fileHandle,data) + else: + sock = connData['OpenedFiles'][fid]['Socket'] + sock.send(data) + respData = sock.recv(maxDataCount) + else: + errorCode = STATUS_INVALID_HANDLE + + smbServer.setConnectionData(connId, connData) + + return respSetup, respParameters, respData, errorCode + +# Here we implement the transaction2 handlers +class TRANS2Commands: + # All these commands return setup, parameters, data, errorCode + @staticmethod + def setPathInformation(connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): + connData = smbServer.getConnectionData(connId) + + respSetup = '' + respParameters = '' + respData = '' + errorCode = STATUS_SUCCESS + setPathInfoParameters = smb.SMBSetPathInformation_Parameters(flags = recvPacket['Flags2'], data = parameters) + if connData['ConnectedShares'].has_key(recvPacket['Tid']): + path = connData['ConnectedShares'][recvPacket['Tid']]['path'] + fileName = decodeSMBString(recvPacket['Flags2'], setPathInfoParameters['FileName']) + 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): + 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + if connData['OpenedFiles'].has_key(setFileInfoParameters['FID']): + 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + if connData['OpenedFiles'].has_key(queryFileInfoParameters['FID']): + 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + path = connData['ConnectedShares'][recvPacket['Tid']]['path'] + try: + infoRecord, errorCode = queryPathInformation(path, decodeSMBString(recvPacket['Flags2'], queryPathInfoParameters['FileName']), queryPathInfoParameters['InformationLevel']) + except Exception, 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + data = queryFsInformation(connData['ConnectedShares'][recvPacket['Tid']]['path'], '', struct.unpack('<H',parameters)[0]) + + smbServer.setConnectionData(connId, connData) + + return '','', data, errorCode + + @staticmethod + def findNext2(connId, smbServer, recvPacket, parameters, data, maxDataCount): + connData = smbServer.getConnectionData(connId) + + respSetup = '' + respParameters = '' + respData = '' + errorCode = STATUS_SUCCESS + findNext2Parameters = smb.SMBFindNext2_Parameters(flags = recvPacket['Flags2'], data = parameters) + + sid = findNext2Parameters['SID'] + if connData['ConnectedShares'].has_key(recvPacket['Tid']): + if connData['SIDs'].has_key(sid): + searchResult = connData['SIDs'][sid] + respParameters = smb.SMBFindNext2Response_Parameters() + endOfSearch = 1 + searchCount = 1 + totalData = 0 + for i in enumerate(searchResult): + data = i[1].getData() + lenData = len(data) + if (totalData+lenData) >= 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + 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('<H', transParameters['Setup'][:2])[0] + + if transCommands.has_key(command): + # Call the TRANS subcommand + setup = '' + parameters = '' + data = '' + try: + setup, parameters, data, errorCode = transCommands[command](connId, + smbServer, + recvPacket, + transData['Trans_Parameters'], + transData['Trans_Data'], + transParameters['MaxDataCount']) + except Exception, e: + #print 'Transaction: %s' % e,e + smbServer.log('Transaction: (%r,%s)' % (command, e), logging.ERROR) + errorCode = STATUS_ACCESS_DENIED + #raise + + if setup == '' and parameters == '' and data == '': + # Something wen't wrong + respParameters = '' + respData = '' + 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.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 transCommands.has_key(command): + # 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, 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('<H', trans2Parameters['Setup'])[0] + if transCommands.has_key(command): + # Call the TRANS2 subcommand + try: + setup, parameters, data, errorCode = transCommands[command](connId, + smbServer, + recvPacket, + trans2Data['Trans_Parameters'], + trans2Data['Trans_Data'], + trans2Parameters['MaxDataCount']) + except Exception, e: + smbServer.log('Transaction2: (0x%x,%s)' % (command, e), logging.ERROR) + #import traceback + #traceback.print_exc() + raise + + if setup == '' and parameters == '' and data == '': + # Something wen't wrong + respParameters = '' + respData = '' + 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.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 connData['OpenedFiles'].has_key(comClose['FID']): + 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, 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, 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 connData['OpenedFiles'].has_key(comWriteParameters['Fid']): + 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, 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 connData['OpenedFiles'].has_key(comFlush['FID']): + errorCode = STATUS_SUCCESS + fileHandle = connData['OpenedFiles'][comFlush['FID']]['FileHandle'] + try: + os.fsync(fileHandle) + except Exception, 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + 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, 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + 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, 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + 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, 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + 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, 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 connData['OpenedFiles'].has_key(writeAndX['Fid']): + fileHandle = connData['OpenedFiles'][writeAndX['Fid']]['FileHandle'] + errorCode = STATUS_SUCCESS + try: + if fileHandle != PIPE_FILE_DESCRIPTOR: + offset = writeAndX['Offset'] + if writeAndX.fields.has_key('HighOffset'): + 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, 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 connData['OpenedFiles'].has_key(comReadParameters['Fid']): + 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, 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 connData['OpenedFiles'].has_key(readAndX['Fid']): + fileHandle = connData['OpenedFiles'][readAndX['Fid']]['FileHandle'] + errorCode = 0 + try: + if fileHandle != PIPE_FILE_DESCRIPTOR: + offset = readAndX['Offset'] + if readAndX.fields.has_key('HighOffset'): + 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, 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + 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 connData['OpenedFiles'].has_key(queryInformation2['Fid']): + 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, 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + # 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 connData['ConnectedShares'][recvPacket['Tid']].has_key('path'): + 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 smbServer.getRegisteredNamedPipes().has_key(unicode(pathName)) 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, 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 smbServer.getRegisteredNamedPipes().has_key(unicode(pathName)): + fid = PIPE_FILE_DESCRIPTOR + sock = socket.socket() + sock.connect(smbServer.getRegisteredNamedPipes()[unicode(pathName)]) + else: + fid = os.open(pathName, mode) + except Exception, 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 connData['ConnectedShares'].has_key(recvPacket['Tid']): + 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 MechTypes.has_key(mechType): + 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('<L',token[len('NTLMSSP\x00'):len('NTLMSSP\x00')+4])[0] + + if messageType == 0x01: + # NEGOTIATE_MESSAGE + negotiateMessage = ntlm.NTLMAuthNegotiate() + negotiateMessage.fromString(token) + # Let's store it in the connection data + connData['NEGOTIATE_MESSAGE'] = negotiateMessage + # Let's build the answer flags + # TODO: Parse all the flags. With this we're leaving some clients out + + ansFlags = 0 + + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_56: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_56 + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_128: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_128 + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_UNICODE + if negotiateMessage['flags'] & ntlm.NTLM_NEGOTIATE_OEM: + ansFlags |= ntlm.NTLM_NEGOTIATE_OEM + + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_VERSION | ntlm.NTLMSSP_NEGOTIATE_TARGET_INFO | ntlm.NTLMSSP_TARGET_TYPE_SERVER | ntlm.NTLMSSP_NEGOTIATE_NTLM | ntlm.NTLMSSP_REQUEST_TARGET + + # Generate the AV_PAIRS + av_pairs = ntlm.AV_PAIRS() + # TODO: Put the proper data from SMBSERVER config + av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] = av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME] = smbServer.getServerName().encode('utf-16le') + av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] = smbServer.getServerDomain().encode('utf-16le') + av_pairs[ntlm.NTLMSSP_AV_TIME] = struct.pack('<q', (116444736000000000 + calendar.timegm(time.gmtime()) * 10000000) ) + + challengeMessage = ntlm.NTLMAuthChallenge() + challengeMessage['flags'] = ansFlags + challengeMessage['domain_len'] = len(smbServer.getServerDomain().encode('utf-16le')) + challengeMessage['domain_max_len'] = challengeMessage['domain_len'] + challengeMessage['domain_offset'] = 40 + 16 + challengeMessage['challenge'] = smbServer.getSMBChallenge() + challengeMessage['domain_name'] = smbServer.getServerDomain().encode('utf-16le') + challengeMessage['TargetInfoFields_len'] = len(av_pairs) + challengeMessage['TargetInfoFields_max_len'] = len(av_pairs) + challengeMessage['TargetInfoFields'] = av_pairs + challengeMessage['TargetInfoFields_offset'] = 40 + 16 + len(challengeMessage['domain_name']) + challengeMessage['Version'] = '\xff'*8 + challengeMessage['VersionLen'] = 8 + + if rawNTLM is False: + respToken = SPNEGO_NegTokenResp() + # accept-incomplete. We want more data + respToken['NegResult'] = '\x01' + respToken['SupportedMech'] = TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'] + + respToken['ResponseToken'] = challengeMessage.getData() + else: + respToken = challengeMessage + + # Setting the packet to STATUS_MORE_PROCESSING + errorCode = STATUS_MORE_PROCESSING_REQUIRED + # Let's set up an UID for this connection and store it + # in the connection's data + # Picking a fixed value + # TODO: Manage more UIDs for the same session + connData['Uid'] = 10 + # Let's store it in the connection data + connData['CHALLENGE_MESSAGE'] = challengeMessage + + elif messageType == 0x02: + # CHALLENGE_MESSAGE + raise Exception('Challenge Message raise, not implemented!') + elif messageType == 0x03: + # AUTHENTICATE_MESSAGE, here we deal with authentication + authenticateMessage = ntlm.NTLMAuthChallengeResponse() + authenticateMessage.fromString(token) + smbServer.log("AUTHENTICATE_MESSAGE (%s\\%s,%s)" % (authenticateMessage['domain_name'], authenticateMessage['user_name'], authenticateMessage['host_name'])) + # TODO: Check the credentials! Now granting permissions + + respToken = SPNEGO_NegTokenResp() + # accept-completed + respToken['NegResult'] = '\x00' + + # Status SUCCESS + errorCode = STATUS_SUCCESS + smbServer.log('User %s\\%s authenticated successfully' % (authenticateMessage['user_name'], authenticateMessage['host_name'])) + # Let's store it in the connection data + connData['AUTHENTICATE_MESSAGE'] = authenticateMessage + try: + jtr_dump_path = smbServer.getJTRdumpPath() + ntlm_hash_data = outputToJohnFormat( connData['CHALLENGE_MESSAGE']['challenge'], authenticateMessage['user_name'], authenticateMessage['domain_name'], authenticateMessage['lanman'], authenticateMessage['ntlm'] ) + smbServer.log(ntlm_hash_data['hash_string']) + if jtr_dump_path is not '': + writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'], jtr_dump_path) + except: + smbServer.log("Could not write NTLM Hashes to the specified JTR_Dump_Path %s" % jtr_dump_path) + else: + raise Exception("Unknown NTLMSSP MessageType %d" % messageType) + + respParameters['SecurityBlobLength'] = len(respToken) + respData['SecurityBlobLength'] = respParameters['SecurityBlobLength'] + respData['SecurityBlob'] = respToken.getData() + + else: + # Process Standard Security + respParameters = smb.SMBSessionSetupAndXResponse_Parameters() + respData = smb.SMBSessionSetupAndXResponse_Data() + sessionSetupParameters = smb.SMBSessionSetupAndX_Parameters(SMBCommand['Parameters']) + sessionSetupData = smb.SMBSessionSetupAndX_Data() + sessionSetupData['AnsiPwdLength'] = sessionSetupParameters['AnsiPwdLength'] + sessionSetupData['UnicodePwdLength'] = sessionSetupParameters['UnicodePwdLength'] + sessionSetupData.fromString(SMBCommand['Data']) + connData['Capabilities'] = sessionSetupParameters['Capabilities'] + # Do the verification here, for just now we grant access + # TODO: Manage more UIDs for the same session + errorCode = STATUS_SUCCESS + connData['Uid'] = 10 + respParameters['Action'] = 0 + smbServer.log('User %s\\%s authenticated successfully (basic)' % (sessionSetupData['PrimaryDomain'], sessionSetupData['Account'])) + try: + jtr_dump_path = smbServer.getJTRdumpPath() + ntlm_hash_data = outputToJohnFormat( '', sessionSetupData['Account'], sessionSetupData['PrimaryDomain'], sessionSetupData['AnsiPwd'], sessionSetupData['UnicodePwd'] ) + smbServer.log(ntlm_hash_data['hash_string']) + if jtr_dump_path is not '': + writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'], jtr_dump_path) + except: + smbServer.log("Could not write NTLM Hashes to the specified JTR_Dump_Path %s" % jtr_dump_path) + + respData['NativeOS'] = encodeSMBString(recvPacket['Flags2'], smbServer.getServerOS()) + respData['NativeLanMan'] = encodeSMBString(recvPacket['Flags2'], smbServer.getServerOS()) + respSMBCommand['Parameters'] = respParameters + respSMBCommand['Data'] = respData + + # From now on, the client can ask for other commands + connData['Authenticated'] = True + # For now, just switching to nobody + #os.setregid(65534,65534) + #os.setreuid(65534,65534) + smbServer.setConnectionData(connId, connData) + + return [respSMBCommand], None, errorCode + + @staticmethod + def smbComNegotiate(connId, smbServer, SMBCommand, recvPacket ): + connData = smbServer.getConnectionData(connId, checkStatus = False) + connData['Pid'] = recvPacket['Pid'] + + SMBCommand = smb.SMBCommand(recvPacket['Data'][0]) + respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_NEGOTIATE) + + resp = smb.NewSMBPacket() + resp['Flags1'] = smb.SMB.FLAGS1_REPLY + resp['Pid'] = connData['Pid'] + resp['Tid'] = recvPacket['Tid'] + resp['Mid'] = recvPacket['Mid'] + + # TODO: We support more dialects, and parse them accordingly + dialects = SMBCommand['Data'].split('\x02') + try: + index = dialects.index('NT LM 0.12\x00') - 1 + # Let's fill the data for NTLM + if recvPacket['Flags2'] & smb.SMB.FLAGS2_EXTENDED_SECURITY: + resp['Flags2'] = smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_UNICODE + #resp['Flags2'] = smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS + _dialects_data = smb.SMBExtended_Security_Data() + _dialects_data['ServerGUID'] = 'A'*16 + blob = SPNEGO_NegTokenInit() + blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']] + _dialects_data['SecurityBlob'] = blob.getData() + + _dialects_parameters = smb.SMBExtended_Security_Parameters() + _dialects_parameters['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY | smb.SMB.CAP_USE_NT_ERRORS | smb.SMB.CAP_NT_SMBS | smb.SMB.CAP_UNICODE + _dialects_parameters['ChallengeLength'] = 0 + + else: + resp['Flags2'] = smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_UNICODE + _dialects_parameters = smb.SMBNTLMDialect_Parameters() + _dialects_data= smb.SMBNTLMDialect_Data() + _dialects_data['Payload'] = '' + if connData.has_key('EncryptionKey'): + _dialects_data['Challenge'] = connData['EncryptionKey'] + _dialects_parameters['ChallengeLength'] = len(str(_dialects_data)) + else: + # TODO: Handle random challenges, now one that can be used with rainbow tables + _dialects_data['Challenge'] = '\x11\x22\x33\x44\x55\x66\x77\x88' + _dialects_parameters['ChallengeLength'] = 8 + _dialects_parameters['Capabilities'] = smb.SMB.CAP_USE_NT_ERRORS | smb.SMB.CAP_NT_SMBS + + # Let's see if we need to support RPC_REMOTE_APIS + config = smbServer.getServerConfig() + if config.has_option('global','rpc_apis'): + if config.getboolean('global', 'rpc_apis') is True: + _dialects_parameters['Capabilities'] |= smb.SMB.CAP_RPC_REMOTE_APIS + + _dialects_parameters['DialectIndex'] = index + _dialects_parameters['SecurityMode'] = smb.SMB.SECURITY_AUTH_ENCRYPTED | smb.SMB.SECURITY_SHARE_USER + _dialects_parameters['MaxMpxCount'] = 1 + _dialects_parameters['MaxNumberVcs'] = 1 + _dialects_parameters['MaxBufferSize'] = 64000 + _dialects_parameters['MaxRawSize'] = 65536 + _dialects_parameters['SessionKey'] = 0 + _dialects_parameters['LowDateTime'] = 0 + _dialects_parameters['HighDateTime'] = 0 + _dialects_parameters['ServerTimeZone'] = 0 + + + respSMBCommand['Data'] = _dialects_data + respSMBCommand['Parameters'] = _dialects_parameters + connData['_dialects_data'] = _dialects_data + connData['_dialects_parameters'] = _dialects_parameters + + except Exception, e: + # No NTLM throw an error + smbServer.log('smbComNegotiate: %s' % e, logging.ERROR) + respSMBCommand['Data'] = struct.pack('<H',0xffff) + + + smbServer.setConnectionData(connId, connData) + + resp.addCommand(respSMBCommand) + + return None, [resp], STATUS_SUCCESS + + @staticmethod + def default(connId, smbServer, SMBCommand, recvPacket): + # By default we return an SMB Packet with error not implemented + smbServer.log("Not implemented command: 0x%x" % recvPacket['Command'],logging.DEBUG) + packet = smb.NewSMBPacket() + packet['Flags1'] = smb.SMB.FLAGS1_REPLY + packet['Flags2'] = smb.SMB.FLAGS2_NT_STATUS + packet['Command'] = recvPacket['Command'] + packet['Pid'] = recvPacket['Pid'] + packet['Tid'] = recvPacket['Tid'] + packet['Mid'] = recvPacket['Mid'] + packet['Uid'] = recvPacket['Uid'] + packet['Data'] = '\x00\x00\x00' + errorCode = STATUS_NOT_IMPLEMENTED + packet['ErrorCode'] = errorCode >> 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 MechTypes.has_key(mechType): + 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('<L',token[len('NTLMSSP\x00'):len('NTLMSSP\x00')+4])[0] + + if messageType == 0x01: + # NEGOTIATE_MESSAGE + negotiateMessage = ntlm.NTLMAuthNegotiate() + negotiateMessage.fromString(token) + # Let's store it in the connection data + connData['NEGOTIATE_MESSAGE'] = negotiateMessage + # Let's build the answer flags + # TODO: Parse all the flags. With this we're leaving some clients out + + ansFlags = 0 + + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_56: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_56 + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_128: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_128 + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY + if negotiateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE: + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_UNICODE + if negotiateMessage['flags'] & ntlm.NTLM_NEGOTIATE_OEM: + ansFlags |= ntlm.NTLM_NEGOTIATE_OEM + + ansFlags |= ntlm.NTLMSSP_NEGOTIATE_VERSION | ntlm.NTLMSSP_NEGOTIATE_TARGET_INFO | ntlm.NTLMSSP_TARGET_TYPE_SERVER | ntlm.NTLMSSP_NEGOTIATE_NTLM | ntlm.NTLMSSP_REQUEST_TARGET + + # Generate the AV_PAIRS + av_pairs = ntlm.AV_PAIRS() + # TODO: Put the proper data from SMBSERVER config + av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] = av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME] = smbServer.getServerName().encode('utf-16le') + av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] = smbServer.getServerDomain().encode('utf-16le') + av_pairs[ntlm.NTLMSSP_AV_TIME] = struct.pack('<q', (116444736000000000 + calendar.timegm(time.gmtime()) * 10000000) ) + + challengeMessage = ntlm.NTLMAuthChallenge() + challengeMessage['flags'] = ansFlags + challengeMessage['domain_len'] = len(smbServer.getServerDomain().encode('utf-16le')) + challengeMessage['domain_max_len'] = challengeMessage['domain_len'] + challengeMessage['domain_offset'] = 40 + 16 + challengeMessage['challenge'] = smbServer.getSMBChallenge() + challengeMessage['domain_name'] = smbServer.getServerDomain().encode('utf-16le') + challengeMessage['TargetInfoFields_len'] = len(av_pairs) + challengeMessage['TargetInfoFields_max_len'] = len(av_pairs) + challengeMessage['TargetInfoFields'] = av_pairs + challengeMessage['TargetInfoFields_offset'] = 40 + 16 + len(challengeMessage['domain_name']) + challengeMessage['Version'] = '\xff'*8 + challengeMessage['VersionLen'] = 8 + + if rawNTLM is False: + respToken = SPNEGO_NegTokenResp() + # accept-incomplete. We want more data + respToken['NegResult'] = '\x01' + respToken['SupportedMech'] = TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'] + + respToken['ResponseToken'] = challengeMessage.getData() + else: + respToken = challengeMessage + + # Setting the packet to STATUS_MORE_PROCESSING + errorCode = STATUS_MORE_PROCESSING_REQUIRED + # Let's set up an UID for this connection and store it + # in the connection's data + # Picking a fixed value + # TODO: Manage more UIDs for the same session + connData['Uid'] = random.randint(1,0xffffffff) + # Let's store it in the connection data + connData['CHALLENGE_MESSAGE'] = challengeMessage + + elif messageType == 0x02: + # CHALLENGE_MESSAGE + raise Exception('Challenge Message raise, not implemented!') + elif messageType == 0x03: + # AUTHENTICATE_MESSAGE, here we deal with authentication + authenticateMessage = ntlm.NTLMAuthChallengeResponse() + authenticateMessage.fromString(token) + smbServer.log("AUTHENTICATE_MESSAGE (%s\\%s,%s)" % (authenticateMessage['domain_name'], authenticateMessage['user_name'], authenticateMessage['host_name'])) + # TODO: Check the credentials! Now granting permissions + + respToken = SPNEGO_NegTokenResp() + # accept-completed + respToken['NegResult'] = '\x00' + + # Status SUCCESS + errorCode = STATUS_SUCCESS + smbServer.log('User %s\\%s authenticated successfully' % (authenticateMessage['user_name'], authenticateMessage['host_name'])) + # Let's store it in the connection data + connData['AUTHENTICATE_MESSAGE'] = authenticateMessage + try: + jtr_dump_path = smbServer.getJTRdumpPath() + ntlm_hash_data = outputToJohnFormat( connData['CHALLENGE_MESSAGE']['challenge'], authenticateMessage['user_name'], authenticateMessage['domain_name'], authenticateMessage['lanman'], authenticateMessage['ntlm'] ) + smbServer.log(ntlm_hash_data['hash_string']) + if jtr_dump_path is not '': + writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'], jtr_dump_path) + except: + smbServer.log("Could not write NTLM Hashes to the specified JTR_Dump_Path %s" % jtr_dump_path) + respSMBCommand['SessionFlags'] = 1 + else: + raise Exception("Unknown NTLMSSP MessageType %d" % messageType) + + respSMBCommand['SecurityBufferOffset'] = 0x48 + respSMBCommand['SecurityBufferLength'] = len(respToken) + respSMBCommand['Buffer'] = respToken.getData() + + # From now on, the client can ask for other commands + connData['Authenticated'] = True + # For now, just switching to nobody + #os.setregid(65534,65534) + #os.setreuid(65534,65534) + smbServer.setConnectionData(connId, connData) + + return [respSMBCommand], None, errorCode + + @staticmethod + def smb2TreeConnect(connId, smbServer, recvPacket): + connData = smbServer.getConnectionData(connId) + + respPacket = smb2.SMB2Packet() + respPacket['Flags'] = smb2.SMB2_FLAGS_SERVER_TO_REDIR + respPacket['Status'] = STATUS_SUCCESS + respPacket['CreditRequestResponse'] = 1 + respPacket['Command'] = recvPacket['Command'] + respPacket['SessionID'] = connData['Uid'] + respPacket['Reserved'] = recvPacket['Reserved'] + respPacket['MessageID'] = recvPacket['MessageID'] + respPacket['TreeID'] = recvPacket['TreeID'] + + respSMBCommand = smb2.SMB2TreeConnect_Response() + + treeConnectRequest = smb2.SMB2TreeConnect(recvPacket['Data']) + + errorCode = STATUS_SUCCESS + + ## Process here the request, does the share exist? + path = str(recvPacket)[treeConnectRequest['PathOffset']:][:treeConnectRequest['PathLength']] + UNCOrShare = path.decode('utf-16le') + + # Is this a UNC? + if ntpath.ismount(UNCOrShare): + path = UNCOrShare.split('\\')[3] + else: + path = ntpath.basename(UNCOrShare) + + share = searchShare(connId, path.upper(), 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 + respPacket['TreeID'] = tid + smbServer.log("Connecting Share(%d:%s)" % (tid,path)) + else: + smbServer.log("SMB2_TREE_CONNECT not found %s" % path, logging.ERROR) + errorCode = STATUS_OBJECT_PATH_NOT_FOUND + respPacket['Status'] = errorCode + ## + + if path == 'IPC$': + respSMBCommand['ShareType'] = smb2.SMB2_SHARE_TYPE_PIPE + respSMBCommand['ShareFlags'] = 0x30 + else: + respSMBCommand['ShareType'] = smb2.SMB2_SHARE_TYPE_DISK + respSMBCommand['ShareFlags'] = 0x0 + + respSMBCommand['Capabilities'] = 0 + respSMBCommand['MaximalAccess'] = 0x000f01ff + + respPacket['Data'] = respSMBCommand + + smbServer.setConnectionData(connId, connData) + + return None, [respPacket], errorCode + + @staticmethod + def smb2Create(connId, smbServer, recvPacket): + connData = smbServer.getConnectionData(connId) + + respSMBCommand = smb2.SMB2Create_Response() + + ntCreateRequest = smb2.SMB2Create(recvPacket['Data']) + + respSMBCommand['Buffer'] = '\x00' + # Get the Tid associated + if connData['ConnectedShares'].has_key(recvPacket['TreeID']): + # If we have a rootFid, the path is relative to that fid + errorCode = STATUS_SUCCESS + if connData['ConnectedShares'][recvPacket['TreeID']].has_key('path'): + path = connData['ConnectedShares'][recvPacket['TreeID']]['path'] + else: + path = 'NONE' + errorCode = STATUS_ACCESS_DENIED + + deleteOnClose = False + + fileName = os.path.normpath(ntCreateRequest['Buffer'][:ntCreateRequest['NameLength']].decode('utf-16le').replace('\\','/')) + if len(fileName) > 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 smbServer.getRegisteredNamedPipes().has_key(unicode(pathName)) 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, 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 smbServer.getRegisteredNamedPipes().has_key(unicode(pathName)): + fid = PIPE_FILE_DESCRIPTOR + sock = socket.socket() + sock.connect(smbServer.getRegisteredNamedPipes()[unicode(pathName)]) + else: + fid = os.open(pathName, mode) + except Exception, 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 connData['LastRequest'].has_key('SMB2_CREATE'): + fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] + else: + fileID = str(closeRequest['FileID']) + else: + fileID = str(closeRequest['FileID']) + + if connData['OpenedFiles'].has_key(fileID): + 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, 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, 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 connData['LastRequest'].has_key('SMB2_CREATE'): + fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] + else: + fileID = str(queryInfo['FileID']) + else: + fileID = str(queryInfo['FileID']) + + if connData['ConnectedShares'].has_key(recvPacket['TreeID']): + if connData['OpenedFiles'].has_key(fileID): + 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 connData['LastRequest'].has_key('SMB2_CREATE'): + fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] + else: + fileID = str(setInfo['FileID']) + else: + fileID = str(setInfo['FileID']) + + if connData['ConnectedShares'].has_key(recvPacket['TreeID']): + path = connData['ConnectedShares'][recvPacket['TreeID']]['path'] + if connData['OpenedFiles'].has_key(fileID): + 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, 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 connData['LastRequest'].has_key('SMB2_CREATE'): + fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] + else: + fileID = str(writeRequest['FileID']) + else: + fileID = str(writeRequest['FileID']) + + if connData['OpenedFiles'].has_key(fileID): + 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, 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 connData['LastRequest'].has_key('SMB2_CREATE'): + fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] + else: + fileID = str(readRequest['FileID']) + else: + fileID = str(readRequest['FileID']) + + if connData['OpenedFiles'].has_key(fileID): + 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, 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 connData['OpenedFiles'].has_key(str(flushRequest['FileID'])): + fileHandle = connData['OpenedFiles'][str(flushRequest['FileID'])]['FileHandle'] + errorCode = STATUS_SUCCESS + try: + os.fsync(fileHandle) + except Exception, 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 connData['ConnectedShares'].has_key(recvPacket['TreeID']) 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 connData['LastRequest'].has_key('SMB2_CREATE'): + fileID = connData['LastRequest']['SMB2_CREATE']['FileID'] + else: + fileID = str(queryDirectoryRequest['FileID']) + else: + fileID = str(queryDirectoryRequest['FileID']) + + if connData['OpenedFiles'].has_key(fileID) 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 connData['ConnectedShares'].has_key(recvPacket['TreeID']): + 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 ioctls.has_key(ioctlRequest['CtlCode']): + 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 connData['OpenedFiles'].has_key(str(ioctlRequest['FileID'])): + 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, 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, 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 conn.has_key('Authenticated') 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 self.__registeredNamedPipes.has_key(pipeName): + del(self.__registeredNamedPipes[unicode(pipeName)]) + return True + return False + + def unregisterTransaction(self, transCommand): + if self.__smbTransCommands.has_key(transCommand): + 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 self.__smbTransCommands.has_key(transCommand): + originalCommand = self.__smbTransCommands[transCommand] + else: + originalCommand = None + + self.__smbTransCommands[transCommand] = callback + return originalCommand + + def unregisterTransaction2(self, transCommand): + if self.__smbTrans2Commands.has_key(transCommand): + del(self.__smbTrans2Commands[transCommand]) + + def hookTransaction2(self, transCommand, callback): + # Here we should add to __smbTrans2Commands + # Same description as Transaction + if self.__smbTrans2Commands.has_key(transCommand): + originalCommand = self.__smbTrans2Commands[transCommand] + else: + originalCommand = None + + self.__smbTrans2Commands[transCommand] = callback + return originalCommand + + def unregisterNTTransaction(self, transCommand): + if self.__smbNTTransCommands.has_key(transCommand): + del(self.__smbNTTransCommands[transCommand]) + + def hookNTTransaction(self, transCommand, callback): + # Here we should add to __smbNTTransCommands + # Same description as Transaction + if self.__smbNTTransCommands.has_key(transCommand): + originalCommand = self.__smbNTTransCommands[transCommand] + else: + originalCommand = None + + self.__smbNTTransCommands[transCommand] = callback + return originalCommand + + def unregisterSmbCommand(self, smbCommand): + if self.__smbCommands.has_key(smbCommand): + 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: + # <list of respSMBCommands>, <list of packets>, errorCode + # <list of packets> 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 self.__smbCommands.has_key(smbCommand): + originalCommand = self.__smbCommands[smbCommand] + else: + originalCommand = None + + self.__smbCommands[smbCommand] = callback + return originalCommand + + def unregisterSmb2Command(self, smb2Command): + if self.__smb2Commands.has_key(smb2Command): + del(self.__smb2Commands[smb2Command]) + + def hookSmb2Command(self, smb2Command, callback): + if self.__smb2Commands.has_key(smb2Command): + 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 self.__smbCommands.has_key(packet['Command']): + 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, 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 self.__smb2Commands.has_key(packet['Command']): + 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, 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 + +###################################################################### +# HELPER CLASSES +###################################################################### + +from impacket.dcerpc.v5.rpcrt import DCERPCServer +from impacket.dcerpc.v5.dtypes import NULL +from impacket.dcerpc.v5.srvs import NetrShareEnum, NetrShareEnumResponse, SHARE_INFO_1, NetrServerGetInfo, NetrServerGetInfoResponse, NetrShareGetInfo, NetrShareGetInfoResponse +from impacket.dcerpc.v5.wkst import NetrWkstaGetInfo, NetrWkstaGetInfoResponse +from impacket.system_errors import ERROR_INVALID_LEVEL + +class WKSTServer(DCERPCServer): + def __init__(self): + DCERPCServer.__init__(self) + self.wkssvcCallBacks = { + 0: self.NetrWkstaGetInfo, + } + self.addCallbacks(('6BFFD098-A112-3610-9833-46C3F87E345A', '1.0'),'\\PIPE\\wkssvc', self.wkssvcCallBacks) + + def NetrWkstaGetInfo(self,data): + request = NetrWkstaGetInfo(data) + self.log("NetrWkstaGetInfo Level: %d" % request['Level']) + + answer = NetrWkstaGetInfoResponse() + + if request['Level'] not in (100, 101): + answer['ErrorCode'] = ERROR_INVALID_LEVEL + return answer + + answer['WkstaInfo']['tag'] = request['Level'] + + if request['Level'] == 100: + # Windows. Decimal value 500. + answer['WkstaInfo']['WkstaInfo100']['wki100_platform_id'] = 0x000001F4 + answer['WkstaInfo']['WkstaInfo100']['wki100_computername'] = NULL + answer['WkstaInfo']['WkstaInfo100']['wki100_langroup'] = NULL + answer['WkstaInfo']['WkstaInfo100']['wki100_ver_major'] = 5 + answer['WkstaInfo']['WkstaInfo100']['wki100_ver_minor'] = 0 + else: + # Windows. Decimal value 500. + answer['WkstaInfo']['WkstaInfo101']['wki101_platform_id'] = 0x000001F4 + answer['WkstaInfo']['WkstaInfo101']['wki101_computername'] = NULL + answer['WkstaInfo']['WkstaInfo101']['wki101_langroup'] = NULL + answer['WkstaInfo']['WkstaInfo101']['wki101_ver_major'] = 5 + answer['WkstaInfo']['WkstaInfo101']['wki101_ver_minor'] = 0 + answer['WkstaInfo']['WkstaInfo101']['wki101_lanroot'] = NULL + + return answer + +class SRVSServer(DCERPCServer): + def __init__(self): + DCERPCServer.__init__(self) + + self._shares = {} + self.__serverConfig = None + self.__logFile = None + + self.srvsvcCallBacks = { + 15: self.NetrShareEnum, + 16: self.NetrShareGetInfo, + 21: self.NetrServerGetInfo, + } + + self.addCallbacks(('4B324FC8-1670-01D3-1278-5A47BF6EE188', '3.0'),'\\PIPE\\srvsvc', self.srvsvcCallBacks) + + def setServerConfig(self, config): + self.__serverConfig = config + + def processConfigFile(self, configFile=None): + if configFile is not None: + self.__serverConfig = ConfigParser.ConfigParser() + self.__serverConfig.read(configFile) + sections = self.__serverConfig.sections() + # Let's check the log file + self.__logFile = self.__serverConfig.get('global','log_file') + 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') + + # Remove the global one + del(sections[sections.index('global')]) + self._shares = {} + for i in sections: + self._shares[i] = dict(self.__serverConfig.items(i)) + + def NetrShareGetInfo(self,data): + request = NetrShareGetInfo(data) + self.log("NetrGetShareInfo Level: %d" % request['Level']) + + s = request['NetName'][:-1].upper() + answer = NetrShareGetInfoResponse() + if self._shares.has_key(s): + share = self._shares[s] + + answer['InfoStruct']['tag'] = 1 + answer['InfoStruct']['ShareInfo1']['shi1_netname']= s+'\x00' + answer['InfoStruct']['ShareInfo1']['shi1_type'] = share['share type'] + answer['InfoStruct']['ShareInfo1']['shi1_remark'] = share['comment']+'\x00' + answer['ErrorCode'] = 0 + else: + answer['InfoStruct']['tag'] = 1 + answer['InfoStruct']['ShareInfo1']= NULL + answer['ErrorCode'] = 0x0906 #WERR_NET_NAME_NOT_FOUND + + return answer + + def NetrServerGetInfo(self,data): + request = NetrServerGetInfo(data) + self.log("NetrServerGetInfo Level: %d" % request['Level']) + answer = NetrServerGetInfoResponse() + answer['InfoStruct']['tag'] = 101 + # PLATFORM_ID_NT = 500 + answer['InfoStruct']['ServerInfo101']['sv101_platform_id'] = 500 + answer['InfoStruct']['ServerInfo101']['sv101_name'] = request['ServerName'] + # Windows 7 = 6.1 + answer['InfoStruct']['ServerInfo101']['sv101_version_major'] = 6 + answer['InfoStruct']['ServerInfo101']['sv101_version_minor'] = 1 + # Workstation = 1 + answer['InfoStruct']['ServerInfo101']['sv101_type'] = 1 + answer['InfoStruct']['ServerInfo101']['sv101_comment'] = NULL + answer['ErrorCode'] = 0 + return answer + + def NetrShareEnum(self, data): + request = NetrShareEnum(data) + self.log("NetrShareEnum Level: %d" % request['InfoStruct']['Level']) + shareEnum = NetrShareEnumResponse() + shareEnum['InfoStruct']['Level'] = 1 + shareEnum['InfoStruct']['ShareInfo']['tag'] = 1 + shareEnum['TotalEntries'] = len(self._shares) + shareEnum['InfoStruct']['ShareInfo']['Level1']['EntriesRead'] = len(self._shares) + shareEnum['ErrorCode'] = 0 + + for i in self._shares: + shareInfo = SHARE_INFO_1() + shareInfo['shi1_netname'] = i+'\x00' + shareInfo['shi1_type'] = self._shares[i]['share type'] + shareInfo['shi1_remark'] = self._shares[i]['comment']+'\x00' + shareEnum['InfoStruct']['ShareInfo']['Level1']['Buffer'].append(shareInfo) + + return shareEnum + +class SimpleSMBServer: + """ + SimpleSMBServer class - Implements a simple, customizable SMB Server + + :param string listenAddress: the address you want the server to listen on + :param integer listenPort: the port number you want the server to listen on + :param string configFile: a file with all the servers' configuration. If no file specified, this class will create the basic parameters needed to run. You will need to add your shares manually tho. See addShare() method + """ + def __init__(self, listenAddress = '0.0.0.0', listenPort=445, configFile=''): + if configFile != '': + self.__server = SMBSERVER((listenAddress,listenPort)) + self.__server.processConfigFile(configFile) + self.__smbConfig = None + else: + # Here we write a mini config for the server + self.__smbConfig = ConfigParser.ConfigParser() + self.__smbConfig.add_section('global') + self.__smbConfig.set('global','server_name',''.join([random.choice(string.letters) for _ in range(8)])) + self.__smbConfig.set('global','server_os',''.join([random.choice(string.letters) for _ in range(8)]) +) + self.__smbConfig.set('global','server_domain',''.join([random.choice(string.letters) for _ in range(8)]) +) + self.__smbConfig.set('global','log_file','None') + self.__smbConfig.set('global','rpc_apis','yes') + self.__smbConfig.set('global','credentials_file','') + self.__smbConfig.set('global', 'challenge', "A"*8) + + # IPC always needed + self.__smbConfig.add_section('IPC$') + self.__smbConfig.set('IPC$','comment','') + self.__smbConfig.set('IPC$','read only','yes') + self.__smbConfig.set('IPC$','share type','3') + self.__smbConfig.set('IPC$','path','') + self.__server = SMBSERVER((listenAddress,listenPort), config_parser = self.__smbConfig) + self.__server.processConfigFile() + + # Now we have to register the MS-SRVS server. This specially important for + # Windows 7+ and Mavericks clients since they WONT (specially OSX) + # ask for shares using MS-RAP. + + self.__srvsServer = SRVSServer() + self.__srvsServer.daemon = True + self.__wkstServer = WKSTServer() + self.__wkstServer.daemon = True + self.__server.registerNamedPipe('srvsvc',('127.0.0.1',self.__srvsServer.getListenPort())) + self.__server.registerNamedPipe('wkssvc',('127.0.0.1',self.__wkstServer.getListenPort())) + + def start(self): + self.__srvsServer.start() + self.__wkstServer.start() + self.__server.serve_forever() + + def registerNamedPipe(self, pipeName, address): + return self.__server.registerNamedPipe(pipeName, address) + + def unregisterNamedPipe(self, pipeName): + return self.__server.unregisterNamedPipe(pipeName) + + def getRegisteredNamedPipes(self): + return self.__server.getRegisteredNamedPipes() + + def addShare(self, shareName, sharePath, shareComment='', shareType = 0, readOnly = 'no'): + self.__smbConfig.add_section(shareName) + self.__smbConfig.set(shareName, 'comment', shareComment) + self.__smbConfig.set(shareName, 'read only', readOnly) + self.__smbConfig.set(shareName, 'share type', shareType) + self.__smbConfig.set(shareName, 'path', sharePath) + self.__server.setServerConfig(self.__smbConfig) + self.__srvsServer.setServerConfig(self.__smbConfig) + self.__server.processConfigFile() + self.__srvsServer.processConfigFile() + + def removeShare(self, shareName): + self.__smbConfig.remove_section(shareName) + self.__server.setServerConfig(self.__smbConfig) + self.__srvsServer.setServerConfig(self.__smbConfig) + self.__server.processConfigFile() + self.__srvsServer.processConfigFile() + + def setSMBChallenge(self, challenge): + if challenge != '': + self.__smbConfig.set('global', 'challenge', unhexlify(challenge)) + self.__server.setServerConfig(self.__smbConfig) + self.__server.processConfigFile() + + def setLogFile(self, logFile): + self.__smbConfig.set('global','log_file',logFile) + self.__server.setServerConfig(self.__smbConfig) + self.__server.processConfigFile() + + def setCredentialsFile(self, logFile): + self.__smbConfig.set('global','credentials_file',logFile) + self.__server.setServerConfig(self.__smbConfig) + self.__server.processConfigFile() + + def setSMB2Support(self, value): + if value is True: + self.__smbConfig.set("global", "SMB2Support", "True") + else: + self.__smbConfig.set("global", "SMB2Support", "False") + self.__server.setServerConfig(self.__smbConfig) + self.__server.processConfigFile() + |