stackoverflow
栈溢出
原理
栈溢出主要是通过程序内部的危险函数实现对栈上空间的可控写入和读取, 进而实现提权等操作.
核心考虑是内部存在会溢出的代码段.
常见的可溢出的函数:
gets()
: 该函数从标准输入中读取一行内容, 会一直读取知道读取到换行符, 不进行长度检查
scanf()
: 在读取%s
的情况下会读取一个字符串, 不检查长度
strcpy()
: 将一个字符串复制到目标位置, 不会检查长度
strcat()
: 将一个字符串连接到目标字符串后, 不检查目标缓冲区大小
memcpy()
: 内存复制, 同样不检查目标缓冲区大小
sprintf()
: 将格式化输出写入字符串的函数, 不限制输出的长度
read()
: 从文件描述符中读取内容, 会限制长度, 但是可能超出缓冲区长度
修复手段
- 使用更安全的函数, 如: fgets() 替代 gets() snprintf() 替代 sprintf() strncpy() 替代 strcpy() strncat() 替代 strcat() 这些安全替代函数允许指定最大输入长度, 从而防止缓冲区溢出
- NX(DEP)保护 不可执行保护, 将数据对应的内存(这里主要指栈上)标记为不可执行. 这样使得直接注入 shellcode 难度增大, 选择使用程序中现有的代码来进行攻击, 如 ret2libc, rop 等.
- Canary保护
栈上canary, 在栈上放置一个canary, 当进行栈上内容覆盖时, 会将这部分也覆盖掉而被检查掉.
Canary因为自身设计原因末尾必须是
0x00
, 所以可以进行爆破破解. - ASLR(Address Space Layout Randomization)保护 地址空间随机化, 在这里主要指对栈, 堆, libc地址的随机化. 在攻击中可以先泄露出某相关地址, 然后根据固定的偏移值来确定具体的地址.
- PIE(Position Independent Executable)保护 位置无关执行保护, 能使得可执行文件在内存中任意地址加载和执行, 主要改变的是程序自身加载的位置. 以此可以修改程序本身中可利用的gadget的地址. 在攻击中与ASLR相同, 可以先泄露地址而后使用偏移值计算具体地址.
- RELRO(ReLocation Read-Only)保护
分为partial RELRO与full RELRO
- partial RELRO: 在这种模式下,某些段(如.dynamic和.got)在初始化后会被标记为只读. 这意味着全局偏移表(GOT)是不可写的, 从而提高了安全性.
- full RELRO: 这种模式比partial RELRO更严格. 除了partial RELRO的特性外, 它还禁止了延迟绑定, 所有的导入符号在程序启动时就会被解析. .got .plt段会被完全初始化为目标函数的最终地址, 并被标记为只读. 在full RELRO模式下, link_map和_dl_runtime_resolve的地址也不会被装入. 开启full RELRO可能会对程序启动时的性能造成影响, 但它能有效防止GOT被篡改.
但是在实际运行中, 对于部分保护, 由于操作系统需要按照页来写入, 也就是无法修改单个表项的内容, 也就是必须在所有表项都解析成功之后再控制其不能写入. 那么在攻击时, 基本上不会达成这样的条件. 而如果开启了全部保护, 那么程序会很卡.