Retired Microsoft Blog disclaimer

This directory is a mirror of retired "Decrypt My World" MSDN blog and is provided as is. All posting authorship and copyrights belong to respective authors.
Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/12/11/how-to-sign-exe-files-with-an-authenticode-certificate-part-2/
Post name: How to sign EXE files with an Authenticode certificate (part 2)
Original author: Alejandro Campos Magencio
Posting date: 2008-12-11T04:59:00+00:00


Hi all, welcome back,


The other day a customer of mine was having an issue with SignTool.exe when signing an EXE file. The EXE file was getting corrupted/unusable after signing it.


When troubleshooting this issue, I had the chance to play a bit morewith SignTool and check what it does behind the scenes.


Note: I already talked a bit about signing EXEs in post How to sign EXE files with an Authenticode certificate (VB.NET). This new post will add more details and samples.


SignTool.exe uses CAPICOM.SignedCode class and its Sign method to do the signing.


The following VBScript shows how we may use CAPICOM to do the signing programmatically:

Option Explicit

Dim szCertName, szExeToSign
szCertName = "My cert Subject"
szExeToSign = "MyApplication.exe"

Const CAPICOM_CURRENT_USER_STORE = 2
Const CAPICOM_STORE_OPEN_READ_ONLY = 0
Const CAPICOM_CERTIFICATE_FIND_SUBJECT_NAME = 1
Const CAPICOM_AUTHENTICATED_ATTRIBUTE_DOCUMENT_DESCRIPTION = 2

' Get certificate for signing
'
Dim objStore
Set objStore = WScript.CreateObject("CAPICOM.Store")
objStore.Open CAPICOM_CURRENT_USER_STORE, "My", CAPICOM_STORE_OPEN_READ_ONLY
Dim objSigningCert
Set objSigningCert = objStore.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SUBJECT_NAME, szCertName).Item(1)

' Create a signer for the code
'
Dim objSigner
Set objSigner = WScript.CreateObject("CAPICOM.Signer")
objSigner.Certificate = objSigningCert

' Sign the file
'
Dim objSignedCode
Set objSignedCode = WScript.CreateObject("CAPICOM.SignedCode")
objSignedCode.FileName = szExeToSign
objSignedCode.Sign objSigner
objSignedCode.TimeStamp "http://timestamp.globalsign.com/scripts/timstamp.dll"

WScript.Echo "Done!"


Note thatthis sample also shows how to time stamp a signature programmatically.


This sample was also reproducing my customer's issue. SoI checked what CAPICOM does behind the scenes to further troubleshoot the issue.


CAPICOM.SignedCode.Sign uses CryptUIWizDigitalSign API to do the signing.


The following VB.NET sample uses CryptUIWizDigitalSign through P/Invoke to do the signing programmatically:

<SAMPLE file="Crypto.vb">

Imports System.Runtime.InteropServices
Imports System.Security.Cryptography
Imports System.ComponentModel
Imports System.Windows.Forms

Public Class Crypto

' #define CRYPTUI_WIZ_NO_UI 1
Public Const CRYPTUI_WIZ_NO_UI As Int32 = 1

' #define CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE 0x01
Public Const CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE As Int32 = 1

' #define CRYPTUI_WIZ_DIGITAL_SIGN_CERT 0x01
Public Const CRYPTUI_WIZ_DIGITAL_SIGN_CERT As Int32 = 1

' typedef struct _CRYPTUI_WIZ_DIGITAL_SIGN_INFO {
' DWORD dwSize;
' DWORD dwSubjectChoice;
' union {
' LPCWSTR pwszFileName;
' PCCRYPTUI_WIZ_DIGITAL_SIGN_BLOB_INFO pSignBlobInfo;
' };
' DWORD dwSigningCertChoice;
' union {
' PCCERT_CONTEXT pSigningCertContext;
' PCCRYPTUI_WIZ_DIGITAL_SIGN_STORE_INFO pSigningCertStore;
' PCCRYPTUI_WIZ_DIGITAL_SIGN_CERT_PVK_INFO pSigningCertPvkInfo;
' };
' LPCWSTR pwszTimestampURL;
' DWORD dwAdditionalCertChoice;
' PCCRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO pSignExtInfo;
' } CRYPTUI_WIZ_DIGITAL_SIGN_INFO;
<StructLayout(LayoutKind.Sequential)> _
Public Structure CRYPTUI_WIZ_DIGITAL_SIGN_INFO
Public dwSize As Int32
Public dwSubjectChoice As Int32
<MarshalAs(UnmanagedType.LPWStr)> Public pwszFileName As String
Public dwSigningCertChoice As Int32
Public pSigningCertContext As IntPtr
Public pwszTimestampURL As String
Public dwAdditionalCertChoice As Int32
Public pSignExtInfo As IntPtr
End Structure

