解决sscanf进行char*转GUID时报"Run-Time Check Failure #2"的错误

本文探讨了使用sscanf进行GUID转换时遇到的Run-Time Check Failure #2错误。通过分析内存布局及_RTC_CheckStackVars函数的工作原理,提出了针对Debug和Release版本的有效解决方案。

    这几天要做个工具,在某个函数中需要用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检查~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值