FlagCasino
4 minutos de lectura
Se nos proporciona un binario llamado casino
:
$ file casino
casino: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7618b017ef4299337610a90a0a6ccb7f9efc44a4, for GNU/Linux 3.2.0, not stripped
Descompilación
Si lo abrimos en Ghidra, veremos esta función main
:
undefined8 main(void)
{
int iVar1;
char local_d;
uint local_c;
puts("[ ** WELCOME TO ROBO CASINO **]");
puts(
" , ,\n (\\____/)\n (_oo_)\n (O)\n __||__ \\)\n []/______\\[] /\n / \\______/ \\/\n / /__\\\n(\\ /
____\\\n---------------------"
);
puts("[*** PLEASE PLACE YOUR BETS ***]");
local_c = 0;
while( true ) {
if (0x1d < local_c) {
puts("[ ** HOUSE BALANCE $0 - PLEASE COME BACK LATER ** ]");
return 0;
}
printf("> ");
iVar1 = __isoc99_scanf(&DAT_001020fc,&local_d);
if (iVar1 != 1) break;
srand((int)local_d);
iVar1 = rand();
if (iVar1 != *(int *)(check + (long)(int)local_c * 4)) {
puts("[ * INCORRECT * ]");
puts("[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]");
/* WARNING: Subroutine does not return */
exit(-2);
}
puts("[ * CORRECT *]");
local_c = local_c + 1;
}
/* WARNING: Subroutine does not return */
exit(-1);
}
Aunque es un código simple, vamos a cambiar el nombre y el tipo de algunas variables para que el código sea más fácil de leer:
int main() {
int r;
char c;
uint i;
puts("[ ** WELCOME TO ROBO CASINO **]");
puts(" , ,\n (\\____/)\n (_oo_)\n (O)\n __||__ \\)\n []/______\\[] /\n / \\______/ \\/\n / /__\\\n(\\ /____\\\n---------------------");
puts("[*** PLEASE PLACE YOUR BETS ***]");
i = 0;
while (true) {
if (29 < i) {
puts("[ ** HOUSE BALANCE $0 - PLEASE COME BACK LATER ** ]");
return 0;
}
printf("> ");
r = __isoc99_scanf(" %c", &c);
if (r != 1) break;
srand((int) c);
r = rand();
if (r != check[(int) i]) {
puts("[ * INCORRECT * ]");
puts("[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]");
exit(-2);
}
puts("[ * CORRECT *]");
i++;
}
exit(-1);
}
El programa toma los caracteres de la entrada del usuario. Para cada carácter, inicializa el PRNG con srand((int) c)
y comprueba que rand()
es igual que check[(int) i]
:
printf("> ");
r = __isoc99_scanf(" %c", &c);
if (r != 1) break;
srand((int) c);
r = rand();
if (r != check[(int) i]) {
puts("[ * INCORRECT * ]");
puts("[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]");
exit(-2);
}
puts("[ * CORRECT *]");
Nótese que check
es un vector de int
de 30
elementos. Sabemos esto porque el bucle while
se para cuando i
es mayor que 29
:
if (29 < i) {
puts("[ ** HOUSE BALANCE $0 - PLEASE COME BACK LATER ** ]");
return 0;
}
Solución
Como estamos buscando una flag, sabemos que comienza con HTB{
:
$ ./casino
[ ** WELCOME TO ROBO CASINO **]
, ,
(\____/)
(_oo_)
(O)
__||__ \)
[]/______\[] /
/ \______/ \/
/ /__\
(\ /____\
---------------------
[*** PLEASE PLACE YOUR BETS ***]
> H
[ * CORRECT *]
> T
[ * CORRECT *]
> B
[ * CORRECT *]
> {
[ * CORRECT *]
> a
[ * INCORRECT * ]
[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]
Ahora necesitamos encontrar una manera de conocer el próximo carácter. Un enfoque es probar todos los caracteres imprimibles hasta que encontremos el que devuelve [ * CORRECT * ]
. Pero esto es muy simple, encontremos otra manera.
Podemos tomar los valores esperados de check
y encontrar qué semilla hará que rand()
produzca esos valores. Podemos ver estos valores en Ghidra:
check XREF[3]: Entry Point(*), main:00101216(*),
main:0010121d(*)
00104080 be 28 4b int[30]
24 05 78
f7 0a 17
00104080 [0] 244B28BEh, AF77805h, 110DFC17h, 7AFC3A1h,
00104090 [4] 6AFEC533h, 4ED659A2h, 33C5D4B0h, 286582B8h,
001040a0 [8] 43383720h, 55A14FCh, 19195F9Fh, 43383720h,
001040b0 [12] 19195F9Fh, 747C9C5Eh, F3DA237h, 615AB299h,
001040c0 [16] 6AFEC533h, 43383720h, F3DA237h, 6AFEC533h,
001040d0 [20] 615AB299h, 286582B8h, 55A14FCh, 3AE44994h,
001040e0 [24] 6D7DFE9h, 4ED659A2h, CCD4ACDh, 57D8ED64h,
001040f0 [28] 615AB299h, 22E9BC2Ah
Probablemente sea mejor mirar el script de solución:
#include <stdlib.h>
#include <stdio.h>
#define MAX 30
int check[MAX] = {0x244b28be, 0xaf77805, 0x110dfc17, 0x7afc3a1, 0x6afec533, 0x4ed659a2, 0x33c5d4b0, 0x286582b8, 0x43383720, 0x55a14fc, 0x19195f9f, 0x43383720, 0x19195f9f, 0x747c9c5e, 0xf3da237, 0x615ab299, 0x6afec533, 0x43383720, 0xf3da237, 0x6afec533, 0x615ab299, 0x286582b8, 0x55a14fc, 0x3ae44994, 0x6d7dfe9, 0x4ed659a2, 0xccd4acd, 0x57d8ed64, 0x615ab299, 0x22e9bc2a};
int main() {
char flag[MAX];
char c;
int r;
int i;
for (c = 0x20; c < 0x7f; c++) {
srand(c);
r = rand();
for (i = 0; i < MAX; i++) {
if (check[i] == r) {
flag[i] = c;
}
}
}
puts(flag);
return 0;
}
Flag
Solo necesitamos compilar el código y ejecutarlo para obtener la flag:
$ gcc solve.c
$ ./a.out
HTB{r4nd_1s_sup3r_pr3d1ct4bl3}