Last active
September 13, 2024 00:34
-
-
Save jborean93/ed5bdc739330c7905b458860635b8a48 to your computer and use it in GitHub Desktop.
Gets the signed catalog file details from the provided paths
This file contains hidden or 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
# Copyright: (c) 2024, Jordan Borean (@jborean93) <[email protected]> | |
# MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
#Requires -Modules Ctypes | |
#Requires -Version 7.3 | |
Function Get-SignedCatalogFile { | |
<# | |
.SYNOPSIS | |
Gets the .cat file for the specified file. | |
.DESCRIPTION | |
Gets the signed catalog file for the provided paths. | |
This can be used to determine what catalog file and the hash/thumbprint | |
used when matching the file to a catalog. | |
.PARAMETER Path | |
The path to the files to get the catalog info for, this accepts wildcards. | |
.PARAMETER LiteralPath | |
The literal path to the file to get the catalog info for without processing | |
wildcard characters in the path | |
.PARAMETER HashAlgorithm | |
The hash algorithm to use when calculating the hash of the file. This | |
defaults to SHA256 which is commonly used by modern Windows versions when | |
building a catalog. It can be set to SHA1 for older catalogs. | |
.EXAMPLE | |
Get-SignedCatalogFile C:\Windows\cmd.exe | |
#> | |
[CmdletBinding(DefaultParameterSetName = "Path")] | |
param ( | |
[Parameter( | |
Mandatory = $true, | |
Position = 0, | |
ValueFromPipeline = $true, | |
ValueFromPipelineByPropertyName = $true, | |
ParameterSetName = "Path" | |
)] | |
[SupportsWildcards()] | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$Path, | |
[Parameter( | |
Mandatory = $true, | |
Position = 0, | |
ValueFromPipelineByPropertyName = $true, | |
ParameterSetName = "LiteralPath" | |
)] | |
[Alias('PSPath')] | |
[ValidateNotNullOrEmpty()] | |
[String[]] | |
$LiteralPath, | |
[Parameter()] | |
[string] | |
$HashAlgorithm = 'SHA256' | |
) | |
begin { | |
$wintrust = New-CtypesLib Wintrust.dll | |
ctypes_struct CATALOG_INFO -CharSet Unicode { | |
[int]$CbStruct | |
[MarshalAs('ByValTStr', SizeConst = 260)] | |
[string]$CatalogFile | |
} | |
$DRIVER_ACTION_VERIFY = [Guid]::new("F750E6C3-38EE-11d1-85E5-00C04FC295EE") | |
$catAdmin = [IntPtr]::Zero | |
$res = $wintrust.SetLastError().CryptCATAdminAcquireContext2[bool]( | |
[ref]$catAdmin, | |
[ref]$DRIVER_ACTION_VERIFY, | |
$wintrust.MarshalAs($HashAlgorithm, "LPWStr"), | |
$null, | |
0) | |
if (-not $res) { | |
$exp = [System.ComponentModel.Win32Exception]::new($wintrust.LastError) | |
$err = [System.Management.Automation.ErrorRecord]::new( | |
$exp, | |
"FailedToOpenCATAdmin", | |
"NotSpecified", | |
$null) | |
$err.ErrorDetails = 'Failed to open CAT Admin Context: Win32 Error 0x{0:X8} - {1}' -f ($exp.NativeErrorCode, $exp.Message) | |
$PSCmdlet.ThrowTerminatingError($err) | |
} | |
} | |
process { | |
if ($PSCmdlet.ParameterSetName -eq 'Path') { | |
$allPaths = $Path | ForEach-Object -Process { | |
$provider = $null | |
try { | |
$PSCmdlet.SessionState.Path.GetResolvedProviderPathFromPSPath($_, [ref]$provider) | |
} | |
catch [System.Management.Automation.ItemNotFoundException] { | |
$PSCmdlet.WriteError($_) | |
} | |
} | |
} | |
elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') { | |
$allPaths = $LiteralPath | ForEach-Object -Process { | |
$resolvedPath = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($_) | |
if (-not (Test-Path -LiteralPath $resolvedPath)) { | |
$msg = "Cannot find path '$resolvedPath' because it does not exist" | |
$err = [System.Management.Automation.ErrorRecord]::new( | |
[System.Management.Automation.ItemNotFoundException]::new($msg), | |
"PathNotFound", | |
"ObjectNotFound", | |
$resolvedPath) | |
$PSCmdlet.WriteError($err) | |
return | |
} | |
$resolvedPath | |
} | |
} | |
foreach ($filePath in $allPaths) { | |
try { | |
$fileStream = [System.IO.File]::OpenRead($filePath) | |
try { | |
$hashSize = 0 | |
$hashBuffer = [Array]::Empty[byte]() | |
$res = $wintrust.SetLastError().CryptCATAdminCalcHashFromFileHandle2[bool]( | |
$catAdmin, | |
$fileStream.SafeFileHandle, | |
[ref]$hashSize, | |
$null, | |
0) | |
if ($wintrust.LastError -eq 122) { | |
$hashBuffer = [byte[]]::new($hashSize) | |
$res = $wintrust.SetLastError().EntryPoint('CryptCATAdminCalcHashFromFileHandle2').CryptCATAdminCalcHashFromFileHandle2WithBuffer[bool]( | |
$catAdmin, | |
$fileStream.SafeFileHandle, | |
[ref]$hashSize, | |
$hashBuffer, | |
0) | |
} | |
if (-not $res) { | |
$exp = [System.ComponentModel.Win32Exception]::new($wintrust.LastError) | |
$err = [System.Management.Automation.ErrorRecord]::new( | |
$exp, | |
"FailedToGetFileHash", | |
"NotSpecified", | |
$filePath) | |
$err.ErrorDetails = 'Failed to get file hash for ''{0}'': Win32 Error 0x{1:X8} - {2}' -f @( | |
$filePath, | |
$exp.NativeErrorCode, | |
$exp.Message) | |
$PSCmdlet.WriteError($err) | |
continue | |
} | |
} | |
finally { | |
$fileStream.Dispose() | |
} | |
$catHandle = $wintrust.SetLastError().CryptCATAdminEnumCatalogFromHash[IntPtr]( | |
$catAdmin, | |
$hashBuffer, | |
$hashBuffer.Length, | |
0, | |
$null) | |
if ($catHandle -eq [IntPtr]::Zero) { | |
$exp = [System.ComponentModel.Win32Exception]::new($wintrust.LastError) | |
if ($wintrust.LastError -eq 0x00000490) { | |
$err = [System.Management.Automation.ErrorRecord]::new( | |
$exp, | |
"FileHasNoCatalog", | |
"ObjectNotFound", | |
$filePath) | |
$err.ErrorDetails = 'File ''{0}'' hash ''{1}'' is not present in any system catalog ' -f @( | |
$filePath, | |
([Convert]::ToHexString($hashBuffer)) | |
) | |
} | |
else { | |
$err = [System.Management.Automation.ErrorRecord]::new( | |
$exp, | |
"FailedToGetCatalogFromHash", | |
"NotSpecified", | |
$filePath) | |
$err.ErrorDetails = 'Failed to get catalog for ''{0}'' with the hash ''{1}'': Win32 Error 0x{2:X8} - {3}' -f @( | |
$filePath, | |
([Convert]::ToHexString($hashBuffer)), | |
$exp.NativeErrorCode, | |
$exp.Message | |
) | |
} | |
$PSCmdlet.WriteError($err) | |
continue | |
} | |
try { | |
$catInfo = [CATALOG_INFO]::new() | |
$catInfo.CbStruct = [System.Runtime.InteropServices.Marshal]::SizeOf[CATALOG_INFO]() | |
$res = $wintrust.SetLastError().CharSet('Unicode').CryptCATCatalogInfoFromContext[bool]( | |
$catHandle, | |
[ref]$catInfo, | |
0) | |
if (-not $res) { | |
$exp = [System.ComponentModel.Win32Exception]::new($wintrust.LastError) | |
$err = [System.Management.Automation.ErrorRecord]::new( | |
$exp, | |
"FailedToGetCatalogInfo", | |
"NotSpecified", | |
$filePath) | |
$err.ErrorDetails = 'Failed to get catalog info for ''{0}'': Win32 Error 0x{1:X8} - {2}' -f @( | |
$filePath, | |
$exp.NativeErrorCode, | |
$exp.Message | |
) | |
$PSCmdlet.WriteError($err) | |
continue | |
} | |
[PSCustomObject]@{ | |
Path = $filePath | |
FileHash = [System.Convert]::ToHexString($hashBuffer) | |
CatalogFile = $catInfo.CatalogFile | |
} | |
} | |
finally { | |
$null = $wintrust.SetLastError().CryptCATAdminReleaseCatalogContext[bool]($catAdmin, $catHandle, 0) | |
} | |
} | |
catch { | |
$PSCmdlet.WriteError($_) | |
} | |
} | |
} | |
clean { | |
if ($catAdmin -ne [IntPtr]::Zero) { | |
$null = $wintrust.SetLastError().CryptCATAdminReleaseContext[bool]($catAdmin, 0) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment