一些Windows API导致的Crash以及使用问题总结(API的AV失败,可以用try catch捕捉后处

时间:2021-11-09 04:16:14

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,函数执行成功了,似乎成功写入了,但是看看注册表里面的情况:

一些Windows API导致的Crash以及使用问题总结(API的AV失败,可以用try catch捕捉后处

竟然没有结尾的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上面有这样一段话(后来想想微软也挺坑的):