Hello S-1-1-0, it’s time for another blog post. Another PowerShell and CryptoAPI blog post.
Recently I had a trivial (or non-trivial?) challenge: read multiple signatures from signed files. Usually files have only one signature:
However, there are cases when files have two signatures (for example, one is SHA1 and second is SHA2) like this:
PowerShell has awesome Get-AuthenticodeSignature
cmdlet that allows to retrieve authenticode signature status and relevant certificates:
PS C:\Users\vPodans> Get-AuthenticodeSignature C:\Windows\System32\msvcr120.dll Directory: C:\Windows\System32 SignerCertificate Status Path ----------------- ------ ---- 108E2BA23632620C427C570B6D9DB51AC31387FE Valid msvcr120.dll PS C:\Users\vPodans> Get-AuthenticodeSignature C:\Windows\System32\msvcr120.dll | fl * SignerCertificate : [Subject] CN=Microsoft Corporation, OU=MOPR, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Issuer] CN=Microsoft Code Signing PCA, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Serial Number] 33000000B011AF0A8BD03B9FDD0001000000B0 [Not Before] 25.01.2013. 0:33:39 [Not After] 25.04.2014. 1:33:39 [Thumbprint] 108E2BA23632620C427C570B6D9DB51AC31387FE TimeStamperCertificate : [Subject] CN=Microsoft Time-Stamp Service, OU=nCipher DSE ESN:C0F4-3086-DEF8, OU=MOPR, O=Microsoft Cor poration, L=Redmond, S=Washington, C=US [Issuer] CN=Microsoft Time-Stamp PCA, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Serial Number] 330000002B393248C1B2C948F300000000002B [Not Before] 05.09.2012. 0:12:34 [Not After] 04.12.2013. 23:12:34 [Thumbprint] 2F497C556F94E32731CF86ADD8629C9867C35A24 Status : Valid StatusMessage : Signature verified. Path : C:\Windows\System32\msvcr120.dll PS C:\Users\vPodans>
There is a bug in PowerShell 5.0 on Windows 10:
TimeStamperCertificate
property is always$null
even if it is properly timestamped.
By using this cmdlet we can retrieve basic information about the signture. However, Get-AuthenticodeSignature cmdlet has the following limitations:
what to do? Master the PowerShell and manage entire world.
Ok, we have identified the problem, now we need to move forward and try to figure out how to fill above mentioned gaps.
For our purposes we will have to use some CryptoAPI native functions:
We will write function signature definition (I will skip steps used to do this, as they are not very important here):
$signature = @" [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptQueryObject( int dwObjectType, [MarshalAs(UnmanagedType.LPWStr)] string pvObject, int dwExpectedContentTypeFlags, int dwExpectedFormatTypeFlags, int dwFlags, ref int pdwMsgAndCertEncodingType, ref int pdwContentType, ref int pdwFormatType, ref IntPtr phCertStore, ref IntPtr phMsg, ref IntPtr ppvContext ); [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptMsgGetParam( IntPtr hCryptMsg, int dwParamType, int dwIndex, byte[] pvData, ref int pcbData ); [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptMsgClose( IntPtr hCryptMsg ); [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CertCloseStore( IntPtr hCertStore, int dwFlags ); "@ Add-Type -AssemblyName System.Security Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32
for CryptQueryObject function calls we will use the following parameters:
CERT_QUERY_OBJECT_FILE
(0x1) for dwObjectType parameter;CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED
(1024) for dwExpectedContentTypeFlags parameterCERT_QUERY_FORMAT_FLAG_BINARY
(0x2) for dwExpectedFormatTypeFlags parameter.These values can be found in the Wincrypt.h header file
Now, call the function:
$CERT_QUERY_OBJECT_FILE = 0x1 $CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 0x400 $CERT_QUERY_FORMAT_FLAG_BINARY = 0x2 $pdwMsgAndCertEncodingType = 0 $pdwContentType = 0 $pdwFormatType = 0 [IntPtr]$phCertStore = [IntPtr]::Zero [IntPtr]$phMsg = [IntPtr]::Zero [IntPtr]$ppvContext = [IntPtr]::Zero $return = [PKI.Crypt32]::CryptQueryObject( $CERT_QUERY_OBJECT_FILE, "C:\Windows\System32\msvcr120.dll", $CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, $CERT_QUERY_FORMAT_FLAG_BINARY, 0, [ref]$pdwMsgAndCertEncodingType, [ref]$pdwContentType, [ref]$pdwFormatType, [ref]$phCertStore, [ref]$phMsg, [ref]$ppvContext )
and the function returns the following:
PS C:\Users\vPodans> $return True PS C:\Users\vPodans> $phMsg 852136756384
The function succeeded and returned a pointer to a PKCS#7 signature block (CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED). Now, we need to extract PKCS#7 signed message and extract managed bytes. We will use CryptMsgGetParam function for these purposes as follows:
$CMSG_ENCODED_MESSAGE = 29 $pcbData = 0 # call the function for the first time to determine the managed buffer size [PKI.Crypt32]::CryptMsgGetParam($phMsg,$CMSG_ENCODED_MESSAGE,0,$null,[ref]$pcbData) # allocate the buffer $pvData = New-Object byte[] -ArgumentList $pcbData # read the PKCS#7 signaed message to managed buffer [PKI.Crypt32]::CryptMsgGetParam($phMsg,$CMSG_ENCODED_MESSAGE,0,$pvData,[ref]$pcbData)
and the code returns the following:
PS C:\Users\vPodans> $pcbData 16022
The whole signature block (encoded) is 16022 bytes (about 16kb). $pvData will store encoded bytes. Now, we leave unmanaged world and return to fully managed. We pass encoded PKCS#7 bytes to SignedCms class:
$SignedCms = New-Object Security.Cryptography.Pkcs.SignedCms $SignedCms.Decode($pvData)
We will focus on a SignerInfos property, which stores signature information. SignerInfos property is a collection of SignerInfo objects. For multi-signed files you would expect that there will be two objects, where each of them represents each signature. It is very wrong assumption.
Technically speaking, Microsoft authenticode signature supports only one signature at a time. Additional signatures are done as nested signatures.
Look at these images:
First picture represents SHA1 signature details and second representd SHA2 signature. We should explore them in more details. There are few fields and two type of attributes: authenticated and unauthenticated. Authenticated attributes are protected by signature. Any modifications there will invalidate the signature. Unauthenticated attributes are not protected by original signature and any modifications there will not invalidate the signature. To protect unauthenticated attributes from tampering, they should implement their own protection mechanisms. Usually, by externally signing these attributes.
First image observation tells us that signature timestamping (Counter Sign attribute) is unauthenticated. This means that we can attach or detach timestamp from the signature without changing original signature. This allows us to timestamp signed files after they are signed.
First image displays another unauthenticated attribute with OID=1.3.6.1.4.1.311.2.4.1 (szOID_NESTED_SIGNATURE). This is a nested SHA2 signature. Here it lies. Let’s go with retrieving general information from the SHA1 signature:
PS C:\Users\vPodans> $SignedCms.SignerInfos[0] Version : 1 Certificate : [Subject] CN=Microsoft Corporation, OU=MOPR, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Issuer] CN=Microsoft Code Signing PCA, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Serial Number] 33000000B011AF0A8BD03B9FDD0001000000B0 [Not Before] 25.01.2013. 0:33:39 [Not After] 25.04.2014. 1:33:39 [Thumbprint] 108E2BA23632620C427C570B6D9DB51AC31387FE SignerIdentifier : System.Security.Cryptography.Pkcs.SubjectIdentifier DigestAlgorithm : System.Security.Cryptography.Oid SignedAttributes : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography. Oid, System.Security.Cryptography.Oid} UnsignedAttributes : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid} CounterSignerInfos : {System.Security.Cryptography.Pkcs.SignerInfo} PS C:\Users\vPodans> $SignedCms.SignerInfos[0].DigestAlgorithm Value FriendlyName ----- ------------ 1.3.14.3.2.26 sha1
as we discovered, timestamp information is stored in the CounterSignerInfos property:
PS C:\Users\vPodans> $SignedCms.SignerInfos[0].CounterSignerInfos Version : 1 Certificate : [Subject] CN=Microsoft Time-Stamp Service, OU=nCipher DSE ESN:C0F4-3086-DEF8, OU=MOPR, O=Microsoft Corpora tion, L=Redmond, S=Washington, C=US [Issuer] CN=Microsoft Time-Stamp PCA, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Serial Number] 330000002B393248C1B2C948F300000000002B [Not Before] 05.09.2012. 0:12:34 [Not After] 04.12.2013. 23:12:34 [Thumbprint] 2F497C556F94E32731CF86ADD8629C9867C35A24 SignerIdentifier : System.Security.Cryptography.Pkcs.SubjectIdentifier DigestAlgorithm : System.Security.Cryptography.Oid SignedAttributes : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography. Oid} UnsignedAttributes : {} CounterSignerInfos : {} PS C:\Users\vPodans>
Okay, we see timestamping certificate, however, where is the signing time (timestamp)? It is stored in the signed attributes, and attribute OID is 1.2.840.113549.1.9.5 (Signing Time):
PS C:\Users\vPodans> $SignedCms.SignerInfos[0].CounterSignerInfos[0].SignedAttributes | ?{$_.Oid.Value -eq "1.2.840.1135 49.1.9.5"} Oid Values --- ------ System.Security.Cryptography.Oid {System.Security.Cryptography.Oid} PS C:\Users\vPodans> ($SignedCms.SignerInfos[0].CounterSignerInfos[0].SignedAttributes | ?{$_.Oid.Value -eq "1.2.840.113 549.1.9.5"}).values SigningTime Oid RawData ----------- --- ------- 05.10.2013. 6:49:07 System.Security.Cryptography.Oid {23, 13, 49, 51...} PS C:\Users\vPodans>
Here it is!
Note that signing time is stored in UTC, so do not forget to convert it to your local time zone.
So, at this point we have retrieved everything we need from the SHA1 signature. How to retrieve SHA2 signature and relevant information? As we discussed, SHA2 is stored in the unauthenticated attribute with OID=1.3.6.1.4.1.311.2.4.1 (szOID_NESTED_SIGNATURE). Let’s go:
PS C:\Users\vPodans> $SignedCms.SignerInfos[0].UnsignedAttributes | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311.2.4.1"} Oid Values --- ------ System.Security.Cryptography.Oid {System.Security.Cryptography.Oid} PS C:\Users\vPodans> ($SignedCms.SignerInfos[0].UnsignedAttributes | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311.2.4.1"}).values Oid RawData --- ------- System.Security.Cryptography.Oid {48, 130, 35, 244...} PS C:\Users\vPodans>
Values[0].RawData is the same signed PKCS#7 message, so we instantiate another SignedCms object from Values[0].RawData and use the already used techniques to process the object:
PS C:\Users\vPodans> $nestedSigAttr = $SignedCms.SignerInfos[0].UnsignedAttributes | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311 .2.4.1"} PS C:\Users\vPodans> $SignedCms2 = New-Object Security.Cryptography.Pkcs.SignedCms PS C:\Users\vPodans> $SignedCms2.Decode($nestedSigAttr.Values[0].RawData) PS C:\Users\vPodans> $SignedCms2.SignerInfos[0] Version : 1 Certificate : [Subject] CN=Microsoft Corporation, OU=MOPR, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Issuer] CN=Microsoft Code Signing PCA 2011, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Serial Number] 330000001A77BB74B307D116B800000000001A [Not Before] 24.09.2013. 20:41:41 [Not After] 24.12.2014. 19:41:41 [Thumbprint] 6474839AF67AB79C91007FF62FE08E2ACF016B83 SignerIdentifier : System.Security.Cryptography.Pkcs.SubjectIdentifier DigestAlgorithm : System.Security.Cryptography.Oid SignedAttributes : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography. Oid, System.Security.Cryptography.Oid...} UnsignedAttributes : {System.Security.Cryptography.Oid} CounterSignerInfos : {} PS C:\Users\vPodans> $SignedCms2.SignerInfos[0].DigestAlgorithm Value FriendlyName ----- ------------ 2.16.840.1.101.3.4.2.1 sha256 PS C:\Users\vPodans>
if we combine all this information, we can write an extended version of Get-AuthenticodeSignature cmdlet:
function Get-AuthenticodeSignatureEx { <# .ForwardHelpTargetName Get-AuthenticodeSignature #> [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [String[]]$FilePath ) begin { $signature = @" [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptQueryObject( int dwObjectType, [MarshalAs(UnmanagedType.LPWStr)] string pvObject, int dwExpectedContentTypeFlags, int dwExpectedFormatTypeFlags, int dwFlags, ref int pdwMsgAndCertEncodingType, ref int pdwContentType, ref int pdwFormatType, ref IntPtr phCertStore, ref IntPtr phMsg, ref IntPtr ppvContext ); [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptMsgGetParam( IntPtr hCryptMsg, int dwParamType, int dwIndex, byte[] pvData, ref int pcbData ); [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptMsgClose( IntPtr hCryptMsg ); [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CertCloseStore( IntPtr hCertStore, int dwFlags ); "@ Add-Type -AssemblyName System.Security Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32 $CERT_QUERY_OBJECT_FILE = 0x1 $CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 0x400 $CERT_QUERY_FORMAT_FLAG_BINARY = 0x2 function getTimeStamps($SignerInfo) { $retValue = @() foreach ($CounterSignerInfos in $SignerInfo.CounterSignerInfos) { $sTime = ($CounterSignerInfos.SignedAttributes | ?{$_.Oid.Value -eq "1.2.840.113549.1.9.5"}).Values | ` Where-Object {$_.SigningTime -ne $null} $tsObject = New-Object psobject -Property @{ Certificate = $CounterSignerInfos.Certificate SigningTime = $sTime.SigningTime.ToLocalTime() } $retValue += $tsObject } $retValue } } process { Get-AuthenticodeSignature $FilePath | ForEach-Object { $Output = $_ if ($Output.SignerCertificate -ne $null) { $pdwMsgAndCertEncodingType = 0 $pdwContentType = 0 $pdwFormatType = 0 [IntPtr]$phCertStore = [IntPtr]::Zero [IntPtr]$phMsg = [IntPtr]::Zero [IntPtr]$ppvContext = [IntPtr]::Zero $return = [PKI.Crypt32]::CryptQueryObject( $CERT_QUERY_OBJECT_FILE, $Output.Path, $CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, $CERT_QUERY_FORMAT_FLAG_BINARY, 0, [ref]$pdwMsgAndCertEncodingType, [ref]$pdwContentType, [ref]$pdwFormatType, [ref]$phCertStore, [ref]$phMsg, [ref]$ppvContext ) if (!$return) {return} $pcbData = 0 $return = [PKI.Crypt32]::CryptMsgGetParam($phMsg,29,0,$null,[ref]$pcbData) if (!$return) {return} $pvData = New-Object byte[] -ArgumentList $pcbData $return = [PKI.Crypt32]::CryptMsgGetParam($phMsg,29,0,$pvData,[ref]$pcbData) $SignedCms = New-Object Security.Cryptography.Pkcs.SignedCms $SignedCms.Decode($pvData) $Infos = $SignedCms.SignerInfos[0] $Output | Add-Member -MemberType NoteProperty -Name TimeStamps -Value $null $Output | Add-Member -MemberType NoteProperty -Name DigestAlgorithm -Value $Infos.DigestAlgorithm.FriendlyName $Output.TimeStamps = getTimeStamps $Infos $second = $Infos.UnsignedAttributes | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311.2.4.1"} if ($second) { $value = $second.Values | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311.2.4.1"} $SignedCms2 = New-Object Security.Cryptography.Pkcs.SignedCms $SignedCms2.Decode($value.RawData) $Output | Add-Member -MemberType NoteProperty -Name NestedSignature -Value $null $Infos = $SignedCms2.SignerInfos[0] $nested = New-Object psobject -Property @{ SignerCertificate = $Infos.Certificate DigestAlgorithm = $Infos.DigestAlgorithm.FriendlyName TimeStamps = getTimeStamps $Infos } $Output.NestedSignature = $nested } $Output [void][PKI.Crypt32]::CryptMsgClose($phMsg) [void][PKI.Crypt32]::CertCloseStore($phCertStore,0) } else { $Output } } } end {} }
And the function usage:
PS C:\Users\vPodans> $sig = Get-AuthenticodeSignatureEx "C:\Windows\System32\msvcr120.dll" PS C:\Users\vPodans> $sig | fl * TimeStamps : @{SigningTime=05.10.2013. 9:49:07; Certificate=[Subject] CN=Microsoft Time-Stamp Service, OU=nCipher DSE ESN:C0F4-3086-DEF8, OU=MOPR, O=Microsoft Cor poration, L=Redmond, S=Washington, C=US [Issuer] CN=Microsoft Time-Stamp PCA, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Serial Number] 330000002B393248C1B2C948F300000000002B [Not Before] 05.09.2012. 0:12:34 [Not After] 04.12.2013. 23:12:34 [Thumbprint] 2F497C556F94E32731CF86ADD8629C9867C35A24 } DigestAlgorithm : sha1 NestedSignature : @{SignerCertificate=[Subject] CN=Microsoft Corporation, OU=MOPR, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Issuer] CN=Microsoft Code Signing PCA 2011, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Serial Number] 330000001A77BB74B307D116B800000000001A [Not Before] 24.09.2013. 20:41:41 [Not After] 24.12.2014. 19:41:41 [Thumbprint] 6474839AF67AB79C91007FF62FE08E2ACF016B83 ; DigestAlgorithm=sha256; TimeStamps=} SignerCertificate : [Subject] CN=Microsoft Corporation, OU=MOPR, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Issuer] CN=Microsoft Code Signing PCA, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Serial Number] 33000000B011AF0A8BD03B9FDD0001000000B0 [Not Before] 25.01.2013. 0:33:39 [Not After] 25.04.2014. 1:33:39 [Thumbprint] 108E2BA23632620C427C570B6D9DB51AC31387FE TimeStamperCertificate : [Subject] CN=Microsoft Time-Stamp Service, OU=nCipher DSE ESN:C0F4-3086-DEF8, OU=MOPR, O=Microsoft Cor poration, L=Redmond, S=Washington, C=US [Issuer] CN=Microsoft Time-Stamp PCA, O=Microsoft Corporation, L=Redmond, S=Washington, C=US [Serial Number] 330000002B393248C1B2C948F300000000002B [Not Before] 05.09.2012. 0:12:34 [Not After] 04.12.2013. 23:12:34 [Thumbprint] 2F497C556F94E32731CF86ADD8629C9867C35A24 Status : Valid StatusMessage : Signature verified. Path : C:\Windows\System32\msvcr120.dll PS C:\Users\vPodans> $sig.TimeStamps SigningTime Certificate ----------- ----------- 05.10.2013. 9:49:07 [Subject]... PS C:\Users\vPodans> $sig.NestedSignature SignerCertificate DigestAlgorithm TimeStamps ----------------- --------------- ---------- [Subject]... sha256 PS C:\Users\vPodans>
as we can see, the function retrieves extended timestamping information (and fixes bug in Windows 10) and nested (secondary) signature information. At least, certificate and digest algorithms.
Hey,
Thanks so much for this. With the new signing requirements in 1607 I needed something like this to check our signing validity. :)
I'm guessing that the lack of license means that you are offering this as in the public domain?
Thanks again!
Dennis.
The script is licensed under Attribution-ShareAlike 4.0 International license. Please, check Disclaimer page for more details.
Thanks Vadims. Attribute and license properly included. :)
Thanks the script saved me the day
HI,
Is it normal the nested signature does not have timestamp? I get an empty list of CounterSignerInfos for the nested signerinfo. How can I retrieve the timestamp of the nested signature? Thanks
Hi!
If you look at signature details images, you will see that CounterSigner attribute is available for the first signature only. There is no CounterSigner attribute in nested signature.
You are using an absolute (and personal ^^) path "c:\Users\vPodans\Desktop\s.bin" on line 94, would change that.
> You are using an absolute (and personal ^^)
Thanks, removed this line.
Also on line 66, change the variable to $FilePath...
Brilliant!.
Thanks alot!
As an alternative, one can use sysinternals sigcheck.exe https://docs.microsoft.com/en-us/sysinternals/downloads/sigcheck
There are various tools. The article is about managed PowerShell way.
thanks a lot for this effort. Everytime i search about hash algorthim excercises with powershell, i get this in google. Much appreciated !!
Is there any solution doing the same with bcypt.dll?
According to Microsoft te WinCrypr-API is deprecated.
No, there is no implementation in bcrypt. The code provided is still current.
At least in PS 5.1.19041.1320 this does not work as posted.
It complains with
Get-AuthenticodeSignature : Die Datei "System.Management.Automation.PSBoundParametersDictionary" wurde nicht gefunden.
In Zeile:66 Zeichen:9
+ Get-AuthenticodeSignature $PSBoundParameters | ForEach-Object ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (:) [Get-AuthenticodeSignature], FileNotFoundException
+ FullyQualifiedErrorId : SignatureCommandsBaseFileNotFound,Microsoft.PowerShell.Commands.GetAuthenticodeSignatureCommand
Doing as was suggested in the comment as of "30.04.2018 12:05 (GMT+2)" and replacing `$PSBoundParameters` by `$FilePath` makes it work.
Now it just has one more drawback, it only works for the first and last signature.
I have files with three or more signatures and can only retrieve the first one using
(Get-AuthenticodeSignatureEx foo.dll).SignerCertificate.Subject
and the last one using
(Get-AuthenticodeSignatureEx foo.dll).NestedSignature.SignerCertificate.Subject
> Get-AuthenticodeSignature $PSBoundParameters
in fact, there should be @PSBoundParameters. It is blog rendering issue which renders "@" character as "$" in PS code snippets. If you replace "$" to "@", the script will work.
The script is fine but I could not retrieve the timestamp of the signature of a Windows Update catalog. There is only one signature inside (sha256). In the propertypage of the file I can see the sigature is from 13th of December 2022, but with the script after "$sig | fl *" in the line "TimeStamps" no value is displayed.
If I opened the Details page of the signature on tab Advanced I see the same as in your screenshot above on the right "Digital Signature Details" image except the first line under "Authenticated attributes". Why I can not retrieve the date as displayed in the property page of the file on tab "Digital Signatures"? Do you have any idea?
I did now a different approach on the same problem by parsing the PE-header of the file. Here a smaple code:
cls
Remove-Variable * -ea 0
$errorActionPreference = 'stop'
$path = "C:\WINDOWS\system32\DRIVERS\fbwwanfilter.sys"
$reader = $null
try {
# Open the file as a BinaryReader
$reader = [System.IO.BinaryReader]::new([System.IO.File]::OpenRead($path))
# Read DOS header:
$null = $reader.BaseStream.Seek(0x3C, 0)
$peHeaderOffset = $reader.ReadUInt32()
# Read PE signature:
$null = $reader.BaseStream.Seek($peHeaderOffset, 0)
$signature = $reader.ReadUInt32()
if ($signature -ne 0x4550) { throw "Not a PE file" }
# Read the PE magic:
$null = $reader.BaseStream.Seek(20, 1)
$magic = $reader.ReadUInt16()
$seekOffset = if ($magic -eq 0x10b) { 126 } elseif ($magic -eq 0x20b) { 142 } else { throw "Unknown PE magic" }
$null = $reader.BaseStream.Seek($seekOffset, 1)
# Read Certificate Table entry in Data Directory:
$certStart = $reader.ReadUInt32()
$certSize = $reader.ReadUInt32()
if ($certStart -eq 0 -or $certSize -eq 0) { throw "No Certificate Table entry" }
# Read Certificate Table
$null = $reader.BaseStream.Seek($certStart, 0)
$len = $reader.ReadUInt32()
if ($len -eq 0) { throw "Certificate length is zero" }
$revision = $reader.ReadUInt16()
$certificateType = $reader.ReadUInt16()
$certBytes = $reader.ReadBytes($len - 8)
Add-Type -AssemblyName System.Security
$signedCms = [Security.Cryptography.Pkcs.SignedCms]::new()
$signedCms.Decode($certBytes)
# Output certificate details
$signedCms.Certificates
}
finally {
if ($reader -ne $null) {
$reader.Close()
}
}
@Carsten Giese while this approach is quite interesting, it is extremely limited to PE-based files only. You can't use this approach to read signatures from PS1 files, for example.
Your function "GetTimeStamps" will not return anything. ($SignerInfo <-> $Infos)
A typo in variable is fixed.
Post your comment:
Comments: