Fuzzing’e Giriş: Başlangıç, Hatalar ve Çalıştırma

Published on

Bu yazı Medium’da yayınlanmıştır.

Uzun süredir fuzzing’e giriş yapmak istiyordum özellikle tarayıcı tarafında. Bu kapsamda ilk aşamalarda olmasa da ilerleyen süreçte Chrome’un açık kaynaklı versiyonu olan Chromium üzerinde çalışmalara devam etme planım bulunuyor.

Tabi ki ilk aşamalarda bir tarayıcının iç yapısını (Browser Proces, Renderer Process, Sandbox) anlama gibi öğrenme kısımları yer alıyor. Bu kısımları da hem okuma hem de bir şeyler deneye deneye öğrenmeye çalışıyorum.

Ancak Chrome gibi devasa bir yapının tamamını sizlerde takdir edersiniz ki herşeyi fuzz etmem çok çok zor olacaktır. Bu kapsamda V8 Engine, WebAssembly (Wasm), Blink (Rendering Engine) ve Chrome’un eklentileri gibi bir alan belirleyip, onun üzerinden gitmek daha sağlıklı olacak.

Fuzzing seçimi

Fuzzing ve fuzzer seçme ile ilgili pek çok dökümantasyon ve bilgilendirici makale yer alıyor. Kendi tarafımda Google’un açık kaynaklı projeler için ölçeklenebilir bir fuzzing altyapısı olarak sunduğu ClusterFuzz’ın daha basit versiyonu olan ve kendi bilgisayarımda küçük ölçekli denemeler yapmak için ClusterFuzzLite sürümünü kullanmaya karar verdim.

ClusterFuzzLite, Google’ın devasa “ClusterFuzz” altyapısının (binlerce makinede koşan versiyonun) hafifletilmiş, yerel veya CI/CD süreçlerine (GitHub Actions gibi) entegre edilebilen halidir. ClusterFuzzLite, kodun içinde “fuzz target” dediğimiz küçük giriş kapıları arar. Bu kapılar, dışarıdan gelen (fuzzer tarafından üretilen) rastgele veriyi alır ve asıl koda (örneğin Chrome’un bir parçasına) iletir.

1. Docker Kurulumu

Eğer Docker yüklü değilse, Docker web sitesinde gidip ilgili versiyonunu indirip, kurabilirsiz. Ben MacOS üzerinde (Apple silicon) çalışacağım için ilgili versiyonunu indirip, gerekli yüklemeyi yaptım.

2. Çalışma klasörünü hazırlama

Makinanınızın herhangi bir dizininde yeni bir klasör açıyoruz. Klasörü açtıktan sonra buranın içerisine “chrome-fuzz-test” adında yeni bir klasör açtım. Tabi ki bunlar benim isimlendirmelerim, herkes kendi isimlendirmesini gerçekleştirebilir.

mkdir chrome-fuzz-testcd chrome-fuzz-test Öncelikli olarak bu klasör içerisine üç yeni dosya açacağız: Dockerfile: Docker’a hangi makineleri kuracağını söyler.> build.sh: Kodun nasıl derleneceğini söyleyen “tarif” dosyasıdır.> fuzz_target.cc: Test etmek istediğimiz (veya Chrome’dan kopyaladığımız) asıl C++ kodudur. Dosyaların içerikleri şu şekilde olacak

2.1 Dockerfile Dosyası

FROM gcr.io/oss-fuzz-base/base-builder# Gerekli paketleri yüklüyoruzRUN apt-get update && apt-get install -y make# Kendi dosyalarımızı Docker’ın içine kopyalıyoruzCOPY . $SRC/# Derleme betiğini kopyalıyoruzCOPY build.sh $SRC/

2.2 build.sh dosyası

#!/bin/bash -eu# Bu komut, C++ kodunu fuzzer motoruyla birleştirir$CXX $CXXFLAGS $LIB_FUZZING_ENGINE fuzz_target.cc -o $OUT/my_chrome_fuzzer

2.3 fuzz_target.cc

Test dosyası olarak konumlandırdım. Dosya ismini kendinize göre adlandırabilirsiniz. Bu kısma daha sonra geleceğiz çünkü bunu daha akıllı bir otomasyon üzerinden yapacağız.

