Inside Get-AuthenticodeSignature

Looking at how Get-Authenticode checks for signing ☢

Introduction

Get-AuthenticodeSignature in PowerShell is used to extract information from a files authenticode:

Gets information about the Authenticode signature for a file.

Here is an example usage on Kernel32.dll:

My requirement is to identify every signed file on a system from a particular vendor. Its surprisingly easy to replicate...

Finding the Code

As these cmdlets tend to be .NET, they can just be opened with dotpeek. However, the first thing is to find which DLL it is imported from. This is essentially the same thing that am0nsec did in AppLocker Policy Enumeration in C.

The following screenshot shows the command to get the DLL:

The DLL location:

C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerShell.Security\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.Security.dll

Opening in dotpeek shows all the classes, namespaces, and so on:

Opening the GetAuthenticodeSignatureCommand class:

Cmdlet definitions are quite clean, we are looking for the GetSignature() function. Going on this function:

Here, we are interested in:

private static Signature GetSignatureFromCatalog(string filename)

There's a good chunk of code in here, all we care about is this section:

try
      {
        using (FileStream fileStream = File.OpenRead(filename))
        {
          System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO psiginfo = new System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO();
          psiginfo.cbSize = (uint) Marshal.SizeOf((object) psiginfo);
          IntPtr zero1 = IntPtr.Zero;
          IntPtr zero2 = IntPtr.Zero;
          try
          {
            if (Utils.Succeeded(System.Management.Automation.Security.NativeMethods.WTGetSignatureInfo(filename, fileStream.SafeFileHandle.DangerousGetHandle(), System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_AUTHENTICODE_SIGNED | System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_SIGNED | System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CHECK_OS_BINARY | System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_BASE_VERIFICATION | System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_FIRST, ref psiginfo, ref zero1, ref zero2)))
            {
              uint fromSignatureState = SignatureHelper.GetErrorFromSignatureState(psiginfo.nSignatureState);
              if (zero1 != IntPtr.Zero)
              {
                X509Certificate2 signer = new X509Certificate2(zero1);
                X509Certificate2 timestamperCert;
                SignatureHelper.TryGetProviderSigner(zero2, out IntPtr _, out timestamperCert);
                  ...

Pretty straight forward. First off, context management with using and then reading the file. What makes this easy is that the structs and enums can be looked up in dotkpeek by clicking F12 on the function, enum, or struct. The first one encountered is:

System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO psiginfo = new System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO();

Pressing F12 on SIGNATURE_INFO goes to NativeMethods.cs:

The same thing happens with function calls, for example:

if (Utils.Succeeded(System.Management.Automation.Security.NativeMethods.WTGetSignatureInfo(filename, fileStream.SafeFileHandle.DangerousGetHandle(), System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_AUTHENTICODE_SIGNED | System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_SIGNED | System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CHECK_OS_BINARY | System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_BASE_VERIFICATION | System.Management.Automation.Security.NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_FIRST, ref psiginfo, ref zero1, ref zero2)))

F12 on WTGetSignatureInfo:

Some F12'ing later, and here are all the DLL Imports:

[DllImportAttribute("wintrust.dll", EntryPoint = "WTGetSignatureInfo", CallingConvention = CallingConvention.StdCall)]
internal static extern int WTGetSignatureInfo
    (
        [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string pszFile,
        [InAttribute()] IntPtr hFile,
        SIGNATURE_INFO_FLAGS sigInfoFlags,
        ref SIGNATURE_INFO psiginfo,
        ref IntPtr ppCertContext,
        ref IntPtr phWVTStateData
    );

[DllImport("wintrust.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr WTHelperProvDataFromStateData(IntPtr hStateData);

[DllImport("wintrust.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr WTHelperGetProvSignerFromChain
  (
    IntPtr pProvData,
    uint idxSigner,
    uint fCounterSigner,
    uint idxCounterSigner
  );

[DllImport("wintrust.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr WTHelperGetProvCertFromChain(IntPtr pSgnr, uint idxCert);

[DllImport("wintrust.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern uint WinVerifyTrust(IntPtr hWndNotUsed, IntPtr pgActionID, IntPtr pWinTrustData);

[DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CertFreeCertificateContext(IntPtr certContext);

All in all, the function requirements:

Knowing all the information, the next thing is to reimplement it.

AuthentiFind

.NET will be the easiest one first as I have all the data. So, my requirements are a complete recursive directory search (including all subdirectories) and Microsoft explained how to do this in How to iterate through a directory tree (C# Programming Guide). After removing a bunch of junk prints, this is the code:

public static void TraverseTree(string root, string issuer)
{
    Stack<string> dirs = new Stack<string>(20);

    if (!Directory.Exists(root))
    {
        throw new ArgumentException();
    }
    dirs.Push(root);

    while (dirs.Count > 0)
    {
        string currentDir = dirs.Pop();
        string[] subDirs;
        try
        {
            subDirs = Directory.GetDirectories(currentDir);
        }
        catch (UnauthorizedAccessException e)
        {
            continue;
        }
        catch (DirectoryNotFoundException e)
        {
            continue;
        }

        string[] files;
        try
        {
            files = Directory.GetFiles(currentDir);
        }
        catch (UnauthorizedAccessException e)
        {
            continue;
        }
        catch (DirectoryNotFoundException e)
        {
            continue;
        }
        foreach (string file in files)
        {
            try
            {
                // Do file check here
            }
            catch (FileNotFoundException e)
            {
                continue;
            }
        }
        foreach (string str in subDirs)
            dirs.Push(str);
    }
}

Note the two function parameters:

  1. A Source Directory (practically this will be c:\ for me)

  2. Issuer: Something to grep for in the Issuer/Subject properties

Using the DotPeek as an outline, this is my parsing function:

private static void ParseFileForSignature(string filepath, string needle)
{
    IntPtr ppCertContext = IntPtr.Zero;
    IntPtr phWVTStateData = IntPtr.Zero;

    try
    {
        using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            SIGNATURE_INFO psiginfo = new SIGNATURE_INFO();
            psiginfo.cbSize = (uint)Marshal.SizeOf((object)psiginfo);


            int error = WTGetSignatureInfo(
                filepath,
                fs.SafeFileHandle.DangerousGetHandle(),
                SIGNATURE_INFO_FLAGS.SIF_AUTHENTICODE_SIGNED | SIGNATURE_INFO_FLAGS.SIF_CATALOG_SIGNED | SIGNATURE_INFO_FLAGS.SIF_CHECK_OS_BINARY | SIGNATURE_INFO_FLAGS.SIF_BASE_VERIFICATION | SIGNATURE_INFO_FLAGS.SIF_CATALOG_FIRST,
                ref psiginfo,
                ref ppCertContext,
                ref phWVTStateData);

            uint state = GetErrorFromSignatureState(psiginfo.nSignatureState);

            if (ppCertContext != IntPtr.Zero)
            {
                X509Certificate2 signer = new X509Certificate2(ppCertContext);
                X509Certificate2 timestamperCert = TryGetProviderSigner(phWVTStateData, out IntPtr _);

                if (signer.Issuer.ToLower().Contains(needle.ToLower()))
                {
                    Console.WriteLine($"{filepath}:{signer.Issuer}");
                }
                else if (timestamperCert.Issuer.ToLower().Contains(needle.ToLower()))
                {
                    Console.WriteLine($"{filepath}:{timestamperCert.Issuer}");
                }
                else if (signer.Subject.ToLower().Contains(needle.ToLower()))
                {
                    Console.WriteLine($"{filepath}:{signer.Subject}");
                }
                else if (timestamperCert.Subject.ToLower().Contains(needle.ToLower()))
                {
                    Console.WriteLine($"{filepath}:{timestamperCert.Subject}");
                }
                else
                {
                    return;
                }
            }
        }
    }
    finally
    {
        if (phWVTStateData != IntPtr.Zero)
            FreeWVTStateData(phWVTStateData);
        if (ppCertContext != IntPtr.Zero)
            CertFreeCertificateContext(ppCertContext);
    }
    return;
}

If the grep data is in the subject or the issuer of either the signer or the timestamp, then display it.

Below is an example output of pointing it at c:\windows\system32\:

Voila.

Conclusion

This is more of a note to self, and is probably only usable in very few scenarios. For me, I needed to find every signed file from a particular vendor and this was my solution. Using the following calls, it could be a potentially useful BOF:

Code is available on GitHub.

Last updated