首页
社区
课程
招聘
[原创]EOP编程
发表于: 2024-5-30 21:16 7518

[原创]EOP编程

2024-5-30 21:16
7518

说明:本篇是以前学习时的记录,后期进行过整理,有些地方没有完善,请各位师傅见谅。

一个三环程序是始于链接器,终于终止信号,虽然链接器执行的过程在程序运行前的过程很有意思,但对PWN掉程序而言不如攻击终止程序来的直接。exitlinux常见的终止程序,本人在学习的过程中发现了一些可以利用的地方,并将其记录了下来和各位师傅一起学习。模仿ROP(Return-oriented programming)我将其命名为EOP(Exit-oriented programming)。

江湖流传的exit_hook劫持流程是 exit => _dl_fini => __rtld_lock_lock_recursive 和 __rtld_lock_unlock_recursive这两个函数指针在_rtld_global (_rtld_local)中,通过修改函数指针来劫持执行流。同时,还有一个at_quick_exit函数,调用initial_quick

我在学习的时候发现_dl_fini 是靠 (fs:0x30) ^ (initial:0x18) (__exit_funcs 存储的内容) 得来,其中 initial:0x20 为参数rdi,这个过程非常有意思,涉及tcb结构体,涉及函数的计算,可以看出这个地方以前肯定被别人攻击过,加入了ptr_guard进行防护,具体内容下面有描述。

名字是一个事务最好解释,在学习库函数过程中,有很多名次简写让人摸不到头脑,所以有必要提前说明一下。

例如:__cxa_atexit

解释:CXXABI简写,是C++语言内部实现的一套标准,其目的是为了确保分开编译的程序模块链接到一起后能够正确工作。所谓C++语言内部实现标准又是什么意思?该标准规定了C++对象在内存中是如何布局的、构造函数和析构函数的规则是什么,如何支持异常,如何创建模板实例,命名空间的规则是什么样的、如何支持虚函数等等。所有这些C++语言内部实现的细节就构成了CXXABI

例如:RTLD_LAZY RTLD_GLOBAL

解释:run time

例如:ld_library_path

解释:load

例如:dlopen dlclose

解释: Dynamic linking library,动态链接库。

例如:# define GLRO(name) _rtld_local_ro._##name

解释:global/local read-only

例如:__do_global_ctors_aux ()

解释:辅助(auxilirary )。gccaux属性用于从C直接访问ARCarchitecture variant 变体架构)的辅助寄存器空间。辅助寄存器编号通过属性参数给出。

例如:__do_global_dtors_aux __do_global_ctors_aux

