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:
- Check that the flag is 100 characters long (hex-encoded);
- Hex-decode the input;
- 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:
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:
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:
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:
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 :-)
- ↑ There was a typo stating (the second time) that the flag starts with
hacklu{
, but everything pointed to theflag{
direction. - ↑ Spoiler: how convenient… (See next note)
- ↑ 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!