[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.
- 서론
- RELRO
- RELRO 우회
- Q&A
- 마치며
ELF는 GOT를 활용하여 반복되는 라이브러리 함수의 호출 비용을 줄입니다.
GOT에 값을 채우는 방법 중,
우리는 함수가 처음 호출될 때 함수의 주소를 구하고 이를 GOT에 적는 'Lazy BInding'을 배웠습니다.
Lazy Binding은 바이너리 실행 중에 GOT 테이블을 업데이트할 수 있어야 하므로,
GOT에 쓰기 권한이 부여됩니다.
하지만 이 점이, 바이너리를 취약하게 만드는 원인이 됩니다.
또한 ELF의 데이터 세그먼트에는 프로세스의 초기화 및 종료와 관련된 .inti_array, .fini_array가 있습니다.
해당 영역들은 프로세스의 시작과 종료에 실행할 함수들의 주소를 저장하고 있는데,
여기에 공격자가 접근할 수 있다면, 프로세스의 실행 흐름을 조작할 수 있습니다.
따라서, 리눅스 개발자들은 이러한 문제를 해결하고자 프로세스의 데이터 세그먼트를 보호하는 'RELocation Read-Only(RELRO)'를 개발했습니다.
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;
}
gcc는 Full RELRO를 기본적으로 적용하며,
PIE를 해제하면 Partial RELRO를 적용합니다.
$ gcc -o prelro relro.c -no-pie fno-PIE
$ checksec prelro
[*] '/home/dreamhack/prelro'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
해당 바이너리를 실행해보면, 0x601000부터 0x602000까지의 주소에는 쓰기 권한이 있는 것을 확인할 수 있습니다.
$ ./prelro
...
/home/dreamhack/prelro
00601000-00602000 rw-p 00001000 08:01 922450
...
섹션 헤더를 참조하면, 해당 영역에는 .got.plt, .data, .bss가 할당되어 있습니다.
따라서 섹션에는 쓰기가 가능합니다.
반면 .init_array, .fini_array는 각각 0x600e10, 0x600e18에 할당되어, 쓰기가 불가능 합니다.
$ objdump -h ./prelro
./prelro: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
17 .init_array 00000008 0000000000600e10 0000000000600e10 00000e10 2**3
CONTENTS, ALLOC, LOAD, DATA
18 .fini_array 00000008 0000000000600e18 0000000000600e18 00000e18 2**3
CONTENTS, ALLOC, LOAD, -
...
DATA
21 .got.plt 00000020 0000000000601000 0000000000601000 00001000 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .data 00000010 0000000000601020 0000000000601020 00001020 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .bss 00000008 0000000000601030 0000000000601030 00001030 2**0
ALLOC
위의 코드에서 옵션을 제거하고 컴파일 하면 Full RELRO가 적용된 바이너리가 생성됩니다.
$ gcc -o frelro relro.c
$ checksec frelro
[*] '/home/hhro/dreamhack/frelro'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
해당 바이너리를 실행한 결과와 섹션 헤더의 정보를 종합하면,
got에는 쓰기 권한이 제거되어 있으며, data와 bss에만 쓰기 권한이 있습니다.
Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩됩니다.
따라서 OGT에는 쓰기 권한이 부여되지 않습니다.
$ ./frelro
55bf3df46000-55bf3df47000 r-xp 00000000 08:10 782483 /home/hhro/dreamhack/frelro
55bf3e146000-55bf3e147000 r--p 00000000 08:10 782483 /home/hhro/dreamhack/frelro
55bf3e147000-55bf3e148000 rw-p 00001000 08:10 782483 /home/hhro/dreamhack/frelro
...
# objdump -h frelro
frelro: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
18 .init_array 00000008 0000000000200da8 0000000000200da8 00000da8 2**3
CONTENTS, ALLOC, LOAD, DATA
19 .fini_array 00000008 0000000000200db0 0000000000200db0 00000db0 2**3
CONTENTS, ALLOC, LOAD, ...DATA
21 .got 00000058 0000000000200fa8 0000000000200fa8 00000fa8 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .data 00000010 0000000000201000 0000000000201000 00001000 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .bss 00000008 0000000000201010 0000000000201010 00001010 2**0
ALLOC
...
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'라고 합니다.
.got와 .got.pltPartial RELRO가 적용된 바이너리는 got와 관련된 섹션이 .got와 .got.plt로 두 개가 존재합니다.
전역 변수 중에서 실행되는 시점에 바인딩(now binding)되는 변수는 .got에 위치합니다.
바이너리가 실행 될 때는 이미 바인딩이 완료되었기 때문에, 이 영역에 쓰기 권한을 부여하지 않습니다.
반면 실행 중에 바인딩(Lazy binding)되는 변수는 .got.plt에 위치합니다.
이 영역은 실행 중에 값이 쓰여져야하기 때문에, 쓰기 권한이 부여됩니다.
Partial RELRO가 적용된 바이너리에서 대부분 함수들의 GOT 엔트리는 .got.plt에 저장됩니다.
-
[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.