Background: Library에서 ELF는 GOT를 활용하여 반복되는 라이브러리 함수의 호출비용을 줄인다고 했다. GOT에 값을 채우는 방식은 다양하다. 지난 강의에서는 그 중에서 함수가 처음 호출될 때의 함수의 주소를 구하고, 이를 GOT에 적은 Lazy Binding을 소개했다.
Lazy binding을 하는 바이너리는 실행 중에 GOT 테이블을 업데이트할 수 있어야 하므로 GOT에 쓰기 권한이 부여된다. 그런데 이는 앞서 소개한 공격 기법들에서 알 수 있듯, 바이너리를 취약하게 만드는 원인이 된다. 또한 ELF의 데이터 세그먼트에는 프로세스의 초기화 및 종료와 관련된 .init_array, .fini_array가 있다. 이 영역들은 프로세스의 시작과 종료에 실행할 함수들의 주소를 저장하고 있는데, 여기에 공격자가 임의로 값을 쓸 수 있다면, 프로세스의 실행 흐름이 조작될 수 있다.
리눅스 개발자들은 이러한 문제를 해결하고자 프로세스의 데이터 세그먼트를 보호하는 RELocation Read-Only(RELRO)을 개발했다. RELFO는 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거한다.
RELRO는 RELRO를 적용하는 범위에 따라 2가지로 구분된다. 하나는 RELRO를 부분적으로 적용하는 Partial RELRO이고, 나머지는 가장 넓은 영역에 RELRO를 적용하는 Full RELFO이다. 이번 강의에서는 각각의 특징과 우회방법을 배울 것이다.
** RELRO
Partial RELRO
이번 강의에서는 Partial RELRO와 Full RELFO의 차이를 확인하기 위해 하단의 코드를 예제로 사용할 것이다. 예제는 자신의 메모리 맵을 출력하는 바이너리의 소스코드이다.
// 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를 적용한다. 바이너리의 RELRO 여부도 checksec으로 검사할 수 있다.
$ gcc -o prelro -no-pie relro.c
$ checksec prelro
[*] '/home/dreamhack/prelro'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

Partial RELRO 권한
prelro를 실행해보면 0x404000부터 0x405000까지의 주소에는 쓰기 권한이 있음을 확인할 수 있다.
$ ./prelro
00400000-00401000 r--p 00000000 08:02 2886150 /home/dreamhack/prelro
00401000-00402000 r-xp 00001000 08:02 2886150 /home/dreamhack/prelro
00402000-00403000 r--p 00002000 08:02 2886150 /home/dreamhack/prelro
00403000-00404000 r--p 00002000 08:02 2886150 /home/dreamhack/prelro
00404000-00405000 rw-p 00003000 08:02 2886150 /home/dreamhack/prelro
0130d000-0132e000 rw-p 00000000 00:00 0 [heap]
7f108632c000-7f108632f000 rw-p 00000000 00:00 0
7f108632f000-7f1086357000 r--p 00000000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f1086357000-7f10864ec000 r-xp 00028000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f10864ec000-7f1086544000 r--p 001bd000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f1086544000-7f1086548000 r--p 00214000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f1086548000-7f108654a000 rw-p 00218000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f108654a000-7f1086557000 rw-p 00000000 00:00 0
7f1086568000-7f108656a000 rw-p 00000000 00:00 0
7f108656a000-7f108656c000 r--p 00000000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f108656c000-7f1086596000 r-xp 00002000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f1086596000-7f10865a1000 r--p 0002c000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f10865a2000-7f10865a4000 r--p 00037000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f10865a4000-7f10865a6000 rw-p 00039000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffe55580000-7ffe555a1000 rw-p 00000000 00:00 0 [stack]
7ffe555de000-7ffe555e2000 r--p 00000000 00:00 0 [vvar]
7ffe555e2000-7ffe555e4000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]

하단의 섹션 헤더를 참조해보면 해당 영역에는 .got.plt, .data, .bss가 할당되어 있다. 따라서 이 섹션들에는 쓰기가 가능하다. 반면, .init_array와 .fini_array는 각각 0x403e10 과 0x403e18 에 할당되어 있는데 모두 쓰기 권한이 없는 00403000-00404000 사이에 존재하므로 쓰기가 불가능하다.
(데이터 세그먼트는 컴파일 시점에 값이 정해진 전역변수 및 전역 상수들이 위치한다,
BSS 세그먼트 (Block Started By Symbol Segment)는 컴파일 시점에 값이 정해지지 않은 전역변수가 위치하는 메모리 영역이다. 여기에는 개발자가 선언만 하고 초기화하지 않은 전역변수 등이 포함된다.)

