# Introduction Blinker is a toolkit for automatically generating any number of CTF challenge instances from challenge templates. It was created as part of an undergraduate dissertation project by Gábor Szarka (gsx), who is happy to answer any queries by email to gs509 *at* srcf *dot* net. The work was supervised by [Dr Frank Stajano](http://www.cl.cam.ac.uk/~fms27). All code produced as part of this project is available freely under the 3-clause BSD license. See below for download details. The project was motivated by the observation that traditional CTF competitions make it relatively easy for participants to cheat (collude), since every participant has to solve the same challenges, which include the same flags. Therefore simply sharing the flag itself, or a script used to obtain the flag is entirely feasible. As testified by [an excellent paper from CMU](https://www.usenix.org/system/files/conference/3gse15/3gse15-burket.pdf), this can be a significant issue even in competitive CTFs, where there are reasonably strong incentives against collusion. In a non-competitive, educational setting this just becomes all the worse. In response to this, there have been efforts in both the CTF and the academic community to provide different challenges to each participant using automated challenge generation. Blinker is an attempt at doing this, focusing mostly on binary challenges. # How it works At the core of Blinker is a simple domain specific language for describing challenges. These descriptions can be thought of as challenge recipees: they allow any number of challenge instances to be produced by the toolkit. Depending on the challenge type, the author may need to put in a different amount of work to ensure that the resulting challenge instances are sufficiently different from one another. Most of the effort in developing Blinker went into dealing with binary challenges, so support for those is reasonable. For other challenge genres, such as *web* or *crypto*, Blinker is barely more than a glorified makefile. For binary challenges, a customised LLVM-based compilation toolchain is included, which attempts to introduce variation into the compilation output in a number of ways. To demonstrate that there may be other, quite different ways of automated challenge generation, Blinker also features a convenient tool for generating network packet capture files for use in forensics challenges. # Availability All downloads will be signed with [this PGP key](signing.pub), which in turn is signed using my main key (0xa004cba9cf18815f), which is available from the [key servers](http://pgp.mit.edu/pks/lookup?op=vindex&search=0xA004CBA9CF18815F). Blinker consists of two main parts: the framework, and the platform. The framework is a standalone toolkit for template-based challenge auto-generation, and the platform is a purpose-built system for hosting a Jeopardy-style CTF based on challenges generated by the framework. The framework is ready for general-purpose use, so in addition to the published source, there are also Ubuntu packages, and a turn-key VM with the framework preinstalled. On the other hand, the platform contains some details relevant only to the controlled experiment carried out for Gábor's dissertation, so is distributed only in source code form for the time being. Adopting it to a simple wargame or Jeopardy-style CTF should be fairly easy to do, but will necessarily involve a small amount of programming work, which was not yet done. ## Source The source is stored in git. The repo is available [on Github](https://github.com/gsx/blinker/). ## Ubuntu packages The recommended way of using the Blinker framework is to install the latest available version from this website (which is also an APT repo) on an Ubuntu 16.04 system. ```sh wget -O- https://gs509.user.srcf.net/blinker/signing.pub | sudo apt-key add - echo 'deb https://gs509.user.srcf.net/blinker xenial main' | sudo tee /etc/apt/sources.list.d/blinker.list sudo apt-get update sudo apt-get install -y libc6-dev gcc haveged blinker-framework blinker-challenge-codegate2016-serial blinker-challenge-nuitduhack2016-night-deamonic-heap blinker-challenge-seccon2016-checker blinker-challenge-random-codegate2016-serial blinker-challenge-random-nuitduhack2016-night-deamonic-heap blinker-challenge-random-seccon2016-checker blinker-challenge-xor-cipher echo -e "\nexport BLINKER_CHALLENGES_DIR=/usr/share/blinker/challenges/" >> ~/.bashrc . ~/.bashrc ``` ## Demo VM To save anyone wishing to try Blinker the burden of having to set up their system to run it, a small VM based on Ubuntu 16.04 is provided which comes with Blinker and a couple of demo challenges preinstalled. Disk image: [blinker-demo.vmdk.gz](blinker-demo.vmdk.gz) Signature: [blinker-demo.vmdk.gz.asc](blinker-demo.vmdk.gz.asc) The above is a disk image in VMware's vmdk format. It should be usable with almost any virtualization technology without too much pain (VirtualBox, QEMU, and VMware Player all support it out of the box). The VM should not be too resource-intensive, but for convenience, allocating 2 GB of memory and 2 processor cores is recommended. Log in as `blinker-admin` with password `blinker`. SSH is installed, and DHCP should work. The `blinker-admin` user has full sudo access. The `blinker` command will be available, and a handful of demo challenges have been preloaded on the VM. After starting up your VM for the first time you might want to run `apt-get update && apt-get upgrade` to make sure you are using the latest released version of Blinker. # Tutorial ## Challenge templates The best way to get a feel for this is by looking at some of the demo challenges. In particular, we will start by looking at Seccon2016Checker, which (as the name suggests) is a reimplementation of the ['checker' challenge](https://ctftime.org/task/3177) from SECCON 2016 Online CTF. Looking into the directory for the challenge (available under `/usr/share/blinker/challenges/seccon2016_checker/` in the VM, as package `blinker-challenge-seccon2016-checker` from the APT repo, or from the source archive above), we can see a file called `Checker.chall`. This is the main challenge description file, which every challenge must have. It serves both as a configuration file and a build script for the challenge. Under the hood, these challenge descriptor files are Ruby scripts written using the Rake (Ruby Make) DSL. Quite a number of things are happening in there, so we will go over it line by line. (The comments were added just for this tutorial.) ```rb # A line like this: # task :something => 'somefile' # corresponds to a phony target in a regular Makefile: # .PHONY: something # something: somefile # The executable that will have to be run is called 'checker' task :executable => 'checker' # The file called 'checker' should be made available to participants for # download task :handout => 'checker' # The instructions displayed to participants are generated at run time from the # file called 'description.html.erb' task :description => 'description.html.erb' # The file 'description.html.erb' is generated by running the Ruby code in the # do-end block and capturing its standard output. generated_file 'description.html.erb' do puts <<EOF <a href="<%= handout_url %>">This executable</a> is running on the server (port #{BlinkerVars.socat_port}). It will happily let you try and guess the flag. However, if 3.4e38 possibilities seem like a little too many, maybe you can try to do something more clever? EOF end # The next C/C++ compilation will use the following options # :stack_protector enables stack canaries, as with vanilla gcc/clang # :relro controls the level of the RELRO hardening applied c_flags :stack_protector => true, :relro => :full # The file 'checker' is produced by compiling the file 'checker.c' as C code c_compiled 'checker' => 'checker.c' # This challenge fits into the typical scenario where a binary is being run on a # server, with its standard input/output connected to a network socket, and # participants have to exploit the vulnerability remotely. scenario 'stdio_socat' ``` ## Generating instances We can now generate an instance of this challenge like this. ```sh blinker-admin@blinker-demo:~$ blinker Seccon2016Checker Challenge generation successful Handout: /tmp/blinker-20170331-1234-151nd8y/checker Deployable: /tmp/blinker-20170331-1234-151nd8y/blinker-challenge-06e1ef816cef708370503cffdf5ed1fd.deb Flag: flag{c3fad669e15b4ad72f408e6c31bfbf08755b86bcb8c28ab134db} ``` This created a new temporary directory, which now holds the 'handout' and the 'deployable', that is, the file to be handed over to the CTF participant, and the file encompassing all of the challenge, which is to be deployed somewhere for the participant to exploit remotely. For convenience, we are also told the correct flag. (Obviously, the tool is not meant to be run by the participants.) We can actually deploy this challenge on the demo VM itself, and attempt to solve it. The challenge description is available in the file `/tmp/blinker-20170331-1234-151nd8y/__blinker_status`, however that is primarily meant to be machine readable, so it is simpler to just install the challenge locally and see what happens. In our case, we can see that a service popped up on port 50517 (this is randomly generated). We can confirm that this is the challenge by connecting to it. ```xml blinker-admin@blinker-demo:~$ sudo dpkg -i /tmp/blinker-20170331-1234-151nd8y/blinker-challenge-06e1ef816cef708370503cffdf5ed1fd.deb Selecting previously unselected package blinker-challenge-06e1ef816cef708370503cffdf5ed1fd. (Reading database ... 106860 files and directories currently installed.) Preparing to unpack .../blinker-challenge-06e1ef816cef708370503cffdf5ed1fd.deb ... Adding system user `challenge' (UID 112) ... Adding new user `challenge' (UID 112) with group `nogroup' ... Creating home directory `/home/challenge' ... Unpacking blinker-challenge-06e1ef816cef708370503cffdf5ed1fd (1.0) ... Setting up blinker-challenge-06e1ef816cef708370503cffdf5ed1fd (1.0) ... Created symlink from /etc/systemd/system/blinker-challenge-06e1ef816cef708370503cffdf5ed1fd.service to /lib/systemd/system/blinker-challenge-06e1ef816cef708370503cffdf5ed1fd.service. Created symlink from /etc/systemd/system/multi-user.target.wants/blinker-challenge-06e1ef816cef708370503cffdf5ed1fd.service to /lib/systemd/system/blinker-challenge-06e1ef816cef708370503cffdf5ed1fd.service. ``` ```xml blinker-admin@blinker-demo:~$ ss -ltpn State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 5 *:50517 *:* LISTEN 0 128 *:22 *:* LISTEN 0 128 :::22 :::* ``` ```xml blinker-admin@blinker-demo:~$ nc localhost 50517 Hello! What is your name? NAME : Batman Do you know flag? >> yes Oh, Really?? Please tell me the flag! FLAG : Na na na na na na na... You are a liar... ``` To make our life easier, an exploit is also attached to the challenge. This uses [pwntools](https://github.com/Gallopsled/pwntools), which is not installed on the VM, but that can quickly be taken care of (`sudo apt-get install python-pip && sudo pip install pwntools`). When that is done, we will want to make a copy of the exploit script in a directory along with the handout binary, comment the line that starts the local process, and uncomment the line that instead connects to a remote target, and finally run the exploit. ``` blinker-admin@blinker-demo:~$ python exploit.py [*] '/home/blinker-admin/checker' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x200000) [+] Opening connection to localhost on port 50517: Done [*] Switching to interactive mode Do you know flag? >> Oh, Really?? Please tell me the flag! FLAG : You are a liar... *** stack smashing detected ***: flag{c3fad669e15b4ad72f408e6c31bfbf08755b86bcb8c28ab134db} terminated [*] Got EOF while reading in interactive $ ``` ### A note on variation Note that the stock exploit worked right away, because this challenge intentionally avoids introducing any variation, as it is meant to be a very close replica of the original challenge. However, its cousin, RandomSeccon2016Checker is a different story, which is worth looking at and comparing. Suffice to say, the stock exploit will not work out of the box in that case. ## Writing your own The best way to experiment is to take one of the existing challenges and start tweaking it. Two things that might be helpful to know about when developing a new challenge are the two environment variables `BLINKER_DEBUG` and `BLINKER_CHALLENGES_DIR`. If `BLINKER_DEBUG` is set to a non-null value, Blinker will print out the commands it used to compile any binaries, and it will also leave all intermediate products in the temporary directory for you to look at. `BLINKER_CHALLENGES_DIR` is useful to avoid having to constantly update code under `/usr/share/blinker/challenges`. The challenges available to Blinker are always those subdirectories of the directory given in `BLINKER_CHALLENGES_DIR` which contain a .chall file. ## Introducing variation automatically ### Binary challenges C/C++ source code in Blinker is always ran through an ERB engine. (ERB is a common ruby templating mechanism.) This means that arbitrary Ruby code can be executed at compile time, which has access to the Blinker arsenal. This allows introducing all kinds of variation into the output by simple means, such as changing `char buf[32]` to `char buf[<%= random_number 10..64 %>]`, or perturbing the stack frame layout by adding a `<%= c_stack_padding 0..3 %>` among the local variables of a function. This second call expands to a buffer that serves as padding, and its size is between 0 and 3 words. Despite not being very creative, these techniques can be surprisingly effective, so should not be dismissed carelessly. However, the part where Blinker is more helpful is the automatic injection of variation. Beyond the ones corresponding to well known compiler settings, there are 8 options in Blinker which are implemented in blinker-llvm, and are designed to help increase variability of the generated challenges. These options are `reorder_got_plt`, `reorder_plt`, `randomize_regs`, `randomize_branches`, `randomize_function_spacing`, `randomize_scheduling`, `reorder_functions`, `reorder_globals`. Currently, these are only described in Gábor's dissertation, which is available [here](blinker.pdf) (section 3.2 is the relevant part). ### Network forensics challenges Another helpful, although admittedly less well developed tool in Blinker allows challenge authors to easily design network forensics challenges, where packet captures are recorded on demand. The solution is implemented on top of Mininet, which is a network simulation engine based on Linux network namespaces. The basic concept is nicely illustrated by the following simplified excerpt from a .chall file. ```rb network_capture 'attack.pcap' => [] do |c| c.capture_on = 0 # means capture traffic on first host in list below c.mask = 24 # means /24 subnet c.hosts = [ { :ip => '192.168.1.1', :command => 'nc -lp 12345' }, { :ip => '192.168.1.100', :command => 'echo hello | nc 192.168.1.1 12345' } ] end ``` This above snippet could be summarised as 'Let attack.pcap be a packet capture file that has recorded how 192.168.1.100 connected to 192.168.1.1, said hello, and then finally terminated the connection.'