mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-31 21:03:43 +01:00
Add verifier for hook signatures
This one is real bad, so we should make sure everyone using a canonical signature
This commit is contained in:
parent
3eb65c85c0
commit
a56d2cf40b
4 changed files with 157 additions and 0 deletions
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||||
using Dalamud.Common;
|
using Dalamud.Common;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Hooking.Internal.Verification;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Storage;
|
using Dalamud.Storage;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
@ -73,6 +74,11 @@ internal sealed unsafe class Dalamud : IServiceType
|
||||||
scanner,
|
scanner,
|
||||||
Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride));
|
Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride));
|
||||||
|
|
||||||
|
using (Timings.Start("HookVerifier Init"))
|
||||||
|
{
|
||||||
|
HookVerifier.Initialize(scanner);
|
||||||
|
}
|
||||||
|
|
||||||
// Set up FFXIVClientStructs
|
// Set up FFXIVClientStructs
|
||||||
this.SetupClientStructsResolver(cacheDir);
|
this.SetupClientStructsResolver(cacheDir);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Hooking.Internal;
|
using Dalamud.Hooking.Internal;
|
||||||
|
using Dalamud.Hooking.Internal.Verification;
|
||||||
|
|
||||||
namespace Dalamud.Hooking;
|
namespace Dalamud.Hooking;
|
||||||
|
|
||||||
|
|
@ -230,6 +231,8 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
if (EnvironmentConfiguration.DalamudForceMinHook)
|
if (EnvironmentConfiguration.DalamudForceMinHook)
|
||||||
useMinHook = true;
|
useMinHook = true;
|
||||||
|
|
||||||
|
HookVerifier.Verify<T>(procAddress);
|
||||||
|
|
||||||
procAddress = HookManager.FollowJmp(procAddress);
|
procAddress = HookManager.FollowJmp(procAddress);
|
||||||
if (useMinHook)
|
if (useMinHook)
|
||||||
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking.Internal.Verification;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exception thrown when a provided delegate for a hook does not match a known delegate.
|
||||||
|
/// </summary>
|
||||||
|
public class HookVerificationException : Exception
|
||||||
|
{
|
||||||
|
private HookVerificationException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="HookVerificationException"/> exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The address of the function that is being hooked.</param>
|
||||||
|
/// <param name="passed">The delegate passed by the user.</param>
|
||||||
|
/// <param name="enforced">The delegate we think is correct.</param>
|
||||||
|
/// <param name="message">Additional context to show to the user.</param>
|
||||||
|
/// <returns>The created exception.</returns>
|
||||||
|
internal static HookVerificationException Create(IntPtr address, Type passed, Type enforced, string message)
|
||||||
|
{
|
||||||
|
return new HookVerificationException(
|
||||||
|
$"Hook verification failed for address 0x{address.ToInt64():X}\n\n" +
|
||||||
|
$"Why: {message}\n" +
|
||||||
|
$"Passed Delegate: {GetSignature(passed)}\n" +
|
||||||
|
$"Correct Delegate: {GetSignature(enforced)}\n\n" +
|
||||||
|
"The hook delegate must exactly match the provided signature to prevent memory corruption and wrong data passed to originals.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSignature(Type delegateType)
|
||||||
|
{
|
||||||
|
var method = delegateType.GetMethod("Invoke");
|
||||||
|
if (method == null) return delegateType.Name;
|
||||||
|
|
||||||
|
var parameters = string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name));
|
||||||
|
return $"{method.ReturnType.Name} ({parameters})";
|
||||||
|
}
|
||||||
|
}
|
||||||
107
Dalamud/Hooking/Internal/Verification/HookVerifier.cs
Normal file
107
Dalamud/Hooking/Internal/Verification/HookVerifier.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking.Internal.Verification;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Global utility that can verify whether hook delegates are correctly declared.
|
||||||
|
/// Initialized out-of-band, since Hook is instantiated all over the place without a service, so this cannot be
|
||||||
|
/// a service either.
|
||||||
|
/// </summary>
|
||||||
|
internal static class HookVerifier
|
||||||
|
{
|
||||||
|
private static readonly ModuleLog Log = new("HookVerifier");
|
||||||
|
|
||||||
|
private static readonly VerificationEntry[] ToVerify =
|
||||||
|
[
|
||||||
|
new(
|
||||||
|
"ActorControlSelf",
|
||||||
|
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
|
||||||
|
typeof(ActorControlSelfDelegate),
|
||||||
|
"Signature changed in Patch 7.4") // 7.4 (new parameters)
|
||||||
|
];
|
||||||
|
|
||||||
|
private delegate void ActorControlSelfDelegate(uint category, uint eventId, uint param1, uint param2, uint param3, uint param4, uint param5, uint param6, uint param7, uint param8, ulong targetId, byte param9);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="HookVerifier"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">Process to scan in.</param>
|
||||||
|
public static void Initialize(TargetSigScanner scanner)
|
||||||
|
{
|
||||||
|
foreach (var entry in ToVerify)
|
||||||
|
{
|
||||||
|
if (!scanner.TryScanText(entry.Signature, out var address))
|
||||||
|
{
|
||||||
|
Log.Error("Could not resolve signature for hook {Name} ({Sig})", entry.Name, entry.Signature);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Address = address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify the hook with the provided address and exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">The address of the function we are hooking.</param>
|
||||||
|
/// <typeparam name="T">The delegate type passed by the creator of the hook.</typeparam>
|
||||||
|
/// <exception cref="HookVerificationException">Exception thrown when we think the hook is not correctly declared.</exception>
|
||||||
|
public static void Verify<T>(IntPtr address) where T : Delegate
|
||||||
|
{
|
||||||
|
var entry = ToVerify.FirstOrDefault(x => x.Address == address);
|
||||||
|
|
||||||
|
// Nothing to verify for this hook?
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var passedType = typeof(T);
|
||||||
|
|
||||||
|
// Directly compare delegates
|
||||||
|
if (passedType == entry.TargetDelegateType)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var passedInvoke = passedType.GetMethod("Invoke")!;
|
||||||
|
var enforcedInvoke = entry.TargetDelegateType.GetMethod("Invoke")!;
|
||||||
|
|
||||||
|
// Compare Return Type
|
||||||
|
var mismatch = passedInvoke.ReturnType != enforcedInvoke.ReturnType;
|
||||||
|
|
||||||
|
// Compare Parameter Count
|
||||||
|
var passedParams = passedInvoke.GetParameters();
|
||||||
|
var enforcedParams = enforcedInvoke.GetParameters();
|
||||||
|
|
||||||
|
if (passedParams.Length != enforcedParams.Length)
|
||||||
|
{
|
||||||
|
mismatch = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Compare Parameter Types
|
||||||
|
for (var i = 0; i < passedParams.Length; i++)
|
||||||
|
{
|
||||||
|
if (passedParams[i].ParameterType != enforcedParams[i].ParameterType)
|
||||||
|
{
|
||||||
|
mismatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mismatch)
|
||||||
|
{
|
||||||
|
throw HookVerificationException.Create(address, passedType, entry.TargetDelegateType, entry.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record VerificationEntry(string Name, string Signature, Type TargetDelegateType, string Message)
|
||||||
|
{
|
||||||
|
public nint Address { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue