¿Quieres aprender C?
Toma mi curso de Introducción a C completamente GRATIS!
Antes de empezar el tutorial, asegúrate que tienes instalado Python 2.7.x, en otras versiones puede que el código funcione no funcione sin modificaciones.
De una vez te aviso que é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 intérprete a todos los usuarios.
Esto es un grave error, aunque muchas veces inevitable, ya que en Python puedes escribir rápidamente escáneres de puertos, troyanos, key loggers, 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 que estás a punto de aprender a programar, es una aplicación de administración remota (RAT) que es uno de los diferentes tipos de Troyanos que existen actualmente.
Una RAT, en palabras muy sencillas, es una aplicación que te permite mantener acceso remoto a un sistema a través de una red como el Internet.
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 un troyano rudimentario.
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 espera conexiones entrantes por parte de navegadores de Internet (Chrome, Firefox, Safari, Internet Explorer, etc...) para enviar contenido.
El navegador en el escenario anterior, se comporta como una aplicación del tipo cliente, es decir: 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 abusar del sistema.
Sin mayor preámbulo, vayamos al código.
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.
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 o HTTPS. 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, está fuera del ámbito de éste tutorial indagar más en ese tema.
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 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 simultáneas 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 objeto 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.
Si corremos el script de python en la terminal, observarás que el script empezará a esperar clientes en el puerto que elegiste:
Cliente TCP
Ya que tenemos un servidor funcional, ahora escribamos el cliente que se conectará al servidor y le enviará instrucciones:
Como podrás darte cuenta, el cliente es mucho más sencillo 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:
$ python cliente.py ACK
El servidor respondió con la palabra 'ACK', si observas la ventana donde el servidor se estaba ejecutando, observarás el siguiente texto:
[*] Conexion establecida con 127.0.0.1:54209 [*] Mensaje recibido: Hola mundo
Si te das cuenta, el puerto que se muestra después de la dirección IP, es el puerto que el sistema operativo del cliente utilizó para establecer la conexión con el servidor. Éste puerto no es necesariamente el mismo puerto que el servidor utiliza para aceptar conexiones.
También, como podrás darte cuenta, es relativamente sencillo modificar el servidor para que ejecute comandos remotamente. Por ejemplo, si modificas el servidor para que ejecute comandos y regrese la salida de ese comando, puedes empezar a construir una RAT bastante rudimentaria:
$ python cliente_maligno.py Applications Backups Desktop Documents Downloads