ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

eixt()的分析

2022-09-10 18:04:31  阅读:227  来源: 互联网

标签:分析 __ function void ef eixt exit IO


eixt()的分析

前言

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

概述

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

  • 用户层面:
    • 需要释放libc中的流缓冲区, 退出前清空下stdout的缓冲区, 释放TLS, …
  • 内核层面:
    • 释放掉这个进程打开的文件描述符, 释放掉task结构体, …
    • 再所有资源都被释放完毕后, 内核会从调度队列从取出这个任务
    • 然后向父进程发送一个信号, 表示有一个子进程终止
    • 此时这个进程才算是真正结束

因此我们可以认为:

  • 进程终止 => 释放其所占有的资源 + 不再分配CPU时间给这个进程

内核层面的终止是通过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()进行系统级别的析构

分析环境

  • OS : ubuntu 20.04
  • 使用的分析程序:VSDODE
  • 分析的glibc版本为2.27

代码审计

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()

  • 其中有一个重要的数据结构:__exit_funcs, 是一个指针, 指向 / 保存析构函数的数组链表/, 其定义如下

    static struct exit_function_list initial;           //initial定义在libc的可写入段中
    struct exit_function_list *__exit_funcs = &initial; //exit函数链表
    
  • exit_function_list结构体定义, 里面保存了多个析构的函数的描述:

struct exit_function_list
{
  struct exit_function_list *next; //单链表, 指向下一个exit_function_list结构体
  size_t idx;                      //记录有多少个函数
  struct exit_function fns[32];    //析构函数数组
};
  • struct exit_function是对单个析构函数的描述, 可以描述多种析构函数类型

    //描述单个析构函数的结构体
    struct exit_function
    {
    long int flavor; 
    /*
       函数类型, 可以是{ef_free, ef_us, ef_on, ef_at, ef_cxa}
            - ef_free表示此位置空闲
           - ef_us表示此位置被使用中, 但是函数类型不知道
           - ef_on, ef_at, ef_cxa 分别对应三种不同的析构函数类型, 主要是参数上的差异
    */
    
    union            //多个种函数类型中只会有一个有用, 所以是联合体
    {
    void (*at)(void); //ef_at类型 没有参数
    struct
    {
     void (*fn)(int status, void *arg);
     void *arg;
    } on; //ef_on类型
    struct
    {
     void (*fn)(void *arg, int status);
     void *arg;
     void *dso_handle;
    } cxa; //ef_cxa类型
    } func;
    };
    

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

  • 对于__libc_atexit

    其实__libc_atexit其实是libc中的一个段,这个段中就是libc退出的析构函数,其中默认只有一个函数fcloseall()

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;
}
  • _IO_cleanup()会调用两个函数
    • _IO_flush_all_lockp()会通过_IO_list_all遍历所有流, 对每个流调用_IO_OVERFLOW(fp), 保证关闭前缓冲器中没有数据残留
    • _IO_unbuffer_all()会通过_IO_list_all遍历所有流, 对每个流调用_IO_SETBUF(fp, NULL, 0), 来释放流的缓冲区

总结

函数调用链为

  • exit
    • __run_exit_handlers
      • fcloseall
        • _IO_cleanup
          • _IO_flush_all_lockp4
            • stderr
              • _IO_OVERFLOW(fp) / stderr + 0xd8

参考链接

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有