본문 바로가기

SYSTEM HACKING

[SWING] Pwnable 03 - 스택 카나리

Stack Canary:

스택 오버플로우로부터 반환 주소를 보호

함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 에필로그에서 해당 값의 변조를 확인하는 보호 기법

변조가 확인될시 프로세스 강제 종료

 

스택 버퍼 오버플로우로 반환 주소를 덮으려면 필연적으로 카나리를 덮어야 하기에, 카나리 값을 모르는 공격자는 카나리 값을 변조하게 된다.

이 때 에필로그에서 변조가 확인되어 공격자는 실행 흐름을 획득하지 못한다.

 

 

산업혁명 시기에 광부들이 탄광에 들어갈 때 같이 들어갔던, 일산화탄소 유출에 민감하게 반응하는 카나리아에서 유래한 작명다운 보호 기법이다.

 

카나리 작동 원리

카나리 정적 분석

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

스택 버퍼 오버플로우 취약점

=> 카나리를 활성화 / 비활성화하여 컴파일한 바이너리 비교

 

카나리 비활성화

-fno-stack-protectort 옵션 추가

Segmentation fault 발생

 

 

카나리 활성화

stack smashing detected, Aborted 에러

: 스택 버퍼 오버플로우 -> 프로세스 강제 종료

 

 

카나리 동적 분석

카나리 저장

추가된 프롤로그 코드에 종단점 설정 + 바이너리 실행

 

 

main+12 => fs:0x28 데이터 읽어서 rax에 저장

fs: 세그먼트 레지스터의 일종

 

 

rax에 첫 바이트가 널바이트인 8바이트 데이터 저장되어 있음

생성된 랜덤값 main+21에서 rbp-0x8에 저장됨

 

 

카나리 검사

추가된 에필로그 코드에 중단점 설정 후 계속 실행한다.

main+54는 rbp-8에 저장한 카나리를 rcx로 옮긴다.

main+58은 rcx를 fs:0x28에 저장된 카나리와 xor한다.

두 값이 동일하면 연산 결과가 0이 되고 JE 조건을 만족하면서 main 함수는 정상적으로 반환된다.

동일하지 않으면 __stack_chk_fail이 호출되면서 프로그램이 강제 종료된다.

 

H를 16개 입력해 카나리를 변조했을 때 실행 흐름을 보자.

 

 

코드를 한 줄 실행하면 rbp-0x8에 저장된 카나리 값이 버퍼 오버플로우로 0x7ffff7e98992가 된 걸 알 수 있다.

 

 

main+58 연산 결과가 0이 아니므로 main+67에서 main+74로 분기하지 않고 main+69의 __stack_chk_fail을 실행한다.

 

 

__stack_chk_fail 함수가 실행되면 프로세스가 종료된다.

 

*** stack smashing detected ***: terminated

Program received signal SIGABRT, Aborted.

 

 

카나리 생성 과정

TLS 주소 파악

카나리 값은 프로세스 시작 시 TLS에 전역 변수로 저장되고, 각 함수마다 프롤로그와 에필로그에서 참조된다.

fs는 TLS를 가리키므로 fs 값을 알면 TLS 주소를 알 수 있다.

그리고 리눅스에서는 특정 시스템 콜을 사용해야만 fs 값을 알 수 있고,

gdb에서 다른 레지스터 값 출력하듯 info register fs, print $fs 같은 방법으로는 알 수 없다.

 

fs 값 설정 시 호출되는 arch_prctl(int code, unsigned long addr) 시스템 콜에 중단점 설정 후 호출

-> fs 값 addr로 설정

 

gdb에 특정 이벤트가 발생하면 프로세스를 중지시키는 명령어, catch 사용해 arch_prctl에 catchpoint 설정 후 카나리 실행

 

 

init_tls() 안에서 catchpoint에 도달할 때까지 continue

 

 

catchpoint에  도달했을 때 rdi 값이 0x1002, ARCH_SET_FS 상숫값이다.

rsi 값이자, 프로세스가 TLS를 저장할 위치이자, fs가 가리키게 될 곳은 0x7ffff7d87740이다.

 

카나리가 저장될 fs + 0x28( 0x7ffff7d87740 + 0x28)의 값엔 아직 어떤 값도 설정되지 않았다.

 

카나리 값 설정

TLS 주소를 알게 되었으니,

gdb의 watch 명령어로 TLS+0x28에 값을 쓸 때 프로세스를 중단시킨다.

 

 

 

watchpoint를 설정하고 프로세스를 계속 진행하면 security_init 함수에서 프로세스가 멈춘다.

여기서 TLS+0x28 값을 조회하면 0xea41dddfbc68d900이 카나리로 설정된 걸 확인할 수 있다.

 

 

실제로 main에서 사용하는 카나리 값인지 확인하기 위해 main에 중단점을 설정하고 계속 실행한다.

mov rax, QWORD PTR fs:0x28을 실행하고 rax 값을 확인하면 security_init에서 설정한 값과 같다.

 

 

 

 

카나리 우회

 

무차별 대입 (Brute Force)

x64 아키텍처: 8바이트 카나리 생성 (7바이트 랜덤 값 + NULL 바이트) => 256^7번 연산

x86 아키텍처: 4바이트 카나리 생성 (3바이트 랜덤 값 + NULL 바이트) => 256^3번 연산

 

실제 서버 대상으로 무차별 대입으로 카나리를 알아내기는 어렵다.

 

TLS 접근

카나리는 TLS에 전역 변수로 저장되고 매 함수마다 참조된다.

TLS 주소는 실행할 때마다 매번 바뀌지만 실행 중에 TLS 주소를 알아내 임의 주소에 읽기/쓰기를 할 수 있다면 TLS에 설정된 카나리 값을 읽거나 조작할 수 있다.

그리고 스택 버퍼 오버플로우를 수행할 때 알아내거나 조작한 카나리 값으로 스택 카나리를 덮으면 함수 에필로그의 카나리 검사를 우회하는 것이 가능하다.

 

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

 

 

'SYSTEM HACKING' 카테고리의 다른 글

[SWING] Pwnable 03 - PLT&GOT  (0) 2023.09.25
[SWING] Pwnable 03 - NX&ASLR  (0) 2023.09.25
[SWING] Pwnable 02 - Stack Buffer Overflow  (0) 2023.09.25
[SWING] Pwnable 01 - gdb, pwntools 설치  (0) 2023.09.25
[SWING] Pwnable 02 - 함수 호출  (0) 2023.09.17