본문 바로가기
Hacking & Security/System Hacking

[Lazenca] Canaries

by 희디 2022. 3. 22.

https://www.lazenca.net/display/TEC/03.Canaries

 

03.Canaries - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List Canaries Canaries 또는 Canary word는 버퍼 오버 플로우를 모니터하기 위해 버퍼와 제어 데이터 사이에 설정 된 값입니다.버퍼 오버플로가 발생하면 Canary

www.lazenca.net

 

Canaries

- Canaries 또는 Canary world 버퍼 오버 플로우를 모니터하기 위해 버퍼와 제어 데이터 사이에 설정된 값이다. 

- 버퍼 오버플로우가 발생하면 Canary삾이 손상되며, Canaries 데이터의 검증에 실패하여 오버플로우에 대한 경고가 출력되고 손상된 데이터를 무효화 처리된다. 

 

Canaries type 

1) Terminator canaries

 

- Terminator canaries는 Canary의 값을 문자열의 끝을 나타내는 문자들을 이용해 생성한다. 

- Terminator canaries의 값을 NULL(0x00), CR(0x0d), LF(0x0a) 및 EOF(0xff)로 구성된다. 

   : 공격자는 Canaries를 우회하기 위해 return address를 쓰기 전에 nill 문자를 써야 한다. 

   : Null 문자로 인해 overflow를 방지하게 된다. 

   : strcpy( )는 null문자의 위치까지 복사한다. 

   : 이 보호에도 불구하고 공격자는 잠재적으로 카나리를 알려진 값으로 겹쳐쓰고 정보를 틀린 값들로 제어해서 카나리 검사 코드를 통과할 수 있다. 

 

2) Random Canaries

 

: 랜덤 카나리는 카나리의 값을 랜덤하게 값이 생성한다. 

: 일반적으로 익플로잇을 이용해 카나리를 읽는 것은 논리적으로 불가능하다. 

: 랜덤 카나리는 프로그램 초기 설정 시에 전역 변수에 카나리값이 저장된다. - 이 값은 보통 매핑되지 않은 페이지에 저장된다. 

: 해당 메모리를 읽으려는 시도를 할 경우 segment fault가 발생하고 프로그램이 종료된다. 

: 공격자가 카나리값이 저장된 stack address를 알거나 스택의 값을 읽어올 수 있는 프로그램이 있으면 카나리값을 확인할 수 있다. 

 

3) Random XOR canaries 

 

: Random XOR canaries는 카나리의 값을 모든 제어 데이터 또는 일부를 사용해 xor-scramble 하여 생성한다. 

: 카나리의 값, 제어 데이터가 오염되면 카나리의 값이 틀려진다. 

: random canaries와 동일한 취약점을 가지고 있다. 

: 단지 카나리값을 stack에서 읽어오는 방법이 조금더 복잡해진다. 

: 공격자는 카나리를 다시 인코딩하기 위해 오리지널 카나리값, 알고리즘, 제어 데티어가 필요하다. 

 

<예시> 

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

 

- build command하는 방법

gcc -fstack-protector –param ssp-buffer-size=N xx.c   -> byte 변경
gcc -fstack-protector-all xx.c                                  -> 모든 함수 보호

 

<Check to Canary(카나리 값 확인방법)>

먼저 사용자 값이 저장되는 영역은 0x7fffffffe180이다. 

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

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

rbp(0x7fffffffe1b0) - 0x8 = 0x7fffffffe1a8

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

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

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

이로 인해 정상적으로 프로그램이 종료된다. 

 

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$

위의 결과는 canary area에 overwrite가 되지 않았다. 

 

아래의 방법은 canary값을 덮어썼을 경우의 프로그램 동작을 확인할 수 있다. 

: 사용자 입력값이 저장되는 위치와 카나리의 위치는 앞에서 설명한 것과 동일하다. 

 

순서로는

1) 사용자 입력값으로 'A' * 40 + 'B' * 8 을 입력한다. 

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

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

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

-> 0x7fffffffe1a8 영역에 저장된 값은 0x4242424242424242

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

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

5) 그래서 프로그램은 "stack smashing detected"이라는 에러 메시지를 출력한다. 

 

아래의 경우는 카나리 area에 overwritten되었다. 

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.

 

<check the protection techniques of binary files>

checksec.sh에서 출력하는 것.

1) Canary_Do-not-set: No canary found

2) Canary: Canary found

 

<"Checksec.sh" file 에서 카나리 확인하기>

Binary에서는 

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

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

 

<Checksec.sh> 

# check for stack canary support
if readelf -s $1 2>/dev/null | grep -q '__stack_chk_fail'; then
  echo -n -e '\033[32mCanary found   \033[m   '
else
  echo -n -e '\033[31mNo canary found\033[m   '
fi

 

readelf -s ./Canary_Do-not-set |grep __stack_chk_fail

lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ readelf -s ./Canary_Do-not-set |grep __stack_chk_fail
lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$

 

readelf -s ./Canary |grep __stack_chk_fail

lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ readelf -s ./Canary |grep __stack_chk_fail
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (3)
    54: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@@GLIBC_2
lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$

 

process

다음과 같은 방법으로 프로세서의 Canary 설정여부를 확인한다. 

- 일단 Binary의 확인 방식과 비슷하며 전달되는 파일의 경로가 다음과 같이 다르다 

ex) /proc/<PID>/exe

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

 

checksec.sh

# check for stack canary support
if readelf -s $1/exe 2>/dev/null | grep -q 'Symbol table'; then
  if readelf -s $1/exe 2>/dev/null | grep -q '__stack_chk_fail'; then
    echo -n -e '\033[32mCanary found         \033[m  '
  else
    echo -n -e '\033[31mNo canary found      \033[m  '
  fi
else
  if [ "$1" != "1" ] ; then
    echo -n -e '\033[33mPermission denied    \033[m  '
  else
    echo -n -e '\033[33mNo symbol table found\033[m  '
  fi
fi

 

readelf -s /proc/12602/exe |grep '__stack_chk_fail'

lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ ./Canary
Hello world!
^Z
[1]+  Stopped                 ./Canary
lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ ps -ef|grep Canary
lazenca+  12602  11197  0 01:21 pts/4    00:00:00 ./Canary
lazenca+  12604  11197  0 01:21 pts/4    00:00:00 grep --color=auto Canary
lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ readelf -s /proc/12602/exe |grep 'Symbol table'
Symbol table '.dynsym' contains 6 entries:
Symbol table '.symtab' contains 70 entries:
lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ readelf -s /proc/12602/exe |grep '__stack_chk_fail'
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (3)
    54: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@@GLIBC_2
lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$