No description, website, or topics provided.
Clone or download
Latest commit 94955a1 Oct 19, 2018
Type Name Latest commit message Commit time
Failed to load latest commit information.
crypto-2 write-up Oct 19, 2018
others-3 missing code, text corr Oct 19, 2018
others-6 write-up Oct 19, 2018
others-7 crosswords Oct 16, 2018
reverse-1 write-up Oct 19, 2018
stego-1 write-up Oct 19, 2018
system-1 first Oct 19, 2018
system-2 write-up Oct 19, 2018
system-3 first Oct 19, 2018
web-3 first Oct 19, 2018
web-4 write-up Oct 19, 2018
web-5 write-up Oct 19, 2018 text corr Oct 19, 2018

TheCatch-2018 write-up

The details for the Catch competition organized by CESNET are available at:

Category: Web

  1. Scandal I.

    Assignment: Agent, before you go on the another mission, read this file from our archive: First part is here: Good luck, Agent

    Solution: Downloading the text and comparing with the original leads to nothing - there must be something in HTML. Yes!

    it is of such weight it may have an influence upon European history."</p>
    <!-- CT18-H61o-Jwhd-Uhoa-TO0r -->
    <p>"I promise," said Holmes.</p>
  2. WiFi

    Assignment: Agent, you are in critical situation, you desperately need to connect to the network and send Intel to our headquarters, but there is no LTE. The only way is to connect to encrypted wifi. Are you able to connect? The login form is here: Good luck, Agent

    Solution: There is nothing particularly interesting in the HTML code, in the HTTP headers, no COOKIE. Why the page contains MAC address? It is not usual.

    Let us look check the manufacturer and available vulnerabilities. The database of manufacturers ( gives us "Ubee Interactive Co., Limited". The exploit database contains an interesting record: ( There are some URLs leading to a leaky cgi-bin/setup.cgi, but we can't modify the URL since it is a fake PHP page starting with Wifi.php.

    Let us focus on the password. First, let us try defaults for Ubee. Nothing worked. Searching for a different CVE revealed a page ( We can enter the SSID and we can obtain the default password generated by the firmware (

    The password is WNFIZUIG.

  3. Scandal II.

    Assignment: Agent, before you go on the another mission, read this file from our archive: Second part is here, but it's encrypted: Good luck, Agent

    Solution: Now, HTML does not contain any Flag. But the advandate is that we know the original text.

    At three o’clock precisely I was at Baker Street, but Holmes ...

    The encoded text starts with

    rb bxjpp y'acyao wjpavkpct v frk rb iropj-kbjppb, izb xycgpk ...

    We can see that there is no encryption, only different symbols are used. It is probably a substitution cipher. This can be confirmed also by the frequency analysis. We can easily determine the mapping, but we are lazy guys, so let us look for an existing tool. And here it is: It is a solver which does not even need the input text. Frequencies of letters of common English text are used instead.

    Decrypted text

    The decrypted text contains these sentenses:

    note from challengemaster: this is my favourite adventure. i think every agent should know sherlock holmes. but it would be too easy to get the flag just with frequency analysis. you have to look somewhere else! maybe try to get some coffee and somthing sweet.

    This hint is quite clear. The page is from some reason a PHP script! Let us check the cookies.


    When we change the value from 0 to 1 and refresh the page, the Flag reveals.

    Or we can directly obtain the results using, e.g., curl:

    $ curl -s --cookie Admin=1 | grep Flag
      Flag is CT18-22xm-uJPb-SFyO-zOkp```
  4. Naval Battle

    Assignment: Agent, you have been temporarily assigned to navy command. Your next mission is to win the most crucial naval battle.You have to win at least 80 out of 100 matches to get the flag. Be careful, the naval drones system interface is still in early beta. nc 8000 The bridge is yours, sir.

    Solution: The most important part of this problem is to realize that this game is a common Battleship game ( and not a tic-tac-toe :-)

    Then, the solution is straightforward. The TCP server uses a text protocol, it outputs a JSON string containing the state of the game as seen from your view, score and result of the last move.

    Send your moves in form A10, B1 ..
      "board": {
        "A": "...............",
        "B": "...............",
        "C": "....XXX........",
        "D": ".....X.........",
        "E": "...........X...",
        "F": "..........XXX..",
        "G": "...........X...",
        "H": "...............",
        "I": "...............",
        "J": ".XX............",
        "K": ".X.............",
        "L": ".........X.....",
        "M": ".........XX....",
        "N": ".XXX.....X.....",
        "O": "..X............",
        "_": "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"
      "myMove": "",
      "myMoveResult": "",
      "overallResult": "0 0 (N/A)",
      "yourMove": "",
      "yourMoveResult": ""

    As we need to win at least eighty games from one hundred, it makes no sense try to do that manually. The only possibility is that there is a weakness in the protocol (biased random generator, buffer overflow, etc.). So let us firstly create a simple bot which uses a random generator to test whether there is a simple strategy at the server that can be beaten. The code of this bot is here:

    Unfortunatelly, the server side AI performs very well and we lost easily. Hence, we need to create a more sophisticated algorithm. We can use an approach based on a simple pattern matching. It consists of three phases executed in each step: 1) pattern-based elimination of the finished battleships and elimination of the cells whose value is apparent, 2) determination of score for each unknown cell, 3) selection of the best candidate for next move.
    The code of the final version is here:

    The most tricky part is the manipulation with the game board. For each cell, we need to extract cell neighborhood consisting of 3x3 cells (function getNeigh). Then we need to calculate the number of matches against each pattern (function getScore). The patterns are defined at the beginning of the source code and they consists of three symbols - 0 (exact match for miss), 1 (exact match for hit), X (arbitrary value). If there is a mismatch, the score is negative. Otherwise, number of matches symbols is returned. If all nine cells match, we can eliminate all 3x3 cells + the surrounding cells. If there are exactly three hits and all these hits match with a pattern, we can eliminate some of the cells (those that are marked with X) inside 3x3 block.

    The second part which determines the score is the most essential part which defines the quality of our AI. For each empty and hit cells we calculate the number of matched hits against each pattern. This number has the highest priority. Then we add the number of ones the pattern contains (i.e. the gain we can receive if there is a such battleship). And finally, we prioritize completely unknown 3x3 blocks (this helps to avoid shooting near to edges a prioritize exploration of the board).

    The successrate of this strategy is not worse than 96% (which is sufficient) depending on the game (there are cases that can't be won as we didn't have success with identifying hits). See, for example, the attached log final.log:

     My move G6
     ??0100???00 0??   ..o.......Xo.o.  |  .. X  ...    ..   .. .......X . .
     ?? 110 ??011 ??   ..OOoo...OOO..o  |  .. XX  .. XX ..   ..XX  ...XXX.. 
     ?? 1 0???0 10??   ...O......o....  |  .. X  ...  X ..   ...X...... ....
     ??0 0?? ??0 0??   .o...o....o....  |  ..   .. ..   ..   . ... .... ....
     ? ????0????????   ............o..  |  . .... ........   ............ ..
     ?? ?? X ???000?   .............o.  |  .. .. X ...   .   ............. .
     ???? *XX ?001 0   .o......o.....o  |  .... *XX .  X     . ...... ..... 
     ? ? ? X0??01110   .o.o..oO.......  |  . . . X .. XXX    . . .. X.......
     ?? ? ?0 ??00 00   o.o..oOOO......  |  .. . .  ..         . .. XXX......
     ????? ??000????   .o....oO...oO..  |  ..... ..   ....   . .... X... X..
     ?? ???? 0100? ?   .o.......ooOOo.  |  .. ....  X  . .   . .......  XX .
     ???? ? ? 110???   ...o.o..X..oO..  |  .... . . XX ...   ... . ..X.. X..
     ???????? 100 ??   .......XX.o...o  |  ........ X   ..   .......XX. ... 
     ? ?? ? ?00 ?? ?   .o......Xo.o...  |  . .. . .   .. .   . ......X . ...
     ???????????????   .o.....o.......  |  ...............   . ..... .......
    Score: 99 1 (99.0%)
    Flag is CT18-SHIP-.....

    The left part (first two boards) is the board state as seen by our algorithm and opponent; the right part shows the positions of the battleships (opponent, our).

  5. Happy CAPTCHA

    Assignment: Agent, we have discovered a service protected by a very peculiar CAPTCHA mechanism. If we can beat it using a computer, we might me able to enumerate the service and get a lot of information. That will take a lot of requests and we just don't have the manpower to do these CAPTCHAs by hand.

    To pass the CAPTCHA you need to identify "happy" smiley faces. Then lookup their R, G and B color channels. For each channel, xor together the values of all "happy" smileys and present the results to the CAPTCHA mechanism.

    You only get one try per image. Submit your results via POST or GET (whichever you like). Submit the values in decimal. Use parameters names of r (for the red channel), g and b.

    There is also a time limit present! You have up to 5 seconds to submit your answer.

    Solution: The problem is clearly specified - we need to retrieve the image using HTTP protocol, detect the faces and POST the result back.

    Let us briefly recap the possibilities we have.

    1. approach of a lazy man: The interface enables to gather a database for training a deep neural network which can provide the answer for a given image. A modern and cool solution, but the problem is that it requires to have a suitable HW to train a network. Otherwise we have to wait a little. We want to be the first one who solved this problem, so let us look at a different possibility we have.

    2. humanAI: We have 5 seconds and several team members. This is an easy problem for our team. Everything we need is to make a little GUI which detects color of a pixel which is near to a point where you clicked. Each member just look at a single row and manually detects smiley faces. Then it is just few clicks, the tool computes XOR of all the colors and makes a single POST. This can be done in several lines of code in Python. Or even better - let us use Javascript and we can avoid creating a GUI.

      We could create a single HTML page that solves this problem, but there is one small problem called cross-origin data. Hence, the easiest solution is to use nwjs instead of a browser. NwJs allows to disable cross-origin security checks (see package.json).

      We need to load image, display this image in canvas and we can then read the pixels from the canvas. In order to avoid problems with antialliasing (different colors can occur even whithin a single smiley), we grab a larger portion of data around the position where we clicked. Then we filter out the black pixels, sort the remaining colors and pick up the color in the middle of the sorted sequence (i.e. the MEDIAN).

      const colors = Array.from(ctx.getImageData(mousePos.x-50, mousePos.y-50, 100, 100).data). /* grab 100x100 pixels */
                           map((val, idx, x) => (idx%4 == 0) ? (x[idx]<<16)|(x[idx+1]<<8)|x[idx+2] : 0). /* convert RGBA tuple to single value */
                           filter(x => x). /* remove zeroes (includes color of the background) */
      const clr = colors[colors.length >> 1]; //median

      The code can be used by executing the following sequence of commands

      # Install nwjs
      $ npm i -g nwjs
      # Install the latest version 
      $ nw install 0.34.0
      # Run nw 
      $ nw .

      Human AI

    3. lonely warrior: The problem is that we do not have so many friends. In fact, our team consists of a single programmer and he is a bit slow. So, let us create a little script that is able to recognize the smiley faces automatically. There is a great library for manipulating with images called OpenCV. To detect the faces, we can use routines for scale invariant object matching. But it requires to create a template (e.g. Haar cascade) and we are a bit lazy. It is not even clear whether the accuracy of the detection will be sufficient. So let us make a custom detector.

      Firstly, let us detect the smiley faces. This can be done by calling function cv2.connectedComponentsWithStats which identifies isolated components and returns their bounding boxes together with centroids (position of the mass). This help us to separate the smileys.

      gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
      #we need to convert the image to b&w
      im_bw = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, 0)
      nlabels, labels, stats, centroids = cv2.connectedComponentsWithStats(im_bw, connectivity=8)

      To detect whether it is a face, we can use a simple condition. The object must be reasonably large and it has to contain at least three subcomponents. Both conditions solve the problem that there may be some small objects due to noise (a garbage).

      for label in range(1,nlabels):
            # retrieving the width of the bounding box of the component
            width, height = stats[label, cv2.CC_STAT_WIDTH], stats[label, cv2.CC_STAT_HEIGHT]
            # retrieving the leftmost and topmost coordinate of the bounding box of the component
            x, y = stats[label, cv2.CC_STAT_LEFT], stats[label, cv2.CC_STAT_TOP]
            #ignore object smaller than 40 pixels
            if min(width,height) < 40: continue
            #ignore object having less than three subcomponents
            subcomps = [i for i in range(nlabels) if rectContains((x,y,width,height),(stats[i, cv2.CC_STAT_LEFT],stats[i, cv2.CC_STAT_TOP]))]
            if len(subcomps) < 3: continue
            #we have smiley face here

      For each smiley face, we identify the sub-components (bounding boxes that are inside) and determine whose components are eyes and which component is the mouth. To detect circles, we can use the knowledge, that the width is equal to the height (+- one pixel) and that the centroid of the circle is equal to the middle of the bounding box (+- one pixels). The remaining component is the mouth. The corresponding code is here:

            circs, mouth = [], None
            for ll in subcomps:
                xx, yy = stats[ll, cv2.CC_STAT_LEFT], stats[ll, cv2.CC_STAT_TOP]
                ww, hh = stats[ll, cv2.CC_STAT_WIDTH], stats[ll, cv2.CC_STAT_HEIGHT]
                ctr = centroids[ll]
                #filter noise
                if max(ww, hh) < 3: continue
                #is it a circle? 
                # 1) width should be equal to height
                # 2)  (circle centroid should be equal to the bounding box centre), max difference is +- 1 pixel in each direction, i.e. sqrt(1+1)
                if cv2.norm(ctr, (xx+ww/2.0,yy+hh/2.0)) < 1.5 and abs(ww-hh) <= 2:
                #print ww,hh, (xx+ww/2.0,yy+hh/2.0), ctr, cv2.norm(ctr, (xx+ww/2.0,yy+hh/2.0))
                mouth = (ll, (xx+ww/2,yy+hh/2), (xx,yy,xx+ww,yy+hh), ll)
      , (xx+ww/2,yy+hh/2), 5, (0,255,0),2)

      Finally, we have to detect whether the face is sad or happy. This is the most tricky part. We already know the position of the eyes, we already know the position and centroid of the component which corresponds with mouth. What we can do is to measure the distance between eye and centroid of mouth. This uniquely discriminates sad and happy faces because all faces are generated by the same algorithm on the server side :-)

            ml = cv2.norm(centroids[mouth[3]], circs[0][1]) / width # measure the length and normalize with smiley BBOX width
            clr = image[circs[0][1][1],circs[0][1][0]]
            #the magic of happiness: the distance between centroid of mouth and an eye should be around 40%
            if ml>0.37: 

      The complete source code is in and is sufficiently fast (tens of milliseconds). In order to run the code, it is necessary to install Opencv.

      > pip install opencv-python
      > pip install cv2
      > python      


Category: System

  1. The Transmittion

    Assignment: Agent, we have obtained a network traffic capture, which was recorded at enemy headquarter. At that time, one of the foreign agents (we suppose it was agent "Mc Pew") connect his device to the network. Analyse the traffic and try to find any usefull information. Good luck, Agent

    Attachment: dhcp.pcap.gz

    Solution: The attached file is a common PCAP file which can be easily viewed in Wireshark. We can see that the file contains only DHCP queries and responses. The first step is to try find the raw string "CT18". This gives us the positive match:


    Then we can identify that there are more DHCP DISCOVER queries from the same MAC address (08:00:27:6e:cc:99). We can create a filter and look at the content of Client MAC address which contains the flag.


  2. Dust Off

    Assignment: Agent, the foreign agents sometimes needs to evacuate (due to injury or disclosure) and they need to provide proper password at proper place to authorise the evacuation. Recently, we have acquired the instruction, how the agents should do it, but it does not make sense at the first sight:

    Wanna stay alive forever?

    Find the sense of the message. Good luck, Agent

    Solution: The message tries to tell us that the flag is related to the DNS servers. The question is, where it is located. In order to check the content of various DNS records, we can use the Python library dnslib. It allows, for example, to ask for all existing types of DNS records. Usually, the data are placed in TXT record. But this record does not exist for Even when we tried all the possible records (see the following code), we got nothing usable.

    import socket
    from dnslib.dns import DNSRecord,DNSQuestion,QTYPE
    for qtype, q in enumerate(QTYPE.reverse):
        q = DNSRecord(q=DNSQuestion('', qtype))
        while True:
               a = DNSRecord.parse(q.send('',53,tcp=False, timeout=1))
            except socket.timeout:
        print a

    We repeated the same analysis also for the second name, i.e. and identified that name server has changed to When we tried to resolve the NS record, name server again changed.

    ;  IN      NS
    ;; AUTHORITY SECTION: 0       IN      NS
    ;  IN      NS
    ;; AUTHORITY SECTION: 0       IN      NS

    We created a little script that tries successively resolve NS records. At some point, the DNS server responds with which is text "Find loc" encoded as a hexadecimal string. The code for retrieving of the flag from DNS servers can be executed as follows:

    $ pip install dnslib
    $ python

    It consists of the loop that tries to go through the NS records. Because the DNS server evenly hangs up and there is lot of records that have to be visited, we are using time out (0.5 second) and in case of a failure, we repeate the query again.

    #DNS traversal code
    nsl = ''
    while True:
        q = DNSRecord(q=DNSQuestion(nsl,getattr(QTYPE,'NS')))
            a = DNSRecord.parse(q.send(dnsserver_ip, 53, timeout=0.5, tcp=False))
        except socket.timeout:
        nsl = str(a.auth[0].rdata)
        if nsl in done:
  3. OS do not forget

    Assignment: Agent, thanks to our VEAL Team Six we have secured a device containing enemy intelligence. The device in question was a custom made laptop with military grade system protection. Shortly after securing this device, it self-destructed! Luckily, one of your fellow agents had performed a hardware-assisted memory dump shortly before the device went up in smoke. This means it may hold crucial enemy intelligence. It is paramount we get our hands on that information. Your task is to recover what you can from the memory dump. URL: Password: os_do_not_forget Best of luck, Agent.

    Solution: The URL points to a 512MB file. Looking at the structure, it is a memory dump from Virtual Box. This can be confirmed also by volatility.

    $ python -f memory imageinfo
    Volatility Foundation Volatility Framework 2.6
    Determining profile based on KDBG search...
    INFO    : volatility.debug     : Determining profile based on KDBG search...
              Suggested Profile(s) : No suggestion (Instantiated with no profile)
                         AS Layer1 : VirtualBoxCoreDumpElf64 (Unnamed AS)
                         AS Layer2 : FileAddressSpace (./memory)
                          PAE type : No PAE
    $ python -f memory vboxinfo
    Volatility Foundation Volatility Framework 2.6
    Magic: 0xc01ac0de
    Format: 0x10005
    VirtualBox 5.2.10 (revision 121806)
    CPUs: 4
    FileOffset Memory Offset Size
        0x8c5c           0x0 0x20000000
    0x20008c5c    0xe0000000  0x1000000
    0x21008c5c    0xf0400000   0x400000
    0x21408c5c    0xf0800000     0x4000
    0x2140cc5c    0xffff0000    0x10000

    Before doing anything more sophisticated (volatility allows to retrieve the bash history, but it requires to have an appropriate profile), we did some grepping with the simple goal. The file contains BOOT_IMAGE=/boot/vmlinuz-4.12.14-lp150.12.16-default string which means that there is a Linux. So, let us check the hostname and username (the goal is to find the content of a screen buffer).

    with open('memory','rb') as f:
     sp = ''
     while 1:
         s =
         if not s: break
         if 'HOSTNAME=' in (sp+s):
             print 'hostname', (sp+s).split('HOSTNAME=',1)[1][:50].split(' ',1)[0].split('\n',1)[0].split('\0',1)[0]
         if 'USER=' in (sp+s):
             print 'user', (sp+s).split('USER=',1)[1][:50].split(' ',1)[0].split('\n',1)[0].split('\0',1)[0]
         sp = s   

    The user is flab and the hostname is linux-lnh1. Let us continue and try to find the string flab@linux-lnh1:.*>. And the result is the follows:

    flab@linux-lnh1:~> less -R flag.txt
    flab@linux-lnh1:~> cat flag.txt

    There is a file that probably contains the flag. We retrieved the flag.txt by Looking at the text which follows the less command. The content looks a little bit weird but we know that strings like <ESC>[0;1;34;94m are the escape codes for a terminal used to switch the color, for example. However, obtaining the correct result migh be a little bit tricky. When catted the output of the file, it outputted some weird utf-8 codes.

    standard font

    So we have to find a suitable font (monospace) that is able to display the result correctly. The file contains the character U+259E. The list of fonts that support this character can be retrieved here: ( If DejaVu Sans Mono is used instead of a default font, the flag appears.


Category: Reversing

  1. SpringPeace

    Assignment: Agent, 'M' has received an email from Elbonia regarding peace conference scheduled for the next spring. Her antivirus didn't detect anything dangerous, but as she's a complete paranoiac, she requires you to investigate the attached document. Pw for the zipfile:infected This message will self destruct in about 4 weeks! Attachment:

    The attached file contains a single XLSX document task1.docm with macros. There is a routine which is executed exactly when the document is opened. The routine looks as follows:

    Sub AutoOpen()
    documentName = ActiveDocument.Name
    If (LCase(documentName) = "fdshkflsdjfsdfdkjlgrgfsd.docm") Then
    If aplib_allocate_memory = True Then
    documentPath = Application.ActiveDocument.Path
    If (LCase(documentPath) = "c:\temp") Then
    MsgBox "aplib compression demo implemented in VBA"
    End If
    End If
    End If

    This routine test name of the document, path where it is located and call another two routines: aplib_allocate_memory and begin. Function aplib_allocate_memory does nothing particularly interesting, it checks parameters of the machine and hard drive. The begin calls aplib_decompress several times. So let us modify the code as follows:

    Sub AutoOpen()
    End If
    Private Sub begin()
    MsgBox "aplib compression done\n" + b
    End Sub

    When we execute the modified code, it outputs a messagebox containing error message and some garbage:


    Don't give up because if we look more thoroughly at the result, we can find CT18-.... string.

Category: Others

  1. Numbers

    Assignment: Hello Agent, we investigate mysterious disappearance of famous hacker von Rhump-Zeiss. The only item we found in his office was his diary. On the last page we have found these numbers. Can you analyse them?

    Solution: This is a piece of cake for Python ...

    84 104 101 32 102 108 97 103 32 105 115 32 67 84 49 56 45 68 50 78 103 45 78 106 107 97 45 85 120 74 115 45 80 107 55 104

    print ''.join([chr(int(a)) for a in '84 104 101 32 102 108 97 103 32 105 115 32 67 84 49 56 45 68 50 78 103 45 78 106 107 97 45 85 120 74 115 45 80 107 55 104'.split()])

    The flag is CT18-D2Ng-Njka-UxJs-Pk7h

  2. Hash

    Assignment: Hello agent, we have penetrated the main server of extremists group and retrieved the hash of admin password. Are you able to recover the password? Maybe it would help us to read the encrypted file.

    The hash is: 84d961568a65073a3bcf0eb216b2a576

    Attachment: Encrypted file containing Flag.txt

    Solution: The string consists of hexadecimal symbols, the length is equal to characters 32 - it may be MD5 hash. Let us try, for example,

    And the result is ... 84d961568a65073a3bcf0eb216b2a576 MD5 : superman

    This is the password for ZIP file.

  3. The Pixels

    Assignment: Agent, we found a secret message in target's apartment. It looks like random pixels, but it reminds me something. Can you help us decode this? Best of luck, we relies on you Agent!

    Attachment: pixels.png (

    Solution: There is a matrix of 25 x 25 black/white squares - it looks like a common QR code with 25x25 modules which can contain up to 27 characters. But the QR is without marks (fixed patterns).

    Let us open our favorite image editor and try do create the corner marks.

    Then we can use, for example, which produces the following result

    The Pixels Solution

  4. Two Symbols

    Assignment: You chased an enemy agent through the city, he has unfortunately escaped but lost his expensive jacket. You have noticed the jacket sews are quite irregular. Does it mean something or was is it just a crappy tailor?

    -.-. - .---- ---.. -....- -. ..--- ...-- .-- -....- .-. -.- -.. ----- -....- --.. ... ..--- --- -....- -..- -.-. . .--.

    Solution: It looks like Morse code, let us try the first online decoder, we find. For example,, sounds good.

    Beep, beep, and the flag is here:


  5. Letters and Numbers

    Assignment: Agent, you've just received an unexpected text message from an unknown number. The message says: 7918dkcugZdrnTFw5CX2uvV1FjZB5x2E4 It can be either a secret code with the flag or tracking number of the new dishwasher that you just ordered. go and find the truth!

    Hint: First idea - 6 = the solution

    Solution: Actually, this problem rewarded by a single point was one of the hardests one. There was a hint for this problem but this can be interpreted in many different ways. We know the format of the flag is CT18-xxxx-xxxx-xxxx-xxxx, but we don't know whether the string contains only the flag itself (24 symbols) or something else. The most tricky fact is that the second and third symbol match the format.

    Possibilities: The provided string consists of numbers and letters which suggests BASE64 encoding. BASE64 seems to be nice here since it is able to reduce 32 letters to 24 symbols (BASE64 needs multiples of four that are reduced to three symbols) which match the length of flag. The problem is that the input string consists of 33 letters. The frequency analysis also suggest something like an encoded string. We know that there must be four dashes, but each symbols occurs at most twice.

    1    2    4    5    7    8    9    B    C    E    F    T    V    X    Z    c    d    g    j    k    n    r    u    v    w    x
    2 x  2 x  1 x  2 x  1 x  1 x  1 x  1 x  1 x  1 x  2 x  1 x  1 x  1 x  2 x  1 x  2 x  1 x  1 x  1 x  1 x  1 x  2 x  1 x  1 x  1 x 

    Maybe there is an extra symbol? We can try brute-force approach. It produces a binary string with similar distribution - the distribution is more uniform.

    Maybe there is a key + encoded string? IDEA is a cipher that uses BASE64 to encode encrypted binary string. Unfortunately, IDEA is a block cipher and the length of does not match.

    Maybe a stream cipher is applied? But nothing (XOR, -6, ...) produces a valid flag.

    One hour, two hours, eight hours, moving from 9th place to 15th place, still nothing useful.

    An extra hint is commin from the wild - it is not BASE64. The BASE64 is the first idea. So what it is? BASE64 - 6 = BASE4, that is nonsense. But it may be BASE58. Yes, this is something that is able to reduce 33 symbols to 24. Wow, damnit bitcoins, BASE58 exists. No programming needed at all.

    Let us take the first online decoder (e.g. and the solution is here and the 20th place is saved.

  6. Quartermaster

    Assignment: Agent, we have discovered a dead drop used by a foreign agency. there was nothing else but a paper with QR code and a piece of written text. Your task is to discover what secret information it contains. Good luck!

    Strange message found together with Q.R.


    Attachment: File Q.png containing

    Solution: The Q.R. code is in fact not a QR code but Datamatrix code which encodes string "Grille". Ok this is probably a hint which suggests that it must be a Grille cipher (

    We spend a lot of time on this problem even though it is rewarded by a single point only. The problem is that we wrongly assumed that there are four matrices consisting of 8x8 symbols each and there should be a shared or partially shared key which determines the positions of the right letters. We know that the flag starts with CT18- what we don't know is whether the message contains only flag or some additional text.

    When we arranged the string as 8x8 matrices, we identified that the first matrix contains the text "FUNWITHFLAGS". The same letters could be found also in the second matrix but on a completely different places. In addition to that, we can see that there are another two matches: "CT18" and "EACH"

    first two squares

    It is clear that there is not a single key. There must be more keys provided that it is a Grille cipher. We tried to find indexes using a brute force approach. We have flag which consists of 24 letters. We have four squares, hence we concluded that we need to find positions for six symbols from each square. First five symbols must be "CT18-". The sixth is unknown. The code is here. We found nothing eventhough we considered even rotations.

    We put this problem away a little and went out to have some fresh air. We abandoned the idea that there are four squares and tried to do the pattern matching manually which resulted in the following letters:

                      F U n w i t h   f l a g  C T18   ea  ch   by     t e N U  l l    vo id     
    fu  nw    i t  hfla g  C  T 18   e a ch           by   te    n  u ll       v oi dCT 18     EA   C  H b y    t e  NU l    l     v   o i d       

    No programming necessary at all, what a disappointment.

  7. Crosswords

    Assignment: Agent, you have a free day today so you decided to have a coffee and read the last issue of Spy news magazine. On the last page you found a weird crossword puzzle. Perhaps it reveals a flag for you. Attachment: crossword.png (

    Solution: It is quite easy problem. The crossword consists of 24 cells. The last column must contain dashes. The labels are probably extended regular expression. The first four symbols of the first row are clear - there must be 'C', 'T', '1', '8'. \1 in regular expression means the same content as that in the parentheses. The last symbol in the last row is also clear - it must be 'S'. The symbol on the left must also be 'S'. The first two symbols in the last but one column must be '8' and 'E'. This means that the symbol in the second row before 'E' must be 'K' and so on ....


Category: Crypto

  1. Almost OK

    Assignment: Hi agent, your informer just brought a flag for you, but it looks a bit weird... FW18-2RpT-edeZ-Io7g-wJ1c Can you fix it? Updated: bug has been fixed

    Solution: Here we were completely confused with the remark "bug has been fixed". It leaded us to conclusion that there was something like a hardware mistake when the agent entered the code (swapped keys on keyboard / mobile phone /etc). It took us a while to realize that this completely encrypted text and Caesar cipher was used. To receive the plain text, we used the tool available here: and shift -3.

  2. An EaSy Challenge

    Assignment: This morning we got an anonymous tip - somebody dropped a USB drive into our mailbox without any additional information. The drive contained a single file. Your fellow agents have analyzed the rest of the drive and haven't found anything suspicious. Is this a red herring or could this mysterious file lead us somewhere? Our Crypto team suggested we put You on the case. Agent, do your best!

    Attachment: groups.xml

    Initially, we known nothing about the mechanism used to store the passwords in this file. As we are a bit familiar with Windows GUIDs, we tried to google a little. The second result for query "3125E937-EB16-4b4c-9934-544FC6D24D26" leaded us to the page with exploit. The password is encrypted using AES, this is OK. But the problem is that the key is public.

    Then it was a matter of few seconds to decode the password using pycrypto library:

    import base64
    from Crypto.Cipher import AES
    from Crypto import Random
    def _unpad(s):
        return s[:-ord(s[len(s)-1:])]
    enc = base64.b64decode('f3QvZm8PqC0ku9q3RVfsYvNv6p8H/R4wadqsF0cYRKEfFxtV5fCLBraxqyWriwa+p28oRY0RUvFABsjcRDRwww==')
    cipher =, AES.MODE_CBC, "\x00"*16 )
    print _unpad(cipher.decrypt(enc))

Category: Stego

  1. The Smile of CESNET

    Assignment: Agent, we have intercepted an e-mail containing strange image (a line of black&white pixels) and a short text saying "The Smile of CESNET". Find the hidden message. Good luck, Agent

    Attachment: message.png

    Hint: The tale of CESNET logo is available on youtube.

    There was a hint pointing out to the story related to CESNET logo. This told us that 7-bit encoding is used. The same, as in good old times when the people were happy that they can work with DOS :-)

    Many possibilies exist, how to read the image and decode the messages. We decided to use OpenCV to read the image. The following code does the job:

    import cv2
    image = cv2.imread('message.png')
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    v, s  = 0, ''
    for i,ch in enumerate(gray[0,:]):
        v = (v << 1) | (~ch&1)
        if i%7==6 and i:
            s += chr(v)
            v = 0
    print 'text: ', s