【问题标题】:Getting WinVerifyTrust to work with catalog signed files such as cmd.exe让 WinVerifyTrust 使用目录签名文件,例如 cmd.exe
【发布时间】:2023-03-15 23:17:02
【问题描述】:

感谢这里有一篇非常古老的帖子

https://web.archive.org/web/20140217003950/http://forum.sysinternals.com/topic16893_post83634.html

我遇到了一个函数,该函数将对文件调用 WinVerifyTrust 以检查嵌入的签名,如果失败,则找到适当的系统目录文件并通过另一个对 WinVerifyTrust 的调用对其进行检查。

但是,使用 C:\Windows\System32\cmd.exe 进行测试失败。请注意,测试应用是 64 位的,因此文件重定向不是问题。

将该函数的输出与 Microsoft 的 Sigcheck 实用程序进行比较,该函数具有正确的文件哈希,并找到了正确的目录文件。但是,当使用目录信息调用 WinVerifyTrust 时,它仍然会失败并显示

TRUST_E_BAD_DIGEST 0x80096010 //对象的数字签名 没有验证。

有趣的是,当 UI 启用时

dwUIChoice = WTD_UI_ALL

失败代码不同:

TRUST_E_SUBJECT_NOT_TRUSTED 0x800B0004 // 主体不可信 用于指定的操作。

但是 Sigcheck.exe 和 Signtool.exe 都说它是可信的。

另外,如果设置了 dwUIChoice = WTD_UI_ALL,我会在下方弹出错误消息,其中包含指向看起来非常有效的证书的链接。

那么为什么 WinVerifyTrust 在 cmd.exe 上指示签名错误?

代码如下,欢迎就可以修复的问题提供任何意见:

