본문 바로가기

DreamHack: System Hacking/F Stage 6

Mitigation: Stack Canary

Stack Canary란

1) 함수 프롤로그에서 스택 버퍼와 SFP 사이에 임의의 값음 삽임함
2) 함수의 에필로그에서 해당 값의 변조를 확인함
               변조 확인(O) -> 프로세스 강제 종료
               변조 확인(X) -> 프로세스 정상 종료

Canary가 적용된 Stack Frame의 구조
Canary가 적용된 파일과 적용되지 않은 파일의 Disassemble 결과 차이

 

Canary가 적용돼있는 Canary.asm을 분석해보자

Canary.c

#include <unistd.h>
int main() {
  char buf[8];
  read(0, buf, 32);
  return 0;
}

Canary.asm

1 push   rbp
2 mov    rbp,rsp
3 sub    rsp,0x10
4 mov    rax,QWORD PTR fs:0x28
5 mov    QWORD PTR [rbp-0x8],rax
6 xor    eax,eax
7 lea    rax,[rbp-0x10]
8 mov    edx,0x20
9 mov    rsi,rax
10 mov    edi,0x0
11 call   read@plt
12 mov    eax,0x0
13 mov    rcx,QWORD PTR [rbp-0x8]
14 xor    rcx,QWORD PTR fs:0x28
15 je     0x6f0 <main+70>
16 call   __stack_chk_fail@plt
17 leave
18 ret

 

1) 4 mov    rax,QWORD PTR fs:0x28

fs:0x28의 데이터를 읽어서 rax에 저장한다. 여기서 *fs는 세그먼트 레지스터의 일종으로 리눅스는 프로세스가 시작될 때 fs:0x28에 랜덤 값을 저장한다. 따라서 이 코드의 결과로 rax에는 리눅스가 생성한 랜덥 값이 저장된다. 위의 코드가 실행되고 rax의 값을 읽어보면 RAX에  "0x7792f74948f22b00"라는 값이 저장되어 있음을 확인할 수 있다. 이때 rax에 저장된 값의 첫 바이트에는 널바이트가 저장되어 있음 또한 확인할 수 있다(little-endian 방식).

*fs에 대한 부가 설명

 

2) 5 mov    QWORD PTR [rbp-0x8],rax

그리고 이렇게 생성된 랜덤 값은 'rbp-0x8'에 저장된다.

 

3) 13 mov    rcx,QWORD PTR [rbp-0x8]

함수 종료 과정에서 rcx에 rbp-0x8에 있는 데이터를 저장한다.

 

4) 

14 xor    rcx,QWORD PTR fs:0x28
15 je     0x6f0 <main+70>
16 call   __stack_chk_fail@plt
17 leave  ; <main+70>
18 ret

rcx와 fs:0x28을 xor하여 값이 같다면 je가 실행되어 프로세스가 정상적으로 종료되고, rcx와 fs:0x28의 값이 같지 않다면 __stack_chk_fail@plt 함수가 실행되어 프로세스가 비정상 강제 종료되게 된다.

 

카나리 우회

1) 무차별 대입(Brute Force)

x64 아키텍처에서는 8바이트의 카나리가 생성되며, x86 아키텍처에서는 4바이트의 카나리가 생성된다. 각각의 카나리에는 NULL 바이트가 포함되어 있으므로, 실제로는 7바이트와 3바이트의 랜덤한 값이 포함된다.

즉, 무차별 대입으로 x64 아키텍처의 카나리 값을 알아내려면 최대 256^7번, x86 에서는 최대 256^3 번의 연산이 필요하다. 연산량이 많아서 x64 아키텍처의 카나리는 무차별 대입으로 알아내는 것 자체가 현실적으로 어려우며, x86 아키텍처는 구할 순 있지만, 실제 서버를 대상으로 저정도 횟수의 무차별 대입을 시도하는 것은 불가능하다.

 

2) TLS 접근

카나리는 TLS에 전역변수로 저장되며, 매 함수마다 이를 참조해서 사용한다. TLS의 주소는 매 실행마다 바뀌지만 만약 실행중에 TLS의 주소를 알 수 있고, 임의 주소에 대한 읽기 또는 쓰기가 가능하다면 TLS에 설정된 카나리 값을 읽거나, 이를 임의의 값으로 조작할 수 있다.

그 뒤, 스택 버퍼 오버플로우를 수행할 때 알아낸 카나리 값 또는 조작한 카나리 값으로 스택 카나리를 덮으면 함수의 에필로그에 있는 카나리 검사를 우회할 수 있다.

 

3) 카나리 릭(Canary Leak) ★★

스택 카나리를 읽을 수 있는 취약점이 있다면, 이를 이용하여 카나리 검사를 우회할 수 있다.

 

카나리 릭 예제)

소스코드

#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;
}

 

  read(0, name, 64);

read 함수가 실행되어 stdin으로 name에 값을 저장할 때, name에 dummy값을 9바이트만큼(name의 크기가 8바이트이고 Canary의 첫 바이트는 NULL 바이트이므로) 넣어주면 

 

printf("hello %s\n", name);

에서 Canary가 유출되어 Canary 값을 읽어낼 수 있다.

 

'DreamHack: System Hacking > F Stage 6' 카테고리의 다른 글

[혼자 실습] ssp_001  (0) 2023.05.05
Exploit Tech: Return to Shellcode  (0) 2023.05.05