说说栈保护指令
说说栈保护指令
在分析反汇编代码时,经常会看到一些特殊的指令序列。这些指令不是程序的功能逻辑,而是编译器插入的安全保护代码,专门用来防御栈溢出攻击。理解这些指令的作用,对逆向分析很有帮助。
基本工作原理
栈溢出攻击利用的是程序的内存管理漏洞。函数调用时,会在栈上分配局部变量空间。如果向局部变量写入数据时没有检查长度,就可能写超出分配的空间,覆盖栈上的其他数据,比如函数返回地址。
攻击者可以精心构造输入数据,让溢出的数据中包含恶意代码的地址,从而控制程序执行流程。这是很经典的一种攻击方式。
编译器的防护思路很简单:在函数开始时,在栈上放一个“金丝雀”值,函数返回前检查这个值是否被修改。如果被修改了,说明栈可能被破坏了,程序就主动崩溃,不让攻击者得逞。
这个“金丝雀”在Windows里叫__security_cookie,Linux里叫canary,原理都差不多。
反汇编中的识别
在反汇编代码里,这些保护指令有固定的模式。一般出现在函数开头和结尾。
函数开头常见这样的指令:
push ebp mov ebp, esp sub esp, 栈空间大小 mov eax, __security_cookie xor eax, ebp mov [ebp-4], eax这几行代码做了几件事。先是标准函数序言,保存ebp,设置新栈帧。然后分配局部变量空间。关键在最后三行:从全局变量获取安全cookie,和ebp做异或运算,然后把结果保存到栈上特定位置(通常是[ebp-4])。
这个异或操作是为了让cookie值更随机。单纯用固定值不够安全,和栈地址异或后,每个函数的cookie值都不同,更难预测。
函数结尾的检查代码:
mov ecx, [ebp-4] xor ecx, ebp call @__security_check_cookie@4 mov esp, ebp pop ebp retn先取出之前保存的值,同样和ebp异或。然后调用检查函数,这个函数内部会比较计算出的值和全局cookie是否一致。如果不一致,就触发异常处理。
检查通过后,才是正常的函数返回流程。
编译器的实现细节
不同版本的编译器,实现有细微差别。VC++ 2003开始引入这个功能,当时叫Buffer Security Check。后来不断改进。
cookie的生成有讲究。程序启动时,系统会生成一个随机数作为全局cookie。在启用了ASLR(地址空间布局随机化)的系统上,这个值更难预测。
保存位置也经过考虑。通常放在局部变量区和返回地址之间。这样,如果局部缓冲区溢出,要覆盖返回地址,必须先覆盖cookie。这就起到了预警作用。
检查函数__security_check_cookie内部实现很简单:
cmp ecx, ___security_cookie jnz short failure retn failure: jmp ___report_gsfailure比较,相等就返回,不等就跳转到错误处理。错误处理函数会记录崩溃信息,然后终止程序。
逆向分析的意义
看到这些指令,能知道几件事:
第一,程序是用VC++编译的,而且开了/GS编译选项。这个选项默认是开启的,但有些追求性能的程序可能会关掉。
第二,函数有局部缓冲区。没有局部变量的小函数,编译器可能不插入保护代码。有缓冲区操作的函数,保护代码出现的概率大。
第三,可以辅助识别函数边界。保护代码通常紧挨着函数序言和结语,找到它们有助于划分函数范围。
第四,在漏洞分析中,如果攻击要成功,必须绕过这个保护。现代漏洞利用技术,有些会先泄露cookie值,或者用其他方法绕过检查。
局限性
这个保护机制不是万能的。有几个弱点:
如果溢出不覆盖返回地址,而是修改函数指针、异常处理结构等其他控制流,可能绕过检查。
如果攻击者能获取cookie值,就可以在溢出数据中包含正确的cookie,让检查通过。这需要信息泄露漏洞配合。
有些溢出是堆溢出,不是栈溢出,这个机制就无效了。
所以现在都是组合防御,除了栈cookie,还有ASLR、DEP、CFG等各种技术一起用。
调试中的表现
调试时,如果触发了保护,会看到程序崩溃在__report_gsfailure。错误信息通常是"Stack cookie instrumentation code detected a stack-based buffer overrun"。
在OllyDbg或x64dbg里,可以在检查函数设断点,观察cookie值的变化。这对于分析漏洞利用很有帮助。
有时候攻击会故意触发这个保护,让程序崩溃,实现拒绝服务。这也是需要考虑的攻击面。
手动分析技巧
分析加了保护的程序,可以注意几点:
找到全局cookie变量,通常在.data或.rdata段。它的值在程序启动时确定,运行时不变。
注意函数序言中cookie的计算方式。老版本可能只是简单mov,新版本会做更多运算增加随机性。
在IDA中,可以写脚本自动识别这些模式,标记出有栈保护的函数。
动态调试时,可以在检查函数设条件断点,监控哪些函数频繁触发检查,这可能提示有问题的代码区域。
总结
栈保护指令是编译器安全加固的一部分。虽然增加了一点性能开销,但大大提高了攻击门槛。在逆向分析中,这些指令提供了额外的信息,有助于理解程序结构和安全特性。