BOOL VerifyEmbeddedSignature2(LPCWSTR pwszSourceFile)
{
    BOOL bRetVal = FALSE;
    LONG lStatus = 0;
    GUID WintrustVerifyGuid = WINTRUST_ACTION_GENERIC_VERIFY_V2;
    WINTRUST_DATA wd;
    WINTRUST_FILE_INFO wfi;

    ////set up structs to verify files with cert signatures
    memset(&wfi, 0, sizeof(wfi));
    wfi.cbStruct = sizeof(WINTRUST_FILE_INFO);
    wfi.pcwszFilePath = pwszSourceFile;

    memset(&wd, 0, sizeof(wd));
    wd.cbStruct = sizeof(WINTRUST_DATA);
    wd.dwUnionChoice = WTD_CHOICE_FILE;
    wd.pFile = &wfi;
    wd.dwUIChoice = WTD_UI_NONE;
    wd.fdwRevocationChecks = WTD_REVOKE_NONE;
    wd.dwStateAction = WTD_STATEACTION_VERIFY;
    wd.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL | WTD_USE_DEFAULT_OSVER_CHECK;

    lStatus = WinVerifyTrust(NULL, &WintrustVerifyGuid, &wd);

    //clean up the state variable
    wd.dwStateAction = WTD_STATEACTION_CLOSE;
    WinVerifyTrust(NULL, &WintrustVerifyGuid, &wd);

    ////if failed, try to verify using catalog files
    if (lStatus != ERROR_SUCCESS)
    {
        GUID DriverActionGuid = DRIVER_ACTION_VERIFY;
        HANDLE hFile = INVALID_HANDLE_VALUE;
        DWORD dwHash = 0;
        BYTE bHash[100] = { 0 };
        HCATINFO hCatInfo = NULL;
        HCATADMIN hCatAdmin = NULL;
        LPWSTR pszMemberTag = NULL;

        //open the file
        hFile = CreateFileW(pwszSourceFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile == INVALID_HANDLE_VALUE)
            goto Cleanup;

        if (!CryptCATAdminAcquireContext(&hCatAdmin, &DriverActionGuid, 0))
            goto Cleanup;

        dwHash = sizeof(bHash);
        if (!CryptCATAdminCalcHashFromFileHandle(hFile, &dwHash, bHash, 0))
            goto Cleanup;

        CloseHandle(hFile);
        hFile = INVALID_HANDLE_VALUE;

        //Create a string form of the hash (used later in pszMemberTag)
        pszMemberTag = new WCHAR[dwHash * 2 + 1];
        for (DWORD dw = 0; dw < dwHash; ++dw)
        {
            wsprintfW(&pszMemberTag[dw * 2], L"%02X", bHash[dw]);
        }

        //find the catalog which contains the hash
        hCatInfo = CryptCATAdminEnumCatalogFromHash(hCatAdmin, bHash, dwHash, 0, NULL);

        if (hCatInfo)
        {
            CATALOG_INFO ci = { 0 };
            ci.cbStruct = sizeof(ci);

            WINTRUST_CATALOG_INFO wci;

            CryptCATCatalogInfoFromContext(hCatInfo, &ci, 0);

            memset(&wci, 0, sizeof(wci));
            wci.cbStruct = sizeof(wci);
            wci.pcwszCatalogFilePath = ci.wszCatalogFile;
            wci.pcwszMemberFilePath = pwszSourceFile;
            wci.pcwszMemberTag = pszMemberTag;

            memset(&wd, 0, sizeof(wd));
            wd.cbStruct = sizeof(WINTRUST_DATA);
            wd.dwUnionChoice = WTD_CHOICE_CATALOG;
            wd.pCatalog = &wci;
            wd.dwUIChoice = WTD_UI_ALL; //WTD_UI_NONE; //
            wd.fdwRevocationChecks = WTD_REVOKE_NONE;
            wd.dwStateAction = WTD_STATEACTION_VERIFY;
            wd.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL | WTD_USE_DEFAULT_OSVER_CHECK;

            lStatus = WinVerifyTrust(NULL, &WintrustVerifyGuid, &wd);
            if(ERROR_SUCCESS == lStatus)
                bRetVal = TRUE;

            //clean up the state variable
            wd.dwStateAction = WTD_STATEACTION_CLOSE;
            WinVerifyTrust(NULL, &WintrustVerifyGuid, &wd);

            CryptCATAdminReleaseCatalogContext(hCatAdmin, hCatInfo, 0);
        }
Cleanup:
        if(NULL != hCatAdmin)
            CryptCATAdminReleaseContext(hCatAdmin, 0);
        hCatAdmin = NULL;
        if(NULL != pszMemberTag)
            delete[] pszMemberTag;
        pszMemberTag = NULL;
        if(INVALID_HANDLE_VALUE != hFile)
            CloseHandle(hFile);
        hFile = INVALID_HANDLE_VALUE;
    }
    else
        bRetVal = TRUE;

    return bRetVal;
}

请注意,要使用上述功能,您需要:

#include <Softpub.h>
#include <wincrypt.h>
#include <wintrust.h>
#include <mscat.h>

// Link with the Wintrust.lib file.
#pragma comment (lib, "wintrust")

更新:来自此处提供的示例

https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Security/CodeSigning/cpp/codesigning.cpp

我刚刚发现使用

CryptCATAdminAcquireContext2(&hCatAdmin, NULL, BCRYPT_SHA256_ALGORITHM, NULL, 0))

而不是 CryptCATAdminAcquireContext 和 CryptCATAdminCalcHashFromFileHandle2 而不是 CryptCATAdminCalcHashFromFileHandle 在我的 Windows Server 2019 上工作

所以现在问题变成了“为什么?”,对于可能运行代码的其他操作系统版本(Win 7?Win 8?Server 2012 R2?),BCRYPT_SHA256_ALGORITHM 是否是合适的参数?

更新 2

CryptCATAdminAcquireContext2 的文档说:“此函数使您能够选择或为您选择在需要目录管理员上下文的函数中使用的哈希算法。虽然您可以设置哈希算法的名称,但我们建议您让函数确定算法。这样做可以保护您的应用程序免受将来可能变得不受信任的硬编码算法的影响。"

