Skip to content

Instantly share code, notes, and snippets.

@jborean93
Last active September 13, 2024 00:34
Show Gist options
  • Save jborean93/ed5bdc739330c7905b458860635b8a48 to your computer and use it in GitHub Desktop.
Save jborean93/ed5bdc739330c7905b458860635b8a48 to your computer and use it in GitHub Desktop.
Gets the signed catalog file details from the provided paths
# 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