二进制程序的一些常见保护。
目录
1、ASLR
aslr是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。也就是说程序每次执行时,栈、堆、库的位置都不一样。
效果如下所示:
//环境:gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04)
#include
int main()
{
int x=0;
printf("%08lX\n",&x);//打印变量x在栈中的地址
}
结果如下:
ex@ex:~/test$ ./a.out
7FFEDE67D2E4
ex@ex:~/test$ ./a.out
7FFFCD469C84
ex@ex:~/test$ ./a.out
7FFD6501FE14
ex@ex:~/test$ ./a.out
7FFE10E29F94
ex@ex:~/test$ ldd a.out
linux-vdso.so.1 (0x00007ffc2f368000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdf818c9000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdf81ebc000)
ex@ex:~/test$ ldd a.out
linux-vdso.so.1 (0x00007fff33fbc000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b8d00a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8b8d5fd000)
ex@ex:~/test$ ldd a.out
linux-vdso.so.1 (0x00007fff4a1bd000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff285a6f000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff286062000)
ex@ex:~/test$ ldd a.out
linux-vdso.so.1 (0x00007ffc4d39e000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f988480b000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9884dfe000)
ASLR(Address space layout randomization)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。据研究表明ASLR可以有效的降低缓冲区溢出攻击的成功率,如今Linux、FreeBSD、Windows等主流操作系统都已采用了该技术。
Linux检查是否开启ASLR,可用下面命令:
cat /proc/sys/kernel/randomize_va_space
如果/proc/sys/kernel/randomize_va_space里的值为0时,则表示ASLR关闭。
可用下面的命令手动关闭:
echo -n "0" > /proc/sys/kernel/randomize_va_space
-n表示不输出换行
Windows10强制开启ALSR,但是可以通过更改PE文件头里的Optional Header结构体里的DLL Characteristics字段,将DLL Characteristics字段的第7位设置为0即可关闭栈的ASLR,但是系统动态库的ALSR是强制开启的,也可以使用PEtools进行更改,如下图:

2、DEP
数据执行保护,默认栈的权限是可读、可写、不可执行。
gcc编译器默认开启该保护,编译时加上 -z execstack 则关闭该保护,-z noexecstack 是开启该保护。(-z 是传参给链接器,execstack是使目标文件的栈可以执行)
3、PIE
PIE(position-independent executable, 地址无关可执行文件)技术就是一个针对代码段.text, 数据段.*data,.bss等固定地址的一个防护技术。同ASLR一样,应用了PIE的程序会在每次加载时都变换加载基址。
gcc编译器 编译时加上-fpie -pie即开启 PIE,原先是默认不开启的,现在好像是默认开启,编译时加上 -no-pie 关闭PIE保护。没有开启的情况下.text, 数据段.*data,.bss等段的地址是固定的。
4、Stack Guard
编译器对栈溢出的一种保护机制,在函数执行时,先在栈上放置一个随机标识符,函数返回前会先检查标识符是否被修改,如果被修改则直接触发中断来中止程序,可以有效的防止栈溢出攻击。
gcc默认开启 Stack Guard ,编译时加上 -fno-stack-protector 参数就可以关闭 Stack Guard(CANARY)。
5、RELRO
relro 是一种用于加强对 binary 数据段的保护的技术。relro 分为 partial relro 和 full relro。参数 -z norelro 是关闭RELRO保护。
Partial RELRO
现在gcc 默认编译就是 partial relro,参数是 -z relro
部分区块(比如:.init_array .fini_array .jcr .dynamic .got)在被动态装载(初始化)后,就被标记为只读区块。
Full RELRO
gcc编译参数是-z relro -z now
拥有 Partial RELRO 的所有特性
所有导入的符号都在 startup time 被解析
关闭fastbin
#include <malloc.h>
// mallopt(1, 0);
mallopt(M_MXFAST, 0);
禁止SYS_execve系统调用
#include <sys/prctl.h>
// prctl(38, 1LL, 0LL, 0LL, 0LL);
prctl(PR_SET_NO_NEW_PRIVS, 1LL, 0LL, 0LL, 0LL);
在Linux中,PR_SET_NO_NEW_PRIVS 当一个进程或者子进程设置了PR_SET_NO_NEW_PRIVS属性,则其不能访问一些无法share的操作,这是kernel 3.5后加的feature。
AddressSanitizer 内存访问越界检查
需要安装的库:libasan.x86_64,新版本的gcc可能还需要安装libubsan,虽然说AddressSanitizer是gcc的一部分,但这两库默认是没有安装的。
使用方法很简单,只要在编译程序时加上-fsanitize=address -fno-omit-frame-pointer两个编译选项即可,需要说明的是要使用系统自带的内存管理库,不能使用第三方的内存管理库,因为这个功能要拦截malloc,free等标准函数。gcc几个常用编译选项如下:
-fsanitize=address #开启地址越界检查功能
-fno-omit-frame-pointer #开启后,可以出界更详细的错误信息
-fsanitize=leak #开启内存泄露检查功能
Undefined Behavior Sanitizer 未知行为检测
UBSan有一些处理程序或魔术功能入口点,它们被称为未定义的行为。编译仪器代码通过适当注入检查;如果检查代码检测到UB,则调用这些处理程序。
它的一个gcc编译标志(-fsanitize=undefined),用于添加运行时检测代码。