01 라이브러리
라이브러리는 컴퓨터 시스템에서, 프로그램들이 함수나, 변수를 공유해서 사용할 수 있게 한다.
C언어를 비롯하여 많은 컴파일 언어들은 자주 사용되는 함수들의 정의를 묶어서 하나의 라이브러리 파일로 만들고, 이를 여러 프로그램이 공유해서 사용할 수 있도록 지원하고 있다. 라이브러리를 사용하면 같은 함수를 반복적으로 정의해야 하는 수고를 덜 수 있어서 코드 개발의 효율이 높아진다는 장점이 있다.
각 언어에서 사용되는 함수들은 표준 라이브러리가 제작되어 있기에 개발자가 쉽게 해당 함수들을 사용할 수 있는 것이다. 대표적으로, C의 표준 라이브러리인 libc는 우분투에 기본으로 탑재된 라이브러리이며, 실습환경에서는 /lib/x86_64-linux-gnu/libc.so.6에 있다.
02 링크
(1) 링크 전
링크(Link)는 많은 프로그래밍 언어에서 컴파일의 마지막 단계이다. 프로그램에서 어떤 라이브러리의 함수를 사용한다면, 호출된 함수와 실제 라이브러리의 함수가 링크 과정에서 연결된다.
아래 간단한 예시를 통해 더 자세히 살펴보자.
// Name: hello-world.c
// Compile: gcc -o hello-world hello-world.c
#include <stdio.h>
int main() {
puts("Hello, world!");
return 0;
}
리눅스에서 C 코드는 ELF 형식을 갖춘 오브젝트 파일로 번역할 수 있다. 아래와 같은 명령어를 통해 hello-world.c를 어셈블 해보자.
위 명령어들의 실행 결과를 통해 puts의 선언이 stdio.h에 있어서 심볼로는 기록되어 있지만, 심볼에 대한 자세한 내용은 하나도 기록되어 있지 않는 것을 확인할 수 있다.
(2) 링크 후
예제를 온전히 컴파일 한 후 아래와 같은 명령어를 통해 링크 전과 비교해보자.
위와 같이 libc에서 puts의 정의를 찾아 연결한 것을 확인할 수 있다.
libc가 있는 /lib/x86_64-linux-gnu/가 표준 라이브러리 경로에 포함되어 있기 때문에 libc를 같이 컴파일하지 않았음에도 libc에서 해당 심볼을 탐색한 이유를 알 수 있다.
(3) 라이브러리와 링크의 종류
1. 동적 링크
동적 링크된 바이너리를 실행하면 동적 라이브러리가 프로세스의 메모리에 매핑됨. 그리고 실행 중에 라이브러리의 함수를 호출하면 매핑된 라이브러리에서 호출할 함수의 주소를 찾고, 그 함수를 실행한다.
▶ 이는 사람이 도서관에 방문해서 원하는 책의 위치를 찾고, 그 책에서 정보를 습득하는 과정과 유사함.
2. 정적 링크
정적 링크를 하면 바이너리에 정적 라이브러리의 필요한 모든 함수가 포함된다. 따라서 해당 함수를 호출할 때, 라이브러리를 참조하는 것이 아닌, 자신의 함수를 호출하는 것처럼 호출할 수 있다. 여러 바이너리에서 라이브러리를 사용하면 그 라이브러리의 복제가 여러 번 이루어지게 되므로 용량을 낭비하게 된다.
▶ 즉, 정적 링크는 도서관에서 필요한 모든 책을 암기하는 것과 같다.
(4) 동적 링크 vs 정적 링크
앞서 사용한 hello-world.c 예제를 정적 컴파일하여 static 을, 동적 컴파일하여 dynamic 을 생성해보자.
각각의 용량을 ls 명령어를 통해 비교해보면 static이 dynamic보다 50배 가까이 더 많은 용량을 차지하는 것을 확인할 수 있다.
호출 방식에도 차이가 발생한다. static에서는 puts가 있는 0x40c140을 직접 호출한다. 반면, dynamic에서는 puts의 plt주소인 0x401040을 호출한다. 이러한 차이가 발생하는 이유는 동적 링크된 바이너리는 함수의 주소를 라이브러리에서 찾아야하기 때문이다. plt는 이 과정에 사용되는 테이블이다.
03 PLT & GOT
(1) PLT와 GOT
PLT(Procedure Linkage Table)와 GOT(Global Offset Table)는 라이브러리에서 동적 링크된 심볼의 주소를 찾을 때 사용하는 테이블이다.
바이너리가 실행되면 ASLR에 의해 라이브러리가 임의의 주소에 매핑된다. 이 상태에서 라이브러리 함수를 호출하면, 함수의 이름을 바탕으로 라이브러리에서 심볼들을 탐색하고, 해당 함수의 정의를 발견하면 그 주소로 실행 흐름을 옮기게 된다. 이 과정을 통틀어 runtime resolve라고 한다.
그런데 만약 반복적으로 호출되는 함수의 정의를 매번 탐색해야 한다면 비효율적이다. 그래서 ELF는 GOT라는 테이블을 두고, resolve된 함수의 주소를 해당 테이블에 저장할 수 있다. 그리고 나중에 다시 해당 함수를 호출하면 저장된 주소를 꺼내서 사용한다.
아래 예제 코드를 통해 어떻게 발생하는지 자세히 살펴보자.
// Name: got.c
// Compile: gcc -o got got.c -no-pie
#include <stdio.h>
int main() {
puts("Resolving address of 'puts'.");
puts("Get address from GOT");
}
resolve 되기 전
got 명령어를 사용하여 GOT의 상태를 확인해보자. puts 의 GOT 엔트리인 0x404018 에는 아직 puts 의 주소를 찾기 전이므로, 함수 주소 대신 .plt 섹션 어딘가의 주소인 0x401030 이 적혀있다.
다음으로 main() 에서 puts@plt 를 호출하는 지점에 중단점을 설정하고, 내부로 따라가 보자. PLT에서는 먼저 puts 의 GOT 엔트리에 쓰인 값인 0x401030 으로 실행 흐름을 옮긴다. pwndbg 컨텍스트에서 DISASM 부분은 프로그램에서 명령어가 호출되는 순서인 제어 흐름(Control flow)을 보여주는데, 실행 흐름을 따라가면 _dl_runtime_resolve_fxsave 가 호출될 것임을 알 수 있다.
여기서 코드를 조금 더 실행시키면 _dl_runtime_resolve_fxsave 라는 함수가 실행되는데, 이 함수에서 puts 의 주소가 구해지고, GOT 엔트리에 주소를 쓸 수 있다.
실제로 ni 명령어를 반복적으로 수행해서 _dl_runtime_resolve_fxsave 안으로 진입한 후, finish 명령어로 함수를 빠져나오면, puts 의 GOT 엔트리에 libc 영역 내 실제 puts 주소인 0x7ffff7e02ed0 가 쓰여 있는 모습을 확인할 수 있다.
resolve된 후
puts@plt 를 두 번째로 호출할 때는 puts 의 GOT 엔트리에 실제 puts 의 주소인 0x7ffff7e02ed0 가 쓰여있어서 바로 puts 가 실행된다는 것을 확인할 수 있다.
(2) 시스템 해킹 관점에서 본 PLT와 GOT
PLT와 GOT는 동적 링크된 바이너리에서 라이브러리 함수의 주소를 찾고, 기록할 때 사용되는 중요한 테이블이다. 그런데, 시스템 해커의 관점에서 보면 PLT에서 GOT를 참조하여 실행 흐름을 옮길 때, GOT의 값을 검증하지 않는다는 보안상의 약점이 있다.
따라서 만약 앞의 예에서 puts의 GOT 엔트리에 저장된 값을 공격자가 임의로 변경할 수 있으면, puts가 호출될 때 공격자가 원하는 코드가 실행되게 할 수 있다.
GOT 엔트리에 저장된 값을 임의로 변조할 수 있는 수단이 있음을 가정하고, 이 공격 기법이 가능한지 gdb를 이용하여 간단하게 실험을 해볼 수 있다. got바이너리에서 main() 내 두 번째 puts() 호출 직전에 puts의 GOT 엔트리를 “AAAAAAAA”로 변경한 후 실행시키면, 실제로 “AAAAAAAA”로 실행 흐름이 옮겨지는 것을 확인할 수 있다.
이와 같이 GOT 엔트리에 임의의 값을 오버라이트(Overwrite)하여 실행 흐름을 변조하는 공격 기법을 GOT Overwrite라고 부른다. 일반적으로 임의 주소에 임의의 값을 오버라이트하는 수단을 가지고 있을 때 수행하는 공격 기법이다.
참고자료 : https://dreamhack.io/lecture/roadmaps/2
'Security > System Hacking' 카테고리의 다른 글
[Bypass NX & ASLR] Return to Library (2) | 2024.10.09 |
---|---|
[Bypass NX & ASLR] NX & ASLR (0) | 2024.10.09 |
basic_exploitation_000 (0) | 2024.10.09 |
basic_exploitation_001 (0) | 2024.10.09 |
[Bypass NX & ASLR] Return Oriented Programming (0) | 2024.10.09 |