본문 바로가기
Security/System Hacking

[Lazenca] Protection Tech > Canaries

by 단월໒꒱ 2022. 1. 30.

1. Canaries

 1) Canaries (Canary word)

   - 버퍼 오버플로우를 모니터하기 위해 버퍼와 제어 데이터 사이에 설정된 값

   - 버퍼 오버플로우가 발생 시

     Canary 값이 손상, Canaries 데이터 검증에 실패, 오버플로우에 대한 경고 출력, 손상된 데이터 무효화 처리

 

  2) 종류

   ① Terminator canaries

      - Canary 값을 문자열의 끝을 나타내는 문자들 (NULL, CR, LF, EOF)을 이용하여 생성

      - 공격자는 Canaries를 우회하기 위해 return address를 쓰기 전에 null 문자 사용해야 함

      - null 문자로 인해 오버플로우 방지 (strcpy 함수는 null문자의 위치까지 복사)

      - 그럼에도 공격자는 잠재적으로 Canary를 알려진 값으로 겹쳐 쓰고 정보를 틀린 값으로 제어해서 Canary 검사 코드 통과 가능

 

   ② Random canaries

      - Canary 값을 랜덤하게 생성

      - 프로그램 초기 설정 시에 전역 변수에 Canary 값이 저장됨

          - 이 값은 보통 매핑되지 않은 페이지에 저장

          - 해당 메모리를 읽으려는 시도를 할 경우 segmentation fault가 발생하고 프로그램 종료됨

          - 공격자가 Canary 값이 저장된 스택 주소를 알거나 스택의 값을 읽어올 수 있으면 Canary 값을 확인할 수 있음

 

   ③ Random XOR canaries

      - Canary 값을 모든 제어 데이터 또는 일부를 사용해 xor 하여 생성
      - Canary의 값, 제어 데이터가 오염되면 Canary 값이 달라짐

      - Random Canaries와 동일한 취약점 갖고 있음

           - 스택에서 Canary 값을 읽어오는 방법이 조금 더 복잡함

           - 공격자는 Canary를 다시 인코딩하기 위해서 원래의 Canary 값, 알고리즘, 제어 데이터가 필요함\

 

 

2. 예제

 1) 소스 코드

 

#include <stdio.h>
 
void main(int argc, char **argv)
{
    char Overflow[32];
     
    printf("Hello world!\n");
    gets(Overflow);
 
}

 

 

 2) Canary 값 확인

 

lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ gdb -q ./Canary
Reading symbols from ./Canary...(no debugging symbols found)...done.
gdb-peda$ disassemble main
Dump of assembler code for function main:
   0x00000000004005d6 <+0>:   push   rbp
   0x00000000004005d7 <+1>:   mov    rbp,rsp
   0x00000000004005da <+4>:   sub    rsp,0x40
   0x00000000004005de <+8>:   mov    DWORD PTR [rbp-0x34],edi
   0x00000000004005e1 <+11>:  mov    QWORD PTR [rbp-0x40],rsi
   0x00000000004005e5 <+15>:  mov    rax,QWORD PTR fs:0x28
   0x00000000004005ee <+24>:  mov    QWORD PTR [rbp-0x8],rax
   0x00000000004005f2 <+28>:  xor    eax,eax
   0x00000000004005f4 <+30>:  mov    edi,0x4006b4
   0x00000000004005f9 <+35>:  call   0x400490 <puts@plt>
   0x00000000004005fe <+40>:  lea    rax,[rbp-0x30]
   0x0000000000400602 <+44>:  mov    rdi,rax
   0x0000000000400605 <+47>:  mov    eax,0x0
   0x000000000040060a <+52>:  call   0x4004c0 <gets@plt>
   0x000000000040060f <+57>:  nop
   0x0000000000400610 <+58>:  mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000400614 <+62>:  xor    rax,QWORD PTR fs:0x28
   0x000000000040061d <+71>:  je     0x400624 <main+78>
   0x000000000040061f <+73>:  call   0x4004a0 <__stack_chk_fail@plt>
   0x0000000000400624 <+78>:  leave 
   0x0000000000400625 <+79>:  ret   
End of assembler dump.
gdb-peda$ b *0x000000000040060a
Breakpoint 1 at 0x40060a
gdb-peda$ b *0x0000000000400610
Breakpoint 2 at 0x400610
gdb-peda$ r
Starting program: /home/lazenca0x0/Documents/Definition/protection/Canary/Canary
Hello world!
 
 
Breakpoint 1, 0x000000000040060a in main ()
gdb-peda$ i r rdi
rdi            0x7fffffffe180   0x7fffffffe180
gdb-peda$ ni
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0x000000000040060f in main ()
gdb-peda$ x/10gx 0x7fffffffe180
0x7fffffffe180: 0x4141414141414141  0x4141414141414141
0x7fffffffe190: 0x4141414141414141  0x4141414141414141
0x7fffffffe1a0: 0x00007fffffffe200  0x3a3b864735c7b300
0x7fffffffe1b0: 0x0000000000400630  0x00007ffff7a2d830
0x7fffffffe1c0: 0x0000000000000000  0x00007fffffffe298
gdb-peda$ c
Continuing.
 
 
Breakpoint 2, 0x0000000000400610 in main ()
gdb-peda$ i r rbp
rbp            0x7fffffffe1b0   0x7fffffffe1b0
gdb-peda$ x/gx 0x7fffffffe1b0 - 0x8
0x7fffffffe1a8: 0x3a3b864735c7b300
gdb-peda$ ni
 
 
0x0000000000400614 in main ()
gdb-peda$ i r rax
rax            0x3a3b864735c7b300   0x3a3b864735c7b300
gdb-peda$ ni
0x000000000040061d in main ()
gdb-peda$ i r rax
rax            0x0  0x0
gdb-peda$ ni
 
