Skip to content
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
38 changes: 33 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
A tool to monitor, analyze and limit the bandwidth (upload/download) of devices on your local network without physical or administrative access.<br>
```evillimiter``` employs [ARP spoofing](https://en.wikipedia.org/wiki/ARP_spoofing) and [traffic shaping](https://en.wikipedia.org/wiki/Traffic_shaping) to throttle the bandwidth of hosts on the network.

**Searching for a Windows-compatible version?**<br>
Check out the open-source alternative [EvilLimiter for Windows](https://github.com/bitbrute/evillimiter-windows).
## Searching for a Windows-compatible version?
- Check out the open-source alternative [EvilLimiter for Windows](https://github.com/bitbrute/evillimiter-windows). <br>
- Also you can run this tool on wsl2/wslg kernels just recompile the kernel with the stock configs and add the [configs here](#wsl-support)

## Requirements
- Linux distribution
Expand All @@ -23,7 +24,7 @@ Possibly missing python packages will be installed during the installation proce
## Installation

```bash
git clone https://github.com/bitbrute/evillimiter.git
git clone https://github.com/GigaArchitect/evillimiter
cd evillimiter
sudo python3 setup.py install
```
Expand Down Expand Up @@ -71,11 +72,38 @@ Type ```evillimiter``` or ```python3 bin/evillimiter``` to run the tool.
## Restrictions

- **Limits IPv4 connctions only**, since [ARP spoofing](https://en.wikipedia.org/wiki/ARP_spoofing) requires the ARP packet that is only present on IPv4 networks.

## WSL Support
re-compile your kernel (WSL-kernel) with those parameters added inside .config (copy from Mircosoft Directory)
```
CONFIG_NET_SCHED=y
CONFIG_NET_SCH_CBQ=m
CONFIG_NET_SCH_HTB=y
CONFIG_NET_SCH_CSZ=m
CONFIG_NET_SCH_PRIO=m
CONFIG_NET_SCH_RED=m
CONFIG_NET_SCH_SFQ=m
CONFIG_NET_SCH_TEQL=m
CONFIG_NET_SCH_TBF=m
CONFIG_NET_SCH_GRED=m
CONFIG_NET_SCH_DSMARK=m
CONFIG_NET_SCH_INGRESS=m
CONFIG_NET_QOS=y
CONFIG_NET_ESTIMATOR=y
CONFIG_NET_CLS=y
CONFIG_NET_CLS_TCINDEX=m
CONFIG_NET_CLS_ROUTE4=m
CONFIG_NET_CLS_ROUTE=y
CONFIG_NET_CLS_FW=m
CONFIG_NET_CLS_U32=m
CONFIG_NET_CLS_RSVP=m
CONFIG_NET_CLS_RSVP6=m
CONFIG_NET_CLS_POLICE=y
CONFIG_NET_SCH_NETEM=y
```
## Disclaimer
[Evil Limiter](https://github.com/bitbrute/evillimiter) is provided by [bitbrute](https://github.com/bitbrute) "as is" and "with all faults". The provider makes no representations or warranties of any kind concerning the safety, suitability, lack of viruses, inaccuracies, typographical errors, or other harmful components of this software. There are inherent dangers in the use of any software, and you are solely responsible for determining whether Evil Limiter is compatible with your equipment and other software installed on your equipment. You are also solely responsible for the protection of your equipment and backup of your data, and the provider will not be liable for any damages you may suffer in connection with using, modifying, or distributing this software.

## 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).
17 changes: 12 additions & 5 deletions evillimiter/console/shell.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
import os
import subprocess
from sys import stderr, stdout
from evillimiter.console.io import IO

DEVNULL = open(os.devnull, 'w')

def check_doas_sudo():
try:
subprocess.run("which doas", check=True, stdout=DEVNULL, stderr=DEVNULL, shell=True)
return "doas "
except subprocess.CalledProcessError as e:
return "sudo "

def execute(command, root=True):
return subprocess.call('sudo ' + command if root else command, shell=True)
return subprocess.call(check_doas_sudo() + command if root else command, shell=True)


def execute_suppressed(command, root=True):
return subprocess.call('sudo ' + command if root else command, shell=True, stdout=DEVNULL, stderr=DEVNULL)
return subprocess.call(check_doas_sudo() + command if root else command, shell=True, stdout=DEVNULL, stderr=DEVNULL)


def output(command, root=True):
return subprocess.check_output('sudo ' + command if root else command, shell=True).decode('utf-8')
return subprocess.check_output(check_doas_sudo() + command if root else command, shell=True).decode('utf-8')


def output_suppressed(command, root=True):
return subprocess.check_output('sudo ' + command if root else command, shell=True, stderr=DEVNULL).decode('utf-8')
return subprocess.check_output(check_doas_sudo() + command if root else command, shell=True, stderr=DEVNULL).decode('utf-8')


def locate_bin(name):
try:
return output_suppressed('which {}'.format(name)).replace('\n', '')
except subprocess.CalledProcessError:
IO.error('missing util: {}, check your PATH'.format(name))
IO.error('missing util: {}, check your PATH'.format(name))
36 changes: 21 additions & 15 deletions evillimiter/networking/scan.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import sys
import logging
import socket
from tqdm import tqdm
import sys

from netaddr import IPAddress
from scapy.all import sr1, ARP # pylint: disable=no-name-in-module
from tqdm import tqdm

logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from concurrent.futures import ThreadPoolExecutor

from .host import Host
from scapy.all import ARP, sr1 # pylint: disable=no-name-in-module

from evillimiter.console.io import IO


from .host import Host


class HostScanner(object):
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.max_workers = 75 # max. amount of threads
self.retries = 0 # ARP retry
self.timeout = 2.5 # time in s to wait for an answer

def scan(self, iprange=None):
self._resolve_names = True
Expand All @@ -28,23 +34,23 @@ def scan(self, iprange=None):
iterable=executor.map(self._sweep, iprange),
total=len(iprange),
ncols=45,
bar_format='{percentage:3.0f}% |{bar}| {n_fmt}/{total_fmt}'
bar_format="{percentage:3.0f}% |{bar}| {n_fmt}/{total_fmt}",
)

try:
for host in iterator:
if host is not None:
try:
host_info = socket.gethostbyaddr(host.ip)
name = '' if host_info is None else host_info[0]
name = "" if host_info is None else host_info[0]
host.name = name
except socket.herror:
pass

hosts.append(host)
except KeyboardInterrupt:
iterator.close()
IO.ok('aborted. waiting for shutdown...')
IO.ok("aborted. waiting for shutdown...")

return hosts

Expand All @@ -62,7 +68,7 @@ def scan_for_reconnects(self, hosts, iprange=None):
if host.mac == s_host.mac and host.ip != s_host.ip:
s_host.name = host.name
reconnected_hosts[host] = s_host

return reconnected_hosts

def _sweep(self, ip):
Expand All @@ -71,7 +77,7 @@ def _sweep(self, ip):
if present the host is online
"""
packet = ARP(op=1, pdst=ip)
answer = sr1(packet, retry=self.retries, timeout=self.timeout, verbose=0, iface=self.interface)
answer = sr1(packet, retry=self.retries, timeout=self.timeout, verbose=0)

if answer is not None:
return Host(ip, answer.hwsrc, '')
return Host(ip, answer.hwsrc, "")
34 changes: 24 additions & 10 deletions evillimiter/networking/spoof.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import time
import threading
from scapy.all import ARP, send # pylint: disable=no-name-in-module
import time

from scapy.all import ARP, send # pylint: disable=no-name-in-module

from .host import Host
from evillimiter.common.globals import BROADCAST

from .host import Host


class ARPSpoofer(object):
def __init__(self, interface, gateway_ip, gateway_mac):
Expand All @@ -25,7 +27,7 @@ def add(self, host):

host.spoofed = True

def remove(self, host, restore=True):
def remove(self, host, restore=True):
with self._hosts_lock:
self._hosts.discard(host)

Expand Down Expand Up @@ -55,26 +57,38 @@ def _spoof(self):
return

self._send_spoofed_packets(host)

time.sleep(self.interval)

def _send_spoofed_packets(self, host):
# 2 packets = 1 gateway packet, 1 host packet
packets = [
ARP(op=2, psrc=host.ip, pdst=self.gateway_ip, hwdst=self.gateway_mac),
ARP(op=2, psrc=self.gateway_ip, pdst=host.ip, hwdst=host.mac)
ARP(op=2, psrc=self.gateway_ip, pdst=host.ip, hwdst=host.mac),
]

[send(x, verbose=0, iface=self.interface) for x in packets]
[send(x, verbose=0) for x in packets]

def _restore(self, host):
"""
Remaps host and gateway to their actual addresses
"""
# 2 packets = 1 gateway packet, 1 host packet
packets = [
ARP(op=2, psrc=host.ip, hwsrc=host.mac, pdst=self.gateway_ip, hwdst=BROADCAST),
ARP(op=2, psrc=self.gateway_ip, hwsrc=self.gateway_mac, pdst=host.ip, hwdst=BROADCAST)
ARP(
op=2,
psrc=host.ip,
hwsrc=host.mac,
pdst=self.gateway_ip,
hwdst=BROADCAST,
),
ARP(
op=2,
psrc=self.gateway_ip,
hwsrc=self.gateway_mac,
pdst=host.ip,
hwdst=BROADCAST,
),
]

[send(x, verbose=0, iface=self.interface, count=3) for x in packets]
[send(x, verbose=0, count=3) for x in packets]
Loading