CTF-All-In-One/doc/4.3_gcc.md
2017-08-16 15:07:55 +08:00

197 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# GCC 堆栈保护技术
- [技术简介](#技术简介)
- [编译参数](#编译参数)
- [保护机制检测](#保护机制检测)
## 技术简介
Linux 中有各种各样的安全防护,其中 ASLR 是由内核直接提供的通过系统配置文件控制。NXCanaryPIERELRO 等需要在编译时根据各项参数开启或关闭。未指定参数时,使用默认设置。
#### CANARY
启用 CANARY 后,函数开始执行的时候会先往栈里插入 canary 信息,当函数返回时验证插入的 canary 是否被修改,如果是,就停止运行。
#### FORTIFY
FORTIFY 的选项 `-D_FORTIFY_SOURCE` 往往和优化 `-O` 选项一起使用,以检测缓冲区溢出的问题。
下面是一个简单的例子:
```c
#include<string.h>
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
PIEPosition Independent Executable需要配合 ASLR 来使用以达到可执行文件的加载时地址随机化。简单来说PIE 是编译时随机化由编译器完成ASLR 是加载时随机化,由操作系统完成。
我们通过实际例子来探索一下 PIE 和 ASLR
```c
#include<stdio.h>
void main() {
printf("%p\n", main);
}
```
首先我们关闭 ASLR使用 `-pie` 进行编译:
```text
# 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 被关闭,入口地址不变。
```text
# 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
```text
# 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` 进行编译:
```text
# 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
```text
# 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 都失效。
> ASLRAddress Space Layout Randomization
>
>关闭:`# echo 0 > /proc/sys/kernel/randomize_va_space`
>
>部分开启(将 mmap 的基址stack 和 vdso 页面随机化):`# echo 1 > /proc/sys/kernel/randomize_va_space`
>
>完全开启(在部分开启的基础上增加 heap的随机化`# echo > /proc/sys/kernel/randomize_va_space`
#### RELRO
RELROReLocation Read-Only设置符号重定向表为只读或在程序启动时就解析并绑定所有动态符号从而减少对 GOTGlobal 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=2`:在编译时和运行时检测溢出
## 保护机制检测
有许多工具可以检测二进制文件所使用的编译器安全技术。下面介绍常用的几种:
#### 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
```