취약점분석/Pwnable

[Pwnable] 스택 카나리 - 2 (Canary Leak)

poiri3r 2025. 9. 22. 21:27

전편에서 알아본 스택 카나리는 버퍼 오버플로우를 막아내는데 아주 유용합니다.

이런 카나리를 우회하는 방법 중 가장 대표적인 방법은 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 + *리턴주소를 입력하여 리턴주소를 덮을 수 있습니다.

오늘 포스팅은 여기까지 마치고 내일 포스팅 할 때 실제 드림핵 문제를 풀면서 적용해보도록 하겠습니다!

읽어주셔서 감사합니다.