缓冲区溢出
程序尝试向缓冲区写入数据时,超过了缓冲区的容量,导致数据溢出到相邻的内存区域。这种溢出可能破坏程序的堆栈,使程序转而执行其它指令,从而达到攻击的目的。
编译选项:
① 关闭栈保护:-fno-stack-protector
② 开启栈可执行:-z execstack
③ 关闭栈随机化:-no-pie
关闭栈保护和开启栈可执行后,系统将不会对缓冲区溢出进行保护;关闭栈随机化后,函数所在内存地址将与反汇编结果一致(64位机器相较于32位机器,特殊的地方就是,即使关闭了对栈的保护①②,仍然没有关闭栈随机化③这一机制)。
相关寄存器
RBP (Base Pointer)
RBP寄存器主要用于维护函数调用栈帧的结构。在函数被调用时,当前的RBP值通常被保存到栈中,然后RBP被更新为当前栈帧的基址,也就是函数局部变量和参数所在栈空间的起点。这样,函数内部可以通过RBP来访问局部变量和参数,而不会受到上层函数调用栈的影响。RBP在函数调用栈中的位置是相对固定的,它指向当前栈帧的顶部,即函数局部变量和参数的起始位置。
RSP (Stack Pointer)
RSP寄存器则始终指向栈顶,即当前栈空间中最后一个被写入数据的地址。每当有新的数据(如函数参数、局部变量或返回地址)被压入栈中,RSP的值会减少(因为栈是向下生长的);而当数据从栈中弹出时,RSP的值会增加。RSP的变化反映了栈空间的动态使用情况,它在函数调用和返回时频繁更新,以反映栈空间的实时状态。
函数调用过
开始阶段:
结束阶段:
retq用于返回rip寄存器,同时rsp+8(对应call指令)
call指令:call指令会把当前rip的值(0x40055e)入栈,然后把rip的值修改为call指令后面的操作数,这里是0x400526,也就是sum函数第一条指令的地址,这样cpu就会跳转到sum函数去执行。自动把rsp寄存器的值减去8然后把函数的返回地址保存到rsp所指的栈内存位置。
局部变量存在栈的内存中,返回后消失,可以把返回结果存在eax等寄存器中
栈保护(Stack Protection)
栈保护是一种编译器级别的安全机制,旨在防止栈溢出攻击,这类攻击通常利用程序中的缓冲区溢出漏洞来篡改栈上的数据,尤其是函数的返回地址,以执行恶意代码。栈保护的主要技术包括Canary值(哨兵值)和返回指向器保护(Return Pointer Protection,RSP)。
Canary值(哨兵值)
- 工作原理:编译器在栈帧中插入一个随机生成的canary值,位于局部变量和函数返回地址之间。在函数返回前,编译器会检查canary值是否被修改。如果canary值被破坏(通常是因为栈溢出),则认为发生了安全违规,程序将被终止,防止恶意代码执行。
- 实现:在GCC编译器中,栈保护可以通过-fstack-protector或-fstack-protector-all选项启用。
返回指向器保护(Return Pointer Protection)
- Return Pointer Protection:这是一种安全特性,主要在现代处理器和操作系统中实施,用于防止栈溢出攻击。它的核心思想是在函数调用时保护返回地址免受篡改。当函数被调用时,处理器会在栈上保存返回地址,即当前函数结束后的下一条指令的位置。在发生栈溢出时,攻击者可能会尝试修改这个返回地址,以便控制程序流程,执行任意代码。Return Pointer Protection通过硬件支持的方式,在函数调用和返回时检查返回地址的完整性,从而阻止这种类型的攻击。
栈不可执行(Non-Executable Stack)
栈不可执行(NX bit 或 DEP)是一种硬件和操作系统级别的安全机制,其目的是防止栈上的数据被当作指令执行。这可以防止攻击者利用栈溢出漏洞直接在栈上注入和执行恶意代码。
工作原理
- 标记栈为非执行:操作系统将栈标记为不可执行,这意味着试图在栈上执行代码会导致一个保护故障,终止程序。
- 兼容性:为了兼容旧的代码,操作系统通常会保留一小部分栈作为可执行区域,但这部分区域受到严格监控,以防止滥用。
实现
- 硬件支持:现代处理器(如Intel x86-64和AMD64)支持将内存标记为可执行或不可执行。
- 操作系统配置:在Linux中,可以通过内核参数kernel.exec-shield来控制NX bit的使用。在Windows中,数据执行保护(DEP)是默认启用的。
总结
栈保护和栈不可执行是两种重要的安全机制,用于增强程序的健壮性和防止常见的安全攻击,如缓冲区溢出和栈溢出攻击。栈保护通过Canary值和返回指向器保护来检测和防止栈溢出,而栈不可执行通过硬件和操作系统级别的控制来防止栈上的数据被当作指令执行。这些技术共同作用,显著提升了现代软件的安全性。