Skip to content

Commit

Permalink
Add a check for cached MSI files with potentially unsafe Custom Actions
Browse files Browse the repository at this point in the history
  • Loading branch information
itm4n committed Dec 25, 2023
1 parent 69ca8ff commit dbc8fc0
Show file tree
Hide file tree
Showing 7 changed files with 911 additions and 44 deletions.
45 changes: 23 additions & 22 deletions PrivescCheck.ps1

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions build/Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function Invoke-Build {
"src\helper\ServiceHelpers.ps1",
"src\helper\HardeningHelpers.ps1",
"src\helper\CredentialHelpers.ps1",
"src\helper\MsiHelpers.ps1",
"src\check\Globals.ps1",
"src\check\Main.ps1",
"src\check\User.ps1",
Expand Down
6 changes: 6 additions & 0 deletions info/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2023-12-25

### Added

- Check for cached MSI files that run potentially unsafe Custom Actions.

## 2023-12-19

### Added
Expand Down
45 changes: 23 additions & 22 deletions release/PrivescCheck.ps1

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/check/Main.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ function Invoke-PrivescCheck {
"UPDATE_HISTORY", "Invoke-WindowsUpdateCheck", "TA0004 - Privilege Escalation", "Last Windows Update date", "None", "Table", "True", "True", "False", "Get information about the latest Windows update. Note that this check might be unreliable."
"UPDATE_HOTFIX_INFO", "Invoke-HotFixCheck -Info", "TA0004 - Privilege Escalation", "Windows Update history", "None", "Table", "True", "True", "False", "Get information about the installed security updates through the registry. If this fails, the check will fall back to using the built-in 'Get-HotFix' cmdlet."
"UPDATE_HOTFIX", "Invoke-HotFixCheck", "TA0004 - Privilege Escalation", "Latest updates installed", "Medium", "Table", "False", "True", "False", "Check whether a Windows security update was installed within the last 31 days."
"MISC_MSI_CUSTOM_ACTIONS", "Invoke-MsiCustomActionsCheck", "TA0004 - Privilege Escalation", "MSI unsafe Custom Actions", "None", "List", "True", "True", "False", "Get information about cached MSI files that execute potentially unsafe Custom Actions. Note that a manual analysis is required to determine if the returned MSI files are actually vulnerable."
"MISC_AVEDR", "Invoke-EndpointProtectionCheck", "TA0005 - Defense Evasion", "Endpoint protection software", "None", "Table", "True", "True", "False", "Get information about the installed security products (AV, EDR). Note that this check follows a keyword-based approach and thus might not be completely reliable."
"MISC_DEFENDER_EXCLUSIONS", "Invoke-DefenderExclusionsCheck", "TA0005 - Defense Evasion", "Windows Defender exclusions", "None", "Table", "True", "True", "False", "Get information about the exclusions configured in Microsoft Defender."
"MISC_SYSINFO", "Invoke-SystemInfoCheck", "TA0043 - Reconnaissance", "Windows version", "None", "Table", "True", "True", "False", "Get information about the Windows version. Note that this information might be useful if the update history cannot be obtained."
Expand Down
139 changes: 139 additions & 0 deletions src/check/Misc.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1093,4 +1093,143 @@ function Invoke-ExploitableLeakedHandlesCheck {
foreach ($ProcessHandle in $ProcessHandles.Values) {
$null = $Kernel32::CloseHandle($ProcessHandle)
}
}

function Invoke-MsiCustomActionsCheck {
<#
.SYNOPSIS
Search for MSI files that run Custom Actions as SYSTEM.
Author: @itm4n
License: BSD 3-Clause
.DESCRIPTION
This cmdlet retrieves a list of cached MSI files and analyzes them to find potentially unsafe Custom Actions.
.EXAMPLE
PS C:\> Invoke-MsiCustomActionsCheck
[snip]
Path : C:\Windows\Installer\33972a.msi
IdentifyingNumber : 436CFA4A-51A6-4F88-985E-2876C580481E
Name : exacqVision Client (x64)
Vendor : Exacq Technologies
Version : 19.06.6.160676
AllUsers : 1
CustomActions :
Action : bz.EarlyInstallFinish2
Type : 3073
Source : bz.CustomActionDll
Target : _InstallFinish2@4
ExeType : Dll
SourceType : BinaryData
ReturnProcessing : ProcessReturnCode,Synchronous
SchedulingFlags : InScript
SecurityContextFlags : NoImpersonate
BinaryExtractCommand : Invoke-MsiExtractBinaryData -Path 'C:\Windows\Installer\33972a.msi' -Name 'bz.CustomActionDll' -OutputPath 'bz.CustomActionDll.dll'
[snip]
#>

[CmdletBinding()]
param ()

begin {
$MsiItems = [object[]] (Get-MsiFileItem)
$CandidateCount = 0

$QuietExecFunctions = @("CAQuietExec", "CAQuietExec64", "WixQuietExec", "WixQuietExec64")
}

process {
foreach ($MsiItem in $MsiItems) {

Write-Verbose "Analyzing file: $($MsiItem.Path)"

# If the MSI doesn't force the installation for all users (i.e., system-wide),
# ignore it.
if ($MsiItem.AllUsers -ne 1) { continue }

# If the MSI doesn't have any Custom Action, ignore it.
if ($null -eq $MsiItem.CustomActions) { continue }

$CustomActions = @()

foreach ($CustomAction in $MsiItem.CustomActions) {

# If the Custom Action is configured to run only on patch uninstall, ignore it.
if ($CustomAction.RunOnPatchUninstallOnly) { continue }

# If the Custom Action is not run as SYSTEM (i.e., impersonation is enabled), ignore it.
if (-not $CustomAction.RunAsSystem) { continue }

# If the Custom Action is a function from a DLL, and the entry point is
# CAQuietExec, CAQuietExec64, WixQuietExec, or WixQuietExec64, ignore it.
if (($CustomAction.ExeType -eq "Dll") -and ($QuietExecFunctions -contains $CustomAction.Target)) { continue }

if ($CustomAction.SourceType -eq "BinaryData") {
$OutputFilename = "$($CustomAction.Source)"
if (-not (($OutputFilename -like "*.dll") -or ($OutputFilename -like "*.exe"))) {
switch ($CustomAction.ExeType) {
"Exe" { $OutputFilename += ".exe"; break }
"Dll" { $OutputFilename += ".dll"; break }
default { $OutputFilename += ".bin" }
}
}
$ExtractCommand = "Invoke-MsiExtractBinaryData -Path '$($MsiItem.Path)' -Name '$($CustomAction.Source)' -OutputPath '$($OutputFilename)'"
$CustomAction | Add-Member -MemberType "NoteProperty" -Name "BinaryExtractCommand" -Value $ExtractCommand
}

$CustomActions += $CustomAction | Select-Object -Property * -ExcludeProperty "RunAsSystem","RunOnPatchUninstallOnly"
}

if ($CustomActions.Count -ne 0) {
$MsiItem.CustomActions = $CustomActions | Format-List | Out-String
$MsiItem
$CandidateCount += 1
}
}
}

end {
Write-Verbose "Candidate count: $($CandidateCount) / $($MsiItems.Count)"
}
}

function Invoke-MsiExtractBinaryData {

[CmdletBinding()]
param (
[Parameter(Position=0, Mandatory=$true)]
[string] $Path,
[Parameter(Position=1, Mandatory=$true)]
[string] $Name,
[Parameter(Position=2, Mandatory=$true)]
[string] $OutputPath
)

begin {
$Installer = New-Object -ComObject WindowsInstaller.Installer
}

process {
try {
if ([string]::IsNullOrEmpty($OutputPath)) { $OutputPath = "$($Name)" }
Write-Verbose "Output path: $($OutputPath)"

$Database = Invoke-MsiOpenDatabase -Installer $Installer -Path $Path -Mode 0
$BinaryData = Get-MsiBinaryDataProperty -Database $Database -Name $Name

Set-Content -Path $OutputPath -Value $BinaryData
}
catch {
Write-Warning "Invoke-MsiExtractBinaryData exception: $($_)"
}
}

end {
$null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($Installer)
}
}
Loading

0 comments on commit dbc8fc0

Please sign in to comment.