[Pwnable] 10. Background: RELRO

wonder_land·2022년 10월 31일
0

[Pwnable]

목록 보기
10/21
post-thumbnail

[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.


  1. 서론
  2. RELRO
  3. RELRO 우회
  4. Q&A
  5. 마치며

1. 서론

ELF는 GOT를 활용하여 반복되는 라이브러리 함수의 호출 비용을 줄입니다.

GOT에 값을 채우는 방법 중,
우리는 함수가 처음 호출될 때 함수의 주소를 구하고 이를 GOT에 적는 'Lazy BInding'을 배웠습니다.

Lazy Binding은 바이너리 실행 중에 GOT 테이블을 업데이트할 수 있어야 하므로,
GOT에 쓰기 권한이 부여됩니다.

하지만 이 점이, 바이너리를 취약하게 만드는 원인이 됩니다.

또한 ELF의 데이터 세그먼트에는 프로세스의 초기화 및 종료와 관련된 .inti_array, .fini_array가 있습니다.

해당 영역들은 프로세스의 시작과 종료에 실행할 함수들의 주소를 저장하고 있는데,
여기에 공격자가 접근할 수 있다면, 프로세스의 실행 흐름을 조작할 수 있습니다.

따라서, 리눅스 개발자들은 이러한 문제를 해결하고자 프로세스의 데이터 세그먼트를 보호하는 'RELocation Read-Only(RELRO)'를 개발했습니다.

RELRO는 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거합니다.

RELRO는 적용하는 범위에 따라 두 가지로 구분됩니다.

  1. Partial RELRO : RELRO를 부분적으로 적용
  2. Full RELRO : RELRO를 가장 넓은 영역에 적용

2. RELRO

1) 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;
    putchar(ch);
  }
  return 0;
}

(1) RELRO 검사

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)

(2) Partial RELRO 권한

해당 바이너리를 실행해보면, 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

2) Full RELRO

(1) RELRO 검사

위의 코드에서 옵션을 제거하고 컴파일 하면 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

(2) Full RELRO 권한

해당 바이너리를 실행한 결과와 섹션 헤더의 정보를 종합하면,
got에는 쓰기 권한이 제거되어 있으며, databss에만 쓰기 권한이 있습니다.

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
...

3. RELRO 우회

1) Partial RELRO 우회

Partial RELRO의 경우,
.init_array, .fini_array에 대한 쓰기 권한이 제거되어,
두 영역을 덮어쓰는 공격을 수행하기 어려워집니다.

하지만, .got.plt 영역에 대한 쓰기 권한이 존재하므로, 'GOT Overwrite' 공격을 활용할 수 있습니다.

2) Full RELRO 우회

Full RELRO의 경우,
.init_array, .fini_array뿐만 아니라 .got 영역에도 쓰기 권한이 제거되었습니다.

그래서 공격자들은 덮어쓸 수 있는 다른 함수 포인터를 찾다가,
라이브러리에 위치한 'hook'을 찾아냈습니다.

라이브러리 함수의 대표적인 hook이 malloc hookfree hook입니다.

원래 이 함수 포인터는 동적 메모리 하당과 해제 과정에서 발생하는 버그를 디버깅하기 쉽게 하려고 만들어졌습니다.

malloc 함수의 코드를 살펴보면,
함수의 시작 부분에서 __malloc_hook이 존재하는지 검사하고, 존재하면 이를 호출합니다.
__malloc_hooklibc.so에서 쓰기 가능한 영역에 위치합니다.

따라서 공격자는 libc가 매핑된 주소를 알 때, 이 변수를 조작하고 malloc을 호출하여 실행 흐름을 조작할 수 있습니다.

이러한 공격기법을 통틀어 'Hook Overwrite'라고 합니다.


4. Q&A

1) .got.got.plt

Partial RELRO가 적용된 바이너리는 got와 관련된 섹션이 .got.got.plt로 두 개가 존재합니다.

전역 변수 중에서 실행되는 시점에 바인딩(now binding)되는 변수는 .got에 위치합니다.

바이너리가 실행 될 때는 이미 바인딩이 완료되었기 때문에, 이 영역에 쓰기 권한을 부여하지 않습니다.

반면 실행 중에 바인딩(Lazy binding)되는 변수는 .got.plt에 위치합니다.

이 영역은 실행 중에 값이 쓰여져야하기 때문에, 쓰기 권한이 부여됩니다.

Partial RELRO가 적용된 바이너리에서 대부분 함수들의 GOT 엔트리는 .got.plt에 저장됩니다.


5. 마치며

-

[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.

profile
아무것도 모르는 컴공 학생의 Wonder_Land

0개의 댓글