FuzzManager jako wybawienie dla pogubionych wśród swoich serwerów #1

Ten wpis jest częścią cyklu “Od zera do bughuntera” – listę wszystkich postów znajdziesz tutaj.

Mozilla rzadko (albo prawie wcale!) nie chwali się swoim działem bezpieczeństwa, w mojej opinii bardzo niesłusznie. GitHubowe repozytoria zespołu MozillaSecurity są bardzo często uaktualniane i można znaleźć w nich złoto – w tym wpisie o jednym z ciekawszych narzędzi o niepozornej nazwie FuzzManager. Swego czasu napisałem (mówiłem o nim na Confidence 2018) do własnego wykorzystania podobne narzędzie, lecz gdybym wiedział o istnieniu tego projektu to oszczędziłbym masę czasu i od razu cieszyłbym się z integracji z mechanizmami badania pokrycia kodu. W tym wpisie przeprowadzę Ciebie drogi Czytelniku przez proces prostej integracji z najpopularniejszymi fuzzerami, czyli AFL oraz Honggfuzz.

Pierwsze kroki w “łapaniu” crashy i ich monitoring

Proces instalacji projektu i początkowej konfiguracji pominę, gdyż jest dobrze udokumentowany w repozytorium. Kompilacja fuzzowanego projektu z dowolonym z rodziny Saintizerów (Address, Memory…) znacząco ułatwi pracę, lecz również przygotujemy się na wykorzystanie Valgrinda w mniej dogodnych konfiguracjach. Punktem wyjścia będzie wrapper w Pythonie, wykorzystujący klasę Collector. Bogatowano komentowany kod jest przystosowany pod obydwa projekty fuzzerów (wystarczy podać odpowiednie ścieżki do folderów):

import subprocess
import os
import hashlib
import time
from Collector.Collector import Collector
from FTB.ProgramConfiguration import ProgramConfiguration
from FTB.Signatures.CrashInfo import CrashInfo

# sciezka i parametry fuzzowanej binarki (zakladam, ze ostatni element to sciezka do inputu - w innym razie wymagana jest modyfikacja listy)
bin_cmd = []
# sciezka do folderu zawierajacego crashe
crash_path = ''

# generacja konfiguracji binarki dla FuzzManagera - pierwszy element listy bin_cmd
bin_configuration = ProgramConfiguration.fromBinary(bin_cmd[0])
crash_hashes = set()

# funkcja pomocnicza do pobierania pelnych sciezek do plikow w folderze
def listdir_fullpath(directory):
    return [os.path.join(directory, f) for f in os.listdir(directory)]

# odpalenie binarki i sprawdzenie pod katem *Sanitizera
def run_with_sanitizer(cmd, f):
    p = subprocess.Popen([cmd + f], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout = p.stdout.read()
    san_report = p.stderr.read()
    return CrashInfo.fromRawCrashData(stdout, san_report, bin_configuration)

# odpalenie binarki i sprawdzenie raportowania Valgrinda (domyslne)
def run_with_valgrind(cmd, f):
    p = subprocess.Popen(['valgrind' + cmd + f], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout = p.stdout.read()
    valgrind_report = p.stderr.read()

    if 'Using Valgrind' in valgrind_report and 'ASan cannot proceed correctly' not in valgrind_report:
        # obiekt zawierajacy kontekst awarii (sparsowany raport Valgrinda)
        return CrashInfo.fromRawCrashData(stdout, valgrind_report, bin_configuration)
    # wykrywanie ASANa
    elif 'ASan cannot proceed correctly' in valgrind_report:
        return run_with_sanitizer(cmd, f)

# upload kontekstu na serwer zdefiniowany w konfiguracji
def submit_crash(crash_context):
    collector = Collector()
    collector.submit(crash_context)


if __name__ == '__main__':
    while True:
        crashes = listdir_fullpath(crash_path)

        for c in crashes:
            # liczenie SHA1 aby nie wysylac zduplikowanych plikow na serwer
            c_hash = hashlib.sha1(open(c).read()).hexdigest()
            if c_hash not in crash_hashes:
                ctx = run_with_valgrind(bin_cmd, c)
                crash_hashes.add(c_hash)
                if ctx:
                    submit_crash(ctx)
        time.sleep(60)

Wynik działania skryptu (błąd aktualnie w trakcie łatania – stacktrace został zamazany):

Podsumowanie

Post intencjonalnie nie jest długi, aby nie zamęczać technicznymi detalami i ma na celu “kopniaka przyśpieszającego” do tworzenia własnych narzędzi w oparciu o FuzzManagera. Następna część traktować będzie o implementacji mechanizmu “crash bucketów”, czyli łączenia podobnych przypadków testowych na podstawie wzorców stacktrace’ów i badania pokrycia kodu w formie graficznej – tymczasem zachęcam Ciebie czytelniku do poprawy lub stworzenia nowego kodu rozwiązującego problemy z fuzzingiem w trochę większej skali 🙂