Ataque return to libc

Habilitar mitigación W^X

Para presentar una serie de estrategias tradicionales de explotación a programas vulnerables se propuso deshabilitar configuraciones de seguridad presentes en los sistemas operativos modernos, entre ellas la mitigacion W^X: una política en relación a la memoria que impide ejecutar código almacenado la pila. Gracias a ello fue posible inyectar un shellcode como string en la pila y ejecutarlo a través de una redirección de la dirección de retorno.
En este punto se propone volver a habilitar la mitigación W^X, lo que nos obligará a crear estrategias de ataque más complejas.

IMPORTANTE:
Antes de avanzar es necesario habilitar la mitigación W^X. Para eso el programa vulnerable ya no debe compilarse con el flag -z exestack que la deshabilitaba. A partir de ahora los programas vulnerables se compilan de la siguiente manera:

user@abos:~$ gcc -m32 -no-pie -fno-stack-protector -ggdb -mpreferred-stack-boundary=2 -o e1 e1.c

Return-oriented programming (ROP)

Se asume un escenario donde no es posible la ejecución de código almacenado en la pila. El objetivo de este nivel va a ser lograr el control de la pila con el objetivo de ejecutar código existente (como puede ser una función de libc) sin inyectar código propio que no podría ejecutarse.

Return-to-libc

Una estrategia para contrarrestar la mitigación W^X es ejecutar código que no se encuentre en la pila sino en un sector de la memoria que sea ejecutable, por ejemplo en libc. Esta táctica se denomina return-to-libc ya que el código utilizado para vulnerar el programa son funciones dentro de esta libreria.
Entonces así como se modificó la dirección de retorno para la ejecución de código arbitrario dentro de la pila, es posible sobreescribir esta dirección pero para que apunte a la biblioteca libc, que cuenta con funciones muy útiles como system() para, por ejemplo, obtener una shell.

ret2libc

El retorno a libc es parte de un tipo de ataque más amplio que se denomina Return-oriented programming (o ROP). La estrategia de ROP en términos generales se trata de concatenar secuencias de instrucciones ya existentes en el programa vulnerable, denominadas gadgets. Estos gadgets al ser ejecutados logran el comportamiento deseado por el atacante, al modificar el estado de los registros y realizar llamadas a sistema que permitan tomar el control del programa vulnerable. Cuando esas instrucciones son parte de libc es que estamos frente a un ataque del tipo return-to-libc.

Función system()

user@abos:~$ man system
NAME
       system - execute a shell command

SYNOPSIS
       #include <stdlib.h>

       int system(const char *command);

Es una función de la biblioteca libc, generalmente utilizada en este tipo de ataques, que ejecuta el programa o comando indicado. Por ejemplo, system("ls") ejecuta el comando ls que lista el contenido del directorio actual.
Su potencialidad en la escritura de exploits se evidencia cuando se llama a esa función con el argumento system("/bin/sh"). Su funcionamiento se observa en el siguiente programa de ejemplo:

include <stdlib.h>

void main(){
	system("/bin/sh");
} 

Lo compilamos y ejecutamos:

user@abos:~$ gcc sys.c -o sys
user@abos:~$ sudo chown root ./sys; sudo chmod u+s ./sys                      ; root owner & setuid
user@abos:~$ ./sys
# whoami
root
# id
uid=1001(user) gid=1001(user) euid=0(root) groups=1001(user),27(sudo)
# 

Efectivamente logramos una shell. Esta shell tiene privilegios de root gracias a que definimos su setuid.
En este punto nos interesa ver el layout de la pila en el llamado a system("/bin/sh"), por lo que desensamblamos el ejecutable:

user@abos:~$ gdb sys
user@abos:~$ disassemble main
>>> disassemble main
Dump of assembler code for function main:
void main(){
   0x080483fb <+0>:  push   ebp
   0x080483fc <+1>:  mov    ebp,esp

  system("/bin/sh");
   0x080483fe <+3>:  push   0x80484a0              ; "/bin/sh"
   0x08048403 <+8>:  call   0x80482d0 <system@plt>
   0x08048408 <+13>: add    esp,0x4
  }
   0x0804840b <+16>: leave  
   0x0804840c <+17>: ret    
End of assembler dump.
>>> x/s 0x80484a0
0x80484a0:  "/bin/sh"

Vemos que, antes del llamado a system, se apila la dirección de /bin/sh con la instrucción push 0x80484a0.
En términos generales la estrategia de return-to-libc apunta a sobreescribir la dirección de retorno de una función para que apunte a la función system dentro de libc con el string “/bin/sh” como argumento. Para que el llamado a system nos devuelva una shell es necesario -previo al call- construir un layout de la pila que simule el llamado a esa función con el argumento “/bin/sh”, tal como indica el gráfico a continuación:

pila en llamado a sys

Ese layout de la pila necesario se podría construir aprovechando una vulnerabilidad de desbordamiento de un búfer o del tipo format string.

Consideraciones:
Es importante tener en cuenta que la técnica de aleatoriedad en el espacio de direcciones (ASLR en inglés) dispone de forma aleatoria no sólo las direcciones de la pila y el heap sino también de las librerias compartidas.
Con esta mitigación funcionando no sería posible un ataque return-to-libc tal como lo planteamos porque no podríamos utilizar la dirección de libc (y dentro de ella de system()) de manera consistente.

En este punto, no nos preocupamos aún por el ASLR, es por ello que lo deshabilitamos con:

user@abos:~$ sudo sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space=0

Práctica

Con los contenidos vistos hasta el momento es posible avanzar con E 1.

Y está disponible una práctica guiada para la resolución de E 1 a través de un ataque return to libc.