404CTF 2025: Cbizarre
Recon
We’re provided with a binary file. Checking the usual stuff:
$ file chall2
chall2: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=683fe4c494b6b7dc5df519f9299a42f6616677ff, for GNU/Linux 3.2.0, not stripped
$ checksec --file=chall2
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 43 Symbols No 0 2 chall2
Some protections are enabled as we can see (NX, PIE). We can proceed to decompile the binary using Ghidra, and after some stripping of the useless/usual lines, and after renaming some labels, we find the interesting part of the code:
if (argc == 2) {
length = strlen((char *)argv[1]);
if (length == 0x14) {
if (*(char *)(argv[1] + 5) != 'Z') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 0xc) != 'o') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)argv[1] != 'f') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 0x12) != '1') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 7) != '%') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 3) != 'M') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 9) != 'y') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 0x10) != 'v') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 0xe) != 'n') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 1) != 'a') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 0x13) != 'x') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 6) != 'a') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 0xf) != 'M') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 8) != '3') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 4) != 'P') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 0xb) != 'K') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 10) != 'N') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 0x11) != '%') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 2) != 'V') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
if (*(char *)(argv[1] + 0xd) != '@') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}
local_28 = 0x661a1c040e625152;
local_20 = 0x492f7e4954;
uStack_1b = 0x200233;
uStack_18 = 0x5026906;
local_10 = xor(&local_28,argv[1],0x14);
printf("Bravo ! Vous avez le flag ! %s\n",local_10);
result = 0;
}
else {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
result = 1;
}
}
else {
fprintf(stderr,"Usage: %s <password>\n",*argv);
result = 1;
}
return result;
}As we can see here, we need to provide a password as a command-line argument to the binary. The first information we gather about that password is that its length is 0x14 bytes (which is 20 in decimal). Then, we see that the program compares some position in memory, with an offset relative to the start of the password string, against a byte: in the below example, the program compares the byte offseted by 5 against ‘Z’:
if (*(char *)(argv[1] + 5) != 'Z') {
fwrite("Error: Incorrect password.\n",1,0x1b,stderr);
exit(1);
}That code part can simply be resumed with this pseudocode:
if (password+5 != 'Z') then exit
Data recovery and exploitation
Looking at all those small conditional statements, it looks like the password can be retrieved by looking at each comparison and assembling all the letters that are compared, in the right order.
After getting through all of the checks manually and writing down the
byte at the correct offset, we’re left with this string:
faVMPZa%3yNKo@nMv%1x.
Throwing it as input to the program gives us the flag:
$ ./chall2 faVMPZa%3yNKo@nMv%1x
Bravo ! Vous avez le flag ! 404CTF{Cg00d&slmpL3}