De 0 a exploiting (IV)

Buenas amigos, soy Fare9, y hoy traigo conmigo la cuarta entrega del curso "De 0 a exploiting", donde veremos paso a paso como va esto del exploiting.

Vamos con los preparativos:
###############################################

Desactivar la aleatoriedad en las direcciones de la pila, para ello debeis ejecutar el comando (como root):
echo 0 > /proc/sys/kernel/randomize_va_space

Compilar (hoy si) con:
gcc -g -fno-stack-protector -z execstack codigo.c -o programa

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

(yo ya tengo hecho un bash para pasar de uno a otro y un bash para compilar).

Hoy vamos a ver el tema de sockets. A través de sockets, en un ordenador, nos conectamos a la red (normalmente utilizando el protocolo TCP/IP). Para usarlos normalmente seguimos una serie de funciones, en este caso si suelen ser funciones específicas de un sistema operativo, aquí dejo las más usuales en C:

Aunque aquí vemos distintas funciones, en realidad las llamadas a la syscall tienen el mismo índice, recordemos del archivo unistd32.h:

#define __NR_socketcall 102

Tras establecer en EAX este valor, seleccionamos en EBX el tipo de llamada, y finalmente en ECX estableceríamos la dirección en la pila de los argumentos(ya no podemos tener un registro por argumento).
Para ver los sockets, veremos como estos funcionan, ya que estos funcionan en un modelo cliente-servidor:
 Como se puede observar, tenemos un servidor, el cual reserva un puerto para empezar a escuchar conexiones, este es un puerto lógico siendo un valor entre 1 y 65535 (64K), tras reservar el puerto, se pone a la escucha hasta que llegue una conexión. 
El cliente entonces realiza una conexión a un servidor con una IP y un puerto, sin contar con firewalls, NIDS ni cualquier otra cosa, tenemos que se ha realizado un establecimiento de la conexión, entonces ambos cliente y servidor tienen un objeto socket por el que pueden pasar streams (flujos) de datos de uno a otro.

Hasta aquí el rollo teórico, bien nosotros queremos algo más, queremos que un atacante esté a la escucha de una víctima al que se le acaba de realizar un ataque a una vulnerabilidad, o queremos que al atacar una vulnerabilidad, dejar en la víctima un puerto a la escucha (lo que llamamos un backdoor). Finalmente tras realizar una conexión queremos obtener una shell, para poder ejecutar comandos, aquí entonces tendremos dos tipos de conexiones Bind Shell y Reverse Shell:
 En esta imagen se muestra un reverse shell, donde es la víctima quien se conecta al atacante, por tanto aunque la victima tenga por delante un router, es posible realizar la conexión, ya que es este quien la comienza.


Bind Shell

Empezaremos explicando Bind Shell, en este caso al explotar la vulnerabilidad, dejamos en la víctima un puerto a la escucha de conexiones. En este caso actúa como servidor y sigue los siguientes pasos:
Primero llamamos a socket, con los valores siguientes, recordemos que los argumentos pasamos primero a la pila, los que están más a la derecha. El primer argumento pasado a socket sería AF_INET con lo que nos referimos a que tenemos que usar el protocolo IPv4, seguido tenemos el argumento SOCK_STREAM para realizar secuenciado de paquetes y añadir fiabilidad, y finalmente tenemos IPPROTO_TCP para que el socket a usar sea TCP en lugar de UDP, finalmente establecer un 0 (como vemos con push %ecx), es para finalizar los argumentos. 
Finalmente hacemos que ecx apunte a la cima de la pila, en este momento y este caso, los argumentos. 
Como vemos al principio metemos en %al el valor 0x66, el cual en hexadecimal es 102, y en bl el valor 0x1, que es el valor de la función socket.
Para acabar realizamos un int $0x80 para ejecutar la función, y metemos en esi lo que devuelve la función socket(), un descriptor del socket (como si fuera un fichero).


Ahora tenemos que "reservar" un puerto para dejarlo a la escucha, para esto rellenamos una estructura del tipo sockaddr, para ello primero insertamos un valor NULL, seguido establecemos el valor del puerto (en hexadecimal) y un valor 0. Tras esto hacemos que ecx apunte a esta estructura por medio de esp. Ahora introducimos los valores de los argumentos en la pila, la longitud en bytes de la estructura (16 bytes), un puntero a la estructura (con ecx) y finalmente el descriptor del socket (con esi), hacemos que ecx apunte a estos argumentos y llamamos a int $0x80, con un valor en eax de 102 y un valor en ebx de 2.

Ya tenemos el puerto, ahora hay que establecerse a la escucha, un servidor puede establecer cuantas conexiones aceptará por un puerto, por tanto en este caso, establecemos que escucharemos por 1 conexión y luego introducimos el descriptor del socket, hacemos que ecx apunte a los parámetros y finalmente realizamos la llamada a Listen.
Una vez estamos a la escucha, debemos hacer que el servidor acepte las conexiones, para ello simplemente necesitamos pasar a la función un único argumento, el descriptor del socket, lo demos podemos establecerlo a NULL. Finalmente llamamos la función accept, y veremos luego que este se queda a la espera, espera que alguien se conecte, y una vez conectado, metemos en ebx el socket de la conexión.