' typedef struct _CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT {
' DWORD dwSize;
' DWORD cbBlob;
' BYTE* pbBlob;
' } CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT;
<StructLayout(LayoutKind.Sequential)> _
Public Structure CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT
Public dwSize As Int32
Public cbBlob As Int32
Public pbBlob As IntPtr
End Structure

' BOOL WINAPI CryptUIWizDigitalSign(
' DWORD dwFlags,
' HWND hwndParent,
' LPCWSTR pwszWizardTitle,
' PCCRYPTUI_WIZ_DIGITAL_SIGN_INFO pDigitalSignInfo,
' PCCRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT* ppSignContext
' );
<DllImport("Cryptui.dll", CharSet:=CharSet.Unicode, SetLastError:=True)> _
Public Shared Function CryptUIWizDigitalSign( _
ByVal dwFlags As Int32, _
ByVal hwndParent As IntPtr, _
ByVal pwszWizardTitle As String, _
ByRef pDigitalSignInfo As CRYPTUI_WIZ_DIGITAL_SIGN_INFO, _
ByRef ppSignContext As IntPtr _
) As Boolean
End Function

' BOOL WINAPI CryptUIWizFreeDigitalSignContext(
' PCCRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT pSignContext
' );
<DllImport("Cryptui.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Public Shared Function CryptUIWizFreeDigitalSignContext( _
ByVal pSignContext As IntPtr _
) As Boolean
End Function

End Class

</SAMPLE>

<SAMPLE file="Module1.vb">

Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports SignExe.Crypto
Imports System.Security.Cryptography.X509Certificates
Imports System.IO

Module Module1

Sub Main()

' Parameters
Dim certPath As String = "MyCert.pfx"
Dim exePath As String = "MyApplication.exe"
Dim sigPath As String = "signature.sig"

' Variables
'
Dim cert As X509Certificate2
Dim digitalSignInfo As CRYPTUI_WIZ_DIGITAL_SIGN_INFO
Dim pSignContext As IntPtr
Dim pSigningCertContext As IntPtr
Dim signContext As CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT
Dim fileOut As FileStream
Dim binWriter As BinaryWriter
Dim blob() As Byte

Try
' Get certificate context
'
cert = New X509Certificate2(certPath, "")
pSigningCertContext = cert.Handle

' Prepare signing info: exe and cert
'
digitalSignInfo = New CRYPTUI_WIZ_DIGITAL_SIGN_INFO
digitalSignInfo.dwSize = Marshal.SizeOf(digitalSignInfo)
digitalSignInfo.dwSubjectChoice = CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE
digitalSignInfo.pwszFileName = exePath
digitalSignInfo.dwSigningCertChoice = CRYPTUI_WIZ_DIGITAL_SIGN_CERT
digitalSignInfo.pSigningCertContext = pSigningCertContext
digitalSignInfo.pwszTimestampURL = vbNullString
digitalSignInfo.dwAdditionalCertChoice = 0
digitalSignInfo.pSignExtInfo = IntPtr.Zero

' Sign exe
'
If (Not CryptUIWizDigitalSign( _
CRYPTUI_WIZ_NO_UI, _
IntPtr.Zero, _
vbNullString, _
digitalSignInfo, _
pSignContext _
)) Then
Throw New Win32Exception(Marshal.GetLastWin32Error(), "CryptUIWizDigitalSign")
End If

' Get the blob with the signature
'
signContext = Marshal.PtrToStructure(pSignContext, GetType(CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT))
blob = New Byte(signContext.cbBlob) {}
Marshal.Copy(signContext.pbBlob, blob, 0, signContext.cbBlob)

' Store the signature in a new file
'
fileOut = File.Open(sigPath, FileMode.Create)
binWriter = New BinaryWriter(fileOut)
binWriter.Write(blob)
binWriter.Close()
fileOut.Close()

