Canary

学习资料:

学习内容:

  • Linux-Pwn,安全保护机制

由于 stack overflow 而引发的攻击非常普遍也非常古老,相应地一种叫做 canary 的 mitigation 技术很早就出现在 glibc 里,直到现在也作为系统安全的第一道防线存在。

  • PostScriptglibc:The GNU C Library, commonly known as glibc, is the GNU Project’s implementation of the C standard library.

原理

canary 不管是实现还是设计思想都比较简单高效,就是插入一个值,在 stack overflow 发生的 高危区域的尾部,当函数返回之时检测 canary 的值是否经过了改变,以此来判断 stack/buffer overflow 是否发生。

PostScript:gcc 中使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ gcc -fstack-protector test.c
# 启用保护,不过只为局部变量中含有数组的函数插入保护

$ gcc -fstack-protector-all test.c
# 启用保护,为所有函数插入保护

$ gcc -fstack-protector-strong test.c

$ gcc -fstack-protector-explicit test.c
# 只对有明确stack_protect attribute的函数开启保护

$ gcc -fno-stack-protector test.c
# 禁用保护

实现原理

开启 Canary 保护的 stack 结构大概如下:

canary.svg

当程序启用 Canary 编译后,在函数序言部分会取 fs 寄存器 0x28 处的值,存放在栈中 %ebp-0x8 的位置。 这个操作即为向栈中插入 Canary 值,代码如下:

1
2
mov    rax, qword ptr fs:[0x28]
mov    qword ptr [rbp - 8], rax

在函数返回之前,会将该值取出,并与 fs:0x28 的值进行异或。如果抑或的结果为 0,说明 canary 未被修改,函数会正常返回,这个操作即为检测是否发生栈溢出。

1
2
3
4
mov    rdx,QWORD PTR [rbp-0x8]
xor    rdx,QWORD PTR fs:0x28
je     0x4005d7 <main+65>
call   0x400460 <__stack_chk_fail@plt>

这意味可以通过劫持 __stack_chk_fail的 got 值劫持流程或者利用 __stack_chk_fail 泄漏内容 (stack smash)。

进一步来说,对于 Linux 来说,fs 寄存器实际上指向的是当前函数栈的 TLS 结构中的 stack_guard,该值由函数 security_init 进行初始化。初始化的值由 glibc 计算,在进入函数的时候就写入了 Kernel 中。

PostScript

  • GOT (Global Offset Table):是一个存储在数据区的地址表。当被执行程序试图寻找编译时未知的全局变量时,程序就会寻找这个表。

  • 延迟绑定:即函数第一次被用到时才进行绑定。通过延迟绑定大大加快了程序的启动速度。而 ELF 则使用了PLT (Procedure Linkage Table) 的技术来实现延迟绑定。

  • TSL (Transport Layer Security):前身是现在已经弃用的 SSL (Secure Sockets Layer),指的是通过计算机网络提供通信安全性的加密协议。详细的概论信息可参见:SSL/TLS Session。一般的协议结构如下图所示:

    protocol_description.png

    可见 TLS/SSL 协议是介于应用层与传输层之间的协议。下图解释了建立一个 SSL Record 的过程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    graph TB;
    D1{Data}
    subgraph 1.Fragment Data; D2["|......|"]; end
    subgraph 2.Compress Data <通常不会有压缩操作>;
    	D3["|......|MAC|<br>Add Message Authentication Code"];
    end
    subgraph 3.Encrypt data; D4["|..........|<br>cipher text"]; end
    subgraph 4.Add header; D5["|header|...........|<br>TLS record header."]; end
    D1-->D2; D2-->D3; D3-->D4; D4-->D5
    

    较高层则完成 Handshake, Change Cipher Spec, Alert, Application Data 这样四项任务。

    其中握手的过程可以用以下的方式来表示:

     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
    
                   TLS Handshake
    
                   +-----+                              +-----+
                   |     |                              |     |
                   |     |        ClientHello           |     |
                   |     o----------------------------> |     |
                   |     |                              |     |
           CLIENT  |     |        ServerHello           |     |  SERVER
                   |     |       [Certificate]          |     |
                   |     |    [ServerKeyExchange]       |     |
                   |     |    [CertificateRequest]      |     |
                   |     |      ServerHelloDone         |     |
                   |     | <----------------------------o     |
                   |     |                              |     |
                   |     |       [Certificate]          |     |
                   |     |     ClientKeyExchange        |     |
                   |     |    [CertificateVerify]       |     |
                   |     |   ** ChangeCipherSpec **     |     |
                   |     |         Finished             |     |
                   |     o----------------------------> |     |
                   |     |                              |     |
                   |     |   ** ChangeCipherSpec **     |     |
                   |     |         Finished             |     |
                   |     | <----------------------------o     |
                   |     |                              |     |
                   +-----+                              +-----+
    

Experiment

尝试以下的 C 语言程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// test_canary.c
int func(){
	int a[20];
	return a[0];
}

int main(){
	func();
	return 0;
}

使用以下的命令编译该程序生成 ELF 可执行文件:

1
$ gcc -fstack-protector-all -o test_canary test_canary.c

使用 gdb-ex 特性查看生成的可执行文件中 func 函数的汇编代码(也可以使用 objdump -d):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ gdb -batch -ex 'file test_canary' -ex 'disas func'
Dump of assembler code for function func:
   0x000000000000066a <+0>:	push   %rbp
   0x000000000000066b <+1>:	mov    %rsp,%rbp
   0x000000000000066e <+4>:	sub    $0x60,%rsp
   0x0000000000000672 <+8>:	mov    %fs:0x28,%rax
   0x000000000000067b <+17>:	mov    %rax,-0x8(%rbp)
   0x000000000000067f <+21>:	xor    %eax,%eax
   0x0000000000000681 <+23>:	mov    -0x60(%rbp),%eax
   0x0000000000000684 <+26>:	mov    -0x8(%rbp),%rdx
   0x0000000000000688 <+30>:	xor    %fs:0x28,%rdx
   0x0000000000000691 <+39>:	je     0x698 <func+46>
   0x0000000000000693 <+41>:	callq  0x540 <__stack_chk_fail@plt>
   0x0000000000000698 <+46>:	leaveq 
   0x0000000000000699 <+47>:	retq   
End of assembler dump.

可以看到它在 *func+8 的位置在函数栈中加入了 canary。在 *func+30*func+39 的位置会检测 canary 是否发生了改变,并在发生错误时延迟绑定(在 plt 段)了 gibc 函数 <__stack_chk_fail>

绕过技术

泄漏栈中的 Canary

Canary 设计为以字节 \x00 结尾,本意是为了保证 Canary 可以截断字符串。

泄露栈中的 Canary 的思路是覆盖 Canary 的低字节,来打印出剩余的 Canary 部分。 这种利用方式需要存在合适的输出函数,并且可能需要第一溢出泄露 Canary,之后再次溢出控制执行流程。

Experiment

存在漏洞的示例源代码如下:

 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
// ex2.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void getshell(void) {
    system("/bin/sh");
}
void init() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}
void vuln() {
    char buf[100];
    for(int i=0;i<2;i++){
        read(0, buf, 0x200);
        printf(buf);
    }
}
int main(void) {
    init();
    puts("Hello Hacker!");
    vuln();
    return 0;
}