Stack Canary
- 스택 카나리는 함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법
- 메모리에는 보통 버퍼와 반환 주소 사이에 아키텍처에 따라 8바이트 혹은 4바이트의 공간이 존재하는 데, 이를 운영체제에서 무작위로 값을 주어 변조되었는지 확인하는 것
- gcc 빌드 과정에서 카나리 보호 기법을 해제하기 위해선
-fno-stack-protector
옵션을 추가해주면 된다.
Canary를 활성화 했을 때 추가 되는 코드
0x00000000000006b2 <+8>: mov rax,QWORD PTR fs:0x28
0x00000000000006bb <+17>: mov QWORD PTR [rbp-0x8],rax
0x00000000000006bf <+21>: xor eax,eax
0x00000000000006dc <+50>: mov rcx,QWORD PTR [rbp-0x8]
0x00000000000006e0 <+54>: xor rcx,QWORD PTR fs:0x28
0x00000000000006e9 <+63>: je 0x6f0 <main+70>
0x00000000000006eb <+65>: call 0x570 <__stack_chk_fail@plt>
- 위와 같이 처음 rax 레지스터에 fs:0x28의 값이 저장된다. fs는 Tread Local Storage(TLS)를 가리키는 포인터인 세그먼트 레지스터의 일종으로 카나리와 다른 값들이 저장되어 있다.
- fs:0x28의 값은 운영체제에서 생성한 무작위 값으로 rax에 저장된다.
- +17의 코드는 이를 rbp-0x8의 위치에 rax값을 저장하게 되고 xor 연산을 통해 eax를 0으로 만들어준다.
- 아래 봐야 할 건 rax에 저장되었던 값이 rcx로 옮겨지고 xor 연산을 통해서 fs:0x28의 위치에 있던 값과 je를 통해 동일한지 체크하고 있다.
- 이 과정에서 값이 동일하지 않으면
stack_chk_fail
함수가 실행되며, 프로그램이 종료되는 것이다.
Canary 생성 과정
-
fs는 TLS를 가리키므로 fs 값을 알면 TLS의 주소를 알 수 있지만, fs는 특정 시스템 콜을 사용해야 조회하거나 설정할 수 있다.
info register fs
나print $fs
와 같은 방식으로 알 수 없다.- fs가 설정될 때,
arch_prctl(int code, unsigned long addr)
이라는 시스템 콜이 호출된다. - gdb의 catch 명령인
catch syscall arch_prctl
을 입력하면 특정 이벤트가 발생할 때 멈춘다. - 멈췄을 때, 레지스터 값을 확인해보면 rdi는 0x1002로 arch_prctl의 상숫값이 나오고 rsi에 나오는 값이 fs를 가리키게 된다.
- 다시 gdb의 watch 명령으로
watch *(0x7ffff7d7f740+0x28)
이렇게 입력해주면 해당 주소의 값이 변경될 때 멈춘다. - 이때 나오는 값이 카나리이며,
mov rax,QWORD PTR fs:0x28
코드를 실행하고 확인해보면 값이 동일한 것을 확인할 수 있다.
Canary 우회
- 무차별 대입
- x64 아키텍처는 8바이트, x86은 4바이트의 카나리가 존재하는데 카나리의 첫 바이트가 널바이트인 걸 제외하면 각각 7바이트와 3바이트이다. 이를 무차별 대입 연산을 하기 위해선 256^7번, 256^3번의 연산이 필요한데, x86은 어떻게든 구하겠지만 실제 서버로는 거의 불가능하다.
- TLS 접근
- 카나리는 TLS에 전역변수로 저장되며, 매 함수마다 이를 참조해서 사용한다.
- TLS의 주소는 매 실행마다 바뀌지만, 실행 중 TLS의 주소를 알 수 있고, 임의 주소에 대한 읽기 또는 쓰기가 가능하다면 TLS에 설정된 카나리 값을 읽거나 조작할 수 있다.
- 스택 카나리 릭
- 함수의 프롤로그에서 스택에 카나리 값을 저장하므로, 이를 읽어낼 수 있으면 카나리를 우회할 수 있다.
댓글남기기