感谢这里有一篇非常古老的帖子
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")
更新:来自此处提供的示例
我刚刚发现使用
CryptCATAdminAcquireContext2(&hCatAdmin, NULL, BCRYPT_SHA256_ALGORITHM, NULL, 0))
而不是 CryptCATAdminAcquireContext,并且 CryptCATAdminCalcHashFromFileHandle 2而不是 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 哈希找到相同的目录文件,即使在目录文件中找不到哈希? 必须有一些额外的信息允许它工作。