Благодаря очень старому сообщению, доступному здесь
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 //Цифровая подпись объекта не проверял.
Интересно, когда пользовательский интерфейс включен с помощью
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 <1896565555556> of CryptcatadmincalChashFromFileHandle <18965655111556>
Итак, теперь вопрос становится "почему?", и будет ли BCRYPT_SHA256_ALGORITHM подходящим параметром для других версий ОС, на которых может выполняться код (Win 7? Win 8? Server 2012 R2?)
ОБНОВЛЕНИЕ 2
Документы для CryptCATAdminAcquireContext2 говорят: «Эта функция позволяет вам выбрать или выбирает для вас алгоритм хэширования, который будет использоваться в функциях, требующих контекста администратора каталога. Хотя вы можете установить имя алгоритма хэширования, мы рекомендуем вы позволяете функции определять алгоритм. Это защищает ваше приложение от алгоритмов жесткого кодирования, которые могут стать ненадежными в будущем."
Однако установка NULL (как рекомендуется в документации) вместо BCRYPT_SHA256_ALGORITHM приводит к ранее замеченным сбоям. Это очень хрупко и, похоже, зависит от ОС :(
Чтобы это надежно работало в разных версиях ОС?
ОБНОВЛЕНИЕ 3 Теперь понятно, почему это работает неправильно. Вот список хэшей из cmd.exe, показанный sigcheck
.При вызове CryptCATAdminAcquireContext2 с NULL вы получаете хэш PESHA1 из CryptCATAdminCalcHashFromFileHandle2. При вызове с BCRYPT_SHA256_ALGORITHM вместо этого вы получаете хэш PE256.
Все это имеет смысл. К сожалению, файлы каталога содержат только хэш PE256. Поэтому, если вы не знаете, какой алгоритм хэширования содержится в файлах каталога, единственное решение, которое я могу придумать, — это перебрать весь этот код с различными алгоритмами для CryptCATAdminAcquireContext2 и продолжать использовать файл снова и снова, пока не будет найден хэш, который существует в файле каталога.
Что НЕ ясно, так это как CryptCATAdminEnumCatalogFromHash находит те же файлы каталога, используя хэш PESHA1, даже если хэш не найден в файле каталога? Где-то должна быть какая-то дополнительная информация, которая позволяет что работать.
Как говорит параметр
pwszHashAlgorithm
изCryptCATAdminAcquireContext2
, Алгоритм хеширования по умолчанию может измениться в будущих версиях Windows.@YangXiaoPo, да, поэтому рекомендуется использовать NULL. Но это не работает. Так что должно быть сделано?
но откуда вы знаете, что у них есть в этом каталоге, а чего нет?
@ Алексей Неудачин, как только вы найдете файл каталога, вы можете дважды щелкнуть его в проводнике Windows и выбрать вкладку «Каталог безопасности», чтобы увидеть хэши. Я предполагаю, что он показывает все хеши в файле, но это всего лишь предположение.