Post

GOT 동작분석

  • PLT(Procedure Linkage Table)

    프로시저 연결 테이블. 외부 라이브러리에 있는 프로시저와 연결해주는 테이블이다.

  • GOT(Global Offset Table)

    프로시저들의 주소가 저장되어 있는 테이블이다.

CET mitigation 해제 후 실습 -fcf-protection=none ; CET mitigation 해제 option (https://dypar-study.tistory.com/84)

처음 호출 시

PLT에서 GOT를 참조하여 호출하는데, 처음 호출 시에는 GOT에 호출하고자 하는 함수의 주소가 없다. 따라서 라이브러리에서 함수의 주소를 찾는 작업을 수행하게 된다.

먼저 PLT 호출부터 시작하여 라이브러리에서 원래함수를 찾는 과정을 서술한다.

1
2
3
4
5
6
   0x401030 <puts@plt+0>     jmp    QWORD PTR [rip+0x2fe2]        # 0x404018 <puts@got.plt>
    0x401036 <puts@plt+6>     push   0x0
    0x40103b <puts@plt+11>    jmp    0x401020

    gef  x/gx 0x404018
	  0x404018 <puts@got.plt>:	0x0000000000401036

1. PLT

  1. puts@plt를 호출

    1
    
        0x401134 <main+14>        call   0x401030 <puts@plt>
    
  2. puts@got.plt로 점프

    0x404018 <puts@got.plt>를 보면 알 수 있듯이 puts의 got로 점프하는 것을 볼 수 있다.

    1
    2
    3
    
        0x401030 <puts@plt+0>     jmp    QWORD PTR [rip+0x2fe2]        # 0x404018 <puts@got.plt>
         0x401036 <puts@plt+6>     push   0x0
         0x40103b <puts@plt+11>    jmp    0x401020
    

    알다시피 처음 호출할 때는 got에 실제 함수의 주소가 없다.

    그래서 참조하려는 got의 주소를 보면 0x401036 인데 이는 puts@plt+6 임을 알 수 있다.

    1
    2
    
     gef  x/gx 0x404018
     0x404018 <puts@got.plt>:	0x0000000000401036
    
  3. puts@plt+6으로 점프

    그리고 나서 0x0을 push 하고 0x401020 로 점프한다.

    여기서 0x0reloc_offset이다.

  4. 0x401020으로 점프

    0x404008에 있는 값을 stack에 push하고 0x404010에 저장된 주소로 점프를 하게된다.

    1
    2
    
        0x401020                  push   QWORD PTR [rip+0x2fe2]        # 0x404008
         0x401026                  jmp    QWORD PTR [rip+0x2fe4]        # 0x404010
    

    그리고 각 주소에 어떤 값이 들어있는 지 확인해보았다.

    • 0x404008 : 0x7ffff7ffe2e0
    • 0x404010 : 0x7ffff7fd8d30 <_dl_runtime_resolve_xsavec>
    1
    2
    3
    4
    5
    6
    7
    8
    
     gef  x/gx 0x404008
     0x404008:	0x00007ffff7ffe2e0
        
     gef  x/gx 0x404010
     0x404010:	0x00007ffff7fd8d30
        
     gef  x/i 0x00007ffff7fd8d30
        0x7ffff7fd8d30 <_dl_runtime_resolve_xsavec>:	endbr64
    

    최종적으로 _dl_runtime_resolve_xsavec 을 호출하며 위에서 stack에 push된 2개의 값들이 매개변수로 들어가게 된다.

1번째로 push 된 reloc_offset은 무엇인가?


현재 실행 파일에서 호출하는 함수들 중 라이브러리 함수에 대한 index 값을 나타낸 것이다.

  • readreloc_offset

    1
    2
    3
    
         0x401050 <read@plt+0>     jmp    QWORD PTR [rip+0x2fd2]        # 0x404028 <read@got.plt>
          0x401056 <read@plt+6>     push   0x2
          0x40105b <read@plt+11>    jmp    0x401020
    
  • putsreloc_offset

    1
    2
    3
    
         0x401030 <puts@plt+0>     jmp    QWORD PTR [rip+0x2fe2]        # 0x404018 <puts@got.plt>
          0x401036 <puts@plt+6>     push   0x0
          0x40103b <puts@plt+11>    jmp    0x401020
    

위와 같이 reloc_offset 값이 라이브러리 함수마다 달라지며 이 값은 고정된 값이 아니다.

2번째로 push된 값은 무엇인가?


결론부터 말하면 2번째로 push된 0x7ffff7ffe2e0link_map이라고 하는 구조체의 주소이다.

그리고 이 주소는 0x404008 에 저장되어 있어서 이 부분에 Hardware Breakpoint를 걸어서 언제 값이 들어오는지 확인했다.

_dl_relocate_object+290의 명령어가 실행이 되고 나서야 link_map 구조체의 주소가 저장되는 것을 볼 수 있다.

1
2
3
4
0x7ffff7fd3e82 <_dl_relocate_object+290> mov    QWORD PTR [rax+0x8], r14

gef  x/gx $rax+0x8
0x404008:	0x00007ffff7ffe2e0

gdb 상에서는 _dl_relocate_object 함수에서 처리하는 것처럼 보이지만 실제 코드를 보면 elf_machine_runtime_setup에서 처리한다.

  • 함수 호출 과정은 다음과 같다.
  • _dl_relocate_objectELF_DYNAMIC_RELOCATEelf_machine_runtime_setup
    • ELF_DYNAMIC_RELOCATE 는 매크로이다.

아래의 코드를 보게되면 매개변수로 받은 link_mapgot[1]에 저장되는 것을 볼 수 있다.

추가적으로 이 때 _dl_runtime_resolvegot[2]에 저장되는 것을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static inline int
elf_machine_runtime_setup (struct link_map *l, int lazy)
{
  extern void _dl_runtime_resolve (Elf32_Word);

  if (lazy)
    {
      /* The GOT entries for functions in the PLT have not yet been filled
         in.  Their initial contents will arrange when called to push an
         offset into the .rel.plt section, push _GLOBAL_OFFSET_TABLE_[1],
         and then jump to _GLOBAL_OFFSET_TABLE[2].  */
      Elf32_Addr *got = (Elf32_Addr *) D_PTR (l, l_info[DT_PLTGOT]);
      got[1] = (Elf32_Addr) l;	/* Identify this shared object.  */

      /* This function will get called to fix up the GOT entry indicated by
         the offset on the stack, and then jump to the resolved address.  */
      got[2] = (Elf32_Addr) &_dl_runtime_resolve;
    }

  return lazy;
}

2. _dl_runtime_resolve_xsavec

_dl_runtime_resolve_xsavecsysdeps/x86_64/dl-trampoline.h**assembly stub으로 작성되어 있고 register를 설정하는 등의 작업을 하는 것 같다.

1
2
3
4
5
6
7
8
9
10
pushq %rbx			# push subtracts stack by 8.
	cfi_adjust_cfa_offset(8)
	cfi_rel_offset(%rbx, 0)
	mov %RSP_LP, %RBX_LP
	cfi_def_cfa_register(%rbx)
	and $-STATE_SAVE_ALIGNMENT, %RSP_LP
# endif
# ifdef REGISTER_SAVE_AREA
	sub $REGISTER_SAVE_AREA, %RSP_LP
...

이런식으로 instruction을 진행하다가 link_mapreloc_offset을 매개변수로 받는 _dl_fixup을 호출한다.

1
2
3
4
5
6
7
8
9
	 0x7ffff7fd8da1 <_dl_runtime_resolve_xsavec+113> mov    rsi, QWORD PTR [rbx+0x10]
   0x7ffff7fd8da5 <_dl_runtime_resolve_xsavec+117> mov    rdi, QWORD PTR [rbx+0x8]
  0x7ffff7fd8da9 <_dl_runtime_resolve_xsavec+121> call   0x7ffff7fd5e70 <_dl_fixup>
───────────────────────────────────────────────────────────────────────────────────────
gef  x/gx $rbx+0x10
0x7fffffffde10:	0x0000000000000000

gef  x/gx $rbx+0x8
0x7fffffffde08:	0x00007ffff7ffe2e0

link_map 구조체는 include/link.h에 정의되어 있다(elf/link.h에 도 같은 이름의 구조체가 정의되어 있지만 프로그램 진행 상 include/link.h의 구조체가 타당한 것 같다, 참고로 elf/link.hlink_map 구조체는 5개의 멤버변수만 정의 되어 있다).

구조체 안에 선언되어 있는 멤버변수들이 굉장히 많은데 중요한 것들만 알아보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct link_map
  {
    /* These first few members are part of the protocol with the debugger.
       This is the same format used in SVR4.  */

    ElfW(Addr) l_addr;		/* Difference between the address in the ELF
				   file and the addresses in memory.  */
    char *l_name;		/* Absolute file name object was found in.  */
    ElfW(Dyn) *l_ld;		/* Dynamic section of the shared object.  */
    struct link_map *l_next, *l_prev; /* Chain of loaded objects.  */

    /* All following members are internal to the dynamic linker.
       They may change without notice.  */

    /* This is an element which is only ever different from a pointer to
       the very same copy of this type for ld.so when it is used in more
       than one namespace.  */
    struct link_map *l_real;
  • l_ld
    • type : Elf64_Dyn
    • .dynamic 섹션의 주소가 저장된다.

      1
      2
      3
      4
      
        gef  x/16gx 0x7ffff7ffe2e0
        0x7ffff7ffe2e0:	0x0000000000000000	0x00007ffff7ffe888
        0x7ffff7ffe2f0:	0x0000000000403e20	<-- l_ld
        ...
      
    • objdump를 통해 .dynamic 섹션의 주소를 확인할 수 있다.

      1
      2
      3
      4
      5
      6
      7
      8
      
        $ objdump -h -j .dynamic testfile
              
        testfile:     file format elf64-x86-64
              
        Sections:
        Idx Name          Size      VMA               LMA               File off  Algn
         20 .dynamic      000001d0  0000000000403e20  0000000000403e20  00002e20  2**3
                          CONTENTS, ALLOC, LOAD, DATA
      
  • l_info
    • type : Elf64_Dyn
    • dynamic 섹션에 대한 인덱싱된 포인터들의 배열.

ELf64_Dyn 구조체


Elf64_Dyn의 구조체는 다음과 같다.

  • 위에서 소개한 l_info는 이러한 구조체가 배열형식으로 되어있고 DT_HASHDT_STRTAB 과 같이 매크로를 통해 배열에 접근한다.
  • 구조체에 대한 정보
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
/* Dynamic section entry.  */

typedef struct
{
  Elf32_Sword	d_tag;			/* Dynamic entry type */
  union
    {
      Elf32_Word d_val;			/* Integer value */
      Elf32_Addr d_ptr;			/* Address value */
    } d_un;
} Elf32_Dyn;

typedef struct
{
  Elf64_Sxword	d_tag;			/* Dynamic entry type */
  union
    {
      Elf64_Xword d_val;		/* Integer value */
      Elf64_Addr d_ptr;			/* Address value */
    } d_un;
} Elf64_Dyn;

/* Legal values for d_tag (dynamic entry type).  */

#define DT_NULL		0		/* Marks end of dynamic section */
#define DT_NEEDED	1		/* Name of needed library */
#define DT_PLTRELSZ	2		/* Size in bytes of PLT relocs */
#define DT_PLTGOT	3		/* Processor defined value */
#define DT_HASH		4		/* Address of symbol hash table */
#define DT_STRTAB	5		/* Address of string table */
#define DT_SYMTAB	6		/* Address of symbol table */
...

3. _dl_fixup

_dl_fixup 함수가 생각보다 코드가 길기 때문에 중요한 부분들만 알아보자.

  1. SYMTAB

    아래의 C코드를 보면 DT_SYMTAB에 해당하는 Elf64_Dyn 구조체를 symtab 변수에 저장한다.

    1
    2
    
     const ElfW(Sym) *const symtab
         = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
    

    어셈블리 코드로 보면 rdi+0x70rax에 저장하는데 이 명령줄을 실행한 뒤 rax 레지스터 값을 확인해보면 다음과 같다.

    • 0x403eb0 : d_tag = 0x6
    • 0x403eb8 : d_ptr = 0x4003c0
    1
    2
    3
    4
    5
    
     	 0x7ffff7fd5e84 <_dl_fixup+20>   mov    rax, QWORD PTR [rdi+0x70]
       0x7ffff7fd5e88 <_dl_fixup+24>   mov    r10, QWORD PTR [rax+0x8]
     ─────────────────────────────────────────────────────────────────────
     gef  x/2gx 0x00000000403eb0
     0x403eb0:	0x0000000000000006	0x00000000004003c0
    

    0x4003c0.dynsym 섹션에 해당하며 .dynsym 섹션은 파일이 import/export 하는 모든 심볼들이 저장되어 있는 섹션이다.

    Elf64_Sym 구조체를 사용한다.

    1
    2
    
     5 .dynsym       00000060  00000000004003c0  00000000004003c0  000003c0  2**3
                       CONTENTS, ALLOC, LOAD, READONLY, DATA
    
    • Elf64_Sym 구조체
      • 구조체 size : 24Bytes
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
        typedef struct
        {
          Elf64_Word	st_name;		/* Symbol name (string tbl index) */
          unsigned char	st_info;		/* Symbol type and binding */
          unsigned char st_other;		/* Symbol visibility */
          Elf64_Section	st_shndx;		/* Section index */
          Elf64_Addr	st_value;		/* Symbol value */
          Elf64_Xword	st_size;		/* Symbol size */
        } Elf64_Sym;
      
  2. STRTAB

    1
    
     const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
    

    아래의 assembly 코드가 위의 C 코드를 수행하는 부분이다.

    1
    2
    3
    4
    
     	 0x7ffff7fd5e9e <_dl_fixup+46>   mov    rdx, QWORD PTR [rbp+0x68]
        0x7ffff7fd5ea2 <_dl_fixup+50>   mov    ebx, esi
        0x7ffff7fd5ea4 <_dl_fixup+52>   lea    rcx, [rbx+rbx*2]
       0x7ffff7fd5ea8 <_dl_fixup+56>   mov    rdi, QWORD PTR [rdx+0x8]
    

    mov rdx, QWORD PTR [rbp+0x68] 을 실행한 후 rdx 레지스터 값을 확인하면 아래와 같다.

    • 0x403ea0 : d_tag = 0x5
    • 0x403ea8 : d_ptr = 0x400420

    즉, 위의 C 코드와 같이 DT_STRTAB에 해당하는 Elf64_Dyn 구조체를 가져오는 것을 볼 수 있다.

    1
    2
    
     gef  x/2gx $rdx
     0x403ea0:	0x0000000000000005	0x0000000000400420
    

    그리고 d_ptr에 해당하는 값인 0x400420 값은 .dynstr 섹션에 해당하는데 확인해보면 심볼들이 문자열로 저장되어 있는 것을 볼 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    
     $ objdump -h -j .dynstr testfile
        
     testfile:     file format elf64-x86-64
        
     Sections:
     Idx Name          Size      VMA               LMA               File off  Algn
       6 .dynstr       00000048  0000000000400420  0000000000400420  00000420  2**0
                       CONTENTS, ALLOC, LOAD, READONLY, DATA
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     gef  x/10s 0x0000000000400420
     0x400420:	""
     0x400421:	"__libc_start_main"
     0x400433:	"puts"
     0x400438:	"libc.so.6"
     0x400442:	"GLIBC_2.2.5"
     0x40044e:	"GLIBC_2.34"
     0x400459:	"__gmon_start__"
     0x400468:	""
    
  3. JMPREL

    아래의 C코드는 DT_JMPREL 에 해당하는 Elf64_Dyn 구조체를 가져와서 reloc_offset의 결과를 더한 뒤 reloc 변수에 저장한다.

    1
    2
    3
    
     const PLTREL *const reloc
         = (const void *) (D_PTR (l, l_info[DT_JMPREL])
     		      + reloc_offset (pltgot, reloc_arg));
    
    • reloc_offset() 함수

      1
      2
      3
      4
      5
      
        static inline uintptr_t
        reloc_offset (uintptr_t plt0, uintptr_t pltn)
        {
          return pltn * sizeof (ElfW(Rela));
        }
      

    PLTRELElf64_Rela 구조체로 정의 되어 있다.

    1
    
     # define PLTREL  ElfW(Rela)
    
    • Elf64_Rela 구조체
      • r_offset : Address
      • r_info : Relocation 타입과 symbol의 index가 저장되어 있음.
      • r_addend : Addend
      1
      2
      3
      4
      5
      6
      
        typedef struct
        {
          Elf64_Addr	r_offset;		/* Address */
          Elf64_Xword	r_info;			/* Relocation type and symbol index */
          Elf64_Sxword	r_addend;		/* Addend */
        } Elf64_Rela;
      

    매개변수로 받은 reloc_offset 값이 담긴 esi 레지스터를 이용해서 offset 값을 만들어준다.

    1. esi를 ebx에 저장 (esi = 0, ebx = 0)
    2. rbx+rbx*2 한 값을 rcx에 저장 (rcx = 0)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     	 0x7ffff7fd5ea2 <_dl_fixup+50>   mov    ebx, esi
        0x7ffff7fd5ea4 <_dl_fixup+52>   lea    rcx, [rbx+rbx*2]
        0x7ffff7fd5ea8 <_dl_fixup+56>   mov    rdi, QWORD PTR [rdx+0x8]
       0x7ffff7fd5eac <_dl_fixup+60>   mov    rdx, QWORD PTR [rbp+0xf8]
        0x7ffff7fd5eb3 <_dl_fixup+67>   mov    rdx, QWORD PTR [rdx+0x8]
        0x7ffff7fd5eb7 <_dl_fixup+71>   add    rdi, r9
        0x7ffff7fd5eba <_dl_fixup+74>   lea    rsi, [rdx+rcx*8]
        0x7ffff7fd5ebe <_dl_fixup+78>   add    rsi, r9
        0x7ffff7fd5ec1 <_dl_fixup+81>   mov    r8, QWORD PTR [rsi+0x8]
     	 0x7ffff7fd5ec5 <_dl_fixup+85>   mov    r12, QWORD PTR [rsi]
    

    이 부분은 DT_JMPREL 에 해당하는 구조체를 가져오는 부분이고 d_ptr 을 rdx에 저장하는 것을 볼 수 있다.

    • 0x403f20 : d_tag = 0x17
    • 0x403f28 : d_ptr = 0x4004d0
    1
    2
    3
    4
    5
    6
    7
    8
    
     	 0x7ffff7fd5eac <_dl_fixup+60>   mov    rdx, QWORD PTR [rbp+0xf8]
       0x7ffff7fd5eb3 <_dl_fixup+67>   mov    rdx, QWORD PTR [rdx+0x8]
     ─────────────────────────────────────────────────────────────────────
     gef  x/2gx $rdx
     0x403f20:	0x0000000000000017	0x00000000004004d0
        
     gef  x/gx 0x00000000004004d0
     0x4004d0:	0x0000000000404018
    

    그리고 d_ptr 에 해당하는 값인 0x4004d0.rela.plt 섹션에 해당하며 위에서 잠깐 소개했던 Elf64_Rela 구조체를 사용한다.

    • 0x4004d0 : r_offset = 0x404018
    • 0x4004d8 : r_info = 0x0000000200000007
    • 0x4004e0 : r_addend = 0x0

    그리고 r_offset에 해당하는 값을 보면 puts 함수의 got 주소인 것을 볼 수 있다.

    1
    2
    3
    
     gef  x/3gx 0x000000004004d0
     0x4004d0:	0x0000000000404018	0x0000000200000007
     0x4004e0:	0x0000000000000000
    

    이어서 코드를 계속 진행 하다 보면 rdx+rcx*8 을 rsi에 저장해주는 것을 볼 수 있는데 이는 Elf64_Rela 구조체의 크기 만큼 건너뛰면서 접근하는 것을 알 수 있다.

    그리고 그리고 접근하고자 하는 구조체에서 r_infor8 레지스터에 저장하고 r_offsetr12 레지스터에 저장한다.

    1
    2
    3
    4
    5
    
        0x7ffff7fd5eb7 <_dl_fixup+71>   add    rdi, r9
        0x7ffff7fd5eba <_dl_fixup+74>   lea    rsi, [rdx+rcx*8]
        0x7ffff7fd5ebe <_dl_fixup+78>   add    rsi, r9
        0x7ffff7fd5ec1 <_dl_fixup+81>   mov    r8, QWORD PTR [rsi+0x8]
     	 0x7ffff7fd5ec5 <_dl_fixup+85>   mov    r12, QWORD PTR [rsi]
    
  4. symbol table

    symtab[(reloc->r_info) >> 32] 의 주소값을 sym 변수에 저장하는 코드이다.

    1
    
     const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
    
    1
    2
    3
    
     #define ELF64_R_SYM(i)			((i) >> 32)
     #define ELF64_R_TYPE(i)			((i) & 0xffffffff)
     #define ELF64_R_INFO(sym,type)		((((Elf64_Xword) (sym)) << 32) + (type))
    

    위에서 분석했다시피 reloc->r_info 에는 0x0000000200000007 라는 값이 저장되어 있고 이 값을 32bit right shift 하게 되면 0x2 가 된다.

    결과적으로 symtab[2]에 접근하게 된다.

    어셈블리 코드로 보면 다음과 같다.

    1
    2
    3
    4
    5
    6
    
     	 0x7ffff7fd5ec8 <_dl_fixup+88>   mov    rdx, r8
        0x7ffff7fd5ecb <_dl_fixup+91>   add    r12, rax
        0x7ffff7fd5ece <_dl_fixup+94>   shr    rdx, 0x20
        0x7ffff7fd5ed2 <_dl_fixup+98>   lea    rcx, [rdx+rdx*1]
        0x7ffff7fd5ed6 <_dl_fixup+102>  add    rdx, rcx
       0x7ffff7fd5ed9 <_dl_fixup+105>  lea    rdx, [r10+rdx*8]
    

    위의 어셈블리 코드를 다 실행시키고 난 뒤 rdx 레지스터에 저장된 값은 다음과 같다.

    1
    2
    
     gef  x/gx $rdx
     0x4003f0:	0x0000001200000013
    
  5. _dl_lookup_symbol_x 호출

    1
    2
    
     result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
     				    version, ELF_RTYPE_CLASS_PLT, flags, NULL);
    

    _dl_lookup_symbol_x 함수 호출에서는 매개변수를 많이 넘겨주는 것을 볼 수 있는데, 첫 번째로 넘어가는 매개변수만 확인해보겠다.

    첫 번째로 넘어가는 매개변수는 strtab + sym->st_name 인데 strtab은 위에서 분석했다시피 .dynstr 섹션의 주소가 저장되어 있다. 따라서 sym->st_name 에 저장된 offset 만큼 더하면 심볼의 문자열 값이 나온다.

    gdb로 확인해보면 아래와 같다.

    • rdx = 0x0000001200000013
    • rdi = 0x400420 (.dynstr)
    1
    2
    3
    4
    5
    
        0x7ffff7fd5f4e <_dl_fixup+222>  mov    edx, DWORD PTR [rdx]
        
     	 ... 생략 ...
        
        0x7ffff7fd5f66 <_dl_fixup+246>  add    rdi, rdx
    

    해당 명령줄이 실행되고 나면 rdi 레지스터에는 puts 라는 심볼 문자열이 저장된다.

    1
    2
    
     gef  x/s $rdi
     0x400433:	"puts"
    

4. _dl_lookup_symbol_x

1
2
3
4
5
6
lookup_t
_dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
		     const ElfW(Sym) **ref,
		     struct r_scope_elem *symbol_scope[],
		     const struct r_found_version *version,
		     int type_class, int flags, struct link_map *skip_map)

