调用过程分析

首先我们编一个简单的main函数,函数主体为空

1
2
3
int main(){

}

编译完成之后,我们需要知道程序第一个调用的函数是什么,查看ELF文件的头部objdump -f mainAna

图片无法显示,请设置GitHub代理

可以看到可执行文件的起始地址是0x80482e0,也就是函数第一个调用的是0x80482e0处存储的函数,那么这个可执行文件的起始地址是如何得到的呢,我们知道每一个elf文件都具有一个头部,头部格式的定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

其中e_entry表示的就是可执行文件的起始地址,我们可以通过readelf -h mainAna获取更加详细的信息

图片无法显示,请设置GitHub代理

那么我们接下来看一下这个地址的函数是什么

图片无法显示,请设置GitHub代理

也就是说程序一开始调用的是_start函数

_start函数分析

linux中启动一个程序的时候,shell或者gui就会调用execve()函数,该函数就会调用系统调用execve,函数会为进程分配新的地址空间,将argc,argv和环境变量数组压入栈中。解析可执行文件,完成重定位,并将代码数据等映射如内存,设置完毕之后就会将控制权交给程序的入口函数也就是_start函数。我们看一下函数的代码

图片无法显示,请设置GitHub代理

在执行_start函数之前,目前函数栈帧的排列如下

图片无法显示,请设置GitHub代理

从上面的启动分析我们可以知道栈顶的元素就是argc(0xffffcf40),其次是argv(0xffffcf44)和环境变量数组(0x0xffffcf4c-start_end)。其中argv与环境变量数组中间相隔一个NULL

函数首先执行的是xor ebp,ebp这是将ebp清零,表示该函数是程序的最外层函数。接着是pop esi,将esi赋值为了argc的数值,然后是mov ecx,especx指向了argv的起始地址。然后是and esp,0xfffffff0这是将esp进行栈对齐。随后进行了一连串的push操作,这是在为调用__libc_start_main函数进行参数准备

__libc_start_main

参数传递

我们首先看一下当前的寄存器中存储的值

图片无法显示,请设置GitHub代理

接着对push的参数进行分析。首先push eax,此时eax中保存的是无效的数值,push进入栈中的操作仅仅是为了栈16字节对齐。我们看一下__libc_start_main的函数调用,函数源代码位于csu/libc-start.c文件中。这里auxvec的值在后面会分析到。

1
2
3
4
5
6
7
8
9
STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
int argc, char **argv,
#ifdef LIBC_START_MAIN_AUXVEC_ARG
ElfW(auxv_t) *auxvec,
#endif
__typeof (main) init,
void (*fini) (void),
void (*rtld_fini) (void), void *stack_end)
  • 首先push esp,此时esp指向的是刚刚push进入栈中的eax的地址,也就是__libc_start_main函数栈的最高地址,也就是栈底
  • 接着push edx,此时传递的是dl_fini函数的起始地址,也就是动态链接库的析构函数,执行和动态加载相关的析构工作
  • 接着压入的是fini函数的地址,该函数执行main函数调用结束之后的收尾工作
  • 接着压入的是init函数的地址,该函数执行main函数调用之前的初始化工作
  • 接着压入的是ecx的值,此时ecx中存储的是argv的起始地址
  • 接着压入的是esi的值,此时esi中存储的是argc的值
  • 最后压入的是main函数的起始地址

最终完成参数在栈中的布局如下

图片无法显示,请设置GitHub代理

但是我们注意到其没有传递环境变量的参数到__libc_start_main函数中,环境变量数组也不是该函数的参数。但是main函数的原型其实是int main(int argc, char** argv, char** envp),那么程序是如何获取环境变量相关的数值的呢。我们会在后面的源码中看到函数设置了一个全局变量_environ,这样全局变量就可以在libc_start_main中的任何位置使用。全局变量指针的赋值是通过argv来进行的&argv[argc + 1]

源码分析

__libc_start_main函数中其执行流程如下

  1. 设置pthread,启动线程
  2. 注册fini相关的函数
  3. 调用init函数
  4. 调用main函数,并将argc,argv和环境变量数组传递给main函数
  5. 调用exit函数,并将main函数的返回值传递给exit函数

