Получение WinVerifyTrust для работы с подписанными файлами каталога, такими как cmd.exe

avatar
DougN
1 июля 2021 в 19:13
660
2
17

Благодаря очень старому сообщению, доступному здесь

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, я получаю всплывающее окно с ошибкой ниже со ссылкой на очень действующий сертификат.

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, 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 и 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

.

cmd.exe hashes

При вызове CryptCATAdminAcquireContext2 с NULL вы получаете хэш PESHA1 из CryptCATAdminCalcHashFromFileHandle2. При вызове с BCRYPT_SHA256_ALGORITHM вместо этого вы получаете хэш PE256.

Все это имеет смысл. К сожалению, файлы каталога содержат только хэш PE256. Поэтому, если вы не знаете, какой алгоритм хэширования содержится в файлах каталога, единственное решение, которое я могу придумать, — это перебрать весь этот код с различными алгоритмами для CryptCATAdminAcquireContext2 и продолжать использовать файл снова и снова, пока не будет найден хэш, который существует в файле каталога.

Что НЕ ясно, так это как CryptCATAdminEnumCatalogFromHash находит те же файлы каталога, используя хэш PESHA1, даже если хэш не найден в файле каталога? Где-то должна быть какая-то дополнительная информация, которая позволяет что работать.

Источник
YangXiaoPo - MSFT
6 июля 2021 в 08:29
0

Как говорит параметр pwszHashAlgorithm из CryptCATAdminAcquireContext2, Алгоритм хеширования по умолчанию может измениться в будущих версиях Windows.

DougN
6 июля 2021 в 15:44
0

@YangXiaoPo, да, поэтому рекомендуется использовать NULL. Но это не работает. Так что должно быть сделано?

Алексей Неудачин
19 июля 2021 в 08:20
0

но откуда вы знаете, что у них есть в этом каталоге, а чего нет?

DougN
31 августа 2021 в 19:19
0

@ Алексей Неудачин, как только вы найдете файл каталога, вы можете дважды щелкнуть его в проводнике Windows и выбрать вкладку «Каталог безопасности», чтобы увидеть хэши. Я предполагаю, что он показывает все хеши в файле, но это всего лишь предположение.

Ответы (2)

avatar
SrPanda
9 апреля 2022 в 22:21
0

Насколько я знаю, WinVerifyTrust на самом деле не работает с установленными каталогами, даже если WINTRUST_DATA имеет параметр каталога, но глядя на то, как данные должны быть доступны, видно, что аргументы предназначены только для чтения, и я не вижу никаких других pCatalog чем использование каталога, который вы создаете, этот образец как бы намекает на эту идею. Процесс поиска в каталоге зависит от того, что вы сообщаете CryptCATAdminAcquireContext2, какой хеш ожидается, вы можете передать просто null, но если вы не хотите иметь конкретный хэш, вам нужно отметить каждую комбинацию, которая может быть доступна без них. Причина, по которой он говорит не проверять конкретный, заключается в том, что в идеале нужно проверить каждый из них; Когда вы отмечаете WTD_UI_ALL, вы получаете сертификат, если вы принимаете диалоговое окно, но это «переопределение пользователя», это похоже на запрос на повышенные привилегии.

#define _UNICODE 1
#define UNICODE 1

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

#include <stdio.h>

#pragma comment (lib, "wintrust")

