LabyREnth Capture the Flag (CTF): Threat Track Solutions

Category: Events

Welcome back to our blog series where we reveal the solutions to LabyREnth, the Unit 42 Capture the Flag (CTF) challenge. We’ll be revealing the solutions to one challenge track per week. Next up, the Threat track.

Threat 1 Challenge: Welcome to the well of wishes!

Challenge Created By: Jeff White @noottrak

For this challenge you’re provided a PCAP that has 30 HTTP GET Requests to Inside each request is the same URL to the below image and a base64, reversed, string that decodes to “Not everything is as it seems…”.


Looking at the actual GET requests, the URL structure is interesting as there are a few sections in the URL that remain static between the URLs so we’ll go ahead and extract them all for further analysis.


Using the below command extracts each URL.

tcpdump -r dopefish_labyrinth.pcap -A |grep “GET /” |grep -o “/.*” |sort –u

The general breakdown of the URL is as follows:


Dropping the URL into an online URL parser shows that the query string being supplied to the file is the entire string after the initial “?”, which seems odd since there appears to be other variables in the URL.


Further analysis shows that the URL is not correctly formatted as “?” and “=” are reserved characters. When they are placed next to each other, without variables in-between, the rest of the URL becomes invalid.

Looking at the query string starting with “=” and the above hint with a reverse base64 string beginning with the same symbol, I try to base64 decode the reversed string…which works, but the output is of no use.


After taking a closer look at the URLs, I noticed there are definite patterns that stand out in the characters, but “NxM” continues to repeat itself roughly every 18 characters. More importantly, the NxM pattern that is seen in the first long string and the long string directly following the “&L4bry1nth_[0-9]{3,5}?” section of the URL. By removing this and putting the two long strings together, reversing it, and base64 decoding it, we get much more usable results.



Continuing to build on our previous line, we wrap it in a for loop and parse out only the two halves, reverse them, and print the result.

