WWW2Exec - atexit()
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
__atexit Structures
Nowadays is very weird to exploit this!
atexit() is a function to which other functions are passed as parameters. These functions will be executed when executing an exit() or the return of the main.
If you can modify the address of any of these functions to point to a shellcode for example, you will gain control of the process, but this is currently more complicated.
Currently the addresses to the functions to be executed are hidden behind several structures and finally the address to which it points are not the addresses of the functions, but are encrypted with XOR and displacements with a random key. So currently this attack vector is not very useful at least on x86 and x64_86.
The encryption function is PTR_MANGLE. Other architectures such as m68k, mips32, mips64, aarch64, arm, hppa... do not implement the encryption function because it returns the same as it received as input. So these architectures would be attackable by this vector.
You can find an in depth explanation on how this works in https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html
link_map
As explained in this post, If the program exits using return or exit() it'll run __run_exit_handlers() which will call registered destructors.
If the program exits via _exit() function, it'll call the exit syscall and the exit handlers will not be executed. So, to confirm __run_exit_handlers() is executed you can set a breakpoint on it.
The important code 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
}Note how map -> l_addr + fini_array -> d_un.d_ptr is used to calculate the position of the array of functions to call.
There are a couple of options:
Overwrite the value of
map->l_addrto make it point to a fakefini_arraywith instructions to execute arbitrary codeOverwrite
l_info[DT_FINI_ARRAY]andl_info[DT_FINI_ARRAYSZ]entries (which are more or less consecutive in memory) , to make them points to a forgedElf64_Dynstructure that will make againarraypoints to a memory zone the attacker controlled.This writeup overwrites
l_info[DT_FINI_ARRAY]with the address of a controlled memory in.bsscontaining a fakefini_array. This fake array contains first a one gadget address which will be executed and then the difference between in the address of this fake array and the value ofmap->l_addrso*arraywill point to the fake array.According to main post of this technique and this writeup ld.so leave a pointer on the stack that points to the binary
link_mapin ld.so. With an arbitrary write it's possible to overwrite it and make it point to a fakefini_arraycontrolled by the attacker with the address to a one gadget for example.
Following the previous code you can find another interesting section with the code:
In this case it would be possible to overwrite the value of map->l_info[DT_FINI] pointing to a forged ElfW(Dyn) structure. Find more information here.
TLS-Storage dtor_list overwrite in __run_exit_handlers
__run_exit_handlersAs explained here, if a program exits via return or exit(), it'll execute __run_exit_handlers() which will call any destructors function registered.
Code from _run_exit_handlers():
Code from __call_tls_dtors():
For each registered function in tls_dtor_list, it'll demangle the pointer from cur->func and call it with the argument cur->obj.
Using the tls function from this fork of GEF, it's possible to see that actually the dtor_list is very close to the stack canary and PTR_MANGLE cookie. So, with an overflow on it's it would be possible to overwrite the cookie and the stack canary.
Overwriting the PTR_MANGLE cookie, it would be possible to bypass the PTR_DEMANLE function by setting it to 0x00, will mean that the xor used to get the real address is just the address configured. Then, by writing on the dtor_list it's possible chain several functions with the function address and it's argument.
Finally notice that the stored pointer is not only going to be xored with the cookie but also rotated 17 bits:
So you need to take this into account before adding a new address.
Find an example in the original post.
Other mangled pointers in __run_exit_handlers
__run_exit_handlersThis technique is explained here and depends again on the program exiting calling return or exit() so __run_exit_handlers() is called.
Let's check more code of this function:
The variable f points to the initial structure and depending on the value of f->flavor different functions will be called.
Depending on the value, the address of the function to call will be in a different place, but it'll always be demangled.
Moreover, in the options ef_on and ef_cxa it's also possible to control an argument.
It's possible to check the initial structure in a debugging session with GEF running gef> p initial.
To abuse this you need either to leak or erase the PTR_MANGLEcookie and then overwrite a cxa entry in initial with system('/bin/sh').
You can find an example of this in the original blog post about the technique.
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Last updated