do_lookup_x를 호출한다.

1
2
3
4
5
/* Search the relevant loaded objects for a definition.  */
for (size_t start = i; *scope != NULL; start = 0, ++scope)
  if (do_lookup_x (undef_name, new_hash, &old_hash, *ref,
	     &current_value, *scope, start, version, flags,
	     skip_map, type_class, undef_map) != 0)
1
2
3
4
5
6
7
8
do_lookup_x (
   $rdi = 0x00000000400433  0x62696c0073747570 ("puts"?),
   $rsi = 0x0000007c9c7b11,
   $rdx = 0x007fffffffd360  0x00000000ffffffff,
   $rcx = 0x000000004003f0   adc eax, DWORD PTR [rax],
   $r8 = 0x007fffffffd370  0x0000000000000000,
   $r9 = 0x007ffff7ffe5a0  0x007ffff7fbba00  0x007ffff7ffe2e0  0x0000000000000000
)

5. do_lookup_x

1
2
3
4
5
6
7
static int
__attribute_noinline__
do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
	     unsigned long int *old_hash, const ElfW(Sym) *ref,
	     struct sym_val *result, struct r_scope_elem *scope, size_t i,
	     const struct r_found_version *const version, int flags,
	     struct link_map *skip, int type_class, struct link_map *undef_map)

