- PeHeaderParser: split export forwarders on the FIRST dot (IndexOf), not the last. A forwarder is "Module.Function" and the module name has no extension, so the last-dot split misparsed export names that themselves contain a dot. - PeHeaderParser: document that API-set (api-ms-win-*/ext-ms-*) and ordinal forwarders are unsupported and should be resolved via the OS loader. - RemoteFunction.CreateDelegate now throws InvalidOperationException unless the session is in-process; an external target's address is not host-mapped and a delegate to it would access-violate on invocation. Tests cover both paths. - Reword the SSE-payload comment: the 16-byte scratch sits below the saved return address, which the aligned store leaves intact (it never overwrote it). Tests: 223 passing, 4 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
3.0 KiB
C#
74 lines
3.0 KiB
C#
using System.Threading.Tasks;
|
|
using WhiteMagic.Assembly;
|
|
using WhiteMagic.Execution;
|
|
|
|
namespace WhiteMagic;
|
|
|
|
/// <summary>
|
|
/// An exported function resolved in the target process, obtained via
|
|
/// <c>magic["module"]["function"]</c>. Executes through one of the session's execution
|
|
/// strategies.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The default <see cref="Execute{T}"/> path uses the always-available
|
|
/// <see cref="RemoteThreadExecutor"/> (<c>CreateRemoteThread</c>), which is safe for
|
|
/// thread-agnostic exports. For a call that touches single-threaded target state, obtain
|
|
/// the <see cref="Address"/> and route it through a <see cref="MainThreadPump"/>, or use
|
|
/// <see cref="CreateDelegate{TDelegate}"/> when running in-process.
|
|
/// </remarks>
|
|
public sealed class RemoteFunction
|
|
{
|
|
private readonly Magic _magic;
|
|
|
|
/// <summary>The export name this function was resolved from.</summary>
|
|
public string Name { get; }
|
|
|
|
/// <summary>The absolute address of the function in the target process.</summary>
|
|
public IntPtr Address { get; }
|
|
|
|
internal RemoteFunction(Magic magic, string name, IntPtr address)
|
|
{
|
|
_magic = magic;
|
|
Name = name;
|
|
Address = address;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls the function via a remote thread and returns its result cast to
|
|
/// <typeparamref name="T"/>.
|
|
/// </summary>
|
|
/// <param name="convention">The calling convention (ignored on x64 targets).</param>
|
|
/// <param name="args">Arguments to pass; primitives, pointers, enums, strings and
|
|
/// structs are supported.</param>
|
|
public T Execute<T>(CallConvention convention, params object?[] args)
|
|
{
|
|
return _magic.RemoteThread.Execute<T>(Address, convention, args);
|
|
}
|
|
|
|
/// <summary>Asynchronous variant of <see cref="Execute{T}"/>.</summary>
|
|
public Task<T> ExecuteAsync<T>(CallConvention convention, params object?[] args)
|
|
{
|
|
return _magic.RemoteThread.ExecuteAsync<T>(Address, convention, args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a managed delegate bound to this function for the in-process scenario.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The session is not in-process. The
|
|
/// resolved <see cref="Address"/> lives in the target process; a delegate to it would
|
|
/// access-violate when invoked from the host, so this is rejected for external sessions.
|
|
/// Use <see cref="Execute{T}"/> (remote thread) for external targets.</exception>
|
|
public TDelegate CreateDelegate<TDelegate>() where TDelegate : Delegate
|
|
{
|
|
if (_magic.Memory is not InProcessReader)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"CreateDelegate is only valid for an in-process session (Magic.OpenInProcess). " +
|
|
"The function address is not mapped into the host process for an external target; " +
|
|
"use Execute<T> to call it via a remote thread.");
|
|
}
|
|
|
|
return new InProcessInvoker(_magic.Memory).CreateFunction<TDelegate>(Address);
|
|
}
|
|
}
|