WWW2Exec - .dtors & .fini_array

Leer AWS-hacking vanaf nul tot held met htARTE (HackTricks AWS Red Team Expert)!

Ander maniere om HackTricks te ondersteun:

.dtors

Teenwoordig is dit baie vreemd om 'n binaêre lêer met 'n .dtors-seksie te vind!

Die destruktore is funksies wat uitgevoer word voordat die program eindig (nadat die main-funksie terugkeer). Die adresse van hierdie funksies word gestoor binne die .dtors-afdeling van die binaêre lêer en daarom, as jy daarin slaag om die adres van 'n shell-kode in __DTOR_END__ te skryf, sal dit uitgevoer word voordat die programme eindig.

Kry die adres van hierdie afdeling met:

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

Gewoonlik sal jy die DTOR merkers tussen die waardes ffffffff en 00000000 vind. So as jy net daardie waardes sien, beteken dit dat daar geen funksie geregistreer is nie. Oorskryf dus die 00000000 met die adres van die shellcode om dit uit te voer.

Natuurlik moet jy eers 'n plek vind om die shellcode te stoor sodat jy dit later kan aanroep.

.fini_array

Essensieel is dit 'n struktuur met funksies wat geroep sal word voordat die program eindig, soos .dtors. Dit is interessant as jy jou shellcode kan roep deur net na 'n adres te spring, of in gevalle waar jy terug moet gaan na main om die kwesbaarheid 'n tweede keer te benut.

objdump -s -j .fini_array ./greeting

./greeting:     file format elf32-i386

Contents of section .fini_array:
8049934 a0850408

#Put your address in 0x8049934

Merk op dat wanneer 'n funksie van die .fini_array uitgevoer word, beweeg dit na die volgende een, sodat dit nie verskeie kere uitgevoer sal word (om ewige lusse te voorkom nie), maar dit sal jou ook slegs 1 uitvoering van die funksie hier gee.

Merk op dat inskrywings in .fini_array in omgekeerde volgorde genoem word, so jy wil waarskynlik begin skryf van die laaste een af.

Ewige lus

Om .fini_array te misbruik om 'n ewige lus te kry, kan jy kyk wat hier gedoen is: As jy ten minste 2 inskrywings in .fini_array het, kan jy:

  • Gebruik jou eerste skryf om die kwesbare willekeurige skryffunksie weer te roep

  • Bereken dan die terugkeeradres in die stapel wat deur __libc_csu_fini gestoor word (die funksie wat al die .fini_array-funksies roep) en sit daar die adres van __libc_csu_fini

  • Dit sal maak dat __libc_csu_fini homself weer roep deur die .fini_array-funksies weer uit te voer wat die kwesbare WWW-funksie 2 keer sal roep: een vir willekeurige skryf en nog een om weer die terugkeeradres van __libc_csu_fini op die stapel te oorskryf om homself weer te roep.

Merk op dat met Volle RELRO, die afdeling .fini_array leesbaar gemaak word.

Soos verduidelik in hierdie pos, As die program eindig deur return of exit() te gebruik, sal dit __run_exit_handlers() hardloop wat geregistreerde vernietigers sal roep.

As die program eindig via _exit()-funksie, sal dit die exit-systeemaanroep roep en die uittreehanteraars sal nie uitgevoer word nie. Om te bevestig dat __run_exit_handlers() uitgevoer word, kan jy 'n breekpunt daarop instel.

Die belangrike kode is (bron):

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
}

Merk op hoe map -> l_addr + fini_array -> d_un.d_ptr gebruik word om die posisie van die array van funksies om te roep te bereken.

