what is _rtld_global & process exit()

https://dreamhack.io/wargame/challenges/35

해당 문제를 풀면서 배운점을 다룬다.

우리는 Arbitary writable 취약점이 있으면 주로 다음과 같은 생각을 한다.

got overwrite이나 스택에 있는 값이나 변수의 값을 바꿀 생각을 한다. 그런데 만약

스택의 주소도 알 수 없고 PIE addr leak도 불가능하다면, 어떻게 할것인가

이 문제에 답이 있었다.

https://jangjongmin.oopy.io/488dd876-1c43-46b7-8310-8703f47f1221#52fa4e57-93db-443c-a65f-6055159275a6

위 블로그를 참고했다.

우리가 main 함수가 끝나고 프로세스가 종료될때 exit 함수가 실행되는 것은 모두 알것이다.

그러면 exit 함수 내부에서 어떤 일이 일어나는지도 아는가? 하면 모른다고 할것이다.

이에 대한 약간의 분석을 가미해서 알아보자

프로그램이 시작될때는

_start > __libc_start_main > main

다음과 같은 방향으로 진행이 된다. 그렇다면 당연히 main 함수가 끝나면 __libc_start_main 으로 돌아가게 된다.

프로그램이 종료될때는

__libc_start_main → __GI_exit → __run_exit_handlers

이 순서로 진행이 되며, __run_exit_handlers 에서 call $rdx 에서 _dl_fini 함수가 호출된다.

_dl_fini 함수에서는 call *0xAAA(rip) 가 호출되는데 0xAAA(rip) 가 _rtld_global

영역이다. 즉 _rtld_global 영역에 있는 어떤 값으로 call을 하겠다는 것이다.

what is _rtld_global

그럼 도대체 이 _rtld_global 가 뭘까? 바로

동적 로더(ld.so)가 사용하는 전역 상태 정보를 담고 있는 비공개 내부 전역 구조체 이다.

동적 라이브러리 로딩, TLS , etc 등의 여러가지 정보들이 담겨있는 구조체이다.

C
pwndbg> p _rtld_global
$1 = {
  _dl_ns = {{
      _ns_loaded = 0x7ffff7ffe168, 
      _ns_nloaded = 4, 
      _ns_main_searchlist = 0x7ffff7ffe420, 
      _ns_global_scope_alloc = 0, 
      _ns_unique_sym_table = {
        lock = {
          mutex = {
            __data = {
              __lock = 0, 
              __count = 0, 
              __owner = 0, 
              __nusers = 0, 
              __kind = 1, 
              __spins = 0, 
              __elision = 0, 
              __list = {
                __prev = 0x0, 
                __next = 0x0
              }
            }, 
            __size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>, 
            __align = 0
          }
        }, 
        entries = 0x0, 
        size = 0, 
        n_elements = 0, 
        free = 0x0
      }, 
      _ns_debug = {
        r_version = 0, 
        r_map = 0x0, 
        r_brk = 0, 
        r_state = RT_CONSISTENT, 
        r_ldbase = 0
      }
    }, {
      _ns_loaded = 0x0, 
      _ns_nloaded = 0, 
      _ns_main_searchlist = 0x0, 
      _ns_global_scope_alloc = 0, 
      _ns_unique_sym_table = {
        lock = {
          mutex = {

... ( 생략 )

다음과 같이 정말 많은 멤버를 가지고 있는 구조체이다. 이 구조체는

GOT 후킹 우회

TLS 조작

등등을 가지고 올 수 있음으로 꼭 기억해두어야 한다.

그러면 _dl_fini함수에서 call *0xAAA(rip) 이 코드가 어떻게 나왔는 가를 분석을 해야 하는데

https://jangjongmin.oopy.io/488dd876-1c43-46b7-8310-8703f47f1221#52fa4e57-93db-443c-a65f-6055159275a6

이 블로그를 참고했다.

C
_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_fini 함수에서 __rtld_lock_lock_recursive 함수를 부르고 인자로 dl_load_lock 을 가지고 있다. 이 부분에서

C
# define __rtld_lock_lock_recursive(NAME) \
  GL(dl_rtld_lock_recursive) (&(NAME).mutex)

매크로가 불러지며

GL(dl_rtld_lock_recursive) (&(NAME).mutex)

이 코드는

GL→ 전역변수에 접근할때 사용

&(NAME).mutex) → NAME 에 뮤텍스락을 건다.

따라서 _rtld_global._dl_rtld_lock_recursive 전역변수의 값을 가지고 오고 이를 함수로 실행시키면서 인자로 들어온 것에 대한 mutex락을 건다는 것

전역변수를 가져오면서 해당 값을 실행시키니깐 call *0xAAA(rip) 이런식의 코드가 되는 것이고 이는 해당 전역변수 위치의 값을 overwrtie 하면 내가 control flow 조작이 가능하게 된다는 것이다. 또한 인자로 오는 dl_load_lock 의 위치에 있는 값을 내가 조작한다면 내가 인자도 조작할 수 있다.

즉, dl_rtld_lock_recursive 값을 one gadget 주소 혹은 system 함수 주소로 바꾸고

dl_load_lock 위치에 “/bin/sh” 값을 덮어쒸우면 익스가 가능하다.

참고로 dl_load_lock 이 data 영역이어서 “/bin/sh” 의 위치를 넣는 것이 아니라 해당 위치에 직접 문자열을 넣어야 한다.

좀더 자세히 알고 싶다면

https://learn.dreamhack.io/269#6

해당 페이지를 참고하자

C
pwndbg> p _rtld_global._dl_rtld_lock_recursive
$4 = (void (*)(void *)) 0x7ffff7dd7c90 <rtld_lock_default_lock_recursive>
pwndbg> disass 0x7ffff7dd7c90
Dump of assembler code for function rtld_lock_default_lock_recursive:
   0x00007ffff7dd7c90 <+0>:	add    DWORD PTR [rdi+0x4],0x1
   0x00007ffff7dd7c94 <+4>:	ret    

보시다시피 일반적인 flow에서는 0x7ffff7dd7c90 와 같은 함수의 위치가 저장이 되어 있고

dl_load_lock 를 rdi로 받는 일반적인 함수임을 알 수 있다. 우리는 0x7ffff7dd7c90 이 값을 overwrite 함으로써 exploit 코드를 작성할 수 있는 것이다

보안패치

glibc 2.34부터 __rtld_lock_recursive 관련 구조가 대대적으로 정리되었다. 따라서 안됨