这几天要做个工具,在某个函数中需要用sscanf将char*转换成GUID变量。转换过程中没遇到什么问题,可是在函数返回处遇到了"Run-Time Check Failure #2"的报错。
GUID devGuid = {0};
char* guid = {"745a17a0-74d3-11d0-b6fe-00a0c90f57da"};
sscanf(guid,"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
&devGuid.Data1,
&devGuid.Data2,
&devGuid.Data3,
&devGuid.Data4[0],
&devGuid.Data4[1],
&devGuid.Data4[2],
&devGuid.Data4[3],
&devGuid.Data4[4],
&devGuid.Data4[5],
&devGuid.Data4[6],
&devGuid.Data4[7]);
return 0;
开始时,怀疑格式化字符串有错误,检查了几次没有发现哪有问题;接着我又怀疑是不是遇到了所谓的格式化字符串漏洞,可是我的程序是x64位,参数基本是通过寄存器传递,不大可能覆盖堆栈中的数据。无奈只能反汇编,发现程序崩溃的位置在一个运行库函数上:
00251403 call @ILT+300(__RTC_CheckEsp) (251131h)
return 0;
00251408 xor eax,eax
}
0025140A push edx
0025140B mov ecx,ebp
0025140D push eax
0025140E lea edx,[ (25143Ch)]
00251414 call @ILT+120(@_RTC_CheckStackVars@8) (25107Dh) <----
00251419 pop eax
0025141A pop edx 上网搜了一圈,发现一篇博文很详细的分析了这个函数的作用:
深入C/C++之基于CheckStackVars的安全检查(VS2008)。结合这篇文章,我猜测十有八九是编译器为变量devGuid前后设置gap----0xCCCCCCCC(引用<软件调试>书中的名词),在执行sscanf的过程中被篡改了。基于这个猜测,我观察了devGuid内存的变化:
上图是执行sscanf前devGuid内存布局:0x0015FCF0开始的0x10B(由蓝笔标示)是devGuid变量自身的内存区;0x0015FCEC和0x0015FD00开始的0x04B(由红笔标示)是由编译器为devGuid分配的gap保护区。
上图是sscanf执行后变量devGuid内存布局。可以看到0x0015FCF0开始的0x10B已经如愿得到了正确的guid值;0x0015FCEC开始的0x04B(黑笔标示)没有改变,依然为0xCCCCCCCC;很不幸的是,0x0015FD00开始的0x04B内存的值被篡改,很明显,元凶是sscanf格式化字符串中最后一个%02x。为什么这么说?sscanf中%x的作用是修改指定地址开始的4B,在这里就是修改从0x0015FCFF开始的4B。这4B中,除了最低1B是devGuid变量的一部分,剩下3B都属于编译器为devGuid分配的gap保护区。
既然知道失败的原因,改动也就呼之欲出了,这里分2种情况:对于Debug版本,针对_RTC_CheckStackVars函数只在退出函数前检测数组是否越界,我们只要在sscanf执行完毕后,恢复devGuid之后的gap值即可;对于Release版本,编译器不会为我们插入gap,并且也不会调用_RTC_CheckStackVars检测数组越界,所以不会跳出"Run time check failure"错误。但是,这并不代表sscanf不会对程序造成潜在影响,所以,我们需要在devGuid之后手动加入一个无用的4B变量,作为与其他有用变量之间的缓冲。(注意堆栈是向下生长的,越早定义的变量,在堆栈中位于越高的位置,我们要保护较变量devGuid位于堆栈中更高处的其他变量,因此,这个无用的4B变量要定义在源码devGuid之前):
int _tmain(int argc, _TCHAR* argv[])
{
DWORD buff; //定义无用的4B变量,用于缓冲
GUID devGuid = {0};
char* guid = {"745a17a0-74d3-11d0-b6fe-00a0c90f57da"};
sscanf(guid,"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
&devGuid.Data1,
&devGuid.Data2,
&devGuid.Data3,
&devGuid.Data4[0],
&devGuid.Data4[1],
&devGuid.Data4[2],
&devGuid.Data4[3],
&devGuid.Data4[4],
&devGuid.Data4[5],
&devGuid.Data4[6],
&devGuid.Data4[7]);
#if _DEBUG
memset((char*)&devGuid+sizeof(GUID),0xCC,sizeof(GUID)); //恢复编译器插入的gap的值
#endif
return 0;
}这样就能顺利通过_RTC_CheckStackVars检查~
本文探讨了使用sscanf进行GUID转换时遇到的Run-Time Check Failure #2错误。通过分析内存布局及_RTC_CheckStackVars函数的工作原理,提出了针对Debug和Release版本的有效解决方案。
9099

被折叠的 条评论
为什么被折叠?



