From 4012ad6d1d254f2f4862ce95cf0b34f5a12732c1 Mon Sep 17 00:00:00 2001 From: bitbrute Date: Sat, 4 Jul 2020 19:55:37 +0200 Subject: [PATCH 1/6] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc7cdb9..3b9255b 100644 --- a/README.md +++ b/README.md @@ -78,4 +78,10 @@ Type ```evillimiter``` or ```python3 bin/evillimiter``` to run the tool. ## License Copyright (c) 2019 by [bitbrute](https://github.com/bitbrute). Some rights reserved.
-[Evil Limiter](https://github.com/bitbrute/evillimiter) is licensed under the MIT License as stated in the [LICENSE file](LICENSE). \ No newline at end of file +[Evil Limiter](https://github.com/bitbrute/evillimiter) is licensed under the MIT License as stated in the [LICENSE file](LICENSE). + +## Donation +You like my projects? Feel free to help me pay off my incredibly overprized Mercedes VIP leasing contract. <3 + +BTC: 16oNzE8Vkcz8YQfnHF4g19moN3E6wc8E31
+ETH: 0xB08D257cf5B76dB0D2fBAd1E68f52b9cCE3581e3 From a5409f10642f8aca331da9d5f96e7ab945dc3f32 Mon Sep 17 00:00:00 2001 From: bitbrute Date: Sun, 5 Jul 2020 19:17:52 +0000 Subject: [PATCH 2/6] Enhance display of host limitation status --- evillimiter/menus/main_menu.py | 2 +- evillimiter/networking/host.py | 10 +--------- evillimiter/networking/limit.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/evillimiter/menus/main_menu.py b/evillimiter/menus/main_menu.py index d3ea40a..ac83e5c 100755 --- a/evillimiter/menus/main_menu.py +++ b/evillimiter/menus/main_menu.py @@ -159,7 +159,7 @@ def _hosts_handler(self, args): host.ip, host.mac, host.name, - host.pretty_status() + self.limiter.pretty_status(host) ]) table = SingleTable(table_data, 'Hosts') diff --git a/evillimiter/networking/host.py b/evillimiter/networking/host.py index ede9f7c..7f0d80f 100644 --- a/evillimiter/networking/host.py +++ b/evillimiter/networking/host.py @@ -15,12 +15,4 @@ def __eq__(self, other): return self.ip == other.ip def __hash__(self): - return hash((self.mac, self.ip)) - - def pretty_status(self): - if self.limited: - return '{}Limited{}'.format(IO.Fore.LIGHTRED_EX, IO.Style.RESET_ALL) - elif self.blocked: - return '{}Blocked{}'.format(IO.Fore.RED, IO.Style.RESET_ALL) - else: - return 'Free' \ No newline at end of file + return hash((self.mac, self.ip)) \ No newline at end of file diff --git a/evillimiter/networking/limit.py b/evillimiter/networking/limit.py index b44b540..62ef012 100755 --- a/evillimiter/networking/limit.py +++ b/evillimiter/networking/limit.py @@ -3,6 +3,7 @@ import evillimiter.console.shell as shell from .host import Host from evillimiter.common.globals import BIN_TC, BIN_IPTABLES +from evillimiter.console.io import IO class Limiter(object): @@ -90,6 +91,33 @@ def replace(self, old_host, new_host): else: self.limit(new_host, info['direction'], info['rate']) + def pretty_status(self, host): + """ + Gets the host limitation status in a formatted and colored string + """ + with self._host_dict_lock: + if host in self._host_dict: + rate = self._host_dict[host]['rate'] + direction = self._host_dict[host]['direction'] + uload = None + dload = None + final = '' + + if direction in (Direction.BOTH, Direction.OUTGOING): + uload = '0bit' if rate is None else rate + if direction in (Direction.BOTH, Direction.INCOMING): + dload = '0bit' if rate is None else rate + + if uload is not None: + final += '{}↑'.format(uload) + if dload is not None: + final += ' {}↓'.format(dload) + + return '{}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, final, IO.Style.RESET_ALL) + + else: + return '{}Free{}'.format(IO.Fore.LIGHTGREEN_EX, IO.Style.RESET_ALL) + def _new_host_limit_ids(self, host, direction): """ Get limit information for corresponding host From 75ea18bff5a2bb62709d8a18a69f29a721c19aa4 Mon Sep 17 00:00:00 2001 From: bitbrute Date: Sun, 5 Jul 2020 23:09:53 +0000 Subject: [PATCH 3/6] Add ability to specify scan intensity --- README.md | 2 +- evillimiter/menus/main_menu.py | 39 ++++++++++++++++++++++---- evillimiter/networking/scan.py | 49 +++++++++++++++++++++++++++------ evillimiter/networking/watch.py | 20 +++++++++++--- 4 files changed, 91 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 3b9255b..dd403be 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Type ```evillimiter``` or ```python3 bin/evillimiter``` to run the tool. | Command | Explanation | | ------- | ----------- | -| ```scan (--range [IP Range])``` | Scans your network for online hosts. One of the first things to do after start.
```--range``` lets you specify a custom IP range.
For example: ```scan --range 192.168.178.1-192.168.178.40``` or just ```scan``` to scan the entire subnet. +| ```scan (--range [IP Range]) (--intensity [(1,2,3)])``` | Scans your network for online hosts. One of the first things to do after start.
```--range``` lets you specify a custom IP range.
```--intensity``` lets you specify the scan intensity / speed (```1``` = quick, ```2``` = normal (standard), ```3``` = intense).
For example: ```scan --range 192.168.178.1-192.168.178.40 --intensity 1``` or just ```scan``` to scan the entire subnet with the standard intensity. | ```hosts (--force)``` | Displays all the hosts/devices previously scanned and basic information. Shows ID for each host that is required for interaction.
```--force``` forces the table to be shown, even when it doesn't fit the terminal. | ```limit [ID1,ID2,...] [Rate] (--upload) (--download)``` | Limits bandwidth of host(s) associated to specified ID. Rate determines the internet speed.
```--upload``` limits outgoing traffic only.
```--download``` limits incoming traffic only.
Valid rates: ```bit```, ```kbit```, ```mbit```, ```gbit```
For example: ```limit 4,5,6 200kbit``` or ```limit all 1gbit``` | ```block [ID1,ID2,...] (--upload) (--download)``` | Blocks internet connection of host(s) associated to specified ID.
```--upload``` limits outgoing traffic only
```--download``` limits incoming traffic only. diff --git a/evillimiter/menus/main_menu.py b/evillimiter/menus/main_menu.py index ac83e5c..bff66ce 100755 --- a/evillimiter/menus/main_menu.py +++ b/evillimiter/menus/main_menu.py @@ -15,7 +15,7 @@ from evillimiter.networking.host import Host from evillimiter.networking.limit import Limiter, Direction from evillimiter.networking.spoof import ARPSpoofer -from evillimiter.networking.scan import HostScanner +from evillimiter.networking.scan import HostScanner, ScanIntensity from evillimiter.networking.monitor import BandwidthMonitor from evillimiter.networking.watch import HostWatcher @@ -31,6 +31,7 @@ def __init__(self, version, interface, gateway_ip, gateway_mac, netmask): scan_parser = self.parser.add_subparser('scan', self._scan_handler) scan_parser.add_parameterized_flag('--range', 'iprange') + scan_parser.add_parameterized_flag('--intensity', 'intensity') limit_parser = self.parser.add_subparser('limit', self._limit_handler) limit_parser.add_parameter('id') @@ -85,7 +86,7 @@ def __init__(self, version, interface, gateway_ip, gateway_mac, netmask): self.arp_spoofer = ARPSpoofer(self.interface, self.gateway_ip, self.gateway_mac) self.limiter = Limiter(self.interface) self.bandwidth_monitor = BandwidthMonitor(self.interface, 1) - self.host_watcher = HostWatcher(self.host_scanner, self._reconnect_callback) + self.host_watcher = HostWatcher(self.interface, self.iprange, self._reconnect_callback) # holds discovered hosts self.hosts = [] @@ -125,6 +126,16 @@ def _scan_handler(self, args): else: iprange = None + if args.intensity: + intensity = self._parse_scan_intensity(args.intensity) + if intensity is None: + IO.error('invalid intensity level.') + return + else: + intensity = ScanIntensity.NORMAL + + self.host_scanner.set_intensity(intensity) + with self.hosts_lock: for host in self.hosts: self._free_host(host) @@ -439,6 +450,7 @@ def _watch_handler(self, args): iprange = self.host_watcher.iprange interval = self.host_watcher.interval + intensity = self.host_watcher.intensity set_table_data.append([ '{}range{}'.format(IO.Fore.LIGHTYELLOW_EX, IO.Style.RESET_ALL), @@ -450,6 +462,11 @@ def _watch_handler(self, args): '{}s'.format(interval) ]) + set_table_data.append([ + '{}intensity{}'.format(IO.Fore.LIGHTYELLOW_EX, IO.Style.RESET_ALL), + intensity + ]) + for host in self.host_watcher.hosts: watch_table_data.append([ '{}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, self._get_host_id(host), IO.Style.RESET_ALL), @@ -517,6 +534,12 @@ def _watch_set_handler(self, args): self.host_watcher.interval = int(args.value) else: IO.error('invalid interval.') + elif args.attribute.lower() in ('intensity', 'scan_intensity'): + intensity = self._parse_scan_intensity(args.value) + if intensity is not None: + self.host_watcher.intensity = intensity + else: + IO.error('invalid scan intensity level.') else: IO.error('{}{}{} is an invalid settings attribute.'.format(IO.Fore.LIGHTYELLOW_EX, args.attribute, IO.Style.RESET_ALL)) @@ -559,10 +582,10 @@ def _help_handler(self, args): IO.print( """ {y}scan (--range [IP range]){r}{}scans for online hosts on your network. -{s}required to find the hosts you want to limit. +{y} (--intensity [(1,2,3)]){r}{}required to find the hosts you want to limit. {b}{s}e.g.: scan {s} scan --range 192.168.178.1-192.168.178.50 -{s} scan --range 192.168.178.1/24{r} +{s} scan --range 192.168.178.1/24 --intensity 3{r} {y}hosts (--force){r}{}lists all scanned hosts. {s}contains host information, including IDs. @@ -598,13 +621,15 @@ def _help_handler(self, args): {y}watch remove [ID1,ID2,...]{r}{}removes host from the reconnection watchlist. {b}{s}e.g.: watch remove all{r} {y}watch set [attr] [value]{r}{}changes reconnect watch settings. -{b}{s}e.g.: watch set interval 120{r} +{b}{s}e.g.: watch set interval 120 +{s} watch set intensity 1{r} {y}clear{r}{}clears the terminal window. {y}quit{r}{}quits the application. """.format( spaces[len('scan (--range [IP range])'):], + spaces[len(' (--intensity [(1,2,3)])'):], spaces[len('hosts (--force)'):], spaces[len('limit [ID1,ID2,...] [rate]'):], spaces[len(' (--upload) (--download)'):], @@ -705,6 +730,10 @@ def _parse_iprange(self, range): except netaddr.core.AddrFormatError: return + def _parse_scan_intensity(self, value): + if value.isdigit() and int(value) in (ScanIntensity.QUICK, ScanIntensity.NORMAL, ScanIntensity.INTENSE): + return int(value) + def _free_host(self, host): """ Stops ARP spoofing and unlimits host diff --git a/evillimiter/networking/scan.py b/evillimiter/networking/scan.py index 06d6961..cf42304 100644 --- a/evillimiter/networking/scan.py +++ b/evillimiter/networking/scan.py @@ -1,5 +1,7 @@ import sys import socket +import threading +import collections from tqdm import tqdm from netaddr import IPAddress from scapy.all import sr1, ARP # pylint: disable=no-name-in-module @@ -10,18 +12,39 @@ class HostScanner(object): + Settings = collections.namedtuple('Settings', 'max_workers retries timeout') + def __init__(self, interface, iprange): self.interface = interface self.iprange = iprange - self.max_workers = 75 # max. amount of threads - self.retries = 0 # ARP retry - self.timeout = 2.5 # time in s to wait for an answer + self._quick_settings = HostScanner.Settings(max_workers=80, retries=0, timeout=2) + self._normal_settings = HostScanner.Settings(max_workers=80, retries=1, timeout=3) + self._intense_settings = HostScanner.Settings(max_workers=80, retries=5, timeout=10) - def scan(self, iprange=None): - self._resolve_names = True + self._settings = self._normal_settings + self._settings_lock = threading.Lock() + + @property + def settings(self): + with self._settings_lock: + return self._settings + + @settings.setter + def settings(self, value): + with self._settings_lock: + self._settings = value + + def set_intensity(self, intensity): + if intensity == ScanIntensity.QUICK: + self.settings = self._quick_settings + elif intensity == ScanIntensity.NORMAL: + self.settings = self._normal_settings + elif intensity == ScanIntensity.INTENSE: + self.settings = self._intense_settings - with ThreadPoolExecutor(max_workers=self.max_workers) as executor: + def scan(self, iprange=None): + with ThreadPoolExecutor(max_workers=self.settings.max_workers) as executor: hosts = [] iprange = [str(x) for x in (self.iprange if iprange is None else iprange)] iterator = tqdm( @@ -49,7 +72,7 @@ def scan(self, iprange=None): return hosts def scan_for_reconnects(self, hosts, iprange=None): - with ThreadPoolExecutor(max_workers=self.max_workers) as executor: + with ThreadPoolExecutor(max_workers=self.settings.max_workers) as executor: scanned_hosts = [] iprange = [str(x) for x in (self.iprange if iprange is None else iprange)] for host in executor.map(self._sweep, iprange): @@ -70,8 +93,16 @@ def _sweep(self, ip): Sends ARP packet and listens for answer, if present the host is online """ + settings = self.settings + packet = ARP(op=1, pdst=ip) - answer = sr1(packet, retry=self.retries, timeout=self.timeout, verbose=0, iface=self.interface) + answer = sr1(packet, retry=settings.retries, timeout=settings.timeout, verbose=0, iface=self.interface) if answer is not None: - return Host(ip, answer.hwsrc, '') \ No newline at end of file + return Host(ip, answer.hwsrc, '') + + +class ScanIntensity: + QUICK = 1 + NORMAL = 2 + INTENSE = 3 \ No newline at end of file diff --git a/evillimiter/networking/watch.py b/evillimiter/networking/watch.py index 4911bb0..7caeb0d 100644 --- a/evillimiter/networking/watch.py +++ b/evillimiter/networking/watch.py @@ -1,16 +1,19 @@ import time import threading +from .scan import HostScanner, ScanIntensity + class HostWatcher(object): - def __init__(self, host_scanner, reconnection_callback): - self._scanner = host_scanner + def __init__(self, interface, iprange, reconnection_callback): + self._scanner = HostScanner(interface, iprange) self._reconnection_callback = reconnection_callback self._hosts = set() self._hosts_lock = threading.Lock() - self._interval = 45 # scan interval in s - self._iprange = None # custom ip range to be watched + self._interval = 45 # scan interval in s + self._iprange = iprange # custom ip range to be watched + self._intensity = ScanIntensity.NORMAL # scan intensity (speed) self._settings_lock = threading.Lock() self._log_list = [] @@ -38,6 +41,15 @@ def iprange(self, value): with self._settings_lock: self._iprange = value + @property + def intensity(self): + return self._intensity + + @intensity.setter + def intensity(self, value): + self._scanner.set_intensity(value) + self._intensity = value + @property def hosts(self): with self._hosts_lock: From 858a63933a5c711f62f62caa88eb3b720f878f7c Mon Sep 17 00:00:00 2001 From: bitbrute Date: Mon, 6 Jul 2020 19:35:34 +0000 Subject: [PATCH 4/6] Add ability to monitor any host --- README.md | 2 +- evillimiter/menus/main_menu.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dd403be..3b8271d 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Type ```evillimiter``` or ```python3 bin/evillimiter``` to run the tool. | ```block [ID1,ID2,...] (--upload) (--download)``` | Blocks internet connection of host(s) associated to specified ID.
```--upload``` limits outgoing traffic only
```--download``` limits incoming traffic only. | ```free [ID1,ID2,...]``` | Unlimits/Unblocks host(s) associated to specified ID. Removes all further restrictions. | ```add [IP] (--mac [MAC])``` | Adds custom host to host list. MAC-Address will be resolved automatically or can be specified manually.
For example: ```add 192.168.178.24``` or ```add 192.168.1.50 --mac 1c:fc:bc:2d:a6:37``` -| ```monitor (--interval [time in ms])``` | Monitors bandwidth usage of limited host(s) (current usage, total bandwidth used, ...).
```--interval``` sets the interval after bandwidth information get refreshed in milliseconds (default 500ms).
For example: ```monitor --interval 1000``` +| ```monitor [ID1,ID2,...] (--interval [time in ms])``` | Monitors bandwidth usage of host(s) (current usage, total bandwidth used, ...).
```--interval``` sets the interval after bandwidth information get refreshed in milliseconds (default 500ms).
For example: ```monitor all --interval 1000``` | ```analyze [ID1,ID2,...] (--duration [time in s])``` | Analyzes traffic of host(s) without limiting to determine who uses how much bandwidth.
```--duration``` specifies the duration of the analysis in seconds (default 30s).
For example: ```analyze 2,3 --duration 120``` | ```watch``` | Shows current watch status. The watch feature detects when a host reconnects with a different IP address. | ```watch add [ID1,ID2,...]``` | Adds specified host(s) to the watchlist.
For example: ```watch add 6,7,8``` diff --git a/evillimiter/menus/main_menu.py b/evillimiter/menus/main_menu.py index bff66ce..2684470 100755 --- a/evillimiter/menus/main_menu.py +++ b/evillimiter/menus/main_menu.py @@ -52,6 +52,7 @@ def __init__(self, version, interface, gateway_ip, gateway_mac, netmask): add_parser.add_parameterized_flag('--mac', 'mac') monitor_parser = self.parser.add_subparser('monitor', self._monitor_handler) + monitor_parser.add_parameter('id') monitor_parser.add_parameterized_flag('--interval', 'interval') analyze_parser = self.parser.add_subparser('analyze', self._analyze_handler) @@ -280,7 +281,10 @@ def _monitor_handler(self, args): """ def get_bandwidth_results(): with self.hosts_lock: - return [x for x in [(y, self.bandwidth_monitor.get(y)) for y in self.hosts] if x[1] is not None] + return sorted( + [x for x in [(y, self.bandwidth_monitor.get(y)) for y in self.hosts] if x[1] is not None], + key=lambda h: not (h[0].limited or h[0].blocked) + ) def display(stdscr, interval): host_results = get_bandwidth_results() @@ -306,6 +310,7 @@ def display(stdscr, interval): y_off += 2 x_off = x_rst + temps_reached = False for host, result in host_results: result_data = [ @@ -317,6 +322,10 @@ def display(stdscr, interval): '{}↑ {}↓'.format(result.upload_total_count, result.download_total_count) ] + if not temps_reached and host in hosts_to_be_freed: + temps_reached = True + y_off += 1 + for j, string in enumerate(result_data): stdscr.addstr(y_off, x_off, string) x_off += header_off[j][1] @@ -334,6 +343,8 @@ def display(stdscr, interval): except KeyboardInterrupt: return + hosts = self._get_hosts_by_ids(args.id) + hosts_to_be_freed = set() interval = 0.5 # in s if args.interval: @@ -343,6 +354,13 @@ def display(stdscr, interval): interval = int(args.interval) / 1000 # from ms to s + for host in hosts: + if not host.spoofed: + hosts_to_be_freed.add(host) + + self.arp_spoofer.add(host) + self.bandwidth_monitor.add(host) + if len(get_bandwidth_results()) == 0: IO.error('no hosts to be monitored.') return @@ -352,6 +370,9 @@ def display(stdscr, interval): except curses.error: IO.error('monitor error occurred. maybe terminal too small?') + for host in hosts_to_be_freed: + self._free_host(host) + def _analyze_handler(self, args): hosts = self._get_hosts_by_ids(args.id) if hosts is None or len(hosts) == 0: @@ -608,8 +629,8 @@ def _help_handler(self, args): {b}{s}e.g.: add 192.168.178.24 {s} add 192.168.1.50 --mac 1c:fc:bc:2d:a6:37{r} -{y}monitor (--interval [time in ms]){r}{}monitors bandwidth usage of limited host(s). -{b}{s}e.g.: monitor --interval 600{r} +{y}monitor [ID1,ID2,...]{r}{}monitors bandwidth usage of host(s). +{y} (--interval [time in ms]){r}{}{b}e.g.: monitor all --interval 600{r} {y}analyze [ID1,ID2,...]{r}{}analyzes traffic of host(s) without limiting {y} (--duration [time in s]){r}{}to determine who uses how much bandwidth. @@ -637,7 +658,8 @@ def _help_handler(self, args): spaces[len(' (--upload) (--download)'):], spaces[len('free [ID1,ID2,...]'):], spaces[len('add [IP] (--mac [MAC])'):], - spaces[len('monitor (--interval [time in ms])'):], + spaces[len('monitor [ID1,ID2,...]'):], + spaces[len(' (--interval [time in ms])'):], spaces[len('analyze [ID1,ID2,...]'):], spaces[len(' (--duration [time in s])'):], spaces[len('watch'):], From 2860ec516a9d5cb54db88c438fbedab3de4e375d Mon Sep 17 00:00:00 2001 From: bitbrute Date: Mon, 6 Jul 2020 19:40:50 +0000 Subject: [PATCH 5/6] Fix spacing of limitation status display --- evillimiter/networking/limit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evillimiter/networking/limit.py b/evillimiter/networking/limit.py index 62ef012..5508024 100755 --- a/evillimiter/networking/limit.py +++ b/evillimiter/networking/limit.py @@ -113,7 +113,7 @@ def pretty_status(self, host): if dload is not None: final += ' {}↓'.format(dload) - return '{}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, final, IO.Style.RESET_ALL) + return '{}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, final.strip(), IO.Style.RESET_ALL) else: return '{}Free{}'.format(IO.Fore.LIGHTGREEN_EX, IO.Style.RESET_ALL) From 08dbb43c49fa0bc8036483af1944ed6d66975ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Antonio=20D=C3=ADaz-Benito=20Soriano?= Date: Mon, 6 Jul 2020 22:14:16 +0200 Subject: [PATCH 6/6] Fix 'maintained?' badge link (#54) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b8271d..a88cd48 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![License Badge](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Compatibility](https://img.shields.io/badge/python-3-brightgreen.svg)](PROJECT) -[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/Naereen/StrapDown.js/graphs/commit-activity) +[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/bitbrute/evillimiter/graphs/commit-activity) [![HitCount](http://hits.dwyl.io/bitbrute/evillimiter.svg)](http://hits.dwyl.io/bitbrute/evillimiter) [![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.svg?v=102)](https://github.com/ellerbrock/open-source-badge/)