No eXecute bypass sobre Linux x32

Bypaseando la contramedida No eXecute

Que es no execute:

La contramedida de no ejecución es la más popular qué podemos encontrar en los software modernos. La idea es que no se ejecute cierto dato en una pila.

Esta contramedida tambien es conocida cómo DEP-data execution Prevention

Un poco de historia:

No eXecute fue inventado en 2004 y puesto en uso con las siglas (NX) Bit en los procesadores AMD y como eXecute Disable bit (XD) en los procesadores Intel tanto en las arquitecturas de 32 y 64 bit

Si intenta ejecutar cualquier dato engañoso en la pila, por ejemplo, después de mover el flujo de ejecución de regreso a la pila, provocando con ello un desbordamiento de búfer, el programa romperá con un SIGSEGV.

NX solo puede ser fácilmente puenteado. Como ya sabrás, en la arquitectura x86 cuando una función es llamada, los argumentos son puestos en la pila, y luego la función es llamada.

NX no permite la ejecución de datos en la pila, pero habiendo argumentos de función en la pila, está técnica es posible

Durante un desbordamiento de pila, estaremos controlando los datos que ponemos en la pila además de cómo podemos desbordar creando y modificando la pila a nuestro gusto. Así que para puentear la protección NX, tenemos que suministrar argumentos de función como parte de la pila para que, mediante otra función, hacer que  el puntero de registro de ejecucion (EIP) los utilice.

Hay numerosas funciones que pueden ser reutilizadas para este propósito. Estás pueden estar dentro del propio ejecutable o en alguna librería usada por este.

Las técnicas que veremos son De la familia “RET2”. Son llamadas así porque retornan a otra área del programa desde la pila. Aquí veremos nombres como RET2 LIBC o RET2 System.

Entre las numerosas librerías que un software usa, en el mundo de Linux, la más común es La librería estándar C también llamada LIBC.

La librería estándar C en Linux, está presente en todos los sistemas por defecto y contiene algunas funciones básicas requeridas por los programas para funcionar correctamente. Podemos chequearlo en nuestro binario objetivo con el siguiente comando:

LDD [binario objetivo] oh si preferimos usar GDB con el comando vmmap

Como ya hemos  comentado con anterioridad, podríamos intentar reutilizar una de las funciones que ya están almacenadas por el procesador de memoria en el área de las librerías (libc)

Lo que necesitamos saber acerca de las librerías es que pueden ser recibidas como una lista de funciones que residen en un desplazamiento conocido desde la librería base. Una vez sabes la librería base y la versión de la librería, podrás saber las direcciones de las funciones en el objetivo. La distancia existente entre la dirección de la librería base y la dirección de la función objetivo se llama OFFSET, el offset es de vital importancia a la hora de realizar un desbordamiento de pila.

Una vez sabemos con certeza las funciones que están disponibles en la librería, podemos hacer uso de ellas para, por ejemplo, crear una Shell. Para llamar a una función, debemos preparar la pila previamente para que a continuación, sabiendo los argumentos que contiene, poder ir llamando a las funciones necesarias.

A modo de resumir esto un poco, nos vamos a quedar con lo siguiente:

  • la contramedida NX previene de la ejecución desde la pila, pero sí contiene argumentos previos en ella, la técnica de puenteo será perfectamente válida
  • el orden de ejecutar una función que se encuentra en una librería es la siguiente:
  • encontrar una función interesante que pueda proveernos de una Shell
  • Configurar correctamente la pila
  • Redirigir el puntero EIP con la función encontrada en el punto 1

Las funciones que son buscadas en primera instancia y que resultan en un comando de ejecución son las siguientes: System, cualquier tipo de archivo EXEC*

Ahora sí vamos a entrar en materia y para ello lo primero que hacemos es crear un binario vulnerable:

Compilación:

A continuación, procedemos a compilar el binario, vemos que hemos dejado la protección de ejecución

Desactivación ASLR y comprobación de contramedidas:

Desactivamos temporalmente el ASLR

Comprobamos que la contramedida se encuentra activa

Ejecución de binario:

ejecutamos el binario en GDB

sabiendo que el buffer inicial es de 500 bit y que en la función overflow lo fijamos en 700 bit, vamos a crear un patrón de 600. Una vez creado el patrón, hacemos correr el binario cargando el patrón que acabamos de crear

Vemos que el programa se detiene en un registro. Pues bien, como hemos expuesto anteriormente, ejecutando un patrón desde el inicio del binario hasta el registro donde se detiene, podemos saber cuál es la distancia desde el inicio hasta la ruptura. en este caso son 516 bits

creación del exploit:

Para confirmar que se trata del punto de ruptura correcto vamos a crear un payload en el cual marcaremos que nos cargue la letra ‘A’ 516 veces y posteriormente cargue cuatro veces la letra ‘B’.

