eixt()的分析
作者:互联网
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()
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
- stderr
- _IO_flush_all_lockp4
- _IO_cleanup
- fcloseall
- __run_exit_handlers
参考链接
- exit()分析与利用 - 安全客,安全资讯平台 (anquanke.com)
- [原创] House of apple 一种新的glibc中IO攻击方法 (1)-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com
- IS-pwn-Linux-glibc-exit函数 | w4rd3n的笔记 (hitworld.github.io)
标签:分析,__,function,void,ef,eixt,exit,IO 来源: https://www.cnblogs.com/7resp4ss/p/16677930.html