Atexit Exploitation Guide
This skill helps you exploit arbitrary write vulnerabilities by hijacking exit handlers to achieve code execution when a program terminates via return or exit().
When to Use This Skill
Use this skill when:
- You have an arbitrary write primitive and need code execution
- The target program exits via
returnorexit()(not_exit()) - You're analyzing binaries for atexit-based vulnerabilities
- You need to understand PTR_MANGLE/PTR_DEMANGLE pointer obfuscation
- You're working with link_map, TLS dtor_list, or exit_function_list structures
Core Concepts
Exit Handler Execution Flow
When a program exits via return or exit():
__run_exit_handlers()is called- TLS destructors are executed via
__call_tls_dtors() - atexit/on_exit registered functions are called
- DT_FINI_ARRAY destructors are executed
Critical: If the program exits via _exit() syscall, exit handlers are NOT executed. Always verify with a breakpoint on __run_exit_handlers().
Pointer Mangling (x86/x64)
On x86/x64, function pointers in exit handlers are obfuscated:
- XORed with a random
PTR_MANGLEcookie - Rotated 17 bits right
mov rax, QWORD PTR [rbx] ; load mangled ptr
ror rax, 0x11 ; rotate right 17 bits
xor rax, QWORD PTR fs:0x30 ; XOR with PTR_MANGLE cookie
Other architectures (m68k, mips32, mips64, aarch64, arm, hppa) do NOT implement mangling - the pointer is used as-is.
Exploitation Techniques
Technique 1: link_map DT_FINI_ARRAY Overwrite
The link_map structure contains l_info[DT_FINI_ARRAY] which points to an array of destructor functions.
Attack vectors:
Fake fini_array: Overwrite
l_info[DT_FINI_ARRAY]to point to a fakeElf64_Dynstructure in controlled memory (e.g.,.bss)- The fake structure's
d_un.d_ptrshould point to your one_gadget address - Account for
map->l_addroffset in calculations
- The fake structure's
Stack pointer overwrite: ld.so leaves a pointer to
link_mapon the stack. Overwrite it to point to a fake fini_array containing your one_gadget.
Structure layout:
struct Elf64_Dyn {
Elf64_Sxval d_tag; // DT_FINI_ARRAY = 0x1e
union {
Elf64_Xword d_val; // function address (one_gadget)
Elf64_Addr d_ptr; // offset from l_addr
} d_un;
};
Technique 2: TLS dtor_list Overwrite
The tls_dtor_list is a linked list of destructor functions stored near the stack canary.
Structure:
struct dtor_list {
dtor_func func; // function pointer (mangled)
void *obj; // argument to function
struct link_map *map;
struct dtor_list *next;
};
Exploitation steps:
- Overflow to overwrite the PTR_MANGLE cookie with 0x00 (bypasses XOR)
- Overwrite the stack canary
- Chain multiple dtor_list entries with your function addresses
- Account for 17-bit rotation when calculating mangled pointers
Mangled pointer calculation:
def mangle_ptr(addr, cookie):
# Rotate right 17 bits, then XOR with cookie
rotated = ((addr >> 17) | (addr << (64 - 17))) & 0xffffffffffffffff
return rotated ^ cookie
def demangle_ptr(mangled, cookie):
# XOR with cookie, then rotate left 17 bits
xored = mangled ^ cookie
return ((xored << 17) | (xored >> (64 - 17))) & 0xffffffffffffffff
Technique 3: exit_function_list Overwrite
The initial structure contains an array of exit functions with different flavors:
struct exit_function {
enum exit_function_flavor flavor;
union {
void (*at) (void); // ef_at
void (*on) (int, void *); // ef_on (with arg)
void (*cxa) (void *, int); // ef_cxa (with arg)
} func;
};
Flavors:
ef_at: atexit() registered function, no argumentsef_on: on_exit() registered function, takes (status, arg)ef_cxa: C++ destructor, takes (arg, status)
Exploitation:
- Leak or erase the PTR_MANGLE cookie
- Overwrite a
cxaoronentry withsystemand/bin/shas argument - The function will be demangled and called during exit
Practical Workflow
Step 1: Verify Exit Handler Execution
# Set breakpoint on exit handler
break __run_exit_handlers
# Run program and verify breakpoint is hit
run
If breakpoint is NOT hit, the program uses _exit() and these techniques won't work.
Step 2: Locate Target Structures
# Find link_map (usually in ld.so)
info proc mappings | grep ld.so
# Find TLS dtor_list
gef> tls
# Find initial structure
gef> p initial
Step 3: Calculate Mangled Pointers
Use the calculate_mangled_pointer.py script (see scripts/) to compute the correct mangled values for your target addresses.
Step 4: Craft the Exploit
- For link_map: Create fake Elf64_Dyn structure with one_gadget
- For TLS: Chain dtor_list entries with mangled function pointers
- For exit_function_list: Overwrite flavor and function pointer fields
Step 5: Trigger Exit
Ensure the program exits via return or exit(), not _exit() or abort().
Architecture Considerations
| Architecture | PTR_MANGLE | Exploitation Difficulty |
|---|---|---|
| x86/x64 | Yes | Harder (need cookie) |
| aarch64 | No | Easier (direct ptr) |
| arm | No | Easier (direct ptr) |
| mips32/64 | No | Easier (direct ptr) |
| m68k | No | Easier (direct ptr) |
| hppa | No | Easier (direct ptr) |
Common Pitfalls
- Program uses _exit(): Exit handlers won't run. Check with GDB breakpoint.
- Wrong mangling calculation: Remember 17-bit rotation + XOR, not just XOR.
- ASLR: Need to leak libc base to calculate one_gadget addresses.
- Stack canary: TLS technique requires overwriting canary first.
- Architecture mismatch: Mangling only applies to x86/x64.
References
- Notes on Abusing Exit Handlers
- Code Execution on Last libc
- DanteCTF 2023 - Sentence To Hell
- Angstrom CTF 2021 - Wallstreet
Scripts
See scripts/ directory for helper tools:
calculate_mangled_pointer.py- Compute mangled/demangled pointersgenerate_fake_structures.py- Create fake Elf64_Dyn and dtor_list structuresfind_exit_handlers.py- Analyze binaries for exit handler vulnerabilities