其他分享
首页 > 其他分享> > eixt()的分析

eixt()的分析

作者:互联网

eixt()的分析

前言

​ 本人在学习IO_file结构体攻击时,发现对FSOP调用链还是不了解,于是总结出该文章。

概述

main()函数return时, 有一些析构工作需要完成

因此我们可以认为:

内核层面的终止是通过exit系统调用来进行的,其实现就是一个syscall , libc中声明为

#include <unistd.h> 
void _exit(int status);

但是如果直接调用_exit(), 会出现一些问题, 比如stdout的缓冲区中的数据会直接被内核释放掉, 无法刷新, 导致信息丢失

因此在调用_exit()之前, 还需要在用户层面进行一些析构工作

libc将负责这个工作的函数定义为exit(), 其声明如下

#include <stdlib.h> 
extern void exit (int __status);

因为我们可以认为:

分析环境

代码审计

exit()

//exit函数位于stdlib.c
void
exit (int status)
{
  __run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)

可以看到exit ()调用 __run_exit_handlers (status, &__exit_funcs, true, true);

__run_exit_handlers()

struct exit_function_list
{
  struct exit_function_list *next; //单链表, 指向下一个exit_function_list结构体
  size_t idx;                      //记录有多少个函数
  struct exit_function fns[32];    //析构函数数组
};

run_exit_handlers()的主要工作就是调用exit_funcs中保存的各种函数指针

//位于stdlib.c
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
		     bool run_list_atexit, bool run_dtors)
{
  /* First, call the TLS destructors.  */
#ifndef SHARED
    //this operate is make the TLS be NULL,maybe to be init?
  if (&__call_tls_dtors != NULL)
#endif
    if (run_dtors)
      __call_tls_dtors ();

  /* We do it this way to handle recursive calls to exit () made by
     the functions registered with `atexit' and `on_exit'. We call
     everyone on the list and use the status value in the last
     exit (). */
  while (true)
    {
      struct exit_function_list *cur;

      __libc_lock_lock (__exit_funcs_lock);

    restart:
      cur = *listp;

      if (cur == NULL)
	{
	  /* Exit processing complete.  We will not allow any more
	     atexit/on_exit registrations.  */
	  __exit_funcs_done = true;
	  __libc_lock_unlock (__exit_funcs_lock);
	  break;
	}

      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;
#ifdef PTR_DEMANGLE
	      PTR_DEMANGLE (onfct);
#endif
	      onfct (status, f->func.on.arg);
	      break;
	    case ef_at:
	      atfct = f->func.at;
#ifdef PTR_DEMANGLE
	      PTR_DEMANGLE (atfct);
#endif
	      atfct ();
	      break;
	    case ef_cxa:
	      /* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
		 we must mark this function as ef_free.  */
	      f->flavor = ef_free;
	      cxafct = f->func.cxa.fn;
#ifdef PTR_DEMANGLE
	      PTR_DEMANGLE (cxafct);
#endif
	      cxafct (f->func.cxa.arg, status);
	      break;
	    }
	  /* Re-lock again before looking at global state.  */
	  __libc_lock_lock (__exit_funcs_lock);

	  if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
	    /* The last exit function, or another thread, has registered
	       more exit functions.  Start the loop over.  */
	    goto restart;
	}

      *listp = cur->next;
      if (*listp != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (cur);

      __libc_lock_unlock (__exit_funcs_lock);
    }

    
    
  //At last, we use the RUN_HOOK to execve __libc_atexit 
  if (run_list_atexit)
    RUN_HOOK (__libc_atexit, ());

  _exit (status);
}

在RUN_HOOK之前的操作为遍历 exit_funcs 链表,我们只需要看最后的RUN_HOOK即可

那么什么是RUN_HOOK呢?

/* Run all the functions hooked on the set called NAME.
   Each function is called like this: `function ARGS'.  */

define RUN_HOOK(NAME, ARGS)						      
do {									      
  void *const *ptr;						      
  for (ptr = (void *const *) symbol_set_first_element (NAME);		 
       ! symbol_set_end_p (NAME, ptr); ++ptr)				      
    (*(__##NAME##_hook_function_t *) *ptr) ARGS;			      
} while (0)

大概就是把ARGS放到NAME

img

fcloseall

本人最初在vscode无法利用_libc_atexit 直接找到fcloseall

然后突发奇想发现可以直接在vscode输入fcloseall然后右键转到定义,就成功找到了fcloseall的源码

//位于libio/fcloseall.c
int
__fcloseall (void)
{
  /* Close all streams.  */
  return _IO_cleanup ();
}

可以看到fcloseall函数主要作用是关闭所有流,主要通过 _IO_cleanup()实现

_IO_cleanup()

//位于liboi/genops.c

int
_IO_cleanup (void)
{
  /* We do *not* want locking.  Some threads might use streams but
     that is their problem, we flush them underneath them.  */
  int result = _IO_flush_all_lockp (0);

  /* We currently don't have a reliable mechanism for making sure that
     C++ static destructors are executed in the correct order.
     So it is possible that other static destructors might want to
     write to cout - and they're supposed to be able to do so.

     The following will make the standard streambufs be unbuffered,
     which forces any output from late destructors to be written out. */
  _IO_unbuffer_all ();

  return result;
}

总结

函数调用链为

参考链接

标签:分析,__,function,void,ef,eixt,exit,IO
来源: https://www.cnblogs.com/7resp4ss/p/16677930.html