racecar
Hello hackers, time for another easy CTF. The HackTheBox racecar challenge is a small 32-bit binary with a classic format string vulnerability. No shellcode, no ROP, just a chatty printf() that tells us more than it should.
printf() as the format string. The flag was already read onto the stack, so %08x dumps it as hex, and a little-endian decode gives it back as text.Recon
First, what kind of file are we dealing with?
% file racecar
racecar: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c5631a370f7704c44312f6692e1da56c25c1863c, not stripped32-bit, PIE and not stripped, so the function names are still there. That makes static analysis a lot friendlier.
Analysis in Ghidra
I loaded it into Ghidra and went through the function list.

There are a couple of interesting names. After a bit of poking around, car_menu turned out to be the one that matters.

Here is the relevant part. When you win the race, the program opens flag.txt, reads its contents into memory, then asks you for a victory message and prints it:
__format = (char *)malloc(0x171);
__stream = fopen("flag.txt","r");
if (__stream == (FILE *)0x0) {
printf("%s[-] Could not open flag.txt. Please contact the creator.\n",&DAT_00011548,puVar5);
/* WARNING: Subroutine does not return */
exit(0x69);
}
fgets(local_3c,0x2c,__stream);
read(0,__format,0x170);
puts(
"\n\x1b[3mThe Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this: \x1b[0m"
);
printf(__format);fgets(local_3c, ...) puts the flag into a local stack variable, and printf(__format) uses our input as the format string. Every %x we send makes printf() read the next 4-byte word off the stack, flag included.Exploit
Straight to the point: win the race, then send a message full of %08x to print the stack.

The output is a wall of hex words. Somewhere in the middle sits the flag, encoded as hex.
0x7b425448 is the string HTB{. Flip the bytes in every word before converting hex to ASCII.Convert the hex values to ASCII, fix the byte order, and the flag is ours. Time to grab a 🍺 and enjoy it. Cheers!
Wrap-up
- Never pass user input as the first argument to
printf().printf("%s", buf)would have fixed this whole challenge. - Secrets loaded onto the stack are only one format string away from being leaked.
- When leaking 32-bit words, always remember little-endian byte order before decoding.