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
puts@plt
를 호출1
→ 0x401134 <main+14> call 0x401030 <puts@plt>
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
puts@plt+6
으로 점프그리고 나서
0x0
을 push 하고0x401020
로 점프한다.여기서
0x0
은reloc_offset
이다.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 값을 나타낸 것이다.
read
의reloc_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
puts
의reloc_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된 0x7ffff7ffe2e0
은 link_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_object
→ELF_DYNAMIC_RELOCATE
→elf_machine_runtime_setup
ELF_DYNAMIC_RELOCATE
는 매크로이다.
아래의 코드를 보게되면 매개변수로 받은 link_map
이 got[1]
에 저장되는 것을 볼 수 있다.
추가적으로 이 때 _dl_runtime_resolve
도 got[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_xsavec
는 sysdeps/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_map
과 reloc_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 의 멤버변수
link_map
구조체는 include/link.h
에 정의되어 있다(elf/link.h
에 도 같은 이름의 구조체가 정의되어 있지만 프로그램 진행 상 include/link.h
의 구조체가 타당한 것 같다, 참고로 elf/link.h
의 link_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
- type :
l_info
- type :
Elf64_Dyn
- dynamic 섹션에 대한 인덱싱된 포인터들의 배열.
- type :
ELf64_Dyn 구조체
Elf64_Dyn
의 구조체는 다음과 같다.
- 위에서 소개한
l_info
는 이러한 구조체가 배열형식으로 되어있고DT_HASH
나DT_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
함수가 생각보다 코드가 길기 때문에 중요한 부분들만 알아보자.
SYMTAB
아래의 C코드를 보면
DT_SYMTAB
에 해당하는Elf64_Dyn
구조체를symtab
변수에 저장한다.1 2
const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
어셈블리 코드로 보면
rdi+0x70
을rax
에 저장하는데 이 명령줄을 실행한 뒤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;
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: ""
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)); }
PLTREL
은Elf64_Rela
구조체로 정의 되어 있다.1
# define PLTREL ElfW(Rela)
Elf64_Rela
구조체r_offset
: Addressr_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 값을 만들어준다.- esi를 ebx에 저장 (
esi = 0, ebx = 0
) 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_info
를r8
레지스터에 저장하고r_offset
을r12
레지스터에 저장한다.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]
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
_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,
¤t_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 코드도 읽어보면서 분석했는데, 굉장히 좋은 경험이 되었다.