according2me
according2me

Reputation: 489

Read environment variables from a process in C#

I want to read the environment variables of process B from C# code in process A. I have seen some solutions for this in C++ but haven't tried adapting these to C#.

Is this possible from C#, and if not, has anyone wrapped a C++ solution yet?

Upvotes: 4

Views: 4490

Answers (3)

jifb
jifb

Reputation: 342

Linux solution (32 and 64 bit can be freely mixed):

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

public static class ProcessCommandLine
{


    public static int Retrieve(Process process, out string commandLine, out List<string> environment)
    {
        int pid = process.Id;
        try
        {
            commandLine = File.ReadAllText("/proc/" + pid + "/cmdline").Replace('\0', ' ');
            environment = File.ReadAllText("/proc/" + pid + "/environ").Split('\0').ToList();
            return 0;
        }
        catch (Exception e)
        {
            commandLine = null; environment = null;
            Console.WriteLine("ProcessCommandLine: Cannot read file - maybe you have to 'sudo mount -t procfs none /proc'? Maybe you have insufficient rights?");
            Console.WriteLine("ProcessCommandLine: Exception was: " + e.GetType() + ": " + e.Message);
        }
        
        return -11;
    }
}

Upvotes: 0

jifb
jifb

Reputation: 342

Windows solution (32 bit CLR, can access 32 and 64 bit processes), slightly modified from https://stackoverflow.com/a/46006415 :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;


// Slightly modified from
    // https://stackoverflow.com/questions/2633628/can-i-get-command-line-arguments-of-other-processes-from-net-c
    // https://stackoverflow.com/a/46006415

public static class ProcessCommandLine
{
    private static class Win32Native
    {
        public const uint PROCESS_BASIC_INFORMATION = 0;

        [Flags]
        public enum OpenProcessDesiredAccessFlags : uint
        {
            PROCESS_VM_READ = 0x0010,
            PROCESS_QUERY_INFORMATION = 0x0400,
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ProcessBasicInformation
        {
            public IntPtr Reserved1;
            public IntPtr PebBaseAddress;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public IntPtr[] Reserved2;
            public IntPtr UniqueProcessId;
            public IntPtr ParentProcessId;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ProcessBasicInformation64
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public IntPtr[] Reserved1;
            public UInt64 PebBaseAddress;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public IntPtr[] Reserved2;
            public IntPtr UniqueProcessId;
            public IntPtr Reserved3;
            public IntPtr ParentProcessId;
            public IntPtr Reserved4;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct UnicodeString
        {
            public ushort Length;
            public ushort MaximumLength;
            public IntPtr Buffer;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct UnicodeString64
        {
            public ushort Length;
            public ushort MaximumLength;
            public UInt32 __padding;
            public UInt64 Buffer;
        }

        // This is not the real struct!
        // I faked it to get ProcessParameters address.
        // Actual struct definition:
        // https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
        [StructLayout(LayoutKind.Sequential)]
        public struct PEB
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public IntPtr[] Reserved;
            public IntPtr ProcessParameters;
        }
        [StructLayout(LayoutKind.Sequential)]
        public struct PEB64
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public IntPtr[] Reserved;
            public UInt64 ProcessParameters;
        }


        /*
         * https://stackoverflow.com/a/63222041

Left:                          Right:
32-bit struct           64-bit struct

 0
  ULONG MaximumLength;             0
  ULONG Length;                    4
  ULONG Flags;                     8
  ULONG DebugFlags;               0c
10
  PVOID ConsoleHandle;            10
  ULONG ConsoleFlags;             18 +4
  PVOID StdInputHandle;           20
  PVOID StdOutputHandle;          28
20
  PVOID StdErrorHandle;           30
  UNICODE_STRING CurrentDirectoryPath;   38
  PVOID CurrentDirectoryHandle;   48
30
  UNICODE_STRING DllPath;         50
  UNICODE_STRING ImagePathName;   60
40
  UNICODE_STRING CommandLine      70
  PVOID Environment;              80
  ULONG StartingPositionLeft;
50
  ULONG StartingPositionTop;\
  ULONG Width;\
  ULONG Height;\
  ULONG CharWidth;\
60
  ULONG CharHeight;\
  ULONG ConsoleTextAttributes;\
  ULONG WindowFlags;\
  ULONG ShowWindowFlags;\
70
  UNICODE_STRING WindowTitle;     b0
  UNICODE_STRING DesktopName;     c0
80
  UNICODE_STRING ShellInfo;       d0
  UNICODE_STRING RuntimeData;     e0
90
  RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[32];\
  ULONG EnvironmentSize;\
        */


        [StructLayout(LayoutKind.Sequential)]
        public struct RtlUserProcessParameters
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public byte[] Reserved1;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
            public IntPtr[] Reserved2;
            public UnicodeString ImagePathName;
            public UnicodeString CommandLine;
            public IntPtr Environment;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]
            public IntPtr[] Reserved3; // StartingPositionLeft, -Top, Width, Height, CharWidth, -Height, ConsoleTextAttributes, WindowFlags, ShowWindowFlags
            public UnicodeString WindowTitle;
            public UnicodeString DesktopName;
            public UnicodeString ShellInfo;
            public UnicodeString RuntimeData;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32 * 4)]
            public IntPtr[] Reserved4;
            public uint EnvironmentSize;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct RtlUserProcessParameters64
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public byte[] Reserved1;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
            public IntPtr[] Reserved2;
            public UnicodeString64 CurrentDirectoryPath;
            public UnicodeString64 DllPath;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public IntPtr[] Reserved2b;
            public UnicodeString64 ImagePathName;
            public UnicodeString64 CommandLine;
            public UInt64 Environment;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]
            public IntPtr[] Reserved3; // StartingPositionLeft, -Top, Width, Height, CharWidth, -Height, ConsoleTextAttributes, WindowFlags, ShowWindowFlags
            public UnicodeString64 WindowTitle;
            public UnicodeString64 DesktopName;
            public UnicodeString64 ShellInfo;
            public UnicodeString64 RuntimeData;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32 * 6)]
            public IntPtr[] Reserved4;
            public uint EnvironmentSize;
        }


        [DllImport("ntdll.dll")]
        public static extern uint NtQueryInformationProcess(
            IntPtr ProcessHandle,
            uint ProcessInformationClass,
            IntPtr ProcessInformation,
            uint ProcessInformationLength,
            out uint ReturnLength);

        [DllImport("ntdll.dll")]
        public static extern uint NtWow64QueryInformationProcess64(
            IntPtr ProcessHandle,
            uint ProcessInformationClass,
            IntPtr ProcessInformation,
            uint ProcessInformationLength,
            out uint ReturnLength);


        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenProcess(
            OpenProcessDesiredAccessFlags dwDesiredAccess,
            [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
            uint dwProcessId);


        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool ReadProcessMemory(
            IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer,
            uint nSize, out uint lpNumberOfBytesRead);



        [DllImport("ntdll.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool NtWow64ReadVirtualMemory64(
            IntPtr hProcess, UInt64 lpBaseAddress, IntPtr lpBuffer,
            UInt64 nSize, out UInt64 lpNumberOfBytesRead);

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

        [DllImport("shell32.dll", SetLastError = true,
            CharSet = CharSet.Unicode, EntryPoint = "CommandLineToArgvW")]
        public static extern IntPtr CommandLineToArgv(string lpCmdLine, out int pNumArgs);
    }


    private static bool ReadProcessMemory(IntPtr hProcess, UInt64 lpBaseAddress, IntPtr mem, UInt32 sz, bool force64)
    {
        if (!force64 && (ulong)0x100000000 > lpBaseAddress)
            return Win32Native.ReadProcessMemory(hProcess, (IntPtr)lpBaseAddress, mem, sz, out UInt32 read) && read == sz;

        Win32Native.NtWow64ReadVirtualMemory64(hProcess, lpBaseAddress, mem, sz, out UInt64 len64);
        return len64 == sz;
    }

    private static bool ReadStructFromProcessMemory<TStruct>(
        IntPtr hProcess, IntPtr lpBaseAddress, out TStruct val, bool bit64 = false)
    {
        return ReadStructFromProcessMemory<TStruct>(hProcess, (UInt64)lpBaseAddress, out val, bit64);
    }
    private static bool ReadStructFromProcessMemory<TStruct>(
        IntPtr hProcess, UInt64 lpBaseAddress, out TStruct val, bool bit64 = false)
    {
        val = default;
        var structSize = Marshal.SizeOf<TStruct>();
        var mem = Marshal.AllocHGlobal(structSize);
        try
        {
            bool ret = ReadProcessMemory(hProcess, lpBaseAddress, mem, (UInt32)structSize, bit64);

            if (ret)
            {
                val = Marshal.PtrToStructure<TStruct>(mem);
                return true;
            }
        }
        finally
        {
            Marshal.FreeHGlobal(mem);
        }
        return false;
    }

    public static string ErrorToString(int error) =>
        new string[]
        {
        "Success",
        "Failed to open process for reading",
        "Failed to query process information",
        "PEB address was null",
        "Failed to read PEB information",
        "Failed to read process parameters",
        "Failed to read command line from process",
        "Failed to read environment from process",
        }[Math.Abs(error)];

    public static int Retrieve(Process process, out string commandLine, out List<string> environment)
    {
        int rc = 0;
        commandLine = null;
        environment = null;
        var hProcess = Win32Native.OpenProcess(
            Win32Native.OpenProcessDesiredAccessFlags.PROCESS_QUERY_INFORMATION |
            Win32Native.OpenProcessDesiredAccessFlags.PROCESS_VM_READ, false, (uint)process.Id);
        if (hProcess != IntPtr.Zero)
        {
            try
            {
                var sizePBI = Marshal.SizeOf<Win32Native.ProcessBasicInformation>();
                var sizePBI64 = Marshal.SizeOf<Win32Native.ProcessBasicInformation64>();
                var memPBI = Marshal.AllocHGlobal(sizePBI64);
                try
                {
                    bool bit64;
                    var ret = Win32Native.NtQueryInformationProcess(
                        hProcess, Win32Native.PROCESS_BASIC_INFORMATION, memPBI,
                        (uint)sizePBI, out var len);

                    if (0 == ret)
                    {
                        var pbiInfo64 = new Win32Native.ProcessBasicInformation64();
                        var pbiInfo = Marshal.PtrToStructure<Win32Native.ProcessBasicInformation>(memPBI);
                        if (pbiInfo.PebBaseAddress != IntPtr.Zero)
                        {
                            bit64 = false;
                        }
                        else
                        {
                            bit64 = true;
                            ret = Win32Native.NtWow64QueryInformationProcess64(
                                hProcess, Win32Native.PROCESS_BASIC_INFORMATION, memPBI,
                                (uint)sizePBI64, out len);
                            pbiInfo64 = Marshal.PtrToStructure<Win32Native.ProcessBasicInformation64>(memPBI);
                        }

                        if (pbiInfo64.PebBaseAddress != (UInt64)0)
                        {
                            Win32Native.PEB64 pebInfo64;
                            Win32Native.PEB pebInfo;
                            pebInfo.ProcessParameters = (IntPtr)0; pebInfo64.ProcessParameters = (UInt64)0;
                            bool ok;
                            if (bit64)
                                ok = ReadStructFromProcessMemory<Win32Native.PEB64>(hProcess, pbiInfo64.PebBaseAddress, out pebInfo64, bit64);
                            else
                                ok = ReadStructFromProcessMemory<Win32Native.PEB>(hProcess, pbiInfo.PebBaseAddress, out pebInfo, bit64);

                            if (ok)
                            {
                                UInt32 CommandLineSize = 0;
                                UInt64 CommandLineBuf = 0;
                                UInt32 EnvironmentSize = 0;
                                UInt64 EnvironmentBuf = 0;

                                if (bit64)
                                {
                                    ok = ReadStructFromProcessMemory<Win32Native.RtlUserProcessParameters64>(hProcess, pebInfo64.ProcessParameters, out var ruppInfo64, bit64);
                                    CommandLineSize = (UInt32)ruppInfo64.CommandLine.MaximumLength;
                                    CommandLineBuf = ruppInfo64.CommandLine.Buffer;
                                    EnvironmentSize = (UInt32)ruppInfo64.EnvironmentSize;
                                    EnvironmentBuf = ruppInfo64.Environment;
                                }
                                else
                                {
                                    ok = ReadStructFromProcessMemory<Win32Native.RtlUserProcessParameters>(hProcess, (UInt64)pebInfo.ProcessParameters, out var ruppInfo, bit64);
                                    CommandLineSize = ruppInfo.CommandLine.MaximumLength;
                                    CommandLineBuf = (UInt64)ruppInfo.CommandLine.Buffer;
                                    EnvironmentSize = ruppInfo.EnvironmentSize;
                                    EnvironmentBuf = (UInt64)ruppInfo.Environment;
                                }

                                if (ok)
                                {
                                    var memCL = Marshal.AllocHGlobal((IntPtr)CommandLineSize);
                                    try
                                    {
                                        if (ReadProcessMemory(hProcess, CommandLineBuf, memCL, CommandLineSize, bit64))
                                        {
                                            commandLine = Marshal.PtrToStringUni(memCL);
                                            rc = 0;
                                        }
                                        else
                                        {
                                            // couldn't read command line buffer
                                            rc = -6;
                                        }
                                    }
                                    finally
                                    {
                                        Marshal.FreeHGlobal(memCL);
                                    }

                                    memCL = Marshal.AllocHGlobal((IntPtr)EnvironmentSize);
                                    try
                                    {
                                        if (ReadProcessMemory(hProcess, EnvironmentBuf, memCL, EnvironmentSize, bit64))
                                        {
                                            environment = new List<string>(40);
                                            IntPtr readPtr = memCL;
                                            while (EnvironmentSize > 0 && Marshal.ReadInt16(readPtr) != 0)
                                            {
                                                string slice = Marshal.PtrToStringUni(readPtr);
                                                environment.Add(slice);
                                                uint size = 0;
                                                do
                                                {
                                                    size += 2;
                                                    readPtr += 2;
                                                }
                                                while (Marshal.ReadInt16(readPtr) != 0);
                                                size += 2;
                                                readPtr += 2;
                                                EnvironmentSize -= size;
                                            }
                                            rc = 0;
                                        }
                                        else
                                        {
                                            // couldn't read command line buffer
                                            rc = -7;
                                        }

                                    }
                                    finally
                                    {
                                        Marshal.FreeHGlobal(memCL);
                                    }
                                }
                                else
                                {
                                    // couldn't read ProcessParameters
                                    rc = -5;
                                }
                            }
                            else
                            {
                                // couldn't read PEB information
                                rc = -4;
                            }
                        }
                        else
                        {
                            // PebBaseAddress is null
                            rc = -3;
                        }
                    }
                    else
                    {
                        // NtQueryInformationProcess failed
                        rc = -2;
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(memPBI);
                }
            }
            finally
            {
                Win32Native.CloseHandle(hProcess);
            }
        }
        else
        {
            // couldn't open process for VM read
            rc = -1;
        }
        return rc;
    }

    public static IReadOnlyList<string> CommandLineToArgs(string commandLine)
    {
        if (string.IsNullOrEmpty(commandLine)) { return new string[] { }; }

        var argv = Win32Native.CommandLineToArgv(commandLine, out var argc);
        if (argv == IntPtr.Zero)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        try
        {
            var args = new string[argc];
            for (var i = 0; i < args.Length; ++i)
            {
                var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
                args[i] = Marshal.PtrToStringUni(p);
            }
            return args.ToList().AsReadOnly();
        }
        finally
        {
            Marshal.FreeHGlobal(argv);
        }
    }
}

Upvotes: 0

Omer Raviv
Omer Raviv

Reputation: 11827

I've skimmed through the links provided by Isalamon and Daniel Hilgarth, and also the code in CLR Profiler's GetServicesEnvironment() method which seems to be doing the same thing, and after a bit of testing found that the most reliable solution is Oleksiy's code (pure C# with P/Invoke) which he published in this blog post. It still has the limitation, where you have to be a 64bit process to read the env vars of another 64bit process.

Upvotes: 1

Related Questions