How to sign EXE files with an Authenticode certificate (part 2)

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 more with 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 "https://timestamp.globalsign.com/scripts/timstamp.dll"

WScript.Echo "Done!"

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

This sample was also reproducing my customer's issue. So I 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.

I hope this helps.

Regards,

 

Alex (Alejandro Campos Magencio)