Pwn
[PWN Dreamhack] PIE & RELRO
2025. 3. 13. 01:26

PIC (Position-Independent Code)

  • 메모리의 어느 주소에 적재되어도 코드의 의미가 훼손되지 않는다는 성질
    • libc.so와 같은 공유 오브젝트(Shared Object, SO)는 기본적으로 Relocation이 가능하도록 설계되어있다.
      • 재배치가 가능하다는 것은 PIC 특성이 있다는 것
      • pic가 적용된 코드는 절대 주소를 사용하지 않으며, rip를 기준으로 데이터를 상대 참조(Relative Addressing)하기 때문에 바이너리가 무작위 주소에 매핑돼도 제대로 실행될 수 있습니다.

상대 참조(Relative Addressing)

  • 어떤 값을 기준으로 다른 주소를 지정하는 방식

PIE (Position-Independent Executable)

  • ASLR 이 코드영역에도 적용되는 보안 기법
    • 무작위 주소에 매핑돼도 실행 가능한 실행파일
  • PIE는 재배치가 가능하므로, ASLR이 적용된 시스템에서는 실행 파일도 무작위 주소에 적재됩니다. 반대로, ASLR이 적용되지 않은 시스템에서는 PIE가 적용된 바이너리더라도 무작위 주소에 적재되지 않습니다.
    -프로그램이 실행 중에 있을 때 PIE base를 leak할 수 있다면, 그를 기반으로 함수들에 대한 offset을 구하여 exploit을 진행해주면 됩니다.
  • PIE의 코드는 모두 PIC이다.

PIE 우회

  1. 코드 베이스 구하기
  • ASLR + PIE 적용된 바이너리는 실행될 때 마다 다른 주소에 적재된다.
    • 코드 영역의 가젯을 사용하거나, 데이터 영역에 접근하려면 바이너리가 적재된 주소를 알아야한다.
      • 이 주소를 PIE 베이스, 코드 베이스라고 한다.
        • PIE베이스(코드 베이스) = 코드영역의 임의 주소 - 오프셋
  1. Partial Overwrite
  • 반환 주소의 일부 바이트만 덮는 공격
    • 코드 베이스 구하기 어려운경우 사용.
  • 함수의 반환 주소는 Caller의 내부를 가리킨다.
  • 특정 함수의 호출 관계는 정적 분석 또는 동적 분석으로 쉽게 확인할 수 있으므로, 공격자는 반환주소를 예측할 수 있다.
  • ASLR의 특성 상, 코드 영역으 주소도 하위12bit값은 항상 같다. 따라서 사용하려는 코드 가젯의 주소가 반환 주소와 하위 한 바이트만 다르다면, 이 값만 덮어서 원하는 코드를 실행시킬 수 있다.
    • 두 바이트 이상이 다른 주소로 실행 흐름을 옮기고자 한다면, ASLR로 뒤섞이는 주소를 맞춰야 하므로 브루트 포싱이 필요하며, 공격이 확률에 따라 성공하게된다.

RELRO (RELocation Read-Only)

  • 불필요한 데이터 세그먼트에 쓰기 권한을 제거하는 보호기법이다.
    • Partial RELRO : 부분적으로 적용
    • Full RELRO : 가장 넓은 영역에 적용
  • Lazy Binding : 함수가 처음 호출될 때 함수의 주소를 구하고 이를 GOT에 적는다.
    • 실행중에 GOT 테이블을 업데이트해야하므로 GOT에 쓰기권한이 부여된다.
  • ELF의 데이터 세그먼트에는 프로세스의 초기화 및 종료와 관련된 .init_array , .fini_array가 있다.
    • 시작과 종료에 실행할 함수들의 주소를 저장하고있다. No RELRO시 해당 부분을 조작할 수 있다.

이러한 데이터 세그먼트 영역에 불필요한 쓰기권한을 제거하여 보호하기위해 RELRO를 적용한다.

