Obsługa wyjątków

https://chacker.pl/

Implementacja obsługi wyjątków bezpośrednio w jądrze ma pewien czynnik ograniczający: nie możemy przewidzieć, co zrobi każdy inny fuzzer, aby odzyskać dane. Powinniśmy dążyć do bardziej elastycznego rozwiązania, więc dlaczego nie pozwolić każdemu fuzzerowi ustawić własny program obsługi wyjątków? Podczas gdy każdy fuzzer może zaimplementować strategię odzyskiwania, która jest optymalna dla jego specyfiki, dobrze jest mieć domyślny zestaw programów obsługi, które są w stanie odzyskać dane z prostych przypadków, dzięki czemu wszystkie fuzzery mogą z tego skorzystać.

Rozszerzamy klasę Fuzzer, aby dodać ogólną obsługę wyjątków. W on_boot dodawane jest wywołanie install_idt (1), aby wstrzyknąć obsługę wyjątków i skonfigurować nowy IDT gościa. Metoda install_idt przyjmuje liczbę wektorów (domyślnie 30) i wywołuje make_vector_handler (2) dla każdej wartości z zakresu od 0 do liczby wektorów. Wpisy zwrócone przez make_vector_handler są używane przez install_idt do wygenerowania nowego IDT (3). Metoda make_vector_handler generuje kod asemblera do obsługi określonego numeru wektora i wstrzykuje go do gościa (4), ale go nie wykonuje. Następnie zwraca wpis IDT wskazujący na obsługę (5). Domyślnie kod wygenerowany przez make_vector_handler po prostu wysyła pustą odpowiedź i przywraca poprzedni stan kontekstu (6). Bez dalszych modyfikacji możemy ponownie przetestować poprzedni fuzzer MSR:

Widzimy, że liczba ponownych uruchomień zmalała, co poprawiło szybkość rozmycia.

MSR Fuzzer

https://chacker.pl/

Poprzedni fuzzer był dobrym przykładem do nauki, ale nie możemy się po nim wiele spodziewać. Teraz przejdziemy do nieco bardziej zaawansowanego. Ten kolejny fuzzer wygeneruje losowe instrukcje RDMSR/WRMSR, aby rozmyć rejestry specyficzne dla modelu. Chociaż jest to nadal superprosty fuzzer, podobny był w stanie znaleźć prawdziwe błędy, takie jak CVE-2020-0751

Tym razem zamiast generować MSR z losowych liczb całkowitych, używamy zakodowanej na stałe listy msrs (1). Nawet jeśli ta lista nie jest wyczerpująca (na przykład brakuje nam syntetycznych MSR, które są specyficzne dla hypervisora), pozwalamy fuzzerowi na mutację (2) elementów listy, dzięki czemu ostatecznie odkryje nowe MSR. Odkryte MSR są przechowywane w słowniku (3), więc nie tylko MSR jest zapisywany, ale także zawartość, która została odczytana lub zapisana. W ten sposób poprzednia zawartość może zostać uwzględniona w korpusie fuzzingu (4) kolejnych iteracji. Zawartość operacji WRMSR jest również wysyłana (5), ponieważ oznacza to, że wykonanie instrukcji nie spowodowało wyjątku. Metoda flip_bits (6) została zaimplementowana w celu wykonania mutacji danych. Przyjmuje ona dwa argumenty: dane do mutacji (w formie liczby całkowitej) i rozmiar w bitach. Długość bitu w zakresie od 1 do argumentu rozmiaru jest wybierana losowo, co daje większe prawdopodobieństwo małym rozmiarom. Ta długość bitu jest używana do generowania losowej maski bitowej, która jest XORowana względem danych. Uruchommy fuzzer i zobaczmy, co się stanie

Można zaobserwować irytującą liczbę restartów; znacznie spowalniają one proces rozmycia. Powodem jest to, że nie zaimplementowaliśmy żadnego mechanizmu obsługi wyjątków, więc omówimy ten problem w następnej sekcji.