PROJET AUTOBLOG


®om's blog

Site original : ®om's blog

⇐ retour index

Mise à jour

Mise à jour de la base de données, veuillez patienter...

Gnirehtet

jeudi 30 mars 2017 à 11:50

Durant ces dernières semaines chez Genymobile, j’ai développé un outil de reverse tethering pour Android, permettant aux téléphones (et aux tablettes) d’utiliser la connexion internet de l’ordinateur sur lequel ils sont branchés, sans accès root (ni sur le téléphone, ni sur le PC). Il fonctionne sur GNU/Linux, Windows et Mac OS.

Nous avons décidé de le publier en open source, sous le nom de gnirehtet.

Oui, c’est un nom bizarre, jusqu’à ce qu’on réalise qu’il s’agit du résultat de la commande bash :

rev <<< tethering

Utilisation

Il suffit de télécharger la dernière release, de l’extraire, et d’exécuter la commande suivante sur le PC :

./gnirehtet rt

Une fois activé, un logo en forme de clé apparaît dans la barre de statut du téléphone :

key

Lisez le fichier README pour plus de détails.

Fonctionnement

Le projet est composé de deux parties :

Depuis, je l’ai réécrit en Rust.

Le client s’enregistre en tant que VPN, de manière à intercepter tout le trafic réseau du téléphone, sous la forme de byte[] de paquets IPv4 bruts, qu’il transmet alors vers le serveur relais sur une connexion TCP (établie par-dessus adb).

Le serveur relais analyse les en-têtes des paquets, ouvre des connexions à partir du PC vers les adresses de destinations demandées, et relaie le contenu dans les deux sens en suivant les protocoles UDP et TCP. Il crée et renvoie des paquets de réponse vers le client Android, qui les écrit sur l’interface VPN.

D’une certaine manière, le serveur relais se comporte comme un NAT, en cela qu’il ouvre des connexions pour le compte d’autres machines qui n’ont pas accès au réseau. Cependant, il diffère des NAT standards dans la manière dont il communique avec les clients, en utilisant un protocole spécifique (très simple) sur une connexion TCP.

archi

Pour plus de détails, lisez la page développeurs.

Conception

Une fois que l’application est capable d’intercepter l’intégralité du traffic réseau du téléphone, différentes approches sont possibles. Voici celles que j’ai considérées.

TL;DR: J’ai d’abord étudié l’utilisation d’un “TUN device” sur le PC, mais ça ne répondait pas à nos besoins. J’ai ensuite voulu utiliser SOCKS pour bénéficier des serveurs existants, mais des contraintes nous empêchaient de relayer le trafic UDP. Alors j’ai implémenté gnirehtet.

TUN device

Lors de mes recherches pour savoir comment implémenter le reverse tethering, j’ai d’abord trouvé des projets créant un TUN device sur l’ordinateur (vpn-reverse-tether and SimpleRT).

Cette conception fonctionne très bien, et a plusieurs avantages :

Cependant :

Il se peut néanmoins que ces applications répondent davantage à vos besoins.

SOCKS

Afin d’éviter d’avoir à développer un serveur relais spécifique, ma première idée était d’écrire un client qui parlait le protocole SOCKS (suivant le RFC 1928). Ainsi, il serait possible d’utiliser n’importe quel serveur SOCKS existant, par exemple celui fourni par ssh -D.

Vous l’avez probablement déjà utilisé pour éviter le filtrage des pare-feux en entreprise. Pour cela, démarrez le tunnel :

ssh mon_serveur -ND1080

Puis configurez votre navigateur pour utiliser le proxy SOCKS localhost:1080. N’oubliez pas d’activer la résolution DNS distante pour résoudre les noms de domaine à partir de mon_serveur (dans Firefox, activez network.proxy.socks_remote_dns dans about:config).

Malheureusement, l’implémentation d’OpenSSH ne supporte pas UDP, même si le protocole SOCKS5 lui-même le supporte. Et nous avons besoin d’UDP, au moins pour les requêtes DNS (ainsi que pour NTP).

Si vous avez lu attentivement les deux paragraphes précédents, vous devriez vous demander :

Comment Firefox peut-il résoudre les noms de domaine à distance alors que le proxy SOCKS d’OpenSSH ne supporte même pas UDP ?

La réponse se trouve dans la section 4 du RFC : l’adresse de destination demandée peut être une IPv4, une IPv6 ou un nom de domaine. Par contre, pour utiliser cette fonctionnalité, le client (par exemple Firefox) doit savoir qu’il passe par un proxy (puisqu’il doit explicitement passer le nom de domaine au lieu de le résoudre localement), alors que notre reverse tethering doit être transparent.

Mais tout n’est pas perdu. Certes, OpenSSH ne supporte pas UDP, mais ce n’est qu’une implémentation spécifique, nous pourrions en utiliser une autre. Malheureusement, SOCKS5 relaie UDP sur UDP, et les téléphones et l’ordinateur communiquent sur adb (grâce à adb reverse), qui ne supporte pas non plus la redirection de ports UDP.

Peut-être que nous pourrions au moins relayer les requêtes DNS en les forçant à utiliser TCP, comme le fait tsocks :

tsocks will normally not be able to send DNS queries through a SOCKS server since SOCKS V4 works on TCP and DNS normally uses UDP. Version 1.5 and up do however provide a method to force DNS lookups to use TCP, which then makes them proxyable.

Mais finalement, SOCKS n’est plus une solution aussi attirante pour implémenter le reverse tethering.

Gnirehtet

Par conséquent, j’ai développé à la fois le client et le serveur relais manuellement.

Ce billet de blog et différents projets open source (SimpleRT, vpn-reverse-tether, LocalVPN et ToyVpn) m’ont beaucoup aidé à comprendre comment implémenter cette solution de reverse tethering.

