diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3cdbe77 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,16 @@ +name: ci +on: + push: + branches: + - master + - main +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install -r requirements.txt + - run: mkdocs gh-deploy --force \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1952316 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Enowars Documentation + +## Development + +You need to have python installed or just use GitPod +```bash +# 1. Install Dependencies +pip install -r requirements.txt + +# 2. Run the preview locally +mkdocs serve +# 3. Make your changes and see them update live +# 4. Commit and the CI Pipeline will deploy your changes. +``` + +To get to know what is possible with MkDocs have a look at the [Documentation](https://squidfunk.github.io/mkdocs-material/reference/abbreviations/). \ No newline at end of file diff --git a/docs/assets/bambictf5.png b/docs/assets/bambictf5.png new file mode 100644 index 0000000..b558eb7 Binary files /dev/null and b/docs/assets/bambictf5.png differ diff --git a/docs/assets/favicon.png b/docs/assets/favicon.png new file mode 100644 index 0000000..9bc9239 Binary files /dev/null and b/docs/assets/favicon.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..986397b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,70 @@ +# Welcome to Enowars + +Enowars is a Framework for running A/D CTF events. + +## Overview + +It's complex: + + +```mermaid +graph TB + EnoLauncher + EnoEngine + EnoFlagSink + EnoELK + database[(Database)] + router{Router} + checker1[[Checker 1..N]] + + + router --> team + router --> EnoFlagSink + EnoEngine -->|send logs| EnoELK + EnoEngine -->|send scoreboard data| EnoScoreboard + EnoEngine -->|plan checks| database + database -->|get reports| EnoEngine + EnoLauncher -->|run| checker1 + EnoLauncher -->|report| database + EnoFlagSink --> database + checker1 --> router + checker1 -->|send logs| EnoELK + + + + subgraph teams[Team Networks] + team[Team 1..N] + end +``` +Now that you've seen this beautiful π chart everything should be clear. For everyone else see below. + + +### Infrastructure + +Click here to get up and running with the underlying [OS and Network stuff](infrastructure/infrastructure.md). + +If you are the software guy/girl have a look at the [engine](infrastructure/engine.md). + +### Services and Checkers + +If you want to program your own service and submit it to us have a look at our [Create a service Guide](service/getting-started.md). + +> Previous [Services](https://github.com/enowars?q=enowars) can be found on our [Github Page](https://github.com/enowars?q=enowars) + +### Miscellaneous + +To support everything we have an army of rogue shell and python scripts, as well as Libraries for testing. + +[EnoChecker](https://github.com/enowars/enochecker) + + + + + + + + + +> TBD: [Specification](https://github.com/enowars/specification) + +--8<-- "includes/abbreviations.md" \ No newline at end of file diff --git a/docs/infrastructure/engine.md b/docs/infrastructure/engine.md new file mode 100644 index 0000000..f5be68c --- /dev/null +++ b/docs/infrastructure/engine.md @@ -0,0 +1,21 @@ +# Engine Installation + + +## Overview +The [Engine](https://github.com/enowars/EnoEngine) is the core Software glueing everything togehter, it consists of: + + - EnoEngine + - EnoLauncher + - EnoFlagSink + - EnoELK + * Elasticsearch + (search engine, noSQL) + * Logstash (ingest and transform data) + * Kibana (webfrontend) + - EnoMoloch + - [ScoreBoard](https://github.com/enowars/EnoLandingPage) + + +## Setup + +> How to setup the engine for a CTF? diff --git a/docs/infrastructure/infrastructure.md b/docs/infrastructure/infrastructure.md new file mode 100644 index 0000000..7ee4760 --- /dev/null +++ b/docs/infrastructure/infrastructure.md @@ -0,0 +1,5 @@ +# Infrastructure Setup + +## Getting started + +Everything is inside here: https://github.com/enowars/bambictf \ No newline at end of file diff --git a/docs/infrastructure/round.md b/docs/infrastructure/round.md new file mode 100644 index 0000000..024bcb1 --- /dev/null +++ b/docs/infrastructure/round.md @@ -0,0 +1,56 @@ +# Inner Workings + +In order to store flags to capture and check whether a teams service is still running nominally the Engine dispatches several requests in each round. + +## Request Types + +| Request | Purpose | +| :--------- | :------------------------------------------ | +| `putflag` | Inserts the flag into the service | +| `getflag` | Retrieves the flag from the service | +| `havoc` | Checks the service functionality | +| `putnoise` | Insert other (public) data into the service | +| `getnoise` | Check other (public) data | + +## Basic requests + +```mermaid +sequenceDiagram + Gameserver->>+Checker: putflag + Checker->>+Service: store flag + Gameserver->>+Checker: getflag + Checker->>+Service: retrieve flag + Service->>+Checker: retrieve flag + +``` + +## Timing + +One round generally lasts 60 seconds. It is divided into 4 quarters, which each last 15 seconds. +The checker tasks are called in the depicted way: + +> TODO: Are those scheduled right (the slides differ)? + +```mermaid +gantt + title Timing + dateFormat mm-ss + axisFormat %M-%S + section Round 1 + + putflag (Round 1 flags) :r1p1, 00-00, 15s + getflag (old flags) :r0g2, 00-00, 15s + + getflag (old flags) :r0g2, 00-30, 15s + havoc :r1h1, 00-30, 15s + putnoise :r1pn1, 00-30, 15s + + getflag (Round 1 flags) :r1g1, 00-45, 15s + getflag (old flags) :r0g3, 00-45, 15s + + section Round 2 + + putflag :a1, 01-00, 15s + +``` + diff --git a/docs/infrastructure/test-setup.md b/docs/infrastructure/test-setup.md new file mode 100644 index 0000000..9e21018 --- /dev/null +++ b/docs/infrastructure/test-setup.md @@ -0,0 +1,17 @@ +# Test Setup + +If you want a cheap way to test all of your service and infrastructure on one VM, that's you guide. + +## Installation + + + + +## Managing the server + +```bash +# Start Engine Services +bash tmux.sh +# Monitor EnoEngine +tmux a -t enoengine_session +``` diff --git a/docs/js/mermaid-loader.js b/docs/js/mermaid-loader.js new file mode 100644 index 0000000..d15aaa6 --- /dev/null +++ b/docs/js/mermaid-loader.js @@ -0,0 +1,336 @@ +// From https://facelessuser.github.io/pymdown-extensions/extras/mermaid/#custom-loader + + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +(function () { + 'use strict'; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function"); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + writable: true, + configurable: true + } + }); + if (superClass) _setPrototypeOf(subClass, superClass); + } + + function _getPrototypeOf(o) { + _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); + } + + function _setPrototypeOf(o, p) { + _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + + return _setPrototypeOf(o, p); + } + + function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + + try { + Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () { })); + return true; + } catch (e) { + return false; + } + } + + function _construct(Parent, args, Class) { + if (_isNativeReflectConstruct()) { + _construct = Reflect.construct; + } else { + _construct = function _construct(Parent, args, Class) { + var a = [null]; + a.push.apply(a, args); + var Constructor = Function.bind.apply(Parent, a); + var instance = new Constructor(); + if (Class) _setPrototypeOf(instance, Class.prototype); + return instance; + }; + } + + return _construct.apply(null, arguments); + } + + function _isNativeFunction(fn) { + return Function.toString.call(fn).indexOf("[native code]") !== -1; + } + + function _wrapNativeSuper(Class) { + var _cache = typeof Map === "function" ? new Map() : undefined; + + _wrapNativeSuper = function _wrapNativeSuper(Class) { + if (Class === null || !_isNativeFunction(Class)) return Class; + + if (typeof Class !== "function") { + throw new TypeError("Super expression must either be null or a function"); + } + + if (typeof _cache !== "undefined") { + if (_cache.has(Class)) return _cache.get(Class); + + _cache.set(Class, Wrapper); + } + + function Wrapper() { + return _construct(Class, arguments, _getPrototypeOf(this).constructor); + } + + Wrapper.prototype = Object.create(Class.prototype, { + constructor: { + value: Wrapper, + enumerable: false, + writable: true, + configurable: true + } + }); + return _setPrototypeOf(Wrapper, Class); + }; + + return _wrapNativeSuper(Class); + } + + function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return self; + } + + function _possibleConstructorReturn(self, call) { + if (call && (_typeof(call) === "object" || typeof call === "function")) { + return call; + } + + return _assertThisInitialized(self); + } + + function _createSuper(Derived) { + var hasNativeReflectConstruct = _isNativeReflectConstruct(); + + return function _createSuperInternal() { + var Super = _getPrototypeOf(Derived), + result; + + if (hasNativeReflectConstruct) { + var NewTarget = _getPrototypeOf(this).constructor; + + result = Reflect.construct(Super, arguments, NewTarget); + } else { + result = Super.apply(this, arguments); + } + + return _possibleConstructorReturn(this, result); + }; + } + /* Notes (as of Mermaid 8.7.0): + * - Gantt: width is always relative to the parent, if you have a small parent, the chart will be squashed. + * Can't help it. + * - Journey: Suffers from the same issues that Gantt does. + * - Pie: These charts have no default height or width. Good luck pinning them down to a reasonable size. + * - Git: The render portion is agnostic to the size of the parent element. But padding of the SVG is relative + * to the parent element. You will never find a happy size. + */ + + /** + * Targets special code or div blocks and converts them to UML. + * @param {string} className is the name of the class to target. + * @return {void} + */ + + + var uml = function uml(className) { + // Custom element to encapsulate Mermaid content. + var MermaidDiv = /*#__PURE__*/function (_HTMLElement) { + _inherits(MermaidDiv, _HTMLElement); + + var _super = _createSuper(MermaidDiv); + /** + * Creates a special Mermaid div shadow DOM. + * Works around issues of shared IDs. + * @return {void} + */ + + + function MermaidDiv() { + var _this; + + _classCallCheck(this, MermaidDiv); + + _this = _super.call(this); // Create the Shadow DOM and attach style + + var shadow = _this.attachShadow({ + mode: "open" + }); + + var style = document.createElement("style"); + style.textContent = "\n :host {\n display: block;\n line-height: initial;\n font-size: 16px;\n }\n div.mermaid {\n margin: 0;\n overflow: visible;\n }"; + shadow.appendChild(style); + return _this; + } + + return MermaidDiv; + }( /*#__PURE__*/_wrapNativeSuper(HTMLElement)); + + if (typeof customElements.get("mermaid-div") === "undefined") { + customElements.define("mermaid-div", MermaidDiv); + } + + var getFromCode = function getFromCode(parent) { + // Handles
text extraction.
+ var text = "";
+
+ for (var j = 0; j < parent.childNodes.length; j++) {
+ var subEl = parent.childNodes[j];
+
+ if (subEl.tagName.toLowerCase() === "code") {
+ for (var k = 0; k < subEl.childNodes.length; k++) {
+ var child = subEl.childNodes[k];
+ var whitespace = /^\s*$/;
+
+ if (child.nodeName === "#text" && !whitespace.test(child.nodeValue)) {
+ text = child.nodeValue;
+ break;
+ }
+ }
+ }
+ }
+
+ return text;
+ }; // We use this to determine if we want the dark or light theme.
+ // This is specific for our MkDocs Material environment.
+ // You should load your configs based on your own environment's needs.
+
+
+ var defaultConfig = {
+ startOnLoad: false,
+ theme: "default",
+ flowchart: {
+ htmlLabels: false
+ },
+ er: {
+ useMaxWidth: false
+ },
+ sequence: {
+ useMaxWidth: false,
+ noteFontWeight: "14px",
+ actorFontSize: "14px",
+ messageFontSize: "16px"
+ }
+ };
+ mermaid.mermaidAPI.globalReset(); // Non Material themes should just use "default"
+
+ var scheme = null;
+
+ try {
+ scheme = document.querySelector("[data-md-color-scheme]").getAttribute("data-md-color-scheme");
+ } catch (err) {
+ scheme = "default";
+ }
+
+ var config = typeof mermaidConfig === "undefined" ? defaultConfig : mermaidConfig[scheme] || mermaidConfig["default"] || defaultConfig;
+ mermaid.initialize(config); // Find all of our Mermaid sources and render them.
+
+ var blocks = document.querySelectorAll("pre.".concat(className, ", mermaid-div"));
+ var surrogate = document.querySelector("html");
+
+ var _loop = function _loop(i) {
+ var block = blocks[i];
+ var parentEl = block.tagName.toLowerCase() === "mermaid-div" ? block.shadowRoot.querySelector("pre.".concat(className)) : block; // Create a temporary element with the typeset and size we desire.
+ // Insert it at the end of our parent to render the SVG.
+
+ var temp = document.createElement("div");
+ temp.style.visibility = "hidden";
+ temp.style.display = "display";
+ temp.style.padding = "0";
+ temp.style.margin = "0";
+ temp.style.lineHeight = "initial";
+ temp.style.fontSize = "16px";
+ surrogate.appendChild(temp);
+
+ try {
+ mermaid.mermaidAPI.render("_mermaid_".concat(i), getFromCode(parentEl), function (content) {
+ var el = document.createElement("div");
+ el.className = className;
+ el.innerHTML = content; // Insert the render where we want it and remove the original text source.
+ // Mermaid will clean up the temporary element.
+
+ var shadow = document.createElement("mermaid-div");
+ shadow.shadowRoot.appendChild(el);
+ block.parentNode.insertBefore(shadow, block);
+ parentEl.style.display = "none";
+ shadow.shadowRoot.appendChild(parentEl);
+
+ if (parentEl !== block) {
+ block.parentNode.removeChild(block);
+ }
+ }, temp);
+ } catch (err) { } // eslint-disable-line no-empty
+
+
+ if (surrogate.contains(temp)) {
+ surrogate.removeChild(temp);
+ }
+ };
+
+ for (var i = 0; i < blocks.length; i++) {
+ _loop(i);
+ }
+ };
+
+ (function () {
+ var onReady = function onReady(fn) {
+ document.addEventListener("DOMContentLoaded", fn);
+ document.addEventListener("DOMContentSwitch", fn);
+ };
+
+ var observer = new MutationObserver(function (mutations) {
+ mutations.forEach(function (mutation) {
+ if (mutation.type === "attributes") {
+ var scheme = mutation.target.getAttribute("data-md-color-scheme");
+
+ if (!scheme) {
+ scheme = "default";
+ }
+
+ localStorage.setItem("data-md-color-scheme", scheme);
+
+ if (typeof mermaid !== "undefined") {
+ uml("mermaid");
+ }
+ }
+ });
+ });
+ onReady(function () {
+ observer.observe(document.querySelector("body"), {
+ attributeFilter: ["data-md-color-scheme"]
+ });
+
+ if (typeof mermaid !== "undefined") {
+ uml("mermaid");
+ }
+ });
+ })();
+})();
\ No newline at end of file
diff --git a/docs/play.md b/docs/play.md
new file mode 100644
index 0000000..83660e9
--- /dev/null
+++ b/docs/play.md
@@ -0,0 +1,84 @@
+# Playing a CTF
+
+## General
+
+A typical attack/defense CTF consists of three components.
+
+### The Gameserver
+
+It is provided by the organizers and runs throughout the competition, starting when the network is opened. It periodically stores flags on your Vulnbox using functionality in the provided services. It then later retrieves these flags, again using existing functionality. The Gameserver does not run exploits! It simply uses the service as intended.
+Now, why can't the other teams then simply do what the Gameserver does?
+The Gameserver has more information. Every service is either designed to allow the Gameserver to store a specific token for each flag or generates one and returns it to the Gameserver.
+The Gameserver uses this token to check periodically that the flag is still there. Whether or not it gets the stored flag using that token, determines your SLA (Service Level Agreement). You mustn't remove or break any legitimate functionality.
+Some services can have a vulnerability that directly leaks the flag, which will let you retrieve the flag easily. For others, it will require more effort.
+
+### Your Vulnbox
+
+The Vulnbox is your running instance of the virtual machine image given to you by the organizers. It contains and runs all the services of the competition and should be reachable at all times. The Gameserver stores its flags here and uses the communication with this machine to decide if your services are working as intended or not. This machine is accessible to everyone on the network, and is the target for all the exploits from other teams.
+Protecting the flags on this machine is what determines your defense points!
+You normally have one hour from getting access to your Vulnbox until the network between teams is opened and everyone can attack each other. Use this time to get the VM running, then start analyzing what's running on it. It has happened that services with vulnerabilities that are easy to find have been exploited as soon as the actual competition starts.
+For the Bambi CTF, we will be providing hosted vulnboxes which are accessible via SSH.
+
+### The other teams
+
+All the other registered teams are connected to the same VPN as you. Their Vulnboxes have known IP addresses, all other machines are off-limits! The other teams will run exploits from their own machines, but the VPN infrastructure will use NAT to obfuscate whether a packet came from the Gameserver or another team.
+Successfully stealing and submitting flags from the Vulnbox of other teams determines your attack score!
+If you have played jeopardy CTFs before, you already know flag submission. In this game however, you'll have to run you exploits periodically, as new flags get stored by the Gameserver every few minutes. So you probably want to script exploits and submit Flags automatically and you don't spend all your time manually exploiting everyone.
+
+> Adapted from [FAUST CTF](https://2020.faustctf.net/information/attackdefense-for-beginners/)
+
+## Network
+
+Summary
+
+ - Game Network:
10.0.0.0/16
+ - Team Vulnbox:
10.0.0.{TeamId}/32
+ - Game Router:
10.0.1.1
+ - Flag Submission:
10.0.13.37:1337
+
+
+```mermaid
+graph LR
+ gamerouter[Game Router] --> flagsubmission[Flag Submission]
+
+ subgraph team1[Team 1]
+ vuln1[Vulnbox Team 1] --> gamerouter
+ player1[Player 1]--> vpn1[VPN]
+ vpn1 --> vuln1
+ end
+
+ subgraph team2[Team 2]
+ vuln2[Vulnbox Team 2] --> gamerouter
+ player2[Player 2]--> vpn2[VPN]
+ vpn2 --> vuln2
+ end
+
+
+ subgraph teamN[Team N]
+ vulnN[Vulnbox Team N] --> gamerouter
+ playerN[Player N]--> vpnN[VPN]
+ vpnN --> vulnN
+ end
+
+
+```
+
+To access your vulnbox, you must first start it. This will only be possible once the competition has started. **Note that you must check in before the competition starts, otherwise you will not be able to start your vulnbox**.
+
+> Please refer to the [rules page](/docs/rules.md) for more information.
+
+After the vulnbox is started, you will be shown your public vulnbox IP address and the root password which you can use to login. You will also be able to download an OpenVPN configuration file only after the vulnbox is started. See the section below for more details.
+
+During the first hour of the CTF, the game network will be closed. This means you will not be able to reach the vulnboxes of other teams through the competition network. To check that your vulnbox and OpenVPN client is working as intended, you can try pinging `10.0.1.1`, which is the game router and should also be reachable even while the game network is closed.
+
+The router performs SNAT on all game traffic, so all incoming traffic appears to be coming from `10.0.1.1`, regardless of whether it is coming from the game engine or other teams.
+
+The `10.0.240.0/24` subnet for the team VPN is identical for all teams. These networks are completely separated from each other and you will not be able to access the devices in the VPN of other teams.
+
+You can get a newline-separated list of all (confirmed) vulnbox addresses at the game portal
+
+## OpenVPN Access for Players
+
+We will provide an OpenVPN server through which you can access your vulnbox and the vulnboxes of the other teams (the latter only once the network has been opened).
+To get seamless access to the game network, install the OpenVPN client for your operating system, and download the client configuration file that will be provided in the enowars portal. The config file will only be available once your have started your vulnbox. Note that the vulnbox serves as OpenVPN server for your team, so you will only be able to access the game network while your own vulnbox is running. The OpenVPN config file can be shared by all members of your team and allows multiple connections at once.
+Please consult a search engine of your choice or the OpenVPN documentation for help getting started with OpenVPN.
diff --git a/docs/rules.md b/docs/rules.md
new file mode 100644
index 0000000..be4dd72
--- /dev/null
+++ b/docs/rules.md
@@ -0,0 +1,26 @@
+# Rules
+
+Flag format: `ENO[A-Za-z0-9+\/=]{48}`
+
+The game will start on the specified time (UTC time!).
+You must register at least X hours before the specified time.
+
+You must check in between X hours and X hours before the start time.
+
+You must start your vulnbox once the game starts.
+A round lasts 60 seconds, flags are valid for several rounds.
+
+Flag submission: `netcat flags.bambi.ovh 1337`
+
+## Scoring
+
+We are currently using the scoring formula by [Faust CTF]()https://2019.faustctf.net/information/rules/.
+
+## Social Conduct
+
+1. The vulnerable services of your opponents are your only valid targets. Do not engage anything else!
+
+2. Do not attempt to exhaust resources on your opponents' vulnboxes, for example by sending excessive amounts of requests or exploiting vulnerabilities leading to a denial of service.
+
+Vulnboxes and VPN servers are provided by us, you don't have to provide
+or take care of anything.
diff --git a/docs/service/checker/checker-python.md b/docs/service/checker/checker-python.md
new file mode 100644
index 0000000..c6f3c2b
--- /dev/null
+++ b/docs/service/checker/checker-python.md
@@ -0,0 +1,145 @@
+We provide a [checker library](https://github.com/enowars/enochecker) for an easy start.
+
+
+## Development
+For local development execute
+
+```bash
+cd checker/src
+gunicorn -c gunicorn.conf.py checker:app
+```
+
+You can add those settings to you `checker/src/gunicorn.conf.py` to make your development faster.
+
+``` py
+reload = True
+loglevel = 'debug'
+```
+
+## Writing
+
+For a simple checker, subclass `BaseChecker` ([API Documentation](https://enowars.github.io/enochecker/enochecker.html)).
+
+``` python
+from enochecker import BaseChecker, BrokenServiceException, run
+
+class AwesomeChecker(BaseChecker):
+ flag_count = 2
+ noise_count = 1
+ havoc_count = 1
+
+
+ def putflag(self): # type: () -> None
+ # TODO: Put flag to service
+ self.debug("flag is {}".format(self.flag))
+ self.http_post("/putflaghere", params={"flag": self.flag})
+ # ...
+
+ def getflag(self): # type: () -> None
+ # tTODO: Get the flag.
+ if not self.http_get("/getflag") == self.flag:
+ raise BrokenServiceException("Ooops, wrong Flag")
+
+ def putnoise(self):
+ # put some noise
+ with self.connect() as telnet:
+ telnet.write(self.noise)
+
+ def getnoise(self):
+ with self.connect() as telnet:
+ telnet.write("gimmeflag\n")
+ telnet.read_expect(self.noise)
+
+ def havoc(self):
+ self.http("FUNFUN").text == "FUNFUN"
+
+
+if __name__ == "__main__":
+ run(AwesomeChecker)
+```
+
+To get to know more about what each function should do read the [Getting Started Guide](../getting-started.md).
+
+The current flag is made available through the `self.flag` instance variable.
+If you wish to place more than one flag per round in different places, the content of `self.flag_idx` tells you which flag you should deploy, starting with `0`.
+
+In that case you should match the value of the variable in the `putflag()` and `getflag()` functions and act accordingly.
+You can communicate the number of flags you want to store per round to the game engine by setting the class variable `flag_count`.
+
+The noise, which is stored/retrieved using the `putnoise()` and `getnoise()` functions, is similar to the flag.
+Your checker should store/retrieve noise to check that the services is still working as intended.
+Unlike the flag, the noise does not need to remain secret, so you could for example post it on a publicly accessible comment section (provided your service has such functionality) to ensure this still works as intended.
+You can communicate the number of noises you want to store per round to the game engine by setting the class variable `noise_count`.
+
+The `havoc()` function is intended to check the functionality of those parts of the service which is not covered by the flag and noise functionality.
+You can communicate the number of havoc calls you want to receive per round to the game engine by setting the class variable `havoc_count`.
+
+### Communicating the service status
+
+To tell the game engine about the status of the service under check, you can raise various exceptions during the execution of your functions.
+If the execution of the function finishes without any exception, it is assumed the status of the service is ok.
+
+In case the service appears to be offline, for example because your connection times out, you should raise a `OfflineException`.
+
+In case the service is online but is not working as intended, for example because it responds with unexpected contents or the flag is missing, you should raise a `BrokenServiceException`.
+
+If the function raises any other exceptions, this results in the `CHECKER BROKEN` status on the scoreboard.
+This should usually never happen, so make sure to catch all exceptions the functions you use might raise.
+
+### Persisting data across executions
+
+Usually you need to store some information when storing the flag that is needed later.
+This could be something like usernames and passwords which are necessary to access the flag.
+There are multiple storage backends (at the moment `~enochecker.storeddict.StoredDict` and `~enochecker.nosqldict.NoSqlDict`) that are accessible through a common interface.
+
+The `self.team_db` dictionary is persisted across restarts.
+A good key for storing your information is usually the flag itself, since you want to access the information you stored during the `putflag` call during a later `getflag` call with the same flag in `self.flag`.
+An example for using the `self.team_db`:
+
+```python
+import secrets
+
+[...]
+
+class AwesomeChecker(BaseChecker):
+ def putflag(self):
+ username = secrets.token_hex(8)
+ password = secrets.token_hex(8)
+ self.team_db[self.flag] = {
+ "username": username,
+ "password": password,
+ }
+ [... register with the generated credentials and store the flag ...]
+
+ def getflag(self):
+ if self.flag not in self.team_db or "username" not in self.team_db[self.flag] or "password" not in self.team_db[self.flag]:
+ raise BrokenServiceException("storing the corresponding flag was unsuccessful")
+ username = self.team_db[self.flag]["username"]
+ password = self.team_db[self.flag]["password"]
+ [... register with the retrieved credentials and get the flag ...]
+
+```
+
+## Testing
+
+Test your checker by executing:
+
+=== "Bash"
+
+ ``` bash
+ pip install enochecker-test
+ ENOCHECKER_TEST_CHECKER_ADDRESS="localhost"
+ ENOCHECKER_TEST_CHECKER_PORT="3031"
+ ENOCHECKER_TEST_SERVICE_ADDRESS="localhost"
+ enochecker_test
+ ```
+
+=== "Powershell"
+
+ ``` powershell
+ pip install enochecker-test
+ $env:ENOCHECKER_TEST_CHECKER_ADDRESS="localhost"
+ $env:ENOCHECKER_TEST_CHECKER_PORT="3031"
+ $env:ENOCHECKER_TEST_SERVICE_ADDRESS="localhost"
+ enochecker_test
+ ```
diff --git a/docs/service/checker/checker.md b/docs/service/checker/checker.md
new file mode 100644
index 0000000..914405f
--- /dev/null
+++ b/docs/service/checker/checker.md
@@ -0,0 +1,16 @@
+# Checker Development
+
+To get to know what the Checker is doing visit the [Getting Started Page](getting-started.md).
+
+Your checker must be startable via
+```bash
+docker-compose up -f docker-compose.yml
+```
+
+It must conform the specifications.
+
+You can write it any language you like, but we provide some guidance for the following:
+
+ - [Python](checker-python.md)
+
+--8<-- "includes/abbreviations.md"
\ No newline at end of file
diff --git a/docs/service/getting-started.md b/docs/service/getting-started.md
new file mode 100644
index 0000000..199d678
--- /dev/null
+++ b/docs/service/getting-started.md
@@ -0,0 +1,52 @@
+# Create a new Service
+
+
+## What's a service pack?
+
+A service pack contains:
+
+ - the service
+ - the checker
+ - documentation
+ - workflows to check that everyhing is working as expected
+
+## Service Structure
+
+Examples: https://github.com/enowars/enowars-service-example
+
+### Service
+
+
+### Checker
+
+The checker stores and checks the flags in your service.
+
+Examples: https://github.com/enowars/enowars-service-example
+
+
+=== "Python"
+
+ ```
+
+ ```
+
+=== "Other"
+
+ ```
+ Other checker lib...
+ ```
+
+
+
+## Create a Service
+
+> Describe how to build a new service
+
+Create a private "enowars-service-" repository in the [Enowars Organization](https://github.com/enowars/).
+
+- We require a [specific project structure](#service-structure)
+- Use the provided files, docker-compose, etc.
+- Adhere to the service & checker tenets
+
+
+--8<-- "includes/abbreviations.md"
diff --git a/docs/service/service.md b/docs/service/service.md
new file mode 100644
index 0000000..a3e2bd7
--- /dev/null
+++ b/docs/service/service.md
@@ -0,0 +1,13 @@
+# Service Development
+
+To get to know what the Service is doing visit the [Getting Started Page](getting-started.md).
+
+Your service must be startable via
+```bash
+docker-compose up -f docker-compose.yml
+```
+
+Apart from that you are free to develop whatevery you like. π
+
+
+--8<-- "includes/abbreviations.md"
\ No newline at end of file
diff --git a/docs/service/tenets.md b/docs/service/tenets.md
new file mode 100644
index 0000000..d46344e
--- /dev/null
+++ b/docs/service/tenets.md
@@ -0,0 +1,80 @@
+# Service & Checker Tenets
+
+In the following we are using [RFC2119](https://www.ietf.org/rfc/rfc2119.txt) Key word to specify the Service and Checker capabilities.
+
+!!! info
+ π΄MUST This word, or the terms "REQUIRED" or "SHALL", mean that the
+ definition is an absolute requirement of the specification.
+
+ π΄MUST NOT This phrase, or the phrase "SHALL NOT", mean that the
+ definition is an absolute prohibition of the specification.
+
+ π‘SHOULD This word, or the adjective "RECOMMENDED", mean that there
+ may exist valid reasons in particular circumstances to ignore a
+ particular item, but the full implications must be understood and
+ carefully weighed before choosing a different course.
+
+ π‘SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that
+ there may exist valid reasons in particular circumstances when the
+ particular behavior is acceptable or even useful, but the full
+ implications should be understood and the case carefully weighed
+ before implementing any behavior described with this label.
+
+ π’MAY This word, or the adjective "OPTIONAL", mean that an item is
+ truly optional. One vendor may choose to include the item because a
+ particular marketplace requires it or because the vendor feels that
+ it enhances the product while another vendor may omit the same item.
+ An implementation which does not include a particular option MUST be
+ prepared to interoperate with another implementation which does
+ include the option, though perhaps with reduced functionality. In the
+ same vein an implementation which does include a particular option
+ MUST be prepared to interoperate with another implementation which
+ does not include the option (except, of course, for the feature the
+ option provides.)
+
+## Service
+
+A service:
+
+ - π΄ MUST be able to store and load flags for a specified number of rounds
+ - π΄ MUST NOT lose flags if it is restarted
+ - π΄ MUST be rebuilt as fast as possible, no redundant build stages should be executed every time the service is built
+ - π΄ MUST be able to endure the expected load
+ - π‘ SHOULD NOT be a simple wrapper for a key-value database, and SHOULD expose more complex functionality
+ - π‘ SHOULD NOT rewritable within within the timeframe of the contest
+ - π’ MAY be written in unexpected languages or using fun frameworks
+
+### Vulnerabilities
+
+A service:
+
+ - π΄ MUST have at least one complex vulnerability
+ - π΄ MUST have at least one "location" where flags are stored (called flag store)
+ - π‘ SHOULD have more than one vulnerability
+ - π‘ SHOULD NOT have unintended vulnerabilities
+ - π‘ SHOULD NOT have vulnerabilities that allow the deletion but not the retrieval of flags
+ - π‘ SHOULD NOT have vulnerabilities that allow only one attacker to extract a flag
+ - π’ MAY have additional flag stores, which requires a separate exploit to extract flags
+
+
+A vulnerability:
+
+ - π΄ MUST be exploitable and result in a correct flag
+ - π΄ MUST stay exploitable over the course of the complete game (I.e. auto delete old flags, if necessary)
+ - π΄ MUST be fixable with reasonable effort and without breaking the checker
+ - π΄ MUST be exploitable without renting excessive computing resources
+ - π΄ MUST be expoitable with reasonable amounts of network traffic
+ - π‘ SHOULD NOT be easily replayable
+
+## Checker
+
+A checker:
+
+- π΄ MUST check whether a flag is retrievable, and MUST NOT fail if the flag is retrievable, and MUST fail if the flag is not retrievable
+- π΄ MUST NOT rely on information stored in the service in rounds before the flag was inserted
+- π΄ MUST NOT crash or return unexpected results under any circumstances
+- π΄ MUST log sufficiently detailed information that operators can handle complaints from participants
+- π΄ MUST check the entire functionality of the service and report faulty behavior, even unrelated to the vulnerabilities
+- π‘ SHOULD not be easily identified by the examination of network traffic
+- π‘ SHOULD use unusual, incorrect or pseudomalicious input to detect network filters
+- π’ MAY use information stored in previous rounds, if it gracefully handles the unexpected absence of that information
\ No newline at end of file
diff --git a/includes/abbreviations.md b/includes/abbreviations.md
new file mode 100644
index 0000000..6e88d8d
--- /dev/null
+++ b/includes/abbreviations.md
@@ -0,0 +1,2 @@
+*[A/D]: Attack / Defense
+*[CTF]: Capture the Flag
diff --git a/init b/init
deleted file mode 100644
index 8b13789..0000000
--- a/init
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..387665b
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,115 @@
+site_name: Enowars Documentation
+site_author: Enowars Team
+
+repo_url: https://github.com/enowars/EnoEngine
+site_description: >-
+ Enowars is an Open Source CTF Hosting Framework providing everything you need to setup your own CTF.
+
+edit_uri: https://github.com/enowars/docs/edit/main/docs
+
+theme:
+ name: material
+ custom_dir: overrides
+
+ features:
+ - navigation.instant
+ - navigation.sections
+ - navigation.tabs
+ palette:
+ - scheme: default
+ primary: indigo
+ accent: indigo
+ toggle:
+ icon: material/toggle-switch-off-outline
+ name: Switch to dark mode
+ - scheme: slate
+ primary: red
+ accent: red
+ toggle:
+ icon: material/toggle-switch
+ name: Switch to light mode
+
+ favicon: assets/favicon.png
+ icon:
+ logo: fontawesome/solid/flag
+
+plugins:
+ - search
+ - mermaid2
+ # Not working together with mermaid2
+ # - minify:
+ # minify_html: true
+
+extra:
+ social:
+ - icon: fontawesome/brands/github
+ link: https://github.com/enowars/
+ - icon: tu-berlin-logo
+ link: https://www.agrs.tu-berlin.de/v_menue/ag_rechnersicherheit/
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/ENOFLAG
+ - icon: ctf-time-logo
+ link: https://ctftime.org/ctf/24
+
+markdown_extensions:
+ - admonition
+ - abbr
+ - pymdownx.snippets
+ - attr_list
+ - def_list
+ - footnotes
+ - meta
+ - md_in_html
+ - toc:
+ permalink: true
+ - pymdownx.arithmatex:
+ generic: true
+ - pymdownx.betterem:
+ smart_enable: all
+ - pymdownx.caret
+ - pymdownx.critic
+ - pymdownx.details
+ - pymdownx.emoji:
+ emoji_index: !!python/name:materialx.emoji.twemoji
+ emoji_generator: !!python/name:materialx.emoji.to_svg
+ - pymdownx.highlight
+ - pymdownx.inlinehilite
+ - pymdownx.keys
+ - pymdownx.magiclink:
+ repo_url_shorthand: true
+ user: squidfunk
+ repo: mkdocs-material
+ - pymdownx.mark
+ - pymdownx.smartsymbols
+ - pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:mermaid2.fence_mermaid_custom
+ # Already done by mermaid2 (and not yet ready in mkdocs-material)
+ # - name: mermaid
+ # class: mermaid
+ # format: !!python/name:pymdownx.superfences.fence_code_format
+ - pymdownx.tabbed
+ - pymdownx.tasklist:
+ custom_checkbox: true
+ - pymdownx.tilde
+
+# Mermaid 2 Custom loader
+extra_javascript:
+ - js/mermaid-loader.js
+
+nav:
+ - Home: index.md
+ - Infrastructure:
+ - Setup: infrastructure/infrastructure.md
+ - Installation: infrastructure/engine.md
+ - Timing of a round: infrastructure/round.md
+ - Create a Service:
+ - Getting started: service/getting-started.md
+ - Tenets: service/tenets.md
+ - Service Development: service/service.md
+ - Checker Development:
+ - General: service/checker/checker.md
+ - Python Checker: service/checker/checker-python.md
+ - Play: play.md
diff --git a/overrides/.icons/ctf-time-logo.svg b/overrides/.icons/ctf-time-logo.svg
new file mode 100644
index 0000000..f8c0f5f
--- /dev/null
+++ b/overrides/.icons/ctf-time-logo.svg
@@ -0,0 +1,26 @@
+
+
diff --git a/overrides/.icons/tu-berlin-logo.svg b/overrides/.icons/tu-berlin-logo.svg
new file mode 100644
index 0000000..60074d4
--- /dev/null
+++ b/overrides/.icons/tu-berlin-logo.svg
@@ -0,0 +1,24 @@
+
+
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..02db035
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,6 @@
+mkdocs-material==7.1.5
+mkdocs-minify-plugin==0.4.0
+Pygments>=2.4
+markdown>=3.2
+pymdown-extensions>=7.0
+mkdocs-mermaid2-plugin==0.5.1
\ No newline at end of file
diff --git a/scripts/create_ctf_json.sh b/scripts/create_ctf_json.sh
new file mode 100644
index 0000000..a55048a
--- /dev/null
+++ b/scripts/create_ctf_json.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+SERVICES=(
+ "1,servicename1,2000"
+ "2,servicename2,2001"
+)
+
+ip="127.0.0.1"
+
+cat << EOF > /services/EnoEngine/ctf.json
+{
+ "title": "TESTCTF",
+ "flagValidityInRounds": 10,
+ "checkedRoundsPerRound": 3,
+ "roundLengthInSeconds": 60,
+ "dnsSuffix": ".domain.com",
+ "teamSubnetBytesLength": 16,
+ "flagSigningKey": "flagsigningkey",
+ "encoding": "Legacy",
+ "teams": [
+ {
+ "id": 1,
+ "name": "Team 1",
+ "address": "$ip",
+ "teamSubnet": "::ffff:10.0.0.8",
+ "countryFlagUrl": "US",
+ "active": true
+ },
+ {
+ "id": 2,
+ "name": "Team 2",
+ "address": "$ip",
+ "teamSubnet": "::ffff:10.0.0.8",
+ "countryFlagUrl": "US",
+ "active": true
+ },
+ {
+ "id": 3,
+ "name": "Team 3",
+ "address": "$ip",
+ "teamSubnet": "::ffff:10.0.0.8",
+ "countryFlagUrl": "US",
+ "active": true
+ }
+ ],
+ "services": [
+EOF
+
+
+for dataRow in "${SERVICES[@]}"; do
+ while IFS=',' read -r id name port; do
+ cat << EOF >> /services/EnoEngine/ctf.json
+{
+ "id": $id,
+ "name": "$name",
+ "flagsPerRoundMultiplier": 1,
+ "noisesPerRoundMultiplier": 1,
+ "havocsPerRoundMultiplier": 1,
+ "weightFactor": 1,
+ "checkers": ["http://$ip:$port"]
+ },
+EOF
+ done <<< "$dataRow"
+done
+
+
+
+cat << EOF >> /services/EnoEngine/ctf.json
+]
+}
+EOF