下面是函数的源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
int argc, char **argv,
#ifdef LIBC_START_MAIN_AUXVEC_ARG
ElfW(auxv_t) *auxvec,
#endif
__typeof (main) init,
void (*fini) (void),
void (*rtld_fini) (void), void *stack_end)
{
/* Result of the 'main' function. */
int result;
//判断是否是多个库
__libc_multiple_libcs = &_dl_starting_up && !_dl_starting_up;

#ifndef SHARED
char **ev = &argv[argc + 1];
//设置环境变量
__environ = ev;

/* Store the lowest stack address. This is done in ld.so if this is
the code for the DSO. */
//保存栈的最高地址
__libc_stack_end = stack_end;

# ifdef HAVE_AUX_VECTOR
//处理辅助变量
/* First process the auxiliary vector since we need to find the
program header to locate an eventually present PT_TLS entry. */
# ifndef LIBC_START_MAIN_AUXVEC_ARG
ElfW(auxv_t) *auxvec;
{
char **evp = ev;
while (*evp++ != NULL)
;
auxvec = (ElfW(auxv_t) *) evp;
//找到辅助变量的位置
}
# endif
_dl_aux_init (auxvec);
if (GL(dl_phdr) == NULL)
# endif
{
/* Starting from binutils-2.23, the linker will define the
magic symbol __ehdr_start to point to our own ELF header
if it is visible in a segment that also includes the phdrs.
So we can set up _dl_phdr and _dl_phnum even without any
information from auxv. */

extern const ElfW(Ehdr) __ehdr_start//指向ELF文件的头部
__attribute__ ((weak, visibility ("hidden")));
if (&__ehdr_start != NULL)
{
assert (__ehdr_start.e_phentsize == sizeof *GL(dl_phdr));
GL(dl_phdr) = (const void *) &__ehdr_start + __ehdr_start.e_phoff;
GL(dl_phnum) = __ehdr_start.e_phnum;
}
}

//...
}

首先前面的赋值部分不必多说,我们来看一下辅助变量。在赋值完环境变量之后就开始处理辅助变量,辅助变量同样存储在栈中,位于环境变量之后,两种相隔一个NULL字段。我们可以使用info auxv来查看辅助变量

图片无法显示,请设置GitHub代理

同样也可以设置环境变量 LD_SHOW_AUXV =1来显示辅助变量。辅助变量是一种内核到用户空间的信息传递基址,在elf文件作为一个进程之前,加载器通过辅助变量传递给进程一些信息,这里可以看到UID,GID,ENTRY等信息,但是我们这里不再做过多的分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
int argc, char **argv,
#ifdef LIBC_START_MAIN_AUXVEC_ARG
ElfW(auxv_t) *auxvec,
#endif
__typeof (main) init,
void (*fini) (void),
void (*rtld_fini) (void), void *stack_end)
{
//环境变量和辅助变量的处理
# ifdef DL_SYSDEP_OSCHECK
if (!__libc_multiple_libcs)
{
//检查操作系统版本
/* This needs to run to initiliaze _dl_osversion before TLS
setup might check it. */
DL_SYSDEP_OSCHECK (__libc_fatal);
}
# endif

/* Perform IREL{,A} relocations. */
//根据CPU架构进行初始化--这里没懂
apply_irel ();

/* Initialize the thread library at least a bit since the libgcc
functions are using thread functions if these are available and
we need to setup errno. */
//初始化pthread
__pthread_initialize_minimal ();

//设置canary
/* Set up the stack checker's canary. */
uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
# ifdef THREAD_SET_STACK_GUARD
THREAD_SET_STACK_GUARD (stack_chk_guard);
# else
__stack_chk_guard = stack_chk_guard;
# endif

/* Set up the pointer guard value. */
//设置canary的值
uintptr_t pointer_chk_guard = _dl_setup_pointer_guard (_dl_random,
stack_chk_guard);
# ifdef THREAD_SET_POINTER_GUARD
THREAD_SET_POINTER_GUARD (pointer_chk_guard);
# else
__pointer_chk_guard_local = pointer_chk_guard;
# endif

#endif

/* Register the destructor of the dynamic linker if there is any. */
if (__glibc_likely (rtld_fini != NULL))
__cxa_atexit ((void (*) (void *)) rtld_fini, NULL, NULL);

#ifndef SHARED
/* Call the initializer of the libc. This is only needed here if we
are compiling for the static library in which case we haven't
run the constructors in `_dl_start_user'. */
//初始化libc
__libc_init_first (argc, argv, __environ);

/* Register the destructor of the program, if any. */
if (fini)
__cxa_atexit ((void (*) (void *)) fini, NULL, NULL);

/* Some security at this point. Prevent starting a SUID binary where
the standard file descriptors are not opened. We have to do this
only for statically linked applications since otherwise the dynamic
loader did the work already. */
if (__builtin_expect (__libc_enable_secure, 0))
__libc_check_standard_fds ();
#endif

/* Call the initializer of the program, if any. */
#ifdef SHARED
if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0))
GLRO(dl_debug_printf) ("\ninitialize program: %s\n\n", argv[0]);
#endif
if (init)
(*init) (argc, argv, __environ MAIN_AUXVEC_PARAM);

}

