Skip to content

Commit

Permalink
optionally route via tunnel (kevoreilly#2481)
Browse files Browse the repository at this point in the history
  • Loading branch information
karlhiramoto authored Feb 6, 2025
1 parent d4859cd commit d8d6c9b
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 4 deletions.
23 changes: 19 additions & 4 deletions docs/book/src/installation/host/routing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,16 @@ Following is the list of available routing options.
+-------------------------+--------------------------------------------------+
| :ref:`routing_tor` | Routes all traffic through Tor. |
+-------------------------+--------------------------------------------------+
| :ref:`routing_tun` | Route traffic though any "tun" interface |
+-------------------------+--------------------------------------------------+
| :ref:`routing_vpn` | Routes all traffic through one of perhaps |
| | multiple pre-defined VPN endpoints. |
+-------------------------+--------------------------------------------------+
| :ref:`routing_socks` | Routes all traffic through one of perhaps |
| | multiple pre-defined VPN endpoints. |
+-------------------------+--------------------------------------------------+


Using Per-Analysis Network Routing
==================================

Expand Down Expand Up @@ -358,6 +361,18 @@ correctly.

.. _`latest stable version of Tor here`: https://www.torproject.org/docs/debian.html.en


.. _routing_tun:

Tun Routing
^^^^^^^^^^^
This allows you to route via any ``tun`` interface. You can pass the tun
interface name on demand per analysis. The interface name can be ``tunX``
or ``tun_foo``. This assumes you create the tunnel inferface outside of CAPE.

Then you set the ``route=tun_foo`` on the ``/apiv2/tasks/create/file/``
API call.

.. _routing_vpn:

VPN Routing
Expand Down Expand Up @@ -454,13 +469,13 @@ VPN persistence & auto-restart `source`_::
6. Reload the daemons:
# sudo systemctl daemon-reload

1. Start the OpenVPN service:
7. Start the OpenVPN service:
# sudo systemctl start openvpn

2. Test if it is working by checking the external IP:
8. Test if it is working by checking the external IP:
# curl ifconfig.co

3. If curl is not installed:
9. If curl is not installed:
# sudo apt install curl

.. _`source`: https://www.ivpn.net/knowledgebase/linux/linux-autostart-openvpn-in-systemd-ubuntu/
Expand Down Expand Up @@ -568,7 +583,7 @@ Assuming you already have any VM running, to test the internet connection using
$ sudo python3 router_manager.py -r internet -e --vm-name win1 --verbose
$ sudo python3 router_manager.py -r internet -d --vm-name win1 --verbose

The ``-e`` flag is used to enable a route and ``-d`` is used to disable it. You can read more about all the options the utility has by running::
The ``-e`` flag is used to enable a route and ``-d`` is used to disable it. You can read more about all the options the utility has by running::

$ sudo python3 router_manager.py -h

Expand Down
27 changes: 27 additions & 0 deletions lib/cuckoo/core/analysis_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

# os.listdir('/sys/class/net/')
HAVE_NETWORKIFACES = False

try:
import psutil

Expand All @@ -42,6 +43,11 @@

latest_symlink_lock = threading.Lock()

def is_network_interface(intf: str):
global network_interfaces
network_interfaces = list(psutil.net_if_addrs().keys())
return intf in network_interfaces


class CuckooDeadMachine(Exception):
"""Exception thrown when a machine turns dead.
Expand Down Expand Up @@ -536,6 +542,9 @@ def route_network(self):
self.rt_table = vpns[self.route].rt_table
elif self.route in self.socks5s:
self.interface = ""
elif self.route[:3] == 'tun' and is_network_interface(self.route):
# tunnel interface starts with "tun" and interface exists on machine
self.interface = self.route
else:
self.log.warning("Unknown network routing destination specified, ignoring routing for this analysis: %s", self.route)
self.interface = None
Expand Down Expand Up @@ -583,6 +592,14 @@ def route_network(self):

elif self.route in ("none", "None", "drop"):
self.rooter_response = rooter("drop_enable", self.machine.ip, str(self.cfg.resultserver.port))
elif self.route[:3] == 'tun' and is_network_interface(self.route):
self.log.info("Network interface {} is tunnel", self.interface)
self.rooter_response = rooter(
"interface_route_tun_enable",
self.machine.ip,
self.route,
str(self.task.id)
)

self._rooter_response_check()

Expand Down Expand Up @@ -714,6 +731,16 @@ def unroute_network(self):

elif self.route in ("none", "None", "drop"):
self.rooter_response = rooter("drop_disable", self.machine.ip, str(self.cfg.resultserver.port))
elif self.route[:3] == 'tun':
self.log.info("Disable tunnel interface {}", self.interface)
self.rooter_response = rooter(
"interface_route_tun_disable",
self.machine.ip,
self.route,
str(self.task.id)
)



self._rooter_response_check()

Expand Down
156 changes: 156 additions & 0 deletions utils/rooter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import errno
import grp
import json
import ipaddress
import logging.handlers
import os
import signal
Expand Down Expand Up @@ -47,6 +48,49 @@ def run(*args):
return stdout, stderr


def get_tun_peer_address(interface_name):
"""Gets the peer address of a tun interface.
Args:
interface_name: The name of the tun interface (e.g., "tun0").
Returns:
The peer IP address as a string, or None if an error occurs. Returns None if the interface does not exist, or does not have a peer.
"""
try:
result = subprocess.run(["ip", "addr", "show", interface_name], capture_output=True, text=True, check=True)
output = result.stdout

for line in output.splitlines():
if "peer" in line:
parts = line.split()
if len(parts) > 1: # Check if there's a second element to avoid IndexError
peer_with_cidr = parts[1]
try:
# Handle CIDR notation using ipaddress library
peer_ip = ipaddress.ip_interface(peer_with_cidr).ip.exploded
return peer_ip
except ValueError: # Handle invalid CIDR notations
try:
peer_ip = peer_with_cidr.split('/')[0] # Try just splitting by /
return peer_ip
except IndexError:
return None # Invalid format - give up.
else:
return None # No peer address found on the line.
return None # "peer" not found in the output

except subprocess.CalledProcessError as e:
if e.returncode == 1: # Interface not found
return None
else:
print(f"Error executing ip command: {e}")
return None
except FileNotFoundError:
print("ip command not found. Is iproute2 installed?")
return None


def enable_ip_forwarding(sysctl="/usr/sbin/sysctl"):
log.debug("Enabling IPv4 forwarding")
run(sysctl, "-w" "net.ipv4.ip_forward=1")
Expand Down Expand Up @@ -641,6 +685,116 @@ def inetsim_disable(ipaddr, inetsim_ip, dns_port, resultserver_port, ports):
run_iptables("-D", "OUTPUT", "--source", ipaddr, "-j", "DROP")


def interface_route_tun_enable(ipaddr: str, out_interface: str, task_id:str):
"""Enable routing and NAT via tun output_interface."""
log.info(f"Enabling interface routing via: {out_interface} for task: {task_id}")

# mark packets from analysis VM
run_iptables(
"-t",
"mangle",
"-I",
"PREROUTING",
"--source",
ipaddr,
"-j",
"MARK",
"--set-mark",
task_id
)

run_iptables(
"-t",
"nat",
"-I",
"POSTROUTING",
"--source",
ipaddr,
"-o",
out_interface,
"-j",
"MASQUERADE"
)
# ACCEPT forward
run_iptables(
"-t",
"filter",
"-I",
"FORWARD",
"--source",
ipaddr,
"-o",
out_interface,
"-j",
"ACCEPT"
)

# in routing table add route table task_id
run(s.ip, "rule", "add", "fwmark", task_id, "lookup", task_id)

peer_ip = get_tun_peer_address(out_interface)
if peer_ip:
log.info(f"interface_route_enable {out_interface} has peer {peer_ip}")
run(s.ip, "route", "add", "default", "via", peer_ip, "table", task_id)
else:
log.error("interface_route_enable missing peer IP ")

def interface_route_tun_disable(ipaddr: str, out_interface: str, task_id:str):
"""Disable routing and NAT via tun output_interface."""
log.info(f"Disable interface routing via: {out_interface} for task: {task_id}")

# mark packets from analysis VM
run_iptables(
"-t",
"mangle",
"-D",
"PREROUTING",
"--source",
ipaddr,
"-j",
"MARK",
"--set-mark",
task_id
)

run_iptables(
"-t",
"nat",
"-D",
"POSTROUTING",
"--source",
ipaddr,
"-o",
out_interface,
"-j",
"MASQUERADE"
)
# ACCEPT forward
run_iptables(
"-t",
"filter",
"-D",
"FORWARD",
"--source",
ipaddr,
"-o",
out_interface,
"-j",
"ACCEPT"
)

# in routing table add route table task_id
run(s.ip, "rule", "del", "fwmark", task_id, "lookup", task_id)

peer_ip = get_tun_peer_address(out_interface)
if peer_ip:
log.info(f"interface_route_disable {out_interface} has peer {peer_ip}")
run(s.ip, "route", "del", "default", "via", peer_ip, "table", task_id)
else:
log.error("interface_route_disable missing peer IP ")



def socks5_enable(ipaddr, resultserver_port, dns_port, proxy_port):
"""Enable hijacking of all traffic and send it to socks5."""
log.info("Enabling socks route.")
Expand Down Expand Up @@ -750,6 +904,8 @@ def drop_disable(ipaddr, resultserver_port):
"srcroute_disable": srcroute_disable,
"inetsim_enable": inetsim_enable,
"inetsim_disable": inetsim_disable,
"interface_route_tun_enable": interface_route_tun_enable,
"interface_route_tun_disable": interface_route_tun_disable,
"socks5_enable": socks5_enable,
"socks5_disable": socks5_disable,
"drop_enable": drop_enable,
Expand Down

0 comments on commit d8d6c9b

Please sign in to comment.