Integralność przepływu sterowania

Integralność przepływu sterowania ( CFI ) to ogólny termin określający techniki bezpieczeństwa komputerowego , które zapobiegają przekierowywaniu przepływu wykonywania ( przepływu sterowania ) programu przez różnorodne ataki złośliwego oprogramowania.

Tło

Program komputerowy często zmienia swój przepływ sterowania, aby podejmować decyzje i używać różnych części kodu. Takie transfery mogą być bezpośrednie , ponieważ adres docelowy jest zapisywany w samym kodzie, lub pośrednie , ponieważ sam adres docelowy jest zmienną w pamięci lub rejestrze procesora. W typowym wywołaniu funkcji program wykonuje wywołanie bezpośrednie, ale wraca do funkcji wywołującej za pomocą stosu – pośredniego wstecznego . Kiedy wskaźnik funkcji , na przykład z wirtualnej tabeli , mówimy, że następuje pośredni transfer krawędzią do przodu .

Atakujący próbują wstrzyknąć kod do programu, aby wykorzystać jego uprawnienia lub wydobyć dane z jego pamięci. Zanim kod wykonywalny stanie się zwykle tylko do odczytu, osoba atakująca może dowolnie zmieniać kod podczas jego uruchamiania, celując w bezpośrednie transfery lub nawet bez żadnych transferów. Po W^X atakujący chce zamiast tego przekierować wykonanie do oddzielnego, niechronionego obszaru zawierającego kod do uruchomienia, korzystając z transferów pośrednich: można nadpisać wirtualną tabelę dla ataku typu forward edge lub zmienić stos wywołań dla ataku wstecznego ( programowanie zorientowane na zwrot ). CFI ma na celu ochronę transferów pośrednich przed udaniem się do niezamierzonych lokalizacji.

Techniki

Powiązane techniki obejmują separację wskaźników kodu (CPS), integralność wskaźników kodu (CPI), stosy kanarków , stosy cienia i weryfikację wskaźnika vtable .

Implementacje

Powiązane implementacje są dostępne w Clang (ogólnie LLVM), Control Flow Guard i Return Flow Guard firmy Microsoft, Indirect Function-Call Checks i Reuse Attack Protector (RAP) firmy Google.

LLVM/Clang

LLVM/Clang zapewnia opcję „CFI”, która działa na krawędzi do przodu, sprawdzając błędy w tabelach wirtualnych i rzutowaniach typów. To zależy od optymalizacji czasu łącza (LTO), aby wiedzieć, jakie funkcje mają być wywoływane w normalnych przypadkach. Istnieje osobny schemat „ stosu wywołań w tle ”, który chroni na tylnej krawędzi, sprawdzając modyfikacje stosu wywołań, dostępny tylko dla aarch64.

Google dostarcza Androida z jądrem Linuksa skompilowanym przez Clang z optymalizacją czasu łącza (LTO) i CFI od 2018 roku. SCS jest dostępny jako opcja dla jądra Linuksa, w tym na Androida.

Technologia Intel Control-flow Enforcement

Technologia Intel Control-flow Enforcement Technology (CET) wykrywa kompromisy w celu kontrolowania integralności przepływu za pomocą stosu w tle (SS) i pośredniego śledzenia rozgałęzień (IBT).

Stos cienia przechowuje kopię adresu zwrotnego każdego WYWOŁANIA w specjalnie chronionym stosie cienia. Na RET procesor sprawdza, czy adres powrotu przechowywany w normalnym stosie iw stosie cienia jest równy. Jeśli adresy nie są równe, procesor generuje INT #21 (błąd ochrony przepływu sterowania).

Pośrednie śledzenie gałęzi wykrywa pośrednie instrukcje JMP lub CALL z nieautoryzowanymi celami. Realizuje się to poprzez dodanie nowej wewnętrznej maszyny stanów w procesorze. Zachowanie pośrednich instrukcji JMP i CALL jest zmienione tak, że przełączają maszynę stanów z IDLE na WAIT_FOR_ENDBRANCH. W stanie WAIT_FOR_ENDBRANCH następną instrukcją do wykonania musi być nowa instrukcja ENDBRANCH (ENDBR32 w trybie 32-bitowym lub ENDBR64 w trybie 64-bitowym), która zmienia wewnętrzny automat stanów z WAIT_FOR_ENDBRANCH z powrotem na IDLE. Zatem każdy autoryzowany cel pośredniego JMP lub CALL musi zaczynać się od ENDBRANCH. Jeśli procesor jest w stanie WAIT_FOR_ENDBRANCH (co oznacza, że ​​poprzednia instrukcja była pośrednim JMP lub CALL), a następna instrukcja nie jest instrukcją ENDBRANCH, procesor generuje błąd INT #21 (błąd ochrony przepływu sterowania). Na procesorach, które nie obsługują pośredniego śledzenia rozgałęzień CET, instrukcje ENDBRANCH są interpretowane jako NOP i nie mają żadnego efektu.

