为何vs编译边出来的程序ebp-4存放的不是第一个局部变量?而是security_cookie——本质上就是存的随机数和ebp异或的值
快速识别
最后那个call就是比较存的随机数和ebp异或的值是否和之前是否一样:
探究security_cookie在程序中的作用
from:https://www.kn0sky.com/?p=66
学习环境:Windows 10 20H2 + Visual Studio 2019
前言
在学习看反汇编程序的时候,使用VS2019编译的release版本的程序里经常会出现__security_cookie
相关的代码
经过查阅资料,了解到这块代码会编译器增加到函数的开始与结束的位置,用来防止栈溢出
好奇心驱使本文来探究一番这个机制是怎么防止栈溢出的
逆向分析:__security_cookie 的作用
随便写一段简单的代码:
#include<stdio.h>
int main(){
char szName[] = "hello selph";
printf("%s",szName);
return 0;
}
使用VS2019编译release版本,调试运行,查看反汇编:
int main()
{
; 这里是函数栈帧操作, 保存ebp, 抬高esp留出栈空间给局部变量使用
00621040 push ebp
00621041 mov ebp,esp
00621043 sub esp,10h
; 这里将一个常量(__security_cookie)拿出来放到eax里
00621046 mov eax,dword ptr [__security_cookie (0623004h)]
; 然后将eax和ebp进行异或操作, 这里的ebp是进入函数前的esp
0062104B xor eax,ebp
; 将计算结果存入ebp-4的位置, 实际上就是存到第一个局部变量里了
0062104D mov dword ptr [ebp-4],eax
; 以下部分是程序内容本身, 本文不对此进行分析, 故省略
...
return 0;
}
; 程序执行结束后
; 将程序开头计算的结果取出来放到ecx
00621073 mov ecx,dword ptr [ebp-4]
; 对printf函数的参数进行平栈操作, C/C++默认是_cdecl函数调用约定,故调用者负责平栈
00621076 add esp,8
; 将取出来的值再次与ebp进异或计算
00621079 xor ecx,ebp
; 清空eax, 准备接收返回值
0062107B xor eax,eax
; 调用函数
0062107D call __security_check_cookie (0621086h)
; 栈帧操作, 还原栈帧到进入调用前
00621082 mov esp,ebp
00621084 pop ebp
00621085 ret
这里在进入程序之前,将进入函数前的栈顶指针esp与一个常量进行异或操作
在位操作里,仅有异或和取反是可以反推的,连续对同一个数异或2次的值=没有进行任何计算的时候的值
这里在程序结束之后,将之前计算的结果再次与进入函数前的栈顶指针esp进行异或操作
此时如果不出意外的话,计算结果应该会变回先前用来异或的那个常量__security_cookie
,值存在ecx里
然后调用函数__security_check_cookie
逆向分析:__security_check_cookie 函数
查看该函数的反汇编:
; 这里将ecx, 也就是经过对同一个值两次异或操作的__security_cookie 与 __security_cookie进行对比
06211086 cmp ecx,dword ptr [__security_cookie (6213004h)]
; 不相同就跳转到 failure
0621108C bnd jne failure (6211091h)
; 相同就返回
0621108F bnd ret
; 无条件跳转到 __report_gsfailure 函数中去
failure:
06211091 bnd jmp __report_gsfailure (6211310h)
网上复制了一段关于BND前缀的介绍:
BND前缀是Intel MPX(内存保护扩展)的一部分,指示应根据BND0至BND3寄存器中指定的界限检查返回目标(或通常为分支目标,因为BND可以应用于任何控制流指令) ,否则将产生异常-表示潜在的堆栈溢出,编程错误或恶意代码攻击。
关于bnd前缀嘛,这里就当他没用吧
__security_check_cookie
函数非常简单,判断__security_cookie
经过进入程序前,程序结束后两次异或操作后,是否为原来的值,为原来的值则表示栈中的该位置没有被各种栈操作影响,也就是说不影响程序接下来的执行,接下来就直接返回
如果该值变了,则表示发生了栈溢出,则跳转到__report_gsfailure
函数中去报告错误,程序将不再继续执行
程序的栈结构变化
进入函数前后的栈的变化:
通过call进入函数,会压入返回地址,然后进入函数代码
在函数代码里,会入栈保存原来的ebp然后抬高esp栈顶指针
是否有防溢出机制__security_cookie
的区别就体现在接下来的操作上了:
- 如果有该机制,则会往ebp-4的位置上存入
__security_cookie
异或值,用于后来再次进行异或检查栈中该位置是否被改变用 - 如果无该机制,则ebp-4开始就是局部变量了
关于 __security_cookie 的值
- __security_cookie 的值是一个随机值,每次运行程序该值都不一样
关于该值是如何进行计算的,可通过搜索编译器CRT下的gs_support.c和gs_cookie.c文件进行查看了解,会进行如下的运算:
- 获得 system time
- 与 GetCurrentProcessId() 异或
- 与 GetCurrentThreadId() 异或
- 与 GetTickCount() 异或
- 与 QueryPerformanceCounter()异或
这里用来计算的值能确保计算出来结果的唯一性,从而能保证cookie的唯一随机性,这里就不进行深入了。
-
__security_cookie 机制并不是会给所有代码都进行生成,只会在存在溢出可能的函数里进行生成
-
这种保护机制是由编译器提供的,可通过编译器设置进行关闭
VS2019的设置位置在:项目右键->属性->C/C++->代码生成->安全检查->启用安全检查(/GS)
总结
__security_cookie 机制通过往局部变量的位置新增一个随机值,在程序开头和结尾分别与进入函数前的esp的值进行异或操作,经过两次异或如果相同,则表示栈中操作不会影响到程序接下来的执行
如果能在栈中的这个位置手工构造出这个用于检测的值,则栈溢出无法避免。