diff --git a/README.md b/README.md
index dc7cdb9..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/)
@@ -52,13 +52,13 @@ 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.
| ```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```
@@ -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
diff --git a/evillimiter/menus/main_menu.py b/evillimiter/menus/main_menu.py
index d3ea40a..2684470 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')
@@ -51,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)
@@ -85,7 +87,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 +127,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)
@@ -159,7 +171,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')
@@ -269,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()
@@ -295,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 = [
@@ -306,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]
@@ -323,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:
@@ -332,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
@@ -341,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:
@@ -439,6 +471,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 +483,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 +555,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 +603,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.
@@ -585,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.
@@ -598,13 +642,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)'):],
@@ -612,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'):],
@@ -705,6 +752,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/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..5508024 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.strip(), 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
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: