티스토리 뷰
개인적으로 흥미로웠던 점 중 하나는 PIE를 사용하여 컴파일하는 과정을 지켜보던 중에 결과로 생긴 바이너리에 영향을 미친다는 점이였다. 아래의 "Hello World" 프로그램을 살펴보자:
1 2 3 4 5 6 7 | #include "not/stdio.h" char message[] = "Hello World"; int main(int argc, char *argv[], char *envp[]) { puts(message); return 0; } | cs |
다른 영향을 최소화하기 위해서, 컴파일 시 나 자신이 구현한 표준 라이브러리 함수를 사용하였다.
$ cc -nostdlib -nodefaultlibs -I. -o static-example os/syscall.x86_64.s os/start.x86_64.s not/strlen.c not/puts.c main.c $ size --format=sysv static-example static-example : section size addr .text 420 4194536 .rodata 2 4194956 .eh_frame 280 4194960 .data 12 6292392 .comment 44 0 Total 758
위의 빌드에 의해 ELF 바이너리는 실행할 경우 libc 또는 로더에 의존하지 않는다. 이 의미는 종속성을 이용해 동적으로 탐색 및 결속을 할 수 있는 링커에 의존하지 않은 채 실행하고 메모리에 로드할 수 있다는 것을 말한다. 게다가 루틴을 공유하고 재활용하기 어렵게 만든다. 여기에 대한 문제에 대한 일반적인 해결 방안은 공유 라이브러리를 생성하는 것이다:
$ cc -fpic -shared -I. -nostdlib -nodefaultlibs -o libnotc.so os/syscall.x86_64.s os/syscall.c not/strlen.c not/puts.c
이 후, 외부의 공유 라이브러리내에 존재하는 심볼 정의부분을 나타내고 있는 메인 바이너리를 재컴파일한다:
$ cc -nostdlib -nodefaultlibs -I. -o dynamic-example os/start.x86_64.s main.c -L. -lnotc
결과적으로 만들어진 바이너리는 공유 라이브러리 libnotc.so 내부의 코드를 포함하고 있는 .text 섹션가지고 있기 때문에 크기가 이전보다 더 작다. 주요 차이점은 다음과 같다:
$ size --format=sysv dynamic-example dynamic-example : section size addr .interp 28 4194816 .note.gnu.build-id 36 4194844 .gnu.hash 48 4194880 .dynsym 144 4194928 .dynstr 46 4195072 .rela.plt 48 4195120 .plt 48 4195168 .text 56 4195216 .eh_frame_hdr 28 4195272 .eh_frame 96 4195304 .dynamic 272 6292552 .got.plt 40 6292824 .data 12 6292864 .comment 44 0 Total 946
프로그램이 제대로 요구사항을 실행하기 위해서는 ELF 바이너리는 로더가 실행할 때 심볼을 처리할 수 있도록 허가하는 것과 같은 방법으로 구축할 필요가 있다. 메모리의 심볼 주소는 메인 바이너리의 일부가 아니기 때문에 로더는 프로시저 연결 테이블(.plt 섹션)에 간접적인 수준으로 추가한다. 직접 puts()와 같은 함수를 호출하는 대신, .plt 섹션은 로더를 가리키는 특별한 엔트리 포인트를 포함한다. 이 때, 로더는 함수의 실제 주소를 확인해야 한다. 그리고 로더는 Global Offset Table(GOT) 내의 엔트리를 업데이트한 후, 같은 루틴을 다시 호출할 경우 해당 GOT 엔트리로부터 점프하도록 만든다.
표준 ELF 바이너리는 일반적으로 가상 메모리의 동일한 베이스 주소에 실행이 될 때마다 로드되어, 링커는 심볼의 절대 주소로 이동하여 재배치할 수 없는 코드라는 장점을 가진다. 이것은 상대 주소를 사용하여 절대 주소보다 빠르게 점프할 수 있어 약간의 성능적 이점을 가지게 된다. 이 과정을 위해 다른 레지스터를 필요로 하기 때문에 i386 응용 프로그램에 적용된다.
PIE로 예제 프로그램의 재컴파일해야 동적 응용 프로그램과 PIE 응용 프로그램 사이에서 차이점을 볼 수 있었다. PIE 프로그램을 만들기 위해서는 이전에 가지고 있던 코드에 -fpic -pie 옵션을 추가하면 된다:
$ cc -fpic -pie -nostdlib -nodefaultlibs -I. -o pie-example os/start.x86_64.s main.c -L. -lnotc $ size --format=sysv pie-example pie-example : section size addr .interp 28 512 .note.gnu.build-id 36 540 .gnu.hash 52 576 .dynsym 192 632 .dynstr 54 824 .rela.dyn 24 880 .rela.plt 48 904 .plt 48 960 .text 61 1008 .eh_frame_hdr 28 1072 .eh_frame 96 1104 .dynamic 320 2098352 .got 8 2098672 .got.plt 40 2098680 .data 12 2098720 .comment 44 0 Total 1091
각 ELF 섹션의 size 명령어를 통해 표시된 주소는 상대 주소이며, 동적-예제에서 표시된 주소는 절대 주소이다. 모든 프로그램은 실행 시 가상 메모리의 랜덤한 위치에 로드되기 때문에 필수적이다. 이전에 링크된 라이브러리가 포함되어 있으며, 표준 시스템 라이브러리의 알려진 주소로 리턴하는 공격에 대비한 효과적인 익스플로잇 완충 기술을 함께 제공한다. 이 방어 기법과 다음 게시물에서 설명할 상대적인 재배치의 수를 줄일 수 있는 방법으로 오버헤드를 발생시킬 것이다.
'System > Linux' 카테고리의 다른 글
Understanding glibc malloc 번역 (0) | 2016.07.16 |
---|---|
Heap overflow using Malloc Maleficarum 번역 (0) | 2016.07.14 |
Memory Leak으로 얻은 주소로 offset 알아내기 (0) | 2016.05.01 |
Heap chunk 정리 (0) | 2016.04.14 |
Linux Remote Shellcode (0) | 2016.01.30 |