From f6ed13a15deb25e1159f4cfbadb0fb3d7c2984af Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 14 Aug 2022 04:26:37 -0700 Subject: [PATCH 001/306] Allow Unsafe code for pointers, fixed-arrays --- deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj index 86b856e..ab61b80 100644 --- a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj +++ b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj @@ -21,6 +21,7 @@ 1.0.1.0 1.0.1.0 1.0.1 + true From 595269a9c025f4d6fe28327d254786bc5eb20fb3 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 22 Aug 2022 03:16:46 -0700 Subject: [PATCH 002/306] build: add PackageDependency 'CsWin32' This .NET 5+ code generator creates C# equivalents of Win32 API code as specified in NativeMethods.txt. The generated code may need adjustments for usability e.g. - implicit conversion of integers to bool - manual adaptation of macros to methods - C-Compiler constant to const field See https://github.com/Microsoft/CsWin32 for help --- deadlock-dotnet-sdk/NativeMethods.txt | 14 ++++++++++++++ deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj | 11 ++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 deadlock-dotnet-sdk/NativeMethods.txt diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt new file mode 100644 index 0000000..9720b6a --- /dev/null +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -0,0 +1,14 @@ +// Enter the name of any *Win32* API function, struct, enum, et cetera +// for it to be generated somewhere under the namespace "Windows.Win32" +// +// # Where can I find X? +// Most of what you'll see in the WDK is not in the Win32 API. +// As such, CsWin32 will not generate code for Kernel API +// except for some Kernel32 definitions. +// +// Some editors support browsing symbols in the current Solution or workspace. +// The hotkey for this is CTRL+T in Visual Studio and Visual Studio Code. +// +// Methods will *always* generate in the class "Windows.Win32.PInvoke" +// Basic types are typically found under "Windows.Win32.Foundation" +// and/or "Windows.Win32.System.WindowsProgramming" diff --git a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj index ab61b80..9f14dc0 100644 --- a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj +++ b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj @@ -21,7 +21,8 @@ 1.0.1.0 1.0.1.0 1.0.1 - true + true + 9 @@ -39,4 +40,12 @@ + + + + + all + + + From 6e921d52f61a15b9ad617bca9e73b094f70103f7 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:18:19 -0700 Subject: [PATCH 003/306] build: add needed symbols to CsWin32's text file --- deadlock-dotnet-sdk/NativeMethods.txt | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index 9720b6a..55e45ef 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -12,3 +12,41 @@ // Methods will *always* generate in the class "Windows.Win32.PInvoke" // Basic types are typically found under "Windows.Win32.Foundation" // and/or "Windows.Win32.System.WindowsProgramming" +// +// SetLastError is only enabled for native functions that support it. + +//// Foundation //// +BOOLEAN +HANDLE +DUPLICATE_CLOSE_SOURCE + +//// Restart Manager //// +RM_PROCESS_INFO + +//// PInvoke //// +CloseHandle +DuplicateHandle +GetFinalPathNameByHandle +IsWow64Process +NtQueryObject +NtQueryInformationProcess +NtQuerySystemInformation +OpenProcess +QueryFullProcessImageName +ReadProcessMemory +RmEndSession +RmGetList +RmRegisterResources +RmStartSession + +//// System.Threading //// +PEB +ProcessBasicInformation +PROCESS_BASIC_INFORMATION +PROCESSINFOCLASS +PROCESS_QUERY_LIMITED_INFORMATION +PROCESS_VM_READ + +//// System.WindowsProgramming //// +PUBLIC_OBJECT_TYPE_INFORMATION + From f516cffb615a19162e604765794802ee5216b07e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:21:20 -0700 Subject: [PATCH 004/306] build: update project file - update indentation - remove LangVersion spec. .NET 6 is C#10 anyway. - simplify CsWin32 PackageReference. Set floating version. --- .../deadlock-dotnet-sdk.csproj | 89 +++++++++---------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj index 9f14dc0..19daf35 100644 --- a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj +++ b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj @@ -1,51 +1,42 @@ - - - net6.0-windows - deadlock_dotnet_sdk - enable - enable - deadlock-dotnet-sdk - DeadLock SDK that allows developers to unlock files that cannot be removed on Windows - Copyright © 2022 CodeDead - https://github.com/CodeDead/deadlock-dotnet-sdk - https://github.com/CodeDead/deadlock-dotnet-sdk - git - file;deadlock;codedead;handle;file-handle; - CodeDead - CodeDead - lock-fill.png - README.md - False - LICENSE - 1.0.1.0 - 1.0.1.0 - 1.0.1 - true - 9 - - - - - True - \ - - - True - \ - - - True - \ - - - - - - - - all - - - + + net6.0-windows + deadlock_dotnet_sdk + enable + enable + deadlock-dotnet-sdk + DeadLock SDK that allows developers to unlock files that cannot be removed on Windows + Copyright © 2022 CodeDead + https://github.com/CodeDead/deadlock-dotnet-sdk + https://github.com/CodeDead/deadlock-dotnet-sdk + git + file;deadlock;codedead;handle;file-handle; + CodeDead + CodeDead + lock-fill.png + README.md + False + LICENSE + 1.0.1.0 + 1.0.1.0 + 1.0.1 + true + + + + True + \ + + + True + \ + + + True + \ + + + + + From 2d597de04ba90c72e8fe3009fe4ede9f25e7e2c2 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:23:36 -0700 Subject: [PATCH 005/306] build: add CsWin32's json config `emitSingleFile` makes searching for symbols a bit easier in VSCode; VSCode can't find code-genned symbols in the workspace right now. --- deadlock-dotnet-sdk/NativeMethods.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 deadlock-dotnet-sdk/NativeMethods.json diff --git a/deadlock-dotnet-sdk/NativeMethods.json b/deadlock-dotnet-sdk/NativeMethods.json new file mode 100644 index 0000000..2dc1665 --- /dev/null +++ b/deadlock-dotnet-sdk/NativeMethods.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "emitSingleFile": true +} From 0c64d06dfbbdd8326ba6a21582f754ad091f4944 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:26:59 -0700 Subject: [PATCH 006/306] refactor: supplement CsWin32 w/ modified/custom definitions --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 211 +++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 deadlock-dotnet-sdk/CsWin32_NativeMethods.cs diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs new file mode 100644 index 0000000..1af20d8 --- /dev/null +++ b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs @@ -0,0 +1,211 @@ +// This file is used to extend the partial definitions generated by CsWin32 + +using System.ComponentModel; +using System.Runtime.InteropServices; +using Windows.Win32.System.Threading; + +namespace Windows.Win32 +{ + namespace Foundation + { + internal partial struct HANDLE : IComparable + { + public static implicit operator HANDLE(nuint v) => new((nint)v); + public static implicit operator nuint(HANDLE v) => (nuint)(nint)v.Value; + + public static implicit operator HANDLE(nint v) => new(v); + public static implicit operator nint(HANDLE v) => v.Value; + + /// + /// Close the handle via the CloseHandle function + /// + /// + /// If the application is running under a debugger, the function will throw an + /// exception if it receives either a handle value that is not valid or a + /// pseudo-handle value. This can happen if you close a handle twice, or if you + /// call CloseHandle on a handle returned by the FindFirstFile function instead + /// of calling the FindClose function. + /// + public void Close() + { + if (!PInvoke.CloseHandle(this)) + throw new Win32Exception(); + } + + public int CompareTo(HANDLE other) => Value.CompareTo(other); + } + + internal readonly partial struct NTSTATUS + { + public bool IsSuccessful => SeverityCode == Severity.Success; + } + + internal unsafe readonly partial struct PWSTR : IDisposable + { + public void Dispose() + { + Marshal.FreeHGlobal((IntPtr)Value); + } + } + + internal partial struct UNICODE_STRING + { + /// + /// Allocates a managed string and copies a specified number of characters from an unmanaged Unicode string into it. + /// + public unsafe string ToStringLength() => Marshal.PtrToStringUni((IntPtr)Buffer.Value, Length); + public string? ToStringZ() => Buffer.ToString(); + public static explicit operator string(UNICODE_STRING v) => v.ToStringLength(); + } + } + + namespace Security + { + /// + /// A simple placeholder for dotnet/PInvoke's ACCESS_MASK struct + /// + /// Process access modifiers are found in Windows.Win32.System.Threading.PROCESS_ACCESS_RIGHTS + internal struct ACCESS_MASK + { + public ACCESS_MASK(uint v) + { + Value = v; + } + + public uint Value; + public PROCESS_ACCESS_RIGHTS ProcessAccess => (PROCESS_ACCESS_RIGHTS)Value; + + public static implicit operator ACCESS_MASK(uint v) => new(v); + public static implicit operator uint(ACCESS_MASK v) => v.Value; + + public const uint DELETE = 0x00010000; + public const uint READ_CONTROL = 0x00020000; + public const uint WRITE_DAC = 0x00040000; + public const uint WRITE_OWNER = 0x00080000; + public const uint SYNCHRONIZE = 0x00100000; + + #region StandardAccess + public const uint STANDARD_RIGHTS_REQUIRED = DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER; + + public const uint STANDARD_RIGHTS_READ = READ_CONTROL; + public const uint STANDARD_RIGHTS_WRITE = READ_CONTROL; + public const uint STANDARD_RIGHTS_EXECUTE = READ_CONTROL; + + public const uint STANDARD_RIGHTS_ALL = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE; + + #endregion StandardAccess + + public const uint SPECIFIC_RIGHTS_ALL = 0x0000FFFF; + + /// + /// AccessSystemAcl access type + /// + public const uint ACCESS_SYSTEM_SECURITY = 0x01000000; + + /// These are the generic rights. + #region GenericRights + public const uint GENERIC_READ = 0x80000000; + public const uint GENERIC_WRITE = 0x40000000; + public const uint GENERIC_EXECUTE = 0x20000000; + public const uint GENERIC_ALL = 0x10000000; + + #endregion GenericRights + /// + /// MaximumAllowed access type + /// + public const uint MAXIMUM_ALLOWED = 0x02000000; + } + } + + namespace System.WindowsProgramming + { + // generated definition lacks SystemHandleInformation, SystemExtendedHandleInformation + internal enum SYSTEM_INFORMATION_CLASS + { + SystemBasicInformation = 0, + SystemPerformanceInformation = 2, + SystemTimeOfDayInformation = 3, + SystemProcessInformation = 5, + SystemProcessorPerformanceInformation = 8, + SystemHandleInformation = 16, + SystemInterruptInformation = 23, + SystemExceptionInformation = 33, + SystemRegistryQuotaInformation = 37, + SystemLookasideInformation = 45, + SystemExtendedHandleInformation = 64, + SystemCodeIntegrityInformation = 103, + SystemPolicyInformation = 134 + } + + internal enum OBJECT_INFORMATION_CLASS + { + ObjectBasicInformation = 0, + ObjectNameInformation = 1, + ObjectTypeInformation = 2, + } + } + + namespace System.Threading + { + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + internal unsafe delegate void PS_POST_PROCESS_INIT_ROUTINE(); + + // Function Pointer workaround. C# 9's function pointers are only allowed in local scope. + internal struct PPS_POST_PROCESS_INIT_ROUTINE : IEquatable + { + public IntPtr Value; + + public static explicit operator PPS_POST_PROCESS_INIT_ROUTINE(IntPtr v) + { + try + { + _ = Marshal.GetDelegateForFunctionPointer(v); + return new() { Value = v }; + } + catch (Exception) + { + // not a delegate or open generic type + // or ptr is null + return new() { Value = IntPtr.Zero }; + } + } + + public bool Equals(PPS_POST_PROCESS_INIT_ROUTINE other) => Value == other.Value; + + public override bool Equals(object obj) + => obj is PPS_POST_PROCESS_INIT_ROUTINE pPS_POST_PROCESS_INIT_ROUTINE && Equals(pPS_POST_PROCESS_INIT_ROUTINE); + } + + [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Windows.CsWin32", "0.2.46-beta+0e9cbfc7b9")] + internal struct PROCESS_BASIC_INFORMATION + { + internal unsafe void* Reserved1; + internal unsafe PEB* PebBaseAddress; + internal __IntPtr_2 Reserved2; + internal nuint UniqueProcessId; + internal unsafe void* Reserved3; + + internal struct __IntPtr_2 + { + internal IntPtr _0, _1; + + /// Always 2. + internal readonly int Length => 2; + + /// + /// Gets a ref to an individual element of the inline array. + /// ⚠ Important ⚠: When this struct is on the stack, do not let the returned reference outlive the stack frame that defines it. + /// + internal ref IntPtr this[int index] => ref AsSpan()[index]; + + /// + /// Gets this inline array as a span. + /// + /// + /// ⚠ Important ⚠: When this struct is on the stack, do not let the returned span outlive the stack frame that defines it. + /// + internal Span AsSpan() => MemoryMarshal.CreateSpan(ref _0, 2); + } + } + } +} From 403ea84f185a4e24e9a4fb16188a4b7d37b7bb19 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:27:51 -0700 Subject: [PATCH 007/306] refactor: adjust usings, align namespace --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 24 ++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 1096d10..25d37ac 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -1,8 +1,26 @@ -using System.Diagnostics; +using System.ComponentModel; +using System.Diagnostics; using System.Runtime.InteropServices; using deadlock_dotnet_sdk.Exceptions; - -namespace deadlock_dotnet_sdk.Domain +using Microsoft.Win32.SafeHandles; +using Windows.Win32.Foundation; +using Windows.Win32.Security; +using Windows.Win32.Storage.FileSystem; +using Windows.Win32.System.RestartManager; +using Windows.Win32.System.Threading; +using Windows.Win32.System.WindowsProgramming; +using static Windows.Win32.PInvoke; + +// Re: StructLayout +// "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." +// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-6.0#remarks + +// new Win32Exception() is defined as +// public Win32Exception() : this(Marshal.GetLastPInvokeError()) +// { +// } + +namespace deadlock_dotnet_sdk.Domain; { /// /// Collection of native methods From f952d308f88e6079ce6f64d6e4b7d8194202c777 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:34:13 -0700 Subject: [PATCH 008/306] style: convert to file-scoped namespace --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 217 ++++++++++---------- 1 file changed, 108 insertions(+), 109 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 25d37ac..df11f5e 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -21,155 +21,154 @@ // } namespace deadlock_dotnet_sdk.Domain; + +/// +/// Collection of native methods +/// +internal static class NativeMethods { - /// - /// Collection of native methods - /// - internal static class NativeMethods - { - #region Variables + #region Variables - private const int RmRebootReasonNone = 0; - private const int CchRmMaxAppName = 255; - private const int CchRmMaxSvcName = 63; + private const int RmRebootReasonNone = 0; + private const int CchRmMaxAppName = 255; + private const int CchRmMaxSvcName = 63; - #endregion + #endregion - #region Enum_Struct + #region Enum_Struct - [StructLayout(LayoutKind.Sequential)] - private struct RmUniqueProcess - { - internal readonly int dwProcessId; - private readonly System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; - } + [StructLayout(LayoutKind.Sequential)] + private struct RmUniqueProcess + { + internal readonly int dwProcessId; + private readonly System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; + } - private enum RmAppType - { - // ReSharper disable once UnusedMember.Local - RmUnknownApp = 0, + private enum RmAppType + { + // ReSharper disable once UnusedMember.Local + RmUnknownApp = 0, - // ReSharper disable once UnusedMember.Local - RmMainWindow = 1, + // ReSharper disable once UnusedMember.Local + RmMainWindow = 1, - // ReSharper disable once UnusedMember.Local - RmOtherWindow = 2, + // ReSharper disable once UnusedMember.Local + RmOtherWindow = 2, - // ReSharper disable once UnusedMember.Local - RmService = 3, + // ReSharper disable once UnusedMember.Local + RmService = 3, - // ReSharper disable once UnusedMember.Local - RmExplorer = 4, + // ReSharper disable once UnusedMember.Local + RmExplorer = 4, - // ReSharper disable once UnusedMember.Local - RmConsole = 5, + // ReSharper disable once UnusedMember.Local + RmConsole = 5, - // ReSharper disable once UnusedMember.Local - RmCritical = 1000 - } + // ReSharper disable once UnusedMember.Local + RmCritical = 1000 + } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - private struct RmProcessInfo - { - internal RmUniqueProcess Process; + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct RmProcessInfo + { + internal RmUniqueProcess Process; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchRmMaxAppName + 1)] - private readonly string strAppName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchRmMaxAppName + 1)] + private readonly string strAppName; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchRmMaxSvcName + 1)] - private readonly string strServiceShortName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchRmMaxSvcName + 1)] + private readonly string strServiceShortName; - private readonly RmAppType ApplicationType; - private readonly uint AppStatus; - private readonly uint TSSessionId; - [MarshalAs(UnmanagedType.Bool)] private readonly bool bRestartable; - } + private readonly RmAppType ApplicationType; + private readonly uint AppStatus; + private readonly uint TSSessionId; + [MarshalAs(UnmanagedType.Bool)] private readonly bool bRestartable; + } + + #endregion - #endregion + #region DllImport - #region DllImport + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] + private static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames, + uint nApplications, [In] RmUniqueProcess[] rgApplications, uint nServices, string[] rgsServiceNames); - [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] - private static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames, - uint nApplications, [In] RmUniqueProcess[] rgApplications, uint nServices, string[] rgsServiceNames); + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] + private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); - [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] - private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); + [DllImport("rstrtmgr.dll")] + private static extern int RmEndSession(uint pSessionHandle); - [DllImport("rstrtmgr.dll")] - private static extern int RmEndSession(uint pSessionHandle); + [DllImport("rstrtmgr.dll")] + private static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, + [In, Out] RmProcessInfo[] rgAffectedApps, ref uint lpdwRebootReasons); - [DllImport("rstrtmgr.dll")] - private static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, - [In, Out] RmProcessInfo[] rgAffectedApps, ref uint lpdwRebootReasons); + #endregion - #endregion + /// + /// Find the processes that are locking a file + /// + /// Path to the file + /// True if inner exceptions should be rethrown, otherwise false + /// A collection of processes that are locking a file + internal static IEnumerable FindLockingProcesses(string path, bool rethrowExceptions) + { + string key = Guid.NewGuid().ToString(); + List processes = new(); - /// - /// Find the processes that are locking a file - /// - /// Path to the file - /// True if inner exceptions should be rethrown, otherwise false - /// A collection of processes that are locking a file - internal static IEnumerable FindLockingProcesses(string path, bool rethrowExceptions) + int res = RmStartSession(out var handle, 0, key); + if (res != 0) { - string key = Guid.NewGuid().ToString(); - List processes = new(); + throw new StartSessionException(); + } + + try + { + const int errorMoreData = 234; + uint pnProcInfo = 0; + uint lpdwRebootReasons = RmRebootReasonNone; + + string[] resources = { path }; + res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); - int res = RmStartSession(out var handle, 0, key); if (res != 0) { - throw new StartSessionException(); + throw new RegisterResourceException(); } - try - { - const int errorMoreData = 234; - uint pnProcInfo = 0; - uint lpdwRebootReasons = RmRebootReasonNone; - - string[] resources = {path}; - res = RmRegisterResources(handle, (uint) resources.Length, resources, 0, null, 0, null); + res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); - if (res != 0) - { - throw new RegisterResourceException(); - } - - res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); + if (res == errorMoreData) + { + RmProcessInfo[] processInfo = new RmProcessInfo[pnProcInfoNeeded]; + pnProcInfo = pnProcInfoNeeded; - if (res == errorMoreData) + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); + if (res == 0) { - RmProcessInfo[] processInfo = new RmProcessInfo[pnProcInfoNeeded]; - pnProcInfo = pnProcInfoNeeded; + processes = new List((int)pnProcInfo); - res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); - if (res == 0) + for (int i = 0; i < pnProcInfo; i++) { - processes = new List((int) pnProcInfo); - - for (int i = 0; i < pnProcInfo; i++) + try + { + processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); + } + catch (ArgumentException) { - try - { - processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); - } - catch (ArgumentException) - { - if (rethrowExceptions) throw; - } + if (rethrowExceptions) throw; } } - else throw new RmListException(); } - else if (res != 0) throw new UnauthorizedAccessException(); - } - finally - { - _ = RmEndSession(handle); + else throw new RmListException(); } - - return processes; + else if (res != 0) throw new UnauthorizedAccessException(); + } + finally + { + _ = RmEndSession(handle); } + + return processes; } } From f23ecb5ed3363b6f96ca9d508ebb1922823fdd26 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:37:12 -0700 Subject: [PATCH 009/306] style: add braces to if-else, use exception filter --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index df11f5e..05439c9 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -154,15 +154,20 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth { processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); } - catch (ArgumentException) + catch (ArgumentException) when (!rethrowExceptions) { - if (rethrowExceptions) throw; } } } - else throw new RmListException(); + else + { + throw new RmListException(); + } + } + else if (res != 0) + { + throw new UnauthorizedAccessException(); } - else if (res != 0) throw new UnauthorizedAccessException(); } finally { From 3673c74fcf59d0f78f8bd052581dfa34caf5f1a1 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:40:10 -0700 Subject: [PATCH 010/306] refactor: use CsWin32 methods --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 05439c9..b43ea49 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -87,24 +87,6 @@ private struct RmProcessInfo #endregion - #region DllImport - - [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] - private static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames, - uint nApplications, [In] RmUniqueProcess[] rgApplications, uint nServices, string[] rgsServiceNames); - - [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] - private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); - - [DllImport("rstrtmgr.dll")] - private static extern int RmEndSession(uint pSessionHandle); - - [DllImport("rstrtmgr.dll")] - private static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, - [In, Out] RmProcessInfo[] rgAffectedApps, ref uint lpdwRebootReasons); - - #endregion - /// /// Find the processes that are locking a file /// From 58679f0312cd6db7925c4f72b5fbe444c5d4347d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:42:58 -0700 Subject: [PATCH 011/306] refactor: use CsWin32 structs, enums --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 56 +-------------------- 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index b43ea49..f45a1bd 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -30,60 +30,6 @@ internal static class NativeMethods #region Variables private const int RmRebootReasonNone = 0; - private const int CchRmMaxAppName = 255; - private const int CchRmMaxSvcName = 63; - - #endregion - - #region Enum_Struct - - [StructLayout(LayoutKind.Sequential)] - private struct RmUniqueProcess - { - internal readonly int dwProcessId; - private readonly System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; - } - - private enum RmAppType - { - // ReSharper disable once UnusedMember.Local - RmUnknownApp = 0, - - // ReSharper disable once UnusedMember.Local - RmMainWindow = 1, - - // ReSharper disable once UnusedMember.Local - RmOtherWindow = 2, - - // ReSharper disable once UnusedMember.Local - RmService = 3, - - // ReSharper disable once UnusedMember.Local - RmExplorer = 4, - - // ReSharper disable once UnusedMember.Local - RmConsole = 5, - - // ReSharper disable once UnusedMember.Local - RmCritical = 1000 - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - private struct RmProcessInfo - { - internal RmUniqueProcess Process; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchRmMaxAppName + 1)] - private readonly string strAppName; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchRmMaxSvcName + 1)] - private readonly string strServiceShortName; - - private readonly RmAppType ApplicationType; - private readonly uint AppStatus; - private readonly uint TSSessionId; - [MarshalAs(UnmanagedType.Bool)] private readonly bool bRestartable; - } #endregion @@ -122,7 +68,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth if (res == errorMoreData) { - RmProcessInfo[] processInfo = new RmProcessInfo[pnProcInfoNeeded]; + RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; pnProcInfo = pnProcInfoNeeded; res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); From a08b98aeca273c0b7c12ebf6a548f344c8ab62ed Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:48:21 -0700 Subject: [PATCH 012/306] refactor: change Guid key from string to disposable PWSTR --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 93 +++++++++++---------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index f45a1bd..de1442f 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -41,67 +41,72 @@ internal static class NativeMethods /// A collection of processes that are locking a file internal static IEnumerable FindLockingProcesses(string path, bool rethrowExceptions) { - string key = Guid.NewGuid().ToString(); - List processes = new(); - - int res = RmStartSession(out var handle, 0, key); - if (res != 0) - { - throw new StartSessionException(); - } - - try + unsafe { - const int errorMoreData = 234; - uint pnProcInfo = 0; - uint lpdwRebootReasons = RmRebootReasonNone; + using (PWSTR key = new((char*)Marshal.StringToHGlobalUni(Guid.NewGuid().ToString()))) + { + List processes = new(); - string[] resources = { path }; - res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); + int res = RmStartSession(out var handle, 0, key); + if (res != 0) + { + throw new StartSessionException(); + } - if (res != 0) - { - throw new RegisterResourceException(); - } + try + { + const int errorMoreData = 234; + uint pnProcInfo = 0; + uint lpdwRebootReasons = RmRebootReasonNone; - res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); + string[] resources = { path }; + res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); - if (res == errorMoreData) - { - RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; - pnProcInfo = pnProcInfoNeeded; + if (res != 0) + { + throw new RegisterResourceException(); + } - res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); - if (res == 0) - { - processes = new List((int)pnProcInfo); + res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); - for (int i = 0; i < pnProcInfo; i++) + if (res == errorMoreData) { - try + RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; + pnProcInfo = pnProcInfoNeeded; + + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); + if (res == 0) { - processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); + processes = new List((int)pnProcInfo); + + for (int i = 0; i < pnProcInfo; i++) + { + try + { + processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); + } + catch (ArgumentException) when (!rethrowExceptions) + { + } + } } - catch (ArgumentException) when (!rethrowExceptions) + else { + throw new RmListException(); } } + else if (res != 0) + { + throw new UnauthorizedAccessException(); + } } - else + finally { - throw new RmListException(); + _ = RmEndSession(handle); } - } - else if (res != 0) - { - throw new UnauthorizedAccessException(); + + return processes; } } - finally - { - _ = RmEndSession(handle); - } - - return processes; } } From 86910ad32c914b83bed21e285b9a151ac3143731 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:52:42 -0700 Subject: [PATCH 013/306] refactor: add additional regions this is going to be messy... The regions will *hopefully* help Git understand changes better --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index de1442f..0846080 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -31,7 +31,9 @@ internal static class NativeMethods private const int RmRebootReasonNone = 0; - #endregion + #endregion Variables + + #region Methods /// /// Find the processes that are locking a file @@ -109,4 +111,10 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth } } } + #endregion Methods + + #region Structs + #endregion Structs + #region Classes + #endregion Classes } From df479883095d6180b4af844c9bb1532e5a55bb0b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:54:22 -0700 Subject: [PATCH 014/306] fix: change res from int to uint --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 0846080..98ec276 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -49,7 +49,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth { List processes = new(); - int res = RmStartSession(out var handle, 0, key); + uint res = RmStartSession(out var handle, 0, key); if (res != 0) { throw new StartSessionException(); From 1c4dc851628fb11b1df68d14889f2a73fd61443b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 03:57:29 -0700 Subject: [PATCH 015/306] fix: call RmRegisterResources with Spans, PWSTRs --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 53 +++++++++++---------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 98ec276..e14dd19 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -62,45 +62,48 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth uint lpdwRebootReasons = RmRebootReasonNone; string[] resources = { path }; - res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); - - if (res != 0) + using (PWSTR pResources = (char*)Marshal.StringToHGlobalUni(path)) { - throw new RegisterResourceException(); - } + res = RmRegisterResources(handle, new Span(new PWSTR[] { pResources }), rgApplications: new(), new()); - res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); + if (res != 0) + { + throw new RegisterResourceException(); + } - if (res == errorMoreData) - { - RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; - pnProcInfo = pnProcInfoNeeded; + res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); - res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); - if (res == 0) + if (res == errorMoreData) { - processes = new List((int)pnProcInfo); + RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; + pnProcInfo = pnProcInfoNeeded; - for (int i = 0; i < pnProcInfo; i++) + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); + if (res == 0) { - try - { - processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); - } - catch (ArgumentException) when (!rethrowExceptions) + processes = new List((int)pnProcInfo); + + for (int i = 0; i < pnProcInfo; i++) { + try + { + processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); + } + catch (ArgumentException) when (!rethrowExceptions) + { + } } } + else + { + throw new RmListException(); + } } - else + else if (res != 0) { - throw new RmListException(); + throw new UnauthorizedAccessException(); } } - else if (res != 0) - { - throw new UnauthorizedAccessException(); - } } finally { From b3e1667a7e97b3818517b09063c39de72569ea2d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 04:15:43 -0700 Subject: [PATCH 016/306] fix: correct the rest of the function calls; Call Dispose --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index e14dd19..d142d26 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -68,17 +68,21 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth if (res != 0) { + pResources.Dispose(); throw new RegisterResourceException(); } - res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); + res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, out lpdwRebootReasons); if (res == errorMoreData) { - RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; + ReadOnlySpan processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; pnProcInfo = pnProcInfoNeeded; - res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); + fixed (RM_PROCESS_INFO* pProcessInfo = processInfo) + { + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, pProcessInfo, out lpdwRebootReasons); + } if (res == 0) { processes = new List((int)pnProcInfo); @@ -87,20 +91,24 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth { try { - processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); + processes.Add(Process.GetProcessById((int)processInfo[i].Process.dwProcessId)); } - catch (ArgumentException) when (!rethrowExceptions) + catch (ArgumentException) { + pResources.Dispose(); + if (rethrowExceptions) throw; } } } else { + pResources.Dispose(); throw new RmListException(); } } else if (res != 0) { + pResources.Dispose(); throw new UnauthorizedAccessException(); } } @@ -108,6 +116,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth finally { _ = RmEndSession(handle); + key.Dispose(); } return processes; From 4fbd8d3925c8ddd3b9f0778e8ea7ec9e7c03cb44 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 04:57:21 -0700 Subject: [PATCH 017/306] feat: add method GetFullProcessImageName This is a wrapper for QueryFullProcessImageName --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index d142d26..d19d995 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -123,6 +123,63 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth } } } + + /// + /// A wrapper for QueryFullProcessImageName + /// + /// + /// The identifier of the local process to be opened. + /// If the specified process is the System Idle Process(0x00000000), + /// the function fails and the last error code is ERROR_INVALID_PARAMETER. + /// If the specified process is the System process or one of the Client Server Run-Time Subsystem(CSRSS) processes, + /// this function fails and the last error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from opening them. + /// + /// A SafeProcessHandle opened with + /// The path to the executable image. + /// Call to or failed. + private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) + { + if (hProcess.IsInvalid) + { + throw new ArgumentException("The process handle is invalid", nameof(hProcess)); + } + + uint size = 260 + 1; + uint bufferLength = size; + IntPtr ptr = Marshal.AllocHGlobal((int)bufferLength); + PWSTR buffer = new PWSTR((char*)ptr); + + if (!QueryFullProcessImageName( + hProcess: hProcess, + dwFlags: PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + lpExeName: buffer, + lpdwSize: ref size)) + { + if (bufferLength < size) + { + ptr = Marshal.ReAllocHGlobal(ptr, (IntPtr)size); + buffer = new((char*)ptr); + _ = QueryFullProcessImageName( + hProcess, + PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + buffer, + ref size); + } + else + { + var err = Marshal.GetLastPInvokeError(); + hProcess.Close(); + throw new Win32Exception(err); + } + } + + // this is horribly inefficient. How many times are we creating new references and/or buffers? + hProcess.Close(); + string retVal = buffer.ToString(); + Marshal.FreeHGlobal((IntPtr)buffer.Value); + return retVal; + } + #endregion Methods #region Structs From 74cdf1b1190e5ad0e14830a71af93b33256ee3b0 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 04:59:36 -0700 Subject: [PATCH 018/306] feat: add SYSTEM_HANDLE_INFORMATION_EX, SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 111 ++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index d19d995..6305264 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -183,6 +183,117 @@ private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) #endregion Methods #region Structs + + /// + /// The SYSTEM_HANDLE_INFORMATION_EX + /// struct is 0x24 or 0x38 bytes in 32-bit and 64-bit Windows, respectively. However, Handles is a variable-length array. + /// + public unsafe struct SYSTEM_HANDLE_INFORMATION_EX + { + /// + /// As documented unofficially, NumberOfHandles is a 4-byte or 8-byte ULONG_PTR in 32-bit and 64-bit Windows, respectively.
+ /// This is not to be confused with uint* or ulong*. + ///
+ public UIntPtr NumberOfHandles; + public UIntPtr Reserved; + public SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* Handles; + + public Span AsSpan() => new(Handles, (int)NumberOfHandles); + public static implicit operator Span(SYSTEM_HANDLE_INFORMATION_EX value) => value.AsSpan(); + } + + /// + /// The + /// SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX + /// structure is a recurring element in the + /// SYSTEM_HANDLE_INFORMATION_EX + /// struct that a successful call to + /// ZwQuerySystemInformation + /// or + /// NtQuerySystemInformation + /// produces in its output buffer when given the information class + /// SystemHandleInformation (0x10). + /// This inline doc was supplemented by ProcessHacker's usage of this struct. + /// + public struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX + { + public unsafe void* Object; + /// + /// ULONG_PTR, cast to HANDLE, int, or uint + /// + public HANDLE UniqueProcessId; + /// + /// ULONG_PTR, cast to HANDLE + /// + public HANDLE HandleValue; + /// + /// This is a bitwise "Flags" data type. + /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. + /// + public ACCESS_MASK GrantedAccess; // ULONG + public ushort CreatorBackTraceIndex; // USHORT + /// ProcessHacker defines a little over a dozen handle-able object types. + public ushort ObjectTypeIndex; // USHORT + /// + public uint HandleAttributes; // ULONG + public uint Reserved; + + /// + /// Get the Type of the object as a string + /// + /// P/Invoke function NtQueryObject failed. See Exception data. + /// The Type of the object as a string. + public unsafe string GetObjectType() + { + /* Query the object type */ + string typeName; + PUBLIC_OBJECT_TYPE_INFORMATION* objectTypeInfo = (PUBLIC_OBJECT_TYPE_INFORMATION*)Marshal.AllocHGlobal(sizeof(PUBLIC_OBJECT_TYPE_INFORMATION)); + uint* returnLength = (uint*)Marshal.AllocHGlobal(sizeof(uint)); + NTSTATUS status; + + if ((status = NtQueryObject(HandleValue, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, objectTypeInfo, (uint)sizeof(PUBLIC_OBJECT_TYPE_INFORMATION), returnLength)).SeverityCode == NTSTATUS.Severity.Success) + { + typeName = objectTypeInfo->TypeName.ToStringLength(); + Marshal.FreeHGlobal((IntPtr)objectTypeInfo); + } + else + { + Marshal.FreeHGlobal((IntPtr)objectTypeInfo); + throw new Win32Exception(); + } + return typeName; + } + + /// Invokes and checks if the result is "File". + /// True if the handle is for a file or directory. + /// Based on source of C/C++ projects Hijack File Handle and Handle Monitor + /// Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details. + public bool IsFileHandle() + { + try + { + string type = GetObjectType(); + return !string.IsNullOrWhiteSpace(type) && string.CompareOrdinal(type, "File") == 0; + } + catch (Exception e) + { + throw new Exception("Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details.", e); + } + } + + /// + /// Try to cast this handle's to a SafeFileHandle; + /// + /// A if this handle's object is a data/directory File. + /// The handle's object is not a File -OR- perhaps NtQueryObject() failed. See for details. + public SafeFileHandle ToSafeFileHandle() + { + return IsFileHandle() + ? (new((nint)HandleValue, (int)UniqueProcessId == Environment.ProcessId)) + : throw new Exception("The handle's object is not a File -OR- NtQueryObject() failed. See InnerException for details."); + } + } + #endregion Structs #region Classes #endregion Classes From 906c86d7cc3d06363645d554e5d227c26be41c29 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 05:01:20 -0700 Subject: [PATCH 019/306] feat: add class SafeHandleEx --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 188 ++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 6305264..0814825 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -295,6 +295,194 @@ public SafeFileHandle ToSafeFileHandle() } #endregion Structs + #region Classes + + /// + /// A SafeHandleZeroOrMinusOneIsInvalid wrapping a SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
+ /// Before querying for system handles, call for easier access to restricted data. + ///
+ public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid + { + protected SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx; + + internal SafeHandleEx(bool ownsHandle = false) : base(ownsHandle) + { } + + public SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX newSysHandleEx, bool ownsHandle = false) : base(ownsHandle) + { + sysHandleEx = newSysHandleEx; + Init(); + } + + internal void Init() + { + try + { + //Process.EnterDebugMode(); Best practice: only call this once. + + /** Open handle for process */ + // PROCESS_QUERY_LIMITED_INFORMATION is necessary for QueryFullProcessImageName + // PROCESS_QUERY_LIMITED_INFORMATION + PROCESS_VM_READ for reading PEB from the process's memory space. + // if we need to duplicate a handle later, we'll use PROCESS_DUP_HANDLE + + HANDLE rawHandle = OpenProcess( + dwDesiredAccess: PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, + bInheritHandle: (BOOL)false, + dwProcessId: (uint)ProcessId + ); + + if (rawHandle.IsNull) + throw new Win32Exception("Failed to open process handle with access rights 'PROCESS_QUERY_LIMITED_INFORMATION' and 'PROCESS_VM_READ'. The following information will be unavailable: main module full name, process name, "); + + SafeProcessHandle hProcess = new(rawHandle, true); + + /** Get main module's full path */ + ProcessMainModulePath = GetFullProcessImageName(hProcess); + + /** Get Process's name */ + if (!string.IsNullOrWhiteSpace(ProcessMainModulePath)) + { + ProcessName = Path.GetFileNameWithoutExtension(ProcessMainModulePath); + } + + /** Get process's possibly-overwritten command line from the PEB struct in its memory space */ + GetProcessCommandLine(hProcess); + } + catch (Exception e) + { + ExceptionLog.Add(e); + } + } + + internal SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX SysHandleEx => sysHandleEx; + + public unsafe void* Object => SysHandleEx.Object; + /// + /// cast to uint + /// + public HANDLE ProcessId => SysHandleEx.UniqueProcessId; + public HANDLE HandleValue => SysHandleEx.HandleValue; + public ushort CreatorBackTraceIndex => SysHandleEx.CreatorBackTraceIndex; + /// + public ACCESS_MASK GrantedAccess => SysHandleEx.GrantedAccess; + public ushort ObjectTypeIndex => SysHandleEx.ObjectTypeIndex; + /// + public uint HandleAttributes => SysHandleEx.HandleAttributes; + + public string? ProcessCommandLine { get; private set; } + public string? ProcessMainModulePath { get; private set; } + public string? ProcessName { get; private set; } + + /// + /// A list of exceptions thrown by constructors and other methods of this class. + /// + /// Use List's methods (e.g. Add) to modify this list. + public static List ExceptionLog { get; } = new(); + + public void CloseDangerously() + { + HANDLE rawHProcess; + SafeProcessHandle? hProcess = null; + try + { + if ((rawHProcess = OpenProcess( + PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, + true, + (uint)ProcessId) + ).IsNull) + { + throw new Win32Exception($"Failed to open process with id {(int)ProcessId} to duplicate and close object handle."); + } + + hProcess = new(rawHProcess, true); + if (DuplicateHandle(hProcess, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_CLOSE_SOURCE)) + { + dupHandle.Close(); + hProcess.Close(); + + // finally, close this SafeHandleEx + Close(); + } + else + { + throw new Win32Exception("Function DuplicateHandle failed to duplicate the handle"); + } + } + catch (Exception e) + { + ExceptionLog.Add(e); + if (hProcess is not null) + hProcess.Close(); + } + } + + public string GetObjectType() => SysHandleEx.GetObjectType(); + + /// + /// Try to get a process's command line from its PEB + /// + /// + /// + /// + private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) + { + /* Get PROCESS_BASIC_INFORMATION */ + uint sysInfoLength = (uint)Marshal.SizeOf(); + PROCESS_BASIC_INFORMATION processBasicInfo; + IntPtr sysInfo = Marshal.AllocHGlobal((int)sysInfoLength); + NTSTATUS status = (NTSTATUS)0; + uint retLength = 0; + + if ((status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + (void*)sysInfo, + sysInfoLength, + ref retLength)) + .IsSuccessful) + { + processBasicInfo = Marshal.PtrToStructure(sysInfo); + + // if our process is WOW64, we need to account for different pointer sizes if + // the target process is 64-bit + IsWow64Process(hProcess, out BOOL wow64Process); + if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess && wow64Process) + { + throw new NotImplementedException("Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented."); + // too much trouble. If someone else wants to do it, be my guest. + // https://stackoverflow.com/a/36798492/14894786 + // Reason: if our process is 32-bit, we'd be stuck with 32-bit pointers. + // If the PEB's address is in 64-bit address space, we can't access it + // because the pointer value we received was truncated from 64 bits to + // 32 bits. + } + + IntPtr buf = Marshal.AllocHGlobal(sizeof(PEB)); + if (ReadProcessMemory(hProcess, processBasicInfo.PebBaseAddress, (void*)buf, (nuint)sizeof(PEB), null)) + { + PEB peb = Marshal.PtrToStructure(buf); + ProcessCommandLine = (*peb.ProcessParameters).CommandLine.ToStringLength(); + } + else + { + // this calls Marshal.GetLastPInvokeError() + // https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/ComponentModel/Win32Exception.cs?L46 + throw new Win32Exception("Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible."); + } + } + else + { + throw new Exception("NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION'"); + } + } + + protected override bool ReleaseHandle() + { + Close(); + return IsClosed; + } + } + #endregion Classes } From a41e5e628b7b404b48ae3b840ade7d45fe46dd27 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 05:03:00 -0700 Subject: [PATCH 020/306] feat: add class SafeFileHandleEx --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 95 +++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 0814825..eed7fc5 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -484,5 +484,100 @@ protected override bool ReleaseHandle() } } + public class SafeFileHandleEx : SafeHandleEx + { + internal SafeFileHandleEx(SafeHandleEx safeHandleEx) + { + sysHandleEx = safeHandleEx.SysHandleEx; + Init(); + InitFile(); + } + + public SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx, bool ownsHandle) : base(newSysHandleEx: sysHandleEx, ownsHandle: ownsHandle) + { + base.sysHandleEx = sysHandleEx; + Init(); + InitFile(); + } + + private void InitFile() + { + bool? isFileHandle; + try + { + isFileHandle = sysHandleEx.IsFileHandle(); + } + catch (Exception) + { + isFileHandle = null; + // IsFileHandle failed + } + if (isFileHandle == true) + { + FullPath = TryGetFinalPath(); + if (FullPath != null) + { + Name = Path.GetFileName(FullPath); + IsDirectory = (File.GetAttributes(FullPath) & FileAttributes.Directory) == FileAttributes.Directory; + } + } + else + { + throw new InvalidCastException("Cannot cast non-file handle to file handle!"); + } + } + + public string? FullPath { get; private set; } + public string? Name { get; private set; } + public bool? IsDirectory { get; private set; } + + /// + /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. + /// + /// If successful, returns a path string formatted as 'X:\dir\file.ext' or 'X:\dir' + /// The path '{fullName}' was not found when querying a file handle. + /// Failed to query path from file handle. Insufficient memory to complete the operation. + /// Failed to query path from file handle. Invalid flags were specified for dwFlags. + private unsafe string TryGetFinalPath() + { + /// Return the normalized drive name. This is the default. + uint bufLength = (uint)short.MaxValue; + var buffer = Marshal.AllocHGlobal((int)bufLength); + PWSTR fullName = new((char*)buffer); + uint length = GetFinalPathNameByHandle(SysHandleEx.ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + + if (length != 0) + { + while (length > bufLength) + { + // buffer was too small. Reallocate buffer with size matched 'length' and try again + buffer = Marshal.ReAllocHGlobal(buffer, (IntPtr)length); + fullName = new((char*)buffer); + + bufLength = GetFinalPathNameByHandle(SysHandleEx.ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + } + return fullName.ToString(); + } + else + { + int error = Marshal.GetLastWin32Error(); + const int ERROR_PATH_NOT_FOUND = 3; + const int ERROR_NOT_ENOUGH_MEMORY = 8; + const int ERROR_INVALID_PARAMETER = 87; // 0x57 + + /* Hold up. Let's free our memory before throwing exceptions. */ + Marshal.FreeHGlobal(buffer); + + throw error switch + { + ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{fullName}' was not found when querying a file handle.", fileName: fullName.ToString()), // Removable storage, deleted item, network shares, et cetera + ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation."), // unlikely, but possible if system has little free memory + ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags."), // possible only if FILE_NAME_NORMALIZED (0) is invalid + _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path."), + }; + } + } + } + #endregion Classes } From 5e4d8283bec37989fb46a499c7363e9c91b810c9 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 05:10:10 -0700 Subject: [PATCH 021/306] feat: add method GetSystemHandleInfoEx --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 75 +++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index eed7fc5..65770c8 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -124,6 +124,81 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth } } + /// + /// Get a Span of via + /// + /// Heavily influenced by ProcessHacker/SystemInformer + private unsafe static Span GetSystemHandleInfoEx() + { + const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; + const uint PH_LARGE_BUFFER_SIZE = 256 * 1024 * 1024; // 256 Mebibytes + const uint STATUS_INSUFFICIENT_RESOURCES = 0xC000009A; + uint systemInformationLength = (uint)sizeof(SYSTEM_HANDLE_INFORMATION_EX); + SYSTEM_HANDLE_INFORMATION_EX* pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.AllocHGlobal(sizeof(SYSTEM_HANDLE_INFORMATION_EX)); + uint returnLength = 0; + + NTSTATUS status = NtQuerySystemInformation( + SystemInformationClass: SYSTEM_INFORMATION_CLASS.SystemExtendedHandleInformation, + SystemInformation: pSysInfoBuffer, + SystemInformationLength: systemInformationLength, + ReturnLength: ref returnLength + ); + + for (uint attempts = 0; status == STATUS_INFO_LENGTH_MISMATCH && attempts < 10; attempts++) + { + systemInformationLength = returnLength; + pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.ReAllocHGlobal((IntPtr)pSysInfoBuffer, (IntPtr)systemInformationLength); + + status = NtQuerySystemInformation( + SYSTEM_INFORMATION_CLASS.SystemExtendedHandleInformation, + pSysInfoBuffer, + systemInformationLength, + ref returnLength + ); + } + + if (!status.IsSuccessful) + { + // Fall back to using the previous code that we've used since Windows XP (dmex) + systemInformationLength = 0x10000; + Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); + pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.ReAllocHGlobal((IntPtr)pSysInfoBuffer, (IntPtr)systemInformationLength); + + while ((status = NtQuerySystemInformation( + SystemInformationClass: SYSTEM_INFORMATION_CLASS.SystemExtendedHandleInformation, + SystemInformation: pSysInfoBuffer, + SystemInformationLength: systemInformationLength, + ReturnLength: ref returnLength + )) == STATUS_INFO_LENGTH_MISMATCH) + { + Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); + systemInformationLength *= 2; + + // Fail if we're resizing the buffer to something very large. + if (systemInformationLength > PH_LARGE_BUFFER_SIZE) + { + throw new Win32Exception(unchecked((int)STATUS_INSUFFICIENT_RESOURCES)); + } + + pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.AllocHGlobal((int)systemInformationLength); + } + } + + if (!status.IsSuccessful) + { + Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); + Marshal.FreeHGlobal((IntPtr)returnLength); + throw new Win32Exception((int)status); + } + + SYSTEM_HANDLE_INFORMATION_EX retVal = *pSysInfoBuffer; + + Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); + Marshal.FreeHGlobal((IntPtr)returnLength); + + return retVal.AsSpan(); + } + /// /// A wrapper for QueryFullProcessImageName /// From fda0475a45991994bbbe541151f25e3622e9f9fe Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 05:10:54 -0700 Subject: [PATCH 022/306] refactor: rename method CloseDangerously to UnlockSystemHandle --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 65770c8..7e9d7c7 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -450,12 +450,13 @@ internal void Init() public string? ProcessName { get; private set; } /// - /// A list of exceptions thrown by constructors and other methods of this class. + /// A list of exceptions thrown by constructors and other methods of this class.
+ /// Intended to explain why the process command line, main module path, and name are unavailable. ///
/// Use List's methods (e.g. Add) to modify this list. public static List ExceptionLog { get; } = new(); - public void CloseDangerously() + public void UnlockSystemHandle() { HANDLE rawHProcess; SafeProcessHandle? hProcess = null; From ee150a72d2e7e2a331a0c821601edc70519049c5 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 05:19:54 -0700 Subject: [PATCH 023/306] feat: add method FindLockingHandles --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 7e9d7c7..46f658b 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -124,6 +124,35 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth } } + /// + /// Query the systems open handles, try to filter them to just files, and try to filter those files to just ones that contain the path query. + /// + /// + /// + /// A List of SafeFileHandle objects. + /// This might be arduously slow... + public static List FindLockingHandles(string? path = null) + { + Process.EnterDebugMode(); // just in case + + List handles = GetSystemHandleInfoEx().ToArray().Cast().ToList(); + List fileHandles = new(); + foreach (var handle in handles) + { + var fileHandle = new SafeFileHandleEx(handle); + // Do we need more path sanitation checks? + if (!string.IsNullOrWhiteSpace(path) && (string.IsNullOrEmpty(fileHandle.FullPath) || fileHandle.FullPath.Contains(path))) + { + // we also add null-path handles bc we can assume we failed to query their paths. If someone wants to filter them out, they can. + fileHandles.Add(fileHandle); + } + // else, the file handle's path is fulfilled, but does not match our query + } + + fileHandles.Sort((a, b) => a.ProcessId.CompareTo(b.ProcessId)); + return fileHandles; + } + /// /// Get a Span of via /// From dd7ccc9aa7417e0ffc179dfde1e7f5f751821e42 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 10 Sep 2022 05:20:51 -0700 Subject: [PATCH 024/306] refactor: set public members to internal ...and comment out redundant calls and assignments --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 46f658b..f0915fb 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -131,7 +131,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth /// /// A List of SafeFileHandle objects. /// This might be arduously slow... - public static List FindLockingHandles(string? path = null) + internal static List FindLockingHandles(string? path = null) { Process.EnterDebugMode(); // just in case @@ -406,9 +406,9 @@ public SafeFileHandle ToSafeFileHandle() /// A SafeHandleZeroOrMinusOneIsInvalid wrapping a SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
/// Before querying for system handles, call for easier access to restricted data. ///
- public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid + internal class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { - protected SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx; + private protected SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx; internal SafeHandleEx(bool ownsHandle = false) : base(ownsHandle) { } @@ -589,8 +589,9 @@ protected override bool ReleaseHandle() } } - public class SafeFileHandleEx : SafeHandleEx + internal class SafeFileHandleEx : SafeHandleEx { + // TODO: there's gotta be a better way to cast a base class to an implementing class internal SafeFileHandleEx(SafeHandleEx safeHandleEx) { sysHandleEx = safeHandleEx.SysHandleEx; @@ -600,8 +601,8 @@ internal SafeFileHandleEx(SafeHandleEx safeHandleEx) public SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx, bool ownsHandle) : base(newSysHandleEx: sysHandleEx, ownsHandle: ownsHandle) { - base.sysHandleEx = sysHandleEx; - Init(); + //base.sysHandleEx = sysHandleEx; + //Init(); InitFile(); } From cde804ec7c36605cc676f6df56a9e0778f2adf34 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:19:52 -0700 Subject: [PATCH 025/306] refactor: remove type cast; tweak handle filter Instead of copying criteria-matching SafeHandleEx instances to a list of SafeFileHandleEx, we cast handles straight to SafeFileHandleEx and remove those which are do not match the criteria. The criteria are unchanged. 1. A handle's object 's Type must be either unidentified or "File" (file/directory). 2. If the object type is a File and we succeed in querying the file path , the path must contain the query string. 3. If we fail to query the Type or the file path, we do not remove it from the results. --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 63 +++++++++++++++------ 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index f0915fb..0f009da 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -125,32 +125,63 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth } /// - /// Query the systems open handles, try to filter them to just files, and try to filter those files to just ones that contain the path query. + /// Query the system's open handles; + /// Try to filter them to just files, optionally including handles for non-File and unidentified object types; + /// Filter "File" handles to only those whose full paths contain the query string. /// - /// - /// - /// A List of SafeFileHandle objects. + /// + /// When a query string is passed to this method, all "File" + /// object handles will be filtered for only those whose full + /// paths contain this query string. + /// + /// + /// By default, this method only returns handles for objects + /// successfully identified as a file/directory ("File"). + /// and + /// + /// + /// A list of SafeFileHandleEx objects. + /// When requested, handles for non-file or unidentified objects will be included with file-specific properties nulled. + /// /// This might be arduously slow... - internal static List FindLockingHandles(string? path = null) + // TODO: Perhaps we should allow a new query without re-calling GetSystemHandleInfoEx(). + internal static List FindLockingHandles(string? query = null, Filter filter = Filter.FilesOnly) { Process.EnterDebugMode(); // just in case - List handles = GetSystemHandleInfoEx().ToArray().Cast().ToList(); - List fileHandles = new(); - foreach (var handle in handles) + List? handles = GetSystemHandleInfoEx().ToArray().Cast().ToList(); + handles.RemoveAll(item => Discard(h: item)); + handles.Sort((a, b) => a.ProcessId.CompareTo(b.ProcessId)); + return handles; + + bool Discard(SafeFileHandleEx h) { - var fileHandle = new SafeFileHandleEx(handle); - // Do we need more path sanitation checks? - if (!string.IsNullOrWhiteSpace(path) && (string.IsNullOrEmpty(fileHandle.FullPath) || fileHandle.FullPath.Contains(path))) + if (h.HandleObjectType is not null) + { + /* Query for object type succeeded and the type is NOT File */ + if (h.HandleObjectType != "File") + { + return !filter.HasFlag(Filter.NonFiles); // When requested, keep non-File object handle. Else, discard. + } + // Discard handle if Query and file's path are not null and file's path does not contain query */ + return (query is not null) && (h.FileFullPath is not null) && (!h.FileFullPath.Contains(query.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))); + } + else { - // we also add null-path handles bc we can assume we failed to query their paths. If someone wants to filter them out, they can. - fileHandles.Add(fileHandle); + return !filter.HasFlag(Filter.TypeQueryFailed); // When requested, keep handle if the object type query failed. Else, discard. } - // else, the file handle's path is fulfilled, but does not match our query } + } - fileHandles.Sort((a, b) => a.ProcessId.CompareTo(b.ProcessId)); - return fileHandles; + /// + /// Filters for + /// + [Flags] + internal enum Filter + { + FilesOnly = 0, + NonFiles = 1, + TypeQueryFailed = 2 } /// From a0ad2d955ff1eb9214452b7e3195eac37ff40076 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:26:15 -0700 Subject: [PATCH 026/306] build: add PackageReference dotnet/pinvoke This is the written-by-hand predecessor of CsWin32's libraries. Its implementation of certain classes, enums, and constants is preferable over those generated by CsWin32. However, we'll continue to use our homebrew implementations (e.g. ACCESS_MASK) where compatibility between CsWin32 and PInvoke is poor. --- deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj index 19daf35..b2199d2 100644 --- a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj +++ b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj @@ -38,5 +38,6 @@ + From cfb7c497ff3d6811dcb0fb74a03c73c7191850f4 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:28:31 -0700 Subject: [PATCH 027/306] refactor: refine NTSTATUS implementation compatibility --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs index 1af20d8..38aeaa3 100644 --- a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs +++ b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs @@ -38,6 +38,11 @@ public void Close() internal readonly partial struct NTSTATUS { public bool IsSuccessful => SeverityCode == Severity.Success; + + public NTStatusException GetNTStatusException() => new(this); + + public static implicit operator NTSTATUS_plus(NTSTATUS v) => new(v.Value); + public static implicit operator NTSTATUS(NTSTATUS_plus v) => new(v.AsInt32); } internal unsafe readonly partial struct PWSTR : IDisposable From 93bd443770334857cde8e0b5592a31d8f9fa7303 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:29:41 -0700 Subject: [PATCH 028/306] refactor: allow implicit conversion of IntPtr to PWSTR --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs index 38aeaa3..5982f16 100644 --- a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs +++ b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs @@ -47,10 +47,12 @@ internal readonly partial struct NTSTATUS internal unsafe readonly partial struct PWSTR : IDisposable { - public void Dispose() - { - Marshal.FreeHGlobal((IntPtr)Value); - } + /// + /// Free the PWSTR's memory with Marshal.FreeHGlobal(IntPtr) + /// + public void Dispose() => Marshal.FreeHGlobal((IntPtr)Value); + + public static implicit operator PWSTR(IntPtr v) => new((char*)v); } internal partial struct UNICODE_STRING From 725c0f2c3b5373736bb08176aba331536ab69d8c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:31:47 -0700 Subject: [PATCH 029/306] refactor: add PInvoke to CsWin32 supplements --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs index 5982f16..a3981ce 100644 --- a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs +++ b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs @@ -1,8 +1,9 @@ -// This file is used to extend the partial definitions generated by CsWin32 - +/// This file supplements code generated by CsWin32 using System.ComponentModel; using System.Runtime.InteropServices; using Windows.Win32.System.Threading; +using NTSTATUS_plus = PInvoke.NTSTATUS; +using NTStatusException = PInvoke.NTStatusException; namespace Windows.Win32 { From 464e8693e7e21610f9bac4f968c670be2d517e5f Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:33:57 -0700 Subject: [PATCH 030/306] fix: account for object nullability in Equals override --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs index a3981ce..840a975 100644 --- a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs +++ b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs @@ -180,7 +180,7 @@ public static explicit operator PPS_POST_PROCESS_INIT_ROUTINE(IntPtr v) public bool Equals(PPS_POST_PROCESS_INIT_ROUTINE other) => Value == other.Value; - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is PPS_POST_PROCESS_INIT_ROUTINE pPS_POST_PROCESS_INIT_ROUTINE && Equals(pPS_POST_PROCESS_INIT_ROUTINE); } From 3461e01b19e5d33922d8efc5281b90bf8f34eeef Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:41:13 -0700 Subject: [PATCH 031/306] refactor: simplify GetFullProcessImageName --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 46 +++++++++++---------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 0f009da..0c6f3e6 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -271,47 +271,51 @@ ref returnLength /// /// A SafeProcessHandle opened with /// The path to the executable image. - /// Call to or failed. + /// The process handle is invalid + /// QueryFullProcessImageName failed. See Exception message for details. + /// //todo: move into SafeHandleEx private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) { if (hProcess.IsInvalid) - { throw new ArgumentException("The process handle is invalid", nameof(hProcess)); - } uint size = 260 + 1; uint bufferLength = size; - IntPtr ptr = Marshal.AllocHGlobal((int)bufferLength); - PWSTR buffer = new PWSTR((char*)ptr); + string retVal = ""; - if (!QueryFullProcessImageName( + using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); + if (QueryFullProcessImageName( hProcess: hProcess, dwFlags: PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, lpExeName: buffer, lpdwSize: ref size)) { - if (bufferLength < size) + retVal = buffer.ToString(); + } + else if (bufferLength < size) + { + using PWSTR newBuffer = Marshal.ReAllocHGlobal((IntPtr)buffer.Value, (IntPtr)size); + if (QueryFullProcessImageName( + hProcess, + PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + newBuffer, + ref size)) { - ptr = Marshal.ReAllocHGlobal(ptr, (IntPtr)size); - buffer = new((char*)ptr); - _ = QueryFullProcessImageName( - hProcess, - PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - buffer, - ref size); + retVal = newBuffer.ToString(); } else { - var err = Marshal.GetLastPInvokeError(); - hProcess.Close(); - throw new Win32Exception(err); + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); } } + else + { + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); + } - // this is horribly inefficient. How many times are we creating new references and/or buffers? - hProcess.Close(); - string retVal = buffer.ToString(); - Marshal.FreeHGlobal((IntPtr)buffer.Value); + // PWSTR instances are freed by their using blocks' finalizers return retVal; } From 5e033c2cec56238226021ec201f572a3e813877a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:41:58 -0700 Subject: [PATCH 032/306] refactor: remove redundant Dispose() calls from FindLockingProcesses() --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 0c6f3e6..16c47d1 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -49,6 +49,8 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth { List processes = new(); + // todo: new RmStartSession overload in CsWin32_NativeMethods.cs which can throw a StartSessionException derived from System.ComponentModel.Win32Exception + // Why? new Win32Exception() will get the last PInvoke error code in addition to the system's message for that Win32ErrorCode. uint res = RmStartSession(out var handle, 0, key); if (res != 0) { @@ -62,13 +64,14 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth uint lpdwRebootReasons = RmRebootReasonNone; string[] resources = { path }; + + // "using" blocks have hidden "finally" blocks which are executed before exceptions leave this context. using (PWSTR pResources = (char*)Marshal.StringToHGlobalUni(path)) { res = RmRegisterResources(handle, new Span(new PWSTR[] { pResources }), rgApplications: new(), new()); if (res != 0) { - pResources.Dispose(); throw new RegisterResourceException(); } @@ -95,20 +98,17 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth } catch (ArgumentException) { - pResources.Dispose(); if (rethrowExceptions) throw; } } } else { - pResources.Dispose(); throw new RmListException(); } } else if (res != 0) { - pResources.Dispose(); throw new UnauthorizedAccessException(); } } @@ -116,7 +116,6 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth finally { _ = RmEndSession(handle); - key.Dispose(); } return processes; From 2cfe2aafe2d525bf5d1793a3b44ae442ae283aa6 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:49:48 -0700 Subject: [PATCH 033/306] refactor: rename GetObjectType() to GetHandleObjectType() refactor: replace GetObjectType() with property in SafeHandleEx refactor: update SafeHandleEx/SafeFileHandleEx constructors refactor: inline Init/InitFile methods refactor: rename File properties in SafeFileHandleEx Unfortunately, many of these changes would be annoying to commit separately. --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 102 ++++++++++---------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 16c47d1..c68a8ce 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -381,7 +381,7 @@ public struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX /// /// P/Invoke function NtQueryObject failed. See Exception data. /// The Type of the object as a string. - public unsafe string GetObjectType() + public unsafe string GetHandleObjectType() { /* Query the object type */ string typeName; @@ -402,7 +402,7 @@ public unsafe string GetObjectType() return typeName; } - /// Invokes and checks if the result is "File". + /// Invokes and checks if the result is "File". /// True if the handle is for a file or directory. /// Based on source of C/C++ projects Hijack File Handle and Handle Monitor /// Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details. @@ -410,7 +410,7 @@ public bool IsFileHandle() { try { - string type = GetObjectType(); + string type = GetHandleObjectType(); return !string.IsNullOrWhiteSpace(type) && string.CompareOrdinal(type, "File") == 0; } catch (Exception e) @@ -442,19 +442,23 @@ public SafeFileHandle ToSafeFileHandle() ///
internal class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { - private protected SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx; - - internal SafeHandleEx(bool ownsHandle = false) : base(ownsHandle) - { } - - public SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX newSysHandleEx, bool ownsHandle = false) : base(ownsHandle) - { - sysHandleEx = newSysHandleEx; - Init(); - } - - internal void Init() + /// + /// Initializes a new instance of the SafeHandleEx class from a , specifying whether the handle is to be reliably released. + /// + /// + /// true to reliably release the handle during the finalization phase; false to prevent reliable release(not recommended). + /// + internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(false) { + SysHandleEx = sysHandleEx; + try + { + HandleObjectType = SysHandleEx.GetHandleObjectType(); + } + catch (Exception e) + { + ExceptionLog.Add(e); + } try { //Process.EnterDebugMode(); Best practice: only call this once. @@ -493,7 +497,7 @@ internal void Init() } } - internal SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX SysHandleEx => sysHandleEx; + internal SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX SysHandleEx { get; } public unsafe void* Object => SysHandleEx.Object; /// @@ -508,6 +512,12 @@ internal void Init() /// public uint HandleAttributes => SysHandleEx.HandleAttributes; + /// + /// The Type of the object as a string. + /// + /// + public string? HandleObjectType { get; private set; } + public string? ProcessCommandLine { get; private set; } public string? ProcessMainModulePath { get; private set; } public string? ProcessName { get; private set; } @@ -556,8 +566,6 @@ public void UnlockSystemHandle() } } - public string GetObjectType() => SysHandleEx.GetObjectType(); - /// /// Try to get a process's command line from its PEB /// @@ -626,50 +634,42 @@ protected override bool ReleaseHandle() internal class SafeFileHandleEx : SafeHandleEx { // TODO: there's gotta be a better way to cast a base class to an implementing class - internal SafeFileHandleEx(SafeHandleEx safeHandleEx) - { - sysHandleEx = safeHandleEx.SysHandleEx; - Init(); - InitFile(); - } - - public SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx, bool ownsHandle) : base(newSysHandleEx: sysHandleEx, ownsHandle: ownsHandle) - { - //base.sysHandleEx = sysHandleEx; - //Init(); - InitFile(); - } + internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) + { } - private void InitFile() + /// + /// Initialize + /// + /// + /// + /// + public SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx) { - bool? isFileHandle; try { - isFileHandle = sysHandleEx.IsFileHandle(); - } - catch (Exception) - { - isFileHandle = null; - // IsFileHandle failed - } - if (isFileHandle == true) - { - FullPath = TryGetFinalPath(); - if (FullPath != null) + if (sysHandleEx.IsFileHandle()) { - Name = Path.GetFileName(FullPath); - IsDirectory = (File.GetAttributes(FullPath) & FileAttributes.Directory) == FileAttributes.Directory; + FileFullPath = TryGetFinalPath(); + if (FileFullPath != null) + { + FileName = Path.GetFileName(FileFullPath); + FileIsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; + } + } + else + { + ExceptionLog.Add(new InvalidCastException("Cannot cast non-file handle to file handle!")); } } - else + catch (Exception ex) { - throw new InvalidCastException("Cannot cast non-file handle to file handle!"); + ExceptionLog.Add(ex); } } - public string? FullPath { get; private set; } - public string? Name { get; private set; } - public bool? IsDirectory { get; private set; } + public string? FileFullPath { get; } + public string? FileName { get; } + public bool? FileIsDirectory { get; private set; } /// /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. From 5c83a062f8ea5cca0e44c1be4b8f6d7c42a4dd45 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:50:35 -0700 Subject: [PATCH 034/306] refactor: update GetProcessCommandLine xml docs --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index c68a8ce..5f4d931 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -569,9 +569,10 @@ public void UnlockSystemHandle() /// /// Try to get a process's command line from its PEB /// - /// - /// - /// + /// A handle to the target process with the rights PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ + /// Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented. + /// Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible. + /// NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION' private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) { /* Get PROCESS_BASIC_INFORMATION */ From 1c03cbf9a15daa4c083323f5ad5d4790fcfc1402 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:51:36 -0700 Subject: [PATCH 035/306] refactor: update UnlockSystemHandle xml doc --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 5f4d931..20ba6fd 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -529,6 +529,15 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals /// Use List's methods (e.g. Add) to modify this list. public static List ExceptionLog { get; } = new(); + /// + /// Release the system handle.
+ /// ! WARNING !
+ /// If the handle or a duplicate is in use by a driver or other kernel-level software, a function that accesses the now-invalid handle will cause a stopcode (AKA Blue Screen Of D). + ///
+ /// + /// See Raymond Chen's devblog article + /// "Kernel handles are not reference-counted". + /// public void UnlockSystemHandle() { HANDLE rawHProcess; From 4711cecd41a591617752fec287899d8c3a89d1cb Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:52:44 -0700 Subject: [PATCH 036/306] refactor: wrap Win32Exception in Exception w/ detailed message --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 20ba6fd..27825c0 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -397,7 +397,7 @@ public unsafe string GetHandleObjectType() else { Marshal.FreeHGlobal((IntPtr)objectTypeInfo); - throw new Win32Exception(); + throw new Exception("P/Invoke function NtQueryObject failed. See Exception data.", status.GetNTStatusException()); } return typeName; } From 579f0fb5b661f1f138e3062c170da565a26eb3ba Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 21:53:37 -0700 Subject: [PATCH 037/306] refactor: remove redundant cast in TryGetFinalPath() --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 27825c0..1e7af59 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -687,14 +687,14 @@ public SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sy /// If successful, returns a path string formatted as 'X:\dir\file.ext' or 'X:\dir' /// The path '{fullName}' was not found when querying a file handle. /// Failed to query path from file handle. Insufficient memory to complete the operation. - /// Failed to query path from file handle. Invalid flags were specified for dwFlags. + /// Failed to query path from file handle. Invalid flags were specified for dwFlags. private unsafe string TryGetFinalPath() { /// Return the normalized drive name. This is the default. uint bufLength = (uint)short.MaxValue; var buffer = Marshal.AllocHGlobal((int)bufLength); PWSTR fullName = new((char*)buffer); - uint length = GetFinalPathNameByHandle(SysHandleEx.ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + uint length = GetFinalPathNameByHandle(this, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); if (length != 0) { From facc3297f09843ac80dfdbf7e3cb3ae28651828c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 22:44:16 -0700 Subject: [PATCH 038/306] refactor: set SafeHandleEx.ExceptionLog as non-static property --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 1e7af59..a2b65a4 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -527,7 +527,7 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals /// Intended to explain why the process command line, main module path, and name are unavailable. ///
/// Use List's methods (e.g. Add) to modify this list. - public static List ExceptionLog { get; } = new(); + public List ExceptionLog { get; } = new(); /// /// Release the system handle.
From 8a04119795d3c3a1740b84414fa0737932dcf59a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 23:05:35 -0700 Subject: [PATCH 039/306] refactor: move GetFullProcessImageName into SafeHandleEx --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 117 ++++++++++---------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index a2b65a4..2d13b0d 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -258,65 +258,7 @@ ref returnLength return retVal.AsSpan(); } - /// - /// A wrapper for QueryFullProcessImageName - /// - /// - /// The identifier of the local process to be opened. - /// If the specified process is the System Idle Process(0x00000000), - /// the function fails and the last error code is ERROR_INVALID_PARAMETER. - /// If the specified process is the System process or one of the Client Server Run-Time Subsystem(CSRSS) processes, - /// this function fails and the last error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from opening them. - /// - /// A SafeProcessHandle opened with - /// The path to the executable image. - /// The process handle is invalid - /// QueryFullProcessImageName failed. See Exception message for details. - /// //todo: move into SafeHandleEx - private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) - { - if (hProcess.IsInvalid) - throw new ArgumentException("The process handle is invalid", nameof(hProcess)); - - uint size = 260 + 1; - uint bufferLength = size; - string retVal = ""; - - using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); - if (QueryFullProcessImageName( - hProcess: hProcess, - dwFlags: PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - lpExeName: buffer, - lpdwSize: ref size)) - { - retVal = buffer.ToString(); - } - else if (bufferLength < size) - { - using PWSTR newBuffer = Marshal.ReAllocHGlobal((IntPtr)buffer.Value, (IntPtr)size); - if (QueryFullProcessImageName( - hProcess, - PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - newBuffer, - ref size)) - { - retVal = newBuffer.ToString(); - } - else - { - // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - throw new Win32Exception(); - } - } - else - { - // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - throw new Win32Exception(); - } - // PWSTR instances are freed by their using blocks' finalizers - return retVal; - } #endregion Methods @@ -634,6 +576,65 @@ private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) } } + /// + /// A wrapper for QueryFullProcessImageName + /// + /// + /// The identifier of the local process to be opened. + /// If the specified process is the System Idle Process(0x00000000), + /// the function fails and the last error code is ERROR_INVALID_PARAMETER. + /// If the specified process is the System process or one of the Client Server Run-Time Subsystem(CSRSS) processes, + /// this function fails and the last error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from opening them. + /// + /// A SafeProcessHandle opened with + /// The path to the executable image. + /// The process handle is invalid + /// QueryFullProcessImageName failed. See Exception message for details. + private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) + { + if (hProcess.IsInvalid) + throw new ArgumentException("The process handle is invalid", nameof(hProcess)); + + uint size = 260 + 1; + uint bufferLength = size; + string retVal = ""; + + using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); + if (QueryFullProcessImageName( + hProcess: hProcess, + dwFlags: PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + lpExeName: buffer, + lpdwSize: ref size)) + { + retVal = buffer.ToString(); + } + else if (bufferLength < size) + { + using PWSTR newBuffer = Marshal.ReAllocHGlobal((IntPtr)buffer.Value, (IntPtr)size); + if (QueryFullProcessImageName( + hProcess, + PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + newBuffer, + ref size)) + { + retVal = newBuffer.ToString(); + } + else + { + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); + } + } + else + { + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); + } + + // PWSTR instances are freed by their using blocks' finalizers + return retVal; + } + protected override bool ReleaseHandle() { Close(); From 6d955d1abd1c579a97fb7314303135abc438b26d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 23:07:02 -0700 Subject: [PATCH 040/306] docs: remove unused param from GetFullProcessImageName docs --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 2d13b0d..444cb34 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -579,13 +579,6 @@ private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) /// /// A wrapper for QueryFullProcessImageName /// - /// - /// The identifier of the local process to be opened. - /// If the specified process is the System Idle Process(0x00000000), - /// the function fails and the last error code is ERROR_INVALID_PARAMETER. - /// If the specified process is the System process or one of the Client Server Run-Time Subsystem(CSRSS) processes, - /// this function fails and the last error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from opening them. - /// /// A SafeProcessHandle opened with /// The path to the executable image. /// The process handle is invalid From 97d87367594de2ac63986292b76cf38c0ada4c31 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 18 Sep 2022 23:21:40 -0700 Subject: [PATCH 041/306] refactor: move SafeHandleEx, SafeFileHandleEx to own files --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 353 +----------------- .../Domain/SafeFileHandleEx.cs | 107 ++++++ deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 268 +++++++++++++ 3 files changed, 376 insertions(+), 352 deletions(-) create mode 100644 deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs create mode 100644 deadlock-dotnet-sdk/Domain/SafeHandleEx.cs diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 444cb34..44a0fb3 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -5,9 +5,7 @@ using Microsoft.Win32.SafeHandles; using Windows.Win32.Foundation; using Windows.Win32.Security; -using Windows.Win32.Storage.FileSystem; using Windows.Win32.System.RestartManager; -using Windows.Win32.System.Threading; using Windows.Win32.System.WindowsProgramming; using static Windows.Win32.PInvoke; @@ -25,7 +23,7 @@ namespace deadlock_dotnet_sdk.Domain; /// /// Collection of native methods /// -internal static class NativeMethods +internal static partial class NativeMethods { #region Variables @@ -375,353 +373,4 @@ public SafeFileHandle ToSafeFileHandle() } #endregion Structs - - #region Classes - - /// - /// A SafeHandleZeroOrMinusOneIsInvalid wrapping a SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
- /// Before querying for system handles, call for easier access to restricted data. - ///
- internal class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid - { - /// - /// Initializes a new instance of the SafeHandleEx class from a , specifying whether the handle is to be reliably released. - /// - /// - /// true to reliably release the handle during the finalization phase; false to prevent reliable release(not recommended). - /// - internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(false) - { - SysHandleEx = sysHandleEx; - try - { - HandleObjectType = SysHandleEx.GetHandleObjectType(); - } - catch (Exception e) - { - ExceptionLog.Add(e); - } - try - { - //Process.EnterDebugMode(); Best practice: only call this once. - - /** Open handle for process */ - // PROCESS_QUERY_LIMITED_INFORMATION is necessary for QueryFullProcessImageName - // PROCESS_QUERY_LIMITED_INFORMATION + PROCESS_VM_READ for reading PEB from the process's memory space. - // if we need to duplicate a handle later, we'll use PROCESS_DUP_HANDLE - - HANDLE rawHandle = OpenProcess( - dwDesiredAccess: PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, - bInheritHandle: (BOOL)false, - dwProcessId: (uint)ProcessId - ); - - if (rawHandle.IsNull) - throw new Win32Exception("Failed to open process handle with access rights 'PROCESS_QUERY_LIMITED_INFORMATION' and 'PROCESS_VM_READ'. The following information will be unavailable: main module full name, process name, "); - - SafeProcessHandle hProcess = new(rawHandle, true); - - /** Get main module's full path */ - ProcessMainModulePath = GetFullProcessImageName(hProcess); - - /** Get Process's name */ - if (!string.IsNullOrWhiteSpace(ProcessMainModulePath)) - { - ProcessName = Path.GetFileNameWithoutExtension(ProcessMainModulePath); - } - - /** Get process's possibly-overwritten command line from the PEB struct in its memory space */ - GetProcessCommandLine(hProcess); - } - catch (Exception e) - { - ExceptionLog.Add(e); - } - } - - internal SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX SysHandleEx { get; } - - public unsafe void* Object => SysHandleEx.Object; - /// - /// cast to uint - /// - public HANDLE ProcessId => SysHandleEx.UniqueProcessId; - public HANDLE HandleValue => SysHandleEx.HandleValue; - public ushort CreatorBackTraceIndex => SysHandleEx.CreatorBackTraceIndex; - /// - public ACCESS_MASK GrantedAccess => SysHandleEx.GrantedAccess; - public ushort ObjectTypeIndex => SysHandleEx.ObjectTypeIndex; - /// - public uint HandleAttributes => SysHandleEx.HandleAttributes; - - /// - /// The Type of the object as a string. - /// - /// - public string? HandleObjectType { get; private set; } - - public string? ProcessCommandLine { get; private set; } - public string? ProcessMainModulePath { get; private set; } - public string? ProcessName { get; private set; } - - /// - /// A list of exceptions thrown by constructors and other methods of this class.
- /// Intended to explain why the process command line, main module path, and name are unavailable. - ///
- /// Use List's methods (e.g. Add) to modify this list. - public List ExceptionLog { get; } = new(); - - /// - /// Release the system handle.
- /// ! WARNING !
- /// If the handle or a duplicate is in use by a driver or other kernel-level software, a function that accesses the now-invalid handle will cause a stopcode (AKA Blue Screen Of D). - ///
- /// - /// See Raymond Chen's devblog article - /// "Kernel handles are not reference-counted". - /// - public void UnlockSystemHandle() - { - HANDLE rawHProcess; - SafeProcessHandle? hProcess = null; - try - { - if ((rawHProcess = OpenProcess( - PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, - true, - (uint)ProcessId) - ).IsNull) - { - throw new Win32Exception($"Failed to open process with id {(int)ProcessId} to duplicate and close object handle."); - } - - hProcess = new(rawHProcess, true); - if (DuplicateHandle(hProcess, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_CLOSE_SOURCE)) - { - dupHandle.Close(); - hProcess.Close(); - - // finally, close this SafeHandleEx - Close(); - } - else - { - throw new Win32Exception("Function DuplicateHandle failed to duplicate the handle"); - } - } - catch (Exception e) - { - ExceptionLog.Add(e); - if (hProcess is not null) - hProcess.Close(); - } - } - - /// - /// Try to get a process's command line from its PEB - /// - /// A handle to the target process with the rights PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ - /// Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented. - /// Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible. - /// NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION' - private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) - { - /* Get PROCESS_BASIC_INFORMATION */ - uint sysInfoLength = (uint)Marshal.SizeOf(); - PROCESS_BASIC_INFORMATION processBasicInfo; - IntPtr sysInfo = Marshal.AllocHGlobal((int)sysInfoLength); - NTSTATUS status = (NTSTATUS)0; - uint retLength = 0; - - if ((status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - (void*)sysInfo, - sysInfoLength, - ref retLength)) - .IsSuccessful) - { - processBasicInfo = Marshal.PtrToStructure(sysInfo); - - // if our process is WOW64, we need to account for different pointer sizes if - // the target process is 64-bit - IsWow64Process(hProcess, out BOOL wow64Process); - if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess && wow64Process) - { - throw new NotImplementedException("Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented."); - // too much trouble. If someone else wants to do it, be my guest. - // https://stackoverflow.com/a/36798492/14894786 - // Reason: if our process is 32-bit, we'd be stuck with 32-bit pointers. - // If the PEB's address is in 64-bit address space, we can't access it - // because the pointer value we received was truncated from 64 bits to - // 32 bits. - } - - IntPtr buf = Marshal.AllocHGlobal(sizeof(PEB)); - if (ReadProcessMemory(hProcess, processBasicInfo.PebBaseAddress, (void*)buf, (nuint)sizeof(PEB), null)) - { - PEB peb = Marshal.PtrToStructure(buf); - ProcessCommandLine = (*peb.ProcessParameters).CommandLine.ToStringLength(); - } - else - { - // this calls Marshal.GetLastPInvokeError() - // https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/ComponentModel/Win32Exception.cs?L46 - throw new Win32Exception("Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible."); - } - } - else - { - throw new Exception("NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION'"); - } - } - - /// - /// A wrapper for QueryFullProcessImageName - /// - /// A SafeProcessHandle opened with - /// The path to the executable image. - /// The process handle is invalid - /// QueryFullProcessImageName failed. See Exception message for details. - private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) - { - if (hProcess.IsInvalid) - throw new ArgumentException("The process handle is invalid", nameof(hProcess)); - - uint size = 260 + 1; - uint bufferLength = size; - string retVal = ""; - - using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); - if (QueryFullProcessImageName( - hProcess: hProcess, - dwFlags: PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - lpExeName: buffer, - lpdwSize: ref size)) - { - retVal = buffer.ToString(); - } - else if (bufferLength < size) - { - using PWSTR newBuffer = Marshal.ReAllocHGlobal((IntPtr)buffer.Value, (IntPtr)size); - if (QueryFullProcessImageName( - hProcess, - PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - newBuffer, - ref size)) - { - retVal = newBuffer.ToString(); - } - else - { - // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - throw new Win32Exception(); - } - } - else - { - // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - throw new Win32Exception(); - } - - // PWSTR instances are freed by their using blocks' finalizers - return retVal; - } - - protected override bool ReleaseHandle() - { - Close(); - return IsClosed; - } - } - - internal class SafeFileHandleEx : SafeHandleEx - { - // TODO: there's gotta be a better way to cast a base class to an implementing class - internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) - { } - - /// - /// Initialize - /// - /// - /// - /// - public SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx) - { - try - { - if (sysHandleEx.IsFileHandle()) - { - FileFullPath = TryGetFinalPath(); - if (FileFullPath != null) - { - FileName = Path.GetFileName(FileFullPath); - FileIsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; - } - } - else - { - ExceptionLog.Add(new InvalidCastException("Cannot cast non-file handle to file handle!")); - } - } - catch (Exception ex) - { - ExceptionLog.Add(ex); - } - } - - public string? FileFullPath { get; } - public string? FileName { get; } - public bool? FileIsDirectory { get; private set; } - - /// - /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. - /// - /// If successful, returns a path string formatted as 'X:\dir\file.ext' or 'X:\dir' - /// The path '{fullName}' was not found when querying a file handle. - /// Failed to query path from file handle. Insufficient memory to complete the operation. - /// Failed to query path from file handle. Invalid flags were specified for dwFlags. - private unsafe string TryGetFinalPath() - { - /// Return the normalized drive name. This is the default. - uint bufLength = (uint)short.MaxValue; - var buffer = Marshal.AllocHGlobal((int)bufLength); - PWSTR fullName = new((char*)buffer); - uint length = GetFinalPathNameByHandle(this, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); - - if (length != 0) - { - while (length > bufLength) - { - // buffer was too small. Reallocate buffer with size matched 'length' and try again - buffer = Marshal.ReAllocHGlobal(buffer, (IntPtr)length); - fullName = new((char*)buffer); - - bufLength = GetFinalPathNameByHandle(SysHandleEx.ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); - } - return fullName.ToString(); - } - else - { - int error = Marshal.GetLastWin32Error(); - const int ERROR_PATH_NOT_FOUND = 3; - const int ERROR_NOT_ENOUGH_MEMORY = 8; - const int ERROR_INVALID_PARAMETER = 87; // 0x57 - - /* Hold up. Let's free our memory before throwing exceptions. */ - Marshal.FreeHGlobal(buffer); - - throw error switch - { - ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{fullName}' was not found when querying a file handle.", fileName: fullName.ToString()), // Removable storage, deleted item, network shares, et cetera - ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation."), // unlikely, but possible if system has little free memory - ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags."), // possible only if FILE_NAME_NORMALIZED (0) is invalid - _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path."), - }; - } - } - } - - #endregion Classes } diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs new file mode 100644 index 0000000..d311663 --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -0,0 +1,107 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.Storage.FileSystem; +using static Windows.Win32.PInvoke; + +// Re: StructLayout +// "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." +// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-6.0#remarks + +// new Win32Exception() is defined as +// public Win32Exception() : this(Marshal.GetLastPInvokeError()) +// { +// } + +namespace deadlock_dotnet_sdk.Domain; + +internal static partial class NativeMethods +{ + internal class SafeFileHandleEx : SafeHandleEx + { + // TODO: there's gotta be a better way to cast a base class to an implementing class + internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) + { } + + /// + /// Initialize + /// + /// + /// + /// + public SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx) + { + try + { + if (sysHandleEx.IsFileHandle()) + { + FileFullPath = TryGetFinalPath(); + if (FileFullPath != null) + { + FileName = Path.GetFileName(FileFullPath); + FileIsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; + } + } + else + { + ExceptionLog.Add(new InvalidCastException("Cannot cast non-file handle to file handle!")); + } + } + catch (Exception ex) + { + ExceptionLog.Add(ex); + } + } + + public string? FileFullPath { get; } + public string? FileName { get; } + public bool? FileIsDirectory { get; private set; } + + /// + /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. + /// + /// If successful, returns a path string formatted as 'X:\dir\file.ext' or 'X:\dir' + /// The path '{fullName}' was not found when querying a file handle. + /// Failed to query path from file handle. Insufficient memory to complete the operation. + /// Failed to query path from file handle. Invalid flags were specified for dwFlags. + private unsafe string TryGetFinalPath() + { + /// Return the normalized drive name. This is the default. + uint bufLength = (uint)short.MaxValue; + var buffer = Marshal.AllocHGlobal((int)bufLength); + PWSTR fullName = new((char*)buffer); + uint length = GetFinalPathNameByHandle(this, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + + if (length != 0) + { + while (length > bufLength) + { + // buffer was too small. Reallocate buffer with size matched 'length' and try again + buffer = Marshal.ReAllocHGlobal(buffer, (IntPtr)length); + fullName = new((char*)buffer); + + bufLength = GetFinalPathNameByHandle(SysHandleEx.ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + } + return fullName.ToString(); + } + else + { + int error = Marshal.GetLastWin32Error(); + const int ERROR_PATH_NOT_FOUND = 3; + const int ERROR_NOT_ENOUGH_MEMORY = 8; + const int ERROR_INVALID_PARAMETER = 87; // 0x57 + + /* Hold up. Let's free our memory before throwing exceptions. */ + Marshal.FreeHGlobal(buffer); + + throw error switch + { + ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{fullName}' was not found when querying a file handle.", fileName: fullName.ToString()), // Removable storage, deleted item, network shares, et cetera + ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation."), // unlikely, but possible if system has little free memory + ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags."), // possible only if FILE_NAME_NORMALIZED (0) is invalid + _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path."), + }; + } + } + } + +} diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs new file mode 100644 index 0000000..998aa8a --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -0,0 +1,268 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using Windows.Win32.Foundation; +using Windows.Win32.Security; +using Windows.Win32.System.Threading; +using static Windows.Win32.PInvoke; + + +namespace deadlock_dotnet_sdk.Domain; + +/// +/// A SafeHandleZeroOrMinusOneIsInvalid wrapping a SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
+/// Before querying for system handles, call for easier access to restricted data. +///
+internal class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid +{ + /// + /// Initializes a new instance of the SafeHandleEx class from a , specifying whether the handle is to be reliably released. + /// + /// + /// true to reliably release the handle during the finalization phase; false to prevent reliable release(not recommended). + /// + internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(false) + { + SysHandleEx = sysHandleEx; + try + { + HandleObjectType = SysHandleEx.GetHandleObjectType(); + } + catch (Exception e) + { + ExceptionLog.Add(e); + } + try + { + //Process.EnterDebugMode(); Best practice: only call this once. + + /** Open handle for process */ + // PROCESS_QUERY_LIMITED_INFORMATION is necessary for QueryFullProcessImageName + // PROCESS_QUERY_LIMITED_INFORMATION + PROCESS_VM_READ for reading PEB from the process's memory space. + // if we need to duplicate a handle later, we'll use PROCESS_DUP_HANDLE + + HANDLE rawHandle = OpenProcess( + dwDesiredAccess: PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, + bInheritHandle: (BOOL)false, + dwProcessId: (uint)ProcessId + ); + + if (rawHandle.IsNull) + throw new Win32Exception("Failed to open process handle with access rights 'PROCESS_QUERY_LIMITED_INFORMATION' and 'PROCESS_VM_READ'. The following information will be unavailable: main module full name, process name, "); + + SafeProcessHandle hProcess = new(rawHandle, true); + + /** Get main module's full path */ + ProcessMainModulePath = GetFullProcessImageName(hProcess); + + /** Get Process's name */ + if (!string.IsNullOrWhiteSpace(ProcessMainModulePath)) + { + ProcessName = Path.GetFileNameWithoutExtension(ProcessMainModulePath); + } + + /** Get process's possibly-overwritten command line from the PEB struct in its memory space */ + GetProcessCommandLine(hProcess); + } + catch (Exception e) + { + ExceptionLog.Add(e); + } + } + + internal NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX SysHandleEx { get; } + + public unsafe void* Object => SysHandleEx.Object; + /// + /// cast to uint + /// + public HANDLE ProcessId => SysHandleEx.UniqueProcessId; + public HANDLE HandleValue => SysHandleEx.HandleValue; + public ushort CreatorBackTraceIndex => SysHandleEx.CreatorBackTraceIndex; + /// + public ACCESS_MASK GrantedAccess => SysHandleEx.GrantedAccess; + public ushort ObjectTypeIndex => SysHandleEx.ObjectTypeIndex; + /// + public uint HandleAttributes => SysHandleEx.HandleAttributes; + + /// + /// The Type of the object as a string. + /// + /// + public string? HandleObjectType { get; private set; } + + public string? ProcessCommandLine { get; private set; } + public string? ProcessMainModulePath { get; private set; } + public string? ProcessName { get; private set; } + + /// + /// A list of exceptions thrown by constructors and other methods of this class.
+ /// Intended to explain why the process command line, main module path, and name are unavailable. + ///
+ /// Use List's methods (e.g. Add) to modify this list. + public List ExceptionLog { get; } = new(); + + /// + /// Release the system handle.
+ /// ! WARNING !
+ /// If the handle or a duplicate is in use by a driver or other kernel-level software, a function that accesses the now-invalid handle will cause a stopcode (AKA Blue Screen Of D). + ///
+ /// + /// See Raymond Chen's devblog article + /// "Kernel handles are not reference-counted". + /// + public void UnlockSystemHandle() + { + HANDLE rawHProcess; + SafeProcessHandle? hProcess = null; + try + { + if ((rawHProcess = OpenProcess( + PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, + true, + (uint)ProcessId) + ).IsNull) + { + throw new Win32Exception($"Failed to open process with id {(int)ProcessId} to duplicate and close object handle."); + } + + hProcess = new(rawHProcess, true); + if (DuplicateHandle(hProcess, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_CLOSE_SOURCE)) + { + dupHandle.Close(); + hProcess.Close(); + + // finally, close this SafeHandleEx + Close(); + } + else + { + throw new Win32Exception("Function DuplicateHandle failed to duplicate the handle"); + } + } + catch (Exception e) + { + ExceptionLog.Add(e); + if (hProcess is not null) + hProcess.Close(); + } + } + + /// + /// Try to get a process's command line from its PEB + /// + /// A handle to the target process with the rights PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ + /// Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented. + /// Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible. + /// NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION' + private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) + { + /* Get PROCESS_BASIC_INFORMATION */ + uint sysInfoLength = (uint)Marshal.SizeOf(); + PROCESS_BASIC_INFORMATION processBasicInfo; + IntPtr sysInfo = Marshal.AllocHGlobal((int)sysInfoLength); + NTSTATUS status = (NTSTATUS)0; + uint retLength = 0; + + if ((status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + (void*)sysInfo, + sysInfoLength, + ref retLength)) + .IsSuccessful) + { + processBasicInfo = Marshal.PtrToStructure(sysInfo); + + // if our process is WOW64, we need to account for different pointer sizes if + // the target process is 64-bit + IsWow64Process(hProcess, out BOOL wow64Process); + if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess && wow64Process) + { + throw new NotImplementedException("Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented."); + // too much trouble. If someone else wants to do it, be my guest. + // https://stackoverflow.com/a/36798492/14894786 + // Reason: if our process is 32-bit, we'd be stuck with 32-bit pointers. + // If the PEB's address is in 64-bit address space, we can't access it + // because the pointer value we received was truncated from 64 bits to + // 32 bits. + } + + IntPtr buf = Marshal.AllocHGlobal(sizeof(PEB)); + if (ReadProcessMemory(hProcess, processBasicInfo.PebBaseAddress, (void*)buf, (nuint)sizeof(PEB), null)) + { + PEB peb = Marshal.PtrToStructure(buf); + ProcessCommandLine = (*peb.ProcessParameters).CommandLine.ToStringLength(); + } + else + { + // this calls Marshal.GetLastPInvokeError() + // https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/ComponentModel/Win32Exception.cs?L46 + throw new Win32Exception("Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible."); + } + } + else + { + throw new Exception("NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION'"); + } + } + + /// + /// A wrapper for QueryFullProcessImageName + /// + /// A SafeProcessHandle opened with + /// The path to the executable image. + /// The process handle is invalid + /// QueryFullProcessImageName failed. See Exception message for details. + private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) + { + if (hProcess.IsInvalid) + throw new ArgumentException("The process handle is invalid", nameof(hProcess)); + + uint size = 260 + 1; + uint bufferLength = size; + string retVal = ""; + + using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); + if (QueryFullProcessImageName( + hProcess: hProcess, + dwFlags: PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + lpExeName: buffer, + lpdwSize: ref size)) + { + retVal = buffer.ToString(); + } + else if (bufferLength < size) + { + using PWSTR newBuffer = Marshal.ReAllocHGlobal((IntPtr)buffer.Value, (IntPtr)size); + if (QueryFullProcessImageName( + hProcess, + PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + newBuffer, + ref size)) + { + retVal = newBuffer.ToString(); + } + else + { + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); + } + } + else + { + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); + } + + // PWSTR instances are freed by their using blocks' finalizers + return retVal; + } + + protected override bool ReleaseHandle() + { + Close(); + return IsClosed; + } +} From 56306ff6ea5de8d82e5d54989b1412f04e5dbba3 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 19 Sep 2022 00:09:14 -0700 Subject: [PATCH 042/306] refactor: implement SafeHandleEx.ToString() as JSON serialization --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 998aa8a..f3d92a1 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -1,13 +1,13 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Text.Json; using Microsoft.Win32.SafeHandles; using Windows.Win32.Foundation; using Windows.Win32.Security; using Windows.Win32.System.Threading; using static Windows.Win32.PInvoke; - namespace deadlock_dotnet_sdk.Domain; /// @@ -265,4 +265,9 @@ protected override bool ReleaseHandle() Close(); return IsClosed; } + + /// + /// Serialize the current instance to JSON-formatted text + /// + public override string? ToString() => JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); } From 9d71bec2791db6379bd111cf7d911c0ad4e18b95 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 11 Oct 2022 21:41:38 -0700 Subject: [PATCH 043/306] refactor: change types of ProcessId, HandleValue --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index f3d92a1..7a28b09 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -45,7 +45,7 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE HANDLE rawHandle = OpenProcess( dwDesiredAccess: PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, bInheritHandle: (BOOL)false, - dwProcessId: (uint)ProcessId + dwProcessId: ProcessId ); if (rawHandle.IsNull) @@ -77,8 +77,8 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE /// /// cast to uint /// - public HANDLE ProcessId => SysHandleEx.UniqueProcessId; - public HANDLE HandleValue => SysHandleEx.HandleValue; + public uint ProcessId => (uint)SysHandleEx.UniqueProcessId; + public nuint HandleValue => SysHandleEx.HandleValue; public ushort CreatorBackTraceIndex => SysHandleEx.CreatorBackTraceIndex; /// public ACCESS_MASK GrantedAccess => SysHandleEx.GrantedAccess; @@ -121,10 +121,10 @@ public void UnlockSystemHandle() if ((rawHProcess = OpenProcess( PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, true, - (uint)ProcessId) + ProcessId) ).IsNull) { - throw new Win32Exception($"Failed to open process with id {(int)ProcessId} to duplicate and close object handle."); + throw new Win32Exception($"Failed to open process with id {ProcessId} to duplicate and close object handle."); } hProcess = new(rawHProcess, true); From 8020e7e89077a4eb33816eff38e0dad304f95976 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 11 Oct 2022 21:42:55 -0700 Subject: [PATCH 044/306] refactor: change class SafeHandleEx to public --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 7a28b09..1dde1d4 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -14,14 +14,12 @@ namespace deadlock_dotnet_sdk.Domain; /// A SafeHandleZeroOrMinusOneIsInvalid wrapping a SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
/// Before querying for system handles, call for easier access to restricted data. ///
-internal class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid +public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { /// /// Initializes a new instance of the SafeHandleEx class from a , specifying whether the handle is to be reliably released. /// /// - /// true to reliably release the handle during the finalization phase; false to prevent reliable release(not recommended). - /// internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(false) { SysHandleEx = sysHandleEx; From feea223740e2679a41b844090ee18ecfd37be6ca Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 11 Oct 2022 21:43:48 -0700 Subject: [PATCH 045/306] docs: add Exception doc for SafeHandleEx.UnlockSystemHandle() --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 1dde1d4..b29de84 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -110,6 +110,7 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE /// See Raymond Chen's devblog article /// "Kernel handles are not reference-counted". /// + /// Failed to open process to duplicate and close object handle. public void UnlockSystemHandle() { HANDLE rawHProcess; From 185f28ee1726bde010636acf4fe414f34ed72739 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 11 Oct 2022 21:44:12 -0700 Subject: [PATCH 046/306] refactor: remove some unused properties from SafeHandleEx --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index b29de84..5565644 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -80,9 +80,6 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE public ushort CreatorBackTraceIndex => SysHandleEx.CreatorBackTraceIndex; /// public ACCESS_MASK GrantedAccess => SysHandleEx.GrantedAccess; - public ushort ObjectTypeIndex => SysHandleEx.ObjectTypeIndex; - /// - public uint HandleAttributes => SysHandleEx.HandleAttributes; /// /// The Type of the object as a string. From f8c89e1ee40ad3ed585fbc4f4c12da22e52871de Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 11 Oct 2022 21:46:51 -0700 Subject: [PATCH 047/306] refactor: use read-only, auto-implemented properties where possible Some properties needed private Set accessors for the old Init methods. --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 5565644..7f8c872 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -85,11 +85,11 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE /// The Type of the object as a string. /// /// - public string? HandleObjectType { get; private set; } + public string? HandleObjectType { get; } public string? ProcessCommandLine { get; private set; } - public string? ProcessMainModulePath { get; private set; } - public string? ProcessName { get; private set; } + public string? ProcessMainModulePath { get; } + public string? ProcessName { get; } /// /// A list of exceptions thrown by constructors and other methods of this class.
From 1fc42168fc377abcb5bcaad7c943992e67b1da37 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 12 Oct 2022 16:31:05 -0700 Subject: [PATCH 048/306] refactor: hide reserved field in system handle --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 44a0fb3..3a7911d 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -314,7 +314,9 @@ public struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX public ushort ObjectTypeIndex; // USHORT /// public uint HandleAttributes; // ULONG - public uint Reserved; +#pragma warning disable RCS1213 + private readonly uint Reserved; // Remove unused field declaration. csharp(RCS1213) | Roslynator +#pragma warning restore RCS1213 /// /// Get the Type of the object as a string From e97200faf95937b1f024968d8f5355e557401075 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 12 Oct 2022 16:33:23 -0700 Subject: [PATCH 049/306] refactor: set ACCESS_MASK public for SafeHandleEx --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs index 840a975..ae580a6 100644 --- a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs +++ b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs @@ -73,7 +73,7 @@ namespace Security /// A simple placeholder for dotnet/PInvoke's ACCESS_MASK struct /// /// Process access modifiers are found in Windows.Win32.System.Threading.PROCESS_ACCESS_RIGHTS - internal struct ACCESS_MASK + public struct ACCESS_MASK { public ACCESS_MASK(uint v) { @@ -81,7 +81,6 @@ public ACCESS_MASK(uint v) } public uint Value; - public PROCESS_ACCESS_RIGHTS ProcessAccess => (PROCESS_ACCESS_RIGHTS)Value; public static implicit operator ACCESS_MASK(uint v) => new(v); public static implicit operator uint(ACCESS_MASK v) => v.Value; From 786e567cb13c326946282310ad59f731baede6d7 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 12 Oct 2022 21:55:50 -0700 Subject: [PATCH 050/306] refactor: add constructor, method, cast to funcptr workaround Affects PPS_POST_PROCESSING_INIT_ROUTINE The IntPtr to funcptr cast has been refactored to method "FromPointer". The explicit cast is retained and now calls the new method. --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs index ae580a6..31af45f 100644 --- a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs +++ b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs @@ -1,7 +1,6 @@ /// This file supplements code generated by CsWin32 using System.ComponentModel; using System.Runtime.InteropServices; -using Windows.Win32.System.Threading; using NTSTATUS_plus = PInvoke.NTSTATUS; using NTStatusException = PInvoke.NTStatusException; @@ -157,12 +156,26 @@ namespace System.Threading [UnmanagedFunctionPointer(CallingConvention.Winapi)] internal unsafe delegate void PS_POST_PROCESS_INIT_ROUTINE(); - // Function Pointer workaround. C# 9's function pointers are only allowed in local scope. + /// + /// Function Pointer workaround. C# 9's function pointers are only allowed in + /// local scope. + /// + /// + /// The pointer returned by the Marshal methods is typically a "Thunk" rather + /// than a handle. If the .NET runtime sees a thunk was already created, it + /// will re-use that thunk. + /// source: + /// internal struct PPS_POST_PROCESS_INIT_ROUTINE : IEquatable { public IntPtr Value; - public static explicit operator PPS_POST_PROCESS_INIT_ROUTINE(IntPtr v) + public PPS_POST_PROCESS_INIT_ROUTINE(PS_POST_PROCESS_INIT_ROUTINE initRoutine) + { + Value = Marshal.GetFunctionPointerForDelegate(initRoutine); + } + + public static PPS_POST_PROCESS_INIT_ROUTINE FromPointer(IntPtr v) { try { @@ -177,6 +190,8 @@ public static explicit operator PPS_POST_PROCESS_INIT_ROUTINE(IntPtr v) } } + public static explicit operator PPS_POST_PROCESS_INIT_ROUTINE(IntPtr v) => FromPointer(v); + public bool Equals(PPS_POST_PROCESS_INIT_ROUTINE other) => Value == other.Value; public override bool Equals(object? obj) From 7821396f9b8c1e3f3b2b97c7a71b4dd045cb95eb Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 14 Oct 2022 22:51:33 -0700 Subject: [PATCH 051/306] refactor: clarify names of Filter flag's members --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 3a7911d..cbad964 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -177,8 +177,8 @@ bool Discard(SafeFileHandleEx h) internal enum Filter { FilesOnly = 0, - NonFiles = 1, - TypeQueryFailed = 2 + IncludeNonFiles = 1, + IncludeFailedTypeQuery = 2 } /// From 7cdfb46e624f4855cc5714f5c3a39f9c5f3a79a8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 14 Oct 2022 22:53:09 -0700 Subject: [PATCH 052/306] feat: add class FileLockerEx for file handle locks --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 deadlock-dotnet-sdk/Domain/FileLockerEx.cs diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs new file mode 100644 index 0000000..faab10e --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -0,0 +1,46 @@ +namespace deadlock_dotnet_sdk.Domain +{ + public class FileLockerEx + { + #region Properties + + /// + /// Get or set the path of the file that is locked + public string Path { get; set; } + + /// + /// Get or set the List of Process objects that are locking the file + /// + public List Lockers { get; set; } + + #endregion + + /// + /// Initialize a new FileLocker + /// + public FileLockerEx() + { + Path = ""; + Lockers = new List(); + } + + /// + /// Initialize a new FileLocker + /// + /// The path of the file + /// The List of Process objects that are locking the file + public FileLockerEx(string path, List lockers) + { + Path = path; + Lockers = lockers; + } + + public static FileLockerEx GetFileLockerEx(string path, NativeMethods.Filter filter) + { + return new( + path, + NativeMethods.FindLockingHandles(path, filter) + ); + } + } +} From 039cae1b767648337cdc735c1692d6b64f688723 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 14 Oct 2022 23:27:23 -0700 Subject: [PATCH 053/306] refactor: move Filter flags to FileLockerEx This enum of flags must be part of the public API for users to make use of it. --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 15 ++++++++++++-- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 22 +++++---------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index faab10e..8492912 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,4 @@ -namespace deadlock_dotnet_sdk.Domain +namespace deadlock_dotnet_sdk.Domain { public class FileLockerEx { @@ -35,12 +35,23 @@ public FileLockerEx(string path, List lockers) Lockers = lockers; } - public static FileLockerEx GetFileLockerEx(string path, NativeMethods.Filter filter) + public static FileLockerEx GetFileLockerEx(string path, ResultsFilter filter = ResultsFilter.FilesOnly) { return new( path, NativeMethods.FindLockingHandles(path, filter) ); } + + /// + /// Filters for + /// + [Flags] + public enum ResultsFilter + { + FilesOnly = 0, + IncludeNonFiles = 1, + IncludeFailedTypeQuery = 2 + } } } diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index cbad964..4368aef 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -7,6 +7,7 @@ using Windows.Win32.Security; using Windows.Win32.System.RestartManager; using Windows.Win32.System.WindowsProgramming; +using static deadlock_dotnet_sdk.Domain.FileLockerEx; using static Windows.Win32.PInvoke; // Re: StructLayout @@ -134,7 +135,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth /// /// By default, this method only returns handles for objects /// successfully identified as a file/directory ("File"). - /// and + /// and /// /// /// A list of SafeFileHandleEx objects. @@ -142,7 +143,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth /// /// This might be arduously slow... // TODO: Perhaps we should allow a new query without re-calling GetSystemHandleInfoEx(). - internal static List FindLockingHandles(string? query = null, Filter filter = Filter.FilesOnly) + internal static List FindLockingHandles(string? query = null, ResultsFilter filter = ResultsFilter.FilesOnly) { Process.EnterDebugMode(); // just in case @@ -158,29 +159,18 @@ bool Discard(SafeFileHandleEx h) /* Query for object type succeeded and the type is NOT File */ if (h.HandleObjectType != "File") { - return !filter.HasFlag(Filter.NonFiles); // When requested, keep non-File object handle. Else, discard. + return !filter.HasFlag(ResultsFilter.IncludeNonFiles); // When requested, keep non-File object handle. Else, discard. } // Discard handle if Query and file's path are not null and file's path does not contain query */ return (query is not null) && (h.FileFullPath is not null) && (!h.FileFullPath.Contains(query.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))); } else { - return !filter.HasFlag(Filter.TypeQueryFailed); // When requested, keep handle if the object type query failed. Else, discard. + return !filter.HasFlag(ResultsFilter.IncludeFailedTypeQuery); // When requested, keep handle if the object type query failed. Else, discard. } } } - /// - /// Filters for - /// - [Flags] - internal enum Filter - { - FilesOnly = 0, - IncludeNonFiles = 1, - IncludeFailedTypeQuery = 2 - } - /// /// Get a Span of via /// @@ -256,8 +246,6 @@ ref returnLength return retVal.AsSpan(); } - - #endregion Methods #region Structs From 876046d740a01a006726feedab9eea03ff02a374 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 15 Oct 2022 00:41:07 -0700 Subject: [PATCH 054/306] docs: add DebugMode note to SafeHandleEx SafeHandleEx instances are typically created by `deadlock-dotnet-sdk.Domain.NativeMethods.FindLockingHandles(string, ResultsFilter)` This method starts off by calling `System.Diagnostics.Process.EnterDebugMode()` before querying any handles. --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 7f8c872..2c52499 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -12,7 +12,10 @@ namespace deadlock_dotnet_sdk.Domain; /// /// A SafeHandleZeroOrMinusOneIsInvalid wrapping a SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
-/// Before querying for system handles, call for easier access to restricted data. +/// Before querying for system handles, call +/// for access to some otherwise restricted data. +/// NOTE: FindLockingHandles(string, Filter) +/// enters Debug mode before querying handles and other data. ///
public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { From 958e1f75990d2ce29eee11ecf87481719dcfd83f Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 15 Oct 2022 01:37:40 -0700 Subject: [PATCH 055/306] refactor: lift SafeFileHandleEx out of NativeMethods --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 10 +- .../Domain/SafeFileHandleEx.cs | 139 +++++++++--------- 2 files changed, 73 insertions(+), 76 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 8492912..80ec16c 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,4 @@ -namespace deadlock_dotnet_sdk.Domain +namespace deadlock_dotnet_sdk.Domain { public class FileLockerEx { @@ -11,9 +11,9 @@ public class FileLockerEx /// /// Get or set the List of Process objects that are locking the file /// - public List Lockers { get; set; } + public List Lockers { get; set; } - #endregion + #endregion Properties /// /// Initialize a new FileLocker @@ -21,7 +21,7 @@ public class FileLockerEx public FileLockerEx() { Path = ""; - Lockers = new List(); + Lockers = new List(); } /// @@ -29,7 +29,7 @@ public FileLockerEx() /// /// The path of the file /// The List of Process objects that are locking the file - public FileLockerEx(string path, List lockers) + public FileLockerEx(string path, List lockers) { Path = path; Lockers = lockers; diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index d311663..6dbefd1 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; +using static deadlock_dotnet_sdk.Domain.NativeMethods; using static Windows.Win32.PInvoke; // Re: StructLayout @@ -13,95 +14,91 @@ // } namespace deadlock_dotnet_sdk.Domain; - -internal static partial class NativeMethods +/// +/// A SafeFileHandle-like wrapper for the undocumented Windows type "SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX" +/// +public class SafeFileHandleEx : SafeHandleEx { - internal class SafeFileHandleEx : SafeHandleEx - { - // TODO: there's gotta be a better way to cast a base class to an implementing class - internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) - { } + // TODO: there's gotta be a better way to cast a base class to an implementing class + internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) + { } - /// - /// Initialize - /// - /// - /// - /// - public SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx) + /// + /// Initialize + /// + /// + internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx) + { + try { - try + if (sysHandleEx.IsFileHandle()) { - if (sysHandleEx.IsFileHandle()) + FileFullPath = TryGetFinalPath(); + if (FileFullPath != null) { - FileFullPath = TryGetFinalPath(); - if (FileFullPath != null) - { - FileName = Path.GetFileName(FileFullPath); - FileIsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; - } - } - else - { - ExceptionLog.Add(new InvalidCastException("Cannot cast non-file handle to file handle!")); + FileName = Path.GetFileName(FileFullPath); + FileIsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; } } - catch (Exception ex) + else { - ExceptionLog.Add(ex); + ExceptionLog.Add(new InvalidCastException("Cannot cast non-file handle to file handle!")); } } + catch (Exception ex) + { + ExceptionLog.Add(ex); + } + } - public string? FileFullPath { get; } - public string? FileName { get; } - public bool? FileIsDirectory { get; private set; } + public string? FileFullPath { get; } + public string? FileName { get; } + public bool? FileIsDirectory { get; private set; } - /// - /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. - /// - /// If successful, returns a path string formatted as 'X:\dir\file.ext' or 'X:\dir' - /// The path '{fullName}' was not found when querying a file handle. - /// Failed to query path from file handle. Insufficient memory to complete the operation. - /// Failed to query path from file handle. Invalid flags were specified for dwFlags. - private unsafe string TryGetFinalPath() - { - /// Return the normalized drive name. This is the default. - uint bufLength = (uint)short.MaxValue; - var buffer = Marshal.AllocHGlobal((int)bufLength); - PWSTR fullName = new((char*)buffer); - uint length = GetFinalPathNameByHandle(this, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + /// + /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. + /// + /// If successful, returns a path string formatted as 'X:\dir\file.ext' or 'X:\dir' + /// The path '{fullName}' was not found when querying a file handle. + /// Failed to query path from file handle. Insufficient memory to complete the operation. + /// Failed to query path from file handle. Invalid flags were specified for dwFlags. + private unsafe string TryGetFinalPath() + { + /// Return the normalized drive name. This is the default. + uint bufLength = (uint)short.MaxValue; + var buffer = Marshal.AllocHGlobal((int)bufLength); + PWSTR fullName = new((char*)buffer); + uint length = GetFinalPathNameByHandle(this, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); - if (length != 0) + if (length != 0) + { + while (length > bufLength) { - while (length > bufLength) - { - // buffer was too small. Reallocate buffer with size matched 'length' and try again - buffer = Marshal.ReAllocHGlobal(buffer, (IntPtr)length); - fullName = new((char*)buffer); + // buffer was too small. Reallocate buffer with size matched 'length' and try again + buffer = Marshal.ReAllocHGlobal(buffer, (IntPtr)length); + fullName = new((char*)buffer); - bufLength = GetFinalPathNameByHandle(SysHandleEx.ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); - } - return fullName.ToString(); + bufLength = GetFinalPathNameByHandle(SysHandleEx.ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); } - else - { - int error = Marshal.GetLastWin32Error(); - const int ERROR_PATH_NOT_FOUND = 3; - const int ERROR_NOT_ENOUGH_MEMORY = 8; - const int ERROR_INVALID_PARAMETER = 87; // 0x57 + return fullName.ToString(); + } + else + { + int error = Marshal.GetLastWin32Error(); + const int ERROR_PATH_NOT_FOUND = 3; + const int ERROR_NOT_ENOUGH_MEMORY = 8; + const int ERROR_INVALID_PARAMETER = 87; // 0x57 - /* Hold up. Let's free our memory before throwing exceptions. */ - Marshal.FreeHGlobal(buffer); + /* Hold up. Let's free our memory before throwing exceptions. */ + Marshal.FreeHGlobal(buffer); - throw error switch - { - ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{fullName}' was not found when querying a file handle.", fileName: fullName.ToString()), // Removable storage, deleted item, network shares, et cetera - ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation."), // unlikely, but possible if system has little free memory - ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags."), // possible only if FILE_NAME_NORMALIZED (0) is invalid - _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path."), - }; - } + throw error switch + { + ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{fullName}' was not found when querying a file handle.", fileName: fullName.ToString()), // Removable storage, deleted item, network shares, et cetera + ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation."), // unlikely, but possible if system has little free memory + ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags."), // possible only if FILE_NAME_NORMALIZED (0) is invalid + _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path."), + }; } } - } From c99a76ec6a5df2bb0503a2e4adb031512e981015 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 15 Oct 2022 01:40:08 -0700 Subject: [PATCH 056/306] docs: clarify summary of FindLockingHandles(string, ResultsFilter) --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 4368aef..c46d5e0 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -123,9 +123,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth } /// - /// Query the system's open handles; - /// Try to filter them to just files, optionally including handles for non-File and unidentified object types; - /// Filter "File" handles to only those whose full paths contain the query string. + /// Query the system's open handles, optionally including non-file handles or handles whose types could not be determined. /// /// /// When a query string is passed to this method, all "File" From 3c1b2caa12692d735d762825ef8ac921b74cfb4e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 15 Oct 2022 04:31:05 -0700 Subject: [PATCH 057/306] docs: finish unfinished xml doc --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 2c52499..85374f5 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -104,11 +104,10 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE /// /// Release the system handle.
/// ! WARNING !
- /// If the handle or a duplicate is in use by a driver or other kernel-level software, a function that accesses the now-invalid handle will cause a stopcode (AKA Blue Screen Of D). + /// If the handle or a duplicate is in use by a driver or other kernel-level software, a function that accesses the now-invalid handle will cause a stopcode (AKA Blue Screen Of Death). ///
/// - /// See Raymond Chen's devblog article - /// "Kernel handles are not reference-counted". + /// See Raymond Chen's devblog article "Kernel handles are not reference-counted". /// /// Failed to open process to duplicate and close object handle. public void UnlockSystemHandle() From 2f22c7e6628b024ec66fbc4fe5b981dd094d59cf Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 15 Oct 2022 04:31:49 -0700 Subject: [PATCH 058/306] docs: fix invalid reference of renamed enum --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index c46d5e0..b3e9feb 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -133,7 +133,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth /// /// By default, this method only returns handles for objects /// successfully identified as a file/directory ("File"). - /// and + /// and /// /// /// A list of SafeFileHandleEx objects. From 1236997caf936e20e62af5caa3456fdf6741ab9a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 15 Oct 2022 04:32:48 -0700 Subject: [PATCH 059/306] docs: clean up re-used docs to reflect new class --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 80ec16c..7c1b4dd 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,4 @@ -namespace deadlock_dotnet_sdk.Domain +namespace deadlock_dotnet_sdk.Domain { public class FileLockerEx { @@ -6,10 +6,11 @@ public class FileLockerEx /// /// Get or set the path of the file that is locked + /// public string Path { get; set; } /// - /// Get or set the List of Process objects that are locking the file + /// Get or set the List of handles that are locking the file /// public List Lockers { get; set; } @@ -27,8 +28,8 @@ public FileLockerEx() /// /// Initialize a new FileLocker /// - /// The path of the file - /// The List of Process objects that are locking the file + /// The path of the file or directory + /// The List of handles that are locking the file public FileLockerEx(string path, List lockers) { Path = path; From a8ac39b4459b07f648bdbb9af2eb5f04782b8722 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 15 Oct 2022 04:33:52 -0700 Subject: [PATCH 060/306] refactor: replace FileLockerEx.GetFileLockerEx() with constructor --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 7c1b4dd..def9a48 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -36,17 +36,21 @@ public FileLockerEx(string path, List lockers) Lockers = lockers; } - public static FileLockerEx GetFileLockerEx(string path, ResultsFilter filter = ResultsFilter.FilesOnly) + /// + /// TODO: + /// + /// The path of the file or directory + /// + public FileLockerEx(string path, ResultsFilter filter) { - return new( - path, - NativeMethods.FindLockingHandles(path, filter) - ); + Path = path; + Lockers = NativeMethods.FindLockingHandles(path, filter); } /// - /// Filters for + /// Filters for /// + /// TODO: rename to HandlesFilter [Flags] public enum ResultsFilter { From 47b1bb74495f5f0f36a4b11fd2d7e70939e76f3e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 15 Oct 2022 04:37:03 -0700 Subject: [PATCH 061/306] refactor: rename ResultsFilter to HandlesFilter --- deadlock-dotnet-sdk/DeadLock.cs | 18 ++++++++++++++++-- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 8 ++++---- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 8 ++++---- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index f4acae5..885d325 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -1,5 +1,6 @@ -using System.Diagnostics; +using System.Diagnostics; using deadlock_dotnet_sdk.Domain; +using HandlesFilter = deadlock_dotnet_sdk.Domain.FileLockerEx.HandlesFilter; namespace deadlock_dotnet_sdk { @@ -12,7 +13,12 @@ public class DeadLock ///
public bool RethrowExceptions { get; set; } - #endregion + /// + /// Property that specifies if any non-file/directory handles are to be included in FileLockerEx handle lists + /// + public HandlesFilter Filter { get; set; } = HandlesFilter.FilesOnly; + + #endregion Properties /// /// Default constructor @@ -31,6 +37,14 @@ public DeadLock(bool rethrowExceptions) RethrowExceptions = rethrowExceptions; } + public DeadLock(bool rethrowExceptions, HandlesFilter filter) + { + RethrowExceptions = rethrowExceptions; + Filter = filter; + } + + #region ProcessLocks + /// /// Retrieve the FileLocker object that contains a List of Process objects that are locking a file /// diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index def9a48..ad999f3 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,4 @@ -namespace deadlock_dotnet_sdk.Domain +namespace deadlock_dotnet_sdk.Domain { public class FileLockerEx { @@ -41,18 +41,18 @@ public FileLockerEx(string path, List lockers) /// /// The path of the file or directory /// - public FileLockerEx(string path, ResultsFilter filter) + public FileLockerEx(string path, HandlesFilter filter) { Path = path; Lockers = NativeMethods.FindLockingHandles(path, filter); } /// - /// Filters for + /// Filters for /// /// TODO: rename to HandlesFilter [Flags] - public enum ResultsFilter + public enum HandlesFilter { FilesOnly = 0, IncludeNonFiles = 1, diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index b3e9feb..0756d67 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -133,7 +133,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth /// /// By default, this method only returns handles for objects /// successfully identified as a file/directory ("File"). - /// and + /// and /// /// /// A list of SafeFileHandleEx objects. @@ -141,7 +141,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth /// /// This might be arduously slow... // TODO: Perhaps we should allow a new query without re-calling GetSystemHandleInfoEx(). - internal static List FindLockingHandles(string? query = null, ResultsFilter filter = ResultsFilter.FilesOnly) + internal static List FindLockingHandles(string? query = null, HandlesFilter filter = HandlesFilter.FilesOnly) { Process.EnterDebugMode(); // just in case @@ -157,14 +157,14 @@ bool Discard(SafeFileHandleEx h) /* Query for object type succeeded and the type is NOT File */ if (h.HandleObjectType != "File") { - return !filter.HasFlag(ResultsFilter.IncludeNonFiles); // When requested, keep non-File object handle. Else, discard. + return !filter.HasFlag(HandlesFilter.IncludeNonFiles); // When requested, keep non-File object handle. Else, discard. } // Discard handle if Query and file's path are not null and file's path does not contain query */ return (query is not null) && (h.FileFullPath is not null) && (!h.FileFullPath.Contains(query.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))); } else { - return !filter.HasFlag(ResultsFilter.IncludeFailedTypeQuery); // When requested, keep handle if the object type query failed. Else, discard. + return !filter.HasFlag(HandlesFilter.IncludeFailedTypeQuery); // When requested, keep handle if the object type query failed. Else, discard. } } } From 637471f9ee5e83ce266195359c9e2f5bf0741c2c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 15 Oct 2022 04:38:36 -0700 Subject: [PATCH 062/306] feat: add equivalent methods for handling handles to public API Ready for testing. Need a CLI project at the very least. --- deadlock-dotnet-sdk/DeadLock.cs | 207 +++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index 885d325..27aee13 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using deadlock_dotnet_sdk.Domain; using HandlesFilter = deadlock_dotnet_sdk.Domain.FileLockerEx.HandlesFilter; @@ -221,5 +221,210 @@ public async Task UnlockAsync(params string[] filePaths) await UnlockAsync(f); } } + + #endregion ProcessLocks + + #region HandleLocks + + /// + /// Retrieve the object that contains a List of handles that are locking a file. + /// + /// The full or partial path of a file or directory. + /// By default, only handles whose object's Type is confirmed to "File" are returned. Optionally, handles for data pipes, printers, and other Types can be included, in addition to handles whose object Type could not be identified for some reason. + /// The object that contains the and a list of handles matching the . + public FileLockerEx FindLockingHandles(string filePath) + { + return new(filePath, Filter); + } + + /// + /// Retrieve the List of objects for one or multiple files and/or directories + /// + /// By default, only handles whose object's Type is confirmed to "File" are returned. Optionally, handles for data pipes, printers, and other Types can be included, in addition to handles whose object Type could not be identified for some reason. + /// The List of objects that contains a List of handles that are locking one or multiple files and/or directories + public List FindLockingHandles(params string[] filePaths) + { + List fileLockers = new(); + if (filePaths.Length == 1) + { + fileLockers.Add(FindLockingHandles(filePaths[0])); + } + else + { + foreach (string filePath in filePaths) + { + fileLockers.Add(FindLockingHandles(filePath)); + } + } + return fileLockers; + } + + /// + /// Asynchronously retrieve the object that contains a List of handles that are locking a file or directory + /// + /// The full or partial path of a file + /// By default, only handles whose object's Type is confirmed to "File" are returned. Optionally, handles for data pipes, printers, and other Types can be included, in addition to handles whose object Type could not be identified for some reason. + /// The object that contains a List of handles that are locking a file or directory + public static async Task FindLockingHandlesAsync(string filePath) + { + FileLockerEx fileLocker = new(); + + await Task.Run(() => + { + fileLocker = new FileLockerEx(filePath, + NativeMethods.FindLockingHandles(filePath)); + }); + + return fileLocker; + } + + /// + /// Asynchronously retrieve the List of objects for one or multiple files and/or directories + /// + /// The full or partial paths of files and/or directories + /// The List of objects that contain the handles that are locking a file or directory + public async Task> FindLockingHandlesAsync(params string[] filePaths) + { + List fileLockers = new(); + + await Task.Run(() => + { + foreach (string filePath in filePaths) + { + fileLockers.Add(new FileLockerEx(filePath, + NativeMethods.FindLockingHandles(filePath, Filter))); + } + }); + + return fileLockers; + } + + /// + /// Unlock a File or Directory by leveraging undocumented kernel functions to make all processes release their handles of the file + /// Release the system handle. + /// ! WARNING ! + /// ! If a handle or a duplicate of a handle is in use by a driver or other kernel-level software, a function that accesses the now-invalid handle can cause a stopcode (AKA Blue Screen Of Death).
+ /// ! Be very wary of potentially destabilizing your or your end-user's system!
+ /// ! Even more so if you used the or filter flags + ///
+ /// The that contains the List of handles that should be released + public void UnlockEx(FileLockerEx fileLocker) + { + foreach (SafeFileHandleEx h in fileLocker.Lockers) + { + if (h.IsClosed && h.IsInvalid) continue; + try + { + h.UnlockSystemHandle(); + } + catch (Exception) when (!RethrowExceptions) { } + } + } + + /// + /// Unlock one or more files or directories by directing each handle's owner process to release the handle + /// ! WARNING ! + /// ! If a handle or a duplicate of a handle is in use by a driver or other kernel-level software, a function that accesses the now-invalid handle can cause a stopcode (AKA Blue Screen Of Death).
+ /// ! Be very wary of potentially destabilizing your or your end-user's system!
+ /// ! Even more so if you used the or filter flags + ///
+ /// The objects that contain the List of handles that are locking a file or directory + public void UnlockEx(params FileLockerEx[] fileLockers) + { + foreach (FileLockerEx fileLocker in fileLockers) + { + UnlockEx(fileLocker); + } + } + + /// + /// Unlock a File or Directory asynchronously by directing each handle's owner process to release the handle + /// + /// The that contains the List of handles that should be released + public async Task UnlockExAsync(FileLockerEx fileLocker) + { + await Task.Run(() => + { + foreach (SafeFileHandleEx h in fileLocker.Lockers) + { + if (h.IsClosed && h.IsInvalid) continue; + try + { + h.UnlockSystemHandle(); + } + catch (Exception) when (!RethrowExceptions) { } + } + }); + } + + /// + /// Unlock one or more files/directories asynchronously by directing each handle's owner process to release the relevant handles + /// + /// The objects that contain the List of handles that are locking a file/directory + public async Task UnlockExAsync(params FileLockerEx[] fileLockers) + { + await Task.Run(() => + { + foreach (FileLockerEx fileLocker in fileLockers) + { + foreach (SafeFileHandleEx h in fileLocker.Lockers) + { + if (h.IsClosed && h.IsInvalid) continue; + try + { + h.UnlockSystemHandle(); + } + catch (Exception) when (!RethrowExceptions) { } + } + } + }); + } + + /// + /// Unlock a file/directory without retrieving the List of FileLockerEx objects + /// + /// The path of the file/directory that should be unlocked + public void UnlockEx(string filePath) + { + FileLockerEx fileLocker = FindLockingHandles(filePath); + UnlockEx(fileLocker); + } + + /// + /// Unlock a file/directory without retrieving the List of FileLockerEx objects asynchronously + /// + /// The path of the file/directory that should be unlocked + public async Task UnlockExAsync(string filePath) + { + FileLockerEx locker = await FindLockingHandlesAsync(filePath); + await UnlockExAsync(locker); + } + + /// + /// Unlock one or more files/directories without retrieving the List of FileLockerEx objects + /// + /// The full or partial paths of the files/directories that should be unlocked + public void UnlockEx(params string[] filePaths) + { + foreach (FileLocker fileLocker in FindLockingProcesses(filePaths)) + { + Unlock(fileLocker); + } + } + + /// + /// Unlock one or more files/directories without retrieving the List of FileLockerEx objects asynchronously + /// + /// The full or partial paths of the files/directories that should be unlocked + public async Task UnlockExAsync(params string[] filePaths) + { + List fileLockers = await FindLockingHandlesAsync(filePaths); + foreach (FileLockerEx f in fileLockers) + { + await UnlockExAsync(f); + } + } + + #endregion HandleLocks } } From f203ee1c1b8395caaefe9f5312201ee3e0fdbfc2 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 19 Oct 2022 23:01:04 -0700 Subject: [PATCH 063/306] refactor: resolve warning PInvoke004 --- deadlock-dotnet-sdk/NativeMethods.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index 55e45ef..c9882da 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -18,7 +18,7 @@ //// Foundation //// BOOLEAN HANDLE -DUPLICATE_CLOSE_SOURCE +DUPLICATE_HANDLE_OPTIONS //// Restart Manager //// RM_PROCESS_INFO @@ -41,11 +41,8 @@ RmStartSession //// System.Threading //// PEB -ProcessBasicInformation PROCESS_BASIC_INFORMATION PROCESSINFOCLASS -PROCESS_QUERY_LIMITED_INFORMATION -PROCESS_VM_READ //// System.WindowsProgramming //// PUBLIC_OBJECT_TYPE_INFORMATION From 7cd5501b403138661cd5b12ff491238a6ff0423e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 19 Oct 2022 23:03:25 -0700 Subject: [PATCH 064/306] refactor: set structs, fields as readonly; ignore CS0649 --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 14 ++++---- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 35 +++++++++++--------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs index 31af45f..d81d73b 100644 --- a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs +++ b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs @@ -198,14 +198,15 @@ public override bool Equals(object? obj) => obj is PPS_POST_PROCESS_INIT_ROUTINE pPS_POST_PROCESS_INIT_ROUTINE && Equals(pPS_POST_PROCESS_INIT_ROUTINE); } +#pragma warning disable CS0649 [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Windows.CsWin32", "0.2.46-beta+0e9cbfc7b9")] - internal struct PROCESS_BASIC_INFORMATION + internal readonly struct PROCESS_BASIC_INFORMATION { - internal unsafe void* Reserved1; - internal unsafe PEB* PebBaseAddress; - internal __IntPtr_2 Reserved2; - internal nuint UniqueProcessId; - internal unsafe void* Reserved3; + internal readonly unsafe void* Reserved1; + internal readonly unsafe PEB* PebBaseAddress; + internal readonly __IntPtr_2 Reserved2; + internal readonly nuint UniqueProcessId; + internal readonly unsafe void* Reserved3; internal struct __IntPtr_2 { @@ -229,5 +230,6 @@ internal struct __IntPtr_2 internal Span AsSpan() => MemoryMarshal.CreateSpan(ref _0, 2); } } +#pragma warning restore CS0649 } } diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 0756d67..2897a27 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -252,15 +252,19 @@ ref returnLength /// The SYSTEM_HANDLE_INFORMATION_EX /// struct is 0x24 or 0x38 bytes in 32-bit and 64-bit Windows, respectively. However, Handles is a variable-length array. ///
- public unsafe struct SYSTEM_HANDLE_INFORMATION_EX + public unsafe readonly struct SYSTEM_HANDLE_INFORMATION_EX { +#pragma warning disable CS0649 + /// /// As documented unofficially, NumberOfHandles is a 4-byte or 8-byte ULONG_PTR in 32-bit and 64-bit Windows, respectively.
/// This is not to be confused with uint* or ulong*. ///
- public UIntPtr NumberOfHandles; - public UIntPtr Reserved; - public SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* Handles; + public readonly UIntPtr NumberOfHandles; + public readonly UIntPtr Reserved; + public readonly SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* Handles; + +#pragma warning restore CS0649 public Span AsSpan() => new(Handles, (int)NumberOfHandles); public static implicit operator Span(SYSTEM_HANDLE_INFORMATION_EX value) => value.AsSpan(); @@ -279,30 +283,31 @@ public unsafe struct SYSTEM_HANDLE_INFORMATION_EX /// SystemHandleInformation (0x10). /// This inline doc was supplemented by ProcessHacker's usage of this struct. ///
- public struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX + public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { - public unsafe void* Object; +#pragma warning disable CS0649 + public readonly unsafe void* Object; /// /// ULONG_PTR, cast to HANDLE, int, or uint /// - public HANDLE UniqueProcessId; + public readonly HANDLE UniqueProcessId; /// /// ULONG_PTR, cast to HANDLE /// - public HANDLE HandleValue; + public readonly HANDLE HandleValue; /// /// This is a bitwise "Flags" data type. /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. /// - public ACCESS_MASK GrantedAccess; // ULONG - public ushort CreatorBackTraceIndex; // USHORT + public readonly ACCESS_MASK GrantedAccess; // ULONG + public readonly ushort CreatorBackTraceIndex; // USHORT /// ProcessHacker defines a little over a dozen handle-able object types. - public ushort ObjectTypeIndex; // USHORT + public readonly ushort ObjectTypeIndex; // USHORT /// - public uint HandleAttributes; // ULONG -#pragma warning disable RCS1213 - private readonly uint Reserved; // Remove unused field declaration. csharp(RCS1213) | Roslynator -#pragma warning restore RCS1213 + public readonly uint HandleAttributes; // ULONG +#pragma warning disable RCS1213 // Remove unused field declaration. csharp(RCS1213) | Roslynator + private readonly uint Reserved; +#pragma warning restore RCS1213, CS0649 /// /// Get the Type of the object as a string From 216a1efd3d137cae42ebe17f7cca66c9ee08cf4d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 19 Oct 2022 23:05:32 -0700 Subject: [PATCH 065/306] refactor: override GetHashCode() in pseudo-funcptr resolves warning CS0659 --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs index d81d73b..00d1537 100644 --- a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs +++ b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs @@ -196,6 +196,8 @@ public static PPS_POST_PROCESS_INIT_ROUTINE FromPointer(IntPtr v) public override bool Equals(object? obj) => obj is PPS_POST_PROCESS_INIT_ROUTINE pPS_POST_PROCESS_INIT_ROUTINE && Equals(pPS_POST_PROCESS_INIT_ROUTINE); + + public override int GetHashCode() => Value.GetHashCode(); } #pragma warning disable CS0649 From e52fdbdbc6ae9611b012763eaddd8c5502135a2d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 19 Oct 2022 23:07:18 -0700 Subject: [PATCH 066/306] refactor: disable warning CS0169 for reserved field --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 2897a27..f39337a 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -305,9 +305,9 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX public readonly ushort ObjectTypeIndex; // USHORT /// public readonly uint HandleAttributes; // ULONG -#pragma warning disable RCS1213 // Remove unused field declaration. csharp(RCS1213) | Roslynator +#pragma warning disable RCS1213, CS0169 // Remove unused field declaration. csharp(RCS1213) | Roslynator private readonly uint Reserved; -#pragma warning restore RCS1213, CS0649 +#pragma warning restore RCS1213, CS0649, CS0169 /// /// Get the Type of the object as a string From 3c6cc0649b9da224c46c6b23caf02545d4110085 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 24 Oct 2022 00:18:11 -0700 Subject: [PATCH 067/306] refactor: move EnterDebugMode call to class FileLockerEx --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 20 +++++++++++++++++--- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 6 ++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index ad999f3..764d2e8 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -37,13 +37,27 @@ public FileLockerEx(string path, List lockers) } /// - /// TODO: + /// Invoke QuerySystemInformationEx() to get SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX + /// objects, optionally including handles of non-file and/or unidentified object types /// /// The path of the file or directory - /// - public FileLockerEx(string path, HandlesFilter filter) + /// Include non-file handles and handles of unidentified object types. Default: Files Only + /// Assign True to rethrow exceptions + /// DeadLock was denied debug permissions to access system, service, and admin processes. By default, Administrators are allowed this permission. Try running as Administrator. + public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions = false) { Path = path; + + try + { + System.Diagnostics.Process.EnterDebugMode(); + } + catch (Exception e) + { + if (rethrowExceptions) + throw new UnauthorizedAccessException("DeadLock was denied debug permissions to access system, service, and admin processes. For debug access, try running this app as Administrator or contact your technician.", e); + } + Lockers = NativeMethods.FindLockingHandles(path, filter); } diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index f39337a..52f7406 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -24,7 +24,7 @@ namespace deadlock_dotnet_sdk.Domain; /// /// Collection of native methods /// -internal static partial class NativeMethods +internal static class NativeMethods { #region Variables @@ -139,12 +139,10 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth /// A list of SafeFileHandleEx objects. /// When requested, handles for non-file or unidentified objects will be included with file-specific properties nulled. /// - /// This might be arduously slow... + /// SeDebugMode may be required for data from system and service processes. Restart app as admin and call The full or partial path of a file or directory. /// By default, only handles whose object's Type is confirmed to "File" are returned. Optionally, handles for data pipes, printers, and other Types can be included, in addition to handles whose object Type could not be identified for some reason. /// The object that contains the and a list of handles matching the . - public FileLockerEx FindLockingHandles(string filePath) + public FileLockerEx FindLockingHandles(string filePath, HandlesFilter filter = HandlesFilter.FilesOnly) { - return new(filePath, Filter); + try + { + return new(filePath, filter, RethrowExceptions); + } + catch (UnauthorizedAccessException) when (!RethrowExceptions) + { return new(); } } /// @@ -242,18 +236,18 @@ public FileLockerEx FindLockingHandles(string filePath) /// /// By default, only handles whose object's Type is confirmed to "File" are returned. Optionally, handles for data pipes, printers, and other Types can be included, in addition to handles whose object Type could not be identified for some reason. /// The List of objects that contains a List of handles that are locking one or multiple files and/or directories - public List FindLockingHandles(params string[] filePaths) + public List FindLockingHandles(HandlesFilter filter = HandlesFilter.FilesOnly, params string[] filePaths) { List fileLockers = new(); if (filePaths.Length == 1) { - fileLockers.Add(FindLockingHandles(filePaths[0])); + fileLockers.Add(FindLockingHandles(filePaths[0], filter)); } else { foreach (string filePath in filePaths) { - fileLockers.Add(FindLockingHandles(filePath)); + fileLockers.Add(FindLockingHandles(filePath, filter)); } } return fileLockers; @@ -265,14 +259,14 @@ public List FindLockingHandles(params string[] filePaths) /// The full or partial path of a file /// By default, only handles whose object's Type is confirmed to "File" are returned. Optionally, handles for data pipes, printers, and other Types can be included, in addition to handles whose object Type could not be identified for some reason. /// The object that contains a List of handles that are locking a file or directory - public static async Task FindLockingHandlesAsync(string filePath) + public static async Task FindLockingHandlesAsync(string filePath, HandlesFilter filter = HandlesFilter.FilesOnly) { FileLockerEx fileLocker = new(); await Task.Run(() => { fileLocker = new FileLockerEx(filePath, - NativeMethods.FindLockingHandles(filePath)); + NativeMethods.FindLockingHandles(filePath, filter)); }); return fileLocker; @@ -283,7 +277,7 @@ await Task.Run(() => /// /// The full or partial paths of files and/or directories /// The List of objects that contain the handles that are locking a file or directory - public async Task> FindLockingHandlesAsync(params string[] filePaths) + public static async Task> FindLockingHandlesAsync(HandlesFilter filter = HandlesFilter.FilesOnly, params string[] filePaths) { List fileLockers = new(); @@ -292,7 +286,7 @@ await Task.Run(() => foreach (string filePath in filePaths) { fileLockers.Add(new FileLockerEx(filePath, - NativeMethods.FindLockingHandles(filePath, Filter))); + NativeMethods.FindLockingHandles(filePath, filter))); } }); @@ -418,7 +412,7 @@ public void UnlockEx(params string[] filePaths) /// The full or partial paths of the files/directories that should be unlocked public async Task UnlockExAsync(params string[] filePaths) { - List fileLockers = await FindLockingHandlesAsync(filePaths); + List fileLockers = await FindLockingHandlesAsync(HandlesFilter.FilesOnly, filePaths); foreach (FileLockerEx f in fileLockers) { await UnlockExAsync(f); From a6c31d25557303348a4f12cfec4d97fb75771f3e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 27 Oct 2022 23:02:25 -0700 Subject: [PATCH 069/306] refactor: override SafeFileHandleEx ToString with JSON serialization --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 6dbefd1..fdd10c2 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using System.Text.Json; using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; using static deadlock_dotnet_sdk.Domain.NativeMethods; @@ -101,4 +102,9 @@ private unsafe string TryGetFinalPath() }; } } + + public override string ToString() + { + return JsonSerializer.Serialize(this, options: new() { WriteIndented = true }); + } } From 7616902608094dc1a4c8f20f7078541c2948f18d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 27 Oct 2022 23:13:01 -0700 Subject: [PATCH 070/306] refactor: replace ToArray with Add and separate instantiation --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 52f7406..149fe8a 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -143,9 +143,16 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth // TODO: Perhaps we should allow a new query without re-calling GetSystemHandleInfoEx(). internal static List FindLockingHandles(string? query = null, HandlesFilter filter = HandlesFilter.FilesOnly) { - List? handles = GetSystemHandleInfoEx().ToArray().Cast().ToList(); + List? handles = new(); + + foreach (var h in GetSystemHandleInfoEx()) + { + handles.Add(new SafeFileHandleEx(h)); + } + handles.RemoveAll(item => Discard(h: item)); handles.Sort((a, b) => a.ProcessId.CompareTo(b.ProcessId)); + return handles; bool Discard(SafeFileHandleEx h) From ab3435fe259df0ba8224c71f3a629f82635c40ea Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 27 Oct 2022 23:32:44 -0700 Subject: [PATCH 071/306] refactor: add method CheckAccess for debugging purposes docs: add missing quotation mark --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 149fe8a..021d3f3 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -139,7 +139,7 @@ internal static IEnumerable FindLockingProcesses(string path, bool reth /// A list of SafeFileHandleEx objects. /// When requested, handles for non-file or unidentified objects will be included with file-specific properties nulled. /// - /// SeDebugMode may be required for data from system and service processes. Restart app as admin and call SeDebugMode may be required for data from system and service processes. Restart app as admin and call . // TODO: Perhaps we should allow a new query without re-calling GetSystemHandleInfoEx(). internal static List FindLockingHandles(string? query = null, HandlesFilter filter = HandlesFilter.FilesOnly) { @@ -243,6 +243,8 @@ ref returnLength SYSTEM_HANDLE_INFORMATION_EX retVal = *pSysInfoBuffer; + retVal.CheckAccess(); + Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); Marshal.FreeHGlobal((IntPtr)returnLength); @@ -272,6 +274,14 @@ public unsafe readonly struct SYSTEM_HANDLE_INFORMATION_EX #pragma warning restore CS0649 public Span AsSpan() => new(Handles, (int)NumberOfHandles); + + /// + /// Test for memory access. System.AccessViolationException due to these values being in a protected memory range is a problem. + /// + internal void CheckAccess() + { + Console.WriteLine(Handles[0].HandleValue); + } public static implicit operator Span(SYSTEM_HANDLE_INFORMATION_EX value) => value.AsSpan(); } From d59487b423e3f3eeea8828e74f857ca705ad5fb6 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 27 Oct 2022 23:36:28 -0700 Subject: [PATCH 072/306] refactor: add parameter to pass out WarningException refactor: add Refresh for FileLockerEx Lockers list --- deadlock-dotnet-sdk/DeadLock.cs | 22 +++++++++++++-------- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 23 ++++++++++++++++++---- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index c678f96..f96517d 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System.ComponentModel; +using System.Diagnostics; using deadlock_dotnet_sdk.Domain; using HandlesFilter = deadlock_dotnet_sdk.Domain.FileLockerEx.HandlesFilter; @@ -221,11 +222,12 @@ public async Task UnlockAsync(params string[] filePaths) /// The full or partial path of a file or directory. /// By default, only handles whose object's Type is confirmed to "File" are returned. Optionally, handles for data pipes, printers, and other Types can be included, in addition to handles whose object Type could not be identified for some reason. /// The object that contains the and a list of handles matching the . - public FileLockerEx FindLockingHandles(string filePath, HandlesFilter filter = HandlesFilter.FilesOnly) + public FileLockerEx FindLockingHandles(string filePath, HandlesFilter filter, out WarningException? warningException) { + warningException = null; try { - return new(filePath, filter, RethrowExceptions); + return new(filePath, filter, RethrowExceptions, out warningException); } catch (UnauthorizedAccessException) when (!RethrowExceptions) { return new(); } @@ -236,18 +238,22 @@ public FileLockerEx FindLockingHandles(string filePath, HandlesFilter filter = H /// /// By default, only handles whose object's Type is confirmed to "File" are returned. Optionally, handles for data pipes, printers, and other Types can be included, in addition to handles whose object Type could not be identified for some reason. /// The List of objects that contains a List of handles that are locking one or multiple files and/or directories - public List FindLockingHandles(HandlesFilter filter = HandlesFilter.FilesOnly, params string[] filePaths) + public List FindLockingHandles(HandlesFilter filter, List warnings, params string[] filePaths) { List fileLockers = new(); + warnings = new(); + if (filePaths.Length == 1) { - fileLockers.Add(FindLockingHandles(filePaths[0], filter)); + fileLockers.Add(FindLockingHandles(filePaths[0], filter, out WarningException? warningException)); + if (warningException != null) warnings.Add(warningException); } else { foreach (string filePath in filePaths) { - fileLockers.Add(FindLockingHandles(filePath, filter)); + fileLockers.Add(FindLockingHandles(filePath, filter, out WarningException? warningException)); + if (warningException != null) warnings.Add(warningException); } } return fileLockers; @@ -265,7 +271,7 @@ public static async Task FindLockingHandlesAsync(string filePath, await Task.Run(() => { - fileLocker = new FileLockerEx(filePath, + fileLocker = new(filePath, NativeMethods.FindLockingHandles(filePath, filter)); }); @@ -380,7 +386,7 @@ await Task.Run(() => /// The path of the file/directory that should be unlocked public void UnlockEx(string filePath) { - FileLockerEx fileLocker = FindLockingHandles(filePath); + FileLockerEx fileLocker = FindLockingHandles(filePath, HandlesFilter.FilesOnly, out _); UnlockEx(fileLocker); } diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 764d2e8..1cf962f 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,6 @@ -namespace deadlock_dotnet_sdk.Domain +using System.ComponentModel; + +namespace deadlock_dotnet_sdk.Domain { public class FileLockerEx { @@ -8,6 +10,7 @@ public class FileLockerEx /// Get or set the path of the file that is locked ///
public string Path { get; set; } + public HandlesFilter Filter { get; } /// /// Get or set the List of handles that are locking the file @@ -41,12 +44,16 @@ public FileLockerEx(string path, List lockers) /// objects, optionally including handles of non-file and/or unidentified object types /// /// The path of the file or directory - /// Include non-file handles and handles of unidentified object types. Default: Files Only + /// Include non-file handles and handles of unidentified object types. /// Assign True to rethrow exceptions + /// When not null, DeadLock failed to grant debug permissions to the current thread failed. See inner Exceptions for more information. /// DeadLock was denied debug permissions to access system, service, and admin processes. By default, Administrators are allowed this permission. Try running as Administrator. - public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions = false) + /// If any errors occur in the context of an individual handle, + public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions, out WarningException? warningException) { + warningException = null; Path = path; + Filter = filter; try { @@ -54,8 +61,11 @@ public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions = } catch (Exception e) { + var uae = new UnauthorizedAccessException("DeadLock was denied debug permissions to access system, service, and admin processes. For debug access, try running this app as Administrator or contact your technician.", e); if (rethrowExceptions) - throw new UnauthorizedAccessException("DeadLock was denied debug permissions to access system, service, and admin processes. For debug access, try running this app as Administrator or contact your technician.", e); + throw uae; + else + warningException = new("Failed to enable Debug Mode for greater access to processes which do not belong to the current user or admin.", uae); } Lockers = NativeMethods.FindLockingHandles(path, filter); @@ -72,5 +82,10 @@ public enum HandlesFilter IncludeNonFiles = 1, IncludeFailedTypeQuery = 2 } + + public void Refresh() + { + Lockers = NativeMethods.FindLockingHandles(Path, Filter); + } } } From 79397ec43b869f1027e872142dd852cb96f52728 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 04:41:55 -0800 Subject: [PATCH 073/306] refactor: move CsWin32 supplements to new files todo: move SYSTEM_HANDLE_ definitions to CsWin32 namespace --- deadlock-dotnet-sdk/CsWin32_NativeMethods.cs | 237 ------------------ deadlock-dotnet-sdk/NativeMethods.txt | 21 +- .../Windows.Win32/Foundation/HANDLE.cs | 31 +++ .../Windows.Win32/Foundation/NTSTATUS.cs | 16 ++ .../Windows.Win32/Foundation/PWSTR.cs | 14 ++ .../Foundation/UNICODE_STRING.cs | 19 ++ .../Windows.Win32/GENERIC_MAPPING.cs | 10 + .../Windows.Win32/OBJECT_TYPES_INFORMATION.cs | 26 ++ .../Windows.Win32/OBJECT_TYPE_INFORMATION.cs | 94 +++++++ deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs | 127 ++++++++++ deadlock-dotnet-sdk/Windows.Win32/SIZE_T.cs | 19 ++ .../System/Threading/KPRIORITY.cs | 16 ++ .../Windows.Win32/System/Threading/PEB.cs | 161 ++++++++++++ .../Threading/PROCESS_BASIC_INFORMATION.cs | 92 +++++++ .../Threading/PS_POST_PROCESS_INIT_ROUTINE.cs | 49 ++++ .../OBJECT_INFORMATION_CLASS.cs | 32 +++ .../SYSTEM_INFORMATION_CLASS.cs | 20 ++ 17 files changed, 746 insertions(+), 238 deletions(-) delete mode 100644 deadlock-dotnet-sdk/CsWin32_NativeMethods.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/Foundation/PWSTR.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/GENERIC_MAPPING.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPES_INFORMATION.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPE_INFORMATION.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/SIZE_T.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/KPRIORITY.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PS_POST_PROCESS_INIT_ROUTINE.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs diff --git a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs b/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs deleted file mode 100644 index 00d1537..0000000 --- a/deadlock-dotnet-sdk/CsWin32_NativeMethods.cs +++ /dev/null @@ -1,237 +0,0 @@ -/// This file supplements code generated by CsWin32 -using System.ComponentModel; -using System.Runtime.InteropServices; -using NTSTATUS_plus = PInvoke.NTSTATUS; -using NTStatusException = PInvoke.NTStatusException; - -namespace Windows.Win32 -{ - namespace Foundation - { - internal partial struct HANDLE : IComparable - { - public static implicit operator HANDLE(nuint v) => new((nint)v); - public static implicit operator nuint(HANDLE v) => (nuint)(nint)v.Value; - - public static implicit operator HANDLE(nint v) => new(v); - public static implicit operator nint(HANDLE v) => v.Value; - - /// - /// Close the handle via the CloseHandle function - /// - /// - /// If the application is running under a debugger, the function will throw an - /// exception if it receives either a handle value that is not valid or a - /// pseudo-handle value. This can happen if you close a handle twice, or if you - /// call CloseHandle on a handle returned by the FindFirstFile function instead - /// of calling the FindClose function. - /// - public void Close() - { - if (!PInvoke.CloseHandle(this)) - throw new Win32Exception(); - } - - public int CompareTo(HANDLE other) => Value.CompareTo(other); - } - - internal readonly partial struct NTSTATUS - { - public bool IsSuccessful => SeverityCode == Severity.Success; - - public NTStatusException GetNTStatusException() => new(this); - - public static implicit operator NTSTATUS_plus(NTSTATUS v) => new(v.Value); - public static implicit operator NTSTATUS(NTSTATUS_plus v) => new(v.AsInt32); - } - - internal unsafe readonly partial struct PWSTR : IDisposable - { - /// - /// Free the PWSTR's memory with Marshal.FreeHGlobal(IntPtr) - /// - public void Dispose() => Marshal.FreeHGlobal((IntPtr)Value); - - public static implicit operator PWSTR(IntPtr v) => new((char*)v); - } - - internal partial struct UNICODE_STRING - { - /// - /// Allocates a managed string and copies a specified number of characters from an unmanaged Unicode string into it. - /// - public unsafe string ToStringLength() => Marshal.PtrToStringUni((IntPtr)Buffer.Value, Length); - public string? ToStringZ() => Buffer.ToString(); - public static explicit operator string(UNICODE_STRING v) => v.ToStringLength(); - } - } - - namespace Security - { - /// - /// A simple placeholder for dotnet/PInvoke's ACCESS_MASK struct - /// - /// Process access modifiers are found in Windows.Win32.System.Threading.PROCESS_ACCESS_RIGHTS - public struct ACCESS_MASK - { - public ACCESS_MASK(uint v) - { - Value = v; - } - - public uint Value; - - public static implicit operator ACCESS_MASK(uint v) => new(v); - public static implicit operator uint(ACCESS_MASK v) => v.Value; - - public const uint DELETE = 0x00010000; - public const uint READ_CONTROL = 0x00020000; - public const uint WRITE_DAC = 0x00040000; - public const uint WRITE_OWNER = 0x00080000; - public const uint SYNCHRONIZE = 0x00100000; - - #region StandardAccess - public const uint STANDARD_RIGHTS_REQUIRED = DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER; - - public const uint STANDARD_RIGHTS_READ = READ_CONTROL; - public const uint STANDARD_RIGHTS_WRITE = READ_CONTROL; - public const uint STANDARD_RIGHTS_EXECUTE = READ_CONTROL; - - public const uint STANDARD_RIGHTS_ALL = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE; - - #endregion StandardAccess - - public const uint SPECIFIC_RIGHTS_ALL = 0x0000FFFF; - - /// - /// AccessSystemAcl access type - /// - public const uint ACCESS_SYSTEM_SECURITY = 0x01000000; - - /// These are the generic rights. - #region GenericRights - public const uint GENERIC_READ = 0x80000000; - public const uint GENERIC_WRITE = 0x40000000; - public const uint GENERIC_EXECUTE = 0x20000000; - public const uint GENERIC_ALL = 0x10000000; - - #endregion GenericRights - /// - /// MaximumAllowed access type - /// - public const uint MAXIMUM_ALLOWED = 0x02000000; - } - } - - namespace System.WindowsProgramming - { - // generated definition lacks SystemHandleInformation, SystemExtendedHandleInformation - internal enum SYSTEM_INFORMATION_CLASS - { - SystemBasicInformation = 0, - SystemPerformanceInformation = 2, - SystemTimeOfDayInformation = 3, - SystemProcessInformation = 5, - SystemProcessorPerformanceInformation = 8, - SystemHandleInformation = 16, - SystemInterruptInformation = 23, - SystemExceptionInformation = 33, - SystemRegistryQuotaInformation = 37, - SystemLookasideInformation = 45, - SystemExtendedHandleInformation = 64, - SystemCodeIntegrityInformation = 103, - SystemPolicyInformation = 134 - } - - internal enum OBJECT_INFORMATION_CLASS - { - ObjectBasicInformation = 0, - ObjectNameInformation = 1, - ObjectTypeInformation = 2, - } - } - - namespace System.Threading - { - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - internal unsafe delegate void PS_POST_PROCESS_INIT_ROUTINE(); - - /// - /// Function Pointer workaround. C# 9's function pointers are only allowed in - /// local scope. - /// - /// - /// The pointer returned by the Marshal methods is typically a "Thunk" rather - /// than a handle. If the .NET runtime sees a thunk was already created, it - /// will re-use that thunk. - /// source: - /// - internal struct PPS_POST_PROCESS_INIT_ROUTINE : IEquatable - { - public IntPtr Value; - - public PPS_POST_PROCESS_INIT_ROUTINE(PS_POST_PROCESS_INIT_ROUTINE initRoutine) - { - Value = Marshal.GetFunctionPointerForDelegate(initRoutine); - } - - public static PPS_POST_PROCESS_INIT_ROUTINE FromPointer(IntPtr v) - { - try - { - _ = Marshal.GetDelegateForFunctionPointer(v); - return new() { Value = v }; - } - catch (Exception) - { - // not a delegate or open generic type - // or ptr is null - return new() { Value = IntPtr.Zero }; - } - } - - public static explicit operator PPS_POST_PROCESS_INIT_ROUTINE(IntPtr v) => FromPointer(v); - - public bool Equals(PPS_POST_PROCESS_INIT_ROUTINE other) => Value == other.Value; - - public override bool Equals(object? obj) - => obj is PPS_POST_PROCESS_INIT_ROUTINE pPS_POST_PROCESS_INIT_ROUTINE && Equals(pPS_POST_PROCESS_INIT_ROUTINE); - - public override int GetHashCode() => Value.GetHashCode(); - } - -#pragma warning disable CS0649 - [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Windows.CsWin32", "0.2.46-beta+0e9cbfc7b9")] - internal readonly struct PROCESS_BASIC_INFORMATION - { - internal readonly unsafe void* Reserved1; - internal readonly unsafe PEB* PebBaseAddress; - internal readonly __IntPtr_2 Reserved2; - internal readonly nuint UniqueProcessId; - internal readonly unsafe void* Reserved3; - - internal struct __IntPtr_2 - { - internal IntPtr _0, _1; - - /// Always 2. - internal readonly int Length => 2; - - /// - /// Gets a ref to an individual element of the inline array. - /// ⚠ Important ⚠: When this struct is on the stack, do not let the returned reference outlive the stack frame that defines it. - /// - internal ref IntPtr this[int index] => ref AsSpan()[index]; - - /// - /// Gets this inline array as a span. - /// - /// - /// ⚠ Important ⚠: When this struct is on the stack, do not let the returned span outlive the stack frame that defines it. - /// - internal Span AsSpan() => MemoryMarshal.CreateSpan(ref _0, 2); - } - } -#pragma warning restore CS0649 - } -} diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index c9882da..f8e9076 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -24,14 +24,18 @@ DUPLICATE_HANDLE_OPTIONS RM_PROCESS_INFO //// PInvoke //// +CheckTokenMembership CloseHandle DuplicateHandle GetFinalPathNameByHandle IsWow64Process -NtQueryObject NtQueryInformationProcess +NtQueryObject NtQuerySystemInformation OpenProcess +OpenProcessToken +OpenThread +OpenThreadToken QueryFullProcessImageName ReadProcessMemory RmEndSession @@ -39,6 +43,10 @@ RmGetList RmRegisterResources RmStartSession +//// System.Memory //// +MEMORY_BASIC_INFORMATION32 +MEMORY_BASIC_INFORMATION64 + //// System.Threading //// PEB PROCESS_BASIC_INFORMATION @@ -47,3 +55,14 @@ PROCESSINFOCLASS //// System.WindowsProgramming //// PUBLIC_OBJECT_TYPE_INFORMATION +//// does not exist in Win32Metadata //// +//LPCVOID +//SIZE_T +//OBJECT_TYPES_INFORMATION +//OBJECT_TYPE_INFORMATION + +//// PInvoke005: This API is only available when targeting a specific CPU architecture. AnyCPU cannot generate this API. //// +//MEMORY_BASIC_INFORMATION +//VirtualQuery + +GENERIC_MAPPING diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE.cs new file mode 100644 index 0000000..de1b2d1 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE.cs @@ -0,0 +1,31 @@ +/// This file supplements code generated by CsWin32 +using Win32Exception = System.ComponentModel.Win32Exception; + +namespace Windows.Win32.Foundation; + +partial struct HANDLE : IComparable +{ + public static implicit operator HANDLE(nuint v) => new((nint)v); + public static implicit operator nuint(HANDLE v) => (nuint)(nint)v.Value; + + public static implicit operator HANDLE(nint v) => new(v); + public static implicit operator nint(HANDLE v) => v.Value; + + /// + /// Close the handle via the CloseHandle function + /// + /// + /// If the application is running under a debugger, the function will throw an + /// exception if it receives either a handle value that is not valid or a + /// pseudo-handle value. This can happen if you close a handle twice, or if you + /// call CloseHandle on a handle returned by the FindFirstFile function instead + /// of calling the FindClose function. + /// + public void Close() + { + if (!PInvoke.CloseHandle(this)) + throw new Win32Exception(); + } + + public int CompareTo(HANDLE other) => Value.CompareTo(other); +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs new file mode 100644 index 0000000..bf37c9e --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs @@ -0,0 +1,16 @@ +/// This file supplements code generated by CsWin32 +using NTStatusException = PInvoke.NTStatusException; + +namespace Windows.Win32.Foundation; + +readonly partial struct NTSTATUS +{ + public bool IsSuccessful => SeverityCode == Severity.Success; + + public NTStatusException GetNTStatusException() => new(this); + + public static implicit operator global::PInvoke.NTSTATUS(NTSTATUS v) => new(v.Value); + public static implicit operator NTSTATUS(global::PInvoke.NTSTATUS v) => new(v.AsInt32); + + public static implicit operator global::PInvoke.NTSTATUS.Code(NTSTATUS v) => ((global::PInvoke.NTSTATUS)v).Value; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/PWSTR.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/PWSTR.cs new file mode 100644 index 0000000..b3836d6 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/PWSTR.cs @@ -0,0 +1,14 @@ +/// This file supplements code generated by CsWin32 +using System.Runtime.InteropServices; + +namespace Windows.Win32.Foundation; + +unsafe readonly partial struct PWSTR : IDisposable +{ + /// + /// Free the PWSTR's memory with Marshal.FreeHGlobal(IntPtr) + /// + public void Dispose() => Marshal.FreeHGlobal((IntPtr)Value); + + public static implicit operator PWSTR(IntPtr v) => new((char*)v); +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs new file mode 100644 index 0000000..df49dad --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs @@ -0,0 +1,19 @@ +/// This file supplements code generated by CsWin32 +using System.Runtime.InteropServices; + +namespace Windows.Win32.Foundation; + +partial struct UNICODE_STRING : IDisposable +{ + public void Dispose() + { + Buffer.Dispose(); + } + + /// + /// Allocates a managed string and copies a specified number of characters from an unmanaged Unicode string into it. + /// + public unsafe string ToStringLength() => Marshal.PtrToStringUni((IntPtr)Buffer.Value, Length / 2); + public string? ToStringZ() => Buffer.ToString(); + public static explicit operator string(UNICODE_STRING v) => v.ToStringLength(); +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/GENERIC_MAPPING.cs b/deadlock-dotnet-sdk/Windows.Win32/GENERIC_MAPPING.cs new file mode 100644 index 0000000..90b01fe --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/GENERIC_MAPPING.cs @@ -0,0 +1,10 @@ +using static PInvoke.Kernel32; + +namespace Windows.Win32; +public struct GENERIC_MAPPING +{ + public ACCESS_MASK GenericRead; + public ACCESS_MASK GenericWrite; + public ACCESS_MASK GenericExecute; + public ACCESS_MASK GenericAll; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPES_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPES_INFORMATION.cs new file mode 100644 index 0000000..1af51ee --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPES_INFORMATION.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualBasic; + +namespace Windows.Win32; +struct OBJECT_TYPES_INFORMATION : IEquatable +{ + public uint NumberOfTypes = 0; + + public OBJECT_TYPES_INFORMATION(uint numberOfTypes) + { + NumberOfTypes = numberOfTypes; + } + + public static explicit operator uint(OBJECT_TYPES_INFORMATION v) => v.NumberOfTypes; + + public static bool operator ==(OBJECT_TYPES_INFORMATION x, OBJECT_TYPES_INFORMATION y) => x.NumberOfTypes == y.NumberOfTypes; + + public static bool operator !=(OBJECT_TYPES_INFORMATION x, OBJECT_TYPES_INFORMATION y) => x.NumberOfTypes != y.NumberOfTypes; + + public override bool Equals(object? obj) + { + return obj is OBJECT_TYPES_INFORMATION information && + NumberOfTypes == information.NumberOfTypes; + } + + public bool Equals(OBJECT_TYPES_INFORMATION other) => NumberOfTypes == other.NumberOfTypes; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPE_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPE_INFORMATION.cs new file mode 100644 index 0000000..781cb28 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPE_INFORMATION.cs @@ -0,0 +1,94 @@ +using Windows.Win32.Foundation; +using ACCESS_MASK = PInvoke.Kernel32.ACCESS_MASK; + +namespace Windows.Win32; +internal struct OBJECT_TYPE_INFORMATION +{ +#pragma warning disable CS0649 + + public UNICODE_STRING TypeName; + public string TypeNameAsString => TypeName.ToStringLength(); + public uint TotalNumberOfObjects; + public uint TotalNumberOfHandles; + public uint TotalPagedPoolUsage; + public uint TotalNonPagedPoolUsage; + public uint TotalNamePoolUsage; + public uint TotalHandleTableUsage; + public uint HighWaterNumberOfObjects; + public uint HighWaterNumberOfHandles; + public uint HighWaterPagedPoolUsage; + public uint HighWaterNonPagedPoolUsage; + public uint HighWaterNamePoolUsage; + public uint HighWaterHandleTableUsage; + public uint InvalidAttributes; + public GENERIC_MAPPING GenericMapping; + public ACCESS_MASK ValidAccessMask; + public BOOLEAN SecurityRequired; + public BOOLEAN MaintainHandleCount; + public byte TypeIndex; // since WINBLUE + public sbyte ReservedByte; + public uint PoolType; + public uint DefaultPagedPoolCharge; + public uint DefaultNonPagedPoolCharge; + + public ObjectTypeInformation ToManaged() => new(this); +} + +public struct ObjectTypeInformation +{ + internal ObjectTypeInformation(OBJECT_TYPE_INFORMATION oti) + { + TypeName = oti.TypeName.ToStringLength(); + + TotalNumberOfObjects = oti.TotalNumberOfObjects; + TotalNumberOfHandles = oti.TotalNumberOfHandles; + TotalPagedPoolUsage = oti.TotalPagedPoolUsage; + TotalNonPagedPoolUsage = oti.TotalNonPagedPoolUsage; + TotalNamePoolUsage = oti.TotalNamePoolUsage; + TotalHandleTableUsage = oti.TotalHandleTableUsage; + + HighWaterHandleTableUsage = oti.HighWaterHandleTableUsage; + HighWaterNumberOfObjects = oti.HighWaterNumberOfObjects; + HighWaterNumberOfHandles = oti.HighWaterNumberOfHandles; + HighWaterPagedPoolUsage = oti.HighWaterPagedPoolUsage; + HighWaterNonPagedPoolUsage = oti.HighWaterNonPagedPoolUsage; + HighWaterNamePoolUsage = oti.HighWaterNamePoolUsage; + HighWaterHandleTableUsage = oti.HighWaterHandleTableUsage; + + InvalidAttributes = oti.InvalidAttributes; + GenericMapping = oti.GenericMapping; + ValidAccessMask = oti.ValidAccessMask; + SecurityRequired = oti.SecurityRequired; + MaintainHandleCount = oti.MaintainHandleCount; + TypeIndex = oti.TypeIndex; + PoolType = oti.PoolType; + DefaultPagedPoolCharge = oti.DefaultPagedPoolCharge; + DefaultNonPagedPoolCharge = oti.DefaultNonPagedPoolCharge; + } + public string TypeName; + #region Total + public uint TotalNumberOfObjects; + public uint TotalNumberOfHandles; + public uint TotalPagedPoolUsage; + public uint TotalNonPagedPoolUsage; + public uint TotalNamePoolUsage; + public uint TotalHandleTableUsage; + #endregion Total + #region HighWater + public uint HighWaterNumberOfObjects; + public uint HighWaterNumberOfHandles; + public uint HighWaterPagedPoolUsage; + public uint HighWaterNonPagedPoolUsage; + public uint HighWaterNamePoolUsage; + public uint HighWaterHandleTableUsage; + #endregion HighWater + public uint InvalidAttributes; + public GENERIC_MAPPING GenericMapping; + public ACCESS_MASK ValidAccessMask; + public bool SecurityRequired; + public bool MaintainHandleCount; + public byte TypeIndex; // since WINBLUE + public uint PoolType; + public uint DefaultPagedPoolCharge; + public uint DefaultNonPagedPoolCharge; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs new file mode 100644 index 0000000..9db7832 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs @@ -0,0 +1,127 @@ +/// This file supplements code generated by CsWin32 +using System.ComponentModel; +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using MemInfo32 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION32; +using MemInfo64 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION64; + +using NTSTATUS_plus = PInvoke.NTSTATUS; + +namespace Windows.Win32; + +static partial class PInvoke +{ + /// Creates a handle that is a duplicate of the specified source handle. + /// A handle to the source process for the handle being duplicated. + /// The handle to duplicate. + /// A handle to the target process that is to receive the new handle. This parameter is optional and can be specified as NULL if the DUPLICATE_CLOSE_SOURCE flag is set in Options. + /// A pointer to a HANDLE variable into which the routine writes the new duplicated handle. The duplicated handle is valid in the specified target process. This parameter is optional and can be specified as NULL if no duplicate handle is to be created. + /// A pointer to a HANDLE variable into which the routine writes the new duplicated handle. The duplicated handle is valid in the specified target process. This parameter is optional and can be specified as NULL if no duplicate handle is to be created. + /// A ULONG that specifies the desired attributes for the new handle. For more information about attributes, see the description of the Attributes member in OBJECT_ATTRIBUTES. + /// + /// A set of flags to control the behavior of the duplication operation. Set this parameter to zero or to the bitwise OR of one or more of the following flags. + /// | Flag name | Description + /// | --------------------------- | ----------- + /// | DUPLICATE_SAME_ATTRIBUTES | Instead of using the HandleAttributes parameter, copy the attributes from the source handle to the target handle. + /// | DUPLICATE_SAME_ACCESS | Instead of using the DesiredAccess parameter, copy the access rights from the source handle to the target handle. + /// | DUPLICATE_CLOSE_SOURCE | Close the source handle. + /// + /// ZwDuplicateObject returns STATUS_SUCCESS if the call is successful.Otherwise, it returns an appropriate error status code. + /// + /// The source handle is evaluated in the context of the specified source process.The calling process must have PROCESS_DUP_HANDLE access to the source process.The duplicate handle is created in the handle table of the specified target process.The calling process must have PROCESS_DUP_HANDLE access to the target process. + /// By default, the duplicate handle is created with the attributes specified by the HandleAttributes parameter, and with the access rights specified by the DesiredAccess parameter. If necessary, the caller can override one or both defaults by setting the DUPLICATE_SAME_ATTRIBUTES and DUPLICATE_SAME_ACCESS flags in the Options parameter. + /// If the call to this function occurs in user mode, you should use the name "NtDuplicateObject" instead of "ZwDuplicateObject". + /// For calls from kernel-mode drivers, the NtXxx and ZwXxx versions of a Windows Native System Services routine can behave differently in the way that they handle and interpret input parameters.For more information about the relationship between the NtXxx and ZwXxx versions of a routine, see Using Nt and Zw Versions of the Native System Services Routines. + /// + [DllImport("ntdll.dll", ExactSpelling = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [global::System.Runtime.Versioning.SupportedOSPlatform("windows5.0")] + internal unsafe static extern NTSTATUS_plus NtDuplicateObject( + HANDLE SourceProcessHandle, + HANDLE SourceHandle, + [Optional] HANDLE TargetProcessHandle, + [Optional] out HANDLE* TargetHandle, + global::PInvoke.Kernel32.ACCESS_MASK DesiredAccess, + uint HandleAttributes, + uint Options + ); + + /// If successful and the current process is 32-bit, returns a MEMORY_BASIC_INFORMATION32 structure. If the current process is 64-bit, returns a MEMORY_BASIC_INFORMATION64 structure. + /// + internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(nuint lpAddress) + { + var is64bit = Environment.Is64BitProcess; + SIZE_T bufferSize = default; + int size64 = default; + int size32 = default; + void* pBuffer = null; + + GCHandle h64 = default; + GCHandle h32 = default; + + if (is64bit) + { + //pBuffer = (void*)Marshal.AllocHGlobal(size64); + size64 = Marshal.SizeOf(); + h64 = GCHandle.Alloc(default(MemInfo64), GCHandleType.Pinned); + bufferSize = __VirtualQuery((void*)lpAddress, (void*)h64.AddrOfPinnedObject(), (SIZE_T)size64); + } + else // is 32-bit process + { + size32 = Marshal.SizeOf(); + h32 = GCHandle.Alloc(default(MemInfo32), GCHandleType.Pinned); + bufferSize = __VirtualQuery((void*)lpAddress, (void*)h32.AddrOfPinnedObject(), (SIZE_T)size32); + } + + if (bufferSize != 0) + { + if (bufferSize == (nuint)size32) + { + return (memInfo32: *(MemInfo32*)pBuffer, default); + } + else if (bufferSize == (nuint)size64) + { + pBuffer = (void*)Marshal.AllocHGlobal((nint)bufferSize); + + if ((bufferSize = __VirtualQuery((void*)lpAddress, pBuffer, bufferSize)) != 0) + return (default, memInfo64: *(MemInfo64*)pBuffer); + else + throw new Win32Exception(); + } + else + { + throw new Exception($"VirtualQuery returned a buffer size ({bufferSize} bytes) that does not match either of two expected sizes ({size32}, {size64})."); + } + } + else + { + // VirtualQuery failed + throw new Win32Exception(); + } + } + + /// + /// Retrieves information about a range of pages in the virtual address space of the calling process. + /// To retrieve information about a range of pages in the address space of another process, use the VirtualQueryEx function. + /// + /// + /// A pointer to the base address of the region of pages to be queried. This value is rounded down to the next page boundary. To determine the size of a page on the host computer, use the GetSystemInfo function. + /// If lpAddress specifies an address above the highest memory address accessible to the process, the function fails with ERROR_INVALID_PARAMETER. + /// + /// A pointer to a MEMORY_BASIC_INFORMATION structure in which information about the specified page range is returned. + /// The size of the buffer pointed to by the lpBuffer parameter, in bytes. + /// + /// The return value is the actual number of bytes returned in the information buffer. + /// If the function fails, the return value is zero.To get extended error information, call GetLastError. + /// Possible error values include ERROR_INVALID_PARAMETER. + /// + // warning PInvoke005: This API is only available when targeting a specific CPU architecture.AnyCPU cannot generate this API. + [DllImport("Kernel32.dll", ExactSpelling = true, EntryPoint = "VirtualQuery", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [global::System.Runtime.Versioning.SupportedOSPlatform("windows5.1")] + private unsafe static extern SIZE_T __VirtualQuery( + void* lpAddress, + void* lpBuffer, + SIZE_T dwLength + ); +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/SIZE_T.cs b/deadlock-dotnet-sdk/Windows.Win32/SIZE_T.cs new file mode 100644 index 0000000..8ac0d90 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/SIZE_T.cs @@ -0,0 +1,19 @@ +namespace Windows.Win32 +{ + public struct SIZE_T + { + private nuint value; + + public static implicit operator SIZE_T(nuint v) => new() { value = v }; + public static implicit operator nuint(SIZE_T v) => v.value; + + public static explicit operator SIZE_T(nint v) => new() { value = (nuint)v }; + public static explicit operator nint(SIZE_T v) => (nint)v.value; + + public static explicit operator SIZE_T(long v) => new() { value = (nuint)v }; + public static explicit operator long(SIZE_T v) => (long)v.value; + + public static explicit operator SIZE_T(int v) => new() { value = (nuint)v }; + public static explicit operator int(SIZE_T v) => (int)v.value; + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/KPRIORITY.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/KPRIORITY.cs new file mode 100644 index 0000000..9180921 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/KPRIORITY.cs @@ -0,0 +1,16 @@ +/// This file supplements code generated by CsWin32 + +namespace Windows.Win32.System.Threading; + +/// +/// This typedef is not emitted by Win32Metadata, but is defined in wdm.h of Windows SDK 10.0.22621.0 +/// +public struct KPRIORITY +{ + public KPRIORITY(int value) : this() => Value = value; + + public int Value { get; set; } + + public static implicit operator KPRIORITY(int value) => new(value); + public static explicit operator int(KPRIORITY kpriority) => kpriority.Value; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs new file mode 100644 index 0000000..3c613f9 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs @@ -0,0 +1,161 @@ +/// This file supplements code generated by CsWin32 +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using Windows.Win32.Foundation; +using Win32Exception = System.ComponentModel.Win32Exception; + +namespace Windows.Win32.System.Threading; + +/// +/// all data which must be copied via ReadProcessMemory +/// +class PEB_Ex +{ + public PEB_Ex(uint processId, ref PEB peb) + { + ProcessId = processId; + ReadVmHandle = new(PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, true, processId), true); + Peb = peb; + + unsafe + { + PEB_LDR_DATA* baseAddr = Peb.Ldr; + PEB_LDR_DATA ldr = default; + nuint br = default; + if (PInvoke.ReadProcessMemory(ReadVmHandle, baseAddr, &ldr, (nuint)Marshal.SizeOf(), &br)) + PebLdrData = ldr; + } + + unsafe + { + RTL_USER_PROCESS_PARAMETERS* baseAddr = Peb.ProcessParameters; + RTL_USER_PROCESS_PARAMETERS procParams = default; + nuint br = default; + if (PInvoke.ReadProcessMemory(ReadVmHandle, baseAddr, &procParams, (nuint)Marshal.SizeOf(), &br)) + ProcessParameters = procParams; + } + } + + public uint ProcessId { get; } + public SafeProcessHandle? ReadVmHandle { get; } + public PEB Peb { get; } + + public PEB_LDR_DATA PebLdrData { get; } + public RTL_USER_PROCESS_PARAMETERS ProcessParameters { get; } +} + +partial struct PEB +{ + public PEB(PROCESS_BASIC_INFORMATION pbi, out PEB_Ex pebEx) + { + this = pbi.Peb; + pebEx = GetPebEx(pbi.ProcessId); + } + + public PEB_Ex GetPebEx(uint processId) => new(processId, ref this); + + public bool InheritedAddressSpace => (BOOLEAN)Reserved1[0]; + public bool ReadImageFileExecOptions => (BOOLEAN)Reserved1[1]; + public bool BeingDebugged_bool => (BOOLEAN)BeingDebugged; + + #region bit field + + public bool ImageUsesLargePages => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.ImageUsesLargePages); + public bool IsProtectedProcess => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsProtectedProcess); + public bool IsImageDynamicallyRelocated => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsImageDynamicallyRelocated); + public bool SkipPatchingUser32Forwarders => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.SkipPatchingUser32Forwarders); + public bool IsPackagedProcess => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsPackagedProcess); + public bool IsAppContainer => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsAppContainer); + public bool IsProtectedProcessLight => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsProtectedProcessLight); + public bool IsLongPathAwareProcess => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsLongPathAwareProcess); + + [Flags] + private enum R2_bits : byte + { + None = 0, + ImageUsesLargePages, + IsProtectedProcess, + IsImageDynamicallyRelocated, + SkipPatchingUser32Forwarders, + IsPackagedProcess, + IsAppContainer, + IsProtectedProcessLight, + IsLongPathAwareProcess + } + + #endregion bit field + + public unsafe HANDLE Mutant => new((IntPtr)Reserved3._0); + public unsafe IntPtr ImageBaseAddress => (IntPtr)Reserved3._1; + + /// + /// Invoke ReadProcessMemory to copy the PED_LDR_DATA from the process's memory + /// + /// + /// ReadProcessMemory failed + public unsafe PEB_LDR_DATA GetPebLdrData(uint processId) + { + using SafeProcessHandle hProcess = new(PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, true, processId), true); + PEB_LDR_DATA ldr = default; + nuint bytesRead = default; + + if (PInvoke.ReadProcessMemory(hProcess, Ldr, &ldr, (nuint)Marshal.SizeOf(), &bytesRead)) + return ldr; + else throw new Win32Exception(); + } + + /// + /// Invoke ReadProcessMemory to copy the RTL_USER_PROCESS_PARAMETERS from the process's memory + /// + /// + /// + /// ReadProcessMemory failed + public unsafe RTL_USER_PROCESS_PARAMETERS GetProcessParameters(uint processId) + { + using SafeProcessHandle hProcess = new(PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, true, processId), true); + RTL_USER_PROCESS_PARAMETERS processParameters = default; + nuint bytesRead = default; + + if (PInvoke.ReadProcessMemory(hProcess, ProcessParameters, &processParameters, (nuint)Marshal.SizeOf(), &bytesRead)) + return processParameters; + else throw new Win32Exception(); + } + + public unsafe IntPtr SubSystemData => (IntPtr)Reserved4._0; + public unsafe IntPtr ProcessHeap => (IntPtr)Reserved4._1; + public unsafe IntPtr FastPebLock => (IntPtr)Reserved4._2; + public unsafe IntPtr IFEOKey => (IntPtr)Reserved5; + + #region CrossProcessFlags + + public bool ProcessInJob => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessInJob); + public bool ProcessInitializing => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessInitializing); + public bool ProcessUsingVEH => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessUsingVEH); + public bool ProcessUsingVCH => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessUsingVCH); + public bool ProcessUsingFTH => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessUsingFTH); + public bool ProcessPreviouslyThrottled => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessPreviouslyThrottled); + public bool ProcessCurrentlyThrottled => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessCurrentlyThrottled); + public bool ProcessImagesHotPatched => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessImagesHotPatched); // REDSTONE5 + + [Flags] + private enum CrossProcessFlags : uint + { + None = 0, + ProcessInJob = 0b0001, + ProcessInitializing = 0b0010, + ProcessUsingVEH = 0b0100, + ProcessUsingVCH = 0b1000, + ProcessUsingFTH = 0b1_0000, + ProcessPreviouslyThrottled = 0b10_0000, + ProcessCurrentlyThrottled = 0b100_0000, + ProcessImagesHotPatched = 0b1000_0000 + } + + #endregion CrossProcessFlags + + public unsafe IntPtr KernelCallbackTable => (IntPtr)Reserved7; + public unsafe IntPtr UserSharedInfoPtr => (IntPtr)Reserved7; + public uint SystemReserved => Reserved8; + + //TODO: https://sourcegraph.com/github.com/winsiderss/systeminformer@master/-/blob/phnt/include/ntpebteb.h?L119 +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs new file mode 100644 index 0000000..447ce39 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs @@ -0,0 +1,92 @@ +/// This file supplements code generated by CsWin32 +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using PInvoke; +using Win32Exception = System.ComponentModel.Win32Exception; + +namespace Windows.Win32.System.Threading; + +/// +/// This struct is not fully emitted by Win32Metadata. The definition in ntddk.h of Windows SDK 10.0.22621.0 has many more documented fields +/// +readonly struct PROCESS_BASIC_INFORMATION +{ + private readonly nuint uniqueProcessId; + private readonly UIntPtr inheritedFromUniqueProcessId; + + /// + /// The process's ID. Backed by a pointer-sized integer field. + /// + /// + public uint ProcessId => (uint)uniqueProcessId; + + public NTSTATUS ExitStatus { get; } + + /// + /// The address of the PEB relative to its process's memory. Read object via + /// + public unsafe PEB* PebBaseAddress { get; } + + /// + /// Invoke ReadProcessMemory to copy the target process's PEB to our memory + /// + /// + public unsafe PEB Peb + { + get + { + if (ProcessId == Environment.ProcessId) + return *PebBaseAddress; + + SafeProcessHandle hProcess = GetProcessHandle(ProcessId); + PEB peb; + nuint bytesRead; + return !PInvoke.ReadProcessMemory(hProcess, PebBaseAddress, &peb, (nuint)Marshal.SizeOf(typeof(PEB)), &bytesRead) + ? throw new Win32Exception() + : peb; + } + } + + public UIntPtr AffinityMask { get; } + public KPRIORITY BasePriority { get; } + public uint InheritedFromUniqueProcessId => (uint)inheritedFromUniqueProcessId; + + /// + /// Create an instance of PROCESS_BASIC_INFORMATION with data acquired by passing + /// a handle for a process with the PROCESS_VM_READ and + /// PROCESS_QUERY_LIMITED_INFORMATION rights to NtQueryInformationProcess + /// + /// A Process handle with the PROCESS_VM_READ and PROCESS_QUERY_LIMITED_INFORMATION rights. This handle must remain open for the PEB pointer to be readable. + /// If UniqueProcessId does not match the current process's ID, read all pointers with NtQueryVirtualMemory + /// NtQueryInformationProcess returned an error code + // TODO: wrap in object containing an object of this Type, a process handle for NtQueryVirtualMemory, and a "shortcut" method for calling that function + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryvirtualmemory + public PROCESS_BASIC_INFORMATION(SafeProcessHandle hProcess) + { + NTSTATUS status; + uint returnLength = default; + unsafe + { + fixed (PROCESS_BASIC_INFORMATION* pThis = &this) + status = PInvoke.NtQueryInformationProcess(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, pThis, (uint)Marshal.SizeOf(this), ref returnLength); + } + + status.ThrowOnError(); + } + + /// + /// Get a SafeProcessHandle with rights suitable for accessing PEB pointers via NtQueryVirtualMemory + /// + /// + /// + /// Failed to open Handle to process with VM_READ and QUERY_LIMITED_INFORMATION rights + public static SafeProcessHandle GetProcessHandle(uint processId) + { + var hProcess = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ | PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, true, processId); + + if (hProcess.IsNull) + throw new Win32Exception(); + else + return new SafeProcessHandle(hProcess, true); + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PS_POST_PROCESS_INIT_ROUTINE.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PS_POST_PROCESS_INIT_ROUTINE.cs new file mode 100644 index 0000000..dd1391b --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PS_POST_PROCESS_INIT_ROUTINE.cs @@ -0,0 +1,49 @@ +/// This file supplements code generated by CsWin32 +using System.Runtime.InteropServices; + +namespace Windows.Win32.System.Threading; + +[UnmanagedFunctionPointer(CallingConvention.Winapi)] +unsafe delegate void PS_POST_PROCESS_INIT_ROUTINE(); + +/// +/// Function Pointer workaround. C# 9's function pointers are only allowed in +/// local scope. +/// +/// +/// The pointer returned by the Marshal methods is typically a "Thunk" rather +/// than a handle. If the .NET runtime sees a thunk was already created, it +/// will re-use that thunk. +/// source: +/// +struct PPS_POST_PROCESS_INIT_ROUTINE : IEquatable +{ + public IntPtr Value; + + public PPS_POST_PROCESS_INIT_ROUTINE(PS_POST_PROCESS_INIT_ROUTINE initRoutine) + => Value = Marshal.GetFunctionPointerForDelegate(initRoutine); + + public static PPS_POST_PROCESS_INIT_ROUTINE FromPointer(IntPtr v) + { + try + { + _ = Marshal.GetDelegateForFunctionPointer(v); //DevSkim: ignore DS104456 + return new() { Value = v }; + } + catch (Exception) + { + // not a delegate or open generic type + // or ptr is null + return new() { Value = IntPtr.Zero }; + } + } + + public static explicit operator PPS_POST_PROCESS_INIT_ROUTINE(IntPtr v) => FromPointer(v); + + public bool Equals(PPS_POST_PROCESS_INIT_ROUTINE other) => Value == other.Value; + + public override bool Equals(object? obj) + => obj is PPS_POST_PROCESS_INIT_ROUTINE pPS_POST_PROCESS_INIT_ROUTINE && Equals(pPS_POST_PROCESS_INIT_ROUTINE); + + public override int GetHashCode() => Value.GetHashCode(); +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs new file mode 100644 index 0000000..4b0f6ba --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs @@ -0,0 +1,32 @@ +/// This file supplements code generated by CsWin32 +namespace Windows.Win32.System.WindowsProgramming; + +/// +/// The generated enum is missing most entries and has ObjectTypeInformation as `1` instead of `2`. Will changing its value prove to be a mistake? +/// https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntobapi_x/object_information_class.htm +/// +enum OBJECT_INFORMATION_CLASS +{ + /// + /// A PUBLIC_OBJECT_BASIC_INFORMATION structure is supplied. + /// + ObjectBasicInformation = 0, + /// Microsoft documents `1` as ObjectTypeInformation instead of ObjectNameInformation + ObjectNameInformation = 1, + /// + /// A PUBLIC_OBJECT_TYPE_INFORMATION structure is supplied. + /// + ObjectTypeInformation = 2, + /// 3.50 and higher + ObjectTypesInformation = 3, + /// 3.50 and higher + ObjectHandleFlagInformation = 4, + /// 5.2 and higher + ObjectSessionInformation = 5, + /// 1703 and higher + ObjectSessionObjectInformation = 6, + /// 6.1 to 1607 + MaxObjectInfoClass_old = ObjectSessionObjectInformation, + /// version > 1607 + MaxObjectInfoClass_new = 7 +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs new file mode 100644 index 0000000..a5ade7a --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs @@ -0,0 +1,20 @@ +/// This file supplements code generated by CsWin32 +namespace Windows.Win32.System.WindowsProgramming; + +// generated definition lacks SystemHandleInformation, SystemExtendedHandleInformation +enum SYSTEM_INFORMATION_CLASS +{ + SystemBasicInformation = 0, + SystemPerformanceInformation = 2, + SystemTimeOfDayInformation = 3, + SystemProcessInformation = 5, + SystemProcessorPerformanceInformation = 8, + SystemHandleInformation = 16, + SystemInterruptInformation = 23, + SystemExceptionInformation = 33, + SystemRegistryQuotaInformation = 37, + SystemLookasideInformation = 45, + SystemExtendedHandleInformation = 64, + SystemCodeIntegrityInformation = 103, + SystemPolicyInformation = 134 +} From 97e4992ba23d0cd0993bfb2fdeb5fe7b25ca9eba Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 04:51:04 -0800 Subject: [PATCH 074/306] refactor: slightly improve getting process command lines It still fails more often than not due to crossing into unreadable memory. I'll look into it later. --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 85374f5..49d2304 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -50,9 +50,9 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE ); if (rawHandle.IsNull) - throw new Win32Exception("Failed to open process handle with access rights 'PROCESS_QUERY_LIMITED_INFORMATION' and 'PROCESS_VM_READ'. The following information will be unavailable: main module full name, process name, "); + throw new Win32Exception("Failed to open process handle with access rights 'PROCESS_QUERY_LIMITED_INFORMATION' and 'PROCESS_VM_READ'. The following information will be unavailable: main module full name, process name, process' startup command line"); - SafeProcessHandle hProcess = new(rawHandle, true); + using SafeProcessHandle hProcess = new(rawHandle, true); /** Get main module's full path */ ProcessMainModulePath = GetFullProcessImageName(hProcess); @@ -64,7 +64,7 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE } /** Get process's possibly-overwritten command line from the PEB struct in its memory space */ - GetProcessCommandLine(hProcess); + ProcessCommandLine = GetProcessCommandLine(hProcess); } catch (Exception e) { @@ -89,8 +89,7 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE ///
/// public string? HandleObjectType { get; } - - public string? ProcessCommandLine { get; private set; } + public string? ProcessCommandLine { get; init; } public string? ProcessMainModulePath { get; } public string? ProcessName { get; } @@ -154,7 +153,7 @@ public void UnlockSystemHandle() /// Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented. /// Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible. /// NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION' - private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) + private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) { /* Get PROCESS_BASIC_INFORMATION */ uint sysInfoLength = (uint)Marshal.SizeOf(); @@ -162,7 +161,6 @@ private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) IntPtr sysInfo = Marshal.AllocHGlobal((int)sysInfoLength); NTSTATUS status = (NTSTATUS)0; uint retLength = 0; - if ((status = NtQueryInformationProcess( hProcess, PROCESSINFOCLASS.ProcessBasicInformation, @@ -171,7 +169,7 @@ private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) ref retLength)) .IsSuccessful) { - processBasicInfo = Marshal.PtrToStructure(sysInfo); + processBasicInfo = Marshal.PtrToStructure(sysInfo); //DevSkim: ignore DS104456 // if our process is WOW64, we need to account for different pointer sizes if // the target process is 64-bit @@ -190,8 +188,8 @@ private unsafe void GetProcessCommandLine(SafeProcessHandle hProcess) IntPtr buf = Marshal.AllocHGlobal(sizeof(PEB)); if (ReadProcessMemory(hProcess, processBasicInfo.PebBaseAddress, (void*)buf, (nuint)sizeof(PEB), null)) { - PEB peb = Marshal.PtrToStructure(buf); - ProcessCommandLine = (*peb.ProcessParameters).CommandLine.ToStringLength(); + PEB peb = Marshal.PtrToStructure(buf); //DevSkim: ignore DS104456 + return (*peb.ProcessParameters).CommandLine.ToStringLength(); } else { From 7d49548b319e9a0ee844b0da94bedb4cdcb1da93 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 05:38:05 -0800 Subject: [PATCH 075/306] refactor: move SYSTEM_HANDLE structs to new files --- ...iveMethods.SYSTEM_HANDLE_INFORMATION_EX.cs | 76 ++++ ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 361 ++++++++++++++++++ deadlock-dotnet-sdk/Domain/NativeMethods.cs | 137 +------ 3 files changed, 441 insertions(+), 133 deletions(-) create mode 100644 deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs create mode 100644 deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs new file mode 100644 index 0000000..4fef99d --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs @@ -0,0 +1,76 @@ +// Re: StructLayout +// "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." +// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-6.0#remarks + +// new Win32Exception() is defined as +// public Win32Exception() : this(Marshal.GetLastPInvokeError()) +// { +// } + +namespace deadlock_dotnet_sdk.Domain; + +internal static partial class NativeMethods +{ + //private static extern void MmIsAddressValid() + /// + /// The SYSTEM_HANDLE_INFORMATION_EX + /// struct is 0x24 or 0x38 bytes in 32-bit and 64-bit Windows, respectively. However, Handles is a variable-length array. + /// + public unsafe struct SYSTEM_HANDLE_INFORMATION_EX + { +#pragma warning disable CS0649 + + /// + /// As documented unofficially, NumberOfHandles is a 4-byte or 8-byte ULONG_PTR in 32-bit and 64-bit Windows, respectively.
+ /// This is not to be confused with uint* or ulong*. + ///
+ public readonly UIntPtr NumberOfHandles; + public readonly UIntPtr Reserved; + public readonly SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handle_0; + + /// + /// If IsEmpty is true, AsSpan() failed. + /// + /// + public ReadOnlySpan ReadOnlySpan + { + get + { + try + { + return AsSpan(); + } + catch (Exception) + { + return ReadOnlySpan.Empty; + } + } + } +#pragma warning restore CS0649 + + /// + /// Infer an array from the address of Handle_0 and NumberOfHandles, then return it as a ReadOnlySpan + /// + /// + /// + public ReadOnlySpan AsSpan() + { + fixed (SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* pHandle_0 = &Handle_0) + return new ReadOnlySpan(pHandle_0, (int)NumberOfHandles).ToArray(); + } + + /// + /// DEBUGGING | Test for memory access. System.AccessViolationException due to these values being in a protected memory range is a problem. + /// + internal void CheckAccess() + { + var tmp = AsSpan(); + + var lastItem = tmp[(int)NumberOfHandles - 1]; + + Console.WriteLine(lastItem + ": " + lastItem.UniqueProcessId); + } + + public static explicit operator ReadOnlySpan(SYSTEM_HANDLE_INFORMATION_EX value) => value.AsSpan(); + } +} diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs new file mode 100644 index 0000000..175c90b --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -0,0 +1,361 @@ +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using PInvoke; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.WindowsProgramming; +using static Windows.Win32.PInvoke; +using NTSTATUS = PInvoke.NTSTATUS; + +// Re: StructLayout +// "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." +// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-6.0#remarks + +// new Win32Exception() is defined as +// public Win32Exception() : this(Marshal.GetLastPInvokeError()) +// { +// } + +namespace deadlock_dotnet_sdk.Domain; + +internal static partial class NativeMethods +{ + private static bool IsSuccessful(this NTSTATUS status) => status.Severity == NTSTATUS.SeverityCode.STATUS_SEVERITY_SUCCESS; + private static bool NT_SUCCESS(this NTSTATUS status) => status.Severity == NTSTATUS.SeverityCode.STATUS_SEVERITY_SUCCESS; + private const uint PH_LARGE_BUFFER_SIZE = int.MaxValue; + private static List? objectTypes; + private static List ObjectTypes => objectTypes ??= ObjectTypesInformationBuffer.PhEnumObjectTypes().ToList(); + + /// + /// ported from SystemInformer for convenience + /// + private static uint? GetObjectTypeNumber(string typeName) + { + Version WINDOWS_8_1 = new(6, 2); + Version WindowsVersion = Environment.OSVersion.Version; + uint objectIndex = uint.MaxValue; + + for (int i = 0; i < ObjectTypes.Count; i++) + { + if (typeName.Equals(ObjectTypes[i].TypeName, StringComparison.OrdinalIgnoreCase)) + { + if (WindowsVersion >= WINDOWS_8_1) + objectIndex = ObjectTypes[i].TypeIndex; + else + objectIndex = (uint)(i + 2); + } + } + + if (objectIndex is uint.MaxValue) + throw new InvalidOperationException("No matching Type found."); + + return objectIndex; + } + + /// + /// ported from SystemInformer for convenience + /// + private static string GetObjectTypeName(int typeIndex) + { + Version WINDOWS_8_1 = new(6, 2); + Version WindowsVersion = Environment.OSVersion.Version; + string objectTypeName = ""; + + for (int i = 0; i < ObjectTypes.Count; i++) + { + if (WindowsVersion >= WINDOWS_8_1) + { + if (typeIndex == ObjectTypes[i].TypeIndex) + objectTypeName = ObjectTypes[i].TypeName; + } + else if (typeIndex == (i + 2)) + { + objectTypeName = ObjectTypes[i].TypeName; + } + } + + if (objectTypeName is "") + throw new InvalidOperationException("No matching Type found."); + + return objectTypeName; + } + + /// + /// The + /// + /// SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX + /// structure is a recurring element in the + /// + /// SYSTEM_HANDLE_INFORMATION_EX + /// struct that a successful call to + /// + /// ZwQuerySystemInformation + /// or + /// + /// NtQuerySystemInformation + /// produces in its output buffer when given the information class + /// SystemExtendedHandleInformation (0x40). + /// This inline doc was supplemented by ProcessHacker's usage of this struct. + /// + public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX + { +#pragma warning disable CS0649 + private readonly unsafe void* Object; + public unsafe nuint ObjectPointer => (nuint)Object; + /// + /// ULONG_PTR, cast to HANDLE, int, or uint + /// + public nuint UniqueProcessId { get; } + /// + /// ULONG_PTR, cast to HANDLE + /// + public HANDLE HandleValue { get; } + /// + /// This is a bitwise "Flags" data type. + /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. + /// + public Kernel32.ACCESS_MASK GrantedAccess { get; } // uint + public ushort CreatorBackTraceIndex { get; } // USHORT + /// ProcessHacker defines a little over a dozen handle-able object types. + public ushort ObjectTypeIndex { get; } // USHORT + /// + public Kernel32.HandleFlags HandleAttributes { get; } // uint +#pragma warning disable RCS1213, CS0169, IDE0051 // Remove unused field declaration. csharp(RCS1213) | Roslynator + private readonly uint Reserved; +#pragma warning restore RCS1213, CS0649, CS0169, IDE0051 + + /// + /// Get the Type of the object as a string
+ /// If calling from a SafeHandle + ///
+ /// P/Invoke function NtQueryObject failed. See Exception data. + /// The Type of the object as a string. + public unsafe string GetHandleObjectType() + { + /* CS1673: Anonymous methods, lambda expressions, query expressions, and local + functions inside structs cannot access instance members of 'this'. + Consider copying 'this' to a local variable outside the anonymous method, lambda + expression, query expression, or local function and using the local instead. + */ + return GetObjectTypeName(ObjectTypeIndex); + + //* Open a handle to the process associated with this system handle - adamkramer */ + + //using SafeFileHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, true, (uint)UniqueProcessId); + //if (hProcess is null || hProcess!.IsInvalid) throw new System.ComponentModel.Win32Exception(); + + //* Duplicate this system handle so we can query it. - adamkramer */ + /* sidebar: DuplicateHandle and NtDuplicateObject seem to have the same purpose, but are in the WinSDK and WDDK, respectively. Question is: Why? Why have two (technically three i.e. ZwDuplicateObject) functions that do the same thing? -BinToss */ + + // if (0 <= NtDuplicateObject(hProcess, HandleValue, Process.GetCurrentProcess().SafeHandle)) + // { + // } + // + // if (DuplicateHandle(hSourceProcessHandle: hProcess, + // hSourceHandle: new Kernel32.SafeObjectHandle(HandleValue), + // hTargetProcessHandle: Process.GetCurrentProcess().SafeHandle, + // lpTargetHandle: out SafeFileHandle lpTargetHandle, + // dwDesiredAccess: default, + // bInheritHandle: true, + // dwOptions: DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) + // { } + + //* Query the object type */ + // string typeName; + // PUBLIC_OBJECT_TYPE_INFORMATION* objectTypeInfo = (PUBLIC_OBJECT_TYPE_INFORMATION*)Marshal.AllocHGlobal(sizeof(PUBLIC_OBJECT_TYPE_INFORMATION)); + // uint* returnLength = (uint*)Marshal.AllocHGlobal(sizeof(uint)); + // NTSTATUS status; + + // if ((status = NtQueryObject(HandleValue, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, objectTypeInfo, (uint)sizeof(PUBLIC_OBJECT_TYPE_INFORMATION), returnLength)).Severity == NTSTATUS.SeverityCode.STATUS_SEVERITY_SUCCESS) + // { + // typeName = objectTypeInfo->TypeName.ToStringLength(); + // Marshal.FreeHGlobal((IntPtr)objectTypeInfo); + // } + // else + // { + // Marshal.FreeHGlobal((IntPtr)objectTypeInfo); + // throw new Exception("P/Invoke function NtQueryObject failed. See Exception data.", new NTStatusException(status)); + // } + + // return typeName; + } + + /// Invokes and checks if the result is "File". + /// True if the handle is for a file or directory. + /// Based on source of C/C++ projects Hijack File Handle and Handle Monitor + /// Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details. + public bool IsFileHandle() + { + try + { + string type = GetHandleObjectType(); + return !string.IsNullOrWhiteSpace(type) && string.CompareOrdinal(type, "File") == 0; + } + catch (Exception e) + { + throw new Exception("Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details.", e); + } + } + + /// + /// Try to cast this handle's to a SafeFileHandle; + /// + /// A if this handle's object is a data/directory File. + /// The handle's object is not a File -OR- perhaps NtQueryObject() failed. See for details. + public SafeFileHandle ToSafeFileHandle() + { + return IsFileHandle() + ? (new((nint)HandleValue, (int)UniqueProcessId == Environment.ProcessId)) + : throw new Exception("The handle's object is not a File -OR- NtQueryObject() failed. See InnerException for details."); + } + } + + private sealed class ObjectTypesInformationBuffer : IDisposable + { + private IntPtr pointer; + private uint bytes; + + public ObjectTypesInformationBuffer(uint lengthInBytes) + { + pointer = Marshal.AllocHGlobal((int)lengthInBytes); + bytes = lengthInBytes; + } + + public IntPtr Pointer => pointer; + public uint SizeInBytes => bytes; + public unsafe uint NumberOfTypes => ((OBJECT_TYPES_INFORMATION*)pointer)->NumberOfTypes; + + public unsafe List ToList() + { + var list = new List(); + var selection = PH_FIRST_OBJECT_TYPE((void*)pointer); + list.Add(new(*selection)); + + for (int i = 1; i < NumberOfTypes; i++) + { + selection = PH_NEXT_OBJECT_TYPE(selection); + list.Add(new(*selection)); + } + return list; + } + + public unsafe void ReAllocate(uint lengthInBytes) + { + pointer = Marshal.ReAllocHGlobal(pointer, (IntPtr)lengthInBytes); + bytes = lengthInBytes; + } + + public void Dispose() => Marshal.FreeHGlobal(pointer); + + /// + /// P/Invoke NtQueryObject for ObjectTypesInformation data. + /// + /// An , a wrapper for OBJECT_TYPES_INFORMATION, OBJECT_TYPE_INFORMATION, and the allocated memory they occupy. + /// + public static unsafe ObjectTypesInformationBuffer PhEnumObjectTypes() + { + NTSTATUS status; + ObjectTypesInformationBuffer buffer; + uint returnLength; + + buffer = new(0x1000); + + while ((status = NtQueryObject( + null, + OBJECT_INFORMATION_CLASS.ObjectTypesInformation, + (void*)buffer.pointer, + buffer.bytes, + &returnLength + )) == NTSTATUS.Code.STATUS_INFO_LENGTH_MISMATCH) + { + // Fail if we're resizing the buffer to something very large. + if (returnLength * 1.5 > PH_LARGE_BUFFER_SIZE) + throw new NTStatusException(NTSTATUS.Code.STATUS_INSUFFICIENT_RESOURCES); + + buffer.ReAllocate((uint)(returnLength * 1.5)); + } + + if (!status.NT_SUCCESS()) + { + buffer.Dispose(); + throw new NTStatusException(status); + } + + return buffer; + } + + public unsafe uint PhGetObjectTypeNumber(string typeName) + { + OBJECT_TYPE_INFORMATION* objectType; + uint objectIndex = uint.MaxValue; + uint i; + + if (NumberOfTypes != default) + { + objectType = PH_FIRST_OBJECT_TYPE((void*)pointer); + + for (i = 0; i < NumberOfTypes; i++) + { + string typeNameSr = (string)objectType->TypeName; + + if (string.Equals(typeNameSr, typeName, StringComparison.OrdinalIgnoreCase)) + { + if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version >= new Version(6, 3)) + { + objectIndex = objectType->TypeIndex; + break; + } + else + { + objectIndex = i + 2; + break; + } + } + + objectType = PH_NEXT_OBJECT_TYPE(objectType); + } + } + + return objectIndex; + } + + public unsafe string? PhGetObjectTypeName(uint TypeIndex) + { + OBJECT_TYPE_INFORMATION* objectType; + string? objectTypeName = null; + uint i; + + objectType = PH_FIRST_OBJECT_TYPE((void*)pointer); + + for (i = 0; i < NumberOfTypes; i++) + { + if (OperatingSystem.IsWindowsVersionAtLeast(6, 2)) + { + if (TypeIndex == objectType->TypeIndex) + { + objectTypeName = (string)objectType->TypeName; + break; + } + } + else + { + if (TypeIndex == (i + 2)) + { + objectTypeName = (string)objectType->TypeName; + break; + } + } + + objectType = PH_NEXT_OBJECT_TYPE(objectType); + } + + return objectTypeName; + } + } + + public static unsafe OBJECT_TYPE_INFORMATION* PH_FIRST_OBJECT_TYPE(void* ObjectTypes) => (OBJECT_TYPE_INFORMATION*)PTR_ADD_OFFSET(ObjectTypes, ALIGN_UP((nuint)sizeof(OBJECT_TYPES_INFORMATION), typeof(UIntPtr))); + public static unsafe OBJECT_TYPE_INFORMATION* PH_NEXT_OBJECT_TYPE(OBJECT_TYPE_INFORMATION* ObjectType) => (OBJECT_TYPE_INFORMATION*)PTR_ADD_OFFSET(ObjectType, (nuint)Marshal.SizeOf() + ALIGN_UP(ObjectType->TypeName.MaximumLength, typeof(UIntPtr))); + public static unsafe void* PTR_ADD_OFFSET(void* Pointer, nuint Offset) => (void*)((nuint)Pointer + Offset); + public static nuint ALIGN_UP(nuint Address, Type type) => ALIGN_UP_BY(Address, (uint)Marshal.SizeOf(type)); + public static nuint ALIGN_UP_BY(nuint Address, uint Align) => (Address + Align - 1) & ~(Align - 1); +} diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 021d3f3..2f1dc4a 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -1,14 +1,14 @@ -using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using deadlock_dotnet_sdk.Exceptions; -using Microsoft.Win32.SafeHandles; +using PInvoke; using Windows.Win32.Foundation; -using Windows.Win32.Security; using Windows.Win32.System.RestartManager; using Windows.Win32.System.WindowsProgramming; using static deadlock_dotnet_sdk.Domain.FileLockerEx; using static Windows.Win32.PInvoke; +using NTSTATUS = PInvoke.NTSTATUS; +using Win32Exception = System.ComponentModel.Win32Exception; // Re: StructLayout // "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." @@ -24,7 +24,7 @@ namespace deadlock_dotnet_sdk.Domain; /// /// Collection of native methods /// -internal static class NativeMethods +internal static partial class NativeMethods { #region Variables @@ -252,133 +252,4 @@ ref returnLength } #endregion Methods - - #region Structs - - /// - /// The SYSTEM_HANDLE_INFORMATION_EX - /// struct is 0x24 or 0x38 bytes in 32-bit and 64-bit Windows, respectively. However, Handles is a variable-length array. - /// - public unsafe readonly struct SYSTEM_HANDLE_INFORMATION_EX - { -#pragma warning disable CS0649 - - /// - /// As documented unofficially, NumberOfHandles is a 4-byte or 8-byte ULONG_PTR in 32-bit and 64-bit Windows, respectively.
- /// This is not to be confused with uint* or ulong*. - ///
- public readonly UIntPtr NumberOfHandles; - public readonly UIntPtr Reserved; - public readonly SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* Handles; - -#pragma warning restore CS0649 - - public Span AsSpan() => new(Handles, (int)NumberOfHandles); - - /// - /// Test for memory access. System.AccessViolationException due to these values being in a protected memory range is a problem. - /// - internal void CheckAccess() - { - Console.WriteLine(Handles[0].HandleValue); - } - public static implicit operator Span(SYSTEM_HANDLE_INFORMATION_EX value) => value.AsSpan(); - } - - /// - /// The - /// SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX - /// structure is a recurring element in the - /// SYSTEM_HANDLE_INFORMATION_EX - /// struct that a successful call to - /// ZwQuerySystemInformation - /// or - /// NtQuerySystemInformation - /// produces in its output buffer when given the information class - /// SystemHandleInformation (0x10). - /// This inline doc was supplemented by ProcessHacker's usage of this struct. - /// - public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX - { -#pragma warning disable CS0649 - public readonly unsafe void* Object; - /// - /// ULONG_PTR, cast to HANDLE, int, or uint - /// - public readonly HANDLE UniqueProcessId; - /// - /// ULONG_PTR, cast to HANDLE - /// - public readonly HANDLE HandleValue; - /// - /// This is a bitwise "Flags" data type. - /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. - /// - public readonly ACCESS_MASK GrantedAccess; // ULONG - public readonly ushort CreatorBackTraceIndex; // USHORT - /// ProcessHacker defines a little over a dozen handle-able object types. - public readonly ushort ObjectTypeIndex; // USHORT - /// - public readonly uint HandleAttributes; // ULONG -#pragma warning disable RCS1213, CS0169 // Remove unused field declaration. csharp(RCS1213) | Roslynator - private readonly uint Reserved; -#pragma warning restore RCS1213, CS0649, CS0169 - - /// - /// Get the Type of the object as a string - /// - /// P/Invoke function NtQueryObject failed. See Exception data. - /// The Type of the object as a string. - public unsafe string GetHandleObjectType() - { - /* Query the object type */ - string typeName; - PUBLIC_OBJECT_TYPE_INFORMATION* objectTypeInfo = (PUBLIC_OBJECT_TYPE_INFORMATION*)Marshal.AllocHGlobal(sizeof(PUBLIC_OBJECT_TYPE_INFORMATION)); - uint* returnLength = (uint*)Marshal.AllocHGlobal(sizeof(uint)); - NTSTATUS status; - - if ((status = NtQueryObject(HandleValue, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, objectTypeInfo, (uint)sizeof(PUBLIC_OBJECT_TYPE_INFORMATION), returnLength)).SeverityCode == NTSTATUS.Severity.Success) - { - typeName = objectTypeInfo->TypeName.ToStringLength(); - Marshal.FreeHGlobal((IntPtr)objectTypeInfo); - } - else - { - Marshal.FreeHGlobal((IntPtr)objectTypeInfo); - throw new Exception("P/Invoke function NtQueryObject failed. See Exception data.", status.GetNTStatusException()); - } - return typeName; - } - - /// Invokes and checks if the result is "File". - /// True if the handle is for a file or directory. - /// Based on source of C/C++ projects Hijack File Handle and Handle Monitor - /// Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details. - public bool IsFileHandle() - { - try - { - string type = GetHandleObjectType(); - return !string.IsNullOrWhiteSpace(type) && string.CompareOrdinal(type, "File") == 0; - } - catch (Exception e) - { - throw new Exception("Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details.", e); - } - } - - /// - /// Try to cast this handle's to a SafeFileHandle; - /// - /// A if this handle's object is a data/directory File. - /// The handle's object is not a File -OR- perhaps NtQueryObject() failed. See for details. - public SafeFileHandle ToSafeFileHandle() - { - return IsFileHandle() - ? (new((nint)HandleValue, (int)UniqueProcessId == Environment.ProcessId)) - : throw new Exception("The handle's object is not a File -OR- NtQueryObject() failed. See InnerException for details."); - } - } - - #endregion Structs } From 123d58131892ce1621e4d396bc61763b0ac2a21d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 05:41:19 -0800 Subject: [PATCH 076/306] refactor: catch exceptions thrown by file info queries --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 17 +++++++++++------ deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 5 +---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index fdd10c2..d9d5bf6 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -32,13 +32,17 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { try { - if (sysHandleEx.IsFileHandle()) + if ((bool)(IsFileHandle = SysHandleEx.IsFileHandle())) { - FileFullPath = TryGetFinalPath(); - if (FileFullPath != null) + try { + FileFullPath = TryGetFinalPath(); FileName = Path.GetFileName(FileFullPath); - FileIsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; + IsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; + } + catch (Exception e) + { + ExceptionLog.Add(e); } } else @@ -54,7 +58,8 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( public string? FileFullPath { get; } public string? FileName { get; } - public bool? FileIsDirectory { get; private set; } + public bool? IsDirectory { get; } + public bool? IsFileHandle { get; } /// /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. @@ -79,7 +84,7 @@ private unsafe string TryGetFinalPath() buffer = Marshal.ReAllocHGlobal(buffer, (IntPtr)length); fullName = new((char*)buffer); - bufLength = GetFinalPathNameByHandle(SysHandleEx.ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + bufLength = GetFinalPathNameByHandle(ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); } return fullName.ToString(); } diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 49d2304..df4780f 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -262,8 +262,5 @@ protected override bool ReleaseHandle() return IsClosed; } - /// - /// Serialize the current instance to JSON-formatted text - /// - public override string? ToString() => JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + internal SafeHandle ToSafeFileHandle() => SysHandleEx.ToSafeFileHandle(); } From f0763ab806d08a3faa4f18a6f0f7e89c8ba9d39b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 05:42:03 -0800 Subject: [PATCH 077/306] refactor: add constructor for copying a SafeHandleEx --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index df4780f..0814d11 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -19,6 +19,9 @@ namespace deadlock_dotnet_sdk.Domain; /// public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { + public SafeHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) + { } + /// /// Initializes a new instance of the SafeHandleEx class from a , specifying whether the handle is to be reliably released. /// From 0e2083a926f4da14873820a61e8ddff6ef1b55c6 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 05:43:40 -0800 Subject: [PATCH 078/306] refactor: handle System process id issues; move GetFullProcessImageName --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 114 +++++++++++---------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 0814d11..f40f4a7 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -29,6 +29,7 @@ public SafeHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(false) { SysHandleEx = sysHandleEx; + try { HandleObjectType = SysHandleEx.GetHandleObjectType(); @@ -37,15 +38,20 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE { ExceptionLog.Add(e); } + + // Get additional details from the handle's owner process try { - //Process.EnterDebugMode(); Best practice: only call this once. - /** Open handle for process */ // PROCESS_QUERY_LIMITED_INFORMATION is necessary for QueryFullProcessImageName // PROCESS_QUERY_LIMITED_INFORMATION + PROCESS_VM_READ for reading PEB from the process's memory space. // if we need to duplicate a handle later, we'll use PROCESS_DUP_HANDLE + if (ProcessId == 4) + { + ProcessName = "System"; + } + HANDLE rawHandle = OpenProcess( dwDesiredAccess: PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, bInheritHandle: (BOOL)false, @@ -149,6 +155,58 @@ public void UnlockSystemHandle() } } + /// + /// A wrapper for QueryFullProcessImageName, a system function that circumvents 32-bit process limitations when permitted the PROCESS_QUERY_LIMITED_INFORMATION right. + /// + /// A SafeProcessHandle opened with + /// The path to the executable image. + /// The process handle is invalid + /// QueryFullProcessImageName failed. See Exception message for details. + private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) + { + if (hProcess.IsInvalid) + throw new ArgumentException("The process handle is invalid", nameof(hProcess)); + + uint size = 260 + 1; + uint bufferLength = size; + string retVal = ""; + + using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); + if (QueryFullProcessImageName( + hProcess: hProcess, + dwFlags: PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + lpExeName: buffer, + lpdwSize: ref size)) + { + retVal = buffer.ToString(); + } + else if (bufferLength < size) + { + using PWSTR newBuffer = Marshal.ReAllocHGlobal((IntPtr)buffer.Value, (IntPtr)size); + if (QueryFullProcessImageName( + hProcess, + PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + newBuffer, + ref size)) + { + retVal = newBuffer.ToString(); + } + else + { + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); + } + } + else + { + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); + } + + // PWSTR instances are freed by their using blocks' finalizers + return retVal; + } + /// /// Try to get a process's command line from its PEB /// @@ -208,57 +266,9 @@ private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) } /// - /// A wrapper for QueryFullProcessImageName + /// Release all resources owned by the current process that are associated with this handle. /// - /// A SafeProcessHandle opened with - /// The path to the executable image. - /// The process handle is invalid - /// QueryFullProcessImageName failed. See Exception message for details. - private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) - { - if (hProcess.IsInvalid) - throw new ArgumentException("The process handle is invalid", nameof(hProcess)); - - uint size = 260 + 1; - uint bufferLength = size; - string retVal = ""; - - using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); - if (QueryFullProcessImageName( - hProcess: hProcess, - dwFlags: PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - lpExeName: buffer, - lpdwSize: ref size)) - { - retVal = buffer.ToString(); - } - else if (bufferLength < size) - { - using PWSTR newBuffer = Marshal.ReAllocHGlobal((IntPtr)buffer.Value, (IntPtr)size); - if (QueryFullProcessImageName( - hProcess, - PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - newBuffer, - ref size)) - { - retVal = newBuffer.ToString(); - } - else - { - // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - throw new Win32Exception(); - } - } - else - { - // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - throw new Win32Exception(); - } - - // PWSTR instances are freed by their using blocks' finalizers - return retVal; - } - + /// protected override bool ReleaseHandle() { Close(); From 44bccb87e7931322545d7d3d33c59b85c7108599 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 05:45:43 -0800 Subject: [PATCH 079/306] refactor: ensure correct ACCESS_MASK is used --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index f40f4a7..e70ad4e 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -7,6 +7,7 @@ using Windows.Win32.Security; using Windows.Win32.System.Threading; using static Windows.Win32.PInvoke; +using ACCESS_MASK = PInvoke.Kernel32.ACCESS_MASK; namespace deadlock_dotnet_sdk.Domain; @@ -92,7 +93,6 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE public ushort CreatorBackTraceIndex => SysHandleEx.CreatorBackTraceIndex; /// public ACCESS_MASK GrantedAccess => SysHandleEx.GrantedAccess; - /// /// The Type of the object as a string. /// From 023a2df68fc28931ef534a925af2fa9fe60f45e8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 05:47:16 -0800 Subject: [PATCH 080/306] refactor: remove unused usings, update prop type and ref --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index e70ad4e..77c49ac 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -1,10 +1,8 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; -using System.Text.Json; using Microsoft.Win32.SafeHandles; using Windows.Win32.Foundation; -using Windows.Win32.Security; using Windows.Win32.System.Threading; using static Windows.Win32.PInvoke; using ACCESS_MASK = PInvoke.Kernel32.ACCESS_MASK; @@ -84,10 +82,7 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE internal NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX SysHandleEx { get; } - public unsafe void* Object => SysHandleEx.Object; - /// - /// cast to uint - /// + public unsafe UIntPtr Object => SysHandleEx.ObjectPointer; public uint ProcessId => (uint)SysHandleEx.UniqueProcessId; public nuint HandleValue => SysHandleEx.HandleValue; public ushort CreatorBackTraceIndex => SysHandleEx.CreatorBackTraceIndex; From 780275a068bd1c1d8ffe002ec5783c0ad729378c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 05:48:17 -0800 Subject: [PATCH 081/306] refactor: change ToString from JSON serialization to formatted text --- .../Domain/SafeFileHandleEx.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index d9d5bf6..7632817 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -1,5 +1,5 @@ +using System.Data; using System.Runtime.InteropServices; -using System.Text.Json; using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; using static deadlock_dotnet_sdk.Domain.NativeMethods; @@ -110,6 +110,30 @@ private unsafe string TryGetFinalPath() public override string ToString() { - return JsonSerializer.Serialize(this, options: new() { WriteIndented = true }); + string[] exLog = ExceptionLog.Cast().ToArray(); + for (int i = 0; i < exLog.Length; i++) + { + exLog[i] = $" {exLog[i]}".Replace("\n", "\n "); + } + + return @$"{GetType().Name} hash:{GetHashCode()} + {nameof(CreatorBackTraceIndex)} : {CreatorBackTraceIndex} + {nameof(FileFullPath)} : {FileFullPath} + {nameof(IsDirectory)} : {IsDirectory} + {nameof(FileName)} : {FileName} + {nameof(GrantedAccess)} : {GrantedAccess} + {nameof(handle)} : {handle} + {nameof(HandleObjectType)} : {HandleObjectType} + {nameof(HandleValue)} : {HandleValue} + {nameof(IsClosed)} : {IsClosed} + {nameof(IsFileHandle)} : {IsFileHandle} + {nameof(IsInvalid)} : {IsInvalid} + {nameof(Object)} : {Object} + {nameof(ProcessCommandLine)} : {ProcessCommandLine} + {nameof(ProcessId)} : {ProcessId} + {nameof(ProcessMainModulePath)} : {ProcessMainModulePath} + {nameof(ProcessName)} : {ProcessName} + {nameof(ExceptionLog)} : ... + " + exLog; } } From 7d3ae8ee288219c7d120ab8f00a7c64991140750 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 05:49:32 -0800 Subject: [PATCH 082/306] refactor: change Exception types, misc changes in GetSystemHandleInfoEx --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 38 ++++++++++----------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 2f1dc4a..15b22ca 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -178,13 +178,14 @@ bool Discard(SafeFileHandleEx h) /// Get a Span of via ///
/// Heavily influenced by ProcessHacker/SystemInformer - private unsafe static Span GetSystemHandleInfoEx() + /// + /// + internal unsafe static ReadOnlySpan GetSystemHandleInfoEx() { const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; const uint PH_LARGE_BUFFER_SIZE = 256 * 1024 * 1024; // 256 Mebibytes - const uint STATUS_INSUFFICIENT_RESOURCES = 0xC000009A; - uint systemInformationLength = (uint)sizeof(SYSTEM_HANDLE_INFORMATION_EX); - SYSTEM_HANDLE_INFORMATION_EX* pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.AllocHGlobal(sizeof(SYSTEM_HANDLE_INFORMATION_EX)); + uint systemInformationLength = (uint)Marshal.SizeOf(); + SYSTEM_HANDLE_INFORMATION_EX* pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.AllocHGlobal(Marshal.SizeOf()); uint returnLength = 0; NTSTATUS status = NtQuerySystemInformation( @@ -194,9 +195,12 @@ private unsafe static Span GetSystemHandleInf ReturnLength: ref returnLength ); - for (uint attempts = 0; status == STATUS_INFO_LENGTH_MISMATCH && attempts < 10; attempts++) + for (uint attempts = 0; status.Value == NTSTATUS.Code.STATUS_INFO_LENGTH_MISMATCH && attempts < 10; attempts++) { - systemInformationLength = returnLength; + /** The value of returnLength depends on how many handles are open. + Handles may be opened or closed before, during, and after this operation, so the return length is rarely correct. + */ + systemInformationLength = (uint)(returnLength * 1.5); pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.ReAllocHGlobal((IntPtr)pSysInfoBuffer, (IntPtr)systemInformationLength); status = NtQuerySystemInformation( @@ -207,11 +211,11 @@ ref returnLength ); } - if (!status.IsSuccessful) + if (status != NTSTATUS.Code.STATUS_SUCCESS) { // Fall back to using the previous code that we've used since Windows XP (dmex) systemInformationLength = 0x10000; - Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); + //Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.ReAllocHGlobal((IntPtr)pSysInfoBuffer, (IntPtr)systemInformationLength); while ((status = NtQuerySystemInformation( @@ -226,29 +230,23 @@ ref returnLength // Fail if we're resizing the buffer to something very large. if (systemInformationLength > PH_LARGE_BUFFER_SIZE) - { - throw new Win32Exception(unchecked((int)STATUS_INSUFFICIENT_RESOURCES)); - } + throw new NTStatusException(NTSTATUS.Code.STATUS_BUFFER_OVERFLOW); - pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.AllocHGlobal((int)systemInformationLength); + pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.ReAllocHGlobal(pv: (IntPtr)pSysInfoBuffer, cb: (IntPtr)systemInformationLength); } } - if (!status.IsSuccessful) + if (status != NTSTATUS.Code.STATUS_SUCCESS) { Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); - Marshal.FreeHGlobal((IntPtr)returnLength); - throw new Win32Exception((int)status); + throw new NTStatusException(status); } - SYSTEM_HANDLE_INFORMATION_EX retVal = *pSysInfoBuffer; - - retVal.CheckAccess(); + var retVal = (*pSysInfoBuffer).AsSpan(); Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); - Marshal.FreeHGlobal((IntPtr)returnLength); - return retVal.AsSpan(); + return retVal; } #endregion Methods From 572ce3268426eeb52a1c19177c36a13cfb1f670b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 3 Jan 2023 05:50:16 -0800 Subject: [PATCH 083/306] refactor: use conditional access --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 15b22ca..fc1b8d9 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -165,7 +165,7 @@ bool Discard(SafeFileHandleEx h) return !filter.HasFlag(HandlesFilter.IncludeNonFiles); // When requested, keep non-File object handle. Else, discard. } // Discard handle if Query and file's path are not null and file's path does not contain query */ - return (query is not null) && (h.FileFullPath is not null) && (!h.FileFullPath.Contains(query.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))); + return (query is not null) && (h.FileFullPath?.Contains(query.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)) == false); } else { From 41b4c5aee2833be8dd226fa6a9ca8fb8943282cb Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 6 Jan 2023 21:17:06 -0800 Subject: [PATCH 084/306] refactor: if PID == 4, don't open process handle --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 36 ++++++++++++---------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 77c49ac..0fb56f5 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -50,29 +50,31 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE { ProcessName = "System"; } + else + { + HANDLE rawHandle = OpenProcess( + dwDesiredAccess: PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, + bInheritHandle: (BOOL)false, + dwProcessId: ProcessId + ); - HANDLE rawHandle = OpenProcess( - dwDesiredAccess: PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, - bInheritHandle: (BOOL)false, - dwProcessId: ProcessId - ); + if (rawHandle.IsNull) + throw new Win32Exception("Failed to open process handle with access rights 'PROCESS_QUERY_LIMITED_INFORMATION' and 'PROCESS_VM_READ'. The following information will be unavailable: main module full name, process name, process' startup command line"); - if (rawHandle.IsNull) - throw new Win32Exception("Failed to open process handle with access rights 'PROCESS_QUERY_LIMITED_INFORMATION' and 'PROCESS_VM_READ'. The following information will be unavailable: main module full name, process name, process' startup command line"); + using SafeProcessHandle hProcess = new(rawHandle, true); - using SafeProcessHandle hProcess = new(rawHandle, true); + /** Get main module's full path */ + ProcessMainModulePath = GetFullProcessImageName(hProcess); - /** Get main module's full path */ - ProcessMainModulePath = GetFullProcessImageName(hProcess); + /** Get Process's name */ + if (!string.IsNullOrWhiteSpace(ProcessMainModulePath)) + { + ProcessName = Path.GetFileNameWithoutExtension(ProcessMainModulePath); + } - /** Get Process's name */ - if (!string.IsNullOrWhiteSpace(ProcessMainModulePath)) - { - ProcessName = Path.GetFileNameWithoutExtension(ProcessMainModulePath); + /** Get process's possibly-overwritten command line from the PEB struct in its memory space */ + ProcessCommandLine = GetProcessCommandLine(hProcess); } - - /** Get process's possibly-overwritten command line from the PEB struct in its memory space */ - ProcessCommandLine = GetProcessCommandLine(hProcess); } catch (Exception e) { From 1f1dc4f4a678ac9c59cb7333e8d5b176b619203a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 13 Feb 2023 04:42:16 -0800 Subject: [PATCH 085/306] refactor: add more native definitions, supporting Types Some are platform-specific, hence the 32/64 suffixes. Although PEB32 and PEB64 are done, plain ol' PEB isn't done yet. I exploited FieldOffsetAttribute quite bit in these platform-specific definitions. Additionally, some of the less important, platform specific type definitions were skipped to save time. --- deadlock-dotnet-sdk/Domain/SafeBufferT.cs | 16 + deadlock-dotnet-sdk/NativeMethods.txt | 7 + .../Windows.Win32/32 and 64 suffixes.md | 4 + .../Windows.Win32/Foundation/HANDLE32.cs | 17 + .../Windows.Win32/Foundation/HANDLE64.cs | 17 + .../Windows.Win32/Foundation/NTSTATUS.cs | 17 +- .../Windows.Win32/Foundation/PCWSTR.cs | 14 + .../Foundation/UNICODE_STRING.cs | 30 +- .../Foundation/UNICODE_STRING32.cs | 19 ++ .../Foundation/UNICODE_STRING64.cs | 11 + .../Windows.Win32/LARGE_INTEGER.cs | 12 + deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs | 47 ++- .../System/Kernel/KAFFINITY32.cs | 8 + .../System/Kernel/KAFFINITY64.cs | 8 + .../System/Kernel/LIST_ENTRY32.cs | 31 ++ .../System/Kernel/LIST_ENTRY64.cs | 31 ++ .../System/Kernel/RTL_BALANCED_NODE32.cs | 22 ++ .../System/Kernel/RTL_BALANCED_NODE64.cs | 22 ++ .../System/Threading/API_SET_NAMESPACE.cs | 13 + .../System/Threading/CURDIR32.cs | 17 + .../System/Threading/CURDIR64.cs | 20 ++ .../System/Threading/KPRIORITY.cs | 2 - .../Windows.Win32/System/Threading/PEB.cs | 163 +++++++--- .../Windows.Win32/System/Threading/PEB32.cs | 303 ++++++++++++++++++ .../Windows.Win32/System/Threading/PEB64.cs | 292 +++++++++++++++++ .../System/Threading/PEB_AppCompat.cs | 38 +++ .../System/Threading/PEB_BitField.cs | 33 ++ .../System/Threading/PEB_CrossProcess.cs | 24 ++ .../Windows.Win32/System/Threading/PEB_Ex.cs | 47 +++ .../System/Threading/PEB_LDR_DATA32.cs | 27 ++ .../System/Threading/PEB_LDR_DATA64.cs | 24 ++ .../System/Threading/PEB_LeapSecond.cs | 7 + .../System/Threading/PEB_Tracing.cs | 12 + ...NE.cs => PPS_POST_PROCESS_INIT_ROUTINE.cs} | 5 +- .../Threading/PROCESS_BASIC_INFORMATION.cs | 63 ++-- .../Threading/PROCESS_BASIC_INFORMATION32.cs | 19 ++ .../Threading/PROCESS_BASIC_INFORMATION64.cs | 19 ++ .../Threading/RTL_CRITICAL_SECTION32.cs | 12 + .../Threading/RTL_CRITICAL_SECTION64.cs | 12 + .../Threading/RTL_DRIVE_LETTER_CURDIR32.cs | 58 ++++ .../Threading/RTL_DRIVE_LETTER_CURDIR64.cs | 64 ++++ .../RTL_USER_PROCESS_PARAMETERS32.cs | 69 ++++ .../RTL_USER_PROCESS_PARAMETERS64.cs | 67 ++++ .../LDR_DATA_TABLE_ENTRY32.cs | 125 ++++++++ .../LDR_DATA_TABLE_ENTRY64.cs | 126 ++++++++ .../WindowsProgramming/LDR_DLL_LOAD_REASON.cs | 16 + .../WindowsProgramming/LdrEntryFlags.cs | 46 +++ .../OBJECT_TYPES_INFORMATION.cs | 4 +- .../OBJECT_TYPE_INFORMATION.cs | 2 +- .../Windows.Win32/UIntPtr32.cs | 28 ++ .../Windows.Win32/UIntPtr64.cs | 22 ++ .../Windows.Win32/ULARGE_INTEGER.cs | 12 + 52 files changed, 2021 insertions(+), 103 deletions(-) create mode 100644 deadlock-dotnet-sdk/Domain/SafeBufferT.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/32 and 64 suffixes.md create mode 100644 deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/Foundation/PCWSTR.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/LARGE_INTEGER.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/API_SET_NAMESPACE.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_AppCompat.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_BitField.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_CrossProcess.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Ex.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LeapSecond.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Tracing.cs rename deadlock-dotnet-sdk/Windows.Win32/System/Threading/{PS_POST_PROCESS_INIT_ROUTINE.cs => PPS_POST_PROCESS_INIT_ROUTINE.cs} (90%) create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DLL_LOAD_REASON.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LdrEntryFlags.cs rename deadlock-dotnet-sdk/Windows.Win32/{ => System/WindowsProgramming}/OBJECT_TYPES_INFORMATION.cs (93%) rename deadlock-dotnet-sdk/Windows.Win32/{ => System/WindowsProgramming}/OBJECT_TYPE_INFORMATION.cs (98%) create mode 100644 deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/ULARGE_INTEGER.cs diff --git a/deadlock-dotnet-sdk/Domain/SafeBufferT.cs b/deadlock-dotnet-sdk/Domain/SafeBufferT.cs new file mode 100644 index 0000000..cb904c3 --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/SafeBufferT.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace deadlock_dotnet_sdk.Domain; + +public class SafeBuffer : SafeBuffer +{ + public SafeBuffer(bool ownsHandle) : base(ownsHandle) + { + } + + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(handle); + return true; + } +} diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index f8e9076..bbefbea 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -66,3 +66,10 @@ PUBLIC_OBJECT_TYPE_INFORMATION //VirtualQuery GENERIC_MAPPING +LDR_DATA_TABLE_ENTRY +PEB32 +PEB64 +ProcessCommandLineInformation +NtWow64ReadVirtualMemory64 +LARGE_INTEGER +RTL_CRITICAL_SECTION diff --git a/deadlock-dotnet-sdk/Windows.Win32/32 and 64 suffixes.md b/deadlock-dotnet-sdk/Windows.Win32/32 and 64 suffixes.md new file mode 100644 index 0000000..05c9ce6 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/32 and 64 suffixes.md @@ -0,0 +1,4 @@ +The "32" and "64" suffixes indicate the bitness of the pointers (`UIntPtr32`, `UIntPtr64`¹) and the bitness of the pointers' owner process. + + +¹ The generic Type `T` indicates the pointer type is a pointer to an object of type `T`. This is intended to mimic conventional pointer types' syntax. `UIntPtr32` is intended to be equivalent to a 32-bit process's `int*`. diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs new file mode 100644 index 0000000..71e4390 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs @@ -0,0 +1,17 @@ +namespace Windows.Win32.Foundation; + +internal struct HANDLE32 +{ + internal readonly UIntPtr32 Value { get; init; } + + public static explicit operator HANDLE32(UIntPtr32 v) => new() { Value = v }; + public static implicit operator UIntPtr32(HANDLE32 v) => v.Value; +} + +internal struct HANDLE32 where T : unmanaged +{ + internal readonly UIntPtr32 Value { get; init; } + + public static explicit operator HANDLE32(UIntPtr32 v) => new() { Value = v }; + public static implicit operator UIntPtr32(HANDLE32 v) => v.Value; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs new file mode 100644 index 0000000..571549a --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs @@ -0,0 +1,17 @@ +namespace Windows.Win32.Foundation; + +internal struct HANDLE64 +{ + internal readonly UIntPtr64 Value { get; init; } + + public static explicit operator HANDLE64(UIntPtr64 v) => new() { Value = v }; + public static implicit operator UIntPtr64(HANDLE64 v) => v.Value; +} + +internal struct HANDLE64 where T : unmanaged +{ + internal readonly UIntPtr64 Value { get; init; } + + public static explicit operator HANDLE64(UIntPtr64 v) => new() { Value = v }; + public static implicit operator UIntPtr64(HANDLE64 v) => v.Value; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs index bf37c9e..a3c663f 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs @@ -1,13 +1,22 @@ +using PInvoke; +/// /// This file supplements code generated by CsWin32 -using NTStatusException = PInvoke.NTStatusException; - +/// namespace Windows.Win32.Foundation; readonly partial struct NTSTATUS { - public bool IsSuccessful => SeverityCode == Severity.Success; + public bool IsSuccessful => SeverityCode is Severity.Success; + + /// + public void ThrowOnError() + { + if (SeverityCode == Severity.Error) + throw new NTStatusException(this); + } - public NTStatusException GetNTStatusException() => new(this); + /// + public string GetMessage() => ((global::PInvoke.NTSTATUS)this).GetMessage(); public static implicit operator global::PInvoke.NTSTATUS(NTSTATUS v) => new(v.Value); public static implicit operator NTSTATUS(global::PInvoke.NTSTATUS v) => new(v.AsInt32); diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/PCWSTR.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/PCWSTR.cs new file mode 100644 index 0000000..31c1a7a --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/PCWSTR.cs @@ -0,0 +1,14 @@ +/// This file supplements code generated by CsWin32 +using System.Runtime.InteropServices; + +namespace Windows.Win32.Foundation; + +unsafe readonly partial struct PCWSTR : IDisposable +{ + /// + /// Free the PWSTR's memory with Marshal.FreeHGlobal(IntPtr) + /// + public void Dispose() => Marshal.FreeHGlobal((IntPtr)Value); + + public static explicit operator PCWSTR(IntPtr v) => new((char*)v); +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs index df49dad..672a245 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs @@ -3,15 +3,43 @@ namespace Windows.Win32.Foundation; +/// +/// The UNICODE_STRING structure is used to define Unicode strings. +/// +/// +/// The UNICODE_STRING structure is used to pass Unicode strings. Use RtlUnicodeStringInit or RtlUnicodeStringInitEx to initialize a UNICODE_STRING structure. +/// If the string is null-terminated, Length does not include the trailing null character. +/// The MaximumLength is used to indicate the length of Buffer so that if the string is passed to a conversion routine such as RtlAnsiStringToUnicodeString the returned string does not exceed the buffer size. +/// +/// - Length -- the length of the string in bytes. +/// - MaximumLength -- the length of Buffer in bytes. +/// - Buffer -- the address of the string. +/// +/// partial struct UNICODE_STRING : IDisposable { + public UNICODE_STRING(ushort maximumLength, PWSTR buffer) + { + Length = (ushort)buffer.Length; + MaximumLength = maximumLength; + Buffer = buffer; + } + public UNICODE_STRING(ushort maximumLength) + { + Length = 0; + MaximumLength = maximumLength; + Buffer = Marshal.AllocHGlobal(maximumLength); + } + + public uint CharCount => (uint)ToStringLength().Length; + public void Dispose() { Buffer.Dispose(); } /// - /// Allocates a managed string and copies a specified number of characters from an unmanaged Unicode string into it. + /// Allocates a managed string and copies `(Length / 2)` number of characters from an unmanaged Unicode string into it. /// public unsafe string ToStringLength() => Marshal.PtrToStringUni((IntPtr)Buffer.Value, Length / 2); public string? ToStringZ() => Buffer.ToString(); diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING32.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING32.cs new file mode 100644 index 0000000..c456d44 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING32.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Windows.Win32.Foundation; + +/// +/// The UNICODE_STRING structure is used to define Unicode strings. +/// +/// +/// The UNICODE_STRING structure is used to pass Unicode strings. Use RtlUnicodeStringInit or RtlUnicodeStringInitEx to initialize a UNICODE_STRING structure. +/// If the string is null-terminated, Length does not include the trailing null character. +/// The MaximumLength is used to indicate the length of Buffer so that if the string is passed to a conversion routine such as RtlAnsiStringToUnicodeString the returned string does not exceed the buffer size. +/// +[StructLayout(LayoutKind.Sequential, Size = 0x08)] +internal struct UNICODE_STRING32 +{ + public ushort Length; + public ushort MaximumLength; + public UIntPtr32 Buffer; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING64.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING64.cs new file mode 100644 index 0000000..864d81c --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING64.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Windows.Win32.Foundation; + +[StructLayout(LayoutKind.Sequential, Size = 0x10)] +internal struct UNICODE_STRING64 +{ + public ushort Length; + public ushort MaximumLength; + public UIntPtr64 Buffer; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/LARGE_INTEGER.cs b/deadlock-dotnet-sdk/Windows.Win32/LARGE_INTEGER.cs new file mode 100644 index 0000000..4f445f6 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/LARGE_INTEGER.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Windows.Win32; + +[StructLayout(LayoutKind.Explicit)] +internal struct LARGE_INTEGER +{ + [FieldOffset(0x00)] internal uint LowPart; + [FieldOffset(0x04)] internal int HighPart; + [FieldOffset(0x00)] internal long QuadPart; + +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs index 9db7832..fe1095d 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs @@ -1,7 +1,10 @@ /// This file supplements code generated by CsWin32 using System.ComponentModel; using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using Microsoft.Win32.SafeHandles; using Windows.Win32.Foundation; +using Windows.Win32.System.Threading; using MemInfo32 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION32; using MemInfo64 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION64; @@ -35,7 +38,7 @@ static partial class PInvoke /// [DllImport("ntdll.dll", ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [global::System.Runtime.Versioning.SupportedOSPlatform("windows5.0")] + [SupportedOSPlatform("windows5.0")] internal unsafe static extern NTSTATUS_plus NtDuplicateObject( HANDLE SourceProcessHandle, HANDLE SourceHandle, @@ -47,10 +50,9 @@ uint Options ); /// If successful and the current process is 32-bit, returns a MEMORY_BASIC_INFORMATION32 structure. If the current process is 64-bit, returns a MEMORY_BASIC_INFORMATION64 structure. - /// + /// internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(nuint lpAddress) { - var is64bit = Environment.Is64BitProcess; SIZE_T bufferSize = default; int size64 = default; int size32 = default; @@ -59,18 +61,18 @@ internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(n GCHandle h64 = default; GCHandle h32 = default; - if (is64bit) + if (Environment.Is64BitProcess) { //pBuffer = (void*)Marshal.AllocHGlobal(size64); size64 = Marshal.SizeOf(); h64 = GCHandle.Alloc(default(MemInfo64), GCHandleType.Pinned); - bufferSize = __VirtualQuery((void*)lpAddress, (void*)h64.AddrOfPinnedObject(), (SIZE_T)size64); + bufferSize = VirtualQuery((void*)lpAddress, (void*)h64.AddrOfPinnedObject(), (SIZE_T)size64); } else // is 32-bit process { size32 = Marshal.SizeOf(); h32 = GCHandle.Alloc(default(MemInfo32), GCHandleType.Pinned); - bufferSize = __VirtualQuery((void*)lpAddress, (void*)h32.AddrOfPinnedObject(), (SIZE_T)size32); + bufferSize = VirtualQuery((void*)lpAddress, (void*)h32.AddrOfPinnedObject(), (SIZE_T)size32); } if (bufferSize != 0) @@ -83,7 +85,7 @@ internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(n { pBuffer = (void*)Marshal.AllocHGlobal((nint)bufferSize); - if ((bufferSize = __VirtualQuery((void*)lpAddress, pBuffer, bufferSize)) != 0) + if ((bufferSize = VirtualQuery((void*)lpAddress, pBuffer, bufferSize)) != 0) return (default, memInfo64: *(MemInfo64*)pBuffer); else throw new Win32Exception(); @@ -100,6 +102,33 @@ internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(n } } + [DllImport("ntdll.dll", ExactSpelling = true, EntryPoint = "NtWow64QueryInformationProcess64")] + internal static extern unsafe NTSTATUS_plus NtWow64QueryInformationProcess64( + [In] SafeProcessHandle ProcessHandle, + PROCESSINFOCLASS ProcessInformationClass, + [Out] void* ProcessInformation, + [In] uint ProcessInformationLength, + [Out] uint* ReturnLength + ); + + [DllImport("ntdll.dll", ExactSpelling = true, EntryPoint = "NtWow64ReadVirtualMemory64")] + internal static extern unsafe NTSTATUS_plus NtWow64ReadVirtualMemory64( + [In] SafeProcessHandle ProcessHandle, + [In] UIntPtr64 BaseAddress, + [Out] void* Buffer, + [In] ulong Size, + [Out] ulong* NumberOfBytesRead + ); + + /// + /// A SafeProcessHandle to the + [SupportedOSPlatform("windows5.1.2600")] + internal static unsafe SafeProcessHandle OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS dwDesiredAccess, bool bInheritHandle, uint dwProcessId) + { + HANDLE __result = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); + return new SafeProcessHandle(__result, ownsHandle: true); + } + /// /// Retrieves information about a range of pages in the virtual address space of the calling process. /// To retrieve information about a range of pages in the address space of another process, use the VirtualQueryEx function. @@ -118,8 +147,8 @@ internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(n // warning PInvoke005: This API is only available when targeting a specific CPU architecture.AnyCPU cannot generate this API. [DllImport("Kernel32.dll", ExactSpelling = true, EntryPoint = "VirtualQuery", SetLastError = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [global::System.Runtime.Versioning.SupportedOSPlatform("windows5.1")] - private unsafe static extern SIZE_T __VirtualQuery( + [SupportedOSPlatform("windows5.1")] + private unsafe static extern SIZE_T VirtualQuery( void* lpAddress, void* lpBuffer, SIZE_T dwLength diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY32.cs new file mode 100644 index 0000000..92dcf35 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY32.cs @@ -0,0 +1,8 @@ +namespace Windows.Win32.System.Kernel; + +internal struct KAFFINITY32 +{ + public uint Value; + + public static implicit operator uint(KAFFINITY32 v) => v.Value; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY64.cs new file mode 100644 index 0000000..4b3cac5 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY64.cs @@ -0,0 +1,8 @@ +namespace Windows.Win32.System.Kernel; + +internal struct KAFFINITY64 +{ + public ulong Value; + + public static implicit operator ulong(KAFFINITY64 v) => v.Value; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs new file mode 100644 index 0000000..9c44d85 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs @@ -0,0 +1,31 @@ +namespace Windows.Win32.System.Kernel; + +/// +internal struct LIST_ENTRY32 where T : unmanaged +{ + /// + /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. + /// Read more on docs.microsoft.com. + /// + internal unsafe UIntPtr32> Flink; + /// + /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. + /// Read more on docs.microsoft.com. + /// + internal unsafe UIntPtr32> Blink; +} + +/// +internal struct LIST_ENTRY32 +{ + /// + /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. + /// Read more on docs.microsoft.com. + /// + internal unsafe UIntPtr32 Flink; + /// + /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. + /// Read more on docs.microsoft.com. + /// + internal unsafe UIntPtr32 Blink; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs new file mode 100644 index 0000000..88b7f78 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs @@ -0,0 +1,31 @@ +namespace Windows.Win32.System.Kernel; + +/// +internal struct LIST_ENTRY64 where T : unmanaged +{ + /// + /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. + /// Read more on docs.microsoft.com. + /// + internal unsafe UIntPtr64> Flink; + /// + /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. + /// Read more on docs.microsoft.com. + /// + internal unsafe UIntPtr64> Blink; +} + +/// +internal struct LIST_ENTRY64 +{ + /// + /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. + /// Read more on docs.microsoft.com. + /// + internal unsafe UIntPtr64 Flink; + /// + /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. + /// Read more on docs.microsoft.com. + /// + internal unsafe UIntPtr64 Blink; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE32.cs new file mode 100644 index 0000000..6005635 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE32.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace Windows.Win32.System.Kernel; + +/// +/// NT 6.2 (Win8) and higher +/// +/// +[StructLayout(LayoutKind.Explicit, Size = 0x0C)] +internal unsafe struct RTL_BALANCED_NODE32 +{ + [FieldOffset(0x00)] internal fixed uint _Children[2]; + internal UIntPtr32[] Children => new UIntPtr32[] { _Children[0], _Children[1] }; + [FieldOffset(0x00)] internal UIntPtr32 Left; + [FieldOffset(0x04)] internal UIntPtr32 Right; + + [FieldOffset(0x08)] internal UIntPtr32 ParentValue; + /// applies if the node is in a Red Black tree + internal byte Red => (byte)(ParentValue & 0b1); + /// applies if the node is in an AVL tree + internal byte Balance => (byte)((ParentValue >> 1) & 0b11); +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs new file mode 100644 index 0000000..4f420eb --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace Windows.Win32.System.Kernel; + +/// +/// NT 6.2 (Win8) and higher +/// +/// +[StructLayout(LayoutKind.Explicit, Size = 0x18)] +internal unsafe struct RTL_BALANCED_NODE64 +{ + [FieldOffset(0x00)] internal fixed uint _Children[2]; + internal UIntPtr64[] Children => new UIntPtr64[] { _Children[0], _Children[1] }; + [FieldOffset(0x00)] internal UIntPtr64 Left; + [FieldOffset(0x08)] internal UIntPtr64 Right; + + [FieldOffset(0x10)] internal UIntPtr64 ParentValue; + /// applies if the node is in a Red Black tree + internal byte Red => (byte)(ParentValue & 0b1); + /// applies if the node is in an AVL tree + internal byte Balance => (byte)((ParentValue >> 1) & 0b11); +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/API_SET_NAMESPACE.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/API_SET_NAMESPACE.cs new file mode 100644 index 0000000..20ae01c --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/API_SET_NAMESPACE.cs @@ -0,0 +1,13 @@ +namespace Windows.Win32.System.Threading +{ + public struct API_SET_NAMESPACE + { + public uint Version; + public uint Size; + public uint Flags; + public uint Count; + public uint EntryOffset; + public uint HashOffset; + public uint HashFactor; + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR32.cs new file mode 100644 index 0000000..e2fda83 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR32.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; + +namespace Windows.Win32.System.Threading; + +internal partial struct RTL_USER_PROCESS_PARAMETERS32 +{ + /// + /// See + /// + [StructLayout(LayoutKind.Sequential, Size = 0x0C)] + internal struct CURDIR32 + { + public UNICODE_STRING64 DosPath; + public HANDLE64 Handle; + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR64.cs new file mode 100644 index 0000000..18846cc --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR64.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; + +namespace Windows.Win32.System.Threading; + +internal partial struct RTL_USER_PROCESS_PARAMETERS64 +{ + /// + /// See + /// + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + internal struct CURDIR64 + { + public UNICODE_STRING64 DosPath; + /// + /// File Handle to the process's current directory + /// + public HANDLE64 Handle; + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/KPRIORITY.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/KPRIORITY.cs index 9180921..020c7a9 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/KPRIORITY.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/KPRIORITY.cs @@ -1,5 +1,3 @@ -/// This file supplements code generated by CsWin32 - namespace Windows.Win32.System.Threading; /// diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs index 3c613f9..f53f1df 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs @@ -1,4 +1,5 @@ /// This file supplements code generated by CsWin32 +using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; using Windows.Win32.Foundation; @@ -6,53 +7,15 @@ namespace Windows.Win32.System.Threading; -/// -/// all data which must be copied via ReadProcessMemory -/// -class PEB_Ex -{ - public PEB_Ex(uint processId, ref PEB peb) - { - ProcessId = processId; - ReadVmHandle = new(PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, true, processId), true); - Peb = peb; - - unsafe - { - PEB_LDR_DATA* baseAddr = Peb.Ldr; - PEB_LDR_DATA ldr = default; - nuint br = default; - if (PInvoke.ReadProcessMemory(ReadVmHandle, baseAddr, &ldr, (nuint)Marshal.SizeOf(), &br)) - PebLdrData = ldr; - } - - unsafe - { - RTL_USER_PROCESS_PARAMETERS* baseAddr = Peb.ProcessParameters; - RTL_USER_PROCESS_PARAMETERS procParams = default; - nuint br = default; - if (PInvoke.ReadProcessMemory(ReadVmHandle, baseAddr, &procParams, (nuint)Marshal.SizeOf(), &br)) - ProcessParameters = procParams; - } - } - - public uint ProcessId { get; } - public SafeProcessHandle? ReadVmHandle { get; } - public PEB Peb { get; } - - public PEB_LDR_DATA PebLdrData { get; } - public RTL_USER_PROCESS_PARAMETERS ProcessParameters { get; } -} - +// TODO: finish definitions partial struct PEB { - public PEB(PROCESS_BASIC_INFORMATION pbi, out PEB_Ex pebEx) + public PEB(PROCESS_BASIC_INFORMATION pbi) { - this = pbi.Peb; - pebEx = GetPebEx(pbi.ProcessId); + this = pbi.GetPeb(); } - public PEB_Ex GetPebEx(uint processId) => new(processId, ref this); + public PEB_Ex GetPebEx(uint processId) => new(processId, this); public bool InheritedAddressSpace => (BOOLEAN)Reserved1[0]; public bool ReadImageFileExecOptions => (BOOLEAN)Reserved1[1]; @@ -112,6 +75,7 @@ public unsafe PEB_LDR_DATA GetPebLdrData(uint processId) /// ReadProcessMemory failed public unsafe RTL_USER_PROCESS_PARAMETERS GetProcessParameters(uint processId) { + Process.EnterDebugMode(); using SafeProcessHandle hProcess = new(PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, true, processId), true); RTL_USER_PROCESS_PARAMETERS processParameters = default; nuint bytesRead = default; @@ -121,10 +85,10 @@ public unsafe RTL_USER_PROCESS_PARAMETERS GetProcessParameters(uint processId) else throw new Win32Exception(); } - public unsafe IntPtr SubSystemData => (IntPtr)Reserved4._0; - public unsafe IntPtr ProcessHeap => (IntPtr)Reserved4._1; - public unsafe IntPtr FastPebLock => (IntPtr)Reserved4._2; - public unsafe IntPtr IFEOKey => (IntPtr)Reserved5; + public unsafe void* SubSystemData => Reserved4._0; + public unsafe void* ProcessHeap => Reserved4._1; + public unsafe void* FastPebLock => Reserved4._2; + public unsafe void* IFEOKey => Reserved5; #region CrossProcessFlags @@ -156,6 +120,111 @@ private enum CrossProcessFlags : uint public unsafe IntPtr KernelCallbackTable => (IntPtr)Reserved7; public unsafe IntPtr UserSharedInfoPtr => (IntPtr)Reserved7; public uint SystemReserved => Reserved8; + public unsafe uint AtlThunkSListPtr32 + { + get + { + fixed (void** uint0 = &Reserved9._0) + return *(uint*)uint0; + } + } + public unsafe API_SET_NAMESPACE* ApiSetMap + { + get + { + fixed (void** res9_0 = &Reserved9._0) + return (API_SET_NAMESPACE*)((nuint)res9_0 + sizeof(uint)); + } + } + public unsafe uint TlsExpansionCounter + { + get + { + fixed (void** res9_1 = &Reserved9._1) + return *(uint*)(nuint)res9_1 + sizeof(uint); + } + } + public unsafe void* TlsBitmap => Reserved9._2; + public unsafe (uint, uint) TlsBitmapBits + { + get + { + fixed (void** res9_3 = &Reserved9._3) + return (*(uint*)res9_3, *(uint*)((nuint)res9_3 + sizeof(uint))); + } + } + + public unsafe void* AnsiCodePageData // PCPTABLEINFO + => Reserved9._4; + public unsafe void* OemCodePageData // PCPTABLEINFO + => Reserved9._5; + public unsafe void* UnicodeCaseTableData // PNLSTABLEINFO + => Reserved9._6; + + public unsafe uint NumberOfProcessors + { + get + { + fixed (void** res9_7 = &Reserved9._7) + { + return *(uint*)res9_7; + } + } + } + public unsafe uint NtGlobalFlag + { + get + { + fixed (void** res9_7 = &Reserved9._7) + return *(uint*)((nuint)res9_7 + sizeof(uint)); + } + } + + public unsafe ulong CriticalSectionTimeout => (ulong)Reserved9._8; + public unsafe SIZE_T HeapSegmentReserve => (nuint)Reserved9._9; + public unsafe SIZE_T HeapSegmentCommit => (nuint)Reserved9._10; + public unsafe SIZE_T HeapDeCommitTotalFreeThreshold => (nuint)Reserved9._11; + public unsafe SIZE_T HeapDeCommitFreeBlockThreshold => (nuint)Reserved9._12; + + public unsafe uint NumberOfHeaps + { + get + { + fixed (void** res9_13 = &Reserved9._13) + return *(uint*)res9_13; + } + } + public unsafe uint MaximumNumberOfHeaps + { + get + { + fixed (void** res9_13 = &Reserved9._13) + return *(uint*)((nuint)res9_13 + sizeof(uint)); + } + } + public unsafe void** ProcessHeaps // PHEAP + => (void**)Reserved9._14; + + public unsafe void* GdiSharedHandleTable => Reserved9._15; + public unsafe void* ProcessStarterHelper => Reserved9._16; + public unsafe uint GdiDCAttributeList + { + get + { + fixed (void** res9_17 = &Reserved9._17) + return *(uint*)res9_17; + } + } + + public unsafe void* LoaderLock // _RTL_CRITICAL_SECTION + { + get + { + fixed (void** res9_17 = &Reserved9._17) + return *(void**)((nuint)res9_17 + sizeof(uint)); + } + } - //TODO: https://sourcegraph.com/github.com/winsiderss/systeminformer@master/-/blob/phnt/include/ntpebteb.h?L119 + //todo: https://sourcegraph.com/github.com/winsiderss/systeminformer@f5dc1c2d40a839315d2ffbb6c606b89943fc0595/-/blob/phnt/include/ntpebteb.h?L143 + //public unsafe uint OSMajorVersion } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs new file mode 100644 index 0000000..b91060a --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs @@ -0,0 +1,303 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; + +namespace Windows.Win32.System.Threading; +/// +/// https://web.archive.org/web/https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm +/// +/// +[StructLayout(LayoutKind.Explicit)] +internal struct PEB32 +{ + #region INITIAL_PEB + /// Compatibility: all + [FieldOffset(0x00)] internal readonly BOOLEAN InheritedAddressSpace; + /// Compatibility: 3.51 and higher + [FieldOffset(0x01)] internal readonly BOOLEAN ReadImageFileExecOptions; + /// + /// Indicates whether the specified process is currently being debugged. The PEB structure, however, is an internal operating-system structure whose layout may change in the future. It is best to use the CheckRemoteDebuggerPresent function instead.
+ /// Compatibility: 3.51 and higher + ///
+ [FieldOffset(0x02)] internal readonly BOOLEAN BeingDebugged; + /// Compatibility: late 5.2 and higher + [FieldOffset(0x03)] internal readonly PEB_BitField BitField; + /// Compatibility: all + [FieldOffset(0x04)] internal readonly HANDLE32 Mutant; + #endregion INITIAL_PEB + + /// Compatibility: all + [FieldOffset(0x08)] internal readonly UIntPtr32 ImageBaseAddress; + /// Compatibility: all
+ /// A pointer to a PEB_LDR_DATA structure that contains information about the loaded modules for the process.
+ [FieldOffset(0x0C)] internal readonly unsafe UIntPtr32 Ldr; + /// Compatibility: All
+ /// A pointer to an RTL_USER_PROCESS_PARAMETERS structure that contains process parameter information such as the command line.
+ [FieldOffset(0x10)] internal readonly unsafe UIntPtr32 ProcessParameters; + /// Compatibility: all
+ /// "SubSystem" refers to WoW64, Posix (via PSXDLL.DLL), or WSL. This stores the per-process data for the relevant subsystem.
+ [FieldOffset(0x14)] internal readonly UIntPtr32 SubSystemData; + /// Compatibility: all + [FieldOffset(0x18)] internal readonly UIntPtr32 ProcessHeap; + + /// Compatibility: 3.10 to 5.0 + [FieldOffset(0x1C), Obsolete] private readonly UIntPtr32 FastPebLock_obsolete; + /// Compatibility: 5.1 and higher + [FieldOffset(0x1C)] internal readonly UIntPtr32 FastPebLock; + /// Compatibility: 3.10 to 5.1 + [FieldOffset(0x20), Obsolete] private readonly UIntPtr32 FastPebLockRoutine; + /// Compatibility: late 5.2 and higher + [FieldOffset(0x20)] internal readonly unsafe UIntPtr32 AtlThunkSListPtr; + /// Compatibility: 3.10 to 5.1 + [FieldOffset(0x24), Obsolete] private readonly UIntPtr32 FastPebUnlockRoutine; + /// Compatibility: 6.0 and higher + [FieldOffset(0x24)] internal readonly UIntPtr32 IFEOKey; + + /// Compatibility: 3.50 to 5.2 + [FieldOffset(0x28), Obsolete] private readonly uint EnvironmentUpdateCount; + /// Compatibility: 6.0 and higher + [FieldOffset(0x28)] internal readonly PEB_CrossProcess CrossProcessFlags; + /// Compatibility: 3.51 and higher + [FieldOffset(0x2C)] internal readonly UIntPtr32 KernelCallBackTable; + /// Compatibility: 6.0 and higher + [FieldOffset(0x2C)] internal readonly UIntPtr32 UserSharedInfoPtr; + /// Compatibility: 3.50 to 4.0 + [FieldOffset(0x30), Obsolete] private readonly HANDLE32 EventLogSection; + /// Compatibility: 3.50 to 4.0 + [FieldOffset(0x34), Obsolete] private readonly UIntPtr32 EventLog; + /// Compatibility: early 5.1; early 5.2
+ /// intended for checking for stack overflow
+ [FieldOffset(0x34), Obsolete] private readonly uint _executionOptions; + /// Compatibility: early 5.1; early 5.2 + [Obsolete] private uint ExecutionOptions => _executionOptions & 0b11; + /// Compatibility: late 5.1; 6.1 and higher + [FieldOffset(0x34)] internal readonly UIntPtr32 AtlThunkSListPtr32; + + /// Compatibility: 3.10 to early 6.0
+ /// Type: PEB_FREE_BLOCK*
+ [FieldOffset(0x38), Obsolete] private readonly UIntPtr32 FreeList; + /// Compatibility: 6.1 and higher + [FieldOffset(0x38)] internal readonly UIntPtr32 ApiSetMap; + + /// Compatibility: all + [FieldOffset(0x3C)] internal readonly uint TlsExpansionCounter; + /// Compatibility: all + [FieldOffset(0x40)] internal readonly UIntPtr32 TlsBitmap; + /// Compatibility: all + [FieldOffset(0x44)] internal unsafe fixed uint TlsBitmapBits[2]; + internal readonly unsafe uint[] TlsBitmapBits_Safe => new uint[2] { TlsBitmapBits[0], TlsBitmapBits[1] }; + + /// Compatibility: all + [FieldOffset(0x4C)] internal readonly UIntPtr32 ReadOnlySharedMemoryBase; + /// Compatibility: 3.10 to 5.2 + [FieldOffset(0x50), Obsolete] private readonly UIntPtr32 ReadOnlySharedMemoryHeap; + /// Compatibility: 6.0 to 6.2 + [FieldOffset(0x50), Obsolete] private readonly UIntPtr32 HotpatchInformation; + /// Compatibility: 1703 and higher + [FieldOffset(0x50)] internal readonly UIntPtr32 SharedData; + /// Compatibility: all + [FieldOffset(0x54)] internal readonly UIntPtr32 ReadOnlyStaticServerData; + /// Compatibility: all + [FieldOffset(0x58)] internal readonly UIntPtr32 AnsiCodePageData; + /// Compatibility: all + [FieldOffset(0x5C)] internal readonly UIntPtr32 OemCodePageData; + /// Compatibility: all + [FieldOffset(0x60)] internal readonly UIntPtr32 UnicodeCaseTableData; + /// Compatibility: 3.51 and higher + [FieldOffset(0x64)] internal readonly uint NumberOfProcessors; + /// Compatibility: 3.51 and higher + [FieldOffset(0x68)] internal readonly uint NtGlobalFlag; + /// Compatibility: 3.10 to 3.50 + [FieldOffset(0x68), Obsolete] private readonly LARGE_INTEGER CriticalSectionTimeout_obsolete; + /// Compatibility: 3.51 and higher + [FieldOffset(0x70)] internal readonly LARGE_INTEGER CriticalSectionTimeout; + + #region Appended for Windows NT 3.51 + /// Compatibility: 3.51 and higher + [FieldOffset(0x78)] internal readonly UIntPtr32 HeapSegmentReserve; + /// Compatibility: 3.51 and higher + [FieldOffset(0x7C)] internal readonly UIntPtr32 HeapSegmentCommit; + /// Compatibility: 3.51 and higher + [FieldOffset(0x80)] internal readonly UIntPtr32 HeapDeCommitTotalFreeThreshold; + /// Compatibility: 3.51 and higher + [FieldOffset(0x84)] internal readonly UIntPtr32 HeapDeCommitFreeBlockThreshold; + /// Compatibility: 3.51 and higher + [FieldOffset(0x88)] internal readonly uint NumberOfHeaps; + /// Compatibility: 3.51 and higher + [FieldOffset(0x8C)] internal readonly uint MaximumNumberOfHeaps; + /// Compatibility: 3.51 and higher + [FieldOffset(0x90)] internal readonly UIntPtr32 ProcessHeaps; + #endregion Appended for Windows NT 3.51 + + #region Appended for Windows NT 4.0 + /// Compatibility: 3.51 and higher + [FieldOffset(0x94)] internal readonly UIntPtr32 GdiSharedHandleTable; + /// Compatibility: 4.0 and higher + [FieldOffset(0x98)] internal readonly UIntPtr32 ProcessTarterHelper; + /// Compatibility: 4.0 and higher + [FieldOffset(0x9C)] internal readonly uint GdiDCAttributeList; + /// Compatibility: 4.0 to 5.1 + [FieldOffset(0xA0), Obsolete] private readonly UIntPtr32 LoaderLock_obsolete; + /// Compatibility: 5.2 and higher + [FieldOffset(0xA0)] internal readonly UIntPtr32 LoaderLock; + /// Compatibility: 4.0 and higher + [FieldOffset(0xA4)] internal readonly uint OSMajorVersion; + /// Compatibility: 4.0 and higher + [FieldOffset(0xA8)] internal readonly uint OSMinorVersion; + /// Compatibility: 4.0 and higher + [FieldOffset(0xAC)] internal readonly ushort OSBuildNumber; + /// Compatibility: 4.0 and higher + [FieldOffset(0xAE)] internal readonly ushort OSCSDVersion; + /// Compatibility: 4.0 and higher + [FieldOffset(0xB0)] internal readonly uint OSPlatformId; + /// Compatibility: 4.0 and higher + [FieldOffset(0xB4)] internal readonly uint ImageSubsystem; + /// Compatibility: 4.0 and higher + [FieldOffset(0xB8)] internal readonly uint ImageSubsystemMajorVersion; + /// Compatibility: 4.0 and higher + [FieldOffset(0xBC)] internal readonly uint ImageSubsystemMinorVersion; + /// Compatibility: 4.0 to early 6.0 + [FieldOffset(0xC0), Obsolete] private readonly KAFFINITY32 ImageProcessAffinityMask; + /// Compatibility: late 6.0 and higher + [FieldOffset(0xC0)] internal readonly KAFFINITY32 ActiveProcessAffinityMask; + /// (only 0x22 array members instead of 0x3C) Compatibility: 4.0 to early 6.0 + [FieldOffset(0xC4), Obsolete] private unsafe fixed uint GdiHandleBuffer_obsolete[0x22]; + /// 4.0 and higher (x86) + [FieldOffset(0xC4)] internal unsafe fixed uint GdiHandleBuffer[0x3C]; + internal unsafe uint[] GdiHandleBuffer_Safe + { + get + { + fixed (uint* pGdiHandleBuffer = &GdiHandleBuffer[0]) + return new ReadOnlySpan(pGdiHandleBuffer, 0x3C).ToArray(); + } + } + #endregion Appended for Windows NT 4.0 + + #region Appended for Windows 2000 + /// Compatibility: 5.0 and higher
+ /// Not supported. Type:
+ [FieldOffset(0x014C)] internal readonly UIntPtr32 PostProcessInitRoutine; + /// Compatibility: 5.0 and higher + [FieldOffset(0x0150)] internal readonly UIntPtr32 TlsExpansionBitmap; + /// Compatibility: 5.0 and higher + [FieldOffset(0x0154)] internal unsafe fixed uint TlsExpansionBitmapBits[0x20]; + internal unsafe uint[] TlsExpansionBitmapBits_Safe + { + get + { + fixed (uint* p = &TlsExpansionBitmapBits[0]) + return new ReadOnlySpan(p, 0x20).ToArray(); + } + } + /// Compatibility: 5.0 and higher
+ /// The Terminal Services session identifier associated with the current process.
+ /// The is one of the two members that Microsoft documented when required to disclose use of internal APIs by so-called middleware. + [FieldOffset(0x01D4)] internal readonly uint SessionId; + /// Compatibility: 5.1 and higher + [FieldOffset(0x01D8)] internal readonly PEB_AppCompat AppCompatFlags; + /// Compatibility: 5.1 and higher + [FieldOffset(0x01E0)] internal readonly PEB_AppCompat AppCompatFlagsUser; + /// Compatibility: 5.1 and higher + [FieldOffset(0x01E8)] internal readonly UIntPtr32 pShimData; + /// Compatibility: 5.0 + [FieldOffset(0x01D8), Obsolete] private readonly UIntPtr32 AppCompatInfo_NT5; + /// Compatibility: 5.1 and higher + [FieldOffset(0x01EC)] internal readonly UIntPtr32 AppCompatInfo; + /// Compatibility: 5.0 + [FieldOffset(0x01DC), Obsolete] private readonly UNICODE_STRING32 CSDVersion_NT5; + /// Compatibility: 5.1 and higher + [FieldOffset(0x01F0)] internal readonly UNICODE_STRING32 CSDVersion; + #endregion Appended for Windows 2000 + + #region Appended for Windows XP + /// Compatibility: 5.1 and higher
+ /// Type: ACTIVATION_CONTEXT_DATA const * (pointer to a constant ACTIVATION_CONTEXT_DATA)
+ [FieldOffset(0x01F8)] internal readonly UIntPtr32 ActivationContextData; + /// Compatibility: 5.1 and higher + /// Type: ACTIVATION_CONTEXT_DATA * + [FieldOffset(0x01FC)] internal readonly UIntPtr32 ProcessAssemblyStorageMap; + /// Compatibility: 5.1 and higher + /// Type: ACTIVATION_CONTEXT_DATA const * + [FieldOffset(0x0200)] internal readonly UIntPtr32 SystemDefaultActivationContextData; + /// Compatibility: 5.1 and higher + /// Type: ASSEMBLY_STORAGE_MAP * + [FieldOffset(0x204)] internal readonly UIntPtr32 SystemAssemblyStorageMap; + /// Compatibility: 5.1 and higher + [FieldOffset(0x208)] internal readonly UIntPtr32 MinimumStackCommit; + #endregion Appended for Windows XP + + #region Appended for Windows Server 2003 + /// Compatibility: 5.2 to 1809 + /// Type: FLS_CALLBACK_INFO * + [FieldOffset(0x020C)] internal readonly UIntPtr32 FlsCallback; + /// Compatibility: 5.2 to 1809 + [FieldOffset(0x0210)] internal readonly LIST_ENTRY32 FlatListHead; // 5.2 to 1809 + /// Compatibility: 5.2 to 1809 + [FieldOffset(0x0218)] internal readonly UIntPtr32 FlsBitmap; + /// Compatibility: 5.2 to 1809 + [FieldOffset(0x021C)] internal unsafe fixed uint FlsBitmapBits[4]; + /// Compatibility: 5.2 to 1809 + [FieldOffset(0x022C)] internal readonly uint FlsHighIndex; + #endregion Appended for Windows Server 2003 + + #region Appended for Windows Vista + /// Compatibility: 6.0 and higher + [FieldOffset(0x0230)] internal readonly UIntPtr32 WerRegistrationData; + /// Compatibility: 6.0 and higher + [FieldOffset(0x0234)] internal readonly UIntPtr32 WerShipAssertPtr; + #endregion Appended for Windows Vista + + #region Appended for Windows 7 + /// Compatibility: 6.1 only + [FieldOffset(0x0238)] internal readonly UIntPtr32 pContextData; + /* [FieldOffset(0x0238)] internal readonly UIntPtr32 pUnused; */ + /// Compatibility: 6.1 and higher + [FieldOffset(0x023C)] internal readonly UIntPtr32 pImageHeaderHash; + [FieldOffset(0x0240)] internal readonly PEB_Tracing TracingFlags; + #endregion Appended for Windows 7 + + #region Appended for Windows 8 + /// Compatibility: 6.2 and higher + [FieldOffset(0x0248)] internal readonly ulong CsrServerReadOnlySharedMemoryBase; + #endregion Appended for Windows 8 + + #region Appended Later in Windows 10 + /// Compatibility: 1511 and higher + [FieldOffset(0x0250)] internal readonly uint TppWorkerpListLock; + /// Compatibility: 1511 and higher + [FieldOffset(0x0254)] internal readonly LIST_ENTRY32 TppWorkerList; + /// Compatibility: 1511 and higher
+ /// Type: Fixed Array of void*
+ [FieldOffset(0x025C)] internal unsafe fixed uint WaitOnAddressHashTable[0x80]; + /// Compatibility: 1709 and higher + [FieldOffset(0x045C)] internal readonly UIntPtr32 TelemetryCoverageHeader; + /// Compatibility: 1709 and higher + [FieldOffset(0x0460)] internal readonly uint CloudFileFlags; + /// Compatibility: 1803 and higher + [FieldOffset(0x0464)] internal readonly uint CloudFileDiagFlags; + /// Compatibility: 1803 and higher + [FieldOffset(0x0468)] internal readonly byte PlaceholderCompatibilityMode; + /// Compatibility: 1803 and higher + /// Type: LEAP_SECOND_DATA * + [FieldOffset(0x0470)] internal readonly UIntPtr32 LeapSecondData; + [FieldOffset(0x0474)] internal readonly PEB_LeapSecond LeapSecondFlags; + /// Compatibility: 1803 and higher + /// The member is indeed named for being in some sense an extension of the much older . + /// Each corresponds to a registry value that can be in either or both of two well-known keys. + /// Each also is the name of a variable in the kernel (one exported, the other only internal), which the kernel initializes from the corresponding registry value in the Session Manager key. + /// This then provides the initial value for the corresponding member, which may then be re-initialized from the same-named registry value in the program's subkey of the Image File Execution Options.

+ /// Only one flag in the new set of them is yet known to be defined. + /// A set 0x00000001 bit in the data for the GlobalFlag2 registry value becomes a set 0x00000001 bit in the member. + /// From there it may set the bit in union with the . + /// The intended effect is that the newly exported RtlpTimeFieldsToTime and RtlpTimeToTimeFields functions become leap-second-aware: when is available, these functions accommodate 60 as the seconds field in a time.

+ /// This support for leap seconds was all new for the 1809 release and thus was also still new, roughly, for the article Leap Seconds for the IT Pro: What you need to know at a Microsoft blog dated Feb 14 2019. + /// Years later, on 27th January 2023, this is still the only match that Google finds when asked to search microsoft.com for pages that contain GlobalFlag2. + /// This is a good example of a trend in what passes as documentation. + /// At various levels of Windows administration and programming, it is often that Microsoft's only disclosure of some new feature, large or small, is a blog. + /// Administrators and programmers are inevitably grateful that Microsoft employees take the time to blog. + /// But let's please not overlook that these blogs are not documentation. + /// The helpfulness of Microsoft's employees in explaining new features in fast-moving development, and the readiness of occasionally desperate administrators and programmers to latch on to this help, disguises that Microsoft is systematically skipping the work of documenting these features.
+ [FieldOffset(0x0478)] internal readonly uint NtGlobalFlag2; + #endregion Appended Later in Windows 10 +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs new file mode 100644 index 0000000..c58cce4 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs @@ -0,0 +1,292 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; + +namespace Windows.Win32.System.Threading; +/// +/// https://web.archive.org/web/https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm +/// +/// +[StructLayout(LayoutKind.Explicit)] +internal struct PEB64 +{ + #region INITIAL_PEB + /// Compatibility: all + [FieldOffset(0x00)] internal readonly BOOLEAN InheritedAddressSpace; + /// Compatibility: 3.51 and higher + [FieldOffset(0x01)] internal readonly BOOLEAN ReadImageFileExecOptions; + /// + /// Indicates whether the specified process is currently being debugged. The PEB structure, however, is an internal operating-system structure whose layout may change in the future. It is best to use the CheckRemoteDebuggerPresent function instead.
+ /// Compatibility: 3.51 and higher + ///
+ [FieldOffset(0x02)] internal readonly BOOLEAN BeingDebugged; + /// Compatibility: late 5.2 and higher + [FieldOffset(0x03)] internal readonly PEB_BitField BitField; + /// Compatibility: all + [FieldOffset(0x08)] internal readonly HANDLE64 Mutant; + #endregion INITIAL_PEB + + /// Compatibility: all + [FieldOffset(0x20)] internal readonly UIntPtr64 ImageBaseAddress; + /// Compatibility: all
+ /// A pointer to a PEB_LDR_DATA structure that contains information about the loaded modules for the process.
+ [FieldOffset(0x18)] internal readonly unsafe UIntPtr64 Ldr; + /// Compatibility: All
+ /// A pointer to an RTL_USER_PROCESS_PARAMETERS structure that contains process parameter information such as the command line.
+ [FieldOffset(0x20)] internal readonly unsafe UIntPtr64 ProcessParameters; + /// Compatibility: all
+ /// "SubSystem" refers to WoW64, Posix (via PSXDLL.DLL), or WSL. This stores the per-process data for the relevant subsystem.
+ [FieldOffset(0x28)] internal readonly UIntPtr64 SubSystemData; + /// Compatibility: all + [FieldOffset(0x30)] internal readonly UIntPtr64 ProcessHeap; + + /// Compatibility: 3.10 to 5.0 + [FieldOffset(0x38), Obsolete] private readonly UIntPtr64 FastPebLock_obsolete; + /// Compatibility: 5.1 and higher + [FieldOffset(0x38)] internal readonly UIntPtr64 FastPebLock; + /// Compatibility: 3.10 to 5.1 + [FieldOffset(0x40), Obsolete] private readonly UIntPtr64 FastPebLockRoutine; + /// Compatibility: late 5.2 and higher + [FieldOffset(0x40)] internal readonly unsafe UIntPtr64 AtlThunkSListPtr; + /// Compatibility: 3.10 to 5.1 + [FieldOffset(0x48), Obsolete] private readonly UIntPtr64 FastPebUnlockRoutine; + /// Compatibility: 6.0 and higher + [FieldOffset(0x48)] internal readonly UIntPtr64 IFEOKey; + + /// Compatibility: 3.50 to 5.2 + [FieldOffset(0x50), Obsolete] private readonly uint EnvironmentUpdateCount; + /// Compatibility: 6.0 and higher + [FieldOffset(0x50)] internal readonly PEB_CrossProcess CrossProcessFlags; + /// Compatibility: 3.51 and higher + [FieldOffset(0x58)] internal readonly UIntPtr64 KernelCallBackTable; + /// Compatibility: 6.0 and higher + [FieldOffset(0x58)] internal readonly UIntPtr64 UserSharedInfoPtr; + /* NOTE: EventLogSection and EventLog became obsolete PEB before Windows 64-bit existed */ + /* NOTE: ExecutionOptions became obsolete before Windows 64-bit existed */ + /// Compatibility: late 5.1; 6.1 and higher + [FieldOffset(0x64)] internal readonly UIntPtr64 AtlThunkSListPtr32; + + /// Compatibility: 3.10 to early 6.0
+ /// Type: PEB_FREE_BLOCK*
+ [FieldOffset(0x68), Obsolete] private readonly UIntPtr64 FreeList; + /// Compatibility: 6.1 and higher + [FieldOffset(0x68)] internal readonly UIntPtr64 ApiSetMap; + + /// Compatibility: all + [FieldOffset(0x70)] internal readonly uint TlsExpansionCounter; + /// Compatibility: all + [FieldOffset(0x78)] internal readonly UIntPtr64 TlsBitmap; + /// Compatibility: all + [FieldOffset(0x80)] internal unsafe fixed uint TlsBitmapBits[2]; + internal readonly unsafe uint[] TlsBitmapBits_Safe => new uint[2] { TlsBitmapBits[0], TlsBitmapBits[1] }; + + /// Compatibility: all + [FieldOffset(0x88)] internal readonly UIntPtr64 ReadOnlySharedMemoryBase; + /// Compatibility: 3.10 to 5.2 + [FieldOffset(0x90), Obsolete] private readonly UIntPtr64 ReadOnlySharedMemoryHeap; + /// Compatibility: 6.0 to 6.2 + [FieldOffset(0x90), Obsolete] private readonly UIntPtr64 HotpatchInformation; + /// Compatibility: 1703 and higher + [FieldOffset(0x90)] internal readonly UIntPtr64 SharedData; + /// Compatibility: all + [FieldOffset(0x98)] internal readonly UIntPtr64 ReadOnlyStaticServerData; + /// Compatibility: all + [FieldOffset(0xA0)] internal readonly UIntPtr64 AnsiCodePageData; + /// Compatibility: all + [FieldOffset(0xA8)] internal readonly UIntPtr64 OemCodePageData; + /// Compatibility: all + [FieldOffset(0xB0)] internal readonly UIntPtr64 UnicodeCaseTableData; + /// Compatibility: 3.51 and higher + [FieldOffset(0xB8)] internal readonly uint NumberOfProcessors; + /// Compatibility: 3.51 and higher + [FieldOffset(0xBC)] internal readonly uint NtGlobalFlag; + /// Compatibility: 3.51 and higher + [FieldOffset(0xC0)] internal readonly LARGE_INTEGER CriticalSectionTimeout; + + #region Appended for Windows NT 3.51 + /// Compatibility: 3.51 and higher + [FieldOffset(0xC8)] internal readonly UIntPtr64 HeapSegmentReserve; + /// Compatibility: 3.51 and higher + [FieldOffset(0xD0)] internal readonly UIntPtr64 HeapSegmentCommit; + /// Compatibility: 3.51 and higher + [FieldOffset(0xD8)] internal readonly UIntPtr64 HeapDeCommitTotalFreeThreshold; + /// Compatibility: 3.51 and higher + [FieldOffset(0xE0)] internal readonly UIntPtr64 HeapDeCommitFreeBlockThreshold; + /// Compatibility: 3.51 and higher + [FieldOffset(0xE8)] internal readonly uint NumberOfHeaps; + /// Compatibility: 3.51 and higher + [FieldOffset(0xEC)] internal readonly uint MaximumNumberOfHeaps; + /// Compatibility: 3.51 and higher + [FieldOffset(0xF0)] internal readonly UIntPtr64 ProcessHeaps; + #endregion Appended for Windows NT 3.51 + + #region Appended for Windows NT 4.0 + /// Compatibility: 3.51 and higher + [FieldOffset(0xF8)] internal readonly UIntPtr64 GdiSharedHandleTable; + /// Compatibility: 4.0 and higher + [FieldOffset(0x0100)] internal readonly UIntPtr64 ProcessTarterHelper; + /// Compatibility: 4.0 and higher + [FieldOffset(0x0108)] internal readonly uint GdiDCAttributeList; + /// Compatibility: 4.0 to 5.1 + [FieldOffset(0x0110), Obsolete] private readonly UIntPtr64 LoaderLock_obsolete; + /// Compatibility: 5.2 and higher + [FieldOffset(0x0110)] internal readonly UIntPtr64 LoaderLock; + /// Compatibility: 4.0 and higher + [FieldOffset(0x0118)] internal readonly uint OSMajorVersion; + /// Compatibility: 4.0 and higher + [FieldOffset(0x011C)] internal readonly uint OSMinorVersion; + /// Compatibility: 4.0 and higher + [FieldOffset(0x0120)] internal readonly ushort OSBuildNumber; + /// Compatibility: 4.0 and higher + [FieldOffset(0x0122)] internal readonly ushort OSCSDVersion; + /// Compatibility: 4.0 and higher + [FieldOffset(0x0124)] internal readonly uint OSPlatformId; + /// Compatibility: 4.0 and higher + [FieldOffset(0x0128)] internal readonly uint ImageSubsystem; + /// Compatibility: 4.0 and higher + [FieldOffset(0x012C)] internal readonly uint ImageSubsystemMajorVersion; + /// Compatibility: 4.0 and higher + [FieldOffset(0x0130)] internal readonly uint ImageSubsystemMinorVersion; + /// Compatibility: 4.0 to early 6.0 + [FieldOffset(0x0138), Obsolete] private readonly KAFFINITY64 ImageProcessAffinityMask; + /// Compatibility: late 6.0 and higher + [FieldOffset(0x0138)] internal readonly KAFFINITY64 ActiveProcessAffinityMask; + /// (only 0x22 array members instead of 0x3C) Compatibility: 4.0 to early 6.0 + [FieldOffset(0x0140), Obsolete] private unsafe fixed uint GdiHandleBuffer_obsolete[0x22]; + /// 4.0 and higher (x86) + [FieldOffset(0x0140)] internal unsafe fixed uint GdiHandleBuffer[0x3C]; + internal unsafe uint[] GdiHandleBuffer_Safe + { + get + { + fixed (uint* pGdiHandleBuffer = &GdiHandleBuffer[0]) + return new ReadOnlySpan(pGdiHandleBuffer, 0x3C).ToArray(); + } + } + #endregion Appended for Windows NT 4.0 + + #region Appended for Windows 2000 + /// Compatibility: 5.0 and higher
+ /// Type: + /// Not supported
+ [FieldOffset(0x0230)] internal readonly UIntPtr64 PostProcessInitRoutine; + /// Compatibility: 5.0 and higher + [FieldOffset(0x0238)] internal readonly UIntPtr64 TlsExpansionBitmap; + /// Compatibility: 5.0 and higher + [FieldOffset(0x0240)] internal unsafe fixed uint TlsExpansionBitmapBits[0x20]; + internal unsafe uint[] TlsExpansionBitmapBits_Safe + { + get + { + fixed (uint* p = &TlsExpansionBitmapBits[0]) + return new ReadOnlySpan(p, 0x20).ToArray(); + } + } + /// Compatibility: 5.0 and higher
+ /// The Terminal Services session identifier associated with the current process.
+ /// The is one of the two members that Microsoft documented when required to disclose use of internal APIs by so-called middleware. + [FieldOffset(0x02C0)] internal readonly uint SessionId; + + /// Compatibility: 5.1 and higher + [FieldOffset(0x02C8)] internal readonly PEB_AppCompat AppCompatFlags; + /// Compatibility: 5.1 and higher + [FieldOffset(0x02D0)] internal readonly PEB_AppCompat AppCompatFlagsUser; + /// Compatibility: 5.1 and higher + [FieldOffset(0x02D8)] internal readonly UIntPtr64 pShimData; + /// Compatibility: 5.1 and higher + [FieldOffset(0x02E0)] internal readonly UIntPtr64 AppCompatInfo; + /// Compatibility: 5.1 and higher + [FieldOffset(0x02E8)] internal readonly UNICODE_STRING64 CSDVersion; + #endregion Appended for Windows 2000 + + #region Appended for Windows XP + /// Compatibility: 5.1 and higher
+ /// Type: ACTIVATION_CONTEXT_DATA const * (pointer to a constant ACTIVATION_CONTEXT_DATA)
+ [FieldOffset(0x02F8)] internal readonly UIntPtr64 ActivationContextData; + /// Compatibility: 5.1 and higher + /// Type: ACTIVATION_CONTEXT_DATA * + [FieldOffset(0x0300)] internal readonly UIntPtr64 ProcessAssemblyStorageMap; + /// Compatibility: 5.1 and higher + /// Type: ACTIVATION_CONTEXT_DATA const * + [FieldOffset(0x0308)] internal readonly UIntPtr64 SystemDefaultActivationContextData; + /// Compatibility: 5.1 and higher + /// Type: ASSEMBLY_STORAGE_MAP * + [FieldOffset(0x310)] internal readonly UIntPtr64 SystemAssemblyStorageMap; + /// Compatibility: 5.1 and higher + [FieldOffset(0x318)] internal readonly UIntPtr64 MinimumStackCommit; + #endregion Appended for Windows XP + + #region Appended for Windows Server 2003 + /// Compatibility: 5.2 to 1809 + /// Type: FLS_CALLBACK_INFO * + [FieldOffset(0x0320)] internal readonly UIntPtr64 FlsCallback; + /// Compatibility: 5.2 to 1809 + [FieldOffset(0x0328)] internal readonly LIST_ENTRY64 FlatListHead; // 5.2 to 1809 + /// Compatibility: 5.2 to 1809 + [FieldOffset(0x0338)] internal readonly UIntPtr64 FlsBitmap; + /// Compatibility: 5.2 to 1809 + [FieldOffset(0x0340)] internal unsafe fixed uint FlsBitmapBits[4]; + /// Compatibility: 5.2 to 1809 + [FieldOffset(0x0350)] internal readonly uint FlsHighIndex; + #endregion Appended for Windows Server 2003 + + #region Appended for Windows Vista + /// Compatibility: 6.0 and higher + [FieldOffset(0x0358)] internal readonly UIntPtr64 WerRegistrationData; + /// Compatibility: 6.0 and higher + [FieldOffset(0x0360)] internal readonly UIntPtr64 WerShipAssertPtr; + #endregion Appended for Windows Vista + + #region Appended for Windows 7 + /// Compatibility: 6.1 only + [FieldOffset(0x0368)] internal readonly UIntPtr64 pContextData; + /* [FieldOffset(0x0238)] internal readonly UIntPtr64 pUnused; */ + /// Compatibility: 6.1 and higher + [FieldOffset(0x0370)] internal readonly UIntPtr64 pImageHeaderHash; + [FieldOffset(0x0378)] internal readonly PEB_Tracing TracingFlags; + #endregion Appended for Windows 7 + + #region Appended for Windows 8 + /// Compatibility: 6.2 and higher + [FieldOffset(0x0380)] internal readonly ulong CsrServerReadOnlySharedMemoryBase; + #endregion Appended for Windows 8 + + #region Appended Later in Windows 10 + /// Compatibility: 1511 and higher + [FieldOffset(0x0388)] internal readonly uint TppWorkerpListLock; + /// Compatibility: 1511 and higher + [FieldOffset(0x0390)] internal readonly LIST_ENTRY64 TppWorkerList; + /// Compatibility: 1511 and higher
+ /// Type: Fixed Array of void*
+ [FieldOffset(0x03A0)] internal unsafe fixed ulong WaitOnAddressHashTable[0x80]; + /// Compatibility: 1709 and higher + [FieldOffset(0x07A0)] internal readonly UIntPtr64 TelemetryCoverageHeader; + /// Compatibility: 1709 and higher + [FieldOffset(0x07A8)] internal readonly uint CloudFileFlags; + /// Compatibility: 1803 and higher + [FieldOffset(0x07AC)] internal readonly uint CloudFileDiagFlags; + /// Compatibility: 1803 and higher + [FieldOffset(0x07B0)] internal readonly byte PlaceholderCompatibilityMode; + /// Compatibility: 1803 and higher + /// Type: LEAP_SECOND_DATA * + [FieldOffset(0x07B8)] internal readonly UIntPtr64 LeapSecondData; + [FieldOffset(0x07C0)] internal readonly PEB_LeapSecond LeapSecondFlags; + /// Compatibility: 1803 and higher + /// The member is indeed named for being in some sense an extension of the much older . + /// Each corresponds to a registry value that can be in either or both of two well-known keys. + /// Each also is the name of a variable in the kernel (one exported, the other only internal), which the kernel initializes from the corresponding registry value in the Session Manager key. + /// This then provides the initial value for the corresponding member, which may then be re-initialized from the same-named registry value in the program's subkey of the Image File Execution Options.

+ /// Only one flag in the new set of them is yet known to be defined. + /// A set 0x00000001 bit in the data for the GlobalFlag2 registry value becomes a set 0x00000001 bit in the member. + /// From there it may set the bit in union with the . + /// The intended effect is that the newly exported RtlpTimeFieldsToTime and RtlpTimeToTimeFields functions become leap-second-aware: when is available, these functions accommodate 60 as the seconds field in a time.

+ /// This support for leap seconds was all new for the 1809 release and thus was also still new, roughly, for the article Leap Seconds for the IT Pro: What you need to know at a Microsoft blog dated Feb 14 2019. + /// Years later, on 27th January 2023, this is still the only match that Google finds when asked to search microsoft.com for pages that contain GlobalFlag2. + /// This is a good example of a trend in what passes as documentation. + /// At various levels of Windows administration and programming, it is often that Microsoft's only disclosure of some new feature, large or small, is a blog. + /// Administrators and programmers are inevitably grateful that Microsoft employees take the time to blog. + /// But let's please not overlook that these blogs are not documentation. + /// The helpfulness of Microsoft's employees in explaining new features in fast-moving development, and the readiness of occasionally desperate administrators and programmers to latch on to this help, disguises that Microsoft is systematically skipping the work of documenting these features.
+ [FieldOffset(0x07C4)] internal readonly uint NtGlobalFlag2; + #endregion Appended Later in Windows 10 +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_AppCompat.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_AppCompat.cs new file mode 100644 index 0000000..73458a5 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_AppCompat.cs @@ -0,0 +1,38 @@ +namespace Windows.Win32.System.Threading; +/// +/// The and +/// members are set by APPHELP.DLL from TAG_FLAG_MASK_KERNEL (0x5005) and TAG_FLAG_MASK_USER (0x5008) +/// tags for the process's description in an SDB file. In the XML that SDB +/// files are compiled from, the two are evaluated from the MASK attribute +/// in a <FLAG> tag whose TYPE attribute is KERNEL or USER, respectively. +/// +[Flags] +internal enum PEB_AppCompat : ulong +{ + KACF_OLDGETSHORTPATHNAME = 1, + KACF_VERSIONLIE_NOT_USED = 1 << 1, + //not defined = 0x00000004, + KACF_GETDISKFREESPACE = 1 << 3, + //not defined = 0x00000010, + KACF_FTMFROMCURRENTAPT = 1 << 5, + KACF_DISALLOWORBINDINGCHANGES = 1 << 6, + KACF_OLE32VALIDATEPTRS = 1 << 7, + KACF_DISABLECICERO = 1 << 8, + KACF_OLE32ENABLEASYNCDOCFILE = 1 << 9, + KACF_OLE32ENABLELEGACYEXCEPTIONHANDLING = 1 << 10, + KACF_RPCDISABLENDRCLIENTHARDENING = 1 << 11, + KACF_RPCDISABLENDRMAYBENULL_SIZEIS = 1 << 12, + KACF_DISABLEALLDDEHACK_NOT_USED = 1 << 13, + KACF_RPCDISABLENDR61_RANGE = 1 << 14, + KACF_RPC32ENABLELEGACYEXCEPTIONHANDLING = 1 << 15, + KACF_OLE32DOCFILEUSELEGACYNTFSFLAGS = 1 << 16, + KACF_RPCDISABLENDRCONSTIIDCHECK = 1 << 17, + KACF_USERDISABLEFORWARDERPATCH = 1 << 18, + //not defined = 0x00080000, + KACF_OLE32DISABLENEW_WMPAINT_DISPATCH = 1 << 20, + KACF_ADDRESTRICTEDSIDINCOINITIALIZESECURITY = 1 << 21, + KACF_ALLOCDEBUGINFOFORCRITSECTIONS = 1 << 22, + KACF_OLEAUT32ENABLEUNSAFELOADTYPELIBRELATIVE = 1 << 23, + KACF_ALLOWMAXIMIZEDWINDOWGAMMA = 1 << 24, + KACF_DONOTADDTOCACHE = 1U << 31, +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_BitField.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_BitField.cs new file mode 100644 index 0000000..537e3bb --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_BitField.cs @@ -0,0 +1,33 @@ +namespace Windows.Win32.System.Threading; + +[Flags] +public enum PEB_BitField +{ + /// Compatibility: late 5.2 and higher + ImageUsedLargePages = 1, + /// Compatibility: 6.0 and higher + IsProtectedProcess = 2, + /// Compatibility: 6.0 to 6.2 + IsLegacyProcess = 4, + /// Compatibility: 6.0 to 6.2 + IsImageDynamicallyRelocated_VistaToWin8 = 8, + /// Compatibility: 6.3 and higher + IsImageDynamicallyRelocated = 4, + /// Compatibility: late 6.0 to 6.2 + SkipPatchingUser32Forwarders_VistaToWin8 = 16, + /// Compatibility: 6.3 and higher + SkipPatchingUser32Forwarders = 8, + /// Compatibility: 6.2 + IsPackagedProcess_Win8 = 32, + /// Compatibility: 6.3 and higher + IsPackagedProcess = 16, + /// Compatibility: 6.2 + IsAppContainer_Win8 = 64, + /// Compatibility: 6.3 and higher + IsAppContainer = 32, + /// Compatibility: 6.3 and higher + IsProtectedProcessLight = 128, + /// Compatibility: 1607 and higher + IsLongPathAwareProcess = 256, + +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_CrossProcess.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_CrossProcess.cs new file mode 100644 index 0000000..8609e66 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_CrossProcess.cs @@ -0,0 +1,24 @@ +namespace Windows.Win32.System.Threading; + +/// +/// https://web.archive.org/web/20221204112657/https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/crossprocessflags.htm +/// +[Flags] +internal enum PEB_CrossProcess : uint +{ + /// Compatibility: 6.0 and higher + ProcessInJob = 1, + /// Compatibility: 6.0 and higher + ProcessInitializing = 1 << 1, + /// Compatibility: 6.1 and higher + ProcessUsingVEH = 1 << 2, + /// Compatibility: 6.1 and higher + ProcessUsingVCH = 1 << 3, + /// Compatibility: 6.1 and higher + ProcessUsingFTH = 1 << 4, + /// Compatibility: 1703 and higher + ProcessPreviouslyThrottled = 1 << 5, + /// Compatibility: 1703 and higher + ProcessCurrentlyThrottled = 1 << 6, + ProcessImagesHotPatched = 1 << 7, +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Ex.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Ex.cs new file mode 100644 index 0000000..72068b0 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Ex.cs @@ -0,0 +1,47 @@ +/// This file supplements code generated by CsWin32 +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace Windows.Win32.System.Threading; + +/// +/// all data which must be copied via ReadProcessMemory +/// +internal class PEB_Ex +{ + public unsafe PEB_Ex(uint processId, PEB peb) + { + ProcessId = processId; + ReadVmHandle = new(PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, true, processId), true); + Peb = peb; + + //{ + // PEB_LDR_DATA* baseAddr = Peb.Ldr; + // PEB_LDR_DATA ldr = default; + // nuint br = default; + // if (PInvoke.ReadProcessMemory(ReadVmHandle, baseAddr, &ldr, (nuint)Marshal.SizeOf(), &br)) + // PebLdrData = ldr; + //} + + { + RTL_USER_PROCESS_PARAMETERS* baseAddr = Peb.ProcessParameters; + RTL_USER_PROCESS_PARAMETERS procParams = default; + nuint br = default; + if (PInvoke.ReadProcessMemory(ReadVmHandle, baseAddr, &procParams, (nuint)Marshal.SizeOf(), &br)) + { + ImagePathName = procParams.ImagePathName.ToStringLength(); + CommandLine = procParams.CommandLine.ToStringLength(); + //ProcessParameters = procParams; + } + } + } + + public uint ProcessId { get; } + public SafeProcessHandle? ReadVmHandle { get; } + public PEB Peb { get; } + + //public PEB_LDR_DATA PebLdrData { get; } // unnecessary unless we want the process's module list + public string? ImagePathName { get; } + public string? CommandLine { get; } + //public RTL_USER_PROCESS_PARAMETERS ProcessParameters { get; } // not needed unless hidden fields are implemented +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs new file mode 100644 index 0000000..b181f0d --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs @@ -0,0 +1,27 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; +using Windows.Win32.System.WindowsProgramming; + +namespace Windows.Win32.System.Threading; + +/// +/// https://web.archive.org/web/https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm +/// +[StructLayout(LayoutKind.Explicit)] +internal readonly struct PEB_LDR_DATA32 +{ + [FieldOffset(0x00)] internal readonly uint Length; + [FieldOffset(0x04)] internal readonly BOOLEAN Initialized; + [FieldOffset(0x08)] internal readonly UIntPtr32 SsHandle; + [FieldOffset(0x0C)] internal readonly LIST_ENTRY32 InLoadOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. For more information, see Remarks. + [FieldOffset(0x14)] internal readonly LIST_ENTRY32 InMemoryOrderModuleList; + [FieldOffset(0x1C)] internal readonly LIST_ENTRY32 InInitializationOrderModuleList; + /// 5.1 and higher + [FieldOffset(0x24)] internal readonly UIntPtr32 EntryInProgress; + /// late 6.0 and higher + [FieldOffset(0x28)] internal readonly BOOLEAN ShutdownInProgress; + /// late 6.0 and higher + [FieldOffset(0x2C)] internal readonly HANDLE32 ShutdownThreadId; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs new file mode 100644 index 0000000..f58fb72 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs @@ -0,0 +1,24 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; +using Windows.Win32.System.WindowsProgramming; + +namespace Windows.Win32.System.Threading; + +/// +/// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm +/// +[StructLayout(LayoutKind.Explicit)] +internal struct PEB_LDR_DATA64 +{ + [FieldOffset(0x00)] internal readonly uint Length; + [FieldOffset(0x04)] internal readonly BOOLEAN Initialized; + [FieldOffset(0x08)] internal readonly unsafe UIntPtr64 SsHandle; + [FieldOffset(0x10)] internal readonly LIST_ENTRY64 InLoadOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. For more information, see Remarks. + [FieldOffset(0x20)] internal readonly LIST_ENTRY64 InMemoryOrderModuleList; + [FieldOffset(0x30)] internal readonly LIST_ENTRY64 InInitializationOrderModuleList; + [FieldOffset(0x40)] internal readonly unsafe UIntPtr64 EntryInProgress; // 5.1 and higher + [FieldOffset(0x48)] internal readonly BOOLEAN ShutdownInProgress; // late 6.0 and higher + [FieldOffset(0x50)] internal readonly HANDLE64 ShutdownThreadId; // late 6.0 and higher +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LeapSecond.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LeapSecond.cs new file mode 100644 index 0000000..95fea46 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LeapSecond.cs @@ -0,0 +1,7 @@ +namespace Windows.Win32.System.Threading; + +[Flags] +internal enum PEB_LeapSecond : uint +{ + SixtySecondEnabled = 1U +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Tracing.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Tracing.cs new file mode 100644 index 0000000..308289b --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Tracing.cs @@ -0,0 +1,12 @@ +namespace Windows.Win32.System.Threading; + +[Flags] +internal enum PEB_Tracing : uint +{ + /// Compatibility: 6.1 and higher + HeapTracingEnabled = 1, + /// Compatibility: 6.1 and higher + CritSecTracingEnabled = 1 << 1, + /// Compatibility: 6.2 and higher + LibLoaderTracingEnabled = 1 << 2, +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PS_POST_PROCESS_INIT_ROUTINE.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PPS_POST_PROCESS_INIT_ROUTINE.cs similarity index 90% rename from deadlock-dotnet-sdk/Windows.Win32/System/Threading/PS_POST_PROCESS_INIT_ROUTINE.cs rename to deadlock-dotnet-sdk/Windows.Win32/System/Threading/PPS_POST_PROCESS_INIT_ROUTINE.cs index dd1391b..46dd58f 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PS_POST_PROCESS_INIT_ROUTINE.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PPS_POST_PROCESS_INIT_ROUTINE.cs @@ -1,10 +1,9 @@ -/// This file supplements code generated by CsWin32 using System.Runtime.InteropServices; namespace Windows.Win32.System.Threading; [UnmanagedFunctionPointer(CallingConvention.Winapi)] -unsafe delegate void PS_POST_PROCESS_INIT_ROUTINE(); +internal unsafe delegate void PS_POST_PROCESS_INIT_ROUTINE(); /// /// Function Pointer workaround. C# 9's function pointers are only allowed in @@ -16,7 +15,7 @@ namespace Windows.Win32.System.Threading; /// will re-use that thunk. /// source: /// -struct PPS_POST_PROCESS_INIT_ROUTINE : IEquatable +internal struct PPS_POST_PROCESS_INIT_ROUTINE : IEquatable { public IntPtr Value; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs index 447ce39..ab8db7f 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs @@ -6,49 +6,26 @@ namespace Windows.Win32.System.Threading; -/// -/// This struct is not fully emitted by Win32Metadata. The definition in ntddk.h of Windows SDK 10.0.22621.0 has many more documented fields +/// This struct is not fully emitted by Win32Metadata. The definition in ntddk.h of Windows SDK 10.0.22621.0 has many more documented fields which are included in this manual definition +/// See https://github.com/winsiderss/systeminformer@master/-/blob/phnt/include/ntpsapi.h /// readonly struct PROCESS_BASIC_INFORMATION { - private readonly nuint uniqueProcessId; - private readonly UIntPtr inheritedFromUniqueProcessId; - - /// - /// The process's ID. Backed by a pointer-sized integer field. - /// - /// - public uint ProcessId => (uint)uniqueProcessId; - public NTSTATUS ExitStatus { get; } - /// /// The address of the PEB relative to its process's memory. Read object via /// public unsafe PEB* PebBaseAddress { get; } + public UIntPtr AffinityMask { get; } + public KPRIORITY BasePriority { get; } + private readonly nuint uniqueProcessId; + private readonly UIntPtr inheritedFromUniqueProcessId; /// - /// Invoke ReadProcessMemory to copy the target process's PEB to our memory - /// + /// The process's ID. Backed by a pointer-sized integer field. /// - public unsafe PEB Peb - { - get - { - if (ProcessId == Environment.ProcessId) - return *PebBaseAddress; - - SafeProcessHandle hProcess = GetProcessHandle(ProcessId); - PEB peb; - nuint bytesRead; - return !PInvoke.ReadProcessMemory(hProcess, PebBaseAddress, &peb, (nuint)Marshal.SizeOf(typeof(PEB)), &bytesRead) - ? throw new Win32Exception() - : peb; - } - } - - public UIntPtr AffinityMask { get; } - public KPRIORITY BasePriority { get; } + /// + public uint ProcessId => (uint)uniqueProcessId; public uint InheritedFromUniqueProcessId => (uint)inheritedFromUniqueProcessId; /// @@ -59,7 +36,7 @@ public unsafe PEB Peb /// A Process handle with the PROCESS_VM_READ and PROCESS_QUERY_LIMITED_INFORMATION rights. This handle must remain open for the PEB pointer to be readable. /// If UniqueProcessId does not match the current process's ID, read all pointers with NtQueryVirtualMemory /// NtQueryInformationProcess returned an error code - // TODO: wrap in object containing an object of this Type, a process handle for NtQueryVirtualMemory, and a "shortcut" method for calling that function + // TODO: wrap in object containing an object of this Type, a process handle for NtQueryVirtualMemory, and a wrapper method for calling that function // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryvirtualmemory public PROCESS_BASIC_INFORMATION(SafeProcessHandle hProcess) { @@ -89,4 +66,24 @@ public static SafeProcessHandle GetProcessHandle(uint processId) else return new SafeProcessHandle(hProcess, true); } + + /// + /// Invoke ReadProcessMemory to copy the target process's PEB to our memory + /// + /// + /// Failed to read process memory. Check Win32 error and message for more info. + public unsafe PEB GetPeb() + { + if (ProcessId == Environment.ProcessId) + return *PebBaseAddress; + + SafeProcessHandle hProcess = GetProcessHandle(ProcessId); + PEB peb; + nuint bytesRead; + return !PInvoke.ReadProcessMemory(hProcess, PebBaseAddress, &peb, (nuint)Marshal.SizeOf(), &bytesRead) + ? throw new Win32Exception() + : peb; + } + + public unsafe PEB_Ex GetPebEx() => new(ProcessId, GetPeb()); } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION32.cs new file mode 100644 index 0000000..d73eacc --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION32.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; + +namespace Windows.Win32.System.Threading; + +/// +/// 32-bit struct for interop with 32-bit processes from a 64-bit process
+/// When running a 32-bit process or when interacting with other 64-bit processes, use
+[StructLayout(LayoutKind.Explicit)] +internal readonly struct PROCESS_BASIC_INFORMATION32 +{ + [FieldOffset(0x00)] public readonly NTSTATUS ExitStatus; + [FieldOffset(0x04)] public readonly UIntPtr32 PebBaseAddress; + [FieldOffset(0x08)] public readonly KAFFINITY32 AffinityMask; + [FieldOffset(0x0B)] public readonly KPRIORITY BasePriority; + [FieldOffset(0x10)] public readonly uint UniqueProcessId; + [FieldOffset(0x14)] public readonly uint InheritedFromUniqueProcessId; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs new file mode 100644 index 0000000..7479f82 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; + +namespace Windows.Win32.System.Threading; + +/// +/// 64-bit struct for interop with 64-bit processes from a 32-bit process
+/// When running a 64-bit process or when interacting with other 32-bit processes, use
+[StructLayout(LayoutKind.Explicit)] +internal readonly struct PROCESS_BASIC_INFORMATION64 +{ + [FieldOffset(0x00)] public readonly NTSTATUS ExitStatus; + [FieldOffset(0x04)] public readonly UIntPtr64 PebBaseAddress; + [FieldOffset(0x00)] public readonly KAFFINITY64 AffinityMask; + [FieldOffset(0x00)] public readonly KPRIORITY BasePriority; + [FieldOffset(0x00)] public readonly ulong UniqueProcessId; + [FieldOffset(0x00)] public readonly ulong InheritedFromUniqueProcessId; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs new file mode 100644 index 0000000..08240c6 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs @@ -0,0 +1,12 @@ +namespace Windows.Win32.System.Threading +{ + internal readonly struct RTL_CRITICAL_SECTION32 + { + internal readonly unsafe UIntPtr32 DebugInfo; + internal readonly int LockCount; + internal readonly int RecursionCount; + internal readonly Foundation.HANDLE32 OwningThread; + internal readonly Foundation.HANDLE32 LockSemaphore; + internal readonly uint SpinCount; + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs new file mode 100644 index 0000000..5a6b377 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs @@ -0,0 +1,12 @@ +namespace Windows.Win32.System.Threading +{ + internal readonly struct RTL_CRITICAL_SECTION64 + { + internal readonly unsafe UIntPtr64 DebugInfo; + internal readonly int LockCount; + internal readonly int RecursionCount; + internal readonly Foundation.HANDLE64 OwningThread; + internal readonly Foundation.HANDLE64 LockSemaphore; + internal readonly ulong SpinCount; + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR32.cs new file mode 100644 index 0000000..86abfdd --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR32.cs @@ -0,0 +1,58 @@ +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using PInvoke; +using Win32Exception = System.ComponentModel.Win32Exception; + +namespace Windows.Win32.System.Threading; + +internal partial struct RTL_USER_PROCESS_PARAMETERS32 +{ + /// + /// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/rtl_drive_letter_curdir.htm + /// + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + public struct RTL_DRIVE_LETTER_CURDIR32 + { + public ushort Flags; + public ushort Length; + public uint TimeStamp; + public STRING32 DosPath; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct STRING32 + { + public ushort Length; + public ushort MaximumLength; + public readonly UIntPtr32 _buffer; + + /// + /// Copy the 8-bit string to a managed, utf-16 string + /// + /// The ID of the process that allocated the string. After opening a handle to the process with PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ rights, a managed copy of the unmanaged string will be made in this process. + public string ToString(uint processId) + { + SafeProcessHandle hProcess; + return (hProcess = PInvoke.OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, processId) + ).IsInvalid + ? throw new Win32Exception() + : ToString(hProcess); + } + + /// + /// Copy the 8-bit string to a managed, utf-16 string + /// + /// a SafeProcessHandle with PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ rights. + /// A managed string that holds a copy of the native string/returns> + /// ReadProcessMemory failed + public unsafe string ToString(SafeProcessHandle hProcess) + { + IntPtr buffer = Marshal.AllocHGlobal(MaximumLength); + nuint retLength; + + return !PInvoke.ReadProcessMemory(hProcess, (void*)_buffer, (void*)buffer, MaximumLength, &retLength) + ? throw new Win32Exception() + : Marshal.PtrToStringAnsi(buffer, MaximumLength); + } + } + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR64.cs new file mode 100644 index 0000000..c485795 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR64.cs @@ -0,0 +1,64 @@ +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using PInvoke; +using Win32Exception = System.ComponentModel.Win32Exception; + +namespace Windows.Win32.System.Threading; + +internal partial struct RTL_USER_PROCESS_PARAMETERS64 +{ + /// + /// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/rtl_drive_letter_curdir.htm + /// + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + internal struct RTL_DRIVE_LETTER_CURDIR64 + { + public ushort Flags; + public ushort Length; + public uint TimeStamp; + public STRING64 DosPath; + + [StructLayout(LayoutKind.Sequential)] + public struct STRING64 + { + public ushort Length; + public ushort MaximumLength; + /* The compiler adds 6 bytes of padding here, hence the total size of 24 bytes instead of 18 bytes */ + private UIntPtr64 _buffer; + + /// + /// Copy the 8-bit string to a managed, utf-16 string + /// + /// The ID of the process that allocated the string. After opening a handle to the process with PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ rights, a managed copy of the unmanaged string will be made in this process. + public string ToString(uint processId) + { + SafeProcessHandle hProcess; + return (hProcess = PInvoke.OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, processId) + ).IsInvalid + ? throw new Win32Exception() + : ToString(hProcess); + } + + /// + /// Copy the 8-bit string to a managed, utf-16 string + /// + /// a SafeProcessHandle with PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ rights. + /// A managed string that holds a copy of the native string/returns> + /// NtWow64ReadVirtualMemory64 failed + public unsafe string ToString(SafeProcessHandle hProcess) + { + // because this process uses 32-bit pointers and the target process + // uses 64-bit pointers, we have to use a ulong to ensure the + // pointer parameter is not truncated. + // our buffer can be a 32-bit pointer, however. + IntPtr buffer = Marshal.AllocHGlobal(MaximumLength); + ulong retLength; + Foundation.NTSTATUS status; + + return !(status = PInvoke.NtWow64ReadVirtualMemory64(hProcess, _buffer, (void*)buffer, MaximumLength, &retLength)).IsSuccessful + ? throw new NTStatusException(status) + : Marshal.PtrToStringAnsi(buffer, MaximumLength); + } + } + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs new file mode 100644 index 0000000..53ea5fc --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs @@ -0,0 +1,69 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; + +namespace Windows.Win32.System.Threading; + +/// +[StructLayout(LayoutKind.Explicit)] +internal partial struct RTL_USER_PROCESS_PARAMETERS32 +{ + [FieldOffset(0x00)] public uint MaximumLength; + [FieldOffset(0x04)] public uint Length; + + [FieldOffset(0x08)] public uint Flags; + [FieldOffset(0x0C)] public uint DebugFlags; + + [FieldOffset(0x10)] public HANDLE32 ConsoleHandle; + [FieldOffset(0x14)] public uint ConsoleFlags; + [FieldOffset(0x18)] public HANDLE32 StandardInput; + [FieldOffset(0x1C)] public HANDLE32 StandardOutput; + [FieldOffset(0x20)] public HANDLE32 StandardError; + + [FieldOffset(0x24)] public CURDIR32 CurrentDirectory; + [FieldOffset(0x30)] public UNICODE_STRING32 DllPath; + [FieldOffset(0x38)] public UNICODE_STRING32 ImagePathName; + [FieldOffset(0x40)] public UNICODE_STRING32 CommandLine; + [FieldOffset(0x48)] public UIntPtr32 Environment; // 32-bit pointer + + [FieldOffset(0x4C)] public uint StartingX; + [FieldOffset(0x50)] public uint StartingY; + [FieldOffset(0x54)] public uint CountX; + [FieldOffset(0x58)] public uint CountY; + [FieldOffset(0x5C)] public uint CountCharsX; + [FieldOffset(0x60)] public uint CountCharsY; + [FieldOffset(0x64)] public uint FillAttribute; + + [FieldOffset(0x68)] public uint WindowFlags; + [FieldOffset(0x6C)] public uint ShowWindowFlags; + [FieldOffset(0x70)] public UNICODE_STRING32 WindowTitle; + [FieldOffset(0x78)] public UNICODE_STRING32 DesktopInfo; + [FieldOffset(0x80)] public UNICODE_STRING32 ShellInfo; + [FieldOffset(0x88)] public UNICODE_STRING32 RuntimeData; + + const int RTL_MAX_DRIVE_LETTERS = 0x20; + // note: CurrentDirectories is misspelled as CurrentDirectores in the original definition + [FieldOffset(0x90)] public unsafe fixed byte _currentDirectories[RTL_MAX_DRIVE_LETTERS * 0x10 /* sizeof(RTL_DRIVE_LETTER_CURDIR32) */]; + //public unsafe fixed RTL_DRIVE_LETTER_CURDIR _pCurrentDirectories[RTL_MAX_DRIVE_LETTERS]; + public unsafe RTL_DRIVE_LETTER_CURDIR32[] CurrentDirectories + { + get + { + fixed (byte* pCurrentDirectories = &_currentDirectories[0]) + return new ReadOnlySpan(&pCurrentDirectories, RTL_MAX_DRIVE_LETTERS).ToArray(); + } + } + + [FieldOffset(0x0290)] public UIntPtr32 EnvironmentSize; + [FieldOffset(0x0294)] public UIntPtr32 EnvironmentVersion; + + [FieldOffset(0x0298)] public UIntPtr32 PackageDependencyData; + [FieldOffset(0x029C)] public uint ProcessGroupId; + [FieldOffset(0x02A0)] public uint LoaderThreads; + + [FieldOffset(0x02A4)] public UNICODE_STRING32 RedirectionDllName; // REDSTONE4 + [FieldOffset(0x02AC)] public UNICODE_STRING32 HeapPartitionName; // 19H1 + [FieldOffset(0x02B4)] public UIntPtr32 DefaultThreadpoolCpuSetMasks; + [FieldOffset(0x02B8)] public uint DefaultThreadpoolCpuSetMaskCount; + [FieldOffset(0x02BC)] public uint DefaultThreadpoolThreadMaximum; +} + diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS64.cs new file mode 100644 index 0000000..4f31016 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS64.cs @@ -0,0 +1,67 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; + +namespace Windows.Win32.System.Threading; + +[StructLayout(LayoutKind.Explicit)] +internal partial struct RTL_USER_PROCESS_PARAMETERS64 +{ + [FieldOffset(0x00)] public uint MaximumLength; + [FieldOffset(0x04)] public uint Length; + + [FieldOffset(0x08)] public uint Flags; + [FieldOffset(0x0C)] public uint DebugFlags; + + [FieldOffset(0x10)] public HANDLE64 ConsoleHandle; + [FieldOffset(0x18)] public uint ConsoleFlags; + [FieldOffset(0x20)] public HANDLE64 StandardInput; + [FieldOffset(0x28)] public HANDLE64 StandardOutput; + [FieldOffset(0x30)] public HANDLE64 StandardError; + + [FieldOffset(0x38)] public CURDIR64 CurrentDirectory; + [FieldOffset(0x50)] public UNICODE_STRING64 DllPath; + [FieldOffset(0x60)] public UNICODE_STRING64 ImagePathName; + [FieldOffset(0x70)] public UNICODE_STRING64 CommandLine; + [FieldOffset(0x80)] public UIntPtr64 Environment; // 64-bit pointer + + [FieldOffset(0x88)] public uint StartingX; + [FieldOffset(0x8C)] public uint StartingY; + [FieldOffset(0x90)] public uint CountX; + [FieldOffset(0x94)] public uint CountY; + [FieldOffset(0x98)] public uint CountCharsX; + [FieldOffset(0x9C)] public uint CountCharsY; + [FieldOffset(0xA0)] public uint FillAttribute; + + [FieldOffset(0xA4)] public uint WindowFlags; + [FieldOffset(0xA8)] public uint ShowWindowFlags; + [FieldOffset(0xB0)] public UNICODE_STRING64 WindowTitle; + [FieldOffset(0xC0)] public UNICODE_STRING64 DesktopInfo; + [FieldOffset(0xD0)] public UNICODE_STRING64 ShellInfo; + [FieldOffset(0xE0)] public UNICODE_STRING64 RuntimeData; + + const int RTL_MAX_DRIVE_LETTERS = 0x20; + // note: CurrentDirectories is misspelled as CurrentDirectores in the original definition + [FieldOffset(0xF0)] public unsafe fixed byte _currentDirectories[RTL_MAX_DRIVE_LETTERS * 0x18/* sizeof(RTL_DRIVE_LETTER_CURDIR64) */]; + //public unsafe fixed RTL_DRIVE_LETTER_CURDIR _pCurrentDirectories[RTL_MAX_DRIVE_LETTERS]; + public unsafe RTL_DRIVE_LETTER_CURDIR64[] CurrentDirectories + { + get + { + fixed (byte* pCurrentDirectories = &_currentDirectories[0]) + return new ReadOnlySpan(&pCurrentDirectories, RTL_MAX_DRIVE_LETTERS).ToArray(); + } + } + + [FieldOffset(0x03F0)] public UIntPtr64 EnvironmentSize; + [FieldOffset(0x03F8)] public UIntPtr64 EnvironmentVersion; + + [FieldOffset(0x0400)] public UIntPtr64 PackageDependencyData; + [FieldOffset(0x0408)] public uint ProcessGroupId; + [FieldOffset(0x040C)] public uint LoaderThreads; + + [FieldOffset(0x0410)] public UNICODE_STRING64 RedirectionDllName; // REDSTONE4 + [FieldOffset(0x0420)] public UNICODE_STRING64 HeapPartitionName; // 19H1 + [FieldOffset(0x0430)] public UIntPtr64 DefaultThreadpoolCpuSetMasks; + [FieldOffset(0x0438)] public uint DefaultThreadpoolCpuSetMaskCount; + [FieldOffset(0x043C)] public uint DefaultThreadpoolThreadMaximum; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs new file mode 100644 index 0000000..312b193 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs @@ -0,0 +1,125 @@ +// https://web.archive.org/web/https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry.htm +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; + +namespace Windows.Win32.System.WindowsProgramming; +[StructLayout(LayoutKind.Explicit)] +internal struct LDR_DATA_TABLE_ENTRY32 +{ + /// 3.10 and higher + [FieldOffset(0x00)] internal LIST_ENTRY32 InLoadOrderLinks; + /// 3.10 and higher + [FieldOffset(0x08)] internal LIST_ENTRY32 InMemoryOrderLinks; + /// 3.10 and higher + [FieldOffset(0x10)] internal LIST_ENTRY32 InInitializationOrderLinks; + /// 6.2 (Win8) and higher + [FieldOffset(0x10)] internal LIST_ENTRY32 InProgressLinks; + /// 3.10 and higher + [FieldOffset(0x18)] internal UIntPtr32 DllBase; + /// 3.10 and higher + [FieldOffset(0x1C)] internal UIntPtr32 Entrypoint; + /// 3.10 and higher + [FieldOffset(0x20)] internal uint SizeOfImage; + /// 3.10 and higher + [FieldOffset(0x24)] internal UNICODE_STRING32 FullDllName; + /// 3.10 and higher + [FieldOffset(0x2C)] internal UNICODE_STRING32 BaseDllName; + /// 6.2 (Win8) and higher + [FieldOffset(0x34)] internal unsafe fixed byte FlagGroup[4]; + /// 3.10 and higher + [FieldOffset(0x34)] internal LdrEntryFlags Flags; + /// 3.10 to 6.1 (Win7) + [FieldOffset(0x38)] internal ushort LoadCount; + /// 6.2 and higher + [FieldOffset(0x38)] internal ushort ObsoleteLoadCount; + /// all + [FieldOffset(0x3A)] internal ushort TlsIndex; + /// 3.10 and higher + [FieldOffset(0x3C)] internal LIST_ENTRY32 HashLinks; + /// 3.10 to 6.1 (Win7) + [FieldOffset(0x3C)] internal UIntPtr32 SectionPointer; + /// 3.10 to 6.1 (Win7) + [FieldOffset(0x3C)] internal uint CheckSum; + + #region Appended for Windows NT 4.0 + /// 4.0 and higher + [FieldOffset(0x44)] internal uint TimeDateStamp; + /// 4.0 to 6.1 + [FieldOffset(0x44)] internal UIntPtr32 LoadedImports; + #endregion Appended for Windows NT 4.0 + + #region Appended for Windows XP + /// 5.1 and higher + [FieldOffset(0x48)] internal UIntPtr32 EntryPointActivationContext; + /// 5.1 from Windows XP SP2 to 6.2 (Win8) + [FieldOffset(0x4C)] internal UIntPtr32 PatchInformation; + /// 6.3 only + [FieldOffset(0x4C)] internal UIntPtr32 Spare; + /// 10.0 and higher + [FieldOffset(0x4C)] internal UIntPtr32 Lock; + #endregion Appended for Windows XP + + #region Appended for Windows Vista + /// 6.0 to 6.1 + [FieldOffset(0x50)] internal LIST_ENTRY32 ForwarderLinks; + /// 6.0 to 6.1 + [FieldOffset(0x58)] internal LIST_ENTRY32 ServiceTagLinks; + /// 6.0 to 6.1 + [FieldOffset(0x60)] internal LIST_ENTRY32 StaticLinks; + #endregion Appended for Windows Vista + + #region Redone for Windows 8 + /// (LDR_DDAG_NODE*) 6.2 and higher + [FieldOffset(0x50)] UIntPtr32 DdagNode; + /// 6.2 and higher + [FieldOffset(0x54)] LIST_ENTRY32 NodeModuleLink; + /// (LDRP_DLL_SNAP_CONTEXT*) 6.2 to 6.3 + [FieldOffset(0x5C)] UIntPtr32 SnapContext; + /// (LDRP_LOAD_CONTEXT*) 10.0 and higher + [FieldOffset(0x5C)] UIntPtr32 LoadContext; + /// 6.2 and higher + [FieldOffset(0x60)] UIntPtr32 ParentBaseDll; + /// 6.2 and higher + [FieldOffset(0x64)] UIntPtr32 SwitchBackContext; + /// 6.2 and higher + [FieldOffset(0x68)] RTL_BALANCED_NODE32 BaseAddressIndexNode; + /// 6.2 and higher + [FieldOffset(0x70)] RTL_BALANCED_NODE32 MappingInfoIndexNode; + #endregion Redone for Windows 8 + + #region Appended for Windows 7 + /// 6.1 only + [FieldOffset(0x68)] UIntPtr32 ContextInformation; + /// 6.1 only + [FieldOffset(0x6C)] UIntPtr32 OriginalBase_NT61; + /// 6.2 and higher + [FieldOffset(0x80)] UIntPtr32 OriginalBase_NT62; + /// 6.1 only + [FieldOffset(0x70)] LARGE_INTEGER LoadTime_NT61; + /// 6.2 and higher + [FieldOffset(0x88)] LARGE_INTEGER LoadTime_NT62; + #endregion Appended for Windows 7 + + #region Appended for Windows 8 + /// 6.2 and higher + [FieldOffset(0x90)] internal uint BaseNameHashValue; + /// 6.2 and higher + [FieldOffset(0x94)] internal LDR_DLL_LOAD_REASON LoadReason; + #endregion Appended for Windows 8 + + #region Appended for Windows 8.1 + /// 6.3 and higher + [FieldOffset(0x98)] internal uint ImplicitPathOptions; + #endregion Appended for Windows 8.1 + + #region Appended for Windows 10 + /// 10.0 and higher + [FieldOffset(0x9C)] internal uint ReferenceCount; + /// 1607 and higher + [FieldOffset(0xA0)] internal uint DependentLoadFlags; + /// 1703 and higher + [FieldOffset(0xA4)] internal byte SigningLevel; + + #endregion Appended for Windows 10 +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs new file mode 100644 index 0000000..ca07664 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs @@ -0,0 +1,126 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; + +namespace Windows.Win32.System.WindowsProgramming; +/// +/// https://web.archive.org/web/https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry.htm +/// +[StructLayout(LayoutKind.Explicit)] +internal struct LDR_DATA_TABLE_ENTRY64 +{ + /// 3.10 and higher + [FieldOffset(0x00)] internal LIST_ENTRY64 InLoadOrderLinks; + /// 3.10 and higher + [FieldOffset(0x10)] internal LIST_ENTRY64 InMemoryOrderLinks; + /// 3.10 and higher + [FieldOffset(0x20)] internal LIST_ENTRY64 InInitializationOrderLinks; + /// 6.2 (Win8) and higher + [FieldOffset(0x20)] internal LIST_ENTRY64 InProgressLinks; + /// 3.10 and higher + [FieldOffset(0x30)] internal UIntPtr64 DllBase; + /// 3.10 and higher + [FieldOffset(0x38)] internal UIntPtr64 Entrypoint; + /// 3.10 and higher + [FieldOffset(0x40)] internal uint SizeOfImage; + /// 3.10 and higher + [FieldOffset(0x48)] internal UNICODE_STRING64 FullDllName; + /// 3.10 and higher + [FieldOffset(0x58)] internal UNICODE_STRING64 BaseDllName; + /// 6.2 (Win8) and higher + [FieldOffset(0x68)] internal unsafe fixed byte FlagGroup[4]; + /// 3.10 and higher + [FieldOffset(0x68)] internal LdrEntryFlags Flags; + /// 3.10 to 6.1 (Win7). Obsolete on 6.2 (Win8) and higher. + [FieldOffset(0x6C)] internal ushort LoadCount; + /// 6.2 and higher + [FieldOffset(0x6C)] internal ushort ObsoleteLoadCount; + /// all + [FieldOffset(0x6E)] internal ushort TlsIndex; + /// 3.10 and higher + [FieldOffset(0x70)] internal LIST_ENTRY64 HashLinks; + /// 3.10 to 6.1 (Win7). Obsolete on 6.2 (Win8) and higher. + [FieldOffset(0x70)] internal UIntPtr64 SectionPointer; + /// 3.10 to 6.1 (Win7). Obsolete on 6.2 (Win8) and higher. + [FieldOffset(0x70)] internal uint CheckSum; // 3.10 to 6.1 (Win7) + + #region Appended for Windows NT 4.0 + /// 4.0 and higher + [FieldOffset(0x80)] internal uint TimeDateStamp; + /// 4.0 to 6.1. Obsolete on 6.2 (Win8) and higher. + [FieldOffset(0x80)] internal UIntPtr64 LoadedImports; + #endregion Appended for Windows NT 4.0 + + #region Appended for Windows XP + /// 5.1 and higher + [FieldOffset(0x88)] internal UIntPtr64 EntryPointActivationContext; + /// 5.1 from Windows XP SP2 to 6.2 (Win8). Obsolete on 6.3 and higher. + [FieldOffset(0x90)] internal UIntPtr64 PatchInformation; + /// 6.3 only + [FieldOffset(0x90)] internal UIntPtr64 Spare; + /// 10.0 and higher + [FieldOffset(0x90)] internal UIntPtr64 Lock; + #endregion Appended for Windows XP + + #region Appended for Windows Vista + /// 6.0 to 6.1 (Win7) + [FieldOffset(0x98)] internal LIST_ENTRY64 ForwarderLinks; + /// 6.0 to 6.1 (Win7) + [FieldOffset(0xA8)] internal LIST_ENTRY64 ServiceTagLinks; + /// 6.0 to 6.1 (Win7) + [FieldOffset(0xB8)] internal LIST_ENTRY64 StaticLinks; + #endregion Appended for Windows Vista + + #region Redone for Windows 8 + /// (LDR_DDAG_NODE*) 6.2 and higher + [FieldOffset(0x98)] UIntPtr64 DdagNode; + /// 6.2 and higher + [FieldOffset(0xA0)] LIST_ENTRY64 NodeModuleLink; + /// (LDRP_DLL_SNAP_CONTEXT*) 6.2 to 6.3 + [FieldOffset(0xB0)] UIntPtr64 SnapContext; + /// (LDRP_LOAD_CONTEXT*) 10.0 and higher + [FieldOffset(0xB0)] UIntPtr64 LoadContext; + /// 6.2 and higher + [FieldOffset(0xB8)] UIntPtr64 ParentBaseDll; + /// 6.2 and higher + [FieldOffset(0xC0)] UIntPtr64 SwitchBackContext; + /// 6.2 and higher + [FieldOffset(0xC8)] RTL_BALANCED_NODE64 BaseAddressIndexNode; + /// 6.2 and higher + [FieldOffset(0xE0)] RTL_BALANCED_NODE64 MappingInfoIndexNode; + #endregion Redone for Windows 8 + + #region Appended for Windows 7 + /// 6.1 only + [FieldOffset(0xC8)] UIntPtr64 ContextInformation; + /// 6.1 only + [FieldOffset(0xD0)] UIntPtr64 OriginalBase_NT61; + /// 6.2 and higher + [FieldOffset(0xF8)] UIntPtr64 OriginalBase_NT62; + /// 6.1 only + [FieldOffset(0xD8)] LARGE_INTEGER LoadTime_NT61; + /// 6.2 and higher + [FieldOffset(0x0100)] LARGE_INTEGER LoadTime_NT62; + #endregion Appended for Windows 7 + + #region Appended for Windows 8 + /// 6.2 and higher + [FieldOffset(0x0108)] internal uint BaseNameHashValue; + /// 6.2 and higher + [FieldOffset(0x010C)] internal LDR_DLL_LOAD_REASON LoadReason; + #endregion Appended for Windows 8 + + #region Appended for Windows 8.1 + /// 6.3 and higher + [FieldOffset(0x0110)] internal uint ImplicitPathOptions; + #endregion Appended for Windows 8.1 + + #region Appended for Windows 10 + /// 10.0 and higher + [FieldOffset(0x0114)] internal uint ReferenceCount; + /// 1607 and higher + [FieldOffset(0x0118)] internal uint DependentLoadFlags; + /// 1703 and higher + [FieldOffset(0x011C)] internal byte SigningLevel; + #endregion Appended for Windows 10 +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DLL_LOAD_REASON.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DLL_LOAD_REASON.cs new file mode 100644 index 0000000..c8a44c5 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DLL_LOAD_REASON.cs @@ -0,0 +1,16 @@ +namespace Windows.Win32.System.WindowsProgramming; +internal enum LDR_DLL_LOAD_REASON +{ + LoadReasonStaticDependency, + LoadReasonStaticForwarderDependency, + LoadReasonDynamicForwarderDependency, + LoadReasonDelayloadDependency, + LoadReasonDynamicLoad, + LoadReasonAsImageLoad, + LoadReasonAsDataLoad, + /// 1709 and higher + LoadReasonEnclavePrimary, + /// 1709 and higher + LoadReasonEnclaveDependency, + LoadReasonUnknown = -1 +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LdrEntryFlags.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LdrEntryFlags.cs new file mode 100644 index 0000000..9ad328b --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LdrEntryFlags.cs @@ -0,0 +1,46 @@ +// https://web.archive.org/web/https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry.htm + +namespace Windows.Win32.System.WindowsProgramming; + +/// +/// unless otherwise specified, all flags are valid for NT 6.2 and higher. +/// Flags deprecated before Windows 7 are not included in this enum +/// +[Flags] +internal enum LdrEntryFlags : uint +{ + PackagedBinary = 0x1U, + /// 3.51 to 6.1 (Win7) + LDRP_STATIC_LINK = 1 << 1, + MarkedForRemoval = 1 << 1, + ImageDll = 1 << 2, + /// 5.1 to 6.1 (Win7) + LDRP_SHIMENG_ENTRY_PROCESSED = 1 << 3, + LoadNotificationsSent = 1 << 3, + TelemetryEntryProcessed = 1 << 4, + ProcessStaticImport = 1 << 5, + InLegacyLists = 1 << 6, + InIndexes = 1 << 7, + InExceptionTable = 1 << 8, + LoadInProgress = 1 << 12, + /// 3.51 to 6.1 (Win7) + LDRP_UNLOAD_IN_PROGRESS = 1 << 13, + /// 10.0 and higher + LoadConfigProcessed = 1 << 13, + EntryProcessed = 1 << 14, + /// 10.0 and higher + ProtectDelayLoad = 1 << 15, + DontCallForThreads = 1 << 18, + ProcessAttachCalled = 1 << 19, + ProcessAttachFailed = 1 << 20, + CorDeferredValidate = 1 << 21, + CorImage = 1 << 22, + /// 5.1 to 6.1 (Win7) + LDRP_COR_OWNS_UNMAP = 1 << 23, + DontRelocate = 1 << 23, + CorILOnly = 1 << 24, + /// 1803 and higher + ChpeImage = 1 << 25, + Redirected = 1 << 28, + CompatDatabaseProcessed = 1U << 31 +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPES_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs similarity index 93% rename from deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPES_INFORMATION.cs rename to deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs index 1af51ee..8361844 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPES_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs @@ -1,6 +1,4 @@ -using Microsoft.VisualBasic; - -namespace Windows.Win32; +namespace Windows.Win32.System.WindowsProgramming; struct OBJECT_TYPES_INFORMATION : IEquatable { public uint NumberOfTypes = 0; diff --git a/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPE_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs similarity index 98% rename from deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPE_INFORMATION.cs rename to deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs index 781cb28..fff7fe0 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/OBJECT_TYPE_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs @@ -1,7 +1,7 @@ using Windows.Win32.Foundation; using ACCESS_MASK = PInvoke.Kernel32.ACCESS_MASK; -namespace Windows.Win32; +namespace Windows.Win32.System.WindowsProgramming; internal struct OBJECT_TYPE_INFORMATION { #pragma warning disable CS0649 diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs new file mode 100644 index 0000000..8d933bf --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.InteropServices; +namespace Windows.Win32; + +/// +/// A stand-in for 32-bit pointers in a 64-bit runtime. +/// +internal struct UIntPtr32 +{ + public uint Value; + + public static implicit operator UIntPtr32(uint v) => new() { Value = v }; + public static implicit operator uint(UIntPtr32 v) => v.Value; + + public unsafe static explicit operator void*(UIntPtr32 v) => (void*)v.Value; +} + +internal struct UIntPtr32 where T : unmanaged +{ + public uint Value; + + public static implicit operator UIntPtr32(uint v) => new() { Value = v }; + public static implicit operator uint(UIntPtr32 v) => v.Value; + + public static explicit operator UIntPtr32(UIntPtr32 v) => v.Value; + public unsafe static explicit operator T*(UIntPtr32 v) => (T*)v.Value; + +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs new file mode 100644 index 0000000..5df9a75 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs @@ -0,0 +1,22 @@ +namespace Windows.Win32; + +/// +/// A stand-in for 64-bit pointers in a 32-bit runtime. +/// +internal struct UIntPtr64 +{ + public ulong Value; + + public static implicit operator UIntPtr64(ulong v) => new() { Value = v }; + public static implicit operator ulong(UIntPtr64 v) => v.Value; +} + +internal struct UIntPtr64 where T : unmanaged +{ + public ulong Value; + + public static implicit operator UIntPtr64(ulong v) => new() { Value = v }; + public static implicit operator ulong(UIntPtr64 v) => v.Value; + + public static explicit operator UIntPtr64(UIntPtr64 v) => v.Value; +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/ULARGE_INTEGER.cs b/deadlock-dotnet-sdk/Windows.Win32/ULARGE_INTEGER.cs new file mode 100644 index 0000000..f01d38b --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/ULARGE_INTEGER.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Windows.Win32; + +[StructLayout(LayoutKind.Explicit)] +internal struct ULARGE_INTEGER +{ + [FieldOffset(0x00)] internal uint LowPart; + [FieldOffset(0x04)] internal uint HighPart; + [FieldOffset(0x00)] internal ulong QuadPart; + +} From cab4e5b804d7077fd620c12997ede69f8169d92d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 19:27:36 -0700 Subject: [PATCH 086/306] refactor: inline temporary variable --- deadlock-dotnet-sdk/DeadLock.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index f96517d..6ac8bb3 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics; using deadlock_dotnet_sdk.Domain; using HandlesFilter = deadlock_dotnet_sdk.Domain.FileLockerEx.HandlesFilter; @@ -42,9 +42,8 @@ public DeadLock(bool rethrowExceptions) /// The FileLocker object that contains a List of Process objects that are locking a file public FileLocker FindLockingProcesses(string filePath) { - FileLocker fileLocker = new(filePath, + return new(filePath, NativeMethods.FindLockingProcesses(filePath, RethrowExceptions).ToList()); - return fileLocker; } /// From 9c29b0a213d411bf61f47b2e89520f599a96fba6 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 19:31:16 -0700 Subject: [PATCH 087/306] refactor: rename UnlockSystemHandle to CloseSourceHandle --- deadlock-dotnet-sdk/DeadLock.cs | 8 ++++---- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index 6ac8bb3..2be0308 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics; using deadlock_dotnet_sdk.Domain; using HandlesFilter = deadlock_dotnet_sdk.Domain.FileLockerEx.HandlesFilter; @@ -314,7 +314,7 @@ public void UnlockEx(FileLockerEx fileLocker) if (h.IsClosed && h.IsInvalid) continue; try { - h.UnlockSystemHandle(); + h.CloseSourceHandle(); } catch (Exception) when (!RethrowExceptions) { } } @@ -349,7 +349,7 @@ await Task.Run(() => if (h.IsClosed && h.IsInvalid) continue; try { - h.UnlockSystemHandle(); + h.CloseSourceHandle(); } catch (Exception) when (!RethrowExceptions) { } } @@ -371,7 +371,7 @@ await Task.Run(() => if (h.IsClosed && h.IsInvalid) continue; try { - h.UnlockSystemHandle(); + h.CloseSourceHandle(); } catch (Exception) when (!RethrowExceptions) { } } diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 0fb56f5..d1073dc 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -115,7 +115,7 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE /// See Raymond Chen's devblog article "Kernel handles are not reference-counted". /// /// Failed to open process to duplicate and close object handle. - public void UnlockSystemHandle() + public void CloseSourceHandle() { HANDLE rawHProcess; SafeProcessHandle? hProcess = null; @@ -147,8 +147,7 @@ public void UnlockSystemHandle() catch (Exception e) { ExceptionLog.Add(e); - if (hProcess is not null) - hProcess.Close(); + hProcess?.Close(); } } From 137c35fc2da8cba574f3c6521d1c2641e23a1b8d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:05:03 -0700 Subject: [PATCH 088/306] refactor: refactor NTSTATUS, mimic dotnet/PInvoke refactor: remove redundant const STATUS_INFO_LENGTH_MISMATCH; refactor: define 'using' aliases to ensure the correct Types are used. --- ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 22 +++++++------------ deadlock-dotnet-sdk/Domain/NativeMethods.cs | 14 ++++++------ .../Domain/SafeFileHandleEx.cs | 16 ++++++++------ deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 6 ++++- .../Windows.Win32/Foundation/NTSTATUS.cs | 11 ++++++++-- 5 files changed, 38 insertions(+), 31 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 175c90b..1b8c5ec 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -1,11 +1,10 @@ using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; -using PInvoke; -using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.WindowsProgramming; +using static PInvoke.Kernel32; using static Windows.Win32.PInvoke; -using NTSTATUS = PInvoke.NTSTATUS; +using Code = PInvoke.NTSTATUS.Code; // Re: StructLayout // "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." @@ -20,8 +19,6 @@ namespace deadlock_dotnet_sdk.Domain; internal static partial class NativeMethods { - private static bool IsSuccessful(this NTSTATUS status) => status.Severity == NTSTATUS.SeverityCode.STATUS_SEVERITY_SUCCESS; - private static bool NT_SUCCESS(this NTSTATUS status) => status.Severity == NTSTATUS.SeverityCode.STATUS_SEVERITY_SUCCESS; private const uint PH_LARGE_BUFFER_SIZE = int.MaxValue; private static List? objectTypes; private static List ObjectTypes => objectTypes ??= ObjectTypesInformationBuffer.PhEnumObjectTypes().ToList(); @@ -114,12 +111,12 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX /// This is a bitwise "Flags" data type. /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. /// - public Kernel32.ACCESS_MASK GrantedAccess { get; } // uint + public ACCESS_MASK GrantedAccess { get; } // uint public ushort CreatorBackTraceIndex { get; } // USHORT /// ProcessHacker defines a little over a dozen handle-able object types. public ushort ObjectTypeIndex { get; } // USHORT /// - public Kernel32.HandleFlags HandleAttributes { get; } // uint + public HandleFlags HandleAttributes { get; } // uint #pragma warning disable RCS1213, CS0169, IDE0051 // Remove unused field declaration. csharp(RCS1213) | Roslynator private readonly uint Reserved; #pragma warning restore RCS1213, CS0649, CS0169, IDE0051 @@ -252,6 +249,7 @@ public unsafe void ReAllocate(uint lengthInBytes) ///
/// An , a wrapper for OBJECT_TYPES_INFORMATION, OBJECT_TYPE_INFORMATION, and the allocated memory they occupy. /// + /// public static unsafe ObjectTypesInformationBuffer PhEnumObjectTypes() { NTSTATUS status; @@ -266,20 +264,16 @@ public static unsafe ObjectTypesInformationBuffer PhEnumObjectTypes() (void*)buffer.pointer, buffer.bytes, &returnLength - )) == NTSTATUS.Code.STATUS_INFO_LENGTH_MISMATCH) + )) == Code.STATUS_INFO_LENGTH_MISMATCH) { // Fail if we're resizing the buffer to something very large. if (returnLength * 1.5 > PH_LARGE_BUFFER_SIZE) - throw new NTStatusException(NTSTATUS.Code.STATUS_INSUFFICIENT_RESOURCES); + throw new PInvoke.NTStatusException(Code.STATUS_INSUFFICIENT_RESOURCES); buffer.ReAllocate((uint)(returnLength * 1.5)); } - if (!status.NT_SUCCESS()) - { - buffer.Dispose(); - throw new NTStatusException(status); - } + status.ThrowOnError(); return buffer; } diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index fc1b8d9..3de4866 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -7,7 +7,8 @@ using Windows.Win32.System.WindowsProgramming; using static deadlock_dotnet_sdk.Domain.FileLockerEx; using static Windows.Win32.PInvoke; -using NTSTATUS = PInvoke.NTSTATUS; +using Code = PInvoke.NTSTATUS.Code; +using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; using Win32Exception = System.ComponentModel.Win32Exception; // Re: StructLayout @@ -182,7 +183,6 @@ bool Discard(SafeFileHandleEx h) /// internal unsafe static ReadOnlySpan GetSystemHandleInfoEx() { - const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; const uint PH_LARGE_BUFFER_SIZE = 256 * 1024 * 1024; // 256 Mebibytes uint systemInformationLength = (uint)Marshal.SizeOf(); SYSTEM_HANDLE_INFORMATION_EX* pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.AllocHGlobal(Marshal.SizeOf()); @@ -195,7 +195,7 @@ internal unsafe static ReadOnlySpan GetSystem ReturnLength: ref returnLength ); - for (uint attempts = 0; status.Value == NTSTATUS.Code.STATUS_INFO_LENGTH_MISMATCH && attempts < 10; attempts++) + for (uint attempts = 0; status.Code is Code.STATUS_INFO_LENGTH_MISMATCH && attempts < 10; attempts++) { /** The value of returnLength depends on how many handles are open. Handles may be opened or closed before, during, and after this operation, so the return length is rarely correct. @@ -211,7 +211,7 @@ ref returnLength ); } - if (status != NTSTATUS.Code.STATUS_SUCCESS) + if (status != Code.STATUS_SUCCESS) { // Fall back to using the previous code that we've used since Windows XP (dmex) systemInformationLength = 0x10000; @@ -223,20 +223,20 @@ ref returnLength SystemInformation: pSysInfoBuffer, SystemInformationLength: systemInformationLength, ReturnLength: ref returnLength - )) == STATUS_INFO_LENGTH_MISMATCH) + )) == Code.STATUS_INFO_LENGTH_MISMATCH) { Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); systemInformationLength *= 2; // Fail if we're resizing the buffer to something very large. if (systemInformationLength > PH_LARGE_BUFFER_SIZE) - throw new NTStatusException(NTSTATUS.Code.STATUS_BUFFER_OVERFLOW); + throw new NTStatusException(Code.STATUS_BUFFER_OVERFLOW); pSysInfoBuffer = (SYSTEM_HANDLE_INFORMATION_EX*)Marshal.ReAllocHGlobal(pv: (IntPtr)pSysInfoBuffer, cb: (IntPtr)systemInformationLength); } } - if (status != NTSTATUS.Code.STATUS_SUCCESS) + if (status != Code.STATUS_SUCCESS) { Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); throw new NTStatusException(status); diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 7632817..835e75e 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -1,7 +1,11 @@ using System.Data; +using System.Diagnostics; using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using PInvoke; using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; +using Windows.Win32.System.Threading; using static deadlock_dotnet_sdk.Domain.NativeMethods; using static Windows.Win32.PInvoke; @@ -90,19 +94,17 @@ private unsafe string TryGetFinalPath() } else { - int error = Marshal.GetLastWin32Error(); - const int ERROR_PATH_NOT_FOUND = 3; - const int ERROR_NOT_ENOUGH_MEMORY = 8; - const int ERROR_INVALID_PARAMETER = 87; // 0x57 + Win32ErrorCode error = (Win32ErrorCode)Marshal.GetLastWin32Error(); + string errMsg = error.GetMessage(); /* Hold up. Let's free our memory before throwing exceptions. */ Marshal.FreeHGlobal(buffer); throw error switch { - ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{fullName}' was not found when querying a file handle.", fileName: fullName.ToString()), // Removable storage, deleted item, network shares, et cetera - ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation."), // unlikely, but possible if system has little free memory - ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags."), // possible only if FILE_NAME_NORMALIZED (0) is invalid + Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{fullName}' was not found when querying a file handle.", fileName: fullName.ToString()), // Removable storage, deleted item, network shares, et cetera + Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation."), // unlikely, but possible if system has little free memory + Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags."), // possible only if FILE_NAME_NORMALIZED (0) is invalid _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path."), }; } diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index d1073dc..22bf375 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -1,11 +1,15 @@ -using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; +using PInvoke; +using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.Threading; using static Windows.Win32.PInvoke; using ACCESS_MASK = PInvoke.Kernel32.ACCESS_MASK; +using Code = PInvoke.NTSTATUS.Code; +using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; +using Win32Exception = System.ComponentModel.Win32Exception; namespace deadlock_dotnet_sdk.Domain; diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs index a3c663f..11dc9be 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs @@ -1,4 +1,6 @@ using PInvoke; +using Code = PInvoke.NTSTATUS.Code; +using FacilityCode = PInvoke.NTSTATUS.FacilityCode; /// /// This file supplements code generated by CsWin32 /// @@ -8,7 +10,8 @@ readonly partial struct NTSTATUS { public bool IsSuccessful => SeverityCode is Severity.Success; - /// + /// Throws an exception if a P/Invoke failed. + /// If SeverityCode is Error, an exception detailing the NTSTATUS error code including the platform-dependent, native error message. public void ThrowOnError() { if (SeverityCode == Severity.Error) @@ -18,8 +21,12 @@ public void ThrowOnError() /// public string GetMessage() => ((global::PInvoke.NTSTATUS)this).GetMessage(); + public Code Code => (Code)Value; + public FacilityCode FacilityCode => ((global::PInvoke.NTSTATUS)this).Facility; + /* property SeverityCode is defined in generated code */ + public static implicit operator global::PInvoke.NTSTATUS(NTSTATUS v) => new(v.Value); public static implicit operator NTSTATUS(global::PInvoke.NTSTATUS v) => new(v.AsInt32); - public static implicit operator global::PInvoke.NTSTATUS.Code(NTSTATUS v) => ((global::PInvoke.NTSTATUS)v).Value; + public static implicit operator Code(NTSTATUS v) => ((global::PInvoke.NTSTATUS)v).Value; } From a9d70cbaf22dd71527859dc74c6814038fdcf906 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:07:46 -0700 Subject: [PATCH 089/306] docs: update inline code docs --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 3 +-- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 1cf962f..28488a5 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace deadlock_dotnet_sdk.Domain { @@ -74,7 +74,6 @@ public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions, o /// /// Filters for /// - /// TODO: rename to HandlesFilter [Flags] public enum HandlesFilter { diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 22bf375..b9f3a98 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -268,7 +268,7 @@ private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) /// /// Release all resources owned by the current process that are associated with this handle. /// - /// + /// Returns a bool indicating both and are true protected override bool ReleaseHandle() { Close(); From 7a21538befb7af88f02fd7c48a21b98a6da411ab Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:10:57 -0700 Subject: [PATCH 090/306] style: update file encoding to UTF-8 BOM --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 28488a5..a0426f2 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace deadlock_dotnet_sdk.Domain { From f50611602f218a0d3640ab53377ee6e0300012be Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:12:47 -0700 Subject: [PATCH 091/306] refactor: move unsafe context to method declaration --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 107 ++++++++++---------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 3de4866..dec2d53 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -41,85 +41,82 @@ internal static partial class NativeMethods /// Path to the file /// True if inner exceptions should be rethrown, otherwise false /// A collection of processes that are locking a file - internal static IEnumerable FindLockingProcesses(string path, bool rethrowExceptions) + internal static unsafe IEnumerable FindLockingProcesses(string path, bool rethrowExceptions) { - unsafe + using (PWSTR key = new((char*)Marshal.StringToHGlobalUni(Guid.NewGuid().ToString()))) { - using (PWSTR key = new((char*)Marshal.StringToHGlobalUni(Guid.NewGuid().ToString()))) + List processes = new(); + + // todo: new RmStartSession overload in CsWin32_NativeMethods.cs which can throw a StartSessionException derived from System.ComponentModel.Win32Exception + // Why? new Win32Exception() will get the last PInvoke error code in addition to the system's message for that Win32ErrorCode. + uint res = RmStartSession(out var handle, 0, key); // TODO: fix params + if (res != 0) { - List processes = new(); + throw new StartSessionException(); + } - // todo: new RmStartSession overload in CsWin32_NativeMethods.cs which can throw a StartSessionException derived from System.ComponentModel.Win32Exception - // Why? new Win32Exception() will get the last PInvoke error code in addition to the system's message for that Win32ErrorCode. - uint res = RmStartSession(out var handle, 0, key); - if (res != 0) - { - throw new StartSessionException(); - } + try + { + const int errorMoreData = 234; + uint pnProcInfo = 0; + uint lpdwRebootReasons = RmRebootReasonNone; + + string[] resources = { path }; - try + // "using" blocks have hidden "finally" blocks which are executed before exceptions leave this context. + using (PWSTR pResources = (char*)Marshal.StringToHGlobalUni(path)) { - const int errorMoreData = 234; - uint pnProcInfo = 0; - uint lpdwRebootReasons = RmRebootReasonNone; + res = RmRegisterResources(handle, new Span(new PWSTR[] { pResources }), rgApplications: new(), new()); - string[] resources = { path }; + if (res != 0) + { + throw new RegisterResourceException(); + } + + res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, out lpdwRebootReasons); - // "using" blocks have hidden "finally" blocks which are executed before exceptions leave this context. - using (PWSTR pResources = (char*)Marshal.StringToHGlobalUni(path)) + if (res == errorMoreData) // Win32ErrorCode.ERROR_MORE_DATA { - res = RmRegisterResources(handle, new Span(new PWSTR[] { pResources }), rgApplications: new(), new()); + ReadOnlySpan processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; + pnProcInfo = pnProcInfoNeeded; - if (res != 0) + fixed (RM_PROCESS_INFO* pProcessInfo = processInfo) { - throw new RegisterResourceException(); + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, pProcessInfo, out lpdwRebootReasons); } - - res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, out lpdwRebootReasons); - - if (res == errorMoreData) + if (res == 0) { - ReadOnlySpan processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; - pnProcInfo = pnProcInfoNeeded; + processes = new List((int)pnProcInfo); - fixed (RM_PROCESS_INFO* pProcessInfo = processInfo) - { - res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, pProcessInfo, out lpdwRebootReasons); - } - if (res == 0) + for (int i = 0; i < pnProcInfo; i++) { - processes = new List((int)pnProcInfo); - - for (int i = 0; i < pnProcInfo; i++) + try { - try - { - processes.Add(Process.GetProcessById((int)processInfo[i].Process.dwProcessId)); - } - catch (ArgumentException) - { - if (rethrowExceptions) throw; - } + processes.Add(Process.GetProcessById((int)processInfo[i].Process.dwProcessId)); + } + catch (ArgumentException) + { + if (rethrowExceptions) throw; } - } - else - { - throw new RmListException(); } } - else if (res != 0) + else { - throw new UnauthorizedAccessException(); + throw new RmListException(); } } + else if (res != 0) + { + throw new UnauthorizedAccessException(); + } } - finally - { - _ = RmEndSession(handle); - } - - return processes; } + finally + { + _ = RmEndSession(handle); + } + + return processes; } } From 777e7eee3c634a8482d4c6b1dbd79eb136be6ad2 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:14:17 -0700 Subject: [PATCH 092/306] refactor: simplify 'using' statement csharp(IDE0063) --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 104 ++++++++++---------- 1 file changed, 50 insertions(+), 54 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index dec2d53..793929c 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -43,81 +43,77 @@ internal static partial class NativeMethods /// A collection of processes that are locking a file internal static unsafe IEnumerable FindLockingProcesses(string path, bool rethrowExceptions) { - using (PWSTR key = new((char*)Marshal.StringToHGlobalUni(Guid.NewGuid().ToString()))) + using PWSTR key = new((char*)Marshal.StringToHGlobalUni(Guid.NewGuid().ToString())); + List processes = new(); + + // todo: new RmStartSession overload in CsWin32_NativeMethods.cs which can throw a StartSessionException derived from System.ComponentModel.Win32Exception + // Why? new Win32Exception() will get the last PInvoke error code in addition to the system's message for that Win32ErrorCode. + uint res = RmStartSession(out var handle, 0, key); // TODO: fix params + if (res != 0) + { + throw new StartSessionException(); + } + + try { - List processes = new(); + const int errorMoreData = 234; + uint pnProcInfo = 0; + uint lpdwRebootReasons = RmRebootReasonNone; + + string[] resources = { path }; + + // "using" blocks have hidden "finally" blocks which are executed before exceptions leave this context. + using PWSTR pResources = (char*)Marshal.StringToHGlobalUni(path); + res = RmRegisterResources(handle, new Span(new PWSTR[] { pResources }), rgApplications: new(), new()); - // todo: new RmStartSession overload in CsWin32_NativeMethods.cs which can throw a StartSessionException derived from System.ComponentModel.Win32Exception - // Why? new Win32Exception() will get the last PInvoke error code in addition to the system's message for that Win32ErrorCode. - uint res = RmStartSession(out var handle, 0, key); // TODO: fix params if (res != 0) { - throw new StartSessionException(); + throw new RegisterResourceException(); } - try - { - const int errorMoreData = 234; - uint pnProcInfo = 0; - uint lpdwRebootReasons = RmRebootReasonNone; + res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, out lpdwRebootReasons); - string[] resources = { path }; + if (res == errorMoreData) // Win32ErrorCode.ERROR_MORE_DATA + { + ReadOnlySpan processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; + pnProcInfo = pnProcInfoNeeded; - // "using" blocks have hidden "finally" blocks which are executed before exceptions leave this context. - using (PWSTR pResources = (char*)Marshal.StringToHGlobalUni(path)) + fixed (RM_PROCESS_INFO* pProcessInfo = processInfo) { - res = RmRegisterResources(handle, new Span(new PWSTR[] { pResources }), rgApplications: new(), new()); - - if (res != 0) - { - throw new RegisterResourceException(); - } - - res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, out lpdwRebootReasons); + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, pProcessInfo, out lpdwRebootReasons); + } + if (res == 0) + { + processes = new List((int)pnProcInfo); - if (res == errorMoreData) // Win32ErrorCode.ERROR_MORE_DATA + for (int i = 0; i < pnProcInfo; i++) { - ReadOnlySpan processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; - pnProcInfo = pnProcInfoNeeded; - - fixed (RM_PROCESS_INFO* pProcessInfo = processInfo) - { - res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, pProcessInfo, out lpdwRebootReasons); - } - if (res == 0) + try { - processes = new List((int)pnProcInfo); - - for (int i = 0; i < pnProcInfo; i++) - { - try - { - processes.Add(Process.GetProcessById((int)processInfo[i].Process.dwProcessId)); - } - catch (ArgumentException) - { - if (rethrowExceptions) throw; - } - } + processes.Add(Process.GetProcessById((int)processInfo[i].Process.dwProcessId)); } - else + catch (ArgumentException) { - throw new RmListException(); + if (rethrowExceptions) throw; } } - else if (res != 0) - { - throw new UnauthorizedAccessException(); - } + } + else + { + throw new RmListException(); } } - finally + else if (res != 0) { - _ = RmEndSession(handle); + throw new UnauthorizedAccessException(); } - - return processes; } + finally + { + _ = RmEndSession(handle); + } + + return processes; } /// From 96f3e5eb270490e8fd7f22b0bebd062d7273014d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:15:50 -0700 Subject: [PATCH 093/306] fix: use correct overload for 'out' param modifier --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 793929c..e3f6b48 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -48,7 +48,7 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo // todo: new RmStartSession overload in CsWin32_NativeMethods.cs which can throw a StartSessionException derived from System.ComponentModel.Win32Exception // Why? new Win32Exception() will get the last PInvoke error code in addition to the system's message for that Win32ErrorCode. - uint res = RmStartSession(out var handle, 0, key); // TODO: fix params + uint res = RmStartSession(out uint handle, key); if (res != 0) { throw new StartSessionException(); From 0242b567a3467c9f0b213c0a37bb6115b4f022ba Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:18:45 -0700 Subject: [PATCH 094/306] fix: use correct types for param rgsFileNames of RmRegisterResources The overload requires a span of PCWSTR, not PWSTR. PCWSTR: A pointer to a null-terminated, constant character string. --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index e3f6b48..c1baafa 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -64,7 +64,7 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo // "using" blocks have hidden "finally" blocks which are executed before exceptions leave this context. using PWSTR pResources = (char*)Marshal.StringToHGlobalUni(path); - res = RmRegisterResources(handle, new Span(new PWSTR[] { pResources }), rgApplications: new(), new()); + res = RmRegisterResources(handle, new Span(new PCWSTR[] { pResources }), rgApplications: new(), new()); if (res != 0) { From 58faeaf8faf0fc7ee7b8bd41c60286a567d4d5a5 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:24:13 -0700 Subject: [PATCH 095/306] docs: remove a TODO (will not implement) --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index c1baafa..a48ee19 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -46,7 +46,6 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo using PWSTR key = new((char*)Marshal.StringToHGlobalUni(Guid.NewGuid().ToString())); List processes = new(); - // todo: new RmStartSession overload in CsWin32_NativeMethods.cs which can throw a StartSessionException derived from System.ComponentModel.Win32Exception // Why? new Win32Exception() will get the last PInvoke error code in addition to the system's message for that Win32ErrorCode. uint res = RmStartSession(out uint handle, key); if (res != 0) From 1a8cb83d66749a4cd6ffe3e5f1fc7ad7ef4c1de1 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:54:49 -0700 Subject: [PATCH 096/306] refactor: improve details of custom exceptions --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 40 ++++++++----------- .../Exceptions/RegisterResourceException.cs | 11 ++++- .../Exceptions/RmListException.cs | 6 +++ .../Exceptions/StartSessionException.cs | 8 ++++ 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index a48ee19..fd5d5c5 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -41,21 +41,21 @@ internal static partial class NativeMethods /// Path to the file /// True if inner exceptions should be rethrown, otherwise false /// A collection of processes that are locking a file + /// Failed to start Restart Manager session. See InnerException for details. + /// Failed to register resources to the Restart Manager session. See InnerException for details. + /// Failed to get list of applications and services that are currently using the resources registered with the Restart Manager session. See InnerException for details. + /// Failed to get list of applications and services that are currently using the resources registered with the Restart Manager session. See InnerException for details. internal static unsafe IEnumerable FindLockingProcesses(string path, bool rethrowExceptions) { using PWSTR key = new((char*)Marshal.StringToHGlobalUni(Guid.NewGuid().ToString())); List processes = new(); - // Why? new Win32Exception() will get the last PInvoke error code in addition to the system's message for that Win32ErrorCode. - uint res = RmStartSession(out uint handle, key); + Win32ErrorCode res = (Win32ErrorCode)RmStartSession(out uint handle, key); if (res != 0) - { - throw new StartSessionException(); - } + throw new StartSessionException("Failed to start Restart Manager session.", new PInvoke.Win32Exception(res)); try { - const int errorMoreData = 234; uint pnProcInfo = 0; uint lpdwRebootReasons = RmRebootReasonNone; @@ -63,25 +63,21 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo // "using" blocks have hidden "finally" blocks which are executed before exceptions leave this context. using PWSTR pResources = (char*)Marshal.StringToHGlobalUni(path); - res = RmRegisterResources(handle, new Span(new PCWSTR[] { pResources }), rgApplications: new(), new()); - if (res != 0) + if ((res = (Win32ErrorCode)RmRegisterResources(handle, new Span(new PCWSTR[] { pResources }), rgApplications: new(), new())) + is not Win32ErrorCode.ERROR_SUCCESS) { - throw new RegisterResourceException(); + throw new RegisterResourceException("Failed to register resources to the Restart Manager session.", new PInvoke.Win32Exception(res)); } - res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, out lpdwRebootReasons); - - if (res == errorMoreData) // Win32ErrorCode.ERROR_MORE_DATA + if ((res = (Win32ErrorCode)RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, out lpdwRebootReasons)) + is Win32ErrorCode.ERROR_MORE_DATA) { ReadOnlySpan processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; pnProcInfo = pnProcInfoNeeded; fixed (RM_PROCESS_INFO* pProcessInfo = processInfo) - { - res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, pProcessInfo, out lpdwRebootReasons); - } - if (res == 0) + res = (Win32ErrorCode)RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, pProcessInfo, out lpdwRebootReasons); if (res is Win32ErrorCode.ERROR_SUCCESS) { processes = new List((int)pnProcInfo); @@ -91,20 +87,18 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo { processes.Add(Process.GetProcessById((int)processInfo[i].Process.dwProcessId)); } - catch (ArgumentException) - { - if (rethrowExceptions) throw; - } + catch (ArgumentException) when (!rethrowExceptions) + { } } } else { - throw new RmListException(); + throw new RmListException("Failed to get list of applications and services that are currently using the resources registered with the Restart Manager session.", new PInvoke.Win32Exception(res)); } } - else if (res != 0) + else if (res is not Win32ErrorCode.ERROR_SUCCESS) { - throw new UnauthorizedAccessException(); + throw new UnauthorizedAccessException("Failed to get list of applications and services that are currently using the resources registered with the Restart Manager session.", new PInvoke.Win32Exception(res)); } } finally diff --git a/deadlock-dotnet-sdk/Exceptions/RegisterResourceException.cs b/deadlock-dotnet-sdk/Exceptions/RegisterResourceException.cs index f1e0536..e6e1f5d 100644 --- a/deadlock-dotnet-sdk/Exceptions/RegisterResourceException.cs +++ b/deadlock-dotnet-sdk/Exceptions/RegisterResourceException.cs @@ -1,4 +1,6 @@ -namespace deadlock_dotnet_sdk.Exceptions +using PInvoke; + +namespace deadlock_dotnet_sdk.Exceptions { public class RegisterResourceException : Exception { @@ -9,5 +11,12 @@ public RegisterResourceException() { // Default constructor } + + public RegisterResourceException(string? message) : base(message) + { } + + public RegisterResourceException(string? message, Exception innerException) : base(message, innerException) + { } + } } diff --git a/deadlock-dotnet-sdk/Exceptions/RmListException.cs b/deadlock-dotnet-sdk/Exceptions/RmListException.cs index 4bd2ec7..a3de757 100644 --- a/deadlock-dotnet-sdk/Exceptions/RmListException.cs +++ b/deadlock-dotnet-sdk/Exceptions/RmListException.cs @@ -9,5 +9,11 @@ public RmListException() { // Default constructor } + + public RmListException(string? message) : base(message) + { } + + public RmListException(string? message, Exception? innerException) : base(message, innerException) + { } } } diff --git a/deadlock-dotnet-sdk/Exceptions/StartSessionException.cs b/deadlock-dotnet-sdk/Exceptions/StartSessionException.cs index a8caf3e..3d27c0f 100644 --- a/deadlock-dotnet-sdk/Exceptions/StartSessionException.cs +++ b/deadlock-dotnet-sdk/Exceptions/StartSessionException.cs @@ -9,5 +9,13 @@ public StartSessionException() { // Default constructor } + + public StartSessionException(string? message) : base(message) + { + } + + public StartSessionException(string? message, Exception? innerException) : base(message, innerException) + { + } } } From d1fe87b20afbba6049f0846365ac056b190b7e98 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 21:59:11 -0700 Subject: [PATCH 097/306] refactor: replace target-OS vars with host-OS vars --- ...tiveMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 1b8c5ec..db9b458 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -28,15 +28,13 @@ internal static partial class NativeMethods /// private static uint? GetObjectTypeNumber(string typeName) { - Version WINDOWS_8_1 = new(6, 2); - Version WindowsVersion = Environment.OSVersion.Version; uint objectIndex = uint.MaxValue; for (int i = 0; i < ObjectTypes.Count; i++) { if (typeName.Equals(ObjectTypes[i].TypeName, StringComparison.OrdinalIgnoreCase)) { - if (WindowsVersion >= WINDOWS_8_1) + if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) objectIndex = ObjectTypes[i].TypeIndex; else objectIndex = (uint)(i + 2); @@ -54,13 +52,11 @@ internal static partial class NativeMethods ///
private static string GetObjectTypeName(int typeIndex) { - Version WINDOWS_8_1 = new(6, 2); - Version WindowsVersion = Environment.OSVersion.Version; string objectTypeName = ""; for (int i = 0; i < ObjectTypes.Count; i++) { - if (WindowsVersion >= WINDOWS_8_1) + if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) { if (typeIndex == ObjectTypes[i].TypeIndex) objectTypeName = ObjectTypes[i].TypeName; @@ -294,7 +290,7 @@ public unsafe uint PhGetObjectTypeNumber(string typeName) if (string.Equals(typeNameSr, typeName, StringComparison.OrdinalIgnoreCase)) { - if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version >= new Version(6, 3)) + if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) { objectIndex = objectType->TypeIndex; break; @@ -309,7 +305,6 @@ public unsafe uint PhGetObjectTypeNumber(string typeName) objectType = PH_NEXT_OBJECT_TYPE(objectType); } } - return objectIndex; } @@ -323,7 +318,7 @@ public unsafe uint PhGetObjectTypeNumber(string typeName) for (i = 0; i < NumberOfTypes; i++) { - if (OperatingSystem.IsWindowsVersionAtLeast(6, 2)) + if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) { if (TypeIndex == objectType->TypeIndex) { From ad659aa710dd33138922e33e754ecba2ab366992 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 22:00:47 -0700 Subject: [PATCH 098/306] refactor: utilize new ctor to init buffer --- .../Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index db9b458..9261db2 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -249,11 +249,9 @@ public unsafe void ReAllocate(uint lengthInBytes) public static unsafe ObjectTypesInformationBuffer PhEnumObjectTypes() { NTSTATUS status; - ObjectTypesInformationBuffer buffer; + using ObjectTypesInformationBuffer buffer = new(0x1000); uint returnLength; - buffer = new(0x1000); - while ((status = NtQueryObject( null, OBJECT_INFORMATION_CLASS.ObjectTypesInformation, From 143b27d8f6fb6dc61d0b9c370468b84c6344e3fd Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:18:12 -0700 Subject: [PATCH 099/306] refactor: overhaul GetProcessCommandLine This adds the following: * Parameter validation * Exception handling and throwing * On windows 8.1 or later, NtQueryInformationProcess is called with ProcessCommandLineInformation (60). Otherwise... * On Windows 8.0 and below: * As a 32-bit process, acquire a 64-bit process's command line. We use a custom, ulong-based UIntPtr64 for pointers. 1. Pass ProcessBasicInformation (0) to NtWow64QueryInformationProcess64 to copy the 64-bit PROCESS_BASIC_INFORMATION. 2. Pass Peb field to NtWow64ReadVirtualMemory64 to copy the 64-bit PEB. 3. Pass ProcessParameters field to NtWow64ReadVirtualMemory64 to copy the 64-bit RTL_USER_PROCESS_PARAMETERS. 4. Pass CommandLine field to NtWow64ReadVirtualMemory to copy the UNICODE_STRING command line. * As a 64-bit process, acquire 32-bit process's command line. We use a custom, uint-based `UIntrPtr32` for pointers. 1. Pass ProcessBasicInformation (0) to NtQueryInformationProcess to copy the 32-bit PROCESS_BASIC_INFORMATION. 2. Pass the Peb field to ReadProcessMemory to copy target's 32-bit PEB. 3. Pass ProcessParameters field to ReadProcessMemory to copy the 32-bit RTL_USER_PROCESS_PARAMETERS. 4. Pass CommandLine field to ReadProcessMemory to copy the UNICODE_STRING command line. --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 275 +++++++++++++++++---- 1 file changed, 232 insertions(+), 43 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index b9f3a98..629580a 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -22,6 +22,8 @@ namespace deadlock_dotnet_sdk.Domain; ///
public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { + private string? processCommandLine; + public SafeHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) { } @@ -72,12 +74,11 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE /** Get Process's name */ if (!string.IsNullOrWhiteSpace(ProcessMainModulePath)) - { ProcessName = Path.GetFileNameWithoutExtension(ProcessMainModulePath); - } /** Get process's possibly-overwritten command line from the PEB struct in its memory space */ - ProcessCommandLine = GetProcessCommandLine(hProcess); + //ProcessCommandLine = GetProcessCommandLine(ProcessId); + // moved to property's Get accessor } } catch (Exception e) @@ -99,9 +100,15 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE ///
/// public string? HandleObjectType { get; } - public string? ProcessCommandLine { get; init; } + //public bool ProcessIs64Bit { get; } // unused, for now + public string? ProcessCommandLine + { + get => processCommandLine ??= GetProcessCommandLine(ProcessId); // if null, call function and assign value + init { processCommandLine = value; } + } public string? ProcessMainModulePath { get; } public string? ProcessName { get; } + //internal PEB_Ex? PebEx { get; } // Won't need this unless we want to start accessing otherwise unread pointer-type members of the PEB and its children (e.g. PEB_LDR_DATA, CURDIR, et cetera) /// /// A list of exceptions thrown by constructors and other methods of this class.
@@ -202,9 +209,18 @@ private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) throw new Win32Exception(); } + } + + private unsafe static string GetProcessCommandLine(uint processId) + { + if (processId == (uint)Environment.ProcessId) + return Environment.CommandLine; - // PWSTR instances are freed by their using blocks' finalizers - return retVal; + Process.EnterDebugMode(); + using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, processId); + if (hProcess.IsInvalid) + throw new Win32Exception(); + else return GetProcessCommandLine(hProcess); } /// @@ -216,52 +232,225 @@ private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) /// NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION' private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) { - /* Get PROCESS_BASIC_INFORMATION */ - uint sysInfoLength = (uint)Marshal.SizeOf(); - PROCESS_BASIC_INFORMATION processBasicInfo; - IntPtr sysInfo = Marshal.AllocHGlobal((int)sysInfoLength); - NTSTATUS status = (NTSTATUS)0; - uint retLength = 0; - if ((status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - (void*)sysInfo, - sysInfoLength, - ref retLength)) - .IsSuccessful) + if (hProcess.IsInvalid) + throw new ArgumentException("The provided process handle is invalid.", paramName: nameof(hProcess)); + + if (!IsWow64Process(hProcess, out BOOL targetIs32BitProcess)) + throw new Win32Exception(); + + bool weAre32BitAndTheyAre64Bit = !Environment.Is64BitProcess && !targetIs32BitProcess; + bool weAre64BitAndTheyAre32Bit = Environment.Is64BitProcess && targetIs32BitProcess; + NTSTATUS status; + uint returnLength = 0; + ulong bytesRead; + + /** If Win8.1 or later */ + if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) { - processBasicInfo = Marshal.PtrToStructure(sysInfo); //DevSkim: ignore DS104456 + const uint ProcessCommandLineInformation = 60u; + uint bufferLength = (uint)Marshal.SizeOf() + 260u; + UNICODE_STRING* pString = (UNICODE_STRING*)Marshal.AllocHGlobal((int)bufferLength); - // if our process is WOW64, we need to account for different pointer sizes if - // the target process is 64-bit - IsWow64Process(hProcess, out BOOL wow64Process); - if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess && wow64Process) + status = NtQueryInformationProcess( + hProcess, + (PROCESSINFOCLASS)ProcessCommandLineInformation, + pString, + bufferLength, + ref returnLength + ); + + if (status == Code.STATUS_INFO_LENGTH_MISMATCH) { - throw new NotImplementedException("Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented."); - // too much trouble. If someone else wants to do it, be my guest. - // https://stackoverflow.com/a/36798492/14894786 - // Reason: if our process is 32-bit, we'd be stuck with 32-bit pointers. - // If the PEB's address is in 64-bit address space, we can't access it - // because the pointer value we received was truncated from 64 bits to - // 32 bits. + bufferLength = returnLength; + pString->MaximumLength = (ushort)returnLength; + pString->Buffer = new((char*)Marshal.ReAllocHGlobal((IntPtr)pString->Buffer.Value, (IntPtr)bufferLength)); + + status = NtQueryInformationProcess( + hProcess, + (PROCESSINFOCLASS)ProcessCommandLineInformation, + pString, + bufferLength, + ref returnLength + ); } - IntPtr buf = Marshal.AllocHGlobal(sizeof(PEB)); - if (ReadProcessMemory(hProcess, processBasicInfo.PebBaseAddress, (void*)buf, (nuint)sizeof(PEB), null)) + if (status.IsSuccessful) + return pString->ToStringLength(); + else + throw new NTStatusException(status); + } + else /** Read CommandLine from PEB's Process Parameters */ + { + /** if our process is 32-bit and the target process is 64-bit, use a workaround. + The following blocks use a hybrid of SystemInformer's solution (PhGetProcessCommandLine) and the alternative provided at https://stackoverflow.com/a/14012919/14894786. + All comments inside the code blocks are from either source. + */ + if (weAre32BitAndTheyAre64Bit) /** This process is 32-bit, that process is 64-bit */ { - PEB peb = Marshal.PtrToStructure(buf); //DevSkim: ignore DS104456 - return (*peb.ProcessParameters).CommandLine.ToStringLength(); + using SafeBuffer buffer = new(true); + PROCESS_BASIC_INFORMATION64 basicInfo = default; + PEB64 peb = default; + RTL_USER_PROCESS_PARAMETERS64 parameters = default; + + // Get the PEB address. + buffer.Initialize(numElements: 1); + status = NtWow64QueryInformationProcess64( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + &basicInfo, + (uint)buffer.ByteLength, + &returnLength); + buffer.Initialize(numBytes: returnLength); + byte* pointer = null; + buffer.AcquirePointer(ref pointer); + status = NtWow64QueryInformationProcess64(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, pointer, (uint)buffer.ByteLength, &returnLength); + buffer.ReleasePointer(); + if (status.IsSuccessful) + { + basicInfo = buffer.Read(0); + buffer.Dispose(); + } + else + { + throw new Exception("NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB.", new NTStatusException(status)); + } + + // copy PEB + if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)basicInfo.PebBaseAddress, &peb, (ulong)Marshal.SizeOf(peb), &bytesRead)).IsSuccessful) + throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process.", new NTStatusException(status)); + + // Copy RTL_USER_PROCESS_PARAMETERS. + if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)peb.ProcessParameters, ¶meters, (ulong)Marshal.SizeOf(parameters), &bytesRead)).IsSuccessful) + throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process.", new NTStatusException(status)); + + using UNICODE_STRING cmdLine = new() + { + MaximumLength = parameters.CommandLine.MaximumLength, + Length = parameters.CommandLine.Length, + Buffer = (char*)Marshal.AllocHGlobal(parameters.CommandLine.MaximumLength) + }; + + if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, &bytesRead)).IsSuccessful) + throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process.", new NTStatusException(status)); + + return cmdLine.ToStringLength(); } - else + else if (weAre64BitAndTheyAre32Bit) /** This is 64-bit, that is 32-bit */ { - // this calls Marshal.GetLastPInvokeError() - // https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/ComponentModel/Win32Exception.cs?L46 - throw new Win32Exception("Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible."); + using SafeBuffer buffer = new(true); + PROCESS_BASIC_INFORMATION32 basicInfo = default; + PEB32 peb = default; + RTL_USER_PROCESS_PARAMETERS32 parameters = default; + + // Get the PEB address. + buffer.Initialize(numElements: 1); + status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + &basicInfo, + (uint)buffer.ByteLength, + ref returnLength); + while (status == Code.STATUS_INFO_LENGTH_MISMATCH) + { + buffer.Initialize(numBytes: returnLength); + byte* pointer = null; + buffer.AcquirePointer(ref pointer); + status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + pointer, + (uint)buffer.ByteLength, + ref returnLength); + buffer.ReleasePointer(); + } + if (status.IsSuccessful) + { + basicInfo = buffer.Read(0); + buffer.Dispose(); + } + else + { + throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB.", new NTStatusException(status)); + } + + // copy PEB + if (!ReadProcessMemory(hProcess, (void*)basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process.", new NTStatusException(status)); + + // Copy RTL_USER_PROCESS_PARAMETERS. + if (!ReadProcessMemory(hProcess, (void*)peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process.", new NTStatusException(status)); + + using UNICODE_STRING cmdLine = new() + { + MaximumLength = parameters.CommandLine.MaximumLength, + Length = parameters.CommandLine.Length, + Buffer = (char*)Marshal.AllocHGlobal(Marshal.SizeOf() * 260) + }; + + if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process.", new NTStatusException(status)); + + return cmdLine.ToStringLength(); + } + else /** this process and that process are the same bit architecture */ + { + using SafeBuffer buffer = new(true); + PROCESS_BASIC_INFORMATION basicInfo = default; + PEB peb = default; + RTL_USER_PROCESS_PARAMETERS parameters = default; + + // Get the PEB address. + buffer.Initialize(numElements: 1); + status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + &basicInfo, + (uint)buffer.ByteLength, + ref returnLength); + while (status == Code.STATUS_INFO_LENGTH_MISMATCH) + { + buffer.Initialize(numBytes: returnLength); + byte* pointer = null; + buffer.AcquirePointer(ref pointer); + status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + pointer, + (uint)buffer.ByteLength, + ref returnLength); + buffer.ReleasePointer(); + } + if (status.IsSuccessful) + { + basicInfo = buffer.Read(0); + buffer.Dispose(); + } + else + { + throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB.", new NTStatusException(status)); + } + + // copy PEB + if (!ReadProcessMemory(hProcess, basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process.", new NTStatusException(status)); + + // Copy RTL_USER_PROCESS_PARAMETERS. + if (!ReadProcessMemory(hProcess, peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process.", new NTStatusException(status)); + + using UNICODE_STRING cmdLine = new() + { + MaximumLength = parameters.CommandLine.MaximumLength, + Length = parameters.CommandLine.Length, + Buffer = (char*)Marshal.AllocHGlobal(Marshal.SizeOf() * 260) + }; + + if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process.", new NTStatusException(status)); + + return cmdLine.ToStringLength(); } - } - else - { - throw new Exception("NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION'"); } } From 4b9135ccd1df2ef3ebc6570d86e6cbc665a67b85 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:23:34 -0700 Subject: [PATCH 100/306] refactor: improve GetFullProcessImageName refactor: use dotnet functionality to acquire process names --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 55 ++++++++++++---------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 629580a..1ab99cc 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -52,25 +52,34 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE // PROCESS_QUERY_LIMITED_INFORMATION + PROCESS_VM_READ for reading PEB from the process's memory space. // if we need to duplicate a handle later, we'll use PROCESS_DUP_HANDLE - if (ProcessId == 4) + if (ProcessId == 0) + { + ProcessName = "System Idle Process"; + } + else if (ProcessId == 4) { ProcessName = "System"; } else { - HANDLE rawHandle = OpenProcess( - dwDesiredAccess: PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, - bInheritHandle: (BOOL)false, - dwProcessId: ProcessId - ); - - if (rawHandle.IsNull) - throw new Win32Exception("Failed to open process handle with access rights 'PROCESS_QUERY_LIMITED_INFORMATION' and 'PROCESS_VM_READ'. The following information will be unavailable: main module full name, process name, process' startup command line"); - - using SafeProcessHandle hProcess = new(rawHandle, true); + try + { + ProcessName = Process.GetProcessById((int)ProcessId).ProcessName; + } + catch (Exception e) + { + ExceptionLog.Add(e); + } /** Get main module's full path */ - ProcessMainModulePath = GetFullProcessImageName(hProcess); + try + { + ProcessMainModulePath = GetFullProcessImageName(ProcessId); + } + catch (Exception e) + { + ExceptionLog.Add(e); + } /** Get Process's name */ if (!string.IsNullOrWhiteSpace(ProcessMainModulePath)) @@ -165,27 +174,23 @@ public void CloseSourceHandle() /// /// A wrapper for QueryFullProcessImageName, a system function that circumvents 32-bit process limitations when permitted the PROCESS_QUERY_LIMITED_INFORMATION right. /// - /// A SafeProcessHandle opened with + /// The ID of the process to open. The resulting SafeProcessHandle is opened with /// The path to the executable image. /// The process handle is invalid /// QueryFullProcessImageName failed. See Exception message for details. - private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) + private unsafe static string? GetFullProcessImageName(uint processId) { - if (hProcess.IsInvalid) - throw new ArgumentException("The process handle is invalid", nameof(hProcess)); - uint size = 260 + 1; uint bufferLength = size; - string retVal = ""; + + using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, processId); + if (hProcess.IsInvalid) + throw new UnauthorizedAccessException("Cannot query process's filename.", new Win32Exception()); using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); - if (QueryFullProcessImageName( - hProcess: hProcess, - dwFlags: PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - lpExeName: buffer, - lpdwSize: ref size)) + if (QueryFullProcessImageName(hProcess, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, lpExeName: buffer, ref size)) { - retVal = buffer.ToString(); + return buffer.ToString(); } else if (bufferLength < size) { @@ -196,7 +201,7 @@ private unsafe static string GetFullProcessImageName(SafeProcessHandle hProcess) newBuffer, ref size)) { - retVal = newBuffer.ToString(); + return newBuffer.ToString(); } else { From ab430427c2fc0346e1e2faa16cc15a192bbbf1ed Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:24:33 -0700 Subject: [PATCH 101/306] refactor: assign native handle value to SafeHandle handle field --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 1ab99cc..e7876c4 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -34,6 +34,7 @@ public SafeHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(false) { SysHandleEx = sysHandleEx; + handle = sysHandleEx.HandleValue; try { From 79f2abe5b1f867b047945ddd61d70bd63910d467 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:26:53 -0700 Subject: [PATCH 102/306] refactor: check if handle is File handle in SafeFileHandleEx ctor refactor: ignore some inaccessible system processes refactor: improve exception handling --- .../Domain/SafeFileHandleEx.cs | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 835e75e..38744a2 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -36,27 +36,41 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { try { - if ((bool)(IsFileHandle = SysHandleEx.IsFileHandle())) + IsFileHandle = SysHandleEx.IsFileHandle(); + } + catch (Exception e) + { + ExceptionLog.Add(e); + } + + if (IsFileHandle == true) + { + try { - try + if (ProcessId == 4) { - FileFullPath = TryGetFinalPath(); - FileName = Path.GetFileName(FileFullPath); - IsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; + ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by {ProcessName} (PID {ProcessId})")); + return; } - catch (Exception e) + + if (ProcessName == "smss") { - ExceptionLog.Add(e); + ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by Windows Session Manager SubSystem ({ProcessName}, PID {ProcessId})")); + return; } + + FileFullPath = TryGetFinalPath(); + FileName = Path.GetFileName(FileFullPath); + IsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; } - else + catch (Exception e) { - ExceptionLog.Add(new InvalidCastException("Cannot cast non-file handle to file handle!")); + ExceptionLog.Add(e); } } - catch (Exception ex) + else { - ExceptionLog.Add(ex); + ExceptionLog.Add(new InvalidCastException("Cannot cast non-file handle to file handle!")); } } From 33453cc59e305d8cfb4d4d45c4c639a7dd5a2b16 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:31:40 -0700 Subject: [PATCH 103/306] refactor: improve TryGetFinalPath * System process is ignored. Throw InvalidOperationException. * Open ProcessHandle for target process * Throw exception if OpenProcess_SafeHandle fails * Duplicate handle. Pass duplicate to GetFinalPathNameByHandle. * Throw exception if DuplicateHandle fails. * Retry when GetFinalPathNameByHandle buffer is too small. * Throw different exceptions based on native error code when GetFinalPathNameByHandle still fails. --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 38744a2..ff7c32a 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -88,11 +88,20 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( /// Failed to query path from file handle. Invalid flags were specified for dwFlags. private unsafe string TryGetFinalPath() { + if (ProcessId == 4) throw new InvalidOperationException("Cannot access handle object information if handle is held by System (PID 4)"); + /// Return the normalized drive name. This is the default. uint bufLength = (uint)short.MaxValue; var buffer = Marshal.AllocHGlobal((int)bufLength); PWSTR fullName = new((char*)buffer); - uint length = GetFinalPathNameByHandle(this, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + var processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); + if (processHandle is null || processHandle?.IsInvalid == true) + throw new Win32Exception(); + + if (!DuplicateHandle(processHandle, new SafeFileHandle((nint)HandleValue, false), Process.GetCurrentProcess().SafeHandle, out SafeFileHandle? dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) + throw new Win32Exception(); + + uint length = GetFinalPathNameByHandle(dupHandle, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); if (length != 0) { @@ -102,14 +111,14 @@ private unsafe string TryGetFinalPath() buffer = Marshal.ReAllocHGlobal(buffer, (IntPtr)length); fullName = new((char*)buffer); - bufLength = GetFinalPathNameByHandle(ToSafeFileHandle(), fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + bufLength = GetFinalPathNameByHandle(dupHandle, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); } return fullName.ToString(); } else { Win32ErrorCode error = (Win32ErrorCode)Marshal.GetLastWin32Error(); - string errMsg = error.GetMessage(); + Debug.Print(error.GetMessage()); /* Hold up. Let's free our memory before throwing exceptions. */ Marshal.FreeHGlobal(buffer); From 83c4bb19331f50a8dab4289f8c24bc7354d328ff Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 00:06:08 -0700 Subject: [PATCH 104/306] refactor: replace C-style (U)LARGE_INTEGER structs with c# types --- deadlock-dotnet-sdk/NativeMethods.txt | 5 ----- deadlock-dotnet-sdk/Windows.Win32/LARGE_INTEGER.cs | 12 ------------ deadlock-dotnet-sdk/Windows.Win32/ULARGE_INTEGER.cs | 12 ------------ 3 files changed, 29 deletions(-) delete mode 100644 deadlock-dotnet-sdk/Windows.Win32/LARGE_INTEGER.cs delete mode 100644 deadlock-dotnet-sdk/Windows.Win32/ULARGE_INTEGER.cs diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index bbefbea..803b14d 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -67,9 +67,4 @@ PUBLIC_OBJECT_TYPE_INFORMATION GENERIC_MAPPING LDR_DATA_TABLE_ENTRY -PEB32 -PEB64 -ProcessCommandLineInformation -NtWow64ReadVirtualMemory64 -LARGE_INTEGER RTL_CRITICAL_SECTION diff --git a/deadlock-dotnet-sdk/Windows.Win32/LARGE_INTEGER.cs b/deadlock-dotnet-sdk/Windows.Win32/LARGE_INTEGER.cs deleted file mode 100644 index 4f445f6..0000000 --- a/deadlock-dotnet-sdk/Windows.Win32/LARGE_INTEGER.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Windows.Win32; - -[StructLayout(LayoutKind.Explicit)] -internal struct LARGE_INTEGER -{ - [FieldOffset(0x00)] internal uint LowPart; - [FieldOffset(0x04)] internal int HighPart; - [FieldOffset(0x00)] internal long QuadPart; - -} diff --git a/deadlock-dotnet-sdk/Windows.Win32/ULARGE_INTEGER.cs b/deadlock-dotnet-sdk/Windows.Win32/ULARGE_INTEGER.cs deleted file mode 100644 index f01d38b..0000000 --- a/deadlock-dotnet-sdk/Windows.Win32/ULARGE_INTEGER.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Windows.Win32; - -[StructLayout(LayoutKind.Explicit)] -internal struct ULARGE_INTEGER -{ - [FieldOffset(0x00)] internal uint LowPart; - [FieldOffset(0x04)] internal uint HighPart; - [FieldOffset(0x00)] internal ulong QuadPart; - -} From 06ac4f6187167c192d7d1d6add377b657349a299 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 03:17:46 -0700 Subject: [PATCH 105/306] reactor: add internal method IsDebugModeEnabled Checks the current security token for SeDebugPrivilege. If it is not present, the user can call Process.EnterDebugMode(). --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 5 +- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 31 ++++++++--- deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs | 57 +++++++++++++++++++- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index a0426f2..edfae98 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace deadlock_dotnet_sdk.Domain { @@ -57,7 +57,8 @@ public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions, o try { - System.Diagnostics.Process.EnterDebugMode(); + if (!Windows.Win32.PInvoke.IsDebugModeEnabled()) + System.Diagnostics.Process.EnterDebugMode(); } catch (Exception e) { diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index e7876c4..ee146c2 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -217,16 +217,35 @@ public void CloseSourceHandle() } } - private unsafe static string GetProcessCommandLine(uint processId) + private static (string? v, Exception? ex) GetProcessCommandLine(uint processId) { + Exception exceptionData = null; + if (processId == (uint)Environment.ProcessId) - return Environment.CommandLine; + return (Environment.CommandLine, null); + + try + { + if (!IsDebugModeEnabled()) + Process.EnterDebugMode(); + } + catch (Exception ex) + { + exceptionData = ex; // What to do with this exception? + } - Process.EnterDebugMode(); - using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, processId); + using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, processId); if (hProcess.IsInvalid) - throw new Win32Exception(); - else return GetProcessCommandLine(hProcess); + return (null, new Win32Exception()); + + try + { + return GetProcessCommandLine(hProcess); + } + catch (Exception ex) + { + return (null, ex); + } } /// diff --git a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs index fe1095d..bf429c5 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs @@ -1,19 +1,59 @@ /// This file supplements code generated by CsWin32 using System.ComponentModel; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Win32.SafeHandles; using Windows.Win32.Foundation; +using Windows.Win32.Security; using Windows.Win32.System.Threading; using MemInfo32 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION32; using MemInfo64 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION64; - using NTSTATUS_plus = PInvoke.NTSTATUS; namespace Windows.Win32; static partial class PInvoke { + /// + /// Check if the current process has been granted Debugger privileges (usually via Process.EnterDebugMode()) + /// + /// + /// LookupPrivilegeValue(), + public static bool IsDebugModeEnabled() + { + if (!LookupPrivilegeValue(null, SE_DEBUG_NAME, out LUID seDebugPrivilege)) + throw new Win32Exception(); + + PRIVILEGE_SET privileges = new() + { + Control = PRIVILEGE_SET_ALL_NECESSARY, + Privilege = new() + { + _0 = new Span(new LUID_AND_ATTRIBUTES[] + { + new() + { + Attributes = TOKEN_PRIVILEGES_ATTRIBUTES.SE_PRIVILEGE_ENABLED, + Luid = seDebugPrivilege, + } + })[0] + }, + PrivilegeCount = 1U + }; + try + { + using SafeFileHandle hProcess = new(Process.GetCurrentProcess().SafeHandle.DangerousGetHandle(), false); + using SafeFileHandle tProcess = OpenProcessToken(hProcess, TOKEN_ACCESS_MASK.TOKEN_QUERY); + // only already-enabled privileges are checked. Those that are present (the token has access to them), but disabled are excluded from the result. + return PrivilegeCheck(tProcess, ref privileges); + } + catch (Exception) + { + throw; + } + } + /// Creates a handle that is a duplicate of the specified source handle. /// A handle to the source process for the handle being duplicated. /// The handle to duplicate. @@ -51,6 +91,8 @@ uint Options /// If successful and the current process is 32-bit, returns a MEMORY_BASIC_INFORMATION32 structure. If the current process is 64-bit, returns a MEMORY_BASIC_INFORMATION64 structure. /// + /// This is currently unused because we don't need this much information about a handle's owning process. However, it would be a shame to remove it entirely. + // TODO: split off into 'C#: Reading 32-bit process's memory from 64-bit and vice versa' project internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(nuint lpAddress) { SIZE_T bufferSize = default; @@ -153,4 +195,17 @@ private unsafe static extern SIZE_T VirtualQuery( void* lpBuffer, SIZE_T dwLength ); + + /// Whether any or all of the specified privileges are enabled in the access token. If the Control member of the PRIVILEGE_SET structure specifies PRIVILEGE_SET_ALL_NECESSARY, this value is TRUE only if all the privileges are enabled; otherwise, this value is TRUE if any of the privileges are enabled. + /// + ///
+ /// An access token contains a list of the privileges held by the account associated with the token. These privileges can be enabled or disabled; most are disabled by default. The PrivilegeCheck function checks only for enabled privileges. To get a list of all the enabled and disabled privileges held by an access token, call the GetTokenInformation function. To enable or disable a set of privileges in an access token, call the AdjustTokenPrivileges function.
+ /// + internal static bool PrivilegeCheck(SafeHandle ClientToken, ref PRIVILEGE_SET RequiredPrivileges) + => !PrivilegeCheck(ClientToken, ref RequiredPrivileges, out int pfResult) ? throw new Win32Exception() : (BOOL)pfResult; + + /// + /// + internal static SafeFileHandle OpenProcessToken(SafeFileHandle ProcessHandle, TOKEN_ACCESS_MASK DesiredAccess) + => OpenProcessToken(ProcessHandle, DesiredAccess, out SafeFileHandle TokenHandle) ? TokenHandle : throw new Win32Exception(); } From f13cff98c82c04818a63c78cdb127da979b3ddec Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 03:41:16 -0700 Subject: [PATCH 106/306] build: set EmitCompilerGeneratedFiles to true This should help with debugging in some editors. --- deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj index b2199d2..d642f7a 100644 --- a/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj +++ b/deadlock-dotnet-sdk/deadlock-dotnet-sdk.csproj @@ -21,6 +21,7 @@ 1.0.1.0 1.0.1 true + true From e11386b9d0bf94b9d300763a4918da23deef019d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 03:42:58 -0700 Subject: [PATCH 107/306] refactor: update NativeMethods.txt symbols --- deadlock-dotnet-sdk/NativeMethods.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index 803b14d..634e5b3 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -2,7 +2,7 @@ // for it to be generated somewhere under the namespace "Windows.Win32" // // # Where can I find X? -// Most of what you'll see in the WDK is not in the Win32 API. +// Most of what you'll see in the WDK (Windows Driver Kit) is not in the Win32 API. // As such, CsWin32 will not generate code for Kernel API // except for some Kernel32 definitions. // @@ -19,6 +19,7 @@ BOOLEAN HANDLE DUPLICATE_HANDLE_OPTIONS +HANDLE_FLAGS //// Restart Manager //// RM_PROCESS_INFO @@ -27,8 +28,11 @@ RM_PROCESS_INFO CheckTokenMembership CloseHandle DuplicateHandle +GetFileType GetFinalPathNameByHandle +GetHandleInformation IsWow64Process +LookupPrivilegeValue NtQueryInformationProcess NtQueryObject NtQuerySystemInformation @@ -36,6 +40,8 @@ OpenProcess OpenProcessToken OpenThread OpenThreadToken +PrivilegeCheck +PRIVILEGE_SET_ALL_NECESSARY QueryFullProcessImageName ReadProcessMemory RmEndSession @@ -68,3 +74,8 @@ PUBLIC_OBJECT_TYPE_INFORMATION GENERIC_MAPPING LDR_DATA_TABLE_ENTRY RTL_CRITICAL_SECTION + +//SE_DEBUG_PRIVILEGE // PInvoke001 +SE_DEBUG_NAME +//ProcessProtectionInformation // PInvoke001: Method, type or constant "ProcessProtectionInformation" not found. +//PS_PROTECTION // PInvoke001: Method, type or constant "PS_PROTECTION" not found. From ee2940185d7f47a4b1b433083fe08825a0200f37 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 03:43:28 -0700 Subject: [PATCH 108/306] style: remove trailing whitespace --- deadlock-dotnet-sdk/DeadLock.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index 2be0308..bb54bf4 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -116,7 +116,7 @@ public void Unlock(FileLocker fileLocker) } /// - /// Unlock one or more files by killing all the processes that are holding a handle on the files + /// Unlock one or more files by killing all the processes that are holding a handle on the files /// /// The FileLocker objects that contain the List of Process objects that are locking a file public void Unlock(params FileLocker[] fileLockers) @@ -145,7 +145,7 @@ await Task.Run(() => } /// - /// Unlock one or more files asynchronously by killing all the processes that are holding a handle on the files + /// Unlock one or more files asynchronously by killing all the processes that are holding a handle on the files /// /// The FileLocker objects that contain the List of Process objects that are locking a file public async Task UnlockAsync(params FileLocker[] fileLockers) @@ -237,7 +237,7 @@ public FileLockerEx FindLockingHandles(string filePath, HandlesFilter filter, ou ///
/// By default, only handles whose object's Type is confirmed to "File" are returned. Optionally, handles for data pipes, printers, and other Types can be included, in addition to handles whose object Type could not be identified for some reason. /// The List of objects that contains a List of handles that are locking one or multiple files and/or directories - public List FindLockingHandles(HandlesFilter filter, List warnings, params string[] filePaths) + public List FindLockingHandles(HandlesFilter filter, ref List warnings, params string[] filePaths) { List fileLockers = new(); warnings = new(); From 23a207f57e30325d56567f7c83b5e63a999f1ccf Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 03:44:54 -0700 Subject: [PATCH 109/306] refactor: clarify exception; make Path readonly --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index edfae98..fd94fc9 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -2,6 +2,8 @@ namespace deadlock_dotnet_sdk.Domain { + //TODO: Add RefreshList(): Check if a handle is closed. If true, remove if from Lockers. SafeHandle.IsClosed may be unreliable depending on the runtime's internal logic. It might only work as intended if the handle is managed by the current runtime. + //https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs public class FileLockerEx { #region Properties @@ -9,12 +11,10 @@ public class FileLockerEx /// /// Get or set the path of the file that is locked /// - public string Path { get; set; } + public string Path { get; } public HandlesFilter Filter { get; } - /// - /// Get or set the List of handles that are locking the file - /// + /// Get or set the List of handles that are locking the file public List Lockers { get; set; } #endregion Properties @@ -62,7 +62,7 @@ public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions, o } catch (Exception e) { - var uae = new UnauthorizedAccessException("DeadLock was denied debug permissions to access system, service, and admin processes. For debug access, try running this app as Administrator or contact your technician.", e); + var uae = new UnauthorizedAccessException("DeadLock failed to check if it already has Debug permission -OR- was denied debug permissions to access system, service, and admin processes. Some functionality won't work. For debug access, try running this app as Administrator.", e); if (rethrowExceptions) throw uae; else From 63dc71870475acfbb5c9bac31e8b8e1e7725b30d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 04:10:28 -0700 Subject: [PATCH 110/306] refactor: move SafeBufferT; add instance methods; utilize it --- deadlock-dotnet-sdk/Domain/SafeBufferT.cs | 16 ------ deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 50 ++++++++++++------- .../Windows.Win32/SafeBufferT.cs | 46 +++++++++++++++++ 3 files changed, 79 insertions(+), 33 deletions(-) delete mode 100644 deadlock-dotnet-sdk/Domain/SafeBufferT.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/SafeBufferT.cs diff --git a/deadlock-dotnet-sdk/Domain/SafeBufferT.cs b/deadlock-dotnet-sdk/Domain/SafeBufferT.cs deleted file mode 100644 index cb904c3..0000000 --- a/deadlock-dotnet-sdk/Domain/SafeBufferT.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Runtime.InteropServices; - -namespace deadlock_dotnet_sdk.Domain; - -public class SafeBuffer : SafeBuffer -{ - public SafeBuffer(bool ownsHandle) : base(ownsHandle) - { - } - - protected override bool ReleaseHandle() - { - Marshal.FreeHGlobal(handle); - return true; - } -} diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index ee146c2..61241b2 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -273,34 +273,51 @@ private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) { const uint ProcessCommandLineInformation = 60u; - uint bufferLength = (uint)Marshal.SizeOf() + 260u; - UNICODE_STRING* pString = (UNICODE_STRING*)Marshal.AllocHGlobal((int)bufferLength); + uint bufferLength = (uint)Marshal.SizeOf() + 2048u; + using SafeBuffer safeBuffer = new(numBytes: bufferLength); status = NtQueryInformationProcess( hProcess, (PROCESSINFOCLASS)ProcessCommandLineInformation, - pString, + (void*)safeBuffer.DangerousGetHandle(), bufferLength, ref returnLength ); if (status == Code.STATUS_INFO_LENGTH_MISMATCH) { +#if DEBUG + Console.Out.WriteLine( + $"bufferLength: {bufferLength}\n" + + $"returnLength: {returnLength}"); bufferLength = returnLength; - pString->MaximumLength = (ushort)returnLength; - pString->Buffer = new((char*)Marshal.ReAllocHGlobal((IntPtr)pString->Buffer.Value, (IntPtr)bufferLength)); +#endif + try + { + // the native call to LocalReAlloc (via Marshal.ReAllocHGlobal) sometimes returns a null pointer. This is a Legacy function. Why does .NET not use malloc/realloc? + //pString->Buffer = new((char*)Marshal.ReAllocHGlobal((IntPtr)pString->Buffer.Value, (IntPtr)bufferLength)); + safeBuffer.Reallocate(numBytes: returnLength); - status = NtQueryInformationProcess( - hProcess, - (PROCESSINFOCLASS)ProcessCommandLineInformation, - pString, - bufferLength, - ref returnLength - ); + status = NtQueryInformationProcess( + hProcess, + (PROCESSINFOCLASS)ProcessCommandLineInformation, + (void*)safeBuffer.DangerousGetHandle(), + bufferLength, + ref returnLength + ); + } + catch (OutOfMemoryException) // ReAllocHGlobal received a null pointer, but didn't check the error code + { + // none of these were of interest... + //var pinerr = Marshal.GetLastPInvokeError(); + //var syserr = Marshal.GetLastSystemError(); + //var winerr = Marshal.GetLastWin32Error(); + throw; + } } if (status.IsSuccessful) - return pString->ToStringLength(); + return safeBuffer.Read(0).ToStringZ() ?? string.Empty; else throw new NTStatusException(status); } @@ -312,7 +329,7 @@ All comments inside the code blocks are from either source. */ if (weAre32BitAndTheyAre64Bit) /** This process is 32-bit, that process is 64-bit */ { - using SafeBuffer buffer = new(true); + using SafeBuffer buffer = new(numBytes: 0); PROCESS_BASIC_INFORMATION64 basicInfo = default; PEB64 peb = default; RTL_USER_PROCESS_PARAMETERS64 parameters = default; @@ -362,7 +379,7 @@ All comments inside the code blocks are from either source. } else if (weAre64BitAndTheyAre32Bit) /** This is 64-bit, that is 32-bit */ { - using SafeBuffer buffer = new(true); + using SafeBuffer buffer = new(numElements: 1); PROCESS_BASIC_INFORMATION32 basicInfo = default; PEB32 peb = default; RTL_USER_PROCESS_PARAMETERS32 parameters = default; @@ -420,13 +437,12 @@ All comments inside the code blocks are from either source. } else /** this process and that process are the same bit architecture */ { - using SafeBuffer buffer = new(true); + using SafeBuffer buffer = new(numElements: 1); PROCESS_BASIC_INFORMATION basicInfo = default; PEB peb = default; RTL_USER_PROCESS_PARAMETERS parameters = default; // Get the PEB address. - buffer.Initialize(numElements: 1); status = NtQueryInformationProcess( hProcess, PROCESSINFOCLASS.ProcessBasicInformation, diff --git a/deadlock-dotnet-sdk/Windows.Win32/SafeBufferT.cs b/deadlock-dotnet-sdk/Windows.Win32/SafeBufferT.cs new file mode 100644 index 0000000..2eef1b2 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/SafeBufferT.cs @@ -0,0 +1,46 @@ +using System.Runtime.InteropServices; + +namespace Windows.Win32; +/// +/// https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeBuffer.cs +/// +/// +public class SafeBuffer : SafeBuffer where T : unmanaged +{ + public SafeBuffer(nuint numBytes) : base(true) + { + Initialize(numBytes); + handle = Marshal.AllocHGlobal((nint)numBytes); + } + + public SafeBuffer(uint numElements) : base(true) + { + Initialize(numElements); + handle = Marshal.AllocHGlobal((nint)ByteLength); + } + + /// + /// heck + /// + /// + /// There is insufficient memory to satisfy the request -or- the call to native function LocalReAlloc failed. + /// numBytes is less than zero. -or- numBytes is greater than the available address space. + public unsafe void Reallocate(nuint numBytes) + { + try + { + handle = Marshal.ReAllocHGlobal(handle, (nint)numBytes); + Initialize(numBytes); + } + catch (OutOfMemoryException) + { throw; } + catch (ArgumentOutOfRangeException) + { throw; } + } + + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(handle); + return true; + } +} From 75dc135658d76ec49323ebe55d7b3487128ec5b0 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 26 Mar 2023 04:04:08 -0700 Subject: [PATCH 111/306] docs: update inline documentation --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 5 ++--- .../Windows.Win32/System/Kernel/LIST_ENTRY64.cs | 2 +- .../System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs | 7 ++----- .../System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs | 1 + .../System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs | 1 - 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 61241b2..fd32cf4 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -248,13 +248,12 @@ private static (string? v, Exception? ex) GetProcessCommandLine(uint processId) } } - /// - /// Try to get a process's command line from its PEB - /// + /// Try to get a process's command line from its PEB /// A handle to the target process with the rights PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ /// Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented. /// Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible. /// NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION' + /// ReAllocHGlobal received a null pointer, but didn't check the error code. This is not a real OutOfMemoryException private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) { if (hProcess.IsInvalid) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs index 88b7f78..08d497d 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs @@ -7,7 +7,7 @@ internal struct LIST_ENTRY64 where T : unmanaged /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. /// Read more on docs.microsoft.com. ///
- internal unsafe UIntPtr64> Flink; + /// Points to a LIST_ENTRY64 /// /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. /// Read more on docs.microsoft.com. diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs index 4b0f6ba..05cbd8a 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs @@ -1,4 +1,3 @@ -/// This file supplements code generated by CsWin32 namespace Windows.Win32.System.WindowsProgramming; /// @@ -11,11 +10,9 @@ enum OBJECT_INFORMATION_CLASS /// A PUBLIC_OBJECT_BASIC_INFORMATION structure is supplied. /// ObjectBasicInformation = 0, - /// Microsoft documents `1` as ObjectTypeInformation instead of ObjectNameInformation + /// Microsoft documents `1` as ObjectTypeInformation instead of ObjectNameInformation, despite it being `2` in winternl.h for Windows 10.0.22000.0 ObjectNameInformation = 1, - /// - /// A PUBLIC_OBJECT_TYPE_INFORMATION structure is supplied. - /// + /// A PUBLIC_OBJECT_TYPE_INFORMATION structure is supplied. ObjectTypeInformation = 2, /// 3.50 and higher ObjectTypesInformation = 3, diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs index fff7fe0..9c17728 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs @@ -34,6 +34,7 @@ internal struct OBJECT_TYPE_INFORMATION public ObjectTypeInformation ToManaged() => new(this); } +/// A copy of OBJECT_TYPE_INFORMATION using a managed string instead of UNICODE_STRING public struct ObjectTypeInformation { internal ObjectTypeInformation(OBJECT_TYPE_INFORMATION oti) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs index a5ade7a..ac959af 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs @@ -1,4 +1,3 @@ -/// This file supplements code generated by CsWin32 namespace Windows.Win32.System.WindowsProgramming; // generated definition lacks SystemHandleInformation, SystemExtendedHandleInformation From f652e54d601ebe7fc8092a13b2edb1def5d5ca2a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:54:51 -0700 Subject: [PATCH 112/306] style: remove trailing whitespace --- deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs index 8d933bf..e49dbb9 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs @@ -24,5 +24,4 @@ internal struct UIntPtr32 where T : unmanaged public static explicit operator UIntPtr32(UIntPtr32 v) => v.Value; public unsafe static explicit operator T*(UIntPtr32 v) => (T*)v.Value; - } From 19469b6f8f199252b22ae41b083976abc6cc6127 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:55:28 -0700 Subject: [PATCH 113/306] refactor: move UIntPtr64 to new file --- deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs | 10 ---------- deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs | 13 +++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs index 5df9a75..96bfbfe 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs @@ -10,13 +10,3 @@ internal struct UIntPtr64 public static implicit operator UIntPtr64(ulong v) => new() { Value = v }; public static implicit operator ulong(UIntPtr64 v) => v.Value; } - -internal struct UIntPtr64 where T : unmanaged -{ - public ulong Value; - - public static implicit operator UIntPtr64(ulong v) => new() { Value = v }; - public static implicit operator ulong(UIntPtr64 v) => v.Value; - - public static explicit operator UIntPtr64(UIntPtr64 v) => v.Value; -} diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs new file mode 100644 index 0000000..3361caa --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs @@ -0,0 +1,13 @@ +namespace Windows.Win32; + +/// +internal struct UIntPtr64 where T : unmanaged +{ + public ulong Value; + + public static implicit operator UIntPtr64(ulong v) => new() { Value = v }; + public static implicit operator ulong(UIntPtr64 v) => v.Value; + + public static explicit operator UIntPtr64(UIntPtr64 v) => v.Value; + public static explicit operator UIntPtr64(UIntPtr64 v) => new() { Value = v.Value }; +} From 8dd3f01bef0d6456641175dfb774a301cff2f2e8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:56:25 -0700 Subject: [PATCH 114/306] refactor: add struct PS_PROTECTION --- deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs diff --git a/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs new file mode 100644 index 0000000..e5aa0ca --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs @@ -0,0 +1,11 @@ +namespace Windows.Win32; +/// +/// https://learn.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess#PS_PROTECTION +/// +internal struct PS_PROTECTION +{ + public byte Level; + public byte Type => (byte)((Level >> 0) & 0b111); + public byte Audit => (byte)((Level >> 3) & 0b1); // Reserved + public byte Signer => (byte)((Level >> 4) & 0b1111); +} From 8605bce0f1b902f6cc4f82e75e1078933244f239 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:57:21 -0700 Subject: [PATCH 115/306] refactor: display NTSTATUS Code enum value in debugger --- deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs index 11dc9be..187eeed 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using PInvoke; using Code = PInvoke.NTSTATUS.Code; using FacilityCode = PInvoke.NTSTATUS.FacilityCode; @@ -6,6 +7,7 @@ /// namespace Windows.Win32.Foundation; +[DebuggerDisplay("{Code}")] readonly partial struct NTSTATUS { public bool IsSuccessful => SeverityCode is Severity.Success; @@ -18,8 +20,10 @@ public void ThrowOnError() throw new NTStatusException(this); } + public global::PInvoke.NTSTATUS.Code Code => ((global::PInvoke.NTSTATUS)this).Value; + /// - public string GetMessage() => ((global::PInvoke.NTSTATUS)this).GetMessage(); + public string Message => ((global::PInvoke.NTSTATUS)this).GetMessage(); public Code Code => (Code)Value; public FacilityCode FacilityCode => ((global::PInvoke.NTSTATUS)this).Facility; From 911b58e306e89a0f15ab969ba9c9149dd55bb471 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 03:58:20 -0700 Subject: [PATCH 116/306] refactor: remove redundant member 'AtlThunkSListPtr32' --- deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs index f53f1df..5295636 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs @@ -120,14 +120,6 @@ private enum CrossProcessFlags : uint public unsafe IntPtr KernelCallbackTable => (IntPtr)Reserved7; public unsafe IntPtr UserSharedInfoPtr => (IntPtr)Reserved7; public uint SystemReserved => Reserved8; - public unsafe uint AtlThunkSListPtr32 - { - get - { - fixed (void** uint0 = &Reserved9._0) - return *(uint*)uint0; - } - } public unsafe API_SET_NAMESPACE* ApiSetMap { get From 2efab6fcb50096be386774c52ed106f6470c7d4d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:01:07 -0700 Subject: [PATCH 117/306] refactor: finish OBJECT_TYPES_INFORMATION definition --- .../OBJECT_TYPES_INFORMATION.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs index 8361844..769c29e 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs @@ -1,24 +1,25 @@ namespace Windows.Win32.System.WindowsProgramming; -struct OBJECT_TYPES_INFORMATION : IEquatable +struct OBJECT_TYPES_INFORMATION { - public uint NumberOfTypes = 0; - public OBJECT_TYPES_INFORMATION(uint numberOfTypes) { NumberOfTypes = numberOfTypes; } - public static explicit operator uint(OBJECT_TYPES_INFORMATION v) => v.NumberOfTypes; - - public static bool operator ==(OBJECT_TYPES_INFORMATION x, OBJECT_TYPES_INFORMATION y) => x.NumberOfTypes == y.NumberOfTypes; + public uint NumberOfTypes; - public static bool operator !=(OBJECT_TYPES_INFORMATION x, OBJECT_TYPES_INFORMATION y) => x.NumberOfTypes != y.NumberOfTypes; + public unsafe OBJECT_TYPE_INFORMATION TypeInformation_0 = default; + public unsafe OBJECT_TYPE_INFORMATION TypeInformation_1 = default; - public override bool Equals(object? obj) + public unsafe OBJECT_TYPE_INFORMATION[] TypeInformation { - return obj is OBJECT_TYPES_INFORMATION information && - NumberOfTypes == information.NumberOfTypes; + get + { + fixed (OBJECT_TYPE_INFORMATION* p = &TypeInformation_0) + return new ReadOnlySpan(p, (int)NumberOfTypes).ToArray(); + } } - public bool Equals(OBJECT_TYPES_INFORMATION other) => NumberOfTypes == other.NumberOfTypes; + public static explicit operator uint(OBJECT_TYPES_INFORMATION v) => v.NumberOfTypes; + } From edd892073db139027942c616270cc710ad9b35d4 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:02:14 -0700 Subject: [PATCH 118/306] refactor: set LDR_DATA_TABLE_ENTRY members to readonly --- .../LDR_DATA_TABLE_ENTRY32.cs | 88 +++++++++---------- .../LDR_DATA_TABLE_ENTRY64.cs | 86 +++++++++--------- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs index 312b193..d34e1ed 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs @@ -8,118 +8,118 @@ namespace Windows.Win32.System.WindowsProgramming; internal struct LDR_DATA_TABLE_ENTRY32 { /// 3.10 and higher - [FieldOffset(0x00)] internal LIST_ENTRY32 InLoadOrderLinks; + [FieldOffset(0x00)] internal readonly LIST_ENTRY32 InLoadOrderLinks; /// 3.10 and higher - [FieldOffset(0x08)] internal LIST_ENTRY32 InMemoryOrderLinks; + [FieldOffset(0x08)] internal readonly LIST_ENTRY32 InMemoryOrderLinks; /// 3.10 and higher - [FieldOffset(0x10)] internal LIST_ENTRY32 InInitializationOrderLinks; + [FieldOffset(0x10)] internal readonly LIST_ENTRY32 InInitializationOrderLinks; /// 6.2 (Win8) and higher - [FieldOffset(0x10)] internal LIST_ENTRY32 InProgressLinks; + [FieldOffset(0x10)] internal readonly LIST_ENTRY32 InProgressLinks; /// 3.10 and higher - [FieldOffset(0x18)] internal UIntPtr32 DllBase; + [FieldOffset(0x18)] internal readonly UIntPtr32 DllBase; /// 3.10 and higher - [FieldOffset(0x1C)] internal UIntPtr32 Entrypoint; + [FieldOffset(0x1C)] internal readonly UIntPtr32 Entrypoint; /// 3.10 and higher - [FieldOffset(0x20)] internal uint SizeOfImage; + [FieldOffset(0x20)] internal readonly uint SizeOfImage; /// 3.10 and higher - [FieldOffset(0x24)] internal UNICODE_STRING32 FullDllName; + [FieldOffset(0x24)] internal readonly UNICODE_STRING32 FullDllName; /// 3.10 and higher - [FieldOffset(0x2C)] internal UNICODE_STRING32 BaseDllName; + [FieldOffset(0x2C)] internal readonly UNICODE_STRING32 BaseDllName; /// 6.2 (Win8) and higher [FieldOffset(0x34)] internal unsafe fixed byte FlagGroup[4]; /// 3.10 and higher - [FieldOffset(0x34)] internal LdrEntryFlags Flags; + [FieldOffset(0x34)] internal readonly LdrEntryFlags Flags; /// 3.10 to 6.1 (Win7) - [FieldOffset(0x38)] internal ushort LoadCount; + [FieldOffset(0x38)] internal readonly ushort LoadCount; /// 6.2 and higher - [FieldOffset(0x38)] internal ushort ObsoleteLoadCount; + [FieldOffset(0x38)] internal readonly ushort ObsoleteLoadCount; /// all - [FieldOffset(0x3A)] internal ushort TlsIndex; + [FieldOffset(0x3A)] internal readonly ushort TlsIndex; /// 3.10 and higher - [FieldOffset(0x3C)] internal LIST_ENTRY32 HashLinks; + [FieldOffset(0x3C)] internal readonly LIST_ENTRY32 HashLinks; /// 3.10 to 6.1 (Win7) - [FieldOffset(0x3C)] internal UIntPtr32 SectionPointer; + [FieldOffset(0x3C)] internal readonly UIntPtr32 SectionPointer; /// 3.10 to 6.1 (Win7) - [FieldOffset(0x3C)] internal uint CheckSum; + [FieldOffset(0x3C)] internal readonly uint CheckSum; #region Appended for Windows NT 4.0 /// 4.0 and higher - [FieldOffset(0x44)] internal uint TimeDateStamp; + [FieldOffset(0x44)] internal readonly uint TimeDateStamp; /// 4.0 to 6.1 - [FieldOffset(0x44)] internal UIntPtr32 LoadedImports; + [FieldOffset(0x44)] internal readonly UIntPtr32 LoadedImports; #endregion Appended for Windows NT 4.0 #region Appended for Windows XP /// 5.1 and higher - [FieldOffset(0x48)] internal UIntPtr32 EntryPointActivationContext; + [FieldOffset(0x48)] internal readonly UIntPtr32 EntryPointActivationContext; /// 5.1 from Windows XP SP2 to 6.2 (Win8) - [FieldOffset(0x4C)] internal UIntPtr32 PatchInformation; + [FieldOffset(0x4C)] internal readonly UIntPtr32 PatchInformation; /// 6.3 only - [FieldOffset(0x4C)] internal UIntPtr32 Spare; + [FieldOffset(0x4C)] internal readonly UIntPtr32 Spare; /// 10.0 and higher - [FieldOffset(0x4C)] internal UIntPtr32 Lock; + [FieldOffset(0x4C)] internal readonly UIntPtr32 Lock; #endregion Appended for Windows XP #region Appended for Windows Vista /// 6.0 to 6.1 - [FieldOffset(0x50)] internal LIST_ENTRY32 ForwarderLinks; + [FieldOffset(0x50)] internal readonly LIST_ENTRY32 ForwarderLinks; /// 6.0 to 6.1 - [FieldOffset(0x58)] internal LIST_ENTRY32 ServiceTagLinks; + [FieldOffset(0x58)] internal readonly LIST_ENTRY32 ServiceTagLinks; /// 6.0 to 6.1 - [FieldOffset(0x60)] internal LIST_ENTRY32 StaticLinks; + [FieldOffset(0x60)] internal readonly LIST_ENTRY32 StaticLinks; #endregion Appended for Windows Vista #region Redone for Windows 8 /// (LDR_DDAG_NODE*) 6.2 and higher - [FieldOffset(0x50)] UIntPtr32 DdagNode; + [FieldOffset(0x50)] internal readonly UIntPtr32 DdagNode; /// 6.2 and higher - [FieldOffset(0x54)] LIST_ENTRY32 NodeModuleLink; + [FieldOffset(0x54)] internal readonly LIST_ENTRY32 NodeModuleLink; /// (LDRP_DLL_SNAP_CONTEXT*) 6.2 to 6.3 - [FieldOffset(0x5C)] UIntPtr32 SnapContext; + [FieldOffset(0x5C)] internal readonly UIntPtr32 SnapContext; /// (LDRP_LOAD_CONTEXT*) 10.0 and higher - [FieldOffset(0x5C)] UIntPtr32 LoadContext; + [FieldOffset(0x5C)] internal readonly UIntPtr32 LoadContext; /// 6.2 and higher - [FieldOffset(0x60)] UIntPtr32 ParentBaseDll; + [FieldOffset(0x60)] internal readonly UIntPtr32 ParentBaseDll; /// 6.2 and higher - [FieldOffset(0x64)] UIntPtr32 SwitchBackContext; + [FieldOffset(0x64)] internal readonly UIntPtr32 SwitchBackContext; /// 6.2 and higher - [FieldOffset(0x68)] RTL_BALANCED_NODE32 BaseAddressIndexNode; + [FieldOffset(0x68)] internal readonly RTL_BALANCED_NODE32 BaseAddressIndexNode; /// 6.2 and higher - [FieldOffset(0x70)] RTL_BALANCED_NODE32 MappingInfoIndexNode; + [FieldOffset(0x70)] internal readonly RTL_BALANCED_NODE32 MappingInfoIndexNode; #endregion Redone for Windows 8 #region Appended for Windows 7 /// 6.1 only - [FieldOffset(0x68)] UIntPtr32 ContextInformation; + [FieldOffset(0x68)] internal readonly UIntPtr32 ContextInformation; /// 6.1 only - [FieldOffset(0x6C)] UIntPtr32 OriginalBase_NT61; + [FieldOffset(0x6C)] internal readonly UIntPtr32 OriginalBase_NT61; /// 6.2 and higher - [FieldOffset(0x80)] UIntPtr32 OriginalBase_NT62; + [FieldOffset(0x80)] internal readonly UIntPtr32 OriginalBase_NT62; /// 6.1 only - [FieldOffset(0x70)] LARGE_INTEGER LoadTime_NT61; + [FieldOffset(0x70)] internal readonly long LoadTime_NT61; /// 6.2 and higher - [FieldOffset(0x88)] LARGE_INTEGER LoadTime_NT62; + [FieldOffset(0x88)] internal readonly long LoadTime_NT62; #endregion Appended for Windows 7 #region Appended for Windows 8 /// 6.2 and higher - [FieldOffset(0x90)] internal uint BaseNameHashValue; + [FieldOffset(0x90)] internal readonly uint BaseNameHashValue; /// 6.2 and higher - [FieldOffset(0x94)] internal LDR_DLL_LOAD_REASON LoadReason; + [FieldOffset(0x94)] internal readonly LDR_DLL_LOAD_REASON LoadReason; #endregion Appended for Windows 8 #region Appended for Windows 8.1 /// 6.3 and higher - [FieldOffset(0x98)] internal uint ImplicitPathOptions; + [FieldOffset(0x98)] internal readonly uint ImplicitPathOptions; #endregion Appended for Windows 8.1 #region Appended for Windows 10 /// 10.0 and higher - [FieldOffset(0x9C)] internal uint ReferenceCount; + [FieldOffset(0x9C)] internal readonly uint ReferenceCount; /// 1607 and higher - [FieldOffset(0xA0)] internal uint DependentLoadFlags; + [FieldOffset(0xA0)] internal readonly uint DependentLoadFlags; /// 1703 and higher - [FieldOffset(0xA4)] internal byte SigningLevel; + [FieldOffset(0xA4)] internal readonly byte SigningLevel; #endregion Appended for Windows 10 } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs index ca07664..5bddf17 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs @@ -10,117 +10,117 @@ namespace Windows.Win32.System.WindowsProgramming; internal struct LDR_DATA_TABLE_ENTRY64 { /// 3.10 and higher - [FieldOffset(0x00)] internal LIST_ENTRY64 InLoadOrderLinks; + [FieldOffset(0x00)] internal readonly LIST_ENTRY64 InLoadOrderLinks; /// 3.10 and higher - [FieldOffset(0x10)] internal LIST_ENTRY64 InMemoryOrderLinks; + [FieldOffset(0x10)] internal readonly LIST_ENTRY64 InMemoryOrderLinks; /// 3.10 and higher - [FieldOffset(0x20)] internal LIST_ENTRY64 InInitializationOrderLinks; + [FieldOffset(0x20)] internal readonly LIST_ENTRY64 InInitializationOrderLinks; /// 6.2 (Win8) and higher - [FieldOffset(0x20)] internal LIST_ENTRY64 InProgressLinks; + [FieldOffset(0x20)] internal readonly LIST_ENTRY64 InProgressLinks; /// 3.10 and higher - [FieldOffset(0x30)] internal UIntPtr64 DllBase; + [FieldOffset(0x30)] internal readonly UIntPtr64 DllBase; /// 3.10 and higher - [FieldOffset(0x38)] internal UIntPtr64 Entrypoint; + [FieldOffset(0x38)] internal readonly UIntPtr64 Entrypoint; /// 3.10 and higher - [FieldOffset(0x40)] internal uint SizeOfImage; + [FieldOffset(0x40)] internal readonly uint SizeOfImage; /// 3.10 and higher - [FieldOffset(0x48)] internal UNICODE_STRING64 FullDllName; + [FieldOffset(0x48)] internal readonly UNICODE_STRING64 FullDllName; /// 3.10 and higher - [FieldOffset(0x58)] internal UNICODE_STRING64 BaseDllName; + [FieldOffset(0x58)] internal readonly UNICODE_STRING64 BaseDllName; /// 6.2 (Win8) and higher [FieldOffset(0x68)] internal unsafe fixed byte FlagGroup[4]; /// 3.10 and higher - [FieldOffset(0x68)] internal LdrEntryFlags Flags; + [FieldOffset(0x68)] internal readonly LdrEntryFlags Flags; /// 3.10 to 6.1 (Win7). Obsolete on 6.2 (Win8) and higher. - [FieldOffset(0x6C)] internal ushort LoadCount; + [FieldOffset(0x6C)] internal readonly ushort LoadCount; /// 6.2 and higher - [FieldOffset(0x6C)] internal ushort ObsoleteLoadCount; + [FieldOffset(0x6C)] internal readonly ushort ObsoleteLoadCount; /// all - [FieldOffset(0x6E)] internal ushort TlsIndex; + [FieldOffset(0x6E)] internal readonly ushort TlsIndex; /// 3.10 and higher - [FieldOffset(0x70)] internal LIST_ENTRY64 HashLinks; + [FieldOffset(0x70)] internal readonly LIST_ENTRY64 HashLinks; /// 3.10 to 6.1 (Win7). Obsolete on 6.2 (Win8) and higher. - [FieldOffset(0x70)] internal UIntPtr64 SectionPointer; + [FieldOffset(0x70)] internal readonly UIntPtr64 SectionPointer; /// 3.10 to 6.1 (Win7). Obsolete on 6.2 (Win8) and higher. - [FieldOffset(0x70)] internal uint CheckSum; // 3.10 to 6.1 (Win7) + [FieldOffset(0x70)] internal readonly uint CheckSum; // 3.10 to 6.1 (Win7) #region Appended for Windows NT 4.0 /// 4.0 and higher - [FieldOffset(0x80)] internal uint TimeDateStamp; + [FieldOffset(0x80)] internal readonly uint TimeDateStamp; /// 4.0 to 6.1. Obsolete on 6.2 (Win8) and higher. - [FieldOffset(0x80)] internal UIntPtr64 LoadedImports; + [FieldOffset(0x80)] internal readonly UIntPtr64 LoadedImports; #endregion Appended for Windows NT 4.0 #region Appended for Windows XP /// 5.1 and higher [FieldOffset(0x88)] internal UIntPtr64 EntryPointActivationContext; /// 5.1 from Windows XP SP2 to 6.2 (Win8). Obsolete on 6.3 and higher. - [FieldOffset(0x90)] internal UIntPtr64 PatchInformation; + [FieldOffset(0x90)] internal readonly UIntPtr64 PatchInformation; /// 6.3 only - [FieldOffset(0x90)] internal UIntPtr64 Spare; + [FieldOffset(0x90)] internal readonly UIntPtr64 Spare; /// 10.0 and higher - [FieldOffset(0x90)] internal UIntPtr64 Lock; + [FieldOffset(0x90)] internal readonly UIntPtr64 Lock; #endregion Appended for Windows XP #region Appended for Windows Vista /// 6.0 to 6.1 (Win7) - [FieldOffset(0x98)] internal LIST_ENTRY64 ForwarderLinks; + [FieldOffset(0x98)] internal readonly LIST_ENTRY64 ForwarderLinks; /// 6.0 to 6.1 (Win7) - [FieldOffset(0xA8)] internal LIST_ENTRY64 ServiceTagLinks; + [FieldOffset(0xA8)] internal readonly LIST_ENTRY64 ServiceTagLinks; /// 6.0 to 6.1 (Win7) - [FieldOffset(0xB8)] internal LIST_ENTRY64 StaticLinks; + [FieldOffset(0xB8)] internal readonly LIST_ENTRY64 StaticLinks; #endregion Appended for Windows Vista #region Redone for Windows 8 /// (LDR_DDAG_NODE*) 6.2 and higher - [FieldOffset(0x98)] UIntPtr64 DdagNode; + [FieldOffset(0x98)] internal readonly UIntPtr64 DdagNode; /// 6.2 and higher - [FieldOffset(0xA0)] LIST_ENTRY64 NodeModuleLink; + [FieldOffset(0xA0)] internal readonly LIST_ENTRY64 NodeModuleLink; /// (LDRP_DLL_SNAP_CONTEXT*) 6.2 to 6.3 - [FieldOffset(0xB0)] UIntPtr64 SnapContext; + [FieldOffset(0xB0)] internal readonly UIntPtr64 SnapContext; /// (LDRP_LOAD_CONTEXT*) 10.0 and higher - [FieldOffset(0xB0)] UIntPtr64 LoadContext; + [FieldOffset(0xB0)] internal readonly UIntPtr64 LoadContext; /// 6.2 and higher - [FieldOffset(0xB8)] UIntPtr64 ParentBaseDll; + [FieldOffset(0xB8)] internal readonly UIntPtr64 ParentBaseDll; /// 6.2 and higher - [FieldOffset(0xC0)] UIntPtr64 SwitchBackContext; + [FieldOffset(0xC0)] internal readonly UIntPtr64 SwitchBackContext; /// 6.2 and higher - [FieldOffset(0xC8)] RTL_BALANCED_NODE64 BaseAddressIndexNode; + [FieldOffset(0xC8)] internal readonly RTL_BALANCED_NODE64 BaseAddressIndexNode; /// 6.2 and higher - [FieldOffset(0xE0)] RTL_BALANCED_NODE64 MappingInfoIndexNode; + [FieldOffset(0xE0)] internal readonly RTL_BALANCED_NODE64 MappingInfoIndexNode; #endregion Redone for Windows 8 #region Appended for Windows 7 /// 6.1 only - [FieldOffset(0xC8)] UIntPtr64 ContextInformation; + [FieldOffset(0xC8)] internal readonly UIntPtr64 ContextInformation; /// 6.1 only - [FieldOffset(0xD0)] UIntPtr64 OriginalBase_NT61; + [FieldOffset(0xD0)] internal readonly UIntPtr64 OriginalBase_NT61; /// 6.2 and higher - [FieldOffset(0xF8)] UIntPtr64 OriginalBase_NT62; + [FieldOffset(0xF8)] internal readonly UIntPtr64 OriginalBase_NT62; /// 6.1 only - [FieldOffset(0xD8)] LARGE_INTEGER LoadTime_NT61; + [FieldOffset(0xD8)] internal readonly long LoadTime_NT61; /// 6.2 and higher - [FieldOffset(0x0100)] LARGE_INTEGER LoadTime_NT62; + [FieldOffset(0x0100)] internal readonly long LoadTime_NT62; #endregion Appended for Windows 7 #region Appended for Windows 8 /// 6.2 and higher - [FieldOffset(0x0108)] internal uint BaseNameHashValue; + [FieldOffset(0x0108)] internal readonly uint BaseNameHashValue; /// 6.2 and higher - [FieldOffset(0x010C)] internal LDR_DLL_LOAD_REASON LoadReason; + [FieldOffset(0x010C)] internal readonly LDR_DLL_LOAD_REASON LoadReason; #endregion Appended for Windows 8 #region Appended for Windows 8.1 /// 6.3 and higher - [FieldOffset(0x0110)] internal uint ImplicitPathOptions; + [FieldOffset(0x0110)] internal readonly uint ImplicitPathOptions; #endregion Appended for Windows 8.1 #region Appended for Windows 10 /// 10.0 and higher - [FieldOffset(0x0114)] internal uint ReferenceCount; + [FieldOffset(0x0114)] internal readonly uint ReferenceCount; /// 1607 and higher - [FieldOffset(0x0118)] internal uint DependentLoadFlags; + [FieldOffset(0x0118)] internal readonly uint DependentLoadFlags; /// 1703 and higher - [FieldOffset(0x011C)] internal byte SigningLevel; + [FieldOffset(0x011C)] internal readonly byte SigningLevel; #endregion Appended for Windows 10 } From 01edeb75facf53235f5196ff2b3eb30d34a4fdec Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:04:39 -0700 Subject: [PATCH 119/306] style: remove trailing whitespace --- .../System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs index 53ea5fc..1debe70 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs @@ -66,4 +66,3 @@ public unsafe RTL_DRIVE_LETTER_CURDIR32[] CurrentDirectories [FieldOffset(0x02B8)] public uint DefaultThreadpoolCpuSetMaskCount; [FieldOffset(0x02BC)] public uint DefaultThreadpoolThreadMaximum; } - From f128e47ad998315cc26df0d303d6efb98413e2d7 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:08:40 -0700 Subject: [PATCH 120/306] refactor: set structs or their members to readonly docs: update PEB_LDR_DATA64 inline docs --- deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs | 4 ++-- deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs | 4 ++-- .../Windows.Win32/System/Kernel/LIST_ENTRY64.cs | 2 +- .../Windows.Win32/System/Threading/PEB_LDR_DATA64.cs | 7 +++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs index 71e4390..b4cde0b 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs @@ -1,6 +1,6 @@ namespace Windows.Win32.Foundation; -internal struct HANDLE32 +internal readonly struct HANDLE32 { internal readonly UIntPtr32 Value { get; init; } @@ -8,7 +8,7 @@ internal struct HANDLE32 public static implicit operator UIntPtr32(HANDLE32 v) => v.Value; } -internal struct HANDLE32 where T : unmanaged +internal readonly struct HANDLE32 where T : unmanaged { internal readonly UIntPtr32 Value { get; init; } diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs index 571549a..8bb1375 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs @@ -1,6 +1,6 @@ namespace Windows.Win32.Foundation; -internal struct HANDLE64 +internal readonly struct HANDLE64 { internal readonly UIntPtr64 Value { get; init; } @@ -8,7 +8,7 @@ internal struct HANDLE64 public static implicit operator UIntPtr64(HANDLE64 v) => v.Value; } -internal struct HANDLE64 where T : unmanaged +internal readonly struct HANDLE64 where T : unmanaged { internal readonly UIntPtr64 Value { get; init; } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs index 08d497d..8ea051e 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs @@ -1,7 +1,7 @@ namespace Windows.Win32.System.Kernel; /// -internal struct LIST_ENTRY64 where T : unmanaged +internal readonly struct LIST_ENTRY64 { /// /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs index f58fb72..a08c8ab 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs @@ -5,11 +5,10 @@ namespace Windows.Win32.System.Threading; -/// -/// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm -/// +/// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm +/// Essentially, the head of three double-linked lists of LDR_DATA_TABLE_ENTRY structures. [StructLayout(LayoutKind.Explicit)] -internal struct PEB_LDR_DATA64 +internal readonly struct PEB_LDR_DATA64 { [FieldOffset(0x00)] internal readonly uint Length; [FieldOffset(0x04)] internal readonly BOOLEAN Initialized; From 49b0e18fe23fb110156adabaecec8bcca5ce1185 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:09:01 -0700 Subject: [PATCH 121/306] style: remove trailing whitespace --- .../Windows.Win32/System/Threading/PEB_BitField.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_BitField.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_BitField.cs index 537e3bb..b99b5cb 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_BitField.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_BitField.cs @@ -29,5 +29,4 @@ public enum PEB_BitField IsProtectedProcessLight = 128, /// Compatibility: 1607 and higher IsLongPathAwareProcess = 256, - } From 3eca4145466540cb17cb5176bcb669fdb36a73a2 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:10:31 -0700 Subject: [PATCH 122/306] chore: disable CS0649 for readonly interop structs --- .../Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs | 2 ++ .../Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs index 08240c6..b7eecd9 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs @@ -2,11 +2,13 @@ namespace Windows.Win32.System.Threading { internal readonly struct RTL_CRITICAL_SECTION32 { +#pragma warning disable CS0649 internal readonly unsafe UIntPtr32 DebugInfo; internal readonly int LockCount; internal readonly int RecursionCount; internal readonly Foundation.HANDLE32 OwningThread; internal readonly Foundation.HANDLE32 LockSemaphore; internal readonly uint SpinCount; +#pragma warning restore CS0649 } } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs index 5a6b377..5c3129b 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs @@ -2,11 +2,13 @@ namespace Windows.Win32.System.Threading { internal readonly struct RTL_CRITICAL_SECTION64 { +#pragma warning disable CS0649 internal readonly unsafe UIntPtr64 DebugInfo; internal readonly int LockCount; internal readonly int RecursionCount; internal readonly Foundation.HANDLE64 OwningThread; internal readonly Foundation.HANDLE64 LockSemaphore; internal readonly ulong SpinCount; +#pragma warning restore CS0649 } } From af39417c5bde7d926202a2b7c593caeab1a7bee9 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:13:00 -0700 Subject: [PATCH 123/306] fix: properly convert UNICODE_STRING 'byte' length to string's 'char' length --- deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs index 672a245..0d3842a 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs @@ -31,7 +31,7 @@ public UNICODE_STRING(ushort maximumLength) Buffer = Marshal.AllocHGlobal(maximumLength); } - public uint CharCount => (uint)ToStringLength().Length; + public uint CharCount => (uint)Length / 2; public void Dispose() { From a3057cc3f60936d682e7c7e0b83c15dfda30bc45 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:13:33 -0700 Subject: [PATCH 124/306] refactor: replace LARGE_INTEGER with long --- deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs | 4 ++-- deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs index b91060a..1ffd7e8 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs @@ -108,9 +108,9 @@ internal struct PEB32 /// Compatibility: 3.51 and higher [FieldOffset(0x68)] internal readonly uint NtGlobalFlag; /// Compatibility: 3.10 to 3.50 - [FieldOffset(0x68), Obsolete] private readonly LARGE_INTEGER CriticalSectionTimeout_obsolete; + [FieldOffset(0x68), Obsolete] private readonly long CriticalSectionTimeout_obsolete; /// Compatibility: 3.51 and higher - [FieldOffset(0x70)] internal readonly LARGE_INTEGER CriticalSectionTimeout; + [FieldOffset(0x70)] internal readonly long CriticalSectionTimeout; #region Appended for Windows NT 3.51 /// Compatibility: 3.51 and higher diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs index c58cce4..de40cb5 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs @@ -101,7 +101,7 @@ internal struct PEB64 /// Compatibility: 3.51 and higher [FieldOffset(0xBC)] internal readonly uint NtGlobalFlag; /// Compatibility: 3.51 and higher - [FieldOffset(0xC0)] internal readonly LARGE_INTEGER CriticalSectionTimeout; + [FieldOffset(0xC0)] internal readonly long CriticalSectionTimeout; #region Appended for Windows NT 3.51 /// Compatibility: 3.51 and higher From cf0998478306ea469e54413ae5e71178a627db06 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:16:51 -0700 Subject: [PATCH 125/306] fix: use non-generic LIST_ENTRY LIST_ENTRY instances can form a list of objects of many different Types. Specifying a Type using LIST_ENTRY is too restrictive. --- .../Windows.Win32/System/Threading/PEB_LDR_DATA32.cs | 10 ++++++---- .../Windows.Win32/System/Threading/PEB_LDR_DATA64.cs | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs index b181f0d..da7eefe 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs @@ -14,10 +14,12 @@ internal readonly struct PEB_LDR_DATA32 [FieldOffset(0x00)] internal readonly uint Length; [FieldOffset(0x04)] internal readonly BOOLEAN Initialized; [FieldOffset(0x08)] internal readonly UIntPtr32 SsHandle; - [FieldOffset(0x0C)] internal readonly LIST_ENTRY32 InLoadOrderModuleList; - /// The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. For more information, see Remarks. - [FieldOffset(0x14)] internal readonly LIST_ENTRY32 InMemoryOrderModuleList; - [FieldOffset(0x1C)] internal readonly LIST_ENTRY32 InInitializationOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were loaded. Each item in the list is a pointer to an structure. See Double Linked Lists + [FieldOffset(0x0C)] internal readonly LIST_ENTRY32 InLoadOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process in the order they appear in memory. Each item in the list is a pointer to an structure. See Double Linked Lists + [FieldOffset(0x14)] internal readonly LIST_ENTRY32 InMemoryOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were initialized. Each item in the list is a pointer to an structure. See Double Linked Lists + [FieldOffset(0x1C)] internal readonly LIST_ENTRY32 InInitializationOrderModuleList; /// 5.1 and higher [FieldOffset(0x24)] internal readonly UIntPtr32 EntryInProgress; /// late 6.0 and higher diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs index a08c8ab..25de60f 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs @@ -13,10 +13,12 @@ internal readonly struct PEB_LDR_DATA64 [FieldOffset(0x00)] internal readonly uint Length; [FieldOffset(0x04)] internal readonly BOOLEAN Initialized; [FieldOffset(0x08)] internal readonly unsafe UIntPtr64 SsHandle; - [FieldOffset(0x10)] internal readonly LIST_ENTRY64 InLoadOrderModuleList; - /// The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. For more information, see Remarks. - [FieldOffset(0x20)] internal readonly LIST_ENTRY64 InMemoryOrderModuleList; - [FieldOffset(0x30)] internal readonly LIST_ENTRY64 InInitializationOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were loaded. Each item in the list is a pointer to an structure. See Double Linked Lists + [FieldOffset(0x10)] internal readonly LIST_ENTRY64 InLoadOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process in the order they appear in memory. Each item in the list is a pointer to an structure. See Double Linked Lists + [FieldOffset(0x20)] internal readonly LIST_ENTRY64 InMemoryOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were initialized. Each item in the list is a pointer to an structure. See Double Linked Lists + [FieldOffset(0x30)] internal readonly LIST_ENTRY64 InInitializationOrderModuleList; [FieldOffset(0x40)] internal readonly unsafe UIntPtr64 EntryInProgress; // 5.1 and higher [FieldOffset(0x48)] internal readonly BOOLEAN ShutdownInProgress; // late 6.0 and higher [FieldOffset(0x50)] internal readonly HANDLE64 ShutdownThreadId; // late 6.0 and higher From 72c5d2dfd39e3da114706751698330e72293f7f5 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:26:53 -0700 Subject: [PATCH 126/306] fix: prevent runtime TypeLoadException Though it Roslyn didn't catch it, this appears to be CS0523: > Struct member 'ExampleType.ExampleMember' of type 'ExampleType' causes a cycle in the struct layout ExampleType cannot have a member of type ExampleType. Likewise, ExampleType cannot have a member of type ExampleType --- .../System/Kernel/LIST_ENTRY32.cs | 20 ++----------------- .../System/Kernel/LIST_ENTRY64.cs | 19 +++--------------- .../System/Kernel/RTL_BALANCED_NODE64.cs | 7 +++++-- 3 files changed, 10 insertions(+), 36 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs index 9c44d85..778342f 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs @@ -1,20 +1,4 @@ namespace Windows.Win32.System.Kernel; - -/// -internal struct LIST_ENTRY32 where T : unmanaged -{ - /// - /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. - /// Read more on docs.microsoft.com. - /// - internal unsafe UIntPtr32> Flink; - /// - /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. - /// Read more on docs.microsoft.com. - /// - internal unsafe UIntPtr32> Blink; -} - /// internal struct LIST_ENTRY32 { @@ -22,10 +6,10 @@ internal struct LIST_ENTRY32 /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. /// Read more on docs.microsoft.com. /// - internal unsafe UIntPtr32 Flink; + internal unsafe UIntPtr32/* */ Flink; /// /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. /// Read more on docs.microsoft.com. /// - internal unsafe UIntPtr32 Blink; + internal unsafe UIntPtr32/* */ Blink; } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs index 8ea051e..162fb66 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs @@ -8,24 +8,11 @@ internal readonly struct LIST_ENTRY64 /// Read more on docs.microsoft.com. ///
/// Points to a LIST_ENTRY64 + internal readonly UIntPtr64/* */ Flink; /// /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. /// Read more on docs.microsoft.com. /// - internal unsafe UIntPtr64> Blink; -} - -/// -internal struct LIST_ENTRY64 -{ - /// - /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. - /// Read more on docs.microsoft.com. - /// - internal unsafe UIntPtr64 Flink; - /// - /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. - /// Read more on docs.microsoft.com. - /// - internal unsafe UIntPtr64 Blink; + /// Points to a LIST_ENTRY64 + internal readonly UIntPtr64/* */ Blink; } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs index 4f420eb..cb242b5 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs @@ -11,12 +11,15 @@ internal unsafe struct RTL_BALANCED_NODE64 { [FieldOffset(0x00)] internal fixed uint _Children[2]; internal UIntPtr64[] Children => new UIntPtr64[] { _Children[0], _Children[1] }; - [FieldOffset(0x00)] internal UIntPtr64 Left; - [FieldOffset(0x08)] internal UIntPtr64 Right; + [FieldOffset(0x00)] private UIntPtr64/* */ left; + [FieldOffset(0x08)] private UIntPtr64/* */ right; [FieldOffset(0x10)] internal UIntPtr64 ParentValue; /// applies if the node is in a Red Black tree internal byte Red => (byte)(ParentValue & 0b1); /// applies if the node is in an AVL tree internal byte Balance => (byte)((ParentValue >> 1) & 0b11); + + internal UIntPtr64 Left { get => (UIntPtr64)left; set => left = (UIntPtr64)value; } + internal UIntPtr64 Right { get => (UIntPtr64)right; set => right = (UIntPtr64)value; } } From 53a3cdef92ee4ce3a8fd115d5ad1ac3424649bb8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:27:40 -0700 Subject: [PATCH 127/306] fix: UNICODE_STRING.ToStringLength() no longer returns garbage --- .../Windows.Win32/Foundation/UNICODE_STRING.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs index 0d3842a..d07ed79 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING.cs @@ -1,4 +1,5 @@ /// This file supplements code generated by CsWin32 +using System.Diagnostics; using System.Runtime.InteropServices; namespace Windows.Win32.Foundation; @@ -41,7 +42,20 @@ public void Dispose() /// /// Allocates a managed string and copies `(Length / 2)` number of characters from an unmanaged Unicode string into it. /// - public unsafe string ToStringLength() => Marshal.PtrToStringUni((IntPtr)Buffer.Value, Length / 2); + public unsafe string ToStringLength() + { + try + { + _ = *Buffer.Value; + return new ReadOnlySpan(Buffer.Value, (int)CharCount).ToString(); + } + catch (Exception e) + { + Debugger.Break(); + throw new Exception("Failed to read string from memory pointer.", e); + } + } + public string? ToStringZ() => Buffer.ToString(); public static explicit operator string(UNICODE_STRING v) => v.ToStringLength(); } From 0a4471074dcc6303dceed0a66fcf12dbcd23086c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:32:53 -0700 Subject: [PATCH 128/306] refactor: rename two functions back to their original names --- .../Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 9261db2..6ac151f 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -26,7 +26,7 @@ internal static partial class NativeMethods /// /// ported from SystemInformer for convenience /// - private static uint? GetObjectTypeNumber(string typeName) + private static uint? PhGetObjectTypeNumber(string typeName) { uint objectIndex = uint.MaxValue; @@ -50,7 +50,7 @@ internal static partial class NativeMethods /// /// ported from SystemInformer for convenience /// - private static string GetObjectTypeName(int typeIndex) + private static string PhGetObjectTypeName(int typeIndex) { string objectTypeName = ""; From 70f83908e567a371e076a4c32d3475b60d81762a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:35:02 -0700 Subject: [PATCH 129/306] refactor: change ObjectTypesInformationBuffer to internal --- .../Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 6ac151f..cea8711 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -203,7 +203,7 @@ public SafeFileHandle ToSafeFileHandle() } } - private sealed class ObjectTypesInformationBuffer : IDisposable + internal sealed class ObjectTypesInformationBuffer : IDisposable { private IntPtr pointer; private uint bytes; From 51b3682dfc1877eb97bb6a45108bcd23005a7f42 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 04:36:36 -0700 Subject: [PATCH 130/306] refactor: rename function to original --- .../Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index cea8711..e26efd6 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -130,7 +130,7 @@ functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead. */ - return GetObjectTypeName(ObjectTypeIndex); + return PhGetObjectTypeName(ObjectTypeIndex); //* Open a handle to the process associated with this system handle - adamkramer */ From dd2a087c0132af5ab8ca2513a8648811124d2769 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 20:09:28 -0700 Subject: [PATCH 131/306] refactor: move const to local scope --- .../Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index e26efd6..517fd17 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -19,7 +19,6 @@ namespace deadlock_dotnet_sdk.Domain; internal static partial class NativeMethods { - private const uint PH_LARGE_BUFFER_SIZE = int.MaxValue; private static List? objectTypes; private static List ObjectTypes => objectTypes ??= ObjectTypesInformationBuffer.PhEnumObjectTypes().ToList(); @@ -261,6 +260,7 @@ public static unsafe ObjectTypesInformationBuffer PhEnumObjectTypes() )) == Code.STATUS_INFO_LENGTH_MISMATCH) { // Fail if we're resizing the buffer to something very large. + const uint PH_LARGE_BUFFER_SIZE = int.MaxValue; if (returnLength * 1.5 > PH_LARGE_BUFFER_SIZE) throw new PInvoke.NTStatusException(Code.STATUS_INSUFFICIENT_RESOURCES); From 9ee76c894956a8af53ede71a9e799d6e825dae88 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 20:23:11 -0700 Subject: [PATCH 132/306] refactor: add GetSafeHandle() to system handle refactor: change readonly fields to Get properties refactor: exclude system handle's GrantedAccess from Json code generation refactor: add GrantedAccessString for Json code generation docs: change some summaries to single line --- ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 517fd17..555983d 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -92,21 +92,19 @@ private static string PhGetObjectTypeName(int typeIndex) public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { #pragma warning disable CS0649 - private readonly unsafe void* Object; - public unsafe nuint ObjectPointer => (nuint)Object; - /// - /// ULONG_PTR, cast to HANDLE, int, or uint - /// + public nuint Object { get; } + /// ULONG_PTR, cast to HANDLE, int, or uint public nuint UniqueProcessId { get; } - /// - /// ULONG_PTR, cast to HANDLE - /// + /// ULONG_PTR, cast to HANDLE public HANDLE HandleValue { get; } - /// - /// This is a bitwise "Flags" data type. - /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. - /// + /// Get the HandleValue as a SafeObjectHandle. Closing this SafeObjectHandle does *not* close the source handle. + public SafeObjectHandle GetSafeHandle() => new(HandleValue, false); + /// This is a bitwise "Flags" data type. + /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. + [System.Text.Json.Serialization.JsonIgnore] public ACCESS_MASK GrantedAccess { get; } // uint + /// Note: SpecificRights requires the Type of `Object` and the code definitions of that Type's access rights. + public string GrantedAccessString => $"0x{GrantedAccess.Value:X} ({GrantedAccess.SpecificRights}, {GrantedAccess.StandardRights}, {GrantedAccess.GenericRights})"; public ushort CreatorBackTraceIndex { get; } // USHORT /// ProcessHacker defines a little over a dozen handle-able object types. public ushort ObjectTypeIndex { get; } // USHORT From 625e24ccd233b3fad76b1446c67b6bb9809d28f5 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 20:25:29 -0700 Subject: [PATCH 133/306] refactor: set HANDLE members to internal Json code generation was assuming the type was public because the members were public. --- .../Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 555983d..6ba7521 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -96,7 +96,7 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX /// ULONG_PTR, cast to HANDLE, int, or uint public nuint UniqueProcessId { get; } /// ULONG_PTR, cast to HANDLE - public HANDLE HandleValue { get; } + internal HANDLE HandleValue { get; } /// Get the HandleValue as a SafeObjectHandle. Closing this SafeObjectHandle does *not* close the source handle. public SafeObjectHandle GetSafeHandle() => new(HandleValue, false); /// This is a bitwise "Flags" data type. From 894e679284ac7675f0b54c7c64b3d553123d9969 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 20:54:48 -0700 Subject: [PATCH 134/306] refactor: replace PhGetObjectTypeName with custom solution --- ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 78 +++++++------------ 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 6ba7521..0eda29b 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -114,60 +114,38 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX private readonly uint Reserved; #pragma warning restore RCS1213, CS0649, CS0169, IDE0051 - /// - /// Get the Type of the object as a string
- /// If calling from a SafeHandle - ///
+ /// Get the Type of the object as a string /// P/Invoke function NtQueryObject failed. See Exception data. /// The Type of the object as a string. public unsafe string GetHandleObjectType() { - /* CS1673: Anonymous methods, lambda expressions, query expressions, and local - functions inside structs cannot access instance members of 'this'. - Consider copying 'this' to a local variable outside the anonymous method, lambda - expression, query expression, or local function and using the local instead. - */ - return PhGetObjectTypeName(ObjectTypeIndex); - - //* Open a handle to the process associated with this system handle - adamkramer */ - - //using SafeFileHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, true, (uint)UniqueProcessId); - //if (hProcess is null || hProcess!.IsInvalid) throw new System.ComponentModel.Win32Exception(); - - //* Duplicate this system handle so we can query it. - adamkramer */ - /* sidebar: DuplicateHandle and NtDuplicateObject seem to have the same purpose, but are in the WinSDK and WDDK, respectively. Question is: Why? Why have two (technically three i.e. ZwDuplicateObject) functions that do the same thing? -BinToss */ - - // if (0 <= NtDuplicateObject(hProcess, HandleValue, Process.GetCurrentProcess().SafeHandle)) - // { - // } - // - // if (DuplicateHandle(hSourceProcessHandle: hProcess, - // hSourceHandle: new Kernel32.SafeObjectHandle(HandleValue), - // hTargetProcessHandle: Process.GetCurrentProcess().SafeHandle, - // lpTargetHandle: out SafeFileHandle lpTargetHandle, - // dwDesiredAccess: default, - // bInheritHandle: true, - // dwOptions: DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) - // { } - - //* Query the object type */ - // string typeName; - // PUBLIC_OBJECT_TYPE_INFORMATION* objectTypeInfo = (PUBLIC_OBJECT_TYPE_INFORMATION*)Marshal.AllocHGlobal(sizeof(PUBLIC_OBJECT_TYPE_INFORMATION)); - // uint* returnLength = (uint*)Marshal.AllocHGlobal(sizeof(uint)); - // NTSTATUS status; - - // if ((status = NtQueryObject(HandleValue, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, objectTypeInfo, (uint)sizeof(PUBLIC_OBJECT_TYPE_INFORMATION), returnLength)).Severity == NTSTATUS.SeverityCode.STATUS_SEVERITY_SUCCESS) - // { - // typeName = objectTypeInfo->TypeName.ToStringLength(); - // Marshal.FreeHGlobal((IntPtr)objectTypeInfo); - // } - // else - // { - // Marshal.FreeHGlobal((IntPtr)objectTypeInfo); - // throw new Exception("P/Invoke function NtQueryObject failed. See Exception data.", new NTStatusException(status)); - // } - - // return typeName; + try + { + NTSTATUS status; + using SafeBuffer buffer = new(numBytes: 256/* (nuint)Marshal.SizeOf() */); + uint returnLength; + using var h = new SafeObjectHandle(HandleValue, false); + + status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); + + // Something's off. Marshal.SizeOf() returns 0x68 (104) but returnLength is 0x78 (120) or sometimes 0x80 (128). Is Win32Metadata's type definition wrong? + while (status.Code is STATUS_BUFFER_OVERFLOW or STATUS_INFO_LENGTH_MISMATCH or STATUS_BUFFER_TOO_SMALL) + { + buffer.Reallocate(returnLength); + status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); + } + + if (status == STATUS_INVALID_HANDLE || !status.IsSuccessful) + throw new NTStatusException(status); + + return (string)buffer.Read(0).TypeName; + + // return GetObjectTypeName(ObjectTypeIndex); + } + catch (Exception) + { + throw; + } } /// Invokes and checks if the result is "File". From 06e1882a3aa9f0557e5f6f79ac7aadd8d5fa77ea Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 21:23:34 -0700 Subject: [PATCH 135/306] refactor: remove dysfunctional Ph functions --- ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 125 +----------------- 1 file changed, 4 insertions(+), 121 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 0eda29b..8086a20 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -20,57 +20,7 @@ namespace deadlock_dotnet_sdk.Domain; internal static partial class NativeMethods { private static List? objectTypes; - private static List ObjectTypes => objectTypes ??= ObjectTypesInformationBuffer.PhEnumObjectTypes().ToList(); - - /// - /// ported from SystemInformer for convenience - /// - private static uint? PhGetObjectTypeNumber(string typeName) - { - uint objectIndex = uint.MaxValue; - - for (int i = 0; i < ObjectTypes.Count; i++) - { - if (typeName.Equals(ObjectTypes[i].TypeName, StringComparison.OrdinalIgnoreCase)) - { - if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) - objectIndex = ObjectTypes[i].TypeIndex; - else - objectIndex = (uint)(i + 2); - } - } - - if (objectIndex is uint.MaxValue) - throw new InvalidOperationException("No matching Type found."); - - return objectIndex; - } - - /// - /// ported from SystemInformer for convenience - /// - private static string PhGetObjectTypeName(int typeIndex) - { - string objectTypeName = ""; - - for (int i = 0; i < ObjectTypes.Count; i++) - { - if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) - { - if (typeIndex == ObjectTypes[i].TypeIndex) - objectTypeName = ObjectTypes[i].TypeName; - } - else if (typeIndex == (i + 2)) - { - objectTypeName = ObjectTypes[i].TypeName; - } - } - - if (objectTypeName is "") - throw new InvalidOperationException("No matching Type found."); - - return objectTypeName; - } + private static List ObjectTypes => objectTypes ??= ObjectTypesInformationBuffer.GetObjectTypes().ToList(); /// /// The @@ -221,7 +171,7 @@ public unsafe void ReAllocate(uint lengthInBytes) /// An , a wrapper for OBJECT_TYPES_INFORMATION, OBJECT_TYPE_INFORMATION, and the allocated memory they occupy. /// /// - public static unsafe ObjectTypesInformationBuffer PhEnumObjectTypes() + public static unsafe ObjectTypesInformationBuffer GetObjectTypes() { NTSTATUS status; using ObjectTypesInformationBuffer buffer = new(0x1000); @@ -233,12 +183,12 @@ public static unsafe ObjectTypesInformationBuffer PhEnumObjectTypes() (void*)buffer.pointer, buffer.bytes, &returnLength - )) == Code.STATUS_INFO_LENGTH_MISMATCH) + )) == STATUS_INFO_LENGTH_MISMATCH) { // Fail if we're resizing the buffer to something very large. const uint PH_LARGE_BUFFER_SIZE = int.MaxValue; if (returnLength * 1.5 > PH_LARGE_BUFFER_SIZE) - throw new PInvoke.NTStatusException(Code.STATUS_INSUFFICIENT_RESOURCES); + throw new NTStatusException(STATUS_INSUFFICIENT_RESOURCES); buffer.ReAllocate((uint)(returnLength * 1.5)); } @@ -247,73 +197,6 @@ public static unsafe ObjectTypesInformationBuffer PhEnumObjectTypes() return buffer; } - - public unsafe uint PhGetObjectTypeNumber(string typeName) - { - OBJECT_TYPE_INFORMATION* objectType; - uint objectIndex = uint.MaxValue; - uint i; - - if (NumberOfTypes != default) - { - objectType = PH_FIRST_OBJECT_TYPE((void*)pointer); - - for (i = 0; i < NumberOfTypes; i++) - { - string typeNameSr = (string)objectType->TypeName; - - if (string.Equals(typeNameSr, typeName, StringComparison.OrdinalIgnoreCase)) - { - if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) - { - objectIndex = objectType->TypeIndex; - break; - } - else - { - objectIndex = i + 2; - break; - } - } - - objectType = PH_NEXT_OBJECT_TYPE(objectType); - } - } - return objectIndex; - } - - public unsafe string? PhGetObjectTypeName(uint TypeIndex) - { - OBJECT_TYPE_INFORMATION* objectType; - string? objectTypeName = null; - uint i; - - objectType = PH_FIRST_OBJECT_TYPE((void*)pointer); - - for (i = 0; i < NumberOfTypes; i++) - { - if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) - { - if (TypeIndex == objectType->TypeIndex) - { - objectTypeName = (string)objectType->TypeName; - break; - } - } - else - { - if (TypeIndex == (i + 2)) - { - objectTypeName = (string)objectType->TypeName; - break; - } - } - - objectType = PH_NEXT_OBJECT_TYPE(objectType); - } - - return objectTypeName; - } } public static unsafe OBJECT_TYPE_INFORMATION* PH_FIRST_OBJECT_TYPE(void* ObjectTypes) => (OBJECT_TYPE_INFORMATION*)PTR_ADD_OFFSET(ObjectTypes, ALIGN_UP((nuint)sizeof(OBJECT_TYPES_INFORMATION), typeof(UIntPtr))); From c83db4d1c993e9da0c4fa015dd4af7f8dc19cf9d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 21:24:11 -0700 Subject: [PATCH 136/306] refactor: add GetHandleInformation method to system handle --- ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 8086a20..972d648 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -98,6 +98,27 @@ public unsafe string GetHandleObjectType() } } + internal unsafe HANDLE_FLAGS GetHandleInformation() + { + try + { + using SafeObjectHandle hObject = new(HandleValue, false); + return Windows.Win32.PInvoke.GetHandleInformation(hObject); + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + } + + // If passing the source handle failed, try passing a duplicate instead + + using SafeProcessHandle sourceProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, (uint)UniqueProcessId); + if (sourceProcess is null) throw new Win32Exception(); + using SafeObjectHandle safeHandleValue = new(HandleValue, false); + DuplicateHandle(sourceProcess, safeHandleValue, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, default, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); + return Windows.Win32.PInvoke.GetHandleInformation(dupHandle); + } + /// Invokes and checks if the result is "File". /// True if the handle is for a file or directory. /// Based on source of C/C++ projects Hijack File Handle and Handle Monitor From 5bc848ab81549e324640579bc4cc940231beca33 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 23:35:27 -0700 Subject: [PATCH 137/306] refactor: update usings chore: remove old comments --- ...Methods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 972d648..56f722a 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -1,19 +1,16 @@ +using System.Diagnostics; using System.Runtime.InteropServices; +using System.Text.Json.Serialization; using Microsoft.Win32.SafeHandles; +using PInvoke; +using Windows.Win32; using Windows.Win32.Foundation; +using Windows.Win32.System.Threading; using Windows.Win32.System.WindowsProgramming; using static PInvoke.Kernel32; +using static PInvoke.NTSTATUS.Code; using static Windows.Win32.PInvoke; -using Code = PInvoke.NTSTATUS.Code; - -// Re: StructLayout -// "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." -// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-6.0#remarks - -// new Win32Exception() is defined as -// public Win32Exception() : this(Marshal.GetLastPInvokeError()) -// { -// } +using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; namespace deadlock_dotnet_sdk.Domain; From ef4337e8bf02d24982289c12aae98014bcc7948e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 23:39:17 -0700 Subject: [PATCH 138/306] refactor: simplify access to JsonIgnore --- .../Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 56f722a..9b04908 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -48,7 +48,7 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX public SafeObjectHandle GetSafeHandle() => new(HandleValue, false); /// This is a bitwise "Flags" data type. /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. - [System.Text.Json.Serialization.JsonIgnore] + [JsonIgnore] public ACCESS_MASK GrantedAccess { get; } // uint /// Note: SpecificRights requires the Type of `Object` and the code definitions of that Type's access rights. public string GrantedAccessString => $"0x{GrantedAccess.Value:X} ({GrantedAccess.SpecificRights}, {GrantedAccess.StandardRights}, {GrantedAccess.GenericRights})"; From 15fdd44938bf9bdeb2dc2b8e583c451bd1085dda Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 23:39:48 -0700 Subject: [PATCH 139/306] refactor: rename GetHandleInformation wrapper --- .../NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 9b04908..d29ed47 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -95,12 +95,12 @@ public unsafe string GetHandleObjectType() } } - internal unsafe HANDLE_FLAGS GetHandleInformation() + internal unsafe HANDLE_FLAGS GetHandleInfo() { try { using SafeObjectHandle hObject = new(HandleValue, false); - return Windows.Win32.PInvoke.GetHandleInformation(hObject); + return GetHandleInformation(hObject); } catch (Exception ex) { @@ -113,7 +113,7 @@ internal unsafe HANDLE_FLAGS GetHandleInformation() if (sourceProcess is null) throw new Win32Exception(); using SafeObjectHandle safeHandleValue = new(HandleValue, false); DuplicateHandle(sourceProcess, safeHandleValue, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, default, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); - return Windows.Win32.PInvoke.GetHandleInformation(dupHandle); + return GetHandleInformation(dupHandle); } /// Invokes and checks if the result is "File". From abf91debd744e7b19dd850f0e231ea5ccf8fb5d2 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 23:40:48 -0700 Subject: [PATCH 140/306] refactor: remove unused ToSafeFileHandle() --- ...ativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index d29ed47..88c328a 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -132,18 +132,6 @@ public bool IsFileHandle() throw new Exception("Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details.", e); } } - - /// - /// Try to cast this handle's to a SafeFileHandle; - /// - /// A if this handle's object is a data/directory File. - /// The handle's object is not a File -OR- perhaps NtQueryObject() failed. See for details. - public SafeFileHandle ToSafeFileHandle() - { - return IsFileHandle() - ? (new((nint)HandleValue, (int)UniqueProcessId == Environment.ProcessId)) - : throw new Exception("The handle's object is not a File -OR- NtQueryObject() failed. See InnerException for details."); - } } internal sealed class ObjectTypesInformationBuffer : IDisposable From 34128bf0e8e4026ecec1c9f5331c87bfb778e1d8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 23:46:13 -0700 Subject: [PATCH 141/306] refactor: base ObjectTypesInformationBuffer on SafeBuffer refactor: add GetTypesInfo() refactor: move ObjectTypes to ObjectTypesInformation buffer refactor: remove ReAllocate(); use SafeBuffer.Reallocate() instead refactor: override Dispose to free buffer with Marshal.FreeHGlobal() refactor: remove remaining Ph functions --- ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 72 ++++++------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 88c328a..d992c88 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -16,9 +16,6 @@ namespace deadlock_dotnet_sdk.Domain; internal static partial class NativeMethods { - private static List? objectTypes; - private static List ObjectTypes => objectTypes ??= ObjectTypesInformationBuffer.GetObjectTypes().ToList(); - /// /// The /// @@ -134,49 +131,29 @@ public bool IsFileHandle() } } - internal sealed class ObjectTypesInformationBuffer : IDisposable + internal sealed class ObjectTypesInformationBuffer : SafeBuffer { - private IntPtr pointer; - private uint bytes; - - public ObjectTypesInformationBuffer(uint lengthInBytes) - { - pointer = Marshal.AllocHGlobal((int)lengthInBytes); - bytes = lengthInBytes; - } + public ObjectTypesInformationBuffer(uint numBytes) : base(numBytes: numBytes) + { } - public IntPtr Pointer => pointer; - public uint SizeInBytes => bytes; - public unsafe uint NumberOfTypes => ((OBJECT_TYPES_INFORMATION*)pointer)->NumberOfTypes; + private static List? objectTypes; + internal static List ObjectTypes => objectTypes ??= GetObjectTypes().GetTypesInfo().ToArray().Cast().ToList(); + public unsafe uint NumberOfTypes => Read(0).NumberOfTypes; - public unsafe List ToList() + public unsafe List GetTypesInfo() { - var list = new List(); - var selection = PH_FIRST_OBJECT_TYPE((void*)pointer); - list.Add(new(*selection)); - - for (int i = 1; i < NumberOfTypes; i++) - { - selection = PH_NEXT_OBJECT_TYPE(selection); - list.Add(new(*selection)); - } - return list; + Span buffer = new(new OBJECT_TYPE_INFORMATION[NumberOfTypes]); + ReadSpan( + (ulong)Marshal.OffsetOf(nameof(OBJECT_TYPES_INFORMATION.TypeInformation_0)), + buffer); + return buffer.ToArray().Cast().ToList(); } - public unsafe void ReAllocate(uint lengthInBytes) - { - pointer = Marshal.ReAllocHGlobal(pointer, (IntPtr)lengthInBytes); - bytes = lengthInBytes; - } - - public void Dispose() => Marshal.FreeHGlobal(pointer); - /// /// P/Invoke NtQueryObject for ObjectTypesInformation data. /// /// An , a wrapper for OBJECT_TYPES_INFORMATION, OBJECT_TYPE_INFORMATION, and the allocated memory they occupy. /// - /// public static unsafe ObjectTypesInformationBuffer GetObjectTypes() { NTSTATUS status; @@ -186,28 +163,21 @@ public static unsafe ObjectTypesInformationBuffer GetObjectTypes() while ((status = NtQueryObject( null, OBJECT_INFORMATION_CLASS.ObjectTypesInformation, - (void*)buffer.pointer, - buffer.bytes, + (void*)buffer.handle, + (uint)buffer.ByteLength, &returnLength )) == STATUS_INFO_LENGTH_MISMATCH) { - // Fail if we're resizing the buffer to something very large. - const uint PH_LARGE_BUFFER_SIZE = int.MaxValue; - if (returnLength * 1.5 > PH_LARGE_BUFFER_SIZE) - throw new NTStatusException(STATUS_INSUFFICIENT_RESOURCES); - - buffer.ReAllocate((uint)(returnLength * 1.5)); + buffer.Reallocate(returnLength); } - status.ThrowOnError(); + return status.IsSuccessful ? buffer : throw new NTStatusException(status); + } - return buffer; + public new void Dispose() + { + Marshal.FreeHGlobal(handle); + base.Dispose(); } } - - public static unsafe OBJECT_TYPE_INFORMATION* PH_FIRST_OBJECT_TYPE(void* ObjectTypes) => (OBJECT_TYPE_INFORMATION*)PTR_ADD_OFFSET(ObjectTypes, ALIGN_UP((nuint)sizeof(OBJECT_TYPES_INFORMATION), typeof(UIntPtr))); - public static unsafe OBJECT_TYPE_INFORMATION* PH_NEXT_OBJECT_TYPE(OBJECT_TYPE_INFORMATION* ObjectType) => (OBJECT_TYPE_INFORMATION*)PTR_ADD_OFFSET(ObjectType, (nuint)Marshal.SizeOf() + ALIGN_UP(ObjectType->TypeName.MaximumLength, typeof(UIntPtr))); - public static unsafe void* PTR_ADD_OFFSET(void* Pointer, nuint Offset) => (void*)((nuint)Pointer + Offset); - public static nuint ALIGN_UP(nuint Address, Type type) => ALIGN_UP_BY(Address, (uint)Marshal.SizeOf(type)); - public static nuint ALIGN_UP_BY(nuint Address, uint Align) => (Address + Align - 1) & ~(Align - 1); } From dcdee0bc4473c25ebaffce0eba6c6d857b7b10ed Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 23:53:33 -0700 Subject: [PATCH 142/306] refactor: set SYSTEM_HANDLE_INFORMATION_EX as readonly --- .../Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs index 4fef99d..1e69c45 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs @@ -16,7 +16,7 @@ internal static partial class NativeMethods /// The SYSTEM_HANDLE_INFORMATION_EX /// struct is 0x24 or 0x38 bytes in 32-bit and 64-bit Windows, respectively. However, Handles is a variable-length array. /// - public unsafe struct SYSTEM_HANDLE_INFORMATION_EX + public readonly unsafe struct SYSTEM_HANDLE_INFORMATION_EX { #pragma warning disable CS0649 From 96016f13d98ba175e2e6330b73836e17d1675358 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 23:57:17 -0700 Subject: [PATCH 143/306] refactor: compare against Win32ErrorCode --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index fd5d5c5..fff0c3f 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -77,7 +77,8 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo pnProcInfo = pnProcInfoNeeded; fixed (RM_PROCESS_INFO* pProcessInfo = processInfo) - res = (Win32ErrorCode)RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, pProcessInfo, out lpdwRebootReasons); if (res is Win32ErrorCode.ERROR_SUCCESS) + res = (Win32ErrorCode)RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, pProcessInfo, out lpdwRebootReasons); + if (res is Win32ErrorCode.ERROR_SUCCESS) { processes = new List((int)pnProcInfo); From c4d3ecee6ec73e92e2e0d7793c73d08a36aa5e08 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 27 Mar 2023 23:57:34 -0700 Subject: [PATCH 144/306] docs: remove old TODO --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index fff0c3f..f1046cb 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -128,7 +128,6 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo /// When requested, handles for non-file or unidentified objects will be included with file-specific properties nulled. /// /// SeDebugMode may be required for data from system and service processes. Restart app as admin and call . - // TODO: Perhaps we should allow a new query without re-calling GetSystemHandleInfoEx(). internal static List FindLockingHandles(string? query = null, HandlesFilter filter = HandlesFilter.FilesOnly) { List? handles = new(); From 952f1ba212b52b61496e75b20bad5ed97c81f4e0 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:06:51 -0700 Subject: [PATCH 145/306] refactor: remove ObjectTypesInformationBuffer It provides caching, but we would needs to match the ObjectTypeIndex to a type in the List. That was unreliable. It often spat out garbage due to platform differences and other causes I couldn't identify. SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.GetHandleObjectType() is easier to implement. --- ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index d992c88..6ae1116 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -130,54 +130,4 @@ public bool IsFileHandle() } } } - - internal sealed class ObjectTypesInformationBuffer : SafeBuffer - { - public ObjectTypesInformationBuffer(uint numBytes) : base(numBytes: numBytes) - { } - - private static List? objectTypes; - internal static List ObjectTypes => objectTypes ??= GetObjectTypes().GetTypesInfo().ToArray().Cast().ToList(); - public unsafe uint NumberOfTypes => Read(0).NumberOfTypes; - - public unsafe List GetTypesInfo() - { - Span buffer = new(new OBJECT_TYPE_INFORMATION[NumberOfTypes]); - ReadSpan( - (ulong)Marshal.OffsetOf(nameof(OBJECT_TYPES_INFORMATION.TypeInformation_0)), - buffer); - return buffer.ToArray().Cast().ToList(); - } - - /// - /// P/Invoke NtQueryObject for ObjectTypesInformation data. - /// - /// An , a wrapper for OBJECT_TYPES_INFORMATION, OBJECT_TYPE_INFORMATION, and the allocated memory they occupy. - /// - public static unsafe ObjectTypesInformationBuffer GetObjectTypes() - { - NTSTATUS status; - using ObjectTypesInformationBuffer buffer = new(0x1000); - uint returnLength; - - while ((status = NtQueryObject( - null, - OBJECT_INFORMATION_CLASS.ObjectTypesInformation, - (void*)buffer.handle, - (uint)buffer.ByteLength, - &returnLength - )) == STATUS_INFO_LENGTH_MISMATCH) - { - buffer.Reallocate(returnLength); - } - - return status.IsSuccessful ? buffer : throw new NTStatusException(status); - } - - public new void Dispose() - { - Marshal.FreeHGlobal(handle); - base.Dispose(); - } - } } From bca3936bef2e1a8c2313a561475483754063df39 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:09:23 -0700 Subject: [PATCH 146/306] refactor: move SYSTEM_HANDLE_INFORMATION_EX to Windows.Win32 --- ...iveMethods.SYSTEM_HANDLE_INFORMATION_EX.cs | 76 ------------------- .../SYSTEM_HANDLE_INFORMATION_EX.cs | 72 ++++++++++++++++++ 2 files changed, 72 insertions(+), 76 deletions(-) delete mode 100644 deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs deleted file mode 100644 index 1e69c45..0000000 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_INFORMATION_EX.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Re: StructLayout -// "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." -// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-6.0#remarks - -// new Win32Exception() is defined as -// public Win32Exception() : this(Marshal.GetLastPInvokeError()) -// { -// } - -namespace deadlock_dotnet_sdk.Domain; - -internal static partial class NativeMethods -{ - //private static extern void MmIsAddressValid() - /// - /// The SYSTEM_HANDLE_INFORMATION_EX - /// struct is 0x24 or 0x38 bytes in 32-bit and 64-bit Windows, respectively. However, Handles is a variable-length array. - /// - public readonly unsafe struct SYSTEM_HANDLE_INFORMATION_EX - { -#pragma warning disable CS0649 - - /// - /// As documented unofficially, NumberOfHandles is a 4-byte or 8-byte ULONG_PTR in 32-bit and 64-bit Windows, respectively.
- /// This is not to be confused with uint* or ulong*. - ///
- public readonly UIntPtr NumberOfHandles; - public readonly UIntPtr Reserved; - public readonly SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handle_0; - - /// - /// If IsEmpty is true, AsSpan() failed. - /// - /// - public ReadOnlySpan ReadOnlySpan - { - get - { - try - { - return AsSpan(); - } - catch (Exception) - { - return ReadOnlySpan.Empty; - } - } - } -#pragma warning restore CS0649 - - /// - /// Infer an array from the address of Handle_0 and NumberOfHandles, then return it as a ReadOnlySpan - /// - /// - /// - public ReadOnlySpan AsSpan() - { - fixed (SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* pHandle_0 = &Handle_0) - return new ReadOnlySpan(pHandle_0, (int)NumberOfHandles).ToArray(); - } - - /// - /// DEBUGGING | Test for memory access. System.AccessViolationException due to these values being in a protected memory range is a problem. - /// - internal void CheckAccess() - { - var tmp = AsSpan(); - - var lastItem = tmp[(int)NumberOfHandles - 1]; - - Console.WriteLine(lastItem + ": " + lastItem.UniqueProcessId); - } - - public static explicit operator ReadOnlySpan(SYSTEM_HANDLE_INFORMATION_EX value) => value.AsSpan(); - } -} diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs new file mode 100644 index 0000000..a906d56 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs @@ -0,0 +1,72 @@ +// Re: StructLayout +// "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." +// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-6.0#remarks + +// new Win32Exception() is defined as +// public Win32Exception() : this(Marshal.GetLastPInvokeError()) +// { +// } + +namespace Windows.Win32; + +/// +/// The SYSTEM_HANDLE_INFORMATION_EX +/// struct is 0x24 or 0x38 bytes in 32-bit and 64-bit Windows, respectively. However, Handles is a variable-length array. +/// +public readonly unsafe struct SYSTEM_HANDLE_INFORMATION_EX +{ +#pragma warning disable CS0649 + + /// + /// As documented unofficially, NumberOfHandles is a 4-byte or 8-byte ULONG_PTR in 32-bit and 64-bit Windows, respectively.
+ /// This is not to be confused with uint* or ulong*. + ///
+ public readonly UIntPtr NumberOfHandles; + public readonly UIntPtr Reserved; + public readonly SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handle_0; + + /// + /// If IsEmpty is true, AsSpan() failed. + /// + /// + public ReadOnlySpan AsReadOnlySpan + { + get + { + try + { + return AsSpan(); + } + catch (Exception) + { + return ReadOnlySpan.Empty; + } + } + } +#pragma warning restore CS0649 + + /// + /// Infer an array from the address of Handle_0 and NumberOfHandles, then return it as a ReadOnlySpan + /// + /// + /// + public ReadOnlySpan AsSpan() + { + fixed (SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* pHandle_0 = &Handle_0) + return new ReadOnlySpan(pHandle_0, (int)NumberOfHandles).ToArray(); + } + + /// + /// DEBUGGING | Test for memory access. System.AccessViolationException due to these values being in a protected memory range is a problem. + /// + internal void CheckAccess() + { + var tmp = AsSpan(); + + var lastItem = tmp[(int)NumberOfHandles - 1]; + + Console.WriteLine(lastItem + ": " + lastItem.UniqueProcessId); + } + + public static explicit operator ReadOnlySpan(SYSTEM_HANDLE_INFORMATION_EX value) => value.AsSpan(); +} From 326cfaa8cfcd38cd106ece7babef11404a10583e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:09:52 -0700 Subject: [PATCH 147/306] docs: remove old comments --- .../Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs index a906d56..ccb3282 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs @@ -1,12 +1,3 @@ -// Re: StructLayout -// "C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default." -// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-6.0#remarks - -// new Win32Exception() is defined as -// public Win32Exception() : this(Marshal.GetLastPInvokeError()) -// { -// } - namespace Windows.Win32; /// From 247e2ebfb69e83587dc1e357372b2b8b81b23b95 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:10:32 -0700 Subject: [PATCH 148/306] refactor: make NTSTATUS more similar to PInvoke definition --- .../Windows.Win32/Foundation/NTSTATUS.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs index 187eeed..a7af389 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/NTSTATUS.cs @@ -20,17 +20,15 @@ public void ThrowOnError() throw new NTStatusException(this); } - public global::PInvoke.NTSTATUS.Code Code => ((global::PInvoke.NTSTATUS)this).Value; - - /// - public string Message => ((global::PInvoke.NTSTATUS)this).GetMessage(); - public Code Code => (Code)Value; public FacilityCode FacilityCode => ((global::PInvoke.NTSTATUS)this).Facility; /* property SeverityCode is defined in generated code */ + /// + public string Message => ((global::PInvoke.NTSTATUS)this).GetMessage(); public static implicit operator global::PInvoke.NTSTATUS(NTSTATUS v) => new(v.Value); public static implicit operator NTSTATUS(global::PInvoke.NTSTATUS v) => new(v.AsInt32); - public static implicit operator Code(NTSTATUS v) => ((global::PInvoke.NTSTATUS)v).Value; + public static implicit operator Code(NTSTATUS v) => v.Code; + public static implicit operator NTSTATUS(Code v) => new((int)v); } From 238ce6b448d6be9f6114cd400e4d6dfe0934be06 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:11:01 -0700 Subject: [PATCH 149/306] refactor: add exception-throwing wrapper for GetHandleInformation --- deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs index bf429c5..c6fcad8 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs @@ -208,4 +208,6 @@ internal static bool PrivilegeCheck(SafeHandle ClientToken, ref PRIVILEGE_SET Re /// internal static SafeFileHandle OpenProcessToken(SafeFileHandle ProcessHandle, TOKEN_ACCESS_MASK DesiredAccess) => OpenProcessToken(ProcessHandle, DesiredAccess, out SafeFileHandle TokenHandle) ? TokenHandle : throw new Win32Exception(); + + internal static HANDLE_FLAGS GetHandleInformation(SafeHandle hObject) => (HANDLE_FLAGS)(GetHandleInformation(hObject, out uint flags) ? flags : throw new Win32Exception()); } From aa3a482bfa11ce7b8bae00f66b3ffc5017239e67 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:19:35 -0700 Subject: [PATCH 150/306] refactor: move IsFileHandle() to SafeHandleEx refactor: change CloseSourceHandle to return bool --- ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 20 ------- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 59 +++++++++++-------- 2 files changed, 33 insertions(+), 46 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 6ae1116..1e0ff62 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Runtime.InteropServices; using System.Text.Json.Serialization; using Microsoft.Win32.SafeHandles; using PInvoke; @@ -83,8 +82,6 @@ public unsafe string GetHandleObjectType() throw new NTStatusException(status); return (string)buffer.Read(0).TypeName; - - // return GetObjectTypeName(ObjectTypeIndex); } catch (Exception) { @@ -112,22 +109,5 @@ internal unsafe HANDLE_FLAGS GetHandleInfo() DuplicateHandle(sourceProcess, safeHandleValue, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, default, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); return GetHandleInformation(dupHandle); } - - /// Invokes and checks if the result is "File". - /// True if the handle is for a file or directory. - /// Based on source of C/C++ projects Hijack File Handle and Handle Monitor - /// Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details. - public bool IsFileHandle() - { - try - { - string type = GetHandleObjectType(); - return !string.IsNullOrWhiteSpace(type) && string.CompareOrdinal(type, "File") == 0; - } - catch (Exception e) - { - throw new Exception("Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. See InnerException for details.", e); - } - } } } diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index fd32cf4..c92ab1d 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -136,42 +136,49 @@ public string? ProcessCommandLine /// See Raymond Chen's devblog article "Kernel handles are not reference-counted". /// /// Failed to open process to duplicate and close object handle. - public void CloseSourceHandle() + public bool CloseSourceHandle() { - HANDLE rawHProcess; - SafeProcessHandle? hProcess = null; try { - if ((rawHProcess = OpenProcess( - PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, - true, - ProcessId) - ).IsNull) - { - throw new Win32Exception($"Failed to open process with id {ProcessId} to duplicate and close object handle."); - } + HANDLE rawHProcess; + using SafeProcessHandle hProcess = new( + !(rawHProcess = OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, true, ProcessId)).IsNull + ? rawHProcess + : throw new Win32Exception($"Failed to open process with id {ProcessId} to duplicate and close object handle."), + true); + if (!DuplicateHandle(hProcess, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_CLOSE_SOURCE)) + throw new Win32Exception("Function DuplicateHandle failed to duplicate the handle"); - hProcess = new(rawHProcess, true); - if (DuplicateHandle(hProcess, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_CLOSE_SOURCE)) - { - dupHandle.Close(); - hProcess.Close(); + dupHandle.Close(); + hProcess.Close(); + // finally, close this SafeHandleEx + Close(); + return true; + } + catch (Exception ex) + { + ExceptionLog.Add(ex); + return false; + } + } - // finally, close this SafeHandleEx - Close(); - } - else - { - throw new Win32Exception("Function DuplicateHandle failed to duplicate the handle"); - } + /// Invokes and checks if the result is "File". + /// True if the handle is for a file or directory. + /// Based on source of C/C++ projects Hijack File Handle and Handle Monitor + /// Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. InnerException Message: + public (bool? v, Exception? ex) GetIsFileHandle() + { + try + { + return (HandleObjectType != default && HandleObjectType.v == "File", null); } - catch (Exception e) + catch (Exception ex) { - ExceptionLog.Add(e); - hProcess?.Close(); + return (null, new Exception($"Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. InnerException Message: {ex.Message}", ex)); } } + /// /// A wrapper for QueryFullProcessImageName, a system function that circumvents 32-bit process limitations when permitted the PROCESS_QUERY_LIMITED_INFORMATION right. /// From feee78c89eddabedf60309e0f078240b661dff1c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:35:14 -0700 Subject: [PATCH 151/306] refactor: add TryGetFullProcessImageName fix: avoid Marshal.ReAllocHGlobal bug ReAllocHGlobal will sometimes encounter an error, but it discards the error and throws a deceptive OutOfMemoryException. See dotnet/runtime.git --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 32 ++++++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index c92ab1d..6d2008e 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -178,6 +178,21 @@ public bool CloseSourceHandle() } } + private (string? v, Exception? ex) TryGetFullProcessImageName() + { + try + { + return (GetFullProcessImageName(ProcessId), null); + } + catch (Win32Exception ex) when (ex.ErrorCode == 31) + { + return (null, new InvalidOperationException("Process has exited, so the requested information is not available.", ex)); + } + catch (Exception ex) + { + return (null, ex); + } + } /// /// A wrapper for QueryFullProcessImageName, a system function that circumvents 32-bit process limitations when permitted the PROCESS_QUERY_LIMITED_INFORMATION right. @@ -186,7 +201,7 @@ public bool CloseSourceHandle() /// The path to the executable image. /// The process handle is invalid /// QueryFullProcessImageName failed. See Exception message for details. - private unsafe static string? GetFullProcessImageName(uint processId) + private unsafe static string GetFullProcessImageName(uint processId) { uint size = 260 + 1; uint bufferLength = size; @@ -202,19 +217,18 @@ public bool CloseSourceHandle() } else if (bufferLength < size) { - using PWSTR newBuffer = Marshal.ReAllocHGlobal((IntPtr)buffer.Value, (IntPtr)size); + using PWSTR newBuffer = Marshal.AllocHGlobal((IntPtr)size); if (QueryFullProcessImageName( - hProcess, - PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - newBuffer, - ref size)) + hProcess, + PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + newBuffer, + ref size)) { - return newBuffer.ToString(); + return newBuffer.ToString(); // newBuffer.Value will not be null here } else { - // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - throw new Win32Exception(); + throw new Win32Exception(); // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) } } else From 8e46b3372e777a57aa8c354b8b8763752f98833c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:36:01 -0700 Subject: [PATCH 152/306] refactor: return a tuple, as intended --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 6d2008e..5e811fd 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -240,11 +240,10 @@ private unsafe static string GetFullProcessImageName(uint processId) private static (string? v, Exception? ex) GetProcessCommandLine(uint processId) { - Exception exceptionData = null; - if (processId == (uint)Environment.ProcessId) return (Environment.CommandLine, null); + Exception? exceptionData = default; try { if (!IsDebugModeEnabled()) @@ -261,7 +260,8 @@ private static (string? v, Exception? ex) GetProcessCommandLine(uint processId) try { - return GetProcessCommandLine(hProcess); + //todo: amend GetProcessCommandLine commit + return (GetProcessCommandLine(hProcess), null); } catch (Exception ex) { From 8d248782c55a81045c212fc0acb6bdeb1d22d3a5 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:53:03 -0700 Subject: [PATCH 153/306] refactor: move SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX to Windows.Win32 --- ...thods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 113 ------------------ deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 14 +-- .../SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 109 +++++++++++++++++ 3 files changed, 111 insertions(+), 125 deletions(-) delete mode 100644 deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs deleted file mode 100644 index 1e0ff62..0000000 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Diagnostics; -using System.Text.Json.Serialization; -using Microsoft.Win32.SafeHandles; -using PInvoke; -using Windows.Win32; -using Windows.Win32.Foundation; -using Windows.Win32.System.Threading; -using Windows.Win32.System.WindowsProgramming; -using static PInvoke.Kernel32; -using static PInvoke.NTSTATUS.Code; -using static Windows.Win32.PInvoke; -using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; - -namespace deadlock_dotnet_sdk.Domain; - -internal static partial class NativeMethods -{ - /// - /// The - /// - /// SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX - /// structure is a recurring element in the - /// - /// SYSTEM_HANDLE_INFORMATION_EX - /// struct that a successful call to - /// - /// ZwQuerySystemInformation - /// or - /// - /// NtQuerySystemInformation - /// produces in its output buffer when given the information class - /// SystemExtendedHandleInformation (0x40). - /// This inline doc was supplemented by ProcessHacker's usage of this struct. - /// - public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX - { -#pragma warning disable CS0649 - public nuint Object { get; } - /// ULONG_PTR, cast to HANDLE, int, or uint - public nuint UniqueProcessId { get; } - /// ULONG_PTR, cast to HANDLE - internal HANDLE HandleValue { get; } - /// Get the HandleValue as a SafeObjectHandle. Closing this SafeObjectHandle does *not* close the source handle. - public SafeObjectHandle GetSafeHandle() => new(HandleValue, false); - /// This is a bitwise "Flags" data type. - /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. - [JsonIgnore] - public ACCESS_MASK GrantedAccess { get; } // uint - /// Note: SpecificRights requires the Type of `Object` and the code definitions of that Type's access rights. - public string GrantedAccessString => $"0x{GrantedAccess.Value:X} ({GrantedAccess.SpecificRights}, {GrantedAccess.StandardRights}, {GrantedAccess.GenericRights})"; - public ushort CreatorBackTraceIndex { get; } // USHORT - /// ProcessHacker defines a little over a dozen handle-able object types. - public ushort ObjectTypeIndex { get; } // USHORT - /// - public HandleFlags HandleAttributes { get; } // uint -#pragma warning disable RCS1213, CS0169, IDE0051 // Remove unused field declaration. csharp(RCS1213) | Roslynator - private readonly uint Reserved; -#pragma warning restore RCS1213, CS0649, CS0169, IDE0051 - - /// Get the Type of the object as a string - /// P/Invoke function NtQueryObject failed. See Exception data. - /// The Type of the object as a string. - public unsafe string GetHandleObjectType() - { - try - { - NTSTATUS status; - using SafeBuffer buffer = new(numBytes: 256/* (nuint)Marshal.SizeOf() */); - uint returnLength; - using var h = new SafeObjectHandle(HandleValue, false); - - status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); - - // Something's off. Marshal.SizeOf() returns 0x68 (104) but returnLength is 0x78 (120) or sometimes 0x80 (128). Is Win32Metadata's type definition wrong? - while (status.Code is STATUS_BUFFER_OVERFLOW or STATUS_INFO_LENGTH_MISMATCH or STATUS_BUFFER_TOO_SMALL) - { - buffer.Reallocate(returnLength); - status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); - } - - if (status == STATUS_INVALID_HANDLE || !status.IsSuccessful) - throw new NTStatusException(status); - - return (string)buffer.Read(0).TypeName; - } - catch (Exception) - { - throw; - } - } - - internal unsafe HANDLE_FLAGS GetHandleInfo() - { - try - { - using SafeObjectHandle hObject = new(HandleValue, false); - return GetHandleInformation(hObject); - } - catch (Exception ex) - { - Debug.Print(ex.ToString()); - } - - // If passing the source handle failed, try passing a duplicate instead - - using SafeProcessHandle sourceProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, (uint)UniqueProcessId); - if (sourceProcess is null) throw new Win32Exception(); - using SafeObjectHandle safeHandleValue = new(HandleValue, false); - DuplicateHandle(sourceProcess, safeHandleValue, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, default, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); - return GetHandleInformation(dupHandle); - } - } -} diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 5e811fd..24d9b67 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -31,7 +31,7 @@ public SafeHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) /// Initializes a new instance of the SafeHandleEx class from a , specifying whether the handle is to be reliably released. /// /// - internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(false) + internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(false) { SysHandleEx = sysHandleEx; handle = sysHandleEx.HandleValue; @@ -111,19 +111,9 @@ internal SafeHandleEx(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleE /// public string? HandleObjectType { get; } //public bool ProcessIs64Bit { get; } // unused, for now - public string? ProcessCommandLine - { - get => processCommandLine ??= GetProcessCommandLine(ProcessId); // if null, call function and assign value - init { processCommandLine = value; } - } - public string? ProcessMainModulePath { get; } - public string? ProcessName { get; } //internal PEB_Ex? PebEx { get; } // Won't need this unless we want to start accessing otherwise unread pointer-type members of the PEB and its children (e.g. PEB_LDR_DATA, CURDIR, et cetera) - /// - /// A list of exceptions thrown by constructors and other methods of this class.
- /// Intended to explain why the process command line, main module path, and name are unavailable. - ///
+ /// A list of exceptions thrown by constructors and other methods of this class. /// Use List's methods (e.g. Add) to modify this list. public List ExceptionLog { get; } = new(); diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs new file mode 100644 index 0000000..ddd3ea7 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -0,0 +1,109 @@ +using System.Diagnostics; +using System.Text.Json.Serialization; +using Microsoft.Win32.SafeHandles; +using PInvoke; +using Windows.Win32.Foundation; +using Windows.Win32.System.Threading; +using Windows.Win32.System.WindowsProgramming; +using static PInvoke.Kernel32; +using static PInvoke.NTSTATUS.Code; +using static Windows.Win32.PInvoke; +using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; + +namespace Windows.Win32; + +/// +/// The +/// +/// SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX +/// structure is a recurring element in the +/// +/// SYSTEM_HANDLE_INFORMATION_EX +/// struct that a successful call to +/// +/// ZwQuerySystemInformation +/// or +/// +/// NtQuerySystemInformation +/// produces in its output buffer when given the information class +/// SystemExtendedHandleInformation (0x40). +/// This inline doc was supplemented by ProcessHacker's usage of this struct. +/// +public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX +{ +#pragma warning disable CS0649 + public nuint Object { get; } + /// ULONG_PTR, cast to HANDLE, int, or uint + public nuint UniqueProcessId { get; } + /// ULONG_PTR, cast to HANDLE + internal HANDLE HandleValue { get; } + /// Get the HandleValue as a SafeObjectHandle. Closing this SafeObjectHandle does *not* close the source handle. + public SafeObjectHandle GetSafeHandle() => new(HandleValue, false); + /// This is a bitwise "Flags" data type. + /// See the "Granted Access" column in the Handles section of a process properties window in ProcessHacker. + [JsonIgnore] + public ACCESS_MASK GrantedAccess { get; } // uint + /// Note: SpecificRights requires the Type of `Object` and the code definitions of that Type's access rights. + public string GrantedAccessString => $"0x{GrantedAccess.Value:X} ({GrantedAccess.SpecificRights}, {GrantedAccess.StandardRights}, {GrantedAccess.GenericRights})"; + public ushort CreatorBackTraceIndex { get; } // USHORT + /// ProcessHacker defines a little over a dozen handle-able object types. + public ushort ObjectTypeIndex { get; } // USHORT + /// + public HandleFlags HandleAttributes { get; } // uint +#pragma warning disable RCS1213, CS0169, IDE0051 // Remove unused field declaration. csharp(RCS1213) | Roslynator + private readonly uint Reserved; +#pragma warning restore RCS1213, CS0649, CS0169, IDE0051 + + /// Get the Type of the object as a string + /// P/Invoke function NtQueryObject failed. See Exception data. + /// The Type of the object as a string. + public unsafe string GetHandleObjectType() + { + try + { + NTSTATUS status; + using SafeBuffer buffer = new(numBytes: 256/* (nuint)Marshal.SizeOf() */); + uint returnLength; + using var h = new SafeObjectHandle(HandleValue, false); + + status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); + + // Something's off. Marshal.SizeOf() returns 0x68 (104) but returnLength is 0x78 (120) or sometimes 0x80 (128). Is Win32Metadata's type definition wrong? + while (status.Code is STATUS_BUFFER_OVERFLOW or STATUS_INFO_LENGTH_MISMATCH or STATUS_BUFFER_TOO_SMALL) + { + buffer.Reallocate(returnLength); + status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); + } + + if (status == STATUS_INVALID_HANDLE || !status.IsSuccessful) + throw new NTStatusException(status); + + return (string)buffer.Read(0).TypeName; + } + catch (Exception) + { + throw; + } + } + + internal unsafe HANDLE_FLAGS GetHandleInfo() + { + try + { + using SafeObjectHandle hObject = new(HandleValue, false); + return GetHandleInformation(hObject); + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + } + + // If passing the source handle failed, try passing a duplicate instead + + using SafeProcessHandle sourceProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, (uint)UniqueProcessId); + if (sourceProcess is null) throw new Win32Exception(); + using SafeObjectHandle safeHandleValue = new(HandleValue, false); + DuplicateHandle(sourceProcess, safeHandleValue, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, default, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); + return GetHandleInformation(dupHandle); + } +} From 657640255629005f86c8605c5894e299acee5ac7 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:39:08 -0700 Subject: [PATCH 154/306] refactor: add exception message refactor: remove unused ToSafeFileHandle() chore: add Methods region --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 24d9b67..76e52f0 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -117,6 +117,8 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals /// Use List's methods (e.g. Add) to modify this list. public List ExceptionLog { get; } = new(); + #region Methods + /// /// Release the system handle.
/// ! WARNING !
@@ -271,7 +273,7 @@ private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) throw new ArgumentException("The provided process handle is invalid.", paramName: nameof(hProcess)); if (!IsWow64Process(hProcess, out BOOL targetIs32BitProcess)) - throw new Win32Exception(); + throw new Win32Exception("Failed to determine target process is running under WOW: {0}"); bool weAre32BitAndTheyAre64Bit = !Environment.Is64BitProcess && !targetIs32BitProcess; bool weAre64BitAndTheyAre32Bit = Environment.Is64BitProcess && targetIs32BitProcess; @@ -515,5 +517,5 @@ protected override bool ReleaseHandle() return IsClosed; } - internal SafeHandle ToSafeFileHandle() => SysHandleEx.ToSafeFileHandle(); + #endregion Methods } From 8b43267836d1ddc36a4a72fd6f8903fd7b24e20b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:55:16 -0700 Subject: [PATCH 155/306] refactor: rework SafeHandleEx constructor, properties docs: add TODO: remove closed handles from list --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 191 +++++++++++++++------ 1 file changed, 137 insertions(+), 54 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 76e52f0..71681b7 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -13,6 +13,7 @@ namespace deadlock_dotnet_sdk.Domain; +//TODO: check if handle is closed. If true, FileLockerEx can remove this object from its locker list. See relevant TODO in FileLockerEx /// /// A SafeHandleZeroOrMinusOneIsInvalid wrapping a SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
/// Before querying for system handles, call @@ -22,7 +23,11 @@ namespace deadlock_dotnet_sdk.Domain; ///
public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { - private string? processCommandLine; + private (string? v, Exception? ex) processCommandLine; + private (string? v, Exception? ex) handleObjectType; + private (string? v, Exception? ex) processMainModulePath; + private (string? v, Exception? ex) processName; + private (bool? v, Exception? ex) processIsProtected; public SafeHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) { } @@ -36,80 +41,158 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals SysHandleEx = sysHandleEx; handle = sysHandleEx.HandleValue; - try - { - HandleObjectType = SysHandleEx.GetHandleObjectType(); - } - catch (Exception e) +#if DEBUG + // Get additional details from the handle's owner process + // WARNING: if a breakpoint is placed in a dependency's Get accessor, the dependent's Get accessor may fail. + _ = ProcessIsProtected; + List tasks = new Task[]{ + new Task(() => _ = HandleObjectType), // Get kernel object type of the handle's object + new Task(() => _ = ProcessName), // Get Process's name + new Task(() => + { + if (ProcessIsProtected.v == false) _ = ProcessMainModulePath; // Get main module's full path + }), + new Task(() => + { + if (ProcessIsProtected.v == false) _ = ProcessCommandLine; // Get process's possibly-overwritten command line from the PEB struct in its memory space + }) + }.ToList(); + foreach (var task in tasks) { task.Start(); } + //_ = Task.WhenAll(tasks); // uncomment if we need to check the tasks' results + + Console.Out.WriteLine( + value: "Handle: " + HandleValue + Console.Out.NewLine + + ToString()); +#endif + } + + internal SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX SysHandleEx { get; } + + public unsafe UIntPtr ObjectAddress => SysHandleEx.Object; + public uint ProcessId => (uint)SysHandleEx.UniqueProcessId; + public nuint HandleValue => SysHandleEx.HandleValue; + public ushort CreatorBackTraceIndex => SysHandleEx.CreatorBackTraceIndex; + /// + public ACCESS_MASK GrantedAccess => SysHandleEx.GrantedAccess; + public string GrantedAccessString => SysHandleEx.GrantedAccessString; + /// The Type of the object as a string. + public (string? v, Exception? ex) HandleObjectType + { + get { - ExceptionLog.Add(e); + if (handleObjectType == default && ProcessIsProtected != default) + { + if (ProcessIsProtected.v == false) + { + try { return handleObjectType = (SysHandleEx.GetHandleObjectType(), null); } + catch (Exception e) { return (null, e); } + } + else if (ProcessIsProtected.v == true) + { + return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; The process is protected.")); + } + else + { + return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)); + } + } + else + { + return handleObjectType; + } } + } - // Get additional details from the handle's owner process - try + //public bool ProcessIs64Bit { get; } // unused, for now + //TODO: change from nullable bool to nullable enum. Light protection allows querying the command line while Full protection does not. + public unsafe (bool? v, Exception? ex) ProcessIsProtected + { + get { - /** Open handle for process */ - // PROCESS_QUERY_LIMITED_INFORMATION is necessary for QueryFullProcessImageName - // PROCESS_QUERY_LIMITED_INFORMATION + PROCESS_VM_READ for reading PEB from the process's memory space. - // if we need to duplicate a handle later, we'll use PROCESS_DUP_HANDLE - - if (ProcessId == 0) + if (processIsProtected == default) { - ProcessName = "System Idle Process"; + const uint ProcessProtectionInformation = 61; // Retrieves a BYTE value indicating the type of protected process and the protected process signer. + PS_PROTECTION protection = default; + uint retLength = 0; + + using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, ProcessId); + NTSTATUS status = NtQueryInformationProcess(hProcess, (PROCESSINFOCLASS)ProcessProtectionInformation, &protection, 1, ref retLength); + + return status.Code == PInvoke.NTSTATUS.Code.STATUS_SUCCESS + ? (processIsProtected = (protection.Type > 0, null)) + : (processIsProtected = (null, new NTStatusException(status))); } - else if (ProcessId == 4) + else { - ProcessName = "System"; + return processIsProtected; } - else + } + } + public (string? v, Exception? ex) ProcessCommandLine => processCommandLine == default ? (processCommandLine = GetProcessCommandLine(ProcessId)) : processCommandLine; + /// + /// The full file path of the handle-owning process's main module (the executable file) or an exception if the Get operation failed. + /// + /// + /// v: If the query succeeded, the full file path of the process's main module, the executable file.
+ /// ex: If the query failed, the error encountered when attempting to query the full file path of the process's main module. + ///
+ /// If IsProtected.v is true or null, returns InvalidOperationException. The queryable details of protected processes (System, Registry, etc.) are limited.. + public (string? v, Exception? ex) ProcessMainModulePath + { + get + { + if (processMainModulePath == default && ProcessIsProtected != default) { - try - { - ProcessName = Process.GetProcessById((int)ProcessId).ProcessName; - } - catch (Exception e) + if (ProcessIsProtected.v == false) { - ExceptionLog.Add(e); + return processMainModulePath = TryGetFullProcessImageName(); } - - /** Get main module's full path */ - try + else if (ProcessIsProtected.v == true) { - ProcessMainModulePath = GetFullProcessImageName(ProcessId); + return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; The process is protected.")); } - catch (Exception e) + else // assume IsProcessProtected returned an Exception { - ExceptionLog.Add(e); + return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)); } - - /** Get Process's name */ - if (!string.IsNullOrWhiteSpace(ProcessMainModulePath)) - ProcessName = Path.GetFileNameWithoutExtension(ProcessMainModulePath); - - /** Get process's possibly-overwritten command line from the PEB struct in its memory space */ - //ProcessCommandLine = GetProcessCommandLine(ProcessId); - // moved to property's Get accessor } + return processMainModulePath; } - catch (Exception e) + } + + public (string? v, Exception? ex) ProcessName + { + get { - ExceptionLog.Add(e); + if (processName == default) + { + switch (ProcessId) + { + case 0: + processName = ("System Idle Process", null); + break; + case 4: + processName = ("System", null); + break; + default: + try + { + var proc = Process.GetProcessById((int)ProcessId); + if (proc.HasExited) + processName = (null, new InvalidOperationException("Process has exited, so the requested information is not available.")); + else processName = (Process.GetProcessById((int)ProcessId).ProcessName, null); + } + catch (Exception ex) + { + processName = (null, ex); + } + break; + } + } + return processName; } } - internal NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX SysHandleEx { get; } - - public unsafe UIntPtr Object => SysHandleEx.ObjectPointer; - public uint ProcessId => (uint)SysHandleEx.UniqueProcessId; - public nuint HandleValue => SysHandleEx.HandleValue; - public ushort CreatorBackTraceIndex => SysHandleEx.CreatorBackTraceIndex; - /// - public ACCESS_MASK GrantedAccess => SysHandleEx.GrantedAccess; - /// - /// The Type of the object as a string. - /// - /// - public string? HandleObjectType { get; } //public bool ProcessIs64Bit { get; } // unused, for now //internal PEB_Ex? PebEx { get; } // Won't need this unless we want to start accessing otherwise unread pointer-type members of the PEB and its children (e.g. PEB_LDR_DATA, CURDIR, et cetera) From 5048cdd1150c1b5517d0bdb873f9ce510280a551 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:56:23 -0700 Subject: [PATCH 156/306] refactor: rework SafeFileHandleEx constructor, properties chore: update a reference to SYSTEM_HANDLE_TABLE_ENTRY_EX --- .../Domain/SafeFileHandleEx.cs | 120 ++++++++++++++---- 1 file changed, 92 insertions(+), 28 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index ff7c32a..b3c45ae 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -3,10 +3,10 @@ using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; using PInvoke; +using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; using Windows.Win32.System.Threading; -using static deadlock_dotnet_sdk.Domain.NativeMethods; using static Windows.Win32.PInvoke; // Re: StructLayout @@ -24,44 +24,46 @@ namespace deadlock_dotnet_sdk.Domain; ///
public class SafeFileHandleEx : SafeHandleEx { + private (bool? v, Exception? ex) isFileHandle; + private (TypeOfFileHandle? v, Exception? ex) fileHandleType; + private (string? v, Exception? ex) fileFullPath; + private (string? v, Exception? ex) fileName; + private (bool? v, Exception? ex) isDirectory; + // TODO: there's gotta be a better way to cast a base class to an implementing class internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) { } - /// - /// Initialize - /// + /// Initialize /// internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx) { - try - { - IsFileHandle = SysHandleEx.IsFileHandle(); - } - catch (Exception e) - { - ExceptionLog.Add(e); - } - - if (IsFileHandle == true) + if (IsFileHandle != default && IsFileHandle.v == true) { try { - if (ProcessId == 4) + if (ProcessIsProtected.v == true) { - ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by {ProcessName} (PID {ProcessId})")); - return; + if (ProcessIsProtected != default) + { + ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by {ProcessName} (PID {ProcessId})")); + } + if (ProcessName.v is "smss") + { + ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by Windows Session Manager SubSystem ({ProcessName}, PID {ProcessId})")); + return; + } } - if (ProcessName == "smss") +#if DEBUG + _ = FileHandleType; + if (FileHandleType.v == TypeOfFileHandle.Disk) { - ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by Windows Session Manager SubSystem ({ProcessName}, PID {ProcessId})")); - return; + _ = FileFullPath; + _ = FileName; + _ = IsDirectory; } - - FileFullPath = TryGetFinalPath(); - FileName = Path.GetFileName(FileFullPath); - IsDirectory = (File.GetAttributes(FileFullPath) & FileAttributes.Directory) == FileAttributes.Directory; +#endif } catch (Exception e) { @@ -74,10 +76,72 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( } } - public string? FileFullPath { get; } - public string? FileName { get; } - public bool? IsDirectory { get; } - public bool? IsFileHandle { get; } + public (bool? v, Exception? ex) IsFileHandle => isFileHandle == default ? (isFileHandle = GetIsFileHandle()) : isFileHandle; + public (TypeOfFileHandle? v, Exception? ex) FileHandleType + { + get + { + if (fileHandleType == default) + { + try + { + return fileHandleType = ((TypeOfFileHandle?)GetFileType(handle), null); + } + catch (Exception ex) + { + return (null, ex); + } + } + else + { + return fileHandleType; + } + } + } + + public (string? v, Exception? ex) FileFullPath => fileFullPath == default ? (fileFullPath = TryGetFinalPath()) : fileFullPath; + + public (string? v, Exception? ex) FileName => fileName == default + ? FileFullPath != default && FileFullPath.v is not null + ? (fileName = (Path.GetFileName(FileFullPath.v), null)) + : (fileName = (null, new InvalidOperationException("Unable to query FileName; This operation requires FileFullPath."))) + : fileName; + + public (bool? v, Exception? ex) IsDirectory + { + get + { + if (isDirectory == default) + { + if (FileFullPath != default && FileFullPath.v != null) // The comparison *should* cause FileFullPath to initialize. + { + try + { + return isDirectory = ((File.GetAttributes(FileFullPath.v) & FileAttributes.Directory) == FileAttributes.Directory, null); + } + catch (Exception ex) + { + return (null, ex); + } + } + + return (null, new InvalidOperationException("Unable to query IsDirectory; This operation requires FileFullPath.")); + } + else + { + return isDirectory; + } + } + } + + public enum TypeOfFileHandle : uint + { + Unknown = FILE_TYPE.FILE_TYPE_UNKNOWN, + Disk = FILE_TYPE.FILE_TYPE_DISK, + Char = FILE_TYPE.FILE_TYPE_CHAR, + Pipe = FILE_TYPE.FILE_TYPE_PIPE, + Remote = FILE_TYPE.FILE_TYPE_REMOTE + } /// /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. From 8b3df17c2dd99ad585ab3aa88189a8c0ba8e1e2d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 00:59:42 -0700 Subject: [PATCH 157/306] refactor: rework TryGetFinalPath() * throw exception if a process is protected i.e. inaccessible because it's a system process * don't throw exceptions * return tuple * use using statements for disposable objects --- .../Domain/SafeFileHandleEx.cs | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index b3c45ae..d83c5fb 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -150,34 +150,38 @@ public enum TypeOfFileHandle : uint /// The path '{fullName}' was not found when querying a file handle. /// Failed to query path from file handle. Insufficient memory to complete the operation. /// Failed to query path from file handle. Invalid flags were specified for dwFlags. - private unsafe string TryGetFinalPath() + private unsafe (string? v, Exception? ex) TryGetFinalPath() { - if (ProcessId == 4) throw new InvalidOperationException("Cannot access handle object information if handle is held by System (PID 4)"); + if (ProcessIsProtected != default && ProcessIsProtected.v is true) + return (null, new InvalidOperationException("Unable to query file path or pipe name; The process is protected.")); + else if (ProcessIsProtected.v is null) + return (null, new InvalidOperationException("Unable to query file path or pipe name; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)); /// Return the normalized drive name. This is the default. - uint bufLength = (uint)short.MaxValue; - var buffer = Marshal.AllocHGlobal((int)bufLength); - PWSTR fullName = new((char*)buffer); - var processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); + using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); if (processHandle is null || processHandle?.IsInvalid == true) - throw new Win32Exception(); + return (null, new Win32Exception()); - if (!DuplicateHandle(processHandle, new SafeFileHandle((nint)HandleValue, false), Process.GetCurrentProcess().SafeHandle, out SafeFileHandle? dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) - throw new Win32Exception(); + if (!DuplicateHandle(processHandle, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) + return (null, new Win32Exception()); - uint length = GetFinalPathNameByHandle(dupHandle, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + uint bufLength = (uint)short.MaxValue; + using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufLength)); + uint length = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); if (length != 0) { - while (length > bufLength) + if (length > bufLength) { // buffer was too small. Reallocate buffer with size matched 'length' and try again - buffer = Marshal.ReAllocHGlobal(buffer, (IntPtr)length); - fullName = new((char*)buffer); - - bufLength = GetFinalPathNameByHandle(dupHandle, fullName, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); + bufLength = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + return (newBuffer.ToString(), null); + } + else + { + return (buffer.ToString(), null); } - return fullName.ToString(); } else { @@ -189,10 +193,10 @@ private unsafe string TryGetFinalPath() throw error switch { - Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{fullName}' was not found when querying a file handle.", fileName: fullName.ToString()), // Removable storage, deleted item, network shares, et cetera - Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation."), // unlikely, but possible if system has little free memory - Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags."), // possible only if FILE_NAME_NORMALIZED (0) is invalid - _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path."), + Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(error)), // Removable storage, deleted item, network shares, et cetera + Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation.", new Win32Exception(error)), // unlikely, but possible if system has little free memory + Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(error)), // possible only if FILE_NAME_NORMALIZED (0) is invalid + _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path.", new Win32Exception(error)) }; } } From 91655bc02d8361c24921c0c07a916631db9d9d9c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 28 Mar 2023 01:00:03 -0700 Subject: [PATCH 158/306] refactor: finish SafeFileHandleEx.ToString() --- .../Domain/SafeFileHandleEx.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index d83c5fb..568f71d 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -206,27 +206,27 @@ public override string ToString() string[] exLog = ExceptionLog.Cast().ToArray(); for (int i = 0; i < exLog.Length; i++) { - exLog[i] = $" {exLog[i]}".Replace("\n", "\n "); + exLog[i] = $" {exLog[i]}".Replace("\n", "\n ") + Environment.NewLine; } return @$"{GetType().Name} hash:{GetHashCode()} {nameof(CreatorBackTraceIndex)} : {CreatorBackTraceIndex} - {nameof(FileFullPath)} : {FileFullPath} - {nameof(IsDirectory)} : {IsDirectory} - {nameof(FileName)} : {FileName} - {nameof(GrantedAccess)} : {GrantedAccess} - {nameof(handle)} : {handle} - {nameof(HandleObjectType)} : {HandleObjectType} - {nameof(HandleValue)} : {HandleValue} + {nameof(FileFullPath)} : {FileFullPath.v ?? FileFullPath.ex?.ToString()} + {nameof(FileHandleType)} : {FileHandleType.v?.ToString() ?? FileFullPath.ex?.ToString()} + {nameof(FileName)} : {FileName.v ?? FileName.ex?.ToString()} + {nameof(GrantedAccess)} : {SysHandleEx.GrantedAccessString} + {nameof(HandleObjectType)} : {HandleObjectType.v ?? HandleObjectType.ex?.ToString()} + {nameof(HandleValue)} : {HandleValue} (0x{HandleValue:X}) {nameof(IsClosed)} : {IsClosed} - {nameof(IsFileHandle)} : {IsFileHandle} + {nameof(IsDirectory)} : {IsDirectory.v?.ToString() ?? IsDirectory.ex?.ToString()} + {nameof(IsFileHandle)} : {IsFileHandle.v?.ToString() ?? IsFileHandle.ex?.ToString()} {nameof(IsInvalid)} : {IsInvalid} - {nameof(Object)} : {Object} - {nameof(ProcessCommandLine)} : {ProcessCommandLine} + {nameof(ObjectAddress)} : {ObjectAddress} (0x{ObjectAddress:X}) + {nameof(ProcessCommandLine)} : {ProcessCommandLine.v ?? ProcessCommandLine.ex?.ToString()} {nameof(ProcessId)} : {ProcessId} - {nameof(ProcessMainModulePath)} : {ProcessMainModulePath} - {nameof(ProcessName)} : {ProcessName} + {nameof(ProcessMainModulePath)} : {ProcessMainModulePath.v ?? ProcessMainModulePath.ex?.ToString()} + {nameof(ProcessName)} : {ProcessName.v ?? ProcessName.ex?.ToString()} {nameof(ExceptionLog)} : ... - " + exLog; + " + string.Concat(exLog); } } From 9cd45c6a0331bf45cea08304fa9967a8feebccea Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 29 Mar 2023 18:07:31 -0700 Subject: [PATCH 159/306] refactor: remove redundant FreeHGlobal We switched from a pointer to a 'using PWSTR'. The hidden Finally statement frees memory before the exiting the method. --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 568f71d..d0b4fbe 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -188,9 +188,6 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() Win32ErrorCode error = (Win32ErrorCode)Marshal.GetLastWin32Error(); Debug.Print(error.GetMessage()); - /* Hold up. Let's free our memory before throwing exceptions. */ - Marshal.FreeHGlobal(buffer); - throw error switch { Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(error)), // Removable storage, deleted item, network shares, et cetera From b110aea36129243ec7abf5273610b1906984c02f Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 29 Mar 2023 18:08:07 -0700 Subject: [PATCH 160/306] refactor: make NativeMethods non-partial --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index f1046cb..c028512 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -25,7 +25,7 @@ namespace deadlock_dotnet_sdk.Domain; /// /// Collection of native methods /// -internal static partial class NativeMethods +internal static class NativeMethods { #region Variables From 28593c1efed4edea23f9d6969be7bc24ae0f3695 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 1 Apr 2023 19:32:28 -0700 Subject: [PATCH 161/306] refactor: rewrite FindLockingHandles' Discard() --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 49 ++++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index c028512..2944507 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -2,6 +2,7 @@ using System.Runtime.InteropServices; using deadlock_dotnet_sdk.Exceptions; using PInvoke; +using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.RestartManager; using Windows.Win32.System.WindowsProgramming; @@ -119,16 +120,14 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo /// paths contain this query string. /// /// - /// By default, this method only returns handles for objects - /// successfully identified as a file/directory ("File"). - /// and + /// By default, this method only returns handles for objects successfully identified as a File. + /// File objects' sub-type can be Directory, File, "File or Directory", Network, Other, or Pipe. /// /// - /// A list of SafeFileHandleEx objects. - /// When requested, handles for non-file or unidentified objects will be included with file-specific properties nulled. + /// A list of SafeFileHandleEx objects. When requested, handles for non-file or unidentified objects will be included with file-specific properties nulled. /// - /// SeDebugMode may be required for data from system and service processes. Restart app as admin and call . - internal static List FindLockingHandles(string? query = null, HandlesFilter filter = HandlesFilter.FilesOnly) + /// TODO: optimize process inspection. Stuff like IsProcessProtected should only be queried once per process + internal static List FindLockingHandles(string query, HandlesFilter filter = HandlesFilter.FilesOnly) { List? handles = new(); @@ -144,20 +143,36 @@ internal static List FindLockingHandles(string? query = null, bool Discard(SafeFileHandleEx h) { - if (h.HandleObjectType is not null) + bool keep = false; + + // Check filters in reverse order + if (filter.HasFlag(HandlesFilter.IncludeProtectedProcesses)) { - /* Query for object type succeeded and the type is NOT File */ - if (h.HandleObjectType != "File") - { - return !filter.HasFlag(HandlesFilter.IncludeNonFiles); // When requested, keep non-File object handle. Else, discard. - } - // Discard handle if Query and file's path are not null and file's path does not contain query */ - return (query is not null) && (h.FileFullPath?.Contains(query.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)) == false); + // if a process is protected, do not discard the handle + keep = h.ProcessIsProtected.v is true; + } + if (!keep && filter.HasFlag(HandlesFilter.IncludeNonFiles)) + { + // keep if object type query succeeded + keep = !string.IsNullOrWhiteSpace(h.HandleObjectType.v); } - else + if (!keep && filter.HasFlag(HandlesFilter.IncludeFailedTypeQuery)) { - return !filter.HasFlag(HandlesFilter.IncludeFailedTypeQuery); // When requested, keep handle if the object type query failed. Else, discard. + keep = string.IsNullOrWhiteSpace(h.HandleObjectType.v); } + if (!keep && filter is HandlesFilter.FilesOnly) + { + // only keep if handle object is 'File' + // note: File objects' sub-type can be Directory, File, "File or Directory", Network, Other, or Pipe. + keep = h.IsFileHandle.v is true; + } + if (!string.IsNullOrWhiteSpace(query)) + { + // only keep if FullFilePath contains query (with normalized directory separators) + keep = h.FileFullPath.v?.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).Contains(query.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)) is true; + } + + return !keep; } } From 64786cc64445f558788961df7213b662a099479c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 1 Apr 2023 19:35:35 -0700 Subject: [PATCH 162/306] refactor: remove old DEBUG preprocessor ops refactor: remove redundant return statement --- .../Domain/SafeFileHandleEx.cs | 11 --------- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 24 ------------------- 2 files changed, 35 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index d0b4fbe..9340436 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -51,19 +51,8 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( if (ProcessName.v is "smss") { ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by Windows Session Manager SubSystem ({ProcessName}, PID {ProcessId})")); - return; } } - -#if DEBUG - _ = FileHandleType; - if (FileHandleType.v == TypeOfFileHandle.Disk) - { - _ = FileFullPath; - _ = FileName; - _ = IsDirectory; - } -#endif } catch (Exception e) { diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 71681b7..f20fb12 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -40,30 +40,6 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { SysHandleEx = sysHandleEx; handle = sysHandleEx.HandleValue; - -#if DEBUG - // Get additional details from the handle's owner process - // WARNING: if a breakpoint is placed in a dependency's Get accessor, the dependent's Get accessor may fail. - _ = ProcessIsProtected; - List tasks = new Task[]{ - new Task(() => _ = HandleObjectType), // Get kernel object type of the handle's object - new Task(() => _ = ProcessName), // Get Process's name - new Task(() => - { - if (ProcessIsProtected.v == false) _ = ProcessMainModulePath; // Get main module's full path - }), - new Task(() => - { - if (ProcessIsProtected.v == false) _ = ProcessCommandLine; // Get process's possibly-overwritten command line from the PEB struct in its memory space - }) - }.ToList(); - foreach (var task in tasks) { task.Start(); } - //_ = Task.WhenAll(tasks); // uncomment if we need to check the tasks' results - - Console.Out.WriteLine( - value: "Handle: " + HandleValue + Console.Out.NewLine + - ToString()); -#endif } internal SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX SysHandleEx { get; } From 48d4d12334d0fce13755b1cef1f45cf8fcd9b708 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 1 Apr 2023 19:40:29 -0700 Subject: [PATCH 163/306] refactor: change PS_PROTECTION to public, add enums --- deadlock-dotnet-sdk/NativeMethods.txt | 1 + .../Windows.Win32/PS_PROTECTION.cs | 30 ++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index 634e5b3..b47af5c 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -79,3 +79,4 @@ RTL_CRITICAL_SECTION SE_DEBUG_NAME //ProcessProtectionInformation // PInvoke001: Method, type or constant "ProcessProtectionInformation" not found. //PS_PROTECTION // PInvoke001: Method, type or constant "PS_PROTECTION" not found. +//PsProtectedTypeProtected // PInvoke001: Method, type or constant "PsProtectedTypeProtected" not found diff --git a/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs index e5aa0ca..6f14688 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs @@ -2,10 +2,32 @@ namespace Windows.Win32; /// /// https://learn.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess#PS_PROTECTION /// -internal struct PS_PROTECTION +public struct PS_PROTECTION { public byte Level; - public byte Type => (byte)((Level >> 0) & 0b111); - public byte Audit => (byte)((Level >> 3) & 0b1); // Reserved - public byte Signer => (byte)((Level >> 4) & 0b1111); + public PS_PROTECTED_TYPE Type => (PS_PROTECTED_TYPE)((Level >> 0) & 0b111); + /// Reserved + public byte Audit => (byte)((Level >> 3) & 0b1); + public PS_PROTECTED_SIGNER Signer => (PS_PROTECTED_SIGNER)((Level >> 4) & 0b1111); + + public enum PS_PROTECTED_TYPE + { + PsProtectedTypeNone = 0, + PsProtectedTypeProtectedLight = 1, + PsProtectedTypeProtected = 2 + } + + public enum PS_PROTECTED_SIGNER + { + PsProtectedSignerNone = 0, + PsProtectedSignerAuthenticode, + PsProtectedSignerCodeGen, + PsProtectedSignerAntimalware, + PsProtectedSignerLsa, + PsProtectedSignerWindows, + PsProtectedSignerWinTcb, + PsProtectedSignerWinSystem, + PsProtectedSignerApp, + PsProtectedSignerMax + } } From a92efb1c5d575133600f582be386725b4988bfb6 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 2 Apr 2023 22:05:03 -0700 Subject: [PATCH 164/306] refactor: return exception to FileHandleType when IsFileHandle.v is not true --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 9340436..6ac0569 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -72,6 +72,9 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { if (fileHandleType == default) { + if (IsFileHandle.v is not true) + return (null, new InvalidOperationException("Unable to query File handle type; This operation is only valid on File handles.")); + try { return fileHandleType = ((TypeOfFileHandle?)GetFileType(handle), null); From 71ac75a930113ade40180e5a752a07c3948af642 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 2 Apr 2023 23:00:26 -0700 Subject: [PATCH 165/306] refactor: convert FileName Get to block body with if-else --- .../Domain/SafeFileHandleEx.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 6ac0569..35cd865 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -93,11 +93,27 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( public (string? v, Exception? ex) FileFullPath => fileFullPath == default ? (fileFullPath = TryGetFinalPath()) : fileFullPath; - public (string? v, Exception? ex) FileName => fileName == default - ? FileFullPath != default && FileFullPath.v is not null - ? (fileName = (Path.GetFileName(FileFullPath.v), null)) - : (fileName = (null, new InvalidOperationException("Unable to query FileName; This operation requires FileFullPath."))) - : fileName; + public (string? v, Exception? ex) FileName + { + get + { + if (fileName == default) + { + if (FileFullPath != default && FileFullPath.v is not null) + { + return fileName = (Path.GetFileName(FileFullPath.v), null); + } + else + { + return fileName = (null, new InvalidOperationException("Unable to query FileName; This operation requires FileFullPath.")); + } + } + else + { + return fileName; + } + } + } public (bool? v, Exception? ex) IsDirectory { From b86b30435531111c9be3833fcb920fb97986e681 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 00:10:27 -0700 Subject: [PATCH 166/306] refactor: remove redundant "is (not) default" checks These were intended to instantiate the tuple property before checking its items, but the runtime runs the Get accessor whenever the property or its items are referenced. --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 10 +++------- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 6 +++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 35cd865..76286ae 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -38,20 +38,16 @@ internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHand /// internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx) { - if (IsFileHandle != default && IsFileHandle.v == true) + if (IsFileHandle.v is true) { try { if (ProcessIsProtected.v == true) { - if (ProcessIsProtected != default) - { - ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by {ProcessName} (PID {ProcessId})")); - } if (ProcessName.v is "smss") - { ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by Windows Session Manager SubSystem ({ProcessName}, PID {ProcessId})")); - } + else + ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by {ProcessName} (PID {ProcessId})")); } } catch (Exception e) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index f20fb12..c1e063d 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -56,7 +56,7 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { get { - if (handleObjectType == default && ProcessIsProtected != default) + if (handleObjectType == default) { if (ProcessIsProtected.v == false) { @@ -117,7 +117,7 @@ public unsafe (bool? v, Exception? ex) ProcessIsProtected { get { - if (processMainModulePath == default && ProcessIsProtected != default) + if (processMainModulePath == default) { if (ProcessIsProtected.v == false) { @@ -221,7 +221,7 @@ public bool CloseSourceHandle() { try { - return (HandleObjectType != default && HandleObjectType.v == "File", null); + return (HandleObjectType.v == "File", null); } catch (Exception ex) { From 05ac5707d88cc875e8dd87a174c48b399adf7472 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:08:07 -0700 Subject: [PATCH 167/306] refactor: ensure all native GetProcessCommandLine exceptions are wrapped and have custom messages docs: update GetProcessCommandLine exception docs docs: update SafeHandleEx.ReleaseHandle() 'returns' docs --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 80 +++++++++++++++------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index c1e063d..77d4740 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -320,11 +320,39 @@ private static (string? v, Exception? ex) GetProcessCommandLine(uint processId) } } + /// TODO: clean up Exception. Implement custom exceptions? /// Try to get a process's command line from its PEB /// A handle to the target process with the rights PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ - /// Reading a 64-bit process's PEB from a 32-bit process (under WOW64) is not yet implemented. - /// Failed to read the process's PEB in memory. While trying to read the PEB, the operation crossed into an area of the process that is inaccessible. - /// NtQueryInformationProcess failed to query the process's 'PROCESS_BASIC_INFORMATION' + /// The provided process handle is invalid. + /// + /// IsWow64Process failed to determine if target process is running under WOW. See InnerException. + /// -OR- + /// NtQueryInformationProcess failed to get a process's command line. See InnerException. + /// -OR- + /// NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB. See InnerException. + /// -OR- + /// NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process. See InnerException. + /// -OR- + /// NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. + /// -OR- + /// NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process. See InnerException. + /// -OR- + /// NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException. + /// -OR- + /// ReadProcessMemory failed to copy another process's PEB to this process. See InnerException. + /// -OR- + /// ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. + /// -OR- + /// ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException. + /// -OR- + /// NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException. + /// -OR- + /// ReadProcessMemory failed to copy another process's PEB to this process. See InnerException. + /// -OR- + /// ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. + /// -OR- + /// ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException. + /// /// ReAllocHGlobal received a null pointer, but didn't check the error code. This is not a real OutOfMemoryException private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) { @@ -332,7 +360,7 @@ private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) throw new ArgumentException("The provided process handle is invalid.", paramName: nameof(hProcess)); if (!IsWow64Process(hProcess, out BOOL targetIs32BitProcess)) - throw new Win32Exception("Failed to determine target process is running under WOW: {0}"); + throw new Exception("Failed to determine target process is running under WOW. See InnerException.", new Win32Exception()); bool weAre32BitAndTheyAre64Bit = !Environment.Is64BitProcess && !targetIs32BitProcess; bool weAre64BitAndTheyAre32Bit = Environment.Is64BitProcess && targetIs32BitProcess; @@ -368,14 +396,6 @@ ref returnLength // the native call to LocalReAlloc (via Marshal.ReAllocHGlobal) sometimes returns a null pointer. This is a Legacy function. Why does .NET not use malloc/realloc? //pString->Buffer = new((char*)Marshal.ReAllocHGlobal((IntPtr)pString->Buffer.Value, (IntPtr)bufferLength)); safeBuffer.Reallocate(numBytes: returnLength); - - status = NtQueryInformationProcess( - hProcess, - (PROCESSINFOCLASS)ProcessCommandLineInformation, - (void*)safeBuffer.DangerousGetHandle(), - bufferLength, - ref returnLength - ); } catch (OutOfMemoryException) // ReAllocHGlobal received a null pointer, but didn't check the error code { @@ -385,12 +405,20 @@ ref returnLength //var winerr = Marshal.GetLastWin32Error(); throw; } + + status = NtQueryInformationProcess( + hProcess, + (PROCESSINFOCLASS)ProcessCommandLineInformation, + (void*)safeBuffer.DangerousGetHandle(), + bufferLength, + ref returnLength + ); } if (status.IsSuccessful) return safeBuffer.Read(0).ToStringZ() ?? string.Empty; else - throw new NTStatusException(status); + throw new Exception("NtQueryInformationProcess failed to get a process's command line. See InnerException.", new NTStatusException(status)); } else /** Read CommandLine from PEB's Process Parameters */ { @@ -425,16 +453,16 @@ All comments inside the code blocks are from either source. } else { - throw new Exception("NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB.", new NTStatusException(status)); + throw new Exception("NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); } // copy PEB if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)basicInfo.PebBaseAddress, &peb, (ulong)Marshal.SizeOf(peb), &bytesRead)).IsSuccessful) - throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process.", new NTStatusException(status)); + throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); // Copy RTL_USER_PROCESS_PARAMETERS. if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)peb.ProcessParameters, ¶meters, (ulong)Marshal.SizeOf(parameters), &bytesRead)).IsSuccessful) - throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process.", new NTStatusException(status)); + throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); using UNICODE_STRING cmdLine = new() { @@ -444,7 +472,7 @@ All comments inside the code blocks are from either source. }; if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, &bytesRead)).IsSuccessful) - throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process.", new NTStatusException(status)); + throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); return cmdLine.ToStringLength(); } @@ -483,16 +511,16 @@ All comments inside the code blocks are from either source. } else { - throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB.", new NTStatusException(status)); + throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); } // copy PEB if (!ReadProcessMemory(hProcess, (void*)basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process.", new NTStatusException(status)); + throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); // Copy RTL_USER_PROCESS_PARAMETERS. if (!ReadProcessMemory(hProcess, (void*)peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process.", new NTStatusException(status)); + throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); using UNICODE_STRING cmdLine = new() { @@ -502,7 +530,7 @@ All comments inside the code blocks are from either source. }; if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process.", new NTStatusException(status)); + throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); return cmdLine.ToStringLength(); } @@ -540,16 +568,16 @@ All comments inside the code blocks are from either source. } else { - throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB.", new NTStatusException(status)); + throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); } // copy PEB if (!ReadProcessMemory(hProcess, basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process.", new NTStatusException(status)); + throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); // Copy RTL_USER_PROCESS_PARAMETERS. if (!ReadProcessMemory(hProcess, peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process.", new NTStatusException(status)); + throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); using UNICODE_STRING cmdLine = new() { @@ -559,7 +587,7 @@ All comments inside the code blocks are from either source. }; if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process.", new NTStatusException(status)); + throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); return cmdLine.ToStringLength(); } @@ -569,7 +597,7 @@ All comments inside the code blocks are from either source. /// /// Release all resources owned by the current process that are associated with this handle. /// - /// Returns a bool indicating both and are true + /// Returns a bool indicating IsClosed is true protected override bool ReleaseHandle() { Close(); From 4a6cdf6171ae6b3c7b72d3c99de10c8ab09e9a27 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:14:22 -0700 Subject: [PATCH 168/306] chore: split, update FileLockerEx TODO Implementing RefreshList() and removing closed/invalid handles are independent tasks. --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index fd94fc9..1ba29fd 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -2,7 +2,8 @@ namespace deadlock_dotnet_sdk.Domain { - //TODO: Add RefreshList(): Check if a handle is closed. If true, remove if from Lockers. SafeHandle.IsClosed may be unreliable depending on the runtime's internal logic. It might only work as intended if the handle is managed by the current runtime. + //TODO: Add RefreshList(). This should clear Lockers and call FindLockingHandles again. + //TODO: If a handle is closed or invalid, remove if from Lockers. SafeHandle.IsClosed is unreliable—it only works on handles managed by the current process. //https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs public class FileLockerEx { From 91860f6902526ba39c497816fa8f4fcaeea2261c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:15:40 -0700 Subject: [PATCH 169/306] docs: update FileLockerEx ctor 'remarks' with Debug, Admin notes --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 1ba29fd..a1f8b3a 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -49,7 +49,7 @@ public FileLockerEx(string path, List lockers) /// Assign True to rethrow exceptions /// When not null, DeadLock failed to grant debug permissions to the current thread failed. See inner Exceptions for more information. /// DeadLock was denied debug permissions to access system, service, and admin processes. By default, Administrators are allowed this permission. Try running as Administrator. - /// If any errors occur in the context of an individual handle, + /// This constructor enables Debugger permissions for the current process. If the process is not running as admin, Debugger permissions may be denied and some functionality won't work as intended. public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions, out WarningException? warningException) { warningException = null; From 5d50cc6d655d3ec4a9ae4358eb9a7cbd1bc8587a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:21:00 -0700 Subject: [PATCH 170/306] refactor: update HandlesFilter enum with IncludeProtectedProcesses ! This may need to be update again to differentiate between different levels of process protection. This new enum value is intended to include handles from processes with Light or Full process protection. This protection limits the information that can be acquired from a process e.g. command line, per-handle details, and more. Among these details is the Name of a handle (i.e. the full file path) and the Type of a handle's object, hence the inherent inclusion of IncludeFailedTypeQuery. --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index a1f8b3a..c0e1e24 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -79,9 +79,12 @@ public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions, o [Flags] public enum HandlesFilter { + /// 'File' objects have a sub-type (Directory, File, "File or Directory", Network, Other, Pipe) FilesOnly = 0, IncludeNonFiles = 1, - IncludeFailedTypeQuery = 2 + IncludeFailedTypeQuery = 1 << 1, + /// 4 + IncludeFailedTypeQuery + IncludeProtectedProcesses = (1 << 2) + IncludeFailedTypeQuery } public void Refresh() From c07b8a1392fa471438190d801d4b3cf2c1753585 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:24:08 -0700 Subject: [PATCH 171/306] refactor: remove unused variable 'exceptionData' This was originally intended for returning a warning in addition to whatever value is returned by GetProcessCommandLine. --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 77d4740..32b2463 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -293,8 +293,6 @@ private static (string? v, Exception? ex) GetProcessCommandLine(uint processId) { if (processId == (uint)Environment.ProcessId) return (Environment.CommandLine, null); - - Exception? exceptionData = default; try { if (!IsDebugModeEnabled()) @@ -302,7 +300,7 @@ private static (string? v, Exception? ex) GetProcessCommandLine(uint processId) } catch (Exception ex) { - exceptionData = ex; // What to do with this exception? + Debug.Print(ex.ToString()); } using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, processId); From cc81b4bdea45b6cb2e458b4962d62d206469a04a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:25:02 -0700 Subject: [PATCH 172/306] refactor: prefix 'safe' GetProcessCommandLine with 'Try' --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 32b2463..a93f22e 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -289,7 +289,7 @@ private unsafe static string GetFullProcessImageName(uint processId) } } - private static (string? v, Exception? ex) GetProcessCommandLine(uint processId) + private static (string? v, Exception? ex) TryGetProcessCommandLine(uint processId) { if (processId == (uint)Environment.ProcessId) return (Environment.CommandLine, null); From d8870cdead6a64d021b7b4ac0927bc64f632b0de Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:25:58 -0700 Subject: [PATCH 173/306] refactor: add return statements to expedite ProcessName Get accessor --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index a93f22e..cd24dfe 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -145,27 +145,27 @@ public unsafe (bool? v, Exception? ex) ProcessIsProtected switch (ProcessId) { case 0: - processName = ("System Idle Process", null); - break; + return processName = ("System Idle Process", null); case 4: - processName = ("System", null); - break; + return processName = ("System", null); default: try { var proc = Process.GetProcessById((int)ProcessId); if (proc.HasExited) - processName = (null, new InvalidOperationException("Process has exited, so the requested information is not available.")); - else processName = (Process.GetProcessById((int)ProcessId).ProcessName, null); + return processName = (null, new InvalidOperationException("Process has exited, so the requested information is not available.")); + else return processName = (Process.GetProcessById((int)ProcessId).ProcessName, null); } catch (Exception ex) { - processName = (null, ex); + return processName = (null, ex); } - break; } } - return processName; + else + { + return processName; + } } } From 62f6ca145363c29f2491487f69746767fa718079 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:29:08 -0700 Subject: [PATCH 174/306] refactor: convert ProcessMainModulePath body to expression --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 26 +++++----------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index cd24dfe..69c7dec 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -113,28 +113,14 @@ public unsafe (bool? v, Exception? ex) ProcessIsProtected /// ex: If the query failed, the error encountered when attempting to query the full file path of the process's main module. /// /// If IsProtected.v is true or null, returns InvalidOperationException. The queryable details of protected processes (System, Registry, etc.) are limited.. - public (string? v, Exception? ex) ProcessMainModulePath - { - get - { - if (processMainModulePath == default) - { - if (ProcessIsProtected.v == false) + public (string? v, Exception? ex) ProcessMainModulePath => processMainModulePath == default + ? ProcessIsProtected.v switch { - return processMainModulePath = TryGetFullProcessImageName(); + false => processMainModulePath = TryGetFullProcessImageName(), + true => processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; The process is protected.")), + _ => processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)), } - else if (ProcessIsProtected.v == true) - { - return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; The process is protected.")); - } - else // assume IsProcessProtected returned an Exception - { - return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)); - } - } - return processMainModulePath; - } - } + : processMainModulePath; public (string? v, Exception? ex) ProcessName { From 7bc09722d9288a80be88211617e2e442f7df6b06 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:30:18 -0700 Subject: [PATCH 175/306] refactor: simplify reference to NTSTATUS.Code enum --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 69c7dec..a55042a 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -94,7 +94,7 @@ public unsafe (bool? v, Exception? ex) ProcessIsProtected using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, ProcessId); NTSTATUS status = NtQueryInformationProcess(hProcess, (PROCESSINFOCLASS)ProcessProtectionInformation, &protection, 1, ref retLength); - return status.Code == PInvoke.NTSTATUS.Code.STATUS_SUCCESS + return status.Code == Code.STATUS_SUCCESS ? (processIsProtected = (protection.Type > 0, null)) : (processIsProtected = (null, new NTStatusException(status))); } From 06f05dd67c197967b84fd51a5283a013cf208cb2 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:34:24 -0700 Subject: [PATCH 176/306] refactor: convert HandleObject if-else to switch statement --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index a55042a..4b995c0 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -58,18 +58,17 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { if (handleObjectType == default) { - if (ProcessIsProtected.v == false) + switch (ProcessIsProtected.v) { - try { return handleObjectType = (SysHandleEx.GetHandleObjectType(), null); } - catch (Exception e) { return (null, e); } - } - else if (ProcessIsProtected.v == true) - { - return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; The process is protected.")); - } - else - { - return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)); + case false: + { + try { return handleObjectType = (SysHandleEx.GetHandleObjectType(), null); } + catch (Exception e) { return (null, e); } + } + case true: + return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; The process is protected.")); + default: + return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)); } } else From fdb3a77caa30140109196e26ee240da2cf14a405 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:51:34 -0700 Subject: [PATCH 177/306] refactor: add SafeHandleEx.ProcessProtection property The CommandLine cannot be queried from Full protection processes, but it *can* be queried from Light protection processes via the Win8.1+ code path. Because the PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone, null); + } + else + { + return processIsProtected = (null, new Exception("ProcessProtection query failed.", ProcessProtection.ex)); + } + } + else + { + return processIsProtected; + } + } + } + public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection + { + get + { + if (processProtection == default) { const uint ProcessProtectionInformation = 61; // Retrieves a BYTE value indicating the type of protected process and the protected process signer. PS_PROTECTION protection = default; @@ -93,17 +115,18 @@ public unsafe (bool? v, Exception? ex) ProcessIsProtected using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, ProcessId); NTSTATUS status = NtQueryInformationProcess(hProcess, (PROCESSINFOCLASS)ProcessProtectionInformation, &protection, 1, ref retLength); - return status.Code == Code.STATUS_SUCCESS - ? (processIsProtected = (protection.Type > 0, null)) - : (processIsProtected = (null, new NTStatusException(status))); + if (status.Code is not Code.STATUS_SUCCESS) + return (null, new NTStatusException(status)); + else + return processProtection = (protection, null); } else { - return processIsProtected; + return processProtection; } } } - public (string? v, Exception? ex) ProcessCommandLine => processCommandLine == default ? (processCommandLine = GetProcessCommandLine(ProcessId)) : processCommandLine; + public (string? v, Exception? ex) ProcessCommandLine => processCommandLine == default ? (processCommandLine = TryGetProcessCommandLine(ProcessId)) : processCommandLine; /// /// The full file path of the handle-owning process's main module (the executable file) or an exception if the Get operation failed. /// From 0750c40b436703dc3d4da3d9281b029f32ba113a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 20:52:54 -0700 Subject: [PATCH 178/306] refactor: convert ProcessIsProtected body to expression --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 27 ++++------------------ 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index a850467..9f47659 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -80,28 +80,11 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals } //public bool ProcessIs64Bit { get; } // unused, for now - //TODO: change from nullable bool to nullable enum. Light protection allows querying the command line while Full protection does not. - public unsafe (bool? v, Exception? ex) ProcessIsProtected - { - get - { - if (processIsProtected == default) - { - if (ProcessProtection.v is not null) - { - return processIsProtected = (ProcessProtection.v.Value.Type > PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone, null); - } - else - { - return processIsProtected = (null, new Exception("ProcessProtection query failed.", ProcessProtection.ex)); - } - } - else - { - return processIsProtected; - } - } - } + public unsafe (bool? v, Exception? ex) ProcessIsProtected => processIsProtected == default + ? ProcessProtection.v is not null + ? (processIsProtected = (ProcessProtection.v.Value.Type > PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone, null)) + : (processIsProtected = (null, new Exception("ProcessProtection query failed.", ProcessProtection.ex))) + : processIsProtected; public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { get From ea8e997759a5f015ecfd515ebbcf74134da8f552 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 21:19:39 -0700 Subject: [PATCH 179/306] refactor: check process protection for command line accessibility refactor: add using static PS_PROTECTED_TYPE for simpler access --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 9f47659..92d37db 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -6,6 +6,7 @@ using Windows.Win32.Foundation; using Windows.Win32.System.Threading; using static Windows.Win32.PInvoke; +using static Windows.Win32.PS_PROTECTION.PS_PROTECTED_TYPE; using ACCESS_MASK = PInvoke.Kernel32.ACCESS_MASK; using Code = PInvoke.NTSTATUS.Code; using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; @@ -82,7 +83,7 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals //public bool ProcessIs64Bit { get; } // unused, for now public unsafe (bool? v, Exception? ex) ProcessIsProtected => processIsProtected == default ? ProcessProtection.v is not null - ? (processIsProtected = (ProcessProtection.v.Value.Type > PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone, null)) + ? (processIsProtected = (ProcessProtection.v.Value.Type > PsProtectedTypeNone, null)) : (processIsProtected = (null, new Exception("ProcessProtection query failed.", ProcessProtection.ex))) : processIsProtected; public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection @@ -109,7 +110,25 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection } } } - public (string? v, Exception? ex) ProcessCommandLine => processCommandLine == default ? (processCommandLine = TryGetProcessCommandLine(ProcessId)) : processCommandLine; + public (string? v, Exception? ex) ProcessCommandLine + { + get + { + if (processCommandLine == default) + { + return ProcessProtection.v?.Type switch + { + PsProtectedTypeNone or PsProtectedTypeProtectedLight => processCommandLine = TryGetProcessCommandLine(ProcessId), + not PsProtectedTypeProtected => processCommandLine = (null, new Exception("ProcessCommandLine cannot be queried or copied; the process's Protection level prevents access to the process's command line.")), + _ => processCommandLine = (null, new Exception("ProcessCommandLine cannot be queried or copied")) + }; + } + else + { + return processCommandLine; + } + } + } /// /// The full file path of the handle-owning process's main module (the executable file) or an exception if the Get operation failed. /// From a0dd6d6a76f9cf7b26f0bccb88fb1a8345f79b6c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 21:20:27 -0700 Subject: [PATCH 180/306] style: change file encoding to UTF-8 with BOM --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index c0e1e24..73baa2a 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace deadlock_dotnet_sdk.Domain { From 7831d99d5258f65601ee3fc6421637cfca8a01e3 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 21:28:01 -0700 Subject: [PATCH 181/306] revert: remove try-catch from GetHandleObjectType Partially reverts 894e6792 "refactor: replace PhGetObjectTypeName with custom solution" --- .../SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index ddd3ea7..7e13ff2 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -55,35 +55,26 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX #pragma warning restore RCS1213, CS0649, CS0169, IDE0051 /// Get the Type of the object as a string - /// P/Invoke function NtQueryObject failed. See Exception data. /// The Type of the object as a string. public unsafe string GetHandleObjectType() { - try - { - NTSTATUS status; - using SafeBuffer buffer = new(numBytes: 256/* (nuint)Marshal.SizeOf() */); - uint returnLength; - using var h = new SafeObjectHandle(HandleValue, false); - - status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); + NTSTATUS status; + using SafeBuffer buffer = new(numBytes: 256/* (nuint)Marshal.SizeOf() */); + uint returnLength; + using var h = new SafeObjectHandle(HandleValue, false); - // Something's off. Marshal.SizeOf() returns 0x68 (104) but returnLength is 0x78 (120) or sometimes 0x80 (128). Is Win32Metadata's type definition wrong? - while (status.Code is STATUS_BUFFER_OVERFLOW or STATUS_INFO_LENGTH_MISMATCH or STATUS_BUFFER_TOO_SMALL) - { - buffer.Reallocate(returnLength); - status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); - } + status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); - if (status == STATUS_INVALID_HANDLE || !status.IsSuccessful) - throw new NTStatusException(status); - - return (string)buffer.Read(0).TypeName; - } - catch (Exception) + // Something's off. Marshal.SizeOf() returns 0x68 (104) but returnLength is 0x78 (120) or sometimes 0x80 (128). Is Win32Metadata's type definition wrong? + while (status.Code is STATUS_BUFFER_OVERFLOW or STATUS_INFO_LENGTH_MISMATCH or STATUS_BUFFER_TOO_SMALL) { - throw; + buffer.Reallocate(returnLength); + status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); } + + return status.IsSuccessful + ? (string)buffer.Read(0).TypeName + : throw new NTStatusException(status); } internal unsafe HANDLE_FLAGS GetHandleInfo() From 1e5c1b0280a32077d22ba969657a9ce2db9c5f91 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 21:32:01 -0700 Subject: [PATCH 182/306] refactor: wrap the entirety of TryGetFinalPath in try-catch refactor: try getting file object's path/name without duplicating the handle; try duplicating if this fails refactor: throw InvalidOperationException for more TryGetFinalPath scenarios docs: update TryGetFinalPath exception docs, remarks --- .../Domain/SafeFileHandleEx.cs | 124 +++++++++++++----- 1 file changed, 89 insertions(+), 35 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 76286ae..e58a09d 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -151,54 +151,108 @@ public enum TypeOfFileHandle : uint /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. /// /// If successful, returns a path string formatted as 'X:\dir\file.ext' or 'X:\dir' - /// The path '{fullName}' was not found when querying a file handle. - /// Failed to query path from file handle. Insufficient memory to complete the operation. - /// Failed to query path from file handle. Invalid flags were specified for dwFlags. + /// GetFinalPathNameByHandle will sometimes hang will querying the Name of a Pipe. private unsafe (string? v, Exception? ex) TryGetFinalPath() { - if (ProcessIsProtected != default && ProcessIsProtected.v is true) - return (null, new InvalidOperationException("Unable to query file path or pipe name; The process is protected.")); - else if (ProcessIsProtected.v is null) - return (null, new InvalidOperationException("Unable to query file path or pipe name; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)); + try + { + if (ProcessIsProtected.v is true) + throw new InvalidOperationException("Unable to query file/network path or pipe name; The process is protected."); + if (ProcessIsProtected.v is null) + throw new InvalidOperationException("Unable to query file/network path or pipe name; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex); + if (IsFileHandle.v is false) + throw new InvalidOperationException("Unable to query file/network path or pipe name; The handle object is not a File."); + if (HandleObjectType.v is null) + throw new InvalidOperationException("Unable to query file/network path or pipe name; Unable to query handle object type." + Environment.NewLine + HandleObjectType.ex); - /// Return the normalized drive name. This is the default. - using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); - if (processHandle is null || processHandle?.IsInvalid == true) - return (null, new Win32Exception()); + uint bufLength = (uint)short.MaxValue; + using PWSTR getLengthBuffer = new((char*)Marshal.AllocHGlobal(1)); + using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufLength)); + uint length = 0; - if (!DuplicateHandle(processHandle, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) - return (null, new Win32Exception()); + // Try without duplicating. If it fails, try duplicating the handle. + try + { + const int timeout = 5000; + Task taskGetLength = new(() => GetFinalPathNameByHandle(handle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED)); + if (Task.WhenAny(taskGetLength, Task.Delay(timeout)).Result == taskGetLength) + length = taskGetLength.Result; + else + throw new TimeoutException($"GetFinalPathNameHandle did not complete in {timeout}ms."); - uint bufLength = (uint)short.MaxValue; - using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufLength)); - uint length = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + if (length is 0) + throw new Win32Exception(); - if (length != 0) - { - if (length > bufLength) + if (length <= bufLength) + { + return (buffer.ToString(), null); + } + else + { + using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); + Task taskGetPath = new(() => GetFinalPathNameByHandle(handle, newBuffer, length, FILE_NAME.FILE_NAME_NORMALIZED)); + if (Task.WhenAny(taskGetPath, Task.Delay(timeout)).Result == taskGetPath) + { + if (taskGetPath.Result is not 0) + return (newBuffer.ToString(), null); + else + throw new Win32Exception(); + } + else + { + throw new TimeoutException($"GetFinalPathNameHandle did not complete in {timeout}ms."); + } + } + } + catch (Exception ex) { - // buffer was too small. Reallocate buffer with size matched 'length' and try again - using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - bufLength = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); - return (newBuffer.ToString(), null); + _ = ex; + } + + /// Return the normalized drive name. This is the default. + using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); + if (processHandle is null || processHandle?.IsInvalid == true) + throw new Win32Exception(); + + if (!DuplicateHandle(processHandle, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) + throw new Win32Exception(); + + length = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + + if (length != 0) + { + if (length > bufLength) + { + // buffer was too small. Reallocate buffer with size matched 'length' and try again + using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); + bufLength = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + return (newBuffer.ToString(), null); + } + else + { + return (buffer.ToString(), null); + } } else { - return (buffer.ToString(), null); + Win32ErrorCode error = (Win32ErrorCode)Marshal.GetLastWin32Error(); + Debug.Print(error.GetMessage()); + + throw error switch + { + // Removable storage, deleted item, network shares, et cetera + Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(error)), + // unlikely, but possible if system has little free memory + Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation.", new Win32Exception(error)), + // possible only if FILE_NAME_NORMALIZED (0) is invalid + Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(error)), + _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path.", new Win32Exception(error)) + }; } } - else + catch (Exception ex) { - Win32ErrorCode error = (Win32ErrorCode)Marshal.GetLastWin32Error(); - Debug.Print(error.GetMessage()); - - throw error switch - { - Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(error)), // Removable storage, deleted item, network shares, et cetera - Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation.", new Win32Exception(error)), // unlikely, but possible if system has little free memory - Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(error)), // possible only if FILE_NAME_NORMALIZED (0) is invalid - _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path.", new Win32Exception(error)) - }; + return (null, ex); } } From a07d7b500c950f383baba5ae7a2a7ab2280f887a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 3 Apr 2023 21:32:40 -0700 Subject: [PATCH 183/306] refactor: implement IEquatable for PS_PROTECTION --- deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs index 6f14688..890069e 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs @@ -2,7 +2,7 @@ namespace Windows.Win32; /// /// https://learn.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess#PS_PROTECTION /// -public struct PS_PROTECTION +public struct PS_PROTECTION : IEquatable { public byte Level; public PS_PROTECTED_TYPE Type => (PS_PROTECTED_TYPE)((Level >> 0) & 0b111); @@ -10,6 +10,8 @@ public struct PS_PROTECTION public byte Audit => (byte)((Level >> 3) & 0b1); public PS_PROTECTED_SIGNER Signer => (PS_PROTECTED_SIGNER)((Level >> 4) & 0b1111); + public bool Equals(PS_PROTECTION other) => Level == other.Level; + public enum PS_PROTECTED_TYPE { PsProtectedTypeNone = 0, @@ -30,4 +32,10 @@ public enum PS_PROTECTED_SIGNER PsProtectedSignerApp, PsProtectedSignerMax } + + public override bool Equals(object? obj) => obj is PS_PROTECTION pS_PROTECTION && Equals(pS_PROTECTION); + + public static bool operator ==(PS_PROTECTION left, PS_PROTECTION right) => left.Equals(right); + + public static bool operator !=(PS_PROTECTION left, PS_PROTECTION right) => !(left == right); } From 306e842226d6d608764c94377bd3c93761e3354d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sat, 8 Apr 2023 23:54:16 -0700 Subject: [PATCH 184/306] refactor: change which FileLockerEx ctor is used by FindLockingHandlesAsync --- deadlock-dotnet-sdk/DeadLock.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index bb54bf4..6cc5ea4 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics; using deadlock_dotnet_sdk.Domain; using HandlesFilter = deadlock_dotnet_sdk.Domain.FileLockerEx.HandlesFilter; @@ -268,11 +268,7 @@ public static async Task FindLockingHandlesAsync(string filePath, { FileLockerEx fileLocker = new(); - await Task.Run(() => - { - fileLocker = new(filePath, - NativeMethods.FindLockingHandles(filePath, filter)); - }); + await Task.Run(() => fileLocker = new(filePath, filter, false, out _)); return fileLocker; } @@ -282,6 +278,7 @@ await Task.Run(() => ///
/// The full or partial paths of files and/or directories /// The List of objects that contain the handles that are locking a file or directory + /// To monitor for a WarningException–an exception signifying reduced functionality–add a trace listener via System.Diagnostics.Trace.Listeners.Add(TraceListener) public static async Task> FindLockingHandlesAsync(HandlesFilter filter = HandlesFilter.FilesOnly, params string[] filePaths) { List fileLockers = new(); @@ -290,8 +287,9 @@ await Task.Run(() => { foreach (string filePath in filePaths) { - fileLockers.Add(new FileLockerEx(filePath, - NativeMethods.FindLockingHandles(filePath, filter))); + fileLockers.Add(new FileLockerEx(filePath, filter, false, out var warning)); + if (warning is not null) + Trace.TraceWarning(warning.ToString()); } }); From d4c3247c3000f05f4c6b184c9fae577f495f7b9b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:37:14 -0700 Subject: [PATCH 185/306] refactor: add SafeFileHandleEx.FileNameInfo refactor: add query GetFileInformationByHandleEx, FILE_NAME_INFO refactor: rename TypeOfFileHandle to FileType refactor: inline GetIsFileHandle() --- .../Domain/SafeFileHandleEx.cs | 95 +++++++++++++++++-- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 16 ---- deadlock-dotnet-sdk/NativeMethods.txt | 3 + .../OBJECT_NAME_INFORMATION.cs | 10 ++ 4 files changed, 99 insertions(+), 25 deletions(-) create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index e58a09d..c4df926 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -25,7 +25,8 @@ namespace deadlock_dotnet_sdk.Domain; public class SafeFileHandleEx : SafeHandleEx { private (bool? v, Exception? ex) isFileHandle; - private (TypeOfFileHandle? v, Exception? ex) fileHandleType; + private (FileType? v, Exception? ex) fileHandleType; + private (string? v, Exception? ex) fileNameInfo; private (string? v, Exception? ex) fileFullPath; private (string? v, Exception? ex) fileName; private (bool? v, Exception? ex) isDirectory; @@ -61,8 +62,13 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( } } - public (bool? v, Exception? ex) IsFileHandle => isFileHandle == default ? (isFileHandle = GetIsFileHandle()) : isFileHandle; - public (TypeOfFileHandle? v, Exception? ex) FileHandleType + public (bool? v, Exception? ex) IsFileHandle => isFileHandle == default + ? HandleObjectType.v is "File" + ? (isFileHandle = (true, null)) + : (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex))) + : isFileHandle; + + public (FileType? v, Exception? ex) FileHandleType { get { @@ -71,31 +77,102 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( if (IsFileHandle.v is not true) return (null, new InvalidOperationException("Unable to query File handle type; This operation is only valid on File handles.")); + FileType type = (FileType)GetFileType(handle); + var err = new Win32Exception(); + return err.ErrorCode is 0 /* success */ + ? fileHandleType = (type, null) + : fileHandleType = (null, err); + } + else + { + return fileHandleType; + } + } + } + + public unsafe (string? v, Exception? ex) FileNameInfo + { + get + { + if (fileNameInfo == default) + { + if (FileHandleType.v is not FileType.Disk) + return (null, new InvalidOperationException("FileNameInfo can only be queried for disk-type file handles.")); + //if (ProcessProtection.ex is not null) + //if (ProcessProtection.v?.Value.Type ) + + /* Get fni.FileNameLength */ + FILE_NAME_INFO fni = default; + int fniSize = Marshal.SizeOf(fni); + int bufferLength = default; + + using CancellationTokenSource cancellationTokenSource = new(50); + Task taskGetInfo = new(() => + { + FILE_NAME_INFO tmp = default; + _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &tmp, (uint)Marshal.SizeOf(fni)); + return tmp; + }, cancellationTokenSource.Token); + + const int taskTimedOut = -1; try { - return fileHandleType = ((TypeOfFileHandle?)GetFileType(handle), null); + if (Task.WaitAny(new Task[] { taskGetInfo }, 50) is taskTimedOut) + { + return (null, new TimeoutException("GetFileInformationByHandleEx did not respond within 50ms.")); + } + else + { + bufferLength = (int)(taskGetInfo.Result.FileNameLength + fniSize); + } } - catch (Exception ex) + catch (AggregateException ae) { - return (null, ex); + foreach (Exception e in ae.InnerExceptions) + { + if (e is TaskCanceledException) + return (null, e); + } + } + + /* Get FileNameInfo */ + FILE_NAME_INFO* buffer = (FILE_NAME_INFO*)Marshal.AllocHGlobal(bufferLength); + using SafeBuffer safeBuffer = new(numBytes: (nuint)bufferLength); + + if (GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, buffer, (uint)bufferLength)) + { + UNICODE_STRING str = new() + { + Buffer = new PWSTR((char*)safeBuffer.DangerousGetHandle()), + Length = (ushort)fni.FileNameLength, + MaximumLength = (ushort)bufferLength + }; + + /* The string conversion copies the data to a new string in the managed heap before freeing safeBuffer and leaving this context. */ + return fileNameInfo = ((string)str, null); + } + else + { + return (null, new Exception("Failed to query FileNameInfo; GetFileInformationByHandleEx encountered an error.", new Win32Exception())); } } else { - return fileHandleType; + return fileNameInfo; } } } public (string? v, Exception? ex) FileFullPath => fileFullPath == default ? (fileFullPath = TryGetFinalPath()) : fileFullPath; + // TODO: leverage GetFileInformationByHandleEx public (string? v, Exception? ex) FileName { get { if (fileName == default) { - if (FileFullPath != default && FileFullPath.v is not null) + if (FileFullPath.v is not null) { return fileName = (Path.GetFileName(FileFullPath.v), null); } @@ -138,7 +215,7 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( } } - public enum TypeOfFileHandle : uint + public enum FileType : uint { Unknown = FILE_TYPE.FILE_TYPE_UNKNOWN, Disk = FILE_TYPE.FILE_TYPE_DISK, diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 92d37db..381dfd3 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -223,22 +223,6 @@ public bool CloseSourceHandle() } } - /// Invokes and checks if the result is "File". - /// True if the handle is for a file or directory. - /// Based on source of C/C++ projects Hijack File Handle and Handle Monitor - /// Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. InnerException Message: - public (bool? v, Exception? ex) GetIsFileHandle() - { - try - { - return (HandleObjectType.v == "File", null); - } - catch (Exception ex) - { - return (null, new Exception($"Failed to determine if this handle's object is a file/directory. Error when calling NtQueryObject. InnerException Message: {ex.Message}", ex)); - } - } - private (string? v, Exception? ex) TryGetFullProcessImageName() { try diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index b47af5c..dbb586b 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -80,3 +80,6 @@ SE_DEBUG_NAME //ProcessProtectionInformation // PInvoke001: Method, type or constant "ProcessProtectionInformation" not found. //PS_PROTECTION // PInvoke001: Method, type or constant "PS_PROTECTION" not found. //PsProtectedTypeProtected // PInvoke001: Method, type or constant "PsProtectedTypeProtected" not found +GetFileInformationByHandleEx +FILE_NAME_INFO + diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs new file mode 100644 index 0000000..a05d80c --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs @@ -0,0 +1,10 @@ +using Windows.Win32.Foundation; + +namespace Windows.Win32.System.WindowsProgramming; + +public struct OBJECT_NAME_INFORMATION +{ + internal UNICODE_STRING Name; + + public string NameAsString => Name.ToStringLength(); +} From c749fe3bab249fe74c8590d37e9b45c057120440 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:24:33 -0700 Subject: [PATCH 186/306] refactor: remove silly, unused explicit operator from OBJECT_TYPES_INFORMATION --- .../System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs index 769c29e..b7e7dd3 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs @@ -19,7 +19,4 @@ public unsafe OBJECT_TYPE_INFORMATION[] TypeInformation return new ReadOnlySpan(p, (int)NumberOfTypes).ToArray(); } } - - public static explicit operator uint(OBJECT_TYPES_INFORMATION v) => v.NumberOfTypes; - } From d6f3e2cac1a8855a8c6d198b767c642ad47cf495 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:25:19 -0700 Subject: [PATCH 187/306] docs: update docs for some OBJECT_INFORMATION_CLASS enums --- .../System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs index 05cbd8a..a3ccd74 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs @@ -10,11 +10,11 @@ enum OBJECT_INFORMATION_CLASS /// A PUBLIC_OBJECT_BASIC_INFORMATION structure is supplied. ///
ObjectBasicInformation = 0, - /// Microsoft documents `1` as ObjectTypeInformation instead of ObjectNameInformation, despite it being `2` in winternl.h for Windows 10.0.22000.0 + /// An structure is supplied. ObjectNameInformation = 1, - /// A PUBLIC_OBJECT_TYPE_INFORMATION structure is supplied. + /// A structure is supplied. ObjectTypeInformation = 2, - /// 3.50 and higher + /// 3.50 and higher; A structure is supplied. ObjectTypesInformation = 3, /// 3.50 and higher ObjectHandleFlagInformation = 4, From ea2fb9fa10920df73fbb5830dcc83c523b133d77 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:25:38 -0700 Subject: [PATCH 188/306] docs: add missing exception doc for GetHandleObjectType() --- .../Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 7e13ff2..852c991 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -56,6 +56,7 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX /// Get the Type of the object as a string /// The Type of the object as a string. + /// NtQueryObject failed. See InnerException. public unsafe string GetHandleObjectType() { NTSTATUS status; From 26c04bc80f0dac24ef09024468e3f5e8038015c2 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:26:14 -0700 Subject: [PATCH 189/306] docs: add description for typeparam T of SafeBuffer --- deadlock-dotnet-sdk/Windows.Win32/SafeBufferT.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/SafeBufferT.cs b/deadlock-dotnet-sdk/Windows.Win32/SafeBufferT.cs index 2eef1b2..c18b9e6 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SafeBufferT.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SafeBufferT.cs @@ -4,7 +4,7 @@ namespace Windows.Win32; /// /// https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeBuffer.cs /// -/// +/// The first or top-most object type in the buffer. public class SafeBuffer : SafeBuffer where T : unmanaged { public SafeBuffer(nuint numBytes) : base(true) From 21d60488be379c68f08651896e9a6575fc838e17 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:27:08 -0700 Subject: [PATCH 190/306] refactor: inline TryGetFullProcessImageName ...but not GetFullProcessImageName. That would be a bit too cluttered for my preferences. --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 59 ++++++++++++++-------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 381dfd3..6e9dd5c 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -137,14 +137,45 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection /// ex: If the query failed, the error encountered when attempting to query the full file path of the process's main module. /// /// If IsProtected.v is true or null, returns InvalidOperationException. The queryable details of protected processes (System, Registry, etc.) are limited.. - public (string? v, Exception? ex) ProcessMainModulePath => processMainModulePath == default - ? ProcessIsProtected.v switch + public (string? v, Exception? ex) ProcessMainModulePath + { + get + { + if (processMainModulePath == default) + { + if (ProcessProtection.v is not null) { - false => processMainModulePath = TryGetFullProcessImageName(), - true => processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; The process is protected.")), - _ => processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)), + if (ProcessProtection.v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) + { + try + { + return processMainModulePath = (GetFullProcessImageName(ProcessId), null); + } + catch (Win32Exception ex) when (ex.ErrorCode == 31) + { + return processMainModulePath = (null, new InvalidOperationException("Process has exited, so the requested information is not available.", ex)); + } + catch (Exception ex) + { + return processMainModulePath = (null, ex); + } + } + else + { + return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; The process is protected.")); + } + } + else + { + return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Unable to query the process's protection:" + Environment.NewLine + ProcessProtection.ex)); } - : processMainModulePath; + } + else + { + return processMainModulePath; + } + } + } public (string? v, Exception? ex) ProcessName { @@ -223,22 +254,6 @@ public bool CloseSourceHandle() } } - private (string? v, Exception? ex) TryGetFullProcessImageName() - { - try - { - return (GetFullProcessImageName(ProcessId), null); - } - catch (Win32Exception ex) when (ex.ErrorCode == 31) - { - return (null, new InvalidOperationException("Process has exited, so the requested information is not available.", ex)); - } - catch (Exception ex) - { - return (null, ex); - } - } - /// /// A wrapper for QueryFullProcessImageName, a system function that circumvents 32-bit process limitations when permitted the PROCESS_QUERY_LIMITED_INFORMATION right. /// From 57d0b382cc038784fd6061398d6939b92b9650cb Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:27:52 -0700 Subject: [PATCH 191/306] docs: add UnauthorizedAccessException info for GetFullProcessImageName --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 6e9dd5c..aae69dd 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -261,6 +261,7 @@ public bool CloseSourceHandle() /// The path to the executable image. /// The process handle is invalid /// QueryFullProcessImageName failed. See Exception message for details. + /// Failed to open process handle for processId; private unsafe static string GetFullProcessImageName(uint processId) { uint size = 260 + 1; From 931038eb968785eae14f4adf9404632c0fae6529 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:29:51 -0700 Subject: [PATCH 192/306] fix: prevent redundant unsuccessful Get ProcessProtection calls --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index aae69dd..f3288bf 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -100,7 +100,7 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection NTSTATUS status = NtQueryInformationProcess(hProcess, (PROCESSINFOCLASS)ProcessProtectionInformation, &protection, 1, ref retLength); if (status.Code is not Code.STATUS_SUCCESS) - return (null, new NTStatusException(status)); + return processProtection = (null, new NTStatusException(status)); else return processProtection = (protection, null); } From 1f84d0cc30ac6f37461ab4dd8cd2222f34471702 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:33:30 -0700 Subject: [PATCH 193/306] refactor: add SafeHandle.ObjectName property This will *hopefully* be less problematic than obtaining a "file" handle's name or path. The possibility of redundancy is acceptable. FindLockingHandles will be updated in a later commit with the addition of ProcessInfo. --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index f3288bf..9811624 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -26,6 +26,7 @@ public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { private (string? v, Exception? ex) processCommandLine; private (string? v, Exception? ex) handleObjectType; + private (string? v, Exception? ex) objectName; private (string? v, Exception? ex) processMainModulePath; private (string? v, Exception? ex) processName; private (bool? v, Exception? ex) processIsProtected; @@ -80,6 +81,43 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals } } + public unsafe (string? v, Exception? ex) ObjectName + { + get + { + if (objectName == default) + { + // I'm assuming process protection prohibits access. I've not tested it. + // This information is not queryable in SystemInformer when a process has Full protection. + if (ProcessProtection.v is null) + return objectName = (null, new UnauthorizedAccessException("Unable to query ObjectName; Failed to query process's protection level.", processProtection.ex)); + else if (ProcessProtection.v.Value.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) + return objectName = (null, new UnauthorizedAccessException("Unable to query ObjectName; The process's protection type prohibits access.")); + + uint bufferLength = 1024u; + using SafeBuffer buffer = new(numBytes: bufferLength); + NTSTATUS status = default; + + while ((status = NtQueryObject(this, + OBJECT_INFORMATION_CLASS.ObjectNameInformation, + (OBJECT_NAME_INFORMATION*)buffer.DangerousGetHandle(), + bufferLength, + &bufferLength)).Code + is Code.STATUS_BUFFER_OVERFLOW or Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL) + { + buffer.Reallocate(bufferLength); + } + + return status.IsSuccessful + ? objectName = (buffer.Read(0).NameAsString, null) + : objectName = (null, new NTStatusException(status)); + } + else + { + return objectName; + } + } + } //public bool ProcessIs64Bit { get; } // unused, for now public unsafe (bool? v, Exception? ex) ProcessIsProtected => processIsProtected == default ? ProcessProtection.v is not null From 9b01fe9cf1db23f1f6e2292390cd128a1504cb05 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 00:35:41 -0700 Subject: [PATCH 194/306] refactor: replace HandleObjectType's ProcessIsProtected refs with ProcessProtection refactor: add uncommitted using namespace docs: add TODO 'override IsInvalid' --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 29 ++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 9811624..1da5eeb 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -5,6 +5,7 @@ using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.Threading; +using Windows.Win32.System.WindowsProgramming; using static Windows.Win32.PInvoke; using static Windows.Win32.PS_PROTECTION.PS_PROTECTED_TYPE; using ACCESS_MASK = PInvoke.Kernel32.ACCESS_MASK; @@ -24,6 +25,7 @@ namespace deadlock_dotnet_sdk.Domain; ///
public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { + // TODO: override IsInvalid private (string? v, Exception? ex) processCommandLine; private (string? v, Exception? ex) handleObjectType; private (string? v, Exception? ex) objectName; @@ -61,17 +63,24 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { if (handleObjectType == default) { - switch (ProcessIsProtected.v) + if (ProcessProtection.v is null) { - case false: - { - try { return handleObjectType = (SysHandleEx.GetHandleObjectType(), null); } - catch (Exception e) { return (null, e); } - } - case true: - return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; The process is protected.")); - default: - return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex)); + return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Unable to query the process's protection:" + Environment.NewLine + ProcessProtection.ex, ProcessProtection.ex)); + } + else if (ProcessProtection.v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) + { + try + { + return handleObjectType = (SysHandleEx.GetHandleObjectType(), null); + } + catch (Exception e) + { + return handleObjectType = (null, e); + } + } + else + { + return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; The process is protected.")); } } else From 02a2afcf6ff9556898ec3dd174894c13fa65fa56 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 01:09:15 -0700 Subject: [PATCH 195/306] docs: add summaries for FileType refactor: remove enum member unused by Windows --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index c4df926..ad726d0 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -217,11 +217,14 @@ public unsafe (string? v, Exception? ex) FileNameInfo public enum FileType : uint { + /// Either the type of the specified file is unknown, or the function failed. Unknown = FILE_TYPE.FILE_TYPE_UNKNOWN, + /// The specified file is a disk file. Disk = FILE_TYPE.FILE_TYPE_DISK, + /// The specified file is a character file, typically an LPT device or a console. Char = FILE_TYPE.FILE_TYPE_CHAR, + /// The specified file is a socket, a named pipe, or an anonymous pipe. Pipe = FILE_TYPE.FILE_TYPE_PIPE, - Remote = FILE_TYPE.FILE_TYPE_REMOTE } /// From 09a4a4a81e78edf61e79416b2b32ef8f4894d174 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 01:49:50 -0700 Subject: [PATCH 196/306] refactor: rewrite TryGetFinalPath exception conditions --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index ad726d0..f7228f6 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -236,14 +236,16 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() { try { - if (ProcessIsProtected.v is true) - throw new InvalidOperationException("Unable to query file/network path or pipe name; The process is protected."); - if (ProcessIsProtected.v is null) - throw new InvalidOperationException("Unable to query file/network path or pipe name; Unable to query the process's protection:" + Environment.NewLine + ProcessIsProtected.ex); - if (IsFileHandle.v is false) - throw new InvalidOperationException("Unable to query file/network path or pipe name; The handle object is not a File."); + if (ProcessProtection.v is null) + throw new InvalidOperationException("Unable to query disk/network path; Failed to query the process's protection:" + Environment.NewLine + ProcessProtection.ex); + if (ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) + throw new InvalidOperationException("Unable to query disk/network path; The process is protected."); if (HandleObjectType.v is null) - throw new InvalidOperationException("Unable to query file/network path or pipe name; Unable to query handle object type." + Environment.NewLine + HandleObjectType.ex); + throw new InvalidOperationException("Unable to query disk/network path; Failed to query handle object type." + Environment.NewLine + HandleObjectType.ex); + if (IsFileHandle.v is false) + throw new InvalidOperationException("Unable to query disk/network path; The handle's object is not a File."); + if (FileHandleType.v is not FileType.Disk) + throw new InvalidOperationException("Unable to query disk/network path; The File object is not a Disk-type File."); uint bufLength = (uint)short.MaxValue; using PWSTR getLengthBuffer = new((char*)Marshal.AllocHGlobal(1)); From 8faaca934dbb3d24af892e06be05063a7d6882d7 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 02:02:12 -0700 Subject: [PATCH 197/306] refactor: use UnauthorizedAccessExceptions where applicable --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 9 +++++---- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 14 +++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index f7228f6..96d5dcc 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -43,12 +43,12 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { try { - if (ProcessIsProtected.v == true) + if (ProcessIsProtected.v is true) { if (ProcessName.v is "smss") - ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by Windows Session Manager SubSystem ({ProcessName}, PID {ProcessId})")); + ExceptionLog.Add(new UnauthorizedAccessException($"The Handle's Name is inaccessible because the handle is owned by Windows Session Manager SubSystem ({ProcessName}, PID {ProcessId})")); else - ExceptionLog.Add(new InvalidOperationException($"The Handle's Name is inaccessible because the handle is owned by {ProcessName} (PID {ProcessId})")); + ExceptionLog.Add(new UnauthorizedAccessException($"The Handle's Name is inaccessible because the handle is owned by {ProcessName} (PID {ProcessId})")); } } catch (Exception e) @@ -98,6 +98,7 @@ public unsafe (string? v, Exception? ex) FileNameInfo { if (FileHandleType.v is not FileType.Disk) return (null, new InvalidOperationException("FileNameInfo can only be queried for disk-type file handles.")); + //TODO: check if process protection inhibits function //if (ProcessProtection.ex is not null) //if (ProcessProtection.v?.Value.Type ) @@ -239,7 +240,7 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() if (ProcessProtection.v is null) throw new InvalidOperationException("Unable to query disk/network path; Failed to query the process's protection:" + Environment.NewLine + ProcessProtection.ex); if (ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) - throw new InvalidOperationException("Unable to query disk/network path; The process is protected."); + throw new UnauthorizedAccessException("Unable to query disk/network path; The process is protected."); if (HandleObjectType.v is null) throw new InvalidOperationException("Unable to query disk/network path; Failed to query handle object type." + Environment.NewLine + HandleObjectType.ex); if (IsFileHandle.v is false) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 1da5eeb..f32e9ce 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -65,7 +65,7 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { if (ProcessProtection.v is null) { - return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Unable to query the process's protection:" + Environment.NewLine + ProcessProtection.ex, ProcessProtection.ex)); + return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Failed to query the process's protection:" + Environment.NewLine + ProcessProtection.ex, ProcessProtection.ex)); } else if (ProcessProtection.v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) { @@ -80,7 +80,7 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals } else { - return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; The process is protected.")); + return handleObjectType = (null, new UnauthorizedAccessException("Unable to query the kernel object's Type; The process is protected.")); } } else @@ -166,8 +166,8 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection return ProcessProtection.v?.Type switch { PsProtectedTypeNone or PsProtectedTypeProtectedLight => processCommandLine = TryGetProcessCommandLine(ProcessId), - not PsProtectedTypeProtected => processCommandLine = (null, new Exception("ProcessCommandLine cannot be queried or copied; the process's Protection level prevents access to the process's command line.")), - _ => processCommandLine = (null, new Exception("ProcessCommandLine cannot be queried or copied")) + PsProtectedTypeProtected => processCommandLine = (null, new UnauthorizedAccessException("ProcessCommandLine cannot be queried or copied; the process's Protection level prevents access to the process's command line.")), + _ => processCommandLine = (null, new InvalidOperationException("ProcessCommandLine cannot be queried or copied; Failed to query the process's protection.")) }; } else @@ -183,7 +183,7 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection /// v: If the query succeeded, the full file path of the process's main module, the executable file.
/// ex: If the query failed, the error encountered when attempting to query the full file path of the process's main module. /// - /// If IsProtected.v is true or null, returns InvalidOperationException. The queryable details of protected processes (System, Registry, etc.) are limited.. + /// If ProcessProtection.v is null, returns InvalidOperationException. If Protected, returns UnauthorizedAccessException. The queryable details of protected processes (System, Registry, etc.) are limited.. public (string? v, Exception? ex) ProcessMainModulePath { get @@ -209,12 +209,12 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection } else { - return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; The process is protected.")); + return processMainModulePath = (null, new UnauthorizedAccessException("Unable to query ProcessMainModulePath; The process is protected.")); } } else { - return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Unable to query the process's protection:" + Environment.NewLine + ProcessProtection.ex)); + return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Failed to query the process's protection:" + Environment.NewLine + ProcessProtection.ex)); } } else From 419a38be0508db53916da159d41d34c47ad66dd2 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 02:07:09 -0700 Subject: [PATCH 198/306] refactor: add ProcessInfo A wrapper for System.Diagnostic.Process objects. Adds properties e.g. Handles lists. An inheriting class would be preferred, but casting to/from base classes and inheriting classes is prohibited --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 deadlock-dotnet-sdk/Domain/ProcessInfo.cs diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs new file mode 100644 index 0000000..5db9153 --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -0,0 +1,14 @@ +using System.Diagnostics; + +namespace deadlock_dotnet_sdk.Domain; + +public class ProcessInfo +{ + public ProcessInfo(Process process) + { + Process = process; + } + + public Process Process { get; } + public List Handles { get; set; } = new(); +} From c887584cdaebc62560423d1c5752bb89fa2d85c8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 02:15:22 -0700 Subject: [PATCH 199/306] refactor: change FindLockingHandles return type to List docs: update FileLockerEx.Path summary I had fun with LINQ and async ops for a while. --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 27 +++----- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 74 ++++++++++++++------- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 73baa2a..1216aee 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace deadlock_dotnet_sdk.Domain { @@ -9,14 +9,14 @@ public class FileLockerEx { #region Properties - /// - /// Get or set the path of the file that is locked - /// + /// The path of the file that is locked public string Path { get; } public HandlesFilter Filter { get; } + // TODO: order by Process ID and then by handle value. Later todo: allow user-specified sorting rule (e.g. by column/property) /// Get or set the List of handles that are locking the file - public List Lockers { get; set; } + public List Lockers => Processes.SelectMany(pi => pi.Handles).Cast().OrderBy(h => h.ProcessId).ToList(); + public List Processes { get; private set; } #endregion Properties @@ -26,18 +26,7 @@ public class FileLockerEx public FileLockerEx() { Path = ""; - Lockers = new List(); - } - - /// - /// Initialize a new FileLocker - /// - /// The path of the file or directory - /// The List of handles that are locking the file - public FileLockerEx(string path, List lockers) - { - Path = path; - Lockers = lockers; + Processes = new(); } /// @@ -70,7 +59,7 @@ public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions, o warningException = new("Failed to enable Debug Mode for greater access to processes which do not belong to the current user or admin.", uae); } - Lockers = NativeMethods.FindLockingHandles(path, filter); + Processes = NativeMethods.FindLockingHandles(path, filter); } /// @@ -89,7 +78,7 @@ public enum HandlesFilter public void Refresh() { - Lockers = NativeMethods.FindLockingHandles(Path, Filter); + Processes = NativeMethods.FindLockingHandles(Path, Filter); } } } diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 2944507..a99642c 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -127,26 +127,61 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo /// A list of SafeFileHandleEx objects. When requested, handles for non-file or unidentified objects will be included with file-specific properties nulled. /// /// TODO: optimize process inspection. Stuff like IsProcessProtected should only be queried once per process - internal static List FindLockingHandles(string query, HandlesFilter filter = HandlesFilter.FilesOnly) + internal static List FindLockingHandles(string query, HandlesFilter filter = HandlesFilter.FilesOnly) { - List? handles = new(); + List processes = Process + .GetProcesses() + .ToList() + .ConvertAll(p => new ProcessInfo(p)); + var handles = GetSystemHandleInfoEx() + .ToArray() + .GroupBy(h => h.UniqueProcessId); + var sw = Stopwatch.StartNew(); + + var results = Parallel.ForEach(processes, p => + { + var match = handles.FirstOrDefault(group => (int)group.Key == p.Process.Id); + if (match is not null) + p.Handles.AddRange(match.ToList().ConvertAll(h => new(h)).Where(h => keep(h))); + else + return; + }); + + processes.Sort((a, b) => a.Process.Id.CompareTo(b.Process.Id)); + sw.Stop(); + Console.WriteLine("FindLockingHandles time elapsed: " + sw.Elapsed); + + //return handles; + return processes; - foreach (var h in GetSystemHandleInfoEx()) + bool keep(SafeFileHandleEx h) { - handles.Add(new SafeFileHandleEx(h)); - } + bool keep = false; - handles.RemoveAll(item => Discard(h: item)); - handles.Sort((a, b) => a.ProcessId.CompareTo(b.ProcessId)); + if (!string.IsNullOrEmpty(query)) + { + // only keep if FullFilePath contains query (with normalized directory separators) + string normalizedQuery = normalize(query); + string normalizedFileNameInfo = h.FileNameInfo.v is not null ? normalize(h.FileNameInfo.v) : string.Empty; + string normalizedFileFullPath = h.FileFullPath.v is not null ? normalize(h.FileFullPath.v) : string.Empty; - return handles; + /* If a handle is unrelated to the query, it doesn't matter. No other conditions matter at this point */ + // If an object name is returned by the system and it is null or zero-length, is it impossible for it to be a File handle? + return (!string.IsNullOrEmpty(h.ObjectName.v) && h.ObjectName.v.Contains(normalizedQuery)) + || (normalizedFileNameInfo.Length is not 0 && normalizedFileNameInfo.Contains(normalizedQuery)) + || (normalizedFileFullPath.Length is not 0 && normalizedFileFullPath.Contains(normalizedQuery)); - bool Discard(SafeFileHandleEx h) - { - bool keep = false; + string normalize(string s) => s is not null ? s.ToLower().Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) : string.Empty; + } + if (filter is HandlesFilter.FilesOnly) + { + // only keep if handle object is 'File' + // note: File objects' sub-type can be Directory, File, "File or Directory", Network, Other, or Pipe. + return h.IsFileHandle.v is not true; // query failed or object is not a File + } - // Check filters in reverse order - if (filter.HasFlag(HandlesFilter.IncludeProtectedProcesses)) + /* Check combined filters in reverse order */ + if (!keep && filter.HasFlag(HandlesFilter.IncludeProtectedProcesses)) { // if a process is protected, do not discard the handle keep = h.ProcessIsProtected.v is true; @@ -160,19 +195,8 @@ bool Discard(SafeFileHandleEx h) { keep = string.IsNullOrWhiteSpace(h.HandleObjectType.v); } - if (!keep && filter is HandlesFilter.FilesOnly) - { - // only keep if handle object is 'File' - // note: File objects' sub-type can be Directory, File, "File or Directory", Network, Other, or Pipe. - keep = h.IsFileHandle.v is true; - } - if (!string.IsNullOrWhiteSpace(query)) - { - // only keep if FullFilePath contains query (with normalized directory separators) - keep = h.FileFullPath.v?.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).Contains(query.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)) is true; - } - return !keep; + return keep; } } From d8f6c6f852c55e8cae780d623efbc616c4c2b758 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 03:21:46 -0700 Subject: [PATCH 200/306] refactor: reduce TryGetFinalPath timeout --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 96d5dcc..4476eb1 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -256,7 +256,7 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() // Try without duplicating. If it fails, try duplicating the handle. try { - const int timeout = 5000; + const int timeout = 50; Task taskGetLength = new(() => GetFinalPathNameByHandle(handle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED)); if (Task.WhenAny(taskGetLength, Task.Delay(timeout)).Result == taskGetLength) length = taskGetLength.Result; From e44c523a5a8e0dea4c9a26567ca4ae3bdb2abb1d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 03:23:03 -0700 Subject: [PATCH 201/306] docs: add ObjectName summary, examples of string values --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index f32e9ce..e6c780d 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -90,6 +90,15 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals } } + /// + /// The name of the object e.g. "\\Device\\HarddiskVolume4\\Repos\\BinToss\\deadlock-dotnet-diagnostics\\deadlock-diagnostics" or "\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\Nls\\Sorting\\Versions" + /// + /// + /// ("\\Sessions\\1\\BaseNamedObjects\\SM0:25004:304:WilStaging_02", null) + /// ("\\Device\\HarddiskVolume4\\Users\\NoahR\\AppData\\Roaming\\Code\\logs\\20230408T181715\\window1\\exthost\\output_logging_20230408T181718\\13-DTDL.log", null) + /// ("\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\Nls\\Sorting\\Versions", null) + /// ("\\Device\\CNG", null) + /// public unsafe (string? v, Exception? ex) ObjectName { get From a1ced273af45faf235f53d7f8b1c33d10b6bfacf Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 03:33:33 -0700 Subject: [PATCH 202/306] fix: change string comparison operator ("File" is "File") == false, apparently Use == for strings. --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 4476eb1..aae1736 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -63,7 +63,7 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( } public (bool? v, Exception? ex) IsFileHandle => isFileHandle == default - ? HandleObjectType.v is "File" + ? HandleObjectType.v == "File" ? (isFileHandle = (true, null)) : (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex))) : isFileHandle; From 6d8ca88dfd044b603bea2bd6059e7891fff48c70 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 03:34:25 -0700 Subject: [PATCH 203/306] fix: handle null and zero-length returns for ObjectName --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index e6c780d..0c9d53e 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -126,8 +126,12 @@ public unsafe (string? v, Exception? ex) ObjectName buffer.Reallocate(bufferLength); } + OBJECT_NAME_INFORMATION oni = buffer.Read(0); + if (oni.Name.Buffer.Value == null) + return (null, new NullReferenceException("Bad data was copied to the buffer. The string pointer is null.")); + return status.IsSuccessful - ? objectName = (buffer.Read(0).NameAsString, null) + ? objectName = (oni.NameAsString, null) : objectName = (null, new NTStatusException(status)); } else From 2bc267b139c5021d5e6299118505d01dadf7be91 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 9 Apr 2023 19:01:29 -0700 Subject: [PATCH 204/306] refactor: add TryGetFinalPath stopwatch refactor: remove unused variable from TryGetFinalPath --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index aae1736..0280062 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -249,11 +249,12 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() throw new InvalidOperationException("Unable to query disk/network path; The File object is not a Disk-type File."); uint bufLength = (uint)short.MaxValue; - using PWSTR getLengthBuffer = new((char*)Marshal.AllocHGlobal(1)); using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufLength)); uint length = 0; // Try without duplicating. If it fails, try duplicating the handle. + var sw = new Stopwatch(); + sw.Start(); try { const int timeout = 50; @@ -291,6 +292,11 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() { _ = ex; } + finally + { + sw.Stop(); + Console.Out.WriteLine($"(handle 0x{handle:X}) TryGetFinalPath time: {sw.Elapsed}"); // TODO: debug. Determine better timeout. + } /// Return the normalized drive name. This is the default. using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); From 05bf4f75d4a72c21b477d65fdd55ed4f06ea3542 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 11 Apr 2023 02:04:20 -0700 Subject: [PATCH 205/306] feat: allow user to specify the how the handle list is sorted * add FileLockerEx.SortByProperty enum * add FileLockerEx.SortByPrimary property * add FileLockerEx.SortBySecondary property Not all SortByProperty members have been fully implemented. --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 76 +++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 1216aee..0593351 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,9 +1,11 @@ using System.ComponentModel; +using System.Text; namespace deadlock_dotnet_sdk.Domain { //TODO: Add RefreshList(). This should clear Lockers and call FindLockingHandles again. //TODO: If a handle is closed or invalid, remove if from Lockers. SafeHandle.IsClosed is unreliable—it only works on handles managed by the current process. + //TODO: feat: finalize OrderBy parameters //https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs public class FileLockerEx { @@ -13,9 +15,81 @@ public class FileLockerEx public string Path { get; } public HandlesFilter Filter { get; } + public SortByProperty SortByPrimary { get; set; } = SortByProperty.ProcessId; + public SortByProperty SortBySecondary { get; set; } = SortByProperty.ObjectProperName; + + /// Used by the user to choose the primary and secondary sortation orders i.e. sort by process id and then by handle value + public enum SortByProperty + { + /// NOT IMPLEMENTED + FileShareAccess, // oh, this is important! Note: System Informer seems to crash when evaluating this property // TODO: implement FileShareAccess property + HandleAttributes, + HandleName, + HandleType, + HandleValue, + GrantedAccessHexadecimal, + GrantedAccessSymbolic, + /// The string returned to the ObjectName property via NtQueryObject. + ObjectOriginalName, + /// + /// (NOT IMPLEMENTED) + /// Differs from ObjectName for types {File, (Registry) Key} + /// + /// TODO: get 'real' paths e.g. "\REGISTRY\MACHINE" -> "HKLM" + ObjectProperName, + ObjectAddress, + ProcessId + } + // TODO: order by Process ID and then by handle value. Later todo: allow user-specified sorting rule (e.g. by column/property) /// Get or set the List of handles that are locking the file - public List Lockers => Processes.SelectMany(pi => pi.Handles).Cast().OrderBy(h => h.ProcessId).ToList(); + public List Lockers + { + get + { + return Processes + .SelectMany(pi => pi.Handles) + .Cast() + .OrderBy(h => + { + switch (SortByPrimary) + { + case SortByProperty.FileShareAccess: throw new NotImplementedException("FileShareAccess is not yet implemented!"); + case SortByProperty.HandleAttributes: throw new NotImplementedException("HandleAttributes is not yet implemented!");//return h.HandleAttributes; // TODO: h.HandleAttributes + case SortByProperty.HandleName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); + case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); + case SortByProperty.HandleValue: return Encoding.ASCII.GetBytes(h.HandleValue.ToString()); + case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); + case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); + case SortByProperty.ObjectOriginalName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); + case SortByProperty.ObjectProperName: throw new NotImplementedException("ObjectTrueName is not yet implemented!"); + case SortByProperty.ObjectAddress: return BitConverter.GetBytes((ulong)h.ObjectAddress); + case SortByProperty.ProcessId: return BitConverter.GetBytes(h.ProcessId); + default: goto case SortByProperty.ProcessId; + } + }) + .ThenBy(h => + { + switch (SortBySecondary) + { + case SortByProperty.FileShareAccess: throw new NotImplementedException("FileShareAccess is not yet implemented!"); + case SortByProperty.HandleAttributes: throw new NotImplementedException("HandleAttributes is not yet implemented!");//return h.HandleAttributes; // TODO: h.HandleAttributes + case SortByProperty.HandleName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); + case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); + case SortByProperty.HandleValue: return Encoding.ASCII.GetBytes(h.HandleValue.ToString()); + case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); + case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); + case SortByProperty.ObjectOriginalName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); + case SortByProperty.ObjectProperName: throw new NotImplementedException("ObjectTrueName is not yet implemented!"); + case SortByProperty.ObjectAddress: return BitConverter.GetBytes((ulong)h.ObjectAddress); + case SortByProperty.ProcessId: return BitConverter.GetBytes(h.ProcessId); + default: goto case SortByProperty.ProcessId; + } + }) + .ToList(); + } + } + public List Processes { get; private set; } #endregion Properties From 5dc8a17072391ebe54a416d13b777cb8501feee9 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 11 Apr 2023 02:06:56 -0700 Subject: [PATCH 206/306] docs: refine FileLockerEx.Path summary It wasn't entirely correct. --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 0593351..cd9062f 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -11,7 +11,7 @@ public class FileLockerEx { #region Properties - /// The path of the file that is locked + /// A keyphrase or the full or partial path of the locked file. public string Path { get; } public HandlesFilter Filter { get; } From 2f461e0cb129dabc602cdea6d64423f9426b4837 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 16 Apr 2023 18:05:16 -0700 Subject: [PATCH 207/306] feat: add class ProcessQueryHandle to store access rights with handle refactor: remove per-ProcessInfo handle lists feat: add ProcessId property to access underlying Process Id --- .../Domain/ProcessInfo.ProcessQueryHandle.cs | 43 ++++++++++ deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 80 ++++++++++++++++++- 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs new file mode 100644 index 0000000..d8b5eb7 --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs @@ -0,0 +1,43 @@ +using Microsoft.Win32.SafeHandles; +using Windows.Win32.System.Threading; +using static Windows.Win32.PInvoke; +using Win32Exception = System.ComponentModel.Win32Exception; + +namespace deadlock_dotnet_sdk.Domain; + +public partial class ProcessInfo +{ + public class ProcessQueryHandle + { + public ProcessQueryHandle(SafeProcessHandle processHandle, PROCESS_ACCESS_RIGHTS accessRights) + { + Handle = processHandle; + AccessRights = accessRights; + } + + public SafeProcessHandle Handle { get; } + public PROCESS_ACCESS_RIGHTS AccessRights { get; } + + /// + /// Open a handle with the requested rights for a process. + /// + /// + /// + /// A ProcessQueryHandle wrapping a SafeProcessHandle and the requested access rights. + /// Failed to open handle. The process might not exist, access was denied, or an unknown error occurred.
+ /// + /// If processId is 0, error code is ERROR_INVALID_PARAMETER
+ /// If process is System (4), CRSS, or similarly protected processes, error code is ERROR_ACCESS_DENIED
+ ///
+ ///
+ /// + /// - If processId == Process.GetCurrentProcess().Id, use Process.GetCurrentProcess().SafeHandle property instead. + /// - If Windows.Win32.PInvoke.IsDebugModeEnabled() == true, the requested access is granted regardless of the security descriptor. See GetSecurityInfo(); + /// + public static ProcessQueryHandle OpenProcessHandle(int processId, PROCESS_ACCESS_RIGHTS accessRights) + { + var h = OpenProcess_SafeHandle(accessRights, false, (uint)processId); + return h is null ? throw new Win32Exception() : (new(h, accessRights)); + } + } +} diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 5db9153..03b94fa 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -1,14 +1,90 @@ using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using PInvoke; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Threading; +using Windows.Win32.System.WindowsProgramming; +using static System.Environment; +using static Windows.Win32.PInvoke; +using static Windows.Win32.PS_PROTECTION.PS_PROTECTED_TYPE; +using Code = PInvoke.NTSTATUS.Code; +using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; +using Win32Exception = System.ComponentModel.Win32Exception; namespace deadlock_dotnet_sdk.Domain; -public class ProcessInfo +public partial class ProcessInfo { + private bool canGetQueryLimitedInfoHandle; + private bool canGetReadMemoryHandle; + private (ProcessQueryHandle? v, Exception? ex) processHandle; + public ProcessInfo(Process process) { Process = process; } + /// The base Process object this instance expands upon. public Process Process { get; } - public List Handles { get; set; } = new(); + public int ProcessId => Process.Id; + + public (ProcessQueryHandle? v, Exception? ex) ProcessHandle + { + get + { + if (processHandle == default) + { + const string exMsg = "Unable to open handle; "; + // We can't lookup the ProcessProtection without opening a process handle to check the process protection. + //PROCESS_ACCESS_RIGHTS access = ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected ? PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION : PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ; + + try + { + return processHandle = (ProcessQueryHandle.OpenProcessHandle( + ProcessId, + PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ), + null); + } + catch (Win32Exception ex) when ((Win32ErrorCode)ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) + { + // Before assuming anything, try without PROCESS_VM_READ. Without it, we don't need Debug privilege, but the PEB and all of its recursive members (e.g. Command Line) will be unavailable. + const string exAccessMsg = exMsg + " The requested permissions were denied."; + string exPermsFirst = NewLine + "First attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) + ", " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); + canGetReadMemoryHandle = false; + + try + { + return processHandle = (ProcessQueryHandle.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION), null); + } + catch (Win32Exception ex2) when ((Win32ErrorCode)ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) + { + // Debug Mode could not be enabled? Was SE_DEBUG_NAME denied to user or is current process not elevated? + canGetQueryLimitedInfoHandle = false; + string exPermsSecond = NewLine + "Second attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); + return (null, new UnauthorizedAccessException(exAccessMsg + exPermsFirst + exPermsSecond, ex2)); + } + catch (Exception ex2) + { + canGetQueryLimitedInfoHandle = false; + return (null, new AggregateException(exMsg + " Permissions were denied and an unknown error occurred.", new Exception[] { ex, ex2 })); + } + } + catch (Win32Exception ex) when ((Win32ErrorCode)ex.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_PARAMETER) + { + return (null, new ArgumentException(exMsg + " A process with ID " + ProcessId + " could not be found. The process may have exited.", ex)); + } + catch (Exception ex) + { + // unknown error + return (null, new Exception(exMsg + " An unknown error occurred.", ex)); + } + } + else + { + return processHandle; + } + } + } } From 717bf3631dd9bb0fb94db4fff9864e369ed6c564 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 17 Apr 2023 18:35:12 -0700 Subject: [PATCH 208/306] refactor: move process properties, methods to ProcessInfo refactor: add method PS_PROTECTION.ToString() refactor: add static property NativeMethods.Processes --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 16 + deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 492 ++++++++++++++++- deadlock-dotnet-sdk/Domain/ProcessList.cs | 57 ++ .../Domain/SafeFileHandleEx.cs | 52 +- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 501 +----------------- .../Windows.Win32/PS_PROTECTION.cs | 25 + 6 files changed, 625 insertions(+), 518 deletions(-) create mode 100644 deadlock-dotnet-sdk/Domain/ProcessList.cs diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index a99642c..7833be1 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -34,6 +34,22 @@ internal static class NativeMethods #endregion Variables + #region Properties + + private static ProcessList processes = new(); + public static ProcessList Processes + { + get + { + if (processes.Count is 0) + processes = (ProcessList)Process.GetProcesses().ToList().ConvertAll(p => new(p)).ToList(); + return (ProcessList)processes.OrderBy(p => p.Process.Id).ToList(); + } + set { processes = value; } + } + + #endregion Properties + #region Methods /// diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 03b94fa..ac7da33 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -19,7 +19,19 @@ public partial class ProcessInfo { private bool canGetQueryLimitedInfoHandle; private bool canGetReadMemoryHandle; + private (string? v, Exception? ex) processCommandLine; private (ProcessQueryHandle? v, Exception? ex) processHandle; + private (bool? v, Exception? ex) processIsProtected; + private (string? v, Exception? ex) processMainModulePath; + private (string? v, Exception? ex) processName; + private (PS_PROTECTION? v, Exception? ex) processProtection; + private readonly int processId; + + internal ProcessInfo(int processId) + { + Process = null; + this.processId = processId; + } public ProcessInfo(Process process) { @@ -27,8 +39,8 @@ public ProcessInfo(Process process) } /// The base Process object this instance expands upon. - public Process Process { get; } - public int ProcessId => Process.Id; + public Process? Process { get; } + public int ProcessId => Process?.Id ?? processId; public (ProcessQueryHandle? v, Exception? ex) ProcessHandle { @@ -87,4 +99,480 @@ public ProcessInfo(Process process) } } } + + //public bool ProcessIs64Bit { get; } // unused, for now + + public unsafe (bool? v, Exception? ex) ProcessIsProtected => processIsProtected == default + ? ProcessProtection.v is not null + ? (processIsProtected = (ProcessProtection.v.Value.Type > PsProtectedTypeNone, null)) + : (processIsProtected = (null, new Exception("ProcessProtection query failed.", ProcessProtection.ex))) + : processIsProtected; + + public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection + { + get + { + if (processProtection == default) + { + const uint ProcessProtectionInformation = 61; // Retrieves a BYTE value indicating the type of protected process and the protected process signer. + PS_PROTECTION protection = default; + uint retLength = 0; + + using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, (uint)ProcessId); + NTSTATUS status = NtQueryInformationProcess(hProcess, (PROCESSINFOCLASS)ProcessProtectionInformation, &protection, 1, ref retLength); + + if (status.Code is not Code.STATUS_SUCCESS) + return processProtection = (null, new NTStatusException(status)); + else + return processProtection = (protection, null); + } + else + { + return processProtection; + } + } + } + + public (string? v, Exception? ex) ProcessCommandLine + { + get + { + if (processCommandLine == default) + { + return ProcessProtection.v?.Type switch + { + PsProtectedTypeNone or PsProtectedTypeProtectedLight => processCommandLine = TryGetProcessCommandLine(ProcessId), + PsProtectedTypeProtected => processCommandLine = (null, new UnauthorizedAccessException("ProcessCommandLine cannot be queried or copied; the process's Protection level prevents access to the process's command line.")), + _ => processCommandLine = (null, new InvalidOperationException("ProcessCommandLine cannot be queried or copied; Failed to query the process's protection.")) + }; + } + else + { + return processCommandLine; + } + } + } + + /// + /// The full file path of the handle-owning process's main module (the executable file) or an exception if the Get operation failed. + /// + /// + /// v: If the query succeeded, the full file path of the process's main module, the executable file.
+ /// ex: If the query failed, the error encountered when attempting to query the full file path of the process's main module. + ///
+ /// If ProcessProtection.v is null, returns InvalidOperationException. If Protected, returns UnauthorizedAccessException. The queryable details of protected processes (System, Registry, etc.) are limited.. + public (string? v, Exception? ex) ProcessMainModulePath + { + get + { + if (processMainModulePath == default) + { + if (ProcessProtection.v is not null) + { + if (ProcessProtection.v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) + { + try + { + return processMainModulePath = (GetFullProcessImageName((uint)ProcessId), null); + } + catch (Win32Exception ex) when (ex.ErrorCode == 31) + { + return processMainModulePath = (null, new InvalidOperationException("Process has exited, so the requested information is not available.", ex)); + } + catch (Exception ex) + { + return processMainModulePath = (null, ex); + } + } + else + { + return processMainModulePath = (null, new UnauthorizedAccessException("Unable to query ProcessMainModulePath; The process is protected.")); + } + } + else + { + return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Failed to query the process's protection:" + NewLine + ProcessProtection.ex)); + } + } + else + { + return processMainModulePath; + } + } + } + + public (string? v, Exception? ex) ProcessName + { + get + { + if (processName == default) + { + switch (ProcessId) + { + case 0: + return processName = ("System Idle Process", null); + case 4: + return processName = ("System", null); + default: + try + { + var proc = Process.GetProcessById(ProcessId); + if (proc.HasExited) + return processName = (null, new InvalidOperationException("Process has exited, so the requested information is not available.")); + else return processName = (Process.GetProcessById(ProcessId).ProcessName, null); + } + catch (Exception ex) + { + return processName = (null, ex); + } + } + } + else + { + return processName; + } + } + } + + //public bool ProcessIs64Bit { get; } // unused, for now + //internal PEB_Ex? PebEx { get; } // Won't need this unless we want to start accessing otherwise unread pointer-type members of the PEB and its children (e.g. PEB_LDR_DATA, CURDIR, et cetera) + /// + /// A wrapper for QueryFullProcessImageName, a system function that circumvents 32-bit process limitations when permitted the PROCESS_QUERY_LIMITED_INFORMATION right. + /// + /// The ID of the process to open. The resulting SafeProcessHandle is opened with + /// The path to the executable image. + /// The process handle is invalid + /// QueryFullProcessImageName failed. See Exception message for details. + /// Failed to open process handle for processId; + private unsafe static string GetFullProcessImageName(uint processId) + { + uint size = 260 + 1; + uint bufferLength = size; + + using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, processId); + if (hProcess.IsInvalid) + throw new UnauthorizedAccessException("Cannot query process's filename.", new Win32Exception()); + + using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); + if (QueryFullProcessImageName(hProcess, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, lpExeName: buffer, ref size)) + { + return buffer.ToString(); + } + else if (bufferLength < size) + { + using PWSTR newBuffer = Marshal.AllocHGlobal((IntPtr)size); + if (QueryFullProcessImageName( + hProcess, + PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, + newBuffer, + ref size)) + { + return newBuffer.ToString(); // newBuffer.Value will not be null here + } + else + { + throw new Win32Exception(); // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + } + } + else + { + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); + } + } + + private static (string? v, Exception? ex) TryGetProcessCommandLine(int processId) + { + if (processId == Environment.ProcessId) + return (CommandLine, null); + try + { + if (!IsDebugModeEnabled()) + Process.EnterDebugMode(); + } + catch (Exception ex) + { + Trace.WriteLine("Failed check if Debug Mode was enabled or failed to enable Debug Mode for the current process." + NewLine + ex.ToString()); + } + + using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, (uint)processId); + if (hProcess.IsInvalid) + return (null, new Win32Exception()); + + try + { + return (GetProcessCommandLine(hProcess), null); + } + catch (Exception ex) + { + return (null, ex); + } + } + + /// TODO: clean up Exception. Implement custom exceptions? + /// Try to get a process's command line from its PEB + /// A handle to the target process with the rights PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ + /// The provided process handle is invalid. + /// + /// IsWow64Process failed to determine if target process is running under WOW. See InnerException. + /// -OR- + /// NtQueryInformationProcess failed to get a process's command line. See InnerException. + /// -OR- + /// NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB. See InnerException. + /// -OR- + /// NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process. See InnerException. + /// -OR- + /// NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. + /// -OR- + /// NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process. See InnerException. + /// -OR- + /// NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException. + /// -OR- + /// ReadProcessMemory failed to copy another process's PEB to this process. See InnerException. + /// -OR- + /// ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. + /// -OR- + /// ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException. + /// -OR- + /// + /// ReAllocHGlobal received a null pointer, but didn't check the error code. This is not a real OutOfMemoryException + private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) + { + if (hProcess.IsInvalid) + throw new ArgumentException("The provided process handle is invalid.", paramName: nameof(hProcess)); + + if (!IsWow64Process(hProcess, out BOOL targetIs32BitProcess)) + throw new Exception("Failed to determine target process is running under WOW. See InnerException.", new Win32Exception()); + + bool weAre32BitAndTheyAre64Bit = !Is64BitProcess && !targetIs32BitProcess; + bool weAre64BitAndTheyAre32Bit = Is64BitProcess && targetIs32BitProcess; + NTSTATUS status; + uint returnLength = 0; + ulong bytesRead; + + /** If Win8.1 or later */ + if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) + { + const uint ProcessCommandLineInformation = 60u; + uint bufferLength = (uint)Marshal.SizeOf() + 2048u; + using SafeBuffer safeBuffer = new(numBytes: bufferLength); + + status = NtQueryInformationProcess( + hProcess, + (PROCESSINFOCLASS)ProcessCommandLineInformation, + (void*)safeBuffer.DangerousGetHandle(), + bufferLength, + ref returnLength + ); + + if (status == Code.STATUS_INFO_LENGTH_MISMATCH) + { +#if DEBUG + Console.Out.WriteLine( + $"bufferLength: {bufferLength}\n" + + $"returnLength: {returnLength}"); + bufferLength = returnLength; +#endif + try + { + // the native call to LocalReAlloc (via Marshal.ReAllocHGlobal) sometimes returns a null pointer. This is a Legacy function. Why does .NET not use malloc/realloc? + //pString->Buffer = new((char*)Marshal.ReAllocHGlobal((IntPtr)pString->Buffer.Value, (IntPtr)bufferLength)); + safeBuffer.Reallocate(numBytes: returnLength); + } + catch (OutOfMemoryException) // ReAllocHGlobal received a null pointer, but didn't check the error code + { + // none of these were of interest... + //var pinerr = Marshal.GetLastPInvokeError(); + //var syserr = Marshal.GetLastSystemError(); + //var winerr = Marshal.GetLastWin32Error(); + throw; + } + + status = NtQueryInformationProcess( + hProcess, + (PROCESSINFOCLASS)ProcessCommandLineInformation, + (void*)safeBuffer.DangerousGetHandle(), + bufferLength, + ref returnLength + ); + } + + if (status.IsSuccessful) + return safeBuffer.Read(0).ToStringZ() ?? string.Empty; + else + throw new Exception("NtQueryInformationProcess failed to get a process's command line. See InnerException.", new NTStatusException(status)); + } + else /** Read CommandLine from PEB's Process Parameters */ + { + /** if our process is 32-bit and the target process is 64-bit, use a workaround. + The following blocks use a hybrid of SystemInformer's solution (PhGetProcessCommandLine) and the alternative provided at https://stackoverflow.com/a/14012919/14894786. + All comments inside the code blocks are from either source. + */ + if (weAre32BitAndTheyAre64Bit) /** This process is 32-bit, that process is 64-bit */ + { + using SafeBuffer buffer = new(numBytes: 0); + PROCESS_BASIC_INFORMATION64 basicInfo = default; + PEB64 peb = default; + RTL_USER_PROCESS_PARAMETERS64 parameters = default; + + // Get the PEB address. + buffer.Initialize(numElements: 1); + status = NtWow64QueryInformationProcess64( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + &basicInfo, + (uint)buffer.ByteLength, + &returnLength); + buffer.Initialize(numBytes: returnLength); + byte* pointer = null; + buffer.AcquirePointer(ref pointer); + status = NtWow64QueryInformationProcess64(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, pointer, (uint)buffer.ByteLength, &returnLength); + buffer.ReleasePointer(); + if (status.IsSuccessful) + { + basicInfo = buffer.Read(0); + buffer.Dispose(); + } + else + { + throw new Exception("NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); + } + + // copy PEB + if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)basicInfo.PebBaseAddress, &peb, (ulong)Marshal.SizeOf(peb), &bytesRead)).IsSuccessful) + throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); + + // Copy RTL_USER_PROCESS_PARAMETERS. + if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)peb.ProcessParameters, ¶meters, (ulong)Marshal.SizeOf(parameters), &bytesRead)).IsSuccessful) + throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); + + using UNICODE_STRING cmdLine = new() + { + MaximumLength = parameters.CommandLine.MaximumLength, + Length = parameters.CommandLine.Length, + Buffer = (char*)Marshal.AllocHGlobal(parameters.CommandLine.MaximumLength) + }; + + if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, &bytesRead)).IsSuccessful) + throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); + + return cmdLine.ToStringLength(); + } + else if (weAre64BitAndTheyAre32Bit) /** This is 64-bit, that is 32-bit */ + { + using SafeBuffer buffer = new(numElements: 1); + PROCESS_BASIC_INFORMATION32 basicInfo = default; + PEB32 peb = default; + RTL_USER_PROCESS_PARAMETERS32 parameters = default; + + // Get the PEB address. + buffer.Initialize(numElements: 1); + status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + &basicInfo, + (uint)buffer.ByteLength, + ref returnLength); + while (status == Code.STATUS_INFO_LENGTH_MISMATCH) + { + buffer.Initialize(numBytes: returnLength); + byte* pointer = null; + buffer.AcquirePointer(ref pointer); + status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + pointer, + (uint)buffer.ByteLength, + ref returnLength); + buffer.ReleasePointer(); + } + if (status.IsSuccessful) + { + basicInfo = buffer.Read(0); + buffer.Dispose(); + } + else + { + throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); + } + + // copy PEB + if (!ReadProcessMemory(hProcess, (void*)basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); + + // Copy RTL_USER_PROCESS_PARAMETERS. + if (!ReadProcessMemory(hProcess, (void*)peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); + + using UNICODE_STRING cmdLine = new() + { + MaximumLength = parameters.CommandLine.MaximumLength, + Length = parameters.CommandLine.Length, + Buffer = (char*)Marshal.AllocHGlobal(Marshal.SizeOf() * 260) + }; + + if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); + + return cmdLine.ToStringLength(); + } + else /** this process and that process are the same bit architecture */ + { + using SafeBuffer buffer = new(numElements: 1); + PROCESS_BASIC_INFORMATION basicInfo = default; + PEB peb = default; + RTL_USER_PROCESS_PARAMETERS parameters = default; + + // Get the PEB address. + status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + &basicInfo, + (uint)buffer.ByteLength, + ref returnLength); + while (status == Code.STATUS_INFO_LENGTH_MISMATCH) + { + buffer.Initialize(numBytes: returnLength); + byte* pointer = null; + buffer.AcquirePointer(ref pointer); + status = NtQueryInformationProcess( + hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, + pointer, + (uint)buffer.ByteLength, + ref returnLength); + buffer.ReleasePointer(); + } + if (status.IsSuccessful) + { + basicInfo = buffer.Read(0); + buffer.Dispose(); + } + else + { + throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); + } + + // copy PEB + if (!ReadProcessMemory(hProcess, basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); + + // Copy RTL_USER_PROCESS_PARAMETERS. + if (!ReadProcessMemory(hProcess, peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); + + using UNICODE_STRING cmdLine = new() + { + MaximumLength = parameters.CommandLine.MaximumLength, + Length = parameters.CommandLine.Length, + Buffer = (char*)Marshal.AllocHGlobal(Marshal.SizeOf() * 260) + }; + + if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) + throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); + + return cmdLine.ToStringLength(); + } + } + } } diff --git a/deadlock-dotnet-sdk/Domain/ProcessList.cs b/deadlock-dotnet-sdk/Domain/ProcessList.cs new file mode 100644 index 0000000..a3f7f10 --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/ProcessList.cs @@ -0,0 +1,57 @@ +using System.Diagnostics; +using static System.Environment; +namespace deadlock_dotnet_sdk.Domain; + +public sealed class ProcessList : List +{ + public ProcessList() { } + public ProcessList(IEnumerable collection) : base(collection) + { } + + public ProcessList(int capacity) : base(capacity) + { } + + /// + /// Find a ProcessInfo by its process ID and returns it. If no existing ProcessInfo is found, the system is queried for a Process with that ID. If the returned Process is not null, it is returned as a ProcessInfo object. + /// + /// The process ID of the process to find. + /// The existing ProcessInfo object with an ID matching . If it does not exist yet, the system is queried for a Process with that ID. If the returned Process is not null, it is returned as a ProcessInfo object. + public ProcessInfo GetProcessById(int processId) + { + var result = Find(p => p.Process?.Id == processId); + if (result is not null) + return result; + + ProcessInfo pi; + + try + { + var p = Process.GetProcessById(processId); + + if (p is null) + { + pi = new ProcessInfo(processId); + Add(pi); + return pi; + } + + pi = new(p); + Add(pi); + return pi; + } + catch (ArgumentException ex) // + { + Trace.WriteLine($"No process was found with ID {processId}. If it *did* exist, the process had exited and is not in .NET's internal process list." + NewLine + ex.ToString(), "ERROR"); + pi = new ProcessInfo(processId); + Add(pi); + return pi; + } + catch (Exception ex) + { + Trace.WriteLine("An unknown exception was thrown." + NewLine + ex, "ERROR"); + pi = new ProcessInfo(processId); + Add(pi); + return pi; + } + } +} diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 0280062..259c522 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -43,12 +43,12 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { try { - if (ProcessIsProtected.v is true) + if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) { - if (ProcessName.v is "smss") - ExceptionLog.Add(new UnauthorizedAccessException($"The Handle's Name is inaccessible because the handle is owned by Windows Session Manager SubSystem ({ProcessName}, PID {ProcessId})")); + if (ProcessInfo.ProcessName.v is "smss") + ExceptionLog.Add(new UnauthorizedAccessException($"The Handle's Name is inaccessible because the handle is owned by Windows Session Manager SubSystem ({ProcessInfo.ProcessName}, PID {ProcessId})")); else - ExceptionLog.Add(new UnauthorizedAccessException($"The Handle's Name is inaccessible because the handle is owned by {ProcessName} (PID {ProcessId})")); + ExceptionLog.Add(new UnauthorizedAccessException($"The Handle's Name is inaccessible because the handle is owned by {ProcessInfo.ProcessName} (PID {ProcessId})")); } } catch (Exception e) @@ -237,9 +237,9 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() { try { - if (ProcessProtection.v is null) - throw new InvalidOperationException("Unable to query disk/network path; Failed to query the process's protection:" + Environment.NewLine + ProcessProtection.ex); - if (ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) + if (ProcessInfo.ProcessProtection.v is null) + throw new InvalidOperationException("Unable to query disk/network path; Failed to query the process's protection:" + Environment.NewLine + ProcessInfo.ProcessProtection.ex); + if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) throw new UnauthorizedAccessException("Unable to query disk/network path; The process is protected."); if (HandleObjectType.v is null) throw new InvalidOperationException("Unable to query disk/network path; Failed to query handle object type." + Environment.NewLine + HandleObjectType.ex); @@ -347,30 +347,32 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() public override string ToString() { - string[] exLog = ExceptionLog.Cast().ToArray(); + string[] exLog = ExceptionLog.ConvertAll(ex => ex.ToString()).ToArray(); for (int i = 0; i < exLog.Length; i++) { exLog[i] = $" {exLog[i]}".Replace("\n", "\n ") + Environment.NewLine; } return @$"{GetType().Name} hash:{GetHashCode()} - {nameof(CreatorBackTraceIndex)} : {CreatorBackTraceIndex} - {nameof(FileFullPath)} : {FileFullPath.v ?? FileFullPath.ex?.ToString()} - {nameof(FileHandleType)} : {FileHandleType.v?.ToString() ?? FileFullPath.ex?.ToString()} - {nameof(FileName)} : {FileName.v ?? FileName.ex?.ToString()} - {nameof(GrantedAccess)} : {SysHandleEx.GrantedAccessString} - {nameof(HandleObjectType)} : {HandleObjectType.v ?? HandleObjectType.ex?.ToString()} - {nameof(HandleValue)} : {HandleValue} (0x{HandleValue:X}) - {nameof(IsClosed)} : {IsClosed} - {nameof(IsDirectory)} : {IsDirectory.v?.ToString() ?? IsDirectory.ex?.ToString()} - {nameof(IsFileHandle)} : {IsFileHandle.v?.ToString() ?? IsFileHandle.ex?.ToString()} - {nameof(IsInvalid)} : {IsInvalid} - {nameof(ObjectAddress)} : {ObjectAddress} (0x{ObjectAddress:X}) - {nameof(ProcessCommandLine)} : {ProcessCommandLine.v ?? ProcessCommandLine.ex?.ToString()} - {nameof(ProcessId)} : {ProcessId} - {nameof(ProcessMainModulePath)} : {ProcessMainModulePath.v ?? ProcessMainModulePath.ex?.ToString()} - {nameof(ProcessName)} : {ProcessName.v ?? ProcessName.ex?.ToString()} - {nameof(ExceptionLog)} : ... + {nameof(CreatorBackTraceIndex)} : {CreatorBackTraceIndex} + {nameof(FileFullPath)} : {FileFullPath.v ?? FileFullPath.ex?.ToString()} + {nameof(FileHandleType)} : {FileHandleType.v?.ToString() ?? FileFullPath.ex?.ToString()} + {nameof(FileName)} : {FileName.v ?? FileName.ex?.ToString()} + {nameof(GrantedAccess)} : {SysHandleEx.GrantedAccessString} + {nameof(HandleObjectType)} : {HandleObjectType.v ?? HandleObjectType.ex?.ToString()} + {nameof(HandleValue)} : {HandleValue} (0x{HandleValue:X}) + {nameof(IsClosed)} : {IsClosed} + {nameof(IsDirectory)} : {IsDirectory.v?.ToString() ?? IsDirectory.ex?.ToString()} + {nameof(IsFileHandle)} : {IsFileHandle.v?.ToString() ?? IsFileHandle.ex?.ToString()} + {nameof(IsInvalid)} : {IsInvalid} + {nameof(ObjectAddress)} : {ObjectAddress} (0x{ObjectAddress:X}) + {nameof(ObjectName)} : {ObjectName.v ?? ObjectName.ex?.ToString()} + {nameof(ProcessId)} : {ProcessId} + {nameof(ProcessInfo.ProcessCommandLine)} : {ProcessInfo.ProcessCommandLine.v ?? ProcessInfo.ProcessCommandLine.ex?.ToString()} + {nameof(ProcessInfo.ProcessMainModulePath)} : {ProcessInfo.ProcessMainModulePath.v ?? ProcessInfo.ProcessMainModulePath.ex?.ToString()} + {nameof(ProcessInfo.ProcessName)} : {ProcessInfo.ProcessName.v ?? ProcessInfo.ProcessName.ex?.ToString()} + {nameof(ProcessInfo.ProcessProtection)} : {(ProcessInfo.ProcessProtection.v is not null ? (ProcessInfo.ProcessProtection.v.ToString()) : (ProcessInfo.ProcessProtection.ex?.ToString()))} + {nameof(ExceptionLog)} : ... " + string.Concat(exLog); } } diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 0c9d53e..bdcd075 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; using PInvoke; using Windows.Win32; @@ -26,13 +25,9 @@ namespace deadlock_dotnet_sdk.Domain; public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { // TODO: override IsInvalid - private (string? v, Exception? ex) processCommandLine; private (string? v, Exception? ex) handleObjectType; private (string? v, Exception? ex) objectName; - private (string? v, Exception? ex) processMainModulePath; - private (string? v, Exception? ex) processName; - private (bool? v, Exception? ex) processIsProtected; - private (PS_PROTECTION? v, Exception? ex) processProtection; + private ProcessInfo? processInfo; public SafeHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) { } @@ -63,11 +58,12 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { if (handleObjectType == default) { - if (ProcessProtection.v is null) + var (v, ex) = ProcessInfo.ProcessProtection; + if (v is null) { - return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Failed to query the process's protection:" + Environment.NewLine + ProcessProtection.ex, ProcessProtection.ex)); + return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Failed to query the process's protection:" + Environment.NewLine + ex, ex)); } - else if (ProcessProtection.v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) + else if (v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) { try { @@ -105,11 +101,12 @@ public unsafe (string? v, Exception? ex) ObjectName { if (objectName == default) { + var (v, ex) = ProcessInfo.ProcessProtection; // I'm assuming process protection prohibits access. I've not tested it. // This information is not queryable in SystemInformer when a process has Full protection. - if (ProcessProtection.v is null) - return objectName = (null, new UnauthorizedAccessException("Unable to query ObjectName; Failed to query process's protection level.", processProtection.ex)); - else if (ProcessProtection.v.Value.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) + if (v is null) + return objectName = (null, new UnauthorizedAccessException("Unable to query ObjectName; Failed to query process's protection level.", ex)); + else if (v.Value.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) return objectName = (null, new UnauthorizedAccessException("Unable to query ObjectName; The process's protection type prohibits access.")); uint bufferLength = 1024u; @@ -140,138 +137,8 @@ public unsafe (string? v, Exception? ex) ObjectName } } } - //public bool ProcessIs64Bit { get; } // unused, for now - public unsafe (bool? v, Exception? ex) ProcessIsProtected => processIsProtected == default - ? ProcessProtection.v is not null - ? (processIsProtected = (ProcessProtection.v.Value.Type > PsProtectedTypeNone, null)) - : (processIsProtected = (null, new Exception("ProcessProtection query failed.", ProcessProtection.ex))) - : processIsProtected; - public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection - { - get - { - if (processProtection == default) - { - const uint ProcessProtectionInformation = 61; // Retrieves a BYTE value indicating the type of protected process and the protected process signer. - PS_PROTECTION protection = default; - uint retLength = 0; - - using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, ProcessId); - NTSTATUS status = NtQueryInformationProcess(hProcess, (PROCESSINFOCLASS)ProcessProtectionInformation, &protection, 1, ref retLength); - - if (status.Code is not Code.STATUS_SUCCESS) - return processProtection = (null, new NTStatusException(status)); - else - return processProtection = (protection, null); - } - else - { - return processProtection; - } - } - } - public (string? v, Exception? ex) ProcessCommandLine - { - get - { - if (processCommandLine == default) - { - return ProcessProtection.v?.Type switch - { - PsProtectedTypeNone or PsProtectedTypeProtectedLight => processCommandLine = TryGetProcessCommandLine(ProcessId), - PsProtectedTypeProtected => processCommandLine = (null, new UnauthorizedAccessException("ProcessCommandLine cannot be queried or copied; the process's Protection level prevents access to the process's command line.")), - _ => processCommandLine = (null, new InvalidOperationException("ProcessCommandLine cannot be queried or copied; Failed to query the process's protection.")) - }; - } - else - { - return processCommandLine; - } - } - } - /// - /// The full file path of the handle-owning process's main module (the executable file) or an exception if the Get operation failed. - /// - /// - /// v: If the query succeeded, the full file path of the process's main module, the executable file.
- /// ex: If the query failed, the error encountered when attempting to query the full file path of the process's main module. - ///
- /// If ProcessProtection.v is null, returns InvalidOperationException. If Protected, returns UnauthorizedAccessException. The queryable details of protected processes (System, Registry, etc.) are limited.. - public (string? v, Exception? ex) ProcessMainModulePath - { - get - { - if (processMainModulePath == default) - { - if (ProcessProtection.v is not null) - { - if (ProcessProtection.v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) - { - try - { - return processMainModulePath = (GetFullProcessImageName(ProcessId), null); - } - catch (Win32Exception ex) when (ex.ErrorCode == 31) - { - return processMainModulePath = (null, new InvalidOperationException("Process has exited, so the requested information is not available.", ex)); - } - catch (Exception ex) - { - return processMainModulePath = (null, ex); - } - } - else - { - return processMainModulePath = (null, new UnauthorizedAccessException("Unable to query ProcessMainModulePath; The process is protected.")); - } - } - else - { - return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Failed to query the process's protection:" + Environment.NewLine + ProcessProtection.ex)); - } - } - else - { - return processMainModulePath; - } - } - } - - public (string? v, Exception? ex) ProcessName - { - get - { - if (processName == default) - { - switch (ProcessId) - { - case 0: - return processName = ("System Idle Process", null); - case 4: - return processName = ("System", null); - default: - try - { - var proc = Process.GetProcessById((int)ProcessId); - if (proc.HasExited) - return processName = (null, new InvalidOperationException("Process has exited, so the requested information is not available.")); - else return processName = (Process.GetProcessById((int)ProcessId).ProcessName, null); - } - catch (Exception ex) - { - return processName = (null, ex); - } - } - } - else - { - return processName; - } - } - } - //public bool ProcessIs64Bit { get; } // unused, for now - //internal PEB_Ex? PebEx { get; } // Won't need this unless we want to start accessing otherwise unread pointer-type members of the PEB and its children (e.g. PEB_LDR_DATA, CURDIR, et cetera) + public ProcessInfo ProcessInfo => processInfo ??= NativeMethods.Processes.GetProcessById((int)(uint)SysHandleEx.UniqueProcessId); /// A list of exceptions thrown by constructors and other methods of this class. /// Use List's methods (e.g. Add) to modify this list. @@ -314,354 +181,6 @@ public bool CloseSourceHandle() } } - /// - /// A wrapper for QueryFullProcessImageName, a system function that circumvents 32-bit process limitations when permitted the PROCESS_QUERY_LIMITED_INFORMATION right. - /// - /// The ID of the process to open. The resulting SafeProcessHandle is opened with - /// The path to the executable image. - /// The process handle is invalid - /// QueryFullProcessImageName failed. See Exception message for details. - /// Failed to open process handle for processId; - private unsafe static string GetFullProcessImageName(uint processId) - { - uint size = 260 + 1; - uint bufferLength = size; - - using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, processId); - if (hProcess.IsInvalid) - throw new UnauthorizedAccessException("Cannot query process's filename.", new Win32Exception()); - - using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); - if (QueryFullProcessImageName(hProcess, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, lpExeName: buffer, ref size)) - { - return buffer.ToString(); - } - else if (bufferLength < size) - { - using PWSTR newBuffer = Marshal.AllocHGlobal((IntPtr)size); - if (QueryFullProcessImageName( - hProcess, - PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - newBuffer, - ref size)) - { - return newBuffer.ToString(); // newBuffer.Value will not be null here - } - else - { - throw new Win32Exception(); // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - } - } - else - { - // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - throw new Win32Exception(); - } - } - - private static (string? v, Exception? ex) TryGetProcessCommandLine(uint processId) - { - if (processId == (uint)Environment.ProcessId) - return (Environment.CommandLine, null); - try - { - if (!IsDebugModeEnabled()) - Process.EnterDebugMode(); - } - catch (Exception ex) - { - Debug.Print(ex.ToString()); - } - - using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, processId); - if (hProcess.IsInvalid) - return (null, new Win32Exception()); - - try - { - //todo: amend GetProcessCommandLine commit - return (GetProcessCommandLine(hProcess), null); - } - catch (Exception ex) - { - return (null, ex); - } - } - - /// TODO: clean up Exception. Implement custom exceptions? - /// Try to get a process's command line from its PEB - /// A handle to the target process with the rights PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ - /// The provided process handle is invalid. - /// - /// IsWow64Process failed to determine if target process is running under WOW. See InnerException. - /// -OR- - /// NtQueryInformationProcess failed to get a process's command line. See InnerException. - /// -OR- - /// NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB. See InnerException. - /// -OR- - /// NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process. See InnerException. - /// -OR- - /// NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. - /// -OR- - /// NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process. See InnerException. - /// -OR- - /// NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException. - /// -OR- - /// ReadProcessMemory failed to copy another process's PEB to this process. See InnerException. - /// -OR- - /// ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. - /// -OR- - /// ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException. - /// -OR- - /// NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException. - /// -OR- - /// ReadProcessMemory failed to copy another process's PEB to this process. See InnerException. - /// -OR- - /// ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. - /// -OR- - /// ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException. - /// - /// ReAllocHGlobal received a null pointer, but didn't check the error code. This is not a real OutOfMemoryException - private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) - { - if (hProcess.IsInvalid) - throw new ArgumentException("The provided process handle is invalid.", paramName: nameof(hProcess)); - - if (!IsWow64Process(hProcess, out BOOL targetIs32BitProcess)) - throw new Exception("Failed to determine target process is running under WOW. See InnerException.", new Win32Exception()); - - bool weAre32BitAndTheyAre64Bit = !Environment.Is64BitProcess && !targetIs32BitProcess; - bool weAre64BitAndTheyAre32Bit = Environment.Is64BitProcess && targetIs32BitProcess; - NTSTATUS status; - uint returnLength = 0; - ulong bytesRead; - - /** If Win8.1 or later */ - if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) - { - const uint ProcessCommandLineInformation = 60u; - uint bufferLength = (uint)Marshal.SizeOf() + 2048u; - using SafeBuffer safeBuffer = new(numBytes: bufferLength); - - status = NtQueryInformationProcess( - hProcess, - (PROCESSINFOCLASS)ProcessCommandLineInformation, - (void*)safeBuffer.DangerousGetHandle(), - bufferLength, - ref returnLength - ); - - if (status == Code.STATUS_INFO_LENGTH_MISMATCH) - { -#if DEBUG - Console.Out.WriteLine( - $"bufferLength: {bufferLength}\n" + - $"returnLength: {returnLength}"); - bufferLength = returnLength; -#endif - try - { - // the native call to LocalReAlloc (via Marshal.ReAllocHGlobal) sometimes returns a null pointer. This is a Legacy function. Why does .NET not use malloc/realloc? - //pString->Buffer = new((char*)Marshal.ReAllocHGlobal((IntPtr)pString->Buffer.Value, (IntPtr)bufferLength)); - safeBuffer.Reallocate(numBytes: returnLength); - } - catch (OutOfMemoryException) // ReAllocHGlobal received a null pointer, but didn't check the error code - { - // none of these were of interest... - //var pinerr = Marshal.GetLastPInvokeError(); - //var syserr = Marshal.GetLastSystemError(); - //var winerr = Marshal.GetLastWin32Error(); - throw; - } - - status = NtQueryInformationProcess( - hProcess, - (PROCESSINFOCLASS)ProcessCommandLineInformation, - (void*)safeBuffer.DangerousGetHandle(), - bufferLength, - ref returnLength - ); - } - - if (status.IsSuccessful) - return safeBuffer.Read(0).ToStringZ() ?? string.Empty; - else - throw new Exception("NtQueryInformationProcess failed to get a process's command line. See InnerException.", new NTStatusException(status)); - } - else /** Read CommandLine from PEB's Process Parameters */ - { - /** if our process is 32-bit and the target process is 64-bit, use a workaround. - The following blocks use a hybrid of SystemInformer's solution (PhGetProcessCommandLine) and the alternative provided at https://stackoverflow.com/a/14012919/14894786. - All comments inside the code blocks are from either source. - */ - if (weAre32BitAndTheyAre64Bit) /** This process is 32-bit, that process is 64-bit */ - { - using SafeBuffer buffer = new(numBytes: 0); - PROCESS_BASIC_INFORMATION64 basicInfo = default; - PEB64 peb = default; - RTL_USER_PROCESS_PARAMETERS64 parameters = default; - - // Get the PEB address. - buffer.Initialize(numElements: 1); - status = NtWow64QueryInformationProcess64( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - &basicInfo, - (uint)buffer.ByteLength, - &returnLength); - buffer.Initialize(numBytes: returnLength); - byte* pointer = null; - buffer.AcquirePointer(ref pointer); - status = NtWow64QueryInformationProcess64(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, pointer, (uint)buffer.ByteLength, &returnLength); - buffer.ReleasePointer(); - if (status.IsSuccessful) - { - basicInfo = buffer.Read(0); - buffer.Dispose(); - } - else - { - throw new Exception("NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); - } - - // copy PEB - if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)basicInfo.PebBaseAddress, &peb, (ulong)Marshal.SizeOf(peb), &bytesRead)).IsSuccessful) - throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); - - // Copy RTL_USER_PROCESS_PARAMETERS. - if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)peb.ProcessParameters, ¶meters, (ulong)Marshal.SizeOf(parameters), &bytesRead)).IsSuccessful) - throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); - - using UNICODE_STRING cmdLine = new() - { - MaximumLength = parameters.CommandLine.MaximumLength, - Length = parameters.CommandLine.Length, - Buffer = (char*)Marshal.AllocHGlobal(parameters.CommandLine.MaximumLength) - }; - - if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, &bytesRead)).IsSuccessful) - throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); - - return cmdLine.ToStringLength(); - } - else if (weAre64BitAndTheyAre32Bit) /** This is 64-bit, that is 32-bit */ - { - using SafeBuffer buffer = new(numElements: 1); - PROCESS_BASIC_INFORMATION32 basicInfo = default; - PEB32 peb = default; - RTL_USER_PROCESS_PARAMETERS32 parameters = default; - - // Get the PEB address. - buffer.Initialize(numElements: 1); - status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - &basicInfo, - (uint)buffer.ByteLength, - ref returnLength); - while (status == Code.STATUS_INFO_LENGTH_MISMATCH) - { - buffer.Initialize(numBytes: returnLength); - byte* pointer = null; - buffer.AcquirePointer(ref pointer); - status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - pointer, - (uint)buffer.ByteLength, - ref returnLength); - buffer.ReleasePointer(); - } - if (status.IsSuccessful) - { - basicInfo = buffer.Read(0); - buffer.Dispose(); - } - else - { - throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); - } - - // copy PEB - if (!ReadProcessMemory(hProcess, (void*)basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); - - // Copy RTL_USER_PROCESS_PARAMETERS. - if (!ReadProcessMemory(hProcess, (void*)peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); - - using UNICODE_STRING cmdLine = new() - { - MaximumLength = parameters.CommandLine.MaximumLength, - Length = parameters.CommandLine.Length, - Buffer = (char*)Marshal.AllocHGlobal(Marshal.SizeOf() * 260) - }; - - if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); - - return cmdLine.ToStringLength(); - } - else /** this process and that process are the same bit architecture */ - { - using SafeBuffer buffer = new(numElements: 1); - PROCESS_BASIC_INFORMATION basicInfo = default; - PEB peb = default; - RTL_USER_PROCESS_PARAMETERS parameters = default; - - // Get the PEB address. - status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - &basicInfo, - (uint)buffer.ByteLength, - ref returnLength); - while (status == Code.STATUS_INFO_LENGTH_MISMATCH) - { - buffer.Initialize(numBytes: returnLength); - byte* pointer = null; - buffer.AcquirePointer(ref pointer); - status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - pointer, - (uint)buffer.ByteLength, - ref returnLength); - buffer.ReleasePointer(); - } - if (status.IsSuccessful) - { - basicInfo = buffer.Read(0); - buffer.Dispose(); - } - else - { - throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); - } - - // copy PEB - if (!ReadProcessMemory(hProcess, basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); - - // Copy RTL_USER_PROCESS_PARAMETERS. - if (!ReadProcessMemory(hProcess, peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); - - using UNICODE_STRING cmdLine = new() - { - MaximumLength = parameters.CommandLine.MaximumLength, - Length = parameters.CommandLine.Length, - Buffer = (char*)Marshal.AllocHGlobal(Marshal.SizeOf() * 260) - }; - - if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); - - return cmdLine.ToStringLength(); - } - } - } - /// /// Release all resources owned by the current process that are associated with this handle. /// diff --git a/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs index 890069e..c43e1d9 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs @@ -38,4 +38,29 @@ public enum PS_PROTECTED_SIGNER public static bool operator ==(PS_PROTECTION left, PS_PROTECTION right) => left.Equals(right); public static bool operator !=(PS_PROTECTION left, PS_PROTECTION right) => !(left == right); + + /// + /// Produces output similar to "Light (Antimalware)" + /// + /// + public override string ToString() + { + string sType = string.Empty; + string sSigner = Signer is PS_PROTECTED_SIGNER.PsProtectedSignerNone ? string.Empty : ('(' + Signer.ToString().Replace("PsProtectedSigner", null) + ')'); + + switch (Type) + { + case PS_PROTECTED_TYPE.PsProtectedTypeNone: + sType = "None"; + break; + case PS_PROTECTED_TYPE.PsProtectedTypeProtectedLight: + sType = "Light"; + break; + case PS_PROTECTED_TYPE.PsProtectedTypeProtected: + sType = "Full"; + break; + } + + return $"{sType} {sSigner}"; + } } From 3ee71ca3a7bed2d3e64c6df6040d62bb4746ff8c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 18 Apr 2023 21:43:34 -0700 Subject: [PATCH 209/306] refactor: remove instanced process lists from FileLockerEx refactor: make FindLockingHandles return List docs: add "TODO: contemplate fuzzy search benefits" refactor: change Console.WriteLine calls to Trace.Trace*() for better logging docs: add "TODO: ProcessList garbage cleanup" All ProcessInfo activity in FindLockingHandles() is now implicit. ProcessInfo data will be instantiated and added to NativeMethods.Processes when requested by a SafeHandleEx (or inheritor's) property. TODO: ProcessList garbage collection. Items are being added to NativeMethods' static Processes, but none are removed. --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 14 +++---- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 43 ++++++++++----------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index cd9062f..e6dda50 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -9,6 +9,7 @@ namespace deadlock_dotnet_sdk.Domain //https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs public class FileLockerEx { + private List lockers; #region Properties /// A keyphrase or the full or partial path of the locked file. @@ -47,9 +48,7 @@ public List Lockers { get { - return Processes - .SelectMany(pi => pi.Handles) - .Cast() + return lockers .OrderBy(h => { switch (SortByPrimary) @@ -90,8 +89,6 @@ public List Lockers } } - public List Processes { get; private set; } - #endregion Properties /// @@ -100,7 +97,7 @@ public List Lockers public FileLockerEx() { Path = ""; - Processes = new(); + lockers = new(); } /// @@ -133,7 +130,7 @@ public FileLockerEx(string path, HandlesFilter filter, bool rethrowExceptions, o warningException = new("Failed to enable Debug Mode for greater access to processes which do not belong to the current user or admin.", uae); } - Processes = NativeMethods.FindLockingHandles(path, filter); + lockers = NativeMethods.FindLockingHandles(path, filter); } /// @@ -150,9 +147,10 @@ public enum HandlesFilter IncludeProtectedProcesses = (1 << 2) + IncludeFailedTypeQuery } + /// Clear existing handles from list and query system for new list. public void Refresh() { - Processes = NativeMethods.FindLockingHandles(Path, Filter); + lockers = NativeMethods.FindLockingHandles(Path, Filter); } } } diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 7833be1..bcab08c 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -37,6 +37,7 @@ internal static class NativeMethods #region Properties private static ProcessList processes = new(); + //TODO: ProcessList garbage collection. Count references and remove a ProcessInfo entry when references is 0 (or 1 if the only reference is by field 'processes'). public static ProcessList Processes { get @@ -142,33 +143,31 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo /// /// A list of SafeFileHandleEx objects. When requested, handles for non-file or unidentified objects will be included with file-specific properties nulled. /// - /// TODO: optimize process inspection. Stuff like IsProcessProtected should only be queried once per process - internal static List FindLockingHandles(string query, HandlesFilter filter = HandlesFilter.FilesOnly) + /// TODO: contemplate fuzzy search benefits. See FuzzySharp (https://www.nuget.org/packages/FuzzySharp | https://github.com/JakeBayer/FuzzySharp) + internal static List FindLockingHandles(string query, HandlesFilter filter = HandlesFilter.FilesOnly) { - List processes = Process - .GetProcesses() - .ToList() - .ConvertAll(p => new ProcessInfo(p)); - var handles = GetSystemHandleInfoEx() - .ToArray() - .GroupBy(h => h.UniqueProcessId); var sw = Stopwatch.StartNew(); - var results = Parallel.ForEach(processes, p => + IEnumerable> handles = GetSystemHandleInfoEx().ToArray().GroupBy(h => (int)h.UniqueProcessId); + List safeHandles = new(); + + ParallelLoopResult results; + if (!(results = Parallel.ForEach( + handles, + h => + safeHandles.AddRange( + h.ToList() + .ConvertAll(h => new(h)) + .Where(h => keep(h))))) + .IsCompleted) { - var match = handles.FirstOrDefault(group => (int)group.Key == p.Process.Id); - if (match is not null) - p.Handles.AddRange(match.ToList().ConvertAll(h => new(h)).Where(h => keep(h))); - else - return; - }); - - processes.Sort((a, b) => a.Process.Id.CompareTo(b.Process.Id)); + Trace.TraceError("The parallel ForEach loop in FindLockingHandles ended early."); + } + sw.Stop(); - Console.WriteLine("FindLockingHandles time elapsed: " + sw.Elapsed); + Trace.TraceInformation("FindLockingHandles time elapsed: " + sw.Elapsed); - //return handles; - return processes; + return safeHandles; bool keep(SafeFileHandleEx h) { @@ -200,7 +199,7 @@ bool keep(SafeFileHandleEx h) if (!keep && filter.HasFlag(HandlesFilter.IncludeProtectedProcesses)) { // if a process is protected, do not discard the handle - keep = h.ProcessIsProtected.v is true; + keep = h.ProcessInfo.ProcessIsProtected.v is true; } if (!keep && filter.HasFlag(HandlesFilter.IncludeNonFiles)) { From e7b2019daa2e79d8bbfbdb7a1c2aa531f6bdf7cb Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 23 Apr 2023 20:23:12 -0700 Subject: [PATCH 210/306] build: expose CsWin32 API publicly Only a few things need to be public, but granular exposure is not an option...yet. --- deadlock-dotnet-sdk/NativeMethods.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/NativeMethods.json b/deadlock-dotnet-sdk/NativeMethods.json index 2dc1665..c12ec2e 100644 --- a/deadlock-dotnet-sdk/NativeMethods.json +++ b/deadlock-dotnet-sdk/NativeMethods.json @@ -1,4 +1,5 @@ { "$schema": "https://aka.ms/CsWin32.schema.json", - "emitSingleFile": true + "emitSingleFile": true, + "public": true } From b18666219f793fe23a853c80de9f5ba4071bdb1e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 23 Apr 2023 20:46:50 -0700 Subject: [PATCH 211/306] fix: assign correct field offsets for PROCESS_BASIC_INFORMATION64 Some were set to default (0x00). Oops. --- .../System/Threading/PROCESS_BASIC_INFORMATION64.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs index 7479f82..a333705 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs @@ -12,8 +12,8 @@ internal readonly struct PROCESS_BASIC_INFORMATION64 { [FieldOffset(0x00)] public readonly NTSTATUS ExitStatus; [FieldOffset(0x04)] public readonly UIntPtr64 PebBaseAddress; - [FieldOffset(0x00)] public readonly KAFFINITY64 AffinityMask; - [FieldOffset(0x00)] public readonly KPRIORITY BasePriority; - [FieldOffset(0x00)] public readonly ulong UniqueProcessId; - [FieldOffset(0x00)] public readonly ulong InheritedFromUniqueProcessId; + [FieldOffset(0x0C)] public readonly KAFFINITY64 AffinityMask; + [FieldOffset(0x14)] public readonly KPRIORITY BasePriority; + [FieldOffset(0x18)] public readonly ulong UniqueProcessId; + [FieldOffset(0x20)] public readonly ulong InheritedFromUniqueProcessId; } From 7df3ebcf01f0cb43e9496358bb42f557610809e5 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 20:45:11 -0700 Subject: [PATCH 212/306] build: add CsWin32 symbol IsWow64Process2 --- deadlock-dotnet-sdk/NativeMethods.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index dbb586b..9248128 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -32,6 +32,7 @@ GetFileType GetFinalPathNameByHandle GetHandleInformation IsWow64Process +IsWow64Process2 LookupPrivilegeValue NtQueryInformationProcess NtQueryObject From 4b05eebf58191567abe0391a0f78faeb3d221bc8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 20:46:19 -0700 Subject: [PATCH 213/306] refactor: add explicit cast (T*)UIntPtr64 --- deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs index 3361caa..5924d14 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs @@ -10,4 +10,6 @@ internal struct UIntPtr64 where T : unmanaged public static explicit operator UIntPtr64(UIntPtr64 v) => v.Value; public static explicit operator UIntPtr64(UIntPtr64 v) => new() { Value = v.Value }; + + public unsafe static explicit operator T*(UIntPtr64 v) => (T*)v.Value; } From 2f1b50dbec6f39aabae327ae60608bdaa0db7b95 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 20:49:41 -0700 Subject: [PATCH 214/306] refactor: remove unused usings --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 1 - deadlock-dotnet-sdk/Exceptions/RegisterResourceException.cs | 3 --- deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 259c522..dd6173b 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -1,4 +1,3 @@ -using System.Data; using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; diff --git a/deadlock-dotnet-sdk/Exceptions/RegisterResourceException.cs b/deadlock-dotnet-sdk/Exceptions/RegisterResourceException.cs index e6e1f5d..7d644c0 100644 --- a/deadlock-dotnet-sdk/Exceptions/RegisterResourceException.cs +++ b/deadlock-dotnet-sdk/Exceptions/RegisterResourceException.cs @@ -1,5 +1,3 @@ -using PInvoke; - namespace deadlock_dotnet_sdk.Exceptions { public class RegisterResourceException : Exception @@ -17,6 +15,5 @@ public RegisterResourceException(string? message) : base(message) public RegisterResourceException(string? message, Exception innerException) : base(message, innerException) { } - } } diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs index e49dbb9..1bc6af5 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs @@ -1,5 +1,3 @@ -using System; -using System.Runtime.InteropServices; namespace Windows.Win32; /// From bcb5a946b1d5c4504e8ae1ddd158e1903779eb37 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 20:51:12 -0700 Subject: [PATCH 215/306] docs: add FindLockingHandles remarks, TODO: review keep() --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index bcab08c..86e2ae0 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -143,6 +143,7 @@ internal static unsafe IEnumerable FindLockingProcesses(string path, bo /// /// A list of SafeFileHandleEx objects. When requested, handles for non-file or unidentified objects will be included with file-specific properties nulled. /// + /// This method should remain internal because its parameters may change at any time. A public API should wrap this method. /// TODO: contemplate fuzzy search benefits. See FuzzySharp (https://www.nuget.org/packages/FuzzySharp | https://github.com/JakeBayer/FuzzySharp) internal static List FindLockingHandles(string query, HandlesFilter filter = HandlesFilter.FilesOnly) { @@ -169,6 +170,7 @@ internal static List FindLockingHandles(string query, HandlesF return safeHandles; + // TODO: review keep() for accuracy. Is field 'keep' really being set to false when needed? bool keep(SafeFileHandleEx h) { bool keep = false; From 3b61f26a9916bff6beaef7e3fc93eff5a7e1e6cd Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 20:58:19 -0700 Subject: [PATCH 216/306] refactor: make PROCESS_BASIC_INFORMATION public refactor: rename InheritedFromUniqueProcessId to ParentProcessId refactor: try IsDebugModeEnabled check and EnterDebugMode call for higher handle access --- .../Threading/PROCESS_BASIC_INFORMATION.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs index ab8db7f..03ca342 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs @@ -1,4 +1,5 @@ /// This file supplements code generated by CsWin32 +using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; using PInvoke; @@ -9,7 +10,7 @@ namespace Windows.Win32.System.Threading; /// This struct is not fully emitted by Win32Metadata. The definition in ntddk.h of Windows SDK 10.0.22621.0 has many more documented fields which are included in this manual definition /// See https://github.com/winsiderss/systeminformer@master/-/blob/phnt/include/ntpsapi.h /// -readonly struct PROCESS_BASIC_INFORMATION +public readonly struct PROCESS_BASIC_INFORMATION { public NTSTATUS ExitStatus { get; } /// @@ -21,12 +22,10 @@ readonly struct PROCESS_BASIC_INFORMATION private readonly nuint uniqueProcessId; private readonly UIntPtr inheritedFromUniqueProcessId; - /// - /// The process's ID. Backed by a pointer-sized integer field. - /// - /// + /// The process's ID. Backed by a pointer-sized integer field. public uint ProcessId => (uint)uniqueProcessId; - public uint InheritedFromUniqueProcessId => (uint)inheritedFromUniqueProcessId; + /// The ID of the parent process. Backed by a pointer-sized unsigned integer field. + public uint ParentProcessId => (uint)inheritedFromUniqueProcessId; /// /// Create an instance of PROCESS_BASIC_INFORMATION with data acquired by passing @@ -59,6 +58,14 @@ public PROCESS_BASIC_INFORMATION(SafeProcessHandle hProcess) /// Failed to open Handle to process with VM_READ and QUERY_LIMITED_INFORMATION rights public static SafeProcessHandle GetProcessHandle(uint processId) { + try + { + if (!PInvoke.IsDebugModeEnabled()) + Process.EnterDebugMode(); + } + catch (Win32Exception) + { } + var hProcess = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ | PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, true, processId); if (hProcess.IsNull) From a600f8fbc69ffd2580564b99d898a43d05ac0339 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:04:29 -0700 Subject: [PATCH 217/306] refactor: add missing managed array properties --- .../Windows.Win32/System/Threading/PEB32.cs | 24 +++++++++++++++++++ .../Windows.Win32/System/Threading/PEB64.cs | 24 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs index 1ffd7e8..3924927 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs @@ -237,6 +237,18 @@ internal unsafe uint[] TlsExpansionBitmapBits_Safe [FieldOffset(0x0218)] internal readonly UIntPtr32 FlsBitmap; /// Compatibility: 5.2 to 1809 [FieldOffset(0x021C)] internal unsafe fixed uint FlsBitmapBits[4]; + public unsafe uint[] FlsBitmapBits_Safe + { + get + { + uint[] v = new uint[4]; + + for (int i = 0; i < 4; i++) + v[i] = FlsBitmapBits[i]; + + return v; + } + } /// Compatibility: 5.2 to 1809 [FieldOffset(0x022C)] internal readonly uint FlsHighIndex; #endregion Appended for Windows Server 2003 @@ -270,6 +282,18 @@ internal unsafe uint[] TlsExpansionBitmapBits_Safe /// Compatibility: 1511 and higher
/// Type: Fixed Array of void*
[FieldOffset(0x025C)] internal unsafe fixed uint WaitOnAddressHashTable[0x80]; + internal unsafe uint[] WaitOnAddressHashTable_Safe + { + get + { + uint[] v = new uint[0x80]; + for (int i = 0; i < 0x80; i++) + v[i] = WaitOnAddressHashTable[i]; + + return v; + } + } + /// Compatibility: 1709 and higher [FieldOffset(0x045C)] internal readonly UIntPtr32 TelemetryCoverageHeader; /// Compatibility: 1709 and higher diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs index de40cb5..88680cf 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs @@ -226,6 +226,18 @@ internal unsafe uint[] TlsExpansionBitmapBits_Safe [FieldOffset(0x0338)] internal readonly UIntPtr64 FlsBitmap; /// Compatibility: 5.2 to 1809 [FieldOffset(0x0340)] internal unsafe fixed uint FlsBitmapBits[4]; + public unsafe uint[] FlsBitmapBits_Safe + { + get + { + uint[] v = new uint[4]; + + for (int i = 0; i < 4; i++) + v[i] = FlsBitmapBits[i]; + + return v; + } + } /// Compatibility: 5.2 to 1809 [FieldOffset(0x0350)] internal readonly uint FlsHighIndex; #endregion Appended for Windows Server 2003 @@ -259,6 +271,18 @@ internal unsafe uint[] TlsExpansionBitmapBits_Safe /// Compatibility: 1511 and higher
/// Type: Fixed Array of void*
[FieldOffset(0x03A0)] internal unsafe fixed ulong WaitOnAddressHashTable[0x80]; + public unsafe ulong[] WaitOnAddressHashTable_Safe + { + get + { + ulong[] v = new ulong[0x80]; + + for (int i = 0; i < 0x80; i++) + v[i] = WaitOnAddressHashTable[i]; + + return v; + } + } /// Compatibility: 1709 and higher [FieldOffset(0x07A0)] internal readonly UIntPtr64 TelemetryCoverageHeader; /// Compatibility: 1709 and higher From 8249163afb95fbd2d0c08a408308eebc34a6ba67 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:16:52 -0700 Subject: [PATCH 218/306] refactor: remove PEB_Ex refactor: rename BeingDebugged_bool to IsBeingDebugged The purpose of the incomplete PEB_Ex is already fulfilled by ProcessEnvironmentBlock and ProcessInfo. --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 2 +- .../Windows.Win32/System/Threading/PEB.cs | 4 +- .../Windows.Win32/System/Threading/PEB_Ex.cs | 47 ------------------- .../Threading/PROCESS_BASIC_INFORMATION.cs | 2 - 4 files changed, 2 insertions(+), 53 deletions(-) delete mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Ex.cs diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index ac7da33..be833c4 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -235,7 +235,7 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection } //public bool ProcessIs64Bit { get; } // unused, for now - //internal PEB_Ex? PebEx { get; } // Won't need this unless we want to start accessing otherwise unread pointer-type members of the PEB and its children (e.g. PEB_LDR_DATA, CURDIR, et cetera) + /// /// A wrapper for QueryFullProcessImageName, a system function that circumvents 32-bit process limitations when permitted the PROCESS_QUERY_LIMITED_INFORMATION right. /// diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs index 5295636..a2d445e 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs @@ -15,11 +15,9 @@ public PEB(PROCESS_BASIC_INFORMATION pbi) this = pbi.GetPeb(); } - public PEB_Ex GetPebEx(uint processId) => new(processId, this); - public bool InheritedAddressSpace => (BOOLEAN)Reserved1[0]; public bool ReadImageFileExecOptions => (BOOLEAN)Reserved1[1]; - public bool BeingDebugged_bool => (BOOLEAN)BeingDebugged; + public bool IsBeingDebugged => (BOOLEAN)BeingDebugged; #region bit field diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Ex.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Ex.cs deleted file mode 100644 index 72068b0..0000000 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Ex.cs +++ /dev/null @@ -1,47 +0,0 @@ -/// This file supplements code generated by CsWin32 -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; - -namespace Windows.Win32.System.Threading; - -/// -/// all data which must be copied via ReadProcessMemory -/// -internal class PEB_Ex -{ - public unsafe PEB_Ex(uint processId, PEB peb) - { - ProcessId = processId; - ReadVmHandle = new(PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, true, processId), true); - Peb = peb; - - //{ - // PEB_LDR_DATA* baseAddr = Peb.Ldr; - // PEB_LDR_DATA ldr = default; - // nuint br = default; - // if (PInvoke.ReadProcessMemory(ReadVmHandle, baseAddr, &ldr, (nuint)Marshal.SizeOf(), &br)) - // PebLdrData = ldr; - //} - - { - RTL_USER_PROCESS_PARAMETERS* baseAddr = Peb.ProcessParameters; - RTL_USER_PROCESS_PARAMETERS procParams = default; - nuint br = default; - if (PInvoke.ReadProcessMemory(ReadVmHandle, baseAddr, &procParams, (nuint)Marshal.SizeOf(), &br)) - { - ImagePathName = procParams.ImagePathName.ToStringLength(); - CommandLine = procParams.CommandLine.ToStringLength(); - //ProcessParameters = procParams; - } - } - } - - public uint ProcessId { get; } - public SafeProcessHandle? ReadVmHandle { get; } - public PEB Peb { get; } - - //public PEB_LDR_DATA PebLdrData { get; } // unnecessary unless we want the process's module list - public string? ImagePathName { get; } - public string? CommandLine { get; } - //public RTL_USER_PROCESS_PARAMETERS ProcessParameters { get; } // not needed unless hidden fields are implemented -} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs index 03ca342..11287c5 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION.cs @@ -91,6 +91,4 @@ public unsafe PEB GetPeb() ? throw new Win32Exception() : peb; } - - public unsafe PEB_Ex GetPebEx() => new(ProcessId, GetPeb()); } From a24601a3f5f68bff5e6bd64a86444f81fef62c5b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:18:18 -0700 Subject: [PATCH 219/306] refactor: add record struct ProcessAndHostOSArch This provides a readonly Type definition of the ValueTuple (IMAGE_FILE_MACHINE, IMAGE_FILE_MACHINE) --- deadlock-dotnet-sdk/Domain/ProcessAndHostOSArch.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 deadlock-dotnet-sdk/Domain/ProcessAndHostOSArch.cs diff --git a/deadlock-dotnet-sdk/Domain/ProcessAndHostOSArch.cs b/deadlock-dotnet-sdk/Domain/ProcessAndHostOSArch.cs new file mode 100644 index 0000000..2c9fbf0 --- /dev/null +++ b/deadlock-dotnet-sdk/Domain/ProcessAndHostOSArch.cs @@ -0,0 +1,12 @@ +using Windows.Win32.System.SystemInformation; + +namespace deadlock_dotnet_sdk.Domain; + +public record struct ProcessAndHostOSArch(IMAGE_FILE_MACHINE Process, IMAGE_FILE_MACHINE Host) +{ + public static implicit operator (IMAGE_FILE_MACHINE Process, IMAGE_FILE_MACHINE Host)(ProcessAndHostOSArch value) + => (value.Process, value.Host); + + public static implicit operator ProcessAndHostOSArch((IMAGE_FILE_MACHINE Process, IMAGE_FILE_MACHINE Host) value) + => new(value.Process, value.Host); +} From e2bd6bbc884bc8123017e5335a9f3e47d871d459 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:22:05 -0700 Subject: [PATCH 220/306] perf: replace enum.HasFlag() with & operator As it turns out, enum.HasFlag performs several Type and reference checks before it checks if the given object has the flag value. --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 6 ++-- .../Windows.Win32/System/Threading/PEB.cs | 32 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 86e2ae0..fd99a15 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -198,17 +198,17 @@ bool keep(SafeFileHandleEx h) } /* Check combined filters in reverse order */ - if (!keep && filter.HasFlag(HandlesFilter.IncludeProtectedProcesses)) + if (!keep && (filter & HandlesFilter.IncludeProtectedProcesses) == HandlesFilter.IncludeProtectedProcesses) { // if a process is protected, do not discard the handle keep = h.ProcessInfo.ProcessIsProtected.v is true; } - if (!keep && filter.HasFlag(HandlesFilter.IncludeNonFiles)) + if (!keep && (filter & HandlesFilter.IncludeNonFiles) != 0) { // keep if object type query succeeded keep = !string.IsNullOrWhiteSpace(h.HandleObjectType.v); } - if (!keep && filter.HasFlag(HandlesFilter.IncludeFailedTypeQuery)) + if (!keep && (filter & HandlesFilter.IncludeFailedTypeQuery) != 0) { keep = string.IsNullOrWhiteSpace(h.HandleObjectType.v); } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs index a2d445e..880dbee 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB.cs @@ -21,14 +21,14 @@ public PEB(PROCESS_BASIC_INFORMATION pbi) #region bit field - public bool ImageUsesLargePages => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.ImageUsesLargePages); - public bool IsProtectedProcess => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsProtectedProcess); - public bool IsImageDynamicallyRelocated => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsImageDynamicallyRelocated); - public bool SkipPatchingUser32Forwarders => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.SkipPatchingUser32Forwarders); - public bool IsPackagedProcess => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsPackagedProcess); - public bool IsAppContainer => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsAppContainer); - public bool IsProtectedProcessLight => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsProtectedProcessLight); - public bool IsLongPathAwareProcess => ((R2_bits)Reserved2[0]).HasFlag(R2_bits.IsLongPathAwareProcess); + public bool ImageUsesLargePages => (((R2_bits)Reserved2[0]) & R2_bits.ImageUsesLargePages) != 0; + public bool IsProtectedProcess => (((R2_bits)Reserved2[0]) & R2_bits.IsProtectedProcess) != 0; + public bool IsImageDynamicallyRelocated => (((R2_bits)Reserved2[0]) & R2_bits.IsImageDynamicallyRelocated) == R2_bits.IsImageDynamicallyRelocated; + public bool SkipPatchingUser32Forwarders => (((R2_bits)Reserved2[0]) & R2_bits.SkipPatchingUser32Forwarders) != 0; + public bool IsPackagedProcess => (((R2_bits)Reserved2[0]) & R2_bits.IsPackagedProcess) == R2_bits.IsPackagedProcess; + public bool IsAppContainer => (((R2_bits)Reserved2[0]) & R2_bits.IsAppContainer) == R2_bits.IsAppContainer; + public bool IsProtectedProcessLight => (((R2_bits)Reserved2[0]) & R2_bits.IsProtectedProcessLight) == R2_bits.IsProtectedProcessLight; + public bool IsLongPathAwareProcess => (((R2_bits)Reserved2[0]) & R2_bits.IsLongPathAwareProcess) != 0; [Flags] private enum R2_bits : byte @@ -90,14 +90,14 @@ public unsafe RTL_USER_PROCESS_PARAMETERS GetProcessParameters(uint processId) #region CrossProcessFlags - public bool ProcessInJob => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessInJob); - public bool ProcessInitializing => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessInitializing); - public bool ProcessUsingVEH => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessUsingVEH); - public bool ProcessUsingVCH => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessUsingVCH); - public bool ProcessUsingFTH => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessUsingFTH); - public bool ProcessPreviouslyThrottled => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessPreviouslyThrottled); - public bool ProcessCurrentlyThrottled => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessCurrentlyThrottled); - public bool ProcessImagesHotPatched => ((CrossProcessFlags)Reserved6).HasFlag(CrossProcessFlags.ProcessImagesHotPatched); // REDSTONE5 + public bool ProcessInJob => (((CrossProcessFlags)Reserved6) & CrossProcessFlags.ProcessInJob) != 0; + public bool ProcessInitializing => (((CrossProcessFlags)Reserved6) & CrossProcessFlags.ProcessInitializing) != 0; + public bool ProcessUsingVEH => (((CrossProcessFlags)Reserved6) & CrossProcessFlags.ProcessUsingVEH) != 0; + public bool ProcessUsingVCH => (((CrossProcessFlags)Reserved6) & CrossProcessFlags.ProcessUsingVCH) != 0; + public bool ProcessUsingFTH => (((CrossProcessFlags)Reserved6) & CrossProcessFlags.ProcessUsingFTH) != 0; + public bool ProcessPreviouslyThrottled => (((CrossProcessFlags)Reserved6) & CrossProcessFlags.ProcessPreviouslyThrottled) != 0; + public bool ProcessCurrentlyThrottled => (((CrossProcessFlags)Reserved6) & CrossProcessFlags.ProcessCurrentlyThrottled) != 0; + public bool ProcessImagesHotPatched => (((CrossProcessFlags)Reserved6) & CrossProcessFlags.ProcessImagesHotPatched) != 0; // REDSTONE5 [Flags] private enum CrossProcessFlags : uint From 67b48d123da514c6b6435b2131cc673e1d069c54 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:26:57 -0700 Subject: [PATCH 221/306] refactor: add wrapper classes for 32-bit, 64-bit native definitions Wrapped structs: - PROCESS_BASIC_INFORMATION - PROCESS_BASIC_INFORMATION32 - PROCESS_BASIC_INFORMATION64 - PEB32 - PEB64 - RTL_USER_PROCESS_PARAMETERS32 - RTL_USER_PROCESS_PARAMETERS64 TODO: PEB, PEB_LDR_DATA, RTL_USER_PROCESS_PARAMETERS --- .../Threading/ProcessBasicInformation.cs | 96 +++ .../ProcessEnvironmentBlock.LoaderData.cs | 47 ++ ...ocessEnvironmentBlock.ProcessParameters.cs | 207 +++++++ .../Threading/ProcessEnvironmentBlock.cs | 571 ++++++++++++++++++ 4 files changed, 921 insertions(+) create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessBasicInformation.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.LoaderData.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.ProcessParameters.cs create mode 100644 deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.cs diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessBasicInformation.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessBasicInformation.cs new file mode 100644 index 0000000..5cdcc3c --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessBasicInformation.cs @@ -0,0 +1,96 @@ +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using PInvoke; +using Code = PInvoke.NTSTATUS.Code; +using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; + +namespace Windows.Win32.System.Threading; + +public class ProcessBasicInformation +{ + internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION pbi) + { + ExitStatus = pbi.ExitStatus; + unsafe { PebBaseAddress = Environment.Is64BitProcess ? (null, (ulong)pbi.PebBaseAddress) : ((uint)pbi.PebBaseAddress, null); } + AffinityMask = Environment.Is64BitProcess ? (null, (ulong)pbi.AffinityMask) : ((uint)pbi.AffinityMask, null); + BasePriority = pbi.BasePriority; + ProcessId = pbi.ProcessId; + ParentProcessId = pbi.ParentProcessId; + } + + internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION32 pbi) + { + ExitStatus = pbi.ExitStatus; + PebBaseAddress = (pbi.PebBaseAddress, null); + AffinityMask = (pbi.AffinityMask, null); + BasePriority = pbi.BasePriority; + ProcessId = pbi.UniqueProcessId; + ParentProcessId = pbi.InheritedFromUniqueProcessId; + } + + internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION64 pbi) + { + ExitStatus = pbi.ExitStatus; + PebBaseAddress = (null, pbi.PebBaseAddress); + AffinityMask = (null, pbi.AffinityMask); + BasePriority = pbi.BasePriority; + ProcessId = (uint)pbi.UniqueProcessId; + ParentProcessId = (uint)pbi.InheritedFromUniqueProcessId; + } + + internal (UIntPtr32? w32, UIntPtr64? w64) PebBaseAddress { get; } + public ProcessEnvironmentBlock? ProcessEnvironmentBlock { get; private set; } + + public NTSTATUS ExitStatus { get; } + public (uint? w32, ulong? w64) AffinityMask { get; } + public KPRIORITY BasePriority { get; } + public uint ProcessId { get; } + public uint ParentProcessId { get; } + + /// Read the process's private memory to recursively copy the PEB. + /// A handle opened with . Requires Debug and/or admin privileges. + /// Read operation failed; The memory region is protected and Read access to the memory region was denied. + /// Unable to copy PEB; The 32-bit and 64-bit pointers are both null. + /// NtWow64ReadVirtualMemory failed to copy 64-bit PEB from target process; (native error message) + /// ReadProcessMemory failed; (native error message) + internal unsafe ProcessEnvironmentBlock GetPEB(SafeProcessHandle hProcess) + { + if (PebBaseAddress is (null, null)) + throw new NullReferenceException("Unable to copy PEB; The 32-bit and 64-bit pointers are both null."); + + using SafeBuffer buffer = new(numElements: 2); // We only use the type for allocation length. It's large enough for either PEB64 or PEB32. + + if (!Environment.Is64BitProcess && PebBaseAddress.w64 is not null) + { + ulong bytesRead64 = 0; + NTSTATUS status; + + if ((status = PInvoke.NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)PebBaseAddress.w64, (void*)buffer.DangerousGetHandle(), buffer.ByteLength, &bytesRead64)).Code is Code.STATUS_PARTIAL_COPY) + throw new AccessViolationException("NtWow64ReadVirtualMemory64 failed; The memory region is protected and Read access to the memory region was denied.", new NTStatusException(status)); + else if (status.Code is not Code.STATUS_SUCCESS) + throw new NTStatusException(status, "NtWow64ReadVirtualMemory failed to copy 64-bit PEB from target process; " + status.Message); + else + return ProcessEnvironmentBlock = new ProcessEnvironmentBlock(buffer.Read(0)); + } + else + { + nuint bytesRead = 0; + if (PebBaseAddress.w32 is not null && PInvoke.ReadProcessMemory(hProcess, (void*)PebBaseAddress.w32, (void*)buffer.DangerousGetHandle(), (nuint)buffer.ByteLength, &bytesRead)) + { + return ProcessEnvironmentBlock = new(buffer.Read(0)); + } + else if (PebBaseAddress.w64 is not null && PInvoke.ReadProcessMemory(hProcess, (void*)PebBaseAddress.w64, (void*)buffer.DangerousGetHandle(), (nuint)buffer.ByteLength, &bytesRead)) + { + return ProcessEnvironmentBlock = new(buffer.Read(0)); + } + else + { + Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError(); + if (err is Win32ErrorCode.ERROR_PARTIAL_COPY) + throw new AccessViolationException("ReadProcessMemory failed; The memory region is protected and Read access to the memory region was denied.", new Win32Exception(err)); + else + throw new Exception("ReadProcessMemory failed; " + err.GetMessage(), new Win32Exception(err)); + } + } + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.LoaderData.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.LoaderData.cs new file mode 100644 index 0000000..ea7156f --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.LoaderData.cs @@ -0,0 +1,47 @@ +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; + +namespace Windows.Win32.System.Threading; + +public partial class ProcessEnvironmentBlock +{ + public class LoaderData + { + internal LoaderData(PEB_LDR_DATA32 loaderData32) + { + EntryInProgress.w32 = loaderData32.EntryInProgress; + InInitializationOrderModuleList.w32 = loaderData32.InInitializationOrderModuleList; + Initialized = loaderData32.Initialized; + InLoadOrderModuleList.w32 = loaderData32.InLoadOrderModuleList; + InMemoryOrderModuleList.w32 = loaderData32.InMemoryOrderModuleList; + Length = loaderData32.Length; + ShutdownInProgress = loaderData32.ShutdownInProgress; + SsHandle.w32 = loaderData32.SsHandle; + } + + internal LoaderData(PEB_LDR_DATA64 loaderData64) + { + EntryInProgress.w64 = loaderData64.EntryInProgress; + InInitializationOrderModuleList.w64 = loaderData64.InInitializationOrderModuleList; + Initialized = loaderData64.Initialized; + InLoadOrderModuleList.w64 = loaderData64.InLoadOrderModuleList; + InMemoryOrderModuleList.w64 = loaderData64.InMemoryOrderModuleList; + Length = loaderData64.Length; + ShutdownInProgress = loaderData64.ShutdownInProgress; + SsHandle.w64 = loaderData64.SsHandle; + } + + public uint Length; + public BOOLEAN Initialized; + internal (UIntPtr32? w32, UIntPtr64? w64) SsHandle; + /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were loaded. Each item in the list is a pointer to an structure. See Double Linked Lists + internal (LIST_ENTRY32? w32, LIST_ENTRY64? w64) InLoadOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process in the order they appear in memory. Each item in the list is a pointer to an structure. See Double Linked Lists + internal (LIST_ENTRY32? w32, LIST_ENTRY64? w64) InMemoryOrderModuleList; + /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were initialized. Each item in the list is a pointer to an structure. See Double Linked Lists + internal (LIST_ENTRY32? w32, LIST_ENTRY64? w64) InInitializationOrderModuleList; + internal (UIntPtr32? w32, UIntPtr64? w64) EntryInProgress; // 5.1 and higher + public BOOLEAN ShutdownInProgress; // late 6.0 and higher + internal (HANDLE32? w32, HANDLE64? w64) ShutdownThreadId; // late 6.0 and higher + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.ProcessParameters.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.ProcessParameters.cs new file mode 100644 index 0000000..5cb8e68 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.ProcessParameters.cs @@ -0,0 +1,207 @@ +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using PInvoke; +using Windows.Win32.Foundation; +using static Windows.Win32.System.Threading.RTL_USER_PROCESS_PARAMETERS32; +using static Windows.Win32.System.Threading.RTL_USER_PROCESS_PARAMETERS64; +using Code = PInvoke.NTSTATUS.Code; +using Env = System.Environment; +using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; +using Win32Exception = PInvoke.Win32Exception; + +namespace Windows.Win32.System.Threading; + +public partial class ProcessEnvironmentBlock +{ + public class UserProcessParameters + { + internal UserProcessParameters(RTL_USER_PROCESS_PARAMETERS32 rupp32) + { + CommandLine.w32 = rupp32.CommandLine; + ConsoleFlags = rupp32.ConsoleFlags; + ConsoleHandle.w32 = rupp32.ConsoleHandle; + CountCharsX = rupp32.CountCharsX; + CountCharsY = rupp32.CountCharsY; + CountX = rupp32.CountX; + CountY = rupp32.CountCharsY; + CurrentDirectories.w32 = rupp32.CurrentDirectories; + CurrentDirectory.w32 = rupp32.CurrentDirectory; + DebugFlags = rupp32.DebugFlags; + DefaultThreadpoolCpuSetMaskCount = rupp32.DefaultThreadpoolCpuSetMaskCount; + DefaultThreadpoolCpuSetMasks.w32 = rupp32.DefaultThreadpoolCpuSetMasks; + DefaultThreadpoolThreadMaximum = rupp32.DefaultThreadpoolThreadMaximum; + DesktopInfo.w32 = rupp32.DesktopInfo; + DllPath.w32 = rupp32.DllPath; + Environment.w32 = rupp32.Environment; + EnvironmentSize.w32 = rupp32.EnvironmentSize; + EnvironmentVersion.w32 = rupp32.EnvironmentVersion; + FillAttribute = rupp32.FillAttribute; + Flags = rupp32.Flags; + HeapPartitionName.w32 = rupp32.HeapPartitionName; + ImagePathName.w32 = rupp32.ImagePathName; + Length = rupp32.Length; + LoaderThreads = rupp32.LoaderThreads; + MaximumLength = rupp32.MaximumLength; + PackageDependencyData.w32 = rupp32.PackageDependencyData; + ProcessGroupId = rupp32.ProcessGroupId; + RedirectionDllName.w32 = rupp32.RedirectionDllName; + RuntimeData.w32 = rupp32.RuntimeData; + ShellInfo.w32 = rupp32.ShellInfo; + ShowWindowFlags = rupp32.ShowWindowFlags; + StandardError.w32 = rupp32.StandardError; + StandardInput.w32 = rupp32.StandardInput; + StandardOutput.w32 = rupp32.StandardOutput; + StartingX = rupp32.StartingX; + StartingY = rupp32.StartingY; + WindowFlags = rupp32.WindowFlags; + WindowTitle.w32 = rupp32.WindowTitle; + } + + internal UserProcessParameters(RTL_USER_PROCESS_PARAMETERS64 rupp64) + { + CommandLine.w64 = rupp64.CommandLine; + ConsoleFlags = rupp64.ConsoleFlags; + ConsoleHandle.w64 = rupp64.ConsoleHandle; + CountCharsX = rupp64.CountCharsX; + CountCharsY = rupp64.CountCharsY; + CountX = rupp64.CountX; + CountY = rupp64.CountCharsY; + CurrentDirectories.w64 = rupp64.CurrentDirectories; + CurrentDirectory.w64 = rupp64.CurrentDirectory; + DebugFlags = rupp64.DebugFlags; + DefaultThreadpoolCpuSetMaskCount = rupp64.DefaultThreadpoolCpuSetMaskCount; + DefaultThreadpoolCpuSetMasks.w64 = rupp64.DefaultThreadpoolCpuSetMasks; + DefaultThreadpoolThreadMaximum = rupp64.DefaultThreadpoolThreadMaximum; + DesktopInfo.w64 = rupp64.DesktopInfo; + DllPath.w64 = rupp64.DllPath; + Environment.w64 = rupp64.Environment; + EnvironmentSize.w64 = rupp64.EnvironmentSize; + EnvironmentVersion.w64 = rupp64.EnvironmentVersion; + FillAttribute = rupp64.FillAttribute; + Flags = rupp64.Flags; + HeapPartitionName.w64 = rupp64.HeapPartitionName; + ImagePathName.w64 = rupp64.ImagePathName; + Length = rupp64.Length; + LoaderThreads = rupp64.LoaderThreads; + MaximumLength = rupp64.MaximumLength; + PackageDependencyData.w64 = rupp64.PackageDependencyData; + ProcessGroupId = rupp64.ProcessGroupId; + RedirectionDllName.w64 = rupp64.RedirectionDllName; + RuntimeData.w64 = rupp64.RuntimeData; + ShellInfo.w64 = rupp64.ShellInfo; + ShowWindowFlags = rupp64.ShowWindowFlags; + StandardError.w64 = rupp64.StandardError; + StandardInput.w64 = rupp64.StandardInput; + StandardOutput.w64 = rupp64.StandardOutput; + StartingX = rupp64.StartingX; + StartingY = rupp64.StartingY; + WindowFlags = rupp64.WindowFlags; + WindowTitle.w64 = rupp64.WindowTitle; + } + + public uint MaximumLength; + public uint Length; + + public uint Flags; + public uint DebugFlags; + + internal (HANDLE32? w32, HANDLE64? w64) ConsoleHandle; + internal uint ConsoleFlags; + internal (HANDLE32? w32, HANDLE64? w64) StandardInput; + internal (HANDLE32? w32, HANDLE64? w64) StandardOutput; + internal (HANDLE32? w32, HANDLE64? w64) StandardError; + + internal (CURDIR32? w32, CURDIR64? w64) CurrentDirectory; + internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) DllPath; + internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) ImagePathName; + internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) CommandLine; + + /// + /// Using a SafeProcessHandle with PROCESS_VM_READ access, copy the target process's command line string. + /// + /// A SafeProcessHandle with + /// A string containing the path of the executable image followed by the process's startup parameters. + /// Unable to get Command Line; The pointers for the 32-bit and 64-bit data are both null. + /// Failed to get Command Line; The process attempted to read protected memory. + /// Failed to get Command Line; (system-provided message) + public unsafe string GetCommandLine(SafeProcessHandle processHandle) + { + // If Resource strings are desired (e.g. for localizations), try using ResJ instead of ResX! + const string unableMsg = "Unable to get Command Line; "; + const string failedMsg = "Failed to get Command Line; "; + const string nullPtrsMsg = "The pointers for the 32-bit and 64-bit data are both null."; + const string protectedMemMsg = "The process attempted to read protected memory."; + + if (CommandLine is (null, null)) + throw new NullReferenceException(unableMsg + nullPtrsMsg); + + using UNICODE_STRING cmdLine = new() + { + Buffer = (char*)Marshal.AllocHGlobal(CommandLine.w32?.MaximumLength ?? CommandLine.w64?.MaximumLength ?? default), + Length = CommandLine.w32?.Length ?? CommandLine.w64?.Length ?? default, + MaximumLength = CommandLine.w32?.MaximumLength ?? CommandLine.w64?.MaximumLength ?? default + }; + + if (!Env.Is64BitProcess && CommandLine.w64 is not null) // we are 32-bit; they are 64-bit + { + ulong bytesRead; + NTSTATUS status; + + if ((status = PInvoke.NtWow64ReadVirtualMemory64(processHandle, (UIntPtr64)CommandLine.w64.Value.Buffer.Value, cmdLine.Buffer.Value, cmdLine.Length, &bytesRead)).Code is Code.STATUS_SUCCESS) + return cmdLine.ToStringLength(); + + if (status.Code is Code.STATUS_PARTIAL_COPY) + throw new AccessViolationException(failedMsg + protectedMemMsg, new NTStatusException(status)); + else + throw new Exception(failedMsg + status.Message, new NTStatusException(status)); + } + else + { + nuint bytesRead; + + if (CommandLine.w32 is not null && PInvoke.ReadProcessMemory(processHandle, (char*)CommandLine.w32.Value.Buffer, cmdLine.Buffer.Value, cmdLine.Length, &bytesRead)) + return cmdLine.ToStringLength(); + else if (CommandLine.w64 is not null && PInvoke.ReadProcessMemory(processHandle, (char*)CommandLine.w64.Value.Buffer, cmdLine.Buffer.Value, cmdLine.Length, &bytesRead)) + return cmdLine.ToStringLength(); + + Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError(); + if (err is Win32ErrorCode.ERROR_PARTIAL_COPY) + throw new AccessViolationException(failedMsg + protectedMemMsg, new Win32Exception(err)); + else + throw new Exception(failedMsg + err.GetMessage(), new Win32Exception(err)); + } + } + + internal (UIntPtr32? w32, UIntPtr64? w64) Environment; + + public uint StartingX; + public uint StartingY; + public uint CountX; + public uint CountY; + public uint CountCharsX; + public uint CountCharsY; + public uint FillAttribute; + + public uint WindowFlags; + public uint ShowWindowFlags; + internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) WindowTitle; + internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) DesktopInfo; + internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) ShellInfo; + internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) RuntimeData; + + internal (RTL_DRIVE_LETTER_CURDIR32[]? w32, RTL_DRIVE_LETTER_CURDIR64[]? w64) CurrentDirectories; + + internal (UIntPtr32? w32, UIntPtr64? w64) EnvironmentSize; + internal (UIntPtr32? w32, UIntPtr64? w64) EnvironmentVersion; + + internal (UIntPtr32? w32, UIntPtr64? w64) PackageDependencyData; + public uint ProcessGroupId; + public uint LoaderThreads; + + internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) RedirectionDllName; // REDSTONE4 + internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) HeapPartitionName; // 19H1 + internal (UIntPtr32? w32, UIntPtr64? w64) DefaultThreadpoolCpuSetMasks; + public uint DefaultThreadpoolCpuSetMaskCount; + public uint DefaultThreadpoolThreadMaximum; + } +} diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.cs new file mode 100644 index 0000000..c125b57 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.cs @@ -0,0 +1,571 @@ +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using PInvoke; +using Windows.Win32.Foundation; +using Windows.Win32.System.Kernel; +using Code = PInvoke.NTSTATUS.Code; +using Env = System.Environment; +using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; + +namespace Windows.Win32.System.Threading; + +public partial class ProcessEnvironmentBlock +{ + internal unsafe ProcessEnvironmentBlock(PEB32 peb32) + { + ActivationContextData = (peb32.ActivationContextData, null); + ActiveProcessAffinityMask = (peb32.ActiveProcessAffinityMask, null); + AnsiCodePageData = (peb32.AnsiCodePageData, null); + ApiSetMap = (peb32.ApiSetMap, null); + AppCompatFlags = peb32.AppCompatFlags; + AppCompatFlagsUser = peb32.AppCompatFlagsUser; + AppCompatInfo = (peb32.AppCompatInfo, null); + AtlThunkSListPtr = (peb32.AtlThunkSListPtr, null); + AtlThunkSListPtr32 = (peb32.AtlThunkSListPtr32, null); + BeingDebugged = peb32.BeingDebugged; + BitField = peb32.BitField; + CloudFileDiagFlags = peb32.CloudFileDiagFlags; + CloudFileFlags = peb32.CloudFileFlags; + CriticalSectionTimeout = peb32.CriticalSectionTimeout; + CrossProcessFlags = peb32.CrossProcessFlags; + CSDVersion = (peb32.CSDVersion, null); + CsrServerReadOnlySharedMemoryBase = peb32.CsrServerReadOnlySharedMemoryBase; + FastPebLock = (peb32.FastPebLock, null); + FlatListHead = (peb32.FlatListHead, null); + FlsBitmap = (peb32.FlsBitmap, null); + FlsBitmapBits = peb32.FlsBitmapBits_Safe; + FlsCallback = (peb32.FlsCallback, null); + FlsHighIndex = peb32.FlsHighIndex; + GdiDCAttributeList = peb32.GdiDCAttributeList; + GdiHandleBuffer = peb32.GdiHandleBuffer_Safe; + GdiSharedHandleTable = (peb32.GdiSharedHandleTable, null); + HeapDeCommitFreeBlockThreshold = (peb32.HeapDeCommitFreeBlockThreshold, null); + HeapDeCommitTotalFreeThreshold = (peb32.HeapDeCommitTotalFreeThreshold, null); + HeapSegmentCommit = (peb32.HeapSegmentCommit, null); + HeapSegmentReserve = (peb32.HeapSegmentReserve, null); + IFEOKey = (peb32.IFEOKey, null); + ImageBaseAddress = (peb32.ImageBaseAddress, null); + ImageSubsystem = peb32.ImageSubsystem; + ImageSubsystemMajorVersion = peb32.ImageSubsystemMajorVersion; + ImageSubsystemMinorVersion = peb32.ImageSubsystemMinorVersion; + InheritedAddressSpace = peb32.InheritedAddressSpace; + KernelCallBackTable = (peb32.KernelCallBackTable, null); + Ldr = (peb32.Ldr, null); + LeapSecondData = (peb32.LeapSecondData, null); + LeapSecondFlags = peb32.LeapSecondFlags; + LoaderLock = (peb32.LoaderLock, null); + MaximumNumberOfHeaps = peb32.MaximumNumberOfHeaps; + MinimumStackCommit = (peb32.MinimumStackCommit, null); + Mutant = (peb32.Mutant, null); + NtGlobalFlag = peb32.NtGlobalFlag; + NtGlobalFlag2 = peb32.NtGlobalFlag2; + NumberOfHeaps = peb32.NumberOfHeaps; + NumberOfProcessors = peb32.NumberOfProcessors; + OemCodePageData = (peb32.OemCodePageData, null); + OSBuildNumber = peb32.OSBuildNumber; + OSCSDVersion = peb32.OSCSDVersion; + OSMajorVersion = peb32.OSMajorVersion; + OSMinorVersion = peb32.OSMinorVersion; + OSPlatformId = peb32.OSPlatformId; + pContextData = (peb32.pContextData, null); + pImageHeaderHash = (peb32.pImageHeaderHash, null); + PlaceholderCompatibilityMode = peb32.PlaceholderCompatibilityMode; + PostProcessInitRoutine = (peb32.PostProcessInitRoutine, null); + ProcessAssemblyStorageMap = (peb32.ProcessAssemblyStorageMap, null); + ProcessHeap = (peb32.ProcessHeap, null); + ProcessHeaps = (peb32.ProcessHeaps, null); + ProcessParameters = (peb32.ProcessParameters, null); + ProcessTarterHelper = (peb32.ProcessTarterHelper, null); + pShimData = (peb32.pShimData, null); + ReadImageFileExecOptions = peb32.ReadImageFileExecOptions; + ReadOnlySharedMemoryBase = (peb32.ReadOnlySharedMemoryBase, null); + ReadOnlyStaticServerData = (peb32.ReadOnlyStaticServerData, null); + SessionId = peb32.SessionId; + SharedData = (peb32.SharedData, null); + SubSystemData = (peb32.SubSystemData, null); + SystemAssemblyStorageMap = (peb32.SystemAssemblyStorageMap, null); + SystemDefaultActivationContextData = (peb32.SystemDefaultActivationContextData, null); + TelemetryCoverageHeader = (peb32.TelemetryCoverageHeader, null); + TlsBitmap = (peb32.TlsBitmap, null); + TlsBitmapBits = new(peb32.TlsBitmapBits_Safe); + TlsExpansionBitmap = (peb32.TlsExpansionBitmap, null); + TlsExpansionBitmapBits = peb32.TlsExpansionBitmapBits_Safe; + TlsExpansionCounter = peb32.TlsExpansionCounter; + TppWorkerList = (peb32.TppWorkerList, null); + TppWorkerpListLock = peb32.TppWorkerpListLock; + TracingFlags = peb32.TracingFlags; + UnicodeCaseTableData = (peb32.UnicodeCaseTableData, null); + UserSharedInfoPtr = (peb32.UserSharedInfoPtr, null); + WaitOnAddressHashTable = ((UIntPtr32[])peb32.WaitOnAddressHashTable_Safe.Cast(), null); + WerRegistrationData = (peb32.WerRegistrationData, null); + WerShipAssertPtr = (peb32.WerShipAssertPtr, null); + } + + internal unsafe ProcessEnvironmentBlock(PEB64 peb64) + { + ActivationContextData = (null, peb64.ActivationContextData); + ActiveProcessAffinityMask = (null, peb64.ActiveProcessAffinityMask); + AnsiCodePageData = (null, peb64.AnsiCodePageData); + ApiSetMap = (null, peb64.ApiSetMap); + AppCompatFlags = peb64.AppCompatFlags; + AppCompatFlagsUser = peb64.AppCompatFlagsUser; + AppCompatInfo = (null, peb64.AppCompatInfo); + AtlThunkSListPtr = (null, peb64.AtlThunkSListPtr); + AtlThunkSListPtr32 = (null, peb64.AtlThunkSListPtr32); + BeingDebugged = peb64.BeingDebugged; + BitField = peb64.BitField; + CloudFileDiagFlags = peb64.CloudFileDiagFlags; + CloudFileFlags = peb64.CloudFileFlags; + CriticalSectionTimeout = peb64.CriticalSectionTimeout; + CrossProcessFlags = peb64.CrossProcessFlags; + CSDVersion = (null, peb64.CSDVersion); + CsrServerReadOnlySharedMemoryBase = peb64.CsrServerReadOnlySharedMemoryBase; + FastPebLock = (null, peb64.FastPebLock); + FlatListHead = (null, peb64.FlatListHead); + FlsBitmap = (null, peb64.FlsBitmap); + FlsBitmapBits = peb64.FlsBitmapBits_Safe; + FlsCallback = (null, peb64.FlsCallback); + FlsHighIndex = peb64.FlsHighIndex; + GdiDCAttributeList = peb64.GdiDCAttributeList; + GdiHandleBuffer = peb64.GdiHandleBuffer_Safe; + GdiSharedHandleTable = (null, peb64.GdiSharedHandleTable); + HeapDeCommitFreeBlockThreshold = (null, peb64.HeapDeCommitFreeBlockThreshold); + HeapDeCommitTotalFreeThreshold = (null, peb64.HeapDeCommitTotalFreeThreshold); + HeapSegmentCommit = (null, peb64.HeapSegmentCommit); + HeapSegmentReserve = (null, peb64.HeapSegmentReserve); + IFEOKey = (null, peb64.IFEOKey); + ImageBaseAddress = (null, peb64.ImageBaseAddress); + ImageSubsystem = peb64.ImageSubsystem; + ImageSubsystemMajorVersion = peb64.ImageSubsystemMajorVersion; + ImageSubsystemMinorVersion = peb64.ImageSubsystemMinorVersion; + InheritedAddressSpace = peb64.InheritedAddressSpace; + KernelCallBackTable = (null, peb64.KernelCallBackTable); + Ldr = (null, peb64.Ldr); + LeapSecondData = (null, peb64.LeapSecondData); + LeapSecondFlags = peb64.LeapSecondFlags; + LoaderLock = (null, peb64.LoaderLock); + MaximumNumberOfHeaps = peb64.MaximumNumberOfHeaps; + MinimumStackCommit = (null, peb64.MinimumStackCommit); + Mutant = (null, peb64.Mutant); + NtGlobalFlag = peb64.NtGlobalFlag; + NtGlobalFlag2 = peb64.NtGlobalFlag2; + NumberOfHeaps = peb64.NumberOfHeaps; + NumberOfProcessors = peb64.NumberOfProcessors; + OemCodePageData = (null, peb64.OemCodePageData); + OSBuildNumber = peb64.OSBuildNumber; + OSCSDVersion = peb64.OSCSDVersion; + OSMajorVersion = peb64.OSMajorVersion; + OSMinorVersion = peb64.OSMinorVersion; + OSPlatformId = peb64.OSPlatformId; + pContextData = (null, peb64.pContextData); + pImageHeaderHash = (null, peb64.pImageHeaderHash); + PlaceholderCompatibilityMode = peb64.PlaceholderCompatibilityMode; + PostProcessInitRoutine = (null, peb64.PostProcessInitRoutine); + ProcessAssemblyStorageMap = (null, peb64.ProcessAssemblyStorageMap); + ProcessHeap = (null, peb64.ProcessHeap); + ProcessHeaps = (null, peb64.ProcessHeaps); + ProcessParameters = (null, peb64.ProcessParameters); + ProcessTarterHelper = (null, peb64.ProcessTarterHelper); + pShimData = (null, peb64.pShimData); + ReadImageFileExecOptions = peb64.ReadImageFileExecOptions; + ReadOnlySharedMemoryBase = (null, peb64.ReadOnlySharedMemoryBase); + ReadOnlyStaticServerData = (null, peb64.ReadOnlyStaticServerData); + SessionId = peb64.SessionId; + SharedData = (null, peb64.SharedData); + SubSystemData = (null, peb64.SubSystemData); + SystemAssemblyStorageMap = (null, peb64.SystemAssemblyStorageMap); + SystemDefaultActivationContextData = (null, peb64.SystemDefaultActivationContextData); + TelemetryCoverageHeader = (null, peb64.TelemetryCoverageHeader); + TlsBitmap = (null, peb64.TlsBitmap); + TlsBitmapBits = new(peb64.TlsBitmapBits_Safe); + TlsExpansionBitmap = (null, peb64.TlsExpansionBitmap); + TlsExpansionBitmapBits = peb64.TlsExpansionBitmapBits_Safe; + TlsExpansionCounter = peb64.TlsExpansionCounter; + TppWorkerList = (null, peb64.TppWorkerList); + TppWorkerpListLock = peb64.TppWorkerpListLock; + TracingFlags = peb64.TracingFlags; + UnicodeCaseTableData = (null, peb64.UnicodeCaseTableData); + UserSharedInfoPtr = (null, peb64.UserSharedInfoPtr); + WaitOnAddressHashTable = (null, peb64.WaitOnAddressHashTable_Safe.Cast().ToArray()); + WerRegistrationData = (null, peb64.WerRegistrationData); + WerShipAssertPtr = (null, peb64.WerShipAssertPtr); + } + + #region INITIAL_PEB + /// Compatibility: all + internal readonly BOOLEAN InheritedAddressSpace; + /// Compatibility: 3.51 and higher + internal readonly BOOLEAN ReadImageFileExecOptions; + /// + /// Indicates whether the specified process is currently being debugged. The PEB structure, however, is an internal operating-system structure whose layout may change in the future. It is best to use the CheckRemoteDebuggerPresent function instead.
+ /// Compatibility: 3.51 and higher + ///
+ internal readonly BOOLEAN BeingDebugged; + public bool IsBeingDebugged => BeingDebugged; + /// Compatibility: late 5.2 and higher + internal readonly PEB_BitField BitField; + /// Compatibility: all + internal readonly (HANDLE32? w32, HANDLE64? w64) Mutant; + #endregion INITIAL_PEB + + /// Compatibility: all + internal readonly (UIntPtr32? w32, UIntPtr64? w64) ImageBaseAddress; + /// Compatibility: all
+ /// A pointer to a PEB_LDR_DATA structure that contains information about the loaded modules for the process.
+ internal readonly unsafe (UIntPtr32? w32, UIntPtr64? w64) Ldr; + + /// + /// Pass a SafeProcessHandle with PROCESS_VM_READ to copy the process's LoaderData from its memory. + /// + /// A SafeProcessHandle opened with . + /// An instance of the LoaderData class, wrapping the target process's 32-bit or 64-bit PEB_LDR_DATA. + /// Unable to get Loader data; The pointers for the 32-bit and 64-bit data are both null. + /// Failed to get Loader data; The process attempted to read protected memory. + /// Failed to get Loader data. An unknown error occurred. See Message and/or InnerException for details. + public unsafe LoaderData GetPEBLoaderData(SafeProcessHandle processHandle) + { + const string unableMsg = "Unable to get Loader data; "; + const string failedMsg = "Failed to get Loader data; "; + const string protectedMemMsg = "The process attempted to read protected memory."; + + if (Ldr is (null, null)) + throw new NullReferenceException(unableMsg + "The pointers for the 32-bit and 64-bit data are both null."); + + if (!Env.Is64BitProcess && Ldr.w64 is not null) + { + using SafeBuffer buffer = new(numElements: 2); // We need one + extra space for trailing data + ulong bytesRead; + NTSTATUS status; + + if ((status = PInvoke.NtWow64ReadVirtualMemory64(processHandle, (UIntPtr64)Ldr.w64, (void*)buffer.DangerousGetHandle(), buffer.ByteLength, &bytesRead)).Code is Code.STATUS_SUCCESS) + return new(buffer.Read(0)); + + if (status.Code is Code.STATUS_PARTIAL_COPY) + throw new AccessViolationException(failedMsg + protectedMemMsg, new NTStatusException(status)); + else + throw new Exception(failedMsg + status.Message, new NTStatusException(status)); + } + else + { + nuint bytesRead; + + if (Ldr.w32 is not null) + { + using SafeBuffer buffer = new(numElements: 2); // We need one + extra space for trailing data + + if (PInvoke.ReadProcessMemory(processHandle, (void*)Ldr.w32, (void*)buffer.DangerousGetHandle(), (nuint)buffer.ByteLength, &bytesRead)) + return new(buffer.Read(0)); + // else, jump to `err` declaration + } + else if (Ldr.w64 is not null) + { + using SafeBuffer buffer = new(numElements: 2); // We need one + extra space for trailing data + + if (PInvoke.ReadProcessMemory(processHandle, (void*)Ldr.w64, (void*)buffer.DangerousGetHandle(), (nuint)buffer.ByteLength, &bytesRead)) + return new(buffer.Read(0)); + // else, jump to `err` declaration + } + + Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError(); + if (err is Win32ErrorCode.ERROR_PARTIAL_COPY) + throw new AccessViolationException(failedMsg + protectedMemMsg, new Win32Exception(err)); + else + throw new Exception(failedMsg + err.GetMessage(), new Win32Exception(err)); + } + } + + /// Compatibility: All
+ /// A pointer to an RTL_USER_PROCESS_PARAMETERS structure that contains process parameter information such as the command line.
+ internal readonly unsafe (UIntPtr32? w32, UIntPtr64? w64) ProcessParameters; + + /// + /// Using a SafeProcessHandle with PROCESS_READ_VM access, copy the target process's 32-bit or 64-bit RTL_USER_PROCESS_PARAMETERS data from its memory. + /// + /// A SafeProcessHandle with access. + /// An instance of wrapping the process's 32-bit or 64-bit struct. Refer to and . + /// Unable to get Process Parameter data The pointer for the 32-bit and 64-bit data are both null. + /// Failed to get Process Parameter data; The process attempted to read protected memory. + /// Failed to get Process Parameter data; (native error message) + public unsafe UserProcessParameters GetUserProcessParameters(SafeProcessHandle processHandle) + { + const string unableMsg = "Unable to get Process Parameter data; "; + const string failedMsg = "Failed to get Process Parameter data; "; + const string protectedMemMsg = "The process attempted to read protected memory."; + const string nullPtrsMsg = "The pointers for the 32-bit and 64-bit data are both null."; + + if (ProcessParameters is (null, null)) + throw new NullReferenceException(unableMsg + nullPtrsMsg); + + if (!Env.Is64BitProcess && Ldr.w64 is not null) + { + using SafeBuffer buffer = new(numElements: 2); // We need one + extra space for trailing data + ulong bytesRead; + NTSTATUS status; + + if ((status = PInvoke.NtWow64ReadVirtualMemory64(processHandle, (UIntPtr64)Ldr.w64, (void*)buffer.DangerousGetHandle(), buffer.ByteLength, &bytesRead)).Code is Code.STATUS_SUCCESS) + return new(buffer.Read(0)); + + if (status.Code is Code.STATUS_PARTIAL_COPY) + throw new AccessViolationException(failedMsg + protectedMemMsg, new NTStatusException(status)); + else + throw new Exception(failedMsg + status.Message, new NTStatusException(status)); + } + else + { + nuint bytesRead; + + if (Ldr.w32 is not null) + { + using SafeBuffer buffer = new(numElements: 2); // We need one + extra space for trailing data + + if (PInvoke.ReadProcessMemory(processHandle, (void*)Ldr.w32, (void*)buffer.DangerousGetHandle(), (nuint)buffer.ByteLength, &bytesRead)) + return new(buffer.Read(0)); + // else, jump to `err` declaration + } + else if (Ldr.w64 is not null) + { + using SafeBuffer buffer = new(numElements: 2); // We need one + extra space for trailing data + + if (PInvoke.ReadProcessMemory(processHandle, (void*)Ldr.w64, (void*)buffer.DangerousGetHandle(), (nuint)buffer.ByteLength, &bytesRead)) + return new(buffer.Read(0)); + // else, jump to `err` declaration + } + + Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError(); + if (err is Win32ErrorCode.ERROR_PARTIAL_COPY) + throw new AccessViolationException(failedMsg + protectedMemMsg, new Win32Exception(err)); + else + throw new Exception(failedMsg + err.GetMessage(), new Win32Exception(err)); + } + } + + /// Compatibility: all
+ /// "SubSystem" refers to WoW64, Posix (via PSXDLL.DLL), or WSL. This stores the per-process data for the relevant subsystem.
+ internal readonly (UIntPtr32? w32, UIntPtr64? w64) SubSystemData; + /// Compatibility: all + internal readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessHeap; + + /// Compatibility: 5.1 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) FastPebLock; + /// Compatibility: late 5.2 and higher + internal readonly unsafe (UIntPtr32? w32, UIntPtr64? w64) AtlThunkSListPtr; + /// Compatibility: 6.0 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) IFEOKey; + /// Compatibility: 6.0 and higher + internal readonly PEB_CrossProcess CrossProcessFlags; + /// Compatibility: 3.51 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) KernelCallBackTable; + /// Compatibility: 6.0 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) UserSharedInfoPtr; + /// Compatibility: late 5.1; 6.1 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) AtlThunkSListPtr32; + + /// Compatibility: 6.1 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) ApiSetMap; + + /// Compatibility: all + internal readonly uint TlsExpansionCounter; + /// Compatibility: all + internal readonly (UIntPtr32? w32, UIntPtr64? w64) TlsBitmap; + /// Compatibility: all + internal readonly TlsBitmapBitsData TlsBitmapBits; + public readonly struct TlsBitmapBitsData + { + /// + /// If an array with more than two members is supplied, only the first two members are used. + /// + /// An array of two UInt32 members + public TlsBitmapBitsData(uint[] bitArray) + { + Value0 = bitArray[0]; + Value1 = bitArray[1]; + } + + public readonly uint Value0; + public readonly uint Value1; + + public static implicit operator uint[](TlsBitmapBitsData v) => new uint[] { v.Value0, v.Value1 }; + } + + /// Compatibility: all + internal readonly (UIntPtr32? w32, UIntPtr64? w64) ReadOnlySharedMemoryBase; + /// Compatibility: 1703 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) SharedData; + /// Compatibility: all + internal readonly (UIntPtr32? w32, UIntPtr64? w64) ReadOnlyStaticServerData; + /// Compatibility: all + internal readonly (UIntPtr32? w32, UIntPtr64? w64) AnsiCodePageData; + /// Compatibility: all + internal readonly (UIntPtr32? w32, UIntPtr64? w64) OemCodePageData; + /// Compatibility: all + internal readonly (UIntPtr32? w32, UIntPtr64? w64) UnicodeCaseTableData; + /// Compatibility: 3.51 and higher + internal readonly uint NumberOfProcessors; + /// Compatibility: 3.51 and higher + internal readonly uint NtGlobalFlag; + /// Compatibility: 3.51 and higher + internal readonly long CriticalSectionTimeout; + + #region Appended for Windows NT 3.51 + /// Compatibility: 3.51 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) HeapSegmentReserve; + /// Compatibility: 3.51 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) HeapSegmentCommit; + /// Compatibility: 3.51 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) HeapDeCommitTotalFreeThreshold; + /// Compatibility: 3.51 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) HeapDeCommitFreeBlockThreshold; + /// Compatibility: 3.51 and higher + internal readonly uint NumberOfHeaps; + /// Compatibility: 3.51 and higher + internal readonly uint MaximumNumberOfHeaps; + /// Compatibility: 3.51 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessHeaps; + #endregion Appended for Windows NT 3.51 + + #region Appended for Windows NT 4.0 + /// Compatibility: 3.51 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) GdiSharedHandleTable; + /// Compatibility: 4.0 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessTarterHelper; + /// Compatibility: 4.0 and higher + internal readonly uint GdiDCAttributeList; + /// Compatibility: 5.2 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) LoaderLock; + /// Compatibility: 4.0 and higher + internal readonly uint OSMajorVersion; + /// Compatibility: 4.0 and higher + internal readonly uint OSMinorVersion; + /// Compatibility: 4.0 and higher + internal readonly ushort OSBuildNumber; + /// Compatibility: 4.0 and higher + internal readonly ushort OSCSDVersion; + /// Compatibility: 4.0 and higher + internal readonly uint OSPlatformId; + /// Compatibility: 4.0 and higher + internal readonly uint ImageSubsystem; + /// Compatibility: 4.0 and higher + internal readonly uint ImageSubsystemMajorVersion; + /// Compatibility: 4.0 and higher + internal readonly uint ImageSubsystemMinorVersion; + /// Compatibility: late 6.0 and higher + internal readonly (KAFFINITY32? w32, KAFFINITY64? w64) ActiveProcessAffinityMask; + /// 4.0 and higher (x86) + internal uint[] GdiHandleBuffer; + + #endregion Appended for Windows NT 4.0 + + #region Appended for Windows 2000 + /// Compatibility: 5.0 and higher
+ /// Not supported. Type:
+ internal readonly (UIntPtr32? w32, UIntPtr64? w64) PostProcessInitRoutine; + /// Compatibility: 5.0 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) TlsExpansionBitmap; + /// Compatibility: 5.0 and higher + internal unsafe uint[] TlsExpansionBitmapBits; + /// Compatibility: 5.0 and higher
+ /// The Terminal Services session identifier associated with the current process.
+ /// The is one of the two members that Microsoft documented when required to disclose use of internal APIs by so-called middleware. + internal readonly uint SessionId; + /// Compatibility: 5.1 and higher + internal readonly PEB_AppCompat AppCompatFlags; + /// Compatibility: 5.1 and higher + internal readonly PEB_AppCompat AppCompatFlagsUser; + /// Compatibility: 5.1 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) pShimData; + /// Compatibility: 5.1 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) AppCompatInfo; + /// Compatibility: 5.1 and higher + internal readonly (UNICODE_STRING32? w32, UNICODE_STRING64? w64) CSDVersion; + #endregion Appended for Windows 2000 + + #region Appended for Windows XP + /// Compatibility: 5.1 and higher
+ /// Type: ACTIVATION_CONTEXT_DATA const * (pointer to a constant ACTIVATION_CONTEXT_DATA)
+ internal readonly (UIntPtr32? w32, UIntPtr64? w64) ActivationContextData; + /// Compatibility: 5.1 and higher + /// Type: ACTIVATION_CONTEXT_DATA * + internal readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessAssemblyStorageMap; + /// Compatibility: 5.1 and higher + /// Type: ACTIVATION_CONTEXT_DATA const * + internal readonly (UIntPtr32? w32, UIntPtr64? w64) SystemDefaultActivationContextData; + /// Compatibility: 5.1 and higher + /// Type: ASSEMBLY_STORAGE_MAP * + internal readonly (UIntPtr32? w32, UIntPtr64? w64) SystemAssemblyStorageMap; + /// Compatibility: 5.1 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) MinimumStackCommit; + #endregion Appended for Windows XP + + #region Appended for Windows Server 2003 + /// Compatibility: 5.2 to 1809 + /// Type: FLS_CALLBACK_INFO * + internal readonly (UIntPtr32? w32, UIntPtr64? w64) FlsCallback; + /// Compatibility: 5.2 to 1809 + internal readonly (LIST_ENTRY32? w32, LIST_ENTRY64? w64) FlatListHead; // 5.2 to 1809 + /// Compatibility: 5.2 to 1809 + internal readonly (UIntPtr32? w32, UIntPtr64? w64) FlsBitmap; + /// Compatibility: 5.2 to 1809 + internal uint[] FlsBitmapBits; + /// Compatibility: 5.2 to 1809 + internal readonly uint FlsHighIndex; + #endregion Appended for Windows Server 2003 + + #region Appended for Windows Vista + /// Compatibility: 6.0 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) WerRegistrationData; + /// Compatibility: 6.0 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) WerShipAssertPtr; + #endregion Appended for Windows Vista + + #region Appended for Windows 7 + /// Compatibility: 6.1 only + internal readonly (UIntPtr32? w32, UIntPtr64? w64) pContextData; + /* internal readonly (UIntPtr32? w32, UIntPtr64? w64) pUnused; */ + /// Compatibility: 6.1 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) pImageHeaderHash; + internal readonly PEB_Tracing TracingFlags; + #endregion Appended for Windows 7 + + #region Appended for Windows 8 + /// Compatibility: 6.2 and higher + internal readonly ulong CsrServerReadOnlySharedMemoryBase; + #endregion Appended for Windows 8 + + #region Appended Later in Windows 10 + /// Compatibility: 1511 and higher + internal readonly uint TppWorkerpListLock; + /// Compatibility: 1511 and higher + internal readonly (LIST_ENTRY32? w32, LIST_ENTRY64? w64) TppWorkerList; + /// Compatibility: 1511 and higher
+ internal (UIntPtr32[]? w32, UIntPtr64[]? w64) WaitOnAddressHashTable; + /// Compatibility: 1709 and higher + internal readonly (UIntPtr32? w32, UIntPtr64? w64) TelemetryCoverageHeader; + /// Compatibility: 1709 and higher + internal readonly uint CloudFileFlags; + /// Compatibility: 1803 and higher + internal readonly uint CloudFileDiagFlags; + /// Compatibility: 1803 and higher + internal readonly byte PlaceholderCompatibilityMode; + /// Compatibility: 1803 and higher + /// Type: LEAP_SECOND_DATA * + internal readonly (UIntPtr32? w32, UIntPtr64? w64) LeapSecondData; + internal readonly PEB_LeapSecond LeapSecondFlags; + /// Compatibility: 1803 and higher + /// The member is indeed named for being in some sense an extension of the much older . + /// Each corresponds to a registry value that can be in either or both of two well-known keys. + /// Each also is the name of a variable in the kernel (one exported, the other only internal), which the kernel initializes from the corresponding registry value in the Session Manager key. + /// This then provides the initial value for the corresponding member, which may then be re-initialized from the same-named registry value in the program's subkey of the Image File Execution Options.

+ /// Only one flag in the new set of them is yet known to be defined. + /// A set 0x00000001 bit in the data for the GlobalFlag2 registry value becomes a set 0x00000001 bit in the member. + /// From there it may set the bit in union with the . + /// The intended effect is that the newly exported RtlpTimeFieldsToTime and RtlpTimeToTimeFields functions become leap-second-aware: when is available, these functions accommodate 60 as the seconds field in a time.

+ /// This support for leap seconds was all new for the 1809 release and thus was also still new, roughly, for the article Leap Seconds for the IT Pro: What you need to know at a Microsoft blog dated Feb 14 2019. + /// Years later, on 27th January 2023, this is still the only match that Google finds when asked to search microsoft.com for pages that contain GlobalFlag2. + /// This is a good example of a trend in what passes as documentation. + /// At various levels of Windows administration and programming, it is often that Microsoft's only disclosure of some new feature, large or small, is a blog. + /// Administrators and programmers are inevitably grateful that Microsoft employees take the time to blog. + /// But let's please not overlook that these blogs are not documentation. + /// The helpfulness of Microsoft's employees in explaining new features in fast-moving development, and the readiness of occasionally desperate administrators and programmers to latch on to this help, disguises that Microsoft is systematically skipping the work of documenting these features.
+ internal readonly uint NtGlobalFlag2; + #endregion Appended Later in Windows 10 +} From 28d3dc59edd527d74d3046efb9bfd4c56780da03 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:27:26 -0700 Subject: [PATCH 222/306] refactor: add implicit cast of ProcessQueryHandle to SafeProcessHandle --- deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs index d8b5eb7..cfaecf7 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs @@ -39,5 +39,7 @@ public static ProcessQueryHandle OpenProcessHandle(int processId, PROCESS_ACCESS var h = OpenProcess_SafeHandle(accessRights, false, (uint)processId); return h is null ? throw new Win32Exception() : (new(h, accessRights)); } + + public static implicit operator SafeProcessHandle(ProcessQueryHandle v) => v.Handle; } } From 3ba04cca2c2123d44b8607bfeb691f7bb69d29d8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:30:51 -0700 Subject: [PATCH 223/306] refactor: remove alias of global::PInvoke.NTSTATUS Most of its properties have been reimplemented in global::Windows.Win32.Foundation.NTSTATUS --- deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs index c6fcad8..4100d73 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs @@ -9,7 +9,6 @@ using Windows.Win32.System.Threading; using MemInfo32 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION32; using MemInfo64 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION64; -using NTSTATUS_plus = PInvoke.NTSTATUS; namespace Windows.Win32; @@ -79,7 +78,7 @@ public static bool IsDebugModeEnabled() [DllImport("ntdll.dll", ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [SupportedOSPlatform("windows5.0")] - internal unsafe static extern NTSTATUS_plus NtDuplicateObject( + internal unsafe static extern NTSTATUS NtDuplicateObject( HANDLE SourceProcessHandle, HANDLE SourceHandle, [Optional] HANDLE TargetProcessHandle, @@ -145,7 +144,7 @@ internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(n } [DllImport("ntdll.dll", ExactSpelling = true, EntryPoint = "NtWow64QueryInformationProcess64")] - internal static extern unsafe NTSTATUS_plus NtWow64QueryInformationProcess64( + internal static extern unsafe NTSTATUS NtWow64QueryInformationProcess64( [In] SafeProcessHandle ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, [Out] void* ProcessInformation, @@ -154,7 +153,7 @@ [Out] uint* ReturnLength ); [DllImport("ntdll.dll", ExactSpelling = true, EntryPoint = "NtWow64ReadVirtualMemory64")] - internal static extern unsafe NTSTATUS_plus NtWow64ReadVirtualMemory64( + internal static extern unsafe NTSTATUS NtWow64ReadVirtualMemory64( [In] SafeProcessHandle ProcessHandle, [In] UIntPtr64 BaseAddress, [Out] void* Buffer, From 436427e1335b6d63ffc96994762c62e31ac2a0c9 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:34:00 -0700 Subject: [PATCH 224/306] fix: reference ProcessInfo.ProcessId instead of ProcessInfo.Process.Id The Process property is null when the .NET runtime failed to create the instance because the process's exited and its data isn't cached in the runtime's internal process list. --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index fd99a15..ddeb94d 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -44,7 +44,7 @@ public static ProcessList Processes { if (processes.Count is 0) processes = (ProcessList)Process.GetProcesses().ToList().ConvertAll(p => new(p)).ToList(); - return (ProcessList)processes.OrderBy(p => p.Process.Id).ToList(); + return (ProcessList)processes.OrderBy(p => p.ProcessId).ToList(); } set { processes = value; } } From 12e7bdb65cf395ed8bdb24641fe7d458250ecc4b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:36:47 -0700 Subject: [PATCH 225/306] refactor: add properties Is32BitEmulatedProcess, ProcessAndHostOSArch --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 106 +++++++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index be833c4..7c28f53 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -1,15 +1,16 @@ +using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; using PInvoke; using Windows.Win32; using Windows.Win32.Foundation; +using Windows.Win32.System.SystemInformation; using Windows.Win32.System.Threading; -using Windows.Win32.System.WindowsProgramming; -using static System.Environment; using static Windows.Win32.PInvoke; using static Windows.Win32.PS_PROTECTION.PS_PROTECTED_TYPE; using Code = PInvoke.NTSTATUS.Code; +using Env = System.Environment; using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; using Win32Exception = System.ComponentModel.Win32Exception; @@ -19,6 +20,8 @@ public partial class ProcessInfo { private bool canGetQueryLimitedInfoHandle; private bool canGetReadMemoryHandle; + private (bool? v, Exception? ex) is32BitEmulatedProcess; + private (ProcessAndHostOSArch? arch, Exception? ex) processAndHostOSArch; private (string? v, Exception? ex) processCommandLine; private (ProcessQueryHandle? v, Exception? ex) processHandle; private (bool? v, Exception? ex) processIsProtected; @@ -38,10 +41,109 @@ public ProcessInfo(Process process) Process = process; } + /// + /// + /// TRUE if the process is...
+ ///
    + ///
  • ...running under WOW64 on an Intel64, x64, AMD64, or ARM64 processor.

  • + ///
  • ...a 32-bit application running under 64-bit Windows 10 on ARM.

  • + ///
+ ///
+ /// + /// FALSE if the process is...
+ ///
    + ///
  • ...running under 32-bit Windows.

  • + ///
  • ...a 64-bit application running under 64-bit Windows.

  • + ///
+ ///
+ ///
+ /// This property's P/Invoke of IsWow64Process(HANDLE, BOOL) requires a process handle with or . + public (bool? v, Exception? ex) Is32BitEmulatedProcess + { + get + { + if (is32BitEmulatedProcess is (null, null)) + { + if (ProcessHandle.v is null) + { + InvalidOperationException ex = new("Unable to query Is32BitEmulatedProcess; Failed to open a process handle with the necessary access.", processHandle.ex); + processAndHostOSArch = (null, ex); + return is32BitEmulatedProcess = (null, ex); + } + else if ((ProcessHandle.v.AccessRights & (PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION)) is 0) + { + UnauthorizedAccessException ex = new("Unable to query Is32BitEmulatedProcess; A process handle was opened, but lacked the necessary access rights ", ProcessHandle.ex); + processAndHostOSArch = (null, ex); + return is32BitEmulatedProcess = (null, ex); + } + else if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 10586)) + { + unsafe + { + IMAGE_FILE_MACHINE pNativeMachine = default; + /** if UNKNOWN, then process architecture is the same as host architecture i.e. process is ARM64 and host is ARM64 + * So, pProcessMachine will never be I386 or ARM when the host is either of those. + * Because the purpose of this property and function call is to determine if we need to use 32-bit or 64-bit definitions, we only care about the following return values: + * IMAGE_FILE_MACHINE_UNKNOWN - The process is running natively. No emulation is taking place. + * IMAGE_FILE_MACHINE_I386 - The process is running through an emulation layer. The host is probably either ARM64 or AMD64. + * IMAGE_FILE_MACHINE_ARM - The process is running through an emulation layer. The host is probably either ARM64 or AMD64. If it's a Windows ARM32 PE, then it's a UWP app. + */ + if (IsWow64Process2(ProcessHandle.v.Handle, out IMAGE_FILE_MACHINE pProcessMachine, &pNativeMachine)) + { + processAndHostOSArch = ((pProcessMachine, pNativeMachine), null); + return is32BitEmulatedProcess = (pProcessMachine is IMAGE_FILE_MACHINE.IMAGE_FILE_MACHINE_I386 or IMAGE_FILE_MACHINE.IMAGE_FILE_MACHINE_ARM, null); + } + else + { + Exception ex = new("Failed to query Is32BitEmulatedProcess.", new Win32Exception()); + processAndHostOSArch = (null, ex); + return is32BitEmulatedProcess = (null, ex); + } + } + } + else if (!Windows.Win32.PInvoke.IsWow64Process(ProcessHandle.v.Handle, out BOOL IsWow64Process)) + { + return is32BitEmulatedProcess = (IsWow64Process, null); + } + else + { + return is32BitEmulatedProcess = (null, new Win32Exception()); + } + } + else + { + return is32BitEmulatedProcess; + } + } + } + /// The base Process object this instance expands upon. public Process? Process { get; } + public int ProcessId => Process?.Id ?? processId; + /// + /// The target ISA of the process and the ISA of the host OS.
+ /// -OR-
+ /// The exception thrown when attempting to get these values. + ///
+ /// The value of this property is provided during Get accessor of Is32BitEmulatedProcess + public (ProcessAndHostOSArch? v, Exception? ex) ProcessAndHostOSArch + { + get + { + if (processAndHostOSArch == default) + { + _ = Is32BitEmulatedProcess; + return processAndHostOSArch; + } + else + { + return processAndHostOSArch; + } + } + } + public (ProcessQueryHandle? v, Exception? ex) ProcessHandle { get From 29d8a205c7ec90c1a094550fbad2550ca5ee2d46 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:45:08 -0700 Subject: [PATCH 226/306] refactor: pass Is32BitEmulateProcess to GetProcessCommandLine refactor: alias System.Environment refactor: make TryGetProcessCommandLine instanced refactor: change references from processId to ProcessId --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 33 +++++++++++------------ 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 7c28f53..d9a7568 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -165,8 +165,7 @@ public ProcessInfo(Process process) { // Before assuming anything, try without PROCESS_VM_READ. Without it, we don't need Debug privilege, but the PEB and all of its recursive members (e.g. Command Line) will be unavailable. const string exAccessMsg = exMsg + " The requested permissions were denied."; - string exPermsFirst = NewLine + "First attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) + ", " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); - canGetReadMemoryHandle = false; + string exPermsFirst = Env.NewLine + "First attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) + ", " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); try { @@ -175,13 +174,11 @@ public ProcessInfo(Process process) catch (Win32Exception ex2) when ((Win32ErrorCode)ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) { // Debug Mode could not be enabled? Was SE_DEBUG_NAME denied to user or is current process not elevated? - canGetQueryLimitedInfoHandle = false; - string exPermsSecond = NewLine + "Second attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); + string exPermsSecond = Env.NewLine + "Second attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); return (null, new UnauthorizedAccessException(exAccessMsg + exPermsFirst + exPermsSecond, ex2)); } catch (Exception ex2) { - canGetQueryLimitedInfoHandle = false; return (null, new AggregateException(exMsg + " Permissions were denied and an unknown error occurred.", new Exception[] { ex, ex2 })); } } @@ -243,7 +240,7 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { return ProcessProtection.v?.Type switch { - PsProtectedTypeNone or PsProtectedTypeProtectedLight => processCommandLine = TryGetProcessCommandLine(ProcessId), + PsProtectedTypeNone or PsProtectedTypeProtectedLight => processCommandLine = TryGetProcessCommandLine(), PsProtectedTypeProtected => processCommandLine = (null, new UnauthorizedAccessException("ProcessCommandLine cannot be queried or copied; the process's Protection level prevents access to the process's command line.")), _ => processCommandLine = (null, new InvalidOperationException("ProcessCommandLine cannot be queried or copied; Failed to query the process's protection.")) }; @@ -383,10 +380,11 @@ private unsafe static string GetFullProcessImageName(uint processId) } } - private static (string? v, Exception? ex) TryGetProcessCommandLine(int processId) + private (string? v, Exception? ex) TryGetProcessCommandLine() { - if (processId == Environment.ProcessId) - return (CommandLine, null); + if (ProcessId == Env.ProcessId) + return (Env.CommandLine, null); + try { if (!IsDebugModeEnabled()) @@ -394,16 +392,16 @@ private static (string? v, Exception? ex) TryGetProcessCommandLine(int processId } catch (Exception ex) { - Trace.WriteLine("Failed check if Debug Mode was enabled or failed to enable Debug Mode for the current process." + NewLine + ex.ToString()); + Trace.WriteLine("Failed check if Debug Mode was enabled or failed to enable Debug Mode for the current process." + Env.NewLine + ex.ToString()); } - using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, (uint)processId); + using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, (uint)ProcessId); if (hProcess.IsInvalid) return (null, new Win32Exception()); try { - return (GetProcessCommandLine(hProcess), null); + return (GetProcessCommandLine(hProcess, Is32BitEmulatedProcess), null); } catch (Exception ex) { @@ -438,16 +436,17 @@ private static (string? v, Exception? ex) TryGetProcessCommandLine(int processId /// -OR- /// /// ReAllocHGlobal received a null pointer, but didn't check the error code. This is not a real OutOfMemoryException - private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess) + private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess, (bool? v, Exception? ex) isWow64Process) { if (hProcess.IsInvalid) throw new ArgumentException("The provided process handle is invalid.", paramName: nameof(hProcess)); - if (!IsWow64Process(hProcess, out BOOL targetIs32BitProcess)) - throw new Exception("Failed to determine target process is running under WOW. See InnerException.", new Win32Exception()); + if (isWow64Process.ex is not null) + throw new Exception("Failed to determine target process is running under WOW. See InnerException.", isWow64Process.ex); - bool weAre32BitAndTheyAre64Bit = !Is64BitProcess && !targetIs32BitProcess; - bool weAre64BitAndTheyAre32Bit = Is64BitProcess && targetIs32BitProcess; + bool targetIs32BitProcess = isWow64Process.v is true; + bool weAre32BitAndTheyAre64Bit = !Env.Is64BitProcess && !targetIs32BitProcess; + bool weAre64BitAndTheyAre32Bit = Env.Is64BitProcess && targetIs32BitProcess; NTSTATUS status; uint returnLength = 0; ulong bytesRead; From acb957b83cbdd72cd1cb154f74f05963b60ab662 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 22:58:20 -0700 Subject: [PATCH 227/306] perf: assign return values to backing field --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index d9a7568..65d3bf6 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -175,21 +175,21 @@ public ProcessInfo(Process process) { // Debug Mode could not be enabled? Was SE_DEBUG_NAME denied to user or is current process not elevated? string exPermsSecond = Env.NewLine + "Second attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); - return (null, new UnauthorizedAccessException(exAccessMsg + exPermsFirst + exPermsSecond, ex2)); + return processHandle = (null, new UnauthorizedAccessException(exAccessMsg + exPermsFirst + exPermsSecond, ex2)); } catch (Exception ex2) { - return (null, new AggregateException(exMsg + " Permissions were denied and an unknown error occurred.", new Exception[] { ex, ex2 })); + return processHandle = (null, new AggregateException(exMsg + " Permissions were denied and an unknown error occurred.", new Exception[] { ex, ex2 })); } } catch (Win32Exception ex) when ((Win32ErrorCode)ex.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_PARAMETER) { - return (null, new ArgumentException(exMsg + " A process with ID " + ProcessId + " could not be found. The process may have exited.", ex)); + return processHandle = (null, new ArgumentException(exMsg + " A process with ID " + ProcessId + " could not be found. The process may have exited.", ex)); } catch (Exception ex) { // unknown error - return (null, new Exception(exMsg + " An unknown error occurred.", ex)); + return processHandle = (null, new Exception(exMsg + " An unknown error occurred.", ex)); } } else From b142b962373106b8ba277b9f9a471dbe929a0ab3 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 23:01:26 -0700 Subject: [PATCH 228/306] refactor: add GetPropertiesViaProcessHandle(), ParentId --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 205 ++++++++++++++++++++++ 1 file changed, 205 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 65d3bf6..ad80533 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -21,7 +21,9 @@ public partial class ProcessInfo private bool canGetQueryLimitedInfoHandle; private bool canGetReadMemoryHandle; private (bool? v, Exception? ex) is32BitEmulatedProcess; + private (int? v, Exception?) parentId; private (ProcessAndHostOSArch? arch, Exception? ex) processAndHostOSArch; + private (ProcessBasicInformation? v, Exception? ex) processBasicInformation; private (string? v, Exception? ex) processCommandLine; private (ProcessQueryHandle? v, Exception? ex) processHandle; private (bool? v, Exception? ex) processIsProtected; @@ -199,6 +201,188 @@ public ProcessInfo(Process process) } } + /// + /// Using a SafeProcessHandle with PROCESS_QUERY_LIMITED_INFORMATION access, copy the target process's PROCESS_BASIC_INFORMATION.
+ /// If the operation succeeds and SafeProcessHandle also has PROCESS_READ_VM access, additional data (e.g. CommandLine) is copied. + ///
+ /// TODO: implement custom Exception types + public unsafe void GetPropertiesViaProcessHandle() + { + if (ProcessHandle.v is null || ProcessHandle.ex is not null) + { + var ex = new Exception("Unable to query process info; Failed to open a process handle with the necessary access rights.", ProcessHandle.ex); + processBasicInformation = (null, ex); + parentId = (null, ex); + processCommandLine = (null, ex); + return; + } + + canGetQueryLimitedInfoHandle = (ProcessHandle.v.AccessRights & PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) != 0; + canGetReadMemoryHandle = (ProcessHandle.v.AccessRights & PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ) != 0; + + if (!canGetQueryLimitedInfoHandle) + throw new Exception("Unable to query process info with access right 'PROCESS_QUERY_LIMITED_INFORMATION'; The access right was denied to this process."); + + if (Is32BitEmulatedProcess.v is null || Is32BitEmulatedProcess.ex is not null) + throw new Exception("Unable to query process's basic information; failed to determine process's targeted CPU architecture.", Is32BitEmulatedProcess.ex); + + // allocate one buffer large enough for either 64-bit or 32-bit interop. + uint returnLength = 0; + NTSTATUS status; + + /** If Win8.1 or later */ + + if (OperatingSystem.IsWindowsVersionAtLeast(6, 3) && processCommandLine is (null, null)) + { + try + { + const uint ProcessCommandLineInformation = 60u; + uint bufferLength = (uint)Marshal.SizeOf() + 2048u; + using SafeBuffer bufferCmdLine = new(numBytes: bufferLength); + + status = NtQueryInformationProcess( + ProcessHandle.v, + (PROCESSINFOCLASS)ProcessCommandLineInformation, + (void*)bufferCmdLine.DangerousGetHandle(), + (uint)bufferCmdLine.ByteLength, + ref returnLength + ); + + while ((status = NtQueryInformationProcess(ProcessHandle.v, (PROCESSINFOCLASS)ProcessCommandLineInformation, (void*)bufferCmdLine.DangerousGetHandle(), bufferLength, ref returnLength)) + .Code is Code.STATUS_INFO_LENGTH_MISMATCH) + { +#if DEBUG + Trace.TraceInformation( + "bufferLength: " + bufferCmdLine.ByteLength + Env.NewLine + + "returnLength: " + returnLength); +#endif + // !WARNING may throw OutOfMemoryException; ReAllocHGlobal received a null pointer, but didn't check the error code + // the native call to LocalReAlloc (via Marshal.ReAllocHGlobal) sometimes returns a null pointer. This is a Legacy function. Why does .NET not use malloc/realloc? + bufferCmdLine.Reallocate(numBytes: returnLength); + // none of these helped debug that internal error... + //var pinerr = Marshal.GetLastPInvokeError(); + //var syserr = Marshal.GetLastSystemError(); + //var winerr = Marshal.GetLastWin32Error(); + } + + processCommandLine = status.IsSuccessful + ? (bufferCmdLine.Read(0).ToStringLength() ?? string.Empty, null) + : throw new NTStatusException(status); // thrown, not assigned. Is the stack trace assigned if the exception is not thrown? + } + catch (Exception ex) + { + processCommandLine = (null, ex); + } + } + + try + { + using SafeBuffer bufferPBI = new(numBytes: (uint)Marshal.SizeOf()); + /** // ! this code will break if the host or process architecture isn't ARM32, AARCH64 (ARM64), i386 (x86), or AMD64/x86_x64. + * IA64 (Intel Itanium) and ARMNT (ARM32 Thumb-2 Little Endian) are the most likely culprits. + */ + /* Do we need to call NtWow64QueryInformationProcess64? */ + if (Env.Is64BitOperatingSystem && !Env.Is64BitProcess && Is32BitEmulatedProcess.v is false) // yes + { + while ((status = NtWow64QueryInformationProcess64(ProcessHandle.v, PROCESSINFOCLASS.ProcessBasicInformation, (void*)bufferPBI.DangerousGetHandle(), (uint)bufferPBI.ByteLength, &returnLength)).Code is Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL or Code.STATUS_BUFFER_OVERFLOW) + bufferPBI.Reallocate(numBytes: returnLength); + + if (status.Code is not Code.STATUS_SUCCESS) + throw new NTStatusException(status, "NtWow64QueryInformationProcess64 failed to query a process's basic information; " + status.Message); + + processBasicInformation = (new ProcessBasicInformation(bufferPBI.Read(0)), null); + } + else + { + while ((status = NtQueryInformationProcess(ProcessHandle.v, PROCESSINFOCLASS.ProcessBasicInformation, (void*)bufferPBI.DangerousGetHandle(), (uint)bufferPBI.ByteLength, ref returnLength)).Code is Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL or Code.STATUS_BUFFER_OVERFLOW) + bufferPBI.Reallocate(returnLength + (uint)IntPtr.Size); + + if (status.Code is not Code.STATUS_SUCCESS) + throw new NTStatusException(status, "NtQueryInformationProcess failed to query a process's basic information; " + status.Message); + + if ((Env.Is64BitOperatingSystem && Is32BitEmulatedProcess.v is true) || (!Env.Is64BitOperatingSystem)) // that process is 32-bit + processBasicInformation = (new ProcessBasicInformation(bufferPBI.Read(0)), null); + else + processBasicInformation = (new ProcessBasicInformation(bufferPBI.Read(0)), null); + } + } + catch (Exception ex) + { + processBasicInformation = (null, ex); + } + + if (processBasicInformation.v is not null) + { + /// fields/props to assign to if default and ProcessHandle has PROCESS_READ_VM: + /// , + if (parentId is (null, null)) + parentId = ((int)processBasicInformation.v.ParentProcessId, null); + + // if any field requires PROCESS_READ_VM and ProcessHandle lacks it, assign the exception here. + if (!canGetReadMemoryHandle) + { + try + { + throw new UnauthorizedAccessException("Unable to copy process's PEB and child objects; Failed to open SafeProcessHandle with PROCESS_VM_READ access.", ProcessHandle.ex); + } + catch (Exception ex) + { + if (processCommandLine is (null, null)) + processCommandLine = (null, ex); + } + } + else + { + try + { + //var peb = processBasicInformation.v.GetPEB(ProcessHandle.v); + //var procParams = peb.GetUserProcessParameters(ProcessHandle.v); + var procParams = processBasicInformation.v.GetPEB(ProcessHandle.v).GetUserProcessParameters(ProcessHandle.v); + + if (processCommandLine is (null, null)) + processCommandLine = (procParams.GetCommandLine(ProcessHandle.v), null); + } + catch (Exception ex) + { + if (processCommandLine is (null, null)) + processCommandLine = (null, ex); + } + } + } + else + { + try + { + throw new NullReferenceException("Unable to retrieve data; This process's ProcessBasicInformation could not be retrieved."); + } + catch (Exception ex) + { + if (parentId is (null, null)) + parentId = (null, ex); + if (processCommandLine is (null, null)) + processCommandLine = (null, ex); + } + } + } + + /// + /// The ID of the process that created/started this object's Process. Used for building tree-leaf UIs.
+ /// Source: PROCESS_BASIC_INFORMATION (this.ProcessBasicInformation) + ///
+ /// 'v' will only be null for fake processes i.e. System Idle Process, Interrupts. Check if 'ex' is null to determine successful operation. + /// TODO: ProcessBasicInformation + /// TODO: check field PROCESS_BASIC_INFORMATION.inheritedFromUniqueProcessId + public (int? v, Exception? ex) ParentId + { + get + { + if (parentId is (null, null)) + GetPropertiesViaProcessHandle(); + + return parentId; + } + } + //public bool ProcessIs64Bit { get; } // unused, for now public unsafe (bool? v, Exception? ex) ProcessIsProtected => processIsProtected == default @@ -232,6 +416,27 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection } } + // TODO: ProcessBasicInformation and recursive members + internal (ProcessBasicInformation? v, Exception? ex) ProcessBasicInformation + { + get + { + if (processBasicInformation == default) + { + GetPropertiesViaProcessHandle(); + return processBasicInformation; + } + else + { + return processBasicInformation; + } + } + } + + /// + /// + /// + /// TODO: rework for ProcessBasicInformation public (string? v, Exception? ex) ProcessCommandLine { get From 696e434630c44431654f8de53b8a02855fab9fd7 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 23:01:08 -0700 Subject: [PATCH 229/306] refactor: shorten ProcessMainModulePath, ProcessName accessors --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 64 +++++++++-------------- 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index ad80533..97c5a81 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -471,31 +471,23 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { if (processMainModulePath == default) { - if (ProcessProtection.v is not null) + if (ProcessProtection.v is null) + return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Failed to query the process's protection:" + Env.NewLine + ProcessProtection.ex)); + + if (ProcessProtection.v.Value.Type is PsProtectedTypeProtected) + return processMainModulePath = (null, new UnauthorizedAccessException("Unable to query ProcessMainModulePath; The process is protected.")); + + try { - if (ProcessProtection.v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) - { - try - { - return processMainModulePath = (GetFullProcessImageName((uint)ProcessId), null); - } - catch (Win32Exception ex) when (ex.ErrorCode == 31) - { - return processMainModulePath = (null, new InvalidOperationException("Process has exited, so the requested information is not available.", ex)); - } - catch (Exception ex) - { - return processMainModulePath = (null, ex); - } - } - else - { - return processMainModulePath = (null, new UnauthorizedAccessException("Unable to query ProcessMainModulePath; The process is protected.")); - } + return processMainModulePath = (GetFullProcessImageName((uint)ProcessId), null); } - else + catch (Win32Exception ex) when (ex.ErrorCode == 31) + { + return processMainModulePath = (null, new InvalidOperationException("Process has exited, so the requested information is not available.", ex)); + } + catch (Exception ex) { - return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Failed to query the process's protection:" + NewLine + ProcessProtection.ex)); + return processMainModulePath = (null, ex); } } else @@ -511,24 +503,16 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { if (processName == default) { - switch (ProcessId) - { - case 0: - return processName = ("System Idle Process", null); - case 4: - return processName = ("System", null); - default: - try - { - var proc = Process.GetProcessById(ProcessId); - if (proc.HasExited) - return processName = (null, new InvalidOperationException("Process has exited, so the requested information is not available.")); - else return processName = (Process.GetProcessById(ProcessId).ProcessName, null); - } - catch (Exception ex) - { - return processName = (null, ex); - } + try + { + var proc = Process.GetProcessById(ProcessId); + if (proc?.HasExited != false) + return processName = (null, new InvalidOperationException("Process has exited, so the requested information is not available.")); + else return processName = (proc.ProcessName, null); + } + catch (Exception ex) + { + return processName = (null, ex); } } else From 89aa84fe305e80a74f511247fc08b59d84f080b3 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 23:04:49 -0700 Subject: [PATCH 230/306] refractor: remove ProcessInfo.ProcessIsProtected --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index ddeb94d..f2b2843 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -201,7 +201,7 @@ bool keep(SafeFileHandleEx h) if (!keep && (filter & HandlesFilter.IncludeProtectedProcesses) == HandlesFilter.IncludeProtectedProcesses) { // if a process is protected, do not discard the handle - keep = h.ProcessInfo.ProcessIsProtected.v is true; + keep = h.ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected; } if (!keep && (filter & HandlesFilter.IncludeNonFiles) != 0) { diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 97c5a81..0f579bb 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -385,12 +385,6 @@ ref returnLength //public bool ProcessIs64Bit { get; } // unused, for now - public unsafe (bool? v, Exception? ex) ProcessIsProtected => processIsProtected == default - ? ProcessProtection.v is not null - ? (processIsProtected = (ProcessProtection.v.Value.Type > PsProtectedTypeNone, null)) - : (processIsProtected = (null, new Exception("ProcessProtection query failed.", ProcessProtection.ex))) - : processIsProtected; - public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { get From e7ce7d626bcf33c71ff13e7693cd065ae9c17a6f Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 25 Apr 2023 23:06:38 -0700 Subject: [PATCH 231/306] refactor: add ParentId to SafeFileHandleEx.ToString() refactor: use null-coalescing expression for ProcessProtection.ToString() --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index dd6173b..16a3851 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -367,10 +367,11 @@ public override string ToString() {nameof(ObjectAddress)} : {ObjectAddress} (0x{ObjectAddress:X}) {nameof(ObjectName)} : {ObjectName.v ?? ObjectName.ex?.ToString()} {nameof(ProcessId)} : {ProcessId} + {nameof(ProcessInfo.ParentId)} : {ProcessInfo.ParentId.v?.ToString() ?? ProcessInfo.ParentId.ex?.ToString() ?? string.Empty} {nameof(ProcessInfo.ProcessCommandLine)} : {ProcessInfo.ProcessCommandLine.v ?? ProcessInfo.ProcessCommandLine.ex?.ToString()} {nameof(ProcessInfo.ProcessMainModulePath)} : {ProcessInfo.ProcessMainModulePath.v ?? ProcessInfo.ProcessMainModulePath.ex?.ToString()} {nameof(ProcessInfo.ProcessName)} : {ProcessInfo.ProcessName.v ?? ProcessInfo.ProcessName.ex?.ToString()} - {nameof(ProcessInfo.ProcessProtection)} : {(ProcessInfo.ProcessProtection.v is not null ? (ProcessInfo.ProcessProtection.v.ToString()) : (ProcessInfo.ProcessProtection.ex?.ToString()))} + {nameof(ProcessInfo.ProcessProtection)} : {ProcessInfo.ProcessProtection.v?.ToString() ?? ProcessInfo.ProcessProtection.ex?.ToString() ?? string.Empty} {nameof(ExceptionLog)} : ... " + string.Concat(exLog); } From dfeb9e9b7de4e1b663d1ee4c6a139b1b270c4a22 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 26 Apr 2023 16:32:27 -0700 Subject: [PATCH 232/306] fix: SortByProperty.HandleType sorts by HandleObjectType refactor: add SortByProperty.HandleSubType for FileHandleType --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index e6dda50..8a1d853 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -26,6 +26,7 @@ public enum SortByProperty FileShareAccess, // oh, this is important! Note: System Informer seems to crash when evaluating this property // TODO: implement FileShareAccess property HandleAttributes, HandleName, + HandleSubType, HandleType, HandleValue, GrantedAccessHexadecimal, @@ -56,7 +57,8 @@ public List Lockers case SortByProperty.FileShareAccess: throw new NotImplementedException("FileShareAccess is not yet implemented!"); case SortByProperty.HandleAttributes: throw new NotImplementedException("HandleAttributes is not yet implemented!");//return h.HandleAttributes; // TODO: h.HandleAttributes case SortByProperty.HandleName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); - case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); + case SortByProperty.HandleSubType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); + case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.HandleObjectType.v?.ToString() ?? string.Empty); case SortByProperty.HandleValue: return Encoding.ASCII.GetBytes(h.HandleValue.ToString()); case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); @@ -74,7 +76,8 @@ public List Lockers case SortByProperty.FileShareAccess: throw new NotImplementedException("FileShareAccess is not yet implemented!"); case SortByProperty.HandleAttributes: throw new NotImplementedException("HandleAttributes is not yet implemented!");//return h.HandleAttributes; // TODO: h.HandleAttributes case SortByProperty.HandleName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); - case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); + case SortByProperty.HandleSubType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); + case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.HandleObjectType.v?.ToString() ?? string.Empty); case SortByProperty.HandleValue: return Encoding.ASCII.GetBytes(h.HandleValue.ToString()); case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); From ac118fca8e799d9c9614be4165dde685c4dc9005 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Wed, 26 Apr 2023 16:34:47 -0700 Subject: [PATCH 233/306] refactor: rename ObjectProperName to ObjectRealName docs: add summary for SafeFileHandleEx.FileHandleType --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 8 ++++---- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 8a1d853..26fe41c 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -17,7 +17,7 @@ public class FileLockerEx public HandlesFilter Filter { get; } public SortByProperty SortByPrimary { get; set; } = SortByProperty.ProcessId; - public SortByProperty SortBySecondary { get; set; } = SortByProperty.ObjectProperName; + public SortByProperty SortBySecondary { get; set; } = SortByProperty.ObjectRealName; /// Used by the user to choose the primary and secondary sortation orders i.e. sort by process id and then by handle value public enum SortByProperty @@ -38,7 +38,7 @@ public enum SortByProperty /// Differs from ObjectName for types {File, (Registry) Key} ///
/// TODO: get 'real' paths e.g. "\REGISTRY\MACHINE" -> "HKLM" - ObjectProperName, + ObjectRealName, ObjectAddress, ProcessId } @@ -63,7 +63,7 @@ public List Lockers case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); case SortByProperty.ObjectOriginalName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); - case SortByProperty.ObjectProperName: throw new NotImplementedException("ObjectTrueName is not yet implemented!"); + case SortByProperty.ObjectRealName: throw new NotImplementedException("ObjectTrueName is not yet implemented!"); case SortByProperty.ObjectAddress: return BitConverter.GetBytes((ulong)h.ObjectAddress); case SortByProperty.ProcessId: return BitConverter.GetBytes(h.ProcessId); default: goto case SortByProperty.ProcessId; @@ -82,7 +82,7 @@ public List Lockers case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); case SortByProperty.ObjectOriginalName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); - case SortByProperty.ObjectProperName: throw new NotImplementedException("ObjectTrueName is not yet implemented!"); + case SortByProperty.ObjectRealName: throw new NotImplementedException("ObjectTrueName is not yet implemented!"); case SortByProperty.ObjectAddress: return BitConverter.GetBytes((ulong)h.ObjectAddress); case SortByProperty.ProcessId: return BitConverter.GetBytes(h.ProcessId); default: goto case SortByProperty.ProcessId; diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 16a3851..d1c8dbe 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -67,6 +67,11 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( : (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex))) : isFileHandle; + /// + /// If the handle object's Type is "File", the type of the File object
+ /// -OR-
+ /// An exception if the P/Invoke operation failed or the object's Type is not "File". + ///
public (FileType? v, Exception? ex) FileHandleType { get From 8c740cd11b0b613d4bbd10014b3311911be645ce Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 27 Apr 2023 19:40:43 -0700 Subject: [PATCH 234/306] feat: add property safeFileHandleEx.IsFilePathRemote refactor: make FileHandleType, FileNameInfo check ProcessProtection In System Informer, data gathered by GetFileType and GetFileInformationByHandleEx is not accessible when the handle's owner (i.e. a process) has Light or full-level protection. If these queries work despite protection, it may be a security oversight. --- .../Domain/SafeFileHandleEx.cs | 68 +++++++++++++++++-- deadlock-dotnet-sdk/NativeMethods.txt | 4 +- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index d1c8dbe..7d27c0f 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -24,6 +24,7 @@ namespace deadlock_dotnet_sdk.Domain; public class SafeFileHandleEx : SafeHandleEx { private (bool? v, Exception? ex) isFileHandle; + private (bool? v, Exception? ex) isFilePathRemote; private (FileType? v, Exception? ex) fileHandleType; private (string? v, Exception? ex) fileNameInfo; private (string? v, Exception? ex) fileFullPath; @@ -67,6 +68,52 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( : (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex))) : isFileHandle; + /// + /// TRUE if the file object's path is a network path i.e. SMB2 network share. FALSE if the file was opened via a local disk path. + /// -OR- + /// Exception encountered because GetFileInformationByHandleEx failed + /// + /// + /// + /// GetFileInformationByHandleEx is another poorly documented win32 + /// function due to the variety of parameters and conditional return + /// values. When + /// is passed to the function, it will try to write a + /// to the supplied buffer. + /// If the file handle's path is not remote, then the function + /// returns . + /// + /// + /// For the particulars of GetFileInformationByHandleEx, see...
+ /// * GetFileInformationByHandleEx function (winbase.h) | Microsoft Learn
+ /// * c++ - Detect if file is open locally or over share - Stack Overflow
+ /// * FileSystemWatcher Fencing(Part 1) – Windows SDK Support Team Blog
+ ///
+ ///
+ public (bool? v, Exception? ex) IsFilePathRemote + { + get + { + if (isFilePathRemote is (null, null)) + { + Win32ErrorCode err; + FILE_REMOTE_PROTOCOL_INFO info; + unsafe + { + return GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileRemoteProtocolInfo, &info, (uint)Marshal.SizeOf(info)) + ? (isFilePathRemote = (true, null)) + : (err = (Win32ErrorCode)Marshal.GetLastPInvokeError()) is Win32ErrorCode.ERROR_INVALID_PARAMETER + ? (isFilePathRemote = (false, null)) + : (isFilePathRemote = (null, new Win32Exception(err))); + } + } + else + { + return isFilePathRemote; + } + } + } + /// /// If the handle object's Type is "File", the type of the File object
/// -OR-
@@ -76,13 +123,18 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { get { - if (fileHandleType == default) + if (fileHandleType is (null, null)) { + const string unableErr = "Unable to query FileHandleType; "; + if (ProcessInfo.ProcessProtection.ex is not null) + return (null, new NullReferenceException(unableErr + "Failed to query the process's protection level.")); + if (ProcessInfo.ProcessProtection.ex is not null) + return (null, new UnauthorizedAccessException(unableErr + "The process's protection prohibits this operation.")); if (IsFileHandle.v is not true) - return (null, new InvalidOperationException("Unable to query File handle type; This operation is only valid on File handles.")); + return (null, new InvalidOperationException(unableErr + "This operation is only valid on File handles.")); FileType type = (FileType)GetFileType(handle); - var err = new Win32Exception(); + Win32Exception err = new(); return err.ErrorCode is 0 /* success */ ? fileHandleType = (type, null) : fileHandleType = (null, err); @@ -100,11 +152,13 @@ public unsafe (string? v, Exception? ex) FileNameInfo { if (fileNameInfo == default) { + const string unableErr = "Unable to query FileNameInfo; "; + if (ProcessInfo.ProcessProtection.ex is not null) + return fileNameInfo = (null, new NullReferenceException(unableErr + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex)); + if (ProcessInfo.ProcessProtection.v?.Type is not PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone) + return fileNameInfo = (null, new UnauthorizedAccessException(unableErr + "The process's protection prohibits querying a file handle's FILE_NAME_INFO.")); if (FileHandleType.v is not FileType.Disk) - return (null, new InvalidOperationException("FileNameInfo can only be queried for disk-type file handles.")); - //TODO: check if process protection inhibits function - //if (ProcessProtection.ex is not null) - //if (ProcessProtection.v?.Value.Type ) + return (null, new InvalidOperationException(unableErr + "FileNameInfo can only be queried for disk-type file handles.")); /* Get fni.FileNameLength */ FILE_NAME_INFO fni = default; diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index 9248128..95f47b6 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -28,6 +28,7 @@ RM_PROCESS_INFO CheckTokenMembership CloseHandle DuplicateHandle +GetFileInformationByHandleEx GetFileType GetFinalPathNameByHandle GetHandleInformation @@ -81,6 +82,5 @@ SE_DEBUG_NAME //ProcessProtectionInformation // PInvoke001: Method, type or constant "ProcessProtectionInformation" not found. //PS_PROTECTION // PInvoke001: Method, type or constant "PS_PROTECTION" not found. //PsProtectedTypeProtected // PInvoke001: Method, type or constant "PsProtectedTypeProtected" not found -GetFileInformationByHandleEx FILE_NAME_INFO - +FILE_REMOTE_PROTOCOL_INFO From df1c8e0b4f0fdba38cfdfb74868d30cc9ba8aee7 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 27 Apr 2023 20:22:14 -0700 Subject: [PATCH 235/306] refactor: make CsWin32 additions public --- .../Windows.Win32/Foundation/HANDLE32.cs | 8 +- .../Windows.Win32/Foundation/HANDLE64.cs | 8 +- .../Foundation/UNICODE_STRING32.cs | 2 +- .../Foundation/UNICODE_STRING64.cs | 2 +- deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs | 16 +- .../SYSTEM_HANDLE_INFORMATION_EX.cs | 2 +- .../SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 4 +- .../System/Kernel/KAFFINITY32.cs | 2 +- .../System/Kernel/KAFFINITY64.cs | 2 +- .../System/Kernel/LIST_ENTRY32.cs | 6 +- .../System/Kernel/LIST_ENTRY64.cs | 6 +- .../System/Kernel/RTL_BALANCED_NODE32.cs | 16 +- .../System/Kernel/RTL_BALANCED_NODE64.cs | 16 +- .../System/Threading/CURDIR32.cs | 4 +- .../System/Threading/CURDIR64.cs | 4 +- .../Windows.Win32/System/Threading/PEB32.cs | 182 +++++++++--------- .../Windows.Win32/System/Threading/PEB64.cs | 180 ++++++++--------- .../System/Threading/PEB_AppCompat.cs | 2 +- .../System/Threading/PEB_CrossProcess.cs | 2 +- .../System/Threading/PEB_LDR_DATA32.cs | 20 +- .../System/Threading/PEB_LDR_DATA64.cs | 20 +- .../System/Threading/PEB_LeapSecond.cs | 2 +- .../System/Threading/PEB_Tracing.cs | 2 +- .../PPS_POST_PROCESS_INIT_ROUTINE.cs | 4 +- .../Threading/PROCESS_BASIC_INFORMATION32.cs | 2 +- .../Threading/PROCESS_BASIC_INFORMATION64.cs | 2 +- .../Threading/ProcessBasicInformation.cs | 10 +- .../ProcessEnvironmentBlock.LoaderData.cs | 16 +- ...ocessEnvironmentBlock.ProcessParameters.cs | 46 ++--- .../Threading/ProcessEnvironmentBlock.cs | 178 ++++++++--------- .../Threading/RTL_CRITICAL_SECTION32.cs | 14 +- .../Threading/RTL_CRITICAL_SECTION64.cs | 14 +- .../Threading/RTL_DRIVE_LETTER_CURDIR32.cs | 2 +- .../Threading/RTL_DRIVE_LETTER_CURDIR64.cs | 4 +- .../RTL_USER_PROCESS_PARAMETERS32.cs | 2 +- .../RTL_USER_PROCESS_PARAMETERS64.cs | 2 +- .../LDR_DATA_TABLE_ENTRY32.cs | 2 +- .../LDR_DATA_TABLE_ENTRY64.cs | 2 +- .../WindowsProgramming/LDR_DLL_LOAD_REASON.cs | 2 +- .../WindowsProgramming/LdrEntryFlags.cs | 2 +- .../OBJECT_INFORMATION_CLASS.cs | 2 +- .../OBJECT_NAME_INFORMATION.cs | 2 +- .../OBJECT_TYPES_INFORMATION.cs | 2 +- .../OBJECT_TYPE_INFORMATION.cs | 2 +- .../SYSTEM_INFORMATION_CLASS.cs | 2 +- .../Windows.Win32/UIntPtr32.cs | 4 +- .../Windows.Win32/UIntPtr64.cs | 2 +- .../Windows.Win32/UIntPtr64_T.cs | 3 +- 48 files changed, 416 insertions(+), 415 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs index b4cde0b..d951e7a 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE32.cs @@ -1,16 +1,16 @@ namespace Windows.Win32.Foundation; -internal readonly struct HANDLE32 +public readonly struct HANDLE32 { - internal readonly UIntPtr32 Value { get; init; } + public readonly UIntPtr32 Value { get; init; } public static explicit operator HANDLE32(UIntPtr32 v) => new() { Value = v }; public static implicit operator UIntPtr32(HANDLE32 v) => v.Value; } -internal readonly struct HANDLE32 where T : unmanaged +public readonly struct HANDLE32 where T : unmanaged { - internal readonly UIntPtr32 Value { get; init; } + public readonly UIntPtr32 Value { get; init; } public static explicit operator HANDLE32(UIntPtr32 v) => new() { Value = v }; public static implicit operator UIntPtr32(HANDLE32 v) => v.Value; diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs index 8bb1375..4854ea5 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE64.cs @@ -1,16 +1,16 @@ namespace Windows.Win32.Foundation; -internal readonly struct HANDLE64 +public readonly struct HANDLE64 { - internal readonly UIntPtr64 Value { get; init; } + public readonly UIntPtr64 Value { get; init; } public static explicit operator HANDLE64(UIntPtr64 v) => new() { Value = v }; public static implicit operator UIntPtr64(HANDLE64 v) => v.Value; } -internal readonly struct HANDLE64 where T : unmanaged +public readonly struct HANDLE64 where T : unmanaged { - internal readonly UIntPtr64 Value { get; init; } + public readonly UIntPtr64 Value { get; init; } public static explicit operator HANDLE64(UIntPtr64 v) => new() { Value = v }; public static implicit operator UIntPtr64(HANDLE64 v) => v.Value; diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING32.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING32.cs index c456d44..ff4e39b 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING32.cs @@ -11,7 +11,7 @@ namespace Windows.Win32.Foundation; /// The MaximumLength is used to indicate the length of Buffer so that if the string is passed to a conversion routine such as RtlAnsiStringToUnicodeString the returned string does not exceed the buffer size. /// [StructLayout(LayoutKind.Sequential, Size = 0x08)] -internal struct UNICODE_STRING32 +public struct UNICODE_STRING32 { public ushort Length; public ushort MaximumLength; diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING64.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING64.cs index 864d81c..ba0a2e2 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/UNICODE_STRING64.cs @@ -3,7 +3,7 @@ namespace Windows.Win32.Foundation; [StructLayout(LayoutKind.Sequential, Size = 0x10)] -internal struct UNICODE_STRING64 +public struct UNICODE_STRING64 { public ushort Length; public ushort MaximumLength; diff --git a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs index 4100d73..c91f4ae 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs @@ -78,7 +78,7 @@ public static bool IsDebugModeEnabled() [DllImport("ntdll.dll", ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [SupportedOSPlatform("windows5.0")] - internal unsafe static extern NTSTATUS NtDuplicateObject( + public unsafe static extern NTSTATUS NtDuplicateObject( HANDLE SourceProcessHandle, HANDLE SourceHandle, [Optional] HANDLE TargetProcessHandle, @@ -92,7 +92,7 @@ uint Options /// /// This is currently unused because we don't need this much information about a handle's owning process. However, it would be a shame to remove it entirely. // TODO: split off into 'C#: Reading 32-bit process's memory from 64-bit and vice versa' project - internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(nuint lpAddress) + public static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(nuint lpAddress) { SIZE_T bufferSize = default; int size64 = default; @@ -144,7 +144,7 @@ internal static unsafe (MemInfo32 memInfo32, MemInfo64 memInfo64) VirtualQuery(n } [DllImport("ntdll.dll", ExactSpelling = true, EntryPoint = "NtWow64QueryInformationProcess64")] - internal static extern unsafe NTSTATUS NtWow64QueryInformationProcess64( + public static extern unsafe NTSTATUS NtWow64QueryInformationProcess64( [In] SafeProcessHandle ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, [Out] void* ProcessInformation, @@ -153,7 +153,7 @@ [Out] uint* ReturnLength ); [DllImport("ntdll.dll", ExactSpelling = true, EntryPoint = "NtWow64ReadVirtualMemory64")] - internal static extern unsafe NTSTATUS NtWow64ReadVirtualMemory64( + public static extern unsafe NTSTATUS NtWow64ReadVirtualMemory64( [In] SafeProcessHandle ProcessHandle, [In] UIntPtr64 BaseAddress, [Out] void* Buffer, @@ -164,7 +164,7 @@ [Out] ulong* NumberOfBytesRead /// /// A SafeProcessHandle to the [SupportedOSPlatform("windows5.1.2600")] - internal static unsafe SafeProcessHandle OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS dwDesiredAccess, bool bInheritHandle, uint dwProcessId) + public static unsafe SafeProcessHandle OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS dwDesiredAccess, bool bInheritHandle, uint dwProcessId) { HANDLE __result = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); return new SafeProcessHandle(__result, ownsHandle: true); @@ -200,13 +200,13 @@ SIZE_T dwLength ///
/// An access token contains a list of the privileges held by the account associated with the token. These privileges can be enabled or disabled; most are disabled by default. The PrivilegeCheck function checks only for enabled privileges. To get a list of all the enabled and disabled privileges held by an access token, call the GetTokenInformation function. To enable or disable a set of privileges in an access token, call the AdjustTokenPrivileges function.
/// - internal static bool PrivilegeCheck(SafeHandle ClientToken, ref PRIVILEGE_SET RequiredPrivileges) + public static bool PrivilegeCheck(SafeHandle ClientToken, ref PRIVILEGE_SET RequiredPrivileges) => !PrivilegeCheck(ClientToken, ref RequiredPrivileges, out int pfResult) ? throw new Win32Exception() : (BOOL)pfResult; /// /// - internal static SafeFileHandle OpenProcessToken(SafeFileHandle ProcessHandle, TOKEN_ACCESS_MASK DesiredAccess) + public static SafeFileHandle OpenProcessToken(SafeFileHandle ProcessHandle, TOKEN_ACCESS_MASK DesiredAccess) => OpenProcessToken(ProcessHandle, DesiredAccess, out SafeFileHandle TokenHandle) ? TokenHandle : throw new Win32Exception(); - internal static HANDLE_FLAGS GetHandleInformation(SafeHandle hObject) => (HANDLE_FLAGS)(GetHandleInformation(hObject, out uint flags) ? flags : throw new Win32Exception()); + public static HANDLE_FLAGS GetHandleInformation(SafeHandle hObject) => (HANDLE_FLAGS)(GetHandleInformation(hObject, out uint flags) ? flags : throw new Win32Exception()); } diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs index ccb3282..799abc7 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_INFORMATION_EX.cs @@ -50,7 +50,7 @@ public ReadOnlySpan AsSpan() /// /// DEBUGGING | Test for memory access. System.AccessViolationException due to these values being in a protected memory range is a problem. /// - internal void CheckAccess() + public void CheckAccess() { var tmp = AsSpan(); diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 852c991..af969c1 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -36,7 +36,7 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX /// ULONG_PTR, cast to HANDLE, int, or uint public nuint UniqueProcessId { get; } /// ULONG_PTR, cast to HANDLE - internal HANDLE HandleValue { get; } + public HANDLE HandleValue { get; } /// Get the HandleValue as a SafeObjectHandle. Closing this SafeObjectHandle does *not* close the source handle. public SafeObjectHandle GetSafeHandle() => new(HandleValue, false); /// This is a bitwise "Flags" data type. @@ -78,7 +78,7 @@ public unsafe string GetHandleObjectType() : throw new NTStatusException(status); } - internal unsafe HANDLE_FLAGS GetHandleInfo() + public unsafe HANDLE_FLAGS GetHandleInfo() { try { diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY32.cs index 92dcf35..b0c92cb 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY32.cs @@ -1,6 +1,6 @@ namespace Windows.Win32.System.Kernel; -internal struct KAFFINITY32 +public struct KAFFINITY32 { public uint Value; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY64.cs index 4b3cac5..80a1b0d 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/KAFFINITY64.cs @@ -1,6 +1,6 @@ namespace Windows.Win32.System.Kernel; -internal struct KAFFINITY64 +public struct KAFFINITY64 { public ulong Value; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs index 778342f..7c1c275 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY32.cs @@ -1,15 +1,15 @@ namespace Windows.Win32.System.Kernel; /// -internal struct LIST_ENTRY32 +public struct LIST_ENTRY32 { /// /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. /// Read more on docs.microsoft.com. /// - internal unsafe UIntPtr32/* */ Flink; + public unsafe UIntPtr32/* */ Flink; /// /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. /// Read more on docs.microsoft.com. /// - internal unsafe UIntPtr32/* */ Blink; + public unsafe UIntPtr32/* */ Blink; } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs index 162fb66..9259e71 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/LIST_ENTRY64.cs @@ -1,18 +1,18 @@ namespace Windows.Win32.System.Kernel; /// -internal readonly struct LIST_ENTRY64 +public readonly struct LIST_ENTRY64 { /// /// For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list. For a LIST_ENTRY structure that serves as the list header, the Flink member points to the first entry in the list or to the LIST_ENTRY structure itself if the list is empty. /// Read more on docs.microsoft.com. /// /// Points to a LIST_ENTRY64 - internal readonly UIntPtr64/* */ Flink; + public readonly UIntPtr64/* */ Flink; /// /// For a LIST_ENTRY structure that serves as a list entry, the Blink member points to the previous entry in the list or to the list header if there is no previous entry in the list. For a LIST_ENTRY structure that serves as the list header, the Blink member points to the last entry in the list or to the LIST_ENTRY structure itself if the list is empty. /// Read more on docs.microsoft.com. /// /// Points to a LIST_ENTRY64 - internal readonly UIntPtr64/* */ Blink; + public readonly UIntPtr64/* */ Blink; } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE32.cs index 6005635..79c2ab4 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE32.cs @@ -7,16 +7,16 @@ namespace Windows.Win32.System.Kernel; /// /// [StructLayout(LayoutKind.Explicit, Size = 0x0C)] -internal unsafe struct RTL_BALANCED_NODE32 +public unsafe struct RTL_BALANCED_NODE32 { - [FieldOffset(0x00)] internal fixed uint _Children[2]; - internal UIntPtr32[] Children => new UIntPtr32[] { _Children[0], _Children[1] }; - [FieldOffset(0x00)] internal UIntPtr32 Left; - [FieldOffset(0x04)] internal UIntPtr32 Right; + [FieldOffset(0x00)] private fixed uint _Children[2]; + public UIntPtr32[] Children => new UIntPtr32[] { _Children[0], _Children[1] }; + [FieldOffset(0x00)] public UIntPtr32 Left; + [FieldOffset(0x04)] public UIntPtr32 Right; - [FieldOffset(0x08)] internal UIntPtr32 ParentValue; + [FieldOffset(0x08)] public UIntPtr32 ParentValue; /// applies if the node is in a Red Black tree - internal byte Red => (byte)(ParentValue & 0b1); + public byte Red => (byte)(ParentValue & 0b1); /// applies if the node is in an AVL tree - internal byte Balance => (byte)((ParentValue >> 1) & 0b11); + public byte Balance => (byte)((ParentValue >> 1) & 0b11); } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs index cb242b5..edacc99 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Kernel/RTL_BALANCED_NODE64.cs @@ -7,19 +7,19 @@ namespace Windows.Win32.System.Kernel; /// ///
[StructLayout(LayoutKind.Explicit, Size = 0x18)] -internal unsafe struct RTL_BALANCED_NODE64 +public unsafe struct RTL_BALANCED_NODE64 { - [FieldOffset(0x00)] internal fixed uint _Children[2]; - internal UIntPtr64[] Children => new UIntPtr64[] { _Children[0], _Children[1] }; + [FieldOffset(0x00)] private fixed uint _Children[2]; + public UIntPtr64[] Children => new UIntPtr64[] { _Children[0], _Children[1] }; [FieldOffset(0x00)] private UIntPtr64/* */ left; [FieldOffset(0x08)] private UIntPtr64/* */ right; - [FieldOffset(0x10)] internal UIntPtr64 ParentValue; + [FieldOffset(0x10)] public UIntPtr64 ParentValue; /// applies if the node is in a Red Black tree - internal byte Red => (byte)(ParentValue & 0b1); + public byte Red => (byte)(ParentValue & 0b1); /// applies if the node is in an AVL tree - internal byte Balance => (byte)((ParentValue >> 1) & 0b11); + public byte Balance => (byte)((ParentValue >> 1) & 0b11); - internal UIntPtr64 Left { get => (UIntPtr64)left; set => left = (UIntPtr64)value; } - internal UIntPtr64 Right { get => (UIntPtr64)right; set => right = (UIntPtr64)value; } + public UIntPtr64 Left { get => (UIntPtr64)left; set => left = (UIntPtr64)value; } + public UIntPtr64 Right { get => (UIntPtr64)right; set => right = (UIntPtr64)value; } } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR32.cs index e2fda83..75935d8 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR32.cs @@ -3,13 +3,13 @@ namespace Windows.Win32.System.Threading; -internal partial struct RTL_USER_PROCESS_PARAMETERS32 +public partial struct RTL_USER_PROCESS_PARAMETERS32 { /// /// See /// [StructLayout(LayoutKind.Sequential, Size = 0x0C)] - internal struct CURDIR32 + public struct CURDIR32 { public UNICODE_STRING64 DosPath; public HANDLE64 Handle; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR64.cs index 18846cc..39af55f 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/CURDIR64.cs @@ -3,13 +3,13 @@ namespace Windows.Win32.System.Threading; -internal partial struct RTL_USER_PROCESS_PARAMETERS64 +public partial struct RTL_USER_PROCESS_PARAMETERS64 { /// /// See /// [StructLayout(LayoutKind.Sequential, Size = 0x18)] - internal struct CURDIR64 + public struct CURDIR64 { public UNICODE_STRING64 DosPath; /// diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs index 3924927..97a40f6 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB32.cs @@ -8,59 +8,59 @@ namespace Windows.Win32.System.Threading; /// /// [StructLayout(LayoutKind.Explicit)] -internal struct PEB32 +public struct PEB32 { #region INITIAL_PEB /// Compatibility: all - [FieldOffset(0x00)] internal readonly BOOLEAN InheritedAddressSpace; + [FieldOffset(0x00)] public readonly BOOLEAN InheritedAddressSpace; /// Compatibility: 3.51 and higher - [FieldOffset(0x01)] internal readonly BOOLEAN ReadImageFileExecOptions; + [FieldOffset(0x01)] public readonly BOOLEAN ReadImageFileExecOptions; /// /// Indicates whether the specified process is currently being debugged. The PEB structure, however, is an internal operating-system structure whose layout may change in the future. It is best to use the CheckRemoteDebuggerPresent function instead.
/// Compatibility: 3.51 and higher ///
- [FieldOffset(0x02)] internal readonly BOOLEAN BeingDebugged; + [FieldOffset(0x02)] public readonly BOOLEAN BeingDebugged; /// Compatibility: late 5.2 and higher - [FieldOffset(0x03)] internal readonly PEB_BitField BitField; + [FieldOffset(0x03)] public readonly PEB_BitField BitField; /// Compatibility: all - [FieldOffset(0x04)] internal readonly HANDLE32 Mutant; + [FieldOffset(0x04)] public readonly HANDLE32 Mutant; #endregion INITIAL_PEB /// Compatibility: all - [FieldOffset(0x08)] internal readonly UIntPtr32 ImageBaseAddress; + [FieldOffset(0x08)] public readonly UIntPtr32 ImageBaseAddress; /// Compatibility: all
/// A pointer to a PEB_LDR_DATA structure that contains information about the loaded modules for the process.
- [FieldOffset(0x0C)] internal readonly unsafe UIntPtr32 Ldr; + [FieldOffset(0x0C)] public readonly unsafe UIntPtr32 Ldr; /// Compatibility: All
/// A pointer to an RTL_USER_PROCESS_PARAMETERS structure that contains process parameter information such as the command line.
- [FieldOffset(0x10)] internal readonly unsafe UIntPtr32 ProcessParameters; + [FieldOffset(0x10)] public readonly unsafe UIntPtr32 ProcessParameters; /// Compatibility: all
/// "SubSystem" refers to WoW64, Posix (via PSXDLL.DLL), or WSL. This stores the per-process data for the relevant subsystem.
- [FieldOffset(0x14)] internal readonly UIntPtr32 SubSystemData; + [FieldOffset(0x14)] public readonly UIntPtr32 SubSystemData; /// Compatibility: all - [FieldOffset(0x18)] internal readonly UIntPtr32 ProcessHeap; + [FieldOffset(0x18)] public readonly UIntPtr32 ProcessHeap; /// Compatibility: 3.10 to 5.0 [FieldOffset(0x1C), Obsolete] private readonly UIntPtr32 FastPebLock_obsolete; /// Compatibility: 5.1 and higher - [FieldOffset(0x1C)] internal readonly UIntPtr32 FastPebLock; + [FieldOffset(0x1C)] public readonly UIntPtr32 FastPebLock; /// Compatibility: 3.10 to 5.1 [FieldOffset(0x20), Obsolete] private readonly UIntPtr32 FastPebLockRoutine; /// Compatibility: late 5.2 and higher - [FieldOffset(0x20)] internal readonly unsafe UIntPtr32 AtlThunkSListPtr; + [FieldOffset(0x20)] public readonly unsafe UIntPtr32 AtlThunkSListPtr; /// Compatibility: 3.10 to 5.1 [FieldOffset(0x24), Obsolete] private readonly UIntPtr32 FastPebUnlockRoutine; /// Compatibility: 6.0 and higher - [FieldOffset(0x24)] internal readonly UIntPtr32 IFEOKey; + [FieldOffset(0x24)] public readonly UIntPtr32 IFEOKey; /// Compatibility: 3.50 to 5.2 [FieldOffset(0x28), Obsolete] private readonly uint EnvironmentUpdateCount; /// Compatibility: 6.0 and higher - [FieldOffset(0x28)] internal readonly PEB_CrossProcess CrossProcessFlags; + [FieldOffset(0x28)] public readonly PEB_CrossProcess CrossProcessFlags; /// Compatibility: 3.51 and higher - [FieldOffset(0x2C)] internal readonly UIntPtr32 KernelCallBackTable; + [FieldOffset(0x2C)] public readonly UIntPtr32 KernelCallBackTable; /// Compatibility: 6.0 and higher - [FieldOffset(0x2C)] internal readonly UIntPtr32 UserSharedInfoPtr; + [FieldOffset(0x2C)] public readonly UIntPtr32 UserSharedInfoPtr; /// Compatibility: 3.50 to 4.0 [FieldOffset(0x30), Obsolete] private readonly HANDLE32 EventLogSection; /// Compatibility: 3.50 to 4.0 @@ -71,100 +71,100 @@ internal struct PEB32 /// Compatibility: early 5.1; early 5.2 [Obsolete] private uint ExecutionOptions => _executionOptions & 0b11; /// Compatibility: late 5.1; 6.1 and higher - [FieldOffset(0x34)] internal readonly UIntPtr32 AtlThunkSListPtr32; + [FieldOffset(0x34)] public readonly UIntPtr32 AtlThunkSListPtr32; /// Compatibility: 3.10 to early 6.0
/// Type: PEB_FREE_BLOCK*
[FieldOffset(0x38), Obsolete] private readonly UIntPtr32 FreeList; /// Compatibility: 6.1 and higher - [FieldOffset(0x38)] internal readonly UIntPtr32 ApiSetMap; + [FieldOffset(0x38)] public readonly UIntPtr32 ApiSetMap; /// Compatibility: all - [FieldOffset(0x3C)] internal readonly uint TlsExpansionCounter; + [FieldOffset(0x3C)] public readonly uint TlsExpansionCounter; /// Compatibility: all - [FieldOffset(0x40)] internal readonly UIntPtr32 TlsBitmap; + [FieldOffset(0x40)] public readonly UIntPtr32 TlsBitmap; /// Compatibility: all - [FieldOffset(0x44)] internal unsafe fixed uint TlsBitmapBits[2]; - internal readonly unsafe uint[] TlsBitmapBits_Safe => new uint[2] { TlsBitmapBits[0], TlsBitmapBits[1] }; + [FieldOffset(0x44)] private unsafe fixed uint TlsBitmapBits[2]; + public readonly unsafe uint[] TlsBitmapBits_Safe => new uint[2] { TlsBitmapBits[0], TlsBitmapBits[1] }; /// Compatibility: all - [FieldOffset(0x4C)] internal readonly UIntPtr32 ReadOnlySharedMemoryBase; + [FieldOffset(0x4C)] public readonly UIntPtr32 ReadOnlySharedMemoryBase; /// Compatibility: 3.10 to 5.2 [FieldOffset(0x50), Obsolete] private readonly UIntPtr32 ReadOnlySharedMemoryHeap; /// Compatibility: 6.0 to 6.2 [FieldOffset(0x50), Obsolete] private readonly UIntPtr32 HotpatchInformation; /// Compatibility: 1703 and higher - [FieldOffset(0x50)] internal readonly UIntPtr32 SharedData; + [FieldOffset(0x50)] public readonly UIntPtr32 SharedData; /// Compatibility: all - [FieldOffset(0x54)] internal readonly UIntPtr32 ReadOnlyStaticServerData; + [FieldOffset(0x54)] public readonly UIntPtr32 ReadOnlyStaticServerData; /// Compatibility: all - [FieldOffset(0x58)] internal readonly UIntPtr32 AnsiCodePageData; + [FieldOffset(0x58)] public readonly UIntPtr32 AnsiCodePageData; /// Compatibility: all - [FieldOffset(0x5C)] internal readonly UIntPtr32 OemCodePageData; + [FieldOffset(0x5C)] public readonly UIntPtr32 OemCodePageData; /// Compatibility: all - [FieldOffset(0x60)] internal readonly UIntPtr32 UnicodeCaseTableData; + [FieldOffset(0x60)] public readonly UIntPtr32 UnicodeCaseTableData; /// Compatibility: 3.51 and higher - [FieldOffset(0x64)] internal readonly uint NumberOfProcessors; + [FieldOffset(0x64)] public readonly uint NumberOfProcessors; /// Compatibility: 3.51 and higher - [FieldOffset(0x68)] internal readonly uint NtGlobalFlag; + [FieldOffset(0x68)] public readonly uint NtGlobalFlag; /// Compatibility: 3.10 to 3.50 [FieldOffset(0x68), Obsolete] private readonly long CriticalSectionTimeout_obsolete; /// Compatibility: 3.51 and higher - [FieldOffset(0x70)] internal readonly long CriticalSectionTimeout; + [FieldOffset(0x70)] public readonly long CriticalSectionTimeout; #region Appended for Windows NT 3.51 /// Compatibility: 3.51 and higher - [FieldOffset(0x78)] internal readonly UIntPtr32 HeapSegmentReserve; + [FieldOffset(0x78)] public readonly UIntPtr32 HeapSegmentReserve; /// Compatibility: 3.51 and higher - [FieldOffset(0x7C)] internal readonly UIntPtr32 HeapSegmentCommit; + [FieldOffset(0x7C)] public readonly UIntPtr32 HeapSegmentCommit; /// Compatibility: 3.51 and higher - [FieldOffset(0x80)] internal readonly UIntPtr32 HeapDeCommitTotalFreeThreshold; + [FieldOffset(0x80)] public readonly UIntPtr32 HeapDeCommitTotalFreeThreshold; /// Compatibility: 3.51 and higher - [FieldOffset(0x84)] internal readonly UIntPtr32 HeapDeCommitFreeBlockThreshold; + [FieldOffset(0x84)] public readonly UIntPtr32 HeapDeCommitFreeBlockThreshold; /// Compatibility: 3.51 and higher - [FieldOffset(0x88)] internal readonly uint NumberOfHeaps; + [FieldOffset(0x88)] public readonly uint NumberOfHeaps; /// Compatibility: 3.51 and higher - [FieldOffset(0x8C)] internal readonly uint MaximumNumberOfHeaps; + [FieldOffset(0x8C)] public readonly uint MaximumNumberOfHeaps; /// Compatibility: 3.51 and higher - [FieldOffset(0x90)] internal readonly UIntPtr32 ProcessHeaps; + [FieldOffset(0x90)] public readonly UIntPtr32 ProcessHeaps; #endregion Appended for Windows NT 3.51 #region Appended for Windows NT 4.0 /// Compatibility: 3.51 and higher - [FieldOffset(0x94)] internal readonly UIntPtr32 GdiSharedHandleTable; + [FieldOffset(0x94)] public readonly UIntPtr32 GdiSharedHandleTable; /// Compatibility: 4.0 and higher - [FieldOffset(0x98)] internal readonly UIntPtr32 ProcessTarterHelper; + [FieldOffset(0x98)] public readonly UIntPtr32 ProcessTarterHelper; /// Compatibility: 4.0 and higher - [FieldOffset(0x9C)] internal readonly uint GdiDCAttributeList; + [FieldOffset(0x9C)] public readonly uint GdiDCAttributeList; /// Compatibility: 4.0 to 5.1 [FieldOffset(0xA0), Obsolete] private readonly UIntPtr32 LoaderLock_obsolete; /// Compatibility: 5.2 and higher - [FieldOffset(0xA0)] internal readonly UIntPtr32 LoaderLock; + [FieldOffset(0xA0)] public readonly UIntPtr32 LoaderLock; /// Compatibility: 4.0 and higher - [FieldOffset(0xA4)] internal readonly uint OSMajorVersion; + [FieldOffset(0xA4)] public readonly uint OSMajorVersion; /// Compatibility: 4.0 and higher - [FieldOffset(0xA8)] internal readonly uint OSMinorVersion; + [FieldOffset(0xA8)] public readonly uint OSMinorVersion; /// Compatibility: 4.0 and higher - [FieldOffset(0xAC)] internal readonly ushort OSBuildNumber; + [FieldOffset(0xAC)] public readonly ushort OSBuildNumber; /// Compatibility: 4.0 and higher - [FieldOffset(0xAE)] internal readonly ushort OSCSDVersion; + [FieldOffset(0xAE)] public readonly ushort OSCSDVersion; /// Compatibility: 4.0 and higher - [FieldOffset(0xB0)] internal readonly uint OSPlatformId; + [FieldOffset(0xB0)] public readonly uint OSPlatformId; /// Compatibility: 4.0 and higher - [FieldOffset(0xB4)] internal readonly uint ImageSubsystem; + [FieldOffset(0xB4)] public readonly uint ImageSubsystem; /// Compatibility: 4.0 and higher - [FieldOffset(0xB8)] internal readonly uint ImageSubsystemMajorVersion; + [FieldOffset(0xB8)] public readonly uint ImageSubsystemMajorVersion; /// Compatibility: 4.0 and higher - [FieldOffset(0xBC)] internal readonly uint ImageSubsystemMinorVersion; + [FieldOffset(0xBC)] public readonly uint ImageSubsystemMinorVersion; /// Compatibility: 4.0 to early 6.0 [FieldOffset(0xC0), Obsolete] private readonly KAFFINITY32 ImageProcessAffinityMask; /// Compatibility: late 6.0 and higher - [FieldOffset(0xC0)] internal readonly KAFFINITY32 ActiveProcessAffinityMask; + [FieldOffset(0xC0)] public readonly KAFFINITY32 ActiveProcessAffinityMask; /// (only 0x22 array members instead of 0x3C) Compatibility: 4.0 to early 6.0 [FieldOffset(0xC4), Obsolete] private unsafe fixed uint GdiHandleBuffer_obsolete[0x22]; /// 4.0 and higher (x86) - [FieldOffset(0xC4)] internal unsafe fixed uint GdiHandleBuffer[0x3C]; - internal unsafe uint[] GdiHandleBuffer_Safe + [FieldOffset(0xC4)] private unsafe fixed uint GdiHandleBuffer[0x3C]; + public unsafe uint[] GdiHandleBuffer_Safe { get { @@ -177,12 +177,12 @@ internal unsafe uint[] GdiHandleBuffer_Safe #region Appended for Windows 2000 /// Compatibility: 5.0 and higher
/// Not supported. Type:
- [FieldOffset(0x014C)] internal readonly UIntPtr32 PostProcessInitRoutine; + [FieldOffset(0x014C)] public readonly UIntPtr32 PostProcessInitRoutine; /// Compatibility: 5.0 and higher - [FieldOffset(0x0150)] internal readonly UIntPtr32 TlsExpansionBitmap; + [FieldOffset(0x0150)] public readonly UIntPtr32 TlsExpansionBitmap; /// Compatibility: 5.0 and higher - [FieldOffset(0x0154)] internal unsafe fixed uint TlsExpansionBitmapBits[0x20]; - internal unsafe uint[] TlsExpansionBitmapBits_Safe + [FieldOffset(0x0154)] private unsafe fixed uint TlsExpansionBitmapBits[0x20]; + public unsafe uint[] TlsExpansionBitmapBits_Safe { get { @@ -193,50 +193,50 @@ internal unsafe uint[] TlsExpansionBitmapBits_Safe /// Compatibility: 5.0 and higher
/// The Terminal Services session identifier associated with the current process.
/// The is one of the two members that Microsoft documented when required to disclose use of internal APIs by so-called middleware. - [FieldOffset(0x01D4)] internal readonly uint SessionId; + [FieldOffset(0x01D4)] public readonly uint SessionId; /// Compatibility: 5.1 and higher - [FieldOffset(0x01D8)] internal readonly PEB_AppCompat AppCompatFlags; + [FieldOffset(0x01D8)] public readonly PEB_AppCompat AppCompatFlags; /// Compatibility: 5.1 and higher - [FieldOffset(0x01E0)] internal readonly PEB_AppCompat AppCompatFlagsUser; + [FieldOffset(0x01E0)] public readonly PEB_AppCompat AppCompatFlagsUser; /// Compatibility: 5.1 and higher - [FieldOffset(0x01E8)] internal readonly UIntPtr32 pShimData; + [FieldOffset(0x01E8)] public readonly UIntPtr32 pShimData; /// Compatibility: 5.0 [FieldOffset(0x01D8), Obsolete] private readonly UIntPtr32 AppCompatInfo_NT5; /// Compatibility: 5.1 and higher - [FieldOffset(0x01EC)] internal readonly UIntPtr32 AppCompatInfo; + [FieldOffset(0x01EC)] public readonly UIntPtr32 AppCompatInfo; /// Compatibility: 5.0 [FieldOffset(0x01DC), Obsolete] private readonly UNICODE_STRING32 CSDVersion_NT5; /// Compatibility: 5.1 and higher - [FieldOffset(0x01F0)] internal readonly UNICODE_STRING32 CSDVersion; + [FieldOffset(0x01F0)] public readonly UNICODE_STRING32 CSDVersion; #endregion Appended for Windows 2000 #region Appended for Windows XP /// Compatibility: 5.1 and higher
/// Type: ACTIVATION_CONTEXT_DATA const * (pointer to a constant ACTIVATION_CONTEXT_DATA)
- [FieldOffset(0x01F8)] internal readonly UIntPtr32 ActivationContextData; + [FieldOffset(0x01F8)] public readonly UIntPtr32 ActivationContextData; /// Compatibility: 5.1 and higher /// Type: ACTIVATION_CONTEXT_DATA * - [FieldOffset(0x01FC)] internal readonly UIntPtr32 ProcessAssemblyStorageMap; + [FieldOffset(0x01FC)] public readonly UIntPtr32 ProcessAssemblyStorageMap; /// Compatibility: 5.1 and higher /// Type: ACTIVATION_CONTEXT_DATA const * - [FieldOffset(0x0200)] internal readonly UIntPtr32 SystemDefaultActivationContextData; + [FieldOffset(0x0200)] public readonly UIntPtr32 SystemDefaultActivationContextData; /// Compatibility: 5.1 and higher /// Type: ASSEMBLY_STORAGE_MAP * - [FieldOffset(0x204)] internal readonly UIntPtr32 SystemAssemblyStorageMap; + [FieldOffset(0x204)] public readonly UIntPtr32 SystemAssemblyStorageMap; /// Compatibility: 5.1 and higher - [FieldOffset(0x208)] internal readonly UIntPtr32 MinimumStackCommit; + [FieldOffset(0x208)] public readonly UIntPtr32 MinimumStackCommit; #endregion Appended for Windows XP #region Appended for Windows Server 2003 /// Compatibility: 5.2 to 1809 /// Type: FLS_CALLBACK_INFO * - [FieldOffset(0x020C)] internal readonly UIntPtr32 FlsCallback; + [FieldOffset(0x020C)] public readonly UIntPtr32 FlsCallback; /// Compatibility: 5.2 to 1809 - [FieldOffset(0x0210)] internal readonly LIST_ENTRY32 FlatListHead; // 5.2 to 1809 + [FieldOffset(0x0210)] public readonly LIST_ENTRY32 FlatListHead; // 5.2 to 1809 /// Compatibility: 5.2 to 1809 - [FieldOffset(0x0218)] internal readonly UIntPtr32 FlsBitmap; + [FieldOffset(0x0218)] public readonly UIntPtr32 FlsBitmap; /// Compatibility: 5.2 to 1809 - [FieldOffset(0x021C)] internal unsafe fixed uint FlsBitmapBits[4]; + [FieldOffset(0x021C)] private unsafe fixed uint FlsBitmapBits[4]; public unsafe uint[] FlsBitmapBits_Safe { get @@ -250,39 +250,39 @@ public unsafe uint[] FlsBitmapBits_Safe } } /// Compatibility: 5.2 to 1809 - [FieldOffset(0x022C)] internal readonly uint FlsHighIndex; + [FieldOffset(0x022C)] public readonly uint FlsHighIndex; #endregion Appended for Windows Server 2003 #region Appended for Windows Vista /// Compatibility: 6.0 and higher - [FieldOffset(0x0230)] internal readonly UIntPtr32 WerRegistrationData; + [FieldOffset(0x0230)] public readonly UIntPtr32 WerRegistrationData; /// Compatibility: 6.0 and higher - [FieldOffset(0x0234)] internal readonly UIntPtr32 WerShipAssertPtr; + [FieldOffset(0x0234)] public readonly UIntPtr32 WerShipAssertPtr; #endregion Appended for Windows Vista #region Appended for Windows 7 /// Compatibility: 6.1 only - [FieldOffset(0x0238)] internal readonly UIntPtr32 pContextData; + [FieldOffset(0x0238)] public readonly UIntPtr32 pContextData; /* [FieldOffset(0x0238)] internal readonly UIntPtr32 pUnused; */ /// Compatibility: 6.1 and higher - [FieldOffset(0x023C)] internal readonly UIntPtr32 pImageHeaderHash; - [FieldOffset(0x0240)] internal readonly PEB_Tracing TracingFlags; + [FieldOffset(0x023C)] public readonly UIntPtr32 pImageHeaderHash; + [FieldOffset(0x0240)] public readonly PEB_Tracing TracingFlags; #endregion Appended for Windows 7 #region Appended for Windows 8 /// Compatibility: 6.2 and higher - [FieldOffset(0x0248)] internal readonly ulong CsrServerReadOnlySharedMemoryBase; + [FieldOffset(0x0248)] public readonly ulong CsrServerReadOnlySharedMemoryBase; #endregion Appended for Windows 8 #region Appended Later in Windows 10 /// Compatibility: 1511 and higher - [FieldOffset(0x0250)] internal readonly uint TppWorkerpListLock; + [FieldOffset(0x0250)] public readonly uint TppWorkerpListLock; /// Compatibility: 1511 and higher - [FieldOffset(0x0254)] internal readonly LIST_ENTRY32 TppWorkerList; + [FieldOffset(0x0254)] public readonly LIST_ENTRY32 TppWorkerList; /// Compatibility: 1511 and higher
/// Type: Fixed Array of void*
- [FieldOffset(0x025C)] internal unsafe fixed uint WaitOnAddressHashTable[0x80]; - internal unsafe uint[] WaitOnAddressHashTable_Safe + [FieldOffset(0x025C)] private unsafe fixed uint WaitOnAddressHashTable[0x80]; + public unsafe uint[] WaitOnAddressHashTable_Safe { get { @@ -295,17 +295,17 @@ internal unsafe uint[] WaitOnAddressHashTable_Safe } /// Compatibility: 1709 and higher - [FieldOffset(0x045C)] internal readonly UIntPtr32 TelemetryCoverageHeader; + [FieldOffset(0x045C)] public readonly UIntPtr32 TelemetryCoverageHeader; /// Compatibility: 1709 and higher - [FieldOffset(0x0460)] internal readonly uint CloudFileFlags; + [FieldOffset(0x0460)] public readonly uint CloudFileFlags; /// Compatibility: 1803 and higher - [FieldOffset(0x0464)] internal readonly uint CloudFileDiagFlags; + [FieldOffset(0x0464)] public readonly uint CloudFileDiagFlags; /// Compatibility: 1803 and higher - [FieldOffset(0x0468)] internal readonly byte PlaceholderCompatibilityMode; + [FieldOffset(0x0468)] public readonly byte PlaceholderCompatibilityMode; /// Compatibility: 1803 and higher /// Type: LEAP_SECOND_DATA * - [FieldOffset(0x0470)] internal readonly UIntPtr32 LeapSecondData; - [FieldOffset(0x0474)] internal readonly PEB_LeapSecond LeapSecondFlags; + [FieldOffset(0x0470)] public readonly UIntPtr32 LeapSecondData; + [FieldOffset(0x0474)] public readonly PEB_LeapSecond LeapSecondFlags; /// Compatibility: 1803 and higher /// The member is indeed named for being in some sense an extension of the much older . /// Each corresponds to a registry value that can be in either or both of two well-known keys. @@ -322,6 +322,6 @@ internal unsafe uint[] WaitOnAddressHashTable_Safe /// Administrators and programmers are inevitably grateful that Microsoft employees take the time to blog. /// But let's please not overlook that these blogs are not documentation. /// The helpfulness of Microsoft's employees in explaining new features in fast-moving development, and the readiness of occasionally desperate administrators and programmers to latch on to this help, disguises that Microsoft is systematically skipping the work of documenting these features. - [FieldOffset(0x0478)] internal readonly uint NtGlobalFlag2; + [FieldOffset(0x0478)] public readonly uint NtGlobalFlag2; #endregion Appended Later in Windows 10 } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs index 88680cf..01d2c87 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB64.cs @@ -8,154 +8,154 @@ namespace Windows.Win32.System.Threading; /// ///
[StructLayout(LayoutKind.Explicit)] -internal struct PEB64 +public struct PEB64 { #region INITIAL_PEB /// Compatibility: all - [FieldOffset(0x00)] internal readonly BOOLEAN InheritedAddressSpace; + [FieldOffset(0x00)] public readonly BOOLEAN InheritedAddressSpace; /// Compatibility: 3.51 and higher - [FieldOffset(0x01)] internal readonly BOOLEAN ReadImageFileExecOptions; + [FieldOffset(0x01)] public readonly BOOLEAN ReadImageFileExecOptions; /// /// Indicates whether the specified process is currently being debugged. The PEB structure, however, is an internal operating-system structure whose layout may change in the future. It is best to use the CheckRemoteDebuggerPresent function instead.
/// Compatibility: 3.51 and higher ///
- [FieldOffset(0x02)] internal readonly BOOLEAN BeingDebugged; + [FieldOffset(0x02)] public readonly BOOLEAN BeingDebugged; /// Compatibility: late 5.2 and higher - [FieldOffset(0x03)] internal readonly PEB_BitField BitField; + [FieldOffset(0x03)] public readonly PEB_BitField BitField; /// Compatibility: all - [FieldOffset(0x08)] internal readonly HANDLE64 Mutant; + [FieldOffset(0x08)] public readonly HANDLE64 Mutant; #endregion INITIAL_PEB /// Compatibility: all - [FieldOffset(0x20)] internal readonly UIntPtr64 ImageBaseAddress; + [FieldOffset(0x20)] public readonly UIntPtr64 ImageBaseAddress; /// Compatibility: all
/// A pointer to a PEB_LDR_DATA structure that contains information about the loaded modules for the process.
- [FieldOffset(0x18)] internal readonly unsafe UIntPtr64 Ldr; + [FieldOffset(0x18)] public readonly unsafe UIntPtr64 Ldr; /// Compatibility: All
/// A pointer to an RTL_USER_PROCESS_PARAMETERS structure that contains process parameter information such as the command line.
- [FieldOffset(0x20)] internal readonly unsafe UIntPtr64 ProcessParameters; + [FieldOffset(0x20)] public readonly unsafe UIntPtr64 ProcessParameters; /// Compatibility: all
/// "SubSystem" refers to WoW64, Posix (via PSXDLL.DLL), or WSL. This stores the per-process data for the relevant subsystem.
- [FieldOffset(0x28)] internal readonly UIntPtr64 SubSystemData; + [FieldOffset(0x28)] public readonly UIntPtr64 SubSystemData; /// Compatibility: all - [FieldOffset(0x30)] internal readonly UIntPtr64 ProcessHeap; + [FieldOffset(0x30)] public readonly UIntPtr64 ProcessHeap; /// Compatibility: 3.10 to 5.0 [FieldOffset(0x38), Obsolete] private readonly UIntPtr64 FastPebLock_obsolete; /// Compatibility: 5.1 and higher - [FieldOffset(0x38)] internal readonly UIntPtr64 FastPebLock; + [FieldOffset(0x38)] public readonly UIntPtr64 FastPebLock; /// Compatibility: 3.10 to 5.1 [FieldOffset(0x40), Obsolete] private readonly UIntPtr64 FastPebLockRoutine; /// Compatibility: late 5.2 and higher - [FieldOffset(0x40)] internal readonly unsafe UIntPtr64 AtlThunkSListPtr; + [FieldOffset(0x40)] public readonly unsafe UIntPtr64 AtlThunkSListPtr; /// Compatibility: 3.10 to 5.1 [FieldOffset(0x48), Obsolete] private readonly UIntPtr64 FastPebUnlockRoutine; /// Compatibility: 6.0 and higher - [FieldOffset(0x48)] internal readonly UIntPtr64 IFEOKey; + [FieldOffset(0x48)] public readonly UIntPtr64 IFEOKey; /// Compatibility: 3.50 to 5.2 [FieldOffset(0x50), Obsolete] private readonly uint EnvironmentUpdateCount; /// Compatibility: 6.0 and higher - [FieldOffset(0x50)] internal readonly PEB_CrossProcess CrossProcessFlags; + [FieldOffset(0x50)] public readonly PEB_CrossProcess CrossProcessFlags; /// Compatibility: 3.51 and higher - [FieldOffset(0x58)] internal readonly UIntPtr64 KernelCallBackTable; + [FieldOffset(0x58)] public readonly UIntPtr64 KernelCallBackTable; /// Compatibility: 6.0 and higher - [FieldOffset(0x58)] internal readonly UIntPtr64 UserSharedInfoPtr; + [FieldOffset(0x58)] public readonly UIntPtr64 UserSharedInfoPtr; /* NOTE: EventLogSection and EventLog became obsolete PEB before Windows 64-bit existed */ /* NOTE: ExecutionOptions became obsolete before Windows 64-bit existed */ /// Compatibility: late 5.1; 6.1 and higher - [FieldOffset(0x64)] internal readonly UIntPtr64 AtlThunkSListPtr32; + [FieldOffset(0x64)] public readonly UIntPtr64 AtlThunkSListPtr32; /// Compatibility: 3.10 to early 6.0
/// Type: PEB_FREE_BLOCK*
[FieldOffset(0x68), Obsolete] private readonly UIntPtr64 FreeList; /// Compatibility: 6.1 and higher - [FieldOffset(0x68)] internal readonly UIntPtr64 ApiSetMap; + [FieldOffset(0x68)] public readonly UIntPtr64 ApiSetMap; /// Compatibility: all - [FieldOffset(0x70)] internal readonly uint TlsExpansionCounter; + [FieldOffset(0x70)] public readonly uint TlsExpansionCounter; /// Compatibility: all - [FieldOffset(0x78)] internal readonly UIntPtr64 TlsBitmap; + [FieldOffset(0x78)] public readonly UIntPtr64 TlsBitmap; /// Compatibility: all - [FieldOffset(0x80)] internal unsafe fixed uint TlsBitmapBits[2]; - internal readonly unsafe uint[] TlsBitmapBits_Safe => new uint[2] { TlsBitmapBits[0], TlsBitmapBits[1] }; + [FieldOffset(0x80)] private unsafe fixed uint TlsBitmapBits[2]; + public readonly unsafe uint[] TlsBitmapBits_Safe => new uint[2] { TlsBitmapBits[0], TlsBitmapBits[1] }; /// Compatibility: all - [FieldOffset(0x88)] internal readonly UIntPtr64 ReadOnlySharedMemoryBase; + [FieldOffset(0x88)] public readonly UIntPtr64 ReadOnlySharedMemoryBase; /// Compatibility: 3.10 to 5.2 [FieldOffset(0x90), Obsolete] private readonly UIntPtr64 ReadOnlySharedMemoryHeap; /// Compatibility: 6.0 to 6.2 [FieldOffset(0x90), Obsolete] private readonly UIntPtr64 HotpatchInformation; /// Compatibility: 1703 and higher - [FieldOffset(0x90)] internal readonly UIntPtr64 SharedData; + [FieldOffset(0x90)] public readonly UIntPtr64 SharedData; /// Compatibility: all - [FieldOffset(0x98)] internal readonly UIntPtr64 ReadOnlyStaticServerData; + [FieldOffset(0x98)] public readonly UIntPtr64 ReadOnlyStaticServerData; /// Compatibility: all - [FieldOffset(0xA0)] internal readonly UIntPtr64 AnsiCodePageData; + [FieldOffset(0xA0)] public readonly UIntPtr64 AnsiCodePageData; /// Compatibility: all - [FieldOffset(0xA8)] internal readonly UIntPtr64 OemCodePageData; + [FieldOffset(0xA8)] public readonly UIntPtr64 OemCodePageData; /// Compatibility: all - [FieldOffset(0xB0)] internal readonly UIntPtr64 UnicodeCaseTableData; + [FieldOffset(0xB0)] public readonly UIntPtr64 UnicodeCaseTableData; /// Compatibility: 3.51 and higher - [FieldOffset(0xB8)] internal readonly uint NumberOfProcessors; + [FieldOffset(0xB8)] public readonly uint NumberOfProcessors; /// Compatibility: 3.51 and higher - [FieldOffset(0xBC)] internal readonly uint NtGlobalFlag; + [FieldOffset(0xBC)] public readonly uint NtGlobalFlag; /// Compatibility: 3.51 and higher - [FieldOffset(0xC0)] internal readonly long CriticalSectionTimeout; + [FieldOffset(0xC0)] public readonly long CriticalSectionTimeout; #region Appended for Windows NT 3.51 /// Compatibility: 3.51 and higher - [FieldOffset(0xC8)] internal readonly UIntPtr64 HeapSegmentReserve; + [FieldOffset(0xC8)] public readonly UIntPtr64 HeapSegmentReserve; /// Compatibility: 3.51 and higher - [FieldOffset(0xD0)] internal readonly UIntPtr64 HeapSegmentCommit; + [FieldOffset(0xD0)] public readonly UIntPtr64 HeapSegmentCommit; /// Compatibility: 3.51 and higher - [FieldOffset(0xD8)] internal readonly UIntPtr64 HeapDeCommitTotalFreeThreshold; + [FieldOffset(0xD8)] public readonly UIntPtr64 HeapDeCommitTotalFreeThreshold; /// Compatibility: 3.51 and higher - [FieldOffset(0xE0)] internal readonly UIntPtr64 HeapDeCommitFreeBlockThreshold; + [FieldOffset(0xE0)] public readonly UIntPtr64 HeapDeCommitFreeBlockThreshold; /// Compatibility: 3.51 and higher - [FieldOffset(0xE8)] internal readonly uint NumberOfHeaps; + [FieldOffset(0xE8)] public readonly uint NumberOfHeaps; /// Compatibility: 3.51 and higher - [FieldOffset(0xEC)] internal readonly uint MaximumNumberOfHeaps; + [FieldOffset(0xEC)] public readonly uint MaximumNumberOfHeaps; /// Compatibility: 3.51 and higher - [FieldOffset(0xF0)] internal readonly UIntPtr64 ProcessHeaps; + [FieldOffset(0xF0)] public readonly UIntPtr64 ProcessHeaps; #endregion Appended for Windows NT 3.51 #region Appended for Windows NT 4.0 /// Compatibility: 3.51 and higher - [FieldOffset(0xF8)] internal readonly UIntPtr64 GdiSharedHandleTable; + [FieldOffset(0xF8)] public readonly UIntPtr64 GdiSharedHandleTable; /// Compatibility: 4.0 and higher - [FieldOffset(0x0100)] internal readonly UIntPtr64 ProcessTarterHelper; + [FieldOffset(0x0100)] public readonly UIntPtr64 ProcessTarterHelper; /// Compatibility: 4.0 and higher - [FieldOffset(0x0108)] internal readonly uint GdiDCAttributeList; + [FieldOffset(0x0108)] public readonly uint GdiDCAttributeList; /// Compatibility: 4.0 to 5.1 [FieldOffset(0x0110), Obsolete] private readonly UIntPtr64 LoaderLock_obsolete; /// Compatibility: 5.2 and higher - [FieldOffset(0x0110)] internal readonly UIntPtr64 LoaderLock; + [FieldOffset(0x0110)] public readonly UIntPtr64 LoaderLock; /// Compatibility: 4.0 and higher - [FieldOffset(0x0118)] internal readonly uint OSMajorVersion; + [FieldOffset(0x0118)] public readonly uint OSMajorVersion; /// Compatibility: 4.0 and higher - [FieldOffset(0x011C)] internal readonly uint OSMinorVersion; + [FieldOffset(0x011C)] public readonly uint OSMinorVersion; /// Compatibility: 4.0 and higher - [FieldOffset(0x0120)] internal readonly ushort OSBuildNumber; + [FieldOffset(0x0120)] public readonly ushort OSBuildNumber; /// Compatibility: 4.0 and higher - [FieldOffset(0x0122)] internal readonly ushort OSCSDVersion; + [FieldOffset(0x0122)] public readonly ushort OSCSDVersion; /// Compatibility: 4.0 and higher - [FieldOffset(0x0124)] internal readonly uint OSPlatformId; + [FieldOffset(0x0124)] public readonly uint OSPlatformId; /// Compatibility: 4.0 and higher - [FieldOffset(0x0128)] internal readonly uint ImageSubsystem; + [FieldOffset(0x0128)] public readonly uint ImageSubsystem; /// Compatibility: 4.0 and higher - [FieldOffset(0x012C)] internal readonly uint ImageSubsystemMajorVersion; + [FieldOffset(0x012C)] public readonly uint ImageSubsystemMajorVersion; /// Compatibility: 4.0 and higher - [FieldOffset(0x0130)] internal readonly uint ImageSubsystemMinorVersion; + [FieldOffset(0x0130)] public readonly uint ImageSubsystemMinorVersion; /// Compatibility: 4.0 to early 6.0 [FieldOffset(0x0138), Obsolete] private readonly KAFFINITY64 ImageProcessAffinityMask; /// Compatibility: late 6.0 and higher - [FieldOffset(0x0138)] internal readonly KAFFINITY64 ActiveProcessAffinityMask; + [FieldOffset(0x0138)] public readonly KAFFINITY64 ActiveProcessAffinityMask; /// (only 0x22 array members instead of 0x3C) Compatibility: 4.0 to early 6.0 [FieldOffset(0x0140), Obsolete] private unsafe fixed uint GdiHandleBuffer_obsolete[0x22]; /// 4.0 and higher (x86) - [FieldOffset(0x0140)] internal unsafe fixed uint GdiHandleBuffer[0x3C]; - internal unsafe uint[] GdiHandleBuffer_Safe + [FieldOffset(0x0140)] private unsafe fixed uint GdiHandleBuffer[0x3C]; + public unsafe uint[] GdiHandleBuffer_Safe { get { @@ -169,12 +169,12 @@ internal unsafe uint[] GdiHandleBuffer_Safe /// Compatibility: 5.0 and higher
/// Type: /// Not supported
- [FieldOffset(0x0230)] internal readonly UIntPtr64 PostProcessInitRoutine; + [FieldOffset(0x0230)] public readonly UIntPtr64 PostProcessInitRoutine; /// Compatibility: 5.0 and higher - [FieldOffset(0x0238)] internal readonly UIntPtr64 TlsExpansionBitmap; + [FieldOffset(0x0238)] public readonly UIntPtr64 TlsExpansionBitmap; /// Compatibility: 5.0 and higher - [FieldOffset(0x0240)] internal unsafe fixed uint TlsExpansionBitmapBits[0x20]; - internal unsafe uint[] TlsExpansionBitmapBits_Safe + [FieldOffset(0x0240)] private unsafe fixed uint TlsExpansionBitmapBits[0x20]; + public unsafe uint[] TlsExpansionBitmapBits_Safe { get { @@ -185,47 +185,47 @@ internal unsafe uint[] TlsExpansionBitmapBits_Safe /// Compatibility: 5.0 and higher
/// The Terminal Services session identifier associated with the current process.
/// The is one of the two members that Microsoft documented when required to disclose use of internal APIs by so-called middleware. - [FieldOffset(0x02C0)] internal readonly uint SessionId; + [FieldOffset(0x02C0)] public readonly uint SessionId; /// Compatibility: 5.1 and higher - [FieldOffset(0x02C8)] internal readonly PEB_AppCompat AppCompatFlags; + [FieldOffset(0x02C8)] public readonly PEB_AppCompat AppCompatFlags; /// Compatibility: 5.1 and higher - [FieldOffset(0x02D0)] internal readonly PEB_AppCompat AppCompatFlagsUser; + [FieldOffset(0x02D0)] public readonly PEB_AppCompat AppCompatFlagsUser; /// Compatibility: 5.1 and higher - [FieldOffset(0x02D8)] internal readonly UIntPtr64 pShimData; + [FieldOffset(0x02D8)] public readonly UIntPtr64 pShimData; /// Compatibility: 5.1 and higher - [FieldOffset(0x02E0)] internal readonly UIntPtr64 AppCompatInfo; + [FieldOffset(0x02E0)] public readonly UIntPtr64 AppCompatInfo; /// Compatibility: 5.1 and higher - [FieldOffset(0x02E8)] internal readonly UNICODE_STRING64 CSDVersion; + [FieldOffset(0x02E8)] public readonly UNICODE_STRING64 CSDVersion; #endregion Appended for Windows 2000 #region Appended for Windows XP /// Compatibility: 5.1 and higher
/// Type: ACTIVATION_CONTEXT_DATA const * (pointer to a constant ACTIVATION_CONTEXT_DATA)
- [FieldOffset(0x02F8)] internal readonly UIntPtr64 ActivationContextData; + [FieldOffset(0x02F8)] public readonly UIntPtr64 ActivationContextData; /// Compatibility: 5.1 and higher /// Type: ACTIVATION_CONTEXT_DATA * - [FieldOffset(0x0300)] internal readonly UIntPtr64 ProcessAssemblyStorageMap; + [FieldOffset(0x0300)] public readonly UIntPtr64 ProcessAssemblyStorageMap; /// Compatibility: 5.1 and higher /// Type: ACTIVATION_CONTEXT_DATA const * - [FieldOffset(0x0308)] internal readonly UIntPtr64 SystemDefaultActivationContextData; + [FieldOffset(0x0308)] public readonly UIntPtr64 SystemDefaultActivationContextData; /// Compatibility: 5.1 and higher /// Type: ASSEMBLY_STORAGE_MAP * - [FieldOffset(0x310)] internal readonly UIntPtr64 SystemAssemblyStorageMap; + [FieldOffset(0x310)] public readonly UIntPtr64 SystemAssemblyStorageMap; /// Compatibility: 5.1 and higher - [FieldOffset(0x318)] internal readonly UIntPtr64 MinimumStackCommit; + [FieldOffset(0x318)] public readonly UIntPtr64 MinimumStackCommit; #endregion Appended for Windows XP #region Appended for Windows Server 2003 /// Compatibility: 5.2 to 1809 /// Type: FLS_CALLBACK_INFO * - [FieldOffset(0x0320)] internal readonly UIntPtr64 FlsCallback; + [FieldOffset(0x0320)] public readonly UIntPtr64 FlsCallback; /// Compatibility: 5.2 to 1809 - [FieldOffset(0x0328)] internal readonly LIST_ENTRY64 FlatListHead; // 5.2 to 1809 + [FieldOffset(0x0328)] public readonly LIST_ENTRY64 FlatListHead; // 5.2 to 1809 /// Compatibility: 5.2 to 1809 - [FieldOffset(0x0338)] internal readonly UIntPtr64 FlsBitmap; + [FieldOffset(0x0338)] public readonly UIntPtr64 FlsBitmap; /// Compatibility: 5.2 to 1809 - [FieldOffset(0x0340)] internal unsafe fixed uint FlsBitmapBits[4]; + [FieldOffset(0x0340)] private unsafe fixed uint FlsBitmapBits[4]; public unsafe uint[] FlsBitmapBits_Safe { get @@ -239,38 +239,38 @@ public unsafe uint[] FlsBitmapBits_Safe } } /// Compatibility: 5.2 to 1809 - [FieldOffset(0x0350)] internal readonly uint FlsHighIndex; + [FieldOffset(0x0350)] public readonly uint FlsHighIndex; #endregion Appended for Windows Server 2003 #region Appended for Windows Vista /// Compatibility: 6.0 and higher - [FieldOffset(0x0358)] internal readonly UIntPtr64 WerRegistrationData; + [FieldOffset(0x0358)] public readonly UIntPtr64 WerRegistrationData; /// Compatibility: 6.0 and higher - [FieldOffset(0x0360)] internal readonly UIntPtr64 WerShipAssertPtr; + [FieldOffset(0x0360)] public readonly UIntPtr64 WerShipAssertPtr; #endregion Appended for Windows Vista #region Appended for Windows 7 /// Compatibility: 6.1 only - [FieldOffset(0x0368)] internal readonly UIntPtr64 pContextData; + [FieldOffset(0x0368)] public readonly UIntPtr64 pContextData; /* [FieldOffset(0x0238)] internal readonly UIntPtr64 pUnused; */ /// Compatibility: 6.1 and higher - [FieldOffset(0x0370)] internal readonly UIntPtr64 pImageHeaderHash; - [FieldOffset(0x0378)] internal readonly PEB_Tracing TracingFlags; + [FieldOffset(0x0370)] public readonly UIntPtr64 pImageHeaderHash; + [FieldOffset(0x0378)] public readonly PEB_Tracing TracingFlags; #endregion Appended for Windows 7 #region Appended for Windows 8 /// Compatibility: 6.2 and higher - [FieldOffset(0x0380)] internal readonly ulong CsrServerReadOnlySharedMemoryBase; + [FieldOffset(0x0380)] public readonly ulong CsrServerReadOnlySharedMemoryBase; #endregion Appended for Windows 8 #region Appended Later in Windows 10 /// Compatibility: 1511 and higher - [FieldOffset(0x0388)] internal readonly uint TppWorkerpListLock; + [FieldOffset(0x0388)] public readonly uint TppWorkerpListLock; /// Compatibility: 1511 and higher - [FieldOffset(0x0390)] internal readonly LIST_ENTRY64 TppWorkerList; + [FieldOffset(0x0390)] public readonly LIST_ENTRY64 TppWorkerList; /// Compatibility: 1511 and higher
/// Type: Fixed Array of void*
- [FieldOffset(0x03A0)] internal unsafe fixed ulong WaitOnAddressHashTable[0x80]; + [FieldOffset(0x03A0)] private unsafe fixed ulong WaitOnAddressHashTable[0x80]; public unsafe ulong[] WaitOnAddressHashTable_Safe { get @@ -284,17 +284,17 @@ public unsafe ulong[] WaitOnAddressHashTable_Safe } } /// Compatibility: 1709 and higher - [FieldOffset(0x07A0)] internal readonly UIntPtr64 TelemetryCoverageHeader; + [FieldOffset(0x07A0)] public readonly UIntPtr64 TelemetryCoverageHeader; /// Compatibility: 1709 and higher - [FieldOffset(0x07A8)] internal readonly uint CloudFileFlags; + [FieldOffset(0x07A8)] public readonly uint CloudFileFlags; /// Compatibility: 1803 and higher - [FieldOffset(0x07AC)] internal readonly uint CloudFileDiagFlags; + [FieldOffset(0x07AC)] public readonly uint CloudFileDiagFlags; /// Compatibility: 1803 and higher - [FieldOffset(0x07B0)] internal readonly byte PlaceholderCompatibilityMode; + [FieldOffset(0x07B0)] public readonly byte PlaceholderCompatibilityMode; /// Compatibility: 1803 and higher /// Type: LEAP_SECOND_DATA * - [FieldOffset(0x07B8)] internal readonly UIntPtr64 LeapSecondData; - [FieldOffset(0x07C0)] internal readonly PEB_LeapSecond LeapSecondFlags; + [FieldOffset(0x07B8)] public readonly UIntPtr64 LeapSecondData; + [FieldOffset(0x07C0)] public readonly PEB_LeapSecond LeapSecondFlags; /// Compatibility: 1803 and higher /// The member is indeed named for being in some sense an extension of the much older . /// Each corresponds to a registry value that can be in either or both of two well-known keys. @@ -311,6 +311,6 @@ public unsafe ulong[] WaitOnAddressHashTable_Safe /// Administrators and programmers are inevitably grateful that Microsoft employees take the time to blog. /// But let's please not overlook that these blogs are not documentation. /// The helpfulness of Microsoft's employees in explaining new features in fast-moving development, and the readiness of occasionally desperate administrators and programmers to latch on to this help, disguises that Microsoft is systematically skipping the work of documenting these features. - [FieldOffset(0x07C4)] internal readonly uint NtGlobalFlag2; + [FieldOffset(0x07C4)] public readonly uint NtGlobalFlag2; #endregion Appended Later in Windows 10 } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_AppCompat.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_AppCompat.cs index 73458a5..8f8a282 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_AppCompat.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_AppCompat.cs @@ -7,7 +7,7 @@ namespace Windows.Win32.System.Threading; /// in a <FLAG> tag whose TYPE attribute is KERNEL or USER, respectively. ///
[Flags] -internal enum PEB_AppCompat : ulong +public enum PEB_AppCompat : ulong { KACF_OLDGETSHORTPATHNAME = 1, KACF_VERSIONLIE_NOT_USED = 1 << 1, diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_CrossProcess.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_CrossProcess.cs index 8609e66..b1042ac 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_CrossProcess.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_CrossProcess.cs @@ -4,7 +4,7 @@ namespace Windows.Win32.System.Threading; /// https://web.archive.org/web/20221204112657/https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/crossprocessflags.htm ///
[Flags] -internal enum PEB_CrossProcess : uint +public enum PEB_CrossProcess : uint { /// Compatibility: 6.0 and higher ProcessInJob = 1, diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs index da7eefe..5388c90 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA32.cs @@ -9,21 +9,21 @@ namespace Windows.Win32.System.Threading; /// https://web.archive.org/web/https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm ///
[StructLayout(LayoutKind.Explicit)] -internal readonly struct PEB_LDR_DATA32 +public readonly struct PEB_LDR_DATA32 { - [FieldOffset(0x00)] internal readonly uint Length; - [FieldOffset(0x04)] internal readonly BOOLEAN Initialized; - [FieldOffset(0x08)] internal readonly UIntPtr32 SsHandle; + [FieldOffset(0x00)] public readonly uint Length; + [FieldOffset(0x04)] public readonly BOOLEAN Initialized; + [FieldOffset(0x08)] public readonly UIntPtr32 SsHandle; /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were loaded. Each item in the list is a pointer to an structure. See Double Linked Lists - [FieldOffset(0x0C)] internal readonly LIST_ENTRY32 InLoadOrderModuleList; + [FieldOffset(0x0C)] public readonly LIST_ENTRY32 InLoadOrderModuleList; /// The head of a doubly-linked list that contains the loaded modules for the process in the order they appear in memory. Each item in the list is a pointer to an structure. See Double Linked Lists - [FieldOffset(0x14)] internal readonly LIST_ENTRY32 InMemoryOrderModuleList; + [FieldOffset(0x14)] public readonly LIST_ENTRY32 InMemoryOrderModuleList; /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were initialized. Each item in the list is a pointer to an structure. See Double Linked Lists - [FieldOffset(0x1C)] internal readonly LIST_ENTRY32 InInitializationOrderModuleList; + [FieldOffset(0x1C)] public readonly LIST_ENTRY32 InInitializationOrderModuleList; /// 5.1 and higher - [FieldOffset(0x24)] internal readonly UIntPtr32 EntryInProgress; + [FieldOffset(0x24)] public readonly UIntPtr32 EntryInProgress; /// late 6.0 and higher - [FieldOffset(0x28)] internal readonly BOOLEAN ShutdownInProgress; + [FieldOffset(0x28)] public readonly BOOLEAN ShutdownInProgress; /// late 6.0 and higher - [FieldOffset(0x2C)] internal readonly HANDLE32 ShutdownThreadId; + [FieldOffset(0x2C)] public readonly HANDLE32 ShutdownThreadId; } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs index 25de60f..fd522f1 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LDR_DATA64.cs @@ -8,18 +8,18 @@ namespace Windows.Win32.System.Threading; /// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm /// Essentially, the head of three double-linked lists of LDR_DATA_TABLE_ENTRY structures. [StructLayout(LayoutKind.Explicit)] -internal readonly struct PEB_LDR_DATA64 +public readonly struct PEB_LDR_DATA64 { - [FieldOffset(0x00)] internal readonly uint Length; - [FieldOffset(0x04)] internal readonly BOOLEAN Initialized; - [FieldOffset(0x08)] internal readonly unsafe UIntPtr64 SsHandle; + [FieldOffset(0x00)] public readonly uint Length; + [FieldOffset(0x04)] public readonly BOOLEAN Initialized; + [FieldOffset(0x08)] public readonly unsafe UIntPtr64 SsHandle; /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were loaded. Each item in the list is a pointer to an structure. See Double Linked Lists - [FieldOffset(0x10)] internal readonly LIST_ENTRY64 InLoadOrderModuleList; + [FieldOffset(0x10)] public readonly LIST_ENTRY64 InLoadOrderModuleList; /// The head of a doubly-linked list that contains the loaded modules for the process in the order they appear in memory. Each item in the list is a pointer to an structure. See Double Linked Lists - [FieldOffset(0x20)] internal readonly LIST_ENTRY64 InMemoryOrderModuleList; + [FieldOffset(0x20)] public readonly LIST_ENTRY64 InMemoryOrderModuleList; /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were initialized. Each item in the list is a pointer to an structure. See Double Linked Lists - [FieldOffset(0x30)] internal readonly LIST_ENTRY64 InInitializationOrderModuleList; - [FieldOffset(0x40)] internal readonly unsafe UIntPtr64 EntryInProgress; // 5.1 and higher - [FieldOffset(0x48)] internal readonly BOOLEAN ShutdownInProgress; // late 6.0 and higher - [FieldOffset(0x50)] internal readonly HANDLE64 ShutdownThreadId; // late 6.0 and higher + [FieldOffset(0x30)] public readonly LIST_ENTRY64 InInitializationOrderModuleList; + [FieldOffset(0x40)] public readonly unsafe UIntPtr64 EntryInProgress; // 5.1 and higher + [FieldOffset(0x48)] public readonly BOOLEAN ShutdownInProgress; // late 6.0 and higher + [FieldOffset(0x50)] public readonly HANDLE64 ShutdownThreadId; // late 6.0 and higher } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LeapSecond.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LeapSecond.cs index 95fea46..d880e96 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LeapSecond.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_LeapSecond.cs @@ -1,7 +1,7 @@ namespace Windows.Win32.System.Threading; [Flags] -internal enum PEB_LeapSecond : uint +public enum PEB_LeapSecond : uint { SixtySecondEnabled = 1U } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Tracing.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Tracing.cs index 308289b..246dcff 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Tracing.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PEB_Tracing.cs @@ -1,7 +1,7 @@ namespace Windows.Win32.System.Threading; [Flags] -internal enum PEB_Tracing : uint +public enum PEB_Tracing : uint { /// Compatibility: 6.1 and higher HeapTracingEnabled = 1, diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PPS_POST_PROCESS_INIT_ROUTINE.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PPS_POST_PROCESS_INIT_ROUTINE.cs index 46dd58f..970ec64 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PPS_POST_PROCESS_INIT_ROUTINE.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PPS_POST_PROCESS_INIT_ROUTINE.cs @@ -3,7 +3,7 @@ namespace Windows.Win32.System.Threading; [UnmanagedFunctionPointer(CallingConvention.Winapi)] -internal unsafe delegate void PS_POST_PROCESS_INIT_ROUTINE(); +public unsafe delegate void PS_POST_PROCESS_INIT_ROUTINE(); /// /// Function Pointer workaround. C# 9's function pointers are only allowed in @@ -15,7 +15,7 @@ namespace Windows.Win32.System.Threading; /// will re-use that thunk. /// source: /// -internal struct PPS_POST_PROCESS_INIT_ROUTINE : IEquatable +public struct PPS_POST_PROCESS_INIT_ROUTINE : IEquatable { public IntPtr Value; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION32.cs index d73eacc..0417e4f 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION32.cs @@ -8,7 +8,7 @@ namespace Windows.Win32.System.Threading; /// 32-bit struct for interop with 32-bit processes from a 64-bit process
/// When running a 32-bit process or when interacting with other 64-bit processes, use
[StructLayout(LayoutKind.Explicit)] -internal readonly struct PROCESS_BASIC_INFORMATION32 +public readonly struct PROCESS_BASIC_INFORMATION32 { [FieldOffset(0x00)] public readonly NTSTATUS ExitStatus; [FieldOffset(0x04)] public readonly UIntPtr32 PebBaseAddress; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs index a333705..3f13978 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/PROCESS_BASIC_INFORMATION64.cs @@ -8,7 +8,7 @@ namespace Windows.Win32.System.Threading; /// 64-bit struct for interop with 64-bit processes from a 32-bit process
/// When running a 64-bit process or when interacting with other 32-bit processes, use
[StructLayout(LayoutKind.Explicit)] -internal readonly struct PROCESS_BASIC_INFORMATION64 +public readonly struct PROCESS_BASIC_INFORMATION64 { [FieldOffset(0x00)] public readonly NTSTATUS ExitStatus; [FieldOffset(0x04)] public readonly UIntPtr64 PebBaseAddress; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessBasicInformation.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessBasicInformation.cs index 5cdcc3c..7ab5924 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessBasicInformation.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessBasicInformation.cs @@ -8,7 +8,7 @@ namespace Windows.Win32.System.Threading; public class ProcessBasicInformation { - internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION pbi) + public ProcessBasicInformation(PROCESS_BASIC_INFORMATION pbi) { ExitStatus = pbi.ExitStatus; unsafe { PebBaseAddress = Environment.Is64BitProcess ? (null, (ulong)pbi.PebBaseAddress) : ((uint)pbi.PebBaseAddress, null); } @@ -18,7 +18,7 @@ internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION pbi) ParentProcessId = pbi.ParentProcessId; } - internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION32 pbi) + public ProcessBasicInformation(PROCESS_BASIC_INFORMATION32 pbi) { ExitStatus = pbi.ExitStatus; PebBaseAddress = (pbi.PebBaseAddress, null); @@ -28,7 +28,7 @@ internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION32 pbi) ParentProcessId = pbi.InheritedFromUniqueProcessId; } - internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION64 pbi) + public ProcessBasicInformation(PROCESS_BASIC_INFORMATION64 pbi) { ExitStatus = pbi.ExitStatus; PebBaseAddress = (null, pbi.PebBaseAddress); @@ -38,7 +38,7 @@ internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION64 pbi) ParentProcessId = (uint)pbi.InheritedFromUniqueProcessId; } - internal (UIntPtr32? w32, UIntPtr64? w64) PebBaseAddress { get; } + public (UIntPtr32? w32, UIntPtr64? w64) PebBaseAddress { get; } public ProcessEnvironmentBlock? ProcessEnvironmentBlock { get; private set; } public NTSTATUS ExitStatus { get; } @@ -53,7 +53,7 @@ internal ProcessBasicInformation(PROCESS_BASIC_INFORMATION64 pbi) /// Unable to copy PEB; The 32-bit and 64-bit pointers are both null. /// NtWow64ReadVirtualMemory failed to copy 64-bit PEB from target process; (native error message) /// ReadProcessMemory failed; (native error message) - internal unsafe ProcessEnvironmentBlock GetPEB(SafeProcessHandle hProcess) + public unsafe ProcessEnvironmentBlock GetPEB(SafeProcessHandle hProcess) { if (PebBaseAddress is (null, null)) throw new NullReferenceException("Unable to copy PEB; The 32-bit and 64-bit pointers are both null."); diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.LoaderData.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.LoaderData.cs index ea7156f..7b36452 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.LoaderData.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.LoaderData.cs @@ -7,7 +7,7 @@ public partial class ProcessEnvironmentBlock { public class LoaderData { - internal LoaderData(PEB_LDR_DATA32 loaderData32) + public LoaderData(PEB_LDR_DATA32 loaderData32) { EntryInProgress.w32 = loaderData32.EntryInProgress; InInitializationOrderModuleList.w32 = loaderData32.InInitializationOrderModuleList; @@ -19,7 +19,7 @@ internal LoaderData(PEB_LDR_DATA32 loaderData32) SsHandle.w32 = loaderData32.SsHandle; } - internal LoaderData(PEB_LDR_DATA64 loaderData64) + public LoaderData(PEB_LDR_DATA64 loaderData64) { EntryInProgress.w64 = loaderData64.EntryInProgress; InInitializationOrderModuleList.w64 = loaderData64.InInitializationOrderModuleList; @@ -33,15 +33,15 @@ internal LoaderData(PEB_LDR_DATA64 loaderData64) public uint Length; public BOOLEAN Initialized; - internal (UIntPtr32? w32, UIntPtr64? w64) SsHandle; + public (UIntPtr32? w32, UIntPtr64? w64) SsHandle; /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were loaded. Each item in the list is a pointer to an structure. See Double Linked Lists - internal (LIST_ENTRY32? w32, LIST_ENTRY64? w64) InLoadOrderModuleList; + public (LIST_ENTRY32? w32, LIST_ENTRY64? w64) InLoadOrderModuleList; /// The head of a doubly-linked list that contains the loaded modules for the process in the order they appear in memory. Each item in the list is a pointer to an structure. See Double Linked Lists - internal (LIST_ENTRY32? w32, LIST_ENTRY64? w64) InMemoryOrderModuleList; + public (LIST_ENTRY32? w32, LIST_ENTRY64? w64) InMemoryOrderModuleList; /// The head of a doubly-linked list that contains the loaded modules for the process in the order they were initialized. Each item in the list is a pointer to an structure. See Double Linked Lists - internal (LIST_ENTRY32? w32, LIST_ENTRY64? w64) InInitializationOrderModuleList; - internal (UIntPtr32? w32, UIntPtr64? w64) EntryInProgress; // 5.1 and higher + public (LIST_ENTRY32? w32, LIST_ENTRY64? w64) InInitializationOrderModuleList; + public (UIntPtr32? w32, UIntPtr64? w64) EntryInProgress; // 5.1 and higher public BOOLEAN ShutdownInProgress; // late 6.0 and higher - internal (HANDLE32? w32, HANDLE64? w64) ShutdownThreadId; // late 6.0 and higher + public (HANDLE32? w32, HANDLE64? w64) ShutdownThreadId; // late 6.0 and higher } } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.ProcessParameters.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.ProcessParameters.cs index 5cb8e68..676bc10 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.ProcessParameters.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.ProcessParameters.cs @@ -15,7 +15,7 @@ public partial class ProcessEnvironmentBlock { public class UserProcessParameters { - internal UserProcessParameters(RTL_USER_PROCESS_PARAMETERS32 rupp32) + public UserProcessParameters(RTL_USER_PROCESS_PARAMETERS32 rupp32) { CommandLine.w32 = rupp32.CommandLine; ConsoleFlags = rupp32.ConsoleFlags; @@ -57,7 +57,7 @@ internal UserProcessParameters(RTL_USER_PROCESS_PARAMETERS32 rupp32) WindowTitle.w32 = rupp32.WindowTitle; } - internal UserProcessParameters(RTL_USER_PROCESS_PARAMETERS64 rupp64) + public UserProcessParameters(RTL_USER_PROCESS_PARAMETERS64 rupp64) { CommandLine.w64 = rupp64.CommandLine; ConsoleFlags = rupp64.ConsoleFlags; @@ -105,16 +105,16 @@ internal UserProcessParameters(RTL_USER_PROCESS_PARAMETERS64 rupp64) public uint Flags; public uint DebugFlags; - internal (HANDLE32? w32, HANDLE64? w64) ConsoleHandle; - internal uint ConsoleFlags; - internal (HANDLE32? w32, HANDLE64? w64) StandardInput; - internal (HANDLE32? w32, HANDLE64? w64) StandardOutput; - internal (HANDLE32? w32, HANDLE64? w64) StandardError; + public (HANDLE32? w32, HANDLE64? w64) ConsoleHandle; + public uint ConsoleFlags; + public (HANDLE32? w32, HANDLE64? w64) StandardInput; + public (HANDLE32? w32, HANDLE64? w64) StandardOutput; + public (HANDLE32? w32, HANDLE64? w64) StandardError; - internal (CURDIR32? w32, CURDIR64? w64) CurrentDirectory; - internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) DllPath; - internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) ImagePathName; - internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) CommandLine; + public (CURDIR32? w32, CURDIR64? w64) CurrentDirectory; + public (UNICODE_STRING32? w32, UNICODE_STRING64? w64) DllPath; + public (UNICODE_STRING32? w32, UNICODE_STRING64? w64) ImagePathName; + public (UNICODE_STRING32? w32, UNICODE_STRING64? w64) CommandLine; /// /// Using a SafeProcessHandle with PROCESS_VM_READ access, copy the target process's command line string. @@ -172,7 +172,7 @@ public unsafe string GetCommandLine(SafeProcessHandle processHandle) } } - internal (UIntPtr32? w32, UIntPtr64? w64) Environment; + public (UIntPtr32? w32, UIntPtr64? w64) Environment; public uint StartingX; public uint StartingY; @@ -184,23 +184,23 @@ public unsafe string GetCommandLine(SafeProcessHandle processHandle) public uint WindowFlags; public uint ShowWindowFlags; - internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) WindowTitle; - internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) DesktopInfo; - internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) ShellInfo; - internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) RuntimeData; + public (UNICODE_STRING32? w32, UNICODE_STRING64? w64) WindowTitle; + public (UNICODE_STRING32? w32, UNICODE_STRING64? w64) DesktopInfo; + public (UNICODE_STRING32? w32, UNICODE_STRING64? w64) ShellInfo; + public (UNICODE_STRING32? w32, UNICODE_STRING64? w64) RuntimeData; - internal (RTL_DRIVE_LETTER_CURDIR32[]? w32, RTL_DRIVE_LETTER_CURDIR64[]? w64) CurrentDirectories; + public (RTL_DRIVE_LETTER_CURDIR32[]? w32, RTL_DRIVE_LETTER_CURDIR64[]? w64) CurrentDirectories; - internal (UIntPtr32? w32, UIntPtr64? w64) EnvironmentSize; - internal (UIntPtr32? w32, UIntPtr64? w64) EnvironmentVersion; + public (UIntPtr32? w32, UIntPtr64? w64) EnvironmentSize; + public (UIntPtr32? w32, UIntPtr64? w64) EnvironmentVersion; - internal (UIntPtr32? w32, UIntPtr64? w64) PackageDependencyData; + public (UIntPtr32? w32, UIntPtr64? w64) PackageDependencyData; public uint ProcessGroupId; public uint LoaderThreads; - internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) RedirectionDllName; // REDSTONE4 - internal (UNICODE_STRING32? w32, UNICODE_STRING64? w64) HeapPartitionName; // 19H1 - internal (UIntPtr32? w32, UIntPtr64? w64) DefaultThreadpoolCpuSetMasks; + public (UNICODE_STRING32? w32, UNICODE_STRING64? w64) RedirectionDllName; // REDSTONE4 + public (UNICODE_STRING32? w32, UNICODE_STRING64? w64) HeapPartitionName; // 19H1 + public (UIntPtr32? w32, UIntPtr64? w64) DefaultThreadpoolCpuSetMasks; public uint DefaultThreadpoolCpuSetMaskCount; public uint DefaultThreadpoolThreadMaximum; } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.cs index c125b57..e2bdaf1 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/ProcessEnvironmentBlock.cs @@ -11,7 +11,7 @@ namespace Windows.Win32.System.Threading; public partial class ProcessEnvironmentBlock { - internal unsafe ProcessEnvironmentBlock(PEB32 peb32) + public unsafe ProcessEnvironmentBlock(PEB32 peb32) { ActivationContextData = (peb32.ActivationContextData, null); ActiveProcessAffinityMask = (peb32.ActiveProcessAffinityMask, null); @@ -101,7 +101,7 @@ internal unsafe ProcessEnvironmentBlock(PEB32 peb32) WerShipAssertPtr = (peb32.WerShipAssertPtr, null); } - internal unsafe ProcessEnvironmentBlock(PEB64 peb64) + public unsafe ProcessEnvironmentBlock(PEB64 peb64) { ActivationContextData = (null, peb64.ActivationContextData); ActiveProcessAffinityMask = (null, peb64.ActiveProcessAffinityMask); @@ -193,26 +193,26 @@ internal unsafe ProcessEnvironmentBlock(PEB64 peb64) #region INITIAL_PEB /// Compatibility: all - internal readonly BOOLEAN InheritedAddressSpace; + public readonly BOOLEAN InheritedAddressSpace; /// Compatibility: 3.51 and higher - internal readonly BOOLEAN ReadImageFileExecOptions; + public readonly BOOLEAN ReadImageFileExecOptions; /// - /// Indicates whether the specified process is currently being debugged. The PEB structure, however, is an internal operating-system structure whose layout may change in the future. It is best to use the CheckRemoteDebuggerPresent function instead.
+ /// Indicates whether the specified process is currently being debugged. The PEB structure, however, is an public operating-system structure whose layout may change in the future. It is best to use the CheckRemoteDebuggerPresent function instead.
/// Compatibility: 3.51 and higher ///
- internal readonly BOOLEAN BeingDebugged; + private readonly BOOLEAN BeingDebugged; public bool IsBeingDebugged => BeingDebugged; /// Compatibility: late 5.2 and higher - internal readonly PEB_BitField BitField; + public readonly PEB_BitField BitField; /// Compatibility: all - internal readonly (HANDLE32? w32, HANDLE64? w64) Mutant; + public readonly (HANDLE32? w32, HANDLE64? w64) Mutant; #endregion INITIAL_PEB /// Compatibility: all - internal readonly (UIntPtr32? w32, UIntPtr64? w64) ImageBaseAddress; + public readonly (UIntPtr32? w32, UIntPtr64? w64) ImageBaseAddress; /// Compatibility: all
/// A pointer to a PEB_LDR_DATA structure that contains information about the loaded modules for the process.
- internal readonly unsafe (UIntPtr32? w32, UIntPtr64? w64) Ldr; + public readonly unsafe (UIntPtr32? w32, UIntPtr64? w64) Ldr; /// /// Pass a SafeProcessHandle with PROCESS_VM_READ to copy the process's LoaderData from its memory. @@ -276,7 +276,7 @@ public unsafe LoaderData GetPEBLoaderData(SafeProcessHandle processHandle) /// Compatibility: All
/// A pointer to an RTL_USER_PROCESS_PARAMETERS structure that contains process parameter information such as the command line.
- internal readonly unsafe (UIntPtr32? w32, UIntPtr64? w64) ProcessParameters; + public readonly unsafe (UIntPtr32? w32, UIntPtr64? w64) ProcessParameters; /// /// Using a SafeProcessHandle with PROCESS_READ_VM access, copy the target process's 32-bit or 64-bit RTL_USER_PROCESS_PARAMETERS data from its memory. @@ -341,34 +341,34 @@ public unsafe UserProcessParameters GetUserProcessParameters(SafeProcessHandle p /// Compatibility: all
/// "SubSystem" refers to WoW64, Posix (via PSXDLL.DLL), or WSL. This stores the per-process data for the relevant subsystem.
- internal readonly (UIntPtr32? w32, UIntPtr64? w64) SubSystemData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) SubSystemData; /// Compatibility: all - internal readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessHeap; + public readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessHeap; /// Compatibility: 5.1 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) FastPebLock; + public readonly (UIntPtr32? w32, UIntPtr64? w64) FastPebLock; /// Compatibility: late 5.2 and higher - internal readonly unsafe (UIntPtr32? w32, UIntPtr64? w64) AtlThunkSListPtr; + public readonly unsafe (UIntPtr32? w32, UIntPtr64? w64) AtlThunkSListPtr; /// Compatibility: 6.0 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) IFEOKey; + public readonly (UIntPtr32? w32, UIntPtr64? w64) IFEOKey; /// Compatibility: 6.0 and higher - internal readonly PEB_CrossProcess CrossProcessFlags; + public readonly PEB_CrossProcess CrossProcessFlags; /// Compatibility: 3.51 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) KernelCallBackTable; + public readonly (UIntPtr32? w32, UIntPtr64? w64) KernelCallBackTable; /// Compatibility: 6.0 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) UserSharedInfoPtr; + public readonly (UIntPtr32? w32, UIntPtr64? w64) UserSharedInfoPtr; /// Compatibility: late 5.1; 6.1 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) AtlThunkSListPtr32; + public readonly (UIntPtr32? w32, UIntPtr64? w64) AtlThunkSListPtr32; /// Compatibility: 6.1 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) ApiSetMap; + public readonly (UIntPtr32? w32, UIntPtr64? w64) ApiSetMap; /// Compatibility: all - internal readonly uint TlsExpansionCounter; + public readonly uint TlsExpansionCounter; /// Compatibility: all - internal readonly (UIntPtr32? w32, UIntPtr64? w64) TlsBitmap; + public readonly (UIntPtr32? w32, UIntPtr64? w64) TlsBitmap; /// Compatibility: all - internal readonly TlsBitmapBitsData TlsBitmapBits; + public readonly TlsBitmapBitsData TlsBitmapBits; public readonly struct TlsBitmapBitsData { /// @@ -388,168 +388,168 @@ public TlsBitmapBitsData(uint[] bitArray) } /// Compatibility: all - internal readonly (UIntPtr32? w32, UIntPtr64? w64) ReadOnlySharedMemoryBase; + public readonly (UIntPtr32? w32, UIntPtr64? w64) ReadOnlySharedMemoryBase; /// Compatibility: 1703 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) SharedData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) SharedData; /// Compatibility: all - internal readonly (UIntPtr32? w32, UIntPtr64? w64) ReadOnlyStaticServerData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) ReadOnlyStaticServerData; /// Compatibility: all - internal readonly (UIntPtr32? w32, UIntPtr64? w64) AnsiCodePageData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) AnsiCodePageData; /// Compatibility: all - internal readonly (UIntPtr32? w32, UIntPtr64? w64) OemCodePageData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) OemCodePageData; /// Compatibility: all - internal readonly (UIntPtr32? w32, UIntPtr64? w64) UnicodeCaseTableData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) UnicodeCaseTableData; /// Compatibility: 3.51 and higher - internal readonly uint NumberOfProcessors; + public readonly uint NumberOfProcessors; /// Compatibility: 3.51 and higher - internal readonly uint NtGlobalFlag; + public readonly uint NtGlobalFlag; /// Compatibility: 3.51 and higher - internal readonly long CriticalSectionTimeout; + public readonly long CriticalSectionTimeout; #region Appended for Windows NT 3.51 /// Compatibility: 3.51 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) HeapSegmentReserve; + public readonly (UIntPtr32? w32, UIntPtr64? w64) HeapSegmentReserve; /// Compatibility: 3.51 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) HeapSegmentCommit; + public readonly (UIntPtr32? w32, UIntPtr64? w64) HeapSegmentCommit; /// Compatibility: 3.51 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) HeapDeCommitTotalFreeThreshold; + public readonly (UIntPtr32? w32, UIntPtr64? w64) HeapDeCommitTotalFreeThreshold; /// Compatibility: 3.51 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) HeapDeCommitFreeBlockThreshold; + public readonly (UIntPtr32? w32, UIntPtr64? w64) HeapDeCommitFreeBlockThreshold; /// Compatibility: 3.51 and higher - internal readonly uint NumberOfHeaps; + public readonly uint NumberOfHeaps; /// Compatibility: 3.51 and higher - internal readonly uint MaximumNumberOfHeaps; + public readonly uint MaximumNumberOfHeaps; /// Compatibility: 3.51 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessHeaps; + public readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessHeaps; #endregion Appended for Windows NT 3.51 #region Appended for Windows NT 4.0 /// Compatibility: 3.51 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) GdiSharedHandleTable; + public readonly (UIntPtr32? w32, UIntPtr64? w64) GdiSharedHandleTable; /// Compatibility: 4.0 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessTarterHelper; + public readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessTarterHelper; /// Compatibility: 4.0 and higher - internal readonly uint GdiDCAttributeList; + public readonly uint GdiDCAttributeList; /// Compatibility: 5.2 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) LoaderLock; + public readonly (UIntPtr32? w32, UIntPtr64? w64) LoaderLock; /// Compatibility: 4.0 and higher - internal readonly uint OSMajorVersion; + public readonly uint OSMajorVersion; /// Compatibility: 4.0 and higher - internal readonly uint OSMinorVersion; + public readonly uint OSMinorVersion; /// Compatibility: 4.0 and higher - internal readonly ushort OSBuildNumber; + public readonly ushort OSBuildNumber; /// Compatibility: 4.0 and higher - internal readonly ushort OSCSDVersion; + public readonly ushort OSCSDVersion; /// Compatibility: 4.0 and higher - internal readonly uint OSPlatformId; + public readonly uint OSPlatformId; /// Compatibility: 4.0 and higher - internal readonly uint ImageSubsystem; + public readonly uint ImageSubsystem; /// Compatibility: 4.0 and higher - internal readonly uint ImageSubsystemMajorVersion; + public readonly uint ImageSubsystemMajorVersion; /// Compatibility: 4.0 and higher - internal readonly uint ImageSubsystemMinorVersion; + public readonly uint ImageSubsystemMinorVersion; /// Compatibility: late 6.0 and higher - internal readonly (KAFFINITY32? w32, KAFFINITY64? w64) ActiveProcessAffinityMask; + public readonly (KAFFINITY32? w32, KAFFINITY64? w64) ActiveProcessAffinityMask; /// 4.0 and higher (x86) - internal uint[] GdiHandleBuffer; + public uint[] GdiHandleBuffer; #endregion Appended for Windows NT 4.0 #region Appended for Windows 2000 /// Compatibility: 5.0 and higher
/// Not supported. Type:
- internal readonly (UIntPtr32? w32, UIntPtr64? w64) PostProcessInitRoutine; + public readonly (UIntPtr32? w32, UIntPtr64? w64) PostProcessInitRoutine; /// Compatibility: 5.0 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) TlsExpansionBitmap; + public readonly (UIntPtr32? w32, UIntPtr64? w64) TlsExpansionBitmap; /// Compatibility: 5.0 and higher - internal unsafe uint[] TlsExpansionBitmapBits; + public unsafe uint[] TlsExpansionBitmapBits; /// Compatibility: 5.0 and higher
/// The Terminal Services session identifier associated with the current process.
/// The is one of the two members that Microsoft documented when required to disclose use of internal APIs by so-called middleware. - internal readonly uint SessionId; + public readonly uint SessionId; /// Compatibility: 5.1 and higher - internal readonly PEB_AppCompat AppCompatFlags; + public readonly PEB_AppCompat AppCompatFlags; /// Compatibility: 5.1 and higher - internal readonly PEB_AppCompat AppCompatFlagsUser; + public readonly PEB_AppCompat AppCompatFlagsUser; /// Compatibility: 5.1 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) pShimData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) pShimData; /// Compatibility: 5.1 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) AppCompatInfo; + public readonly (UIntPtr32? w32, UIntPtr64? w64) AppCompatInfo; /// Compatibility: 5.1 and higher - internal readonly (UNICODE_STRING32? w32, UNICODE_STRING64? w64) CSDVersion; + public readonly (UNICODE_STRING32? w32, UNICODE_STRING64? w64) CSDVersion; #endregion Appended for Windows 2000 #region Appended for Windows XP /// Compatibility: 5.1 and higher
/// Type: ACTIVATION_CONTEXT_DATA const * (pointer to a constant ACTIVATION_CONTEXT_DATA)
- internal readonly (UIntPtr32? w32, UIntPtr64? w64) ActivationContextData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) ActivationContextData; /// Compatibility: 5.1 and higher /// Type: ACTIVATION_CONTEXT_DATA * - internal readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessAssemblyStorageMap; + public readonly (UIntPtr32? w32, UIntPtr64? w64) ProcessAssemblyStorageMap; /// Compatibility: 5.1 and higher /// Type: ACTIVATION_CONTEXT_DATA const * - internal readonly (UIntPtr32? w32, UIntPtr64? w64) SystemDefaultActivationContextData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) SystemDefaultActivationContextData; /// Compatibility: 5.1 and higher /// Type: ASSEMBLY_STORAGE_MAP * - internal readonly (UIntPtr32? w32, UIntPtr64? w64) SystemAssemblyStorageMap; + public readonly (UIntPtr32? w32, UIntPtr64? w64) SystemAssemblyStorageMap; /// Compatibility: 5.1 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) MinimumStackCommit; + public readonly (UIntPtr32? w32, UIntPtr64? w64) MinimumStackCommit; #endregion Appended for Windows XP #region Appended for Windows Server 2003 /// Compatibility: 5.2 to 1809 /// Type: FLS_CALLBACK_INFO * - internal readonly (UIntPtr32? w32, UIntPtr64? w64) FlsCallback; + public readonly (UIntPtr32? w32, UIntPtr64? w64) FlsCallback; /// Compatibility: 5.2 to 1809 - internal readonly (LIST_ENTRY32? w32, LIST_ENTRY64? w64) FlatListHead; // 5.2 to 1809 + public readonly (LIST_ENTRY32? w32, LIST_ENTRY64? w64) FlatListHead; // 5.2 to 1809 /// Compatibility: 5.2 to 1809 - internal readonly (UIntPtr32? w32, UIntPtr64? w64) FlsBitmap; + public readonly (UIntPtr32? w32, UIntPtr64? w64) FlsBitmap; /// Compatibility: 5.2 to 1809 - internal uint[] FlsBitmapBits; + public uint[] FlsBitmapBits; /// Compatibility: 5.2 to 1809 - internal readonly uint FlsHighIndex; + public readonly uint FlsHighIndex; #endregion Appended for Windows Server 2003 #region Appended for Windows Vista /// Compatibility: 6.0 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) WerRegistrationData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) WerRegistrationData; /// Compatibility: 6.0 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) WerShipAssertPtr; + public readonly (UIntPtr32? w32, UIntPtr64? w64) WerShipAssertPtr; #endregion Appended for Windows Vista #region Appended for Windows 7 /// Compatibility: 6.1 only - internal readonly (UIntPtr32? w32, UIntPtr64? w64) pContextData; + public readonly (UIntPtr32? w32, UIntPtr64? w64) pContextData; /* internal readonly (UIntPtr32? w32, UIntPtr64? w64) pUnused; */ /// Compatibility: 6.1 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) pImageHeaderHash; - internal readonly PEB_Tracing TracingFlags; + public readonly (UIntPtr32? w32, UIntPtr64? w64) pImageHeaderHash; + public readonly PEB_Tracing TracingFlags; #endregion Appended for Windows 7 #region Appended for Windows 8 /// Compatibility: 6.2 and higher - internal readonly ulong CsrServerReadOnlySharedMemoryBase; + public readonly ulong CsrServerReadOnlySharedMemoryBase; #endregion Appended for Windows 8 #region Appended Later in Windows 10 /// Compatibility: 1511 and higher - internal readonly uint TppWorkerpListLock; + public readonly uint TppWorkerpListLock; /// Compatibility: 1511 and higher - internal readonly (LIST_ENTRY32? w32, LIST_ENTRY64? w64) TppWorkerList; + public readonly (LIST_ENTRY32? w32, LIST_ENTRY64? w64) TppWorkerList; /// Compatibility: 1511 and higher
- internal (UIntPtr32[]? w32, UIntPtr64[]? w64) WaitOnAddressHashTable; + public (UIntPtr32[]? w32, UIntPtr64[]? w64) WaitOnAddressHashTable; /// Compatibility: 1709 and higher - internal readonly (UIntPtr32? w32, UIntPtr64? w64) TelemetryCoverageHeader; + public readonly (UIntPtr32? w32, UIntPtr64? w64) TelemetryCoverageHeader; /// Compatibility: 1709 and higher - internal readonly uint CloudFileFlags; + public readonly uint CloudFileFlags; /// Compatibility: 1803 and higher - internal readonly uint CloudFileDiagFlags; + public readonly uint CloudFileDiagFlags; /// Compatibility: 1803 and higher - internal readonly byte PlaceholderCompatibilityMode; + public readonly byte PlaceholderCompatibilityMode; /// Compatibility: 1803 and higher /// Type: LEAP_SECOND_DATA * - internal readonly (UIntPtr32? w32, UIntPtr64? w64) LeapSecondData; - internal readonly PEB_LeapSecond LeapSecondFlags; + public readonly (UIntPtr32? w32, UIntPtr64? w64) LeapSecondData; + public readonly PEB_LeapSecond LeapSecondFlags; /// Compatibility: 1803 and higher /// The member is indeed named for being in some sense an extension of the much older . /// Each corresponds to a registry value that can be in either or both of two well-known keys. @@ -566,6 +566,6 @@ public TlsBitmapBitsData(uint[] bitArray) /// Administrators and programmers are inevitably grateful that Microsoft employees take the time to blog. /// But let's please not overlook that these blogs are not documentation. /// The helpfulness of Microsoft's employees in explaining new features in fast-moving development, and the readiness of occasionally desperate administrators and programmers to latch on to this help, disguises that Microsoft is systematically skipping the work of documenting these features. - internal readonly uint NtGlobalFlag2; + public readonly uint NtGlobalFlag2; #endregion Appended Later in Windows 10 } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs index b7eecd9..4ef928b 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION32.cs @@ -1,14 +1,14 @@ namespace Windows.Win32.System.Threading { - internal readonly struct RTL_CRITICAL_SECTION32 + public readonly struct RTL_CRITICAL_SECTION32 { #pragma warning disable CS0649 - internal readonly unsafe UIntPtr32 DebugInfo; - internal readonly int LockCount; - internal readonly int RecursionCount; - internal readonly Foundation.HANDLE32 OwningThread; - internal readonly Foundation.HANDLE32 LockSemaphore; - internal readonly uint SpinCount; + public readonly unsafe UIntPtr32 DebugInfo; + public readonly int LockCount; + public readonly int RecursionCount; + public readonly Foundation.HANDLE32 OwningThread; + public readonly Foundation.HANDLE32 LockSemaphore; + public readonly uint SpinCount; #pragma warning restore CS0649 } } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs index 5c3129b..18a1e49 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_CRITICAL_SECTION64.cs @@ -1,14 +1,14 @@ namespace Windows.Win32.System.Threading { - internal readonly struct RTL_CRITICAL_SECTION64 + public readonly struct RTL_CRITICAL_SECTION64 { #pragma warning disable CS0649 - internal readonly unsafe UIntPtr64 DebugInfo; - internal readonly int LockCount; - internal readonly int RecursionCount; - internal readonly Foundation.HANDLE64 OwningThread; - internal readonly Foundation.HANDLE64 LockSemaphore; - internal readonly ulong SpinCount; + public readonly unsafe UIntPtr64 DebugInfo; + public readonly int LockCount; + public readonly int RecursionCount; + public readonly Foundation.HANDLE64 OwningThread; + public readonly Foundation.HANDLE64 LockSemaphore; + public readonly ulong SpinCount; #pragma warning restore CS0649 } } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR32.cs index 86abfdd..a1e3fa8 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR32.cs @@ -5,7 +5,7 @@ namespace Windows.Win32.System.Threading; -internal partial struct RTL_USER_PROCESS_PARAMETERS32 +public partial struct RTL_USER_PROCESS_PARAMETERS32 { /// /// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/rtl_drive_letter_curdir.htm diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR64.cs index c485795..8abf040 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_DRIVE_LETTER_CURDIR64.cs @@ -5,13 +5,13 @@ namespace Windows.Win32.System.Threading; -internal partial struct RTL_USER_PROCESS_PARAMETERS64 +public partial struct RTL_USER_PROCESS_PARAMETERS64 { /// /// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/rtl_drive_letter_curdir.htm /// [StructLayout(LayoutKind.Sequential, Size = 0x18)] - internal struct RTL_DRIVE_LETTER_CURDIR64 + public struct RTL_DRIVE_LETTER_CURDIR64 { public ushort Flags; public ushort Length; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs index 1debe70..59532ee 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS32.cs @@ -5,7 +5,7 @@ namespace Windows.Win32.System.Threading; /// [StructLayout(LayoutKind.Explicit)] -internal partial struct RTL_USER_PROCESS_PARAMETERS32 +public partial struct RTL_USER_PROCESS_PARAMETERS32 { [FieldOffset(0x00)] public uint MaximumLength; [FieldOffset(0x04)] public uint Length; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS64.cs index 4f31016..6725f62 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/Threading/RTL_USER_PROCESS_PARAMETERS64.cs @@ -4,7 +4,7 @@ namespace Windows.Win32.System.Threading; [StructLayout(LayoutKind.Explicit)] -internal partial struct RTL_USER_PROCESS_PARAMETERS64 +public partial struct RTL_USER_PROCESS_PARAMETERS64 { [FieldOffset(0x00)] public uint MaximumLength; [FieldOffset(0x04)] public uint Length; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs index d34e1ed..965d15b 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY32.cs @@ -5,7 +5,7 @@ namespace Windows.Win32.System.WindowsProgramming; [StructLayout(LayoutKind.Explicit)] -internal struct LDR_DATA_TABLE_ENTRY32 +public struct LDR_DATA_TABLE_ENTRY32 { /// 3.10 and higher [FieldOffset(0x00)] internal readonly LIST_ENTRY32 InLoadOrderLinks; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs index 5bddf17..dda3faa 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DATA_TABLE_ENTRY64.cs @@ -7,7 +7,7 @@ namespace Windows.Win32.System.WindowsProgramming; /// https://web.archive.org/web/https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry.htm /// [StructLayout(LayoutKind.Explicit)] -internal struct LDR_DATA_TABLE_ENTRY64 +public struct LDR_DATA_TABLE_ENTRY64 { /// 3.10 and higher [FieldOffset(0x00)] internal readonly LIST_ENTRY64 InLoadOrderLinks; diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DLL_LOAD_REASON.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DLL_LOAD_REASON.cs index c8a44c5..d726a63 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DLL_LOAD_REASON.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LDR_DLL_LOAD_REASON.cs @@ -1,5 +1,5 @@ namespace Windows.Win32.System.WindowsProgramming; -internal enum LDR_DLL_LOAD_REASON +public enum LDR_DLL_LOAD_REASON { LoadReasonStaticDependency, LoadReasonStaticForwarderDependency, diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LdrEntryFlags.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LdrEntryFlags.cs index 9ad328b..b15b618 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LdrEntryFlags.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/LdrEntryFlags.cs @@ -7,7 +7,7 @@ namespace Windows.Win32.System.WindowsProgramming; /// Flags deprecated before Windows 7 are not included in this enum ///
[Flags] -internal enum LdrEntryFlags : uint +public enum LdrEntryFlags : uint { PackagedBinary = 0x1U, /// 3.51 to 6.1 (Win7) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs index a3ccd74..a915693 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_INFORMATION_CLASS.cs @@ -4,7 +4,7 @@ namespace Windows.Win32.System.WindowsProgramming; /// The generated enum is missing most entries and has ObjectTypeInformation as `1` instead of `2`. Will changing its value prove to be a mistake? /// https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntobapi_x/object_information_class.htm ///
-enum OBJECT_INFORMATION_CLASS +public enum OBJECT_INFORMATION_CLASS { /// /// A PUBLIC_OBJECT_BASIC_INFORMATION structure is supplied. diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs index a05d80c..6ad045b 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs @@ -4,7 +4,7 @@ namespace Windows.Win32.System.WindowsProgramming; public struct OBJECT_NAME_INFORMATION { - internal UNICODE_STRING Name; + public UNICODE_STRING Name; public string NameAsString => Name.ToStringLength(); } diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs index b7e7dd3..3158a0d 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPES_INFORMATION.cs @@ -1,5 +1,5 @@ namespace Windows.Win32.System.WindowsProgramming; -struct OBJECT_TYPES_INFORMATION +public struct OBJECT_TYPES_INFORMATION { public OBJECT_TYPES_INFORMATION(uint numberOfTypes) { diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs index 9c17728..10e5db9 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_TYPE_INFORMATION.cs @@ -2,7 +2,7 @@ using ACCESS_MASK = PInvoke.Kernel32.ACCESS_MASK; namespace Windows.Win32.System.WindowsProgramming; -internal struct OBJECT_TYPE_INFORMATION +public struct OBJECT_TYPE_INFORMATION { #pragma warning disable CS0649 diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs index ac959af..7543ba9 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/SYSTEM_INFORMATION_CLASS.cs @@ -1,7 +1,7 @@ namespace Windows.Win32.System.WindowsProgramming; // generated definition lacks SystemHandleInformation, SystemExtendedHandleInformation -enum SYSTEM_INFORMATION_CLASS +public enum SYSTEM_INFORMATION_CLASS { SystemBasicInformation = 0, SystemPerformanceInformation = 2, diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs index 1bc6af5..986b4c2 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs @@ -3,7 +3,7 @@ namespace Windows.Win32; /// /// A stand-in for 32-bit pointers in a 64-bit runtime. /// -internal struct UIntPtr32 +public struct UIntPtr32 { public uint Value; @@ -13,7 +13,7 @@ internal struct UIntPtr32 public unsafe static explicit operator void*(UIntPtr32 v) => (void*)v.Value; } -internal struct UIntPtr32 where T : unmanaged +public struct UIntPtr32 where T : unmanaged { public uint Value; diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs index 96bfbfe..1f0811c 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64.cs @@ -3,7 +3,7 @@ namespace Windows.Win32; /// /// A stand-in for 64-bit pointers in a 32-bit runtime. /// -internal struct UIntPtr64 +public struct UIntPtr64 { public ulong Value; diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs index 5924d14..e88b3b4 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr64_T.cs @@ -1,7 +1,8 @@ +using System.Diagnostics.Contracts; namespace Windows.Win32; /// -internal struct UIntPtr64 where T : unmanaged +public struct UIntPtr64 where T : unmanaged { public ulong Value; From bbac7d1280da28da0bb40593d222d4c848574669 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 27 Apr 2023 20:22:44 -0700 Subject: [PATCH 236/306] refactor: remove orphaned isProcessProtected field --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 0f579bb..aab5ae0 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -26,7 +26,6 @@ public partial class ProcessInfo private (ProcessBasicInformation? v, Exception? ex) processBasicInformation; private (string? v, Exception? ex) processCommandLine; private (ProcessQueryHandle? v, Exception? ex) processHandle; - private (bool? v, Exception? ex) processIsProtected; private (string? v, Exception? ex) processMainModulePath; private (string? v, Exception? ex) processName; private (PS_PROTECTION? v, Exception? ex) processProtection; From 30bf1adb94073bbe095d4254c0efec88e7ff7c43 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 27 Apr 2023 20:23:53 -0700 Subject: [PATCH 237/306] refactor: simplify some ProcessInfo properties --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 34 ++++++++--------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index aab5ae0..fffd3e4 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -133,15 +133,9 @@ public ProcessInfo(Process process) { get { - if (processAndHostOSArch == default) - { + if (processAndHostOSArch is (null, null)) _ = Is32BitEmulatedProcess; - return processAndHostOSArch; - } - else - { - return processAndHostOSArch; - } + return processAndHostOSArch; } } @@ -149,7 +143,7 @@ public ProcessInfo(Process process) { get { - if (processHandle == default) + if (processHandle is (null, null)) { const string exMsg = "Unable to open handle; "; // We can't lookup the ProcessProtection without opening a process handle to check the process protection. @@ -409,20 +403,14 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection } } - // TODO: ProcessBasicInformation and recursive members internal (ProcessBasicInformation? v, Exception? ex) ProcessBasicInformation { get { - if (processBasicInformation == default) - { + if (processBasicInformation is (null, null)) GetPropertiesViaProcessHandle(); - return processBasicInformation; - } - else - { - return processBasicInformation; - } + + return processBasicInformation; } } @@ -462,7 +450,7 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { get { - if (processMainModulePath == default) + if (processMainModulePath is (null, null)) { if (ProcessProtection.v is null) return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Failed to query the process's protection:" + Env.NewLine + ProcessProtection.ex)); @@ -494,13 +482,13 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { get { - if (processName == default) + if (processName is (null, null)) { try { - var proc = Process.GetProcessById(ProcessId); - if (proc?.HasExited != false) - return processName = (null, new InvalidOperationException("Process has exited, so the requested information is not available.")); + Process proc = Process.GetProcessById(ProcessId); + if (proc.HasExited) + return processName = (null, new InvalidOperationException("Process has exited so the requested information is not available.")); else return processName = (proc.ProcessName, null); } catch (Exception ex) From 28b66315ef6f2b97526cb72d9a299fbf60d8680f Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 27 Apr 2023 20:24:39 -0700 Subject: [PATCH 238/306] refactor: change ProcessCommandLine to call GetPropertiesViaProcessHandle instead of TryGetProcessCommandLine --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 317 +--------------------- 1 file changed, 7 insertions(+), 310 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index fffd3e4..3abbf6c 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -415,26 +414,21 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection } /// - /// + /// The string used to start the process (e.g. \??\C:\WINDOWS\system32\conhost.exe 0x4, C:\WINDOWS\System32\svchost.exe -k netsvcs -p -s ShellHWDetection). /// /// TODO: rework for ProcessBasicInformation public (string? v, Exception? ex) ProcessCommandLine { get { - if (processCommandLine == default) + if (processCommandLine is (null, null)) { - return ProcessProtection.v?.Type switch - { - PsProtectedTypeNone or PsProtectedTypeProtectedLight => processCommandLine = TryGetProcessCommandLine(), - PsProtectedTypeProtected => processCommandLine = (null, new UnauthorizedAccessException("ProcessCommandLine cannot be queried or copied; the process's Protection level prevents access to the process's command line.")), - _ => processCommandLine = (null, new InvalidOperationException("ProcessCommandLine cannot be queried or copied; Failed to query the process's protection.")) - }; - } - else - { - return processCommandLine; + if (ProcessId == Env.ProcessId) + return processCommandLine = (Env.CommandLine, null); + GetPropertiesViaProcessHandle(); } + + return processCommandLine; } } @@ -549,301 +543,4 @@ private unsafe static string GetFullProcessImageName(uint processId) throw new Win32Exception(); } } - - private (string? v, Exception? ex) TryGetProcessCommandLine() - { - if (ProcessId == Env.ProcessId) - return (Env.CommandLine, null); - - try - { - if (!IsDebugModeEnabled()) - Process.EnterDebugMode(); - } - catch (Exception ex) - { - Trace.WriteLine("Failed check if Debug Mode was enabled or failed to enable Debug Mode for the current process." + Env.NewLine + ex.ToString()); - } - - using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, false, (uint)ProcessId); - if (hProcess.IsInvalid) - return (null, new Win32Exception()); - - try - { - return (GetProcessCommandLine(hProcess, Is32BitEmulatedProcess), null); - } - catch (Exception ex) - { - return (null, ex); - } - } - - /// TODO: clean up Exception. Implement custom exceptions? - /// Try to get a process's command line from its PEB - /// A handle to the target process with the rights PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ - /// The provided process handle is invalid. - /// - /// IsWow64Process failed to determine if target process is running under WOW. See InnerException. - /// -OR- - /// NtQueryInformationProcess failed to get a process's command line. See InnerException. - /// -OR- - /// NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB. See InnerException. - /// -OR- - /// NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process. See InnerException. - /// -OR- - /// NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. - /// -OR- - /// NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process. See InnerException. - /// -OR- - /// NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException. - /// -OR- - /// ReadProcessMemory failed to copy another process's PEB to this process. See InnerException. - /// -OR- - /// ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException. - /// -OR- - /// ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException. - /// -OR- - /// - /// ReAllocHGlobal received a null pointer, but didn't check the error code. This is not a real OutOfMemoryException - private unsafe static string GetProcessCommandLine(SafeProcessHandle hProcess, (bool? v, Exception? ex) isWow64Process) - { - if (hProcess.IsInvalid) - throw new ArgumentException("The provided process handle is invalid.", paramName: nameof(hProcess)); - - if (isWow64Process.ex is not null) - throw new Exception("Failed to determine target process is running under WOW. See InnerException.", isWow64Process.ex); - - bool targetIs32BitProcess = isWow64Process.v is true; - bool weAre32BitAndTheyAre64Bit = !Env.Is64BitProcess && !targetIs32BitProcess; - bool weAre64BitAndTheyAre32Bit = Env.Is64BitProcess && targetIs32BitProcess; - NTSTATUS status; - uint returnLength = 0; - ulong bytesRead; - - /** If Win8.1 or later */ - if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) - { - const uint ProcessCommandLineInformation = 60u; - uint bufferLength = (uint)Marshal.SizeOf() + 2048u; - using SafeBuffer safeBuffer = new(numBytes: bufferLength); - - status = NtQueryInformationProcess( - hProcess, - (PROCESSINFOCLASS)ProcessCommandLineInformation, - (void*)safeBuffer.DangerousGetHandle(), - bufferLength, - ref returnLength - ); - - if (status == Code.STATUS_INFO_LENGTH_MISMATCH) - { -#if DEBUG - Console.Out.WriteLine( - $"bufferLength: {bufferLength}\n" + - $"returnLength: {returnLength}"); - bufferLength = returnLength; -#endif - try - { - // the native call to LocalReAlloc (via Marshal.ReAllocHGlobal) sometimes returns a null pointer. This is a Legacy function. Why does .NET not use malloc/realloc? - //pString->Buffer = new((char*)Marshal.ReAllocHGlobal((IntPtr)pString->Buffer.Value, (IntPtr)bufferLength)); - safeBuffer.Reallocate(numBytes: returnLength); - } - catch (OutOfMemoryException) // ReAllocHGlobal received a null pointer, but didn't check the error code - { - // none of these were of interest... - //var pinerr = Marshal.GetLastPInvokeError(); - //var syserr = Marshal.GetLastSystemError(); - //var winerr = Marshal.GetLastWin32Error(); - throw; - } - - status = NtQueryInformationProcess( - hProcess, - (PROCESSINFOCLASS)ProcessCommandLineInformation, - (void*)safeBuffer.DangerousGetHandle(), - bufferLength, - ref returnLength - ); - } - - if (status.IsSuccessful) - return safeBuffer.Read(0).ToStringZ() ?? string.Empty; - else - throw new Exception("NtQueryInformationProcess failed to get a process's command line. See InnerException.", new NTStatusException(status)); - } - else /** Read CommandLine from PEB's Process Parameters */ - { - /** if our process is 32-bit and the target process is 64-bit, use a workaround. - The following blocks use a hybrid of SystemInformer's solution (PhGetProcessCommandLine) and the alternative provided at https://stackoverflow.com/a/14012919/14894786. - All comments inside the code blocks are from either source. - */ - if (weAre32BitAndTheyAre64Bit) /** This process is 32-bit, that process is 64-bit */ - { - using SafeBuffer buffer = new(numBytes: 0); - PROCESS_BASIC_INFORMATION64 basicInfo = default; - PEB64 peb = default; - RTL_USER_PROCESS_PARAMETERS64 parameters = default; - - // Get the PEB address. - buffer.Initialize(numElements: 1); - status = NtWow64QueryInformationProcess64( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - &basicInfo, - (uint)buffer.ByteLength, - &returnLength); - buffer.Initialize(numBytes: returnLength); - byte* pointer = null; - buffer.AcquirePointer(ref pointer); - status = NtWow64QueryInformationProcess64(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, pointer, (uint)buffer.ByteLength, &returnLength); - buffer.ReleasePointer(); - if (status.IsSuccessful) - { - basicInfo = buffer.Read(0); - buffer.Dispose(); - } - else - { - throw new Exception("NtWow64QueryInformationProcess64 failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); - } - - // copy PEB - if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)basicInfo.PebBaseAddress, &peb, (ulong)Marshal.SizeOf(peb), &bytesRead)).IsSuccessful) - throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); - - // Copy RTL_USER_PROCESS_PARAMETERS. - if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)peb.ProcessParameters, ¶meters, (ulong)Marshal.SizeOf(parameters), &bytesRead)).IsSuccessful) - throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); - - using UNICODE_STRING cmdLine = new() - { - MaximumLength = parameters.CommandLine.MaximumLength, - Length = parameters.CommandLine.Length, - Buffer = (char*)Marshal.AllocHGlobal(parameters.CommandLine.MaximumLength) - }; - - if (!(status = NtWow64ReadVirtualMemory64(hProcess, (UIntPtr64)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, &bytesRead)).IsSuccessful) - throw new Exception("NtWow64ReadVirtualMemory64 failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); - - return cmdLine.ToStringLength(); - } - else if (weAre64BitAndTheyAre32Bit) /** This is 64-bit, that is 32-bit */ - { - using SafeBuffer buffer = new(numElements: 1); - PROCESS_BASIC_INFORMATION32 basicInfo = default; - PEB32 peb = default; - RTL_USER_PROCESS_PARAMETERS32 parameters = default; - - // Get the PEB address. - buffer.Initialize(numElements: 1); - status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - &basicInfo, - (uint)buffer.ByteLength, - ref returnLength); - while (status == Code.STATUS_INFO_LENGTH_MISMATCH) - { - buffer.Initialize(numBytes: returnLength); - byte* pointer = null; - buffer.AcquirePointer(ref pointer); - status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - pointer, - (uint)buffer.ByteLength, - ref returnLength); - buffer.ReleasePointer(); - } - if (status.IsSuccessful) - { - basicInfo = buffer.Read(0); - buffer.Dispose(); - } - else - { - throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); - } - - // copy PEB - if (!ReadProcessMemory(hProcess, (void*)basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); - - // Copy RTL_USER_PROCESS_PARAMETERS. - if (!ReadProcessMemory(hProcess, (void*)peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); - - using UNICODE_STRING cmdLine = new() - { - MaximumLength = parameters.CommandLine.MaximumLength, - Length = parameters.CommandLine.Length, - Buffer = (char*)Marshal.AllocHGlobal(Marshal.SizeOf() * 260) - }; - - if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); - - return cmdLine.ToStringLength(); - } - else /** this process and that process are the same bit architecture */ - { - using SafeBuffer buffer = new(numElements: 1); - PROCESS_BASIC_INFORMATION basicInfo = default; - PEB peb = default; - RTL_USER_PROCESS_PARAMETERS parameters = default; - - // Get the PEB address. - status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - &basicInfo, - (uint)buffer.ByteLength, - ref returnLength); - while (status == Code.STATUS_INFO_LENGTH_MISMATCH) - { - buffer.Initialize(numBytes: returnLength); - byte* pointer = null; - buffer.AcquirePointer(ref pointer); - status = NtQueryInformationProcess( - hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, - pointer, - (uint)buffer.ByteLength, - ref returnLength); - buffer.ReleasePointer(); - } - if (status.IsSuccessful) - { - basicInfo = buffer.Read(0); - buffer.Dispose(); - } - else - { - throw new Exception("NtQueryInformationProcess failed to get the memory address of another process's PEB. See InnerException.", new NTStatusException(status)); - } - - // copy PEB - if (!ReadProcessMemory(hProcess, basicInfo.PebBaseAddress, &peb, (nuint)Marshal.SizeOf(peb), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's PEB to this process. See InnerException.", new NTStatusException(status)); - - // Copy RTL_USER_PROCESS_PARAMETERS. - if (!ReadProcessMemory(hProcess, peb.ProcessParameters, ¶meters, (nuint)Marshal.SizeOf(parameters), (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's RTL_USER_PROCESS_PARAMETERS to this process. See InnerException.", new NTStatusException(status)); - - using UNICODE_STRING cmdLine = new() - { - MaximumLength = parameters.CommandLine.MaximumLength, - Length = parameters.CommandLine.Length, - Buffer = (char*)Marshal.AllocHGlobal(Marshal.SizeOf() * 260) - }; - - if (!ReadProcessMemory(hProcess, (void*)parameters.CommandLine.Buffer, cmdLine.Buffer.Value, cmdLine.MaximumLength, (nuint*)&bytesRead)) - throw new Exception("ReadProcessMemory failed to copy another process's command line character string to this process. See InnerException.", new NTStatusException(status)); - - return cmdLine.ToStringLength(); - } - } - } } From 9e5cf5641177fc428ece4939e8f2b172e74977ee Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 17:25:47 -0700 Subject: [PATCH 239/306] refactor: add PS_PROTECTION.GetHashCode() override --- deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs index c43e1d9..4ec0dec 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PS_PROTECTION.cs @@ -63,4 +63,6 @@ public override string ToString() return $"{sType} {sSigner}"; } + + public override int GetHashCode() => Level; } From f600e6183c2061fb136f6d5861b67a520700770c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 17:27:05 -0700 Subject: [PATCH 240/306] refactor: make ProcessProtection compare field to (null, null) instead of default It's a small difference that *should* skip some type comparisons. --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 3abbf6c..01351ce 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -381,7 +381,7 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { get { - if (processProtection == default) + if (processProtection is (null, null)) { const uint ProcessProtectionInformation = 61; // Retrieves a BYTE value indicating the type of protected process and the protected process signer. PS_PROTECTION protection = default; From 9045fa262ffab8172aee071e2c3ebe95488445dc Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 17:31:09 -0700 Subject: [PATCH 241/306] fix: allow casting List to/from ProcessList refactor: change ProcessList from List to IList with List backing field .NET Runtime prohibits casting an object from a base class to an inheriting class. I can come up with more reasons why it should be *allowed* than *disallowed*. --- deadlock-dotnet-sdk/Domain/ProcessList.cs | 44 ++++++++++++++--------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessList.cs b/deadlock-dotnet-sdk/Domain/ProcessList.cs index a3f7f10..4a766a6 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessList.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessList.cs @@ -1,15 +1,32 @@ +using System.Collections; using System.Diagnostics; using static System.Environment; namespace deadlock_dotnet_sdk.Domain; -public sealed class ProcessList : List +public sealed class ProcessList : IList { - public ProcessList() { } - public ProcessList(IEnumerable collection) : base(collection) - { } + private readonly List value; - public ProcessList(int capacity) : base(capacity) - { } + public ProcessList() { value = new(); } + public ProcessList(List list) => value = list; + public ProcessList(IEnumerable collection) => value = new(collection); + public ProcessList(int capacity) => value = new(capacity); + + #region IList implementation + public ProcessInfo this[int index] { get => value[index]; set => this.value[index] = value; } + public int Count => value.Count; + public bool IsReadOnly => ((ICollection)value).IsReadOnly; + public void Add(ProcessInfo item) => value.Add(item); + public void Clear() => value.Clear(); + public bool Contains(ProcessInfo item) => value.Contains(item); + public void CopyTo(ProcessInfo[] array, int arrayIndex) => value.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => ((IEnumerable)value).GetEnumerator(); + public int IndexOf(ProcessInfo item) => value.IndexOf(item); + public void Insert(int index, ProcessInfo item) => value.Insert(index, item); + public bool Remove(ProcessInfo item) => value.Remove(item); + public void RemoveAt(int index) => value.RemoveAt(index); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)value).GetEnumerator(); + #endregion IList implementation /// /// Find a ProcessInfo by its process ID and returns it. If no existing ProcessInfo is found, the system is queried for a Process with that ID. If the returned Process is not null, it is returned as a ProcessInfo object. @@ -18,7 +35,7 @@ public ProcessList(int capacity) : base(capacity) /// The existing ProcessInfo object with an ID matching . If it does not exist yet, the system is queried for a Process with that ID. If the returned Process is not null, it is returned as a ProcessInfo object. public ProcessInfo GetProcessById(int processId) { - var result = Find(p => p.Process?.Id == processId); + var result = value.Find(p => p.Process?.Id == processId); if (result is not null) return result; @@ -26,16 +43,7 @@ public ProcessInfo GetProcessById(int processId) try { - var p = Process.GetProcessById(processId); - - if (p is null) - { - pi = new ProcessInfo(processId); - Add(pi); - return pi; - } - - pi = new(p); + pi = new(Process.GetProcessById(processId)); Add(pi); return pi; } @@ -54,4 +62,6 @@ public ProcessInfo GetProcessById(int processId) return pi; } } + + public static explicit operator ProcessList(List v) => new(v); } From cc5c5a1a7bb1da2b0c26921a9c4fefcc76a02db5 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 17:32:19 -0700 Subject: [PATCH 242/306] refactor: change Trace.WriteLine calls to Trace.TraceError --- deadlock-dotnet-sdk/Domain/ProcessList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessList.cs b/deadlock-dotnet-sdk/Domain/ProcessList.cs index 4a766a6..99f6156 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessList.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessList.cs @@ -49,14 +49,14 @@ public ProcessInfo GetProcessById(int processId) } catch (ArgumentException ex) // { - Trace.WriteLine($"No process was found with ID {processId}. If it *did* exist, the process had exited and is not in .NET's internal process list." + NewLine + ex.ToString(), "ERROR"); + Trace.TraceError($"No process was found with ID {processId}. If it *did* exist, the process had exited and is not in .NET's internal process list." + NewLine + ex.ToString()); pi = new ProcessInfo(processId); Add(pi); return pi; } catch (Exception ex) { - Trace.WriteLine("An unknown exception was thrown." + NewLine + ex, "ERROR"); + Trace.TraceError("An unknown exception was thrown." + NewLine + ex); pi = new ProcessInfo(processId); Add(pi); return pi; From 61d8c3032307f020707aef59ffd8b71b062cdc13 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 17:33:37 -0700 Subject: [PATCH 243/306] refactor: make FileName check ObjectName if FileFullPath is null --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 7d27c0f..ffa5487 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -235,6 +235,10 @@ public unsafe (string? v, Exception? ex) FileNameInfo { return fileName = (Path.GetFileName(FileFullPath.v), null); } + else if (ObjectName.v is not null) + { + return fileName = (Path.GetFileName(ObjectName.v), null); + } else { return fileName = (null, new InvalidOperationException("Unable to query FileName; This operation requires FileFullPath.")); From b4f89874a8d3769be6ee7d553ec42def2ba5785b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 17:40:01 -0700 Subject: [PATCH 244/306] refactor: inline TryGetFinalPath refactor: remove tasks from FileFullPath --- .../Domain/SafeFileHandleEx.cs | 107 +++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index ffa5487..f6d1f23 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -222,7 +222,112 @@ public unsafe (string? v, Exception? ex) FileNameInfo } } - public (string? v, Exception? ex) FileFullPath => fileFullPath == default ? (fileFullPath = TryGetFinalPath()) : fileFullPath; + public unsafe (string? v, Exception? ex) FileFullPath + { + get + { + if (fileFullPath == default) + { + try + { + if (ProcessInfo.ProcessProtection.v is null) + return fileFullPath = (null, new InvalidOperationException("Unable to query disk/network path; Failed to query the process's protection:" + Environment.NewLine + ProcessInfo.ProcessProtection.ex)); + if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) + return fileFullPath = (null, new UnauthorizedAccessException("Unable to query disk/network path; The process is protected.")); + if (HandleObjectType.v is null) + return fileFullPath = (null, new InvalidOperationException("Unable to query disk/network path; Failed to query handle object type." + Environment.NewLine + HandleObjectType.ex)); + if (IsFileHandle.v is false) + return fileFullPath = (null, new InvalidOperationException("Unable to query disk/network path; The handle's object is not a File.")); + if (FileHandleType.v is not FileType.Disk) + return fileFullPath = (null, new InvalidOperationException("Unable to query disk/network path; The File object is not a Disk-type File.")); + + uint bufLength = (uint)short.MaxValue; + using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufLength)); + uint length = 0; + const uint LengthIndicatesError = 0; + + // Try without duplicating. If it fails, try duplicating the handle. + var sw = new Stopwatch(); + sw.Start(); + try + { + if ((length = GetFinalPathNameByHandle(handle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)) <= bufLength) + { + return (buffer.ToString(), null); + } + else if (length > bufLength) + { + using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); + if ((length = GetFinalPathNameByHandle(handle, newBuffer, length, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)) is not 0) + return (newBuffer.ToString(), null); + else + throw new Win32Exception(); + } + else + { + throw new Win32Exception(); + } + } + catch (Exception ex) + { + _ = ex; + } + finally + { + sw.Stop(); + Console.Out.WriteLine($"(handle 0x{handle:X}) TryGetFinalPath time: {sw.Elapsed}"); // TODO: debug. Determine better timeout. + } + + /// Return the normalized drive name. This is the default. + using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); + if (processHandle is null || processHandle?.IsInvalid == true) + throw new Win32Exception(); + + if (!DuplicateHandle(processHandle, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) + throw new Win32Exception(); + + length = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); + + if (length != 0) + { + if (length <= bufLength) + return fileFullPath = (buffer.ToString(), null); + + { + // buffer was too small. Reallocate buffer with size matched 'length' and try again + using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); + bufLength = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); + return (newBuffer.ToString(), null); + } + } + else + { + Win32ErrorCode error = (Win32ErrorCode)Marshal.GetLastWin32Error(); + Trace.TraceError(error.GetMessage()); + + return (null, error switch + { + // Removable storage, deleted item, network shares, et cetera + Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(error)), + // unlikely, but possible if system has little free memory + Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation.", new Win32Exception(error)), + // possible only if FILE_NAME_NORMALIZED (0) is invalid + Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(error)), + _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path.", new Win32Exception(error)) + }); + } + } + catch (Exception ex) + { + return fileFullPath = (null, ex); + } + } + else + { + return fileFullPath; + } + } + } // TODO: leverage GetFileInformationByHandleEx public (string? v, Exception? ex) FileName From 29e198d192d694767d5470363bbdfa8413fe4933 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 17:42:58 -0700 Subject: [PATCH 245/306] fix: update remaining references of FILE_NAME enum to GETFINALPATHNAMEBYHANDLE_FLAGS --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index f6d1f23..e1f9861 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -425,7 +425,7 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() try { const int timeout = 50; - Task taskGetLength = new(() => GetFinalPathNameByHandle(handle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED)); + Task taskGetLength = new(() => GetFinalPathNameByHandle(handle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)); if (Task.WhenAny(taskGetLength, Task.Delay(timeout)).Result == taskGetLength) length = taskGetLength.Result; else @@ -441,7 +441,7 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() else { using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - Task taskGetPath = new(() => GetFinalPathNameByHandle(handle, newBuffer, length, FILE_NAME.FILE_NAME_NORMALIZED)); + Task taskGetPath = new(() => GetFinalPathNameByHandle(handle, newBuffer, length, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)); if (Task.WhenAny(taskGetPath, Task.Delay(timeout)).Result == taskGetPath) { if (taskGetPath.Result is not 0) @@ -473,7 +473,7 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() if (!DuplicateHandle(processHandle, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) throw new Win32Exception(); - length = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + length = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); if (length != 0) { @@ -481,7 +481,7 @@ private unsafe (string? v, Exception? ex) TryGetFinalPath() { // buffer was too small. Reallocate buffer with size matched 'length' and try again using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - bufLength = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, FILE_NAME.FILE_NAME_NORMALIZED); + bufLength = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); return (newBuffer.ToString(), null); } else From acd9b15a85ecc35ce5d468cc486a9fdcee365ff0 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 17:43:42 -0700 Subject: [PATCH 246/306] refactor: copy TryGetFinalPath docs to FileFullPath --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index e1f9861..80d9b21 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -222,6 +222,11 @@ public unsafe (string? v, Exception? ex) FileNameInfo } } + /// + /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. + /// + /// If successful, returns a path string formatted as 'X:\dir\file.ext' or 'X:\dir' + /// GetFinalPathNameByHandle will sometimes hang when querying the Name of a Pipe. public unsafe (string? v, Exception? ex) FileFullPath { get From 7d5a7b8ef285df3a5f4b9680e139143167e2b1a8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 17:44:19 -0700 Subject: [PATCH 247/306] refactor: remove TryGetFinalPath --- .../Domain/SafeFileHandleEx.cs | 117 ------------------ 1 file changed, 117 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 80d9b21..12c8344 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -400,123 +400,6 @@ public enum FileType : uint Pipe = FILE_TYPE.FILE_TYPE_PIPE, } - /// - /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. - /// - /// If successful, returns a path string formatted as 'X:\dir\file.ext' or 'X:\dir' - /// GetFinalPathNameByHandle will sometimes hang will querying the Name of a Pipe. - private unsafe (string? v, Exception? ex) TryGetFinalPath() - { - try - { - if (ProcessInfo.ProcessProtection.v is null) - throw new InvalidOperationException("Unable to query disk/network path; Failed to query the process's protection:" + Environment.NewLine + ProcessInfo.ProcessProtection.ex); - if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) - throw new UnauthorizedAccessException("Unable to query disk/network path; The process is protected."); - if (HandleObjectType.v is null) - throw new InvalidOperationException("Unable to query disk/network path; Failed to query handle object type." + Environment.NewLine + HandleObjectType.ex); - if (IsFileHandle.v is false) - throw new InvalidOperationException("Unable to query disk/network path; The handle's object is not a File."); - if (FileHandleType.v is not FileType.Disk) - throw new InvalidOperationException("Unable to query disk/network path; The File object is not a Disk-type File."); - - uint bufLength = (uint)short.MaxValue; - using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufLength)); - uint length = 0; - - // Try without duplicating. If it fails, try duplicating the handle. - var sw = new Stopwatch(); - sw.Start(); - try - { - const int timeout = 50; - Task taskGetLength = new(() => GetFinalPathNameByHandle(handle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)); - if (Task.WhenAny(taskGetLength, Task.Delay(timeout)).Result == taskGetLength) - length = taskGetLength.Result; - else - throw new TimeoutException($"GetFinalPathNameHandle did not complete in {timeout}ms."); - - if (length is 0) - throw new Win32Exception(); - - if (length <= bufLength) - { - return (buffer.ToString(), null); - } - else - { - using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - Task taskGetPath = new(() => GetFinalPathNameByHandle(handle, newBuffer, length, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)); - if (Task.WhenAny(taskGetPath, Task.Delay(timeout)).Result == taskGetPath) - { - if (taskGetPath.Result is not 0) - return (newBuffer.ToString(), null); - else - throw new Win32Exception(); - } - else - { - throw new TimeoutException($"GetFinalPathNameHandle did not complete in {timeout}ms."); - } - } - } - catch (Exception ex) - { - _ = ex; - } - finally - { - sw.Stop(); - Console.Out.WriteLine($"(handle 0x{handle:X}) TryGetFinalPath time: {sw.Elapsed}"); // TODO: debug. Determine better timeout. - } - - /// Return the normalized drive name. This is the default. - using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); - if (processHandle is null || processHandle?.IsInvalid == true) - throw new Win32Exception(); - - if (!DuplicateHandle(processHandle, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) - throw new Win32Exception(); - - length = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); - - if (length != 0) - { - if (length > bufLength) - { - // buffer was too small. Reallocate buffer with size matched 'length' and try again - using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - bufLength = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); - return (newBuffer.ToString(), null); - } - else - { - return (buffer.ToString(), null); - } - } - else - { - Win32ErrorCode error = (Win32ErrorCode)Marshal.GetLastWin32Error(); - Debug.Print(error.GetMessage()); - - throw error switch - { - // Removable storage, deleted item, network shares, et cetera - Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(error)), - // unlikely, but possible if system has little free memory - Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation.", new Win32Exception(error)), - // possible only if FILE_NAME_NORMALIZED (0) is invalid - Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(error)), - _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path.", new Win32Exception(error)) - }; - } - } - catch (Exception ex) - { - return (null, ex); - } - } - public override string ToString() { string[] exLog = ExceptionLog.ConvertAll(ex => ex.ToString()).ToArray(); From 82ae66d7353c32081acd0a0574db3b637263264c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 18:29:41 -0700 Subject: [PATCH 248/306] refactor: change handleObjectType from private to protected --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index bdcd075..6c8be4a 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -25,7 +25,7 @@ namespace deadlock_dotnet_sdk.Domain; public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { // TODO: override IsInvalid - private (string? v, Exception? ex) handleObjectType; + protected (string? v, Exception? ex) handleObjectType; private (string? v, Exception? ex) objectName; private ProcessInfo? processInfo; From edbaae2cf7f99586f178b0d928339afa421099ce Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 18:30:34 -0700 Subject: [PATCH 249/306] refactor: override base IsClosed with call to GetHandleInformation --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 6c8be4a..4bcf81d 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -24,7 +24,6 @@ namespace deadlock_dotnet_sdk.Domain; /// public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { - // TODO: override IsInvalid protected (string? v, Exception? ex) handleObjectType; private (string? v, Exception? ex) objectName; private ProcessInfo? processInfo; @@ -86,6 +85,28 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals } } + /// + /// (non-persistent) Pass the handle to GetHandleInformation and check for ERROR_INVALID_HANDLE to determine if the handle is open or closed. + /// + public new bool IsClosed => GetIsClosed(); + + private bool GetIsClosed() + { + try + { + HANDLE_FLAGS flags = GetHandleInformation(this); + } + catch (PInvoke.Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_HANDLE) + { + return true; + } + catch (PInvoke.Win32Exception ex) + { + Trace.TraceError(ex.ToString()); + } + return false; + } + /// /// The name of the object e.g. "\\Device\\HarddiskVolume4\\Repos\\BinToss\\deadlock-dotnet-diagnostics\\deadlock-diagnostics" or "\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\Nls\\Sorting\\Versions" /// From 2267f2080b801335ed588e2dc62ee9e15528471b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 18:38:29 -0700 Subject: [PATCH 250/306] refactor: change objectName from private to protected --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 4bcf81d..63f4de6 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -25,7 +25,7 @@ namespace deadlock_dotnet_sdk.Domain; public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { protected (string? v, Exception? ex) handleObjectType; - private (string? v, Exception? ex) objectName; + protected (string? v, Exception? ex) objectName; private ProcessInfo? processInfo; public SafeHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) From ea518acf0b2d0d79adc3659dfd058104075fdcec Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 18:43:27 -0700 Subject: [PATCH 251/306] refactor: allow calling SafeFileHandleEx.ToString without initializing properties' fields refactor: add SafeHandleEx.IsInvalid --- .../Domain/SafeFileHandleEx.cs | 25 ++++++++++++------- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 ++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 12c8344..e03e47b 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -400,7 +400,14 @@ public enum FileType : uint Pipe = FILE_TYPE.FILE_TYPE_PIPE, } - public override string ToString() + public override string ToString() => ToString(true); + + /// + /// Get the string representation of this SafeFileHandleEx object. + /// + /// If TRUE, get values from Properties. If FALSE, get values from Properties' backing fields. + /// The string representation of this SafeFileHandleEx object. + public string ToString(bool init) { string[] exLog = ExceptionLog.ConvertAll(ex => ex.ToString()).ToArray(); for (int i = 0; i < exLog.Length; i++) @@ -408,20 +415,20 @@ public override string ToString() exLog[i] = $" {exLog[i]}".Replace("\n", "\n ") + Environment.NewLine; } - return @$"{GetType().Name} hash:{GetHashCode()} + return @$"{nameof(SafeFileHandleEx)} hash:{GetHashCode()} {nameof(CreatorBackTraceIndex)} : {CreatorBackTraceIndex} - {nameof(FileFullPath)} : {FileFullPath.v ?? FileFullPath.ex?.ToString()} - {nameof(FileHandleType)} : {FileHandleType.v?.ToString() ?? FileFullPath.ex?.ToString()} - {nameof(FileName)} : {FileName.v ?? FileName.ex?.ToString()} + {nameof(FileFullPath)} : {(init ? (FileFullPath.v ?? FileFullPath.ex?.ToString()) : (fileFullPath.v ?? fileFullPath.ex?.ToString()))} + {nameof(FileHandleType)} : {(init ? (FileHandleType.v?.ToString() ?? FileFullPath.ex?.ToString()) : (fileHandleType.v?.ToString() ?? fileHandleType.ex?.ToString()))} + {nameof(FileName)} : {(init ? (FileName.v ?? FileName.ex?.ToString()) : (fileName.v ?? fileName.ex?.ToString()))} {nameof(GrantedAccess)} : {SysHandleEx.GrantedAccessString} - {nameof(HandleObjectType)} : {HandleObjectType.v ?? HandleObjectType.ex?.ToString()} + {nameof(HandleObjectType)} : {(init ? (HandleObjectType.v ?? HandleObjectType.ex?.ToString()) : (handleObjectType.v ?? handleObjectType.ex?.ToString()))} {nameof(HandleValue)} : {HandleValue} (0x{HandleValue:X}) {nameof(IsClosed)} : {IsClosed} - {nameof(IsDirectory)} : {IsDirectory.v?.ToString() ?? IsDirectory.ex?.ToString()} - {nameof(IsFileHandle)} : {IsFileHandle.v?.ToString() ?? IsFileHandle.ex?.ToString()} + {nameof(IsDirectory)} : {(init ? (IsDirectory.v?.ToString() ?? IsDirectory.ex?.ToString()) : (isDirectory.v?.ToString() ?? isDirectory.ex?.ToString()))} + {nameof(IsFileHandle)} : {(init ? (IsFileHandle.v?.ToString() ?? IsFileHandle.ex?.ToString()) : (isFileHandle.v?.ToString() ?? isFileHandle.ex?.ToString()))} {nameof(IsInvalid)} : {IsInvalid} {nameof(ObjectAddress)} : {ObjectAddress} (0x{ObjectAddress:X}) - {nameof(ObjectName)} : {ObjectName.v ?? ObjectName.ex?.ToString()} + {nameof(ObjectName)} : {(init ? (ObjectName.v ?? ObjectName.ex?.ToString()) : (objectName.v ?? objectName.ex?.ToString()))} {nameof(ProcessId)} : {ProcessId} {nameof(ProcessInfo.ParentId)} : {ProcessInfo.ParentId.v?.ToString() ?? ProcessInfo.ParentId.ex?.ToString() ?? string.Empty} {nameof(ProcessInfo.ProcessCommandLine)} : {ProcessInfo.ProcessCommandLine.v ?? ProcessInfo.ProcessCommandLine.ex?.ToString()} diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 63f4de6..61c3a07 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -107,6 +107,8 @@ private bool GetIsClosed() return false; } + public new bool IsInvalid => IsClosed || base.IsInvalid || base.IsClosed; + /// /// The name of the object e.g. "\\Device\\HarddiskVolume4\\Repos\\BinToss\\deadlock-dotnet-diagnostics\\deadlock-diagnostics" or "\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\Nls\\Sorting\\Versions" /// From fbe991c1736d09e7fc3dd8a8b0e2ec63b8ad31fd Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 18:44:10 -0700 Subject: [PATCH 252/306] refactor: move HANDLE_FLAGS cast closer to target variable --- deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs index c91f4ae..d6ac51b 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs @@ -208,5 +208,5 @@ public static bool PrivilegeCheck(SafeHandle ClientToken, ref PRIVILEGE_SET Requ public static SafeFileHandle OpenProcessToken(SafeFileHandle ProcessHandle, TOKEN_ACCESS_MASK DesiredAccess) => OpenProcessToken(ProcessHandle, DesiredAccess, out SafeFileHandle TokenHandle) ? TokenHandle : throw new Win32Exception(); - public static HANDLE_FLAGS GetHandleInformation(SafeHandle hObject) => (HANDLE_FLAGS)(GetHandleInformation(hObject, out uint flags) ? flags : throw new Win32Exception()); + public static HANDLE_FLAGS GetHandleInformation(SafeHandle hObject) => GetHandleInformation(hObject, out uint flags) ? (HANDLE_FLAGS)flags : throw new Win32Exception(); } From 66104ffcb7cd854ec71ff401df90521411378fa8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 19:31:06 -0700 Subject: [PATCH 253/306] refactor: add constants for FileFullPath strings refactor: improve FileFullPath error handling refactor: replace NewLine with return-new constant --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 8 ++-- deadlock-dotnet-sdk/Domain/ProcessList.cs | 4 +- .../Domain/SafeFileHandleEx.cs | 46 +++++++++++++------ deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 01351ce..cf858ec 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -159,7 +159,7 @@ public ProcessInfo(Process process) { // Before assuming anything, try without PROCESS_VM_READ. Without it, we don't need Debug privilege, but the PEB and all of its recursive members (e.g. Command Line) will be unavailable. const string exAccessMsg = exMsg + " The requested permissions were denied."; - string exPermsFirst = Env.NewLine + "First attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) + ", " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); + const string exPermsFirst = "\r\nFirst attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) + ", " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); try { @@ -168,7 +168,7 @@ public ProcessInfo(Process process) catch (Win32Exception ex2) when ((Win32ErrorCode)ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) { // Debug Mode could not be enabled? Was SE_DEBUG_NAME denied to user or is current process not elevated? - string exPermsSecond = Env.NewLine + "Second attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); + const string exPermsSecond = "\r\nSecond attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); return processHandle = (null, new UnauthorizedAccessException(exAccessMsg + exPermsFirst + exPermsSecond, ex2)); } catch (Exception ex2) @@ -245,7 +245,7 @@ ref returnLength { #if DEBUG Trace.TraceInformation( - "bufferLength: " + bufferCmdLine.ByteLength + Env.NewLine + + "bufferLength: " + bufferCmdLine.ByteLength + "\r\n" + "returnLength: " + returnLength); #endif // !WARNING may throw OutOfMemoryException; ReAllocHGlobal received a null pointer, but didn't check the error code @@ -447,7 +447,7 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection if (processMainModulePath is (null, null)) { if (ProcessProtection.v is null) - return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Failed to query the process's protection:" + Env.NewLine + ProcessProtection.ex)); + return processMainModulePath = (null, new InvalidOperationException("Unable to query ProcessMainModulePath; Failed to query the process's protection:\r\n" + ProcessProtection.ex, ProcessProtection.ex)); if (ProcessProtection.v.Value.Type is PsProtectedTypeProtected) return processMainModulePath = (null, new UnauthorizedAccessException("Unable to query ProcessMainModulePath; The process is protected.")); diff --git a/deadlock-dotnet-sdk/Domain/ProcessList.cs b/deadlock-dotnet-sdk/Domain/ProcessList.cs index 99f6156..dc914da 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessList.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessList.cs @@ -49,14 +49,14 @@ public ProcessInfo GetProcessById(int processId) } catch (ArgumentException ex) // { - Trace.TraceError($"No process was found with ID {processId}. If it *did* exist, the process had exited and is not in .NET's internal process list." + NewLine + ex.ToString()); + Trace.TraceError($"No process was found with ID {processId}. If it *did* exist, the process had exited and is not in .NET's internal process list." + "\r\n" + ex.ToString()); pi = new ProcessInfo(processId); Add(pi); return pi; } catch (Exception ex) { - Trace.TraceError("An unknown exception was thrown." + NewLine + ex); + Trace.TraceError("An unknown exception was thrown.\r\n" + ex); pi = new ProcessInfo(processId); Add(pi); return pi; diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index e03e47b..3e71041 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -235,16 +235,18 @@ public unsafe (string? v, Exception? ex) FileFullPath { try { + const string errUnableMsg = "Unable to query " + nameof(FileFullPath) + "; "; + const string errFailMsg = "Failed to query " + nameof(FileFullPath) + "; "; if (ProcessInfo.ProcessProtection.v is null) - return fileFullPath = (null, new InvalidOperationException("Unable to query disk/network path; Failed to query the process's protection:" + Environment.NewLine + ProcessInfo.ProcessProtection.ex)); + return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "Failed to query the process's protection.", ProcessInfo.ProcessProtection.ex)); if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) - return fileFullPath = (null, new UnauthorizedAccessException("Unable to query disk/network path; The process is protected.")); + return fileFullPath = (null, new UnauthorizedAccessException(errUnableMsg + "The process is protected.")); if (HandleObjectType.v is null) - return fileFullPath = (null, new InvalidOperationException("Unable to query disk/network path; Failed to query handle object type." + Environment.NewLine + HandleObjectType.ex)); + return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "Failed to query handle object type.", HandleObjectType.ex)); if (IsFileHandle.v is false) - return fileFullPath = (null, new InvalidOperationException("Unable to query disk/network path; The handle's object is not a File.")); + return fileFullPath = (null, new ArgumentException(errUnableMsg + "The handle's object is not a File.", nameof(IsFileHandle))); if (FileHandleType.v is not FileType.Disk) - return fileFullPath = (null, new InvalidOperationException("Unable to query disk/network path; The File object is not a Disk-type File.")); + return fileFullPath = (null, new ArgumentException(errUnableMsg + "The File object is not a Disk-type File.", nameof(FileHandleType))); uint bufLength = (uint)short.MaxValue; using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufLength)); @@ -256,31 +258,45 @@ public unsafe (string? v, Exception? ex) FileFullPath sw.Start(); try { - if ((length = GetFinalPathNameByHandle(handle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)) <= bufLength) + Win32ErrorCode errorCode = Win32ErrorCode.ERROR_SUCCESS; + length = GetFinalPathNameByHandle(handle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); + + if (length is not LengthIndicatesError && length <= bufLength) { - return (buffer.ToString(), null); + return fileFullPath = (buffer.ToString(), null); } else if (length > bufLength) { using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - if ((length = GetFinalPathNameByHandle(handle, newBuffer, length, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)) is not 0) - return (newBuffer.ToString(), null); - else - throw new Win32Exception(); + if ((length = GetFinalPathNameByHandle(handle, newBuffer, length, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)) is not LengthIndicatesError) + return fileFullPath = (newBuffer.ToString(), null); } else { - throw new Win32Exception(); + errorCode = (Win32ErrorCode)Marshal.GetLastPInvokeError(); } + + Trace.TraceError(errorCode.GetMessage()); + + return fileFullPath = (null, errorCode switch + { + // Removable storage, deleted item, network shares, et cetera + Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException(errFailMsg + $"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(errorCode)), + // unlikely, but possible if system has little free memory + Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException(errFailMsg + "Insufficient memory to complete the operation.", new Win32Exception(errorCode)), + // possible only if FILE_NAME_NORMALIZED (0) is invalid + Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(errorCode)), + _ => new Exception($"An undocumented error ({errorCode}) was returned when querying a file handle for its path.", new Win32Exception(errorCode)) + }); } catch (Exception ex) { - _ = ex; + return fileFullPath = (null, ex); } finally { sw.Stop(); - Console.Out.WriteLine($"(handle 0x{handle:X}) TryGetFinalPath time: {sw.Elapsed}"); // TODO: debug. Determine better timeout. + Console.WriteLine($"(handle 0x{handle:X}) TryGetFinalPath time: {sw.Elapsed}"); } /// Return the normalized drive name. This is the default. @@ -412,7 +428,7 @@ public string ToString(bool init) string[] exLog = ExceptionLog.ConvertAll(ex => ex.ToString()).ToArray(); for (int i = 0; i < exLog.Length; i++) { - exLog[i] = $" {exLog[i]}".Replace("\n", "\n ") + Environment.NewLine; + exLog[i] = $" {exLog[i]}".Replace("\n", "\n ") + "\r\n"; } return @$"{nameof(SafeFileHandleEx)} hash:{GetHashCode()} diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 61c3a07..58ca106 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -60,7 +60,7 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals var (v, ex) = ProcessInfo.ProcessProtection; if (v is null) { - return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Failed to query the process's protection:" + Environment.NewLine + ex, ex)); + return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Failed to query the process's protection:\r\n" + ex, ex)); } else if (v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) { From ee46bfb65ca96a3eb43781949ff626f2d38e69f6 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 19:37:59 -0700 Subject: [PATCH 254/306] refactor: do not normalize FileFullPath when IsFilePathRemote > The Server Message Block (SMB) Protocol does not support queries for normalized paths. Consequently, when you call this function passing the handle of a file opened using SMB, and with the FILE_NAME_NORMALIZED flag, the function splits the path into its components and tries to query for the normalized name of each of those components in turn. If the user lacks access permission to any one of those components, then the function call fails with ERROR_ACCESS_DENIED. --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 3e71041..44a7c23 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -258,8 +258,9 @@ public unsafe (string? v, Exception? ex) FileFullPath sw.Start(); try { + GETFINALPATHNAMEBYHANDLE_FLAGS flags = IsFilePathRemote.v is true ? GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_OPENED : GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED; Win32ErrorCode errorCode = Win32ErrorCode.ERROR_SUCCESS; - length = GetFinalPathNameByHandle(handle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); + length = GetFinalPathNameByHandle(handle, buffer, bufLength, flags); if (length is not LengthIndicatesError && length <= bufLength) { @@ -268,7 +269,7 @@ public unsafe (string? v, Exception? ex) FileFullPath else if (length > bufLength) { using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - if ((length = GetFinalPathNameByHandle(handle, newBuffer, length, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED)) is not LengthIndicatesError) + if ((length = GetFinalPathNameByHandle(handle, newBuffer, length, flags)) is not LengthIndicatesError) return fileFullPath = (newBuffer.ToString(), null); } else From 34315423d751cb86fd47c6382dc7478fbd750955 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 21:23:06 -0700 Subject: [PATCH 255/306] refactor: use PInvoke.Win32Exception wrapper in ProcessInfo --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index cf858ec..cc3e7b2 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -11,7 +11,7 @@ using Code = PInvoke.NTSTATUS.Code; using Env = System.Environment; using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; -using Win32Exception = System.ComponentModel.Win32Exception; +using Win32Exception = PInvoke.Win32Exception; namespace deadlock_dotnet_sdk.Domain; @@ -155,7 +155,7 @@ public ProcessInfo(Process process) PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ), null); } - catch (Win32Exception ex) when ((Win32ErrorCode)ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) + catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) { // Before assuming anything, try without PROCESS_VM_READ. Without it, we don't need Debug privilege, but the PEB and all of its recursive members (e.g. Command Line) will be unavailable. const string exAccessMsg = exMsg + " The requested permissions were denied."; @@ -165,7 +165,7 @@ public ProcessInfo(Process process) { return processHandle = (ProcessQueryHandle.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION), null); } - catch (Win32Exception ex2) when ((Win32ErrorCode)ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) + catch (Win32Exception ex2) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) { // Debug Mode could not be enabled? Was SE_DEBUG_NAME denied to user or is current process not elevated? const string exPermsSecond = "\r\nSecond attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); @@ -456,9 +456,9 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { return processMainModulePath = (GetFullProcessImageName((uint)ProcessId), null); } - catch (Win32Exception ex) when (ex.ErrorCode == 31) + catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_GEN_FAILURE) { - return processMainModulePath = (null, new InvalidOperationException("Process has exited, so the requested information is not available.", ex)); + return processMainModulePath = (null, new InvalidOperationException("Process has exited, but some of its handles are still open. The requested information is not available.", ex)); } catch (Exception ex) { From d0cadd7e6ea08d2dec012bd383453f38b43aa1e5 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 21:24:21 -0700 Subject: [PATCH 256/306] refactor: make get_ProcessHandle assign values to canGetQueryLimitedInfoHandle, canGetReadMemoryHandle --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index cc3e7b2..f86823b 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -150,10 +150,10 @@ public ProcessInfo(Process process) try { - return processHandle = (ProcessQueryHandle.OpenProcessHandle( - ProcessId, - PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ), - null); + ProcessQueryHandle h = ProcessQueryHandle.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); + canGetQueryLimitedInfoHandle = true; + canGetReadMemoryHandle = true; + return processHandle = (h, null); } catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) { @@ -163,7 +163,9 @@ public ProcessInfo(Process process) try { - return processHandle = (ProcessQueryHandle.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION), null); + ProcessQueryHandle h = ProcessQueryHandle.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); + canGetQueryLimitedInfoHandle = true; + return processHandle = (h, null); } catch (Win32Exception ex2) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) { @@ -176,7 +178,7 @@ public ProcessInfo(Process process) return processHandle = (null, new AggregateException(exMsg + " Permissions were denied and an unknown error occurred.", new Exception[] { ex, ex2 })); } } - catch (Win32Exception ex) when ((Win32ErrorCode)ex.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_PARAMETER) + catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_PARAMETER) { return processHandle = (null, new ArgumentException(exMsg + " A process with ID " + ProcessId + " could not be found. The process may have exited.", ex)); } From 810210867ad2e71175b07f943561085b45e36c05 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 21:35:41 -0700 Subject: [PATCH 257/306] refactor: make GetFullProcessImageName an instance method --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 26 ++++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index f86823b..52300b0 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -509,17 +509,20 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection /// The process handle is invalid /// QueryFullProcessImageName failed. See Exception message for details. /// Failed to open process handle for processId; - private unsafe static string GetFullProcessImageName(uint processId) + private unsafe string GetFullProcessImageName(uint processId) { + //TODO: inline uint size = 260 + 1; uint bufferLength = size; + const string errUnableMsg = "Unable to query " + nameof(ProcessMainModulePath) + "; "; - using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, processId); - if (hProcess.IsInvalid) - throw new UnauthorizedAccessException("Cannot query process's filename.", new Win32Exception()); + if (ProcessHandle.v is null) + throw new InvalidOperationException(errUnableMsg + "Failed to open ProcessHandle.", ProcessHandle.ex); + if ((ProcessHandle.v.AccessRights & PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) is 0) + throw new UnauthorizedAccessException(errUnableMsg + nameof(ProcessHandle) + " was opened with insufficient access rights to perform this operation."); using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); - if (QueryFullProcessImageName(hProcess, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, lpExeName: buffer, ref size)) + if (QueryFullProcessImageName(ProcessHandle.v, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, lpExeName: buffer, ref size)) { return buffer.ToString(); } @@ -527,22 +530,15 @@ private unsafe static string GetFullProcessImageName(uint processId) { using PWSTR newBuffer = Marshal.AllocHGlobal((IntPtr)size); if (QueryFullProcessImageName( - hProcess, + ProcessHandle.v, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, newBuffer, ref size)) { return newBuffer.ToString(); // newBuffer.Value will not be null here } - else - { - throw new Win32Exception(); // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - } - } - else - { - // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) - throw new Win32Exception(); } + // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) + throw new Win32Exception(); } } From d037aacb7dd83782d0293102baeddb7265b1227e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 21:48:17 -0700 Subject: [PATCH 258/306] refactor: use SafeBuffer instead of PWSTR PWSTR doesn't have a Reallocate method (which would dispose the old buffer). I *could* write it. --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 52300b0..4bcac07 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -513,7 +513,7 @@ private unsafe string GetFullProcessImageName(uint processId) { //TODO: inline uint size = 260 + 1; - uint bufferLength = size; + char[] array = new char[size]; const string errUnableMsg = "Unable to query " + nameof(ProcessMainModulePath) + "; "; if (ProcessHandle.v is null) @@ -521,21 +521,23 @@ private unsafe string GetFullProcessImageName(uint processId) if ((ProcessHandle.v.AccessRights & PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) is 0) throw new UnauthorizedAccessException(errUnableMsg + nameof(ProcessHandle) + " was opened with insufficient access rights to perform this operation."); - using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufferLength)); - if (QueryFullProcessImageName(ProcessHandle.v, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, lpExeName: buffer, ref size)) + SafeBuffer buffer = new(numElements: size); + if (QueryFullProcessImageName(ProcessHandle.v, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, lpExeName: buffer.DangerousGetHandle(), ref size)) { - return buffer.ToString(); + buffer.ReadArray(0, array, 0, (int)size); + return new string(array); } - else if (bufferLength < size) + else if (buffer.ByteLength < size) { - using PWSTR newBuffer = Marshal.AllocHGlobal((IntPtr)size); + buffer.Reallocate((nuint)(size * Marshal.SizeOf())); if (QueryFullProcessImageName( ProcessHandle.v, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, - newBuffer, + buffer.DangerousGetHandle(), ref size)) { - return newBuffer.ToString(); // newBuffer.Value will not be null here + buffer.ReadArray(0, array, 0, (int)size); + return new string(array); } } // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) From a1f886f68965fbe8b521ea129dbc93f737d021b1 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 21:48:49 -0700 Subject: [PATCH 259/306] refactor: add const error messages to ProcessHandle --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 4bcac07..c05bc77 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -144,7 +144,7 @@ public ProcessInfo(Process process) { if (processHandle is (null, null)) { - const string exMsg = "Unable to open handle; "; + const string errUnableMsg = "Unable to open handle; "; // We can't lookup the ProcessProtection without opening a process handle to check the process protection. //PROCESS_ACCESS_RIGHTS access = ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected ? PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION : PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ; @@ -158,7 +158,7 @@ public ProcessInfo(Process process) catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) { // Before assuming anything, try without PROCESS_VM_READ. Without it, we don't need Debug privilege, but the PEB and all of its recursive members (e.g. Command Line) will be unavailable. - const string exAccessMsg = exMsg + " The requested permissions were denied."; + const string exAccessMsg = errUnableMsg + " The requested permissions were denied."; const string exPermsFirst = "\r\nFirst attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) + ", " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); try @@ -175,17 +175,17 @@ public ProcessInfo(Process process) } catch (Exception ex2) { - return processHandle = (null, new AggregateException(exMsg + " Permissions were denied and an unknown error occurred.", new Exception[] { ex, ex2 })); + return processHandle = (null, new AggregateException(errUnableMsg + " Permissions were denied and an unknown error occurred.", new Exception[] { ex, ex2 })); } } catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_PARAMETER) { - return processHandle = (null, new ArgumentException(exMsg + " A process with ID " + ProcessId + " could not be found. The process may have exited.", ex)); + return processHandle = (null, new ArgumentException(errUnableMsg + " A process with ID " + ProcessId + " could not be found. The process may have exited.", ex)); } catch (Exception ex) { // unknown error - return processHandle = (null, new Exception(exMsg + " An unknown error occurred.", ex)); + return processHandle = (null, new Exception(errUnableMsg + " An unknown error occurred.", ex)); } } else From b1ca0d949b8b306a1b1cb81478f46e24f7ce9a7f Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 1 May 2023 21:50:46 -0700 Subject: [PATCH 260/306] refactor: compare fields to const values instead of default refactor: ensure all properties' code paths assign values to their uninitialized fields --- .../Domain/SafeFileHandleEx.cs | 92 ++++++++++--------- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 10 +- 2 files changed, 53 insertions(+), 49 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 44a7c23..404811b 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -62,7 +62,7 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( } } - public (bool? v, Exception? ex) IsFileHandle => isFileHandle == default + public (bool? v, Exception? ex) IsFileHandle => isFileHandle is (null, null) ? HandleObjectType.v == "File" ? (isFileHandle = (true, null)) : (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex))) @@ -127,11 +127,11 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { const string unableErr = "Unable to query FileHandleType; "; if (ProcessInfo.ProcessProtection.ex is not null) - return (null, new NullReferenceException(unableErr + "Failed to query the process's protection level.")); + return fileHandleType = (null, new NullReferenceException(unableErr + "Failed to query the process's protection level.")); if (ProcessInfo.ProcessProtection.ex is not null) - return (null, new UnauthorizedAccessException(unableErr + "The process's protection prohibits this operation.")); + return fileHandleType = (null, new UnauthorizedAccessException(unableErr + "The process's protection prohibits this operation.")); if (IsFileHandle.v is not true) - return (null, new InvalidOperationException(unableErr + "This operation is only valid on File handles.")); + return fileHandleType = (null, new InvalidOperationException(unableErr + "This operation is only valid on File handles.")); FileType type = (FileType)GetFileType(handle); Win32Exception err = new(); @@ -150,27 +150,27 @@ public unsafe (string? v, Exception? ex) FileNameInfo { get { - if (fileNameInfo == default) + if (fileNameInfo is (null, null)) { - const string unableErr = "Unable to query FileNameInfo; "; + const string unableErrMsg = "Unable to query " + nameof(FileNameInfo) + "; "; if (ProcessInfo.ProcessProtection.ex is not null) - return fileNameInfo = (null, new NullReferenceException(unableErr + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex)); + return fileNameInfo = (null, new NullReferenceException(unableErrMsg + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex)); if (ProcessInfo.ProcessProtection.v?.Type is not PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone) - return fileNameInfo = (null, new UnauthorizedAccessException(unableErr + "The process's protection prohibits querying a file handle's FILE_NAME_INFO.")); + return fileNameInfo = (null, new UnauthorizedAccessException(unableErrMsg + "The process's protection prohibits querying a file handle's FILE_NAME_INFO.")); if (FileHandleType.v is not FileType.Disk) - return (null, new InvalidOperationException(unableErr + "FileNameInfo can only be queried for disk-type file handles.")); + return fileNameInfo = (null, new InvalidOperationException(unableErrMsg + "FileNameInfo can only be queried for disk-type file handles.")); /* Get fni.FileNameLength */ FILE_NAME_INFO fni = default; int fniSize = Marshal.SizeOf(fni); int bufferLength = default; - + //TODO: remove task using CancellationTokenSource cancellationTokenSource = new(50); Task taskGetInfo = new(() => { - FILE_NAME_INFO tmp = default; - _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &tmp, (uint)Marshal.SizeOf(fni)); - return tmp; + FILE_NAME_INFO fni = default; + _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &fni, (uint)Marshal.SizeOf(fni)); + return fni; }, cancellationTokenSource.Token); const int taskTimedOut = -1; @@ -178,7 +178,7 @@ public unsafe (string? v, Exception? ex) FileNameInfo { if (Task.WaitAny(new Task[] { taskGetInfo }, 50) is taskTimedOut) { - return (null, new TimeoutException("GetFileInformationByHandleEx did not respond within 50ms.")); + return fileNameInfo = (null, new TimeoutException("GetFileInformationByHandleEx did not respond within 50ms.")); } else { @@ -190,7 +190,7 @@ public unsafe (string? v, Exception? ex) FileNameInfo foreach (Exception e in ae.InnerExceptions) { if (e is TaskCanceledException) - return (null, e); + return fileNameInfo = (null, e); } } @@ -212,7 +212,7 @@ public unsafe (string? v, Exception? ex) FileNameInfo } else { - return (null, new Exception("Failed to query FileNameInfo; GetFileInformationByHandleEx encountered an error.", new Win32Exception())); + return fileNameInfo = (null, new Exception("Failed to query FileNameInfo; GetFileInformationByHandleEx encountered an error.", new Win32Exception())); } } else @@ -231,7 +231,7 @@ public unsafe (string? v, Exception? ex) FileFullPath { get { - if (fileFullPath == default) + if (fileFullPath is (null, null)) { try { @@ -254,41 +254,43 @@ public unsafe (string? v, Exception? ex) FileFullPath const uint LengthIndicatesError = 0; // Try without duplicating. If it fails, try duplicating the handle. - var sw = new Stopwatch(); - sw.Start(); + Stopwatch sw = Stopwatch.StartNew(); try { GETFINALPATHNAMEBYHANDLE_FLAGS flags = IsFilePathRemote.v is true ? GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_OPENED : GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED; Win32ErrorCode errorCode = Win32ErrorCode.ERROR_SUCCESS; length = GetFinalPathNameByHandle(handle, buffer, bufLength, flags); - if (length is not LengthIndicatesError && length <= bufLength) + if (length is not LengthIndicatesError) { - return fileFullPath = (buffer.ToString(), null); - } - else if (length > bufLength) - { - using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - if ((length = GetFinalPathNameByHandle(handle, newBuffer, length, flags)) is not LengthIndicatesError) - return fileFullPath = (newBuffer.ToString(), null); + if (length <= bufLength) + { + return fileFullPath = (buffer.ToString(), null); + } + else if (length > bufLength) + { + using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); + if ((length = GetFinalPathNameByHandle(handle, newBuffer, length, flags)) is not LengthIndicatesError) + return fileFullPath = (newBuffer.ToString(), null); + } } else { errorCode = (Win32ErrorCode)Marshal.GetLastPInvokeError(); - } - - Trace.TraceError(errorCode.GetMessage()); - return fileFullPath = (null, errorCode switch - { - // Removable storage, deleted item, network shares, et cetera - Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException(errFailMsg + $"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(errorCode)), - // unlikely, but possible if system has little free memory - Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException(errFailMsg + "Insufficient memory to complete the operation.", new Win32Exception(errorCode)), - // possible only if FILE_NAME_NORMALIZED (0) is invalid - Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(errorCode)), - _ => new Exception($"An undocumented error ({errorCode}) was returned when querying a file handle for its path.", new Win32Exception(errorCode)) - }); + Trace.TraceError(errorCode.GetMessage()); + + return fileFullPath = (null, errorCode switch + { + // Removable storage, deleted item, network shares, et cetera + Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException(errFailMsg + $"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(errorCode)), + // unlikely, but possible if system has little free memory + Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException(errFailMsg + "Insufficient memory to complete the operation.", new Win32Exception(errorCode)), + // possible only if FILE_NAME_NORMALIZED (0) is invalid + Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(errorCode)), + _ => new Exception($"An undocumented error ({errorCode}) was returned when querying a file handle for its path.", new Win32Exception(errorCode)) + }); + } } catch (Exception ex) { @@ -319,7 +321,7 @@ public unsafe (string? v, Exception? ex) FileFullPath // buffer was too small. Reallocate buffer with size matched 'length' and try again using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); bufLength = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); - return (newBuffer.ToString(), null); + return fileFullPath = (newBuffer.ToString(), null); } } else @@ -356,7 +358,7 @@ public unsafe (string? v, Exception? ex) FileFullPath { get { - if (fileName == default) + if (fileName is (null, null)) { if (FileFullPath.v is not null) { @@ -382,7 +384,7 @@ public unsafe (string? v, Exception? ex) FileFullPath { get { - if (isDirectory == default) + if (isDirectory is (null, null)) { if (FileFullPath != default && FileFullPath.v != null) // The comparison *should* cause FileFullPath to initialize. { @@ -392,11 +394,11 @@ public unsafe (string? v, Exception? ex) FileFullPath } catch (Exception ex) { - return (null, ex); + return isDirectory = (null, ex); } } - return (null, new InvalidOperationException("Unable to query IsDirectory; This operation requires FileFullPath.")); + return isDirectory = (null, new InvalidOperationException("Unable to query IsDirectory; This operation requires FileFullPath.")); } else { diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 58ca106..a5385ec 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -55,7 +55,7 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { get { - if (handleObjectType == default) + if (handleObjectType is (null, null)) { var (v, ex) = ProcessInfo.ProcessProtection; if (v is null) @@ -124,13 +124,15 @@ public unsafe (string? v, Exception? ex) ObjectName { if (objectName == default) { + const string errUnableMsg = "Unable to query " + nameof(ObjectName) + "; "; + const string errFailedMsg = "Failed to query " + nameof(ObjectName) + "; "; var (v, ex) = ProcessInfo.ProcessProtection; // I'm assuming process protection prohibits access. I've not tested it. // This information is not queryable in SystemInformer when a process has Full protection. if (v is null) - return objectName = (null, new UnauthorizedAccessException("Unable to query ObjectName; Failed to query process's protection level.", ex)); + return objectName = (null, new UnauthorizedAccessException(errUnableMsg + "Failed to query process's protection level.", ex)); else if (v.Value.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) - return objectName = (null, new UnauthorizedAccessException("Unable to query ObjectName; The process's protection type prohibits access.")); + return objectName = (null, new UnauthorizedAccessException(errUnableMsg + "The process's protection type prohibits access.")); uint bufferLength = 1024u; using SafeBuffer buffer = new(numBytes: bufferLength); @@ -148,7 +150,7 @@ public unsafe (string? v, Exception? ex) ObjectName OBJECT_NAME_INFORMATION oni = buffer.Read(0); if (oni.Name.Buffer.Value == null) - return (null, new NullReferenceException("Bad data was copied to the buffer. The string pointer is null.")); + return objectName = (null, new NullReferenceException(errFailedMsg + "Bad data was copied to the buffer. The string pointer is null.")); return status.IsSuccessful ? objectName = (oni.NameAsString, null) From 5dd5b916c5de01bf1f0596d3f4915f1ecf8d9abf Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Tue, 2 May 2023 17:14:03 -0700 Subject: [PATCH 261/306] refactor: move UIntPtr32 to new file --- deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs | 11 ----------- deadlock-dotnet-sdk/Windows.Win32/UIntPtr32_T.cs | 13 +++++++++++++ 2 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 deadlock-dotnet-sdk/Windows.Win32/UIntPtr32_T.cs diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs index 986b4c2..c6f58f5 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32.cs @@ -12,14 +12,3 @@ public struct UIntPtr32 public unsafe static explicit operator void*(UIntPtr32 v) => (void*)v.Value; } - -public struct UIntPtr32 where T : unmanaged -{ - public uint Value; - - public static implicit operator UIntPtr32(uint v) => new() { Value = v }; - public static implicit operator uint(UIntPtr32 v) => v.Value; - - public static explicit operator UIntPtr32(UIntPtr32 v) => v.Value; - public unsafe static explicit operator T*(UIntPtr32 v) => (T*)v.Value; -} diff --git a/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32_T.cs b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32_T.cs new file mode 100644 index 0000000..74f9073 --- /dev/null +++ b/deadlock-dotnet-sdk/Windows.Win32/UIntPtr32_T.cs @@ -0,0 +1,13 @@ +namespace Windows.Win32; + +/// +public struct UIntPtr32 where T : unmanaged +{ + public uint Value; + + public static implicit operator UIntPtr32(uint v) => new() { Value = v }; + public static implicit operator uint(UIntPtr32 v) => v.Value; + + public static explicit operator UIntPtr32(UIntPtr32 v) => v.Value; + public static unsafe explicit operator T*(UIntPtr32 v) => (T*)v.Value; +} From 5273f062a74c9ad0bbad5a7ee78c3b2a8e478a04 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 4 May 2023 13:21:16 -0700 Subject: [PATCH 262/306] refactor: sort SafeFileHandleEx properties lexicographically --- .../Domain/SafeFileHandleEx.cs | 249 +++++++++--------- 1 file changed, 129 insertions(+), 120 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 404811b..7839fc6 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -23,13 +23,13 @@ namespace deadlock_dotnet_sdk.Domain; /// public class SafeFileHandleEx : SafeHandleEx { + private (bool? v, Exception? ex) isDirectory; private (bool? v, Exception? ex) isFileHandle; private (bool? v, Exception? ex) isFilePathRemote; - private (FileType? v, Exception? ex) fileHandleType; - private (string? v, Exception? ex) fileNameInfo; private (string? v, Exception? ex) fileFullPath; + private (FileType? v, Exception? ex) fileHandleType; private (string? v, Exception? ex) fileName; - private (bool? v, Exception? ex) isDirectory; + private (string? v, Exception? ex) fileNameInfo; // TODO: there's gotta be a better way to cast a base class to an implementing class internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) @@ -62,6 +62,35 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( } } + #region Properties + + public (bool? v, Exception? ex) IsDirectory + { + get + { + if (isDirectory is (null, null)) + { + if (FileFullPath != default && FileFullPath.v != null) // The comparison *should* cause FileFullPath to initialize. + { + try + { + return isDirectory = ((File.GetAttributes(FileFullPath.v) & FileAttributes.Directory) == FileAttributes.Directory, null); + } + catch (Exception ex) + { + return isDirectory = (null, ex); + } + } + + return isDirectory = (null, new InvalidOperationException("Unable to query IsDirectory; This operation requires FileFullPath.")); + } + else + { + return isDirectory; + } + } + } + public (bool? v, Exception? ex) IsFileHandle => isFileHandle is (null, null) ? HandleObjectType.v == "File" ? (isFileHandle = (true, null)) @@ -114,114 +143,6 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( } } - /// - /// If the handle object's Type is "File", the type of the File object
- /// -OR-
- /// An exception if the P/Invoke operation failed or the object's Type is not "File". - ///
- public (FileType? v, Exception? ex) FileHandleType - { - get - { - if (fileHandleType is (null, null)) - { - const string unableErr = "Unable to query FileHandleType; "; - if (ProcessInfo.ProcessProtection.ex is not null) - return fileHandleType = (null, new NullReferenceException(unableErr + "Failed to query the process's protection level.")); - if (ProcessInfo.ProcessProtection.ex is not null) - return fileHandleType = (null, new UnauthorizedAccessException(unableErr + "The process's protection prohibits this operation.")); - if (IsFileHandle.v is not true) - return fileHandleType = (null, new InvalidOperationException(unableErr + "This operation is only valid on File handles.")); - - FileType type = (FileType)GetFileType(handle); - Win32Exception err = new(); - return err.ErrorCode is 0 /* success */ - ? fileHandleType = (type, null) - : fileHandleType = (null, err); - } - else - { - return fileHandleType; - } - } - } - - public unsafe (string? v, Exception? ex) FileNameInfo - { - get - { - if (fileNameInfo is (null, null)) - { - const string unableErrMsg = "Unable to query " + nameof(FileNameInfo) + "; "; - if (ProcessInfo.ProcessProtection.ex is not null) - return fileNameInfo = (null, new NullReferenceException(unableErrMsg + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex)); - if (ProcessInfo.ProcessProtection.v?.Type is not PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone) - return fileNameInfo = (null, new UnauthorizedAccessException(unableErrMsg + "The process's protection prohibits querying a file handle's FILE_NAME_INFO.")); - if (FileHandleType.v is not FileType.Disk) - return fileNameInfo = (null, new InvalidOperationException(unableErrMsg + "FileNameInfo can only be queried for disk-type file handles.")); - - /* Get fni.FileNameLength */ - FILE_NAME_INFO fni = default; - int fniSize = Marshal.SizeOf(fni); - int bufferLength = default; - //TODO: remove task - using CancellationTokenSource cancellationTokenSource = new(50); - Task taskGetInfo = new(() => - { - FILE_NAME_INFO fni = default; - _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &fni, (uint)Marshal.SizeOf(fni)); - return fni; - }, cancellationTokenSource.Token); - - const int taskTimedOut = -1; - try - { - if (Task.WaitAny(new Task[] { taskGetInfo }, 50) is taskTimedOut) - { - return fileNameInfo = (null, new TimeoutException("GetFileInformationByHandleEx did not respond within 50ms.")); - } - else - { - bufferLength = (int)(taskGetInfo.Result.FileNameLength + fniSize); - } - } - catch (AggregateException ae) - { - foreach (Exception e in ae.InnerExceptions) - { - if (e is TaskCanceledException) - return fileNameInfo = (null, e); - } - } - - /* Get FileNameInfo */ - FILE_NAME_INFO* buffer = (FILE_NAME_INFO*)Marshal.AllocHGlobal(bufferLength); - using SafeBuffer safeBuffer = new(numBytes: (nuint)bufferLength); - - if (GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, buffer, (uint)bufferLength)) - { - UNICODE_STRING str = new() - { - Buffer = new PWSTR((char*)safeBuffer.DangerousGetHandle()), - Length = (ushort)fni.FileNameLength, - MaximumLength = (ushort)bufferLength - }; - - /* The string conversion copies the data to a new string in the managed heap before freeing safeBuffer and leaving this context. */ - return fileNameInfo = ((string)str, null); - } - else - { - return fileNameInfo = (null, new Exception("Failed to query FileNameInfo; GetFileInformationByHandleEx encountered an error.", new Win32Exception())); - } - } - else - { - return fileNameInfo; - } - } - } - /// /// Try to get the absolute path of the file. Traverses filesystem links (e.g. symbolic, junction) to get the 'real' path. /// @@ -353,6 +274,43 @@ public unsafe (string? v, Exception? ex) FileFullPath } } + /// + /// If the handle object's Type is "File", the type of the File object
+ /// -OR-
+ /// An exception if the P/Invoke operation failed or the object's Type is not "File". + ///
+ public (FileType? v, Exception? ex) FileHandleType + { + get + { + if (fileHandleType is (null, null)) + { + const string unableErr = "Unable to query FileHandleType; "; + if (ProcessInfo.ProcessProtection.ex is not null) + return fileHandleType = (null, new NullReferenceException(unableErr + "Failed to query the process's protection level.")); + if (ProcessInfo.ProcessProtection.ex is not null) + return fileHandleType = (null, new UnauthorizedAccessException(unableErr + "The process's protection prohibits this operation.")); + if (IsFileHandle.v is not true) + return fileHandleType = (null, new InvalidOperationException(unableErr + "This operation is only valid on File handles.")); + + FileType type = (FileType)GetFileType(handle); + if (type is FileType.Unknown) + { + Win32Exception err = new(); + return err.NativeErrorCode is Win32ErrorCode.ERROR_SUCCESS ? (fileHandleType = (null, err)) : (fileHandleType = (type, null)); + } + else + { + return fileHandleType = (type, null); + } + } + else + { + return fileHandleType; + } + } + } + // TODO: leverage GetFileInformationByHandleEx public (string? v, Exception? ex) FileName { @@ -380,33 +338,84 @@ public unsafe (string? v, Exception? ex) FileFullPath } } - public (bool? v, Exception? ex) IsDirectory + public unsafe (string? v, Exception? ex) FileNameInfo { get { - if (isDirectory is (null, null)) + if (fileNameInfo is (null, null)) { - if (FileFullPath != default && FileFullPath.v != null) // The comparison *should* cause FileFullPath to initialize. + const string unableErrMsg = "Unable to query " + nameof(FileNameInfo) + "; "; + if (ProcessInfo.ProcessProtection.ex is not null) + return fileNameInfo = (null, new NullReferenceException(unableErrMsg + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex)); + if (ProcessInfo.ProcessProtection.v?.Type is not PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone) + return fileNameInfo = (null, new UnauthorizedAccessException(unableErrMsg + "The process's protection prohibits querying a file handle's FILE_NAME_INFO.")); + if (FileHandleType.v is not FileType.Disk) + return fileNameInfo = (null, new InvalidOperationException(unableErrMsg + "FileNameInfo can only be queried for disk-type file handles.")); + + /* Get fni.FileNameLength */ + FILE_NAME_INFO fni = default; + int fniSize = Marshal.SizeOf(fni); + int bufferLength = default; + //TODO: remove task + using CancellationTokenSource cancellationTokenSource = new(50); + Task taskGetInfo = new(() => { - try + FILE_NAME_INFO fni = default; + _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &fni, (uint)Marshal.SizeOf(fni)); + return fni; + }, cancellationTokenSource.Token); + + const int taskTimedOut = -1; + try + { + if (Task.WaitAny(new Task[] { taskGetInfo }, 50) is taskTimedOut) { - return isDirectory = ((File.GetAttributes(FileFullPath.v) & FileAttributes.Directory) == FileAttributes.Directory, null); + return fileNameInfo = (null, new TimeoutException("GetFileInformationByHandleEx did not respond within 50ms.")); } - catch (Exception ex) + else { - return isDirectory = (null, ex); + bufferLength = (int)(taskGetInfo.Result.FileNameLength + fniSize); + } + } + catch (AggregateException ae) + { + foreach (Exception e in ae.InnerExceptions) + { + if (e is TaskCanceledException) + return fileNameInfo = (null, e); } } - return isDirectory = (null, new InvalidOperationException("Unable to query IsDirectory; This operation requires FileFullPath.")); + /* Get FileNameInfo */ + FILE_NAME_INFO* buffer = (FILE_NAME_INFO*)Marshal.AllocHGlobal(bufferLength); + using SafeBuffer safeBuffer = new(numBytes: (nuint)bufferLength); + + if (GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, buffer, (uint)bufferLength)) + { + UNICODE_STRING str = new() + { + Buffer = new PWSTR((char*)safeBuffer.DangerousGetHandle()), + Length = (ushort)fni.FileNameLength, + MaximumLength = (ushort)bufferLength + }; + + /* The string conversion copies the data to a new string in the managed heap before freeing safeBuffer and leaving this context. */ + return fileNameInfo = ((string)str, null); + } + else + { + return fileNameInfo = (null, new Exception("Failed to query FileNameInfo; GetFileInformationByHandleEx encountered an error.", new Win32Exception())); + } } else { - return isDirectory; + return fileNameInfo; } } } + #endregion Properties + public enum FileType : uint { /// Either the type of the specified file is unknown, or the function failed. From 1c54e028042bc5fd04e67903f041cefb22296747 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 13:19:42 -0700 Subject: [PATCH 263/306] perf: remove superfluous GetFileInformationByHandleEx task --- .../Domain/SafeFileHandleEx.cs | 38 +++---------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 7839fc6..339b96b 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -352,41 +352,13 @@ public unsafe (string? v, Exception? ex) FileNameInfo if (FileHandleType.v is not FileType.Disk) return fileNameInfo = (null, new InvalidOperationException(unableErrMsg + "FileNameInfo can only be queried for disk-type file handles.")); - /* Get fni.FileNameLength */ + /** Get fni.FileNameLength */ FILE_NAME_INFO fni = default; - int fniSize = Marshal.SizeOf(fni); - int bufferLength = default; - //TODO: remove task - using CancellationTokenSource cancellationTokenSource = new(50); - Task taskGetInfo = new(() => - { - FILE_NAME_INFO fni = default; - _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &fni, (uint)Marshal.SizeOf(fni)); - return fni; - }, cancellationTokenSource.Token); - - const int taskTimedOut = -1; - try - { - if (Task.WaitAny(new Task[] { taskGetInfo }, 50) is taskTimedOut) - { - return fileNameInfo = (null, new TimeoutException("GetFileInformationByHandleEx did not respond within 50ms.")); - } - else - { - bufferLength = (int)(taskGetInfo.Result.FileNameLength + fniSize); - } - } - catch (AggregateException ae) - { - foreach (Exception e in ae.InnerExceptions) - { - if (e is TaskCanceledException) - return fileNameInfo = (null, e); - } - } + _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &fni, (uint)Marshal.SizeOf(fni)); - /* Get FileNameInfo */ + /** Get FileNameInfo */ + int fniSize = Marshal.SizeOf(fni); + int bufferLength = (int)(fni.FileNameLength + fniSize); FILE_NAME_INFO* buffer = (FILE_NAME_INFO*)Marshal.AllocHGlobal(bufferLength); using SafeBuffer safeBuffer = new(numBytes: (nuint)bufferLength); From 771e379eafcc07ad05161e387de113866137296f Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 13:22:23 -0700 Subject: [PATCH 264/306] fix: replace GetFileInformationByHandleEx buffer param with safeBuffer --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 339b96b..994471b 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -359,10 +359,9 @@ public unsafe (string? v, Exception? ex) FileNameInfo /** Get FileNameInfo */ int fniSize = Marshal.SizeOf(fni); int bufferLength = (int)(fni.FileNameLength + fniSize); - FILE_NAME_INFO* buffer = (FILE_NAME_INFO*)Marshal.AllocHGlobal(bufferLength); using SafeBuffer safeBuffer = new(numBytes: (nuint)bufferLength); - if (GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, buffer, (uint)bufferLength)) + if (GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, (FILE_NAME_INFO*)safeBuffer.DangerousGetHandle(), (uint)bufferLength)) { UNICODE_STRING str = new() { From f003c8b4abd113cb4485f40381b4bb049da42c57 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 13:26:44 -0700 Subject: [PATCH 265/306] refactor: inline temporary variable --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 994471b..51b39c3 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -357,8 +357,7 @@ public unsafe (string? v, Exception? ex) FileNameInfo _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &fni, (uint)Marshal.SizeOf(fni)); /** Get FileNameInfo */ - int fniSize = Marshal.SizeOf(fni); - int bufferLength = (int)(fni.FileNameLength + fniSize); + int bufferLength = (int)(fni.FileNameLength + Marshal.SizeOf(fni)); using SafeBuffer safeBuffer = new(numBytes: (nuint)bufferLength); if (GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, (FILE_NAME_INFO*)safeBuffer.DangerousGetHandle(), (uint)bufferLength)) From 0e6c43b109314660e857217b337e191454e08edc Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 13:41:32 -0700 Subject: [PATCH 266/306] fix: assign, return type when GetFileType succeeds refactor: simplify FileHandleType code branching refactor: add errFailedMsg, errUnableMsg refactor: rename unableErrMsg, unableErr, errFailMsg refactor: prepend Win32Exception message with errFailedMsg --- .../Domain/SafeFileHandleEx.cs | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 51b39c3..8822779 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -157,7 +157,7 @@ public unsafe (string? v, Exception? ex) FileFullPath try { const string errUnableMsg = "Unable to query " + nameof(FileFullPath) + "; "; - const string errFailMsg = "Failed to query " + nameof(FileFullPath) + "; "; + const string errFailedMsg = "Failed to query " + nameof(FileFullPath) + "; "; if (ProcessInfo.ProcessProtection.v is null) return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "Failed to query the process's protection.", ProcessInfo.ProcessProtection.ex)); if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) @@ -204,12 +204,12 @@ public unsafe (string? v, Exception? ex) FileFullPath return fileFullPath = (null, errorCode switch { // Removable storage, deleted item, network shares, et cetera - Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException(errFailMsg + $"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(errorCode)), + Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException(errFailedMsg + $"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(errorCode)), // unlikely, but possible if system has little free memory - Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException(errFailMsg + "Insufficient memory to complete the operation.", new Win32Exception(errorCode)), + Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException(errFailedMsg + "Insufficient memory to complete the operation.", new Win32Exception(errorCode)), // possible only if FILE_NAME_NORMALIZED (0) is invalid - Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(errorCode)), - _ => new Exception($"An undocumented error ({errorCode}) was returned when querying a file handle for its path.", new Win32Exception(errorCode)) + Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException(errFailedMsg + "Invalid flags were specified for dwFlags.", new Win32Exception(errorCode)), + _ => new Exception($"{errFailedMsg}An undocumented error ({errorCode}) was returned when querying a file handle for its path.", new Win32Exception(errorCode)) }); } } @@ -285,24 +285,24 @@ public unsafe (string? v, Exception? ex) FileFullPath { if (fileHandleType is (null, null)) { - const string unableErr = "Unable to query FileHandleType; "; + const string errUnableMsg = "Unable to query " + nameof(FileHandleType) + "; "; + const string errFailedMsg = "Failed to query " + nameof(FileHandleType) + "; "; if (ProcessInfo.ProcessProtection.ex is not null) - return fileHandleType = (null, new NullReferenceException(unableErr + "Failed to query the process's protection level.")); + return fileHandleType = (null, new NullReferenceException(errUnableMsg + "Failed to query the process's protection level.")); if (ProcessInfo.ProcessProtection.ex is not null) - return fileHandleType = (null, new UnauthorizedAccessException(unableErr + "The process's protection prohibits this operation.")); + return fileHandleType = (null, new UnauthorizedAccessException(errUnableMsg + "The process's protection prohibits this operation.")); if (IsFileHandle.v is not true) - return fileHandleType = (null, new InvalidOperationException(unableErr + "This operation is only valid on File handles.")); + return fileHandleType = (null, new InvalidOperationException(errUnableMsg + "This operation is only valid on File handles.")); FileType type = (FileType)GetFileType(handle); if (type is FileType.Unknown) { - Win32Exception err = new(); - return err.NativeErrorCode is Win32ErrorCode.ERROR_SUCCESS ? (fileHandleType = (null, err)) : (fileHandleType = (type, null)); - } - else - { - return fileHandleType = (type, null); + Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError(); + if (err is not Win32ErrorCode.ERROR_SUCCESS) + return fileHandleType = (null, new Win32Exception(err, errFailedMsg + err.GetMessage())); } + + return fileHandleType = (type, null); } else { @@ -318,6 +318,7 @@ public unsafe (string? v, Exception? ex) FileFullPath { if (fileName is (null, null)) { + const string errUnableMsg = "Unable to query " + nameof(FileName) + "; "; if (FileFullPath.v is not null) { return fileName = (Path.GetFileName(FileFullPath.v), null); @@ -328,7 +329,7 @@ public unsafe (string? v, Exception? ex) FileFullPath } else { - return fileName = (null, new InvalidOperationException("Unable to query FileName; This operation requires FileFullPath.")); + return fileName = (null, new InvalidOperationException(errUnableMsg + "This operation requires FileFullPath or ObjectName.")); } } else @@ -344,13 +345,14 @@ public unsafe (string? v, Exception? ex) FileNameInfo { if (fileNameInfo is (null, null)) { - const string unableErrMsg = "Unable to query " + nameof(FileNameInfo) + "; "; + const string errUnableMsg = "Unable to query " + nameof(FileNameInfo) + "; "; + const string errFailedMsg = "Failed to query " + nameof(FileNameInfo) + "; "; if (ProcessInfo.ProcessProtection.ex is not null) - return fileNameInfo = (null, new NullReferenceException(unableErrMsg + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex)); + return fileNameInfo = (null, new NullReferenceException(errUnableMsg + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex)); if (ProcessInfo.ProcessProtection.v?.Type is not PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone) - return fileNameInfo = (null, new UnauthorizedAccessException(unableErrMsg + "The process's protection prohibits querying a file handle's FILE_NAME_INFO.")); + return fileNameInfo = (null, new UnauthorizedAccessException(errUnableMsg + "The process's protection prohibits querying a file handle's FILE_NAME_INFO.")); if (FileHandleType.v is not FileType.Disk) - return fileNameInfo = (null, new InvalidOperationException(unableErrMsg + "FileNameInfo can only be queried for disk-type file handles.")); + return fileNameInfo = (null, new InvalidOperationException(errUnableMsg + "FileNameInfo can only be queried for disk-type file handles.")); /** Get fni.FileNameLength */ FILE_NAME_INFO fni = default; @@ -374,7 +376,7 @@ public unsafe (string? v, Exception? ex) FileNameInfo } else { - return fileNameInfo = (null, new Exception("Failed to query FileNameInfo; GetFileInformationByHandleEx encountered an error.", new Win32Exception())); + return fileNameInfo = (null, new Exception(errFailedMsg + "GetFileInformationByHandleEx encountered an error.", new Win32Exception())); } } else From 0f298a9acaeee78dfc5ff5a64810b7f6816f374c Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 15:47:49 -0700 Subject: [PATCH 267/306] refactor: rewrite IsDirectory to use GetFileInformationByHandleEx --- .../Domain/SafeFileHandleEx.cs | 20 +++++++++---------- deadlock-dotnet-sdk/NativeMethods.txt | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 8822779..3e7e869 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -70,19 +70,19 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { if (isDirectory is (null, null)) { - if (FileFullPath != default && FileFullPath.v != null) // The comparison *should* cause FileFullPath to initialize. + const string errFailedMsg = "Failed to query " + nameof(IsDirectory) + "; "; + FILE_ATTRIBUTE_TAG_INFO attr = default; + bool success; + unsafe { - try - { - return isDirectory = ((File.GetAttributes(FileFullPath.v) & FileAttributes.Directory) == FileAttributes.Directory, null); - } - catch (Exception ex) - { - return isDirectory = (null, ex); - } + success = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileAttributeTagInfo, &attr, (uint)Marshal.SizeOf(attr)); } - return isDirectory = (null, new InvalidOperationException("Unable to query IsDirectory; This operation requires FileFullPath.")); + if (success) + return isDirectory = ((attr.FileAttributes & (uint)FileAttributes.Directory) != 0, null); + + Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError(); + return isDirectory = (null, new Win32Exception(err, errFailedMsg + err.GetMessage())); } else { diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index 95f47b6..e1bc4c9 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -84,3 +84,4 @@ SE_DEBUG_NAME //PsProtectedTypeProtected // PInvoke001: Method, type or constant "PsProtectedTypeProtected" not found FILE_NAME_INFO FILE_REMOTE_PROTOCOL_INFO +FILE_ATTRIBUTE_TAG_INFO From 756d0b71548df6dad77e9009f13b5d7837fdeb63 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 15:46:41 -0700 Subject: [PATCH 268/306] feat: add HandleAttributes property feat: implement sorting by HandleAttributes, ObjectRealName (FileFullPath, FileNameInfo) docs: add notes regarding FileShareAccess requirements refactor: remove redundancy "SortByProperty.HandleName" FileShareAccess can't be queried without a kernel mode driver. --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 19 ++++++++----------- .../Domain/SafeFileHandleEx.cs | 10 +++++++++- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 26fe41c..9f2784e 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -25,7 +25,6 @@ public enum SortByProperty /// NOT IMPLEMENTED FileShareAccess, // oh, this is important! Note: System Informer seems to crash when evaluating this property // TODO: implement FileShareAccess property HandleAttributes, - HandleName, HandleSubType, HandleType, HandleValue, @@ -52,18 +51,17 @@ public List Lockers return lockers .OrderBy(h => { - switch (SortByPrimary) + switch (SortByPrimary) // returns byte[] { - case SortByProperty.FileShareAccess: throw new NotImplementedException("FileShareAccess is not yet implemented!"); - case SortByProperty.HandleAttributes: throw new NotImplementedException("HandleAttributes is not yet implemented!");//return h.HandleAttributes; // TODO: h.HandleAttributes - case SortByProperty.HandleName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); + //case SortByProperty.FileShareAccess: return h.FileShareAccess; // not possible without a kernel mode driver; see IoCheckShareAccess + case SortByProperty.HandleAttributes: return Encoding.ASCII.GetBytes(h.HandleAttributes.ToString()); case SortByProperty.HandleSubType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.HandleObjectType.v?.ToString() ?? string.Empty); case SortByProperty.HandleValue: return Encoding.ASCII.GetBytes(h.HandleValue.ToString()); case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); case SortByProperty.ObjectOriginalName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); - case SortByProperty.ObjectRealName: throw new NotImplementedException("ObjectTrueName is not yet implemented!"); + case SortByProperty.ObjectRealName: return Encoding.ASCII.GetBytes(h.FileFullPath.v ?? h.FileNameInfo.v ?? string.Empty); // TODO: implement Registry key parsing case SortByProperty.ObjectAddress: return BitConverter.GetBytes((ulong)h.ObjectAddress); case SortByProperty.ProcessId: return BitConverter.GetBytes(h.ProcessId); default: goto case SortByProperty.ProcessId; @@ -71,18 +69,17 @@ public List Lockers }) .ThenBy(h => { - switch (SortBySecondary) + switch (SortBySecondary) // returns byte[] { - case SortByProperty.FileShareAccess: throw new NotImplementedException("FileShareAccess is not yet implemented!"); - case SortByProperty.HandleAttributes: throw new NotImplementedException("HandleAttributes is not yet implemented!");//return h.HandleAttributes; // TODO: h.HandleAttributes - case SortByProperty.HandleName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); + //case SortByProperty.FileShareAccess: return h.FileShareAccess; // not possible without a kernel mode driver; see IoCheckShareAccess + case SortByProperty.HandleAttributes: return Encoding.ASCII.GetBytes(h.HandleAttributes.ToString()); case SortByProperty.HandleSubType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.HandleObjectType.v?.ToString() ?? string.Empty); case SortByProperty.HandleValue: return Encoding.ASCII.GetBytes(h.HandleValue.ToString()); case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); case SortByProperty.ObjectOriginalName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); - case SortByProperty.ObjectRealName: throw new NotImplementedException("ObjectTrueName is not yet implemented!"); + case SortByProperty.ObjectRealName: return Encoding.ASCII.GetBytes(h.FileFullPath.v ?? h.FileNameInfo.v ?? string.Empty); // TODO: implement Registry key parsing case SortByProperty.ObjectAddress: return BitConverter.GetBytes((ulong)h.ObjectAddress); case SortByProperty.ProcessId: return BitConverter.GetBytes(h.ProcessId); default: goto case SortByProperty.ProcessId; diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 3e7e869..8e91ee6 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -30,8 +30,9 @@ public class SafeFileHandleEx : SafeHandleEx private (FileType? v, Exception? ex) fileHandleType; private (string? v, Exception? ex) fileName; private (string? v, Exception? ex) fileNameInfo; + //private (FileShare? v, Exception? ex) fileShareAccess; // see property - // TODO: there's gotta be a better way to cast a base class to an implementing class + // TODO: there's gotta be a better way to cast a base class to an inheriting class internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) { } @@ -386,6 +387,13 @@ public unsafe (string? v, Exception? ex) FileNameInfo } } + public Kernel32.HandleFlags HandleAttributes => SysHandleEx.HandleAttributes; + + /// + /// Inaccessible by user code; Only available to kernel-mode drivers; + /// + //public unsafe (FileShare? v, Exception? ex) FileShareAccess { get { if (fileShareAccess is (null, null)) { _ } else { return fileShareAccess; } } } + #endregion Properties public enum FileType : uint From 95d9eb01a24216db43fa83d752c819cc18b6c79f Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 15:49:29 -0700 Subject: [PATCH 269/306] refactor: invert if statement; remove else --- .../Domain/SafeFileHandleEx.cs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 8e91ee6..1d4b3f6 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -363,22 +363,18 @@ public unsafe (string? v, Exception? ex) FileNameInfo int bufferLength = (int)(fni.FileNameLength + Marshal.SizeOf(fni)); using SafeBuffer safeBuffer = new(numBytes: (nuint)bufferLength); - if (GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, (FILE_NAME_INFO*)safeBuffer.DangerousGetHandle(), (uint)bufferLength)) - { - UNICODE_STRING str = new() - { - Buffer = new PWSTR((char*)safeBuffer.DangerousGetHandle()), - Length = (ushort)fni.FileNameLength, - MaximumLength = (ushort)bufferLength - }; + if (!GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, (FILE_NAME_INFO*)safeBuffer.DangerousGetHandle(), (uint)bufferLength)) + return fileNameInfo = (null, new Exception(errFailedMsg + "GetFileInformationByHandleEx encountered an error.", new Win32Exception())); - /* The string conversion copies the data to a new string in the managed heap before freeing safeBuffer and leaving this context. */ - return fileNameInfo = ((string)str, null); - } - else + UNICODE_STRING str = new() { - return fileNameInfo = (null, new Exception(errFailedMsg + "GetFileInformationByHandleEx encountered an error.", new Win32Exception())); - } + Buffer = new PWSTR((char*)safeBuffer.DangerousGetHandle()), + Length = (ushort)fni.FileNameLength, + MaximumLength = (ushort)bufferLength + }; + + /* The string conversion copies the data to a new string in the managed heap before freeing safeBuffer and leaving this context. */ + return fileNameInfo = ((string)str, null); } else { From beeb6b9356e4bfa52b1aa2f0c9cf749ff75d5777 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 23:19:49 -0700 Subject: [PATCH 270/306] refactor: also check FileNameInfo for FileName processing refactor: return exception to FileName when a path's file/directory name cannot be obtained --- .../Domain/SafeFileHandleEx.cs | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 1d4b3f6..772ea52 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -312,7 +312,6 @@ public unsafe (string? v, Exception? ex) FileFullPath } } - // TODO: leverage GetFileInformationByHandleEx public (string? v, Exception? ex) FileName { get @@ -322,15 +321,37 @@ public unsafe (string? v, Exception? ex) FileFullPath const string errUnableMsg = "Unable to query " + nameof(FileName) + "; "; if (FileFullPath.v is not null) { - return fileName = (Path.GetFileName(FileFullPath.v), null); + getFileOrDirectoryName(FileFullPath.v); + return fileName; + } + else if (FileNameInfo.v is not null) + { + getFileOrDirectoryName(FileNameInfo.v); + return fileName; } else if (ObjectName.v is not null) { - return fileName = (Path.GetFileName(ObjectName.v), null); + getFileOrDirectoryName(ObjectName.v); + return fileName; } else { - return fileName = (null, new InvalidOperationException(errUnableMsg + "This operation requires FileFullPath or ObjectName.")); + return fileName = (null, new InvalidOperationException(errUnableMsg + "This operation requires FileFullPath, FileNameInfo, or ObjectName.")); + } + + void getFileOrDirectoryName(string path) + { + string? tmp = Path.GetFileName(path); + if (tmp.Length is 0) + { + fileName = (tmp = Path.GetDirectoryName(path)) is null + ? (null, new InvalidOperationException(errUnableMsg + $"'{path}' could not be processed for a file or directory name.")) + : (tmp, null); + } + else + { + fileName = (tmp, null); + } } } else From a056ce3b119493ee02ec9ac30262d42eb81a8910 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 23:21:49 -0700 Subject: [PATCH 271/306] refactor: use errFailedMessage for FileName failures --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 772ea52..c0dc1db 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -319,6 +319,7 @@ public unsafe (string? v, Exception? ex) FileFullPath if (fileName is (null, null)) { const string errUnableMsg = "Unable to query " + nameof(FileName) + "; "; + const string errFailedMsg = "Failed to query " + nameof(FileName) + "; "; if (FileFullPath.v is not null) { getFileOrDirectoryName(FileFullPath.v); @@ -345,7 +346,7 @@ void getFileOrDirectoryName(string path) if (tmp.Length is 0) { fileName = (tmp = Path.GetDirectoryName(path)) is null - ? (null, new InvalidOperationException(errUnableMsg + $"'{path}' could not be processed for a file or directory name.")) + ? (null, new InvalidOperationException(errFailedMsg + $"'{path}' could not be processed for a file or directory name.")) : (tmp, null); } else From 57dc820ef75106dd91936176ceb7facd4d751bb8 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 23:23:34 -0700 Subject: [PATCH 272/306] feat: add ProcessId property to system handle ex struct; converts from UniqueProcessId refactor: make HandleValue display as both hexadecimal and decimal in debugger --- .../Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index af969c1..1240374 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -35,7 +35,9 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX public nuint Object { get; } /// ULONG_PTR, cast to HANDLE, int, or uint public nuint UniqueProcessId { get; } + public uint ProcessId => (uint)UniqueProcessId; /// ULONG_PTR, cast to HANDLE + [DebuggerDisplay("0x{Value} ({Value:D})")] public HANDLE HandleValue { get; } /// Get the HandleValue as a SafeObjectHandle. Closing this SafeObjectHandle does *not* close the source handle. public SafeObjectHandle GetSafeHandle() => new(HandleValue, false); From 28b576dba55002cd2c7468f37b08a4a552381c42 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Fri, 5 May 2023 23:29:11 -0700 Subject: [PATCH 273/306] feat: finish SortByProperty implementation refactor: comment out SortByProperty.FileShareAccess docs: update SortByProperty.ObjectRealName summary to reflect implementation docs: update SortByProperty.FileShareAccess comment to reflect requirements docs: remove SortByProperty TODO --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 9f2784e..64c3930 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -4,8 +4,7 @@ namespace deadlock_dotnet_sdk.Domain { //TODO: Add RefreshList(). This should clear Lockers and call FindLockingHandles again. - //TODO: If a handle is closed or invalid, remove if from Lockers. SafeHandle.IsClosed is unreliable—it only works on handles managed by the current process. - //TODO: feat: finalize OrderBy parameters + //TODO: If a handle is closed or invalid, remove it from Lockers. SafeHandle.IsClosed is unreliable—it only works on handles managed by the current process. //https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs public class FileLockerEx { @@ -23,7 +22,7 @@ public class FileLockerEx public enum SortByProperty { /// NOT IMPLEMENTED - FileShareAccess, // oh, this is important! Note: System Informer seems to crash when evaluating this property // TODO: implement FileShareAccess property + //FileShareAccess, // oh, this is important! Note: System Informer seems to crash when evaluating this property // not possible without a kernel mode driver; see IoCheckShareAccess HandleAttributes, HandleSubType, HandleType, @@ -33,7 +32,7 @@ public enum SortByProperty /// The string returned to the ObjectName property via NtQueryObject. ObjectOriginalName, /// - /// (NOT IMPLEMENTED) + /// (PARTIALLY IMPLEMENTED) /// Differs from ObjectName for types {File, (Registry) Key} /// /// TODO: get 'real' paths e.g. "\REGISTRY\MACHINE" -> "HKLM" From e38511c9612befcb208f7d4531071513db9b1585 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 15:06:19 -0700 Subject: [PATCH 274/306] feat: finish implementing new IsClosed property It is now used to discard a closed or invalid handle during the keep() function and SafeHandleEx ctors --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 1 - deadlock-dotnet-sdk/Domain/NativeMethods.cs | 3 ++ .../Domain/SafeFileHandleEx.cs | 50 ++++++++++++++++--- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 32 +++++++++--- 4 files changed, 70 insertions(+), 16 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 64c3930..4433099 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -4,7 +4,6 @@ namespace deadlock_dotnet_sdk.Domain { //TODO: Add RefreshList(). This should clear Lockers and call FindLockingHandles again. - //TODO: If a handle is closed or invalid, remove it from Lockers. SafeHandle.IsClosed is unreliable—it only works on handles managed by the current process. //https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs public class FileLockerEx { diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index f2b2843..e3c9b8e 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -175,6 +175,9 @@ bool keep(SafeFileHandleEx h) { bool keep = false; + if (h.IsClosed) + return false; + if (!string.IsNullOrEmpty(query)) { // only keep if FullFilePath contains query (with normalized directory separators) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index c0dc1db..da0d726 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -40,6 +40,12 @@ internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHand /// internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx) { + if (IsClosed) + { + ExceptionLog.Add(new NullReferenceException("This handle was closed before it was passed to this SafeFileHandleEx constructor.")); + return; + } + if (IsFileHandle.v is true) { try @@ -71,7 +77,10 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { if (isDirectory is (null, null)) { + const string errUnableMsg = "Unable to query " + nameof(IsDirectory) + "; "; const string errFailedMsg = "Failed to query " + nameof(IsDirectory) + "; "; + if (IsClosed) + return isDirectory = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); FILE_ATTRIBUTE_TAG_INFO attr = default; bool success; unsafe @@ -92,11 +101,25 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( } } - public (bool? v, Exception? ex) IsFileHandle => isFileHandle is (null, null) - ? HandleObjectType.v == "File" + public (bool? v, Exception? ex) IsFileHandle + { + get + { + if (isFileHandle is (null, null)) + { + const string errUnableMsg = "Unable to query " + nameof(IsFileHandle) + "; "; + if (IsClosed) + return isFileHandle = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); + return HandleObjectType.v == "File" ? (isFileHandle = (true, null)) - : (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex))) - : isFileHandle; + : (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex))); + } + else + { + return isFileHandle; + } + } + } /// /// TRUE if the file object's path is a network path i.e. SMB2 network share. FALSE if the file was opened via a local disk path. @@ -126,6 +149,11 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { if (isFilePathRemote is (null, null)) { + const string errUnableMsg = "Unable to query " + nameof(IsFilePathRemote) + "; "; + const string errFailedMsg = "Failed to query " + nameof(IsFilePathRemote) + "; "; + if (IsClosed) + return isFilePathRemote = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); + Win32ErrorCode err; FILE_REMOTE_PROTOCOL_INFO info; unsafe @@ -134,7 +162,7 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( ? (isFilePathRemote = (true, null)) : (err = (Win32ErrorCode)Marshal.GetLastPInvokeError()) is Win32ErrorCode.ERROR_INVALID_PARAMETER ? (isFilePathRemote = (false, null)) - : (isFilePathRemote = (null, new Win32Exception(err))); + : (isFilePathRemote = (null, new Win32Exception(err, errFailedMsg + err.GetMessage()))); } } else @@ -155,10 +183,12 @@ public unsafe (string? v, Exception? ex) FileFullPath { if (fileFullPath is (null, null)) { + const string errUnableMsg = "Unable to query " + nameof(FileFullPath) + "; "; + const string errFailedMsg = "Failed to query " + nameof(FileFullPath) + "; "; + if (IsClosed) + return fileFullPath = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); try { - const string errUnableMsg = "Unable to query " + nameof(FileFullPath) + "; "; - const string errFailedMsg = "Failed to query " + nameof(FileFullPath) + "; "; if (ProcessInfo.ProcessProtection.v is null) return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "Failed to query the process's protection.", ProcessInfo.ProcessProtection.ex)); if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) @@ -288,6 +318,8 @@ public unsafe (string? v, Exception? ex) FileFullPath { const string errUnableMsg = "Unable to query " + nameof(FileHandleType) + "; "; const string errFailedMsg = "Failed to query " + nameof(FileHandleType) + "; "; + if (IsClosed) + return fileHandleType = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); if (ProcessInfo.ProcessProtection.ex is not null) return fileHandleType = (null, new NullReferenceException(errUnableMsg + "Failed to query the process's protection level.")); if (ProcessInfo.ProcessProtection.ex is not null) @@ -320,6 +352,8 @@ public unsafe (string? v, Exception? ex) FileFullPath { const string errUnableMsg = "Unable to query " + nameof(FileName) + "; "; const string errFailedMsg = "Failed to query " + nameof(FileName) + "; "; + if (IsClosed) + return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); if (FileFullPath.v is not null) { getFileOrDirectoryName(FileFullPath.v); @@ -370,6 +404,8 @@ public unsafe (string? v, Exception? ex) FileNameInfo { const string errUnableMsg = "Unable to query " + nameof(FileNameInfo) + "; "; const string errFailedMsg = "Failed to query " + nameof(FileNameInfo) + "; "; + if (IsClosed) + return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); if (ProcessInfo.ProcessProtection.ex is not null) return fileNameInfo = (null, new NullReferenceException(errUnableMsg + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex)); if (ProcessInfo.ProcessProtection.v?.Type is not PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index a5385ec..2743791 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -14,7 +14,6 @@ namespace deadlock_dotnet_sdk.Domain; -//TODO: check if handle is closed. If true, FileLockerEx can remove this object from its locker list. See relevant TODO in FileLockerEx /// /// A SafeHandleZeroOrMinusOneIsInvalid wrapping a SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
/// Before querying for system handles, call @@ -24,7 +23,9 @@ namespace deadlock_dotnet_sdk.Domain; ///
public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { + protected const string errHandleClosedMsgSuffix = "The handle is closed."; protected (string? v, Exception? ex) handleObjectType; + private bool isClosed; protected (string? v, Exception? ex) objectName; private ProcessInfo? processInfo; @@ -57,10 +58,14 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { if (handleObjectType is (null, null)) { + const string errUnableMsg = "Unable to query " + nameof(HandleObjectType) + "; "; + const string errFailedMsg = "Failed to query " + nameof(HandleObjectType) + "; "; + if (IsClosed) + return handleObjectType = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); var (v, ex) = ProcessInfo.ProcessProtection; if (v is null) { - return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Failed to query the process's protection:\r\n" + ex, ex)); + return handleObjectType = (null, new InvalidOperationException(errUnableMsg + "Failed to query the process's protection.", ex)); } else if (v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight) { @@ -70,12 +75,12 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals } catch (Exception e) { - return handleObjectType = (null, e); + return handleObjectType = (null, new InvalidOperationException(errFailedMsg + e.Message, e)); } } else { - return handleObjectType = (null, new UnauthorizedAccessException("Unable to query the kernel object's Type; The process is protected.")); + return handleObjectType = (null, new UnauthorizedAccessException(errUnableMsg + "The process is protected.")); } } else @@ -88,7 +93,13 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals /// /// (non-persistent) Pass the handle to GetHandleInformation and check for ERROR_INVALID_HANDLE to determine if the handle is open or closed. /// - public new bool IsClosed => GetIsClosed(); + public new bool IsClosed + { + get + { + return isClosed ? isClosed : (isClosed = GetIsClosed()); + } + } private bool GetIsClosed() { @@ -96,12 +107,15 @@ private bool GetIsClosed() { HANDLE_FLAGS flags = GetHandleInformation(this); } - catch (PInvoke.Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_HANDLE) + catch (Exception ex) when ( + (ex is Win32Exception inWin32Exception && inWin32Exception.NativeErrorCode is (int)Win32ErrorCode.ERROR_INVALID_HANDLE) + || (ex is PInvoke.Win32Exception piWin32Exception && piWin32Exception.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_HANDLE)) { return true; } - catch (PInvoke.Win32Exception ex) + catch (Exception ex) { + Console.Error.WriteLine($"GetIsClosed failed; {ex.ToString}"); Trace.TraceError(ex.ToString()); } return false; @@ -122,10 +136,12 @@ public unsafe (string? v, Exception? ex) ObjectName { get { - if (objectName == default) + if (objectName is (null, null)) { const string errUnableMsg = "Unable to query " + nameof(ObjectName) + "; "; const string errFailedMsg = "Failed to query " + nameof(ObjectName) + "; "; + if (IsClosed) + return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); var (v, ex) = ProcessInfo.ProcessProtection; // I'm assuming process protection prohibits access. I've not tested it. // This information is not queryable in SystemInformer when a process has Full protection. From 4dd2396b2386cc2eee753d6708d11c66df6263ca Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 15:07:42 -0700 Subject: [PATCH 275/306] docs: document GetHandleInfo(), GetHandleInformation(SafeHandle) --- deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs | 4 ++++ .../Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs index d6ac51b..e0aee51 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs @@ -208,5 +208,9 @@ public static bool PrivilegeCheck(SafeHandle ClientToken, ref PRIVILEGE_SET Requ public static SafeFileHandle OpenProcessToken(SafeFileHandle ProcessHandle, TOKEN_ACCESS_MASK DesiredAccess) => OpenProcessToken(ProcessHandle, DesiredAccess, out SafeFileHandle TokenHandle) ? TokenHandle : throw new Win32Exception(); + /// Get the handle's flags/attributes. + /// + /// A copy of the handle's current flags/attributes as + /// The P/Invoke operation failed. public static HANDLE_FLAGS GetHandleInformation(SafeHandle hObject) => GetHandleInformation(hObject, out uint flags) ? (HANDLE_FLAGS)flags : throw new Win32Exception(); } diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 1240374..cd897e5 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -80,6 +80,11 @@ public unsafe string GetHandleObjectType() : throw new NTStatusException(status); } + /// + /// Get the current HANDLE_FLAGS of this handle if it is still open. + /// + /// + /// Failed to get handle information -OR- failed to open process to duplicate handle. public unsafe HANDLE_FLAGS GetHandleInfo() { try From 4251f0569b31dcbc5dd8e9d3c7300a3ebc715742 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 15:28:48 -0700 Subject: [PATCH 276/306] feat: add CloseSourceHandle(bool) overload The parameterless overload now calls CloseSourceHandle(false) to increase perceived safety at the cost of reliability. When parameter removeCloseProtection is true, the method will attempt to remove the HANDLE_FLAG_PROTECT_FROM_CLOSE attribute from the handle before attempting to close it. --- deadlock-dotnet-sdk/DeadLock.cs | 6 +++--- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 23 +++++++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index 6cc5ea4..3f6fe44 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -312,7 +312,7 @@ public void UnlockEx(FileLockerEx fileLocker) if (h.IsClosed && h.IsInvalid) continue; try { - h.CloseSourceHandle(); + h.CloseSourceHandle(true); } catch (Exception) when (!RethrowExceptions) { } } @@ -347,7 +347,7 @@ await Task.Run(() => if (h.IsClosed && h.IsInvalid) continue; try { - h.CloseSourceHandle(); + h.CloseSourceHandle(true); } catch (Exception) when (!RethrowExceptions) { } } @@ -369,7 +369,7 @@ await Task.Run(() => if (h.IsClosed && h.IsInvalid) continue; try { - h.CloseSourceHandle(); + h.CloseSourceHandle(true); } catch (Exception) when (!RethrowExceptions) { } } diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 2743791..3f2d0cc 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; using PInvoke; using Windows.Win32; @@ -187,19 +188,39 @@ public unsafe (string? v, Exception? ex) ObjectName #region Methods + /// + /// Release the system handle. If the handle has the HANDLE_FLAG_PROTECT_FROM_CLOSE attribute, the operation fails.
+ /// ! WARNING !
+ /// If the handle or a duplicate is in use by a driver or other kernel-level software, a function that accesses the now-invalid handle will cause a stopcode (AKA Blue Screen Of Death). + ///
+ /// + /// See Raymond Chen's devblog article "Kernel handles are not reference-counted". + /// + /// Failed to open process to duplicate and close object handle. + public bool CloseSourceHandle() => CloseSourceHandle(false); + /// /// Release the system handle.
/// ! WARNING !
/// If the handle or a duplicate is in use by a driver or other kernel-level software, a function that accesses the now-invalid handle will cause a stopcode (AKA Blue Screen Of Death). ///
+ /// Some handles have the HANDLE_FLAG_PROTECT_FROM_CLOSE attribute. In this case, the attribute must be removed for the handle to be closed. Otherwise, the operation will fail. This parameter is made available to allow /// /// See Raymond Chen's devblog article "Kernel handles are not reference-counted". /// /// Failed to open process to duplicate and close object handle. - public bool CloseSourceHandle() + public bool CloseSourceHandle(bool removeCloseProtection) { try { + if (removeCloseProtection + && ((SysHandleEx.HandleAttributes & Kernel32.HandleFlags.HANDLE_FLAG_PROTECT_FROM_CLOSE) is not 0) + && !SetHandleInformation(this, (uint)HANDLE_FLAGS.HANDLE_FLAG_PROTECT_FROM_CLOSE, 0)) + { + Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError(); + throw new PInvoke.Win32Exception(err, "Failed to remove HANDLE_FLAG_PROTECT_FROM_CLOSE attribute; " + err.GetMessage()); + } + HANDLE rawHProcess; using SafeProcessHandle hProcess = new( !(rawHProcess = OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, true, ProcessId)).IsNull From f5e5c72ba8b90c851915ecafc0518565f0ac1ebe Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 15:19:07 -0700 Subject: [PATCH 277/306] docs: add readonly HandleAttributes remarks refactor: use coalesce expression --- .../Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index cd897e5..ff9de6c 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -51,6 +51,7 @@ public readonly struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX /// ProcessHacker defines a little over a dozen handle-able object types. public ushort ObjectTypeIndex { get; } // USHORT /// + /// Not updated when SetHandleInformation is called. public HandleFlags HandleAttributes { get; } // uint #pragma warning disable RCS1213, CS0169, IDE0051 // Remove unused field declaration. csharp(RCS1213) | Roslynator private readonly uint Reserved; @@ -99,8 +100,7 @@ public unsafe HANDLE_FLAGS GetHandleInfo() // If passing the source handle failed, try passing a duplicate instead - using SafeProcessHandle sourceProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, (uint)UniqueProcessId); - if (sourceProcess is null) throw new Win32Exception(); + using SafeProcessHandle sourceProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, (uint)UniqueProcessId) ?? throw new Win32Exception(); using SafeObjectHandle safeHandleValue = new(HandleValue, false); DuplicateHandle(sourceProcess, safeHandleValue, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, default, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); return GetHandleInformation(dupHandle); From 4312a64852d6af414b713158a53a28db0a6f8b0e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 15:25:47 -0700 Subject: [PATCH 278/306] fix: convert properties to strings for sorting instead of to byte arrays This *should* prevent an InvalidOperationException thrown by `System.Collection.Generic.ArraySortHelper.Sort(Span keys, Comparison comparison)`. Its inner exception, an ArgumentException "At least one object must implement IComparable", is thrown by System.Collection.Comparer.Compare(Object a, Object b). --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 53 +++++++--------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 4433099..7f38b47 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -40,50 +40,29 @@ public enum SortByProperty ProcessId } - // TODO: order by Process ID and then by handle value. Later todo: allow user-specified sorting rule (e.g. by column/property) /// Get or set the List of handles that are locking the file public List Lockers { get { return lockers - .OrderBy(h => - { - switch (SortByPrimary) // returns byte[] - { - //case SortByProperty.FileShareAccess: return h.FileShareAccess; // not possible without a kernel mode driver; see IoCheckShareAccess - case SortByProperty.HandleAttributes: return Encoding.ASCII.GetBytes(h.HandleAttributes.ToString()); - case SortByProperty.HandleSubType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); - case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.HandleObjectType.v?.ToString() ?? string.Empty); - case SortByProperty.HandleValue: return Encoding.ASCII.GetBytes(h.HandleValue.ToString()); - case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); - case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); - case SortByProperty.ObjectOriginalName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); - case SortByProperty.ObjectRealName: return Encoding.ASCII.GetBytes(h.FileFullPath.v ?? h.FileNameInfo.v ?? string.Empty); // TODO: implement Registry key parsing - case SortByProperty.ObjectAddress: return BitConverter.GetBytes((ulong)h.ObjectAddress); - case SortByProperty.ProcessId: return BitConverter.GetBytes(h.ProcessId); - default: goto case SortByProperty.ProcessId; - } - }) - .ThenBy(h => - { - switch (SortBySecondary) // returns byte[] - { - //case SortByProperty.FileShareAccess: return h.FileShareAccess; // not possible without a kernel mode driver; see IoCheckShareAccess - case SortByProperty.HandleAttributes: return Encoding.ASCII.GetBytes(h.HandleAttributes.ToString()); - case SortByProperty.HandleSubType: return Encoding.ASCII.GetBytes(h.FileHandleType.v?.ToString() ?? string.Empty); - case SortByProperty.HandleType: return Encoding.ASCII.GetBytes(h.HandleObjectType.v?.ToString() ?? string.Empty); - case SortByProperty.HandleValue: return Encoding.ASCII.GetBytes(h.HandleValue.ToString()); - case SortByProperty.GrantedAccessHexadecimal: return BitConverter.GetBytes(h.GrantedAccess.Value); - case SortByProperty.GrantedAccessSymbolic: return Encoding.ASCII.GetBytes(h.GrantedAccessString); - case SortByProperty.ObjectOriginalName: return Encoding.ASCII.GetBytes(h.ObjectName.v ?? string.Empty); - case SortByProperty.ObjectRealName: return Encoding.ASCII.GetBytes(h.FileFullPath.v ?? h.FileNameInfo.v ?? string.Empty); // TODO: implement Registry key parsing - case SortByProperty.ObjectAddress: return BitConverter.GetBytes((ulong)h.ObjectAddress); - case SortByProperty.ProcessId: return BitConverter.GetBytes(h.ProcessId); - default: goto case SortByProperty.ProcessId; - } - }) + .OrderBy(h => SortBy(h, SortByPrimary)) + .ThenBy(h => SortBy(h, SortBySecondary)) .ToList(); + static string SortBy(SafeFileHandleEx h, SortByProperty property) => property switch // returns string + { + //case SortByProperty.FileShareAccess: return h.FileShareAccess; // not possible without a kernel mode driver; see IoCheckShareAccess + SortByProperty.HandleAttributes => h.HandleAttributes.ToString(), + SortByProperty.HandleSubType => h.FileHandleType.v?.ToString() ?? string.Empty, + SortByProperty.HandleType => h.HandleObjectType.v?.ToString() ?? string.Empty, + SortByProperty.HandleValue => h.HandleValue.ToString(), + SortByProperty.GrantedAccessHexadecimal => h.GrantedAccess.Value.ToString(), + SortByProperty.GrantedAccessSymbolic => h.GrantedAccessString, + SortByProperty.ObjectOriginalName => h.ObjectName.v ?? string.Empty, + SortByProperty.ObjectRealName => h.FileFullPath.v ?? h.FileNameInfo.v ?? string.Empty,// TODO: implement Registry key parsing + SortByProperty.ObjectAddress => h.ObjectAddress.ToString(), + _ => h.ProcessId.ToString(), + }; } } From 72fe0a30daa1207061f3a436b768fac57a422472 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 15:27:46 -0700 Subject: [PATCH 279/306] fix: do not initialize properties by default when evaluating SafeFileHandleEx.ToString() This issue was causing the debugger to unpause threads or sometimes crash the debuggee process. --- .../Domain/SafeFileHandleEx.cs | 47 +++++++++++-------- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index da0d726..ea815d8 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -462,42 +462,49 @@ public enum FileType : uint Pipe = FILE_TYPE.FILE_TYPE_PIPE, } - public override string ToString() => ToString(true); + public override string ToString() => ToString(false, false); /// /// Get the string representation of this SafeFileHandleEx object. /// - /// If TRUE, get values from Properties. If FALSE, get values from Properties' backing fields. + /// If TRUE, get values from Properties. If FALSE, get values from Properties' backing fields. /// The string representation of this SafeFileHandleEx object. - public string ToString(bool init) + public string ToString(bool initProps, bool initProcessInfo) { - string[] exLog = ExceptionLog.ConvertAll(ex => ex.ToString()).ToArray(); - for (int i = 0; i < exLog.Length; i++) + try { - exLog[i] = $" {exLog[i]}".Replace("\n", "\n ") + "\r\n"; - } + string[] exLog = ExceptionLog.ConvertAll(ex => ex.ToString()).ToArray(); + for (int i = 0; i < exLog.Length; i++) + { + exLog[i] = $" {exLog[i]}".Replace("\n", "\n ") + "\r\n"; + } - return @$"{nameof(SafeFileHandleEx)} hash:{GetHashCode()} + return @$"{nameof(SafeFileHandleEx)} hash:{GetHashCode()} {nameof(CreatorBackTraceIndex)} : {CreatorBackTraceIndex} - {nameof(FileFullPath)} : {(init ? (FileFullPath.v ?? FileFullPath.ex?.ToString()) : (fileFullPath.v ?? fileFullPath.ex?.ToString()))} - {nameof(FileHandleType)} : {(init ? (FileHandleType.v?.ToString() ?? FileFullPath.ex?.ToString()) : (fileHandleType.v?.ToString() ?? fileHandleType.ex?.ToString()))} - {nameof(FileName)} : {(init ? (FileName.v ?? FileName.ex?.ToString()) : (fileName.v ?? fileName.ex?.ToString()))} + {nameof(FileFullPath)} : {(initProps ? (FileFullPath.v ?? FileFullPath.ex?.ToString()) : (fileFullPath.v ?? fileFullPath.ex?.ToString()))} + {nameof(FileHandleType)} : {(initProps ? (FileHandleType.v?.ToString() ?? FileFullPath.ex?.ToString()) : (fileHandleType.v?.ToString() ?? fileHandleType.ex?.ToString()))} + {nameof(FileName)} : {(initProps ? (FileName.v ?? FileName.ex?.ToString()) : (fileName.v ?? fileName.ex?.ToString()))} {nameof(GrantedAccess)} : {SysHandleEx.GrantedAccessString} - {nameof(HandleObjectType)} : {(init ? (HandleObjectType.v ?? HandleObjectType.ex?.ToString()) : (handleObjectType.v ?? handleObjectType.ex?.ToString()))} + {nameof(HandleObjectType)} : {(initProps ? (HandleObjectType.v ?? HandleObjectType.ex?.ToString()) : (handleObjectType.v ?? handleObjectType.ex?.ToString()))} {nameof(HandleValue)} : {HandleValue} (0x{HandleValue:X}) {nameof(IsClosed)} : {IsClosed} - {nameof(IsDirectory)} : {(init ? (IsDirectory.v?.ToString() ?? IsDirectory.ex?.ToString()) : (isDirectory.v?.ToString() ?? isDirectory.ex?.ToString()))} - {nameof(IsFileHandle)} : {(init ? (IsFileHandle.v?.ToString() ?? IsFileHandle.ex?.ToString()) : (isFileHandle.v?.ToString() ?? isFileHandle.ex?.ToString()))} + {nameof(IsDirectory)} : {(initProps ? (IsDirectory.v?.ToString() ?? IsDirectory.ex?.ToString()) : (isDirectory.v?.ToString() ?? isDirectory.ex?.ToString()))} + {nameof(IsFileHandle)} : {(initProps ? (IsFileHandle.v?.ToString() ?? IsFileHandle.ex?.ToString()) : (isFileHandle.v?.ToString() ?? isFileHandle.ex?.ToString()))} {nameof(IsInvalid)} : {IsInvalid} {nameof(ObjectAddress)} : {ObjectAddress} (0x{ObjectAddress:X}) - {nameof(ObjectName)} : {(init ? (ObjectName.v ?? ObjectName.ex?.ToString()) : (objectName.v ?? objectName.ex?.ToString()))} + {nameof(ObjectName)} : {(initProps ? (ObjectName.v ?? ObjectName.ex?.ToString()) : (objectName.v ?? objectName.ex?.ToString()))} {nameof(ProcessId)} : {ProcessId} - {nameof(ProcessInfo.ParentId)} : {ProcessInfo.ParentId.v?.ToString() ?? ProcessInfo.ParentId.ex?.ToString() ?? string.Empty} - {nameof(ProcessInfo.ProcessCommandLine)} : {ProcessInfo.ProcessCommandLine.v ?? ProcessInfo.ProcessCommandLine.ex?.ToString()} - {nameof(ProcessInfo.ProcessMainModulePath)} : {ProcessInfo.ProcessMainModulePath.v ?? ProcessInfo.ProcessMainModulePath.ex?.ToString()} - {nameof(ProcessInfo.ProcessName)} : {ProcessInfo.ProcessName.v ?? ProcessInfo.ProcessName.ex?.ToString()} - {nameof(ProcessInfo.ProcessProtection)} : {ProcessInfo.ProcessProtection.v?.ToString() ?? ProcessInfo.ProcessProtection.ex?.ToString() ?? string.Empty} + {nameof(ProcessInfo.ParentId)} : {(initProcessInfo ? (ProcessInfo.ParentId.v?.ToString() ?? ProcessInfo.ParentId.ex?.ToString()) : (processInfo?.ParentId.v?.ToString() ?? processInfo?.ParentId.ex?.ToString() ?? string.Empty))} + {nameof(ProcessInfo.ProcessCommandLine)} : {(initProcessInfo ? (ProcessInfo.ProcessCommandLine.v ?? ProcessInfo.ProcessCommandLine.ex?.ToString()) : (processInfo?.ProcessCommandLine.v ?? processInfo?.ProcessCommandLine.ex?.ToString() ?? string.Empty))} + {nameof(ProcessInfo.ProcessMainModulePath)} : {(initProcessInfo ? (ProcessInfo.ProcessMainModulePath.v ?? ProcessInfo.ProcessMainModulePath.ex?.ToString()) : (processInfo?.ProcessMainModulePath.v ?? processInfo?.ProcessMainModulePath.ex?.ToString() ?? string.Empty))} + {nameof(ProcessInfo.ProcessName)} : {(initProcessInfo ? (ProcessInfo.ProcessName.v ?? ProcessInfo.ProcessName.ex?.ToString()) : (processInfo?.ProcessName.v ?? processInfo?.ProcessName.ex?.ToString() ?? string.Empty))} + {nameof(ProcessInfo.ProcessProtection)} : {(initProcessInfo ? (ProcessInfo.ProcessProtection.v?.ToString() ?? ProcessInfo.ProcessProtection.ex?.ToString()) : (processInfo?.ProcessProtection.v?.ToString() ?? processInfo?.ProcessProtection.ex?.ToString() ?? string.Empty))} {nameof(ExceptionLog)} : ... " + string.Concat(exLog); + } + catch (Exception ex) + { + return $"Error while evaluating properties for SafeFileHandleEx.ToString(): {ex}"; + } } } diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 3f2d0cc..935c7ec 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -28,7 +28,7 @@ public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid protected (string? v, Exception? ex) handleObjectType; private bool isClosed; protected (string? v, Exception? ex) objectName; - private ProcessInfo? processInfo; + protected ProcessInfo? processInfo; public SafeHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHandleEx) { } From bfb841bf6c1a13e7bfaa966a722a2146c817f381 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 15:28:09 -0700 Subject: [PATCH 280/306] refactor: remove FileFullPath stopwatch --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index ea815d8..7753779 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -206,7 +206,6 @@ public unsafe (string? v, Exception? ex) FileFullPath const uint LengthIndicatesError = 0; // Try without duplicating. If it fails, try duplicating the handle. - Stopwatch sw = Stopwatch.StartNew(); try { GETFINALPATHNAMEBYHANDLE_FLAGS flags = IsFilePathRemote.v is true ? GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_OPENED : GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED; @@ -248,11 +247,6 @@ public unsafe (string? v, Exception? ex) FileFullPath { return fileFullPath = (null, ex); } - finally - { - sw.Stop(); - Console.WriteLine($"(handle 0x{handle:X}) TryGetFinalPath time: {sw.Elapsed}"); - } /// Return the normalized drive name. This is the default. using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); From 7ad07871cd2a677d5cdb1acd497f543ed6f18e5d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 15:28:28 -0700 Subject: [PATCH 281/306] chore: sort NativeMethods.txt contents --- deadlock-dotnet-sdk/NativeMethods.txt | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/deadlock-dotnet-sdk/NativeMethods.txt b/deadlock-dotnet-sdk/NativeMethods.txt index e1bc4c9..cd9e349 100644 --- a/deadlock-dotnet-sdk/NativeMethods.txt +++ b/deadlock-dotnet-sdk/NativeMethods.txt @@ -50,6 +50,19 @@ RmEndSession RmGetList RmRegisterResources RmStartSession +SetHandleInformation + +//// Security //// +GENERIC_MAPPING + +//// Security.Apis //// +SE_DEBUG_NAME + +//// Storage.FileSystem //// +FILE_ATTRIBUTE_TAG_INFO +FILE_NAME_INFO +FILE_REMOTE_PROTOCOL_INFO + //// System.Memory //// MEMORY_BASIC_INFORMATION32 @@ -61,6 +74,7 @@ PROCESS_BASIC_INFORMATION PROCESSINFOCLASS //// System.WindowsProgramming //// +LDR_DATA_TABLE_ENTRY PUBLIC_OBJECT_TYPE_INFORMATION //// does not exist in Win32Metadata //// @@ -73,15 +87,10 @@ PUBLIC_OBJECT_TYPE_INFORMATION //MEMORY_BASIC_INFORMATION //VirtualQuery -GENERIC_MAPPING -LDR_DATA_TABLE_ENTRY -RTL_CRITICAL_SECTION +//// PInvoke001: Method, type or constant not found. +//ProcessProtectionInformation +//PS_PROTECTION +//PsProtectedTypeProtected +//SE_DEBUG_PRIVILEGE -//SE_DEBUG_PRIVILEGE // PInvoke001 -SE_DEBUG_NAME -//ProcessProtectionInformation // PInvoke001: Method, type or constant "ProcessProtectionInformation" not found. -//PS_PROTECTION // PInvoke001: Method, type or constant "PS_PROTECTION" not found. -//PsProtectedTypeProtected // PInvoke001: Method, type or constant "PsProtectedTypeProtected" not found -FILE_NAME_INFO -FILE_REMOTE_PROTOCOL_INFO -FILE_ATTRIBUTE_TAG_INFO +RTL_CRITICAL_SECTION From 1ddff98e17b8096916ab423308538c6f99e06664 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 18:10:34 -0700 Subject: [PATCH 282/306] fix: if buffer too small and returnLength is less than buffer length, double buffer length instead of assigning returnLength --- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 4 ++-- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 16 +++++++++++++--- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 4 +++- .../SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 10 +++++----- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index e3c9b8e..620067b 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -256,7 +256,7 @@ ref returnLength ); } - if (status != Code.STATUS_SUCCESS) + if (status.Code is not Code.STATUS_SUCCESS) { // Fall back to using the previous code that we've used since Windows XP (dmex) systemInformationLength = 0x10000; @@ -268,7 +268,7 @@ ref returnLength SystemInformation: pSysInfoBuffer, SystemInformationLength: systemInformationLength, ReturnLength: ref returnLength - )) == Code.STATUS_INFO_LENGTH_MISMATCH) + )).Code is Code.STATUS_INFO_LENGTH_MISMATCH) { Marshal.FreeHGlobal((IntPtr)pSysInfoBuffer); systemInformationLength *= 2; diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index c05bc77..6d041b5 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -252,7 +252,9 @@ ref returnLength #endif // !WARNING may throw OutOfMemoryException; ReAllocHGlobal received a null pointer, but didn't check the error code // the native call to LocalReAlloc (via Marshal.ReAllocHGlobal) sometimes returns a null pointer. This is a Legacy function. Why does .NET not use malloc/realloc? - bufferCmdLine.Reallocate(numBytes: returnLength); + if (returnLength < bufferCmdLine.ByteLength) + bufferCmdLine.Reallocate(numBytes: (nuint)(bufferCmdLine.ByteLength * 2)); + else bufferCmdLine.Reallocate(numBytes: returnLength); // none of these helped debug that internal error... //var pinerr = Marshal.GetLastPInvokeError(); //var syserr = Marshal.GetLastSystemError(); @@ -279,7 +281,11 @@ ref returnLength if (Env.Is64BitOperatingSystem && !Env.Is64BitProcess && Is32BitEmulatedProcess.v is false) // yes { while ((status = NtWow64QueryInformationProcess64(ProcessHandle.v, PROCESSINFOCLASS.ProcessBasicInformation, (void*)bufferPBI.DangerousGetHandle(), (uint)bufferPBI.ByteLength, &returnLength)).Code is Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL or Code.STATUS_BUFFER_OVERFLOW) - bufferPBI.Reallocate(numBytes: returnLength); + { + if (returnLength < bufferPBI.ByteLength) + bufferPBI.Reallocate(numBytes: (nuint)(bufferPBI.ByteLength * 2)); + else bufferPBI.Reallocate(numBytes: returnLength); + } if (status.Code is not Code.STATUS_SUCCESS) throw new NTStatusException(status, "NtWow64QueryInformationProcess64 failed to query a process's basic information; " + status.Message); @@ -289,7 +295,11 @@ ref returnLength else { while ((status = NtQueryInformationProcess(ProcessHandle.v, PROCESSINFOCLASS.ProcessBasicInformation, (void*)bufferPBI.DangerousGetHandle(), (uint)bufferPBI.ByteLength, ref returnLength)).Code is Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL or Code.STATUS_BUFFER_OVERFLOW) - bufferPBI.Reallocate(returnLength + (uint)IntPtr.Size); + { + if (returnLength < bufferPBI.ByteLength) + bufferPBI.Reallocate((nuint)(bufferPBI.ByteLength * 2)); + else bufferPBI.Reallocate((returnLength is 0 ? returnLength = (uint)(Marshal.SizeOf() * 2) : returnLength) + (uint)IntPtr.Size); + } if (status.Code is not Code.STATUS_SUCCESS) throw new NTStatusException(status, "NtQueryInformationProcess failed to query a process's basic information; " + status.Message); diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 935c7ec..a067c67 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -162,7 +162,9 @@ public unsafe (string? v, Exception? ex) ObjectName &bufferLength)).Code is Code.STATUS_BUFFER_OVERFLOW or Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL) { - buffer.Reallocate(bufferLength); + if (bufferLength < buffer.ByteLength) + buffer.Reallocate((uint)(buffer.ByteLength * 2)); + else buffer.Reallocate(bufferLength); } OBJECT_NAME_INFORMATION oni = buffer.Read(0); diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index ff9de6c..4cba3b7 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -67,13 +67,13 @@ public unsafe string GetHandleObjectType() uint returnLength; using var h = new SafeObjectHandle(HandleValue, false); - status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); - // Something's off. Marshal.SizeOf() returns 0x68 (104) but returnLength is 0x78 (120) or sometimes 0x80 (128). Is Win32Metadata's type definition wrong? - while (status.Code is STATUS_BUFFER_OVERFLOW or STATUS_INFO_LENGTH_MISMATCH or STATUS_BUFFER_TOO_SMALL) + while ((status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength)) + .Code is STATUS_BUFFER_OVERFLOW or STATUS_INFO_LENGTH_MISMATCH or STATUS_BUFFER_TOO_SMALL) { - buffer.Reallocate(returnLength); - status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength); + if (returnLength < buffer.ByteLength) + buffer.Reallocate((nuint)(2 * buffer.ByteLength)); + else buffer.Reallocate(returnLength); } return status.IsSuccessful From b4819c23b99895cf77bb425a855cff06e6a4da0d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 18:16:25 -0700 Subject: [PATCH 283/306] refactor: use 'is' when comparing to constants style: remove unnecessary parentheses --- deadlock-dotnet-sdk/DeadLock.cs | 2 +- deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs | 4 ++-- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 2 +- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index 3f6fe44..09da664 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -242,7 +242,7 @@ public List FindLockingHandles(HandlesFilter filter, ref List fileLockers = new(); warnings = new(); - if (filePaths.Length == 1) + if (filePaths.Length is 1) { fileLockers.Add(FindLockingHandles(filePaths[0], filter, out WarningException? warningException)); if (warningException != null) warnings.Add(warningException); diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs index cfaecf7..10c3619 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs @@ -32,12 +32,12 @@ public ProcessQueryHandle(SafeProcessHandle processHandle, PROCESS_ACCESS_RIGHTS /// /// /// - If processId == Process.GetCurrentProcess().Id, use Process.GetCurrentProcess().SafeHandle property instead. - /// - If Windows.Win32.PInvoke.IsDebugModeEnabled() == true, the requested access is granted regardless of the security descriptor. See GetSecurityInfo(); + /// - If Windows.Win32.PInvoke.IsDebugModeEnabled() is true, the requested access is granted regardless of the security descriptor. See GetSecurityInfo(); /// public static ProcessQueryHandle OpenProcessHandle(int processId, PROCESS_ACCESS_RIGHTS accessRights) { var h = OpenProcess_SafeHandle(accessRights, false, (uint)processId); - return h is null ? throw new Win32Exception() : (new(h, accessRights)); + return h is null ? throw new Win32Exception() : new(h, accessRights); } public static implicit operator SafeProcessHandle(ProcessQueryHandle v) => v.Handle; diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 7753779..9bda3cb 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -110,7 +110,7 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( const string errUnableMsg = "Unable to query " + nameof(IsFileHandle) + "; "; if (IsClosed) return isFileHandle = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); - return HandleObjectType.v == "File" + return HandleObjectType.v is "File" ? (isFileHandle = (true, null)) : (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex))); } diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index a067c67..86eaca1 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -168,7 +168,7 @@ public unsafe (string? v, Exception? ex) ObjectName } OBJECT_NAME_INFORMATION oni = buffer.Read(0); - if (oni.Name.Buffer.Value == null) + if (oni.Name.Buffer.Value is null) return objectName = (null, new NullReferenceException(errFailedMsg + "Bad data was copied to the buffer. The string pointer is null.")); return status.IsSuccessful From bfbd325712af9ca6e0120b3748432caf571105f7 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 20:30:01 -0700 Subject: [PATCH 284/306] fix: offset FileNameInfo buffer to start of char sequence --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 9bda3cb..33c0087 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -420,7 +420,7 @@ public unsafe (string? v, Exception? ex) FileNameInfo UNICODE_STRING str = new() { - Buffer = new PWSTR((char*)safeBuffer.DangerousGetHandle()), + Buffer = new PWSTR((char*)(((FILE_NAME_INFO*)safeBuffer.DangerousGetHandle()) + sizeof(int))), Length = (ushort)fni.FileNameLength, MaximumLength = (ushort)bufferLength }; From 36eceb1f2e2bf1f33cf6fc98214874b8ed9f3666 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 20:36:13 -0700 Subject: [PATCH 285/306] fix: convert ex to string, not the ToString method --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 86eaca1..2c3f982 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -116,7 +116,7 @@ private bool GetIsClosed() } catch (Exception ex) { - Console.Error.WriteLine($"GetIsClosed failed; {ex.ToString}"); + Console.Error.WriteLine($"GetIsClosed failed; {ex}"); Trace.TraceError(ex.ToString()); } return false; From 1cdea2604a5419dc57f175ddbdc7aa90f1c1079a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 20:38:35 -0700 Subject: [PATCH 286/306] fix: prevent reallocation loops when buffer larger than returnLength --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 11 +++++------ deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 14 +++++++------- .../SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 6d041b5..2d4b998 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -252,7 +252,7 @@ ref returnLength #endif // !WARNING may throw OutOfMemoryException; ReAllocHGlobal received a null pointer, but didn't check the error code // the native call to LocalReAlloc (via Marshal.ReAllocHGlobal) sometimes returns a null pointer. This is a Legacy function. Why does .NET not use malloc/realloc? - if (returnLength < bufferCmdLine.ByteLength) + if (returnLength is 0) bufferCmdLine.Reallocate(numBytes: (nuint)(bufferCmdLine.ByteLength * 2)); else bufferCmdLine.Reallocate(numBytes: returnLength); // none of these helped debug that internal error... @@ -282,7 +282,7 @@ ref returnLength { while ((status = NtWow64QueryInformationProcess64(ProcessHandle.v, PROCESSINFOCLASS.ProcessBasicInformation, (void*)bufferPBI.DangerousGetHandle(), (uint)bufferPBI.ByteLength, &returnLength)).Code is Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL or Code.STATUS_BUFFER_OVERFLOW) { - if (returnLength < bufferPBI.ByteLength) + if (returnLength is 0) bufferPBI.Reallocate(numBytes: (nuint)(bufferPBI.ByteLength * 2)); else bufferPBI.Reallocate(numBytes: returnLength); } @@ -294,11 +294,10 @@ ref returnLength } else { - while ((status = NtQueryInformationProcess(ProcessHandle.v, PROCESSINFOCLASS.ProcessBasicInformation, (void*)bufferPBI.DangerousGetHandle(), (uint)bufferPBI.ByteLength, ref returnLength)).Code is Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL or Code.STATUS_BUFFER_OVERFLOW) + while ((status = NtQueryInformationProcess(ProcessHandle.v, PROCESSINFOCLASS.ProcessBasicInformation, (void*)bufferPBI.DangerousGetHandle(), (uint)bufferPBI.ByteLength, ref returnLength)).Code + is Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL or Code.STATUS_BUFFER_OVERFLOW) { - if (returnLength < bufferPBI.ByteLength) - bufferPBI.Reallocate((nuint)(bufferPBI.ByteLength * 2)); - else bufferPBI.Reallocate((returnLength is 0 ? returnLength = (uint)(Marshal.SizeOf() * 2) : returnLength) + (uint)IntPtr.Size); + bufferPBI.Reallocate(returnLength is 0 ? returnLength = (uint)(bufferPBI.ByteLength * 2) : returnLength); } if (status.Code is not Code.STATUS_SUCCESS) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index 2c3f982..cde6df8 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -151,25 +151,25 @@ public unsafe (string? v, Exception? ex) ObjectName else if (v.Value.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) return objectName = (null, new UnauthorizedAccessException(errUnableMsg + "The process's protection type prohibits access.")); - uint bufferLength = 1024u; - using SafeBuffer buffer = new(numBytes: bufferLength); + uint returnLength = 1024u; + using SafeBuffer buffer = new(numBytes: returnLength); NTSTATUS status = default; while ((status = NtQueryObject(this, OBJECT_INFORMATION_CLASS.ObjectNameInformation, (OBJECT_NAME_INFORMATION*)buffer.DangerousGetHandle(), - bufferLength, - &bufferLength)).Code + returnLength, + &returnLength)).Code is Code.STATUS_BUFFER_OVERFLOW or Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL) { - if (bufferLength < buffer.ByteLength) + if (returnLength is 0) buffer.Reallocate((uint)(buffer.ByteLength * 2)); - else buffer.Reallocate(bufferLength); + else buffer.Reallocate(returnLength); } OBJECT_NAME_INFORMATION oni = buffer.Read(0); if (oni.Name.Buffer.Value is null) - return objectName = (null, new NullReferenceException(errFailedMsg + "Bad data was copied to the buffer. The string pointer is null.")); + return objectName = (null, new NullReferenceException(errFailedMsg + "The object is unnamed -OR- bad data was copied to the buffer. The string pointer is null.")); return status.IsSuccessful ? objectName = (oni.NameAsString, null) diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index 4cba3b7..f00645e 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -71,7 +71,7 @@ public unsafe string GetHandleObjectType() while ((status = NtQueryObject(h, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, (void*)buffer.DangerousGetHandle(), (uint)buffer.ByteLength, &returnLength)) .Code is STATUS_BUFFER_OVERFLOW or STATUS_INFO_LENGTH_MISMATCH or STATUS_BUFFER_TOO_SMALL) { - if (returnLength < buffer.ByteLength) + if (returnLength is 0) buffer.Reallocate((nuint)(2 * buffer.ByteLength)); else buffer.Reallocate(returnLength); } From cbbc524587475f14f5561aaef804bbdc83e043d3 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 20:39:06 -0700 Subject: [PATCH 287/306] feat: add NameBuffer placeholder for trailing char array --- .../System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs index 6ad045b..1838986 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/System/WindowsProgramming/OBJECT_NAME_INFORMATION.cs @@ -5,6 +5,7 @@ namespace Windows.Win32.System.WindowsProgramming; public struct OBJECT_NAME_INFORMATION { public UNICODE_STRING Name; + public unsafe fixed char NameBuffer[1]; public string NameAsString => Name.ToStringLength(); } From 7b052c5dad3b83d34dbd7903354b563e6c468cc1 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 20:39:53 -0700 Subject: [PATCH 288/306] refactor: realign output of SafeFileHandleEx.ToString() --- .../Domain/SafeFileHandleEx.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 33c0087..6613211 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -474,26 +474,26 @@ public string ToString(bool initProps, bool initProcessInfo) } return @$"{nameof(SafeFileHandleEx)} hash:{GetHashCode()} - {nameof(CreatorBackTraceIndex)} : {CreatorBackTraceIndex} - {nameof(FileFullPath)} : {(initProps ? (FileFullPath.v ?? FileFullPath.ex?.ToString()) : (fileFullPath.v ?? fileFullPath.ex?.ToString()))} - {nameof(FileHandleType)} : {(initProps ? (FileHandleType.v?.ToString() ?? FileFullPath.ex?.ToString()) : (fileHandleType.v?.ToString() ?? fileHandleType.ex?.ToString()))} - {nameof(FileName)} : {(initProps ? (FileName.v ?? FileName.ex?.ToString()) : (fileName.v ?? fileName.ex?.ToString()))} - {nameof(GrantedAccess)} : {SysHandleEx.GrantedAccessString} - {nameof(HandleObjectType)} : {(initProps ? (HandleObjectType.v ?? HandleObjectType.ex?.ToString()) : (handleObjectType.v ?? handleObjectType.ex?.ToString()))} - {nameof(HandleValue)} : {HandleValue} (0x{HandleValue:X}) - {nameof(IsClosed)} : {IsClosed} - {nameof(IsDirectory)} : {(initProps ? (IsDirectory.v?.ToString() ?? IsDirectory.ex?.ToString()) : (isDirectory.v?.ToString() ?? isDirectory.ex?.ToString()))} - {nameof(IsFileHandle)} : {(initProps ? (IsFileHandle.v?.ToString() ?? IsFileHandle.ex?.ToString()) : (isFileHandle.v?.ToString() ?? isFileHandle.ex?.ToString()))} - {nameof(IsInvalid)} : {IsInvalid} - {nameof(ObjectAddress)} : {ObjectAddress} (0x{ObjectAddress:X}) - {nameof(ObjectName)} : {(initProps ? (ObjectName.v ?? ObjectName.ex?.ToString()) : (objectName.v ?? objectName.ex?.ToString()))} - {nameof(ProcessId)} : {ProcessId} + {nameof(CreatorBackTraceIndex)} : {CreatorBackTraceIndex} + {nameof(FileFullPath)} : {(initProps ? (FileFullPath.v ?? FileFullPath.ex?.ToString()) : (fileFullPath.v ?? fileFullPath.ex?.ToString()))} + {nameof(FileHandleType)} : {(initProps ? (FileHandleType.v?.ToString() ?? FileFullPath.ex?.ToString()) : (fileHandleType.v?.ToString() ?? fileHandleType.ex?.ToString()))} + {nameof(FileName)} : {(initProps ? (FileName.v ?? FileName.ex?.ToString()) : (fileName.v ?? fileName.ex?.ToString()))} + {nameof(GrantedAccess)} : {SysHandleEx.GrantedAccessString} + {nameof(HandleObjectType)} : {(initProps ? (HandleObjectType.v ?? HandleObjectType.ex?.ToString()) : (handleObjectType.v ?? handleObjectType.ex?.ToString()))} + {nameof(HandleValue)} : {HandleValue} (0x{HandleValue:X}) + {nameof(IsClosed)} : {IsClosed} + {nameof(IsDirectory)} : {(initProps ? (IsDirectory.v?.ToString() ?? IsDirectory.ex?.ToString()) : (isDirectory.v?.ToString() ?? isDirectory.ex?.ToString()))} + {nameof(IsFileHandle)} : {(initProps ? (IsFileHandle.v?.ToString() ?? IsFileHandle.ex?.ToString()) : (isFileHandle.v?.ToString() ?? isFileHandle.ex?.ToString()))} + {nameof(IsInvalid)} : {IsInvalid} + {nameof(ObjectAddress)} : {ObjectAddress} (0x{ObjectAddress:X}) + {nameof(ObjectName)} : {(initProps ? (ObjectName.v ?? ObjectName.ex?.ToString()) : (objectName.v ?? objectName.ex?.ToString()))} + {nameof(ProcessId)} : {ProcessId} {nameof(ProcessInfo.ParentId)} : {(initProcessInfo ? (ProcessInfo.ParentId.v?.ToString() ?? ProcessInfo.ParentId.ex?.ToString()) : (processInfo?.ParentId.v?.ToString() ?? processInfo?.ParentId.ex?.ToString() ?? string.Empty))} {nameof(ProcessInfo.ProcessCommandLine)} : {(initProcessInfo ? (ProcessInfo.ProcessCommandLine.v ?? ProcessInfo.ProcessCommandLine.ex?.ToString()) : (processInfo?.ProcessCommandLine.v ?? processInfo?.ProcessCommandLine.ex?.ToString() ?? string.Empty))} {nameof(ProcessInfo.ProcessMainModulePath)} : {(initProcessInfo ? (ProcessInfo.ProcessMainModulePath.v ?? ProcessInfo.ProcessMainModulePath.ex?.ToString()) : (processInfo?.ProcessMainModulePath.v ?? processInfo?.ProcessMainModulePath.ex?.ToString() ?? string.Empty))} {nameof(ProcessInfo.ProcessName)} : {(initProcessInfo ? (ProcessInfo.ProcessName.v ?? ProcessInfo.ProcessName.ex?.ToString()) : (processInfo?.ProcessName.v ?? processInfo?.ProcessName.ex?.ToString() ?? string.Empty))} {nameof(ProcessInfo.ProcessProtection)} : {(initProcessInfo ? (ProcessInfo.ProcessProtection.v?.ToString() ?? ProcessInfo.ProcessProtection.ex?.ToString()) : (processInfo?.ProcessProtection.v?.ToString() ?? processInfo?.ProcessProtection.ex?.ToString() ?? string.Empty))} - {nameof(ExceptionLog)} : ... + {nameof(ExceptionLog)} : ... " + string.Concat(exLog); } catch (Exception ex) From 008788023be67f2d63a08285558b87abbf16198a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 20:42:01 -0700 Subject: [PATCH 289/306] fix: subtract FileNameLength size from buffer length --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 6613211..3c445db 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -422,7 +422,7 @@ public unsafe (string? v, Exception? ex) FileNameInfo { Buffer = new PWSTR((char*)(((FILE_NAME_INFO*)safeBuffer.DangerousGetHandle()) + sizeof(int))), Length = (ushort)fni.FileNameLength, - MaximumLength = (ushort)bufferLength + MaximumLength = (ushort)(bufferLength - sizeof(int)) }; /* The string conversion copies the data to a new string in the managed heap before freeing safeBuffer and leaving this context. */ From 3766142eb6a6d3b409180db42caeb989e846c61d Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 20:42:25 -0700 Subject: [PATCH 290/306] refactor: remove unnecessary parameter --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 2d4b998..cfc3e02 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -465,7 +465,7 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection try { - return processMainModulePath = (GetFullProcessImageName((uint)ProcessId), null); + return processMainModulePath = (GetFullProcessImageName(), null); } catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_GEN_FAILURE) { @@ -518,7 +518,7 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection /// The process handle is invalid /// QueryFullProcessImageName failed. See Exception message for details. /// Failed to open process handle for processId; - private unsafe string GetFullProcessImageName(uint processId) + private unsafe string GetFullProcessImageName() { //TODO: inline uint size = 260 + 1; From 500c7ec4d4c9b660d45edd7e817d6e3fc105095f Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 20:43:58 -0700 Subject: [PATCH 291/306] fix: remove trailing null characters from ProcessMainModulePath refactor: remove unnecessary array buffer --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index cfc3e02..b5492f4 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -522,7 +522,6 @@ private unsafe string GetFullProcessImageName() { //TODO: inline uint size = 260 + 1; - char[] array = new char[size]; const string errUnableMsg = "Unable to query " + nameof(ProcessMainModulePath) + "; "; if (ProcessHandle.v is null) @@ -533,8 +532,7 @@ private unsafe string GetFullProcessImageName() SafeBuffer buffer = new(numElements: size); if (QueryFullProcessImageName(ProcessHandle.v, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, lpExeName: buffer.DangerousGetHandle(), ref size)) { - buffer.ReadArray(0, array, 0, (int)size); - return new string(array); + return new string((char*)buffer.DangerousGetHandle(), 0, (int)size); } else if (buffer.ByteLength < size) { @@ -545,8 +543,7 @@ private unsafe string GetFullProcessImageName() buffer.DangerousGetHandle(), ref size)) { - buffer.ReadArray(0, array, 0, (int)size); - return new string(array); + return new string((char*)buffer.DangerousGetHandle(), 0, (int)size); } } // this constructor calls Marshal.GetLastPInvokeError() and Marshal.GetPInvokeErrorMessage(int) From 76a9c298a8b47528a5cb50b0e382ddd1a5612f03 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 21:09:03 -0700 Subject: [PATCH 292/306] fix: correctly init, read FileNameInfo buffer Wrong buffer size. Wrong offsets. Redundant operations. --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 3c445db..777933d 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -412,21 +412,16 @@ public unsafe (string? v, Exception? ex) FileNameInfo _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &fni, (uint)Marshal.SizeOf(fni)); /** Get FileNameInfo */ - int bufferLength = (int)(fni.FileNameLength + Marshal.SizeOf(fni)); + int bufferLength = Marshal.SizeOf(fni.FileNameLength) + (int)fni.FileNameLength; using SafeBuffer safeBuffer = new(numBytes: (nuint)bufferLength); if (!GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, (FILE_NAME_INFO*)safeBuffer.DangerousGetHandle(), (uint)bufferLength)) return fileNameInfo = (null, new Exception(errFailedMsg + "GetFileInformationByHandleEx encountered an error.", new Win32Exception())); - UNICODE_STRING str = new() - { - Buffer = new PWSTR((char*)(((FILE_NAME_INFO*)safeBuffer.DangerousGetHandle()) + sizeof(int))), - Length = (ushort)fni.FileNameLength, - MaximumLength = (ushort)(bufferLength - sizeof(int)) - }; + string tmp = new((char*)(safeBuffer.DangerousGetHandle() + sizeof(uint)), 0, (int)fni.FileNameLength / 2); /* The string conversion copies the data to a new string in the managed heap before freeing safeBuffer and leaving this context. */ - return fileNameInfo = ((string)str, null); + return fileNameInfo = (tmp, null); } else { From abae5f7804fc00e9afbd5b103350c494b9c440db Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 21:11:59 -0700 Subject: [PATCH 293/306] docs: add FileNameInfo summary --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 777933d..0c780bd 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -390,6 +390,11 @@ void getFileOrDirectoryName(string path) } } + /// + /// Returns a path without the drive letter and separator e.g. "\\Repos\\BinToss\\deadlock-dotnet-diagnostics\\deadlock-diagnostics"
+ /// -OR-
+ /// The exception detailing what prevented the operation from starting or what caused it to fail. + ///
public unsafe (string? v, Exception? ex) FileNameInfo { get From 491df6c9fe219201d1ae4e47d3a0948eb2963ebf Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Sun, 7 May 2023 21:16:22 -0700 Subject: [PATCH 294/306] refactor: inline temporary variable --- deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 0c780bd..7c87572 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -423,10 +423,8 @@ public unsafe (string? v, Exception? ex) FileNameInfo if (!GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, (FILE_NAME_INFO*)safeBuffer.DangerousGetHandle(), (uint)bufferLength)) return fileNameInfo = (null, new Exception(errFailedMsg + "GetFileInformationByHandleEx encountered an error.", new Win32Exception())); - string tmp = new((char*)(safeBuffer.DangerousGetHandle() + sizeof(uint)), 0, (int)fni.FileNameLength / 2); - /* The string conversion copies the data to a new string in the managed heap before freeing safeBuffer and leaving this context. */ - return fileNameInfo = (tmp, null); + return fileNameInfo = (new string((char*)(safeBuffer.DangerousGetHandle() + sizeof(uint)), 0, (int)fni.FileNameLength / 2), null); } else { From d32b6c8d192d885680ae57bc2a1a44b2b5078f9a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 11 May 2023 00:58:00 -0700 Subject: [PATCH 295/306] feat: add handle-returning DuplicateHandle overload refactor: throw exception when OpenProcess_SafeHandle fails refactor: use global::PInvoke.Win32Exception instead of built-in type refactor: remove unnecessary usings I'm sorry I didn't commit every thing separately. TODO: Dup handles if not owned by current process --- deadlock-dotnet-sdk/Domain/FileLockerEx.cs | 1 - .../Domain/ProcessInfo.ProcessQueryHandle.cs | 15 ++--- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 29 +++++---- .../Domain/SafeFileHandleEx.cs | 24 ++++++-- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 2 + deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs | 59 +++++++++++++++++-- 6 files changed, 95 insertions(+), 35 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs index 7f38b47..9b56308 100644 --- a/deadlock-dotnet-sdk/Domain/FileLockerEx.cs +++ b/deadlock-dotnet-sdk/Domain/FileLockerEx.cs @@ -1,5 +1,4 @@ using System.ComponentModel; -using System.Text; namespace deadlock_dotnet_sdk.Domain { diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs index 10c3619..8a87082 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs @@ -1,7 +1,6 @@ using Microsoft.Win32.SafeHandles; using Windows.Win32.System.Threading; using static Windows.Win32.PInvoke; -using Win32Exception = System.ComponentModel.Win32Exception; namespace deadlock_dotnet_sdk.Domain; @@ -24,21 +23,15 @@ public ProcessQueryHandle(SafeProcessHandle processHandle, PROCESS_ACCESS_RIGHTS /// /// /// A ProcessQueryHandle wrapping a SafeProcessHandle and the requested access rights. - /// Failed to open handle. The process might not exist, access was denied, or an unknown error occurred.
- /// - /// If processId is 0, error code is ERROR_INVALID_PARAMETER
- /// If process is System (4), CRSS, or similarly protected processes, error code is ERROR_ACCESS_DENIED
- ///
- ///
+ /// Failed to open process (ID ) with access rights ''. + /// Cannot open handle for process (ID ). + /// Unrecognized error occurred when attempting to open handle for process with ID . /// /// - If processId == Process.GetCurrentProcess().Id, use Process.GetCurrentProcess().SafeHandle property instead. /// - If Windows.Win32.PInvoke.IsDebugModeEnabled() is true, the requested access is granted regardless of the security descriptor. See GetSecurityInfo(); /// public static ProcessQueryHandle OpenProcessHandle(int processId, PROCESS_ACCESS_RIGHTS accessRights) - { - var h = OpenProcess_SafeHandle(accessRights, false, (uint)processId); - return h is null ? throw new Win32Exception() : new(h, accessRights); - } + => new(OpenProcess_SafeHandle(accessRights, false, (uint)processId), accessRights); public static implicit operator SafeProcessHandle(ProcessQueryHandle v) => v.Handle; } diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index b5492f4..89427b5 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -394,17 +394,24 @@ public unsafe (PS_PROTECTION? v, Exception? ex) ProcessProtection { if (processProtection is (null, null)) { - const uint ProcessProtectionInformation = 61; // Retrieves a BYTE value indicating the type of protected process and the protected process signer. - PS_PROTECTION protection = default; - uint retLength = 0; - - using SafeProcessHandle? hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, (uint)ProcessId); - NTSTATUS status = NtQueryInformationProcess(hProcess, (PROCESSINFOCLASS)ProcessProtectionInformation, &protection, 1, ref retLength); - - if (status.Code is not Code.STATUS_SUCCESS) - return processProtection = (null, new NTStatusException(status)); - else - return processProtection = (protection, null); + try + { + //TODO: use ProcessInfo.ProcessHandle + const uint ProcessProtectionInformation = 61; // Retrieves a BYTE value indicating the type of protected process and the protected process signer. + PS_PROTECTION protection = default; + uint retLength = 0; + using SafeProcessHandle hProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, (uint)ProcessId); + NTSTATUS status = NtQueryInformationProcess(hProcess, (PROCESSINFOCLASS)ProcessProtectionInformation, &protection, 1, ref retLength); + + if (status.Code is not Code.STATUS_SUCCESS) + return processProtection = (null, new NTStatusException(status)); + else + return processProtection = (protection, null); + } + catch (Exception ex) + { + return processProtection = (null, ex); + } } else { diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index 7c87572..c004d9d 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -206,6 +206,7 @@ public unsafe (string? v, Exception? ex) FileFullPath const uint LengthIndicatesError = 0; // Try without duplicating. If it fails, try duplicating the handle. + // TODO: only try is ProcessId == this process try { GETFINALPATHNAMEBYHANDLE_FLAGS flags = IsFilePathRemote.v is true ? GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_OPENED : GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED; @@ -249,12 +250,23 @@ public unsafe (string? v, Exception? ex) FileFullPath } /// Return the normalized drive name. This is the default. - using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); - if (processHandle is null || processHandle?.IsInvalid == true) - throw new Win32Exception(); - - if (!DuplicateHandle(processHandle, this, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) - throw new Win32Exception(); + SafeFileHandle dupHandle; + try + { + // throws UnauthorizedAccessException, ArgumentException, or Exception + using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); + // todo: use handle-returning overload + if (!DuplicateHandle(processHandle, this, Process.GetCurrentProcess().SafeHandle, out dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) + throw new Win32Exception(); + } + catch (Win32Exception ex) + { + return fileFullPath = (null, new Exception(errFailedMsg + "Failed to duplicate handle to this process.", ex)); + } + catch (Exception ex) + { + return fileFullPath = (null, new Exception(errFailedMsg + "Failed to open a process handle.", ex)); + } length = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index cde6df8..c338314 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -106,6 +106,8 @@ private bool GetIsClosed() { try { + SafeProcessHandle sourceProcessHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); + SafeFileHandle duplicate = DuplicateHandle(sourceProcessHandle, this, Process.GetCurrentProcess().SafeHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_CLOSE_SOURCE | DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS) HANDLE_FLAGS flags = GetHandleInformation(this); } catch (Exception ex) when ( diff --git a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs index e0aee51..b489e49 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/PInvoke.cs @@ -1,19 +1,52 @@ /// This file supplements code generated by CsWin32 -using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Win32.SafeHandles; +using PInvoke; using Windows.Win32.Foundation; using Windows.Win32.Security; using Windows.Win32.System.Threading; using MemInfo32 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION32; using MemInfo64 = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION64; +using NTSTATUS = Windows.Win32.Foundation.NTSTATUS; +using Win32Exception = PInvoke.Win32Exception; namespace Windows.Win32; static partial class PInvoke { + /// + /// , , or is closed or invalid. If and are known to be good, then is closed or invalid. + /// failed with an unrecognized error. + /// + public static SafeFileHandle DuplicateHandle( + SafeProcessHandle hSourceProcessHandle, + SafeHandle hSourceHandle, + SafeProcessHandle hTargetProcessHandle, + uint dwDesiredAccess, + bool bInheritHandle, + DUPLICATE_HANDLE_OPTIONS dwOptions) + { + if (DuplicateHandle( + hSourceProcessHandle, + hSourceHandle, + hTargetProcessHandle, + out SafeFileHandle lpTargetHandle, + dwDesiredAccess, + (BOOL)bInheritHandle, + dwOptions)) + { + return lpTargetHandle; + } + + Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError(); + if (err is Win32ErrorCode.ERROR_INVALID_HANDLE) + throw new ArgumentException("sourceProcessHandle, sourceHandle, or targetProcessHandle is closed or invalid.", new Win32Exception(err)); + + throw new Win32Exception(err, $"P/Invoke DuplicateHandle failed with params (sourceProcessHandle: {hSourceProcessHandle.DangerousGetHandle()}, sourceHandle: {hSourceHandle.DangerousGetHandle()}, desiredAccess: {dwDesiredAccess}, options: {dwOptions}). {err.GetMessage()}"); + } + /// /// Check if the current process has been granted Debugger privileges (usually via Process.EnterDebugMode()) /// @@ -83,7 +116,7 @@ public unsafe static extern NTSTATUS NtDuplicateObject( HANDLE SourceHandle, [Optional] HANDLE TargetProcessHandle, [Optional] out HANDLE* TargetHandle, - global::PInvoke.Kernel32.ACCESS_MASK DesiredAccess, + Kernel32.ACCESS_MASK DesiredAccess, uint HandleAttributes, uint Options ); @@ -161,13 +194,27 @@ public static extern unsafe NTSTATUS NtWow64ReadVirtualMemory64( [Out] ulong* NumberOfBytesRead ); + /// An open handle to the specified process. + /// Failed to open process (ID ) with access rights ''. + /// Cannot open handle for process (ID ). + /// The system may have failed to find a process with ID . + /// Unrecognized error occurred when attempting to open handle for process with ID . /// - /// A SafeProcessHandle to the [SupportedOSPlatform("windows5.1.2600")] - public static unsafe SafeProcessHandle OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS dwDesiredAccess, bool bInheritHandle, uint dwProcessId) + public static SafeProcessHandle OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS desiredAccess, bool inheritHandle, uint processId) { - HANDLE __result = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); - return new SafeProcessHandle(__result, ownsHandle: true); + HANDLE h = OpenProcess(desiredAccess, inheritHandle, processId); + if (h.Value != IntPtr.Zero) + return new SafeProcessHandle(h, ownsHandle: true); + + Win32Exception ex = new(); + throw ex.NativeErrorCode switch + { + Win32ErrorCode.ERROR_ACCESS_DENIED => new UnauthorizedAccessException($"Failed to open process (ID {processId}) with access rights '{desiredAccess}'.", ex), + Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException($"Cannot open handle for process (ID {processId}).", ex), + Win32ErrorCode.ERROR_SUCCESS => new InvalidOperationException($"The system may have failed to find a process with ID {processId}.", ex), + _ => new Exception($"Unrecognized error occurred when attempting to open handle for process with ID {processId}.", ex), + }; } /// From 007e96e4fd4a1ccc6bce31c84993c9d5471f78a1 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 11 May 2023 00:58:43 -0700 Subject: [PATCH 296/306] refactor: define Handle-void* conversion --- deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE.cs b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE.cs index de1b2d1..6e4565e 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/Foundation/HANDLE.cs @@ -11,6 +11,8 @@ partial struct HANDLE : IComparable public static implicit operator HANDLE(nint v) => new(v); public static implicit operator nint(HANDLE v) => v.Value; + public static unsafe explicit operator HANDLE(void* v) => new((IntPtr)v); + public static unsafe implicit operator void*(HANDLE v) => (void*)v.Value; /// /// Close the handle via the CloseHandle function /// From 7c6ead92d58b30748c35c74b35c9082a8d776f88 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 11 May 2023 01:02:31 -0700 Subject: [PATCH 297/306] refactor: add overload SysHandleEx.GetHandleInfo(SafeProcessHandle) Still unused, but may be useful at a later date. --- .../SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs index f00645e..051e404 100644 --- a/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs +++ b/deadlock-dotnet-sdk/Windows.Win32/SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.cs @@ -84,25 +84,27 @@ public unsafe string GetHandleObjectType() /// /// Get the current HANDLE_FLAGS of this handle if it is still open. /// - /// + /// The handle's attributes as HANDLE_FLAGS + /// Failed to open process (ID ) with access right ''. + /// Cannot open handle for process (ID ). + /// Unrecognized error occurred when attempting to open handle for process with ID . /// Failed to get handle information -OR- failed to open process to duplicate handle. public unsafe HANDLE_FLAGS GetHandleInfo() { - try + if (ProcessId == Environment.ProcessId) { using SafeObjectHandle hObject = new(HandleValue, false); return GetHandleInformation(hObject); } - catch (Exception ex) - { - Debug.Print(ex.ToString()); - } - // If passing the source handle failed, try passing a duplicate instead + using SafeProcessHandle sourceProcessHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, (uint)UniqueProcessId); + return GetHandleInfo(sourceProcessHandle); + } - using SafeProcessHandle sourceProcess = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, (uint)UniqueProcessId) ?? throw new Win32Exception(); - using SafeObjectHandle safeHandleValue = new(HandleValue, false); - DuplicateHandle(sourceProcess, safeHandleValue, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, default, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); + public unsafe HANDLE_FLAGS GetHandleInfo(SafeProcessHandle sourceProcessHandle) + { + using SafeObjectHandle sourceHandle = new(HandleValue, false); + DuplicateHandle(sourceProcessHandle, sourceHandle, Process.GetCurrentProcess().SafeHandle, out SafeFileHandle dupHandle, default, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); return GetHandleInformation(dupHandle); } } From 672cf785b9057802f5d2b606f3157537a691f5d2 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 11 May 2023 01:20:03 -0700 Subject: [PATCH 298/306] feat: add property DuplicateHandle refactor: split IsClosed to BREAK: IsClosed is now a tuple This is the noun usage and pronunciation of "duplicate". --- deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 130 ++++++++++++++++----- 1 file changed, 103 insertions(+), 27 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index c338314..e34dc15 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -25,8 +25,9 @@ namespace deadlock_dotnet_sdk.Domain; public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid { protected const string errHandleClosedMsgSuffix = "The handle is closed."; + protected (SafeFileHandle? v, Exception? ex) duplicateHandle; protected (string? v, Exception? ex) handleObjectType; - private bool isClosed; + private (bool? v, Exception? ex) isClosed; protected (string? v, Exception? ex) objectName; protected ProcessInfo? processInfo; @@ -61,7 +62,9 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { const string errUnableMsg = "Unable to query " + nameof(HandleObjectType) + "; "; const string errFailedMsg = "Failed to query " + nameof(HandleObjectType) + "; "; - if (IsClosed) + if (IsClosed.v is null) + return objectName = (null, new InvalidOperationException(errUnableMsg + "Failed to determine if the handle is still open.")); + if (IsClosed.v is true) return handleObjectType = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); var (v, ex) = ProcessInfo.ProcessProtection; if (v is null) @@ -94,37 +97,76 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals /// /// (non-persistent) Pass the handle to GetHandleInformation and check for ERROR_INVALID_HANDLE to determine if the handle is open or closed. /// - public new bool IsClosed - { - get - { - return isClosed ? isClosed : (isClosed = GetIsClosed()); - } - } + public new (bool? v, Exception? ex) IsClosed => isClosed.v is true ? isClosed : (isClosed = GetIsClosed()); - private bool GetIsClosed() + private (bool?, Exception?) GetIsClosed() { - try - { - SafeProcessHandle sourceProcessHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); - SafeFileHandle duplicate = DuplicateHandle(sourceProcessHandle, this, Process.GetCurrentProcess().SafeHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_CLOSE_SOURCE | DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS) - HANDLE_FLAGS flags = GetHandleInformation(this); - } - catch (Exception ex) when ( - (ex is Win32Exception inWin32Exception && inWin32Exception.NativeErrorCode is (int)Win32ErrorCode.ERROR_INVALID_HANDLE) - || (ex is PInvoke.Win32Exception piWin32Exception && piWin32Exception.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_HANDLE)) + const string errFailedMsg = "Failed to query " + nameof(IsClosed) + "; "; + const string errFailedDupMsg = errFailedMsg + "Failed to duplicate handle."; + string errFailedOpenProcMsg = $"{errFailedMsg}Failed to open handle to process {ProcessId}."; + SafeProcessHandle? hSourceProcessHandle = null; + SafeProcessHandle hTargetProcessHandle = Process.GetCurrentProcess().SafeHandle; + SafeFileHandle? duplicate = null; + + if (ProcessId != Environment.ProcessId) { - return true; + /** Open Process Handle */ + try + { + hSourceProcessHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); + } + catch (Exception ex) + { + return (null, new Exception(errFailedOpenProcMsg, ex)); + } + + /** Duplicate handle to this process */ + try + { + duplicate = DuplicateHandle(hSourceProcessHandle, this, hTargetProcessHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); + } + catch (ArgumentException) when (!(hSourceProcessHandle.IsInvalid || hTargetProcessHandle.IsInvalid)) // if both are valid... + { + return (true, null); // ...then the current handle is invalid + } + catch (Exception ex) + { + return (null, new Exception(errFailedDupMsg, ex)); + } + finally + { + hSourceProcessHandle?.Dispose(); + } + + /** GetHandleInformation */ + } - catch (Exception ex) + else { - Console.Error.WriteLine($"GetIsClosed failed; {ex}"); - Trace.TraceError(ex.ToString()); + try + { + HANDLE_FLAGS flags = GetHandleInformation(this); + } + catch (Exception ex) when ( + (ex is Win32Exception inWin32Exception && inWin32Exception.NativeErrorCode is (int)Win32ErrorCode.ERROR_INVALID_HANDLE) + || (ex is PInvoke.Win32Exception piWin32Exception && piWin32Exception.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_HANDLE)) + { + return (true, null); + } + catch (Exception ex) + { + return (null, new Exception(errFailedMsg + "GetHandleInformation encountered an error.", ex)); + } + finally + { + hSourceProcessHandle?.Dispose(); + duplicate?.Dispose(); + } } - return false; + return (false, null); } - public new bool IsInvalid => IsClosed || base.IsInvalid || base.IsClosed; + public new bool IsInvalid => IsClosed.v is true || base.IsInvalid || base.IsClosed; /// /// The name of the object e.g. "\\Device\\HarddiskVolume4\\Repos\\BinToss\\deadlock-dotnet-diagnostics\\deadlock-diagnostics" or "\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\Nls\\Sorting\\Versions" @@ -143,7 +185,9 @@ public unsafe (string? v, Exception? ex) ObjectName { const string errUnableMsg = "Unable to query " + nameof(ObjectName) + "; "; const string errFailedMsg = "Failed to query " + nameof(ObjectName) + "; "; - if (IsClosed) + if (IsClosed.v is null) + return objectName = (null, new InvalidOperationException(errUnableMsg + "Failed to determine if the handle is still open.")); + if (IsClosed.v is true) return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); var (v, ex) = ProcessInfo.ProcessProtection; // I'm assuming process protection prohibits access. I've not tested it. @@ -186,6 +230,38 @@ public unsafe (string? v, Exception? ex) ObjectName public ProcessInfo ProcessInfo => processInfo ??= NativeMethods.Processes.GetProcessById((int)(uint)SysHandleEx.UniqueProcessId); + /// + /// A duplicated handle for handle info queries. + /// + /// CloseSourceHandle makes a separate duplicate with different permissions. + public (SafeFileHandle? v, Exception? ex) DuplicateHandle + { + get + { + if (duplicateHandle is (null, null)) + { + const string errFailedMsg = "Failed to duplicate handle; "; + const string errUnableMsg = "Unable to duplicate handle; "; + if (ProcessInfo.ProcessHandle.v is null) + return duplicateHandle = (null, new InvalidOperationException(errUnableMsg + "Failed to open source process handle.", ProcessInfo.ProcessHandle.ex)); + + try + { + SafeFileHandle v = DuplicateHandle(ProcessInfo.ProcessHandle.v, this, Process.GetCurrentProcess().SafeHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); + return duplicateHandle = (v, null); + } + catch (Exception ex) + { + return duplicateHandle = (null, new Exception(errFailedMsg + "The DuplicateHandle function encountered an error.", ex)); + } + } + else + { + return duplicateHandle; + } + } + } + /// A list of exceptions thrown by constructors and other methods of this class. /// Use List's methods (e.g. Add) to modify this list. public List ExceptionLog { get; } = new(); @@ -254,7 +330,7 @@ public bool CloseSourceHandle(bool removeCloseProtection) protected override bool ReleaseHandle() { Close(); - return IsClosed; + return IsClosed.v is true; } #endregion Methods From b32aa35d4ca8def00b34a86fcb9f963f37ab9635 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 11 May 2023 21:43:56 -0700 Subject: [PATCH 299/306] refactor: rename ProcessQueryHandle to SafeProcessHandleEx refactor: open one temporary process handle per requested access right to determine which ones can be acquired refactor: try opening process handle with PROCESS_DUP_HANDLE to duplicate other process's owned handles --- ....cs => ProcessInfo.SafeProcessHandleEx.cs} | 8 +++--- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 26 ++++++++++++++++--- 2 files changed, 26 insertions(+), 8 deletions(-) rename deadlock-dotnet-sdk/Domain/{ProcessInfo.ProcessQueryHandle.cs => ProcessInfo.SafeProcessHandleEx.cs} (82%) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.SafeProcessHandleEx.cs similarity index 82% rename from deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs rename to deadlock-dotnet-sdk/Domain/ProcessInfo.SafeProcessHandleEx.cs index 8a87082..19a0b8d 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.ProcessQueryHandle.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.SafeProcessHandleEx.cs @@ -6,9 +6,9 @@ namespace deadlock_dotnet_sdk.Domain; public partial class ProcessInfo { - public class ProcessQueryHandle + public class SafeProcessHandleEx { - public ProcessQueryHandle(SafeProcessHandle processHandle, PROCESS_ACCESS_RIGHTS accessRights) + public SafeProcessHandleEx(SafeProcessHandle processHandle, PROCESS_ACCESS_RIGHTS accessRights) { Handle = processHandle; AccessRights = accessRights; @@ -30,9 +30,9 @@ public ProcessQueryHandle(SafeProcessHandle processHandle, PROCESS_ACCESS_RIGHTS /// - If processId == Process.GetCurrentProcess().Id, use Process.GetCurrentProcess().SafeHandle property instead. /// - If Windows.Win32.PInvoke.IsDebugModeEnabled() is true, the requested access is granted regardless of the security descriptor. See GetSecurityInfo(); /// - public static ProcessQueryHandle OpenProcessHandle(int processId, PROCESS_ACCESS_RIGHTS accessRights) + public static SafeProcessHandleEx OpenProcessHandle(int processId, PROCESS_ACCESS_RIGHTS accessRights) => new(OpenProcess_SafeHandle(accessRights, false, (uint)processId), accessRights); - public static implicit operator SafeProcessHandle(ProcessQueryHandle v) => v.Handle; + public static implicit operator SafeProcessHandle(SafeProcessHandleEx v) => v.Handle; } } diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 89427b5..6a395d7 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -19,12 +20,13 @@ public partial class ProcessInfo { private bool canGetQueryLimitedInfoHandle; private bool canGetReadMemoryHandle; + private bool canDuplicateHandles; private (bool? v, Exception? ex) is32BitEmulatedProcess; private (int? v, Exception?) parentId; private (ProcessAndHostOSArch? arch, Exception? ex) processAndHostOSArch; private (ProcessBasicInformation? v, Exception? ex) processBasicInformation; private (string? v, Exception? ex) processCommandLine; - private (ProcessQueryHandle? v, Exception? ex) processHandle; + private (SafeProcessHandleEx? v, Exception? ex) processHandle; private (string? v, Exception? ex) processMainModulePath; private (string? v, Exception? ex) processName; private (PS_PROTECTION? v, Exception? ex) processProtection; @@ -138,7 +140,7 @@ public ProcessInfo(Process process) } } - public (ProcessQueryHandle? v, Exception? ex) ProcessHandle + public (SafeProcessHandleEx? v, Exception? ex) ProcessHandle { get { @@ -148,11 +150,27 @@ public ProcessInfo(Process process) // We can't lookup the ProcessProtection without opening a process handle to check the process protection. //PROCESS_ACCESS_RIGHTS access = ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected ? PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION : PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ; + PROCESS_ACCESS_RIGHTS[] AccessRightsRequested = { PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ, PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE }; + ConcurrentBag AccessRightsGranted = new(); + var parallelLoopResult = Parallel.ForEach(AccessRightsRequested, accessRight => + { + try + { + SafeProcessHandleEx.OpenProcessHandle(processId, accessRight); + AccessRightsGranted.Add(accessRight); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Failed to open a temporary process handle to check permissible access rights. {ex}"); + } + }); + try { - ProcessQueryHandle h = ProcessQueryHandle.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); + SafeProcessHandleEx h = SafeProcessHandleEx.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ | PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE); canGetQueryLimitedInfoHandle = true; canGetReadMemoryHandle = true; + canDuplicateHandles = true; return processHandle = (h, null); } catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) @@ -163,7 +181,7 @@ public ProcessInfo(Process process) try { - ProcessQueryHandle h = ProcessQueryHandle.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); + SafeProcessHandleEx h = SafeProcessHandleEx.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); canGetQueryLimitedInfoHandle = true; return processHandle = (h, null); } From a5c665e6e7b7eb0e3fd0cfeffdac18595f3a4ac9 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 11 May 2023 21:45:31 -0700 Subject: [PATCH 300/306] refactor: aggregate granted process access rights to open the SafeProcessHandleEx --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 6a395d7..f2d7d9c 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -167,7 +167,7 @@ public ProcessInfo(Process process) try { - SafeProcessHandleEx h = SafeProcessHandleEx.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ | PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE); + SafeProcessHandleEx h = SafeProcessHandleEx.OpenProcessHandle(ProcessId, AccessRightsGranted.Aggregate((a, b) => a |= b)); canGetQueryLimitedInfoHandle = true; canGetReadMemoryHandle = true; canDuplicateHandles = true; From b158c423fba5384d303402cfb225981950675df5 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Thu, 11 May 2023 22:29:26 -0700 Subject: [PATCH 301/306] refactor: replace ProcessHandle's consecutive try-catch statements with Parallel.ForEach docs: document ProcessInfo.ProcessHandle summary, value --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 58 +++++++++-------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index f2d7d9c..3be68fb 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -140,13 +140,24 @@ public ProcessInfo(Process process) } } + /// + /// A persistent process handle with all (or some of) the rights we need for various operations. See ProcessHandle.v.AccessRights for granted access rights.
+ /// -OR-
+ /// An Exception detailing why the 'get' operation failed. + ///
+ /// + /// If successful, a SafeProcessHandle with its AccessRights property assigned the PROCESS_ACCESS_RIGHTS used to open it. Else...
+ /// Unable to open the process with any of requested access rights.
+ ///
+ ///
public (SafeProcessHandleEx? v, Exception? ex) ProcessHandle { get { if (processHandle is (null, null)) { - const string errUnableMsg = "Unable to open handle; "; + const string errUnableMsg = "Unable to open process handle; "; + const string errFailedMsg = "Failed to open process handle; "; // We can't lookup the ProcessProtection without opening a process handle to check the process protection. //PROCESS_ACCESS_RIGHTS access = ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected ? PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION : PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ; @@ -159,51 +170,28 @@ public ProcessInfo(Process process) SafeProcessHandleEx.OpenProcessHandle(processId, accessRight); AccessRightsGranted.Add(accessRight); } - catch (Exception ex) + catch (Exception ex) // we don't want exceptions to break the loop. They just mean we can't use an access right. { Console.Error.WriteLine($"Failed to open a temporary process handle to check permissible access rights. {ex}"); } }); + if (AccessRightsGranted.Count is 0) + return processHandle = (null, new UnauthorizedAccessException(errUnableMsg + "None of the following rights were granted: " + AccessRightsRequested.Aggregate((a, b) => a | b))); + try { - SafeProcessHandleEx h = SafeProcessHandleEx.OpenProcessHandle(ProcessId, AccessRightsGranted.Aggregate((a, b) => a |= b)); - canGetQueryLimitedInfoHandle = true; - canGetReadMemoryHandle = true; - canDuplicateHandles = true; + PROCESS_ACCESS_RIGHTS authorizedAccess = AccessRightsGranted.Aggregate((a, b) => a | b); + SafeProcessHandleEx h = SafeProcessHandleEx.OpenProcessHandle(ProcessId, authorizedAccess); + canGetQueryLimitedInfoHandle = (authorizedAccess & PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) is not 0; + canGetReadMemoryHandle = (authorizedAccess & PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ) is not 0; + canDuplicateHandles = (authorizedAccess & PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE) is not 0; return processHandle = (h, null); } - catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) - { - // Before assuming anything, try without PROCESS_VM_READ. Without it, we don't need Debug privilege, but the PEB and all of its recursive members (e.g. Command Line) will be unavailable. - const string exAccessMsg = errUnableMsg + " The requested permissions were denied."; - const string exPermsFirst = "\r\nFirst attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION) + ", " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); - - try - { - SafeProcessHandleEx h = SafeProcessHandleEx.OpenProcessHandle(ProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); - canGetQueryLimitedInfoHandle = true; - return processHandle = (h, null); - } - catch (Win32Exception ex2) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_ACCESS_DENIED) - { - // Debug Mode could not be enabled? Was SE_DEBUG_NAME denied to user or is current process not elevated? - const string exPermsSecond = "\r\nSecond attempt's requested permissions: " + nameof(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION); - return processHandle = (null, new UnauthorizedAccessException(exAccessMsg + exPermsFirst + exPermsSecond, ex2)); - } - catch (Exception ex2) - { - return processHandle = (null, new AggregateException(errUnableMsg + " Permissions were denied and an unknown error occurred.", new Exception[] { ex, ex2 })); - } - } - catch (Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_PARAMETER) - { - return processHandle = (null, new ArgumentException(errUnableMsg + " A process with ID " + ProcessId + " could not be found. The process may have exited.", ex)); - } catch (Exception ex) { - // unknown error - return processHandle = (null, new Exception(errUnableMsg + " An unknown error occurred.", ex)); + /* Normally, we'd wrap the exceptions in another to add a contextual message, but the caught exceptions' messages suffice. */ + return processHandle = (null, ex); } } else From 886dbb8231fd021796832e597ca200e372b88938 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 15 May 2023 19:12:24 -0700 Subject: [PATCH 302/306] fix: duplicate source handle when not owned by current process refactor: remove redundant 'duplicate handle' block refactor: finish refactoring IsClosed references When the handle is owned by the current process, DuplicateHandle is a duplicate the the kernel-provided handle-the same as when the handle is owned by a different process. DuplicateHandle will NEVER have DUPLICATE_SOURCE_CLOSE. --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 4 +- .../Domain/SafeFileHandleEx.cs | 165 ++++++------------ deadlock-dotnet-sdk/Domain/SafeHandleEx.cs | 43 +++-- 3 files changed, 80 insertions(+), 132 deletions(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 3be68fb..2867ba2 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -148,7 +148,7 @@ public ProcessInfo(Process process) /// /// If successful, a SafeProcessHandle with its AccessRights property assigned the PROCESS_ACCESS_RIGHTS used to open it. Else...
/// Unable to open the process with any of requested access rights.
- ///
+ ///
//TODO: exception docs ///
public (SafeProcessHandleEx? v, Exception? ex) ProcessHandle { @@ -158,6 +158,8 @@ public ProcessInfo(Process process) { const string errUnableMsg = "Unable to open process handle; "; const string errFailedMsg = "Failed to open process handle; "; + if (ProcessId == Environment.ProcessId) + return processHandle = (new(Process.GetCurrentProcess().SafeHandle, PROCESS_ACCESS_RIGHTS.PROCESS_ALL_ACCESS), null); // We can't lookup the ProcessProtection without opening a process handle to check the process protection. //PROCESS_ACCESS_RIGHTS access = ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected ? PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION : PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ; diff --git a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs index c004d9d..4252eac 100644 --- a/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs @@ -40,7 +40,7 @@ internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHand /// internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx) { - if (IsClosed) + if (IsClosed.v is true) { ExceptionLog.Add(new NullReferenceException("This handle was closed before it was passed to this SafeFileHandleEx constructor.")); return; @@ -79,13 +79,15 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { const string errUnableMsg = "Unable to query " + nameof(IsDirectory) + "; "; const string errFailedMsg = "Failed to query " + nameof(IsDirectory) + "; "; - if (IsClosed) + if (IsClosed.v is true) return isDirectory = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); + if (DuplicateHandle.v is null) + return isDirectory = (null, new InvalidOperationException(errUnableMsg + "A duplicate handle is required for this operation.", DuplicateHandle.ex)); FILE_ATTRIBUTE_TAG_INFO attr = default; bool success; unsafe { - success = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileAttributeTagInfo, &attr, (uint)Marshal.SizeOf(attr)); + success = GetFileInformationByHandleEx(DuplicateHandle.v, FILE_INFO_BY_HANDLE_CLASS.FileAttributeTagInfo, &attr, (uint)Marshal.SizeOf(attr)); } if (success) @@ -108,7 +110,7 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( if (isFileHandle is (null, null)) { const string errUnableMsg = "Unable to query " + nameof(IsFileHandle) + "; "; - if (IsClosed) + if (IsClosed.v is true) return isFileHandle = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); return HandleObjectType.v is "File" ? (isFileHandle = (true, null)) @@ -151,14 +153,17 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base( { const string errUnableMsg = "Unable to query " + nameof(IsFilePathRemote) + "; "; const string errFailedMsg = "Failed to query " + nameof(IsFilePathRemote) + "; "; - if (IsClosed) + if (IsClosed.v is null) return isFilePathRemote = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); + if (DuplicateHandle.v is null) + return isFilePathRemote = (null, new InvalidOperationException(errUnableMsg + "A duplicate handle is required for this operation.", DuplicateHandle.ex)); + Win32ErrorCode err; FILE_REMOTE_PROTOCOL_INFO info; unsafe { - return GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileRemoteProtocolInfo, &info, (uint)Marshal.SizeOf(info)) + return GetFileInformationByHandleEx(DuplicateHandle.v, FILE_INFO_BY_HANDLE_CLASS.FileRemoteProtocolInfo, &info, (uint)Marshal.SizeOf(info)) ? (isFilePathRemote = (true, null)) : (err = (Win32ErrorCode)Marshal.GetLastPInvokeError()) is Win32ErrorCode.ERROR_INVALID_PARAMETER ? (isFilePathRemote = (false, null)) @@ -185,119 +190,59 @@ public unsafe (string? v, Exception? ex) FileFullPath { const string errUnableMsg = "Unable to query " + nameof(FileFullPath) + "; "; const string errFailedMsg = "Failed to query " + nameof(FileFullPath) + "; "; - if (IsClosed) + if (IsClosed.v is null) return fileFullPath = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); + if (DuplicateHandle.v is null) + return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "A duplicate handle is required for this operation.", DuplicateHandle.ex)); + if (ProcessInfo.ProcessProtection.v is null) + return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "Failed to query the process's protection.", ProcessInfo.ProcessProtection.ex)); + if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) + return fileFullPath = (null, new UnauthorizedAccessException(errUnableMsg + "The process is protected.")); + if (HandleObjectType.v is null) + return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "Failed to query handle object type.", HandleObjectType.ex)); + if (IsFileHandle.v is false) + return fileFullPath = (null, new ArgumentException(errUnableMsg + "The handle's object is not a File.", nameof(IsFileHandle))); + if (FileHandleType.v is not FileType.Disk) + return fileFullPath = (null, new ArgumentException(errUnableMsg + "The File object is not a Disk-type File.", nameof(FileHandleType))); + try { - if (ProcessInfo.ProcessProtection.v is null) - return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "Failed to query the process's protection.", ProcessInfo.ProcessProtection.ex)); - if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) - return fileFullPath = (null, new UnauthorizedAccessException(errUnableMsg + "The process is protected.")); - if (HandleObjectType.v is null) - return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "Failed to query handle object type.", HandleObjectType.ex)); - if (IsFileHandle.v is false) - return fileFullPath = (null, new ArgumentException(errUnableMsg + "The handle's object is not a File.", nameof(IsFileHandle))); - if (FileHandleType.v is not FileType.Disk) - return fileFullPath = (null, new ArgumentException(errUnableMsg + "The File object is not a Disk-type File.", nameof(FileHandleType))); - + const uint LengthIndicatesError = 0; uint bufLength = (uint)short.MaxValue; - using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufLength)); uint length = 0; - const uint LengthIndicatesError = 0; + using PWSTR buffer = new((char*)Marshal.AllocHGlobal((int)bufLength)); + GETFINALPATHNAMEBYHANDLE_FLAGS flags = IsFilePathRemote.v is true ? GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_OPENED : GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED; + Win32ErrorCode errorCode = Win32ErrorCode.ERROR_SUCCESS; + length = GetFinalPathNameByHandle(DuplicateHandle.v, buffer, bufLength, flags); - // Try without duplicating. If it fails, try duplicating the handle. - // TODO: only try is ProcessId == this process - try + if (length is not LengthIndicatesError) { - GETFINALPATHNAMEBYHANDLE_FLAGS flags = IsFilePathRemote.v is true ? GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_OPENED : GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED; - Win32ErrorCode errorCode = Win32ErrorCode.ERROR_SUCCESS; - length = GetFinalPathNameByHandle(handle, buffer, bufLength, flags); - - if (length is not LengthIndicatesError) + if (length <= bufLength) { - if (length <= bufLength) - { - return fileFullPath = (buffer.ToString(), null); - } - else if (length > bufLength) - { - using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - if ((length = GetFinalPathNameByHandle(handle, newBuffer, length, flags)) is not LengthIndicatesError) - return fileFullPath = (newBuffer.ToString(), null); - } + return fileFullPath = (buffer.ToString(), null); } - else + else if (length > bufLength) { - errorCode = (Win32ErrorCode)Marshal.GetLastPInvokeError(); - - Trace.TraceError(errorCode.GetMessage()); - - return fileFullPath = (null, errorCode switch - { - // Removable storage, deleted item, network shares, et cetera - Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException(errFailedMsg + $"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(errorCode)), - // unlikely, but possible if system has little free memory - Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException(errFailedMsg + "Insufficient memory to complete the operation.", new Win32Exception(errorCode)), - // possible only if FILE_NAME_NORMALIZED (0) is invalid - Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException(errFailedMsg + "Invalid flags were specified for dwFlags.", new Win32Exception(errorCode)), - _ => new Exception($"{errFailedMsg}An undocumented error ({errorCode}) was returned when querying a file handle for its path.", new Win32Exception(errorCode)) - }); + using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); + if ((length = GetFinalPathNameByHandle(DuplicateHandle.v, newBuffer, length, flags)) is not LengthIndicatesError) + return fileFullPath = (newBuffer.ToString(), null); } } - catch (Exception ex) - { - return fileFullPath = (null, ex); - } - /// Return the normalized drive name. This is the default. - SafeFileHandle dupHandle; - try - { - // throws UnauthorizedAccessException, ArgumentException, or Exception - using SafeProcessHandle processHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); - // todo: use handle-returning overload - if (!DuplicateHandle(processHandle, this, Process.GetCurrentProcess().SafeHandle, out dupHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) - throw new Win32Exception(); - } - catch (Win32Exception ex) - { - return fileFullPath = (null, new Exception(errFailedMsg + "Failed to duplicate handle to this process.", ex)); - } - catch (Exception ex) - { - return fileFullPath = (null, new Exception(errFailedMsg + "Failed to open a process handle.", ex)); - } + errorCode = (Win32ErrorCode)Marshal.GetLastPInvokeError(); - length = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); - - if (length != 0) - { - if (length <= bufLength) - return fileFullPath = (buffer.ToString(), null); + Trace.TraceError(errorCode.GetMessage()); - { - // buffer was too small. Reallocate buffer with size matched 'length' and try again - using PWSTR newBuffer = new((char*)Marshal.AllocHGlobal((int)length)); - bufLength = GetFinalPathNameByHandle(dupHandle, buffer, bufLength, GETFINALPATHNAMEBYHANDLE_FLAGS.FILE_NAME_NORMALIZED); - return fileFullPath = (newBuffer.ToString(), null); - } - } - else + return fileFullPath = (null, errorCode switch { - Win32ErrorCode error = (Win32ErrorCode)Marshal.GetLastWin32Error(); - Trace.TraceError(error.GetMessage()); - - return (null, error switch - { - // Removable storage, deleted item, network shares, et cetera - Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException($"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(error)), - // unlikely, but possible if system has little free memory - Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException("Failed to query path from file handle. Insufficient memory to complete the operation.", new Win32Exception(error)), - // possible only if FILE_NAME_NORMALIZED (0) is invalid - Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException("Failed to query path from file handle. Invalid flags were specified for dwFlags.", new Win32Exception(error)), - _ => new Exception($"An undocumented error ({error}) was returned when querying a file handle for its path.", new Win32Exception(error)) - }); - } + // Removable storage, deleted item, network shares, et cetera + Win32ErrorCode.ERROR_PATH_NOT_FOUND => new FileNotFoundException(errFailedMsg + $"The path '{buffer}' was not found when querying a file handle.", fileName: buffer.ToString(), new Win32Exception(errorCode)), + // unlikely, but possible if system has little free memory + Win32ErrorCode.ERROR_NOT_ENOUGH_MEMORY => new OutOfMemoryException(errFailedMsg + "Insufficient memory to complete the operation.", new Win32Exception(errorCode)), + // possible only if FILE_NAME_NORMALIZED (0) is invalid + Win32ErrorCode.ERROR_INVALID_PARAMETER => new ArgumentException(errFailedMsg + "Invalid flags were specified for dwFlags.", new Win32Exception(errorCode)), + _ => new Exception($"{errFailedMsg}An undocumented error ({errorCode}) was returned when querying a file handle for its path.", new Win32Exception(errorCode)) + }); } catch (Exception ex) { @@ -324,7 +269,7 @@ public unsafe (string? v, Exception? ex) FileFullPath { const string errUnableMsg = "Unable to query " + nameof(FileHandleType) + "; "; const string errFailedMsg = "Failed to query " + nameof(FileHandleType) + "; "; - if (IsClosed) + if (IsClosed.v is true) return fileHandleType = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); if (ProcessInfo.ProcessProtection.ex is not null) return fileHandleType = (null, new NullReferenceException(errUnableMsg + "Failed to query the process's protection level.")); @@ -332,8 +277,10 @@ public unsafe (string? v, Exception? ex) FileFullPath return fileHandleType = (null, new UnauthorizedAccessException(errUnableMsg + "The process's protection prohibits this operation.")); if (IsFileHandle.v is not true) return fileHandleType = (null, new InvalidOperationException(errUnableMsg + "This operation is only valid on File handles.")); + if (DuplicateHandle.v is null) + return fileHandleType = (null, new InvalidOperationException(errUnableMsg + "A duplicate handle is required for this operation.", DuplicateHandle.ex)); - FileType type = (FileType)GetFileType(handle); + FileType type = (FileType)GetFileType(DuplicateHandle.v); if (type is FileType.Unknown) { Win32ErrorCode err = (Win32ErrorCode)Marshal.GetLastPInvokeError(); @@ -358,7 +305,7 @@ public unsafe (string? v, Exception? ex) FileFullPath { const string errUnableMsg = "Unable to query " + nameof(FileName) + "; "; const string errFailedMsg = "Failed to query " + nameof(FileName) + "; "; - if (IsClosed) + if (IsClosed.v is true) return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); if (FileFullPath.v is not null) { @@ -415,7 +362,7 @@ public unsafe (string? v, Exception? ex) FileNameInfo { const string errUnableMsg = "Unable to query " + nameof(FileNameInfo) + "; "; const string errFailedMsg = "Failed to query " + nameof(FileNameInfo) + "; "; - if (IsClosed) + if (IsClosed.v is true) return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); if (ProcessInfo.ProcessProtection.ex is not null) return fileNameInfo = (null, new NullReferenceException(errUnableMsg + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex)); @@ -426,13 +373,13 @@ public unsafe (string? v, Exception? ex) FileNameInfo /** Get fni.FileNameLength */ FILE_NAME_INFO fni = default; - _ = GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &fni, (uint)Marshal.SizeOf(fni)); + _ = GetFileInformationByHandleEx(DuplicateHandle.v, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, &fni, (uint)Marshal.SizeOf(fni)); /** Get FileNameInfo */ int bufferLength = Marshal.SizeOf(fni.FileNameLength) + (int)fni.FileNameLength; using SafeBuffer safeBuffer = new(numBytes: (nuint)bufferLength); - if (!GetFileInformationByHandleEx(this, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, (FILE_NAME_INFO*)safeBuffer.DangerousGetHandle(), (uint)bufferLength)) + if (!GetFileInformationByHandleEx(DuplicateHandle.v, FILE_INFO_BY_HANDLE_CLASS.FileNameInfo, (FILE_NAME_INFO*)safeBuffer.DangerousGetHandle(), (uint)bufferLength)) return fileNameInfo = (null, new Exception(errFailedMsg + "GetFileInformationByHandleEx encountered an error.", new Win32Exception())); /* The string conversion copies the data to a new string in the managed heap before freeing safeBuffer and leaving this context. */ diff --git a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs index e34dc15..7740dea 100644 --- a/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs +++ b/deadlock-dotnet-sdk/Domain/SafeHandleEx.cs @@ -97,12 +97,14 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals /// /// (non-persistent) Pass the handle to GetHandleInformation and check for ERROR_INVALID_HANDLE to determine if the handle is open or closed. /// - public new (bool? v, Exception? ex) IsClosed => isClosed.v is true ? isClosed : (isClosed = GetIsClosed()); + public new (bool? v, Exception? ex) IsClosed => isClosed.v is true ? isClosed : (isClosed.v is null ? isClosed = GetIsClosed() : isClosed); + // TODO: use block property, inline method private (bool?, Exception?) GetIsClosed() { const string errFailedMsg = "Failed to query " + nameof(IsClosed) + "; "; const string errFailedDupMsg = errFailedMsg + "Failed to duplicate handle."; + const string errProcessHandleMsg = "A process handle with PROCESS_DUP_HANDLE is required for this operation."; string errFailedOpenProcMsg = $"{errFailedMsg}Failed to open handle to process {ProcessId}."; SafeProcessHandle? hSourceProcessHandle = null; SafeProcessHandle hTargetProcessHandle = Process.GetCurrentProcess().SafeHandle; @@ -111,21 +113,18 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals if (ProcessId != Environment.ProcessId) { /** Open Process Handle */ - try - { - hSourceProcessHandle = OpenProcess_SafeHandle(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, false, ProcessId); - } - catch (Exception ex) - { - return (null, new Exception(errFailedOpenProcMsg, ex)); - } + if (ProcessInfo.ProcessHandle.v is null) + return (null, new InvalidOperationException(errFailedOpenProcMsg, ProcessInfo.ProcessHandle.ex)); + + if ((ProcessInfo.ProcessHandle.v.AccessRights & PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE) is 0) + return (null, new UnauthorizedAccessException(errProcessHandleMsg)); /** Duplicate handle to this process */ try { - duplicate = DuplicateHandle(hSourceProcessHandle, this, hTargetProcessHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); + duplicate = DuplicateHandle(ProcessInfo.ProcessHandle.v, this, hTargetProcessHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); } - catch (ArgumentException) when (!(hSourceProcessHandle.IsInvalid || hTargetProcessHandle.IsInvalid)) // if both are valid... + catch (ArgumentException) when (!(ProcessInfo.ProcessHandle.v.Handle.IsInvalid || hTargetProcessHandle.IsInvalid)) // if both are valid... { return (true, null); // ...then the current handle is invalid } @@ -137,9 +136,6 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals { hSourceProcessHandle?.Dispose(); } - - /** GetHandleInformation */ - } else { @@ -185,23 +181,27 @@ public unsafe (string? v, Exception? ex) ObjectName { const string errUnableMsg = "Unable to query " + nameof(ObjectName) + "; "; const string errFailedMsg = "Failed to query " + nameof(ObjectName) + "; "; + if (IsClosed.v is null) return objectName = (null, new InvalidOperationException(errUnableMsg + "Failed to determine if the handle is still open.")); if (IsClosed.v is true) return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix)); - var (v, ex) = ProcessInfo.ProcessProtection; + // I'm assuming process protection prohibits access. I've not tested it. // This information is not queryable in SystemInformer when a process has Full protection. - if (v is null) - return objectName = (null, new UnauthorizedAccessException(errUnableMsg + "Failed to query process's protection level.", ex)); - else if (v.Value.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) + if (ProcessInfo.ProcessProtection.v is null) + return objectName = (null, new UnauthorizedAccessException(errUnableMsg + "Failed to query process's protection level.", ProcessInfo.ProcessProtection.ex)); + else if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected) return objectName = (null, new UnauthorizedAccessException(errUnableMsg + "The process's protection type prohibits access.")); + if (DuplicateHandle.v is null) + return objectName = (null, new NullReferenceException(errUnableMsg + "A duplicate handle is required for this operation.", DuplicateHandle.ex)); + uint returnLength = 1024u; using SafeBuffer buffer = new(numBytes: returnLength); NTSTATUS status = default; - while ((status = NtQueryObject(this, + while ((status = NtQueryObject(this.DuplicateHandle.v, OBJECT_INFORMATION_CLASS.ObjectNameInformation, (OBJECT_NAME_INFORMATION*)buffer.DangerousGetHandle(), returnLength, @@ -233,7 +233,7 @@ public unsafe (string? v, Exception? ex) ObjectName /// /// A duplicated handle for handle info queries. /// - /// CloseSourceHandle makes a separate duplicate with different permissions. + /// CloseSourceHandle makes a separate duplicate with different permissions. This handle will never have DUPLICATE_SOURCE_CLOSE. public (SafeFileHandle? v, Exception? ex) DuplicateHandle { get @@ -247,8 +247,7 @@ public unsafe (string? v, Exception? ex) ObjectName try { - SafeFileHandle v = DuplicateHandle(ProcessInfo.ProcessHandle.v, this, Process.GetCurrentProcess().SafeHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS); - return duplicateHandle = (v, null); + return duplicateHandle = (DuplicateHandle(ProcessInfo.ProcessHandle.v, this, Process.GetCurrentProcess().SafeHandle, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS), null); } catch (Exception ex) { From 03756774b0601796f3cc9cf5e38fcdfc35991122 Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 15 May 2023 19:28:05 -0700 Subject: [PATCH 303/306] docs: update ProcessInfo.ProcessHandle exceptions --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index 2867ba2..e4f3c3a 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -148,7 +148,9 @@ public ProcessInfo(Process process) /// /// If successful, a SafeProcessHandle with its AccessRights property assigned the PROCESS_ACCESS_RIGHTS used to open it. Else...
/// Unable to open the process with any of requested access rights.
- ///
//TODO: exception docs + /// Failed to open process (ID ) with access rights ''.
+ /// Cannot open handle for process (ID ).
+ /// Unrecognized error occurred when attempting to open handle for process with ID .
///
public (SafeProcessHandleEx? v, Exception? ex) ProcessHandle { From 0ec0c1104a17d861fa35b2bad48f3812ab5e144b Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 15 May 2023 23:20:54 -0700 Subject: [PATCH 304/306] fix: finish IsClosed reference refactors --- deadlock-dotnet-sdk/DeadLock.cs | 6 +++--- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index 09da664..f0ae00f 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -309,7 +309,7 @@ public void UnlockEx(FileLockerEx fileLocker) { foreach (SafeFileHandleEx h in fileLocker.Lockers) { - if (h.IsClosed && h.IsInvalid) continue; + if (h.IsClosed.v is true && h.IsInvalid) continue; try { h.CloseSourceHandle(true); @@ -344,7 +344,7 @@ await Task.Run(() => { foreach (SafeFileHandleEx h in fileLocker.Lockers) { - if (h.IsClosed && h.IsInvalid) continue; + if (h.IsClosed.v is true && h.IsInvalid) continue; try { h.CloseSourceHandle(true); @@ -366,7 +366,7 @@ await Task.Run(() => { foreach (SafeFileHandleEx h in fileLocker.Lockers) { - if (h.IsClosed && h.IsInvalid) continue; + if (h.IsClosed.v is true && h.IsInvalid) continue; try { h.CloseSourceHandle(true); diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 620067b..8c10e7a 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -175,7 +175,7 @@ bool keep(SafeFileHandleEx h) { bool keep = false; - if (h.IsClosed) + if (h.IsClosed.v is true) return false; if (!string.IsNullOrEmpty(query)) From 0349dd01ce6dae34c46567f60f156ab5ac2db69e Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 15 May 2023 23:22:19 -0700 Subject: [PATCH 305/306] fix: use ProcessInfo's ProcessId property, NOT the field --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index e4f3c3a..f1937d2 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -171,7 +171,7 @@ public ProcessInfo(Process process) { try { - SafeProcessHandleEx.OpenProcessHandle(processId, accessRight); + SafeProcessHandleEx.OpenProcessHandle(ProcessId, accessRight); AccessRightsGranted.Add(accessRight); } catch (Exception ex) // we don't want exceptions to break the loop. They just mean we can't use an access right. From 192d36da10e5bef7041f072233e3bcce42e1545a Mon Sep 17 00:00:00 2001 From: Noah Sherwin Date: Mon, 15 May 2023 23:23:08 -0700 Subject: [PATCH 306/306] refactor: change ProcessBasicInformation's buffer reallocation implementation --- deadlock-dotnet-sdk/Domain/ProcessInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs index f1937d2..ebf3cb2 100644 --- a/deadlock-dotnet-sdk/Domain/ProcessInfo.cs +++ b/deadlock-dotnet-sdk/Domain/ProcessInfo.cs @@ -307,7 +307,7 @@ ref returnLength while ((status = NtQueryInformationProcess(ProcessHandle.v, PROCESSINFOCLASS.ProcessBasicInformation, (void*)bufferPBI.DangerousGetHandle(), (uint)bufferPBI.ByteLength, ref returnLength)).Code is Code.STATUS_INFO_LENGTH_MISMATCH or Code.STATUS_BUFFER_TOO_SMALL or Code.STATUS_BUFFER_OVERFLOW) { - bufferPBI.Reallocate(returnLength is 0 ? returnLength = (uint)(bufferPBI.ByteLength * 2) : returnLength); + bufferPBI.Reallocate(returnLength += (uint)bufferPBI.ByteLength); } if (status.Code is not Code.STATUS_SUCCESS)