diff --git a/Dalamud.Bootstrap/GameProcess.cs b/Dalamud.Bootstrap/GameProcess.cs index 3ff3c0855..d0d864131 100644 --- a/Dalamud.Bootstrap/GameProcess.cs +++ b/Dalamud.Bootstrap/GameProcess.cs @@ -3,38 +3,305 @@ using Dalamud.Bootstrap.OS.Windows; using Dalamud.Bootstrap.OS.Windows.Raw; using Microsoft.Win32.SafeHandles; using System; +using System.Runtime.InteropServices; namespace Dalamud.Bootstrap { public sealed partial class GameProcess : IDisposable { - private Process m_process; + private IntPtr m_handle; - // maybe saved acl shit - - private GameProcess(Process process) + public GameProcess(IntPtr handle) { - m_process = process; + m_handle = handle; + } + + ~GameProcess() + { + Dispose(false); } public void Dispose() { - m_process?.Dispose(); - m_process = null!; + Dispose(true); + GC.SuppressFinalize(true); } - // /// - // /// - // /// - // /// A process handle. - // private static void AllowHandleAccess(Process handle) - // { + private void Dispose(bool disposing) + { + if (m_handle != IntPtr.Zero) + { + Kernel32.CloseHandle(m_handle); - // } + m_handle = IntPtr.Zero; + } + } - // private static void DenyHandleAccess(SafeProcessHandle handle) - // { + private static IntPtr OpenProcessHandle(uint pid, uint access) + { + var handle = Kernel32.OpenProcess(access, false, pid); - // } + if (handle == IntPtr.Zero) + { + ProcessException.ThrowLastOsError(); + } + + return handle; + } + + public static GameProcess Open(uint pid) + { + // + var secHandle = OpenProcessHandle(pid, (uint)(PROCESS_ACCESS_RIGHTS.READ_CONTROL | PROCESS_ACCESS_RIGHTS.WRITE_DAC)); + try + { + RelaxProcessHandle(secHandle, (_) => + { + + }); + } + finally + { + Kernel32.CloseHandle(secHandle); + } + } + + private static void RelaxProcessHandle(IntPtr handle, Action scope) + { + // relax shit + unsafe + { + SECURITY_DESCRIPTOR* pSecurityDescOrig; + ACL* pDaclOrig, pDaclRelaxed; + + var error = Advapi32.GetSecurityInfo( + handle, + SE_OBJECT_TYPE.SE_KERNEL_OBJECT, + SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, + null, + null, + &pDaclOrig, + null, + &pSecurityDescOrig + ); + + if (error != 0) + { + throw new ProcessException(); + } + + try + { + scope(handle); + } + finally + { + // Restore permission + + Kernel32.LocalFree(pSecurityDescOrig); + } + } + + + + } + + public void GetSecurityInfo() + { + + var error = Advapi32.GetSecurityInfo(m_handle, SE_OBJECT_TYPE.SE_KERNEL_OBJECT, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, ); + + if (error != 0 /* ERROR_SUCCESS */) + { + throw new ProcessException($"Could not read a security info. (Error {error})"); + } + + + } + + private static void AllowDacl(Process process) + { + + } + + /// + /// Reads process memory. + /// + /// + /// A number of bytes that is actually read. + /// + /// + /// Thrown when failed to read memory. + /// + public int ReadMemory(IntPtr address, Span destination) + { + unsafe + { + fixed (byte* pDest = destination) + { + if (!Kernel32.ReadProcessMemory(Handle, address, pDest, (IntPtr)destination.Length, out var bytesRead)) + { + ProcessException.ThrowLastOsError(); + } + + // This is okay because destination will never be longer than int.Max + return bytesRead.ToInt32(); + } + } + } + + /// + /// Reads the exact number of bytes required to fill the buffer. + /// + /// + /// Thrown when failed to read memory. + /// + public void ReadMemoryExact(IntPtr address, Span destination) + { + var totalBytesRead = 0; + + while (totalBytesRead < destination.Length) + { + var bytesRead = ReadMemory(address + totalBytesRead, destination[totalBytesRead..]); + + if (bytesRead == 0) + { + // prolly page fault; there's not much we can do here + var readBeginAddr = address.ToInt64() + totalBytesRead; + var readEndAddr = address.ToInt64() + destination.Length; + + ProcessException.ThrowLastOsError(); + } + + totalBytesRead += bytesRead; + } + } + + + /// + /// Thrown when failed to read memory. + /// + public byte[] ReadMemoryExact(IntPtr address, int length) + { + var buffer = new byte[length]; + ReadMemoryExact(address, buffer); + + return buffer; + } + + + /// + /// Thrown when failed to read memory. + /// + public void ReadMemoryExact(IntPtr address, ref T value) where T : unmanaged + { + var span = MemoryMarshal.CreateSpan(ref value, 1); // span should never leave this function since it has unbounded lifetime. + var buffer = MemoryMarshal.AsBytes(span); + + ReadMemoryExact(address, buffer); + } + + + private IntPtr GetPebAddress() + { + unsafe + { + PROCESS_BASIC_INFORMATION info = default; + + var status = Ntdll.NtQueryInformationProcess(Handle, PROCESSINFOCLASS.ProcessBasicInformation, &info, sizeof(PROCESS_BASIC_INFORMATION), (IntPtr*)IntPtr.Zero); + + if (!status.Success) + { + throw new ProcessException($"Could not query information on process. (Status: {status})"); + } + + return info.PebBaseAddress; + } + } + + /// + /// Reads command-line arguments from the process. + /// + public string[] GetProcessArguments() + { + PEB peb = default; + RTL_USER_PROCESS_PARAMETERS procParam = default; + + // Find where the command line is allocated + var pebAddr = GetPebAddress(); + ReadMemoryExact(pebAddr, ref peb); + ReadMemoryExact(peb.ProcessParameters, ref procParam); + + // Read the command line (which is utf16-like string) + var commandLine = ReadMemoryExact(procParam.CommandLine.Buffer, procParam.CommandLine.Length); + + return ParseCommandLineToArguments(commandLine); + } + + /// + /// Returns a time when the process was started. + /// + public DateTime GetCreationTime() + { + if (!Kernel32.GetProcessTimes(Handle, out var creationTime, out var _, out var _, out var _)) + { + ProcessException.ThrowLastOsError(); + } + + return creationTime.ToDateTime(); + } + + private string[] ParseCommandLineToArguments(ReadOnlySpan commandLine) + { + unsafe + { + char** argv; + int argc; + + fixed (byte* pCommandLine = commandLine) + { + argv = Shell32.CommandLineToArgvW(pCommandLine, out argc); + } + + if (argv == null) + { + ProcessException.ThrowLastOsError(); + } + + // NOTE: argv must be deallocated via LocalFree when we're done + try + { + var arguments = new string[argc]; + + for (var i = 0; i < argc; i++) + { + arguments[i] = new string(argv[i]); + } + + return arguments; + } + finally + { + Kernel32.LocalFree(argv); + } + } + } + + public string GetImageFilePath() + { + var buffer = new StringBuilder(300); + + // From docs: + // On input, specifies the size of the lpExeName buffer, in characters. + // On success, receives the number of characters written to the buffer, not including the null-terminating character. + var size = buffer.Capacity; + + if (!Kernel32.QueryFullProcessImageNameW(Handle, 0, buffer, ref size)) + { + ProcessException.ThrowLastOsError(); + } + + return buffer.ToString(); + } } } diff --git a/Dalamud.Bootstrap/OS/Windows/Process.cs b/Dalamud.Bootstrap/OS/Windows/Process.cs deleted file mode 100644 index 082cade9c..000000000 --- a/Dalamud.Bootstrap/OS/Windows/Process.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Text; -using Dalamud.Bootstrap.OS.Windows.Raw; -using Microsoft.Win32.SafeHandles; - -namespace Dalamud.Bootstrap.OS.Windows -{ - /// - /// Provides a thin wrapper around process API. - /// - internal sealed class Process : IDisposable - { - /// - /// A process handle that can be passed to PInvoke. Note that it should never outlive a process object. - /// This handle will be disposed when the process object is disposed. - /// - public IntPtr Handle { get; private set; } - - public Process(IntPtr handle) - { - Handle = handle; - } - - public void Dispose() - { - if (Handle != IntPtr.Zero) - { - Kernel32.CloseHandle(Handle); - } - - Handle = IntPtr.Zero; - } - - /// - /// Reads process memory. - /// - /// - /// A number of bytes that is actually read. - /// - /// - /// Thrown when failed to read memory. - /// - public int ReadMemory(IntPtr address, Span destination) - { - unsafe - { - fixed (byte* pDest = destination) - { - if (!Kernel32.ReadProcessMemory(Handle, address, pDest, (IntPtr)destination.Length, out var bytesRead)) - { - ProcessException.ThrowLastOsError(); - } - - // This is okay because destination will never be longer than int.Max - return bytesRead.ToInt32(); - } - } - } - - /// - /// Reads the exact number of bytes required to fill the buffer. - /// - /// - /// Thrown when failed to read memory. - /// - public void ReadMemoryExact(IntPtr address, Span destination) - { - var totalBytesRead = 0; - - while (totalBytesRead < destination.Length) - { - var bytesRead = ReadMemory(address + totalBytesRead, destination[totalBytesRead..]); - - if (bytesRead == 0) - { - // prolly page fault; there's not much we can do here - var readBeginAddr = address.ToInt64() + totalBytesRead; - var readEndAddr = address.ToInt64() + destination.Length; - - ProcessException.ThrowLastOsError(); - } - - totalBytesRead += bytesRead; - } - } - - - /// - /// Thrown when failed to read memory. - /// - public byte[] ReadMemoryExact(IntPtr address, int length) - { - var buffer = new byte[length]; - ReadMemoryExact(address, buffer); - - return buffer; - } - - - /// - /// Thrown when failed to read memory. - /// - public void ReadMemoryExact(IntPtr address, ref T value) where T : unmanaged - { - var span = MemoryMarshal.CreateSpan(ref value, 1); // span should never leave this function since it has unbounded lifetime. - var buffer = MemoryMarshal.AsBytes(span); - - ReadMemoryExact(address, buffer); - } - - - private IntPtr GetPebAddress() - { - unsafe - { - PROCESS_BASIC_INFORMATION info = default; - - var status = Ntdll.NtQueryInformationProcess(Handle, PROCESSINFOCLASS.ProcessBasicInformation, &info, sizeof(PROCESS_BASIC_INFORMATION), (IntPtr*)IntPtr.Zero); - - if (!status.Success) - { - throw new ProcessException($"Could not query information on process. (Status: {status})"); - } - - return info.PebBaseAddress; - } - } - - /// - /// Reads command-line arguments from the process. - /// - public string[] GetProcessArguments() - { - PEB peb = default; - RTL_USER_PROCESS_PARAMETERS procParam = default; - - // Find where the command line is allocated - var pebAddr = GetPebAddress(); - ReadMemoryExact(pebAddr, ref peb); - ReadMemoryExact(peb.ProcessParameters, ref procParam); - - // Read the command line (which is utf16-like string) - var commandLine = ReadMemoryExact(procParam.CommandLine.Buffer, procParam.CommandLine.Length); - - return ParseCommandLineToArguments(commandLine); - } - - /// - /// Returns a time when the process was started. - /// - public DateTime GetCreationTime() - { - if (!Kernel32.GetProcessTimes(Handle, out var creationTime, out var _, out var _, out var _)) - { - ProcessException.ThrowLastOsError(); - } - - return creationTime.ToDateTime(); - } - - private string[] ParseCommandLineToArguments(ReadOnlySpan commandLine) - { - unsafe - { - char** argv; - int argc; - - fixed (byte* pCommandLine = commandLine) - { - argv = Shell32.CommandLineToArgvW(pCommandLine, out argc); - } - - if (argv == null) - { - ProcessException.ThrowLastOsError(); - } - - // NOTE: argv must be deallocated via LocalFree when we're done - try - { - var arguments = new string[argc]; - - for (var i = 0; i < argc; i++) - { - arguments[i] = new string(argv[i]); - } - - return arguments; - } - finally - { - Kernel32.LocalFree(argv); - } - } - } - - public string GetImageFilePath() - { - var buffer = new StringBuilder(300); - - // From docs: - // On input, specifies the size of the lpExeName buffer, in characters. - // On success, receives the number of characters written to the buffer, not including the null-terminating character. - var size = buffer.Capacity; - - if (!Kernel32.QueryFullProcessImageNameW(Handle, 0, buffer, ref size)) - { - ProcessException.ThrowLastOsError(); - } - - return buffer.ToString(); - } - } -} diff --git a/Dalamud.Bootstrap/OS/Windows/Raw/Advapi32.cs b/Dalamud.Bootstrap/OS/Windows/Raw/Advapi32.cs index f95a3dbf4..c36db8320 100644 --- a/Dalamud.Bootstrap/OS/Windows/Raw/Advapi32.cs +++ b/Dalamud.Bootstrap/OS/Windows/Raw/Advapi32.cs @@ -18,6 +18,9 @@ namespace Dalamud.Bootstrap.OS.Windows.Raw public static extern void BuildExplicitAccessWithNameW(out EXPLICIT_ACCESS_W pExplicitAccess, string pTrusteeName, uint AccessPermissions, ACCESS_MODE AccessMode, uint Inheritance); [DllImport(Name, CallingConvention = CallingConvention.Winapi)] - public static extern uint GetSecurityInfo(IntPtr handle, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo); + public static extern uint GetSecurityInfo(IntPtr handle, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, SID** ppsidOwner, SID** ppsidGroup, ACL** ppDacl, ACL** ppSacl, SECURITY_DESCRIPTOR** ppSecurityDescriptor); + + [DllImport(Name, CallingConvention = CallingConvention.Winapi)] + public static extern uint SetSecurityInfo(IntPtr handle, SE_OBJECT_TYPE _OBJECT_TYPE, SECURITY_INFORMATION SecurityInfo, SID* psidOwner, SID* psidGroup, ACL* pDacl, ACL* pSacl); } } diff --git a/Dalamud.Bootstrap/OS/Windows/Raw/Constants.cs b/Dalamud.Bootstrap/OS/Windows/Raw/Constants.cs index d2f831795..ea1e59648 100644 --- a/Dalamud.Bootstrap/OS/Windows/Raw/Constants.cs +++ b/Dalamud.Bootstrap/OS/Windows/Raw/Constants.cs @@ -24,6 +24,8 @@ namespace Dalamud.Bootstrap.OS.Windows.Raw PROCESS_QUERY_INFORMATION = 0x400, PROCESS_SUSPEND_RESUME = 0x800, PROCESS_QUERY_LIMITED_INFORMATION = 0x1000, + READ_CONTROL = 0x20000, + WRITE_DAC = 0x40000, SYNCHRONIZE = 0x100000, } @@ -124,4 +126,23 @@ namespace Dalamud.Bootstrap.OS.Windows.Raw SE_REGISTRY_WOW64_32KEY, SE_REGISTRY_WOW64_64KEY } + + internal enum SECURITY_INFORMATION : uint + { + // https://docs.rs/winapi/0.3.8/src/winapi/um/winnt.rs.html#2880 + OWNER_SECURITY_INFORMATION = 0x00000001, + GROUP_SECURITY_INFORMATION = 0x00000002, + DACL_SECURITY_INFORMATION = 0x00000004, + SACL_SECURITY_INFORMATION = 0x00000008, + LABEL_SECURITY_INFORMATION = 0x00000010, + ATTRIBUTE_SECURITY_INFORMATION = 0x00000020, + SCOPE_SECURITY_INFORMATION = 0x00000040, + PROCESS_TRUST_LABEL_SECURITY_INFORMATION = 0x00000080, + ACCESS_FILTER_SECURITY_INFORMATION = 0x00000100, + BACKUP_SECURITY_INFORMATION = 0x00010000, + UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000, + UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000, + PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000, + PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000, + } } diff --git a/Dalamud.Bootstrap/OS/Windows/Raw/Structures.cs b/Dalamud.Bootstrap/OS/Windows/Raw/Structures.cs index 6492bf4d7..16cb4ccfe 100644 --- a/Dalamud.Bootstrap/OS/Windows/Raw/Structures.cs +++ b/Dalamud.Bootstrap/OS/Windows/Raw/Structures.cs @@ -131,7 +131,7 @@ namespace Dalamud.Bootstrap.OS.Windows.Raw public IntPtr Dacl; } - [StructLayout(LayoutKind.Explicit)] + [StructLayout(LayoutKind.Sequential)] internal unsafe struct TRUSTEE_W { public TRUSTEE_W* pMultipleTrustee; @@ -144,19 +144,36 @@ namespace Dalamud.Bootstrap.OS.Windows.Raw [StructLayout(LayoutKind.Sequential)] internal struct EXPLICIT_ACCESS_W { - uint grfAccessPermissions; - ACCESS_MODE grfAccessMode; - uint grfInheritance; - TRUSTEE_W Trustee; + public uint grfAccessPermissions; + public ACCESS_MODE grfAccessMode; + public uint grfInheritance; + public TRUSTEE_W Trustee; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SID + { + // NOTE: this structure actually has no fixed length and therefore should not be used directly. + // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid + byte Revision; + byte SubAuthorityCount; + SID_IDENTIFIER_AUTHORITY IdentifierAuthority; + uint SubAuthority; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SID_IDENTIFIER_AUTHORITY + { + public unsafe fixed byte Value[6]; } [StructLayout(LayoutKind.Sequential)] internal struct ACL { - byte AclRevision; - byte Sbz1; - ushort AclSize; - ushort AceCount; - ushort Sbz2; + public byte AclRevision; + public byte Sbz1; + public ushort AclSize; + public ushort AceCount; + public ushort Sbz2; } } diff --git a/Dalamud.Bootstrap/OS/Windows/RelaxedProcessHandle.cs b/Dalamud.Bootstrap/OS/Windows/RelaxedProcessHandle.cs deleted file mode 100644 index f5f3e0b12..000000000 --- a/Dalamud.Bootstrap/OS/Windows/RelaxedProcessHandle.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using Dalamud.Bootstrap.OS.Windows.Raw; -using Microsoft.Win32.SafeHandles; - -namespace Dalamud.Bootstrap.Windows -{ - internal sealed class RelaxedProcessHandle : IDisposable - { - private SafeProcessHandle m_handle; - - private RelaxedProcessHandle(SafeProcessHandle handle) - { - m_handle = handle; - } - - public void Dispose() - { - - } - - /// - /// - /// - /// - /// - /// - /// - /// - public static RelaxedProcessHandle Create(SafeProcessHandle handle, PROCESS_ACCESS_RIGHTS access) - { - - - return new RelaxedProcessHandle(handle); - } - } -} diff --git a/Dalamud.Bootstrap/OS/ProcessException.cs b/Dalamud.Bootstrap/ProcessException.cs similarity index 100% rename from Dalamud.Bootstrap/OS/ProcessException.cs rename to Dalamud.Bootstrap/ProcessException.cs