
Una herramienta de instrumentación basada en LLVM para el seguimiento universal de taint, el análisis de flujo de datos y el trazado.
PolyTracker es una herramienta creada originalmente para la Automated Lexical Annotation and Navigation of Parsers, un backronym ideado únicamente para el propósito de referirse a ella como The ALAN Parsers Project. Sin embargo, ha evolucionado hasta convertirse en una herramienta de propósito general para realizar de forma eficiente análisis de flujo de datos y de flujo de control de programas. PolyTracker es un pass de LLVM que instrumenta programas para rastrear qué bytes de un archivo de entrada son operados por qué funciones. Genera una base de datos que contiene la información de flujo de datos, así como una traza de ejecución. PolyTracker también proporciona una biblioteca de Python para interactuar con su salida y analizarla, además de un REPL de Python interactivo.
PolyTracker se puede utilizar junto con PolyFile para determinar automáticamente el propósito semántico de las funciones de un parser. También tiene una característica experimental capaz de generar una gramática libre de contexto que representa el lenguaje aceptado por un parser.
A diferencia de las alternativas de instrumentación dinámica como , PolyTracker impone una sobrecarga de rendimiento insignificante para casi todas las entradas, y es capaz de rastrear cada byte de la entrada a la vez. PolyTracker comenzó como un fork del DataFlowSanitizer de LLVM y toma mucha inspiración del . Sin embargo, a diferencia del sistema Angora, PolyTracker es capaz de rastrear toda la de un taint. En febrero de 2021, el DataFlowSanitizer de LLVM añadió una nueva característica para rastrear la proveniencia del taint llamada . Sin embargo, solo es capaz de rastrear un máximo de 16 taints a la vez, mientras que PolyTracker puede rastrear hasta 2-1.
Este README sirve como guía de uso general para instalar PolyTracker y compilar/instrumentar binarios. Para interactuar programáticamente con o extender PolyTracker a través de su API de Python, así como para interactuar con trazas de ejecución producidas por código instrumentado, consulta la documentación de Python.
PolyTracker se controla mediante un script de Python llamado polytracker. Puedes
instalarlo ejecutando
pip3 install polytracker
PolyTracker requiere un entorno de sistema muy específico para ejecutarse, por lo que casi todos
los usuarios probablemente lo ejecutarán en un entorno contenedorizado. Por suerte,
polytracker lo hace fácil. Todo lo que necesitas es tener docker instalado,
y luego ejecutar:
polytracker docker pull
y
polytracker docker run
El último comando montará el directorio de trabajo actual en el contenedor Docker de PolyTracker y te permitirá compilar y ejecutar programas instrumentados.
El script de control polytracker —que puedes ejecutar desde tu sistema anfitrión
o desde dentro del contenedor Docker— tiene una variedad de comandos, tanto para
instrumentar programas como para analizar los artefactos resultantes. Por
ejemplo, puedes explorar los flujos de datos en la ejecución, reconstruir el grafo de flujo de control del
programa instrumentado e incluso extraer una gramática libre de contexto que coincida con las entradas aceptadas por el programa. Puedes explorar estos
comandos ejecutando
polytracker --help
El script polytracker también es un REPL, si se ejecuta sin argumentos de línea de comandos:
$ polytracker
PolyTracker (4.0.0)
https://github.com/trailofbits/polytracker
Type "help" or "commands"
>>> commands
PolyTracker también incluye un comando build. Este comando permite al usuario
ejecutar cualquier comando de compilación en un entorno instrumentado por Blight.
Esto producirá un archivo blight_journal.jsonl que
registra todos los comandos ejecutados durante la compilación. Si tienes un objetivo C/C++, puedes
instrumentarlo invocando polytracker build y pasando tu comando de compilación:
polytracker build gcc -g -o my_binary my_source.c
Para instrumentar un objetivo de compilación, usa el comando instrument-targets. Por defecto,
el comando usará un blight_journal.jsonl en tu directorio de trabajo
actual para construir una versión instrumentada de tu objetivo de compilación. El
objetivo de compilación instrumentado se construirá usando las mismas banderas que el objetivo de compilación original.
polytracker instrument-targets my_binary
build también admite programas más complejos que usan un sistema de compilación como
autotools o CMake:
polytracker build cmake .. -DCMAKE_BUILD_TYPE=Release
polytracker build ninja
# or
polytracker build ./configure
polytracker build make
Luego ejecuta instrument-targets en cualquiera de los objetivos de la compilación:
polytracker instrument-targets a.bin b.so
Entonces a.instrumented.bin y b.instrumented.so serán las versiones
instrumentadas. Consulta los Dockerfiles en el directorio
examples
para ver ejemplos de cómo se pueden instrumentar programas del mundo real.
El software instrumentado escribirá su salida en la ruta especificada en
POLYDB, o en polytracker.tdag si se omite. Este es un archivo binario que se puede
operar ejecutando:
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}"
)
También puedes ejecutar un binario instrumentado directamente desde el 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")
Esto ejecutará automáticamente el binario instrumentado en un contenedor Docker, si es necesario.
⚠️ Si ejecutas PolyTracker dentro de Docker o una VM: PolyTracker puede ser muy lento si se ejecuta en un entorno virtualizado y el archivo de entrada o, especialmente, la base de datos de salida están ubicados en un directorio mapeado o montado desde el sistema operativo anfitrión. Esto es particularmente cierto cuando se ejecuta PolyTracker en Docker desde un anfitrión macOS. La solución es escribir la base de datos en una ruta dentro del contenedor/VM y luego copiarla al sistema anfitrión al final.
La documentación de la API de Python está disponible aquí.
A nivel de ejecución, la instrumentación de PolyTracker busca una serie de parámetros de configuración especificados a través de variables de entorno. Esto permite modificar los parámetros de instrumentación sin necesidad de recompilar el binario.
PolyTracker acepta parámetros de configuración en forma de variables de entorno para evitar recompilar los programas objetivo. El conjunto actual de variables de entorno que admite PolyTracker es:
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 establecerá sus parámetros de configuración en el siguiente orden:
DFSan usa listas ABI para determinar qué funciones debe instrumentar automáticamente, qué funciones debe ignorar y qué envoltorios de funciones personalizados existen. Consulta la documentación de dfsan para obtener más información.
Intentar compilar grandes proyectos de software puede llevar mucho tiempo, especialmente los antiguos/no soportados. Aún más tiempo consume intentar modificar el sistema de compilación para que admita cambios, como la instrumentación de dfsan/la nuestra.
Hay un script ubicado en polytracker/scripts que puedes ejecutar en cualquier biblioteca
ELF y generará una lista de funciones a ignorar. Lo usamos cuando no
queremos rastrear información que pasa a través de una biblioteca específica como libpng, u
otros subcomponentes de un programa. Dockerfile-listgen.demo existe para compilar
bibliotecas de código abierto comunes y así poder crear estas listas.
Este script es una versión ligeramente modificada de la que tiene DataFlowSanitizer, que
se centra en ignorar las bibliotecas del sistema. El script original se puede encontrar en
dfsan_rt.
Revisa este repositorio de Git. Desde la raíz, puedes compilar la imagen base de PolyTracker en Docker:
pip3 install -e ".[dev]" && polytracker docker rebuild
o simplemente extraer la última versión precompilada de DockerHub:
docker pull trailofbits/polytracker:latest
Para una demostración de PolyTracker ejecutándose en el analizador MuPDF, ejecuta este comando:
docker build -t trailofbits/polytracker-demo-mupdf -f examples/pdf/Dockerfile-mupdf.demo .
mutool_track se compilará en /polytracker/the_klondike/mupdf/build/debug.
Ejecutar mutool_track generará polytracker.tdag, que contiene la
información proporcionada por el análisis de taint.
Para una demostración de PolyTracker ejecutándose en Poppler utils versión 0.84.0, ejecuta este comando:
docker build -t trailofbits/polytracker-demo-poppler -f examples/pdf/Dockerfile-poppler.demo .
Todas las utilidades de poppler se ubicarán en
/polytracker/the_klondike/poppler-0.84.0/build/utils.
cd /polytracker/the_klondike/poppler-0.84.0/build/utils
./pdfinfo_track some_pdf.pdf
Supongamos que quieres profundizar un poco más en la extensión del código base de PolyTracker o en el análisis de trazas TDAG, y no quieres ensuciar tu entorno local instalando una versión de LLVM altamente personalizada.
Si trabajas en Ubuntu y partes de una base relativamente limpia de 22.04 o 24.04, el Gist vinculado detalla los pasos para obtener una versión pasante funcional del contenedor base de PolyTracker. El contenedor base proporciona un entorno de desarrollo con todas las dependencias en el que puedes trabajar directamente, o que puedes ampliar (como hemos hecho en los Dockerfiles de ejemplo).
La ejecución de las pruebas unitarias de Python y C++ debe hacerse dentro del contenedor Docker de PolyTracker.
Las pruebas unitarias de Catch2 en unittests/ se encuentran en
/polytracker-build/unittests/src/taintdag/ dentro del contenedor. Ejecuta el binario de prueba
dentro del contenedor Docker con
cd /polytracker-build/unittests/src/taintdag/ && ./tests-taintdag
Las pruebas unitarias de Python en tests/ requieren programas C++ de prueba locales que los
accesorios de prueba instrumentarán. Ejecútalas usando Pytest en el directorio de trabajo
pytest tests
O usa pytest para ejecutar un solo archivo de prueba con
pytest tests/test_foo.py
PolyTracker actualmente solo se ejecuta en Linux, porque es el único sistema compatible con DataFlowSanitizer. Esta limitación se debe únicamente a la falta de soporte de semánticas de llamadas al sistema de otros SO, que podría añadirse en el futuro. Sin embargo, esto significa que ejecutar PolyTracker en un sistema que no sea Linux requerirá tener Docker instalado.
El taint no se propagará a través de bibliotecas cargadas dinámicamente a menos que esas bibliotecas se hayan compilado desde el código fuente usando PolyTracker, o que exista un soporte específico para las llamadas a bibliotecas implementado en PolyTracker. Actualmente sí hay soporte para propagar el taint a través de la mayoría de las llamadas de la biblioteca estándar de C no instrumentadas. Para ser claros, los programas que usan funciones no instrumentadas seguirán funcionando normalmente; sin embargo, las operaciones realizadas por llamadas a bibliotecas no soportadas no propagarán el taint. Actualmente estamos trabajando en añadir un soporte robusto para programas de C++, pero por ahora los mejores resultados se obtendrán con programas de C.
Si hay problemas con Docker, intenta realizar una limpieza del sistema y compilar con
--no-cache tanto para PolyTracker como para cualquier demo que estés intentando ejecutar.
El peor caso de rendimiento de PolyTracker se produce cuando un solo byte en memoria está simultáneamente marcado por un gran número de bytes de entrada del archivo fuente. Esto es más común al instrumentar algoritmos de compresión y criptográficos con grandes tamaños de bloque. Actualmente se están investigando y desarrollando varias mitigaciones para este comportamiento.
Estas son algunas de las cosas disponibles públicamente que hemos hecho con PolyTracker. ¡Si conoces algo más que te gustaría ver listado aquí, háznoslo saber!
mapping y cavities más precisamente) para señalar un CVE y escribió sobre ello en el blog de Trail of Bits.Esta investigación fue desarrollada por Trail of Bits con financiación de la Agencia de Proyectos de Investigación Avanzada de Defensa (DARPA) bajo el programa SafeDocs como subcontratista de Galois. Está licenciada bajo la licencia Apache 2.0. © 2019, Trail of Bits.
Por favor, contáctanos usando [email protected].