AFFICHER CET ARTICLE EN MODE PAGE ENTIERE
SOMMAIRE
2) Prelude à TCP/IP : revenir à la source
__2.1)Principes
__2.2)Qu'est-ce qu'une adresse IP ?
__2.3)Le protocole ICMP : l'ordonnanceur des "Echo request" et des "Echo reply"
__2.4) Le Reverse DNS : "Dis moi qui tu es et je ne te dirai pas qui je suis...."
__2.5)Les classes d'adresses : indices des caractéristiques d'un réseau
3) IP RANGE SCANNING : principe et rôle dans la prise d'empreintes d'un réseau
__3.1) Introduction
__3.2) Principe et rôle
4) IPnSCAN : un troisième oeil
__4.1) fichier header : structures, liste chaînée, variables globales et macros
__4.2) Les fonctions de notre scanner
__4.3) Le parser
__4.4) Tests
La technique de prise d'empreintes d'un réseau ou d'un serveur, appelé plus communément "fingerprinting", est la première étape d'une longue série qui constitue la préparation d'une attaque ayant pour objectif dans la plupart des cas une tentative d'intrusion. En effet il n'est possible de compromettre un serveur qu'après le rassemblement d'une multitude d'informations le concernant (OS, daemons...) afin de pouvoir élaborer la meilleure stratégie d'attaque. Cette prise d'empreintes sur un serveur peut-être résumée en une suite d'actions fastidieuses nécessitant indispensablement l'aide de programmes afin d'obtenir des résultats fiables et exhaustifs ; on pourra par exemple citer l'excellent nmap écrit par fyodor [1], ou encore nessus etc... L'approche de l'attaque d'un réseau est sensiblement similaire dans la mesure où comme un serveur, un réseau possède des caractéristiques qui lui sont propres (classe...) et qui l'identifient par rapport à certaines vulnérabilités qui ne feront pas l'objet de cet article. En effet l'objectif de ce papier est de senbiliser le lecteur au principe du l'IP Scanning d'un point de vue programmation puisque j'expliquerai comment concevoir pas à pas et simplement un scanner en C permettant d'analyser des plages d'adresses IP ainsi que d'effectuer des reverses DNS.
2) Prelude à TCP/IP : revenir à la source
Avant toute chose il est évidemment indispensable d'expliquer le pourquoi du comment i.e les caractéristiques des protocoles régissant le fonctionnement des communications sur un réseau afin qu'une information puisse être acheminée d'un point A à un point B sans contraintes.
TCP/IP est une suite de protocoles pour l'exploitation des réseaux de communication, on peut alors se demander "qu'est-ce qu'une réseau?" et quel rôle joue les protocoles dans son fonctionnement. Un réseau est en fait un ensemble de machines informatiques qui communiquent ensemble grâce à une technique commune de transmission. Ainsi si une machine A veut communiquer avec une machine B, la machine A doit pouvoir envoyer une message à la machine B et cette dernière doit pourvoir comprendre ce message, plus précisément des applications (i.e des logiciels) de la machine A vont discuter avec des applications de la machines B, or pour que ces deux machines se comprennent il faut qu'elles reconnaissent toutes les deux un ensemble de règles régissant les opérations de transfert sur un réseaux, c'est cet ensemble de règles qui va constituer un protocole (c'est exactement comme si un allemand et un italien se retrouvait dans la même pièce sans que l'un ne parle un mot de la langue de l'autre, et bien pour pouvoir se comprendre il vont devoir utiliser un langage auxiliaire afin de communiquer, ce langage va être soumis à un ensemble de règles comme le vocabulaire etc... c'est exactement la même chose en ce qui concerne un protocole). Evidemment je ne développerait pas tous les points de la suite de protocole TCP/IP, la tache est beaucoup trop importante et c'est pourquoi je vous invite à consulter les RFC (Request For Comment)[2] qui sont de vrai mines d'or pour ceux qui souhaiterait se plonger dans le fonctionnement du réseau des réseaux. Ainsi nous n'allons ne nous intéresser qu'à certains points principaux de TCP/IP indispensable à la compréhension de la suite de l'article.
Une carte réseau est pourvut d'une adresse physique unique qui l'identifie et qui lui est attribué lors de sa fabrication. Dans le cas d'un réseau local éthernet, la carte va analyser la totalité du traffic afin d'en extraire les messages concernant sa propre adresse physique. Cette méthode n'est bien évidemment pas acceptable lorqu'il d'agit d'un réseau d'envergure mondiale comme Internet. Pour son fonctionnement, une autre méthode a été envisagée ; en effet les administrateurs réseaux segmentent leurs réseaux en sous-réseaux appelés "subnet" au moyen de routeurs (dispositifs permettant de lire les adresses IP et d'orienter les données à transmettre vers leur destinataire). Ces subnets vont être hierarchisés de façon à ce que un message soit acheminé de façon optimale vers son destinataire, TCP/IP offre cette possibilité de hierarchisation grâce aux adresses IP. Ces adresses IP sont soumisent à un ensemble de règles définis par le protocole IP dont on peut étudier toutes les caractéristiquent dans la RFC n°791 . Les adresses IP d'un réseau sont organisées de telle façon que l'emplacement d'une machine soit définit par son adresse, voici à quoi ressemble une adresse IP :
192.128.75.46 |
Chaque datagramme IP (paquet envoyé sur le réseau à une adresse IP et contenant des données) commence par une en-tête IP. C'est le logiciel TCP/IP de la machine émettrice qui construit l'en-tête IP. Le logiciel TCP/IP de la machine de destination utilise l'informmation codée dans l'en-tête IP afin de traiter ce datagramme.
Version |
IHL |
TOS |
Longueur totale |
Identification |
Drapeau |
Décalage de framents |
Durée de vie |
Protocole |
Somme de contrôle de l'en-tête |
Adresse IP source |
Adresse IP de destination |
Options IP (optionnel) |
Bits de bourrage |
Données |
Champs réservé à d'autres possibles données |
Rassurez vous il ne sera pas nécessaire d'ajuster chacun de ces champs avant le début d'un scan puisque qu'ils présentent tous une valeur par défaut (mis à part l'adresse de destination bien sûr) qui pour certain champs conviendra parfaitement. Néanmoins je dois revenir sur un de ces champs à savoir le champs protocole. Comme dis précédemment ce champs de 8 bits indique quel protocole sera utilisé pour le traitement des données, nous utiliserons le protocole ICMP qui a pour identificateur de protocole "1", ainsi ce champs sera remplit de la façon suivante [ 0000 0001 ].
2.3)Le protocole ICMP : l'ordonnanceur des "Echo request" et des "Echo reply"
TCP/IP est un protocole orienté connection, i.e il effectue, contrairement au protocole UDP (User Datagramme Protocole) qui lui est orienté non connection, une surveillance de l'état de la liaison durant la transmission. Autrement dit, le récepteur envoie un accusé de réception des données reçues tandis que l'emetteur reçoit des informations sur la validité des données acheminées, c'est sur ce principe que s'effectue une connection entre 2 machines par TCP, plus communément appelée la "poignée de main en trois temps". TCP assure donc le contrôle intégral des erreurs et des flots de données pour garantir une livraison correcte à destination. Ainsi les données envoyées à une machine distante traversent souvent un ou plusieurs routeurs. Or ces routeurs peuvent rencontrer un certains nombre de problèmes avant de remettre le message à sa destination ultime. Les routeurs utilisent ICMP pour rendre compte de ces problèmes à la machine source. ICMP est également utilisé à d'autres tâches de diagnostics, voici que ceux que nous utiliserons :
_Requête et réponse Echo : souvent utilisées lors de tests. Lorsqu'on utilise la commande Ping pour tester la connectivité avec une autre ______________________-machine, c'est ICMP qui est mis à contribution. Ping envoie un datagramme à l'adresse IP et demande à la ______________________-machine de destination de retourner les données émises dans un datagramme de réponse. Dans ce cas, les ______________________-commandes sont la requête echo = Echo request et la réponse echo = Echo reply.
_Destination inaccessible (Destination unreachable) : si un routeur reçoit un datagramme qu'il est est incapable de délivrer, ICMP envoie un ______________________-____________________message destination inaccessible à la source IP.
_Durée dépassée (Time Exceeded) : ICMP ce message à la source IP si le datagramme est rejeté parce que le TTL atteint zéro. Cela ______________________-______-indique que la destination est située au-delà d'un trop grand nombre de routeurs par rapport à la ____________________________--valeur du TTL.
Selon le type de message ICMP désiré, l'allure du datagramme va être sensiblement différente puisqu'il en existe différente sorte (ICMP redirect message, ICMP error message etc...). Nous allons nous intéresser aux ICMP query messages dont font partie les "requêtes et réponses Echo". La forme générale d'un message ICMP query est le suivant :
TYPE |
CODE |
CHECKSUM |
IDENTIFIER |
SEQUENCE MEMBER |
DEPEND DU TYPE DE MESSAGE |
Ainsi lors de l'envoie de nos paquets IP lors du scan, après avoir spécifié que nous utiliserons le protocole ICMP (code 1), le champ [ donnée ] de ces paquets sera remplit avec les champs précedents. En effet un datagramme IP a une taille de 32 octets, 24 sont réservés pour les champs qui lui sont propres et les 8 derniers sont réservés au champs [ DONNEES ] et donc en locurrence à notre message ICMP query.
Le message ICMP que nous enverrons lors du scan sera une requête Echo mais avant d'aller plus loin dans notre approche technique de nos paquets, je m'arrête un moment pour expliquer ce qu'est une requête Echo. Notre but premier pour le scan sera de détecter si les adresses IP scannées sont actives ou non i.e si elles sont attribuée par l'ICANN à un hôte connecté au moment du scan. Pour cela nous allons utiliser une méthode très simple qui est d'ailleurs très abondamment utilisée par les administrateurs réseaux lorsqu'il sont confrontés à des problèmes de toutes sortes, les "Requêtes ECHO". Elles consistent en un envoie d'un datagramme IP avec le champs [ PROTOCOLE ] initialisé à [ 1 ] indiquant l'utilisation du protocole ICMP et ayant un message ICMP de type QUERY dans le champs [ DONNEES ] ; dans ce message le champs [ type ] est initialisé à 8 soit [ 0000 1000 ] en binaire pour indiquer que le message est de type "Echo Request". La machine distante (si elle existe) va recevoir ce datagramme et selon les normes imposées par les protocole de la suite TCP/IP elle va renvoyer un datagramme presque similaire en changeant simplement 2 champs : tout d'abord le champs [ TYPE ] qui va être passé à 0 soit [ 0000 0000 ] en binaire, le message ICMP devient de type "Echo Reply", et le champs [ CHECKSUM ] correspondant à la somme de contrôle qui est utilisée pour vérifier l'intégrité du message et que cette dernière va recalculer avant de renvoyer le message. De l'autre côté si, après l'envoie de la "requête Echo" et avant que le TTL (Time To Live = durée de vie du datagramme sur le réseau) n'atteigne 0, une "réponse Echo" est reçut, cela signifie que l'hôte distant est actif, inactif dans le cas contraire. Comme dit plus haut dans l'article il faudra veiller à programmer une option qui permettra à l'utilisateur d'ajuster ce TTL avant le debut du scan car certains hotes distants peuvent demander un passage par de nombreux routeurs avant d'être atteint et sans réglages initiaux le paquet pourrait mourir avant d'avoir atteint sa cible, aucune "Réponse ECHO" n'est renvoyée dans ce cas là mais simplement un message ICMP de type 11 "Time exceeded" alors que l'hôte existe bel et bien. Néanmoins il ne faudra pas être trop gourmant avec le TTL car sinon c'est la rapidité du scan qui pourrait en souffir gravement. La deuxième cause pour laquelle on pourrait ne pas recevoir une "réponse Echo" serait le filtrage des données par l'hôte distant, filtrage effectué bien évidemment par un firewall dont certains refusent le passage aux "requêtes Echo", on se retrouverait à nouveau avec un message d'erreur ICMP mais cette fois de type 3 et qui correspond au message "Destination Unreachable" signifiant que la cible ne peut-être atteinte ou est invalide. Voici une liste exhaustive des messages d'erreurs ICMP :
ICMP ERROR MESSAGES |
Destination Unreachable |
3 |
Source Quench |
4 |
Redirect |
5 |
Time Exceeded |
11 |
Parameter Problem |
12 |
J'ai choisit de vous présenter différents dumps (enregistrement d'une partie du traffic transitant sur un réseau) matérialisant les 2 messages d'erreurs auquels on pourra être confronté durant nos scans ainsi nos "requetes Echo" et nos "Réponses ECHO". Ils ont été réalisés par Ofir Arkin du Sys-Security Group dans son excellent papier sur l'ICMP Usage in scanning [3] à l'aide du logiciel snort [4] :
Destination unreachable :
05/09/01-12:29:41.399543 RoutersIP -> SourceIP
ICMP TTL:244 TOS:0x0 ID:24442 IpLen:20 DgmLen:56
Type:3 Code:13 DESTINATION UNREACHABLE: PACKET FILTERED
** ORIGINAL DATAGRAM DUMP:
SourceIP:4667 -> DestinationIP:53 TCP TTL:53 TOS:0x0 ID:40019
IpLen:20 DgmLen:60 **U****F Seq: 0x97EABAF6 Ack: 0x1C1D1E1F
Win: 0x2223 TcpLen: 8
UrgPtr: 0x2627
** END OF DUMP
00 00 00 00 45 00 00 3C 9C 53 40 00 35 06 29 B0 ....E..<.S@.5.).
xx xx xx xx yy- yy yy yy -12 3B 00 35 97 EA BA F6 .....Z...;.5.......
Time exceeded :
05/13/01-16:05:47.639747 RouterIP -> 172.18.2.201
ICMP TTL:117 TOS:0x0 ID:61586 IpLen:20 DgmLen:56
Type:11 Code:0 TTL EXCEEDED
00 00 00 00 45 00 00 54 00 00 40 00 01 01 FA 0F ....E..T..@.....
AC 12 02 C9 yy yy -yy yy 08 00 F1 67 4F 1B 01 00 .....Z.d...gO...
Echo request :
05/14/01-11:55:30.171542 172.18.2.201 -> 172.18.2.200
ICMP TTL:64 TOS:0x0 ID:0 IpLen:20 DgmLen:84 DF
Type:8 Code:0 ID:58628 Seq:768 ECHO
82 9D FF 3A 5C 9E 02 00 08 09 0A 0B 0C 0D 0E 0F ...:\.................
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F ......................
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F !"#$%&'()*+,-./
30 31 32 33 34 35 36 37 ________________________01234567
Echo reply :
05/14/01-11:55:30.171542 172.18.2.200 -> 172.18.2.201 ICMP TTL:255 TOS:0x0 ID:769 IpLen:20 DgmLen:84 Type:0 Code:0 ID:58628 Seq:768 ECHO REPLY 82 9D FF 3A 5C 9E 02 00 08 09 0A 0B 0C 0D 0E 0F ...:\................. 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F ...................... 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F !"#$%&'()*+,-./ 30 31 32 33 34 35 36 37 ________________________01234567 |
Dans les dumps précédents, l'adresse source est [ 172.18.2.201 ] et l'adresse de destination est [ 172.18.2.200 ].
Voici un shéma qui vous explique le principe très simple de la requête-réponse Echo :
Ainsi sera la principale fonction de notre scanner, à savoir envoyer des datagrammes IP contenant des ECHO_REQUESTS et analyser les datagrammes reçus pour en dégager les messages correspondant à des ECHO_REPLIES significatifs d'une activité distante.
2.4) Le Reverse DNS : "Dis moi qui tu es et je ne te dirai pas qui je suis...."
Dans cette avant-dernière étape de notre survol de la suite de protocole TCP/IP, je vais brièvement vous parler du reverse DNS. Revenons au début de l'histoire, lorsque sont apparus les premiers réseaux TCP/IP, les utilisateurs se sont vite rendu compte qu'il n'était pas simple de se rappeler d'une adresse IP pour telle ou telle machine d'un réseau. En effet, les agents d'un centre de recherche ne peuvent perdrent du temps à essayer de se rappeler que la machine A de l'immeuble 4 à pour adresse IP 192.125.40.87, il fallait trouver un moyen qui permette d'adapter ce système hierarchique d'adressage à l'homme. Ainsi est né le système des noms de machines, dans ce dernier chaque machine se voit assigner un nom alphanumérique. Un fichier, le fichier hosts, contient toutes les correspondances "Adresses - Noms" et donc si un OS rencontre un nom aplhanumérique là où il attendait une adresse IP, il consulte ce fichier afin de faire la correspondance.
Voici un exemple de fichier hosts :
127.0.0.1 _______localhost _______#Machine de l'utilisateur |
Ce principe n'est évidemment pas tenable sur un réseau de la grandeur d'internet, en effet les recherches de correspondances "Adresses - Noms" doivent être très rapide pour maintenir un l'efficacité du réseau, or cette rapidité diminue avec la taille du fichier dans lequel la recherche s'effectue et qui de plus doit sans cesse être mis à jour. Les développeurs de TCP/IP ont donc inventé la résolution des noms par DNS (Domain Name Serveur). DNS place les données de résolution de noms sur des serveurs. Ainsi, si une machine du réseau rencontre un nom de machine dont elle a besoin de connaître l'adresse IP, elle envoie une requête au serveur, lui demandant qu'elle est l'adresse IP associée à ce nom de machine, si ce dernier connait l'adresse, il l'a lui retourne et cette dernière substitue l'adresse IP au nom de machine et ceci de façon transparente. C'est ce que l'on appelle le reverse DNS.
Nous pourrions parler encore longtemps de tout ce qui a traits de près ou de loin au système DNS mais ceci nous suffit pour poursuivre notre tâche...
2.5)Les classes d'adresses : indices des caractéristiques d'un réseau
Comme nous l'avons vu précédemment, une adresse IP est un chiffre binaire de 32 bits ( 32 0 ou 1) sous-divisé en 4 segments de 8 bits appelets octets. L'espace total que l'on peut couvrir avec 8 bits est le suivant : 0000 0000 -> 1111 1111 soit au format décimal : 0 -> 255. C'est pourquoi chaque segment d'une adresse IP est compris entre 0 et 255. Une partie de l'adresse est utlisée pour l'identificateur de réseau et une autre pour l'identificateur de machine. La partie correspondant à l'identificateur de réseau varie en fonction de ce dernier. Ainsi les adresses IP sont regroupées en 3 parties selon la classe du réseau auquel elles appartiennent, les classes sont les suivantes :
Adresse de classe A : Les 8 premiers bits de l'adresse sont utilisés pour l'identificateur de réseau. Les 24 bits restants sont utilisés pour l'identificateur de ________________--machine. Les réseaux auquels appartiennent ces adresses peuvent supporter environ 16 777 216 machines. Néanmoins leur ________________--identificateur de réseau reste faible et les possibilités de créations de sous-réseaux également en conséquence.
Adresse de classe B : Les 16 premiers bits sont utilisés pour l'identificateur de réseau. Les 16 bits restants pour l'identificateur de machine. Un réseau de ________________--classe B peut supporter environ 65536 machines.
Adresse de classe C : Les 24 premiers bits sont utilisés pour l'identificateur de réseau. Les 8 bits restants pour l'identificateur de machine. Un réseau de ________________--classe C peut supporter environ 256 machines. Néanmoins l'identificateur de réseau reste puissant.
Comment reconnaitre la classe d'une adresse à partir de cette dernière ? C'est excessivement simple, les concepteurs de TCP/IP ont rédigé des règles d'adressage permettant de déduire la classe d'une adresse à partir de celle-ci et plus précisément à partir des 8 premiers bits ou plus simplement à partir du premier segment de l'adresse :
PREMIER SEGMENT DE L'ADRESSE |
CLASSE D'ADRESSE |
0 à 127 |
A |
128 à 191 |
B |
192 à 223 |
C |
Note : Cette technique ne fonctionne évidemment pas avec les adresses IP de version 6 qui sont codés sur 128 bits et dont chaque segment peut atteindre pour valeur maximale : 65535.
La théorique étant quasiment terminée, nous allons bientôt passer à la pratique mais il va s'en dire que lors ce bref survol de quelques protocoles de TCP/IP, j'ai outre-passer de nombreux points qu'il vous faudra étudier si vous débuter en réseaux afin de saisir le mieux possible le fonctionnement du système. Je pensais notamment au principe de l'encapsulation, des différentes couches du modèle TCP/IP, du protocole ARP et RARP, du protocole TCP etc...
3) IP RANGE SCANNING : principe et rôle dans la prise d'empreintes d'un réseau
Bien que vous devez commencez à vous faire votre petite idée sur l'utilité des points que nous venons d'aborder en ce qui concerne notre scanner, je vais m'y attarder encore un instant pour être sûr que cela soit compris. Comme dit précédemment, l'action principale de notre scanner sera d'envoyer des ECHO_REQUESTS à une plage d'adresses IP et d'en attendre les ECHO_REPLIES afin de déterminer les machines actives ; il devra être également capable de déterminer la classe de chaque adresse IP et une partie des caractéristique du réseau que vous êtes en train de scanner et enfin il présentera une option pour réaliser des reverse DNS. Nous allons voir tout de suite l'intérêt et le rôle de ces méthodes dans une prise d'empreintes...
L'IP Range scanning consiste à scanner une plage d'adresses IP afin de déterminer lesquelles sont actives et lesquelles ne le sont pas. Mais quel en est l'intérêt ? Comme dit précédemment le protocole IP permet un système d'adressage hierarchique d'un réseau et une adresse IP est composée d'un identificateur réseau et d'un identificateur machine ; il devient alors possible par le biais d'un scan sur un identificateur réseau de dresser la carte de ce réseau ou sous-réseau et d'en identifier toutes les machines par le biais de reverses DNS. Déterminer la classe du réseau nous permettra d'en déterminer son importance mais surtout ses cararctéristiques comme le nombre de sous-réseaux qu'il peut présenter, à partir de là on peut effectuer un scan sur un hypothétique sous réseau et en déduire son existence en fonction des résultats. On peut alors imaginer une entreprise ayant 3 bâtiments hébergeant chacun une partie du réseau de l'entreprise, il devient alors possible d'élever la carte de ce reseau par le biais de multiples scans sur les identificateurs réseaux de chaque bâtiment. Les reverse DNS peuvent être d'une très précieuse aide car il n'est pas rare de voir un administrateur réseau donner à chaque machine d'un sous-réseau un code qui identifie le réseau auquel elle appartient (ex : "batA.machine1", "batB.machine1"). Ceci ne permettra évidemment pas de donner toutes les informations nécessaires à la préparation d'une attaque sur un réseau et ce n'est pas le but mais cela constitue une bonne base pour qui souhaite élever la carte d'un réseau et peut se révéler très utile pour celui qui veut tester le fonctionnement du réseau qu'il vient de monter.
4) IPnSCAN : le troisième oeil
4.1) fichier header : structures, liste chaînée, variables globales et macros
Dans notre fichier en-tête, nous aurons tout d'abord besoin des .h habituels (stdio.h, stdlib.h, string.h) mais aussi de deux structures, chacune correspondante à une version de l'adresse IP. Les versions 4 sont composées de 4 segments et les versions 6 de 8 segments. Nous allons également définir des constantes pour la taille du buffer (l'espace mémoire qui sera pris en compte lors des allocations mémoires), la sortie normale et la sortie d'erreur, un macro pour la suppression des logs en fin de scan. Nous allons également définir des macros pour la vitesse du scan, plus précisément c'est le timeout qui devra être régler (i.e le temps d'attente entre l'envoie d'un datagramme et sa réception, si ce temps est écoulé sans qu'un datagramme n'est été reçut, le datagramme envoyé esr considéré comme perdu), en effet un scan rapide sera moins efficace qu'un scan lent (il détectera moins d'IP active) mais sera...plus rapide :) (sisi je vous jure !). C'est pourquoi nous définirons 3 modes pour notre scan :
_Le mode SHERLOCK : timeout = 1000 ms.
_______________--__caractéristiques : scan lent mais minutieux, haute précision._Le mode NORMALE : timeout = 500 ms.
_______________-_-caractéristiques : vitesse normale, résultats corrects._Le mode BURN : timeout = 100 ms.
____________-_-caractéristiques : scan rapide mais peu de précision.
Voici quelques chiffres provenant d'un test que j'ai réaliser avec IPnSCAN qui relatent les performances des différents modes, test réalisé sur une plage de 100 adresses IP :
Mode burn : 24 ip actives détectées en 43 secondes. Conclusion : |
Ensuite comment allons nous réaliser l'envoie de nos requêtes ECHO, on pourrait se plonger dans les sockets mais a quoi bon réinventer la roue puisqu'il existe un outil sous Windows comme sous Unix qui fait déjà ça très bien, je veux parler de l'utilitaire ping bien sûr. Nous allons donc effectuer des appels à system() en lui fournissant notre requête, à savoir "ping -[OPTIONS] [ADRESSE]", nous pourrons avoir jusqu'à 2 options mais au minimum une à savoir l'option -n qui permet de déterminer le nombre de paquets à envoyer. On pourra utiliser une deuxième option qui sera l'option -w permettant de définir le timeout qui sera fonction du mode choisit par l'utilisateur. Par défaut le mode BURN sera sélectionner. A noter que la syntaxe de la requête sera différente selon que l'on travaille sur des adresses IP version 4 ou 6. C'est pourquoi nous définirons plusieurs macros pour la syntaxe de ces requêtes, ces derniers ne contiendrons évidemment qu'une partie de la requête puisque l'adresse cible n'a pas encore été choisie par l'utilisateur. Pour déduire le résultat du ping d'un point de vue "programmation" il suffira de récupérer le code de retour de l'appel system(), en effet ce dernier renverra 0 si une ECHO_REPLY est reçut, 1 dans le cas contraire. Donc si l'on reçoit 0 après l'execution de notre ping, l'adresse distante est active, inactive dans le cas contraire. A noter qu'en ce qui concerne les adresses IP version 6, c'est l'utilitaire ping6 que nous utiliserons.
Enfin un dernier problème se pose encore à nous avant de nous plonger dans le code : "comment sauvegarder les adresses IP actives détectées ?", 2 solutions : l'utilisation d'un fichier dans lequel on pourra écrire et lire ou l'utilisation d'une liste chaînée pour sauvegarder les adresses en mémoire. J'ai choisit la deuxième méthode car l'écriture et la lecture dans un fichier est bien plus lente qu'une allocation mémoire pour la sauvegarde d'une adresse. Les adresses IP actives détectées seront donc sauvegardées en mémoire selon la méthode suivante :
[Adresse 1 | ]-->[Adresse 2 | ]-->[Adresse 3| ]-->[Adresse 4| NULL ] |
Nous déclarerons en conséquence de façon globale une liste que l'on initialisera à NULL ainsi qu'un maillon. Nous aurons besoin d'une variable pour stocker le nombre d'adresses IP actives détectées et nous déclarerons bien évidemment le prototype de chaque fonction que l'on utilisera.
#include <stdio.h> /*printf(), scanf()...*/ #include <string.h> /*strlen(), strcmp()...*/ #include <stdlib.h> /*malloc(), exit()...*/ /*----- Définitions des constantes pour le réglage de la vitesse du scan -----*/ /*------ Définitions des constantes relatives à la syntaxe des requêtes ------*/ /*------------ Taille définit pour les allocations mémoires ------------------*/ /*--------------------- Constantes pour appel système ------------------------*/ /*----------------------------- Autres ---------------------------------------*/ /*------Définitions des structures relatives à la version des adresses IP-----*/ /*------------------------*/ typedef struct /*------------------------*/
/*---------------- Structure relative à la liste chainnée --------------------*/
/*------------- Variables globales nécessaires au programme ------------------*/
/*----------------------------------------------------------------------------- void demande(void); void saisiev4 (void); void saisiev6 (void); int decouper(char adresse1[], char adresse2[]); int ping4 (char adreschoix[], int timeout); int ping6 (char adreschoix[], int timeout); int verif_syntaxv4(adress_v4 adresse1, adress_v4 adresse2); int verif_syntaxv6(adress_v6 adresse1, adress_v6 adresse2); void calcul_adressv4(adress_v4 adresse1, adress_v4 adresse2, int timeout); void calcul_adressv6(adress_v6 adresse1, adress_v6 adresse2, int timeout); void incrementev4(adress_v4 adresse1, adress_v4 adresse2, int timeout); void incrementev6(adress_v6 adresse, adress_v6 adresse2, int timeout); list insert_tailing(char *adresse, list l); void lookupv4(void); void lookupv6(void); void namehotechoix(void); void namehotev4(char *adresse); void namehotev6(char *adresse); void credit(void); void usage(void); |
Bien rentrons dans le coeur de notre programme, nous allons réaliser plusieurs fonctions qui vont assez fidèlement se ressembler du fait que les opérations sur les adresses IP version 4 et 6 sont sensiblement identiques. Avant d'aller plus loin j'ai choisit d'augmenter légèrement la difficultée en voulant réaliser 2 modes pour notre programme : une version intéractive où l'utilisateur rentrera ses données au clavier étape par étape et une version en ligne de commande où les paramètres du scan seront directement rentrés directement via le parser, autrement dit en même temps que l'execution du programme et ce afin de rendre notre scanner "batchable" ce qui nous permettrait lancer plusieurs scans en parallèle via un fichier batch. Il a s'en dire que la version intéractive fera intervenir quelques fonctions de plus et il faudra également penser à purger les fonctions qui seront utilisées par la version en ligne de commande de toute instruction bloquante comme scanf(), getchar() etc...
En conséquence nous aurons besoin d'une fonction de demande pour le type de scan (IPv4 ou IPv6) :
void demande(void) { _int choix; _list aux; _compteurip=0; /* on initialise compteurip à 0*/ _/* libération de l'espace mémoire occupée par la liste chainnée contenant les _/*---------------------------------Menu--------------------------------------*/ _printf("--------[1 Scanner une plage d'adresse IP version 4\n"); |
A noter que l'on libère au début de la fonction l'espace mémoire occupé par la sauvegarde des adresses IP actives détectées lors d'un possible scan précédent.
Il nous faut également 2 fonctions de saisie (pour chaque version de l'adresse et elle ne seront utilisées que pour la version intéractive). Avant de donner le code de ces fonctions, attardons nous quelques instants sur un problème relatif aux adresses IP version 6. En effet pour effectuer des opérations sur des IPv6, le système requiert certains pilotes qui ne sont pas installés par défaut donc avant d'effectuer des "ping" sur ce type d'adresse il nous faudra tout d'abord nous assurer que ces pilotes soient bien installés et ce grâce à la commande [ ipv6 if ] qui effectue justement un test de présence de ces pilotes, mais nous utiliserons surtout le code de retour de cette commande : 0 si les pilotes sont installés, 1 dans le cas contraire :
void saisiev4 (void) { _adress_v4 adresse1, adresse2; _int choixmd; _unsigned int speed; _char choix, ip[20]; _printf("\nEntrez l'adresse ip correspondant au debut de la plage a scanner :" ________"\n\n------------------------------["); /* 1ere adresse */ _scanf("%d.%d.%d.%d%*c",&adresse1.d1,&adresse1.d2,&adresse1.d3,&adresse1.d4); _printf("\nEntrez l'adresse ip correspondant a la fin de la plage a scanner :" _switch(verif_syntaxv4(adresse1,adresse2)) /*------------------------------Modes------------------------------------*/ /*on transmet à calcul_adress() les 2 adresses éclatées*/ /*on incremente l'adresse pour scanner tte la plage*/ /*si le nbre d'ip actives est nulle (une fois que toutes les ft st terminées)*/ printf("\n\nAppuyez sur ENTREE pour retourner au menu...");
void saisiev6 (void) printf("\nEntrez l'adresse ip correspondant au debut de la plage a scanner : \n\n--------------------["); printf("\nEntrez l'adresse ip correspondant au debut de la plage a scanner : \n\n--------------------["); switch(verif_syntaxv6(adresse1,adresse2)) printf("\n\n----[Quel mode voulez-vous utiliser pour le scan ?"); switch(choixmd) /*-On fait un test pour déterminer si le pilote IPv6 est installé sur le système-*/ calcul_adressv6(adresse1, speed); incrementev6(adresse1, adresse2, speed); if (!compteurip) printf("\n\nAppuyez sur ENTREE pour retourner au menu..."); |
A noter que nous appelons dans cette fonction 2 autres fonctions encore non définies. l'une calcul_adress() (v4 ou v6) sera chargée de récupérer le code de retour de l'appel system() avec l'utilitaire ping (Rappel : le code de retour est 0 si une ECHO_REPLY est reçut, 1 dans le cas contraire) et de sauvegarder l'adresse dans notre liste chaînée si l'adresse se révèle active. Elle sera également chargée de déterminer la classe du réseau auquel appartient l'adresse, cette dernière se détermine en fonction du premier segment de l'adresse (revenez en arrière dans l'article si vous avez oubliez comment cela fonctionne) :
void calcul_adressv4(adress_v4 adresse, int timeout) { char *adress, classe; int res; /*réservation de l'espace mémoire pour stocker l'adresse*/ /*concaténation de l'adresse IP*/ /*on appelle la ft ping a laquelle on envoi l'adresse ip reconsituée avec les 2 fragements*/ if(classe != 'N')
void calcul_adressv6(adress_v6 adresse, int timeout) if((adress=(char*)malloc(BUFFER*sizeof(char)))==NULL) res=ping6(adress, timeout); if (!res) |
La deuxième fonction apellée dans nos fonctions saisie() est la fonction incrémente() (respectivement v4 pour saisiev4() et v6 pour saisiev6() ) qui va être chargée comme son som l'indique d'incrémenter l'adresse, qui vient d'être traitée, de 1 afin de pouvoir traitée l'adresse suivante et ce jusqu'à ce que l'adresse de fin de la plage à scanner rentrée par l'utilisateur est été atteinte :
void incrementev4(adress_v4 adresse1, adress_v4 adresse2, int timeout) { /*tant que l'ip de début et l'ip de fin ne st pas egalles*/ while (adresse1.d1!=adresse2.f1 || adresse1.d2!=adresse2.f2 || adresse1.d3!=adresse2.f3 || adresse1.d4!=adresse2.f4) { /*ip du type 1.1.1.2 qui devient 1.1.1.3*/ if (adresse1.d4!=255) { adresse1.d4++; calcul_adressv4(adresse1, timeout); } if (adresse2.f4 == adresse1.d4 && adresse1.d1 == adresse2.f1 && /*ip du type 1.1.1.255 qui devient 1.1.2.0*/
if (adresse2.f4 == adresse1.d4 && adresse1.d1 == adresse2.f1 && /*ip du type 1.1.255.255 qui devient 1.2.0.0*/ if (adresse2.f4 == adresse1.d4 && adresse1.d1 == adresse2.f1 && /*ip du type 1.255.255.255 qui devient 2.0.0.0*/
void incrementev6( adress_v6 adresse1, adress_v6 adresse2, int timeout) /*si la huitieme partie de l'adresse de fin egale FFFFF alors on appelle lookupv6*/
/*ip du type 1:1:1:1:1:1:1:FFFFF*/ if (adresse1.g8 == adresse2.h8 && adresse1.g7 == adresse2.h7 && /*ip du type 1:1:1:1:1:1:FFFFF:FFFFF*/
if (adresse1.g8 == adresse2.h8 && adresse1.g7 == adresse2.h7 &&
/*ip du type 1:1:1:1:1:FFFFF:FFFFF:FFFFF*/ if (adresse1.g8 == adresse2.h8 && adresse1.g7 == adresse2.h7 && /*ip du type 1:1:1:1:FFFFFF:FFFFFF:FFFFF:FFFFF*/
if (adresse1.g8 == adresse2.h8 && adresse1.g7 == adresse2.h7 && /*ip du type 1:1:1:FFFFFF:FFFFFF:FFFFFF:FFFFF:FFFFF*/ if (adresse1.g8 == adresse2.h8 && adresse1.g7 == adresse2.h7 && /*ip du type 1:1:FFFFFF:FFFFFF:FFFFFF:FFFFFF:FFFFF:FFFFF*/ if (adresse1.g8 == adresse2.h8 && adresse1.g7 == adresse2.h7 && /*ip du type 1:FFFFFF:FFFFFF:FFFFFF:FFFFFF:FFFFFF:FFFFF:FFFFF*/ |
Nous avons ensuite besoin de la fonction qui va réaliser l'appel system() avec l'utilitaire ping et renvoyer le code de retour de cet appel afin de pouvoir déterminer si l'adresse en cours de traitement est active ou non, nous utilisons cette fonction dans nos 2 fonctions calcul_adress() :
int ping4 (char *adreschoix, int timeout) { char *req_aux, *req, *mute=">nul"; int res; req_aux=V4_REQUEST; /*req="ping -n 1 -w "*/ /*réservation de l'espace mémoire nécessaire pour la requête*/ /*concaténation de la requête*/ res=system(req); /* res recoit le code de retour de l'appel système avec la requete :
int ping6 (char *adreschoix, int timeout)/* idem à ping4() à l'exception de la requête */ req_aux=V6_REQUEST; if((req=(char*)malloc(BUFFER*sizeof(char)))==NULL) sprintf(req,"%s %d %s%s", req_aux, timeout, adreschoix, mute); res=system(req); free(req); |
Vous avez peut-être remarqué que lorsque le scan est terminé (donc lorsque l'adresse correspondant à la fin de la plage à scanner à été atteinte) une fonction lookup(v4 ou v6) est appelée. Ces dernières n'ont d'autre utilité que d'afficher le contenu de la liste chaînée ayant sauvegardée l'ensemble des adresses IP actives détectées durant le scan :
void lookupv4(void) { if (compteurip==0) /* si compteurip=0 i.e si aucune ip actives n'a été détectée*/ return; /* on retourne au menu */ printf("\n\n%lu adresse(s) IP active(s) detectee(s) : \n", compteurip); while(l != NULL) /*on affiche l'ensemble du contenu de la liste*/
void lookupv6(void) /* idem à lookupv4() à l'exception de la requete*/ printf("\n\n%lu adresse(s) IP active(s) detectee(s) : \n", compteurip); while(l != NULL) |
Il ne faut pas non plus oublier de vérifier la synthaxe de chaque adresse rentrée par l'utilisateur afin de ne pas l'induire en erreur lors du scan par des codes de retour ne correspondant pas à la réalité. En effet plusieurs contraintes vont se poser, par exemple pour les adresses de version 4, chaque segment devra être compris entre 0 et 255, il ne faudra pas non plus que l'adresse de début soit supérieure à l'adresse de fin, le cas échéant une boucle infinie se mettrait en place :
int verif_syntaxv4(adress_v4 adresse1, adress_v4 adresse2) { /* vérification de la syntaxe de l'adresse de debut */ /*si l'un des membres de l'adresse ip de début est superieur à 255 ou inférieur à 0*/ if (adresse1.d1>255 || adresse1.d2>255 || adresse1.d3>255 || adresse1.d4>255) return (-1); /* vérification de la syntaxe de l'adresse de fin */ /* vérification de la cohérence de la plage entrée */ return SUCCESS;
int verif_syntaxv6(adress_v6 adresse1, adress_v6 adresse2) /* Adresse de fin */ |
Voici la fonction qui permet de sauvegarder les adresses IP actives détectées dans une liste chaînée :
list insert_tailing(char *adresse, list l) { /*réservation de l'espace mémoire pour un maillon*/ if((m=(maillon *)malloc(sizeof(maillon)))==NULL) { fprintf(stderr,"\n\n----[Problème d'allocation memoire !" "\n Abandon du reverse DNS." "\n\n Appuyez sur ENTREE pour quitter"); getchar(); exit(SUCCESS); } /*si la liste est vide, le maillon devient le seul élément de la liste*/ /* sinon on procède à un appel récursif afin de recommencer le test avec le return l; |
Pour terminer nous allons avoir besoin de 2 fonctions qui vont permettre de réaliser des reverse DNS sur des adresses choisies au préalable. Pour ceci nous utiliserons encore une fois l'utilitaire ping (ping6 pour les IPv6) avec l'option -a via un appel system() ce qui aura pour résultat de nous renvoyer le nom de la machine associée à l'adresse en cours de traitement. Si l'adresse n'est pas active, c'est l'adresse que l'on aura rentrée qui nous sera renvoyée, il suffira donc de le vérifier pour prévenir l'utilisateur au cas où l'adresse qu'il aurait rentrée ne serait pas active :
void namehotev4(char *adresse) /* idem à lookupv4 */ { char *req_aux, *req, *file=">log.txt", *host="Envoi", hostfind[100]; int i; FILE *hoste; /* pour le cas ou le fichier aurait déjà été ouvert (dans la version en ligne de commande par exemple) */ req_aux=V4_LOOKUP; /*req=ping -a*/ /*réservation de l'expace mémoire pour stocker la requête*/ /*req=ping -a ADRESSE_IP>result.txt*/ printf("\nExecution du reverse DNS sur l'adresse %s en cours...", adresse); system(req); /*on ouvre le fichier où est sauvegardé resultat de la requete*/ /*on recherche le nom d'hote parmis les informations*/ for (i=0; i < 5; i++) if(!strcmp(hostfind, adresse)) else fclose(hoste); /*on ferme log.txt*/ void namehotev6(char *adresse) /* idem à lookupv6 */ req_aux=V6_LOOKUP; if((req=(char*)malloc(BUFFER*sizeof(char)))==NULL) sprintf(req,"%s %s%s", req_aux, adresse, file); printf("\nExecution du reverse DNS sur l'adresse %s en cours...", adresse); system(req); if((hoste = fopen("log.txt", "r"))==NULL) while (strcmp(hostfind, host)!=0) for (i=0; i < 4; i++) if(!strcmp(hostfind, adresse)) else fclose(hoste); |
Voici le code du parser qui permettra à l'utilisateur de choisir entre une version intéractive du programme ou une version en ligne de commande, les fonctions utilisées sont les mêmes que précédemment et le code reste simple donc je pense que vous comprendrez sans trop de peine son fonctionnement :
int main(int argc, char **argv) if(argc<=1)/* si pas d'arguments on passe à la version intéractive */ for (i = 1; i < argc; i++) switch (arg[0]) case 'q': case 's': case 'v': case 'd' : case 'f' : default : |
Et voilà notre programme est terminé. Faisons quelques tests :
Bien ça à l'air de fonctionner, passons à la version en ligne de commande :
Aucun problème non plus. Les tests sur les adresses IP version 6 ont également été concluents.
Ainsi s'achève notre périple dans le petit monde des ECHO_REQUESTS & ECHO REPLIES. J'espère que le voyage vous a plût et pour ceux qui aimerait en savoir davantage je ne saurai trop vous conseiller de vous plonger dans l'article extrèmement complet et pédagogique de Ofir Arkin : L'ICMP USAGE IN SCANNING. Je vous conseille également de lire Le Scanning d'IP par requête ICMP Echo écrit par S/asH dans le RTC-Mag n°4 dans lequel ce dernier explique également comment coder un scanner d'IP mais cette fois en utilisant les sockets, cela peut donc être intéressant d'étudier les deux méthode s d'approche. Vous aurez compris je le pense après la lecture de cet article à quel point il peut être simple, en jouant avec les différents champs d'un paquet, de retirer le voile mince qui souvent recouvre un réseau afin d'en découvrir jusqu'à ses plus profonds secrets...
Le code source complet et indenté de IPnSCAN se trouve dans la partie [ Annexe ].
[1] Fyodor - http://www.insecure.org/ - nmap
[2] RFC en français - http://abcdrfc.free.fr/
[3] Ofir Arkin
- ICMP usage in scanning
[4] snort - http://www.snort.org/
[] Team RTC - http://www.rtc.fr.st/
[] Le meilleur site en ce qui concerne le hacking via les paquets réseaux :
http://www. phenoelit .de/
[] The Not
- A TCP/IP Tutorial : Behind The Internet - Phrack #34-0x08 - http://www.phrack.org/show.php?p=34&a=8/
[] Ofir Arkin & Fyodor Yarochkin -
ICMP based remote OS TCP/IP stack fingerprinting techniques - Phrack #57-0x07 - http://www.phrack.org/show.php?p=57&a=7/
BY ANDRAS
IPnSCAN a été écrit par ANDRAS & SKYRUNNER
Copyright © 2005 ARENHACK - DHS