解释:构造函数(constructors),析构函数(destructors

例如:crt1.o, crti.o, crtbegin.o, crtend.o, crtn.o

解释:c runtime ,用于执行进入main之前的初始化和退出main之后的扫尾工作。

例如:DT_NUM DT_THISPROCNUM

解释:d_tag (dynamic entry type),连接器的类型

exit部分有用的函数调用过程如下,

没啥好说的,只是传入了__exit_funcs的地址,它存储的是initial结构体的地址。

结构体相关内容如下,可以看出它里面保存了32个函数的地址及参数,当然如果不够用可以用链表进行串联。

__run_exit_handlers 属于exit的主要函数,内容挺多,我们一步一步说

上面的枚举很简单就是从0到4,

一般initial结构体内容如下,可以看出flavor为4,也就是一直执行cxafct(arg, status)

此函数的作用使tls析构,是执行tls_dtor_list结构体中的函数,并且参数也在其中。通常结构体为空,判断一下就出来了。此部分内容涉及线程结构体,linux中要用到fs寄存器。

从上面可以看出 cxafct显然不是一个正常的内存地址,是通过PTR_DEMANGLE (cxafct);之后才成为正常函数的。但这里我有点疑问,因为我查到的原码是循环右移9位,然而真实是循环右移11位??????????????????可能与Makefile配置相关,有待再去看详细过程。

tcbhead_t结构体如下

从上面可以看出, _dl_fini函数地址的产生过程为pointer_guard (fs:0x30) ^ cxafct (initial:0x18) ,如果我们能泄露initial的内容就能反算出 pointer_guard ,然后再将initial:0x18处的值就能执行任意函数,参数虽然最多只能有1个,既rdi,但配合house of 一骑当千使用仍然可以绕过沙盒。

此宏非常复杂,如果跟踪宏定义可能进入一个陷阱,我们从从libc的反编译中进行观察,是顺序__libc_atexit节里面的指针,2.31的汇编如下,其中,__elf_set___libc_atexit_element__IO_cleanup__里面存的就是 _IO_cleanup的地址。注意:此部分内容2.35之后已不可写

执行 _IO_cleanup

**说明:**现在的版本中__libc_atexit节为虚表的结尾。

_dl_fini函数没有符号表,需要通过交叉引用才能找到。通过链接器的start可以快速定位,既ld.sostart程序,源代码如下

反编译定位如下

这个函数的说明很有意思,主要是考虑dlopen的问题,我就不删了。里面很多GL()其实就是调用_rtld_local,这个也是一个疑问**我没找到_rtld_local的定义???**在gdb调试中也将_rtld_local视为_rtld_global,暂时就这样看吧。

我本来想将__rtld_lock_lock_recursive改为gets,再将GL(dl_load_lock)处的值修改为自己想要的,然后将__rtld_lock_unlock_recursive改为setcontext+53就可以控制执行流,但实际操作中 GL(dl_nns)等于1,就要执行一通操作,并且涉及link_map

_ns_nloaded一般为4,同时 _ns_loaded存储的是文件的基地址。

_rtld_global结构体每一代版本都不同,以下是 glibc-2.31。这个结构体是链接器的起始鼻祖,可以说是包罗万象,其中最重要的就是涉及所有文件的内存地址。例如:

_dl_rtld_map结构体中,l_name存储的是链接器名称所在的地址,因为链接器名字必然写在文件中,所以可以计算出文件的地址。

__exit_funcs没有符号表,需要手动查找偏移,通过exit可以快速定位。

攻击__exit_funcs必须要结合攻击initial,因为要反算pointer_guard,除非能利用泄露tcb结构体数据。攻击思路如下。

泄露 __exit_funcs 和 initial => 计算pointer_guard => 修改initial的内容,或者将__exit_funcs的内容改到堆上。

1.__exit_funcs附近数据非常丰富,可以使用house of spirit等方式,在不能使用tcache时可以考虑。

2.双堆题目时,将__exit_funcs作为指向数据块的指针,此时能较为方便泄露initial。那么再次申请一个堆块伪造initial就水到渠成。

3.对于2.32以前的版本时可以使用tcache直连续2次接申请到initial处。

1.攻击的核心还是伪造initial,不如直接攻击initial来的方便。

2.参数只能有1个,绕过沙盒需要借助house of 一骑当千

3.需要泄露堆地址。

initial也是没有符号表,需要手动查找偏移,利用__exit_funcs可以快速定位。

攻击initial相对较为简单,也需要反算pointer_guard,除非能利用泄露tcb结构体数据。攻击思路如下,显然攻击思路要简单很多

泄露 initial => 计算pointer_guard => 修改initial的内容。

1.攻击步骤简单

2.可以使用多函数调用,需要注意的是调用过程是从后向前调用。利用getcontext + gets + gets + setcontext执行 mproject + shellcode

1.能控制initial的方法太少了,它附近没有什么数据可以利用,只能使用tcache

2.34后 __rtld_lock_lock_recursive 和__rtld_lock_unlock_recursive 不能攻击,_IO_cleanup是另外一个思路。注意:2.35之后_IO_cleanup已不可写

不用 ld 文件就可以确定地址

只能使用 one_gadget,无法控制参数。

_dl_fini 是靠 (fs:0x30) ^ (initial:0x18) (__exit_funcs 存储的内容) 得来,其中 (initial:0x20) 为参数(rdi)。那么我们可以有以下攻击思路。

1.修改 initial 结构体的值

2.将 __exit_funcs 的值改在堆上 (house of spirit)

其实,目前攻击_dl_fini就是攻击_rtld_global,它不能直接泄露,需要有二次泄露的机会。

对于2.32以前的版本时可以使用tcache直连续2次接申请到_rtld_global处。

没啥好说的,通用技能

1.__rtld_lock_define_recursive (EXTERN, _dl_load_lock) 地址是这两个函数的参数,只有一个字长,不能写入太多数据,写入太多会影响后面程序执行。

2.2.34 已无法使用

这种攻击方式与unlink结合简直是绝配。因为_dl_rtld_map.ld_name存储的就是链接器字符串所在的地址,将其泄露后可以计算出file的地址,此时就能利用unlink申请到任一内存。注意:由于不能劫持执行流,后续的攻击还要结合其他方式。

1.可以让攻击思路异常简单。

2._dl_rtld_map附近数据非常丰富,各种手段都能够攻击到这里。

1.在没有ld文件的情况下要泄露 3 次

2.LOAD节末尾一般都是 00,所以puts泄漏时需要注意

3.free的次数需要足够多。

4.攻击过程非常繁琐。

以下代码主要是检测l->l_next,所以伪造的_ns_loaded需要回复原来的l->l_next,实际中通过 _rtld_global._dl_ns._ns_loaded.l_next 计算偏移

下面一共有3个检测

能够执行多个函数

不能控制参数,绕过沙盒比较复杂

还有一种攻击方式是能多次执行__rtld_lock_lock_recursive 和 __rtld_lock_unlock_recursive ,由于这三个数据距离非常远,攻击步骤就显得非常繁琐,除非将_rtld_global全部改写。实用性不高,因为太繁琐所以没测试。

以上的攻击中都存在一个问题,因为攻击的函数都是指针,参数数量无法改变,除了个别可以使用house of 一骑当千,其他考虑使用多步的攻击方进行。

题目如果不给出ld 文件,则无法直接确定 _rtld_local 地址,可以在libc中找到libc.symbols['_rtld_global_ptr']找到,但需要一次泄露,对于2.32以前的版本时可以使用tcache直连续2次接申请到_rtld_global处。

每个版本libc_rtld_local 结构体可能会有变化,需要具体问题具体分析。

没有 ld文件也就不知道 _dl_fini 的地址。

可以结合攻击environ 泄露栈地址。

EOP对泄露次数要求较高。

在我退役前,很多题目都是直接调用__exit函数,所以很正的使用机会很少,以下板子使用频率并不高。

ptr_guard是攻击initial的关键,主要有以下两个函数

EOP之后我才发现house of 一骑当千的用法,所以上面 getcontext + gets + gets + setcontext可以简化为 gets + setcontext

在我退役前,很多题目都是直接调用__exit函数,所以很正的使用机会很少。下面以2022巅峰极客 happy_note为例。

一次UAF

2次malloc

比赛时我还不会house of apple,使用EOP处理的。

醋打哪酸,盐打哪咸,西瓜怎么那么甜,大蒜青椒哪个辣,何物苦又赛黄连
段有何用,节在哪空,程序何时才启动,动静加载因何改,金丝雀怎样飞入宫。
醋打哪酸,盐打哪咸,西瓜怎么那么甜,大蒜青椒哪个辣,何物苦又赛黄连
段有何用,节在哪空,程序何时才启动,动静加载因何改,金丝雀怎样飞入宫。
https://sourceware.org/legacy-ml/libc-help/2012-03/msg00006.html
The GLRO() macro is used to access global or local  read-only data, see sysdeps/generic/ldsodefs.h.
https://sourceware.org/legacy-ml/libc-help/2012-03/msg00006.html
The GLRO() macro is used to access global or local  read-only data, see sysdeps/generic/ldsodefs.h.
exit =
    __run_exit_handlers(status, &__exit_funcs, true, true)  =
        __call_tls_dtors
        _dl_fini(void) =>
            _dl_rtld_lock_recursive  (函数及参数均在 _rtld_global 中,下同)
            _dl_rtld_unlock_recursive
        _IO_cleanup
        _exit  (系统调用封装)
exit =
    __run_exit_handlers(status, &__exit_funcs, true, true)  =
        __call_tls_dtors
        _dl_fini(void) =>
            _dl_rtld_lock_recursive  (函数及参数均在 _rtld_global 中,下同)
            _dl_rtld_unlock_recursive
        _IO_cleanup
        _exit  (系统调用封装)
void  exit (int status)
{
  __run_exit_handlers (status, &__exit_funcs, true, true); // __exit_funcs 中只保存了 initial 的地址
}
libc_hidden_def (exit)
void  exit (int status)
{
  __run_exit_handlers (status, &__exit_funcs, true, true); // __exit_funcs 中只保存了 initial 的地址
}
libc_hidden_def (exit)
// /stdlib/cxa_atexit.c
static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;
 
// /stdlib/exit.h
struct exit_function_list
  {
    struct exit_function_list *next; // exit_function_list 的链表
    size_t idx;  // 执行 exit_function_list 中函数的个数,用于遍历执行
    struct exit_function fns[32]; // exit_function_list 中真正的函数
  };
 
struct exit_function
  {
    long int flavor;  // 类似于 flag ,用于选择执行下面联合体中的哪个结构
    union // 联合体的函数类型
      {
    void (*at) (void); // 函数类型1:无参
    struct
      {
        void (*fn) (int status, void *arg); // 函数类型2,这个参数在后面,一般不采用
        void *arg; // 函数的参数
      } on;
    struct
      {
        void (*fn) (void *arg, int status);  //  函数类型3,一般使用这个
        void *arg;
        void *dso_handle; // 不知道
      } cxa;  //  c++ api
      } func;
  };
// /stdlib/cxa_atexit.c
static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;
 
// /stdlib/exit.h
struct exit_function_list
  {
    struct exit_function_list *next; // exit_function_list 的链表
    size_t idx;  // 执行 exit_function_list 中函数的个数,用于遍历执行
    struct exit_function fns[32]; // exit_function_list 中真正的函数
  };
 
struct exit_function
  {
    long int flavor;  // 类似于 flag ,用于选择执行下面联合体中的哪个结构
    union // 联合体的函数类型
      {
    void (*at) (void); // 函数类型1:无参
    struct
      {
        void (*fn) (int status, void *arg); // 函数类型2,这个参数在后面,一般不采用
        void *arg; // 函数的参数
      } on;
    struct
      {
        void (*fn) (void *arg, int status);  //  函数类型3,一般使用这个
        void *arg;
        void *dso_handle; // 不知道
      } cxa;  //  c++ api
      } func;
  };
//  /stdlib/exit.c
void attribute_hidden __run_exit_handlers (int status, struct exit_function_list **listp,
             bool run_list_atexit, bool run_dtors)
{
#ifndef SHARED
  if (&__call_tls_dtors != NULL)
#endif
    if (run_dtors)
      __call_tls_dtors (); //说明见下
 
  __libc_lock_lock (__exit_funcs_lock);
 
  while (true)
    {
      struct exit_function_list *cur = *listp; // 在此取得 initial 里面的第一个 exit_function_list
 
      if (cur == NULL)  // 不会为空
    {
      __exit_funcs_done = true;
      break;
    }
     
      // 执行 exit_function_list 中函数的个数,idx 用于遍历执行
      while (cur->idx > 0)   // 判断是否到底
    {
      struct exit_function *const f = &cur->fns[--cur->idx];
      const uint64_t new_exitfn_called = __new_exitfn_called;
 
      switch (f->flavor)  // 取得 flag
        {
          void (*atfct) (void);
          void (*onfct) (int status, void *arg);
          void (*cxafct) (void *arg, int status);
          void *arg;
 
        case ef_free:  // 是一个枚举,值为 0,下面说明
        case ef_us:  // 是一个枚举,值为 1,下面说明
          break;
        case ef_on:  // 是一个枚举,值为 2,下面说明
          onfct = f->func.on.fn;
          arg = f->func.on.arg;
#ifdef PTR_DEMANGLE
          PTR_DEMANGLE (onfct);
#endif
          __libc_lock_unlock (__exit_funcs_lock);
          onfct (status, arg); // 执行函数
          __libc_lock_lock (__exit_funcs_lock);
          break;
        case ef_at:  // 是一个枚举,值为 3,下面说明
          atfct = f->func.at;
#ifdef PTR_DEMANGLE
          PTR_DEMANGLE (atfct);
#endif
          /* Unlock the list while we call a foreign function.  */
          __libc_lock_unlock (__exit_funcs_lock);
          atfct (); // 执行无参函数
          __libc_lock_lock (__exit_funcs_lock);
          break;
        case ef_cxa:  // 是一个枚举,值为 4,下面说明
          f->flavor = ef_free;  // 是一个枚举,下面说明
          cxafct = f->func.cxa.fn;
          arg = f->func.cxa.arg;
#ifdef PTR_DEMANGLE
          PTR_DEMANGLE (cxafct);  // PTR_DEMANGLE 后面一并说
#endif
          __libc_lock_unlock (__exit_funcs_lock);
          cxafct (arg, status); // 我们一般采取这里的执行函数
          __libc_lock_lock (__exit_funcs_lock);
          break;
        }
 
      if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
            continue;
    }
       
     // 查看 exit_function_list 还有没有,如果有的话再来一遍,并且free掉当前的
      *listp = cur->next;
      if (*listp != NULL)
          free (cur);
    }
  __libc_lock_unlock (__exit_funcs_lock);
  if (run_list_atexit)
    RUN_HOOK (__libc_atexit, ());
  _exit (status);
}
//  /stdlib/exit.c
void attribute_hidden __run_exit_handlers (int status, struct exit_function_list **listp,
             bool run_list_atexit, bool run_dtors)
{
#ifndef SHARED
  if (&__call_tls_dtors != NULL)
#endif
    if (run_dtors)
      __call_tls_dtors (); //说明见下
 
  __libc_lock_lock (__exit_funcs_lock);
 
  while (true)
    {
      struct exit_function_list *cur = *listp; // 在此取得 initial 里面的第一个 exit_function_list
 
      if (cur == NULL)  // 不会为空
    {
      __exit_funcs_done = true;
      break;
    }
     
      // 执行 exit_function_list 中函数的个数,idx 用于遍历执行
      while (cur->idx > 0)   // 判断是否到底
    {
      struct exit_function *const f = &cur->fns[--cur->idx];
      const uint64_t new_exitfn_called = __new_exitfn_called;
 
      switch (f->flavor)  // 取得 flag
        {
          void (*atfct) (void);
          void (*onfct) (int status, void *arg);
          void (*cxafct) (void *arg, int status);
          void *arg;
 
        case ef_free:  // 是一个枚举,值为 0,下面说明
        case ef_us:  // 是一个枚举,值为 1,下面说明
          break;
        case ef_on:  // 是一个枚举,值为 2,下面说明
          onfct = f->func.on.fn;
          arg = f->func.on.arg;
#ifdef PTR_DEMANGLE
          PTR_DEMANGLE (onfct);
#endif
          __libc_lock_unlock (__exit_funcs_lock);
          onfct (status, arg); // 执行函数
          __libc_lock_lock (__exit_funcs_lock);
          break;
        case ef_at:  // 是一个枚举,值为 3,下面说明
          atfct = f->func.at;
#ifdef PTR_DEMANGLE
          PTR_DEMANGLE (atfct);
#endif
          /* Unlock the list while we call a foreign function.  */
          __libc_lock_unlock (__exit_funcs_lock);
          atfct (); // 执行无参函数
          __libc_lock_lock (__exit_funcs_lock);
          break;
        case ef_cxa:  // 是一个枚举,值为 4,下面说明
          f->flavor = ef_free;  // 是一个枚举,下面说明
          cxafct = f->func.cxa.fn;
          arg = f->func.cxa.arg;
#ifdef PTR_DEMANGLE
          PTR_DEMANGLE (cxafct);  // PTR_DEMANGLE 后面一并说
#endif
          __libc_lock_unlock (__exit_funcs_lock);
          cxafct (arg, status); // 我们一般采取这里的执行函数
          __libc_lock_lock (__exit_funcs_lock);
          break;
        }
 
      if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
            continue;
    }
       
     // 查看 exit_function_list 还有没有,如果有的话再来一遍,并且free掉当前的
      *listp = cur->next;
      if (*listp != NULL)
          free (cur);
    }
  __libc_lock_unlock (__exit_funcs_lock);
  if (run_list_atexit)
    RUN_HOOK (__libc_atexit, ());
  _exit (status);
}
enum
{
  ef_free,  /* `ef_free' MUST be zero!  */
  ef_us,
  ef_on,
  ef_at,
  ef_cxa
};
enum
{
  ef_free,  /* `ef_free' MUST be zero!  */
  ef_us,
  ef_on,
  ef_at,
  ef_cxa
};
//  /stdlib/cxa_thread_atexit_impl.c
struct dtor_list
{
  dtor_func func;
  void *obj;
  struct link_map *map;
  struct dtor_list *next;
};
 
static __thread struct dtor_list *tls_dtor_list;  // 使用的是线程结构体
 
void __call_tls_dtors (void)
{
  while (tls_dtor_list) // 一般这个结构体为空
    {
      struct dtor_list *cur = tls_dtor_list;
      dtor_func func = cur->func;
#ifdef PTR_DEMANGLE
      PTR_DEMANGLE (func); // PTR_DEMANGLE 后面一并说
#endif
      tls_dtor_list = tls_dtor_list->next;
      func (cur->obj); // 如果 tls_dtor_list 不为空,则也能执行某个函数
      atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
      free (cur);  // 当结构体不为空时,说明有多线程运作,会调用 free 函数。
    }
}
libc_hidden_def (__call_tls_dtors)
//  /stdlib/cxa_thread_atexit_impl.c
struct dtor_list
{
  dtor_func func;
  void *obj;
  struct link_map *map;
  struct dtor_list *next;
};
 
static __thread struct dtor_list *tls_dtor_list;  // 使用的是线程结构体
 
void __call_tls_dtors (void)
{
  while (tls_dtor_list) // 一般这个结构体为空
    {
      struct dtor_list *cur = tls_dtor_list;
      dtor_func func = cur->func;
#ifdef PTR_DEMANGLE
      PTR_DEMANGLE (func); // PTR_DEMANGLE 后面一并说
#endif
      tls_dtor_list = tls_dtor_list->next;
      func (cur->obj); // 如果 tls_dtor_list 不为空,则也能执行某个函数
      atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
      free (cur);  // 当结构体不为空时,说明有多线程运作,会调用 free 函数。
    }
}
libc_hidden_def (__call_tls_dtors)
// /sysdeps/unix/sysv/linux/i386/sysdep.h
/* Pointer mangling support.  */
#if IS_IN (rtld)
/* We cannot use the thread descriptor because in ld.so we use setjmp
   earlier than the descriptor is initialized.  Using a global variable
   is too complicated here since we have no PC-relative addressing mode.  */
#else
# ifdef __ASSEMBLER__
#  define PTR_MANGLE(reg)   xorl %gs:POINTER_GUARD, reg;              \
                roll $9, reg
#  define PTR_DEMANGLE(reg) rorl $9, reg;                     \
                xorl %gs:POINTER_GUARD, reg
# else
#  define PTR_MANGLE(var)   asm ("xorl %%gs:%c2, %0\n"            \
                     "roll $9, %0"                \
                     : "=r" (var)                 \
                     : "0" (var),                 \
                       "i" (offsetof (tcbhead_t,          \
                              pointer_guard)))