Ahora vamos a realizar un paso, muy sencillo pero bastante interesante, además de en el libro, vi como lo realizaba un troyano, estableceremos los descriptores de ficheros de STDIN, STDOUT y STDERROR en lugar de los normales (teclado y pantalla), los estableceremos al socket, así todo lo que se envíe será tomado como el teclado, y las respuestas en lugar de por pantalla se enviarán por el socket. Para ello vamos a usar la llamada dup2 con la cual podemos reasignar descriptores de ficheros en Linux, en lugar de usar los STD* antes dichos, estableceremos el socket:
Como vemos, realizamos un bucle, para ello metemos el número de rondas en cl muy típico en ensamblador, para llamar a dup2, tenemos en ebx el socket de conexión, y en ecx tendremos los valores 2,1 y 0, con esto al llamar a dup2, modificamos entradas y salidas del programa.
Finalmente llamamos a la shell igual que hicimos en el anterior post en "pusheando cadenas":
Como modificación, añadimos que las variables de entorno estén a NULL.


Si juntamos todo este código en un archivo (por ejemplo bindshell.s) y realizamos lo siguiente:

as bindshell.s -o bindshell.o
ld bindshell.o -o bindshell

Tendremos un programa el cual deje a la escucha un puerto (el 31337), podemos conectarnos a él con una herramienta como "Netcat" o "Ncat":
Tenemos aquí a la espera de conexiones un puerto.
Nos conectamos a la IP donde escucha el bindshell y al puerto, vemos que a partir de ahora podemos ejecutar comandos.

Bien, antes de ver la explotación, veremos el reverse shell y finalmente explotación.

Reverse Shell

En este caso imaginemos que como todos, la víctima tiene un router o tiene un firewall, el cual evita las conexiones desde fuera, por tanto necesitamos que esta vez la víctima sea el cliente, y se conecte a una IP y un puerto.
Esta vez el código será más sencillo, ya que simplemente, creamos el socket y llamamos a connect, finalmente abrimos la shell.

Como vemos, igual que antes,  llamamos a socket para poder obtener un descriptor con el cual realizar las operaciones.
Ahora tenemos algo muy importante, pues debemos realizar una llamada a connect, y necesitamos introducir una IP y un puerto, lo que pasa que en la IP, no podemos establecer nunca un byte 0, por tanto la IP no puede ser algo como "192.168.0.12", así que en mi caso tengo una tarjeta Wifi de red con la IP 192.168.1.32, como vemos insertamos primero la IP (en hexadecimal en Little Endian con los bytes al reves primero el 32 luego el 1 luego el 168 y luego el 192), el puerto al que debe conectarse (en hexadecimal) y luego bx que en este caso tiene un 2 para elegir AF_INET
Hacemos que ecx apunte a esta estructura, y para la llamada a connect, introducimos el tamaño de la estructura, puntero a la estructura y finalmente el descriptor del socket.

Igual que en bindshell, con el socket de conexión en ebx vamos recorriendo los STD*, para pasarlos al socket y finalmente ejecutamos la shell.


Ahora compilamos igual que antes, por ejemplo lo llamaremos al código reverseshell.s y para ejecutarlo antes, necesitamos tener un puerto a la escucha, igual que antes con ncat.
Ncat a la escucha en el puerto 31337, ahora ejecutamos reverseshell.
Al ejecutar reverseshell obtenemos una shell, que va por el socket.



Explotación

Antes de nada tenemos que crear un programa ejemplo2.c , donde poder introducir más caracteres que antes, ya que los opcodes actuales pues son bastantes más, dejo aquí el código:
Como vemos ahora tenemos un buffer de 128 bytes.

Para sacar cuantos bytes son necesarios para llegar a EIP, podemos usar igual que antes Pattern Petater, el cual nos dicen que son 144 bytes (pero animo a probarlo), y luego usando edb debugger, obtenemos la dirección de retorno.

Ahora tenemos un problema, y es que el código antes visto... No vale, ya que al introducir datos en la pila, y estar el código en la pila, los datos van sobreescribiendo el código, por tanto necesitamos código que vaya aumentando la pila. Aquí voy a dejar el código y luego preparamos el exploit típico de Python:

bindshell2.s


reverseshell2.s
 


Exploit

Una vez finalizado, compilamos y obtenemos los opcodes con Objdump, y lo insertamos finalmente todo en un exploit:

Con el bindshell, nos conectaremos con Ncat y con reverseshell, dejaremos un puerto a la escucha.


Bueno hasta aquí el tema de sockets, hemos creado dos shellcodes bastante extensos, pero vamos aprendiendo poco a poco, como crear nuevas formas de shells y nuevos exploits en otros programas.

--------------------------------------------------------FIN

Share this

Related Posts

Previous
Next Post »