RELRO (RELocation Read-Only)
ELF 바이너리(프로세스)의 다이나믹 섹션에 읽기 권한만을 부여해 데이터 섹션의 보안을 강화하는 보호기술
* Lazy Binding
바이너리가 실행되는 도중 함수가 처음 호출될 때 주소를 찾는 방식
ELF 바이너리에서 동적 라이브러리의 함수를 호출할 때 호출된 함수를 찾기 위해 PLT와 GOT를 사용하는데 이 때 GOT가 사용되는 방식과 같다. PLT, GOT 정리▷▶doongdangdoongdangdong.tistory.com/117?category=869864
Lazy Binding을 하기 위해서는 프로그램이 실행되는 도중 GOT에 라이브러리 함수의 주소를 덮어써야 한다. 즉, GOT에 쓰기 권한이 있어야한다.
==> RELRO가 설정되어 있는 바이너리에서는 GOT와 같은 다이나믹 섹션이 읽기 권한만 가지게 된다.
Partial RELRO / Full RELRO
No RELRO | Partial RELRO | Full RELRO | |
컴파일 방식 | gcc -Wl,-z,norelro (++) | gcc -Wl,-z,relro (++) | gcc -WI,-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 |
(++): -o 바이너리명 소스코드.c / D.S: Dynamic Section / 컴파일할 때 위에 적힌 옵션들끼리 띄어쓰기하면 안된다.
#include <stdio.h>
#include <string.h>
void main(){
char address[16];
size_t *pointer;
int count = 1;
while(count != 100)
{
printf("----- %d -----\n",count);
memset(address,0,16);
printf("Input Pointer address : ");
fgets(address,16,stdin);
pointer = strtol(address,0,16);
printf("Pointer address : %p\n",pointer);
printf("Input Pointer text : ");
fgets(pointer,16,stdin);
printf("Pointer text : %s\n",pointer);
count++;
}
scanf("%s",address);
}
△ RELRO.c
program header & dynamic section 변화
Partial RELRO | Full RELRO |
'Program Header' - Read Only 권한의 RELRO 영역이 생성된다. - INIT_ARRAY, FINI_ARRAY 섹션이 포함된다. >> GOT를 덮어 쓸 수 있다. |
"Program Header" - Read Only 권한의 RELRO 영역이 생성된다. - INIT_ARRAY, FINI_ARRAY, PLTGOT 섹션이 포함된다. "Dynamic Section" - PLTRELSZ, PLTREL, JMPREL 섹션 제거 - BIND_NOW, FLAGS_1 섹션 추가 >> GOT를 덮어 쓸 수 없다. |
$ readelf - ld 바이너리 >> program header와 dynamic section의 정보를 확인할 수 있다.
overwrite test
No RELRO >> .got 영역을 덮어 쓸 수 있다.
__isoc99_scanf 함수의 주소값은 .got.plt 영역에 저장되어 있다. 이 때 .got.plt 영역(0x600c20 - 0x600c70)은 "W"(쓰기) 권한이 있는 영역에 포함되기 때문에 GOT 값을 덮어쓸 수 있다.
Partial RELRO >> .got.plt 영역을 덮어 쓸 수 있다.
.got.plt 영역은 0x601000으로, "W"권한이 설정되어 있는 영역에 포함된다. 반면, .init_array, .fini_arrray, .jcr, .dynamic, .got 헤더가 포함된 0x600000 ~ 0x601000 영역은 r--p로, 쓰기권한은 설정되어 있지않다.
Full RELRO >> GOT 영역을 덮어 쓸 수 없다.
Full Relro에서는 디버거에서 __isoc99_scanf의 심볼 정보를 찾을 수 없다.
0x4007fd (main+263): 0x4005f8 영역을 호출한다.
0x4005f8: 0x600ff8(rip + 0x2009f6)영역에 저장된 주소로 이동한다.
rip+0x2009f6: 0x00007ffff7a784e0이 저장되어 있다.
0x00007ffff7a784e0: __isoc99_scanf 함수의 시작주소이다.
No Relro, Partial RELRO와 다르게 ".rela.plt", ".got.plt" 헤더가 존재하지 않는다.
function calls
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"영역에 저장된 값을 변경할 수 없다. |
Detecting RELRO
➰ Bianry
"readelf" 명령어를 이용해 해당 파일의 프로그램 헤더와 dynamic section 정보를 가져와 RELRO 설정여부를 확인할 수 있다.
➰ Process
바이너리의 확인 방식과 비슷하다.
"/proc/<PID>/exe" 파일에서 바이너리에서 확인했던 방식대로 찾으면 되고, 추가적으로 'Program Headers' 정보가 있는지 확인하면 된다.
'System > System Hacking' 카테고리의 다른 글
FSB (Format String Bug) (0) | 2021.02.10 |
---|---|
[Protection Tech.] Canaries(카나리), SSP (0) | 2021.01.27 |
[Protection Tech.] ASLR (0) | 2021.01.15 |
PLT, GOT (0) | 2021.01.14 |
[Protection Tech.] NX bit (2) | 2021.01.03 |