RELRO
- 함수가 처음 호출될 때 함수의 주소를 구하고, 이를 GOT에 적는 것을 Lazy Binding 이라고 한다.
- Lazy Binding을 하는 바이너리는 실행 중에 GOT 테이블을 업데이트 해야 하므로 쓰기 권한이 부여되지만, 이 과정에서 바이너리를 취약하게 만드는 원인이 된다.
- ELF의 데이터 세그먼트에 프로세스 초기화 및 종료와 관련된
.init_array
,.fini_array
가 있는데 이 영역들은 프로세스의 시작과 종료에 실행할 함수들의 주소를 저장하고 있다.- 이 부분에 공격자가 임의로 쓸 수 있는 값이 있다면 프로세스의 실행 흐름이 조작될 수 있다.
- 리눅스 개발자들은 이러한 문제를 해결하고자 프로세스의 데이터 세그먼트를 보호하는 RELocation Read-Only(RELRO)를 개발했고, RELRO는 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거한다.
- RELRO는 RELRO를 적용하는 범위에 따라 두 가지로 구분된다. 하나는 RELRO를 부분적으로 적용하는 Partial RELRO이고, 나머지는 가장 넓은 영역에 RELRO를 적용하는 Full RELRO이다.
RELRO 실습 코드
// Name: relro.c
// Compile: gcc -o prelro relro.c -no-pie -fno-PIE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
FILE *fp;
char ch;
fp = fopen("/proc/self/maps", "r");
while (1) {
ch = fgetc(fp);
if (ch == EOF) break;
putchar(ch);
}
return 0;
}
RELRO 검사
checksec
도구로 RELRO가 Partial인지 Full인지 검사할 수 있다.- 보통 gcc는 Full RELRO를 기본으로 적용하며, PIE를 해제하면 Partial RELRO를 적용한다.
Partial RELRO 권한
- 아래는 prelro 바이너리를 실행한 결과고 0x40400부터 0x405000까지의 주소에 쓰기 권한을 확인할 수 있다.
objdump -h ./prerlo
명령으로 섹션 헤더를 참조해보면.got.plt
,.data
,.bss
가 할당되어 있다.- 이 섹션들의 주소가 위 주소 사이에 존재하므로 쓰기가 가능하지만 init_arry와 fini_array는 위 주소 범위에 해당하지 않으므로 쓰기가 불가능한 것을 알 수 있다.
.got와 .got.plt
Partial RELRO가 적용된 바이너리는 got와 관련된 섹션이 .got와 .got.plt 두 가지가 존재한다. 전역 변수 중에서 실행되는 시점에 바인딩되는 변수는 .got에 위치한다. 바이너리가 실행될 때는 이미 바인딩이 완료되었으므로 .got에는 쓰기 권한을 부여하지 않는다. 반면 .got.plt 는 실행 중에 값이 써져야 하므로 쓰기 권한이 부여된다. Partial RELRO가 적용된 바이너리에서 대부분 함수들의 GOT 엔트리는 .got.plt에 저장된다.
Full RELRO
- relro.c 의 실습 코드에서 별도의 컴파일 옵션 없이 컴파일하면 Full RELRO가 적용된 바이너리가 생성된다.
- 바이너리를 실행하여 메모리 맵을 확인해보면, 전과 다르게 got에는 쓰기 권한이 제거되어 있으며 data와 bss에만 쓰기 권한이 있다.
- 바이너리를 실행하면 쓰기 권한이 있는 주소를 찾고,
objdump -h ./frelro
명령을 통해 data의 섹션 주소와 매핑된 첫 주소를 더하면 쓰기 권한이 있는지 확인할 수 있다. - Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩된다. 따라서 GOT에는 쓰기 권한이 부여되지 않는다.
RELRO 우회
- Partial RELRO의 경우,
.init_array
와.fini_array
에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 수행하기 어려워진다. 하지만.got.plt
영역에 대한 쓰기 권한이 존재하므로 GOT Overwrite 공격을 활용할 수 있다. - Full RELRO의 경우
.init_array
와.fini_array
뿐만 아니라.got
영역에도 쓰기 권한이 제거되었다. 그래서 공격자들은 덮어쓸 수 있는 다른 함수 포인터를 찾다가 라이브러리에 위치한 hook을 찾아냈다. 라이브러리 함수의 대표적인 hook이 malloc hook과 free hook이다. 원래 이 함수 포인터는 동적 메모리의 할당과 해제 과정에서 버그를 디버깅하기 쉽게 하려고 만들어 졌다. - malloc 함수의 코드를 살펴보면, 함수의 시작 부분에서
__malloc_hook
이 존재하는지 검사하고, 존재하면 이를 호출한다.__malloc_hook
은 libc.so에서 쓰기 가능한 영역에 위치한다. 따라서 공격자는 libc가 매핑된 주소를 알 때, 이 변수를 조작하고 malloc을 호출하여 실행 흐름을 조작할 수 있다. 이와 같은 공격 기법을 통틀어 Hook Overwrite라고 부른다.
요약
- RELRO 보호 기법은 프로세스의 데이터 영역을 보호하기 위해 도입된 보호 기법
- Partial RELRO 바이너리는 got에 쓰기 권한이 남아 있어 GOT Overwrite 공격으로 우회 가능
- Full RELRO 바이너리는 데이터 영역의 모든 불필요한 쓰기 권한이 삭제되어 Hook Overwrite등의 새로운 공격 기법 사용해야 함
댓글남기기