
Ein LLVM-basiertes Instrumentierungswerkzeug für universelles Taint-Tracking, Datenflussanalyse und Tracing.
PolyTracker ist ein Werkzeug, das ursprünglich für die Automatische Lexikalische Annotation und Navigation von Parsern entwickelt wurde – ein retronymisches Kunstwort, das einzig zu dem Zweck erfunden wurde, es als The ALAN Parsers Project bezeichnen zu können. Inzwischen hat es sich jedoch zu einem allgemeinen Werkzeug für die effiziente Durchführung von Datenfluss- und Kontrollflussanalysen von Programmen entwickelt. PolyTracker ist ein LLVM-Pass, der Programme instrumentiert, um zu verfolgen, welche Bytes einer Eingabedatei von welchen Funktionen verarbeitet werden. Es gibt eine Datenbank mit den Datenflussinformationen sowie einen Laufzeit-Trace aus. PolyTracker bietet außerdem eine Python-Bibliothek für die Interaktion mit und die Analyse seiner Ausgaben sowie eine interaktive Python-REPL.
PolyTracker kann zusammen mit PolyFile verwendet werden, um automatisch den semantischen Zweck der Funktionen eines Parsers zu bestimmen. Es verfügt außerdem über eine experimentelle Funktion, die eine kontextfreie Grammatik erzeugen kann, welche die von einem Parser akzeptierte Sprache repräsentiert.
Im Gegensatz zu dynamischen Instrumentierungsalternativen wie Taintgrind verursacht PolyTracker bei fast allen Eingaben einen vernachlässigbaren Leistungsaufwand und ist in der Lage, jedes Byte der Eingabe gleichzeitig zu verfolgen. PolyTracker entstand als Fork des LLVM DataFlowSanitizer und lässt sich stark vom Angora Fuzzer inspirieren. Anders als das Angora-System ist PolyTracker jedoch in der Lage, die gesamte Herkunft (Provenance) einer Taint-Information zu verfolgen. Im Februar 2021 wurde dem LLVM DataFlowSanitizer eine neue Funktion zur Verfolgung der Taint-Herkunft hinzugefügt, die sogenannte Origin-Tracking. Diese kann jedoch höchstens 16 Taints gleichzeitig verfolgen, während PolyTracker bis zu 231-1 verfolgen kann.
Diese README dient als allgemeiner Leitfaden für die Installation von PolyTracker und das Kompilieren/Instrumentieren von Binärdateien. Für die programmatische Interaktion mit oder Erweiterung von PolyTracker über seine Python-API sowie für die Interaktion mit Laufzeit-Traces aus instrumentiertem Code konsultieren Sie die Python-Dokumentation.
PolyTracker wird über ein Python-Skript namens polytracker gesteuert. Sie können es installieren, indem Sie
pip3 install polytracker
ausführen.
PolyTracker benötigt eine sehr spezielle Systemumgebung, um zu laufen, daher werden fast alle Benutzer es wahrscheinlich in einer Container-Umgebung ausführen. Glücklicherweise macht polytracker dies einfach. Sie müssen lediglich docker installiert haben und dann ausführen:
polytracker docker pull
und
polytracker docker run
Der letztgenannte Befehl mountet das aktuelle Arbeitsverzeichnis in den PolyTracker-Docker-Container und ermöglicht es Ihnen, instrumentierte Programme zu bauen und auszuführen.
Das polytracker-Steuerskript – das Sie entweder von Ihrem Hostsystem oder von innerhalb des Docker-Containers aus ausführen können – verfügt über eine Vielzahl von Befehlen, sowohl zum Instrumentieren von Programmen als auch zur Analyse der resultierenden Artefakte. Sie können beispielsweise die Datenflüsse in der Ausführung untersuchen, den Kontrollflussgraphen des instrumentierten Programms rekonstruieren und sogar eine kontextfreie Grammatik extrahieren, die zu den vom Programm akzeptierten Eingaben passt. Sie können diese Befehle erkunden, indem Sie
polytracker --help
ausführen.
Das polytracker-Skript ist auch eine REPL, wenn es ohne Befehlszeilenargumente ausgeführt wird:
$ polytracker
PolyTracker (4.0.0)
https://github.com/trailofbits/polytracker
Type "help" or "commands"
>>> commands
PolyTracker wird auch mit einem build-Befehl geliefert. Dieser Befehl ermöglicht es dem Benutzer, beliebige Build-Befehle in einer mit Blight instrumentierten Umgebung auszuführen. Dies erzeugt eine Datei blight_journal.jsonl, die alle während des Builds ausgeführten Befehle aufzeichnet. Wenn Sie ein C/C++-Ziel haben, können Sie es instrumentieren, indem Sie polytracker build aufrufen und Ihren Build-Befehl übergeben:
polytracker build gcc -g -o my_binary my_source.c
Um ein Build-Ziel zu instrumentieren, verwenden Sie den Befehl instrument-targets. Standardmäßig verwendet der Befehl eine blight_journal.jsonl in Ihrem aktuellen Arbeitsverzeichnis, um eine instrumentierte Version Ihres Build-Ziels zu erstellen. Das instrumentierte Build-Ziel wird mit denselben Flags wie das ursprüngliche Build-Ziel erstellt.
polytracker instrument-targets my_binary
build unterstützt auch komplexere Programme, die ein Build-System wie Autotools oder CMake verwenden:
polytracker build cmake .. -DCMAKE_BUILD_TYPE=Release
polytracker build ninja
# oder
polytracker build ./configure
polytracker build make
Führen Sie dann instrument-targets für beliebige Ziele des Builds aus:
polytracker instrument-targets a.bin b.so
Anschließend sind a.instrumented.bin und b.instrumented.so die instrumentierten Versionen. Siehe die Dockerfiles im examples-Verzeichnis für Beispiele, wie reale Programme instrumentiert werden können.
Die instrumentierte Software schreibt ihre Ausgabe in den Pfad, der in POLYDB angegeben ist, oder, falls dieser weggelassen wird, in polytracker.tdag. Dies ist eine Binärdatei, die mit folgendem Befehl bearbeitet werden kann:
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}"
)
Sie können ein instrumentiertes Binary auch direkt aus der REPL ausführen:
$ polytracker
PolyTracker (4.0.0)
https://github.com/trailofbits/polytracker
Type "help" or "commands"
>>> trace = run_trace("path_to_binary", "path_to_input_file")
Dies führt das instrumentierte Binary bei Bedarf automatisch in einem Docker-Container aus.
⚠️ Wenn Sie PolyTracker in Docker oder einer VM ausführen: PolyTracker kann sehr langsam sein, wenn es in einer virtualisierten Umgebung läuft und entweder die Eingabedatei oder, insbesondere, die Ausgabedatenbank in einem vom Host-Betriebssystem zugeordneten oder gemounteten Verzeichnis liegt. Dies gilt besonders, wenn PolyTracker in Docker von einem macOS-Host aus ausgeführt wird. Die Lösung besteht darin, die Datenbank in einen Pfad innerhalb des Containers/der VM zu schreiben und sie ganz am Ende auf das Hostsystem zu kopieren.
Die Python-API-Dokumentation ist hier verfügbar.
Zur Laufzeit sucht die PolyTracker-Instrumentierung nach einer Reihe von Konfigurationsparametern, die über Umgebungsvariablen angegeben werden. Dies ermöglicht es, Instrumentierungsparameter zu ändern, ohne das Binary neu kompilieren zu müssen.
PolyTracker akzeptiert Konfigurationsparameter in Form von Umgebungsvariablen, um eine Neukompilierung der Zielprogramme zu vermeiden. Der aktuelle Satz von Umgebungsvariablen, die PolyTracker unterstützt, ist:
POLYDB: Ein Pfad, unter dem die Ausgabedatenbank gespeichert werden soll (Standard ist polytracker.tdag)
WLLVM_ARTIFACT_STORE: Gibt einen Pfad zu einem vorhandenen Verzeichnis an, in dem Artefakte/Manifeste für alle Build-Ziele gespeichert werden
POLYTRACKER_TAINT_ARGV: Auf '1' setzen, um argv als Taint-Quelle zu verwenden.
POLYTRACKER_STDIN_SOURCE: Auf '1' setzen, um stdin als Taint-Quelle zu verwenden.
POLYTRACKER_STDOUT_SINK: Auf '1' setzen, um stdout als Taint-Senke zu verwenden.
POLYTRACKER_STDERR_SINK: Auf '1' setzen, um stderr als Taint-Senke zu verwenden.
PolyTracker setzt seine Konfigurationsparameter in der folgenden Reihenfolge:
DFSan verwendet ABI-Listen, um zu bestimmen, welche Funktionen es automatisch instrumentieren soll, welche Funktionen es ignorieren soll und welche benutzerdefinierten Funktions-Wrapper existieren. Weitere Informationen finden Sie in der dfsan-Dokumentation.
Der Versuch, große Softwareprojekte zu bauen, kann zeitaufwändig sein, insbesondere bei älteren/ nicht unterstützten Projekten. Noch zeitaufwändiger ist es, das Build-System so zu modifizieren, dass es Änderungen wie die dfsan-/unsere Instrumentierung unterstützt.
Es gibt ein Skript in polytracker/scripts, das Sie auf jede ELF-Bibliothek anwenden können; es gibt eine Liste von zu ignorierenden Funktionen aus. Wir verwenden dies, wenn wir keine Informationen verfolgen möchten, die durch eine bestimmte Bibliothek wie libpng oder andere Unterkomponenten eines Programms fließen. Die Dockerfile-listgen.demo existiert, um gängige Open-Source-Bibliotheken zu bauen, damit wir diese Listen erstellen können.
Dieses Skript ist eine leicht angepasste Version dessen, was DataFlowSanitizer hat und das sich auf das Ignorieren von Systembibliotheken konzentriert. Das Originalskript finden Sie in dfsan_rt.
Checken Sie dieses Git-Repository aus. Bauen Sie von der Wurzel aus entweder das Basis-PolyTracker-Docker-Image:
pip3 install -e ".[dev]" && polytracker docker rebuild
oder ziehen Sie einfach die neueste vorgebaute Version von DockerHub:
docker pull trailofbits/polytracker:latest
Für eine Demo von PolyTracker, die auf dem MuPDF-Parser läuft, führen Sie diesen Befehl aus:
docker build -t trailofbits/polytracker-demo-mupdf -f examples/pdf/Dockerfile-mupdf.demo .
mutool_track wird in /polytracker/the_klondike/mupdf/build/debug gebaut. Das Ausführen von mutool_track gibt polytracker.tdag aus, das die von der Taint-Analyse bereitgestellten Informationen enthält.
Für eine Demo von PolyTracker, die auf Poppler-Utils Version 0.84.0 läuft, führen Sie diesen Befehl aus:
docker build -t trailofbits/polytracker-demo-poppler -f examples/pdf/Dockerfile-poppler.demo .
Alle Poppler-Utils befinden sich in /polytracker/the_klondike/poppler-0.84.0/build/utils.
cd /polytracker/the_klondike/poppler-0.84.0/build/utils
./pdfinfo_track some_pdf.pdf
Angenommen, Sie möchten etwas tiefer in die Erweiterung der PolyTracker-Codebasis oder in die Analyse von TDAG-Traces einsteigen und möchten Ihre lokale Umgebung nicht mit einer stark angepassten LLVM-Version belasten.
Wenn Sie unter Ubuntu arbeiten und von einer relativ sauberen 22.04- oder 24.04-Basis ausgehen, beschreibt das verlinkte Gist die Schritte, um eine funktionierende Passthrough-Version des PolyTracker-Basiscontainers zu erhalten. Der Basiscontainer bietet eine Entwicklungsumgebung mit allen Abhängigkeiten, in der Sie direkt arbeiten oder die Sie erweitern können (wie wir es in den Beispiel-Dockerfiles getan haben).
Die Ausführung der Python- und C++-Unit-Tests sollte innerhalb des PolyTracker-Docker-Containers erfolgen.
Die Catch2-Unit-Tests in unittests/ befinden sich innerhalb des Containers in /polytracker-build/unittests/src/taintdag/. Führen Sie das Test-Binary innerhalb des Docker-Containers aus mit
cd /polytracker-build/unittests/src/taintdag/ && ./tests-taintdag
Die Python-Unit-Tests in tests/ benötigen lokale Test-C++-Programme, die die Test-Fixtures instrumentieren. Führen Sie sie mit Pytest im Arbeitsverzeichnis aus:
pytest tests
Oder verwenden Sie Pytest, um eine einzelne Testdatei auszuführen:
pytest tests/test_foo.py
PolyTracker läuft derzeit nur unter Linux, da dies das einzige System ist, das vom DataFlowSanitizer unterstützt wird. Diese Einschränkung ist lediglich auf das Fehlen von Semantiken für Systemaufrufe anderer Betriebssysteme zurückzuführen, die in Zukunft hinzugefügt werden könnten. Dies bedeutet jedoch, dass für die Ausführung von PolyTracker auf einem Nicht-Linux-System Docker installiert sein muss.
Taints werden sich nicht durch dynamisch geladene Bibliotheken ausbreiten, es sei denn, diese Bibliotheken wurden mit PolyTracker aus dem Quellcode kompiliert, oder es gibt spezifische Unterstützung für die in PolyTracker implementierten Bibliotheksaufrufe. Derzeit gibt es durchaus Unterstützung für die Ausbreitung von Taint durch die Mehrheit der nicht instrumentierten C-Standardbibliotheksaufrufe. Um es klar zu sagen: Programme, die nicht instrumentierte Funktionen verwenden, laufen weiterhin normal; jedoch werden Operationen, die von nicht unterstützten Bibliotheksaufrufen ausgeführt werden, keine Taints ausbreiten. Wir arbeiten derzeit an der Implementierung einer robusten Unterstützung für C++-Programme, aber derzeit liefern C-Programme die besten Ergebnisse.
Wenn es Probleme mit Docker gibt, versuchen Sie einen System-Prun und bauen Sie mit --no-cache sowohl für PolyTracker als auch für die Demo, die Sie ausführen möchten.
Der Worst-Case-Leistungsabfall von PolyTracker tritt auf, wenn ein einzelnes Byte im Speicher gleichzeitig von einer großen Anzahl von Eingabebytes aus der Quelldatei verunreinigt (tainted) ist. Dies ist am häufigsten bei der Instrumentierung von Komprimierungs- und Kryptografiealgorithmen mit großen Blockgrößen der Fall. Es gibt eine Reihe von Abschwächungen für dieses Verhalten, die derzeit erforscht und entwickelt werden.
Hier sind einige der öffentlich verfügbaren Dinge, die wir mit PolyTracker gemacht haben. Wenn Sie etwas anderes kennen, das Sie hier aufgelistet sehen möchten, lassen Sie es uns bitte wissen!
mapping und cavities), um eine CVE zu lokalisieren und schrieb darüber im Trail-of-Bits-Blog.Diese Forschung wurde von Trail of Bits mit Finanzierung durch die Defense Advanced Research Projects Agency (DARPA) im Rahmen des SafeDocs-Programms als Subunternehmer von Galois entwickelt. Sie ist unter der Apache-2.0-Lizenz lizenziert. © 2019, Trail of Bits.
Bitte kontaktieren Sie uns unter [email protected].