next   previous   contents

Analisi dei pacchetti catturati e validazione delle sequenze

 Come gia' esposto in precedenza, ogni pacchetto catturato dalla funzione pcap_next, viene passato a proc_packet, perche' esso venga analizzato, traendo dagli header informazioni quali l'indirizzo IP sorgente e la porta di destinazione, necessarie per procedere al riconoscimento od all'invalidazione delle sequenze di knock.
 La funzione in questione viene invocata passandole i seguenti tre parametri : char *packet, int offset, struct pcap_pkthdr* hdr, ovvero il pacchetto appena ricevuto, l'offset specifico per il tipo di link layer utilizzato, e l'indirizzo della struttura pcap_pkthdr ove sono memorizzate le informazioni di base sul pacchetto, come ad esempio il timestamp del momento del momento della cattura.
 Essendo noto lo scostamento del pacchetto IP all'interno del frame con cui viene ricevuto, vi si fa puntare struct ipv4_hdr *ip, in modo da accedere alla struttura dati che rappresenta il pacchetto tramite il puntatore ip.
 I primi controlli prevedono la verifica della versione del pacchetto IP, poiche' non e' ancora supportato il protocollo IPv6, e dell'indirizzo di destinazione, per ignorare i pacchetti non diretti all'interfaccia sulla quale servknock e' in ascolto. Poiche' l'interfaccia di rete utilizzata non lavora in modalita' promiscua, si potrebbe ritenere inutile questo controllo, ma si immagini ad esempio il caso in cui arrivi un pacchetto di broadcast.
 In seguito viene memorizzato il timestamp del momento della cattura del pacchetto, e convertito in data e ora, per poterle utilizzare nelle entries del file di log generato dal programma. Tale conversione viene compiuta ricorrendo alla funzione localtime(), che memorizza i dati in una struct di tipo tm, definita in bits/time.h .

ip = (struct ipv4_hdr *) (packet+offset);

/*check ip version, if not ipv4 ignore*/
if(ip->ip_v != 4)
 return;

/*if we aren't the destination ip, ignore the packet*/
strncpy(IPSrc, inet_ntoa(ip->ip_src),sizeof(IPSrc) );
strncpy(IPDst, inet_ntoa(ip->ip_dst),sizeof(IPSrc) );
if (strcmp(IPDst, IfaceIp))
 return;

/* timestamp*/
pkt_secs = hdr->ts.tv_sec;
pkt_tm = localtime(&pkt_secs);
snprintf(pkt_date, 11, "%04d-%02d-%02d", pkt_tm->tm_year+1900, pkt_tm->tm_mon, pkt_tm->tm_mday);
snprintf(pkt_time, 9, "%02d:%02d:%02d", pkt_tm->tm_hour, pkt_tm->tm_min, pkt_tm->tm_sec);

 In Module Knock invece, ogni volta che un pacchetto TCP, UPD o ICMP viene catturato, ed il rispettivo decoder viene applicato con successo, e' invocata la funzione del modulo KnockProcFunc, alla quale e' passato il parametro PacketSlot, un intero indicante la posizione del pacchetto all'interno del vettore di elementi PacketRec.
 Per prima cosa vengono recuperate le informazioni dell'header IP, per mezzo della funzione GetDataByID, alla quale vengono passati tre parametri: int PacketSlot, la posizione del pacchetto all'interno del vettore, int DecoderID, l'identificatore numerico del decoder IP e void** IData, puntatore per mezzo del quale si accedera' alla struttura che rappresenta l'header IP. Come per servknock viene effettuato il controllo della versione del pacchetto IP e la memorizzazione del timestamp del momento di cattura del pacchetto.

p=&Globals.Packets[PacketSlot];

/*check ip version, if notipv4 ignore*/
if (!GetDataByID(PacketSlot, IPDecoderID, (void**)&IData))
{
  #ifdef DEBUG
  printf("Couldn't get IP Header\n");
  #endif
  return;
}
if (IData->Header->version != 4)
  return;

