리눅스 ELF 바이너리에서 라이브러리 함수의 시작주소를 구하지 않다가, 함수를 처음 호출할 때 해당 주소를 구하는 것
이후 호출에는 함수의 실제 주소를 구하지 않고 바로바로 이동한다.
* plt, got 에 대한 개념을 알아야 함
ex) puts() 함수를 호출하는 경우
맨 처음에 puts() 를 호출하면, puts@plt 영역으로 이동한다. 그리고 got 영역 (0x804a00c) 으로 이동(1번)하나, 첫 호출이기 때문에 plt+6 의 주소가 저장되어 있다. (2번) 따라서 plt + 6 에서 값을 스택에 넣고 점프(3번)하는데, 두 영역의 주소를 보면 가깝게 위치한다. 그 이유는 Lazy Binding 을 위해 push ; jmp 명령어를 놓기 위한 여분의 자리가 있는 상태에서 그 자리에 여분의 코드가 들어갔기 때문이다. 결국 컴파일 방식에 따라 없을 수도 있다.
아무튼 중요한건 노란색 박스에 보여진 값인데, 0x0은 reloc_offset 이고 0x804a004 는 link_map 구조체의 포인터로 자세한 설명은 아래를 참고하고, 두 값은 모두 _dl_runtime_resolve() 함수의 인자이다.
실제 GOT 영역에 주소가 저장되는 과정
>> 함수 호출 순서
// 깊이 들어갈수록 나중에 호출되는 것
_dl_runtime_resolve(int reloc_offset, struct link_map *l) ;
_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) ;
_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 (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)
>> reloc_offset
: 아래와 같은 구조체의 시작주소를 얻기 위해 이용된다.
(32-bit) JMPREL(.rel.plt) 영역에서 해당 함수의 Elf32_Rel 구조체의 오프셋 (주소 오프셋)
(64-bit) RELA(.rela.plt) 영역에서 해당 함수의 Elf32_Rela 구조체의 오프셋 (구조체 배열의 인덱스)
구조체의 크기가 다르기 때문에, 32비트인 경우 주소의 오프셋을 사용하나, 64비트인 경우 배열의 인덱스를 사용한다.
>> link_map 구조체
: 링커가 런타임에서 라이브러리 함수들을 메모리에 매핑시킬 때 사용하는 구조체
struct link_map
{
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. link_map 앞,뒤 노드 */
};
- link_map 은 이중 연결 리스트 구조이며, GOT 영역에서 2번째 원소에 첫번째 노드의 주소가 있다. - 의미는 각 주석 참조 - l_name 은 문자열 포인터인데 STRTAB(.dynstr) 에 있는 라이브러리의 전체 경로 문자열을 참조한다. - 찾고자 하는 함수의 메모리 상의 실제 주소를 구할 때 사용 (라이브러리의 시작주소부터 알아야 하니, 필요하다.)
0x804a000 은 GOT 영역이며, 다음과 같은 정보를 가진다.
GOT[0] = .dynsym 시작주소
GOT[1] = link_map 연결 리스트의 첫번째 노드의 시작주소
GOT[2] = _dl_runtime_resolve() 의 시작주소
GOT[3] = PLT 에서 참조하는 GOT 영역 (0x804a000 + 0x10 이후)
아래에 있는 link_map 이 이중 연결 리스트 구조인 것을 알 수 있다. 그림에서 마지막 노드의 경우 l_name은 "/lib32/libc.so.6"이다.
>> Elf32_Rel 그리고 Elf64_Rela 구조체
: 함수의 재배치 정보를 가지며, _dl_runtime_resolve() 에서 참조된다.
typedef uint32_t Elf32_Word;
typedef uint32_t Elf32_Addr;
/* Relocation table entry without addend (in section of type SHT_REL). */
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
- r_offset : 재배치된 후 (함수 바인딩이 끝나고) 실제 함수 주소가 저장될 GOT 영역의 주소 - r_info : 첫 1바이트는 재배치 타입(ELF32_R_TYPE), 나머지 3바이트는 심볼 테이블 정보(ELF32_R_SYM)