Practical case: Secura Grand Slam CTF “Easy Reverse”

This article was published on the 21st of June 2018, and was updated on the 28th of October 2021.

Analysing files requires a lot from an analyst, from understanding the used tooling all the way to understanding concepts in programming. This article will analyse a capture the flag challenge step-by-step, with an in-depth explanation along the way. However, many of the encountered concepts warrant their own article due to the complexity. In the coming chapters, all of these concepts are explained in the detail that they deserve.

This article will go over many of these concepts in a very quick pace, so do not be alarmed if it is hard(er) to follow along. The goal of this article is to be a showcase of sorts, providing insight as to what one can do after the first two chapters of the course.

Table of contents

The challenge

On the 14th of June 2018, I participated in the Grand Slam CTF by Secura in Nieuwegein, The Netherlands. This write-up covers theEasy Reverse challenge, which I solved together with a teammate who goes by the nickname of “Exploiteer”. The write-up is written solemnly by me.

The file can be downloaded here.

A first look

To easily execute the file in a safe environment, it is important to know the required operating system and architecture. This can be done using file, which is present in most Linux distributions. The output of it is given below.

ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/, for GNU/Linux 3.0.0, BuildID[sha1]=dc20ee648c7b1b86f97c62675f8fd92361ed9d0f, stripped

The most important information to take away from the output are ELF, 32-bit, Intel 80386, and stripped. This means that the binary is of the ELF type for a 32-bit Intel architecture, and it is stripped from any debugging symbols.

Executing the binary on a suitable operating system, such as Ubuntu, shows the first hint, as can be seen below.

Please supply your corp. secret

The analysis

To disassemble the file, one can use many tools. In this example, Radare2 is used. Note that the output of Radare2 in this blog is based on the version that was the latest at the time of writing in 2018. For those following along: your output can differ, which is perfectly fine. To open the file using Radare2, run the command that is given below in the terminal, assuming the terminal’s working directory is in the same location as the challenge, which is named easy.

r2 ./easy

The default address in the binary is located at the entry0 function, which is the entry point of the binary. We then use Radare2 to analyse the binary with the command aaaa, which includes experimental analysis methods. The output is given below.

[0x08048460]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)

Once the analysis is complete, it is useful to get an overview of the functions that are present in the binary. This can be done using afl, which stands for Analysis Function List. The output is given below.

[0x08048460]> afl
0x080483ac    3 35           fcn.080483ac
0x080483e0    1 6            sym.imp.printf
0x080483f0    1 6            sym.imp.fflush
0x08048400    1 6            sym.imp.sleep
0x08048410    1 6            sym.imp.puts
0x08048420    1 6            sym.imp.strlen
0x08048430    1 6            sym.imp.__libc_start_main
0x08048440    1 6            sym.imp.fprintf
0x08048450    1 6            sub.__gmon_start_450
0x08048460    1 33           entry0
0x08048490    1 4            fcn.08048490
0x080484a0    4 42           fcn.080484a0
0x08048510    3 30           entry2.fini
0x08048530    8 45   -> 99   entry1.init
0x08048563   16 447  -> 446  main

There are a couple of imported functions, which are denoted with sym.imp. Imported functions are part of libraries which are used by this program. The functions that are denoted with fcn are functions that contain the binary’s code. The numbers behind fcn indicate the address at which the function starts.

The function main is the default user defined start function, which is where this adventure starts. One can issue commands for a given address in Radare2. Omitting the address will ensure that the command is processed as if the current address is used. To change the current address, one uses s, which stands for seek. To change the current address to that of the main function, one uses s main. To print the disassembly of the function at the current address, one can use pdf, which stands for Print Disassembly Function.

When looking at the main function, several things stand out.Firstly, the provided argument count should equal two. By default, one argument is always provided: the binary itself. Additional parameters are provided by the user as command line arguments. The local variables, as defined by Radare2, are shown in the function signature, as can be seen below.

main (int arg_8h, int arg_ch);
0x0804856d      837d0802       cmp dword [arg_8h], 2
0x08048571      0f8595010000   jne 0x804870c

The first value that is passed to the main function is the argument count (defined as arg_8h), which is later compared to the value two.

The next six lines are another piece of the puzzle, as can be seen below.

0x08048577      8b450c         mov eax, dword [arg_ch]     ; [0xc:4]=-1 ; 12
0x0804857a      83c004         add eax, 4
0x0804857d      8b00           mov eax, dword [eax]
0x0804857f      890424         mov dword [esp], eax        ; const char *s
0x08048582      e899feffff     call sym.imp.strlen         ; size_t strlen(const char *s)
0x08048587      83f804         cmp eax, 4

Remember how the argument count was the first parameter, arg_8h, that was passed to the main function. The second passed argument, arg_ch, is the argument value, as an array. The start location of the array is known, which is equal to the address of arg_ch. This is written as [arg_ch]. This address is stored in the EAX (accumulating) register, to which then 4 bytes are added and later loaded. The four bytes are added as the second variable from the array is required, since that is the first command-line argument’s location. The reason why it is 4 bytes, is due to the 32-bit architecture: 32-bits equal 4 bytes.

The function “sym.imp.strlen” is the standard C function strlen in the string.h header. The value is provided in the general purpose stack pointer register ESP. The return value is stored, as is default, in the general purpose accumulator register EAX. The length of the string is compared to 4, meaning that the provided parameter has to 4 characters in length.

To test this, one can execute the executable with one parameter of 4 characters. This yields a different result, as can be seen below.

easy abcd

Your UID is: 1684234849


The command-line argument expects the corp secret, as the initial error message already referred to. This begs the question: how is the UID generated? Looking in the assembly of the binary, the question can be solved relatively fast.

