Przyjrzyjmy się przykładowi pozyskiwania i wyodrębniania zbiorczej aktualizacji dla systemu Windows 10. Kiedy spojrzymy na poprzednią listę luk CVE z lipca 2021 r., zobaczymy, że CVE-2021-34527 mówi: „Luka umożliwiająca zdalne wykonanie kodu w buforze wydruku systemu Windows”. Jest to luka o nazwie „PrintNightmare”, jak można zobaczyć w ogłoszeniu firmy Microsoft pod adresem https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34527. W okresie od czerwca 2021 r. do sierpnia 2021 r. i później wydano różne poprawki. Na potrzeby tego przewodnika pobierzemy zbiorczą aktualizację z czerwca 2021 r. i lipca 2021 r. dla systemu Windows 10 21H1 x64. Naszym celem jest zlokalizowanie podatnego i załatanego pliku powiązanego z PrintNightmare i uzyskanie wstępnych informacji o tym, jak został on naprawiony. Najpierw musimy przejść do https://www.catalog.update.microsoft.com/Home.aspx i wprowadzić kryteria wyszukiwania 2021-06 21H1 x64 cumulative. Po wykonaniu tej czynności otrzymamy następujące wyniki:
Pobierzemy plik „2021-06 Cumulative Update for Windows 10 Version 21H1 for x64-based Systems (KB5004476)”. Następnie zmienimy kryteria wyszukiwania na 2021-07 21H1 x64 cumulative. Wyniki są wyświetlane dalej.
Pobierzemy plik „2021-07 Cumulative Update for Windows 10 Version 21H1 for x64-based Systems (KB5004237)”. Mamy teraz obie zbiorcze aktualizacje, które powinny zawierać pliki potrzebne do sprawdzenia CVE-2021-34527, ale muszą zostać wyodrębnione. Poprawki można wyodrębnić ręcznie za pomocą narzędzia expand firmy Microsoft, dołączonego do większości wersji systemu Windows. Narzędzie to rozszerza pliki z formatu skompresowanego, takiego jak plik cabinet lub pakiet Microsoft Standalone Update (MSU). Gdy argument -F: jest używany do określenia pliku, obsługiwane są symbole wieloznaczne ze znakiem *. Polecenie wyglądałoby mniej więcej tak expand.exe -F:* <plik do wyodrębnienia> <miejsce docelowe>. Gdy uruchamiasz to polecenie w odniesieniu do pobranej zbiorczej aktualizacji, szybko wyodrębniany jest plik Patch Storage File (PSF) z rozszerzeniem .cab. Aby wyodrębnić zawartość, należy zastosować to samo polecenie expand do tego pliku. Wykonanie tego zajmie trochę czasu (prawdopodobnie ponad 10 minut), ponieważ zazwyczaj są dziesiątki tysięcy folderów i plików. Dla zwięzłości nie będziemy zagłębiać się w powiązaną wewnętrzną strukturę i hierarchię związaną z wnętrzem pliku poprawki, z wyjątkiem tych, które są niezbędne do szybkiego przejścia do różnicowania poprawek. Aby przyspieszyć proces, użyjemy narzędzia PatchExtract Grega Linaresa, które wykorzystuje narzędzie expand. Zaktualizowana wersja Jaime Geigera jest wymieniona w sekcji „Do dalszej lektury” i jest wersją używaną w tym rozdziale. Narzędzie PatchExtract to skrypt programu PowerShell, który zarówno wyodrębnia zawartość pliku poprawki, jak i starannie organizuje pliki w różnych folderach. Aby użyć tego narzędzia, dobrym pomysłem jest utworzenie folderu docelowego, w którym chcesz umieścić wyodrębnione pliki. Na nasze potrzeby nazwiemy jeden folder „2021-06”, a drugi folder „2021-07”. Wypakujemy zawartość aktualizacji z czerwca 2021 r. do folderu „2021-06”, a zawartość aktualizacji z lipca 2021 r. do folderu „2021-07”. Po skopiowaniu pliku zbiorczej aktualizacji .msu z czerwca 2021 r. do folderu „2021-06” uruchamiamy następujące polecenie (wpisane w jednym wierszu) za pomocą sesji PowerShell ISE:
Po wykonaniu tego polecenia, wyodrębnienie plików zajęło około 20 minut. Pojawiło się również kilka komunikatów programu PowerShell o już istniejących nazwach, ale nic nie przeszkodziło w pełnym wyodrębnieniu poprawki. Po zakończeniu pozostały nam różne foldery, w tym JUNK, MSIL, PATCH, WOW64, x64 i x86. Folder JUNK zawiera pliki, którymi nie jesteśmy zainteresowani, takie jak pliki manifestu i pliki katalogu zabezpieczeń. Folder PATCH zawiera większe zagnieżdżone pliki cabinet, które właśnie wyodrębniliśmy. Foldery MSIL, WOW64, x64 i x86 zawierają większość danych platformy i plików poprawek, którymi jesteśmy zainteresowani.
Wewnątrz folderu x64 znajduje się ponad 2900 podfolderów, wszystkie o różnych opisowych nazwach, jak widać tutaj:
W każdym z tych folderów znajdują się zazwyczaj dwa podfoldery, zwane „f” i „r”, które oznaczają odpowiednio forward i reverse. Inna nazwa podfolderu, na którą możesz się natknąć, to „n”, co oznacza null. Te foldery zawierają pliki poprawek delta. Folder „r” zawiera pliki różnicowe odwrotne, folder „f” zawiera pliki różnicowe do przodu, a folder „n” zawiera nowe pliki do dodania. Kiedyś poprawka obejmowała cały plik do zastąpienia, taki jak DLL lub sterownik. Microsoft przeszedł na format delta, w którym plik różnicowy odwrotny przenosi zaktualizowany plik, po zainstalowaniu, z powrotem do wersji Release To Manufacturing (RTM), a plik różnicowy do przodu przenosi plik z RTM do miejsca, w którym musi się znaleźć dla bieżącej aktualizacji.2 Jeśli nowy plik zostanie dodany do systemu w Patch Tuesday, za pośrednictwem folderu null, można go uznać za wersję RTM. Po załataniu tego pliku podczas kolejnej aktualizacji Patch Tuesday można zastosować plik różnicowy do przodu, aby stał się aktualny. Ta aktualizacja będzie również zawierać plik różnicowy odwrotny, który można zastosować, aby przywrócić plik do wersji RTM, tak aby można było zastosować przyszły różnicowy plik do przodu, aby nadal był aktualny. Jak wspomniano, kiedyś poprawki firmy Microsoft obejmowały całe pliki, aby zastąpić te, które są łatane; jednak jeśli przyjrzysz się plikom poprawek w folderach f i r, szybko zauważysz, że rozmiar pliku rzekomych bibliotek DLL lub sterowników jest o wiele za mały, aby stanowić cały plik. Kilka lat temu firma Microsoft stworzyła zestaw interfejsów API delta poprawek. Obecnym interfejsem API jest interfejs API MSDELTA.3 Zawiera on zestaw funkcji do wykonywania działań, takich jak stosowanie delty poprawek. Aime Geiger stworzył skrypt o nazwie „delta_patch.py”, aby wykorzystać interfejs API w celu zastosowania delt odwrotnych i do przodu, których wkrótce użyjemy. Pliki poprawek delta zawierają 4-bajtową sumę kontrolną CRC32 na początku pliku, po której następuje magiczna liczba PA30. Zanim przejdziemy do stosowania poprawek delta, musimy zidentyfikować plik powiązany z poprawką, która nas interesuje. CVE-2021-34527 jest powiązany z luką w zabezpieczeniach „PrintNightmare”. Aby ustalić, które pliki chcemy różnicować, musimy dowiedzieć się nieco więcej o usługach buforowania w systemie Windows. Spójrz na poniższy obraz firmy Microsoft, który pokazuje zarówno lokalne, jak i zdalne komponenty dostawcy drukarek:
Na obrazach możemy zobaczyć kilku kandydatów do różnic, w tym winspool.drv, spoolsv.exe, spools.dll i localspl.dll. Luka związana z PrintNightmare wskazywała na potencjalne zdalne wykonanie kodu (RCE). Na obrazie po prawej stronie możemy zobaczyć wywołanie RPC do spoolsv.exe. W naszej wstępnej analizie ustalono, że spoolsv.exe, winspool.drv i localspl.dll są najciekawszymi celami. Zaczniemy od analizy spoolsv.exe. Naszym następnym krokiem jest zastosowanie poprawek delta dla aktualizacji z czerwca 2021 r. i lipca 2021 r. Musimy zidentyfikować kopię spoolsv.exe z naszego folderu WinSxS systemu Windows 10, zastosować powiązaną odwrotną deltę, a następnie zastosować do przodu deltę dla każdego z dwóch miesięcy. WinSxS to technologia montażu równoległego systemu Windows. Krótko mówiąc, jest to sposób, w jaki system Windows może zarządzać różnymi wersjami bibliotek DLL i innymi typami plików. System Windows potrzebuje sposobu na zastąpienie zaktualizowanych plików, a także sposobu na powrót do starszych wersji, jeśli aktualizacja zostanie odinstalowana. Duża liczba bibliotek DLL i plików systemowych może być skomplikowana w zarządzaniu. Przejrzymy folder WinSxS, aby znaleźć kopię pliku spoolsv.exe i powiązaną z nim odwrotną poprawkę delta, aby przywrócić ją do wersji RTM. Przyjrzyj się następującemu poleceniu programu PowerShell i powiązanym wynikom:
Możemy zobaczyć plik spoolsv.exe z maja 2021 r., a także folder r i folder f, który zawiera pliki poprawki delta. Utworzymy folder spoolsv w naszym folderze C:\grayhat\Chapter 18\, a następnie skopiujemy cały plik spoolsv.exe wraz z folderem r i jego zawartością. Pozwoli nam to zastosować poprawkę odwrotnej delty, a następnie użyć poprawki do przodu delty z aktualizacji z czerwca 2021 r. i lipca 2021 r. do pliku, używając narzędzia delta_patch.py.
Jak widać, poprawki delta w przód i w tył zostały zastosowane pomyślnie. Mamy teraz wersje pliku spoolsv.exe zarówno na czerwiec, jak i lipiec. Użyjemy wtyczki BinDiff dla IDA Pro, aby porównać różnice między dwiema wersjami. Aby to zrobić, będziemy musieli wykonać następujące czynności:
- Sprawić, aby IDA wykonało swoją automatyczną analizę obu plików.
- Załaduj wersję z czerwca do IDA i naciśnij CTRL-6, aby wyświetlić menu BinDiff.
• Wykonaj diff i przeanalizuj wyniki.
W wynikach możemy zobaczyć zmiany w pięciu funkcjach, usunięcie czterech funkcji i dwóch importów oraz dodanie dwóch nowych funkcji w poprawionej wersji spoolsv.exe, jak widać na karcie Secondary Unmatched. Nazwa funkcji YRestrictDriverInstallationToAdministrators brzmi jak oczywista funkcja interesująca. Wykonajmy wizualną różnicę funkcji RpcAddPrinterDriverEx.
Możemy zobaczyć dużą liczbę różnic między wersjami funkcji. Przybliżając obszar w kierunku środka u góry, widzimy następujące:
Po stronie podstawowej (niezałatanej) znajduje się wywołanie RunningAsLUA, które zostało usunięte ze strony wtórnej (załatanej). W wersji załatanej znajduje się nowe wywołanie funkcji YRestrictDriverInstallationToAdministrators. Podczas badania odwołań krzyżowych do tej nowej funkcji widzimy dwa wywołania. Jedno wywołanie pochodzi z RpcAddPrinterDriver, a drugie z RpcAddPrinterDriverEx. Obie te funkcje zostały zidentyfikowane jako mające zmiany. Poniższa ilustracja pokazuje blok kodu w RpcAddPrinterDriverEx, w którym znajduje się wywołanie YIsElevationRequired i YImpersonateClient.
Przyglądając się każdej z tych funkcji, widzimy, że uzyskiwany jest dostęp do unikalnego klucza rejestru, jak pokazano poniżej:
Funkcja YIsElevationRequired sprawdza klucz o nazwie NoWarning-NoElevationOnInstall, a funkcja RestrictDriverInstallationToAdministrators sprawdza klucz o nazwie RestrictDriverInstallationToAdministrators. Wynik z funkcji YIsElevationRequired jest rejestrowany w r14, a wynik z funkcji RestrictDriverInstallationToAdministrators jest rejestrowany w r15. Przyjrzyjmy się pseudokodowi funkcji RpcAddPrinterDriverEx, aby lepiej zrozumieć przepływ. Używamy dekompilatora Hex-Rays, ale możesz również użyć narzędzia Ghidra lub innego.
Linia 4 pokazuje nam, że v6 reprezentuje r14, który będzie zawierał zwrot z YIsElevationRequired w linii 21. Linia 5 pokazuje nam, że v7 reprezentuje r15, który będzie zawierał zwrot z YRestrictDriverInstallationToAdministrators w linii 22. Linia 26 ustawia v10 (esi), jeśli użytkownik jest administratorem. Warunek w linii 45 mówi, że jeśli v6 jest ustawione (wymagane podniesienie), a nie v10 (nie jest administratorem), to my i zmienna a3 z 0x8000, co jest 10000000000000000 w systemie binarnym. To anuluje flagę na 15. pozycji bitu a3 (edi) na 0. Warunek w linii 48 mówi, że jeśli v7 nie jest ustawiona (instalacja nie jest ograniczona do administratorów) lub v10 jest ustawiona (jest administratorem), wywołaj funkcję YAddPrinterDriverEx, przekazując a3 (flagi kontrolowane przez użytkownika) jako jeden z argumentów. Jeśli sobie przypominasz, obraz od Microsoft dla komponentów dostawcy drukarki wysokiego poziomu pokazuje wywołanie RPC do zdalnego procesu spoolsv.exe. Z kolei wykonywanie przechodzi przez localspl.dll przed przejściem do trybu jądra w celu komunikacji z rzeczywistą drukarką. Patrząc na tabelę adresów eksportu (EAT) localspl.dll, możemy zobaczyć funkcję SplAddPrinterDriverEx. Została ona zdekompilowana, jak pokazano tutaj:
Spójrz na wiersze 28–33. Zmienna a4 jest taka sama jak zmienna a3 z poprzedniego pseudokodu zrzutu z RpcAddPrinterDriverEx, zawierająca flagi. Możemy kontrolować tę wartość, która w niezałatanej wersji spoolsv.exe nie ma kontroli powiązanych kluczy rejestru (NoWarningNoElevationOnInstall i RestrictDriverInstallationToAdministrators). Możemy skutecznie ominąć wywołanie ValidateObjectAccess i przejść bezpośrednio do InternalAddPrinterDriverEx. Wiersz 28 ustawia v12 na 0. Wiersz 29 mówi, że jeśli 15. pozycja bitu w a4 nie jest ustawiona, to ustaw v12 tak, aby było równe a7, co prawdopodobnie zmienia wartość v12 z 0. W wierszu 31, jeśli v12 jest ustawione (nie zero), to wywołaj ValidateObjectAccess i sprawdź, czy prawo sedebugprivilege jest ustawione. Jeśli uda nam się sprawić, że 15. pozycja bitu w a4 będzie włączona, to w wierszu 29 nie przejdziemy do bloku i zamiast tego wywołamy InternalAddPrinterDriverEx. To skutecznie pozwala atakującemu ominąć sprawdzanie i zainstalować sterownik, umożliwiając wykonanie kodu jako użytkownik NT AUTHORITY\SYSTEM. W momencie pisania tego tekstu nadal pojawiały się dodatkowe ustalenia i poprawki; jednak jest to jeden z głównych błędów, które można wykorzystać.