#  define PTR_DEMANGLE(var) asm ("rorl $9, %0\n"                  \
                     "xorl %%gs:%c2, %0"              \
                     : "=r" (var)                 \
                     : "0" (var),                 \
                       "i" (offsetof (tcbhead_t,          \
                              pointer_guard)))
# endif
#endif
// /sysdeps/unix/sysv/linux/i386/sysdep.h
/* Pointer mangling support.  */
#if IS_IN (rtld)
/* We cannot use the thread descriptor because in ld.so we use setjmp
   earlier than the descriptor is initialized.  Using a global variable
   is too complicated here since we have no PC-relative addressing mode.  */
#else
# ifdef __ASSEMBLER__
#  define PTR_MANGLE(reg)   xorl %gs:POINTER_GUARD, reg;              \
                roll $9, reg
#  define PTR_DEMANGLE(reg) rorl $9, reg;                     \
                xorl %gs:POINTER_GUARD, reg
# else
#  define PTR_MANGLE(var)   asm ("xorl %%gs:%c2, %0\n"            \
                     "roll $9, %0"                \
                     : "=r" (var)                 \
                     : "0" (var),                 \
                       "i" (offsetof (tcbhead_t,          \
                              pointer_guard)))
#  define PTR_DEMANGLE(var) asm ("rorl $9, %0\n"                  \
                     "xorl %%gs:%c2, %0"              \
                     : "=r" (var)                 \
                     : "0" (var),                 \
                       "i" (offsetof (tcbhead_t,          \
                              pointer_guard)))
