what is glibc Relocation section

우리가 흔히 바이너리에서 재배치 영역을 볼 수 있다. 그렇다면, glibc에서도 got 영역이 존재할까?

결론은 존재한다.

일단 우리가 흔히 아는 got 영역인 R_X86_64_JUMP_SLOT 가 존재한다.

R_X86_64_JUMP_SLOT

C

(pwnenv) root@9ac1fe20f27d:/home/gaia# objdump -R ./libc.so.6 | grep "R_X86_64_JUMP_SLOT"
000000000021a028 R_X86_64_JUMP_SLOT  realloc@@GLIBC_2.2.5
000000000021a038 R_X86_64_JUMP_SLOT  _dl_exception_create@GLIBC_PRIVATE
000000000021a050 R_X86_64_JUMP_SLOT  calloc@@GLIBC_2.2.5
000000000021a088 R_X86_64_JUMP_SLOT  _dl_find_dso_for_object@GLIBC_PRIVATE
000000000021a0c8 R_X86_64_JUMP_SLOT  _dl_deallocate_tls@GLIBC_PRIVATE
000000000021a0d0 R_X86_64_JUMP_SLOT  __tls_get_addr@GLIBC_2.3
000000000021a0f0 R_X86_64_JUMP_SLOT  _dl_fatal_printf@GLIBC_PRIVATE
000000000021a138 R_X86_64_JUMP_SLOT  _dl_audit_symbind_alt@GLIBC_PRIVATE
000000000021a168 R_X86_64_JUMP_SLOT  _dl_rtld_di_serinfo@GLIBC_PRIVATE
000000000021a170 R_X86_64_JUMP_SLOT  _dl_allocate_tls@GLIBC_PRIVATE
000000000021a178 R_X86_64_JUMP_SLOT  __tunable_get_val@GLIBC_PRIVATE
000000000021a1a0 R_X86_64_JUMP_SLOT  _dl_allocate_tls_init@GLIBC_PRIVATE
000000000021a1a8 R_X86_64_JUMP_SLOT  __nptl_change_stack_perm@GLIBC_PRIVATE
000000000021a1b8 R_X86_64_JUMP_SLOT  _dl_audit_preinit@GLIBC_PRIVATE

바로 확인을 해보면 다음과 같이 got 영역이 존재하는 것을 할 수 있다. 근데 자세히 보면 실질적인 함수는 realloc , calloc , __tls_get_addr 이고 나머지는 모두다 로더에 관련된 함수인거 같다. → GLIBC_PRIVATE 심볼이 있음

이러한 got 영역이 왜 존재 할까?

  1. 교차 DSO 호출: 예컨대 libc → 동적 로더 ld-linux의 GLIBC_PRIVATE 심볼(예: _dl_allocate_tls) 같은 다른 DSO의 함수를 부를 때. 이 경우 당연히 동적 해석이 필요해 JUMP_SLOT가 생김.
  2. 심볼 인터포지션 보장: glibc의 어떤 공개 심볼은 사용자 LD_PRELOAD 등으로 가로채기(interpose) 가능해야 해. 그런 **“외부에서 바뀔 수 있는 호출”**은 PLT 경유를 유지해야 하므로 JUMP_SLOT가 남는다. 반대로, 내부 전용 호출은 비용을 줄이려고 glibc가 visibility를 hidden으로 만들고(예: libc_hidden_*, __GI_* 별칭) PLT를 우회한다. 그래서 glibc 안의 “자기 자신 호출”엔 JUMP_SLOT가 의외로 적다.

주로 다음과 같은 이유가 있을것이다.

하지만, 이것만으로는 부족하다고 생각할 수 있다. 왜냐하면 우리가 공격을 위해서 알아야 할 got 영역은 이것말고도 훨씬 많기 때문이다.

R_X86_64_GLOB_DAT

C
0000000000219dd8 R_X86_64_GLOB_DAT  svc_max_pollfd@GLIBC_2.2.5
0000000000219de0 R_X86_64_GLOB_DAT  obstack_alloc_failed_handler@@GLIBC_2.2.5
0000000000219df0 R_X86_64_GLOB_DAT  __ctype_toupper@GLIBC_2.2.5
0000000000219e00 R_X86_64_GLOB_DAT  loc1@GLIBC_2.2.5
0000000000219e08 R_X86_64_GLOB_DAT  _dl_argv@GLIBC_PRIVATE
0000000000219e18 R_X86_64_GLOB_DAT  __libc_single_threaded@@GLIBC_2.32
0000000000219e20 R_X86_64_GLOB_DAT  free@@GLIBC_2.2.5
0000000000219e28 R_X86_64_GLOB_DAT  re_syntax_options@@GLIBC_2.2.5
0000000000219e30 R_X86_64_GLOB_DAT  rpc_createerr@GLIBC_2.2.5
0000000000219e38 R_X86_64_GLOB_DAT  stdout@@GLIBC_2.2.5
0000000000219e40 R_X86_64_GLOB_DAT  __ctype32_toupper@GLIBC_2.2.5
0000000000219e48 R_X86_64_GLOB_DAT  opterr@@GLIBC_2.2.5
0000000000219e58 R_X86_64_GLOB_DAT  getdate_err@@GLIBC_2.2.5

흔히 아는 심볼들이 이 재배치 영역 R_X86_64_GLOB_DAT 에 존재한다. 물론 공격에 사용하기 힘들다는 단점이 있다.

R_X86_64_IRELATIVE

이제 본격적인 시작인데 일단 IFUNC 이라는 개념을 알아야한다. 이 부분은 다음글에 정리를 했다.

