이전 학습 ↓

 

[시스템] 스택 카나리 분석(Canary)

카나리 정적 분석 스택 버퍼 오버플로우 취약점이 존재하는 코드#include int main() { char buf[8]; read(0, buf, 32); return 0;}   gcc는 기본적으로 스택 카나리를 적용하여 컴파일한다-fno-stack-protector 옵션

fight-hacker.tistory.com

 


카나리 생성 과정

 

카나리 값은 프로세스가 시작될 때, TLS에 전역 변수로 저장된다

TLS에 카나리 값이 저장되는 과정을 분석해보자

 

fsTLS를 가리키므로 fs 값만 알면 TLS 주소를 알 수 있다

하지만 리눅스에서 fs 값은 특정 시스템 콜을 사용해야만 조회 가능하다

 

그래서 fs 값을 설정할 때 호출되는 arch_prctl(int code, unsigned long addr) 시스템 콜에 중단점을 설정한다

 

 

catch 명령어는 특정 이벤트가 발생했을 때, 프로세스를 중지시킨다

arch_prctl에 catchpoint를 설정하고 canary를 실행한다

 

 

 

init_tls() 안에서 catchpoint에 도달할 때까지 countinue 명령어 실행

 

 

rdi 값이 0x1002이며, 이 값은 ARCH_SET_FS의 상숫값이다

rsi 값이0x7ffff7fa8740이므로, 이 프로세스는 TLS 0x7ffff7fa8740에 저장할 것이다

 

 

카나리가 저장될 fs+0x28 (0x7ffff7fa8740 + 0x28) 값에는 아직 어떠한 값도 설정되어 있지 않음

(리눅스는 TLS의 0x28 오프셋에 카나리를 저장함)

x/gx: 8바이트 단위로 메모리 값을 출력

 

 

 

TLS+0x28에 값을 쓸 때 프로세스를 중단시킨다

watch는 특정 주소에 저장된 값이 변경되면 프로세스를 중단시키는 명령어다

 

 

 

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

 

 

 

여기서 TLS+0x28의 값을 조회하면 카나리가 설정된 것을 확인 가능

 

 

 

실제로 이 값이 main 함수에서 사용하는 카나리값인지 확인하기 위해

main 함수에 중단점 설정하고 진행

ni 명령어로 한 줄 씩 실행

 

 

rax 값을 확인해보면 security_init에서 설정한 값과 같은 것을 확인 가능(0x33225375db8eb500)

 

 

 

 

 

 

 

 

 

카나리 정적 분석

 

스택 버퍼 오버플로우 취약점이 존재하는 코드

#include <unistd.h>

int main() {
  char buf[8];
  read(0, buf, 32);
  return 0;
}

 

 

 

gcc는 기본적으로 스택 카나리를 적용하여 컴파일한다

-fno-stack-protector 옵션으로 카나리 없이 컴파일 가능

버퍼 오버플로우 경고문이 뜨면서 컴파일됨

 

 

바이너리를 실행하고 긴 문자열을 입력하면 반환 주소가 덮여서 Segmentation fault가 발생

 

 

 

 

카나리를 활성화하여 컴파일하고 실행하면

stack samshing detected, Aborted 에러가 발생

→  스택 버퍼 오버플로우가 탐지되어 프로세스가 강제 종료됐다는 뜻

 

 

 

 

 

 


 

카나리 동적 분석

 

이제 카나리가 적용된 바이너리를 분석해보자

 

 

 

중단점을 설정하고 바이너리를 실행시킨다

 

 

 

<main+12> fs:0x28의 데이터를 읽어서 rax에 저장한다

fs는 세크먼트 레지스터의 일종으로, 리눅스는 프로세스가 시작될 때, fs:0x28에 랜덤 값을 저장한다

따라서 rax에 리눅스가 생성한 랜덤 값이 저장된다 

fs
cs, ds, es는 CPU가 사용 목적을 명시한 레지스터인 반면, fs와 gs는 목적이 정해지지 않아 운영체제가 임의로 사용할 수 있는
레지스터이다. 리눅스는 fs를 Thread Local Storage(TLS)를 가리키는 포인터로 사용한다. TLS에는 카나리를 비롯하여 
프로세스 실행에 필요한 여러 데이터가 저장된다.

 

 

 

 

코드를 두 줄 실행하면 rax에 첫 바이트가 널 바이트인 8바이트 데이터가 저장된다

 

 

 

코드를 한 줄 더 실행한다

 

 

 

그러면 rax에 저장된 랜덤값은 rbp-0x8에 저장된다

 

 

<main+54>에 중단점을 설정하고 H*16를 입력한다

rbp-0x8에 저장된 카나리 값이 버퍼 오버플로우로 인해 0x4848484848484848('HHHHHHHH')이 됨

 

 

<main+58>의 연산 결과가 0이 아니므로 <main+69>__stack_chk_fail 을 실행하게 됨

→  프로세스가 강제로 종료됨

 

 

 

 

 

+ Recent posts