Présentation de TorPylle

Comme j’aime bien l’analyse de protocoles réseau, je me suis intéressé au protocole Tor, dont le logiciel Tor est la seule implémentation à ma connaissance. Et comme j’aime bien Scapy, j’ai commencé à écrire une implémentation du protocole Tor en Scapy, TorPylle (annoncée ici et il y a quelques temps déjà, et publiée sur GitHub et BitBucket).

Attention

TorPylle n’est pas (et ne se veut pas) un client Tor. C’est simplement un outil permettant de jouer avec les autres implémentations de ce protocole, comme Scapy permet de jouer avec les implémentations de protocoles réseau.

Il ne faut donc surtout pas voir en TorPylle une alternative à Tor, pas plus qu’il ne faut voir Scapy comme une alternative possible à la pile réseau d’un système d’exploitation.

Contenu

TorPylle contient (ou a vocation à contenir) :

  • des constantes définies par le protocole (par exemple, les paramètres utilisés pour Diffie-Hellman) ou dans l’implémentation Tor (par exemple, les directory servers par défaut) ;
  • des classes correspondant aux différents types de cells (ce sont les messages du protocole, des paquets au sens de Scapy) ;
  • une classe fille de StreamSocket pour communiquer avec les nœuds d’entrée ;
  • des classes pour manipuler les objets du protocole Tor (nœud, circuit, etc.) ;
  • quelques fonctions pour utiliser le tout.

Exemple

Voici un extrait commenté du fichier examples.py.

import torpylle

# on ajoute les directory servers codés en dur dans Tor
torpylle.add_default_directory_authorities()

# on choisit un directory server au hasard pour lui demander le
# "consensus". NB: on lui fait confiance aveuglément, ce qui serait
# une très mauvaise idée si on voulait réellement utiliser Tor.
d = torpylle.random.choice(torpylle.DIRECTORY_SERVERS)
d.parse_consensus()

# maintenant on choisit un nœud d'entrée, qui ait les flags Fast,
# Running (c'est mieux) et Guard, et dont la version soit compatible
# avec le protocole 3.
n1 = torpylle.random.choice(torpylle.search_node(
                            flags=['Fast', 'Running', 'Guard'],
                            minversion=torpylle.torminversionproto3))
# et on récupère les informations sur ce nœud
d.get_node_info(n1)
# puis on s'y connecte
## La création de l'objet entraine la création de la socket SSL
s = torpylle.TorSocket(torpylle.KNOWN_NODES[n1])
## puis on échange les premiers "cells" (on "parle" Tor)
s.init_connection()

# Quelques infos de connexion
print "Mon adresse publique", s.public_address.Address
print "Connecté à", ', '.join([x.Address for x in s.node.addresses])

# On crée le circuit (nouvel échange de "cells")
c = s.create(fast=False)

# De la même manière que pour le nœud d'entrée, on choisit un nœud
# de sortie (notre circuit comportera seulement deux nœuds) qui ait
# les flags Fast, Exit et Running. Cette fois-ci, pas de prérequis sur
# la version.
n2 = torpylle.random.choice(torpylle.search_node(flags=['Fast', 'Exit', 'Running']))
d.get_node_info(n2)

# On étend maintenant notre circuit avec le nœud choisi
c.extend(torpylle.KNOWN_NODES[n2])

# Notre circuit est prêt :

## On effectue une résolution DNS dans Tor
rslv = c.resolve("www.google.com")
print "www.google.com:", rslv[0].Address

## On crée une connexion et on l'utilise
strm = c.connect((rslv[0].Address, 80))
c.send(strm[0], 'GET / HTTP/1.0\r\n\r\n')
data = ''
da = c.recv()[1]
while da:
    data += da
    da = c.recv()[1]

print "GET http://www.google.com/:"
print data[:80]
if len(data) > 80:
    print '[...]'

Ce code montre l’utilisation de fonctions de (relativement) haut niveau, il est possible avec TorPylle de se placer bien plus bas.

L’implémentation de la méthode Circuit.resolve() en fournit un exemple :

def resolve(self, name):
    streamid = RandShort()._fix()
    self.socket.send(Raw(self.encrypt_cell(
                CellRelay(CircID=self.circid,
                          RelayCommand="RELAY_RESOLVE",
                          StreamID=streamid,
                          Data=name))))
    r = self.decrypt_cell(self.socket.recv_cell("RELAY"))
    return r.Address, r.TTL

On note ici l’utilisation de la méthode circuit.encrypt_cell() qui effectue les appels successifs à circuit.stream_encrypt() pour chaque relais du circuit. En poursuivant ainsi, on arrive aux appels (et paramètres) de STREAM_FUNC(), qui n’est autre que Crypto.Cipher.AES().

Pour le reste, c’est assez lisible : on envoie dans la socket réseau le cell chiffré (les champs CircID et RelayCommand restent cependant en clair) correspondant à une commande de résolution pour le nom name.

À quoi ça sert ?

Excellente question… ça peut servir à comprendre le protocole, à interagir finement avec un relais Tor, à regarder comment fonctionne le réseau, etc.

En bref, c’est un outil pour travailler (faire de la recherche, du développement, etc.) sur le réseau, le logiciel et le protocole Tor.

Feedback

Si vous utilisez ce code, si vous l’avez lu et/ou si le sujet vous parle, votre avis m’intéresse!