💡 .got와 .got.plt
Partial RELRO가 적용된 바이너리는 got와 관련된 섹션이 .got와 .got.plt로 두 개가 존재합니다. 전역 변수 중에서 실행되는 시점에 바인딩(now binding)되는 변수는 .got에 위치합니다. 바이너리가 실행될 때는 이미 바인딩이 완료되어있으므로 이 영역에 쓰기 권한을 부여하지 않습니다.
반면 실행 중에 바인딩(lazy binding)되는 변수는 .got.plt에 위치합니다. 이 영역은 실행 중에 값이 써져야 하므로 쓰기 권한이 부여됩니다. Partial RELRO가 적용된 바이너리에서 대부분 함수들의 GOT 엔트리는 .got.plt에 저장됩니다.
** Full RELRO
앞서 소개했던 relro 예시 코드를 별도의 컴파일 옵션 없이 컴파일하면 Full RELRO가 적용된 바이너리가 생성된다.
$ gcc -o frelro relro.c
$ checksec frelro
[*] '/home/dreamhack/frelro'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

frelro를 실행하여 메모리 맵을 확인하고, 이를 섹션 헤더 정보와 종합헤보면 got에는 쓰기 권한이 제거되어있으며, data와 bss에만 쓰기 권한이 있다.
$ ./frelro
563782c64000-563782c65000 r--p 00000000 08:02 2886178 /home/dreamhack/frelro
563782c65000-563782c66000 r-xp 00001000 08:02 2886178 /home/dreamhack/frelro
563782c66000-563782c67000 r--p 00002000 08:02 2886178 /home/dreamhack/frelro
563782c67000-563782c68000 r--p 00002000 08:02 2886178 /home/dreamhack/frelro
563782c68000-563782c69000 rw-p 00003000 08:02 2886178 /home/dreamhack/frelro
563784631000-563784652000 rw-p 00000000 00:00 0 [heap]
7f966f91f000-7f966f922000 rw-p 00000000 00:00 0
7f966f922000-7f966f94a000 r--p 00000000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f966f94a000-7f966fadf000 r-xp 00028000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f966fadf000-7f966fb37000 r--p 001bd000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f966fb37000-7f966fb3b000 r--p 00214000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f966fb3b000-7f966fb3d000 rw-p 00218000 08:02 132492 /usr/lib/x86_64-linux-gnu/libc.so.6
7f966fb3d000-7f966fb4a000 rw-p 00000000 00:00 0
7f966fb5b000-7f966fb5d000 rw-p 00000000 00:00 0
7f966fb5d000-7f966fb5f000 r--p 00000000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f966fb5f000-7f966fb89000 r-xp 00002000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f966fb89000-7f966fb94000 r--p 0002c000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f966fb95000-7f966fb97000 r--p 00037000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f966fb97000-7f966fb99000 rw-p 00039000 08:02 132486 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffc1bace000-7ffc1baef000 rw-p 00000000 00:00 0 [stack]
7ffc1bb22000-7ffc1bb26000 r--p 00000000 00:00 0 [vvar]
7ffc1bb26000-7ffc1bb28000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]

