Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #129

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

Expand Down Expand Up @@ -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.<br>```--range``` lets you specify a custom IP range.<br>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.<br>```--range``` lets you specify a custom IP range.<br>```--intensity``` lets you specify the scan intensity / speed (```1``` = quick, ```2``` = normal (standard), ```3``` = intense).<br>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.<br>```--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.<br>```--upload``` limits outgoing traffic only.<br>```--download``` limits incoming traffic only.<br>Valid rates: ```bit```, ```kbit```, ```mbit```, ```gbit```<br>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.<br>```--upload``` limits outgoing traffic only <br>```--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.<br>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, ...).<br>```--interval``` sets the interval after bandwidth information get refreshed in milliseconds (default 500ms).<br>For example: ```monitor --interval 1000```
| ```monitor [ID1,ID2,...] (--interval [time in ms])``` | Monitors bandwidth usage of host(s) (current usage, total bandwidth used, ...).<br>```--interval``` sets the interval after bandwidth information get refreshed in milliseconds (default 500ms).<br>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.<br>```--duration``` specifies the duration of the analysis in seconds (default 30s).<br>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.<br>For example: ```watch add 6,7,8```
Expand All @@ -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.<br>
[Evil Limiter](https://github.com/bitbrute/evillimiter) is licensed under the MIT License as stated in the [LICENSE file](LICENSE).
[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<br>
ETH: 0xB08D257cf5B76dB0D2fBAd1E68f52b9cCE3581e3
71 changes: 61 additions & 10 deletions evillimiter/menus/main_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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')
Expand All @@ -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)
Expand Down Expand Up @@ -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 = []
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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()
Expand All @@ -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 = [
Expand All @@ -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]
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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),
Expand 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),
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -598,21 +642,24 @@ 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)'):],
spaces[len('block [ID1,ID2,...]'):],
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'):],
Expand Down Expand Up @@ -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
Expand Down
10 changes: 1 addition & 9 deletions evillimiter/networking/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
return hash((self.mac, self.ip))
28 changes: 28 additions & 0 deletions evillimiter/networking/limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
49 changes: 40 additions & 9 deletions evillimiter/networking/scan.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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):
Expand All @@ -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, '')
return Host(ip, answer.hwsrc, '')


class ScanIntensity:
QUICK = 1
NORMAL = 2
INTENSE = 3
Loading