Getting Started
Beast is a tool for automatic deployment of CTF type challenges, the intial aim of the project was to asist the deployment for challenges on backdoor.sdslabs.co, but since its inception beast has grown a lot beyond that scope. It is a general tool for deployment of Jeopardy style CTF challenges and is not coupled with backdoor anymore.
The main hurdle for deployment of such challenges is the requirement of stong isolation
of the environment in which the challenges are being deployed, docker provides us with all the
sandboxing we need with minimum overhead. There are more secure runtime like runsc
(gVisor) which can
be used to improve the sandboxing capabilities of the containers.
There are a lot of features that comes embedded with beast, take a look here
Beast comes in with an embedded web server which can be used as an interface for interacting with beast. The web server is built with gin framework and is very performant, to run the server with debugging mode on use the following command
beast run -v -p 3333
To interact with beast you need to first authenticate yourself, currently this process is a little bit tedious since we don't currently have a strict database storing the details of our users. To check out how the authentication flow works for beast take a look here
Once you have the authentication token with you all you need to do is Embed the token in Headers of your request as
Authorization: Bearer <token>
.
There is swagger generated API documentation with the details of endpoints exposed by beast which can be used for interacting with the web server.
Deploying your first challenge
In this section we will try to create a new challenge and deploy it using beast. For the simplicitiy of this tutorial we are deploying a simple buffer overflow challenge.
The source code for our challenge file is:
#include <stdio.h>
#include <unistd.h>
int sample()
{ FILE *ptr_file;
char buf[100];
ptr_file = fopen("flag.txt","r");
if (!ptr_file)
return 1;
while (fgets(buf,100, ptr_file)!=NULL)
fprintf(stderr, "%s",buf);
fclose(ptr_file);
return 0;
}
void test()
{ char input[50];
gets(input);
sleep(1);
fprintf(stderr, "ECHO: %s\n",input);
}
int main()
{ test();
return 0;
}
Create a new directory for the challenge and create the source code file(pwn_me.c
) with the above contents. Compile this source code file to produce a binary.
$ gcc -o pwn pwn_me.c
Now create a new directory named 'static' inside the challenge directory. Copy the above created binary into this directory.
$ mkdir -p static
$ cp pwn static/
Create a file beast.toml
with the following contents which defines the configuration of the challenge
[author]
name = "author" # Name of the challenge creator
email = "[email protected]" # Email for contact
ssh_key = "ssh-rsa AAAAB3NzaC1y..." # Public SSH key for the challenge author
[challenge.metadata]
name = "baby_pwn" # Name of the challenge, must be same as the directory name.
flag = "flag{b4by_ch4113ng3_pwned!}" # Flag for the challenge
type = "service" # Type of (beast) challenge (service, docker, static, etc.)
tags = ["pwn"] # Tag(s) for challenge (pwn, crypto, etc.)
# Minimum and maximum points for dynamic scoring
# Set both to the same value to emulate static scoring
minPoints = 100
maxPoints = 500
# Prompt for the challenge
description = "Here ya go... the customary baby challenge!"
# Static challenge files to be provided to the challenge solvers
# These challenge files may be generated dynamically with the setup_scripts (see below)
# But (dummy) defaults must be provided inside the directory specified by static_dir
assets = ["pwn"]
# This section defines the environment for the challenge
[challenge.env]
# Define the dependencies we might need for the challenge.
# For example in this case we need gcc for compilation of the source file
apt_deps = ["gcc"]
# The relative path of the binary or executable which we should
# run for each connection to the challenge.
# This binary (or executable) can also be generated dynamically
# But, once again, (dummy) default needs to provided inside the challenge root directory
service_path = "pwn"
# Port to run the challenge on
ports = [17671]
# Since we still haven't defined how we are going to compile the source
# code, these scripts are for setting up the environment.
setup_scripts = ["setup.sh"]
# Directory inside the challenge folder where the static challenge files are present
# Any assets defined in the metadata section must be preset in this directory
static_dir = "static/"
The above configuration is simple and intuitive from the perspective of the challenge creator. It simply asks how would they have setup the challenge manually. So for example they install some dependencies first like gcc, then compile the source and generate a binary, and finally serve the binary by exposing it as a service at some port. The above configuration is just a description of that. So from a challenge creator's perspective, this is pretty neat!
Let's see how our setup scripts look like.
set -e
gcc -o pwn pwn_me.c
- All the commands that you execute are executed from the within the root of the challenge directory.
It's simple we just compile the source code.
The final step is important and needs to be taken care of. We have setup the challenge but now we also need to provide the binary we obtained to the participant as part of the challenge.
Beast provides a way to do this using a special file named post-build.sh
. This script is run once finally
after all our environment setup is done, in this file you can read/write/modify the final environment once more
before finalizing challenge setup. Up until this point we have the binary with us, we can write this script
to copy the final binary to a special publically available directory within the challenge specified as the static_dir
in the configuration.
This directory(static/
in our example) is exposed by beast using the static content provider. To make any file from within your challenge
publically accessible as part of the challenge you need to put that file in this directory (and also specify it in the list of assets
in the metadata section of the configuration), rest is handled by beast itself.
This is how our post-build.sh
script looks like for this particular challenge
#!/bin/bash
set -euxo pipefail
cp pwn static/
The final directory structure looks like
baby_pwn
├── beast.toml
├── flag.txt
├── post-build.sh
├── pwn
├── pwn_me.c
├── setup.sh
└── static
└── pwn
And that's it, we have our challenge ready to be deployed by beast.
Deployment using beast
We have our challenge ready with the required configuration. To deploy the challenge check out the Deployment flow here.
Note
- To know more about the environment configuration possiblities with beast head out to this section.