Mik*_*keF 5 c++ winapi code-signing authenticode digital-signature
我一直在努力修改来自 Microsoft 的此代码示例,该示例显示(有些过时)如何从可执行文件中检索代码签名信息。它可以工作,但如果二进制文件是双重签名的,则它不会检索信息。
所以我做了一些研究并尝试重写它以使其识别 Windows 中许多现代可执行文件中存在的双重签名。不幸的是,很少有(模糊的)建议可用(1),(2),例如使用UnauthenticatedAttributes和szOID_NESTED_SIGNATURE(无论这意味着什么)但仅用于检索时间戳的建议。
所以我试图重写那个微软代码,这是我得到的:
(要构建它,只需将其复制到 Visual Studio 控制台项目中并更改 .exe 文件路径。处理双重签名的代码在PrintDualSignatureInfo()函数中。)
#include "stdafx.h"
//SOURCE:
// https://support.microsoft.com/en-us/help/323809/how-to-get-information-from-authenticode-signed-executables
//
#include <windows.h>
#include <wincrypt.h>
#include <wintrust.h>
#include <stdio.h>
#include <tchar.h>
#include <atlconv.h>
#pragma comment(lib, "crypt32.lib")
#define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)
typedef struct {
LPWSTR lpszProgramName;
LPWSTR lpszPublisherLink;
LPWSTR lpszMoreInfoLink;
} SPROG_PUBLISHERINFO, *PSPROG_PUBLISHERINFO;
BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,
PSPROG_PUBLISHERINFO Info);
BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st);
BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext);
BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo,
PCMSG_SIGNER_INFO *pCounterSignerInfo);
void PrintSignatureAlgorithm(CRYPT_ALGORITHM_IDENTIFIER* pSigAlgo);
void PrintDualSignatureInfo(PCMSG_SIGNER_INFO pSignerInfo);
void PrintCertificateInfo(HCERTSTORE hStore, PCMSG_SIGNER_INFO pSignerInfo, LPCTSTR pStrCertName);
int main()
{
WCHAR szFileName[MAX_PATH];
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
BOOL fResult;
DWORD dwEncoding, dwContentType, dwFormatType;
PCMSG_SIGNER_INFO pSignerInfo = NULL;
DWORD dwSignerInfo;
SPROG_PUBLISHERINFO ProgPubInfo;
ZeroMemory(&ProgPubInfo, sizeof(ProgPubInfo));
__try
{
LPCTSTR pExePath = L"C:\\Users\\UserName\\Downloads\\procmon.exe"; //works
//pExePath = L"C:\\Users\\UserName\\Downloads\\putty.exe"; //doesnt work
lstrcpynW(szFileName, pExePath, MAX_PATH);
// Get message handle and store handle from the signed file.
fResult = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
szFileName,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
&dwEncoding,
&dwContentType,
&dwFormatType,
&hStore,
&hMsg,
NULL);
if (!fResult)
{
_tprintf(_T("CryptQueryObject failed with %x\n"), GetLastError());
__leave;
}
// Get signer information size.
fResult = CryptMsgGetParam(hMsg,
CMSG_SIGNER_INFO_PARAM,
0,
NULL,
&dwSignerInfo);
if (!fResult)
{
_tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());
__leave;
}
// Allocate memory for signer information.
pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo);
if (!pSignerInfo)
{
_tprintf(_T("Unable to allocate memory for Signer Info.\n"));
__leave;
}
// Get Signer Information.
fResult = CryptMsgGetParam(hMsg,
CMSG_SIGNER_INFO_PARAM,
0,
(PVOID)pSignerInfo,
&dwSignerInfo);
if (!fResult)
{
_tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());
__leave;
}
// Get program name and publisher information from
// signer info structure.
if (GetProgAndPublisherInfo(pSignerInfo, &ProgPubInfo))
{
if (ProgPubInfo.lpszProgramName != NULL)
{
wprintf(L"Program Name : %s\n",
ProgPubInfo.lpszProgramName);
}
if (ProgPubInfo.lpszPublisherLink != NULL)
{
wprintf(L"Publisher Link : %s\n",
ProgPubInfo.lpszPublisherLink);
}
if (ProgPubInfo.lpszMoreInfoLink != NULL)
{
wprintf(L"MoreInfo Link : %s\n",
ProgPubInfo.lpszMoreInfoLink);
}
}
_tprintf(_T("\n"));
// Print Signer certificate information.
PrintCertificateInfo(hStore, pSignerInfo, L"Signer Certificate");
//Look for dual signature
PrintDualSignatureInfo(pSignerInfo);
}
__finally
{
// Clean up.
if (ProgPubInfo.lpszProgramName != NULL)
LocalFree(ProgPubInfo.lpszProgramName);
if (ProgPubInfo.lpszPublisherLink != NULL)
LocalFree(ProgPubInfo.lpszPublisherLink);
if (ProgPubInfo.lpszMoreInfoLink != NULL)
LocalFree(ProgPubInfo.lpszMoreInfoLink);
if (pSignerInfo != NULL) LocalFree(pSignerInfo);
if (hStore != NULL) CertCloseStore(hStore, 0);
if (hMsg != NULL) CryptMsgClose(hMsg);
}
return 0;
}
void PrintCertificateInfo(HCERTSTORE hStore, PCMSG_SIGNER_INFO pSignerInfo, LPCTSTR pStrCertName)
{
if(hStore &&
pSignerInfo)
{
PCCERT_CONTEXT pCertContext = NULL;
CERT_INFO CertInfo = {0};
PCMSG_SIGNER_INFO pCounterSignerInfo = NULL;
SYSTEMTIME st;
__try
{
// Search for the signer certificate in the temporary
// certificate store.
CertInfo.Issuer = pSignerInfo->Issuer;
CertInfo.SerialNumber = pSignerInfo->SerialNumber;
pCertContext = CertFindCertificateInStore(hStore,
ENCODING,
0,
CERT_FIND_SUBJECT_CERT,
(PVOID)&CertInfo,
NULL);
if (!pCertContext)
{
_tprintf(_T("CertFindCertificateInStore failed with %x\n"),
GetLastError());
__leave;
}
// Print Signer certificate information.
_tprintf(L"%s:\n\n", pStrCertName); //(_T("Signer Certificate:\n\n"));
PrintCertificateInfo(pCertContext);
_tprintf(_T("\n"));
// Get the timestamp certificate signerinfo structure.
if (GetTimeStampSignerInfo(pSignerInfo, &pCounterSignerInfo))
{
// Search for Timestamp certificate in the temporary
// certificate store.
CertInfo.Issuer = pCounterSignerInfo->Issuer;
CertInfo.SerialNumber = pCounterSignerInfo->SerialNumber;
pCertContext = CertFindCertificateInStore(hStore,
ENCODING,
0,
CERT_FIND_SUBJECT_CERT,
(PVOID)&CertInfo,
NULL);
if (!pCertContext)
{
_tprintf(_T("CertFindCertificateInStore failed with %x\n"),
GetLastError());
__leave;
}
// Print timestamp certificate information.
_tprintf(_T("TimeStamp Certificate:\n\n"));
PrintCertificateInfo(pCertContext);
_tprintf(_T("\n"));
// Find Date of timestamp.
if (GetDateOfTimeStamp(pCounterSignerInfo, &st))
{
_tprintf(_T("Date of TimeStamp : %02d/%02d/%04d %02d:%02d\n"),
st.wMonth,
st.wDay,
st.wYear,
st.wHour,
st.wMinute);
}
_tprintf(_T("\n"));
}
}
__finally
{
if (pCounterSignerInfo != NULL)
LocalFree(pCounterSignerInfo);
if (pCertContext != NULL)
CertFreeCertificateContext(pCertContext);
}
}
}
void PrintDualSignatureInfo(PCMSG_SIGNER_INFO pSignerInfo)
{
if(pSignerInfo)
{
for(DWORD a = 0; a < pSignerInfo->UnauthAttrs.cAttr; a++)
{
if(pSignerInfo->UnauthAttrs.rgAttr[a].pszObjId &&
lstrcmpiA(pSignerInfo->UnauthAttrs.rgAttr[a].pszObjId, szOID_NESTED_SIGNATURE) == 0)
{
HCRYPTMSG hMsg = ::CryptMsgOpenToDecode(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, 0, NULL, NULL, NULL);
if(hMsg)
{
if(::CryptMsgUpdate(hMsg,
pSignerInfo->UnauthAttrs.rgAttr[a].rgValue->pbData,
pSignerInfo->UnauthAttrs.rgAttr[a].rgValue->cbData,
TRUE))
{
DWORD dwSignerInfo = 0;
::CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo);
if(dwSignerInfo != 0)
{
PCMSG_SIGNER_INFO pSignerInfo2 = (PCMSG_SIGNER_INFO)new (std::nothrow) BYTE[dwSignerInfo];
if(pSignerInfo2)
{
if(::CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM,
0, (PVOID)pSignerInfo2, &dwSignerInfo))
{
CRYPT_DATA_BLOB p7Data;
p7Data.cbData = pSignerInfo->UnauthAttrs.rgAttr[a].rgValue->cbData;
p7Data.pbData = pSignerInfo->UnauthAttrs.rgAttr[a].rgValue->pbData;
HCERTSTORE hStore = ::CertOpenStore(CERT_STORE_PROV_PKCS7, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, 0, &p7Data);
if(hStore)
{
// Print Signer certificate information.
PrintCertificateInfo(hStore, pSignerInfo2, L"Dual Signer Certificate");
//Close
::CertCloseStore(hStore, CERT_CLOSE_STORE_FORCE_FLAG);
}
}
//Free mem
delete[] pSignerInfo2;
pSignerInfo2 = NULL;
}
}
}
//Close message
::CryptMsgClose(hMsg);
}
}
}
}
}
void PrintSignatureAlgorithm(CRYPT_ALGORITHM_IDENTIFIER* pSigAlgo)
{
if(pSigAlgo &&
pSigAlgo->pszObjId)
{
PCCRYPT_OID_INFO pCOI = ::CryptFindOIDInfo(CRYPT_OID_INFO_OID_KEY, pSigAlgo->pszObjId, 0);
if(pCOI &&
pCOI->pwszName)
{
_tprintf(L"%s", pCOI->pwszName);
}
else
{
USES_CONVERSION;
_tprintf(L"%s", A2W(pSigAlgo->pszObjId));
}
}
}
BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext)
{
BOOL fReturn = FALSE;
LPTSTR szName = NULL;
DWORD dwData;
__try
{
// Print Serial Number.
_tprintf(_T("Serial Number: "));
dwData = pCertContext->pCertInfo->SerialNumber.cbData;
for (DWORD n = 0; n < dwData; n++)
{
_tprintf(_T("%02x "),
pCertContext->pCertInfo->SerialNumber.pbData[dwData - (n + 1)]);
}
_tprintf(_T("\n"));
//Hashing algoriths
_tprintf(L"Signature Algorithm: ");
PrintSignatureAlgorithm(&pCertContext->pCertInfo->SignatureAlgorithm);
_tprintf(_T("\n"));
// Get Issuer name size.
if (!(dwData = CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
NULL,
0)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// Allocate memory for Issuer name.
szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
if (!szName)
{
_tprintf(_T("Unable to allocate memory for issuer name.\n"));
__leave;
}
// Get Issuer name.
if (!(CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
szName,
dwData)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// print Issuer name.
_tprintf(_T("Issuer Name: %s\n"), szName);
LocalFree(szName);
szName = NULL;
// Get Subject name size.
if (!(dwData = CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
NULL,
0)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// Allocate memory for subject name.
szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
if (!szName)
{
_tprintf(_T("Unable to allocate memory for subject name.\n"));
__leave;
}
// Get subject name.
if (!(CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
szName,
dwData)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// Print Subject Name.
_tprintf(_T("Subject Name: %s\n"), szName);
fReturn = TRUE;
}
__finally
{
if (szName != NULL) LocalFree(szName);
}
return fReturn;
}
LPWSTR AllocateAndCopyWideString(LPCWSTR inputString)
{
LPWSTR outputString = NULL;
outputString = (LPWSTR)LocalAlloc(LPTR,
(wcslen(inputString) + 1) * sizeof(WCHAR));
if (outputString != NULL)
{
lstrcpyW(outputString, inputString);
}
return outputString;
}
BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,
PSPROG_PUBLISHERINFO Info)
{
BOOL fReturn = FALSE;
PSPC_SP_OPUS_INFO OpusInfo = NULL;
DWORD dwData;
BOOL fResult;
__try
{
// Loop through authenticated attributes and find
// SPC_SP_OPUS_INFO_OBJID OID.
for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)
{
if (lstrcmpA(SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)
{
// Get Size of SPC_SP_OPUS_INFO structure.
fResult = CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
NULL,
&dwData);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
// Allocate memory for SPC_SP_OPUS_INFO structure.
OpusInfo = (PSPC_SP_OPUS_INFO)LocalAlloc(LPTR, dwData);
if (!OpusInfo)
{
_tprintf(_T("Unable to allocate memory for Publisher Info.\n"));
__leave;
}
// Decode and get SPC_SP_OPUS_INFO structure.
fResult = CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
OpusInfo,
&dwData);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
// Fill in Program Name if present.
if (OpusInfo->pwszProgramName)
{
Info->lpszProgramName =
AllocateAndCopyWideString(OpusInfo->pwszProgramName);
}
else
Info->lpszProgramName = NULL;
// Fill in Publisher Information if present.
if (OpusInfo->pPublisherInfo)
{
switch (OpusInfo->pPublisherInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
Info->lpszPublisherLink =
AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszUrl);
break;
case SPC_FILE_LINK_CHOICE:
Info->lpszPublisherLink =
AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszFile);
break;
default:
Info->lpszPublisherLink = NULL;
break;
}
}
else
{
Info->lpszPublisherLink = NULL;
}
// Fil
输出中的摘要算法与文件属性中显示的摘要算法不匹配的原因是,您在输出中显示的是签名证书链中第一个证书的摘要算法,而不是 Authenticode 的摘要算法签名本身。就像这样:
Run Code Online (Sandbox Code Playgroud)+-----------+ +-------------------+ +---------+ | Root Cert | signs | Intermediate Cert | signs | PE Data | |-----------|=========>|-------------------|=========>|---------| | SHA256 | | SHA256 | | SHA1 | +-----------+ +-------------------+ +---------+ ^ ^ | | You are showing But you this want to show this
Authenticode 的工作方式是,首先使用有时(微软)称为“摘要算法”的方法来计算文件摘要。然后使用提供证书的签名密钥对该摘要进行签名。但该证书本身是通过使用所谓的“签名摘要”计算其摘要并使用证书链中更高级别的证书中的密钥对其进行签名等来进行签名的。
通过该函数可以获得证书链中的第一个证书CertFindCertificateInStore。然后,您应该CertFindCertificateInStore在 while 循环中继续调用以获取其他证书。您在代码中所做的就是获取证书链中的第一个证书(带有CertFindCertificateInStore)并打印其签名摘要算法。相反,您想要做的是获取并打印文件签名的摘要算法。CryptMsgGetParam您可以使用标志来做到这一点CMSG_SIGNER_INFO_PARAM,并且您确实获得了它,只是不打印它。
另一种思考这个问题的方法是根据MSG_SIGNER_INFO和之间的关系CERT_INFO。这不是信息需要匹配的 1-1 关系。它更像是:
Run Code Online (Sandbox Code Playgroud)+---------------+ +---------------+ | SIGNER_INFO 1 | | SIGNER_INFO 2 | |---------------| |---------------| | SHA1 | | SHA256 | +---------------+ +---------------+ | | | +-------------+ | +-------------+ +---| CERT_INFO 1 | +---| CERT_INFO 3 | |-------------| |-------------| | SHA256 | | SHA256 | +-------------+ +-------------+ | | +-------------+ +-------------+ | CERT_INFO 2 | | CERT_INFO 4 | |-------------| |-------------| | SHA1 | | SHA1 | +-------------+ +-------------+