1309 단어
7 분
Buffer Overflow & Stack Canary 공격부터 방어 기법까지
2026-03-30

C언어는 성능이 좋지만 메모리 경계를 직접 관리해야 한다. 그 결과 스택 기반 버버 오버플로우든 30년이 넘은 지금도 여전히 실효성이 높은 취약점이다. 이를 방지하기 위해 등장한 기술 중 하나가 Stack Canary 이다. 하지만 Stack Canary는 만능이 아니다. 해커들은 이를 Canart Leak을 하거나 우회할 수 있다. 따라서 필자는 이번 글에서 버퍼 오버플로우의 메모리 구조적 원리와 Stack Canary의 내부 동작 원리와 우회 기법, 방어 전략까지 설명하도록 하겠다.


1. 스택 카나리의 내부 동작 원리#

x86_64 기준 스택 프레임 구조는 다음과 같다.

Return Address
Saved RBP (Base Pointer)
canary(4~8 bytes) <- 보호장치
Local buffer <- 오버플로우 발생 위치

버퍼 오버플로우가 발생하면 스택이 위로 확장되며 Canary와 RBP, RET를 차례로 덮는다. 해커는 이 구조를 통해 흐름 탈취를 시도한다.

1.1 스택 카나리의 삽입 검사#

스택 카나리는 다음과 같은 과정으로 동작한다.

  • 함수 시작 시 __stack_chk_guard 값이 TLS에서 로드되어 스택에 저장된다.

  • 함수 종료 시 스택에 저장된 값과 TLS의 현재 Canary 값이 비교된다.

  • 불일치시 __stack_chk_fail() 함수가 호출되어 프로그램이 종료된다.

1.2 카나리의 특성#

  • 일반적으로 카나리는 0x00XXXXXXXXXX 형식으로 NULL로 시작하여 문자열 기반 함수의 우회를 방지한다.

  • x86_64 아키텍처에서는 %fs:0x28 오프셋을 통해 TLS에서 Canary 값을 로드한다.

2. 스택 카나리 우회 기법#

2.1 카나리 릭 (Canary Leak)#

해커는 포맷 문자열 취약점이나 메모리 덤프를 통해 스택에 저장된 값을 유출할 수 있습니다.

cahr buf[128];
gets(buf);
printf(buf); // "%19$lx" -> Canary 값 출력

해당 코드에서 printf(buf);는 사용자가 입력한 문자열을 포맷 문자열 포인터 자체로 사용한다. 따라서 해커가 %19$lx 같은 포맷 문자열 지정자를 입력하면 printf()는 현재 스택의 특정 위치에 있는 데이터를 16진수로 출력하게 된다.

2.1.1 포맷 문자열 접근 원리#

C언어 가변 인자 함수(printf, sprintf 등)는 스택에 인자들이 순서대로 푸시된다. 포맷 문자열 내 %N$lx는 스택에 있는 N번째 인자 위치의 값을 출력하라는 의미이다.

2.1.2 스택 레이아웃#

x86_64의 스택 레이아웃은 다음과 같다.

x86_64
buf
padding
canary
saved rbp
return address

gets(buf)를 통해 입력값으로 %19$lx 같은 포맷 스트링이 들어가면 이 값은 buf에 저장되고 이후 printf(buf)가 호출되면 이 문자열을 포맷 문자열로 해석한다. printf는 내부적으로 va_arg() 매크로를 통해 스택 상의 다음 인자들을 접근한다. 따라서 %19$lxprintf가 스택에서 19번째 인자 위치를 읽어 그 값을 16진후 long 값으로 출력하라는 의미이다. 그러므로 스택의 19번째 위치에 Canary가 있을 경우 %19$lx는 Canary값을 화면에 출력하게 된다.

2.2 부분 덮어쓰기 (Partial Overwrite)#

카나리 값의 일부만 덮어쓰는 기법으로 특히 카나리 값의 첫 바이트가 NULL(0x00)인 경우 문자열 복사 함수들이 이를 문자열의 끝으로 인식하여 전체 카나리 값을 덮어쓰지 못하게 된다. 그러나 memcpy와 같은 함수는 NULL 바이트를 포함한 데이터를 복사할 수 있으므로 이를 이용하여 카나리 값을 부분적으로 덮어쓸 수 있다.

2.3 브루트 포싱#

32비트 시스템에서는 카나리 값의 엔트로피가 낮아(24비트) 브루트 포싱이 현실적으로 가능할 가능성이 있다. 프로세스가 자주 재시작되거나 포크되는 경우 동일한 카나리 값이 재사용 될 수 있으므로 이를 이용하여 카나리 값을 추측할 수 있다.

3. 스택 카나리 방어#

3.1 포맷 문자열 취약점 방지#

printf(user_input)과 같은 코드를 찾아 %s와 같은 포맷 지정자를 명시적으로 사용하는지 확인하고 -Wformat -Werror=format-security 플래그를 사용하여 포맷 문자열 취약점을 컴파일 시점에 탐지한다.

3.2 카나리 값의 엔트로피 증가 및 보호#

프로그램 시작 시 고유한 랜덤 카나리 값을 생성하여 사용하고 카나리 값을 TLS에 저장하여 각 스레드마다 독립적인 값을 유지하며 접근은 제한한다.

3.3 메모리 보호 기법 활용#

ASLR(Address Space Layout Randomization), NX(No-eXecute) 비트 설정, RELRO(Read-Only Relocations) 등의 메모리 보호 기법을 사용한다.


오늘은 버퍼 오버플로우와 스택 카나리에 대해서 알아보았다. 다음 글에서는 새로운 공격 기법과 방어 기법들을 가지고 오도록 하겠다.

Buffer Overflow & Stack Canary 공격부터 방어 기법까지
https://blog.paisl.cloud/posts/001/
저자
PAISL
발행일
2026-03-30
콘텐츠 라이선스
CC BY-NC-SA 4.0