函数首先是初始化了thread,然后在atexit中注册了rtld_finifini,以便完成在加载器和函数结束之后的清理工作。我们来看一下__libc_init_first函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void
__libc_init_first (int argc, char **argv, char **envp)
{
#ifdef SHARED
/* For DSOs we do not need __libc_init_first but instead _init. */
}
void
attribute_hidden
_init (int argc, char **argv, char **envp)
{
//连接上面的libc_init_first
#endif
#ifdef USE_NONOPTION_FLAGS
extern void __getopt_clean_environment (char **);
#endif

__libc_multiple_libcs = &_dl_starting_up && !_dl_starting_up;

/* Make sure we don't initialize twice. */
if (!__libc_multiple_libcs)
{
/* Set the FPU control word to the proper default value if the
kernel would use a different value. */
if (__fpu_control != GLRO(dl_fpu_control))
__setfpucw (__fpu_control);
}

/* Save the command-line arguments. */
__libc_argc = argc;
__libc_argv = argv;
__environ = envp;

#ifndef SHARED
__libc_init_secure ();

/* First the initialization which normally would be done by the
dynamic linker. */
_dl_non_dynamic_init ();
#endif

#ifdef VDSO_SETUP
VDSO_SETUP ();
#endif

__init_misc (argc, argv, envp);

#ifdef USE_NONOPTION_FLAGS
/* This is a hack to make the special getopt in GNU libc working. */
__getopt_clean_environment (envp);
#endif

/* Initialize ctype data. */
__ctype_init ();

#if defined SHARED && !defined NO_CTORS_DTORS_SECTIONS
__libc_global_ctors ();
#endif
}

从函数定义上来看,其实际上调用的是_init函数,执行了一些安全相关的初始化工作。

_libc_csu_init

接下来就是调用传入的init函数了,函数默认是libc_csu_init函数,源码位于csu/elf-init.c文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void
__libc_csu_init (int argc, char **argv, char **envp)
{
/* For dynamically linked executables the preinit array is executed by
the dynamic linker (before initializing any shared object). */

#ifndef LIBC_NONSHARED
/* For static executables, preinit happens right before init. */
{
const size_t size = __preinit_array_end - __preinit_array_start;
size_t i;
for (i = 0; i < size; i++)
(*__preinit_array_start [i]) (argc, argv, envp);
}
#endif

#ifndef NO_INITFINI
_init ();
#endif
//执行init_array中存储的函数指针
const size_t size = __init_array_end - __init_array_start;
for (size_t i = 0; i < size; i++)
(*__init_array_start [i]) (argc, argv, envp);
}

从这里可以看出初始化节区执行的流程就是preinit_array>.init>.init_array。那么我们接下来看有喜爱_init函数,这里执行的是init_proc函数,我们看一下其汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.init:0804828C _init_proc      proc near               ; CODE XREF: __libc_csu_init+1C↓p
.init:0804828C push ebx ; _init
.init:0804828D sub esp, 8
.init:08048290 call __x86_get_pc_thunk_bx
.init:08048295 add ebx, 1D6Bh
.init:0804829B mov eax, ds:(__gmon_start___ptr - 804A000h)[ebx]
.init:080482A1 test eax, eax
.init:080482A3 jz short loc_80482AA
.init:080482A5 call __gmon_start__
.init:080482AA
.init:080482AA loc_80482AA: ; CODE XREF: _init_proc+17↑j
.init:080482AA add esp, 8
.init:080482AD pop ebx
.init:080482AE retn
.init:080482AE _init_proc endp
.init:080482AE
.init:080482AE _init ends

