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.