# endif
#endif
// /sysdeps/i386/nptl/tls.h
 
typedef struct
{
  void *tcb;        /* Pointer to the TCB.  Not necessarily the
               thread descriptor used by libpthread.  */
  dtv_t *dtv;
  void *self;       /* Pointer to the thread descriptor.  指向自己 */
  int multiple_threads;
  uintptr_t sysinfo;
  uintptr_t stack_guard;  // 这个就是canary
  uintptr_t pointer_guard; //  这个就是异或的值
  int gscope_flag;
#ifndef __ASSUME_PRIVATE_FUTEX
  int private_futex;
#else
  int __glibc_reserved1;
#endif
  /* Reservation of some values for the TM ABI.  */
  void *__private_tm[4];
  /* GCC split stack support.  */
  void *__private_ss;
} tcbhead_t;
// /sysdeps/i386/nptl/tls.h
 
typedef struct
{
  void *tcb;        /* Pointer to the TCB.  Not necessarily the
               thread descriptor used by libpthread.  */
  dtv_t *dtv;
  void *self;       /* Pointer to the thread descriptor.  指向自己 */
  int multiple_threads;
  uintptr_t sysinfo;
  uintptr_t stack_guard;  // 这个就是canary
  uintptr_t pointer_guard; //  这个就是异或的值
  int gscope_flag;
#ifndef __ASSUME_PRIVATE_FUTEX
  int private_futex;
#else
  int __glibc_reserved1;
#endif
  /* Reservation of some values for the TM ABI.  */
  void *__private_tm[4];
  /* GCC split stack support.  */
  void *__private_ss;
} tcbhead_t;
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;
}
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;
}
// /sysdeps/x86_64/dl-machine.h
#ifdef RTLD_START
RTLD_START
#else
# error "sysdeps/MACHINE/dl-machine.h fails to define RTLD_START"
#endif
 