2.4 Local Builder’ı Çalıştırma

Terminal üzerinden klasörümüze gidiyoruz ve docker ile built etme sürecini başlatıyoruz. Bu kısım makinanızın hızına göre yavaş yada hızlı sürebilir.

docker build — platform linux/amd64 -t fuzz-builder . Bu işlem bittikten sonra derleme (compile) işlemini başlatıyoruz. Bu komut; klasördeki dosyaları alır, Docker içindeki fuzzer araçlarıyla işler ve out isimli bir klasöre gönderir. docker run — platform linux/amd64 — rm \ -e FUZZING_LANGUAGE=c++ \ -v $(pwd):/src \ -v $(pwd)/out:/out \ fuzz-builder \ /bin/bash -c “compile_libfuzzer && clang++ -O1 -fno-omit-frame-pointer -gline-tables-only -fsanitize=address,fuzzer /src/fuzz_target.cc -o /out/my_chrome_fuzzer” Ve son olarakta fuzzer sürecini başlatırız: docker run — platform linux/amd64 — rm \ -e RUN_FUZZER_MODE=standalone \ -e FUZZING_ENGINE=libfuzzer \ -e SANITIZER=address \ -v $(pwd)/out:/out \ gcr.io/oss-fuzz-base/base-runner \ run_fuzzer my_chrome_fuzzer Bu komutlar genel olarak çalışacaktır. Aşağıdaki hatalar ve çözümü sonralarındaki final/ana komutlar yukarıdakilerdir.

2.5 Built etme sürecindeki hataların çözümü

2.5.1 Mimari uyuşmazlığı

Google’ın ClusterFuzz ve OSS-Fuzz altyapısı ağırlıklı olarak x86_64 (Intel/AMD tabanlı bulut sunucuları) için optimize edilmiştir. Apple Silicon için yerel (native) imajlar bazen eksik veya hatalı olabilir. Bu yüzden emülasyonla (amd64) devam etmek, testlerin doğruluğu özellikle bellek hatalarının tespiti açısından daha güvenli denilebilir.

Benim makinam Apple Silicon, Google’ın fuzzer imajı ise x86_64 (amd64) mimarisinde olduğu için bir uyuşmazlık hatası verebilir:

1 warning found (use docker —debug to expand):> - InvalidBaseImagePlatform: Base image gcr.io/oss-fuzz-base/base-builder was pulled with platform “linux/amd64”, expected “linux/arm64” for current build (line 1) Bu kapsamda biz Docker’a mimari farkını bildiğimiz ve yine de amd64 olarak çalıştırmasını istediğini söyleyeceğiz. Komutlar ise şu şekilde olacak:

Build oluştururken:

docker build — platform linux/amd64 -t gcr.io/oss-fuzz-base/base-builder-local . Derleme (Compile) yaparken: docker run — platform linux/amd64 — rm -v $(pwd):/src gcr.io/oss-fuzz-base/base-builder-local compile Çalıştırırken (Run): docker run — platform linux/amd64 — rm -v $(pwd)/out:/out gcr.io/oss-fuzz-base/base-runner run_fuzzer my_chrome_fuzzer 2.5.2 Hangi dilde bir fuzzer derliyorum?

2.5.1’de derleme yaparken, mimari taraftaki problemi atlatabilmek için yeni bir komut belirtmiştik. O komutu çalıştırdığımızda, aşağıdaki gibi bir hata/sonuç görebiliriz.

docker run — platform linux/amd64 — rm -v $(pwd):/src gcr.io/oss-fuzz-base/base-builder-local compile Bu hata, ClusterFuzzLite’ın “ben hangi dilde bir fuzzer derliyorum” sorusuna yanıt bulamadığı için önümüze geliyor. Biz C++ kodlarını kullandığımız için, derleme komutumuzu şu şekilde güncelliyoruz: docker run — platform linux/amd64 — rm \ -e FUZZING_LANGUAGE=c++ \ -v $(pwd):/src \ gcr.io/oss-fuzz-base/base-builder-local compile 2.5.3 Çalıştırırken alınan “unbound variable” engeli sha256: ……Status: Downloaded newer image for gcr.io/oss-fuzz-base/base-runner:latestsysctl: setting key “vm.mmap_rnd_bits”, ignoring: Read-only file system/usr/local/bin/run_fuzzer: line 47: RUN_FUZZER_MODE: unbound variable Run yaptığımız zaman, yukarıdaki hata ile karşılaşabiliriz ki ben karşılaştım.