但是,设置 NULL(按照文档中的建议)而不是 BCRYPT_SHA256_ALGORITHM 会导致之前出现的故障。这非常脆弱,似乎是特定于操作系统的:(

无论如何要使这项工作在操作系统版本之间可靠地工作?

更新 3 现在很明显为什么这不能正常工作。这是 sigcheck 显示的来自 cmd.exe 的哈希列表

当使用 NULL 调用 CryptCATAdminAcquireContext2 时,您会从 CryptCATAdminCalcHashFromFileHandle2 获得 PESHA1 哈希。当使用 BCRYPT_SHA256_ALGORITHM 调用时,您会得到 PE256 哈希。

这一切都说得通。不幸的是,目录文件只包含 PE256 哈希。因此,如果您不知道目录文件包含什么散列算法,我能想到的唯一解决方案是使用 CryptCATAdminAcquireContext2 的各种算法遍历所有这些代码,并一遍又一遍地拥有该文件,直到找到一个散列存在于目录文件中。

不清楚的是,CryptCATAdminEnumCatalogFromHash 如何使用 PESHA1 哈希找到相同的目录文件,即使在目录文件中找不到哈希? 一定有一些附加信息允许它工作。

【问题讨论】:

  • 正如CryptCATAdminAcquireContext2 的参数pwszHashAlgorithm 所说,默认的哈希算法可能会在未来的Windows 版本中发生变化。
  • @YangXiaoPo,是的,所以推荐使用NULL。但它不起作用。那么应该怎么做呢?
  • 但是从什么你知道他们在这个目录中有什么和没有什么?
  • @Алексей Неудачин 找到目录文件后,您可以在 Windows 资源管理器中双击它,然后选择“安全目录”选项卡以查看哈希值。我假设它显示了文件中的所有哈希值,但这只是一个假设。

标签: c++ windows winapi certificate winverifytrust


【解决方案1】:

我测试了以下代码,该代码设置为 NULL(按照文档中的建议)而不是 BCRYPT_SHA256_ALGORITHM。没问题。
虽然文档说默认哈希算法可能会在未来的 Windows 版本中发生变化,但对于 Microsoft 来说,保持一致的行为是必要的。

DWORD VerifyCatalogSignature(_In_ HANDLE FileHandle,
    _In_ bool UseStrongSigPolicy)
{
    ...

    if (UseStrongSigPolicy != false)
    {
        SigningPolicy.cbSize = sizeof(CERT_STRONG_SIGN_PARA);
        SigningPolicy.dwInfoChoice = CERT_STRONG_SIGN_OID_INFO_CHOICE;
        //SigningPolicy.pszOID = const_cast<char*>(szOID_CERT_STRONG_SIGN_OS_CURRENT);
        SigningPolicy.pszOID = const_cast<char*>(szOID_CERT_STRONG_KEY_OS_1);
        if (!CryptCATAdminAcquireContext2(
            &CatAdminHandle,
            NULL,
            NULL,
            &SigningPolicy,
            0))
        {
            Error = GetLastError();
            goto Cleanup;
        }
    }
    else
    {
        if (!CryptCATAdminAcquireContext2(
            &CatAdminHandle,
            NULL,
            BCRYPT_SHA256_ALGORITHM,
            NULL,
            0))
        {
            Error = GetLastError();
            goto Cleanup;
        }
    }

    ...
}

【讨论】:

  • 你在 C:\WIndows\System32\cmd.exe 上试过了吗? UseStrongSigPolicy 是真的还是假的?在错误的情况下,BCRYPT_SHA256_ALGORITHM 仍在上面的代码中使用。
  • 命令参数:-p -c C:\Windows\System32\cmd.exe
  • 我没有注意到上面的 szOID_CERT_STRONG_KEY_OS_1 更改(duh),但即使进行了更改,WinVerifyTrust 也不会在找到的目录中返回 ERROR_SUCCESS。鉴于您提到在命令行上使用 -p -c,我认为您正在使用上面链接中的 codesigning.cpp。这会验证目录中的文件,但实际上并不调用 WinVerifyTrust。
猜你喜欢
  • 2010-11-07
  • 1970-01-01
  • 2012-04-30
  • 1970-01-01
  • 1970-01-01
  • 2014-01-21
  • 1970-01-01
  • 2017-11-20
  • 2013-07-23
相关资源
最近更新 更多