Created
August 1, 2024 16:27
-
-
Save smourier/9dfa31822800aa485cd445fd0e2518f0 to your computer and use it in GitHub Desktop.
A C# utility to decode all authenticode signatures (primary and secondaries)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class AuthenticodeSignature | |
{ | |
private AuthenticodeSignature() { } | |
public int Index { get; private set; } | |
public string ProgramName { get; private set; } | |
public string PublisherLink { get; private set; } | |
public string MoreLink { get; private set; } | |
public string SerialNumber { get; private set; } | |
public X509Certificate2 Certificate { get; private set; } | |
public string HashAlgorithm { get; private set; } | |
public AuthenticodeSignature TimestampSignature { get; private set; } | |
public override string ToString() => Certificate?.ToString() ?? string.Empty; | |
public static IReadOnlyList<AuthenticodeSignature> GetAll(string filePath) | |
{ | |
if (filePath == null) | |
throw new ArgumentNullException(nameof(filePath)); | |
if (!CryptQueryObject( | |
CERT_QUERY_OBJECT_FILE, | |
filePath, | |
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, | |
CERT_QUERY_FORMAT_FLAG_BINARY, | |
0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, | |
out var storeHandle, | |
out var msg, | |
IntPtr.Zero)) | |
return Array.Empty<AuthenticodeSignature>(); | |
try | |
{ | |
var signatures = new List<AuthenticodeSignature>(); | |
var info = GetSignerInfo(msg); | |
if (info != IntPtr.Zero) | |
{ | |
try | |
{ | |
// get all signatures | |
var primary = GetSignatures(signatures, Marshal.PtrToStructure<CMSG_SIGNER_INFO>(info)); | |
// get signing cert | |
using var store = new X509Store(storeHandle); | |
primary.Certificate = store.Certificates.Find(X509FindType.FindBySerialNumber, primary.SerialNumber, false)[0]; | |
// get ts cert | |
if (primary.TimestampSignature != null && primary.TimestampSignature.Certificate == null && primary.TimestampSignature.SerialNumber != null) | |
{ | |
primary.TimestampSignature.Certificate = store.Certificates.Find(X509FindType.FindBySerialNumber, primary.TimestampSignature.SerialNumber, false)[0]; | |
} | |
} | |
finally | |
{ | |
Marshal.FreeHGlobal(info); | |
} | |
} | |
return signatures.AsReadOnly(); | |
} | |
finally | |
{ | |
CryptMsgClose(msg); | |
CertCloseStore(storeHandle, 0); | |
} | |
} | |
private static AuthenticodeSignature GetSignatures(List<AuthenticodeSignature> signatures, CMSG_SIGNER_INFO signerInfo) | |
{ | |
var signature = new AuthenticodeSignature | |
{ | |
Index = signatures.Count, | |
HashAlgorithm = GetAlgorithmName(signerInfo.HashAlgorithm.pszObjId), | |
SerialNumber = GetSerialNumber(signerInfo.SerialNumber) | |
}; | |
signatures.Add(signature); | |
// get program name and publisher information | |
var opusAtt = CertFindAttribute(SPC_SP_OPUS_INFO_OBJID, signerInfo.AuthAttrs.cAttr, signerInfo.AuthAttrs.rgAttr); | |
if (opusAtt != IntPtr.Zero) | |
{ | |
var att = Marshal.PtrToStructure<CRYPT_ATTRIBUTE>(opusAtt); | |
var blob = Marshal.PtrToStructure<CRYPT_INTEGER_BLOB>(att.rgValue); | |
var size = 0; | |
if (!CryptDecodeObject( | |
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, | |
SPC_SP_OPUS_INFO_OBJID, | |
blob.pbData, | |
blob.cbData, | |
0, | |
IntPtr.Zero, | |
ref size)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
var opusInfoPtr = Marshal.AllocHGlobal(size); | |
try | |
{ | |
if (!CryptDecodeObject( | |
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, | |
SPC_SP_OPUS_INFO_OBJID, | |
blob.pbData, | |
blob.cbData, | |
0, | |
opusInfoPtr, | |
ref size)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
var opusInfo = Marshal.PtrToStructure<SPC_SP_OPUS_INFO>(opusInfoPtr); | |
if (opusInfo.pwszProgramName != IntPtr.Zero) | |
{ | |
signature.ProgramName = opusInfo.ProgramName; | |
} | |
if (opusInfo.pPublisherInfo != IntPtr.Zero) | |
{ | |
var pub = Marshal.PtrToStructure<SPC_LINK>(opusInfo.pPublisherInfo); | |
if (pub.dwLinkChoice == SPC_URL_LINK_CHOICE || pub.dwLinkChoice == SPC_FILE_LINK_CHOICE) | |
{ | |
signature.PublisherLink = pub.Pwsz; | |
} | |
} | |
if (opusInfo.pMoreInfo != IntPtr.Zero) | |
{ | |
var more = Marshal.PtrToStructure<SPC_LINK>(opusInfo.pMoreInfo); | |
if (more.dwLinkChoice == SPC_URL_LINK_CHOICE || more.dwLinkChoice == SPC_FILE_LINK_CHOICE) | |
{ | |
signature.MoreLink = more.Pwsz; | |
} | |
} | |
} | |
finally | |
{ | |
Marshal.FreeHGlobal(opusInfoPtr); | |
} | |
} | |
// get timestamp info | |
var ts = CertFindAttribute(szOID_RSA_counterSign, signerInfo.UnauthAttrs.cAttr, signerInfo.UnauthAttrs.rgAttr); | |
if (ts != IntPtr.Zero) | |
{ | |
var tsAtt = Marshal.PtrToStructure<CRYPT_ATTRIBUTE>(ts); | |
var tsBlob = Marshal.PtrToStructure<CRYPT_INTEGER_BLOB>(tsAtt.rgValue); | |
var size = 0; | |
if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (IntPtr)PKCS7_SIGNER_INFO, tsBlob.pbData, tsBlob.cbData, 0, IntPtr.Zero, ref size)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
var ptr = Marshal.AllocHGlobal(size); | |
try | |
{ | |
if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, (IntPtr)PKCS7_SIGNER_INFO, tsBlob.pbData, tsBlob.cbData, 0, ptr, ref size)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
var tsInfo = Marshal.PtrToStructure<CMSG_SIGNER_INFO>(ptr); | |
signature.TimestampSignature = GetSignatures(new List<AuthenticodeSignature>(), tsInfo); | |
} | |
finally | |
{ | |
Marshal.FreeHGlobal(ptr); | |
} | |
} | |
// get RFC3161 timestamp info | |
var ts3161 = CertFindAttribute(szOID_RFC3161_counterSign, signerInfo.UnauthAttrs.cAttr, signerInfo.UnauthAttrs.rgAttr); | |
if (ts3161 != IntPtr.Zero) | |
{ | |
var ts3161Att = Marshal.PtrToStructure<CRYPT_ATTRIBUTE>(ts3161); | |
var msg = CryptMsgOpenToDecode(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); | |
if (msg == IntPtr.Zero) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
try | |
{ | |
var ts3161BlobPtr = ts3161Att.rgValue; | |
var ts3151Blob = Marshal.PtrToStructure<CRYPT_INTEGER_BLOB>(ts3161BlobPtr); | |
if (!CryptMsgUpdate(msg, ts3151Blob.pbData, ts3151Blob.cbData, true)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
var info = GetSignerInfo(msg); | |
if (info != IntPtr.Zero) | |
{ | |
try | |
{ | |
signature.TimestampSignature = GetSignatures(new List<AuthenticodeSignature>(), Marshal.PtrToStructure<CMSG_SIGNER_INFO>(info)); | |
signature.TimestampSignature.Certificate = GetSigningCert(ts3161BlobPtr, signature.TimestampSignature.SerialNumber); | |
} | |
finally | |
{ | |
Marshal.FreeHGlobal(info); | |
} | |
} | |
} | |
finally | |
{ | |
CryptMsgClose(msg); | |
} | |
} | |
// get secondary signatures | |
var nested = CertFindAttribute(szOID_NESTED_SIGNATURE, signerInfo.UnauthAttrs.cAttr, signerInfo.UnauthAttrs.rgAttr); | |
if (nested != IntPtr.Zero) | |
{ | |
var nestedAtt = Marshal.PtrToStructure<CRYPT_ATTRIBUTE>(nested); | |
for (var i = 0; i < nestedAtt.cValue; i++) | |
{ | |
var msg = CryptMsgOpenToDecode(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); | |
if (msg == IntPtr.Zero) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
try | |
{ | |
var nestedBlobPtr = nestedAtt.rgValue + i * Marshal.SizeOf<CRYPT_INTEGER_BLOB>(); | |
var nestedBlob = Marshal.PtrToStructure<CRYPT_INTEGER_BLOB>(nestedBlobPtr); | |
if (!CryptMsgUpdate(msg, nestedBlob.pbData, nestedBlob.cbData, true)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
var info = GetSignerInfo(msg); | |
if (info != IntPtr.Zero) | |
{ | |
try | |
{ | |
var current = GetSignatures(signatures, Marshal.PtrToStructure<CMSG_SIGNER_INFO>(info)); | |
current.Certificate = GetSigningCert(nestedBlobPtr, current.SerialNumber); | |
} | |
finally | |
{ | |
Marshal.FreeHGlobal(info); | |
} | |
} | |
} | |
finally | |
{ | |
CryptMsgClose(msg); | |
} | |
} | |
} | |
return signature; | |
} | |
private static IntPtr GetSignerInfo(IntPtr msg) | |
{ | |
var size = 0; | |
if (!CryptMsgGetParam(msg, CMSG_SIGNER_INFO_PARAM, 0, IntPtr.Zero, ref size)) | |
return IntPtr.Zero; | |
var ptr = Marshal.AllocHGlobal(size); | |
if (!CryptMsgGetParam(msg, CMSG_SIGNER_INFO_PARAM, 0, ptr, ref size)) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
return ptr; | |
} | |
private static X509Certificate2 GetSigningCert(IntPtr data, string serialNumber) | |
{ | |
var handle = CertOpenStore(sz_CERT_STORE_PROV_PKCS7, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, IntPtr.Zero, 0, data); | |
if (handle == IntPtr.Zero) | |
throw new Win32Exception(Marshal.GetLastWin32Error()); | |
try | |
{ | |
using var store = new X509Store(handle); | |
return store.Certificates.Find(X509FindType.FindBySerialNumber, serialNumber, false)[0]; | |
} | |
finally | |
{ | |
CertCloseStore(handle, 0); | |
} | |
} | |
private static string GetSerialNumber(CRYPT_INTEGER_BLOB blob) | |
{ | |
var sb = new StringBuilder(blob.cbData * 2); | |
for (var i = blob.cbData - 1; i >= 0; i--) | |
{ | |
sb.Append(Marshal.ReadByte(blob.pbData + i).ToString("X2")); | |
} | |
return sb.ToString(); | |
} | |
private static string GetAlgorithmName(IntPtr oid) // .NET 5+ has a HashAlgorithmName.FromOid method | |
{ | |
var ptr = CryptFindOIDInfo(CRYPT_OID_INFO_OID_KEY, oid, 0); | |
return ptr == IntPtr.Zero ? Marshal.PtrToStringAnsi(oid) : Marshal.PtrToStructure<CRYPT_OID_INFO>(ptr).Name; | |
} | |
[DllImport("crypt32", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern bool CryptQueryObject( | |
int dwObjectType, | |
string pvObject, | |
int dwExpectedContentTypeFlags, | |
int dwExpectedFormatTypeFlags, | |
int dwFlags, | |
IntPtr pdwMsgAndCertEncodingType, | |
IntPtr pdwContentType, | |
IntPtr pdwFormatType, | |
out IntPtr phCertStore, | |
out IntPtr phMsg, | |
IntPtr ppvContext); | |
[DllImport("crypt32", SetLastError = true)] | |
private static extern bool CryptMsgGetParam( | |
IntPtr hCryptMsg, | |
int dwParamType, | |
int dwIndex, | |
IntPtr pvData, | |
ref int pcbData); | |
[DllImport("crypt32", SetLastError = true)] | |
private static extern bool CryptMsgGetParam( | |
IntPtr hCryptMsg, | |
int dwParamType, | |
int dwIndex, | |
ref int pvData, | |
ref int pcbData); | |
[DllImport("crypt32", SetLastError = true, CharSet = CharSet.Ansi)] | |
private static extern bool CryptDecodeObject( | |
int dwCertEncodingType, | |
string lpszStructType, | |
IntPtr pbEncoded, | |
int cbEncoded, | |
int dwFlags, | |
IntPtr pvStructInfo, | |
ref int pcbStructInfo); | |
[DllImport("crypt32", SetLastError = true, CharSet = CharSet.Ansi)] | |
private static extern bool CryptDecodeObject( | |
int dwCertEncodingType, | |
IntPtr lpszStructType, | |
IntPtr pbEncoded, | |
int cbEncoded, | |
int dwFlags, | |
IntPtr pvStructInfo, | |
ref int pcbStructInfo); | |
[DllImport("crypt32", SetLastError = true)] | |
private static extern IntPtr CryptMsgOpenToDecode( | |
int dwMsgEncodingType, | |
int dwFlags, | |
int dwMsgType, | |
IntPtr hCryptProv, | |
IntPtr pRecipientInfo, | |
IntPtr pStreamInfo); | |
[DllImport("crypt32", SetLastError = true)] | |
private static extern IntPtr CertOpenStore( | |
string lpszStoreProvider, | |
int dwEncodingType, | |
IntPtr hCryptProv, | |
int dwFlags, | |
IntPtr pvPara); | |
[DllImport("crypt32", SetLastError = true)] | |
private static extern bool CryptMsgUpdate(IntPtr hCryptMsg, IntPtr pbData, int cbData, bool fFinal); | |
[DllImport("crypt32", SetLastError = true)] | |
private static extern bool CryptMsgClose(IntPtr hCryptMsg); | |
[DllImport("crypt32", SetLastError = true)] | |
private static extern bool CertCloseStore(IntPtr hCertStore, int dwFlags); | |
[DllImport("crypt32", SetLastError = true, CharSet = CharSet.Ansi)] | |
private static extern IntPtr CertFindAttribute(string pszObjId, int cAttr, IntPtr rgAttr); | |
[DllImport("crypt32", SetLastError = true)] | |
private static extern IntPtr CryptFindOIDInfo(int dwKeyType, IntPtr pvKey, int dwGroupId); | |
private const int CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 0x00000400; | |
private const int CERT_QUERY_FORMAT_FLAG_BINARY = 0x00000002; | |
private const int CERT_QUERY_OBJECT_FILE = 1; | |
private const int CMSG_SIGNER_COUNT_PARAM = 0x00000005; | |
private const int CMSG_SIGNER_INFO_PARAM = 0x00000006; | |
private const int CRYPT_OID_INFO_OID_KEY = 1; | |
private const string SPC_SP_OPUS_INFO_OBJID = "1.3.6.1.4.1.311.2.1.12"; | |
private const string szOID_NESTED_SIGNATURE = "1.3.6.1.4.1.311.2.4.1"; | |
private const string szOID_RSA_counterSign = "1.2.840.113549.1.9.6"; | |
private const string szOID_RFC3161_counterSign = "1.3.6.1.4.1.311.3.3.1"; | |
private const string sz_CERT_STORE_PROV_PKCS7 = "PKCS7"; | |
private const int X509_ASN_ENCODING = 0x00000001; | |
private const int PKCS_7_ASN_ENCODING = 0x00010000; | |
private const int PKCS7_SIGNER_INFO = 500; | |
private const int SPC_URL_LINK_CHOICE = 1; | |
private const int SPC_MONIKER_LINK_CHOICE = 2; | |
private const int SPC_FILE_LINK_CHOICE = 3; | |
private struct CMSG_SIGNER_INFO | |
{ | |
public int dwVersion; | |
public CRYPT_INTEGER_BLOB Issuer; | |
public CRYPT_INTEGER_BLOB SerialNumber; | |
public CRYPT_ALGORITHM_IDENTIFIER HashAlgorithm; | |
public CRYPT_ALGORITHM_IDENTIFIER HashEncryptionAlgorithm; | |
public CRYPT_INTEGER_BLOB EncryptedHash; | |
public CRYPT_ATTRIBUTES AuthAttrs; | |
public CRYPT_ATTRIBUTES UnauthAttrs; | |
} | |
private struct SPC_SP_OPUS_INFO | |
{ | |
public IntPtr pwszProgramName; | |
public IntPtr pMoreInfo; | |
public IntPtr pPublisherInfo; | |
public string ProgramName => Marshal.PtrToStringUni(pwszProgramName); | |
} | |
private struct SPC_LINK | |
{ | |
public int dwLinkChoice; | |
public IntPtr pwsz; // pwszUrl or Moniker or pwszFile | |
public string Pwsz => Marshal.PtrToStringUni(pwsz); | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
private struct CRYPT_ALGORITHM_IDENTIFIER | |
{ | |
public IntPtr pszObjId; | |
public CRYPT_INTEGER_BLOB Parameters; | |
public string ObjId => Marshal.PtrToStringAnsi(pszObjId); | |
} | |
private struct CRYPT_OID_INFO | |
{ | |
public int cbSize; | |
public IntPtr pszOID; | |
public IntPtr pwszName; | |
public OidGroup dwGroupId; | |
public int AlgId; | |
public int cbData; | |
public IntPtr pbData; | |
public string Name => Marshal.PtrToStringUni(pwszName); | |
} | |
private struct CRYPT_ATTRIBUTES | |
{ | |
public int cAttr; | |
public IntPtr rgAttr; | |
} | |
private struct CRYPT_ATTRIBUTE | |
{ | |
public IntPtr pszObjId; | |
public int cValue; | |
public IntPtr rgValue; | |
public string ObjId => Marshal.PtrToStringAnsi(pszObjId); | |
} | |
private struct CRYPT_INTEGER_BLOB | |
{ | |
public int cbData; | |
public IntPtr pbData; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var path = @"C:\Windows\System32\drivers\AppleKmdfFilter.sys"; | |
foreach (var info in AuthenticodeSignature.GetAll(path)) | |
{ | |
Console.WriteLine("Index : " + info.Index); | |
Console.WriteLine("HashAlgorithm : " + info.HashAlgorithm); | |
Console.WriteLine("SerialNumber : " + info.SerialNumber); | |
Console.WriteLine("ProgramName : " + info.ProgramName); | |
Console.WriteLine("PublisherLink : " + info.PublisherLink); | |
Console.WriteLine("MoreLink : " + info.MoreLink); | |
Console.WriteLine("Certificate : " + info.Certificate?.Issuer); | |
Console.WriteLine("TsSignature : " + info.TimestampSignature?.Certificate?.Issuer); | |
Console.WriteLine(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment