보안/시스템 해킹

[ 시스템 해킹 ] Background : RELRO

haena02 2022. 8. 4. 03:03
반응형

1. RELRO

 

함수가 처음 호출될 때 함수의 주소를 구하고, 이를 GOT에 적기 위해서는 GOT 테이블을 업데이트할 수 있어야 하므로 GOT에 쓰기 권한이 부여된다. 그런데 이는 바이너리를 취약하게 만드는 원인이 됩니다.

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

이 영역들은 프로세스의 시작과 종료에 실행할 함수들의 주소를 저장하고 있는데, 여기에 임의로 값을 쓸 수 있다면, 프로세스의 실행 흐름이 조작될 수 있다.

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

 

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

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

하나는 RELRO를 부분적으로 적용하는 Partial RELRO이고,

나머지는 가장 넓은 영역에 RELRO를 적용하는 Full RELRO이다. 

 

2. Partial RELRO VS Full RELRO

 

아래 코드로 Partial RELRO와 Full 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;
}

위 코드를 partial RELRO로 컴파일하였다.

바이너리의 RELRO 여부도 checksec으로 검사할 수 있다.

 

$ ./prelro
00400000-00401000 r-xp 00000000 08:01 922450                             /home/dreamhack/prelro
00600000-00601000 r--p 00000000 08:01 922450                             /home/dreamhack/prelro
00601000-00602000 rw-p 00001000 08:01 922450                             /home/dreamhack/prelro
01cfa000-01d1b000 rw-p 00000000 00:00 0                                  [heap]
7ff3885e5000-7ff3887cc000 r-xp 00000000 08:01 1099150                    /lib/x86_64-linux-gnu/libc-2.27.so
7ff3887cc000-7ff3889cc000 ---p 001e7000 08:01 1099150                    /lib/x86_64-linux-gnu/libc-2.27.so
.
.
.

 

위 코드르 실행 시켜보면 위와같은 결과가 나온다.

세번째 줄을 보면 " 00601000-00602000 rw-p " 601000부터 602000까지 쓰기권한이 있는 것을 알 수 있다.

 

바이너리의 섹션 헤더를 보면 .got.plt, .data, .bss가 할당되어 있다, 즉, 이 섹션들은 작성이 가능하다.

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

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

바이너리가 실행될 때는 이미 바인딩이 완료되어있으므로 이 영역에 쓰기 권한을 부여하지 않는다.

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

이 영역은 실행 중에 값이 써져야 하므로 쓰기 권한이 부여된다.

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


이번에는 위 코드를 Full RELRO로 컴파일하고 실행시켜보았다.

 

실행결과와 섹션 헤더 정보랑 종합해 보면 Partial과 달리

got에는 쓰기 권한이 제거되어 있으며 data와 bss에만 쓰기 권한이 있다.

 

 

3. RELRO 기법 우회

Partial RELRO의 경우, .init_array와 .fini_array에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 수행하기 어려워진다. 하지만, .got.plt 영역에 대한 쓰기 권한이 존재하므로 GOT overwrite 공격을 활용할 수 있다.



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

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

라이브러리 함수의 대표적인 hook이 malloc hook과 free hook이다.

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

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

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

이와 같은 공격 기법을 통틀어 Hook Overwrite라고 부릅니다. 

 

반응형