gpt4 book ai didi

c++ - 修改代码以从 Windows 中的 PE 可执行文件检索双重签名信息?

转载 作者:塔克拉玛干 更新时间:2023-11-03 01:59:21 25 4
gpt4 key购买 nike

纠结了好久才修改this code sample来自 Microsoft,显示(有点过时)如何从可执行文件中检索代码签名信息的方法。它可以工作,但如果二进制文件是双重签名的,它不会检索信息。

因此我做了一些研究并尝试重写它以使其识别 Windows 中许多现代可执行文件中存在的双重签名。不幸的是,很少有(模糊的)建议可用( 1 ),( 2 ),例如那些使用 UnauthenticatedAttributesszOID_NESTED_SIGNATURE (不管那是什么意思)但是仅检索时间戳。

因此我尝试重写该 Microsoft 代码,结果如下:

(要构建它,只需将它复制到 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;
}

// Fill in More Info if present.
if (OpusInfo->pMoreInfo)
{
switch (OpusInfo->pMoreInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
Info->lpszMoreInfoLink =
AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszUrl);
break;

case SPC_FILE_LINK_CHOICE:
Info->lpszMoreInfoLink =
AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszFile);
break;

default:
Info->lpszMoreInfoLink = NULL;
break;
}
}
else
{
Info->lpszMoreInfoLink = NULL;
}

fReturn = TRUE;

break; // Break from for loop.
} // lstrcmp SPC_SP_OPUS_INFO_OBJID
} // for
}
__finally
{
if (OpusInfo != NULL) LocalFree(OpusInfo);
}

return fReturn;
}

BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st)
{
BOOL fResult;
FILETIME lft, ft;
DWORD dwData;
BOOL fReturn = FALSE;

// Loop through authenticated attributes and find
// szOID_RSA_signingTime OID.
for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)
{
if (lstrcmpA(szOID_RSA_signingTime,
pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)
{
// Decode and get FILETIME structure.
dwData = sizeof(ft);
fResult = CryptDecodeObject(ENCODING,
szOID_RSA_signingTime,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
(PVOID)&ft,
&dwData);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
break;
}

// Convert to local time.
FileTimeToLocalFileTime(&ft, &lft);
FileTimeToSystemTime(&lft, st);

fReturn = TRUE;

break; // Break from for loop.

} //lstrcmp szOID_RSA_signingTime
} // for

return fReturn;
}

BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo, PCMSG_SIGNER_INFO *pCounterSignerInfo)
{
PCCERT_CONTEXT pCertContext = NULL;
BOOL fReturn = FALSE;
BOOL fResult;
DWORD dwSize;

__try
{
*pCounterSignerInfo = NULL;

// Loop through unathenticated attributes for
// szOID_RSA_counterSign OID.
for (DWORD n = 0; n < pSignerInfo->UnauthAttrs.cAttr; n++)
{
if (lstrcmpA(pSignerInfo->UnauthAttrs.rgAttr[n].pszObjId,
szOID_RSA_counterSign) == 0)
{
// Get size of CMSG_SIGNER_INFO structure.
fResult = CryptDecodeObject(ENCODING,
PKCS7_SIGNER_INFO,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,
0,
NULL,
&dwSize);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}

// Allocate memory for CMSG_SIGNER_INFO.
*pCounterSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSize);
if (!*pCounterSignerInfo)
{
_tprintf(_T("Unable to allocate memory for timestamp info.\n"));
__leave;
}

// Decode and get CMSG_SIGNER_INFO structure
// for timestamp certificate.
fResult = CryptDecodeObject(ENCODING,
PKCS7_SIGNER_INFO,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,
0,
(PVOID)*pCounterSignerInfo,
&dwSize);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}

fReturn = TRUE;

break; // Break from for loop.
}
}
}
__finally
{
// Clean up.
if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);
}

return fReturn;
}

不幸的是,在处理双重签名时,我的想法并不总是能正常工作。

例如,一个工作示例。如果我在 Sysiternal 的 ProcMon 上运行它,它具有双重签名,正如我们从 Windows 资源管理器中看到的那样:

enter image description here

我的代码正确检索了 SHA1SHA256 签名:

enter image description here

但是,这里有一个不工作的例子。如果我在其他一些双重签名文件上运行它,比如 Putty可执行文件,它也有双重签名:

enter image description here

上面的代码两次检索相同的 SHA256 证书:

enter image description here

知道为什么吗?

附言。这不仅仅发生在 Putty 的签名上。还有其他双重签名的可执行文件表现出相同的行为。

最佳答案

输出中的摘要算法与文件属性中显示的摘要算法不匹配的原因是因为您在输出中显示的是签名证书链中第一个证书的摘要算法,而不是摘要算法Authenticode 签名本身。是这样的:

+-----------+          +-------------------+          +---------+
| Root Cert | signs | Intermediate Cert | signs | PE Data |
|-----------|=========>|-------------------|=========>|---------|
| SHA256 | | SHA256 | | SHA1 |
+-----------+ +-------------------+ +---------+

^ ^
| |
You are showing But you
this want to show
this

Authenticode 的工作方式是,首先使用(Microsoft)有时称为“摘要算法”的方法计算文件摘要。然后使用提供证书的签名 key 对该摘要进行签名。但是该证书本身是通过使用所谓的“签名摘要”计算其摘要并使用证书链中更高级别的证书中的 key 对其进行签名的来签名的,依此类推。

可以使用CertFindCertificateInStore 函数获取证书链中的第一个证书。然后,您应该在 while 循环中继续调用 CertFindCertificateInStore 以获取其他证书。您在代码中所做的是获取证书链中的第一个证书(使用 CertFindCertificateInStore)并打印其签名摘要算法。您要做的是获取并打印文件签名的摘要算法。您可以使用带有 CMSG_SIGNER_INFO_PARAM 标志的 CryptMsgGetParam 来做到这一点,您确实获得了它,只是不打印它。


另一种思考方式是根据 MSG_SIGNER_INFOCERT_INFO 之间的关系。这不是信息需要匹配的 1-1 关系。它更像是:

+---------------+                  +---------------+
| SIGNER_INFO 1 | | SIGNER_INFO 2 |
|---------------| |---------------|
| SHA1 | | SHA256 |
+---------------+ +---------------+
| |
| +-------------+ | +-------------+
+---| CERT_INFO 1 | +---| CERT_INFO 3 |
|-------------| |-------------|
| SHA256 | | SHA256 |
+-------------+ +-------------+
| |
+-------------+ +-------------+
| CERT_INFO 2 | | CERT_INFO 4 |
|-------------| |-------------|
| SHA1 | | SHA1 |
+-------------+ +-------------+

关于c++ - 修改代码以从 Windows 中的 PE 可执行文件检索双重签名信息?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50976612/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com