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

VerQueryValueA writes invalid memory outside of the resource block

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

// 
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());

// 
    LPVOID pvFileVersion{};
    UINT iFileVersionLen{};

    if ( !VerQueryValueA(&ResCopy[0], "\StringFileInfo\040904E4\FileVersion", &pvFileVersion, &iFileVersionLen) )
        return "(unknown)"s;

    char buf[200];
    sprintf(buf, "%p\n%p\n%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 应该 return 指向资源块的指针。但是,我得到的输出是:

000000000594b460
000000000594b748
000000000594b802

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

000000000594b460
000000000594b948
000000000594b802

我唯一可以得出的结论是 VerQueryValueA 函数在原始情况下进行了越界写入;它正在写入超过 SizeofResource 给出的资源大小的末尾;它在我的矢量之外写。

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

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


注意:如果我使用 VerQueryValueW,那么它首先会在 ResCopy 中创建一个指针 return。

This answer 似乎暗示了这个问题,但是我没有使用 GetFileVersionInfo (它需要一个文件名,似乎没有任何等效的函数可以获取模块句柄)。

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

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

The first parameter to VerQueryValue really must be a buffer you obtained from GetFileVersionInfo

The documentation for the VerQueryValue function states that the first parameter is a "pointer to the buffer containing the version-information resource returned by the GetFileVersionInfo function." Some people, however, decide to bypass this step and pass a pointer to data that was obtained some other way, and then wonder why VerQueryValue doesn't work.

The documentation says that the first parameter to VerQueryValue must be a buffer returned by the GetFileVersionInfo function for a reason. The buffer returned by GetFileVersionInfo is an opaque data block specifically formatted so that VerQueryValue will work. You're not supposed to look inside that buffer, and you certainly can't try to "obtain the data some other way". Because if you do, VerQueryValue will look for something in a buffer that is not formatted in the manner the function expects.

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

If it wasn't obvious enough from the documentation that you can't just pass a pointer to a version resource obtained "some other way", it's even more obvious once you see the format of 32-bit version resources. Notice that all strings are stored in Unicode. But if you call the ANSI version VerQueryValueA to request a string, the function has to give you a pointer to an ANSI string. There is no ANSI version of the string in the raw version resource, so what can it possibly return? You can't return a pointer to something that doesn't exist. VerQueryValueA needs to produce an ANSI string, and it does so from memory that GetFileVersionInfo prepared when the resources were extracted.

对于您正在尝试执行的操作,您只需要从复制的资源中查询 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。

I'm not using GetFileVersionInfo (which requires a filename, there doesn't seem to be any equivalent function that takes a module handle).

解决方案很简单:调用 GetModuleFileName() 来检索可执行文件的文件路径,然后按照记录调用 GetFileVersionInfoSize()GetFileVersionInfo()VerQueryValue()