WWW2Exec - .dtors & .fini_array
.dtors
Obecnie jest bardzo dziwne znalezienie binarnego pliku z sekcją .dtors!
Destruktory to funkcje, które są wykonywane przed zakończeniem programu (po zakończeniu działania funkcji main
).
Adresy tych funkcji są przechowywane wewnątrz sekcji .dtors
binarnego pliku, dlatego jeśli uda ci się zapisać adres do shellcode w __DTOR_END__
, to zostanie wykonany przed zakończeniem programu.
Pobierz adres tej sekcji za pomocą:
Zazwyczaj znajdziesz znaczniki DTOR pomiędzy wartościami ffffffff
i 00000000
. Jeśli widzisz tylko te wartości, oznacza to, że nie ma zarejestrowanej żadnej funkcji. Nadpisz 00000000
adresem shellcode, aby go uruchomić.
Oczywiście najpierw musisz znaleźć miejsce do przechowywania shellcode, aby później móc go wywołać.
.fini_array
W zasadzie jest to struktura zawierająca funkcje, które zostaną wywołane przed zakończeniem programu, podobnie jak .dtors
. Jest to interesujące, jeśli możesz wywołać swój shellcode, skacząc do adresu, lub w przypadkach, gdy musisz wrócić do main
ponownie, aby wykorzystać podatność po raz drugi.
Zauważ, że gdy funkcja z .fini_array
jest wywoływana, przechodzi do następnej, więc nie będzie wykonywana wielokrotnie (zapobiegając wiecznym pętlom), ale również dostaniesz tylko 1 wywołanie funkcji umieszczonej tutaj.
Zauważ, że wpisy w .fini_array
są wywoływane w odwrotnej kolejności, więc prawdopodobnie chcesz zacząć pisanie od ostatniego.
Wieczna pętla
Aby nadużyć .fini_array
i uzyskać wieczną pętlę, możesz sprawdzić, co zostało tutaj zrobione: Jeśli masz co najmniej 2 wpisy w .fini_array
, możesz:
Użyj swojego pierwszego zapisu, aby ponownie wywołać podatną funkcję do dowolnego zapisu
Następnie oblicz adres powrotu na stosie przechowywany przez
__libc_csu_fini
(funkcja wywołująca wszystkie funkcje.fini_array
) i umieść tam adres__libc_csu_fini
Spowoduje to, że
__libc_csu_fini
ponownie wywoła siebie, wykonując ponownie funkcje.fini_array
, które ponownie wywołają podatną funkcję WWW 2 razy: raz dla dowolnego zapisu i jeszcze raz, aby ponownie nadpisać adres powrotu__libc_csu_fini
na stosie, aby ponownie wywołać siebie.
Zauważ, że przy pełnym RELRO, sekcja .fini_array
jest ustawiona jako tylko do odczytu.
link_map
Jak wyjaśniono w tym poście, jeśli program zakończy działanie za pomocą return
lub exit()
, zostanie uruchomiona funkcja __run_exit_handlers()
, która wywoła zarejestrowane destruktory.
Jeśli program zakończy działanie za pomocą funkcji _exit()
, zostanie wywołane wywołanie systemowe exit
i obsługiwane nie będą wykonywane. Aby potwierdzić wykonanie __run_exit_handlers()
, można ustawić punkt przerwania na to.
Ważny kod to (źródło):
Zauważ, jak map -> l_addr + fini_array -> d_un.d_ptr
jest używane do obliczenia pozycji tablicy funkcji do wywołania.
Istnieje kilka opcji:
Nadpisz wartość
map->l_addr
, aby wskazywała na fałszywyfini_array
z instrukcjami do wykonania arbitralnego koduNadpisz wpisy
l_info[DT_FINI_ARRAY]
il_info[DT_FINI_ARRAYSZ]
(które są mniej więcej kolejne w pamięci), aby wskazywały na sfałszowaną strukturęElf64_Dyn
, która ponownie spowoduje, żearray
wskazuje na obszar pamięci kontrolowany przez atakującego.Ten opis nadpisuje
l_info[DT_FINI_ARRAY]
adresem kontrolowanej pamięci w.bss
, zawierającą fałszywyfini_array
. Ten fałszywy array zawiera najpierw adres one gadget, który zostanie wykonany, a następnie różnicę między adresem tego fałszywego arraya a wartościąmap->l_addr
, aby*array
wskazywał na fałszywy array.Zgodnie z głównym postem na temat tej techniki i tym opisem ld.so pozostawia wskaźnik na stosie, który wskazuje na binarny
link_map
w ld.so. Dzięki arbitralnemu zapisowi możliwe jest nadpisanie go i spowodowanie, że wskazuje na fałszywyfini_array
kontrolowany przez atakującego z adresem one gadget na przykład.
Po poprzednim kodzie znajdziesz kolejny interesujący fragment kodu:
W tym przypadku możliwe byłoby nadpisanie wartości map->l_info[DT_FINI]
, wskazującej na sfałszowaną strukturę ElfW(Dyn)
. Znajdź więcej informacji tutaj.
Nadpisanie listy dtor_list w pamięci TLS w __run_exit_handlers
__run_exit_handlers
Jak wyjaśniono tutaj, jeśli program kończy działanie za pomocą return
lub exit()
, zostanie wykonana funkcja __run_exit_handlers()
, która wywoła zarejestrowane funkcje destrukcyjne.
Kod z _run_exit_handlers()
:
Kod z __call_tls_dtors()
:
Dla każdej zarejestrowanej funkcji w tls_dtor_list
, zostanie odszyfrowany wskaźnik z cur->func
i zostanie ona wywołana z argumentem cur->obj
.
Korzystając z funkcji tls
z tego forka GEF, można zobaczyć, że dtor_list
jest bardzo blisko stack canary i cookie PTR_MANGLE. Dlatego, przy przepełnieniu go, możliwe byłoby nadpisanie cookie i stack canary.
Przy nadpisaniu cookie PTR_MANGLE, możliwe byłoby obejście funkcji PTR_DEMANLE
, ustawiając ją na 0x00, co oznacza, że xor
użyty do uzyskania rzeczywistego adresu to po prostu skonfigurowany adres. Następnie, pisząc na dtor_list
, możliwe jest łańcuchowe wywoływanie kilku funkcji z adresem funkcji i jej argumentem.
Na koniec zauważ, że przechowywany wskaźnik nie tylko będzie xorowany z cookie, ale także obracany o 17 bitów:
Należy wziąć to pod uwagę przed dodaniem nowego adresu.
Znajdź przykład w oryginalnym poście.
Inne zmodyfikowane wskaźniki w __run_exit_handlers
__run_exit_handlers
Ta technika jest wyjaśniona tutaj i ponownie zależy od programu zakończającego działanie za pomocą return
lub exit()
, aby zostało wywołane __run_exit_handlers()
.
Sprawdźmy więcej kodu tej funkcji:
Zmienna f
wskazuje na strukturę initial
i w zależności od wartości f->flavor
zostaną wywołane różne funkcje.
W zależności od wartości, adres funkcji do wywołania będzie w innym miejscu, ale zawsze będzie rozwiązany.
Co więcej, w opcjach ef_on
i ef_cxa
można również kontrolować argument.
Możliwe jest sprawdzenie struktury initial
w sesji debugowania z uruchomionym GEF za pomocą gef> p initial
.
Aby to wykorzystać, należy albo ujawnić lub usunąć ciasteczko PTR_MANGLE
a następnie nadpisać wpis cxa
w initial wartością system('/bin/sh')
.
Przykład tego można znaleźć w oryginalnym wpisie na blogu dotyczącym tej techniki.
Last updated