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