Skip to main content

Command Palette

Search for a command to run...

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

Published
3 min read
라이브러리, 링크, Plt와 Got

라이브러리

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


링크

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

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

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

따라서

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

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

와 같은 소스 코드를

gcc -o hello-world.c -o hello-world.o

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

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

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

❯ 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 한 헤더의 함수가 모두 포함될 수도 있고 그렇지 않을 수도 있다.

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

컴파일 옵션을 붙여서

   ~/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

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

  • dynamic

동적 컴파일에서는 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를 참조하게 된다.

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

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

의 코드를

gcc -o got got.c -no-pie

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

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

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

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

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

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

그 상태로 계속 실행하면

segmentation fault가 발생한다.

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

More from this blog

락프리 데이터 구조와 알고리즘

여기서는 락프리 데이터 구조를 설명한다. 락프리(lock-free) 란 배타락을 이용하지 않고 처리를 수행하는 데이터 구조 및 그에 대한 조작 알고리즘을 총칭한다. 왜 락프리인가? 전통적인 동시성 제어 방법인 뮤텍스나 세마포어는 여러 문제점을 가지고 있다: 성능 저하: 락 경합(lock contention)으로 인한 대기 시간 데드락: 여러 스레드가 서로의 락을 기다리는 상황 우선순위 역전: 낮은 우선순위 스레드가 높은 우선순위 스레드를 ...

Jul 27, 20257 min read126

소프트웨어 트랜잭셔널 메모리

소프트웨어 트랜잭셔널 메모리 동시성 프로그래밍에서 공유 자원에 대한 안전한 접근은 항상 중요한 과제다. 전통적으로 뮤텍스 락과 같은 비관적 락(Negative Lock) 방식을 사용해왔다. 이 방식은 크리티컬 섹션에 진입하기 전에 반드시 락을 획득해야 하며, 락을 얻지 못하면 코드 실행 자체가 블록된다. 하지만 이와는 다른 접근 방식이 있다. 바로 낙관적 락(Optimistic Lock) 방식인데, 이는 "일단 실행하고 나중에 검증하자"는 철학...

Jul 20, 202517 min read263

공평한 배타 제어

공평한 배타 제어 여기서는 공평한 배타 제어에 대해 설명한다. 먼저 컨텐션(contention) 이라는 개념을 이해할 필요가 있다. 컨텐션이란 여러 스레드가 동시에 같은 락을 획득하려고 경쟁하는 상황을 말한다. 컨텐션이 높을수록 스레드들이 락을 기다리는 시간이 길어지고 성능이 저하된다. 이러한 컨텐션 상황은 시스템 아키텍처에 따라 더욱 복잡해질 수 있다. 특히 비균일 메모리 접근(Non-Uniform Memory Access, NUMA) 와 같...

Jul 13, 20259 min read21

KernelSnitch[논문 리뷰]

Paper 1. Intro 이 글은 NDSS 2025에서 발표된 KernelSnitch 논문을 소개이다. 이 연구는 커널의 평범한 데이터 구조체들이 가진 본질적인 특성이 어떻게 심각한 보안 취약점이 되는지를 보여준다. 핵심은 이러하다: "데이터 구조체의 크기에 따른 접근 시간 차이를 이용해 커널의 비밀 정보를 유출할 수 있다" 여기서는 커널 힙 포인터 유출에 집중해서 설명한다. 이 공격이 성공하면 KASLR을 우회하고 더 심각한 커널 익스플로...

Jul 11, 20257 min read131

멀티태스크와 액터 모델

멀티태스크 협조적/비협조적 멀티태스크 선점: 프로세스와의 협조 없이 수행하는 컨택스트 스위칭이라고는 하나, 결국 뺏어오는 게 가능하냐의 문제다. 협조적 멀티태스크(비선점형, cooperative): 각각의 프로세스가 자발적으로 컨택스트 스위칭을 수행하는 멀티태스크 방식. 장점: 멀티태스크 매커니즘을 구현하기 쉽다. 단점: 프로세스가 자발적으로 컨텍스트 스위칭을 해야하는데, 만약 버그가 발생하여 프로세스가 무한 루프에 빠지거나 정지하게 되면 그 ...

Jul 6, 20252 min read25
M

MaxLog

35 posts