Conclusion

Gnirehtet permet aux téléphones et tablettes Android d’utiliser facilement la connection internet d’un ordinateur par USB, sans accès root. C’est très utile quand vous ne pouvez pas accéder au réseau par un point d’accès WiFi.

J’espère qu’il pourra être utile à certains d’entre vous.

Discussions sur reddit et Hacker News.

Gnirehtet

jeudi 30 mars 2017 à 11:50

Durant ces dernières semaines chez Genymobile, j’ai développé un outil de reverse tethering pour Android, permettant aux téléphones (et aux tablettes) d’utiliser la connexion internet de l’ordinateur sur lequel ils sont branchés, sans accès root (ni sur le téléphone, ni sur le PC). Il fonctionne sur GNU/Linux, Windows et Mac OS.

Nous avons décidé de le publier en open source, sous le nom de gnirehtet.

Oui, c’est un nom bizarre, jusqu’à ce qu’on réalise qu’il s’agit du résultat de la commande bash :

rev <<< tethering

Utilisation

Il suffit de télécharger la dernière release, de l’extraire, et d’exécuter la commande suivante sur le PC :

./gnirehtet rt

Une fois activé, un logo en forme de clé apparaît dans la barre de statut du téléphone :

key

Lisez le fichier README pour plus de détails.

Fonctionnement

Le projet est composé de deux parties :

Le client s’enregistre en tant que VPN, de manière à intercepter tout le trafic réseau du téléphone, sous la forme de byte[] de paquets IPv4 bruts, qu’il transmet alors vers le serveur relais sur une connexion TCP (établie par-dessus adb).

Le serveur relais analyse les en-têtes des paquets, ouvre des connexions à partir du PC vers les adresses de destinations demandées, et relaie le contenu dans les deux sens en suivant les protocoles UDP et TCP. Il crée et renvoie des paquets de réponse vers le client Android, qui les écrit sur l’interface VPN.

D’une certaine manière, le serveur relais se comporte comme un NAT, en cela qu’il ouvre des connexions pour le compte d’autres machines qui n’ont pas accès au réseau. Cependant, il diffère des NAT standards dans la manière dont il communique avec les clients, en utilisant un protocole spécifique (très simple) sur une connexion TCP.

archi

Pour plus de détails, lisez la page développeurs.

Conception

Une fois que l’application est capable d’intercepter l’intégralité du traffic réseau du téléphone, différentes approches sont possibles. Voici celles que j’ai considérées.

TL;DR: J’ai d’abord étudié l’utilisation d’un “TUN device” sur le PC, mais ça ne répondait pas à nos besoins. J’ai ensuite voulu utiliser SOCKS pour bénéficier des serveurs existants, mais des contraintes nous empêchaient de relayer le trafic UDP. Alors j’ai implémenté gnirehtet.

TUN device

Lors de mes recherches pour savoir comment implémenter le reverse tethering, j’ai d’abord trouvé des projets créant un TUN device sur l’ordinateur (vpn-reverse-tether and SimpleRT).

Cette conception fonctionne très bien, et a plusieurs avantages :

Cependant :

Il se peut néanmoins que ces applications répondent davantage à vos besoins.

SOCKS

Afin d’éviter d’avoir à développer un serveur relais spécifique, ma première idée était d’écrire un client qui parlait le protocole SOCKS (suivant le RFC 1928). Ainsi, il serait possible d’utiliser n’importe quel serveur SOCKS existant, par exemple celui fourni par ssh -D.

Vous l’avez probablement déjà utilisé pour éviter le filtrage des pare-feux en entreprise. Pour cela, démarrez le tunnel :

ssh mon_serveur -ND1080

Puis configurez votre navigateur pour utiliser le proxy SOCKS localhost:1080. N’oubliez pas d’activer la résolution DNS distante pour résoudre les noms de domaine à partir de mon_serveur (dans Firefox, activez network.proxy.socks_remote_dns dans about:config).

Malheureusement, l’implémentation d’OpenSSH ne supporte pas UDP, même si le protocole SOCKS5 lui-même le supporte. Et nous avons besoin d’UDP, au moins pour les requêtes DNS (ainsi que pour NTP).

Si vous avez lu attentivement les deux paragraphes précédents, vous devriez vous demander :

Comment Firefox peut-il résoudre les noms de domaine à distance alors que le proxy SOCKS d’OpenSSH ne supporte même pas UDP ?

La réponse se trouve dans la section 4 du RFC : l’adresse de destination demandée peut être une IPv4, une IPv6 ou un nom de domaine. Par contre, pour utiliser cette fonctionnalité, le client (par exemple Firefox) doit savoir qu’il passe par un proxy (puisqu’il doit explicitement passer le nom de domaine au lieu de le résoudre localement), alors que notre reverse tethering doit être transparent.

Mais tout n’est pas perdu. Certes, OpenSSH ne supporte pas UDP, mais ce n’est qu’une implémentation spécifique, nous pourrions en utiliser une autre. Malheureusement, SOCKS5 relaie UDP sur UDP, et les téléphones et l’ordinateur communiquent sur adb (grâce à adb reverse), qui ne supporte pas non plus la redirection de ports UDP.

Peut-être que nous pourrions au moins relayer les requêtes DNS en les forçant à utiliser TCP, comme le fait tsocks :

tsocks will normally not be able to send DNS queries through a SOCKS server since SOCKS V4 works on TCP and DNS normally uses UDP. Version 1.5 and up do however provide a method to force DNS lookups to use TCP, which then makes them proxyable.

Mais finalement, SOCKS n’est plus une solution aussi attirante pour implémenter le reverse tethering.

Gnirehtet

