Skip to content

Instantly share code, notes, and snippets.

@mrousavy
Created May 21, 2019 12:49
Show Gist options
  • Select an option

  • Save mrousavy/cc9447f9076fdb768055497d02d72893 to your computer and use it in GitHub Desktop.

Select an option

Save mrousavy/cc9447f9076fdb768055497d02d72893 to your computer and use it in GitHub Desktop.
Process Extension to read environment variables of a process
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