Reversing: Eat The Cake

Eat The Cake is a reversing challenge in the medium category on Hack The Box.



Unpacking the zip archive shows that the binary is called cake.exe, apparently an PE executable. Lets check it using the die application.


The most important thing to note here is that the binary was packed with upx. This means that we will need to unpack it in order to analyze it.


Using upx the binary can be unpacked. I like to safe the binary with a new name to indicate what tool produced it. Here I unpack it to cake-upx.exe.


Sadly you notice that running the application does nothing. There is a good reason for this, explained nicely on the SANS blog in 2014. Some software uses hardcoded addresses, which due to unpacking using upx, are no longer valid.

Luckily ASLR can be turned off. Instead of doing it for the entire operating system this can be done for the executable only by using setdllcharacteristics.


Now that DYNAMIC_BASE, or ASLR, is turned off, the unpacked executable runs as expected.



Running strings on the binary yields little help. Apparently there is a need to enter 2 passwords to get to the flag.

A challenge brought to you by little_pwnie ;)
Please enter the 10-digit password (Only numbers and capital letters):
Please enter the 15-digit password (Only numbers and capital letters):
Congratulations! Now go validate your flag!
Better luck next time...
string too long
invalid string position

Running it

Lets give the unpacked binary a try and enter some passwords. Experimenting with the 1st password it seems to just accept anything. The 2nd password will accept an input of 15 characters.


Time to look at some code!


When opening up a binary in Ghidra I first want to see the entry functions. In the Symbol Tree window select Exports and there should only be the function entry.

Reading the code it shows a lot of setup stuff for the terminal. The application uses the terminal so this is to be expected. At the end there is a function call that seems to determine the outcome of the application, FUN_00401350.

DAT_0040544c = FUN_00401350();
if (_DAT_00405450 != 0) {
    if (_DAT_00405448 == 0) {
    return DAT_0040544c;
/* WARNING: Subroutine does not return */

Examining the function indeed shows us the output we saw before on the screen. I like to rename the function, in case I need to stop and get back to it later. This can be done by pressing L when on the function name, or the right click Edit function signature. I renamed it to challenge.

The decompilation window of Ghidra now shows us the beautiful ascii art and a lot of code that works on single characters. Each line with ascii art is called with function FUN_00401ba0, lets rename that one to printLine, again just press L to rename the function.

Then the answers to the questions are read into the program using the following code:

FUN_00401de0((int *)cin_exref,local_438);

From this it is clear that FUN_00401de0 is a scan function, taking std::cin as input and putting the result in local_438. Lets rename the function and the variable.

scan((int *)cin_exref,guess);
while (local_428 != 0xf) {
    printLine((int *)cout_exref,
              "Please enter the 15-digit password (Only numbers and capital letters): ");
    scan((int *)cin_exref,guess);

With these changes it is clear that our guess buffer is reused to fill in the 15 positions password. Next the guess is transferred to a temporary variable, the length is compared to 15 (0x0F) and if it is the correct length it is copied into local_420 using strncpy_s. Notice the insane buffer length of 0x400.

_Src = guess;
if (0xf < local_424) {
    _Src = guess[0];
strncpy_s(&local_420,0x400,(char *)_Src,0x400);

Next there are a lot of modifications to local variables. The variable names seem to be a form of sequence (420, 41d, 418, etc). At the top of the function Ghidra has also put all the variables underneath eachother with their type set to undefined.

if ((local_41d == 'k') && (local_418 == 'a')) {
    cVar1 = local_439;
    if ((local_420 != 'h') || (local_416 != 'a')) goto LAB_004014fd;
    if (((local_41b == 'h') && (local_417 == 'r')) && (local_415 == 'd')) {
        cVar1 = '\x01';
        goto LAB_004014fd;

Within Ghidra we can help it by manually setting the type for variables. We know the key should be 15 positions, so we can try and change the signature of local_420 to a char[15].

if ((local_420[3] == 'k') && (local_420[8] == 'a')) {
    cVar1 = local_439;
    if ((local_420[0] != 'h') || (local_420[10] != 'a')) goto LAB_004014fd;
    if (((local_420[5] == 'h') && (local_420[9] == 'r')) && (local_420[11] == 'd')) {
        cVar1 = '\x01';
        goto LAB_004014fd;

That makes it much better to read and understand what is going on. The character array is checked with values at various places. This allows us to reconstruct the password using the array operations.

Before we continue there is only one small thing to look at still. Just before the above if statement a function call takes local_420 as an argument.


Following the function it is clear that the generated signature is not correct. The int should actually be a char * to match our redefinition of char[15]. Lets change the function signature. I also took the chance to rename the function to checkChars.

All that is left to do is to take all the local_420 array assignments and create the password string.

local_420[0] != 'h'
local_420[1] == '@'
local_420[2] == 'c'
local_420[3] == 'k'
local_420[4] == 't' - checkChars
local_420[5] == 'h'
local_420[6] == '3' - checkChars
local_420[7] == 'p' - checkChars
local_420[8] == 'a'
local_420[9] == 'r'
local_420[10] != 'a'
local_420[11] == 'd'
local_420[12] == '1' - checkChars
local_420[13] == '$'
local_420[14] == 'E'

The 15 position password to check is h@ckth3parad1$E. This is also the flag for the challenge.