_dl_lookup_symbol_x 로부터 넘어온 매개변수의 값은 다음과 같다.

  • undef_name : $rdi = 0x00000000400433 → 0x62696c0073747570 ("puts"?),
  • new_hash : $rsi = 0x0000007c9c7b11
  • old_hash : 0x007fffffffd360 → 0x00000000ffffffff
  • ref : 0x000000004003f0
  • result : 0x007fffffffd370
  • scope : 0x007ffff7ffe5a0 → 0x007ffff7fbba00 → 0x007ffff7ffe2e0 → 0x0000000000000000
1
2
3
4
sym = check_match (undef_name, ref, version, flags,
					   type_class, &symtab[symidx], symidx,
					   strtab, map, &versioned_sym,
					   &num_versions);

6. check_match

찾고자하는 symbol의 정보가 유효한지 check를 하는 함수이다.

1
2
3
4
5
6
7
8
9
10
11
12
static const ElfW(Sym) *
check_match (const char *const undef_name,
	     const ElfW(Sym) *const ref,
	     const struct r_found_version *const version,
	     const int flags,
	     const int type_class,
	     const ElfW(Sym) *const sym,
	     const Elf_Symndx symidx,
	     const char *const strtab,
	     const struct link_map *const map,
	     const ElfW(Sym) **const versioned_sym,
	     int *const num_versions)

