Przechwytywanie wpisów PLT

https://chacker.pl/

Kolejna technika wywoływania wstrzykniętego kodu, czyli przechwytywanie PLT, jest podobna do przechwytywania GOT. Podobnie jak GOT, przechwytywanie PLT pozwala na wstawienie zamiennika dla istniejącej funkcji bibliotecznej. Jedyną różnicą jest to, że zamiast zmieniać adres funkcji przechowywany we wpisie GOT używanym przez element zastępczy PLT, zmienia się sam element zastępczy PLT. Ponieważ technika ta wiąże się ze zmianą sekcji PLT, która jest sekcją kodu, nie nadaje się ona do modyfikowania zachowania pliku binarnego w czasie wykonywania. Listing  pokazuje, jak wykorzystać technikę przechwytywania PLT.

Listing : Calling injected code by hijacking a PLT entry

(1) $ cp /bin/ls ls.plt

(2) $ ./elfinject ls.plt hello-got.bin „.injected” 0x800000 -1

$ objdump -M intel -d ls.plt

(3) 0000000000402800 <fwrite_unlocked@plt>:

402800: (4)ff 25 9a ba 21 00 jmp QWORD PTR [rip+0x21ba9a] # 61e2a0 <_fini@@Base+0x20a644>

402806: 68 51 00 00 00 push 0x51

40280b: e9 d0 fa ff ff jmp 4022e0 <_init@@Base+0x28>

(5) $ hexedit ls.plt

$ objdump -M intel -d ls.plt

(6) 0000000000402800 <fwrite_unlocked@plt>:

402800: e9 73 e6 3f 00 jmp 800e78 <_end@@Base+0x1e1b10>

402805: 00 68 51 add BYTE PTR [rax+0x51],ch

402808: 00 00 add BYTE PTR [rax],al

40280a: 00 e9 add cl,ch

40280c: d0 fa sar dl,1

40280e: ff (bad)

40280f: ff .byte 0xff

(7)Ð $ ./ls.plt

hello world!

hello world!

hello world!

hello world!

hello world!

Tak jak poprzednio, zacznij od utworzenia kopii pliku binarnego ls (1) i wstrzyknięcia do niego nowego kodu (2). Zauważ, że ten przykład używa tego samego kodu, co w technice przejęcia GOT. Podobnie jak w przykładzie przejęcia GOT, zastąpisz wywołanie biblioteki fwrite_unlocked funkcją „hello world”. Używając objdump, spójrz na wpis PLT dla fwrite_unlocked (3). Ale tym razem nie interesuje Cię adres wpisu GOT używanego przez stub PLT. Zamiast tego spójrz na kodowanie binarne pierwszej instrukcji stubu PLT. Jak pokazuje objdump, kodowanie to ff259aba2100 (4), co odpowiada instrukcji pośredniej jmp z przesunięciem względem rejestru rip. Możesz przejąć wpis PLT, nadpisując tę ​​instrukcję inną, która skacze bezpośrednio do wstrzykniętego kodu. Następnie, używając hexedit, wyszukaj sekwencję bajtów ff259aba2100 odpowiadającą pierwszej instrukcji szczątkowej instrukcji PLT (5). Po jej znalezieniu zastąp ją ciągiem e973e63f00, który jest kodowaniem dla bezpośredniego jmp na adres 0x800e78, gdzie znajduje się wstrzykiwany kod. Pierwszy bajt, e9, ciągu zastępującego, to kod operacji dla bezpośredniego jmp, a kolejne 4 bajty to przesunięcie wstrzykiwanego kodu względem samej instrukcji jmp. Po zakończeniu modyfikacji ponownie zdezasembluj instrukcję PLT, używając objdump do weryfikacji zmian (6). Jak widać, pierwsza zdezasemblowana instrukcja wpisu fwrite_unlocked instrukcji PLT teraz odczytuje jmp 800e78: bezpośredni skok do wstrzykiwanego kodu. Następnie deasembler pokazuje kilka błędnych instrukcji wynikających z pozostałych bajtów oryginalnego wpisu PLT, których nie nadpisałeś. Błędne instrukcje nie stanowią problemu, ponieważ pierwsza instrukcja jest jedyną, która i tak zostanie wykonana. Teraz sprawdźmy, czy modyfikacje zadziałały. Po uruchomieniu zmodyfikowanego pliku binarnego ls zobaczysz, że komunikat „hello world” jest wyświetlany przy każdym wywołaniu funkcji fwrite_unlocked (7), zgodnie z oczekiwaniami, dając taki sam rezultat, jak w przypadku przejęcia kontroli nad GOT.

Przechwytywanie wpisów GOT

https://chacker.pl/

