Excuse the ads! We need some help to keep our site up.
List
Canaries
- Canaries 또는 Canary word는 버퍼 오버 플로우를 모니터하기 위해 버퍼와 제어 데이터 사이에 설정 된 값입니다.
- 버퍼 오버플로가 발생하면 Canary 값이 손상되며, Canaries 데이터의 검증에 실패하여, 오버플로에 대한 경고가 출력되고, 손상된 데이터를 무효화 처리됩니다.
Types of canaries
Terminator canaries
- Terminator Canaries는 Canary의 값을 문자열의 끝을 나타내는 문자들을 이용해 생성합니다.
- Terminator Canaries의 값은 NULL (0x00), CR (0x0d), LF (0x0a) 및 EOF (0xff)로 구성되어 있습니다.
- 공격자는 Canaries를 우회하기 위해 위해 Return address를 쓰기 전에 null 문자를 써야 합니다.
- null문자로 인해 Overflow를 방지하게 됩니다.
- strcpy()는 null문자의 위치까지 복사합니다.
- 이 보호에도 불구하고 공격자는 잠재적으로 Canary를 알려진 값으로 겹쳐쓰고 정보를 틀린 값들로 제어해서 Canary 검사 코드를 통과할 수 있다.
Random canaries
Random Canaries는 Canary의 값을 랜덤하게 값이 생성합니다.
일반적으로 익스플로잇을 이용해 Canary를 읽는 것은 논리적으로 불가능하다.
Random Canaries는 프로그램 초기 설정 시에 전역 변수에 Canary 값이 저장된다.
이 값은 보통 매핑되지 않은 페이지에 저장됩니다.
해당 메모리를 읽으려는 시도를 할 경우 segmentation fault가 발생하고 프로그램이 종료됩니다.
공격자가 Canary 값이 저장된 stack address를 알거나 스택의 값을 읽어올수 있는 프로그램이 있다면 Canary의 값을 확인 할 수 있습니다.
Random XOR canaries
- Random XOR Canaries는 Canary의 값을 모든 제어 데이터 또는 일부를 사용해 XOR-scramble 하여 생성합니다.
- 이 방식은 Canary의 값, 제어 데이터가 오염되면 Canary의 값이 틀려집니다.
- Random XOR Canaries는 Random Canaries와 동일한 취약점을 가지고 있습니다.
- 단지 Canary 값을 Stack에서 읽어오는 방법이 조금더 복잡해집니다.
- 공격자는 canary를 다시 인코딩 하기위해 Original Canary 값, 알고리즘, 제어 데티어가 필요합니다.
Example
Source code
#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 ==> 모든 함수 보호
Build Command(Do not set Canary)
gcc -fstack-protector -o canary canary.c
Check to Canary
- 다음과 같이 Canary 값을 확인 할 수 있습니다.
- 사용자 값이 저장되는 영역은 0x7fffffffe180 입니다.
- 해당 영역에 코드에서 할당한 길이의 문자를 저장합니다. ('A' * 32)
- 0x400610 코드 영역에서 rax 레지스터에 rbp - 0x8 영역에 저장된 값을 저장합니다.
- rbp(0x7fffffffe1b0) - 0x8 = 0x7fffffffe1a8
0x7fffffffe1a8 영역에 저장된 값 : 0x3a3b864735c7b300
0x400614 코드 영역에서 rax 레지스터에 저장된 값과 fs:0x28 레지스터에 저장된 값을 xor 연산합니다.
0x40061d 코드 영역에서 rax 레지스터의 값이 0과 같으면 0x400624 영역으로 이동합니다.
- 이로 인해 정상적으로 프로그램이 종료됩니다.
- 사용자 값이 저장되는 영역은 0x7fffffffe180 입니다.
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 값을 덮어썻을 경우의 프로그램 동작을 확인 할 수 있습니다.
- 사용자 입력 값이 저장되는 위치와 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)으로 이동합니다.
- 이로 인해 프로그램에서 아래와 같은 Error 메시지를 출력합니다.
"stack smashing detected"
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
- Checksec.sh에서 다음과 같은 결과를 출력합니다.
Canary_Do-not-set: No canary found
- Canary: Canary found
Not set Canary | gcc -fstack-protector -o Canary_Do-not-set Canary.c lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ checksec.sh --file ./Canary_Do-not-set RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH ./Canary_Do-not-set lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ |
---|---|
Set Canary | gcc -o Canary Canary.c lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ checksec.sh --file ./Canary RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH ./Canary lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ |
How to detect Canary in the "Checksec.sh" file
Binary
- 다음과 같은 방법으로 바이너리의 Canary 설정여부를 확인합니다.
- 'readelf' 명령어를 이용해 해당 파일의 심볼 테이블 정보를 가져와 Canary 설졍여부를 확인합니다.
- 파일의 심볼 테이블에 "__stack_chk_fail"가 있으면 Canary가 적용되었다고 판단합니다.
# 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
lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$ readelf -s ./Canary_Do-not-set |grep __stack_chk_fail lazenca0x0@ubuntu:~/Documents/Definition/protection/Canary$
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' 정보가 있는지 확인합니다.
- Binary의 확인 방식과 비슷하며, 전달되는 파일의 경로가 다음과 같이 다릅니다.
# 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
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$