Canaries 보호기법 이론 및 실습
List of Contents 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 Canary
1
2
3
4
5
6
7
8
9
#include <stdio.h>
void main ( int argc , char ** argv ) {
char Overflow [ 32 ];
printf ( "Hello world! \n " );
gets ( Overflow );
}
Build command **Option **
💡 gcc -fstack-protector –param ssp-buffer-size=N xx.c ==> byte 변경 gcc -fstack-protector-all xx.c ==> 모든 함수 보호 Build Command(Do not set Canary)
1
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 영역으로 이동합니다. 이로 인해 정상적으로 프로그램이 종료됩니다. Do not overwrite the value in the canary area.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
gdb - peda$ di sassemble main
Dump of assembler code for function main :
0 x00000000004005d6 <+ 0 > : push rbp
0 x00000000004005d7 <+ 1 > : mov rbp , rsp
0 x00000000004005da <+ 4 > : sub rsp , 0x40
0 x00000000004005de <+ 8 > : mov DWORD PTR [ rbp - 0x34 ], edi
0 x00000000004005e1 <+ 11 > : mov QWORD PTR [ rbp - 0x40 ], rsi
0 x00000000004005e5 <+ 15 > : mov rax , QWORD PTR fs : 0x28
0 x00000000004005ee <+ 24 > : mov QWORD PTR [ rbp - 0x8 ], rax
0 x00000000004005f2 <+ 28 > : xor eax , eax
0 x00000000004005f4 <+ 30 > : mov edi , 0x4006b4
0 x00000000004005f9 <+ 35 > : call 0x400490 < puts@plt >
0 x00000000004005fe <+ 40 > : lea rax ,[ rbp - 0x30 ]
0 x0000000000400602 <+ 44 > : mov rdi , rax
0 x0000000000400605 <+ 47 > : mov eax , 0x0
0 x000000000040060a <+ 52 > : call 0x4004c0 < gets@plt >
0 x000000000040060f <+ 57 > : nop
0 x0000000000400610 <+ 58 > : mov rax , QWORD PTR [ rbp - 0x8 ]
0 x0000000000400614 <+ 62 > : xor rax , QWORD PTR fs : 0x28
0 x000000000040061d <+ 71 > : je 0x400624 < main + 78 >
0 x000000000040061f <+ 73 > : call 0x4004a0 < __stack_chk_fail@plt >
0 x0000000000400624 <+ 78 > : leave
0 x0000000000400625 <+ 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
0 x000000000040060f in main ()
gdb - peda$ x / 10 gx 0x7fffffffe180
0 x7fffffffe180: 0 x4141414141414141 0x4141414141414141
0 x7fffffffe190: 0 x4141414141414141 0x4141414141414141
0 x7fffffffe1a0: 0 x00007fffffffe200 0x3a3b864735c7b300
0 x7fffffffe1b0: 0 x0000000000400630 0x00007ffff7a2d830
0 x7fffffffe1c0: 0 x0000000000000000 0x00007fffffffe298
gdb - peda$ c
Continuing.
Breakpoint 2 , 0x0000000000400610 in main ()
gdb - peda$ i r rbp
rbp 0x7fffffffe1b0 0x7fffffffe1b0
gdb - peda$ x / gx 0x7fffffffe1b0 - 0x8
0 x7fffffffe1a8: 0 x3a3b864735c7b300
gdb - peda$ ni
0 x0000000000400614 in main ()
gdb - peda$ i r rax
rax 0x3a3b864735c7b300 0x3a3b864735c7b300
gdb - peda$ ni
0 x000000000040061d in main ()
gdb - peda$ i r rax
rax 0x0 0x0
gdb - peda$ ni
0 x0000000000400624 in main ()
gdb - peda$ x / 2 i $ rip
= > 0 x400624 < main + 78 > : leave
0 x400625 < main + 79 > : ret
다음과 같이 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” The value is overwritten in the canary area.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
0 x000000000040060f in main ()
gdb - peda$ x / 10 gx 0x7fffffffe180
0 x7fffffffe180: 0 x4141414141414141 0x4141414141414141
0 x7fffffffe190: 0 x4141414141414141 0x4141414141414141
0 x7fffffffe1a0: 0 x4141414141414141 0x4242424242424242
0 x7fffffffe1b0: 0 x0000000000400600 0x00007ffff7a2d830
0 x7fffffffe1c0: 0 x0000000000000000 0x00007fffffffe298
gdb - peda$ c
Continuing.
Breakpoint 2 , 0x0000000000400610 in main ()
gdb - peda$ i r rbp
rbp 0x7fffffffe1b0 0x7fffffffe1b0
gdb - peda$ x / gx 0x7fffffffe1b0 - 0x8
0 x7fffffffe1a8: 0 x4242424242424242
gdb - peda$ ni
0 x0000000000400614 in main ()
gdb - peda$ i r rax
rax 0x4242424242424242 0x4242424242424242
gdb - peda$ ni
0 x000000000040061d in main ()
gdb - peda$ i r rax
rax 0x61061c8ecf993242 0x61061c8ecf993242
gdb - peda$ ni
0 x000000000040061f in main ()
gdb - peda$ x / 3 i $ rip
= > 0 x40061f < main + 73 > : call 0x4004a0 < __stack_chk_fail@plt >
0 x400624 < main + 78 > : leave
0 x400625 < main + 79 > : ret
gdb - peda$ c
Continuing.
*** stack smashing detected *** : / home / lazenca0x0 / Documents / Definition / protection / Canary / Canary terminated
Program received si gnal SI GABRT , 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
1
2
3
root $ 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
Set Canary
1
2
3
root $ 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
How to detect Canary in the “Chekcsec.sh” file Binary 다음과 같은 방법으로 바이너리의 Canary 설정여부를 확인합니다. ‘readelf’ 명령어를 이용해 해당 파일의 심볼 테이블 정보를 가져와 Canary 설졍여부를 확인합니다. 파일의 심볼 테이블에 ”__stack_chk_fail”가 있으면 Canary가 적용되었다고 판단합니다. Checksec.sh - line 156
1
2
3
4
5
6
# 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**
1
$ readelf -s ./Canary_Do-not-set |grep __stack_chk_fail
**readelf -s ./Canary grep __stack_chk_fail**
1
2
3
$ 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
Process 다음과 같은 방법으로 프로세서의 Canary 설정여부를 확인합니다. Binary의 확인 방식과 비슷하며, 전달되는 파일의 경로가 다음과 같이 다릅니다. 추가된 동작은 ‘/proc//exe' 파일에 'Symbol table' 정보가 있는지 확인합니다. Checksec.sh - line 215
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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’**
1
2
3
4
5
6
7
8
9
10
11
$ 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
$ readelf -s /proc/12602/exe |grep 'Symbol table'
Symbol table '.dynsym' contains 6 entries:
Symbol table '.symtab' contains 70 entries:
$ 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