TOOR

[TOOR] 8,10 공유 라이브러리 & Lazy Binding & Now Binding

lmxx 2023. 9. 20. 22:05
728x90
반응형

Shared Library


컴파일을 하면 오브젝트 파일이 생성된다.

하지만 오브젝트 파일은 그 자체로 실행이 가능하지는 않다.
Printf의 구현 코드를 모르기 때문에 printf 를 호출 했을 때 어떤 코드를 실행해야 하는지, printf 를 호출 하기 위해서는 printf 의 구현체를 연결해야 한다.
printf 의 실행 코드는 printf의 구현 코드를 컴파일한 오브젝트 파일로, 이런 오브젝트 파일들이 모여있는 곳을 라이브러리(Library) 라고 한다.

라이브러리 등 필요한 오브젝트 파일들을 연결시키는 작업을 링킹(Linking) 이라고 한다.
이렇게 링크 과정까지 마치면 최정적인 실행 파일이 생긴다
링크를 하는 방법에는 Static 과 Dynamic 방식이 있다.

Static

Static 으로 컴파일 하기 위해서는

gcc ex.c -o ex -static 

정적으로 링크하면 함수를 호출할 때 정적인 주소를 이용해 호출한다.
call 0x410480 <puts>
정적으로 링크한 경우 바이너리 내부에 함수들의 주소가 있기 때문에 외부 라이브러리에 접근할 필요가 없다.

Dynamic Link

아무런 옵션도 주지 않고 컴파일 하면 동적으로 링크 된다
동적으로 링크하면 함수를 호출할때 plt 를 이용한다.
call 0x5555555545d0 puts@plt

Dynamic Link 방식으로 컴파일 하면 라이브러리가 프로그램 외부에 있기 때문에 함수의 주소를 알아오는 과정이 필요하다.

첫 호출 시 Linker 가 dl_resolve 함수를 사용해 필요한 함수의 주소를 알아오고, GOT 에 그 주소를 써준 후 해당 함수를 호출한다.

no relro 했는데 왜 Full Relro

norelro 로 컴파일 해서 최초 실행인데 왜 바인딩이 돼있지?

partial Relro 로 하려고 -z relro 옵션으로 컴파일 했는데 계속 Full Relro 여서 보니까 PIE 가 켜져있으면 Full Relro 가 된다.
PIE 끄면 partial 이 된다.

PLT, GOT

Dynamic Linking 에서 라이브러리 함수의 실제 주소를 가져오기 위해서 사용한다.

PLT

Procedure Linkage Table

외부 라이브러리 함수를 사용할 수 있도록 주소를 연결 해주는 테이블

GOT

Global Offset Table

PLT에서 호출하는 resolve()함수를 통해 구한 라이브러리 함수의 절대 주소가 저장되어 있는 테이블

Lazy Binding 간단 요약

동적으로 링크 된 바이너리에서 라이브러리 함수인 printf를 호출한다고 가정하자.

  1. call printf@plt 형태로 라이브러리 함수인 printf() 호출
  2. PLT에 접근하여 GOT로 점프
  3. 처음 호출한 경우 링커가 _dl_resolve() 함수를 통해 printf()의 실제 주소를 알아내어 GOT에 쓴다.
  4. GOT에 있는 printf()의 실제 주소를 이용해 printf()호출

이후 호출에는 3번 과정에서 바로 실제 함수의 주소로 접근할 수 있다

detail

우선 library 함수를 호출하게 되면


plt로 가게 된다.

처음 호출된 경우 해당 함수의 got 에는 plt의 주소가 적혀있는데


이로 인해서 Lazy Binding 이 시작된다.

0x8은 reloc_offset 이다.
이 reloc_offset 은 구조체의 시작 주소를 찾기 위해서 사용한다.

DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) DL_ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
	ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
	struct link_map *l, ElfW(Word) reloc_arg)

_dl_fixup 를 보면 인자로 link_map 구조체와 reloc_arg 가 들어간다.


이제 다시 보면 0x8049030 로 jmp 해주는데


여기서 push 로 어떤 값을 넣어준다.


이 값은 link_map 구조체 포인터이다.

그리고 jmp 해주는데 이 함수는 _dl_runtime_resolve이다.


보면 call 하는 부분이 있는데 이게 _dl_fixup 함수이다.

인자로 들어가는 것은
eax = link_map 구조체 포인터
edx = reloc_offset 이다.


_dl_fixup 함수 안에서 link_map 구조체를 이용해서 STRTAB의 주소를 알아 낸다.
보통 [link_map + 0x34] 로 STRTAB의 시작 주소를 찾을 수 있다.

STRTAB

strtab 은 프로그램 내에서 쓰이는 각종 심볼들의 string 이 들어있는 테이블이다.

알아낸 STRTAB의 주소를 보면 심볼들의 string 이 있는 걸 볼 수 있다.

이런식으로 주소를 구한다

또한 link_map 을 이용해서 JMPREL 주소를 구한다.
보통 [link_map + 0x7c] 로 구할 수 있다.

JMPREL

재배치 정보를 담고 있는 재배치 테이블 Elf32_Rel 구조체로 이루어져 있다.
Elf32_Rel 구조체는 GOT의 주소와 재배치 정보들로 이루어져 있다.
하나의 entry 크기는 8바이트 처음 4byte는 GOT의 주소 다음 4byte의 첫번째 1byte는 재배치 타입, 나머지 byte가 DYNSYM 테이블에서의 index를 나타낸다.


이렇게 찾을 수 있고
link_map 에 0x7c를 더해도 된다.

오른쪽의 3바이트는 DYNSYM 의 index 이고 1바이트는 type이다.


이 과정을 통해 구한게 JMPREL 이고
JMPREL에 reloc_offset 을 더하면 해당 함수가 사용하는 Elf32_REL 구조체의 주소가 된다.


이게 Elf32_REL 이다.
왼쪽에 있는 4바이트는 GOT
오른쪽 의 3바이트는 DYNSYM 의 index
나머지 1바이트는 type 이다.

지금까지 구한 STRTAB 과 DYNSYM 으로 함수 이름을 구한다.

DYNSYM

동적 심볼(섹션) 테이블
처음 16byte는 0으로 세팅된다.
import 및 export하는 모든 심볼의 정보가 담겨있다.
ELF32_SYM구조체로 이루어져 있다.
중요한 값은 첫 번째와 다섯 번째 값이다.

readelf -S 명령어나
gef, pwndbg 등에서 elf 명령을 사용하면 .dynsym 주소를 알 수 있다.

0x00000010 0x00000000 0x00000000 0x00 0x00 0x0012

중요한 값은 첫 번째와 다섯 번째 값이다.
첫 번째 값은 offset이고
다섯 번째 값은 3과 &연산을 해서 0이냐 아니냐로 이미 로딩 된 함수인지 아닌지를 판단한다

0x00은 3과 &연산 했을 때 0이 나오는 값이므로 로딩되지 않은 함수이다.


아까 찾은 STRTAB 에 offset 을 더해주면 해당 함수의 이름이 나온다.

이렇게 함수 이름을 구하고 _dl_lookup_symbol_x 를 호출한다.

_dl_lookup_symbol_x

함수가 종료되면 레지스터에 라이브러리 시작주소와 SYMTAB의 주소가 쓰여진다.
부르는 함수에 해당하는 offset이 적한 주소를 찾아온다.


함수 종료 후 레지스터 상태
_dl_lookup_symbol_x 함수가 종료되면 eax에 라이브러리의 시작주소가 쓰여있고, ebp-0x1c에 SYMTAB주소가 쓰여있다.

이후에

 

함수의 실제 주소를 가져왔음을 알 수 있다.

SYMTAB에 적혀 있는 offset중에서 부르는 함수에 해당하는 offset이 적힌 주소를 찾아온다.
이렇게 구해온 라이브러리 시작 주소와 SYMTAB 내에 있는 실제 함수의 오프셋을 더해서 실제 함수의 주소를 구하고 이것을 GOT에 기록한다.

_dl_fixup
에서 got 에 실제 주소가 써졌다.

이후에 _dl_runtime_resolve 에 의해 실제 함수로 넘어간다.

Now Binding

Full Relro 가 걸리게 되면 Now Binding 으로 링킹을 하게 되는데 Lazy Binding 과는 달리 프로그램이 실행 될 때 링킹을 하고 got 에 쓰기 권한을 제거한다.
이렇게 하면 got overwrite 공격을 보호할 수 있다.


앞선 실습때 사용한 것에 Full Relro 만 추가했다


실행 전에 섹션을 확인해보면 got 영역에 쓰기 권한이 있음을 알 수 있다.


실행 전에는 값이 안 적혀 있지만


실행하자마자 확인하면 실제 주소가 적혀있음을 알 수 있다.


또한 vmmap 으로 확인해보면 쓰기 권한이 없다.

이 글은 옵시디언을 이용해서 작성되었습니다.

728x90
반응형