[보호기법] Stack Canary 다시보기

해당 글은 매우 카나리 보호기법에 익숙치 않은 사람들이 공부할 수 있는 방법을 기술했습니다.

Buffer Overflow Attack을 통해 실행흐름을 조작하거나 데이터 변조, 유출이 가능하고 이를 막기 위한 방법 중 하나를 알아보겠습니다.

 

카나리가 적용되면 Buffer Overflow가 날 경우 Canary 값을 비교합니다.

 

Canary도 종류가 여러개 있는데

Null Canary 0x00000000 0x00
Terminator Canary 0x00000aff 00x00, 0x0a, 0xff
Random Canary 4Byte (32-Bit), 8Byte (64-Bit) Random 0x00으로 시작
Random XOR Canary Random Canary와 동일 취약점을 가지지만 Canary 값을 읽어오는 과정이 더 복잡함

 

그러면 Buffer 값을 넘기면 각각 어떻게 동작하는지 알아보겠습니다.

 


1. Stack Canary 적용 프로그램 실행 비교

 

첫 번째 실행은 Stack Canary가 적용된 상태, 두 번째 실행은 Stack Canary가 적용되지 않은 상태에서 각각 Buf값을 넘겨보았습니다. (직관적으로 볼 수 있게 직접 a를 타이핑해봤습니다)

 

Canary가 적용되면 *** stack smashing detected *** : terminated 로 프로그램이 종료되는데 Buffer 다음 Canary 검사에서 Fail했기 때문입니다.

 

Canary가 적용되지 않은 상태라면 Buffer 이후 값은 이미 다른 값으로 덮여진 상태입니다.

이 상태에서는 Segmentation fault로 프로그램이 종료되었습니다.

 

 

 

다음은 아주 간단한게 짠 코드와 보호기법에 따른 어셈블리입니다.

X64 시스템

확인해보면 해당 스택을 할당하는 과정에서 Canary가 적용되면서 0x10만큼 더 할당되었습니다.

<+12> 카나리가 저장되는 fs:0x28에서 카나리 값을 불러옵니다.

<+21> rbp-0x8 에 카나리 값을 저장합니다.

함수내 모든 명령어가 끝난 후 

<+86> rbp-0x8 카나리 값이 변조되었는지 확인하기 위해

<+90> fs:0x28 (원래 카나리 가져온 곳)과 XOR하여 (Assembly상 sub)

<+99> 0이 만족하는지 검사한다. 만족한다면 leave

<+101> 불만족한다면(rbp-0x8이 위조되었으면) __stack_chk_fail 호출 -> Stack Smashing Detected

 

즉 정리하자면

1. 스택에 공간이 주어지자마자 rbp-0x8에 카나리를 fs:0x28로부터 읽어옴

2. 스택 공간 내에서 모든 연산이 끝난 뒤 rbp-0x8가 변조되었는지 확인하기 위해 rbp-0x8값과 fs:0x28을 XOR(또는 SUB)

3. 0이 나오면 변조가 되지 않았다고 판단하여 정상 실행, 0이 아니면 변조되었다 판단하여 프로그램 강제 중단

 

 

 

X86 시스템

x86에서는 gs:0x14 에서 값을 가져오는 것을 확인할 수 있다. [ref. GS Buffer Security Check]

그외는 x64에서 설명한 방법과 동일하다.

 


2. Stack Canary 생성 과정

fs 레지스터는 gdb에서 레지스터 값을 출력하듯 뽑을 수 없습니다.

그래서 gdb에서 catch syscall arch_prctl을 통해 시스템 콜을 잡아봤습니다.

 

fs의 값을 설정할 때 호출되는 arch_prctl(int code, unsigned long addr)로 캐치포인트를 잡고

이때 rdi, rsi를 확인해보는 것입니다.

잡힌 이후 rdi 값은 0x1002, addr(fs base)은 0x7ffff7d8d740으로 잡힙니다.

그러면 카나리가 생성되는 fs:0x28은 0x7ffff7d8d740+0x28입니다.

 

하지만 해당 값은 0으로 채워져있어 watch *0x7ffff7d8d768 명령어로 해당 값이 바뀔 때 프로그램이 멈추도록 했습니다.

그 결과 값이 변했고 직접 까보니 0xe2fe5d5048d2a600이 카나리 값으로 설정된 것을 볼 수 있습니다.

 


3. Stack Canary 우회 방법

3-1. Leak Canary 방법

Stack Canary를 Leak 할 수 있는 POC 코드를 만들어봤습니다.

 

do_me()에 도달하기 위해 중간에 Canary 위변조 없이 RET(rbp+8)를 do_me() 함수의 주소로 설정해야 합니다.

Canary를 Leak하기 위해 <cnry.c + 18>에 그대로 buf를 출력하게 만들어보겠습니다.

 

사진에 dummy와 Canary 사이의 \x00을 임의의 값으로 채워주면 <cnry.c + 18>에서 NULL문자가 없으므로 뒤에 Stack Canary가 Leak될 겁니다. 

 

사진의 어셈블리를 보면 [rbp-50]부터 [rbp-8]까지 buffer + dummy 이고 Canary의 '\x00'은 [rbp-7]입니다.

즉, 우리는 처음 read 함수 <cnry.c + 17>에서 buffer를 삽입할 때 임의의 문자 0x49 만큼 넣어주면 카나리 값이 출력될겁니다.

 

실제로 A 바이트를 0x49만큼 채워주면 뒤에 Canary가 출력된걸 볼 수 있습니다.

 

우리는 Leak된 Canary를 받아주고 다음 buf 입력때 0x48 + Canary(8) + SFP(8) + do_me(8)를 넣어주면 카나리 검사를 통과하고 실행흐름을 조작할 수 있습니다.

 

왼쪽은 Exploit 코드이고 우측 상단은 Exploit 결과입니다.

do_me 함수가 정상적으로 작동된 것을 확인할 수 있습니다.

 

3-2. Master Canary

이번엔 Canary를 Leak 하는 것이 아닌 Canary가 저장된 부분(fs:0x28)을 덮어서 검사를 우회하는 방법입니다.

일반적인 Canary Leak와 상황이 다른데, Thread를 사용하는 환경에서만 Master Canary를 덮어서 우회가 가능합니다.

 

Thread에서 할당한 변수는 Master Canary가 저장된 주소보다 낮은 주소에 있기에 카나리를 덮어쓸 수 있습니다.

 

Thread에서 할당해준 변수가 아닐 경우 주소차이가 덮기엔 너무 크게 차이납니다.

 

 

Exploit Tech는 매우 간단하니 아래 강좌를 참고해주세요.

 

https://learn.dreamhack.io/267#1

 

로그인 | Dreamhack

 

dreamhack.io

 


시간이 좀 될 때 예제 작성 후 포스팅하겠습니다.