GetFileVersionInfo 获取文件版本信息错误原因分析

来源:互联网 发布:nvh分析软件 编辑:程序博客网 时间:2024/05/18 01:45

概述

这两天遇到一个奇怪的问题:在一个进程中,通过GetFileVersionInfo去获取一个绝对路径文件的版本号时, 实际路径对应文件根本不存在,却获取到了版本号信息。在仔细分析GetFileVersionInfo内部实现后,真相终于大白,为了以后能更好的使用这个API,故把分析过程记录下来。

问题背景

一进程在判断是否需要打补丁时,需要去读取文件的版本号,读取路径是从一个库中配置的。在测试的库中,配置的条件是判断C:\Windows\system32\gdiplus.dll的版本号小于某个补丁,就说明用户机器上面需要安装该补丁。 

该进程运行后,分析到库的条件时,就会通过GetFileVersionIno获取C:\Windows\system32\gdiplus.dll模块的版本号来进行判断, 实际上在C:\Windows\system32\目录下面是不存在gdiplus.dll文件,但是GetFileVersionInfo这个函数却返回了成功,而且可以通过VerQueryValue能够查询出版本号, 从而导致扫描出的结果不符合预期。

为什么GetFileVersionInfo获取不存在路径的版本号信息会出错呢?

查找原因

查看msdn,GetFileVersionInfo:

GetFileVersionInfo function

Retrieves version information for the specified file.

Syntax

BOOL WINAPI GetFileVersionInfo(

  _In_        LPCTSTR lptstrFilename,

  _Reserved_  DWORD dwHandle,

  _In_        DWORD dwLen,

  _Out_       LPVOID lpData

);

Parameters

lptstrFilename [in]

Type: LPCTSTR

The name of the file. If a full path is not specified, the function uses the search sequence specified by the LoadLibrary function.

msdn 来看,如果输入不是全路径,则会通过LoadLibrary去搜索标识的文件名。

进一步查看GetFileVersionInfo内部实现:

BOOLAPIENTRYGetFileVersionInfoA(        LPSTR lpstrFilename,        DWORD dwHandle,        DWORD dwLen,        LPVOID lpData        ){    UNICODE_STRING FileName;    ANSI_STRING AnsiString;    NTSTATUS Status;    BOOL bStatus;    RtlInitAnsiString(&AnsiString, lpstrFilename);    Status = RtlAnsiStringToUnicodeString(&FileName, &AnsiString, TRUE);    if (!NT_SUCCESS(Status)) {        SetLastError(Status);        return FALSE;    }   <span style="color:#ff6666;"> bStatus = GetFileVersionInfoW(FileName.Buffer, dwHandle, dwLen, lpData);</span>    RtlFreeUnicodeString(&FileName);    return bStatus;}

GetFileVersionInfoW的实现:

