Skip to content

Instantly share code, notes, and snippets.

@mgraeber-rc
Created December 28, 2023 18:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mgraeber-rc/8f833bf0b464306ee5c970e64bb4c998 to your computer and use it in GitHub Desktop.
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).
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