Network & capture · Router VM

Router VM (Kali)

A dedicated Kali Linux VM acts as the gateway for the analysis lab. Static 10.0.100.1/24 on the LAN side, iptables NAT outbound, DHCP for the bus, and a pre-installed tap pipeline that mirrors every frame to the host capture.

Why a router VM (and not the host)

Most VM apps NAT through the host's network stack. SecVF can do that too (it's the default "NAT" mode). But for analysis, routing through a dedicated guest is strictly better:

  • Tooling parity. Run tcpdump, tshark, or nmap on the router and you see exactly what malware sees on the wire. Host-side tools can't.
  • iptables / netfilter access. Full Linux firewall stack — DNAT, mangle, conntrack — without touching the host's networking.
  • Containment. If malware exploits a network stack bug, the explosion is in the router VM, not on macOS. Restore the snapshot, move on.
  • Reproducibility. The router's state — config, certs, captures — is in ~/.avf/Linux/Kali-Router.bundle/. Snapshot it, share it, version it.
  • L7 instrumentation. Easy to wedge a transparent proxy (mitmproxy, sslsplit) inline. Can't do that with host NAT.

Topology

┌─────────────────────────────────────────────────────────────┐
│  Host (macOS)                                               │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  SecVF process                                      │   │
│   │   ┌─────────────────────────────────────────────┐   │   │
│   │   │  VirtualNetworkSwitch (in-process L2/L3)    │   │   │
│   │   │  ┌──────┐  ┌──────┐  ┌──────┐  ┌─────────┐  │   │   │
│   │   │  │ VM A │  │ VM B │  │ VM C │  │  Kali   │  │   │   │
│   │   │  │      │  │      │  │      │  │ router  │  │   │   │
│   │   │  └──┬───┘  └──┬───┘  └──┬───┘  └────┬────┘  │   │   │
│   │   └─────┼─────────┼─────────┼───────────┼───────┘   │   │
│   │         └─────────┴─────────┘           │           │   │
│   │              LAN: 10.0.100.0/24         │  WAN      │   │
│   │              (no host involvement)      │  via      │   │
│   │                                         │  framework│   │
│   │                                         │  NAT      │   │
│   └─────────────────────────────────────────┼───────────┘   │
│                                             ▼               │
└─────────────────────────────────────────────────────────────┘
                                                 Internet (or FakeNet)

The router has two virtual NICs:

  • eth0 (LAN) — attached to the virtual switch with static 10.0.100.1/24. Other VMs use this as their gateway.
  • eth1 (WAN) — attached to the framework's NAT, gets DHCP from macOS. This is the only path out.

Provisioning the router

Start with a clean Kali install (see Your first VM), then attach the Scripts USB:

  1. In the VM Library, right-click the Kali VM → Mount Scripts USB… SecVF creates an HFS+ disk image with kali-router-setup.sh on it and attaches it as a USB device.
  2. Inside the guest, mount and run:
    sudo mount /dev/sdb1 /mnt
    cd /mnt
    sudo bash kali-router-setup.sh
  3. Reboot when the script finishes (sudo reboot). The router is now configured.

The script (~250 lines, source at scripts/kali-router-setup.sh) does:

  • Sets eth0 static 10.0.100.1/24 via /etc/network/interfaces.d/eth0
  • Enables IP forwarding (net.ipv4.ip_forward = 1 in /etc/sysctl.d/99-secvf.conf)
  • Installs and configures isc-dhcp-server on eth0
  • Installs and configures dnsmasq on eth0 for DNS
  • Installs analysis tooling: tcpdump, tshark, nmap, tcpflow, ngrep, bettercap
  • Adds the helper commands (secvf-status, secvf-monitor, secvf-capture) to /usr/local/bin/
  • Writes iptables rules to /etc/iptables/rules.v4 and enables netfilter-persistent
  • Disables Kali's default services that aren't needed (cups, avahi, etc.)

Network configuration

Subnet layout

RangeUse
10.0.100.1Router (eth0). Static.
10.0.100.10 – 10.0.100.99DHCP pool. Most lab VMs land here.
10.0.100.100 – 10.0.100.199Reserved for static assignments (mitmproxy hosts, FakeNet endpoints).
10.0.100.200 – 10.0.100.254Free-form; use for ad-hoc IPs.

DHCP server (isc-dhcp-server)

Config at /etc/dhcp/dhcpd.conf:

option domain-name "secvf.lab";
option domain-name-servers 10.0.100.1;
default-lease-time 600;
max-lease-time 7200;
authoritative;

subnet 10.0.100.0 netmask 255.255.255.0 {
  range 10.0.100.10 10.0.100.99;
  option routers 10.0.100.1;
  option broadcast-address 10.0.100.255;
}

DNS resolver (dnsmasq)

Default config forwards upstream to 1.1.1.1 and 9.9.9.9. Override with FakeNet by replacing /etc/dnsmasq.conf with the FakeNet variant — see FakeNet.

Firewall & NAT rules

The router's /etc/iptables/rules.v4:

*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i eth0 -p icmp -j ACCEPT
-A INPUT -i eth0 -p tcp --dport 22 -j ACCEPT      # SSH from LAN
-A INPUT -i eth0 -p udp --dport 53 -j ACCEPT      # DNS
-A INPUT -i eth0 -p udp --dport 67 -j ACCEPT      # DHCP
COMMIT

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o eth1 -j MASQUERADE                # NAT out via WAN
COMMIT

Reload with sudo iptables-restore < /etc/iptables/rules.v4 or just sudo systemctl restart netfilter-persistent.

FORWARD is ACCEPT by default. This is permissive — you almost certainly want to tighten it once the lab stabilizes. Common refinement: only allow forwarding from eth0 → eth1 (so guests can reach the internet) but drop eth1 → eth0 (so the outside can never initiate to lab guests).

Helper commands

Three commands ship with the router setup. All live in /usr/local/bin/ and run as root via sudoers entry.

secvf-status

One-shot summary of the router state. Useful first-thing diagnostic.

$ secvf-status
== SecVF Router ==
IP forwarding   : enabled
LAN (eth0)      : 10.0.100.1/24 UP
WAN (eth1)      : 192.168.64.7/24 UP (gw 192.168.64.1)
DHCP            : running (4 active leases)
DNS             : running (forwarders 1.1.1.1, 9.9.9.9)
iptables FORWARD: ACCEPT (default)
NAT             : POSTROUTING MASQUERADE on eth1
Conntrack       : 213 entries, max 65536

secvf-monitor

Live tail of new connections, DNS queries, and ARP activity. Curses-based, refreshes 4 Hz.

$ secvf-monitor
─ Connections (last 30s) ─────────────────────────
  10.0.100.15 → 1.1.1.1:53 (UDP)
  10.0.100.15 → 142.250.80.46:443 (TCP, ESTABLISHED)
  10.0.100.42 → 185.199.108.153:443 (TCP, SYN_SENT)
─ DNS (last 30s) ────────────────────────────────
  10.0.100.15 ?  google.com           → 142.250.80.46
  10.0.100.42 ?  github.io            → 185.199.108.153
  10.0.100.42 ?  evil.example.invalid → (no answer)
─ ARP ─────────────────────────────────────────
  10.0.100.15 = 52:54:00:a3:b1:90
  10.0.100.42 = 52:54:00:c8:d2:e4

secvf-capture

Wrapper around tcpdump/tshark with sensible defaults — rotating files at 100 MB each, in /var/captures/, with hostname + timestamp filenames.

$ secvf-capture start                       # capture all eth0 traffic
[+] writing /var/captures/kali-router-2026-05-10T14-32-00.pcap (rotate 100 MB)

$ secvf-capture status
running: pid 1234, 2 files, 187 MB total

$ secvf-capture stop
[+] stopped. files in /var/captures/

$ secvf-capture --host 10.0.100.15 start    # filter to one client
$ secvf-capture --port 443 --no-rotate start

In-guest capture vs host capture

You have two places you can capture traffic when running in Router-VM mode. Use both for full coverage:

LocationWhat you seeWhat you miss
Host (SecVF packet panel)Every frame on the virtual switch — before the router touches it.Internet-side traffic past the router's MASQUERADE.
Router (secvf-capture)eth1 (post-NAT) traffic — what the internet actually sees.Inter-VM lab traffic that the router never sees.

For a typical malware analysis: start the host capture before detonation (catches first-stage staging traffic, fast), and start secvf-capture on the router (gives you post-NAT correlation with anything external).

Hardening the router

Defaults are convenient, not secure. Once your lab is stable:

  1. Tighten FORWARD. Replace the default ACCEPT with a curated set of rules: allow eth0 → eth1 only, drop eth1 → eth0, optionally rate-limit.
  2. Lock down OUTPUT. The router itself shouldn't be initiating connections except to its known forwarders (DNS) and its package mirror.
  3. Disable SSH password auth. PasswordAuthentication no in /etc/ssh/sshd_config.d/secvf.conf. Only key-based access from the host.
  4. Snapshot it. Once configured, duplicate the bundle (see Snapshots). If a future analysis bricks the router, restore in seconds.
  5. Disable systemd services you don't need. cups, avahi-daemon, bluetooth — none of them belong on a router VM. The provision script does most of this; review the final list with systemctl list-unit-files --state=enabled.

Troubleshooting

SymptomLikely causeFix
Lab VMs get DHCP but no internetIP forwarding disabled, or MASQUERADE missingsysctl net.ipv4.ip_forward should be 1; iptables -t nat -L POSTROUTING -v should show MASQUERADE rule
No DHCP lease at allisc-dhcp-server not running, or listening on the wrong interfacesystemctl status isc-dhcp-server; check /etc/default/isc-dhcp-server has INTERFACESv4="eth0"
DNS resolves on host but not lab guestdnsmasq not running, or guest pointing at host's resolversystemctl status dnsmasq; in lab guest, resolvectl status should show 10.0.100.1
External hosts can reach lab VMs (bad)FORWARD is ACCEPT by default — see hardening aboveTighten FORWARD chain
Router itself can't reach the interneteth1 not getting a DHCP lease, or upstream firewalleddhclient -v eth1 from inside the router; check Apple framework's NAT range
secvf-status reports "command not found"Scripts USB not mounted, or setup script never ranRe-mount Scripts USB and re-run kali-router-setup.sh