这里我们频繁遇到了下面的汇编代码

1
2
call __x86.get_pc_thunk.bx
add ebx, $_GLOBAL_OFFSET_TABLE_

这里第一个call其实是获取当前eip的地址并保存在ebx中,其做法就是把当前的esp中存储的地址赋值到ebx中,因为call指令会将下一条指令压入栈中。$_GLOBAL_OFFSET_TABLE_则表示的是当前代码与位置无关代码got之间的差值,执行完毕之后ebx就指向了got表的地址。

首先函数判断了是否进行gmon_start函数的调用,该函数是调用一个例程开始profiling,并且在at_exit中注册了一个清理函数,并且在运行结束的时候生成gmon.out文件。如果未设置调用该函数,则直接返回。

调用该函数需要在编译的时候加入-pg选项

调用完_init函数之后就开始调用.init_array中存储的函数,我们看一下保存的函数指针的内容

图片无法显示,请设置GitHub代理

函数指向的是08049f08,而其中存储的内容为

图片无法显示,请设置GitHub代理

我们在ida中看一下该函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.text:080483B0 frame_dummy     proc near               ; CODE XREF: __libc_csu_init+44↓p
.text:080483B0 ; DATA XREF: .init_array:__frame_dummy_init_array_entry↓o
.text:080483B0 mov eax, offset __JCR_LIST__
.text:080483B5 mov edx, [eax]
.text:080483B7 test edx, edx
.text:080483B9 jnz short loc_80483C0
.text:080483BB
.text:080483BB loc_80483BB: ; CODE XREF: frame_dummy+17↓j
.text:080483BB jmp short register_tm_clones
.text:080483BB ; -----------------------------------------------------------------------
.text:080483BD align 10h
.text:080483C0
.text:080483C0 loc_80483C0: ; CODE XREF: frame_dummy+9↑j
.text:080483C0 mov edx, 0
.text:080483C5 test edx, edx
.text:080483C7 jz short loc_80483BB
.text:080483C9 push ebp
.text:080483CA mov ebp, esp
.text:080483CC sub esp, 14h
.text:080483CF push eax
.text:080483D0 call edx
.text:080483D2 add esp, 10h
.text:080483D5 leave
.text:080483D6 jmp register_tm_clones
.text:080483D6 frame_dummy endp

也就是执行完_init函数之后会执行frame_dummy函数,该函数的主要作用就是调用__register_frame_info函数,frame_dummy函数是为__register_frame_info函数设置参数的,并进行错误处理。这里不做详细的分析。函数调用完成后返回到libc_csu_init函数中,接着返回到libc_start_main函数中,我们接着分析源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
int argc, char **argv,
#ifdef LIBC_START_MAIN_AUXVEC_ARG
ElfW(auxv_t) *auxvec,
#endif
__typeof (main) init,
void (*fini) (void),
void (*rtld_fini) (void), void *stack_end)
{
//环境变量和辅助变量的处理
//调用一系列的初始化函数,设置安全相关的参数
//最为重要的是调用了`.init`中存储的函数和`.init_array`中存储的函数
#ifdef SHARED
/* Auditing checkpoint: we have a new object. */
//应该是处理动态链接相关
if (__glibc_unlikely (GLRO(dl_naudit) > 0))
{
struct audit_ifaces *afct = GLRO(dl_audit);
struct link_map *head = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
{
if (afct->preinit != NULL)
afct->preinit (&head->l_audit[cnt].cookie);

afct = afct->next;
}
}
#endif

#ifdef SHARED
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS))
GLRO(dl_debug_printf) ("\ntransferring control: %s\n\n", argv[0]);
#endif