Partial RELRO

  • Full RELRO가 default이며, PIE를 해제하면 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;
}

  • 실행하여 자신의 메모리맵을 출력해보면, 0x404000 ~ 0x405000 까지의 주소에는 w권한이있다.

  • objdump -h [파일명]으로 하단의 섹션 헤더를 참조해보면 해당영역에는 .got.plt, data, .bss가 할당되어있다.
    • 이 섹션들에는 w가 가능하다.
  • .init_array.fini_array는 각각 0x403e10와 0x40e18에 할당되어있는데, 모두 쓰기권한이 없는 0x403000`0x404000에 존재하므로 w가 불가능하다.

| Partial RELRO가 적용된 바이너리는 .got.got.plt 두개가 있다. 전역변수 중에서 실행되는 시점에 바인딩(now binding)되는 변수는 .got에 위치한다. 바이너리가 실행될 때는 이미 바인딩이 완료되어있으므로 이 영역에 쓰기권한을 부여하지 않는다.
| 실행중에 바인딩(lazy binding)되는 변수는 .got.plt에 위치한다. 이 영역은 실행 중에 값이 싸져야 하므로 쓰기 권한이 부여된다. Partial RELRO가 적용된 바이너리에서 대부분 함수들의 GOT entry는 .got.plt에 저장된다.

Full RELRO

  • gcc -o frelro relro.c로 컴파일하면 default로 Full RELRO가 설정된다.
  • 결론 : got에는 쓰기권한이 제거되어 있으며 data와 bss에만 쓰기권한이있다.

  • 실행하여 자신의 메모리맵을 출력해보면, 5561f7d7b000-5561f7d7c000 까지의 주소에는 w권한이있다.
  • 해당 파일 /home/unstraw0454/dreamhack/PIE_RELRO/frelro가 매핑된 주소는 5561f7d77000임을 알 수 있다.

  • .data 섹션의 오프셋은 0x4000이다. 이를 파일이 매핑된 주소 0x5561f7d77000에 더하면, 0x5561F7D7B000이 나오며, 쓰기권한이 있는 위치에 속한다.
  • .bss 섹션의 오프셋은 0x4010이고, 이를 매핑된 주소에 더하면 마찬가지로 쓰기권한이 있는 위치에 속하게된다.
  • Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩(GOT에 함수 주소가 기록)된다. 따라서 GOT에는 쓰기 권한이 부여되지 않는다.

RELRO 기법 우회

  • Partial RELRO : .init_array.fini_array 에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 수행하기 어려워진다.
    • .got.plt영역에 대한 쓰기권한이 존재하므로 GOT overwrite 공격 가능
  • Full RELRO : .init_array, .fini_array 뿐만 아니라 .got 영역에도 쓰기 권한이 제거
    • hook(라이브러리에 위치한 덮어쓸 수 있는 다른 함수 포인터)를 조작하는 공격 가능
      • malloc hook
        • malloc 함수 호출 -> __malloc_hook(libc.so에서 쓰기 가능 영역에 위치) 존재 검사 -> 존재시 호출
          • Hook Overwrite : libc가 매핑된 주소를 알 때, 이 변수를 조작하고 malloc을 호출해 실행 흐름을 조작할 수 있다.
      • free hook

Hooking

  • Hooking : OS가 어떤 코드를 실행하려할 때, 이를 낚아채 다른 코드(Hook)가 실행되게하는 것

    • 함수에 훅을 심어 다른 함수의 호출을 모니터링하거나, 함수에 기능을 추가 제거 수정이 가능하다.

    첫 번째는 훅 오버라이트(Hook Overwrite)로, 훅의 특징을 이용한 공격 기법입니다. Glibc 2.33 이하 버전에서 libc 데이터 영역에는 malloc()과 free()를 호출할 때 함께 호출되는 훅(Hook)이 함수 포인터 형태로 존재합니다. 이 함수 포인터를 임의의 함수 주소로 오버라이트(Overwrite)하여 악의적인 코드를 실행하는 기법을 배울 것입니다. Full RELRO가 적용되더라도 libc의 데이터 영역에는 쓰기가 가능하므로 Full RELRO를 우회하는 기법이기도 합니다.

두 번째는 libc 내에 존재하는 가젯인 원가젯(one-gadget)입니다. 기존에는 셸을 실행하려면 여러 개의 가젯을 조합해서 ROP Chain을 구성했지만, 원가젯은 단일 가젯만으로도 셸을 실행할 수 있는 매우 강력한 가젯입니다. 하지만 원가젯은 Glibc 버전마다 다르게 존재하며, 사용하기 위한 제약 조건도 모두 다릅니다. 일반적으로 Glibc 버전이 높아질수록 제약 조건을 만족하기가 어려워지는 특성이 있습니다.

fho

let textNodes = document.querySelectorAll("div.tt_article_useless_p_margin.contents_style > *:not(figure):not(pre)"); textNodes.forEach(function(a) { a.innerHTML = a.innerHTML.replace(/`(.*?)`/g, '$1'); });