100887399

Page 1


SZCZEGÓŁOWY SPIS TREŚCI

SŁOWO WSTĘPNE XVII

PRZEDMO WA X XI

PODZIĘKOWANIA X XIII

WSTĘP

Czym jest analiza binarna i dlaczego jej potrzebujesz?

Dlaczego analiza binarna stanowi wyzwanie?

Kto powinien przeczytać tę książkę?

Co jest w tej książce?

Jak korzystać z tej książki?

Model programowy procesora

BINARNE

.1 .1

Faza preprocesowania

pliku obiektowego

3 2 Badamy kompletny wykonywalny plik binarny

2

2 .1 .6 . Pole e_ehsize

2 .1 .8 . Pole e_shstrndx

2 .2 .1 . Pole sh_name

.2 .3 . Pole sh_flags

2 .2 .5 . Pole sh_link

2 .2 .6 . Pole sh_info

2 .2 .7 . Pole sh_addralign

2 .2 .8 . Pole sh_entsize

2 .3 . Sekcje

2 .3 .1 . Sekcje .init i .fini

2 3 2 Sekcja

2 .3 .3 . Sekcje .bss, .data i .rodata

sh_size

2 .4

2 3 4 Wiązanie leniwe symboli i sekcje plt, got oraz got plt

2 .3 .5 . Sekcje .rel .* i .rela

2 3 6 Sekcja dynamic

2 .3 .7 . Sekcje .init_array i .fini_array

2 3 8 Sekcje shstr tab, symtab, str ttab, dynsym i dynstr

Nagłówki programów

.4 .2

2 4 3 Pola p_offset, p_vaddr, p_paddr, p_filesz i p_memsz

2 .4 .4 . Pole p_align

2 .5 . Podsumowanie

3 2 Sygnatura, nagłówek i

3 .2 .3 . Nagłówek opcjonalny pliku PE

3 .3 . Tablica nagłówków sekcji

3 .4 . Sekcje

3 .4 .1 . Sekcje .edata i .idata

3 .4 .2 . Wyrównywanie w sekcjach kodu PE

3 .5 . Podsumowanie

4 3 Implementacja loadera plików binarnych

4 .3 .1 . Inicjalizacja libbfd i otwieranie pliku binarnego

4 3 2 Parsowanie podstawowych właściwości pliku binarnego

4 .3 .3 . Ładowanie symboli .

4 3 4 Ładowanie sekcji

4 .4 . Testowanie loadera binarnego

4 5 Podsumowanie

CZĘŚĆ II PODSTAWY ANALIZY BINARNEJ

5 PODSTAWOWA ANALIZA BINARNA

5 1 Rozwiązywanie kryzysów tożsamości przy użyciu file

5 .2 . Uż ycie ldd do badania zależności

5 3 Oglądanie zawartości pliku w xxd

5 .4 . Parsowanie ekstrahowanego ELF za pomocą readelf

5 5 Parsowanie symboli za pomocą nm

5 .6 . Szukanie wskazówek za pomocą strings

5 .7 . Śledzenie wywołań systemowych i bibliotecznych za pomocą strace i ltrace

5 .8 . Badanie zachowań na poziomie instrukcji przy użyciu objdump

5 .9 . Zrzut dynamicznego bufora łańcucha za pomocą gdb

5 .10 . Podsumowanie

6 .1 .1 . Deasemblacja liniowa

6 .1 .2 . Deasemblacja rekurencyjna

6 .2 . Deasemblacja dynamiczna

6 .2 .1 . Przykład: śledzenie wykonywania pliku binarnego za pomocą gdb

6 .2 .2 . Strategie pokrycia kodu

6 .3 . Struktur yzacja deasemblowanego kodu i danych

6 .3 .1 . Struktur yzowanie kodu

6 .3 .2 . Struktur yzacja danych

6 3 3 Dekompilacja

6 .3 .4 . Reprezentacje pośrednie

6 4 Podstawowe metody analizy

6 .4 .1 . Własności analiz y binarnej

6 4 2 Analiza przepływu sterowania

6 .4 .3 . Analiza przepływu danych

7 2 Modyfikacja zachowania biblioteki współdzielonej przy użyciu LD_PRELOAD

7 .2 .1 . Zagrożenie przepełnieniem sterty .

7 2 2 Wykrywanie przepełnienia sterty

7 .3 . Wstrzykiwanie sekcji kodu .

7 3 1 Wstrzykiwanie sekcji ELF: przegląd wysokiego poziomu

7 .3 .2 . Uż ycie elfinject do wstrzykiwania sekcji ELF

