0x74 0x68 0x65 0x72 0x65 0x20 0x61 0x72 0x65 0x20 0x31 0x30 0x20 0x74 0x79 0x70 0x65 0x73 0x20 0x6f 0x66 0x20 0x70 0x65 0x6f 0x70 0x6c 0x65 0x74 0x68 0x6f 0x73 0x65 0x20 0x77 0x68 0x6f 0x20 0x75 0x6e 0x64 0x65 0x72 0x73 0x74 0x61 0x6e 0x64 0x20 0x62 0x69 0x6e 0x61 0x72 0x79 0x20 0x61 0x6e 0x64 0x20 0x74 0x68 0x6f 0x73 0x65 0x20 0x77 0x68 0x6f 0x20 0x64 0x6f 0x6e 0x74
01110100 01101000 01100101 01110010 01100101 00100000 01100001 01110010 01100101 00100000 00110001 00110000 00100000 01110100 01111001 01110000 01100101 01110011 0xdeadbeef 0xcafebabe 0x1337
0x74 0x68 0x6f 0x73 0x65 0x20 0x77 0x68 0x6f 0x20 0x75 0x6e 0x64 0x65 0x72 0x73 0x74 0x61 0x6e 0x64 0x20 0x62 0x69 0x6e 0x61 0x72 0x79 0x20 0x61 0x6e 0x64 0x20 0x74 0x68 0x6f 0x73 0x65 0x20 0x77 0x68 0x6f 0x20 0x64 0x6f 0x6e 0x74 0x74 0x68 0x65 0x72 0x65 0x20 0x61 0x72 0x65 0x20 0x31 0x30 0x20 0x74 0x79 0x70 0x65 0x73 0x20 0x6f 0x66 0x20 0x70 0x65 0x6f 0x70 0x6c 0x65
01010111 01100101 00100000 01101000 01100001 01100011 01101011 00100000 01110100 01101000 01100101 00100000 01110000 01101100 01100001 01101110 01100101 01110100 01100110 00110000 01111000 01100110 00110100 01100100 01100101
0x6e 0x65 0x76 0x65 0x72 0x20 0x67 0x6f 0x6e 0x6e 0x61 0x20 0x64 0x72 0x6f 0x70 0x20 0x79 0x6f 0x75 0x72 0x20 0x73 0x68 0x65 0x6c 0x6c 0x20 0x6e 0x65 0x76 0x65 0x72 0x20 0x67 0x6f 0x6e 0x6e 0x61 0x20 0x6b 0x69 0x6c 0x6c 0x20 0x79 0x6f 0x75 0x72 0x20 0x74 0x68 0x72 0x65 0x61 0x64 0x20 0x6e 0x65 0x76 0x65 0x72 0x20 0x67 0x6f 0x6e 0x6e 0x61 0x20 0x6c 0x6f 0x73 0x65 0x20 0x79 0x6f 0x75 0x72 0x20 0x70 0x61 0x63 0x6b 0x65 0x74 0x20 0x61 0x6e 0x64 0x20 0x64 0x65 0x73 0x65 0x72 0x74 0x20 0x79 0x6f 0x75 0x72 0x20 0x71 0x75 0x65 0x75 0x65
Published on

Building the South Park Range with Ludus

Building the South Park Range with Ludus

Series

Delivery And Control

  1. 01Stagers, Agents, and the Chain Between Them
  2. 02Building the South Park Range with Ludus
  3. 03Writing a Stager

Before we build anything we need somewhere to test it. This post sets up the range we use throughout Delivery and Control: a small AD environment with a redirector in front of the C2, all provisioned with Ludus.

🎉 Celebrate

Huge shoutout to Bad Sector Labs (Erik) for the amazing work creating Ludus! If you haven't already, come join the conversations in the Ludus Discord


What Ludus Is

