Rogdham

Hack.lu 2014 CTF: Guess the Flag write up

Overview

The CTF for the Hack.lu security conference in 2014 took place during 48h at the end of October. There were a variety of independent challenges. This write-up is for the challenge “Guess the Flag” by TheJH.

The challenge is to exploit a remote service, for which we have the C code. You can download it together with the exploit.

The remote service is quite simple: it asks for the flag as an hex-encrypted string, and tells if what we entered was the actual flag. We also know how the flag looks like1: flag{ followed by 44 characters in [0-9a-f], followed by }.

To make our life easier, we could just remove altogether the network handling part, by removing lines 78-84, and replace the content of main with the following: hande(0);. But this is quite unnecessary, really.

Compiling with gcc directly leads to some fatal error, but fortunately2 a comment at the beginning of the code gives us the command line to compile:

// compile with `gcc -o guess_the_flag guess_the_flag.c -std=gnu99 -g`

Looking closer

The code is quite simple to understand, the interesting part is how the input string is compared to the actual flag.

Here is how it goes:

  1. Check that the flag is 100 characters long (hex-encoded);
  2. Hex-decode the input;
  3. Compare in constant-time the hex-decoded input with the actual flag.

The second step looks hand-made. First, a table of all 256 possible values for a character is filled with -1, except for the characters [0-9a-fA-F] where the value is the corresponding value (e.g. 11 for b).

This table is then used as follows:

int is_flag_correct(char *flag_hex /* the user's guess in hex */) { /* check length */ char bin_by_hex[256] = {/* insert table definition here */}; /* the correct flag was censored out */ char flag[50] = "flag{0123456789abcdef0123456789abcdef0123456789ab}"; // decode flag_hex into given_flag so we can compare them char given_flag[50]; bzero(given_flag, 50); for (int i=0; i<50; i++) { char value1 = bin_by_hex[flag_hex[i*2 ]]; char value2 = bin_by_hex[flag_hex[i*2+1]]; if (value1 == -1 || value2 == -1) { /* print "bad input blah blah" */ exit(0); } given_flag[i] = (value1<<4) | value2; } char diff = /* insert timing-safe comparison of the two flags here */; return (diff == 0); }

Can you spot the vulnerability right away? Even if you can't, gcc can, if you used the -Wall flag3:

gcc output with -Wall flag

Indeed, flag_hex is a signed char array, so the values can be negative, and they are used as indexes in the bin_by_hex array.

Passing the flag validation

So the idea is to use negative values in the input flag, so that the value returned by the bin_by_hex lookup match the ones from the flag content.

Let's look at where the variables are stored in memory by running gdb on the provided binary:

Extracting variable addresses with gdb

Great, the difference between those two variables it 64.

So we want each byte of the given_flag variable to match each byte from the flag variable. Here is the related C code for one byte:

char value1 = bin_by_hex[flag_hex[i*2 ]]; char value2 = bin_by_hex[flag_hex[i*2+1]]; given_flag[i] = (value1<<4) | value2;

To make our life easier, we will set value1 to 0, and we only have to set value2 to the matching byte value of the flag variable.

For the first byte (i == 0), value2 is obtained from the second byte (i*2+1) of flag_hex. So we will use the previously computed difference as the second character. For the next byte (i == 1), we will have to remove 1 to that difference, and so on.

The following python script gives the string to use (pseudoflag.py in the archive):

from struct import pack print ''.join( '0' + pack('<b', -64 + i) for i in xrange(50) )

Which is 0\xc00\xc10\xc2...0\xf00\xf1.

Let's try that right away:

Passing the flag test

Yaaaay! We are passing the flag validation!!! Now, what?

Guessing the flag

Time to do as the challenge name suggests: let's guess the flag, one byte at a time.

Indeed, now that we have a passing flag, we could bruteforce one byte of the actual flag at a time, by trying all the possible values for that byte.

That's not too much of a bruteforce: we already know 6 of the bytes, and each other byte has only 16 possible values (in [0-9a-f]). That's ~700 tries at most, which is low enough.

At that point, nothing can go wrong, you merely have to write a few lines of Python code before watching the bytes of the flag being printed one by one on your terminal:

Getting the real flag, one byte at a time

Now, you quickly submit the flag before hex-decoding it once more, and indeed, itsjustlikeinthemovies! Almost…

Conclusion

Reminder: the exploit code is available for download.

That was a fun challenge to solve! Many thanks to TheJH for writing it, and the A Finite Number Of Monkeys team for letting me join them once more :-)

  1. There was a typo stating (the second time) that the flag starts with hacklu{, but everything pointed to the flag{ direction.
  2. Spoiler: how convenient… (See next note)
  3. Of course, you did not used it in the first place because lazy you had copy-pasted the compilation line from the comment in the source code… Well played, TheJH!

This article is released under the CC BY-SA licence.

Short URL: https://r.rogdham.net/24.