Fuzzing

Kaan Caglan
5 min readOct 1, 2020

Fuzzing’in arkasındaki temel fikir, bir güvenlik açığını tetiklemek için programa çok sayıda rastgele girdi girmektir. İlgilenilen kod için bir test kümesi oluşturursunuz, onu rastgele veri üreten bir fuzzing motoruyla eleştirirsiniz ve onu bir yerde bir sunucuda başlatırsınız. Saatler, günler veya haftalar sonra eğer test kümeniz sağlamsa kodun belli bir noktada çökmesine neden olan bir dizi girdi ile geri gelir. Bu süreci hızlandırmak için “Address Sanitizer” kullanılabilir. Address Sanitizier Google’ın çıkardığı bir açık kaynak kodlu bellek bozulması hatalarını algılayan programdır.

Address Sanitizer bunları bulabilir:

  • Use after free (dangling pointer dereference)
  • Heap buffer overflow
  • Stack buffer overflow
  • Global buffer overflow
  • Use after return
  • Use after scope
  • Initialization order bugs
  • Memory leaks

Fuzzing çeşitlerine gelecek olursak genel olarak iki tür Fuzzer vardir.

  1. Mutation Fuzzer (Dumb Fuzzer): Mutation Fuzzer “aptalca” fuzzing olarak nitelendirilebilir. Bunun sebebi ürettiği girdileri tamamen rastgele üretiyor olmasıdır. Üretilen girdiler sonucunda uygulamada açıklar bulunabilir ama gerçek açıkları bulması tamamen rastgele üretildiği için çok zordur.
  2. Generation Fuzzer: Fuzzer algoritmasının sıfırdan hatalı biçimlendirilmiş girdi oluşturabileceği bazı başlangıç test verilerini gerektiren bir tür “akıllı” fuzzing. Generation Fuzzer birçok durumda Mutation Fuzzerdan daha iyidir çünkü program beklenen girdileri alır.

American Fuzzy Lop (AFL)

AFL(American Fuzzy Lop) bir çok insan tarafından tercih edilen ‘Generation Fuzzer’ sayabileceğimiz google’ın geliştirdiği açık kaynak kodlu bir Fuzzer algoritması. İnsanlar tarafından daha çok tercih edilme sebebi AFL test edilen programın dahili davranışlarını gözlemler ve keşfedilmemiş yürütme yollarını tetiklemek için test senaryoları ayarlar. Kurulumu çok basittir ve fuzzing süreci hakkındaki çok fazla bilgiyi anlık olarak görmemizi sağlayan bir UI’a sahiptir.

AFL kullanmak için 2 farklı yolumuz var.

1- AFL-GCC ile derlemek. Bunu yapabilmemiz için kaynak koda ihtiyacımız var. Bu süreçte AFL programı derlerken kendi fonksiyonlarını yerleştiriyor.

2- QEMU kullanmak. Eğer kaynak koda sahip değilsek derlenmiş programımızı QEMU emülatörü çalıştırarak fuzzing işlemini gerçekleştirebiliriz.

Kurulum

Paket Yöneticisi yardımı ile:

  1. sudo apt-get update -y
  2. sudo apt-get install -y afl

Kaynak Kodunu kurarak:

  1. git clone https://github.com/mirrorer/afl
  2. cd afl
  3. make && sudo make install

Kullanım

Bu yazıda sadece kaynak kodu elimizde olan programı fuzzlamayı göstereceğim. Github üzerinden herhangi bir açık kaynak kodlu projeyi kullanabiliriz. Ben bilinçli bir şekilde zaafiyetli yazılmış olan bir c projesini kullanmayı düşündüm.

  1. “git clone https://github.com/fuzzstati0n/fuzzgoat.git”
  2. cd fuzzgoat && make

Kaynak kodunu incelerseniz fuzzgoat.c dosyasının içerisinde zaafiyetlerin hepsi yorum olarak yazılmış.

Kaynak kodumuzu “make” yardımıyla derledikten sonra fuzzing işlemine başlayabiliriz.

Makefile’ın içeriğine bakacak olursak CC = afl-gcc tanımını görüyoruz. CC, C programının hangi derleyici ile derleneceğini belirtmek için kullanılan bir makrodur. Derleyici olarak afl-gcc kullanmamızın sebebi yazının başında bahsettiğimiz gibi AFL nin kendi fonksiyonlarını ekliyor olması. “make” komutunun sonunda fuzzgoat ve fuzzgoat_ASAN isminde 2 binary oluşuyor. fuzzgoat_ASAN ın fuzzgoattan farkı “-fsanitize=address” ile derlenmiş olması. Bu da Adress Sanitizer in aktifleştirildiği anlamına geliyor. Makefile kullanmadan derleyecek olursak:

