Eat the Cake!
7 minutes to read
We have a Windows PE called cake.exe
:
$ file cake.exe
cake.exe: PE32 executable (console) Intel 80386, for MS Windows, UPX compressed
Decompression
As can be seen, it is compressed with UPX, so let’s decompress it:
$ upx -d cake.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2023
UPX 4.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 30th 2023
File size Ratio Format Name
-------------------- ------ ----------- -----------
15872 <- 9216 58.06% win32/pe cake.exe
Unpacked 1 file.
$ file cake.exe
cake.exe: PE32 executable (console) Intel 80386, for MS Windows
Decompilation
Let’s open it in Ghidra and decompile it. The entry
function does not give any hint on what to look:
void entry() {
___security_init_cookie();
___tmainCRTStartup();
return;
}
If we read ___tmainCRTStartup
, we will find the address of the “main” function:
int ___tmainCRTStartup() {
bool bVar1;
void *pvVar2;
void *pvVar3;
int iVar4;
BOOL BVar5;
bVar1 = false;
do {
pvVar3 = NULL;
LOCK();
pvVar2 = StackBase;
if (DAT_0040548c != NULL) {
pvVar3 = DAT_0040548c;
pvVar2 = DAT_0040548c;
}
DAT_0040548c = pvVar2;
UNLOCK();
if (pvVar3 == NULL) goto LAB_004023ed;
} while (pvVar3 != StackBase);
bVar1 = true;
LAB_004023ed:
if (_DAT_00405490 == 1) {
_amsg_exit(0x1f);
} else if (_DAT_00405490 == 0) {
_DAT_00405490 = 1;
iVar4 = _initterm_e(&DAT_00403150,&DAT_00403160);
if (iVar4 != 0) {
return 0xff;
}
} else {
_DAT_00405448 = 1;
}
if (_DAT_00405490 == 1) {
_initterm(&DAT_00403134,&DAT_0040314c);
_DAT_00405490 = 2;
}
if (!bVar1) {
LOCK();
DAT_0040548c = NULL;
UNLOCK();
}
if ((DAT_00405494 != NULL) &&
(BVar5 = __IsNonwritableInCurrentImage((PBYTE)&DAT_00405494), BVar5 != 0)) {
(*DAT_00405494)(0,2,0);
}
*(undefined4 *)__winitenv_exref = DAT_0040545c;
DAT_0040544c = FUN_00401350();
if (_DAT_00405450 != 0) {
if (_DAT_00405448 == 0) {
_cexit();
}
return DAT_0040544c;
}
// WARNING: Subroutine does not return
exit(DAT_0040544c);
}
The “main” function is FUN_00401350
:
void FUN_00401350() {
char cVar1;
undefined4 ****_Src;
uint uVar2;
char *pcVar3;
undefined auStack_440[7];
char cStack_439;
undefined4 ****local_438[4];
int local_428;
uint local_424;
char cStack_420;
char cStack_41f;
char cStack_41e;
char cStack_41d;
char cStack_41b;
char cStack_418;
char cStack_417;
char cStack_416;
char cStack_415;
char cStack_413;
char cStack_412;
undefined uStack_21;
uint local_1c;
void *local_14;
undefined *puStack_10;
undefined4 local_c;
puStack_10 = &LAB_0040299b;
local_14 = ExceptionList;
local_1c = DAT_00405000 ^ (uint) auStack_440;
ExceptionList = &local_14;
local_424 = 0xf;
local_428 = 0;
local_438[0] = (undefined4 ****) ((uint) local_438[0] & 0xffffff00);
local_c = 0;
FUN_00401ba0((int *) cout_exref, "\n $\n / \\ \n / | !\n / / !\n / / !\n");
FUN_00401ba0((int *) cout_exref, " _/ ____/ \\ \n / !\n ! !\n \\ / \n");
FUN_00401ba0((int *) cout_exref, " _-----\\ _ _ _ _ _ _ _ _/----- \n / \t\t\t \\ \n !\t \t ____\ t ____\t !\n _\\ ======| |~~| |=/_ \n");
FUN_00401ba0((int *) cout_exref, " /\t\t ````\t ````\t \\ \n ! )--------( !\n ! \t\t\t !\n \\\t\t\t\t / \n !---------------------------$-!\n");
FUN_00401ba0((int *) cout_exref, " !\t !\t |\t ! $$$ \n !\t !\t |\t ! $$$\n !\t !\t |\t ! $$$\n !\t !\t |\t ! $$$\n !\t !\t |\t ! $$$\n");
FUN_00401ba0((int *) cout_exref," !\t !\t |\t ! $$\n !\t !\t |\t ! !$$\n ! !\t |\t ! ! *\n !\t !\t |\t ! !\n !\t !\t |\t ! !\n !----!--------- ----!----!\n");
FUN_00401ba0((int *) cout_exref, " | ii_\\ | ii_\\ \n |______\\ |______\\ \n\n\n");
FUN_00401ba0((int *) cout_exref, "A challenge brought to you by little_pwnie ;)\n\n");
FUN_00401ba0((int *) cout_exref, "Please enter the 10-digit password (Only numbers and capital letters): ");
FUN_00401de0((int *) cin_exref, local_438);
while (local_428 != 0xf) {
FUN_00401ba0((int *) cout_exref, "Please enter the 15-digit password (Only numbers and capital letters): ");
FUN_00401de0((int *) cin_exref, local_438);
}
_Src = local_438;
if (0xf < local_424) {
_Src = local_438[0];
}
strncpy_s(&cStack_420, 0x400, (char *) _Src, 0x400);
uStack_21 = 0;
uVar2 = FUN_004012f0((int) &cStack_420);
if ((cStack_41d == 'k') && (cStack_418 == 'a')) {
cVar1 = cStack_439;
if ((cStack_420 != 'h') || (cStack_416 != 'a')) goto LAB_004014fd;
if (((cStack_41b == 'h') && (cStack_417 == 'r')) && (cStack_415 == 'd')) {
cVar1 = '\x01';
goto LAB_004014fd;
}
}
cVar1 = '\0';
LAB_004014fd:
if ((cStack_41f == '@') && (cStack_412 == 'E')) {
if ((cStack_41e == 'c') && (cStack_413 == '$')) {
cStack_439 = '\x01';
}
} else {
cStack_439 = '\0';
}
if ((((char)uVar2 == '\0') || (cVar1 == '\0')) || (pcVar3 = "Congratulations! Now go validate your flag!\n", cStack_439 == '\0')) {
pcVar3 = "Better luck next time...\n";
}
FUN_00401ba0((int *) cout_exref, pcVar3);
system("pause");
if (0xf < local_424) {
operator_delete(local_438[0]);
}
ExceptionList = local_14;
FUN_00401fff(local_1c ^ (uint) auStack_440);
}
Another way of finding the “main” function is searching for strings that appear printed on screen when executing the binary and searching for cross-references in functions. But we are not going to execute the program, the challenge can be solved statically.
Understanding the program
The program asks for a 10-digit password (or a 15-digit password), although only numbers and capital letters are allowed:
FUN_00401ba0((int *) cout_exref, "Please enter the 10-digit password (Only numbers and capital letters): ");
FUN_00401de0((int *) cin_exref, local_438);
while (local_428 != 0xf) {
FUN_00401ba0((int *) cout_exref, "Please enter the 15-digit password (Only numbers and capital letters): ");
FUN_00401de0((int *) cin_exref, local_438);
}
Once we have entered it, the program checks if its is correct. The input password is stored in local_438
, then copied to cStack_420
:
_Src = local_438;
if (0xf < local_424) {
_Src = local_438[0];
}
strncpy_s(&cStack_420, 0x400, (char *) _Src, 0x400);
uStack_21 = 0;
uVar2 = FUN_004012f0((int) &cStack_420);
After that, there are two if
blocks that are interesting:
if ((cStack_41d == 'k') && (cStack_418 == 'a')) {
cVar1 = cStack_439;
if ((cStack_420 != 'h') || (cStack_416 != 'a')) goto LAB_004014fd;
if (((cStack_41b == 'h') && (cStack_417 == 'r')) && (cStack_415 == 'd')) {
cVar1 = '\x01';
goto LAB_004014fd;
}
}
cVar1 = '\0';
LAB_004014fd:
if ((cStack_41f == '@') && (cStack_412 == 'E')) {
if ((cStack_41e == 'c') && (cStack_413 == '$')) {
cStack_439 = '\x01';
}
} else {
cStack_439 = '\0';
}
Each of them is checking some bytes and setting cVar1
and cStack_439
to 1
or 0
when the checks are passed or not, respectively. If both checks pass, we will see a success message, otherwise, a failure message:
if ((((char)uVar2 == '\0') || (cVar1 == '\0')) || (pcVar3 = "Congratulations! Now go validate your flag!\n", cStack_439 == '\0')) {
pcVar3 = "Better luck next time...\n";
}
If we sort the byte checks, we will get this:
cStack_412 == 'E'
cStack_413 == '$'
cStack_415 == 'd'
cStack_416 != 'a'
cStack_417 == 'r'
cStack_418 == 'a'
cStack_41b == 'h'
cStack_41d == 'k'
cStack_41e == 'c'
cStack_41f == '@'
cStack_420 != 'h'
But we are missing some values… The rest of them can be found at FUN_004012f0
, which sets the variable uVar2
, also used for verification:
uint __fastcall FUN_004012f0(int param_1) {
uint uVar1;
int iVar2;
uVar1 = isdigit((int) *(char *) (param_1 + 6));
if (uVar1 != 0) {
uVar1 = isdigit((int) *(char *) (param_1 + 0xc));
if (uVar1 != 0) {
iVar2 = atoi((char *) (param_1 + 6));
uVar1 = atoi((char *) (param_1 + 0xc));
if ((((iVar2 == 3) && (uVar1 == 1)) && (*(char *) (param_1 + 4) == 't')) && (*(char *) (param_1 + 7) == 'p')) {
return 1;
}
}
}
return uVar1 & 0xffffff00;
}
The value of param_1
is &cStack_420
, so:
param_1 + 4
is&cStack_4c
param_1 + 6
is&cStack_1a
param_1 + 7
is&cStack_19
param_1 + 0xc
is&cStack_14
So, we can complete the list:
cStack_412 == 'E'
cStack_413 == '$'
cStack_414 == 1
cStack_415 == 'd'
cStack_416 != 'a'
cStack_417 == 'r'
cStack_418 == 'a'
cStack_419 == 'p'
cStack_41a == 3
cStack_41b == 'h'
cStack_41c == 't'
cStack_41d == 'k'
cStack_41e == 'c'
cStack_41f == '@'
cStack_420 != 'h'
A password can be glimpsed if we read it from the bottom: h@ckth3parad1$E
. This is because of little-endian format (that’s also the reason why &cStack_420 + 7 = &cStack_419
, it goes backwards).
Flag
Having found this password (in fact, a 15-byte password), the flag is just HTB{h@ckth3parad1$E}
.