Daar is 'n paar opsies:

  • Oorskryf die waarde van map->l_addr om dit te laat wys na 'n vals fini_array met instruksies om willekeurige kode uit te voer

  • Oorskryf l_info[DT_FINI_ARRAY] en l_info[DT_FINI_ARRAYSZ] inskrywings (wat min of meer opeenvolgend in die geheue is), om hulle te laat wys na 'n vervalsde Elf64_Dyn struktuur wat weer array laat wys na 'n geheue sonde wat deur die aanvaller beheer word.

  • Hierdie uiteensetting oorskryf l_info[DT_FINI_ARRAY] met die adres van 'n beheerde geheue in .bss wat 'n vals fini_array bevat. Hierdie valse array bevat eerstens 'n een-gadget adres wat uitgevoer sal word en dan die verskil tussen die adres van hierdie vals array en die waarde van map->l_addr sodat *array na die valse array wys.

  • Volgens die hoofpos van hierdie tegniek en hierdie uiteensetting laat ld.so 'n aanwysing op die stapel wat wys na die binêre link_map in ld.so. Met 'n willekeurige skryf is dit moontlik om dit te oorskryf en dit te laat wys na 'n vals fini_array wat deur die aanvaller beheer word met die adres van 'n een-gadget byvoorbeeld.

Na die vorige kode kan jy 'n ander interessante afdeling met die kode vind:

/* 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));
}

In hierdie geval sou dit moontlik wees om die waarde van map->l_info[DT_FINI] te oorskryf wat na 'n vervalsde ElfW(Dyn) struktuur wys. Vind meer inligting hier.

TLS-Stoor dtor_list oorskrywing in __run_exit_handlers

Soos hier verduidelik, as 'n program afsluit via return of exit(), sal dit __run_exit_handlers() uitvoer wat enige vernietigersfunksie wat geregistreer is, sal aanroep.

Kode van _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 ();

Kode van __call_tls_dtors():

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);
[...]
}
}

Vir elke geregistreerde funksie in tls_dtor_list, sal dit die pointer van cur->func demangle en dit oproep met die argument cur->obj.

Deur die tls funksie van hierdie fork van GEF te gebruik, is dit moontlik om te sien dat die dtor_list baie naby aan die stapel kanarie en PTR_MANGLE koekie is. Dus, met 'n oorvloei daarop sou dit moontlik wees om die koekie en die stapel kanarie te owerwrite. Deur die PTR_MANGLE koekie te oorskryf, sou dit moontlik wees om die PTR_DEMANLE funksie te omseil deur dit na 0x00 in te stel, wat beteken dat die xor wat gebruik word om die werklike adres te kry, net die gekonfigureerde adres is. Dan, deur op die dtor_list te skryf, is dit moontlik om verskeie funksies aan mekaar te ketting met die funksie adres en sy argument.

Laastens, let daarop dat die gestoorde pointer nie net met die koekie ge-xor nie, maar ook 17 bietjies geroteer word:

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

So jy moet hierdie in ag neem voordat jy 'n nuwe adres byvoeg.

Vind 'n voorbeeld in die oorspronklike pos.

Ander verminkte aanwysers in __run_exit_handlers

Hierdie tegniek word hier verduidelik en hang weer af van die program wat afsluit deur return of exit() te roep sodat __run_exit_handlers() geroep word.

Laat ons meer kode van hierdie funksie nagaan:

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);

Die veranderlike f wys na die initial struktuur en afhangende van die waarde van f->flavor sal verskillende funksies aangeroep word. Afhanklik van die waarde sal die adres van die funksie om te roep op 'n ander plek wees, maar dit sal altyd gedekodeer wees.

Verder, in die opsies ef_on en ef_cxa is dit ook moontlik om 'n argument te beheer.

Dit is moontlik om die initial struktuur in 'n foutopsporing-sessie met GEF te kontroleer deur gef> p initial uit te voer.

Om hiervan misbruik te maak, moet jy ofwel die PTR_MANGLE koekie lek of uitvee en dan 'n cxa inskrywing in initial oorskryf met system('/bin/sh'). Jy kan 'n voorbeeld hiervan vind in die oorspronklike blogpos oor die tegniek.

Last updated