지금부터 Linux 환경에서의 메모리 보호 기법에 대해 알아봅시다!
이번 편에서는 ASLR, NX, ASCII-Armor, Stack canary에 대해 알아보겠습니다.
위키를 통해 “메모리 보호”라는 말의 정의를 알아보고 넘어갑시다.
- 실습 환경: CentOS 6.7 (32bit)
ASLR : Address Space Layout Randomization
먼저 ASLR에 대해 알아보겠습니다.
ASLR이란, 메모리상의 공격을 어렵게 하기 위해 스택이나 힙, 라이브러리 등의 주소를 랜덤으로 프로세스 주소 공간에 배치함으로써 실행할 때 마다 데이터의 주소가 바뀌게 하는 기법입니다.
말로만 설명하면 잘 모르겠으니 눈으로 확인 해 봅시다 :)
cat /proc/self/maps 명령으로 스택, 힙, 라이브러리 등의 주소가 랜덤하게 바뀌는 것이 확인됩니다.
여기서 cat /proc/self/maps 명령이 뭐냐하면.
/proc : process의 줄임말이며, 이 디렉터리에 프로세스의 정보들이 저장됩니다.
/proc/self : 현재 실행되고 있는 프로세스의 정보가 담겨있는 디렉토리입니다.
/proc/self/maps : 현재 실행되고 있는 프로세스의 주소 맵입니다.
즉 우리는 cat /proc/self/maps를 통해 cat에 대한 실제 주소 공간 레이아웃을 본겁니다. 그러면 이제 ASLR이 적용되지 않았을 때의 주소 공간 레이아웃을 봐봅시다.
ASLR을 해제하는 명령은 echo 0 > /proc/sys/kernel/randomize_va_space 입니다.
randomize_va_space=0 : ASLR 해제
randomize_va_space=1 : 랜덤 스택 & 랜덤 라이브러리 설정
randomize_va_space=2 : 랜덤 스택 & 랜덤 라이브러리 & 랜덤 힙 설정
ASLR을 해제하니 스택, 힙, 라이브러리 등의 주소가 매번 고정적이게 됩니다. 다시 한 번 예제를 통해 ASLR이 적용됐을 때와 적용되지 않았을 때의 차이를 한눈에 보여드리겠습니다.
아래와 같이 간단하게 힙과 스택의 주소를 출력해 주는 프로그램을 짭니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <stdio.h> #include <stdlib.h> #include <string.h> int main(){ char *buf = NULL; char *buf = NULL; buf = ( char *) malloc (100); buf2 = "abcd" ; printf ( "[Heap] buf addr: %p\n" , buf); printf ( "[Stack] buf2 addr: %p\n" , buf2); return 0; } |
위 프로그램에서는 malloc을 이용해 힙 영역에 buf를 100bytes만큼 할당을 하고, buf2는 스택 영역에 저장을 합니다. 그리고는 buf와 buf2의 주소를 각각 출력해 줍니다.
gcc -o test test.cgcc 를 이용하여 컴파일을 해주고 프로그램을 실행시킵니다.
왼쪽은 ASLR이 설정되어 있는 상황이며 buf와 buf2의 주소가 프로그램이 실행될 때마다 바뀝니다. 오른쪽은 ASLR이 해제되어 있는 상황이며 buf와 buf2의 주소가 변함없습니다.
이쯤되면 ASLR이 뭔지 감을 잡으셨을 것 같으니 다음으로 넘어가봅시당.
DEP : Data Execution Prevention
다음으로 DEP에 대해 알아보겠습니다.
DEP란, 데이터 영역에서 코드가 실행되는 것을 막는 기법입니다.
쉬운 예로, 공격자가 Buffer Overflow 공격을 일으켜 return address를 스택상의 한 주소(쉘코드가 위치한 주소)로 변경했다고 칩시다. DEP가 적용되지 않았을 경우에는 그대로 쉘코드가 실행이 되겠지만, DEP가 적용된 경우에는 실행권한이 없으므로 쉘코드가 실행되지 않고 프로그램에 대한 예외처리 후 종료가 됩니다.
예제 프로그램을 통해 DEP에 대해 알아봅시다.
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h> #include <stdlib.h> int main(){ char str[256]; char * char = ( char *) malloc (100); printf ( "Input: " ); gets (str); printf ( "%p\n" , str); } |
예제 프로그램은 간단한 bof 취약점이 존재하는 프로그램입니다. 이제 이 프로그램의 스택에 실행권한을 설정(-z execstack 옵션)하여 컴파일한 후 실행시키면 다음과 같습니다.
그리고 이 예제 프로그램의 스택과 힙에는 실행권한이 있습니다.
이제 스택에 실행권한이 있을 때와 없을 때의 차이를 위의 예제를 통해 알아보겠습니다.checksec.sh이라는 스크립트를 통해 아래와 같이 파일에 어떤 보호기법이 걸려있는 지 알 수 있습니다.
이렇게 바이너리에 DEP가 걸려 있지 않다면(NX disabled) bof를 통해 return address를 스택 상에 쉘 코드가 위치한 곳으로 변조하면 그대로 쉘코드가 실행됩니다.
ASCII-Armor
다음으로 ASCII-Armor입니다.
ASCII-Armor란, 공유라이브러리 영역의 상위 주소에 0x00을 포함시키는 방법입니다.
이 기법은 RTL(Return To Library) 공격에 대응하기 위한 방법으로, 공격자가 라이브러리를 호출하는 Buffer Overflow 공격을 해도 NULL바이트가 삽입된 주소로는 접근할 수 없습니다.
잠깐 RTL 공격에 대해 이야기를 하자면, 한마디로 Return address에 libc 내의 함수를 덮어씌워 쉘코드 없이 exploit 하는 것입니다. 아무튼 RTL 공격은 libc라고 하는 공유 라이브러리 내의 함수로 리턴하게 하여 프로그램의 실행흐름을 조작하는 공격이기 때문에, 운영체제에 ASCII-Armor 보호기법이 적용되어 있다면 공격자가 라이브러리를 호출하는 공격을 한다고 하더라도 NULL바이트가 삽입되게 되므로 쉽게 exploit 하지 못하게 됩니다.
Stack Canary
마지막으로 Stack Canary에 대해 알아보겠습니다.
함수 진입 시 스택에 SFP(Saved Frame Pointer)와 return addressr 정보를 저장할 때, 이 정보들이 공격자에 의해 덮어씌워지는 것으로부터 보호하기 위해 스택상의 변수들의 공간과 SFP 사이에 특정한 값을 추가하는데 이 값을 Canary라고 합니다.
공격자가 return address를 조작하기 위해 Buffer Overflow 공격을 시도할 때, return address를 덮기 이전에 Canary 값이 먼저 덮어지기 때문에 이 Canary 값의 변조 유무로 Buffer Overflow를 탐지할 수 있습니다.
위 화면에서 main+12는 gs:0x14에서 canary값을 얻어와 eax 레지스터에 저장하는 부분이고 main+18은 eax 레지스터에 저장된 값(canary값)을 스택에 저장하는 부분입니다. 이 때 canary 값은 다음과 같은 위치에 저장됩니다.
그리고 프로그램이 기능을 다 하고 return되어 종료될 때, 프로그램 코드 실행 전 스택에 저장했던 canary 값과 gs:0x14의 원본 값을 비교하여 그 값이 다르다면 Buffer Overflow 공격을 당했다고 판단하고 그 자리에서 바로 프로그램을 종료해버립니다. (main+73~89 부분)
이제 실제로 종료가 되는지 Buffer Overflow 공격을 해서 canary값을 변조시켜 봅시다!먼저 A 300개를 a라는 파일에 저장시킨 후 프로그램 실행 시 입력시켜줬습니다.
원래의 Canary 값과 현재 스택에 저장된 Canary 값이 바뀌어서 Buffer Overflow 공격을 탐지하고 프로그램이 종료되었습니다.
이렇게 리눅스 환경에서의 메모리 보호기법 4가지에 대해 알아보았습니다 :)
다음 편에서는 RELRO에 대해 알아보겠습니다.
-written by Salen