티스토리 뷰

원본 URL : https://access.redhat.com/blogs/766093/posts/1975793

------------------------------------------------------------------------------------------------------------
Position Independent Executables(PIE, 위치 독립 코드)

Fedora Engineering Steering Committee(FESCo, 페도라 공학 운영 위원회)는 GCC의 보안 기능을 사용한 빌드 패키지 목록을 보수적으로 유리하였다. 해당 목록에서 패키지는 패키지 제작사의 재량에 맞긴 보안 기능을 가지고 있지 않다. 보안이 강화된 바이너리가 필요한 경우, 지역 사회에서의 합의는 현재 없는 상태이다. 그 결과, 보안이 강화된 바이너리의 사용은 논란의 소지가 될 수 있는 소재이다. 대부분의 논쟁은 기능의 사용에 대한 성능보다 보안 이점이 더 비중이 높은지가 될 것이다.

Position Independent Executables(PIE)는 강화된 패키지로 빌드 처리한 결과물이다. 모든 PIE 바이너리는 응용 프로그램이 실행된 각 시간마다 가상 메모리 내부에 랜덤한 위치에 로드된다. 덕분에 Return Oriented Programming(ROP) 공격을 보다 어렵게 만든다. 이 블로그 포스트를 통해 최근 PIE를 사용하도록 빌드된 프로그램의 효과를 보았던 것으로 PIE에 대해 연구한 결과물을 보여줄 것이다. 이 연구에서 기술적인 분석을 기반으로 보안에 대해 더 좋은 결정을 할 수 있는 것을 목적으로 하여, 한 개의 프로그램이 시작할 동안의 로더에 나타나는 오버헤드를 조사하였다. 프로그램 시작을 중심으로 하여 주로 어디서 PIE가 최대의 성능으로 영향을 나타내는지 테스트하였다. 실행 처리 전송 성능은 대체로 프로그램과 공유 라이브러리를 얼마나 잘 계획했는지에 따라서 x86_64 기기의 표준 Dynamic Shared Objects(DSOs)와 비슷하다. 현재 올라온 것과 같이 보안 기능에 더 초점을 두었다. 그리고 PIE 응용 프로그램의 시작 시간과 일반적인 응용 프로그램의 비교할 수 있을 부분을 테스트하였다.


개인적으로 흥미로웠던 점 중 하나는 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
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31