X-CTF 2016 Badge Firmware

As promised, we are releasing the source code for the X-CTF badge, about 1 month after the event to give interested participants the chance to take a crack at it. If you are interested in the badge design process, check out my previous post on the hardware aspects.

Jeremias and Jeremy gave a talk at one of the Null Security meetups. Check out the slides if you haven’t already. In one part, Jeremy talks about the custom firmware he wrote for his badge and the additional challenges he set up for partipants to get more points. The 2nd part of the talk covers the electronic badge and challenges.

The Challenges

The challenges try to exploit the nature of being a self-contained electronic device. Rather than trying to replicate more CTF puzzles and simply placing them into the badge, we specially designed them for the badge.

You can find the answers to the badge puzzles (and the main CTF puzzles) in the X-CTF GitHub repo, which was released shortly after the event.

Since there’s only a single entry point into the set of challenges (meaning you must solve each puzzle before getting to the next), the puzzles must be designed with increasing levels of difficulty; too difficult and the participants will totally give up.

Stage 1: Catch Me If You Can

animation of challenge 1

I particularly like this one. Unlike a program running on the computer, you can’t easily snapshot the state of the program, nor try to influence (slow down) its execution.

Initially, we thought of just having the letters flash at random by choosing indices using a random function, but we were afraid that some letters would never appear or appear very infrequently, making the user wait forever. Instead, we decided it was safer to use a list of all character positions and randomize this list.

    for (int i = 0; i < flaglen - 3; ) {
      int r = random(0, flaglen);

      // check number wasn't used
      if (! idxSeen[r]) {
        sequence[i] = r;
        idxSeen[r] = true;
        i++; // move to pick next random number
      } else {
        // redo loop to pick a new number

Also, just like the concern we had with the random function, we also filled up the last few indices with whatever numbers that were remaining, hoping that the random function had already done its job for the front part of the list.

    // last 3 numbers just pick whatever's not there
    for (int i = flaglen - 3; i < flaglen; i++) {
      for (int j = 0; j < flaglen; j++) {
        if (! idxSeen[j]) {
          sequence[i] = j;
          idxSeen[j] = true;

This method worked pretty well, but since the same randomized list was replayed indefinitely, the user might notice it’s not entirely random.

Stage 2: A Bit Off

This was a regular crypto puzzle.

Stage 3: Pigpen

This was yet another crypto puzzle but with a non-standard “cipher”. The underscore, numbers and the @ symbol were disguised as cipher text. There was also an easter egg embedded in the code for this challenge.

If you look into the firmware, you will find a string that looks like a flag. An interesting characteristic of the string is that each character is unique (non-repeating). If you have worked with graphics LCDs before, you might know that they are just framebuffers and require a character map. Such a character map can be seen here in the Arduino Playground code here.

For this challenge, a special character map containing the pigpen symbols is used in place of the normal one. And instead of using the real flag string, a (well-chosen) dummy string was used as an index into this map. You can see that IDA Pro has detected that the code references the string from index 5, which is right after the curly brace.

IDA Pro disassembly showing the fake flag reference

Stage 4: Offline Dino


This is the final puzzle and it was intentionally made unsolvable by conventional means. You may recognize this game from the Chrome browser. This version was written for the Arduboy platform but simple enough to port to our badge using a thin wrapper.

To get past this level, you need to get a score of 31337, and since the score advances at about a rate of 1 per second, it would take more than 9 hours to complete this stage (and without tripping over)! The CTF event lasted only 8 hours.

This prompts participants to extract the firmware and revese-engineer it.

Crypto all the things!

One thing you’ll notice is that the flags are not in the code itself. The flags are verified by MD5 hashing the user input and comparing it to a set of stored values.

Solving the last stage will present the user with a QR code. Astute readers will also notice that image of the QR code cannot be found in the code. You will however find an AES 128 encrypted blob that, when decrypted, contains the actual QR image. And, this AES key for decryption is also not present in the code. 😉

This, together with the fake flag, discourages participants from simply running strings on the extracted firmware image for quick answers. If all this sounds familiar, it’s because I discussed about applying this concept to my encrypted drives previously.


A simple framework ties all of this together, making it easy to add more challenges and screens without duplicating code and abstracts away code for screen management.

While the screen only has room for 6 lines, you can add as many item as you wish. The menu class takes care of scrolling the “viewport”, and highlighting the current menu selection.

Displays a simple on-screen keyboard for accepting user input. This is much better than the SyScan 2015 badge (if you have used it before). You can also enter more characters than what is shown on screen.

App-lets are basically mini “apps”, much like what you might find in Android. Each screen and each challenge is implemented as an applet. All of the screens, including the menu and keyboard, inherit from the Applet class. The framework maintains an applet stack, and only the “foreground” applet receives events. To go back to the previous screen, you simply pop-off the current applet from the applet stack, and tell the now-foreground applet to redraw itself.

The bulk of the framework code can be found in applet.cpp and its header file. The main driver is in badger.ino, the main sketch file.

Reverse-Engineering ESP8266 Code

Well I was (and still am) waiting for someone to write this up. If you get stuck, here’s a Russian post for some hints.

ESP8266 flash layout, with Russian annotations

Go forth and reverse-engineer some firmware!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.