' Free blob
'
If (Not CryptUIWizFreeDigitalSignContext(pSignContext)) Then
Throw New Win32Exception(Marshal.GetLastWin32Error(), "CryptUIWizFreeDigitalSignContext")
End If

' We are done
Console.WriteLine("Done!!!")

Catch ex As Win32Exception
' Any expected errors?
'
Console.WriteLine(ex.Message + " error#" + ex.NativeErrorCode.ToString)
Catch ex As Exception
' Any unexpected errors?
'
Console.WriteLine(ex.Message)
End Try

' We are done
'
Console.WriteLine("<< Press any key to continue >>")
Console.ReadKey()

End Sub

End Module

</SAMPLE>


This sample was also reproducing the issue. So I checked what CryptUIWizDigitalSign does behind the scenes.


CryptUIWizDigitalSign API uses SignerSignEx API to do the signing.


The following VC++ sample uses SignerSignEx API to do the signing programmatically:

#include "windows.h"
#include "Wincrypt.h"
#include "stdio.h"
#include "conio.h"

// STRUCTS

typedef struct _SIGNER_FILE_INFO {
DWORD cbSize;
LPCWSTR pwszFileName;
HANDLE hFile;
} SIGNER_FILE_INFO, *PSIGNER_FILE_INFO;

typedef struct _SIGNER_BLOB_INFO {
DWORD cbSize;
GUID *pGuidSubject;
DWORD cbBlob;
BYTE *pbBlob;
LPCWSTR pwszDisplayName;
} SIGNER_BLOB_INFO, *PSIGNER_BLOB_INFO;

typedef struct _SIGNER_SUBJECT_INFO {
DWORD cbSize;
DWORD *pdwIndex;
DWORD dwSubjectChoice;
union {
SIGNER_FILE_INFO *pSignerFileInfo;
SIGNER_BLOB_INFO *pSignerBlobInfo;
} ;
} SIGNER_SUBJECT_INFO, *PSIGNER_SUBJECT_INFO;

typedef struct _SIGNER_CERT_STORE_INFO {
DWORD cbSize;
PCCERT_CONTEXT pSigningCert;
DWORD dwCertPolicy;
HCERTSTORE hCertStore;
} SIGNER_CERT_STORE_INFO, *PSIGNER_CERT_STORE_INFO;

typedef struct _SIGNER_SPC_CHAIN_INFO {
DWORD cbSize;
LPCWSTR pwszSpcFile;
DWORD dwCertPolicy;
HCERTSTORE hCertStore;
} SIGNER_SPC_CHAIN_INFO, *PSIGNER_SPC_CHAIN_INFO;

typedef struct _SIGNER_CERT {
DWORD cbSize;
DWORD dwCertChoice;
union {
LPCWSTR pwszSpcFile;
SIGNER_CERT_STORE_INFO *pCertStoreInfo;
SIGNER_SPC_CHAIN_INFO *pSpcChainInfo;
} ;
HWND hwnd;
} SIGNER_CERT, *PSIGNER_CERT;

typedef struct _SIGNER_ATTR_AUTHCODE {
DWORD cbSize;
BOOL fCommercial;
BOOL fIndividual;
LPCWSTR pwszName;
LPCWSTR pwszInfo;
} SIGNER_ATTR_AUTHCODE, *PSIGNER_ATTR_AUTHCODE;

typedef struct _SIGNER_SIGNATURE_INFO {
DWORD cbSize;
ALG_ID algidHash;
DWORD dwAttrChoice;
union {
SIGNER_ATTR_AUTHCODE *pAttrAuthcode;
} ;
PCRYPT_ATTRIBUTES psAuthenticated;
PCRYPT_ATTRIBUTES psUnauthenticated;
} SIGNER_SIGNATURE_INFO, *PSIGNER_SIGNATURE_INFO;

typedef struct _SIGNER_PROVIDER_INFO {
DWORD cbSize;
LPCWSTR pwszProviderName;
DWORD dwProviderType;
DWORD dwKeySpec;
DWORD dwPvkChoice;
union {
LPWSTR pwszPvkFileName;
LPWSTR pwszKeyContainer;
} ;
} SIGNER_PROVIDER_INFO, *PSIGNER_PROVIDER_INFO;

