一些Windows API导致的Crash以及使用问题总结(API的AV失败,可以用try catch捕捉后处理)
RegQueryValueEx
gethostbyname/getaddrinfo
_localtime64
FindFirstFile/FindNextFile
VerQueryValue
CreateFileMapping相关
SetDllDirectory
Windows API就没有问题、没有BUG吗?答案是否定的!代码都是写出来,怎么可能完全没有问题呢?下面我们就来看看目前发现有哪些Windows API是有问题的,或者说使用上面有误区的。
1、RegQueryValueEx
首先看看这个API,获取注册表里面的信息,这个API本身没有问题,暂时还没见到崩溃在这个API里面的。不过这个API的使用上面有一些小技巧需要注意。使用不当会引发一些意想不到的问题甚至崩溃(API的具体使用请查阅MSDN,下面不再赘述)。
问题主要发生在我们获取注册表里面的字符串值的情况下,看看这样一段代码:
DWORD dwType = REG_SZ;
DWORD dwcbData = 0;
LONG lRet =::RegQueryValueExW(hKey, L"InstallTimeTest", NULL, &dwType, NULL, &dwcbData);
if (ERROR_SUCCESS == lRet
&& REG_SZ == dwType)
{
PBYTE pBuffer = new BYTE[dwcbData];
我们一般在枚举注册表时,使用这样的代码来探测,某个注册表项需要的存储空间是多大,然后分配空间,再进行读取,一般情况下是没有问题的。
但是再看看这样的一个设置注册表的操作:
DWORD dwType = REG_SZ;
wchar_t szInstalTime[5] = {L'1', L'2', L'3', L'4', L'5'};
DWORD dwcbData = sizeof(szInstalTime); // 字节数
LONG lRet =::RegSetValueExW(hKey, L"InstallTimeTest", NULL, dwType, LPBYTE(szInstalTime), dwcbData);
if (ERROR_SUCCESS == lRet)
{
bRet = true;
}
奇葩了吧,是的,它设置了一个字符串到注册表里面,字符串的总长度是5,字节数是10字节(宽字符,不包括结尾的0),传给RegSetValueEx的长度也是10,函数执行成功了,似乎成功写入了,但是看看注册表里面的情况:
竟然没有结尾的0,此时我们再用上面的方式去读取的时候,它首先会告诉你需要10字节的空间,你分配10个字节,然后再去读就会读到一个不以0结尾的字符串存放到你的缓冲区里面,之后你再对缓冲区进行字符串的各种操作,读写,计算长度等等,会发生什么我想就不用我多说了。
也许会有人说,微软不是说了使用RegSetValueEx时,如果是设置字符串的话,传入的长度要包括结尾的0吗?是不是RegSetValueEx用错了,Yes,你没说错,但是问题是这种使用方法也是可以的,不排除有人恶意为之来使得你的程序崩溃,如果一个安全软件轻易就这样崩溃了……有没有一种人生观被彻底摧毁的敢脚。
好吧,说回来,还是看看我们应该怎样应对这种异常情况吧,其实知道了原因我想方法也很简单了,就是每次分配的时候,多分配一些内存(宽字符要多分配2个字节,对于多行字符串就要多分配4个字节了,因为它是以两个0结尾的)。再有一种使用方法是这样的,如果你大概知道需要多长的空间,而且你对数据没读全不是很关心。那么你提前分配一定字节的空间(譬如N字节),然后调用RegQueryValueExW时传入空间大小时,传入N-2或者N-4字节,也可以解决这个问题。
2、gethostbyname/getaddrinfo
在hosts文件里面含有一些特殊构造的数据时,这两个API已经被证明必然会crash,其实原因就在于它里面有一处调用没有对指针判空就调用wcslen来计算长度,可以通过反汇编mswsock模块来研究这个问题。而其解决方法也很直接,那就是直接写一个此类API的代理函数,然后把这种crash捕获住,发生异常时直接返回失败即可,因为这就是一个简单的AV异常,因此捕获之后不会造成其它的问题,是安全的捕获。
代理函数可以这样写:
struct hostent FAR * PASCAL FAR gethostbyname_safe(IN const char FAR * name)
{
if (NULL == name)
return NULL;
__try
{
return ::gethostbyname(name);
}
__except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
return NULL;
}
}
至于getaddrinfo函数则可以如法炮制。
3、_localtime64
这并不是一个API咯,而是微软扩展的CRT函数,用来把一个time_t时间转换成本地时间并存放到tm结构中。使用中发现如果给它传入了一个不正确的值,会引发crash。最早是蓝光的兄弟报告了这个函数的问题(其实这个函数和_localtime32是类似的,另外一个函数是_localtime,这个函数则是在编译期根据是否宏定义了_USE_32BIT_TIME_T来决定是调用_localtime32还是调用_localtime64)。
你可以试试给这几个函数传入0之类的异常数据(负数,超大的数,64位版本超过After 23:59:59, December 31, 3000)试试……
其实微软也知道这个问题,在MSDN上面有这样一段话(后来想想微软也挺坑的):
These functions validate their parameters. If timer is a null pointer, or if the timer value is negative, these functions invoke an invalid parameter handler, as described in Parameter Validation. If execution is allowed to continue, the functions return NULL and set errno to EINVAL.
它建议使用_set_invalid_parameter_handler来设置错误参数异常处理函数,不过很遗憾,其实这种方法也有漏洞,它虽然能捕获这种错误,但是当系统滥用这种方法的时候,譬如多个模块调用这个函数来设置自己的错误处理函数,这种方法变得非常不可靠。譬如说,模块1调用了_set_invalid_parameter_handler,然后模块2又调用了_set_invalid_parameter_handler,过了一段时间模块2卸载了,这时错误处理函数还指向模块2里面,此时如果模块1再发生参数错误就会导致执行一个非法地址的错误处理函数,问题更严重了,模块2卸载前把错误处理函数恢复如何呢?但是如果还有模块3呢?怎么保证恢复的错误处理函数指针是完全可靠的呢?总的来说,此方法不太可靠,不建议使用。
更好的解决方案是按照自己的需求,对_localtime系列函数再包装一下,先帮他们检查一下参数,然后再传给真正的_localtime函数处理,以规避这种问题。可以参考下面的这个函数的检测方式(你完全可以根据自己的需求来实现这个函数)。
inline time_t FixupTime64Range(const time_t time)
{
time_t tmp_time = time;
if(tmp_time < 0 ||// underflow
tmp_time > (_MAX__TIME64_T - 14 * 60 * 60)) // overflow
{
tmp_time = 0; // reset time to 0
}
return tmp_time;
}
/* number of seconds from00:00:00, 01/01/1970 UTC to 23:59:59. 12/31/3000 UTC */
#define _MAX__TIME64_T 0x793406fffi64
14 * 60 * 60 用于控制范围,目前最早的时区是UTC+14(http://zh.wikipedia.org/zh-hant/UTC%2B14)
4、FindFirstFile/FindNextFile
这两个组合API大部分人都用过吧,用来枚举文件和文件夹的,一般使用很难发现问题,但是在安全领域,用来进行文件扫描时,因为时间一般比较长,很容易发生扫描的目标文件夹或者磁盘里面发生文件被删除的情况,此时这两个API很容易就crash了,从实践来看,发生在FindNextFile里面更多一些,可以使用如下方法进行规避(封装一个SafeFindNextFile函数,FindFirstFile也可以类似为之)。如果是比较重要的逻辑,这两个API发生了crash之后,建议重置逻辑(譬如如果你正在进行病毒扫描,可以保存初始状态,然后重新启动扫描)。
BOOL bRet = FALSE;
__try
{
bRet = FindNextFile(hFindFile, lpFindFileData);
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
}
return bRet;
5、VerQueryValue
这个API的crash,相信肯定有不少人遇到过,这个函数通常用于从文件的版本资源中获取一些类似于文件版本之类的信息,不过这个API一般要和GetFileVersionInfo和GetFileVersionInfoSize配合调用,而且它的使用会有一些小小的麻烦(主要在于对第二个参数传递的字符串的理解),这里因为不是讲解这个API的使用,因此就不再啰嗦了,有兴趣的可以查阅MSDN上面对这个API的解说。
至于这API会发生crash的原因主要是因为它内部的一些实现上面不够健壮,在获取一些特殊的样本的版本信息时,它必然crash,说到特殊样本,这也是为什么一般使用的情况,基本不会发生问题,主要是安全软件在进行样本扫描时遇到的机率比较大。
解决方法和上面的FindNextFile类似,把这种异常catch住自己实现一个安全的函数,因为一般取文件的版本,描述等等信息都是辅助显示的,并不是关键逻辑,因此使用这种方式是比较简单和安全的。
inline BOOL APIENTRY VerQueryValueWSafe(const LPVOID pBlock, LPTSTR lpSubBlock, LPVOID *lplpBuffer, PUINT puLen)
{
BOOL bRet = FALSE;
__try
{
bRet = VerQueryValueW(pBlock, lpSubBlock, lplpBuffer, puLen);
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
bRet = FALSE;
}
return bRet;
}
6、CreateFileMapping相关
其实CreateFileMapping这个API本身内部并没有见过crash,其出现问题一般都是一些使用上的问题以及一些无法避免的问题导致在后面的使用中出现crash。
使用内存映射文件的一个常见场景是随机读取文件内容时,此时会将一个文件通过CreateFileMapping和MapViewOfFile映射到内存之后进行随机读写。在映射完成之后,即将读写之前,此时如果文件所在的硬盘卷被卸载,或者映射的是一个移动设备(包括U盘,移动硬盘等等)文件,而移动设备被拔掉,或者一个网络文件,网络异常断开等等情况,会立即引发一个crash,错误ID类似:0xC0000006: In page error.
究其原因,在于内存映射文件的实现机制有点类似虚拟内存,他也保留了一个地址空间区域,在需要的时候才会把相关内容提交到物理存储器,也就是说并不是真的一开始就把整个文件放到内存里面去了,而是在需要的时候产生一个类似“缺页中断”响应,去外部存储器上面把文件的相关内容真正映射到物理存储器,如果那个文件没了,或者U盘被拔掉了,那么系统也处理不了,只好抛出异常了。
其实这个问题MSDN上面已经有个说明了:http://msdn.microsoft.com/en-us/library/windows/desktop/aa366801(v=vs.85).aspx,也给出了一种解决方案。
DWORD dwLength;
__try
{
dwLength = *((LPDWORD) lpMapAddress);
}
__except(GetExceptionCode()==EXCEPTION_IN_PAGE_ERROR ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
// Failed to read from the view.
}
既然避免不了的问题,让它优雅的结束也许也是一种不错的方式。处理这个问题的另外一种方式是不使用内存映射,而使用ReadFile之类的API去读取,可以通过判断ReadFile的返回值来识别这种情况。
7、SetDllDirectory
这个API的问题是因为微软的某个版本的kernel32.dll里面对SetDllDirectory的实现有缺陷,它内部调用的线程同步的API调错了,造成使用这个API就会出现访问违例的crash。后来微软修正了kernel32.dll里面的这个问题。但是不排除外面还存在类似的问题。
解决方法最好的一种是就是不使用这个API,从使用这个API的目的来,仅仅是为了指定动态链接库的搜索路径,因此可以使用LoadLibraryEx来替代,这个API可指定LOAD_WITH_ALTERED_SEARCH_PATH临时改变搜索路径。其实出于安全考虑,我的建议是任何时候都应该使用绝对路径来加载DLL。
http://blog.csdn.net/magictong/article/details/11617135
一些Windows API导致的Crash以及使用问题总结(API的AV失败,可以用try catch捕捉后处理)的更多相关文章
- 一些Windows API导致的Crash以及使用问题总结
RegQueryValueEx gethostbyname/getaddrinfo _localtime64 FindFirstFile/FindNextFile VerQueryValue Crea ...
- 【Python学习】由于windows环境问题导致的不能安装某些需要VC编译的插件
由于windows环境问题导致的不能安装某些需要VC编译的插件 下载地址:http://www.lfd.uci.edu/~gohlke/pythonlibs/ 安装方法: 在CMD中输入 pip in ...
- Windows Azure入门教学系列 (七):使用REST API访问Storage Service
本文是Windows Azure入门教学的第七篇文章. 本文将会介绍如何使用REST API来直接访问Storage Service. 在前三篇教学中,我们已经学习了使用Windows Azure S ...
- 案例:Standby RAC遭遇ORA-1157,1111,1110导致实例crash处理
案例:Standby RAC遭遇ORA-1157,1111,1110导致实例crash处理 环境:RHEL 6.5 + Oracle RAC 11.2.0.4 + Dataguard 今天在实验环境的 ...
- 打开Voice Over时,CATextLayer的string对象兼容NSString和NSAttributedString导致的Crash(二解决思路3)
续前一篇:打开Voice Over时,CATextLayer的string对象兼容NSString和NSAttributedString导致的Crash(二解决思路2)ok,到这里已经能够锁定范围了, ...
- 关于CALayer导致的crash问题
push到一个页面进行绘图时,设置如下: CALayer * layer = [CALayer layer]; layer.frame = CGRectMake(, , , ); layer.dele ...
- 构建你的长寿命的API第1部分:规范驱动的API开发
构建你的长寿命的API第1部分:规范驱动的API开发 这篇文章是由MuleSoft的Mike Stowe在nginx.conf 2016公布的演示文稿改编的.第一部分重点是规范驱动的API开发. 第二 ...
- 移动IP 它最初设想每个人都在编写应用层(7)API而不是传输层(4)API 对于QUIC,连接的标识符不是“套接字”(源/目标端口/地址协议组合)的传统概念,而是分配给连接的64位标识符
小结: 1. 因为您对OSI模型的教育中缺少的一点是,它最初设想每个人都在编写应用层(7)API而不是传输层(4)API.应该有像应用程序服务元素之类的 东西,它们可以以标准方式处理文件传输和消息传递 ...
- 【API管理 APIM】APIM中对后端API服务的DNS域名缓存问题
问题描述 在使用API Management来进行API管理时,当我们后端的API DNS IP地址发生改变或者是API的域名发生改变后,通过APIM请求访问的还是是旧的域名或者IP地址,这是因API ...
随机推荐
- TensorFlow 实战(三)—— 实现常见公式
tf.reduce_mean (求向量的均值)等价于 1N∑i=1Nxi 1. 对权值矩阵进行 l2 正则 def variable_with_weight_loss(shape, stddev, w ...
- HDU 4313 Matrix 树形dp
题意: 给定n个点的树,m个黑点 以下n-1行给出边和删除这条边的费用 以下m个黑点的点标[0,n-1] 删除一些边使得随意2个黑点都不连通. 问删除的最小花费. 思路: 树形dp 每一个点有2个状态 ...
- 配置文件——App.config文件读取和修改
作为普通的xml文件读取的话,首先就要知道怎么寻找文件的路径.我们知道一般配置文件就在跟可执行exe文件在同一目录下,且仅仅在名称后面添加了一个.config 因此,可以用Application.Ex ...
- R 语言学习(二)—— 向量
1. 入门 将摄氏度转化为华氏度 >> 27*1.8+32 [1] 80.6 [1]:表示数字的向量索引号,在 R 语言中任何一个数字都看作一个向量. 向量化 >> temp ...
- 在.net core中一个简单的加密算法
using System; using System.Text; //System.Security下加密算法的命名空间 using System.Security.Cryptography; nam ...
- matlab 二元函数的画法
plot:画线(curve,二维空间以及三维空间) surf:画面(surface,一般在三维空间) 1. surf 绘图函数 surf 是 surface 的缩写,表示表面(显然至少三维图像才会有表 ...
- python 教程 第五章、 函数
第五章. 函数 定义语句后面要加冒号 1) 定义函数 def sayHello(): print 'Hello World!' sayHello() 2) 变量作用域 LEGB原则 L本地 ...
- WPF应用程序内嵌网页
原文:WPF应用程序内嵌网页 版权声明:本文为博主原创文章,转载请注明出处. https://blog.csdn.net/shaynerain/article/details/78160984 WPF ...
- Java异常处理错误
Java异常处理错误 研究发现,在编译阶段的最佳时机错误,序之前.然而,编译期间并不能找出全部的错误,余下的问题必须在执行阶段解决.这就须要错误源通过某种方式把适当的信息传给某个接收者,该接收者知道怎 ...
- 操作系统hosts文件
为了便于北京和大连两个更好的测试系统.该公司专门申请一个域名:大连r \\ u0026 D侧只需要部署(我方系统全权负责在大连研发.所以在大连并列比较的部署方面easy--不要忘记,该项目比我们实际做 ...