Last active
February 22, 2025 02:34
-
-
Save jsecurity101/6b9e87f5a428f31d41ffc8c1ee05a999 to your computer and use it in GitHub Desktop.
Powershell script that will pull whether a process or service is running as protected (PPL).
This file contains 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
#Author: Jonthan Johnson (@jsecurity101) | |
if (-not ('ProtectedObjects.ProcessNativeMethods' -as [Type])) { | |
$TypeDef = @' | |
using System; | |
using System.Runtime.InteropServices; | |
namespace ProtectedObjects { | |
[Flags] | |
public enum ProcessAccess { | |
AllAccess = 0x001FFFFF, | |
Terminate = 0x00000001, | |
CreateThread = 0x00000002, | |
VirtualMemoryOperation = 0x00000008, | |
VirtualMemoryRead = 0x00000010, | |
VirtualMemoryWrite = 0x00000020, | |
DuplicateHandle = 0x00000040, | |
CreateProcess = 0x000000080, | |
SetQuota = 0x00000100, | |
SetInformation = 0x00000200, | |
QueryInformation = 0x00000400, | |
QueryLimitedInformation = 0x00001000, | |
Synchronize = 0x00100000 | |
} | |
[Flags] | |
public enum PROCESSINFOCLASS | |
{ | |
ProcessProtectionInformation = 0x3D, | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct _PsProtection | |
{ | |
public PsProtectedType Type; | |
public PsProtectedSigner Signer; | |
public bool Audit; | |
} | |
[Flags] | |
public enum PsProtectedType | |
{ | |
PsProtectedTypeNone = 0x0, | |
PsProtectedTypeProtectedLight = 0x1, | |
PsProtectedTypeProtected = 0x2, | |
PsProtectedTypeMax = 0x3, | |
} | |
[Flags] | |
public enum PsProtectedSigner | |
{ | |
PsProtectedSignerNone = 0x0, | |
PsProtectedSignerAuthenticode = 0x1, | |
PsProtectedSignerCodeGen = 0x2, | |
PsProtectedSignerAntimalware = 0x3, | |
PsProtectedSignerLsa = 0x4, | |
PsProtectedSignerWindows = 0x5, | |
PsProtectedSignerWinTcb = 0x6, | |
PsProtectedSignerMax = 0x7, | |
} | |
public class ProcessNativeMethods { | |
[DllImport("kernel32.dll", SetLastError = true)] | |
public static extern IntPtr OpenProcess( | |
ProcessAccess processAccess, | |
bool bInheritHandle, | |
int processId); | |
[DllImport("ntdll.dll", SetLastError=true)] | |
public static extern int NtQueryInformationProcess( | |
IntPtr processHandle, | |
PROCESSINFOCLASS processInformationClass, | |
ref _PsProtection processInformation, | |
int processInformationLength, | |
ref int returnLength); | |
[DllImport("kernel32.dll", SetLastError=true)] | |
public static extern bool CloseHandle( | |
IntPtr hHandle); | |
} | |
} | |
'@ | |
Add-Type -TypeDefinition $TypeDef | |
} | |
function Get-ProtectedProcess { | |
<# | |
.SYNOPSIS | |
Performs an audit on processes to find their protection level. | |
.DESCRIPTION | |
Get-ProtectedProcess performs a protected process level audit on current running processes. | |
.PARAMETER ProcessId | |
Specifies process identifier of the desired process to be audited. | |
.PARAMETER ReturnOnlyProtected | |
Switch to only return processes where the protection level is greater than (0x0) PsProtectedTypeNone | |
.EXAMPLE | |
$Result = Get-ProtectedProcess -ReturnOnlyProtected | |
.EXAMPLE | |
$Result = Get-Process lsass | Get-ProtectedProcess | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(ValueFromPipelineByPropertyName)] | |
[Int32] | |
[Alias('Id')] | |
$ProcessId, | |
[switch] | |
$ReturnOnlyProtected | |
) | |
if (($PSBoundParameters.ContainsKey('ProcessId'))) | |
{ | |
$Processes = Get-Process -Id $ProcessId | |
} | |
else{ | |
$Processes = Get-Process | |
} | |
foreach ($p in $Processes) | |
{ | |
$ProcessHandle = [ProtectedObjects.ProcessNativeMethods]::OpenProcess( | |
'QueryLimitedInformation', | |
$False, | |
$p.Id | |
);$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
if($ProcessHandle -eq [IntPtr]::Zero){ | |
Write-Warning $LastError | |
continue | |
} | |
$ppl = New-Object ProtectedObjects._PsProtection | |
$Size = [System.Runtime.InteropServices.Marshal]::SizeOf($ppl) | |
$returnLength = New-Object Int | |
[int]$Status = [ProtectedObjects.ProcessNativeMethods]::NtQueryInformationProcess( | |
$ProcessHandle, | |
'ProcessProtectionInformation', | |
[ref]$ppl, | |
$Size, | |
[ref]$returnLength | |
);$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
$null = [ProtectedObjects.ProcessNativeMethods]::CloseHandle($ProcessHandle) | |
if($Status -ne 0){ | |
Write-Error $LastError | |
return | |
} | |
else{ | |
$level = ('{0:x}' -f (([byte]$ppl.Type) -bor $ppl.Audit -bor ([int]$ppl.Signer -shl 4))) | |
switch($level) | |
{ | |
0 | |
{ | |
$levelStr = "PsProtectedTypeNone" | |
} | |
11 | |
{ | |
$levelStr = "PsProtectedSignerAuthenticode-Light" | |
} | |
21 | |
{ | |
$levelStr ="PsProtectedSignerCodeGen-Light" | |
} | |
31 | |
{ | |
$levelStr = "PsProtectedSignerAntimalware-Light" | |
} | |
41 | |
{ | |
$levelStr = "PsProtectedSignerLsa-Light" | |
} | |
51 | |
{ | |
$levelStr = "PsProtectedSignerWindows-Light" | |
} | |
61 | |
{ | |
$levelStr = "PsProtectedSignerWinTcb-Light" | |
} | |
71 | |
{ | |
$levelStr = "PsProtectedSignerMax-Light" | |
} | |
12 | |
{ | |
$levelStr = "PsProtectedSignerAuthenticode" | |
} | |
22 | |
{ | |
$levelStr = "PsProtectedSignerCodeGen" | |
} | |
32 | |
{ | |
$levelStr = "PsProtectedSignerAntimalware" | |
} | |
42 | |
{ | |
$levelStr = "PsProtectedSignerLsa" | |
} | |
52 | |
{ | |
$levelStr = "PsProtectedSignerWindows" | |
} | |
62 | |
{ | |
$levelStr = "PsProtectedSignerWinTcb" | |
} | |
72 | |
{ | |
$levelStr = "PsProtectedSignerMax" | |
} | |
13 | |
{ | |
$levelStr = "PsProtectedSignerAuthenticode-Max" | |
} | |
23 | |
{ | |
$levelStr = "PsProtectedSignerCodeGen-Max" | |
} | |
33 | |
{ | |
$levelStr = "PsProtectedSignerAntimalware-Max" | |
} | |
43 | |
{ | |
$levelStr = "PsProtectedSignerLsa-Max" | |
} | |
53 | |
{ | |
$levelStr = "PsProtectedSignerWindows-Max" | |
} | |
63 | |
{ | |
$levelStr = "PsProtectedSignerWinTcb-Max" | |
} | |
73 | |
{ | |
$levelStr = "PsProtectedSignerMax-Max" | |
} | |
} | |
if ($ReturnOnlyProtected){ | |
if ($levelStr -ne "PsProtectedTypeNone"){ | |
[PSCustomObject] @{ | |
ProcessName = $p.Name | |
ProcessId = $p.Id | |
ProtectedLevelHex = '0x'+$level | |
ProtectedLevel = $levelStr | |
} | |
} | |
else{ | |
} | |
} | |
else{ | |
[PSCustomObject] @{ | |
ProcessName = $p.Name | |
ProcessId = $p.Id | |
ProtectedLevelHex = '0x'+$level | |
ProtectedLevel = $levelStr | |
} | |
} | |
} | |
} | |
} | |
function Get-ProtectedService { | |
<# | |
.SYNOPSIS | |
Performs an audit on services to find their protection level. | |
.DESCRIPTION | |
Get-ProtectedService performs an audit to see the protection level a service is launched as. | |
.PARAMETER ServiceName | |
Specifies the target service. | |
.EXAMPLE | |
$Result = Get-ProtectedService -ServiceName WinDefend | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(ValueFromPipelineByPropertyName)] | |
[string] | |
$ServiceName | |
) | |
if (-not ($PSBoundParameters.ContainsKey('ServiceName'))){ | |
$Services = Get-CimInstance Win32_Service -Filter "State = `"Running`"" | |
} | |
else{ | |
$Services = Get-CimInstance Win32_Service -Filter "Name = `"$ServiceName`"" | |
} | |
foreach ($service in $Services.Name) { | |
$query = Get-ItemProperty Registry::HKLM\SYSTEM\CurrentControlSet\Services\$service | |
if ($query.LaunchProtected -ne $null){ | |
switch($query.LaunchProtected){ | |
0 | |
{ | |
$LaunchProtected = 'None' | |
} | |
1 | |
{ | |
$LaunchProtected = 'Windows' | |
} | |
2 | |
{ | |
$LaunchProtected = 'WindowsLight' | |
} | |
3 | |
{ | |
$LaunchProtected = 'AntiMalwareLight' | |
} | |
} | |
[PSCustomObject] @{ | |
ServiceName = $service | |
ProtectedLevelHex = $query.LaunchProtected | |
ProtectedLevel = $LaunchProtected | |
} | |
} | |
} | |
} | |
function Get-ProtectedProperties { | |
$ProtectedProcess = Get-ProtectedProcess -ReturnOnlyProtected | |
$ProtectedService = Get-ProtectedService | |
[PSCustomObject] @{ | |
Process = $ProtectedProcess | |
Service = $ProtectedService | |
} | |
} |
an alternative is also using sc
sc
can only report the protection level of services, which is already useful in itself, but not of processes. The PS script is therefore still useful at least in this regard.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
your post led me here
great script! an alternative is also using sc: