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

时间:2018-02-02 04:35:44

标签: c++ winapi versioning

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

// 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, "%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应该返回指向资源块的指针。但是,我得到的输出是:

000000000594b460
000000000594b748
000000000594b802

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

000000000594b460
000000000594b948
000000000594b802

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

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

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

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

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

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

2 个答案:

答案 0 :(得分:4)

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

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

  

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

     

文档说明,VerQueryValue的第一个参数必须是GetFileVersionInfo函数返回的缓冲区。 GetFileVersionInfo返回的缓冲区是一个专门格式化的不透明数据块,以便VerQueryValue可以工作。你不应该查看那个缓冲区,你当然不能尝试“以其他方式获取数据”。因为如果你这样做,VerQueryValue将在缓冲区中查找未按函数预期的方式格式化的内容

除了在资源数据的最开头查询VS_FIXEDFILEINFO之外,使用VerQueryValue()从原始资源数据查询其他版本数据实际上是不安全的。该数据尚未准备好VerQueryValue()使用。您链接到的问题的答案甚至陈述了这一点,如上文所述:

  

如果从文档中不够明显,你不能只是将指针传递给“其他方式”获得的版本资源,那么一旦你看到the format of 32-bit version resources就会更加明显。请注意,所有字符串都以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);
}

答案 1 :(得分:1)

作为Remy Lebeau already explained,您正在以无证方式使用API​​。

  

我没有使用GetFileVersionInfo(需要文件名,那里   似乎不是任何采用模块的等效函数   处理)。

解决方案很简单:致电GetModuleFileName()以检索可执行文件的文件路径,然后根据文档调用GetFileVersionInfoSize()GetFileVersionInfo()VerQueryValue()