Par conséquent, j’ai développé à la fois le client et le serveur relais manuellement.

Ce billet de blog et différents projets open source (SimpleRT, vpn-reverse-tether, LocalVPN et ToyVpn) m’ont beaucoup aidé à comprendre comment implémenter cette solution de reverse tethering.

Conclusion

Gnirehtet permet aux téléphones et tablettes Android d’utiliser facilement la connection internet d’un ordinateur par USB, sans accès root. C’est très utile quand vous ne pouvez pas accéder au réseau par un point d’accès WiFi.

J’espère qu’il pourra être utile à certains d’entre vous.

Cet article est également disponible en anglais sur Medium.

Serveur-client

dimanche 12 mars 2017 à 23:17

L’objectif de ce billet est de parvenir à nous connecter à un serveur a priori inaccessible derrière un NAT.

Client-serveur

De nos jours, TCP est toujours utilisé en mode client-serveur :

Une fois la connexion établie, cependant, le client et le serveur jouent exactement le même rôle au niveau de la communication. Par contre, très souvent, leur rôle applicatif dépend directement de celui qui a initié la connexion :

ssh

Ce fonctionnement paraît tellement naturel que “client” désigne bien souvent à la fois celui qui initie la connexion et celui qui effectue des requêtes (au serveur), alors que “serveur” désigne aussi bien la partie en écoute que celle qui répondra aux requêtes (des clients).

Puis vint le NAT…

Avec la pénurie d’adresses IPv4, le NAT s’est généralisé. Bien souvent, un accès internet ne fournit qu’une seule adresse IPv4. Les différents ordinateurs partageant la même connexion ne sont alors pas accessibles directement depuis l’extérieur (il est nécessaire d’ouvrir des ports).

Ainsi, derrière un NAT sans ports ouverts, un serveur ne sera pas accessible publiquement. Par contre, un client pourra continuer à se connecter à n’importe quel serveur public.

ssh-nat

Inversion des rôles

Il existe des situations pour lesquelles nous souhaitons qu’un logiciel joue le rôle de serveur au niveau applicatif, afin de répondre aux requêtes des clients, mais client au niveau de la communication, afin de passer les NATs sans difficultés.

Par exemple, nous pouvons vouloir accéder, grâce à VNC ou SSH, à un ordinateur se trouvant derrière un NAT sur lequel, par hypothèse, nous n’avons pas la main. Dans ce cas, seul le serveur (au sens applicatif) aura la capacité d’ouvrir une connexion vers le client.

Logiciel dédié

Il est possible d’utiliser un logiciel spécialement conçu pour gérer cette inversion des rôles. C’est le cas par exemple de gitso, qui inverse le protocole VNC afin de simplifier l’aide de novices à distance.

Cette solution a cependant l’inconvénient d’être très spécifique, nécessitant un développement supplémentaire pour chaque protocole.

Redirection de port distant via SSH

SSH permet d’ouvrir un tunnel pour rediriger un port d’une machine distance vers une adresse quelconque.

Par exemple, après avoir démarré la redirection :

ssh un_serveur_public -NR2222:localhost:22

toutes les connexions arrivant sur un_serveur_public:2222 seront redirigées de manière transparente vers localhost:22 (sur la machine ayant initié le tunnel, donc).

(Cela nécessite d’activer GatewayPorts yes dans /etc/ssh/sshd_config sur un_serveur_public.)

De cette manière, un serveur SSH inaccessible derrière un NAT est rendu accessible à travers un tunnel en passant par une machine publique (un_serveur_public). Ainsi, il est possible de s’y connecter avec la commande :

ssh un_serveur_public -p2222

ssh-remote

Cette stratégie fonctionne bien, mais elle nécessite que la machine qui souhaite exposer un serveur grâce à un tunnel possède un accès SSH sur un_serveur_public.

Si l’on souhaite aider quelqu’un grâce à la prise de contrôle de sa machine à distance, il y a toutes les chances que cette personne n’ait pas d’accès SSH vers une machine publiquement accessible. Il est alors possible de lui créer un compte restreint dédié sur un serveur que l’on contrôle, mais c’est très intrusif, et il faut s’assurer de ne pas réduire la sécurité.

Mais en fait, cette contrainte est superflue.

Redirections SOCAT

La redirection de port distant nécessite des permissions car, outre le fait qu’elle est implémentée sur SSH, il serait déraisonnable d’autoriser n’importe qui à ouvrir une socket en écoute sur un port arbitraire d’une machine distante.

Pour éviter ce problème, nous pouvons décomposer la redirection de port distant fourni par SSH en deux parties :

  1. l’ouverture de la connexion vers un_serveur_public, redirigée vers l’adresse localhost:22 dans l’exemple précédent ;
  2. l’ouverture d’une socket en écoute sur un port (2222) de la machine distante, redirigée vers la première connexion.

L’idée est de mettre en place le premier demi-tunnel sur la machine serveur, et le second demi-tunnel, nécessitant des permissions, sur la machine publique, contrôlée par le client.

Pour cela, nous allons utiliser l’outil socat, qui permet de relayer les données entre deux sockets, quelque soit le rôle qu’elles aient joué lors de l’initialisation.

Active-passive

Pour comprendre son utilisation, nous allons ouvrir grâce à netcat (nc) une socket TCP en écoute sur le port 5000 et nous y connecter :

# terminal 1
nc -l -p 5000
# terminal 2
nc localhost 5000

Toute entrée validée par un retour à la ligne dans le terminal 1 s’affichera dans le terminal 2 (et vice-versa).

nc

Passive-passive

Démarrons maintenant dans deux terminaux différents une socket en écoute sur les ports 1111 et 2222 :

# terminal 1
nc -l -p 1111
# terminal 2
nc -l -p 2222

