VerQueryValueA 在资源块之外写入无效内存

VerQueryValueA writes invalid memory outside of the resource block

本文关键字:无效 内存 资源 VerQueryValueA      更新时间:2023-10-16

我使用以下代码检索当前可执行文件的版本字符串:

// http://stackoverflow.com/questions/13941837/how-to-get-version-info-from-resources
std::string get_version_string()
{
auto hInst = GetModuleHandle(NULL);
// The following functions allocate persistent one-time space in the process's memory - they don't need to have results freed
auto hResInfo = FindResourceA(hInst, MAKEINTRESOURCE(1), RT_VERSION);
auto dwSize = SizeofResource(hInst, hResInfo);
auto hResData = LoadResource(hInst, hResInfo);
char *pRes = static_cast<char *>(LockResource(hResData));
if ( !dwSize || !pRes ) return {};
// Copy is required because VerQueryValue modifies the object, but LoadResource's resource is non-modifiable.
// SizeofResource yielded the size in bytes, according to its documentation.
std::vector<char> ResCopy(dwSize);
std::copy(pRes, pRes + dwSize, ResCopy.begin());
// https://stackoverflow.com/a/1174697/1505939
LPVOID pvFileVersion{};
UINT iFileVersionLen{};
if ( !VerQueryValueA(&ResCopy[0], "\StringFileInfo\040904E4\FileVersion", &pvFileVersion, &iFileVersionLen) )
return "(unknown)"s;
char buf[200];
sprintf(buf, "%pn%pn%p", &ResCopy[0], &ResCopy[0] + ResCopy.size(), pvFileVersion);
debug_output ( buf );
auto s = static_cast<char *>(pvFileVersion);
return std::string( s, s + iFileVersionLen );
}

VerQueryValue 文档以及有关该主题的其他 SO 问题表明,VerQueryValue应该返回一个指向资源块的指针。但是,我得到的输出是:

000000000594b460
000000000594b748
000000000594b802

此外,如果我将ResCopy的分配更改为std::vector<char> ResCopy(dwSize + 0x200);,那么我得到输出:

000000000594b460
000000000594b948
000000000594b802

我唯一能从中得出结论的是,VerQueryValueA函数在原始情况下正在执行越界写入;它写超过资源大小的末尾,如SizeofResource给出的那样;它在我的向量之外写入。

即使该功能似乎正常工作,我怀疑这实际上可能是一个错误。

我的问题是:我做错了什么,还是VerQueryValueA中的错误?我应该如何解决问题?


注意:如果我使用VerQueryValueW,那么它首先会在ResCopy内返回一个指针。

这个答案似乎暗示了这个问题,但我没有使用GetFileVersionInfo(这需要文件名,似乎没有任何等效的函数来获取模块句柄)。

这样做的更大目的是能够在日志文件中记录我的应用程序的版本字符串,并且当我们显然已经加载可执行文件运行时,尝试根据文件名查找和打开文件似乎是一堆更多可能的故障点运行它。

>GetFileVersionInfo()执行VerQueryValue()所依赖的修正和数据转换。 Raymond Chen甚至写了一篇关于它的博客文章:

VerQueryValue 的第一个参数实际上必须是您从 GetFileVersionInfo 获得的缓冲区

VerQueryValue 函数的文档指出,第一个参数是"指向包含 GetFileVersionInfo 函数返回的版本信息资源的缓冲区的指针"。但是,有些人决定绕过此步骤并传递指向以其他方式获得的数据的指针,然后想知道为什么VerQueryValue不起作用

文档指出,VerQueryValue 的第一个参数必须是 GetFileVersionInfo 函数返回的缓冲区,这是有原因的。GetFileVersionInfo 返回的缓冲区是一个专门格式化的不透明数据块,以便 VerQueryValue 正常工作。您不应该查看该缓冲区内部,当然也不能尝试"以其他方式获取数据"。因为如果这样做,VerQueryValue 将在缓冲区中查找未按照函数预期方式格式化的内容

除了在资源数据最开头查询VS_FIXEDFILEINFO之外,使用VerQueryValue()从原始资源数据中查询其他版本数据确实不安全。 这些数据尚未准备好供VerQueryValue()使用。您链接到的问题的答案甚至说明了这一点,上面的文章也是如此:

如果从文档中看不够明显,你不能只传递指向"其他方式"获得的版本资源的指针,那么一旦你看到 32 位版本资源的格式,这一点就更加明显了。请注意,所有字符串都存储在 Unicode 中。但是,如果您调用 ANSI 版本 VerQueryValueA 来请求字符串,则该函数必须为您提供指向 ANSI 字符串的指针。原始版本资源中没有字符串的 ANSI 版本,那么它可能会返回什么?不能返回指向不存在的内容的指针。VerQueryValueA 需要生成一个 ANSI 字符串,并且它从提取资源时 GetFileVersionInfo 准备的内存中执行此操作

对于您尝试执行的操作,您只需要从复制的资源查询VS_FIXEDFILEINFO即可。它包含您要查找的版本号,与语言无关,并且不依赖于GetFileVersionInfo()

std::string get_version_string()
{
auto hInst = GetModuleHandle(NULL);
auto hResInfo = FindResourceA(hInst, MAKEINTRESOURCE(1), RT_VERSION);
if ( !hResInfo ) return {};
auto dwSize = SizeofResource(hInst, hResInfo);
if ( !dwSize ) return {};
auto hResData = LoadResource(hInst, hResInfo);
char *pRes = static_cast<char *>(LockResource(hResData));
if ( !pRes ) return {};
std::vector<char> ResCopy(pRes, pRes + dwSize);
VS_FIXEDFILEINFO *pvFileInfo;
UINT uiFileInfoLen;
if ( !VerQueryValueA(ResCopy.data(), "\", reinterpret_cast<void**>(&pvFileInfo), &uiFileInfoLen) )
return "(unknown)"s;
char buf[25];
int len = sprintf(buf, "%hu.%hu.%hu.%hu", 
HIWORD(pvFileInfo->dwFileVersionMS),
LOWORD(pvFileInfo->dwFileVersionMS),
HIWORD(pvFileInfo->dwFileVersionLS),
LOWORD(pvFileInfo->dwFileVersionLS)
);
return std::string(buf, len);
}

正如 Remy Lebeau 已经解释的那样,您正在以未记录的方式使用 API。

我没有使用GetFileVersionInfo(这需要一个文件名,那里 似乎没有任何采用模块的等效函数 句柄)。

解决方案很简单:调用GetModuleFileName()检索可执行文件的文件路径,然后调用GetFileVersionInfoSize()GetFileVersionInfo()VerQueryValue()如文档所示。