typedef struct _SIGNER_CONTEXT {
DWORD cbSize;
DWORD cbBlob;
BYTE *pbBlob;
} SIGNER_CONTEXT, *PSIGNER_CONTEXT;

// EXPORTS

typedef HRESULT (WINAPI* SignerFreeSignerContextType)(
__in SIGNER_CONTEXT *pSignerContext
);

typedef HRESULT (WINAPI *SignerSignExType)(
__in DWORD dwFlags,
__in SIGNER_SUBJECT_INFO *pSubjectInfo,
__in SIGNER_CERT *pSignerCert,
__in SIGNER_SIGNATURE_INFO *pSignatureInfo,
__in_opt SIGNER_PROVIDER_INFO *pProviderInfo,
__in_opt LPCWSTR pwszHttpTimeStamp,
__in_opt PCRYPT_ATTRIBUTES psRequest,
__in_opt LPVOID pSipData,
__out SIGNER_CONTEXT **ppSignerContext
);

// MAIN

void main()
{
// PARAMETERS

// File to sign
LPCWSTR pwszFileName = L"C:\\TEST\\MyApplication.exe";

// Signing Cert Subject
LPCWSTR pwszCertSubject = L"My cert Subject";

// VARIABLES
HRESULT hResult = S_OK;
BOOL bResult = TRUE;
HMODULE hMssign32 = NULL;
SignerSignExType pfSignerSignEx = NULL;
SignerFreeSignerContextType pfSignerFreeSignerContext = NULL;
HANDLE hFile = NULL;
HCERTSTORE hCertStore = NULL;
PCCERT_CONTEXT pCertContext = NULL;
DWORD dwIndex = 0;
SIGNER_FILE_INFO signerFileInfo;
SIGNER_SUBJECT_INFO signerSubjectInfo;
SIGNER_CERT_STORE_INFO signerCertStoreInfo;
SIGNER_CERT signerCert;
SIGNER_SIGNATURE_INFO signerSignatureInfo;
SIGNER_CONTEXT * pSignerContext = NULL;

// MAIN

// Attach a debugger now!
printf("<< Press any key to continue>>\n");
_getch();

// Load library containing SignerSignEx and SignerFreeSignerContext
printf("LoadLibrary...");
hMssign32 = LoadLibrary(L"Mssign32.dll");
if (!hMssign32)
{
printf("Error #%d\n", GetLastError()); goto cleanup;
}
printf("Done!\n");

// Get SignerSignEx function
printf("GetProcAddress(SignerSignEx)...");
pfSignerSignEx = (SignerSignExType) GetProcAddress(hMssign32, "SignerSignEx");
if (!pfSignerSignEx)
{
printf("Error #%d\n", GetLastError()); goto cleanup;
}
printf("Done!\n");

// Get SignerFreeSignerContext function
printf("GetProcAddress(SignerFreeSignerContext)...");
pfSignerFreeSignerContext = (SignerFreeSignerContextType) GetProcAddress(hMssign32, "SignerFreeSignerContext");
if (!pfSignerFreeSignerContext)
{
printf("Error #%d\n", GetLastError()); goto cleanup;
}
printf("Done!\n");

// Open file to sign
printf("CreateFile...");
hFile = CreateFile(
pwszFileName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (!hFile)
{
printf("Error #%d\n", GetLastError()); goto cleanup;
}
printf("Done!\n");

// Open MY cert store
printf("CertOpenStore...");
hCertStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
NULL,
CERT_SYSTEM_STORE_CURRENT_USER,
L"MY"
);
if (!hCertStore)
{
printf("Error #%d\n", GetLastError()); goto cleanup;
}
printf("Done!\n");

// Find signing cert in MY cert store
printf("CertFindCertificateInStore...");
pCertContext = CertFindCertificateInStore(
hCertStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR,
(void *)pwszCertSubject,
NULL
);
if (!pCertContext)
{
printf("Error #%d\n", GetLastError()); goto cleanup;
}
printf("Done!\n");

// Prepare SIGNER_FILE_INFO struct
signerFileInfo.cbSize = sizeof(SIGNER_FILE_INFO);
signerFileInfo.pwszFileName = pwszFileName;
signerFileInfo.hFile = hFile;

// Prepare SIGNER_SUBJECT_INFO struct
signerSubjectInfo.cbSize = sizeof(SIGNER_SUBJECT_INFO);
dwIndex = 0;
signerSubjectInfo.pdwIndex = &dwIndex;
signerSubjectInfo.dwSubjectChoice = 1; // SIGNER_SUBJECT_FILE
signerSubjectInfo.pSignerFileInfo = &signerFileInfo;

// Prepare SIGNER_CERT_STORE_INFO struct
signerCertStoreInfo.cbSize = sizeof(SIGNER_CERT_STORE_INFO);
signerCertStoreInfo.pSigningCert = pCertContext;
signerCertStoreInfo.dwCertPolicy = 2; // SIGNER_CERT_POLICY_CHAIN
signerCertStoreInfo.hCertStore = NULL;

// Prepare SIGNER_CERT struct
signerCert.cbSize = sizeof(SIGNER_CERT);
signerCert.dwCertChoice = 2; // SIGNER_CERT_STORE
signerCert.pCertStoreInfo = &signerCertStoreInfo;
signerCert.hwnd = NULL;

// Prepare SIGNER_SIGNATURE_INFO struct
signerSignatureInfo.cbSize = sizeof(SIGNER_SIGNATURE_INFO);
signerSignatureInfo.algidHash = CALG_SHA1;
signerSignatureInfo.dwAttrChoice = 0; // SIGNER_NO_ATTR
signerSignatureInfo.pAttrAuthcode = NULL;
signerSignatureInfo.psAuthenticated = NULL;
signerSignatureInfo.psUnauthenticated = NULL;

// Sign file with cert
printf("SignerSignEx...");
hResult = pfSignerSignEx(
0,
&signerSubjectInfo,
&signerCert,
&signerSignatureInfo,
NULL,
NULL,
NULL,
NULL,
&pSignerContext
);
if (S_OK != hResult)
{
printf("Error #%d\n", hResult); goto cleanup;
}
printf("Done!\n");

printf("\nSUCCESS!!!\n");

// Clean up
cleanup:

if (pSignerContext)
{
hResult = pfSignerFreeSignerContext(pSignerContext);
}

if (pCertContext)
{
bResult = CertFreeCertificateContext(pCertContext);
}

if (hCertStore)
{
bResult = CertCloseStore(hCertStore, CERT_CLOSE_STORE_CHECK_FLAG);
}

if (hFile)
{
bResult = CloseHandle(hFile);
}

if (hMssign32)
{
bResult = FreeLibrary(hMssign32);
}

// Exit
printf("<< Press any key to exit >>\n");
_getch();
return;
}


