最近用到了 checksec,想整理整理一些 Linux 防溢出的保护措施,因为容易弄混。。
Checksec是一个bash脚本,用于检查可执行文件的属性(如PIE,RELRO,PaX,Canaries,ASLR,Fortify Source),例如:
USER@NAME:~# checksec a
[*] '/a'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Arch 不用说了,是架构。
Relocation Read-Only (RELRO) 可以使程序某些部分成为只读的。它分为两种,Partial RELRO 和 Full RELRO,即 部分RELRO 和 完全RELRO。
部分RELRO 是 GCC 的默认设置,几乎所有的二进制文件都至少使用 部分RELRO。但是从攻击者角度来看呢,部分RELRO 不堪一击,因为它仅仅使 GOT 存储在 bss 段之前,这样仅仅只能防止全局变量上的缓冲区溢出从而覆盖 GOT。
完全RELRO 使整个 GOT 只读,从而无法被覆盖,但这样会大大增加程序的启动时间,因为程序在启动之前需要解析所有的符号。
gcc -o a a.c // 默认情况下,是Partial RELRO
gcc -z norelro -o a a.c // 关闭,即No RELRO
gcc -z lazy -o a a.c // 部分开启,即Partial RELRO
gcc -z now -o a a.c // 全部开启,即Full RELRO
这种栈保护措施在栈帧中任何局部缓冲区与栈状态之间存储一个特殊的金丝雀值,也称哨兵值,它是程序每次运行时随机产生的。在恢复寄存器状态之前和从函数返回之前,程序检查这个金丝雀值是否被该函数的某个操作或者该函数调用的某个函数的某个操作改变了,如果发现异常程序会中止。
最近的 GCC 版本可能会试图自动插入这种溢出检测值。
gcc -o a a.c // GCC 可能会试图自动插入这种溢出检测值
gcc -fno-stack-protector -o a a.c //禁用栈保护
gcc -fstack-protector -o a a.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
gcc -fstack-protector-all -o a a.c //启用堆栈保护,为所有函数插入保护代码
NX 即 Non-executable,类似 windows 的 DEP。顾名思义,就是不能执行数据所在的页面。
gcc -o a a.c // 默认情况下,开启NX保护
gcc -z execstack -o a a.c // 禁用NX保护
gcc -z noexecstack -o a a.c // 开启NX保护
地址空间布局随机化,Address-Space Layout Randomization,每次运行时,程序的不同部分的位置都会被加载到不同的区域。
而在 linux 上,有三种不同的设置:
0 - 表示关闭进程地址空间随机化。
1 - 表示将mmap的基址,stack和vdso页面随机化。
2 - 表示在1的基础上增加栈(heap)的随机化。
可以使用下面的命令更改其值:
sudo -s echo 0 > /proc/sys/kernel/randomize_va_space
ASLR 并不负责代码段和数据段的随机化,这由 PIE 负责,但是只有在开启 ASLR 之后,PIE 才会生效。
可以看一下这个https://blog.csdn.net/plus_re/article/details/79199772
Position Independent Code,位置无关代码
gcc -o test test.c // 默认情况下,不开启PIE
gcc -fpie -pie -o test test.c // 开启PIE,此时强度为1
gcc -fPIE -pie -o test test.c // 开启PIE,此时为最高强度2
gcc -fpic -o test test.c // 开启PIC,此时强度为1,不会开启PIE
gcc -fPIC -o test test.c // 开启PIC,此时为最高强度2,不会开启PIE
以下来自https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html
-fpic
Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine. Such code accesses all constant addresses through a global offset table (GOT). The dynamic loader resolves the GOT entries when the program starts (the dynamic loader is not part of GCC; it is part of the operating system). If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC, 28k on AArch64 and 32k on the m68k and RS/6000. The x86 has no such limit.)
Position-independent code requires special support, and therefore works only on certain machines. For the x86, GCC supports PIC for System V but not for the Sun 386i. Code generated for the IBM RS/6000 is always position-independent.
When this flag is set, the macros __pic__ and __PIC__ are defined to 1.
-fPIC
If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on AArch64, m68k, PowerPC and SPARC.
Position-independent code requires special support, and therefore works only on certain machines.
When this flag is set, the macros __pic__ and __PIC__ are defined to 2.
-fpie
-fPIE
These options are similar to -fpic and -fPIC, but the generated position-independent code can be only linked into executables. Usually these options are used to compile code that will be linked using the -pie GCC option.
-fpie and -fPIE both define the macros __pie__ and __PIE__. The macros have the value 1 for -fpie and 2 for -fPIE.