As said in its manpage:
atexit()function registers the given function to be called at program exit, whether via
exit(3)or via return from the program’s
main(). Functions so registered are called in reverse order; no arguments are passed. At least 32 functions can always be registered, and more are allowed as long as sufficient memory can be allocated.
In the glibc, the
pointers to the function are obfuscated with a
xor via the
macro against a secret, which is roughly equivalent to what Windows is
This mitigation is completely bypassed with an
arbitrary read: get the secret, obfuscate the pointer to your payload, done.
the pointers are stored in a read-only memory zone, only made writeable when
__cxa_atexit is called. To bypass this, an attacker would need to get code
execution to modify the permissions of the memory zone.
It was added in 2002 by Daniel Hartmeier, with help for Theo de Raadt. It’s a pretty cool mitigation, completely killing this vector.