
Un outil d'instrumentation basé sur LLVM pour le suivi universel de taint, l'analyse de flux de données et le traçage.
PolyTracker est un outil créé à l'origine pour l'annotation lexicale automatisée et la navigation des analyseurs, un rétro-acronyme conçu uniquement dans le but de le désigner comme The ALAN Parsers Project. Cependant, il a évolué pour devenir un outil polyvalent permettant d'effectuer efficacement des analyses de flux de données et de flux de contrôle des programmes. PolyTracker est une passe LLVM qui instrumente les programmes pour suivre quels octets d'un fichier d'entrée sont manipulés par quelles fonctions. Il produit une base de données contenant les informations de flux de données, ainsi qu'une trace d'exécution. PolyTracker fournit également une bibliothèque Python pour interagir avec et analyser sa sortie, ainsi qu'un REPL Python interactif.
PolyTracker peut être utilisé conjointement avec PolyFile pour déterminer automatiquement la finalité sémantique des fonctions d'un analyseur. Il dispose également d'une fonctionnalité expérimentale capable de générer une grammaire hors contexte représentant le langage accepté par un analyseur.
Contrairement aux alternatives d'instrumentation dynamique comme , PolyTracker impose une surcharge de performance négligeable pour presque toutes les entrées, et est capable de suivre chaque octet d'entrée à la fois. PolyTracker a commencé comme un fork du DataFlowSanitizer de LLVM et s'inspire fortement du . Cependant, contrairement au système Angora, PolyTracker est capable de suivre la complète d'un taint. En février 2021, le DataFlowSanitizer de LLVM a ajouté une nouvelle fonctionnalité pour le suivi de la provenance du taint appelée . Cependant, il ne peut suivre que 16 taints au maximum à la fois, alors que PolyTracker peut en suivre jusqu'à 2-1.
Ce README sert de guide d'utilisation général pour installer PolyTracker et compiler/instrumenter des binaires. Pour interagir programmatiquement avec ou étendre PolyTracker via son API Python, ainsi que pour interagir avec les traces d'exécution produites par le code instrumenté, consultez la documentation Python.
PolyTracker est contrôlé via un script Python appelé polytracker. Vous pouvez
l'installer en exécutant
pip3 install polytracker
PolyTracker nécessite un environnement système très particulier pour fonctionner, donc presque
tous les utilisateurs sont susceptibles de l'exécuter dans un environnement conteneurisé.
Heureusement, polytracker facilite les choses. Tout ce que vous avez à faire est
d'avoir docker installé, puis d'exécuter :
polytracker docker pull
et
polytracker docker run
Cette dernière commande monte le répertoire de travail courant dans le conteneur Docker PolyTracker et vous permet de compiler et d'exécuter des programmes instrumentés.
Le script de contrôle polytracker—que vous pouvez exécuter depuis votre système
hôte ou depuis l'intérieur du conteneur Docker—propose une variété de commandes, à la fois
pour instrumenter des programmes et pour analyser les artefacts produits. Par
exemple, vous pouvez explorer les flux de données de l'exécution, reconstruire le
graphe de flux de contrôle du programme instrumenté, et même extraire une grammaire
hors contexte correspondant aux entrées acceptées par le programme. Vous pouvez explorer ces
commandes en exécutant
polytracker --help
Le script polytracker est également un REPL, s'il est exécuté sans argument en
ligne de commande :
$ polytracker
PolyTracker (4.0.0)
https://github.com/trailofbits/polytracker
Type "help" or "commands"
>>> commands
PolyTracker est également fourni avec une commande build. Cette commande permet
à l'utilisateur d'exécuter n'importe quelle commande de compilation dans un
environnement instrumenté Blight. Cela produira
un fichier blight_journal.jsonl qui enregistre toutes les commandes exécutées lors de la
compilation. Si vous avez une cible C/C++, vous pouvez l'instrumenter en invoquant
polytracker build et en passant votre commande de compilation :
polytracker build gcc -g -o my_binary my_source.c
Pour instrumenter une cible de compilation, utilisez la commande instrument-targets. Par
défaut, la commande utilisera un blight_journal.jsonl dans votre répertoire de travail
courant pour construire une version instrumentée de votre cible de compilation. La
cible de compilation instrumentée sera construite avec les mêmes options que la
cible d'origine.
polytracker instrument-targets my_binary
build prend également en charge des programmes plus complexes qui utilisent un système de
compilation comme autotools ou CMake :
polytracker build cmake .. -DCMAKE_BUILD_TYPE=Release
polytracker build ninja
# ou
polytracker build ./configure
polytracker build make
Exécutez ensuite instrument-targets sur toutes les cibles de la compilation :
polytracker instrument-targets a.bin b.so
Ensuite, a.instrumented.bin et b.instrumented.so seront les versions
instrumentées. Consultez les Dockerfiles du répertoire
examples
pour des exemples d'instrumentation de programmes réels.
Le logiciel instrumenté écrira sa sortie dans le chemin spécifié par
POLYDB, ou polytracker.tdag si omis. Il s'agit d'un fichier binaire sur
lequel on peut agir en exécutant :
from polytracker import PolyTrackerTrace, taint_dag
trace = PolyTrackerTrace.load("polytracker.tdag")
tdfile = trace.tdfile
first_node = list(tdfile.nodes)[0]
print(f"First node affects control flow: {first_node.affects_control_flow}")
# Operate on all Range nodes
for index, node in enumerate(tdfile.nodes):
if isinstance(node, taint_dag.TDRangeNode):
print(f"Node {index}: first {node.first}, last {node.last}")
# Access taint forest
tdforest = trace.taint_forest
n1 = tdforest.get_node(1)
print(
f"Forest node {n1.label}. Parent labels: {n1.parent_labels}, "
f"source: {n1.source.path if n1.source is not None else None}, "
f"affects control flow: {n1.affected_control_flow}"
)
Vous pouvez également exécuter un binaire instrumenté directement depuis le REPL :
$ polytracker
PolyTracker (4.0.0)
https://github.com/trailofbits/polytracker
Type "help" or "commands"
>>> trace = run_trace("path_to_binary", "path_to_input_file")
Cela exécutera automatiquement le binaire instrumenté dans un conteneur Docker, si nécessaire.
⚠️ Si vous exécutez PolyTracker dans Docker ou une machine virtuelle : PolyTracker peut être très lent si vous l'exécutez dans un environnement virtualisé et que le fichier d'entrée ou, surtout, la base de données de sortie se trouvent dans un répertoire mappé ou monté depuis le système d'exploitation hôte. C'est particulièrement vrai lors de l'exécution de PolyTracker dans Docker depuis un hôte macOS. La solution consiste à écrire la base de données dans un chemin à l'intérieur du conteneur/VM, puis à la copier vers le système hôte à la toute fin.
La documentation de l'API Python est disponible ici.
Au moment de l'exécution, l'instrumentation PolyTracker recherche un certain nombre de paramètres de configuration spécifiés via des variables d'environnement. Cela permet de modifier les paramètres d'instrumentation sans avoir à recompiler le binaire.
PolyTracker accepte des paramètres de configuration sous forme de variables d'environnement afin d'éviter de recompiler les programmes cibles. L'ensemble actuel des variables d'environnement prises en charge par PolyTracker est :
POLYDB: A path to which to save the output database (default is polytracker.tdag)
WLLVM_ARTIFACT_STORE: Provides a path to an existing directory to store artifact/manifest for all build targets
POLYTRACKER_TAINT_ARGV: Set to '1' to use argv as a taint source.
POLYTRACKER_STDIN_SOURCE: Set to '1' to use stdin as a taint source.
POLYTRACKER_STDOUT_SINK: Set to '1' to use stdout as a taint sink.
POLYTRACKER_STDERR_SINK: Set to '1' to use stderr as a taint sink.
Polytracker définira ses paramètres de configuration dans l'ordre suivant :
DFSan utilise des listes ABI pour déterminer quelles fonctions il doit instrumenter automatiquement, quelles fonctions il doit ignorer, et quels wrappers de fonctions personnalisés existent. Consultez la documentation dfsan pour plus d'informations.
Tenter de compiler de grands projets logiciels peut prendre du temps, en particulier les plus anciens ou non pris en charge. Il est encore plus long d'essayer de modifier le système de compilation pour qu'il prenne en charge des changements, comme l'instrumentation de dfsan/la nôtre.
Un script se trouve dans polytracker/scripts ; vous pouvez l'exécuter sur
n'importe quelle bibliothèque ELF et il produira une liste de fonctions à ignorer.
Nous l'utilisons lorsque nous ne voulons pas suivre les informations transitant
par une bibliothèque spécifique comme libpng, ou d'autres sous-composants d'un programme.
Le Dockerfile-listgen.demo existe pour compiler des bibliothèques open source
courantes afin de créer ces listes.
Ce script est une version légèrement modifiée de celui de DataFlowSanitizer, qui
se concentre sur l'ignorance des bibliothèques système. Le script original se
trouve dans dfsan_rt.
Clonez ce dépôt Git. Depuis la racine, soit construisez l'image Docker de base de PolyTracker :
pip3 install -e ".[dev]" && polytracker docker rebuild
ou tirez simplement la dernière version pré-construite depuis DockerHub :
docker pull trailofbits/polytracker:latest
Pour une démonstration de PolyTracker sur l'analyseur MuPDF, exécutez cette commande :
docker build -t trailofbits/polytracker-demo-mupdf -f examples/pdf/Dockerfile-mupdf.demo .
mutool_track sera compilé dans /polytracker/the_klondike/mupdf/build/debug.
L'exécution de mutool_track produira polytracker.tdag, qui contient les
informations fournies par l'analyse de taint.
Pour une démonstration de PolyTracker sur les utilitaires Poppler version 0.84.0, exécutez cette commande :
docker build -t trailofbits/polytracker-demo-poppler -f examples/pdf/Dockerfile-poppler.demo .
Tous les utilitaires poppler se trouveront dans
/polytracker/the_klondike/poppler-0.84.0/build/utils.
cd /polytracker/the_klondike/poppler-0.84.0/build/utils
./pdfinfo_track some_pdf.pdf
Supposons que vous souhaitiez approfondir l'extension du code de PolyTracker ou l'analyse des traces TDAG, et que vous ne vouliez pas modifier votre environnement local en installant une version fortement personnalisée de LLVM.
Si vous travaillez sous Ubuntu et partez d'une base relativement propre en 22.04 ou 24.04, le Gist lié détaille les étapes pour obtenir une version passthrough fonctionnelle du conteneur de base PolyTracker. Le conteneur de base fournit un environnement de développement avec toutes les dépendances dans lequel vous pouvez travailler directement, ou que vous pouvez étendre (comme nous l'avons fait dans les Dockerfiles d'exemple).
L'exécution des tests unitaires Python et C++ doit se faire à l'intérieur du conteneur Docker PolyTracker.
Les tests unitaires Catch2 dans unittests/ se trouvent dans
/polytracker-build/unittests/src/taintdag/ à l'intérieur du conteneur. Exécutez
le binaire de test dans le conteneur Docker avec
cd /polytracker-build/unittests/src/taintdag/ && ./tests-taintdag
Les tests unitaires Python dans tests/ nécessitent des programmes C++ de test
locaux que les fixtures de test instrumenteront. Exécutez-les avec Pytest dans le
répertoire de travail
pytest tests
Ou utilisez pytest pour exécuter un seul fichier de test avec
pytest tests/test_foo.py
PolyTracker ne fonctionne actuellement que sous Linux, car c'est le seul système pris en charge par DataFlow Sanitizer. Cette limitation est uniquement due à un manque de prise en charge des sémantiques des appels système des autres systèmes d'exploitation, ce qui pourrait être ajouté à l'avenir. Cela signifie toutefois que l'exécution de PolyTracker sur un système non Linux nécessitera l'installation de Docker.
Les taints ne se propageront pas à travers les bibliothèques chargées dynamiquement, à moins que ces bibliothèques aient été compilées à partir des sources avec PolyTracker, ou qu'il existe une prise en charge spécifique des appels de bibliothèque implémentée dans PolyTracker. Il existe actuellement une prise en charge de la propagation du taint à travers la majorité des appels de la bibliothèque standard C non instrumentée. Pour être clair, les programmes qui utilisent des fonctions non instrumentées fonctionneront toujours normalement, mais les opérations effectuées par des appels de bibliothèque non pris en charge ne propageront pas le taint. Nous travaillons actuellement à l'ajout d'une prise en charge robuste des programmes C++, mais pour l'instant, les meilleurs résultats proviendront des programmes C.
En cas de problèmes avec Docker, essayez d'effectuer un prune système et de
compiler avec --no-cache pour PolyTracker ainsi que pour la démo que vous essayez
d'exécuter.
Les performances de PolyTracker dans le pire cas sont sollicitées lorsqu'un seul octet en mémoire est simultanément marqué (tainted) par un grand nombre d'octets d'entrée du fichier source. C'est plus courant lors de l'instrumentation d'algorithmes de compression et de cryptographie ayant de grandes tailles de blocs. Un certain nombre d'atténuations pour ce comportement sont actuellement en cours de recherche et de développement.
Voici quelques-unes des réalisations publiquement disponibles que nous avons faites avec PolyTracker. Si vous connaissez autre chose que vous aimeriez voir listé ici, n'hésitez pas à nous le faire savoir !
mapping et cavities plus précisément) pour identifier une CVE et en a parlé sur le blog Trail of Bits.Cette recherche a été développée par Trail of Bits avec le financement de la Defense Advanced Research Projects Agency (DARPA) dans le cadre du programme SafeDocs, en tant que sous-traitant de Galois. Elle est sous licence Apache 2.0. © 2019, Trail of Bits.
Veuillez nous contacter en utilisant [email protected].