Théorie

Forger des paquets a la main

Raw Socket

En utilisant les sockets il est possible de forger ses paquets IP a la main (adresse source, destination, ...), et de recevoir les paquets sans decapsulation. Vous devez etre root pour pouvoir creer de telles sockets.

Comme pour les sockets normales, il faut specifier le protocole au dessus de IP (ICMP, TCP ou UDP). Le reste des api sont utilisees comme d'habitude. Voici comment afficher tous les parquets TCP qui passent :

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>

int	main(int argc, char *argv[])
{
  int	fd = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
  char	buffer[8192];
  struct ip *iph;
  struct tcphdr *tcph;

  while (read (fd, buffer, 8192) > 0)
  {
    iph = (struct ip*)buffer;
    tcph = (struct tcphdr*)(buffer + sizeof (struct iphdr));
    printf ("TCP : saddr %s daddr %s sport %d dport %d:\n'%s'\n",
	    inet_ntoa(iph->ip_src), inet_ntoa(iph->ip_dst),
	    tcph->source, tcph->dest,
	    buffer + sizeof (struct iphdr) + sizeof (struct tcphdr));
  }
  return 0;
}

    

Voyons maintenant comment forger ses propres paquets tcp (ici paquets SYN sur le port 25):

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>

#define P 25		/* lets flood the sendmail port */

unsigned short csum(unsigned short *addr, int len)
{
  register int sum = 0;
  u_short answer = 0;
  register u_short *w = addr;
  register int nleft = len;
  
  while (nleft > 1) {
    sum += *w++;
    nleft -= 2;
  }
  
  if (nleft == 1) {
    *(u_char *)(&answer) = *(u_char *)w ;
    sum += answer;
  }

  sum = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);
  answer = ~sum;
  return(answer);
}

int 
main (void)
{
  int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);	/* open raw socket */
  char datagram[4096];
  struct ip *iph = (struct ip *) datagram;
  struct tcphdr *tcph = (struct tcphdr *) datagram + sizeof (struct ip);
  struct sockaddr_in sin;
			/* the sockaddr_in containing the dest. address is used
			   in sendto() to determine the datagrams path */

  sin.sin_family = AF_INET;
  sin.sin_port = htons (P);
  sin.sin_addr.s_addr = inet_addr ("127.0.0.1");

  memset (datagram, 0, 4096);	/* zero out the buffer */

  iph->ip_hl = 5;
  iph->ip_v = 4;
  iph->ip_tos = 0;
  iph->ip_len = sizeof (struct ip) + sizeof (struct tcphdr);	/* no payload */
  iph->ip_id = htonl (54321);	/* the value doesn't matter here */
  iph->ip_off = 0;
  iph->ip_ttl = 255;
  iph->ip_p = IPPROTO_TCP;
  iph->ip_src.s_addr = inet_addr ("1.2.3.4");/* SYN's can be blindly spoofed */
  iph->ip_dst.s_addr = sin.sin_addr.s_addr;
  iph->ip_sum = csum ((unsigned short *) datagram, iph->ip_len >> 1);

  tcph->source = htons (1234);	/* arbitrary port */
  tcph->dest = htons (P);
  tcph->seq = random ();/* in a SYN packet, the sequence is a random */
  tcph->ack_seq = 0;/* number, and the ack sequence is 0 in the 1st packet */
  tcph->res1 = 0;
  tcph->doff = 0;		/* first and only tcp segment */
  tcph->res2 = 0;
  tcph->fin = 0;
  tcph->syn = 1;
  tcph->rst = 0;
  tcph->psh = 0;
  tcph->ack = 0;
  tcph->urg = 0;
  tcph->urg_ptr = 0;

  tcph->window = htonl (65535);	/* maximum allowed window size */
  tcph->check = 0;/* if you set a checksum to zero, your kernel's IP stack
		      should fill in the correct checksum during transmission */

/* finally, it is very advisable to do a IP_HDRINCL call, to make sure
   that the kernel knows the header is included in the data, and doesn't
   insert its own header into the packet before our data */

  {
    int one = 1;
    if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof (one)) < 0)
      printf ("Warning: Cannot set HDRINCL!\n");
  }

  if (sendto (s, datagram, iph->ip_len, 0, (struct sockaddr *) &sin, sizeof (sin)) < 0)
    printf ("error\n");
  else
    printf (".");
  
  return 0;
}
    

Exercice : forger a la main un paquet ICMP (PING REQUEST) venant de l'adresse 42.42.42.42

Avec la bibliotheque libpcap

Il existe une bibliotheque specialisee dans la capture de paquets : libpcap-devel. Voici quelques fonctions :

  • pcap_lookupnet() permet de charger les informations dont pcap a besoin (ex: netmask), premier argument a NULL pour sniffer sur toutes les interfaces : un attaquant ne sait pas sur quelle interface sniffer.
  • pcap_open_live() : premier argument NULL pour les memes raisons que precedemment, deuxieme argument a 1024 (taille des packets captures), le troisieme permet de passer en mode promiscuous : on le met a NULL pour plus de discretion.
  • pcap_compile() permet de compiler des regles de filtrages : le troisieme argument est une string qui doit contenir "udp dst port 53" pour ne capturer que les packets a destination du port 53 udp, par exemple.
  • pcap_setfilter() active le filtre.
  • pcap_loop() permet de lancer la boucle de capture et envoie les paquets captures a un handler appelle forcement packet_handler : packet_handler(u_char *ptrnull, const struct pcap_pkthdr *pkt_info, const u_char *packet)

