전편에서 알아본 스택 카나리는 버퍼 오버플로우를 막아내는데 아주 유용합니다.
이런 카나리를 우회하는 방법 중 가장 대표적인 방법은 TLS에 접근하여 카나리 값을 조작하는 TLS 접근 방법과 스택 카나리를 읽을 수 있는 취약점을 찾아내 이용하는 카나리 릭이 있습니다.
오늘은 그 중 카나리 릭에 대해 알아보겠습니다.
릭은 "샌다"라는 뜻을 가지며 카나리 릭에는 여러가지 경로가 있습니다.
그 중 NULL 미종결로 인한 카나리 릭입니다.
(*카나리 값의 첫 바이트는 항상 널바이트로 시작합니다.)
#include <stdio.h>
#include <unistd.h>
int main(void){
char name[8];
write(1, "name: ", 6);
ssize_t n = read(0, name, 64);
(void)n;
printf("hello %s\n", name);
return 0;
}
위의 코드를 보겠습니다.
여기서 취약점이 발생하는 부분은 printf입니다.
printf는 널문자를 찾을 때 까지 계속 메모리를 읽어가며 출력합니다.
이 떄 입력에서 8바이트가 아닌 9바이트를 보내게 되면 카나리의 LSB(첫번째 바이트)인 NULL이 덮어씌워져서 printf가 카나리의 남은 7바이트를 포함해 인접한 스택의 값들을 출력하게 됩니다.
위의 코드를 컴파일하고 실행해보겠습니다.
먼저 정상적인 입출력입니다.

그 뒤 오버플로를 발생시킨 출력입니다.

hello porierrr 뒤에 이상한 문자열들이 출력되고 프로세스가 terminate 됩니다.
이러한 문자열들은 버퍼 뒤의 바이트들이 비인쇄 문자라서 글자처럼 보이지 않아 보이는 것이며 실제로는 카나리의 남은 7바이트 + 인접스택바이트가 출력되는 것입니다.
이제 다음 코드들을 살펴보겠습니다.
// Name: bypass_canary.c
// Compile: gcc -o bypass_canary bypass_canary.c
#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바이트 + 쉘코드를 넣으면 익스플로잇이 가능해집니다.

첫번째 출력에 나온 값들로 카나리값을 구할 수 있습니다.
pwngdb를 쓰는 경우 그냥 canary만 입력해도 나오긴 하더라고요

(프로세스마다 canary값이 다르게 사용됩니다.)

이렇게 리틀 엔디언이 나오면 그 뒤 memo를 입력하는 칸에 문자 16개 + 뽑은 canary값 + 0*16 + *리턴주소를 입력하여 리턴주소를 덮을 수 있습니다.
오늘 포스팅은 여기까지 마치고 내일 포스팅 할 때 실제 드림핵 문제를 풀면서 적용해보도록 하겠습니다!
읽어주셔서 감사합니다.

'취약점분석 > Pwnable' 카테고리의 다른 글
| [Pwnable] NX & ASLR (0) | 2025.09.30 |
|---|---|
| [Pwnable] ssp_001 write up (0) | 2025.09.24 |
| [Pwnable] 스택 카나리 - 1 ( Stack Canary ) (1) | 2025.09.22 |
| [Pwnable] 스택 버퍼 오버플로우 (1) | 2025.06.30 |
| [Pwnable] Shellcraft와 셸코드 문제 풀이(shell_basic) (0) | 2025.06.19 |