Obie omówione do tej pory techniki – modyfikacja punktu wejścia i przechwytywanie konstruktora/destruktora – pozwalają na jednorazowe uruchomienie wstrzykniętego kodu podczas uruchamiania lub zamykania pliku binarnego. Co zrobić, jeśli chcesz wielokrotnie wywoływać wstrzykniętą funkcję, na przykład w celu zastąpienia istniejącej funkcji bibliotecznej? Pokażę teraz, jak przechwycić wpis GOT, aby zastąpić wywołanie biblioteki wstrzykniętą funkcją. Przypomnijmy , że Global Offset Table (GOT) to tabela zawierająca wskaźniki do współdzielonych funkcji bibliotecznych, używanych do dynamicznego linkowania. Nadpisanie jednego lub więcej z tych wpisów daje zasadniczo taki sam poziom kontroli, jak technika LD_PRELOAD, ale bez potrzeby zewnętrznej biblioteki zawierającej nową funkcję, co pozwala zachować integralność pliku binarnego. Co więcej, przechwytywanie GOT to odpowiednia technika nie tylko do trwałej modyfikacji pliku binarnego, ale także do wykorzystywania pliku binarnego w czasie wykonywania. Technika przejęcia kontroli nad GOT wymaga niewielkiej modyfikacji wstrzykiwanego kodu, jak pokazano na Listingu

Listing : hello-got.s

BITS 64

SECTION .text

global main

main:

push rax ; save all clobbered registers

push rcx ; (rcx and r11 destroyed by kernel)

push rdx

push rsi

push rdi

push r11

mov rax,1 ; sys_write

mov rdi,1 ; stdout

lea rsi,[rel $+hello-$] ; hello

mov rdx,[rel $+len-$] ; len

syscall

pop r11

pop rdi

pop rsi

pop rdx

pop rcx

pop rax

(1) ret ; return

hello: db „hello world”,33,10

len : dd 13

W przypadku przejęcia GOT następuje całkowita zamiana funkcji bibliotecznej, więc nie ma potrzeby przekazywania kontroli z powrotem do oryginalnej implementacji po zakończeniu wstrzykiwanego kodu. Zatem Listing 7-17 nie zawiera żadnego zakodowanego na stałe adresu, na który przekazywane jest sterowanie na końcu. Zamiast tego kończy się po prostu normalnym zwrotem (1). Przyjrzyjmy się, jak zaimplementować technikę przejęcia GOT w praktyce. Listing 18 przedstawia przykład, w którym wpis GOT dla funkcji bibliotecznej fwrite_unlocked w pliku binarnym ls jest zastępowany wskaźnikiem do funkcji „hello world”, jak pokazano na Listingu wcześniejszym. Funkcja fwrite_unlocked to funkcja, której ls używa do wyświetlania wszystkich swoich komunikatów na ekranie.

Listing : Calling injected code by hijacking a GOT entry

(1) $ cp /bin/ls ls.got

(2)$ ./elfinject ls.got hello-got.bin „.injected” 0x800000 -1

$ objdump -M intel -d ls.got

(3) 0000000000402800 <fwrite_unlocked@plt>:

402800: ff 25 9a ba 21 00 jmp QWORD PTR [rip+0x21ba9a] # (4)61e2a0 <_fini@@Base+0x20a644>

402806: 68 51 00 00 00 push 0x51

40280b: e9 d0 fa ff ff jmp 4022e0 <_init@@Base+0x28>

$ objdump ls.got -s –section=.got.plt

ls.got: file format elf64-x86-64

Contents of section .got.plt:

61e290 e6274000 00000000 f6274000 00000000 .’@……’@…..

