GunP4ng 2024. 7. 15. 22:59

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 명령어로 확인해보자

NO RELRO

RELRO 가 적용되지 않은 것을 확인할 수 있다.

 

2. 메모리 영역 확인

gdb 에서 main 함수에 break 를 걸고 실행해보자

vmmap 명령어로 메모리 상태 및 권한을 확인할 수 있다.

vmmap

이제 각 영역이 어떤 부분인지 알아보자

 

$ readelf -S norelro

readelf 명령은 elf 파일의 각종 정보를 보기 위한 명령어이다.

-S 옵션은 섹션 헤더를 출력한다.

 

0x400000           0x401000 r--p     1000      0 /home/gunp4ng/pwnable/relro/norelro

0x400000 ~ 0x401000 영역은 읽기 권한만 있는 것을 알 수 있다.

0x400000 ~ 0x401000

0x400000 ~ 0x401000 영역은 ELF Header프로그램 헤더읽기 권한만 있다.

 

0x401000           0x402000 r-xp     1000   1000 /home/gunp4ng/pwnable/relro/norelro

0x401000 ~ 0x402000 영역은 읽기, 쓰기 권한만 있는 것을 알 수 있다.

0x401000 ~ 0x402000

0x401000 ~ 0x402000 영역은 코드 영역으로 읽기 쓰기 권한만 있다.

 

0x402000           0x403000 r--p     1000   2000 /home/gunp4ng/pwnable/relro/norelro

0x402000 ~ 0x403000 영역은 읽기 권한만 있는 것을 알 수 있다.

0x402000 ~ 0x403000

0x402000 ~ 0x403000 영역은 RODATA 영역으로 읽기 권한만 있다.

 

0x403000           0x404000 rw-p     1000   2000 /home/gunp4ng/pwnable/relro/norelro

0x403000 ~ 0x404000 영역은 읽기, 쓰기 권한이 있는 것을 확인할 수 있다.

0x403000 ~ 0x404000

0x403000 ~ 0x404000 영역은 got, bss, _dynamic 등으로 읽기 쓰기 권한만 있다.

NO RELRO 는 이 영역 전체를 읽고 쓸 수 있는 권한이 있어 매우 취약하다.

 

3. 각 섹션에 데이터 써보기

각 영역에 데이터 써보기

각 영역에 데이터를 쓰면 값이 잘 써지는 것을 확인할 수 있다.

 

.fini_array 영역 0x403100main 함수가 종료되고 돌아갈 주소를 저장한다.

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

Partial RELRO 가 적용된 것을 확인할 수 있다.

 

2. 메모리 영역 확인

gdb 에서 main 함수에 break 걸고 실행해보자

vmmap 명령어로 메모리 상태 및 권한을 확인할 수 있다.

vmmap

NO RELRO 일 때와는 다르게 0x403000 ~ 0x404000 영역에 쓰기 권한이 사라진 것을 볼 수 있다.

그리고 0x404000 ~ 0x405000 영역이 추가되고 쓰기 권한이 부여된 것을 확인할 수 있다.

 

추가된 0x404000 ~ 0x405000 영역을 알아보자

$ readelf -S partialrelro

readelf 명령어로 각 영역의 주소를 확인한다.

 

0x404000 ~ 0x405000

0x404000 ~ 0x405000 영역은 .got.plt .data .bss 영역이다

이 영역들을 제외하고는 쓰기 권한이 제거된 것을 확인할 수 있다.

 

추가로 0x403000 ~ 0x404000 영역에 .got 가 있다.

.got

그럼 .got.got.plt 는 무슨 차이가 있을까?

.plt 영역에 대응되는 영역은 정확히 .got.plt 영역이다

 

.got 영역은 global data 와 관련된 영역으로, 로딩 될 때 동적 링크가 재배치 해준다.

 

.got.plt 영역은 함수 호출과 관련된 .plt 와 대응되는 영역이다.

이전 글에서 설명했던 실제 함수가 호출되는 시점(runtime resolve)재배치가 일어난다.

 

.got.plt 영역에 쓰기가 가능하기 때문에 GOT Overwrite 가 가능하다

 

