# GCC 堆栈保护技术 - [技术简介](#技术简介) - [编译参数](#编译参数) - [保护技术检测](#保护技术检测) ## 技术简介 Linux 中有各种各样的安全防护,其中 ASLR 是由内核直接提供的,通过系统配置文件控制。NX,Canary,PIE,RELRO 等需要在编译时根据各项参数开启或关闭。未指定参数时,使用默认设置。 #### CANARY 启用 CANARY 后,函数开始执行的时候会先往栈里插入 canary 信息,当函数返回时验证插入的 canary 是否被修改,如果是,就停止运行。 #### FORTIFY FORTIFY 的选项 `-D_FORTIFY_SOURCE` 往往和优化 `-O` 选项一起使用,以检测缓冲区溢出的问题。 下面是一个简单的例子: ```c #include void main() { char str[3]; strcpy(str, "abcde"); } ``` ```text $ gcc -O2 fortify.c $ checksec --file a.out RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No 0 0a.out $ gcc -O2 -D_FORTIFY_SOURCE=2 fortify.c In file included from /usr/include/string.h:639:0, from fortify.c:1: In function ‘strcpy’, inlined from ‘main’ at fortify.c:4:2: /usr/include/bits/string3.h:109:10: warning: ‘__builtin___memcpy_chk’ writing 6 bytes into a region of size 3 overflows the destination [-Wstringop-overflow=] return __builtin___strcpy_chk (__dest, __src, __bos (__dest)); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ $ checksec --file a.out RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH Yes 2 2a.out ``` 开启优化 `-O2` 后,编译没有检测出任何问题,checksec 后 FORTIFY 为 No。当配合 `-D_FORTIFY_SOURCE=2`(也可以 `=1`)使用时,提示存在溢出问题,checksec 后 FORTIFY 为 Yes。 #### NX No-eXecute,表示不可执行,其原理是将数据所在的内存页标识为不可执行,如果程序产生溢出转入执行 shellcode 时,CPU 会抛出异常。其绕过方法是 ret2libc。 #### PIE PIE(Position Independent Executable)需要配合 ASLR 来使用,以达到可执行文件的加载时地址随机化。简单来说,PIE 是编译时随机化,由编译器完成;ASLR 是加载时随机化,有操作系统完成。 我们通过实际例子来探索一下 PIE 和 ASLR: ```c #include void main() { printf("%p\n", main); } ``` 首先我们关闭 ASLR,使用 `-pie` 进行编译: ``` # echo 0 > /proc/sys/kernel/randomize_va_space # gcc -m32 -pie random.c -o a.out # checksec --file a.out RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No 0 2 a.out # ./a.out 0x5655553d # ./a.out 0x5655553d ``` 我们虽然开启了 `-pie`,但是 ASLR 被关闭,入口地址不变。 ``` # ldd a.out linux-gate.so.1 (0xf7fd7000) libc.so.6 => /usr/lib32/libc.so.6 (0xf7dd9000) /lib/ld-linux.so.2 (0xf7fd9000) # ldd a.out linux-gate.so.1 (0xf7fd7000) libc.so.6 => /usr/lib32/libc.so.6 (0xf7dd9000) /lib/ld-linux.so.2 (0xf7fd9000) ``` 可以看出动态链接库地址也不变。然后我们开启 ASLR: ``` # echo 2 > /proc/sys/kernel/randomize_va_space # ./a.out 0x5665353d # ./a.out 0x5659753d # ldd a.out linux-gate.so.1 (0xf7727000) libc.so.6 => /usr/lib32/libc.so.6 (0xf7529000) /lib/ld-linux.so.2 (0xf7729000) # ldd a.out linux-gate.so.1 (0xf77d6000) libc.so.6 => /usr/lib32/libc.so.6 (0xf75d8000) /lib/ld-linux.so.2 (0xf77d8000) ``` 入口地址和动态链接库地址都变得随机。 接下来关闭 ASLR,并使用 `-no-pie` 进行编译: ``` # echo 0 > /proc/sys/kernel/randomize_va_space # gcc -m32 -no-pie random.c -o b.out # checksec --file b.out RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 2 b.out # ./b.out 0x8048406 # ./b.out 0x8048406 # ldd b.out linux-gate.so.1 (0xf7fd7000) libc.so.6 => /usr/lib32/libc.so.6 (0xf7dd9000) /lib/ld-linux.so.2 (0xf7fd9000) # ldd b.out linux-gate.so.1 (0xf7fd7000) libc.so.6 => /usr/lib32/libc.so.6 (0xf7dd9000) /lib/ld-linux.so.2 (0xf7fd9000) ``` 入口地址和动态库都是固定的。下面开启 ASLR: ``` # echo 2 > /proc/sys/kernel/randomize_va_space # ./b.out 0x8048406 # ./b.out 0x8048406 # ldd b.out linux-gate.so.1 (0xf7797000) libc.so.6 => /usr/lib32/libc.so.6 (0xf7599000) /lib/ld-linux.so.2 (0xf7799000) # ldd b.out linux-gate.so.1 (0xf770a000) libc.so.6 => /usr/lib32/libc.so.6 (0xf750c000) /lib/ld-linux.so.2 (0xf770c000) ``` 入口地址依然固定,但是动态库变为随机。 所以在分析一个 PIE 开启的二进制文件时,只需要关闭 ASLR,即可使 PIE 和 ASLR 都失效。 > ASLR(Address Space Layout Randomization) > >关闭:`# echo 0 > /proc/sys/kernel/randomize_va_space` > >部分开启(将 mmap 的基址,stack 和 vdso 页面随机化):`# echo 1 > /proc/sys/kernel/randomize_va_space` > >完全开启(在部分开启的基础上增加 heap的随机化:`# echo 2 > /proc/sys/kernel/randomize_va_space` #### RELOAD RELRO 设置符号重定向表为只读或在程序启动时就解析并绑定所有动态符号,从而减少对 GOT(Global Offset Table)的攻击。 ## 编译参数 各种安全技术的编译参数如下: 安全技术 | 完全开启 | 部分开启 | 关闭 --- | --- | --- | Canary | -fstack-protector-all | -fstack-protector | -fno-stack-protector NX | -z noexecstack | | -z execstack PIE | -pie | | -no-pie RELRO | -z now | -z lazy | -z norelro 关闭所有保护: ```text gcc hello.c -o hello-L -fstack-protector -z execstack -no-pie -z norelro ``` 开启所有保护: ```text gcc hello.c -o hello-S -fstack-protector-all -z noexecstack -pie -z now ``` - FORTIFY - `-D_FORTIFY_SOURCE=1`:仅在编译时检测溢出 - `-D_FORTIFY_SOURCE=1`:在编译时和运行时检测溢出 ## 保护机制检测 有许多工具可以检测二进制文件所使用的编译器安全技术。下面介绍常用的几种: #### checksec ```text $ checksec --file /bin/ls RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 5 15 /bin/ls ``` #### peda 自带的 checksec ```text $ gdb /bin/ls gdb-peda$ checksec CANARY : ENABLED FORTIFY : ENABLED NX : ENABLED PIE : disabled RELRO : Partial ```