/* Initial entry point code for the dynamic linker.
   The C function `_dl_start' is the real entry point;
   its return value is the user program's entry point.  */
#define RTLD_START asm ("\n\
.text\n\
    .align 16\n\
.globl _start\n\
.globl _dl_start_user\n\
_start:\n\
    movq %rsp, %rdi\n\
    /* 调用 _dl_start*/
    call _dl_start\n\
    /* 真正的运行我们的程序 */
_dl_start_user:\n\
    # Save the user entry point address in %r12.\n\
    /* 保存入口函数在 r12 寄存器 */
    movq %rax, %r12\n\
    # See if we were run as a command with the executable file\n\
    # name as an extra leading argument.\n\
    /* 处理命令行参数 */
    movl _dl_skip_args(%rip), %eax\n\
    # Pop the original argument count.\n\
    popq %rdx\n\
    # Adjust the stack pointer to skip _dl_skip_args words.\n\
    leaq (%rsp,%rax,8), %rsp\n\
    # Subtract _dl_skip_args from argc.\n\
    subl %eax, %edx\n\
    # Push argc back on the stack.\n\
    pushq %rdx\n\
    # Call _dl_init (struct link_map *main_map, int argc, char **argv, char **env)\n\
    # argc -> rsi\n\
    movq %rdx, %rsi\n\
    # Save %rsp value in %r13.\n\
    movq %rsp, %r13\n\
    # And align stack for the _dl_init call. \n\
    andq $-16, %rsp\n\
    # _dl_loaded -> rdi\n\
    movq _rtld_local(%rip), %rdi\n\
    # env -> rcx\n\
    leaq 16(%r13,%rdx,8), %rcx\n\
    # argv -> rdx\n\
    leaq 8(%r13), %rdx\n\
    # Clear %rbp to mark outermost frame obviously even for constructors.\n\
    xorl %ebp, %ebp\n\
    # Call the function to run the initializers.\n\
    call _dl_init\n\
    # Pass our finalizer function to the user in %rdx, as per ELF ABI.\n\
    leaq _dl_fini(%rip), %rdx\n\   /* _dl_fini */
    # And make sure %rsp points to argc stored on the stack.\n\
    movq %r13, %rsp\n\
    # Jump to the user's entry point.\n\
    /* 跳转到用户入口程序 */
    jmp *%r12\n\
