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

问题描述

感谢这里提供了一个非常古老的帖子

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,我会在下方弹出错误消息,其中包含一个看起来非常有效的证书的链接。

enter image description here

那么为什么 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,sizeof(wfi));
    wfi.cbStruct = sizeof(WINTRUST_FILE_INFO);
    wfi.pcwszFilePath = pwszSourceFile;

    memset(&wd,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,&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,dwHash,NULL);

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

            WINTRUST_CATALOG_INFO wci;

            CryptCATCatalogInfoFromContext(hCatInfo,&ci,0);

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

            memset(&wd,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,&wd);
            if(ERROR_SUCCESS == lStatus)
                bRetVal = TRUE;

            //clean up the state variable
            wd.dwStateAction = WTD_STATEACTION_CLOSE;
            WinVerifyTrust(NULL,&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,BCRYPT_SHA256_ALGORITHM,0))

代替 CryptCATAdminAcquireContext,和 CryptCATAdminCalcHashFromFileHandle2 代替 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 hashes

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

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

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

解决方法

我测试了以下代码,其中设置了 NULL(如文档中的建议)而不是 BCRYPT_SHA256_ALGORITHM。没问题。
虽然文档说默认的散列算法可能会在未来的 Windows 版本中改变,但对于微软来说,保持一致的行为是必要的。

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,&SigningPolicy,0))
        {
            Error = GetLastError();
            goto Cleanup;
        }
    }
    else
    {
        if (!CryptCATAdminAcquireContext2(
            &CatAdminHandle,BCRYPT_SHA256_ALGORITHM,0))
        {
            Error = GetLastError();
            goto Cleanup;
        }
    }

    ...
}

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...