In this article, we'll walk through a method to append arbitrary binary data to a .exe file and protect it with a digital signature, ensuring that any tampering with the file (including the appended data) invalidates the signature.
This technique is an enhancement of the fourth method detailed in our previous article:
Previous Article: How to Embed Binary Data into a Win32 Executable File in 4 Methods
We’ll also implement a runtime integrity check within the application to verify that the executable hasn't been altered after signing.
⚠️ This guide is more effective in the fourth method explained in the last article, useful to better protect the data concatenated at the end of the executables
Step 1: Creating Authenticode Signature Certificate
Open PowerShell for Visual Studio Developer Command Prompt and run the following command to create a self-signed certificate for local testing:
$cert = New-SelfSignedCertificate -DnsName "certName" -CertStoreLocation "cert:\CurrentUser\My" -Type CodeSigning -KeyUsageProperty Sign -KeyUsage CertSign -KeySpec Signature -KeyExportPolicy Exportable
This command will create a certificate in your user area, valid only for local testing
💡 Replace
certName
with a name of your choice to easily identify the certificate.
Exporting the .pfx
certificate (Recommended)
Now declare a password to export the certificate securely:
$password = ConvertTo-SecureString "yourPW" -AsPlainText -Force
🔒 Replace
yourPW
with your actual password.
Retrieve the existing certificate from the CurrentUser store based on its Common Name (CN)
$cert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Subject -like "*CN=certName*" }
Export the certificate to a .pfx
file:
Export-PfxCertificate -Cert $cert.PSPath -FilePath "C:\Path\of\Your\Cert.pfx" -Password $password
ℹ️ Notify: Exporting the
.pfx
certificate is not absolutely mandatory, but it is a safe way to always have a backup copy since it contains the private key to sign the executables.
Step 2: Retrieve Certificate Thumbprint
To get the thumbprint of your certificate, run:
Get-ChildItem -Path Cert:\CurrentUser\My | Format-List Subject, Thumbprint
Example output:
Subject : CN=certName
Thumbprint : A1B2C3D4E5F67890ABCDEF1234567890ABCDEF12
Copy the Thumbprint, as you’ll use it later in your C++ code for validation.
Step 3: Implement Runtime Signature Verification in Visual C++
Now in our application code we should import two libraries in the source file that handles the startup and declare a helper function that will allow us to get the signature thumbprint
Add the following to your main application source file:
Standard Win32 APIs Version:
#include "windows.h"
#include <wintrust.h>
#include <softpub.h>
// Link the necessary libraries
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "wintrust.lib")
bool VerifySignatureThumbprint(const std::string& filePath, const std::string& expectedThumbprint)
{
int wlen = MultiByteToWideChar(CP_ACP, 0, filePath.c_str(), -1, nullptr, 0);
std::wstring wPath(wlen, 0);
MultiByteToWideChar(CP_ACP, 0, filePath.c_str(), -1, &wPath[0], wlen);
DWORD contentType = 0;
DWORD formatType = 0;
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
BOOL res = CryptQueryObject(
CERT_QUERY_OBJECT_FILE,
wPath.c_str(),
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
nullptr,
&contentType,
&formatType,
&hStore,
&hMsg,
nullptr
);
if (!res || !hStore) {
if (hStore) CertCloseStore(hStore, 0);
if (hMsg) CryptMsgClose(hMsg);
return false;
}
PCCERT_CONTEXT pCert = CertEnumCertificatesInStore(hStore, nullptr);
if (!pCert) {
CertCloseStore(hStore, 0);
CryptMsgClose(hMsg);
return false;
}
BYTE hash[20]; // SHA1 produces 20-byte hash
DWORD hashSize = sizeof(hash);
if (!CryptHashCertificate(
nullptr,
CALG_SHA1,
0,
pCert->pbCertEncoded,
pCert->cbCertEncoded,
hash,
&hashSize))
{
CertFreeCertificateContext(pCert);
CertCloseStore(hStore, 0);
CryptMsgClose(hMsg);
return false;
}
// Convert hash to hex string (ANSI)
std::string computedThumbprint;
for (DWORD i = 0; i < hashSize; ++i) {
char szHex[5];
sprintf_s(szHex, "%02X", hash[i]);
computedThumbprint += szHex;
}
CertFreeCertificateContext(pCert);
CertCloseStore(hStore, 0);
CryptMsgClose(hMsg);
return (computedThumbprint.compare(expectedThumbprint) == 0);
}
If you are currently using the MFC Framework, this is the same function adapted
MFC Version:
#include "afxwin.h"
#include <wintrust.h>
#include <softpub.h>
// Link the necessary libraries
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "wintrust.lib")
bool VerifySignatureThumbprint(const CString& filePath, const CString& expectedThumbprint)
{
CStringW wPath;
int size = MultiByteToWideChar(CP_ACP, 0, filePath, -1, NULL, 0);
MultiByteToWideChar(CP_ACP, 0, filePath, -1, wPath.GetBuffer(size), size);
wPath.ReleaseBuffer();
DWORD contentType = 0;
DWORD formatType = 0;
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
BOOL res = CryptQueryObject(
CERT_QUERY_OBJECT_FILE,
wPath.GetString(),
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
NULL,
&contentType,
&formatType,
&hStore,
&hMsg,
NULL
);
if (!res) {
return false;
}
PCCERT_CONTEXT pCert = CertEnumCertificatesInStore(hStore, NULL);
if (!pCert) {
CertCloseStore(hStore, 0);
CryptMsgClose(hMsg);
return false;
}
BYTE hash[20];
DWORD hashSize = sizeof(hash);
if (!CryptHashCertificate(
NULL,
CALG_SHA1,
0,
pCert->pbCertEncoded,
pCert->cbCertEncoded,
hash,
&hashSize))
{
CertFreeCertificateContext(pCert);
CertCloseStore(hStore, 0);
CryptMsgClose(hMsg);
return false;
}
CString computedThumbprint;
for (DWORD i = 0; i < hashSize; i++) {
CString hexByte;
hexByte.Format(_T("%02X"), hash[i]);
computedThumbprint += hexByte;
}
CertFreeCertificateContext(pCert);
CertCloseStore(hStore, 0);
CryptMsgClose(hMsg);
return (computedThumbprint.CompareNoCase(expectedThumbprint) == 0);
}
Now in the body of your application's entry point, add this to check if the condition is false
main.cpp
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow)
{
char exePath[MAX_PATH];
GetModuleFileNameA(NULL, exePath, MAX_PATH);
// Now using the helper function we check if the signature has been invalidated
if (!VerifySignatureThumbprint(exePath, "A1B2C3D4E5F67890ABCDEF1234567890ABCDEF12"))
{
// Invalidated signature, show error message and prevent execution for security
MessageBox(0, "Unable to verify application integrity, click OK to terminate the application now", "Error", MB_ICONERROR | MB_OK);
ExitProcess(1);
return 0;
}
// The rest of your code here...
}
⚠️ Replace
A1B2C3D4E5F67890ABCDEF1234567890ABCDEF12
with the Thumbprint founded previously
At this point, we have implemented a runtime integrity check in our application. If even a single byte is tampered with, the application will refuse to run.
Step 4: Append Binary Data to the Executable
Open Command Prompt (not PowerShell) in your build folder and run:
copy /b original.exe + mydata.bin final.exe
-
original.exe
: your compiled executable -
mydata.bin
: binary data to append -
final.exe
: final executable with appended data
✅ The added data does not interfere with the PE format, and execution is unaffected.
Step 5: Sign the Final Executable
Once ready, run the following command from Powershell Developer for VS and sign the final.exe
using the certificate:
signtool sign /n certName /fd SHA1 .\final.exe
ℹ️
certName
should match the name used when generating the certificate.
⚠️ Note on signtool:
The
signtool
utility is part of the Windows SDK and is not available by default in standard PowerShell or CMD. To use it:
- Open a Developer Command Prompt for Visual Studio (like x64 Native Tools Command Prompt)
- Or manually add the path to
signtool.exe
in your system'sPATH
environment variable
Now, if any part of the file is modified—including the appended data—the signature becomes invalid, and the application will reject execution.
Optional: Automate via Visual Studio Post-Build Events
Save time by automating the append and signing steps after each build.
Steps:
1. In Visual Studio, go to Project > Properties
2. Navigate to Build Events > Post-Build Event
3. Add the following to the Command Line field:
Add these two command lines:
copy /b /y "$(TargetPath)" + "$(TargetDir)mydata.bin" "$(TargetDir)final.exe"
signtool sign /n certName /fd SHA1 "$(TargetDir)final.exe"
Then after you will have to insert in the compilation folder, whether Debug or Release, the binary file that we will have to concatenate to the end of the executable
Click Apply and OK to apply the changes
Each time you compile, the binary data will be automatically appended and the resulting executable signed.
Final Notes
- This method ensures data appended to an executable is tamper-proof via Authenticode.
- The application checks its own integrity at runtime, stopping execution if the signature is invalid.
- Ideal for embedding secure payloads or configuration data that must not be altered after distribution.
🔐 Security Note: The Role of the .pfx
File
Even though it’s possible to export the public part of a certificate (as a .cer
file), this is not sufficient to re-sign the executable.
✅ The
.pfx
file contains the private key, which is mandatory to sign executables.
Without the .pfx
:
- You cannot apply a valid signature, even if you have the
.cer
. - An attacker cannot re-sign a tampered executable unless they have access to the original
.pfx
.
This guarantees that the appended data, once signed, is secure against external modifications. If someone tries to alter the executable—even with the correct .cer
—they won’t be able to restore a valid signature.
And even if someone manages to obtain the .pfx
file, they would still need to know the associated password in order to import and use it—making the certificate even more secure.
🧷 For production, the
.pfx
should be securely stored and access to it strictly limited.
⚠️ About .CER Exports
⚠️ Important Note: If you export a
.CER
file from a signed .exe using Windows UI, it contains only the public certificate.
It cannot be used to re-sign files, export a.pfx
, or recover the private key.
Signing requires access to the original.pfx
file created with the private key.
Summary Table
Format | Contains Private Key? | Can Be Used to Sign? | Exportable from .exe ? |
---|---|---|---|
.cer |
❌ No | ❌ No | ✅ Yes |
.pfx |
✅ Yes | ✅ Yes | ❌ No |
Conclusion
In this article, we demonstrated how to append binary data, such as ZIP archives, to a Windows executable and protect it using an Authenticode digital signature. This method ensures that any modification to the file—whether to the executable code or the appended data—invalidates the digital signature, allowing the application to detect tampering at runtime.
A key point concerns attached ZIP files: before signing, tools like 7-Zip can freely read and modify the embedded ZIP archive. However, once the executable is signed, any modification to the file, including the attached ZIP, will invalidate the signature and prevent 7-Zip or similar tools from accessing or modifying the archive. (Once the executable is signed, 7-Zip will not be able to let us modify the attached ZIP)
The only way to modify the appended ZIP is to manually extract the executable and the appended ZIP separately, make changes, and then recombine them manually (for example, using copy /b
). But this process breaks the signature and causes the application’s integrity check to fail, rendering it unusable.
This protection guarantees that the data appended to the executable remains intact and secure, providing an effective barrier against unauthorized tampering.
To maintain this security, it’s critical to carefully store the .pfx
file containing the private key used for signing, restricting its access strictly.
Top comments (0)