본문 바로가기
Security/System Hacking

[Dreamhack System Hacking] STAGE 7

by 단월໒꒱ 2022. 2. 10.

[PIE]

1. PIC

 1) PIC (Position-Independent Code)

   - 리눅스에서 ELF는 실행 파일(Executable)과 공유 오브젝트(Shared Object, SO) 두 가지가 존재합니다.

   - 실행 파일은 addr처럼 일반적인 실행 파일, 공유 오브젝트는 libc.so와 같은 라이브러리 파일

   - 공유 오브젝트는 기본적으로 재배치(Relocation)가 가능

      - 재배치가 가능하다는 것 = 메모리의 어느 주소에 적재되어도 코드의 의미가 훼손되지 않음

      - 이런 성질을 만족하는 코드가 PIC

 

 

 

 

   - gcc는 PIC 컴파일 지원

   - 예제 코드

 

// Name: pic.c
// Compile: gcc -o pic pic.c
// 	      : gcc -o no_pic pic.c -fno-pic -no-pie
#include <stdio.h>
char *data = "Hello World!";
int main() {
  printf("%s", data);
  return 0;
}

 

 2) PIC 코드 분석

   - no_pic와 pic의 main 함수를 비교해보면, main+14에서 “%p” 문자열을 printf에 전달하는 방식이 조금 다름

   - no_pic : 0x4005a1라는 절대 주소로 문자열 참조

   - pic : 문자열의 주소를 rip+0xa2로 참조

 

 

no_pic

 

 

pic

 

 

   - 바이너리가 매핑되는 주소가 바뀌면 0x4005a1에 있던 데이터도 함께 이동하므로 no_pic의 코드는 제대로 실행되지 못함

   - 그러나 pic의 코드는 rip를 기준으로 데이터를 상대 참조하기 때문에 바이너리가 무작위 주소에 매핑돼도 제대로 실행될 수 있음

 

 

 

 

2. PIE

 1) PIE (Position-Independent Executable)

   - 무작위 주소에 매핑돼도 실행 가능한 실행 파일

 

 2) PIE on ASLR

   - PIE는 재배치가 가능하므로, ASLR이 적용된 시스템에서는 실행 파일도 무작위 주소에 적재됨

 

 

 

 

   - PIE가 적용되자 main함수의 주소가 매 실행마다 바뀌고 있음을 알 수 있음

 

 

3. PIE 우회

 1) 코드 베이스 구하기

   - ASLR 환경에서 PIE가 적용된 바이너리는 실행될 때 마다 다른 주소에 적재됨

      -> 코드 영역의 가젯을 사용하거나, 데이터 영역에 접근하려면 바이너리가 적재된 주소를 알아야 함

      - 이 주소를 PIE 베이스, 또는 코드 베이스라고 부름

   - 코드 베이스를 구하려면 라이브러리의 베이스 주소를 구할 때 처럼 코드 영역의 임의 주소를 읽고, 그 주소에서 오프셋을 빼야함

 

 2) Partial Overwrite

   - 반환 주소의 일부 바이트만 덮는 공격

   - 일반적으로 함수의 반환 주소는 호출 함수의 내부를 가리키는데, 특정 함수의 호출 관계는 정적 분석이나 동적 분석으로 쉽게 확인할 수 있으므로, 공격자는 반환 주소를 예측할 수 있음

   - ASLR의 특성 상, 코드 영역의 주소도 하위 12비트 값은 항상 같음

      -> 사용하려는 코드 가젯의 주소가 반환 주소와 하위 한 바이트만 다르다면, 이 값만 덮어서 원하는 코드 실행 가능

   - 만약 두 바이트 이상이 다른 주소로 실행 흐름을 옮기려 한다면, ASLR로 뒤섞이는 주소를 맞춰야 하므로 브루트 포싱이 필요

 

 

 

[RELRO]

1. Partial RELRO

 1) 예제 코드

   - 자신의 메모리 맵을 출력하는 바이너리의 소스 코드

 

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

 

 

 2) RELRO 검사

   - gcc는 Full RELRO를 기본 적용

   - PIE 해제 시 Partial RELRO 적용

   - checksec으로 RELRO 여부 검사 가능

 

 

 

 

 3) Partial RELRO 권한

   

 

 

   - prelro 실행 시 0x601000부터 0x602000까지의 주소에는 쓰기 권한이 있음을 확인 가능

 

 

아래도 더 있었지만 너무 길어서 여기까지만 캡쳐함

 

 

   - 섹션 헤더를 참고해보면 해당 영역에 .got.plt, .data, .bss가 할당되어 있는 것을 확인할 수 있음 (아래에 있는데 다 캡쳐하지 못함)

 

 

 cf) .got와 .got.plt

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

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

      - 바이너리가 실행될 때는 이미 바인딩이 완료되어있으므로 이 영역에 쓰기 권한 부여 x

   - 실행 중에 바인딩(lazy binding)되는 변수는 .got.plt에 위치

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

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

 

 

2. Full RELRO

 1) 예제 코드

 

// Name: relro.c
// Compile: gcc -o frelro relro.c 
#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;
}

 

 2) RELRO 검사

   - 옵션 제거하고 컴파일시 Full RELRO가 적용된 바이너리 생성

   - checksec으로 RELRO 여부 검사 가능

 

 

 

 

 3) Full RELRO 권한

 

 

 

 

 

     - frelro를 실행하여 메모리 맵을 확인하고, 이를 섹션 헤더 정보와 종합해보면 got에는 쓰기 권한이 제거되어 있으며 data와 bss에만 쓰기 권한이 있는 것을 확인할 수 있음

   - Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩됨

      -> GOT에는 쓰기 권한이 부여 X

 

 

3. RELRO 기법 우회

 1) Partial RELRO의 경우

   - init_array와 .fini_array에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 수행하기 어려움

   - 하지만, .got.plt 영역에 대한 쓰기 권한이 존재하므로 GOT overwrite 공격 활용 가능

 

 2) Full RELRO의 경우

   - init_array, .fini_array 뿐만 아니라 .got 영역에도 쓰기 권한이 제거됨

   - 따라서, 공격자들은 덮어쓸 수 있는 다른 함수 포인터를 찾다가 라이브러리에 위치한 hook을 찾아냄 (malloc hook, free hook 등)

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

       - malloc 함수의 코드를 살펴보면, 함수의 시작 부분에서 __malloc_hook이 존재하는지 검사하고, 존재하면 이를 호출함

       - __malloc_hook은 libc.so에서 쓰기 가능한 영역에 위치

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

 

 

 

댓글