C'était un de ces soirs durant lesquels je geeke jusqu'à tomber de sommeil, sauf que là j'étais en vacances. C'est ennuyeux, les vacances. Alors j'ai pris la (sage) décision de mettre à profit mon temps disponible pour avancer mes nombreux projets. Et comme à mon habitude, je n'ai pas pu m'empêcher de diverger et d'aller titiller d'autres sujets. Je ne sais plus trop pourquoi, mais je me suis intéressé à un moyen d'évaluer de manière fiable ou presque le trafic d'un serveur distant, et la première chose qui m'est venue à l'esprit est l'analyse du champ Identification des datagrammes IP.
Premiers tests, et premières déceptions
J'ai donc entamé la recherche d'une méthode efficace permettant d'évaluer le trafic d'un serveur distant, en me basant sur l'évolution du champ Identification du protocole IP. Ce champ est en réalité un compteur sur 16 bits (entier non-signé), incrémenté de 1 à chaque émission de paquet. Au vu des quantités de paquets émis par un serveur, ce compteur boucle assez régulièrement, et il est donc très difficile de déduire quoi que ce soit. Ce champ est aussi souvent appelé IP/ID.
J'ai donc commencé mes tests par le développement d'un outil en Python, utilisant Scapy, et émettant à intervalle très court deux requêtes ICMP Echo Request, calculant le temps de parcours depuis l'envoi, et essayant de déterminer la vitesse d'évolution des IP/ID. L'implémentation n'a pas été un problème en soit, par contre les résultats n'étaient pas au rendez-vous, et pour cause: la mesure de temps n'est pas fiable. En réalité, nous ne disposons que d'une référence pour cette mesure de temps: la machine émettrice. Le réseau Internet étant ce qu'il est nous ne sommes pas sûrs de plusieurs choses: * les paquets peuvent avoir été routés via des chemins différents entre les deux requêtes * des latences réseau ont pu se produire, et faire varier le temps d'émission./réception des paquets Autant de facteurs qui rendent la mesure imprécise, et au vu du laps de temps très court séparant l'envoi des deux paquets, l'erreur introduite est élevée. Il n'est donc pas fiable d'employer cette méthode pour déterminer la quantité de trafic qu'émet un serveur distant.
Les timestamps à la rescousse
Heureusement, le protocole ICMP met à notre disposition d'autres types de messages, comme celui permet de demander à une machine distante de nous envoyer des indications sur son horloge interne (Timestamp), ce qui a pour intérêt de pouvoir nous renseigner sur l'heure d'émission du paquet. En effet, le protocole ICMP étant un protocole de couche 4 (Transport), ses datagrammes sont encapsulés dans des datagrammes IP (protocole couche 3). Autrement dit, en envoyant un message ICMP Timestamp Request (Code:0, Type:13), le serveur distant peut y répondre avec un paquet ICMP contenant notamment un Timestamp paramétré par la machine ayant effectué la requête, et un Timestamp correspondant à l'heure d'émission de la réponse.
Il ne me fallait pas plus de choses pour que j'implémente une variante de l'outil précédent, prenant en compte cette fois-ci une mesure de temps beaucoup plus précise. Bien sûr, il faut que le serveur distant testé réponde à ce type de requête ICMP, ce qui n'est pas monnaie courante. Mais j'ai pu identifier certains sites connus qui y répondent favorablement: www.hsc.fr, www.lemonde.fr, www.lefigaro.fr, etc ... L'exemple ci-dessous, réalisé avec l'utilitaire hping2, démontre bien les réponses reçues:
virtubox:/home/virtualabs# hping2 -1 -K 0 -C 13 -r www.hsc.fr HPING www.hsc.fr (wlan0 217.174.211.25): icmp mode set, 28 headers + 0 data bytes len=40 ip=217.174.211.25 ttl=53 id=21302 icmp_seq=0 rtt=23.3 ms ICMP timestamp: Originate=2647898 Receive=2647955 Transmit=2647955 ICMP timestamp RTT tsrtt=24 len=40 ip=217.174.211.25 ttl=53 id=+15 icmp_seq=1 rtt=24.0 ms ICMP timestamp: Originate=2648899 Receive=2648955 Transmit=2648955 ICMP timestamp RTT tsrtt=24 len=40 ip=217.174.211.25 ttl=53 id=+6 icmp_seq=2 rtt=23.5 ms ICMP timestamp: Originate=2649899 Receive=2649955 Transmit=2649955 ICMP timestamp RTT tsrtt=23
Le timestamp qui m'intéressa fut celui dénommé Transmit, correspondant à l'heure de transmission. Ce timestamp est en réalité le nombre de millisecondes écoulées depuis 00h00, et est donc suffisamment précis pour être utilisé. De là, j'ai développé un premier outil affichant un aperçu visuel du trafic, toujours en Python avec la bibliothèque curses. Le résultat n'est pas super joli, mais fait l'affaire:
J'étais assez satisfait du résultat, bien que cet outil ne casse pas trois pattes à un canard. Néanmoins, après différents tests sur des serveurs publics, j'ai pu noter les inconvénients suivants:
Et cela a commencé à m'ennuyer, je suis donc reparti à la recherche de la méthode ultime permettant de déterminer dans tous les cas de figures la quantité de trafic supporté par une machine distante. Et c'est devenu un poil plus complexe ...
Approche probabiliste
Je me suis rapidement rendu à l'évidence, après plusieurs tentatives infructueuses: réaliser une étude analytique pure de la variation du champ IP/ID est une pure perte de temps, à cause de la faible entropie de ce compteur. Il me fallait partir dans une autre direction, et trouver une approche différente, mais toutefois fiable. Et c'est là que m'est venu l'idée de prendre le problème dans l'autre sens. Si une machine subit un trafic conséquent, alors il devrait être très difficile de prédire la valeur du prochain IP/ID retourné. Il s'agit ici d'une simple constatation probabiliste. Plus il y a de trafic, plus le champ IP/ID varie, et plus il est difficile de prédire sa prochaine valeur. Il existe toutefois un cas de figure, mais qui reste très peu probable: celui où le trafic est conséquent et constant.
Il m'a donc semblé intéressant de tenter cette approche. J'ai donc entamé le développement d'une preuve de concept, toujours en Python, qui visait à réussir une prédiction pour un serveur donné. Malheureusement, ceci est très difficile à obtenir, surtout lorsque l'on cherche à prédire exactement la valeur du prochain IP/ID. J'ai donc introduit une variable représentant une marge d'erreur augmentant progressivement (suite aux tests infructueux), et en faisant varier cette marge d'erreur de manière exponentielle, j'ai pu obtenir des résultants probant.
La recherche de prédiction exacte et/ou approximative permet, lorsqu'elle est réalisée sur une durée suffisamment longue, de pouvoir associer un score (heuristique) à la quantité de trafic émis par la machine distante. Il y a dans le cas présent une relation qui lie cette quantité de trafic (et de manière indirecte, la variation de cette quantité de trafic) à la difficulté de prédiction du champ IP/ID. En évaluant la difficulté de prédiction, on peut tenter de déduire la quantité de trafic. Il sagit d'une méthode probabiliste, aboutissant à un résultat fiable (qualitatif) mais non-quantitatif, permettant d'apprécier la quantité de trafic par rapport à un trafic de référence. Il semblerait toutefois qu'en cas de présence de répartiteurs de charge ou lorsque l'intervalle de temps entre deux mesures est trop grand, la mesure ne soit pas fiable.
To be continued ...
Je suis actuellement en train d'approfondir cette méthode d'évaluation probabiliste, en tentant notamment de lier la difficulté de prédiction d'IP/ID à la quantité de paquets émis par le serveur, bien que cela ne me semble pas évident du tout. De même, je continue mes tests afin de vérifier la validité de mes résultats, bien que pour le moment ils soient positifs.
Je publierai mon analyse dans les détails par la suite, ainsi que le code source de ma preuve de concept.
Depuis quelques semaines, le site YouHaveDownloaded fait parler de lui, et tout récemment encore à propos de supposés téléchargements illégaux réalisés par des personnes au sein du Ministère de la Culture, situé rue de Valois à Paris. Nicolas Perrier s'en est d'ailleurs fait écho via un post sur son blog, et c'est via Google+ que j'ai pu en discuter avec lui.
Le protocole BitTorrent
Avant toute chose, je tiens à apporter quelques précisions sur le fonctionnement du protocole BitTorrent, et en particulier sur son architecture et sur les données stockées sur le serveur central (tracker). Le système BitTorrent repose sur un serveur central, serveur qui a donc connaissance de toutes les adresses IP des utilisateurs connectés, et qui les distribue à qui les demande. Non, il n'y a aucune authentification ni vérification faites sur les utilisateurs réclamant des adresses IP, tout au plus une limitation sur le nombre de requêtes de récupération d'adresses IP réalisées à l'heure.
Chaque fichier partagé sur BitTorrent est référencé par un hash SHA1 qui l'identifie de manière unique. Ce hash est appelé infohash, et est fourni par les utilisateurs souhaitant partager un fichier. Pour partager un fichier, le logiciel BitTorrent (ou tout autre client BitTorrent) effectue une requête HTTP sur une URL d'annonce, en passant en paramètres différentes informations, telles que recensées sur le site contenant les spécifications du protocole:
<quote> * info_hash: urlencoded 20-byte SHA1 hash of the value of the info key from the Metainfo file. Note that the value will be a bencoded dictionary, given the definition of the info key above. * peer_id: urlencoded 20-byte string used as a unique ID for the client, generated by the client at startup. This is allowed to be any value, and may be binary data. There are currently no guidelines for generating this peer ID. However, one may rightly presume that it must at least be unique for your local machine, thus should probably incorporate things like process ID and perhaps a timestamp recorded at startup. See peer_id below for common client encodings of this field. * port: The port number that the client is listening on. Ports reserved for BitTorrent are typically 6881-6889. Clients may choose to give up if it cannot establish a port within this range. * uploaded: The total amount uploaded (since the client sent the 'started' event to the tracker) in base ten ASCII. While not explicitly stated in the official specification, the concensus is that this should be the total number of bytes uploaded. downloaded: The total amount downloaded (since the client sent the 'started' event to the tracker) in base ten ASCII. While not explicitly stated in the official specification, the consensus is that this should be the total number of bytes downloaded. * left: The number of bytes this client still has to download in base ten ASCII. compact: Setting this to 1 indicates that the client accepts a compact response. The announce-list is replaced by a peers string with 6 bytes per peer. The first four bytes are the host (in network byte order), the last two bytes are the port (again in network byte order). It should be noted that some trackers only support compact responses (for saving bandwidth) and either refuse requests without "compact=1" or simply send a compact response unless the request contains "compact=0" (in which case they will refuse the request.) [...] * ip: Optional. The true IP address of the client machine, in dotted quad format or rfc3513 defined hexed IPv6 address. Notes: In general this parameter is not necessary as the address of the client can be determined from the IP address from which the HTTP request came. The parameter is only needed in the case where the IP address that the request came in on is not the IP address of the client. This happens if the client is communicating to the tracker through a proxy (or a transparent web proxy/cache.) It also is necessary when both the client and the tracker are on the same local side of a NAT gateway. The reason for this is that otherwise the tracker would give out the internal (RFC1918) address of the client, which is not routable. Therefore the client must explicitly state its (external, routable) IP address to be given out to external peers. Various trackers treat this parameter differently. Some only honor it only if the IP address that the request came in on is in RFC1918 space. Others honor it unconditionally, while others ignore it completely. In case of IPv6 address (e.g.: 2001:db8:1:2::100) it indicates only that client can communicate via IPv6. * numwant: Optional. Number of peers that the client would like to receive from the tracker. This value is permitted to be zero. If omitted, typically defaults to 50 peers. </quote>
Le champ ip est un champ optionnel, mais le tracker peut le prendre en compte en lieu et place de l'adresse IP réelle effectuant la requête. Cependant la plupart des implémentations de trackers supportent ce paramètre sans le prendre en compte. Il est donc possible de spécifier une adresse IP autre que celle qui permet d'envoyer la requête, et donc potentiellement d'injecter des adresses IP falsifiées dans la liste des utilisateurs connectés, en envoyant une requête d'annonce avec une adresse IP falsifiée. Attention toutefois, certains trackers sont quand même sensibles à ce type d'injection.
La méthode TMG/YouHaveDownloaded
Un système de flicage de clients BitTorrent ne peut donc pas se fier qu'aux adresses IP fournies par le serveur central, le tracker, mais doit effectuer une vérification pro-active. Cette vérification est notamment faite via une connection aux adresses IP soupçonnées, et une requête permettant de valider si la personne partage une portion du fichier incriminé. Si oui, son adresse IP est répertoriée, et c'est a priori sur ce principe qu'est basée la détection de TMG.
YouHaveDownloaded utilise certainement le même type de détection, mais sans faire de vérification directe car il s'agit là d'une méthode lourde et fastidieuse. Du moins, c'est ce que l'on peut déduire des informations retournées par ce site, et notamment celles qui ont fait du bruit récemment: environ 250 adresses IP publiques appartenant à une plage d'adresses attribuée au Ministère de la Culture ont servi à télécharger des fichiers divers. Deux explications possibles:
Le problème de la méthode YouHaveDownloaded, c'est que les trackers servant de référence ne sont pas documentés sur le site, et il suffit qu'un seul de ces trackers soit vulnérable à une injection d'adresse IP pour que la totalité de la base de données créée à partir de ce tracker ne soit plus crédible. A mon humble avis, cette possibilité est très fortement probable, ce qui expliquerait les cas similaires observés de par le monde, aux USA et au Royaume-Uni notamment. Lorsque l'on sait que des hackers ont lancé il y a de cela presque deux ans des séries d'offensives contre l'HADOPI lors des discussions parlementaires et du vote, et fait la promotion d'un outil nommé seedfuck visant à injecter des adresses IP du Ministère de la Culture (mais pas que) dans des trackers de renom, il semble probable que ces mêmes personnes en veuille toujours au système mis en place.
MAJ du jeudi 29 décembre 2012: Un lecteur m'a signalé aussi le fait que les propriétaires de trackers (`et tout spécialement ceux de ThePirateBay <http://www.numerama.com/magazine/10885-the-pirate-bay-rend-la-riposte-graduee-dangereuse-pour-tous.html>`_) avaient parlé il y a de cela un moment d'injecter eux-mêmes des adresses IP falsifiées dans leurs trackers, ce qui est tout à fait probable mais peut aboutir à une baisse des performances du tracker si cette injection est trop présente (tout comme seedfuck abaisse les performances du tracker pour le fichier qu'il empoisonne).
BitTorrent, tu peux pas test.
Afin de vérifier cela, j'ai développé un petit outil (très rapidement, c'est-à-dire en commentant peu et pas très proprement, oui j'ai honte) permettant de tester l'injection d'adresse IP sur un tracker à partir de son URL d'annonce. Vous trouverez des URLs d'annonce sur ce site par exemple, afin de tester l'outil. Ce bout de code Python annonce un fichier inexistant (donc considéré comme nouveau) en précisant un paramètre ip, et vérifie que celui-ci est bien fourni ensuite par le serveur aux autres utilisateurs. L'outil seedfuck ne faisait pas cette vérification, et ne fonctionnait donc pas sur la grande majorité des trackers.
Je me suis servi de cet outil sur la plupart des "gros" trackers BitTorrent, et il s'avère que ceux-ci ne sont pas vulnérables. Néanmoins, je mets cet outil à disposition, cela pourrait servir ...
Certains d'entre vous le savent peut-être déjà, mais nous avons perdu un hacker et très bon ami samedi dernier, le 3 décembre 2011. La nouvelle s'est répandu assez rapidement dans son entourage, puis dans son cercle d'amis. CrashFr, hacker au grand coeur qui a créé il y a de cela plusieurs années Hacker'z Voice, est décédé à l'âge de 30 ans. Brutalement.
Je l'ai rencontré il y a plusieurs années, durant les nombreuses Nuit du Hack qu'il a organisé, et auxquelles j'ai pu participer. Un type formidable, ce CrashFr. C'est grâce à lui notamment qu'on a pu monter certains magazines (qui n'ont pas forcément marché), que l'on a pu faire entendre la voix des hackers et faire valoir leurs compétences et leur savoir-faire. Il s'est toujours battu pour ses idées, et il était toujours le premier à foncer. C'était une de ces personnes qui ne s'avouaient jamais vaincues, même lorsque le moral était au plus bas ou que rien ne laissait présager de bon. Un battant, ce Paolo. A plusieurs reprises je l'ai vu confronté à des problèmes, retourner dans tous les sens des questions, mais sans qu'il perde son optimisme ni sa bonne humeur.
On ne s'y attendait pas. Personne. Mais la vie est cruelle, et si fragile. Trop même. Je n'ai pas les mots justes pour décrire dans quel état d'esprit je suis, certainement trop bouleversé pour pouvoir réaliser exactement ce qu'il s'est passé, je ne sais pas comment appréhender les jours à venir. A tous ceux qui lui étaient proches, je vous souhaite du courage pour surmonter cette épreuve. Un hacker de renom nous a quitté, sans crier gare, sans même qu'on ait eu le temps de crier. Et s'il y a bien une chose que j'ai envie de faire en ce moment, c'est bien ca, crier. Mais il ne reviendra pas. J'ai beau essayer, ca ne change pas. Toujours cette même réalité, ce mauvais cauchemar qui n'en est pas un. Tu es parti mon ami. Et je réalise maintenant à quel point tu comptais vraiment.
Tu resteras toujours avec nous, dans nos mémoires de geeks, toi le hacker à la casquette. Repose en paix.