Pour les mettre en communication avec socat, dans un 3e terminal :

socat tcp:localhost:1111 tcp:localhost:2222

socat-connect

Active-active

Inversement, il est possible de mettre en communication deux sockets actives (sans compter sur leur synchronisation). Pour cela, commençons par ouvrir le serveur relai :

socat tcp-listen:1111 tcp-listen:2222

Puis connectons-y deux sockets :

# terminal 1
nc localhost 1111
# terminal 2
nc localhost 2222

socat-connect

Tunnel

Nous sommes maintenant prêts pour créer l’équivalent d’une redirection de port distant SSH grâce à deux socats, qui vont permettre d’inverser la connexion uniquement sur la portion qui permet de traverser le NAT :

# sur un_serveur_public
socat tcp-listen:1234 tcp-listen:5678
# sur le serveur derrière le NAT
socat tcp:un_serveur_public:1234 tcp:localhost:22
# sur le client
ssh un_serveur_public -p5678

ssh-socat

Serveur-client

dimanche 12 mars 2017 à 23:17

L’objectif de ce billet est de parvenir à nous connecter à un serveur a priori inaccessible derrière un NAT.

Client-serveur

De nos jours, TCP est toujours utilisé en mode client-serveur :

Une fois la connexion établie, cependant, le client et le serveur jouent exactement le même rôle au niveau de la communication. Par contre, très souvent, leur rôle applicatif dépend directement de celui qui a initié la connexion :

ssh

Ce fonctionnement paraît tellement naturel que “client” désigne bien souvent à la fois celui qui initie la connexion et celui qui effectue des requêtes (au serveur), alors que “serveur” désigne aussi bien la partie en écoute que celle qui répondra aux requêtes (des clients).

Puis vint le NAT…

Avec la pénurie d’adresses IPv4, le NAT s’est généralisé. Bien souvent, un accès internet ne fournit qu’une seule adresse IPv4. Les différents ordinateurs partageant la même connexion ne sont alors pas accessibles directement depuis l’extérieur (il est nécessaire d’ouvrir des ports).

Ainsi, derrière un NAT sans ports ouverts, un serveur ne sera pas accessible publiquement. Par contre, un client pourra continuer à se connecter à n’importe quel serveur public.

ssh-nat

Inversion des rôles

Il existe des situations pour lesquelles nous souhaitons qu’un logiciel joue le rôle de serveur au niveau applicatif, afin de répondre aux requêtes des clients, mais client au niveau de la communication, afin de passer les NATs sans difficultés.

Par exemple, nous pouvons vouloir accéder, grâce à VNC ou SSH, à un ordinateur se trouvant derrière un NAT sur lequel, par hypothèse, nous n’avons pas la main. Dans ce cas, seul le serveur (au sens applicatif) aura la capacité d’ouvrir une connexion vers le client.

Logiciel dédié

Il est possible d’utiliser un logiciel spécialement conçu pour gérer cette inversion des rôles. C’est le cas par exemple de gitso, qui inverse le protocole VNC afin de simplifier l’aide de novices à distance.

Cette solution a cependant l’inconvénient d’être très spécifique, nécessitant un développement supplémentaire pour chaque protocole.

Redirection de port distant via SSH

SSH permet d’ouvrir un tunnel pour rediriger un port d’une machine distance vers une adresse quelconque.

Par exemple, après avoir démarré la redirection :

ssh un_serveur_public -NR2222:localhost:22

toutes les connexions arrivant sur un_serveur_public:2222 seront redirigées de manière transparente vers localhost:22 (sur la machine ayant initié le tunnel, donc).

(Cela nécessite d’activer GatewayPorts yes dans /etc/ssh/sshd_config sur un_serveur_public.)

De cette manière, un serveur SSH inaccessible derrière un NAT est rendu accessible à travers un tunnel en passant par une machine publique (un_serveur_public). Ainsi, il est possible de s’y connecter avec la commande :

ssh un_serveur_public -p2222

ssh-remote

Cette stratégie fonctionne bien, mais elle nécessite que la machine qui souhaite exposer un serveur grâce à un tunnel possède un accès SSH sur un_serveur_public.

Si l’on souhaite aider quelqu’un grâce à la prise de contrôle de sa machine à distance, il y a toutes les chances que cette personne n’ait pas d’accès SSH vers une machine publiquement accessible. Il est alors possible de lui créer un compte restreint dédié sur un serveur que l’on contrôle, mais c’est très intrusif, et il faut s’assurer de ne pas réduire la sécurité.

Mais en fait, cette contrainte est superflue.

Redirections SOCAT

La redirection de port distant nécessite des permissions car, outre le fait qu’elle est implémentée sur SSH, il serait déraisonnable d’autoriser n’importe qui à ouvrir une socket en écoute sur un port arbitraire d’une machine distante.

Pour éviter ce problème, nous pouvons décomposer la redirection de port distant fourni par SSH en deux parties :

  1. l’ouverture de la connexion vers un_serveur_public, redirigée vers l’adresse localhost:22 dans l’exemple précédent ;
  2. l’ouverture d’une socket en écoute sur un port (2222) de la machine distante, redirigée vers la première connexion.

L’idée est de mettre en place le premier demi-tunnel sur la machine serveur, et le second demi-tunnel, nécessitant des permissions, sur la machine publique, contrôlée par le client.

Pour cela, nous allons utiliser l’outil socat, qui permet de relayer les données entre deux sockets, quelque soit le rôle qu’elles aient joué lors de l’initialisation.

Active-passive

Pour comprendre son utilisation, nous allons ouvrir grâce à netcat (nc) une socket TCP en écoute sur le port 5000 et nous y connecter :

