Czy pracujesz w firmie z branży cyberbezpieczeństwa? Chcesz zobaczyć, jak Twoja firma jest reklamowana na HackTricks? lub chcesz mieć dostęp do najnowszej wersji PEASS lub pobrać HackTricks w formacie PDF? Sprawdź PLANY SUBSKRYPCYJNE!
Poprzedni program ma 9 nagłówków programu, następnie mapowanie segmentów wskazuje, w którym nagłówku programu (od 00 do 08) znajduje się każda sekcja.
PHDR - Nagłówek programu
Zawiera tabele nagłówków programów oraz same metadane.
INTERP
Wskazuje ścieżkę do ładowacza, który ma załadować binarny plik do pamięci.
LOAD
Te nagłówki służą do wskazania, jak załadować binarny plik do pamięci.
Każdy nagłówek LOAD wskazuje obszar pamięci (rozmiar, uprawnienia i wyrównanie) oraz wskazuje bajty ELF do skopiowania tam.
Na przykład drugi ma rozmiar 0x1190, powinien znajdować się pod adresem 0x1fc48 z uprawnieniami do odczytu i zapisu oraz zostanie wypełniony wartościami 0x528 od przesunięcia 0xfc48 (nie wypełnia całej zarezerwowanej przestrzeni). Ta pamięć będzie zawierać sekcje .init_array .fini_array .dynamic .got .data .bss.
DYNAMIC
Ten nagłówek pomaga łączyć programy z ich zależnościami bibliotecznymi i stosować relokacje. Sprawdź sekcję .dynamic.
NOTE
Przechowuje informacje metadanych dostawcy o binarnym pliku.
GNU_EH_FRAME
Definiuje lokalizację tabel odwijania stosu, używanych przez debuggery i funkcje obsługi wyjątków C++.
GNU_STACK
Zawiera konfigurację obrony przed wykonywaniem kodu ze stosu. Jeśli jest włączona, binarny plik nie będzie mógł wykonywać kodu ze stosu.
GNU_RELRO
Wskazuje konfigurację RELRO (Relocation Read-Only) binarnego pliku. Ta ochrona oznacza jako tylko do odczytu pewne sekcje pamięci (takie jak GOT lub tabele init i fini) po załadowaniu programu i przed jego uruchomieniem.
W poprzednim przykładzie kopiowanych jest 0x3b8 bajtów do 0x1fc48 jako tylko do odczytu, wpływając na sekcje .init_array .fini_array .dynamic .got .data .bss.
Zauważ, że RELRO może być częściowy lub pełny, wersja częściowa nie chroni sekcji .plt.got, która jest używana do opóźnionego wiązania i wymaga, aby ta przestrzeń pamięci miała uprawnienia do zapisu, aby zapisać adresy bibliotek za pierwszym razem, gdy ich lokalizacja jest wyszukiwana.
TLS
Definiuje tabelę wpisów TLS, która przechowuje informacje o zmiennych lokalnych wątku.
Nagłówki sekcji
Nagłówki sekcji dają bardziej szczegółowy widok binarnego pliku ELF.
Tabela ciągów: Zawiera wszystkie ciągi potrzebne przez plik ELF (ale nie te, które faktycznie są używane przez program). Na przykład zawiera nazwy sekcji takie jak .text lub .data. Jeśli sekcja .text znajduje się na przesunięciu 45 w tabeli ciągów, użyje liczby 45 w polu nazwa.
Aby znaleźć, gdzie znajduje się tabela ciągów, ELF zawiera wskaźnik do tabeli ciągów.
Tabela symboli: Zawiera informacje o symbolach, takie jak nazwa (przesunięcie w tabeli ciągów), adres, rozmiar i więcej metadanych o symbolu.
Główne sekcje
.text: Instrukcje programu do wykonania.
.data: Zmienne globalne zdefiniowane wartością w programie.
.bss: Zmienne globalne pozostawione niezainicjowane (lub zainicjowane na zero). Zmienne tutaj są automatycznie inicjowane na zero, zapobiegając tym samym dodawaniu niepotrzebnych zer do pliku binarnego.
.rodata: Stałe zmienne globalne (sekcja tylko do odczytu).
.tdata i .tbss: Podobne do .data i .bss, gdy używane są zmienne lokalne wątku (__thread_local w C++ lub __thread w).
.dynamic: Patrz poniżej.
Symbole
Symbole to nazwane lokalizacje w programie, które mogą być funkcją, globalnym obiektem danych, zmiennymi lokalnymi wątku...
readelf -s lnstat
Symbol table '.dynsym' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000001088 0 SECTION LOCAL DEFAULT 12 .init
2: 0000000000020000 0 SECTION LOCAL DEFAULT 23 .data
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtok@GLIBC_2.17 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND s[...]@GLIBC_2.17 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.17 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.17 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.17 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.17 (2)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
11: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.17 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
[...]
Każdy wpis symbolu zawiera:
Nazwę
Atrybuty wiązania (słabe, lokalne lub globalne): Symbol lokalny może być dostępny tylko przez program, podczas gdy symbole globalne są udostępniane poza programem. Obiekt słaby to na przykład funkcja, która może zostać zastąpiona inną.
Typ: NOTYPE (brak określonego typu), OBJECT (zmienna danych globalnych), FUNC (funkcja), SECTION (sekcja), FILE (plik źródłowy dla debuggerów), TLS (zmienna lokalna wątku), GNU_IFUNC (funkcja pośrednia do relokacji)
Katalog NEEDED wskazuje, że program musi załadować wymienioną bibliotekę, aby kontynuować. Katalog NEEDED zostaje uzupełniony, gdy współdzielona biblioteka jest w pełni operacyjna i gotowa do użycia.
Przesunięcia
Ładowacz musi również przesunąć zależności po ich załadowaniu. Te przesunięcia są wskazane w tabeli przesunięć w formatach REL lub RELA, a liczba przesunięć jest podana w sekcjach dynamicznych RELSZ lub RELASZ.
Jeśli program jest załadowany w innym miejscu niż preferowany adres (zwykle 0x400000) z powodu zajęcia adresu lub z powodu ASLR lub innych powodów, statyczne przemieszczenie poprawia wskaźniki, które miały wartości oczekujące, że binarny zostanie załadowany w preferowanym adresie.
Na przykład dowolna sekcja typu R_AARCH64_RELATIV powinna zmienić adres w przemieszczeniu plus wartość dodaną.
Dynamiczne przemieszczenia i GOT
Przemieszczenie może również odnosić się do symbolu zewnętrznego (jak funkcja zależności). Na przykład funkcja malloc z libC. Wtedy, ładowacz podczas ładowania libC pod adresem sprawdzającym, gdzie jest załadowana funkcja malloc, zapisze ten adres w tabeli GOT (Global Offset Table) (wskazanej w tabeli przemieszczeń), gdzie powinien być określony adres malloc.
Tabela łączenia procedur
Sekcja PLT pozwala na leniwe wiązanie, co oznacza, że rozwiązanie lokalizacji funkcji będzie wykonywane za pierwszym razem, gdy zostanie ona użyta.
Więc gdy program wywołuje malloc, faktycznie wywołuje odpowiadającą lokalizację malloc w PLT (malloc@plt). Za pierwszym razem, gdy jest wywoływana, rozwiązuje adres malloc i przechowuje go, więc następnym razem, gdy jest wywoływana malloc, używany jest ten adres zamiast kodu PLT.
Inicjalizacja programu
Po załadowaniu programu nadszedł czas na jego uruchomienie. Jednak pierwszy kod, który jest uruchamiany, nie zawsze jest funkcją main. Dzieje się tak na przykład w C++, gdy zmienna globalna jest obiektem klasy, ten obiekt musi być zainicjowanyprzed uruchomieniem funkcji main, jak w:
Zauważ, że te zmienne globalne znajdują się w .data lub .bss, ale w listach __CTOR_LIST__ i __DTOR_LIST__ obiekty do zainicjowania i zniszczenia są przechowywane w celu śledzenia ich.
Z kodu C można uzyskać ten sam wynik, korzystając z rozszerzeń GNU:
__attributte__((constructor)) //Add a constructor to execute before__attributte__((destructor)) //Add to the destructor list
Z perspektywy kompilatora, aby wykonać te czynności przed i po wykonaniu funkcji main, można utworzyć funkcję init i funkcję fini, które będą odwoływane w sekcji dynamicznej jako INIT i FIN oraz umieszczone w sekcjach init i fini pliku ELF.
Inną opcją, jak wspomniano, jest odwołanie do list __CTOR_LIST__ i __DTOR_LIST__** w wpisach **INIT_ARRAY** i **FINI_ARRAY** sekcji dynamicznej, a ich długość jest określana przez **INIT_ARRAYSZ** i **FINI_ARRAYSZ`. Każdy wpis to wskaźnik funkcji, który zostanie wywołany bez argumentów.
Ponadto możliwe jest posiadanie PREINIT_ARRAY z wskaźnikami, które zostaną wykonane przed wskaźnikami INIT_ARRAY.
Kolejność inicjalizacji
Program jest wczytywany do pamięci, statyczne zmienne globalne są inicjowane w sekcji .data, a niezainicjowane są zerowane w sekcji .bss.
Wszystkie zależności programu lub bibliotek są inicjowane, a następnie wykonywane jest dynamiczne łączenie.
Wykonywane są funkcje PREINIT_ARRAY.
Wykonywane są funkcje INIT_ARRAY.
Jeśli istnieje wpis INIT, zostaje on wywołany.
Jeśli to biblioteka, dlopen kończy działanie tutaj, jeśli to program, nadszedł czas na wywołanie prawdziwego punktu wejścia (funkcji main).
Pamięć lokalna wątku (TLS)
Są one definiowane za pomocą słowa kluczowego __thread_local w C++ lub rozszerzenia GNU __thread.
Każdy wątek będzie utrzymywał unikalne miejsce dla tej zmiennej, dzięki czemu tylko wątek może uzyskać dostęp do swojej zmiennej.
Gdy jest to używane, sekcje .tdata i .tbss są używane w pliku ELF. Są to odpowiedniki .data (zainicjowane) i .bss (niezainicjowane), ale dla TLS.
Każda zmienna ma wpis w nagłówku TLS określający rozmiar i przesunięcie TLS, czyli przesunięcie, które będzie używane w lokalnej przestrzeni danych wątku.
__TLS_MODULE_BASE to symbol używany do odwołania do bazowego adresu pamięci lokalnej wątku i wskazuje na obszar w pamięci zawierający wszystkie dane lokalne wątku modułu.