Background: RELRO
# 서론
- Lazy Binding: 함수가 처음 호출 될 때 함수의 주소를 구하고, 이를 GOT에 적는 것
- Lazy Binding을 하는 바이너리는 실행 중에 GOT 테이블을 업데이트할 수 있어먀 하므로 GOT에 쓰기 권한이 부여됨 → 바이너리를 취약하게 만드는 원인
- ELF의 데이터 세그먼트에는 프로세스의 초기화 및 종료와 관련된 .init_arrary, .fini_array가 있음
- 해당 영역들은 프로세스의 시작과 종료에 실행할 함수들의 주소를 저장하고 있음
- 여기에 공격자가 임의로 값을 쓸 수 있다면, 프로세스의 실행 흐름이 조작될 수 있음
- 이러한 문제를 해결하고자 프로세스의 데이터 세그먼트를 보호하는 RELocation Read-Only(RELRO)가 개발됨
- RELRO는 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거함
- Partial RELRO: RELRO를 부분적으로 적용
- FULL RELRO: 가장 넓은 영역에 RELRO 적용
# RELRO
- Partial RELRO
- Figure 1. 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;
}
- RELRO 검사: 실습 환경의 gcc는 Full RELRO를 기본 적용하며, PIE를 해제하면 Partial RELRO 여부도 checksec로 검사할 수 있음
- Partial RELRO 권한
- Figure 3. Partial RELRO 바이너리의 메모리 맵.
- prelro 를 실행해보면 0x601000부터 0x602000 까지의 주소에는 쓰기 권한이 있는 것을 확인할 수 있음
- Figure 4. Partial RELRO 바이너리의 섹션 헤더: objdump -h ./prelro
- 위에서 확인한 영역에는 .got.plt, .data, .bss 가 할당되어 있음 → 해당 섹션들에는 쓰기가 가능
- .init_array 와 .fini_array 는 각각 0x600e10 과 0x600e18 에 할당되어 쓰기가 불가능함
💡 .got와 .got.plt
- Partial RELRO가 적용된 바이너리는 got와 관련된 섹션이 .got와 .got.plt로 두 개가 존재
- 전역 변수 중에서 실행되는 시점에 바인딩(now binding)되는 변수는 .got에 위치함
- 바이너리가 실행될 때는 이미 바인딩이 완료되었으므로 이 영역에 쓰기 권한을 부여하지 않음
- 반면 실행 중에 바인딩(lazy binding)되는 변수는 .got.plt에 위치함
- 이 영역은 실행 중에 값이 써져야 하므로 쓰기 권한이 부여됨
- Partial RELRO가 적용된 바이너리에서 대부분 함수들의 GOT 엔트리는 .got.plt에 저장됨
- Full RELRO
- Figure 5. Full RELRO 바이너리 checksec
- Figure 6. Full RELRO 바이너리의 메모리 맵
- Figure 7. frelro objdump: objdump -h frelro
- got에는 쓰기 권한이 제거되어 있으며 data와 bss에만 쓰기 권한이 있음
- Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩되기 때문에 GOT에는 쓰기 권한이 부여되지 않음
# RELRO 우회
- Parial RELRO
- .init_array 와 .fini_array 에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 수행하기 어려움
- .got.plt 영역에 대한 쓰기 권한이 존재하므로 GOT overwrite 공격을 활용할 수 있음
- Full RELRO
- .init_array, .fini_array 뿐만 아니라 .got 영역에도 쓰기 권한이 제거됨
- 공격자들은 덮어쓸 수 있는 다른 함수 포인터를 찾다가 라이브러리에 위치한 hook을 찾아냄
- 라이브러리 함수의 대표적인 hook은 malloc hook과 free hook이 있음
- 원래 hook 함수 포인터는 동적 메모리의 할당과 해제 과정에서 발생하는 버그를 디버깅하기 쉽게 하고자 만들어짐
- Figure 8. glibc malloc 소스코드
- malloc 함수의 코드를 살펴보면, 함수의 시작 부분에서 __malloc_hook이 존재하는지 검사하고, 존재하면 이를 호출함
- __malloc_hook은 libc.so에서 쓰기 가능한 영역에 위치함
- 공격자는 libc가 매핑된 주소를 알 때, 이 변수를 조작하고 malloc을 호출하여 실행 흐름을 조작할 수 있음
- 이와 같은 공격 기법을 Hook Overwrite라고 부름
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook); // read hook
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0)); // call hook
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes);
// ...
# 마치며
- RELocation Read-Only(RELRO): 불필요한 데이터 영역에 쓰기 권한을 제거함
- Partial RELRO: init array, fini array 등 여러 섹션에 쓰기 권한을 제거함. Lazy binding을 사용하므로 라이브러리 함수들의 GOT 엔트리는 쓰기가 가능함. GOT Overwrite 등의 공격으로 우회가 가능함
- Full RELRO: init array, fini array 뿐만 아니라 GOT에도 쓰기 권한을 제거함. Lazy binding을 사용하지 않으며 라이브러리 함수들의 주소는 바이너리가 로드되는 시점에 바인딩됨. libc의 malloc hook, free hook과 같은 함수 포인터를 조작하는 공격으로 우회할 수 있음
# Quiz
1번)
2번)
3번)
4번)
'Hacking Tech > System Hacking' 카테고리의 다른 글
[Dreamhack] Background: PIE (0) | 2022.09.15 |
---|---|
[Dreamhack] Background: Library - Static Link vs. Dynamic Link (0) | 2022.09.07 |
[Dreamhack] Mitigation:NX & ASLR (0) | 2022.09.05 |
[Dreamhack] Mitigation: Stack Canary (0) | 2022.09.04 |
[Dreamhack] Memory Corruption: Stack Buffer Overflow (0) | 2022.09.01 |