3. 각 섹션에 데이터 써보기

각 영역에 데이터 쓰기

값이 잘 써지는 것을 확인할 수 있다.

 

아까 .got 영역은 쓰기 권한이 부여되지 않았다.

실제로도 값이 써지지 않는지 확인해보자

.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

Full RELRO 가 적용된 것을 확인할 수 있다.

 

2. 메모리 영역 확인

vmmap 명령어로 메모리를 확인해보자

vmmap

Partial RELRO 와 똑같이 0x403000 ~ 0x404000 영역의 쓰기 권한이 사라지고,

0x404000 ~ 0x405000 영역에 쓰기 권한이 부여됐다.

 

readelf 명령어로 각 영역의 주소를 알아보자

0x404000 ~ 0x405000

쓰기 권한이 부여된 0x404000 ~ 0x405000 영역에 .got.plt 영역이 사라진 것을 볼 수 있다.

 

.got.plt 영역에 쓰기 권한이 없기 때문에 GOT Overwrite불가능하다.

 

3. 각 섹션에 데이터 써보기

.data .bss

.data .bss 영역을 제외하고 쓰기가 불가능한 것을 확인할 수 있다.

 

.got

.got 영역 0x0403fc8 에 값을 써보자

Segmentation fault 에러가 뜨는 것을 확인할 수 있다.

.got 영역은 쓰기 권한이 없다.

 

 

5. Full RELRO 에서 .got.plt 영역이 없는 이유

1. NO RELRO, Partial RELRO

NO RELRO, Partial RELRO 는 라이브러리를 불러오는 방식이 같다.

 

Runtime Resolve 과정을 통해서 라이브러리에서 함수의 주소를 가져오게 된다.

PLTGOT 를 참조하고 첫 호출이라면 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 으로 메모리를 확인해보자

vmmap

0x403000 ~ 0x405000쓰기 권한이 설정된 것을 볼 수 있다.

.got

.got 영역은 0x403fc8 로 쓰기 권한이 있는 것을 알 수 있다.

 

Full RELROgot 영역에 쓰기 권한이 없는 것을 위에서 확인했다.

그럼 Full RELRODynamic Linker 를 호출하여 GOT 영역에 값을 다 써준 뒤

mprotect(메모리 권한을 바꿔주는 함수) 로 권한을 바꿔주는 것이라고 볼 수 있다.

직접 확인해보자

 

_dl_start 함수는 _dl_sysdep_start 함수를 호출한다.

call _dl_sysdep_start

 

_dl_sysdep_start 함수는 dl_main 함수를 호출한다.

call dl_main

 

dl_main 함수는 _dl_relocate_object 함수를 호출한다.

call _dl_relocate_objcet

 

_dl_relocate_object 함수를 확인해보자

_dl_relocate_object

Runtime Resolve 과정에서 확인했던 _dl_lookup_symbol_x 함수가 보인다.

_dl_lookup_symbol_x 함수는 라이브러리에서 함수를 가져오는 역할을 한다.

반복문으로 모든 함수의 라이브러리 주소를 가져온다.

그 다음 GOT 에 값을 써준다

GOT

strtol GOT 에 값이 써진 것을 확인할 수 있다.

Dynamic Linker 의 역할은 여기서 끝이다.

 

Full RELRO 일 때는 GOT 영역에 쓰기 권한이 없기 때문에 메모리 권한을 설정해줘야 한다.

gdb 로 확인해보자

권한 설정 전

mprotect 를 호출 전에는 GOT쓰기 권한이 있는 것을 볼 수 있다.

 

mprotect

위의 명령어는

0x403000 부터 0x404000 (0x403000 + 0x1000) 까지 read(0x1) 권한만 설정하겠다는 뜻이다.

 

권한 설정 후

 

mprotect 가 권한을 설정해준 것을 볼 수 있다.

 

 

Reference


https://blackperl-security.gitlab.io/blog/2016/05/02/2016-05-02-linux-02/

https://howd4ys.github.io/2019-01-02/RELRO-is-fun1

https://howd4ys.github.io/2019-01-02/RELRO-is-fun2