| name | atexit-exploitation |
| description | Guide for exploiting arbitrary write vulnerabilities through atexit handlers, link_map manipulation, and TLS dtor_list overwrites. Use this skill whenever the user mentions atexit, exit handlers, link_map, TLS destructors, PTR_MANGLE, __run_exit_handlers, or needs to convert an arbitrary write primitive into code execution via program exit. Also use when analyzing binaries for exit handler vulnerabilities or crafting exploits that trigger code execution on program termination. |
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
return or exit() (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_MANGLE cookie
- 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 fake Elf64_Dyn structure in controlled memory (e.g., .bss)
- The fake structure's
d_un.d_ptr should point to your one_gadget address
- Account for
map->l_addr offset in calculations
-
Stack pointer overwrite: ld.so leaves a pointer to link_map on the stack. Overwrite it to point to a fake fini_array containing your one_gadget.
Structure layout:
struct Elf64_Dyn {
Elf64_Sxval d_tag;
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} 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;
void *obj;
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):
rotated = ((addr >> 17) | (addr << (64 - 17))) & 0xffffffffffffffff
return rotated ^ cookie
def demangle_ptr(mangled, cookie):
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);
void (*on) (int, void *);
void (*cxa) (void *, int);
} func;
};
Flavors:
ef_at: atexit() registered function, no arguments
ef_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
cxa or on entry with system and /bin/sh as 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
Scripts
See scripts/ directory for helper tools:
calculate_mangled_pointer.py - Compute mangled/demangled pointers
generate_fake_structures.py - Create fake Elf64_Dyn and dtor_list structures
find_exit_handlers.py - Analyze binaries for exit handler vulnerabilities