System Hacking - [SSP] Stack Canary
Canary ?
SSP(Stack Smashing Protector)는 Stack Buffer Overflow 취약점을 막기 위해 개발된 보호 기법이다. 스택에서 버퍼와 SFP사이에 랜덤 값을 삽입하는 방식으로 동작하는데, 이 랜덤한 값을 Canary라고 부른다.
Canary 보호 기법은 함수의 Return Address 조작을 방지하는 보호 장치이다. 함수 프롤로그에 랜덤한 값을 생성시켜 지역변수의 스택 영역보다 높은 메모리 영역에 미리 저장해둔다. 이러면 BOF공격으로 buf의 영역을 넘어서 RET까지 도달하는 과정에서 Canary를 거치면서 조작할 수 밖에 없는데, 함수 에필로그에서 Canary값이 변조되어 있으면 프로그램을 종료한다.
1
2
3
4
5
6
7
8
9
10
Stack Frame Example
+-------------------+ # Low
| buf |
+-------------------+
| Canary |
+-------------------+
| SFP |
+-------------------+
| RET |
+-------------------+ # High
카나리는 0x79156d420438ff00와 같은 형식(64비트 아키텍처)인데, 널 바이트(\x00)를 LSB로 갖는다는 특징이 있다. 리틀엔디안 형식을 따를 때 이 널바이트는 메모리의 가장 낮은 주소에 위치한다는것을 인지하자. (Canary leak exploit 짤 때 널바이트를 추가하는 과정에서 메모리의 높은쪽인지 낮은쪽인지 헷갈릴 수 있음)
How?
1
2
3
4
5
6
7
//! gcc -fstack-protector -o canary canary.c
#include <unistd.h>
int main(){
char buf[8];
read(0, buf, 32);
return 0;
}
buf를 갖는 간단한 코드로 예시를 들어보자. canary 보호기법을 포함하여 컴파일 한 후 어셈블 코드를 보면, 다음과 같이 C코드에서는 없던 동작을 하는 코드가 추가됨을 확인할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
pwndbg> disass main
...
0x000055728cecd151 <+8>: mov rax,QWORD PTR fs:0x28 < Canary 불러오기
0x000055728cecd15a <+17>: mov QWORD PTR [rbp-0x8],rax
0x000055728cecd15e <+21>: xor eax,eax
...
0x000055728cecd17b <+50>: mov rdx,QWORD PTR [rbp-0x8]
0x000055728cecd17f <+54>: sub rdx,QWORD PTR fs:0x28 < Canary 검사
0x000055728cecd188 <+63>: je 0x55728cecd18f <main+70>
0x000055728cecd18a <+65>: call 0x55728cecd030 <__stack_chk_fail@plt>
...
canary in fs:0x28
리눅스는 프로세스가 시작될 때 fs:0x28에 랜덤 값을 저장한다. fs는 세그먼트 레지스터로, 리눅스에서는 Thread Local Storage를 가리키는 포인터로 사용된다. TLS에는 카나리를 비롯하여 프로세스 실행에 필요한 여러 데이터가 저장된다.
리눅스에서 fs의 값은 특정 시스템콜을 통해서만 조회하거나 설정할 수 있다. 따라서, 다른 레지스터와 같이 gdb에서
info register fs이런식으로 조회할 수 없다.
fs setting syscall
fs의 값을 설정할 때 arch_prctl(int code, unsigned long addr)시스템 콜이 호출된다. arch_prctl(ARCH_SET_FS, addr)의 형태로 호출되면 fs의 값이 addr로 설정된다. catch:(특정 이벤트가 발생했을 때 프로세스를 중지시키는 gdb 명령)를 통해 catchpoint를 설정하고 살펴보자.
1
2
3
4
5
6
7
pwndbg> catch syscall arch_prctl
Catchpoint 1 (syscall 'arch_prctl' [158])
pwndbg> r
...
pwndbg> info register rdi rsi
rdi 0x1002 4098
rsi 0x7f12caf6f740 139718691321664
Catchpoint에 도달해서 Register를 살펴보면 rdi는 0x1002, rsi는 0x7f12caf6f740의 값을 갖는다. 0x1002는 ARCH_SET_FS의 상숫값이다. rsi의 값에 따라 fs가 저장되므로, 이 프로세스는 TLS를 0x7f12caf6f740에 저장할 것이고, fs는 이를 가리키게 된다.
이 시점에서 Canary가 저장되는 fs:0x28을 살펴보면 아직 아무런 값이 설정되지 않은것을 볼 수 있다.
1
2
pwndbg> x/gx 0x7f12caf6f740+0x28
0x7f12caf6f768: 0x0000000000000000
watch는 특정 주소에 저장된 값이 변경되면 프로세스를 중단시키는 gdb 명령이다. 이를 통해 TLS+0x28에 값을 쓸 때 프로세스를 중지시켜 분석해보자.
1
2
3
4
5
6
7
pwndbg> watch *(0x7f12caf6f740+0x28)
Hardware watchpoint 2: *(0x7f12caf6f740+0x28)
pwndbg> c
Hardware watchpoint 2: *(0x7f12caf6f740+0x28)
...
pwndbg> x/gx 0x7f12caf6f740+0x28
0x7f12caf6f768: 0x08fb104537df8300
watchpoint까지 진행 시킨 후 TLS+0x28의 값을 조회해보면 Canary값이 저장되어 있다.
Canary 우회
카나리는 TLS에 전역변수로 저장되며, 매 함수마다 이를 참조하여 사용한다. 여기서 카나리를 우회하는 핵심 아이디어는 TLS의 주소를 실행중에 알아내는 것이다. 만약 TLS의 주소를 알고, 임의 주소에 대하여 읽기 또는 쓰기가 가능하다면 카나리를 미리 읽어내거나 조작하는것이 가능해 지는것이다.
예를들어, 카나리 값을 알아낸다면 BOF시에 Canary 위치에는 미리 알아낸 Canary를 입력해두면 함수 에플로그에서 Canary 검사 시 정상적인 동작이라고 판별되는 것이다.
따라서 카나리를 읽어내는 취약점이 존재한다면, 카나리 검사를 우회할 수 있게된다.