# terminal 1
nc -l -p 5000
# terminal 2
nc localhost 5000

Toute entrée validée par un retour à la ligne dans le terminal 1 s’affichera dans le terminal 2 (et vice-versa).

nc

Passive-passive

Démarrons maintenant dans deux terminaux différents une socket en écoute sur les ports 1111 et 2222 :

# terminal 1
nc -l -p 1111
# terminal 2
nc -l -p 2222

Pour les mettre en communication avec socat, dans un 3e terminal :

socat tcp:localhost:1111 tcp:localhost:2222

socat-connect

Active-active

Inversement, il est possible de mettre en communication deux sockets actives (sans compter sur leur synchronisation). Pour cela, commençons par ouvrir le serveur relai :

socat tcp-listen:1111 tcp-listen:2222

Puis connectons-y deux sockets :

# terminal 1
nc localhost 1111
# terminal 2
nc localhost 2222

socat-connect

Tunnel

Nous sommes maintenant prêts pour créer l’équivalent d’une redirection de port distant SSH grâce à deux socats, qui vont permettre d’inverser la connexion uniquement sur la portion qui permet de traverser le NAT :

# sur un_serveur_public
socat tcp-listen:1234 tcp-listen:5678
# sur le serveur derrière le NAT
socat tcp:un_serveur_public:1234 tcp:localhost:22
# sur le client
ssh un_serveur_public -p5678

ssh-socat

SHAdow

mercredi 1 mars 2017 à 00:11

Le 23 février, une équipe de chercheurs a annoncé avoir cassé SHA-1 en pratique, en générant une collision.

À partir de leur travail, il est possible de produire de nouvelles paires de fichiers PDF arbitrairement différents qui auront la même signature SHA-1. Par exemple :

shadow1-thumb shadow2-thumb

$ sha1sum shadow1.pdf shadow2.pdf
fffe36a1d6f0a76a585af4f3838a4a46b6714f0c  shadow1.pdf
fffe36a1d6f0a76a585af4f3838a4a46b6714f0c  shadow2.pdf
$ sha256sum shadow1.pdf shadow2.pdf
502ccf8ecee10176d891fa4aeab295edec22b95141c2ae16d85f13b39879e37e  shadow1.pdf
2546d272df653c5a99ef0914fa6ed43b336f309758ea873448154ebde90cdfe1  shadow2.pdf

J’explique dans ce billet le principe, et je fournis un outil qui produit, à partir de deux images JPEG, deux fichiers PDF différents de même SHA-1.

Réutilisation

En fabriquant leur collision, les auteurs ont pris soin de la rendre réutilisable :

Furthermore, the prefix of the colliding messages was carefully chosen so that they allow an attacker to forge two PDF documents with the same SHA-1 hash yet that display arbitrarily-chosen distinct visual content.

Aujourd’hui, nous allons jouer aux attaquants.

La réutilisation de la collision repose sur le fait qu’avec SHA-1, ajouter un suffixe identique à une collision existante produit encore une collision :

SHA1(A) == SHA1(B) ==> SHA1(A|X) == SHA1(B|X)

(où X|Y est la concaténation de X et de Y)

Autrement dit, vous prenez les fichiers qui produisent une collision, vous ajoutez les mêmes octets aux deux, vous obtenez le même SHA-1 :

$ { cat shattered-1.pdf; echo bonjour; } | sha1sum
4bfd4b804da3aa207b29d6f1300dde507988dc4b  -
$ { cat shattered-2.pdf; echo bonjour; } | sha1sum
4bfd4b804da3aa207b29d6f1300dde507988dc4b  -

Il est donc trivial de créer de nouvelles collisions.

Mais pour qu’elles aient un intérêt, encore faut-il :

  1. que les fichiers produits soient valides ;
  2. qu’une différence entre les fichiers soit visible par l’utilisateur.

Différences

Les différences entre shattered-1.pdf et shattered-2.pdf se situent entre les adresses 0xc0 et 0x13f :

diff -U3 <(hd shattered-1.pdf) <(hd shattered-2.pdf)
--- /dev/fd/63  2017-02-28 21:11:11.530135134 +0100
+++ /dev/fd/62  2017-02-28 21:11:11.530135134 +0100
@@ -10,14 +10,14 @@
 00000090  72 65 61 6d 0a ff d8 ff  fe 00 24 53 48 41 2d 31  |ream......$SHA-1|
 000000a0  20 69 73 20 64 65 61 64  21 21 21 21 21 85 2f ec  | is dead!!!!!./.|
 000000b0  09 23 39 75 9c 39 b1 a1  c6 3c 4c 97 e1 ff fe 01  |.#9u.9...<L.....|
