WWW2Exec - atexit(), TLS Berging & Ander gemanipuleerde Pointers
Reading time: 9 minutes
tip
Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
__atexit Strukture
caution
Vandag is dit baie vreemd om dit te benut!
atexit()
is 'n funksie waaraan ander funksies as parameters deurgegee word. Hierdie funksies sal uitgevoer word wanneer 'n exit()
of die terugkeer van die main uitgevoer word.
As jy die adres van enige van hierdie funksies kan wysig om na 'n shellcode te verwys, sal jy beheer oor die proses verkry, maar dit is tans meer ingewikkeld.
Tans is die adresse na die funksies wat uitgevoer moet word versteek agter verskeie strukture en uiteindelik is die adres waaraan dit verwys nie die adresse van die funksies nie, maar is geënkripteer met XOR en verskuiwings met 'n willekeurige sleutel. So tans is hierdie aanvalsvector nie baie nuttig nie, ten minste op x86 en x64_86.
Die enkripsiefunksie is PTR_MANGLE
. Ander argitekture soos m68k, mips32, mips64, aarch64, arm, hppa... implementeer nie die enkripsie funksie nie omdat dit diezelfde teruggee as wat dit as invoer ontvang. So hierdie argitekture sou deur hierdie vektor aangeval kon word.
Jy kan 'n diepgaande verduideliking vind oor hoe dit werk in https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html
link_map
Soos verduidelik in hierdie pos, As die program verlaat deur return
of exit()
sal dit __run_exit_handlers()
uitvoer wat geregistreerde vernietigers sal aanroep.
caution
As die program verlaat via _exit()
funksie, sal dit die exit
syscall aanroep en die uitgangshandelaars sal nie uitgevoer word nie. So, om te bevestig dat __run_exit_handlers()
uitgevoer word, kan jy 'n breekpunt daarop stel.
Die belangrike kode is (source):
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
}
Let op hoe map -> l_addr + fini_array -> d_un.d_ptr
gebruik word om die posisie van die array van funksies om aan te roep te bereken.
Daar is 'n paar opsies:
- Oorskryf die waarde van
map->l_addr
om dit na 'n valsefini_array
met instruksies om arbitrêre kode uit te voer, te laat wys. - Oorskryf
l_info[DT_FINI_ARRAY]
enl_info[DT_FINI_ARRAYSZ]
inskrywings (wat meer of minder aaneengeskakeld in geheue is), om hulle na 'n vervalsteElf64_Dyn
struktuur te laat wys wat weerarray
na 'n geheue gebied sal laat wys wat die aanvaller beheer. - Hierdie skrywe oorskryf
l_info[DT_FINI_ARRAY]
met die adres van 'n beheerde geheue in.bss
wat 'n valsefini_array
bevat. Hierdie valse array bevat eers 'n one gadget adres wat uitgevoer sal word en dan die verskil tussen die adres van hierdie valse array en die waarde vanmap->l_addr
sodat*array
na die valse array sal wys. - Volgens die hoofpos van hierdie tegniek en hierdie skrywe laat ld.so 'n wysser op die stapel wat na die binêre
link_map
in ld.so wys. Met 'n arbitrêre skrywe is dit moontlik om dit oor te skryf en dit na 'n valsefini_array
te laat wys wat deur die aanvaller beheer word met die adres na 'n one gadget byvoorbeeld.
Volg 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 sal dit moontlik wees om die waarde van map->l_info[DT_FINI]
te oorskryf wat na 'n vervalste ElfW(Dyn)
struktuur wys. Vind meer inligting hier.
TLS-Storage dtor_list oorskrywing in __run_exit_handlers
Soos hier verduidelik, as 'n program verlaat via return
of exit()
, sal dit __run_exit_handlers()
uitvoer wat enige geregistreerde vernietigersfunksies 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 aanroep met die argument cur->obj
.
Met die tls
funksie van hierdie fork van GEF, is dit moontlik om te sien dat die dtor_list
eintlik baie naby die stack canary en PTR_MANGLE cookie is. So, met 'n oorgang op dit, sou dit moontlik wees om die cookie en die stack canary te oorwrite.
Deur die PTR_MANGLE cookie te oorskry, sou dit moontlik wees om die PTR_DEMANLE
funksie te bypass deur dit op 0x00 te stel, wat beteken dat die xor
wat gebruik word om die werklike adres te kry net die adres is wat geconfigureer is. Dan, deur op die dtor_list
te skryf, is dit moontlik om verskeie funksies te ketting met die funksie adres en sy argument.
Laastens, let daarop dat die gestoor pointer nie net met die cookie xored gaan word nie, maar ook 17 bits gedraai sal 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 dit in ag neem voordat jy 'n nuwe adres byvoeg.
Vind 'n voorbeeld in die oorspronklike pos.
Ander gemanipuleerde punte in __run_exit_handlers
Hierdie tegniek is hier verduidelik en hang weer af van die program wat verlaat deur return
of exit()
aan te roep sodat __run_exit_handlers()
aangeroep word.
Kom ons kyk na meer kode van hierdie funksie:
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
verwys na die initial
struktuur en afhangende van die waarde van f->flavor
sal verskillende funksies aangeroep word.
Afhangende van die waarde, sal die adres van die funksie om aan te roep in 'n ander plek wees, maar dit sal altyd demangled wees.
Boonop is dit in die opsies ef_on
en ef_cxa
ook moontlik om 'n argument te beheer.
Dit is moontlik om die initial
struktuur in 'n foutopsporing sessie met GEF wat gef> p initial
loop, te kontroleer.
Om dit te misbruik, moet jy ofwel die leak of die PTR_MANGLE
cookie verwyder en dan 'n cxa
inskrywing in initial met system('/bin/sh')
oorskryf.
Jy kan 'n voorbeeld hiervan in die oorspronklike blogpos oor die tegniek vind.
tip
Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.