Aprende a escribir un troyano 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 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.

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 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.

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 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.

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:

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:

  1. Que la conexión siempre será exitosa
  2. Que el servidor siempre enviará una respuesta
  3. 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
  • as

    Hola, excelente trabajo! Una pregunta, con esta versión se pueden ejecutar comandos como "cd" "mkdir" y esos? Si no es así, como se podría evitar que el script se tilde?

    • Gracias! Así es con ésa versión se pueden ejecutar cualquier tipo de comandos.

      No entiendo a que te refieres con "evitar que el script se tilde", podrías elaborar más? Gracias!

      • ale

        Me gustaría poder enviarle todo tipo de órdenes, como 'cd ..', 'mkdir algo', etc.
        Si le mando "cd" para que vuelva al directorio principal se cierra el programa porque no comprende la orden y la conexión queda abierta, entonces tengo que cambiar de puerto.

        • El programa como esta escrito en este momento, va a exhibir ese comportamiento. Tendrias que cambiar algunas cosas para que la conexion se quede abierta "permanentemente". Si no tienes prisa, este fin de semana puedo escribir una version diferente del mismo programa que mantenga la conexion abierta, y actualizar el post.

          • ale

            Al final lo pude hacer andar, lo que hice fue detectar si escribo "cd ..." para usar el módulo "os" y cambiar de directorio, entre otras cosas mas!
            Pero si haces el post lo miro con gusto!
            Saludos!

          • Florentino Messi

            hola al final conseguistes actualizarlo me interesa hacer uno propio empezar con lo que hicistes y ponerle mas cosas un salu2

  • Florentino Messi

    obtengo el siguiente error:
    Traceback (most recent call last):
    File "", line 1, in
    File "cliente.py", line 7, in
    cliente.connect((servidor, puerto))
    File "C:Python27libsocket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
    socket.error: [Errno 10061] Aucune connexion nÆa pu Ûtre Útablie car lÆordinateu
    r cible lÆa expressÚment refusÚe

  • Florentino Messi

    ahora obtengo este error :
    Traceback (most recent call last):
    File "", line 1, in
    File "cliente.py", line 9, in
    respuesta = cliente.recv(4096)
    socket.error: [Errno 10054] Une connexion existante a d¹ Ûtre fermÚe par lÆh¶te
    distant
    >>>
    respuesta para que esta dando problemas

  • Florentino Messi

    vale esta linea da error print "[*] Conexion establecida con %s:%d" % (direccion[0], direccion[2])

  • Florentino Messi

    alan chavez como te puedo contactar quiero comentarte algunas cosas sobre pyhook y sobre algunas basicas porque el pyhook siempre me saca del while gracias espero contactar contigo increible blog impresionante

  • Florentino Messi

    podrias poner un ejemplo del pyhook sin que el cliente se cierre???

  • Florentino Messi

    alan podrias explicar sobre como implementar pyhook en tu troyano en python para agregarle un keylogger basico. Esque muchas veces me da conflicto porque el hook me saca del bucle o cuando hago en un thread aparte no me captura las letras. Mm, hay alguna manera de hacerlo para que el cliente mande las teclas al servidor sin que el cliente se cierre o no falle.

A %d blogueros les gusta esto: