Partial 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;
puchar(ch);
}
return 0;
}
objdump -h ./prelro
./prelro: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000400274 0000000000400274 00000274 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash 0000001c 0000000000400298 0000000000400298 00000298 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym 00000090 00000000004002b8 00000000004002b8 000002b8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynstr 0000004c 0000000000400348 0000000000400348 00000348 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version 0000000c 0000000000400394 0000000000400394 00000394 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version_r 00000020 00000000004003a0 00000000004003a0 000003a0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rela.dyn 00000030 00000000004003c0 00000000004003c0 000003c0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rela.plt 00000048 00000000004003f0 00000000004003f0 000003f0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .init 00000017 0000000000400438 0000000000400438 00000438 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .plt 00000040 0000000000400450 0000000000400450 00000450 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .text 000001a2 0000000000400490 0000000000400490 00000490 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .fini 00000009 0000000000400634 0000000000400634 00000634 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .rodata 00000016 0000000000400640 0000000000400640 00000640 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
15 .eh_frame_hdr 0000003c 0000000000400658 0000000000400658 00000658 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
16 .eh_frame 00000100 0000000000400698 0000000000400698 00000698 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
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
19 .dynamic 000001d0 0000000000600e20 0000000000600e20 00000e20 2**3
CONTENTS, ALLOC, LOAD, DATA
20 .got 00000010 0000000000600ff0 0000000000600ff0 00000ff0 2**3
CONTENTS, ALLOC, LOAD, DATA
21 .got.plt 00000030 0000000000601000 0000000000601000 00001000 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .data 00000010 0000000000601030 0000000000601030 00001030 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .bss 00000008 0000000000601040 0000000000601040 00001040 2**0
ALLOC
24 .comment 00000029 0000000000000000 0000000000000000 00001040 2**0
CONTENTS, READONLY
prelro를 실행해보면 0x601000~0x602000까지의 주소에는 쓰기 권한이 존재한다. 위의 .got.plt, .data, .bss가 할당되어 있다. 이 섹션들에는 쓰기가 가능하다.
반면에는 .init_array, .fini_array는 각각 0x600e10, 0x600e18에 할당되어 있어 쓰기가 불가능하다.
Full RELRO가 적용된 바이너리의 메모리 맵을 확인해보자.
objdump -h ./frelro
./frelro: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000000238 0000000000000238 00000238 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 0000000000000254 0000000000000254 00000254 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000000274 0000000000000274 00000274 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash 0000001c 0000000000000298 0000000000000298 00000298 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym 000000d8 00000000000002b8 00000000000002b8 000002b8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynstr 00000091 0000000000000390 0000000000000390 00000390 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version 00000012 0000000000000422 0000000000000422 00000422 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version_r 00000020 0000000000000438 0000000000000438 00000438 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rela.dyn 000000c0 0000000000000458 0000000000000458 00000458 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rela.plt 00000048 0000000000000518 0000000000000518 00000518 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .init 00000017 0000000000000560 0000000000000560 00000560 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .plt 00000040 0000000000000580 0000000000000580 00000580 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .plt.got 00000008 00000000000005c0 00000000000005c0 000005c0 2**3
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .text 000001d2 00000000000005d0 00000000000005d0 000005d0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .fini 00000009 00000000000007a4 00000000000007a4 000007a4 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
15 .rodata 00000016 00000000000007b0 00000000000007b0 000007b0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
16 .eh_frame_hdr 0000003c 00000000000007c8 00000000000007c8 000007c8 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
17 .eh_frame 00000108 0000000000000808 0000000000000808 00000808 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
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
20 .dynamic 000001f0 0000000000200db8 0000000000200db8 00000db8 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
24 .comment 00000029 0000000000000000 0000000000000000 00001010 2**0
CONTENTS, READONLY
frelro의 메모리 맵을 보면 got에는 쓰기 권한이 제거되어 있고 data와 bss에만 쓰기 권한이 있다.Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에서 모두 바인딩된다.
RELRO 기법 우회
Partial RELRO의 경우, .init_array와 .fini_array에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 수행하기 어려워진다. 하지만, .got.plt 영역에 대학 쓰기 권한이 존재하므로 GOT overwrite 공격을 활용할수 있다.
Full RELRO의 경우. init_array, .fini_array 뿐만 아니라 .got 영역에도 쓰기 권한이 제거되어있다. 라이브러리에 위치한 hook 함수 포인터를 이용하는 것이다.
대표적으로 malloc hook과 free hook이다. 이 함수 포인터는 동적 메모리의 할당과 해제 과정에서 발생하는 버그를 디버깅하기 쉽게 하려고 만들었다.
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);
// ...
glibc malloc 소스코드를 보면 __malloc_hook이 존재하는지 검사하고, 존재하면 __malloc_hook을 호출한다. 이는 libc.so에서 쓰기 가능한 영역에 위치한다. libc가 매핑된 주소를 알 때, 이 변수를 조작하고 malloc을 호출하여 실행 흐름을 조작할 수 있다. 이를 Hook Overwrite라고 부른다.
정리
- 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과 같은 함수 포인터를 조작하는 공격으로 우회할 수 있다.
'Pwnable > Techniques' 카테고리의 다른 글
Memory Corruption: Format String Bug (0) | 2022.01.05 |
---|---|
Out of Bounds (0) | 2021.12.30 |
RELRO (0) | 2021.12.29 |
PIE (0) | 2021.12.29 |
Exploit Tech: Return Oriented Programming (0) | 2021.12.27 |
댓글