[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.plt
Partial RELRO가 적용된 바이너리는 got와 관련된 섹션이 .got
와 .got.plt
로 두 개가 존재합니다.
전역 변수 중에서 실행되는 시점에 바인딩(now binding)되는 변수는 .got
에 위치합니다.
바이너리가 실행 될 때는 이미 바인딩이 완료되었기 때문에, 이 영역에 쓰기 권한을 부여하지 않습니다.
반면 실행 중에 바인딩(Lazy binding)되는 변수는 .got.plt
에 위치합니다.
이 영역은 실행 중에 값이 쓰여져야하기 때문에, 쓰기 권한이 부여됩니다.
Partial RELRO가 적용된 바이너리에서 대부분 함수들의 GOT 엔트리는 .got.plt
에 저장됩니다.
-
[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.