Fascination
article thumbnail

Background: PIE


# 들어가며

  • ASLR이 적용되면 바이너리가 실행될 때마다 스택, 힙, 공유 라이브러리 등이 무작위 주소에 매핑되므로, 공격자가 이 영역들을 공격에 활용하기 어려워짐
  • PIE: ASLR이 코드 영역에도 적용되게 해주는 기술
    • 해당 기술은 보안성 향상을 위해 도입된 것이 아니기 때문에 엄밀하게 보호 기법은 아니나 이를 보호 기법이라고 소개하는 글이나 발표도 있음
  • Figure 1. PIE가 적용되지 않은 addr의 실행 결과

 

 

 

# PIC와 PIE

- PIC

  • 리눅스에서 ELF는 실행 파일(Executable)과 공유 오브젝트(Shared Object, SO)로 두 가지가 존재
  • 실행 파일은 addr 바이너리처럼 일반적인 실행 파일이 해당하고, 공유 오브젝트는 libc.so와 같은 라이브러리 파일이 해당함
  • 공유 오브젝트는 기본적으로 재배치(Relocation)가 가능하도록 설계되어 있음
  • 재배치가 가능하다는 것은 메모리의 어느 주소에 적재되어도 코드의 의미가 훼손되지 않음을 의미하는데, 컴퓨터 과학에서는 이런 성질을 만족하는 코드를 Position-Independent Code(PIC)라고 함
  • Figure 2. addr과 libc-2.27.so의 파일 형식

  • Figure 3. pic.c
    • gcc는 PIC 컴파일을 지원함
    • 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;
}

 

- PIC 코드 분석

  • Diff: no_pic → pic
    • no_picpicmain 함수를 비교해보면, main+14에서 "%s" 문자열을 printf에 전달하는 방식이 조금 다름
    • no_pic에서는 0x4005a1라는 절대 주소로 문자열을 참조하고 있음
    • 반면 pic는 문자열의 주소를 rip+0x2a로 참조하고 있음

  • 바이너리가 매핑되는 주소가 바뀌면 0x4005a1에 있던 데이터도 함께 이동하므로 no_pic의 코드는 제대로 실행되지 못함
  • 그러나 pic의 코드는 rip를 기준으로 상대 참조(Relative Addressing)하기 때문에 바이너리가 무작위 주소에 매핑돼도 제대로 실행될 수 있음

 

- PIE

  • Position-Independent Executable(PIE)은 무작위 주소에 매핑돼도 실행 가능한 실행 파일을 뜻함
  • ASLR이 도입되기 전에는 실행 파일을 무작위 주소에 매핑할 필요가 없었음 → 리눅스의 실행 파일 형식은 재배치를 고려하지 않고 설계됨
  • 이후 ASLR이 도입되었을 때는 실행 파일도 무작위 주소에 매핑될 수 있게 하고 싶었으나, 이미 널리 사용되는 실행 파일의 형식을 변경하면 호환성 문제가 발생할 것이 분명하여 원래 재배치가 가능했던 공유 오브젝트를 실행 파일로 사용하기로 함
  • Figure 4. ls의 파일 형식: 리눅스의 기본 실행 파일 중 하나인 /bin/ls는 공유 오브젝트 형식을 띄고 있음

 

- PIE on ASLR

  • PIE는 재배치가 가능하므로, ASLR이 적용된 시스템에서는 실행 파일도 무작위 주소에 적재됨
  • addr.c 코드를 이번에는 PIE를 적용하여 컴파일하고 실행 결과를 확인해 볼 것
  • 현재 gcc는 PIE를 기본적으로 적용하므로 모든 옵션을 제거하면 PIE가 적용된 바이너리로 컴파일 됨
  • Figure 5. PIE가 적용된 addr의 실행 결과
    • PIE가 적용되자 main  함수의 주소가 매 실행마다 바뀌고 있음을 알 수 있음

 

 

# PIE 우회

- 코드 베이스 구하기

  • ASLR 환경에서 PIE가 적용된 바이너리는 실행될 때 마다 다른 주소에 적재됨
  • 코드 영역의 가젯을 사용하거나, 데이터 영역에 접근하려면 바이너리가 적재된 주소를 알아야 함
  • 해당 주소를 PIE 베이스, 코드 베이스라고 부름
  • 코드 베이스를 구하려면 라이브러리 베이스 주소를 구할 때 처럼 코드 영역의 임의 주소를 읽고, 그 주소에서 offset을 빼주어야 함

 

- Partial Overwrite

  • 코드 베이스를 구하기 어렵다면 반환 주소의 일부 바이트만 덮는 공격을 고려해볼 수 있음 → Partial Overwrite
  • 일반적으로 함수의 반호나 주소는 함수 호출(Caller)의 내부를 가리킴
  • 특정 함수의 호출 관계는 정적 분석 또는 동적 분석으로 쉽게 확인할 수 있으므로, 공격자는 반환 주소를 예측할 수 있음
  • ASLR의 특성 상, 코드 영역의 주소도 하위 12비트 값은 항상 같음
  • 따라서 코드 가젯의 주소가 반환 주소와 하위 한 바이트만 다르다면, 이 값만 덮어서 원하는 코드를 실행시킬 수 있음
  • 그러나 만약 두 바이트 이상이 다른 주소로 실행 흐름을 옮기고자 한다면, ASLR로 뒤섞이는 주소를 맞춰야 하므로 브루트포싱이 필요하며, 공격 성공의 결과가 확률에 따라 다르게 됨

 

 

# 마치며

  • 상대 참조(Relative Addressing): 어떤 값을 기준으로 다른 주소를 지정하는 방식
  • Position Independent Code(PIC): 어떤 주소에 매핑되어도 실행 가능한 코드. 절대 주소를 사용하지 않으며 일반적으로 rip를 기준으로 하는 상대 주소를 사용함
  • Position Independent Executable(PIE): 어떤 주소에 매핑되어도 실행 가능한 실행 파일. PIE의 코드는 모두 PIC임. 자체적으로 보호 기법은 아니지만 ASLR이 적용된 환경에서는 시스템을 더욱 안전하게 만드는 효과가 있음. 최신 gcc는 기본적으로 PIE 컴파일을 함
  • Partial Overwrite: 어떤 값을 일부분만 덮는 공격 방법. PIE를 우회하기 위해 사용될 수 있음

 

 

# Quiz

1번)

 

2번)

 

3번)

profile

Fascination

@euna-319

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!