Challenge accepted!

A while back someone tipped me off about challenges posted by the Swedish FRA that they use for recruiting, and for fun as you will see in this post. FRA, the National Defence Radio Establishment, is the Swedish national authority for Signals Intelligence. So I checked it out and immediately got excited.

Check them out here:

The first one that caught my eye was the one labeled “Underrättelseanalytiker inom IT-säkerhetsområdet”, ruffly translated it means “IT Security Intelligence Analyst”, which was the first one that involved cryptography. So I downloaded the zip-file (

Once I had extracted the file I was presented with two files;

  • challenge.pcap
  • readme.txt

The readme.txt contain the objectives written in Swedish, so here is a translation:

Are you the right person for us?

We understand that everyone does not have the time to solve all tasks but we
want you to solve at least one in order to apply for a job here.

1.      What color is the ball?

2.      Draw the game map

3.      Draw the game map in color

4.      What does the high score look like?

E-mail your solution to

Good luck!

OK, I was not expecting that given I was presented a pcap file which normally is a file containing captured traffic on a network, but I got even more excited to look into the packet capture. So I fired up Wireshark, which is a program to capture traffic but also to look at and analyze captured traffic.


The first thing I noticed was that the captured traffic was local on a single system (the same machine). “” is always the localhost and I saw that both the source and the destination was “” . This most likely meant that the server and the client software was running on the same machine as the software capturing the traffic.

The time of the capture is mid-lunch on the 21st of January 2016. I guess the creator of the challenge took a desk lunch.

The first six packets are two TCP-handshakes which are then followed by an HTTP GET. HTTP GET is a request usually made by web browsers when they want to download something from a web server. In this case we can see that it is likely a Google Chrome browser requesting the index file of “http://localhost:8000” where the “:8000″ means that instead of the standard port 80 for HTTP, the browser should query port 8000.

I continued to look at the packets after the GET request, which is the response to the HTTP GET request I saw earlier:

I was looking at HTML code that presents the contents of a directory on the host, which was two files;

  • crypto.txt

“.py” indicates a script written in my favorite language Python, so now I knew this was going to be a fun exercise.

I continued browsing the packets to see if there was a download of the files, and I did find a GET request for the Python script shortly followed by the actual script.

For the Python people, here is the full script, as-is:

# tested on Ubuntu

import json, socket
import tty, termios, sys, select, time
import socket, md5
from Crypto.Cipher import ARC4 # apt-get install python-pycryptopp
import random, string

class ClientView:
    def __init__(self, s):

            f=open('crypto.txt', 'r')
            # If keys are missing
            self.xor_key=raw_input('Enter xor key: ')
            self.longxor_key=raw_input('Enter long xor key: ')

            # generate rc4 key
            self.rc4_key=''.join(random.sample(string.uppercase, 7))
            print "Keys are stored in crypto.txt"
            f=open('crypto.txt', 'w')
            print >> f, self.xor_key
            print >> f, self.longxor_key
            print >> f, self.rc4_key

        def render(self):
                while 1:
                    except socket.timeout:

                    if len(data):
                        for d in ''.join(data).split('\n'):
                                if not len(d):


                                if j['encoding']=='longxor':
                                    decoded=''.join([chr(ord(j['buf'][i]) ^ ord(self.longxor_key[i % len(self.longxor_key)])) for i in range(len(j['buf']))])
                                elif j['encoding']=='xor':
                                    decoded=''.join([chr(ord(i) ^ ord(self.xor_key[0])) for i in j['buf']])
                                elif j['encoding']=='rc4':

                                # Received data must be decoded correctly
                                if j['md5']==md5.md5(decoded).hexdigest():
                                    print decoded
                                print "ERROR", len(data), len(d)

        def send_packet(self, com, arg):
            buf={"command": com, "arg": arg}

        def set_player_name(self, buf):
            self.send_packet("set_player_name", buf)

        def player_input(self, buf):
            if 'LEFT'==buf:
                self.send_packet("input", "LEFT")
            if 'RIGHT'==buf:
                self.send_packet("input", "RIGHT")
            if 'FIRE'==buf:
                self.send_packet("input", "FIRE")

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))