0x0000000000400624 in main ()
gdb-peda$ x/2i $rip
=> 0x400624 <main+78>: leave 
   0x400625 <main+79>:    ret   
gdb-peda$

 

 

   - disassemble 등의 명령어가 안 먹혀서 코드를 복사한 것으로 대체함

   - 사용자 값이 저장되는 영역은 0x7fffffffe180

       - 해당 영역에 코드에서 할당한 길이의 문자를 저장 ('A' * 32)

   - 0x400610 코드 영역에서 rax 레지스터에 rbp - 0x8 영역에 저장된 값을 저장

       - rbp(0x7fffffffe1b0) - 0x8 = 0x7fffffffe1a8

       - 0x7fffffffe1a8 영역에 저장된 값 : 0x3a3b864735c7b300

   - 0x400614 코드 영역에서 rax 레지스터에 저장된 값과 fs:0x28 레지스터에 저장된 값을 xor 연산

   - 0x40061d 코드 영역에서 rax 레지스터의 값이 0과 같으면 0x400624 영역으로 이동

   - 이로 인해 정상적으로 프로그램 종료

 

 

 3) Canary 값을 덮어썼을 경우 프로그램 동작 확인

 

gdb-peda$ r
Starting program: /home/lazenca0x0/Documents/Definition/protection/Canary/Canary
Hello world!
Breakpoint 1, 0x000000000040060a in main ()
gdb-peda$ i r rdi
rdi            0x7fffffffe180   0x7fffffffe180
gdb-peda$ ni
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB
0x000000000040060f in main ()
gdb-peda$ x/10gx 0x7fffffffe180
0x7fffffffe180: 0x4141414141414141  0x4141414141414141
0x7fffffffe190: 0x4141414141414141  0x4141414141414141
0x7fffffffe1a0: 0x4141414141414141  0x4242424242424242
0x7fffffffe1b0: 0x0000000000400600  0x00007ffff7a2d830
0x7fffffffe1c0: 0x0000000000000000  0x00007fffffffe298
gdb-peda$ c
Continuing.
 
 
Breakpoint 2, 0x0000000000400610 in main ()
gdb-peda$ i r rbp
rbp            0x7fffffffe1b0   0x7fffffffe1b0
gdb-peda$ x/gx 0x7fffffffe1b0 - 0x8
0x7fffffffe1a8: 0x4242424242424242
gdb-peda$ ni
 
 
0x0000000000400614 in main ()
gdb-peda$ i r rax
rax            0x4242424242424242   0x4242424242424242
gdb-peda$ ni
0x000000000040061d in main ()
gdb-peda$ i r rax
rax            0x61061c8ecf993242   0x61061c8ecf993242
gdb-peda$ ni
 
0x000000000040061f in main ()
gdb-peda$ x/3i $rip
=> 0x40061f <main+73>: call   0x4004a0 <__stack_chk_fail@plt>
   0x400624 <main+78>:    leave 
   0x400625 <main+79>:    ret   
gdb-peda$ c
Continuing.
*** stack smashing detected ***: /home/lazenca0x0/Documents/Definition/protection/Canary/Canary terminated
 
Program received signal SIGABRT, Aborted.

 

 

   - 버전이 달라서 그런지 주소값이랑 결과가 달라서 코드를 복사한 것으로 대체함

   - 사용자 입력 값이 저장되는 위치와 Canary의 위치는 앞에서 설명한 것과 동일

   - 사용자 입력 값으로 'A' * 40 + 'B' * 8 을 입력

       - 해당 값으로 인해 canary의 값이 0x4242424242424242(BBBBBBBB) 으로 변경

   - 0x400610 코드 영역에서 rax 레지스터에 rbp - 0x8 영역에 저장된 값을 저장

       - rbp(0x7fffffffe1b0) - 0x8 = 0x7fffffffe1a8

       - 0x7fffffffe1a8 영역에 저장된 값 : 0x4242424242424242

   - 0x400614 코드 영역에서 rax 레지스터에 저장된 값과 fs:0x28 레지스터에 저장된 값을 xor 연산

   - 0x40061d 코드 영역에서 rax 레지스터의 값이 0x61061c8ecf993242 이기 때문에 다음 코드 영역(0x40061f)으로 이동

   - 이로 인해 프로그램에서 "stack smashing detected" Error 메시지를 출력

 

 

3. 바이너리 파일의 보호기법 확인

 1) checksec.sh

   - checksec.sh에서 아래와 같은 결과 출력

      ① Canary_Do-not-set : No canary found

      ② Canary : Canary found

 

 

 

4. "Checksec.sh" 파일에서 Canary 발견 방법

 1) Binary

   - 아래와 같은 방법으로 바이너리의 Canary 설정여부 확인

       - 'readelf' 명령어를 이용해 해당 파일의 심볼 테이블 정보를 가져와서 Canary 설졍여부를 확인

       - 파일의 심볼 테이블에 "__stack_chk_fail"가 있으면 Canary가 적용되었다고 판단

 

 

 

 2) Process

   - 아래와 같은 방법으로 프로세서의 Canary 설정여부를 확인합니다.

       - Binary의 확인 방식과 비슷하며, 전달되는 파일의 경로가 다음과 같이 다름  ex) /proc/<PID>/exe

       - 추가된 동작은 '/proc/<PID>/exe' 파일에 'Symbol table' 정보가 있는지 확인

 

 

 

댓글