BOOLAPIENTRYGetFileVersionInfoW(                   LPWSTR lpwstrFilename,                   DWORD dwHandle,                   DWORD dwLen,                   LPVOID lpData                   ){    VERHEAD *pVerHead;    VERHEAD16 *pVerHead16;    HANDLE hMod;    HANDLE hVerRes;    HANDLE h;    UINT   dwTemp;    BOOL bTruncate, rc;    UNREFERENCED_PARAMETER(dwHandle);    // Check minimum size to prevent access violations    if (dwLen < sizeof(((VERHEAD*)lpData)->wTotLen)) {        SetLastError(ERROR_INSUFFICIENT_BUFFER);        return (FALSE);    }    dwTemp = SetErrorMode(SEM_FAILCRITICALERRORS);    <span style="color:#ff0000;">hMod = LoadLibraryEx(lpwstrFilename, NULL, LOAD_LIBRARY_AS_DATAFILE);</span>    SetErrorMode(dwTemp);    if (hMod == NULL) {        // Allow 16bit stuff        __try {            dwTemp = MyExtractVersionResource16W( lpwstrFilename, &hVerRes );        } __except( EXCEPTION_EXECUTE_HANDLER ) {            dwTemp = 0 ;        }        if (!dwTemp)            return (FALSE);        if (!(pVerHead16 = GlobalLock(hVerRes))) {            SetLastError(ERROR_INVALID_DATA);            GlobalFree(hVerRes);            return (FALSE);        }        __try {            dwTemp = (DWORD)pVerHead16->wTotLen;            if ((dwTemp * 3) > dwLen) {                //                // We are forced to truncate.                //                dwTemp = dwLen/3;                bTruncate = TRUE;            } else {                bTruncate = FALSE;            }            // Now mem copy only the real size of the resource.  (We alloced            // extra space for unicode)            memcpy((PVOID)lpData, (PVOID)pVerHead16, dwTemp);            if (bTruncate) {                // If we truncated above, then we must set the new                // size of the block so that we don't overtraverse.                ((VERHEAD16*)lpData)->wTotLen = (WORD)dwTemp;            }            rc = TRUE;        } __except( EXCEPTION_EXECUTE_HANDLER ) {            rc = FALSE;        }        GlobalUnlock(hVerRes);        GlobalFree(hVerRes);        return rc;    }   <span style="color:#ff0000;"> if (((hVerRes = FindResource(hMod, MAKEINTRESOURCE(VS_VERSION_INFO), VS_FILE_INFO)) == NULL) ||        ((pVerHead = LoadResource(hMod, hVerRes)) == NULL)) {        rc = FALSE;</span>    } else {        __try {            dwTemp = (DWORD)pVerHead->wTotLen;            if (((dwTemp * 2) + sizeof(VER2_SIG)) > dwLen) {                // We are forced to truncate.                //                // dwLen = UnicodeBuffer + AnsiBuffer.                //                // if we try to "memcpy" with "(dwLen/3) * 2" size, pVerHead                // might not have such a big data...                //                dwTemp = (dwLen / 2) - sizeof(VER2_SIG);                bTruncate = TRUE;            } else {                bTruncate = FALSE;            }            // Now mem copy only the real size of the resource.  (We alloced            // extra space for ansi)            memcpy((PVOID)lpData, (PVOID)pVerHead, dwTemp);            // Store a sig between the raw data and the ANSI translation area so we know            // how much space we have available in VerQuery for ANSI translation.            *((PDWORD)((ULONG_PTR)lpData + dwTemp)) = VER2_SIG;            if (bTruncate) {                // If we truncated above, then we must set the new                // size of the block so that we don't overtraverse.                ((VERHEAD*)lpData)->wTotLen = (WORD)dwTemp;            }            rc = TRUE;        } __except( EXCEPTION_EXECUTE_HANDLER ) {            rc = FALSE;        }    }    <span style="color:#ff0000;">FreeLibrary(hMod);</span>    return (rc);}

从win2k源码来看,

GetFileVersionInfoW先是检测输入BuffSize是否足够大, 然后直接通过LoadLibraryExlpwstrFilename作为DATAFILE进行加载; 如果加载成功则通过FindResource查找其模块资源,然后取出资源数据。

到了这里,我们应该可以得出结论,GetFileVersionInfo依赖于LoadLibraryEx函数的执行结果。对于Dll的加载,最重要的是要搞清其搜索路径;到哪里去搞清?当然是问问微软了!MSDN上面有一篇专门讲DLL搜索顺序的文章(Dynamic-Link Library Search Order http://msdn.microsoft.com/en-us/library/ms682586(v=vs.85).aspx)。

按照msdn的说法,应用程序可以通过完整路径,使用重定向,或者manifest来控制加载哪个dll, 否则对于输入是相对路径文件名,其搜索路径如下:

在安全DLL搜索模式开启的情况下,搜索顺序是:

           1、应用程序EXE所在的路径。

           2、系统目录。

           316位系统目录

           4Windows目录

           5、当前目录

           6PATH环境变量指定的目录

 

如果安全DLL搜索模式不支持或者被禁用,那么搜索顺序是:

           1、应用程序EXE所在的路径。

           2、当前目录

           3、系统目录。

           416位系统目录

           5Windows目录

           6PATH环境变量指定的目录

安全模式是由HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode这个注册表值控制。默认情况下是开启状态。对于Win XP默认是关闭,可以通过建立这样的注册表值来开启。

关于安全DLL搜索路径可以参考(Dynamic-Link Library Security

 http://msdn.microsoft.com/en-us/library/ff919712(v=vs.85).aspx

根据前面的分析: 该场景下,我们传递的是一个路径C:\Windows\system32\gdiplus.dll, 只有可能是使用了dll重定向,加载到了重定向目录的dll.

直接写测试程序,加载C:\Windows\System32\gdiplus.dll,而该目录下面并不存在GdiPlus.dll; 按常理应该加载失败;但它却直接读取的是C:\Windows\winsxs下面的文件

那如果C:\Windows\System32\目录下存在gdiplus.dll呢,拷贝一个模块到该目录,重命名为 gdiplus.dll, 它仍然是加载的C:\Windows\winsxs目录下面的gdiplus.dll

再次测试了把gdiplus.dll放在C:\windows目录下,加载C:\Windows\gdiplus.dll, 能够成功加载到 C:\Windows\gdiplus.dll,

gdiplud.dll放到应用程序的exe同目录,加载D:\Data\testLoad\Debug\gdiplus.dll成功加载到了D:\Data\testLoad\Debug\gdiplus.dll的模块。但是直接加载gdiplus.dll,则加载到了 C\Windows\winsxs目录下面的gdiplus.dll

总结下测试结果:

1. 采用full path加载dll, 如果是加载C:\Windows\system32目录下面,则会重定向去加载C:\Windows\winsxs目录下面的gdiplus.dll,其他情况都能正确加载到给定路径的文件。

2. 采用相对文件名gdiplus.dll加载尽管exe目录下面有gdiplus.dll,也会加载到C:\Windows\winsxs目录下面的gdiplus.dll.

也就是说,GetFileVersionInfo能够获取不存在的路径C:\Windows\System32\gdiplus.dll的版本号是因为触发了DLL加载的重定向机制,实际获取的是C:\Windows\winsxs目录下面对应文件的信息。

这个被称为side by side Assembly, msdn有一篇文章介绍(side-by-side component http://msdn.microsoft.com/en-us/library/dd408052(v=vs.85).aspx),这个东东的作用就是为了解决 以前windows上的“Dll 地狱”(参考附录参考链接) 问题才产生的新的DLL管理解决方案。大家知道,Dll是动态加载共享库,同一个Dll可能被多个程序所使用,而所谓“Dll 地狱”就是当不同程序依赖的Dll相同,但版本不同时,由于系统不能分辨到底哪个是哪个,所以加载错了Dll版本,然后就挂了。于是盖茨就吸取了教训,搞了一个程序集清单的东东,每个程序都要有一个清单,这个清单存在和自己应用程序同名的.manifest文件中,里面列出其所需要的所有依赖,这儿所列出的依赖可不是简单地靠文件明来区分的,而是根据一种叫做“强文件名”的东西区分的。

具体细节这里就不详细描述了, 直接参考链接就可以了。

总结

1. GetFileVersionInfo内部实现是通过LoadLibrary加载对应模块,并获取其Resource的信息来获取文件信息。

2. LoadLibrary加载DLL时会受到DLL重定向的影响,也就是说,如果确实需要判断系统盘\System32\目录的会重定向的文件的版本时,不能通过GetFileVersionInfo去获取版本信息,因为获取的是重定向路径的dll版本信息。只能采用其他方式获取。

附录

Dynamic-Link Library Search Order :

http://msdn.microsoft.com/en-us/library/ms682586(v=vs.85).aspx)。

side-by-side component :

http://msdn.microsoft.com/en-us/library/dd408052(v=vs.85).aspx 

DLL HELL及解决办法:

http://wenku.baidu.com/link?url=zqy12i40ZhJOAmyWqBKWGy-nOiDtKQeWC6FdeLAt-Rc6ifRg44HP7iYOkyHNAeU5ZeZ_poGpvv5n7W_VSAeYvu_HsqL-4M6mJqzNMdemcnW 

Supported Microsoft Side-by-side Assemblies

http://msdn.microsoft.com/en-us/library/aa376609(v=vs.85).aspx 

Activation Contexts

http://msdn.microsoft.com/en-us/library/windows/desktop/aa374153(v=vs.85).aspx

0 0
原创粉丝点击