개념 정리

_rtld_global

J4guar 2022. 4. 28. 20:15
728x90
반응형

프로세스가 등록되거나 종료될 때 쓰이는 변수와 영역들을 알고 있다면 이를 이용한 새로운 공격 기법을 개발할 수 있다고 한다.

그런 의미에서 프로세스를 어떤 방식으로 종료하는지 확인해보자

#include<stdio.h>

int main(){
        return 0;
}

위와 같은 프로세스를 gdb를 통해 분석해보자

ret 명령에서 step into를 통해 들어가보면 __libc_start_main+243의 코드가 실행되고 이어 __GI_exit함수가 호출된다.

__GI_exit에는 __run_exit_handlers 함수가 존재하는 것을 확인할 수 있다.

 

__run_exit_handlers

/* Call all functions registered with `atexit' and `on_exit',
   in the reverse of the order in which they were registered
   perform stdio cleanup, and terminate program execution with STATUS.  */
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
		     bool run_list_atexit, bool run_dtors)
{
  /* First, call the TLS destructors.  */
#ifndef SHARED
  if (&__call_tls_dtors != NULL)
#endif
    if (run_dtors)
      __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 (true)
    {
      struct exit_function_list *cur;

      __libc_lock_lock (__exit_funcs_lock);

    restart:
      cur = *listp;

      if (cur == NULL)
	{
	  /* Exit processing complete.  We will not allow any more
	     atexit/on_exit registrations.  */
	  __exit_funcs_done = true;
	  __libc_lock_unlock (__exit_funcs_lock);
	  break;
	}

      while (cur->idx > 0)
	{
	  struct exit_function *const f = &cur->fns[--cur->idx];
	  const uint64_t new_exitfn_called = __new_exitfn_called;

	  /* Unlock the list while we call a foreign function.  */
	  __libc_lock_unlock (__exit_funcs_lock);
	  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:
	      /* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
		 we must mark this function as ef_free.  */
	      f->flavor = ef_free;
	      cxafct = f->func.cxa.fn;
#ifdef PTR_DEMANGLE
	      PTR_DEMANGLE (cxafct);
#endif
	      cxafct (f->func.cxa.arg, status);
	      break;
	    }
	  /* Re-lock again before looking at global state.  */
	  __libc_lock_lock (__exit_funcs_lock);

	  if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
	    /* The last exit function, or another thread, has registered
	       more exit functions.  Start the loop over.  */
	    goto restart;
	}

      *listp = cur->next;
      if (*listp != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (cur);

      __libc_lock_unlock (__exit_funcs_lock);
    }

  if (run_list_atexit)
    RUN_HOOK (__libc_atexit, ());

  _exit (status);
}

exit_function 구조체의 멤버 변수에 따른 함수 포인터를 호출한다.

 

return 명령어를 통해 프로세스를 종료한다면 _dl_fini함수를 호출한다.

_dl_fini

위의 코드는 _dl_fini함수의 일부분으로 _dl_load_lock을 인자로 __rtld_lock_lock_recursive함수를 호출하고 있다.

해당 함수는 dl_rtld_lock_recursive라는 함수 포인터이다.

해당 함수 포인터는 _rtld_global구조체의 멤버 변수이다.

 

다음은 gdb-peda에서 살펴본 _rtld_global구조체를 출력한 모습이다.

해당 구조체의 함수 포인터가 저장된 영역은 읽기 및 쓰기 권한이 존재하기 때문에 덮어써서 실행흐름을 제어할 수 있습니다.

 

프로세스를 로드할 때 호출되는 dl_main코드의 일부로, _rtld_global구조체의 dl_rtld_lock_recursive함수 포인터가 초기되는 것을 확인할 수 있다.

_rtld_global overwrite

1. 라이브러리 및 로더 베이스 주소 계산

Loader base address = Loader address - libc base address

2. _rtld_global 구조체 계산

gdb-peda$ p &_rtld_global._dl_load_lock

gdb-peda$ p &_rtld_global._dl_rtld_lock_recursive

Loader base address + rtld_global 구조체의 심볼 주소 = _rtld_global 구조체 주소

_dl_load_lock과 _dl_rtld_lock_recursive 함수 포인터 주소 구하기

3. _rtld_global 구조체 조작

프로그램을 종료시 _rtld_global 구조체의 _dl_load_lock을 인자로 _dl_rtld_lock_recursive 함수 포인터를 호출합니다.

따라서 dl_load_lock에 "/bin/sh" 또는 "sh" 문자열을 삽입하고 _dl_rtld_lock_recursive를 system 함수로 덮어쓰면 shell을 획득할 수 있습니다.

또는  _dl_rtld_lock_recursive에 one_shot gadget으로 덮어써서 shell을 획득할 수도 있습니다.

 

Reference

 
반응형