int verify_signature(const wchar_t * file_path, bool ui = false){
    int ret = 0;
    long status = 0;
    GUID policy_guid = WINTRUST_ACTION_GENERIC_VERIFY_V2;

    HANDLE file_handle = CreateFileW(
        file_path, GENERIC_READ, FILE_SHARE_READ,
        NULL, OPEN_EXISTING, 0, NULL
    );
    if (file_handle == INVALID_HANDLE_VALUE){
        ret = GetLastError();
        goto cleanup;
    }

    // [*] Shared strutcs

    WINTRUST_FILE_INFO file_info;
    ZeroMemory(&file_info, sizeof(file_info));
    file_info.cbStruct = sizeof(WINTRUST_FILE_INFO);
    file_info.pcwszFilePath = file_path;
    file_info.hFile = file_handle;

    /*/

        sign = ['RSA', 'DSA', 'ECDSA']
        hash = ['SHA256', 'SHA512']
        comb = []
        for a in sign:
            for b in hash:
                comb.append('{0}/{1}'.format(a, b))
                
        print(';'.join(comb))

    /*/

    wchar_t * sign_hash = L"RSA/SHA256;RSA/SHA512;DSA/SHA256;DSA/SHA512;ECDSA/SHA256;ECDSA/SHA512";

    CERT_STRONG_SIGN_SERIALIZED_INFO policy_rule;
    policy_rule.dwFlags = 0;
    policy_rule.pwszCNGSignHashAlgids = sign_hash;
    policy_rule.pwszCNGPubKeyMinBitLengths = nullptr;

    CERT_STRONG_SIGN_PARA policy;
    ZeroMemory(&policy, sizeof(policy));
    policy.cbSize = sizeof(CERT_STRONG_SIGN_PARA);
    policy.dwInfoChoice = CERT_STRONG_SIGN_SERIALIZED_INFO_CHOICE;
    policy.pSerializedInfo = &policy_rule;

    // [1] Check for catalogs

    HCATINFO  info_handle  = NULL;
    HCATADMIN admin_handle = NULL;

    // if (!CryptCATAdminAcquireContext2(&admin_handle, NULL, 0, &policy, 0)) {
    if (!CryptCATAdminAcquireContext2(&admin_handle, NULL, 0, NULL, 0)) {
        ret = GetLastError();
        goto cleanup;
    }

    DWORD hash_len = 0;
    BYTE * hash_data = nullptr;
    CryptCATAdminCalcHashFromFileHandle2(
        admin_handle, file_handle, &hash_len, NULL, 0
    
    );
    hash_data = new BYTE[hash_len];
    if (!CryptCATAdminCalcHashFromFileHandle2(
        admin_handle, file_handle, &hash_len, hash_data, 0 )) {

        ret = GetLastError();
        goto cleanup;
    }

    CATALOG_INFO catalog;
    ZeroMemory(&catalog, sizeof(CATALOG_INFO));
    do {
        info_handle = CryptCATAdminEnumCatalogFromHash(
            admin_handle, hash_data, hash_len, 0, &info_handle
        );

        if (CryptCATCatalogInfoFromContext(info_handle, &catalog, 0 )){
            wprintf(L" - Catalog %ls \n", catalog.wszCatalogFile);
        }

    } while (info_handle != NULL);

    // [2] Check for embeded ones

    WINTRUST_SIGNATURE_SETTINGS sign_settings;
    ZeroMemory(&sign_settings, sizeof(sign_settings));
    sign_settings.cbStruct = sizeof(WINTRUST_SIGNATURE_SETTINGS);
    sign_settings.dwFlags = WSS_VERIFY_SPECIFIC;
    sign_settings.dwIndex = 0;

    WINTRUST_DATA wintrust_data;
    ZeroMemory(&wintrust_data, sizeof(wintrust_data));
    wintrust_data.cbStruct = sizeof(WINTRUST_DATA);
    wintrust_data.dwUIChoice = ui ? WTD_UI_ALL : WTD_UI_NONE;
    wintrust_data.fdwRevocationChecks = WTD_REVOKE_NONE; 
    wintrust_data.dwUnionChoice = WTD_CHOICE_FILE;
    wintrust_data.dwProvFlags = WTD_HASH_ONLY_FLAG;

    wintrust_data.pFile = &file_info;
    wintrust_data.pSignatureSettings = &sign_settings;
    wintrust_data.pSignatureSettings->pCryptoPolicy = &policy;

    CRYPT_PROVIDER_DATA * prov_data = nullptr;
    CRYPT_PROVIDER_SGNR * prov_signer = nullptr;

    do {

        wintrust_data.dwStateAction = WTD_STATEACTION_VERIFY;
        status = WinVerifyTrust(
            // same ase GetDC, 0 means desktop
            ui ? 0 : (HWND)INVALID_HANDLE_VALUE, 
            &policy_guid, (LPVOID)&wintrust_data
        );

        // Check -> WinTruest.h:291
        if (status == ERROR_SUCCESS){
            prov_data =  WTHelperProvDataFromStateData(
                wintrust_data.hWVTStateData
            );
            prov_signer = WTHelperGetProvSignerFromChain(
                prov_data, wintrust_data.pSignatureSettings->dwIndex, FALSE, 0
            );

            if (prov_signer != nullptr){
                // prov_signer is just a nigtmare to use
                printf(" - Embeded %s \n", prov_signer->pChainContext->rgpChain[0]->rgpElement[0]->pCertContext->pCertInfo->SignatureAlgorithm.pszObjId);
            }

        }

        wintrust_data.dwStateAction = WTD_STATEACTION_CLOSE;
        WinVerifyTrust(
            ui ? 0 : (HWND)INVALID_HANDLE_VALUE, 
            &policy_guid, (LPVOID)&wintrust_data
        );

         wintrust_data.pSignatureSettings->dwIndex++;
    } while (
        wintrust_data.pSignatureSettings->dwIndex <= 
        wintrust_data.pSignatureSettings->cSecondarySigs
    );

    cleanup:
    if (hash_data != nullptr){
        delete [] hash_data;
    }
    if (admin_handle != NULL){
        CryptCATAdminReleaseContext(admin_handle, NULL);
    }
    if (file_handle != NULL){
        CloseHandle(file_handle);
    }
    return ret;
}

int main(void){
    // paths that use a single \ may not work
    wchar_t * paths [] = {
        L"C:/Windows/explorer.exe",                      // catalog / embeded
        L"C:/Windows/System32/cmd.exe",                  // only catalog
        L"C:/Program Files/AMD/CNext/CNext/AMDLink.exe"  // Only embeded
    };

    for (wchar_t * path : paths){
        wprintf(L"%ls \n", path);
        int ver = verify_signature(path);
        wprintf(L"\n");
    }
    return 0;
}

Следует отметить, что, как и некоторые вещи в Windows, они работают, потому что могут, и терпят неудачу, потому что хотят (или, по крайней мере, так я думаю), файл .cat может содержать гораздо больше вещей, но если функция говорит свое вот так, вот оно

avatar
YangXiaoPo - MSFT
7 июля 2021 в 06:53
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;
        }
    }

    ...
}
DougN
8 июля 2021 в 02:25
0

Вы пробовали это на C:\WIndows\System32\cmd.exe? Было ли UseStrongSigPolicy истинным или ложным? В ложном случае BCRYPT_SHA256_ALGORITHM все еще используется в приведенном выше коде.

YangXiaoPo - MSFT
8 июля 2021 в 03:22
0

Аргументы команды: -p -c C:\Windows\System32\cmd.exe

DougN
8 июля 2021 в 14:50
0

Я не заметил вышеуказанного изменения szOID_CERT_STRONG_KEY_OS_1, но даже с этим изменением WinVerifyTrust никогда не возвращает ERROR_SUCCESS с найденными каталогами. Учитывая, что вы упоминаете использование -p -c в командной строке, я думаю, вы используете codesigning.cpp из приведенной выше ссылки. Это проверяет файл в каталоге, но фактически не вызывает WinVerifyTrust.