1. Introduction
지난 시간에 함수가 처음 호출 될 때 함수의 주소를 구하고, 이를 GOT에 적는 Lazy Binding을 했고,
이 후, GOT에 쓰기 권한이 있는 Lazy Binding의 취약점을 활용해 GOT Overwrite를 해 ROP 공격을 했다.
ELF의 데이터 세그먼트에는 프로세스의 초기화 및 종료와 관련된 .init_array, .fini_array가 있다.
이 영역은 프로세스의 시작과 종료에 실행할 함수들의 주소를 저장하고 있는데, 이를 임의의 값을 넣어 공격해 프로세스의 실행 흐름을 방해 혹은 조작이 가능하다.
위와 같이 데이터 세그먼트에서의 흐름을 조작하는 것을 보호하기 위한 RELRO(RELocation Read-Only) 보호기법이 있다.
RELRO는 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거한다.
2. RELRO
2.1 Partial RELRO
#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.1.1 checksec
Partial RELRO를 확인하기 위해 checksec를 사용해서 알아냈다.
2.1.2 Partail RELRO 권한
0x404000 ~ 0x405000 주소에 쓰기권한이 있는 것을 확인 할 수 있다.
이는 .got.plt의 일부분, .data, .bss 영역에 해당된다.
하지만, .init_array와 .fini_array는 각각 0x403df8, 0x403e00에 해당되어 쓰기 권한이 없다.
got와 got.plt의 차이점은
전역 변수 중에서 실행되는 시점에 바인딩(now binding)되는 변수는 .got에 위치한다.
바이너리가 실행될 때는 이미 바인딩이 완료가 되어 있으므로 쓰기권한이 없다.
하지만, 실행 중에 바인딩(lazy binding)되는 변수는 .got.plt에 위치한다.
이 영역은 실행 중에 값이 써져야 하므로 쓰기 권한이 부여된다.
2.2 Full RELRO
#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.2.1 checksec
2.2.2 Full RELRO 권한
got에는 쓰기 권한이 제거되어 있고, data와 bss 영역에만 쓰기권한이 있다.
Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩된다.
따라서 GOT에는 쓰기 권한이 부여되지 않는다.
3. RELRO 우회
Partial RELRO 같은 경우는 .init_array와 .fini_array에 대한 쓰기 권한이 제거되어 두 영역을 덮었는 공격이 어렵다.
하지만, 지난 시간에 했던 것처럼 lazy binding의 취약점을 이용해 GOT Overwrite가 가능하다.
Full RELRO는 .init_array와 .fini_array, .got 영역에도 쓰기 권한이 없기 때문에 GOT Overwrite가 불가능하다.
따라서 라이브러리에 위치한 hook을 활용해서 overwrite가 가능하다.
라이브러리 함수의 hook은 malloc hook과 free hook이다. 이 함수 포인터는 동적 메모리의 할당과 해제 과정에서 발생하는 버그를 쉽게 디버깅하고 모니터링하기 쉽게 만들어졌다.
다음 시간에 Hook Overwrite에 대해 자세하게 알아보자.
4. Conclusion
RELocation Read-Only(RELRO): 불필요한 데이터 영역에 쓰기 권한을 제거함.
Partial RELRO: init array, fini array 등 여러 섹션에 쓰기 권한을 제거함. Lazy binding을 사용하므로 라이브러리 함수들의 GOT 엔트리는 쓰기가 가능함. GOT Overwrite등의 공격으로 우회가 가능함.
Full RELRO: init array, fini array 뿐만 아니라 GOT에도 쓰기 권한을 제거함. Lazy binding을 사용하지 않으며 라이브러리 함수들의 주소는 바이너리가 로드되는 시점에 바인딩됨. libc의 malloc hook, free hook과 같은 함수 포인터를 조작하는 공격으로 우회할 수 있음.
마치며
이번 시간엔 보호 기법 RELRO에 대해 알아 보았다.
다음 시간에는 RELRO를 우회하는 Hook Overwrite에 대해 알아보자.
Reference
https://dreamhack.io/lecture/courses/99