[포너블] Mitigation: Stack Canary

Chris Kim·2024년 10월 14일

시스템해킹

목록 보기
9/33

본 문서는 드림핵 커리큘럼 강의를 요약한 문서입니다.

0. 서론

지난번에는 스택 버퍼 오버플로우를 통해 스택의 반환 주소를 조작하고 실행 흐름을 획득했다. 이로부터 보호하는 기법이 바로 스택 카나리(Stack Canary) 다.
이 기법은 버퍼와 반환주소 사이에 카나리 값을 집어 넣는다. 버퍼 입력을 통해 반환 주소를 조작하려면 카나리 값도 덮어쓰게 된다. 정해진 카나리 값이 아님을 감지하면 프로세스가 강제로 종료되므로, 공격자는 실행 흐름을 획득하지 못한다.

1. 작동 원리

1.1 정적 분석

예제 코드는 다음과 같다.

// Name: canary.c

#include <unistd.h>

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

카나리 비활성화

Ubuntu 22.04 gcc는 기본적으로 스택 카나리를 적용한다. 컴파일 옵션 -fno-stack-protector을 추가하면 카나리를 비활성화 할 수 있다.

$ gcc -o no_canary canary.c -fno-stack-protector
$ ./no_canary
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
Segmentation fault

카나리 활성화

카나리를 활성화해서 컴파일 해보고, 긴 문자열을 입력하면 stack smashing detectedAborted 에러가 발생한다.

$ gcc -o canary canary.c

왼쪽이 no_canary, 오른쪽이 canary다.

1.2 동적 분석

카나리 저장

초반부에 중단점을 설정하고, 실행흐름을 보면 fs:-0x28의 데이터를 읽어서 rax에 저장한다. fs는 세그먼트 레지스터의 일종으로, 리눅스는 프로세스가 시작될 때 fs:0x28에 랜덤 값을 저장한다. 즉 rax에는 랜덤값이 저장된다. 아래는 rax에 저장된 값이다.

잘보면 첫 바이트가 널바이트다.(자세한 내용은 여기) 이는 rbp-0x8에 저장된다.

fs
fs와 gs는 목적이 정해지지 않은 임의의 레지스터다. 리눅스는 fs를 Thread Local Stroage(TLS) 를 가리키는 포인터로 사용한다.

카나리 검사

main+49에 중단점을 설정하고 계속 실행시켜보자.

rbp-0x8의 값이 rdx로 옮겨지고 있다. 하지만 카나리는 H로 덮어쓰기 되어져 있다. 그 결과 뒤에서 fs:0x28rdx에서 sub 하면 뒤의 je 조건을 만족하지 못한다.(xor등을 통해 체크하기도 한다. 어쨋든 0을 통해 je 조건을 만족하면 정상적으로 프로그램이 종료되겠지만, 여기서는 그게 아니기에 __stack_chk_fail을 실행한다. 그리고 강제종료

2. 카나리 생성과정

2.1 TLS 주소파악

fs가 TLS를 가리키므로 fs의 값을 알아내면 되지만, 리눅스는 fs의 값을 특정 시스템 콜을 사용해야만 조회, 설정 할 수 있게 만들었다. info register fs, print $fs? 안된다.
arch_prctl(int code, unsigned long addr) 시스템 콜은 fs의 값을 설정할 때 호출된다. 이 시스템 콜을 arch_prctl(ARCH_SET_FS, addr) 형태로 호출하면 fs의 값은 addr로 설정된다.
catch gdb 명령어를 통해 arch_prctl에 catchpoint를 설정해주자.

c로 탐색하다보면 catchpoint에 도착한다. 이때 rdi의 값이 0x1002인데 이는 ARCH_SET_FS의 상숫값이다. rsi의 값이 0x7ffff7faa740이므로 이 프로세스는 TLS를
0x7ffff7faa740에 저장하고 fs가 이를 가리킬 것이다.

여길 보면 카나리가 저장될 위치(fs:0x28)에 어떤 값도 없음을 알 수 있다.

2.2 카나리 값 설정

gdb의 watch 명령어는 특정 주소의 지정된 값이 변경되면 프로세스를 중단시키는 명령어다.


security_init함수에서 중단되었다.

TLS+0x28의 값은 다음과 같이 카나리 값이 설정되어 있다.

main+21까지 진행하면 rax에 이 값이 저장된 것을 볼 수 있다.

3. 카나리 우회방법

3.1 무차별 대입(Brute Force)

x64 아키텍처는 8바이트, x86 아키텍처는 4바이트의 카나리를 생성한다. 첫 바이트는 널 바이트이므로 각각 7, 3바이트가 랜덤이다. 이걸 무차별 대입하려면 최대 256^7, 256^3번의 연산이 필요하다. 무리

3.2 TLS 접근

카나리는 TLS에 저장되고, 카나리 기반 보호 함수는 TLS를 참조한다. 즉 TLS의 주소가 실행마다 바뀌지만, 실행 중에 이를 획득한다면, 카나리 값을 읽거나, 임의 값으로 조정할 수 있다.

3.3 스택 카나리 릭

스택 카나리를 읽을 수 있다면 이를 이용해 검사를 우회할 수 있다. 카나리 값이 널바이트를 포함하고 있지 않다면, 카나리 값 바로 앞에 있는 버퍼를 오버플로우시켜 카나리 값을 얻을 수 있다.

profile
회계+IT=???

0개의 댓글