using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using WhiteMagic.Native; using WhiteMagic.ThreadEnvironment; namespace WhiteMagic.Thread; /// /// A handle to an existing thread in the target process. Provides suspend/resume, /// context read/write, and TEB query. /// public sealed class RemoteThread : IDisposable { private readonly MemoryBase _memory; private readonly SafeMemoryHandle _handle; private readonly int _id; private bool _disposed; /// The operating-system identifier of this thread. public int Id => _id; /// The native thread handle. internal SafeMemoryHandle Handle => _handle; internal RemoteThread(MemoryBase memory, int threadId, SafeMemoryHandle handle) { _memory = memory ?? throw new ArgumentNullException(nameof(memory)); _id = threadId; _handle = handle ?? throw new ArgumentNullException(nameof(handle)); } /// /// Opens the thread specified by in the target process /// represented by . /// public RemoteThread(MemoryBase memory, int threadId) : this(memory, threadId, OpenHandle(threadId)) { } private static SafeMemoryHandle OpenHandle(int threadId) { if (threadId <= 0) throw new ArgumentException("Thread ID must be positive.", nameof(threadId)); const ThreadAccess requiredAccess = ThreadAccess.SuspendResume | ThreadAccess.GetContext | ThreadAccess.SetContext | ThreadAccess.QueryInformation; SafeMemoryHandle handle = NativeMethods.OpenThread(requiredAccess, false, threadId); if (handle.IsInvalid) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"OpenThread failed for thread {threadId}: error {error}."); } return handle; } /// /// Suspends the thread and returns its previous suspend count. /// public uint Suspend() { uint result = NativeMethods.SuspendThread(_handle); if (result == 0xFFFFFFFF) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"SuspendThread failed for thread {_id}: error {error}."); } return result; } /// /// Resumes the thread and returns its previous suspend count. /// public uint Resume() { uint result = NativeMethods.ResumeThread(_handle); if (result == 0xFFFFFFFF) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"ResumeThread failed for thread {_id}: error {error}."); } return result; } /// /// Reads the 64-bit native context of the thread. Valid only for 64-bit targets. /// public unsafe void GetContext64(out Context64 context) { nint size = Marshal.SizeOf(); void* ptr = NativeMemory.AlignedAlloc((nuint)size, 16); try { Unsafe.InitBlock(ptr, 0, (uint)size); ((Context64*)ptr)->ContextFlags = ContextFlags.Amd64Full; if (!NativeMethods.GetThreadContext(_handle, ref *(Context64*)ptr)) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"GetThreadContext failed for thread {_id}: error {error}."); } context = *(Context64*)ptr; } finally { NativeMemory.AlignedFree(ptr); } } /// /// Writes the 64-bit native context of the thread. Valid only for 64-bit targets. /// public unsafe void SetContext64(ref Context64 context) { nint size = Marshal.SizeOf(); void* ptr = NativeMemory.AlignedAlloc((nuint)size, 16); try { *(Context64*)ptr = context; if (!NativeMethods.SetThreadContext(_handle, ref *(Context64*)ptr)) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"SetThreadContext failed for thread {_id}: error {error}."); } } finally { NativeMemory.AlignedFree(ptr); } } /// /// Reads the 32-bit native context of the thread. Valid only for 32-bit targets. /// public void GetContext32(out Context32 context) { if (_memory.Is64Bit) { context = default; throw new InvalidOperationException( "Use GetContext64 for 64-bit targets; GetContext32 is valid for 32-bit targets only."); } context = new Context32 { ContextFlags = ContextFlags.X86Full }; if (!NativeMethods.GetThreadContext(_handle, ref context)) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"GetThreadContext failed for thread {_id}: error {error}."); } } /// /// Writes the 32-bit native context of the thread. Valid only for 32-bit targets. /// public void SetContext32(ref Context32 context) { if (_memory.Is64Bit) throw new InvalidOperationException( "Use SetContext64 for 64-bit targets; SetContext32 is valid for 32-bit targets only."); if (!NativeMethods.SetThreadContext(_handle, ref context)) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException($"SetThreadContext failed for thread {_id}: error {error}."); } } /// /// Returns a managed reader for this thread's Thread Environment Block. /// public ManagedTeb GetTeb() { return new ManagedTeb(_memory, _id); } /// public void Dispose() { if (!_disposed) { _disposed = true; _handle.Dispose(); } } }