for i in $(tcpdump -r dopefish_labyrinth.pcap -A |grep “GET /” |grep -o “/.*” |sort -u |cut -d”=” -f2 |cut -d”-” -f1 |sed -e ‘s/&L4bry1nth_.*?//g’); do echo “=$i” |rev |base64 -D ; done


There is a very apparent pattern in the output. The last step we take is to add one more command to our line and strip out the “317”, which turns out was the “NxM” from the URL.

for i in $(tcpdump -r dopefish_labyrinth.pcap -A |grep “GET /” |grep -o “/.*” |sort -u |cut -d”=” -f2 |cut -d”-” -f1 |sed -e ‘s/&L4bry1nth_.*?//g’); do echo “=$i” |rev |base64 -D ; done |sed -e ‘s/317//g’


The key is PAN{th3D0p3fshl1v3s}.

Threat 2 Challenge: The rest of us, we died with our honor.

Challenge Created By: Micah Yates @m1kachu_

The hint is referring to this awesome web-comic.

To begin the challenge, we are given a file named jareth1.gif


The thumbnail makes it look like a valid gif, but is it?


The file opens and animates fine.

Opening the gif with a hex editor, the header looks like a regular gif header.


Scroll to the bottom, and this file is missing the standard gif trailer of 0x3B. (Read more about what’s in a valid gif.)

The hex values at the end are not the standard gif trailer.


There is data appended to this gif.

So what do we do with this information? One trick I like to employ with tampered image files is to do a reverse image lookup via Google. I upload the jareth1.gif file to google image search and get this back:


Clicking through a few of the visually similar images we find this gif. It animates the same and has the same dimensions as our jareth1.gif but a different size.

When diffing the two gifs, we can see that the original gif ended at offset 0x3436D with a valid trailer of 0x3B. Some shellcode and malware authors like to hide data by XOR-ing it with single or multi-byte hex values. Since it appears that the 0xAA bytes are repeating at the end of the file, and some data typically contains nulls, lets XOR the entire file by 0xAA.

Now let’s look at the leftover data’s header:


Based off of the header, it appears that the hidden data is a 7zip archive. Let’s save off this XOR’d data to another file.

One trick I like to use is 7zip’s ability to unzip files when the header is in the incorrect place. Simply use the command line or right click and select 7zip -> Extract here.

Decompressing that file gives us another file simply named “file”.

Taking a quick look at the header we see that this is an html file.

Renaming it to file.html and opening it with a browser yields this glorious ASCII art of David Bowie:


So what now? There’s no obfuscated javascript, just a seemingly incomprehensible mess of html. It also looks as if there is a repeating pattern of A7 A0 bookending some other data.

Let’s drop a section of that hex looking data into a hex editor:


None of it renders into ASCII, let’s try XORing it with 2 byte 0xA7A0 to see if there’s data underneath:


Still nothing to work with, but it does look like it could possibly be ASCII text.

Let’s undo that and try again with the original single byte 0xAA:


Much better. The text renders as:

Write a YARA rule to find normal valid GIF files.

Using the template below:

  1. replace each “**” pair in $header with the appropriate 6 bytes.
  2. replace each “*” in $trailer with the appropriate regex.
  3. replace the “*” in condition with the appropriate digit.

After reading this article about what’s in a gif:

  1. The header is self-explanatory.
  2. The regex format is required here to make sure there is no following data after the 3B
  3. And of course the header has to be at the beginning of the file at position 0

Submitting the rule above will result in the key: PAN{848Y_wIsh3D_4w4y}

Threat 3 Challenge: Matryoshkas got nothing on me.

Challenge Created By: Josh Grunzweig @jgrunzweig

For this challenge, we’re presented with a Python script. When you run the script, the only output you receive is this:

You fell into a pit and died… of dysentery.

Looking at the script, it appears to base64 decode the data and decrypt it before passing it to exec() function. The data is rather large, with over one million characters. It’s using AES for the encryption from the Crypto.Cipher suite and stepping through it with a debugger shows that it continues this iteration process where each blob of data executed contains the same code with a new blob of data.


Following this line of logic, we can quickly script out this process. We’ll write each decrypted section to a new file and include the headers necessary to run it again, so on and so forth, until we reach the end.

First, we’ll create header.txt with the following data:

#!/usr/bin/env python

from Crypto.Cipher import AES as tiywynstbg
import base64 as ufjliotyds
import itertools as abtwsjxzys
from itertools import cycle, izip
def gasfewfesafds(message, key):
return ”.join(chr(ord(c)^ord(k)) for c,k in abtwsjxzys.izip(message, abtwsjxzys.cycle(key)))

Next, we’ll put together a one-liner to traverse the dark depths of Python and see where it leads.

cp; for i in $(seq 1 50); do sed -e ‘s/exec(/print(/g’ h0ggle_$ > temp; mv temp h0ggle_$; python h0ggle_$ > temp; cat header.txt temp > h0ggle_$((i+1)).py; done

We run into a syntax error on file, which shows our dysentery error message.

File “”, line 8
You fell into a pit and died… of dysentery.

Looking at we can see how to safely cross the river!


Key = PAN{all_dir3ctionz_l3ad_n0wh3r3}

Threat 4 Challenge: The same, but different.

Challenge Created By: Micah Yates @m1kachu_

The hint is referring to the previous yara challenge, Threat 2 Challenge: The rest of us, we died with our honor.

To begin the challenge, we are given 6 word docs.


Let’s open them all up


The files open and display the somewhat same word doc.

Running the file command on all of these files results in:

3673c9d7a5b2f978d3a34001d360ac485f22ed6fa868c8304eb99273a6efb268.doc: Microsoft Word 2007+
668bed5ed5d5effb3be659e8dab55c63369985064f7ee80f9365e75b34f6283d.doc: Microsoft Word 2007+
7717bd124dd0c0881afd6b327ff41b420bff77d3c5ae338a31cce5cfdcb3b5d0.doc: data
87f146c41082d7ba885f9433e0223b346f3032f7364bf18675b924a017994779.doc: Microsoft Word 2007+
afc502de73482404cc344301c207f27c7da7b31641cd2192b3bba40f3ab6964e.doc: Composite Document File V2 Document, Little Endian, Os: Windows, Version 6.1, Code page: 1252, Author: Micah Yates, Template: Normal.dotm, Last Saved By: Micah Yates, Revision Number: 2, Name of Creating Application: Microsoft Office Word, Create Time/Date: Wed Jul 13 17:19:00 2016, Last Saved Time/Date: Wed Jul 13 17:19:00 2016, Number of Pages: 1, Number of Words: 146, Number of Characters: 837, Security: 0
d48a2f4922bca81ce8fff8c18d788f41d2034c7999ca1ed03965d914dc06a9df.doc: Rich Text Format data, version 1, unknown character set

They’re not all the same file format, but all contain the same basic content. There’s a .doc, .docx, .rtf, .mhtm, .dot, and .docm file with the same plaintext inside. Simply changing their extensions to .doc allows for Word to try and open them as a standard Word .doc

Let’s open up the RTF (d48a2f4922bca81ce8fff8c18d788f41d2034c7999ca1ed03965d914dc06a9df.doc) in a hex editor. They’re typically fairly simple to follow. The header looks fine so lets scroll down to the bottom of the file.


Looks weird right? Let’s take a look at the RTF file format on Wikipedia, specifically the Code Syntax:

Get all that?

So the TL;DR of that section states that RTF data must be within curly braces “{}”. This RTF file clearly has data appended to it.

Remember the hint? “The same, but different.”Well it seems this challenge is similar to the Threat 2 challenge. There is unknown data appended to a legitimate looking file.

Let’s attack the data that seems to be repeating. The appended data looks similar to base64 encoding, but somewhat obscured. Within this appended data we have 3 sequences of data that are 48 bytes long that are repeating.



Two of them are different, and of those, one is clearly not a valid Base64. (See Wikipedia’s definition of Base64.)

When there are sequences of four repeating characters in Base64 encoded data, the underlying data typically repeats in some sort of pattern. For example: AAAAAAAAA Base64 encoded is QUFBQUFBQUFB

Let’s assume these two different sequences are hiding the same data, but have encoded it differently due to data position. If that’s true, it looks like both sequences have also been obscured by a single byte operation. So what do we do to figure out that operation? Brute Force!

Let’s write a small script that performs single byte operations on the two sequences and then test it to see if they’re valid Base64 characters. In short, this runs a simple one byte XOR over each byte in the sequence and checks to see if they are ASCII compatible with Base64 characters.

I truncated the two sequences above into an 8-byte sequence of data:


Running the script over these shortened sequences, it returns 4 XOR candidates:


So we have four potential XOR values that decode valid Base64 characters.

Starting with XOR-ing the appended data with 0x26 we get this:


Looks pretty promising, and almost all characters are Base64 standard except there are no “+” characters, only “-“.

This looks like it may be using an alternate encoding string with “-“ in place of “+”. If we try and decode with this alphabet: “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/”, there is no data that looks promising.

Some malicious Base64 encodings use alternate alphabets. Sometimes they’re simple and just change up the position of the alphabet. Let’s write another brute force script to rotate the position of the alphabet above, decode, and test to see if it has valid ASCII text.

So what we did in the script was enter our data that had been XOR-ed with 0x26. We defined an alternative Base64 alphabet and then looped through all variations of that alphabet by rotating the entire alphabet by one character per loop. We then checked each output to see if it was valid ASCII, and then printed it. Running the above code returns:


Here’s the encoded data and clue. It appears that the decoding alphabet has been rotated by 26 characters.

Would you look at that, three lines of repeating characters, all the same length: “{ ** ** ** ** ** ** ** ** ** ** ** ** }”

So to recap, we brute-forced a single byte XOR, then brute forced an alternate base64 alphabet that had been rotated left by 26 characters.

Going back through the other documents we find the two variations to this encoded data, and fill out the YARA rule like this:

Submitting the rule above will result in the key: PAN{7H1r7EEn-hOuR_71me_l1M17}

Threat 5 Challenge: Hello Confetti!

Challenge Created By: Anthony Kasza @anthonykasza

Opening the provided “hello.pcap” file with Wireshark and examining the protocol hierarchy within the pcap shows only UDP traffic. Wireshark believes a few of the packets are malformed real-time transport control packets while the majority of the packets are data.


Observing the UDP conversations within Wireshark, only a single “connection” occurred within the pcap. The connection is between port 9090 and 53321 on The inconsistency of protocols above combined with a single connection within the trace file leads to the conclusion the protocol being used is non-standard.


The first packet in the trace contains the data “hello :)” followed by an 0x0a. This is the only packet with a length that’s not 8234 and sent from port 53321 to port 9090. The second packet contains data which starts with “BZh” which is the magic file header for a bzip file. Ignoring the first “hello” packet, a bzip archive can be extracted from the trace file. Decompressing the bzip archive reveals a second pcap trace.

Using similar techniques as before, multiple FTP over IPv6 connections can be observed within the pcap. Following one of the FTP connections reveals a series of requests and responses, which all streams mimic:

  • an anonymous FTP login occurs
  • a series of change directory commands is issued to /this/is/going/to/be/so/much/fun/
  • a size request for a file resulting in a response of 61052
  • a REST request for a specified byte offset of the file
  • the TCP connection is then reset

This series of commands is indicative of an FTP range request.


Reassembling the bytes of the file transferred via FTP range requests provides a tarball. Within the tarball is a third pcap trace file.

Again, observing protocols and conversations within the pcap reveals multiple HTTP connections between two end points. By observing the HTTP response codes in the connections a participant may have noticed only “206 Partial Content” codes. These codes are used to respond to HTTP range requests. The “Range” header is also included in all requests issued within the trace file.


Similar to FTP range requests, HTTP range requests can be used to request specific byte ranges for resources. Reassembling the byte ranges reveals a fourth and final packet trace file.

Within this trace file is a single TLS session between a host and Google. Within the requested SNI names of the “Client Hello” message of the TLS exchange additional server names (besides are present. This should be a big red flag to participants as this indicates the client would accept HTTP host names besides These SNI entries are also not ASCII characters, which also may have been a red flag.


The additional SNI entries in hexadecimal representation follow:

  1. 61707f4a
  2. 6801117501561d11
  3. 7811795450435511680144117d585a54116172706162110b75
  4. 4c

Knowing previous challenge flags took the form of “PAN{ FLAG }” XORing the first value of the first SNI entry, 0x61, with 0x50 (“P” in hexadecimal) revealed the key, 0x31, used to XOR the remaining characters with. Doing so produces the key for this challenge:

PAN{Y0 D0g, I Heard Y0u Like PCAPS :D}

This challenge tested the participant’s knowledge of standard networking protocols and how these protocols can be (mis)used to fragment data at the application layer.  It also tested the participant’s tenacity as it was a rather long and obfuscation heavy challenge. Tools used to solve this challenge could include Wireshark, common command line utilities, Bro, and Python’s dpkt module.

Threat 6 Challenge: There can only be one.

Challenge Created By: Micah Yates @m1kachu_

The hint is referring to the singular string required for the challenge YARA rule.

We are given the following instructions in the directions.txt file:

So lets unzip the included archive and take a look at the included files.

Based off of the above conditions, it appears that the rule must be exclusive to this set of 48 files. That rules out common repeating elements of a PE file, like header, padding etc. So I’m going to look at data and functionality within the binaries.

My approach to this problem would be to diff the two smallest files in the archive and see what code lines up. (If you have a paid version of IDA-Pro, you can use a free plugin, BinDiff.) We’ll diff the two smallest files:

  • fc2751ff381d75154c76da7a42211509f7cc3fd4b50956e36e53b4f7653534d5
  • e96de8414e0e438184d2352be17d1f31f2f309fe5f4c7c167dd4375fa28f96b0

Let’s sort the diffed files by basic block count, this will give us the longest functions in the binaries.

It looks like they’re 100% the same according to bindiff. Looks promising.


Opening up both files at sub_10001000 in Hex View in IDA we can see that they’re practically identical.


Since we’re going to convert this hex to a yara rule lets open up these files in bash and convert them to text. Then do a silly grep for the beginning of the hex that matches and almost everything after that.


This gives us a nice text file with all of the similar hex bytes:


There are 48 unique lines, so it looks like we’re on the right track. Next step is to write a python script to figure out the placement of those 52 wildcards.


This script checks line by line, then character by character to see if the hex text matches, if not it’s replaced with a “?”. It then cuts off the rule after the 52nd occurrence of the “?” and prints out the rule:


We end up with a yara rule that catches all 48 samples:

Submitting the rule above will result in the key: PAN{8oogI3_WonD3rL4nd}


If you combine the keys from all three yara challenges, they write out a Haiku about the Labyrinth Movie.

Threat 7 Challenge: There has been a breach of the Borg’s drone network!

Challenge Created By: Jeff White @noottrak

For this challenge, we’re given a Windows PE “drone.exe” and when we run it we’re greeted with a Borg Cube and some text about an apparent encryption.


Based on the text, it appears the URL ( – the Borg montra!) and key “borgdata” are encrypted to form the hash “374316062B033D0A3E6A746B46560377367A3328393720611641435A400C0C0B7E6E69
E392C2C394E5B5B1717061B0A”. Then an error occurs and another hash is displayed and the program exits.

Looking at the strings for the program, a number of them immediately stand out and, after a quick trip to Google, imply that this PE was built with PyInstaller.



%s returned %d


Cannot allocate memory for ARCHIVE_STATUS


Cannot open self %s or archive %s


Failed to get executable path.

GetModuleFileNameW: %s

Failed to convert executable path to UTF-8.


Cannot GetProcAddress for Py_DontWriteBytecodeFlag


Cannot GetProcAddress for Py_FileSystemDefaultEncoding


Cannot GetProcAddress for Py_FrozenFlag


PyInstaller is “a program that packages Python programs into stand-alone executables.”Now we know what we’re dealing with. I decided that the quickest way to tackle this challenge would be to extract the Python script instead of trying to reverse-engineer a 9MB PE that wraps a Python script.

After some more Google-Fu, we find PyInstaller Extractor on Sourceforge and try to run it against our binary but immediately receive a traceback.

Traceback (most recent call last):
File “”, line 115, in <module>
IOError: [Errno 2] No such file or directory: ”

Analyzing the traceback and the section of code where it happened, it looks like it had issues opening a file.

There were also some interesting files dropped before the program had the issue, which may be useful later.


Testing our assumption, we modify the script to print the ‘name’ field and can validate it’s printing out the file names we saw written to disk.


Given this, we simply wrap that action in a try/except where we specify the filename as “broke” if it is empty.

Running it again, we see a slew of files now get written to disk.


Looking at our “broke” file, we see it’s actually the script for the program!


Lots of things going on in the script, the main ones of interest are some of the ones that immediately jump out for investigation.

First things first, we’ll take a look at the encryption function and decipher that. We set a breakpoint on the main() function and begin to step through the code to understand what it’s doing.

  • Tries to load the file “borgstruct.cfg” and if that fails, it writes a dictionary to disk as that file.
  • Check if dictionary ‘key’[1] is equal to 1 and if so, puts together a string from various locations within the dictionary. This URL contains the Youtube video mentioned above.
  • Sets another variable to “borgdata”.
    Calls the encryption routine AABBBC(URL,”borgdata”,25).


  • Checks if URL is divisible by 8 and, if not, pads it with “@”.
  • Splits each variable into a list and begins the XoR the URL by key “borgdata”.CTF_threat_46
  • Once it has the first set of 8 ordinals it reverses them and uses this set as the next XoR key, which continues on for the full length of the URL.CTF_Threat_47
    This is classic cipher block chaining where each encrypted ciphertext is used as the encryption key for the next block.
  • If we let the process continue until the end, it reverses the order of the final ordinal list and then converts it to hex.

Since we know the ciphertext and now we know how the key is derived, we have enough pieces of the puzzle to build a decryptor. By copying the code from the script and de-obfuscating it, we can build our reverse decryption function.

Running the code with the known plain text and initial key shows we get the same ciphertext shown in the initial run of the drone.exe executable, along with our known YouTube URL.


Now we can take the returned value and put it through our decryptor to see what we get.

The hash “405E520E4A0E6F3401584E0A4E121E00322C24793B7E6C3304594B0E41131B032C6867
07A3E2A2B484553174C0E064724696363753C372F5B40550117061B0A” becomes “???????”.

Browsing to that Twitter address, we’re greeted with yet another hash.CTF_Threat_49

This hash decrypts to another excellent Star Trek Youtube video.

Looking back at our script, we can tell that the script sends a hash to on port TCP/2600 and based on the result of what the server sends back, either shuts down or updates its configuration with a new “function” called “FLAG REQUEST”. Sounds promising.

Let’s try editing the script and sending the hash from Borg Head.


We get a slightly different error message this time and no hash like we did originally. Instead of “HIVE|MIND|HASH” it’s “HIVE|ERROR|DATA|874”. Looking at the Scapy command again, the TCP Window Size is set by variable “scalar_array” which pulls from the configuration dictionary.


Setting the Window Size to 34 nets us a change in response from the server.


Looking at the 2.0 configuration, this key and values stand out immediately.

Running the bot again with the new configuration, it loads a new URL (with quite possibly the best Star Trek video ever made) and XoR key of “borgcube” to generate a new hash. Placing the new hash in our send command, we get the following error “HIVE|ERROR|CMD|2E”, which is different than the previous “ERROR|DATA” message we received.


Changing the Window Size back to 874 didn’t result in any change of the message; however, while looking through the rest of Borg Head’s Tweets, we find this little gem among the memes and Borgs talking to each other.

In the background of this message is a spreadsheet with a table showing various commands (CMD) and their respective Window Size. We can see that value 874 corresponds to “DRONE CHECKIN”, value 34 is “DRONE UPDATE”, and value 824 is “FLAG REQUEST”!

Updating our script one last time with our new Window Size we are rewarded with our key.


A Star Wars troll for all the Star Trek fans…



2 Reader Comments

  1. what happened to publishing the next set of official solutions for one of the tracks in LabyREnth ?

Got something to say?

Get updates: Unit 42

Sign up to receive the latest news, cyber threat intelligence and research from Unit42