ClusterFuzzLite’ın run_fuzzer betiği, hangi modda çalışacağını (standalone, batch, veya interactive) bilmek istediği için “unbound variable” engeline takılıyoruz. Local’de testleri yapacağımız için bunu standalone moduna ayarlamamız gerekiyor.

Düzeltilmiş çalıştırma (run) komutu:

docker run — platform linux/amd64 — rm \ -e RUN_FUZZER_MODE=standalone \ -v $(pwd)/out:/out \ gcr.io/oss-fuzz-base/base-runner \ run_fuzzer my_chrome_fuzzer -e RUN_FUZZER_MODE=standalone: Betiğe “şu an bir CI/CD sisteminde değilim, sadece bu testi çalıştır ve bitir” diyoruz.

run_fuzzer: Bu betik, bizim out klasöründeki my_chrome_fuzzer dosyasını bulur, arkasına gerekli bayrakları ekler ve motoru ateşler.

2.5.4 Çalıştırırken alınan “FUZZING ENGINE” hatası

sysctl: setting key “vm.mmap_rnd_bits”, ignoring: Read-only file system/usr/local/bin/run_fuzzer: line 50: FUZZING_ENGINE: unbound variable 2.5.3’deki ayarlamalardan sonra çalıştırma yaptığımızda yukarıdaki hatayı aldık. ClusterFuzzLite’ın run_fuzzer betiği bize “”Hangi fuzzer motorunu kullanıyorsun?” tarzında bir soruyla karşımıza geldi. Biz burada “libfuzzer” kullanacağız ve komutumuz ona göre güncelleyeceğiz.

Bununla birlikte artı olarak “-e SANITIZER=address” komutunu ekledik. Burada bellek hatalarını takip eden ASan’ın devrede olduğunu söyledik.

docker run — platform linux/amd64 — rm \ -e RUN_FUZZER_MODE=standalone \ -e FUZZING_ENGINE=libfuzzer \ -e SANITIZER=address \ -v $(pwd)/out:/out \ gcr.io/oss-fuzz-base/base-runner \ run_fuzzer my_chrome_fuzzer

3. Scripti daha akıllı hale getirmek

Denemeler gerçekleştirirken, devamlı run yapma işimi çok her defasında uzatacaktı. Bu yüzden bir klasör oluşturdum, hem eski hem yeni denemelerimi bunun içerisine gömdüm. Yine denemelerden sonra ortaya çıkan crash vb. kısımlarına da kolayca erişmek istiyordum özellikle her bir test kodu için. Ya da ortaya çıkacak hataya uygun klasör isimlendirmesi yapıp, işimi biraz daha fazla kolaylaştırmayı amaçladım.

Bu kapsamda chrome-fuzz-test klasörünün yeni yapısı şu şekilde oldu:

