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_pic와 pic의 main 함수를 비교해보면, 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번)
'Hacking Tech > System Hacking' 카테고리의 다른 글
[Dreamhack] Background: RELRO (0) | 2022.09.14 |
---|---|
[Dreamhack] Background: Library - Static Link vs. Dynamic Link (0) | 2022.09.07 |
[Dreamhack] Mitigation:NX & ASLR (0) | 2022.09.05 |
[Dreamhack] Mitigation: Stack Canary (0) | 2022.09.04 |
[Dreamhack] Memory Corruption: Stack Buffer Overflow (0) | 2022.09.01 |