.previous\n\
");
// /sysdeps/x86_64/dl-machine.h
#ifdef RTLD_START
RTLD_START
#else
# error "sysdeps/MACHINE/dl-machine.h fails to define RTLD_START"
#endif
 
/* Initial entry point code for the dynamic linker.
   The C function `_dl_start' is the real entry point;
   its return value is the user program's entry point.  */
#define RTLD_START asm ("\n\
.text\n\
    .align 16\n\
.globl _start\n\
.globl _dl_start_user\n\
_start:\n\
    movq %rsp, %rdi\n\
    /* 调用 _dl_start*/
    call _dl_start\n\
    /* 真正的运行我们的程序 */
_dl_start_user:\n\
    # Save the user entry point address in %r12.\n\
    /* 保存入口函数在 r12 寄存器 */
    movq %rax, %r12\n\
    # See if we were run as a command with the executable file\n\
    # name as an extra leading argument.\n\
    /* 处理命令行参数 */
    movl _dl_skip_args(%rip), %eax\n\
    # Pop the original argument count.\n\
    popq %rdx\n\
    # Adjust the stack pointer to skip _dl_skip_args words.\n\
    leaq (%rsp,%rax,8), %rsp\n\
    # Subtract _dl_skip_args from argc.\n\
    subl %eax, %edx\n\
    # Push argc back on the stack.\n\
    pushq %rdx\n\
    # Call _dl_init (struct link_map *main_map, int argc, char **argv, char **env)\n\
    # argc -> rsi\n\
    movq %rdx, %rsi\n\
    # Save %rsp value in %r13.\n\
    movq %rsp, %r13\n\
    # And align stack for the _dl_init call. \n\
    andq $-16, %rsp\n\
    # _dl_loaded -> rdi\n\
    movq _rtld_local(%rip), %rdi\n\
    # env -> rcx\n\
    leaq 16(%r13,%rdx,8), %rcx\n\
    # argv -> rdx\n\
    leaq 8(%r13), %rdx\n\
    # Clear %rbp to mark outermost frame obviously even for constructors.\n\
    xorl %ebp, %ebp\n\
    # Call the function to run the initializers.\n\
    call _dl_init\n\
    # Pass our finalizer function to the user in %rdx, as per ELF ABI.\n\
    leaq _dl_fini(%rip), %rdx\n\   /* _dl_fini */
    # And make sure %rsp points to argc stored on the stack.\n\
    movq %r13, %rsp\n\
    # Jump to the user's entry point.\n\
    /* 跳转到用户入口程序 */
    jmp *%r12\n\
