RELocation Read-Only
메모리 보호 기법의 일종이다.
What is RELRO
바이너리의 symbol 및 got 등에 보호기법을 거는 것을 말한다.
RELRO 는 3가지가 있다.
No RELRO, Partial RELRO, Full RELRO 취약한 순서대로 이다.
Lazy Binding 를 하기 위해서는 프로그램이 실행되는 도중 GOT 에 라이브러리 함수의 주소를 덮어써야 한다.
즉, GOT에 쓰기 권한이 있어야 한다.
No RELRO | Partial RELRO | Full RELRO | |
컴파일 방식 | gcc -WI,-z,norlro | gcc -Wl,-z,relro | gcc -Wl,-z,relro, -z,now |
D.S에 BIND_NOW 포함 여부 | X | X | O |
D.S에 JMPREL 포함 여부 | O | O | X |
D.S에 PLTREL 포함 여부 | O | O | X |
D.S에 PLTRELSZ 포함 여부 | O | O | X |
RELRO에 포함된 섹션 | X | INIT_ARRAY,FINI_ARRAY | INIT_ARRAY,FINI_ARRAY,PLT GOT |
프로그램 헤더에 RELRO | X(없음) | O(있음) | O(있음) |
Lazy Binding | O | O | X |
Now Binding | X | X | O |
GOT의 쓰기 권한 | O | O | X |
// Relro test
// gcc -o frelro relro.c -no-pie -fno-PIE
// gcc -o No_RELRO Relro.c -Wl,-z,norelro -no-pie -fno-PIE
// gcc -o Partial_RELRO Relro.c -no-pie -fno-PIE
// gcc -o Full_RELRO Relro.c -Wl,-z,relro,-z,now -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;
}
No RELRO
No Relro는 ELF 기본헤더, 코드영역을 제외한 거의 모든 부분에 Read, Write 권한을 주는 것이다.
gcc -o test test.c -Wl,-z,norelro
Partial RELRO
No RELRO와 매우 비슷하지만 _DYNAMIC 섹션에 쓰기 권한이 없어진다.
- Program Header
Read Only 권한의 RELRO 영역이 생성된다.
INIT_ARRAY, FINI_ARRAY 섹션이 포함된다.
GOT를 덮어 쓸 수 있다.
Partial RELRO 가 적용된 바이너리는 GOT와 관련된 섹션이 .got 와 .got.plt로 두 개가 존재한다.
전역 변수 중에서 실행되는 시점에 바인딩 (Now Binding)되는 변수는 .got 에 위치한다.
바이너리가 실행될 때는 이미 바인딩이 완료되어있으므로 이 영역에 쓰기 권한을 부여하지 않는다.
반면 실행 중에 바인딩(lazy binding)되는 변수는 .got.plt에 위치한다.
이 영역은 실행 중에 값이 써져야 하므로 쓰기 권한이 부여된다.
Partial Relro가 적용된 바이너리에서 대부분 함수들의 GOT엔트리는 .got.plt에 저장된다.
gcc -o test test.c -Wl,-z,relro #Full RELRO로 잡힘 머지 , -no-pie 를 같이 켜줘야 한다.
gcc -o Partial_RELRO Relro.c -no-pie -fno-PIE
Full RELRO
data, bss 영역을 제외한 모든 부분에서 write권한이 없어진다.
- Program Header
Read Only 권한의 Relro 영역이 생성된다.
INIT_ARRAY, FINI_ARRAY, PLTGOT 섹션이 포함된다. - Dynamic Section
PLTRELSZ, PLTREL, JMPREL섹션 제거
BIND_NOW, FLAGS_1 섹션 추가
Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩된다. (Now Binding)
따라서 GOT에는 쓰기 권한이 부여되지 않는다.
GOT를 덮어 쓸 수 없다.
gcc -o test test.c -Wl,-z,relro,-z,now
Detecting RERLO
Program header와 dynamic section의 정보를 확인할 수 있다.
readelf -ld binary
ProgramHeader : readelf -l
Dynamic section: readelf -d
process
바이너리의 확인 방식과 비슷하다.
/proc/<PID>/exe 파일에서 바이너리에서 확인했던 방식대로 찾으면 된다. 추가적으로 Program Headers정보가 있는지 확인하면 된다
Function Call
Partial RELRO | Full RELRO | |
main 함수에서 | “.plt” 영역의 메모리 주소 호출 | “.plt.got” 영역의 메모리 주소 호출 |
.plt 에서 | jmp QWORD PTR [-------][-------]는 “.got.plt” 영역으로, 이 영역에 저장된 주소로 점프 | jmp QWORD PTR [-------][-------]는 “.got” 영역으로, 이 영역에 저장된 주소로 점프 |
.got.plt / .got | .got.plt동적 라이브러리의 주소가 아닌 “.plt” 영역 저장(호출되기 전 함수들의 .got.plt에는 stub 코드가 저장되어 있다.) | .got아무런 값도 저장되어 있지 않음(아직 함수 호출 전이기 때문이다.) |
함수 호출 시작 | “.got.plt” 영역에 동적라이브러리의 함수의 시작 주소 값이 저장 | “.got” 영역에 동적라이브러리의 함수 시작 주소 값이 저장 |
Partial RELRO 가 적용된 바이너리는 “.got.plt” 영역이 writable하기 때문에 “.got.plt” 영역에 저장된 값을 변경할 수 있다. | Full RELRO가 적용된 바이너리는 “.got” 영역이 read-only로 설정되어 있기 때문에 ".got"영역에 저장된 값을 변경할 수 없다. |
이 글은 옵시디언을 이용해서 작성되었습니다.
'TOOR' 카테고리의 다른 글
[TOOR] 16.1 OOB (Out of Bound) (0) | 2023.10.02 |
---|---|
[TOOR] 15.1 Format String Bug (0) | 2023.10.02 |
[TOOR] 13.1. PIE (0) | 2023.09.29 |
[TOOR] 12.3. SROP (0) | 2023.09.24 |
[TOOR] 12.2 rop_2 write_up (0) | 2023.09.24 |