/*make sure this packet was sent to us, not sent by us*/
strcpy(IPSrc, inet_ntoa(*(struct in_addr*)&IData->Header->saddr) );
strcpy(IPDst, inet_ntoa(*(struct in_addr*)&IData->Header->daddr) );
if(strcmp(IPDst, IfaceIp))
  return;

/* timestamp*/
pkt_secs = p->tv.tv_sec;
pkt_tm = localtime(&pkt_secs);
snprintf(pkt_date, 11, "%04d-%02d-%02d", pkt_tm->tm_year+1900, pkt_tm->tm_mon,pkt_tm->tm_mday);
snprintf(pkt_time, 9, "%02d:%02d:%02d", pkt_tm->tm_hour, pkt_tm->tm_min, pkt_tm->tm_sec);

 Superati questi primi due check, viene controllato il protocollo di livello superiore del pacchetto incapsulato all'interno del datagramma IP. Valori ammessi per gli elementi della sequenza di knock sono IPPROTO_ICMP, IPPROTO_TCP ed IPPROTO_UDP. Se il valore in ip->ip_p, non corrisponde a nessuno dei tre previsti, il pacchetto viene ignorato.
 Nel caso dei pacchetti TCP ed UDP si accede inoltre al loro header tramite i puntatori struct tcp_hdr *tcp e struct udp_hdr *udp, per estrarre il numero di porta di destinazione, verificare che esso rientri tra i range monitorati da servknock e trasformarlo con la funzione demap in numero compreso tra 1 e 255, per poterlo poi utilizzare nella operazione di decifratura.  Se il valore di Dport non fosse compreso negli intervalli previsti il pacchetto verrebbe ignorato, senza interferire con il riconoscimento delle sequenze in corso.

/*check protocol*/
switch(ip->ip_p)
 {//switch
 /*UDP packet*/
 case IPPROTO_UDP:
  udp = (struct udp_hdr *) (packet + IPV4_H + offset);
  /*if the destination port is out of portspan, ignore the udp packet*/
  DPort=ntohs(udp->uh_dport);
  if (!(lp=check_port_range (DPort)))
   return;
  DPort = demap(DPort,lp);
  if(opt_debug)
   printf("[UDP] port %d\n", DPort );
  break;
 /*ICMP packet*/
 case IPPROTO_ICMP:
  DPort = 0;
  if(opt_debug)
   printf("[ICMP] icmp packet\n");
  break;
 /*TCP packet*/
 case IPPROTO_TCP:
  tcp = (struct tcp_hdr *) (packet + IPV4_H + offset);
  /*if the destination port is out of portspan, ignore the tcp packet*/
  DPort= ntohs(tcp->th_dport);
  if (!(lp=check_port_range (DPort)))
   return;
  DPort=demap(DPort,lp);
  if(opt_debug)
   {
   printf("[TCP] port %d\n", DPort );
   printf("FIN(%d) SYN(%d) RST(%d) PSH(%d) ACK(%d) URG(%d)\n", tcp->fin, tcp->syn, tcp->rst, tcp->psh, tcp->ack, tcp->urg );
   }
  break;
 /*other protocols, ignore them*/
 default:
  return;
 }//end switch

In Module Knock invece viene riutilizzata la funzione GetDataByID tre volte, specificando l'ID del decoder adeguato, e fornendo il puntatore alla struttura che conterra' l'header di livello superiore. Ovviamente al termine di questa serie di operazioni solo una di queste tre variabili non puntera' a NULL.

GetDataByID(PacketSlot, TCPDecoderID, (void**)&TData);
GetDataByID(PacketSlot, UDPDecoderID, (void**)&UData);
GetDataByID(PacketSlot, ICMPDecoderID, (void**)&CData);

#ifdef DEBUG
  printf("%d) saddr : %s ", numero_pacchetto_ricevuto, IPSrc );
  printf("- daddr : %s \n", IPDst);
#endif

