
범용 오염 추적, 데이터 흐름 분석 및 트레이싱을 위한 LLVM 기반 계측 도구입니다.
PolyTracker는 원래 파서의 자동 어휘 주석 및 탐색(Automated Lexical Annotation and Navigation of Parsers) 을 위해 만들어진 도구로, 이 이름은 오직 ALAN 파서 프로젝트(The ALAN Parsers Project) 라고 부르기 위해 뒤에서 만든 역두문자어(backronym)입니다. 그러나 현재는 프로그램의 데이터 흐름 및 제어 흐름 분석을 효율적으로 수행하기 위한 범용 도구로 발전했습니다. PolyTracker는 입력 파일의 어떤 바이트가 어떤 함수에 의해 조작되는지 추적하도록 프로그램을 계측(instrument)하는 LLVM 패스입니다. 데이터 흐름 정보를 담은 데이터베이스와 런타임 트레이스를 출력합니다. 또한 PolyTracker는 출력물을 상호작용하고 분석하기 위한 Python 라이브러리와 대화형 Python REPL도 제공합니다.
PolyTracker는 PolyFile과 함께 사용하여 파서에 있는 함수들의 의미론적 용도를 자동으로 결정할 수 있습니다. 또한 파서가 수용하는 언어를 나타내는 문맥 자유 문법(context free grammar)을 생성할 수 있는 실험적 기능도 있습니다.
Taintgrind 같은 동적 계측 대안과 달리, PolyTracker는 거의 모든 입력에 대해 무시할 만한 성능 오버헤드를 부과하며 입력의 모든 바이트를 한 번에 추적할 수 있습니다. PolyTracker는 LLVM DataFlowSanitizer의 포크로 시작되었으며 Angora Fuzzer에서 많은 영감을 받았습니다. 그러나 Angora 시스템과 달리 PolyTracker는 오염(taint)의 전체 출처(provenance) 를 추적할 수 있습니다. 2021년 2월, LLVM DataFlowSanitizer는 이라는 오염 출처 추적 기능을 추가했습니다. 그러나 한 번에 최대 16개의 오염만 추적할 수 있는 반면, PolyTracker는 최대 2-1개까지 추적할 수 있습니다.
이 README는 PolyTracker를 설치하고 바이너리를 컴파일/계측하기 위한 일반적인 사용 가이드입니다. Python API를 통해 PolyTracker를 프로그래밍 방식으로 사용하거나 확장하는 방법, 그리고 계측된 코드에서 생성된 런타임 트레이스를 다루는 방법은 Python 문서를 참조하십시오.
PolyTracker는 polytracker라는 Python 스크립트로 제어됩니다. 다음을 실행하여 설치할 수 있습니다.
pip3 install polytracker
PolyTracker는 실행에 매우 특정한 시스템 환경을 요구하므로 거의 모든 사용자는 컨테이너 환경에서 실행할 가능성이
높습니다. 다행히 polytracker는 이를 쉽게 만들어 줍니다. docker가 설치되어 있기만 하면 다음을 실행하면 됩니다.
polytracker docker pull
그리고
polytracker docker run
후자의 명령은 현재 작업 디렉터리를 PolyTracker Docker 컨테이너에 마운트하고 계측된 프로그램을 빌드하고 실행할 수 있게 해줍니다.
polytracker 제어 스크립트는 호스트 시스템이나 Docker 컨테이너 내부 어디에서든 실행할 수 있으며, 프로그램 계측과
결과 아티팩트 분석을 위한 다양한 명령을 제공합니다. 예를 들어, 실행 중의 데이터 흐름을 탐색하고, 계측된 프로그램의
제어 흐름 그래프를 재구성하며, 프로그램이 수용하는 입력과 일치하는 문맥 자유 문법까지 추출할 수 있습니다. 다음을
실행하여 이러한 명령들을 살펴볼 수 있습니다.
polytracker --help
polytracker 스크립트는 명령줄 인수 없이 실행하면 REPL로도 동작합니다:
$ polytracker
PolyTracker (4.0.0)
https://github.com/trailofbits/polytracker
Type "help" or "commands"
>>> commands
PolyTracker에는 build 명령도 함께 제공됩니다. 이 명령을 사용하면 Blight
계측 환경에서 어떤 빌드 명령이든 실행할 수 있습니다. 그러면 빌드 중 실행된 모든 명령을 기록하는
blight_journal.jsonl 파일이 생성됩니다. C/C++ 대상이 있다면 polytracker build를 호출하고 빌드 명령을 전달하여
계측할 수 있습니다:
polytracker build gcc -g -o my_binary my_source.c
빌드 대상을 계측하려면 instrument-targets 명령을 사용하십시오. 기본적으로 이 명령은 현재 작업 디렉터리의
blight_journal.jsonl을 사용하여 빌드 대상의 계측된 버전을 빌드합니다. 계측된 빌드 대상은 원래 빌드 대상과 동일한
플래그를 사용하여 빌드됩니다.
polytracker instrument-targets my_binary
build는 autotools나 CMake 같은 빌드 시스템을 사용하는 더 복잡한 프로그램도 지원합니다:
polytracker build cmake .. -DCMAKE_BUILD_TYPE=Release
polytracker build ninja
# 또는
polytracker build ./configure
polytracker build make
그런 다음 빌드의 임의 대상에 대해 instrument-targets를 실행합니다:
polytracker instrument-targets a.bin b.so
그러면 a.instrumented.bin과 b.instrumented.so가 계측된 버전이 됩니다. 실제 프로그램이 어떻게 계측될 수 있는지에
대한 예제는 examples 디렉터리의 Dockerfile을
참조하십시오.
계측된 소프트웨어는 POLYDB에 지정된 경로(생략 시 polytracker.tdag)에 출력을 기록합니다. 이것은 다음을 실행하여
조작할 수 있는 바이너리 파일입니다:
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}")
# 모든 Range 노드 조작
for index, node in enumerate(tdfile.nodes):
if isinstance(node, taint_dag.TDRangeNode):
print(f"Node {index}: first {node.first}, last {node.last}")
# 오염 포리스트(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}"
)
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")
이렇게 하면 필요에 따라 계측된 바이너리가 Docker 컨테이너에서 자동으로 실행됩니다.
⚠️ Docker 또는 VM 내부에서 PolyTracker를 실행하는 경우: 가상화된 환경에서 실행 중이고 입력 파일이나, 특히 출력 데이터베이스가 호스트 OS에서 매핑 또는 마운트된 디렉터리에 있으면 PolyTracker가 매우 느려질 수 있습니다. 이는 특히 macOS 호스트에서 Docker로 PolyTracker를 실행할 때 두드러집니다. 해결책은 데이터베이스를 컨테이너/VM 내부의 경로에 쓰고 맨 마지막에 호스트 시스템으로 복사해 내는 것입니다.
Python API 문서는 여기에서 확인할 수 있습니다.
런타임에 PolyTracker 계측은 환경 변수를 통해 지정된 여러 구성 매개변수를 찾습니다. 이를 통해 바이너리를 다시 컴파일할 필요 없이 계측 매개변수를 수정할 수 있습니다.
PolyTracker는 대상 프로그램을 다시 컴파일하지 않도록 환경 변수 형태의 구성 매개변수를 허용합니다. 현재 PolyTracker가 지원하는 환경 변수 세트는 다음과 같습니다:
POLYDB: 출력 데이터베이스를 저장할 경로 (기본값은 polytracker.tdag)
WLLVM_ARTIFACT_STORE: 모든 빌드 대상의 아티팩트/매니페스트를 저장할 기존 디렉터리 경로를 제공
POLYTRACKER_TAINT_ARGV: '1'로 설정하면 argv를 오염 소스로 사용
POLYTRACKER_STDIN_SOURCE: '1'로 설정하면 stdin을 오염 소스로 사용
POLYTRACKER_STDOUT_SINK: '1'로 설정하면 stdout을 오염 싱크로 사용
POLYTRACKER_STDERR_SINK: '1'로 설정하면 stderr을 오염 싱크로 사용
Polytracker는 다음 순서로 구성 매개변수를 설정합니다:
DFSan은 ABI 목록을 사용하여 자동으로 계측해야 하는 함수, 무시해야 하는 함수, 존재하는 사용자 정의 함수 래퍼를 결정합니다. 자세한 내용은 dfsan 문서를 참조하십시오.
대규모 소프트웨어 프로젝트를 빌드하는 것은 시간이 많이 걸릴 수 있으며, 특히 오래되었거나 지원되지 않는 프로젝트는 더욱 그렇습니다. dfsan/우리 계측과 같은 변경 사항을 지원하도록 빌드 시스템을 수정하는 것은 더욱 시간이 많이 걸립니다.
polytracker/scripts에는 ELF 라이브러리에서 실행할 수 있는 스크립트가 있으며, 무시할 함수 목록을 출력합니다. 우리는
libpng 같은 특정 라이브러리나 프로그램의 다른 하위 구성 요소를 통과하는 정보를 추적하고 싶지 않을 때 이를 사용합니다.
Dockerfile-listgen.demo는 일반적인 오픈 소스 라이브러리를 빌드하여 이러한 목록을 만들 수 있도록 존재합니다.
이 스크립트는 DataFlowSanitizer가 가진 것으로, 시스템 라이브러리 무시에 중점을 둔 버전을 약간 수정한 것입니다. 원본
스크립트는 dfsan_rt에서 찾을 수 있습니다.
이 Git 저장소를 체크아웃하십시오. 루트에서 기본 PolyTracker Docker 이미지를 빌드하거나:
pip3 install -e ".[dev]" && polytracker docker rebuild
DockerHub에서 최신 사전 빌드 버전을 가져올 수 있습니다:
docker pull trailofbits/polytracker:latest
MuPDF 파서에서 PolyTracker를 실행하는 데모를 보려면 다음 명령을 실행하십시오:
docker build -t trailofbits/polytracker-demo-mupdf -f examples/pdf/Dockerfile-mupdf.demo .
mutool_track는 /polytracker/the_klondike/mupdf/build/debug에 빌드됩니다. mutool_track를 실행하면 오염 분석에서
제공하는 정보가 담긴 polytracker.tdag가 출력됩니다.
Poppler utils 버전 0.84.0에서 PolyTracker를 실행하는 데모를 보려면 다음 명령을 실행하십시오:
docker build -t trailofbits/polytracker-demo-poppler -f examples/pdf/Dockerfile-poppler.demo .
모든 poppler 유틸리티는 /polytracker/the_klondike/poppler-0.84.0/build/utils에 위치합니다.
cd /polytracker/the_klondike/poppler-0.84.0/build/utils
./pdfinfo_track some_pdf.pdf
PolyTracker 코드베이스를 확장하거나 TDAG 트레이스를 분석하는 데 좀 더 깊이 들어가고 싶은데, 로컬 환경을 어지럽히며 매우 맞춤화된 LLVM 버전을 설치하고 싶지 않다고 가정해 봅시다.
Ubuntu에서 작업 중이고 비교적 깨끗한 22.04 또는 24.04 베이스에서 시작한다면, 링크된 Gist에 PolyTracker 기본 컨테이너의 작동하는 패스스루(passthrough) 버전을 얻는 단계가 자세히 설명되어 있습니다. 기본 컨테이너는 모든 종속성이 포함된 개발 환경을 제공하며, 그 안에서 직접 작업하거나 (예제 Dockerfile에서 했던 것처럼) 확장할 수 있습니다.
Python 및 C++ 단위 테스트는 모두 PolyTracker Docker 컨테이너 내부에서 실행해야 합니다.
unittests/의 Catch2 단위 테스트는 컨테이너 내부의 /polytracker-build/unittests/src/taintdag/에 있습니다. Docker
컨테이너 내부에서 테스트 바이너리를 실행하십시오:
cd /polytracker-build/unittests/src/taintdag/ && ./tests-taintdag
tests/의 Python 단위 테스트는 테스트 픽스처가 계측할 로컬 테스트 C++ 프로그램이 필요합니다. 작업 디렉터리에서
Pytest를 사용하여 실행하십시오:
pytest tests
또는 pytest를 사용하여 단일 테스트 파일을 실행할 수 있습니다:
pytest tests/test_foo.py
PolyTracker는 현재 Linux에서만 실행됩니다. DataFlow Sanitizer가 지원하는 유일한 시스템이기 때문입니다. 이 제한은 단지 다른 OS의 시스템 호출에 대한 의미론이 부족하기 때문이며, 향후 추가될 수 있습니다. 그러나 이는 비-Linux 시스템에서 PolyTracker를 실행하려면 Docker를 설치해야 한다는 것을 의미합니다.
동적으로 로드된 라이브러리를 통해 오염이 전파되지 않습니다. 해당 라이브러리가 PolyTracker를 사용하여 소스에서 컴파일되었거나, PolyTracker에 구현된 라이브러리 호출에 대한 특정 지원이 있는 경우는 예외입니다. 현재 계측되지 않은 대부분의 C 표준 라이브러리 호출을 통한 오염 전파를 지원하고 있습니다. 분명히 말하면, 계측되지 않은 함수를 사용하는 프로그램은 여전히 정상적으로 실행되지만, 지원되지 않는 라이브러리 호출이 수행한 연산은 오염을 전파하지 않습니다. 현재 C++ 프로그램에 대한 강력한 지원을 추가하기 위해 작업 중이지만, 현재 가장 좋은 결과는 C 프로그램에서 나옵니다.
Docker에 문제가 있는 경우 시스템 프룬(prune)을 수행하고 PolyTracker와 실행하려는 데모 모두에 대해 --no-cache로
빌드해 보십시오.
PolyTracker의 최악의 성능 상황은 메모리의 단일 바이트가 소스 파일의 많은 수의 입력 바이트에 의해 동시에 오염될 때 발생합니다. 이는 큰 블록 크기를 가진 압축 및 암호화 알고리즘을 계측할 때 가장 흔합니다. 현재 이 동작에 대한 여러 완화 방법이 연구 및 개발 중입니다.
다음은 PolyTracker로 수행한 공개적으로 이용 가능한 작업들입니다. 여기에 나열되기를 원하는 다른 것을 알고 있다면 알려주십시오!
mapping 및 cavities를 더 정확히) 트레이스 분석 기능을 사용하여 CVE를
찾아냈고 Trail of Bits 블로그에 이에 대해 글을 썼습니다.이 연구는 Galois의 하청업체로서 SafeDocs 프로그램 하에 국방 고등 연구 계획국(DARPA)의 자금 지원으로 Trail of Bits에서 개발되었습니다. Apache 2.0 라이선스에 따라 라이선스가 부여됩니다. © 2019, Trail of Bits.
[email protected]으로 연락해 주십시오.