
Implante de canal encubierto DNS para Equipos Rojos.
WEASEL es un pequeño implante en memoria que utiliza Python 3 sin dependencias. El cliente baliza envía una pequeña cantidad de información identificativa sobre su host a una zona DNS que usted controla. El servidor WEASEL puede asignar a los clientes la ejecución de comandos predefinidos o arbitrarios.
WEASEL es un payload de etapa 1, diseñado para ser difícil de detectar y útil para recuperar el acceso cuando sus ruidosas etapas completas son detectadas.
Estado
Consulte el uso en el README del cliente y el README del servidor para instrucciones específicas.
Para iniciar el servidor o el cliente, ejecute los scripts directamente o pásalos al intérprete de Python.
Asegúrese de que cada dominio C2 tenga un registro NS con la dirección IP del host que ejecuta server.py.
WEASEL requiere Python 3.6+.
El cliente es autónomo y solo utiliza bibliotecas estándar, por lo tanto, puede ejecutarse en macOS, Linux, etc.
El servidor tiene algunas dependencias de pip, incluidas en requirements.txt del servidor. El servidor debe ejecutarse en Linux, pero nada impide que se ejecute en macOS u otros *nix.
No es necesario ofuscar y minimizar la baliza en este caso. Las declaraciones de impresión se conservan. Al igual que en el Uso anterior, asegúrese de que los registros NS del dominio(s) en servidores en beacon.py apunten a la dirección IP de server.py.
En el host del servidor:
sudo python3 server.py
En el host de la víctima (puede ser el mismo que el servidor):
python3 beacon.py
No necesita entender nada de esto para usar WEASEL.
La baliza se comunica a través de DNS utilizando consultas y respuestas AAAA. No utiliza registros TXT debido a que son conocidos por ser utilizados por malware y túneles DNS. Los equipos azules suelen tener detecciones de túneles DNS que alertan sobre consultas TXT grandes.
El lado del cliente no necesita root para funcionar, no utiliza sockets sin procesar y no crea paquetes DNS malformados. Utiliza interfaces regulares del sistema y del lenguaje para realizar solicitudes DNS. La información está codificada y cifrada en los propios registros.
Esta baliza está diseñada para ser lenta y discreta, con poco ancho de banda. Debe decirnos en qué hosts se encuentra y darnos una manera de lanzar más etapas según sea necesario, y nada más. Aunque tiene soporte para comandos arbitrarios, no está destinada a usarse como un shell interactivo regular o canal de comunicaciones.
WEASEL es una etapa 1 que dejas ejecutando, asegurando acceso continuo mientras tus etapas completas (y por lo tanto más ruidosas) son detectadas.
WEASEL fue inicialmente dirigido a servidores de alta disponibilidad donde teníamos un vector de punto de apoyo/explotación confiable. Evadir el análisis forense era una alta prioridad. Como resultado, no tiene características de persistencia nativas.
Puedes hacerlo persistente agregando su ejecución a tu técnica de persistencia favorita, lo cual se deja como ejercicio para el lector :)
Una solicitud (del cliente) es una única consulta AAAA para un nombre formateado como:
<preamble><data>.<stream>.<session>.domain.tld
El preámbulo tiene 2 bytes. Preámbulo[0] es el número de secuencia de ese paquete. Preámbulo[1] es el número total de paquetes en esa secuencia.
Los datos están limitados a 50 bytes (configurable) y contienen la carga útil. La carga útil está codificada en base32 con un alfabeto personalizado.
Primero, todos los caracteres 'w' se intercambian por '-'.
Luego, el carácter de relleno se intercambia de '=' a 'w' para ajustarse al conjunto de caracteres DNS: [a-z0-9] y [-].
No intercambiamos '=' con '-' directamente porque el relleno siempre estará al final de la cadena, y terminar un nombre de host con '---' es sospechoso y va en contra de la RFC de DNS. De esta manera, cuando una cadena tiene relleno, terminará en 'www', lo cual es menos sospechoso y cumple con la RFC.
Una respuesta (del servidor) está compuesta por una o más respuestas AAAA.
Cada respuesta AAAA es una carga útil cifrada de 16 bytes representada como una dirección IPv6 usando socket.inet_ntop. Las respuestas en una respuesta DNS no mantienen su orden en tránsito, por lo que se secuencian y reensamblan como las solicitudes del cliente.
La carga útil de transporte es una cadena de elementos de datos separados por un carácter ^.
Las solicitudes y respuestas siguen este formato:
<type>|<data>
Las sesiones son de larga duración: un cliente inicia una sesión cuando la baliza se ejecuta por primera vez y esa sesión debe durar todo el tiempo que la baliza esté activa en ese cliente. Tenga en cuenta que, dado que la baliza está en memoria y no es persistente, los datos de la sesión se almacenan en la memoria de ese proceso de Python. Cualquier nueva invocación de la baliza iniciará una nueva sesión.
Iniciar una sesión implica que el cliente elabore un mensaje con un preámbulo no de datos que identifique de manera única (para señalar al servidor que esta es una nueva sesión): la concatenación de una clave pública Diffie-Hellman de 32 bytes y un IV AES aleatorio de 16 bytes.
El servidor recibe esto y responde con su propia clave pública de 32 bytes. En este punto, el cliente y el servidor han establecido una clave de sesión compartida que se utilizará durante toda la vida de esta sesión para cifrar las cargas útiles de datos usando AES-128 en modo CTR. El intercambio Diffie-Hellman Efímero asegura que cada conexión cliente-servidor use una clave de sesión única con secreto hacia adelante.
El cifrado es deliberadamente malo por varias razones:
Aquí hay algunos problemas conocidos con el esquema criptográfico:
p es RFC 3526 Grupo 5 truncado a los primeros 32 bytes. Esto no solo limita las claves públicas y privadas a 32 bytes, sino que el Grupo 5 ya está obsoleto y se recomienda no usarlo. Llamo a esta mala decisión "Grupo 1".a en lugar de un CSPRNG.Cada mensaje enviado entre un cliente y un servidor debe dividirse en paquetes de máximo 50 bytes, para mantenerse por debajo del límite de 52 bytes para detecciones comunes de canales encubiertos DNS. Todos los paquetes de un mensaje particular son parte del mismo flujo. Mensaje == Flujo.
Los flujos se identifican con un número hexadecimal aleatorio de 2 bytes. Recuerde que el formato de solicitud del cliente es:
<preamble><data>.<stream>.<session>.domain.tld
El preámbulo de 2 bytes de cada paquete en el flujo tiene un número de secuencia y el número total de paquetes en ese flujo. Esto permite que el servidor sepa cuándo ha llegado todo.
Debido a que esto es DNS, lo hacemos todo sobre UDP, que no proporciona ninguna garantía sobre el orden en que llegarán los datagramas. Por eso WEASEL tiene que tener en cuenta la secuenciación, el reensamblaje y el seguimiento de múltiples flujos de muchas balizas.
Cada flujo reinicia un cifrador AES-128-CTR compartido globalmente para cifrar/descifrar cargas útiles.
La carga útil solo se puede descifrar una vez que el flujo está completo (todos los paquetes han llegado). Si no fuera por base32, podríamos descifrar lo que tenemos del mensaje incluso si nos faltaran paquetes (porque AES-CTR es un cifrador de flujo), pero no podemos decodificar base32 en flujos parciales. Bueno. Por la naturaleza de los clientes DNS, las solicitudes se realizan varias veces (generalmente 2 o 4 veces) hasta que se recibe una respuesta, por lo que tenemos una buena probabilidad de recibir todos los paquetes en un flujo, ya que cada paquete debe ser enviado por el cliente al menos dos veces. Si perdemos paquetes o flujos, no es un gran problema, la baliza se registrará nuevamente más tarde y probablemente tendrá mejor suerte entonces.
Consulte el archivo CONTRIBUTING para saber cómo ayudar.
WEASEL tiene licencia MIT, como se encuentra en el archivo LICENSE.
| Type | Significado (remitente) | AKA | Datos |
|---|
| 0 | Reconocido | ACK | Hex aleatorio |
| 1 | Registrándose (cliente) | PING | Hex aleatorio |
| 2 | Termínate a ti mismo (servidor), terminándome a mí mismo (cliente) | FIN | |
| 3 | Mensaje de inicialización (cliente) | SYN | `version |
| 4 | Reconectar (servidor) | RST | |
| 5 | Establecer intervalo de devolución de llamada (servidor) | segundos | |
| 6 | Obtener datos de interfaz de red | eth0 1.2.3.4/24\neth1 fe80:::/64\n... | |
| 8 | Evaluar código Python3 arbitrario hasta 666 bytes (servidor), devolviendo los primeros 400 bytes de salida (cliente) | EVAL | script de una línea python3 |
| 9 | Ejecutar comando arbitrario hasta 666 bytes (servidor), devolviendo los primeros 400 bytes de salida (cliente) | EXEC | comando bash |