# 라이브러리, 링크, Plt와 Got

# 라이브러리

라이브러리는 컴퓨터 시스템에서 프로그램들이 함수, 변수를 공유해서 쓸 수 있게 한다. 그 중 범용적으로 많이 사용되는 함수들은 표준 라이브러리가 제작되어 있다. <s>백준에서 사용 가능한 그것들</s>

---

# 링크

컴파일의 마지막 단계라고 할 수 있다.

![](https://i.imgur.com/J6JVlw0.png align="left")

사진은 컴파일의 과정인데 링킹은 오브젝트 파일을 실행 파일로 바꾸는 것을 말한다.

오브젝트 파일은 실행 가능한 형식이기는 하지만 라이브러리 함수들의 정의가 어딨는지 알지 못한다.

따라서

```C
//hello-world.c
#include <stdio.h>

int main() {
	puts("Hello World!");
	return 0;
}
```

와 같은 소스 코드를

```sh
gcc -o hello-world.c -o hello-world.o
```

로 오브젝트 파일로까지만 바꾸면 소스코드 자체는 라이브러리를 include했음에도 puts의 선언이 있는 stdio.h 심볼의 자세한 정보가 기록되어있지 않다.

```sh
❯ readelf -s hello-world.o | grep puts
     5: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts
```

완전한 컴파일을 진행해주면

```sh
❯ gcc hello-world.c -o hello-world
❯ ldd hello-world
     linux-vdso.so.1 (0x00007101e1c26000)
     libc.so.6 => /usr/lib/libc.so.6 (0x00007101e1a05000)
     /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007101e1c28000)
```

`puts`의 정의를 찾아 연결한 것을 확인할 수 있다. 링크를 거치고 나면 프로그램에서 `puts`를 호출할 때 `puts`의 정의가 있는 라이브러리에서 `puts`의 코드를 찾고 해당 코드를 실행하게 된다.

---

# 라이브러리와 링크의 종류

라이브러리는 동적 라이브러리와 정적 라이브러리로 구분되고, 동적 라이브러리를 링크하는 것을 동적 링크, 정적 라이브러리를 링크하는 것을 정적 링크라고 한다.

## 동적 링크

동적 링크된 바이너리를 실행하면 동적 라이브러리가 프로세스의 메모리에 매핑된다. 실행 중에 라이브러리의 함수를 호출하면 매핑된 라이브러리에서 호출할 함수의 주소를 찾고 함수를 실행한다.

## 정적 링크

정적 링크를 하면 바이너리에 정적 라이브러리의 필요한 '모든' 함수가 포함된다. 탐색 자체의 비용은 절감될 수 있지만 여러 바이너리에서 라이브러리를 사용하면 복제가 여러 번 이루어지므로 용량의 낭비가 있을 수 있다. 컴파일 옵션에 따라 include 한 헤더의 함수가 모두 포함될 수도 있고 그렇지 않을 수도 있다.

## 동적 링크와 정적 링크 비교하기

컴파일 옵션을 붙여서

```sh
   ~/hacking ─────────────────────────────   max-env at   20:53:25
❯ gcc -o static hello-world.c -static

   ~/hacking ─────────────────────────────   max-env at   21:55:37
❯ gcc -o dynamic hello-world.c -no-pie
```

와 같이 각각 정적 컴파일, 동적 컴파일을 수행할 수 있다.

각각의 실행파일을 디스어셈블하면

* static
    
    ![](https://i.imgur.com/s7vxXqu.png align="left")
    

정적 컴파일에서는 `puts`를 직접적으로 호출하지만

* dynamic
    
    ![](https://i.imgur.com/u3XVKq8.png align="left")
    

동적 컴파일에서는 `puts@plt`을 호출하고 있다. 여기서 `plt`이란 함수의 주소를 라이브러리에서 찾을 수 있게 하는 테이블이다. 자세한 내용은 아래 설명한다.

---

# PLT & GOT

PLT와 GOT는 아까 간략히 소개한대로 라이브러리에서 동적 링크된 심볼의 주소를 찾을 때 사용하는 테이블이다.

* PLT(Procedure Linkage Table): 외부 라이브러리 함수를 사용할 수 있도록 그 함수가 있는 주소를 프로그램에 연결시켜주는 테이블
    
* GOT(Global Offset Table): PLT가 참조하는 테이블로 '실제 함수'들의 주소는 여기에 들어있음.
    

그러한 테이블들을 바탕으로 프로그램이 실행되는 동안(런타임)에 특정 코드 요소들을 찾아서 동적으로 연결하는 과정을 `runtime resolve`라고 한다.

**runtime resolve**:

1. 바이너리가 실행되면 ASLR에 의해 라이브러리가 임의의 주소에 매핑
    
2. 여기서 라이브러리 함수를 호출하면 함수의 이름을 바탕으로 라이브러리에서 심볼 탐색
    
3. 탐색에서 해당 함수의 정의를 발견하면 함수 주소로 실행 흐름 이동
    

함수의 주소를 찾아가는 과정에서, 동일한 함수를 여러 번 호출하는 경우 첫 번째는 PLT를 참조하고 PLT가 GOT를 참조해서 실제 함수의 절대 주소를 알아가는데, 두 번째부터는 바로 GOT를 참조하게 된다.

![](https://i.imgur.com/Wf9h0WL.png align="left")

위 과정을 GDB로 추적해 볼 수 있다.

```c
//got.c
#include <stdio.h>
int main() {
	puts("Resolving address of 'puts'.");
	puts("Get address from GOT");
}
```

의 코드를

```sh
gcc -o got got.c -no-pie
```

로 컴파일하고 gdb로 열어보자.

![](https://i.imgur.com/FRb8ozM.png align="left")

`main`함수에 `puts`를 두 번 호출하게 되어있는데,

![](https://i.imgur.com/m4Oy9Nh.png align="left")

![](https://i.imgur.com/b47UxGp.png align="left")

아직은 `plt`의 어딘가에 대한 정보밖에 없다. 그런데 진행하다보면 `<_dl_runtime_resolve_xsavec>` 를 발견할 수 있는데 이러한 resolve가 끝나면 실제 함수의 주소가 담기게 된다. `ni`를 통해 resolve 함수 내부로 진입하고 `finish`를 통해 빠져나온 후 `got`를 입력해주면

![](https://i.imgur.com/lAthmkt.png align="left")

실제 함수의 주소가 담기는 것을 볼 수 있다.

그러나 PLT가 GOT 엔트리의 값을 참조하여 실행 흐름을 옮길 때, GOT의 값을 검증하지 않는다는 문제가 있다.

![](https://i.imgur.com/jLwtCAD.png align="left")

`set` 명령어로 GOT 엔트리에 저장된 값을 임의로 변조해버릴 수 있다.

그 상태로 계속 실행하면

![](https://i.imgur.com/hf6l9J6.png align="left")

segmentation fault가 발생한다.

이처럼 GOT 엔트리의 값을 오버라이트해서 실행 흐름을 변조하는 공격 기법을 GOT Overwrite이라고 한다.
