Background: _rtld_global
이번에는 프로그램을 종료하는 과정을 이용한 공격 기법을 알아보기에 앞 라이브러리의 코드를 분석하면서 어떤 방식으로 프로세스를 종료하는지 알아보도록 하자.
다음 코드는 종료하는 과정을 알아보기 위한 예제로, 컴파일해두자.
// Name: rtld.c
// Compile: gcc -o rtld rtld.c
int main() {
return 0;
}
_rtld_global
- __Gl_exit
바로 위에서 컴파일한 예제 코드는 별다른 코드를 실행하지 않고 프로그램을 종료한다. 프로그램을 종료할 때에는 우리가 모르는 많은 코드들이 내부적으로 실행되는데, 디버깅으로 한번 살펴보자.
먼저 다음과 같이 main 함수 내 리턴하는 명령어에 BP를 설정하고, step into를 통해 다음 코드를 확인해본다.
디버깅 결과를 살펴보면, main 함수 내에서 리턴 명령어를 실행하면 스택 최상단에 있는 __libc_start_main+231의 코드가 실행되고, 내부에서 __GI_exit 함수를 호출하는 것을 볼 수 있다.
__GI_exit 함수 내부에서 또 다른 함수를 호출하는지 확인하기 위해 다시 한번 step into 명령어를 실행한다.
다음은 __GI_exit 함수 내부의 모습으로, 또 다른 __run_exit_handlers 함수가 보인다.
이 __run_exit_handlers 함수는 코드의 크기가 크므로, 라이브러리 코드를 통해 분석해보자.
- __run_exit_handlers
다음은 __run_exit_handlers 함수의 코드로 exit_function 구조체의 멤버 변수에 따른 함수 포인터를 호출한다.
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;
}
}
해당 구조체의 모습은 아래와 같으며, 예제와 같이 리턴 명령어를 실행해 프로그램을 종료한다면 _dl_fini 함수를 호출한다.
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_fini
다음은 로더에 존재하는 _dl_fini 함수 코드의 일부이다.
# 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));
코드를 살펴보면, _dl_load_lock을 인자로 __rtld_lock_lock_recursive 함수를 호출하는 것을 볼 수 있고, 매크로를 보면, 해당 함수는 dl_rtld_lock_recursive라는 함수 포인터임도 알 수 있다.
이 함수 포인터는 _rtld_global 구조체의 멤버 변수이다. 해당 구조체는 매우 방대하기 때문에 함수 포인터와 전달되는 인자인 dl_load_lock만을 살펴보자.
- _rtld_global
다음은 디버깅에서 _rtld_global 구조체를 출력한 내용이다.
구조체 내 _dl_rtld_lock_recursive 함수 포인터에는 rtld_lock_default_lock_recursive 함수 주소를 저장하고 있다. 구조체의 함수 포인터가 저장된 영역은 읽기 및 쓰기 권한이 존재하기 때문에 덮어쓰는 것도 가능하다.
_rtld_global 초기화
다음은 프로세스를 로드할 때 호출되는 dl_main 코드의 일부이다.
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;
_rtld_global 구조체의 dl_rtld_lock_recursive 함수 포인터가 초기화되는 것을 확인할 수 있다.
'Security > System Hacking' 카테고리의 다른 글
[Dreamhack System Hacking Advanced] STAGE 4 - 혼자 실습 (0) | 2022.09.25 |
---|---|
[Dreamhack System Hacking Advanced] STAGE 4 - 함께 실습 (0) | 2022.09.25 |
[Dreamhack System Hacking Advanced] STAGE 3 - 혼자 실습 (0) | 2022.09.18 |
[Dreamhack System Hacking Advanced] STAGE 3 - 함께 실습 (0) | 2022.09.18 |
[Dreamhack System Hacking Advanced] STAGE 3 (0) | 2022.09.18 |
댓글