Fingerprinting

Le fingerprinting est une technique utilisee pour reconnaitre un systeme grace au paquets qu'il genere. Les differents systemes ont des stack ip differentes et ont tendance a utiliser differentes options dans les paquets, ce qui permet souvent de reconnaitre le systeme sur lequel ils ont ete forges.

Pouvoir reconnaitre un systeme sur un reseau est tres interessant cela peut etre aussi bien utilise par les attaquants qui recherchent une cible precise que par des administrateurs pour generer des statistiques ou pour savoir quel systeme utilise un attaquant.

Il existe deux methodes de fingerprinting : le fingerprinting 'actif' ou 'passif'.

Le fingerprinting actif correspond a envoyer des paquets et a analyser leur reponse, c'est ce que fait par exemple nmap qui est un scanneur de port que l'ont va developper juste apres.

Le fingerprinting passif correspond a analyser les paquets qui sont sur le reseau, ou un dump, c'est ce que fait p0f, un des meilleurs logiciels de fingerprinting, ecrit par Michal Zalewski qui a ecrit un livre tres original sur la securite informatique dans lequel il d'ecrit entre autre des technique avances de fingerprinting. ("Silence on the Wire", que l'on vous conseille vivement).

Active Fingerprinting

L'active fingerprinting permet detecter un systeme en analysant des paquets. Cette technique est tres souvent utilisee mais on peut la detecter facilement, des IDS comme snort vont emettre une alerte s'il reconnaissent des paquets malformes.

Pour lancer nmap en mode detection d'os il faut utiliser l'options -O. L'options --osscan-guess permet une detection plus aggressive.

Voici quelques methodes utilisees par nmap pour detecter un OS:

  • FIN:

    Envoie d'un packet FIN (sans ACK ou SYN) vers un port ouvert. Normallement l'os ne pas doit pas repondre mais certains repondent (Windows, BSDI, CISCO, HP/UX, ...)

  • Flag BUG:

    On positionne un flag TCP indefini dans l'en-tete TCP d'un paquet SYN. Les Linux < 2.0.35 conservent ce flag dans leur reponse (c'est le seul OS a faire ca).

  • Echantillonage TCP ISN:

    On peut aussi essayer de voir comment les ISN (Initial Sequence Number) sont crees. ils sont senses etre aleatoires mais en fait les OS utilisent des methodes peu aleatoire et qui peuvent etre reconnues (Windows par exemple incremente le nombre avec le temps ecoule)

Il existe beaucoup d'autres techniques basees sur le meme principe envoi d'un packet avec des options speciales et sur l'analyse de la reponse, en mettant en relation les differents resultats. Les fingerprinter peuvent donc obtenir un resultat precis, avec juste quelques details d'implementation.

Passive OS Fingerprinting

Un des avantages majeur de la prise d'empreinte passive face a la prise d'empreinte active est que l'on emet pas de paquets, on analyse juste les paquets qui passent sur le reseaux ce qui rend cette technique indetectable.

p0f est de loin le meilleur outil de fingerprinting, son auteur a decouvert de nombreuses astuces qui permettent d'identifier un systeme. P0f a ete integre dans de nombreux outils comme des IDS ou dans pf, le firewall d'OpenBSD.

Par default p0f ecoute sur une interface et essaye de detecter par quel OS les paquets on ete emis. Vous pouvez aussi l'utiliser sur un dump avec l'options -s file.

Voici un extrait de la liste des verifications que p0f a integre pour verifier les paquets et qui ont ete inventes pour p0f :

  • ACK pas a 0 dans un paquet d'initialisation SYN

  • Champ TCP non-utilise pas mis a 0.

  • Donnee dans le payload de packet de control.

  • SEQ number egal au ACK number

Il existe d'autres methodes connues, par exemple le TTL d'un linux est pas defaut 64, et de 255 pour un BSD et 128 pour un windows.

Conclusion

Le fingerprinting, surtout avec la methode passive, est tres utile car il peut servir a :

  • Faire du profiling

    Avoir plus d'informations sur les visteurs d'un site, ou pour servir un contenu dynamique d'apres les OS, en etant utilise sur un serveur ou un firewall.

  • Faire des pentests, et savoir si on peut reperer l'OS qui tourne sur les machines a tester.

  • Passer outre un firewall : en utilisant du passive OS fingerprinting, on peut analyser des reponses de machines derrieres un firewall, ce qui n'est pas le cas avec nmap.

  • Detecter la machine d'un intrus (forensic).

Pour que l'os d'un systeme ne puisse pas etre detecte, il faut donc changer ou modifier sa stack IP. On peut ainsi passer pour un OS inconnu ou pour un OS qui n'est pas celui installe sur la machine. Avec un firewall il est aussi possible de modifier les paquets renvoyes pour qu'ils ne soient pas reconnaissables (avec pf sous OpenBSD).

Exercice : coder un programme qui envoie une requete ICMP et en deduit si la machine est de type BSD, linux ou windows.