스택 버퍼 오버플로우 취약점을 이용하면 스택 반환 주소(RET)를 조작하여 실행 흐름을 획득하는 공격이 가능하다.
스택 버퍼 오버플로우로부터 반환 주소를 보호하는 기법이 스택 카나리(Stack Canary)이다.
함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 함수 에필로그에서 해당 값의 변조를 확인하는 방법이다.
카나리 값의 변조가 확인될 시 프로세스가 강제종료 된다.

공격자는 BOF로 RET 값을 변조하려는 경우, 카나리 값을 건드리게 된다.
카나리 분석 해보기
스택 카나리 적용은 -fno-stack-protector로 끌 수 있다.


긴 값을 주면 RET가 변환되어서 세그멘테이션 오류가 발생한다.

카나리를 적용하여 컴파일하니, stack smashing detected라고 뜨며 '중지됨' (Aborted) 이라고 출력된다.

오른쪽이 카나리를 적용한 프로그램이다.
gdb를 실행시켜 추가된 부분인 main+12에 중단점을 설정하고 실행한다.
fs:0x28의 데이터를 읽어서 rax에 저장한다.
fs는 세그먼트 레지스터로, 리눅스 프로세스가 시작될 때 fs:0x28에 랜덤값을 저장한다. 이는 Thread Local Storage(TLS)를 가리키는 포인터로 사용한다.
이 값을 rax에 저장하는 것이다.

rax의 값을 확인했을 때 첫 바이트가 널바이트라는 것을 확인할 수 있다. 이렇게 생성된 값은 rbp-8 주소에 저장된다.

에필로그의 추가되었던 줄에 중단점을 설정하고 c로 계속 실행한다.
rbp-8의 값을 rdx에 저장하고, main+58에서 rdx값에서 fs:0x28의 값을 뺀다. 값이 동일하면 연산 결과가 0이 되어 je를 만족하고 main에서 정상적으로 반환된다. 값이 동일하지 않다면 __stack_chk_fail이 호출되고 프로그램이 강제종료된다.

카나리 생성 과정
fs는 TLS를 가리키므로, fs의 값을 알면 TLS의 주소를 알 수 있다.
리눅스에서 이 값은 특정 시스템콜을 사용해야 확인/설정이 가능하다. (info register fs, print $fs로 확인할 수 없다)
fs를 설정할 때 arch_prctl()에 중단점을 설정하여 fs가 어떤 값으로 설정되는지 확인할 수 있다. arch_prctl(ARCH_SET_FS, addr)로 호출하면 fs값이 addr로 설정된다.
특정 이벤트 발생 시 프로세스 중지 시키는 catch로 확인할 수 있다.

checkpoint에 도달했을 때 rdi값은 0x1002이다. 이는 ARCH_SET_FS를 뜻하는 상수다. rsi값은 0x7ffff7fa9740으로, 이 프로세스가 TLS를 해당 주소에 저장할 것이고, fs는 이 주소를 가리킬 것이다.

카나리값이 저장될 주소를 확인해 보면 아직 설정된 값이 없음을 확인할 수 있다.
watch 명령어는 특정 주소에 저장된 값이 변경되면 프로세스를 중단시킨다. watch로 TLS+0x28에 값이 써질 때 프로세스를 멈출 수 있겠다. *설정할 때는 띄워쓰기 영향을 받는 듯하다. 주소값은 붙여서 써야겠다


카나리 값의 첫바이트는 널바이트이다. _dl_setup_stack_chk_guard라는 함수에서 카나리값을 생성한다.
이 함수에서는 메모리 상에서 첫 값을 널바이트로 설정한다.
버그가 발생해서 strcpy등의 함수를 통해 스택을 복사하게될 때, 널 바이트를 통해 카나리값과 그 이후의 스택 값을 유출되지 않게 하기 위함이다.
카나리 우회 방법
무차별 대입(Brute Force)
x64 아키텍처는 8바이트의 카나리, x86 아키텍처는 4바이트의 카나리가 생성된다.
널 바이트가 포함되어있기에 실제로는 7바이트, 3바이트의 값이다.
근데 실제로는... 어려울 거다.
TLS 접근
카나리가 TLS에 저장되고 카나리에 의해 보호되는 함수마다 이를 참조한다. TLS 주소는 프로세스 실행마다 변경되지만, 실행 중에 TLS 주소를 알 수 있고, 임의 주소에 대한 읽기/쓰기가 가능하다면 TLS에 설정된 카나리 값을 읽거나 임의의 값으로 조작할 수 있다.
스택 버퍼 오버플로우를 수행할 때 알아낸 카나리 값 또는 조작한 카나리 값으로 스택 카나리를 덮으면 함수의 에필로그에 있는 카나리 검사를 우회할 수 있다.
스택 카나리 릭
스택 카나리를 읽을 수 있는 취약점이 존재한다면, 이를 이용하여 카나리 검사를 우회할 수 있다.
#include <stdio.h>
#include <unistd.h>
int main()
{
char memo[8];
char name[8];
printf("name: ");
read(0, name, 64);
printf("hello %s\n", name);
printf("memo: ");
read(0, memo, 64);
printf("memo: %s\n", memo);
return 0;
}
name은 memo보다 뒤에 위치한다 -> name에 9바이트의 문자를 넣으면 널바이트가 없는 카나리 값을 얻을 수 있다.
memo 값에 입력 받을 때 name까지 덮을 16바이트와 카나리 값 8바이트를 넣으면 카나리 검사를 우회하면서 반환 주소를 덮을 수 있다.
'System Hacking' 카테고리의 다른 글
| Return to Library (RTL공격. NX우회) (0) | 2026.05.19 |
|---|---|
| Static Link와 Dynamic Link (0) | 2026.05.19 |
| NX & ASLR (0) | 2026.05.19 |
| Stack Buffer Overflow 스택 버퍼 오버플로우 (0) | 2026.05.16 |
| 셸코드 Shellcode (0) | 2026.05.12 |