Ludus runs on bare metal with Proxmox underneath. You write a YAML config, run a deploy, and it handles provisioning, Windows domain setup, domain joins, firewall rules, DNS, and Ansible role execution.

A few things it gives you:

  • Repeatable deploys. The config is a file. Starting over is one command.
  • Snapshots. Deploy, snapshot, detonate a payload, roll back, iterate. Rebuilding a domain every time you test something is not viable.
  • Roles. Ansible Galaxy roles you reference in the config get installed and run during deploy. This range uses netpenguins.ludus_redirector to configure Apache on the redirector VM automatically.
  • Network segmentation. VLAN firewall rules live in the same config as the VMs.

Installation

Install guide is at docs.ludus.cloud. Needs a dedicated bare-metal host with virtualization extensions, not a VM.

Come back once this returns output:

ludus range list

Roles

This range uses netpenguins.ludus_redirector. It configures Apache with mod_rewrite to proxy specific URI paths to the Kali C2.

Install the role before deploying:

ludus ansible role add netpenguins.ludus_redirector

Docs for the role system, writing your own, and what community roles exist: docs.ludus.cloud/docs/configuration/roles.


Range Layout

Four VMs across three VLANs:

HostnameRoleVLANIP
SPDC01-2022Domain Controller (Server 2022)1010.X.10.11
SPWIN11-25H2-1Workstation (Windows 11, domain joined)1010.X.10.21
REDIRECTORApache redirector (Debian 12)10010.X.100.10
KALIC2 (Kali)20010.X.200.5

X is the second octet Ludus assigns to your range.

Network rules:

  • Targets (VLAN 10) hit the redirector on 80 and 443 only. No direct access to Kali.
  • Redirector and Kali talk freely in both directions.
  • Everything else drops.

The redirector gets DNS rewrites redir.ludus and *.redir.ludus. Kali gets c2.ludus and *.c2.ludus. Use the hostnames in your configs instead of hardcoded IPs.


The Config

# yaml-language-server: $schema=https://docs.ludus.cloud/schemas/range-config.json
ludus:
  - vm_name: "{{ range_id }}-SPDC01-2022"
    hostname: "{{ range_id }}-SPDC01-2022"
    template: win2022-server-x64-template
    vlan: 10
    ip_last_octet: 11
    ram_gb: 8
    cpus: 4
    windows:
      sysprep: false
    domain:
      fqdn: southpark.com
      role: primary-dc

  - vm_name: "{{ range_id }}-SPWIN11-25H2-1"
    hostname: "{{ range_id }}-SPWIN11-25H2-1"
    template: win11-25h2-x64-enterprise-template
    vlan: 10
    ip_last_octet: 21
    ram_gb: 8
    cpus: 4
    windows:
      install_additional_tools: true
      office_version: 2021
      office_arch: 64bit
      chocolatey_ignore_checksums: true
      chocolatey_packages:
        - ilspy
        - vscodium
        - sysinternals
        - googlechrome
    domain:
      fqdn: southpark.com
      role: member

  - vm_name: REDIRECTOR
    hostname: redirector
    template: debian-12-x64-server-template
    vlan: 100
    ip_last_octet: 10
    ram_gb: 2
    cpus: 2
    linux: true
    dns_rewrites:
      - redir.ludus
      - '*.redir.ludus'
    roles:
      - netpenguins.ludus_redirector
    role_vars:
      redirector_host: 'c2.redir.ludus'
      redirector_target_host: "c2.ludus"
      kali_vlan_subnet: "10.{{ range_second_octet }}.200.0/24"
      rewrite_rules:
        - 'RewriteRule ^/l33t(/.*)?$ https://10.{{ range_second_octet }}.200.5/l33t$1 [P,L]'
        - 'RewriteRule ^/g3t(/.*)?$ http://10.{{ range_second_octet }}.200.5/g3t$1 [P,L]'

  - vm_name: KALI
    hostname: kali
    template: kali-x64-desktop-template
    dns_rewrites:
      - "c2.ludus"
      - "*.c2.ludus"
    vlan: 200
    ip_last_octet: 5
    ram_gb: 4
    cpus: 2
    linux: true

