Lazy Binding

리눅스 ELF 바이너리에서 라이브러리 함수의 시작주소를 구하지 않다가, 함수를 처음 호출할 때 해당 주소를 구하는 것

이후 호출에는 함수의 실제 주소를 구하지 않고 바로바로 이동한다.

* plt, got 에 대한 개념을 알아야 함

 

ex) puts() 함수를 호출하는 경우

_dl_runtime_resolve () 를 호출하기 전

맨 처음에 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) 에 있는 라이브러리의 전체 경로 문자열을 참조한다.
- 찾고자 하는 함수의 메모리 상의 실제 주소를 구할 때 사용 (라이브러리의 시작주소부터 알아야 하니, 필요하다.)

 

GOT 영역과 link_map 구조체

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)

#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)

 

- 32비트일 때 구조체는 크기가 8바이트이며, 64비트인 경우 크기는 24바이트이다.

typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;
typedef int64_t  Elf64_Sxword;
 
typedef struct
{
	Elf64_Addr	r_offset;	/* Address */
	Elf64_Xword	r_info;		/* Relocation type and symbol index */
	Elf64_Sxword	r_addend;	/* Addend */
} Elf64_Rela;

 

>> Elf32_Sym 그리고 Elf64_Sym 구조체

 : 함수의 심볼테이블 엔트리에 해당된다. _dl_fixup() 에서 참조된다.
  (아래 방식으로 주소를 구함, Elf32_Sym 구조체는 크기가 16바이트)

SYMTAB(.dynsym) 시작주소 + r_info->ELF32_R_SYM * 16 = 해당 함수의 Elf32_Sym 구조체 시작주소

typedef uint16_t Elf32_Section;
 
/* Symbol table entry.  */
typedef struct
{
	Elf32_Word	st_name;	/* Symbol name (string tbl index) */
	Elf32_Addr	st_value;	/* Symbol value */
	Elf32_Word	st_size;	/* Symbol size */
	unsigned char	st_info;	/* Symbol type and binding */
	unsigned char	st_other;	/* Symbol visibility */
	Elf32_Section	st_shndx;	/* Section index */
} Elf32_Sym;

STRTAB(.dynstr) 시작주소 + Elf32_Sym->st_name = 함수 이름이 저장된 주소

 

- 64비트인 경우 

typedef uint32_t Elf64_Word;
typedef uint16_t Elf64_Section;
typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;
 
 
typedef struct
{
	Elf64_Word	st_name;	/* Symbol name (string tbl index), 4 Byte */
	unsigned char	st_info;	/* Symbol type and binding, 1 Byte */
	unsigned char	st_other;	/* Symbol visibility, 1 Byte */
	Elf64_Section	st_shndx;	/* Section index, 2 Byte */
	Elf64_Addr	st_value;	/* Symbol value, 8 Byte */
	Elf64_Xword	st_size;	/* Symbol size, 8 Byte */
} Elf64_Sym;

 

>> 이후 라이브러리 주소를 구하는 과정

  1. 함수이름의 시작 주소를 인자로 _dl_lookup_symbol_x() 함수를 호출
  2. 함수 이름을 해쉬값으로 바꿔서 do_lookup_x() 함수에서 검사
  3. 실제 라이브러리 영역에서의 심볼 테이블 인덱스 구함 (do_lookup_x 함수에서 반환됨)
  4. 인덱스를 가지고 라이브러리 상에서의 오프셋을 구함 (do_lookup_x 함수에서 반환됨)
  5. 로드된 라이브러리 파일에서 해당 함수의 Elf32_Sym 구조체 주소에 접근

    "libc의 .dynsym 영역" + "symidx offset" = libc에서 찾고자 하는 함수의 Elf32_Sym 구조체 영역
  6. Elf32_Sym->st_value 는 실제 함수의 상대주소를 가지고 있음

    즉, libc base address + Elf32_Sym->st_value = 실제 주소


    끝으로, Elf32_Rel->r_offset 에 구한 실제 주소를 저장

 

Reference

https://www.lazenca.net/display/TEC/01.Return-to-dl-resolve+-+x86

 

01.Return-to-dl-resolve - x86 - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List Return-to-dl-resolve - x86 Return-to-dl-resolve란 프로그램에서 동적라이브러리 함수의 주소를 찾기 위해 Lazy binding 을 사용할 경우 활용이 가능한 기법입니다.Return-to-dl-resolve는 Lazy binding 을 악용해 필요한 함수를 호출합니다. Lazy binding Flow Lazy bin

www.lazenca.net

https://www.lazenca.net/pages/viewpage.action?pageId=19300744

 

02.Return-to-dl-resolve - x64(feat.Return-to-csu) - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List Return-to-dl-resolve - x64 Return-to-dl-resolve란 프로그램에서 동적라이브러리 함수의 주소를 찾기 위해 Lazy binding 을 사용할 경우 활용이 가능한 기법입니다.Return-to-dl-resolve는 Lazy binding 을 악용해 필요한 함수를 호출합니다. Lazy binding Source code -

www.lazenca.net

 

'Security > System' 카테고리의 다른 글

CTF Summary  (0) 2020.02.07
[Heap Overflow] House Of Orange  (0) 2019.07.06
linux system call table  (0) 2019.07.03
ROP Gadget Dictionary  (0) 2019.06.30
BROP (Blind Return Oriented Programming)  (0) 2019.06.26

+ Recent posts