Jaka jest opcja -fPIE dla plików wykonywalnych niezależnych od pozycji w gcc i ld?

Odpowiedzi:

103

PIE ma obsługiwać randomizację układu przestrzeni adresowej (ASLR) w plikach wykonywalnych.

Przed utworzeniem trybu PIE, pliku wykonywalnego programu nie można było umieścić pod losowym adresem w pamięci, tylko biblioteki dynamiczne z kodem niezależnym od pozycji (PIC) mogły zostać przeniesione do losowego przesunięcia. Działa bardzo podobnie do tego, co robi PIC dla bibliotek dynamicznych, różnica polega na tym, że nie jest tworzona tabela łączenia procedur (PLT), zamiast tego używana jest relokacja względem komputera.

Po włączeniu obsługi PIE w gcc / linkers, treść programu jest kompilowana i linkowana jako kod niezależny od pozycji. Dynamiczny linker wykonuje pełne przetwarzanie relokacji w module programu, podobnie jak biblioteki dynamiczne. Jakiekolwiek użycie danych globalnych jest konwertowane w celu uzyskania dostępu za pośrednictwem tabeli globalnych przesunięć (GOT) i dodawane są relokacje GOT.

PIE jest dobrze opisana w tej prezentacji OpenBSD PIE .

Zmiany funkcji są pokazane na tym slajdzie (PIE vs PIC).

x86 pic vs ciasto

Lokalne zmienne globalne i funkcje są zoptymalizowane w postaci kołowej

Zewnętrzne zmienne globalne i funkcje są takie same jak na rys

i na tym slajdzie (SROKA a linkowanie w starym stylu)

x86 pie vs no-flags (naprawione)

Lokalne zmienne i funkcje globalne są podobne do stałych

Zewnętrzne zmienne globalne i funkcje są takie same jak na rys

Należy pamiętać, że PIE może być niezgodne z -static

osgx
źródło
3
Również w Wikipedii: en.wikipedia.org/wiki/ ...
osgx
6
Dlaczego -pie i -static są kompatybilne z ARM i NIE są kompatybilne z x86? Moje pytanie SO: stackoverflow.com/questions/27082959/…
4ntoine
Tak. to naprawdę dobra odpowiedź. ASLR, co oznacza, że ​​po prostu ładuje się do pamięci losowej. ta pamięć losowa nie oznacza pamięci fizycznej. Wykorzystuje również pamięć wirtualną. jeśli używasz opcji -fno-pie i -no-pie w gcc, a następnie użyj objdump -d. następnie możesz sprawdzić, czy każdy program uruchamia się 0x800. ale jeśli używasz tylko opcji pie, to sprawdzasz, czy program startuje z niskim adresem. ten adres jest inny, gdy jest ładowany do pamięci przez program ładujący proces.
JaeIL Ryu
59

Minimalny działający przykład: GDB plik wykonywalny dwukrotnie

Dla tych, którzy chcą zobaczyć jakąś akcję, zobaczmy, jak ASLR działa na pliku wykonywalnym PIE i zmienia adresy między uruchomieniami:

main.c

main.sh

Dla kogoś z -no-piewszystkim jest nudno:

Przed rozpoczęciem wykonywania break mainustawia punkt przerwania na 0x401126.

Następnie podczas obu egzekucji runzatrzymuje się pod adresem 0x401126.

Ten z -piejednak jest znacznie bardziej interesujący:

Przed przystąpieniem do wykonania, GDB zajmuje tylko „atrapę” adres, który jest obecny w pliku wykonywalnego: 0x1139.

Jednak po uruchomieniu GDB inteligentnie zauważa, że ​​dynamiczny moduł ładujący umieścił program w innym miejscu, a pierwsza przerwa zatrzymała się na 0x5630df2d6139.

Następnie drugi przebieg również inteligentnie zauważył, że plik wykonywalny ponownie się poruszył i zakończył się przerwaniem 0x55763ab2e139.

echo 2 | sudo tee /proc/sys/kernel/randomize_va_spacezapewnia, że ​​ASLR jest włączony (ustawienie domyślne w Ubuntu 17.10): Jak mogę tymczasowo wyłączyć ASLR (randomizacja układu przestrzeni adresowej)? | Zapytaj Ubuntu .

set disable-randomization offjest potrzebne, w przeciwnym razie GDB, jak sugeruje nazwa, domyślnie wyłącza ASLR dla procesu, aby nadawać stałe adresy w przebiegach, aby poprawić wrażenia z debugowania: Różnica między adresami gdb a adresami „rzeczywistymi”? | Przepełnienie stosu .