symbol 문자열 확인

1
2
3
if (sym != ref && strcmp (strtab + sym->st_name, undef_name))
    /* Not the symbol we are looking for.  */
    return NULL;

7. elf_machine_fixup_plt

위의 함수들을 다 실행하고 난 뒤 _dl_fixup 함수로 되돌아오게 된다.

이어서 코드를 진행하다보면 elf_machine_fixup_plt를 호출하게 되는데 이 때 got에 puts 함수의 원래 주소가 저장된다.

1
2
3
4
5
6
7
8
static inline ElfW(Addr)
elf_machine_fixup_plt (struct link_map *map, lookup_t t,
		       const ElfW(Sym) *refsym, const ElfW(Sym) *sym,
		       const ElfW(Rel) *reloc,
		       ElfW(Addr) *reloc_addr, ElfW(Addr) value)
{
  return *reloc_addr = value;
}

gdb로 확인해보면 puts@got.plt의 주소가 __GI__IO_puts의 주소로 바뀐 것을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
0x7ffff7fd5feb <_dl_fixup+379>  mov    QWORD PTR [r12], rax
  0x7ffff7fd5fef <_dl_fixup+383>  add    rsp, 0x18
─────────────────────────────────────────────────────────────────────
gef  x/gx $r12
0x404018 <puts@got.plt>:	0x00007ffff7e03ed0
gef  x/gx $rax
0x7ffff7e03ed0 <__GI__IO_puts>:	0x55415641fa1e0ff3
gef  got