.previous\n\
");
// /elf/dl-fini.c
void _dl_fini (void)
{
  /* Lots of fun ahead.  We have to call the destructors for all still
     loaded objects, in all namespaces.  The problem is that the ELF
     specification now demands that dependencies between the modules
     are taken into account.  I.e., the destructor for a module is
     called before the ones for any of its dependencies.
 
     To make things more complicated, we cannot simply use the reverse
     order of the constructors.  Since the user might have loaded objects
     using `dlopen' there are possibly several other modules with its
     dependencies to be taken into account.  Therefore we have to start
     determining the order of the modules once again from the beginning.  */
 
  /* We run the destructors of the main namespaces last.  As for the
     other namespaces, we pick run the destructors in them in reverse
     order of the namespace ID.  */
#ifdef SHARED
  int do_audit = 0;
 again:
#endif
  for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)  //取得 dl_nns 的值
    {
      /* Protect against concurrent loads and unloads.  */
      __rtld_lock_lock_recursive (GL(dl_load_lock)); // 加锁,其实是调用 _rtld_global 中得指针
 
      unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;  // nloaded 一般为4
      /* 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)); // 解锁,其实是调用 _rtld_global 中得指针
      else
    {
#ifdef SHARED
      _dl_audit_activity_nsid (ns, LA_ACT_DELETE);
#endif
 
      /* Now we can allocate an array to hold all the pointers and
         copy the pointers in.  */
      struct link_map *maps[nloaded];
 
      unsigned int i;
      struct link_map *l;  // link_map 载入
      assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);
      for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
        /* Do not handle ld.so in secondary namespaces.  */
        if (l == l->l_real)
          {
        assert (i < nloaded);
 
        maps[i] = l;
        l->l_idx = i;
        ++i;
 
        /* Bump l_direct_opencount of all objects so that they
           are not dlclose()ed from underneath us.  */
        ++l->l_direct_opencount;
          }
      assert (ns != LM_ID_BASE || i == nloaded);
      assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1);
      unsigned int nmaps = i;  // nmaps==3
 
      /* Now we have to do the sorting.  We can skip looking for the
         binary itself which is at the front of the search list for
         the main namespace.  */
      _dl_sort_maps (maps, nmaps, (ns == LM_ID_BASE), true); //暂时没有发现可利用的地方
 
      /* We do not rely on the linked list of loaded object anymore
         from this point on.  We have our own list here (maps).  The
         various members of this list cannot vanish since the open
         count is too high and will be decremented in this loop.  So
         we release the lock so that some code which might be called
         from a destructor can directly or indirectly access the
         lock.  */
      __rtld_lock_unlock_recursive (GL(dl_load_lock));  // 解锁
 
      /* 'maps' now contains the objects in the right order.  Now
         call the destructors.  We have to process this array from
         the front.  */
      for (i = 0; i < nmaps; ++i)
        {
          struct link_map *l = maps[i];
 
          if (l->l_init_called)
        {
          /* Make sure nothing happens if we are called twice.  */
          l->l_init_called = 0;
 
          /* Is there a destructor function?  */
          if (l->l_info[DT_FINI_ARRAY] != NULL
              || (ELF_INITFINI && l->l_info[DT_FINI] != NULL))
            {
              /* When debugging print a message first.  */
              if (__builtin_expect (GLRO(dl_debug_mask)
                        & DL_DEBUG_IMPCALLS, 0))
            _dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
                      DSO_FILENAME (l->l_name),
                      ns);
 
              /* First see whether an array is given.  */
              if (l->l_info[DT_FINI_ARRAY] != NULL)
            {
              ElfW(Addr) *array =
                (ElfW(Addr) *) (l->l_addr
                        + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
              unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
                        / sizeof (ElfW(Addr)));
              while (i-- > 0)
                  // 又是一个函数调用,调用程序中的 _fini_array 中的存储和 __libc_csu_fini
                  // house of banana
                ((fini_t) array[i]) ();
            }
 
              /* Next try the old-style destructor.  */
              if (ELF_INITFINI && l->l_info[DT_FINI] != NULL)
            DL_CALL_DT_FINI
              (l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr);
            }
 
#ifdef SHARED
          /* Auditing checkpoint: another object closed.  */
          _dl_audit_objclose (l);
#endif
        }
 
          /* Correct the previous increment.  */
          --l->l_direct_opencount;
        }
 