이제 그럼 IFUNC을 사용하기 위해서는 plt → got를 거치게 된다. ( glibc 내에서도 ) 따라서 got 영역이 생기고 plt 코드도 생긴다. 가장 유명한 부분 ( keyme 님이 사용한 조각 코드 ) 를 보면

C
pwndbg> x/10i 0x7ffff7d92000 + 0x2CDC4
   0x7ffff7dbedc4 <__gconv_read_conf+756>:	lea    0x10(%r8),%rdi
   0x7ffff7dbedc8 <__gconv_read_conf+760>:	mov    %rbx,%rsi
   0x7ffff7dbedcb <__gconv_read_conf+763>:	mov    %r8,0x8(%rsp)
   0x7ffff7dbedd0 <__gconv_read_conf+768>:	sub    %rbx,%rbp
   0x7ffff7dbedd3 <__gconv_read_conf+771>:	call   0x7ffff7dba620 <*ABS*+0xa9c10@plt>

다음 부분을 사용한 것을 알 수 있는데

iconv/gconv_conf.c 파일의

C
new_alias->fromname = memcpy ((char *) new_alias + sizeof (struct gconv_alias),   from, wp - from);

이 부분이다. 즉, memcpy가 대표적인 IFUNC 이기 때문에 다음과 같이 plt가 심볼이 없이 ( ABS ) 보이고

C
pwndbg> disass 0x7ffff7dba620
Dump of assembler code for function *ABS*+0xa9c10@plt:
   0x00007ffff7dba620 <+0>:	endbr64 
   0x00007ffff7dba624 <+4>:	bnd jmp *0x1f1b35(%rip)        # 0x7ffff7fac160 <*ABS*@got.plt>
   0x00007ffff7dba62b <+11>:	nopl   0x0(%rax,%rax,1)

저 plt 함수 안을 보면 0x7ffff7fac160 와 같은 got 영역을 사용한다는 것을 알 수 있다.

C
000000000021a160 R_X86_64_IRELATIVE  *ABS*+0x00000000000a9c10

따라서 이러한 IFUNC 관련 got가 재배치 영역에 들어간다.

000000000021a160 → got 영역 R_X86_64_IRELATIVE → IFUNC 관련 섹션 *ABS*+0x00000000000a9c10 → 리졸버 함수의 위치

여기서 리졸버 함수란..

glibc의 R_X86_64_IRELATIVE 영역은 lazy-binding이 아니다. 즉, 바로 got 영역을 채우면서 시작된다.

이 채우기 위해서 함수를 ( ex. memcpy_evec , memcpy_avx .. .etc ) 컴퓨터의 맞게 선정하기 위한 함수가 리졸버 함수이다.

그 위치가 *ABS*+0x00000000000a9c10 라는 뜻.

여기서 ABS는 단지 심볼이 없다는 의미이다. 실제 계산은 단순히 lb + 0x00000000000a9c10 처럼 계산된다.

C
pwndbg> disass 0xa9c10 + 0x7ffff7d92000
Dump of assembler code for function __new_memcpy_ifunc:
   0x00007ffff7e3bc10 <+0>:	endbr64 
   0x00007ffff7e3bc14 <+4>:	mov    0x1702ad(%rip),%rcx        # 0x7ffff7fabec8
   0x00007ffff7e3bc1b <+11>:	lea    0x1acbe(%rip),%rax        # 0x7ffff7e568e0 <__memmove_erms>
   0x00007ffff7e3bc22 <+18>:	mov    0x1a4(%rcx),%edx
   0x00007ffff7e3bc28 <+24>:	test   $0x48,%dh
   0x00007ffff7e3bc2b <+27>:	jne    0x7ffff7e3bca3 <__new_memcpy_ifunc+147>
   0x00007ffff7e3bc2d <+29>:	mov    0xb8(%rcx),%esi
   0x00007ffff7e3bc33 <+35>:	test   $0x10000,%esi
   0x00007ffff7e3bc39 <+41>:	jne    0x7ffff7e3bca8 <__new_memcpy_ifunc+152>
   0x00007ffff7e3bc3b <+43>:	test   $0x2,%dh
   0x00007ffff7e3bc3e <+46>:	je     0x7ffff7e3bc80 <__new_memcpy_ifunc+112>
   0x00007ffff7e3bc40 <+48>:	test   %esi,%esi
   0x00007ffff7e3bc42 <+50>:	js     0x7ffff7e3bcf8 <__new_memcpy_ifunc+232>
   0x00007ffff7e3bc48 <+56>:	test   $0x800,%esi
   0x00007ffff7e3bc4e <+62>:	je     0x7ffff7e3bc70 <__new_memcpy_ifunc+96>
   0x00007ffff7e3bc50 <+64>:	and    $0x200,%esi

실제호 해당 영역을 확인해 보면 리졸버 함수가 있는 것을 확인 할 수 있다.

근데

C
000000000021a160 R_X86_64_IRELATIVE  *ABS*+0x00000000000a9c10

이런식으로 보면 어떤 IFUNC 의 got 인지 확인할 수가 없다. 이럴때 0x00000000000a9c10 와 같은 리졸버의 함수 위치를 심볼로 검색을 하면

C
readelf -W -s libc.so.6 | awk '$4=="IFUNC"{printf "0x%s %s\n",$2,$8}' | grep -i a9c10
C
(pwn) amemoyoi ~$ readelf -W -s libc.so.6 | awk '$4=="IFUNC"{printf "0x%s %s\n",$2,$8}' | grep -i a9c10
0x00000000000a9c10 memcpy@@GLIBC_2.14

다음과 같이 어떤 함수인지 나온다.