또한 .data 섹션의 오프셋은 0x4000이다. 이를 /home/dreamhack/frelro가 매핑된 0x563782c64000에 더하면, 0x563782c68000이 되며, 이는 쓰기 권한이 있는 영역에 속한다. .bss 섹션 역시 동일한 방법으로 매핑된 주소를 계산해보면 0x563782c68010가 나오며 마찬가지로 쓰기 권한이 존재하는 영역에 속한다.
(내 실습에서는 /home/dreamhack/frelro가 매핑된 0x61090c6fa000에 더하면 0x61090c73a000이 되며, 이는 쓰기 권한이 있는 영역에 속한다-> heap 바로 위 bss섹션 offset은 0x4010이므로 0x61090c6fa000에 더하면 0x61090c73a010가 나오며, 마찬가지로 쓰기 권한(w)이 존재하는 영역에 속한다.
$ objdump -h ./frelro
./frelro: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
...
20 .init_array 00000008 0000000000003da8 0000000000003da8 00002da8 2**3
CONTENTS, ALLOC, LOAD, DATA
21 .fini_array 00000008 0000000000003db0 0000000000003db0 00002db0 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .dynamic 000001f0 0000000000003db8 0000000000003db8 00002db8 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .got 00000058 0000000000003fa8 0000000000003fa8 00002fa8 2**3
CONTENTS, ALLOC, LOAD, DATA
24 .data 00000010 0000000000004000 0000000000004000 00003000 2**3
CONTENTS, ALLOC, LOAD, DATA
25 .bss 00000008 0000000000004010 0000000000004010 00003010 2**0
ALLOC
...

Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩 된다. 따라서 GOT에는 쓰기 권한이 부여되지 않는다.
** RELRO 우회
RELRO 기법 우회
Partial RELRO, Full RELRO의 적용 방식과 효과를 확인해보았다. Partial RELRO의 경우, .init_array와 .fini_array에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 수행하기 어려워지지만 .got .plt 영역에 대한 쓰기 권한이 존재하므로 GOT Overwrite 공격을 활용할 수 있다.
Full RELRO의 경우, .init_aray, .fini_array 뿐만 아니라, .got 영역에도 쓰기 권한이 제거되었다. 그래서 공격자들은 덮어쓸 수 있는 다른 함수 포인터를 찾다가 라이브러리에 위치한 hook을 찾아냈다. 라이브러리 함수의 대표적인 hook이 malloc hook과 free hook이다. 원래 이 함수 포인터는 동적 메모리의 할당과 해제 과정에서 발생하는 버그를 디버깅하기 쉽게 하려고 만들어졌다.
malloc 함수의 코드를 살펴보면, 함수의 시작 부분에서 __malloc_hook이 존재하는지 검사하고, 존재하면 이를 호출한다. __malloc_hook은 libc.so에서 쓰기 가능한 영역에 위치한다. 따라서 공격자는 libc가 매핑된 주소를 알 때, 이 변수를 조작하고 malloc을 호출하여 실행 흐름을 조작할 수 있다.이와 같은 공격 기법을 통틀어 Hook Overwrite라고 부른다.
glibc malloc 소스 코드
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);
// ...
마치며
마치며
이번 강의에서는 프로세스의 데이터 영역을 보호하기 위해 도입된 RELRO 보호 기법에 대하여 배워보았습니다. 프로세스의 데이터 영역에는 got, init array, fini array를 비롯하여 공격에 사용될 수 있는 여러 변수가 있습니다. Partial RELRO 바이너리는 got에 쓰기 권한이 남아있어서 GOT Overwrite로 이를 우회할 수 있지만, Full RELRO가 적용되면 데이터 영역의 모든 불필요한 쓰기 권한이 삭제되면서 Hook Overwrite 등의 새로운 공격 기법을 사용해야 합니다.
다음 강의에서는 Hook Overwrite 기법을 실습을 통해 자세히 배워보도록 하겠습니다. 🚩
키워드
|
Q1. Partial RELRO에서는 .fini_array를 조작하여 실행 흐름을 조작할 수 있다.
X
Partial RELRO의 경우, .init_array와 .fini_array에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 수행하기 어렵다.
Q2. Full RELRO에서는 .got 영역을 조작하여 실행 흐름을 조작할 수 있다.
X
Full RELRO의 경우, .init_aray, .fini_array 뿐만 아니라, .got 영역에도 쓰기 권한이 제거되었다.
Q3. No RELRO는 어떠한 RELRO 보호기법도 적용되지 않는 상태를 의미한다.
O
Q4. RELRO는 RELocation Read-Only의 줄임말이다
O
'공부중 > 시스템 해킹' 카테고리의 다른 글
| [Dreamhack] Memory Corruption: Out of Bounds (0) | 2024.09.28 |
|---|---|
| [Dreamhack] Exploit Tech: Hook Overwrite (0) | 2024.09.27 |
| [Dreamhack] Background: PIE (0) | 2024.09.24 |
| [Dreamhack] Exploit Tech: ROP x64 (0) | 2024.09.22 |
| [Dreamhack] Exploit Tech: Return Oriented Programming (ROP) (0) | 2024.09.22 |
댓글