스택 버퍼 오버플로우로부터 반환 주소를 보호하는 방법
- 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입
- 함수의 에필로그에서 해당 값의 변조를 확인하는 기법
- 카나리가 적용되면, 스택 버퍼 오버플로우가 탐지되면 프로세스가 강제 종료됨
예제 코드
#include <unistd.h> int main() { char buf[8]; read(0, buf, 32); // 32자리를 받아오므로 오버플로우 가능성 return 0; }
카나리가 적용되지 않았을 때 :
긴 입력이 들어오면Segmentation fault
가 발생한다.카나리가 적용되었을 때 :
긴 입력이 들어오면stack smashing detected
,Aborted
가 발생한다.
프로세스가 강제로 종료되었다.
fs
: 세그먼트 레지스터의 일종, 리눅스는 프로세스가 시작될 때fs:0x28
에 랜덤 값을 저장한다.
fs:0x28
에서 데이터를 읽어서rax
에 저장한다. (랜덤 값)
rax
에 널 바이트로 시작하는 8바이트의 데이터가 저장되어 있고, 이는rbp-0x8
에 저장된다.
rbp-0x8
에 저장해뒀던 카나리를rcx
로 옮기고,
xor 연산으로 기존의 카나리(fs:0x28
) 와 비교합니다.
xor 연산의 결과가 0이라면,je
의 조건을 만족하여 정상적으로 반환된다.
두 값이 동일하지 않으면__stack_chk_fail
이 호출되면서 프로그램이 강제로 종료된다.
카나리 값은 프로세스가 시작될 때, TLS에 전역 변수로 저장되고,
각 함수마다 프롤로그와 에필로그에서 이 값을 참조한다.주소 파악
fs
는 TLS를 가리키므로fs
의 값을 알면 TLS의 주소를 알 수 있다.
→fs
값을 설정할 때 호출되는arch_prctl
시스템 콜을 통해 알아낸다.
무차별 대입 (Brute Force)
맨 앞의 널 바이트를 제외하면
x64 아키텍처에서는 7바이트의 랜덤한 값을,
x86-64 아키텍처에서는 3바이트의 랜덤한 값을 가진다.
이를 무차별 대입하여 카나리 값을 알아낸다.TLS 접근
카나리는 TLS에 전역 변수로 저장된다. 매 실행마다 TLS의 주소가 바뀌지만
실행중에 TLS의 주소를 알 수 있고, 임의 주소에 대한 읽기/쓰기가 가능하다면
TLS에 설정된 카나리 값을 읽거나, 이를 임의로 조작할 수 있다.스택 카나리 릭
함수의 프롤로그에서 스택에 카나리 값을 저장하므로, 이를 읽어낼 수 있으면
카나리를 우회할 수 있다. 가장 현실적인 방법