
API de Python para usar con la especificación External C2 de Cobalt Strike
Framework en Python para construir y utilizar interfaces que transfieran datos entre frameworks, con un enfoque específico en servir como extensión para frameworks de Comando y Control.
Actualmente, esto solo está pensado como una implementación de la especificación External C2 de Cobalt Strike, tal como se describe en este documento de especificación, pero está sujeto a cambios a medida que el proyecto madure.
Un enorme agradecimiento a xychix. Este proyecto no habría sido posible en absoluto sin sus valiosas contribuciones. Básicamente, este proyecto es una reconstrucción y extensión del proyecto External C2 de Outflank
Este proyecto consta de las siguientes partes principales:
El constructor lee un archivo de configuración y utiliza las opciones configuradas para generar una construcción reemplazando marcadores dentro de los esqueletos.
El constructor se puede usar con build_files.py. Se proporciona una configuración de constructor de ejemplo como sample_builder_config.config.sample.
Los esqueletos son los diferentes "esqueletos" de código que el constructor poblará dinámicamente para generar una construcción completamente utilizable. Los esqueletos contienen marcadores que serán reemplazados con valores utilizables por el constructor. Hay tres 'tipos' diferentes de esqueletos:
Un marcador se puede colocar dentro de cualquier archivo en un esqueleto, y será reemplazado con un valor especificado en la configuración del constructor. Como buena práctica, los marcadores nunca deben usarse para escribir directamente variables, y solo deben usarse para establecer valores. Si el valor de un marcador debe reutilizarse, se debe optar por almacenar el valor en una variable y referenciarlo de esa manera, en lugar de reutilizar el mismo marcador.
El formato del marcador es: ```[var:::identificador_del_marcador]```
Las cadenas se escribirán en un esqueleto directamente envueltas en comillas simples, y los números se escribirán tal cual.
En caso de que una cadena en la configuración esté envuelta en comillas dobles, la cadena se escribirá directamente en el archivo envuelta en comillas dobles, y las comillas simples envolventes se eliminarán.
Esta relación se puede demostrar como:
#################
# Código del esqueleto #
#################
# El esqueleto contiene la siguiente línea de código:
foo = ```[var:::bar]```
###########
# Resultado final #
###########
# Almacenado en la configuración como:
# foo = bar
# Escrito como:
foo = 'bar'
# Almacenado en la configuración como:
# foo = "bar"
# Escrito como:
foo = "bar"
# Almacenado en la configuración como:
# foo = 2
# Escrito como:
foo = 2
# Almacenado en la configuración como:
# foo = "2"
# Escrito como:
foo = "2"
Los frameworks son la aplicación base que determina qué datos están siendo utilizados por el transporte y el codificador, y cómo se utilizan esos datos. Lo que realmente haga un framework específico no importa, siempre que exista lógica para importar y usar el codificador y el transporte. La mayoría de las partes esenciales de un framework (principalmente la lógica del cliente) se almacenarán como un esqueleto, con una interfaz para interactuar con la parte del servidor almacenada como un objeto framework base.
Generalmente, un framework contiene un servidor y un cliente, y hace uso de codificadores y transportes para retransmitir datos entre ellos.
Hay algunos fundamentos a considerar al construir un framework:
framework es responsable de asegurar que el codificador esté disponible para que el transporte lo utilice.framework utiliza una relación cliente-servidor, deben organizarse apropiadamente como tal.cliente de un framework, por lo que si se desea que las cosas sean reconfigurables en el cliente, debe poder hacerlo durante la ejecución sin interacción directa.esqueleto de servidor porque el usuario final va a interactuar directamente con el servidor de un framework. En su lugar, se debe optar tanto por leer las opciones de una configuración como por dar al usuario final la capacidad de modificar opciones (como un temporizador de bloque o la verbosidad) durante la ejecución.esqueleto de framework será procesado por el constructor, iterando a través de cada archivo en él, por lo que si un argumento determinado necesita ser configurable en el momento de la construcción, se puede hacer fácilmente.servidor de framework debería poder ser interfazado por un gestor_de_framework común.El servidor es la aplicación que gestiona la comunicación entre el cliente y el servidor C2. La lógica del servidor es principalmente estática. La lógica del servidor para el framework cobalt_strike, denominado Controlador de Cliente de Terceros dentro de la especificación, se muestra a continuación:
codificadortransportetransportecodificadortransportetransportecodificadorUn servidor debe soportar la capacidad de manejar múltiples clientes (aún no implementado), y ser interfazado por un gestor_de_framework.
El cliente es esencialmente el payload que se ejecuta en el endpoint. La lógica del cliente para el framework cobalt_strike es principalmente estática y se muestra a continuación:
transportetransportetransporte en busca de nuevas tareastransporteEl cliente utiliza el codificador y el transporte especificados para retransmitir datos entre sí mismo y su servidor respectivo.
Los codificadores reciben datos, luego modifican esos datos para prepararlos para su uso al ser enviados a través del transporte, o decodifican datos recibidos a través del transporte de vuelta a su forma original para ser interpretados por el componente del framework que los esté utilizando.
Los codificadores deben esperar ser interfazados directamente por el transporte, y manejar los datos de manera independiente del framework y del componente.
Los transportes tienen el rol de enviar y recibir datos a través de un canal de comunicación, e interactuar con el codificador para asegurar que los datos se transformen al formato necesario. Los transportes deben esperar recibir datos de un componente del framework, o a través del canal de comunicación, y tener la capacidad de retransmitir datos a través del canal de comunicación. Los transportes son responsables de llamar al codificador para codificar o decodificar datos según sea necesario.
Los transportes deben esperar ser interfazados directamente por el componente del framework, y manejar los datos de manera independiente del framework y del componente.
Primero, determina qué módulo de transporte y codificación deseas usar. Usaremos transport_imgur y encoder_lsbjpg para el siguiente ejemplo.
A continuación, crea un builder_config.config que se adapte a tus necesidades, consulta la configuración de ejemplo y la plantilla proporcionadas para orientación sobre cómo hacerlo.
Genera una construcción con build_files.py. Como ejemplo, se generaría una construcción en el directorio builds usando encoder_lsbjpg y transport_imgur para Cobalt Strike, de manera verbosa, con el siguiente comando:
python build_files.py -b builds -f cobalt_strike -c sample_builder_config.config.sample -e encoder_lsbjpg -t transport_imgur -v
En la máquina que ejecuta el servidor, ejecuta:
python server.py
Para una salida más verbosa, puedes ejecutar:
python server.py -v
Para una salida más verbosa y adicional útil para depuración, puedes ejecutar:
python server.py -d
A continuación, ejecuta el cliente en el endpoint objetivo.
Si todo funcionó, se registrará un nuevo beacon en la consola de Cobalt Strike con el que podrás interactuar.
¿Por qué escribiste esto?: No había muchas implementaciones publicadas de la especificación de Cobalt Strike, y de las que están publicadas, o no están en un lenguaje que conozco o no tienen la modularidad y abstracción que buscaba.
¿Por qué Python 2?: Soy flojo y es fácil implementar nuevos canales de transporte y codificación en él.
Tu código apesta: Eso no es una pregunta.
¿Puedo enviar nuevos módulos de transporte y/o codificador?: ¡Por favor, sí! Envía un pull request y estaré encantado de revisarlo.
¿Cómo compilo el cliente en un ejecutable que pueda distribuir?:
He probado esto con éxito en Kali. Para recrear mi entorno, asegúrate de tener veil-evasion instalado y de haber ejecutado su configuración. Debería haber configurado un entorno wine con Python instalado que tenga todas las dependencias que necesitas. Es posible que también tengas que instalar el módulo pefile en este entorno.
Luego, puedes ir al directorio del cliente para el cual deseas generar un ejecutable y ejecutar:
chmod +x compile_dll.sh
./compile_dll.sh
wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -w --key "ayyyyyyylmao" client.py
Reemplaza el valor de key con el que desees. Deberías ver el ejecutable del cliente en el directorio dist/. Si deseas generar un ejecutable que proporcione una consola que puedas usar para depuración, compila el ejecutable con wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -c client.py.