Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5e2a046
test
frnzfk Mar 18, 2026
2fa2ee2
fix port allocation
niklashaug Mar 18, 2026
b50a169
change port in virtual
frnzfk Mar 18, 2026
0c9c1ff
fix: navigation to ctf challenges from within iframe
niklashaug Mar 18, 2026
893ff82
Merge branch 'fix-macos-docker-setup'
frnzfk Mar 18, 2026
04eb191
Merge remote-tracking branch 'origin/fix-ctf-navigation'
frnzfk Mar 18, 2026
ba5f5ba
add draft for CTF challenge
niklashaug Mar 20, 2026
92e3b49
Initial plan
Copilot Mar 23, 2026
33f0577
Add firmware-updater service for STM32 update mechanism
Copilot Mar 23, 2026
d26a2b9
Add test suite for firmware-updater service (16 tests, all passing)
Copilot Mar 23, 2026
b43e3d1
Merge branch 'main' of github.com:niklashaug/CybICS
frnzfk Mar 24, 2026
7266fa2
Merge branch 'main' of github.com:niklashaug/CybICS
frnzfk Mar 24, 2026
4b49cb3
Refactor flashing to use flash_firmware.sh (analogous to flash_if_nee…
Copilot Mar 24, 2026
6e571af
Add pyyaml to tests/requirements.txt for firmware-updater tests
Copilot Mar 24, 2026
e1df6f1
Revert "Refactor flashing to use flash_firmware.sh (analogous to flas…
zierh Mar 24, 2026
9639fd3
draft: add lifecycle status controls to challenge
niklashaug Mar 24, 2026
2fd23fe
draft: test lifecycle for ids ctf
niklashaug Mar 24, 2026
3df4bb1
draft: show setup container as floating banner
niklashaug Mar 24, 2026
22ea468
add openwrt dockerfile
frnzfk Mar 29, 2026
a960163
add tap interfaces
frnzfk Mar 29, 2026
196dc17
draft: aktueller stand mit qemu (bäääh)
frnzfk Mar 31, 2026
2f91e3b
per ssh erreichbar
frnzfk Apr 1, 2026
8693f60
refactor lifecycle process to use profile instead of service ids
niklashaug Apr 10, 2026
66eaf0c
add fastapi firmware update server
niklashaug Apr 10, 2026
6c1fb9b
change int network name
frnzfk Apr 11, 2026
c67972e
debug commit
frnzfk Apr 12, 2026
0a3bb97
add router test
frnzfk Apr 13, 2026
0d6fdc8
update hints regarding update machnism analysis step
niklashaug Apr 13, 2026
5b773c0
Merge branch 'mniedermaier:main' into main
niklashaug Apr 13, 2026
6daeafe
router jetzt gateway
frnzfk Apr 13, 2026
82098af
Merge branch '7-router-virtualsiert' of github.com:niklashaug/CybICS …
frnzfk Apr 13, 2026
a96687c
Merge branch '7-router-virtualsiert' of github.com:niklashaug/CybICS …
frnzfk Apr 13, 2026
dca30b2
Merge pull request #13 from niklashaug/7-router-virtualsiert
frnzfk Apr 13, 2026
14a5810
Merge remote-tracking branch 'origin/main' into ctf-draft
niklashaug Apr 13, 2026
9d08b49
Merge pull request #5 from niklashaug/ctf-draft
niklashaug Apr 13, 2026
6aa1e67
remove unneeded files
niklashaug Apr 13, 2026
26e0103
attempt to fix lifecycle stuff
niklashaug Apr 21, 2026
4318b19
fix: remove macOS quarantine flag from sh script
niklashaug Apr 21, 2026
14f2f82
fix: disable autostart for docker container
zierh Mar 26, 2026
7e95d3f
fix: buid zephyr with root priviliges to bypass permission issues
zierh Apr 21, 2026
1635387
testitest
frnzfk Apr 21, 2026
abdc7cc
feat: add firtual firmware flashing mode
zierh Apr 23, 2026
7f74bda
Merge branch 'copilot/implement-update-mechanism'
zierh Apr 30, 2026
ada8c75
rm legacy /landing/scripts folder from Dockerfile
niklashaug Apr 30, 2026
d6d75ec
fix: use remote submodule url instead of local path
zierh Apr 30, 2026
178d373
add polling for lifecycle status in CTF challenge
niklashaug May 5, 2026
b54fd97
improve lifecycle starting healthcheck
niklashaug May 5, 2026
09891f1
perform proper router cleanup to avoid stale docker network ids
niklashaug May 5, 2026
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: 3 additions & 0 deletions .devcontainer/stm32/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ RUN apt-get update && apt-get install -y \
openssh-client \
&& rm -rf /var/lib/apt/lists/*

# Allow root to use west extension commands in this workspace.
RUN git config --system --add safe.directory '*'

# Create user
RUN addgroup --gid 1000 docker \
&& adduser --uid 1000 --ingroup docker --home /home/docker --disabled-password --gecos "" docker \
Expand Down
3 changes: 2 additions & 1 deletion .devcontainer/stm32/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ services:
# Mount stm32 source into pre-baked Zephyr workspace
- $CYBICS_ROOT/software/stm32:/home/docker/zephyrproject/app
- ~/.ssh:/home/docker/.ssh/
user: ${HOST_UID:-1000}:${HOST_UID:-1000}
# Run as root to avoid bind-mount write failures on hosts with user namespace remapping.
user: root
89 changes: 79 additions & 10 deletions .devcontainer/virtual/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
context: ../../software/attack-machine
dockerfile: Dockerfile
hostname: attack-machine
restart: always
restart: "no"
stdin_open: true
tty: true
cap_add:
Expand Down Expand Up @@ -33,7 +33,7 @@ services:
build:
context: ../../software/OpenPLC
dockerfile: Dockerfile
restart: always
restart: "no"
privileged: true
ports:
- 8080:8080
Expand All @@ -48,7 +48,7 @@ services:
build:
context: ../../software/opcua
dockerfile: Dockerfile
restart: always
restart: "no"
ports:
- 4840:4840
depends_on:
Expand All @@ -62,7 +62,7 @@ services:
build:
context: ../../software/s7com
dockerfile: Dockerfile
restart: always
restart: "no"
ports:
- 1102:1102
depends_on:
Expand All @@ -76,7 +76,7 @@ services:
build:
context: ../../software/FUXA
dockerfile: Dockerfile
restart: always
restart: "no"
ports:
- 1881:1881
depends_on:
Expand All @@ -90,7 +90,7 @@ services:
build:
context: ../../software/hwio-virtual
dockerfile: Dockerfile
restart: always
restart: "no"
ports:
- 8090:8090
depends_on:
Expand All @@ -105,6 +105,8 @@ services:
context: ../..
dockerfile: software/landing/Dockerfile
restart: always
ports:
- 80:80
network_mode: host
cap_add:
- NET_ADMIN
Expand All @@ -119,7 +121,7 @@ services:
build:
context: ../../software
dockerfile: engineeringWS/Dockerfile
restart: always
restart: "no"
ports:
- 6080:6080
- 5901:5901
Expand All @@ -137,9 +139,9 @@ services:
build:
context: ../../software/cybicsagent
dockerfile: Dockerfile
restart: always
restart: "no"
ports:
- 5000:5000
- 5001:5000
- 11434:11434
environment:
# Recommended models: tinyllama (fast), phi3:mini (balanced), llama3.2:3b (quality)
Expand All @@ -166,13 +168,77 @@ services:
build:
context: ../../software/ids
dockerfile: Dockerfile
restart: always
restart: "no"
cap_add:
- NET_ADMIN
- NET_RAW
network_mode: host
depends_on:
- openplc
profiles:
- ids
- full

firmware-updater:
image: mniedermaier1337/cybics-firmware-updater:${CYBICS_VERSION:-latest}
build:
context: ../../software/firmware-updater
dockerfile: Dockerfile
restart: "no"
depends_on:
- stm32
volumes:
- firmware_data:/opt/cybics/firmware
- firmware_keys:/opt/cybics/keys
environment:
- UPDATE_SERVER_URL=${UPDATE_SERVER_URL:-http://update.cybics:8080}
- OPENOCD_HOST=stm32
- OPENOCD_TELNET_PORT=4444
networks:
virt-cybics:
ipv4_address: 172.18.0.8
profiles:
- firmware-server
- hardware

update-server:
image: mniedermaier1337/cybics-update-server:${CYBICS_VERSION:-latest}
build:
context: ../../software/update-server
dockerfile: Dockerfile
restart: always
environment:
- FIRMWARE_VERSION=${FIRMWARE_VERSION:-1.2.1}
- FIRMWARE_MAC=${FIRMWARE_MAC:-extended-mac}
- FIRMWARE_PATH=/opt/cybics/update-server/firmware/firmware.bin
volumes:
- firmware_data:/opt/cybics/update-server/firmware
profiles:
- firmware-server
ports:
- 6689:6689
networks:
virt-cybics:
ipv4_address: 172.18.0.9

stm32:
image: mniedermaier1337/cybics-stm32:${CYBICS_VERSION:-latest}
build:
context: ../../software/stm32
dockerfile: Dockerfile
restart: "no"
ports:
- 3333:3333
- 4444:4444
volumes:
- firmware_data:/opt/cybics/firmware
environment:
- OPENOCD_MODE=${OPENOCD_MODE:-virtual}
networks:
virt-cybics:
ipv4_address: 172.18.0.7
profiles:
- hardware

networks:
virt-cybics:
Expand All @@ -183,3 +249,6 @@ networks:
- subnet: 172.18.0.0/24
gateway: 172.18.0.1

volumes:
firmware_data:
firmware_keys:
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "software/OpenPLC/OpenPLC_v3"]
path = software/OpenPLC/OpenPLC_v3
url = ../OpenPLC_v3
url = https://github.com/mniedermaier/openplc_v3
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

---

## What is CybICS?
## What is CybICS? ZERRRRRVUS

CybICS (Cybersecurity for Industrial Control Systems) is an open-source training platform designed to help cybersecurity professionals, students, and researchers understand the unique challenges of securing industrial control systems (ICS) and SCADA environments.

Expand Down
Binary file modified doc/pics/cybics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion software/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ docker buildx build --platform linux/arm64 -t 172.17.0.1:5050/cybics-opcua:lates
docker buildx build --platform linux/arm64 -t 172.17.0.1:5050/cybics-s7com:latest --push ./s7com
docker buildx build --platform linux/arm64 -t 172.17.0.1:5050/cybics-fuxa:latest --push ./FUXA
docker buildx build --platform linux/arm64 -t 172.17.0.1:5050/cybics-stm32:latest --push ./stm32
docker buildx build --platform linux/arm64 -t 172.17.0.1:5050/cybics-update-server:latest --push ./update-server
# Build landing service from root context
docker buildx build --platform linux/arm64 -t 172.17.0.1:5050/cybics-landing:latest --push -f ./landing/Dockerfile ..

# Note: Builder automatically switches back to default on EXIT via trap
# Note: Builder automatically switches back to default on EXIT via trap
21 changes: 21 additions & 0 deletions software/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,29 @@ services:
mem_limit: 32m
ports:
- 3333:3333
volumes:
- firmware_data:/opt/cybics/firmware
networks:
br-cybics:
ipv4_address: 172.18.0.7

firmware-updater:
image: localhost:5000/cybics-firmware-updater:latest
restart: always
mem_limit: 64m
depends_on:
- stm32
volumes:
- firmware_data:/opt/cybics/firmware
- firmware_keys:/opt/cybics/keys
environment:
- UPDATE_SERVER_URL=${UPDATE_SERVER_URL:-http://update.cybics:8080}
- OPENOCD_HOST=stm32
- OPENOCD_TELNET_PORT=4444
networks:
br-cybics:
ipv4_address: 172.18.0.8

networks:
br-cybics:
name: br-cybics
Expand All @@ -106,3 +125,5 @@ networks:

volumes:
fuxa_data:
firmware_data:
firmware_keys:
21 changes: 21 additions & 0 deletions software/firmware-updater/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM python:3.11-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
openocd \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /opt/cybics/update-service

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY update_daemon.py .
COPY config.yaml .
COPY generate_mac.py .
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh

# Persistent directories (override via volume mounts)
RUN mkdir -p /opt/cybics/firmware /opt/cybics/keys /opt/cybics/logs

ENTRYPOINT ["/opt/cybics/update-service/entrypoint.sh"]
36 changes: 36 additions & 0 deletions software/firmware-updater/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# CybICS Firmware Update Service configuration
#
# Environment variable overrides (highest priority):
# UPDATE_SERVER_URL – base URL of the update server
# OPENOCD_HOST – hostname/IP of the OpenOCD server
# OPENOCD_TELNET_PORT – OpenOCD telnet port (default: 4444)
# CONFIG_PATH – path to this config file

update_server:
# Base URL of the firmware update endpoint.
# Under normal CTF operation this server does not exist (returns 404).
# Attackers redirect this hostname to their rogue server.
url: "http://update.cybics:8080"
endpoint: "/api/v1/firmware/latest"
# Polling interval in seconds
poll_interval: 30

firmware:
# Path to the currently running firmware binary
current: "/opt/cybics/firmware/current.bin"
# Path where a downloaded firmware is staged before flashing
pending: "/opt/cybics/firmware/pending.bin"

openocd:
# Hostname of the container running OpenOCD (stm32 service)
host: "stm32"
# OpenOCD telnet interface port
telnet_port: 4444

security:
# Path to the 16-byte MAC secret key (generated by entrypoint.sh)
key_path: "/opt/cybics/keys/update.key"
# Hash algorithm used for MAC: MD5(secret || firmware)
algorithm: "md5"
# Secret key length in bytes (provided as CTF hint)
secret_length: 16
22 changes: 22 additions & 0 deletions software/firmware-updater/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# CybICS Firmware Update Service – container entrypoint
#
# 1. Generates a random 16-byte MAC secret key on first run.
# 2. Starts the Python update daemon.

set -e

KEY_FILE="/opt/cybics/keys/update.key"
KEY_DIR="$(dirname "$KEY_FILE")"

if [ ! -f "$KEY_FILE" ]; then
echo "[entrypoint] Generating 16-byte MAC secret key..."
mkdir -p "$KEY_DIR"
dd if=/dev/urandom bs=16 count=1 of="$KEY_FILE" 2>/dev/null
chmod 600 "$KEY_FILE"
echo "[entrypoint] MAC key written to $KEY_FILE"
fi

mkdir -p /opt/cybics/firmware /opt/cybics/logs

exec python3 /opt/cybics/update-service/update_daemon.py
31 changes: 31 additions & 0 deletions software/firmware-updater/generate_mac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""
CTF setup helper – generate the initial MAC for a firmware binary.

Usage:
python3 generate_mac.py <firmware.bin> <key_file>

The resulting hex string is the MAC that update clients will verify.
Participants receive the firmware binary and this MAC as their starting
artefacts and must perform a Length Extension Attack to forge a valid
MAC for a modified firmware.
"""

import hashlib
import sys


def generate_mac(firmware_path: str, key_path: str) -> str:
"""Return MD5(secret || firmware) as a hex string."""
with open(firmware_path, "rb") as f:
firmware = f.read()
with open(key_path, "rb") as f:
secret = f.read()
return hashlib.md5(secret + firmware).hexdigest()

Check failure

Code scanning / CodeQL

Use of a broken or weak cryptographic hashing algorithm on sensitive data High

Sensitive data (secret)
is used in a hashing algorithm (MD5) that is insecure.


if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <firmware.bin> <key_file>", file=sys.stderr)
sys.exit(1)
print(generate_mac(sys.argv[1], sys.argv[2]))
2 changes: 2 additions & 0 deletions software/firmware-updater/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests>=2.31.0
pyyaml>=6.0
Loading
Loading