Bu şekilde derleyebiliriz. Şu anda elimizde AFL ile derlenmiş bir binary var, fuzzlama işlemine başlayabiliriz.

Yazının başında bahsettiğimiz gibi AFL ile fuzzing işlemini yapabilmemiz için bir input dizinine ihtiyacımız var, programın beklediği input türüne örnekler verirsek fuzzing işleminin sonucunda aldığımız çıktılar daha doğru olacaktır (veya en azından daha hızlı sonuç alacağız).

Programımıza bakarsak girdi olarak bizden bir json dosyası istiyor o yüzden eğer input dizinimize “json” örnekleri girmek AFL işleminin daha başarılı olmasını sağlayacaktır. fuzzgoat dizini içerisinde önceden hazırlanmış in ve input klasörleri var. İçeriğine bakacak olursak

Boş bir json örneği verilmiş, bu sayede AFL input türümüzün json olduğunu bilecek ve ona göre aramaya başlayacak. Eğer hiçbir şey vermeyip “seed” dosyasının içerisine sadece “fuzzing” gibi öylesine bir şey yazsaydık da AFL işlemini başlatabilecektik ve programımız çok büyük olmadığı için muhtemelen doğru sonuçlar alacaktık. Ama program büyüdükçe doğru inputları vermek önemli oluyor. Daha büyük programlar için önceden yazılmış test case içerisindeki dosyaları kullanabiliriz.

Fuzzing işlemini başlatmaya gelirsek uygulamamız gereken komut:

“$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program […params…]” (https://github.com/google/AFL)

Buradaki testcase_dir girdilerimizin bulunduğu dizin, findings_dir çıktılarımızın kaydedileceği dizin /path/to/program binaryimizin bulunduğu dizin, […params…] ise fuzzing işlemine ekstaradan verebileceğimiz parametreler.

Her afl-fuzz kopyası 1 CPU çekirdeği ile çalışıyor, bu yüzden elimizde eğer yeterli sayıda CPU çekirdeğimiz varsa birden fazla fuzzier başlatabiliriz. Elimizdeki CPU çekirdeği sayısına “afl-gotcpu” komutu ile bakabiliriz.

Bu durumda eğer istersem 7 fuzzer başlatabilirim. 7 Fuzzerin 1 tanesi master, kalan 6 tanesi de slave olarak oluşturuluyor bu da az önce bahsettiğimiz fuzzing işlemine vereceğimiz ekstra parametrelerden birisi. Bu örnekte 3 tane fuzzer başlatarak ilerleyelim. Bu basit bir shell script yazarak başlatabiliriz.

Burada -M master fuzzer olduğunu, -S lerde slave fuzzer olduğunu belirtmek için. Verilen isimlerde birazdan ./out dizininde oluşacak olan farklı fuzzerların farklı çıktılarını görebilmemiz için.

./run.sh şeklinde çalıştırıp başlatabiliriz ve daha sonrasında o screen e erişmek için

screen -rd <screen_name>

komutunu kullanabiliriz.

Çıktımızı inceleyecek olursak:

Projemiz fuzzing işlemi için özel olarak bilinçli bir şekilde “bug” lardan oluştuğu için sadece 2 dakika içerisine 1961 (24 unique) hata bulundu, normal şartlarda bu süreç günler sürebilir. Hangi girdilerin bu hatalara sebeb olduğuna bakmak için belirttiğimiz -o dizinine (bu örnekte ./out) bakabiliriz. İçerisinde 3 adet klasör bulunacak, fuzzer-master, fuzzer-slave1 ve fuzzer-slave2. Bunlar da başlattığımız 3 farklı fuzzerin çıktıları. Her bir klasörün içerisinde ./crashes adında bir klasör var, eğer unique_crash bulabildiysek o klasörün içerisine bu girdiler kaydedilecektir.

Elimizde bir süre sonra bir çok programa hata verdiren girdi örneği olacak. Bunların içeriğine bakarak girilen hangi girdinin hataya ve hangi hataya sebeb olduğunu öğrenebiliriz.

Eğer oluşturulan hatalardan birisini programı gdb ile başlattıktan sonra programa verirsek aldığı hatayı da görebiliriz. Daha sonrasında GDB nin Exploitable(https://github.com/jfoote/exploitable) isimli pluginini kullanarak bu hatanın exploit üretilebilir bir hata olup olmadığına bakabiliriz.

Exploitability Classification: EXPLOITABLE

Sonuç olarak programda birden fazla açık bulduk ve bulunan açıklardan bazıları exploit yazılabilecek türden hatalar.

--

--