LabyREnth Capture the Flag (CTF): Random 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. Over the last several weeks, we revealed the solutions for each of the challenge tracks. The time has come for us to share the solutions to our last track, the Random track.

Random 1 Challenge: OMG Java

Challenge Created By: Jacob Soo @_jsoo_

We are given a Java file that we can decompile with ByteCodeViewer.


We can see that we need an environment variable Admin and there is an if condition (if –isDrunk) in the contents of the variable. If we run it with that variable, we get the following output.


If we base32 decode the output, we get the flag.

We hope you enjoy this Java app as a warm-up. The rest will be much more exciting! The flag is PAN{D0_Y0u_Ev3n_Base32}

Random 2 Challenge: Can you express yourself regularly?

Challenge Created By: Richard Wartell @wartortell

For this challenge, we’re handed two files: and omglob_what_is_dis_crap.txt. Taking a quick look at, it looks like this is a server running somewhere, accepting connections, checking a regex, and then returning a key if the passed information doesn’t match the regex.

So what’s the regex that it tests against? Well, we see that it reads in the string from omglob_what_is_dis_crap.txt. So let’s take a look at that…

From this regex we can see three different types of conditions:

  1. .*[^0mglo8sc1enC3].*
    This condition tells us that the regex will match on any string containing characters other than “0mglo8sc1enC3”
  2. .{,190}|.{192,}
    This condition tells us that the regex will match on any string that is not 191 characters long, giving us the correct key length.
  3. .{97}[cgClm]
    The rest of the conditions look like this one. Each of them gives us a position in the key string, and tells us characters that that position will match on. Essentially, each one is telling us what characters don’t work at certain positions.

So, we need to create a string that is 191 characters long, and use the third type of condition to tell what characters each position can be. We can create a python script that parses the regex and gets out all of the appropriate conditions, then creates a string for us that won’t match those conditions. So feast your eyes below on a regex to parse a regex.

When we run this, we get the following output:

We can now test this regex to see if it works with the server and it will give us the key:


Which returns the key to get us to the next level:


Random 3 Challenge: I’m not sure how they got in or what they were after but they left some cookie crumbs.

Challenge Created By: Anthony Kasza @anthonykasza

Opening the provided network trace file with Wireshark and observing the IPv4 conversations, participants found only two systems communicating in the pcap. By observing the TCP tab of the conversations menu, participants could see the pcap file contains many connections from a single source port to almost all destination ports. This is indicative of a port scan.

This hypothesis can be confirmed by looking at the content of the TCP connections; there is none. Each SYN packet (except for 7) is responded to with an RST. The remaining seven connections, which responded with a SYN+ACK, were immediately closed with a RST by the originating side of the connection. This pcap definitely contains a TCP SYN port scan.

From this point, participants were required to hunt around in the pcap for the next step of the challenge. The only thing that changes between SYN packets in the trace file are:

  • Destination port numbers
  • Sequence numbers
  • TCP checksums

By observing the first SYN packet of the capture file, participants could locate a zip file header (0x504b0304) within the sequence number. I also tweeted a hint for this challenge referencing DoS cookies, a technology used to prevent SYN floods.

Below is a picture of the zip header in the first packet’s sequence number of the trace file.


A Python script using dpkt could easily be written to extract and reassemble the zip from the sequence numbers of the SYN packets.

Upon extracting the contents of the extracted zip file, participants find 853 numbered files. Opening and observing the contents of the files, participants should have been able to recognize base64 encoding. The files are each “chunks” of a base64 encoded file. By grepping the files for ‘==’ participants could find the last “chunk”, 339.bin. Then, grepping for the beginning contents of 339.bin, “SBAW”, participants could identify a file which overlapped with 339.bin, 531.bin. Participants could continue this manual process and reconstruct the original base64 blob using mad copy and paste skills. Or, a script like this could have been used.

Decoding the base64 blob results in another zip file which contains four images. One of the images contains the flag, PAN{YouDiD4iT.GREATjob}.

Random 4 Challenge: And you thought you hated PHP before you started this challenge

Challenge Created By: Josh Grunzweig @jgrunzweig

For the fourth challenge in the Random track, users are presented with a PHP script. This particular script weighs in around 1500 lines and presents a user with a text-based maze-like game where they must appropriately choose the correct path to receive the answer.


Looking at the underlying code, we see a large blob of obfuscated PHP code, followed by the HTML that generates the data above. The code consists primarily of what looks to be junk code, along with a few lines of unique code scattered within that to actually perform tasks.


Making a copy of this file and removing this junk code provides us with the following:

After de-obfuscating the code above, we get a better understanding of what is going on. Comments have been added and the code has been better formatted to show what everything is doing.

Using this information, we can decode the provided PHP using the following Python code:

This leaves us with the following decoded PHP code:

At this point we’re able to trace what is happening when we enter various options within the script. Data is written to a ‘.backup.bin’ file to keep track of what data was previously inputted. After stepping through the code and seeing what checks have been included, we determine that the following order must be provided:

  1. Right
  2. 246 Degrees
  3. East
  4. Back
  5. West
  6. 94 Degrees
  7. 94 Degrees
  8. Up
  9. Down
  10. 246 Degrees
  11. East
  12. South
  13. Stroll
  14. North
  15. Left
  16. Up
  17. Up
  18. Skip
  19. Right
  20. South
  21. 246 Degrees
  22. Back
  23. 94 Degrees
  24. Run
  25. Left
  26. South
  27. Run
  28. East
  29. 246 Degrees
  30. Back
  31. Up
  32. Jump

Entering this into the program leaves us with the following:


This leaves us with a key of PAN{Life is a maze of complications. Also, puppets are sometimes involved. Deal with it.}

Random 5 Challenge: You might have to be a snake charmer to crack the newest version of APT Maker Pro. What’s the worst that could be in ten lines of Python?

Challenge Created By: Gabriel Kirkpatrick @gabe_k


TLOP is the final challenge of the LabyREnth CTF random track. When you download it, you’re given a file called TLOP.pyw. If you run it, it will open a program called APT Maker Pro – UNREGISTERED TRIAL VERSION. There’s a button labeled “Generate APT” which informs you that you need to activate APT Maker Pro, and a button labeled “Activate APT Maker Pro!” which brings up a dialog asking for the product key. The challenge here is to find the product key to activate the program, allowing you to generate the APT.

Let’s start reversing! TLOP.pyw is a pyw file, not a py file, which is pretty much the same except that on Windows pyw, files don’t bring up a command line window.  It can be renamed to py if you’d like to print debug info to STDOUT. Once you open up the file, you’ll see ten lines of python (get it? Ten Lines of Python? TLOP… it’s stupid). Most of the code after the imports simply sets up a TKinter window, which is actually the splash screen. The last line is where it gets interesting:

exec marshal.loads(zlib.decompress(<longgggggggg gibberish string>))

So it’s an exec statement being passed the result of marshal.loads, which is loading some zlib’d data. Marshal is the Python module for serializing and deserializing builtin Python types. If we remove the exec and run the same line in the Python shell, we can see the result is a code object. Exec statements in Python accept two types of input, strings of Python code and code objects which contain compiled Python bytecode. Code objects are most often seen used in .pyc files, which are compiled Python files.   These compiled Python files are generated when Python modules are imported. Pyc files contain a 32-bit magic number specifying the Python version, a 32-bit timestamp of the compilation, and a marshaled code object. Since we already have a marshaled code object, we can turn this into a .pyc file with the following code:

Using uncompyle2 we can decompile the stage1.pyc file we created.


Decompiled output

The resulting decompilation contains a class called AptMaker which contains most of the code for the UI. After the class there is a RC4 function, as well as another exec on the result of another marshal. We can build the exec’d object into another pyc file to analyze it.

If we attempt to decompile the stage2.pyc with uncompyle2, we get the following error:

Throwing it at other Python decompilers will likely yield similar errors, so we’re going to have to take a different approach. Since it won’t decompile, we can attempt to disassemble the bytecode. Python has a built-in module for disassembling Python bytecode called dis, which we can use to disassemble the pyc file we produced by running the following code:

The following output is produced from the code above:

The code output is relatively simple. The first instruction, LOAD_CONST 0, pushes the constant at index 0 in the current code object onto the stack. After that we have MAKE_FUNCTION 0, which pops a code object off of the stack and turns it into a function object. The third instruction, STORE_NAME 0, pops the top item on the stack (the function we just created) and stores it with the name at index 0 in the code object.  That name in this case is “verify_license”. What this sequence of instructions does in practice is to create a function named verify_license. We can see the verify_license function is referenced in our previous decompilation inside the “is_licensed” function:

The two last instructions simply push the constant None to the stack, and then return it. This is present in all Python code objects that don’t have an explicit return value because all Python code objects must return something.

Now that we know that all this code is doing is creating a function, we can actually run it and use the verify_license function from the Python shell by importing the stage2.pyc file. If we run the following code we can start to play around with the function from the shell:

From this we know that verify_license is a function that returns a boolean. If we disassemble the function we will see the following:

Let’s take a look at this instruction by instruction:

First off it’s pushing constant 0, which is a code object, to the stack.

The next thing it does is push constant 1, which is None, to the stack.

DUP_TOP duplicates the top item of the stack, so it pushes another None to the stack.

EXEC_STMT is the equivalent of the “exec” keyword in regular Python. It takes three parameters, a code object or Python string, and two optional parameters containing global and local variables. In this case the code object is the one pushed at the start of the function, and the global and locals are not used, so those are the two Nones on the stack.