#ifdef SHARED
      _dl_audit_activity_nsid (ns, LA_ACT_CONSISTENT);
#endif
    }
    }
 
#ifdef SHARED
  if (! do_audit && GLRO(dl_naudit) > 0)
    {
      do_audit = 1;
      goto again;
    }
 
  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_STATISTICS))
    _dl_debug_printf ("\nruntime linker statistics:\n"
              "           final number of relocations: %lu\n"
              "final number of relocations from cache: %lu\n",
              GL(dl_num_relocations),
              GL(dl_num_cache_relocations));
#endif
}
// /elf/dl-fini.c
void _dl_fini (void)
{
  /* Lots of fun ahead.  We have to call the destructors for all still
     loaded objects, in all namespaces.  The problem is that the ELF
     specification now demands that dependencies between the modules
     are taken into account.  I.e., the destructor for a module is
     called before the ones for any of its dependencies.
 
     To make things more complicated, we cannot simply use the reverse
     order of the constructors.  Since the user might have loaded objects
     using `dlopen' there are possibly several other modules with its
     dependencies to be taken into account.  Therefore we have to start
     determining the order of the modules once again from the beginning.  */
 
  /* We run the destructors of the main namespaces last.  As for the
     other namespaces, we pick run the destructors in them in reverse
     order of the namespace ID.  */
#ifdef SHARED
  int do_audit = 0;
 again:
#endif
  for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)  //取得 dl_nns 的值
    {
      /* Protect against concurrent loads and unloads.  */
      __rtld_lock_lock_recursive (GL(dl_load_lock)); // 加锁,其实是调用 _rtld_global 中得指针
 
      unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;  // nloaded 一般为4
      /* 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)); // 解锁,其实是调用 _rtld_global 中得指针
      else
    {
#ifdef SHARED
      _dl_audit_activity_nsid (ns, LA_ACT_DELETE);
#endif
 
      /* Now we can allocate an array to hold all the pointers and
         copy the pointers in.  */
      struct link_map *maps[nloaded];
 
      unsigned int i;
      struct link_map *l;  // link_map 载入
      assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);

[峰会]看雪.第八届安全开发者峰会10月23日上海龙之梦大酒店举办!

上传的附件:
收藏
免费 5
支持
分享
最新回复 (2)
雪    币: 73
活跃值: (923)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
这位应该是考了研的
2024-5-31 11:19
0
雪    币: 6027
活跃值: (3610)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
3
我就不能是退休了吗
2024-5-31 13:33
1
游客
登录 | 注册 方可回帖
返回
//