network:
  inter_vlan_default: DROP
  external_default: ACCEPT
  rules:
    - name: Allow Kali to Redirector
      vlan_src: 200
      vlan_dst: 100
      protocol: "all"
      ports: "all"
      action: ACCEPT
    - name: Allow Redirector to Kali
      vlan_src: 100
      vlan_dst: 200
      protocol: "all"
      ports: "all"
      action: ACCEPT
    - name: Allow Target to Redirector HTTP
      vlan_src: 10
      vlan_dst: 100
      protocol: tcp
      ports: 80
      action: ACCEPT
    - name: Allow Target to Redirector HTTPS
      vlan_src: 10
      vlan_dst: 100
      protocol: tcp
      ports: 443
      action: ACCEPT

Things worth knowing about the redirector config:

  • redirector_host is what Apache presents as. redirector_target_host is where it proxies matching traffic.
  • rewrite_rules define the URI paths that are C2 traffic. /l33t goes to Kali over HTTPS, /g3t over HTTP. Anything else does not match and does not forward.
  • range_second_octet is injected by Ludus at deploy time. Do not hardcode the IP.
  • inter_vlan_default: DROP means if your payload tries to call back to Kali directly instead of through the redirector, it gets nothing.
  • external_default: ACCEPT leaves outbound internet open so Kali and the redirector can pull packages.

Deploying

ludus range config set -f southpark-range.yaml
ludus range deploy

Takes 20-40 minutes. Watch it with:

ludus range logs -f

When done:

ludus range list

All four VMs up, and the redirector role has already run so Apache is configured.


Snapshot

Take a clean snapshot before touching anything:

ludus range snapshot create --name clean-deploy

Roll back with:

ludus range snapshot revert --name clean-deploy

Name by state not by date. You will want mythic-ready, defender-on, defender-off as you go. Dates mean nothing later.


Accessing the Range

Ludus uses WireGuard. Once connected:

ludus range info  # credentials and IPs
ssh [email protected]

RDP to the workstation at 10.X.10.21 with the domain creds from range info.


Installing Mythic on Kali

sudo apt update && sudo apt install -y docker.io docker-compose git
sudo systemctl enable --now docker
sudo usermod -aG docker kali
newgrp docker
git clone https://github.com/its-a-feature/Mythic
cd Mythic
make

make generates a .env with randomized credentials. Grab them:

grep -E "MYTHIC_ADMIN|PASSWORD" .env

Start it:

./mythic-cli start

Install Apollo and the HTTP profile:

./mythic-cli install github https://github.com/MythicAgents/Apollo
./mythic-cli install github https://github.com/MythicC2Profiles/http
./mythic-cli start

Restart is required after installing. Mythic has to compile Apollo before it shows up in the payload generator.

UI at https://10.X.200.5:7443. Snapshot when it is up:

ludus range snapshot create --name mythic-ready

Verify the Plumbing

From Kali:

ping 10.X.10.11   # DC
ping 10.X.10.21   # workstation
curl -v http://10.X.100.10/g3t/test  # should proxy through to Kali

From the workstation in PowerShell:

Test-NetConnection -ComputerName 10.X.100.10 -Port 443  # should connect
Test-NetConnection -ComputerName 10.X.200.5  -Port 443  # should time out

If Kali is unreachable directly, the network rules are working.


What You Have

  • southpark.com domain, Server 2022 DC, Windows 11 workstation with Office
  • Debian redirector with Apache proxying /l33t and /g3t paths to Kali
  • Kali running Mythic with Apollo and HTTP profile installed
  • Targets can only reach the redirector, not the C2 directly
  • Clean and mythic-ready snapshots to work from

Next post is the loader.