chrome-fuzz-test/├── experiments/ ← Tüm eski ve gelecekteki kodları burada tutuyorum│ ├── 01_heap_overflow.cc│ ├── 02_uaf_mojo.cc│ └── 03_v8_jit_mock.cc├── fuzz_target.cc # Bu dosya sadece “sembolik link” veya kopya olacak├── commander.sh # Akıllı scriptimiz└── results/ # Bulunan crashler commander.sh scriptimiz şu şekilde olacak: #!/bin/bash> # Görsel Arayüz Renkleri> GREEN=’\033[0;32m’> RED=’\033[0;31m’> YELLOW=’\033[1;33m’> NC=’\033[0m’> # — — DENEY SEÇİCİ — -> echo -e “${YELLOW} — — Mevcut Deneyler — -${NC}”> ls -1 experiments/> echo -e “${YELLOW} — — — — — — — — — — — -${NC}”> echo -n “Çalıştırmak istediğiniz deneyi yazın: “> read EX_FILE> if [ -f “experiments/$EX_FILE” ]; then> cp “experiments/$EX_FILE” fuzz_target.cc> echo -e “${GREEN}[+] $EX_FILE yüklendi ve fuzz_target.cc olarak hazırlandı.${NC}”> else> echo -e “${RED}[!] Hata: experiments/$EX_FILE bulunamadı!${NC}”> exit 1> fi> # — — — — — — — — — —> echo -e “${GREEN}[+] Fuzz-Commander Pro Başlatılıyor…${NC}”> # 1. Klasörleri Hazırla> mkdir -p out corpus artifacts results> # 2. Derleme (Her zaman temiz bir build ile başla)> echo -e “${YELLOW}[] Derleniyor…${NC}”> docker run — platform linux/amd64 — rm -v $(pwd):/src -v $(pwd)/out:/out > gcr.io/oss-fuzz-base/base-builder > /bin/bash -c “clang++ -O1 -g -fsanitize=address,fuzzer /src/fuzz_target.cc -o /out/my_chrome_fuzzer”> # 3. Fuzzer’ı Arka Planda Başlat> docker run — platform linux/amd64 — rm > -v $(pwd):/src -v $(pwd)/out:/out -v $(pwd)/corpus:/tmp/corpus -v $(pwd)/artifacts:/tmp/artifacts > gcr.io/oss-fuzz-base/base-runner > /out/my_chrome_fuzzer /tmp/corpus -artifact_prefix=/tmp/artifacts/ -use_value_profile=1 > fuzzing.log 2>&1 &> FUZZ_PID=$!> echo -e “${GREEN}[+] Av başladı. PID: $FUZZ_PID${NC}”> while true; do> sleep 5> CRASH_FILE=$(ls artifacts/crash- 2>/dev/null | head -1)> if [ ! -z “$CRASH_FILE” ]; then> echo -e “${RED}[!] HATA TESPİT EDİLDİ! Analiz ediliyor…${NC}”> # A. Hata Tipini Belirle (Triage)> # ASan raporundaki hata türünü yakala (örn: heap-use-after-free)> ERROR_TYPE=$(grep “ERROR: AddressSanitizer:” fuzzing.log | tail -1 | awk ‘{print $3}’)> [ -z “$ERROR_TYPE” ] && ERROR_TYPE=”unknown_error”> # B. Klasörleme> TIMESTAMP=$(date +%Y%m%d_%H%M%S)> FINAL_DIR=”results/${ERROR_TYPE}${TIMESTAMP}”> mkdir -p “$FINAL_DIR”> # C. Minimize Etme> echo -e “${YELLOW}[*] Dosya minimize ediliyor (15 saniye)…${NC}”> docker run — platform linux/amd64 — rm -v $(pwd):/src -v $(pwd)/out:/out > gcr.io/oss-fuzz-base/base-runner > /out/my_chrome_fuzzer -minimize_crash=1 “/src/$CRASH_FILE” -target_run_time=15 > “$FINAL_DIR/minimized_log.txt” 2>&1> # D. Dosyaları Taşı ve Temizle> mv “$CRASH_FILE” “$FINAL_DIR/original_crash”> [ -f “minimized_from$CRASH_FILE” ] && mv “minimized_from_$CRASH_FILE” “$FINAL_DIR/minimized_crash”> cp fuzzing.log “$FINAL_DIR/asan_report.txt”> echo -e “${GREEN}[V] Analiz tamamlandı: $FINAL_DIR${NC}”> # E. Yeniden Başlat (Fuzzer çöktüğü için yeni süreç açıyoruz)> echo -e “${YELLOW}[] Fuzzer yeniden başlatılıyor…${NC}”> rm -f artifacts/crash-> exec “$0” # Scripti kendi üzerinde yeniden başlatır (Yenileme)> fi> # Coverage takibi> COV=$(grep -o ‘cov: [0–9]*’ fuzzing.log | tail -1)> echo -ne “Süreç: $FUZZ_PID | Kapsam: $COV \r”> done Aslında script içerisinde fazlaca detay var ama hepsi ile ilgili detay yazamadım. Bir sonraki kısımda kod testlerine odaklanacağım. Not olarak, kod testleri sırasında dahi farklı farklı hatalar ile karşılaştım.