Microsoft Control Flow Guard

Funkcja Control Flow Guard (CFG) została po raz pierwszy wydana dla systemu Windows 8.1 Update 3 (KB3000850) w listopadzie 2014 r. Deweloperzy mogą dodawać CFG do swoich programów, dodając flagę konsolidatora /guard:cf przed połączeniem programu w programie Visual Studio 2015 lub nowszym.

Począwszy od aktualizacji Windows 10 Creators Update (Windows 10 wersja 1703), jądro systemu Windows jest kompilowane z CFG. Jądro systemu Windows używa funkcji Hyper-V , aby uniemożliwić złośliwemu kodowi jądra nadpisanie bitmapy CFG.

CFG działa poprzez tworzenie mapy bitowej dla każdego procesu, gdzie ustawiony bit wskazuje, że adres jest prawidłowym miejscem docelowym. Przed wykonaniem każdego wywołania funkcji pośredniej aplikacja sprawdza, czy adres docelowy znajduje się w bitmapie. Jeśli adres docelowy nie znajduje się w mapie bitowej, program kończy działanie. Utrudnia to atakującemu wykorzystanie opcji użycia po zwolnieniu przez zastąpienie zawartości obiektu, a następnie użycie pośredniego wywołania funkcji w celu wykonania ładunku.

Szczegóły dotyczące wdrożenia

Dla wszystkich chronionych wywołań funkcji pośrednich wywoływana jest funkcja _guard_check_ill , która wykonuje następujące kroki:

  1. Konwertuj adres docelowy na przesunięcie i numer bitowy w mapie bitowej.
    1. Najwyższe 3 bajty to przesunięcie bajtów w mapie bitowej
    2. Przesunięcie bitowe jest wartością 5-bitową. Pierwsze cztery bity to najmniej znaczące bity adresu od czwartego do ósmego.
    3. Piąty bit przesunięcia bitowego jest ustawiany na 0, jeśli adres docelowy jest wyrównany z 0x10 (ostatnie cztery bity to 0), a 1, jeśli tak nie jest.
  2. Sprawdź wartość adresu celu w mapie bitowej
    1. Jeśli adres docelowy znajduje się w mapie bitowej, wróć bez błędu.
    2. Jeśli adres docelowy nie znajduje się w mapie bitowej, zakończ program.

Techniki omijania

Istnieje kilka ogólnych technik omijania CFG:

  • Ustaw miejsce docelowe na kod znajdujący się w module innym niż CFG załadowanym w tym samym procesie.
  • Znajdź połączenie pośrednie, które nie było chronione przez CFG (CALL lub JMP).
  • Użyj wywołania funkcji z inną liczbą argumentów niż wywołanie jest przeznaczone, powodując niewspółosiowość stosu i wykonanie kodu po zwróceniu funkcji (załatane w systemie Windows 10).
  • Użyj wywołania funkcji z taką samą liczbą argumentów, ale jeden z przekazanych wskaźników jest traktowany jako obiekt i zapisuje przesunięcie oparte na wskaźniku, co pozwala na nadpisanie adresu zwrotnego.
  • Zastąp wywołanie funkcji używane przez CFG do sprawdzania poprawności adresu (poprawione w marcu 2015 r.)
  • Ustaw bitmapę CFG na wszystkie 1, zezwalając na wszystkie pośrednie wywołania funkcji
  • Użyj prymitywu kontrolowanego zapisu, aby zastąpić adres na stosie (ponieważ stos nie jest chroniony przez CFG)

Microsoft eXtended Flow Guard

eXtended Flow Guard (XFG) nie został jeszcze oficjalnie wydany, ale jest dostępny w podglądzie Windows Insider i został publicznie zaprezentowany na Bluehat Shanghai w 2019 roku.

XFG rozszerza CFG, sprawdzając poprawność sygnatur wywołań funkcji, aby zapewnić, że pośrednie wywołania funkcji dotyczą tylko podzbioru funkcji o tej samej sygnaturze. Walidacja sygnatury wywołania funkcji jest realizowana poprzez dodanie instrukcji zapisania skrótu funkcji docelowej w rejestrze r10 bezpośrednio przed wywołaniem pośrednim i zapisanie obliczonego skrótu funkcji w pamięci bezpośrednio przed kodem adresu docelowego. Gdy wykonywane jest wywołanie pośrednie, funkcja sprawdzania poprawności XFG porównuje wartość w r10 z przechowywanym hashem funkcji docelowej.

Zobacz też