This sample was also reproducing the issue.



Finally we saw that the issue was not in SignTool.exe / CAPICOM.SignCode.Sign / CryptUIWizDigitalSign / SignerSignEx, but in the EXE itself!


I run Visual Studio's Dumpbin.exe with its "/HEADER" parameter to list the problematic EXE's PE header information before and after signing it (the Microsoft Portable Executable and Common Object File Format Specification gives detailed information about PE header information).


Before signing, I could see the following Optional Header Value:

1400 [ C40] RVA [size] of Certificates Directory

After signing, I could see the following value:

1400 [ 1650] RVA [size] of Certificates Directory


Certificates Directory points to the location of the code signing signature in the binary. So an EXE which has not been signed should contain the following values:

0 [ 0] RVA [size] of Certificates Directory

Which was not our case. So SignerSignEx was not corrupting the EXE when signing it. It was already corrupted before that, even if it was successfully running before signing!


Just for testing, I opened the problematic EXE with a binary editor before signing it. I looked for "00 14 00 00 04 0c 00 00" bytes (correspondent to "1400 [ C40] RVA [size] of Certificates Directory" values), changed them to "00 00 00 00 00 00 00 00" and signed the EXE. It worked! Now the EXE is not corrupted and I can see the Digital Signature in Explorer, the certificate I used to sign, launch the EXE and run it as expected.


Manually modifying the PE header is not supported by Microsoft. If you face a similar issue, you should work with the team that developed the EXE and focus on why the EXE got generated with an invalid PE header in the first place.


I looked on the Internet and found that there are some third-party tools which may help us to see and modify the PE header if needed for testing purposes, for example PEInfo 0.9 BETA which I haven't tried so I can't neither recommend nor discourage its use.


Ihope this helps.


Regards,



Alex (Alejandro Campos Magencio)


Share this article:

Comments:

Comments are closed.