POSTS
MRMCDCTF2019: Cereal
Solution to Cereal (easy) from MRMCDCTF 2019
Cereal is another reversing challenge I have written for this years MRMCD CTF. But unlike Sitting duck, this reversing challenge actually involves reversing!
At first look the binary
is similar to Sitting duck:
It’s a 64 bit ELF executable, and it asks us for the password/flag when started.
But simply trying strings
will bring up nothing useful, so we have to actually analyse what the program does internally to find the flag.
There are many different ways to analyse a binary executable, but one of the easiest to learn is to use a decompiler. A decompiler tries to convert the compiled machine code back to easily readable source code. The resulting code is not exactly the source code used to create the binary, but it’s reasonably similar. And while decompiled code is most often everything but pretty, it’s still more comfortable to read disassembled code.
For this text I will use Ghidra. It’s free and produces good decompiled code. If you’re not afraid of running software provided by the NSA, you should give it a try.
A complete introduction into Ghidra would be way out of scope for this post. I will only give high level instructions on its usage, but there are many good tutorials out there (like this one).
So lets begin: Launch Ghidra, make a new project if necessary, and import the cereal binary. It will give you some information on the file and add it to the project. Then drag the file to the CodeBrowser (the dragon from the tool chest) and confirm that you want to analyse it now.
Select ‘Functions’ from the Symbol Tree subwindow (left side, upper half), and then select ‘main’. You should see something similar to this:
In the central window you see the disassembly of main, and in the right one the decompiled (pseudo-)code. The complete code of main is here:
undefined8 main(void)
{
bool bVar1;
undefined8 uVar2;
long in_FS_OFFSET;
uint local_13c;
int local_138 [4];
undefined4 local_128;
undefined4 local_124;
undefined4 local_120;
undefined4 local_11c;
undefined4 local_118;
undefined4 local_114;
undefined4 local_110;
undefined4 local_10c;
undefined4 local_108;
undefined4 local_104;
undefined4 local_100;
undefined4 local_fc;
undefined4 local_f8;
undefined4 local_f4;
undefined4 local_f0;
undefined4 local_ec;
undefined4 local_e8;
undefined4 local_e4;
undefined4 local_e0;
undefined4 local_dc;
undefined4 local_d8;
undefined4 local_d4;
undefined4 local_d0;
undefined4 local_cc;
undefined4 local_c8;
undefined4 local_c4;
undefined4 local_c0;
undefined4 local_bc;
undefined4 local_b8;
undefined4 local_b4;
undefined4 local_b0;
undefined4 local_ac;
undefined4 local_a8;
undefined4 local_a4;
undefined4 local_a0;
char local_98 [136];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
bVar1 = false;
local_138[0] = 0x4d;
local_138[1] = 0x52;
local_138[2] = 0x4d;
local_138[3] = 0x43;
local_128 = 0x44;
local_124 = 0x43;
local_120 = 0x54;
local_11c = 0x46;
local_118 = 0x7b;
local_114 = 0x73;
local_110 = 0x6f;
local_10c = 0x6d;
local_108 = 0x65;
local_104 = 0x74;
local_100 = 0x68;
local_fc = 0x69;
local_f8 = 0x6e;
local_f4 = 0x67;
local_f0 = 0x5f;
local_ec = 0x65;
local_e8 = 0x61;
local_e4 = 0x73;
local_e0 = 0x79;
local_dc = 0x5f;
local_d8 = 0x66;
local_d4 = 0x6f;
local_d0 = 0x72;
local_cc = 0x5f;
local_c8 = 0x62;
local_c4 = 0x72;
local_c0 = 0x65;
local_bc = 0x61;
local_b8 = 0x6b;
local_b4 = 0x66;
local_b0 = 0x61;
local_ac = 0x73;
local_a8 = 0x74;
local_a4 = 0x7d;
local_a0 = 0;
local_13c = 0;
puts("The password is the flag. What is the password?");
fgets(local_98,0x80,stdin);
strtok(local_98,"\n");
while (local_13c < 0x27) {
if (local_138[(long)(int)local_13c] != (int)local_98[(long)(int)local_13c]) {
bVar1 = true;
}
local_13c = local_13c + 1;
}
if (bVar1) {
puts("Wrong. Try again.");
uVar2 = 0xffffffff;
}
else {
puts(
"\n##############################################\n### Congratulations! This is the flag! ###\n##############################################\n"
);
uVar2 = 0;
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar2;
}
It’s far away from beautiful code, but still quite readable: At first one notices the large number of local variables that are declared and initialised. This is actually a common failure mode of decompilers: It’s not trivially possible to distinguish a bunch of local variables and a local array from the machine instructions alone. So, when in doubt, the decompiler defaults to “It’s all local variables, and a lot of them”.
In the actual code we see the “What is the password?”-message and the password input using fgets.
puts("The password is the flag. What is the password?");
fgets(local_98,0x80,stdin);
This tells us that the char array ‘local_98’ is our input buffer. You can rename variable in Ghidra by placing the cursor on them and pressing ‘l’ (lowercase ‘L’) or by rightclick->‘Rename varibale’. In my opinion this is one of the most useful features of a good decompiler and can help you a lot to make sense of complex decompiled code. I renamed ‘local_98’ to ‘input_buffer’ in the rest of the snippets.
After that, things get interesting:
while (local_13c < 0x27) {
if (local_138[(long)(int)local_13c] != (int)input_buffer[(long)(int)local_13c]) {
bVar2 = true;
}
local_13c = local_13c + 1;
}
if (bVar2) {
puts("Wrong. Try again.");
uVar3 = 0xffffffff;
}
else {
puts(
"\n##############################################\n### Congratulations! This is the flag! ###\n##############################################\n"
);
uVar3 = 0;
}
We can easily see that ‘local_13c’ is a loop counter. In that loop, every byte of the input is compared to an other value from the array at ‘local_138’. If they are different, ‘bVar2’ is set to ‘true’. And later the ‘Congratulations!’ is displayed if and only if ‘bVar2’ is 0.
We can tell from this that ‘local_138’ contains the correct flag, and that is at least 0x27 (39) entries long (the loop runs 0x27 times). Using ‘CTRL+l’ (or rightclick->‘Retype variale’) we can change its type from “int [4]” to “int [39]“.
We can also rename these variables to make the function better readable:
undefined8 main(void)
{
long lVar1;
undefined8 uVar2;
long in_FS_OFFSET;
uint loop_counter;
int flag [39];
char input_buffer [136];
bool difference_detected;
lVar1 = *(long *)(in_FS_OFFSET + 0x28);
difference_detected = false;
flag[0] = 0x4d;
flag[1] = 0x52;
flag[2] = 0x4d;
flag[3] = 0x43;
flag[4] = 0x44;
flag[5] = 0x43;
flag[6] = 0x54;
flag[7] = 0x46;
flag[8] = 0x7b;
flag[9] = 0x73;
flag[10] = 0x6f;
flag[11] = 0x6d;
flag[12] = 0x65;
flag[13] = 0x74;
flag[14] = 0x68;
flag[15] = 0x69;
flag[16] = 0x6e;
flag[17] = 0x67;
flag[18] = 0x5f;
flag[19] = 0x65;
flag[20] = 0x61;
flag[21] = 0x73;
flag[22] = 0x79;
flag[23] = 0x5f;
flag[24] = 0x66;
flag[25] = 0x6f;
flag[26] = 0x72;
flag[27] = 0x5f;
flag[28] = 0x62;
flag[29] = 0x72;
flag[30] = 0x65;
flag[31] = 0x61;
flag[32] = 0x6b;
flag[33] = 0x66;
flag[34] = 0x61;
flag[35] = 0x73;
flag[36] = 0x74;
flag[37] = 0x7d;
flag[38] = 0;
loop_counter = 0;
puts("The password is the flag. What is the password?");
fgets(input_buffer,0x80,stdin);
strtok(input_buffer,"\n");
while (loop_counter < 0x27) {
if (flag[(long)(int)loop_counter] != (int)input_buffer[(long)(int)loop_counter]) {
difference_detected = true;
}
loop_counter = loop_counter + 1;
}
if (difference_detected) {
puts("Wrong. Try again.");
uVar2 = 0xffffffff;
}
else {
puts(
"\n##############################################\n### Congratulations! This is the flag! ###\n##############################################\n"
);
uVar2 = 0;
}
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar2;
}
All that’s left to do is getting the flag from the ‘flag’ variable. We could transform all the values by hand, but it might be faster to simply copy the array initialisation code to an other C program and print the flag:
#include <stdio.h>
// gcc -o printflag printflag.c
void main()
{
// copies from decompiled code
//int flag [39];
char flag [39]; // declared as char to make in printable
flag[0] = 0x4d;
flag[1] = 0x52;
flag[2] = 0x4d;
flag[3] = 0x43;
flag[4] = 0x44;
flag[5] = 0x43;
flag[6] = 0x54;
flag[7] = 0x46;
flag[8] = 0x7b;
flag[9] = 0x73;
flag[10] = 0x6f;
flag[11] = 0x6d;
flag[12] = 0x65;
flag[13] = 0x74;
flag[14] = 0x68;
flag[15] = 0x69;
flag[16] = 0x6e;
flag[17] = 0x67;
flag[18] = 0x5f;
flag[19] = 0x65;
flag[20] = 0x61;
flag[21] = 0x73;
flag[22] = 0x79;
flag[23] = 0x5f;
flag[24] = 0x66;
flag[25] = 0x6f;
flag[26] = 0x72;
flag[27] = 0x5f;
flag[28] = 0x62;
flag[29] = 0x72;
flag[30] = 0x65;
flag[31] = 0x61;
flag[32] = 0x6b;
flag[33] = 0x66;
flag[34] = 0x61;
flag[35] = 0x73;
flag[36] = 0x74;
flag[37] = 0x7d;
flag[38] = 0;
printf("The flag is %s\n",flag);
}
And this little program will print out the correct flag.
Or, alternatively, one could have skipped all the analysis, just eyeballed the decompiled main, seen the long list of values and noticed that they all fall into the printable ASCII range. And then could have added two and two together.
Whatever works best.