GOT protection: Partial RelRO | GOT functions: 1

[0x404018] puts@GLIBC_2.2.5    0x7ffff7e0eed0

8. call puts()

_dl_fixup() 함수가 끝나고 _dl_runtime_resolve_xsavec() 로 돌아가고 instruction을 쭉 실행하다 보면 jmp r11 을 실행시키는데 r11 레지스터에는 puts() 함수의 주소가 들어가 있다.

1
2
3
0x7ffff7fd8dae <_dl_runtime_resolve_xsavec+126> mov    r11, rax
...(생략)
0x7ffff7fd8dea <_dl_runtime_resolve_xsavec+186> jmp    r11

이런식으로 puts() 함수가 실행된다.

1
2
3
4
 0x7ffff7e0eed0 <puts+0>         endbr64
   0x7ffff7e0eed4 <puts+4>         push   r14
   0x7ffff7e0eed6 <puts+6>         push   r13
   0x7ffff7e0eed8 <puts+8>         push   r12

처음에 PLT, GOT 관련해서 공부했을 때 그럼 GOT는 어떻게 원래 주소를 가져올까 궁금했다. 그래서 gdb로 하나하나씩 따라가면서, 방대한 양의 libc 코드도 읽어보면서 분석했는데, 굉장히 좋은 경험이 되었다.

This post is licensed under CC BY 4.0 by the author.