[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로 참조
- 바이너리가 매핑되는 주소가 바뀌면 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을 호출하여 실행 흐름 조작 가능
'Security > System Hacking' 카테고리의 다른 글
[Dreamhack System Hacking] STAGE 7 - oneshot (0) | 2022.02.10 |
---|---|
[Dreamhack System Hacking] STAGE 7 - 함께실습 (0) | 2022.02.10 |
[Lazenca] Protection Tech > NX, ASLR (0) | 2022.02.06 |
[Dreamhack System Hacking] STAGE 6 - basic_rop_x86 (0) | 2022.02.06 |
[Dreamhack System Hacking] STAGE 6 - basic_rop_x64 (0) | 2022.02.06 |
댓글