if (TData)
{
  /*if the destination port is out of portspan, ignore the tcp packet*/
  DPort=ntohs(TData->Header->dest);
  if (!(lp=check_port_range (DPort)))
    return;
  DPort=demap(DPort,lp);
  SPort=ntohs(TData->Header->source);
  #ifdef DEBUG
    printf("[TCP] sport : %d - dport : %d\n", SPort, DPort );
    printf("\t flags-> syn(%d), rst(%d)\n", TData->Header->syn, TData->Header->rst);
  #endif
}

else if (UData)
{
/*if the destination port is out of portspan, ignore the udp packet*/
  DPort=ntohs(UData->Header->dest);
  if (!(lp=check_port_range (DPort)))
    return;
    DPort = demap(DPort,lp);
    SPort=ntohs(UData->Header->source);
    #ifdef DEBUG
      printf("[UDP] sport : %d - dport : %d\n", SPort, DPort );
    #endif
  }

else if (CData){
  DPort = 0;
  #ifdef DEBUG
    printf ("[ICMP] pacchetto ICMP\n");
  #endif
}

 A questo punto, prima di procedere nel riconoscimento della sequenza, viene ripulita la lista attempts_list, per eliminare gli attempt relativi a sequenze di knock ormai completate o fallite. Vengono inoltre rimossi anche gli elementi delle sequenze in corso di riconoscimento, per le quali sia scaduto il timeout specificato da seq_timeout, confrontando tale valore con la differenza tra il timestamp del momento della cattura del pacchetto attuale e quello iniziale della sequenza di knock puntato da attempt->seq_start.

/*expired attempt found*/
else if ((pkt_secs - attempt->seq_start) >= attempt->knock -> seq_timeout)
 {
 if(opt_verbose)
  printf ("[%s] Seq timeout from %s [DELETE]\n", attempt->knock->name, attempt->src );
 fprintf(LogFilePtr, "[%s] %s-%s Seq timeout from ip %s\n", attempt->knock->name, pkt_date, pkt_time, attempt->src );
 delete=1;
 }

 Come si puo' notare dal secondo diagramma di flusso, ogniqualvolta un pacchetto superi i primi controlli si verifica che esso appartenga o meno ad una sequenza di knock gia' in fase di riconoscimento. Tale verifica viene svolta scandendo la lista attempts_list, e valutando se il valore puntato da lp->data->src e l'indirizzo IP sorgente del pacchetto corrispondono. In caso affermativo si procede nel riconoscimento, aggiornando o invalidando l'elemento della lista puntato da attempt.

