RELRO
RELRO
1. RELRO (Relocation Read-Only)
1. RELRO 란?
RELRO 는 Relocation Read-Only 줄임말로,
ELF 바이너리 또는 프로세스의 데이터 섹션을 보호하는 기술이다.
즉 메모리가 변경되는 것을 보호하는 기술이다.
RELRO 가 적용되면 GOT_Overwrite 공격을 막을 수 있다.
RELRO 에는 크게 3가지 종류가 있다.
- FULL RELRO
- Partial RELRO
- NO RELRO
//Name : relro.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
size_t *p = (size_t *)strtol(argv[1], NULL, 16);
p[0] = 0x41414141;
printf("RELRO TEST : %p\n", p);
return 0;
}
0x41414141 값을 주어진 주소에 쓰는 프로그램이다.
이 프로그램을 이용하여 각 영역에 쓰기 권한이 부여됐는지 확인할 수 있다.
2. NO RELRO
1. 보호기법 확인
gcc -o norelro relro.c -fno-stack-protector -no-pie -z norelro
gcc 에서 -z norelro 옵션을 넣으면 RELRO 를 적용하지 않은 상태로 컴파일 할 수 있다.
checksec 명령어로 확인해보자
RELRO 가 적용되지 않은 것을 확인할 수 있다.
2. 메모리 영역 확인
gdb 에서 main 함수에 break 를 걸고 실행해보자
vmmap 명령어로 메모리 상태 및 권한을 확인할 수 있다.
이제 각 영역이 어떤 부분인지 알아보자
$ readelf -S norelro
readelf 명령은 elf 파일의 각종 정보를 보기 위한 명령어이다.
-S 옵션은 섹션 헤더를 출력한다.
0x400000 0x401000 r--p 1000 0 /home/gunp4ng/pwnable/relro/norelro
0x400000 ~ 0x401000 영역은 읽기 권한만 있는 것을 알 수 있다.
0x400000 ~ 0x401000 영역은 ELF Header 및 프로그램 헤더로 읽기 권한만 있다.
0x401000 0x402000 r-xp 1000 1000 /home/gunp4ng/pwnable/relro/norelro
0x401000 ~ 0x402000 영역은 읽기, 쓰기 권한만 있는 것을 알 수 있다.
0x401000 ~ 0x402000 영역은 코드 영역으로 읽기 쓰기 권한만 있다.
0x402000 0x403000 r--p 1000 2000 /home/gunp4ng/pwnable/relro/norelro
0x402000 ~ 0x403000 영역은 읽기 권한만 있는 것을 알 수 있다.
0x402000 ~ 0x403000 영역은 RODATA 영역으로 읽기 권한만 있다.
0x403000 0x404000 rw-p 1000 2000 /home/gunp4ng/pwnable/relro/norelro
0x403000 ~ 0x404000 영역은 읽기, 쓰기 권한이 있는 것을 확인할 수 있다.
0x403000 ~ 0x404000 영역은 got, bss, _dynamic 등으로 읽기 쓰기 권한만 있다.
NO RELRO 는 이 영역 전체를 읽고 쓸 수 있는 권한이 있어 매우 취약하다.
3. 각 섹션에 데이터 써보기
각 영역에 데이터를 쓰면 값이 잘 써지는 것을 확인할 수 있다.
.fini_array 영역 0x403100 은 main 함수가 종료되고 돌아갈 주소를 저장한다.
Segmentation fault 에러가 뜨는 이유는 main 함수 종료 후 돌아갈 주소값이 0x41414141 로 바뀌어
올바른 값이 아니기 때문이다.
3. Partial RELRO
1. 보호기법 확인
gcc -o partialrelro relro.c -fno-stack-protector -no-pie -z relro
gcc 에서 -z relro 옵션을 넣으면 Partial RELRO 로 컴파일 할 수 있다.
checksec 명령어로 확인해보자
Partial RELRO 가 적용된 것을 확인할 수 있다.
2. 메모리 영역 확인
gdb 에서 main 함수에 break 걸고 실행해보자
vmmap 명령어로 메모리 상태 및 권한을 확인할 수 있다.
NO RELRO 일 때와는 다르게 0x403000 ~ 0x404000 영역에 쓰기 권한이 사라진 것을 볼 수 있다.
그리고 0x404000 ~ 0x405000 영역이 추가되고 쓰기 권한이 부여된 것을 확인할 수 있다.
추가된 0x404000 ~ 0x405000 영역을 알아보자
$ readelf -S partialrelro
readelf 명령어로 각 영역의 주소를 확인한다.
0x404000 ~ 0x405000 영역은 .got.plt .data .bss 영역이다
이 영역들을 제외하고는 쓰기 권한이 제거된 것을 확인할 수 있다.
추가로 0x403000 ~ 0x404000 영역에 .got 가 있다.
그럼 .got 와 .got.plt 는 무슨 차이가 있을까?
.plt 영역에 대응되는 영역은 정확히 .got.plt 영역이다
.got 영역은 global data 와 관련된 영역으로, 로딩 될 때 동적 링크가 재배치 해준다.
.got.plt 영역은 함수 호출과 관련된 .plt 와 대응되는 영역이다.
이전 글에서 설명했던 실제 함수가 호출되는 시점(runtime resolve) 에 재배치가 일어난다.
.got.plt 영역에 쓰기가 가능하기 때문에 GOT Overwrite 가 가능하다
3. 각 섹션에 데이터 써보기
값이 잘 써지는 것을 확인할 수 있다.
아까 .got 영역은 쓰기 권한이 부여되지 않았다.
실제로도 값이 써지지 않는지 확인해보자
Segmentation fault 에러가 뜨면서 값이 써지지 않는 것을 확인할 수 있다.
4. Full RELRO
1. 보호기법 확인
gcc -o fullrelro relro.c -no-pie -fno-stack-protector -z relro -z now
gcc 에서 -z relro -z now 옵션을 넣으면 Full RELRO 로 컴파일 할 수 있다.
checksec 명령어로 확인해보자
Full RELRO 가 적용된 것을 확인할 수 있다.
2. 메모리 영역 확인
vmmap 명령어로 메모리를 확인해보자
Partial RELRO 와 똑같이 0x403000 ~ 0x404000 영역의 쓰기 권한이 사라지고,
0x404000 ~ 0x405000 영역에 쓰기 권한이 부여됐다.
readelf 명령어로 각 영역의 주소를 알아보자
쓰기 권한이 부여된 0x404000 ~ 0x405000 영역에 .got.plt 영역이 사라진 것을 볼 수 있다.
.got.plt 영역에 쓰기 권한이 없기 때문에 GOT Overwrite 가 불가능하다.
3. 각 섹션에 데이터 써보기
.data .bss 영역을 제외하고 쓰기가 불가능한 것을 확인할 수 있다.
.got 영역 0x0403fc8 에 값을 써보자
Segmentation fault 에러가 뜨는 것을 확인할 수 있다.
.got 영역은 쓰기 권한이 없다.
5. Full RELRO 에서 .got.plt 영역이 없는 이유
1. NO RELRO, Partial RELRO
NO RELRO, Partial RELRO 는 라이브러리를 불러오는 방식이 같다.
Runtime Resolve 과정을 통해서 라이브러리에서 함수의 주소를 가져오게 된다.
PLT 가 GOT 를 참조하고 첫 호출이라면 GOT 에 _dl_runtime_resolve 함수가 라이브러리의 실제 주소를 써준다.
.got.plt 영역이 존재하고 쓰기가 가능한 이유이다.
2. Full RELRO
Full RELRO 에는 위에서 .got.plt 영역이 존재하지 않는 것을 확인했다.
NO RELRO, Partial RELRO 처럼 실행 중에 동적 링커를 통해 .got.plt 영역에 값을 쓸 수 없다.
그럼 라이브러리에서 함수의 실제 주소를 어떻게 가져올까?
gdb 에서 파일을 실행하지 않으면 Dynamic Linker 가 로딩되지 않는다.
두 번째 실행부터는 Dynamic Linker 가 로딩되어 동적 링크된 함수들을 볼 수 있다.
Dynamic Linker 시작 함수인 _dl_start 함수에 break 를 걸고 실행한다.
vmmap 으로 메모리를 확인해보자
0x403000 ~ 0x405000 에 쓰기 권한이 설정된 것을 볼 수 있다.
.got 영역은 0x403fc8 로 쓰기 권한이 있는 것을 알 수 있다.
Full RELRO 는 got 영역에 쓰기 권한이 없는 것을 위에서 확인했다.
그럼 Full RELRO 는 Dynamic Linker 를 호출하여 GOT 영역에 값을 다 써준 뒤
mprotect(메모리 권한을 바꿔주는 함수) 로 권한을 바꿔주는 것이라고 볼 수 있다.
직접 확인해보자
_dl_start 함수는 _dl_sysdep_start 함수를 호출한다.
_dl_sysdep_start 함수는 dl_main 함수를 호출한다.
dl_main 함수는 _dl_relocate_object 함수를 호출한다.
_dl_relocate_object 함수를 확인해보자
Runtime Resolve 과정에서 확인했던 _dl_lookup_symbol_x 함수가 보인다.
_dl_lookup_symbol_x 함수는 라이브러리에서 함수를 가져오는 역할을 한다.
반복문으로 모든 함수의 라이브러리 주소를 가져온다.
그 다음 GOT 에 값을 써준다
strtol GOT 에 값이 써진 것을 확인할 수 있다.
Dynamic Linker 의 역할은 여기서 끝이다.
Full RELRO 일 때는 GOT 영역에 쓰기 권한이 없기 때문에 메모리 권한을 설정해줘야 한다.
gdb 로 확인해보자
mprotect 를 호출 전에는 GOT 에 쓰기 권한이 있는 것을 볼 수 있다.
위의 명령어는
0x403000 부터 0x404000 (0x403000 + 0x1000) 까지 read(0x1) 권한만 설정하겠다는 뜻이다.
mprotect 가 권한을 설정해준 것을 볼 수 있다.
Reference
https://blackperl-security.gitlab.io/blog/2016/05/02/2016-05-02-linux-02/