Check-LSAPackageExtensions.ps1
|
<#PSScriptInfo .VERSION 1.0.1 .GUID d5e6f7a8-b9c0-1234-5678-90abcdef1234 .AUTHOR Krishnaramanan .COMPANYNAME Krishnaramanan .COPYRIGHT (c) 2024 Krishnaramanan .TAGS LSA Security Windows PowerShell Audit Authentication Hardening .PROJECTURI .LICENSEURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .SYNOPSIS Checks for non-Microsoft LSA packages on local or remote Windows Servers. .DESCRIPTION This script queries the LSA registry keys (Authentication Packages, Notification Packages, Security Packages) on localhost or specified remote computers and checks the signature of each configured DLL. It reports any DLLs that are not properly signed by a trusted publisher. Also checks OSConfig\Security Packages, LSA Protection (RunAsPPL), and DLL metadata. Uses WinRM/PowerShell Remoting for remote connections. Ensure WinRM is configured on target machines. .PARAMETER ComputerName An array of computer names or IPs to check. Defaults to localhost if not specified. .PARAMETER IncludeMicrosoft Also include Microsoft-signed packages in the output (for audit purposes). .PARAMETER AllowedPublishers An array of allowed publisher name patterns. DLLs signed by these publishers will not be flagged. .PARAMETER ExportPath Optional path to export results to a CSV file. .PARAMETER CheckOnline Open a web browser search for each flagged DLL to verify legitimacy. .PARAMETER OnlineOnly Only report packages that are NOT in the built-in known-good Microsoft LSA package list. .EXAMPLE PS C:\> .\Check-LSAPackageExtensions.ps1 .EXAMPLE PS C:\> .\Check-LSAPackageExtensions.ps1 -ComputerName "MyADServer01", "MyNPSServer01" .EXAMPLE PS C:\> .\Check-LSAPackageExtensions.ps1 -ComputerName "MyADServer01" -IncludeMicrosoft -ExportPath "C:\Temp\lsa_report.csv" .EXAMPLE PS C:\> .\Check-LSAPackageExtensions.ps1 -CheckOnline #> [CmdletBinding()] param ( [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [string[]]$ComputerName = @("localhost"), [switch]$IncludeMicrosoft, [string[]]$AllowedPublishers = @(), [string]$ExportPath, [switch]$CheckOnline, [switch]$OnlineOnly ) # Known legitimate Microsoft LSA packages (base list - can be extended) $KnownGoodLsaPackages = @( 'msv1_0', 'rassfm', 'scecli', 'kerberos', 'schannel', 'wdigest', 'tspkg', 'pku2u', 'cloudap', 'aadp', 'mskdc', 'kdc', 'negotiate', 'livessp' ) function Test-HostReachable { param([string]$ComputerName) if ($ComputerName -eq "localhost" -or $ComputerName -eq "." -or $ComputerName -eq $env:COMPUTERNAME) { return $true } try { $ping = Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop return $ping } catch { Write-Verbose "Ping failed for $ComputerName. $_" return $false } } function Test-WinRMAvailable { param([string]$ComputerName) if ($ComputerName -eq "localhost" -or $ComputerName -eq "." -or $ComputerName -eq $env:COMPUTERNAME) { return $true } try { $session = New-PSSession -ComputerName $ComputerName -ErrorAction Stop Remove-PSSession -Session $session -ErrorAction SilentlyContinue return $true } catch { Write-Verbose "WinRM not available on $ComputerName. $_" return $false } } function Get-SignerInfo { param([string]$FilePath) try { $signature = Get-AuthenticodeSignature -FilePath $FilePath -ErrorAction Stop $publisher = if ($signature.SignerCertificate) { $signature.SignerCertificate.Subject } else { "Not Signed" } $thumbprint = if ($signature.SignerCertificate) { $signature.SignerCertificate.Thumbprint } else { "N/A" } $isMicrosoft = $publisher -like "*Microsoft*" -or $publisher -like "*Windows*" return [PSCustomObject]@{ Status = $signature.Status Publisher = $publisher Thumbprint = $thumbprint IsMicrosoft = $isMicrosoft } } catch { return [PSCustomObject]@{ Status = "Error" Publisher = "Error reading signature: $_" Thumbprint = "N/A" IsMicrosoft = $false } } } function Get-DllMetadata { param([string]$FilePath) try { $item = Get-Item -Path $FilePath -ErrorAction Stop $versionInfo = $item.VersionInfo return [PSCustomObject]@{ FileDescription = $versionInfo.FileDescription ProductName = $versionInfo.ProductName CompanyName = $versionInfo.CompanyName FileVersion = $versionInfo.FileVersion ProductVersion = $versionInfo.ProductVersion } } catch { return [PSCustomObject]@{ FileDescription = "Error: $_" ProductName = "N/A" CompanyName = "N/A" FileVersion = "N/A" ProductVersion = "N/A" } } } function Test-PackageAllowed { param([string]$Publisher, [string[]]$AllowedPublishers) foreach ($pattern in $AllowedPublishers) { if ($Publisher -like $pattern) { return $true } } return $false } function Get-LegitimacyInfo { param([string]$PackageName, [string]$Publisher, [string]$FilePath) $isKnownGood = $false $legitimacyNote = "" $searchUrl = "" if ($KnownGoodLsaPackages -contains $PackageName.ToLower()) { $isKnownGood = $true $legitimacyNote = "Known Microsoft LSA package" } if ($isKnownGood) { return [PSCustomObject]@{ PackageName = $PackageName IsKnownGood = $true LegitimacyNote = $legitimacyNote SearchUrl = "https://learn.microsoft.com/en-us/search/?terms=$PackageName.dll" Recommendation = "LEGITIMATE - Microsoft built-in package" } } else { $encoded = [System.Web.HttpUtility]::UrlEncode("$PackageName.dll LSA package legitimate OR malware") $searchUrl = "https://www.bing.com/search?q=$encoded" return [PSCustomObject]@{ PackageName = $PackageName IsKnownGood = $false LegitimacyNote = "Not in known-good Microsoft LSA list" SearchUrl = $searchUrl Recommendation = "UNKNOWN - Requires manual review" } } } function Get-LSAProtectionStatus { param([string]$ComputerName) try { $regPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' $runAsPPL = Get-ItemProperty -Path $regPath -Name 'RunAsPPL' -ErrorAction SilentlyContinue if ($runAsPPL -and $runAsPPL.RunAsPPL -eq 1) { return [PSCustomObject]@{ LSAProtectionEnabled = $true Status = "ENABLED" Recommendation = "LSA Protection is enabled. This adds an extra layer of security by requiring LSA plugins to run as Protected Process Light (PPL)." } } else { return [PSCustomObject]@{ LSAProtectionEnabled = $false Status = "DISABLED" Recommendation = "LSA Protection is DISABLED. Consider enabling RunAsPPL=1 to protect LSA from unauthorized plugins. Warning: May break third-party LSA packages that are not PPL-compatible." } } } catch { return [PSCustomObject]@{ LSAProtectionEnabled = $false Status = "UNKNOWN" Recommendation = "Could not determine LSA Protection status. Check registry key: HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL" } } } function Invoke-LSACheck { param( [string]$ComputerName, [bool]$UseWinRM, [bool]$IncludeMicrosoft, [string[]]$AllowedPublishers ) $scriptBlock = { param($IncludeMicrosoft, $AllowedPublishers) function Get-SignerInfoLocal { param([string]$FilePath) try { $signature = Get-AuthenticodeSignature -FilePath $FilePath -ErrorAction Stop $publisher = if ($signature.SignerCertificate) { $signature.SignerCertificate.Subject } else { "Not Signed" } $thumbprint = if ($signature.SignerCertificate) { $signature.SignerCertificate.Thumbprint } else { "N/A" } $isMicrosoft = $publisher -like "*Microsoft*" -or $publisher -like "*Windows*" return [PSCustomObject]@{ Status = $signature.Status; Publisher = $publisher; Thumbprint = $thumbprint; IsMicrosoft = $isMicrosoft } } catch { return [PSCustomObject]@{ Status = "Error"; Publisher = "Error: $_"; Thumbprint = "N/A"; IsMicrosoft = $false } } } function Get-DllMetadataLocal { param([string]$FilePath) try { $item = Get-Item -Path $FilePath -ErrorAction Stop $versionInfo = $item.VersionInfo return [PSCustomObject]@{ FileDescription = $versionInfo.FileDescription; ProductName = $versionInfo.ProductName; CompanyName = $versionInfo.CompanyName; FileVersion = $versionInfo.FileVersion } } catch { return [PSCustomObject]@{ FileDescription = "Error: $_"; ProductName = "N/A"; CompanyName = "N/A"; FileVersion = "N/A" } } } $PackageTypes = @('Authentication Packages', 'Notification Packages', 'Security Packages') $Results = foreach ($PackageType in $PackageTypes) { try { $PackagesProperty = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -Name $PackageType -ErrorAction Stop if ($PackagesProperty) { $PackageList = $PackagesProperty.$PackageType if ($PackageList -isnot [array]) { $PackageList = @($PackageList) } foreach ($Package in $PackageList) { $cleanPackage = $Package.Trim() if (-not [string]::IsNullOrEmpty($cleanPackage) -and $cleanPackage -ne '""') { $dllPath = Join-Path -Path $env:SystemRoot -ChildPath "System32\$cleanPackage.dll" $dllPathWow = Join-Path -Path $env:SystemRoot -ChildPath "SysWOW64\$cleanPackage.dll" $foundPath = $null if (Test-Path $dllPath) { $foundPath = $dllPath } elseif (Test-Path $dllPathWow) { $foundPath = $dllPathWow } if ($foundPath) { $signer = Get-SignerInfoLocal -FilePath $foundPath $meta = Get-DllMetadataLocal -FilePath $foundPath $isRisk = $false; $riskReason = @() if ($signer.Status -ne 'Valid') { $isRisk = $true; $riskReason += "Invalid signature ($($signer.Status))" } if (-not $isRisk -and $AllowedPublishers -and $AllowedPublishers.Count -gt 0) { $allowed = $false; foreach ($p in $AllowedPublishers) { if ($signer.Publisher -like $p) { $allowed = $true; break } } if (-not $allowed) { $isRisk = $true; $riskReason += "Not in allowed publishers list" } } if (-not $isRisk -and -not $signer.IsMicrosoft) { $isRisk = $true; $riskReason += "Non-Microsoft package" } if (-not $isRisk -and $IncludeMicrosoft -and $signer.IsMicrosoft) { $isRisk = $true; $riskReason += "Microsoft package (audit mode)" } if ($isRisk) { [pscustomobject]@{ ComputerName = $env:COMPUTERNAME PackageType = $PackageType Package = $cleanPackage Path = $foundPath Publisher = $signer.Publisher Status = $signer.Status IsMicrosoft = $signer.IsMicrosoft RiskReason = $riskReason -join "; " FileDescription = $meta.FileDescription ProductName = $meta.ProductName CompanyName = $meta.CompanyName FileVersion = $meta.FileVersion } } } else { [pscustomobject]@{ ComputerName = $env:COMPUTERNAME PackageType = $PackageType Package = $cleanPackage Path = "Not Found in System32 or SysWOW64" Publisher = "N/A" Status = "N/A" IsMicrosoft = $false RiskReason = "DLL not found on disk" FileDescription = "N/A" ProductName = "N/A" CompanyName = "N/A" FileVersion = "N/A" } } } } } } catch { Write-Verbose "Registry key '$PackageType' not found." } } return $Results } try { if ($UseWinRM) { $Splat = @{ ComputerName = $ComputerName ErrorAction = 'Stop' ScriptBlock = $scriptBlock } $Splat.ArgumentList = @($IncludeMicrosoft, $AllowedPublishers) return Invoke-Command @Splat } else { throw "WinRM is not available on $ComputerName. Cannot perform full LSA package check remotely. Configure WinRM on the target machine or run this script locally on the target." } } catch { throw "Failed to execute LSA check on $ComputerName. $_" } } function Invoke-LSACheckLocal { param($IncludeMicrosoft, $AllowedPublishers) $PackageTypes = @('Authentication Packages', 'Notification Packages', 'Security Packages') $Results = foreach ($PackageType in $PackageTypes) { try { $PackagesProperty = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -Name $PackageType -ErrorAction Stop if ($PackagesProperty) { $PackageList = $PackagesProperty.$PackageType if ($PackageList -isnot [array]) { $PackageList = @($PackageList) } foreach ($Package in $PackageList) { $cleanPackage = $Package.Trim() if (-not [string]::IsNullOrEmpty($cleanPackage) -and $cleanPackage -ne '""') { $dllPath = Join-Path -Path $env:SystemRoot -ChildPath "System32\$cleanPackage.dll" $dllPathWow = Join-Path -Path $env:SystemRoot -ChildPath "SysWOW64\$cleanPackage.dll" $foundPath = $null if (Test-Path $dllPath) { $foundPath = $dllPath } elseif (Test-Path $dllPathWow) { $foundPath = $dllPathWow } if ($foundPath) { $signer = Get-SignerInfo -FilePath $foundPath $meta = Get-DllMetadata -FilePath $foundPath $isRisk = $false; $riskReason = @() if ($signer.Status -ne 'Valid') { $isRisk = $true; $riskReason += "Invalid signature ($($signer.Status))" } if (-not $isRisk -and $AllowedPublishers -and $AllowedPublishers.Count -gt 0) { $allowed = $false; foreach ($p in $AllowedPublishers) { if ($signer.Publisher -like $p) { $allowed = $true; break } } if (-not $allowed) { $isRisk = $true; $riskReason += "Not in allowed publishers list" } } if (-not $isRisk -and -not $signer.IsMicrosoft) { $isRisk = $true; $riskReason += "Non-Microsoft package" } if (-not $isRisk -and $IncludeMicrosoft -and $signer.IsMicrosoft) { $isRisk = $true; $riskReason += "Microsoft package (audit mode)" } if ($isRisk) { [pscustomobject]@{ ComputerName = $env:COMPUTERNAME PackageType = $PackageType Package = $cleanPackage Path = $foundPath Publisher = $signer.Publisher Status = $signer.Status IsMicrosoft = $signer.IsMicrosoft RiskReason = $riskReason -join "; " FileDescription = $meta.FileDescription ProductName = $meta.ProductName CompanyName = $meta.CompanyName FileVersion = $meta.FileVersion } } } else { [pscustomobject]@{ ComputerName = $env:COMPUTERNAME PackageType = $PackageType Package = $cleanPackage Path = "Not Found in System32 or SysWOW64" Publisher = "N/A" Status = "N/A" IsMicrosoft = $false RiskReason = "DLL not found on disk" FileDescription = "N/A" ProductName = "N/A" CompanyName = "N/A" FileVersion = "N/A" } } } } } } catch { Write-Verbose "Registry key '$PackageType' not found." } } return $Results } function Invoke-OSConfigCheck { param($IncludeMicrosoft, $AllowedPublishers) $Results = @() $regPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\OSConfig' try { $PackagesProperty = Get-ItemProperty -Path $regPath -Name 'Security Packages' -ErrorAction Stop if ($PackagesProperty) { $PackageList = $PackagesProperty.'Security Packages' if ($PackageList -isnot [array]) { $PackageList = @($PackageList) } foreach ($Package in $PackageList) { $cleanPackage = $Package.Trim() if (-not [string]::IsNullOrEmpty($cleanPackage) -and $cleanPackage -ne '""') { $dllPath = Join-Path -Path $env:SystemRoot -ChildPath "System32\$cleanPackage.dll" $dllPathWow = Join-Path -Path $env:SystemRoot -ChildPath "SysWOW64\$cleanPackage.dll" $foundPath = $null if (Test-Path $dllPath) { $foundPath = $dllPath } elseif (Test-Path $dllPathWow) { $foundPath = $dllPathWow } if ($foundPath) { $signer = Get-SignerInfo -FilePath $foundPath $meta = Get-DllMetadata -FilePath $foundPath $isRisk = $false; $riskReason = @() if ($signer.Status -ne 'Valid') { $isRisk = $true; $riskReason += "Invalid signature ($($signer.Status))" } if (-not $isRisk -and $AllowedPublishers -and $AllowedPublishers.Count -gt 0) { $allowed = $false; foreach ($p in $AllowedPublishers) { if ($signer.Publisher -like $p) { $allowed = $true; break } } if (-not $allowed) { $isRisk = $true; $riskReason += "Not in allowed publishers list" } } if (-not $isRisk -and -not $signer.IsMicrosoft) { $isRisk = $true; $riskReason += "Non-Microsoft package" } if (-not $isRisk -and $IncludeMicrosoft -and $signer.IsMicrosoft) { $isRisk = $true; $riskReason += "Microsoft package (audit mode)" } if ($isRisk) { $Results += [pscustomobject]@{ ComputerName = $env:COMPUTERNAME PackageType = "OSConfig\Security Packages" Package = $cleanPackage Path = $foundPath Publisher = $signer.Publisher Status = $signer.Status IsMicrosoft = $signer.IsMicrosoft RiskReason = $riskReason -join "; " FileDescription = $meta.FileDescription ProductName = $meta.ProductName CompanyName = $meta.CompanyName FileVersion = $meta.FileVersion } } } else { $Results += [pscustomobject]@{ ComputerName = $env:COMPUTERNAME PackageType = "OSConfig\Security Packages" Package = $cleanPackage Path = "Not Found in System32 or SysWOW64" Publisher = "N/A" Status = "N/A" IsMicrosoft = $false RiskReason = "DLL not found on disk" FileDescription = "N/A" ProductName = "N/A" CompanyName = "N/A" FileVersion = "N/A" } } } } } } catch { Write-Verbose "OSConfig\Security Packages registry key not found. This is normal on some systems." } return $Results } function Add-LegitimacyInfo { param([System.Collections.ArrayList]$Results) for ($i = 0; $i -lt $Results.Count; $i++) { $r = $Results[$i] if ($r.RiskReason -eq "DLL not found on disk") { $Results[$i] | Add-Member -NotePropertyName Legitimacy -NotePropertyValue "UNKNOWN - File missing" -Force $Results[$i] | Add-Member -NotePropertyName SearchUrl -NotePropertyValue "" -Force } else { $legitimacy = Get-LegitimacyInfo -PackageName $r.Package -Publisher $r.Publisher -FilePath $r.Path $Results[$i] | Add-Member -NotePropertyName Legitimacy -NotePropertyValue $legitimacy.Recommendation -Force $Results[$i] | Add-Member -NotePropertyName SearchUrl -NotePropertyValue $legitimacy.SearchUrl -Force } } } function Show-InvestigationGuidance { param([object]$Result) Write-Host "`n --- Investigation Steps for $($Result.Package) ---" -ForegroundColor Yellow Write-Host " 1. Identify which application installed this package." -ForegroundColor White Write-Host " Path: $($Result.Path)" -ForegroundColor Gray Write-Host " 2. Verify digital signature details:" -ForegroundColor White Write-Host " Publisher: $($Result.Publisher)" -ForegroundColor Gray Write-Host " Status: $($Result.Status)" -ForegroundColor Gray Write-Host " 3. Check file metadata:" -ForegroundColor White Write-Host " Description: $($Result.FileDescription)" -ForegroundColor Gray Write-Host " Product: $($Result.ProductName)" -ForegroundColor Gray Write-Host " Company: $($Result.CompanyName)" -ForegroundColor Gray Write-Host " Version: $($Result.FileVersion)" -ForegroundColor Gray Write-Host " 4. Confirm whether the application is approved in your environment." -ForegroundColor White Write-Host " 5. Check vendor documentation for LSA/PPL compatibility." -ForegroundColor White Write-Host " 6. Review relevant Windows event logs (Event Viewer > Applications and Services Logs > Microsoft > Windows > LSA)." -ForegroundColor White Write-Host " 7. Update the application to a supported version." -ForegroundColor White Write-Host " 8. Remove ONLY after confirming it is unnecessary or unauthorized." -ForegroundColor White Write-Host " Search for more info: $($Result.SearchUrl)" -ForegroundColor Cyan Write-Host "" } function Main { Write-Host '=== LSA Package Extension Checker ===' -ForegroundColor Cyan Write-Host "" $allResults = @() foreach ($Computer in $ComputerName) { Write-Host "--- Checking LSA packages on $Computer ---" $isLocal = $Computer -eq "localhost" -or $Computer -eq "." -or $Computer -eq $env:COMPUTERNAME if (-not $isLocal) { if (-not (Test-HostReachable -ComputerName $Computer)) { Write-Warning "[SKIP] Host $Computer is not reachable. Skipping." Write-Host "--- Finished check for $Computer ---`n" continue } $hasWinRM = Test-WinRMAvailable -ComputerName $Computer if ($hasWinRM) { Write-Host "[INFO] Using WinRM for $Computer." -ForegroundColor Cyan } else { Write-Warning "[BLOCKED] WinRM is not available on $Computer. Cannot check this host remotely." Write-Warning " To enable WinRM on the target, run: Enable-PSRemoting -Force" Write-Warning " Alternatively, copy and run this script directly on the target machine." Write-Host "--- Finished check for $Computer ---`n" continue } } else { Write-Host "[INFO] Checking local machine." -ForegroundColor Cyan } try { $results = if ($isLocal) { $localResults = Invoke-LSACheckLocal -IncludeMicrosoft $IncludeMicrosoft -AllowedPublishers $AllowedPublishers $osConfigResults = Invoke-OSConfigCheck -IncludeMicrosoft $IncludeMicrosoft -AllowedPublishers $AllowedPublishers $localResults + $osConfigResults } else { Invoke-LSACheck -ComputerName $Computer -UseWinRM $hasWinRM -IncludeMicrosoft $IncludeMicrosoft -AllowedPublishers $AllowedPublishers } $lsaProtection = Get-LSAProtectionStatus -ComputerName $Computer Write-Host "`n LSA Protection Status: $($lsaProtection.Status)" -ForegroundColor $(if ($lsaProtection.LSAProtectionEnabled) { "Green" } else { "Yellow" }) Write-Host " $($lsaProtection.Recommendation)`n" -ForegroundColor Gray if ($results) { $resultsArrayList = [System.Collections.ArrayList]@($results) Add-LegitimacyInfo -Results $resultsArrayList $results = $resultsArrayList if ($OnlineOnly) { $results = $results | Where-Object { $_.Legitimacy -ne "LEGITIMATE - Microsoft built-in package" } } if ($results) { $allResults += $results Write-Host "[ALERT] Found $($results.Count) LSA package(s) that may pose a security risk on $Computer." $results | Format-Table -AutoSize if ($CheckOnline) { Write-Host "`n--- Opening Online Legitimacy Checks ---" -ForegroundColor Yellow foreach ($r in $results) { Write-Host "[$($r.Package)] $($r.Legitimacy)" -ForegroundColor $(if ($r.Legitimacy -like "LEGITIMATE*") { "Green" } else { "Red" }) Write-Host " Search: $($r.SearchUrl)" try { Start-Process $r.SearchUrl } catch { Write-Warning " Could not open browser: $_" } } } foreach ($r in $results) { Show-InvestigationGuidance -Result $r } } } else { Write-Host "[OK] No risky LSA packages found on $Computer." -ForegroundColor Green } } catch { Write-Warning "[ERROR] Failed to check $Computer. WinRM may not be configured. To enable: Run 'Enable-PSRemoting -Force' on the target machine, or run this script directly on the target. Message: $($_.Exception.Message)" } Write-Host "--- Finished check for $Computer ---`n" } if ($allResults.Count -gt 0 -and $ExportPath) { try { $allResults | Export-Csv -Path $ExportPath -NoTypeInformation -Encoding UTF8 -ErrorAction Stop Write-Host "`nResults exported to: $ExportPath" -ForegroundColor Cyan } catch { Write-Warning "Failed to export results: $_" } } Write-Host "" Write-Host "=== Summary ===" -ForegroundColor Cyan Write-Host "Total computers checked: $($ComputerName.Count)" Write-Host "Total risky packages found: $($allResults.Count)" } Main |