/* check if the packet is part of existing attempt*/
for (lp=attempts_list; lp; lp=lp->next)
 {//for exist
 if (!strncmp(((attempt_t*)lp->data)->src,IPSrc,sizeof(IPSrc)))
  {
  /*found attempt*/
  attempt=(attempt_t*)lp->data;
  break;
  }
 }//end for exist

 In caso contrario il pacchetto catturato potrebbe essere il primo di una nuova sequenza di knock, per il quale allocare un nuovo elemento attempt_t. Prima che esso venga allocato si verifica che protocollo e porta di destinazione (quest'ultima solo nel caso dei pacchetto TCP ed UDP) corrispondano con il primo elemento di una sequenza, scandendo la lista knocks_list e confrontandoli con i valori knock->protocol[0] e knock->sequence[0].
 Poiche' le sequenze di knock non vengono inviate “in chiaro”, ma crittate per mezzo di opportuni algoritmi, si rende prima necessario decrittare il numero della porta di destinazione, tramite una chiamata alla funzione decrypt_first :

/* Detect the sequence by decrypting the first port */
if (ip->ip_p != IPPROTO_ICMP)
 {
 /*if decrypt fails, ignore this packet*/
 if( decrypt_first((u_char*)&DPort) )
  return;
 }

 Tale funzione ha il compito di decrittare il numero della porta di destinazione del pacchetto catturato, appoggiandosi alla libreria libmcrypt. Poiche' essa accetta come parametro un puntatore di tipo u_char, quando viene invocata e' necessario passarle l'indirizzo della variabile Dport, castandolo in maniera opportuna ( (u_char*)&DPort ).

 Per potere comprendere al meglio le linee di codice successive si ricordano brevemente le principali funzioni della libreria libmcrypt utilizzate :

 int mcrypt_generic_init( MCRYPT td, void *key, int lenofkey, void *IV) : permette di inizializzare i vari buffer necessari per le operazioni di cifratura/decifratura. Il parametro td indica il descrittore ritornato da mcrypt_module_open, key la chiave da utilizzare, lenofkey la sua lunghezza espressa in byte, ed infine IV il vettore di inizializzazione. In caso di errore la funzione ritorna un valore negativo.
 int mdecrypt_generic(MCRYPT td, void *ciphertext, int len) : la funzione di decifratura, ritorna 0 in caso di successo. Nel codice di servknock essa viene invocata passandole come secondo parametro il carattere corrispondente al numero di porta di destinazione del pacchetto catturato.
 int mcrypt_generic_deinit( MCRYPT td) : libera tutti i buffer utilizzati nelle operazioni associate al descrittore td, senza pero' chiudere i moduli caricati. Ritorna un valore negativo in caso di errore.
 int mcrypt_module_close( MCRYPT td) : chiude i moduli usati dal descrittore td.

/* Decrypt the first port of a sequence */
int decrypt_first (u_char* port)
 {
 int i;
 i=mcrypt_generic_init( hd, Key, KEYSIZE, IV);
 if (i<0)
  {
  mcrypt_perror(i);
  return 1;
  }

 mdecrypt_generic (hd, port, 1);
 mcrypt_generic_deinit (hd);
 return 0;
 }

 Oltre a controllare la corrispondendenza tra protocollo e porta di destinazione, nel caso dei pacchetti TCP appartenenti alla sequenza si valuta anche quali flag siano settati ad uno. E' infatti possibile discriminare tali pacchetti in base ai flag impostati. Utilizzare sequenze con particolari configurazioni dei flag TCP ha l'indubbio vantaggio di rendere piu' remota l'ipotesi che del normale traffico di rete possa interferire nell'operazione di riconoscimento.

/* flag check */
if (tcp)
 {//check tcp flags
 if(knock->flag_fin == 1 && tcp->fin != 1)
  {
  if(opt_debug)
   printf("packet is not FIN, ignoring...\n");
  continue;
  }
 /*other flags*/
 [...]
 }//end check tcp flags

 Come gia' esposto, se protocollo, porta di destinazione ed eventuali flag TCP impostati corrispondono con il primo pacchetto della sequenza, viene allocato un nuovo elemento attempt_t. Naturalmente, se tale pacchetto e' valido per piu' sequenze, devono essere allocati diversi elementi attempt_t, anziche' uno soltanto.
 Per indicare lo stadio raggiunto nel riconoscimento viene impostato ad uno il valore attempt->stage, ed a 0 i campi attempt->ipstage e attempt->is_in_ipstage, poiche' questa fase riguarda ancora la prima parte della sequenza.
 Essendo compresi nel knock anche i bit dell'indirizzo IP, essi vengono memorizzati separatamente come elementi dell'array attempt->ipbyte, per essere utilizzati nelle successive operazioni di controllo.
 Infine, prima di aggiungere l'elemento appena allocato alla lista attempts_list, viene copiata in attempt->sequence_crypted la prima parte della sequenza di knock, per poi cifrarla ricorrendo alla funzione encrypt_sequence.
 Poiche' il range di porte utilizzato e' costituito di 255 elementi, ed il valore dei byte di un indirizzo IP variano tra 0 e 255, e' facile fare corrispondere questi valori con i rispettivi elementi del set ASCII, e quindi l'utilizzo di una stringa come forma di rappresentazione si presta particolarmente bene allo scopo.

/*alloc a new attempt element*/
attempt = (attempt_t*)malloc(sizeof(attempt_t));
if(attempt == NULL)
 {
 printf("Error: can't alloc attempt structure\n");
 exit(EXIT_FAILURE);
 }

/*init the attempt element*/
strcpy(attempt->src, IPSrc);
attempt->stage = 1;
attempt->seq_start = pkt_secs;
attempt->knock = knock;
attempt->ipstage = 0;
attempt->is_in_ipstage = 0;

while ( (byte = strsep(&tmp_ip, ".")) )
 {
 attempt->ipbyte[i]=(u_char) atoi (byte);
 i++;
 }

attempt->sequence_crypted = (u_char*) calloc (knock->seqcount, sizeof(u_char));
strncpy(attempt->sequence_crypted, knock->sequence, knock->seqcount);

/*calculate encrypted sequence. If encryption fails invalidate the attempt*/
if ( encrypt_sequence (attempt, knock) )
 attempt->stage=-1;

attempts_list = list_add(attempts_list, attempt);

 La stringa viene costruita ricorrendo alla funzione encrypt_sequence, riportata nel seguente frammento di codice. I suoi parametri consistono in due puntatori: il primo alla struct attempt_t nella quale deve essere memorizzata la stringa costruita ed il secondo alla struttura di tipo knock_t relativa alla sequenza di knock che si vuole riconoscere.
 Ogni singolo elemento della sequenza (contenuti negli array sequence_crypted ed ipbyte) viene crittato chiamando la funzione mcrypt_generic, passandole il descrittore hd ritornato precedentemente da mcrypt_module_open, il testo da crittare e la sua lunghezza espressa in bytes.
 Poiche' i caratteri vengono crittati uno alla volta, per ogni occasione in cui viene invocata tale funzione, le si passa un singolo elemento degli array gia' menzionati ed il valore 1 come lunghezza.

int encrypt_sequence (attempt_t* a, knock_t* k)
 {
 int i;
 i=mcrypt_generic_init( hd, Key, KEYSIZE, IV);
 if (i<0)
  {
  mcrypt_perror(i);
  return 1;
  }

 /* encrypt sequence */
 for (i=0; i < k->seqcount; i++)
  {
  if (k->protocol[i] != IPPROTO_ICMP)
   mcrypt_generic (hd, &a->sequence_crypted[i], 1);
  }

  /* encrypt ip */
 for (i=0; i < 4; i++)
  mcrypt_generic (hd, &a->ipbyte[i], 1);

 mcrypt_generic_deinit (hd);
 return 0;
 }

 Scandendo la lista attempts_list, se il valore puntato da lp->data->src e l'indirizzo IP sorgente del pacchetto catturato coincidono, cio' sta ad indicare che esso appartiene ad una sequenza di knock gia' “avviata” ed e' necessario procedere nel suo riconoscimento, aggiornando o invalidando l'elemento della lista puntato da attempt.

 Per prima cosa, nel caso il pacchetto catturato sia di tipo TCP si verifica che i flag impostati corrispondano a quelli definiti nel file di configurazione. Se tale corrispondenza non esiste il pacchetto ricevuto viene ignorato, senza conseguenze sul riconoscimento della sequenza stessa.

int flagsmatch = 1;
/* if TCP packet check its flags */
if(tcp!=NULL)
 {//check tcp flags
 if(attempt->knock->flag_fin == 1 && tcp->fin != 1)
  {
  if(opt_debug)
   printf("[%s] packet is not FIN, ignoring...\n", attempt->knock->name );
  flagsmatch = 0;
  }
 /*check other flags */
 [...]
 }//end check tcp flags

/*if flags don't match ignore this packet */
if (flagsmatch == 0)
 return;

 Se invece i flag corrispondono a quanto definito in servknock.conf si procede controllando il valore di attempt->is_in_ipstage. In caso esso valga uno, significa che la prima parte della sequenza e' gia' stata correttamente riconosciuta, e ci si trova nella seconda fase dell'operazione di riconoscimento.

 In questa fase si deve verificare che il valore della variabile Dport per gli ultimi quattro pacchetti della sequenza di knock corrisponda ai singoli byte crittati dell'indirizzo IP del richiedente, memorizzati nell'array attempt->ipbyte, ed ai quali si accede per mezzo dell'indice attempt->ipstage .
 Tale indice inizialmente posto a zero, viene incrementato di un'unita' per ogni elemento dell'indirizzo IP correttamente riconosciuto. Quando esso raggiunge il valore quattro, significa che tutti i bytes sono stati riconosciuti, e di conseguenza l'intera sequenza di autenticazione ritenuta valida. E' quindi possibile passare all'esecuzione dei comandi ad essa associati.
 Nel caso il riconoscimento di uno dei bytes dovesse fallire la sequenza sarebbe invece invalidata pondendo a -1 il valore di attempt->ipstage, in modo che l'attempt allocato possa essere liberato nella successiva operazione di cleanup della lista attempt_list.

/*if the first part of the knock sequence is OK , check for the ip*/
if (attempt->is_in_ipstage)
 {//ipstage
 /*check the ipstage byte of the ip address*/
 if (DPort == attempt->ipbyte[attempt->ipstage])
  {//check the byte
  /* ip byte is OK!! */
  attempt->ipstage++;
  if(opt_debug)
   printf("[%s] %s Stage %d\n", attempt->knock->name, attempt->src, attempt->stage+attempt->ipstage);   fprintf(LogFilePtr, "[%s] %s-%s Stage %d from ip %s\n", attempt->knock->name, pkt_date, pkt_time, attempt->stage+attempt->ipstage ,attempt->src );

  /*succesful knock*/
  if(attempt->ipstage >= 4)
   {
   [...]
   }
  }//end check the byte
 /*ip byte is wrong, seq. failed*/
 else
  attempt->ipstage = -1;
 }//end ipstage

 Se il riconoscimento fosse invece ancora nella prima fase (evento riconoscibile dal valore zero della variabile attempt->is_in_ipstage), il flusso di esecuzione del programma dovrebbe procedere attrverso l'altro ramo del costrutto condizionale nel quale e' controllato il valore della variabile attempt->is_in_ipstage.
 A questo punto si deve verificare che porta di destinazione e protocollo del pacchetto catturato corrispondano a quelli previsti per la sequenza, confrontandoli con i valori puntati da attempt->sequence_crypted[attempt->stage] ed attempt->knock->protocol[attempt->stage].
 Nella circostanza in cui il confronto abbia esito positivo, viene incrementato di un'unita' attempt->stage e successivamente verificato che sia maggiore o uguale ad attempt->knock->seqcount. Tale situazione indica che la prima parte della sequenze di knock e' stata correttamente riconosciuta. Come conseguenza si imposta ad uno il valore attempt->is_in_ipstage, in modo da potere passare alla seconda fase del riconoscimento.
 In caso contrario, se porta e protocollo non dovessero essere giudicati validi, la sequenza andrebbe invalidata impostando il valore di attempt->stage a -1, permettendo cosi' alla successiva operazione di cleanup della lista attempt_list, di liberare la memoria utilizzata dall'attuale attempt .

/*we are in the first part of the knock still*/
else
 {//first part of knock
 /*check protocol and destination port*/
 if( ip->ip_p == attempt->knock->protocol[attempt->stage] && DPort == attempt- >sequence_crypted[attempt->stage])
  {//check proto&port
  attempt->stage++;
  [...]
  if(attempt->stage >= attempt->knock->seqcount)
   {
   /*first part of the sequence is OK!*/
   [...]
   attempt->is_in_ipstage = 1;
   }
  }//end check proto&port
 else
  {
  /*check for proto&port has failed, knock sequence is wrong*/
  attempt->stage = -1;
  }
 }//end first part of knock


next   previous   contents