readelf analiza

Ponadto możemy również zauważyć, że:

podaje rzeczywisty adres ładowania środowiska uruchomieniowego (pc wskazał następującą instrukcję 4 bajty dalej):

podczas:

daje tylko przesunięcie:

Wyłączając ASLR (za pomocą jednego randomize_va_spacelub set disable-randomization offdrugiego), GDB zawsze podaje mainadres 0x5555555547a9:, więc wnioskujemy, że -pieadres składa się z:

TODO, gdzie jest na stałe zakodowany kod 0x555555554000 w jądrze Linux / programie ładującym glibc / gdziekolwiek? Jak jest określany adres sekcji tekstowej pliku wykonywalnego PIE w systemie Linux?

Minimalny przykład montażu

Kolejną fajną rzeczą, którą możemy zrobić, jest pobawienie się kodem asemblera, aby bardziej konkretnie zrozumieć, co oznacza PIE.

Możemy to zrobić za pomocą wolnostojącego zestawu Linux x86_64 hello world:

sieć elektryczna

GitHub upstream

i łączy się i działa dobrze z:

Jeśli jednak spróbujemy połączyć go jako PIE z ( --no-dynamic-linkerjest to wymagane, jak wyjaśniono w: Jak utworzyć statycznie powiązany plik wykonywalny niezależny od pozycji w systemie Linux? ):

wtedy połączenie zakończy się niepowodzeniem z:

Ponieważ linia:

koduje na stałe adres komunikatu w movoperandzie i dlatego nie jest niezależny od pozycji.

Jeśli zamiast tego napiszemy to w sposób niezależny od pozycji:

wtedy łącze PIE działa dobrze, a GDB pokazuje nam, że plik wykonywalny jest ładowany za każdym razem w innym miejscu w pamięci.

Różnica polega na tym, że leazakodował adres msgwzględny w stosunku do bieżącego adresu komputera ze względu na ripskładnię, zobacz także: Jak używać adresowania względnego RIP w 64-bitowym programie asemblerowym?

Możemy to również ustalić, demontując obie wersje za pomocą:

które dają odpowiednio:

Widzimy więc wyraźnie, że leama już pełny poprawny adres msgzakodowany jako aktualny adres + 0x19.

movWersja jednak ustawił adres, na 00 00 00 00, co oznacza, że przeniesienie będą tam wykonywane: Czego łączniki zrobić? Tajemniczy R_X86_64_32Swld komunikacie o błędzie jest rzeczywisty typ relokacji, który był wymagany i który nie może mieć miejsca w plikach wykonywalnych PIE.

Kolejną zabawną rzeczą, którą możemy zrobić, jest umieszczenie msgw sekcji danych zamiast .text:

Teraz .ozbiera się, aby:

więc offset RIP wynosi teraz 0i domyślamy się, że asembler zażądał relokacji. Potwierdzamy to:

co daje:

tak oczywiste R_X86_64_PC32jest relokacja względna komputera, która ldmoże obsłużyć pliki wykonywalne PIE.

Ten eksperyment nauczył nas, że sam linker sprawdza, czy program może być SROKĄ i oznacza go jako taki.

Następnie podczas kompilacji z GCC -pie mówi GCC, aby wygenerował niezależny montaż pozycji.

Ale jeśli sami piszemy assembler, musimy ręcznie upewnić się, że osiągnęliśmy niezależność pozycji.

W ARMv8 aarch64 niezależną pozycję hello world można uzyskać za pomocą instrukcji ADR .

Jak ustalić, czy ELF jest niezależny od pozycji?

Oprócz uruchamiania go przez GDB, niektóre metody statyczne są wymienione na:

Testowane w Ubuntu 18.10.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
1
Cześć Ciro! Czy możesz utworzyć osobne pytanie dla adresu początkowego z wyłączonym ASLR i połączyć je tutaj?
osgx
1
@osgx Gotowe. Czy już wiesz, czy zamierzasz to wykopać w locie? :-)
Skoro już o tym mowa,
Jeszcze nie wiem, ale wiem, że należy go wykopać z rtld glibc - glibc / elf github.com/lattera/glibc/tree/master/elf (jeśli interpreter jest nadal ld-linux.so). Trzy lata temu Basile nie był pewien co do 0x55555555 również stackoverflow.com/questions/29856044 , ale to pytanie dotyczyło adresu początkowego samego ld.so, więc zagłęb się w skrypty fs / binfmt_elf.c lub readelf / objdump i linker jądra .
osgx