WWW2Exec - .dtors & .fini_array

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

Drugi načini podrške HackTricks-u:

.dtors

Danas je veoma čudno naći binarni fajl sa .dtors sekcijom!

Destruktori su funkcije koje se izvršavaju pre završetka programa (nakon što se funkcija main završi). Adrese ovih funkcija se čuvaju unutar .dtors sekcije binarnog fajla i stoga, ako uspete da upišete adresu shell koda u __DTOR_END__, taj će se izvršiti pre nego što program završi.

Dobijte adresu ove sekcije sa:

objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”

Obično ćete pronaći DTOR markere između vrednosti ffffffff i 00000000. Dakle, ako vidite samo te vrednosti, to znači da nema registrovane funkcije. Zato prepišite 00000000 sa adresom shell koda kako biste ga izvršili.

Naravno, prvo morate pronaći mesto za čuvanje shell koda kako biste ga kasnije mogli pozvati.

.fini_array

Essentially this is a structure with functions that will be called before the program finishes, like .dtors. This is interesting if you can call your shellcode just jumping to an address, or in cases where you need to go back to main again to exploit the vulnerability a second time.

objdump -s -j .fini_array ./greeting

./greeting:     file format elf32-i386

Contents of section .fini_array:
8049934 a0850408

#Put your address in 0x8049934

Napomena da kada se izvrši funkcija iz .fini_array prelazi se na sledeću, tako da neće biti izvršena više puta (sprječavajući večne petlje), ali će vam dati samo 1 izvršenje funkcije postavljene ovde.

Napomena da se unosi u .fini_array nazivaju u obrnutom redosledu, pa verovatno želite da počnete sa pisanjem od poslednjeg.

Večna petlja

Da biste zloupotrebili .fini_array kako biste dobili večnu petlju, možete proveriti šta je urađeno ovde: Ako imate najmanje 2 unosa u .fini_array, možete:

  • Koristite svoj prvi zapis da ponovo pozovete ranjivu funkciju za proizvoljno pisanje

  • Zatim, izračunajte povratnu adresu na steku koju čuva __libc_csu_fini (funkcija koja poziva sve funkcije .fini_array) i stavite tamo adresu __libc_csu_fini

  • To će naterati __libc_csu_fini da pozove sam sebe ponovo izvršavajući ponovo funkcije .fini_array koje će pozvati ranjivu WWW funkciju 2 puta: jednom za proizvoljno pisanje i još jednom da ponovo prepiše povratnu adresu __libc_csu_fini na steku da bi se ponovo pozvao.

Napomena da sa Full RELRO, odeljak .fini_array je postavljen kao samo za čitanje.

Kao što je objašnjeno u ovom postu, ako program završi korišćenjem return ili exit(), pokrenuće se __run_exit_handlers() koji će pozvati registrovane destruktore.

Ako program izađe putem funkcije _exit(), pozvaće se exit syscall i hendleri za izlaz neće biti izvršeni. Dakle, da biste potvrdili da se __run_exit_handlers() izvršava, možete postaviti prekidnu tačku na nju.

Važan kod je (izvor):

ElfW(Dyn) *fini_array = map->l_info[DT_FINI_ARRAY];
if (fini_array != NULL)
{
ElfW(Addr) *array = (ElfW(Addr) *) (map->l_addr + fini_array->d_un.d_ptr);
size_t sz = (map->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)));

while (sz-- > 0)
((fini_t) array[sz]) ();
}
[...]




// This is the d_un structure
ptype l->l_info[DT_FINI_ARRAY]->d_un
type = union {
Elf64_Xword d_val;	// address of function that will be called, we put our onegadget here
Elf64_Addr d_ptr;	// offset from l->l_addr of our structure
}

Primetite kako se map -> l_addr + fini_array -> d_un.d_ptr koristi za izračunavanje pozicije niza funkcija za pozivanje.

Postoje nekoliko opcija:

  • Prepisati vrednost map->l_addr kako bi pokazivala na lažni fini_array sa instrukcijama za izvršavanje proizvoljnog koda

  • Prepisati unose l_info[DT_FINI_ARRAY] i l_info[DT_FINI_ARRAYSZ] (koji su više-manje uzastopni u memoriji), kako bi pokazivali na lažnu strukturu Elf64_Dyn koja će ponovo naterati array da pokazuje na memoriju kojom upravlja napadač.

  • Ovaj opis prepisuje l_info[DT_FINI_ARRAY] sa adresom kontrolisane memorije u .bss koja sadrži lažni fini_array. Ovaj lažni niz sadrži prvo adresu jednog alata koja će biti izvršena, a zatim razliku između adrese ovog lažnog niza i vrednosti map->l_addr tako da će *array pokazivati na lažni niz.

  • Prema glavnoj objavi ove tehnike i ovom opisu ld.so ostavlja pokazivač na steku koji pokazuje na binarni link_map u ld.so. Pomoću proizvoljnog pisanja moguće je prepisati ga i naterati da pokazuje na lažni fini_array koji kontroliše napadač sa adresom jednog alata na primer.

Nakon prethodnog koda možete pronaći još jedan interesantan odeljak sa kodom:

/* Next try the old-style destructor.  */
ElfW(Dyn) *fini = map->l_info[DT_FINI];
if (fini != NULL)
DL_CALL_DT_FINI (map, ((void *) map->l_addr + fini->d_un.d_ptr));
}

U ovom slučaju bilo bi moguće prebrisati vrednost map->l_info[DT_FINI] koja pokazuje na lažiranu strukturu ElfW(Dyn). Pronađite više informacija ovde.

Prepisivanje TLS-Storage dtor_list-a u __run_exit_handlers

Kao što je objašnjeno ovde, ako program završi putem return ili exit(), izvršiće se __run_exit_handlers() koji će pozvati sve registrovane funkcije destruktora.

Kod iz _run_exit_handlers():

/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered
perform stdio cleanup, and terminate program execution with STATUS.  */
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
/* First, call the TLS destructors.  */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
if (run_dtors)
__call_tls_dtors ();

Kod iz __call_tls_dtors() funkcije:

typedef void (*dtor_func) (void *);
struct dtor_list //struct added
{
dtor_func func;
void *obj;
struct link_map *map;
struct dtor_list *next;
};

[...]
/* Call the destructors.  This is called either when a thread returns from the
initial function or when the process exits via the exit function.  */
void
__call_tls_dtors (void)
{
while (tls_dtor_list)		// parse the dtor_list chained structures
{
struct dtor_list *cur = tls_dtor_list;		// cur point to tls-storage dtor_list
dtor_func func = cur->func;
PTR_DEMANGLE (func);						// demangle the function ptr

tls_dtor_list = tls_dtor_list->next;		// next dtor_list structure
func (cur->obj);
[...]
}
}

Za svaku registrovanu funkciju u tls_dtor_list, demantovace pokazivac iz cur->func i pozvace je sa argumentom cur->obj.

Koriscenjem tls funkcije iz ovog forka GEF-a, moguce je videti da je dtor_list veoma blizu stack canary-ja i PTR_MANGLE cookie-ja. Dakle, prelivanjem preko njega bilo bi moguce prepisati cookie i stack canary. Prelivanjem PTR_MANGLE cookie-ja, bilo bi moguce zaobici funkciju PTR_DEMANLE postavljanjem je na 0x00, sto znaci da je xor koji se koristi za dobijanje prave adrese samo adresa koja je konfigurisana. Zatim, pisanjem na dtor_list moguce je povezati nekoliko funkcija sa funkcijom adrese i njenim argumentom.

Na kraju primetite da sacuvani pokazivac nece biti samo ekskluzivno xorovan sa cookie-jem vec i rotiran za 17 bitova:

0x00007fc390444dd4 <+36>:	mov    rax,QWORD PTR [rbx]      --> mangled ptr
0x00007fc390444dd7 <+39>:	ror    rax,0x11		        --> rotate of 17 bits
0x00007fc390444ddb <+43>:	xor    rax,QWORD PTR fs:0x30	--> xor with PTR_MANGLE

Dakle, morate uzeti ovo u obzir pre dodavanja nove adrese.

Pronađite primer u originalnom postu.

Ostali izmenjeni pokazivači u __run_exit_handlers

Ova tehnika je objašnjena ovde i ponovo zavisi od programa koji izlazi pozivajući return ili exit() tako da se poziva __run_exit_handlers().

Pogledajmo više koda ove funkcije:

while (true)
{
struct exit_function_list *cur;

restart:
cur = *listp;

if (cur == NULL)
{
/* Exit processing complete.  We will not allow any more
atexit/on_exit registrations.  */
__exit_funcs_done = true;
break;
}

while (cur->idx > 0)
{
struct exit_function *const f = &cur->fns[--cur->idx];
const uint64_t new_exitfn_called = __new_exitfn_called;

switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);
void *arg;

case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
arg = f->func.on.arg;
PTR_DEMANGLE (onfct);

/* Unlock the list while we call a foreign function.  */
__libc_lock_unlock (__exit_funcs_lock);
onfct (status, arg);
__libc_lock_lock (__exit_funcs_lock);
break;
case ef_at:
atfct = f->func.at;
PTR_DEMANGLE (atfct);

/* Unlock the list while we call a foreign function.  */
__libc_lock_unlock (__exit_funcs_lock);
atfct ();
__libc_lock_lock (__exit_funcs_lock);
break;
case ef_cxa:
/* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
we must mark this function as ef_free.  */
f->flavor = ef_free;
cxafct = f->func.cxa.fn;
arg = f->func.cxa.arg;
PTR_DEMANGLE (cxafct);

/* Unlock the list while we call a foreign function.  */
__libc_lock_unlock (__exit_funcs_lock);
cxafct (arg, status);
__libc_lock_lock (__exit_funcs_lock);
break;
}

if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
/* The last exit function, or another thread, has registered
more exit functions.  Start the loop over.  */
goto restart;
}

*listp = cur->next;
if (*listp != NULL)
/* Don't free the last element in the chain, this is the statically
allocate element.  */
free (cur);
}

__libc_lock_unlock (__exit_funcs_lock);

Promenljiva f pokazuje na strukturu initial i u zavisnosti od vrednosti f->flavor biće pozvane različite funkcije. Adresa funkcije koja će biti pozvana biće na različitom mestu u zavisnosti od vrednosti, ali će uvek biti demangleovana.

Osim toga, u opcijama ef_on i ef_cxa takođe je moguće kontrolisati argument.

Moguće je proveriti strukturu initial u sesiji za debagovanje sa GEF pokretanjem gef> p initial.

Da biste iskoristili ovo, potrebno je ili procureti ili izbrisati PTR_MANGLE kolačić a zatim prepisati cxa unos u initial sa system('/bin/sh'). Primer ovoga možete pronaći u originalnom blog postu o tehnici.

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

Drugi načini podrške HackTricks-u:

Last updated