Created
December 28, 2023 18:46
-
-
Save mgraeber-rc/8f833bf0b464306ee5c970e64bb4c998 to your computer and use it in GitHub Desktop.
A tool to perform rapid triage of decompressed application packages (.msix and .appx files).
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
filter Get-AppPackageTriageInfo { | |
<# | |
.SYNOPSIS | |
A tool to perform rapid triage of decompressed application packages (.msix and .appx files). | |
.DESCRIPTION | |
Get-AppPackageTriageInfo parses key information from an uncompressed application package (.msix and .appx) without needing to first install it. | |
.PARAMETER PackageDirectoryPath | |
Specifies the directory path that contains the application package artifacts. | |
.INPUTS | |
Accepts one or more directory objects (System.IO.DirectoryInfo) over the pipeline. | |
.OUTPUTS | |
PSObject | |
The following object properties may be populated: | |
* Name - The name of the package taken from the Identity element in AppxManifest.xml. | |
* Version - The version of the package taken from the Identity element in AppxManifest.xml. | |
* Architecture - The architecture of the package taken from the Identity element in AppxManifest.xml. If not architecture is present, "neutral" is the default. | |
* Publisher - The publisher of the package taken from the Identity element in AppxManifest.xml. This value corresponds to the certificate subject name that was used to sign the package. This value should be identical to the CertPublisher property. | |
* PublisherId - The encoded, hashed representation of the application publisher. | |
* PackageFullName - The full name of the package. This value is generated dynamically without needing to be installed. | |
* PackageFamilyName - The shorter, family name of the package. This value is generated dynamically without needing to be installed. | |
* Languages - Specifies the languages for resources contained within the application package. Note: the first language in a list of languages is the default language. | |
* Capabilities - Specifies the declared package capabilities. | |
* CertPublisher - Specifies the subject name of the certificate that was used to sign AppxSignature.p7x. This property should match the Publisher property. | |
* CertThumbprint - Specifies the thumbprint of the certificate that was used to sign AppxSignature.p7x. This value can be used to search VirusTotal for similar samples. | |
* Applications - Specifies information related to each declared application in AppxManifest.xml. This refers to the code that will run when the application package is launched. | |
* PackageFilesFromManifest - List the files present in the package as specified in AppxBlockMap.xml. | |
* PSFConfiguration - If a Package Support Framework config.json file is present, this property will display information relevant to EXE or PowerShell script execution. | |
* Metadata - Specifies any metadata present in the application package. | |
.EXAMPLE | |
Get-AppPackageTriageInfo -PackageDirectoryPath (Get-AppPackage -Name Microsoft.WindowsStore).InstallLocation | |
.EXAMPLE | |
Get-AppPackageTriageInfo -PackageDirectoryPath .\ExtractedSuspiciousMsixDir | |
.EXAMPLE | |
Get-AppPackageTriageInfo -PackageDirectoryPath .\ExtractedSuspiciousMsixDir | ConvertTo-Json -Depth 10 | |
.EXAMPLE | |
$AllInstalledApps = Get-AppPackage | Select-Object -ExpandProperty InstallLocation | Get-Item | Get-AppPackageTriageInfo | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory, ValueFromPipelineByPropertyName)] | |
[String] | |
[Alias('FullName')] | |
[ValidateNotNullOrEmpty()] | |
$PackageDirectoryPath | |
) | |
# Useful PublisherId generator from Stack Overflow: https://stackoverflow.com/a/61040540 | |
function Get-PublisherIdFromPublisher ($Publisher) { | |
$EncUTF16LE = [system.Text.Encoding]::Unicode | |
$EncSha256 = [System.Security.Cryptography.HashAlgorithm]::Create("SHA256") | |
# Convert to UTF16 Little Endian | |
$UTF16LE = $EncUTF16LE.GetBytes($Publisher) | |
# Calculate SHA256 hash on UTF16LE Byte array. Store first 8 bytes in new Byte Array | |
$Bytes = @() | |
(($EncSha256.ComputeHasH($UTF16LE))[0..7]) | % { $Bytes += '{0:x2}' -f $_ } | |
# Convert Byte Array to Binary string; Adding padding zeros on end to it has 13*5 bytes | |
$BytesAsBinaryString = -join $Bytes.ForEach{ [convert]::tostring([convert]::ToByte($_,16),2).padleft(8,'0') } | |
$BytesAsBinaryString = $BytesAsBinaryString.PadRight(65,'0') | |
# Crockford Base32 encode. Read each 5 bits; convert to decimal. Lookup position in lookup table | |
$Coded = $null | |
For ($i=0;$i -lt (($BytesAsBinaryString.Length)); $i+=5) { | |
$String = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" | |
[int]$Int = [convert]::Toint32($BytesAsBinaryString.Substring($i,5),2) | |
$Coded += $String.Substring($Int,1) | |
} | |
Return $Coded.tolower() | |
} | |
$FullPackageDirectoryPath = Resolve-Path -Path $PackageDirectoryPath | |
$AppxBlockMapPath = Join-Path -Path $FullPackageDirectoryPath -ChildPath 'AppxBlockMap.xml' | |
$AppxManifestPath = Join-Path -Path $FullPackageDirectoryPath -ChildPath 'AppxManifest.xml' | |
$AppxSignaturePath = Join-Path -Path $FullPackageDirectoryPath -ChildPath 'AppxSignature.p7x' | |
# All three files must exist to constitute a valid package. | |
if (-not (Test-Path -Path $AppxBlockMapPath -PathType Leaf)) { Write-Error -Message 'AppxBlockMap.xml is missing.' -ErrorAction Stop } | |
if (-not (Test-Path -Path $AppxManifestPath -PathType Leaf)) { Write-Error -Message 'AppxManifest.xml is missing.' -ErrorAction Stop } | |
if (-not (Test-Path -Path $AppxSignaturePath -PathType Leaf)) { Write-Error -Message 'AppxSignature.xml is missing.' -ErrorAction Stop } | |
$AppXSignature = Get-AuthenticodeSignature -FilePath $AppxSignaturePath -ErrorAction Stop | |
$CertThumbprint = $null | |
$CertPublisher = $null | |
if ($AppXSignature) { | |
$CertThumbprint = $AppXSignature.SignerCertificate.Thumbprint | |
$CertPublisher = $AppXSignature.SignerCertificate.Subject | |
} | |
# Start parsing the application manifest | |
try { | |
$ManifestXml = [Xml] (Get-Content -Path $AppxManifestPath -Raw) | |
} catch { | |
Write-Error -Message 'Failed to parse AppxManifest.xml XML.' -ErrorAction Stop | |
} | |
try { | |
$BlockMapXml = [Xml] (Get-Content -Path $AppxBlockMapPath -Raw) | |
} catch { | |
Write-Error -Message 'Failed to parse AppxBlockMap.xml XML.' -ErrorAction Stop | |
} | |
$IdentityElement = $ManifestXml.Package.Identity | |
$PublisherId = Get-PublisherIdFromPublisher -Publisher $IdentityElement.Publisher | |
if ($IdentityElement.ProcessorArchitecture) { | |
$Architecture = $IdentityElement.ProcessorArchitecture | |
} else { | |
$Architecture = 'neutral' | |
} | |
$PackageFullName = "$($IdentityElement.Name)_$($IdentityElement.Version)_$($Architecture)_$($IdentityElement.ResourceId)_$PublisherId" | |
$PackageFamilyName = "$($IdentityElement.Name)_$PublisherId" | |
$ApplicationInfo = foreach ($Application in $ManifestXml.Package.Applications.Application) { | |
$AppId = $null | |
$AppExecutable = $null | |
$AppEntryPoint = $null | |
$AppFullTrustLevel = $false | |
if ($Application.EntryPoint -and $Application.Executable -and $Application.Id) { | |
$AppId = $Application.Id | |
$AppExecutable = $Application.Executable | |
if ($Application.EntryPoint.ToLower() -eq 'windows.fulltrustapplication') { $AppFullTrustLevel = $true } | |
} elseif ($Application.TrustLevel -and $Application.Executable -and $Application.Id) { | |
$AppId = $Application.Id | |
$AppExecutable = $Application.Executable | |
if ($Application.TrustLevel.ToLower() -eq 'mediumil') { $AppFullTrustLevel = $true } | |
} | |
if ($AppId -and $AppExecutable) { | |
$ExecutableFullPath = Join-Path -Path $FullPackageDirectoryPath -ChildPath $AppExecutable | |
if (Test-Path -Path $ExecutableFullPath -PathType Leaf) { | |
$ExecutableSHA256Hash = Get-FileHash -Path $ExecutableFullPath -Algorithm SHA256 | |
$ExecutableSigner = Get-AuthenticodeSignature -FilePath $ExecutableFullPath | |
} | |
[PSCustomObject] @{ | |
Id = $AppId | |
Executable = $AppExecutable | |
FullTrust = $AppFullTrustLevel | |
SHA256 = $ExecutableSHA256Hash.Hash | |
Publisher = $ExecutableSigner.SignerCertificate.Subject | |
Thumbprint = $ExecutableSigner.SignerCertificate.Thumbprint | |
} | |
} | |
} | |
$Languages = @($ManifestXml.Package.Resources.Resource | Where-Object { $_.Language } | Select-Object -ExpandProperty Language) | |
$Capabilities = $ManifestXml.Package.Capabilities.Capability.Name | |
$Metadata = $ManifestXml.Package.Metadata.ChildNodes | ForEach-Object { | |
if ($_.Name) { | |
[PSCustomObject] @{ | |
Name = $_.Name | |
Version = $_.Version | |
Value = $_.Value | |
} | |
} | |
} | |
$ConfigJsonPath = Join-Path -Path $FullPackageDirectoryPath -ChildPath 'config.json' | |
$PSFConfig = $null | |
if (Test-Path -Path $ConfigJsonPath -PathType Leaf) { | |
$PSFConfigText = Get-Content -Path $ConfigJsonPath -Raw | |
$PSFConfig = ConvertFrom-Json -InputObject $PSFConfigText | |
$PSFConfig = foreach ($PSFApplication in $PSFConfig.applications) { | |
$PSFId = $null | |
$PSFExecutable = $null | |
$PSFScriptExecutionMode = $null | |
$PSFStartScriptPath = $null | |
$PSFStartScriptArguments = $null | |
$PSFEndScriptPath = $null | |
$PSFEndScriptArguments = $null | |
if ($PSFApplication.id) { $PSFId = $PSFApplication.id } | |
if ($PSFApplication.executable) { $PSFExecutable = $PSFApplication.executable } | |
if ($PSFApplication.scriptExecutionMode) { $PSFScriptExecutionMode = $PSFApplication.scriptExecutionMode } | |
if ($PSFApplication.startScript.scriptPath) { $PSFStartScriptPath = $PSFApplication.startScript.scriptPath } | |
if ($PSFApplication.startScript.scriptArguments) { $PSFStartScriptArguments = $PSFApplication.startScript.scriptArguments } | |
if ($PSFApplication.endScript.scriptPath) { $PSFEndScriptPath = $PSFApplication.endScript.scriptPath } | |
if ($PSFApplication.endScript.scriptArguments) { $PSFEndScriptArguments = $PSFApplication.endScript.scriptArguments } | |
if ($PSFId) { | |
[PSCustomObject] @{ | |
Id = $PSFId | |
Executable = $PSFExecutable | |
ScriptExecutionMode = $PSFScriptExecutionMode | |
StartScriptPath = $PSFStartScriptPath | |
StartScriptArguments = $PSFStartScriptArguments | |
EndScriptPath = $PSFEndScriptPath | |
EndScriptArguments = $PSFEndScriptArguments | |
} | |
} | |
} | |
} | |
[PSCustomObject] @{ | |
Name = $IdentityElement.Name | |
Version = $IdentityElement.Version | |
Architecture = $Architecture | |
Publisher = $IdentityElement.Publisher | |
PublisherId = $PublisherId | |
PackageFullName = $PackageFullName | |
PackageFamilyName = $PackageFamilyName | |
Languages = $Languages | |
Capabilities = $Capabilities | |
CertPublisher = $CertPublisher | |
CertThumbprint = $CertThumbprint | |
Applications = $ApplicationInfo | |
PackageFilesFromManifest = $BlockMapXml.BlockMap.File.Name | |
PSFConfiguration = $PSFConfig | |
Metadata = $Metadata | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment