Enumerate non-signed service binaries

This script identifies services on a Windows system where the associated binaries are either not signed or are signed by an issuer that does not match "Microsoft". The script can be modified to check for other certificate issuers as well by editing the final Where-Object statement

Where-Object {(-not $_.Signed) -or ($_.Issuer -notmatch 'Microsoft|CompanyA|CompanyB')}
function Get-NonstandardService {
<#
.SYNOPSIS

Returns services where the associated binaries are either not signed, or are
signed by an issuer not matching 'Microsoft'.

Required Dependencies: None  
#>
    [CmdletBinding()]
    Param()

	# helper to clone objects
    function CloneObject($Object) {
        $NewObj = New-Object PsObject
        $Object.psobject.Properties | ForEach-Object { Add-Member -MemberType NoteProperty -InputObject $NewObj -Name $_.Name -Value $_.Value }
        $NewObj
    }

	# retrieves the base .exe/.dll/.sys binary path without the arguments
    function Get-BinaryBasePath {

        [CmdletBinding()]
        Param(
            [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
            [Alias('PathName', 'FilePath')]
            [String]
            $Path
        )

        if ($Path -and ($Path -match '^\W*(?<ServicePath>[a-z]:\\.+?(\.exe|\.dll|\.sys))\W*')) {
            $Matches['ServicePath']
        }
        else {
            Write-Warning "Regex failed for the following path: $Path"
        }
    }

	# retrieves metadata about a PE
    function Get-PEMetaData {

        [CmdletBinding()]
        param($Path)

        try {
            $FullPath = Resolve-Path -Path $Path -ErrorAction Stop
            try {
				# try to get the assembly name - quick check if the binary is .NET
				# this works if the binary is .NET
                $Null = [Reflection.AssemblyName]::GetAssemblyName($FullPath)
                $IsDotNet = $True
            }
            catch {
                $IsDotNet = $False
            }

			# grab the signature for the PE and issuer if present
            $Signature = Get-AuthenticodeSignature -FilePath $FullPath -ErrorAction SilentlyContinue
            if ($Signature -and ($Signature.Status -eq 'NotSigned')) {
                $Signed = $False
                $Issuer = $Null
            }
            else {
                $Signed = $True
                $Issuer = $Signature.SignerCertificate.Issuer
            }

			# create the custom output object
            $Out = New-Object PSObject
            $Out | Add-Member Noteproperty 'Path' $FullPath
            $Out | Add-Member Noteproperty 'Signed' $Signed
            $Out | Add-Member Noteproperty 'Issuer' $Issuer
            $Out | Add-Member Noteproperty 'IsDotNet' $IsDotNet
            $Out
        }
        catch {
            Write-Warning "Unable to resolve path: $Path"
        }
    }

	# hashtable that functions as a "cache" to prevent a PE from being enumerated more than once
    $MetadataCache = @{}
	# use WMI to enumerate all services and binary paths
    Get-WmiObject -Class win32_Service -Property Name,PathName,StartMode,State,ProcessID | Where-Object { $_.PathName } | ForEach-Object {
        $BasePath = Get-BinaryBasePath -Path $_.PathName
        $ServiceName = $_.Name

        Write-Verbose "[Get-NonstandardService] Service $ServiceName : $BasePath"

        if ($MetadataCache[$BasePath]) {
            $Metadata = $MetadataCache[$BasePath]
        }
        else {
            $Metadata = Get-PEMetaData -Path $BasePath
            $MetadataCache[$BasePath] = $Metadata
        }

        $ObjectMetadata = CloneObject $Metadata
        $ObjectMetadata | Add-Member Noteproperty 'Name' $ServiceName
        $ObjectMetadata | Add-Member Noteproperty 'PathName' $_.PathName
        $ObjectMetadata | Add-Member Noteproperty 'StartMode' $_.StartMode
        $ObjectMetadata | Add-Member Noteproperty 'State' $_.State
        $ObjectMetadata | Add-Member Noteproperty 'ProcessID' $_.ProcessID
        $ObjectMetadata
    } | Where-Object {(-not $_.Signed) -or ($_.Issuer -notmatch 'Microsoft')}
}

References:

Last updated