Skip to content

Instantly share code, notes, and snippets.

@mattifestation
Created January 22, 2019 18:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mattifestation/3c2e8f80ca1fe1a7e276ee2607da8d18 to your computer and use it in GitHub Desktop.
Save mattifestation/3c2e8f80ca1fe1a7e276ee2607da8d18 to your computer and use it in GitHub Desktop.
function Get-ProcessStartKey {
<#
.SYNOPSIS
Derives the process start key for one or more processes.
.DESCRIPTION
Get-ProcessStartKey derives the process start key for one or more processes. Process start keys were introduced in Win 10 1507 and are intended to serve as a locally unique identifier for a process. A process ID cannot be considered a unique identifier since process IDs are repeatable.
Author: Matthew Graeber (@mattifestation)
.PARAMETER Id
Specifies one or more processes by process ID (PID). To specify multiple IDs, use commas to separate the IDs.
.EXAMPLE
Get-ProcessStartKey -Id $PID
Retrieves the process start key for the current PowerShell process.
.EXAMPLE
Get-Process notepad | Get-ProcessStartKey
Retrieves the process start keys for notepad.exe processes.
.INPUTS
System.Diagnostics.Process
Accepts one or more Process objects over the pipeline.
#>
param (
[Parameter(ValueFromPipelineByPropertyName)]
[Int32[]]
[Alias('PID')]
$Id
)
BEGIN {
$WindowsReleaseId = [UInt32] (Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'ReleaseId')
# Process start key is supported in versions >= 1507 according to this doc:
# https://docs.microsoft.com/en-us/windows/desktop/ETW/enable-trace-parameters
if ($WindowsReleaseId -ge 1507) {
$ProcessSequenceNumber = 92
$UInt64Length = 8
if (-not ('Win32.NativeMethods' -as [Type])) {
$Sig = @'
[DllImport("ntdll.dll")]
public static extern int NtQueryInformationProcess(IntPtr processHandle, int ProcessInformationClass, out ulong ProcessSequenceNumber, uint ProcessInformationLength, out uint ReturnLength);
'@
Add-Type -MemberDefinition $Sig -Name 'NativeMethods' -Namespace 'Win32' -ErrorAction Stop
}
# Get _KUSER_SHARED_DATA.BootId (offset 0x02C4 for both x86 and x64)
[UInt64] $BootId = [System.Runtime.InteropServices.Marshal]::ReadInt32(0x7ffe02C4)
} else {
Write-Error 'Process start key is not supported in this version of Windows. Win 10 1507+ is required.'
}
}
PROCESS {
foreach ($ProcessID in $ID) {
$ProcessInfo = Get-Process -Id $ProcessID
if ($ProcessInfo.Handle) {
[UInt64] $SequenceNumber = 0
[UInt32] $ReturnLength = 0
# This won't work in 32-bit PowerShell on a 64-bit OS.
$Result = [Win32.NativeMethods]::NtQueryInformationProcess($ProcessInfo.Handle, $ProcessSequenceNumber, [Ref] $SequenceNumber, $UInt64Length, [Ref] $ReturnLength)
if (($Result -eq 0) -and ($ReturnLength -eq $UInt64Length)) {
[PSCustomObject] @{
PID = $ProcessID
ProcessStartKey = (($BootId -shl 0x30) -bor $SequenceNumber)
BootID = $BootId
ProcessSequenceNumber = $SequenceNumber
}
} else {
$NTSTATUSMessage = Get-NTStatusException -ErrorCode $Result
Write-Error "Unable to obtain process sequence number for process ID $ProcessID. NtQueryInformationProcess result: $NTSTATUSMessage (0x$($Result.ToString('X8')))"
}
} else {
Write-Error "Unable to obtain process handle for process ID $ProcessID."
}
}
}
}
function Get-NTStatusException
{
<#
.SYNOPSIS
Resolves an NTSTATUS error code.
.DESCRIPTION
Get-NTStatusException returns a friendly error message based on the NTSTATUS code passed in. This function is useful when interacting with Windows Native API functions with NTSTATUS return codes.
Author: Matthew Graeber (@mattifestation)
.PARAMETER ErrorCode
An NTSTATUS code returned by a native API function (Nt or Rtl prefixed functions)
.EXAMPLE
C:\PS> Get-NTStatusException -ErrorCode 0xC0000005
Invalid access to memory location.
.EXAMPLE
C:\PS> 0xC0000005, 0xC0000017, 0x00000000 | Get-NTStatusException
Invalid access to memory location.
Not enough storage is available to process this command.
The operation completed successfully.
#>
[CmdletBinding()] Param (
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)]
[Int32[]]
$ErrorCode
)
BEGIN
{
Set-StrictMode -Version 2
$Win32Native = [AppDomain]::CurrentDomain.GetAssemblies() | %{ $_.GetTypes() } | ? { $_.FullName -eq 'Microsoft.Win32.Win32Native' }
if ($Win32Native -eq $null)
{
throw "Unable to get a reference to type: Microsoft.Win32.Win32Native"
}
$LsaNtStatusToWinError = $Win32Native.GetMethod('LsaNtStatusToWinError', [Reflection.BindingFlags] 'NonPublic, Static')
$GetMessage = $Win32Native.GetMethod('GetMessage', [Reflection.BindingFlags] 'NonPublic, Static')
}
PROCESS
{
foreach ($Error in $ErrorCode)
{
$WinErrorCode = $LsaNtStatusToWinError.Invoke($null, @($ErrorCode))
$GetMessage.Invoke($null, @($WinErrorCode))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment