Ver Mensaje Individual
  #1 (permalink)  
Antiguo 23/01/2015, 20:37
AlanChavez
 
Fecha de Ingreso: junio-2010
Ubicación: Charlotte, NC
Mensajes: 611
Antigüedad: 13 años, 10 meses
Puntos: 95
[APORTE] Cimientos de una RAT en Python

En éste tutorial, te voy a enseñar a como escribir los cimientos de una herramienta de administración remota (RAT) en Python.

Antes de empezar el tutorial, asegúrate que tienes instalado Python 2.7.x, en otras versiones puede que el código funcione o puede que no. Éste código no va a funcionar en Python 3. Así que si tienes otra versión de Python, y el código aquí mostrado no funciona, no me reclames que no te lo advertí.

Primero que nada quiero aclarar que Python es el lenguaje dominante en el campo de la seguridad, y por una buena razón:

Supongamos que ya comprometiste un servidor, y piensas utilizar éste servidor para escanear una red privada. Sin embargo éste servidor no tiene herramientas como netcat instaladas, ni tampoco puedes instalarlas, peor aún, tampoco tienes compiladores disponibles, y la conexión al exterior se encuentra sumamente restringida.

Éste escenario suena como cualquier otro día en la vida de un profesional de la seguridad tratando de penetrar un servidor Enterprise administrado por alguien que sabe lo que hace. No obstante, la gran mayoría de las ocasiones, muchos administradores pasan por alto la versatilidad y poder de Python, y dejan accessible el interprete a todos los usuarios.

Esto es un grave error, aunque muchas veces inevitable, ya que en Python puedes escribir rápidamente escaneres de puertos, troyanos, keyloggers, fuzzers y un sin fin de herramientas que facilitarán penetrar otros servidores, descubrir nuevas vulnerabilidades e incluso mantener el acceso a un servidor comprometido.

La aplicación más básica en redes computacionales es una aplicación cliente-servidor. La aplicación que te mostraré a continuación te servirá como cimiento para crear una herramienta de administración remota o RAT (Remote Administration Tool) rudimentaria.

Un aplicación de tipo servidor, es una aplicación que abre un puerto y espera instrucciones de los clientes que se conecten a éste puerto. Por ejemplo, un servidor web, es una aplicación que normalmente, abre el puerto 80 en una computadora, y un navegador de Internet (Chrome, Firefox, Safari, Internet Explorer, etc...) es una aplicación del tipo cliente, que se comunica con el servidor para intercambiar información.

En el caso de usuarios maliciosos, una aplicación servidor abre un puerto en una computadora para esperar instrucciones de algún cliente que se conecte, y le envíe instrucciones para realizar determinadas tareas, como extraer o borrar archivos.

El módulo que vamos a explorar es "socket". Éste modulo contiene todas las instrucciones y métodos necesarios para construir una herramienta de administración remota e incluso un troyano rudimentario.

Sin mayor preámbulo, vayamos al código.
Código python:
Ver original
  1. # servidor.py
  2. import socket
  3. import threading
  4.  
  5.  
  6. def administrar_clientes(socket_cliente):
  7.     peticion = socket_cliente.recv(1024)
  8.     print "[*] Mensaje recibido: %s" % peticion
  9.     socket_cliente.send("ACK")
  10.     socket_cliente.close()
  11.  
  12. ip = "0.0.0.0"
  13. puerto = 39421
  14. max_conexiones = 5
  15. servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  16.  
  17. servidor.bind((ip, puerto))
  18. servidor.listen(max_conexiones)
  19.  
  20. print "[*] Esperando conexiones en %s:%d" % (ip, puerto)
  21.  
  22. while True:
  23.     cliente, direccion = servidor.accept()
  24.     print "[*] Conexion establecida con %s:%d" % (direccion[0], direccion[2])
  25.     administrador_de_clientes = threading.Thread(target=administrar_clientes, args=(cliente,))
  26.     administrador_de_clientes.start()

Explicación
Primero empezamos importando las librerías que vamos a utilizar: socket y threading.

Una aplicación de tipo servidor necesita utilizar hilos, ya que de lo contrario, solamente se podría atender un cliente al mismo tiempo. Es por eso que estamos importando la librería para crear hilos (threads).

De momento vamos a ignorar la definición de la función "administrar_clientes", ya que en Python es imposible definir una función después de llamarla.

Posteriormente indicamos que la dirección "IP" en la que esperamos recibir conexiones es la IP 0.0.0.0 y en el puerto 39421 seguido del número máximo de conexiones que nuestro servidor va a recibir.

OJO: En éste contexto la dirección 0.0.0.0 tiene un significado especial. Esta fuera del ámbito de éste artículo explicar el significado de 0.0.0.0, pero si quieres aprender más, lee mi artículo "La diferencia entre 0.0.0.0 y 127.0.0.1"

El número del puerto es un número arbitrario, puedes utilizar el que se te antoje. Tradicionalmente los puertos del 1 al 1024 son puertos reservados para protocolos ya establecidos como FTP, SSH, HTTP, HTTPS, entre otros. Solamente un usuario con privilegios especiales puede abrir esos puertos.

Incluso hay una serie de reglas y buenas prácticas que se deben seguir para determinar que puerto abrir, pero nuevamente, esta fuera del ámbito de éste tutorial indagar más en ese tema. Si quieres conocer más acerca de ése tema, te invito a que leas mi artículo "Puertos bien conocidos, puertos registrados y puertos efímeros"

Después de inicializar las variables que vamos a utilizar, tenemos que indicarle a Python que espere conexiones utilizando esos parámetros, y eso lo hacemos llamando al método socket() donde usamos la constante socket.AF_INET para indicarle a python que estamos utilizando el protocolo IPv4 y la constante socket.SOCK_STREAM para indicarle a python el tipo de socket que estamos creando.

Inmediatamente después de inicializar crear el socket, enlazamos el socket con la dirección IP y el puerto que vamos a utilizar, y le indicamos al socket que solamente aceptará 5 conexiones simultaneas como máximo. Posteriormente le mostramos al usuario que el servidor ha sido inicializado, y que estamos en espera de conexiones.

Finalmente, como necesitamos que el servidor siempre esté corriendo, tenemos que aceptar las conexiones en un bucle infinito. De lo contrario, el programa terminaría su ejecución, el puerto se liberaría y no podríamos establecer una conexión con el servidor.

Dentro del bucle infinito, te darás cuesta que estamos creando un nuevo hilo por conexión aceptada, y cuando llamamos al método que se encarga de crear el hilo, pasamos como argumento la función que vamos a hilar, junto con los argumentos de ésta función (si es que tiene). En este caso, estamos llamando a la función "administrar_clientes" y le pasamos el argumento del cliente conectado.

El cliente conectado, no es otra cosa mas que una copia del objecto socket que puede ser utilizado para enviar y recibir datos. Dentro de la función "administrar_clientes", llamamos al método "recv(1024)" que nos permite recibir información que el cliente envía. El argumento que "recv()" recibe, es el tamaño del buffer. Idealmente, para maximizar la compatibilidad con diferentes tipos de hardware, el tamaño del buffer debe ser una potencia pequeña de 2 como 1024 o 4096.

Después de recibir el mensaje del cliente, en este ejemplo, solamente le informamos al cliente que recibimos su instrucción y cerramos la conexión.

Nota: En éste ejemplo estamos enviamos la palabra 'ACK'; pero en este contexto 'ACK' puede ser cualquier cosa; no obstante, en otras aplicaciones 'ACK' se utiliza para confirmar que los datos fueron recibidos correctamente.

Si corremos el script de python en la terminal, observarás que el script empezará a esperar clientes en el puerto que elegiste.

Código bash:
Ver original
  1. $ python servidor.py[*] Esperando conexiones en 0.0.0.0:39421

Cliente TCP

Ya que tenemos un servidor funcional, ahora escribamos el cliente que se conectará al servidor y le enviará instrucciones:

Código python:
Ver original
  1. # cliente.py
  2. import socket
  3.  
  4. servidor = "127.0.0.1"
  5. puerto = 39421
  6.  
  7. cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  8. cliente.connect((servidor, puerto))
  9. cliente.send("Hola mundo");
  10. respuesta = cliente.recv(4096)
  11. print respuesta

Como puedes ver, la aplicación cliente, es mucho más sencilla que el servidor, volver a explicar linea por linea el código es redundante, ya que no estamos utilizando ninguna instrucción diferente o nueva.

Cabe resaltar que en ésta aplicación, estoy asumiendo tres cosas:
  • Que la conexión siempre será exitosa
  • Que el servidor siempre enviará una respuesta
  • Que el servidor enviará la respuesta relativamente rápido
Generalmente, cuando estas escribiendo scripts para mantener acceso o ejecutar instrucciones remotas, es recomendable asumir que esas condiciones siempre serán verdaderas para progresar rápido. No estamos tratando de crear una herramienta robusta, sino una herramienta que nos permita hacer cosas rápidamente.

Si dejaste el primer script corriendo, ahora ejecuta éste script en otra terminal y observa lo que sucede:

Código shell:
Ver original
  1. $ python cliente.py
  2. ACK

El servidor respondió con la palabra 'ACK', si observas la ventana donde el servidor se estaba ejecutando, observarás el siguiente texto:

Código shell:
Ver original
  1. [*] Conexion establecida con 127.0.0.1:54209
  2. [*] Mensaje recibido: Hola mundo