tcpsnitch
est un outil de traçage conçu pour étudier les interactions entre une application et la pile TCP/IP. tcpsnitch
exécute la commande spécifiée jusqu'à ce qu'elle se termine et intercepte tous les appels de fonction libc sur les sockets Internet.
Pour commencer en douceur, on peut exécuter la commande suivante pour tracer le programme curl
:
$ tcpsnitch curl google.com
Pour chaque socket Internet ouverte, tcpsnitch
construit une liste ordonnée d'appels de fonction (une invocation de fonction est appelée un événement dans le reste de ce document). Pour chaque événement, tcpsnitch
enregistre les arguments, la valeur de retour et diverses informations telles que l'horodatage actuel ou l'identifiant du thread. Plus précisément, un événement connect()
pourrait ressembler à ceci dans une trace de socket :
{
"type" : " connect " ,
"timestamp_usec" : 1491043720731853 ,
"return_value" : 0 ,
"success" : true ,
"thread_id" : 17313 ,
"details" : {
"addr" : {
"sa_family" : " AF_INET " ,
"ip" : " 127.0.1.1 " ,
"port" : " 53 "
}
}
}
Les traces de socket sont écrites dans des fichiers texte où chaque ligne est un objet JSON représentant un événement unique. Le responsable d'une telle trace pourrait aimer ceci :
{ "type" : " socket " , "timestamp_usec" : 1491043720731840 , "return_value" : 6 , "success" : true , "thread_id" : 17313 , "details" : { "sock_info" : { "domain" : " AF_INET " , "type" : " SOCK_DGRAM " , "protocol" : 0 , "SOCK_CLOEXEC" : false , "SOCK_NONBLOCK" : true }}}
{ "type" : " ioctl " , "timestamp_usec" : 1491043720765019 , "return_value" : 0 , "success" : true , "thread_id" : 17313 , "details" : { "request" : " FIONREAD " }}
{ "type" : " recvfrom " , "timestamp_usec" : 1491043720765027 , "return_value" : 44 , "success" : true , "thread_id" : 17313 , "details" : { "bytes" : 2048 , "flags" : { "MSG_CMSG_CLOEXEC" : false , "MSG_DONTWAIT" : false , "MSG_ERRQUEUE" : false , "MSG_OOB" : false , "MSG_PEEK" : false , "MSG_TRUNC" : false , "MSG_WAITALL" : false }, "addr" : { "sa_family" : " AF_INET " , "ip" : " 127.0.1.1 " , "port" : " 53 " }}}
{ "type" : " ioctl " , "timestamp_usec" : 1491043720770075 , "return_value" : 0 , "success" : true , "thread_id" : 17313 , "details" : { "request" : " FIONREAD " }}
{ "type" : " recvfrom " , "timestamp_usec" : 1491043720770094 , "return_value" : 56 , "success" : true , "thread_id" : 17313 , "details" : { "bytes" : 65536 , "flags" : { "MSG_CMSG_CLOEXEC" : false , "MSG_DONTWAIT" : false , "MSG_ERRQUEUE" : false , "MSG_OOB" : false , "MSG_PEEK" : false , "MSG_TRUNC" : false , "MSG_WAITALL" : false }, "addr" : { "sa_family" : " AF_INET " , "ip" : " 127.0.1.1 " , "port" : " 53 " }}}
Comme une seule commande peut bifurquer sur plusieurs processus (et tcpsnitch
suit des forks), toutes les traces de socket appartenant à un processus donné sont regroupées dans un répertoire, nommé d'après le processus tracé. Dans un tel répertoire, les traces de socket sont nommées en fonction de l'ordre dans lequel elles ont été ouvertes par le processus.
Par défaut, les traces sont enregistrées dans un répertoire aléatoire sous /tmp
et automatiquement téléchargées sur www.tcpsnitch.org, une plateforme conçue pour centraliser, visualiser et analyser les traces. Notez que toutes les traces téléchargées sont publiques et accessibles à tous pour consultation et téléchargement.
Comme visible sur l'extrait de code suivant, tcpsnitch
vous donne l'URL à laquelle votre trace est disponible.
$ tcpsnitch curl google.com
Trace saved in /tmp/tmp.4ERKizKyU3.
Uploading trace....
Trace successfully uploaded at https://tcpsnitch.org/app_traces/20.
Trace archive will be imported shortly. Refresh this page in a few minutes...
Notez que plusieurs minutes sont nécessaires pour importer la trace (c'est-à-dire extraire l'archive de trace et insérer tous les événements dans la base de données). Une fois importée, plusieurs minutes supplémentaires peuvent être nécessaires pour calculer l’analyse quantitative de la trace.
Enfin, tcpsnitch
permet également d'extraire l'option de socket TCP_INFO
à des intervalles définis par l'utilisateur et d'enregistrer une trace .pcap
pour chaque socket individuel. Voir la section utilisation pour plus d'informations.
tcpsnitch
permet de tracer des applications sur :
Comme tcpsnitch
fonctionne en interceptant les appels aux fonctions de la libc à l'aide de la variable d'environnement LD_PRELOAD
, le traçage ne peut pas être effectué pour les applications liées statiquement à la libc.
Remarque : sous Linux, Chrome (et toute application basée sur Chromium telle qu'Electron, Opera, etc...) est connu pour n'être PAS compatible.
Pour les utilisateurs qui souhaitent tracer les applications Android, faites défiler jusqu'à la section « Compilation pour Android ».
Testé sur Ubuntu 16 & 14, Debian 8, Elementary 0.4, Mint 18
sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install make gcc gcc-multilib libc6-dev libc6-dev:i386 libjansson-dev libjansson-dev:i386 libpcap0.8 libpcap0.8:i386 libpcap0.8-dev
Testé sur Fedora 25 et CentOS 7
sudo yum install make gcc glibc-devel glibc-devel.i686 libgcc libgcc.i686 libpcap-devel.x86_64 libpcap-devel.i686 jansson jansson.i686 && curl -O http://www.digip.org/jansson/releases/jansson-2.10.tar.bz2 && bunzip2 -c jansson-2.10.tar.bz2 | tar xf - && rm -f jansson-2.10.tar.bz2 && cd jansson-2.10 && ./configure && make && sudo make install && cd .. && rm -rf jansson-2.10
Construire et installer :
./configure
make
sudo make install
Utilisation : tcpsnitch [<options>] <cmd> [<cmd_args>]
où :
<options>
sont des options tcpsnitch
<cmd>
est la commande à tracer (obligatoire)<cmd_args>
sont les arguments de <cmd>
. Voici un exemple simple avec curl
et toutes les options par défaut :
tcpsnitch curl google.com
On peut émettre tcpsnitch -h
pour obtenir plus d'informations sur les options prises en charge. Les plus importants sont les suivants :
-b
et -u
sont utilisés pour extraire TCP_INFO
à des intervalles définis par l'utilisateur. Voir la section "Extraction TCP_INFO
" pour plus d'informations.-c
est utilisé pour capturer les traces pcap
des sockets. Voir la section "Capture de paquets" pour plus d'informations.-a
et -k
sont utilisés pour tracer l'application Android. Voir la section « Utilisation d'Android » pour plus d'informations.-n
désactive le téléchargement automatique des traces.-d
définit le répertoire dans lequel la trace sera écrite (au lieu d'un répertoire aléatoire dans /tmp
).-f
définit le niveau de verbosité des journaux enregistrés dans un fichier. Par défaut, seuls les messages WARN et ERROR sont écrits dans les journaux. Ceci est principalement utile pour signaler un bug et déboguer.-l
est similaire à -f
mais définit la verbosité du journal sur STDOUT, qui par défaut n'affiche que les messages d'ERREUR. Ceci est utilisé à des fins de débogage.-t
contrôle la fréquence à laquelle les événements sont transférés dans un fichier. Par défaut, les événements sont écrits dans un fichier toutes les 1 000 millisecondes.-v
est plutôt inutile pour le moment, mais il est censé mettre tcpsnitch
en mode verbeux à la manière de strace
. Reste à implémenter (pour le moment, il n'affiche que les noms d'événements).TCP_INFO
-b <bytes>
et -u <usec>
permettent d'extraire la valeur de l'option de socket TCP_INFO
pour chaque socket à des intervalles définis par l'utilisateur. Notez que les valeurs TCP_INFO
apparaissent comme tout autre événement dans la trace JSON du socekt.
-b <bytes>
, TCP_INFO
est enregistré tous les <bytes>
envoyés+reçus sur le socket.-u <usec>
, TCP_INFO
est enregistré toutes <usec>
microsecondes.TCP_INFO
est enregistré lorsque l'une des deux conditions est remplie. Par défaut, cette option est désactivée. Notez également que tcpsnitch
vérifie ces conditions uniquement lorsqu'une fonction remplacée est appelée.
L'option -c
active la capture d'une trace .pcap
pour chaque socket. Notez que vous devez disposer des autorisations appropriées pour pouvoir capturer le trafic sur une interface (voir man pcap
pour plus d'informations sur ces autorisations).
Cette fonctionnalité n'est pas disponible pour Android pour le moment.
L'utilisation sur Android est un processus en deux étapes, très similaire à l'utilisation sur Linux. Tout d'abord, tcpsnitch
configure et lance l'application à tracer avec les options appropriées, puis les traces sont extraites de l'appareil et copiées sur la machine hôte.
Toutes les options sont prises en charge sur Android, à l'exception de l'option -c
pour capturer les traces .pcap
.
Quelques étapes de configuration préliminaires doivent être effectuées une fois sur l'appareil :
adb devices
et assurez-vous que votre téléphone est visible (votre device
devrait voir dans la deuxième colonne). Lorsque le périphérique est accessible via adb
, l'utilisation est quasiment la même que sous Linux :
tcpsnitch
standard avec l'option -a
pour indiquer que vous souhaitez tracer une application sur l'appareil Android connecté. Notez que l'argument <cmd>
doit correspondre au nom d'un package installé sur l'appareil via un simple grep
. Par exemple, pour tracer l'application Firefox dont le nom du package est org.firefox.com
, on peut émettre tcpsnitch -a firefox
. tcpsnitch
vous informera du package correspondant trouvé et démarrera immédiatement l'application.tcpsnitch -k <package>
pour tuer l'application et mettre fin au processus de traçage. Les traces seront extraites de l'appareil et enregistrées sur votre disque dans /tmp
avant d'être téléchargées sur www.tcpsnitch.org. Important : vous devez redémarrer votre appareil Android pour désactiver complètement le traçage. Comme tcpsnitch
utilise les propriétés Android pour configurer la bibliothèque LD_PRELOAD
et que ces propriétés ne peuvent pas être supprimées, il faut redémarrer l'appareil pour supprimer les propriétés (peut-être que quelqu'un connaît une meilleure solution ?).
Voici un exemple complet pour tracer Firefox :
$ tcpsnitch -a firefox
Found Android package: ' org.mozilla.firefox ' .
Uploading tcpsnitch library to /data/libtcpsnitch.so.0.1-arm.
Start package ' org.mozilla.firefox ' .
Execute ' ./tcpsnitch -k firefox ' to terminate the capture.
# INTERACTING WITH APPLICATION
$ tcpsnitch -k firefox
Found Android package: ' org.mozilla.firefox ' .
Pulling trace from Android device....
Trace saved in /tmp/tmp.MidCH9rm3x.
Uploading trace....
Trace successfully uploaded at https://tcpsnitch.org/app_traces/21.
Trace archive will be imported shortly. Refresh this page in a few minutes...
Notez qu'en cas de correspondances multiples pour un package, le premier package correspondant sera utilisé. Vous devrez peut-être donc être plus précis pour éviter les conflits. Vous pouvez exécuter adb shell pm list packages
pour obtenir le nom de tous les packages installés sur votre appareil.
Notez également qu'un seul appareil doit être visible par adb
.
Afin de tracer les applications Android, tcpsnitch
doit être compilé avec le kit de développement natif Android (NDK). La compilation est plus complexe et la configuration nécessite un appareil Android rooté.
Fondamentalement, cela implique les étapes suivantes :
libjansson
et libpcap
avec le NDK et rendez les bibliothèques compilées et les fichiers d'en-tête disponibles pour la chaîne d'outils autonome.tcpsnitch
avec la chaîne d'outils autonome et préparez l'appareil Android.La section suivante donne un exemple complexe qui vous guide à travers toutes les étapes.
Quelques hypothèses :
<NDK_PATH>
.<TCPSNITCH_PATH>
.Tout d'abord, définissons quelques variables :
export TCPSNITCH=<TCPSNITCH_PATH>
export NDK=<NDK_PATH>
# Where the standalone toolchain WILL be created
export TOOLCHAIN=<TOOLCHAIN_PATH>
Commençons maintenant par générer une chaîne d'outils autonome pour un appareil ARM exécutant l'API Android 23 (version 6.0, Marshmallow). Le mappage entre les versions d'Android et les niveaux d'API sur la page suivante.
$NDK/build/tools/make_standalone_toolchain.py --arch arm --api 23 --install-dir $TOOLCHAIN
Nous devons maintenant compiler libjansson
et libpcap
avec le NDK. Lorsque cela est fait, nous devons installer leurs fichiers d'en-tête et les bibliothèques compilées dans le "sysroot" de notre chaîne d'outils autonome.
Commençons par libjansson
:
git clone https://github.com/akheron/jansson && cd jansson
# Configuration file which we don't use, we may leave it empty
touch src/jansson_private_config.h
sed -i 's/BUILD_SHARED_LIBRARY/BUILD_STATIC_LIBRARY/g' Android.mk
$NDK/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk
cp obj/local/armeabi/libjansson.a $TOOLCHAIN/sysroot/usr/lib/
cp src/jansson.h android/jansson_config.h $TOOLCHAIN/sysroot/usr/include/
cd .. && rm -rf jansson
Passons maintenant à libpcap
:
git clone https://github.com/the-tcpdump-group/libpcap && cd libpcap
export CC=$TOOLCHAIN/bin/arm-linux-androideabi-gcc
./configure --host=arm-linux --with-pcap=linux --prefix=/usr
# You will need to install some missing dependencies (e.g. `flex` & `bison`)
sudo apt-get install flex bison
# Reissue ./configure untill all dependencies are met
./configure --host=arm-linux --with-pcap=linux --prefix=/usr
# Compile && install in toolchain
make && sudo make install DESTDIR=$TOOLCHAIN/sysroot
cd .. && rm -rf libpcap
Nous sommes maintenant prêts à compiler tcpsnitch
:
# First, let's fix the buggy `tcp.h` header from the NDK
sed -i 's/include <linux/tcp.h>/include <sys/cdefs.h>n#include <linux/tcp.h>/g' $TOOLCHAIN/sysroot/usr/include/netinet/tcp.h
# Configure the compiler
export CC_ANDROID=$TOOLCHAIN/bin/arm-linux-androideabi-gcc
# Build & install tcpsnitch
make android && sudo make install
Vous êtes prêt à partir ! Consultez la section Utilisation d’Android pour savoir comment démarrer le traçage des applications.
Une fonctionnalité intéressante de l'éditeur de liens dynamique Linux ( ld.so
) est la possibilité de lier les bibliothèques partagées spécifiées par l'utilisateur avant les bibliothèques spécifiées dans la liste des dépendances d'un programme. Cette fonctionnalité peut être contrôlée avec la variable d'environnement LD_PRELOAD
qui contient une liste (éventuellement vide) de bibliothèques supplémentaires spécifiées par l'utilisateur. En particulier, cette variable LD_PRELOAD
peut forcer l'éditeur de liens dynamique à lier une bibliothèque partagée spécifiée par l'utilisateur avant la bibliothèque libc
. Par conséquent, toute fonction définie dans cette bibliothèque spécifiée par l'utilisateur a priorité sur une fonction avec la même signature définie dans libc
.
L'implication ici est que cela permet d'intercepter les appels aux fonctions de wrapper d'appel système. Nous devons simplement ajouter une bibliothèque partagée personnalisée qui redéfinit ces fonctions de wrappers d'appels système à LD_PRELOAD
. Une telle bibliothèque shim intercepte ensuite de manière transparente les appels de fonction libc
et effectue un certain traitement avant d'appeler les fonctions wrapper libc
d'origine.
wrong ELF class
? Rien de grave, ceux-ci peuvent être ignorés. La bibliothèque partagée tcpsnitch
est compilée pour les architectures 32 bits et 64 bits. Lors du traçage d'une commande, les deux bibliothèques sont chargées dans la variable d'environnement LD_PRELOAD
car il n'existe pas de moyen simple de connaître l'architecture du binaire de la commande (il s'agit souvent d'un script shell exécutant un autre binaire). L'éditeur de liens dynamique se charge alors de charger la bibliothèque compatible et ignore la seconde (mais renvoie toujours une erreur).
Venez discuter de tcpsnitch
sur https://gitter.im/Tcpsnitch.
L'e-mail de l'auteur est gregory.vanderschueren[at]gmail.com