#ifndef SHARED
_dl_debug_initialize (0, LM_ID_BASE);
#endif
#ifdef HAVE_CLEANUP_JMP_BUF
/* Memory for the cancellation buffer. */
struct pthread_unwind_buf unwind_buf;

int not_first_call;
not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf);
if (__glibc_likely (! not_first_call))
{
struct pthread *self = THREAD_SELF;

/* Store old info. */
unwind_buf.priv.data.prev = THREAD_GETMEM (self, cleanup_jmp_buf);
unwind_buf.priv.data.cleanup = THREAD_GETMEM (self, cleanup);

/* Store the new cleanup handler info. */
THREAD_SETMEM (self, cleanup_jmp_buf, &unwind_buf);

/* Run the program. */
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
}
else
{
/* Remove the thread-local data. */
# ifdef SHARED
PTHFCT_CALL (ptr__nptl_deallocate_tsd, ());
# else
extern void __nptl_deallocate_tsd (void) __attribute ((weak));
__nptl_deallocate_tsd ();
# endif

/* One less thread. Decrement the counter. If it is zero we
terminate the entire process. */
result = 0;
# ifdef SHARED
unsigned int *ptr = __libc_pthread_functions.ptr_nthreads;
# ifdef PTR_DEMANGLE
PTR_DEMANGLE (ptr);
# endif
# else
extern unsigned int __nptl_nthreads __attribute ((weak));
unsigned int *const ptr = &__nptl_nthreads;
# endif

if (! atomic_decrement_and_test (ptr))
/* Not much left to do but to exit the thread, not the process. */
__exit_thread ();
}
#else
/* Nothing fancy, just call the function. */
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
#endif

exit (result);
}

然后根据是否设置了HAVE_CLEANUP_JMP_BUF来调用main函数,函数的返回结果存储在了result变量中。调用完毕main函数之后将函数的返回结果作为参数传递到exit函数中,函数位于stdlib/exit.c文件中

exit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true);
}
libc_hidden_def (exit)

void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit)
{
/* First, call the TLS destructors. */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
//依次调用tls_dtor_list中存储的函数指针
__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 (*listp != NULL)
{
struct exit_function_list *cur = *listp;

while (cur->idx > 0)
{
const struct exit_function *const f =
&cur->fns[--cur->idx];
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:
cxafct = f->func.cxa.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (cxafct);
#endif
cxafct (f->func.cxa.arg, status);
break;
}
}

*listp = cur->next;
if (*listp != NULL)
/* Don't free the last element in the chain, this is the statically
allocate element. */
free (cur);
}
//清理IO。即调用_IO_cleanup
if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());

_exit (status);
}

函数首先调用了__call_tls_dtors,该函数会依次调用tls_dtor_list中存储的函数指针,接着按照顺序调用了之前在at_exit中注册的函数,我们跟踪函数的调用过程,首先调用的是dl_fini也就是传入的fini函数,该函数定义在elf/dl-fini.c文件中,函数的主要作用是释放程序链接的共享库。在该函数中我们需要注意的是下面的代码

1
__rtld_lock_unlock_recursive (GL(dl_load_lock));

我们看一下函数的定义

1
2
3
4
5
6
7
8
# define __rtld_lock_lock_recursive(NAME) \
GL(dl_rtld_lock_recursive) (&(NAME).mutex)

# if IS_IN (rtld)
# define GL(name) _rtld_local._##name
# else
# define GL(name) _rtld_global._##name
# endif

__rtld_lock_unlock_recursivertld_global结构体中的一个函数指针。

函数还逆序调用了.fini_array中的函数

1
2
3
4
5
6
7
8
9
10
11
/* 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_t) array[i]) ();
}

执行完毕之后执行了程序中的_fini函数,也就是保存在.fini段中的函数。执行完毕之后返回到__run_exit_handlers函数,最后调用IO_cleanup和系统调用_exit,至此程序完全退出。

那么针对exit函数的利用现在可以发现有两种方式,一种是利用调用dl_fini函数,覆盖rtld_global结构体中的__rtld_lock_unlock_recursive指针;另一种是利用调用IO_cleanup->IO_flush_all_lockup函数覆盖stdout中的vtable指针

参考

Linux X86 程序启动 – main函数是如何被执行的?