una vez creado el archivo lo pasamos a texto plano.

Buscando el punto de ruptura:

volvemos a cargar el binario en el GDB e iniciamos el mismo cargando el exploit. Podemos observar cómo se cargan las letras ‘A’ en el registro base y en su respectivo puntero y cómo carga exactamente en el puntero EIP las letras ‘B’, por lo tanto, podemos confirmar que ese es el punto de desbordamiento.

Solicitamos que nos muestre las 20 palabras del tope de pila ($ESP) en su registro 100. Observamos que la letra ‘A’ se encuentra en el total de ese registro. Elegimos el más cercano al inicio y lo copiamos para poder añadirlo a nuestro exploit.

Con el registro copiado, volvemos a abrir nuestro archivo exploit.py e insertamos el registro, pero como se puede ver en la imagen inferior es necesario pasar ese registro a código hexadecimal. Recordemos que, para pasar el registro a hexadecimal, debemos introducirlo en orden inverso y añadir un cero en caso necesario, si el registro se tratase de un número impar. En nuestro caso, nos encontramos con un registro par, por lo tanto, tan solo es necesario introducirlo como acabamos de explicar.

Guardamos los cambios realizados, y relanzamos el GDB con el nuevo exploit. Observar cómo el puntero del registro EIP se detiene en el registro especificado en el Exploit.

Comprobando la dirección de los punteros:

Una vez comprobado que el puntero del del registro de instrucción se corresponde al registro que hemos introducido previamente en el exploit, procedemos a modificar nuevamente el exploit, esta vez para determinar a qué cadena de palabras apunta cada puntero.

Una vez guardado los cambios, volvemos a relanzar el GDB, observamos que el puntero de registro base carga (EBP) la cadena con la letra ‘A’, el registro tope de pila(ESP) carga las cadenas con las letras ‘C’, ’D’, ’E’ y, por último, que el puntero de registro de instrucción (EIP) vuelve a cargar la cadena con la letra ‘B’.

Ahora vamos a buscar el punto más cercano al final de la cadena del registro ESP .Podemos ver en la imagen que, en dicho registro, se cargaron las cadenas con las letras ‘C’, ‘D’ y ‘E’.

Buscando y seleccionando librerías:

Ahora vamos a hacer uso de las librerías para encontrar una función la cual nos permita ejecutar una Shell. Como vemos en la imagen, basta con teclear en nuestra consola la palabra man system para que se nos abra el manual de la librería system

también necesitamos encontrar la librería sobre la que se apoya la función, primeramente, con el comando locate seguido el nombre de la librería nos ofrecerá las rutas donde se encuentra almacenada la librería en cuestión. Al tratarse de un equipo arquitectura Linux x32, haremos uso de la librería correspondiente, en nuestro caso la primera. Con el comando readelf seguido de la ruta dónde se encuentra la librería más la búsqueda de la palabra clave system nos arrojara en qué registro se encuentra almacenada la librería que debemos usar.

ahora vamos a lanzar nuevamente el binario con GDB para obtener los registros tanto de la función System cómo de la función exit

Perfilando el exploit:

Con casi toda la información recabada, podemos perfilar el exploit como se puede ver en la imagen:

!!!Importante!!! Cada cambio en el exploit debe ser pasado del formato Python a formato texto plano

Hacemos uso del binario en GDB y corremos nuestro exploit. Con el comando find /bin Buscamos los registros que hacen referencia al archivo bin. Entre todas las opciones que nos arroja podemos ver que en el registro acabado en 8338 se encuentra la llamada a Shell

volvemos a nuestro exploit e introducimos el nuevo dato. Como podemos observar tenemos tanto la cadena inicial como los distintos registros necesario para obtener nuestra Shell.

convertimos nuestro exploit finalizado en texto plano y lanzamos el binario en GDB. hacemos correr el binario cargando el exploit y observamos cómo se crean dos nuevos procesos qué hacen referencia ejecución de una Shell

Solucionando errores :

Ahora nos encontramos con un nuevo problema y es que, si ejecutamos el exploit fuera de GDB, efectivamente se carga el exploit dentro del binario. No obstante, tal como se inicia y se ejecuta el proceso muere.

Como solución a este problema podemos ejecutar el comando que observamos en la captura inferior, este comando lo que está solicitando es que lea el archivo exploit dentro del binario pero que continúe leyendo, lo cual provoca que el proceso no finalice y podamos hacer uso de una Shell.

Ejecucion de la Shell:

Espero este que pequeño tutorial o sea de gran ayuda para la comprensión de cómo funciona un bypass sobre un binario que tiene activo la contramedida No eXécution.

Happy Hack!!!