Lazy Binding이란 Dynamic Linking 방식으로 컴파일된 ELF 바이너리는 공유 라이브러리에 있는 함수의 주소를 동적으로 가져오기 위해 사용하는 방법이다. 이때 GOT(Global Offset Table) 테이블을 이용하게 된다.
GOT 동작 과정

우선 함수가 처음 호출될 때 plt와 got의 값이다. call 명령어를 보면 plt를 호출하고 있다. 그리고 plt는 got를 참조하기 때문에 got로 jump하게 된다. 이때 got의 앖은 plt+6의 주소가 들어있다. 그리고 난 후 _dl_runtime_resolve 함수가 호출된다.
_dl_runtime_resolve
_dl_runtime_resolve(int reloc_offset, struct link_map *l) ;
해당 함수의 인자는 reloc_offset과 link_map이다. 우선 reloc_offset은 아래의 구조체의 시작 주소를 얻기 위해 사용된다.
- 32bit : JMPREL(.rel.plt) 영역에서 해당 함수의 Elf32_Rel 구조체의 오프셋 (주소 오프셋)
- 64bit : RELA(.rela.plt) 영역에서 해당 함수의 Elf64_Rela 구조체의 오프셋 (구조체 배열의 인덱스)
link_map 구조체는 링커가 런타임에서 라이브러리 함수들을 메모리에 매핑시킬 때 사용하는 구조체이다.
여기서 Elf32_Rel 구조체와 ELF64_Rela 구조체는 함수의 재배치 정보를 가지고 있다. 만약 바이너리를 expolit하는 과정에서 해당 구조체의 값을 출력할 수 있다면 libc의 값을 leak할 수 있다.
_dl_fixup
_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) ;
_dl_runtime_resolve 함수가 호출된 후 호출되는 함수는 _dl_fixup 함수이다. 해당 함수가 호출될 때 Elf32_Sym 구조체와 Elf64_Sym 구조체가 참조된다. 해당 구조체는 함수의 심볼테이블 엔트리에 해당된다.
이후 과정
- 함수이름의 시작 주소를 인자로
_dl_lookup_symbol_x()함수를 호출 - 함수 이름을 해시값으로 바꿔서
do_lookup_x()함수에서 검사 - 실제 라이브러리 영역에서의 심볼 테이블 인덱스 구함 (
do_lookup_x함수에서 반환됨) - 인덱스를 가지고 라이브러리 상에서의 오프셋을 구함 (
do_lookup_x함수에서 반환됨) - 로드된 라이브러리 파일에서 해당 함수의
Elf32_Sym구조체 주소에 접근
"libc의 .dynsym 영역" + "symidx offset" = libc에서 찾고자 하는 함수의Elf32_Sym구조체 영역 Elf32_Sym->st_value는 실제 함수의 상대주소를 가지고 있음
즉, libc base address + Elf32_Sym->st_value = 실제 주소
전체적인 호출 순서
위의 과정을 간단하게 정리하면 아래와 같은 순서로 함수가 호출된다.
_dl_runtime_resolve() -> _dl_fixup() -> _dl_lookup_symbol_x() -> do_lookup_x()
참고로 __DT_STRTAB 영역의 string 문자열을 가지고 함수의 주소를 가지고 오기 때문에 해당 영역의 문자열의 값을 변경할 수 있다면 원하는 함수를 호출할 수 있다.
- ex) printf 라는 문자열을 system으로 변경한다면 처음 printf를 호출했을 때 가져오는 함수의 주소는 system 함수의 주소가 된다.
참고 사이트
-https://bpsecblog.wordpress.com/2016/03/09/about_got_plt_2/#comments
-https://movefast.tistory.com/200