ProcessHelper.cs

using System;
using System.Runtime.InteropServices;
using System.Text;
 
public class TTProcessHelper
{
    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_BASIC_INFORMATION
    {
        public IntPtr Reserved1;
        public IntPtr PebBaseAddress;
        public IntPtr Reserved2_0;
        public IntPtr Reserved2_1;
        public IntPtr UniqueProcessId;
        public IntPtr Reserved3;
    }
 
    [DllImport("ntdll.dll")]
    private static extern int NtQueryInformationProcess(
        IntPtr hProcess, int pic, ref PROCESS_BASIC_INFORMATION pbi, int cb, out int pSize);
 
    [DllImport("kernel32.dll")]
    private static extern bool ReadProcessMemory(
        IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);
 
    [DllImport("kernel32.dll")]
    private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
 
    [DllImport("kernel32.dll")]
    private static extern bool CloseHandle(IntPtr hObject);
 
    [DllImport("user32.dll")]
    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
 
    [DllImport("user32.dll")]
    public static extern bool IsWindowVisible(IntPtr hWnd);
 
    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();
 
    [DllImport("user32.dll")]
    public static extern bool SetForegroundWindow(IntPtr hWnd);
 
    public const int SW_HIDE = 0;
    public const int SW_SHOW = 5;
    public const int SW_MINIMIZE = 6;
    public const int SW_RESTORE = 9;
 
    private const int PROCESS_QUERY_INFORMATION = 0x0400;
    private const int PROCESS_VM_READ = 0x0010;
 
    /// <summary>
    /// Reads the current working directory of a process by inspecting its PEB.
    /// Works for same-user processes without elevation.
    /// Returns null on failure.
    /// </summary>
    public static string GetWorkingDirectory(int pid)
    {
        IntPtr hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
        if (hProcess == IntPtr.Zero)
            return null;
 
        try
        {
            var pbi = new PROCESS_BASIC_INFORMATION();
            int size;
            if (NtQueryInformationProcess(hProcess, 0, ref pbi, Marshal.SizeOf(pbi), out size) != 0)
                return null;
 
            bool is64 = IntPtr.Size == 8;
 
            // Read ProcessParameters pointer from PEB
            // x64: offset 0x20, x86: offset 0x10
            int paramOffset = is64 ? 0x20 : 0x10;
            byte[] ptrBuf = new byte[IntPtr.Size];
            int bytesRead;
 
            if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress + paramOffset, ptrBuf, ptrBuf.Length, out bytesRead))
                return null;
 
            IntPtr processParameters = is64
                ? (IntPtr)BitConverter.ToInt64(ptrBuf, 0)
                : (IntPtr)BitConverter.ToInt32(ptrBuf, 0);
 
            // CurrentDirectory.DosPath is a UNICODE_STRING
            // x64: offset 0x38, x86: offset 0x24 in RTL_USER_PROCESS_PARAMETERS
            int cwdOffset = is64 ? 0x38 : 0x24;
 
            // Read UNICODE_STRING structure (Length: ushort, MaxLength: ushort, padding, Buffer: IntPtr)
            int usSize = is64 ? 16 : 8; // UNICODE_STRING size with alignment
            byte[] usBuf = new byte[usSize];
 
            if (!ReadProcessMemory(hProcess, processParameters + cwdOffset, usBuf, usBuf.Length, out bytesRead))
                return null;
 
            ushort len = BitConverter.ToUInt16(usBuf, 0);
            if (len == 0 || len > 2048)
                return null;
 
            IntPtr strPtr = is64
                ? (IntPtr)BitConverter.ToInt64(usBuf, 8)
                : (IntPtr)BitConverter.ToInt32(usBuf, 4);
 
            byte[] strBuf = new byte[len];
            if (!ReadProcessMemory(hProcess, strPtr, strBuf, len, out bytesRead))
                return null;
 
            string result = Encoding.Unicode.GetString(strBuf, 0, bytesRead);
            // Remove trailing backslash (except for root like C:\)
            if (result.Length > 3 && result.EndsWith("\\"))
                result = result.Substring(0, result.Length - 1);
 
            return result;
        }
        catch
        {
            return null;
        }
        finally
        {
            CloseHandle(hProcess);
        }
    }
}