-000000c0  73 46 dc 91 66 b6 7e 11  8f 02 9a b6 21 b2 56 0f  |sF..f.~.....!.V.|
-000000d0  f9 ca 67 cc a8 c7 f8 5b  a8 4c 79 03 0c 2b 3d e2  |..g....[.Ly..+=.|
-000000e0  18 f8 6d b3 a9 09 01 d5  df 45 c1 4f 26 fe df b3  |..m......E.O&...|
-000000f0  dc 38 e9 6a c2 2f e7 bd  72 8f 0e 45 bc e0 46 d2  |.8.j./..r..E..F.|
-00000100  3c 57 0f eb 14 13 98 bb  55 2e f5 a0 a8 2b e3 31  |<W......U....+.1|
-00000110  fe a4 80 37 b8 b5 d7 1f  0e 33 2e df 93 ac 35 00  |...7.....3....5.|
-00000120  eb 4d dc 0d ec c1 a8 64  79 0c 78 2c 76 21 56 60  |.M.....dy.x,v!V`|
-00000130  dd 30 97 91 d0 6b d0 af  3f 98 cd a4 bc 46 29 b1  |.0...k..?....F).|
+000000c0  7f 46 dc 93 a6 b6 7e 01  3b 02 9a aa 1d b2 56 0b  |.F....~.;.....V.|
+000000d0  45 ca 67 d6 88 c7 f8 4b  8c 4c 79 1f e0 2b 3d f6  |E.g....K.Ly..+=.|
+000000e0  14 f8 6d b1 69 09 01 c5  6b 45 c1 53 0a fe df b7  |..m.i...kE.S....|
+000000f0  60 38 e9 72 72 2f e7 ad  72 8f 0e 49 04 e0 46 c2  |`8.rr/..r..I..F.|
+00000100  30 57 0f e9 d4 13 98 ab  e1 2e f5 bc 94 2b e3 35  |0W...........+.5|
+00000110  42 a4 80 2d 98 b5 d7 0f  2a 33 2e c3 7f ac 35 14  |B..-....*3....5.|
+00000120  e7 4d dc 0f 2c c1 a8 74  cd 0c 78 30 5a 21 56 64  |.M..,..t..x0Z!Vd|
+00000130  61 30 97 89 60 6b d0 bf  3f 98 cd a8 04 46 29 a1  |a0..`k..?....F).|
 00000140  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
 *
 00000230  00 00 ff fe 00 fc 00 00  00 00 00 00 00 00 ff e0  |................|

Nous devrons donc, quoi qu’il arrive, conserver les 0x140 (320) premiers octets : il s’agira forcément d’un fichier PDF.

Pour analyser la structure sur un exemple minimal, je vous conseille l’exemple fourni à la dernière page du papier (good.pdf et bad.pdf) :

<< -- base64 -d | tar xj
QlpoOTFBWSZTWbL5V5MABl///////9Pv///v////+/////HDdK739/677r+W3/75rUNr4Aa/AAAAAAA
CgEVTRtQDQAaA0AAyGmjTQGmgAAANGgAaMIAYgGgAABo0AAAAAADQAIAGQ0MgDIGmjQA0DRk0AaMQ0D
QAGIANGgAAGRoNGQMRpo0GIGgBoGQAAIAGQ0MgDIGmjQA0DRk0AaMQ0DQAGIANGgAAGRoNGQMRpo0GI
GgBoGQAAIAGQ0MgDIGmjQA0DRk0AaMQ0DQAGIANGgAAGRoNGQMRpo0GIGgBoGQAAIAGQ0MgDIGmjQA0
DRk0AaMQ0DQAGIANGgAAGRoNGQMRpo0GIGgBoGQAABVTUExEZATTICnkxNR+p6E09JppoyamjGhkm0a
mmIyaekbUejU9JiGnqZqaaDxJ6m0JkZMQ2oaYmJ6gxqMyE2TUzJqfItligtJQJfYbl9Zy9QjQuB5mHQ
RdSSXCCTHMgmSDYmdOoOmLTBJWiCpOhMQYpQlOYpJjn+wQUJSTCEpOMekaFaaNB6glCC0hKEJdHr6Bm
UIHeph7YxS8WJYyGwgWnMTFJBDFSxSCCYljiEk7HZgJzJVDHJxMgY6tCEIIWgsKSlSZ0S8GckoIIF+5
51Ro4RCw260VCEpWJSlpWx/PMrLyVoyhWMAneDilBcUIeZ1j6NCkus0qUCWnahhk5KT4GpWMh3vm2nJ
WjTL9Qg+84iExBJhNKpbV9tvEN265t3fu/TKkt4rXFTsV+NcupJXhOhOhJMQQktrqt4K8mSh9M2DAO2
X7uXGVL9YQxUtzQmS7uBndL7M6R7vX869VxqPurenSuHYNq1yTXOfNWLwgvKlRlFYqLCs6OChDp0HuT
zCWscmGudLyqUuwVGG75nmyZhKpJyOE/pOZyHyrZxGM51DYIN+Jc8yVJgAykxKCEtW55MlfudLg3KG6
TtozalunXrroSxUpVLStWrWLFihMnVpkyZOrQnUrE6xq1CGtJlbAb5ShMbV1CZgqlKC0wCFCpMmUKSE
kvFLaZC8wHOCVAlvzaJQ/T+XLb5Dh5TNM67p6KZ4e4ZSGyVENx2O27LzrTIteAreTkMZpW95GS0CEJY
hMc4nToTJ0wQhKEyddaLb/rTqmgJSlkpnALxMhlNmuKEpkEkqhKUoEq3SoKUpIQcDgWlC0rYahMmLuP
Q0fHqZaF4v2W8IoJ2EhMhYmSw7qql27WJS+G4rUplToFi2rSv0NSrVvDUpltQ8Lv6F8pXyxmFBSxiLS
xglNC4uvXVKmAtusXy4YXGX1ixedEvXF1aX6t8adYnYCpC6rW1ZzdZYlCCxKEv8vpbqdSsXl8v1jCQv
0KEPxPTa/5rtWSF1dSgg4z4KjfIMNtgwWoWLEsRhKxsSA9ji7V5LRPwtumeQ8V57UtFSPIUmtQdOQfs
eI2Ly1DMtk4Jl8n927w34zrWG6Pi4jzC82js/46Rt2IZoadWxOtMInS2xYmcu8mOw9PLYxQ4bdfFw3Z
Pf/g2pzSwZDhGrZAl9lqky0W+yeanadC037xk496t0Dq3ctfmqmjgie8ln9k6Q0K1krb3dK9el4Xsu4
4LpGcenr2eQZ1s1IhOhnE56WnXf0BLWn9Xz15fMkzi4kpVxiTKGEpffErEEMvEeMZhUl6yD1SdeJYbx
zGNM3ak2TAaglLZlDCVnoM6wV5DRrycwF8Zh/fRsdmhkMfAO1duwknrsFwrzePWeMwl107DWzymxdQw
iSXx/lncnn75jL9mUzw2bUDqj20LTgtawxK2SlQg1CCZDQMgSpEqLjRMsykM9zbSIUqil0zNk7Nu+b5
J0DKZlhl9CtpGKgX5uyp0idoJ3we9bSrY7PupnUL5eWiDpV5mmnNUhOnYi8xyClkLbNmAXyoWk7GaVr
M2umkbpqHDzDymiKjetgzTocWNsJ2E0zPcfht46J4ipaXGCfF7fuO0a70c82bvqo3HceIcRlshgu73s
eO8BqlLIap2z5jTOY+T2ucCnBtAtva3aHdchJg9AJ5YdKHz7LoA3VKmeqxAlFyEnQLBxB2PAhAZ8Kvm
uR6ELXws1Qr13Nd1i4nsp189jqvaNzt+0nEnIaniuP1+/UOZdyfoZh57ku8sYHKdvfW/jYSUks+0rK+
qtte+py8jWL9cOJ0fV8rrH/t+85/p1z2N67p/ZsZ3JmdyliL7lrNxZUlx0MVIl6PxXOUuGOeArW3vuE
vJ2beoh7SGyZKHKbR2bBWO1d49JDIcVM6lQtu9UO8ec8pOnXmkcponBPLNM2CwZ9kNC/4ct6rQkPkQH
McV/8XckU4UJCy+VeTA==
--

Aiguillage

Notre objectif est que les quelques octets différents entre les deux fichiers PDF déterminent l’image à afficher.

Il serait en théorie possible d’appliquer cet aiguillage au niveau de la structure du PDF, mais c’est en fait au niveau du JPEG qu’il sera implémenté :

PDFs with the same MD5 hash have previously been constructed by Gebhardt et al. [12] by exploiting so-called Indexed Color Tables and Color Transformation functions. However, this method is not effective for many common PDF viewers that lack support for these functionalities. Our PDFs rely on distinct parsings of JPEG images, similar to Gebhardt et al.’s TIFF technique [12] and Albertini et al.’s JPEG technique [1].

Le format JPEG

Voici le strict nécessaire à savoir sur le format JPEG pour notre besoin.

Une image est stockée entre les marqueurs 0xffd8 (Start Of Image) et 0xffd9 (End Of Image).

Il est possible d’insérer autant de commentaires que l’on veut, grâce au marqueur 0xfffe suivi de sa taille sur 2 octets (en big-endian). La taille compte le header de taille, mais pas le marqueur initial.

Par exemple, si je veux insérer le commentaire “Hello” au tout début, mon fichier JPEG ressemblera à ceci :

 ff d8 ff fe 00 07 48 65 6c 6c 6f  …  ff d9
 [SOI]                                [EOI]
      [[COM] [LEN]  H  e  l  l  o]
             <------------------->
                       7

Et c’est à peu près tout ce qu’il y a à savoir.

L’astuce

Mettons en évidence la première différence entre les fichiers en collision.

Dans le fichier 1 :

000000b0  -- -- -- -- -- -- -- --  -- -- -- -- -- ff fe 01
000000c0  73 -- -- -- -- -- -- --  -- -- -- -- -- -- -- --

Dans le fichier 2 :

000000b0  -- -- -- -- -- -- -- --  -- -- -- -- -- ff fe 01
000000c0  7f -- -- -- -- -- -- --  -- -- -- -- -- -- -- --

Chacun définit un bloc de commentaires, mais pas de mêmes tailles. Dans le fichier 1, le début du prochain bloc sera à l’adresse 0x232 (0xbf + 0x173), alors que dans le fichier 2 il sera à l’adresse 0x23e (0xbf + 0x17f).

Nous avons donc trouvé notre aiguillage ; nous allons maintenant utiliser des commentaires JPEG pour cacher soit la première image, soit la seconde.

Pour l’exploiter jusqu’au bout, il suffit de disposer les commentaires astucieusement pour que les deux versions représentent des images parfaitement valides.

Nous allons donc commencer en 0x232 un bloc de commentaires, ayant une taille permettant de recouvrir l’intégralité de l’image que nous allons stocker en 0x23e. Et inversement, nous devons démarrer un commentaire à la fin de l’image stockée en 0x23e pour cacher la deuxième image.

Comparons sur le résultat ce qu’observe un parseur qui parcourt chacun des fichiers.

(GG et HH sont les deux images à stocker. J1 et J2 sont les longueurs des sauts pour enjamber chacune des images.)

Le fichier 1 est parsé ainsi :


00000090  -- -- -- -- -- ff d8 --  -- -- -- -- -- -- -- --
       …
000000b0  -- -- -- -- -- -- -- --  -- -- -- -- -- ff fe 01
000000c0  73 -- -- -- -- -- -- --  -- -- -- -- -- -- -- --
       …
00000230  -- -- ff fe J1 J1 -- --  -- -- -- -- -- -- GG GG
00000240  GG GG GG GG GG GG GG GG  GG GG GG GG GG GG GG GG
       …
       i  GG GG GG GG GG GG ff fe  J2 J2 HH HH HH HH HH HH
  i+0x10  HH HH HH HH HH HH HH HH  HH HH HH HH HH HH HH HH
       …
       j  HH HH ff d9 -- -- -- --  -- -- -- -- -- -- -- --

Le fichier 2 est parsé différemment :


00000090  -- -- -- -- -- ff d8 --  -- -- -- -- -- -- -- --
       …
000000b0  -- -- -- -- -- -- -- --  -- -- -- -- -- ff fe 01
000000c0  7f -- -- -- -- -- -- --  -- -- -- -- -- -- -- --
       …
00000230  -- -- ff fe J1 J1 -- --  -- -- -- -- -- -- GG GG
00000240  GG GG GG GG GG GG GG GG  GG GG GG GG GG GG GG GG
       …
       i  GG GG GG GG GG GG ff fe  J2 J2 HH HH HH HH HH HH
  i+0x10  HH HH HH HH HH HH HH HH  HH HH HH HH HH HH HH HH
       …
       j  HH HH ff d9 -- -- -- --  -- -- -- -- -- -- -- --

Les structures JPEG sont donc valides dans les deux fichiers. L’image affichée dépendra de l’octet stocké à l’adresse 0xc0, valant soit 0x73, soit 0x7f.

Maintenant, il nous reste à rendre notre PDF valide.

PDF

Le header participant à la collision SHA-1 (donc figé) définit des configurations dans des sections séparées (donc non figées) :

00000000  25 50 44 46 2d 31 2e 33  0a 25 e2 e3 cf d3 0a 0a  |%PDF-1.3.%......|
00000010  0a 31 20 30 20 6f 62 6a  0a 3c 3c 2f 57 69 64 74  |.1 0 obj.<</Widt|
00000020  68 20 32 20 30 20 52 2f  48 65 69 67 68 74 20 33  |h 2 0 R/Height 3|
00000030  20 30 20 52 2f 54 79 70  65 20 34 20 30 20 52 2f  | 0 R/Type 4 0 R/|
00000040  53 75 62 74 79 70 65 20  35 20 30 20 52 2f 46 69  |Subtype 5 0 R/Fi|
00000050  6c 74 65 72 20 36 20 30  20 52 2f 43 6f 6c 6f 72  |lter 6 0 R/Color|
00000060  53 70 61 63 65 20 37 20  30 20 52 2f 4c 65 6e 67  |Space 7 0 R/Leng|
00000070  74 68 20 38 20 30 20 52  2f 42 69 74 73 50 65 72  |th 8 0 R/BitsPer|
00000080  43 6f 6d 70 6f 6e 65 6e  74 20 38 3e 3e 0a 73 74  |Component 8>>.st|

Ainsi, la largeur (Width) est définie dans l’objet 2, la hauteur (Height) dans l’objet 3, etc.

Ces objets sont à définir à la suite des fichiers JPEG embarqués. Pour comprendre leur format, le plus simple est de lire le fichier good.pdf que je recommandais plus haut :

tail -c+$((0x746)) good.pdf

On y trouve la définition des objets (entre autres les dimensions de l’image) :

2 0 obj
8
endobj

3 0 obj
8
endobj

4 0 obj
/XObject
endobj

5 0 obj
/Image
endobj

6 0 obj
/DCTDecode
endobj

7 0 obj
/DeviceRGB
endobj

8 0 obj
1693
endobj

9 0 obj
<<
  /Type /Catalog
  /Pages 10 0 R
>>
endobj


10 0 obj
<<
  /Type /Pages
  /Count 1
  /Kids [11 0 R]
>>
endobj

11 0 obj
<<
  /Type /Page
  /Parent 10 0 R
  /MediaBox [0 0 8 8]
  /CropBox [0 0 8 8]
  /Contents 12 0 R
  /Resources
  <<
    /XObject <</Im0 1 0 R>>
  >>
>>
endobj

12 0 obj
<</Length 30>>
stream
q
  8 0 0 8 0 0 cm
  /Im0 Do
Q
endstream
endobj

Ensuite vient la table de références croisées ; elle indique l’offset de chacun des objets définis, dans l’ordre :

xref
0 13 
0000000000 65535 f 
0000000017 00000 n 
0000001861 00000 n 
0000001879 00000 n 
0000001897 00000 n 
0000001922 00000 n 
0000001945 00000 n 
0000001972 00000 n 
0000001999 00000 n 
0000002020 00000 n 
0000002076 00000 n 
0000002142 00000 n 
0000002309 00000 n 

À chaque ajout ou suppression de caractères dans la définition des objets, cette table doit être mise à jour.

Le fichier se termine par un trailer, contenant l’offset de la table de références :

trailer << /Root 9 0 R /Size 13>>

startxref
2391
%%EOF

Ces offsets sont un peu fastidieux à modifier à la main, mais ça fonctionne.

SHAdow

J’ai donc écrit un petit outil qui applique toutes ces opérations automatiquement.

git clone http://git.rom1v.com/shadow.git

(ou sur github)

Il prend en entrée deux images JPEG (de moins de 64K, puisque la taille d’un commentaire est codé sur 2 octets), ainsi que leurs dimensions (afin d’éviter d’utiliser des dépendances pour les extraire) :

./shadow.py img1.jpg img2.jpg 200 200

Il génère deux fichiers dans le répertoire courant, shadow1.pdf et shadow2.pdf.

Il ne reste qu’à vérifier qu’ils ont bien le même SHA-1 :

sha1sum shadow1.pdf shadow2.pdf
Error happened! 0 - Call to undefined function simplexml_load_string() In: /var/www/Projet-Autoblog/autoblogs/autoblog.php:364 http://www.couturat.fr/Projet-Autoblog/autoblogs/blogrom1vcom_4af8d17d34d978843ff2ff40339aa5760e6458bc/?2 #0 /var/www/Projet-Autoblog/autoblogs/autoblog.php(932): VroumVroum_Blog->update() #1 /var/www/Projet-Autoblog/autoblogs/blogrom1vcom_4af8d17d34d978843ff2ff40339aa5760e6458bc/index.php(1): require_once('/var/www/Projet...') #2 {main}