// Name: rtld.c
// Compile: gcc -o rtld rtld.c
int main() {
return 0;
}
gef➤ disas main
Dump of assembler code for function main:
0x00005555554005fa <+0>: push rbp
0x00005555554005fb <+1>: mov rbp,rsp
=> 0x00005555554005fe <+4>: mov eax,0x0
0x0000555555400603 <+9>: pop rbp
0x0000555555400604 <+10>: ret
End of assembler dump.
gef➤ b * main+10
Breakpoint 1 at 0x555555400604
gef➤ r
gef➤ si
→ 0x7ffff7a03c87 <__libc_start_main+231> mov edi, eax
0x7ffff7a03c89 <__libc_start_main+233> call 0x7ffff7a25110 <__GI_exit>
main 함수 ret에 breakpoint를 걸고 step into를 통해 다음 코드를 살펴보면 libc_start_main+231의 코드가 실행되고, GI_exit 함수가 호출됩니다.
step into로 __GI_exit 함수 내부를 봐보면
→ 0x7ffff7a03c89 <__libc_start_main+233> call 0x7ffff7a25110 <__GI_exit>
↳ 0x7ffff7a25110 <exit+0> lea rsi, [rip+0x3a8601] # 0x7ffff7dcd718 <__exit_funcs>
0x7ffff7a25117 <exit+7> sub rsp, 0x8
0x7ffff7a2511b <exit+11> mov ecx, 0x1
0x7ffff7a25120 <exit+16> mov edx, 0x1
0x7ffff7a25125 <exit+21> call 0x7ffff7a24ec0 <__run_exit_handlers>
또 다른 __run_exit_handlers 함수가 등장합니다.
exit_function 구조체의 멤버 변수에 따른 함수 포인터를 호출합니다. 리턴 명령어를 실행해 프로그램을 종료한다면 _dl_fini 함수를 호출합니다.
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
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;
}
}
struct exit_function
{
/* `flavour' should be of type of the `enum' above but since we need
this element in an atomic operation we have to use `long int'. */
long int flavor;
union
{
void (*at) (void);
struct
{
void (*fn) (int status, void *arg);
void *arg;
} on;
struct
{
void (*fn) (void *arg, int status);
void *arg;
void *dso_handle;
} cxa;
} func;
};
_dl_load_lock을 인자로 __rtld_lock_lock_recursive 함수를 호출합니다. 매크로를 보면 해당 함수는 dl_rtld_lock_recursive라는 함수 포인터입니다.
해당 함수 포인터는 __rtld_global 구조체의 멤버 변수 입니다.
# define __rtld_lock_lock_recursive(NAME) \
GL(dl_rtld_lock_recursive) (&(NAME).mutex)
void
_dl_fini (void)
{
#ifdef SHARED
int do_audit = 0;
again:
#endif
for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
{
/* Protect against concurrent loads and unloads. */
__rtld_lock_lock_recursive (GL(dl_load_lock));
_rtld_global 구조체 내 _dl_rtld_lock_recursive 함수 포인터에는 rtld_lock_default_lock_recursive 함수 주소를 저장하고 있습니다. 구조체의 함수 포인터가 저장된 영역은 읽기 및 쓰기 권한이 존재하기 때문에 덮어쓰는 것이 가능합니다.
gdb-peda$ p _rtld_global
_dl_load_lock = {
mutex = {
__data = {
__lock = 0x0,
__count = 0x0,
__owner = 0x0,
__nusers = 0x0,
__kind = 0x1,
__spins = 0x0,
__elision = 0x0,
__list = {
__prev = 0x0,
__next = 0x0
}
},
__size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>,
__align = 0x0
}
},
_dl_rtld_lock_recursive = 0x7ffff7dd60e0 <rtld_lock_default_lock_recursive>,
...
}
gdb-peda$ p &_rtld_global._dl_rtld_lock_recursive
$2 = (void (**)(void *)) 0x7ffff7ffdf60 <_rtld_global+3840>
gdb-peda$ vmmap 0x7ffff7ffdf60
Start End Perm Name
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.27.so
프로세스를 로드할 대 호출되는 dl_main 코드 일부를 봐보면,
_rtld_global 구조체의 dl_rtld_lock_recursive 함수 포인터가 초기화 되는 것을 확인할 수 있습니다.
static void
dl_main (const ElfW(Phdr) *phdr,
ElfW(Word) phnum,
ElfW(Addr) *user_entry,
ElfW(auxv_t) *auxv)
{
GL(dl_init_static_tls) = &_dl_nothread_init_static_tls;
#if defined SHARED && defined _LIBC_REENTRANT \
&& defined __rtld_lock_default_lock_recursive
GL(dl_rtld_lock_recursive) = rtld_lock_default_lock_recursive;
GL(dl_rtld_unlock_recursive) = rtld_lock_default_unlock_recursive;