/*
* Process Hacker -
* image verification
*
* Copyright (C) 2009-2013 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see .
*/
#include
#include
#include
_CryptCATAdminCalcHashFromFileHandle CryptCATAdminCalcHashFromFileHandle;
_CryptCATAdminCalcHashFromFileHandle2 CryptCATAdminCalcHashFromFileHandle2;
_CryptCATAdminAcquireContext CryptCATAdminAcquireContext;
_CryptCATAdminAcquireContext2 CryptCATAdminAcquireContext2;
_CryptCATAdminEnumCatalogFromHash CryptCATAdminEnumCatalogFromHash;
_CryptCATCatalogInfoFromContext CryptCATCatalogInfoFromContext;
_CryptCATAdminReleaseCatalogContext CryptCATAdminReleaseCatalogContext;
_CryptCATAdminReleaseContext CryptCATAdminReleaseContext;
_WTHelperProvDataFromStateData WTHelperProvDataFromStateData_I;
_WTHelperGetProvSignerFromChain WTHelperGetProvSignerFromChain_I;
_WinVerifyTrust WinVerifyTrust_I;
_CertNameToStr CertNameToStr_I;
_CertDuplicateCertificateContext CertDuplicateCertificateContext_I;
_CertFreeCertificateContext CertFreeCertificateContext_I;
static PH_INITONCE PhpVerifyInitOnce = PH_INITONCE_INIT;
static GUID WinTrustActionGenericVerifyV2 = WINTRUST_ACTION_GENERIC_VERIFY_V2;
static GUID DriverActionVerify = DRIVER_ACTION_VERIFY;
static VOID PhpVerifyInitialization(
VOID
)
{
HMODULE wintrust;
HMODULE crypt32;
wintrust = LoadLibrary(L"wintrust.dll");
crypt32 = LoadLibrary(L"crypt32.dll");
CryptCATAdminCalcHashFromFileHandle = (PVOID)GetProcAddress(wintrust, "CryptCATAdminCalcHashFromFileHandle");
CryptCATAdminCalcHashFromFileHandle2 = (PVOID)GetProcAddress(wintrust, "CryptCATAdminCalcHashFromFileHandle2");
CryptCATAdminAcquireContext = (PVOID)GetProcAddress(wintrust, "CryptCATAdminAcquireContext");
CryptCATAdminAcquireContext2 = (PVOID)GetProcAddress(wintrust, "CryptCATAdminAcquireContext2");
CryptCATAdminEnumCatalogFromHash = (PVOID)GetProcAddress(wintrust, "CryptCATAdminEnumCatalogFromHash");
CryptCATCatalogInfoFromContext = (PVOID)GetProcAddress(wintrust, "CryptCATCatalogInfoFromContext");
CryptCATAdminReleaseCatalogContext = (PVOID)GetProcAddress(wintrust, "CryptCATAdminReleaseCatalogContext");
CryptCATAdminReleaseContext = (PVOID)GetProcAddress(wintrust, "CryptCATAdminReleaseContext");
WTHelperProvDataFromStateData_I = (PVOID)GetProcAddress(wintrust, "WTHelperProvDataFromStateData");
WTHelperGetProvSignerFromChain_I = (PVOID)GetProcAddress(wintrust, "WTHelperGetProvSignerFromChain");
WinVerifyTrust_I = (PVOID)GetProcAddress(wintrust, "WinVerifyTrust");
CertNameToStr_I = (PVOID)GetProcAddress(crypt32, "CertNameToStrW");
CertDuplicateCertificateContext_I = (PVOID)GetProcAddress(crypt32, "CertDuplicateCertificateContext");
CertFreeCertificateContext_I = (PVOID)GetProcAddress(crypt32, "CertFreeCertificateContext");
}
VERIFY_RESULT PhpStatusToVerifyResult(
_In_ LONG Status
)
{
switch (Status)
{
case 0:
return VrTrusted;
case TRUST_E_NOSIGNATURE:
return VrNoSignature;
case CERT_E_EXPIRED:
return VrExpired;
case CERT_E_REVOKED:
return VrRevoked;
case TRUST_E_EXPLICIT_DISTRUST:
return VrDistrust;
case CRYPT_E_SECURITY_SETTINGS:
return VrSecuritySettings;
case TRUST_E_BAD_DIGEST:
return VrBadSignature;
default:
return VrSecuritySettings;
}
}
BOOLEAN PhpGetSignaturesFromStateData(
_In_ HANDLE StateData,
_Out_ PCERT_CONTEXT **Signatures,
_Out_ PULONG NumberOfSignatures
)
{
PCRYPT_PROVIDER_DATA provData;
PCRYPT_PROVIDER_SGNR sgnr;
PCERT_CONTEXT *signatures;
ULONG i;
ULONG numberOfSignatures;
ULONG index;
provData = WTHelperProvDataFromStateData_I(StateData);
if (!provData)
{
*Signatures = NULL;
*NumberOfSignatures = 0;
return FALSE;
}
i = 0;
numberOfSignatures = 0;
while (sgnr = WTHelperGetProvSignerFromChain_I(provData, i, FALSE, 0))
{
if (sgnr->csCertChain != 0)
numberOfSignatures++;
i++;
}
if (numberOfSignatures != 0)
{
signatures = PhAllocate(numberOfSignatures * sizeof(PCERT_CONTEXT));
i = 0;
index = 0;
while (sgnr = WTHelperGetProvSignerFromChain_I(provData, i, FALSE, 0))
{
if (sgnr->csCertChain != 0)
signatures[index++] = (PCERT_CONTEXT)CertDuplicateCertificateContext_I(sgnr->pasCertChain[0].pCert);
i++;
}
}
else
{
signatures = NULL;
}
*Signatures = signatures;
*NumberOfSignatures = numberOfSignatures;
return TRUE;
}
VOID PhpViewSignerInfo(
_In_ PPH_VERIFY_FILE_INFO Information,
_In_ HANDLE StateData
)
{
static PH_INITONCE initOnce = PH_INITONCE_INIT;
static _CryptUIDlgViewSignerInfo cryptUIDlgViewSignerInfo;
if (PhBeginInitOnce(&initOnce))
{
HMODULE cryptui = LoadLibrary(L"cryptui.dll");
cryptUIDlgViewSignerInfo = (PVOID)GetProcAddress(cryptui, "CryptUIDlgViewSignerInfoW");
PhEndInitOnce(&initOnce);
}
if (cryptUIDlgViewSignerInfo)
{
CRYPTUI_VIEWSIGNERINFO_STRUCT viewSignerInfo = { sizeof(CRYPTUI_VIEWSIGNERINFO_STRUCT) };
PCRYPT_PROVIDER_DATA provData;
PCRYPT_PROVIDER_SGNR sgnr;
if (!(provData = WTHelperProvDataFromStateData_I(StateData)))
return;
if (!(sgnr = WTHelperGetProvSignerFromChain_I(provData, 0, FALSE, 0)))
return;
viewSignerInfo.hwndParent = Information->hWnd;
viewSignerInfo.pSignerInfo = sgnr->psSigner;
viewSignerInfo.hMsg = provData->hMsg;
viewSignerInfo.pszOID = szOID_PKIX_KP_CODE_SIGNING;
cryptUIDlgViewSignerInfo(&viewSignerInfo);
}
}
VERIFY_RESULT PhpVerifyFile(
_In_ PPH_VERIFY_FILE_INFO Information,
_In_ HANDLE FileHandle,
_In_ ULONG UnionChoice,
_In_ PVOID UnionData,
_In_ PGUID ActionId,
_In_opt_ PVOID PolicyCallbackData,
_Out_ PCERT_CONTEXT **Signatures,
_Out_ PULONG NumberOfSignatures
)
{
LONG status;
WINTRUST_DATA trustData = { 0 };
trustData.cbStruct = sizeof(WINTRUST_DATA);
trustData.pPolicyCallbackData = PolicyCallbackData;
trustData.dwUIChoice = WTD_UI_NONE;
trustData.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN;
trustData.dwUnionChoice = UnionChoice;
trustData.dwStateAction = WTD_STATEACTION_VERIFY;
trustData.dwProvFlags = WTD_SAFER_FLAG;
trustData.pFile = UnionData;
if (UnionChoice == WTD_CHOICE_CATALOG)
trustData.pCatalog = UnionData;
if (Information->Flags & PH_VERIFY_PREVENT_NETWORK_ACCESS)
{
trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
if (WindowsVersion >= WINDOWS_VISTA)
trustData.dwProvFlags |= WTD_CACHE_ONLY_URL_RETRIEVAL;
else
trustData.dwProvFlags |= WTD_REVOCATION_CHECK_NONE;
}
status = WinVerifyTrust_I(NULL, ActionId, &trustData);
PhpGetSignaturesFromStateData(trustData.hWVTStateData, Signatures, NumberOfSignatures);
if (status == 0 && (Information->Flags & PH_VERIFY_VIEW_PROPERTIES))
PhpViewSignerInfo(Information, trustData.hWVTStateData);
// Close the state data.
trustData.dwStateAction = WTD_STATEACTION_CLOSE;
WinVerifyTrust_I(NULL, ActionId, &trustData);
return PhpStatusToVerifyResult(status);
}
BOOLEAN PhpCalculateFileHash(
_In_ HANDLE FileHandle,
_In_ PWSTR HashAlgorithm,
_Out_ PUCHAR *FileHash,
_Out_ PULONG FileHashLength,
_Out_ HANDLE *CatAdminHandle
)
{
HANDLE catAdminHandle;
PUCHAR fileHash;
ULONG fileHashLength;
if (CryptCATAdminAcquireContext2)
{
if (!CryptCATAdminAcquireContext2(&catAdminHandle, &DriverActionVerify, HashAlgorithm, NULL, 0))
return FALSE;
}
else
{
if (!CryptCATAdminAcquireContext(&catAdminHandle, &DriverActionVerify, 0))
return FALSE;
}
fileHashLength = 32;
fileHash = PhAllocate(fileHashLength);
if (CryptCATAdminCalcHashFromFileHandle2)
{
if (!CryptCATAdminCalcHashFromFileHandle2(catAdminHandle, FileHandle, &fileHashLength, fileHash, 0))
{
PhFree(fileHash);
fileHash = PhAllocate(fileHashLength);
if (!CryptCATAdminCalcHashFromFileHandle2(catAdminHandle, FileHandle, &fileHashLength, fileHash, 0))
{
CryptCATAdminReleaseContext(catAdminHandle, 0);
PhFree(fileHash);
return FALSE;
}
}
}
else
{
if (!CryptCATAdminCalcHashFromFileHandle(FileHandle, &fileHashLength, fileHash, 0))
{
PhFree(fileHash);
fileHash = PhAllocate(fileHashLength);
if (!CryptCATAdminCalcHashFromFileHandle(FileHandle, &fileHashLength, fileHash, 0))
{
CryptCATAdminReleaseContext(catAdminHandle, 0);
PhFree(fileHash);
return FALSE;
}
}
}
*FileHash = fileHash;
*FileHashLength = fileHashLength;
*CatAdminHandle = catAdminHandle;
return TRUE;
}
VERIFY_RESULT PhpVerifyFileFromCatalog(
_In_ PPH_VERIFY_FILE_INFO Information,
_In_ HANDLE FileHandle,
_In_opt_ PWSTR HashAlgorithm,
_Out_ PCERT_CONTEXT **Signatures,
_Out_ PULONG NumberOfSignatures
)
{
VERIFY_RESULT verifyResult = VrNoSignature;
PCERT_CONTEXT *signatures;
ULONG numberOfSignatures;
WINTRUST_CATALOG_INFO catalogInfo = { 0 };
LARGE_INTEGER fileSize;
ULONG fileSizeLimit;
PUCHAR fileHash;
ULONG fileHashLength;
PPH_STRING fileHashTag;
HANDLE catAdminHandle;
HANDLE catInfoHandle;
ULONG i;
*Signatures = NULL;
*NumberOfSignatures = 0;
if (!NT_SUCCESS(PhGetFileSize(FileHandle, &fileSize)))
return VrNoSignature;
signatures = NULL;
numberOfSignatures = 0;
if (Information->FileSizeLimitForHash != -1)
{
fileSizeLimit = PH_VERIFY_DEFAULT_SIZE_LIMIT;
if (Information->FileSizeLimitForHash != 0)
fileSizeLimit = Information->FileSizeLimitForHash;
if (fileSize.QuadPart > fileSizeLimit)
return VrNoSignature;
}
if (PhpCalculateFileHash(FileHandle, HashAlgorithm, &fileHash, &fileHashLength, &catAdminHandle))
{
fileHashTag = PhBufferToHexStringEx(fileHash, fileHashLength, TRUE);
// Search the system catalogs.
catInfoHandle = CryptCATAdminEnumCatalogFromHash(
catAdminHandle,
fileHash,
fileHashLength,
0,
NULL
);
if (catInfoHandle)
{
CATALOG_INFO ci = { 0 };
DRIVER_VER_INFO verInfo = { 0 };
if (CryptCATCatalogInfoFromContext(catInfoHandle, &ci, 0))
{
// Disable OS version checking by passing in a DRIVER_VER_INFO structure.
verInfo.cbStruct = sizeof(DRIVER_VER_INFO);
catalogInfo.cbStruct = sizeof(catalogInfo);
catalogInfo.pcwszCatalogFilePath = ci.wszCatalogFile;
catalogInfo.pcwszMemberFilePath = Information->FileName;
catalogInfo.pcwszMemberTag = fileHashTag->Buffer;
catalogInfo.pbCalculatedFileHash = fileHash;
catalogInfo.cbCalculatedFileHash = fileHashLength;
catalogInfo.hCatAdmin = catAdminHandle;
verifyResult = PhpVerifyFile(Information, FileHandle, WTD_CHOICE_CATALOG, &catalogInfo, &DriverActionVerify, &verInfo, &signatures, &numberOfSignatures);
if (verInfo.pcSignerCertContext)
CertFreeCertificateContext_I(verInfo.pcSignerCertContext);
}
CryptCATAdminReleaseCatalogContext(catAdminHandle, catInfoHandle, 0);
}
else
{
// Search any user-supplied catalogs.
for (i = 0; i < Information->NumberOfCatalogFileNames; i++)
{
PhFreeVerifySignatures(signatures, numberOfSignatures);
catalogInfo.cbStruct = sizeof(catalogInfo);
catalogInfo.pcwszCatalogFilePath = Information->CatalogFileNames[i];
catalogInfo.pcwszMemberFilePath = Information->FileName;
catalogInfo.pcwszMemberTag = fileHashTag->Buffer;
catalogInfo.pbCalculatedFileHash = fileHash;
catalogInfo.cbCalculatedFileHash = fileHashLength;
catalogInfo.hCatAdmin = catAdminHandle;
verifyResult = PhpVerifyFile(Information, FileHandle, WTD_CHOICE_CATALOG, &catalogInfo, &WinTrustActionGenericVerifyV2, NULL, &signatures, &numberOfSignatures);
if (verifyResult == VrTrusted)
break;
}
}
PhDereferenceObject(fileHashTag);
PhFree(fileHash);
CryptCATAdminReleaseContext(catAdminHandle, 0);
}
*Signatures = signatures;
*NumberOfSignatures = numberOfSignatures;
return verifyResult;
}
NTSTATUS PhVerifyFileEx(
_In_ PPH_VERIFY_FILE_INFO Information,
_Out_ VERIFY_RESULT *VerifyResult,
_Out_opt_ PCERT_CONTEXT **Signatures,
_Out_opt_ PULONG NumberOfSignatures
)
{
NTSTATUS status;
HANDLE fileHandle;
VERIFY_RESULT verifyResult;
PCERT_CONTEXT *signatures;
ULONG numberOfSignatures;
WINTRUST_FILE_INFO fileInfo = { 0 };
if (PhBeginInitOnce(&PhpVerifyInitOnce))
{
PhpVerifyInitialization();
PhEndInitOnce(&PhpVerifyInitOnce);
}
// Make sure we have successfully imported the required functions.
if (
!CryptCATAdminCalcHashFromFileHandle ||
!CryptCATAdminAcquireContext ||
!CryptCATAdminEnumCatalogFromHash ||
!CryptCATCatalogInfoFromContext ||
!CryptCATAdminReleaseCatalogContext ||
!CryptCATAdminReleaseContext ||
!WinVerifyTrust_I ||
!WTHelperProvDataFromStateData_I ||
!WTHelperGetProvSignerFromChain_I ||
!CertNameToStr_I ||
!CertDuplicateCertificateContext_I ||
!CertFreeCertificateContext_I
)
return STATUS_NOT_SUPPORTED;
if (!NT_SUCCESS(status = PhCreateFileWin32(
&fileHandle,
Information->FileName,
FILE_GENERIC_READ,
0,
FILE_SHARE_READ | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
)))
return status;
fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
fileInfo.pcwszFilePath = Information->FileName;
fileInfo.hFile = fileHandle;
verifyResult = PhpVerifyFile(Information, fileHandle, WTD_CHOICE_FILE, &fileInfo, &WinTrustActionGenericVerifyV2, NULL, &signatures, &numberOfSignatures);
if (verifyResult == VrNoSignature)
{
if (CryptCATAdminAcquireContext2 && CryptCATAdminCalcHashFromFileHandle2)
{
PhFreeVerifySignatures(signatures, numberOfSignatures);
verifyResult = PhpVerifyFileFromCatalog(Information, fileHandle, BCRYPT_SHA256_ALGORITHM, &signatures, &numberOfSignatures);
}
if (verifyResult != VrTrusted)
{
PhFreeVerifySignatures(signatures, numberOfSignatures);
verifyResult = PhpVerifyFileFromCatalog(Information, fileHandle, NULL, &signatures, &numberOfSignatures);
}
}
*VerifyResult = verifyResult;
if (Signatures)
*Signatures = signatures;
else
PhFreeVerifySignatures(signatures, numberOfSignatures);
if (NumberOfSignatures)
*NumberOfSignatures = numberOfSignatures;
NtClose(fileHandle);
return STATUS_SUCCESS;
}
VOID PhFreeVerifySignatures(
_In_ PCERT_CONTEXT *Signatures,
_In_ ULONG NumberOfSignatures
)
{
ULONG i;
if (Signatures)
{
for (i = 0; i < NumberOfSignatures; i++)
CertFreeCertificateContext_I(Signatures[i]);
PhFree(Signatures);
}
}
PPH_STRING PhpGetCertNameString(
_In_ PCERT_NAME_BLOB Blob
)
{
PPH_STRING string;
ULONG bufferSize;
// CertNameToStr doesn't give us the correct buffer size unless we don't provide a buffer at
// all.
bufferSize = CertNameToStr_I(
X509_ASN_ENCODING,
Blob,
CERT_X500_NAME_STR,
NULL,
0
);
string = PhCreateStringEx(NULL, bufferSize * sizeof(WCHAR));
CertNameToStr_I(
X509_ASN_ENCODING,
Blob,
CERT_X500_NAME_STR,
string->Buffer,
bufferSize
);
PhTrimToNullTerminatorString(string);
return string;
}
PPH_STRING PhpGetX500Value(
_In_ PPH_STRINGREF String,
_In_ PPH_STRINGREF KeyName
)
{
WCHAR keyNamePlusEqualsBuffer[10];
PH_STRINGREF keyNamePlusEquals;
SIZE_T keyNameLength;
PH_STRINGREF firstPart;
PH_STRINGREF remainingPart;
keyNameLength = KeyName->Length / sizeof(WCHAR);
assert(!(keyNameLength > sizeof(keyNamePlusEquals) / sizeof(WCHAR) - 1));
keyNamePlusEquals.Buffer = keyNamePlusEqualsBuffer;
keyNamePlusEquals.Length = (keyNameLength + 1) * sizeof(WCHAR);
memcpy(keyNamePlusEquals.Buffer, KeyName->Buffer, KeyName->Length);
keyNamePlusEquals.Buffer[keyNameLength] = '=';
// Find "Key=".
if (!PhSplitStringRefAtString(String, &keyNamePlusEquals, FALSE, &firstPart, &remainingPart))
return NULL;
if (remainingPart.Length == 0)
return NULL;
// Is the value quoted? If so, return the part inside the quotes.
if (remainingPart.Buffer[0] == '"')
{
PhSkipStringRef(&remainingPart, sizeof(WCHAR));
if (!PhSplitStringRefAtChar(&remainingPart, '"', &firstPart, &remainingPart))
return NULL;
return PhCreateString2(&firstPart);
}
else
{
PhSplitStringRefAtChar(&remainingPart, ',', &firstPart, &remainingPart);
return PhCreateString2(&firstPart);
}
}
PPH_STRING PhGetSignerNameFromCertificate(
_In_ PCERT_CONTEXT Certificate
)
{
PCERT_INFO certInfo;
PH_STRINGREF keyName;
PPH_STRING name;
PPH_STRING value;
// Cert context -> Cert info
certInfo = Certificate->pCertInfo;
if (!certInfo)
return NULL;
// Cert info subject -> Subject X.500 string
name = PhpGetCertNameString(&certInfo->Subject);
// Subject X.500 string -> CN or OU value
PhInitializeStringRef(&keyName, L"CN");
value = PhpGetX500Value(&name->sr, &keyName);
if (!value)
{
PhInitializeStringRef(&keyName, L"OU");
value = PhpGetX500Value(&name->sr, &keyName);
}
PhDereferenceObject(name);
return value;
}
/**
* Verifies a file's digital signature.
*
* \param FileName A file name.
* \param SignerName A variable which receives a pointer to a string containing the signer name. You
* must free the string using PhDereferenceObject() when you no longer need it. Note that the signer
* name may be NULL if it is not valid.
*
* \return A VERIFY_RESULT value.
*/
VERIFY_RESULT PhVerifyFile(
_In_ PWSTR FileName,
_Out_opt_ PPH_STRING *SignerName
)
{
PH_VERIFY_FILE_INFO info = { 0 };
VERIFY_RESULT verifyResult;
PCERT_CONTEXT *signatures;
ULONG numberOfSignatures;
info.FileName = FileName;
info.Flags = PH_VERIFY_PREVENT_NETWORK_ACCESS;
if (NT_SUCCESS(PhVerifyFileEx(&info, &verifyResult, &signatures, &numberOfSignatures)))
{
if (SignerName)
{
*SignerName = NULL;
if (numberOfSignatures != 0)
*SignerName = PhGetSignerNameFromCertificate(signatures[0]);
}
PhFreeVerifySignatures(signatures, numberOfSignatures);
return verifyResult;
}
else
{
if (SignerName)
*SignerName = NULL;
return VrNoSignature;
}
}