Stack Canary

  • 스택 카나리는 함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법
  • 메모리에는 보통 버퍼와 반환 주소 사이에 아키텍처에 따라 8바이트 혹은 4바이트의 공간이 존재하는 데, 이를 운영체제에서 무작위로 값을 주어 변조되었는지 확인하는 것
  • gcc 빌드 과정에서 카나리 보호 기법을 해제하기 위해선 -fno-stack-protector 옵션을 추가해주면 된다.


Canary를 활성화 했을 때 추가 되는 코드

0x00000000000006b2 <+8>:     mov    rax,QWORD PTR fs:0x28     
0x00000000000006bb <+17>:    mov    QWORD PTR [rbp-0x8],rax   
0x00000000000006bf <+21>:    xor    eax,eax       


0x00000000000006dc <+50>:    mov    rcx,QWORD PTR [rbp-0x8]    
0x00000000000006e0 <+54>:    xor    rcx,QWORD PTR fs:0x28      
0x00000000000006e9 <+63>:    je     0x6f0 <main+70>                   
0x00000000000006eb <+65>:    call   0x570 <__stack_chk_fail@plt>
  • 위와 같이 처음 rax 레지스터에 fs:0x28의 값이 저장된다. fs는 Tread Local Storage(TLS)를 가리키는 포인터인 세그먼트 레지스터의 일종으로 카나리와 다른 값들이 저장되어 있다.
  • fs:0x28의 값은 운영체제에서 생성한 무작위 값으로 rax에 저장된다.
  • +17의 코드는 이를 rbp-0x8의 위치에 rax값을 저장하게 되고 xor 연산을 통해 eax를 0으로 만들어준다.
  • 아래 봐야 할 건 rax에 저장되었던 값이 rcx로 옮겨지고 xor 연산을 통해서 fs:0x28의 위치에 있던 값과 je를 통해 동일한지 체크하고 있다.
  • 이 과정에서 값이 동일하지 않으면 stack_chk_fail 함수가 실행되며, 프로그램이 종료되는 것이다.


Canary 생성 과정

  1. fs는 TLS를 가리키므로 fs 값을 알면 TLS의 주소를 알 수 있지만, fs는 특정 시스템 콜을 사용해야 조회하거나 설정할 수 있다.

  2. info register fsprint $fs와 같은 방식으로 알 수 없다.
  3. fs가 설정될 때, arch_prctl(int code, unsigned long addr)이라는 시스템 콜이 호출된다.
  4. gdb의 catch 명령인 catch syscall arch_prctl을 입력하면 특정 이벤트가 발생할 때 멈춘다.
  5. 멈췄을 때, 레지스터 값을 확인해보면 rdi는 0x1002로 arch_prctl의 상숫값이 나오고 rsi에 나오는 값이 fs를 가리키게 된다.
  6. 다시 gdb의 watch 명령으로 watch *(0x7ffff7d7f740+0x28) 이렇게 입력해주면 해당 주소의 값이 변경될 때 멈춘다.
  7. 이때 나오는 값이 카나리이며, mov rax,QWORD PTR fs:0x28 코드를 실행하고 확인해보면 값이 동일한 것을 확인할 수 있다.


Canary 우회

  • 무차별 대입
    • x64 아키텍처는 8바이트, x86은 4바이트의 카나리가 존재하는데 카나리의 첫 바이트가 널바이트인 걸 제외하면 각각 7바이트와 3바이트이다. 이를 무차별 대입 연산을 하기 위해선 256^7번, 256^3번의 연산이 필요한데, x86은 어떻게든 구하겠지만 실제 서버로는 거의 불가능하다.
  • TLS 접근
    • 카나리는 TLS에 전역변수로 저장되며, 매 함수마다 이를 참조해서 사용한다.
    • TLS의 주소는 매 실행마다 바뀌지만, 실행 중 TLS의 주소를 알 수 있고, 임의 주소에 대한 읽기 또는 쓰기가 가능하다면 TLS에 설정된 카나리 값을 읽거나 조작할 수 있다.
  • 스택 카나리 릭
    • 함수의 프롤로그에서 스택에 카나리 값을 저장하므로, 이를 읽어낼 수 있으면 카나리를 우회할 수 있다.

댓글남기기