TestFloat修复文档
运行“TestFloat.exe”程序弹出异常对话框:
是R6002运行时错误,查找相关资料得出的结论是:
引用------------------------------------------------------------------------------------
C 运行时错误 R6002
错误消息
未加载浮点支持
未链接必需的浮点库。
出错原因:
该程序通过选项(如 /FPi87,该选项要求有协处理器)被编译或链接,但该程序运行在一台未安装协处理器的计算机上。
printf_s 或 scanf_s 函数的格式字符串包含浮点格式规范,而该程序不包含任何浮点值或变量。
编译器仅当必要时才通过加载浮点支持以最小化程序大小。编译器无法检测到格式字符串中的浮点格式规范,因此编译器未加载必要的浮点例程。
使用浮点参数以符合浮点格式规范,或在程序的其他地方执行浮点赋值。该操作将导致加载浮点支持。
在由混合语言编写的程序中,当程序进行链接时在 FORTRAN 库之前指定了 C 库。重新链接并最后指定 C 库。
-------------------------------------------------------------------------------------------
一言以蔽之,就是“浮点支持库”没有加载的原因导致“TestFloat.exe”运行出错。接下来就是定位出出错代码,最后再进行修复。
由于是点击按钮后才发生的运行时错误,因此定位按钮的CLICK事件最好不过,我尝试下消息断点但是没有成功。当然还有其他方法,这里提出两种方法。
第一, RUN跟踪定位。
使用插件“MapConv”将“.map”文件导入OD辅助分析。开启“RUN跟踪”并“添加所有函数过程入口”,F9运行。点击按钮后弹出异常对话框,此时打开RUN跟踪记录,右键“统计模块”。在统计窗口中要从下向上翻阅(次数为1的在最下面,只关心次数为1的)高亮地址。
向上找到第7个高亮地址时,会在反汇编窗口里发现“OnBnClickedBtnCrash”字样的注释。很肯定这个地址开始就是按钮的CLICK处理过程,F2下上断点。
第二, 查找参考文本串。
“查找可疑文本串”永远都是一个有效的方法,因为是与浮点有关的异常,当发现串"PI * 2 = %f"时就应该查看其上下文代码,也很容易确定按钮的CLICK处理过程了。
定位出错位置并查出原因
在00420D20处下断,OD重新载入,点击按钮中断。F8单步执行,当执行到:
“00420D81 call <__snwprintf>”并步过时异常抛出,可以肯定的是错误就在这里,也就是对函数__snwprintf调用发生了异常。究其原因,不是因为调用形式有错误,而是因为“浮点支持库”没有加载,在将浮点型转换成字符串时便发生了异常;而“浮点支持库”没有加载的原因是某些版本的编译器对浮点运算的处理不同而导致的。
修复错误
由于对函数__snwprintf调用发生了异常,便想到覆盖对此函数的调用,转向对正确函数的调用。
于是就想到给“TestFloat.exe”附加一个“补丁”,我这里使用一个“float.dll”文件,它包含对<__snwprintf>函数的正确调用方法“pure”。给“TestFloat.exe”增加导入表信息:float.dll/pure,并且让地址00420D81的调用转向对“pure”的调用即可,而pure函数包含了对<__snwprintf>函数的正确调用。如下是“float.dll”工程的主要代码,语句“float f=0.1f;”是为了确保“浮点库”被加载。
// float.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include "wchar.h"
extern "C" _declspec(dllexport) __stdcall int pure(wchar_t* buffer,size_t count,const wchar_t *format,double f);
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
float f=0.1f; //确保“浮点库”被加载
return TRUE;
}
extern "C" _declspec(dllexport) __stdcall int pure(wchar_t* buffer,size_t count,const wchar_t *format,double f);
{
_snwprintf(buffer,count,format,f);
return 0;
}
接下来要做的是为“TestFloat.exe”增加导入表信息:float.dll/pure,直接使用PeEditor即可快速完成(为了让PeEditor正确找到pure方法的地址,先复制“float.dll”文件到system32目录下,待导入成功后删除即可)。
<记下FirstThunk的值,我这里是0003B0D9>
OD载入“TestFloat.exe”,将地址00420D81开始的指令汇编为:
00420D81 |. FF15 D9B04300 call dword ptr [<&float.pure>] ; float.pure
00420D87 |. 90 nop
00420D88 |. 90 nop
注:
pure方法采用stdcall调用形式,堆栈修复由子程序自身完成,故原本的堆栈修复指令可以NOP掉。
保存修改到可执行文件即可,重新运行修复之后的程序弹出PI*2的结果:
不过这个答案是不对的,但是能修复原因,我不知道为什么,希望高手点拨一二。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)