61e2a0 (5)06284000 00000000 16284000 00000000 .(@……(@…..

61e2b0 26284000 00000000 36284000 00000000 &(@…..6(@…..

(6) $ hexedit ls.got

$ objdump ls.got -s –section=.got.plt

ls.got: file format elf64-x86-64

Contents of section .got.plt:

61e290 e6274000 00000000 f6274000 00000000 .’@……’@…..

61e2a0 (7)780e8000 00000000 16284000 00000000 x……..(@…..

61e2b0 26284000 00000000 36284000 00000000 &(@…..6(@…..

(8) $ ./ls.got

hello world!

hello world!

hello world!

hello world!

hello world!

Po utworzeniu nowej kopii ls (1) i wstrzyknięciu do niej swojego kodu (2), możesz użyć objdump do wyświetlenia wpisów PLT pliku binarnego (gdzie używane są wpisy GOT) i znaleźć wpis dla fwrite_unlocked (3). Zaczyna się on pod adresem 0x402800, a używany przez niego wpis GOT znajduje się pod adresem 0x61e2a0 (4), czyli w sekcji .got.plt. Używając objdump do wyświetlenia sekcji .got.plt, możesz zobaczyć oryginalny adres zapisany we wpisie GOT (5): 402806 (zakodowany w formacie little-endian). Jak wyjaśniono w rozdziale 2, jest to adres następnej instrukcji we wpisie PLT fwrite_unlocked, którą chcesz nadpisać adresem wstrzykniętego kodu. Następnym krokiem jest uruchomienie hexeditu, wyszukanie ciągu 062840 i zastąpienie go adresem 0x800e78 wstrzykniętego kodu (6), jak zwykle. Zmiany potwierdza się, ponownie używając objdump, aby wyświetlić zmodyfikowany wpis GOT (7). Po zmianie wpisu GOT tak, aby wskazywał na funkcję „hello world”,

program ls wyświetla teraz „hello world” za każdym razem, gdy wywołuje fwrite_unlocked (8), zastępując wszystkie standardowe dane wyjściowe ls kopiami ciągu „hello world”. Oczywiście w rzeczywistości warto zastąpić fwrite_unlocked bardziej użyteczną funkcją. Zaletą przechwytywania GOT jest to, że jest ono nie tylko proste, ale można je również łatwo wykonać w czasie wykonywania. Dzieje się tak, ponieważ, w przeciwieństwie do sekcji kodu, plik .got.plt jest zapisywalny w czasie wykonywania. W rezultacie przejęcie kontroli nad GOT stało się popularną techniką nie tylko w przypadku statycznych modyfikacji plików binarnych, jak pokazałem tutaj, ale także w przypadku ataków mających na celu zmianę zachowania uruchomionego procesu.

Przejmowanie konstruktorów i destruktorów

https://chacker.pl/

Przyjrzyjmy się teraz innemu sposobowi, aby upewnić się, że wstrzykiwany kod zostanie wywołany raz w trakcie życia pliku binarnego, na początku lub na końcu wykonywania. Przypomnijmy z rozdziału 2, że pliki binarne x86 ELF skompilowane za pomocą gcc zawierają sekcje o nazwach .init_array i .fini_array, które zawierają odpowiednio wskaźniki do szeregu konstruktorów i destruktorów. Nadpisując jeden z tych wskaźników, można spowodować, że wstrzykiwany kod zostanie wywołany przed lub po funkcji głównej pliku binarnego, w zależności od tego, czy nadpisuje się konstruktor, czy wskaźnik do destruktora. Oczywiście po zakończeniu wstrzykiwanego kodu należy przekazać kontrolę z powrotem do przejętego konstruktora lub destruktora. Wymaga to drobnych zmian w wstrzykiwanym kodzie, jak pokazano na listingu . W tym listingu zakładam, że przekażesz kontrolę z powrotem do konkretnego konstruktora, którego adres znajdziesz za pomocą objdump.

Listing : hello-ctor.s

BITS 64

SECTION .text

global main

main:

push rax ; save all clobbered registers

push rcx ; (rcx and r11 destroyed by kernel)

push rdx

push rsi

push rdi

push r11

mov rax,1 ; sys_write

mov rdi,1 ; stdout

lea rsi,[rel $+hello-$] ; hello

mov rdx,[rel $+len-$] ; len

syscall

pop r11

pop rdi

pop rsi

pop rdx

pop rcx

pop rax

(1) push 0x404a70 ; jump to original constructor

ret

hello: db „hello world”,33,10

len : dd 13

Kod przedstawiony na Liście 7-15 jest taki sam jak kod z Liście 7-12, z tą różnicą, że zamiast adresu punktu wejścia wstawiłem adres przejętego konstruktora, aby powrócić do (1). Polecenie złożenia kodu do surowego pliku binarnego jest takie samo, jak omówiono w poprzedniej sekcji. Listing pokazuje, jak wstrzyknąć kod do pliku binarnego i przejąć konstruktor.

Listing : Calling injected code by hijacking a constructor

(1) $ cp /bin/ls ls.ctor

(2)$ ./elfinject ls.ctor hello-ctor.bin „.injected” 0x800000 -1

$ readelf –wide -S ls.ctor

There are 29 section headers, starting at offset 0x1e738:

Section Headers:

[Nr] Name Type Address Off Size ES Flg Lk Inf Al

[ 0] NULL 0000000000000000 000000 000000 00 0 0 0

[ 1] .interp PROGBITS 0000000000400238 000238 00001c 00 A 0 0 1

[ 2] .init PROGBITS 00000000004022b8 0022b8 00001a 00 AX 0 0 4

[ 3] .note.gnu.build-id NOTE 0000000000400274 000274 000024 00 A 0 0 4

[ 4] .gnu.hash GNU_HASH 0000000000400298 000298 0000c0 00 A 5 0 8

[ 5] .dynsym DYNSYM 0000000000400358 000358 000cd8 18 A 6 1 8

[ 6] .dynstr STRTAB 0000000000401030 001030 0005dc 00 A 0 0 1

[ 7] .gnu.version VERSYM 000000000040160c 00160c 000112 02 A 5 0 2

[ 8] .gnu.version_r VERNEED 0000000000401720 001720 000070 00 A 6 1 8

[ 9] .rela.dyn RELA 0000000000401790 001790 0000a8 18 A 5 0 8

[10] .rela.plt RELA 0000000000401838 001838 000a80 18 AI 5 24 8

[11] .plt PROGBITS 00000000004022e0 0022e0 000710 10 AX 0 0 16

[12] .plt.got PROGBITS 00000000004029f0 0029f0 000008 00 AX 0 0 8

[13] .text PROGBITS 0000000000402a00 002a00 011259 00 AX 0 0 16

[14] .fini PROGBITS 0000000000413c5c 013c5c 000009 00 AX 0 0 4

[15] .rodata PROGBITS 0000000000413c80 013c80 006974 00 A 0 0 32

[16] .eh_frame_hdr PROGBITS 000000000041a5f4 01a5f4 000804 00 A 0 0 4

[17] .eh_frame PROGBITS 000000000041adf8 01adf8 002c6c 00 A 0 0 8

[18] .jcr PROGBITS 000000000061de10 01de10 000008 00 WA 0 0 8

(3) [19] .init_array INIT_ARRAY 000000000061de00 01de00 000008 00 WA 0 0 8

[20] .fini_array FINI_ARRAY 000000000061de08 01de08 000008 00 WA 0 0 8

[21] .got PROGBITS 000000000061dff8 01dff8 000008 08 WA 0 0 8

[22] .dynamic DYNAMIC 000000000061de18 01de18 0001e0 10 WA 6 0 8

[23] .got.plt PROGBITS 000000000061e000 01e000 000398 08 WA 0 0 8

[24] .data PROGBITS 000000000061e3a0 01e3a0 000260 00 WA 0 0 32

[25] .gnu_debuglink PROGBITS 0000000000000000 01e600 000034 00 0 0 1

[26] .bss NOBITS 000000000061e600 01e600 000d68 00 WA 0 0 32

[27] .injected PROGBITS 0000000000800e78 01ee78 00003f 00 AX 0 0 16

[28] .shstrtab STRTAB 0000000000000000 01e634 000102 00 0 0 1

Key to Flags:

W (write), A (alloc), X (execute), M (merge), S (strings), l (large)

I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)

O (extra OS processing required) o (OS specific), p (processor specific)

$ objdump ls.ctor -s –section=.init_array

ls: file format elf64-x86-64

Contents of section .init_array:

61de00 (4)704a4000 00000000 pJ@…..

(5) $ hexedit ls.ctor

$ objdump ls.ctor -s –section=.init_array

ls.ctor: file format elf64-x86-64

Contents of section .init_array:

61de00 (6)780e8000 00000000 x…….

(7) $ ./ls.ctor

hello world!

elfinject elfinject.c hello.s hello.bin ls Makefile

Tak jak poprzednio, zaczynasz od skopiowania /bin/ls (1) i wstrzyknięcia nowego kodu do kopii (2), bez zmiany punktu wejścia. Używając readelf, możesz zobaczyć, że sekcja .init_array istnieje (3). Sekcja .fini_array również tam jest, ale w tym przypadku przejmuję konstruktor, a nie destruktor. Zawartość .init_array możesz wyświetlić za pomocą objdump, który ujawnia pojedynczy wskaźnik do funkcji konstruktora o wartości 0x404a70 (przechowywany w formacie littleendian) (4). Teraz możesz użyć hexedit, aby wyszukać ten adres i zmienić go (5) na adres wejścia 0x800e78 wstrzykiwanego kodu. Po wykonaniu tej czynności wskaźnik ingle w .init_array wskazuje na wstrzykiwany kod zamiast na oryginalny konstruktor (6). Pamiętaj, że po wykonaniu tej czynności wstrzykiwany kod przekazuje sterowanie z powrotem do oryginalnego konstruktora. Po nadpisaniu wskaźnika konstruktora, zaktualizowany plik binarny ls uruchamia się, wyświetlając komunikat „hello world”, a następnie normalnie wyświetla listę katalogów (7). Dzięki tej technice można uruchomić kod jednokrotnie na początku lub na końcu pliku binarnego bez konieczności modyfikowania jego punktu wejścia.

Modyfikacja punktu wejścia

https://chacker.pl/

Najpierw krótko podsumujmy technikę modyfikacji punktu wejścia ELF. W poniższym przykładzie przekażę kontrolę do sekcji kodu wstrzykniętej za pomocą elfinject, ale zamiast używać elfinject do aktualizacji samego punktu wejścia, użyję edytora heksadecymalnego. Pokażę, jak uogólnić tę technikę na kod wstrzykiwany na różne sposoby. Listing przedstawia instrukcje asemblera dla kodu, który wstrzyknę. Jest to przykład „hello world” użyty w poprzedniej sekcji.

Listing : hello.s

(1)BITS 64

SEKCJA .text

global main

main:

(2) push rax ; zapisz wszystkie zablokowane rejestry

push rcx ; (rcx i r11 zniszczone przez jądro)

push rdx

push rsi

push rdi

push r11

(3) mov rax,1 ; sys_write

mov rdi,1 ; stdout

lea rsi,[rel $+hello-$] ; hello

mov rdx,[rel $+len-$] ; len

(4) wywołanie systemowe

(5) pop r11

pop rdi

pop rsi

pop rdx

pop rcx

pop rax

(6) push 0x4049a0 ; przejdź do oryginalnego punktu wejścia

ret

(7) hello: db „hello world”,33,10

(8) len: dd 13

Kod jest w składni Intela i przeznaczony do asemblacji za pomocą asemblera nasm w trybie 64-bitowym (1). Pierwsze kilka instrukcji asemblera zapisuje rejestry rax, rcx, rdx, rsi i rdi poprzez umieszczenie ich na stosie (2). Rejestry te mogą zostać uszkodzone przez jądro i należy przywrócić je do pierwotnych wartości po zakończeniu wstrzykiwania kodu, aby uniknąć kolizji z innym kodem. Następne instrukcje konfigurują argumenty dla wywołania systemowego sys_write (3), które wyświetli na ekranie tekst „hello world!”. (Więcej informacji na temat wszystkich standardowych numerów i argumentów wywołań systemowych Linuksa znajdziesz w podręczniku systemowym syscall). W przypadku sys_write numer wywołania systemowego (umieszczony w rax) wynosi 1, a argumenty to: deskryptor pliku, do którego ma zostać zapisany (1 dla stdout), wskaźnik do ciągu, który ma zostać wydrukowany, oraz długość ciągu. Po przygotowaniu wszystkich argumentów instrukcja systemowa (4) wywołuje właściwe wywołanie systemowe, drukując ciąg znaków. Po wywołaniu wywołania systemowego sys_write kod przywraca rejestry do ich wcześniej zapisanego stanu (5). Następnie umieszcza adres 0x4049a0 oryginalnego punktu wejścia (który znalazłem za pomocą readelf, jak wkrótce zobaczysz) i wraca pod ten adres, rozpoczynając wykonywanie oryginalnego programu (6). Ciąg „hello world” (7) jest deklarowany po instrukcjach asemblera wraz z liczbą całkowitą zawierającą długość ciągu znaków (8), które są używane w wywołaniu systemowym sys_write.

Aby kod nadawał się do wstrzyknięcia, należy go zaasemblować do surowego pliku binarnego, który zawiera jedynie binarne kodowanie instrukcji asemblera i danych. Dzieje się tak, ponieważ nie chcemy tworzyć pełnoprawnego pliku binarnego ELF zawierającego nagłówki i inne dane narzutowe niepotrzebne do wstrzyknięcia. Aby zmontować plik hello.s do surowego pliku binarnego, można użyć opcji -f bin asemblera NASM, jak pokazano na listingu . Plik Makefile dla tego rozdziału zawiera cel hello.bin, który automatycznie uruchamia to polecenie.

Listing : Asemblacja pliku hello.s do pliku hello.bin za pomocą nasm

$ nasm -f bin -o hello.bin hello.s

W ten sposób tworzony jest plik hello.bin, który zawiera surowe instrukcje binarne i dane nadające się do wstrzyknięcia. Teraz użyjmy elfinject do wstrzyknięcia tego pliku i przekierujmy punkt wejścia ELF za pomocą edytora szesnastkowego, aby wstrzyknięty kod uruchamiał się podczas uruchamiania pliku binarnego. Listing  pokazuje, jak to zrobić.

Listing 7-14: Wywołanie wstrzykniętego kodu poprzez nadpisanie punktu wejścia ELF

(1) $ cp /bin/ls ls.entry

(2) $ ./elfinject ls.entry hello.bin „.injected” 0x800000 -1

$ readelf -h ./ls.entry

Nagłówek ELF:

Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

Klasa: ELF64

Dane: uzupełnienie do 2, little endian

Wersja: 1 (bieżąca)

OS/ABI: UNIX – System V

Wersja ABI: 0

Typ: EXEC (plik wykonywalny)

Maszyna: Advanced Micro Devices X86-64

Wersja: 0x1

Adres punktu wejścia: (3)0x4049a0

Początek nagłówków programu: 64 (bajtów w pliku)

Początek nagłówków sekcji: 124728 (bajtów w pliku)

Flagi: 0x0

Rozmiar tego nagłówka: 64 (bajtów)

Rozmiar nagłówków programu: 56 (bajtów)

Liczba nagłówków programu: 9

Rozmiar nagłówków sekcji: 64 (bajtów)

Liczba nagłówków sekcji: 29

Indeks tabeli ciągów nagłówków sekcji: 28

$ readelf –wide -S code/chapter7/ls.entry

Istnieje 29 nagłówków sekcji, począwszy od przesunięcia 0x1e738:

Nagłówki sekcji:

[Nr] Nazwa Typ Adres Wył. Rozmiar ES Flg Lk Inf Al

[27] .injected PROGBITS (4)0000000000800e78 01ee78 00003f 00 AX 0 0 16

(5) $ ./ls.entry

elfinject elfinject.c hello.s hello.bin ls Makefile

(6) $ hexedit ./ls.entry

$ readelf -h ./ls.entry

Nagłówek ELF:

Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

Klasa: ELF64

Dane: uzupełnienie do 2, little endian

Wersja: 1 (bieżąca)

OS/ABI: UNIX – System V

Wersja ABI: 0

Typ: EXEC (Plik wykonywalny)

Maszyna: Advanced Micro Devices X86-64

Wersja: 0x1

Adres punktu wejścia: (7)0x800e78

Początek nagłówków programu: 64 (bajtów w pliku)

Początek nagłówków sekcji: 124728 (bajtów w pliku)

Flagi: 0x0

Rozmiar tego nagłówka: 64 (bajtów)

Rozmiar nagłówków programu: 56 (bajtów)

Liczba nagłówków programu: 9

Rozmiar nagłówków sekcji: 64 (bajtów)

Liczba nagłówków sekcji: 29

Indeks tabeli ciągów nagłówków sekcji: 28

(8) $ ./ls.entry

Witaj świecie!

elfinject elfinject.c hello.s hello.bin ls Makefile

Najpierw skopiuj plik binarny /bin/ls do pliku ls.entry (1). Będzie on służył jako plik binarny hosta dla wstrzyknięcia. Następnie możesz użyć elfinject do wstrzyknięcia właśnie przygotowanego kodu do pliku binarnego z adresem ładowania 0x800000 (2), dokładnie tak, jak omówiono w sekcji 7.3.2, z jedną istotną różnicą: ustaw ostatni argument elfinject na -1, aby elfinject pozostawił punkt wejścia niezmieniony (ponieważ nadpiszesz go ręcznie). Za pomocą readelf możesz zobaczyć oryginalny punkt wejścia pliku binarnego: 0x4049a0 (3). Zauważ, że jest to adres, na który wstrzyknięty kod przeskakuje po zakończeniu drukowania komunikatu „hello world”, jak pokazano na Listingu 7-12. Za pomocą readelf możesz również zobaczyć, że wstrzyknięta sekcja faktycznie zaczyna się od adresu 0x800e78 (4), a nie od adresu 0x800000. Dzieje się tak, ponieważ elfinject nieznacznie zmienił adres, aby spełnić wymagania wyrównania formatu ELF, co omawiam bardziej szczegółowo w Dodatku B. Ważne jest, aby 0x800e78 był nowym adresem, którym chcesz nadpisać adres punktu wejścia. Ponieważ punkt wejścia jest nadal niezmodyfikowany, jeśli teraz uruchomisz ls.entry, będzie ono zachowywać się jak normalne polecenie ls bez dodanego komunikatu „hello world” na początku (5). Aby zmodyfikować punkt wejścia, otwórz plik binarny ls.entry w hexedit (6) i wyszukaj oryginalny adres punktu wejścia. Przypomnij sobie, że możesz otworzyć okno dialogowe wyszukiwania w hexedit za pomocą klawisza /, a następnie wpisać adres do wyszukania. Adres jest przechowywany w formacie little-endian, więc będziesz musiał wyszukać bajty a04940 zamiast 4049a0. Po znalezieniu punktu wejścia nadpisz go nowym, ponownie z odwróconą kolejnością bajtów: 780e80. Teraz naciśnij CTRL+X, aby wyjść, i naciśnij Y, aby zapisać zmiany. Możesz teraz zobaczyć w readelf, że punkt wejścia został zaktualizowany do 0x800e78 (7), wskazując na początek wstrzykniętego kodu. Teraz po uruchomieniu ls.entry, wyświetli się „hello world” przed wyświetleniem listy katalogów (8). Pomyślnie nadpisałeś punkt wejścia!

Wywoływanie wstrzykniętego kodu

https://chacker.pl/

W poprzedniej sekcji dowiedziałeś się, jak za pomocą elfinject wstrzyknąć nową sekcję kodu do istniejącego pliku binarnego. Aby nowy kod został wykonany, zmodyfikowałeś punkt wejścia ELF, co spowodowało uruchomienie nowego kodu natychmiast po przekazaniu kontroli przez program ładujący do pliku binarnego. Nie zawsze jednak chcesz użyć wstrzykniętego kodu od razu po uruchomieniu pliku binarnego. Czasami wstrzyknięty kod może być potrzebny z różnych powodów, na przykład w celu zastąpienia istniejącej funkcji innym kodem. W tej sekcji omówię alternatywne techniki przekazywania kontroli do wstrzykniętego kodu, inne niż modyfikacja punktu wejścia ELF. Podsumuję również technikę modyfikacji punktu wejścia ELF, tym razem używając tylko edytora szesnastkowego do zmiany punktu wejścia. Pozwoli to na przekierowanie punktu wejścia nie tylko do kodu wstrzykniętego za pomocą elfinject, ale także do kodu wstawionego w inny sposób, na przykład poprzez nadpisanie martwego kodu, takiego jak instrukcje dopełniające. Należy pamiętać, że wszystkie techniki omówione w tej sekcji nadają się do stosowania z dowolną metodą wstrzykiwania kodu, nie tylko z nadpisywaniem PT_NOTE

Użycie elfinject do wstrzyknięcia sekcji ELF

https://chacker.pl/

Aby bardziej ukonkretnić technikę wstrzykiwania PT_NOTE, przyjrzyjmy się, jak użyć narzędzia elfinject dostępnego na maszynie wirtualnej. Listing  pokazuje, jak użyć elfinject do wstrzyknięcia sekcji kodu do pliku binarnego.

Listing : Użycie elfinject

W folderze kodu dla tego rozdziału na maszynie wirtualnej zobaczysz plik o nazwie hello.bin (1), który zawiera nowy kod, który wstrzykniesz w surowej postaci binarnej (bez nagłówków ELF). Jak wkrótce zobaczysz, kod wyświetla komunikat „hello world!”, a następnie przekazuje sterowanie do oryginalnego punktu wejścia pliku binarnego hosta, wznawiając normalne wykonywanie pliku binarnego. Jeśli jesteś zainteresowany, możesz znaleźć instrukcje asemblera lub wstrzyknięty kod w pliku o nazwie hello.s lub w sekcji 7.4. Przyjrzyjmy się teraz użyciu elfinject (2). Jak widać, elfinject oczekuje pięciu argumentów: ścieżki do pliku binarnego hosta, ścieżki do pliku wstrzykniętego, nazwy i adresu wstrzykniętej sekcji oraz przesunięcia do punktu wejścia wstrzykniętego kodu (lub -1, jeśli nie ma punktu wejścia). Plik wstrzyknięty hello.bin jest wstrzyknięty do pliku binarnego hosta z podaną nazwą, adresem i punktem wejścia. W tym przykładzie używam kopii pliku /bin/ls jako pliku binarnego hosta (3). Jak widać, polecenie ls zachowuje się normalnie przed wstrzyknięciem, wyświetlając listę bieżącego katalogu (4). Za pomocą polecenia readelf można zobaczyć, że plik binarny zawiera sekcję .note.ABI-tag (5) oraz segment PT_NOTE (6), który zostanie nadpisany przez wstrzyknięcie. Teraz czas na wstrzyknięcie kodu. W tym przykładzie używam polecenia elfinject do wstrzyknięcia pliku hello.bin do pliku binarnego ls, używając nazwy .injected i adresu ładowania 0x800000 dla sekcji wstrzykniętej (którą elfinject dodaje na końcu pliku binarnego) (7). Używam 0 jako punktu wejścia, ponieważ punkt wejścia pliku hello.bin znajduje się na samym początku. Po pomyślnym zakończeniu działania elfinject, readelf pokazuje, że plik binarny ls zawiera teraz sekcję kodu o nazwie .injected (8) oraz nowy segment wykonywalny typu PT_LOAD (9), który zawiera tę sekcję. Ponadto sekcja .note.ABI-tag i segment PT_NOTE zniknęły, ponieważ zostały nadpisane. Wygląda na to, że wstrzyknięcie zakończyło się powodzeniem! Teraz sprawdźmy, czy wstrzyknięty kod zachowuje się zgodnie z oczekiwaniami. Wykonując zmodyfikowany plik binarny ls (10), można zauważyć, że plik binarny uruchamia wstrzyknięty kod podczas uruchamiania, wyświetlając komunikat „Hello World!”. Wstrzyknięty kod następnie przekazuje wykonywanie do oryginalnego punktu wejścia pliku binarnego, aby wznowić jego normalne działanie, czyli wyświetlanie listy katalogów.

Przekierowanie punktu wejścia ELF

https://chacker.pl/

Krok (4) na rysunku wcześniejszym jest opcjonalny. W tym kroku zmieniam pole e_entry w nagłówku pliku wykonywalnego ELF, aby wskazywało na adres w nowej sekcji .injected, zamiast na oryginalny punkt wejścia, który zazwyczaj znajduje się gdzieś w pliku .text. Musisz to zrobić tylko wtedy, gdy chcesz, aby jakiś kod w sekcji .injected został uruchomiony od razu na początku programu. W przeciwnym razie możesz po prostu pozostawić punkt wejścia bez zmian, chociaż w takim przypadku nowy, wstrzyknięty kod nigdy nie zostanie uruchomiony, chyba że przekierujesz niektóre wywołania w oryginalnej sekcji .text do wstrzykniętego kodu, użyjesz części wstrzykniętego kodu jako konstruktorów lub zastosujesz inną metodę, aby dotrzeć do wstrzykniętego kodu. .

Nadpisywanie segmentu PT_NOTE

https://www.chacker.pl/

Jak właśnie zauważyłeś, łatwiej jest nadpisać istniejący nagłówek sekcji i nagłówek programu niż dodawać zupełnie nowe. Ale skąd wiesz, które nagłówki możesz bezpiecznie nadpisać bez uszkodzenia pliku binarnego? Jednym z nagłówków programu, który zawsze możesz bezpiecznie nadpisać, jest nagłówek PT_NOTE, który opisuje segment PT_NOTE. Segment PT_NOTE obejmuje sekcje zawierające informacje pomocnicze o pliku binarnym. Na przykład może informować, że jest to plik binarny GNU/Linux, jakiej wersji jądra oczekuje plik binarny itd. W pliku wykonywalnym /bin/ls na maszynie wirtualnej, w szczególności, segment PT_NOTE zawiera te informacje w dwóch sekcjach o nazwach .note.ABI-tag i .note.gnu.build-id. Jeśli tych informacji brakuje, program ładujący po prostu zakłada, że ​​jest to natywny plik binarny, więc można bezpiecznie nadpisać nagłówek PT_NOTE bez obawy o uszkodzenie pliku binarnego. Ta sztuczka jest powszechnie stosowana przez złośliwe pasożyty do infekowania plików binarnych, ale działa również w przypadku łagodnych modyfikacji. Rozważmy teraz zmiany potrzebne w kroku (2) , gdzie nadpisujesz jeden z nagłówków sekcji .note.*, aby przekształcić go w nagłówek nowej sekcji kodu (.injected). Wybiorę (arbitralnie) nadpisanie nagłówka sekcji .note.ABI-tag. Jak widać , zmieniam sh_type z SHT_NOTE na SHT_PROGBITS, aby wskazać, że nagłówek teraz opisuje sekcję kodu. Ponadto zmieniam pola sh_addr, sh_offset i sh_size, aby opisywać lokalizację i rozmiar nowej sekcji .injected zamiast przestarzałej sekcji note.ABI-tag. Na koniec zmieniam wyrównanie sekcji (sh_addralign) na 16 bajtów, aby zapewnić prawidłowe wyrównanie kodu po załadowaniu do pamięci, i dodaję flagę SHF_EXECINSTR do pola sh_flags, aby oznaczyć sekcję jako wykonywalną. Zmiany w kroku (3) są podobne, z tą różnicą, że tutaj zmieniam nagłówek programu PT_NOTE zamiast nagłówka sekcji. Ponownie zmieniam typ nagłówka, ustawiając p_type na PT_LOAD, aby wskazać, że nagłówek teraz opisuje segment ładowalny zamiast segmentu PT_NOTE. Powoduje to, że program ładujący ładuje segment (obejmujący nową sekcję .injected) do pamięci podczas uruchamiania programu. Zmieniam również wymagane pola adresu, przesunięcia i rozmiaru: p_offset, p_vaddr (i p_paddr, niewidoczne na rysunku), p_filesz i p_memsz. Ustawiam p_flags, aby oznaczyć segment jako czytelny i wykonywalny, a nie tylko czytelny, i poprawiam wyrównanie (p_align). Warto również zaktualizować tabelę ciągów znaków, zmieniając nazwę starej sekcji .note.ABI-tag na coś w rodzaju .injected, aby odzwierciedlić fakt dodania nowej sekcji kodu.

Wstrzykiwanie sekcji ELF: Przegląd ogólny

https://chacker.pl/

Rysunek  przedstawia główne kroki niezbędne do wstrzyknięcia nowej sekcji kodu do pliku ELF.

Lewa strona rysunku przedstawia oryginalny (niezmodyfikowany) plik ELF, a prawa strona – zmodyfikowany plik z dodaną nową sekcją, o nazwie .injected. Aby dodać nową sekcję do pliku binarnego ELF, należy najpierw wstrzyknąć bajty, które będzie zawierała sekcja (krok (1) na rysunku 7-2), dołączając je na końcu pliku binarnego. Następnie tworzy się nagłówek sekcji (2) i nagłówek programu (3) dla wstrzykniętej sekcji. Jak zapewne pamiętasz z rozdziału 2, tabela nagłówków programu zazwyczaj znajduje się tuż za nagłówkiem wykonywalnym (4). Z tego powodu dodanie dodatkowego nagłówka programu spowodowałoby przesunięcie wszystkich sekcji i nagłówków, które następują po nim. Aby uniknąć konieczności skomplikowanego przesunięcia, można po prostu nadpisać istniejący nagłówek zamiast dodawać nowy, jak pokazano na rysunku . Tę samą sztuczkę implementuje elfinject, a Ty możesz zastosować tę samą sztuczkę z nadpisywaniem nagłówków, aby uniknąć dodawania nowego nagłówka sekcji do pliku binarnego.

Wstrzykiwanie sekcji kodu

https://chacker.pl/

Techniki modyfikacji plików binarnych, których się do tej pory nauczyłeś, mają dość ograniczone zastosowanie. Edycja szesnastkowa jest przydatna w przypadku drobnych modyfikacji, ale nie można dodać zbyt wiele (jeśli w ogóle) nowego kodu ani danych. LD_PRELOAD pozwala na łatwe dodawanie nowego kodu, ale można go używać tylko do modyfikowania wywołań bibliotek. Zanim przejdziemy do omówienia bardziej elastycznych technik modyfikacji plików binarnych w rozdziale 9, przyjrzyjmy się, jak wstrzyknąć zupełnie nową sekcję kodu do pliku binarnego ELF; ta stosunkowo prosta sztuczka jest bardziej elastyczna niż omówione wcześniej. Na maszynie wirtualnej istnieje kompletne narzędzie o nazwie elfinject, które implementuje tę technikę wstrzykiwania kodu. Ponieważ kod źródłowy elfinject jest dość obszerny, nie będę go tutaj omawiał, ale w Dodatku B załączam wyjaśnienie implementacji elfinject, jeśli jesteś zainteresowany. Dodatek stanowi również wprowadzenie do libelf, popularnej biblioteki open source do parsowania plików binarnych ELF. Chociaż znajomość języka libelf nie jest konieczna do zrozumienia reszty książki, może być on przydatny podczas wdrażania własnych narzędzi do analizy binarne. W tej sekcji przedstawię ogólny zarys, który wyjaśnia główne kroki techniki wstrzykiwania sekcji kodu. Następnie pokażę, jak użyć narzędzia elfinject dostępnego na maszynie wirtualnej, aby wstrzyknąć sekcję kodu do pliku binarnego ELF.