Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting WinVerifyTrust to work with catalog signed files such as cmd.exe

Thanks to a very old post available here

https://web.archive.org/web/20140217003950/http://forum.sysinternals.com/topic16893_post83634.html

I came across a function that will call WinVerifyTrust on a file to check an embedded signature, and if that fails, finds the appropriate system catalog file and checks it with another call to WinVerifyTrust.

However, testing with C:\Windows\System32\cmd.exe fails. Note that the test app is 64-bit so file redirection is not an issue.

Comparing the output of the function with Microsoft's Sigcheck utility, the function has the correct file hash, as well as finds the correct catalog file. However, when WinVerifyTrust is called with the catalog information, it still fails with

TRUST_E_BAD_DIGEST 0x80096010 //The digital signature of the object did not verify.

Interestingly, when the UI is enable with

dwUIChoice = WTD_UI_ALL

the failure code is different:

TRUST_E_SUBJECT_NOT_TRUSTED 0x800B0004 // The subject is not trusted for the specified action.

But Sigcheck.exe and Signtool.exe both say it is trusted.

Also, if dwUIChoice = WTD_UI_ALL is set, I get an error pop-up below, with a link to what looks like a very valid certificate.

enter image description here

So why is WinVerifyTrust indicating the signature is bad on cmd.exe?

The code is below and I welcome any input on what could be fixed:

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;
}

Note that to use the above function you'll need:

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

// Link with the Wintrust.lib file.
#pragma comment (lib, "wintrust")

UPDATE: From a sample available here

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

I just discovered using

CryptCATAdminAcquireContext2(&hCatAdmin, NULL, BCRYPT_SHA256_ALGORITHM, NULL, 0))

instead of CryptCATAdminAcquireContext, AND CryptCATAdminCalcHashFromFileHandle2 instead of CryptCATAdminCalcHashFromFileHandle works on my Windows Server 2019.

So now the question becomes 'why?', and will BCRYPT_SHA256_ALGORITHM be an appropriate parameter for other OS versions that code might possibly run on (Win 7? Win 8? Server 2012 R2?)

UPDATE 2

Docs for CryptCATAdminAcquireContext2 say: "This function enables you to choose, or chooses for you, the hash algorithm to be used in functions that require the catalog administrator context. Although you can set the name of the hashing algorithm, we recommend that you let the function determine the algorithm. Doing so protects your application from hard coding algorithms that may become untrusted in the future."

However, setting NULL (as recommended in the docs) instead of BCRYPT_SHA256_ALGORITHM causes the previously seen failures. This is very brittle and seems to be OS-specific :(

Anyway to make this work reliably across OS versions?

UPDATE 3 It's now obvious why this doesn't work correctly. Here is a list of hashes from cmd.exe shown by sigcheck

cmd.exe hashes

When calling CryptCATAdminAcquireContext2 with NULL you get the PESHA1 hash from CryptCATAdminCalcHashFromFileHandle2. When calling with BCRYPT_SHA256_ALGORITHM instead, you get the PE256 hash.

That all makes sense. Unfortunately, the catalog files only contain the PE256 hash. So if you don't know what hashing algorithm the catalog files contain, the only solution I can think of is to loop through all this code with various algorithms for CryptCATAdminAcquireContext2, and keep hasing the file over and over, until a hash is found that exists in the catalog file.

What is NOT clear, is how does CryptCATAdminEnumCatalogFromHash find the same catalog files using the PESHA1 hash, even though the hash isn't found in the catalog file? There must be some additional information somewhere that allows that to work.

like image 904
DougN Avatar asked Jul 01 '21 19:07

DougN


1 Answers

I Tested the Following Code Which sets NULL (as recommended in the docs) instead of BCRYPT_SHA256_ALGORITHM. It's No Problem.
Although The Document says The default hashing algorithm may change in future Windows versions, It’s necessary to maintain consistent behavior For 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;
        }
    }

    ...
}
like image 106
YangXiaoPo - MSFT Avatar answered Nov 18 '22 12:11

YangXiaoPo - MSFT