让 WinVerifyTrust 使用目录签名文件,例如 cmd.exe

Dou*_*ugN 12 c++ windows winapi certificate winverifytrust

感谢这里有一个非常旧的帖子

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

// Link with the Wintrust.lib file.
#pragma comment (lib, "wintrust")
Run Code Online (Sandbox Code Playgroud)

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

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

我刚刚发现使用

CryptCATAdminAcquireContext2(&hCatAdmin, NULL, BCRYPT_SHA256_ALGORITHM, NULL, 0))
Run Code Online (Sandbox Code Playgroud)

而不是CryptCATAdminAcquireContext,和CryptCATAdminCalcHashFromFileHandle的2代替CryptCATAdminCalcHashFromFileHandle工作我在Windows Server 2019上。

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

更新 2

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

但是,设置 NULL(如文档中的建议)而不是 BCRYPT_SHA256_ALGORITHM 会导致之前看到的失败。这非常脆弱,似乎是特定于操作系统的 :(

无论如何要使这个跨操作系统版本可靠地工作?

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

cmd.exe 哈希值

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

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

不清楚的是,即使在目录文件中找不到散列,CryptCATAdminEnumCatalogFromHash 如何使用 PESHA1 散列找到相同的目录文件? 某处必须有一些额外的信息,允许它工作。

小智 1

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

\n
DWORD VerifyCatalogSignature(_In_ HANDLE FileHandle,\n    _In_ bool UseStrongSigPolicy)\n{\n    ...\n\n    if (UseStrongSigPolicy != false)\n    {\n        SigningPolicy.cbSize = sizeof(CERT_STRONG_SIGN_PARA);\n        SigningPolicy.dwInfoChoice = CERT_STRONG_SIGN_OID_INFO_CHOICE;\n        //SigningPolicy.pszOID = const_cast<char*>(szOID_CERT_STRONG_SIGN_OS_CURRENT);\n        SigningPolicy.pszOID = const_cast<char*>(szOID_CERT_STRONG_KEY_OS_1);\n        if (!CryptCATAdminAcquireContext2(\n            &CatAdminHandle,\n            NULL,\n            NULL,\n            &SigningPolicy,\n            0))\n        {\n            Error = GetLastError();\n            goto Cleanup;\n        }\n    }\n    else\n    {\n        if (!CryptCATAdminAcquireContext2(\n            &CatAdminHandle,\n            NULL,\n            BCRYPT_SHA256_ALGORITHM,\n            NULL,\n            0))\n        {\n            Error = GetLastError();\n            goto Cleanup;\n        }\n    }\n\n    ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n