name=raw_input('What is your name? ')


old_settings = termios.tcgetattr(sys.stdin)

    while 1:
        # get updates from server

        # nonblocking input
        if[sys.stdin], [], [], 0) == ([sys.stdin], [], []):

            if c=='\033':
                break # player pressed esc
            if c=='z': # left
            if c=='x': # right
            if c==' ': # space


    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)


So now I had something we could call a client, that look a lot like a game given the references in the comments (in-line comments in Python starts with a ‘#’) like “# player pressed esc”, “FIRE”, etc.

Basically, it starts by asking the player to enter his or her name. Then the server will send data to the client, which can be encoded with either “xor_key”, “long_xor_key”, “rc4_key”, or no encoding.

Given that this was a challenge in network protocols and crypto I did not expect to find any trace of the crypto.txt in the packet capture. But I looked anyway. Doing so I stumbled upon a packet with payload that looked like JSON.

JSON is a way of structuring data and in this case it contains four parameters with different values;

  • “function” set to “clear”
  • “md5” set to “84fc620ddf5d285f4d15bfe440da1100”
  • “buf” set to “\u001b[2J”
  • “encoding” set to “none”

Seeing this I noted two things; MD5 is a hashing function, or one-way encryption algorithm designed to output unique strings called “hashes”, or “checksums”, or “digests” for its input. And the value of “buf” sure looks like an ANSI escape code. ANSI escape codes are used to control the formatting, color, and other options in terminals, like this:

Entering print u”\u001b[2J” cleared the console and I had a confirmed link between the function “clear” and the value of “buf” which is an ANSI escape code to clear the terminal. To verify the logic I calculated the MD5 checksum of the “buf” value, and it matched the “md5” value perfectly.

After the “clear” packet there is another packet with JSON. This time the “function” has the value of “layout”, then there is the MD5 hash and the encoding is set to “none”. But “buf” contain a lot more data, still ANSI escape codes.

{"function": "layout", "md5": "98997c968f32a8e562b6a07e15961505", "buf": "\u001b[0;0H\u001b[42m                                                                                         \u001b[1;0H \u001b[1;66H \u001b[1;90H \u001b[2;0H \u001b[2;66H \u001b[2;90H \u001b[3;0H \u001b[3;66H \u001b[3;90H \u001b[4;0H \u001b[4;66H \u001b[4;90H \u001b[5;0H \u001b[5;66H \u001b[5;90H \u001b[6;0H \u001b[6;66H \u001b[6;90H \u001b[7;0H \u001b[7;66H \u001b[7;90H \u001b[8;0H \u001b[8;66H \u001b[8;90H \u001b[9;0H \u001b[9;66H \u001b[9;90H \u001b[10;0H \u001b[10;66H \u001b[10;90H \u001b[11;0H \u001b[11;66H \u001b[11;90H \u001b[12;0H \u001b[12;66H \u001b[12;90H \u001b[13;0H \u001b[13;66H \u001b[13;90H \u001b[14;0H \u001b[14;66H \u001b[14;90H \u001b[15;0H \u001b[15;66H \u001b[15;90H \u001b[16;0H \u001b[16;66H \u001b[16;90H \u001b[17;0H \u001b[17;66H \u001b[17;90H \u001b[18;0H \u001b[18;66H \u001b[18;90H \u001b[19;0H \u001b[19;66H \u001b[19;90H \u001b[20;0H \u001b[20;66H \u001b[20;90H \u001b[21;0H \u001b[21;66H \u001b[21;90H \u001b[22;0H \u001b[22;66H \u001b[22;90H \u001b[23;0H \u001b[23;66H \u001b[23;90H \u001b[24;0H \u001b[24;66H \u001b[24;90H \u001b[25;0H \u001b[25;66H \u001b[25;90H \u001b[26;0H \u001b[26;66H \u001b[26;90H \u001b[27;0H \u001b[27;66H \u001b[27;90H \u001b[28;0H \u001b[28;66H \u001b[28;90H \u001b[29;0H \u001b[29;66H \u001b[29;90H \u001b[30;0H \u001b[30;66H \u001b[30;90H \u001b[31;0H \u001b[31;66H \u001b[31;90H \u001b[32;0H \u001b[32;66H \u001b[32;90H \u001b[33;0H \u001b[33;66H \u001b[33;90H \u001b[34;0H \u001b[34;66H \u001b[34;90H \u001b[35;0H \u001b[35;66H \u001b[35;90H \u001b[36;0H \u001b[36;66H \u001b[36;90H \u001b[37;0H \u001b[37;66H \u001b[37;90H \u001b[38;0H                                                                                         \u001b[40m", "encoding": "none"}

The next step was obvious, print it in a console!

Now this really started to look like the games I knew from way back when I was a kid messing around with my Commodore 64. So I went back to look more closely at the code in and doing so I thought that I could easily write my own version that take a file as input and parses each JSON message and print it out in the console. In theory, I should be able to see the game being played. First, I went through the rest of the packet capture and found other “functions” like “menu”, “highscore”, “score”, “ball”, “player”, “mono_map”, “color_map”. The “mono_map” and the “color_map” data was encrypted using XOR and “highscore” was encrypted with RC4 which is also using XOR, but with a continuous key stream instead of a shorter, repeating key.

XOR, or eXclusive OR, is a logical operator that look at two bits (a bit is a one or a zero, where 8 of them sums up to a byte) and if they are equal it output zero, but if they are not equal it output one, like this:














How XOR works

In addition to all these “functions” I found “commands”, also in JSON format, that seemed to be things sent to the server;

{"command": "set_player_name", "arg": "Kalle"}

{"command": "input", "arg": "RIGHT"}

{"command": "input", "arg": "LEFT"}

I decided to extract all the JSON messages which is very easy to do in Wireshark. Right-click on any packet and choose “Follow” -> “TCP stream”. This will open a window with all the payload nice and clean without any other details about the packets. Doing this I saw that there were two TCP streams with “game” data. I saved both in case I needed the other one later. I could already see that the “highscore” data differed when I was comparing the streams.

Recon analysis

Now I had a lot of information about this challenge and could draw some conclusions;

  • It was the network traffic containing the download of a python script that looked like a game client, followed by the conversation between the game client and the game server.
  • The conversation was in JSON, which I had extracted. Two TCP streams (or game rounds?).
  • There were three kinds of encoding/encryption all using XOR. One with a long key and presumably another with a smaller key, and one using RC4 which is a streaming key that is as long as the input. This made sense looking back at the tasks:

The first task is to find out the color of the ball and that had no encryption, leading me to assume that the first task was made to test basic skills in analyzing network traffic and identifying the payload as JSON and ANSI escape codes.

The second task was to draw the mono_map which was XOR’ed with the smaller key. So when I looked at the code again for the xor_key I noticed this:

elif j['encoding'] == 'xor':
    decoded=''.join([chr(ord(i) ^ ord(self.xor_key[0])) for i in j['buf']])

The [0] means that whatever the xor_key consist of I should only look at the first character! Easy.

The third task was to draw the color_map which was XOR’ed with a longer key, and finally tell what the high score looked like which was encrypted with RC4 stream cipher.

The difficulty was undeniably increased for every task.

  • It should be possible to write a “player” to animate the game that was played, the hard part is to break the keys…

Writing the player

Stealing with pride I altered the to read from a file instead and just print out the ANSI escape codes, if the MD5-hash was correct for each “function”:


from __future__ import print_function import json

import tty, termios, sys, select, time
from hashlib import md5
from Crypto.Cipher import ARC4

old_settings = termios.tcgetattr(sys.stdin)

xor_key = ''
long_xor_key = ''
rc4_key = ''

f = open('packets1.dump', 'r')
data =

for d in ''.join(data).split('\n'):
        if not len(d):

        j = json.loads(d)

        if j['function'] != 'clear':

        if j['encoding'] == 'longxor' and len(long_xor_key) > 0:
            decoded = ''.join([chr(ord(j['buf'][i]) ^ ord(long_xor_key[i % len(long_xor_key)])) for i in range(len(j['buf']))])
        elif j['encoding'] == 'xor' and len(xor_key) > 0:
            decoded = ''.join([chr(ord(i) ^ ord(xor_key)) for i in j['buf']])
        elif j['encoding'] == 'rc4' and len(rc4_key) > 0:
            C =
            decoded = C.decrypt(j['buf'].decode('base64'))
            decoded = j['buf']

        if j['md5'] == md5(decoded).hexdigest():

        if[sys.stdin], [], [], 0) == ([sys.stdin], [], []):
            c =

            if c == '\033':

termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

Testing the player

There was plenty of data that had encoding set to “none”, so I was now very eager to test the player and confirm my suspicion about the game thing…and voilà!

I had solved the first task. The color of the ball was blue (ETERNAL BLUE?) and I was witnessing the good old Arkanoid game. I could see the ball bouncing against something that was not visible, but the score was increasing so that was making sense in terms of how the game works. I was ready to find the xor_key.


Using the same code, but stripping of all of the unnecessary things and adding a “brute force” part was simple enough since I already knew that the xor_key was only one character. In Python that is a simple task by iterating the key in an array called string.printable that contains all printable characters A-Z, a-z, 0-9 and all special characters and white spaces. So after each XOR I compared the MD5 hash with the expected, and if I had a match I knew I had the correct key.


from __future__ import print_function
import string
import json
from hashlib import md5

f = open('packets1.dump', 'r')
data =

keyFound = False

for d in ''.join(data).split('\n'):
   if keyFound:

       if not len(d):

       j = json.loads(d)

       if j['encoding'] == 'xor':
           for char in string.printable:
               decoded = ''.join([chr(ord(i) ^ ord(char)) for i in j['buf']])
               if j['md5'] == md5(decoded).hexdigest():
                   print('XOR key: %s' % char)
                   keyFound = True

It was simple enough. Running the script took no time, and the result was:

So I inserted the super secret character “A” into my player; xor_key = ‘A’ and ran the player again:

I had now successfully completed task number two: “Draw the game map”.

The next task proved to be a bit more difficult. I had no information about long_xor_key. How long was it? Does it contain non-printable characters? I also knew very little about the plain text that would be the result after XOR’ing the data with the long_xor_key except that I was dead sure it had to be ANSI escape codes.

Breaking the long_xor_key

It then hit me that the ANSI escape codes in the color_map should have many similarities with the ANSI escape codes in the mono_map, and I had the plain text of that. The cool thing with XOR is that if you take a plain text and XOR it with a key, then XOR the output of that first operation with the plain text you end up with the key, like so (the XOR operation is represented by ^):


So if I wrote a script that take the plain text of the mono_map and XOR that with the encoded version of the color_map I should get some hints about what the long_xor_key could be. So I decided to create something that did just that, then look at the result for repeating strings of a certain length and store them along with the number of times it repeated. (Probably not how one should do this, but hey, I am just having fun!) Then I would “move” the mono_map one step left and repeat the procedure to see if the same repetitions occur elsewhere, and if so add the number of repetitions to the previous result. So “sliding” the mono_map plain text over the color_map a number of times should give me a better idea about the long_xor_key than just doing it once. I also wanted to sort the list of potential keys by how many times it was found and start with the ones with most repetitions first to hopefully speed up the process. I also wanted to be able to limit the key length, starting out with a minimum length of 8 characters and a maximum length of 16 characters. I also wanted to disqualify any key that did not repeat at least 64 times during the process. In addition I decided to slide 16 times (the same as the longest acceptable key length). The code ended up like this (I never claimed to be a skilled programmer, I just want to be able to write things that solve my problems):

from __future__ import print_function
import json
import string
from hashlib import md5
import operator

chars = string.digits + string.lowercase + string.uppercase # Assuming there are no non-printable characters in the key and for now excluding whitespaces
MAX_SLIDING_WINDOW = 16 # We want to use an offset to find the key

xor_key = 'A' # We already know the XOR key and we need it to produce the known plaintext
gotXor = False # To know when we have data encoded with the short XOR key
gotLongXor = False # To know when we have data encoded with the long XOR key

running = True # We only want to work on one set, not the entire file, this is to keep track

xor = '' # Placeholder for data with short XOR key
longxor = '' # Placeholder for data with long XOR key
md5longXor = '' # Placeholder for the MD5 hash of the longxor data to check if we got the correct key
result = '' # Placeholder for the result of XOR of known plaintext and XORed data

f = open('packets1.dump', 'r') # File containing JSON from the packet capture
data = # Load file content

for d in ''.join(data).split('\n'):
   if not running:

       if not len(d):

       j = json.loads(d)

       if j['encoding'] == 'xor' and not gotXor:
           xor = ''.join([chr(ord(i) ^ ord(xor_key)) for i in j['buf']])
           gotXor = True

       if j['encoding'] == 'longxor' and not gotLongXor:
           longxor = j['buf']
           md5longXor = j['md5']
           gotLongXor = True

       if gotXor and gotLongXor:
           running = False
           candidates = {}

           for slidingWindow in range(MAX_SLIDING_WINDOW):
               for i in range(0, len(xor)):
                   char = ord(xor[i]) ^ ord(longxor[i + slidingWindow])
                   if chr(char) in chars:
                       result += chr(char)
           for cursor in range(len(result)):
               for window in range(MIN_LENGTH, MAX_LENGTH):
                   if (cursor + window < len(result)): strBuffer = result[cursor:cursor + window] if len(strBuffer) >= MIN_LENGTH:
                           if strBuffer in candidates:
                               candidates[strBuffer] += 1
                               candidates[strBuffer] = 1
           shortList = {}
           for key in candidates:
               if candidates[key] >= MIN_REPEAT:
                   shortList[key] = candidates[key]

           rShortList = sorted(shortList, key=operator.itemgetter(1), reverse=True)

           print('Testing the most likely keys...')
           for key in rShortList:
               print('Testing key: %s' % key)
               decoded = ''.join([chr(ord(longxor[i]) ^ ord(key[i % len(key)])) for i in range(len(longxor))])

               print('Checking MD5...', end='')
               if md5longXor == md5(decoded).hexdigest():
                   print('success!\n\nLong XOR key found: %s' % key)
                   print('not a match.')

Finally, I ran the script which completed much faster than I had expected. I was probably lucky, but when I saw the result I smiled…

I quickly went back to my player and insert the value for long_xor_key and ran the player again:

I had completed the third task; “Draw the game map in color”, and I was quite happy with my achievements so far. But there was a gaping, black, empty square taunting me…the area where the RC4 encrypted high score is supposed to be.

The final task

I knew this would be much harder. Breaking the xor_key was easy, the long_xor_keywas a bit harder (and I might had been lucky). But the rc4_key should be something else in terms of complexity. After some thinking I came to my senses and realized I was never going to find out the exact key that was used to produce the random noise of bits that was actually used to encrypt the high score. I am still scratching my head so there will (hopefully) be a part two to this article. But I do have a way forward…I just need to find the time. What I have so far is:

  • I have several messages encoded with RC4 (EM1, EM2, EM3, etc).
  • RC4 produce pseudo-random bits based on a key which can be referred to as a keystream (KS).
  • The same key is used each time! (based on the Python code in the file).


Key -> RC4 algorithm -> KS

M1 ^ KS = EM1

M2 ^ KS = EM2


When I think back of the properties of XOR I understand that XOR’ing M1E and M2E would cancel out the KS. This means that:

EM1 ^ EM2 = (M1 ^ KS) ^ (M2 ^ KS)


(M1 ^ KS) ^ (M2 ^ KS) = M1 ^ M2


EM1 ^ EM2 = M1 ^ M2

So, XOR’ing two RC4 encrypted messages (that was encrypted using the same key) should get me the same product as XOR’ing the original messages. The key will be gone!

I still want the KS, at least as long as the longest message, this way I can decode the high score on the fly, but I will rid my player of the RC4 key and just use an even longer XOR key like in the previous tasks. But how do I get the key stream? I could use the above to get more plain text of the high score, then attack an encrypted message to get the key stream. I know the player name is “Kalle” and his final score. I am pretty sure there is ANSI escape codes. So I have things to play with…I just need time.

I will give the last task a go, but if I fail I will put it on ice for a while and move to another challenge. It is fun and I learn a lot.

Hat’s of to FRA for creating these challenges!