[Pwnable] 9. Background: Library - Static Link vs. Dynamic Link

Wonder_Land🛕·2022년 10월 24일
0

[Pwnable]

목록 보기
9/21
post-thumbnail

[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.


  1. 서론
  2. 라이브러리
  3. 링크
  4. PLT & GOT
  5. Q&A
  6. 마치며

1. 서론

많은 정보가 저장되고, 처리되는 컴퓨터에는 '도서관'과 비슷한 '라이브러리(Library; 도서관)'라는 개념이 있습니다.

실제 도서관과의 유사점과 차이점을 통해, 시스템 해킹 관점에서 라이브러리가 갖는 중요성에 대해 살펴보겠습니다.


2. 라이브러리(Library)

  • 라이브러리(Library)
    : 컴퓨터 시스템에서, 프로그램들이 함수나 변수를 공유할 수 있게끔 함

예를 들어, printf, scanf, strlen 등의 함수는 많은 C프로그래머들이 사용하는 함수입니다.

C언어를 비롯해 많은 컴파일 언어들은 자주 사용되는 함수들의 정의를 묶어서 하나의 라이브러리 파일로 만들고,
이를 여러 프로그램이 공유해서 사용할 수 있도록 지원하고 있습니다.
라이브러리를 사용하면 같은 함수를 반복적으로 정의해야 하는 수고를 덜 수 있어, 개발의 효율이 높아진다는 장점이 있습니다.

또한, 각 언어에서 범용적으로 많이 사용되는 함수들은 표준 라이브러리가 제작되어 있어, 개발자들은 해당 함수들을 사용할 수 있습니다.
대표적으로 C의 표준 라이브러리인 libc는 우분투에 기본으로 탑재된 라이브러리입니다.


3. 링크(Link)

  • 링크(Link)
    : 많은 프로그래밍 언어에서 컴파일의 마지막 단계
    : 호출된 함수와 실제 라이브러리의 함수를 연결하는 과정

리눅스에서 C 소스 코드는 전처리, 컴파일, 어셈블 과정을 거쳐 ELF 형식을 갖춘 '오브젝트 파일(Object File)'로 번역됩니다.

오브젝트 파일은 실행 가능한 형식을 갖추고 있지만,
라이브러리 함수들의 정의가 어디 있는지 알지 못하므로 실행은 불가능합니다.

따라서 심볼(symbol)과 관련된 정보들을 찾아서 최종 실행 파일에 기록하는 것이 링크 과정에서 하는 일 중 하나입니다.

컴파일할 때, libc를 컴파일하지 않아도 libc에서 해당 심볼을 탐색한 것은,
libc가 존재하는 '/lib/x86_64-linux-gnu/가 표준 라이브러리 경로에 포함되어 있기 때문입니다.
gcc는 소스 코드를 컴파일할 때 표준 라이브러리의 라이브러리 파일들을 모두 탐색합니다.

링크를 거치고 나면 프로그램에서 해당 함수를 호출할 때,
해당 함수의 정의가 있는 libc에서 해당 함수의 코드를 찾고,
해당 코드를 실행하게 됩니다.


라이브러리는 크게 동적 라이브러리와 정적 라이브러리로 구분되며,
동적 라이브러리를 링크하는 것을 '동적 링크(Dynamic Link)',
정적 라이브러리를 링크하는 것을 '정적 링크(Static Link)'라고 부릅니다.

라이브러리를 도서관으로, 프로그램을 사람으로 비유했을 때,
동적 링크는 가장 자연스러운 도서관 사용 방법입니다.

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

이 과정은 사람이 도서관에 방문해서 원하는 책의 위치를 찾고, 그 책에서 정보를 습득하는 과정과 유사합니다.

정적 링크는 도서관에서 필요한 모든 책을 암기하는 것과 같습니다.

정적 링크를 하면 바이너리에 정적 라이브러리의 필요한 모든 함수가 포함됩니다.

따라서 해당 함수를 호출할 때, 라이브러리를 참조하는 것이 아니라, 자신의 함수를 호출하는 것처럼 호출할 수 잇습니다.

라이브러리에서 원하는 함수를 찾지 않아도 되니 탐색의 비용이 절감되는 것처럼 보이지만,
여러 바이너리에서 라이브러리를 사용하면 그 라이브러리의 복제가 여러 번 이루어지게 되므로 용량을 낭비하게 됩니다.


3) 동적 링크 Vs 정적 링크

해당 코드를 각각의 방법으로 컴파일하겠습니다.

// Name: hello-world.c

#include <stdio.h>
int main() {
  puts("Hello, world!");
  return 0;
}

static, no-pie 옵션으로 각각의 방법으로 컴파일할 수 있습니다.

$ gcc -o static hello-world.c -static
$ gcc -o dynamic hello-world.c -no-pie

static

main:
  push   rbp
  mov    rbp,rsp
  lea    rdi,[rip+0x915dc] # 0x492144
  call   0x410230 <puts>
  mov    eax,0x0
  pop    rbp
  ret

dynamic

main: 
 push   rbp
 mov    rbp,rsp
 lea    rdi,[rip+0x92] # 0x400584
 call   0x4003f0 <puts@plt>
 mov    eax,0x0
 pop    rbp
 ret

static에서는 puts가 있는 0x410230를 직접 호출합니다.
반면, dynamic에서는 puts의 plt주소인 0x4003f0를 호출합니다.

이러한 차이가 발생하는 이유는,
동적 링크된 바이너리는 함수의 주소를 라이브러리에서 '찾아야'하기 때문입니다.
plt는 이 과정에 사용되는 테이블입니다.


4. PLT & GOT

'PLT(Procedure Linkage Table)'와 'GOT(Global Offset Talbe)'는 라이브러리에서 동적 링크된 심볼의 주소를 찾을 때 사용하는 방법입니다.

바이너리가 실행되면 ASLR에 의해 라이브러리가 임의의 주소에 매핑됩니다.

이 상태에서 라이브러리 함수를 호출하면,
함수의 이름을 바탕으로 라이브러리에서 심볼들을 탐색하고,
해당 함수의 정의를 발견하면 그 주소로 실행 흐름을 옮기게 됩니다.
이 전 과정을 통틀어 'runtime resolve'라고 합니다.

그런데 만약, 반보적으로 호출되는 함수의 정의를 매번 탐색해야 한다면 비효율적일 것입니다.

그래서 ELF는 'GOT'라는 테이블을 두고, resolved된 함수의 주소를 해당 테이블에 저장합니다.
그리고 나중에 다시 해당 함수를 호출하면 저장된 주소를 꺼내서 사용합니다.

PLT와 GOT는 동적 링크된 바이너리에서 라이브러리 함수의 주소를 찾고, 기록할 때 사용되는 중요한 테이블입니다.

그런데, 시스템 해커의 관점에서 보면 PLT에서 GOT를 참조하여 실행 흐름을 옮길 때,
GOT의 값을 검증하지 않는다는 보안상의 약점이 있습니다.

따라서, GOT에서 저장된 어떤 함수의 주소를 공격자가 임의로 변경할 수 있다면,
해당 함수를 다음 번에 호출할 때, 공격자가 원하는 코드가 실행될 수 있습니다.

이런 공격 기법을 'GOT Overwrite'라고 부르며,
임의 주소에 값을 쓸 수 있을 때,
RCE를 하기 위한 방법으로 상요될 수 있습니다.


5. Q&A

-


6. 마치며

-

[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.

profile
아무것도 모르는 컴공 학생의 Wonder_Land

0개의 댓글