|   |  | 1 |  | using Microsoft.Win32.SafeHandles; | 
|   |  | 2 |  | using System; | 
|   |  | 3 |  | using System.Diagnostics; | 
|   |  | 4 |  | using System.IO; | 
|   |  | 5 |  | using System.Linq; | 
|   |  | 6 |  | using System.Runtime.CompilerServices; | 
|   |  | 7 |  | using System.Runtime.InteropServices; | 
|   |  | 8 |  | using static LockCheck.Windows.NativeMethods; | 
|   |  | 9 |  |  | 
|   |  | 10 |  | namespace LockCheck.Windows; | 
|   |  | 11 |  |  | 
|   |  | 12 |  | [DebuggerDisplay("{HasError} {ProcessId} {ExecutableFullPath}")] | 
|   |  | 13 |  | internal class Peb : IWin32ProcessDetails, IHasErrorState | 
|   |  | 14 |  | { | 
|   |  | 15 |  | #if DEBUG | 
|   |  | 16 |  | #pragma warning disable IDE0052 | 
|   |  | 17 |  |     private string? _errorStack; | 
|   |  | 18 |  |     private Exception? _errorCause; | 
|   |  | 19 |  |     private int _errorCode; | 
|   |  | 20 |  | #pragma warning restore IDE0052 | 
|   |  | 21 |  | #endif | 
|   |  | 22 |  |  | 
|   | 2 | 23 |  |     public int ProcessId { get; private set; } | 
|   | 2 | 24 |  |     public DateTime StartTime { get; private set; } | 
|   | 2 | 25 |  |     public int SessionId { get; private set; } | 
|   | 2 | 26 |  |     public int? ParentProcessId { get; private set; } | 
|   | 2 | 27 |  |     public string? ProcessName { get; private set; } | 
|   | 2 | 28 |  |     public string? CommandLine { get; private set; } | 
|   | 2 | 29 |  |     public string? CurrentDirectory { get; private set; } | 
|   | 2 | 30 |  |     public string? WindowTitle { get; private set; } | 
|   | 2 | 31 |  |     public string? ExecutableFullPath { get; private set; } | 
|   | 2 | 32 |  |     public string? DesktopInfo { get; private set; } | 
|   | 2 | 33 |  |     public string? Owner { get; private set; } | 
|   | 2 | 34 |  |     public bool HasError { get; private set; } | 
|   | 2 | 35 |  |     public bool? IsCritical { get; private set; } | 
|   | 2 | 36 |  |     public bool IsPseudoProcess { get; private set; } | 
|   | 2 | 37 |  |     public ulong? ProcessSequenceNumber { get; private set; } | 
|   | 2 | 38 |  |     public ulong? ProcessStartKey { get; private set; } | 
|   |  | 39 |  |  | 
|   |  | 40 |  |     public void SetError(Exception? ex = null, int errorCode = 0) | 
|   |  | 41 |  |     { | 
|   | 2 | 42 |  |         if (!HasError) | 
|   |  | 43 |  |         { | 
|   | 2 | 44 |  |             HasError = true; | 
|   |  | 45 |  | #if DEBUG | 
|   |  | 46 |  |             if (Debugger.IsAttached) | 
|   |  | 47 |  |             { | 
|   |  | 48 |  |                 // Support manual inspection at a later point | 
|   |  | 49 |  |                 _errorStack = Environment.StackTrace; | 
|   |  | 50 |  |                 _errorCause = ex; | 
|   |  | 51 |  |                 _errorCode = errorCode; | 
|   |  | 52 |  |             } | 
|   |  | 53 |  | #endif | 
|   |  | 54 |  |         } | 
|   | 2 | 55 |  |     } | 
|   |  | 56 |  |  | 
|   | 2 | 57 |  |     internal Peb(SYSTEM_PROCESS_INFORMATION pi, ulong? processSequenceNumber) | 
|   |  | 58 |  |     { | 
|   |  | 59 |  |         // Convert as many members as possible, without actually needing to open the process handle. | 
|   |  | 60 |  |         // Also, ProcessId/StartTime serve as identity. So it is "useful" to have them. | 
|   | 2 | 61 |  |         ProcessId = pi.UniqueProcessId.ToInt32(); | 
|   | 2 | 62 |  |         StartTime = DateTime.FromFileTime(pi.CreateTime); | 
|   | 2 | 63 |  |         ParentProcessId = pi.InheritedFromUniqueProcessId.ToInt32(); | 
|   | 2 | 64 |  |         ProcessSequenceNumber = processSequenceNumber; | 
|   |  | 65 |  |  | 
|   | 2 | 66 |  |         if (pi.NamePtr != IntPtr.Zero) | 
|   |  | 67 |  |         { | 
|   |  | 68 |  |             // NamePtr will contain information not otherwise easily obtainable. | 
|   |  | 69 |  |             // For example it will contain "System", "Secure System", "Registry", etc. | 
|   |  | 70 |  |             // for those Windows pseudo processes. For regular processes it contains | 
|   |  | 71 |  |             // the of the executable. | 
|   | 2 | 72 |  |             ProcessName = Marshal.PtrToStringUni(pi.NamePtr); | 
|   |  | 73 |  |         } | 
|   |  | 74 |  |  | 
|   | 2 | 75 |  |         Initialize(); | 
|   | 2 | 76 |  |     } | 
|   |  | 77 |  |  | 
|   | 2 | 78 |  |     internal Peb(int processId, DateTime startTime) | 
|   |  | 79 |  |     { | 
|   | 2 | 80 |  |         ProcessId = processId; | 
|   | 2 | 81 |  |         StartTime = startTime; | 
|   |  | 82 |  |  | 
|   | 2 | 83 |  |         Initialize(); | 
|   | 2 | 84 |  |     } | 
|   |  | 85 |  |  | 
|   |  | 86 |  |     private void Initialize() | 
|   |  | 87 |  |     { | 
|   | 2 | 88 |  |         if (NtDll.TryGetSystemPseudoProcess(ProcessId, out var pseudoPeb)) | 
|   |  | 89 |  |         { | 
|   |  | 90 |  |             // Common values for pseudo processes. All other fields don't really make sense for | 
|   |  | 91 |  |             // these and also would cause access denied errors when attempting to read memory | 
|   |  | 92 |  |             // or even "open" the "processes". | 
|   |  | 93 |  |  | 
|   | 2 | 94 |  |             ProcessName ??= pseudoPeb.ProcessName; | 
|   | 2 | 95 |  |             IsPseudoProcess = pseudoPeb.IsPseudoProcess; | 
|   | 2 | 96 |  |             IsCritical = pseudoPeb.IsCritical; | 
|   | 2 | 97 |  |             ExecutableFullPath = pseudoPeb.ExecutableFullPath; | 
|   | 2 | 98 |  |             Owner = pseudoPeb.Owner; | 
|   | 2 | 99 |  |             SessionId = pseudoPeb.SessionId; | 
|   | 2 | 100 |  |             ParentProcessId = pseudoPeb.ParentProcessId; | 
|   | 2 | 101 |  |             ProcessSequenceNumber ??= pseudoPeb.ProcessSequenceNumber; | 
|   |  | 102 |  |         } | 
|   |  | 103 |  |         else | 
|   |  | 104 |  |         { | 
|   | 2 | 105 |  |             using var process = OpenProcessRead(ProcessId); | 
|   |  | 106 |  |  | 
|   | 2 | 107 |  |             if (!SUCCEEDED(!process.IsInvalid, this)) | 
|   |  | 108 |  |             { | 
|   |  | 109 |  |                 // If if not a system pseudo process, this can still fail with access denied errors. | 
|   | 2 | 110 |  |                 return; | 
|   |  | 111 |  |             } | 
|   |  | 112 |  |  | 
|   |  | 113 |  |             // Need to check if either the current process, or the target process is 32bit or 64bit. | 
|   |  | 114 |  |             // Additionally, if it is a 32bit process on a 64bit OS (WOW64). | 
|   |  | 115 |  |  | 
|   | 2 | 116 |  |             bool os64 = Environment.Is64BitOperatingSystem; | 
|   | 2 | 117 |  |             bool self64 = Environment.Is64BitProcess; | 
|   | 2 | 118 |  |             bool target64 = false; | 
|   |  | 119 |  |  | 
|   | 2 | 120 |  |             if (os64) | 
|   |  | 121 |  |             { | 
|   | 2 | 122 |  |                 if (!SUCCEEDED(IsWow64Process(process, out bool isWow64Target), this)) | 
|   |  | 123 |  |                 { | 
|   | 0 | 124 |  |                     return; | 
|   |  | 125 |  |                 } | 
|   |  | 126 |  |  | 
|   | 2 | 127 |  |                 target64 = !isWow64Target; | 
|   |  | 128 |  |             } | 
|   |  | 129 |  |  | 
|   | 2 | 130 |  |             var offsets = PebOffsets.Get(target64); | 
|   |  | 131 |  |  | 
|   | 2 | 132 |  |             if (os64) | 
|   |  | 133 |  |             { | 
|   | 2 | 134 |  |                 if (!target64) | 
|   |  | 135 |  |                 { | 
|   |  | 136 |  |                     // os: 64bit, self: any, target: 32bit | 
|   | 2 | 137 |  |                     InitTarget32SelfAny(process, offsets, this); | 
|   |  | 138 |  |                 } | 
|   | 2 | 139 |  |                 else if (!self64) | 
|   |  | 140 |  |                 { | 
|   |  | 141 |  |                     // os: 64bit, self: 32bit, target: 64bit | 
|   | 0 | 142 |  |                     InitTarget64Self32(process, offsets, this); | 
|   |  | 143 |  |                 } | 
|   |  | 144 |  |                 else | 
|   |  | 145 |  |                 { | 
|   |  | 146 |  |                     // os: 64bit, self: 64bit, target: 64bit | 
|   | 2 | 147 |  |                     InitTargetAnySelfAny(process, offsets, this); | 
|   |  | 148 |  |                 } | 
|   |  | 149 |  |             } | 
|   |  | 150 |  |             else | 
|   |  | 151 |  |             { | 
|   |  | 152 |  |                 // os: 32bit, self: 32bit, target: 32bit | 
|   | 0 | 153 |  |                 InitTargetAnySelfAny(process, offsets, this); | 
|   |  | 154 |  |             } | 
|   |  | 155 |  |  | 
|   |  | 156 |  |             // Make sure that the current directory always ends with a backslash. AFAICT that is always the case, | 
|   |  | 157 |  |             // so this should really be a noop, but we need to make sure to ensure hassle free comparison later. | 
|   | 2 | 158 |  |             if (!string.IsNullOrEmpty(CurrentDirectory) && CurrentDirectory[CurrentDirectory.Length - 1] != '\\') | 
|   |  | 159 |  |             { | 
|   | 0 | 160 |  |                 CurrentDirectory += "\\"; | 
|   |  | 161 |  |             } | 
|   |  | 162 |  |  | 
|   |  | 163 |  |             // Owner is not really part of the native PEB, but since we have the process handle | 
|   |  | 164 |  |             // here anyway, and going to need this value later on, we get it here as well. | 
|   | 2 | 165 |  |             Owner = GetProcessOwner(process); | 
|   | 2 | 166 |  |             IsCritical = IsProcessCritical(process, this); | 
|   | 2 | 167 |  |             ProcessName ??= Path.GetFileName(ExecutableFullPath); | 
|   |  | 168 |  |         } | 
|   |  | 169 |  |  | 
|   | 2 | 170 |  |         if (ProcessStartKey == null && ProcessSequenceNumber != null) | 
|   |  | 171 |  |         { | 
|   | 2 | 172 |  |             ProcessStartKey = GetProcessStartKey(ProcessSequenceNumber.Value); | 
|   |  | 173 |  |         } | 
|   | 2 | 174 |  |     } | 
|   |  | 175 |  |  | 
|   |  | 176 |  |     private static void InitTargetAnySelfAny(SafeProcessHandle handle, PebOffsets offsets, Peb peb) | 
|   |  | 177 |  |     { | 
|   | 2 | 178 |  |         var pbi = new PROCESS_BASIC_INFORMATION(); | 
|   | 2 | 179 |  |         if (SUCCEEDED(NtQueryInformationProcess(handle, PROCESS_INFORMATION_CLASS.ProcessBasicInformation, ref pbi, Mars | 
|   |  | 180 |  |         { | 
|   | 2 | 181 |  |             peb.ParentProcessId ??= pbi.InheritedFromUniqueProcessId.ToInt32(); | 
|   |  | 182 |  |  | 
|   | 2 | 183 |  |             var pp = new IntPtr(); | 
|   | 2 | 184 |  |             if (SUCCEEDED(ReadProcessMemory(handle, new IntPtr(pbi.PebBaseAddress.ToInt64() + offsets.ProcessParametersO | 
|   |  | 185 |  |             { | 
|   | 2 | 186 |  |                 peb.CommandLine = GetString(handle, pp, offsets.CommandLineOffset, peb); | 
|   | 2 | 187 |  |                 peb.CurrentDirectory = GetString(handle, pp, offsets.CurrentDirectoryOffset, peb); | 
|   | 2 | 188 |  |                 peb.ExecutableFullPath = GetString(handle, pp, offsets.ImagePathNameOffset, peb); | 
|   | 2 | 189 |  |                 peb.WindowTitle = GetString(handle, pp, offsets.WindowTitleOffset, peb); | 
|   | 2 | 190 |  |                 peb.DesktopInfo = GetString(handle, pp, offsets.DesktopInfoOffset, peb); | 
|   |  | 191 |  |             } | 
|   |  | 192 |  |  | 
|   | 2 | 193 |  |             peb.SessionId = GetInt32(handle, pbi.PebBaseAddress, offsets.SessionIdOffset, peb); | 
|   |  | 194 |  |         } | 
|   |  | 195 |  |  | 
|   | 2 | 196 |  |         if (SupportsProcessSequenceNumber && peb.ProcessSequenceNumber == null) | 
|   |  | 197 |  |         { | 
|   |  | 198 |  | #if NET | 
|   | 2 | 199 |  |             using var psn = new ScopedNativeMemory(stackalloc byte[sizeof(ulong)]); | 
|   |  | 200 |  | #else | 
|   |  | 201 |  |             using var psn = new ScopedNativeMemory(Marshal.SizeOf<ulong>()); | 
|   |  | 202 |  | #endif | 
|   | 2 | 203 |  |             var buffer = (IntPtr)psn; | 
|   | 2 | 204 |  |             if (SUCCEEDED(NtQueryInformationProcess(handle, PROCESS_INFORMATION_CLASS.ProcessSequenceNumber, ref buffer, | 
|   |  | 205 |  |             { | 
|   | 2 | 206 |  |                 peb.ProcessSequenceNumber = (ulong)buffer.ToInt64(); | 
|   |  | 207 |  |             } | 
|   |  | 208 |  |         } | 
|   | 2 | 209 |  |     } | 
|   |  | 210 |  |  | 
|   |  | 211 |  |     private static void InitTarget64Self32(SafeProcessHandle handle, PebOffsets offsets, Peb peb) | 
|   |  | 212 |  |     { | 
|   | 0 | 213 |  |         var pbi = new PROCESS_BASIC_INFORMATION_WOW64(); | 
|   | 0 | 214 |  |         if (SUCCEEDED(NtWow64QueryInformationProcess64(handle, PROCESS_INFORMATION_CLASS.ProcessBasicInformation, ref pb | 
|   |  | 215 |  |         { | 
|   | 0 | 216 |  |             peb.ParentProcessId ??= (int)pbi.InheritedFromUniqueProcessId; | 
|   |  | 217 |  |  | 
|   | 0 | 218 |  |             long pp = 0; | 
|   | 0 | 219 |  |             if (SUCCEEDED(NtWow64ReadVirtualMemory64(handle, pbi.PebBaseAddress + offsets.ProcessParametersOffset, ref p | 
|   |  | 220 |  |             { | 
|   | 0 | 221 |  |                 peb.CommandLine = GetStringTarget64(handle, pp, offsets.CommandLineOffset, peb); | 
|   | 0 | 222 |  |                 peb.CurrentDirectory = GetStringTarget64(handle, pp, offsets.CurrentDirectoryOffset, peb); | 
|   | 0 | 223 |  |                 peb.ExecutableFullPath = GetStringTarget64(handle, pp, offsets.ImagePathNameOffset, peb); | 
|   | 0 | 224 |  |                 peb.WindowTitle = GetStringTarget64(handle, pp, offsets.WindowTitleOffset, peb); | 
|   | 0 | 225 |  |                 peb.DesktopInfo = GetStringTarget64(handle, pp, offsets.DesktopInfoOffset, peb); | 
|   |  | 226 |  |             } | 
|   |  | 227 |  |  | 
|   | 0 | 228 |  |             peb.SessionId = GetInt32Target64(handle, pbi.PebBaseAddress, offsets.SessionIdOffset, peb); | 
|   |  | 229 |  |         } | 
|   |  | 230 |  |  | 
|   | 0 | 231 |  |         if (SupportsProcessSequenceNumber && peb.ProcessSequenceNumber == null) | 
|   |  | 232 |  |         { | 
|   |  | 233 |  | #if NET | 
|   | 0 | 234 |  |             using var psn = new ScopedNativeMemory(stackalloc byte[sizeof(ulong)]); | 
|   |  | 235 |  | #else | 
|   |  | 236 |  |             using var psn = new ScopedNativeMemory(Marshal.SizeOf<ulong>()); | 
|   |  | 237 |  | #endif | 
|   | 0 | 238 |  |             var buffer = (IntPtr)psn; | 
|   | 0 | 239 |  |             if (SUCCEEDED(NtWow64QueryInformationProcess64(handle, PROCESS_INFORMATION_CLASS.ProcessSequenceNumber, ref  | 
|   |  | 240 |  |             { | 
|   | 0 | 241 |  |                 peb.ProcessSequenceNumber = (ulong)buffer.ToInt64(); | 
|   |  | 242 |  |             } | 
|   |  | 243 |  |         } | 
|   | 0 | 244 |  |     } | 
|   |  | 245 |  |  | 
|   |  | 246 |  |     private static void InitTarget32SelfAny(SafeProcessHandle handle, PebOffsets offsets, Peb peb) | 
|   |  | 247 |  |     { | 
|   | 2 | 248 |  |         var pbi = new PROCESS_BASIC_INFORMATION(); | 
|   | 2 | 249 |  |         if (SUCCEEDED(NtQueryInformationProcess(handle, PROCESS_INFORMATION_CLASS.ProcessBasicInformation, ref pbi, Mars | 
|   |  | 250 |  |         { | 
|   | 2 | 251 |  |             peb.ParentProcessId ??= pbi.InheritedFromUniqueProcessId.ToInt32(); | 
|   |  | 252 |  |  | 
|   |  | 253 |  |             // A 32bit process on a 64bit OS has a separate PEB structure. | 
|   | 2 | 254 |  |             var peb32 = new IntPtr(); | 
|   | 2 | 255 |  |             if (SUCCEEDED(NtQueryInformationProcessWow64(handle, PROCESS_INFORMATION_CLASS.ProcessWow64Information, ref  | 
|   |  | 256 |  |             { | 
|   | 2 | 257 |  |                 var pp = new IntPtr(); | 
|   | 2 | 258 |  |                 if (SUCCEEDED(ReadProcessMemory(handle, new IntPtr(peb32.ToInt64() + offsets.ProcessParametersOffset), r | 
|   |  | 259 |  |                 { | 
|   | 2 | 260 |  |                     peb.CommandLine = GetStringTarget32(handle, pp, offsets.CommandLineOffset, peb); | 
|   | 2 | 261 |  |                     peb.CurrentDirectory = GetStringTarget32(handle, pp, offsets.CurrentDirectoryOffset, peb); | 
|   | 2 | 262 |  |                     peb.ExecutableFullPath = GetStringTarget32(handle, pp, offsets.ImagePathNameOffset, peb); | 
|   | 2 | 263 |  |                     peb.WindowTitle = GetStringTarget32(handle, pp, offsets.WindowTitleOffset, peb); | 
|   | 2 | 264 |  |                     peb.DesktopInfo = GetStringTarget32(handle, pp, offsets.DesktopInfoOffset, peb); | 
|   |  | 265 |  |                 } | 
|   |  | 266 |  |  | 
|   | 2 | 267 |  |                 peb.SessionId = GetInt32Target32(handle, new IntPtr(peb32.ToInt64()), offsets.SessionIdOffset, peb); | 
|   |  | 268 |  |             } | 
|   |  | 269 |  |  | 
|   | 2 | 270 |  |             if (SupportsProcessSequenceNumber && peb.ProcessSequenceNumber == null) | 
|   |  | 271 |  |             { | 
|   |  | 272 |  | #if NET | 
|   | 0 | 273 |  |                 using var psn = new ScopedNativeMemory(stackalloc byte[sizeof(ulong)]); | 
|   |  | 274 |  | #else | 
|   |  | 275 |  |                 using var psn = new ScopedNativeMemory(Marshal.SizeOf<ulong>()); | 
|   |  | 276 |  | #endif | 
|   | 0 | 277 |  |                 var buffer = (IntPtr)psn; | 
|   | 0 | 278 |  |                 if (SUCCEEDED(NtQueryInformationProcessWow64(handle, PROCESS_INFORMATION_CLASS.ProcessSequenceNumber, re | 
|   |  | 279 |  |                 { | 
|   | 0 | 280 |  |                     peb.ProcessSequenceNumber = (ulong)buffer.ToInt64(); | 
|   |  | 281 |  |                 } | 
|   |  | 282 |  |             } | 
|   |  | 283 |  |         } | 
|   | 2 | 284 |  |     } | 
|   |  | 285 |  |  | 
|   |  | 286 |  |     private static int GetInt32Target32(SafeProcessHandle handle, IntPtr pp, int offset, Peb he) | 
|   |  | 287 |  |     { | 
|   | 2 | 288 |  |         var ptr = IntPtr.Zero; | 
|   | 2 | 289 |  |         if (SUCCEEDED(ReadProcessMemory(handle, pp + offset, ref ptr, new IntPtr(sizeof(int)), IntPtr.Zero), he)) | 
|   |  | 290 |  |         { | 
|   | 2 | 291 |  |             return ptr.ToInt32(); | 
|   |  | 292 |  |         } | 
|   |  | 293 |  |  | 
|   | 0 | 294 |  |         return default; | 
|   |  | 295 |  |     } | 
|   |  | 296 |  |  | 
|   |  | 297 |  |     private static string? GetStringTarget32(SafeProcessHandle handle, IntPtr pp, int offset, Peb he) | 
|   |  | 298 |  |     { | 
|   | 2 | 299 |  |         var us = new UNICODE_STRING_32(); | 
|   | 2 | 300 |  |         if (SUCCEEDED(ReadProcessMemory(handle, pp + offset, ref us, new IntPtr(Marshal.SizeOf(us)), IntPtr.Zero), he)) | 
|   |  | 301 |  |         { | 
|   | 2 | 302 |  |             if (us.Buffer != 0) | 
|   |  | 303 |  |             { | 
|   | 2 | 304 |  |                 if (us.Length == 0) | 
|   |  | 305 |  |                 { | 
|   | 0 | 306 |  |                     return string.Empty; | 
|   |  | 307 |  |                 } | 
|   |  | 308 |  |  | 
|   | 2 | 309 |  |                 string lpBuffer = us.GetEmptyBuffer(); | 
|   | 2 | 310 |  |                 if (SUCCEEDED(ReadProcessMemory(handle, new IntPtr(us.Buffer), lpBuffer, new IntPtr(us.Length), IntPtr.Z | 
|   |  | 311 |  |                 { | 
|   | 2 | 312 |  |                     return lpBuffer; | 
|   |  | 313 |  |                 } | 
|   |  | 314 |  |             } | 
|   |  | 315 |  |         } | 
|   |  | 316 |  |  | 
|   | 0 | 317 |  |         return null; | 
|   |  | 318 |  |     } | 
|   |  | 319 |  |  | 
|   |  | 320 |  |     private static int GetInt32Target64(SafeProcessHandle handle, long pp, int offset, Peb he) | 
|   |  | 321 |  |     { | 
|   | 0 | 322 |  |         var ptr = IntPtr.Zero; | 
|   | 0 | 323 |  |         uint buf = 0; | 
|   | 0 | 324 |  |         if (SUCCEEDED(NtWow64ReadVirtualMemory64(handle, pp + offset, ref buf, sizeof(uint), IntPtr.Zero), he)) | 
|   |  | 325 |  |         { | 
|   | 0 | 326 |  |             ptr = new IntPtr(buf); | 
|   | 0 | 327 |  |             return ptr.ToInt32(); | 
|   |  | 328 |  |         } | 
|   |  | 329 |  |  | 
|   | 0 | 330 |  |         return default; | 
|   |  | 331 |  |     } | 
|   |  | 332 |  |  | 
|   |  | 333 |  |     private static string? GetStringTarget64(SafeProcessHandle handle, long pp, int offset, Peb he) | 
|   |  | 334 |  |     { | 
|   | 0 | 335 |  |         var us = new UNICODE_STRING_WOW64(); | 
|   | 0 | 336 |  |         if (SUCCEEDED(NtWow64ReadVirtualMemory64(handle, pp + offset, ref us, Marshal.SizeOf(us), IntPtr.Zero), he)) | 
|   |  | 337 |  |         { | 
|   | 0 | 338 |  |             if (us.Buffer != 0) | 
|   |  | 339 |  |             { | 
|   | 0 | 340 |  |                 if (us.Length == 0) | 
|   |  | 341 |  |                 { | 
|   | 0 | 342 |  |                     return string.Empty; | 
|   |  | 343 |  |                 } | 
|   |  | 344 |  |  | 
|   | 0 | 345 |  |                 string lpBuffer = us.GetEmptyBuffer(); | 
|   | 0 | 346 |  |                 if (SUCCEEDED(NtWow64ReadVirtualMemory64(handle, us.Buffer, lpBuffer, us.Length, IntPtr.Zero), he)) | 
|   |  | 347 |  |                 { | 
|   | 0 | 348 |  |                     return lpBuffer; | 
|   |  | 349 |  |                 } | 
|   |  | 350 |  |             } | 
|   |  | 351 |  |         } | 
|   |  | 352 |  |  | 
|   | 0 | 353 |  |         return null; | 
|   |  | 354 |  |     } | 
|   |  | 355 |  |  | 
|   |  | 356 |  |     private static int GetInt32(SafeProcessHandle handle, IntPtr pp, int offset, Peb he) | 
|   |  | 357 |  |     { | 
|   | 2 | 358 |  |         var ptr = IntPtr.Zero; | 
|   | 2 | 359 |  |         if (SUCCEEDED(ReadProcessMemory(handle, pp + offset, ref ptr, new IntPtr(IntPtr.Size), IntPtr.Zero), he)) | 
|   |  | 360 |  |         { | 
|   | 2 | 361 |  |             return ptr.ToInt32(); | 
|   |  | 362 |  |         } | 
|   |  | 363 |  |  | 
|   | 0 | 364 |  |         return 0; | 
|   |  | 365 |  |     } | 
|   |  | 366 |  |  | 
|   |  | 367 |  |     private static string? GetString(SafeProcessHandle handle, IntPtr pp, int offset, Peb he) | 
|   |  | 368 |  |     { | 
|   | 2 | 369 |  |         var us = new UNICODE_STRING(); | 
|   | 2 | 370 |  |         if (SUCCEEDED(ReadProcessMemory(handle, pp + offset, ref us, new IntPtr(Marshal.SizeOf(us)), IntPtr.Zero), he)) | 
|   |  | 371 |  |         { | 
|   | 2 | 372 |  |             if (us.Buffer != IntPtr.Zero) | 
|   |  | 373 |  |             { | 
|   | 2 | 374 |  |                 if (us.Length == 0) | 
|   |  | 375 |  |                 { | 
|   | 2 | 376 |  |                     return string.Empty; | 
|   |  | 377 |  |                 } | 
|   |  | 378 |  |  | 
|   | 2 | 379 |  |                 string lpBuffer = us.GetEmptyBuffer(); | 
|   | 2 | 380 |  |                 if (SUCCEEDED(ReadProcessMemory(handle, us.Buffer, lpBuffer, new IntPtr(us.Length), IntPtr.Zero), he)) | 
|   |  | 381 |  |                 { | 
|   | 2 | 382 |  |                     return lpBuffer; | 
|   |  | 383 |  |                 } | 
|   |  | 384 |  |             } | 
|   |  | 385 |  |         } | 
|   |  | 386 |  |  | 
|   | 0 | 387 |  |         return null; | 
|   |  | 388 |  |     } | 
|   |  | 389 |  |  | 
|   |  | 390 |  |     /// <summary> | 
|   |  | 391 |  |     /// Checks if <paramref name="status"/> is success, otherwise sets <c><paramref name="he"/>.SetError(errorCode: <par | 
|   |  | 392 |  |     /// </summary> | 
|   |  | 393 |  |     private static bool SUCCEEDED(uint status, Peb he, [CallerMemberName] string? callerName = null, [CallerFilePath] st | 
|   |  | 394 |  |     { | 
|   | 2 | 395 |  |         if (status != STATUS_SUCCESS) | 
|   |  | 396 |  |         { | 
|   | 0 | 397 |  |             he.SetError(errorCode: (int)status); | 
|   | 0 | 398 |  |             return false; | 
|   |  | 399 |  |         } | 
|   |  | 400 |  |  | 
|   | 2 | 401 |  |         return true; | 
|   |  | 402 |  |     } | 
|   |  | 403 |  |  | 
|   |  | 404 |  |     /// <summary> | 
|   |  | 405 |  |     /// Checks if <paramref name="status"/> is success, otherwise sets <c><paramref name="he"/>.SetError(errorCode: <par | 
|   |  | 406 |  |     /// </summary> | 
|   |  | 407 |  |     private static bool SUCCEEDED(int status, Peb he, [CallerMemberName] string? callerName = null, [CallerFilePath] str | 
|   |  | 408 |  |     { | 
|   | 2 | 409 |  |         if (status != STATUS_SUCCESS) | 
|   |  | 410 |  |         { | 
|   | 0 | 411 |  |             he.SetError(errorCode: status); | 
|   | 0 | 412 |  |             return false; | 
|   |  | 413 |  |         } | 
|   |  | 414 |  |  | 
|   | 2 | 415 |  |         return true; | 
|   |  | 416 |  |     } | 
|   |  | 417 |  |  | 
|   |  | 418 |  |     /// <summary> | 
|   |  | 419 |  |     /// Checks if <paramref name="status"/> is success, otherwise sets <c><paramref name="he"/>.SetError(errorCode: <see | 
|   |  | 420 |  |     /// </summary> | 
|   |  | 421 |  |     private static bool SUCCEEDED(bool result, Peb he, [CallerMemberName] string? callerName = null, [CallerFilePath] st | 
|   |  | 422 |  |     { | 
|   | 2 | 423 |  |         if (!result) | 
|   |  | 424 |  |         { | 
|   | 2 | 425 |  |             he.SetError(errorCode: Marshal.GetLastWin32Error()); | 
|   | 2 | 426 |  |             return false; | 
|   |  | 427 |  |         } | 
|   |  | 428 |  |  | 
|   | 2 | 429 |  |         return true; | 
|   |  | 430 |  |     } | 
|   |  | 431 |  | } |