0x04

对齐

计算机系统对基本数据类型的可允许地址做出了一些限制,要求某种类型的对象的地址必须是某个值k的倍数。这种对其限制简化了处理器和存储器系统之间的接口的硬件设计。无论数据是否对齐,x86-64硬件都能正确工作。不过,Intel还是建议要对齐数据以提高内存系统的性能。对齐原则是任何K字节的基本对象的地址必须是K的倍数。

编译器在汇编代码中放入命令,指明全局数据所需的对齐:

.align 8

就保证了它后面的数据会从以8为倍数的地址处开始。Intel建议要对齐数据以提高内存系统的性能,对齐原则是任何K字节的基本对象的地址必须是K的倍数。

分配存储器的库例程(如malloc)的设计必须使得它们返回的指针能满足最糟糕情况的对其限制,通常是4或者8。

存储器的越界引用和缓冲区溢出

C对于数组引用不进行任何边界检查,而且局部变量和状态信息(如寄存器值和返回指针)都存放在栈中,这两种情况结合到一起就能导致严重的错误,一个对越界的数组元素的写操作破坏了存储在栈中的状态信息。然后当程序使用这个被破坏的状态,试图重新加载寄存器或执行ret指令时,就会出现严重的错误。

void echo() {
char buf[4];
gets(buf);
puts(buf);
}

其产生的汇编代码:

echo:
pushl %ebp
movel %esp, %ebp
subl $20, $esp
pushl %ebx Save %ebx
addl $-12, %esp
leal -4(%ebp), %ebx
pushl %ebx
call gets Call gets

当程序进行函数调用时,经常说的是先将函数压栈,当函数调用结束后,再出栈。会用到三种寄存器:

void main(void) {
fun()
printf("end");
}

EIP:存储CPU下次要执行的指令的地址。也就是调用完fun函数后,让CPU知道应该执行main函数中的printf语句。

EBR:存储栈的栈底指针,通常叫做栈基址,是一开始进行fun函数调用前,由ESP传递给EBP的。

ESP:存储在调用函数fun后,栈的栈顶。并且始终指向栈顶。

当fun函数结束后,系统根据EIP寄存器里存储的地址,CPU就能够知道函数调用完,下一步应该做什么,也就是执行printf。EBP寄存器存储栈底地址,而这个地址是由ESP在函数调用前传递给EBP的,等到调用结束,EBP会把其地址再次传回给ESP。

所有对buf[4]buf[7]的写都会导致%ebp的保存值被破坏,当程序随后试图以它为栈指针进行恢复时,所有后来的栈引用都会是非法的。所有对buf[8]buf[11]的写都会导致返回地址被破坏,当在函数结尾执行ret指令时,程序会返回到错误的地址。

对抗缓冲区溢出攻击

栈随机化

栈随机化的思想使得栈的位置在程序每次运行时都有变化。程序开始时,在栈上分配一段0~n字节之间的随机大小的空间。分配的范围n必须足够大,才能获得足够多的栈地址变化,但是又要足够小,不至于浪费程序太多空间。

Linux系统中,栈随机化已经变成了标准行为。是更大的一类技术中的一种,这类技术称为地址空间布局随机化。每次运行时程序的不同部分,包括程序代码、库代码、栈、全局变量和堆数据,都会被加载到内存的不同区域。

栈破坏检测

最近的GCC版本在产生的代码中加入了一种栈保护者机制,来检测缓冲区越界。其思想是在栈帧中任何局部缓冲区与栈状态之间存储一个特殊的值,也称哨兵值,是在程序每次运行时随机产生的。在恢复寄存器状态和从函数返回前,程序检查这个金丝雀值是否被该函数的某个操作或者该函数调用的某个函数的某个操作改变了。

限制可执行代码区域

消除攻击者向系统中插入可执行代码的能力,限制哪些内存区域能够存放可执行代码,只有保存编译器产生的代码的那部分才需要是可执行的,其他部分可以被限制为只允许读和写。

支持变长栈帧

有些函数,需要的局部存储是变长的。为了管理变长栈帧,x86-64代码使用寄存器%rbp作为帧指针。在较早版本的x86代码中,每个函数都使用了帧指针,而现在,只在栈帧长可变的情况下才使用。

posted @   Pannnn  阅读(304)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
-->
点击右上角即可分享
微信分享提示