Last week, Secadmin conference was held with some easy CTF challenges about crypto, web and reversing. This write-ups is for “El Ninja Contrareloj” challenge, a reversing one.

The challenge has a long statement, in summary, they let you a binary and you need to reverse it to obtain the flag. Go go go go!

First is first, execute the binary and see what it does. When executing, it ask you for the flag, then wait some time and then, if wrong (obviosly, the first try will be wrong), the program tells you need some “Cruzcampo” (possibly the worst Spanish beer ever).

execute

A file to the binary outputs that binary is not stripped, so functions and some variable names will remain, cool for our purpose.

$ file secadmin_ctf

secadmin_ctf: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, 
BuildID[sha1]=460f36a3556ccc50b9befcd63b15de8fccb1b34f, not stripped

Binary analysis, using Cutter!

This time I’ll not use IDA to analyse the binary, let’s use Cutter, a QT based multiplatform GUI for radare2, really cool project started by the great Hugo Teso as Iato and currently mantained (as Cutter) by a lot of great radare2 community people.

Opening the binary using Cutter, shows us at first some useful information about the binary compilation flags.

dashboard

Not really so much information as the file command, but adds some information useful from an exploting perspective, such as Canary stack protection, NX bit, PIE and RELRO flags.

Well, the next step is review the binary functions, as this binary is not stripped, function names remain and it’s easier for us to identify each one.

functions

Starting from main function, let’s see what it does. As I previosly executed the binary to see the execution flow, it’s expected to see some header print, then print “Introduce tu flag…”, and gets the user STDIN input, and then process that input. This last “process” is the interesting part.

Here’s the dissembly of the interesting part:

main

Ok, as you can see, obj.flag is the variable where the user input will be stored using scanf (yep, there’s a stack buffer overflow here). Then, “Espere unos instantes” message is displayed and obj.flag is stored on an auxiliar variable obj.msg.

Here comes the interesting part, three functions are called on different threads using the pthread_create C function. This function calls another function and its parameters and execute it on a new thread. Debug threads using raw GDB can be a headache.

At the end, it compares the obj.s global string with obj.j global string, so it’s expected that our initial string is going to be transformed and stored in obj.j and then compared with the transformed flag, stored in obj.s.

Taking a look to obj.s global variable, we can see it’s initialized with 22 bytes. To do this, just double click over any obj.s reference, and then, select the “Hexdump” tab.

s

Seems like we need to input something that end up beeing like obj.s bytes, so first hint, our input needs to be 22 bytes long, and probably starting with the flag format secadmin{.

Let’s first analyze the sym.timer, sym.tr1 and sym.tr2 functions, and see what they do.

“timer” function

Let’s analyze first, the sym.timer function, which is the first function called.

timer

This function basically loops indefinitely, increasing the value of obj.tiempo variable by one each time, but before this, it calls usleep C function, passing it 0xf4240 as parameter. If you don’t know, usleep function will sleep the thread N microseconds. 0xf4240 is the hex representation of the decimal 1000000, which in seconds is 1 second.

So this function, increase the value of the global variable obj.tiempo by one each one second.

void timer(){
  while(1) {
    usleep(1000000); // 1 second
    ++tiempo;
  }
}

“tr1” function

Then, let’s analyze first, the sym.tr1 function, which is the second function called.

tr1

Easy flow here, first, the length of obj.msg (which is our input) is calculated, and we iterate from i=0 to i<strlen(obj.msg). Inside the for loop, the application xors each byte of the user input string, with it’s user input length and stores the result on another global variable obj.j. Then sleeps 0x30d40 microseconds, wich in decimal seconds corresponds with 0.2 seconds.

The C code will look like the following:

void tr1(){
  int i = 0;
  for(i; i<strlen(msg); ++i){
    j[i] = msg[i] ^ strlen(msg);
    usleep(200000); // 0.2 segundos
  }
  return 0;
}

“tr2” function

Last function called is tr2, let’s see what this function does.

tr2

Uuuh, really similar to the tr1 function right? This function also calculates the strlen of the user input and iterate from 0 to user input string length too. But this time the xor formula changes and it sleeps 0.4 seconds insted of 0.2.

This time, this function does not take the user input as a xor parameter, it uses obj.j instead, xored with a obj.t array and 0x80, the xor formula pseudocode is as follows:

j[i] = j[i] ^ t[2 * tiempo] ^ 0x80

If you remember, j is a global variable where tr1 will write the result of its xor function, then tiempo is another global variable wich will increase by one each second. As tr1, tr2 and timer functions are in different threads, this values will increase asynchronously.

Solving the equations.

Been this a short program, we can reconstruct it’s C code, to avoid dealing with GDB debugging over threads and time dependant variables, it’s easier for me to do it this way.

Here’s the C code:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

int tiempo = 0;
const char s[] = {0x91,0x87,0xEE,0xEC,0x2C,0x25,0x21,0x0e,0x1b,0xa5,0xc0,0x9c,0x2e,0x42,0xfd,0xbf,0x93,0x96,0xc1,0xce,0xc6,0xdf};
const char t[] = {0x74, 0x5A, 0x1B, 0x67, 0xde, 0x34, 0xf6, 0x34, 0x67, 0x5A, 0x8B, 0x74, 0x5A, 0x1B, 0x67, 0xDE, 0x34, 0xF6, 0x34, 0x67, 0x5A, 0x8B, 0x31, 0x3B, 0x36, 0x30, 0x2C, 0x03, 0x3F, 0x66, 0x24, 0x08, 0x66, 0x24, 0x08, 0x39, 0x67, 0x23, 0x08, 0x24, 0x36, 0x39, 0x64, 0x2A, 0x54};
char *msg = "secadmin{AAAAAAAAAAAA}";
char j[64];

void timer(){
  while(1) {
    usleep(1000000); // 1 segundo
    ++tiempo;
  }
}

void* tr1(){
  int i = 0;
  for(i; i<strlen(msg); ++i){
    printf("TR1 -> 0x%02x ^ 22\n",msg[i]);
    j[i] = msg[i] ^ strlen(msg);
    printf("TR1 -> j[%d] = 0x%02x\n",i,j[i]);
    usleep(200000); // 0.2 segundos
  }
  return 0LL;
}

void* tr2(){
  int i = 0;
  for(i; i<strlen(msg); ++i){
    usleep(400000); // 0.4 segundos
    printf("TR2 -> 0x%02x = 0x%02x ^ 0x%02x ^ 0x80",j[i],j[i],t[2 * tiempo]);
    j[i] = j[i] ^ t[2 * tiempo] ^ 0x80;
    printf(" -> 0x%2x\n", j[i]);
  }
  return 0LL;
}

int main(int argc, char const *argv[])
{
  pthread_t tr1_t;
  pthread_t tr2_t;
  pthread_t timer_t;
  puts("Starting! ...");
  printf("Size of s: %d\n", (int)sizeof(s));
  fflush(stdin);

  pthread_create(&timer_t,0LL,(void *(*)(void *))timer,0LL);
  pthread_create(&tr1_t,0LL,tr1,0LL);
  pthread_create(&tr1_t,0LL,tr2,0LL);

  usleep(13000000);
  printf("s: %s",j);

  printf("sizeof j: %d\n",(int) sizeof(j));

  if (!strcmp(j,s)){
    printf("Wiiiiiiiin!!");
  } else {
    printf("Looooooose!!");
  }

  return 0;
}

When executed, we obtain the following output, which clears us what the program is doing:

Starting! ...
Size of s: 22
TR1 -> 0x73 ^ 22
TR1 -> j[0] = 0x65
TR1 -> 0x65 ^ 22
TR1 -> j[1] = 0x73

TR2 -> 0x65 = 0x65 ^ 0x74 ^ 0x80 -> 0xffffff91

TR1 -> 0x63 ^ 22
TR1 -> j[2] = 0x75
TR1 -> 0x61 ^ 22
TR1 -> j[3] = 0x77

TR2 -> 0x73 = 0x73 ^ 0x74 ^ 0x80 -> 0xffffff87

TR1 -> 0x64 ^ 22
TR1 -> j[4] = 0x72
TR1 -> 0x6d ^ 22
TR1 -> j[5] = 0x7b

TR2 -> 0x75 = 0x75 ^ 0x1b ^ 0x80 -> 0xffffffee

TR1 -> 0x69 ^ 22
TR1 -> j[6] = 0x7f
TR1 -> 0x6e ^ 22
TR1 -> j[7] = 0x78

TR2 -> 0x77 = 0x77 ^ 0x1b ^ 0x80 -> 0xffffffec

TR1 -> 0x7b ^ 22
TR1 -> j[8] = 0x6d
TR1 -> 0x41 ^ 22
TR1 -> j[9] = 0x57

TR2 -> 0x72 = 0x72 ^ 0xffffffde ^ 0x80 -> 0x2c

TR1 -> 0x41 ^ 22
TR1 -> j[10] = 0x57
TR1 -> 0x41 ^ 22
TR1 -> j[11] = 0x57

TR2 -> 0x7b = 0x7b ^ 0xffffffde ^ 0x80 -> 0x25

TR1 -> 0x41 ^ 22
TR1 -> j[12] = 0x57
TR1 -> 0x41 ^ 22
TR1 -> j[13] = 0x57

TR2 -> 0x7f = 0x7f ^ 0xffffffde ^ 0x80 -> 0x21

TR1 -> 0x41 ^ 22
TR1 -> j[14] = 0x57
TR1 -> 0x41 ^ 22
TR1 -> j[15] = 0x57

TR2 -> 0x78 = 0x78 ^ 0xfffffff6 ^ 0x80 -> 0x e

TR1 -> 0x41 ^ 22
TR1 -> j[16] = 0x57
TR1 -> 0x41 ^ 22
TR1 -> j[17] = 0x57

TR2 -> 0x6d = 0x6d ^ 0xfffffff6 ^ 0x80 -> 0x1b

TR1 -> 0x41 ^ 22
TR1 -> j[18] = 0x57
TR1 -> 0x41 ^ 22
TR1 -> j[19] = 0x57

TR2 -> 0x57 = 0x57 ^ 0x67 ^ 0x80 -> 0xffffffb0

TR1 -> 0x41 ^ 22
TR1 -> j[20] = 0x57
TR1 -> 0x7d ^ 22
TR1 -> j[21] = 0x6b

TR2 -> 0x57 = 0x57 ^ 0x67 ^ 0x80 -> 0xffffffb0
...

It’s easy to see the pattern, right? tr1 creates two new bytes on obj.j global variable, and then tr2 takes one byte, transform it using t[2*tiempo] value.

As you can see, our input (the msg variable), when transformed by tr2 needs to be equal to s variable byte by byte. We guessed that msg should start with secadmin{ as this is the flag format of the challenges. If you take a look, first s variable bytes are: 0x91,0x87,0xEE,0xEC,0x2C,0x25,0x21,0x0e,0x1b, and our tr2 function output is doing it correctly! This confirms that our guess was correct, and also means that the last character of msg might be }.

Well, we just need to find which ascii values satisfies the formula for this string secadmin{AAAAAAAAAAAA}, been each A, a value to guess.

As you can see, t[2*tiempo] follows always the same pattern, so with a simple ruby script, we can guess this characters:

t = [0x67,0x67,0x67,0x8b,0x8b,0x5a,0x5a,0x5a,0x67,0x67,0x34,0x34,0x34]
s = [0xa5,0xc0,0x9c,0x2e,0x42,0xfd,0xbf,0x93,0x96,0xc1,0xce,0xc6]

for i in 0..t.length-1
  for x in 0x21..0x7D do
    for y in 0x00..0xFF do
      if ((x ^ 22) ^ t[i] ^ 0x80) == s[i] then
        puts "x: #{x.chr}"
        i+=1
      end
    end
  end
end

This outputs: T1m3_1s_g0ld, so the final flag is secadmin{T1m3_1s_g0ld}

Final thoughts.

There were, of course, many ways to solve this challenge, this is a simple xor function solving challenge. The threads where an issue when trying to dinamically debug this challenge, because threads will die disallowing us to comfortably debug the binary workflow. That’s why I decided to rewrite the source code based on the assembly, and simply execute it and see the values that t[2*tiempo] takes along the time, a really lazy solution.

Hope you like it, contact me if you see any mistake. Thanks!