-
-
[原创]EOP编程
-
发表于: 2024-5-30 21:16 7518
-
说明:本篇是以前学习时的记录,后期进行过整理,有些地方没有完善,请各位师傅见谅。
一个三环程序是始于链接器,终于终止信号,虽然链接器执行的过程在程序运行前的过程很有意思,但对PWN
掉程序而言不如攻击终止程序来的直接。exit
是linux
常见的终止程序,本人在学习的过程中发现了一些可以利用的地方,并将其记录了下来和各位师傅一起学习。模仿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
)。gcc
中aux
属性用于从C直接访问ARC
(architecture 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.so
的start
程序,源代码如下
反编译定位如下
这个函数的说明很有意思,主要是考虑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日上海龙之梦大酒店举办!
赞赏
- [原创]反序列化的前生今世 8366
- [原创]gdb在逆向爆破中的应用 2912
- [原创]EOP编程 7519
- [原创]格式化字符串打出没有回头路(下)——回头望月 42575
- [原创]格式化字符串打出没有回头路(上) 14034