- [pwn基础]Linux安全机制
- Canary(栈溢出保护)
- 开启关闭Cannary
- Canary的种类
- Terminator canaries(终结者金丝雀)
- Random cannaries(随机金丝雀)
- Random XOR cannaries(随机异或金丝雀)
- 绕过方式总结:
- NX(No-eXecute)
- PIE(ASLR地址随机化)
- 关闭PIE/ALSR(地址随机化)
- PIE/ALSR 检查脚本
- FORTIFY_SOURCE
- 开启关闭FORTIFY_SOURCE
- RELRO
- Partial RELRO(部分RELRO):
- Full RELRO(完全RELRO):
- 开启关闭RELRO
- PWN菜鸡交流小分队
- Canary(栈溢出保护)
Canary:(取名自地下煤矿的金丝雀,因为它能够比旷工更早的发现煤气泄漏,有预警的作用)
,是一种用于对抗栈溢出攻击
的技术,即SSP安全机制
,有时候也叫做Stack cookies
。
Canary的值是栈
上的一个随机数
,在程序启动时随机生成并且保存在比函数返回地址
更低的位置。(看过栈溢出文章的应该都知道,我们栈溢出就是为了覆盖返回地址),由于我们覆盖到返回地址,那么不可避免的会覆盖到这个随机数(Cannary),程序在函数返回前,检查下Cannary是否被覆盖从而判断是否被栈溢出,从而达到保护栈的目的。
#默认情况下是会开启Canary保护的
#禁用栈保护
gcc -fno-stack-protector cannary.c -o cannary
#开启局部保护(只保护局部变量中含有char数组的函数)
gcc -fstack-protector cannary.c -o cannary
#开启全局保护
gcc -fstack-protector-all cannary.c -o cannary
#命令速记
stack-protector(栈保护)
-f(开启)
-fno(关闭)
-all(全部)
Canary的种类
Cannary通常可以分为3中类型,terminator、random、random XOR
,具体的实现有StackGuard、StackShield、Propoliced等
。
大多数缓冲区溢出攻击都基于某些字符串操作,比如说strcpy
,这些操作基本上以字符串终止符结束\x00
。
终止符金丝雀包含 NULL(0x00)、CR(0x0d)、LF(0x0a)和 EOF(0xff)
这四个字符,这四个字符应终止大多数字符串操作,从而使溢出尝试无害。
这可以防止使用 strcpy() 和其他方法的攻击,这些方法在复制空字符时返回。
绕过方法:
攻击者可以使用其已知值覆盖金丝雀,并使用特制值覆盖返回地址,从而绕过这种类型的保护,从而导致代码执行。
当使用非字符串函数来复制缓冲区并且缓冲区内容和缓冲区长度都受攻击者控制时,可能会发生这种情况。
Random cannaries(随机金丝雀)
随机金丝雀是在程序执行时随机选择的。
使用此方法,攻击者无法在程序启动之前通过搜索可执行映像来了解金丝雀值。随机值取自 /dev/urandom(如果可用),如果不支持 /dev/urandom,则通过hash一天中的时间来创建。这种随机性足以阻止大多数预测尝试。
绕过方法:
需要配合信息泄漏漏洞
如果应用程序中存在可用于读取金丝雀值的信息泄漏漏洞,则可以绕过这种保护。
Random XOR cannaries(随机异或金丝雀)
随机异或金丝雀是使用全部或部分控制数据(帧指针+返回地址等)进行异或加干扰的随机金丝雀。
这样,一旦金丝雀或控制数据被破坏,金丝雀值就是错误的,它将导致程序立即终止。
绕过方式总结:泄露内存中的canary,如通过格式化字符串漏洞打印出来
one-by-one爆破,但是一般是多线程的程序,产生新线程后canary不变才行。最高位为00。
劫持_stack_chk_fail函数,canary验证失败会进行该函数,__stack_chk_fail 函数是一个普通的延迟绑定函数,可以通过修改 GOT 表劫持这个函数。
覆盖线程局部存储TLS中的canary,溢出尺寸比较大可以用。同时修改栈上的canary和TLS中的canary.
NX(No-eXecute)
No-eXecute(NX)表示不可执行
,其原理是将数据所在的内存页标识为不可执行。
相当于Windows平台上的DEP(数据执行保护),NX的实现由软件和硬件共同完成
- 硬件层:利用CPU的NX位,对相应页表项中的第63位进行设置
- NX=1,不可知性
- NX=0,可执行
- 软件层面:需要操作系统支持NX以配置页表,涉及相关API
- Windows:
VirtualAlloc
、VirtualProtect
等 - Linux:
mmap
、mprotect
等
- Windows:
if ${readelf} -l "${1}" 2>/dev/null | grep -q 'GNU_STACK'; then
if ${readelf} -l "${1}" 2>/dev/null | grep 'GNU_STACK' | grep -q 'RWE'; then
echo_message '\033[31mNX disabled\033[m ' 'NX disabled,' ' nx="no"' '"nx":"no",'
else
echo_message '\033[32mNX enabled \033[m ' 'NX enabled,' ' nx="yes"' '"nx":"yes",'
fi
else
echo_message '\033[31mNX disabled\033[m ' 'NX disabled,' ' nx="no"' '"nx":"no",'
fi
#判断是否有GNU_STACK,继续判断是否有RWE权限,否则就是开启了NX保护.
绕过方式:
在Linx中,当装载器将程序载入进内存空间后,将程序的.text节
标记为可执行,而其余的数据段.data、.bss、.rodata
以及堆栈
均为不可执行。
所以:无法用传统的修改GOT表来执行shellcode.
绕过的正确方法:
攻击者可以通过代码重用来执行攻击(ret2libc)。
PIE(ASLR地址随机化)
PIE(Position-Independent Executable
) 中文解释为地址无关可执行文件。
也叫做ASLR(地址空间随机化
)全称是叫(Address Space Layout Randomization
)。
这个搞过逆向的应该都不会陌生,比如搞过Windows逆向或者是iOS/macOS逆向的,我们在逆一个程序的时候。用动态调试器去调试他的时候,他的装载地址或入口点地址,每次重启后都是会变化的,
所以每次都需要在IDA中计算出基地址,然后再加上模块的基址才能在调试器中找到真实的内存地址,在iOS中则每次都要image list -o -f
来找装载的地址。
ASLR cat /proc/sys/kernel/randomize_va_space
有三种情况:
0:表示全部关闭
1:表示部分开启
2:表示完全开启(在部分开启的基础上增加了heap的随机化)
PIE 位置无关可执行文件,在应用层的编译器上实现,通过将程序编译为位置无关代码PIC,使程序加载到任意位置,就像是一个特殊的共享库。PIE会一定程度上影响性能。
关闭PIE/ALSR(地址随机化)sudo -s echo 0 > /proc/sys/kernel/randomize_va_space
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
#助记
-pie (开启)
-no-pie(关闭)
PIE/ALSR 检查脚本
if ${readelf} -h "${1}" 2>/dev/null | grep -q 'Type:[[:space:]]*EXEC'; then
echo_message '\033[31mNo PIE \033[m ' 'No PIE,' ' pie="no"' '"pie":"no",'
elif ${readelf} -h "${1}" 2>/dev/null | grep -q 'Type:[[:space:]]*DYN'; then
if ${readelf} -d "${1}" 2>/dev/null | grep -q 'DEBUG'; then
echo_message '\033[32mPIE enabled \033[m ' 'PIE enabled,' ' pie="yes"' '"pie":"yes",'
else
echo_message '\033[33mDSO \033[m ' 'DSO,' ' pie="dso"' '"pie":"dso",'
fi
elif ${readelf} -h "${1}" 2>/dev/null | grep -q 'Type:[[:space:]]*REL'; then
echo_message '\033[33mREL \033[m ' 'REL,' ' pie="rel"' '"pie":"rel",'
else
echo_message '\033[33mNot an ELF file\033[m ' 'Not an ELF file,' ' pie="not_elf"' '"pie":"not_elf",'
fi
FORTIFY_SOURCE
FORTIFY_SOURCE(源码增强),这个其实有点类似与Windows中用新版Visual Studio
进行开发的时候,当你用一些危险函数比如strcpy、sprintf、strcat
,编译器会提示你用xx_s
加强版函数。
FORTIFY_SOURCE本质上一种检查和替换机制,对GCC和glibc的一个安全补丁。
目前支持memcpy
, memmove
, memset
, strcpy
, strncpy
, strcat
, strncat
,sprintf
, vsprintf
, snprintf
, vsnprintf
, gets
等。
#默认Ubuntu16.04下是关闭的,测试发现Ubuntu18.04是开启的
gcc -D_FORTIFY_SOURCE=1 仅仅只在编译时进行检查(尤其是#include <string.h>这种文件头)
gcc -D_FORTIFY_SOURCE=2 程序执行时也会进行检查(如果检查到缓冲区溢出,就会终止程序)
#助记
FORTIFY_SOURCE(代码增强)
-D 1(开启缓冲区溢出攻击检查)
-D 2(开启缓冲区溢出以及格式化字符串攻击检查) ,通过数组大小来判断替换`strcpy、memcpy、memset`等函数名,来防止缓冲区溢出。
RELRO
RELRO全称:Relocation Read-Only
(重定位表只读),之前文章有介绍过动态链接(GOT表、PLT表)需要延迟绑定。
在启用延时绑定时,符号的解析只发生在第一次使用的时候,该过程是通过PLT表进行的,解析完成后,相应的GOT表条目才会修改为正确的函数地址。因此,在延迟绑定的情况下,.got.plt
必须是可写的。攻击者就可以通过篡改地址
劫持程序。
RELRO就是为了解决延迟绑定的安全问题的,之前可以看到我们很轻松的用调试器修改了GOT表,达到了劫持效果,RELRO会把这些延迟绑定表设置成只读,防止来修改。
RELRO一般有两种形式:
Partial RELRO(部分RELRO):Ubuntu 16.04
开始默认的GCC设置,几乎所有的二进制文件都至少有部分RELRO。这样仅仅只能防止全局变量上的缓冲区溢出从而覆盖到GOT。
(.dynamic、.got
)初始化时候标记为只读。
使整个GOT只读,从而无法被覆盖,但是这样会大大增加程序的启动时间,因为需要在启动之前解析所有的符号。
也就是延迟绑定被禁止了,所有的导入符号在开始时候被解析,.got.plt
段会被完全初始化为目标函数的最终地址
,并且被mprotect
标记为只读
,但是.got.plt
会被合并到.got
,也就看不到这些段了,对性能会造成影响。
#关闭RELRO
-z norelro 禁用relro
#开启RELRO
-z lazy 开启Partial RELRO(部分RELRO)
-z now FULL PARTIAL(完全开启RELRO)
#助记
-z (RELRO)
norelro (关闭RELRO)
lazy (开启部分RELRO)
now (完全开启RELRO)
PWN菜鸡交流小分队
最后感谢大家的阅读,本菜鸡也是刚学,文章中如有错误请及时指出。
大家也可以来群里骂我哈哈哈,群里有PWN、RE、WEB大佬,欢迎交流
参考文章 :
http://www.manongzj.com/blog/27-gvafmooopa.html (PWN保护机制详解)
https://www.cnblogs.com/ttxs69/p/pwn_canary.html (PWN之Canary学习)
https://lzeroyuee.cn/2021/02/23/Linux-Pwn-安全机制/ (Linux Pwn - 安全机制)
https://www.redhat.com/en/blog/security-technologies-stack-smashing-protection-stackguard (安全技术:堆栈粉碎保护 (StackGuard))
https://clibre.io/blog/por-secciones/hardening/item/413-proteccion-de-ejecutables-fortify-source (可执行文件保护:FORTIFY_SOURCE)
《CTF权威指南(PWN篇)》