diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index dddf77ad6..b2ad61f79 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -51,7 +51,8 @@ - IDE1006;CS1591;CS1701;CS1702 + IDE0003;IDE0044;IDE1006;CS1591;CS1701;CS1702 + @@ -84,6 +85,5 @@ - diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index eff8b81a9..3a21473c3 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -8,7 +8,6 @@ using System.Text; using System.Threading; using Dalamud.Game; -using Dalamud.Interface.Internal; using Newtonsoft.Json; using Reloaded.Memory.Buffers; using Serilog; @@ -131,7 +130,6 @@ namespace Dalamud.Injector Log.Logger = new LoggerConfiguration() .WriteTo.Async(a => a.File(logPath)) - .WriteTo.Sink(SerilogEventSink.Instance) .MinimumLevel.ControlledBy(levelSwitch) .CreateLogger(); } diff --git a/Dalamud.Injector/Injector.cs b/Dalamud.Injector/Injector.cs index 3f9c14046..20a668047 100644 --- a/Dalamud.Injector/Injector.cs +++ b/Dalamud.Injector/Injector.cs @@ -11,6 +11,7 @@ using PeNet.Header.Pe; using Reloaded.Memory.Buffers; using Reloaded.Memory.Sources; using Reloaded.Memory.Utilities; +using Serilog; using static Dalamud.Injector.NativeFunctions; using static Iced.Intel.AssemblerRegisters; @@ -27,7 +28,7 @@ namespace Dalamud.Injector private readonly Process targetProcess; private readonly ExternalMemory extMemory; private readonly CircularBuffer circularBuffer; - private readonly PrivateMemoryBuffer privateBuffer; + private readonly PrivateMemoryBuffer memoryBuffer; private IntPtr loadLibraryShellPtr; private IntPtr loadLibraryRetPtr; @@ -45,7 +46,7 @@ namespace Dalamud.Injector this.extMemory = new ExternalMemory(targetProcess); this.circularBuffer = new CircularBuffer(4096, this.extMemory); - this.privateBuffer = new MemoryBufferHelper(targetProcess).CreatePrivateMemoryBuffer(4096); + this.memoryBuffer = new MemoryBufferHelper(targetProcess).CreatePrivateMemoryBuffer(4096); using var kernel32Module = this.GetProcessModule("KERNEL32.DLL"); var kernel32PeFile = new PeFile(kernel32Module.FileName); @@ -67,7 +68,7 @@ namespace Dalamud.Injector this.targetProcess?.Dispose(); this.circularBuffer?.Dispose(); - this.privateBuffer?.Dispose(); + this.memoryBuffer?.Dispose(); } /// @@ -82,6 +83,8 @@ namespace Dalamud.Injector if (lpParameter == IntPtr.Zero) throw new Exception("Unable to allocate LoadLibraryW parameter"); + Log.Verbose($"CreateRemoteThread: call 0x{this.loadLibraryShellPtr.ToInt64():X} with 0x{lpParameter.ToInt64():X}"); + var threadHandle = CreateRemoteThread( this.targetProcess.Handle, IntPtr.Zero, @@ -93,7 +96,7 @@ namespace Dalamud.Injector _ = WaitForSingleObject(threadHandle, uint.MaxValue); - this.extMemory.Read(this.loadLibraryRetPtr, out address); + address = this.extMemory.Read(this.loadLibraryRetPtr); if (address == IntPtr.Zero) throw new Exception($"Error calling LoadLibraryW with {modulePath}"); @@ -107,7 +110,8 @@ namespace Dalamud.Injector /// Address to the function. public void GetFunctionAddress(IntPtr module, string functionName, out IntPtr address) { - var getProcAddressParams = new GetProcAddressParams(module, this.WriteNullTerminatedASCIIString(functionName)); + var functionNamePtr = this.WriteNullTerminatedASCIIString(functionName); + var getProcAddressParams = new GetProcAddressParams(module, functionNamePtr); var lpParameter = this.circularBuffer.Add(ref getProcAddressParams); if (lpParameter == IntPtr.Zero) @@ -151,19 +155,25 @@ namespace Dalamud.Injector _ = WaitForSingleObject(threadHandle, uint.MaxValue); GetExitCodeThread(threadHandle, out exitCode); + + CloseHandle(threadHandle); } private void SetupLoadLibrary(ProcessModule kernel32Module, ExportFunction[] kernel32Exports) { var offset = this.GetExportedFunctionOffset(kernel32Exports, "LoadLibraryW"); var functionAddr = kernel32Module.BaseAddress + (int)offset; - var functionPtr = this.privateBuffer.Add(ref functionAddr); + Log.Verbose($"LoadLibraryW: 0x{functionAddr.ToInt64():X}"); + + var functionPtr = this.memoryBuffer.Add(ref functionAddr); + Log.Verbose($"LoadLibraryPtr: 0x{functionPtr.ToInt64():X}"); if (functionPtr == IntPtr.Zero) throw new Exception("Unable to allocate LoadLibraryW function ptr"); - var dummy = 0L; - this.loadLibraryRetPtr = this.privateBuffer.Add(ref dummy); + var dummy = IntPtr.Zero; + this.loadLibraryRetPtr = this.memoryBuffer.Add(ref dummy); + Log.Verbose($"LoadLibraryRetPtr: 0x{this.loadLibraryRetPtr.ToInt64():X}"); if (this.loadLibraryRetPtr == IntPtr.Zero) throw new Exception("Unable to allocate LoadLibraryW return value"); @@ -180,23 +190,41 @@ namespace Dalamud.Injector asm.ret(); // ret // Restore stack ptr. (Callee cleanup) var bytes = this.Assemble(asm); - this.loadLibraryShellPtr = this.privateBuffer.Add(bytes); + this.loadLibraryShellPtr = this.memoryBuffer.Add(bytes); + Log.Verbose($"LoadLibraryShellPtr: 0x{this.loadLibraryShellPtr.ToInt64():X}"); if (this.loadLibraryShellPtr == IntPtr.Zero) throw new Exception("Unable to allocate LoadLibraryW shellcode"); + + this.extMemory.ChangePermission(this.loadLibraryShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE); + +#if DEBUG + var outFunctionPtr = this.extMemory.Read(functionPtr); + Log.Verbose($"LoadLibraryPtr: {this.GetResultMarker(outFunctionPtr == functionAddr)}"); + + var outRetPtr = this.extMemory.Read(this.loadLibraryRetPtr); + Log.Verbose($"LoadLibraryRet: {this.GetResultMarker(dummy == outRetPtr)}"); + + this.extMemory.ReadRaw(this.loadLibraryShellPtr, out var outBytes, bytes.Length); + Log.Verbose($"LoadLibraryShellPtr: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))}"); +#endif } private void SetupGetProcAddress(ProcessModule kernel32Module, ExportFunction[] kernel32Exports) { var offset = this.GetExportedFunctionOffset(kernel32Exports, "GetProcAddress"); var functionAddr = kernel32Module.BaseAddress + (int)offset; - var functionPtr = this.privateBuffer.Add(ref functionAddr); + Log.Verbose($"GetProcAddress: 0x{functionAddr.ToInt64():X}"); + + var functionPtr = this.memoryBuffer.Add(ref functionAddr); + Log.Verbose($"GetProcAddressPtr: 0x{functionPtr.ToInt64():X}"); if (functionPtr == IntPtr.Zero) throw new Exception("Unable to allocate GetProcAddress function ptr"); - var dummy = 0L; - this.getProcAddressRetPtr = this.privateBuffer.Add(ref dummy); + var dummy = IntPtr.Zero; + this.getProcAddressRetPtr = this.memoryBuffer.Add(ref dummy); + Log.Verbose($"GetProcAddressRetPtr: 0x{this.loadLibraryRetPtr.ToInt64():X}"); if (this.getProcAddressRetPtr == IntPtr.Zero) throw new Exception("Unable to allocate GetProcAddress return value"); @@ -215,10 +243,24 @@ namespace Dalamud.Injector asm.ret(); // ret // Restore stack ptr. (Callee cleanup) var bytes = this.Assemble(asm); - this.getProcAddressShellPtr = this.privateBuffer.Add(bytes); + this.getProcAddressShellPtr = this.memoryBuffer.Add(bytes); + Log.Verbose($"GetProcAddressShellPtr: 0x{this.getProcAddressShellPtr.ToInt64():X}"); if (this.getProcAddressShellPtr == IntPtr.Zero) throw new Exception("Unable to allocate GetProcAddress shellcode"); + + this.extMemory.ChangePermission(this.getProcAddressShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE); + +#if DEBUG + var outFunctionPtr = this.extMemory.Read(functionPtr); + Log.Verbose($"GetProcAddressPtr: {this.GetResultMarker(outFunctionPtr == functionAddr)}"); + + var outRetPtr = this.extMemory.Read(this.loadLibraryRetPtr); + Log.Verbose($"GetProcAddressRet: {this.GetResultMarker(dummy == outRetPtr)}"); + + this.extMemory.ReadRaw(this.getProcAddressShellPtr, out var outBytes, bytes.Length); + Log.Verbose($"GetProcAddressShellPtr: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))}"); +#endif } private byte[] Assemble(Assembler assembler) @@ -264,28 +306,42 @@ namespace Dalamud.Injector return exportFunction.Address; } - private IntPtr WriteNullTerminatedASCIIString(string libraryPath) + private IntPtr WriteNullTerminatedASCIIString(string value) { - var libraryNameBytes = Encoding.ASCII.GetBytes(libraryPath + '\0'); - var value = this.circularBuffer.Add(libraryNameBytes); + var bytes = Encoding.ASCII.GetBytes(value + '\0'); + var address = this.circularBuffer.Add(bytes); - if (value == IntPtr.Zero) + if (address == IntPtr.Zero) throw new Exception("Unable to write ASCII string to buffer"); - return value; +#if DEBUG + this.extMemory.ReadRaw(address, out var outBytes, bytes.Length); + Log.Verbose($"WriteASCII: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address.ToInt64():X} {value}"); +#endif + + return address; } - private IntPtr WriteNullTerminatedUnicodeString(string libraryPath) + private IntPtr WriteNullTerminatedUnicodeString(string value) { - var libraryNameBytes = Encoding.Unicode.GetBytes(libraryPath + '\0'); - var value = this.circularBuffer.Add(libraryNameBytes); + var bytes = Encoding.Unicode.GetBytes(value + '\0'); + var address = this.circularBuffer.Add(bytes); - if (value == IntPtr.Zero) + if (address == IntPtr.Zero) throw new Exception("Unable to write Unicode string to buffer"); - return value; +#if DEBUG + this.extMemory.ReadRaw(address, out var outBytes, bytes.Length); + Log.Verbose($"WriteUnicode: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address.ToInt64():X} {value}"); +#endif + + return address; } +#if DEBUG + private string GetResultMarker(bool result) => result ? "✅" : "❌"; +#endif + [StructLayout(LayoutKind.Sequential)] private struct GetProcAddressParams { diff --git a/Dalamud.Injector/NativeFunctions.cs b/Dalamud.Injector/NativeFunctions.cs index 46da6728c..b9ed9f33d 100644 --- a/Dalamud.Injector/NativeFunctions.cs +++ b/Dalamud.Injector/NativeFunctions.cs @@ -582,6 +582,22 @@ namespace Dalamud.Injector WAIT_FAILED = 0xFFFFFFF, } + /// + /// Closes an open object handle. + /// + /// + /// A valid handle to an open object. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error + /// information, call GetLastError. If the application is running under a debugger, the function will throw an exception if it receives + /// either a handle value that is not valid or a pseudo-handle value. This can happen if you close a handle twice, or if you call + /// CloseHandle on a handle returned by the FindFirstFile function instead of calling the FindClose function. + /// + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + /// /// Creates a thread that runs in the virtual address space of another process. Use the CreateRemoteThreadEx function /// to create a thread that runs in the virtual address space of another process and optionally specify extended attributes. @@ -651,6 +667,34 @@ namespace Dalamud.Injector [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode); + /// + /// Opens an existing local process object. + /// + /// + /// The access to the process object. This access right is checked against the security descriptor for the process. This parameter can be one or + /// more of the process access rights. If the caller has enabled the SeDebugPrivilege privilege, the requested access is granted regardless of the + /// contents of the security descriptor. + /// + /// + /// If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do not inherit this handle. + /// + /// + /// The identifier of the local process to be opened. If the specified process is the System Idle Process(0x00000000), the function fails and the + /// last error code is ERROR_INVALID_PARAMETER.If the specified process is the System process or one of the Client Server Run-Time Subsystem(CSRSS) + /// processes, this function fails and the last error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from + /// opening them. If you are using GetCurrentProcessId as an argument to this function, consider using GetCurrentProcess instead of OpenProcess, for + /// improved performance. + /// + /// + /// If the function succeeds, the return value is an open handle to the specified process. + /// If the function fails, the return value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr OpenProcess( + ProcessAccessFlags dwDesiredAccess, + bool bInheritHandle, + int dwProcessId); + /// /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex. /// Reserves, commits, or changes the state of a region of memory within the virtual address space of a specified process. diff --git a/Dalamud.Injector/RemotePinnedData.cs b/Dalamud.Injector/RemotePinnedData.cs deleted file mode 100644 index fc713639f..000000000 --- a/Dalamud.Injector/RemotePinnedData.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text; - -using static Dalamud.Injector.NativeFunctions; - -namespace Dalamud.Injector -{ - /// - /// Pin an arbitrary string to a remote process. - /// - internal class RemotePinnedData : IDisposable - { - private readonly Process process; - private readonly byte[] data; - private readonly IntPtr allocAddr; - - /// - /// Initializes a new instance of the class. - /// - /// Process to write in. - /// Data to write. - public unsafe RemotePinnedData(Process process, byte[] data) - { - this.process = process; - this.data = data; - - this.allocAddr = VirtualAllocEx( - this.process.Handle, - IntPtr.Zero, - this.data.Length, - AllocationType.Commit, - MemoryProtection.ReadWrite); - - if (this.allocAddr == IntPtr.Zero || Marshal.GetLastWin32Error() != 0) - { - throw new Exception("Error allocating memory"); - } - - var result = WriteProcessMemory( - this.process.Handle, - this.allocAddr, - this.data, - this.data.Length, - out _); - - if (!result || Marshal.GetLastWin32Error() != 0) - { - throw new Exception("Error writing memory"); - } - } - - /// - /// Gets the address of the pinned data. - /// - public IntPtr Address => this.allocAddr; - - /// - public void Dispose() - { - if (this.allocAddr == IntPtr.Zero) - { - return; - } - - var result = VirtualFreeEx( - this.process.Handle, - this.allocAddr, - 0, - AllocationType.Release); - - if (!result || Marshal.GetLastWin32Error() != 0) - { - throw new Exception("Error freeing memory"); - } - } - } -}