; var int local_4h @ esp+0x4
0x080485a0      89442404       mov dword [local_4h], eax
0x080485a4      c70424c08704.  mov dword [esp], str.Your_UID_is:__d ; [0x80487c0:4]=0x72756f59 ; "Your UID is: %d\n" ; const char *format
0x080485ab      e830feffff     call sym.imp.printf         ; int printf(const char *format)

The UID is printed using printf, with two arguments. The arguments are passed in the inverse order due to the calling convention. The first argument is equal to Your UID is: %d\n. The %d is replaced with the second argument, as a signed decimal number.

The system on which the binary is executed is Ubuntu 16.04, whose endianness equals little endian. The UID of “abcd” equals 1684234849 in decimal notation, or 0x64636261 in hexadecimal notation. Everything that was printed based on the given input is covered, but there is more to it than meets the eye

A bit further down in the main function, the string Access is printed, regardless of the value of the user defined parameter. Additionally, there is a for-loop which prints the three dots. If the input value is incorrect, the string denied!! is printed with a one second sleep between each character. This is most likely to prevent any brute force attempts.

If the answer is correct, the string granted! is printed after the three dots. Directly after that, a blank line and the string “Win!” are printed.

The variable arg_ch  is the array that contains all command-line arguments, with the first one (on index 0) being the binary itself. By adding 4 bytes (as it is a 32-bit binary), the next item in the array is selected, resulting in the parameter that the user provided.

The memory address 0x804a038 equals the parameter that was given as input by the user, as can be seen below.

0x0804858c      8b450c         mov eax, dword [arg_ch]     ; [0xc:4]=-1 ; 12
0x0804858f      83c004         add eax, 4
0x08048596      a338a00408     mov dword [0x804a038], eax  ; [0x804a038:4]=0

Between the printing of the output, the given user input is changed a fair bit, resulting in only a single user input being accepted as the correct answer. Firstly, the ror rotate right instruction is used to rotate the user input string 0x10 places to the right:

0x080485b0      a138a00408     mov eax, dword [0x804a038]  ; [0x804a038:4]=0
0x080485bd      c1c810         ror eax, 0x10

Then, the result of the rotation is subtracted with 0x12d70fde:

0x080485ca      a138a00408     mov eax, dword [0x804a038]  ; [0x804a038:4]=0
0x080485cf      2dde0fd712     sub eax, 0x12d70fde

To enter the part where the win condition is triggered, the general purpose register EAX is compared to the value 0x216e6957.

0x080485e5      a138a00408     mov eax, dword [0x804a038]  ; [0x804a038:4]=0
0x080485ea      3d57696e21     cmp eax, 0x216e6957

To get the correct value, one has to reverse the order of the steps, meaning that the eventual value is used as the starting value. The value that is deducted from the user-input, is added to the starting value, after the total should be rotated 10 places to the left. This provides is the flag that solves the challenge, when written in human readable characters.

Writing the password generator

Below is a proof-of-concept, written in C, with the above-described steps. The code is commented to explain every step of the process.

 * File:   main.c
 * Author: Max 'Libra' Kersten
 * Created on June 14, 2018, 11:43 AM
#include <stdio.h>
 * Taken from Wikipedia (
 * @param value the value to be changed
 * @param shift the amount of times to shift
 * @return the shifted value
unsigned int rotate_left(const unsigned int value, int shift) {
    if ((shift &= sizeof (value)*8 - 1) == 0)
        return value;
    return (value << shift) | (value >> (sizeof (value)*8 - shift));
 * PoC for the "Easy Reverse" challenge in the Secura Grand Slam CTF. Binary SHA-256 hash equals "935ed42837f4b940beaafd8d4f887acd5154416c32ab38d6343bd2d9e246a570"
int main(int argc, char** argv) {
    //Original value to which the user input is compared
    int finalCompareValue = 0x216e6957;
    //The subtraction that is done before the finalCompareValue is compared with the "cmp" operand
    int subtraction = 0x12d70fde;
    //The total value before the ror (rotation_right) is done
    int totalPreRotation = finalCompareValue + subtraction;
    //The value of the int before the ror (rotation_right) is done. This is displayed as the UID of the user
    int rotatedValue = rotate_left(totalPreRotation, 0x10); //rotate_right was used in the binary, to revert this, use rotate_left
    //The decimal value, known as the UID of the flag was given to us:
    printf("Decimal value (shown as UID):     %d\n", rotatedValue);
    //The hexadecimal value of the flag
    printf("Hexadecimal value of the flag:    %x\n", rotatedValue);
    //Variable to store the flag before printing
    char flag[4];
    //Convert the first character of the decimal integer (little endian, so only the last two digits are used). The value is stored at the address of the first character of the character array "flag"
    sprintf(&flag[0], "%c", rotatedValue & 0xFF);
    //Convert the second character, ignoring the first character (so 8 bits)
    sprintf(&flag[1], "%c", (rotatedValue >> 8) & 0xFF);
    //Convert the third character, ignoring the first two characters (so 16 bits)
    sprintf(&flag[2], "%c", (rotatedValue >> 16) & 0xFF);
    //Convert the fourth and final character, ignoring the first three characters (so 24 bits)
    sprintf(&flag[3], "%c", (rotatedValue >> 24) & 0xFF);
    //Print the ASCIi value of the flag
    printf("The flag in ASCII:                %s\n", flag);

The output of this program leaves us with the correct answer:

Decimal value (shown as UID): 2033529925

Hexadecimal value of the flag: 79353445

The flag in ASCII: E45y

To test this, we can enter “E45y” as the parameter when we start the binary:

easy E45y

Your UID is: 2033529925



To contact me, you can e-mail me at [info][at][maxkersten][dot][nl], send me a PM on Reddit, or DM me on Twitter @Libranalysis.