[포너블] PIE & RELRO

Chris Kim·2024년 10월 21일

시스템해킹

목록 보기
16/33

출처 드림핵

1. PIC와 PIE

1.1 PIC

PIC는 Position-Independent Code의 약자로 재배치가 가능한 성질을 만족하는 코드를 말한다. 리눅스의 공유오브젝트(Share Object)가 대표적인 예시다. PIC 적용이 되지 않은 바이너리 파일은 printf 함수에 문자열을 전달할 때 절대 주소를 참조하지만, PIC 적용이 된 파일은 rip로 부터의 상대주소를 참조한다.

1.2 PIE

Position-Independent Executable, 줄여서 PIE는 무작위 주소에 매핑되도 실행 가능한 실행 파일을 뜻한다. ASLR이 도입되면서, 개발자는 원래 재배치가 가능했던 공유 오브젝트를 실행 파일로 사용하기로 했다.(본래 실행 파일은 재배치를 고려하지 않았기 때문)
/bin/ls의 파일 헤더를 보면 Type이 공유오브젝트를 나타내는 DYN(ET_DYN)이다.

1.3 PIE on ASLR

ASLR 적용 시스템에서는 실행 파일도 무작위 주소에 적재 될 수 있다. 그러나 ASLR 미적용 시스템에서는 PIE 바이너리 파일이라도 무작위 주소에 적재되지 않는다.

2. PIE 우회

2.1 우회 방법

2.1.1 코드 베이스 구하기

먼저 바이너리가 적재된 주소를 알아내야 한다. 이는 ROP에서 하는 것처럼 코드의 임의 주소를 읽고, 거기서 오프셋을 빼주면 된다.

2.1.2 Partial Overwrite

만약 코드 베이스를 구하기 어렵다면 반환 주소의 일부만 덮는 공격을 할 수 있다. ASLR 특성상, 코드 영역의 주소도 하위 12비트 값은 항상 같다. 따라서 코드 가젯 주소가 반환주소와 한 바이트만 다르다면 이 값만 덮어서 원하는 코드를 실행시킬 수 있다. 하지만 두 바이트 이상 다른 주소로 실행을 옮기고자 하는 경우 브루트 포싱이 필요하다.

3. RELRO

3.1 소개

프로세스의 실행 흐름이 조작될 수 있는 위험을 줄이기 위해, 데이터 세그먼트를 보호하는 RELocation Read-Only(RELRO)가 개발되었다. 이 기술은 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거한다. RELRO는 Partial RELRO와 Full RELRO로 구분된다.

3.2 실습 코드

// 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;
}

3.3 Partial RELRO 권한

prelro를 실행해보면 0x404000 부터 0x405000까지는 쓰기 권한이 있음을 확인할 수 있다.

섹션헤더를 보면 .init_array.fini_array같은 함수는 모두 쓰기 권한이 없는 메모리 영역에 존재한다.

$ objdump -h ./prelro

./prelro:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
...
 19 .init_array   00000008  0000000000403e10  0000000000403e10  00002e10  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 20 .fini_array   00000008  0000000000403e18  0000000000403e18  00002e18  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .dynamic      000001d0  0000000000403e20  0000000000403e20  00002e20  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got          00000010  0000000000403ff0  0000000000403ff0  00002ff0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .got.plt      00000030  0000000000404000  0000000000404000  00003000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 24 .data         00000010  0000000000404030  0000000000404030  00003030  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 25 .bss          00000008  0000000000404040  0000000000404040  00003040  2**0
                  ALLOC
...

3.4 Full RELRO

옵션없이 컴파일 하고 위와 같은 방법으로 분석을 수행하면 data와 bss에만 쓰기 권한이 있는 메모리 영역에 위치함을 알 수 있다. Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩된다. 따라서 GOT에는 쓰기 권한이 부여되지 않는다.

4. RELRO 우회

4.1 Hook Overwrite

Partial RELRO의 경우 .init_arrayfini_array에 대한 쓰기 권한이 없지만, .got.plt에 쓰기 권한이 존재하므로 GOT overwrite 공격을 활용할 수 있다.
Full RELRO의 경우 .init_array, .fini_array 뿐만 아니라 .got영역에도 쓰기 권한이 제거되었다. 이에 공격자들은 덮어쓸 수 있는 함수 중 hook을 찾아냈다. 이 함수 포인터는 동적 메모리 할당과 해제 과정에서 발생하는 버그를 디버깅하기 쉽게 하려고 만들어졌다.
malloc 함수는 시작 부분에서 __malloc_hook 존재 여부를 검사하고, 존재하면 호출한다. __malloc_hook함수는 쓰기 가능 영역에 위치한다. 따라서 공격자는 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);
  // ...
profile
회계+IT=???

0개의 댓글