This code pushes the value stored for name 0 and returns it. Name 0 here appears to be “ “, which is not a valid Python name and is not referenced anywhere else in the function, so it’s safe to assume this gets set by the code run by the EXEC_STMT.

Since there’s not much code in here, we can assume the meat of the code is inside the code object that gets exec’d. We can disassemble that code object by running the following:

Unlike the previous times we’ve run dis in here, you’ll start seeing incredibly long output that looks something like this:


A bird’s eye view of the disassembly

If we actually let the disassembly run until it’s entirely finished we won’t actually get any useful information. The entire output is thousands of EXTENDED_ARG instructions with increasingly large arguments, followed by a JUMP_FORWARD to the same large value.

So what’s the issue? The Python runtime stores the arguments for Python instructions in a signed 32-bit integer called oparg. Python instructions that have arguments are 3 bytes long, 1 byte for the opcode and 2 bytes for the oparg value. The problem with this is that instructions can only set the lower 16-bits of the oparg, instead of the whole 32-bits. To get around this limitation, Python has an instruction called EXTENDED_ARG, which shifts its argument to the left 16-bits, allowing you to set the upper 16-bit in one instruction, and the lower 16 in the next. If you put multiple EXTENDED_ARG instructions in a row, the Python runtime will simply keep shifting the 32-bit integer that is oparg, and bits will fall off the end. However, if you disassemble that code with dis, oparg is stored in a Python number. Since dis uses a Python number instead of a fixed 32-bit integer, the number keeps on growing with every single EXTENDED_ARG instruction.

After coming across this and a few other issues with how dis handles funky bytecode, I wrote my own assembler/disassembler called pyasm, which we can use to produce a slightly more useful disassembly.

If we run on stage2.pyc, we will get stage2.pyasm. The code object we are looking at starts at line 11 in stage2.pyasm, with the actual instructions starting at line 100. By looking at the first bunch of instructions we start to notice a pattern:

We can see there are consistently two-three EXTENDED_ARG instructions with different arg values, followed by 133 using the arg value 0xFFFF. If we scroll down to the very end of the instructions at line 1390 we can see the last two instructions deviate slightly from this pattern:

We can work out the actual argument for JUMP_FORWARD as 65533 << 16 | 52549 which comes out to 0xfffdcd45. Oparg is signed, so it is actually -144059, which is actually a jump back to the second byte of the bytecode, instead of the first byte where it normally starts execution. This is a form of instruction overlapping, since the second byte is the argument for the first instruction, the code is hidden in the arguments of all of the EXTENDED_ARG instructions.

If we open the stage2.pyc file in a hex editor, we can delete the first byte of the bytecode, so that it starts disassembly from the second byte. To do this we just delete the byte at 0x6D in stage2.pyc, then change the 32-bit int containing the length of the bytecode 0x69 from 0x232BC to 0x232BB. Now if we disassemble the file we should see the following at the start of the code:

Now it’s starting to look more like normal bytecode. The first thing it does is load name 0x9100 which is “license key” and then jump forward 401 bytes. If we cut out the 401 bytes after the jump forward and disassemble again we get even more:

Cool, we’re starting to get more. Now if we do this a few more times, we start to see some interesting stuff:

If we strip out all the JUMP_FORWARD and NOP instructions it becomes easier to see what it’s doing:

It’s taking the variable named “license_key”, and a constant with the value 0, and doing a BINARY_SUBSCR, which allows you to retrieve a value at an index, and then it is storing it in name 37122, which is a string of whitespace. This is equivalent to the following line of Python:

= license_key[0]

It then does the same thing, but instead of using the variable “license_key” it uses const 37122, which if we look is actually a PNG, and it gets the value at index 542 and stores it in a different name 37123, which is also whitespace. After that it loads const 37131, which is a code object, and does an exec. If we look at the code in const 37131 it’s fairly simple:

It is loading the two whitespace named variables that we just set up, comparing them, and storing the result of the comparison in a third whitespace named variable. This is where the license key is actually being checked. We can make the program actually spit out it’s key by simply inserting a print statement in this code object:

Now if we build the patched stage2.pyasm file with makepy and run verify_license again we can see it print out the correct key:

> from stage2solve import *
>>> verify_license(‘A’ * 100)

So the license key is “1_W4nnA_b3_Th3_vERy_b3ST!” if we go and plug that into the program we can see that it turns green and activates.



Yay! Now we can press the “Generate APT” button, which creates a file called “EVIL_MALWARE_CYBER_PATHOGEN.pyc”. If we run that it will scroll the ASCII art flag across the screen.


And there’s the flag! PAN{l1Ke_n0_oN3_ev3r_Wa5}!

That’s it folks! We hope you enjoyed participating in these challenges as much as we enjoyed creating them. Be sure to also check out how other threat researchers solved the challenges from this track:

Random 1:

Random 2:

Random 3:

Random 4:

Random 5:

Got something to say?

Get updates: Unit 42

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