Created
May 21, 2019 12:49
-
-
Save mrousavy/cc9447f9076fdb768055497d02d72893 to your computer and use it in GitHub Desktop.
Process Extension to read environment variables of a process
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Collections.Specialized; | |
| using System.Diagnostics; | |
| using System.Runtime.CompilerServices; | |
| using System.Runtime.InteropServices; | |
| using System.Text; | |
| namespace GameFinder | |
| { | |
| public static class ProcessExtensions | |
| { | |
| public static Dictionary<string, string> ReadEnvironmentVariables(this Process process) => | |
| GetEnvironmentVariablesForProcessHandle(process.Handle); | |
| public static Dictionary<string, string> TryReadEnvironmentVariables(this Process process) | |
| { | |
| try | |
| { | |
| return ReadEnvironmentVariables(process); | |
| } catch | |
| { | |
| return null; | |
| } | |
| } | |
| private static Dictionary<string, string> GetEnvironmentVariablesForProcessHandle(IntPtr hProcess) | |
| { | |
| var penv = GetPenv(hProcess); | |
| const int maxEnvSize = 32767; | |
| byte[] envData; | |
| if (penv.CanBeRepresentedByNativePointer) | |
| { | |
| if (!HasReadAccess(hProcess, penv, out int dataSize)) | |
| { | |
| throw new Exception("Unable to read environment block."); | |
| } | |
| if (dataSize > maxEnvSize) | |
| { | |
| dataSize = maxEnvSize; | |
| } | |
| envData = new byte[dataSize]; | |
| var resLen = IntPtr.Zero; | |
| bool read = WindowsApi.ReadProcessMemory( | |
| hProcess, | |
| penv, | |
| envData, | |
| new IntPtr(dataSize), | |
| ref resLen); | |
| if (!read || (int) resLen != dataSize) | |
| { | |
| throw new Exception("Unable to read environment block data."); | |
| } | |
| } else if (penv.Size == 8 && IntPtr.Size == 4) | |
| { | |
| // Accessing 64 bit process under 32 bit host. | |
| if (!HasReadAccessWow64(hProcess, penv.ToInt64(), out int dataSize)) | |
| { | |
| throw new Exception("Unable to read environment block with WOW64 API."); | |
| } | |
| if (dataSize > maxEnvSize) | |
| { | |
| dataSize = maxEnvSize; | |
| } | |
| envData = new byte[dataSize]; | |
| long resLen = 0; | |
| int result = WindowsApi.NtWow64ReadVirtualMemory64( | |
| hProcess, | |
| penv.ToInt64(), | |
| envData, | |
| dataSize, | |
| ref resLen); | |
| if (result != WindowsApi.STATUS_SUCCESS || resLen != dataSize) | |
| { | |
| throw new Exception("Unable to read environment block data with WOW64 API."); | |
| } | |
| } else | |
| { | |
| throw new Exception("Unable to access process memory due to unsupported bitness cardinality."); | |
| } | |
| return RawEnvironmentVariablesToDictionary(envData); | |
| } | |
| private static Dictionary<string, string> RawEnvironmentVariablesToDictionary(byte[] env) | |
| { | |
| var result = new Dictionary<string, string>(); | |
| int len = env.Length; | |
| if (len < 4) | |
| { | |
| return result; | |
| } | |
| int n = len - 3; | |
| for (int i = 0; i < n; ++i) | |
| { | |
| byte c1 = env[i]; | |
| byte c2 = env[i + 1]; | |
| byte c3 = env[i + 2]; | |
| byte c4 = env[i + 3]; | |
| if (c1 == 0 && c2 == 0 && c3 == 0 && c4 == 0) | |
| { | |
| len = i + 3; | |
| break; | |
| } | |
| } | |
| var environmentCharArray = Encoding.Unicode.GetChars(env, 0, len); | |
| for (int i = 0; i < environmentCharArray.Length; i++) | |
| { | |
| int startIndex = i; | |
| while (environmentCharArray[i] != '=' && environmentCharArray[i] != '\0') | |
| { | |
| i++; | |
| } | |
| if (environmentCharArray[i] != '\0') | |
| { | |
| if (i - startIndex == 0) | |
| { | |
| while (environmentCharArray[i] != '\0') | |
| { | |
| i++; | |
| } | |
| } else | |
| { | |
| string str = new string(environmentCharArray, startIndex, i - startIndex); | |
| i++; | |
| int num3 = i; | |
| while (environmentCharArray[i] != '\0') | |
| { | |
| i++; | |
| } | |
| string str2 = new string(environmentCharArray, num3, i - num3); | |
| result[str] = str2; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| private static bool TryReadIntPtr32(IntPtr hProcess, IntPtr ptr, out IntPtr readPtr) | |
| { | |
| bool result; | |
| RuntimeHelpers.PrepareConstrainedRegions(); | |
| try | |
| { } finally | |
| { | |
| const int dataSize = sizeof(int); | |
| var data = Marshal.AllocHGlobal(dataSize); | |
| var resLen = IntPtr.Zero; | |
| bool b = WindowsApi.ReadProcessMemory( | |
| hProcess, | |
| ptr, | |
| data, | |
| new IntPtr(dataSize), | |
| ref resLen); | |
| readPtr = new IntPtr(Marshal.ReadInt32(data)); | |
| Marshal.FreeHGlobal(data); | |
| if (!b || (int) resLen != dataSize) | |
| { | |
| result = false; | |
| } else | |
| { | |
| result = true; | |
| } | |
| } | |
| return result; | |
| } | |
| private static bool TryReadIntPtr(IntPtr hProcess, IntPtr ptr, out IntPtr readPtr) | |
| { | |
| bool result; | |
| RuntimeHelpers.PrepareConstrainedRegions(); | |
| try | |
| { } finally | |
| { | |
| int dataSize = IntPtr.Size; | |
| var data = Marshal.AllocHGlobal(dataSize); | |
| var resLen = IntPtr.Zero; | |
| bool b = WindowsApi.ReadProcessMemory( | |
| hProcess, | |
| ptr, | |
| data, | |
| new IntPtr(dataSize), | |
| ref resLen); | |
| readPtr = Marshal.ReadIntPtr(data); | |
| Marshal.FreeHGlobal(data); | |
| if (!b || (int) resLen != dataSize) | |
| { | |
| result = false; | |
| } else | |
| { | |
| result = true; | |
| } | |
| } | |
| return result; | |
| } | |
| private static bool TryReadIntPtrWow64(IntPtr hProcess, long ptr, out long readPtr) | |
| { | |
| bool result; | |
| RuntimeHelpers.PrepareConstrainedRegions(); | |
| try | |
| { } finally | |
| { | |
| const int dataSize = sizeof(long); | |
| var data = Marshal.AllocHGlobal(dataSize); | |
| long resLen = 0; | |
| int status = WindowsApi.NtWow64ReadVirtualMemory64( | |
| hProcess, | |
| ptr, | |
| data, | |
| dataSize, | |
| ref resLen); | |
| readPtr = Marshal.ReadInt64(data); | |
| Marshal.FreeHGlobal(data); | |
| if (status != WindowsApi.STATUS_SUCCESS || resLen != dataSize) | |
| { | |
| result = false; | |
| } else | |
| { | |
| result = true; | |
| } | |
| } | |
| return result; | |
| } | |
| private static UniPtr GetPenv(IntPtr hProcess) | |
| { | |
| int processBitness = GetProcessBitness(hProcess); | |
| if (processBitness == 64) | |
| { | |
| if (Environment.Is64BitProcess) | |
| { | |
| // Accessing 64 bit process under 64 bit host. | |
| IntPtr pPeb = GetPeb64(hProcess); | |
| if (!TryReadIntPtr(hProcess, pPeb + 0x20, out var ptr)) | |
| { | |
| throw new Exception("Unable to read PEB."); | |
| } | |
| if (!TryReadIntPtr(hProcess, ptr + 0x80, out var penv)) | |
| { | |
| throw new Exception("Unable to read RTL_USER_PROCESS_PARAMETERS."); | |
| } | |
| return penv; | |
| } else | |
| { | |
| // Accessing 64 bit process under 32 bit host. | |
| var pPeb = GetPeb64(hProcess); | |
| if (!TryReadIntPtrWow64(hProcess, pPeb.ToInt64() + 0x20, out long ptr)) | |
| { | |
| throw new Exception("Unable to read PEB."); | |
| } | |
| if (!TryReadIntPtrWow64(hProcess, ptr + 0x80, out long penv)) | |
| { | |
| throw new Exception("Unable to read RTL_USER_PROCESS_PARAMETERS."); | |
| } | |
| return new UniPtr(penv); | |
| } | |
| } | |
| { | |
| // Accessing 32 bit process under 32 bit host. | |
| var pPeb = GetPeb32(hProcess); | |
| if (!TryReadIntPtr32(hProcess, pPeb + 0x10, out var ptr)) | |
| { | |
| throw new Exception("Unable to read PEB."); | |
| } | |
| if (!TryReadIntPtr32(hProcess, ptr + 0x48, out var penv)) | |
| { | |
| throw new Exception("Unable to read RTL_USER_PROCESS_PARAMETERS."); | |
| } | |
| return penv; | |
| } | |
| } | |
| private static int GetProcessBitness(IntPtr hProcess) | |
| { | |
| if (Environment.Is64BitOperatingSystem) | |
| { | |
| if (!WindowsApi.IsWow64Process(hProcess, out bool wow64)) | |
| { | |
| return 32; | |
| } | |
| if (wow64) | |
| { | |
| return 32; | |
| } | |
| return 64; | |
| } | |
| return 32; | |
| } | |
| private static IntPtr GetPeb32(IntPtr hProcess) | |
| { | |
| if (Environment.Is64BitProcess) | |
| { | |
| var ptr = IntPtr.Zero; | |
| int resLen = 0; | |
| int pbiSize = IntPtr.Size; | |
| int status = WindowsApi.NtQueryInformationProcess( | |
| hProcess, | |
| WindowsApi.ProcessWow64Information, | |
| ref ptr, | |
| pbiSize, | |
| ref resLen); | |
| if (resLen != pbiSize) | |
| { | |
| throw new Exception("Unable to query process information."); | |
| } | |
| return ptr; | |
| } | |
| return GetPebNative(hProcess); | |
| } | |
| private static IntPtr GetPebNative(IntPtr hProcess) | |
| { | |
| var pbi = new WindowsApi.PROCESS_BASIC_INFORMATION(); | |
| int resLen = 0; | |
| int pbiSize = Marshal.SizeOf(pbi); | |
| int status = WindowsApi.NtQueryInformationProcess( | |
| hProcess, | |
| WindowsApi.ProcessBasicInformation, | |
| ref pbi, | |
| pbiSize, | |
| ref resLen); | |
| if (resLen != pbiSize) | |
| { | |
| throw new Exception("Unable to query process information."); | |
| } | |
| return pbi.PebBaseAddress; | |
| } | |
| private static UniPtr GetPeb64(IntPtr hProcess) | |
| { | |
| if (Environment.Is64BitProcess) | |
| { | |
| return GetPebNative(hProcess); | |
| } | |
| // Get PEB via WOW64 API. | |
| var pbi = new WindowsApi.PROCESS_BASIC_INFORMATION_WOW64(); | |
| int resLen = 0; | |
| int pbiSize = Marshal.SizeOf(pbi); | |
| int status = WindowsApi.NtWow64QueryInformationProcess64( | |
| hProcess, | |
| WindowsApi.ProcessBasicInformation, | |
| ref pbi, | |
| pbiSize, | |
| ref resLen); | |
| if (resLen != pbiSize) | |
| { | |
| throw new Exception("Unable to query process information."); | |
| } | |
| return new UniPtr(pbi.PebBaseAddress); | |
| } | |
| private static bool HasReadAccess(IntPtr hProcess, IntPtr address, out int size) | |
| { | |
| size = 0; | |
| var memInfo = new WindowsApi.MEMORY_BASIC_INFORMATION(); | |
| int result = WindowsApi.VirtualQueryEx( | |
| hProcess, | |
| address, | |
| ref memInfo, | |
| Marshal.SizeOf(memInfo)); | |
| if (result == 0) | |
| { | |
| return false; | |
| } | |
| if (memInfo.Protect == WindowsApi.PAGE_NOACCESS || memInfo.Protect == WindowsApi.PAGE_EXECUTE) | |
| { | |
| return false; | |
| } | |
| try | |
| { | |
| size = Convert.ToInt32(memInfo.RegionSize.ToInt64() - | |
| (address.ToInt64() - memInfo.BaseAddress.ToInt64())); | |
| } catch (OverflowException) | |
| { | |
| return false; | |
| } | |
| if (size <= 0) | |
| { | |
| return false; | |
| } | |
| return true; | |
| } | |
| private static bool HasReadAccessWow64(IntPtr hProcess, long address, out int size) | |
| { | |
| size = 0; | |
| WindowsApi.MEMORY_BASIC_INFORMATION_WOW64 memInfo; | |
| var memInfoType = typeof(WindowsApi.MEMORY_BASIC_INFORMATION_WOW64); | |
| int memInfoLength = Marshal.SizeOf(memInfoType); | |
| const int memInfoAlign = 8; | |
| long resultLength = 0; | |
| int result; | |
| var hMemInfo = Marshal.AllocHGlobal(memInfoLength + memInfoAlign * 2); | |
| try | |
| { | |
| // Align to 64 bits. | |
| var hMemInfoAligned = new IntPtr(hMemInfo.ToInt64() & ~(memInfoAlign - 1L)); | |
| result = WindowsApi.NtWow64QueryVirtualMemory64( | |
| hProcess, | |
| address, | |
| WindowsApi.MEMORY_INFORMATION_CLASS.MemoryBasicInformation, | |
| hMemInfoAligned, | |
| memInfoLength, | |
| ref resultLength); | |
| memInfo = (WindowsApi.MEMORY_BASIC_INFORMATION_WOW64) Marshal.PtrToStructure(hMemInfoAligned, | |
| memInfoType); | |
| } finally | |
| { | |
| Marshal.FreeHGlobal(hMemInfo); | |
| } | |
| if (result != WindowsApi.STATUS_SUCCESS) | |
| { | |
| return false; | |
| } | |
| if (memInfo.Protect == WindowsApi.PAGE_NOACCESS || memInfo.Protect == WindowsApi.PAGE_EXECUTE) | |
| { | |
| return false; | |
| } | |
| try | |
| { | |
| size = Convert.ToInt32(memInfo.RegionSize - (address - memInfo.BaseAddress)); | |
| } catch (OverflowException) | |
| { | |
| return false; | |
| } | |
| if (size <= 0) | |
| { | |
| return false; | |
| } | |
| return true; | |
| } | |
| /// <summary> | |
| /// Universal pointer. | |
| /// </summary> | |
| private struct UniPtr | |
| { | |
| public UniPtr(IntPtr p) | |
| { | |
| Value = p.ToInt64(); | |
| Size = IntPtr.Size; | |
| } | |
| public UniPtr(long p) | |
| { | |
| Value = p; | |
| Size = sizeof(long); | |
| } | |
| public readonly long Value; | |
| public readonly int Size; | |
| public static implicit operator IntPtr(UniPtr p) => new IntPtr(p.Value); | |
| public static implicit operator UniPtr(IntPtr p) => new UniPtr(p); | |
| public override string ToString() => Value.ToString(); | |
| public bool FitsInNativePointer => Size <= IntPtr.Size; | |
| public bool CanBeRepresentedByNativePointer | |
| { | |
| get | |
| { | |
| int actualSize = Size; | |
| if (actualSize == 8) | |
| { | |
| if (Value >> 32 == 0) | |
| { | |
| actualSize = 4; | |
| } | |
| } | |
| return actualSize <= IntPtr.Size; | |
| } | |
| } | |
| public long ToInt64() => Value; | |
| } | |
| private static class WindowsApi | |
| { | |
| public enum MEMORY_INFORMATION_CLASS | |
| { | |
| MemoryBasicInformation | |
| } | |
| public const int ProcessBasicInformation = 0; | |
| public const int ProcessWow64Information = 26; | |
| public const int STATUS_SUCCESS = 0; | |
| public const int PAGE_NOACCESS = 0x01; | |
| public const int PAGE_EXECUTE = 0x10; | |
| [DllImport("ntdll.dll", SetLastError = true)] | |
| public static extern int NtQueryInformationProcess( | |
| IntPtr hProcess, | |
| int pic, | |
| ref PROCESS_BASIC_INFORMATION pbi, | |
| int cb, | |
| ref int pSize); | |
| [DllImport("ntdll.dll", SetLastError = true)] | |
| public static extern int NtQueryInformationProcess( | |
| IntPtr hProcess, | |
| int pic, | |
| ref IntPtr pi, | |
| int cb, | |
| ref int pSize); | |
| [DllImport("ntdll.dll", SetLastError = true)] | |
| public static extern int NtQueryInformationProcess( | |
| IntPtr hProcess, | |
| int pic, | |
| ref long pi, | |
| int cb, | |
| ref int pSize); | |
| [DllImport("ntdll.dll", SetLastError = true)] | |
| public static extern int NtWow64QueryInformationProcess64( | |
| IntPtr hProcess, | |
| int pic, | |
| ref PROCESS_BASIC_INFORMATION_WOW64 pbi, | |
| int cb, | |
| ref int pSize); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool ReadProcessMemory( | |
| IntPtr hProcess, | |
| IntPtr lpBaseAddress, | |
| [Out] byte[] lpBuffer, | |
| IntPtr dwSize, | |
| ref IntPtr lpNumberOfBytesRead); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool ReadProcessMemory( | |
| IntPtr hProcess, | |
| IntPtr lpBaseAddress, | |
| IntPtr lpBuffer, | |
| IntPtr dwSize, | |
| ref IntPtr lpNumberOfBytesRead); | |
| [DllImport("ntdll.dll", SetLastError = true)] | |
| public static extern int NtWow64ReadVirtualMemory64( | |
| IntPtr hProcess, | |
| long lpBaseAddress, | |
| IntPtr lpBuffer, | |
| long dwSize, | |
| ref long lpNumberOfBytesRead); | |
| [DllImport("ntdll.dll", SetLastError = true)] | |
| public static extern int NtWow64ReadVirtualMemory64( | |
| IntPtr hProcess, | |
| long lpBaseAddress, | |
| [Out] byte[] lpBuffer, | |
| long dwSize, | |
| ref long lpNumberOfBytesRead); | |
| [DllImport("kernel32.dll")] | |
| public static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, | |
| ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength); | |
| [DllImport("ntdll.dll")] | |
| public static extern int NtWow64QueryVirtualMemory64( | |
| IntPtr hProcess, | |
| long lpAddress, | |
| MEMORY_INFORMATION_CLASS memoryInformationClass, | |
| IntPtr lpBuffer, // MEMORY_BASIC_INFORMATION_WOW64, pointer must be 64-bit aligned | |
| long memoryInformationLength, | |
| ref long returnLength); | |
| [DllImport("kernel32.dll")] | |
| public static extern bool IsWow64Process(IntPtr hProcess, out bool wow64Process); | |
| [StructLayout(LayoutKind.Sequential, Pack = 1)] | |
| public struct PROCESS_BASIC_INFORMATION | |
| { | |
| public readonly IntPtr Reserved1; | |
| public readonly IntPtr PebBaseAddress; | |
| [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] | |
| public readonly IntPtr[] Reserved2; | |
| public readonly IntPtr UniqueProcessId; | |
| public readonly IntPtr Reserved3; | |
| } | |
| [StructLayout(LayoutKind.Sequential, Pack = 1)] | |
| public struct PROCESS_BASIC_INFORMATION_WOW64 | |
| { | |
| public readonly long Reserved1; | |
| public readonly long PebBaseAddress; | |
| [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] | |
| public readonly long[] Reserved2; | |
| public readonly long UniqueProcessId; | |
| public readonly long Reserved3; | |
| } | |
| [StructLayout(LayoutKind.Sequential)] | |
| public struct MEMORY_BASIC_INFORMATION | |
| { | |
| public IntPtr BaseAddress; | |
| public readonly IntPtr AllocationBase; | |
| public readonly int AllocationProtect; | |
| public IntPtr RegionSize; | |
| public readonly int State; | |
| public readonly int Protect; | |
| public readonly int Type; | |
| } | |
| [StructLayout(LayoutKind.Sequential)] | |
| public struct MEMORY_BASIC_INFORMATION_WOW64 | |
| { | |
| public readonly long BaseAddress; | |
| public readonly long AllocationBase; | |
| public readonly int AllocationProtect; | |
| public readonly long RegionSize; | |
| public readonly int State; | |
| public readonly int Protect; | |
| public readonly int Type; | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment