
Uma ferramenta de instrumentação baseada em LLVM para rastreamento universal de taint, análise de fluxo de dados e rastreamento.
O PolyTracker é uma ferramenta originalmente criada para a Automated Lexical Annotation and Navigation of Parsers, um backronym concebido unicamente com o propósito de referir-se a ele como The ALAN Parsers Project. No entanto, ele evoluiu para uma ferramenta de propósito geral para realizar com eficiência a análise de fluxo de dados e fluxo de controle de programas. O PolyTracker é um pass do LLVM que instrumenta programas para rastrear quais bytes de um arquivo de entrada são operados por quais funções. Ele gera um banco de dados contendo as informações de fluxo de dados, bem como um trace de execução. O PolyTracker também fornece uma biblioteca Python para interagir com e analisar sua saída, além de um REPL Python interativo.
O PolyTracker pode ser usado em conjunto com PolyFile para determinar automaticamente a finalidade semântica das funções em um parser. Ele também possui um recurso experimental capaz de gerar uma gramática livre de contexto que representa a linguagem aceita por um parser.
Diferentemente de alternativas de instrumentação dinâmica como o , o PolyTracker impõe sobrecarga de desempenho insignificante para quase todas as entradas e é capaz de rastrear cada byte de entrada de uma só vez. O PolyTracker começou como um fork do LLVM DataFlowSanitizer e se inspira fortemente no . No entanto, diferentemente do sistema Angora, o PolyTracker é capaz de rastrear toda a de um taint. Em fevereiro de 2021, o LLVM DataFlowSanitizer adicionou um novo recurso para rastrear a proveniência de taint chamado . No entanto, ele só é capaz de rastrear no máximo 16 taints por vez, enquanto o PolyTracker pode rastrear até 2-1.
Este README serve como guia geral de uso para instalar o PolyTracker e compilar/instrumentar binários. Para interagir programaticamente com ou estender o PolyTracker por meio de sua API Python, bem como para interagir com traces de execução produzidos por código instrumentado, consulte a documentação do Python.
O PolyTracker é controlado por meio de um script Python chamado polytracker. Você pode
instalá-lo executando
pip3 install polytracker
O PolyTracker exige um ambiente de sistema muito específico para ser executado, então quase todos
os usuários provavelmente o executarão em um ambiente conteinerizado. Por sorte,
polytracker torna isso fácil. Tudo o que você precisa fazer é ter o docker instalado,
e então executar:
polytracker docker pull
e
polytracker docker run
O último comando montará o diretório de trabalho atual no contêiner Docker do PolyTracker e permitirá que você compile e execute programas instrumentados.
O script de controle polytracker—que você pode executar a partir do seu sistema host
ou de dentro do contêiner Docker—possui uma variedade de comandos, tanto para
instrumentar programas quanto para analisar os artefatos resultantes. Por
exemplo, você pode explorar os fluxos de dados na execução, reconstruir o grafo de fluxo
de controle do programa instrumentado e até mesmo extrair uma gramática livre de
contexto que corresponda às entradas aceitas pelo programa. Você pode explorar esses
comandos executando
polytracker --help
O script polytracker também é um REPL, se for executado sem argumentos de linha de comando:
$ polytracker
PolyTracker (4.0.0)
https://github.com/trailofbits/polytracker
Type "help" or "commands"
>>> commands
O PolyTracker também vem com um comando build. Esse comando permite que o usuário
execute qualquer comando de build em um ambiente instrumentado com Blight.
Isso produzirá um arquivo blight_journal.jsonl que
registra todos os comandos executados durante o build. Se você tiver um alvo C/C++, você pode
instrumentá-lo invocando polytracker build e passando seu comando de build:
polytracker build gcc -g -o my_binary my_source.c
Para instrumentar um alvo de build, use o comando instrument-targets. Por padrão
o comando utilizará o blight_journal.jsonl no seu diretório de trabalho atual para
criar uma versão instrumentada do seu alvo de build. O
alvo de build instrumentado será compilado usando os mesmos flags do alvo de build original.
polytracker instrument-targets my_binary
O build também suporta programas mais complexos que usam um sistema de build como
autotiools ou CMake:
polytracker build cmake .. -DCMAKE_BUILD_TYPE=Release
polytracker build ninja
# ou
polytracker build ./configure
polytracker build make
Em seguida, execute instrument-targets em qualquer alvo do build:
polytracker instrument-targets a.bin b.so
Então, a.instrumented.bin e b.instrumented.so serão as versões
instrumentadas. Veja os Dockerfiles no diretório
examples
para exemplos de como programas do mundo real podem ser instrumentados.
O software instrumentado gravará sua saída no caminho especificado em
POLYDB, ou em polytracker.tdag se omitido. Este é um arquivo binário que pode ser
manipulado executando:
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}"
)
Você também pode executar um binário instrumentado diretamente do 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")
Isso executará automaticamente o binário instrumentado em um contêiner Docker, se necessário.
⚠️ Se você estiver executando o PolyTracker dentro do Docker ou de uma VM: o PolyTracker pode ser muito lento se estiver sendo executado em um ambiente virtualizado e o arquivo de entrada ou, especialmente, o banco de dados de saída estiverem localizados em um diretório mapeado ou montado a partir do sistema operacional host. Isso é particularmente verdadeiro ao executar o PolyTracker no Docker a partir de um host macOS. A solução é gravar o banco de dados em um caminho dentro do contêiner/VM e depois copiá-lo para o sistema host no final.
A documentação da API Python está disponível aqui.
Em tempo de execução, a instrumentação do PolyTracker procura uma série de parâmetros de configuração especificados por meio de variáveis de ambiente. Isso permite que alguém modifique os parâmetros de instrumentação sem precisar recompilar o binário.
O PolyTracker aceita parâmetros de configuração na forma de variáveis de ambiente para evitar a recompilação dos programas alvo. O conjunto atual de variáveis de ambiente suportadas pelo PolyTracker é:
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.
O Polytracker definirá seus parâmetros de configuração na seguinte ordem:
O DFSan usa listas ABI para determinar quais funções ele deve instrumentar automaticamente, quais funções deve ignorar e quais wrappers de função personalizados existem. Consulte a documentação do dfsan para obter mais informações.
Tentar compilar projetos de software grandes pode ser demorado, especialmente os mais antigos ou sem suporte. É ainda mais demorado tentar modificar o sistema de build para que ele suporte mudanças, como a instrumentação do dfsan/nossa instrumentação.
Existe um script localizado em polytracker/scripts que você pode executar em qualquer
biblioteca ELF e ele gerará uma lista de funções a serem ignoradas. Nós o usamos quando não
queremos rastrear informações que passam por uma biblioteca específica, como libpng, ou
outros subcomponentes de um programa. O Dockerfile-listgen.demo existe para compilar
bibliotecas open source comuns, permitindo criar essas listas.
Este script é uma versão ligeiramente modificada do que o DataFlowSanitizer possui, que
se concentra em ignorar bibliotecas do sistema. O script original pode ser encontrado em
dfsan_rt.
Clone este repositório Git. A partir da raiz, construa a imagem Docker base do PolyTracker:
pip3 install -e ".[dev]" && polytracker docker rebuild
ou simplesmente baixe a versão pré-compilada mais recente do DockerHub:
docker pull trailofbits/polytracker:latest
Para uma demonstração do PolyTracker executando no parser MuPDF, execute este comando:
docker build -t trailofbits/polytracker-demo-mupdf -f examples/pdf/Dockerfile-mupdf.demo .
mutool_track será compilado em /polytracker/the_klondike/mupdf/build/debug.
Executar mutool_track gerará polytracker.tdag, que contém as informações
fornecidas pela análise de taint.
Para uma demonstração do PolyTracker executando no Poppler utils versão 0.84.0, execute este comando:
docker build -t trailofbits/polytracker-demo-poppler -f examples/pdf/Dockerfile-poppler.demo .
Todos os utilitários do poppler estarão localizados em
/polytracker/the_klondike/poppler-0.84.0/build/utils.
cd /polytracker/the_klondike/poppler-0.84.0/build/utils
./pdfinfo_track some_pdf.pdf
Suponha que você queira se aprofundar um pouco mais na extensão do código do PolyTracker ou na análise de traces TDAG, e não queira mexer no seu ambiente local instalando uma versão de LLVM altamente personalizada.
Se você estiver trabalhando no Ubuntu e partindo de uma base relativamente limpa 22.04 ou 24.04, o Gist vinculado detalha as etapas para obter uma versão passthrough funcional do contêiner base do PolyTracker. O contêiner base fornece um ambiente de desenvolvimento com todas as dependências no qual você pode trabalhar diretamente ou que pode ser estendido (como fizemos nos Dockerfiles de exemplo).
Executar os testes unitários de Python e C++ deve ser feito dentro do contêiner Docker do PolyTracker.
Os testes unitários Catch2 em unittests/ ficam em
/polytracker-build/unittests/src/taintdag/ dentro do contêiner. Execute o binário de teste
dentro do contêiner Docker com
cd /polytracker-build/unittests/src/taintdag/ && ./tests-taintdag
Os testes unitários Python em tests/ exigem programas C++ de teste locais que as
fixtures de teste irão instrumentar. Execute-os usando Pytest no diretório de trabalho
pytest tests
Ou use o pytest para executar um único arquivo de teste com
pytest tests/test_foo.py
Atualmente, o PolyTracker só executa no Linux, pois esse é o único sistema suportado pelo DataFlow Sanitizer. Essa limitação se deve apenas à falta de suporte para a semântica das chamadas de sistema de outros sistemas operacionais, o que pode ser adicionado no futuro. No entanto, isso significa que executar o PolyTracker em um sistema não Linux exigirá a instalação do Docker.
Taints não se propagarão por bibliotecas carregadas dinamicamente, a menos que essas bibliotecas tenham sido compiladas a partir do código-fonte usando o PolyTracker, ou haja suporte específico para as chamadas de biblioteca implementadas no PolyTracker. Atualmente, há suporte para propagar taint pela maioria das chamadas da biblioteca padrão C não instrumentadas. Para ser claro, programas que usam funções não instrumentadas continuarão funcionando normalmente; no entanto, operações realizadas por chamadas de biblioteca não suportadas não propagarão taint. Estamos atualmente trabalhando para adicionar suporte robusto a programas C++, mas, por enquanto, os melhores resultados virão de programas C.
Se houver problemas com o Docker, tente executar um system prune e compilar com
--no-cache tanto para o PolyTracker quanto para qualquer demonstração que você esteja tentando executar.
O pior caso de desempenho do PolyTracker ocorre quando um único byte na memória é simultaneamente taintado por um grande número de bytes de entrada do arquivo de origem. Isso é mais comum ao instrumentar algoritmos de compressão e criptográficos com grandes tamanhos de bloco. Há uma série de mitigações para esse comportamento atualmente em pesquisa e desenvolvimento.
Aqui estão algumas das coisas publicamente disponíveis que fizemos com o PolyTracker. Se você souber de algo mais que gostaria de ver listado aqui, por favor nos avise!
mapping e cavities, mais precisamente) para identificar um CVE e escreveu sobre isso no blog da Trail of Bits.Esta pesquisa foi desenvolvida pela Trail of Bits com financiamento da Defense Advanced Research Projects Agency (DARPA) sob o programa SafeDocs como subcontratada da Galois. É licenciada sob a licença Apache 2.0. © 2019, Trail of Bits.
Entre em contato conosco usando [email protected].