7 4 Wywołanie wstrzykniętego kodu

7 .4 .1 . Modyfik acja adresu startowego

7 4 2 Przejęcie konstruktorów i destruktorów

7 .4 .3 . Przejęcie wpisów w GOT

7 .4 .4 . Przejęcie wpisów PLT

7 .4 .5 . Przekierowanie wywołań bezpośrednich i pośrednich

7 .5 . Podsumowanie

8 1 Dlaczego pisać własny program do deasemblacji

8 .1 .1 . Sprawa dla dostosowania deasemblacji: zaciemniony kod

8 .1 .2 . Inne powody, by napisać dostosowany deasembler

8 .2 . Wprowadzenie do Capstone’a

8 .2 .1 . Instalacja Capstone’a

8 .2 .2 . Deasemblacja liniowa z Capstone'a

8 .2 .3 . Eksploracja API C Capstone’a

8 2 4 Deasemblacja rekurencyjna z Capstone

8 .3 . Implementacja skanera gadżetów ROP

8 3 1 Wprowadzenie do programowania zorientowanego na powrót (ROP)

8 .3 .2 . Wykrywanie gadżetów ROP

8 4

9 .1 . Czym jest instrumentacja binarna?

9 1 1 API instrumentacji binarnej

9 .1 .2 . Statyczna kontra dynamiczna instrumentacja binarna

9 2 Statyczna instrumentacja binarna

.2 .1 . Podejście int 3

9 2 2 Podejście z uż yciem trampolin

9 .3 . Dynamiczna instrumentacja binarna

9 3 1 Architektura systemu DBI

9 .3 .2 . Wprowadzenie do Pin

9 4 Profilowanie za pomocą Pin

9 .4 .1 . Struktur y danych profilera i kod instalacyjny

9 .4 .2 . Parsowanie symboli funkcji

9 .4 .3 . Instrumentacja bloków podstawowych

9 .4 .4 . Instrumentacja instrukcji przepływu sterowania

9 .4 .5 . Liczenie instrukcji, transferów sterowania i wywołań systemowych

9 .4 .6 . Testowanie profilera

9 5 Automatyczne rozpakowywanie plików binarnych z Pin

9 .5 .1 . Wprowadzenie do programów pakujących

9 5 2 Struktur y danych i kod instalacyjny programu rozpakowującego

9 .5 .3 . Instrumentowanie zapisów do pamięci

9 5 4 Instrumentowanie instrukcji przepływu sterowania

9 .5 .5 . Śledzenie zapisów do pamięci

9 5 6 Wykrywanie pierwotnego punktu wejścia i zrzucanie rozpakowanego pliku binarnego

9 5 7 Testowanie programu rozpakowującego

9 .6 Podsumowanie

10 1 Czym jest DTA?

10 .2 . DTA w trzech krokach: źródła taint, taint sinks i propagacja taint

10 2 1 Definiowanie źródeł taint

10 .2 .2 . Definiowanie taint sinks

10 2 3 Śledzenie propagacji taint

10 .3 . Uż ycie DTA do wykrycia błędu Heartbleed

10 .3 .1 . Krótki przegląd podatności na Heartbleed

10 .3 .2 . Wykrywanie Heartbleed przez taint

10 .4 . Cz ynniki w projektowaniu DTA: ziarnistość taint, kolory taint i zasady propagacji taint

10 .4 .1 . Ziarnistość taint

10 4 2 Kolory taint

10 .4 .3 . Zasady propagacji taint

10 4 4 Nadmierne i niedostateczne pokrycie taint

10 .4 .5 . Zależności sterowania

10 4 6 Shadow memor y

10 .5 . Podsumowanie

11 .1 . Wstęp do libdft

11 .1 .1 . Organizacja wewnętrzna libdft

11 .2 . Wykorzystanie DTA do wykrywania zdalnego przechwycenia sterowania

11 2 1 Sprawdzanie informacji o taint

11 .2 .2 . Źródła taint: taintowanie otrzymywanych bajtów

11 2 3 Taint sinks: sprawdzanie argumentów execve

11 .2 .4 . Wykrywanie próby przejęcia przepływu sterowania

11 3 Obejście DTA za pomocą przepływów niejawnych

11 .4 . Detektor eksfiltracji danych oparty na DTA

11 4 1 Źródła taint: śledzenie taint dla otwartych plików

11 .4 .2 . Taint sinks: monitorowanie przesyłów sieciowych w poszukiwaniu eksfiltracji danych

11 .4 .3 . Wykrywanie próby eksfiltracji danych

11 .5 . Podsumowanie

12 1 . Przegląd wykonywania symbolicznego .

12 1 1 Wykonywanie symboliczne kontra konkretne

12 .1 .2 . Rodzaje i ograniczenia wykonywania symbolicznego

12 1 3 Zwiększanie sk alowalności wykonywania symbolicznego 307 12 .2 . Rozwiązywanie ograniczeń za pomocą Z3

12 2 1 Dowodzenie osiągalności instrukcji

12 .2 .2 . Dowodzenie nieosiągalności instrukcji

12 2 3 Dowodzenie poprawności formuły 313 12 .2 .4 . Upraszczanie wyrażeń

12 2 5 Modelowanie ograniczeń dla kodu maszynowego za pomocą wektorów bitowych

12 .2 .6 . Rozwiązywanie nieprzejrzystego kodu warunkowego za pomocą wektorów bitowych

12 .3 . Podsumowanie

13 1 . Wprowadzenie do Tritona

13 2 Utrzymywanie stanu symbolicznego przy użyciu drzew składniowych (AST)

13 .3 . Slicing wsteczny z Tritonem

13 .3 .2 . Plik konfiguracji symbolicznej

13 .3 .3 . Emulowanie instrukcji

13 .3 .4 . Konfigurowanie architektury Tritona

13 .3 .5 . Obliczanie „wycinka wstecznego”

13 .4 . Wykorzystanie Tritona do zwiększenia pokrycia kodu

13 .4 .1 . Tworzenie zmiennych symbolicznych

13 4 2 Znajdowanie modelu dla nowej ścieżki

13 .4 .3 . Testowanie narzędzia pokrycia kodu

13 5 Automatyczna eksploitacja podatności

13 .5 .1 . Podatny program

13 5 2 Znajdowanie adresu podatnego miejsca wywołania

.5 .3

13 5 4 Uz yskiwanie dostępu do powłoki administratora

13 .6 . Podsumowanie

A .1 . Zarys programu w języku asemblera

A 1 1 Instrukcje jęz yka asemblera, dyrektywy, etykiety i komentarze

A .1 .2 . Rozdzielenie kodu od danych

A 2 1 Reprezentacja instrukcji x86 na poziomie języka asemblera

.2 .2 . Instrukcje x86 na poziomie kodu maszynowego

.2 .3 . Operandy rejestru

A 2 4 Operandy pamięci

A .2 .5 . Operandy bezpośrednie

A3 Częste instrukcje x86

A .3 .1 . Porównywanie operandów i ustawianie flag stanu

A 3 2 Implementacja wywołań systemowych

A .3 .3 . Implementacja skoków warunkowych

A 3 4 Ładowanie adresów pamięci

A .4 . Częste konstrukcje kodu w języku asemblera

A 4 1 Stos

A .4 .2 . Wywołania funkcji i ramki funkcji

A .4 .3 . Instrukcje warunkowe

A .4 .4 . Pętle

B 1 Wymagane pliki nagłówkowe

B .2 . Struktur y danych używane w elfinject

B 3 Inicjalizacja libelf

B .4 . Uz yskiwanie nagłówka pliku wykonywalnego

B .5 . Znajdowanie segmentu PT_NOTE

B .6 . Wstrzykiwanie bajtów kodu .

B .7 . Dopasowanie adresu ładowania dla wstrzykiwanej sekcji

B .8 . Nadpisanie nagłówk a sekcji .note .ABI-tag

B .9 . Ustawienie nazwy dla wstrzykniętej sekcji

B .10 . Nadpisanie nagłówk a programu PT_NOTE

B .11 . Modyfik acja adresu startowego

.1 . Deasemblery

C .2 . Debuggery

C .3 . Platformy deasemblerów

C .4 . Platformy analizy binarnej

D .1 . Standardy i źródła

D .2 . Ar tykuły i raporty techniczne

D .3 . Książki

INDEKS

13.3.4. Konfigurowanie architektury Tritona

Funkcja main narzędzia backward_slicing wywołuje set_triton_arch, by dostosować Tritona do zestawu instrukcji pliku binarnego i uzyskać nazwę rejestru wskaźnika instrukcji używanego przez daną architekturę. Listing 13.3 pokazuje sposób implementacji set_triton_arch.

staticint

set_triton_arch(Binary&bin,triton::API&api,triton::arch::registers_e&ip) { if(bin.arch!=Binary::BinaryArch::ARCH_X86){ fprintf(stderr,"Unsupportedarchitecture\n"); return-1; }

if(bin.bits==32){

api.setArchitecture(triton::arch::ARCH_X86); ip= triton::arch::ID_REG_EIP; }elseif(bin.bits==64){

api.setArchitecture(triton::arch::ARCH_X86_64); ip=triton::arch::ID_REG_RIP; }else{ fprintf(stderr,"Unsupportedbitwidthforx86:%ubits\n",bin.bits); return-1; } return0; }

Listing 13.3: backward_slicing.cc (ciąg dalszy)

Funkcja ta przyjmuje trzy parametry: referencję do obiektu Binary, zwracanego przez loader binarny, referencję do API Tritona i referencję do triton::arch::registers_e, w którym przechowuje nazwę rejestru wskaźnika instrukcji. Jeżeli wszystko w porządku, set_triton_arch zwraca 0, jeżeli wystąpił błąd, zwraca -1.

Najpierw set_triton_arch upewnia się, że ma do czynienia z plikiem binarnym na x86 (32- albo 64-bitowym)  . Jeżeli nie, zwraca błąd, ponieważ na razie Triton nie obsługuje innych architektur, niż x86.

Jeżeli nie ma błędu, set_triton_arch sprawdza długość słowa dla pliku . Jeżeli plik używa 32 bitów, konfiguruje Tritona w trybie 32 bity na x86 (triton::arch::ARCH_X86)  i ustawia ID_REG_EIP jako nazwę rejestru wskaźnika instrukcji . Podobnie, jeżeli jest to plik 64-bitowy, ustawia architekturę w Tritonie na triton::arch::aRCH_X86_64  i ustawia ID_REG_RIP jako wskaźnik instrukcji . Aby skonfigurować architekturę Tritona, używasz funkcji api.setArchitecture, która przyjmuje typ architektury jako jedyny parametr.

13.3.5. Obliczanie „wycinka wstecznego”

Aby obliczyć i wydrukować faktyczny „wycinek”, backward_slice wywołuje funkcję print_slice, kiedy emulacja natrafi na adres, od którego należy wycinać. Możesz obejrzeć implementację print_slice na listingu 13.4.

staticvoid print_slice(triton::API&api,Section *sec,uint64_tslice_addr, triton::arch::registers_ereg,constchar *regname) { triton::engines::symbolic::SymbolicExpression *regExpr; std::map<triton::usize,triton::engines::symbolic::SymbolicExpression*> slice; charmnemonic[32],operands[200];

regExpr=api.getSymbolicRegisters()[reg]; slice=api.sliceExpressions(regExpr);

for(auto&kv:slice){

printf("%s\n",kv.second->getComment().c_str()); }

disasm_one(sec,slice_addr,mnemonic,operands); std::stringtarget=mnemonic;target+="";target+=operands;

printf("(slicefor%s@0x%jx:%s)\n",regname,slice_addr,target.c_str()); }

Listing 13.4: backward_slicing.cc (ciąg dalszy)

Jak pamiętasz, „wycinki” są obliczane względem konkretnego rejestru, który jest określony przez parametr reg. Aby obliczyć „wycinek”, potrzebujesz wyrażenia symbolicznego skojarzonego z tym rejestrem tuż po emulowaniu instrukcji pod adresem „wycinka”. Aby uzyskać to wyrażenie, print_slice wywołuje api.getSymbolicRegisters, która zwraca mapę wszystkich rejestrów na związane z nimi wyrażenia symboliczne, a następnie indeksuje tę mapę, by otrzymać wyrażenie związane z reg  . Z kolei uzyskuje „wycinek” wszystkich wyrażeń symbolicznych, które wnoszą wkład do wyrażenia reg , za pomocą api.sliceExpressions  , która zwraca „wycinek” w postaci std::map, mapującej całkowitoliczbowe identyfikatory wyrażeń na obiekty triton::engines::symbolic::SymbolicExpression*. Masz teraz „wycinek” z wyrażeniami symbolicznymi, ale tak naprawdę potrzebujesz „wycinka” instrukcji asemblacji na x86. To właśnie jest celem komentarzy do wyrażeń symbolicznych, które wiążą każde wyrażenie z łańcuchami skrótowych nazw asemblacji i operandów dla instrukcji, która dała początek temu wyrażeniu. Stąd, by wypisać „wycinek”, print_slice po prostu wykonuje pętlę nad „wycinkiem” z wyrażeniami symbolicznymi, uzyskuje komentarze do nich za pomocą getComment i drukuje je na ekranie . Dla kompletności print_slice deasembluje też instrukcję, od której obliczasz ten „wycinek” i również ją drukuje  Możesz wypróbować program backward_slice na VM, uruchamiając go tak, jak pokazano na listingu 13.5.

Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.