Skip to content

Instantly share code, notes, and snippets.

@smourier
Created August 1, 2024 16:27
Show Gist options
  • Save smourier/9dfa31822800aa485cd445fd0e2518f0 to your computer and use it in GitHub Desktop.
Save smourier/9dfa31822800aa485cd445fd0e2518f0 to your computer and use it in GitHub Desktop.
A C# utility to decode all authenticode signatures (primary and secondaries)
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;
}
}
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